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.
- checksums.yaml +4 -4
- data/lib/cocoapods-podgenerate/benchmark/profiler.rb +6 -4
- data/lib/cocoapods-podgenerate/command.rb +20 -5
- data/lib/cocoapods-podgenerate/parallel/batch_processor.rb +26 -25
- data/lib/cocoapods-podgenerate/parallel/thread_pool.rb +12 -21
- data/lib/cocoapods-podgenerate/patches/analyzer_patch.rb +210 -36
- data/lib/cocoapods-podgenerate/patches/cache_analyzer_patch.rb +77 -26
- data/lib/cocoapods-podgenerate/patches/installer_patch.rb +145 -41
- data/lib/cocoapods-podgenerate/patches/multi_project_generator_patch.rb +52 -16
- data/lib/cocoapods-podgenerate/patches/project_patch.rb +44 -11
- data/lib/cocoapods-podgenerate/patches/project_writer_patch.rb +144 -100
- data/lib/cocoapods-podgenerate/patches/user_integrator_patch.rb +64 -14
- data/lib/cocoapods-podgenerate.rb +48 -7
- metadata +1 -1
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# [cocoapods-podgenerate]
|
|
4
|
-
# Monkey-patches ProjectCacheAnalyzer
|
|
4
|
+
# Monkey-patches ProjectCacheAnalyzer,并行计算缓存键。
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
6
|
+
# 优化原理:
|
|
7
|
+
# ProjectCacheAnalyzer#create_cache_key_mappings 为所有 pod_target
|
|
8
|
+
# 和 aggregate_target 计算 TargetCacheKey。每个 target 的计算需要
|
|
9
|
+
# - 遍历文件列表计算 MD5 校验和
|
|
10
|
+
# - 汇总上游依赖的资源信息
|
|
11
|
+
# 所有 target 的计算完全独立,天然适合并行化。
|
|
8
12
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
13
|
+
# 性能收益(150 pod target 场景):
|
|
14
|
+
# 原串行实现逐个计算 MD5,总耗时随 pod 数量线性增长。
|
|
15
|
+
# 线程池并行后,计算时间除以线程数(通常 10+ 线程 → ~10x 加速)。
|
|
12
16
|
#
|
|
13
|
-
#
|
|
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
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
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 =
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
4
|
+
# Monkey-patches Pod::Installer 和 PodsProjectGenerator,优化步骤 3/4。
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
6
|
+
# 优化原理:
|
|
7
|
+
# CocoaPods 内置了 incremental_installation 和 generate_multiple_pod_projects
|
|
8
|
+
# 两个选项。本补丁强制启用这两个选项,并在其基础上进一步增强:
|
|
9
|
+
# - 完全无变更时跳过整个项目生成
|
|
10
|
+
# - 并行执行 PodTargetIntegrator
|
|
11
|
+
# - 并行配置 scheme 文件
|
|
12
|
+
# - 修复快速跳过路径的 ivars 和 hooks 缺失问题
|
|
10
13
|
#
|
|
11
|
-
# v0.1.
|
|
12
|
-
#
|
|
14
|
+
# v0.1.1 优化:
|
|
15
|
+
# 1. 强制启用 incremental_installation + generate_multiple_pod_projects
|
|
16
|
+
# 2. 完全无变更时跳过项目生成
|
|
17
|
+
# 3. 并行化 PodTargetIntegrator 集成
|
|
13
18
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
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
|
|
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
|
-
# ──
|
|
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
|
-
# ──
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
#
|
|
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
|
-
# ──
|
|
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
|
|
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
|
-
#
|
|
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 =
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
# ──
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
|
4
|
+
# Monkey-patches MultiPodsProjectGenerator,将 PodTarget 安装并行化。
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
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
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
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
|
-
#
|
|
16
|
-
#
|
|
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
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
9
|
-
#
|
|
6
|
+
# 优化原理:
|
|
7
|
+
# 原始 `pod_group(pod_name)` 每次调用都执行:
|
|
8
|
+
# pod_groups.find { |group| group.name == pod_name }
|
|
9
|
+
# 这是 O(n) 的线性扫描(n = pod 数量)。
|
|
10
10
|
#
|
|
11
|
-
#
|
|
11
|
+
# 在项目生成过程中,pod_group 被调用 3-5 次/pod(从
|
|
12
|
+
# FileReferencesInstaller、PodTargetInstaller 的不同位置调用)。
|
|
13
|
+
# 对于 200+ pod 的项目,这意味着 600-1000 次线性扫描,
|
|
14
|
+
# 每次扫描 200 个元素 → ~120k-200k 次比较。
|
|
12
15
|
#
|
|
13
|
-
#
|
|
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
|
-
#
|
|
26
|
-
#
|
|
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
|
-
#
|
|
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|
|