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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/base/funlog.rb +12 -1
  3. data/lib/pindo/base/pindocontext.rb +3 -0
  4. data/lib/pindo/command/android/autobuild.rb +62 -9
  5. data/lib/pindo/command/android/build.rb +6 -6
  6. data/lib/pindo/command/android/debug.rb +9 -9
  7. data/lib/pindo/command/android.rb +1 -1
  8. data/lib/pindo/command/appstore.rb +1 -1
  9. data/lib/pindo/command/deploy/build.rb +2 -4
  10. data/lib/pindo/command/deploy/cert.rb +4 -5
  11. data/lib/pindo/command/deploy/configproj.rb +3 -3
  12. data/lib/pindo/command/deploy/confusecode.rb +1 -1
  13. data/lib/pindo/command/deploy/confuseproj.rb +1 -1
  14. data/lib/pindo/command/deploy/pem.rb +3 -4
  15. data/lib/pindo/command/dev/autobuild.rb +1 -1
  16. data/lib/pindo/command/dev/build.rb +1 -1
  17. data/lib/pindo/command/dev/feishu.rb +1 -1
  18. data/lib/pindo/command/gplay/pullconfig.rb +48 -0
  19. data/lib/pindo/command/gplay.rb +7 -6
  20. data/lib/pindo/command/ios/adhoc.rb +6 -5
  21. data/lib/pindo/command/ios/autobuild.rb +24 -24
  22. data/lib/pindo/command/ios/build.rb +7 -6
  23. data/lib/pindo/command/ios/debug.rb +1 -0
  24. data/lib/pindo/command/ios.rb +1 -1
  25. data/lib/pindo/command/ipa/import.rb +2 -3
  26. data/lib/pindo/command/ipa/output.rb +2 -3
  27. data/lib/pindo/command/jps/upload.rb +6 -5
  28. data/lib/pindo/command/unity/apk.rb +19 -2
  29. data/lib/pindo/command/unity/autobuild.rb +58 -63
  30. data/lib/pindo/command/unity/initpack.rb +1 -1
  31. data/lib/pindo/command/unity/ipa.rb +17 -14
  32. data/lib/pindo/command/unity/pack.rb +1 -1
  33. data/lib/pindo/command/unity/upload.rb +1 -1
  34. data/lib/pindo/command/unity/web.rb +2 -2
  35. data/lib/pindo/command/utils/icon.rb +1 -1
  36. data/lib/pindo/command/utils/renewcert.rb +1 -2
  37. data/lib/pindo/command/utils/renewproj.rb +9 -10
  38. data/lib/pindo/command/web/autobuild.rb +2 -2
  39. data/lib/pindo/command/web/run.rb +1 -1
  40. data/lib/pindo/command.rb +2 -1
  41. data/lib/pindo/module/android/android_build_config_helper.rb +267 -35
  42. data/lib/pindo/module/android/android_build_helper.rb +300 -0
  43. data/lib/pindo/module/android/android_project_helper.rb +279 -0
  44. data/lib/pindo/module/android/gp_compliance_helper.rb +33 -87
  45. data/lib/pindo/module/android/gradle_helper.rb +524 -255
  46. data/lib/pindo/module/android/java_env_helper.rb +633 -0
  47. data/lib/pindo/module/android/keystore_helper.rb +1118 -0
  48. data/lib/pindo/module/appselect.rb +109 -0
  49. data/lib/pindo/module/appstore/appstore_in_app_purchase.rb +1 -1
  50. data/lib/pindo/module/build/{buildhelper.rb → build_helper.rb} +1 -2
  51. data/lib/pindo/module/build/{commonconfuseproj.rb → confuse_xcodeproj.rb} +2 -2
  52. data/lib/pindo/module/cert/cert_helper.rb +245 -0
  53. data/lib/pindo/module/cert/keychain_helper.rb +152 -0
  54. data/lib/pindo/module/cert/pem_helper.rb +67 -0
  55. data/lib/pindo/module/cert/xcodecerthelper.rb +2 -2
  56. data/lib/pindo/module/pgyer/pgyerhelper.rb +21 -1
  57. data/lib/pindo/module/{build/unityhelper.rb → unity/unity_helper.rb} +0 -17
  58. data/lib/pindo/module/xcode/{xcodereshandler.rb → res/xcode_res_handler.rb} +1 -1
  59. data/lib/pindo/module/xcode/{xcodebuildconfig.rb → xcode_build_config.rb} +7 -6
  60. data/lib/pindo/module/xcode/{xcodebuildhelper.rb → xcode_build_helper.rb} +4 -3
  61. data/lib/pindo/module/xcode/{xcodehelper.rb → xcode_project_helper.rb} +4 -2
  62. data/lib/pindo/module/xcode/{xcodereshelper.rb → xcode_res_helper.rb} +12 -9
  63. data/lib/pindo/version.rb +185 -7
  64. data/lib/pindo.rb +14 -9
  65. metadata +24 -23
  66. data/lib/pindo/module/android/apk_helper.rb +0 -138
  67. data/lib/pindo/module/android/base_helper.rb +0 -964
  68. data/lib/pindo/module/android/build_helper.rb +0 -128
  69. data/lib/pindo/module/android/so_helper.rb +0 -18
  70. data/lib/pindo/module/cert/certhelper.rb +0 -246
  71. data/lib/pindo/module/cert/keychainhelper.rb +0 -150
  72. data/lib/pindo/module/cert/pemhelper.rb +0 -65
  73. /data/lib/pindo/module/android/{androidreshelper.rb → android_res_helper.rb} +0 -0
  74. /data/lib/pindo/module/appstore/{iap_tier.json → appstore_iap_tier.json} +0 -0
  75. /data/lib/pindo/module/build/{swarkhelper.rb → swark_helper.rb} +0 -0
  76. /data/lib/pindo/module/build/{versionhelper.rb → version_helper.rb} +0 -0
  77. /data/lib/pindo/module/cert/{provisioninghelper.rb → provisioning_helper.rb} +0 -0
  78. /data/lib/pindo/module/unity/{nugethelper.rb → nuget_helper.rb} +0 -0
  79. /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