Top Level Namespace

Defined Under Namespace

Modules: CocoapodsBinary, Pod

Constant Summary collapse

CONFIGURATION =
"Debug"
PLATFORMS =
{ 'iphonesimulator' => 'iOS',
'appletvsimulator' => 'tvOS',
'watchsimulator' => 'watchOS' }

Instance Method Summary collapse

Instance Method Details

#build_for_iosish_platform(sandbox, build_dir, output_path, target, device, simulator, bitcode_enabled, custom_build_options = [], custom_build_options_simulator = []) ⇒ Object

Build specific target to framework file

@param [PodTarget] target
       a specific pod target


13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/cocoapods-binary-matchup/rome/build_framework.rb', line 13

def build_for_iosish_platform(sandbox, 
                              build_dir, 
                              output_path,
                              target, 
                              device, 
                              simulator,
                              bitcode_enabled,
                              custom_build_options = [], # Array<String>
                              custom_build_options_simulator = [] # Array<String>
                              )
  
  target_label = target.label # name with platform if it's used in multiple platforms
  Pod::UI.puts "Prebuilding #{target_label}..."
  
  other_options = []
  # bitcode enabled
  other_options += ['BITCODE_GENERATION_MODE=bitcode'] if bitcode_enabled

  is_succeed, _ = xcodebuild(sandbox, target_label, device, target, other_options + custom_build_options)
  exit 1 unless is_succeed
  if Pod::Podfile::DSL.simulator_build_enabled
    # make less arch to iphone simulator for faster build
    custom_build_options_simulator += ['ARCHS=x86_64', 'ONLY_ACTIVE_ARCH=NO'] if simulator == 'iphonesimulator'
    is_succeed, _ = xcodebuild(sandbox, target_label, simulator, target, other_options + custom_build_options_simulator)
    exit 1 unless is_succeed
  end

  # paths
  target_name = target.name # equals target.label, like "AFNeworking-iOS" when AFNetworking is used in multiple platforms.
  module_name = target.product_module_name
  device_framework_path = "#{build_dir}/#{CONFIGURATION}-#{device}/#{target_name}/#{module_name}.framework"
  device_binary = device_framework_path + "/#{module_name}"

  tmp_lipoed_binary_path = "#{build_dir}/#{target_name}"
  lipo_log = `lipo -create -output #{tmp_lipoed_binary_path} #{device_binary}`

  if Pod::Podfile::DSL.simulator_build_enabled
    simulator_framework_path = "#{build_dir}/#{CONFIGURATION}-#{simulator}/#{target_name}/#{module_name}.framework"
    simulator_binary = simulator_framework_path + "/#{module_name}"
    return unless File.file?(device_binary) && File.file?(simulator_binary)
  else
    return unless File.file?(device_binary)
  end

  puts lipo_log unless File.exist?(tmp_lipoed_binary_path)
  FileUtils.mv tmp_lipoed_binary_path, device_binary, :force => true
  
  # collect the swiftmodule file for various archs.
  device_swiftmodule_path = device_framework_path + "/Modules/#{module_name}.swiftmodule"
  if File.exist?(device_swiftmodule_path) && Pod::Podfile::DSL.simulator_build_enabled
    simulator_framework_path = "#{build_dir}/#{CONFIGURATION}-#{simulator}/#{target_name}/#{module_name}.framework"
    simulator_swiftmodule_path = simulator_framework_path + "/Modules/#{module_name}.swiftmodule"
    FileUtils.cp_r simulator_swiftmodule_path + "/.", device_swiftmodule_path
  end

  # combine the generated swift headers
  # (In xcode 10.2, the generated swift headers vary for each archs)
  # https://github.com/leavez/cocoapods-binary/issues/58
  if Pod::Podfile::DSL.simulator_build_enabled
    device_generated_swift_header_path = device_framework_path + "/Headers/#{module_name}-Swift.h"
    device_header = File.read(device_generated_swift_header_path)
    simulator_generated_swift_header_path = simulator_framework_path + "/Headers/#{module_name}-Swift.h"
    if File.exist? simulator_generated_swift_header_path
      
      simulator_header = File.read(simulator_generated_swift_header_path)
      # https://github.com/Carthage/Carthage/issues/2718#issuecomment-473870461
      combined_header_content = %Q{
  #if TARGET_OS_SIMULATOR // merged by cocoapods-binary

  #{simulator_header}

  #else // merged by cocoapods-binary

  #{device_header}

  #endif // merged by cocoapods-binary
  }
      File.write(device_generated_swift_header_path, combined_header_content.strip)
    end
  else
    device_generated_swift_header_path = device_framework_path + "/Headers/#{module_name}-Swift.h"
    if File.exist?(device_generated_swift_header_path)
      device_header = File.read(device_generated_swift_header_path)
      combined_header_content = %Q{
        #if TARGET_OS_SIMULATOR // merged by cocoapods-binary

        #else // merged by cocoapods-binary
      
        #{device_header}
      
        #endif // merged by cocoapods-binary
        }
      File.write(device_generated_swift_header_path, combined_header_content.strip)
    end
  end

  # handle the dSYM files
  # device_dsym = "#{device_framework_path}.dSYM"
  # if File.exist? device_dsym
  #   # lipo the simulator dsym
  #   simulator_dsym = "#{simulator_framework_path}.dSYM"
  #   if File.exist? simulator_dsym
  #     tmp_lipoed_binary_path = "#{output_path}/#{module_name}.draft"
  #     lipo_log = `lipo -create -output #{tmp_lipoed_binary_path} #{device_dsym}/Contents/Resources/DWARF/#{module_name} #{simulator_dsym}/Contents/Resources/DWARF/#{module_name}`
  #     puts lipo_log unless File.exist?(tmp_lipoed_binary_path)
  #     FileUtils.mv tmp_lipoed_binary_path, "#{device_framework_path}.dSYM/Contents/Resources/DWARF/#{module_name}", :force => true
  #   end
  #   # move
  #   FileUtils.mv device_dsym, output_path, :force => true
  # end

  # output
  output_path.mkpath unless output_path.exist?

  if target.static_framework?
    # 🔑 添加资源处理 - 修复静态库资源丢失问题
    copy_resources_to_framework(sandbox, target, device_framework_path)
  end

  FileUtils.mv device_framework_path, output_path, :force => true

