Skip to content

Commit

Permalink
feat: autolink on iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
alloy authored and grabbou committed Apr 16, 2019
1 parent fb94fef commit 76f079e
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 3 deletions.
10 changes: 10 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ jobs:
- attach_workspace:
at: ~/react-native-cli
- run: yarn flow-check
cocoa-pods:
<<: *defaults
docker:
- image: circleci/ruby:2.4-node
- attach_workspace:
at: ~/react-native-cli
- run: yarn test:ci:cocoapods
unit-tests:
<<: *defaults
steps:
Expand Down Expand Up @@ -70,3 +77,6 @@ workflows:
- e2e-tests:
requires:
- install-dependencies
- cocoa-pods:
requires:
- install-dependencies
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"test": "jest",
"test:ci:unit": "jest packages --ci --coverage",
"test:ci:e2e": "jest e2e --ci -i",
"test:ci:cocoapods": "ruby packages/platform-ios/native_modules.rb",
"lint": "eslint . --cache --report-unused-disable-directives",
"flow-check": "flow check",
"postinstall": "yarn build",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ Object {
"sourceDir": "./abc",
},
},
"root": "<<REPLACED>>/node_modules/react-native-test",
}
`;

Expand Down Expand Up @@ -113,6 +114,7 @@ Object {
"sourceDir": "<<REPLACED>>/node_modules/react-native-foo/customLocation",
},
},
"root": "<<REPLACED>>/node_modules/react-native-foo",
}
`;

