pindo 5.10.6 → 5.10.9
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 +4 -4
- data/lib/pindo/base/funlog.rb +12 -1
- data/lib/pindo/base/pindocontext.rb +3 -0
- data/lib/pindo/command/android/autobuild.rb +62 -9
- data/lib/pindo/command/android/build.rb +6 -6
- data/lib/pindo/command/android/debug.rb +9 -9
- data/lib/pindo/command/android.rb +1 -1
- data/lib/pindo/command/appstore.rb +1 -1
- data/lib/pindo/command/deploy/build.rb +2 -4
- data/lib/pindo/command/deploy/cert.rb +4 -5
- data/lib/pindo/command/deploy/configproj.rb +3 -3
- data/lib/pindo/command/deploy/confusecode.rb +1 -1
- data/lib/pindo/command/deploy/confuseproj.rb +1 -1
- data/lib/pindo/command/deploy/pem.rb +3 -4
- data/lib/pindo/command/dev/autobuild.rb +1 -1
- data/lib/pindo/command/dev/build.rb +1 -1
- data/lib/pindo/command/dev/feishu.rb +1 -1
- data/lib/pindo/command/gplay/pullconfig.rb +48 -0
- data/lib/pindo/command/gplay.rb +7 -6
- data/lib/pindo/command/ios/adhoc.rb +6 -5
- data/lib/pindo/command/ios/autobuild.rb +24 -24
- data/lib/pindo/command/ios/build.rb +7 -6
- data/lib/pindo/command/ios/debug.rb +1 -0
- data/lib/pindo/command/ios.rb +1 -1
- data/lib/pindo/command/ipa/import.rb +2 -3
- data/lib/pindo/command/ipa/output.rb +2 -3
- data/lib/pindo/command/jps/upload.rb +6 -5
- data/lib/pindo/command/unity/apk.rb +19 -2
- data/lib/pindo/command/unity/autobuild.rb +58 -63
- data/lib/pindo/command/unity/initpack.rb +1 -1
- data/lib/pindo/command/unity/ipa.rb +17 -14
- data/lib/pindo/command/unity/pack.rb +1 -1
- data/lib/pindo/command/unity/upload.rb +1 -1
- data/lib/pindo/command/unity/web.rb +2 -2
- data/lib/pindo/command/utils/icon.rb +1 -1
- data/lib/pindo/command/utils/renewcert.rb +1 -2
- data/lib/pindo/command/utils/renewproj.rb +9 -10
- data/lib/pindo/command/web/autobuild.rb +2 -2
- data/lib/pindo/command/web/run.rb +1 -1
- data/lib/pindo/command.rb +2 -1
- data/lib/pindo/module/android/android_build_config_helper.rb +267 -35
- data/lib/pindo/module/android/android_build_helper.rb +300 -0
- data/lib/pindo/module/android/android_project_helper.rb +279 -0
- data/lib/pindo/module/android/gp_compliance_helper.rb +33 -87
- data/lib/pindo/module/android/gradle_helper.rb +524 -255
- data/lib/pindo/module/android/java_env_helper.rb +633 -0
- data/lib/pindo/module/android/keystore_helper.rb +1118 -0
- data/lib/pindo/module/appselect.rb +109 -0
- data/lib/pindo/module/appstore/appstore_in_app_purchase.rb +1 -1
- data/lib/pindo/module/build/{buildhelper.rb → build_helper.rb} +1 -2
- data/lib/pindo/module/build/{commonconfuseproj.rb → confuse_xcodeproj.rb} +2 -2
- data/lib/pindo/module/cert/cert_helper.rb +245 -0
- data/lib/pindo/module/cert/keychain_helper.rb +152 -0
- data/lib/pindo/module/cert/pem_helper.rb +67 -0
- data/lib/pindo/module/cert/xcodecerthelper.rb +2 -2
- data/lib/pindo/module/pgyer/pgyerhelper.rb +21 -1
- data/lib/pindo/module/{build/unityhelper.rb → unity/unity_helper.rb} +0 -17
- data/lib/pindo/module/xcode/{xcodereshandler.rb → res/xcode_res_handler.rb} +1 -1
- data/lib/pindo/module/xcode/{xcodebuildconfig.rb → xcode_build_config.rb} +7 -6
- data/lib/pindo/module/xcode/{xcodebuildhelper.rb → xcode_build_helper.rb} +4 -3
- data/lib/pindo/module/xcode/{xcodehelper.rb → xcode_project_helper.rb} +4 -2
- data/lib/pindo/module/xcode/{xcodereshelper.rb → xcode_res_helper.rb} +12 -9
- data/lib/pindo/version.rb +185 -7
- data/lib/pindo.rb +14 -9
- metadata +24 -23
- data/lib/pindo/module/android/apk_helper.rb +0 -138
- data/lib/pindo/module/android/base_helper.rb +0 -964
- data/lib/pindo/module/android/build_helper.rb +0 -128
- data/lib/pindo/module/android/so_helper.rb +0 -18
- data/lib/pindo/module/cert/certhelper.rb +0 -246
- data/lib/pindo/module/cert/keychainhelper.rb +0 -150
- data/lib/pindo/module/cert/pemhelper.rb +0 -65
- /data/lib/pindo/module/android/{androidreshelper.rb → android_res_helper.rb} +0 -0
- /data/lib/pindo/module/appstore/{iap_tier.json → appstore_iap_tier.json} +0 -0
- /data/lib/pindo/module/build/{swarkhelper.rb → swark_helper.rb} +0 -0
- /data/lib/pindo/module/build/{versionhelper.rb → version_helper.rb} +0 -0
- /data/lib/pindo/module/cert/{provisioninghelper.rb → provisioning_helper.rb} +0 -0
- /data/lib/pindo/module/unity/{nugethelper.rb → nuget_helper.rb} +0 -0
- /data/lib/pindo/module/xcode/{xcoderesconstant.rb → res/xcode_res_constant.rb} +0 -0
|
@@ -0,0 +1,1118 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'json'
|
|
3
|
+
require_relative '../../config/pindoconfig'
|
|
4
|
+
|
|
5
|
+
module Pindo
|
|
6
|
+
module KeystoreHelper
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
# 从 PindoConfig 统一配置中读取 Android 签名配置
|
|
10
|
+
# @return [Hash, nil] 签名配置哈希,包含 debug 和 release 配置
|
|
11
|
+
def get_android_sign_config
|
|
12
|
+
begin
|
|
13
|
+
# 使用 PindoConfig 单例获取配置
|
|
14
|
+
pindo_config = Pindoconfig.instance
|
|
15
|
+
|
|
16
|
+
# 从 pindo_user_config_json 中获取 android_sign_config
|
|
17
|
+
android_sign_config = pindo_config.pindo_user_config_json["android_sign_config"]
|
|
18
|
+
|
|
19
|
+
if android_sign_config.nil? || android_sign_config.empty?
|
|
20
|
+
Funlog.warning("配置中未找到 android_sign_config")
|
|
21
|
+
return nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# 验证配置结构
|
|
25
|
+
unless validate_sign_config(android_sign_config)
|
|
26
|
+
Funlog.error("android_sign_config 配置格式不正确")
|
|
27
|
+
return nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# 解析文件路径
|
|
31
|
+
android_sign_config = resolve_keystore_paths(android_sign_config)
|
|
32
|
+
|
|
33
|
+
return android_sign_config
|
|
34
|
+
|
|
35
|
+
rescue => e
|
|
36
|
+
Funlog.error("读取签名配置失败: #{e.message}")
|
|
37
|
+
return nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# 验证签名配置格式
|
|
42
|
+
# @param config [Hash] 签名配置
|
|
43
|
+
# @return [Boolean] 是否有效
|
|
44
|
+
def validate_sign_config(config)
|
|
45
|
+
return false unless config.is_a?(Hash)
|
|
46
|
+
|
|
47
|
+
# 至少需要有 debug 或 release 配置
|
|
48
|
+
return false if config["debug"].nil? && config["release"].nil?
|
|
49
|
+
|
|
50
|
+
# 验证每个配置的必需字段
|
|
51
|
+
["debug", "release"].each do |build_type|
|
|
52
|
+
next if config[build_type].nil?
|
|
53
|
+
|
|
54
|
+
cfg = config[build_type]
|
|
55
|
+
required_fields = ["storeFile", "storePassword", "keyAlias", "keyPassword"]
|
|
56
|
+
|
|
57
|
+
required_fields.each do |field|
|
|
58
|
+
if cfg[field].nil? || cfg[field].to_s.empty?
|
|
59
|
+
Funlog.error("#{build_type} 配置缺少必需字段: #{field}")
|
|
60
|
+
return false
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# 解析 keystore 文件路径
|
|
69
|
+
# @param config [Hash] 签名配置
|
|
70
|
+
# @return [Hash] 解析后的配置
|
|
71
|
+
def resolve_keystore_paths(config)
|
|
72
|
+
# 使用 PindoConfig 获取配置目录
|
|
73
|
+
pindo_config = Pindoconfig.instance
|
|
74
|
+
pindo_common_config_dir = pindo_config.pindo_common_configdir
|
|
75
|
+
|
|
76
|
+
resolved_config = {}
|
|
77
|
+
|
|
78
|
+
["debug", "release"].each do |build_type|
|
|
79
|
+
next if config[build_type].nil?
|
|
80
|
+
|
|
81
|
+
cfg = config[build_type].dup
|
|
82
|
+
store_file = cfg["storeFile"]
|
|
83
|
+
|
|
84
|
+
# 如果是相对路径,则基于 pindo_common_config 目录解析
|
|
85
|
+
unless store_file.start_with?("/")
|
|
86
|
+
cfg["storeFile"] = File.join(pindo_common_config_dir, store_file)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# 验证文件是否存在
|
|
90
|
+
unless File.exist?(cfg["storeFile"])
|
|
91
|
+
Funlog.warning("Keystore 文件不存在: #{cfg["storeFile"]}")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
resolved_config[build_type] = cfg
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
resolved_config
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# 从工程的 build.gradle 中读取已有的 keystore 配置
|
|
101
|
+
# @param project_path [String] 项目路径
|
|
102
|
+
# @param debug [Boolean] 是否为 debug 构建
|
|
103
|
+
# @return [Hash, nil] keystore 配置哈希
|
|
104
|
+
def get_keystore_config_from_project(project_path, debug = false)
|
|
105
|
+
main_module = Pindo::AndroidProjectHelper.get_main_module(project_path)
|
|
106
|
+
return nil unless main_module
|
|
107
|
+
|
|
108
|
+
gradle_kts_path = File.join(main_module, "build.gradle.kts")
|
|
109
|
+
gradle_path = File.join(main_module, "build.gradle")
|
|
110
|
+
|
|
111
|
+
if File.exist?(gradle_kts_path)
|
|
112
|
+
puts "KTS 项目,读取 #{File.basename(gradle_kts_path)} 文件"
|
|
113
|
+
get_keystore_config_kts(gradle_kts_path, project_path, debug)
|
|
114
|
+
elsif File.exist?(gradle_path)
|
|
115
|
+
puts "Groovy 项目,读取 #{File.basename(gradle_path)} 文件"
|
|
116
|
+
get_keystore_config_groovy(gradle_path, project_path, debug)
|
|
117
|
+
else
|
|
118
|
+
puts "未找到 build.gradle 或 build.gradle.kts 文件"
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# 将签名配置应用到 Android 工程
|
|
124
|
+
# 新策略:
|
|
125
|
+
# 1. 从 buildTypes 中找到 debug/release 引用的 signingConfig 名称
|
|
126
|
+
# 2. 检查该 signingConfig 配置是否存在且完整
|
|
127
|
+
# 3. 如果不完整,用 Pindo 配置更新该 signingConfig
|
|
128
|
+
# 4. 不修改 buildTypes 中的引用名称
|
|
129
|
+
# @param project_dir [String] 项目目录
|
|
130
|
+
# @param build_type [String] 构建类型 "debug" 或 "release"
|
|
131
|
+
# @return [Boolean] 是否成功
|
|
132
|
+
def apply_keystore_config(project_dir, build_type = "debug")
|
|
133
|
+
raise ArgumentError, "项目目录不能为空" if project_dir.nil?
|
|
134
|
+
raise ArgumentError, "build_type 必须是 debug 或 release" unless ["debug", "release"].include?(build_type)
|
|
135
|
+
|
|
136
|
+
# 查找主模块的 build.gradle
|
|
137
|
+
main_module = Pindo::AndroidProjectHelper.get_main_module(project_dir)
|
|
138
|
+
unless main_module
|
|
139
|
+
Funlog.error("无法找到主模块")
|
|
140
|
+
return false
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
gradle_file = find_gradle_file(main_module)
|
|
144
|
+
unless gradle_file
|
|
145
|
+
Funlog.error("无法找到 build.gradle 文件")
|
|
146
|
+
return false
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
puts "检查并配置 keystore 签名..."
|
|
150
|
+
|
|
151
|
+
# 获取签名配置并拷贝 keystore 文件到项目
|
|
152
|
+
sign_config = get_android_sign_config
|
|
153
|
+
if sign_config && sign_config[build_type]
|
|
154
|
+
# 拷贝文件并更新配置路径
|
|
155
|
+
copy_keystore_to_project(project_dir, build_type, sign_config[build_type])
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# 根据文件类型选择处理方法,传递修改后的配置
|
|
159
|
+
if gradle_file.end_with?(".kts")
|
|
160
|
+
ensure_keystore_config_kts(gradle_file, project_dir, build_type, sign_config)
|
|
161
|
+
else
|
|
162
|
+
ensure_keystore_config_groovy(gradle_file, project_dir, build_type, sign_config)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
# =================== Keystore 文件管理 ===================
|
|
169
|
+
|
|
170
|
+
# 拷贝 keystore 文件到项目的 signing 目录
|
|
171
|
+
# @param project_dir [String] 项目根目录
|
|
172
|
+
# @param build_type [String] 构建类型 "debug" 或 "release"
|
|
173
|
+
# @param config [Hash] keystore 配置
|
|
174
|
+
def copy_keystore_to_project(project_dir, build_type, config)
|
|
175
|
+
return unless config && config["storeFile"]
|
|
176
|
+
|
|
177
|
+
source_file = config["storeFile"]
|
|
178
|
+
return unless File.exist?(source_file)
|
|
179
|
+
|
|
180
|
+
# 创建 signing 目录
|
|
181
|
+
signing_dir = File.join(project_dir, "signing")
|
|
182
|
+
FileUtils.mkdir_p(signing_dir) unless File.directory?(signing_dir)
|
|
183
|
+
|
|
184
|
+
# 生成目标文件名(使用 build_type 作为前缀)
|
|
185
|
+
source_filename = File.basename(source_file)
|
|
186
|
+
target_filename = "#{build_type}_#{source_filename}"
|
|
187
|
+
target_file = File.join(signing_dir, target_filename)
|
|
188
|
+
|
|
189
|
+
# 拷贝文件(如果源文件和目标文件不同)
|
|
190
|
+
unless File.exist?(target_file) && FileUtils.identical?(source_file, target_file)
|
|
191
|
+
puts " 拷贝 keystore 文件到项目: signing/#{target_filename}"
|
|
192
|
+
FileUtils.cp(source_file, target_file)
|
|
193
|
+
# 设置文件权限为只读
|
|
194
|
+
File.chmod(0444, target_file)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# 更新配置中的路径为相对路径(用于后续生成配置)
|
|
198
|
+
config["relative_store_file"] = "$rootDir/signing/#{target_filename}"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# =================== 确保 keystore 配置的核心方法 ===================
|
|
202
|
+
|
|
203
|
+
# 确保 Groovy 格式的 build.gradle 有完整的 keystore 配置
|
|
204
|
+
# 新策略:
|
|
205
|
+
# 1. 检查 buildTypes 中是否有 signingConfig 引用
|
|
206
|
+
# 2. 如果有引用,始终更新对应的 signingConfigs 为相对路径
|
|
207
|
+
# 3. 不再检查配置是否"完整",直接替换
|
|
208
|
+
def ensure_keystore_config_groovy(gradle_file, project_dir, build_type, sign_config = nil)
|
|
209
|
+
content = File.read(gradle_file)
|
|
210
|
+
original_content = content.dup
|
|
211
|
+
|
|
212
|
+
# 使用更宽松的方式提取 signingConfig 引用
|
|
213
|
+
signing_config_name = extract_signing_config_reference_safely_groovy(content, build_type)
|
|
214
|
+
|
|
215
|
+
if signing_config_name
|
|
216
|
+
# buildTypes 中已有 signingConfig 引用
|
|
217
|
+
puts " buildTypes.#{build_type} 引用的签名配置: #{signing_config_name}"
|
|
218
|
+
|
|
219
|
+
# 获取 Pindo 配置
|
|
220
|
+
sign_config ||= get_android_sign_config
|
|
221
|
+
pindo_config = sign_config ? sign_config[build_type] : nil
|
|
222
|
+
|
|
223
|
+
unless pindo_config
|
|
224
|
+
Funlog.error("无法获取 Pindo 配置")
|
|
225
|
+
return false
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# 确保使用相对路径
|
|
229
|
+
unless pindo_config["relative_store_file"]
|
|
230
|
+
source_filename = File.basename(pindo_config["storeFile"])
|
|
231
|
+
pindo_config["relative_store_file"] = "$rootDir/signing/#{build_type}_#{source_filename}"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# 始终更新 signingConfigs 为 Pindo 配置(确保使用相对路径)
|
|
235
|
+
puts " → 更新 signingConfigs.#{signing_config_name} 为 Pindo 配置"
|
|
236
|
+
puts " 使用路径: #{pindo_config["relative_store_file"]}"
|
|
237
|
+
|
|
238
|
+
# 先删除旧配置,再注入新配置
|
|
239
|
+
content = inject_signing_config_groovy(content, signing_config_name, pindo_config)
|
|
240
|
+
else
|
|
241
|
+
# 没有 signingConfig 引用,跳过
|
|
242
|
+
puts " ⚠ buildTypes.#{build_type} 中未找到 signingConfig 引用"
|
|
243
|
+
puts " → 跳过配置(避免破坏 buildTypes 结构)"
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# 写入文件
|
|
247
|
+
if content != original_content
|
|
248
|
+
File.write(gradle_file, content)
|
|
249
|
+
puts " ✓ build.gradle 已更新"
|
|
250
|
+
else
|
|
251
|
+
puts " ✓ build.gradle 无需修改"
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
true
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# 确保 Kotlin DSL 格式的 build.gradle.kts 有完整的 keystore 配置
|
|
258
|
+
# 新策略:
|
|
259
|
+
# 1. 检查 buildTypes 中是否有 signingConfig 引用
|
|
260
|
+
# 2. 如果有引用,始终更新对应的 signingConfigs 为相对路径
|
|
261
|
+
# 3. 不再检查配置是否"完整",直接替换
|
|
262
|
+
def ensure_keystore_config_kts(gradle_file, project_dir, build_type, sign_config = nil)
|
|
263
|
+
content = File.read(gradle_file)
|
|
264
|
+
original_content = content.dup
|
|
265
|
+
|
|
266
|
+
# 使用更宽松的方式提取 signingConfig 引用
|
|
267
|
+
signing_config_name = extract_signing_config_reference_safely_kts(content, build_type)
|
|
268
|
+
|
|
269
|
+
if signing_config_name
|
|
270
|
+
# buildTypes 中已有 signingConfig 引用
|
|
271
|
+
puts " buildTypes.#{build_type} 引用的签名配置: #{signing_config_name}"
|
|
272
|
+
|
|
273
|
+
# 获取 Pindo 配置
|
|
274
|
+
sign_config ||= get_android_sign_config
|
|
275
|
+
pindo_config = sign_config ? sign_config[build_type] : nil
|
|
276
|
+
|
|
277
|
+
unless pindo_config
|
|
278
|
+
Funlog.error("无法获取 Pindo 配置")
|
|
279
|
+
return false
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# 确保使用相对路径
|
|
283
|
+
unless pindo_config["relative_store_file"]
|
|
284
|
+
source_filename = File.basename(pindo_config["storeFile"])
|
|
285
|
+
pindo_config["relative_store_file"] = "$rootDir/signing/#{build_type}_#{source_filename}"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# 始终更新 signingConfigs 为 Pindo 配置(确保使用相对路径)
|
|
289
|
+
puts " → 更新 signingConfigs.#{signing_config_name} 为 Pindo 配置"
|
|
290
|
+
puts " 使用路径: #{pindo_config["relative_store_file"]}"
|
|
291
|
+
|
|
292
|
+
# 先删除旧配置,再注入新配置
|
|
293
|
+
content = inject_signing_config_kts(content, signing_config_name, pindo_config)
|
|
294
|
+
else
|
|
295
|
+
# 没有 signingConfig 引用,跳过
|
|
296
|
+
puts " ⚠ buildTypes.#{build_type} 中未找到 signingConfig 引用"
|
|
297
|
+
puts " → 跳过配置(避免破坏 buildTypes 结构)"
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# 写入文件
|
|
301
|
+
if content != original_content
|
|
302
|
+
File.write(gradle_file, content)
|
|
303
|
+
puts " ✓ build.gradle.kts 已更新"
|
|
304
|
+
else
|
|
305
|
+
puts " ✓ build.gradle.kts 无需修改"
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
true
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# 安全地从 buildTypes 中提取 signingConfig 引用(Groovy)
|
|
312
|
+
# 使用更宽松的正则,支持各种格式
|
|
313
|
+
def extract_signing_config_reference_safely_groovy(content, build_type)
|
|
314
|
+
# 提取整个 buildTypes 块
|
|
315
|
+
build_types_match = content.match(/buildTypes\s*\{[\s\S]*?^\}/m)
|
|
316
|
+
return nil unless build_types_match
|
|
317
|
+
|
|
318
|
+
build_types_block = build_types_match[0]
|
|
319
|
+
|
|
320
|
+
# 使用更宽松的正则查找 buildType 块和其中的 signingConfig
|
|
321
|
+
# 支持格式:
|
|
322
|
+
# 1. buildTypes { debug {
|
|
323
|
+
# 2. buildTypes {\n debug {
|
|
324
|
+
# 3. buildTypes {release { debug {
|
|
325
|
+
if build_types_block =~ /#{build_type}\s*\{[^}]*signingConfig\s+signingConfigs\.(\w+)/m
|
|
326
|
+
return $1
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
nil
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# 从 buildTypes 中提取 signingConfig 引用(Groovy)- 保留旧函数供其他地方使用
|
|
333
|
+
def extract_signing_config_reference_from_build_types_groovy(content, build_type)
|
|
334
|
+
# 提取 buildTypes 块
|
|
335
|
+
build_types_match = content.match(/buildTypes\s*\{([\s\S]*?)^\s*\}/m)
|
|
336
|
+
return nil unless build_types_match
|
|
337
|
+
|
|
338
|
+
build_types_content = build_types_match[1]
|
|
339
|
+
|
|
340
|
+
# 提取指定 buildType 的块
|
|
341
|
+
build_type_block = extract_build_type_block_from_content_groovy(build_types_content, build_type)
|
|
342
|
+
return nil unless build_type_block
|
|
343
|
+
|
|
344
|
+
# 提取 signingConfig 引用
|
|
345
|
+
if build_type_block =~ /signingConfig\s+signingConfigs\.(\w+)/
|
|
346
|
+
return $1
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
nil
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# 安全地从 buildTypes 中提取 signingConfig 引用(Kotlin DSL)
|
|
353
|
+
def extract_signing_config_reference_safely_kts(content, build_type)
|
|
354
|
+
# 提取整个 buildTypes 块
|
|
355
|
+
build_types_match = content.match(/buildTypes\s*\{[\s\S]*?^\}/m)
|
|
356
|
+
return nil unless build_types_match
|
|
357
|
+
|
|
358
|
+
build_types_block = build_types_match[0]
|
|
359
|
+
|
|
360
|
+
# 使用更宽松的正则查找 buildType 块和其中的 signingConfig
|
|
361
|
+
if build_types_block =~ /getByName\s*\(\s*"#{build_type}"\s*\)\s*\{[^}]*signingConfig\s*=\s*signingConfigs\.getByName\s*\(\s*"(\w+)"\s*\)/m
|
|
362
|
+
return $1
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
nil
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# 从 buildTypes 中提取 signingConfig 引用(Kotlin DSL)- 保留旧函数供其他地方使用
|
|
369
|
+
def extract_signing_config_reference_from_build_types_kts(content, build_type)
|
|
370
|
+
# 提取 buildTypes 块
|
|
371
|
+
build_types_match = content.match(/buildTypes\s*\{([\s\S]*?)^\s*\}/m)
|
|
372
|
+
return nil unless build_types_match
|
|
373
|
+
|
|
374
|
+
build_types_content = build_types_match[1]
|
|
375
|
+
|
|
376
|
+
# 提取指定 buildType 的块
|
|
377
|
+
build_type_block = extract_build_type_block_from_content_kts(build_types_content, build_type)
|
|
378
|
+
return nil unless build_type_block
|
|
379
|
+
|
|
380
|
+
# 提取 signingConfig 引用
|
|
381
|
+
if build_type_block =~ /signingConfig\s*=\s*signingConfigs\.getByName\s*\(\s*"(\w+)"\s*\)/
|
|
382
|
+
return $1
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
nil
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# 从内容中提取 buildType 块(Groovy)
|
|
389
|
+
def extract_build_type_block_from_content_groovy(content, build_type)
|
|
390
|
+
match = content.match(/^\s*#{build_type}\s*\{/m)
|
|
391
|
+
return nil unless match
|
|
392
|
+
|
|
393
|
+
start_pos = match.begin(0)
|
|
394
|
+
brace_count = 0
|
|
395
|
+
in_block = false
|
|
396
|
+
end_pos = start_pos
|
|
397
|
+
|
|
398
|
+
content[start_pos..-1].each_char.with_index(start_pos) do |char, i|
|
|
399
|
+
if char == '{'
|
|
400
|
+
brace_count += 1
|
|
401
|
+
in_block = true
|
|
402
|
+
elsif char == '}'
|
|
403
|
+
brace_count -= 1
|
|
404
|
+
if in_block && brace_count == 0
|
|
405
|
+
end_pos = i + 1
|
|
406
|
+
break
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
content[start_pos...end_pos]
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# 从内容中提取 buildType 块(Kotlin DSL)
|
|
415
|
+
def extract_build_type_block_from_content_kts(content, build_type)
|
|
416
|
+
match = content.match(/^\s*getByName\s*\(\s*"#{build_type}"\s*\)\s*\{/m)
|
|
417
|
+
return nil unless match
|
|
418
|
+
|
|
419
|
+
start_pos = match.begin(0)
|
|
420
|
+
brace_count = 0
|
|
421
|
+
in_block = false
|
|
422
|
+
end_pos = start_pos
|
|
423
|
+
|
|
424
|
+
content[start_pos..-1].each_char.with_index(start_pos) do |char, i|
|
|
425
|
+
if char == '{'
|
|
426
|
+
brace_count += 1
|
|
427
|
+
in_block = true
|
|
428
|
+
elsif char == '}'
|
|
429
|
+
brace_count -= 1
|
|
430
|
+
if in_block && brace_count == 0
|
|
431
|
+
end_pos = i + 1
|
|
432
|
+
break
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
content[start_pos...end_pos]
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# 检查 signing config 是否存在且完整(Groovy)
|
|
441
|
+
# 增强检查:不仅检查字段是否存在,还检查是否使用了相对路径
|
|
442
|
+
def check_signing_config_groovy(content, config_name)
|
|
443
|
+
signing_configs_block = extract_signing_configs_groovy(content)
|
|
444
|
+
return nil unless signing_configs_block
|
|
445
|
+
|
|
446
|
+
config_block = extract_config_block_by_name_groovy(signing_configs_block, config_name)
|
|
447
|
+
return nil unless config_block
|
|
448
|
+
|
|
449
|
+
config_block = remove_groovy_comments(config_block)
|
|
450
|
+
|
|
451
|
+
# 检查是否包含所有必需字段
|
|
452
|
+
has_store_file = config_block =~ /storeFile\s+/
|
|
453
|
+
has_store_password = config_block =~ /storePassword\s+/
|
|
454
|
+
has_key_alias = config_block =~ /keyAlias\s+/
|
|
455
|
+
has_key_password = config_block =~ /keyPassword\s+/
|
|
456
|
+
|
|
457
|
+
# 检查是否使用了相对路径(rootProject.file 或 signing 目录)
|
|
458
|
+
uses_relative_path = config_block =~ /storeFile\s+rootProject\.file/ ||
|
|
459
|
+
config_block =~ /storeFile\s+file\(["']signing\//
|
|
460
|
+
|
|
461
|
+
# 检查是否使用了绝对路径(不推荐)
|
|
462
|
+
uses_absolute_path = config_block =~ /storeFile\s+file\(["']\/Users\// ||
|
|
463
|
+
config_block =~ /storeFile\s+file\(["']\/home\// ||
|
|
464
|
+
config_block =~ /storeFile\s+file\(["']C:\\/
|
|
465
|
+
|
|
466
|
+
if has_store_file && has_store_password && has_key_alias && has_key_password
|
|
467
|
+
if uses_absolute_path
|
|
468
|
+
# 使用了绝对路径,需要更新
|
|
469
|
+
puts " ⚠ 检测到使用绝对路径,需要更新为相对路径"
|
|
470
|
+
nil
|
|
471
|
+
elsif uses_relative_path
|
|
472
|
+
# 使用了相对路径,配置正确
|
|
473
|
+
{ exists: true, relative_path: true }
|
|
474
|
+
else
|
|
475
|
+
# 可能使用了其他形式的路径,保守处理
|
|
476
|
+
{ exists: true }
|
|
477
|
+
end
|
|
478
|
+
else
|
|
479
|
+
nil
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# 检查 signing config 是否存在且完整(Kotlin DSL)
|
|
484
|
+
# Pindo 策略:简单检查配置是否存在,如果存在就认为有效
|
|
485
|
+
# 不解析复杂的变量引用,统一用 Pindo 配置替换
|
|
486
|
+
def check_signing_config_kts(content, config_name)
|
|
487
|
+
signing_configs_block = extract_signing_configs_kts(content)
|
|
488
|
+
return nil unless signing_configs_block
|
|
489
|
+
|
|
490
|
+
config_block = extract_config_block_by_name_kts(signing_configs_block, config_name)
|
|
491
|
+
return nil unless config_block
|
|
492
|
+
|
|
493
|
+
config_block = remove_kts_comments(config_block)
|
|
494
|
+
|
|
495
|
+
# 检查是否包含所有必需字段
|
|
496
|
+
has_store_file = config_block =~ /storeFile\s*=/
|
|
497
|
+
has_store_password = config_block =~ /storePassword\s*=/
|
|
498
|
+
has_key_alias = config_block =~ /keyAlias\s*=/
|
|
499
|
+
has_key_password = config_block =~ /keyPassword\s*=/
|
|
500
|
+
|
|
501
|
+
# 检查是否使用了相对路径(rootProject.file 或 signing 目录)
|
|
502
|
+
uses_relative_path = config_block =~ /storeFile\s*=\s*rootProject\.file/ ||
|
|
503
|
+
config_block =~ /storeFile\s*=\s*file\(["']signing\//
|
|
504
|
+
|
|
505
|
+
# 检查是否使用了绝对路径(不推荐)
|
|
506
|
+
uses_absolute_path = config_block =~ /storeFile\s*=\s*file\(["']\/Users\// ||
|
|
507
|
+
config_block =~ /storeFile\s*=\s*file\(["']\/home\// ||
|
|
508
|
+
config_block =~ /storeFile\s*=\s*file\(["']C:\\/
|
|
509
|
+
|
|
510
|
+
if has_store_file && has_store_password && has_key_alias && has_key_password
|
|
511
|
+
if uses_absolute_path
|
|
512
|
+
# 使用了绝对路径,需要更新
|
|
513
|
+
puts " ⚠ 检测到使用绝对路径,需要更新为相对路径"
|
|
514
|
+
nil
|
|
515
|
+
elsif uses_relative_path
|
|
516
|
+
# 使用了相对路径,配置正确
|
|
517
|
+
{ exists: true, relative_path: true }
|
|
518
|
+
else
|
|
519
|
+
# 可能使用了其他形式的路径,保守处理
|
|
520
|
+
{ exists: true }
|
|
521
|
+
end
|
|
522
|
+
else
|
|
523
|
+
nil
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
# 注入签名配置(Groovy)
|
|
528
|
+
def inject_signing_config_groovy(content, config_name, pindo_config)
|
|
529
|
+
signing_config_block = generate_signing_config_groovy(config_name, pindo_config)
|
|
530
|
+
|
|
531
|
+
# 删除已存在的同名配置
|
|
532
|
+
content = remove_signing_config_by_name_groovy(content, config_name)
|
|
533
|
+
|
|
534
|
+
# 插入新配置
|
|
535
|
+
insert_signing_config_groovy(content, signing_config_block)
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
# 注入签名配置(Kotlin DSL)
|
|
539
|
+
def inject_signing_config_kts(content, config_name, pindo_config)
|
|
540
|
+
signing_config_block = generate_signing_config_kts(config_name, pindo_config)
|
|
541
|
+
|
|
542
|
+
# 删除已存在的同名配置
|
|
543
|
+
content = remove_signing_config_by_name_kts(content, config_name)
|
|
544
|
+
|
|
545
|
+
# 插入新配置
|
|
546
|
+
insert_signing_config_kts(content, signing_config_block)
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# 删除指定名称的签名配置(Groovy)
|
|
550
|
+
# 只删除 signingConfigs 块中的配置,不影响其他地方
|
|
551
|
+
def remove_signing_config_by_name_groovy(content, config_name)
|
|
552
|
+
# 先找到 signingConfigs 块的位置
|
|
553
|
+
if content =~ /signingConfigs\s*\{/
|
|
554
|
+
# 找到 signingConfigs 块的开始位置
|
|
555
|
+
start_match = $~
|
|
556
|
+
start_pos = start_match.end(0)
|
|
557
|
+
|
|
558
|
+
# 找到对应的结束大括号
|
|
559
|
+
brace_count = 1
|
|
560
|
+
end_pos = start_pos
|
|
561
|
+
content[start_pos..-1].each_char.with_index do |char, index|
|
|
562
|
+
if char == '{'
|
|
563
|
+
brace_count += 1
|
|
564
|
+
elsif char == '}'
|
|
565
|
+
brace_count -= 1
|
|
566
|
+
if brace_count == 0
|
|
567
|
+
end_pos = start_pos + index
|
|
568
|
+
break
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
# 提取 signingConfigs 的内容
|
|
574
|
+
signing_configs_content = content[start_pos...end_pos]
|
|
575
|
+
|
|
576
|
+
# 从内容中删除指定的配置块
|
|
577
|
+
modified_content = remove_config_block_from_content(signing_configs_content, config_name)
|
|
578
|
+
|
|
579
|
+
# 重组完整内容
|
|
580
|
+
content[0...start_pos] + modified_content + content[end_pos..-1]
|
|
581
|
+
else
|
|
582
|
+
content
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
# 辅助方法:从内容中删除指定的配置块
|
|
587
|
+
def remove_config_block_from_content(content, config_name)
|
|
588
|
+
lines = content.split("\n")
|
|
589
|
+
result_lines = []
|
|
590
|
+
in_target_block = false
|
|
591
|
+
brace_count = 0
|
|
592
|
+
|
|
593
|
+
lines.each do |line|
|
|
594
|
+
# 检查是否是目标配置的开始
|
|
595
|
+
if !in_target_block && line =~ /^\s*#{config_name}\s*\{/
|
|
596
|
+
in_target_block = true
|
|
597
|
+
brace_count = 1
|
|
598
|
+
next
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
# 如果在目标块中,计算大括号
|
|
602
|
+
if in_target_block
|
|
603
|
+
line.chars.each do |char|
|
|
604
|
+
brace_count += 1 if char == '{'
|
|
605
|
+
brace_count -= 1 if char == '}'
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
# 如果大括号平衡了,说明块结束了
|
|
609
|
+
if brace_count == 0
|
|
610
|
+
in_target_block = false
|
|
611
|
+
end
|
|
612
|
+
next
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
# 不在目标块中,保留这一行
|
|
616
|
+
result_lines << line
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
result_lines.join("\n")
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
# 删除指定名称的签名配置(Kotlin DSL)
|
|
623
|
+
# 只删除 signingConfigs 块中的配置,不影响其他地方
|
|
624
|
+
def remove_signing_config_by_name_kts(content, config_name)
|
|
625
|
+
# 先找到 signingConfigs 块的位置
|
|
626
|
+
if content =~ /signingConfigs\s*\{/
|
|
627
|
+
# 找到 signingConfigs 块的开始位置
|
|
628
|
+
start_match = $~
|
|
629
|
+
start_pos = start_match.end(0)
|
|
630
|
+
|
|
631
|
+
# 找到对应的结束大括号
|
|
632
|
+
brace_count = 1
|
|
633
|
+
end_pos = start_pos
|
|
634
|
+
content[start_pos..-1].each_char.with_index do |char, index|
|
|
635
|
+
if char == '{'
|
|
636
|
+
brace_count += 1
|
|
637
|
+
elsif char == '}'
|
|
638
|
+
brace_count -= 1
|
|
639
|
+
if brace_count == 0
|
|
640
|
+
end_pos = start_pos + index
|
|
641
|
+
break
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
# 提取 signingConfigs 的内容
|
|
647
|
+
signing_configs_content = content[start_pos...end_pos]
|
|
648
|
+
|
|
649
|
+
# 从内容中删除指定的配置块(Kotlin DSL 使用 create 语法)
|
|
650
|
+
modified_content = remove_config_block_from_content_kts(signing_configs_content, config_name)
|
|
651
|
+
|
|
652
|
+
# 重组完整内容
|
|
653
|
+
content[0...start_pos] + modified_content + content[end_pos..-1]
|
|
654
|
+
else
|
|
655
|
+
content
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
# 辅助方法:从 Kotlin DSL 内容中删除指定的配置块
|
|
660
|
+
def remove_config_block_from_content_kts(content, config_name)
|
|
661
|
+
lines = content.split("\n")
|
|
662
|
+
result_lines = []
|
|
663
|
+
in_target_block = false
|
|
664
|
+
brace_count = 0
|
|
665
|
+
|
|
666
|
+
lines.each do |line|
|
|
667
|
+
# 检查是否是目标配置的开始(Kotlin DSL 格式)
|
|
668
|
+
if !in_target_block && line =~ /^\s*create\s*\(\s*["']#{config_name}["']\s*\)\s*\{/
|
|
669
|
+
in_target_block = true
|
|
670
|
+
brace_count = 1
|
|
671
|
+
next
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
# 如果在目标块中,计算大括号
|
|
675
|
+
if in_target_block
|
|
676
|
+
line.chars.each do |char|
|
|
677
|
+
brace_count += 1 if char == '{'
|
|
678
|
+
brace_count -= 1 if char == '}'
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
# 如果大括号平衡了,说明块结束了
|
|
682
|
+
if brace_count == 0
|
|
683
|
+
in_target_block = false
|
|
684
|
+
end
|
|
685
|
+
next
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
# 不在目标块中,保留这一行
|
|
689
|
+
result_lines << line
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
result_lines.join("\n")
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
# =================== 从工程读取 keystore 配置的辅助方法 ===================
|
|
696
|
+
|
|
697
|
+
# 处理 Kotlin DSL (build.gradle.kts)
|
|
698
|
+
# Pindo 策略:配置已经被统一为 signingConfigs.debug/release,直接读取即可
|
|
699
|
+
def get_keystore_config_kts(gradle_kts_path, project_path, debug)
|
|
700
|
+
content = File.read(gradle_kts_path)
|
|
701
|
+
puts "读取 #{File.basename(gradle_kts_path)} 文件"
|
|
702
|
+
|
|
703
|
+
build_type = debug ? 'debug' : 'release'
|
|
704
|
+
|
|
705
|
+
# 直接从统一的 signingConfigs.debug/release 读取
|
|
706
|
+
signing_configs_block = extract_signing_configs_kts(content)
|
|
707
|
+
return nil unless signing_configs_block
|
|
708
|
+
|
|
709
|
+
config_block = extract_config_block_by_name_kts(signing_configs_block, build_type)
|
|
710
|
+
|
|
711
|
+
# 如果找不到对应的配置,尝试使用另一个配置
|
|
712
|
+
if config_block.nil?
|
|
713
|
+
alt_build_type = debug ? 'release' : 'debug'
|
|
714
|
+
puts " 未找到 signingConfigs.#{build_type},尝试使用 signingConfigs.#{alt_build_type}"
|
|
715
|
+
config_block = extract_config_block_by_name_kts(signing_configs_block, alt_build_type)
|
|
716
|
+
return nil unless config_block
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
config_block = remove_kts_comments(config_block)
|
|
720
|
+
|
|
721
|
+
# 直接提取字段值(不处理变量引用)
|
|
722
|
+
# 支持 file() 和 rootProject.file() 两种格式
|
|
723
|
+
store_file = config_block[/storeFile\s*=\s*(?:rootProject\.)?file\(["']([^"']+)["']\)/, 1]
|
|
724
|
+
store_password = config_block[/storePassword\s*=\s*["']([^"']+)["']/, 1]
|
|
725
|
+
key_alias = config_block[/keyAlias\s*=\s*["']([^"']+)["']/, 1]
|
|
726
|
+
key_password = config_block[/keyPassword\s*=\s*["']([^"']+)["']/, 1]
|
|
727
|
+
|
|
728
|
+
# 解析相对路径
|
|
729
|
+
store_file = fix_store_file_path(store_file, project_path) if store_file
|
|
730
|
+
|
|
731
|
+
{
|
|
732
|
+
store_file: store_file,
|
|
733
|
+
store_password: store_password,
|
|
734
|
+
key_alias: key_alias,
|
|
735
|
+
key_password: key_password
|
|
736
|
+
}
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
# 处理 Groovy DSL (build.gradle)
|
|
740
|
+
def get_keystore_config_groovy(gradle_path, project_path, debug)
|
|
741
|
+
content = File.read(gradle_path)
|
|
742
|
+
puts "读取 #{File.basename(gradle_path)} 文件"
|
|
743
|
+
|
|
744
|
+
build_type = debug ? 'debug' : 'release'
|
|
745
|
+
|
|
746
|
+
# 先从 buildTypes 中找到实际引用的 signingConfig 名称
|
|
747
|
+
signing_config_name = extract_signing_config_name_from_build_type(content, build_type)
|
|
748
|
+
|
|
749
|
+
if signing_config_name.nil?
|
|
750
|
+
# 如果当前 buildType 没有配置,尝试另一个
|
|
751
|
+
alt_build_type = debug ? 'release' : 'debug'
|
|
752
|
+
puts " buildTypes.#{build_type} 未配置 signingConfig,尝试 buildTypes.#{alt_build_type}"
|
|
753
|
+
signing_config_name = extract_signing_config_name_from_build_type(content, alt_build_type)
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
if signing_config_name.nil?
|
|
757
|
+
puts " 未找到任何 signingConfig 配置"
|
|
758
|
+
return nil
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
puts " 找到签名配置: signingConfigs.#{signing_config_name}"
|
|
762
|
+
|
|
763
|
+
# 从 signingConfigs 中读取对应名称的配置
|
|
764
|
+
signing_configs_block = extract_signing_configs_groovy(content)
|
|
765
|
+
return nil unless signing_configs_block
|
|
766
|
+
|
|
767
|
+
config_block = extract_config_block_by_name_groovy(signing_configs_block, signing_config_name)
|
|
768
|
+
|
|
769
|
+
if config_block.nil?
|
|
770
|
+
puts " 未找到 signingConfigs.#{signing_config_name} 的详细配置"
|
|
771
|
+
return nil
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
config_block = remove_groovy_comments(config_block)
|
|
775
|
+
|
|
776
|
+
# 直接提取字段值(不处理变量引用)
|
|
777
|
+
# 支持 file() 和 rootProject.file() 两种格式
|
|
778
|
+
store_file = config_block[/storeFile\s+(?:rootProject\.)?file\(["']([^"']+)["']\)/, 1]
|
|
779
|
+
store_password = config_block[/storePassword\s+["']([^"']+)["']/, 1]
|
|
780
|
+
key_alias = config_block[/keyAlias\s+["']([^"']+)["']/, 1]
|
|
781
|
+
key_password = config_block[/keyPassword\s+["']([^"']+)["']/, 1]
|
|
782
|
+
|
|
783
|
+
# 解析相对路径
|
|
784
|
+
store_file = fix_store_file_path(store_file, project_path) if store_file
|
|
785
|
+
|
|
786
|
+
{
|
|
787
|
+
store_file: store_file,
|
|
788
|
+
store_password: store_password,
|
|
789
|
+
key_alias: key_alias,
|
|
790
|
+
key_password: key_password
|
|
791
|
+
}
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
def extract_signing_configs_kts(content)
|
|
795
|
+
# 提取 signingConfigs { ... },使用大括号计数来正确处理嵌套
|
|
796
|
+
if content =~ /signingConfigs\s*\{/
|
|
797
|
+
start_match = $~
|
|
798
|
+
start_pos = start_match.end(0)
|
|
799
|
+
|
|
800
|
+
# 计算大括号平衡
|
|
801
|
+
brace_count = 1
|
|
802
|
+
end_pos = start_pos
|
|
803
|
+
|
|
804
|
+
content[start_pos..-1].each_char.with_index do |char, index|
|
|
805
|
+
if char == '{'
|
|
806
|
+
brace_count += 1
|
|
807
|
+
elsif char == '}'
|
|
808
|
+
brace_count -= 1
|
|
809
|
+
if brace_count == 0
|
|
810
|
+
end_pos = start_pos + index
|
|
811
|
+
break
|
|
812
|
+
end
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
# 返回 signingConfigs 块的内容(不包括外层大括号)
|
|
817
|
+
return content[start_pos...end_pos] if brace_count == 0
|
|
818
|
+
end
|
|
819
|
+
|
|
820
|
+
nil
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
# 从 buildTypes 中提取引用的 signingConfig 名称
|
|
824
|
+
def extract_signing_config_name_from_build_type(content, build_type)
|
|
825
|
+
# 先提取 buildTypes 块
|
|
826
|
+
if content =~ /buildTypes\s*\{/
|
|
827
|
+
start_match = $~
|
|
828
|
+
start_pos = start_match.end(0)
|
|
829
|
+
|
|
830
|
+
# 计算大括号平衡来找到 buildTypes 块的结束
|
|
831
|
+
brace_count = 1
|
|
832
|
+
end_pos = start_pos
|
|
833
|
+
|
|
834
|
+
content[start_pos..-1].each_char.with_index do |char, index|
|
|
835
|
+
if char == '{'
|
|
836
|
+
brace_count += 1
|
|
837
|
+
elsif char == '}'
|
|
838
|
+
brace_count -= 1
|
|
839
|
+
if brace_count == 0
|
|
840
|
+
end_pos = start_pos + index
|
|
841
|
+
break
|
|
842
|
+
end
|
|
843
|
+
end
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
build_types_block = content[start_pos...end_pos] if brace_count == 0
|
|
847
|
+
|
|
848
|
+
if build_types_block
|
|
849
|
+
# 提取特定 buildType 的配置块
|
|
850
|
+
build_type_block = extract_config_block_by_name_groovy(build_types_block, build_type)
|
|
851
|
+
|
|
852
|
+
if build_type_block
|
|
853
|
+
# 查找 signingConfig signingConfigs.XXX
|
|
854
|
+
if build_type_block =~ /signingConfig\s+signingConfigs\.(\w+)/
|
|
855
|
+
return $1
|
|
856
|
+
end
|
|
857
|
+
end
|
|
858
|
+
end
|
|
859
|
+
end
|
|
860
|
+
|
|
861
|
+
nil
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
def extract_signing_configs_groovy(content)
|
|
865
|
+
# 提取 signingConfigs { ... },使用大括号计数来正确处理嵌套
|
|
866
|
+
if content =~ /signingConfigs\s*\{/
|
|
867
|
+
start_match = $~
|
|
868
|
+
start_pos = start_match.end(0)
|
|
869
|
+
|
|
870
|
+
# 计算大括号平衡
|
|
871
|
+
brace_count = 1
|
|
872
|
+
end_pos = start_pos
|
|
873
|
+
|
|
874
|
+
content[start_pos..-1].each_char.with_index do |char, index|
|
|
875
|
+
if char == '{'
|
|
876
|
+
brace_count += 1
|
|
877
|
+
elsif char == '}'
|
|
878
|
+
brace_count -= 1
|
|
879
|
+
if brace_count == 0
|
|
880
|
+
end_pos = start_pos + index
|
|
881
|
+
break
|
|
882
|
+
end
|
|
883
|
+
end
|
|
884
|
+
end
|
|
885
|
+
|
|
886
|
+
# 返回 signingConfigs 块的内容(不包括外层大括号)
|
|
887
|
+
return content[start_pos...end_pos] if brace_count == 0
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
nil
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
# 根据配置名称提取配置块(Kotlin DSL)
|
|
894
|
+
def extract_config_block_by_name_kts(signing_configs_block, config_name)
|
|
895
|
+
# 支持 create("xxx") { ... }
|
|
896
|
+
match = signing_configs_block.match(/create\s*\(\s*["']#{config_name}["']\s*\)\s*\{([\s\S]*?)^\s*\}/m)
|
|
897
|
+
match ? match[1] : nil
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
# 根据配置名称提取配置块(Groovy)
|
|
901
|
+
def extract_config_block_by_name_groovy(signing_configs_block, config_name)
|
|
902
|
+
# 支持 xxx { ... }
|
|
903
|
+
match = signing_configs_block.match(/#{config_name}\s*\{([\s\S]*?)^\s*\}/m)
|
|
904
|
+
match ? match[1] : nil
|
|
905
|
+
end
|
|
906
|
+
|
|
907
|
+
def remove_kts_comments(block)
|
|
908
|
+
# 去除 // 注释
|
|
909
|
+
block.lines.reject { |line| line.strip.start_with?("//") }.join
|
|
910
|
+
end
|
|
911
|
+
|
|
912
|
+
def remove_groovy_comments(block)
|
|
913
|
+
# 去除 // 和 /* ... */ 注释
|
|
914
|
+
block = block.gsub(/\/\*[\s\S]*?\*\//, "")
|
|
915
|
+
block.lines.reject { |line| line.strip.start_with?("//") }.join
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
def fix_store_file_path(store_file, project_path)
|
|
919
|
+
return nil if store_file.nil? || store_file.empty?
|
|
920
|
+
|
|
921
|
+
# 如果已经是绝对路径,直接返回
|
|
922
|
+
return store_file if store_file.start_with?('/') && File.exist?(store_file)
|
|
923
|
+
|
|
924
|
+
# 处理相对路径(如 signing/xxx.keystore)
|
|
925
|
+
# 这种路径来自于 rootProject.file("signing/xxx.keystore")
|
|
926
|
+
if !store_file.start_with?('/')
|
|
927
|
+
# 相对路径,基于项目根目录解析
|
|
928
|
+
absolute_path = File.join(project_path, store_file)
|
|
929
|
+
return absolute_path if File.exist?(absolute_path)
|
|
930
|
+
|
|
931
|
+
# 如果文件不存在,尝试其他位置
|
|
932
|
+
store_file = File.expand_path(store_file, project_path)
|
|
933
|
+
end
|
|
934
|
+
|
|
935
|
+
# 兼容旧格式:$rootDir、${rootDir}、project.rootDir
|
|
936
|
+
store_file = store_file.gsub(/^\$?\{?rootDir\}?\/?/, project_path + "/")
|
|
937
|
+
store_file = store_file.gsub(/^project\.rootDir\/?/, project_path + "/")
|
|
938
|
+
|
|
939
|
+
store_file
|
|
940
|
+
end
|
|
941
|
+
|
|
942
|
+
# =================== 写入 keystore 配置的辅助方法 ===================
|
|
943
|
+
|
|
944
|
+
# 生成签名配置代码块(Groovy)
|
|
945
|
+
def generate_signing_config_groovy(config_name, pindo_config)
|
|
946
|
+
# 优先使用相对路径,如果不存在则使用绝对路径
|
|
947
|
+
store_file = pindo_config["relative_store_file"] || pindo_config["storeFile"]
|
|
948
|
+
store_password = pindo_config["storePassword"]
|
|
949
|
+
key_alias = pindo_config["keyAlias"]
|
|
950
|
+
key_password = pindo_config["keyPassword"]
|
|
951
|
+
|
|
952
|
+
# 如果是相对路径($rootDir),使用不同的语法
|
|
953
|
+
if store_file && store_file.start_with?("$rootDir")
|
|
954
|
+
# 使用 rootProject.file() 或直接使用 $rootDir 变量
|
|
955
|
+
store_file_line = "storeFile file(\"#{store_file.sub('$rootDir/', '')}\")"
|
|
956
|
+
if store_file.include?("$rootDir/")
|
|
957
|
+
# 对于 $rootDir/signing/xxx.keystore 格式
|
|
958
|
+
relative_path = store_file.sub('$rootDir/', '')
|
|
959
|
+
store_file_line = "storeFile rootProject.file(\"#{relative_path}\")"
|
|
960
|
+
end
|
|
961
|
+
else
|
|
962
|
+
store_file_line = "storeFile file(\"#{store_file}\")"
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
<<~GROOVY
|
|
966
|
+
#{config_name} {
|
|
967
|
+
#{store_file_line}
|
|
968
|
+
storePassword "#{store_password}"
|
|
969
|
+
keyAlias "#{key_alias}"
|
|
970
|
+
keyPassword "#{key_password}"
|
|
971
|
+
}
|
|
972
|
+
GROOVY
|
|
973
|
+
end
|
|
974
|
+
|
|
975
|
+
# 生成签名配置代码块(Kotlin DSL)
|
|
976
|
+
def generate_signing_config_kts(config_name, pindo_config)
|
|
977
|
+
# 优先使用相对路径,如果不存在则使用绝对路径
|
|
978
|
+
store_file = pindo_config["relative_store_file"] || pindo_config["storeFile"]
|
|
979
|
+
store_password = pindo_config["storePassword"]
|
|
980
|
+
key_alias = pindo_config["keyAlias"]
|
|
981
|
+
key_password = pindo_config["keyPassword"]
|
|
982
|
+
|
|
983
|
+
# 如果是相对路径($rootDir),使用不同的语法
|
|
984
|
+
if store_file && store_file.start_with?("$rootDir")
|
|
985
|
+
# 对于 Kotlin DSL,使用 rootProject.file()
|
|
986
|
+
if store_file.include?("$rootDir/")
|
|
987
|
+
relative_path = store_file.sub('$rootDir/', '')
|
|
988
|
+
store_file_line = "storeFile = rootProject.file(\"#{relative_path}\")"
|
|
989
|
+
else
|
|
990
|
+
store_file_line = "storeFile = file(\"#{store_file}\")"
|
|
991
|
+
end
|
|
992
|
+
else
|
|
993
|
+
store_file_line = "storeFile = file(\"#{store_file}\")"
|
|
994
|
+
end
|
|
995
|
+
|
|
996
|
+
<<~KOTLIN
|
|
997
|
+
create("#{config_name}") {
|
|
998
|
+
#{store_file_line}
|
|
999
|
+
storePassword = "#{store_password}"
|
|
1000
|
+
keyAlias = "#{key_alias}"
|
|
1001
|
+
keyPassword = "#{key_password}"
|
|
1002
|
+
}
|
|
1003
|
+
KOTLIN
|
|
1004
|
+
end
|
|
1005
|
+
|
|
1006
|
+
# 插入签名配置到 signingConfigs 块(Groovy)
|
|
1007
|
+
def insert_signing_config_groovy(content, signing_config_block)
|
|
1008
|
+
# 查找 signingConfigs 块
|
|
1009
|
+
if content =~ /signingConfigs\s*\{/
|
|
1010
|
+
# 在 signingConfigs 块内插入
|
|
1011
|
+
content.sub(/(signingConfigs\s*\{)/) do
|
|
1012
|
+
"#{$1}\n #{signing_config_block.strip.gsub(/\n/, "\n ")}\n"
|
|
1013
|
+
end
|
|
1014
|
+
else
|
|
1015
|
+
# 没有 signingConfigs 块,在 android 块内创建
|
|
1016
|
+
android_match = content.match(/(android\s*\{)/m)
|
|
1017
|
+
if android_match
|
|
1018
|
+
insert_pos = content.index(android_match[0]) + android_match[0].length
|
|
1019
|
+
signing_configs_block = "\n signingConfigs {\n #{signing_config_block.strip.gsub(/\n/, "\n ")}\n }\n"
|
|
1020
|
+
content.insert(insert_pos, signing_configs_block)
|
|
1021
|
+
else
|
|
1022
|
+
content
|
|
1023
|
+
end
|
|
1024
|
+
end
|
|
1025
|
+
end
|
|
1026
|
+
|
|
1027
|
+
# 插入签名配置到 signingConfigs 块(Kotlin DSL)
|
|
1028
|
+
def insert_signing_config_kts(content, signing_config_block)
|
|
1029
|
+
# 查找 signingConfigs 块
|
|
1030
|
+
if content =~ /signingConfigs\s*\{/
|
|
1031
|
+
# 在 signingConfigs 块内插入
|
|
1032
|
+
content.sub(/(signingConfigs\s*\{)/) do
|
|
1033
|
+
"#{$1}\n #{signing_config_block.strip.gsub(/\n/, "\n ")}\n"
|
|
1034
|
+
end
|
|
1035
|
+
else
|
|
1036
|
+
# 没有 signingConfigs 块,在 android 块内创建
|
|
1037
|
+
android_match = content.match(/(android\s*\{)/m)
|
|
1038
|
+
if android_match
|
|
1039
|
+
insert_pos = content.index(android_match[0]) + android_match[0].length
|
|
1040
|
+
signing_configs_block = "\n signingConfigs {\n #{signing_config_block.strip.gsub(/\n/, "\n ")}\n }\n"
|
|
1041
|
+
content.insert(insert_pos, signing_configs_block)
|
|
1042
|
+
else
|
|
1043
|
+
content
|
|
1044
|
+
end
|
|
1045
|
+
end
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
# 在 buildType 中添加 signingConfig 引用(Groovy)
|
|
1049
|
+
def add_signing_config_reference_to_build_type_groovy(content, build_type, config_name)
|
|
1050
|
+
# 第一步:提取 buildTypes 块
|
|
1051
|
+
build_types_match = content.match(/buildTypes\s*\{([\s\S]*?)^\s*\}/m)
|
|
1052
|
+
return content unless build_types_match
|
|
1053
|
+
|
|
1054
|
+
build_types_content = build_types_match[1]
|
|
1055
|
+
build_types_full = build_types_match[0]
|
|
1056
|
+
|
|
1057
|
+
# 第二步:从 buildTypes 块中提取 buildType
|
|
1058
|
+
build_type_block = extract_build_type_block_from_content_groovy(build_types_content, build_type)
|
|
1059
|
+
return content unless build_type_block
|
|
1060
|
+
|
|
1061
|
+
original_block = build_type_block.dup
|
|
1062
|
+
|
|
1063
|
+
# 第三步:在 buildType 块开始后添加 signingConfig 引用
|
|
1064
|
+
new_block = build_type_block.sub(/^(\s*#{build_type}\s*\{\s*)$/m) do
|
|
1065
|
+
"#{$1}\n signingConfig signingConfigs.#{config_name}\n"
|
|
1066
|
+
end
|
|
1067
|
+
|
|
1068
|
+
# 第四步:替换 buildTypes 块中的内容
|
|
1069
|
+
new_build_types_content = build_types_content.sub(original_block, new_block)
|
|
1070
|
+
new_build_types_full = build_types_full.sub(build_types_content, new_build_types_content)
|
|
1071
|
+
|
|
1072
|
+
# 第五步:替换整个文件中的 buildTypes 块
|
|
1073
|
+
content.sub(build_types_full, new_build_types_full)
|
|
1074
|
+
end
|
|
1075
|
+
|
|
1076
|
+
# 在 buildType 中添加 signingConfig 引用(Kotlin DSL)
|
|
1077
|
+
def add_signing_config_reference_to_build_type_kts(content, build_type, config_name)
|
|
1078
|
+
# 第一步:提取 buildTypes 块
|
|
1079
|
+
build_types_match = content.match(/buildTypes\s*\{([\s\S]*?)^\s*\}/m)
|
|
1080
|
+
return content unless build_types_match
|
|
1081
|
+
|
|
1082
|
+
build_types_content = build_types_match[1]
|
|
1083
|
+
build_types_full = build_types_match[0]
|
|
1084
|
+
|
|
1085
|
+
# 第二步:从 buildTypes 块中提取 buildType
|
|
1086
|
+
build_type_block = extract_build_type_block_from_content_kts(build_types_content, build_type)
|
|
1087
|
+
return content unless build_type_block
|
|
1088
|
+
|
|
1089
|
+
original_block = build_type_block.dup
|
|
1090
|
+
|
|
1091
|
+
# 第三步:在 buildType 块开始后添加 signingConfig 引用
|
|
1092
|
+
new_block = build_type_block.sub(/^(\s*getByName\s*\(\s*"#{build_type}"\s*\)\s*\{\s*)$/m) do
|
|
1093
|
+
"#{$1}\n signingConfig = signingConfigs.getByName(\"#{config_name}\")\n"
|
|
1094
|
+
end
|
|
1095
|
+
|
|
1096
|
+
# 第四步:替换 buildTypes 块中的内容
|
|
1097
|
+
new_build_types_content = build_types_content.sub(original_block, new_block)
|
|
1098
|
+
new_build_types_full = build_types_full.sub(build_types_content, new_build_types_content)
|
|
1099
|
+
|
|
1100
|
+
# 第五步:替换整个文件中的 buildTypes 块
|
|
1101
|
+
content.sub(build_types_full, new_build_types_full)
|
|
1102
|
+
end
|
|
1103
|
+
|
|
1104
|
+
# 查找 gradle 文件
|
|
1105
|
+
# @param module_dir [String] 模块目录
|
|
1106
|
+
# @return [String, nil] gradle 文件路径
|
|
1107
|
+
def find_gradle_file(module_dir)
|
|
1108
|
+
gradle_kts = File.join(module_dir, "build.gradle.kts")
|
|
1109
|
+
gradle_groovy = File.join(module_dir, "build.gradle")
|
|
1110
|
+
|
|
1111
|
+
return gradle_kts if File.exist?(gradle_kts)
|
|
1112
|
+
return gradle_groovy if File.exist?(gradle_groovy)
|
|
1113
|
+
nil
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1116
|
+
end
|
|
1117
|
+
end
|
|
1118
|
+
end
|