pod-binary-pack 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,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'repo'
4
+ require_relative 'repo_module'
5
+
6
+ module Build
7
+ class RepoCocoapods < Repo
8
+
9
+ # @return [RepoModule]
10
+ attr_accessor :repo_module
11
+
12
+ #获取某个pod_name的所有版本号
13
+ # @return [Array] 返回数组或nil
14
+ def pod_name_versions(pod_name)
15
+ pod_name_path = "#{self.path}/#{pod_name}"
16
+ unless File.directory?(pod_name_path)
17
+ return nil
18
+ end
19
+
20
+ entries = Dir.entries(pod_name_path)
21
+ entries.select { |entry| entry != '.' && entry != '..' }
22
+ end
23
+
24
+ def copy_podspec(repo_module)
25
+ @repo_module = repo_module
26
+ Build.log.step("拷贝podspec文件到cocoapods中(#{File.basename(self.path)}):")
27
+
28
+ # 判断文件夹是否存在
29
+ pod_name = repo_module.pod_name
30
+ pod_version_final = repo_module.pod_version_final
31
+ pod_name_path = "#{self.path}/#{pod_name}"
32
+
33
+ unless File.directory?(pod_name_path)
34
+ Dir.mkdir(pod_name_path)
35
+ end
36
+
37
+ pod_version_path = "#{pod_name_path}/#{pod_version_final}"
38
+ if File.directory?(pod_version_path)
39
+ if not repo_module.build_item.snapshot and not repo_module.third
40
+ error = "#{pod_name}已发布相同版本#{pod_version_final},请更换更高的版本号"
41
+ pusher.push("❌ 打包失败", error)
42
+ Process.exit(-1)
43
+ end
44
+ else
45
+ Dir.mkdir(pod_version_path)
46
+ end
47
+
48
+ FileUtils.cp(repo_module.podspec_json_path, pod_version_path)
49
+
50
+ # 移除掉已有的podspec文件
51
+ podspec_path = "#{pod_version_path}/#{pod_name}.podspec"
52
+ if File.exist?(podspec_path)
53
+ FileUtils.rm(podspec_path)
54
+ end
55
+ Build.log.detail("podspec 拷贝完毕")
56
+ end
57
+
58
+ def git_commit(commit_msg = nil)
59
+ commit_msg = repo_module.get_commit_msg
60
+ super commit_msg
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,281 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'repo'
4
+ require_relative 'build_item'
5
+
6
+ module Build
7
+
8
+ class RepoModule < Repo
9
+ # podspec 扩展字段:构建期写入的元数据(源码commit信息/产物架构/校验和等)。
10
+ # 命名刻意与 gem 名解耦,工具后续重命名不影响该数据契约;
11
+ # 未知嵌套字段会被 CocoaPods 整体忽略,不会与官方 podspec 属性冲突。
12
+ PODSPEC_METADATA_KEY = 'build_metadata'.freeze
13
+
14
+ # 版本分支默认前缀(build.yml 可在 profile 中通过 release_branch_prefix 定制)
15
+ DEFAULT_RELEASE_BRANCH_PREFIX = 'release'.freeze
16
+
17
+ # 分支中可解析的版本号规则(不限制段数):
18
+ # 主体: 至少两段点分数字,如 1.2 / 1.2.0 / 1.2.2.0
19
+ # 后缀(可选): 以 - 或 + 起始的字母数字串(内部允许 . -),如 1.2.2-fix / 1.2.2-beta.1 / 1.2.0+build.3
20
+ VERSION_PATTERN = '\\d+(?:\\.\\d+)+(?:[-+][0-9A-Za-z](?:[0-9A-Za-z.-]*[0-9A-Za-z])?)?'.freeze
21
+
22
+ # 从版本分支名解析出版本号,不符合规则返回nil。
23
+ # 规则: <前缀> + 可选分隔符(_/-) + 版本号(如 release_1.2.0 / release-1.2.2-fix)。
24
+ # 前缀本身已带分隔符(如 "release/")也兼容;版本部分须符合 VERSION_PATTERN,乱七八糟的字符不解析
25
+ # @param branch [String] 分支名
26
+ # @param prefix [String, nil] 前缀,空则用默认 release
27
+ # @return [String, nil]
28
+ def self.parse_version_from_branch(branch, prefix = nil)
29
+ p = prefix.to_s.strip
30
+ p = DEFAULT_RELEASE_BRANCH_PREFIX if p.empty?
31
+ match = branch.to_s.match(/\A#{Regexp.escape(p)}([_-]?)(#{VERSION_PATTERN})\z/)
32
+ match && match[2]
33
+ end
34
+
35
+ # @return [BuildItem] 构建参数
36
+ attr_accessor :build_item
37
+ attr_accessor :pod_name
38
+ attr_accessor :pod_version
39
+ # 内部三方库处理标记(不锁分支/不改版本tag/允许同版本重复发布等)。
40
+ # 已无对外 --third 入口,仅 fork-github 流程由 CLI 内部置为 true,自研组件恒为 false
41
+ attr_accessor :third
42
+ attr_accessor :snapshot
43
+ attr_accessor :pod_version_final
44
+
45
+ # @return [String] podspec_json路径
46
+ attr_accessor :podspec_json_path
47
+
48
+ # @return [JSON] podspec_content
49
+ attr_accessor :podspec_content
50
+
51
+ # @return [String] 区别于pod_name, 特指打包后的framework的名字
52
+ attr_accessor :framework_name
53
+
54
+ # @return [Build::Hosting::RepoInfo] 代码托管平台上的仓库信息(Gitee为full_name,GitLab为数字ID)
55
+ attr_accessor :repo_info
56
+
57
+ # @return [String, nil] podspec相对仓库根目录的路径(podspec不在根目录时使用,如 Sub/Foo.podspec)
58
+ attr_accessor :podspec_rel_path
59
+
60
+ # @return [Array<String>, nil] 显式指定的打包集成subspec短名列表(nil=默认集成全部)。
61
+ # 用于互斥subspec组件(如FMDB的standard/standalone/SQLCipher是不同sqlite后端,全量合并编译必冲突),
62
+ # 经 build.yml components.<组件>.subspecs 或 --subspecs 声明
63
+ attr_accessor :build_subspecs
64
+
65
+ # @return [String, nil] 一仓多组件时的源码tag前缀(如 "Foo/"),最终tag为 Foo/1.0.0;二进制一组件一仓不使用
66
+ attr_accessor :tag_prefix
67
+
68
+ # @return [String] 版本分支前缀(分支名形如 release_1.2.0 时可从中解析版本号)
69
+ attr_accessor :release_branch_prefix
70
+
71
+ def initialize(path, ssh_url, branch, pod_name, pod_version, third, build_item,
72
+ podspec_rel_path: nil, tag_prefix: nil, release_branch_prefix: nil,
73
+ build_subspecs: nil)
74
+ super(path, ssh_url, branch)
75
+ @pod_name = pod_name
76
+ @pod_version = pod_version
77
+ @third = third.to_s.downcase == "true"
78
+ @build_item = build_item
79
+ @podspec_rel_path = podspec_rel_path.to_s.strip
80
+ @podspec_rel_path = nil if @podspec_rel_path && @podspec_rel_path.empty?
81
+ # subspec覆盖列表:规范化为去空白的短名数组,全空视为未指定(nil=集成全部)
82
+ @build_subspecs = Array(build_subspecs).map { |s| s.to_s.strip }.reject(&:empty?)
83
+ @build_subspecs = nil if @build_subspecs.empty?
84
+ prefix = tag_prefix.to_s.strip
85
+ @tag_prefix = if prefix.empty? || prefix.end_with?('/')
86
+ prefix.empty? ? nil : prefix
87
+ else
88
+ "#{prefix}/"
89
+ end
90
+ custom_prefix = release_branch_prefix.to_s.strip
91
+ @release_branch_prefix = custom_prefix.empty? ? DEFAULT_RELEASE_BRANCH_PREFIX : custom_prefix
92
+ validate
93
+ analyzer
94
+ end
95
+
96
+ def validate
97
+
98
+ if pod_name.empty?or pod_version.empty?
99
+ error = "pod_name pod_version 均不能为空"
100
+ pusher.push("❌ 打包失败", error)
101
+ Process.exit(-1)
102
+ end
103
+
104
+ if pod_name == "xxx" or pod_version == "xxx" or ssh_url == "xxx" or branch == "xxx"
105
+ error = "pod_name pod_version ssh_url branch 任一个都不能为xxx"
106
+ pusher.push("❌ 打包失败", error)
107
+ Process.exit(-1)
108
+ end
109
+
110
+ if pod_version.include?("SNAPSHOT")
111
+ error = "版本号里不能带SNAPSHOT,如需SNAPSHOT请使用snapshot参数"
112
+ pusher.push("❌ 打包失败", error)
113
+ Process.exit(-1)
114
+ end
115
+
116
+ if self.third and self.build_item.snapshot
117
+ error = "三方库不能打SNAPSHOT包"
118
+ pusher.push("❌ 打包失败", error)
119
+ Process.exit(-1)
120
+ end
121
+
122
+ end
123
+
124
+ def analyzer
125
+ @pod_version_final = self.build_item.snapshot ? pod_version+"-SNAPSHOT" : pod_version
126
+ end
127
+
128
+ # 打在组件仓上的实际tag名:一仓多组件时带组件名前缀(Foo/1.0.0)避免共仓tag冲突,
129
+ # 默认无前缀即裸版本号;二进制一组件一仓,前缀始终为空
130
+ def version_tag
131
+ "#{tag_prefix}#{pod_version_final}"
132
+ end
133
+
134
+ # 组件文件匹配根目录(实现文件/资源的相对路径基准),默认仓库根目录;
135
+ # podspec在子目录时由RepoModuleSource覆写为podspec所在目录(CocoaPods :podspec 语义一致)
136
+ def component_root_dir
137
+ path
138
+ end
139
+
140
+ # 是否构建了二进制
141
+ def build_binary
142
+ build_item.real_device or build_item.simulator
143
+ end
144
+
145
+ def update_binary
146
+ # Array()+dup 避免直接持有podspec_content内部的数组引用被后续append污染
147
+ source_files_array = Array(podspec_content["source_files"]).dup
148
+
149
+ sub_specs = podspec_content["subspecs"]
150
+ if sub_specs
151
+ sub_specs.each do |sub_spec|
152
+ # Array(nil)=[],subspec未声明source_files时安全跳过;元素统一展平为一维
153
+ source_files_array.concat(Array(sub_spec["source_files"]))
154
+ end
155
+ end
156
+
157
+ # 定制组件:不编译framework,也不发布二进制(名单见BuildItem::NO_BINARY_PODS)
158
+ if Build::BuildItem::NO_BINARY_PODS.include?(pod_name)
159
+ build_item.real_device = false
160
+ build_item.simulator = false
161
+ build_item.header_only = false
162
+ warn_msg = "定制组件#{pod_name}不需要打二进制"
163
+ Build.log.warn(warn_msg)
164
+ pusher.push("【⚠️组件不构建二进制提醒】", warn_msg)
165
+ return
166
+ end
167
+
168
+ have_implementation = false
169
+ have_header = false
170
+ # 包含子模块的所有;source_files 可能是 glob、单个文件,也可能直接指向目录
171
+ source_files_array.each do |source_files|
172
+ source_files_path = "#{component_root_dir}/#{source_files}"
173
+ if FileHelper.have_implementation_files(source_files_path)
174
+ have_implementation = true
175
+ break
176
+ end
177
+ # 记录是否至少匹配到头文件(纯头文件组件判定)
178
+ have_header = true if FileHelper.have_header_files(source_files_path)
179
+ end
180
+
181
+ if not have_implementation
182
+ build_item.real_device = false
183
+ build_item.simulator = false
184
+ if have_header
185
+ # 纯头文件组件:不编译framework,但仍发布头文件形态的二进制podspec,
186
+ # 保证二进制spec源也能解析到该组件(App默认优先二进制源)
187
+ build_item.header_only = true
188
+ warn_msg = "当前组件无实现文件(纯头文件组件),不编译framework,但仍发布头文件形态的二进制podspec"
189
+ Build.log.warn(warn_msg)
190
+ pusher.push("【⚠️纯头文件组件提醒】", warn_msg)
191
+ else
192
+ build_item.header_only = false
193
+ warn_msg = "当前组件无实现文件也无头文件,不构建二进制"
194
+ Build.log.warn(warn_msg)
195
+ pusher.push("【⚠️组件不构建二进制提醒】", warn_msg)
196
+ end
197
+ end
198
+ end
199
+
200
+ # 按配置裁剪了subspec的组件(update_podspec 会在build_metadata写入subspec_note),
201
+ # 各仓库提交信息据此追加关键字,便于在提交历史中快速识别与检索
202
+ def subspec_pruned?
203
+ metadata = podspec_content[PODSPEC_METADATA_KEY] || {}
204
+ !metadata["subspec_note"].to_s.empty?
205
+ end
206
+
207
+ # 各仓库提交信息的关键字前缀:裁剪subspec / 模拟器产物裁剪arm64架构,可同时存在;
208
+ # 统一放在提交信息最前边,git log 列表展示被截断时也能看到
209
+ def commit_keyword_prefix
210
+ prefix = +""
211
+ prefix << "[裁剪subspec] " if subspec_pruned?
212
+ prefix << "[裁剪架构] " if build_item.respond_to?(:simulator_exclude_arm64) && build_item.simulator_exclude_arm64
213
+ prefix
214
+ end
215
+
216
+ def get_commit_msg
217
+ # 扩展信息统一读取 build_metadata 嵌套字段
218
+ metadata = podspec_content[PODSPEC_METADATA_KEY] || {}
219
+ committer = metadata["committer"]
220
+ commit_time = metadata["commit_time"]
221
+ # 提交信息里的 SHA 仅供人工溯源,对齐 CocoaPods 官方 Specs 仓惯例取 7 位短哈希;
222
+ # 注意:build_metadata.commit_id(podspec.json 内)必须保留完整 40 位,
223
+ # 发布校验需拿它与 Gitee/GitLab API 返回的完整 SHA 精确比对,不可一并缩短
224
+ short_commit_id = metadata["commit_id"].to_s[0, 7]
225
+ "#{commit_keyword_prefix}#{self.pod_name} #{self.pod_version_final} (#{committer} #{commit_time} #{short_commit_id})"
226
+ end
227
+
228
+ # 重写父类方法
229
+ def git_commit(commit_msg = nil)
230
+ commit_msg = get_commit_msg
231
+ super commit_msg
232
+ end
233
+
234
+ # 打标签
235
+ def git_tag
236
+ current = Dir.pwd
237
+ Dir.chdir(self.path)
238
+
239
+ dir_name = File.basename(self.path)
240
+ Build.log.step("推送版本号tag(#{dir_name})")
241
+
242
+ # 丢弃修改并拉取代码
243
+ discard_pull
244
+
245
+ tag = version_tag
246
+
247
+ # snapshot和三方库 需要删除老的标签
248
+ if self.build_item.snapshot or self.third
249
+ # 删除本地标签
250
+ git_tag_delete_local(tag)
251
+
252
+ # 删除远端标签
253
+ git_tag_delete_origin(tag)
254
+ end
255
+
256
+ # 本地打标签
257
+ git_tag_local(tag)
258
+
259
+ # 推送到远端
260
+ git_tag_origin(tag)
261
+
262
+ Dir.chdir(current)
263
+ end
264
+
265
+ # 重写父类方法 clone完毕后获取仓库信息
266
+ def git_clone(only_head)
267
+ super
268
+ info = hosting.find_project(pod_name, http_url, can_raise: true)
269
+
270
+ unless info
271
+ return
272
+ end
273
+
274
+ @repo_info = info
275
+ Build.log.info("查询(#{http_url})仓库信息: #{info.full_name} 命名空间:#{info.namespace_path}")
276
+
277
+ end
278
+
279
+ end
280
+
281
+ end