mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Allow library podspec to declare Swift Package Manager dependencies (#44627)
Summary:
React-Native uses Cocapods for native dependency management on iOS. While CocoaPods is flexible and popular, Apple's Swift Package Manager is the new standard. Currently consuming packages available only via Swift Package Manager is not possible. This change implements a single extension so .podspec files can declare Swift Package Manager dependencies via
```ruby
ReactNativePodsUtils.spm_dependency(s,
url: 'https://github.com/apple/swift-atomics.git',
requirement: {kind: 'upToNextMajorVersion', minimumVersion: '1.1.0'},
products: ['Atomics']
)
```
bypass-github-export-checks
## Changelog:
[IOS] [ADDED] - libraries can now declare Swift Package Manager dependencies in their .podspec with `ReactNativePodsUtils.spm_dependency`
Pull Request resolved: https://github.com/facebook/react-native/pull/44627
Test Plan:
https://github.com/mfazekas/rn-spm-rfc-poc/
Is a simple demo for the feature:
1. Podspec declare dependency with:
```ruby
if const_defined?(:ReactNativePodsUtils) && ReactNativePodsUtils.respond_to?(:spm_dependency)
ReactNativePodsUtils.spm_dependency(s,
url: 'https://github.com/apple/swift-atomics.git',
requirement: {kind: 'upToNextMajorVersion', minimumVersion: '1.1.0'},
products: ['Atomics']
)
else
raise "Please upgrade React Native to >=0.75.0 to use SPM dependencies."
end
```
2. [`import Atomics`](https://github.com/mfazekas/rn-spm-rfc-poc/blob/e4eb1034f7498dedee4cb673d327c34a6048bda2/ios/MultiplyInSwift.swift#L1C2-L1C15) and [`ManagedAtomic`](https://github.com/mfazekas/rn-spm-rfc-poc/blob/e4eb1034f7498dedee4cb673d327c34a6048bda2/ios/MultiplyInSwift.swift#L7-L13) is used in the code
3.) `spm_dependency` causes the dependency to be added via `post_install` hook in the workspace
<img width="261" alt="image" src="https://github.com/facebook/react-native/assets/52435/ad6aee1c-ac88-4c84-8aa3-50e148c4f5b2">
4.) `spm_dependecy` causes the library to be linked with `Atomics` library
<img width="817" alt="image" src="https://github.com/facebook/react-native/assets/52435/bfc8dfc0-aeb7-4c75-acbd-937eab1cbf80">
Limitations:
1.) only works `USE_FRAMEWORKS=dynamic pod install` otherwise the linker fails [with known Xcode issue - duplicate link issue](https://forums.swift.org/t/objc-flag-causes-duplicate-symbols-with-swift-packages/27926)
2.) .xcworkspace needs to be reopened after `pod install` - this could be worked around by not removing/readding spm dependencies
### See also:
https://github.com/react-native-community/discussions-and-proposals/issues/587#issuecomment-2117025448
https://github.com/react-native-community/discussions-and-proposals/pull/787
Reviewed By: cortinico
Differential Revision: D58947066
Pulled By: cipolleschi
fbshipit-source-id: ae3bf955cd36a02cc78472595fa003cc9e843dd5
This commit is contained in:
committed by
Blake Friedman
parent
4cec121a7b
commit
2e06efbf5d
@@ -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
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user