pindo 5.18.13 → 5.18.20

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.
@@ -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
- attr_reader :config_file_path, :config_json, :app_repo_name
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. 替换所有 Bundle ID AdHoc 配置的 Bundle ID(确保使用 AdHoc 证书)
301
- # 2. 合并第三方 SDK 配置(FacebookAdMobAppLovin)
302
- # 3. 合并 app_setting(排除主机和 URL 配置)
369
+ # 1. 替换 Apple ID / 公司名为 AdHoc 配置值
370
+ # 2. 替换所有 Bundle ID(含 ExtensionApp GroupiCloud)为 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
- return false unless loaded?
307
- return false if adhoc_config_dir.nil? || adhoc_config_dir.empty?
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
- # 4. 合并 app_setting(排除主机和 URL 配置)
422
- excluded_keys = ['kGUKeyAppClientHost', 'kGUKeyResBaseUrl', 'app_web_host', 'app_client_url']
386
+ adhoc_config_json = read_adhoc_config(adhoc_config_dir)
423
387
 
424
- if @config_json['app_setting'] && adhoc_config_json['app_setting']
425
- @config_json['app_setting'].each_key do |key|
426
- next if excluded_keys.include?(key)
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
- if adhoc_config_json['app_setting'][key]
429
- @config_json['app_setting'][key] = adhoc_config_json['app_setting'][key]
430
- end
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
- puts "✅ 已替换 Apple ID、Bundle ID 并合并 AdHoc 配置(Facebook、AdMob、AppLovin)"
439
- true
440
- rescue => e
441
- puts "合并发布配置失败: #{e.message}"
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
@@ -110,19 +110,27 @@ module Pindo
110
110
 
111
111
  apk_path = nil
112
112
  begin
113
+ # PAD 必须在签名注入之前完成,保证:
114
+ # 1) Keystore 的 Gradle 快照登记时 launcher/build.gradle 已含 assetPacks;
115
+ # 2) restore_managed_signing_config! 构建结束后恢复的是"含 assetPacks、不含签名改动"的干净状态。
116
+ has_workflow = workflow_name && !workflow_name.to_s.empty?
117
+ pad_persist = pad_persist_enabled?
118
+
119
+ if has_workflow && pad_persist
120
+ # 永久 PAD:清理旧 *_pack 并从当前 assets 重建;无可拆分资源则仅幂等补写引用
121
+ setup_play_asset_delivery_persistent(project_dir)
122
+ elsif !has_workflow
123
+ # 兼容旧行为:非 workflow 场景仍在构建前做一次 PAD(永久写入)
124
+ setup_play_asset_delivery(project_dir)
125
+ end
126
+ # 注:has_workflow && !pad_persist(临时 PAD)由 injector.prepare!(enable_pad: true) 内部处理
127
+
113
128
  unless Pindo::KeystoreHelper.apply_keystore_config(project_dir, build_type, bundle_id: bundle_name)
114
129
  raise RuntimeError, "Keystore 签名配置失败"
115
130
  end
116
131
  puts "✓ Android 签名配置成功"
117
132
 
118
- if workflow_name && !workflow_name.to_s.empty?
119
- pad_persist = pad_persist_enabled?
120
-
121
- # 永久 PAD:每次构建前清理旧的 *_pack,并从当前导出产物重新生成
122
- # 注意:PAD 会移动 unityLibrary/src/main/assets 下的目录到 *_pack;
123
- # 因此要保证在此之前已经把 Unity/ 导出产物同步回主工程(Unity as lib 分支已同步)。
124
- setup_play_asset_delivery_persistent(project_dir) if pad_persist
125
-
133
+ if has_workflow
126
134
  injector = Pindo::Android::WorkflowGradleInjector.new
