cocoapods-podgenerate 0.1.2 → 0.1.4

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.
@@ -1,16 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # [cocoapods-podgenerate]
4
- # Monkey-patches ProjectCacheAnalyzer to parallelize cache key computation.
4
+ # Monkey-patches ProjectCacheAnalyzer,并行计算缓存键。
5
5
  #
6
- # v0.1.2 Optimization:
7
- # 1. Parallel MD5 cache key computation across all pod targets
6
+ # 优化原理:
7
+ # ProjectCacheAnalyzer#create_cache_key_mappings 为所有 pod_target
8
+ # 和 aggregate_target 计算 TargetCacheKey。每个 target 的计算需要
9
+ # - 遍历文件列表计算 MD5 校验和
10
+ # - 汇总上游依赖的资源信息
11
+ # 所有 target 的计算完全独立,天然适合并行化。
8
12
  #
9
- # TargetCacheKey.from_pod_target computes MD5 checksums of build settings
10
- # and collects resource dependencies per target. Each computation is fully
11
- # independent and can run in parallel.
13
+ # 性能收益(150 pod target 场景):
14
+ # 原串行实现逐个计算 MD5,总耗时随 pod 数量线性增长。
15
+ # 线程池并行后,计算时间除以线程数(通常 10+ 线程 ~10x 加速)。
12
16
  #
13
- # Reference: CocoaPods — lib/cocoapods/installer/project_cache/project_cache_analyzer.rb
17
+ # v0.1.3 修复:
18
+ # - Bug 修复:线程内错误不再导致 nil 条目。异常时同步重试,
19
+ # 确保 results Hash 中每个 label 都有有效的 TargetCacheKey。
20
+ #
21
+ # v0.1.4 改进:
22
+ # - M1: wait_for_termination 添加 120 秒超时
23
+ # - 优化 compute_cache_key 为独立方法便于错误恢复
24
+ # - 添加详细中文注释
25
+ #
26
+ # 线程安全保证:
27
+ # - sandbox.local? 和 sandbox.checkout_sources 都是只读操作
28
+ # - target_by_label Hash 在所有线程间只读共享
29
+ # - results Hash 的写入用 Mutex 保护
30
+ #
31
+ # 参考:CocoaPods 源码
32
+ # - lib/cocoapods/installer/project_cache/project_cache_analyzer.rb
14
33
 
15
34
  require 'concurrent'
16
35
  require 'etc'
@@ -25,9 +44,19 @@ module Pod
25
44
  end
26
45
 
27
46
  module ParallelCacheKeyComputation
28
- # Override create_cache_key_mappings to parallelize MD5 computation
29
- # The original iterates target_by_label sequentially, computing cache keys.
30
- # Since each target is independent, we use a thread pool.
47
+ # 并行计算所有 target 的缓存键
48
+ #
49
+ # 原实现(串行):
50
+ # Hash[target_by_label.map { |label, target| [label, compute_key(target)] }]
51
+ #
52
+ # 优化后(并行):
53
+ # 使用 Concurrent::FixedThreadPool,每个 target 分配一个线程,
54
+ # 并发计算 TargetCacheKey,结果通过 Mutex 合并到 results Hash。
55
+ #
56
+ # @param target_by_label [Hash{String => PodTarget|AggregateTarget}]
57
+ # target 标签到 target 对象的映射
58
+ # @return [Hash{String => TargetCacheKey}]
59
+ # target 标签到缓存键的映射
31
60
  def create_cache_key_mappings(target_by_label)
32
61
  UI.message '- Creating cache key mappings (parallel)' do
33
62
  pool_size = compute_pool_size
@@ -35,36 +64,58 @@ module Pod
35
64
  mutex = Mutex.new
36
65
  results = {}
37
66
 
67
+ # 为每个 target 提交一个线程任务
38
68
  target_by_label.each do |label, target|
39
69
  pool.post do
