pindo 5.17.4 → 5.18.3
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 +120 -38
- data/lib/pindo/command/android/autobuild.rb +92 -31
- data/lib/pindo/command/appstore/adhocbuild.rb +1 -1
- data/lib/pindo/command/appstore/autobuild.rb +1 -1
- data/lib/pindo/command/appstore/autoresign.rb +1 -1
- data/lib/pindo/command/appstore/updateid.rb +229 -0
- data/lib/pindo/command/appstore.rb +1 -0
- data/lib/pindo/command/ios/autobuild.rb +70 -33
- data/lib/pindo/command/ios/podpush.rb +1 -1
- data/lib/pindo/command/unity/autobuild.rb +38 -18
- data/lib/pindo/command/utils/allcopyconfig.rb +144 -0
- data/lib/pindo/command/utils/copyconfig.rb +207 -0
- data/lib/pindo/command/utils/icon.rb +2 -2
- data/lib/pindo/command/utils/renewbundleid.rb +199 -0
- data/lib/pindo/command/utils/renewcert.rb +56 -54
- data/lib/pindo/command/utils.rb +3 -0
- data/lib/pindo/command/web/autobuild.rb +10 -8
- data/lib/pindo/config/build_info_manager.rb +1 -3
- data/lib/pindo/module/android/android_build_helper.rb +198 -33
- data/lib/pindo/module/android/android_config_helper.rb +305 -88
- data/lib/pindo/module/android/android_project_helper.rb +124 -14
- data/lib/pindo/module/android/android_res_helper.rb +349 -51
- data/lib/pindo/module/android/keystore_helper.rb +611 -295
- data/lib/pindo/module/android/workflow_gradle_injector.rb +702 -0
- data/lib/pindo/module/appselect.rb +4 -4
- data/lib/pindo/module/appstore/bundleid_helper.rb +204 -14
- data/lib/pindo/module/build/build_helper.rb +76 -10
- data/lib/pindo/module/build/git_repo_helper.rb +4 -4
- data/lib/pindo/module/cert/mode/base_cert_operator.rb +12 -6
- data/lib/pindo/module/pgyer/pgyerhelper.rb +124 -39
- data/lib/pindo/module/task/model/build/android_build_dev_task.rb +64 -6
- data/lib/pindo/module/task/model/git/git_commit_task.rb +70 -54
- data/lib/pindo/module/task/model/git/git_tag_task.rb +13 -9
- data/lib/pindo/module/task/model/jps/jps_upload_task.rb +110 -3
- data/lib/pindo/module/task/model/unity/unity_export_task.rb +2 -1
- data/lib/pindo/module/task/model/unity/unity_update_task.rb +2 -1
- data/lib/pindo/module/task/model/unity/unity_yoo_asset_task.rb +2 -1
- data/lib/pindo/module/task/model/unity_task.rb +2 -1
- data/lib/pindo/module/unity/unity_helper.rb +13 -10
- data/lib/pindo/module/unity/unity_proc_helper.rb +27 -2
- data/lib/pindo/module/xcode/applovin_xcode_helper.rb +6 -2
- data/lib/pindo/module/xcode/res/xcode_res_constant.rb +72 -0
- data/lib/pindo/module/xcode/res/xcode_res_handler.rb +3 -3
- data/lib/pindo/module/xcode/xcode_build_config.rb +46 -17
- data/lib/pindo/module/xcode/xcode_build_helper.rb +186 -25
- data/lib/pindo/module/xcode/xcode_project_helper.rb +1 -1
- data/lib/pindo/module/xcode/xcode_res_helper.rb +32 -16
- data/lib/pindo/options/groups/build_options.rb +5 -5
- data/lib/pindo/options/groups/git_options.rb +7 -5
- data/lib/pindo/options/groups/unity_options.rb +11 -0
- data/lib/pindo/options/helpers/bundleid_selector.rb +25 -0
- data/lib/pindo/options/helpers/git_constants.rb +7 -6
- data/lib/pindo/version.rb +3 -3
- metadata +12 -7
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'fileutils'
|
|
2
2
|
require 'open-uri'
|
|
3
|
+
require 'json'
|
|
3
4
|
require_relative '../../base/funlog'
|
|
4
5
|
require_relative 'android_project_helper'
|
|
5
6
|
require_relative '../../base/executable'
|
|
@@ -431,6 +432,105 @@ module Pindo
|
|
|
431
432
|
end
|
|
432
433
|
end
|
|
433
434
|
|
|
435
|
+
# 从 Android 导出工程路径推断 Unity 工程根目录(GoodPlatform/BaseAndroid 布局)
|
|
436
|
+
# @param export_project_dir [String] 例如 GoodPlatform/BaseAndroid
|
|
437
|
+
# @return [String, nil] Unity 根目录绝对路径,无法推断时返回 nil
|
|
438
|
+
def self.infer_unity_project_root_from_export_dir(export_project_dir)
|
|
439
|
+
return nil if export_project_dir.nil? || !File.directory?(export_project_dir)
|
|
440
|
+
|
|
441
|
+
abs = File.expand_path(export_project_dir)
|
|
442
|
+
base = File.basename(abs)
|
|
443
|
+
parent = File.basename(File.dirname(abs))
|
|
444
|
+
if base == 'BaseAndroid' && parent == 'GoodPlatform'
|
|
445
|
+
return File.expand_path('../..', abs)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
dir = abs
|
|
449
|
+
8.times do
|
|
450
|
+
if File.directory?(File.join(dir, 'Assets')) &&
|
|
451
|
+
File.directory?(File.join(dir, 'ProjectSettings'))
|
|
452
|
+
return dir
|
|
453
|
+
end
|
|
454
|
+
parent_dir = File.dirname(dir)
|
|
455
|
+
break if parent_dir == dir
|
|
456
|
+
|
|
457
|
+
dir = parent_dir
|
|
458
|
+
end
|
|
459
|
+
nil
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
# Unity 工程中 Firebase Android 配置(Editor 与 Gradle 均可能读取)
|
|
463
|
+
# @param unity_root [String] Unity 工程根目录
|
|
464
|
+
# @return [String]
|
|
465
|
+
def self.unity_assets_firebase_google_services_path(unity_root)
|
|
466
|
+
File.join(unity_root, 'Assets', 'Scripts', 'Firebase', 'google-services.json')
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# Unity Editor 侧 Firebase 可能读取的桌面配置(与 Assets/Firebase 下 json 宜保持一致)
|
|
470
|
+
# @param unity_root [String] Unity 工程根目录
|
|
471
|
+
# @return [String]
|
|
472
|
+
def self.unity_streaming_assets_google_services_desktop_path(unity_root)
|
|
473
|
+
File.join(unity_root, 'Assets', 'StreamingAssets', 'google-services-desktop.json')
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
# @param clients [Array]
|
|
477
|
+
# @param package_name [String]
|
|
478
|
+
# @return [Boolean]
|
|
479
|
+
def self.google_services_has_android_package?(clients, package_name)
|
|
480
|
+
return false unless clients.is_a?(Array)
|
|
481
|
+
|
|
482
|
+
clients.any? do |c|
|
|
483
|
+
next false unless c.is_a?(Hash)
|
|
484
|
+
ci = c['client_info']
|
|
485
|
+
next false unless ci.is_a?(Hash)
|
|
486
|
+
aci = ci['android_client_info']
|
|
487
|
+
aci.is_a?(Hash) && aci['package_name'] == package_name
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# 取首个含 android_client_info.package_name 的 client,作为追加新包名时的模板
|
|
492
|
+
# @param clients [Array]
|
|
493
|
+
# @return [Hash, nil]
|
|
494
|
+
def self.google_services_first_android_client(clients)
|
|
495
|
+
return nil unless clients.is_a?(Array)
|
|
496
|
+
|
|
497
|
+
clients.each do |c|
|
|
498
|
+
next unless c.is_a?(Hash)
|
|
499
|
+
ci = c['client_info']
|
|
500
|
+
next unless ci.is_a?(Hash)
|
|
501
|
+
aci = ci['android_client_info']
|
|
502
|
+
if aci.is_a?(Hash) && aci['package_name'].is_a?(String) && !aci['package_name'].empty?
|
|
503
|
+
return c
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
nil
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
# 不修改已有 client;若尚无目标 package_name,则深拷贝模板 client 并追加
|
|
510
|
+
# @param json [Hash]
|
|
511
|
+
# @param package_name [String]
|
|
512
|
+
# @return [Boolean] 是否追加了新 client
|
|
513
|
+
def self.google_services_merge_android_package!(json, package_name)
|
|
514
|
+
return false unless json.is_a?(Hash)
|
|
515
|
+
|
|
516
|
+
clients = json['client']
|
|
517
|
+
return false unless clients.is_a?(Array) && !clients.empty?
|
|
518
|
+
|
|
519
|
+
return false if google_services_has_android_package?(clients, package_name)
|
|
520
|
+
|
|
521
|
+
template = google_services_first_android_client(clients)
|
|
522
|
+
unless template
|
|
523
|
+
return false
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
new_client = JSON.parse(JSON.generate(template))
|
|
527
|
+
new_client['client_info'] ||= {}
|
|
528
|
+
new_client['client_info']['android_client_info'] ||= {}
|
|
529
|
+
new_client['client_info']['android_client_info']['package_name'] = package_name
|
|
530
|
+
clients << new_client
|
|
531
|
+
true
|
|
532
|
+
end
|
|
533
|
+
|
|
434
534
|
# 从配置仓库拷贝 google-services.json 到项目的 launcher 目录
|
|
435
535
|
# @param config_repo_dir [String] 配置仓库的路径
|
|
436
536
|
# @param project_dir [String] Android项目目录路径
|
|
@@ -493,9 +593,94 @@ module Pindo
|
|
|
493
593
|
puts " ⚠ 请确认文件位置是否正确"
|
|
494
594
|
end
|
|
495
595
|
|
|
596
|
+
# Unity Editor(Firebase.Editor)也会读取 Assets/Scripts/Firebase/google-services.json
|
|
597
|
+
unity_root = infer_unity_project_root_from_export_dir(project_dir)
|
|
598
|
+
if unity_root && File.directory?(File.join(unity_root, 'Assets'))
|
|
599
|
+
assets_firebase_dir = File.join(unity_root, 'Assets', 'Scripts', 'Firebase')
|
|
600
|
+
FileUtils.mkdir_p(assets_firebase_dir)
|
|
601
|
+
unity_gs = File.join(assets_firebase_dir, 'google-services.json')
|
|
602
|
+
FileUtils.cp(source_file, unity_gs)
|
|
603
|
+
puts " ✓ google-services.json 已拷贝到: #{unity_gs}"
|
|
604
|
+
end
|
|
605
|
+
|
|
496
606
|
return true
|
|
497
607
|
end
|
|
498
608
|
|
|
609
|
+
# 与 update_google_services_package_name 相同的候选路径(不一定存在)
|
|
610
|
+
def self.google_services_json_candidate_paths(project_dir)
|
|
611
|
+
candidates = [
|
|
612
|
+
File.join(project_dir, 'unityLibrary', 'launcher', 'google-services.json'),
|
|
613
|
+
File.join(project_dir, 'unityLibrary', 'google-services.json'),
|
|
614
|
+
File.join(project_dir, 'launcher', 'google-services.json'),
|
|
615
|
+
File.join(project_dir, 'app', 'google-services.json'),
|
|
616
|
+
File.join(project_dir, 'google-services.json')
|
|
617
|
+
]
|
|
618
|
+
unity_root = infer_unity_project_root_from_export_dir(project_dir)
|
|
619
|
+
if unity_root
|
|
620
|
+
candidates << unity_assets_firebase_google_services_path(unity_root)
|
|
621
|
+
candidates << unity_streaming_assets_google_services_desktop_path(unity_root)
|
|
622
|
+
end
|
|
623
|
+
candidates.uniq
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
# @param project_dir [String]
|
|
627
|
+
# @return [Array<String>] 项目内已存在的 google-services.json 绝对路径
|
|
628
|
+
def self.existing_google_services_json_paths(project_dir)
|
|
629
|
+
raise ArgumentError, "项目目录不能为空" if project_dir.nil?
|
|
630
|
+
raise ArgumentError, "项目路径无效: #{project_dir}" unless File.directory?(project_dir)
|
|
631
|
+
|
|
632
|
+
google_services_json_candidate_paths(project_dir).select { |p| File.exist?(p) }
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
# 在 google-services.json 的 client 数组中确保存在目标 Android package_name:不改写已有 client,
|
|
636
|
+
# 仅当尚不存在时深拷贝模板 client 并追加,避免切换 bundle id / applicationId 时 Firebase Unity SDK 反复弹窗。
|
|
637
|
+
# @param project_dir [String] Android项目目录路径
|
|
638
|
+
# @param package_name [String] 目标 applicationId(例如: com.heroneverdie101.fancyapptest)
|
|
639
|
+
# @return [Boolean] 是否至少在一个文件中新追加了 client(已存在目标包名则视为无需写入)
|
|
640
|
+
def self.update_google_services_package_name(project_dir: nil, package_name: nil)
|
|
641
|
+
raise ArgumentError, "项目目录不能为空" if project_dir.nil?
|
|
642
|
+
raise ArgumentError, "Package Name不能为空" if package_name.nil? || package_name.empty?
|
|
643
|
+
raise ArgumentError, "项目路径无效: #{project_dir}" unless File.directory?(project_dir)
|
|
644
|
+
|
|
645
|
+
target_files = existing_google_services_json_paths(project_dir)
|
|
646
|
+
if target_files.empty?
|
|
647
|
+
Funlog.instance.fancyinfo_warning("未找到 google-services.json,无法合并 package_name: #{package_name}")
|
|
648
|
+
return false
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
updated_any = false
|
|
652
|
+
|
|
653
|
+
target_files.each do |target_file|
|
|
654
|
+
begin
|
|
655
|
+
raw = File.read(target_file)
|
|
656
|
+
json = JSON.parse(raw)
|
|
657
|
+
|
|
658
|
+
unless json.is_a?(Hash) && json['client'].is_a?(Array) && !json['client'].empty?
|
|
659
|
+
Funlog.instance.fancyinfo_warning("google-services.json 缺少有效 client 数组,跳过: #{target_file}")
|
|
660
|
+
next
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
unless google_services_first_android_client(json['client'])
|
|
664
|
+
Funlog.instance.fancyinfo_warning("google-services.json 中无可用 Android client 模板,跳过: #{target_file}")
|
|
665
|
+
next
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
merged = google_services_merge_android_package!(json, package_name)
|
|
669
|
+
if merged
|
|
670
|
+
File.write(target_file, JSON.pretty_generate(json) + "\n")
|
|
671
|
+
puts " ✓ 已合并 #{File.basename(target_file)}:新增 client package_name #{package_name}(保留原有条目)"
|
|
672
|
+
updated_any = true
|
|
673
|
+
end
|
|
674
|
+
rescue JSON::ParserError
|
|
675
|
+
Funlog.instance.fancyinfo_warning("google-services.json 不是合法 JSON,跳过更新: #{target_file}")
|
|
676
|
+
rescue StandardError => e
|
|
677
|
+
Funlog.instance.fancyinfo_warning("更新 google-services.json 失败: #{e.message} (#{target_file})")
|
|
678
|
+
end
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
updated_any
|
|
682
|
+
end
|
|
683
|
+
|
|
499
684
|
# 从配置仓库应用配置(拷贝 config.json)
|
|
500
685
|
# @param config_repo_dir [String] 配置仓库的路径
|
|
501
686
|
# @param project_dir [String] Android项目目录路径
|
|
@@ -661,13 +846,9 @@ module Pindo
|
|
|
661
846
|
return true
|
|
662
847
|
end
|
|
663
848
|
|
|
664
|
-
#
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
unless success
|
|
668
|
-
# DOM方法失败,尝试文本替换
|
|
669
|
-
success = add_scheme_with_text_replace(manifest_path, scheme_name)
|
|
670
|
-
end
|
|
849
|
+
# 为了避免 Nokogiri 重新序列化导致整体格式变化,优先使用文本方式局部插入。
|
|
850
|
+
activity_name = main_activity["name"] || main_activity["android:name"] || main_activity["#{android_prefix}:name"]
|
|
851
|
+
success = add_scheme_with_text_replace(manifest_path, scheme_name, activity_name: activity_name)
|
|
671
852
|
|
|
672
853
|
unless success
|
|
673
854
|
Funlog.instance.fancyinfo_error("无法添加scheme: #{scheme_name}")
|
|
@@ -772,33 +953,17 @@ module Pindo
|
|
|
772
953
|
[scheme_exists, existing_scheme]
|
|
773
954
|
end
|
|
774
955
|
|
|
775
|
-
#
|
|
776
|
-
def self.
|
|
956
|
+
# 使用文本替换添加scheme
|
|
957
|
+
def self.add_scheme_with_text_replace(manifest_path, scheme_name, activity_name: nil)
|
|
777
958
|
begin
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
# 创建intent-filter
|
|
781
|
-
intent_filter = doc.create_element('intent-filter')
|
|
782
|
-
|
|
783
|
-
# 添加子元素
|
|
784
|
-
intent_filter.add_child(create_element(doc, 'action', "#{android_prefix}:name", 'android.intent.action.VIEW'))
|
|
785
|
-
intent_filter.add_child(create_element(doc, 'category', "#{android_prefix}:name", 'android.intent.category.DEFAULT'))
|
|
786
|
-
intent_filter.add_child(create_element(doc, 'category', "#{android_prefix}:name", 'android.intent.category.BROWSABLE'))
|
|
787
|
-
intent_filter.add_child(create_element(doc, 'data', "#{android_prefix}:scheme", scheme_name))
|
|
788
|
-
|
|
789
|
-
# 添加空白和缩进
|
|
790
|
-
activity.add_child(doc.create_text_node("\n "))
|
|
791
|
-
activity.add_child(intent_filter)
|
|
792
|
-
activity.add_child(doc.create_text_node("\n "))
|
|
959
|
+
# 读取原始内容
|
|
960
|
+
xml_content = File.read(manifest_path)
|
|
793
961
|
|
|
794
|
-
|
|
795
|
-
|
|
962
|
+
modified_xml = insert_scheme_intent_filter_before_activity_close(xml_content, scheme_name, activity_name: activity_name)
|
|
963
|
+
return false unless modified_xml
|
|
796
964
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
File.write(manifest_path, xml_content)
|
|
800
|
-
return true
|
|
801
|
-
end
|
|
965
|
+
File.write(manifest_path, modified_xml)
|
|
966
|
+
return true
|
|
802
967
|
|
|
803
968
|
return false
|
|
804
969
|
rescue => e
|
|
@@ -806,75 +971,127 @@ module Pindo
|
|
|
806
971
|
end
|
|
807
972
|
end
|
|
808
973
|
|
|
809
|
-
|
|
810
|
-
def self.create_element(doc, name, attr_name, attr_value)
|
|
811
|
-
element = doc.create_element(name)
|
|
812
|
-
element[attr_name] = attr_value
|
|
813
|
-
element
|
|
814
|
-
end
|
|
815
|
-
|
|
816
|
-
# 使用文本替换添加scheme
|
|
817
|
-
def self.add_scheme_with_text_replace(manifest_path, scheme_name)
|
|
974
|
+
def self.update_existing_scheme(manifest_path, activity, android_prefix, existing_scheme, scheme_name)
|
|
818
975
|
begin
|
|
819
|
-
# 读取原始内容
|
|
820
976
|
xml_content = File.read(manifest_path)
|
|
977
|
+
modified = update_scheme_value_in_place(xml_content, existing_scheme, scheme_name)
|
|
978
|
+
return false unless modified
|
|
821
979
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
<intent-filter>
|
|
825
|
-
<action android:name="android.intent.action.VIEW"/>
|
|
826
|
-
<category android:name="android.intent.category.DEFAULT"/>
|
|
827
|
-
<category android:name="android.intent.category.BROWSABLE"/>
|
|
828
|
-
<data android:scheme="#{scheme_name}"/>
|
|
829
|
-
</intent-filter>}
|
|
830
|
-
|
|
831
|
-
# 在</activity>前添加intent-filter
|
|
832
|
-
if xml_content.match(/<\/activity>/)
|
|
833
|
-
modified_xml = xml_content.gsub(/<\/activity>/) do |match|
|
|
834
|
-
"#{scheme_intent_filter}\n #{match}"
|
|
835
|
-
end
|
|
836
|
-
|
|
837
|
-
# 保存修改
|
|
838
|
-
File.write(manifest_path, modified_xml)
|
|
839
|
-
return true
|
|
840
|
-
end
|
|
841
|
-
|
|
842
|
-
return false
|
|
980
|
+
File.write(manifest_path, modified)
|
|
981
|
+
true
|
|
843
982
|
rescue => e
|
|
844
983
|
return false
|
|
845
984
|
end
|
|
846
985
|
end
|
|
847
986
|
|
|
848
|
-
def self.
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
# 查找所有intent-filter
|
|
853
|
-
intent_filters = doc.xpath("//intent-filter")
|
|
854
|
-
|
|
855
|
-
intent_filters.each do |intent_filter|
|
|
856
|
-
# 检查intent-filter中的scheme
|
|
857
|
-
intent_filter.xpath("data[@#{android_prefix}:scheme]").each do |data|
|
|
858
|
-
current_scheme = data["#{android_prefix}:scheme"]
|
|
859
|
-
if current_scheme == existing_scheme
|
|
860
|
-
# 找到intent-filter,更新scheme
|
|
861
|
-
data["#{android_prefix}:scheme"] = scheme_name
|
|
862
|
-
end
|
|
863
|
-
end
|
|
864
|
-
end
|
|
987
|
+
def self.update_scheme_value_in_place(xml_content, existing_scheme, scheme_name)
|
|
988
|
+
return nil if xml_content.to_s.empty?
|
|
989
|
+
return nil if existing_scheme.to_s.empty? || scheme_name.to_s.empty?
|
|
865
990
|
|
|
866
|
-
|
|
867
|
-
|
|
991
|
+
# 尽量局部替换 data 的 android:scheme / scheme 属性值,避免重排整个 XML。
|
|
992
|
+
patterns = [
|
|
993
|
+
/(?<attr>\bandroid:scheme)\s*=\s*(?<q>["'])#{Regexp.escape(existing_scheme)}\k<q>/,
|
|
994
|
+
/(?<attr>\bscheme)\s*=\s*(?<q>["'])#{Regexp.escape(existing_scheme)}\k<q>/
|
|
995
|
+
]
|
|
868
996
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
997
|
+
patterns.each do |pat|
|
|
998
|
+
next unless xml_content.match?(pat)
|
|
999
|
+
return xml_content.gsub(pat) { |m| %(#{$~[:attr]}="#{scheme_name}") }
|
|
1000
|
+
end
|
|
1001
|
+
|
|
1002
|
+
nil
|
|
1003
|
+
end
|
|
1004
|
+
|
|
1005
|
+
def self.insert_scheme_intent_filter_before_activity_close(xml_content, scheme_name, activity_name: nil)
|
|
1006
|
+
return nil if xml_content.to_s.empty?
|
|
1007
|
+
|
|
1008
|
+
newline = xml_content.include?("\r\n") ? "\r\n" : "\n"
|
|
1009
|
+
|
|
1010
|
+
activity_block = nil
|
|
1011
|
+
if activity_name && !activity_name.to_s.empty?
|
|
1012
|
+
name = Regexp.escape(activity_name.to_s)
|
|
1013
|
+
# 锁定到目标 activity(兼容 android:name / name)
|
|
1014
|
+
activity_block = xml_content.match(
|
|
1015
|
+
/(?<open><activity\b[^>]*(?:\bandroid:name|\bname)\s*=\s*["']#{name}["'][^>]*>)(?<inner>.*?)(?<close><\/activity>)/m
|
|
1016
|
+
)
|
|
1017
|
+
end
|
|
1018
|
+
|
|
1019
|
+
# 找不到目标 activity 时,再退化为第一个 </activity>(保持向后兼容)
|
|
1020
|
+
if activity_block
|
|
1021
|
+
open_tag = activity_block[:open]
|
|
1022
|
+
inner = activity_block[:inner]
|
|
1023
|
+
# close 缩进必须与原文件一致(避免 </activity> 顶格)
|
|
1024
|
+
close_line_indent =
|
|
1025
|
+
activity_block[0].match(/^(?<indent>[ \t]*)<\/activity>\s*$/m)&.[](:indent) ||
|
|
1026
|
+
activity_block[0].match(/^(?<indent>[ \t]*)<activity\b/m)&.[](:indent) ||
|
|
1027
|
+
""
|
|
1028
|
+
close_line = "#{close_line_indent}</activity>"
|
|
1029
|
+
|
|
1030
|
+
# 复用该 activity 内已存在 intent-filter 的缩进(取最短的,避免被历史插歪的块污染)
|
|
1031
|
+
intent_filter_indents = inner.scan(/^(?<indent>[ \t]*)<intent-filter\b/m).flatten
|
|
1032
|
+
child_indent = intent_filter_indents.reject(&:empty?).min_by(&:length)
|
|
1033
|
+
|
|
1034
|
+
# 如果没有 intent-filter,则尝试复用其它子元素缩进
|
|
1035
|
+
child_indent ||= inner.scan(/^(?<indent>[ \t]*)<(?:action|category|data)\b/m).flatten.reject(&:empty?).min_by(&:length)
|
|
1036
|
+
|
|
1037
|
+
# 如果 activity 内部完全没有子元素,则用 close_tag 行缩进推导
|
|
1038
|
+
unless child_indent
|
|
1039
|
+
# 默认与现有 AndroidManifest 常见风格对齐:activity 内缩进步长通常为 4
|
|
1040
|
+
child_indent = close_line_indent + " "
|
|
873
1041
|
end
|
|
874
1042
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1043
|
+
# 推导 intent-filter 内部子元素的缩进步长(2/4/tab 都兼容)
|
|
1044
|
+
existing_grandchild = inner.match(/^(?<indent>[ \t]*)<(?:action|category|data)\b/m)&.[](:indent)
|
|
1045
|
+
indent_step = nil
|
|
1046
|
+
if existing_grandchild && existing_grandchild.start_with?(child_indent)
|
|
1047
|
+
step = existing_grandchild.delete_prefix(child_indent)
|
|
1048
|
+
indent_step = step unless step.empty?
|
|
1049
|
+
end
|
|
1050
|
+
indent_step ||= " "
|
|
1051
|
+
grandchild_indent = child_indent + indent_step
|
|
1052
|
+
|
|
1053
|
+
block = [
|
|
1054
|
+
"#{child_indent}<intent-filter>",
|
|
1055
|
+
"#{grandchild_indent}<action android:name=\"android.intent.action.VIEW\" />",
|
|
1056
|
+
"#{grandchild_indent}<category android:name=\"android.intent.category.DEFAULT\" />",
|
|
1057
|
+
"#{grandchild_indent}<category android:name=\"android.intent.category.BROWSABLE\" />",
|
|
1058
|
+
"#{grandchild_indent}<data android:scheme=\"#{scheme_name}\" />",
|
|
1059
|
+
"#{child_indent}</intent-filter>",
|
|
1060
|
+
].join(newline)
|
|
1061
|
+
|
|
1062
|
+
# 规范化:块与块之间留 1 个空行;保持 </activity> 缩进对齐
|
|
1063
|
+
normalized_inner = inner.sub(/(?:#{Regexp.escape(newline)}[ \t]*)*\z/, "")
|
|
1064
|
+
replaced = [
|
|
1065
|
+
open_tag,
|
|
1066
|
+
normalized_inner,
|
|
1067
|
+
newline,
|
|
1068
|
+
newline,
|
|
1069
|
+
block,
|
|
1070
|
+
newline,
|
|
1071
|
+
close_line
|
|
1072
|
+
].join
|
|
1073
|
+
return xml_content.sub(activity_block[0], replaced)
|
|
1074
|
+
end
|
|
1075
|
+
|
|
1076
|
+
m = xml_content.match(/^(?<indent>[ \t]*)<\/activity>\s*$/m)
|
|
1077
|
+
return nil unless m
|
|
1078
|
+
|
|
1079
|
+
close_indent = m[:indent] || ""
|
|
1080
|
+
# 默认与现有 AndroidManifest 常见风格对齐:activity 内缩进步长通常为 4
|
|
1081
|
+
child_indent = close_indent + " "
|
|
1082
|
+
grandchild_indent = child_indent + " "
|
|
1083
|
+
|
|
1084
|
+
block = [
|
|
1085
|
+
"#{child_indent}<intent-filter>",
|
|
1086
|
+
"#{grandchild_indent}<action android:name=\"android.intent.action.VIEW\" />",
|
|
1087
|
+
"#{grandchild_indent}<category android:name=\"android.intent.category.DEFAULT\" />",
|
|
1088
|
+
"#{grandchild_indent}<category android:name=\"android.intent.category.BROWSABLE\" />",
|
|
1089
|
+
"#{grandchild_indent}<data android:scheme=\"#{scheme_name}\" />",
|
|
1090
|
+
"#{child_indent}</intent-filter>",
|
|
1091
|
+
].join(newline)
|
|
1092
|
+
|
|
1093
|
+
xml_content.sub(/^(?<indent>[ \t]*)<\/activity>\s*$/m) do |match|
|
|
1094
|
+
"#{newline}#{block}#{newline}#{newline}#{match}"
|
|
878
1095
|
end
|
|
879
1096
|
end
|
|
880
1097
|
|
|
@@ -228,30 +228,49 @@ module Pindo
|
|
|
228
228
|
end
|
|
229
229
|
|
|
230
230
|
content = File.read(settings_gradle_path)
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
modules = extract_modules_from_settings(content)
|
|
232
|
+
project_dir_map = extract_module_project_dirs(content)
|
|
233
233
|
|
|
234
234
|
main_module = modules.find do |m|
|
|
235
235
|
module_name = m.split(':').last
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
236
|
+
module_rel_path = project_dir_map[m] || module_name
|
|
237
|
+
module_dir = File.join(project_path, module_rel_path)
|
|
238
|
+
gradle_path = File.join(module_dir, "build.gradle")
|
|
239
|
+
gradle_kts_path = File.join(module_dir, "build.gradle.kts")
|
|
240
|
+
|
|
241
|
+
gradle_file = if File.exist?(gradle_kts_path)
|
|
242
|
+
gradle_kts_path
|
|
243
|
+
elsif File.exist?(gradle_path)
|
|
244
|
+
gradle_path
|
|
242
245
|
end
|
|
243
246
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
next false unless gradle_file
|
|
248
|
+
|
|
249
|
+
gradle_content = File.read(gradle_file)
|
|
250
|
+
android_application_module?(gradle_content)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# 兜底:一些工程未声明标准 application 插件,优先尝试常见主模块名
|
|
254
|
+
if main_module.nil?
|
|
255
|
+
%w[app launcher application].each do |candidate|
|
|
256
|
+
candidate_module = modules.find { |m| m.split(':').last == candidate }
|
|
257
|
+
next unless candidate_module
|
|
258
|
+
|
|
259
|
+
module_rel_path = project_dir_map[candidate_module] || candidate
|
|
260
|
+
module_dir = File.join(project_path, module_rel_path)
|
|
261
|
+
has_gradle = File.exist?(File.join(module_dir, "build.gradle")) || File.exist?(File.join(module_dir, "build.gradle.kts"))
|
|
262
|
+
has_manifest = File.exist?(File.join(module_dir, "src/main/AndroidManifest.xml"))
|
|
263
|
+
if has_gradle && has_manifest
|
|
264
|
+
main_module = candidate_module
|
|
265
|
+
break
|
|
266
|
+
end
|
|
249
267
|
end
|
|
250
268
|
end
|
|
251
269
|
return nil unless main_module
|
|
252
270
|
|
|
253
271
|
module_name = main_module.split(':').last
|
|
254
|
-
|
|
272
|
+
module_rel_path = project_dir_map[main_module] || module_name
|
|
273
|
+
File.join(project_path, module_rel_path)
|
|
255
274
|
end
|
|
256
275
|
|
|
257
276
|
|
|
@@ -273,7 +292,98 @@ module Pindo
|
|
|
273
292
|
android_dir
|
|
274
293
|
end
|
|
275
294
|
|
|
295
|
+
# 在 Unity 作为 lib 的工程中,将 Unity 根目录下 gradle.properties
|
|
296
|
+
# 中的关键配置同步到主工程的 gradle.properties 中
|
|
297
|
+
# 当前仅同步以下键:
|
|
298
|
+
# - android.aapt2FromMavenOverride
|
|
299
|
+
# - org.gradle.java.home
|
|
300
|
+
def sync_gradle_properties_from_unity_to_main(project_path)
|
|
301
|
+
return unless unity_as_lib_android_project?(project_path)
|
|
302
|
+
|
|
303
|
+
unity_gradle_properties = File.join(project_path, "Unity", "gradle.properties")
|
|
304
|
+
return unless File.exist?(unity_gradle_properties)
|
|
305
|
+
|
|
306
|
+
keys_to_sync = [
|
|
307
|
+
"android.aapt2FromMavenOverride",
|
|
308
|
+
"org.gradle.java.home",
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
unity_values = {}
|
|
312
|
+
File.read(unity_gradle_properties).each_line do |line|
|
|
313
|
+
stripped = line.strip
|
|
314
|
+
next if stripped.empty? || stripped.start_with?("#", "!")
|
|
315
|
+
|
|
316
|
+
keys_to_sync.each do |key|
|
|
317
|
+
# 兼容前后有空格的 "key = value" 写法
|
|
318
|
+
if stripped =~ /^#{Regexp.escape(key)}\s*=\s*(.+)$/
|
|
319
|
+
unity_values[key] = Regexp.last_match(1).strip
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
return if unity_values.empty?
|
|
325
|
+
|
|
326
|
+
main_gradle_properties = File.join(project_path, "gradle.properties")
|
|
327
|
+
main_content = File.exist?(main_gradle_properties) ? File.read(main_gradle_properties) : ""
|
|
328
|
+
original_content = main_content.dup
|
|
329
|
+
|
|
330
|
+
keys_to_sync.each do |key|
|
|
331
|
+
value = unity_values[key]
|
|
332
|
+
next unless value
|
|
333
|
+
|
|
334
|
+
key_regex = /^#{Regexp.escape(key)}\s*=.*$/
|
|
335
|
+
if main_content =~ key_regex
|
|
336
|
+
# 替换已存在的配置行
|
|
337
|
+
main_content = main_content.gsub(key_regex, "#{key}=#{value}")
|
|
338
|
+
else
|
|
339
|
+
# 追加新的配置行
|
|
340
|
+
main_content << "\n" unless main_content.empty? || main_content.end_with?("\n")
|
|
341
|
+
main_content << "#{key}=#{value}\n"
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
return if main_content == original_content
|
|
346
|
+
|
|
347
|
+
File.write(main_gradle_properties, main_content)
|
|
348
|
+
end
|
|
349
|
+
|
|
276
350
|
private
|
|
351
|
+
|
|
352
|
+
def extract_modules_from_settings(content)
|
|
353
|
+
modules = []
|
|
354
|
+
# 兼容 include ':app' / include(":app", ":lib")
|
|
355
|
+
content.scan(/^\s*include(?:\s*\(|\s+)(.+)$/).each do |match|
|
|
356
|
+
include_args = match[0]
|
|
357
|
+
include_args.scan(/['"](:[^'"]+)['"]/).each do |module_match|
|
|
358
|
+
modules << module_match[0]
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
modules.uniq
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def extract_module_project_dirs(content)
|
|
365
|
+
module_dirs = {}
|
|
366
|
+
|
|
367
|
+
# Groovy: project(':app').projectDir = new File('android/app')
|
|
368
|
+
content.scan(/project\(\s*['"](:[^'"]+)['"]\s*\)\.projectDir\s*=\s*new\s+File\(\s*['"]([^'"]+)['"]\s*\)/).each do |module_name, rel_path|
|
|
369
|
+
module_dirs[module_name] = rel_path
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Kotlin DSL: project(":app").projectDir = file("android/app")
|
|
373
|
+
content.scan(/project\(\s*['"](:[^'"]+)['"]\s*\)\.projectDir\s*=\s*file\(\s*['"]([^'"]+)['"]\s*\)/).each do |module_name, rel_path|
|
|
374
|
+
module_dirs[module_name] = rel_path
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
module_dirs
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def android_application_module?(gradle_content)
|
|
381
|
+
# 标准声明方式
|
|
382
|
+
return true if gradle_content.include?("com.android.application")
|
|
383
|
+
|
|
384
|
+
# Version Catalog alias 声明(如 alias(libs.plugins.android.application))
|
|
385
|
+
gradle_content.match?(/alias\s*\(\s*libs\.plugins\.[A-Za-z0-9_.-]*android[A-Za-z0-9_.-]*application[A-Za-z0-9_.-]*\s*\)/)
|
|
386
|
+
end
|
|
277
387
|
end
|
|
278
388
|
end
|
|
279
389
|
end
|