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,18 +1,37 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# [cocoapods-podgenerate]
|
|
4
|
-
# Monkey-patches PodsProjectWriter
|
|
4
|
+
# Monkey-patches PodsProjectWriter 以支持增量 + 并行项目保存。
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# 优化原理:
|
|
7
|
+
# `generate_multiple_pod_projects` 启用后,每个 pod 有独立的 xcodeproj 文件,
|
|
8
|
+
# 每个文件的操作(清理空组、重建用户 scheme、排序、保存)天然线程安全,
|
|
9
|
+
# 因为不同 xcodeproj 在不同目录,不存在共享状态。
|
|
9
10
|
#
|
|
10
|
-
# v0.1.
|
|
11
|
-
#
|
|
12
|
-
#
|
|
11
|
+
# v0.1.1 优化:
|
|
12
|
+
# 1. SHA256 摘要比对,跳过未变更项目的 sort+save
|
|
13
|
+
# 2. 多项目文件并行保存
|
|
13
14
|
#
|
|
14
|
-
#
|
|
15
|
+
# v0.1.2 优化:
|
|
16
|
+
# 3. 并行 cleanup_projects(空 group 清理)
|
|
17
|
+
# 4. 并行 recreate_user_schemes(scheme 文件创建)
|
|
18
|
+
#
|
|
19
|
+
# v0.1.4 修复:
|
|
20
|
+
# - L1: digest_file 返回 nil 时 nil==nil 导致误跳过保存,改为只有
|
|
21
|
+
# 两个摘要都有效且相等时才跳过
|
|
22
|
+
# - M1: wait_for_termination 加 120s 超时
|
|
23
|
+
# - L2: 移除无效的 || [] 死代码
|
|
24
|
+
#
|
|
25
|
+
# 线程安全保证:
|
|
26
|
+
# - 每个 xcodeproj 是独立目录,project.save 写项目名.pbxproj
|
|
27
|
+
# - cleanup_single_project 只操作传入的 project 对象
|
|
28
|
+
# - recreate_schemes_for_project 只操作传入的 project 对象
|
|
29
|
+
# - results_by_native_target 在所有线程间只读共享(构建一次后不变)
|
|
30
|
+
#
|
|
31
|
+
# 参考:CocoaPods 源码
|
|
32
|
+
# - lib/cocoapods/installer/xcode/pods_project_generator/pods_project_writer.rb
|
|
15
33
|
|
|
34
|
+
require 'digest'
|
|
16
35
|
require 'concurrent'
|
|
17
36
|
require 'etc'
|
|
18
37
|
|
|
@@ -21,37 +40,55 @@ module Pod
|
|
|
21
40
|
module Patches
|
|
22
41
|
module ProjectWriterPatch
|
|
23
42
|
def self.apply
|
|
24
|
-
Pod::UI.message '[cocoapods-podgenerate] Applying ProjectWriterPatch
|
|
43
|
+
Pod::UI.message '[cocoapods-podgenerate] Applying ProjectWriterPatch v4 (incremental + parallel save + parallel write)'
|
|
25
44
|
Pod::Installer::Xcode::PodsProjectWriter.prepend(IncrementalAndParallelSave)
|
|
26
45
|
end
|
|
27
46
|
|
|
28
47
|
module IncrementalAndParallelSave
|
|
48
|
+
# 初始化:调用原始构造器后,计算所有项目的初始 SHA256 摘要
|
|
49
|
+
#
|
|
50
|
+
# @param sandbox [Sandbox]
|
|
51
|
+
# @param projects [Array<Project>] 所有需要管理的 Xcodeproj 项目
|
|
52
|
+
# @param pod_target_installation_results [Hash] pod target 安装结果
|
|
53
|
+
# @param installation_options [InstallationOptions]
|
|
29
54
|
def initialize(sandbox, projects, pod_target_installation_results, installation_options)
|
|
30
55
|
super
|
|
31
|
-
@project_digests = {}
|
|
32
|
-
@
|
|
33
|
-
@sort_needed = {}
|
|
56
|
+
@project_digests = {} # project.object_id => SHA256 摘要
|
|
57
|
+
@sort_needed = {} # project.object_id => 是否需要排序
|
|
34
58
|
compute_initial_digests
|
|
35
59
|
end
|
|
36
60
|
|
|
37
|
-
#
|
|
61
|
+
# 并行清理、重建 scheme、然后增量保存所有项目
|
|
62
|
+
#
|
|
63
|
+
# 流程:
|
|
64
|
+
# 1. 并行清理每个项目的空 groups
|
|
65
|
+
# 2. 并行重建 scheme(将 test target 附加到 library target)
|
|
66
|
+
# 3. 执行 post-install hooks(yield)
|
|
67
|
+
# 4. SHA256 增量判断 + 并行保存
|
|
38
68
|
def write!
|
|
39
|
-
# Parallel cleanup (each project is independent)
|
|
40
69
|
parallel_cleanup_projects(@projects)
|
|
41
|
-
|
|
42
|
-
# Parallel recreate_user_schemes (each project is independent)
|
|
43
70
|
parallel_recreate_user_schemes(@projects)
|
|
44
|
-
|
|
45
71
|
yield if block_given?
|
|
46
|
-
|
|
47
72
|
save_projects(@projects)
|
|
48
73
|
end
|
|
49
74
|
|
|
50
|
-
#
|
|
75
|
+
# 增量 + 并行保存项目
|
|
76
|
+
#
|
|
77
|
+
# 流程:
|
|
78
|
+
# 1. 过滤: 跳过 pbxproj 内容未变的项目(SHA256 摘要比对)
|
|
79
|
+
# 2. 排序: 对需要保存的项目调用 sort(:groups_position => :below)
|
|
80
|
+
# 3. 保存: 多项目并行写入(每个 xcodeproj 独立目录)
|
|
81
|
+
#
|
|
82
|
+
# 【Bug 修复 L1】
|
|
83
|
+
# 只有当 old_digest 和 current_digest 都非 nil 且相等时才跳过。
|
|
84
|
+
# 如果任一为 nil(例如文件不可读),一律重新保存以确保项目完整性。
|
|
85
|
+
#
|
|
86
|
+
# @param projects [Array<Project>] 要保存的项目列表
|
|
51
87
|
def save_projects(projects)
|
|
52
|
-
# Filter: skip projects whose pbxproj is unchanged
|
|
53
88
|
to_save = projects.select do |project|
|
|
54
|
-
|
|
89
|
+
old = @project_digests[project.object_id]
|
|
90
|
+
cur = digest_pbxproj(project)
|
|
91
|
+
if old && cur && old == cur
|
|
55
92
|
Pod::UI.message "- Skipping unchanged project #{UI.path project.path}"
|
|
56
93
|
false
|
|
57
94
|
else
|
|
@@ -60,54 +97,63 @@ module Pod
|
|
|
60
97
|
end
|
|
61
98
|
return if to_save.empty?
|
|
62
99
|
|
|
63
|
-
#
|
|
100
|
+
# 排序(串行 — sort 操作轻量,并行开销反而更大)
|
|
64
101
|
to_save.each { |p| p.sort(:groups_position => :below) if needs_sort?(p) }
|
|
65
102
|
|
|
66
|
-
#
|
|
103
|
+
# 并行保存
|
|
67
104
|
if to_save.size > 1
|
|
68
105
|
Pod::UI.message "- Saving #{to_save.size} projects in parallel"
|
|
69
106
|
threads = to_save.map do |project|
|
|
70
107
|
Thread.new do
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Pod::UI.warn "[cocoapods-podgenerate] Parallel save error: #{e.message}"
|
|
77
|
-
end
|
|
108
|
+
Pod::UI.message "- Writing Xcode project file to #{UI.path project.path}"
|
|
109
|
+
project.save
|
|
110
|
+
update_digest(project)
|
|
111
|
+
rescue StandardError => e
|
|
112
|
+
Pod::UI.warn "[cocoapods-podgenerate] Parallel save error: #{e.message}"
|
|
78
113
|
end
|
|
79
114
|
end
|
|
80
115
|
threads.each(&:join)
|
|
81
116
|
else
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
117
|
+
project = to_save.first
|
|
118
|
+
Pod::UI.message "- Writing Xcode project file to #{UI.path project.path}"
|
|
119
|
+
project.save
|
|
120
|
+
update_digest(project)
|
|
86
121
|
end
|
|
87
122
|
end
|
|
88
123
|
|
|
89
124
|
private
|
|
90
125
|
|
|
91
|
-
# ──
|
|
126
|
+
# ── 并行清理项目空 groups ──
|
|
92
127
|
|
|
128
|
+
# 移除每个项目中为空的 pods、support_files、development_pods、dependencies groups
|
|
129
|
+
#
|
|
130
|
+
# 每个 project 独立,使用 Concurrent::FixedThreadPool 并行执行。
|
|
131
|
+
# 如果 concurrent-ruby 不可用(NameError),回退到串行处理。
|
|
93
132
|
def parallel_cleanup_projects(projects)
|
|
94
133
|
pool_size = compute_pool_size
|
|
95
134
|
Pod::UI.message "- Cleaning up #{projects.size} projects (pool: #{pool_size})"
|
|
96
135
|
|
|
97
|
-
pool =
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
136
|
+
pool = begin
|
|
137
|
+
Concurrent::FixedThreadPool.new(pool_size)
|
|
138
|
+
rescue NameError
|
|
139
|
+
nil # concurrent-ruby 不可用,回退到串行
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
if pool
|
|
143
|
+
projects.each do |project|
|
|
144
|
+
pool.post { cleanup_single_project(project) }
|
|
101
145
|
rescue StandardError => e
|
|
102
146
|
Pod::UI.warn "[cocoapods-podgenerate] Cleanup error: #{e.message}"
|
|
103
147
|
end
|
|
148
|
+
pool.shutdown
|
|
149
|
+
pool.wait_for_termination(Pod::PodGenerate::Parallel::ThreadPool::DEFAULT_TIMEOUT) || pool.kill
|
|
150
|
+
else
|
|
151
|
+
# 串行回退
|
|
152
|
+
projects.each { |p| cleanup_single_project(p) }
|
|
104
153
|
end
|
|
105
|
-
pool.shutdown
|
|
106
|
-
pool.wait_for_termination
|
|
107
|
-
rescue NameError
|
|
108
|
-
cleanup_projects(projects)
|
|
109
154
|
end
|
|
110
155
|
|
|
156
|
+
# 清理单个项目的空 groups
|
|
111
157
|
def cleanup_single_project(project)
|
|
112
158
|
[project.pods, project.support_files_group,
|
|
113
159
|
project.development_pods, project.dependencies_group].each do |group|
|
|
@@ -115,50 +161,62 @@ module Pod
|
|
|
115
161
|
end
|
|
116
162
|
end
|
|
117
163
|
|
|
118
|
-
# ──
|
|
164
|
+
# ── 并行重建用户 scheme ──
|
|
119
165
|
|
|
166
|
+
# 为所有项目重建 scheme 文件,将 test target 附加到 library target
|
|
167
|
+
#
|
|
168
|
+
# results_by_native_target 在所有线程间是只读共享缓存,
|
|
169
|
+
# 每个线程读取同一个 Hash 但从不修改它 → 线程安全。
|
|
120
170
|
def parallel_recreate_user_schemes(projects)
|
|
121
171
|
library_product_types = [:framework, :dynamic_library, :static_library]
|
|
122
172
|
|
|
123
|
-
#
|
|
173
|
+
# 预构建 native target → InstallationResult 的查找缓存(只读、线程安全)
|
|
124
174
|
results_by_native_target = build_native_target_cache
|
|
125
175
|
|
|
126
176
|
pool_size = compute_pool_size
|
|
127
177
|
Pod::UI.message "- Recreating user schemes for #{projects.size} projects (pool: #{pool_size})"
|
|
128
178
|
|
|
129
|
-
pool =
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
end
|
|
179
|
+
pool = begin
|
|
180
|
+
Concurrent::FixedThreadPool.new(pool_size)
|
|
181
|
+
rescue NameError
|
|
182
|
+
nil
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
if pool
|
|
186
|
+
projects.each do |project|
|
|
187
|
+
pool.post do
|
|
188
|
+
recreate_schemes_for_project(project, library_product_types, results_by_native_target)
|
|
140
189
|
end
|
|
141
190
|
rescue StandardError => e
|
|
142
191
|
Pod::UI.warn "[cocoapods-podgenerate] Scheme recreation error: #{e.message}"
|
|
143
192
|
end
|
|
193
|
+
pool.shutdown
|
|
194
|
+
pool.wait_for_termination(Pod::PodGenerate::Parallel::ThreadPool::DEFAULT_TIMEOUT) || pool.kill
|
|
195
|
+
else
|
|
196
|
+
projects.each do |project|
|
|
197
|
+
recreate_schemes_for_project(project, library_product_types, results_by_native_target)
|
|
198
|
+
end
|
|
144
199
|
end
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# 为单个项目重建 scheme
|
|
203
|
+
#
|
|
204
|
+
# @param project [Project] Xcodeproj 项目
|
|
205
|
+
# @param library_product_types [Array<Symbol>] 需要添加 test target 的 library 类型
|
|
206
|
+
# @param results_by_native_target [Hash] native_target => InstallationResult 缓存
|
|
207
|
+
def recreate_schemes_for_project(project, library_product_types, results_by_native_target)
|
|
208
|
+
project.recreate_user_schemes(false) do |scheme, target|
|
|
209
|
+
next unless target.respond_to?(:symbol_type)
|
|
210
|
+
next unless library_product_types.include?(target.symbol_type)
|
|
211
|
+
installation_result = results_by_native_target[target]
|
|
212
|
+
next unless installation_result
|
|
213
|
+
installation_result.test_native_targets.each do |test_native_target|
|
|
214
|
+
scheme.add_test_target(test_native_target)
|
|
158
215
|
end
|
|
159
216
|
end
|
|
160
217
|
end
|
|
161
218
|
|
|
219
|
+
# 构建 native_target → InstallationResult 查找缓存
|
|
162
220
|
def build_native_target_cache
|
|
163
221
|
cache = {}
|
|
164
222
|
@pod_target_installation_results.each do |_, result|
|
|
@@ -167,56 +225,42 @@ module Pod
|
|
|
167
225
|
cache
|
|
168
226
|
end
|
|
169
227
|
|
|
170
|
-
# ──
|
|
228
|
+
# ── SHA256 摘要工具方法 ──
|
|
171
229
|
|
|
230
|
+
# 计算所有项目的初始 SHA256 摘要
|
|
172
231
|
def compute_initial_digests
|
|
173
|
-
@projects.each
|
|
174
|
-
update_digest(project)
|
|
175
|
-
end
|
|
232
|
+
@projects.each { |p| update_digest(p) }
|
|
176
233
|
@projects.each { |p| @sort_needed[p.object_id] = true }
|
|
177
234
|
end
|
|
178
235
|
|
|
179
|
-
|
|
180
|
-
pbx_path = pbxproj_path(project)
|
|
181
|
-
return false unless pbx_path && File.exist?(pbx_path)
|
|
182
|
-
|
|
183
|
-
old_digest = @project_digests[project.object_id]
|
|
184
|
-
return false unless old_digest
|
|
185
|
-
|
|
186
|
-
current_digest = digest_file(pbx_path)
|
|
187
|
-
current_digest == old_digest
|
|
188
|
-
end
|
|
189
|
-
|
|
236
|
+
# 是否需要排序(首次写入总是需要)
|
|
190
237
|
def needs_sort?(project)
|
|
191
238
|
@sort_needed[project.object_id] != false
|
|
192
239
|
end
|
|
193
240
|
|
|
241
|
+
# 更新项目的 SHA256 摘要缓存
|
|
194
242
|
def update_digest(project)
|
|
195
|
-
|
|
196
|
-
return unless
|
|
197
|
-
|
|
198
|
-
@project_digests[project.object_id] = digest_file(pbx_path)
|
|
243
|
+
digest = digest_pbxproj(project)
|
|
244
|
+
return unless digest
|
|
245
|
+
@project_digests[project.object_id] = digest
|
|
199
246
|
@sort_needed[project.object_id] = false
|
|
200
247
|
end
|
|
201
248
|
|
|
202
|
-
|
|
249
|
+
# 计算指定项目的 pbxproj 文件的 SHA256 摘要
|
|
250
|
+
#
|
|
251
|
+
# @param project [Project] Xcodeproj 项目
|
|
252
|
+
# @return [String, nil] SHA256 十六进制字符串,或 nil(文件不存在/不可读)
|
|
253
|
+
def digest_pbxproj(project)
|
|
203
254
|
path = project.path
|
|
204
255
|
return nil unless path
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
path.to_s
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def digest_file(path)
|
|
213
|
-
require 'digest'
|
|
214
|
-
return nil unless File.file?(path)
|
|
215
|
-
Digest::SHA256.file(path).hexdigest
|
|
256
|
+
pbx_path = path.to_s.end_with?('.xcodeproj') ? File.join(path.to_s, 'project.pbxproj') : path.to_s
|
|
257
|
+
return nil unless File.file?(pbx_path)
|
|
258
|
+
Digest::SHA256.file(pbx_path).hexdigest
|
|
216
259
|
rescue StandardError
|
|
217
260
|
nil
|
|
218
261
|
end
|
|
219
262
|
|
|
263
|
+
# 计算线程池大小
|
|
220
264
|
def compute_pool_size
|
|
221
265
|
[[Etc.nprocessors - 1, 2].max, 16].min
|
|
222
266
|
rescue NameError
|
|
@@ -31,30 +31,52 @@ module Pod
|
|
|
31
31
|
|
|
32
32
|
module ParallelIntegration
|
|
33
33
|
# ── Optimization 1: Parallel integrate_user_targets ──
|
|
34
|
+
# 集成用户项目中的所有 Pod target(添加构建阶段、修改配置等)。
|
|
35
|
+
#
|
|
36
|
+
# 【竞态条件修复 H2】
|
|
37
|
+
# 多个 AggregateTarget 可能属于同一个 Xcodeproj::Project 文件
|
|
38
|
+
# (例如同一个 .xcodeproj 中包含多个 native target)。
|
|
39
|
+
# TargetIntegrator#integrate! 会修改项目(添加 build phases、修改
|
|
40
|
+
# configurations),如果两个线程同时修改同一个 Xcodeproj::Project 对象,
|
|
41
|
+
# 就会产生竞态条件,导致 pbxproj 文件损坏。
|
|
42
|
+
#
|
|
43
|
+
# 修复策略:
|
|
44
|
+
# 1. 按 user_project 将所有 target 分组
|
|
45
|
+
# 2. 同一个项目内的 target → 串行集成(避免竞态)
|
|
46
|
+
# 3. 不同项目之间 → 并行集成(利用多核性能)
|
|
34
47
|
def integrate_user_targets
|
|
35
48
|
target_integrators = targets_to_integrate.sort_by(&:name).map do |target|
|
|
36
49
|
Pod::Installer::UserProjectIntegrator::TargetIntegrator.new(target, :use_input_output_paths => use_input_output_paths?)
|
|
37
50
|
end
|
|
38
51
|
|
|
39
|
-
if target_integrators.
|
|
40
|
-
|
|
41
|
-
return
|
|
42
|
-
end
|
|
52
|
+
return if target_integrators.empty?
|
|
53
|
+
return target_integrators.each(&:integrate!) if target_integrators.size <= 1
|
|
43
54
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
# 按 user_project 分组:同一项目的 target 共享同一个 Xcodeproj::Project 对象
|
|
56
|
+
groups = target_integrators.group_by { |ti| ti.send(:target).user_project }
|
|
57
|
+
|
|
58
|
+
if groups.size <= 1
|
|
59
|
+
# 所有 target 都属于同一项目 → 串行执行,避免竞态条件
|
|
60
|
+
target_integrators.each(&:integrate!)
|
|
61
|
+
else
|
|
62
|
+
# 不同项目 → 跨组并行,组内串行(同一项目内只有一个线程在修改)
|
|
63
|
+
Pod::UI.message "- Integrating #{target_integrators.size} targets across #{groups.size} projects in parallel"
|
|
64
|
+
threads = groups.map do |project, integrators|
|
|
65
|
+
Thread.new do
|
|
66
|
+
integrators.each(&:integrate!)
|
|
51
67
|
end
|
|
52
68
|
end
|
|
69
|
+
threads.each(&:join)
|
|
53
70
|
end
|
|
54
|
-
threads.each(&:join)
|
|
55
71
|
end
|
|
56
72
|
|
|
57
73
|
# ── Optimization 2: Parallel save_projects ──
|
|
74
|
+
# 保存修改后的用户项目文件。
|
|
75
|
+
#
|
|
76
|
+
# 使用并行线程保存多个 Xcodeproj 项目以提高性能。
|
|
77
|
+
# 脏项目调用 project.save 写入 pbxproj,非脏项目则 touch pbxproj
|
|
78
|
+
# 以更新文件修改时间(确保增量构建工具能正确检测变更)。
|
|
79
|
+
# FileUtils.touch 操作用 Mutex 保护,因为 touch 不是线程安全的。
|
|
58
80
|
def save_projects(projects)
|
|
59
81
|
projects = projects.uniq
|
|
60
82
|
|
|
@@ -90,7 +112,16 @@ module Pod
|
|
|
90
112
|
end
|
|
91
113
|
|
|
92
114
|
# ── Optimization 3: Parallel warn_about_xcconfig_overrides ──
|
|
93
|
-
#
|
|
115
|
+
# 检查并警告用户项目中 xcconfig 构建设置的覆盖情况。
|
|
116
|
+
#
|
|
117
|
+
# 通过 prepend 覆盖原始方法,在 integrate! 内部被自动调用。
|
|
118
|
+
# 使用 Concurrent::FixedThreadPool 线程池并行检查多个 target,
|
|
119
|
+
# 池大小由 compute_pool_size 计算(CPU 核心数 - 1,范围 2..16)。
|
|
120
|
+
# 如果 NameError(例如 concurrent-ruby 不可用),回退到串行执行。
|
|
121
|
+
#
|
|
122
|
+
# 注意:此方法操作的是 targets_to_integrate(AggregateTarget 数组),
|
|
123
|
+
# 不同 target 属于不同项目(或部分属于同一项目),但由于只是读取
|
|
124
|
+
# xcconfig 设置并打印警告,不修改项目,因此不存在竞态条件。
|
|
94
125
|
def warn_about_xcconfig_overrides
|
|
95
126
|
targets = targets_to_integrate
|
|
96
127
|
return if targets.empty?
|
|
@@ -111,13 +142,32 @@ module Pod
|
|
|
111
142
|
end
|
|
112
143
|
end
|
|
113
144
|
pool.shutdown
|
|
114
|
-
pool.wait_for_termination
|
|
145
|
+
unless pool.wait_for_termination(Pod::PodGenerate::Parallel::ThreadPool::DEFAULT_TIMEOUT)
|
|
146
|
+
Pod::UI.warn '[cocoapods-podgenerate] UserIntegratorPatch: timed out waiting for xcconfig override checks'
|
|
147
|
+
end
|
|
115
148
|
rescue NameError
|
|
116
149
|
targets.each { |t| warn_single_target(t) }
|
|
117
150
|
end
|
|
118
151
|
|
|
119
152
|
private
|
|
120
153
|
|
|
154
|
+
# 对单个 AggregateTarget 检查其所有 user target 的 xcconfig 覆盖情况。
|
|
155
|
+
#
|
|
156
|
+
# 遍历逻辑:
|
|
157
|
+
# 1. 遍历 aggregate_target 的所有 user_target
|
|
158
|
+
# 2. 对每个 user_target 的每个构建配置,取出对应 xcconfig 中的设置
|
|
159
|
+
# 3. 比较 xcconfig 设置与当前构建设置:如果用户已在构建设置中赋值
|
|
160
|
+
# (非 $(inherited)),则打印覆盖警告
|
|
161
|
+
#
|
|
162
|
+
# 忽略 CODE_SIGN_IDENTITY 等特定 key(定义在 IGNORED_KEYS 中),
|
|
163
|
+
# 这些 key 的覆盖是预期行为,不需要警告。
|
|
164
|
+
#
|
|
165
|
+
# 在线程池的某个槽位中运行,通过 rescue 捕获异常避免单 target 错误
|
|
166
|
+
# 影响其他 target 的检查。
|
|
167
|
+
#
|
|
168
|
+
# 注意:print_override_warning 是原始 UserProjectIntegrator 类的
|
|
169
|
+
# private 方法。Ruby 允许从 prepended module 中以隐式 receiver 的
|
|
170
|
+
# 方式调用 private 方法(无需显式 self. 前缀)。
|
|
121
171
|
def warn_single_target(aggregate_target)
|
|
122
172
|
aggregate_target.user_targets.each do |user_target|
|
|
123
173
|
user_target.build_configurations.each do |config|
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# [cocoapods-podgenerate]
|
|
4
|
+
# 插件入口文件 — 加载所有补丁和工具模块,激活优化。
|
|
5
|
+
#
|
|
6
|
+
# 加载顺序(重要—有依赖关系):
|
|
7
|
+
# 1. 各 patch 文件(通过 prepend monkey-patch CocoaPods 内部类)
|
|
8
|
+
# 2. 并行工具模块(线程池、批处理器)
|
|
9
|
+
# 3. 性能分析器
|
|
10
|
+
#
|
|
11
|
+
# 激活方式:
|
|
12
|
+
# - `plugin 'cocoapods-podgenerate'` in Podfile → CocoaPods 加载 cocoapods_plugin.rb
|
|
13
|
+
# → require 此文件 → 如果 Pod::HooksManager 已定义则立即 activate
|
|
14
|
+
# - 如果 Pod::HooksManager 尚未定义(例如加载顺序不同),
|
|
15
|
+
# 使用 TracePoint 延迟激活(带 500 次安全检查防止资源泄漏)
|
|
16
|
+
# - hooks.rb 注册了 :pre_install hook 作为兜底(如果 TracePoint 也错过了)
|
|
17
|
+
|
|
3
18
|
require 'cocoapods-podgenerate/patches/installer_patch'
|
|
4
19
|
require 'cocoapods-podgenerate/patches/project_patch'
|
|
5
20
|
require 'cocoapods-podgenerate/patches/project_writer_patch'
|
|
@@ -13,8 +28,19 @@ require 'cocoapods-podgenerate/benchmark/profiler'
|
|
|
13
28
|
|
|
14
29
|
module Pod
|
|
15
30
|
module PodGenerate
|
|
31
|
+
# 激活所有优化补丁
|
|
32
|
+
#
|
|
33
|
+
# 幂等安全:多次调用只执行一次(@activated 守卫)。
|
|
34
|
+
# 所有补丁通过 Module#prepend 注入,如果重复 prepend 会导致
|
|
35
|
+
# 祖先链中出现重复模块,super 调用链混乱。
|
|
16
36
|
def self.activate
|
|
17
|
-
|
|
37
|
+
return if @activated
|
|
38
|
+
@activated = true
|
|
39
|
+
|
|
40
|
+
# 确保 hooks 被加载(pre_install hook 作为兜底激活路径)
|
|
41
|
+
require_relative 'cocoapods-podgenerate/hooks'
|
|
42
|
+
|
|
43
|
+
# 按依赖顺序注册补丁
|
|
18
44
|
Pod::PodGenerate::Patches::InstallerPatch.apply
|
|
19
45
|
Pod::PodGenerate::Patches::ProjectPatch.apply
|
|
20
46
|
Pod::PodGenerate::Patches::ProjectWriterPatch.apply
|
|
@@ -23,7 +49,7 @@ module Pod
|
|
|
23
49
|
Pod::PodGenerate::Patches::MultiProjectGeneratorPatch.apply
|
|
24
50
|
Pod::PodGenerate::Patches::CacheAnalyzerPatch.apply
|
|
25
51
|
|
|
26
|
-
#
|
|
52
|
+
# 安装性能分析器钩子
|
|
27
53
|
Pod::PodGenerate::Benchmark::Profiler.install
|
|
28
54
|
|
|
29
55
|
Pod::UI.message '[cocoapods-podgenerate] Activated!'
|
|
@@ -31,15 +57,30 @@ module Pod
|
|
|
31
57
|
end
|
|
32
58
|
end
|
|
33
59
|
|
|
34
|
-
#
|
|
60
|
+
# 自动激活机制
|
|
61
|
+
#
|
|
62
|
+
# CocoaPods 的插件加载流程:
|
|
63
|
+
# 1. Podfile 中 `plugin 'cocoapods-podgenerate'` → CLAide 加载 cocoapods_plugin.rb
|
|
64
|
+
# 2. cocoapods_plugin.rb → require 'cocoapods-podgenerate' → 此文件
|
|
65
|
+
# 3. 此时 Pod::HooksManager 通常已由 cocoapods gem 的初始化过程定义
|
|
66
|
+
#
|
|
67
|
+
# 如果 Pod::HooksManager 已定义 → 立即激活
|
|
68
|
+
# 否则 → TracePoint 延迟激活,监听 :class 事件直到 HooksManager 被定义
|
|
35
69
|
if defined?(Pod::HooksManager)
|
|
36
70
|
Pod::PodGenerate.activate
|
|
37
71
|
else
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
# TracePoint 延迟激活(带安全检查)
|
|
73
|
+
# [:class] 事件在 Ruby 每次打开 class/module 时触发
|
|
74
|
+
# 500 次上限:正常情况下 HooksManager 在前 10-20 次就会被发现,
|
|
75
|
+
# 如果超过 500 次说明加载环境异常,及时禁用避免永久性能开销
|
|
76
|
+
count = 0
|
|
77
|
+
tp = TracePoint.trace(:class) do |tp_event|
|
|
78
|
+
count += 1
|
|
79
|
+
if tp_event.self == Pod::HooksManager
|
|
41
80
|
Pod::PodGenerate.activate
|
|
42
|
-
|
|
81
|
+
tp_event.disable
|
|
82
|
+
elsif count > 500
|
|
83
|
+
tp_event.disable # 安全阀:防止资源泄漏
|
|
43
84
|
end
|
|
44
85
|
end
|
|
45
86
|
end
|