pindo 5.2.4 → 5.3.7
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/aeshelper.rb +23 -2
- data/lib/pindo/base/pindocontext.rb +259 -0
- data/lib/pindo/client/pgyer_feishu_oauth_cli.rb +343 -80
- data/lib/pindo/client/pgyerclient.rb +30 -20
- data/lib/pindo/command/android/autobuild.rb +52 -22
- data/lib/pindo/command/android/build.rb +27 -16
- data/lib/pindo/command/android/debug.rb +25 -15
- data/lib/pindo/command/dev/debug.rb +2 -51
- data/lib/pindo/command/dev/feishu.rb +19 -2
- data/lib/pindo/command/ios/adhoc.rb +2 -1
- data/lib/pindo/command/ios/autobuild.rb +35 -8
- data/lib/pindo/command/ios/debug.rb +2 -132
- data/lib/pindo/command/lib/lint.rb +24 -1
- data/lib/pindo/command/setup.rb +24 -4
- data/lib/pindo/command/unity/apk.rb +15 -0
- data/lib/pindo/command/unity/ipa.rb +16 -0
- data/lib/pindo/command.rb +13 -2
- data/lib/pindo/module/android/android_build_config_helper.rb +425 -0
- data/lib/pindo/module/android/apk_helper.rb +23 -25
- data/lib/pindo/module/android/base_helper.rb +572 -0
- data/lib/pindo/module/android/build_helper.rb +8 -318
- data/lib/pindo/module/android/gp_compliance_helper.rb +668 -0
- data/lib/pindo/module/android/gradle_helper.rb +746 -3
- data/lib/pindo/module/appselect.rb +18 -5
- data/lib/pindo/module/build/buildhelper.rb +120 -29
- data/lib/pindo/module/build/unityhelper.rb +675 -18
- data/lib/pindo/module/build/versionhelper.rb +146 -0
- data/lib/pindo/module/cert/certhelper.rb +33 -2
- data/lib/pindo/module/cert/xcodecerthelper.rb +3 -1
- data/lib/pindo/module/pgyer/pgyerhelper.rb +114 -31
- data/lib/pindo/module/xcode/xcodebuildconfig.rb +232 -0
- data/lib/pindo/module/xcode/xcodebuildhelper.rb +0 -1
- data/lib/pindo/version.rb +356 -86
- data/lib/pindo.rb +72 -3
- metadata +5 -1
@@ -2,6 +2,9 @@ require 'open3'
|
|
2
2
|
require 'json'
|
3
3
|
require 'singleton'
|
4
4
|
|
5
|
+
# 定义Informative异常类
|
6
|
+
class Informative < StandardError; end
|
7
|
+
|
5
8
|
module Pindo
|
6
9
|
module Client
|
7
10
|
class UnityHelper
|
@@ -9,12 +12,14 @@ module Pindo
|
|
9
12
|
|
10
13
|
UNITY_MAC_PATHS = [
|
11
14
|
"/Applications/Unity/Unity.app/Contents/MacOS/Unity",
|
12
|
-
"/Applications/Unity/Hub/Editor/*/Unity.app/Contents/MacOS/Unity"
|
15
|
+
"/Applications/Unity/Hub/Editor/*/Unity.app/Contents/MacOS/Unity",
|
16
|
+
"/Applications/Unity/*/Unity.app/Contents/MacOS/Unity"
|
13
17
|
]
|
14
18
|
|
15
19
|
UNITY_WINDOWS_PATHS = [
|
16
20
|
"C:/Program Files/Unity/Editor/Unity.exe",
|
17
|
-
"C:/Program Files/Unity/Hub/Editor/*/Unity.exe"
|
21
|
+
"C:/Program Files/Unity/Hub/Editor/*/Unity.exe",
|
22
|
+
"C:/Program Files/Unity/*/Editor/Unity.exe"
|
18
23
|
]
|
19
24
|
|
20
25
|
class << self
|
@@ -56,8 +61,8 @@ module Pindo
|
|
56
61
|
end
|
57
62
|
elsif File.exist?(path)
|
58
63
|
version = extract_version_from_path(path)
|
59
|
-
major_version = version.split('.')[0..1].join('.')
|
60
64
|
if version
|
65
|
+
major_version = version.split('.')[0..1].join('.')
|
61
66
|
unity_versions << {
|
62
67
|
path: path,
|
63
68
|
version: version,
|
@@ -68,7 +73,20 @@ module Pindo
|
|
68
73
|
end
|
69
74
|
|
70
75
|
if unity_versions.empty?
|
71
|
-
|
76
|
+
puts "调试信息: 搜索的Unity路径:"
|
77
|
+
paths.each do |path|
|
78
|
+
puts " - #{path}"
|
79
|
+
if path.include?("*")
|
80
|
+
Dir.glob(path).each do |expanded_path|
|
81
|
+
puts " 展开: #{expanded_path}"
|
82
|
+
end
|
83
|
+
elsif File.exist?(path)
|
84
|
+
puts " 存在: #{path}"
|
85
|
+
else
|
86
|
+
puts " 不存在: #{path}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
raise Informative, "未找到任何Unity版本,请检查Unity是否正确安装"
|
72
90
|
end
|
73
91
|
|
74
92
|
|
@@ -81,10 +99,15 @@ module Pindo
|
|
81
99
|
select_unity_versions = unity_versions.select { |v| v[:major_version] == unity_major_version } if unity_major_version
|
82
100
|
if select_unity_versions.nil? || select_unity_versions.empty?
|
83
101
|
if force_change_version
|
102
|
+
puts "强制使用最新版本: #{unity_versions.last[:version]}"
|
84
103
|
return unity_versions.last[:path]
|
85
104
|
else
|
86
|
-
|
87
|
-
|
105
|
+
puts "调试信息: 项目Unity版本: #{project_unity_version}"
|
106
|
+
puts "调试信息: 可用的Unity版本:"
|
107
|
+
unity_versions.each do |v|
|
108
|
+
puts " - #{v[:version]} (#{v[:major_version]})"
|
109
|
+
end
|
110
|
+
raise Informative, "未找到匹配的Unity版本 #{project_unity_version},可用的版本: #{unity_versions.map { |v| v[:version] }.join(', ')}"
|
88
111
|
end
|
89
112
|
else
|
90
113
|
return select_unity_versions.first[:path]
|
@@ -97,19 +120,47 @@ module Pindo
|
|
97
120
|
def extract_version_from_path(path)
|
98
121
|
# macOS路径格式: /Applications/Unity/Hub/Editor/2021.3.45f1/Unity.app/Contents/MacOS/Unity
|
99
122
|
# macOS路径格式(变体): /Applications/Unity/Hub/Editor/2021.3.45f1c1/Unity.app/Contents/MacOS/Unity
|
123
|
+
# macOS路径格式(旧版): /Applications/Unity/Unity.app/Contents/MacOS/Unity
|
100
124
|
# Windows路径格式: C:/Program Files/Unity/Hub/Editor/2021.3.45f1/Editor/Unity.exe
|
101
125
|
# Windows路径格式(变体): C:/Program Files/Unity/Hub/Editor/2021.3.45f1c1/Editor/Unity.exe
|
126
|
+
# Windows路径格式(旧版): C:/Program Files/Unity/Editor/Unity.exe
|
102
127
|
|
103
|
-
# 尝试匹配 macOS 路径格式
|
128
|
+
# 尝试匹配 macOS Hub 路径格式
|
104
129
|
if match = path.match(/Editor\/([\d.]+[a-zA-Z]\d+(?:c\d+)?)\//)
|
105
130
|
return match[1]
|
106
131
|
end
|
107
132
|
|
108
|
-
# 尝试匹配 Windows 路径格式
|
133
|
+
# 尝试匹配 Windows Hub 路径格式
|
109
134
|
if match = path.match(/([\d.]+[a-zA-Z]\d+(?:c\d+)?)\/Editor\//)
|
110
135
|
return match[1]
|
111
136
|
end
|
112
137
|
|
138
|
+
# 尝试匹配 macOS 旧版路径格式 (从Info.plist提取版本)
|
139
|
+
if match = path.match(/Unity\.app\/Contents\/MacOS\/Unity$/)
|
140
|
+
info_plist_path = File.join(File.dirname(File.dirname(path)), "Info.plist")
|
141
|
+
if File.exist?(info_plist_path)
|
142
|
+
begin
|
143
|
+
content = File.read(info_plist_path)
|
144
|
+
if content =~ /<key>CFBundleVersion<\/key>\s*<string>([^<]+)<\/string>/
|
145
|
+
return $1.strip
|
146
|
+
elsif content =~ /<key>CFBundleShortVersionString<\/key>\s*<string>Unity version ([^<]+)<\/string>/
|
147
|
+
return $1.strip
|
148
|
+
end
|
149
|
+
rescue => e
|
150
|
+
puts "警告: 无法读取Info.plist文件: #{e.message}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# 尝试匹配 Windows 旧版路径格式
|
156
|
+
if match = path.match(/Unity\.exe$/)
|
157
|
+
# 对于旧版Unity,尝试从父目录获取版本信息
|
158
|
+
parent_dir = File.dirname(path)
|
159
|
+
if File.basename(parent_dir) =~ /^([\d.]+[a-zA-Z]\d+(?:c\d+)?)$/
|
160
|
+
return $1
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
113
164
|
nil
|
114
165
|
end
|
115
166
|
|
@@ -120,6 +171,31 @@ module Pindo
|
|
120
171
|
raise Informative, "Unity path not found!"
|
121
172
|
end
|
122
173
|
|
174
|
+
# 调试级别:通过环境变量或参数控制
|
175
|
+
debug_level = ENV['UNITY_BUILD_DEBUG'] || additional_args[:debug_level] || 'normal'
|
176
|
+
# debug_level: 'quiet' - 只显示错误
|
177
|
+
# 'normal' - 显示错误、警告和关键进度
|
178
|
+
# 'verbose' - 显示所有输出
|
179
|
+
|
180
|
+
# 检查项目路径是否存在
|
181
|
+
unless File.directory?(project_path)
|
182
|
+
raise Informative, "Unity项目路径不存在: #{project_path}"
|
183
|
+
end
|
184
|
+
|
185
|
+
# 检查项目是否是Unity项目
|
186
|
+
project_settings = File.join(project_path, "ProjectSettings")
|
187
|
+
unless File.directory?(project_settings)
|
188
|
+
raise Informative, "项目路径不是Unity项目: #{project_path}"
|
189
|
+
end
|
190
|
+
|
191
|
+
# 如果是 Android 平台,检查 Java 版本
|
192
|
+
platform = additional_args[:platform]
|
193
|
+
if platform == 'Android'
|
194
|
+
unless ensure_java_version_for_android
|
195
|
+
raise Informative, "Java 版本不符合要求,无法继续构建 Android 项目"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
123
199
|
cmd_args = [
|
124
200
|
unity_exe_full_path,
|
125
201
|
"-batchmode",
|
@@ -137,39 +213,607 @@ module Pindo
|
|
137
213
|
end
|
138
214
|
|
139
215
|
puts "Unity command: #{cmd_args.join(' ')}"
|
140
|
-
|
216
|
+
puts ""
|
217
|
+
puts "\e[33m正在执行 Unity 构建,这可能需要几分钟时间,请耐心等待...\e[0m"
|
218
|
+
|
219
|
+
# 根据平台显示不同的构建步骤
|
220
|
+
platform = additional_args[:platform]
|
221
|
+
if platform == 'Android'
|
222
|
+
puts "\e[36m提示: Unity 正在后台处理项目,包括以下步骤:\e[0m"
|
223
|
+
puts "\e[36m • 编译 C# 脚本\e[0m"
|
224
|
+
puts "\e[36m • 处理资源文件\e[0m"
|
225
|
+
puts "\e[36m • 生成 Android 项目\e[0m"
|
226
|
+
puts "\e[36m • 构建 AAB 文件\e[0m"
|
227
|
+
elsif platform == 'iOS'
|
228
|
+
puts "\e[36m提示: Unity 正在后台处理项目,包括以下步骤:\e[0m"
|
229
|
+
puts "\e[36m • 编译 C# 脚本\e[0m"
|
230
|
+
puts "\e[36m • 处理资源文件\e[0m"
|
231
|
+
puts "\e[36m • 生成 iOS 项目\e[0m"
|
232
|
+
puts "\e[36m • 构建 Xcode 项目\e[0m"
|
233
|
+
elsif platform == 'WebGL'
|
234
|
+
puts "\e[36m提示: Unity 正在后台处理项目,包括以下步骤:\e[0m"
|
235
|
+
puts "\e[36m • 编译 C# 脚本\e[0m"
|
236
|
+
puts "\e[36m • 处理资源文件\e[0m"
|
237
|
+
puts "\e[36m • 生成 WebGL 项目\e[0m"
|
238
|
+
puts "\e[36m • 构建 Web 资源\e[0m"
|
239
|
+
else
|
240
|
+
puts "\e[36m提示: Unity 正在后台处理项目,包括以下步骤:\e[0m"
|
241
|
+
puts "\e[36m • 编译 C# 脚本\e[0m"
|
242
|
+
puts "\e[36m • 处理资源文件\e[0m"
|
243
|
+
puts "\e[36m • 生成项目文件\e[0m"
|
244
|
+
end
|
245
|
+
|
246
|
+
puts "\e[36m如果长时间无响应(Unity 进程可能已卡死),请完全终止当前终端后重试\e[0m"
|
247
|
+
puts "\e[36m或者,打开 Unity Editor 通过构建工具执行一次项目导出后,关闭 Unity Editor 再执行此命令\e[0m"
|
248
|
+
puts ""
|
249
|
+
|
250
|
+
# 使用更智能的进度检测机制
|
251
|
+
progress_thread = nil
|
252
|
+
start_time = Time.now
|
253
|
+
last_output_time = Time.now
|
254
|
+
|
255
|
+
begin
|
256
|
+
# 使用 Open3.popen3 来实时监控输出
|
257
|
+
Open3.popen3(*cmd_args) do |stdin, stdout, stderr, wait_thr|
|
258
|
+
stdin.close
|
259
|
+
|
260
|
+
# 启动进度显示线程
|
261
|
+
progress_thread = Thread.new do
|
262
|
+
dots = 0
|
263
|
+
while wait_thr.alive?
|
264
|
+
sleep(2)
|
265
|
+
dots = (dots + 1) % 4
|
266
|
+
elapsed = (Time.now - start_time).to_i
|
267
|
+
print "\r\e[33mUnity 构建中#{'.' * dots}#{' ' * (3 - dots)} (已用时: #{elapsed}秒)\e[0m"
|
268
|
+
$stdout.flush
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# 实时读取输出
|
273
|
+
stdout_buffer = ""
|
274
|
+
stderr_buffer = ""
|
275
|
+
|
276
|
+
# 定义错误关键词模式(优化正则,避免重复)
|
277
|
+
error_pattern = /error|exception|failed|Build completed with a result of 'Failed'/i
|
278
|
+
warning_pattern = /warning|warn/i
|
279
|
+
success_pattern = /Build completed successfully|Exiting batchmode successfully/i
|
280
|
+
|
281
|
+
# 使用非阻塞方式读取输出
|
282
|
+
while wait_thr.alive?
|
283
|
+
# 检查是否有输出可读
|
284
|
+
ready_streams = IO.select([stdout, stderr], nil, nil, 1)
|
285
|
+
|
286
|
+
if ready_streams
|
287
|
+
ready_streams[0].each do |stream|
|
288
|
+
if line = stream.gets
|
289
|
+
# 记录输出
|
290
|
+
if stream == stdout
|
291
|
+
stdout_buffer += line
|
292
|
+
else
|
293
|
+
stderr_buffer += line
|
294
|
+
end
|
295
|
+
last_output_time = Time.now
|
296
|
+
|
297
|
+
# 根据调试级别和内容类型显示输出
|
298
|
+
case debug_level
|
299
|
+
when 'verbose'
|
300
|
+
# 详细模式:显示所有输出
|
301
|
+
puts line
|
302
|
+
when 'quiet'
|
303
|
+
# 安静模式:只显示错误
|
304
|
+
if line.match?(error_pattern)
|
305
|
+
puts "\e[31m[错误] #{line.strip}\e[0m"
|
306
|
+
end
|
307
|
+
else # 'normal'
|
308
|
+
# 正常模式:显示错误、警告和关键进度
|
309
|
+
if line.match?(error_pattern)
|
310
|
+
puts "\n\e[31m[错误] #{line.strip}\e[0m"
|
311
|
+
elsif line.match?(warning_pattern)
|
312
|
+
puts "\n\e[33m[警告] #{line.strip}\e[0m"
|
313
|
+
elsif line.match?(success_pattern)
|
314
|
+
puts "\n\e[32m[成功] #{line.strip}\e[0m"
|
315
|
+
elsif line.match?(/\d+%/) || line.match?(/Building|Compiling|Processing/i)
|
316
|
+
# 显示进度相关信息
|
317
|
+
print "\r\e[36m[进度] #{line.strip}\e[0m"
|
318
|
+
$stdout.flush
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
141
324
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
325
|
+
# 检查是否长时间无输出(可能卡死)
|
326
|
+
if Time.now - last_output_time > 300 # 5分钟无输出
|
327
|
+
puts "\n\e[33m⚠️ Unity 构建已超过5分钟无输出,可能遇到问题\e[0m"
|
328
|
+
puts "\e[33m建议:\e[0m"
|
329
|
+
puts "\e[33m 1. 检查 Unity Editor 是否有弹窗需要确认\e[0m"
|
330
|
+
puts "\e[33m 2. 查看 Unity 日志文件\e[0m"
|
331
|
+
puts "\e[33m 3. 如确认卡死,可按 Ctrl+C 终止构建\e[0m"
|
332
|
+
last_output_time = Time.now # 重置计时器
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# 读取剩余输出
|
337
|
+
stdout_buffer += stdout.read
|
338
|
+
stderr_buffer += stderr.read
|
339
|
+
|
340
|
+
# 停止进度显示线程
|
341
|
+
progress_thread.kill if progress_thread
|
342
|
+
|
343
|
+
# 检查构建是否真的成功
|
344
|
+
build_success = wait_thr.value.success?
|
345
|
+
|
346
|
+
# 检查是否有构建错误的关键词
|
347
|
+
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/)
|
348
|
+
build_success = false
|
349
|
+
puts "\n\e[31m检测到构建错误信息,构建可能失败\e[0m"
|
350
|
+
end
|
351
|
+
|
352
|
+
if build_success
|
353
|
+
print "\r\e[32mUnity 构建完成!\e[0m\n"
|
354
|
+
# 构建完成后检查并清理可能的 Unity 进程残留
|
355
|
+
cleanup_unity_processes_after_build(unity_exe_full_path: unity_exe_full_path, project_path: project_path)
|
356
|
+
else
|
357
|
+
print "\r\e[31mUnity 构建失败!\e[0m\n"
|
358
|
+
puts "\e[31m构建输出:\e[0m"
|
359
|
+
puts stdout_buffer if !stdout_buffer.empty?
|
360
|
+
puts "\e[31m错误输出:\e[0m"
|
361
|
+
puts stderr_buffer if !stderr_buffer.empty?
|
362
|
+
# 构建失败时也清理可能的进程残留
|
363
|
+
cleanup_unity_processes_after_build(unity_exe_full_path: unity_exe_full_path, project_path: project_path)
|
364
|
+
end
|
365
|
+
|
366
|
+
# 返回结果
|
367
|
+
{
|
368
|
+
success: build_success,
|
369
|
+
stdout: stdout_buffer,
|
370
|
+
stderr: stderr_buffer,
|
371
|
+
exit_status: wait_thr.value.exitstatus,
|
372
|
+
unity_version: extract_version_from_path(unity_exe_full_path)
|
373
|
+
}
|
374
|
+
end
|
375
|
+
rescue => e
|
376
|
+
# 停止进度显示线程
|
377
|
+
progress_thread.kill if progress_thread
|
378
|
+
print "\r\e[31mUnity 构建失败!\e[0m\n"
|
379
|
+
raise e
|
380
|
+
end
|
148
381
|
end
|
149
382
|
|
150
383
|
|
151
384
|
def build_project(unity_exe_full_path:nil, project_path:nil, platform: nil, isLibrary: false)
|
152
385
|
|
386
|
+
# 检查是否有Unity进程在运行,传入Unity路径和项目路径以精确匹配
|
387
|
+
check_unity_processes(unity_exe_full_path: unity_exe_full_path, project_path: project_path)
|
388
|
+
|
153
389
|
additional_args = {}
|
154
390
|
additional_args[:platform] = platform if platform
|
155
391
|
additional_args[:buildtype] = 'library' if isLibrary
|
392
|
+
|
393
|
+
# 首先尝试使用GoodUnityBuild.BuildManager.BatchBuild
|
156
394
|
result = execute_unity_command(unity_exe_full_path, project_path, additional_args)
|
157
395
|
|
158
396
|
if result[:success]
|
159
397
|
puts "Unity build completed successfully"
|
160
398
|
puts "Using Unity version: #{result[:unity_version]}"
|
161
|
-
|
399
|
+
if !result[:stdout].empty?
|
400
|
+
puts "Unity输出:"
|
401
|
+
puts result[:stdout]
|
402
|
+
end
|
162
403
|
else
|
163
404
|
puts "Unity build failed"
|
164
405
|
puts "Unity version: #{result[:unity_version]}"
|
165
|
-
puts result[:
|
166
|
-
|
406
|
+
puts "Exit status: #{result[:exit_status]}"
|
407
|
+
|
408
|
+
if !result[:stdout].empty?
|
409
|
+
puts "Unity标准输出:"
|
410
|
+
puts result[:stdout]
|
411
|
+
end
|
412
|
+
|
413
|
+
if !result[:stderr].empty?
|
414
|
+
puts "Unity错误输出:"
|
415
|
+
puts result[:stderr]
|
416
|
+
else
|
417
|
+
puts "Unity没有输出错误信息"
|
418
|
+
end
|
419
|
+
|
420
|
+
# 提供更详细的错误信息
|
421
|
+
error_msg = "Unity构建失败 (退出码: #{result[:exit_status]})"
|
422
|
+
|
423
|
+
# 检查是否是Unity实例冲突错误
|
424
|
+
if !result[:stdout].empty? && result[:stdout].include?("Multiple Unity instances cannot open the same project")
|
425
|
+
error_msg = "Unity实例冲突错误:\n"
|
426
|
+
error_msg += "检测到另一个Unity实例正在运行并打开了同一个项目。\n"
|
427
|
+
error_msg += "请关闭所有Unity实例后重试。\n"
|
428
|
+
error_msg += "可以使用以下命令检查Unity进程:\n"
|
429
|
+
error_msg += " ps aux | grep -i unity\n"
|
430
|
+
error_msg += "然后使用以下命令关闭Unity进程:\n"
|
431
|
+
error_msg += " kill <进程ID>"
|
432
|
+
# 检查是否是Android SDK下载问题
|
433
|
+
elsif !result[:stdout].empty? && (result[:stdout].include?("Failed to download package") || result[:stdout].include?("Install Android SDK Platform"))
|
434
|
+
error_msg = "Android SDK下载问题:\n"
|
435
|
+
error_msg += "Unity在下载Android SDK组件时遇到网络问题。\n"
|
436
|
+
error_msg += "解决方案:\n"
|
437
|
+
error_msg += "1. 检查网络连接\n"
|
438
|
+
error_msg += "2. 使用VPN或代理\n"
|
439
|
+
error_msg += "3. 在Unity Editor中手动配置Android SDK路径\n"
|
440
|
+
error_msg += "4. 清理Unity缓存: rm -rf Library/BuildCache\n"
|
441
|
+
error_msg += "5. 重新运行构建命令"
|
442
|
+
elsif !result[:stderr].empty?
|
443
|
+
error_msg += "\n错误详情: #{result[:stderr].lines.first.strip}"
|
444
|
+
elsif !result[:stdout].empty?
|
445
|
+
error_msg += "\nUnity输出: #{result[:stdout].lines.first.strip}"
|
446
|
+
end
|
447
|
+
|
448
|
+
raise error_msg
|
167
449
|
end
|
168
450
|
|
169
451
|
result
|
170
452
|
end
|
171
453
|
|
172
454
|
|
455
|
+
|
456
|
+
def check_unity_processes(unity_exe_full_path: nil, project_path: nil)
|
457
|
+
# 如果没有提供路径参数,直接跳过Unity进程检查
|
458
|
+
if unity_exe_full_path.nil? && project_path.nil?
|
459
|
+
return
|
460
|
+
end
|
461
|
+
|
462
|
+
# 检查是否有Unity进程在运行
|
463
|
+
unity_processes = `ps aux | grep -i unity | grep -v grep`.strip
|
464
|
+
|
465
|
+
if !unity_processes.empty?
|
466
|
+
# 解析Unity进程信息,传入Unity路径和项目路径进行精确匹配
|
467
|
+
unity_pids = parse_unity_processes(unity_processes, unity_exe_full_path: unity_exe_full_path, project_path: project_path)
|
468
|
+
|
469
|
+
# 过滤掉僵尸进程和无效进程
|
470
|
+
unity_pids = filter_valid_unity_processes(unity_pids)
|
471
|
+
|
472
|
+
if unity_pids.any?
|
473
|
+
puts "⚠️ 检测到与当前项目相关的 Unity 进程正在运行:"
|
474
|
+
# 只显示真正的Unity进程,不显示 pindo 相关进程
|
475
|
+
unity_pids.each_with_index do |process_info, index|
|
476
|
+
puts " #{index + 1}. PID: #{process_info[:pid]}, 命令: #{process_info[:command]}"
|
477
|
+
end
|
478
|
+
puts ""
|
479
|
+
|
480
|
+
# 询问用户是否要自动关闭Unity进程
|
481
|
+
loop do
|
482
|
+
puts "批处理模式需要关闭这些 Unity 进程以避免冲突"
|
483
|
+
puts " [y] 是, 自动关闭所有 Unity 进程"
|
484
|
+
puts " [s] 跳过, 我已经手动关闭 Unity 进程,继续构建(可能导致冲突)"
|
485
|
+
puts " [e] 退出编译"
|
486
|
+
print "请输入选择 (y/s/e): "
|
487
|
+
|
488
|
+
input = STDIN.gets
|
489
|
+
choice = input ? input.chomp.strip.downcase : ""
|
490
|
+
|
491
|
+
case choice
|
492
|
+
when 'y', 'yes', '1'
|
493
|
+
puts "\n正在关闭 Unity 相关进程..."
|
494
|
+
success_count = 0
|
495
|
+
unity_pids.each do |process_info|
|
496
|
+
if kill_unity_process(process_info[:pid])
|
497
|
+
puts "✅ 成功关闭进程 #{process_info[:pid]}"
|
498
|
+
success_count += 1
|
499
|
+
else
|
500
|
+
puts "❌ 关闭进程 #{process_info[:pid]} 失败"
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
if success_count > 0
|
505
|
+
puts "\n✅ 已关闭 #{success_count} 个 Unity 相关进程"
|
506
|
+
puts "等待3秒后继续编译..."
|
507
|
+
sleep(3)
|
508
|
+
else
|
509
|
+
puts "\n❌ 无法关闭 Unity 相关进程,请手动关闭后重试"
|
510
|
+
raise "Unity进程冲突:无法自动关闭 Unity 相关进程,请手动关闭后重试"
|
511
|
+
end
|
512
|
+
break # 退出循环
|
513
|
+
|
514
|
+
when 's', 'skip', '2'
|
515
|
+
puts "\n✅ 跳过检查,继续编译..."
|
516
|
+
puts "假设Unity Editor已经手动关闭,如果仍在运行可能导致编译失败"
|
517
|
+
sleep(1)
|
518
|
+
break # 退出循环
|
519
|
+
|
520
|
+
when 'e', 'exit', '3'
|
521
|
+
puts "\n⚠️ 用户选择退出编译"
|
522
|
+
puts "退出Unity编译流程"
|
523
|
+
exit 0
|
524
|
+
|
525
|
+
else
|
526
|
+
puts "\n⚠️ 无效选择: '#{choice}'"
|
527
|
+
puts "请输入 y (是), s (跳过), 或 e (退出)\n\n"
|
528
|
+
# 继续循环,让用户重新输入
|
529
|
+
end
|
530
|
+
end
|
531
|
+
else
|
532
|
+
# 没有检测到与当前项目相关的Unity进程,静默继续
|
533
|
+
end
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
# 解析Unity进程信息
|
538
|
+
def parse_unity_processes(processes_output, unity_exe_full_path: nil, project_path: nil)
|
539
|
+
processes = []
|
540
|
+
current_pid = Process.pid.to_s
|
541
|
+
|
542
|
+
processes_output.lines.each do |line|
|
543
|
+
# 解析 ps aux 输出格式
|
544
|
+
parts = line.strip.split(/\s+/)
|
545
|
+
if parts.length >= 11
|
546
|
+
pid = parts[1]
|
547
|
+
command = parts[10..-1].join(' ')
|
548
|
+
|
549
|
+
# 过滤掉grep进程本身、pindo进程和当前进程
|
550
|
+
unless command.include?('grep') || command.include?('pindo') || pid == current_pid
|
551
|
+
# 精确匹配Unity进程
|
552
|
+
is_relevant_unity_process = false
|
553
|
+
match_reasons = []
|
554
|
+
|
555
|
+
# 1. 必须是Unity Editor主进程(排除VS Code等其他进程)
|
556
|
+
is_unity_editor = command.match?(/Unity\.app.*\/Unity/i) || command.match?(/Unity\.exe$/i)
|
557
|
+
|
558
|
+
if is_unity_editor
|
559
|
+
# 2. 检查是否是打开了指定项目的Unity进程
|
560
|
+
# Unity 使用 -projectpath 参数指定项目路径
|
561
|
+
if project_path
|
562
|
+
# 标准化路径以确保匹配
|
563
|
+
normalized_project_path = File.expand_path(project_path)
|
564
|
+
# 精确匹配项目路径(必须完全匹配,不能只是包含)
|
565
|
+
if command.match?(/-projectpath\s+#{Regexp.escape(normalized_project_path)}(\s|$)/i)
|
566
|
+
# 只有当项目路径完全匹配时才认为是相关进程
|
567
|
+
is_relevant_unity_process = true
|
568
|
+
match_reasons << "Unity Editor打开了当前项目"
|
569
|
+
|
570
|
+
# 如果还指定了Unity路径,也要验证
|
571
|
+
if unity_exe_full_path
|
572
|
+
if command.include?(unity_exe_full_path)
|
573
|
+
match_reasons << "使用指定的Unity版本"
|
574
|
+
else
|
575
|
+
# Unity路径不匹配,不是我们要的进程
|
576
|
+
is_relevant_unity_process = false
|
577
|
+
match_reasons.clear
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
elsif unity_exe_full_path && command.include?(unity_exe_full_path)
|
582
|
+
# 只提供了Unity路径,没有项目路径时才匹配
|
583
|
+
# 这种情况下匹配所有使用该Unity版本的进程
|
584
|
+
is_relevant_unity_process = true
|
585
|
+
match_reasons << "使用指定的Unity版本"
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
# 3. 如果没有提供路径参数,不匹配任何进程(由上层函数处理)
|
590
|
+
|
591
|
+
if is_relevant_unity_process
|
592
|
+
processes << { pid: pid, command: command }
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
end
|
597
|
+
processes
|
598
|
+
end
|
599
|
+
|
600
|
+
# 过滤掉僵尸进程和无效进程
|
601
|
+
def filter_valid_unity_processes(processes)
|
602
|
+
valid_processes = []
|
603
|
+
|
604
|
+
processes.each do |process_info|
|
605
|
+
# PID 可能是字符串,确保正确处理
|
606
|
+
pid = process_info[:pid]
|
607
|
+
pid_int = pid.to_i
|
608
|
+
|
609
|
+
# 检查进程是否真的存在且活跃
|
610
|
+
if process_exists_and_active?(pid_int)
|
611
|
+
valid_processes << process_info
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
valid_processes
|
616
|
+
end
|
617
|
+
|
618
|
+
# 检查进程是否真的存在且活跃
|
619
|
+
def process_exists_and_active?(pid)
|
620
|
+
begin
|
621
|
+
# 使用更简单可靠的方式检查进程
|
622
|
+
# 尝试发送信号0来检查进程是否存在
|
623
|
+
Process.kill(0, pid)
|
624
|
+
|
625
|
+
# 如果需要检查进程状态,使用不同的命令格式
|
626
|
+
# -o pid=,stat= 去掉标题行
|
627
|
+
process_info = `ps -p #{pid} -o pid=,stat= 2>/dev/null`.strip
|
628
|
+
|
629
|
+
if process_info.empty?
|
630
|
+
return false
|
631
|
+
end
|
632
|
+
|
633
|
+
# 解析进程状态
|
634
|
+
parts = process_info.split(/\s+/)
|
635
|
+
if parts.length >= 2
|
636
|
+
stat = parts[1]
|
637
|
+
# Z = 僵尸进程, T = 停止进程
|
638
|
+
# 只过滤僵尸进程,不过滤停止进程(T可能是正常的暂停状态)
|
639
|
+
if stat.include?('Z')
|
640
|
+
puts " 进程 #{pid} 是僵尸进程,过滤掉"
|
641
|
+
return false
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
true
|
646
|
+
rescue Errno::ESRCH
|
647
|
+
# 进程不存在
|
648
|
+
false
|
649
|
+
rescue Errno::EPERM
|
650
|
+
# 权限不足,但进程存在
|
651
|
+
true
|
652
|
+
rescue => e
|
653
|
+
puts "检查进程 #{pid} 时出错: #{e.message}"
|
654
|
+
# 如果出错,假设进程存在(保守处理)
|
655
|
+
true
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
# 关闭Unity进程
|
660
|
+
def kill_unity_process(pid)
|
661
|
+
begin
|
662
|
+
pid_int = pid.to_i
|
663
|
+
|
664
|
+
# 安全检查:确保不是当前进程
|
665
|
+
if pid_int == Process.pid
|
666
|
+
puts "⚠️ 跳过当前进程 #{pid}"
|
667
|
+
return true
|
668
|
+
end
|
669
|
+
|
670
|
+
# 检查进程是否存在
|
671
|
+
unless process_exists?(pid_int)
|
672
|
+
puts "进程 #{pid} 已不存在"
|
673
|
+
return true
|
674
|
+
end
|
675
|
+
|
676
|
+
# 获取进程信息进行额外验证
|
677
|
+
process_info = get_process_info(pid_int)
|
678
|
+
if process_info && !process_info.include?('Unity')
|
679
|
+
puts "⚠️ 跳过非Unity进程 #{pid}: #{process_info}"
|
680
|
+
return true
|
681
|
+
end
|
682
|
+
|
683
|
+
puts "正在关闭Unity进程 #{pid}..."
|
684
|
+
|
685
|
+
# 先尝试优雅关闭
|
686
|
+
begin
|
687
|
+
Process.kill('TERM', pid_int)
|
688
|
+
puts "已发送TERM信号给进程 #{pid}"
|
689
|
+
rescue Errno::EPERM
|
690
|
+
puts "❌ 没有权限关闭进程 #{pid},尝试使用sudo"
|
691
|
+
return kill_unity_process_with_sudo(pid)
|
692
|
+
rescue Errno::ESRCH
|
693
|
+
puts "进程 #{pid} 已不存在"
|
694
|
+
return true
|
695
|
+
end
|
696
|
+
|
697
|
+
# 等待进程优雅退出
|
698
|
+
sleep(3)
|
699
|
+
|
700
|
+
# 检查进程是否还存在
|
701
|
+
if process_exists?(pid_int)
|
702
|
+
puts "进程 #{pid} 未响应TERM信号,尝试强制关闭..."
|
703
|
+
begin
|
704
|
+
Process.kill('KILL', pid_int)
|
705
|
+
puts "已发送KILL信号给进程 #{pid}"
|
706
|
+
sleep(2)
|
707
|
+
rescue Errno::EPERM
|
708
|
+
puts "❌ 没有权限强制关闭进程 #{pid}"
|
709
|
+
return false
|
710
|
+
rescue Errno::ESRCH
|
711
|
+
puts "进程 #{pid} 已不存在"
|
712
|
+
return true
|
713
|
+
end
|
714
|
+
end
|
715
|
+
|
716
|
+
# 最终检查
|
717
|
+
if process_exists?(pid_int)
|
718
|
+
puts "❌ 无法关闭进程 #{pid}"
|
719
|
+
false
|
720
|
+
else
|
721
|
+
puts "✅ 成功关闭进程 #{pid}"
|
722
|
+
true
|
723
|
+
end
|
724
|
+
|
725
|
+
rescue => e
|
726
|
+
puts "❌ 关闭进程 #{pid} 时出错: #{e.message}"
|
727
|
+
puts "错误类型: #{e.class}"
|
728
|
+
false
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
# 使用sudo关闭进程
|
733
|
+
def kill_unity_process_with_sudo(pid)
|
734
|
+
begin
|
735
|
+
puts "尝试使用sudo关闭进程 #{pid}..."
|
736
|
+
result = system("sudo kill -TERM #{pid}")
|
737
|
+
if result
|
738
|
+
sleep(3)
|
739
|
+
if process_exists?(pid.to_i)
|
740
|
+
puts "TERM信号无效,尝试KILL信号..."
|
741
|
+
system("sudo kill -KILL #{pid}")
|
742
|
+
sleep(2)
|
743
|
+
end
|
744
|
+
!process_exists?(pid.to_i)
|
745
|
+
else
|
746
|
+
puts "❌ sudo命令执行失败"
|
747
|
+
false
|
748
|
+
end
|
749
|
+
rescue => e
|
750
|
+
puts "❌ sudo关闭进程时出错: #{e.message}"
|
751
|
+
false
|
752
|
+
end
|
753
|
+
end
|
754
|
+
|
755
|
+
# 获取进程信息
|
756
|
+
def get_process_info(pid)
|
757
|
+
begin
|
758
|
+
`ps -p #{pid} -o comm=`.strip
|
759
|
+
rescue => e
|
760
|
+
nil
|
761
|
+
end
|
762
|
+
end
|
763
|
+
|
764
|
+
# 检查进程是否存在
|
765
|
+
def process_exists?(pid)
|
766
|
+
begin
|
767
|
+
Process.kill(0, pid)
|
768
|
+
true
|
769
|
+
rescue Errno::ESRCH
|
770
|
+
false
|
771
|
+
rescue Errno::EPERM
|
772
|
+
# 权限不足,但进程存在
|
773
|
+
true
|
774
|
+
end
|
775
|
+
end
|
776
|
+
|
777
|
+
# 构建完成后清理 Unity 进程残留
|
778
|
+
def cleanup_unity_processes_after_build(unity_exe_full_path: nil, project_path: nil)
|
779
|
+
begin
|
780
|
+
# 等待一小段时间让 Unity 进程自然退出
|
781
|
+
sleep(2)
|
782
|
+
|
783
|
+
# 如果没有提供路径参数,不清理进程
|
784
|
+
if unity_exe_full_path.nil? && project_path.nil?
|
785
|
+
return
|
786
|
+
end
|
787
|
+
|
788
|
+
# 检查是否还有 Unity 进程在运行
|
789
|
+
unity_processes = `ps aux | grep -i unity | grep -v grep`.strip
|
790
|
+
|
791
|
+
if !unity_processes.empty?
|
792
|
+
# 解析进程信息,传入Unity路径和项目路径进行精确匹配
|
793
|
+
unity_pids = parse_unity_processes(unity_processes, unity_exe_full_path: unity_exe_full_path, project_path: project_path)
|
794
|
+
|
795
|
+
if unity_pids.any?
|
796
|
+
puts "\e[33m检测到构建后仍有项目相关的 Unity 进程残留,正在清理...\e[0m"
|
797
|
+
# 自动清理残留的 Unity 进程
|
798
|
+
cleaned_count = 0
|
799
|
+
unity_pids.each do |process_info|
|
800
|
+
if kill_unity_process(process_info[:pid])
|
801
|
+
cleaned_count += 1
|
802
|
+
end
|
803
|
+
end
|
804
|
+
|
805
|
+
if cleaned_count > 0
|
806
|
+
puts "\e[32m✅ 已清理 #{cleaned_count} 个 Unity 相关进程残留\e[0m"
|
807
|
+
end
|
808
|
+
end
|
809
|
+
end
|
810
|
+
rescue => e
|
811
|
+
# 清理失败不影响主流程
|
812
|
+
# 静默处理,不输出错误信息
|
813
|
+
end
|
814
|
+
end
|
815
|
+
|
816
|
+
|
173
817
|
def get_unity_version(project_path)
|
174
818
|
version_path = File.join(project_path, "ProjectSettings", "ProjectVersion.txt")
|
175
819
|
if File.exist?(version_path)
|
@@ -197,6 +841,19 @@ module Pindo
|
|
197
841
|
File.directory?(packages_path) &&
|
198
842
|
File.exist?(File.join(project_settings_path, "ProjectSettings.asset"))
|
199
843
|
end
|
844
|
+
|
845
|
+
# 确保 Android 构建所需的 Java 版本
|
846
|
+
def ensure_java_version_for_android
|
847
|
+
# 动态加载 BaseAndroidHelper 模块
|
848
|
+
require_relative '../android/base_helper'
|
849
|
+
|
850
|
+
# 创建一个临时对象来调用方法
|
851
|
+
helper = Object.new
|
852
|
+
helper.extend(Pindo::BaseAndroidHelper)
|
853
|
+
|
854
|
+
# 调用 Java 版本检测方法
|
855
|
+
helper.ensure_java_version_compliance
|
856
|
+
end
|
200
857
|
end
|
201
858
|
end
|
202
859
|
end
|