Skip to content

Commit

Permalink
Make react-native link play nicely with CocoaPods-based iOS projects.
Browse files Browse the repository at this point in the history
Summary:
The core React Native codebase already has full support for CocoaPods. However, `react-native link` doesn’t play nicely with CocoaPods, so installing third-party libs from the RN ecosystem is really hard.

This change will allow to link projects that contains its own `.podspec` file to CocoaPods-based projects. In case `link` detect `Podfile` in `iOS` directory, it will look for related `.podspec` file in linked project directory, and add it to `Podfile`. If `Podfile` and `.podspec` files are not present, it will fall back to previous implementation.

**Test Plan**
1. Build a React Native project where the iOS part uses CocoaPods to manage its dependencies. The most common scenario here is to have React Native be a Pod dependency, among others.
2. Install a RN-related library, that contains `.podspec` file, with `react-native link` (as an example it could be: [react-native-maps](https://github.com/airbnb/react-native-maps)
3. Building the resulting iOS workspace should succeed (and there should be new entry in `Podfile`)
Closes facebook/react-native#15460

Differential Revision: D6078649

Pulled By: hramos

fbshipit-source-id: 9651085875892fd66299563ca0e42fb2bcc00825
  • Loading branch information
mironiasty authored and facebook-github-bot committed Oct 18, 2017
1 parent daee51d commit f2d2de9
Show file tree
Hide file tree
Showing 28 changed files with 541 additions and 11 deletions.
5 changes: 5 additions & 0 deletions core/__fixtures__/ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ exports.valid = {
'demoProject.xcodeproj': {
'project.pbxproj': fs.readFileSync(path.join(__dirname, './files/project.pbxproj')),
},
'TestPod.podspec': 'empty'
};

exports.validTestName = {
'MyTestProject.xcodeproj': {
'project.pbxproj': fs.readFileSync(path.join(__dirname, './files/project.pbxproj')),
},
};

exports.pod = {
'TestPod.podspec': 'empty'
};
8 changes: 7 additions & 1 deletion core/__fixtures__/projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const ios = require('./ios');
const flat = {
android: android.valid,
ios: ios.valid,
Podfile: 'empty'
};

const nested = {
Expand All @@ -19,4 +20,9 @@ const withExamples = {
android: android.valid,
};

module.exports = { flat, nested, withExamples };
const withPods = {
Podfile: 'content',
ios: ios.pod
};

module.exports = { flat, nested, withExamples, withPods };
20 changes: 20 additions & 0 deletions core/__tests__/ios/findPodfilePath.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

jest.mock('fs');

const findPodfilePath = require('../../ios/findPodfilePath');
const fs = require('fs');
const projects = require('../../__fixtures__/projects');
const ios = require('../../__fixtures__/ios');

describe('ios::findPodfilePath', () => {
it('returns null if there is no Podfile', () => {
fs.__setMockFilesystem(ios.valid);
expect(findPodfilePath('')).toBeNull();
});

it('returns Podfile path if it exists', () => {
fs.__setMockFilesystem(projects.withPods);
expect(findPodfilePath('/ios')).toContain('Podfile');
});
});
44 changes: 44 additions & 0 deletions core/__tests__/ios/findPodspecName.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';

jest.mock('fs');

const findPodspecName = require('../../ios/findPodspecName');
const fs = require('fs');
const projects = require('../../__fixtures__/projects');
const ios = require('../../__fixtures__/ios');

describe('ios::findPodspecName', () => {
it('returns null if there is not podspec file', () => {
fs.__setMockFilesystem(projects.flat);
expect(findPodspecName('')).toBeNull();
});

it('returns podspec name if only one exists', () => {
fs.__setMockFilesystem(ios.pod);
expect(findPodspecName('/')).toBe('TestPod');
});

it('returns podspec name that match packet directory', () => {
fs.__setMockFilesystem({
user: {
PacketName: {
'Another.podspec': 'empty',
'PacketName.podspec': 'empty'
}
}
});
expect(findPodspecName('/user/PacketName')).toBe('PacketName');
});

it('returns first podspec name if not match in directory', () => {
fs.__setMockFilesystem({
user: {
packet: {
'Another.podspec': 'empty',
'PacketName.podspec': 'empty'
}
}
});
expect(findPodspecName('/user/packet')).toBe('Another');
});
});
11 changes: 11 additions & 0 deletions core/ios/findPodfilePath.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

const fs = require('fs');
const path = require('path');

module.exports = function findPodfilePath(projectFolder) {
const podFilePath = path.join(projectFolder, '..', 'Podfile');
const podFileExists = fs.existsSync(podFilePath);

return podFileExists ? podFilePath : null;
};
28 changes: 28 additions & 0 deletions core/ios/findPodspecName.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

const glob = require('glob');
const path = require('path');

module.exports = function findPodspecName(folder) {
const podspecs = glob.sync('*.podspec', { cwd: folder });
let podspecFile = null;
if (podspecs.length === 0) {
return null;
}
else if (podspecs.length === 1) {
podspecFile = podspecs[0];
}
else {
const folderParts = folder.split(path.sep);
const currentFolder = folderParts[folderParts.length - 1];
const toSelect = podspecs.indexOf(currentFolder + '.podspec');
if (toSelect === -1) {
podspecFile = podspecs[0];
}
else {
podspecFile = podspecs[toSelect];
}
}

return podspecFile.replace('.podspec', '');
};
4 changes: 4 additions & 0 deletions core/ios/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
'use strict';

const findProject = require('./findProject');
const findPodfilePath = require('./findPodfilePath');
const findPodspecName = require('./findPodspecName');
const path = require('path');

/**
Expand Down Expand Up @@ -44,6 +46,8 @@ exports.projectConfig = function projectConfigIOS(folder, userConfig) {
sourceDir: path.dirname(projectPath),
folder: folder,
pbxprojPath: path.join(projectPath, 'project.pbxproj'),
podfile: findPodfilePath(projectPath),
podspec: findPodspecName(folder),
projectPath: projectPath,
projectName: path.basename(projectPath),
libraryFolder: userConfig.libraryFolder || 'Libraries',
Expand Down
8 changes: 8 additions & 0 deletions link/__fixtures__/pods/PodfileSimple
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'

target 'Testing' do
pod 'TestPod', '~> 3.1'

# test should point to this line
end
30 changes: 30 additions & 0 deletions link/__fixtures__/pods/PodfileWithFunction
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'

target 'none' do
pod 'React',
:path => "../node_modules/react-native",
:subspecs => [
"Core",
"ART",
"RCTActionSheet",
"RCTAnimation",
"RCTCameraRoll",
"RCTGeolocation",
"RCTImage",
"RCTNetwork",
"RCTText",
"RCTVibration",
"RCTWebSocket",
"DevSupport",
"BatchedBridge"
]

pod 'Yoga',
:path => "../node_modules/react-native/ReactCommon/yoga"

# test should point to this line
post_install do |installer|

end
end
34 changes: 34 additions & 0 deletions link/__fixtures__/pods/PodfileWithMarkers
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
source 'https://github.com/CocoaPods/Specs.git'
# platform :ios, '9.0'

target 'None' do
# Uncomment the next line if you're using Swift or would like to use dynamic frameworks
# use_frameworks!
# Your 'node_modules' directory is probably in the root of your project, # but if not, adjust the `:path` accordingly
pod 'React', :path => '../node_modules/react-native', :subspecs => [
'Core',
'RCTText',
'RCTNetwork',
'BatchedBridge',
'RCTImage',
'RCTWebSocket', # needed for debugging
# Add any other subspecs you want to use in your project
]

# Add new pods below this line

# test should point to this line
target 'NoneTests' do
inherit! :search_paths
# Pods for testing
end
end

target 'Second' do

target 'NoneUITests' do
inherit! :search_paths
# Add new pods below this line
end

end
32 changes: 32 additions & 0 deletions link/__fixtures__/pods/PodfileWithTarget
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
source 'https://github.com/CocoaPods/Specs.git'
# platform :ios, '9.0'

target 'None' do
# Uncomment the next line if you're using Swift or would like to use dynamic frameworks
# use_frameworks!
# Your 'node_modules' directory is probably in the root of your project, # but if not, adjust the `:path` accordingly
pod 'React', :path => '../node_modules/react-native', :subspecs => [
'Core',
'RCTText',
'RCTNetwork',
'BatchedBridge',
'RCTImage',
'RCTWebSocket', # needed for debugging
# Add any other subspecs you want to use in your project
]

# Explicitly include Yoga if you are using RN >= 0.42.0
pod "Yoga", :path => "../node_modules/react-native/ReactCommon/yoga"

# test should point to this line
target 'NoneTests' do
inherit! :search_paths
# Pods for testing
end

target 'NoneUITests' do
inherit! :search_paths
# Pods for testing
end

end
30 changes: 30 additions & 0 deletions link/__tests__/pods/findLineToAddPod.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';

const path = require('path');
const findLineToAddPod = require('../../pods/findLineToAddPod');
const readPodfile = require('../../pods/readPodfile');

const PODFILES_PATH = path.join(__dirname, '../../__fixtures__/pods');
const LINE_AFTER_TARGET_IN_TEST_PODFILE = 4;

describe('pods::findLineToAddPod', () => {
it('returns null if file is not Podfile', () => {
const podfile = readPodfile(path.join(PODFILES_PATH, '../Info.plist'));
expect(findLineToAddPod(podfile, LINE_AFTER_TARGET_IN_TEST_PODFILE)).toBeNull();
});

it('returns correct line number for Simple Podfile', () => {
const podfile = readPodfile(path.join(PODFILES_PATH, 'PodfileSimple'));
expect(findLineToAddPod(podfile, LINE_AFTER_TARGET_IN_TEST_PODFILE)).toEqual({ line: 7, indentation: 2 });
});

it('returns correct line number for Podfile with target', () => {
const podfile = readPodfile(path.join(PODFILES_PATH, 'PodfileWithTarget'));
expect(findLineToAddPod(podfile, LINE_AFTER_TARGET_IN_TEST_PODFILE)).toEqual({ line: 21, indentation: 2 });
});

it('returns correct line number for Podfile with function', () => {
const podfile = readPodfile(path.join(PODFILES_PATH, 'PodfileWithFunction'));
expect(findLineToAddPod(podfile, LINE_AFTER_TARGET_IN_TEST_PODFILE)).toEqual({ line: 26, indentation: 2 });
});
});
26 changes: 26 additions & 0 deletions link/__tests__/pods/findMarkedLinesInPodfile.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

const path = require('path');
const readPodfile = require('../../pods/readPodfile');
const findMarkedLinesInPodfile = require('../../pods/findMarkedLinesInPodfile');

const PODFILES_PATH = path.join(__dirname, '../../__fixtures__/pods');
const LINE_AFTER_TARGET_IN_TEST_PODFILE = 4;

describe('pods::findMarkedLinesInPodfile', () => {
it('returns empty array if file is not Podfile', () => {
const podfile = readPodfile(path.join(PODFILES_PATH, '../Info.plist'));
expect(findMarkedLinesInPodfile(podfile)).toEqual([]);
});

it('returns empty array for Simple Podfile', () => {
const podfile = readPodfile(path.join(PODFILES_PATH, 'PodfileSimple'));
expect(findMarkedLinesInPodfile(podfile, LINE_AFTER_TARGET_IN_TEST_PODFILE)).toEqual([]);
});

it('returns correct line numbers for Podfile with marker', () => {
const podfile = readPodfile(path.join(PODFILES_PATH, 'PodfileWithMarkers'));
const expectedObject = [{ line: 18, indentation: 2 }, { line: 31, indentation: 4 }];
expect(findMarkedLinesInPodfile(podfile, LINE_AFTER_TARGET_IN_TEST_PODFILE)).toEqual(expectedObject);
});
});
24 changes: 24 additions & 0 deletions link/__tests__/pods/findPodTargetLine.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

const path = require('path');
const findPodTargetLine = require('../../pods/findPodTargetLine');
const readPodfile = require('../../pods/readPodfile');

const PODFILES_PATH = path.join(__dirname, '../../__fixtures__/pods');

describe('pods::findPodTargetLine', () => {
it('returns null if file is not Podfile', () => {
const podfile = readPodfile(path.join(PODFILES_PATH, '../Info.plist'));
expect(findPodTargetLine(podfile, 'name')).toBeNull();
});

it('returns null if there is not matching project name', () => {
const podfile = readPodfile(path.join(PODFILES_PATH, 'PodfileSimple'));
expect(findPodTargetLine(podfile, 'invalidName')).toBeNull();
});

it('returns null if there is not matching project name', () => {
const podfile = readPodfile(path.join(PODFILES_PATH, 'PodfileSimple'));
expect(findPodTargetLine(podfile, 'Testing')).toBe(4);
});
});
32 changes: 32 additions & 0 deletions link/__tests__/pods/isInstalled.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';

const path = require('path');
const isInstalled = require('../../pods/isInstalled');

const PODFILES_PATH = path.join(__dirname, '../../__fixtures__/pods');

describe('pods::isInstalled', () => {
it('returns false if pod is missing', () => {
const project = { podfile: path.join(PODFILES_PATH, 'PodfileSimple') };
const podspecName = { podspec: 'NotExisting' };
expect(isInstalled(project, podspecName)).toBe(false);
});

it('returns true for existing pod with version number', () => {
const project = { podfile: path.join(PODFILES_PATH, 'PodfileSimple') };
const podspecName = { podspec: 'TestPod' };
expect(isInstalled(project, podspecName)).toBe(true);
});

it('returns true for existing pod with path', () => {
const project = { podfile: path.join(PODFILES_PATH, 'PodfileWithTarget') };
const podspecName = { podspec: 'Yoga' };
expect(isInstalled(project, podspecName)).toBe(true);
});

it('returns true for existing pod with multiline definition', () => {
const project = { podfile: path.join(PODFILES_PATH, 'PodfileWithFunction') };
const podspecName = { podspec: 'React' };
expect(isInstalled(project, podspecName)).toBe(true);
});
});
Loading

0 comments on commit f2d2de9

Please sign in to comment.