xcframework_converter 0.4.0 → 0.7.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ed138cd025eb265a64d714f9342520dd10ca44c9d78120a20eb6695623f1e86
4
- data.tar.gz: 870d95ae53553761641a9572a6e08f58103435f4312035749794353c2d34979c
3
+ metadata.gz: 7a1169fa0a3c860b9ddffd50b22592d95ba42d5f32a1f97dbc7507827491ac47
4
+ data.tar.gz: 05b3b160406d9b1db0db39cc0c999e4c5370d08be44e5dbd5f069089751ab890
5
5
  SHA512:
6
- metadata.gz: f5f9e9d85f7385b2a1f7406b5942741c7060c9263e751a9e59bdeb13c234e117c0ba1f58a3f3f2c0f509d797b76c9b51afbb12131fde7bdfefe9940a29e4ac4d
7
- data.tar.gz: a2201235913e84433ee593288511da552b23cdf9e0e09ca0efdcc525f82ebd2e884d9d134b71f7323ed07a7b1ea88d70d49406492c51e2834afa3af02c1071e9
6
+ metadata.gz: 4356364e00c34af6f9a817d9a3bea2411f46ab0fee145586bbb48d9f1ffa23335a1728970535ea2e8e94559eae15cb3de4abecce8c983fa1b9b653235878a50e
7
+ data.tar.gz: a29311d0abee698408d037bbca8ebba0a9603e2eb9c4851c4b74c2581688a324fe529f26a374c04ea29405a45174931eff113362e99edf460b953dfb66364f8a
data/bin/xcfconvert CHANGED
@@ -5,8 +5,10 @@ require 'bundler/setup'
5
5
  require 'xcframework_converter'
6
6
 
7
7
  if ARGV.empty?
8
- warn 'Usage: xcfconvert <path/to/Framework.framework>'
8
+ warn 'Usage: xcfconvert <path/to/Framework.framework> [ios|tvos|watchos]'
9
9
  exit 1
10
10
  end
11
11
 
12
- XCFrameworkConverter.convert_framework_to_xcframework(Pathname.new(ARGV[0]).realpath)
12
+ path = Pathname.new(ARGV.shift).realpath
13
+ platform = ARGV.shift&.to_sym || :ios
14
+ XCFrameworkConverter.convert_framework_to_xcframework(path, platform)
@@ -104,7 +104,7 @@ public enum Transmogrifier {
104
104
  return datas.merge()
105
105
  }
106
106
 
