Permalink
Browse files

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
  • Loading branch information...
mironiasty authored and facebook-github-bot committed Oct 18, 2017
1 parent ce937e5 commit 74146cb3158273921b768c97f229695e680ea82d
Showing with 547 additions and 13 deletions.
  1. +6 −2 docs/LinkingLibraries.md
  2. +5 −0 local-cli/core/__fixtures__/ios.js
  3. +7 −1 local-cli/core/__fixtures__/projects.js
  4. +20 −0 local-cli/core/__tests__/ios/findPodfilePath.spec.js
  5. +44 −0 local-cli/core/__tests__/ios/findPodspecName.spec.js
  6. +11 −0 local-cli/core/ios/findPodfilePath.js
  7. +28 −0 local-cli/core/ios/findPodspecName.js
  8. +4 −0 local-cli/core/ios/index.js
  9. +8 −0 local-cli/link/__fixtures__/pods/PodfileSimple
  10. +30 −0 local-cli/link/__fixtures__/pods/PodfileWithFunction
  11. +34 −0 local-cli/link/__fixtures__/pods/PodfileWithMarkers
  12. +32 −0 local-cli/link/__fixtures__/pods/PodfileWithTarget
  13. +30 −0 local-cli/link/__tests__/pods/findLineToAddPod.spec.js
  14. +26 −0 local-cli/link/__tests__/pods/findMarkedLinesInPodfile.spec.js
  15. +24 −0 local-cli/link/__tests__/pods/findPodTargetLine.spec.js
  16. +32 −0 local-cli/link/__tests__/pods/isInstalled.spec.js
  17. +35 −0 local-cli/link/__tests__/pods/removePodEntry.spec.js
  18. +9 −5 local-cli/link/link.js
  19. +21 −0 local-cli/link/pods/addPodEntry.js
  20. +24 −0 local-cli/link/pods/findLineToAddPod.js
  21. +12 −0 local-cli/link/pods/findMarkedLinesInPodfile.js
  22. +14 −0 local-cli/link/pods/findPodTargetLine.js
  23. +19 −0 local-cli/link/pods/isInstalled.js
  24. +8 −0 local-cli/link/pods/readPodfile.js
  25. +25 −0 local-cli/link/pods/registerNativeModule.js
  26. +7 −0 local-cli/link/pods/removePodEntry.js
  27. +8 −0 local-cli/link/pods/savePodFile.js
  28. +13 −0 local-cli/link/pods/unregisterNativeModule.js
  29. +11 −5 local-cli/link/unlink.js
View
@@ -35,8 +35,8 @@ Install a library with native dependencies:
$ npm install <library-with-native-dependencies> --save
```
**Note:** _`--save` or `--save-dev` flag is very important for this step. React Native will link
your libs based on `dependencies` and `devDependencies` in your `package.json` file._
> ***Note:*** `--save` or `--save-dev` flag is very important for this step. React Native will link
your libs based on `dependencies` and `devDependencies` in your `package.json` file.
#### Step 2
@@ -47,6 +47,10 @@ $ react-native link
Done! All libraries with native dependencies should be successfully linked to your iOS/Android project.
> ***Note:*** If your iOS project is using CocoaPods (contains `Podfile`) and linked library has `podspec` file,
then `react-native link` will link library using Podfile. To support non-trivial Podfiles
add `# Add new pods below this line` comment to places where you expect pods to be added.
### Manual linking
#### Step 1
@@ -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'
};
@@ -4,6 +4,7 @@ const ios = require('./ios');
const flat = {
android: android.valid,
ios: ios.valid,
Podfile: 'empty'
};
const nested = {
@@ -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 };
@@ -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');
});
});
@@ -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');
});
});
@@ -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;
};
@@ -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', '');
};
@@ -9,6 +9,8 @@
'use strict';
const findProject = require('./findProject');
const findPodfilePath = require('./findPodfilePath');
const findPodspecName = require('./findPodspecName');
const path = require('path');
/**
@@ -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',
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 });
});
});
@@ -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);
});
});
@@ -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);
});
});
Oops, something went wrong.

0 comments on commit 74146cb

Please sign in to comment.