module XCFrameworkConverter

Converts a framework (static or dynamic) to XCFrameworks, adding an arm64 simulator patch. For more info: static libs: bogo.wtf/arm64-to-sim.html dylibs: bogo.wtf/arm64-to-sim-dylibs.html

Constants

VERSION

Public Class Methods

arm2sim_path() click to toggle source
# File lib/xcframework_converter.rb, line 96
def arm2sim_path
  Pathname.new(__FILE__).dirname.join('arm2sim.swift')
end
cleanup_unused_archs(slice) click to toggle source
# File lib/xcframework_converter.rb, line 118
def cleanup_unused_archs(slice)
  supported_archs = slice.supported_archs
  unsupported_archs = `xcrun lipo \"#{slice.binary_path}\" -archs`.split - supported_archs
  unsupported_archs.each do |arch|
    `xcrun lipo \"#{slice.binary_path}\" -remove \"#{arch}\" -output \"#{slice.binary_path}\"`
  end
end
convert_framework_to_xcframework(path) click to toggle source
# File lib/xcframework_converter.rb, line 51
def convert_framework_to_xcframework(path)
  plist = Xcodeproj::Plist.read_from_path(plist_template_path)
  xcframework_path = Pathname.new(path).sub_ext('.xcframework')
  xcframework_path.mkdir
  plist['AvailableLibraries'].each do |slice|
    slice_path = xcframework_path.join(slice['LibraryIdentifier'])
    slice_path.mkdir
    slice['LibraryPath'] = File.basename(path)
    FileUtils.cp_r(path, slice_path)
  end
  Xcodeproj::Plist.write_to_path(plist, xcframework_path.join('Info.plist'))
  FileUtils.rm_rf(path)
  final_framework = Pod::Xcode::XCFramework.new(xcframework_path.realpath)
  final_framework.slices.each do |slice|
    patch_arm_binary(slice) if slice.platform == :ios && slice.platform_variant == :simulator
    cleanup_unused_archs(slice)
  end
end
convert_frameworks_to_xcframeworks!(installer) click to toggle source
# File lib/xcframework_converter.rb, line 18
def convert_frameworks_to_xcframeworks!(installer)
  installer.analysis_result.specifications.each do |spec|
    next unless spec.attributes_hash['vendored_frameworks']

    frameworks = Array(spec.attributes_hash['vendored_frameworks'])
    unconverted_frameworks = frameworks.select { |f| File.extname(f) == '.framework' }
    next if unconverted_frameworks.empty?
    next if spec.local?

    pod_path = installer.sandbox.pod_dir(Pod::Specification.root_name(spec.name))
    convert_xcframeworks_if_present(pod_path)

    converted_frameworks = unconverted_frameworks.map do |path|
      Pathname.new(path).sub_ext('.xcframework').to_s
    end
    spec.attributes_hash['vendored_frameworks'] = frameworks - unconverted_frameworks + converted_frameworks
    # some pods put these as a way to NOT support arm64 sim
    spec.attributes_hash['pod_target_xcconfig']&.delete('EXCLUDED_ARCHS[sdk=iphonesimulator*]')
    spec.attributes_hash['user_target_xcconfig']&.delete('EXCLUDED_ARCHS[sdk=iphonesimulator*]')
  end
end
convert_xcframeworks_if_present(pod_path) click to toggle source
# File lib/xcframework_converter.rb, line 40
def convert_xcframeworks_if_present(pod_path)
  unconverted_paths = Dir[pod_path.join('**/*.framework')] - Dir[pod_path.join('**/*.xcframework/**/*')]
  unconverted_paths.each do |path|
    convert_framework_to_xcframework(path)
  end
end
patch_arm_binary(slice) click to toggle source
# File lib/xcframework_converter.rb, line 70
def patch_arm_binary(slice)
  require 'macho'

  case slice.build_type.linkage
  when :dynamic
    patch_arm_binary_dynamic(slice)
  when :static
    patch_arm_binary_static(slice)
  end

  slice.path.glob('**/arm64*.swiftinterface').each do |interface_file|
    `sed -i '' -E 's/target arm64-apple-ios([0-9.]+) /target arm64-apple-ios\\1-simulator /g' "#{interface_file}"`
  end
end
patch_arm_binary_dynamic(slice) click to toggle source
# File lib/xcframework_converter.rb, line 85
def patch_arm_binary_dynamic(slice)
  extracted_path = slice.path.join('arm64.dylib')
  `xcrun lipo \"#{slice.binary_path}\" -thin arm64 -output \"#{extracted_path}\"`

  file = MachO::MachOFile.new(extracted_path)
  sdk_version = file[:LC_VERSION_MIN_IPHONEOS].first.version_string
  `xcrun vtool -arch arm64 -set-build-version 7 #{sdk_version} #{sdk_version} -replace -output \"#{extracted_path}\" \"#{extracted_path}\"`
  `xcrun lipo \"#{slice.binary_path}\" -replace arm64 \"#{extracted_path}\" -output \"#{slice.binary_path}\"`
  extracted_path.rmtree
end
patch_arm_binary_static(slice) click to toggle source
# File lib/xcframework_converter.rb, line 100
def patch_arm_binary_static(slice)
  extracted_path = slice.path.join('arm64.a')
  `xcrun lipo \"#{slice.binary_path}\" -thin arm64 -output \"#{extracted_path}\"`
  extracted_path_dir = slice.path.join('arm64-objects')
  extracted_path_dir.mkdir
  `cd \"#{extracted_path_dir}\" ; ar x \"#{extracted_path}\"`
  Dir[extracted_path_dir.join('*.o')].each do |object_file|
    file = MachO::MachOFile.new(object_file)
    sdk_version = file[:LC_VERSION_MIN_IPHONEOS].first.version_string.to_i
    `xcrun swift \"#{arm2sim_path}\" \"#{object_file}\" \"#{sdk_version}\" \"#{sdk_version}\"`
  end
  `cd \"#{extracted_path_dir}\" ; ar crv \"#{extracted_path}\" *.o`

  `xcrun lipo \"#{slice.binary_path}\" -replace arm64 \"#{extracted_path}\" -output \"#{slice.binary_path}\"`
  extracted_path_dir.rmtree
  extracted_path.rmtree
end
plist_template_path() click to toggle source
# File lib/xcframework_converter.rb, line 47
def plist_template_path
  Pathname.new(__FILE__).dirname.join('xcframework_template.plist')
end