diff --git a/packages/react-native/scripts/cocoapods/spm.rb b/packages/react-native/scripts/cocoapods/spm.rb new file mode 100644 index 00000000000..78ca3c31e08 --- /dev/null +++ b/packages/react-native/scripts/cocoapods/spm.rb @@ -0,0 +1,94 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +class SPMManager + def initialize() + @dependencies_by_pod = {} + end + + def dependency(pod_spec, url:, requirement:, products:) + @dependencies_by_pod[pod_spec.name] ||= [] + @dependencies_by_pod[pod_spec.name] << { url: url, requirement: requirement, products: products} + end + + def apply_on_post_install(installer) + project = installer.pods_project + + log 'Cleaning old SPM dependencies from Pods project' + clean_spm_dependencies_from_target(project, @dependencies_by_pod) + log 'Adding SPM dependencies to Pods project' + @dependencies_by_pod.each do |pod_name, dependencies| + dependencies.each do |spm_spec| + log "Adding SPM dependency on product #{spm_spec[:products]}" + add_spm_to_target( + project, + project.targets.find { |t| t.name == pod_name}, + spm_spec[:url], + spm_spec[:requirement], + spm_spec[:products] + ) + log " Adding workaround for Swift package not found issue" + target = project.targets.find { |t| t.name == pod_name} + target.build_configurations.each do |config| + target.build_settings(config.name)['SWIFT_INCLUDE_PATHS'] ||= ['$(inherited)'] + search_path = '${SYMROOT}/${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}/' + unless target.build_settings(config.name)['SWIFT_INCLUDE_PATHS'].include?(search_path) + target.build_settings(config.name)['SWIFT_INCLUDE_PATHS'].push(search_path) + end + end + end + end + + unless @dependencies_by_pod.empty? + log_warning "If you're using Xcode 15 or earlier you might need to close and reopen the Xcode workspace" + unless ENV["USE_FRAMEWORKS"] == "dynamic" + @dependencies_by_pod.each do |pod_name, dependencies| + log_warning "Pod #{pod_name} is using swift package(s) #{dependencies.map{|i| i[:products]}.flatten.uniq.join(", ")} with static linking, this might cause linker errors. Consider using USE_FRAMEOWRKS=dynamic, see https://github.com/facebook/react-native/pull/44627#issuecomment-2123119711 for more information" + end + end + end + end + + private + + def log(msg) + ::Pod::UI.puts "[SPM] #{msg}" + end + + def log_warning(msg) + ::Pod::UI.puts "\n\n[SPM] WARNING!!! #{msg}\n\n" + end + + def clean_spm_dependencies_from_target(project, new_targets) + project.root_object.package_references.delete_if { |pkg| (pkg.class == Xcodeproj::Project::Object::XCRemoteSwiftPackageReference) } + end + + def add_spm_to_target(project, target, url, requirement, products) + pkg_class = Xcodeproj::Project::Object::XCRemoteSwiftPackageReference + ref_class = Xcodeproj::Project::Object::XCSwiftPackageProductDependency + pkg = project.root_object.package_references.find { |p| p.class == pkg_class && p.repositoryURL == url } + if !pkg + pkg = project.new(pkg_class) + pkg.repositoryURL = url + pkg.requirement = requirement + log(" Adding package to workspace: #{pkg.inspect}") + project.root_object.package_references << pkg + end + products.each do |product_name| + ref = target.package_product_dependencies.find do |r| + r.class == ref_class && r.package == pkg && r.product_name == product_name + end + next if ref + + log(" Adding product dependency #{product_name} to #{target.name}") + ref = project.new(ref_class) + ref.package = pkg + ref.product_name = product_name + target.package_product_dependencies << ref + end + end +end + +SPM = SPMManager.new diff --git a/packages/react-native/scripts/react_native_pods.rb b/packages/react-native/scripts/react_native_pods.rb index 7c158908c99..66e90e55599 100644 --- a/packages/react-native/scripts/react_native_pods.rb +++ b/packages/react-native/scripts/react_native_pods.rb @@ -17,6 +17,7 @@ require_relative './cocoapods/local_podspec_patch.rb' require_relative './cocoapods/runtime.rb' require_relative './cocoapods/helpers.rb' require_relative './cocoapods/privacy_manifest_utils.rb' +require_relative './cocoapods/spm.rb' # Importing to expose use_native_modules! require_relative './cocoapods/autolinking.rb' @@ -242,6 +243,18 @@ def install_modules_dependencies(spec, new_arch_enabled: NewArchitectureHelper.n NewArchitectureHelper.install_modules_dependencies(spec, new_arch_enabled, folly_config[:version]) end + +# This function can be used by library developer to declare a SwiftPackageManager dependency. +# +# Parameters: +# - spec: The spec the Swift Package Manager dependency has to be added to +# - url: The URL of the Swift Package Manager dependency +# - requirement: The version requirement of the Swift Package Manager dependency (eg. ` {kind: 'upToNextMajorVersion', minimumVersion: '5.9.1'},`) +# - products: The product/target of the Swift Package Manager dependency (eg. AlamofireDynamic) +def spm_dependency(spec, url:, requirement:, products:) + SPM.dependency(spec, url: url, requirement: requirement, products: products) +end + # It returns the default flags. # deprecated. def get_default_flags() @@ -297,6 +310,7 @@ def react_native_post_install( ReactNativePodsUtils.updateOSDeploymentTarget(installer) ReactNativePodsUtils.set_dynamic_frameworks_flags(installer) ReactNativePodsUtils.add_ndebug_flag_to_pods_in_release(installer) + SPM.apply_on_post_install(installer) if privacy_file_aggregation_enabled PrivacyManifestUtils.add_aggregated_privacy_manifest(installer)