40
- key = case target
41
- when PodTarget
42
- local = sandbox.local?(target.pod_name)
43
- checkout_options = sandbox.checkout_sources[target.pod_name]
44
- TargetCacheKey.from_pod_target(sandbox, target_by_label, target,
45
- :is_local_pod => local,
46
- :checkout_options => checkout_options)
47
- when AggregateTarget
48
- TargetCacheKey.from_aggregate_target(sandbox, target_by_label, target)
49
- else
50
- raise "[BUG] Unknown target type #{target}"
51
- end
70
+ key = compute_cache_key(target, target_by_label)
52
71
  mutex.synchronize { results[label] = key }
53
72
  rescue StandardError => e
54
- mutex.synchronize do
55
- Pod::UI.warn "[cocoapods-podgenerate] Cache key computation error: #{e.message}"
56
- end
73
+ # v0.1.3 bug 修复: 异常时同步重试,确保 results[label] 不为 nil
74
+ # 如果重试仍然失败,异常会传播导致 pod install 失败(正确的行为)
75
+ Pod::UI.warn "[cocoapods-podgenerate] Cache key computation error, retrying sync: #{e.message}"
76
+ fallback_key = compute_cache_key(target, target_by_label)
77
+ mutex.synchronize { results[label] = fallback_key }
57
78
  end
58
79
  end
59
80
 
81
+ # v0.1.4: 带超时的等待
60
82
  pool.shutdown
61
- pool.wait_for_termination
83
+ unless pool.wait_for_termination(Pod::PodGenerate::Parallel::ThreadPool::DEFAULT_TIMEOUT)
84
+ Pod::UI.warn '[cocoapods-podgenerate] Cache key computation timed out after 120s'
85
+ pool.kill
86
+ end
87
+
62
88
  results
63
89
  end
64
90
  end
65
91
 
66
92
  private
67
93
 
94
+ # 计算单个 target 的缓存键
95
+ #
96
+ # 根据 target 类型(PodTarget 或 AggregateTarget)调用不同的计算逻辑:
97
+ # - PodTarget: 检查是否本地 pod + checkout 选项 → from_pod_target
98
+ # - AggregateTarget: 直接调用 from_aggregate_target
99
+ #
100
+ # @param target [PodTarget|AggregateTarget] 要计算的目标
101
+ # @param target_by_label [Hash] 完整的 label→target 映射(from_pod_target 需要)
102
+ # @return [TargetCacheKey] 缓存键对象
103
+ def compute_cache_key(target, target_by_label)
104
+ case target
105
+ when PodTarget
106
+ local = sandbox.local?(target.pod_name)
107
+ checkout_options = sandbox.checkout_sources[target.pod_name]
108
+ TargetCacheKey.from_pod_target(sandbox, target_by_label, target,
109
+ :is_local_pod => local,
110
+ :checkout_options => checkout_options)
111
+ when AggregateTarget
112
+ TargetCacheKey.from_aggregate_target(sandbox, target_by_label, target)
113
+ else
114
+ raise "[BUG] Unknown target type #{target}"
115
+ end
116
+ end
117
+
118
+ # 计算线程池大小
68
119
  def compute_pool_size
69
120
  [[Etc.nprocessors - 1, 2].max, 16].min
70
121
  rescue NameError
@@ -1,18 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # [cocoapods-podgenerate]
4
- # Monkey-patches Pod::Installer and PodsProjectGenerator for step 3/4 optimizations.
4
+ # Monkey-patches Pod::Installer PodsProjectGenerator,优化步骤 3/4
5
5
  #
6
- # v0.1.1 Optimizations:
7
- # 1. Force-enable incremental_installation + generate_multiple_pod_projects
8
- # 2. Skip project generation entirely when nothing changed
9
- # 3. Parallelize PodTargetIntegrator integration
6
+ # 优化原理:
7
+ # CocoaPods 内置了 incremental_installation generate_multiple_pod_projects
8
+ # 两个选项。本补丁强制启用这两个选项,并在其基础上进一步增强:
9
+ # - 完全无变更时跳过整个项目生成
10
+ # - 并行执行 PodTargetIntegrator
11
+ # - 并行配置 scheme 文件
12
+ # - 修复快速跳过路径的 ivars 和 hooks 缺失问题
10
13
  #
