Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 1 addition & 13 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,6 @@ commands:
- attach_workspace:
at: ~/react-native-cli
- run: yarn lint
run-cocoa-pods-tests:
steps:
- attach_workspace:
at: ~/react-native-cli
- run: yarn test:ci:cocoapods
run-unit-tests:
steps:
- attach_workspace:
Expand All @@ -80,14 +75,10 @@ jobs:
executor: node8
steps:
- run-lint
cocoa-pods:
unit-tests:
executor: noderuby
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know which version of Node is run on this image? Would be cool if it was the latest or so

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn’t spot an explicit listing of the version in the CI output, but it’s using circleci/ruby:2.4-node and in this listing I see ENV NODE_VERSION=12.16.2. So probably that?

steps:
- install-cocoapods
- run-cocoa-pods-tests
unit-tests:
executor: node8
steps:
- run-unit-tests
e2e-tests:
executor: reactnative
Expand Down Expand Up @@ -116,7 +107,4 @@ workflows:
- e2e-tests:
requires:
- setup
- cocoa-pods:
requires:
- setup
- lts-tests
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ build/
.cache
.watchmanconfig
coverage
!packages/platform-ios/src/config/__fixtures__/native_modules/node_modules
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"test:ci:unit": "jest packages --ci --coverage",
"test:ci:e2e": "jest e2e --ci -i",
"lint": "eslint --ext .js,.ts . --cache --report-unused-disable-directives",
"test:ci:cocoapods": "ruby packages/platform-ios/native_modules.rb",
"postinstall": "yarn build",
"link-packages": "node ./scripts/linkPackages.js",
"publish": "yarn build-clean-all && yarn install && lerna publish",
Expand Down
6 changes: 4 additions & 2 deletions packages/cli-types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
IOSProjectParams,
IOSDependencyConfig,
IOSDependencyParams,
IOSNativeModulesConfig,
} from './ios';
import {
AndroidProjectConfig,
Expand Down Expand Up @@ -127,7 +128,7 @@ export type ProjectConfig = {
* @property platforms - Map of available platforms (build-ins and dynamically loaded)
* @property commands - An array of commands that are present in 3rd party packages
*/
export type Config = {
export interface Config extends IOSNativeModulesConfig {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done purely to ensure the Config doesn’t deviate in a way that the otherwise uncheckable Ruby source.

root: string;
reactNativePath: string;
project: ProjectConfig;
Expand All @@ -149,7 +150,7 @@ export type Config = {
[name: string]: PlatformConfig<any, any, any, any>;
};
commands: Command[];
};
}

/**
* Shares some structure with Config, except that root is calculated and can't
Expand Down Expand Up @@ -180,6 +181,7 @@ export {
IOSProjectParams,
IOSDependencyConfig,
IOSDependencyParams,
IOSNativeModulesConfig,
};

export {
Expand Down
59 changes: 59 additions & 0 deletions packages/cli-types/src/ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
*/
export interface IOSProjectParams {
project?: string;
/**
* @deprecated A podspec should always be at the root of a package and
* have the name of the package. This property will be
* removed in a future major version.
*
* @todo Log a warning when this is used.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have to follow-up on this in a future iteration.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to identify libraries that follow a pattern of adding a Podspec into ios/ dir and send PRs.

*/
podspecPath?: string;
sharedLibraries?: string[];
libraryFolder?: string;
Expand All @@ -31,3 +38,55 @@ export interface IOSProjectConfig {
}

export interface IOSDependencyConfig extends IOSProjectConfig {}

/**
* @see https://www.rubydoc.info/gems/cocoapods-core/Pod/Podfile/DSL#script_phase-instance_method
*
* The only difference is that `script` may be omitted in favour of a
* `path`, relative to the root of the package, whose content will be
* used.
*/
export type IOSScriptPhase = ({script: string} | {path: string}) & {
name: string;
shell_path?: string;
input_files?: string[];
output_files?: string[];
input_file_lists?: string[];
output_file_lists?: string[];
show_env_vars_in_log?: boolean;
dependency_file?: string;
execution_position?: 'before_compile' | 'after_compile' | 'any';
};

/**
* This describes the data that is expected by `native_modules.rb`. It is only
* meant to ensure the `Config` interface follows exactly what is needed, so
* only make changes to this interface (or `IOSScriptPhase`) if the data
* requirements of `native_modules.rb` change.
*/
export interface IOSNativeModulesConfig {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗ So this should only change if the Ruby source changes and should not be refactored/shared with other typings, even if they are similar.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is that? Wouldn't it be better to just reference

export interface Config extends IOSNativeModulesConfig {
root: string;
reactNativePath: string;
project: ProjectConfig;
assets: string[];
dependencies: {[key: string]: Dependency};
platforms: {
android: PlatformConfig<
AndroidProjectConfig,
AndroidProjectParams,
AndroidDependencyConfig,
AndroidDependencyParams
>;
ios: PlatformConfig<
IOSProjectConfig,
IOSProjectParams,
IOSDependencyConfig,
IOSDependencyParams
>;
[name: string]: PlatformConfig<any, any, any, any>;
};
commands: Command[];
}
directly? This is the exact type of the JSON output from the command.

Could you help me understand where and how it is used? Maybe it's not the right place for it then (in shared package), but next to a utility?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because TS cannot type-check the Ruby code, so people making changes to this Config interface might accidentally break some input data assumption made by it. Put differently, you should see this interface as if it were the function signature declaration of the Ruby code, if it were written in TS.

project: {
ios?: {
sourceDir: string;
};
};
dependencies: {
[name: string]: {
root: string;
platforms: {
ios?: null | {
/**
* @deprecated A podspec should always be at the root of a package and
* have the name of the package. This property will be
* removed in a future major version.
*
* @todo Log a warning when this is used.
*/
podspecPath: string;
scriptPhases?: Array<IOSScriptPhase>;
};
android?: null | {};
};
};
};
}
192 changes: 39 additions & 153 deletions packages/platform-ios/native_modules.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# This is a function which is used inside your Podfile.
# It uses `react-native config` to grab a list of dependencies, and pulls out.all of the ones
# which declare themselves to be iOS dependencies (via having a Podspec) and automatically
# imports those into your current target.
#
# It uses `react-native config` to grab a list of dependencies, and pulls out
# all of the ones which declare themselves to be iOS/macOS dependencies (by
# virtue of having a Podspec) and automatically imports those into your current
# target.
#
# See the `IOSNativeModulesConfig` interface in `cli-types/src/ios.ts` to
# understand what the input data should look like. Be sure to update that file
# in lock-step with additional data being used here.

require 'pathname'
require 'cocoapods'

Expand Down Expand Up @@ -54,6 +60,16 @@ def use_native_modules!(config = nil)

spec = Pod::Specification.from_file(podspec_path)

# Skip pods that do not support the platform of the current target.
if platform = current_target_definition.platform
next unless spec.supported_on_platform?(platform.name)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual semantic change of the PR, so tiny 😂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for that comment! And also, I am impressed by the CocoaPods API :D

else
# TODO: In a future RN version we should update the Podfile template and
# enable this assertion.
#
# raise Pod::Informative, "Cannot invoke `use_native_modules!` before defining the supported `platform`"
end

# 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
Expand Down Expand Up @@ -101,169 +117,39 @@ def use_native_modules!(config = nil)

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}"
Pod::UI.puts "Auto-linking React Native #{"module".pluralize(found_pods.size)} for target `#{current_target_definition.name}`: #{pods}"
end

config
end

# You can run the tests for this file by running:
# $ ruby packages/platform-ios/native_modules.rb
# $ yarn jest packages/platform-ios/src/config/__tests__/native_modules.test.ts
if $0 == __FILE__
require "minitest/spec"
require "minitest/autorun"

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" => "/root/app/node_modules/react",
"platforms" => {
"ios" => {
"podspecPath" => "/root/app/node_modules/react/React.podspec",
},
"android" => nil,
},
}
@android_package = {
"root" => "/root/app/node_modules/react-native-google-play-game-services",
"platforms" => {
"ios" => nil,
"android" => {
# This is where normally more config would be
},
}
}
@project = {
"ios" => {
"sourceDir" => "/root/app/ios"
}
}
@config = {
"project" => @project,
"dependencies" => {
"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
require "json"
runInput = JSON.parse(ARGF.read)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Ruby script receives a JSON payload via stdin


Pod::Specification.singleton_class.send(:define_method, :from_file) do |podspec_path|
podspec_path.must_equal ios_package["platforms"]["ios"]["podspecPath"]
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

target_definition.singleton_class.send(:define_method, :abstract?) do
false
end
unless runInput["captureStdout"]
Pod::Config.instance.silent = true
end

podfile.singleton_class.send(:define_method, :current_target_definition) do
target_definition
podfile = Pod::Podfile.new do
if runInput["podsActivatedByUser"]
runInput["podsActivatedByUser"].each do |name|
pod(name)
end
end

it "activates iOS pods" do
@podfile.use_native_modules(@config)
@activated_pods.must_equal [{
name: "ios-dep",
options: { path: "../node_modules/react" }
}]
target 'iOS Target' do
platform :ios
use_native_modules!(runInput["dependencyConfig"])
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 []
target 'macOS Target' do
platform :osx
use_native_modules!(runInput["dependencyConfig"])
end
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({ "project" => @project, "dependencies" => {} })
@podfile.use_native_modules({ "project" => @project, "dependencies" => { "pkg-1" => @ios_package }})
@podfile.use_native_modules({
"project" => @project, "dependencies" => { "pkg-1" => @ios_package, "pkg-2" => @ios_package }
})
@printed_messages.must_equal [
"Detected React Native module pod for ios-dep",
"Detected React Native module pods for ios-dep and ios-dep"
]
end

describe "concerning script_phases" do
it "uses the options directly" do
@config["dependencies"]["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["dependencies"]["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
unless runInput["captureStdout"]
puts podfile.to_hash.to_json
end
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…and spits out a serialised version of the Podfile to stdout.

end
3 changes: 2 additions & 1 deletion packages/platform-ios/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"@types/glob": "^7.1.1",
"@types/js-yaml": "^3.12.1",
"@types/lodash": "^4.14.149",
"@types/plist": "^3.0.2"
"@types/plist": "^3.0.2",
"hasbin": "^1.2.3"
},
"files": [
"build",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading