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.
@@ -0,0 +1,171 @@
1
+ require 'cocoapods/installer'
2
+ require 'yaml'
3
+ require_relative 'shell'
4
+ require_relative 'local'
5
+ require_relative 'pod_target'
6
+
7
+ module Pod
8
+ # 裁剪开关的下载流程补丁。必须用prepend而不是重开class重定义:
9
+ # 重开class定义同名方法是覆盖,super找不到原方法;prepend后super即CocoaPods原始实现
10
+ module InstallerTrim
11
+ def download_dependencies
12
+ if Pod::YamlDependencies.new.trim_unused_pod?
13
+ UI.section '下载依赖...' do
14
+
15
+ thin_pod_unused #自定义
16
+ thin_delete_pods_unused #自定义
17
+
18
+ install_pod_sources
19
+ run_podfile_pre_install_hooks
20
+ clean_pod_sources
21
+ end
22
+ else
23
+ # 未开启裁剪时走CocoaPods原始下载流程(pod install/update --full或默认)
24
+ super
25
+ end
26
+ end
27
+ end
28
+
29
+ class Installer
30
+
31
+ # @return [Hash{String, Pod:Target}] 所有的pod库被使用的次数
32
+ attr_accessor :thin_pods
33
+
34
+ # @return [String] 未被使用的pod库列表
35
+ attr_accessor :thin_unused_pods
36
+
37
+ prepend InstallerTrim
38
+
39
+ # 删除未被使用的依赖
40
+ # @return nil
41
+ def thin_pod_unused
42
+ @thin_pods = Hash.new
43
+ thin_all_deps_pods_targets
44
+ thin_all_deps_shell_targets
45
+ thin_all_deps_local_yaml
46
+ thin_unused_deps
47
+ end
48
+
49
+ # 获取未被使用到的依赖库
50
+ # @return thin_unused_pods
51
+ def thin_unused_deps
52
+ thin_unused_pods = []
53
+ thin_pods.each do |name, pod_target|
54
+ pod_target.reduce_reference_count
55
+ end
56
+
57
+ thin_pods.each do |name, pod_target|
58
+ if pod_target.reference_count <= 0
59
+ thin_unused_pods << name
60
+ end
61
+ end
62
+
63
+ @thin_unused_pods = thin_unused_pods
64
+ end
65
+
66
+ # 获取在pods_targets使用的依赖库
67
+ # @return thin_pods
68
+ def thin_all_deps_pods_targets
69
+
70
+ pod_targets = analysis_result.pod_targets
71
+
72
+ pod_targets.each do |pod_target|
73
+ thin_pods[pod_target.to_s] = pod_target
74
+ end
75
+
76
+ pod_targets.each do |pod_target|
77
+ pod_target.dependent_targets.each do|sub_pod_target|
78
+ thin_update_reference_count(sub_pod_target)
79
+ end
80
+ end
81
+
82
+ thin_pods
83
+ end
84
+
85
+ # 递归更新引用计数
86
+ # @return nil
87
+ def thin_update_reference_count(pod_target)
88
+ pod = thin_pods[pod_target.to_s]
89
+ pod.reference_count += 1
90
+ pod.dependent_targets.each do|sub_pod_target|
91
+ thin_update_reference_count(sub_pod_target)
92
+ end
93
+ end
94
+
95
+ # 获取在壳工程里使用的依赖库
96
+ # @return thin_pods
97
+ def thin_all_deps_shell_targets
98
+ shell = Shell.new
99
+
100
+ shell.deps_shell.each { |shell_pod|
101
+ if thin_pods.has_key?(shell_pod)
102
+ thin_pods[shell_pod].max_reference_count
103
+ end
104
+ }
105
+ thin_pods
106
+ end
107
+
108
+ # 获取在local_yaml里使用的依赖库
109
+ # @return thin_pods
110
+ def thin_all_deps_local_yaml
111
+ local = Local.new
112
+ local.deps_yaml.each do |yaml_pod|
113
+ if thin_pods.has_key?(yaml_pod)
114
+ thin_pods[yaml_pod].max_reference_count
115
+ end
116
+ end
117
+
118
+ thin_pods
119
+ end
120
+
121
+ # 删除某些依赖库
122
+ # @return nil
123
+ def thin_delete_pods_unused
124
+ if thin_unused_pods.empty?
125
+ return
126
+ end
127
+
128
+ thin_unused_pods.each do |unused_pod|
129
+ title_options = { :verbose_prefix => '-> '.red }
130
+ UI.titled_section("忽略安装 #{unused_pod}".red, title_options) do
131
+ thin_delete_pod(unused_pod)
132
+ end
133
+ end
134
+
135
+ end
136
+
137
+ # 删除某个依赖库
138
+ # @param unused_pod: String 依赖库名字
139
+ # @return nil
140
+ def thin_delete_pod(unused_pod)
141
+ sandbox_state.added.delete(unused_pod)
142
+ sandbox_state.changed.delete(unused_pod)
143
+ sandbox_state.unchanged.delete(unused_pod)
144
+
145
+ pod_targets.delete_if { |pod_target|
146
+ pod_target.to_s == unused_pod
147
+ }
148
+
149
+ aggregate_targets.each { |aggregate_target|
150
+ aggregate_target.pod_targets.delete_if { |pod_target|
151
+ pod_target.to_s == unused_pod
152
+ }
153
+ }
154
+
155
+ aggregate_targets.each do |aggregate_target|
156
+ aggregate_target.user_build_configurations.each_key do |config|
157
+ pod_targets = aggregate_target.pod_targets_for_build_configuration(config)
158
+ pod_targets.delete_if { |pod_target|
159
+ pod_target.to_s == unused_pod
160
+ }
161
+ end
162
+ end
163
+
164
+ analysis_result.specifications.delete_if { |specification|
165
+ real_specification = specification.to_s.split(" (")[0]
166
+ real_pod = real_specification.split("/")[0]
167
+ real_pod == unused_pod
168
+ }
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,40 @@
1
+ require 'pathname'
2
+ require_relative '../yaml/yaml_dependencies'
3
+
4
+ module Pod
5
+ class Local
6
+
7
+ def initialize
8
+ super
9
+ @reference_pods = []
10
+ end
11
+
12
+ # LocalDependencies.yaml指定的本地依赖库
13
+ # @return [String]
14
+ attr_accessor :reference_pods
15
+
16
+ def deps_yaml
17
+
18
+ yaml_dep = YamlDependencies.new
19
+
20
+ yaml_dep.generate_yaml
21
+
22
+ local_yaml = yaml_dep.local_yaml_name
23
+ yaml_dep.read_yaml_deps_file(local_yaml)
24
+
25
+ yaml_dependencies = yaml_dep.yaml_deps[local_yaml]
26
+
27
+ if yaml_dependencies
28
+ # 处理烦人的子模块
29
+ yaml_dependencies.keys.each do |pod|
30
+ real_pod = pod.split("/")[0]
31
+ unless reference_pods.include?(real_pod)
32
+ reference_pods << real_pod
33
+ end
34
+ end
35
+ end
36
+
37
+ reference_pods
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ module Pod
2
+ class PodTarget < Target
3
+
4
+ # 被引用的次数
5
+ # @return Int
6
+ attr_accessor :reference_count
7
+
8
+ # 引用计数最大
9
+ def max_reference_count
10
+ self.reference_count = 99999
11
+ self.dependent_targets.each do |dep|
12
+ dep.max_reference_count
13
+ end
14
+ end
15
+
16
+ # 引用计数减1
17
+ def reduce_reference_count
18
+ self.reference_count -= 1
19
+
20
+ self.dependent_targets.each do |dep|
21
+ dep.reduce_reference_count
22
+ end
23
+ end
24
+
25
+ # 获取引用计数
26
+ # @return Int
27
+ def reference_count
28
+ @reference_count ? @reference_count : 0
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,180 @@
1
+ require 'xcodeproj'
2
+ require 'pathname'
3
+ require_relative '../check/xcodeproj'
4
+
5
+ module Pod
6
+ class Shell
7
+
8
+ def initialize
9
+ super
10
+ @reference_files = []
11
+ @reference_pods = []
12
+ end
13
+
14
+ # 壳工程project对象
15
+ # @return Xcodeproj::Project
16
+ attr_accessor :project_shell
17
+
18
+ # 壳工程引用的文件列表
19
+ # @return [Pathname]
20
+ attr_accessor :reference_files
21
+
22
+ # 壳工程引用的依赖库
23
+ # @return [String]
24
+ attr_accessor :reference_pods
25
+
26
+ # 获取壳工程所引用的依赖库
27
+ # @return [String]
28
+ def deps_shell
29
+ init_project_shell
30
+
31
+ reference_code_file(project_shell.root_object.main_group)
32
+
33
+ reference_files.each do |reference_file|
34
+ pod_in_file(reference_file)
35
+ end
36
+
37
+ reference_pods
38
+ end
39
+
40
+ # 初始化project对象
41
+ # @return project_shell
42
+ def init_project_shell
43
+ # 统一走XcodeprojManager的壳工程查找逻辑,避免多份实现选取结果不一致
44
+ @project_shell = XCProject::XcodeprojManager.share_manager.get_shell_project
45
+ end
46
+
47
+ # 参与import分析的源码后缀(.swift参与后Swift的import Module才能进引用图,
48
+ # 否则开启裁剪时Swift依赖会被误删)
49
+ CODE_FILE_EXTS = %w[.h .m .mm .pch .c .cc .hpp .cpp .swift].freeze
50
+
51
+ # 获取其对应的引用代码文件和路径
52
+ # @return reference_files
53
+ def reference_code_file(group)
54
+ # 文件引用
55
+ file_reference = Xcodeproj::Project::Object::PBXFileReference
56
+ # PBXFileSystemSynchronizedRootGroup在xcodeproj 1.25(Xcode16)才引入,
57
+ # gemspec允许xcodeproj>=1.23,旧版本必须走const_defined?兜底,避免直接NameError
58
+ synchronized_group_class =
59
+ if Xcodeproj::Project::Object.const_defined?(:PBXFileSystemSynchronizedRootGroup)
60
+ Xcodeproj::Project::Object::PBXFileSystemSynchronizedRootGroup
61
+ end
62
+ group.children.each {|child|
63
+
64
+ # Xcode16+的文件系统同步分组: 它在父group的children里,但自身没有parent/real_path,
65
+ # 用所在父group的磁盘路径拼接其相对path后直接扫描磁盘目录
66
+ if synchronized_group_class && child.is_a?(synchronized_group_class)
67
+ scan_disk_code_files(synchronized_disk_path(group, child))
68
+ next
69
+ end
70
+
71
+ if child.class != file_reference
72
+ self.reference_code_file(child)
73
+ next
74
+ end
75
+
76
+ file_name = child.real_path.basename
77
+ extend_name = file_name.extname
78
+ next unless CODE_FILE_EXTS.include?(extend_name)
79
+
80
+ # 引用关系路径
81
+ hierarchy_path_array = child.hierarchy_path.split("/")
82
+ if hierarchy_path_array.size < 2
83
+ next
84
+ end
85
+
86
+ reference_files << child.real_path
87
+ }
88
+ end
89
+
90
+ # 递归收集磁盘目录下的源码文件(对应Xcode16+同步文件夹分组)
91
+ def scan_disk_code_files(dir_path)
92
+ return unless dir_path && File.directory?(dir_path)
93
+ Dir.glob(File.join(dir_path, '**', '*')).each do |path|
94
+ next unless File.file?(path)
95
+ next unless CODE_FILE_EXTS.include?(File.extname(path))
96
+ reference_files << Pathname.new(path)
97
+ end
98
+ end
99
+
100
+ # 计算同步分组在磁盘上的绝对路径
101
+ # @param parent_group 承载该同步分组的普通PBXGroup(有real_path)
102
+ # @param sync_group PBXFileSystemSynchronizedRootGroup(只有相对path)
103
+ def synchronized_disk_path(parent_group, sync_group)
104
+ relative = sync_group.path
105
+ return nil if relative.nil? || relative.empty?
106
+ path = Pathname.new(relative)
107
+ return path if path.absolute?
108
+
109
+ case sync_group.source_tree
110
+ when 'SOURCE_ROOT'
111
+ base = sync_group.project.root_object.main_group.real_path
112
+ else # '<group>'(默认): 相对所在父分组
113
+ base = parent_group.real_path
114
+ end
115
+ base ? base.join(relative) : path
116
+ end
117
+
118
+ # 获取某个文件里引用的依赖库名字
119
+ # @param file_path 文件路径
120
+ # @return reference_pods
121
+ def pod_in_file(file_path)
122
+ if File.directory?(file_path)
123
+ return reference_pods
124
+ end
125
+
126
+ # Swift与OC的import语法完全不同,按文件后缀分流解析
127
+ if File.extname(file_path) == '.swift'
128
+ parse_swift_imports(file_path)
129
+ else
130
+ parse_oc_imports(file_path)
131
+ end
132
+ reference_pods
133
+ end
134
+
135
+ private
136
+
137
+ # 解析OC源码的依赖引用: 兼容 #import <A/B.h> 与 #import<A/B.h>(无空格)两种写法,
138
+ # 无空格写法漏识别会导致组件不进引用图,开启裁剪时被误删
139
+ def parse_oc_imports(file_path)
140
+ safe_readlines(file_path).each { |line|
141
+ match = line.match(/\A#\s*import\s*<([^>]+)>/)
142
+ next unless match
143
+
144
+ import_path = match[1]
145
+ next unless import_path.include?("/")
146
+
147
+ # 取第一段作为组件名,处理 #import <A/B/C.h> 多级子模块的情况
148
+ add_reference_pod(import_path.split("/").first)
149
+ }
150
+ end
151
+
152
+ # 解析Swift源码的依赖引用: import Module / @testable import Module /
153
+ # import class Module.Sub(子符号取第一段)等
154
+ def parse_swift_imports(file_path)
155
+ safe_readlines(file_path).each { |line|
156
+ match = line.match(/\A(?:@\w+\s+)?import\s+(?:(?:class|struct|enum|protocol|func|var|let|typealias)\s+)?([A-Za-z_]\w*)/)
157
+ next unless match
158
+
159
+ add_reference_pod(match[1])
160
+ }
161
+ end
162
+
163
+ def add_reference_pod(module_name)
164
+ unless reference_pods.include?(module_name)
165
+ reference_pods << module_name
166
+ end
167
+ end
168
+
169
+ # 容错读取源码行:壳工程历史源码可能存在GBK等非UTF-8文件。
170
+ # 直接IO.readlines按默认编码读,后续start_with?/gsub遇到非法字节会抛
171
+ # invalid byte sequence中断整个裁剪流程;先按二进制读取再显式做一次
172
+ # 同编码转换替换非法字节
173
+ def safe_readlines(file_path)
174
+ content = File.read(file_path, mode: 'rb')
175
+ content.encode!('UTF-8', 'UTF-8', invalid: :replace, undef: :replace, replace: '?')
176
+ content.lines
177
+ end
178
+
179
+ end
180
+ end
@@ -0,0 +1,2 @@
1
+ # pod install/update --trim / --full 的单次开关(nil表示读取yaml配置)
2
+ $trim_unused_pod = nil