cocoapods-meitu-bin 3.0.2 → 3.0.3
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/README.md +268 -29
- data/lib/cocoapods-meitu-bin/command/bin/build_all.rb +46 -16
- data/lib/cocoapods-meitu-bin/config/config.rb +33 -6
- data/lib/cocoapods-meitu-bin/gem_version.rb +1 -1
- data/lib/cocoapods-meitu-bin/helpers/buildAll/bin_helper.rb +13 -1
- data/lib/cocoapods-meitu-bin/helpers/buildAll/podspec_util.rb +30 -13
- data/lib/cocoapods-meitu-bin/helpers/worktree_identity.rb +93 -0
- data/lib/cocoapods-meitu-bin/native/installer.rb +1 -1
- data/lib/cocoapods-meitu-bin/native/lockfile.rb +1 -1
- data/lib/cocoapods-meitu-bin/native/resolver.rb +105 -9
- data/lib/cocoapods-meitu-bin/source_provider_hook.rb +19 -20
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 02cd60b43188e551ce7bcadb27453077e9e23353e1470e2d2f3396e901201c9b
|
|
4
|
+
data.tar.gz: 7431d9db2c1f969a60ef6c65f11e1098d9501a5b33c8c102e89c59cd68d8910c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 91f2b8468a6b747ab6a69e9ed0d5c61c138f73e3e2624c31f169ec85c8be15aecb799353031211b641179e779f8ba2eb41807f465c6f88b3d267ed7e3cbe8cb6
|
|
7
|
+
data.tar.gz: 70f76e6bbfc54db74e5bd310054747407e87e1be33987ccb3ecdd33e0d970cadc1bb841860e4b7d8e1d8d5177e137dc81225ed3630732ebf8405648a241b8fbd
|
data/README.md
CHANGED
|
@@ -2,57 +2,296 @@
|
|
|
2
2
|
|
|
3
3
|
## 简介
|
|
4
4
|
|
|
5
|
-
`cocoapods-meitu-bin
|
|
5
|
+
`cocoapods-meitu-bin` 是 `CocoaPods` 的二进制插件,提供两类核心能力:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
1. 基于壳工程/组件工程制作二进制(`pod bin build-all`)。
|
|
8
|
+
2. 在 `pod install` 期间动态切换源码与二进制(含 monorepo `:path` 组件)。
|
|
9
|
+
|
|
10
|
+
对应 monorepo `:path` 动态切换的关键实现可参考提交:`6ca9994`(`support worktree identity for local path pods`)。
|
|
8
11
|
|
|
9
12
|
## 安装
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
支持两种方式:
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
1. 安装到本机 RubyGems。
|
|
17
|
+
2. 通过项目 `Gemfile` 管理。
|
|
15
18
|
|
|
16
|
-
###
|
|
19
|
+
### 方式 1:安装到本机
|
|
17
20
|
|
|
18
21
|
```shell
|
|
19
|
-
|
|
22
|
+
sudo gem install cocoapods-meitu-bin
|
|
20
23
|
```
|
|
21
|
-
|
|
22
|
-
### 使用Gemfile
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
### 方式 2:Gemfile
|
|
26
|
+
|
|
27
|
+
在 `Gemfile` 中添加:
|
|
25
28
|
|
|
26
29
|
```ruby
|
|
27
30
|
gem 'cocoapods-meitu-bin'
|
|
28
31
|
```
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
然后执行:
|
|
34
|
+
|
|
35
|
+
```shell
|
|
36
|
+
bundle install
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 快速使用
|
|
40
|
+
|
|
41
|
+
### 1) Podfile 开启二进制
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
plugin 'cocoapods-meitu-bin'
|
|
45
|
+
|
|
46
|
+
# true: 开启二进制;false: 全源码
|
|
47
|
+
use_binaries!(ENV['MEITU_USE_BINARIES'] != 'false')
|
|
48
|
+
|
|
49
|
+
# 源码白名单(这些组件强制走源码)
|
|
50
|
+
set_use_source_pods ['WCDBSwift']
|
|
51
|
+
|
|
52
|
+
# 可选:按环境切换二进制配置(Debug / Release / Distribution)
|
|
53
|
+
set_configuration(ENV['MEITU_USE_CONFIGURATION'] || 'Debug')
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2) monorepo 本地 `:path` 接入示例
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
MTXX_MONOREPO_ROOT = File.expand_path('../../..', __dir__)
|
|
60
|
+
MTXX_MONOREPO_POD_PATHS = {
|
|
61
|
+
'MTVIPModule' => 'Modules/MTVIPModule',
|
|
62
|
+
'MTImageEditor' => 'Modules/MTXXImageModule'
|
|
63
|
+
}.freeze
|
|
64
|
+
|
|
65
|
+
def mtxx_monorepo_pod(name, fallback = nil, fallback_options = nil)
|
|
66
|
+
if MTXX_MONOREPO_POD_PATHS.key?(name)
|
|
67
|
+
options = { :path => File.join(MTXX_MONOREPO_ROOT, MTXX_MONOREPO_POD_PATHS[name]) }
|
|
68
|
+
pod name, options
|
|
69
|
+
elsif fallback_options
|
|
70
|
+
pod name, fallback, fallback_options
|
|
71
|
+
elsif fallback
|
|
72
|
+
pod name, fallback
|
|
73
|
+
else
|
|
74
|
+
pod name
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
target 'MTXX' do
|
|
79
|
+
mtxx_monorepo_pod 'MTVIPModule'
|
|
80
|
+
mtxx_monorepo_pod 'MTImageEditor'
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Monorepo 专章:`:path` 组件动态切换(重点)
|
|
85
|
+
|
|
86
|
+
本节专门说明 monorepo 下本地 `:path` 组件如何在一次 `pod install` 中动态决定“走源码”还是“走二进制”。
|
|
87
|
+
|
|
88
|
+
### 目标与结论
|
|
89
|
+
|
|
90
|
+
1. 目标:本地组件有改动就切源码,没改动且二进制可用就切 binary。
|
|
91
|
+
2. 结论:判定依据不是单一 commit,而是 `worktree_identity`(覆盖 `HEAD + staged + unstaged + untracked`)。
|
|
92
|
+
3. 命中 binary:local path pod 从 `Development Pods` 提升为 binary pod。
|
|
93
|
+
4. 未命中 binary:自动回退源码,不阻断依赖分析流程。
|
|
94
|
+
|
|
95
|
+
### 核心实现入口
|
|
96
|
+
|
|
97
|
+
| 文件 | 作用 |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `lib/cocoapods-meitu-bin/helpers/worktree_identity.rb` | 计算 `worktree_identity`,提供规则版本和 git/fallback 双路径。 |
|
|
100
|
+
| `lib/cocoapods-meitu-bin/native/resolver.rb` | 在 `resolve` / `resolver_specs_by_target` 阶段执行 source/binary 决策与替换。 |
|
|
101
|
+
| `lib/cocoapods-meitu-bin/helpers/buildAll/bin_helper.rb` | 生成 binary version,优先使用 `worktree_identity`。 |
|
|
102
|
+
| `lib/cocoapods-meitu-bin/config/config.rb` (`PodUpdateConfig`) | 缓存 identity、checkout options、依赖关系、promote 状态。 |
|
|
103
|
+
| `lib/cocoapods-meitu-bin/source_provider_hook.rb` | `source_provider`/`pre_install` 阶段收集 `:path/:commit` 上下文。 |
|
|
104
|
+
|
|
105
|
+
### `worktree_identity` 生成规则(详细)
|
|
106
|
+
|
|
107
|
+
规则版本:`v1_git_tree_diff_untracked_sha1`
|
|
108
|
+
|
|
109
|
+
计算对象:当前一次依赖分析涉及的本地组件目录(通常是 local podspec 所在目录)。
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
base_tree_sha = git rev-parse HEAD:<relative_path>
|
|
113
|
+
staged_diff_sha = sha1(git diff --cached --binary -- <path>)
|
|
114
|
+
unstaged_sha = sha1(git diff --binary -- <path>)
|
|
115
|
+
untracked_sha = sha1(untracked_file_list + file_contents)
|
|
116
|
+
|
|
117
|
+
worktree_identity = sha1(base_tree_sha + "|" + staged_diff_sha + "|" + unstaged_sha + "|" + untracked_sha)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
字段解释:
|
|
121
|
+
|
|
122
|
+
1. `base_tree_sha`:模块在 `HEAD` 下的快照标识(提交态)。
|
|
123
|
+
2. `staged_diff_sha`:已 `git add` 但未提交的改动。
|
|
124
|
+
3. `unstaged_sha`:工作区未暂存改动。
|
|
125
|
+
4. `untracked_sha`:未跟踪文件列表与内容摘要。
|
|
31
126
|
|
|
32
|
-
|
|
127
|
+
为什么必须包含 `unstaged/untracked`:
|
|
33
128
|
|
|
34
|
-
|
|
129
|
+
1. 只用提交态会误把“本地已改但未提交”组件当成可复用旧 binary。
|
|
130
|
+
2. 新增文件(最常见)如果不纳入 identity,会出现错误命中旧二进制。
|
|
131
|
+
|
|
132
|
+
回退策略:
|
|
133
|
+
|
|
134
|
+
1. 若目录不在 git 仓库内,或 git 查询失败,则回退为“目录相对路径 + 文件内容”摘要。
|
|
135
|
+
2. 回退后仍能保证 identity 随文件变化而变化。
|
|
136
|
+
|
|
137
|
+
### 在依赖分析中的实际执行过程(按时序)
|
|
138
|
+
|
|
139
|
+
1. `:pre_install` / `:source_provider`:
|
|
140
|
+
插件加载并收集 Podfile 中 `:path`、`:commit` 等信息,写入 `PodUpdateConfig`。
|
|
141
|
+
2. `Resolver#resolve` 前半段:
|
|
142
|
+
识别本轮本地 root(`sandbox.development_pods + Podfile external_source[:path]`)。
|
|
143
|
+
3. `Resolver#resolve` 预计算:
|
|
144
|
+
对本地 root 调用 `WorktreeIdentity.for_sandbox_pod`,把 identity 写入 `PodUpdateConfig`。
|
|
145
|
+
4. `Resolver#resolve` 依赖关系:
|
|
146
|
+
构建 `dependency_relationships`,给 binary version 计算提供依赖链输入。
|
|
147
|
+
5. `Resolver#resolver_specs_by_target` 决策:
|
|
148
|
+
对每个 rspec 判定 `use_binary`,并处理 `use_source_pods` / 黑名单 / selector。
|
|
149
|
+
6. binary version 计算:
|
|
150
|
+
`BinHelper.version` 对 local root 优先取 `worktree_identity`,其次 `commit/tag`,最后才是版本号。
|
|
151
|
+
7. 命中 binary:
|
|
152
|
+
用 binary source 的 spec 替换 rspec,并执行 `promote_local_path_pod_to_binary`。
|
|
153
|
+
8. 未命中 binary:
|
|
154
|
+
保留源码 rspec(必要时补 `checkout_source`),自动回退源码流程。
|
|
155
|
+
9. root + subspec 一致性:
|
|
156
|
+
同一 local root 一旦命中 binary,其余 subspec 强制复用同一 binary version,避免 mixed mode。
|
|
157
|
+
|
|
158
|
+
### 状态流转(`PodUpdateConfig`)
|
|
159
|
+
|
|
160
|
+
| 状态 | 含义 | 主要写入点 |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| `worktree_identities` | root -> identity | `resolve` 预计算与按需补算 |
|
|
163
|
+
| `checkout_options` | root -> git/commit/tag | 解析 checkout options 后写入 |
|
|
164
|
+
| `dependency_relationships` | root -> 依赖身份串 | `resolve` 依赖图构建后写入 |
|
|
165
|
+
| `promoted_local_pods` | 已提升为 binary 的本地 root | `promote_local_path_pod_to_binary` |
|
|
166
|
+
| `external_source_binary` | 壳工程制作时可参与处理的 external 组件 | resolver 记录 |
|
|
167
|
+
|
|
168
|
+
### Monorepo 动态切换流程图(详细)
|
|
169
|
+
|
|
170
|
+
```mermaid
|
|
171
|
+
flowchart TD
|
|
172
|
+
A["pod install"] --> B["pre_install/source_provider 收集 :path/:commit"]
|
|
173
|
+
B --> C["resolve: 识别本地 root 集合"]
|
|
174
|
+
C --> D["预计算 worktree_identity 并写入 PodUpdateConfig"]
|
|
175
|
+
D --> E["构建 dependency_relationships"]
|
|
176
|
+
E --> F["resolver_specs_by_target 逐 rspec 判定 use_binary"]
|
|
177
|
+
F --> G{"是否强制源码\\n(use_source_pods / selector / force env)?"}
|
|
178
|
+
G -->|是| H["保留源码 rspec"]
|
|
179
|
+
G -->|否| I["BinHelper.version 计算 binary version\\n优先 identity"]
|
|
180
|
+
I --> J{"binary source 命中?"}
|
|
181
|
+
J -->|命中| K["替换为 binary spec\\npromote local path pod"]
|
|
182
|
+
J -->|未命中| L["回退源码\\n保留 external/local source"]
|
|
183
|
+
H --> M["生成最终 Pods 工程"]
|
|
184
|
+
K --> M
|
|
185
|
+
L --> M
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## 环境变量与开关
|
|
189
|
+
|
|
190
|
+
| 变量 | 默认值 | 作用 |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `MEITU_USE_BINARIES` | `true`(未设时) | Podfile 层总开关。设为 `false` 可全源码。 |
|
|
193
|
+
| `MEITU_USE_CONFIGURATION` | `Debug` | 指定二进制版本计算和命中所用配置(如 `Debug`/`Release`)。 |
|
|
194
|
+
| `MEITU_FORCE_LOCAL_PATH_SOURCE` | `false` | 兼容旧逻辑。设为 `true` 且 `configuration_env == 'dev'` 时,本地 `:path` 组件强制进源码白名单。 |
|
|
195
|
+
| `MEITU_BIN_WORKTREE_DEBUG` | `0` | 设为 `1` 输出 worktree identity 统计和切换调试日志。 |
|
|
196
|
+
| `MEITU_BIN_BUILD_LOCAL_PATH_PODS` | `true` | 控制 `pod bin build-all` 是否制作本地 `:path` 组件。设为 `false` 回退旧行为(跳过本地库)。 |
|
|
197
|
+
|
|
198
|
+
## 二进制制作(build-all)
|
|
199
|
+
|
|
200
|
+
进入 `Podfile` 所在目录执行:
|
|
201
|
+
|
|
202
|
+
```shell
|
|
203
|
+
pod bin build-all --configuration=Debug
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
常见参数:
|
|
35
207
|
|
|
36
208
|
| 选项 | 含义 |
|
|
37
209
|
|---|---|
|
|
38
|
-
| `--clean` |
|
|
39
|
-
| `--clean-single` |
|
|
40
|
-
| `--repo-update` |
|
|
41
|
-
| `--full-build` |
|
|
42
|
-
| `--skip-simulator` |
|
|
43
|
-
| `--configuration=configName` |
|
|
210
|
+
| `--clean` | 全部制作完成后清理临时目录 |
|
|
211
|
+
| `--clean-single` | 每个组件完成后立刻清理临时目录 |
|
|
212
|
+
| `--repo-update` | 先更新 Podfile 里声明的 source 仓库 |
|
|
213
|
+
| `--full-build` | 强制全量制作(不复用已存在 binary) |
|
|
214
|
+
| `--skip-simulator` | 跳过模拟器架构制作 |
|
|
215
|
+
| `--configuration=configName` | 指定构建配置 |
|
|
216
|
+
| `--shell-project` | 基于壳工程产物执行打包 |
|
|
44
217
|
|
|
45
|
-
|
|
218
|
+
### build-all 对不同依赖类型的处理
|
|
46
219
|
|
|
47
|
-
|
|
220
|
+
1. 本地 `:path` 组件:默认参与制作。
|
|
221
|
+
2. external source(如 `:git/:commit/:tag`):
|
|
222
|
+
在普通 `build-all` 流程中,命中 `sandbox.checkout_sources` 即跳过,不参与制作。
|
|
223
|
+
在 `--shell-project` 流程中,只有当组件被标记进 `PodUpdateConfig.external_source_binary` 集合时才会参与制作;未命中集合仍跳过。
|
|
224
|
+
`external_source_binary` 集合由 resolver 阶段记录,目的是给后续壳工程制作场景提供“可制作的 external 组件”输入。
|
|
225
|
+
3. 无源码组件:跳过。
|
|
226
|
+
4. 已存在同版本 binary:跳过。
|
|
48
227
|
|
|
49
|
-
|
|
228
|
+
## 流程图
|
|
50
229
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
230
|
+
### 1) `pod install`:monorepo 本地 path 与二进制动态切换
|
|
231
|
+
|
|
232
|
+
```mermaid
|
|
233
|
+
flowchart TD
|
|
234
|
+
A["pod install"] --> B["source_provider / pre_install"]
|
|
235
|
+
B --> C["resolve 阶段预计算 local path worktree_identity"]
|
|
236
|
+
C --> D["构建 dependency_relationships"]
|
|
237
|
+
D --> E["resolver_specs_by_target 按 rspec 决策 source/binary"]
|
|
238
|
+
E --> F{"是否强制源码\n(MEITU_FORCE_LOCAL_PATH_SOURCE / use_source_pods)?"}
|
|
239
|
+
F -->|是| G["保留源码(Development Pods)"]
|
|
240
|
+
F -->|否| H["计算 binary version\n(identity/commit/tag/依赖关系)"]
|
|
241
|
+
H --> I{"binary spec 是否命中?"}
|
|
242
|
+
I -->|命中| J["promote local path pod -> binary pod\n移出 Development Pods"]
|
|
243
|
+
I -->|未命中| K["回退源码(保留 :path / external source)"]
|
|
244
|
+
G --> L["生成 Pods 工程/Lockfile"]
|
|
245
|
+
J --> L
|
|
246
|
+
K --> L
|
|
58
247
|
```
|
|
248
|
+
|
|
249
|
+
### 2) `pod bin build-all`:二进制制作流程(含本地 `:path`)
|
|
250
|
+
|
|
251
|
+
```mermaid
|
|
252
|
+
flowchart TD
|
|
253
|
+
A["pod bin build-all"] --> B["读取 BinConfig.yaml + CLI 参数"]
|
|
254
|
+
B --> C["Analyzer 分析 pod_targets"]
|
|
255
|
+
C --> D["遍历每个 pod_target"]
|
|
256
|
+
D --> E{"命中黑名单/不在白名单?"}
|
|
257
|
+
E -->|是| D2["跳过"]
|
|
258
|
+
E -->|否| F{"本地 :path 且\nMEITU_BIN_BUILD_LOCAL_PATH_PODS=false?"}
|
|
259
|
+
F -->|是| D2
|
|
260
|
+
F -->|否| G{"external source 或无源码?"}
|
|
261
|
+
G -->|是| D2
|
|
262
|
+
G -->|否| H["计算 binary version"]
|
|
263
|
+
H --> I{"binary 已存在且非 full-build?"}
|
|
264
|
+
I -->|是| D2
|
|
265
|
+
I -->|否| J["编译产物 -> create_binary"]
|
|
266
|
+
J --> K["zip + upload artifact"]
|
|
267
|
+
K --> L["生成并 push binary podspec"]
|
|
268
|
+
L --> M["汇总 Success/Fail 结果"]
|
|
269
|
+
D2 --> M
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## 常用命令
|
|
273
|
+
|
|
274
|
+
```shell
|
|
275
|
+
# 开启调试日志看切换细节
|
|
276
|
+
MEITU_BIN_WORKTREE_DEBUG=1 pod install
|
|
277
|
+
|
|
278
|
+
# 强制本地 :path 组件全源码(应急排障,仅在 Podfile 的 configuration_env == 'dev' 时生效)
|
|
279
|
+
MEITU_FORCE_LOCAL_PATH_SOURCE=true pod install
|
|
280
|
+
|
|
281
|
+
# 本地 :path 组件也参与制作(默认就是 true)
|
|
282
|
+
MEITU_BIN_BUILD_LOCAL_PATH_PODS=true pod bin build-all --configuration=Debug
|
|
283
|
+
|
|
284
|
+
# 回退旧行为:build-all 跳过本地 :path
|
|
285
|
+
MEITU_BIN_BUILD_LOCAL_PATH_PODS=false pod bin build-all --configuration=Debug
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## 排障建议
|
|
289
|
+
|
|
290
|
+
1. 切换不生效:先开 `MEITU_BIN_WORKTREE_DEBUG=1`,看 `resolved/missing` 与 `forced_source` 统计。
|
|
291
|
+
2. 命中旧 binary:检查是否有未跟踪文件变化、`set_use_source_pods` 白名单、`MEITU_FORCE_LOCAL_PATH_SOURCE`。
|
|
292
|
+
3. binary 存在但安装失败:检查 binary spec 与 artifact 是否同时可访问。
|
|
293
|
+
4. 本地库制作被跳过:检查 `MEITU_BIN_BUILD_LOCAL_PATH_PODS` 是否被设为 `false`。
|
|
294
|
+
|
|
295
|
+
## 参考
|
|
296
|
+
|
|
297
|
+
- [美图秀秀 iOS 客户端二进制之路](https://juejin.cn/post/7175023366783385659)
|
|
@@ -49,6 +49,7 @@ module Pod
|
|
|
49
49
|
@configuration = argv.option('configuration', 'Debug')
|
|
50
50
|
@base_dir = ENV['POD_BUILD_BASE_DIR'] || "#{Pathname.pwd}/all_build/Build"
|
|
51
51
|
@version_helper = BinHelper.new
|
|
52
|
+
@subspec_root_payload_white_list = []
|
|
52
53
|
super
|
|
53
54
|
end
|
|
54
55
|
|
|
@@ -117,7 +118,9 @@ module Pod
|
|
|
117
118
|
@post_build = build_config['post_build']
|
|
118
119
|
@black_list = build_config['black_list']
|
|
119
120
|
@write_list = build_config['write_list']
|
|
121
|
+
@subspec_root_payload_white_list = Array(build_config['subspec_root_payload_white_list']).compact
|
|
120
122
|
UI.info "BinConfig:`#{@write_list}`".yellow
|
|
123
|
+
UI.info "BinConfig subspec_root_payload_white_list:`#{@subspec_root_payload_white_list}`".yellow
|
|
121
124
|
end
|
|
122
125
|
end
|
|
123
126
|
|
|
@@ -211,19 +214,18 @@ module Pod
|
|
|
211
214
|
created_pods = []
|
|
212
215
|
pod_targets.map do |pod_target|
|
|
213
216
|
begin
|
|
214
|
-
version = @version_helper.version(pod_target.pod_name, pod_target.root_spec.version.to_s, @analyze_result.specifications, @configuration, podfile.include_dependencies?)
|
|
215
217
|
# 黑名单(不分全量和非全量)
|
|
216
218
|
next if skip_build?(pod_target)
|
|
217
219
|
# 白名单(有白名单,只看白名单,不分全量和非全量)
|
|
218
|
-
UI.info "USE_WHITELIST:`#{ ENV['USE_WHITELIST']}`".yellow
|
|
219
|
-
UI.info "include?:`#{@write_list.include?(pod_target.pod_name)}`".yellow
|
|
220
220
|
next if ENV['USE_WHITELIST'] && !@write_list.nil? && !@write_list.empty? && !@write_list.include?(pod_target.pod_name)
|
|
221
|
-
|
|
222
|
-
if
|
|
221
|
+
local_pod = @sandbox.local?(pod_target.pod_name)
|
|
222
|
+
if local_pod && !build_local_path_pods?
|
|
223
223
|
local_pods << pod_target.pod_name
|
|
224
|
-
show_skip_tip("#{pod_target.pod_name}
|
|
224
|
+
show_skip_tip("#{pod_target.pod_name} 是本地库(MEITU_BIN_BUILD_LOCAL_PATH_PODS=false,跳过制作)")
|
|
225
225
|
next
|
|
226
226
|
end
|
|
227
|
+
# 默认将 :path 组件纳入 build-all,和 resolver 的动态切换保持同一套版本语义。
|
|
228
|
+
show_build_tip("#{pod_target.pod_name} 是本地库,将参与二进制制作") if local_pod
|
|
227
229
|
# 外部源(如 git)
|
|
228
230
|
if @sandbox.checkout_sources[pod_target.pod_name]
|
|
229
231
|
external_pods << pod_target.pod_name
|
|
@@ -236,9 +238,11 @@ module Pod
|
|
|
236
238
|
show_skip_tip("#{pod_target.pod_name} 无需编译")
|
|
237
239
|
next
|
|
238
240
|
end
|
|
241
|
+
version = @version_helper.version(pod_target.pod_name, pod_target.root_spec.version.to_s, @analyze_result.specifications, @configuration, podfile.include_dependencies?)
|
|
239
242
|
# 非全量编译、不在白名单中且已经有相应的二进制版本
|
|
240
243
|
if has_created_binary?(pod_target.pod_name, version)
|
|
241
244
|
created_pods << pod_target.pod_name
|
|
245
|
+
# 版本命中后直接复用已有 binary,避免重复构建上传。
|
|
242
246
|
show_skip_tip("#{pod_target.pod_name}(#{version}) 已经有二进制版本了")
|
|
243
247
|
next
|
|
244
248
|
end
|
|
@@ -257,7 +261,13 @@ module Pod
|
|
|
257
261
|
fail_pods << pod_target.pod_name unless result
|
|
258
262
|
next unless result
|
|
259
263
|
# 生成二进制podspec并上传
|
|
260
|
-
podspec_creator = PodspecUtil.new(
|
|
264
|
+
podspec_creator = PodspecUtil.new(
|
|
265
|
+
pod_target,
|
|
266
|
+
version,
|
|
267
|
+
builder.build_as_framework?,
|
|
268
|
+
@configuration,
|
|
269
|
+
subspec_root_payload_white_list: @subspec_root_payload_white_list
|
|
270
|
+
)
|
|
261
271
|
bin_spec = podspec_creator.create_binary_podspec
|
|
262
272
|
bin_spec_file = podspec_creator.write_binary_podspec(bin_spec)
|
|
263
273
|
result = podspec_creator.push_binary_podspec(bin_spec_file)
|
|
@@ -280,7 +290,8 @@ module Pod
|
|
|
280
290
|
'No Source File' => binary_pods,
|
|
281
291
|
'Created Binary' => created_pods,
|
|
282
292
|
'Black List' => @black_list || [],
|
|
283
|
-
'Write List' => @write_list || []
|
|
293
|
+
'Write List' => @write_list || [],
|
|
294
|
+
'Subspec Root Payload White List' => @subspec_root_payload_white_list || []
|
|
284
295
|
}
|
|
285
296
|
show_results(results)
|
|
286
297
|
results
|
|
@@ -302,24 +313,25 @@ module Pod
|
|
|
302
313
|
# mutex = Mutex.new
|
|
303
314
|
pod_targets.map do |pod_target|
|
|
304
315
|
begin
|
|
305
|
-
version = @version_helper.version(pod_target.pod_name, pod_target.root_spec.version.to_s, @analyze_result.specifications, @configuration, podfile.include_dependencies?)
|
|
306
316
|
# 黑名单(不分全量和非全量)
|
|
307
317
|
next if skip_build?(pod_target)
|
|
308
318
|
# 白名单(有白名单,只看白名单,不分全量和非全量)
|
|
309
319
|
next if ENV['USE_WHITELIST'] && !@write_list.nil? && !@write_list.empty? && !@write_list.include?(pod_target.pod_name)
|
|
310
|
-
|
|
311
|
-
if
|
|
320
|
+
local_pod = @sandbox.local?(pod_target.pod_name)
|
|
321
|
+
if local_pod && !build_local_path_pods?
|
|
312
322
|
local_pods << pod_target.pod_name
|
|
313
|
-
show_skip_tip("#{pod_target.pod_name}
|
|
323
|
+
show_skip_tip("#{pod_target.pod_name} 是本地库(MEITU_BIN_BUILD_LOCAL_PATH_PODS=false,跳过制作)")
|
|
314
324
|
next
|
|
315
325
|
end
|
|
326
|
+
# shell-project 模式下同样遵循本地 :path 组件默认参与制作的策略。
|
|
327
|
+
show_build_tip("#{pod_target.pod_name} 是本地库,将参与二进制制作") if local_pod
|
|
316
328
|
# 外部源(如 git)
|
|
317
329
|
excluded_names = PodUpdateConfig.external_source_binary
|
|
318
|
-
.map { |s| s[:name] }
|
|
330
|
+
.map { |s| s[:name] || s['name'] }
|
|
319
331
|
.compact # 过滤nil值
|
|
320
332
|
.uniq # 去重
|
|
321
333
|
|
|
322
|
-
if
|
|
334
|
+
if excluded_names.include?(pod_target.pod_name) || (@sandbox.checkout_sources[pod_target.pod_name] && !pod_target.should_build?)
|
|
323
335
|
external_pods << pod_target.pod_name
|
|
324
336
|
show_skip_tip("#{pod_target.pod_name} 以external方式引入")
|
|
325
337
|
next
|
|
@@ -330,6 +342,7 @@ module Pod
|
|
|
330
342
|
show_skip_tip("#{pod_target.pod_name} 无需编译")
|
|
331
343
|
next
|
|
332
344
|
end
|
|
345
|
+
version = @version_helper.version(pod_target.pod_name, pod_target.root_spec.version.to_s, @analyze_result.specifications, @configuration, podfile.include_dependencies?)
|
|
333
346
|
# 非全量编译、不在白名单中且已经有相应的二进制版本
|
|
334
347
|
if has_created_binary?(pod_target.pod_name, version)
|
|
335
348
|
created_pods << pod_target.pod_name
|
|
@@ -383,7 +396,13 @@ module Pod
|
|
|
383
396
|
fail_pods << pod_target.pod_name unless result
|
|
384
397
|
next unless result
|
|
385
398
|
# 生成二进制podspec并上传
|
|
386
|
-
podspec_creator = PodspecUtil.new(
|
|
399
|
+
podspec_creator = PodspecUtil.new(
|
|
400
|
+
pod_target,
|
|
401
|
+
version,
|
|
402
|
+
builder.build_as_framework?,
|
|
403
|
+
@configuration,
|
|
404
|
+
subspec_root_payload_white_list: @subspec_root_payload_white_list
|
|
405
|
+
)
|
|
387
406
|
bin_spec = podspec_creator.create_binary_podspec
|
|
388
407
|
bin_spec_file = podspec_creator.write_binary_podspec(bin_spec)
|
|
389
408
|
result = podspec_creator.push_binary_podspec(bin_spec_file)
|
|
@@ -407,7 +426,8 @@ module Pod
|
|
|
407
426
|
'No Source File' => binary_pods,
|
|
408
427
|
'Created Binary' => created_pods,
|
|
409
428
|
'Black List' => @black_list || [],
|
|
410
|
-
'Write List' => @write_list || []
|
|
429
|
+
'Write List' => @write_list || [],
|
|
430
|
+
'Subspec Root Payload White List' => @subspec_root_payload_white_list || []
|
|
411
431
|
}
|
|
412
432
|
|
|
413
433
|
show_results(results)
|
|
@@ -419,11 +439,21 @@ module Pod
|
|
|
419
439
|
UI.info title.yellow
|
|
420
440
|
end
|
|
421
441
|
|
|
442
|
+
def show_build_tip(title)
|
|
443
|
+
UI.info title.green
|
|
444
|
+
end
|
|
445
|
+
|
|
422
446
|
# 是否跳过编译
|
|
423
447
|
def skip_build?(pod_target)
|
|
424
448
|
!@black_list.nil? && !@black_list.empty? && @black_list.include?(pod_target.pod_name)
|
|
425
449
|
end
|
|
426
450
|
|
|
451
|
+
# 本地 :path 组件是否参与 build-all 二进制制作
|
|
452
|
+
# 默认开启;设置 MEITU_BIN_BUILD_LOCAL_PATH_PODS=false 可回退旧行为(本地库跳过)
|
|
453
|
+
def build_local_path_pods?
|
|
454
|
+
ENV['MEITU_BIN_BUILD_LOCAL_PATH_PODS'] != 'false'
|
|
455
|
+
end
|
|
456
|
+
|
|
427
457
|
# 展示结果
|
|
428
458
|
def show_results(results)
|
|
429
459
|
UI.title '打包结果:'.green do
|
|
@@ -178,6 +178,10 @@ class PodUpdateConfig
|
|
|
178
178
|
# 记录 Podfile 中通过 :commit 指定的本地组件及其 git 信息,格式: { pod_name => { git: git_url, commit: commit_hash } }
|
|
179
179
|
# mbox 激活组件,避免关联组件都切换到源码,影响编译速度
|
|
180
180
|
@@pods_with_commit = {}
|
|
181
|
+
# local path 组件在当前一次 resolve 中的身份快照(root_name => worktree_identity)
|
|
182
|
+
@@worktree_identities = {}
|
|
183
|
+
# 已从 Development Pods 提升为 binary 的 local path 组件(用于去重和调试)
|
|
184
|
+
@@promoted_local_pods = {}
|
|
181
185
|
|
|
182
186
|
# 设置依赖关系集合
|
|
183
187
|
def self.set_dependency_relationships(relationships)
|
|
@@ -230,6 +234,33 @@ class PodUpdateConfig
|
|
|
230
234
|
|
|
231
235
|
end
|
|
232
236
|
|
|
237
|
+
def self.set_worktree_identity(pod_name, identity)
|
|
238
|
+
return if identity.nil? || identity.empty?
|
|
239
|
+
|
|
240
|
+
@@worktree_identities[Pod::Specification.root_name(pod_name)] = identity
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def self.get_worktree_identity(pod_name)
|
|
244
|
+
@@worktree_identities[Pod::Specification.root_name(pod_name)]
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def self.worktree_identities
|
|
248
|
+
@@worktree_identities
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def self.promote_local_pod(pod_name, metadata = {})
|
|
252
|
+
# metadata 默认包含命中的 binary version 与 identity,便于后续日志/排障追踪。
|
|
253
|
+
@@promoted_local_pods[Pod::Specification.root_name(pod_name)] = metadata
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def self.promoted_local_pod?(pod_name)
|
|
257
|
+
@@promoted_local_pods.key?(Pod::Specification.root_name(pod_name))
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def self.promoted_local_pods
|
|
261
|
+
@@promoted_local_pods
|
|
262
|
+
end
|
|
263
|
+
|
|
233
264
|
def self.set_external_source_binary(dic)
|
|
234
265
|
@@external_source_binary << dic
|
|
235
266
|
end
|
|
@@ -400,16 +431,12 @@ class PodUpdateConfig
|
|
|
400
431
|
@@pods = []
|
|
401
432
|
@@lockfile = nil
|
|
402
433
|
@@is_clear = true
|
|
434
|
+
@@worktree_identities = {}
|
|
435
|
+
@@promoted_local_pods = {}
|
|
403
436
|
end
|
|
404
437
|
def self.is_clear
|
|
405
438
|
@@is_clear
|
|
406
439
|
end
|
|
407
|
-
|
|
408
|
-
def self.clear
|
|
409
|
-
@@pods = []
|
|
410
|
-
@@lockfile = nil
|
|
411
|
-
@@is_clear = true
|
|
412
|
-
end
|
|
413
440
|
def self.is_podfile_lock_nil
|
|
414
441
|
@@is_podfile_lock_nil
|
|
415
442
|
end
|
|
@@ -21,7 +21,12 @@ module CBin
|
|
|
21
21
|
# 将当前 pod 自身的 commit/tag 加入版本号计算字符串
|
|
22
22
|
# 修复:Podfile 中通过 :commit/:tag 指定的 pod,其自身 commit 信息必须参与 MD5 计算
|
|
23
23
|
# 否则更新 Podfile 的 commit 后版本号不变,会错误命中旧的二进制缓存
|
|
24
|
-
self_checkout = PodUpdateConfig.
|
|
24
|
+
self_checkout = PodUpdateConfig.get_worktree_identity(pod_name)
|
|
25
|
+
if ENV['MEITU_BIN_WORKTREE_DEBUG'] == '1' && self_checkout
|
|
26
|
+
UI.puts "#{pod_name} worktree_identity:#{self_checkout}".yellow
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
self_checkout ||= PodUpdateConfig.get_checkout_option(pod_name)
|
|
25
30
|
self_checkout ||= PodUpdateConfig.get_with_commit(pod_name)
|
|
26
31
|
specs << "#{pod_name}(#{self_checkout})" if self_checkout
|
|
27
32
|
|
|
@@ -67,6 +72,13 @@ module CBin
|
|
|
67
72
|
return '' if all_dependencies.nil? || all_dependencies.empty?
|
|
68
73
|
result = []
|
|
69
74
|
all_dependencies.each do |pod_name|
|
|
75
|
+
worktree_identity = PodUpdateConfig.get_worktree_identity(pod_name)
|
|
76
|
+
if worktree_identity
|
|
77
|
+
# local :path 组件优先采用 worktree identity,避免仅靠 commit/tag 导致命中旧缓存。
|
|
78
|
+
result << "#{pod_name}(#{worktree_identity})"
|
|
79
|
+
next
|
|
80
|
+
end
|
|
81
|
+
|
|
70
82
|
# 优先使用 get_checkout_option 中的 commit 或 tag
|
|
71
83
|
checkout_option = PodUpdateConfig.get_checkout_option(pod_name)
|
|
72
84
|
if checkout_option
|
|
@@ -4,11 +4,12 @@ module CBin
|
|
|
4
4
|
class PodspecUtil
|
|
5
5
|
include Pod
|
|
6
6
|
|
|
7
|
-
def initialize(pod_target, version, build_as_framework = false, configuration = 'Debug')
|
|
7
|
+
def initialize(pod_target, version, build_as_framework = false, configuration = 'Debug', options = {})
|
|
8
8
|
@pod_target = pod_target
|
|
9
9
|
@version = version
|
|
10
10
|
@build_as_framework = build_as_framework
|
|
11
11
|
@configuration = configuration
|
|
12
|
+
@subspec_root_payload_white_list = Array(options[:subspec_root_payload_white_list]).compact
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
# 创建二进制podspec
|
|
@@ -93,9 +94,9 @@ module CBin
|
|
|
93
94
|
private
|
|
94
95
|
|
|
95
96
|
# 删除无用的字段
|
|
96
|
-
def delete_unused(spec)
|
|
97
|
+
def delete_unused(spec, keep_resource_bundles: false)
|
|
97
98
|
spec.delete('project_header_files')
|
|
98
|
-
spec.delete('resource_bundles')
|
|
99
|
+
spec.delete('resource_bundles') unless keep_resource_bundles
|
|
99
100
|
spec.delete('exclude_files')
|
|
100
101
|
spec.delete('preserve_paths')
|
|
101
102
|
spec.delete('prepare_command')
|
|
@@ -103,35 +104,51 @@ module CBin
|
|
|
103
104
|
|
|
104
105
|
# 处理subspecs
|
|
105
106
|
def handle_subspecs(spec)
|
|
107
|
+
root_payload_only = subspec_root_payload_only?
|
|
108
|
+
if root_payload_only
|
|
109
|
+
UI.info "PodspecUtil:`#{@pod_target.pod_name}` 命中 subspec_root_payload_white_list,subspec 不复制 vendored/resources".yellow
|
|
110
|
+
end
|
|
106
111
|
spec['subspecs'].map do |subspec|
|
|
107
112
|
# 处理单个subspec
|
|
108
|
-
handle_single_subspec(subspec, spec)
|
|
113
|
+
handle_single_subspec(subspec, spec, root_payload_only)
|
|
109
114
|
# 递归处理subspec
|
|
110
|
-
recursive_handle_subspecs(subspec['subspecs'], spec)
|
|
115
|
+
recursive_handle_subspecs(subspec['subspecs'], spec, root_payload_only)
|
|
111
116
|
end if spec && spec['subspecs']
|
|
112
117
|
end
|
|
113
118
|
|
|
114
119
|
# 递归处理subspecs
|
|
115
|
-
def recursive_handle_subspecs(subspecs, spec)
|
|
120
|
+
def recursive_handle_subspecs(subspecs, spec, root_payload_only)
|
|
116
121
|
subspecs.map do |s|
|
|
117
122
|
# 处理单个subspec
|
|
118
|
-
handle_single_subspec(s, spec)
|
|
123
|
+
handle_single_subspec(s, spec, root_payload_only)
|
|
119
124
|
# 递归处理
|
|
120
|
-
recursive_handle_subspecs(s['subspecs'], spec)
|
|
125
|
+
recursive_handle_subspecs(s['subspecs'], spec, root_payload_only)
|
|
121
126
|
end if subspecs
|
|
122
127
|
end
|
|
123
128
|
|
|
124
129
|
# 处理单个subspec
|
|
125
|
-
def handle_single_subspec(subspec, spec)
|
|
130
|
+
def handle_single_subspec(subspec, spec, root_payload_only)
|
|
126
131
|
subspec['source_files'] = spec['source_files']
|
|
127
132
|
subspec['public_header_files'] = spec['public_header_files']
|
|
128
133
|
subspec['private_header_files'] = spec['private_header_files']
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
134
|
+
if root_payload_only
|
|
135
|
+
# 仅 root 声明二进制 payload,避免 subspec 重复嵌入同名 framework
|
|
136
|
+
subspec.delete('vendored_frameworks')
|
|
137
|
+
subspec.delete('vendored_libraries')
|
|
138
|
+
subspec.delete('resources')
|
|
139
|
+
subspec.delete('resource_bundles')
|
|
140
|
+
else
|
|
141
|
+
subspec['vendored_frameworks'] = spec['vendored_frameworks']
|
|
142
|
+
subspec['vendored_libraries'] = spec['vendored_libraries']
|
|
143
|
+
subspec['resources'] = spec['resources']
|
|
144
|
+
end
|
|
132
145
|
# 删除无用字段
|
|
133
146
|
subspec.delete('source')
|
|
134
|
-
delete_unused(subspec)
|
|
147
|
+
delete_unused(subspec, keep_resource_bundles: !root_payload_only)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def subspec_root_payload_only?
|
|
151
|
+
@subspec_root_payload_white_list.include?(@pod_target.pod_name)
|
|
135
152
|
end
|
|
136
153
|
|
|
137
154
|
def source
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
require 'digest'
|
|
2
|
+
require 'find'
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
|
|
6
|
+
module CBin
|
|
7
|
+
module Helpers
|
|
8
|
+
module WorktreeIdentity
|
|
9
|
+
module_function
|
|
10
|
+
# 规则版本会进入调试日志,便于在不同实现版本间快速对齐排障。
|
|
11
|
+
RULE_VERSION = 'v1_git_tree_diff_untracked_sha1'.freeze
|
|
12
|
+
|
|
13
|
+
def rule_version
|
|
14
|
+
RULE_VERSION
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def for_sandbox_pod(sandbox, pod_name)
|
|
18
|
+
podspec_path = sandbox.local_podspec(pod_name)
|
|
19
|
+
return nil unless podspec_path
|
|
20
|
+
|
|
21
|
+
# local podspec 所在目录即该组件在 monorepo 中的实际模块目录。
|
|
22
|
+
compute(Pathname.new(podspec_path).dirname)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def compute(module_dir)
|
|
26
|
+
module_dir = Pathname.new(module_dir).expand_path
|
|
27
|
+
git_root = git_output(module_dir.to_s, %w[rev-parse --show-toplevel])
|
|
28
|
+
return fallback_directory_digest(module_dir) if git_root.nil? || git_root.empty?
|
|
29
|
+
|
|
30
|
+
git_root_path = Pathname.new(git_root)
|
|
31
|
+
relative_path = module_dir.relative_path_from(git_root_path).to_s
|
|
32
|
+
# 四段输入覆盖 HEAD/index/worktree/untracked:
|
|
33
|
+
# 1) base_tree: HEAD 下模块快照
|
|
34
|
+
# 2) staged_diff: 已暂存改动
|
|
35
|
+
# 3) unstaged_diff: 未暂存改动
|
|
36
|
+
# 4) untracked_digest: 未跟踪文件列表 + 内容
|
|
37
|
+
base_tree = git_output(git_root, ['rev-parse', "HEAD:#{relative_path}"], allow_failure: true).to_s
|
|
38
|
+
staged_diff = git_output(git_root, ['diff', '--cached', '--binary', '--no-ext-diff', '--', relative_path], allow_failure: true).to_s
|
|
39
|
+
unstaged_diff = git_output(git_root, ['diff', '--binary', '--no-ext-diff', '--', relative_path], allow_failure: true).to_s
|
|
40
|
+
untracked_lines = git_output(git_root, ['ls-files', '--others', '--exclude-standard', '--', relative_path], allow_failure: true).to_s
|
|
41
|
+
|
|
42
|
+
untracked_digest = Digest::SHA1.new
|
|
43
|
+
untracked_lines.lines.map(&:chomp).reject(&:empty?).sort.each do |entry|
|
|
44
|
+
abs_path = git_root_path.join(entry)
|
|
45
|
+
untracked_digest.update(entry)
|
|
46
|
+
untracked_digest.update("\0")
|
|
47
|
+
untracked_digest.file(abs_path.to_s) if abs_path.file?
|
|
48
|
+
untracked_digest.update("\0")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
payload = [
|
|
52
|
+
base_tree,
|
|
53
|
+
Digest::SHA1.hexdigest(staged_diff),
|
|
54
|
+
Digest::SHA1.hexdigest(unstaged_diff),
|
|
55
|
+
untracked_digest.hexdigest,
|
|
56
|
+
].join('|')
|
|
57
|
+
|
|
58
|
+
Digest::SHA1.hexdigest(payload)
|
|
59
|
+
rescue ArgumentError, Errno::ENOENT
|
|
60
|
+
# Git 查询异常时回退到全目录摘要,确保仍有稳定 identity 可用于版本计算。
|
|
61
|
+
fallback_directory_digest(module_dir)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def git_output(chdir, args, allow_failure: false)
|
|
65
|
+
stdout, status = Open3.capture2('git', *args, chdir: chdir)
|
|
66
|
+
return stdout.strip if status.success?
|
|
67
|
+
# allow_failure 为 true 的场景用于容忍“模块首次接入/路径不存在于 HEAD”等可预期情况。
|
|
68
|
+
return nil if allow_failure
|
|
69
|
+
|
|
70
|
+
nil
|
|
71
|
+
rescue Errno::ENOENT
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def fallback_directory_digest(module_dir)
|
|
76
|
+
digest = Digest::SHA1.new
|
|
77
|
+
|
|
78
|
+
# 非 git 场景:按相对路径 + 文件内容做摘要,保证目录态变化可感知。
|
|
79
|
+
Find.find(module_dir.to_s) do |path|
|
|
80
|
+
next if File.directory?(path)
|
|
81
|
+
|
|
82
|
+
rel_path = Pathname.new(path).relative_path_from(module_dir).to_s
|
|
83
|
+
digest.update(rel_path)
|
|
84
|
+
digest.update("\0")
|
|
85
|
+
digest.file(path)
|
|
86
|
+
digest.update("\0")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
digest.hexdigest
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -6,6 +6,7 @@ require 'cocoapods-meitu-bin/native/installation_options'
|
|
|
6
6
|
require 'cocoapods-meitu-bin/gem_version'
|
|
7
7
|
# require 'cocoapods-meitu-bin/command/bin/archive'
|
|
8
8
|
require 'cocoapods-meitu-bin/helpers/buildAll/bin_helper'
|
|
9
|
+
require 'cocoapods-meitu-bin/helpers/worktree_identity'
|
|
9
10
|
require 'cocoapods-meitu-bin/config/config'
|
|
10
11
|
|
|
11
12
|
module Pod
|
|
@@ -150,16 +151,24 @@ module Pod
|
|
|
150
151
|
|
|
151
152
|
|
|
152
153
|
if podfile.use_binaries?
|
|
154
|
+
# 预计算 local path 组件 identity,避免在 rspec 循环里重复扫描目录。
|
|
153
155
|
path_pods = @podfile_dependency_cache.podfile_dependencies
|
|
154
156
|
.select { |dep| dep.external_source && dep.external_source[:path] }
|
|
155
157
|
.map(&:root_name)
|
|
156
158
|
.uniq
|
|
159
|
+
local_pods = (sandbox.development_pods.keys + path_pods)
|
|
160
|
+
.map { |name| Pod::Specification.root_name(name) }
|
|
161
|
+
.uniq
|
|
157
162
|
path_commit_pods = {}
|
|
158
|
-
|
|
163
|
+
local_pods.each do |pod_name|
|
|
164
|
+
worktree_identity = CBin::Helpers::WorktreeIdentity.for_sandbox_pod(sandbox, pod_name)
|
|
165
|
+
PodUpdateConfig.set_worktree_identity(pod_name, worktree_identity) if worktree_identity
|
|
159
166
|
if PodUpdateConfig.pods_with_commit[pod_name]
|
|
167
|
+
# :path + :commit 的组件继续保留 commit 输入,避免 identity 缺失时版本退化。
|
|
160
168
|
path_commit_pods[pod_name] = PodUpdateConfig.pods_with_commit[pod_name]
|
|
161
169
|
end
|
|
162
170
|
end
|
|
171
|
+
print_worktree_identity_debug(local_pods, 'after_precompute')
|
|
163
172
|
PodUpdateConfig.parse_checkout_options_from_specs(checkout_options,podfile,path_commit_pods)
|
|
164
173
|
end
|
|
165
174
|
|
|
@@ -238,8 +247,12 @@ module Pod
|
|
|
238
247
|
# end
|
|
239
248
|
else
|
|
240
249
|
name_version = "#{pod_name}_#{dep_info[:version].version}"
|
|
250
|
+
worktree_identity = PodUpdateConfig.get_worktree_identity(pod_name)
|
|
241
251
|
opts = path_commit_pods&.[](pod_name)
|
|
242
|
-
if
|
|
252
|
+
if worktree_identity
|
|
253
|
+
name_worktree = "#{pod_name}_#{worktree_identity}"
|
|
254
|
+
pod_names << name_worktree unless pod_names.join("|").include?(pod_name)
|
|
255
|
+
elsif opts && opts[:commit]
|
|
243
256
|
commit = opts[:commit]
|
|
244
257
|
name_commit = "#{pod_name}_#{commit}"
|
|
245
258
|
pod_names << name_commit unless pod_names.join("|").include?(pod_name)
|
|
@@ -278,14 +291,29 @@ module Pod
|
|
|
278
291
|
|
|
279
292
|
sources_manager = Config.instance.sources_manager
|
|
280
293
|
use_source_pods = podfile.use_source_pods
|
|
294
|
+
use_source_roots = use_source_pods.map { |name| Pod::Specification.root_name(name) }.uniq
|
|
281
295
|
|
|
282
296
|
# 从BinConfig读取black_list
|
|
283
297
|
black_list = read_black_list
|
|
284
298
|
use_source_pods.concat(black_list).uniq! unless black_list.nil?
|
|
299
|
+
use_source_roots = use_source_pods.map { |name| Pod::Specification.root_name(name) }.uniq
|
|
300
|
+
if podfile.use_binaries? && ENV['MEITU_BIN_WORKTREE_DEBUG'] == '1'
|
|
301
|
+
local_path_roots = sandbox.development_pods.keys.map { |name| Pod::Specification.root_name(name) }.uniq
|
|
302
|
+
forced_source_roots = local_path_roots & use_source_roots
|
|
303
|
+
candidate_binary_roots = local_path_roots - forced_source_roots
|
|
304
|
+
UI.puts "worktree_identity_debug(use_source): local_path=#{local_path_roots.size}, forced_source=#{forced_source_roots.size}, binary_candidates=#{candidate_binary_roots.size}".yellow
|
|
305
|
+
unless forced_source_roots.empty?
|
|
306
|
+
UI.puts "worktree_identity_debug(forced_source_local): #{forced_source_roots.sort.join(',')}".yellow
|
|
307
|
+
end
|
|
308
|
+
end
|
|
285
309
|
|
|
286
310
|
specifications = specs_by_target.values.flatten.map(&:spec).uniq
|
|
287
311
|
|
|
288
312
|
missing_binary_specs = []
|
|
313
|
+
# 注意这里取快照:后续 promote_local_path_pod_to_binary 会改 sandbox.local? 状态。
|
|
314
|
+
local_path_roots_snapshot = sandbox.development_pods.keys.map { |name| Pod::Specification.root_name(name) }.uniq
|
|
315
|
+
binary_version_by_local_root = {}
|
|
316
|
+
local_root_binary_state = {}
|
|
289
317
|
specs_by_target.each do |target, rspecs|
|
|
290
318
|
# use_binaries 并且 use_source_pods 不包含 本地可过滤
|
|
291
319
|
use_binary_rspecs = if podfile.use_binaries? || podfile.use_binaries_selector
|
|
@@ -305,30 +333,52 @@ module Pod
|
|
|
305
333
|
|
|
306
334
|
# developments 组件采用默认输入的 spec (development pods 的 source 为 nil)
|
|
307
335
|
# 可以使 :podspec => "htts://IMYFoundation.podspec"可以走下去,by slj
|
|
308
|
-
|
|
336
|
+
root_name = Pod::Specification.root_name(rspec.root.name)
|
|
337
|
+
dependency_source = PodUpdateConfig.get_checkout_option(root_name)
|
|
338
|
+
# 注意:本轮中会调用 promote_local_path_pod_to_binary 修改 sandbox.local? 状态,
|
|
339
|
+
# 这里使用快照保证同一 root 的 root+subspec 判定一致,避免 mixed mode。
|
|
340
|
+
is_local_path_pod = local_path_roots_snapshot.include?(root_name)
|
|
341
|
+
local_root_state = if is_local_path_pod
|
|
342
|
+
local_root_binary_state[root_name] ||= { all_binary_hits: true, spec_version: nil }
|
|
343
|
+
end
|
|
344
|
+
if is_local_path_pod && PodUpdateConfig.get_worktree_identity(rspec.root.name).nil?
|
|
345
|
+
worktree_identity = CBin::Helpers::WorktreeIdentity.for_sandbox_pod(sandbox, rspec.root.name)
|
|
346
|
+
PodUpdateConfig.set_worktree_identity(rspec.root.name, worktree_identity) if worktree_identity
|
|
347
|
+
end
|
|
309
348
|
|
|
310
349
|
# 关键修复:本地 path 依赖的组件(sandbox.local? 为 true)
|
|
311
350
|
# 应该直接使用原始 rspec,不应走 spec repo 查找和替换逻辑
|
|
312
351
|
# 否则如果 spec repo 中的旧版 podspec 缺少某些 subspecs,
|
|
313
352
|
# subspec_by_name 会返回 nil,导致该 subspec 丢失
|
|
314
|
-
if
|
|
353
|
+
if is_local_path_pod && !use_binary_rspecs.include?(rspec)
|
|
354
|
+
local_root_state[:all_binary_hits] = false if local_root_state
|
|
315
355
|
next rspec
|
|
316
356
|
end
|
|
317
357
|
|
|
318
358
|
unless rspec.spec.respond_to?(:spec_source) && rspec.spec.spec_source
|
|
319
359
|
|
|
320
|
-
if dependency_source.nil?
|
|
360
|
+
if dependency_source.nil? && !is_local_path_pod
|
|
321
361
|
next rspec
|
|
322
362
|
end
|
|
323
363
|
end
|
|
324
364
|
|
|
325
365
|
# 采用二进制依赖并且不为开发组件
|
|
326
366
|
use_binary = use_binary_rspecs.include?(rspec)
|
|
367
|
+
if !use_binary && binary_version_by_local_root[root_name]
|
|
368
|
+
# 同一 local root 已命中 binary 时,其余 subspec 也强制复用同一 binary version
|
|
369
|
+
# 避免 root 与 subspec 混合 source/binary 造成 Swift module/header 不一致。
|
|
370
|
+
use_binary = true
|
|
371
|
+
end
|
|
327
372
|
|
|
328
373
|
if use_binary
|
|
329
374
|
source = sources_manager.binary_source
|
|
330
|
-
|
|
331
|
-
spec_version
|
|
375
|
+
spec_version = binary_version_by_local_root[root_name]
|
|
376
|
+
if spec_version.nil?
|
|
377
|
+
configuration = ENV['configuration'] || podfile.configuration
|
|
378
|
+
spec_version = version_helper.version(rspec.root.name, rspec.spec.version, specifications, configuration, podfile.include_dependencies?)
|
|
379
|
+
binary_version_by_local_root[root_name] = spec_version if is_local_path_pod
|
|
380
|
+
end
|
|
381
|
+
local_root_state[:spec_version] ||= spec_version if local_root_state
|
|
332
382
|
else
|
|
333
383
|
# 关键修复:对于通过 commit/tag 指定的组件,在切换到源码时
|
|
334
384
|
# 应该保持原始 rspec,让 CocoaPods 走正常的 external source 下载流程
|
|
@@ -368,6 +418,10 @@ module Pod
|
|
|
368
418
|
|
|
369
419
|
# 这里可能出现分析依赖的 source 和切换后的 source 对应 specification 的 subspec 对应不上
|
|
370
420
|
# 造成 subspec_by_name 返回 nil,这个是正常现象
|
|
421
|
+
if specification.nil? && is_local_path_pod && use_binary && local_root_state
|
|
422
|
+
local_root_state[:all_binary_hits] = false
|
|
423
|
+
binary_version_by_local_root.delete(root_name)
|
|
424
|
+
end
|
|
371
425
|
next unless specification
|
|
372
426
|
is_exist_binary_version = true
|
|
373
427
|
used_by_only = if Pod.match_version?('~> 1.7')
|
|
@@ -386,6 +440,9 @@ module Pod
|
|
|
386
440
|
# 没有从新的 source 找到对应版本组件
|
|
387
441
|
missing_binary_specs << rspec.spec if use_binary
|
|
388
442
|
is_exist_binary_version = false
|
|
443
|
+
# 同一 local root 若某个 subspec 未命中,需要回退该 root 的 binary 版本缓存。
|
|
444
|
+
binary_version_by_local_root.delete(root_name) if is_local_path_pod
|
|
445
|
+
local_root_state[:all_binary_hits] = false if is_local_path_pod && use_binary && local_root_state
|
|
389
446
|
|
|
390
447
|
# 关键修复:对于有 commit/tag 指定的组件,二进制不存在时:
|
|
391
448
|
# 1. 将完整的 checkout_options 添加到 sandbox.checkout_sources
|
|
@@ -420,18 +477,29 @@ module Pod
|
|
|
420
477
|
end
|
|
421
478
|
# 有对应二进制组件版本才进行替换记录,否则走之前pre-downloading 逻辑
|
|
422
479
|
if is_exist_binary_version && sandbox.predownloaded?(rspec.root.name) && spec_version.is_a?(String) && spec_version.include?("bin")
|
|
423
|
-
PodUpdateConfig.set_external_source({
|
|
480
|
+
PodUpdateConfig.set_external_source({ name: rspec.root.name, source: dependency_source })
|
|
424
481
|
end
|
|
425
482
|
if $ARGV.include?("bin") || !is_exist_binary_version
|
|
426
483
|
# 记录为制作的 external_source 组件,为后续二进制制作准备
|
|
427
484
|
if dependency_source && spec_version.is_a?(String) && spec_version.include?("bin")
|
|
428
|
-
PodUpdateConfig.set_external_source_binary({
|
|
485
|
+
PodUpdateConfig.set_external_source_binary({ name: rspec.root.name, source: dependency_source })
|
|
429
486
|
end
|
|
430
487
|
end
|
|
431
488
|
|
|
432
489
|
rspec
|
|
433
490
|
end.compact
|
|
434
491
|
end
|
|
492
|
+
local_root_binary_state.each do |root_name, state|
|
|
493
|
+
next unless state[:all_binary_hits]
|
|
494
|
+
next unless state[:spec_version]
|
|
495
|
+
|
|
496
|
+
# 本地 :path 仅在同一 root 的 binary 解析全部成功后,统一提升为普通 binary pod。
|
|
497
|
+
promote_local_path_pod_to_binary(root_name, state[:spec_version])
|
|
498
|
+
end
|
|
499
|
+
if podfile.use_binaries?
|
|
500
|
+
local_roots = specs_by_target.values.flatten.map { |rs| Pod::Specification.root_name(rs.root.name) }.uniq.select { |name| sandbox.local?(name) }
|
|
501
|
+
print_worktree_identity_debug(local_roots, 'after_resolver_specs')
|
|
502
|
+
end
|
|
435
503
|
|
|
436
504
|
def print_dependency_tree(vertex, indent = 0)
|
|
437
505
|
puts "#{' ' * indent}#{vertex.name} (#{vertex.payload&.version || '?'})"
|
|
@@ -480,6 +548,34 @@ module Pod
|
|
|
480
548
|
end
|
|
481
549
|
end
|
|
482
550
|
|
|
551
|
+
def print_worktree_identity_debug(local_pods, stage)
|
|
552
|
+
return unless ENV['MEITU_BIN_WORKTREE_DEBUG'] == '1'
|
|
553
|
+
|
|
554
|
+
roots = local_pods.map { |name| Pod::Specification.root_name(name) }.uniq.sort
|
|
555
|
+
missing = roots.reject { |name| PodUpdateConfig.get_worktree_identity(name) }
|
|
556
|
+
if stage == 'after_precompute'
|
|
557
|
+
UI.puts "worktree_identity_debug(rule): #{CBin::Helpers::WorktreeIdentity.rule_version}".yellow
|
|
558
|
+
end
|
|
559
|
+
UI.puts "worktree_identity_debug(#{stage}): local=#{roots.size}, resolved=#{roots.size - missing.size}, missing=#{missing.size}".yellow
|
|
560
|
+
unless missing.empty?
|
|
561
|
+
UI.puts "worktree_identity_debug_missing(#{stage}): #{missing.join(',')}".yellow
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def promote_local_path_pod_to_binary(pod_name, spec_version)
|
|
566
|
+
root_name = Pod::Specification.root_name(pod_name)
|
|
567
|
+
return if PodUpdateConfig.promoted_local_pod?(root_name)
|
|
568
|
+
|
|
569
|
+
# 提升后的 root 不再视为本地 development pod,后续安装流程将按 binary pod 处理。
|
|
570
|
+
PodUpdateConfig.promote_local_pod(root_name, {
|
|
571
|
+
version: spec_version,
|
|
572
|
+
identity: PodUpdateConfig.get_worktree_identity(root_name),
|
|
573
|
+
})
|
|
574
|
+
PodUpdateConfig.set_external_source({ name: root_name, source: { promoted_from: 'path' } })
|
|
575
|
+
sandbox.development_pods.delete(root_name)
|
|
576
|
+
sandbox.remove_local_podspec(root_name) if sandbox.respond_to?(:remove_local_podspec)
|
|
577
|
+
end
|
|
578
|
+
|
|
483
579
|
# 获取组件的所有依赖(包括直接和间接依赖)
|
|
484
580
|
def get_all_dependencies(vertex, visited = Set.new, dependencies = {})
|
|
485
581
|
return dependencies if vertex.nil? || visited.include?(vertex.name)
|
|
@@ -221,8 +221,10 @@ Pod::HooksManager.register('cocoapods-meitu-bin', :pre_install) do |_context|
|
|
|
221
221
|
end
|
|
222
222
|
# get_podfile_lock
|
|
223
223
|
|
|
224
|
-
#
|
|
225
|
-
|
|
224
|
+
# 兼容旧逻辑:dev 环境下将 :path 组件自动加入源码白名单
|
|
225
|
+
# 默认关闭,避免阻断动态二进制切换。
|
|
226
|
+
force_local_path_source = ENV['MEITU_FORCE_LOCAL_PATH_SOURCE'] == 'true'
|
|
227
|
+
if force_local_path_source && _context.podfile.plugins.keys.include?('cocoapods-meitu-bin') && _context.podfile.configuration_env == 'dev'
|
|
226
228
|
dependencies = _context.podfile.dependencies
|
|
227
229
|
dependencies.each do |d|
|
|
228
230
|
next unless d.respond_to?(:external_source) &&
|
|
@@ -231,7 +233,7 @@ Pod::HooksManager.register('cocoapods-meitu-bin', :pre_install) do |_context|
|
|
|
231
233
|
$ARGV[1] != 'archive'
|
|
232
234
|
_context.podfile.set_use_source_pods d.name
|
|
233
235
|
end
|
|
234
|
-
|
|
236
|
+
Pod::UI.puts "MEITU_FORCE_LOCAL_PATH_SOURCE=true: local :path pods forced to source".yellow if ENV['MEITU_BIN_WORKTREE_DEBUG'] == '1'
|
|
235
237
|
end
|
|
236
238
|
PodUpdateConfig.set_prepare_time(Time.now - start_time)
|
|
237
239
|
# 同步 BinPodfile 文件
|
|
@@ -269,30 +271,27 @@ Pod::HooksManager.register('cocoapods-meitu-bin', :source_provider) do |context,
|
|
|
269
271
|
sources_manager = Pod::Config.instance.sources_manager
|
|
270
272
|
podfile = Pod::Config.instance.podfile
|
|
271
273
|
|
|
272
|
-
podfile_path = podfile.defined_in_file
|
|
273
274
|
pods_with_commit = {}
|
|
274
275
|
pods_with_path = 0
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
276
|
+
# source_provider 阶段直接使用 Podfile 解析结果:
|
|
277
|
+
# 1) 收集 external_source 中的 :commit 信息,参与后续 binary version 输入
|
|
278
|
+
# 2) 统计 :path 数量,辅助决定是否规避 lockfile 缓存
|
|
279
|
+
podfile.dependencies.each do |dependency|
|
|
280
|
+
source = dependency.respond_to?(:external_source) ? dependency.external_source : nil
|
|
281
|
+
next unless source.is_a?(Hash)
|
|
279
282
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if line =~ /pod\s+['"]([^'"]+)['"]/
|
|
283
|
-
pod_name = $1
|
|
284
|
-
# 提取 commit hash
|
|
285
|
-
if line =~ /:commit\s*=>\s*['"]([^'"]+)['"]/
|
|
286
|
-
commit_hash = $1
|
|
287
|
-
pods_with_commit[pod_name] = { commit: commit_hash }
|
|
288
|
-
end
|
|
289
|
-
end
|
|
290
|
-
elsif line.include?(':path =>')
|
|
291
|
-
pods_with_path = pods_with_path + 1
|
|
283
|
+
normalized_source = source.each_with_object({}) do |(key, value), memo|
|
|
284
|
+
memo[key.to_sym] = value
|
|
292
285
|
end
|
|
286
|
+
|
|
287
|
+
root_name = Pod::Specification.root_name(dependency.name)
|
|
288
|
+
commit_hash = normalized_source[:commit]
|
|
289
|
+
pods_with_commit[root_name] = { commit: commit_hash } if commit_hash
|
|
290
|
+
pods_with_path += 1 if normalized_source[:path]
|
|
293
291
|
end
|
|
294
292
|
# 解决多个组件切换从二进制切换到源码受podfile.lock 缓存影响导致切换异常的问题
|
|
295
293
|
if pods_with_path > 2
|
|
294
|
+
# :path 组件较多时,强制触发重新分析,避免沿用旧 lockfile 造成切换不一致。
|
|
296
295
|
PodUpdateConfig.set_is_podfile_lock_nil(true)
|
|
297
296
|
end
|
|
298
297
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cocoapods-meitu-bin
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jensen
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: parallel
|
|
@@ -140,6 +140,7 @@ files:
|
|
|
140
140
|
- lib/cocoapods-meitu-bin/helpers/spec_files_helper.rb
|
|
141
141
|
- lib/cocoapods-meitu-bin/helpers/spec_source_creator.rb
|
|
142
142
|
- lib/cocoapods-meitu-bin/helpers/upload_helper.rb
|
|
143
|
+
- lib/cocoapods-meitu-bin/helpers/worktree_identity.rb
|
|
143
144
|
- lib/cocoapods-meitu-bin/native.rb
|
|
144
145
|
- lib/cocoapods-meitu-bin/native/acknowledgements.rb
|
|
145
146
|
- lib/cocoapods-meitu-bin/native/analyzer.rb
|