require_relative 'constants' require_relative 'package' # Require extensions to CocoaPods' classes require_relative 'cocoapods/pod_target' require_relative 'cocoapods/sandbox' require_relative 'cocoapods/target_definition' require_relative 'cocoapods/umbrella_header_generator' require_relative 'cocoapods/user_project_integrator' module Expo class AutolinkingManager require 'colored2' include Pod public def initialize(podfile, target_definition, options) @podfile = podfile @target_definition = target_definition @options = options @packages = resolve()['modules'].map { |json_package| Package.new(json_package) } 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) 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 podspec_dir_path = Pathname.new(pod.podspec_dir).relative_path_from(project_directory).to_path # 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_swift_modules_to_link? podspec = get_podspec_for_pod(pod) use_modular_headers_for_dependencies(podspec.all_dependencies) end pod_options = { :path => podspec_dir_path, :configuration => package.debugOnly ? ['Debug'] : [] # An empty array means all configurations }.merge(global_flags, package.flags) if tests_only || include_tests podspec = podspec || get_podspec_for_pod(pod) test_specs_names = podspec.test_specs.map { |test_spec| test_spec.name.delete_prefix(podspec.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 self end # Spawns `expo-module-autolinking generate-package-list` command. public def generate_package_list(target_name, target_path) Process.wait IO.popen(generate_package_list_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 @packages.select { |package| package.modules.any? } 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 # 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 # 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 private def node_command_args(command_name) search_paths = @options.fetch(:searchPaths, @options.fetch(:modules_paths, nil)) ignore_paths = @options.fetch(:ignorePaths, nil) exclude = @options.fetch(:exclude, []) args = [ 'node', '--eval', 'require(\'expo-modules-autolinking\')(process.argv.slice(1))', command_name, '--platform', 'ios' ] 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 resolve_command_args node_command_args('resolve').concat(['--json']) end private def generate_package_list_command_args(target_path) node_command_args('generate-package-list').concat([ '--target', target_path ]) end private def get_podspec_for_pod(pod) podspec_file_path = File.join(pod.podspec_dir, pod.pod_name + ".podspec") return Pod::Specification.from_file(podspec_file_path) 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 end # class AutolinkingManager end # module Expo