Expand All @@ -130,13 +132,14 @@ Object {
"pbxprojPath": "<<REPLACED>>/node_modules/react-native-test/customLocation/customProject.xcodeproj/project.pbxproj",
"plist": Array [],
"podfile": null,
"podspec": null,
"podspec": "ReactNativeTest",
"projectName": "customProject.xcodeproj",
"projectPath": "<<REPLACED>>/node_modules/react-native-test/customLocation/customProject.xcodeproj",
"sharedLibraries": Array [],
"sourceDir": "<<REPLACED>>/node_modules/react-native-test/customLocation",
},
},
"root": "<<REPLACED>>/node_modules/react-native-test",
}
`;

Expand All @@ -151,6 +154,7 @@ Object {
"android": null,
"ios": null,
},
"root": "<<REPLACED>>/node_modules/react-native",
},
"react-native-test": Object {
"assets": Array [],
Expand All @@ -172,6 +176,7 @@ Object {
"sourceDir": "<<REPLACED>>/node_modules/react-native-test/ios",
},
},
"root": "<<REPLACED>>/node_modules/react-native-test",
},
}
`;
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/tools/config/__tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ test('should return dependencies from package.json', () => {
test('should read a config of a dependency and use it to load other settings', () => {
writeFiles(DIR, {
'node_modules/react-native/package.json': '{}',
'node_modules/react-native-test/ReactNativeTest.podspec': '',
'node_modules/react-native-test/package.json': `{
"react-native": {
"dependency": {
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/tools/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function loadConfig(projectRoot: string = process.cwd()): ConfigT {
}

/**
* This workaround is neccessary for development only before
* This workaround is necessary for development only before
* first 0.60.0-rc.0 gets released and we can switch to it
* while testing.
*/
Expand All @@ -76,6 +76,7 @@ function loadConfig(projectRoot: string = process.cwd()): ConfigT {
get [dependencyName]() {
return merge(
{
root,
name: dependencyName,
platforms: Object.keys(finalConfig.platforms).reduce(
(dependency, platform) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/tools/config/readConfigFromDisk.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const loadProjectCommands = (
};

/**
* Reads a legacy configuaration from a `package.json` "rnpm" key.
* Reads a legacy configuration from a `package.json` "rnpm" key.
*/
export function readLegacyDependencyConfigFromDisk(
rootFolder: string,
Expand Down
237 changes: 237 additions & 0 deletions packages/platform-ios/native_modules.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
def use_native_modules!(packages = nil)
if (!packages)
cli_bin = Pod::Executable.execute_command("node", ["-e", "console.log(require.resolve('@react-native-community/cli/build/index.js'))"], true).strip
output = Pod::Executable.execute_command("node", [cli_bin, "config"], true)
json = []
output.each_line do |line|
case line
when /^warn\s(.+)/
Pod::UI.warn($1)
when /^(success|info|error|debug)\s(.+)/
Pod::UI.message($1)
else
json << line
end
end
config = JSON.parse(json.join("\n"))
packages = config["dependencies"]
end

found_pods = []

packages.each do |package_name, package|
next unless package_config = package["platforms"]["ios"]

podspec_path = File.join(package["root"], "#{package_config["podspec"]}.podspec")
spec = Pod::Specification.from_file(podspec_path)

# We want to do a look up inside the current CocoaPods target
# to see if it's already included, this:
# 1. Gives you the chance to define it beforehand
# 2. Ensures CocoaPods won't explode if it's included twice
#
this_target = current_target_definition
existing_deps = current_target_definition.dependencies

# Skip dependencies that the user already activated themselves.
next if existing_deps.find do |existing_dep|
existing_dep.name.split('/').first == spec.name
end

pod spec.name, :path => package["root"]

if package_config["scriptPhases"]
# Can be either an object, or an array of objects
Array(package_config["scriptPhases"]).each do |phase|
# see https://www.rubydoc.info/gems/cocoapods-core/Pod/Podfile/DSL#script_phase-instance_method
# for the full object keys

# Support passing in a path relative to the root of the package
if phase["path"]
phase["script"] = File.read(File.expand_path(phase["path"], package["root"]))
phase.delete("path")
end

# Support converting the execution position into a symbol
if phase["execution_position"]
phase["execution_position"] = phase["execution_position"].to_sym
end

script_phase phase
end
end

found_pods.push spec
end

if found_pods.size > 0
pods = found_pods.map { |p| p.name }.sort.to_sentence
Pod::UI.puts "Detected React Native module #{"pod".pluralize(found_pods.size)} for #{pods}"
end
end

# You can run the tests for this file by running:
# $ ruby use_native_modules.rb
if $0 == __FILE__
require "minitest/spec"
require "minitest/autorun"

# Define this here, because we’re not actually loading this code.
module Pod
class Specification
end

module UI
end
end

# CocoaPods loads ActiveSupport, but we’re not doing that here just for the test.
class Array
def to_sentence
size == 1 ? self[0] : "#{self[0..-2].join(", ")}, and #{self[-1]}"
end
end
class String
def pluralize(count)
count == 1 ? self : "#{self}s"
end
end

describe "use_native_modules!" do
before do
@script_phase = {
"script" => "123",
"name" => "My Name",
"execution_position" => "before_compile",
"input" => "string"
}

@ios_package = ios_package = {
"root" => "/Users/grabbou/Repositories/WebViewDemoApp/node_modules/react",
"platforms" => {
"ios" => {
"podspec" => "React",
},
"android" => nil,
},
}
@android_package = {
"root" => "/Users/grabbou/Repositories/WebViewDemoApp/node_modules/react-native-google-play-game-services",
"platforms" => {
"ios" => nil,
"android" => {
# This is where normally more config would be
},
}
}
@config = { "ios-dep" => @ios_package, "android-dep" => @android_package }

@activated_pods = activated_pods = []
@current_target_definition_dependencies = current_target_definition_dependencies = []
@printed_messages = printed_messages = []
@added_scripts = added_scripts = []
@target_definition = target_definition = Object.new
@podfile = podfile = Object.new
@spec = spec = Object.new

spec.singleton_class.send(:define_method, :name) { "ios-dep" }

podfile.singleton_class.send(:define_method, :use_native_modules) do |config|
use_native_modules!(config)
end

Pod::Specification.singleton_class.send(:define_method, :from_file) do |podspec_path|
podspec_path.must_equal File.join(ios_package["root"], "#{ios_package["platforms"]["ios"]["podspec"]}.podspec")
spec
end

Pod::UI.singleton_class.send(:define_method, :puts) do |message|
printed_messages << message
end

podfile.singleton_class.send(:define_method, :pod) do |name, options|
activated_pods << { name: name, options: options }
end

podfile.singleton_class.send(:define_method, :script_phase) do |options|
added_scripts << options
end

target_definition.singleton_class.send(:define_method, :dependencies) do
current_target_definition_dependencies
end

podfile.singleton_class.send(:define_method, :current_target_definition) do
target_definition
end
end

it "activates iOS pods" do
@podfile.use_native_modules(@config)
@activated_pods.must_equal [{
name: "ios-dep",
options: { path: @ios_package["root"] }
}]
end

it "does not activate pods that were already activated previously (by the user in their Podfile)" do
activated_pod = Object.new
activated_pod.singleton_class.send(:define_method, :name) { "ios-dep" }
@current_target_definition_dependencies << activated_pod
@podfile.use_native_modules(@config)
@activated_pods.must_equal []
end

it "does not activate pods whose root spec were already activated previously (by the user in their Podfile)" do
activated_pod = Object.new
activated_pod.singleton_class.send(:define_method, :name) { "ios-dep/foo/bar" }
@current_target_definition_dependencies << activated_pod
@podfile.use_native_modules(@config)
@activated_pods.must_equal []
end

it "prints out the native module pods that were found" do
@podfile.use_native_modules({})
@podfile.use_native_modules({ "pkg-1" => @ios_package })
@podfile.use_native_modules({ "pkg-1" => @ios_package, "pkg-2" => @ios_package })
@printed_messages.must_equal [
"Detected native module pod for ios-dep",
"Detected native module pods for ios-dep, and ios-dep"
]
end

describe "concerning script_phases" do
it "uses the options directly" do
@config["ios-dep"]["platforms"]["ios"]["scriptPhases"] = [@script_phase]
@podfile.use_native_modules(@config)
@added_scripts.must_equal [{
"script" => "123",
"name" => "My Name",
"execution_position" => :before_compile,
"input" => "string"
}]
end

it "reads a script file relative to the package root" do
@script_phase.delete("script")
@script_phase["path"] = "./some_shell_script.sh"
@config["ios-dep"]["platforms"]["ios"]["scriptPhases"] = [@script_phase]

file_read_mock = MiniTest::Mock.new
file_read_mock.expect(:call, "contents from file", [File.join(@ios_package["root"], "some_shell_script.sh")])

File.stub(:read, file_read_mock) do
@podfile.use_native_modules(@config)
end

@added_scripts.must_equal [{
"script" => "contents from file",
"name" => "My Name",
"execution_position" => :before_compile,
"input" => "string"
}]
file_read_mock.verify
end
end
end
end
1 change: 1 addition & 0 deletions types/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export type ConfigT = {|
dependencies: {
[key: string]: {
name: string,
root: string,
platforms: {
android?: DependencyConfigAndroidT | null,
ios?: DependencyConfigIOST | null,
Expand Down

0 comments on commit 76f079e

Please sign in to comment.