xcframework_converter 0.4.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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