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
data/lib/core/logger.rb
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'colored2'
|
|
4
|
+
|
|
5
|
+
module CocoaPods
|
|
6
|
+
module Yaml
|
|
7
|
+
# 统一日志,替代散落各处的 puts/warn。
|
|
8
|
+
#
|
|
9
|
+
# 级别:
|
|
10
|
+
# step 阶段标题(⏩,前置空行)
|
|
11
|
+
# info 普通信息(默认色)
|
|
12
|
+
# detail 细节说明/阶段性完成(-> 前缀);流程内各步骤完成统一用此级别
|
|
13
|
+
# success 成功提示(✅,绿色);仅用于整条命令/流程的最终关键结果,阶段步骤不要用,避免满屏✅
|
|
14
|
+
# warn 警告(⚠️,黄色,不中断流程)
|
|
15
|
+
# error 错误(❌,红色;是否退出由调用方决定)
|
|
16
|
+
# debug 调试细节(· 前缀,仅 pod install --verbose 或 COCOAPODS_YAML_VERBOSE 非空时输出)
|
|
17
|
+
#
|
|
18
|
+
# 说明: warn/error也输出到stdout而非stderr。本插件运行在CocoaPods进程内,
|
|
19
|
+
# 分流到stderr会被CocoaPods自身输出缓冲穿插,导致警告标题与其明细行错位。
|
|
20
|
+
# 仅当输出流是TTY时才着色,避免CI/重定向日志中出现ANSI转义码。
|
|
21
|
+
class Logger
|
|
22
|
+
DEBUG_ENV = 'COCOAPODS_YAML_VERBOSE'
|
|
23
|
+
|
|
24
|
+
def step(message)
|
|
25
|
+
write($stdout, "\n⏩ #{message}", :cyan, true)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def info(message = '', color = nil)
|
|
29
|
+
write($stdout, message.to_s, color)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def detail(message)
|
|
33
|
+
write($stdout, "-> #{message}", :blue)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def success(message)
|
|
37
|
+
write($stdout, "✅ #{message}", :green)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def warn(message)
|
|
41
|
+
write($stdout, "⚠️ #{message}", :yellow)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def error(message)
|
|
45
|
+
write($stdout, "❌ #{message}", :red)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def debug(message)
|
|
49
|
+
write($stdout, "· #{message}", nil) if debug_enabled?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def debug_enabled?
|
|
53
|
+
return true unless ENV.fetch(DEBUG_ENV, '').strip.empty?
|
|
54
|
+
if defined?(Pod::Config)
|
|
55
|
+
begin
|
|
56
|
+
return !!Pod::Config.instance.verbose?
|
|
57
|
+
rescue StandardError
|
|
58
|
+
false
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
false
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# @param io [IO] 输出流
|
|
67
|
+
# @param message [String] 内容
|
|
68
|
+
# @param color [Symbol, nil] 颜色方法(colored2),nil不着色
|
|
69
|
+
# @param bold [Boolean] 是否加粗
|
|
70
|
+
def write(io, message, color, bold = false)
|
|
71
|
+
text = message
|
|
72
|
+
if color && io.respond_to?(:tty?) && io.tty?
|
|
73
|
+
text = text.public_send(color)
|
|
74
|
+
text = text.bold if bold
|
|
75
|
+
end
|
|
76
|
+
io.puts(text)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @return [CocoaPods::Yaml::Logger] 全局日志实例
|
|
81
|
+
def self.log
|
|
82
|
+
@log ||= Logger.new
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'find'
|
|
5
|
+
require 'pathname'
|
|
6
|
+
|
|
7
|
+
module ModifyCode
|
|
8
|
+
# 三方库源码编译兼容补丁
|
|
9
|
+
#
|
|
10
|
+
# 在 pod install/update 的 post_install 阶段,对集成进工程的三方库源码做
|
|
11
|
+
# 不改变功能的微调,使其能在新版 Xcode 上编译。放在 cocoapods-yaml 中执行是为了
|
|
12
|
+
# 同时覆盖两种使用场景,避免“打包机能编译、开发者源码接入却编译失败”:
|
|
13
|
+
# 1. 日常开发的普通 pod 源码接入(Pods/<组件名>)
|
|
14
|
+
# 2. pod-binary-pack 打包期的 :path 开发 pod(源码目录可能在 Pods 之外)
|
|
15
|
+
# 二进制 pod 目录下仅含 .framework/.xcframework,遍历源码时会剪枝跳过,绝不改动已构建产物
|
|
16
|
+
class SourcePatcher
|
|
17
|
+
# 可编译源码后缀
|
|
18
|
+
SOURCE_SUFFIX = %w[.h .m .mm .c .cpp].freeze
|
|
19
|
+
# 设置该环境变量可临时关闭全部源码兼容补丁
|
|
20
|
+
DISABLE_ENV = 'COCOAPODS_YAML_SKIP_SOURCE_PATCH'
|
|
21
|
+
|
|
22
|
+
# 一条兼容规则
|
|
23
|
+
# pod: 组件名;nil 表示对所有组件的源码生效(规则本身必须安全通用)
|
|
24
|
+
# files: 相对组件源码根的精确文件路径数组;nil 表示遍历全部源码文件
|
|
25
|
+
# pattern: String 时字面匹配,Regexp 时可用 \1 \2 反向引用
|
|
26
|
+
# replacement 配合 pattern 使用,日志标签 label 便于定位是哪条兼容规则
|
|
27
|
+
Rule = Struct.new(:pod, :files, :pattern, :replacement, :label, keyword_init: true) do
|
|
28
|
+
def global?
|
|
29
|
+
pod.nil?
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# 规则按声明顺序执行;新增同类兼容处理时在此追加一条即可,不要把逻辑散落各处
|
|
34
|
+
RULES = [
|
|
35
|
+
# 通用规则:新版 Xcode(16+) SDK 将 netinet6/in6.h 划为 Darwin 模块私有头,
|
|
36
|
+
# 且该头内部 #error 明确要求改引公共头 netinet/in.h(AFNetworking 4.0.1 等旧库因此编译失败)
|
|
37
|
+
Rule.new(
|
|
38
|
+
pod: nil,
|
|
39
|
+
files: nil,
|
|
40
|
+
pattern: /^(\s*#\s*(?:import|include)\s*[<"])netinet6\/in6\.h([>"])/,
|
|
41
|
+
replacement: '\\1netinet/in.h\\2',
|
|
42
|
+
label: 'netinet6/in6.h 已废弃,改用公共头 netinet/in.h'
|
|
43
|
+
),
|
|
44
|
+
|
|
45
|
+
# YYText 1.0.7:命中字符簇/emoji 内部时按距离远近吸附到左右边界,
|
|
46
|
+
# 原代码把三元运算符误写成链式比较 a < b < c,Xcode 16+ 将 -Wparentheses 升级为编译错误。
|
|
47
|
+
# 同文件内垂直/水平两种写法各出现 2 次,String 精确替换全部命中
|
|
48
|
+
Rule.new(
|
|
49
|
+
pod: 'YYText',
|
|
50
|
+
files: ['YYText/Component/YYTextLayout.m'],
|
|
51
|
+
pattern: 'position = fabs(left - point.y) < fabs(right - point.y) < (right ? prev : next);',
|
|
52
|
+
replacement: 'position = fabs(left - point.y) < fabs(right - point.y) ? prev : next;',
|
|
53
|
+
label: '修复链式比较 X<Y<Z 为最近边界三元选择'
|
|
54
|
+
),
|
|
55
|
+
Rule.new(
|
|
56
|
+
pod: 'YYText',
|
|
57
|
+
files: ['YYText/Component/YYTextLayout.m'],
|
|
58
|
+
pattern: 'position = fabs(left - point.x) < fabs(right - point.x) < (right ? prev : next);',
|
|
59
|
+
replacement: 'position = fabs(left - point.x) < fabs(right - point.x) ? prev : next;',
|
|
60
|
+
label: '修复链式比较 X<Y<Z 为最近边界三元选择'
|
|
61
|
+
),
|
|
62
|
+
|
|
63
|
+
# FBRetainCycleDetector 历史编译兼容(原 modify_fb 逻辑原样迁移为规则)
|
|
64
|
+
Rule.new(
|
|
65
|
+
pod: 'FBRetainCycleDetector',
|
|
66
|
+
files: ['FBRetainCycleDetector/Layout/Classes/FBClassStrongLayout.mm'],
|
|
67
|
+
pattern: 'layoutCache[currentClass] = ivars;',
|
|
68
|
+
replacement: 'layoutCache[(id<NSCopying>)currentClass] = ivars;',
|
|
69
|
+
label: '修复新版 Xcode 下 layoutCache 下标类型不匹配'
|
|
70
|
+
),
|
|
71
|
+
Rule.new(
|
|
72
|
+
pod: 'FBRetainCycleDetector',
|
|
73
|
+
files: ['fishhook/fishhook.c'],
|
|
74
|
+
pattern: 'indirect_symbol_bindings[i] = cur->rebindings[j].replacement;',
|
|
75
|
+
replacement: 'if(i < (sizeof(indirect_symbol_bindings) /sizeof(indirect_symbol_bindings[0]))) {indirect_symbol_bindings[i]=cur->rebindings[j].replacement;}',
|
|
76
|
+
label: '修复 fishhook 数组越界风险'
|
|
77
|
+
),
|
|
78
|
+
Rule.new(
|
|
79
|
+
pod: 'FBRetainCycleDetector',
|
|
80
|
+
files: ['FBRetainCycleDetector/Graph/FBObjectiveCGraphElement.mm'],
|
|
81
|
+
pattern: '#import <objc/runtime.h>',
|
|
82
|
+
replacement: "#import <objc/runtime.h>\n#import <malloc/malloc.h>",
|
|
83
|
+
label: '补充 malloc/malloc.h 头引用'
|
|
84
|
+
),
|
|
85
|
+
Rule.new(
|
|
86
|
+
pod: 'FBRetainCycleDetector',
|
|
87
|
+
files: ['FBRetainCycleDetector/Graph/FBObjectiveCGraphElement.mm'],
|
|
88
|
+
pattern: 'Class aCls = object_getClass(object);',
|
|
89
|
+
replacement: 'malloc_zone_t *zone = malloc_zone_from_ptr((__bridge void *)object); if (zone) { Class aCls=object_getClass(object);',
|
|
90
|
+
label: 'malloc_zone 增加非空保护'
|
|
91
|
+
),
|
|
92
|
+
Rule.new(
|
|
93
|
+
pod: 'FBRetainCycleDetector',
|
|
94
|
+
files: ['FBRetainCycleDetector/Graph/FBObjectiveCGraphElement.mm'],
|
|
95
|
+
pattern: '#endif',
|
|
96
|
+
replacement: "}\n# endif",
|
|
97
|
+
label: '补齐 malloc_zone 非空保护的闭合括号'
|
|
98
|
+
)
|
|
99
|
+
].freeze
|
|
100
|
+
|
|
101
|
+
# post_install 钩子入口
|
|
102
|
+
# @param sandbox [Pod::Sandbox] post_install 上下文中的沙盒
|
|
103
|
+
def self.patch(sandbox)
|
|
104
|
+
return if ENV.key?(DISABLE_ENV)
|
|
105
|
+
return unless sandbox
|
|
106
|
+
|
|
107
|
+
new(sandbox).patch
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def initialize(sandbox)
|
|
111
|
+
@sandbox = sandbox
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def patch
|
|
115
|
+
roots = pod_source_roots
|
|
116
|
+
return if roots.empty?
|
|
117
|
+
|
|
118
|
+
RULES.each do |rule|
|
|
119
|
+
targets = rule.global? ? roots : roots.select { |name, _| name == rule.pod }
|
|
120
|
+
targets.each do |pod_name, root|
|
|
121
|
+
hit_files = apply_rule(rule, pod_name, root)
|
|
122
|
+
# 兼容补丁可能改变三方库原始行为,实际命中的文件逐条用警告级别记录,
|
|
123
|
+
# 确保默认日志可见、有迹可查;无改动则完全静默
|
|
124
|
+
hit_files.each do |relative|
|
|
125
|
+
CocoaPods::Yaml.log.warn("源码兼容补丁[#{rule.label}]:#{pod_name}/#{relative}")
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
# 收集本次安装实际存在源码的组件根目录:开发 pod(:path) + 普通 pod(Pods/<名>)
|
|
134
|
+
# @return [Hash{String => String}] 组件名 => 源码根绝对路径
|
|
135
|
+
def pod_source_roots
|
|
136
|
+
roots = {}
|
|
137
|
+
|
|
138
|
+
# 组件名取 Manifest.lock 实际安装列表 + 开发 pod(:path)列表(后者兜底 Manifest 缺失场景)。
|
|
139
|
+
# 注意:development_pods 存的是 podspec 文件路径(如 /path/YYText.podspec.json)而非源码目录,
|
|
140
|
+
# 不能直接遍历它;统一走 sandbox.pod_dir —— 开发 pod 内部取 dirname、普通 pod 取 Pods/<名>
|
|
141
|
+
names = []
|
|
142
|
+
manifest = @sandbox.respond_to?(:manifest) ? @sandbox.manifest : nil
|
|
143
|
+
names.concat(manifest.pod_names) if manifest
|
|
144
|
+
names.concat(@sandbox.development_pods.keys)
|
|
145
|
+
names.uniq.each do |name|
|
|
146
|
+
dir = @sandbox.pod_dir(name)
|
|
147
|
+
roots[name] = dir.to_s if dir&.directory?
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
roots
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @return [Array<String>] 实际被改动文件的相对路径
|
|
154
|
+
def apply_rule(rule, pod_name, root)
|
|
155
|
+
files =
|
|
156
|
+
if rule.files
|
|
157
|
+
rule.files.map { |rel| File.join(root, rel) }.select { |f| File.file?(f) }
|
|
158
|
+
else
|
|
159
|
+
source_files(root)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
files.filter_map do |file|
|
|
163
|
+
relative = file.sub("#{root}/", '')
|
|
164
|
+
relative if replace_in_file(file, rule)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# 遍历组件根下全部源码文件,剪枝 .git 与 .framework/.xcframework 等产物目录
|
|
169
|
+
def source_files(root)
|
|
170
|
+
result = []
|
|
171
|
+
Find.find(root) do |path|
|
|
172
|
+
base = File.basename(path)
|
|
173
|
+
if File.directory?(path)
|
|
174
|
+
# .framework/.xcframework 是目录名后缀(如 BinKit.framework),.git 为精确目录名
|
|
175
|
+
Find.prune if base == '.git' || base.end_with?('.framework', '.xcframework')
|
|
176
|
+
elsif File.file?(path) && SOURCE_SUFFIX.include?(File.extname(path).downcase)
|
|
177
|
+
result << path
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
result
|
|
181
|
+
rescue Errno::ENOENT
|
|
182
|
+
[]
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# @return [Boolean] 文件是否实际发生了改动
|
|
186
|
+
def replace_in_file(file, rule)
|
|
187
|
+
# 二进制方式读写:源码文件不一定是 UTF-8,按字节处理避免转码崩溃
|
|
188
|
+
content = File.binread(file)
|
|
189
|
+
updated =
|
|
190
|
+
if rule.pattern.is_a?(Regexp)
|
|
191
|
+
content.gsub(rule.pattern, rule.replacement)
|
|
192
|
+
else
|
|
193
|
+
# String 模式:替换内容原样使用,避免反斜杠被当成反向引用
|
|
194
|
+
content.gsub(rule.pattern) { rule.replacement }
|
|
195
|
+
end
|
|
196
|
+
return false if updated == content
|
|
197
|
+
|
|
198
|
+
# 三方库源码文件可能只读(历史上有此情况),先确保可写
|
|
199
|
+
FileUtils.chmod('u+w', file)
|
|
200
|
+
File.binwrite(file, updated)
|
|
201
|
+
true
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module MyConstants
|
|
3
|
+
|
|
4
|
+
# 本地依赖覆盖文件(全局唯一,不提交git)
|
|
5
|
+
LOCAL_DEPENDENCIES_YAML = 'LocalDependencies.yaml'
|
|
6
|
+
# 每个target的依赖文件后缀: <TargetName>Dependencies.yaml
|
|
7
|
+
DEPENDENCIES_YAML_SUFFIX = 'Dependencies.yaml'
|
|
8
|
+
# 打包进bundle的依赖清单后缀(仅Debug): <TargetName>Dependencies.json
|
|
9
|
+
DEPENDENCIES_JSON_SUFFIX = 'Dependencies.json'
|
|
10
|
+
PODFILE_LOCK = 'Podfile.lock'
|
|
11
|
+
PODFILE = 'Podfile'
|
|
12
|
+
|
|
13
|
+
LIBLIBARYS = %w[time removefile AppleTextureEncoder standards AvailabilityMacros semaphore SystemHealthClient dns_sd ftw checkint pwd bitstring utime err CommonCrypto inttypes resolv c++ _ctermid stdlib sysdir unwind fts vis AvailabilityVersions membership libxml2 ulimit _stdio ntsid net notify_keys CMakeLists.txt AssertMacros float aio libxml ConditionalMacros locale fmtmsg unicode _wctype cpio langinfo xlocale limits Endian unwind_itanium _types db bank _ctype unistd stddef dispatch wctype fcntl mach_debug Availability stringlist tar readpassphrase _locale ttyent bzlib cache signal dirent mpool libDER unwind_arm_ehabi spawn crt_externs simd regex AvailabilityInternal arpa syslog bsm netinet libunwind libgen corpses rpcsvc monetary compression expat_external setjmp tgmath notify getopt _regex dlfcn strings TargetConditionals fnmatch pthread xlocale execinfo networkext printf architecture alloca sys iconv fenv expat grp secure ctype fstab pthread_spis machine SystemHealthManager voucher wordexp zlib nl_types paths AppleEXR wchar sqlite3 util gethostuuid sched sqlite3ext iso646 Block netdb pthread math memory asl __wctype mach errno device termios copyfile os xattr_flags poll _xlocale stdio mach-o malloc string_x86 arm Spatial __libunwind_config nameser dns _types rpc netinet6 pthread_impl objc MacTypes libkern uuid search assert AppleArchive cache_callbacks dns_util glob ifaddrs utmpx sandbox arm64 stdint complex runetype zconf ucontext sysexits string ndbm]
|
|
14
|
+
FRAMEWORKS = %w[MetricKit Network GameKit AddressBookUI CreateML Speech SafariServices ProximityReader Metal AppClip QuartzCore CoreGraphics StoreKit GSS CoreML WatchConnectivity UserNotificationsUI ARKit CoreLocationUI MetalPerformanceShaders AutomaticAssessmentConfiguration ExternalAccessory MediaPlayer LinkPresentation IdentityLookupUI MediaToolbox UIKit MessageUI MetalFX CoreNFC HomeKit CryptoKit ScreenTime NewsstandKit MLCompute NaturalLanguage CoreVideo MetalKit OSLog PDFKit CoreText UniformTypeIdentifiers IOKit ManagedSettings AVFoundation Accelerate DeviceCheck ImageIO BackgroundTasks FamilyControls ClockKit CoreBluetooth ThreadNetwork SensorKit Security Contacts CarPlay ExtensionFoundation RealityKit PhotosUI RoomPlan ReplayKit IOSurface SpriteKit MobileCoreServices IntentsUI QuickLookThumbnailing CoreMedia BusinessChat Intents ColorSync VideoSubscriberAccount PencilKit ManagedSettingsUI CoreServices HealthKit MultipeerConnectivity BackgroundAssets WebKit NotificationCenter SystemConfiguration GameController CoreTelephony ActivityKit AVFAudio AssetsLibrary AudioUnit FileProvider DeviceActivity Social IdentityLookup Matter CoreImage CoreAudio MusicKit DeviceDiscoveryExtension MediaSetup AutomatedDeviceEnrollment CoreAudioKit ExposureNotification ClassKit VisionKit SwiftUI Combine ModelIO PHASE SharedWithYou OpenGLES MetalPerformanceShadersGraph CreateMLComponents Accounts FileProviderUI ShazamKit CarKey RealityFoundation QuickLook AppIntents AuthenticationServices WeatherKit DataDetection AudioToolbox NearbyInteraction ContactsUI CoreSpotlight LocalAuthentication MatterSupport Foundation AdSupport LocalAuthenticationEmbeddedUI Accessibility HealthKitUI PushKit Vision CoreAudioTypes NetworkExtension ExtensionKit OpenAL EventKit MediaAccessibility Charts iAd MapKit AppTrackingTransparency CoreHaptics CallKit CoreFoundation TabularData CoreMotion AVRouting WidgetKit AVKit AddressBook SoundAnalysis GroupActivities CoreLocation CloudKit SharedWithYouCore CFNetwork Photos JavaScriptCore GameplayKit PushToTalk CoreMIDI Twitter PassKit ImageCaptureCore CoreData GLKit AdServices Messages CryptoTokenKit VideoToolbox SafetyKit UserNotifications DeveloperToolsSupport CoreTransferable EventKitUI SceneKit]
|
|
15
|
+
|
|
16
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require_relative 'my_constants'
|
|
2
|
+
require 'yaml'
|
|
3
|
+
|
|
4
|
+
module PodfileLock
|
|
5
|
+
class PodfileLockManager
|
|
6
|
+
# 读取podfile.lock
|
|
7
|
+
def self.podfile_lock_content
|
|
8
|
+
podfile_lock = MyConstants::PODFILE_LOCK
|
|
9
|
+
|
|
10
|
+
unless File.exist?(podfile_lock)
|
|
11
|
+
return {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
YAML.load_file(podfile_lock)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require_relative '../check/xcodeproj'
|
|
3
|
+
require_relative '../my_constants'
|
|
4
|
+
require_relative '../yaml/yaml_dependencies'
|
|
5
|
+
|
|
6
|
+
module Project
|
|
7
|
+
class ProjectManager
|
|
8
|
+
|
|
9
|
+
# 生成Debug包内的依赖清单json,并注入到对应target的resources脚本中(仅Debug)
|
|
10
|
+
# @param hook_context post_install钩子上下文:
|
|
11
|
+
# 插件hook拿到的是Pod::PostInstallHooksContext(umbrella_targets),
|
|
12
|
+
# Podfile里直接拿到的是Pod::Installer(aggregate_targets),两种都兼容
|
|
13
|
+
def self.dependency_manifest(hook_context)
|
|
14
|
+
CocoaPods::Yaml.log.step("依赖清单json生成(仅Debug入包)")
|
|
15
|
+
|
|
16
|
+
yaml_dep = Pod::YamlDependencies.new
|
|
17
|
+
records = yaml_dep.build_dependency_records
|
|
18
|
+
|
|
19
|
+
# 各target实际安装的组件根名(TRIM_UNUSED_POD裁剪后为真实子集)
|
|
20
|
+
installed_map = extract_installed_map(hook_context)
|
|
21
|
+
if installed_map.empty?
|
|
22
|
+
CocoaPods::Yaml.log.warn("未获取到target实际安装信息,跳过依赖清单生成")
|
|
23
|
+
return
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
records.each do |target_name, pod_records|
|
|
27
|
+
support_dir = "#{Dir.pwd}/Pods/Target Support Files/Pods-#{target_name}"
|
|
28
|
+
next unless File.directory?(support_dir)
|
|
29
|
+
|
|
30
|
+
installed = installed_map[target_name]
|
|
31
|
+
# 聚合target不存在(该target没有任何pod)时不产出清单
|
|
32
|
+
next if installed.nil?
|
|
33
|
+
|
|
34
|
+
# 按组件根名去重(子组件只保留一条),并过滤掉未实际安装的组件
|
|
35
|
+
merged_records = []
|
|
36
|
+
did_merge = {}
|
|
37
|
+
pod_records.each do |record|
|
|
38
|
+
root_name = record['pod'].to_s.split('/').first
|
|
39
|
+
next unless installed.include?(root_name)
|
|
40
|
+
next if did_merge[root_name]
|
|
41
|
+
did_merge[root_name] = true
|
|
42
|
+
merged_records << record
|
|
43
|
+
end
|
|
44
|
+
merged_records.sort_by! { |record| record['pod'].to_s.downcase }
|
|
45
|
+
|
|
46
|
+
json_name = yaml_dep.target_json_name(target_name)
|
|
47
|
+
manifest = {
|
|
48
|
+
'target' => target_name,
|
|
49
|
+
'cocoapods-yaml' => CocoaPods::Yaml::VERSION,
|
|
50
|
+
'pods' => merged_records,
|
|
51
|
+
}
|
|
52
|
+
json_path = File.join(support_dir, json_name)
|
|
53
|
+
File.open(json_path, 'w') { |file| file.write(JSON.pretty_generate(manifest)) }
|
|
54
|
+
|
|
55
|
+
resource_shell_path = File.join(support_dir, "Pods-#{target_name}-resources.sh")
|
|
56
|
+
inject_manifest_resource(resource_shell_path, target_name, json_name)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# 从两种钩子上下文中提取 target名 => 实际安装组件根名列表
|
|
61
|
+
# Pod::PostInstallHooksContext(插件hook)用umbrella_targets;
|
|
62
|
+
# Pod::Installer(Podfile内post_install)用aggregate_targets
|
|
63
|
+
def self.extract_installed_map(hook_context)
|
|
64
|
+
installed_map = {}
|
|
65
|
+
return installed_map unless hook_context
|
|
66
|
+
|
|
67
|
+
if hook_context.respond_to?(:umbrella_targets)
|
|
68
|
+
hook_context.umbrella_targets.each do |umbrella|
|
|
69
|
+
label = umbrella.cocoapods_target_label.to_s
|
|
70
|
+
target_name = label.sub(/^Pods-/, '')
|
|
71
|
+
installed_map[target_name] = umbrella.specs.map { |spec| spec.root.name }.uniq
|
|
72
|
+
end
|
|
73
|
+
elsif hook_context.respond_to?(:aggregate_targets)
|
|
74
|
+
hook_context.aggregate_targets.each do |aggregate_target|
|
|
75
|
+
target_name = aggregate_target.name.sub(/^Pods-/, '')
|
|
76
|
+
installed_map[target_name] = aggregate_target.specs.map { |spec| spec.root.name }.uniq
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
installed_map
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# 仅往Debug块注入清单资源,Release包不带任何依赖清单
|
|
83
|
+
def self.inject_manifest_resource(file_path, target_name, json_name)
|
|
84
|
+
return unless File.exist?(file_path)
|
|
85
|
+
|
|
86
|
+
content = IO.read(file_path)
|
|
87
|
+
return if content.include?(json_name)
|
|
88
|
+
|
|
89
|
+
resource_line = " install_resource \"${PODS_ROOT}/Target Support Files/Pods-#{target_name}/#{json_name}\"\n"
|
|
90
|
+
debug_marker = 'if [[ "$CONFIGURATION" == "Debug" ]]; then'
|
|
91
|
+
|
|
92
|
+
lines = content.lines
|
|
93
|
+
inserted = false
|
|
94
|
+
lines.each_with_index do |line, index|
|
|
95
|
+
next unless line.include?(debug_marker)
|
|
96
|
+
lines.insert(index + 1, resource_line)
|
|
97
|
+
inserted = true
|
|
98
|
+
break
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
unless inserted
|
|
102
|
+
CocoaPods::Yaml.log.warn("未找到Debug配置块,#{json_name}未注入(#{File.basename(file_path)})")
|
|
103
|
+
return
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
File.open(file_path, 'w') { |file| file.write(lines.join) }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# 将yaml文件加入Pods工程引用(仅为Xcode内可见可编辑),并把本地覆盖文件加入git忽略
|
|
111
|
+
def self.yaml_reference
|
|
112
|
+
CocoaPods::Yaml.log.step("yaml文件引用添加")
|
|
113
|
+
self.add_yaml
|
|
114
|
+
self.add_local_yaml_ignore
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def self.add_yaml
|
|
118
|
+
yaml_dep = Pod::YamlDependencies.new
|
|
119
|
+
project_manager = XCProject::XcodeprojManager.share_manager
|
|
120
|
+
pods_project = project_manager.get_pods_project
|
|
121
|
+
return unless pods_project
|
|
122
|
+
|
|
123
|
+
yaml_files = dependency_yaml_files(yaml_dep, project_manager)
|
|
124
|
+
|
|
125
|
+
did_add = []
|
|
126
|
+
pods_project.main_group.children.each {|child|
|
|
127
|
+
did_add << child.display_name
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
yaml_files.each do |yaml_file|
|
|
131
|
+
unless did_add.include?(yaml_file)
|
|
132
|
+
path = "#{Dir.pwd}/#{yaml_file}"
|
|
133
|
+
pods_project.new_file(path)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
pods_project.sort(:groups_position => :below)
|
|
138
|
+
|
|
139
|
+
# 依赖相关文件固定排在最前: Podfile、LocalDependencies.yaml、各target依赖文件(按工程target顺序)
|
|
140
|
+
preferred_names = ['Podfile'] + yaml_files
|
|
141
|
+
reorder_preferred_files(pods_project, preferred_names)
|
|
142
|
+
|
|
143
|
+
pods_project.save
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# 将指定名称的文件引用移动到main_group最前面(保持preferred_names给定的顺序),其余文件顺序不变
|
|
147
|
+
def self.reorder_preferred_files(pods_project, preferred_names)
|
|
148
|
+
group = pods_project.main_group
|
|
149
|
+
children = group.children
|
|
150
|
+
preferred = preferred_names.map { |name| children.find { |child| child.display_name == name } }.compact
|
|
151
|
+
return if preferred.empty?
|
|
152
|
+
group.children.replace(preferred + (children - preferred))
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# 需要在Pods工程中可见的yaml文件: 本地覆盖文件 + 各target依赖文件
|
|
156
|
+
def self.dependency_yaml_files(yaml_dep, project_manager)
|
|
157
|
+
files = []
|
|
158
|
+
local_name = yaml_dep.local_yaml_name
|
|
159
|
+
files << local_name if File.exist?(local_name)
|
|
160
|
+
|
|
161
|
+
project_manager.get_app_targets.each do |target|
|
|
162
|
+
next unless yaml_dep.integratable_target?(target)
|
|
163
|
+
yaml_name = yaml_dep.target_yaml_name(target.name)
|
|
164
|
+
files << yaml_name if File.exist?(yaml_name)
|
|
165
|
+
end
|
|
166
|
+
files
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# 把本地覆盖文件添加到忽略文件中
|
|
170
|
+
def self.add_local_yaml_ignore
|
|
171
|
+
|
|
172
|
+
gitignore = ".gitignore"
|
|
173
|
+
local_yaml = MyConstants::LOCAL_DEPENDENCIES_YAML
|
|
174
|
+
|
|
175
|
+
contain = false
|
|
176
|
+
gitignore_path = "#{Dir.pwd}/#{gitignore}"
|
|
177
|
+
if File.exist?(gitignore_path)
|
|
178
|
+
safe_readlines(gitignore_path).each do |line|
|
|
179
|
+
# include?兼容已有条目带/前缀、子目录路径、行尾注释等不同形态,避免重复追加
|
|
180
|
+
contain = true if line.include?(local_yaml)
|
|
181
|
+
break if contain
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
unless contain
|
|
186
|
+
File.open(gitignore_path, 'a+') { |file|
|
|
187
|
+
file.write("\n#{local_yaml}\n")
|
|
188
|
+
}
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# 容错读取文件行:历史工程的.gitignore可能是CRLF行尾或GBK等非UTF-8编码。
|
|
193
|
+
# 直接给IO.readlines传encoding: 'utf-8'在内外编码相同时不会转码,非法字节仍会透传,
|
|
194
|
+
# 先按二进制读取再显式做一次同编码转换替换非法字节,避免后续处理抛invalid byte sequence
|
|
195
|
+
def self.safe_readlines(file_path)
|
|
196
|
+
content = File.read(file_path, mode: 'rb')
|
|
197
|
+
content.encode!('UTF-8', 'UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
|
198
|
+
content.lines
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require_relative '../my_constants'
|
|
2
|
+
require_relative '../yaml/yaml_dependencies'
|
|
3
|
+
|
|
4
|
+
module Unused
|
|
5
|
+
class UnusedManager
|
|
6
|
+
|
|
7
|
+
# 打印未被引用的依赖库
|
|
8
|
+
def self.check_unused
|
|
9
|
+
|
|
10
|
+
yaml_dep = Pod::YamlDependencies.new
|
|
11
|
+
log = CocoaPods::Yaml.log
|
|
12
|
+
|
|
13
|
+
if yaml_dep.trim_unused_pod?
|
|
14
|
+
msg = "💚 当前已开启未使用依赖裁剪,请关注上方日志 '下载依赖...'部分内容。"
|
|
15
|
+
msg += "\n #{MyConstants::LOCAL_DEPENDENCIES_YAML}文件内的依赖库不会被裁剪。如需关闭,有2种方式任选其一:"
|
|
16
|
+
msg += "\n 1. 在#{MyConstants::LOCAL_DEPENDENCIES_YAML}中把最外层 TRIM_UNUSED_POD 设置为false,然后重新pod install"
|
|
17
|
+
msg += "\n 2. 执行 pod install/update --full 可单次关闭"
|
|
18
|
+
log.info(msg, :green)
|
|
19
|
+
else
|
|
20
|
+
msg = "💛 检测到你未开启未使用依赖裁剪。开发模式下可开启,移除未被引用的依赖库以加速编译,有2种方式任选其一:"
|
|
21
|
+
msg += "\n 1. 在#{MyConstants::LOCAL_DEPENDENCIES_YAML}中把最外层 TRIM_UNUSED_POD 设置为true,然后重新pod install"
|
|
22
|
+
msg += "\n 2. 执行 pod install/update --trim 可单次开启"
|
|
23
|
+
log.info(msg, :yellow)
|
|
24
|
+
end
|
|
25
|
+
log.info('')
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'time'
|
|
5
|
+
require_relative '../yaml/yaml_dependencies'
|
|
6
|
+
|
|
7
|
+
module CocoaPods
|
|
8
|
+
module Yaml
|
|
9
|
+
# Podfile初始化: 生成接入cocoapods-yaml的模板Podfile
|
|
10
|
+
class PodfileSetup
|
|
11
|
+
TEMPLATE = <<~'PODFILE'
|
|
12
|
+
# frozen_string_literal: true
|
|
13
|
+
# cocoapods-yaml 插件会被CocoaPods自动加载,无需require;
|
|
14
|
+
# 若 pod 命令提示找不到 yaml_dependencies!,请执行: gem install cocoapods-yaml
|
|
15
|
+
|
|
16
|
+
platform :ios, '15.0'
|
|
17
|
+
|
|
18
|
+
yaml_dependencies! do |target_name|
|
|
19
|
+
# target_name: 当前集成的target名称(App/扩展),每个声明了依赖的target回调一次
|
|
20
|
+
# 可按target定制,例如:
|
|
21
|
+
# use_frameworks! :linkage => :static if target_name == 'ShareExtension'
|
|
22
|
+
use_frameworks!
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
post_install do |installer|
|
|
26
|
+
installer.pods_project.targets.each do |target|
|
|
27
|
+
target.build_configurations.each do |config|
|
|
28
|
+
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '$(inherited)'
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
PODFILE
|
|
33
|
+
|
|
34
|
+
# pod yaml init: 显式初始化
|
|
35
|
+
# 无Podfile直接生成;已有Podfile但未接入时先备份再替换
|
|
36
|
+
def self.run
|
|
37
|
+
setup = new
|
|
38
|
+
unless setup.xcodeproj?
|
|
39
|
+
CocoaPods::Yaml.log.error('当前目录未找到.xcodeproj工程文件,请在iOS工程根目录执行 pod yaml init')
|
|
40
|
+
exit 1
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if File.exist?('Podfile')
|
|
44
|
+
if setup.integrated?
|
|
45
|
+
CocoaPods::Yaml.log.info('当前工程已接入cocoapods-yaml,无需初始化')
|
|
46
|
+
return
|
|
47
|
+
end
|
|
48
|
+
setup.backup_podfile
|
|
49
|
+
setup.write_template
|
|
50
|
+
else
|
|
51
|
+
setup.write_template
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
setup.generate_yamls
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# pod install/update 时自动初始化: 仅限无Podfile的零风险场景
|
|
58
|
+
# 已有Podfile但未接入时只引导,不自动替换
|
|
59
|
+
def self.auto_on_install
|
|
60
|
+
setup = new
|
|
61
|
+
return unless setup.xcodeproj?
|
|
62
|
+
|
|
63
|
+
if File.exist?('Podfile')
|
|
64
|
+
unless setup.integrated?
|
|
65
|
+
CocoaPods::Yaml.log.warn('检测到Podfile未接入cocoapods-yaml,执行 pod yaml init 可备份后生成模板(不会自动替换现有Podfile)')
|
|
66
|
+
end
|
|
67
|
+
return
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
setup.write_template
|
|
71
|
+
CocoaPods::Yaml.log.detail('未找到Podfile,已自动生成接入cocoapods-yaml的模板Podfile,依赖yaml文件将在解析时生成')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# 当前目录是否存在xcodeproj
|
|
75
|
+
def xcodeproj?
|
|
76
|
+
Dir.glob('*.xcodeproj').any?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Podfile是否已接入yaml_dependencies!
|
|
80
|
+
def integrated?
|
|
81
|
+
File.read('Podfile').include?('yaml_dependencies!')
|
|
82
|
+
rescue StandardError
|
|
83
|
+
false
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def backup_podfile
|
|
87
|
+
backup_name = "Podfile.bak.#{Time.now.strftime('%Y%m%d%H%M%S')}"
|
|
88
|
+
FileUtils.cp('Podfile', backup_name)
|
|
89
|
+
CocoaPods::Yaml.log.detail("已备份原Podfile为: #{backup_name}")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def write_template
|
|
93
|
+
File.write('Podfile', TEMPLATE)
|
|
94
|
+
CocoaPods::Yaml.log.detail('已生成模板Podfile')
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# 生成本地覆盖文件与各target依赖文件模板
|
|
98
|
+
def generate_yamls
|
|
99
|
+
yaml_dep = Pod::YamlDependencies.new
|
|
100
|
+
yaml_dep.init_main_targets
|
|
101
|
+
yaml_dep.generate_yaml
|
|
102
|
+
rescue StandardError => e
|
|
103
|
+
CocoaPods::Yaml.log.warn("依赖yaml模板生成失败(不影响已生成的Podfile): #{e.message}")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|