end

#build_framework_resources_block(framework_resources) ⇒ Object

构建framework资源安装代码块



374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/cocoapods-binary-matchup/Main.rb', line 374

def build_framework_resources_block(framework_resources)
    block = "  # Prebuilt Framework Resources\n"
    block += "  echo \"Installing prebuilt framework resources...\"\n"
    
    framework_resources.each do |resource|
        block += "  install_resource \"#{resource}\"\n"
    end
    
    block += "  echo \"Prebuilt framework resources installation completed\"\n"
    
    return block
end

#class_attr_accessor(symbol) ⇒ Object

attr_accessor for class variable. usage:

```
class Pod
    class_attr_accessor :is_prebuild_stage
end
```


10
11
12
# File 'lib/cocoapods-binary-matchup/tool/tool.rb', line 10

def class_attr_accessor(symbol)
    self.class.send(:attr_accessor, symbol)
end

#collect_prebuilt_framework_resources(installer, aggregate_target) ⇒ Object

收集预编译framework中的资源



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/cocoapods-binary-matchup/Main.rb', line 236

def collect_prebuilt_framework_resources(installer, aggregate_target)
    framework_resources = []
    
    # 获取预编译的pod列表 - 强制刷新以确保获取最新状态
    prebuilt_pod_names = installer.refresh_prebuild_pod_names!
    
    aggregate_target.pod_targets.each do |pod_target|
        next unless prebuilt_pod_names.include?(pod_target.pod_name)
        
        Pod::UI.puts "    📱 Scanning prebuilt pod: #{pod_target.pod_name}"
        
        # 获取pod目录
        pod_dir = installer.sandbox.pod_dir(pod_target.pod_name)
        next unless pod_dir.exist?
        
        # 查找framework目录
        framework_dirs = pod_dir.children.select { |child| 
            child.directory? && child.extname == '.framework' 
        }
        
        framework_dirs.each do |framework_dir|
            framework_name = framework_dir.basename.to_s
            Pod::UI.puts "      🔍 Scanning framework: #{framework_name}"
            
            # 扫描framework中的资源
            resources = scan_framework_resources(framework_dir, pod_target.pod_name)
            framework_resources.concat(resources)
        end
    end
    
    framework_resources.uniq