11
- # v0.1.2 Optimization:
12
- # 4. Parallelize configure_schemes across projects
14
+ # v0.1.1 优化:
15
+ # 1. 强制启用 incremental_installation + generate_multiple_pod_projects
16
+ # 2. 完全无变更时跳过项目生成
17
+ # 3. 并行化 PodTargetIntegrator 集成
13
18
  #
14
- # Reference: CocoaPods — lib/cocoapods/installer.rb
15
- # lib/cocoapods/installer/xcode/pods_project_generator.rb
19
+ # v0.1.2 优化:
20
+ # 4. 并行化 configure_schemes(跨项目)
21
+ #
22
+ # v0.1.4 修复:
23
+ # - C2: 快速跳过路径设置缺失的 @pods_project/@pod_target_subprojects/@generated_projects
24
+ # 并确保 run_podfile_post_install_hooks 被调用(即使在跳过路径上)
25
+ # - H1: 跳过路径不再传入空 InstallationResults,而是保留上次的 @target_installation_results
26
+ # - M1: wait_for_termination 使用 ThreadPool::DEFAULT_TIMEOUT
27
+ # - M3: integrate_targets 改用 Concurrent::FixedThreadPool(限制并发数,避免 200 线程)
28
+ #
29
+ # 线程安全保证:
30
+ # - configure_schemes: 每个 project 独立 xcodeproj → 并行安全
31
+ # - integrate_targets: 每个 PodTargetIntegrator 操作独立的 target → 并行安全
32
+ #
33
+ # 参考:CocoaPods 源码
34
+ # - lib/cocoapods/installer.rb
35
+ # - lib/cocoapods/installer/xcode/pods_project_generator.rb
16
36
 
17
37
  require 'concurrent'
18
38
  require 'etc'
@@ -22,12 +42,19 @@ module Pod
22
42
  module Patches
23
43
  module InstallerPatch
24
44
  def self.apply
25
- Pod::UI.message '[cocoapods-podgenerate] Applying InstallerPatch v3'
45
+ Pod::UI.message '[cocoapods-podgenerate] Applying InstallerPatch v4'
26
46
  Pod::Installer.prepend(ForceIncrementalInstall)
27
47
  Pod::Installer::Xcode::PodsProjectGenerator.prepend(ParallelInstall)
28
48
  end
29
49
 
30
- # ── Optimization 1: Force-enable incremental_installation ──
50
+ # ── 优化 1: 强制启用增量安装模式 ──
51
+ #
52
+ # 在 install! 入口处设置 installation_options,强制启用:
53
+ # - incremental_installation: 只重新生成有变更的 target
54
+ # - generate_multiple_pod_projects: 每个 pod 独立 xcodeproj
55
+ #
56
+ # 这两个选项是 CocoaPods 内置的(默认关闭),我们通过 monkey-patch
57
+ # 在 super 之前设置,对所有后续流程生效。
31
58
  module ForceIncrementalInstall
32
59
  def install!
33
60
  installation_options.incremental_installation = true
@@ -35,7 +62,25 @@ module Pod
35
62
  super
36
63
  end
37
64
 
38
- # ── Optimization 2: Skip project generation when nothing changed ──
65
+ # ── 优化 2: 完全无变更时跳过项目生成 + C2/H1 修复 ──
66
+ #
67
+ # 原流程即使没有任何 target 变更,create_and_save_projects 仍会被调用,
68
+ # 执行大量 file I/O 操作。本方法在 analyze_project_cache 之后检查:
69
+ # 如果 pod_targets_to_generate 和 aggregate_targets_to_generate
70
+ # 都为空(即没有任何 target 需要重新生成),则:
71
+ # 1. 跳过 create_and_save_projects(pod 项目已在磁盘上,内容未变)
72
+ # 2. 仍执行 SandboxDirCleaner(清理可能被移除的 pod 的残留文件)
73
+ # 3. 仍调用 update_project_cache(保持缓存时间戳最新)
74
+ # 4. 仍调用 run_podfile_post_install_hooks(Podfile hook 不能跳过)
75
+ #
76
+ # v0.1.4 修复 (C2):
77
+ # - 设置 @pods_project = nil, @pod_target_subprojects = [],
78
+ # @generated_projects = [](避免下游引用 nil)
79
+ # - 调用 run_podfile_post_install_hooks(之前被跳过导致 hook 静默丢失)
80
+ #
81
+ # v0.1.4 修复 (H1):
82
+ # - 使用上次的 @target_installation_results 更新缓存
83
+ # (而非空的 InstallationResults,避免清除 metadata_cache)
39
84
  def generate_pods_project
