pindo 5.13.6 → 5.13.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/pindo/command/android/autobuild.rb +38 -7
- data/lib/pindo/command/appstore/adhocbuild.rb +52 -6
- data/lib/pindo/command/appstore/autobuild.rb +51 -5
- data/lib/pindo/command/appstore/autoresign.rb +2 -2
- data/lib/pindo/command/ios/autobuild.rb +62 -16
- data/lib/pindo/command/ios/fixproj.rb +92 -0
- data/lib/pindo/command/ios.rb +1 -0
- data/lib/pindo/command/jps/upload.rb +1 -1
- data/lib/pindo/command/unity/autobuild.rb +47 -10
- data/lib/pindo/command/web/autobuild.rb +44 -14
- data/lib/pindo/module/pgyer/pgyerhelper.rb +1 -1
- data/lib/pindo/module/task/model/git_tag_task.rb +1 -1
- data/lib/pindo/module/task/model/{jps_upload_task.rb → jps/jps_upload_task.rb} +2 -2
- data/lib/pindo/module/task/model/{ipa_local_resign_task.rb → resign/ipa_local_resign_task.rb} +2 -2
- data/lib/pindo/module/task/model/{jps_resign_task.rb → resign/jps_resign_task.rb} +2 -2
- data/lib/pindo/module/task/model/unity/unity_config_task.rb +107 -0
- data/lib/pindo/module/task/model/{unity_export_task.rb → unity/unity_export_task.rb} +71 -74
- data/lib/pindo/module/task/model/unity/unity_update_task.rb +94 -0
- data/lib/pindo/module/task/model/unity/unity_yoo_asset_task.rb +155 -0
- data/lib/pindo/module/task/model/unity_task.rb +113 -0
- data/lib/pindo/module/task/task_manager.rb +9 -1
- data/lib/pindo/module/unity/unity_command_helper.rb +188 -0
- data/lib/pindo/module/unity/unity_env_helper.rb +208 -0
- data/lib/pindo/module/unity/unity_helper.rb +189 -746
- data/lib/pindo/module/unity/unity_proc_helper.rb +390 -0
- data/lib/pindo/module/xcode/xcode_build_helper.rb +3 -3
- data/lib/pindo/version.rb +1 -1
- metadata +13 -5
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
require 'pindo/module/task/model/unity_task'
|
|
2
|
+
require 'pindo/base/funlog'
|
|
3
|
+
|
|
4
|
+
module Pindo
|
|
5
|
+
module TaskSystem
|
|
6
|
+
# Unity YooAsset 资源构建任务
|
|
7
|
+
# 用于构建 YooAsset 资源包
|
|
8
|
+
class UnityYooAssetTask < UnityTask
|
|
9
|
+
attr_reader :platform
|
|
10
|
+
|
|
11
|
+
def self.task_type
|
|
12
|
+
:unity_yoo_asset
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param platform [String] 目标平台 ('ios', 'android', 'web')
|
|
16
|
+
# @param options [Hash] 额外选项
|
|
17
|
+
# - :project_path [String] Unity 项目路径(默认当前目录)
|
|
18
|
+
def initialize(platform, options = {})
|
|
19
|
+
@platform = platform
|
|
20
|
+
@unity_exe_path = nil # 将在 do_work 中自动查找
|
|
21
|
+
|
|
22
|
+
name = case platform
|
|
23
|
+
when 'ios', 'ipa'
|
|
24
|
+
"Unity资源Yoo 打包 iOS"
|
|
25
|
+
when 'android', 'apk'
|
|
26
|
+
"Unity资源Yoo 打包 Android"
|
|
27
|
+
when 'web', 'html'
|
|
28
|
+
"Unity资源Yoo 打包 WebGL"
|
|
29
|
+
else
|
|
30
|
+
"Unity资源Yoo 打包 #{platform.upcase}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# 传递 project_path 给基类
|
|
34
|
+
project_path = options[:project_path] || Dir.pwd
|
|
35
|
+
super(name, project_path: project_path, options: options)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def validate
|
|
39
|
+
super
|
|
40
|
+
|
|
41
|
+
# 验证 Unity 工程是否存在
|
|
42
|
+
unless unity_helper.unity_project?(@unity_root_path)
|
|
43
|
+
raise Informative, "当前目录不是 Unity 工程:#{@unity_root_path}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
protected
|
|
50
|
+
|
|
51
|
+
def do_work
|
|
52
|
+
# 获取 Unity 版本
|
|
53
|
+
project_unity_version = get_unity_version
|
|
54
|
+
|
|
55
|
+
# 查找 Unity 执行路径
|
|
56
|
+
unless @unity_exe_path
|
|
57
|
+
@unity_exe_path = unity_helper.find_unity_path(
|
|
58
|
+
project_unity_version: project_unity_version,
|
|
59
|
+
force_change_version: false
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
if @unity_exe_path.nil? || @unity_exe_path.empty?
|
|
64
|
+
raise Informative, "无法找到 Unity 执行路径"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# 显示 Unity 信息
|
|
68
|
+
puts "项目路径: #{@unity_root_path}"
|
|
69
|
+
puts "Unity 执行路径: #{@unity_exe_path}"
|
|
70
|
+
puts "Unity 版本: #{project_unity_version}"
|
|
71
|
+
puts "目标平台: #{normalize_platform_name(@platform)}\n"
|
|
72
|
+
|
|
73
|
+
# 执行 YooAsset 构建
|
|
74
|
+
result = build_yoo_asset
|
|
75
|
+
|
|
76
|
+
result
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# 重写清理方法,提供更精确的参数
|
|
80
|
+
def cleanup_unity_processes
|
|
81
|
+
Funlog.warning("清理 Unity 进程...")
|
|
82
|
+
begin
|
|
83
|
+
Pindo::Unity::UnityProcHelper.cleanup_unity_processes_after_build(
|
|
84
|
+
unity_exe_full_path: @unity_exe_path,
|
|
85
|
+
project_path: @unity_root_path
|
|
86
|
+
)
|
|
87
|
+
rescue => e
|
|
88
|
+
Funlog.warning("清理 Unity 进程失败: #{e.message}")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
# 构建 YooAsset 资源
|
|
95
|
+
def build_yoo_asset
|
|
96
|
+
platform_name = normalize_platform_name(@platform)
|
|
97
|
+
|
|
98
|
+
# 调用 UnityHelper 的 build_yoo_asset 方法
|
|
99
|
+
result = unity_helper.build_yoo_asset(
|
|
100
|
+
unity_exe_full_path: @unity_exe_path,
|
|
101
|
+
project_path: @unity_root_path,
|
|
102
|
+
platform: platform_name
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# 如果跳过(YooAsset 不存在),输出警告并显示命令
|
|
106
|
+
if result[:skipped]
|
|
107
|
+
Funlog.warning("项目中未检测到 YooAsset,跳过资源构建")
|
|
108
|
+
print_unity_command
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
success: true,
|
|
112
|
+
skipped: true,
|
|
113
|
+
platform: @platform,
|
|
114
|
+
reason: result[:reason]
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# 正常完成
|
|
119
|
+
{
|
|
120
|
+
success: true,
|
|
121
|
+
platform: @platform,
|
|
122
|
+
unity_result: result
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# 打印将要执行的 Unity 命令
|
|
127
|
+
def print_unity_command
|
|
128
|
+
platform_name = normalize_platform_name(@platform)
|
|
129
|
+
|
|
130
|
+
cmd = "#{@unity_exe_path} -batchmode -quit " \
|
|
131
|
+
"-projectPath #{@unity_root_path} " \
|
|
132
|
+
"-executeMethod Yoo.Editor.YooCommandHelper.BatchBuild " \
|
|
133
|
+
"-platform #{platform_name}"
|
|
134
|
+
|
|
135
|
+
puts "\n本应执行的 Unity 命令:"
|
|
136
|
+
puts cmd
|
|
137
|
+
puts ""
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# 标准化平台名称
|
|
141
|
+
def normalize_platform_name(platform)
|
|
142
|
+
case platform
|
|
143
|
+
when 'ios', 'ipa'
|
|
144
|
+
'iOS'
|
|
145
|
+
when 'android', 'apk'
|
|
146
|
+
'Android'
|
|
147
|
+
when 'web', 'html'
|
|
148
|
+
'WebGL'
|
|
149
|
+
else
|
|
150
|
+
platform.to_s
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
require 'pindo/module/task/pindo_task'
|
|
2
|
+
require 'pindo/module/unity/unity_helper'
|
|
3
|
+
require 'pindo/module/unity/unity_proc_helper'
|
|
4
|
+
|
|
5
|
+
module Pindo
|
|
6
|
+
module TaskSystem
|
|
7
|
+
# Unity 任务基类
|
|
8
|
+
# 所有 Unity 相关任务的父类,提供通用的 Unity 操作和配置
|
|
9
|
+
class UnityTask < PindoTask
|
|
10
|
+
attr_reader :project_path, :unity_root_path
|
|
11
|
+
|
|
12
|
+
def initialize(name, project_path:, options: {})
|
|
13
|
+
@project_path = File.expand_path(project_path)
|
|
14
|
+
@unity_root_path = options[:unity_root_path] || @project_path
|
|
15
|
+
|
|
16
|
+
super(name, options)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Unity 任务类型
|
|
20
|
+
def self.task_type
|
|
21
|
+
:unity
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Unity 任务默认重试配置
|
|
25
|
+
def self.default_retry_mode
|
|
26
|
+
RetryMode::DELAYED
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.default_retry_count
|
|
30
|
+
2 # Unity 任务默认重试 2 次
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.default_retry_delay
|
|
34
|
+
15 # Unity 任务默认延迟 15 秒重试
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# 验证 Unity 项目路径
|
|
38
|
+
def validate
|
|
39
|
+
super
|
|
40
|
+
|
|
41
|
+
unless File.directory?(@project_path)
|
|
42
|
+
raise Informative, "Unity 项目路径不存在: #{@project_path}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
unless File.directory?(@unity_root_path)
|
|
46
|
+
raise Informative, "Unity 根目录不存在: #{@unity_root_path}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# 获取 Unity Helper
|
|
53
|
+
def unity_helper
|
|
54
|
+
@unity_helper ||= Pindo::Unity::UnityHelper.share_instance
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# 重试前清理 Unity 进程
|
|
58
|
+
def before_retry
|
|
59
|
+
super
|
|
60
|
+
cleanup_unity_processes
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
protected
|
|
64
|
+
|
|
65
|
+
# 检查 Unity 项目是否有效
|
|
66
|
+
def valid_unity_project?
|
|
67
|
+
# 检查是否存在 Assets 和 ProjectSettings 目录
|
|
68
|
+
assets_path = File.join(@unity_root_path, 'Assets')
|
|
69
|
+
project_settings_path = File.join(@unity_root_path, 'ProjectSettings')
|
|
70
|
+
|
|
71
|
+
File.directory?(assets_path) && File.directory?(project_settings_path)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# 清理 Unity 进程
|
|
75
|
+
def cleanup_unity_processes
|
|
76
|
+
Funlog.warning("清理 Unity 进程...")
|
|
77
|
+
begin
|
|
78
|
+
# 调用 UnityProcHelper 的清理方法
|
|
79
|
+
# 如果子类有 unity_exe_path,可以重写这个方法传入更精确的参数
|
|
80
|
+
Pindo::Unity::UnityProcHelper.cleanup_unity_processes_after_build(
|
|
81
|
+
unity_exe_full_path: nil,
|
|
82
|
+
project_path: @unity_root_path
|
|
83
|
+
)
|
|
84
|
+
rescue => e
|
|
85
|
+
Funlog.warning("清理 Unity 进程失败: #{e.message}")
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# 获取 Unity 版本
|
|
90
|
+
def get_unity_version
|
|
91
|
+
version_file = File.join(@unity_root_path, 'ProjectSettings', 'ProjectVersion.txt')
|
|
92
|
+
return nil unless File.exist?(version_file)
|
|
93
|
+
|
|
94
|
+
content = File.read(version_file)
|
|
95
|
+
match = content.match(/m_EditorVersion:\s*(.+)/)
|
|
96
|
+
match ? match[1].strip : nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# 打印 Unity 项目信息
|
|
100
|
+
def print_unity_info
|
|
101
|
+
puts "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
102
|
+
puts " Unity 项目信息"
|
|
103
|
+
puts "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
104
|
+
puts "项目路径: #{@project_path}"
|
|
105
|
+
puts "Unity 根路径: #{@unity_root_path}"
|
|
106
|
+
|
|
107
|
+
version = get_unity_version
|
|
108
|
+
puts "Unity 版本: #{version || '未知'}"
|
|
109
|
+
puts "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -254,8 +254,16 @@ module Pindo
|
|
|
254
254
|
# 获取任务类型的显示名称
|
|
255
255
|
def get_type_display_name(type)
|
|
256
256
|
case type
|
|
257
|
+
when :git_tag
|
|
258
|
+
"Git仓库打标签"
|
|
259
|
+
when :unity_config
|
|
260
|
+
"Unity编译模式配置"
|
|
261
|
+
when :unity_update
|
|
262
|
+
"Unity工具库更新"
|
|
263
|
+
when :unity_yoo_asset
|
|
264
|
+
"Unity资源Yoo 打包"
|
|
257
265
|
when :unity_export
|
|
258
|
-
"Unity
|
|
266
|
+
"Unity导出"
|
|
259
267
|
when :build
|
|
260
268
|
"编译构建"
|
|
261
269
|
when :upload
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
require 'pindo/base/funlog'
|
|
3
|
+
require 'pindo/config/pindoconfig'
|
|
4
|
+
require 'pindo/module/unity/unity_env_helper'
|
|
5
|
+
require 'pindo/module/unity/unity_proc_helper'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
module Pindo
|
|
9
|
+
module Unity
|
|
10
|
+
# Unity 命令执行助手
|
|
11
|
+
# 负责执行 Unity 命令行操作
|
|
12
|
+
# 所有方法均为类方法,无需实例化
|
|
13
|
+
class UnityCommandHelper
|
|
14
|
+
|
|
15
|
+
# 执行 Unity 命令
|
|
16
|
+
# @param unity_exe_full_path [String] Unity 可执行文件路径
|
|
17
|
+
# @param project_path [String] Unity 项目路径
|
|
18
|
+
# @param method [String] Unity 方法名(默认: GoodUnityBuild.BuildManager.BatchBuild)
|
|
19
|
+
# @param additional_args [Hash] 额外参数
|
|
20
|
+
# @return [Hash] 执行结果 { success:, stdout:, stderr:, exit_status:, unity_version: }
|
|
21
|
+
def self.execute_unity_command(unity_exe_full_path, project_path, method: 'GoodUnityBuild.BuildManager.BatchBuild', additional_args: {})
|
|
22
|
+
if unity_exe_full_path.nil?
|
|
23
|
+
raise Informative, "Unity path not found!"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# 调试级别:通过环境变量或参数控制
|
|
27
|
+
debug_level = ENV['UNITY_BUILD_DEBUG'] || additional_args[:debug_level] || 'normal'
|
|
28
|
+
# debug_level: 'quiet' - 只显示错误
|
|
29
|
+
# 'normal' - 显示错误、警告和关键进度
|
|
30
|
+
# 'verbose' - 显示所有输出
|
|
31
|
+
|
|
32
|
+
# 检查项目路径是否存在
|
|
33
|
+
unless File.directory?(project_path)
|
|
34
|
+
raise Informative, "Unity项目路径不存在: #{project_path}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# 检查项目是否是Unity项目
|
|
38
|
+
project_settings = File.join(project_path, "ProjectSettings")
|
|
39
|
+
unless File.directory?(project_settings)
|
|
40
|
+
raise Informative, "项目路径不是Unity项目: #{project_path}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
cmd_args = [
|
|
44
|
+
unity_exe_full_path,
|
|
45
|
+
"-batchmode",
|
|
46
|
+
"-quit",
|
|
47
|
+
"-projectPath",
|
|
48
|
+
project_path.to_s,
|
|
49
|
+
"-executeMethod",
|
|
50
|
+
method
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# Add any additional arguments
|
|
54
|
+
additional_args.each do |key, value|
|
|
55
|
+
cmd_args << "-#{key}"
|
|
56
|
+
cmd_args << value.to_s if value
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
puts "Unity command: #{cmd_args.join(' ')}"
|
|
60
|
+
puts ""
|
|
61
|
+
|
|
62
|
+
# 使用更智能的进度检测机制
|
|
63
|
+
progress_thread = nil
|
|
64
|
+
start_time = Time.now
|
|
65
|
+
last_output_time = Time.now
|
|
66
|
+
|
|
67
|
+
begin
|
|
68
|
+
# 使用 Open3.popen3 来实时监控输出
|
|
69
|
+
Open3.popen3(*cmd_args) do |stdin, stdout, stderr, wait_thr|
|
|
70
|
+
stdin.close
|
|
71
|
+
|
|
72
|
+
# 启动进度显示线程
|
|
73
|
+
progress_thread = Thread.new do
|
|
74
|
+
dots = 0
|
|
75
|
+
while wait_thr.alive?
|
|
76
|
+
sleep(2)
|
|
77
|
+
dots = (dots + 1) % 4
|
|
78
|
+
elapsed = (Time.now - start_time).to_i
|
|
79
|
+
print "\r\e[33mUnity 构建中#{'.' * dots}#{' ' * (3 - dots)} (已用时: #{elapsed}秒)\e[0m"
|
|
80
|
+
$stdout.flush
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# 实时读取输出
|
|
85
|
+
stdout_buffer = ""
|
|
86
|
+
stderr_buffer = ""
|
|
87
|
+
|
|
88
|
+
# 定义错误关键词模式(优化正则,避免重复)
|
|
89
|
+
error_pattern = /error|exception|failed|Build completed with a result of 'Failed'/i
|
|
90
|
+
warning_pattern = /warning|warn/i
|
|
91
|
+
success_pattern = /Build completed successfully|Exiting batchmode successfully/i
|
|
92
|
+
|
|
93
|
+
# 使用非阻塞方式读取输出
|
|
94
|
+
while wait_thr.alive?
|
|
95
|
+
# 检查是否有输出可读
|
|
96
|
+
ready_streams = IO.select([stdout, stderr], nil, nil, 1)
|
|
97
|
+
|
|
98
|
+
if ready_streams
|
|
99
|
+
ready_streams[0].each do |stream|
|
|
100
|
+
if line = stream.gets
|
|
101
|
+
# 记录输出
|
|
102
|
+
if stream == stdout
|
|
103
|
+
stdout_buffer += line
|
|
104
|
+
else
|
|
105
|
+
stderr_buffer += line
|
|
106
|
+
end
|
|
107
|
+
last_output_time = Time.now
|
|
108
|
+
|
|
109
|
+
# 根据调试级别和内容类型显示输出
|
|
110
|
+
case debug_level
|
|
111
|
+
when 'verbose'
|
|
112
|
+
# 详细模式:显示所有输出
|
|
113
|
+
puts line
|
|
114
|
+
when 'quiet'
|
|
115
|
+
# 安静模式:只显示错误
|
|
116
|
+
if line.match?(error_pattern)
|
|
117
|
+
puts "\e[31m[错误] #{line.strip}\e[0m"
|
|
118
|
+
end
|
|
119
|
+
else # 'normal'
|
|
120
|
+
# 正常模式:显示错误、警告和关键进度
|
|
121
|
+
if line.match?(error_pattern)
|
|
122
|
+
puts "\n\e[31m[错误] #{line.strip}\e[0m"
|
|
123
|
+
elsif line.match?(warning_pattern)
|
|
124
|
+
puts "\n\e[33m[警告] #{line.strip}\e[0m"
|
|
125
|
+
elsif line.match?(success_pattern)
|
|
126
|
+
puts "\n\e[32m[成功] #{line.strip}\e[0m"
|
|
127
|
+
elsif line.match?(/\d+%/) || line.match?(/Building|Compiling|Processing/i)
|
|
128
|
+
# 显示进度相关信息
|
|
129
|
+
print "\r\e[36m[进度] #{line.strip}\e[0m"
|
|
130
|
+
$stdout.flush
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# 读取剩余输出
|
|
140
|
+
stdout_buffer += stdout.read
|
|
141
|
+
stderr_buffer += stderr.read
|
|
142
|
+
|
|
143
|
+
# 停止进度显示线程
|
|
144
|
+
progress_thread.kill if progress_thread
|
|
145
|
+
|
|
146
|
+
# 检查构建是否真的成功
|
|
147
|
+
build_success = wait_thr.value.success?
|
|
148
|
+
|
|
149
|
+
# 检查是否有构建错误的关键词
|
|
150
|
+
if stdout_buffer.match?(/Build completed with a result of 'Failed'|Build completed with a result of 'Cancelled'|BuildPlayerWindow\+BuildMethod\+Invoke|error|Error|ERROR|exception|Exception|EXCEPTION|failed|Failed|FAILED/)
|
|
151
|
+
build_success = false
|
|
152
|
+
# puts "\n\e[31m检测到构建错误信息,构建可能失败\e[0m"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
if build_success
|
|
156
|
+
print "\r\e[32mUnity 构建完成!\e[0m\n"
|
|
157
|
+
# 构建完成后检查并清理可能的 Unity 进程残留
|
|
158
|
+
UnityProcHelper.cleanup_unity_processes_after_build(unity_exe_full_path: unity_exe_full_path, project_path: project_path)
|
|
159
|
+
else
|
|
160
|
+
print "\r\e[31mUnity 构建失败!\e[0m\n"
|
|
161
|
+
puts "\e[31m构建输出:\e[0m"
|
|
162
|
+
puts stdout_buffer if !stdout_buffer.empty?
|
|
163
|
+
puts "\e[31m错误输出:\e[0m"
|
|
164
|
+
puts stderr_buffer if !stderr_buffer.empty?
|
|
165
|
+
# 构建失败时也清理可能的进程残留
|
|
166
|
+
UnityProcHelper.cleanup_unity_processes_after_build(unity_exe_full_path: unity_exe_full_path, project_path: project_path)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# 返回结果
|
|
170
|
+
{
|
|
171
|
+
success: build_success,
|
|
172
|
+
stdout: stdout_buffer,
|
|
173
|
+
stderr: stderr_buffer,
|
|
174
|
+
exit_status: wait_thr.value.exitstatus,
|
|
175
|
+
unity_version: UnityEnvHelper.extract_version_from_path(unity_exe_full_path)
|
|
176
|
+
}
|
|
177
|
+
end
|
|
178
|
+
rescue => e
|
|
179
|
+
# 停止进度显示线程
|
|
180
|
+
progress_thread.kill if progress_thread
|
|
181
|
+
print "\r\e[31mUnity 构建失败!\e[0m\n"
|
|
182
|
+
raise e
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
module Pindo
|
|
2
|
+
module Unity
|
|
3
|
+
# Unity 环境管理助手
|
|
4
|
+
# 负责 Unity 版本查找、路径管理等环境相关功能
|
|
5
|
+
# 所有方法均为类方法,无需实例化
|
|
6
|
+
class UnityEnvHelper
|
|
7
|
+
# macOS Unity 安装路径
|
|
8
|
+
UNITY_MAC_PATHS = [
|
|
9
|
+
"/Applications/Unity/Unity.app/Contents/MacOS/Unity",
|
|
10
|
+
"/Applications/Unity/Hub/Editor/*/Unity.app/Contents/MacOS/Unity",
|
|
11
|
+
"/Applications/Unity/*/Unity.app/Contents/MacOS/Unity"
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
# Windows Unity 安装路径
|
|
15
|
+
UNITY_WINDOWS_PATHS = [
|
|
16
|
+
"C:/Program Files/Unity/Editor/Unity.exe",
|
|
17
|
+
"C:/Program Files/Unity/Hub/Editor/*/Unity.exe",
|
|
18
|
+
"C:/Program Files/Unity/*/Editor/Unity.exe"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
# 查找 Unity 路径
|
|
22
|
+
# @param project_unity_version [String] 项目所需的 Unity 版本
|
|
23
|
+
# @param force_change_version [Boolean] 是否强制使用最新版本
|
|
24
|
+
# @return [String] Unity 可执行文件的完整路径
|
|
25
|
+
def self.find_unity_path(project_unity_version: nil, force_change_version: false)
|
|
26
|
+
if project_unity_version.nil? || project_unity_version.empty?
|
|
27
|
+
raise "Project Unity version is nil or empty"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
unity_major_version = project_unity_version.split('.')[0..1].join('.')
|
|
31
|
+
paths = case RUBY_PLATFORM
|
|
32
|
+
when /darwin/
|
|
33
|
+
UNITY_MAC_PATHS
|
|
34
|
+
when /mswin|mingw|windows/
|
|
35
|
+
UNITY_WINDOWS_PATHS
|
|
36
|
+
else
|
|
37
|
+
raise "Unsupported platform: #{RUBY_PLATFORM}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
unity_versions = []
|
|
41
|
+
|
|
42
|
+
paths.each do |path|
|
|
43
|
+
if path.include?("*")
|
|
44
|
+
Dir.glob(path).each do |expanded_path|
|
|
45
|
+
version = extract_version_from_path(expanded_path)
|
|
46
|
+
if version
|
|
47
|
+
major_version = version.split('.')[0..1].join('.')
|
|
48
|
+
unity_versions << {
|
|
49
|
+
path: expanded_path,
|
|
50
|
+
version: version,
|
|
51
|
+
major_version: major_version
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
elsif File.exist?(path)
|
|
56
|
+
version = extract_version_from_path(path)
|
|
57
|
+
if version
|
|
58
|
+
major_version = version.split('.')[0..1].join('.')
|
|
59
|
+
unity_versions << {
|
|
60
|
+
path: path,
|
|
61
|
+
version: version,
|
|
62
|
+
major_version: major_version
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
if unity_versions.empty?
|
|
69
|
+
puts "调试信息: 搜索的Unity路径:"
|
|
70
|
+
paths.each do |path|
|
|
71
|
+
puts " - #{path}"
|
|
72
|
+
if path.include?("*")
|
|
73
|
+
Dir.glob(path).each do |expanded_path|
|
|
74
|
+
puts " 展开: #{expanded_path}"
|
|
75
|
+
end
|
|
76
|
+
elsif File.exist?(path)
|
|
77
|
+
puts " 存在: #{path}"
|
|
78
|
+
else
|
|
79
|
+
puts " 不存在: #{path}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
raise Informative, "未找到任何Unity版本,请检查Unity是否正确安装"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# 精确匹配项目版本
|
|
86
|
+
select_unity_versions = unity_versions.select { |v| v[:version] == project_unity_version } || []
|
|
87
|
+
if !select_unity_versions.nil? && !select_unity_versions.empty? && select_unity_versions.length >= 1
|
|
88
|
+
return select_unity_versions.first[:path]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# 按主版本匹配
|
|
92
|
+
unity_versions.sort_by! { |v| v[:major_version] }
|
|
93
|
+
select_unity_versions = unity_versions.select { |v| v[:major_version] == unity_major_version } if unity_major_version
|
|
94
|
+
if select_unity_versions.nil? || select_unity_versions.empty?
|
|
95
|
+
if force_change_version
|
|
96
|
+
puts "强制使用最新版本: #{unity_versions.last[:version]}"
|
|
97
|
+
return unity_versions.last[:path]
|
|
98
|
+
else
|
|
99
|
+
puts "调试信息: 项目Unity版本: #{project_unity_version}"
|
|
100
|
+
puts "调试信息: 可用的Unity版本:"
|
|
101
|
+
unity_versions.each do |v|
|
|
102
|
+
puts " - #{v[:version]} (#{v[:major_version]})"
|
|
103
|
+
end
|
|
104
|
+
raise Informative, "未找到匹配的Unity版本 #{project_unity_version},可用的版本: #{unity_versions.map { |v| v[:version] }.join(', ')}"
|
|
105
|
+
end
|
|
106
|
+
else
|
|
107
|
+
return select_unity_versions.first[:path]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# 从路径中提取 Unity 版本号
|
|
112
|
+
# @param path [String] Unity 可执行文件路径
|
|
113
|
+
# @return [String, nil] 版本号或 nil
|
|
114
|
+
def self.extract_version_from_path(path)
|
|
115
|
+
# macOS路径格式: /Applications/Unity/Hub/Editor/2021.3.45f1/Unity.app/Contents/MacOS/Unity
|
|
116
|
+
# macOS路径格式(变体): /Applications/Unity/Hub/Editor/2021.3.45f1c1/Unity.app/Contents/MacOS/Unity
|
|
117
|
+
# macOS路径格式(旧版): /Applications/Unity/Unity.app/Contents/MacOS/Unity
|
|
118
|
+
# Windows路径格式: C:/Program Files/Unity/Hub/Editor/2021.3.45f1/Editor/Unity.exe
|
|
119
|
+
# Windows路径格式(变体): C:/Program Files/Unity/Hub/Editor/2021.3.45f1c1/Editor/Unity.exe
|
|
120
|
+
# Windows路径格式(旧版): C:/Program Files/Unity/Editor/Unity.exe
|
|
121
|
+
|
|
122
|
+
# 尝试匹配 macOS Hub 路径格式
|
|
123
|
+
if match = path.match(/Editor\/([\d.]+[a-zA-Z]\d+(?:c\d+)?)\//)
|
|
124
|
+
return match[1]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# 尝试匹配 Windows Hub 路径格式
|
|
128
|
+
if match = path.match(/([\d.]+[a-zA-Z]\d+(?:c\d+)?)\/Editor\//)
|
|
129
|
+
return match[1]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# 尝试匹配 macOS 旧版路径格式 (从Info.plist提取版本)
|
|
133
|
+
if match = path.match(/Unity\.app\/Contents\/MacOS\/Unity$/)
|
|
134
|
+
info_plist_path = File.join(File.dirname(File.dirname(path)), "Info.plist")
|
|
135
|
+
if File.exist?(info_plist_path)
|
|
136
|
+
begin
|
|
137
|
+
content = File.read(info_plist_path)
|
|
138
|
+
if content =~ /<key>CFBundleVersion<\/key>\s*<string>([^<]+)<\/string>/
|
|
139
|
+
return $1.strip
|
|
140
|
+
elsif content =~ /<key>CFBundleShortVersionString<\/key>\s*<string>Unity version ([^<]+)<\/string>/
|
|
141
|
+
return $1.strip
|
|
142
|
+
end
|
|
143
|
+
rescue => e
|
|
144
|
+
puts "警告: 无法读取Info.plist文件: #{e.message}"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# 尝试匹配 Windows 旧版路径格式
|
|
150
|
+
if match = path.match(/Unity\.exe$/)
|
|
151
|
+
# 对于旧版Unity,尝试从父目录获取版本信息
|
|
152
|
+
parent_dir = File.dirname(path)
|
|
153
|
+
if File.basename(parent_dir) =~ /^([\d.]+[a-zA-Z]\d+(?:c\d+)?)$/
|
|
154
|
+
return $1
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# 比较版本号 (语义化版本比较)
|
|
162
|
+
# @param v1 [String] 版本1
|
|
163
|
+
# @param v2 [String] 版本2
|
|
164
|
+
# @return [Boolean] true 如果 v1 < v2, false 否则
|
|
165
|
+
def self.version_less_than?(v1, v2)
|
|
166
|
+
return false if v1.nil? || v2.nil?
|
|
167
|
+
|
|
168
|
+
Gem::Version.new(v1) < Gem::Version.new(v2)
|
|
169
|
+
rescue ArgumentError
|
|
170
|
+
# 版本格式无效时的降级处理
|
|
171
|
+
v1.to_s < v2.to_s
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# 获取项目的 Unity 版本
|
|
175
|
+
# @param project_path [String] Unity 项目路径
|
|
176
|
+
# @return [String] Unity 版本号
|
|
177
|
+
def self.get_unity_version(project_path)
|
|
178
|
+
version_path = File.join(project_path, "ProjectSettings", "ProjectVersion.txt")
|
|
179
|
+
if File.exist?(version_path)
|
|
180
|
+
content = File.read(version_path)
|
|
181
|
+
if content =~ /m_EditorVersion: (.*)/
|
|
182
|
+
version = $1.strip
|
|
183
|
+
version
|
|
184
|
+
else
|
|
185
|
+
raise "Could not parse Unity version from #{version_path}"
|
|
186
|
+
end
|
|
187
|
+
else
|
|
188
|
+
raise "Project version file not found at #{version_path}"
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# 检查是否为有效的 Unity 项目
|
|
193
|
+
# @param project_path [String] 项目路径
|
|
194
|
+
# @return [Boolean] true 如果是有效的 Unity 项目
|
|
195
|
+
def self.unity_project?(project_path)
|
|
196
|
+
# 检查关键Unity工程文件和目录是否存在
|
|
197
|
+
project_settings_path = File.join(project_path, "ProjectSettings")
|
|
198
|
+
assets_path = File.join(project_path, "Assets")
|
|
199
|
+
packages_path = File.join(project_path, "Packages")
|
|
200
|
+
|
|
201
|
+
File.directory?(project_settings_path) &&
|
|
202
|
+
File.directory?(assets_path) &&
|
|
203
|
+
File.directory?(packages_path) &&
|
|
204
|
+
File.exist?(File.join(project_settings_path, "ProjectSettings.asset"))
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|