class Dongjia::Binarization
Public Class Methods
load_private_config(ctx, binary)
click to toggle source
加载配置
# File lib/dongjia_binarization.rb, line 26 def load_private_config(ctx, binary) @@ctx = ctx submodule_info = load_submodules_info @@submodules = submodule_info.to_h { |s| [ s[:name], { sha: s[:sha], binary: s[:binary], } ] } @@enabled ||= binary["enabled"] @@server_host = binary["server_host"] @@target_name = (binary["target_name"] || "").strip ignores = binary["ignores"] || [] if @@enabled @@submodules = query_components_existing(submodule_info).to_h { |s| [ s["name"], { sha: s["sha"], binary: s["binary"], } ] }.each { |k, v| ignored = ignores.include?(k) v[:binary] &&= !ignored && @@enabled v[:ignored] = ignored } end rescue => e puts "Binarization error: #{e}" end
process(installer)
click to toggle source
开始处理二进制
# File lib/dongjia_binarization.rb, line 92 def process(installer) return if @@ctx == nil || @@ctx.sandbox_root == nil path = File.join(@@ctx.sandbox_root, "Pods.xcodeproj") @@pods_proj = Xcodeproj::Project.open(path) if File.exist?(path) @@pods_target = (@@target_name.empty?) ? @@pods_proj.targets.first : @@pods_proj.target_by_name("Pods-#{@@target_name}") if @@pods_target.nil? Pod::UI.warn("[Binarization] 未完成二进制转换,target_name 配置错误") return end setup_pod_cfg(installer) download_frameworks each_pod_proj do |name, cfg| process_pods_project_dependencies(name) # 处理 Binary Group process_binary_group(name) # 处理 XXX-xcframeworks.sh 脚本 process_xcframeworks_shell(name) process_pod_xcconfig(name) end # 更新依赖关系 each_pod_proj do |name, cfg| target = cfg[:target] next if target.nil? delete_list = [] add_list = [] target.dependencies.each { |dep| dep_pod_name = real_name(dep.name) dep_cfg = @@pod_cfg[dep_pod_name] next if dep_cfg.nil? if dep_cfg[:target].name != dep.name add_list << dep_cfg[:target] delete_list << dep end } target.dependencies.delete_if { |dep| delete_list.include?(dep) } add_list.each { |t| target.add_dependency(t) } cfg[:save] ||= !delete_list.empty? || !add_list.empty? end # 保存 save_projects process_aggregate_target_xcconfig(installer.aggregate_targets.first.name) end
remove_dirty_pod_projects()
click to toggle source
移除脏工程
# File lib/dongjia_binarization.rb, line 59 def remove_dirty_pod_projects if @@enabled @@submodules .filter { |k,v| !v[:binary] } .map { |k,v| k } .each { |name| path = File.join(@@ctx.sandbox_root, "#{name}.xcodeproj") next unless File.exist?(path) proj = Xcodeproj::Project.open(path) if proj.targets.map(&:name).include?("#{name}-Binary") FileUtils.rm_rf(path) end } else # 不启用,清除所有包含 -Binary 的工程 removing_paths = [] Dir.foreach(@@ctx.sandbox_root) { |filename| next if File.extname(filename) != '.xcodeproj' pod_name = filename.delete_suffix('.xcodeproj') next if pod_name == "Pods" process_pod_xcconfig(pod_name) path = File.join(@@ctx.sandbox_root, filename) proj = Xcodeproj::Project.open(path) included_binary_target = proj.targets.map(&:name).any? { |target_name| target_name.end_with?("-Binary") } removing_paths << path if included_binary_target } removing_paths.each { |path| FileUtils.rm_rf(path) } end end
xcframeworks_shell(pod_name)
click to toggle source
# File lib/dongjia_binarization.rb, line 465 def self.xcframeworks_shell(pod_name) <<-DESC #!/bin/sh set -e set -u set -o pipefail function on_error { echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" } trap 'on_error $LINENO' ERR # This protects against multiple targets copying the same framework dependency at the same time. The solution # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") copy_dir() { local source="$1" local destination="$2" # Use filter instead of exclude so missing patterns don't throw errors. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \\"- CVS/\\" --filter \\"- .svn/\\" --filter \\"- .git/\\" --filter \\"- .hg/\\" \\"${source}\\" \\"${destination}\\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" "${source}" "${destination}" } SELECT_SLICE_RETVAL="" select_slice() { local paths=("$@") # Locate the correct slice of the .xcframework for the current architectures local target_path="" # Split archs on space so we can find a slice that has all the needed archs local target_archs=$(echo $ARCHS | tr " " "\\n") local target_variant="" if [[ "$PLATFORM_NAME" == *"simulator" ]]; then target_variant="simulator" fi if [[ ! -z ${EFFECTIVE_PLATFORM_NAME+x} && "$EFFECTIVE_PLATFORM_NAME" == *"maccatalyst" ]]; then target_variant="maccatalyst" fi for i in ${!paths[@]}; do local matched_all_archs="1" for target_arch in $target_archs do if ! [[ "${paths[$i]}" == *"$target_variant"* ]]; then matched_all_archs="0" break fi # Verifies that the path contains the variant string (simulator or maccatalyst) if the variant is set. if [[ -z "$target_variant" && ("${paths[$i]}" == *"simulator"* || "${paths[$i]}" == *"maccatalyst"*) ]]; then matched_all_archs="0" break fi # This regex matches all possible variants of the arch in the folder name: # Let's say the folder name is: ios-armv7_armv7s_arm64_arm64e/CoconutLib.framework # We match the following: -armv7_, _armv7s_, _arm64_ and _arm64e/. # If we have a specific variant: ios-i386_x86_64-simulator/CoconutLib.framework # We match the following: -i386_ and _x86_64- # When the .xcframework wraps a static library, the folder name does not include # any .framework. In that case, the folder name can be: ios-arm64_armv7 # We also match _armv7$ to handle that case. local target_arch_regex="[_\\-]${target_arch}([\\/_\\-]|$)" if ! [[ "${paths[$i]}" =~ $target_arch_regex ]]; then matched_all_archs="0" break fi done if [[ "$matched_all_archs" == "1" ]]; then # Found a matching slice echo "Selected xcframework slice ${paths[$i]}" SELECT_SLICE_RETVAL=${paths[$i]} break fi done } install_library() { local source="$1" local name="$2" local destination="${PODS_XCFRAMEWORKS_BUILD_DIR}/${name}" # Libraries can contain headers, module maps, and a binary, so we'll copy everything in the folder over local source="$binary" echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \\"- CVS/\\" --filter \\"- .svn/\\" --filter \\"- .git/\\" --filter \\"- .hg/\\" \\"${source}/*\\" \\"${destination}\\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" "${source}/*" "${destination}" } # Copies a framework to derived data for use in later build phases install_framework() { local source="$1" local name="$2" local destination="${PODS_XCFRAMEWORKS_BUILD_DIR}/${name}" if [ ! -d "$destination" ]; then mkdir -p "$destination" fi copy_dir "$source" "$destination" echo "Copied $source to $destination" } install_xcframework_library() { local basepath="$1" local name="$2" local paths=("$@") # Locate the correct slice of the .xcframework for the current architectures select_slice "${paths[@]}" local target_path="$SELECT_SLICE_RETVAL" if [[ -z "$target_path" ]]; then echo "warning: [CP] Unable to find matching .xcframework slice in '${paths[@]}' for the current build architectures ($ARCHS)." return fi install_framework "$basepath/$target_path" "$name" } install_xcframework() { local basepath="$1" local name="$2" local package_type="$3" local paths=("$@") # Locate the correct slice of the .xcframework for the current architectures select_slice "${paths[@]}" local target_path="$SELECT_SLICE_RETVAL" if [[ -z "$target_path" ]]; then echo "warning: [CP] Unable to find matching .xcframework slice in '${paths[@]}' for the current build architectures ($ARCHS)." return fi local source="$basepath/$target_path" local destination="${PODS_XCFRAMEWORKS_BUILD_DIR}/${name}" if [ ! -d "$destination" ]; then mkdir -p "$destination" fi copy_dir "$source/" "$destination" echo "Copied $source to $destination" } install_xcframework "${PODS_ROOT}/_Frameworks/#{pod_name}.xcframework" "#{pod_name}" "framework" "ios-arm64_armv7" "ios-x86_64-simulator" DESC end
Private Class Methods
add_binary_target(proj, name)
click to toggle source
# File lib/dongjia_binarization.rb, line 354 def add_binary_target(proj, name) source_target = proj.target_by_name(name) bin_target = proj.new_aggregate_target("#{name}-Binary", [], :ios, '10.0') # 处理 xcconfig 引用 cfg_list0 = source_target.build_configuration_list cfg_list1 = bin_target.build_configuration_list cfg_list1["Debug"].base_configuration_reference ||= cfg_list0["Debug"].base_configuration_reference cfg_list1["Release"].base_configuration_reference ||= cfg_list0["Release"].base_configuration_reference # 处理 Build Phases phase = bin_target.new_shell_script_build_phase("[CP] Copy XCFrameworks") phase.shell_script = "\"${PODS_ROOT}/Target Support Files/#{name}/#{name}-xcframeworks.sh\"" # 添加依赖 bin_target.dependencies.replace(source_target.dependencies) return bin_target end
download_frameworks()
click to toggle source
下载 framework 到 Pods/_Frameworks 目录下
# File lib/dongjia_binarization.rb, line 395 def download_frameworks return unless @@enabled framework_root = File.join(@@ctx.sandbox_root, "_Frameworks") cache_root = File.join(framework_root, "Caches") FileUtils.mkdir_p(cache_root) unless File.exist?(cache_root) @@submodules.each { |k, v| if !v[:binary] || !v[:sha] puts "#{k} 未发现二进制版本" if @@enabled && !v[:ignored] next end module_cache_dir = File.join(cache_root, k) FileUtils.mkdir_p(module_cache_dir) unless File.exist?(module_cache_dir) filename = "#{v[:sha]}.zip" binary_dir = File.join(module_cache_dir, v[:sha]) if not File.exist?(binary_dir) # 目录不存在,下载 zip 包并解压 binary_zip_path = File.join(module_cache_dir, filename) puts "Downloading #{k} (#{v[:sha]})" url = URI::join(@@server_host, "binary/#{k}/#{filename}") Down.download(url, destination: binary_zip_path) Archive::Zip.extract(binary_zip_path, binary_dir) FileUtils.rm_f(binary_zip_path) end target = File.expand_path(File.join(binary_dir, "#{k}.xcframework")) FileUtils.ln_s(target, framework_root, force: true) } end
each_pod_proj() { |name, cfg| ... }
click to toggle source
遍历 Pods 目录下的 xcodeproj
# File lib/dongjia_binarization.rb, line 183 def each_pod_proj Dir.foreach(@@ctx.sandbox_root) do |filename| next if File.extname(filename) != '.xcodeproj' name = File.basename(filename, '.xcodeproj') next if name == 'Pods' cfg = @@pod_cfg[name] next if cfg.nil? yield(name, cfg) if block_given? end end
get_wifi_ssid()
click to toggle source
获取当前 Wi-Fi 的 SSID
# File lib/dongjia_binarization.rb, line 454 def get_wifi_ssid lines = `/System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport -I`.split("\n").map { |line| line.strip } target = lines.find { |x| x.start_with?("SSID: ") } target.nil? ? "" : target.delete_prefix("SSID: ") end
git_sha_value(component_name)
click to toggle source
获取子模块的哈希值
# File lib/dongjia_binarization.rb, line 223 def git_sha_value(component_name) git_dir = ".git/modules/componentsOnRemote/#{component_name}" git_sha = File.read(File.join(git_dir, "HEAD")).strip if git_sha.start_with?("ref: ") head_file = git_sha[5, git_sha.length] head_path = File.join(git_dir, head_file) if File.exist?(head_path) git_sha = File.read(head_path).strip else git_sha = nil end end git_sha end
load_submodules_info()
click to toggle source
加载子模块信息
# File lib/dongjia_binarization.rb, line 219 def load_submodules_info return [] unless File.exist?("./componentsOnRemote") # 获取子模块的哈希值 def git_sha_value(component_name) git_dir = ".git/modules/componentsOnRemote/#{component_name}" git_sha = File.read(File.join(git_dir, "HEAD")).strip if git_sha.start_with?("ref: ") head_file = git_sha[5, git_sha.length] head_path = File.join(git_dir, head_file) if File.exist?(head_path) git_sha = File.read(head_path).strip else git_sha = nil end end git_sha end Dir.foreach("./componentsOnRemote").to_a .delete_if { |x| x.start_with?('.') } .delete_if { |x| # 如果子模块初始化错误,会导致目录下没有内容,需要排除掉 Dir.foreach("./componentsOnRemote/#{x}").to_a.all? { |f| f.start_with?(".") } } .map { |comp| { name: comp, sha: git_sha_value(comp), binary: false, } } end
process_aggregate_target_xcconfig(aggregate_target_name)
click to toggle source
处理集成对象的 xcconfig(项目的 xcconfig)
# File lib/dongjia_binarization.rb, line 333 def process_aggregate_target_xcconfig(aggregate_target_name) # 处理 Pods/Target Support Files/Pods-项目名/Pods-项目名.debug(release).xcconfig # 所有被二进制化的 pod,增加 "${PODS_XCFRAMEWORKS_BUILD_DIR}/#{pod_name}" ["debug", "release"].each { |cfg_name| cfg_path = Pathname(File.join(@@ctx.sandbox_root, "Target Support Files", aggregate_target_name, "#{aggregate_target_name}.#{cfg_name}.xcconfig")) next if not File.exist?(cfg_path) cfg = Xcodeproj::Config.new(cfg_path) fsp = cfg.attributes["FRAMEWORK_SEARCH_PATHS"].split(" ") || ["$(inherited)"] @@submodules.each { |k, v| path = "\"${PODS_XCFRAMEWORKS_BUILD_DIR}/#{k}\"" if v[:binary] == true fsp << path if not fsp.include?(path) else fsp.delete_if { |x| x == path } end } cfg.attributes["FRAMEWORK_SEARCH_PATHS"] = fsp.flatten.uniq.join(" ") cfg.save_as(cfg_path) } end
process_binary_group(name)
click to toggle source
# File lib/dongjia_binarization.rb, line 426 def process_binary_group(name) cfg = @@pod_cfg[name] proj = cfg[:project] binary_group = proj.groups.find { |g| g.name == "Binary" } if cfg[:binary] == true if binary_group == nil binary_group = proj.new_group("Binary") binary_group.new_reference("_Frameworks/#{name}.xcframework") proj.sort end else if binary_group != nil binary_group.remove_from_project end end end
process_pod_xcconfig(name)
click to toggle source
处理组件 pod 的 xcconfig
# File lib/dongjia_binarization.rb, line 297 def process_pod_xcconfig(name) # 根据 pod 是源码还是二进制包,切换路径为 PODS_CONFIGURATION_BUILD_DIR 或 PODS_XCFRAMEWORKS_BUILD_DIR ["debug", "release"].each { |cfg_name| cfg_path = Pathname(File.join(@@ctx.sandbox_root, "Target Support Files", name, "#{name}.#{cfg_name}.xcconfig")) next if not File.exist?(cfg_path) cfg = Xcodeproj::Config.new(cfg_path) fsp_str = cfg.attributes["FRAMEWORK_SEARCH_PATHS"] || "$(inherited)" fsp = fsp_str.split(" ") # 源码的 prefix source_prefix = "\"${PODS_CONFIGURATION_BUILD_DIR}/" # 二进制包的 prefix binary_prefix = "\"${PODS_XCFRAMEWORKS_BUILD_DIR}/" fsp.map! { |path| if path.start_with?(source_prefix) pod_name = path[source_prefix.length, path.length].delete_suffix("\"") sm = @@submodules[pod_name] if !sm.nil? && sm[:binary] path = path.gsub("PODS_CONFIGURATION_BUILD_DIR", "PODS_XCFRAMEWORKS_BUILD_DIR") end elsif path.start_with?(binary_prefix) pod_name = path[binary_prefix.length, path.length].delete_suffix("\"") sm = @@submodules[pod_name] if !sm.nil? && !sm[:binary] path = path.gsub("PODS_XCFRAMEWORKS_BUILD_DIR", "PODS_CONFIGURATION_BUILD_DIR") end end path } cfg.attributes["FRAMEWORK_SEARCH_PATHS"] = fsp.flatten.uniq.join(" ") cfg.save_as(cfg_path) } end
process_pods_project_dependencies(name)
click to toggle source
处理 Pods.xcodeproj 的依赖
# File lib/dongjia_binarization.rb, line 254 def process_pods_project_dependencies(name) cfg = @@pod_cfg[name] proj = cfg[:project] changed = false latest_target = nil removing_target_name = nil if cfg[:binary] == true # 使用 Binary 版本的 target,如果不存在则创建一个 binary_target = proj.target_by_name("#{name}-Binary") if binary_target == nil binary_target = add_binary_target(proj, name) changed = true end latest_target = binary_target removing_target_name = name else # 使用源码版本的 target source_target = proj.target_by_name(name) source_target.build_configurations.each { |cfg| if cfg.build_settings["BUILD_LIBRARY_FOR_DISTRIBUTION"] != "YES" cfg.build_settings["BUILD_LIBRARY_FOR_DISTRIBUTION"] = "YES" changed = true end } latest_target = source_target removing_target_name = "#{name}-Binary" end cfg[:target] = latest_target # 更新依赖 changed = update_pods_target_dependency(removing_target_name, latest_target) || changed changed = proj.targets.map(&:name).include?(removing_target_name) || changed proj.targets.delete_if { |t| t.name == removing_target_name } cfg[:save] = changed end
process_xcframeworks_shell(name)
click to toggle source
# File lib/dongjia_binarization.rb, line 443 def process_xcframeworks_shell(name) cfg = @@pod_cfg[name] return if cfg[:binary] != true script_path = File.join(@@ctx.sandbox_root, "Target Support Files", name, "#{name}-xcframeworks.sh") File.open(script_path, 'w') do |f| f.write(xcframeworks_shell(name)) end FileUtils.chmod('a+x', script_path) end
query_components_existing(components)
click to toggle source
向服务器查询哪些 Pod
需要打包
# File lib/dongjia_binarization.rb, line 198 def query_components_existing(components) return components unless @@enabled result = components begin uri = URI.parse(URI::join(@@server_host, 'api/binary/checkup').to_s) headers = { 'Content-Type': 'application/json' } req = Net::HTTP::Post.new(uri, headers) req.body = {components: components}.to_json resp = Net::HTTP.start(uri.host, uri.port) do |http| http.request(req) end result = JSON.parse(resp.body).dig('res', 'components') rescue => e puts "Binarization error: #{e}" end return result end
real_name(name)
click to toggle source
获取实际的 pod 名,如果有 -Binary 后缀,则去除后缀
# File lib/dongjia_binarization.rb, line 160 def real_name(name) name = name.delete_suffix("-Binary") if name.end_with?("-Binary") return name end
save_projects()
click to toggle source
# File lib/dongjia_binarization.rb, line 165 def save_projects if Pod::Config.instance.verbose == true pods_should_save = @@pod_cfg.filter { |k, v| v[:save] }.keys Pod::UI.notice "Saving Pods project" unless pods_should_save.empty? pods_should_save.each { |name| puts " #{name}" } end should_save = false each_pod_proj do |name, cfg| cfg[:project].save if cfg[:save] should_save ||= cfg[:save] end @@pods_proj.save if !@@pods_proj.nil? && should_save end
setup_pod_cfg(installer)
click to toggle source
加载配置
# File lib/dongjia_binarization.rb, line 148 def setup_pod_cfg(installer) installer.analysis_result.podfile_dependency_cache.podfile_dependencies.each { |dep| name = dep.name.split('/').first cfg = @@submodules[name] || {} cfg[:binary] = false unless cfg[:binary] cfg[:save] = false unless cfg[:save] cfg[:project] = Xcodeproj::Project.open(File.join(@@ctx.sandbox_root, "#{name}.xcodeproj")) @@pod_cfg[name] = cfg } end
update_pods_target_dependency(removing_target_name, new_target)
click to toggle source
更新 Pods.xcodeproj 中 target 的依赖关系
# File lib/dongjia_binarization.rb, line 375 def update_pods_target_dependency(removing_target_name, new_target) dep_names = @@pods_target.dependencies.map(&:name) changed = false index = dep_names.index(removing_target_name) if index @@pods_target.dependencies.delete_at(index) changed = true end index = dep_names.index(new_target.name) unless index @@pods_target.add_dependency(new_target) changed = true end return changed end