end

#copy_resources_to_framework(sandbox, target, framework_path) ⇒ Object

新增函数:拷贝资源文件到 framework



137
138
139
140
141
142
143
144
145
146
147
# File 'lib/cocoapods-binary-matchup/rome/build_framework.rb', line 137

def copy_resources_to_framework(sandbox, target, framework_path)
  Pod::UI.puts "📋 Processing resources for #{target.name}..."
  
  pod_dir = Pathname.new(sandbox.pod_dir(target.pod_name))
  framework_dir = Pathname.new(framework_path)
  
  # 直接递归扫描 pod 目录下的所有资源文件,拷贝到 framework 根目录
  scan_and_copy_resources_flat(pod_dir, framework_dir)
  
  Pod::UI.puts "✅ Resource processing completed for #{target.name}"
end

#find_and_copy_resources_recursively(current_dir, target_dir, source_root_dir, resource_extensions, special_extensions) ⇒ Object

递归查找资源文件并拷贝



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/cocoapods-binary-matchup/rome/build_framework.rb', line 175

def find_and_copy_resources_recursively(current_dir, target_dir, source_root_dir, resource_extensions, special_extensions)
  # 确保所有参数都是Pathname类型
  current_dir = Pathname.new(current_dir) unless current_dir.is_a?(Pathname)
  target_dir = Pathname.new(target_dir) unless target_dir.is_a?(Pathname) 
  source_root_dir = Pathname.new(source_root_dir) unless source_root_dir.is_a?(Pathname)
  
  return unless current_dir.exist? && current_dir.directory?
  
  current_dir.children.each do |child|
    # 跳过隐藏文件和系统目录
    next if child.basename.to_s.start_with?('.')
    next if ['.git', '.svn', 'node_modules', '.DS_Store'].include?(child.basename.to_s)
    
    if child.directory?
      # 检查是否是特殊资源目录(需要完整保留的)
      if special_extensions.include?(child.extname)
        Pod::UI.puts "  📦 Copying special directory: #{child.basename}"
        target_path = target_dir + child.basename
        FileUtils.cp_r(child.to_s, target_path.to_s, :remove_destination => true)
      else
        # 递归处理普通目录
        find_and_copy_resources_recursively(child, target_dir, source_root_dir, resource_extensions, special_extensions)
      end
    else
      # 检查是否是资源文件
      if resource_extensions.include?(child.extname.downcase)
        Pod::UI.puts "  📄 Copying resource file: #{child.basename}"
        target_path = target_dir + child.basename
        
        # 处理文件名冲突(如果有重名文件,添加路径前缀)
        if target_path.exist?
          # 计算相对于根目录的路径作为前缀
          relative_dir = child.dirname.relative_path_from(source_root_dir)
          safe_name = "#{relative_dir.to_s.gsub('/', '_')}_#{child.basename}"
          target_path = target_dir + safe_name
          Pod::UI.puts "    ⚠️  File name conflict, renamed to: #{safe_name}"
        end
        
        FileUtils.cp(child.to_s, target_path.to_s)
      end
    end
  end
end

#modify_resources_script(script_path, framework_resources) ⇒ Object

