Skip to content

Commit 74146cb

Browse files
mironiastyfacebook-github-bot
authored andcommitted
Make react-native link play nicely with CocoaPods-based iOS projects.
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 #15460 Differential Revision: D6078649 Pulled By: hramos fbshipit-source-id: 9651085875892fd66299563ca0e42fb2bcc00825
1 parent ce937e5 commit 74146cb

29 files changed

+547
-13
lines changed

Diff for: docs/LinkingLibraries.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ Install a library with native dependencies:
3535
$ npm install <library-with-native-dependencies> --save
3636
```
3737

38-
**Note:** _`--save` or `--save-dev` flag is very important for this step. React Native will link
39-
your libs based on `dependencies` and `devDependencies` in your `package.json` file._
38+
> ***Note:*** `--save` or `--save-dev` flag is very important for this step. React Native will link
39+
your libs based on `dependencies` and `devDependencies` in your `package.json` file.
4040

4141
#### Step 2
4242

@@ -47,6 +47,10 @@ $ react-native link
4747

4848
Done! All libraries with native dependencies should be successfully linked to your iOS/Android project.
4949

50+
> ***Note:*** If your iOS project is using CocoaPods (contains `Podfile`) and linked library has `podspec` file,
51+
then `react-native link` will link library using Podfile. To support non-trivial Podfiles
52+
add `# Add new pods below this line` comment to places where you expect pods to be added.
53+
5054
### Manual linking
5155

5256
#### Step 1

Diff for: local-cli/core/__fixtures__/ios.js

+5
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ exports.valid = {
55
'demoProject.xcodeproj': {
66
'project.pbxproj': fs.readFileSync(path.join(__dirname, './files/project.pbxproj')),
77
},
8+
'TestPod.podspec': 'empty'
89
};
910

1011
exports.validTestName = {
1112
'MyTestProject.xcodeproj': {
1213
'project.pbxproj': fs.readFileSync(path.join(__dirname, './files/project.pbxproj')),
1314
},
1415
};
16+
17+
exports.pod = {
18+
'TestPod.podspec': 'empty'
19+
};

Diff for: local-cli/core/__fixtures__/projects.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const ios = require('./ios');
44
const flat = {
55
android: android.valid,
66
ios: ios.valid,
7+
Podfile: 'empty'
78
};
89