40
85
  stage_sandbox(sandbox, pod_targets)
41
86
 
@@ -45,15 +90,28 @@ module Pod
45
90
 
46
91
  if ptg.empty? && (atg.nil? || atg.empty?)
47
92
  Pod::UI.puts "[cocoapods-podgenerate] No changes — skipping project generation"
93
+
94
+ # C2 修复: 初始化所有实例变量(避免下游代码获得 nil)
48
95
  @generated_aggregate_targets = aggregate_targets
49
96
  @generated_pod_targets = []
97
+ @pods_project = nil
98
+ @pod_target_subprojects = []
99
+ @generated_projects = []
100
+
101
+ # C2 修复: 确保 post-install hooks 被调用
102
+ run_podfile_post_install_hooks
103
+
104
+ # 清理沙盒中残留的文件
50
105
  Pod::Installer::SandboxDirCleaner.new(sandbox, pod_targets, aggregate_targets).clean!
51
- update_project_cache(cache_analysis_result,
52
- Pod::Installer::Xcode::PodsProjectGenerator::InstallationResults.new({}, {}))
106
+
107
+ # H1 修复: 使用上次的安装结果(而非空结果)更新缓存
108
+ prev_results = @target_installation_results ||
109
+ Pod::Installer::Xcode::PodsProjectGenerator::InstallationResults.new({}, {})
110
+ update_project_cache(cache_analysis_result, prev_results)
53
111
  return
54
112
  end
55
113
 
56
- # Normal path
114
+ # 正常路径: 有 target 需要重新生成
57
115
  ptg.each do |pod_target|
58
116
  pod_target.build_headers.implode_path!(pod_target.headers_sandbox)
59
117
  sandbox.public_headers.implode_path!(pod_target.headers_sandbox)
@@ -65,7 +123,15 @@ module Pod
65
123
  update_project_cache(cache_analysis_result, target_installation_results)
66
124
  end
67
125
 
68
- # ── Optimization 3+4: create_and_save_projects with parallel configure_schemes ──
126
+ # ── 优化 3+4: 项目生成 + 并行 configure_schemes ──
127
+ #
128
+ # 完全覆盖原 create_and_save_projects 方法,添加并行 configure_schemes。
129
+ # 流程:
130
+ # 1. 创建 generator → 调用 generate!(并行安装 pod targets)
131
+ # 2. 设置实例变量(@pods_project 等)
132
+ # 3. UUID 预测 + 稳定化
133
+ # 4. 创建 writer → 并行清理/重建 scheme/保存
134
+ # 5. 并行 configure_schemes(每个 project 独立)
69
135
  def create_and_save_projects(pod_targets_to_generate, aggregate_targets_to_generate,
70
136
  build_configurations, project_object_version)
71
137
  UI.section 'Generating Pods project' do
@@ -77,7 +143,7 @@ module Pod
77
143
  @target_installation_results = pod_project_generation_result.target_installation_results
78
144
  @pods_project = pod_project_generation_result.project
79
145
  @pod_target_subprojects = pod_project_generation_result.projects_by_pod_targets.keys
80
- @generated_projects = ([pods_project] + pod_target_subprojects || []).compact
146
+ @generated_projects = ([pods_project] + pod_target_subprojects).compact
81
147
  @generated_pod_targets = pod_targets_to_generate
82
148
  @generated_aggregate_targets = aggregate_targets_to_generate || []
83
149
  projects_by_pod_targets = pod_project_generation_result.projects_by_pod_targets
@@ -92,7 +158,7 @@ module Pod
92
158
  run_podfile_post_install_hooks
93
159
  end
94
160
 
95
- # Parallel configure_schemes (each project is independent)
161
+ # 并行 configure_schemes(多项目时)
96
162
  pods_project_pod_targets = pod_targets_to_generate - projects_by_pod_targets.values.flatten