修改resources脚本,添加framework资源



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/cocoapods-binary-matchup/Main.rb', line 317

def modify_resources_script(script_path, framework_resources)
    return if framework_resources.empty?
    
    original_content = script_path.read
    
    # 检查是否已经添加过framework资源(避免重复)
    # if original_content.include?('# Prebuilt Framework Resources')
    #     Pod::UI.puts "    ℹ️  Framework resources already added, skipping"
    #     return
    # end
    
    # Pod::UI.puts "    📝 Adding #{framework_resources.count} framework resources to script"
    # framework_resources.each_with_index do |resource, index|
    #     Pod::UI.puts "      #{index + 1}. #{resource}"
    # end
    
    # 构建要添加的资源安装代码
    framework_resources_block = build_framework_resources_block(framework_resources)
    
    # 在Debug配置块中插入framework资源(而不是脚本末尾)
    debug_pattern = /if \[\[ "\$CONFIGURATION" == "Debug" \]\]; then/
    if original_content.match(debug_pattern)
        # 找到Debug配置块,在其中插入framework资源
        modified_content = original_content.sub(debug_pattern) do |match|
            "#{match}\n#{framework_resources_block}"
        end
    else
        # 如果没有找到Debug配置块,创建一个新的
        framework_resources_with_condition = "        if [[ \"$CONFIGURATION\" == \"Debug\" ]]; then\n        \#{framework_resources_block}\n        fi\n        SCRIPT\n        \n        # \u5728\u811A\u672C\u672B\u5C3E\uFF08exit 0\u4E4B\u524D\uFF09\u63D2\u5165\n        if original_content.include?('exit 0')\n            modified_content = original_content.sub(/exit 0/, \"\#{framework_resources_with_condition}\\nexit 0\")\n        else\n            modified_content = original_content + \"\\n\#{framework_resources_with_condition}\"\n        end\n    end\n    \n    # \u5199\u56DE\u4FEE\u6539\u540E\u7684\u5185\u5BB9\n    script_path.write(modified_content)\n    \n    Pod::UI.puts \"    \u2705 Script modified successfully\"\n    \n    # \u663E\u793A\u4FEE\u6539\u540E\u811A\u672C\u7684\u76F8\u5173\u90E8\u5206\u4EE5\u4FBF\u9A8C\u8BC1\n    debug_lines = modified_content.lines.select.with_index do |line, index|\n        line.include?('CONFIGURATION') || line.include?('Prebuilt Framework') || \n        (index > 0 && modified_content.lines[index-1].include?('CONFIGURATION'))\n    end\n    Pod::UI.puts \"    \u{1F4C4} Debug configuration block preview:\"\n    debug_lines.first(5).each { |line| Pod::UI.puts \"      \#{line.chomp}\" }\nend\n"

#scan_and_copy_resources_flat(source_dir, target_dir) ⇒ Object

递归扫描资源文件并平铺拷贝到目标目录



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/cocoapods-binary-matchup/rome/build_framework.rb', line 150

def scan_and_copy_resources_flat(source_dir, target_dir)
  # 确保参数是Pathname类型
  source_dir = Pathname.new(source_dir) unless source_dir.is_a?(Pathname)
  target_dir = Pathname.new(target_dir) unless target_dir.is_a?(Pathname)
  
  Pod::UI.puts "📁 Recursively scanning directory: #{source_dir}"
  
  # 资源文件扩展名
  resource_extensions = [
    '.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg',     # 图片
    '.storyboard', '.xib', '.storyboardc', '.nib',        # 界面文件
    '.ttf', '.otf', '.woff', '.woff2',                    # 字体
    '.mp3', '.wav', '.m4a', '.mp4', '.mov', '.avi',       # 媒体
    '.json', '.plist', '.strings', '.xcassets',           # 配置文件
    'metallib'                                            # metal or opengl文件
  ]
  
  # 特殊处理的目录类型(这些需要完整保留)
  special_directory_extensions = ['.bundle', '.xcassets', '.lproj']
  
  # 递归查找并拷贝资源文件,传递原始根目录用于路径计算
  find_and_copy_resources_recursively(source_dir, target_dir, source_dir, resource_extensions, special_directory_extensions)
