pindo 5.18.17 → 5.19.1
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/git_handler.rb +44 -83
- data/lib/pindo/command/appstore/adhocbuild.rb +13 -2
- data/lib/pindo/command/appstore/autobuild.rb +2 -1
- data/lib/pindo/command/appstore/iap.rb +58 -54
- data/lib/pindo/command/repo/create.rb +1 -1
- data/lib/pindo/command/repo/search.rb +1 -1
- data/lib/pindo/command/utils/installskills.rb +2 -2
- data/lib/pindo/command/utils/renewproj.rb +0 -1
- data/lib/pindo/config/build_info_manager.rb +31 -0
- data/lib/pindo/config/ios_config_parser.rb +212 -140
- data/lib/pindo/module/android/android_build_helper.rb +39 -16
- data/lib/pindo/module/android/gradle_helper.rb +8 -14
- data/lib/pindo/module/appstore/appstore_in_app_purchase.rb +80 -56
- data/lib/pindo/module/cert/cert_helper.rb +2 -2
- data/lib/pindo/module/pgyer/pgyerhelper.rb +18 -20
- data/lib/pindo/module/task/model/build/android_build_dev_task.rb +1 -37
- data/lib/pindo/module/task/model/build/android_build_task.rb +40 -0
- data/lib/pindo/module/task/model/build/ios_build_adhoc_task.rb +9 -35
- data/lib/pindo/module/task/model/build/ios_build_appstore_task.rb +5 -5
- data/lib/pindo/module/task/model/build/ios_build_dev_task.rb +14 -4
- data/lib/pindo/module/task/model/build_task.rb +2 -0
- data/lib/pindo/module/task/model/git/git_commit_task.rb +11 -1
- data/lib/pindo/module/task/model/git/git_tag_task.rb +92 -77
- data/lib/pindo/module/task/model/jps/jps_workflow_message_task.rb +8 -10
- data/lib/pindo/module/task/model/unity/unity_export_task.rb +44 -0
- data/lib/pindo/module/task/pindo_task.rb +2 -2
- data/lib/pindo/module/task/task_manager.rb +14 -6
- data/lib/pindo/module/task/task_reporter.rb +45 -0
- data/lib/pindo/module/unity/nuget_helper.rb +5 -20
- data/lib/pindo/module/utils/git_repo_helper.rb +69 -36
- data/lib/pindo/module/xcode/ipa_resign_helper.rb +33 -62
- data/lib/pindo/module/xcode/res/xcode_res_handler.rb +25 -4
- data/lib/pindo/module/xcode/xcode_build_config.rb +7 -1
- data/lib/pindo/module/xcode/xcode_build_helper.rb +36 -4
- data/lib/pindo/module/xcode/xcode_res_helper.rb +8 -0
- data/lib/pindo/options/helpers/bundleid_selector.rb +23 -6
- data/lib/pindo/version.rb +1 -1
- metadata +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'singleton'
|
|
2
2
|
require 'json'
|
|
3
|
+
require 'pindo/base/informative'
|
|
3
4
|
|
|
4
5
|
module Pindo
|
|
5
6
|
# iOS 配置解析器(单例模式)
|
|
@@ -21,7 +22,32 @@ module Pindo
|
|
|
21
22
|
class IosConfigParser
|
|
22
23
|
include Singleton
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
# AdHoc 合并时需要替换前缀的所有 Bundle ID 字段
|
|
26
|
+
ADHOC_BUNDLE_ID_KEYS = %w[
|
|
27
|
+
app_identifier
|
|
28
|
+
app_identifier_pushcontent
|
|
29
|
+
app_identifier_pushservice
|
|
30
|
+
app_identifier_keyboard
|
|
31
|
+
app_identifier_imessage
|
|
32
|
+
app_identifier_extension
|
|
33
|
+
app_identifier_siri
|
|
34
|
+
app_identifier_siriui
|
|
35
|
+
app_identifier_widget
|
|
36
|
+
app_identifier_extensionad
|
|
37
|
+
app_identifier_extensionporn
|
|
38
|
+
app_identifier_watchapp
|
|
39
|
+
app_identifier_watchapp_extension
|
|
40
|
+
].freeze
|
|
41
|
+
|
|
42
|
+
# AdHoc 合并 app_setting 时需要保留原值(服务器/URL)的 key
|
|
43
|
+
ADHOC_EXCLUDED_APP_SETTING_KEYS = %w[
|
|
44
|
+
kGUKeyAppClientHost
|
|
45
|
+
kGUKeyResBaseUrl
|
|
46
|
+
app_web_host
|
|
47
|
+
app_client_url
|
|
48
|
+
].freeze
|
|
49
|
+
|
|
50
|
+
attr_reader :config_file_path, :config_json, :app_repo_name, :adhoc_config_dir
|
|
25
51
|
|
|
26
52
|
def initialize
|
|
27
53
|
@config_file_path = nil
|
|
@@ -29,6 +55,8 @@ module Pindo
|
|
|
29
55
|
@config_data = {}
|
|
30
56
|
@overrides = {}
|
|
31
57
|
@app_repo_name = nil # 保存原始 Bundle ID,用于配置仓库路径
|
|
58
|
+
@adhoc_merged = false # 是否已合并 AdHoc 配置(幂等标志)
|
|
59
|
+
@adhoc_config_dir = nil # 已合并的 AdHoc 配置仓库目录(缓存,避免重复克隆)
|
|
32
60
|
end
|
|
33
61
|
|
|
34
62
|
# 加载配置文件
|
|
@@ -47,6 +75,11 @@ module Pindo
|
|
|
47
75
|
raise "配置文件格式错误: #{e.message}"
|
|
48
76
|
end
|
|
49
77
|
|
|
78
|
+
# 从磁盘重新加载的配置是未合并的,重置 AdHoc 标志,
|
|
79
|
+
# 否则后续 merge 会被幂等短路错误跳过,导致用原始配置构建
|
|
80
|
+
@adhoc_merged = false
|
|
81
|
+
@adhoc_config_dir = nil
|
|
82
|
+
|
|
50
83
|
# 解析配置
|
|
51
84
|
parse_config_data
|
|
52
85
|
|
|
@@ -69,6 +102,14 @@ module Pindo
|
|
|
69
102
|
@config_data = {}
|
|
70
103
|
@overrides = {}
|
|
71
104
|
@app_repo_name = nil
|
|
105
|
+
@adhoc_merged = false
|
|
106
|
+
@adhoc_config_dir = nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# AdHoc 配置是否已合并(幂等短路用)
|
|
110
|
+
# @return [Boolean]
|
|
111
|
+
def adhoc_merged?
|
|
112
|
+
@adhoc_merged == true
|
|
72
113
|
end
|
|
73
114
|
|
|
74
115
|
# ==================== 账号信息 ====================
|
|
@@ -270,6 +311,34 @@ module Pindo
|
|
|
270
311
|
@config_data[:ios_deployment_target]
|
|
271
312
|
end
|
|
272
313
|
|
|
314
|
+
# ==================== Unity AppConfig 映射 ====================
|
|
315
|
+
|
|
316
|
+
# 提取需要写入 Unity 工程 AppConfig(TextAsset JSON)的字段
|
|
317
|
+
# 仅包含在 config.json 中有明确来源的项,已去除 nil/空值
|
|
318
|
+
# @return [Hash{String=>String}] AppConfig 键 => 值
|
|
319
|
+
def unity_appconfig_values
|
|
320
|
+
return {} unless loaded?
|
|
321
|
+
|
|
322
|
+
app_info = @config_json['app_info'] || {}
|
|
323
|
+
app_setting = @config_json['app_setting'] || {}
|
|
324
|
+
|
|
325
|
+
{
|
|
326
|
+
"apple_app_id" => app_info['app_id_ios'],
|
|
327
|
+
"appsflyer_dev_key" => app_setting['kGUKeyAppsFlyerDevKey'],
|
|
328
|
+
"support_email" => app_setting['kGUKeyAppSupportEmail'],
|
|
329
|
+
"fb_app_id" => app_info['facebook_app_id'],
|
|
330
|
+
"fb_client_token" => app_info['facebook_client_token'],
|
|
331
|
+
"fb_app_label" => app_info['app_display_name'],
|
|
332
|
+
"mecha_host" => app_setting['kGUKeyAppClientHost'],
|
|
333
|
+
"mecha_app_id" => app_setting['kGUKeyAppPlatform'],
|
|
334
|
+
"google_web_client_id" => app_setting['kGUKeyGoogleWebClientId'],
|
|
335
|
+
"google_ios_client_id" => app_setting['kGUKeyGoogleIosClientId']
|
|
336
|
+
}.each_with_object({}) do |(key, value), result|
|
|
337
|
+
next if value.nil? || value.to_s.strip.empty?
|
|
338
|
+
result[key] = value.to_s.strip
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
273
342
|
# ==================== 配置验证 ====================
|
|
274
343
|
|
|
275
344
|
# 验证必需的配置项是否存在
|
|
@@ -297,151 +366,38 @@ module Pindo
|
|
|
297
366
|
# ==================== AdHoc 发布配置合并 ====================
|
|
298
367
|
|
|
299
368
|
# 合并 AdHoc 发布配置
|
|
300
|
-
# 1.
|
|
301
|
-
# 2.
|
|
302
|
-
# 3.
|
|
369
|
+
# 1. 替换 Apple ID / 公司名为 AdHoc 配置值
|
|
370
|
+
# 2. 替换所有 Bundle ID(含 Extension、App Group、iCloud)为 AdHoc 前缀(确保使用 AdHoc 证书)
|
|
371
|
+
# 3. 合并第三方 SDK 配置(Facebook、AdMob、AppLovin)
|
|
372
|
+
# 4. 合并 app_setting(排除主机和 URL 配置)
|
|
373
|
+
#
|
|
374
|
+
# 幂等:已合并则直接短路,不再二次改写。
|
|
375
|
+
# 失败采用 fail-fast:配置文件缺失/解析失败/缺关键字段时抛 Informative,
|
|
376
|
+
# 由调用方决定是否中断,避免静默退化为"原始配置"产出错误身份的包。
|
|
377
|
+
#
|
|
303
378
|
# @param adhoc_config_dir [String] AdHoc 配置仓库目录
|
|
304
|
-
# @return [Boolean]
|
|
379
|
+
# @return [Boolean] 是否成功合并
|
|
380
|
+
# @raise [Pindo::Informative] AdHoc 配置无效或缺失关键字段
|
|
305
381
|
def modify_config_with_adhoc(adhoc_config_dir:)
|
|
306
|
-
|
|
307
|
-
return
|
|
308
|
-
|
|
309
|
-
begin
|
|
310
|
-
# 1. 读取 AdHoc 配置
|
|
311
|
-
adhoc_config_file = File.join(adhoc_config_dir, "config.json")
|
|
312
|
-
unless File.exist?(adhoc_config_file)
|
|
313
|
-
puts "AdHoc 配置文件不存在: #{adhoc_config_file}"
|
|
314
|
-
return false
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
adhoc_config_json = JSON.parse(File.read(adhoc_config_file))
|
|
318
|
-
|
|
319
|
-
# 1.5 替换 Apple ID(使用 AdHoc 配置的 Apple ID)
|
|
320
|
-
if adhoc_config_json.dig('account_info', 'apple_acount_id')
|
|
321
|
-
original_apple_id = @config_json.dig('account_info', 'apple_acount_id')&.strip # 去除前后空格
|
|
322
|
-
adhoc_apple_id = adhoc_config_json['account_info']['apple_acount_id']&.strip # 去除前后空格
|
|
323
|
-
|
|
324
|
-
if original_apple_id && adhoc_apple_id && !adhoc_apple_id.empty? && original_apple_id != adhoc_apple_id
|
|
325
|
-
@config_json['account_info']['apple_acount_id'] = adhoc_apple_id
|
|
326
|
-
puts " 替换 Apple ID:"
|
|
327
|
-
puts " 原始 Apple ID: #{original_apple_id}"
|
|
328
|
-
puts " AdHoc Apple ID: #{adhoc_apple_id}"
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
# 如果 AdHoc 配置包含 company_name,也一并替换
|
|
332
|
-
if adhoc_config_json.dig('account_info', 'acount_company_name')
|
|
333
|
-
company_name = adhoc_config_json['account_info']['acount_company_name']&.strip # 去除前后空格
|
|
334
|
-
@config_json['account_info']['acount_company_name'] = company_name if company_name && !company_name.empty?
|
|
335
|
-
end
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
# 2. 替换所有 Bundle ID(使用 AdHoc 配置的 Bundle ID)
|
|
339
|
-
original_bundle_id = @config_json.dig('app_info', 'app_identifier')
|
|
340
|
-
adhoc_bundle_id = adhoc_config_json.dig('app_info', 'app_identifier')
|
|
341
|
-
|
|
342
|
-
if original_bundle_id && adhoc_bundle_id && original_bundle_id != adhoc_bundle_id
|
|
343
|
-
puts " 替换 Bundle ID 前缀:"
|
|
344
|
-
puts " 原始前缀: #{original_bundle_id}"
|
|
345
|
-
puts " AdHoc 前缀: #{adhoc_bundle_id}"
|
|
346
|
-
|
|
347
|
-
# Bundle ID 字段映射表
|
|
348
|
-
bundle_id_keys = [
|
|
349
|
-
'app_identifier', # 主应用
|
|
350
|
-
'app_identifier_pushcontent', # Push Content Extension
|
|
351
|
-
'app_identifier_pushservice', # Push Service Extension
|
|
352
|
-
'app_identifier_keyboard', # Keyboard Extension
|
|
353
|
-
'app_identifier_imessage', # iMessage Extension
|
|
354
|
-
'app_identifier_extension', # 通用 Extension
|
|
355
|
-
'app_identifier_siri', # Siri Extension
|
|
356
|
-
'app_identifier_siriui', # Siri UI Extension
|
|
357
|
-
'app_identifier_widget', # Widget Extension
|
|
358
|
-
'app_identifier_extensionad', # 广告 Extension
|
|
359
|
-
'app_identifier_extensionporn',# Porn Extension
|
|
360
|
-
'app_identifier_watchapp', # Watch App
|
|
361
|
-
'app_identifier_watchapp_extension' # Watch App Extension
|
|
362
|
-
]
|
|
363
|
-
|
|
364
|
-
# 遍历所有 Bundle ID 字段并替换前缀
|
|
365
|
-
bundle_id_keys.each do |key|
|
|
366
|
-
current_value = @config_json.dig('app_info', key)
|
|
367
|
-
next if current_value.nil? || current_value.empty?
|
|
368
|
-
|
|
369
|
-
# 如果当前值以原始主 Bundle ID 开头,替换为 AdHoc Bundle ID
|
|
370
|
-
if current_value.start_with?(original_bundle_id)
|
|
371
|
-
# 计算后缀部分(如 .extension、.keyboard 等)
|
|
372
|
-
suffix = current_value[original_bundle_id.length..-1]
|
|
373
|
-
new_value = adhoc_bundle_id + suffix
|
|
374
|
-
|
|
375
|
-
@config_json['app_info'][key] = new_value
|
|
376
|
-
puts " ✓ #{key}: #{current_value} → #{new_value}"
|
|
377
|
-
end
|
|
378
|
-
end
|
|
379
|
-
end
|
|
380
|
-
|
|
381
|
-
# 2.5 替换 App Group ID 和 iCloud Container ID
|
|
382
|
-
if original_bundle_id && adhoc_bundle_id && original_bundle_id != adhoc_bundle_id
|
|
383
|
-
# 处理 app_group_id (格式: group.com.example.app)
|
|
384
|
-
if @config_json.dig('app_setting', 'app_group_id')
|
|
385
|
-
current_group_id = @config_json['app_setting']['app_group_id']
|
|
386
|
-
if current_group_id.include?(original_bundle_id)
|
|
387
|
-
new_group_id = current_group_id.gsub(original_bundle_id, adhoc_bundle_id)
|
|
388
|
-
@config_json['app_setting']['app_group_id'] = new_group_id
|
|
389
|
-
puts " ✓ app_group_id: #{current_group_id} → #{new_group_id}"
|
|
390
|
-
end
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
# 处理 app_icloud_id (格式: iCloud.com.example.app)
|
|
394
|
-
if @config_json.dig('app_setting', 'app_icloud_id')
|
|
395
|
-
current_icloud_id = @config_json['app_setting']['app_icloud_id']
|
|
396
|
-
if current_icloud_id.include?(original_bundle_id)
|
|
397
|
-
new_icloud_id = current_icloud_id.gsub(original_bundle_id, adhoc_bundle_id)
|
|
398
|
-
@config_json['app_setting']['app_icloud_id'] = new_icloud_id
|
|
399
|
-
puts " ✓ app_icloud_id: #{current_icloud_id} → #{new_icloud_id}"
|
|
400
|
-
end
|
|
401
|
-
end
|
|
402
|
-
end
|
|
403
|
-
|
|
404
|
-
# 3. 合并第三方 SDK 配置
|
|
405
|
-
# Facebook 配置
|
|
406
|
-
if adhoc_config_json.dig('app_info', 'facebook_app_id')
|
|
407
|
-
@config_json['app_info']['facebook_app_id'] = adhoc_config_json['app_info']['facebook_app_id']
|
|
408
|
-
@config_json['app_info']['facebook_client_token'] = adhoc_config_json['app_info']['facebook_client_token']
|
|
409
|
-
end
|
|
410
|
-
|
|
411
|
-
# AdMob 配置
|
|
412
|
-
if @config_json.dig('app_info', 'admob_app_id') && adhoc_config_json.dig('app_info', 'admob_app_id')
|
|
413
|
-
@config_json['app_info']['admob_app_id'] = adhoc_config_json['app_info']['admob_app_id']
|
|
414
|
-
end
|
|
415
|
-
|
|
416
|
-
# AppLovin 配置
|
|
417
|
-
if @config_json.dig('app_info', 'applovin_app_id') && adhoc_config_json.dig('app_info', 'applovin_app_id')
|
|
418
|
-
@config_json['app_info']['applovin_app_id'] = adhoc_config_json['app_info']['applovin_app_id']
|
|
419
|
-
end
|
|
382
|
+
raise Informative, "配置未加载,无法合并 AdHoc 配置" unless loaded?
|
|
383
|
+
return true if adhoc_merged? # 幂等短路
|
|
384
|
+
raise Informative, "AdHoc 配置目录无效" if adhoc_config_dir.nil? || adhoc_config_dir.empty?
|
|
420
385
|
|
|
421
|
-
|
|
422
|
-
excluded_keys = ['kGUKeyAppClientHost', 'kGUKeyResBaseUrl', 'app_web_host', 'app_client_url']
|
|
386
|
+
adhoc_config_json = read_adhoc_config(adhoc_config_dir)
|
|
423
387
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
388
|
+
merge_adhoc_apple_account(adhoc_config_json)
|
|
389
|
+
replace_adhoc_bundle_identifiers(adhoc_config_json)
|
|
390
|
+
merge_adhoc_sdk_config(adhoc_config_json)
|
|
391
|
+
merge_adhoc_app_setting(adhoc_config_json)
|
|
427
392
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
end
|
|
432
|
-
end
|
|
433
|
-
|
|
434
|
-
# 5. 重新解析配置数据,同步更新 @config_data
|
|
435
|
-
# 这样 bundle_id、bundle_id_pushcontent 等属性方法才能返回替换后的值
|
|
436
|
-
parse_config_data
|
|
393
|
+
# 重新解析配置数据,同步更新 @config_data
|
|
394
|
+
# 这样 bundle_id、bundle_id_pushcontent 等属性方法才能返回替换后的值
|
|
395
|
+
parse_config_data
|
|
437
396
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
puts e.backtrace.join("\n") if ENV['PINDO_DEBUG']
|
|
443
|
-
false
|
|
444
|
-
end
|
|
397
|
+
@adhoc_merged = true
|
|
398
|
+
@adhoc_config_dir = adhoc_config_dir
|
|
399
|
+
puts "✅ 已替换 Apple ID、Bundle ID 并合并 AdHoc 配置(Facebook、AdMob、AppLovin)"
|
|
400
|
+
true
|
|
445
401
|
end
|
|
446
402
|
|
|
447
403
|
# ==================== 调试和信息 ====================
|
|
@@ -483,6 +439,122 @@ module Pindo
|
|
|
483
439
|
|
|
484
440
|
private
|
|
485
441
|
|
|
442
|
+
# ==================== AdHoc 合并辅助方法 ====================
|
|
443
|
+
|
|
444
|
+
# 读取并解析 AdHoc 配置仓库的 config.json(失败 fail-fast)
|
|
445
|
+
# @raise [Pindo::Informative] 文件不存在或 JSON 解析失败
|
|
446
|
+
def read_adhoc_config(adhoc_config_dir)
|
|
447
|
+
adhoc_config_file = File.join(adhoc_config_dir, "config.json")
|
|
448
|
+
unless File.exist?(adhoc_config_file)
|
|
449
|
+
raise Informative, "AdHoc 配置文件不存在: #{adhoc_config_file}"
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
JSON.parse(File.read(adhoc_config_file))
|
|
453
|
+
rescue JSON::ParserError => e
|
|
454
|
+
raise Informative, "AdHoc 配置文件解析失败: #{e.message}"
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
# 替换 Apple ID 与公司名
|
|
458
|
+
# 注:原配置无 account_info 容器则跳过(原代码此处会 NoMethodError,这里改为安全跳过)
|
|
459
|
+
def merge_adhoc_apple_account(adhoc_config_json)
|
|
460
|
+
return unless @config_json['account_info']
|
|
461
|
+
|
|
462
|
+
adhoc_account = adhoc_config_json['account_info'] || {}
|
|
463
|
+
|
|
464
|
+
adhoc_apple_id = adhoc_account['apple_acount_id']&.strip
|
|
465
|
+
if adhoc_apple_id && !adhoc_apple_id.empty?
|
|
466
|
+
original_apple_id = @config_json['account_info']['apple_acount_id']&.strip
|
|
467
|
+
if original_apple_id != adhoc_apple_id
|
|
468
|
+
@config_json['account_info']['apple_acount_id'] = adhoc_apple_id
|
|
469
|
+
puts " 替换 Apple ID:"
|
|
470
|
+
puts " 原始 Apple ID: #{original_apple_id}"
|
|
471
|
+
puts " AdHoc Apple ID: #{adhoc_apple_id}"
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
company_name = adhoc_account['acount_company_name']&.strip
|
|
476
|
+
if company_name && !company_name.empty?
|
|
477
|
+
@config_json['account_info']['acount_company_name'] = company_name
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# 替换主 Bundle ID、所有 Extension Bundle ID、App Group、iCloud Container
|
|
482
|
+
# @raise [Pindo::Informative] AdHoc 配置缺少主 Bundle ID
|
|
483
|
+
def replace_adhoc_bundle_identifiers(adhoc_config_json)
|
|
484
|
+
original_bundle_id = @config_json.dig('app_info', 'app_identifier')
|
|
485
|
+
adhoc_bundle_id = adhoc_config_json.dig('app_info', 'app_identifier')
|
|
486
|
+
|
|
487
|
+
if adhoc_bundle_id.nil? || adhoc_bundle_id.empty?
|
|
488
|
+
raise Informative, "AdHoc 配置缺少 app_identifier(主 Bundle ID),无法替换证书身份"
|
|
489
|
+
end
|
|
490
|
+
return if original_bundle_id.nil? || original_bundle_id == adhoc_bundle_id
|
|
491
|
+
|
|
492
|
+
puts " 替换 Bundle ID 前缀:"
|
|
493
|
+
puts " 原始前缀: #{original_bundle_id}"
|
|
494
|
+
puts " AdHoc 前缀: #{adhoc_bundle_id}"
|
|
495
|
+
|
|
496
|
+
# 替换所有 app_identifier_* 字段:凡以原始主 Bundle ID 开头的,替换前缀保留后缀
|
|
497
|
+
ADHOC_BUNDLE_ID_KEYS.each do |key|
|
|
498
|
+
current_value = @config_json.dig('app_info', key)
|
|
499
|
+
next if current_value.nil? || current_value.empty?
|
|
500
|
+
next unless current_value.start_with?(original_bundle_id)
|
|
501
|
+
|
|
502
|
+
new_value = adhoc_bundle_id + current_value[original_bundle_id.length..-1]
|
|
503
|
+
@config_json['app_info'][key] = new_value
|
|
504
|
+
puts " ✓ #{key}: #{current_value} → #{new_value}"
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
# App Group ID(group.xxx)与 iCloud Container ID(iCloud.xxx)内嵌的 Bundle ID
|
|
508
|
+
replace_adhoc_app_setting_id('app_group_id', original_bundle_id, adhoc_bundle_id)
|
|
509
|
+
replace_adhoc_app_setting_id('app_icloud_id', original_bundle_id, adhoc_bundle_id)
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
# 替换 app_setting 中内嵌了原始 Bundle ID 的标识符(app_group_id / app_icloud_id)
|
|
513
|
+
def replace_adhoc_app_setting_id(key, original_bundle_id, adhoc_bundle_id)
|
|
514
|
+
current_value = @config_json.dig('app_setting', key)
|
|
515
|
+
return if current_value.nil? || !current_value.include?(original_bundle_id)
|
|
516
|
+
|
|
517
|
+
new_value = current_value.gsub(original_bundle_id, adhoc_bundle_id)
|
|
518
|
+
@config_json['app_setting'][key] = new_value
|
|
519
|
+
puts " ✓ #{key}: #{current_value} → #{new_value}"
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
# 合并第三方 SDK 配置(Facebook / AdMob / AppLovin)
|
|
523
|
+
# 统一规则:仅当 AdHoc 配置存在对应值时才覆盖,避免用 nil 抹掉原有有效值
|
|
524
|
+
def merge_adhoc_sdk_config(adhoc_config_json)
|
|
525
|
+
app_info = adhoc_config_json['app_info'] || {}
|
|
526
|
+
|
|
527
|
+
if app_info['facebook_app_id']
|
|
528
|
+
@config_json['app_info']['facebook_app_id'] = app_info['facebook_app_id']
|
|
529
|
+
# token 存在才覆盖,否则保留原有值
|
|
530
|
+
if app_info['facebook_client_token']
|
|
531
|
+
@config_json['app_info']['facebook_client_token'] = app_info['facebook_client_token']
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
if @config_json.dig('app_info', 'admob_app_id') && app_info['admob_app_id']
|
|
536
|
+
@config_json['app_info']['admob_app_id'] = app_info['admob_app_id']
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
if @config_json.dig('app_info', 'applovin_app_id') && app_info['applovin_app_id']
|
|
540
|
+
@config_json['app_info']['applovin_app_id'] = app_info['applovin_app_id']
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# 合并 app_setting,逐 key 用 AdHoc 值覆盖原配置已有的 key
|
|
545
|
+
# 排除服务器/URL 相关 key,保留原环境后端地址(让测试包连真实后端)
|
|
546
|
+
def merge_adhoc_app_setting(adhoc_config_json)
|
|
547
|
+
adhoc_app_setting = adhoc_config_json['app_setting']
|
|
548
|
+
return unless @config_json['app_setting'] && adhoc_app_setting
|
|
549
|
+
|
|
550
|
+
@config_json['app_setting'].each_key do |key|
|
|
551
|
+
next if ADHOC_EXCLUDED_APP_SETTING_KEYS.include?(key)
|
|
552
|
+
next unless adhoc_app_setting[key]
|
|
553
|
+
|
|
554
|
+
@config_json['app_setting'][key] = adhoc_app_setting[key]
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
|
|
486
558
|
# 解析配置数据
|
|
487
559
|
def parse_config_data
|
|
488
560
|
return unless @config_json
|
|
@@ -6,6 +6,7 @@ require_relative 'java_env_helper'
|
|
|
6
6
|
require_relative 'keystore_helper'
|
|
7
7
|
require_relative 'workflow_gradle_injector'
|
|
8
8
|
require 'fileutils'
|
|
9
|
+
require 'find'
|
|
9
10
|
|
|
10
11
|
module Pindo
|
|
11
12
|
class AndroidBuildHelper
|
|
@@ -24,6 +25,10 @@ module Pindo
|
|
|
24
25
|
raise ArgumentError, "项目目录不能为空" if project_dir.nil?
|
|
25
26
|
raise ArgumentError, "项目目录不存在" unless File.directory?(project_dir)
|
|
26
27
|
|
|
28
|
+
# 构建前先清理 macOS 自动生成的 .DS_Store
|
|
29
|
+
# 避免其被带入 base / asset pack 模块的 assets/ 导致冲突,或引起 Gradle clean 在遍历删除时出现竞态
|
|
30
|
+
prune_ds_store_files!(project_dir)
|
|
31
|
+
|
|
27
32
|
# 设置 Java Home 环境变量
|
|
28
33
|
Pindo::JavaEnvHelper.setup_java_home_from_project(project_dir)
|
|
29
34
|
|
|
@@ -396,20 +401,19 @@ module Pindo
|
|
|
396
401
|
puts ' AAB 构建前先执行 Gradle clean'
|
|
397
402
|
gradle_clean_resilient!(project_path)
|
|
398
403
|
|
|
399
|
-
|
|
400
|
-
system(
|
|
404
|
+
# 数组参数 + chdir,避免 project_path 拼接进 shell
|
|
405
|
+
system("./gradlew", "--no-daemon", bundle_task, chdir: project_path)
|
|
401
406
|
end
|
|
402
407
|
|
|
403
408
|
# clean 失败(占用、.DS_Store 竞态等)时:停 Daemon → 再删 .DS_Store → 物理删除各模块 build/,与 Gradle clean 产物一致,不中断后续 bundle
|
|
404
409
|
private def gradle_clean_resilient!(project_path)
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
return if system(cmd)
|
|
410
|
+
prune_ds_store_files!(project_path)
|
|
411
|
+
return if system("./gradlew", "--no-daemon", "clean", chdir: project_path)
|
|
408
412
|
|
|
409
413
|
puts ' ⚠ Gradle clean 失败,正在自动恢复:--stop、清理 .DS_Store、删除各模块 build/ ...'
|
|
410
|
-
system(
|
|
414
|
+
system("./gradlew", "--stop", chdir: project_path, out: File::NULL, err: File::NULL)
|
|
411
415
|
sleep 0.3
|
|
412
|
-
|
|
416
|
+
prune_ds_store_files!(project_path)
|
|
413
417
|
remove_android_module_build_dirs!(project_path)
|
|
414
418
|
end
|
|
415
419
|
|
|
@@ -445,18 +449,37 @@ module Pindo
|
|
|
445
449
|
puts " ⚠ 物理删除 build 目录时出现异常(将继续尝试 bundle): #{e.message}"
|
|
446
450
|
end
|
|
447
451
|
|
|
448
|
-
# macOS
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
452
|
+
# 清理 macOS 自动生成的 .DS_Store 文件
|
|
453
|
+
# 作用:
|
|
454
|
+
# 1) 避免被带入 base 与 asset pack 模块的 assets/ 造成打包冲突
|
|
455
|
+
# 2) 避免 Gradle clean 遍历删除 build 目录时 Finder 回写 .DS_Store 触发
|
|
456
|
+
# Unable to delete / New files were found 的竞态错误
|
|
457
|
+
private def prune_ds_store_files!(project_path)
|
|
458
|
+
return 0 unless project_path && File.directory?(project_path)
|
|
459
|
+
|
|
460
|
+
count = 0
|
|
461
|
+
Find.find(project_path) do |path|
|
|
462
|
+
next unless File.basename(path) == '.DS_Store'
|
|
463
|
+
next unless File.file?(path)
|
|
464
|
+
|
|
465
|
+
begin
|
|
466
|
+
File.delete(path)
|
|
467
|
+
count += 1
|
|
468
|
+
rescue StandardError
|
|
469
|
+
# 个别文件删除失败不影响整体流程
|
|
470
|
+
next
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
puts " 已清理 #{count} 个 .DS_Store 文件" if count.positive?
|
|
474
|
+
count
|
|
475
|
+
rescue StandardError => e
|
|
476
|
+
puts " ⚠ 清理 .DS_Store 异常:#{e.message}"
|
|
477
|
+
0
|
|
454
478
|
end
|
|
455
479
|
|
|
456
480
|
def build_so_library(project_path)
|
|
457
|
-
# 编译so库 (Thread Safe)
|
|
458
|
-
|
|
459
|
-
system(cmd)
|
|
481
|
+
# 编译so库 (Thread Safe);数组参数 + chdir,避免 project_path 拼接进 shell
|
|
482
|
+
system("./gradlew", "unityLibrary:BuildIl2CppTask", chdir: project_path)
|
|
460
483
|
end
|
|
461
484
|
|
|
462
485
|
def copy_so_files(source_path, target_path)
|
|
@@ -518,18 +518,16 @@ module Pindo
|
|
|
518
518
|
return false
|
|
519
519
|
end
|
|
520
520
|
|
|
521
|
-
# 3. 使用unzip -t测试ZIP
|
|
522
|
-
|
|
523
|
-
system(test_cmd)
|
|
521
|
+
# 3. 使用unzip -t测试ZIP文件完整性(数组参数,避免路径注入)
|
|
522
|
+
system("unzip", "-t", zip_file, out: File::NULL, err: File::NULL)
|
|
524
523
|
rescue => e
|
|
525
524
|
false
|
|
526
525
|
end
|
|
527
526
|
|
|
528
527
|
# 使用curl下载文件
|
|
529
528
|
def download_with_curl(url, output_path)
|
|
530
|
-
#
|
|
531
|
-
|
|
532
|
-
system(cmd)
|
|
529
|
+
# 数组参数形式,避免 URL/路径中的 shell 元字符注入
|
|
530
|
+
system("curl", "-L", "-o", output_path, url)
|
|
533
531
|
rescue => e
|
|
534
532
|
false
|
|
535
533
|
end
|
|
@@ -537,9 +535,8 @@ module Pindo
|
|
|
537
535
|
# 解压Gradle分发包
|
|
538
536
|
def extract_gradle_distribution(zip_file, extract_dir, gradle_version)
|
|
539
537
|
begin
|
|
540
|
-
# 使用unzip
|
|
541
|
-
|
|
542
|
-
if system(extract_cmd)
|
|
538
|
+
# 使用unzip命令解压(数组参数 + chdir,避免路径注入)
|
|
539
|
+
if system("unzip", "-q", zip_file, chdir: File.dirname(extract_dir))
|
|
543
540
|
# 设置执行权限
|
|
544
541
|
gradle_script = File.join(extract_dir, "bin/gradle")
|
|
545
542
|
if File.exist?(gradle_script)
|
|
@@ -577,11 +574,8 @@ module Pindo
|
|
|
577
574
|
|
|
578
575
|
# 使用官方Gradle命令生成wrapper
|
|
579
576
|
def generate_gradle_wrapper_official(project_path, gradle_version)
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
cmd = "gradle wrapper"
|
|
583
|
-
system(cmd)
|
|
584
|
-
end
|
|
577
|
+
# 数组参数 + chdir,避免 shell 解析
|
|
578
|
+
system("gradle", "wrapper", chdir: project_path)
|
|
585
579
|
rescue => e
|
|
586
580
|
false
|
|
587
581
|
end
|