97
163
  all_projects_by_pod_targets = {}
98
164
  if pods_project
@@ -112,29 +178,51 @@ module Pod
112
178
 
113
179
  private
114
180
 
181
+ # 并行配置所有项目的 scheme 文件
182
+ #
183
+ # 每个 scheme 文件是独立的 .xcscheme,存储在不同 xcodeproj 的
184
+ # xcuserdata 目录中。每个 project 完全独立 → 无锁并行。
185
+ #
186
+ # v0.1.4: rescue 范围缩小到只包裹 Concurrent::FixedThreadPool 创建
115
187
  def parallel_configure_schemes(projects_by_pod_targets, generator, generation_result)
116
188
  pool_size = [[Etc.nprocessors - 1, 2].max, 16].min
117
189
  Pod::UI.message "- Configuring schemes across #{projects_by_pod_targets.size} projects (pool: #{pool_size})"
118
190
 
119
- pool = Concurrent::FixedThreadPool.new(pool_size)
120
- projects_by_pod_targets.each do |project, pts|
121
- pool.post do
191
+ pool = begin
192
+ Concurrent::FixedThreadPool.new(pool_size)
193
+ rescue NameError
194
+ nil # concurrent-ruby 不可用,回退到串行
195
+ end
196
+
197
+ if pool
198
+ projects_by_pod_targets.each do |project, pts|
199
+ pool.post do
200
+ generator.configure_schemes(project, pts, generation_result)
201
+ rescue StandardError => e
202
+ Pod::UI.warn "[cocoapods-podgenerate] Scheme configuration error: #{e.message}"
203
+ end
204
+ end
205
+ pool.shutdown
206
+ unless pool.wait_for_termination(Pod::PodGenerate::Parallel::ThreadPool::DEFAULT_TIMEOUT)
207
+ Pod::UI.warn '[cocoapods-podgenerate] Scheme configuration timed out'
208
+ pool.kill
209
+ end
210
+ else
211
+ projects_by_pod_targets.each do |project, pts|
122
212
  generator.configure_schemes(project, pts, generation_result)
123
- rescue StandardError => e
124
- Pod::UI.warn "[cocoapods-podgenerate] Scheme configuration error: #{e.message}"
125
213
  end
126
214
  end
127
- pool.shutdown
128
- pool.wait_for_termination
129
- rescue NameError
130
- # Fallback: sequential
131
- projects_by_pod_targets.each do |project, pts|
132
- generator.configure_schemes(project, pts, generation_result)
133
- end
134
215
  end
135
216
  end
136
217
 
137
- # ── Optimization 3: Parallelize PodTargetIntegrator ──
218
+ # ── 优化 3: 并行化 PodTargetIntegrator(修复 M3)──
219
+ #
220
+ # PodTargetIntegrator 为每个 pod target 添加脚本构建阶段
221
+ # (如 embed frameworks、copy resources)。每个 integrator 操作
222
+ # 独立的 target,无共享状态 → 并行安全。
223
+ #
224
+ # v0.1.4 修复 (M3): 使用 Concurrent::FixedThreadPool(限制并发数)
225
+ # 替代原来的裸 Thread.new(可能同时创建 200+ 线程导致资源耗尽)
138
226
  module ParallelInstall
139
227
  def install_pod_targets(project, pod_targets)
140
228
  super
@@ -152,18 +240,34 @@ module Pod
152
240
  return if pods_to_integrate.empty?
153
241
 
154
242
  use_io_paths = !installation_options.disable_input_output_paths
