284 lines
11 KiB
Ruby
284 lines
11 KiB
Ruby
require_relative 'constants'
|
|
require_relative 'package'
|
|
require_relative 'packages_config'
|
|
|
|
# Require extensions to CocoaPods' classes
|
|
require_relative 'cocoapods/sandbox'
|
|
require_relative 'cocoapods/target_definition'
|
|
require_relative 'cocoapods/user_project_integrator'
|
|
require_relative 'cocoapods/installer'
|
|
|
|
module Expo
|
|
class AutolinkingManager
|
|
require 'colored2'
|
|
include Pod
|
|
|
|
public def initialize(podfile, target_definition, options)
|
|
@podfile = podfile
|
|
@target_definition = target_definition
|
|
@options = options
|
|
|
|
validate_target_definition()
|
|
resolve_result = resolve()
|
|
|
|
Expo::PackagesConfig.instance.coreFeatures = resolve_result['coreFeatures']
|
|
|
|
@packages = resolve_result['modules'].map { |json_package| Package.new(json_package) }
|
|
@extraPods = resolve_result['extraDependencies']
|
|
end
|
|
|
|
public def use_expo_modules!
|
|
if has_packages?
|
|
return
|
|
end
|
|
|
|
global_flags = @options.fetch(:flags, {})
|
|
tests_only = @options.fetch(:testsOnly, false)
|
|
include_tests = @options.fetch(:includeTests, false)
|
|
|
|
# Add any additional framework modules to patch using the patched Podfile class in installer.rb
|
|
# We'll be reading from Podfile.properties.json and optionally parameters passed to use_expo_modules!
|
|
podfile_properties = JSON.parse(File.read(File.join(Pod::Config.instance.project_root, 'Podfile.properties.json'))) rescue {}
|
|
additional_framework_modules_to_patch = @options.fetch(:additionalFrameworkModulesToPatch, []) +
|
|
JSON.parse(podfile_properties['ios.forceStaticLinking'] || "[]")
|
|
|
|
Pod::UI.puts("Forcing static linking for pods: #{additional_framework_modules_to_patch}") if !additional_framework_modules_to_patch.empty?
|
|
@podfile.expo_add_modules_to_patch(additional_framework_modules_to_patch) if !additional_framework_modules_to_patch.empty?
|
|
|
|
project_directory = Pod::Config.instance.project_root
|
|
|
|
UI.section 'Using Expo modules' do
|
|
@packages.each { |package|
|
|
package.pods.each { |pod|
|
|
# The module can already be added to the target, in which case we can just skip it.
|
|
# This allows us to add a pod before `use_expo_modules` to provide custom flags.
|
|
if @target_definition.dependencies.any? { |dependency| dependency.name == pod.pod_name }
|
|
UI.message '— ' << package.name.green << ' is already added to the target'.yellow
|
|
next
|
|
end
|
|
|
|
# Skip if the podspec doesn't include the platform for the current target.
|
|
unless pod.supports_platform?(@target_definition.platform)
|
|
UI.message '- ' << package.name.green << " doesn't support #{@target_definition.platform.string_name} platform".yellow
|
|
next
|
|
end
|
|
|
|
# Ensure that the dependencies of packages with Swift code use modular headers, otherwise
|
|
# `pod install` may fail if there is no `use_modular_headers!` declaration or
|
|
# `:modular_headers => true` is not used for this particular dependency.
|
|
# The latter require adding transitive dependencies to user's Podfile that we'd rather like to avoid.
|
|
if package.has_something_to_link?
|
|
use_modular_headers_for_dependencies(pod.spec.all_dependencies)
|
|
end
|
|
|
|
podspec_dir_path = Pathname.new(pod.podspec_dir).relative_path_from(project_directory).to_path
|
|
|
|
debug_configurations = @target_definition.build_configurations ? @target_definition.build_configurations.select { |config| config.include?('Debug') }.keys : ['Debug']
|
|
|
|
pod_options = {
|
|
:path => podspec_dir_path,
|
|
:configuration => package.debugOnly ? debug_configurations : [] # An empty array means all configurations
|
|
}.merge(global_flags, package.flags)
|
|
|
|
if tests_only || include_tests
|
|
test_specs_names = pod.spec.test_specs.map { |test_spec|
|
|
test_spec.name.delete_prefix(pod.spec.name + "/")
|
|
}
|
|
|
|
# Jump to the next package when it doesn't have any test specs (except interfaces, they're required)
|
|
# TODO: Can remove interface check once we move all the interfaces into the core.
|
|
next if tests_only && test_specs_names.empty? && !pod.pod_name.end_with?('Interface')
|
|
|
|
pod_options[:testspecs] = test_specs_names
|
|
end
|
|
|
|
# Install the pod.
|
|
@podfile.pod(pod.pod_name, pod_options)
|
|
|
|
# TODO: Can remove this once we move all the interfaces into the core.
|
|
next if pod.pod_name.end_with?('Interface')
|
|
|
|
UI.message "— #{package.name.green} (#{package.version})"
|
|
}
|
|
}
|
|
end
|
|
|
|
@extraPods.each { |pod|
|
|
UI.info "Adding extra pod - #{pod['name']} (#{pod['version'] || '*'})"
|
|
requirements = Array.new
|
|
requirements << pod['version'] if pod['version']
|
|
options = Hash.new
|
|
options[:configurations] = pod['configurations'] if pod['configurations']
|
|
options[:modular_headers] = pod['modular_headers'] if pod['modular_headers']
|
|
options[:source] = pod['source'] if pod['source']
|
|
options[:path] = pod['path'] if pod['path']
|
|
options[:podspec] = pod['podspec'] if pod['podspec']
|
|
options[:testspecs] = pod['testspecs'] if pod['testspecs']
|
|
options[:git] = pod['git'] if pod['git']
|
|
options[:branch] = pod['branch'] if pod['branch']
|
|
options[:tag] = pod['tag'] if pod['tag']
|
|
options[:commit] = pod['commit'] if pod['commit']
|
|
requirements << options
|
|
@podfile.pod(pod['name'], *requirements)
|
|
}
|
|
self
|
|
end
|
|
|
|
# Spawns `expo-module-autolinking generate-modules-provider` command.
|
|
public def generate_modules_provider(target_name, target_path)
|
|
Process.wait IO.popen(generate_modules_provider_command_args(target_path)).pid
|
|
end
|
|
|
|
# If there is any package to autolink.
|
|
public def has_packages?
|
|
@packages.empty?
|
|
end
|
|
|
|
# Filters only these packages that needs to be included in the generated modules provider.
|
|
public def packages_to_generate
|
|
platform = @target_definition.platform
|
|
|
|
@packages.select do |package|
|
|
# Check whether the package has any module to autolink
|
|
# and if there is any pod that supports target's platform.
|
|
package.has_something_to_link? && package.pods.any? { |pod| pod.supports_platform?(platform) }
|
|
end
|
|
end
|
|
|
|
# Returns the provider name which is also a name of the generated file
|
|
public def modules_provider_name
|
|
@options.fetch(:providerName, Constants::MODULES_PROVIDER_FILE_NAME)
|
|
end
|
|
|
|
# Absolute path to `Pods/Target Support Files/<pods target name>/<modules provider file>` within the project path
|
|
public def modules_provider_path(target)
|
|
File.join(target.support_files_dir, modules_provider_name)
|
|
end
|
|
|
|
# For now there is no need to generate the modules provider for testing.
|
|
public def should_generate_modules_provider?
|
|
return !@options.fetch(:testsOnly, false)
|
|
end
|
|
|
|
# Returns the platform name of the current target definition.
|
|
# Note that it is suitable to be presented to the user (i.e. is not lowercased).
|
|
public def platform_name
|
|
return @target_definition.platform&.string_name
|
|
end
|
|
|
|
# Returns the app project root if provided in the options.
|
|
public def custom_app_root
|
|
# TODO: Follow up on renaming `:projectRoot` and migrate to `appRoot`
|
|
return @options.fetch(:appRoot, @options.fetch(:projectRoot, nil))
|
|
end
|
|
|
|
# privates
|
|
|
|
private def resolve
|
|
json = []
|
|
|
|
IO.popen(resolve_command_args) do |data|
|
|
while line = data.gets
|
|
json << line
|
|
end
|
|
end
|
|
|
|
begin
|
|
JSON.parse(json.join())
|
|
rescue => error
|
|
raise "Couldn't parse JSON coming from `expo-modules-autolinking` command:\n#{error}"
|
|
end
|
|
end
|
|
|
|
public def base_command_args
|
|
search_paths = @options.fetch(:searchPaths, @options.fetch(:modules_paths, nil))
|
|
ignore_paths = @options.fetch(:ignorePaths, nil)
|
|
exclude = @options.fetch(:exclude, [])
|
|
args = []
|
|
|
|
if !search_paths.nil? && !search_paths.empty?
|
|
args.concat(search_paths)
|
|
end
|
|
|
|
if !ignore_paths.nil? && !ignore_paths.empty?
|
|
args.concat(['--ignore-paths'], ignore_paths)
|
|
end
|
|
|
|
if !exclude.nil? && !exclude.empty?
|
|
args.concat(['--exclude'], exclude)
|
|
end
|
|
|
|
args
|
|
end
|
|
|
|
private def node_command_args(command_name)
|
|
eval_command_args = [
|
|
'node',
|
|
'--no-warnings',
|
|
'--eval',
|
|
'require(\'expo/bin/autolinking\')',
|
|
'expo-modules-autolinking',
|
|
command_name,
|
|
'--platform',
|
|
'apple'
|
|
]
|
|
return eval_command_args.concat(base_command_args())
|
|
end
|
|
|
|
private def resolve_command_args
|
|
resolve_command_args = ['--json']
|
|
|
|
project_root = @options.fetch(:projectRoot, nil)
|
|
if project_root
|
|
resolve_command_args.concat(['--project-root', project_root])
|
|
end
|
|
|
|
node_command_args('resolve').concat(resolve_command_args)
|
|
end
|
|
|
|
public def generate_modules_provider_command_args(target_path)
|
|
command_args = ['--target', target_path]
|
|
|
|
if !custom_app_root.nil?
|
|
command_args.concat(['--app-root', custom_app_root])
|
|
end
|
|
|
|
node_command_args('generate-modules-provider').concat(
|
|
command_args,
|
|
['--packages'],
|
|
packages_to_generate.map(&:name)
|
|
)
|
|
end
|
|
|
|
private def use_modular_headers_for_dependencies(dependencies)
|
|
dependencies.each { |dependency|
|
|
# The dependency name might be a subspec like `ReactCommon/turbomodule/core`,
|
|
# but the modular headers need to be enabled for the entire `ReactCommon` spec anyway,
|
|
# so we're stripping the subspec path from the dependency name.
|
|
root_spec_name = dependency.name.partition('/').first
|
|
|
|
unless @target_definition.build_pod_as_module?(root_spec_name)
|
|
UI.info "[Expo] ".blue << "Enabling modular headers for pod #{root_spec_name.green}"
|
|
|
|
# This is an equivalent to setting `:modular_headers => true` for the specific dependency.
|
|
@target_definition.set_use_modular_headers_for_pod(root_spec_name, true)
|
|
end
|
|
}
|
|
end
|
|
|
|
# Validates whether the Expo modules can be autolinked in the given target definition.
|
|
private def validate_target_definition
|
|
# The platform must be declared within the current target (e.g. `platform :ios, '13.0'`)
|
|
if platform_name.nil?
|
|
raise "Undefined platform for target #{@target_definition.name}, make sure to call `platform` method globally or inside the target"
|
|
end
|
|
|
|
# The declared platform must be iOS, macOS or tvOS, others are not supported.
|
|
unless ['iOS', 'macOS', 'tvOS'].include?(platform_name)
|
|
raise "Target #{@target_definition.name} is dedicated to #{platform_name} platform, which is not supported by Expo Modules"
|
|
end
|
|
end
|
|
|
|
end # class AutolinkingManager
|
|
end # module Expo
|