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