expo-modules-autolinking 55.0.20 → 55.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,16 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 55.0.22 — 2026-05-13
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ [iOS] Added fallback to source for missing framework slice. ([#45664](https://github.com/expo/expo/pull/45664) by [@chrfalch](https://github.com/chrfalch))
18
+
19
+ ## 55.0.21 — 2026-05-05
20
+
21
+ _This version does not introduce any user-facing changes._
22
+
13
23
  ## 55.0.20 — 2026-05-05
14
24
 
15
25
  _This version does not introduce any user-facing changes._
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-autolinking",
3
- "version": "55.0.20",
3
+ "version": "55.0.22",
4
4
  "description": "Scripts that autolink Expo modules.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -38,10 +38,10 @@
38
38
  "memfs": "^3.2.0"
39
39
  },
40
40
  "dependencies": {
41
- "@expo/require-utils": "^55.0.4",
41
+ "@expo/require-utils": "^55.0.5",
42
42
  "@expo/spawn-async": "^1.7.2",
43
43
  "chalk": "^4.1.0",
44
44
  "commander": "^7.2.0"
45
45
  },
46
- "gitHead": "17007db30c487d21b82548365b84180a73af25ff"
46
+ "gitHead": "0675db12d13a5309e3109e8ecf7f6011522194c6"
47
47
  }
@@ -21,16 +21,18 @@ module Expo
21
21
 
22
22
  validate_target_definition()
23
23
 
24
- # Clear stale CocoaPods download cache for precompiled pods.
25
- Expo::PrecompiledModules.clear_cocoapods_cache
26
-
27
24
  resolve_result = resolve()
28
25
 
29
26
  Expo::PackagesConfig.instance.coreFeatures = resolve_result['coreFeatures']
30
27
 
31
- # Pass buildFromSource configuration to PrecompiledModules
32
28
  configuration = resolve_result['configuration'] || {}
33
- Expo::PrecompiledModules.build_from_source = configuration['buildFromSource'] || []
29
+ Expo::PrecompiledModules.configure(
30
+ target_platform: @target_definition.platform,
31
+ build_from_source: configuration['buildFromSource'] || []
32
+ )
33
+
34
+ # Clear stale CocoaPods download cache for precompiled pods.
35
+ Expo::PrecompiledModules.clear_cocoapods_cache
34
36
 
35
37
  @packages = resolve_result['modules'].map { |json_package| Package.new(json_package) }
36
38
  @extraPods = resolve_result['extraDependencies']
@@ -29,7 +29,9 @@
29
29
  require 'fileutils'
30
30
  require 'json'
31
31
  require 'net/http'
32
+ require 'open3'
32
33
  require 'set'
34
+ require 'tempfile'
33
35
  require 'uri'
34
36
 
35
37
  module Expo
@@ -76,6 +78,8 @@ module Expo
76
78
  @framework_owner_map = nil # Hash: framework_name -> owning_pod_name
77
79
  @failed_remote_downloads = Set.new
78
80
  @warned_no_prebuilt_react = false
81
+ @target_platform = nil
82
+ @xcframework_slice_cache = nil
79
83
 
80
84
  class << self
81
85
  # Returns the build flavor (debug/release) for precompiled modules.
@@ -113,13 +117,26 @@ module Expo
113
117
  false
114
118
  end
115
119
 
116
- # Sets the list of package name patterns that should be built from source
117
- # instead of using precompiled xcframeworks. Patterns are treated as regexes
118
- # (e.g., ".*" for all, "expo-audio" for exact match, "expo-.*" for prefix).
120
+ def configure(target_platform: nil, build_from_source: nil)
121
+ self.target_platform = target_platform unless target_platform.nil?
122
+ self.build_from_source = build_from_source unless build_from_source.nil?
123
+ end
124
+
119
125
  def build_from_source=(patterns)
120
126
  @build_from_source_patterns = (patterns || []).map { |p| Regexp.new("^#{p}$") }
121
127
  end
122
128
 
129
+ def target_platform=(platform)
130
+ normalized = normalize_xcframework_platform(platform)
131
+ return if @target_platform == normalized
132
+
133
+ @target_platform = normalized
134
+ @all_bundled_frameworks = nil
135
+ @claimed_vendored_frameworks = nil
136
+ @framework_owner_map = nil
137
+ @xcframework_slice_cache = nil
138
+ end
139
+
123
140
  # Checks if a pod is configured to be built from source via buildFromSource.
124
141
  # Matches against both the pod name and the npm package name.
125
142
  def build_from_source?(pod_name)
@@ -1761,6 +1778,7 @@ module Expo
1761
1778
  product_name = pod_info[:product_name] || pod_name