910
const nested = {
@@ -19,4 +20,9 @@ const withExamples = {
1920
android: android.valid,
2021
};
2122

22-
module.exports = { flat, nested, withExamples };
23+
const withPods = {
24+
Podfile: 'content',
25+
ios: ios.pod
26+
};
27+
28+
module.exports = { flat, nested, withExamples, withPods };

Diff for: local-cli/core/__tests__/ios/findPodfilePath.spec.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
jest.mock('fs');
4+
5+
const findPodfilePath = require('../../ios/findPodfilePath');
6+
const fs = require('fs');
7+
const projects = require('../../__fixtures__/projects');
8+
const ios = require('../../__fixtures__/ios');
9+
10+
describe('ios::findPodfilePath', () => {
11+
it('returns null if there is no Podfile', () => {
12+
fs.__setMockFilesystem(ios.valid);
13+
expect(findPodfilePath('')).toBeNull();
14+
});
15+
16+
it('returns Podfile path if it exists', () => {
17+
fs.__setMockFilesystem(projects.withPods);
18+
expect(findPodfilePath('/ios')).toContain('Podfile');
19+
});
20+
});

Diff for: local-cli/core/__tests__/ios/findPodspecName.spec.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
3+
jest.mock('fs');
4+
5+
const findPodspecName = require('../../ios/findPodspecName');
6+
const fs = require('fs');
7+
const projects = require('../../__fixtures__/projects');
8+
const ios = require('../../__fixtures__/ios');
9+
10+
describe('ios::findPodspecName', () => {
11+
it('returns null if there is not podspec file', () => {
12+
fs.__setMockFilesystem(projects.flat);
13+
expect(findPodspecName('')).toBeNull();
14+
});
15+
16+
it('returns podspec name if only one exists', () => {
17+
fs.__setMockFilesystem(ios.pod);
18+
expect(findPodspecName('/')).toBe('TestPod');
19+
});
20+
21+
it('returns podspec name that match packet directory', () => {
22+
fs.__setMockFilesystem({
23+
user: {
24+
PacketName: {
25+
'Another.podspec': 'empty',
26+
'PacketName.podspec': 'empty'
27+
}
28+
}
29+
});
30+
expect(findPodspecName('/user/PacketName')).toBe('PacketName');
31+
});
32+
33+
it('returns first podspec name if not match in directory', () => {
34+
fs.__setMockFilesystem({
35+
user: {
36+
packet: {
37+
'Another.podspec': 'empty',
38+
'PacketName.podspec': 'empty'
39+
}
40+
}
41+
});
42+
expect(findPodspecName('/user/packet')).toBe('Another');
43+
});
44+
});

Diff for: local-cli/core/ios/findPodfilePath.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use strict';
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
6+
module.exports = function findPodfilePath(projectFolder) {
7+
const podFilePath = path.join(projectFolder, '..', 'Podfile');
8+
const podFileExists = fs.existsSync(podFilePath);
9+
10+
return podFileExists ? podFilePath : null;
11+
};

Diff for: local-cli/core/ios/findPodspecName.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
3+
const glob = require('glob');
4+
const path = require('path');
5+
6+
module.exports = function findPodspecName(folder) {
7+
const podspecs = glob.sync('*.podspec', { cwd: folder });
8+
let podspecFile = null;
9+
if (podspecs.length === 0) {
10+
return null;
11+
}
12+
else if (podspecs.length === 1) {
13+
podspecFile = podspecs[0];
14+
}
15+
else {
16+
const folderParts = folder.split(path.sep);
17+
const currentFolder = folderParts[folderParts.length - 1];
18+
const toSelect = podspecs.indexOf(currentFolder + '.podspec');
19+
if (toSelect === -1) {
20+
podspecFile = podspecs[0];
21+
}
22+
else {
23+
podspecFile = podspecs[toSelect];
24+
}
25+
}
26+
27+
return podspecFile.replace('.podspec', '');
28+
};

Diff for: local-cli/core/ios/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
'use strict';
1010

1111
const findProject = require('./findProject');
12+
const findPodfilePath = require('./findPodfilePath');
13+
const findPodspecName = require('./findPodspecName');
1214
const path = require('path');
1315

1416
/**
@@ -44,6 +46,8 @@ exports.projectConfig = function projectConfigIOS(folder, userConfig) {
4446
sourceDir: path.dirname(projectPath),
4547
folder: folder,
4648
pbxprojPath: path.join(projectPath, 'project.pbxproj'),
49+
podfile: findPodfilePath(projectPath),
50+
podspec: findPodspecName(folder),
4751
projectPath: projectPath,
4852
projectName: path.basename(projectPath),
4953
libraryFolder: userConfig.libraryFolder || 'Libraries',

Diff for: local-cli/link/__fixtures__/pods/PodfileSimple

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
source 'https://github.com/CocoaPods/Specs.git'
2+
platform :ios, '9.0'
3+
4+
target 'Testing' do
5+
pod 'TestPod', '~> 3.1'
6+
7+
# test should point to this line
8+
end

Diff for: local-cli/link/__fixtures__/pods/PodfileWithFunction

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
source 'https://github.com/CocoaPods/Specs.git'
2+
platform :ios, '9.0'
3+
4+
target 'none' do
5+
pod 'React',
6+
:path => "../node_modules/react-native",
7+
:subspecs => [
8+
"Core",
9+
"ART",
10+
"RCTActionSheet",
11+
"RCTAnimation",
12+
"RCTCameraRoll",
13+
"RCTGeolocation",
14+
"RCTImage",
15+
"RCTNetwork",
16+
"RCTText",
17+
"RCTVibration",
18+
"RCTWebSocket",
19+
"DevSupport",
20+
"BatchedBridge"
21+
]
22+
23+
pod 'Yoga',
24+
:path => "../node_modules/react-native/ReactCommon/yoga"
25+
26+
# test should point to this line
27+
post_install do |installer|
28+
29+
end
30+
end

Diff for: local-cli/link/__fixtures__/pods/PodfileWithMarkers

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
source 'https://github.com/CocoaPods/Specs.git'
2+
# platform :ios, '9.0'
3+
4+
target 'None' do
5+
# Uncomment the next line if you're using Swift or would like to use dynamic frameworks
6+
# use_frameworks!
7+
# Your 'node_modules' directory is probably in the root of your project, # but if not, adjust the `:path` accordingly
8+
pod 'React', :path => '../node_modules/react-native', :subspecs => [
9+
'Core',
10+
'RCTText',
11+
'RCTNetwork',
12+
'BatchedBridge',
13+
'RCTImage',
14+
'RCTWebSocket', # needed for debugging
15+
# Add any other subspecs you want to use in your project
16+
]
17+
18+
# Add new pods below this line
19+
20+
# test should point to this line
21+
target 'NoneTests' do
22+
inherit! :search_paths
23+
# Pods for testing
24+
end
25+
end
26+
27+
target 'Second' do
28+
29+
target 'NoneUITests' do
30+
inherit! :search_paths
31+
# Add new pods below this line
32+
end
33+
34+
end

Diff for: local-cli/link/__fixtures__/pods/PodfileWithTarget

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
source 'https://github.com/CocoaPods/Specs.git'
2+
# platform :ios, '9.0'
3+
4+
target 'None' do
5+
# Uncomment the next line if you're using Swift or would like to use dynamic frameworks
6+
# use_frameworks!
7+
# Your 'node_modules' directory is probably in the root of your project, # but if not, adjust the `:path` accordingly
8+
pod 'React', :path => '../node_modules/react-native', :subspecs => [
9+
'Core',
10+
'RCTText',
11+
'RCTNetwork',
12+
'BatchedBridge',
13+
'RCTImage',
14+
'RCTWebSocket', # needed for debugging
15+
# Add any other subspecs you want to use in your project
16+
]
17+
18+
# Explicitly include Yoga if you are using RN >= 0.42.0
19+
pod "Yoga", :path => "../node_modules/react-native/ReactCommon/yoga"
20+
21+
# test should point to this line
22+
target 'NoneTests' do
23+
inherit! :search_paths
24+
# Pods for testing
25+
end
26+
27+
target 'NoneUITests' do
28+
inherit! :search_paths
29+
# Pods for testing
30+
end
31+
32+
end
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use strict';
2+
3+
const path = require('path');
4+
const findLineToAddPod = require('../../pods/findLineToAddPod');
5+
const readPodfile = require('../../pods/readPodfile');
6+
7+
const PODFILES_PATH = path.join(__dirname, '../../__fixtures__/pods');
8+
const LINE_AFTER_TARGET_IN_TEST_PODFILE = 4;
9+
10+
describe('pods::findLineToAddPod', () => {
11+
it('returns null if file is not Podfile', () => {
12+
const podfile = readPodfile(path.join(PODFILES_PATH, '../Info.plist'));
13+
expect(findLineToAddPod(podfile, LINE_AFTER_TARGET_IN_TEST_PODFILE)).toBeNull();
14+
});
15+
16+
it('returns correct line number for Simple Podfile', () => {
17+
const podfile = readPodfile(path.join(PODFILES_PATH, 'PodfileSimple'));
18+
expect(findLineToAddPod(podfile, LINE_AFTER_TARGET_IN_TEST_PODFILE)).toEqual({ line: 7, indentation: 2 });
19+
});
20+
21+
it('returns correct line number for Podfile with target', () => {
22+
const podfile = readPodfile(path.join(PODFILES_PATH, 'PodfileWithTarget'));
23+
expect(findLineToAddPod(podfile, LINE_AFTER_TARGET_IN_TEST_PODFILE)).toEqual({ line: 21, indentation: 2 });
24+
});
25+
26+
it('returns correct line number for Podfile with function', () => {
27+
const podfile = readPodfile(path.join(PODFILES_PATH, 'PodfileWithFunction'));
28+
expect(findLineToAddPod(podfile, LINE_AFTER_TARGET_IN_TEST_PODFILE)).toEqual({ line: 26, indentation: 2 });
29+
});
30+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
3+
const path = require('path');
4+
const readPodfile = require('../../pods/readPodfile');
5+
const findMarkedLinesInPodfile = require('../../pods/findMarkedLinesInPodfile');
6+
7+
const PODFILES_PATH = path.join(__dirname, '../../__fixtures__/pods');
8+
const LINE_AFTER_TARGET_IN_TEST_PODFILE = 4;
9+
10+
describe('pods::findMarkedLinesInPodfile', () => {
11+
it('returns empty array if file is not Podfile', () => {
12+
const podfile = readPodfile(path.join(PODFILES_PATH, '../Info.plist'));
13+
expect(findMarkedLinesInPodfile(podfile)).toEqual([]);
14+
});
15+
16+
it('returns empty array for Simple Podfile', () => {
17+
const podfile = readPodfile(path.join(PODFILES_PATH, 'PodfileSimple'));
18+
expect(findMarkedLinesInPodfile(podfile, LINE_AFTER_TARGET_IN_TEST_PODFILE)).toEqual([]);
19+
});
20+
21+
it('returns correct line numbers for Podfile with marker', () => {
22+
const podfile = readPodfile(path.join(PODFILES_PATH, 'PodfileWithMarkers'));
23+
const expectedObject = [{ line: 18, indentation: 2 }, { line: 31, indentation: 4 }];
24+
expect(findMarkedLinesInPodfile(podfile, LINE_AFTER_TARGET_IN_TEST_PODFILE)).toEqual(expectedObject);
25+
});
26+
});
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
const path = require('path');
4+
const findPodTargetLine = require('../../pods/findPodTargetLine');
5+
const readPodfile = require('../../pods/readPodfile');
6+
7+
const PODFILES_PATH = path.join(__dirname, '../../__fixtures__/pods');
8+
9+
describe('pods::findPodTargetLine', () => {
10+
it('returns null if file is not Podfile', () => {
11+
const podfile = readPodfile(path.join(PODFILES_PATH, '../Info.plist'));
12+
expect(findPodTargetLine(podfile, 'name')).toBeNull();
13+
});
14+
15+
it('returns null if there is not matching project name', () => {
16+
const podfile = readPodfile(path.join(PODFILES_PATH, 'PodfileSimple'));
17+
expect(findPodTargetLine(podfile, 'invalidName')).toBeNull();
18+
});
19+
20+
it('returns null if there is not matching project name', () => {
21+
const podfile = readPodfile(path.join(PODFILES_PATH, 'PodfileSimple'));
22+
expect(findPodTargetLine(podfile, 'Testing')).toBe(4);
23+
});
24+
});

0 commit comments

Comments
 (0)