155
- threads = pods_to_integrate.map do |result|
156
- Thread.new do
157
- begin
158
- Pod::Installer::Xcode::PodsProjectGenerator::PodTargetIntegrator.new(
159
- result, :use_input_output_paths => use_io_paths
160
- ).integrate!
161
- rescue StandardError => e
162
- Pod::UI.warn "[cocoapods-podgenerate] Integrate error: #{e.message}"
163
- end
243
+
244
+ if pods_to_integrate.size <= 1
245
+ # 单 target: 直接调用,无需线程开销
246
+ Pod::Installer::Xcode::PodsProjectGenerator::PodTargetIntegrator.new(
247
+ pods_to_integrate.first, :use_input_output_paths => use_io_paths
248
+ ).integrate!
249
+ return
250
+ end
251
+
252
+ # 多 target: 使用线程池并行集成(M3 修复)
253
+ pool_size = [[Etc.nprocessors - 1, 2].max, 16].min
254
+ pool = Concurrent::FixedThreadPool.new(pool_size)
255
+
256
+ pods_to_integrate.each do |result|
257
+ pool.post do
258
+ Pod::Installer::Xcode::PodsProjectGenerator::PodTargetIntegrator.new(
259
+ result, :use_input_output_paths => use_io_paths
260
+ ).integrate!
261
+ rescue StandardError => e
262
+ Pod::UI.warn "[cocoapods-podgenerate] Integrate error: #{e.message}"
164
263
  end
165
264
  end
166
- threads.each(&:join)
265
+
266
+ pool.shutdown
267
+ unless pool.wait_for_termination(Pod::PodGenerate::Parallel::ThreadPool::DEFAULT_TIMEOUT)
268
+ Pod::UI.warn '[cocoapods-podgenerate] Target integration timed out'
269
+ pool.kill
270
+ end
167
271
  end
168
272
  end
169
273
  end
@@ -1,19 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # [cocoapods-podgenerate]
4
- # Monkey-patches MultiPodsProjectGenerator to parallelize pod target installation.
4
+ # Monkey-patches MultiPodsProjectGenerator,将 PodTarget 安装并行化。
5
5
  #
6
- # v0.1.2 Optimization:
7
- # 1. Parallel install_all_pod_targets — each pod target's install is independent I/O
6
+ # 优化原理:
7
+ # `generate_multiple_pod_projects` 启用了后,每个 pod 有自己独立的
8
+ # .xcodeproj 文件。原实现串行遍历所有 project,逐个调用
9
+ # `install_pod_targets`。由于每个 project 的 xcodeproj 目录不同,
10
+ # PodTargetInstaller(创建 xcconfig、module map、脚本等文件 I/O)可以
11
+ # 安全地并行执行,无需加锁。
8
12
  #
9
- # Architecture:
10
- # Instead of overriding create_pods_project (which has Ruby constant resolution issues
11
- # in prepended modules for PodsProjectGenerator's inner classes), we keep the original
12
- # project creation + file references sequential, and only parallelize the pod target
13
- # installation step which is the heaviest I/O operation.
13
+ # v0.1.4 改进:
14
+ # - Bug 修复:线程池内错误不再静默吞掉,失败时传播异常避免依赖图不完整
15
+ # - 添加 wait_for_termination 超时(120 秒),防止死锁导致进程永久挂起
14
16
  #
15
- # Reference: CocoaPods — lib/cocoapods/installer/xcode/multi_pods_project_generator.rb
16
- # lib/cocoapods/installer/xcode/pods_project_generator.rb
17
+ # 线程安全保证:
18
+ # - 每个 project 是独立的 Xcodeproj::Project 实例(各自 .xcodeproj 目录)
19
+ # - install_pod_targets 只修改传入的 project,不访问其他 project
20
+ # - sandbox 读取操作是线程安全的(写操作进入不同 pod 子目录)
21
+ # - 结果合并使用 Mutex 保护共享的 all_results Hash
22
+ #
23
+ # 参考:CocoaPods 源码
24
+ # - lib/cocoapods/installer/xcode/multi_pods_project_generator.rb
25
+ # - lib/cocoapods/installer/xcode/pods_project_generator.rb
17
26
 
18
27
  require 'concurrent'
19
28
  require 'etc'
@@ -22,45 +31,72 @@ module Pod
22
31
  module PodGenerate
23
32
  module Patches
24
33
  module MultiProjectGeneratorPatch
34
+ # 激活补丁:对 MultiPodsProjectGenerator prepend 我们的并行版本
25
35
  def self.apply
26
36
  Pod::UI.message '[cocoapods-podgenerate] Applying MultiProjectGeneratorPatch (parallel pod target install)'