1762
1779
  tarball = resolve_prebuilt_tarball(pod_info, product_name, build_flavor, pod_name)
1763
1780
  return { available: false, reason: :missing_tarball, path: tarball } unless File.exist?(tarball)
1781
+ return { available: false, reason: :missing_platform_slice, path: tarball } unless xcframework_supports_target_platform?(tarball)
1764
1782
 
1765
1783
  { available: true, resolved: [pod_info, product_name, tarball] }
1766
1784
  end
@@ -1818,6 +1836,74 @@ module Expo
1818
1836
  remote_tarball
1819
1837
  end
1820
1838
 
1839
+ def normalize_xcframework_platform(platform)
1840
+ name = platform.respond_to?(:name) ? platform.name : platform
1841
+ name = name.string_name if name.respond_to?(:string_name)
1842
+ normalized = name.to_s.downcase
1843
+ normalized == 'osx' ? 'macos' : normalized
1844
+ end
1845
+
1846
+ def xcframework_supports_target_platform?(path)
1847
+ return true unless @target_platform
1848
+
1849
+ @xcframework_slice_cache ||= {}
1850
+ cache_key = [path, @target_platform]
1851
+ return @xcframework_slice_cache[cache_key] if @xcframework_slice_cache.key?(cache_key)
1852
+
1853
+ @xcframework_slice_cache[cache_key] = begin
1854
+ info_plists = File.directory?(path) ? [read_plist(File.join(path, 'Info.plist'))] : read_xcframework_info_plists_from_tarball(path)
1855
+ info_plists.any? && info_plists.all? { |info_plist| info_plist_supports_target_platform?(info_plist) }
1856
+ end
1857
+ end
1858
+
1859
+ def info_plist_supports_target_platform?(info_plist)
1860
+ return false unless info_plist
1861
+
1862
+ available_libraries = info_plist['AvailableLibraries']
1863
+ return false unless available_libraries.is_a?(Array)
1864
+
1865
+ available_libraries.any? do |library|
1866
+ normalize_xcframework_platform(library['SupportedPlatform']) == @target_platform
1867
+ end
1868
+ end
1869
+
1870
+ def read_xcframework_info_plists_from_tarball(tarball)
1871
+ entries_output, status = Open3.capture2e('tar', 'tzf', tarball)
1872
+ unless status.success?
1873
+ Pod::UI.warn "[Expo-precompiled] Failed to inspect #{File.basename(tarball)}: #{entries_output.strip}"
1874
+ return []
1875
+ end
1876
+
1877
+ entries = entries_output.lines.map(&:strip)
1878
+ plist_entries = entries.select { |entry| entry.end_with?('.xcframework/Info.plist') }
1879
+ Pod::UI.warn "[Expo-precompiled] No XCFramework Info.plist found in #{File.basename(tarball)}" if plist_entries.empty?
1880
+
1881
+ plist_entries.filter_map do |entry|
1882
+ plist_data, plist_status = Open3.capture2e('tar', 'xOzf', tarball, entry)
1883
+ unless plist_status.success?
1884
+ Pod::UI.warn "[Expo-precompiled] Failed to extract #{entry} from #{File.basename(tarball)}: #{plist_data.strip}"
1885
+ next
1886
+ end
1887
+
1888
+ Tempfile.create(['expo-xcframework-info', '.plist']) do |file|
1889
+ file.binmode
1890
+ file.write(plist_data)
1891
+ file.flush
1892
+ read_plist(file.path)
1893
+ end
1894
+ end
1895
+ rescue StandardError => e
1896
+ Pod::UI.warn "[Expo-precompiled] Failed to inspect #{File.basename(tarball)}: #{e.message}"
1897
+ []
1898
+ end
1899
+
1900
+ def read_plist(path)
1901
+ Xcodeproj::Plist.read_from_path(path)
1902
+ rescue StandardError => e
1903
+ Pod::UI.warn "[Expo-precompiled] Failed to read #{File.basename(path)}: #{e.message}"
1904
+ nil
1905
+ end
1906
+
1821
1907
  def failed_remote_downloads
1822
1908
  @failed_remote_downloads ||= Set.new
1823
1909
  end
@@ -2096,6 +2182,8 @@ module Expo
2096
2182
  'prebuilt config not found'
2097
2183
  when :missing_tarball
2098
2184
  'prebuilt tarball not found'
2185
+ when :missing_platform_slice
2186
+ "prebuilt xcframework does not contain a slice for #{@target_platform}"
2099
2187
  when :dependency_unavailable
2100
2188
  reason = format_prebuilt_unavailable_reason(reason: info[:dependency_reason], path: info[:dependency_path])
2101
2189
  "dependency #{info[:dependency]} is not using prebuilt: #{reason}"