end

#scan_framework_resources(framework_dir, pod_name) ⇒ Object

扫描framework中的资源文件



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/cocoapods-binary-matchup/Main.rb', line 270

def scan_framework_resources(framework_dir, pod_name)
    resources = []
    framework_name = framework_dir.basename.to_s
    
    Pod::UI.puts "      🎯 Framework path: #{framework_dir}"
    
    # 资源文件类型
    resource_patterns = [
        '*.png', '*.jpg', '*.jpeg', '*.gif', '*.webp',     # 图片
        '*.storyboard', '*.xib',                           # 界面文件
        '*.storyboardc',                                   # 编译后的storyboard
        '*.nib',                                           # 编译后的xib
        '*.bundle',                                        # 资源包
        '*.ttf', '*.otf',                                  # 字体
        '*.mp3', '*.wav', '*.m4a', '*.mp4', '*.mov',        # 媒体
        '*.metallib'                                            # metal or opengl文件
    ]
    
    # 扫描framework根目录
    resource_patterns.each do |pattern|
        Dir.glob(framework_dir + pattern).each do |resource_path|
            resource_file = Pathname(resource_path)
                # 其他资源使用BUILT_PRODUCTS_DIR/framework路径
                # 例如:${PODS_ROOT}/CocoaDebug/CocoaDebug.framework/App.storyboardc
            resource_entry = "${PODS_ROOT}/#{pod_name}/#{framework_name}/#{resource_file.basename}"            
            resources << resource_entry
        end
    end
    
    # 扫描framework/Resources目录
    resources_dir = framework_dir + 'Resources'
    if resources_dir.exist?
        resource_patterns.each do |pattern|
            Dir.glob(resources_dir + pattern).each do |resource_path|
                resource_file = Pathname(resource_path)
                
                # Resources目录中的资源也使用BUILT_PRODUCTS_DIR路径
                resource_entry = "${PODS_ROOT}/#{pod_name}/#{framework_name}/#{resource_file.basename}"
                resources << resource_entry                
            end
        end
    end
    
    resources
end

#xcodebuild(sandbox, target, sdk = 'macosx', deployment_target = nil, other_options = []) ⇒ Object



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/cocoapods-binary-matchup/rome/build_framework.rb', line 219

def xcodebuild(sandbox, target, sdk='macosx', deployment_target=nil, other_options=[])
  args = %W(-project #{sandbox.project_path.realdirpath} -scheme #{target} -configuration #{CONFIGURATION} -sdk #{sdk} )
  platform = PLATFORMS[sdk]
  args += Fourflusher::SimControl.new.destination(:oldest, platform, deployment_target) unless platform.nil?
  args += other_options
  Pod::UI.puts "📦 start compile project #{target} (#{args}) "
  log = `xcodebuild #{args.join(" ")} 2>&1`
  exit_code = $?.exitstatus  # Process::Status
  is_succeed = (exit_code == 0)

  if !is_succeed
    Pod::UI.puts "❌ Build failed for target #{target} (exit code: #{exit_code})".red
    begin
        if log.include?('** BUILD FAILED **')
            # use xcpretty to print build log
            # 64 represent command invalid. http://www.manpagez.com/man/3/sysexits/
            printer = XCPretty::Printer.new({:formatter => XCPretty::Simple, :colorize => 'auto'})
            log.each_line do |line|
              printer.pretty_print(line)
            end
        else
            raise "shouldn't be handle by xcpretty"
        end
    rescue
        puts log.red
    end
    Pod::UI.puts "💥 Compilation terminated due to build failure. Please check the error messages above.".red
    exit 1
  end
  [is_succeed, log]
end