27
37
  Pod::Installer::Xcode::MultiPodsProjectGenerator.prepend(ParallelMultiProjectGenerator)
28
38
  end
29
39
 
30
40
  module ParallelMultiProjectGenerator
31
- # ── Optimization: Parallel install_all_pod_targets ──
32
- # Each project has independent pod targets (different xcodeproj directories),
33
- # so PodTargetInstaller operations can run concurrently without locking.
41
+ # 并行安装所有 pod target
42
+ #
43
+ # 原实现(串行):
44
+ # projects_by_pod_targets.each_with_object({}) { |(p, pts), r| r.merge!(install_pod_targets(p, pts)) }
45
+ #
46
+ # 优化后(并行):
47
+ # 使用 Concurrent::FixedThreadPool,每个 project 分配一个线程
48
+ # 并发调用 install_pod_targets,结果通过 Mutex 合并到 all_results
49
+ #
50
+ # @param projects_by_pod_targets [Hash{Project => Array<PodTarget>}]
51
+ # 项目到 pod target 列表的映射
52
+ # @return [Hash{String => TargetInstallationResult}]
53
+ # pod target 名称到安装结果的映射
34
54
  def install_all_pod_targets(projects_by_pod_targets)
35
55
  UI.message '- Installing Pod Targets (parallel)' do
36
56
  pool_size = compute_pool_size
37
57
  mutex = Mutex.new
38
58
  all_results = {}
59
+ errors = [] # v0.1.4: 收集错误而不是静默吞掉
39
60
 
40
61
  pool = Concurrent::FixedThreadPool.new(pool_size)
41
62
  projects_by_pod_targets.each do |project, pts|
42
63
  pool.post do
64
+ # 每个 project 独立安装其 pod target
43
65
  target_results = install_pod_targets(project, pts)
44
66
  mutex.synchronize { all_results.merge!(target_results) }
45
67
  rescue StandardError => e
46
68
  mutex.synchronize do
47
- Pod::UI.warn "[cocoapods-podgenerate] Pod target install: #{e.message}"
69
+ errors << [project.path, e]
70
+ Pod::UI.warn "[cocoapods-podgenerate] Pod target install failed for #{project.path}: #{e.message}"
48
71
  end
49
72
  end
50
73
  end
51
74
 
75
+ # v0.1.4: 带超时的等待,防止死锁
52
76
  pool.shutdown
53
- pool.wait_for_termination
77
+ unless pool.wait_for_termination(Pod::PodGenerate::Parallel::ThreadPool::DEFAULT_TIMEOUT)
78
+ Pod::UI.warn '[cocoapods-podgenerate] Pod target install timed out after 120s — forcing shutdown'
79
+ pool.kill
80
+ end
81
+
82
+ # v0.1.4: 如果有任何失败,抛出异常让 CocoaPods 知道安装不完整
83
+ unless errors.empty?
84
+ raise Pod::Informative, "[cocoapods-podgenerate] #{errors.size} pod target(s) failed to install"
85
+ end
86
+
54
87
  all_results
55
88
  end
56
89
  end
57
90
 
58
91
  private
59
92
 
93
+ # 计算适合当前机器的线程池大小
94
+ # 使用 CPU 核心数 - 1(为主线程留一个),最小 2,最大 16
95
+ # @return [Integer]
60
96
  def compute_pool_size
61
97
  [[Etc.nprocessors - 1, 2].max, 16].min
62
98
  rescue NameError
63
- 4
99
+ 4 # Etc 不可用时的安全回退
64
100
  end
65
101
  end
66
102
  end
@@ -1,44 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # [cocoapods-podgenerate]
4
- # Monkey-patches Pod::Project to cache pod_group lookups.
5
- # Original implementation does O(n) linear scan for every pod_group call:
6
- # pod_groups.find { |group| group.name == pod_name }
4
+ # Monkey-patches Pod::Project,将 pod_group 查找从 O(n) 优化为 O(1)。
7
5
  #