127
135
  ctx = injector.prepare!(
128
136
  project_dir: project_dir,
@@ -136,8 +144,6 @@ module Pindo
136
144
  Pindo::Android::WorkflowGradleInjector.remove_project_pindo_tmp_dir!(project_dir)
137
145
  end
138
146
  else
139
- # 兼容旧行为:仍会永久写入 PAD 相关变更(如需临时恢复,请传 workflow_name 走注入管线)
140
- setup_play_asset_delivery(project_dir)
141
147
  apk_path = build_apk(project_dir, debug)
142
148
  end
143
149
  ensure
@@ -163,17 +169,43 @@ module Pindo
163
169
  true
164
170
  end
165
171
 
166
- # 永久 PAD:先清理旧的 *_pack,再从当前 assets 生成新的 pack
172
+ # 永久 PAD:仅当 assets 中存在可拆分资源时,才清理旧 *_pack 并重新生成;
173
+ # 若无可拆分资源但工程已有 *_pack 模块,则幂等补写 settings.gradle / launcher/build.gradle 的引用,
174
+ # 避免 Unity 重新导出覆盖 launcher/build.gradle 导致 assetPacks 丢失。
167
175
  private def setup_play_asset_delivery_persistent(project_dir)
168
- # 仅 Unity 导出工程才有 unityLibrary/src/main/assets
169
176
  unity_assets = File.join(project_dir, "unityLibrary", "src", "main", "assets")
170
177
  return unless File.directory?(unity_assets)
171
178
 
172
- # 清理旧 pack:避免 assets 内容变化后残留旧 pack 目录导致重复/脏数据
179
+ splittable = Dir.glob(File.join(unity_assets, "*")).select { |p| File.directory?(p) }
180
+ splittable.reject! { |p| File.basename(p).downcase == "bin" }
181
+ splittable.reject! { |p| Dir.glob(File.join(p, "*")).empty? }
182
+
183
+ if splittable.empty?
184
+ existing_packs = Dir.glob(File.join(project_dir, "*_pack")).select do |dir|
185
+ next false unless File.directory?(dir)
186
+
187
+ gradle = File.join(dir, "build.gradle")
188
+ next false unless File.file?(gradle)
189
+
190
+ File.read(gradle).include?("com.android.asset-pack")
191
+ end
192
+
193
+ if existing_packs.empty?
194
+ puts "unityLibrary assets 下无可拆分资源,且工程无 *_pack 模块,跳过 PAD"
195
+ return
196
+ end
197
+
198
+ pack_module_refs = existing_packs.map { |dir| ":#{File.basename(dir)}" }
199
+ puts "unityLibrary assets 下无可拆分资源,复用已有 *_pack 模块:#{pack_module_refs.join(', ')}"
200
+ GradleHelper.update_settings_gradle(project_dir, pack_module_refs)
201
+ GradleHelper.update_launcher_build_gradle(project_dir, pack_module_refs)
202
+ return
203
+ end
204
+
205
+ # 有新资源可拆分,先清理旧 pack 再重建,避免残留脏数据
173
206
  Dir.glob(File.join(project_dir, "*_pack")).each do |dir|
174
207
  next unless File.directory?(dir)
175
208
 
176
- # 尽量只删除我们生成的模块:要求 build.gradle 存在且包含 asset-pack 插件
177
209
  gradle = File.join(dir, "build.gradle")
178
210
  next unless File.file?(gradle)
179
211
  content = File.read(gradle)
@@ -529,7 +561,30 @@ module Pindo
529
561
  asset_subdirs = Dir.glob(File.join(assets_path, "*")).select { |path| File.directory?(path) }
530
562
 
531
563
  if asset_subdirs.empty?
532
- puts "未找到 assets 下的子目录,跳过 Play Asset Delivery 设置"
564
+ # 重要:assets 为空并不代表不需要 PAD。
565
+ # 很多工程会在首次分包后把资源移动到 *_pack,从而 assets 留空;
566
+ # 此时仍需要确保 settings.gradle / launcher/build.gradle 中的 include/assetPacks 引用存在,
567
+ # 否则会出现 `assetPacks = [":yoo_pack"]` 未写入/被外部基线覆盖的问题。
568
+ existing_pack_modules = Dir.glob(File.join(project_dir, "*_pack")).filter_map do |dir|
569
+ next unless File.directory?(dir)
570
+
571
+ gradle = File.join(dir, "build.gradle")
572
+ next unless File.file?(gradle)
573
+
574
+ content = File.read(gradle)
575
+ next unless content.include?("com.android.asset-pack")
576
+
577
+ ":#{File.basename(dir)}"
578
+ end.uniq
579
+
580
+ if existing_pack_modules.any?
581
+ puts "assets 下无可拆分资源,检测到已存在的 Asset Pack 模块,将确保工程引用完整..."
582
+ GradleHelper.update_settings_gradle(project_dir, existing_pack_modules)
583
+ GradleHelper.update_launcher_build_gradle(project_dir, existing_pack_modules)
584
+ puts "✓ 已同步现存 Asset Pack 引用:#{existing_pack_modules.join(', ')}"
585
+ else
586
+ puts "未找到 assets 下的子目录,且工程中不存在 *_pack 模块,跳过 Play Asset Delivery 设置"
587
+ end
533
588
  return
534
589
  end
535
590
 
@@ -646,13 +646,24 @@ module Pindo
646
646
  return
647
647
  end
648
648
 
649
+ pack_modules = Array(pack_modules).compact.map(&:to_s).reject(&:empty?).uniq
650
+ if pack_modules.empty?
651
+ puts "✓ 未检测到需要写入的 Asset Pack 模块,跳过更新 launcher/build.gradle"
652
+ return
653
+ end
654
+
649
655
  content = File.read(launcher_build_gradle_path)
650
656
  original_content = content.dup
651
657
 
652
658
  # 检查是否已有 assetPacks 配置
653
659
  if content.match(/assetPacks\s*=\s*\[/)
654
- # 更新现有的 assetPacks 配置
655
- content.gsub!(/assetPacks\s*=\s*\[[^\]]*\]/, "assetPacks = #{pack_modules.inspect}")
660
+ # 合并现有的 assetPacks 配置(不要覆盖用户已有项)
661
+ content.gsub!(/assetPacks\s*=\s*\[([^\]]*)\]/m) do
662
+ raw_items = Regexp.last_match(1).to_s
663
+ existing = raw_items.scan(/["']([^"']+)["']/).flatten.map(&:to_s).reject(&:empty?)
664
+ merged = (existing + pack_modules).uniq
665
+ "assetPacks = #{merged.inspect}"
666
+ end
656
667
  else
657
668
  # 在 android 块中添加 assetPacks 配置
658
669
  android_block_pattern = /(android\s*\{)/