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,145 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'shellwords'
|
|
3
|
+
require 'tempfile'
|
|
4
|
+
|
|
5
|
+
module Xcodeproj
|
|
6
|
+
|
|
7
|
+
# 模拟器架构兼容检查:扫描已安装的二进制依赖(xcframework / fat framework / 静态库.a),
|
|
8
|
+
# 找出模拟器产物不含 arm64 slice 的组件。Apple Silicon 上 Xcode 默认按 arm64 编译模拟器
|
|
9
|
+
# 目标,这类组件无法参与链接(报 framework not found),只能 Rosetta 运行或重新打包支持。
|
|
10
|
+
# 源码编译型组件(无预编译二进制)天然支持,不在检查范围
|
|
11
|
+
class SimulatorArchChecker
|
|
12
|
+
|
|
13
|
+
# Pods目录下非组件安装目录(跳过不扫描)
|
|
14
|
+
SKIP_PODS_DIR_NAMES = ['Headers', 'Local Podspecs', 'Manifest.lock', 'Pods.xcodeproj', 'Target Support Files'].freeze
|
|
15
|
+
|
|
16
|
+
def check
|
|
17
|
+
pods_dir = File.join(Dir.pwd, 'Pods')
|
|
18
|
+
unless File.directory?(pods_dir)
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
log.step('模拟器架构检查中...')
|
|
23
|
+
start = (Time.now.to_f * 1000).to_i
|
|
24
|
+
|
|
25
|
+
rosetta = []
|
|
26
|
+
no_sim = []
|
|
27
|
+
checked = 0
|
|
28
|
+
|
|
29
|
+
Dir.children(pods_dir).sort.each do |name|
|
|
30
|
+
next if SKIP_PODS_DIR_NAMES.include?(name)
|
|
31
|
+
pod_dir = File.join(pods_dir, name)
|
|
32
|
+
next unless File.directory?(pod_dir)
|
|
33
|
+
|
|
34
|
+
status = check_pod_dir(pod_dir)
|
|
35
|
+
next if status == :none
|
|
36
|
+
checked += 1
|
|
37
|
+
if status == :rosetta
|
|
38
|
+
rosetta << name
|
|
39
|
+
elsif status == :no_sim
|
|
40
|
+
no_sim << name
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if rosetta.any?
|
|
45
|
+
log.warn('以下组件的模拟器产物不含 arm64 slice,Apple Silicon 上按默认 arm64 编译模拟器会链接失败(报 framework not found):')
|
|
46
|
+
log.warn(" #{rosetta.join('、')}")
|
|
47
|
+
log.warn('建议用打包工具重新发布这些组件以支持 arm64 模拟器;临时可在工程设置 EXCLUDED_ARCHS=arm64 用 Rosetta 模式运行')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if no_sim.any?
|
|
51
|
+
log.warn('以下组件完全不含模拟器 slice,无法在模拟器上编译链接(仅真机可用):')
|
|
52
|
+
log.warn(" #{no_sim.join('、')}")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
duration = (((Time.now.to_f * 1000).to_i - start) * 0.001).round(2)
|
|
56
|
+
log.info("模拟器架构检查完毕! 共检查#{checked}个含二进制产物的组件(不支持arm64模拟器 #{rosetta.size}个) 耗时:#{duration}秒")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def log
|
|
62
|
+
CocoaPods::Yaml.log
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [:ok, :rosetta, :no_sim, :none] 组件内预编译二进制的模拟器支持情况(取最差结果);
|
|
66
|
+
# :none 表示目录内没有预编译二进制(源码编译型组件)
|
|
67
|
+
def check_pod_dir(pod_dir)
|
|
68
|
+
worst = :none
|
|
69
|
+
each_binary(pod_dir) do |kind, path|
|
|
70
|
+
status = classify_binary(kind, path)
|
|
71
|
+
next if status == :none
|
|
72
|
+
worst = status if worst == :none || (worst == :rosetta && status == :no_sim)
|
|
73
|
+
end
|
|
74
|
+
worst
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# 遍历组件目录内的预编译二进制:xcframework / fat framework / 静态库.a
|
|
78
|
+
def each_binary(pod_dir)
|
|
79
|
+
Dir.glob(File.join(pod_dir, '**', '*.xcframework')).sort.each do |path|
|
|
80
|
+
yield(:xcframework, path)
|
|
81
|
+
end
|
|
82
|
+
Dir.glob(File.join(pod_dir, '**', '*.framework')).sort.reject { |path| path.include?('.xcframework/') }.each do |framework|
|
|
83
|
+
binary = File.join(framework, File.basename(framework, '.framework'))
|
|
84
|
+
yield(:framework, binary) if File.file?(binary)
|
|
85
|
+
end
|
|
86
|
+
Dir.glob(File.join(pod_dir, '**', '*.a')).sort.reject { |path| path.include?('.xcframework/') }.each do |path|
|
|
87
|
+
yield(:static, path)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @return [:ok, :rosetta, :no_sim, :none] :none表示非Mach-O或无Info.plist,跳过不计
|
|
92
|
+
def classify_binary(kind, path)
|
|
93
|
+
return xcframework_status(path) if kind == :xcframework
|
|
94
|
+
|
|
95
|
+
archs = `lipo -archs #{Shellwords.escape(path)} 2>/dev/null`.split
|
|
96
|
+
return :none if archs.empty?
|
|
97
|
+
|
|
98
|
+
if archs.include?('arm64')
|
|
99
|
+
return :ok if arm64_supports_simulator?(path)
|
|
100
|
+
return :rosetta if archs.include?('x86_64')
|
|
101
|
+
:no_sim
|
|
102
|
+
else
|
|
103
|
+
archs.include?('x86_64') ? :rosetta : :no_sim
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# xcframework 的模拟器slice架构;Info.plist缺失或解析失败返回nil(跳过不计)
|
|
108
|
+
def xcframework_status(xcframework_path)
|
|
109
|
+
archs = xcframework_simulator_archs(xcframework_path)
|
|
110
|
+
return :none if archs.nil?
|
|
111
|
+
return :no_sim if archs.empty?
|
|
112
|
+
return :ok if archs.include?('arm64')
|
|
113
|
+
archs.include?('x86_64') ? :rosetta : :no_sim
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# 解析xcframework的Info.plist,返回模拟器slice支持的架构数组(无模拟器slice返回空数组)
|
|
117
|
+
def xcframework_simulator_archs(xcframework_path)
|
|
118
|
+
plist_path = File.join(xcframework_path, 'Info.plist')
|
|
119
|
+
return nil unless File.exist?(plist_path)
|
|
120
|
+
|
|
121
|
+
json = `plutil -convert json -o - #{Shellwords.escape(plist_path)} 2>/dev/null`
|
|
122
|
+
plist = JSON.parse(json) rescue nil
|
|
123
|
+
libraries = plist.is_a?(Hash) ? plist['AvailableLibraries'] : nil
|
|
124
|
+
return nil unless libraries.is_a?(Array)
|
|
125
|
+
|
|
126
|
+
libraries.select { |lib| lib['SupportedPlatformVariant'] == 'simulator' }
|
|
127
|
+
.flat_map { |lib| Array(lib['SupportedArchitectures']) }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# arm64 slice 是否支持模拟器:现代 arm64-simulator 目标必带 LC_BUILD_VERSION 且
|
|
131
|
+
# platform 为 7(iOSSimulator);其余情况(LC_VERSION_MIN_IPHONEOS 或 platform=iOS)均为真机
|
|
132
|
+
def arm64_supports_simulator?(binary)
|
|
133
|
+
Tempfile.create(['arm64_slice', '.a']) do |tmp|
|
|
134
|
+
tmp.close
|
|
135
|
+
unless system("lipo #{Shellwords.escape(binary)} -thin arm64 -output #{Shellwords.escape(tmp.path)}",
|
|
136
|
+
out: File::NULL, err: File::NULL)
|
|
137
|
+
return false
|
|
138
|
+
end
|
|
139
|
+
load_info = `otool -l #{Shellwords.escape(tmp.path)} 2>/dev/null`
|
|
140
|
+
load_info.include?('LC_BUILD_VERSION') && load_info.match?(/\bplatform\s+7\b/)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Xcodeproj
|
|
2
|
+
class Util
|
|
3
|
+
#读取podspec内容
|
|
4
|
+
# @return [Hash]
|
|
5
|
+
# @param podspec_path::[Pathname]
|
|
6
|
+
def self.get_podspec_content(podspec_path)
|
|
7
|
+
|
|
8
|
+
podspec_json_path = podspec_path.to_s.end_with?(".podspec.json") ? podspec_path : nil
|
|
9
|
+
|
|
10
|
+
podspec_content = nil
|
|
11
|
+
if podspec_json_path and File.exist?(podspec_json_path)
|
|
12
|
+
json = File.read(podspec_json_path)
|
|
13
|
+
podspec_content = JSON.parse(json)
|
|
14
|
+
elsif podspec_path and File.exist?(podspec_path)
|
|
15
|
+
json = get_podspec_file_content(podspec_path)
|
|
16
|
+
podspec_content = JSON.parse(json)
|
|
17
|
+
end
|
|
18
|
+
podspec_content
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# 根据podspec路径把podspec转为json
|
|
22
|
+
def self.get_podspec_file_content(podspec_path)
|
|
23
|
+
|
|
24
|
+
spec_contents = File.open(podspec_path, 'r:utf-8', &:read)
|
|
25
|
+
|
|
26
|
+
if spec_contents.respond_to?(:encoding) && spec_contents.encoding.name != 'UTF-8'
|
|
27
|
+
spec_contents.encode!('UTF-8')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
path = Pathname.new(podspec_path)
|
|
31
|
+
|
|
32
|
+
spec = ::Pod._eval_podspec(spec_contents, path)
|
|
33
|
+
|
|
34
|
+
spec.to_json
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require 'xcodeproj'
|
|
2
|
+
require 'pathname'
|
|
3
|
+
|
|
4
|
+
module XCProject
|
|
5
|
+
|
|
6
|
+
class XcodeprojManager
|
|
7
|
+
|
|
8
|
+
# 缓存某个的依赖库
|
|
9
|
+
# {{"module_nameA":["module_nameA", module_nameB]},}
|
|
10
|
+
attr_reader :module_frameworks
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@module_frameworks = {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# 单例方法
|
|
17
|
+
@@instance = XcodeprojManager.new
|
|
18
|
+
|
|
19
|
+
# 获取单例
|
|
20
|
+
def self.share_manager
|
|
21
|
+
@@instance
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private_class_method :new
|
|
25
|
+
|
|
26
|
+
# 壳工程xcodeproj文件名(取目录下第一个匹配项)
|
|
27
|
+
# @return [String, nil]
|
|
28
|
+
def get_shell_project_file
|
|
29
|
+
xcodeproj_file = nil
|
|
30
|
+
Dir.foreach(Dir.pwd) do |file|
|
|
31
|
+
if file.end_with?(".xcodeproj")
|
|
32
|
+
xcodeproj_file = file
|
|
33
|
+
break
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
xcodeproj_file
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# 获取壳工程的project对象
|
|
40
|
+
def get_shell_project
|
|
41
|
+
|
|
42
|
+
xcodeproj_file = get_shell_project_file
|
|
43
|
+
return nil unless xcodeproj_file
|
|
44
|
+
|
|
45
|
+
xcodeproj_path = "#{Dir.pwd}/#{xcodeproj_file}"
|
|
46
|
+
|
|
47
|
+
Xcodeproj::Project.open(xcodeproj_path)
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
#获取主APP target列表 包含扩展
|
|
52
|
+
def get_shell_targets
|
|
53
|
+
results = []
|
|
54
|
+
project = self.get_shell_project
|
|
55
|
+
targets = project.root_object.targets
|
|
56
|
+
targets.each do |target|
|
|
57
|
+
results << target.name
|
|
58
|
+
end
|
|
59
|
+
results
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#获取主工程全部顶层target(含App与扩展,是否可通过yaml集成由调用方按product_type过滤)
|
|
63
|
+
def get_app_targets
|
|
64
|
+
project = self.get_shell_project
|
|
65
|
+
project.root_object.targets
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# 获取Pods下project对象
|
|
69
|
+
def get_pods_project
|
|
70
|
+
|
|
71
|
+
xcodeproj_path = "#{Dir.pwd}/Pods/Pods.xcodeproj"
|
|
72
|
+
unless File.exist?(xcodeproj_path)
|
|
73
|
+
return nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
Xcodeproj::Project.open(xcodeproj_path)
|
|
77
|
+
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'cocoapods'
|
|
2
|
+
require_relative '../variable'
|
|
3
|
+
|
|
4
|
+
module Pod
|
|
5
|
+
class Command
|
|
6
|
+
# 以 prepend 模块补丁的方式给官方 pod install / pod update 追加 --trim/--full 单次模式选项。
|
|
7
|
+
# 不 reopen 重写官方方法:类方法 options 与 initialize/run 均通过 super 走 CocoaPods 原始实现,
|
|
8
|
+
# 上游新增选项/参数校验时自动保留,不会随版本升级丢失。
|
|
9
|
+
# pod update 内部同样通过 Pod::Installer#install! 执行,下载流程补丁对其同样生效,
|
|
10
|
+
# 因此两个命令共享同一份开关解析。
|
|
11
|
+
module TrimModePatch
|
|
12
|
+
TRIM_OPTIONS = [
|
|
13
|
+
['--trim', '单次开启未使用依赖裁剪(仅本次执行生效,优先于 LocalDependencies.yaml 的 TRIM_UNUSED_POD)'],
|
|
14
|
+
['--full', '单次关闭未使用依赖裁剪,安装全部依赖(仅本次执行生效)']
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
module ClassMethods
|
|
18
|
+
def options
|
|
19
|
+
super.concat(TrimModePatch::TRIM_OPTIONS)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.prepended(base)
|
|
24
|
+
base.singleton_class.prepend(ClassMethods)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def initialize(argv)
|
|
28
|
+
@yaml_trim_mode = argv.flag?('trim')
|
|
29
|
+
@yaml_full_mode = argv.flag?('full')
|
|
30
|
+
# 此时 Podfile 尚未解析(在 run 阶段才读取),提前写入全局变量可供 yaml_dependencies! 使用
|
|
31
|
+
$trim_unused_pod = @yaml_trim_mode ? true : (@yaml_full_mode ? false : nil)
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def run
|
|
36
|
+
# 参数互斥校验放在 run 而非 initialize:parse 实例化阶段抛出的异常
|
|
37
|
+
# 在 CLAide 中 command 尚为 nil,即使是 Informative 也会带堆栈输出
|
|
38
|
+
if @yaml_trim_mode && @yaml_full_mode
|
|
39
|
+
raise Pod::Informative, '--trim 与 --full 互斥,请只指定其中一个选项'
|
|
40
|
+
end
|
|
41
|
+
super
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class Install < Command
|
|
46
|
+
prepend TrimModePatch
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class Update < Command
|
|
50
|
+
prepend TrimModePatch
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'cocoapods'
|
|
2
|
+
require_relative '../setup/podfile_setup'
|
|
3
|
+
|
|
4
|
+
module Pod
|
|
5
|
+
class Command
|
|
6
|
+
class Yaml < Command
|
|
7
|
+
self.abstract_command = true
|
|
8
|
+
self.summary = 'yaml依赖配置相关命令'
|
|
9
|
+
|
|
10
|
+
class Init < Yaml
|
|
11
|
+
|
|
12
|
+
self.summary = '生成接入cocoapods-yaml的模板Podfile'
|
|
13
|
+
|
|
14
|
+
self.description = <<-DESC
|
|
15
|
+
在iOS工程根目录执行 pod yaml init:
|
|
16
|
+
* 有.xcodeproj且无Podfile: 直接生成模板Podfile及依赖yaml
|
|
17
|
+
* 已有Podfile但未接入yaml_dependencies!: 先备份为 Podfile.bak.时间戳 再生成模板
|
|
18
|
+
* 已接入: 不做处理
|
|
19
|
+
DESC
|
|
20
|
+
|
|
21
|
+
def run
|
|
22
|
+
CocoaPods::Yaml::PodfileSetup.run
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require_relative '../dep/find'
|
|
2
|
+
require_relative '../../version'
|
|
3
|
+
module Pod
|
|
4
|
+
class Command
|
|
5
|
+
class Yaml < Command
|
|
6
|
+
class Relation < Yaml
|
|
7
|
+
|
|
8
|
+
self.summary = '从Podfile.lock检索组件的正向/反向依赖关系'
|
|
9
|
+
|
|
10
|
+
self.description = '默认输出反向依赖(谁依赖了该组件);加--forward输出正向依赖。' \
|
|
11
|
+
'不传组件名时输出全部组件,可配合--sort按依赖数量排序。'
|
|
12
|
+
|
|
13
|
+
self.arguments = [
|
|
14
|
+
CLAide::Argument.new('组件名(可选,不传则输出全部)', false)
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
def self.options
|
|
18
|
+
[
|
|
19
|
+
%w[--sort 输出依赖时按数量从小到大排序],
|
|
20
|
+
%w[--forward 输出正向依赖],
|
|
21
|
+
].concat(super).reject { |(name, _)| (name == '--no-ansi' or name == '--silent' or name == '--allow-root' or name == '--verbose') }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(argv)
|
|
25
|
+
@name = argv.shift_argument
|
|
26
|
+
super
|
|
27
|
+
CocoaPods::Yaml.log.info("插件版本号: #{CocoaPods::Yaml::VERSION}")
|
|
28
|
+
@sort_count = argv.flag?('sort', false)
|
|
29
|
+
@forward = argv.flag?('forward', false)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def run
|
|
33
|
+
verify_lockfile_exists!
|
|
34
|
+
finder = Pod::DepFinder.new
|
|
35
|
+
finder.find_name = @name
|
|
36
|
+
finder.sort_count = @sort_count
|
|
37
|
+
finder.forward = @forward
|
|
38
|
+
finder.find
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'colored2'
|
|
3
|
+
|
|
4
|
+
module Pod
|
|
5
|
+
class DepFinder
|
|
6
|
+
|
|
7
|
+
# @param [Hash] podfileLock内容
|
|
8
|
+
attr_accessor :lockfile_content
|
|
9
|
+
|
|
10
|
+
# @param [String] 要查询的组件名
|
|
11
|
+
attr_accessor :find_name
|
|
12
|
+
|
|
13
|
+
# @param [bool] 是否按数量从小到大排序
|
|
14
|
+
attr_accessor :sort_count
|
|
15
|
+
|
|
16
|
+
# @param [bool] 是否打印正向依赖
|
|
17
|
+
attr_accessor :forward
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
super
|
|
21
|
+
init_lock
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def init_lock
|
|
25
|
+
# 读取名为Podfile.lock的yaml文件, 并通过分析里面的PODS数组,输出依赖的第三方库的名字
|
|
26
|
+
unless File.exist?("Podfile.lock")
|
|
27
|
+
CocoaPods::Yaml.log.error("Podfile.lock文件不存在,请先执行pod install或pod update")
|
|
28
|
+
exit!
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
@sort_count = false
|
|
32
|
+
|
|
33
|
+
@lockfile_content = YAML.load_file('Podfile.lock')
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @api 反向依赖查询
|
|
38
|
+
def find
|
|
39
|
+
|
|
40
|
+
if @find_name
|
|
41
|
+
find_single_dep
|
|
42
|
+
else
|
|
43
|
+
find_all_dep
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
print_usage_hint
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# 按当前场景只提示真正可用的后续操作
|
|
50
|
+
def print_usage_hint
|
|
51
|
+
hints = []
|
|
52
|
+
if @find_name
|
|
53
|
+
hints << '去掉 --forward 可同时看反向依赖' if @forward
|
|
54
|
+
else
|
|
55
|
+
hints << '查询单个组件: pod yaml relation <组件名>'
|
|
56
|
+
hints << '加 --forward 看正向依赖' unless @forward
|
|
57
|
+
hints << '加 --sort 按依赖数量从少到多排序' unless @sort_count
|
|
58
|
+
end
|
|
59
|
+
return if hints.empty?
|
|
60
|
+
CocoaPods::Yaml.log.info('')
|
|
61
|
+
CocoaPods::Yaml.log.detail(hints.join(';'))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def find_all_dep
|
|
65
|
+
|
|
66
|
+
result_map = Hash.new
|
|
67
|
+
# 循环遍历lockfile对象中的PODS数组
|
|
68
|
+
@lockfile_content['PODS'].each do |pod|
|
|
69
|
+
# 拿到所有的组件名
|
|
70
|
+
key = pod.is_a?(Hash) ? pod.keys[0] : pod
|
|
71
|
+
key = key.split(" (")[0]
|
|
72
|
+
|
|
73
|
+
if @forward
|
|
74
|
+
result_map[key] = pod.is_a?(Hash) ? pod.values[0] : Array.new
|
|
75
|
+
else
|
|
76
|
+
result_map[key] = find_reverse_with_name(key)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
msg = @forward ? "正向依赖(各组件依赖了谁)" : "反向依赖(谁依赖了各组件)"
|
|
81
|
+
CocoaPods::Yaml.log.step(msg)
|
|
82
|
+
|
|
83
|
+
sorted_hash = result_map
|
|
84
|
+
if @sort_count
|
|
85
|
+
sorted_hash = Hash[result_map.sort_by { |key, value| value.length }]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
sorted_hash.each do |key, array|
|
|
89
|
+
title = @forward ? "#{key} 依赖的组件(#{array.length}个)" : "依赖#{key}的组件(#{array.length}个)"
|
|
90
|
+
CocoaPods::Yaml.log.info(title, :green)
|
|
91
|
+
array.each { |obj| CocoaPods::Yaml.log.info(" #{obj}") }
|
|
92
|
+
CocoaPods::Yaml.log.info('')
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def find_single_dep
|
|
97
|
+
|
|
98
|
+
CocoaPods::Yaml.log.step("#{@find_name} 依赖关系")
|
|
99
|
+
|
|
100
|
+
# 默认正反向都展示;--forward时只看正向
|
|
101
|
+
unless @forward
|
|
102
|
+
results = find_reverse_with_name(@find_name)
|
|
103
|
+
CocoaPods::Yaml.log.info("反向依赖 · 谁依赖了#{@find_name}(#{results.length}个):", :green)
|
|
104
|
+
results.each { |res| CocoaPods::Yaml.log.info(" #{res}") }
|
|
105
|
+
CocoaPods::Yaml.log.info('')
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# 正向依赖查询结果
|
|
109
|
+
forward_results = find_forward_with_name(@find_name)
|
|
110
|
+
CocoaPods::Yaml.log.info("正向依赖 · #{@find_name}依赖了谁(#{forward_results.length}个):", :green)
|
|
111
|
+
forward_results.each { |res| CocoaPods::Yaml.log.info(" #{res}") }
|
|
112
|
+
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def find_forward_with_name(name)
|
|
116
|
+
if not @lockfile_content or not @lockfile_content['PODS']
|
|
117
|
+
return []
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
results = Array.new
|
|
121
|
+
@lockfile_content['PODS'].each do |pod|
|
|
122
|
+
# 拿到所有的组件名
|
|
123
|
+
key = pod.is_a?(Hash) ? pod.keys[0] : pod
|
|
124
|
+
key = key.split(" (")[0]
|
|
125
|
+
if key == name and pod.is_a?(Hash)
|
|
126
|
+
pod.each_value do |value|
|
|
127
|
+
# value是依赖数组,需展平为逐条依赖,不能把整个数组当一个元素
|
|
128
|
+
results.concat(Array(value))
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
results
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def find_reverse_with_name(name)
|
|
136
|
+
if not @lockfile_content or not @lockfile_content['PODS']
|
|
137
|
+
return []
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
results = Array.new
|
|
141
|
+
# 循环遍历lockfile对象中的PODS数组
|
|
142
|
+
@lockfile_content['PODS'].each do |pod|
|
|
143
|
+
if pod.is_a?(Hash)
|
|
144
|
+
pod.values.each do |value|
|
|
145
|
+
# 如果value是一个数组,则判断数组中是否包含name
|
|
146
|
+
if value.is_a?(Array)
|
|
147
|
+
value.each { |sub_value|
|
|
148
|
+
# 这里的处理包含了子模块的情况
|
|
149
|
+
sub_module = sub_value.split(" (")[0]
|
|
150
|
+
if sub_module == name
|
|
151
|
+
results << pod.keys.first
|
|
152
|
+
end
|
|
153
|
+
}
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# 去重
|
|
160
|
+
results.uniq
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require_relative '../podfilelock'
|
|
2
|
+
|
|
3
|
+
module PodfileLock
|
|
4
|
+
class Indirect
|
|
5
|
+
def log_indirect
|
|
6
|
+
|
|
7
|
+
# 内容读取
|
|
8
|
+
dependencies_lock = PodfileLock::PodfileLockManager.podfile_lock_content
|
|
9
|
+
|
|
10
|
+
dependencies = dependencies_lock['DEPENDENCIES']
|
|
11
|
+
unless dependencies
|
|
12
|
+
return
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
check_sums = dependencies_lock['SPEC CHECKSUMS']
|
|
16
|
+
unless check_sums
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# 直接依赖名归一化(去掉版本要求和subspec)。
|
|
21
|
+
# 注意: 必须用非破坏式的map,不能对lock原始数组使用collect!,否则会改写内存中的lock数据
|
|
22
|
+
direct_dependencies = dependencies.map do |item|
|
|
23
|
+
item.split(' (')[0].split('/')[0]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# 从PODS依赖树构建"组件 -> 引用方"索引,用于输出来源
|
|
27
|
+
referrers = build_referrers(dependencies_lock['PODS'])
|
|
28
|
+
|
|
29
|
+
log = CocoaPods::Yaml.log
|
|
30
|
+
check_sums.each_key do |pod_name|
|
|
31
|
+
next if direct_dependencies.include?(pod_name)
|
|
32
|
+
|
|
33
|
+
parents = referrers[pod_name] || []
|
|
34
|
+
if parents.empty?
|
|
35
|
+
log.detail("间接依赖: #{pod_name}")
|
|
36
|
+
else
|
|
37
|
+
log.detail("间接依赖: #{pod_name}(由 #{parents.join('、')} 引入)")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# 从Podfile.lock的PODS段解析引用关系
|
|
45
|
+
# @param pods [Array<String, Hash>] PODS段内容
|
|
46
|
+
# @return [Hash{String => Array<String>}] 组件名 -> 直接引用它的组件名(已去重)
|
|
47
|
+
def build_referrers(pods)
|
|
48
|
+
result = Hash.new { |hash, key| hash[key] = [] }
|
|
49
|
+
return result unless pods.is_a?(Array)
|
|
50
|
+
|
|
51
|
+
pods.each do |entry|
|
|
52
|
+
# PODS段中无下级依赖的组件是纯字符串,带依赖的是Hash
|
|
53
|
+
next unless entry.is_a?(Hash)
|
|
54
|
+
|
|
55
|
+
entry.each do |spec, child_dependencies|
|
|
56
|
+
parent = normalize_pod_name(spec)
|
|
57
|
+
Array(child_dependencies).each do |child|
|
|
58
|
+
child_name = normalize_pod_name(child)
|
|
59
|
+
result[child_name] << parent unless result[child_name].include?(parent)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
result
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# 归一化pod名: 去掉版本要求与subspec路径
|
|
67
|
+
# 例: "BSDebugPlugins/Cookie (= 1.2.0-SNAPSHOT)" -> "BSDebugPlugins"
|
|
68
|
+
def normalize_pod_name(name)
|
|
69
|
+
name.to_s.split(' (')[0].split('/')[0]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|