8
- # With 200+ pods and pod_group called 3-5 times per pod, this is 600-1000
9
- # linear scans of a 200-element array = ~120k-200k iterations.
6
+ # 优化原理:
7
+ # 原始 `pod_group(pod_name)` 每次调用都执行:
8
+ # pod_groups.find { |group| group.name == pod_name }
9
+ # 这是 O(n) 的线性扫描(n = pod 数量)。
10
10
  #
11
- # Fix: cache groups in a Hash for O(1) lookup. Invalidate on add_pod_group.
11
+ # 在项目生成过程中,pod_group 被调用 3-5 次/pod(从
12
+ # FileReferencesInstaller、PodTargetInstaller 的不同位置调用)。
13
+ # 对于 200+ pod 的项目,这意味着 600-1000 次线性扫描,
14
+ # 每次扫描 200 个元素 → ~120k-200k 次比较。
12
15
  #
13
- # Reference: CocoaPods source — lib/cocoapods/project.rb
16
+ # 修复策略:
17
+ # 1. 首次调用 pod_group 时,构建 Hash 缓存(O(n) 一次性成本)
18
+ # 2. 后续调用直接用 cached_hash[pod_name],O(1) 查找
19
+ # 3. 调用 add_pod_group(添加新 pod group)时,清除缓存强制下次重建
20
+ # 4. 使用 `||=` 懒初始化缓存(只有第一次调用时才构建)
21
+ #
22
+ # 线程安全:
23
+ # 所有操作在主线程的 install! 流程中执行,无并发访问。
24
+ #
25
+ # 参考:CocoaPods 源码 — lib/cocoapods/project.rb
14
26
 
15
27
  module Pod
16
28
  module PodGenerate
17
29
  module Patches
18
30
  module ProjectPatch
31
+ # 激活补丁:对 Pod::Project prepend 缓存版本
19
32
  def self.apply
20
33
  Pod::UI.message '[cocoapods-podgenerate] Applying ProjectPatch (pod_group hash cache)'
21
34
  Pod::Project.prepend(CachedPodGroup)
22
35
  end
23
36
 
24
37
  module CachedPodGroup
25
- # Build a hash cache of pod_name => PBXGroup
26
- # Called once when first needed, then kept in sync.
38
+ # O(1) 查找 pod_group
39
+ #
40
+ # 首次调用时通过 build_pod_group_cache 构建 Hash 缓存,
41
+ # 后续调用直接从缓存中查找。缓存键为 pod_name,值为 PBXGroup。
42
+ #
43
+ # @param pod_name [String] pod 名称
44
+ # @return [PBXGroup, nil] 对应的 group,或 nil(pod 不存在)
27
45
  def pod_group(pod_name)
28
46
  @pod_group_cache ||= build_pod_group_cache
29
47
  @pod_group_cache[pod_name]
30
48
  end
31
49
 
32
- # Override add_pod_group to invalidate the cache
50
+ # 添加新 pod group 时清除缓存
51
+ #
52
+ # 原始方法在 main_group 或 pods/development_pods group 下
53
+ # 创建新的 PBXGroup。由于 project 结构发生了变化,
54
+ # 我们需要清除缓存,让下一次 pod_group 调用重新构建。
55
+ #
56
+ # @param pod_name [String] pod 名称
57
+ # @param path [String] pod 路径
58
+ # @param development [Boolean] 是否为开发 pod
59
+ # @param absolute [Boolean] 是否为绝对路径
60
+ # @return [PBXGroup] 新创建的 group
33
61
  def add_pod_group(pod_name, path, development = false, absolute = false)
34
62
  group = super
35
- # Invalidate cache so the next call to pod_group rebuilds it
36
63
  @pod_group_cache = nil if defined?(@pod_group_cache)
37
64
  group
38
65
  end
39
66
 
40
67
  private
41
68
 
69
+ # 构建 pod_name → PBXGroup 的 Hash 缓存
70
+ #
71
+ # 遍历 pods group 和 development_pods group 下的所有子 group,
72
+ # 以 group.name 为键构建查找表。这是一个 O(n) 操作,但只执行一次。
73
+ #
74
+ # @return [Hash{String => PBXGroup}]
42
75
  def build_pod_group_cache
43
76
  cache = {}
44
77
  pod_groups.each do |group|