107
- private static func updateVersionMin(_ data: Data, _ offset: UInt32, minos: UInt32, sdk: UInt32) -> Data {
107
+ private static func createLcBuildVersion(minos: UInt32, sdk: UInt32) -> Data {
108
108
  var command = build_version_command(cmd: UInt32(LC_BUILD_VERSION),
109
109
  cmdsize: UInt32(MemoryLayout<build_version_command>.stride),
110
110
  platform: UInt32(PLATFORM_IOSSIMULATOR),
@@ -115,6 +115,15 @@ public enum Transmogrifier {
115
115
  return Data(bytes: &command, count: MemoryLayout<build_version_command>.stride)
116
116
  }
117
117
 
118
+ private static func updateLcBuildVersion(_ data: Data, minos: UInt32, sdk: UInt32) -> Data {
119
+ var command: build_version_command = data.asStruct()
120
+ command.platform = UInt32(PLATFORM_IOSSIMULATOR)
121
+ command.minos = minos << 16 | 0 << 8 | 0
122
+ command.sdk = sdk << 16 | 0 << 8 | 0
123
+
124
+ return Data(bytes: &command, count: data.count)
125
+ }
126
+
118
127
  private static func updateDataInCode(_ data: Data, _ offset: UInt32) -> Data {
119
128
  var command: linkedit_data_command = data.asStruct()
120
129
  command.dataoff += offset
@@ -154,8 +163,6 @@ public enum Transmogrifier {
154
163
  }
155
164
 
156
165
  if contains_LC_VERSION_MIN_IPHONEOS {
157
- // `offset` is kind of a magic number here, since we know that's the only meaningful change to binary size
158
- // having a dynamic `offset` requires two passes over the load commands and is left as an exercise to the reader
159
166
  return updatePreiOS12ObjectFile
160
167
  } else {
161
168
  return updatePostiOS12ObjectFile
@@ -167,7 +174,7 @@ public enum Transmogrifier {
167
174
  let cmd = Int32(bitPattern: lc.loadCommand)
168
175
  switch cmd {
169
176
  case LC_BUILD_VERSION:
170
- return updateVersionMin(lc, 0, minos: minos, sdk: sdk)
177
+ return updateLcBuildVersion(lc, minos: minos, sdk: sdk)
171
178
  default:
172
179
  return lc
173
180
  }
@@ -182,28 +189,25 @@ public enum Transmogrifier {
182
189
  case LC_SEGMENT_64:
183
190
  return updateSegment64(lc, offset)
184
191
  case LC_VERSION_MIN_IPHONEOS:
185
- return updateVersionMin(lc, offset, minos: minos, sdk: sdk)
192
+ return createLcBuildVersion(minos: minos, sdk: sdk)
186
193
  case LC_DATA_IN_CODE, LC_LINKER_OPTIMIZATION_HINT:
187
194
  return updateDataInCode(lc, offset)
188
195
  case LC_SYMTAB:
189
196
  return updateSymTab(lc, offset)
190
197
  case LC_BUILD_VERSION:
191
- return updateVersionMin(lc, offset, minos: minos, sdk: sdk)
198
+ fatalError("pre-12 object file shold not contain LC_BUILD_VERSION!")
192
199
  default:
193
200
  return lc
194
201
  }
195
202
  }
196
203
 
197
204
  static func updateDylibFile(lc: Data, minos: UInt32, sdk: UInt32) -> Data {
198
- // `offset` is kind of a magic number here, since we know that's the only meaningful change to binary size
199
- // having a dynamic `offset` requires two passes over the load commands and is left as an exercise to the reader
200
- let offset = UInt32(abs(MemoryLayout<build_version_command>.stride - MemoryLayout<version_min_command>.stride))
201
205
  let cmd = Int32(bitPattern: lc.loadCommand)
202
206
  guard cmd != LC_BUILD_VERSION else {
203
207
  fatalError("This arm64 binary already contains an LC_BUILD_VERSION load command!")
204
208
  }
205
209
  if cmd == LC_VERSION_MIN_IPHONEOS {
206
- return updateVersionMin(lc, offset, minos: minos, sdk: sdk)
210
+ return createLcBuildVersion(minos: minos, sdk: sdk)
207
211
  }
208
212
  return lc
209
213
  }
@@ -2,10 +2,12 @@
2
2
 
3
3
  require 'cocoapods'
4
4
  require 'cocoapods/xcode/xcframework'
5
+ require 'digest/md5'
5
6
  require 'fileutils'
7
+ require 'shellwords'
6
8
  require 'xcodeproj'
7
9
 
8
- # rubocop:disable Metrics/AbcSize
10
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
9
11
 
10
12
  module XCFrameworkConverter
11
13
  # Patches a binary (static or dynamic), turning an arm64-device into an arm64-simualtor.
@@ -35,9 +37,8 @@ module XCFrameworkConverter
35
37
  extracted_path = slice.path.join('arm64.dylib')
36
38
  `xcrun lipo \"#{slice.binary_path}\" -thin arm64 -output \"#{extracted_path}\"`
37
39
 
38
- file = MachO::MachOFile.new(extracted_path)
39
- sdk_version = file[:LC_VERSION_MIN_IPHONEOS].first.version_string
40
- `xcrun vtool -arch arm64 -set-build-version 7 #{sdk_version} #{sdk_version} -replace -output \"#{extracted_path}\" \"#{extracted_path}\"`
40
+ minos_version, sdk_version = version_strings(extracted_path).map(&:to_i)
41
+ `xcrun vtool -arch arm64 -set-build-version 7 #{minos_version} #{sdk_version} -replace -output \"#{extracted_path}\" \"#{extracted_path}\"`
41
42
  `xcrun lipo \"#{slice.binary_path}\" -replace arm64 \"#{extracted_path}\" -output \"#{slice.binary_path}\"`
42
43
  extracted_path.rmtree
43
44
  end
@@ -61,21 +62,61 @@ module XCFrameworkConverter
61
62
  `xcrun lipo \"#{slice.binary_path}\" -thin arm64 -output \"#{extracted_path}\"`
62
63
  extracted_path_dir = slice.path.join('arm64-objects')
63
64
  extracted_path_dir.mkdir
64
- `cd \"#{extracted_path_dir}\" ; ar x \"#{extracted_path}\"`
65
- Dir[extracted_path_dir.join('*.o')].each do |object_file|
66
- file = MachO::MachOFile.new(object_file)
67
- sdk_version = file[:LC_VERSION_MIN_IPHONEOS].first.version_string.to_i
68
- `\"#{arm2sim_path}\" \"#{object_file}\" \"#{sdk_version}\" \"#{sdk_version}\"`
69
- $stderr.printf '.'
65
+ if macho_file_type(extracted_path) == :object
66
+ patch_object_file(extracted_path)
67
+ else
68
+ object_files = `ar t \"#{extracted_path}\"`.split("\n").map(&:chomp).sort
69
+ .select { |o| o.end_with?('.o') }
70
+ .group_by(&:itself).transform_values(&:count)
71
+ processed_files = []
72
+ index = 0
73
+ while object_files.any?
74
+ object_files.keys.each do |object_file|
75
+ file_shard = Digest::MD5.hexdigest(object_file).to_s[0..2]
76
+ file_dir = extracted_path_dir.join("#{index}-#{file_shard}")
77
+ file_path = file_dir.join(object_file)
78
+ file_dir.mkdir unless file_dir.exist?
79
+ `ar p \"#{extracted_path}\" \"#{object_file}\" > \"#{file_path}\"`
80
+ patch_object_file(file_path)
81
+ $stderr.printf '.'
82
+ processed_files << file_path
83
+ end
84
+ `ar d \"#{extracted_path}\" #{object_files.keys.map(&:shellescape).join(' ')}`
85
+ $stderr.printf '#'
86
+ object_files.reject! { |_, count| count <= index + 1 }
87
+ index += 1
88
+ end
89
+ $stderr.puts
90
+ `cd \"#{extracted_path_dir}\" ; ar cqv \"#{extracted_path}\" #{processed_files.map(&:shellescape).join(' ')}`
70
91
  end
71
- $stderr.puts
72
- `cd \"#{extracted_path_dir}\" ; ar crv \"#{extracted_path}\" *.o`
73
-
74
92
  `xcrun lipo \"#{slice.binary_path}\" -replace arm64 \"#{extracted_path}\" -output \"#{slice.binary_path}\"`
75
93
  extracted_path_dir.rmtree
76
94
  extracted_path.rmtree
77
95
  end
78
96
 
97
+ def macho_file_type(file_path)
98
+ MachO.open(file_path).filetype
99
+ rescue MachO::MagicError
100
+ nil
101
+ end
102
+
103
+ def patch_object_file(file_path)
104
+ minos_version, sdk_version = version_strings(file_path).map(&:to_i)
105
+ `\"#{arm2sim_path}\" \"#{file_path}\" \"#{minos_version}\" \"#{sdk_version}\"`
106
+ end
107
+
108
+ def version_strings(file_path)
109
+ macho_file = MachO::MachOFile.new(file_path)
110
+ if (version_min_command = macho_file.load_commands.find { |c| c.is_a?(MachO::LoadCommands::VersionMinCommand) })
111
+ return [version_min_command.version_string, version_min_command.sdk_string]
112
+ end
113
+ if (build_version_command = macho_file.load_commands.find { |c| c.is_a?(MachO::LoadCommands::BuildVersionCommand) })
114
+ return [build_version_command.minos_string, build_version_command.sdk_string]
115
+ end
116
+
117
+ raise "Could not find version strings in #{file_path}"
118
+ end
119
+
79
120
  public
80
121
 
81
122
  def cleanup_unused_archs(slice)
@@ -20,21 +20,24 @@ module XCFrameworkConverter
20
20
  Pathname.new(__FILE__).dirname.join('../xcframework_template.plist')
21
21
  end
22
22
 
23
- def convert_framework_to_xcframework(path)
23
+ def convert_framework_to_xcframework(path, platform)
24
24
  plist = Xcodeproj::Plist.read_from_path(plist_template_path)
25
25
  xcframework_path = Pathname.new(path).sub_ext('.xcframework')
26
26
  xcframework_path.mkdir
27
27
  plist['AvailableLibraries'].each do |slice|
28
- slice_path = xcframework_path.join(slice['LibraryIdentifier'])
28
+ slice_library_identifier = slice['LibraryIdentifier'].sub('platform', platform.to_s)
29
+ slice_path = xcframework_path.join(slice_library_identifier)
29
30
  slice_path.mkdir
30
31
  slice['LibraryPath'] = File.basename(path)
32
+ slice['SupportedPlatform'] = platform.to_s
33
+ slice['LibraryIdentifier'] = slice_library_identifier
31
34
  FileUtils.cp_r(path, slice_path)
32
35
  end
33
36
  Xcodeproj::Plist.write_to_path(plist, xcframework_path.join('Info.plist'))
34
37
  FileUtils.rm_rf(path)
35
38
  final_framework = Pod::Xcode::XCFramework.open_xcframework(xcframework_path)
36
39
  final_framework.slices.each do |slice|
37
- ArmPatcher.patch_arm_binary(slice) if slice.platform == :ios && slice.platform_variant == :simulator
40
+ ArmPatcher.patch_arm_binary(slice) if slice.platform == platform && slice.platform_variant == :simulator
38
41
  ArmPatcher.cleanup_unused_archs(slice)
39
42
  end
40
43
  end
@@ -18,18 +18,33 @@ module XCFrameworkConverter
18
18
  class << self
19
19
  def patch_xcframework(xcframework_path)
20
20
  xcframework = Pod::Xcode::XCFramework.open_xcframework(xcframework_path)
21
+ slices_to_convert = xcframework.slices.select do |slice|
22
+ slice.platform != :osx &&
23
+ slice.platform_variant != :simulator &&
24
+ slice.supported_archs.include?('arm64')
25
+ end
26
+
27
+ slices_to_convert.each do |slice|
28
+ patch_xcframework_impl(xcframework_path, slice.platform.name)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def patch_xcframework_impl(xcframework_path, platform)
35
+ xcframework = Pod::Xcode::XCFramework.open_xcframework(xcframework_path)
21
36
 
22
37
  return nil if xcframework.slices.any? do |slice|
23
- slice.platform == :ios &&
38
+ slice.platform == platform &&
24
39
  slice.platform_variant == :simulator &&
25
40
  slice.supported_archs.include?('arm64')
26
41
  end
27
42
 
28
43
  original_arm_slice_identifier = xcframework.slices.find do |slice|
29
- slice.platform == :ios && slice.supported_archs.include?('arm64')
44
+ slice.platform == platform && slice.supported_archs.include?('arm64')
30
45
  end.identifier
31
46
 
32
- patched_arm_slice_identifier = 'ios-arm64-simulator'
47
+ patched_arm_slice_identifier = "#{platform}-arm64-simulator"
33
48
 
34
49
  warn "Will patch #{xcframework_path}: #{original_arm_slice_identifier} -> #{patched_arm_slice_identifier}"
35
50
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module XCFrameworkConverter
4
- VERSION = '0.4.0'
4
+ VERSION = '0.7.0'
5
5
  end
@@ -39,8 +39,8 @@ module XCFrameworkConverter
39
39
  after_rename = before_rename.map { |f| Pathname.new(f).sub_ext('.xcframework').to_s }
40
40
  proxy = Pod::Specification::DSL::PlatformProxy.new(spec, platform.symbolic_name)
41
41
  proxy.vendored_frameworks = consumer.vendored_frameworks - before_rename + after_rename
42
- before_rename.map { |f| pod_path.join(f) }
43
- end.flatten.uniq
42
+ before_rename.map { |f| [pod_path.join(f), platform.symbolic_name] }
43
+ end.flatten(1).uniq
44
44
 
45
45
  convert_xcframeworks_if_present(frameworks_to_convert)
46
46
 
@@ -53,8 +53,8 @@ module XCFrameworkConverter
53
53
  end
54
54
 
55
55
  def convert_xcframeworks_if_present(frameworks_to_convert)
56
- frameworks_to_convert.each do |path|
57
- convert_framework_to_xcframework(path) if Dir.exist?(path)
56
+ frameworks_to_convert.each do |path, platform|
57
+ convert_framework_to_xcframework(path, platform) if Dir.exist?(path)
58
58
  end
59
59
  end
60
60
 
@@ -65,21 +65,31 @@ module XCFrameworkConverter
65
65
  end
66
66
 
67
67
  def remove_troublesome_xcconfig_items(spec)
68
+ # some pods put these as a way to NOT support arm64 sim
69
+ # may stop working if a pod decides to put these in a platform proxy
70
+
68
71
  xcconfigs = %w[
69
72
  pod_target_xcconfig
70
73
  user_target_xcconfig
71
74
  ].map { |key| spec.attributes_hash[key] }.compact
72
75
 
73
- xcconfigs.each do |xcconfig|
74
- # some pods put these as a way to NOT support arm64 sim
75
- # may stop working if a pod decides to put these in a platform proxy
76
- excluded_arm = xcconfig['EXCLUDED_ARCHS[sdk=iphonesimulator*]']&.include?('arm64')
77
- not_inlcuded_arm = xcconfig['VALID_ARCHS[sdk=iphonesimulator*]'] && !xcconfig['VALID_ARCHS[sdk=iphonesimulator*]'].include?('arm64')
76
+ platforms = %w[
77
+ iphonesimulator
78
+ appletvsimulator
79
+ watchsimulator
80
+ ]
81
+
82
+ (xcconfigs.product platforms).each do |xcconfig, platform|
83
+ excluded_archs_key = "EXCLUDED_ARCHS[sdk=#{platform}*]"
84
+ inlcuded_arch_key = "VALID_ARCHS[sdk=#{platform}*]"
85
+
86
+ excluded_arm = xcconfig[excluded_archs_key]&.include?('arm64')
87
+ not_inlcuded_arm = xcconfig[inlcuded_arch_key] && !xcconfig[inlcuded_arch_key].include?('arm64')
78
88
 
79
89
  remember_spec_as_patched(spec) if excluded_arm || not_inlcuded_arm
80
90
 
81
- xcconfig.delete('EXCLUDED_ARCHS[sdk=iphonesimulator*]')
82
- xcconfig.delete('VALID_ARCHS[sdk=iphonesimulator*]')
91
+ xcconfig.delete(excluded_archs_key)
92
+ xcconfig.delete(inlcuded_arch_key)
83
93
  end
84
94
  end
85
95
 
@@ -6,7 +6,7 @@
6
6
  <array>
7
7
  <dict>
8
8
  <key>LibraryIdentifier</key>
9
- <string>ios-arm64_x86_64-simulator</string>
9
+ <string>platform-arm64_x86_64-simulator</string>
10
10
  <key>LibraryPath</key>
11
11
  <string>Placeholder.framework</string>
12
12
  <key>SupportedArchitectures</key>
@@ -21,7 +21,7 @@
21
21
  </dict>
22
22
  <dict>
23
23
  <key>LibraryIdentifier</key>
24
- <string>ios-arm64_armv7</string>
24
+ <string>platform-arm64_armv7</string>
25
25
  <key>LibraryPath</key>
26
26
  <string>Placeholder.framework</string>
27
27
  <key>SupportedArchitectures</key>
@@ -30,7 +30,7 @@
30
30
  <string>armv7</string>
31
31
  </array>
32
32
  <key>SupportedPlatform</key>
33
- <string>ios</string>
33
+ <string>platform</string>
34
34
  </dict>
35
35
  </array>
36
36
  <key>CFBundlePackageType</key>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xcframework_converter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Makarov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-06 00:00:00.000000000 Z
11
+ date: 2022-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cocoapods