cocoapods-yaml 1.0.0
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 +7 -0
- data/CHANGELOG.md +13 -0
- data/README.md +129 -0
- data/lib/cocoapods/yaml.rb +27 -0
- data/lib/cocoapods_plugin.rb +64 -0
- data/lib/core/check/import.rb +74 -0
- data/lib/core/check/item.rb +220 -0
- data/lib/core/check/podspec.rb +195 -0
- data/lib/core/check/project.rb +405 -0
- data/lib/core/check/simulator_arch.rb +145 -0
- data/lib/core/check/util.rb +37 -0
- data/lib/core/check/xcodeproj.rb +81 -0
- data/lib/core/command/trim_mode.rb +53 -0
- data/lib/core/command/yaml_init.rb +28 -0
- data/lib/core/command/yaml_relation.rb +43 -0
- data/lib/core/dep/find.rb +164 -0
- data/lib/core/indirect/indirect.rb +72 -0
- data/lib/core/logger.rb +85 -0
- data/lib/core/modify/source_patcher.rb +204 -0
- data/lib/core/my_constants.rb +16 -0
- data/lib/core/podfilelock.rb +17 -0
- data/lib/core/reference/project.rb +201 -0
- data/lib/core/reference/unused.rb +30 -0
- data/lib/core/setup/podfile_setup.rb +107 -0
- data/lib/core/thin/installer.rb +171 -0
- data/lib/core/thin/local.rb +40 -0
- data/lib/core/thin/pod_target.rb +32 -0
- data/lib/core/thin/shell.rb +180 -0
- data/lib/core/variable.rb +2 -0
- data/lib/core/yaml/yaml_dependencies.rb +774 -0
- data/lib/core/yaml/yaml_pod.rb +63 -0
- data/lib/core_helper.rb +10 -0
- data/lib/version.rb +7 -0
- metadata +133 -0
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
require_relative 'yaml_pod'
|
|
2
|
+
require_relative '../../version'
|
|
3
|
+
require_relative '../my_constants'
|
|
4
|
+
require_relative '../check/xcodeproj'
|
|
5
|
+
require_relative '../variable'
|
|
6
|
+
require 'colored2'
|
|
7
|
+
require 'yaml'
|
|
8
|
+
require 'json'
|
|
9
|
+
require 'shellwords'
|
|
10
|
+
|
|
11
|
+
module Pod
|
|
12
|
+
class YamlDependencies
|
|
13
|
+
|
|
14
|
+
# 需要集成依赖的主工程target列表(application/app扩展)
|
|
15
|
+
# @return [Array]
|
|
16
|
+
attr_accessor :targets
|
|
17
|
+
|
|
18
|
+
# 所有yaml文件定义的依赖
|
|
19
|
+
# @return {String => {String => Pod:YamlPod}} key: yaml文件名
|
|
20
|
+
attr_accessor :yaml_deps
|
|
21
|
+
|
|
22
|
+
# 所有target最终生效的依赖(已合并LocalDependencies覆盖)
|
|
23
|
+
# @return {target对象 => [Pod:YamlPod]}
|
|
24
|
+
attr_accessor :targets_dependencies
|
|
25
|
+
|
|
26
|
+
# 每个target依赖文件声明的SOURCES(有序)
|
|
27
|
+
# @return {String => [String]} key: yaml文件名
|
|
28
|
+
attr_accessor :file_sources
|
|
29
|
+
|
|
30
|
+
attr_accessor :local_yaml_name
|
|
31
|
+
|
|
32
|
+
def initialize
|
|
33
|
+
super
|
|
34
|
+
@targets = []
|
|
35
|
+
@yaml_deps = {}
|
|
36
|
+
@targets_dependencies = {}
|
|
37
|
+
@file_sources = {}
|
|
38
|
+
@local_yaml_name = MyConstants::LOCAL_DEPENDENCIES_YAML
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# 某个target对应的依赖yaml文件名
|
|
42
|
+
def target_yaml_name(target_name)
|
|
43
|
+
"#{target_name}#{MyConstants::DEPENDENCIES_YAML_SUFFIX}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# 某个target对应的包内依赖清单文件名(仅Debug入包)
|
|
47
|
+
def target_json_name(target_name)
|
|
48
|
+
"#{target_name}#{MyConstants::DEPENDENCIES_JSON_SUFFIX}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# 仅主App(.application)和App扩展(.app-extension)需要通过yaml集成依赖
|
|
52
|
+
def integratable_target?(target)
|
|
53
|
+
type = target.product_type.to_s
|
|
54
|
+
type.end_with?('.application') || type.end_with?('.app-extension')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def install_yaml_dep(podfile)
|
|
58
|
+
analysis_yaml_dep
|
|
59
|
+
|
|
60
|
+
# 为路径依赖的组件仓库开启 SNAPSHOT tag 自动同步(最小权限: 只配置被本地引用的仓库)
|
|
61
|
+
ensure_local_path_tag_sync
|
|
62
|
+
|
|
63
|
+
# 将YAML中声明的私有源注册为Podfile顶层source,屏蔽CocoaPods隐式追加的CDN trunk源
|
|
64
|
+
configure_private_sources(podfile)
|
|
65
|
+
|
|
66
|
+
install_dependencies(targets_dependencies, podfile) do|name|
|
|
67
|
+
yield(name) if block_given?
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# 是否允许使用CocoaPods官方CDN trunk源。
|
|
72
|
+
# 由LocalDependencies.yaml顶层 ENABLE_TRUNK 控制(工程级,不提交git,只影响本工程),默认false
|
|
73
|
+
def trunk_enabled?
|
|
74
|
+
content = read_yaml_content(local_yaml_name)
|
|
75
|
+
content ? content['ENABLE_TRUNK'] == true : false
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Podfile未声明任何顶层source时,CocoaPods会隐式加入CDN trunk源(cdn.cocoapods.org)。
|
|
79
|
+
# 团队组件(含三方库fork)全部在私有源,无需trunk:trunk既会因网络问题导致pod install失败,
|
|
80
|
+
# 也会与私有源存在同名podspec(如WechatOpenSDK)而产生"Found multiple specifications"警告。
|
|
81
|
+
# 这里按各target依赖文件中SOURCES的声明顺序注册为顶层source(把二进制源放在第一位,
|
|
82
|
+
# 可保证间接依赖优先命中二进制产物),逐依赖显式指定的source追加在后。
|
|
83
|
+
def configure_private_sources(podfile)
|
|
84
|
+
if trunk_enabled?
|
|
85
|
+
log.detail("已开启CDN trunk源(LocalDependencies.yaml中ENABLE_TRUNK: true)")
|
|
86
|
+
return
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private_sources = []
|
|
90
|
+
targets.each do |target|
|
|
91
|
+
name = target_yaml_name(target.name)
|
|
92
|
+
Array(file_sources[name]).each { |repo| private_sources << repo }
|
|
93
|
+
dependencies = yaml_deps[name]
|
|
94
|
+
next unless dependencies.is_a?(Hash)
|
|
95
|
+
dependencies.each_value do |dependency|
|
|
96
|
+
repo = dependency.source.to_s.strip
|
|
97
|
+
private_sources << repo unless repo.empty?
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
private_sources.uniq!
|
|
101
|
+
|
|
102
|
+
log.info('')
|
|
103
|
+
if private_sources.empty?
|
|
104
|
+
log.warn("target依赖文件未声明任何私有源(SOURCES),保留CocoaPods默认CDN trunk源")
|
|
105
|
+
return
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private_sources.each { |repo| podfile.source(repo) }
|
|
109
|
+
log.detail("已限定使用#{private_sources.size}个私有specs源,不使用CDN trunk")
|
|
110
|
+
private_sources.each { |repo| log.info(" - #{repo}") }
|
|
111
|
+
log.info(" 如需恢复trunk,在#{local_yaml_name}顶层设置 ENABLE_TRUNK: true")
|
|
112
|
+
log.info('')
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# SNAPSHOT 同名 tag 每次发布会被删除后前移,而 git 默认 fetch 不会覆盖本地已存在的 tag,
|
|
116
|
+
# 导致本地/同事本地的组件仓 tag 停留在旧位置。
|
|
117
|
+
# 这里仅对 LocalDependencies.yaml 中以 path 方式引用、且本身是 git 仓库的目录,
|
|
118
|
+
# 幂等写入仓库级(.git/config)配置:
|
|
119
|
+
# 1. fetch refspec 对 tags 加 '+' 前缀,允许强制更新已移动的 tag
|
|
120
|
+
# 2. tagOpt=--no-tags 关闭默认的 tag 自动跟随,避免与上面的显式 refspec 冲突
|
|
121
|
+
# (否则会报 multiple updates for ref 'refs/tags/xxx' not allowed)
|
|
122
|
+
def ensure_local_path_tag_sync
|
|
123
|
+
local_deps = yaml_deps[local_yaml_name]
|
|
124
|
+
return unless local_deps.is_a?(Hash)
|
|
125
|
+
|
|
126
|
+
tag_refspec = '+refs/tags/*:refs/tags/*'
|
|
127
|
+
configured = 0
|
|
128
|
+
|
|
129
|
+
local_deps.values.map(&:path).compact.uniq.each do |raw_path|
|
|
130
|
+
repo_path = File.expand_path(raw_path)
|
|
131
|
+
next unless File.exist?(File.join(repo_path, '.git'))
|
|
132
|
+
|
|
133
|
+
begin
|
|
134
|
+
existing = `git -C #{Shellwords.escape(repo_path)} config --get-all remote.origin.fetch 2>/dev/null`.to_s
|
|
135
|
+
next if existing.lines.map(&:strip).include?(tag_refspec)
|
|
136
|
+
|
|
137
|
+
# 数组形式system避免路径/refs注入,并检查退出码,失败不静默
|
|
138
|
+
add_ok = system('git', '-C', repo_path, 'config', '--add', 'remote.origin.fetch', tag_refspec,
|
|
139
|
+
out: File::NULL, err: File::NULL)
|
|
140
|
+
tag_ok = add_ok && system('git', '-C', repo_path, 'config', 'remote.origin.tagOpt', '--no-tags',
|
|
141
|
+
out: File::NULL, err: File::NULL)
|
|
142
|
+
unless add_ok && tag_ok
|
|
143
|
+
log.warn("路径仓库SNAPSHOT tag自动同步配置未生效(git config执行失败): #{repo_path}")
|
|
144
|
+
next
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
log.info('') if configured == 0
|
|
148
|
+
log.detail("已开启路径仓库的SNAPSHOT tag自动同步(后续 git fetch/pull 会自动前移): #{repo_path}")
|
|
149
|
+
configured += 1
|
|
150
|
+
rescue => e
|
|
151
|
+
log.warn("配置路径仓库tag同步失败(#{repo_path}): #{e.message}")
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
log.detail("共为#{configured}个路径仓库开启了SNAPSHOT tag自动同步") if configured > 0
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def install_dependencies(targets_dependencies, podfile)
|
|
159
|
+
# 存储所有的依赖
|
|
160
|
+
targets_dependencies.each { |target_obj, dependencies|
|
|
161
|
+
# 零依赖target(如尚未声明依赖的扩展)不生成空的Podfile target块
|
|
162
|
+
next if dependencies.empty?
|
|
163
|
+
target_name = target_obj.name
|
|
164
|
+
podfile.target target_name do
|
|
165
|
+
dependencies.each { |dependency|
|
|
166
|
+
if dependency.version
|
|
167
|
+
podfile.pod dependency.pod, dependency.version, dependency.option
|
|
168
|
+
else
|
|
169
|
+
podfile.pod dependency.pod, dependency.option
|
|
170
|
+
end
|
|
171
|
+
}
|
|
172
|
+
yield(target_name) if block_given?
|
|
173
|
+
end
|
|
174
|
+
}
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# 解析所有target在yaml文件中定义的依赖
|
|
178
|
+
# @return targets_dependencies
|
|
179
|
+
def analysis_yaml_dep
|
|
180
|
+
init_main_targets
|
|
181
|
+
check_orphan_yaml_files
|
|
182
|
+
generate_yaml
|
|
183
|
+
read_yaml_deps
|
|
184
|
+
resolve_targets_deps
|
|
185
|
+
print_targets_deps
|
|
186
|
+
targets_dependencies
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# 构建各target最终依赖的结构化记录(供包内依赖清单json使用,不生成模板文件、不打印日志)
|
|
190
|
+
# @return {String => Array<Hash>} key: target名
|
|
191
|
+
def build_dependency_records
|
|
192
|
+
init_main_targets
|
|
193
|
+
read_yaml_deps
|
|
194
|
+
resolve_targets_deps
|
|
195
|
+
records = {}
|
|
196
|
+
targets_dependencies.each do |target, dependencies|
|
|
197
|
+
records[target.name] = dependencies.map { |dependency| dependency_record(dependency) }
|
|
198
|
+
end
|
|
199
|
+
records
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# 归一化依赖记录(空值与默认值不输出,保持与yaml精简风格一致)
|
|
203
|
+
def dependency_record(dependency)
|
|
204
|
+
record = { 'pod' => dependency.pod }
|
|
205
|
+
%w[version git tag branch podspec path].each do |field|
|
|
206
|
+
value = dependency.public_send(field)
|
|
207
|
+
record[field] = value unless value.nil? || value.to_s.strip.empty?
|
|
208
|
+
end
|
|
209
|
+
record['configurations'] = dependency.configurations if Array(dependency.configurations).any?
|
|
210
|
+
record['source'] = dependency.source unless dependency.source.to_s.strip.empty?
|
|
211
|
+
# 默认true,仅false(本地覆盖默认值)时显式输出
|
|
212
|
+
record['inhibit_warnings'] = false if dependency.inhibit_warnings == false
|
|
213
|
+
record['local'] = true if dependency.from_local
|
|
214
|
+
record
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# 合并LocalDependencies.yaml的本地声明到某个target的依赖上
|
|
218
|
+
# 规则:
|
|
219
|
+
# 1. 对target文件中已声明的同名组件(按组件根名匹配)做本地覆盖;
|
|
220
|
+
# 覆盖时清除该组件在target中的全部旧声明(含各subspec),
|
|
221
|
+
# 否则root(path来源)与subspec(版本号来源)会触发CocoaPods多源冲突
|
|
222
|
+
# 2. target文件未声明的"全新组件"不在此处处理,
|
|
223
|
+
# 由inject_local_only_dependencies统一注入主App target
|
|
224
|
+
# @param file_deps {String => YamlPod} target依赖文件中的依赖
|
|
225
|
+
# @return {String => YamlPod}
|
|
226
|
+
def merge_local_dependencies(file_deps)
|
|
227
|
+
result = file_deps.dup
|
|
228
|
+
local_deps = yaml_deps[local_yaml_name]
|
|
229
|
+
return result unless local_deps.is_a?(Hash)
|
|
230
|
+
|
|
231
|
+
declared_roots = result.keys.map { |key| key.split('/').first }.uniq
|
|
232
|
+
|
|
233
|
+
local_deps.each do |name, dep|
|
|
234
|
+
real_name = name.split('/')[0]
|
|
235
|
+
next unless declared_roots.include?(real_name)
|
|
236
|
+
|
|
237
|
+
result.delete_if { |key, _| key.split('/').first == real_name }
|
|
238
|
+
# 覆盖明细不单独打印,下方依赖逐行输出中的[本地覆盖]标记已体现
|
|
239
|
+
result[name] = dep
|
|
240
|
+
end
|
|
241
|
+
result
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def resolve_targets_deps
|
|
245
|
+
merged_hashes = {}
|
|
246
|
+
targets.each do |target|
|
|
247
|
+
file_deps = yaml_deps[target_yaml_name(target.name)] || {}
|
|
248
|
+
merged_hashes[target] = merge_local_dependencies(file_deps)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# LocalDependencies中声明、但所有target依赖文件都未声明的全新组件,注入主App target
|
|
252
|
+
inject_local_only_dependencies(merged_hashes)
|
|
253
|
+
|
|
254
|
+
merged_hashes.each { |target, deps| targets_dependencies[target] = deps.values }
|
|
255
|
+
targets_dependencies
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# LocalDependencies.yaml里声明、但没有任何target依赖文件声明的组件(本地联调主动引入的新组件)。
|
|
259
|
+
# 统一注入主App target(.application):
|
|
260
|
+
# - 本地开发新增组件的标准路径就是写LocalDependencies,理应能直接pod install集成
|
|
261
|
+
# - 裁剪(TRIM_UNUSED_POD)对LocalDependencies中的组件本就整体保留,注入后同样不会被剪掉
|
|
262
|
+
# 不向App扩展target注入:扩展通常只允许子集依赖,自动塞入无关库会拖大扩展体积甚至引发链接问题,
|
|
263
|
+
# 扩展需要新组件时请在对应<Target>Dependencies.yaml显式声明
|
|
264
|
+
# @param merged_hashes {target => {String => YamlPod}} 各target已合并本地覆盖的依赖
|
|
265
|
+
def inject_local_only_dependencies(merged_hashes)
|
|
266
|
+
local_deps = yaml_deps[local_yaml_name]
|
|
267
|
+
return unless local_deps.is_a?(Hash) && local_deps.any?
|
|
268
|
+
|
|
269
|
+
declared_roots = targets.flat_map { |target| (yaml_deps[target_yaml_name(target.name)] || {}).keys }
|
|
270
|
+
.map { |key| key.split('/').first }
|
|
271
|
+
.uniq
|
|
272
|
+
|
|
273
|
+
new_entries = local_deps.reject { |name, _| declared_roots.include?(name.split('/').first) }
|
|
274
|
+
return if new_entries.empty?
|
|
275
|
+
|
|
276
|
+
main_target = targets.find { |target| target.product_type.to_s.end_with?('.application') }
|
|
277
|
+
new_roots = new_entries.keys.map { |name| name.split('/').first }.uniq
|
|
278
|
+
unless main_target
|
|
279
|
+
log.warn("LocalDependencies.yaml中的新组件(#{new_roots.join('、')})未在任何target依赖文件中声明,且未找到主App target,无法自动集成;请在对应<Target>Dependencies.yaml中声明")
|
|
280
|
+
return
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# version引用且未显式指定source时,借用主App target依赖文件SOURCES的第一个源,
|
|
284
|
+
# 与target依赖文件"不写source默认取SOURCES第一个"的语义保持一致;
|
|
285
|
+
# path/podspec/git引用不经过specs源解析,无需补source
|
|
286
|
+
default_source = file_sources[target_yaml_name(main_target.name)]&.first
|
|
287
|
+
|
|
288
|
+
merged = merged_hashes[main_target]
|
|
289
|
+
added_roots = []
|
|
290
|
+
new_entries.each do |name, dep|
|
|
291
|
+
next if merged.key?(name)
|
|
292
|
+
|
|
293
|
+
dep.local_injected = true
|
|
294
|
+
dep.source = default_source if dep.version && default_source && dep.source.to_s.strip.empty?
|
|
295
|
+
merged[name] = dep
|
|
296
|
+
added_roots << name.split('/').first
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
log.detail("LocalDependencies中的新组件(#{added_roots.uniq.join('、')})未在target依赖文件声明,已自动加入主App target[#{main_target.name}](不会被裁剪;App扩展target不会自动注入,需在对应依赖文件显式声明)") if added_roots.any?
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# 读取所有target和本地覆盖的yaml文件
|
|
303
|
+
def read_yaml_deps
|
|
304
|
+
targets.each { |target| read_yaml_deps_file(target_yaml_name(target.name)) }
|
|
305
|
+
read_yaml_deps_file(local_yaml_name)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def read_yaml_deps_file(yaml_name)
|
|
309
|
+
|
|
310
|
+
yaml_content = read_yaml_content(yaml_name)
|
|
311
|
+
|
|
312
|
+
unless yaml_content
|
|
313
|
+
return
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
unless yaml_content["PODS"]
|
|
317
|
+
log.detail("#{yaml_name}共0个依赖(文件内的key[PODS]不存在)")
|
|
318
|
+
return
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
if yaml_content["PODS"].class != Array
|
|
322
|
+
log.detail("#{yaml_name}共0个依赖(文件配置的依赖PODS项不是数组或数组为空)")
|
|
323
|
+
return
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
if yaml_name != local_yaml_name
|
|
327
|
+
sources = yaml_content["SOURCES"]
|
|
328
|
+
# 零依赖文件(如尚未配置的扩展)允许SOURCES留空;一旦声明依赖,SOURCES必须是非空字符串数组
|
|
329
|
+
unless yaml_content["PODS"].empty?
|
|
330
|
+
if sources.class != Array or sources.empty? or sources.any? { |s| s.to_s.strip.empty? }
|
|
331
|
+
log.error("#{yaml_name}的SOURCES字段必须是非空数组,例如: SOURCES: ['git@xxx_binary.git', 'git@xxx_source.git']")
|
|
332
|
+
exit!
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
if sources.is_a?(Array) and sources.all? { |s| s.to_s.strip != "" }
|
|
336
|
+
file_sources[yaml_name] = sources.map(&:to_s).map(&:strip)
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# 依赖未显式指定source时,默认使用SOURCES中的第一个源(通常把二进制源放在第一位)
|
|
341
|
+
default_source = file_sources[yaml_name]&.first
|
|
342
|
+
|
|
343
|
+
# 依赖列表
|
|
344
|
+
dependencies_pods = yaml_content['PODS']
|
|
345
|
+
|
|
346
|
+
# 当前yaml文件的组件名字 用来校验本文件内是否重复
|
|
347
|
+
dependencies_name = []
|
|
348
|
+
|
|
349
|
+
yaml_dependencies = {}
|
|
350
|
+
|
|
351
|
+
# 遍历依赖库列表
|
|
352
|
+
dependencies_pods.each { |dependency|
|
|
353
|
+
|
|
354
|
+
check_pod(dependency, yaml_name, dependencies_name)
|
|
355
|
+
|
|
356
|
+
# source: 显式指定优先,否则取默认源;LocalDependencies无SOURCES时允许为空(path/podspec引用不需要)
|
|
357
|
+
source = dependency['source'].to_s.strip
|
|
358
|
+
source = default_source if source.empty? && default_source
|
|
359
|
+
|
|
360
|
+
# inhibit_warnings: target依赖文件默认true(远程库警告不好修改),本地覆盖文件默认false(开发时能看到本地库警告)
|
|
361
|
+
inhibit_warnings = if dependency.key?('inhibit_warnings')
|
|
362
|
+
!!dependency['inhibit_warnings']
|
|
363
|
+
else
|
|
364
|
+
yaml_name != local_yaml_name
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# configurations和subspecs cocoapods不能同时支持,所以要展开
|
|
368
|
+
if Array(dependency['subspecs']).size > 0 and Array(dependency['configurations']).size > 0
|
|
369
|
+
dependency['subspecs'].each {|subspec|
|
|
370
|
+
dep = new_yaml_dep(dependency, "#{dependency['pod']}/#{subspec}", source, inhibit_warnings, yaml_name)
|
|
371
|
+
dep.subspecs = nil
|
|
372
|
+
yaml_dependencies[dep.pod] = dep
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
else
|
|
376
|
+
dep = new_yaml_dep(dependency, dependency['pod'], source, inhibit_warnings, yaml_name)
|
|
377
|
+
yaml_dependencies[dep.pod] = dep
|
|
378
|
+
end
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
yaml_deps[yaml_name] = yaml_dependencies
|
|
382
|
+
|
|
383
|
+
yaml_dependencies
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def new_yaml_dep(dependency, pod_name, source, inhibit_warnings, yaml_name)
|
|
387
|
+
dep = YamlPod.new(pod_name)
|
|
388
|
+
dep.subspecs = dependency['subspecs']
|
|
389
|
+
dep.path = dependency['path']
|
|
390
|
+
dep.podspec = dependency['podspec']
|
|
391
|
+
dep.version = dependency['version']
|
|
392
|
+
dep.git = dependency['git']
|
|
393
|
+
dep.tag = dependency['tag']
|
|
394
|
+
dep.branch = dependency['branch']
|
|
395
|
+
dep.configurations = dependency['configurations']
|
|
396
|
+
dep.inhibit_warnings = inhibit_warnings
|
|
397
|
+
dep.source = source
|
|
398
|
+
dep.from_local = (yaml_name == local_yaml_name)
|
|
399
|
+
dep
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def check_pod(dependency, yaml_name, dependencies_name)
|
|
403
|
+
# 依赖条目必须是keyValue形式;写成"- Foo"这种纯字符串时后续each_key会抛原始堆栈,这里前置拦截
|
|
404
|
+
unless dependency.is_a?(Hash)
|
|
405
|
+
log.error("#{yaml_name}: 依赖条目必须为keyValue形式(每项以 - pod: 开头),当前条目: #{dependency.inspect}")
|
|
406
|
+
exit!
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# 不支持提醒
|
|
410
|
+
available_keys = %w[pod subspecs path podspec version git tag branch configurations inhibit_warnings source]
|
|
411
|
+
dependency.each_key { |key|
|
|
412
|
+
unless available_keys.include?(key)
|
|
413
|
+
gem_name = "cocoapods-yaml"
|
|
414
|
+
log.error("#{yaml_name}: 当前#{gem_name}版本(#{CocoaPods::Yaml::VERSION})不支持字段#{key}")
|
|
415
|
+
log.info("请检查对应字段或尝试执行如下命令升级")
|
|
416
|
+
log.info("gem uninstall #{gem_name} && gem install #{gem_name}")
|
|
417
|
+
exit!
|
|
418
|
+
end
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
# 校验字段
|
|
422
|
+
unless dependency['pod']
|
|
423
|
+
log.error("#{yaml_name}: pod 字段(组件名字)必须存在,请检查:")
|
|
424
|
+
exit!
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
current_type = nil
|
|
428
|
+
current_type = check_only_one_dep(dependency, "path", current_type, yaml_name)
|
|
429
|
+
current_type = check_only_one_dep(dependency, "podspec", current_type, yaml_name)
|
|
430
|
+
current_type = check_only_one_dep(dependency, "version", current_type, yaml_name)
|
|
431
|
+
current_type = check_only_one_dep(dependency, "tag", current_type, yaml_name)
|
|
432
|
+
current_type = check_only_one_dep(dependency, "branch", current_type, yaml_name)
|
|
433
|
+
|
|
434
|
+
unless current_type
|
|
435
|
+
log.error("#{yaml_name} - #{dependency['pod']}: path/podspec/version/git/tag/branch 字段请指定其中一个")
|
|
436
|
+
exit!
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
unless dependency['git']
|
|
440
|
+
if dependency['tag']
|
|
441
|
+
log.error("#{yaml_name} - #{dependency['pod']}: 你指定了tag,但未配置git地址,请为此组件配置git字段")
|
|
442
|
+
exit!
|
|
443
|
+
end
|
|
444
|
+
if dependency['branch']
|
|
445
|
+
log.error("#{yaml_name} - #{dependency['pod']}: 你指定了branch,但未配置git地址,请为此组件配置git字段")
|
|
446
|
+
exit!
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# 检查数据类型
|
|
451
|
+
if dependency['configurations'] and dependency['configurations'].class != Array
|
|
452
|
+
log.error("#{yaml_name} - #{dependency['pod']} - #{dependency['configurations']}: configurations字段必须是数组,例如:['Debug','Release']")
|
|
453
|
+
exit!
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
if dependency['subspecs'] and dependency['subspecs'].class != Array
|
|
457
|
+
log.error("#{yaml_name} - #{dependency['pod']} - #{dependency['subspecs']}: subspecs字段必须是数组,例如:['A','B','C']")
|
|
458
|
+
exit!
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# 校验自身文件重复
|
|
462
|
+
if dependencies_name.include?(dependency['pod'])
|
|
463
|
+
log.error("发现#{yaml_name}内重复组件:#{dependency['pod']},请检查并删除重复依赖")
|
|
464
|
+
exit!
|
|
465
|
+
else
|
|
466
|
+
dependencies_name << dependency['pod']
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# 警告
|
|
470
|
+
if dependency['path'] and yaml_name != local_yaml_name
|
|
471
|
+
log.warn("组件#{dependency['pod']}使用path依赖方式应该写在#{local_yaml_name}")
|
|
472
|
+
end
|
|
473
|
+
if dependency['podspec'] and yaml_name != local_yaml_name
|
|
474
|
+
log.warn("组件#{dependency['pod']}使用podspec依赖方式应该写在#{local_yaml_name}")
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def check_only_one_dep(dependency, key, current_type, yaml_name)
|
|
480
|
+
if dependency[key]
|
|
481
|
+
if current_type
|
|
482
|
+
log.error("#{yaml_name} - #{dependency['pod']}: #{current_type}字段和#{key}只能使用其中一个,请删除不需要的一个")
|
|
483
|
+
exit!
|
|
484
|
+
else
|
|
485
|
+
return key
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
current_type
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def read_yaml_content(yaml_name)
|
|
492
|
+
unless File.exist?(yaml_name)
|
|
493
|
+
log.detail("文件不存在:#{yaml_name}")
|
|
494
|
+
return nil
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
begin
|
|
498
|
+
# aliases: true 支持YAML锚点变量(SOURCES上的 &binary/&source 锚点被组件的 *source 引用)
|
|
499
|
+
yaml_content = YAML.load_file(yaml_name, aliases: true)
|
|
500
|
+
rescue Exception => e
|
|
501
|
+
log.error(e.to_s)
|
|
502
|
+
log.error("#{yaml_name}文件解析异常, 请检查 ⬆️")
|
|
503
|
+
exit!
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
unless yaml_content.class == Hash
|
|
507
|
+
log.warn("#{yaml_name} => (文件不是keyValue形式)")
|
|
508
|
+
return nil
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
yaml_content
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
def print_targets_deps
|
|
515
|
+
log.step('读取依赖文件')
|
|
516
|
+
yaml_deps.each do |yaml_name, deps|
|
|
517
|
+
next if yaml_name == local_yaml_name
|
|
518
|
+
log.detail("#{yaml_name} => #{deps.size}个")
|
|
519
|
+
end
|
|
520
|
+
local_deps = yaml_deps[local_yaml_name]
|
|
521
|
+
log.detail("#{local_yaml_name} => #{local_deps ? local_deps.size : 0}个(本地覆盖)") if local_deps
|
|
522
|
+
|
|
523
|
+
targets_dependencies.each do |target, dependencies|
|
|
524
|
+
# 零依赖target只打印个数,不遍历具体依赖
|
|
525
|
+
log.step("target[#{target.name}] 解析后依赖共计#{dependencies.size}个")
|
|
526
|
+
next if dependencies.empty?
|
|
527
|
+
|
|
528
|
+
sort_deps = dependencies.sort do |dep1, dep2|
|
|
529
|
+
if dep1.type != dep2.type
|
|
530
|
+
dep1.type < dep2.type ? 1 : -1
|
|
531
|
+
else
|
|
532
|
+
dep1.pod.downcase <=> dep2.pod.downcase
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
# 默认值只在这里打印一次,与默认值相同的字段不再逐行输出
|
|
537
|
+
default_source = file_sources[target_yaml_name(target.name)]&.first
|
|
538
|
+
log.detail("以下字段使用默认值时逐行省略: :inhibit_warnings => true#{default_source ? ", :source => '#{default_source}'" : ''}")
|
|
539
|
+
log.info('')
|
|
540
|
+
sort_deps.each { |dependency|
|
|
541
|
+
# 依赖行缩进2空格,归属到当前target块
|
|
542
|
+
puts_result = " pod '#{dependency.pod}'"
|
|
543
|
+
|
|
544
|
+
if dependency.path #路径引用
|
|
545
|
+
puts_result += ", :path => '#{dependency.path}'"
|
|
546
|
+
elsif dependency.podspec #podspec引用
|
|
547
|
+
puts_result += ", :podspec => '#{dependency.podspec}'"
|
|
548
|
+
elsif dependency.version #版本号引用
|
|
549
|
+
puts_result += ", '#{dependency.version.to_s}'"
|
|
550
|
+
elsif dependency.git #git仓库引用
|
|
551
|
+
puts_result += ", :git => '#{dependency.git}'"
|
|
552
|
+
if dependency.tag
|
|
553
|
+
puts_result += ", :tag => '#{dependency.tag}'"
|
|
554
|
+
elsif dependency.branch
|
|
555
|
+
puts_result += ", :branch => '#{dependency.branch}'"
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
if dependency.configurations
|
|
560
|
+
puts_result += ", :configurations => ['#{dependency.configurations.join("','")}']"
|
|
561
|
+
end
|
|
562
|
+
if dependency.subspecs
|
|
563
|
+
puts_result += ", :subspecs => ['#{dependency.subspecs.join("','")}']"
|
|
564
|
+
end
|
|
565
|
+
# inhibit_warnings默认true,仅显式false时打印
|
|
566
|
+
puts_result += ", :inhibit_warnings => false" if dependency.inhibit_warnings == false
|
|
567
|
+
# source默认取SOURCES第一个,仅版本号引用且使用非默认源时打印
|
|
568
|
+
if dependency.version and dependency.source and dependency.source != default_source
|
|
569
|
+
puts_result += ", :source => '#{dependency.source}'"
|
|
570
|
+
end
|
|
571
|
+
if dependency.local_injected
|
|
572
|
+
puts_result += " [本地新增]"
|
|
573
|
+
elsif dependency.from_local
|
|
574
|
+
puts_result += " [本地覆盖]"
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
if dependency.path
|
|
578
|
+
branch = repo_branch(dependency.path)
|
|
579
|
+
modify = repo_modify(dependency.path)
|
|
580
|
+
# 路径依赖青色,分支名/仓库修改状态品红
|
|
581
|
+
log.info(colorize(puts_result, :cyan) + colorize(branch, :magenta) + colorize(modify, :magenta))
|
|
582
|
+
|
|
583
|
+
elsif dependency.branch
|
|
584
|
+
log.info(colorize(puts_result, :yellow))
|
|
585
|
+
else
|
|
586
|
+
log.info(puts_result)
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
}
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
# 检查工程里不存在对应target的孤儿依赖文件(target改名/删除后残留)
|
|
594
|
+
def check_orphan_yaml_files
|
|
595
|
+
expected = targets.map { |target| target_yaml_name(target.name) }
|
|
596
|
+
Dir.glob("*#{MyConstants::DEPENDENCIES_YAML_SUFFIX}").each do |file|
|
|
597
|
+
next if file == local_yaml_name
|
|
598
|
+
next if expected.include?(file)
|
|
599
|
+
# 文件名约定为 <TargetName>Dependencies.yaml,去掉后缀即配置对应的 target 名
|
|
600
|
+
target_name = File.basename(file, MyConstants::DEPENDENCIES_YAML_SUFFIX)
|
|
601
|
+
log.warn("检测到配置文件 #{file} 在工程目录中,但未检测到工程的target '#{target_name}',已忽略加载该配置文件。请检查该target是否存在并仍在使用,如不存在建议删除该配置文件,以免再次产生此警告")
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
# 生成默认yaml文件
|
|
606
|
+
def generate_yaml
|
|
607
|
+
generate_local_yaml
|
|
608
|
+
targets.each { |target| generate_target_yaml(target_yaml_name(target.name)) }
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
def generate_local_yaml
|
|
612
|
+
if File.exist?(local_yaml_name)
|
|
613
|
+
return
|
|
614
|
+
end
|
|
615
|
+
log.detail("生成#{local_yaml_name}文件")
|
|
616
|
+
|
|
617
|
+
content = ""
|
|
618
|
+
content = content + "# 本地依赖文件(不提交git),两种用途:\n"
|
|
619
|
+
content = content + "# 1. 按pod名覆盖任意 <TargetName>Dependencies.yaml 中的同名组件(通常写path做本地联调)\n"
|
|
620
|
+
content = content + "# 2. 直接引入target依赖文件未声明的新组件: 会自动加入主App target且不被裁剪;\n"
|
|
621
|
+
content = content + "# App扩展target不会自动注入,扩展需要的新组件请在对应target依赖文件显式声明\n"
|
|
622
|
+
content = content + "# 引用方式(path/podspec/tag/branch)与字段规则同target依赖文件\n"
|
|
623
|
+
content = content + "# inhibit_warnings 不写默认false,方便开发时看到本地库的警告并及时修改\n\n"
|
|
624
|
+
|
|
625
|
+
content = content + "# TRIM_UNUSED_POD为true时自动移除未被引用的依赖库,加速开发编译\n"
|
|
626
|
+
content = content + "TRIM_UNUSED_POD: false\n\n"
|
|
627
|
+
|
|
628
|
+
content = content + "# 默认只使用target依赖文件中SOURCES声明的私有源,不使用CocoaPods官方CDN trunk源\n"
|
|
629
|
+
content = content + "# 如本工程确需trunk,把下行改为true(仅本工程生效)\n"
|
|
630
|
+
content = content + "ENABLE_TRUNK: false\n\n"
|
|
631
|
+
|
|
632
|
+
content = content + "PODS: []\n\n"
|
|
633
|
+
|
|
634
|
+
content = content + "# 示例:\n"
|
|
635
|
+
content = content + "# - pod: Masonry\n"
|
|
636
|
+
content = content + "# path: ~/MasonryCodePath\n"
|
|
637
|
+
content = content + "# - pod: MJRefresh\n"
|
|
638
|
+
content = content + "# git: git@gitee.com:xxx/MJRefresh.git\n"
|
|
639
|
+
content = content + "# branch: master\n"
|
|
640
|
+
|
|
641
|
+
File.open(local_yaml_name, 'w') { |file|
|
|
642
|
+
file.write(content)
|
|
643
|
+
}
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
def generate_target_yaml(yaml_name)
|
|
647
|
+
if File.exist?(yaml_name)
|
|
648
|
+
return
|
|
649
|
+
end
|
|
650
|
+
log.detail("生成#{yaml_name}文件")
|
|
651
|
+
|
|
652
|
+
content = ""
|
|
653
|
+
content = content + "# #{File.basename(yaml_name, MyConstants::DEPENDENCIES_YAML_SUFFIX)} target的依赖清单(企业内部组件与第三方组件统一维护)\n"
|
|
654
|
+
content = content + "# 1、每个组件只能指定 version/tag/branch/path/podspec 其中一种引用方式(tag/branch需配合git)\n"
|
|
655
|
+
content = content + "# 2、path/podspec 本地联调依赖请写在#{local_yaml_name}中\n"
|
|
656
|
+
content = content + "# 3、组件不写source时默认使用SOURCES中的第一个源(通常把二进制源放第一位)\n"
|
|
657
|
+
content = content + "# 4、源地址可用YAML锚点定义为变量(&名字),组件下通过 *名字 引用,避免重复书写\n"
|
|
658
|
+
content = content + "# 5、inhibit_warnings 不写默认true(屏蔽远程库警告);configurations/subspecs 为数组且不能同时使用\n\n"
|
|
659
|
+
|
|
660
|
+
content = content + "SOURCES:\n"
|
|
661
|
+
content = content + "# - &binary 这里替换成默认specs源(一般是二进制源)\n"
|
|
662
|
+
content = content + "# - &source 这里替换成备选specs源(如源码源)\n\n"
|
|
663
|
+
|
|
664
|
+
content = content + "PODS: []\n\n"
|
|
665
|
+
|
|
666
|
+
content = content + "# 示例:\n"
|
|
667
|
+
content = content + "# - pod: Masonry # 默认源 + 版本号引用\n"
|
|
668
|
+
content = content + "# version: 1.1.0\n"
|
|
669
|
+
content = content + "# - pod: BSDebugPlugins # 用锚点引用源码源 + 仅Debug集成\n"
|
|
670
|
+
content = content + "# version: 1.0.0-SNAPSHOT\n"
|
|
671
|
+
content = content + "# source: *source\n"
|
|
672
|
+
content = content + "# configurations: [Debug]\n"
|
|
673
|
+
content = content + "# - pod: CocoaService # git分支引用\n"
|
|
674
|
+
content = content + "# git: git@gitee.com:xxx/CocoaService.git\n"
|
|
675
|
+
content = content + "# branch: master\n"
|
|
676
|
+
|
|
677
|
+
File.open(yaml_name, 'w') { |file|
|
|
678
|
+
file.write(content)
|
|
679
|
+
}
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
# 解析路径依赖仓库的真实.git目录。
|
|
683
|
+
# 普通仓库/.git是目录;submodule与worktree下.git是文件(内容为 gitdir: 实际git目录路径)
|
|
684
|
+
# @return [String, ""] 真实.git目录绝对路径,非git仓库返回""
|
|
685
|
+
def resolve_git_dir(path)
|
|
686
|
+
full_path = File.expand_path(path)
|
|
687
|
+
git_path = "#{full_path}/.git"
|
|
688
|
+
return "" unless Dir.exist?(git_path) || File.file?(git_path)
|
|
689
|
+
|
|
690
|
+
return git_path if Dir.exist?(git_path)
|
|
691
|
+
|
|
692
|
+
gitdir = File.read(git_path)[/gitdir:\s*(.+)/, 1]
|
|
693
|
+
return "" if gitdir.to_s.strip.empty?
|
|
694
|
+
|
|
695
|
+
File.expand_path(gitdir.strip, full_path)
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
# 获取某个目录下的当前分支
|
|
699
|
+
def repo_branch(path)
|
|
700
|
+
git_dir = resolve_git_dir(path)
|
|
701
|
+
return "" if git_dir.empty?
|
|
702
|
+
|
|
703
|
+
head_path = "#{git_dir}/HEAD"
|
|
704
|
+
unless File.exist?(head_path)
|
|
705
|
+
return ""
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
content = File.read(head_path)
|
|
709
|
+
content = content.gsub("\n", "")
|
|
710
|
+
|
|
711
|
+
branch_key_word = "ref: refs/heads/"
|
|
712
|
+
if content.include?(branch_key_word)
|
|
713
|
+
branch = content.gsub(branch_key_word, "")
|
|
714
|
+
return "(#{branch})"
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
if content.size >= 8
|
|
718
|
+
short_sha = content.slice(0, 7)
|
|
719
|
+
return "(#{short_sha})"
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
" #(#{content})"
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
# 获取仓库状态是否有修改
|
|
726
|
+
def repo_modify(path)
|
|
727
|
+
git_dir = resolve_git_dir(path)
|
|
728
|
+
return "" if git_dir.empty?
|
|
729
|
+
|
|
730
|
+
# git -C 在子进程中切换目录,避免全局Dir.chdir的副作用;
|
|
731
|
+
# 传仓库工作副本根目录,submodule/worktree由git自身通过.git文件定位真实git目录
|
|
732
|
+
status = %x(git -C #{Shellwords.escape(File.expand_path(path))} status -suno 2>/dev/null)
|
|
733
|
+
status.size > 0 ? "[modified]" : ""
|
|
734
|
+
end
|
|
735
|
+
|
|
736
|
+
# 读取主工程需要集成依赖的target列表
|
|
737
|
+
def init_main_targets
|
|
738
|
+
project_manager = XCProject::XcodeprojManager.share_manager
|
|
739
|
+
@targets = project_manager.get_app_targets.select { |target| integratable_target?(target) }
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
# 是否裁剪未被引用的依赖库(加速编译)
|
|
743
|
+
# 单次选项 pod install/update --trim/--full 优先于 LocalDependencies.yaml 的 TRIM_UNUSED_POD
|
|
744
|
+
def trim_unused_pod?
|
|
745
|
+
yaml_content = read_yaml_content(local_yaml_name)
|
|
746
|
+
return $trim_unused_pod unless $trim_unused_pod.nil?
|
|
747
|
+
yaml_content ? !!(yaml_content["TRIM_UNUSED_POD"]) : false
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
#是否禁用检查podspec文件
|
|
751
|
+
def disable_check_podspec
|
|
752
|
+
yaml_content = read_yaml_content(local_yaml_name)
|
|
753
|
+
disable_check_podspec = yaml_content ? !!(yaml_content["DISABLE_CHECK_PODSPEC"]) : false
|
|
754
|
+
if disable_check_podspec
|
|
755
|
+
log.warn("已关闭podspec文件检查,如需开启,请在#{local_yaml_name}里设置DISABLE_CHECK_PODSPEC为false")
|
|
756
|
+
end
|
|
757
|
+
disable_check_podspec
|
|
758
|
+
end
|
|
759
|
+
|
|
760
|
+
private
|
|
761
|
+
|
|
762
|
+
# 统一日志入口
|
|
763
|
+
def log
|
|
764
|
+
CocoaPods::Yaml.log
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
# 仅TTY下对着色,避免CI/重定向日志出现ANSI转义码
|
|
768
|
+
def colorize(text, color)
|
|
769
|
+
return text.to_s if text.to_s.empty?
|
|
770
|
+
$stdout.tty? ? text.to_s.public_send(color) : text.to_s
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
end
|
|
774
|
+
end
|