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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6d26a277c924f83166f71d24a5c4268faac627ec35aa125b7771ba4f962d035
|
|
4
|
+
data.tar.gz: cf7d5a1740bcce3e2d9b55301ed07ae1a73a33083a1199e0027824eaf7c13e18
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2bf8fcda7f3bd691211959324e96bc61fa74172675ff1c93a518727513299104108689d844059e446510d07bc7b1a52a229032bd7c3e1483502df83ebe15e8ae
|
|
7
|
+
data.tar.gz: 50eae7e57d1883adf0dd706b1a67e31760670e088de0c2d45aeee22491243f44e6e00d8fd8ce592f1fe858e01c8d393bc69b62c98894e28b97efafea05a8683e
|
|
@@ -9,15 +9,17 @@ module Pod
|
|
|
9
9
|
module Benchmark
|
|
10
10
|
module Profiler
|
|
11
11
|
@phase_timings = []
|
|
12
|
+
@timings_mutex = Mutex.new
|
|
12
13
|
|
|
13
14
|
class << self
|
|
14
15
|
def enabled?
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
ENV['POD_GENERATE_DEBUG'] == '1' ||
|
|
17
|
+
ENV['COCOAPODS_PODGENERATE_DEBUG'] == '1' ||
|
|
18
|
+
@enabled_override
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def enable!
|
|
20
|
-
@
|
|
22
|
+
@enabled_override = true
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def install
|
|
@@ -27,7 +29,7 @@ module Pod
|
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
def record_phase(name, duration)
|
|
30
|
-
@phase_timings << [name, duration]
|
|
32
|
+
@timings_mutex.synchronize { @phase_timings << [name, duration] }
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
def report
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# [cocoapods-podgenerate]
|
|
4
|
+
# `pod podgenerate` CLI 命令 — 带优化运行 pod install 的快捷方式。
|
|
5
|
+
#
|
|
6
|
+
# 用法:
|
|
7
|
+
# pod podgenerate # 运行优化的 pod install
|
|
8
|
+
# pod podgenerate --debug # 启用详细性能分析输出
|
|
9
|
+
# pod podgenerate --verbose # 传递 --verbose 给 pod install
|
|
10
|
+
#
|
|
11
|
+
# v0.1.4 修复 (M7):
|
|
12
|
+
# 用户参数(如 --no-repo-update、--verbose)现在会正确传递给
|
|
13
|
+
# 底层的 pod install 命令,不再被静默丢弃。
|
|
14
|
+
|
|
3
15
|
module Pod
|
|
4
16
|
class Command
|
|
5
17
|
class Podgenerate < Command
|
|
@@ -20,19 +32,22 @@ module Pod
|
|
|
20
32
|
|
|
21
33
|
def initialize(argv)
|
|
22
34
|
@debug = argv.flag?('debug', false)
|
|
35
|
+
# v0.1.4: 保存原始参数,稍后传递给 pod install(修复 M7)
|
|
36
|
+
@remaining_argv = argv
|
|
23
37
|
super
|
|
24
38
|
end
|
|
25
39
|
|
|
26
40
|
def run
|
|
27
|
-
Pod::PodGenerate.activate
|
|
28
|
-
|
|
29
41
|
if @debug
|
|
42
|
+
Pod::PodGenerate::Benchmark::Profiler.enable!
|
|
30
43
|
Pod::UI.puts '[cocoapods-podgenerate] Debug mode enabled — verbose profiling output will be shown.'
|
|
31
|
-
ENV['COCOAPODS_PODGENERATE_DEBUG'] = '1'
|
|
32
44
|
end
|
|
33
45
|
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
Pod::PodGenerate.activate
|
|
47
|
+
|
|
48
|
+
# 委托给标准的 pod install 命令执行
|
|
49
|
+
# v0.1.4: 传递用户原参数给 pod install(修复 M7)
|
|
50
|
+
install_command = Pod::Command::Install.new(CLAide::ARGV.new(@remaining_argv.remainder!))
|
|
36
51
|
install_command.run
|
|
37
52
|
end
|
|
38
53
|
end
|
|
@@ -1,48 +1,49 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# [cocoapods-podgenerate]
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# 批处理器 — 将工作项在线程池中并行分配处理。
|
|
5
|
+
#
|
|
6
|
+
# 用于将需要并行化的批量任务分配到 Concurrent::FixedThreadPool,
|
|
7
|
+
# 保持结果的输入顺序(通过索引数组)。
|
|
8
|
+
#
|
|
9
|
+
# v0.1.4 改进:
|
|
10
|
+
# - wait_for_termination 添加超时(120s)防止死锁
|
|
11
|
+
# - 移除未使用的 batch_size 和 completed 变量
|
|
6
12
|
|
|
7
13
|
require 'concurrent'
|
|
14
|
+
require_relative 'thread_pool'
|
|
8
15
|
|
|
9
16
|
module Pod
|
|
10
17
|
module PodGenerate
|
|
11
18
|
module Parallel
|
|
12
19
|
module BatchProcessor
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# @param
|
|
16
|
-
# @param pool [Concurrent::FixedThreadPool]
|
|
17
|
-
# @yield [item]
|
|
18
|
-
# @return [Array]
|
|
19
|
-
def self.process(items, pool:,
|
|
20
|
+
# 在线程池中并行处理项目,保持结果的输入顺序
|
|
21
|
+
#
|
|
22
|
+
# @param items [Array] 要处理的项目列表
|
|
23
|
+
# @param pool [Concurrent::FixedThreadPool] 线程池实例
|
|
24
|
+
# @yield [item] 处理每个项目的代码块
|
|
25
|
+
# @return [Array] 结果列表,顺序与输入相同(nil 表示处理失败的项目)
|
|
26
|
+
def self.process(items, pool:, &block)
|
|
20
27
|
return [] if items.empty?
|
|
21
28
|
|
|
22
29
|
results = Array.new(items.size)
|
|
23
30
|
mutex = Mutex.new
|
|
24
|
-
count = items.size
|
|
25
|
-
completed = 0
|
|
26
31
|
|
|
27
32
|
items.each_with_index do |item, idx|
|
|
28
33
|
pool.post do
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
mutex.synchronize do
|
|
34
|
-
Pod::UI.warn "[cocoapods-podgenerate] BatchProcessor error on item #{idx}: #{e.message}"
|
|
35
|
-
end
|
|
36
|
-
ensure
|
|
37
|
-
mutex.synchronize do
|
|
38
|
-
completed += 1
|
|
39
|
-
end
|
|
40
|
-
end
|
|
34
|
+
result = block.call(item)
|
|
35
|
+
mutex.synchronize { results[idx] = result }
|
|
36
|
+
rescue StandardError => e
|
|
37
|
+
Pod::UI.warn "[cocoapods-podgenerate] BatchProcessor error on item #{idx}: #{e.message}"
|
|
41
38
|
end
|
|
42
39
|
end
|
|
43
40
|
|
|
44
|
-
#
|
|
45
|
-
pool.
|
|
41
|
+
# v0.1.4: 带超时的等待
|
|
42
|
+
pool.shutdown
|
|
43
|
+
unless pool.wait_for_termination(Pod::PodGenerate::Parallel::ThreadPool::DEFAULT_TIMEOUT)
|
|
44
|
+
Pod::UI.warn '[cocoapods-podgenerate] BatchProcessor timed out after 120s'
|
|
45
|
+
pool.kill
|
|
46
|
+
end
|
|
46
47
|
|
|
47
48
|
results
|
|
48
49
|
end
|
|
@@ -1,34 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# [cocoapods-podgenerate]
|
|
4
|
-
#
|
|
5
|
-
|
|
4
|
+
# 线程池工具模块 — 提供跨所有补丁共享的线程池大小计算和超时配置。
|
|
5
|
+
|
|
6
|
+
require 'etc'
|
|
6
7
|
|
|
7
8
|
module Pod
|
|
8
9
|
module PodGenerate
|
|
9
10
|
module Parallel
|
|
10
11
|
module ThreadPool
|
|
12
|
+
# 默认的线程池等待超时(秒)
|
|
13
|
+
DEFAULT_TIMEOUT = 120
|
|
14
|
+
|
|
11
15
|
class << self
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
# 计算适合当前机器的线程池大小
|
|
17
|
+
# 使用 nproc - 1(为主线程留一个核心),最小 2,最大 16
|
|
18
|
+
# @return [Integer] 推荐的线程池大小
|
|
19
|
+
def pool_size
|
|
20
|
+
[[Etc.nprocessors - 1, 2].max, 16].min
|
|
14
21
|
rescue NameError
|
|
15
|
-
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Create and yield a thread pool, then shut it down.
|
|
19
|
-
def with_pool(size: nil, &block)
|
|
20
|
-
pool = create(size: size)
|
|
21
|
-
yield pool
|
|
22
|
-
ensure
|
|
23
|
-
pool&.each(&:kill)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def create(size: nil)
|
|
27
|
-
pool_size = size || default_size
|
|
28
|
-
# Return an array of available threads - caller manages them
|
|
29
|
-
Array.new(pool_size) { Thread.new { sleep } }.each(&:exit)
|
|
30
|
-
# We use a simpler approach - caller creates threads directly
|
|
31
|
-
nil
|
|
22
|
+
4
|
|
32
23
|
end
|
|
33
24
|
end
|
|
34
25
|
end
|
|
@@ -1,16 +1,40 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# [cocoapods-podgenerate]
|
|
4
|
-
# Monkey-patches Analyzer
|
|
4
|
+
# Monkey-patches Analyzer 来缓存依赖解析结果,跳过 Molinillo 算法。
|
|
5
5
|
#
|
|
6
|
-
# Molinillo
|
|
7
|
-
#
|
|
8
|
-
# resolution entirely and use cached results from a previous run.
|
|
6
|
+
# Molinillo 依赖解析的时间复杂度在最坏情况下为 O(n²)(n = pod 数量),
|
|
7
|
+
# 对于 200+ pod 的项目需要 30-120 秒。
|
|
9
8
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
9
|
+
# 优化策略:
|
|
10
|
+
# - 首次运行后,保存解析结果(按 TargetDefinition 分组的 pod 名→版本号映射)
|
|
11
|
+
# 到缓存文件
|
|
12
|
+
# - 缓存键 = SHA256(Podfile.to_yaml + Podfile.lock.to_yaml + CocoaPods 版本号)
|
|
13
|
+
# - 后续运行时,如果缓存命中,从 Manifest 的 specifications 重建完整 Specification
|
|
14
|
+
# 对象列表,完全跳过 Molinillo 解析
|
|
12
15
|
#
|
|
13
|
-
#
|
|
16
|
+
# YAML 序列化可行性说明:
|
|
17
|
+
# - Specification 对象包含大量内部状态(Source、Checksum、Platform 等),
|
|
18
|
+
# 无法直接 YAML 序列化(会触发 Marshal.dump 或递归深度错误)
|
|
19
|
+
# - 解决方案:只保存 pod_name(字符串)和 version(字符串),它们是 Ruby
|
|
20
|
+
# 基本类型,可以安全地序列化为 YAML
|
|
21
|
+
# - 下次加载时,通过 sandbox.manifest.specifications 获取所有已安装的完整
|
|
22
|
+
# Specification 对象,按名称匹配重建结果 Hash
|
|
23
|
+
# - sandbox.manifest 是 Podfile.lock 的内存表示,在 pod install 开始时即加载,
|
|
24
|
+
# 反映上一次成功安装的状态。当 Podfile 和 lockfile 都未改变时,
|
|
25
|
+
# manifest 中的 specification 与 Molinillo 将解析出的结果完全一致
|
|
26
|
+
#
|
|
27
|
+
# 线程安全:
|
|
28
|
+
# - 所有操作在主线程中执行(Analyzer#resolve_dependencies 在 install! 主流程中)
|
|
29
|
+
# - 文件 I/O 使用原子写入(临时文件 + rename)避免写入中断导致缓存损坏
|
|
30
|
+
#
|
|
31
|
+
# 缓存失效条件(任一满足即视为 MISS,触发完整 Molinillo 解析):
|
|
32
|
+
# - Podfile 内容发生任何变化
|
|
33
|
+
# - Podfile.lock 内容发生任何变化
|
|
34
|
+
# - CocoaPods 版本升级
|
|
35
|
+
# - 缓存文件不存在或格式损坏
|
|
36
|
+
#
|
|
37
|
+
# 参考:CocoaPods 源码 — lib/cocoapods/installer/analyzer.rb
|
|
14
38
|
|
|
15
39
|
require 'digest'
|
|
16
40
|
require 'yaml'
|
|
@@ -19,90 +43,240 @@ module Pod
|
|
|
19
43
|
module PodGenerate
|
|
20
44
|
module Patches
|
|
21
45
|
module AnalyzerPatch
|
|
46
|
+
# 缓存文件路径(相对于 Pods 目录)
|
|
47
|
+
# 放在 Pods 目录下,与其他生成物一起管理
|
|
22
48
|
CACHE_FILE = '.cocoapods-resolution-cache.yaml'
|
|
23
49
|
|
|
50
|
+
# 应用补丁入口
|
|
51
|
+
# 将 CachedResolution 模块 prepend 到 Pod::Installer::Analyzer,
|
|
52
|
+
# 使得 resolve_dependencies 方法先执行缓存逻辑
|
|
24
53
|
def self.apply
|
|
25
54
|
Pod::UI.message '[cocoapods-podgenerate] Applying AnalyzerPatch (resolution cache)'
|
|
26
55
|
Pod::Installer::Analyzer.prepend(CachedResolution)
|
|
27
56
|
end
|
|
28
57
|
|
|
58
|
+
# 缓存解析结果的模块
|
|
59
|
+
# 通过 prepend 机制覆盖 Analyzer#resolve_dependencies,
|
|
60
|
+
# 在 Molinillo 执行前后插入缓存读写逻辑
|
|
29
61
|
module CachedResolution
|
|
30
|
-
#
|
|
31
|
-
#
|
|
62
|
+
# 重写 resolve_dependencies,利用缓存跳过 Molinillo 解析
|
|
63
|
+
#
|
|
64
|
+
# 工作流程:
|
|
65
|
+
# 1. compute_resolution_cache_key: 计算当前 Podfile + lockfile 的 SHA256 缓存键
|
|
66
|
+
# 2. load_cached_result: 如果缓存命中且有效,从 Manifest 重建 specs_by_target 并返回
|
|
67
|
+
# 3. super(locked_dependencies): 缓存未命中,调用原始 Molinillo 解析
|
|
68
|
+
# 4. save_cached_result: 保存本次解析结果到 YAML 文件供下次使用
|
|
69
|
+
#
|
|
70
|
+
# @param locked_dependencies [Hash] 锁定的依赖关系,传递给原始解析器
|
|
71
|
+
# @return [Hash{TargetDefinition => Array<Specification>}]
|
|
72
|
+
# 每个 Podfile TargetDefinition 映射到其依赖的 Specification 对象数组
|
|
32
73
|
def resolve_dependencies(locked_dependencies)
|
|
74
|
+
# 步骤 1:计算缓存键(SHA256,基于 Podfile + lockfile 内容)
|
|
33
75
|
cache_key = compute_resolution_cache_key(locked_dependencies)
|
|
34
|
-
cached = load_cached_result(cache_key)
|
|
35
76
|
|
|
77
|
+
# 步骤 2:尝试从缓存加载并重建结果
|
|
78
|
+
cached = load_cached_result(cache_key)
|
|
36
79
|
if cached
|
|
37
|
-
Pod::UI.message '[cocoapods-podgenerate]
|
|
80
|
+
Pod::UI.message '[cocoapods-podgenerate] 解析缓存命中 - 跳过 Molinillo 解析'
|
|
38
81
|
return cached
|
|
39
82
|
end
|
|
40
83
|
|
|
41
|
-
|
|
84
|
+
# 步骤 3:缓存未命中,执行完整的 Molinillo 依赖解析
|
|
85
|
+
Pod::UI.message '[cocoapods-podgenerate] 解析缓存未命中 - 运行 Molinillo 解析'
|
|
42
86
|
result = super(locked_dependencies)
|
|
43
87
|
|
|
88
|
+
# 步骤 4:保存解析结果到缓存(只保存可序列化的名称和版本)
|
|
44
89
|
save_cached_result(cache_key, result)
|
|
45
90
|
result
|
|
46
91
|
end
|
|
47
92
|
|
|
48
93
|
private
|
|
49
94
|
|
|
95
|
+
# 计算缓存键:SHA256(Podfile.to_yaml + Manifest.to_yaml + CocoaPods 版本)
|
|
96
|
+
#
|
|
97
|
+
# 为什么使用 to_yaml 而不是 to_s/to_hash:
|
|
98
|
+
# - to_s 返回的对象字符串表示可能包含内存地址(如 #<Podfile:0x00007f...>),
|
|
99
|
+
# 在跨进程中不稳定,导致缓存永久失效
|
|
100
|
+
# - to_yaml 产出纯文本的、确定性的 YAML 序列化结果,
|
|
101
|
+
# 只要 Podfile 语义不变,YAML 输出就不变
|
|
102
|
+
#
|
|
103
|
+
# 加入 CocoaPods 版本号的原因:
|
|
104
|
+
# - 不同版本的 CocoaPods 可能有不同的解析行为(API 变更、bug 修复)
|
|
105
|
+
# - 版本升级时自动使所有缓存失效,确保使用新版本的解析逻辑
|
|
106
|
+
#
|
|
107
|
+
# @param locked_deps [Hash] 锁定的依赖关系,序列化后加入键中
|
|
108
|
+
# @return [String] 64 位十六进制 SHA256 摘要字符串
|
|
50
109
|
def compute_resolution_cache_key(locked_deps)
|
|
51
|
-
#
|
|
52
|
-
pf_content =
|
|
110
|
+
# Podfile 内容:使用 to_yaml 获得稳定的确定性序列化
|
|
111
|
+
pf_content = ''
|
|
112
|
+
if respond_to?(:podfile) && podfile
|
|
113
|
+
pf_content = podfile.to_yaml
|
|
114
|
+
end
|
|
53
115
|
|
|
54
|
-
#
|
|
55
|
-
|
|
116
|
+
# Podfile.lock 内容(通过 sandbox.manifest 访问)
|
|
117
|
+
# 也使用 to_yaml 确保序列化稳定性
|
|
118
|
+
lockfile_content = ''
|
|
56
119
|
if sandbox && sandbox.manifest
|
|
57
|
-
|
|
120
|
+
lockfile_content = sandbox.manifest.to_yaml
|
|
58
121
|
end
|
|
59
122
|
|
|
60
|
-
|
|
123
|
+
# 锁定的依赖关系,使用 YAML 序列化
|
|
124
|
+
locked_deps_str = locked_deps.to_yaml if locked_deps
|
|
61
125
|
|
|
62
|
-
|
|
126
|
+
# 组合所有输入并计算 SHA256 哈希
|
|
127
|
+
# 使用 '|' 作为分隔符避免不同输入的意外拼接
|
|
128
|
+
raw = [pf_content, lockfile_content, locked_deps_str, Pod::VERSION].join('|')
|
|
63
129
|
Digest::SHA256.hexdigest(raw)
|
|
64
130
|
end
|
|
65
131
|
|
|
132
|
+
# 返回缓存文件的完整绝对路径
|
|
133
|
+
#
|
|
134
|
+
# @return [String] 缓存文件的绝对路径(位于 Pods 目录下)
|
|
66
135
|
def cache_path
|
|
67
136
|
sandbox_root = sandbox.root
|
|
68
|
-
|
|
69
|
-
File.join(cache_dir, CACHE_FILE)
|
|
137
|
+
File.join(sandbox_root.to_s, CACHE_FILE)
|
|
70
138
|
end
|
|
71
139
|
|
|
140
|
+
# 从 YAML 缓存文件加载并重建解析结果
|
|
141
|
+
#
|
|
142
|
+
# 缓存文件 YAML 格式:
|
|
143
|
+
# cache_key: <SHA256 字符串>
|
|
144
|
+
# timestamp: <时间戳,供调试参考>
|
|
145
|
+
# cocoaPods_version: <CocoaPods 版本号>
|
|
146
|
+
# pod_count: <总 pod 数量,供调试参考>
|
|
147
|
+
# targets:
|
|
148
|
+
# Pods-MyApp: # TargetDefinition 的名称
|
|
149
|
+
# - pod_name: A # pod 的根名称
|
|
150
|
+
# version: 1.0.0 # pod 的版本号字符串
|
|
151
|
+
# Pods-MyApp-Tests:
|
|
152
|
+
# - pod_name: B
|
|
153
|
+
# version: 2.0.0
|
|
154
|
+
#
|
|
155
|
+
# 重建策略(关键实现细节):
|
|
156
|
+
# 1. 从 sandbox.manifest.specifications 获取所有已安装的 Specification 对象
|
|
157
|
+
# (Manifest = Podfile.lock 的内存表示,在 pod install 开始时已加载)
|
|
158
|
+
# 2. 按 pod 名称分组建立索引
|
|
159
|
+
# 3. 从 podfile.target_definitions 获取 TargetDefinition 对象
|
|
160
|
+
# 4. 按缓存中记录的目标→pod 关系,组装 Hash{TargetDefinition => [Spec]}
|
|
161
|
+
#
|
|
162
|
+
# 为什么 Manifest 中的 specs 就是正确的:
|
|
163
|
+
# 缓存命中意味着 Podfile 和 Podfile.lock 都未改变,
|
|
164
|
+
# 而 Manifest 反映的是上一次 pod install 成功的状态,
|
|
165
|
+
# 此时 Manifest 中的 specifications 与 Molinillo 将解析出的结果一致
|
|
166
|
+
#
|
|
167
|
+
# @param cache_key [String] 期望的缓存键,与文件中存储的键比较验证有效性
|
|
168
|
+
# @return [Hash, nil] 重建的解析结果(TargetDefinition => [Specification]),
|
|
169
|
+
# 如果缓存无效、过期或损坏则返回 nil
|
|
72
170
|
def load_cached_result(cache_key)
|
|
73
171
|
path = cache_path
|
|
74
172
|
return nil unless File.exist?(path)
|
|
75
173
|
|
|
174
|
+
# 安全地加载 YAML,允许 Symbol 类型的反序列化
|
|
76
175
|
data = YAML.safe_load(File.read(path), permitted_classes: [Symbol])
|
|
77
176
|
return nil unless data.is_a?(Hash)
|
|
177
|
+
|
|
178
|
+
# 验证缓存键匹配 — 如果不匹配说明 Podfile 或 lockfile 已变化
|
|
78
179
|
return nil unless data['cache_key'] == cache_key
|
|
79
180
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
#
|
|
84
|
-
|
|
181
|
+
cached_targets = data['targets']
|
|
182
|
+
return nil unless cached_targets.is_a?(Hash) && !cached_targets.empty?
|
|
183
|
+
|
|
184
|
+
# 步骤 1:从 Manifest 获取所有已安装的 Specification 对象
|
|
185
|
+
# manifest.specifications 返回 Array<Specification>
|
|
186
|
+
manifest_specs = sandbox.manifest.specifications
|
|
187
|
+
|
|
188
|
+
# 步骤 2:按 pod 根名称建立索引
|
|
189
|
+
# 注意:一个 pod 可能有多个 subspec,它们共享同一个 root.name
|
|
190
|
+
# 使用 group_by 将同名 pod 的所有 spec(含 subspec)归为一组
|
|
191
|
+
specs_by_name = {}
|
|
192
|
+
manifest_specs.each do |spec|
|
|
193
|
+
name = spec.root.name
|
|
194
|
+
specs_by_name[name] ||= []
|
|
195
|
+
specs_by_name[name] << spec
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# 步骤 3:从 Podfile 获取 TargetDefinition 对象,按名称建立索引
|
|
199
|
+
# podfile.target_definitions 包含所有抽象 target 和具体 target
|
|
200
|
+
target_defs_by_name = {}
|
|
201
|
+
podfile.target_definitions.each do |td|
|
|
202
|
+
target_defs_by_name[td.name] = td
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# 步骤 4:按缓存记录的目标→pod 关系,重建 {TargetDefinition => [Spec]}
|
|
206
|
+
result = {}
|
|
207
|
+
cached_targets.each do |target_name, pods_data|
|
|
208
|
+
target_def = target_defs_by_name[target_name]
|
|
209
|
+
next unless target_def # 跳过缓存中存在但 Podfile 中已删除的目标
|
|
210
|
+
|
|
211
|
+
specs = []
|
|
212
|
+
pods_data.each do |pod_entry|
|
|
213
|
+
pod_name = pod_entry['pod_name']
|
|
214
|
+
pod_specs = specs_by_name[pod_name]
|
|
215
|
+
specs.concat(pod_specs) if pod_specs
|
|
216
|
+
end
|
|
217
|
+
result[target_def] = specs unless specs.empty?
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
return nil if result.empty?
|
|
221
|
+
result
|
|
85
222
|
rescue StandardError => e
|
|
86
|
-
|
|
223
|
+
# 缓存加载失败不应中断 pod install 主流程
|
|
224
|
+
# 最坏情况:缓存文件损坏 → MISS → 正常 Molinillo 解析
|
|
225
|
+
Pod::UI.warn "[cocoapods-podgenerate] 加载解析缓存失败: #{e.message}"
|
|
87
226
|
nil
|
|
88
227
|
end
|
|
89
228
|
|
|
229
|
+
# 将 Molinillo 解析结果保存到 YAML 缓存文件
|
|
230
|
+
#
|
|
231
|
+
# 保存策略:
|
|
232
|
+
# - 不保存 Specification 对象(无法序列化)
|
|
233
|
+
# - 只保存 pod 名称+版本号字符串
|
|
234
|
+
# - 按 TargetDefinition 名称组织数据结构
|
|
235
|
+
# - 使用原子写入(临时文件 + rename)避免写入中断导致缓存损坏
|
|
236
|
+
#
|
|
237
|
+
# 原子写入步骤:
|
|
238
|
+
# 1. 先写入 .tmp 临时文件
|
|
239
|
+
# 2. 写入完成后 rename 到目标文件
|
|
240
|
+
# 3. rename 是原子操作,避免了进程崩溃时残留损坏的缓存文件
|
|
241
|
+
#
|
|
242
|
+
# @param cache_key [String] 缓存键,写入文件头部供下次 load 验证
|
|
243
|
+
# @param result [Hash{TargetDefinition => Array<Specification>}] Molinillo 解析结果
|
|
90
244
|
def save_cached_result(cache_key, result)
|
|
91
245
|
path = cache_path
|
|
246
|
+
|
|
247
|
+
# 提取纯数据:从 Specification 对象中只取名称和版本号
|
|
248
|
+
# Specification.root.name 返回 pod 的根名称(不含 subspec 后缀)
|
|
249
|
+
# Specification.version.to_s 返回版本号字符串(如 "1.2.3")
|
|
250
|
+
targets_data = {}
|
|
251
|
+
result.each do |target_def, specs|
|
|
252
|
+
pod_list = specs.map do |spec|
|
|
253
|
+
{
|
|
254
|
+
'pod_name' => spec.root.name,
|
|
255
|
+
'version' => spec.version.to_s,
|
|
256
|
+
}
|
|
257
|
+
end
|
|
258
|
+
# 去重:同一个 pod 的多个 subspec 共享相同的 root.name
|
|
259
|
+
pod_list.uniq! { |entry| entry['pod_name'] }
|
|
260
|
+
targets_data[target_def.name] = pod_list unless pod_list.empty?
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# 组装缓存数据
|
|
92
264
|
data = {
|
|
93
265
|
'cache_key' => cache_key,
|
|
94
266
|
'timestamp' => Time.now.to_s,
|
|
95
|
-
'
|
|
267
|
+
'cocoaPods_version' => Pod::VERSION,
|
|
268
|
+
'pod_count' => targets_data.values.flatten.size,
|
|
269
|
+
'targets' => targets_data,
|
|
96
270
|
}
|
|
97
|
-
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# re-run when nothing changed.
|
|
103
|
-
File.write(path, YAML.dump(data))
|
|
271
|
+
|
|
272
|
+
# 原子写入:先写临时文件,成功后再 rename
|
|
273
|
+
tmp_path = "#{path}.tmp"
|
|
274
|
+
File.write(tmp_path, YAML.dump(data))
|
|
275
|
+
File.rename(tmp_path, path)
|
|
104
276
|
rescue StandardError => e
|
|
105
|
-
|
|
277
|
+
# 缓存保存失败不应中断 pod install 主流程
|
|
278
|
+
# 最坏情况:下次运行时缓存 MISS → 正常 Molinillo 解析
|
|
279
|
+
Pod::UI.warn "[cocoapods-podgenerate] 保存解析缓存失败: #{e.message}"
|
|
106
280
|
end
|
|
107
281
|
end
|
|
108
282
|
end
|