Class: Pindo::Client::UnityHelper

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/pindo/module/build/unityhelper.rb

Constant Summary collapse

UNITY_MAC_PATHS =
[
  "/Applications/Unity/Unity.app/Contents/MacOS/Unity",
  "/Applications/Unity/Hub/Editor/*/Unity.app/Contents/MacOS/Unity",
  "/Applications/Unity/*/Unity.app/Contents/MacOS/Unity"
]
UNITY_WINDOWS_PATHS =
[
  "C:/Program Files/Unity/Editor/Unity.exe",
  "C:/Program Files/Unity/Hub/Editor/*/Unity.exe",
  "C:/Program Files/Unity/*/Editor/Unity.exe"
]

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.share_instanceObject



26
27
28
# File 'lib/pindo/module/build/unityhelper.rb', line 26

def share_instance
  instance
end

Instance Method Details

#build_project(unity_exe_full_path: nil, project_path: nil, platform: nil, isLibrary: false) ⇒ Object



384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/pindo/module/build/unityhelper.rb', line 384

def build_project(unity_exe_full_path:nil, project_path:nil, platform: nil, isLibrary: false)

  # 检查是否有Unity进程在运行,传入Unity路径和项目路径以精确匹配
  check_unity_processes(unity_exe_full_path: unity_exe_full_path, project_path: project_path)

  additional_args = {}
  additional_args[:platform] = platform if platform
  additional_args[:buildtype] = 'library' if isLibrary

  # 首先尝试使用GoodUnityBuild.BuildManager.BatchBuild
  result = execute_unity_command(unity_exe_full_path, project_path, additional_args)

  if result[:success]
    puts "Unity build completed successfully"
    puts "Using Unity version: #{result[:unity_version]}"
    if !result[:stdout].empty?
      puts "Unity输出:"
      puts result[:stdout]
    end
  else
    puts "Unity build failed"
    puts "Unity version: #{result[:unity_version]}"
    puts "Exit status: #{result[:exit_status]}"
    
    if !result[:stdout].empty?
      puts "Unity标准输出:"
      puts result[:stdout]
    end
    
    if !result[:stderr].empty?
      puts "Unity错误输出:"
      puts result[:stderr]
    else
      puts "Unity没有输出错误信息"
    end
    
    # 提供更详细的错误信息
    error_msg = "Unity构建失败 (退出码: #{result[:exit_status]})"
    
    # 检查是否是Unity实例冲突错误
    if !result[:stdout].empty? && result[:stdout].include?("Multiple Unity instances cannot open the same project")
      error_msg = "Unity实例冲突错误:\n"
      error_msg += "检测到另一个Unity实例正在运行并打开了同一个项目。\n"
      error_msg += "请关闭所有Unity实例后重试。\n"
      error_msg += "可以使用以下命令检查Unity进程:\n"
      error_msg += "  ps aux | grep -i unity\n"
      error_msg += "然后使用以下命令关闭Unity进程:\n"
      error_msg += "  kill <进程ID>"
    # 检查是否是Android SDK下载问题
    elsif !result[:stdout].empty? && (result[:stdout].include?("Failed to download package") || result[:stdout].include?("Install Android SDK Platform"))
      error_msg = "Android SDK下载问题:\n"
      error_msg += "Unity在下载Android SDK组件时遇到网络问题。\n"
      error_msg += "解决方案:\n"
      error_msg += "1. 检查网络连接\n"
      error_msg += "2. 使用VPN或代理\n"
      error_msg += "3. 在Unity Editor中手动配置Android SDK路径\n"
      error_msg += "4. 清理Unity缓存: rm -rf Library/BuildCache\n"
      error_msg += "5. 重新运行构建命令"
    elsif !result[:stderr].empty?
      error_msg += "\n错误详情: #{result[:stderr].lines.first.strip}"
    elsif !result[:stdout].empty?
      error_msg += "\nUnity输出: #{result[:stdout].lines.first.strip}"
    end
    
    raise error_msg
  end

  result
end

#check_unity_processes(unity_exe_full_path: nil, project_path: nil) ⇒ Object



456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
# File 'lib/pindo/module/build/unityhelper.rb', line 456

def check_unity_processes(unity_exe_full_path: nil, project_path: nil)
  # 如果没有提供路径参数,直接跳过Unity进程检查
  if unity_exe_full_path.nil? && project_path.nil?
    return
  end

  # 检查是否有Unity进程在运行
  unity_processes = `ps aux | grep -i unity | grep -v grep`.strip

  if !unity_processes.empty?
    # 解析Unity进程信息,传入Unity路径和项目路径进行精确匹配
    unity_pids = parse_unity_processes(unity_processes, unity_exe_full_path: unity_exe_full_path, project_path: project_path)

    # 过滤掉僵尸进程和无效进程
    unity_pids = filter_valid_unity_processes(unity_pids)
    
    if unity_pids.any?
      puts "⚠️  检测到与当前项目相关的 Unity 进程正在运行:"
      # 只显示真正的Unity进程,不显示 pindo 相关进程
      unity_pids.each_with_index do |process_info, index|
        puts "  #{index + 1}. PID: #{process_info[:pid]}, 命令: #{process_info[:command]}"
      end
      puts ""

      # 询问用户是否要自动关闭Unity进程
      loop do
        puts "批处理模式需要关闭这些 Unity 进程以避免冲突"
        puts "  [y] 是, 自动关闭所有 Unity 进程"
        puts "  [s] 跳过, 我已经手动关闭 Unity 进程,继续构建(可能导致冲突)"
        puts "  [e] 退出编译"
        print "请输入选择 (y/s/e): "

        input = STDIN.gets
        choice = input ? input.chomp.strip.downcase : ""

        case choice
        when 'y', 'yes', '1'
          puts "\n正在关闭 Unity 相关进程..."
          success_count = 0
          unity_pids.each do |process_info|
            if kill_unity_process(process_info[:pid])
              puts "✅ 成功关闭进程 #{process_info[:pid]}"
              success_count += 1
            else
              puts "❌ 关闭进程 #{process_info[:pid]} 失败"
            end
          end

          if success_count > 0
            puts "\n✅ 已关闭 #{success_count} 个 Unity 相关进程"
            puts "等待3秒后继续编译..."
            sleep(3)
          else
            puts "\n❌ 无法关闭 Unity 相关进程,请手动关闭后重试"
            raise "Unity进程冲突:无法自动关闭 Unity 相关进程,请手动关闭后重试"
          end
          break  # 退出循环

        when 's', 'skip', '2'
          puts "\n✅ 跳过检查,继续编译..."
          puts "假设Unity Editor已经手动关闭,如果仍在运行可能导致编译失败"
          sleep(1)
          break  # 退出循环

        when 'e', 'exit', '3'
          puts "\n⚠️  用户选择退出编译"
          puts "退出Unity编译流程"
          exit 0

        else
          puts "\n⚠️ 无效选择: '#{choice}'"
          puts "请输入 y (是), s (跳过), 或 e (退出)\n\n"
          # 继续循环,让用户重新输入
        end
      end
    else
      # 没有检测到与当前项目相关的Unity进程,静默继续
    end
  end
end

#cleanup_unity_processes_after_build(unity_exe_full_path: nil, project_path: nil) ⇒ Object

构建完成后清理 Unity 进程残留



777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
# File 'lib/pindo/module/build/unityhelper.rb', line 777

def cleanup_unity_processes_after_build(unity_exe_full_path: nil, project_path: nil)
  begin
    # 等待一小段时间让 Unity 进程自然退出
    sleep(2)

    # 如果没有提供路径参数,不清理进程
    if unity_exe_full_path.nil? && project_path.nil?
      return
    end

    # 检查是否还有 Unity 进程在运行
    unity_processes = `ps aux | grep -i unity | grep -v grep`.strip

    if !unity_processes.empty?
      # 解析进程信息,传入Unity路径和项目路径进行精确匹配
      unity_pids = parse_unity_processes(unity_processes, unity_exe_full_path: unity_exe_full_path, project_path: project_path)

      if unity_pids.any?
        puts "\e[33m检测到构建后仍有项目相关的 Unity 进程残留,正在清理...\e[0m"
        # 自动清理残留的 Unity 进程
        cleaned_count = 0
        unity_pids.each do |process_info|
          if kill_unity_process(process_info[:pid])
            cleaned_count += 1
          end
        end

        if cleaned_count > 0
          puts "\e[32m✅ 已清理 #{cleaned_count} 个 Unity 相关进程残留\e[0m"
        end
      end
    end
  rescue => e
    # 清理失败不影响主流程
    # 静默处理,不输出错误信息
  end
end

#ensure_java_version_for_androidObject

确保 Android 构建所需的 Java 版本



845
846
847
848
849
850
851
852
853
854
855
# File 'lib/pindo/module/build/unityhelper.rb', line 845

def ensure_java_version_for_android
  # 动态加载 BaseAndroidHelper 模块
  require_relative '../android/base_helper'
  
  # 创建一个临时对象来调用方法
  helper = Object.new
  helper.extend(Pindo::BaseAndroidHelper)
  
  # 调用 Java 版本检测方法
  helper.ensure_java_version_compliance
end

#execute_unity_command(unity_exe_full_path, project_path, additional_args = {}) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/pindo/module/build/unityhelper.rb', line 169

def execute_unity_command(unity_exe_full_path, project_path, additional_args = {})
  if unity_exe_full_path.nil?
    raise Informative, "Unity path not found!"
  end

  # 调试级别:通过环境变量或参数控制
  debug_level = ENV['UNITY_BUILD_DEBUG'] || additional_args[:debug_level] || 'normal'
  # debug_level: 'quiet' - 只显示错误
  #              'normal' - 显示错误、警告和关键进度
  #              'verbose' - 显示所有输出

  # 检查项目路径是否存在
  unless File.directory?(project_path)
    raise Informative, "Unity项目路径不存在: #{project_path}"
  end

  # 检查项目是否是Unity项目
  project_settings = File.join(project_path, "ProjectSettings")
  unless File.directory?(project_settings)
    raise Informative, "项目路径不是Unity项目: #{project_path}"
  end

  # 如果是 Android 平台,检查 Java 版本
  platform = additional_args[:platform]
  if platform == 'Android'
    unless ensure_java_version_for_android
      raise Informative, "Java 版本不符合要求,无法继续构建 Android 项目"
    end
  end

  cmd_args = [
    unity_exe_full_path,
    "-batchmode",
    "-quit",
    "-projectPath",
    project_path.to_s,
    "-executeMethod",
    "GoodUnityBuild.BuildManager.BatchBuild"
  ]

  # Add any additional arguments
  additional_args.each do |key, value|
    cmd_args << "-#{key}"
    cmd_args << value.to_s if value
  end

  puts "Unity command: #{cmd_args.join(' ')}"
  puts ""
  puts "\e[33m正在执行 Unity 构建,这可能需要几分钟时间,请耐心等待...\e[0m"
  
  # 根据平台显示不同的构建步骤
  platform = additional_args[:platform]
  if platform == 'Android'
    puts "\e[36m提示: Unity 正在后台处理项目,包括以下步骤:\e[0m"
    puts "\e[36m  • 编译 C# 脚本\e[0m"
    puts "\e[36m  • 处理资源文件\e[0m"
    puts "\e[36m  • 生成 Android 项目\e[0m"
    puts "\e[36m  • 构建 AAB 文件\e[0m"
  elsif platform == 'iOS'
    puts "\e[36m提示: Unity 正在后台处理项目,包括以下步骤:\e[0m"
    puts "\e[36m  • 编译 C# 脚本\e[0m"
    puts "\e[36m  • 处理资源文件\e[0m"
    puts "\e[36m  • 生成 iOS 项目\e[0m"
    puts "\e[36m  • 构建 Xcode 项目\e[0m"
  elsif platform == 'WebGL'
    puts "\e[36m提示: Unity 正在后台处理项目,包括以下步骤:\e[0m"
    puts "\e[36m  • 编译 C# 脚本\e[0m"
    puts "\e[36m  • 处理资源文件\e[0m"
    puts "\e[36m  • 生成 WebGL 项目\e[0m"
    puts "\e[36m  • 构建 Web 资源\e[0m"
  else
    puts "\e[36m提示: Unity 正在后台处理项目,包括以下步骤:\e[0m"
    puts "\e[36m  • 编译 C# 脚本\e[0m"
    puts "\e[36m  • 处理资源文件\e[0m"
    puts "\e[36m  • 生成项目文件\e[0m"
  end
  
  puts "\e[36m如果长时间无响应(Unity 进程可能已卡死),请完全终止当前终端后重试\e[0m"
  puts "\e[36m或者,打开 Unity Editor 通过构建工具执行一次项目导出后,关闭 Unity Editor 再执行此命令\e[0m"
  puts ""
  
  # 使用更智能的进度检测机制
  progress_thread = nil
  start_time = Time.now
  last_output_time = Time.now
  
  begin
    # 使用 Open3.popen3 来实时监控输出
    Open3.popen3(*cmd_args) do |stdin, stdout, stderr, wait_thr|
      stdin.close
      
      # 启动进度显示线程
      progress_thread = Thread.new do
        dots = 0
        while wait_thr.alive?
          sleep(2)
          dots = (dots + 1) % 4
          elapsed = (Time.now - start_time).to_i
          print "\r\e[33mUnity 构建中#{'.' * dots}#{' ' * (3 - dots)} (已用时: #{elapsed}秒)\e[0m"
          $stdout.flush
        end
      end
      
      # 实时读取输出
      stdout_buffer = ""
      stderr_buffer = ""
      
      # 定义错误关键词模式(优化正则,避免重复)
      error_pattern = /error|exception|failed|Build completed with a result of 'Failed'/i
      warning_pattern = /warning|warn/i
      success_pattern = /Build completed successfully|Exiting batchmode successfully/i

      # 使用非阻塞方式读取输出
      while wait_thr.alive?
        # 检查是否有输出可读
        ready_streams = IO.select([stdout, stderr], nil, nil, 1)

        if ready_streams
          ready_streams[0].each do |stream|
            if line = stream.gets
              # 记录输出
              if stream == stdout
                stdout_buffer += line
              else
                stderr_buffer += line
              end
              last_output_time = Time.now

              # 根据调试级别和内容类型显示输出
              case debug_level
              when 'verbose'
                # 详细模式:显示所有输出
                puts line
              when 'quiet'
                # 安静模式:只显示错误
                if line.match?(error_pattern)
                  puts "\e[31m[错误] #{line.strip}\e[0m"
                end
              else # 'normal'
                # 正常模式:显示错误、警告和关键进度
                if line.match?(error_pattern)
                  puts "\n\e[31m[错误] #{line.strip}\e[0m"
                elsif line.match?(warning_pattern)
                  puts "\n\e[33m[警告] #{line.strip}\e[0m"
                elsif line.match?(success_pattern)
                  puts "\n\e[32m[成功] #{line.strip}\e[0m"
                elsif line.match?(/\d+%/) || line.match?(/Building|Compiling|Processing/i)
                  # 显示进度相关信息
                  print "\r\e[36m[进度] #{line.strip}\e[0m"
                  $stdout.flush
                end
              end
            end
          end
        end

        # 检查是否长时间无输出(可能卡死)
        if Time.now - last_output_time > 300 # 5分钟无输出
          puts "\n\e[33m⚠️  Unity 构建已超过5分钟无输出,可能遇到问题\e[0m"
          puts "\e[33m建议:\e[0m"
          puts "\e[33m  1. 检查 Unity Editor 是否有弹窗需要确认\e[0m"
          puts "\e[33m  2. 查看 Unity 日志文件\e[0m"
          puts "\e[33m  3. 如确认卡死,可按 Ctrl+C 终止构建\e[0m"
          last_output_time = Time.now # 重置计时器
        end
      end
      
      # 读取剩余输出
      stdout_buffer += stdout.read
      stderr_buffer += stderr.read
      
      # 停止进度显示线程
      progress_thread.kill if progress_thread
      
      # 检查构建是否真的成功
      build_success = wait_thr.value.success?
      
      # 检查是否有构建错误的关键词
      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/)
        build_success = false
        puts "\n\e[31m检测到构建错误信息,构建可能失败\e[0m"
      end
      
      if build_success
        print "\r\e[32mUnity 构建完成!\e[0m\n"
        # 构建完成后检查并清理可能的 Unity 进程残留
        cleanup_unity_processes_after_build(unity_exe_full_path: unity_exe_full_path, project_path: project_path)
      else
        print "\r\e[31mUnity 构建失败!\e[0m\n"
        puts "\e[31m构建输出:\e[0m"
        puts stdout_buffer if !stdout_buffer.empty?
        puts "\e[31m错误输出:\e[0m"
        puts stderr_buffer if !stderr_buffer.empty?
        # 构建失败时也清理可能的进程残留
        cleanup_unity_processes_after_build(unity_exe_full_path: unity_exe_full_path, project_path: project_path)
      end
      
      # 返回结果
      {
        success: build_success,
        stdout: stdout_buffer,
        stderr: stderr_buffer,
        exit_status: wait_thr.value.exitstatus,
        unity_version: extract_version_from_path(unity_exe_full_path)
      }
    end
  rescue => e
    # 停止进度显示线程
    progress_thread.kill if progress_thread
    print "\r\e[31mUnity 构建失败!\e[0m\n"
    raise e
  end
end

#filter_valid_unity_processes(processes) ⇒ Object

过滤掉僵尸进程和无效进程



600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
# File 'lib/pindo/module/build/unityhelper.rb', line 600

def filter_valid_unity_processes(processes)
  valid_processes = []

  processes.each do |process_info|
    # PID 可能是字符串,确保正确处理
    pid = process_info[:pid]
    pid_int = pid.to_i

    # 检查进程是否真的存在且活跃
    if process_exists_and_active?(pid_int)
      valid_processes << process_info
    end
  end

  valid_processes
end

#find_unity_path(project_unity_version: nil, force_change_version: false) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/pindo/module/build/unityhelper.rb', line 31

def find_unity_path(project_unity_version:nil, force_change_version:false)

  if project_unity_version.nil? || project_unity_version.empty?
    raise "Project Unity version is nil or empty"
  end

  unity_major_version = project_unity_version.split('.')[0..1].join('.')
  paths = case RUBY_PLATFORM
          when /darwin/
            UNITY_MAC_PATHS
          when /mswin|mingw|windows/
            UNITY_WINDOWS_PATHS
          else
            raise "Unsupported platform: #{RUBY_PLATFORM}"
          end

  unity_versions = []

  paths.each do |path|
    if path.include?("*")
      Dir.glob(path).each do |expanded_path|
        version = extract_version_from_path(expanded_path)
        if version
          major_version = version.split('.')[0..1].join('.')
          unity_versions << {
            path: expanded_path,
            version: version,
            major_version: major_version
          }
        end
      end
    elsif File.exist?(path)
      version = extract_version_from_path(path)
      if version
        major_version = version.split('.')[0..1].join('.')
        unity_versions << {
          path: path,
          version: version,
          major_version: major_version
        }
      end
    end
  end

  if unity_versions.empty?
    puts "调试信息: 搜索的Unity路径:"
    paths.each do |path|
      puts "  - #{path}"
      if path.include?("*")
        Dir.glob(path).each do |expanded_path|
          puts "    展开: #{expanded_path}"
        end
      elsif File.exist?(path)
        puts "    存在: #{path}"
      else
        puts "    不存在: #{path}"
      end
    end
    raise Informative, "未找到任何Unity版本,请检查Unity是否正确安装"
  end


  select_unity_versions = unity_versions.select { |v| v[:version] == project_unity_version } || []
  if !select_unity_versions.nil? && !select_unity_versions.empty? && select_unity_versions.length >= 1
    return select_unity_versions.first[:path]
  end

  unity_versions.sort_by! { |v| v[:major_version] }
  select_unity_versions = unity_versions.select { |v| v[:major_version] == unity_major_version } if unity_major_version
  if select_unity_versions.nil? || select_unity_versions.empty?
    if force_change_version
      puts "强制使用最新版本: #{unity_versions.last[:version]}"
      return unity_versions.last[:path]
    else
      puts "调试信息: 项目Unity版本: #{project_unity_version}"
      puts "调试信息: 可用的Unity版本:"
      unity_versions.each do |v|
        puts "  - #{v[:version]} (#{v[:major_version]})"
      end
      raise Informative, "未找到匹配的Unity版本 #{project_unity_version},可用的版本: #{unity_versions.map { |v| v[:version] }.join(', ')}"
    end
  else
    return select_unity_versions.first[:path]
  end

end

#get_process_info(pid) ⇒ Object

获取进程信息



755
756
757
758
759
760
761
# File 'lib/pindo/module/build/unityhelper.rb', line 755

def get_process_info(pid)
  begin
    `ps -p #{pid} -o comm=`.strip
  rescue => e
    nil
  end
end

#get_unity_version(project_path) ⇒ Object



816
817
818
819
820
821
822
823
824
825
826
827
828
829
# File 'lib/pindo/module/build/unityhelper.rb', line 816

def get_unity_version(project_path)
  version_path = File.join(project_path, "ProjectSettings", "ProjectVersion.txt")
  if File.exist?(version_path)
    content = File.read(version_path)
    if content =~ /m_EditorVersion: (.*)/
      version = $1.strip
      version
    else
      raise "Could not parse Unity version from #{version_path}"
    end
  else
    raise "Project version file not found at #{version_path}"
  end
end

#kill_unity_process(pid) ⇒ Object

关闭Unity进程



659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
# File 'lib/pindo/module/build/unityhelper.rb', line 659

def kill_unity_process(pid)
  begin
    pid_int = pid.to_i
    
    # 安全检查:确保不是当前进程
    if pid_int == Process.pid
      puts "⚠️  跳过当前进程 #{pid}"
      return true
    end
    
    # 检查进程是否存在
    unless process_exists?(pid_int)
      puts "进程 #{pid} 已不存在"
      return true
    end
    
    # 获取进程信息进行额外验证
    process_info = get_process_info(pid_int)
    if process_info && !process_info.include?('Unity')
      puts "⚠️  跳过非Unity进程 #{pid}: #{process_info}"
      return true
    end
    
    puts "正在关闭Unity进程 #{pid}..."
    
    # 先尝试优雅关闭
    begin
      Process.kill('TERM', pid_int)
      puts "已发送TERM信号给进程 #{pid}"
    rescue Errno::EPERM
      puts "❌ 没有权限关闭进程 #{pid},尝试使用sudo"
      return kill_unity_process_with_sudo(pid)
    rescue Errno::ESRCH
      puts "进程 #{pid} 已不存在"
      return true
    end
    
    # 等待进程优雅退出
    sleep(3)
    
    # 检查进程是否还存在
    if process_exists?(pid_int)
      puts "进程 #{pid} 未响应TERM信号,尝试强制关闭..."
      begin
        Process.kill('KILL', pid_int)
        puts "已发送KILL信号给进程 #{pid}"
        sleep(2)
      rescue Errno::EPERM
        puts "❌ 没有权限强制关闭进程 #{pid}"
        return false
      rescue Errno::ESRCH
        puts "进程 #{pid} 已不存在"
        return true
      end
    end
    
    # 最终检查
    if process_exists?(pid_int)
      puts "❌ 无法关闭进程 #{pid}"
      false
    else
      puts "✅ 成功关闭进程 #{pid}"
      true
    end
    
  rescue => e
    puts "❌ 关闭进程 #{pid} 时出错: #{e.message}"
    puts "错误类型: #{e.class}"
    false
  end
end

#kill_unity_process_with_sudo(pid) ⇒ Object

使用sudo关闭进程



732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
# File 'lib/pindo/module/build/unityhelper.rb', line 732

def kill_unity_process_with_sudo(pid)
  begin
    puts "尝试使用sudo关闭进程 #{pid}..."
    result = system("sudo kill -TERM #{pid}")
    if result
      sleep(3)
      if process_exists?(pid.to_i)
        puts "TERM信号无效,尝试KILL信号..."
        system("sudo kill -KILL #{pid}")
        sleep(2)
      end
      !process_exists?(pid.to_i)
    else
      puts "❌ sudo命令执行失败"
      false
    end
  rescue => e
    puts "❌ sudo关闭进程时出错: #{e.message}"
    false
  end
end

#parse_unity_processes(processes_output, unity_exe_full_path: nil, project_path: nil) ⇒ Object

解析Unity进程信息



538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
# File 'lib/pindo/module/build/unityhelper.rb', line 538

def parse_unity_processes(processes_output, unity_exe_full_path: nil, project_path: nil)
  processes = []

  processes_output.lines.each do |line|
    # 解析 ps aux 输出格式
    parts = line.strip.split(/\s+/)
    if parts.length >= 11
      pid = parts[1]
      command = parts[10..-1].join(' ')

      # 过滤掉grep进程本身和pindo进程
      unless command.include?('grep') || command.include?('pindo')
        # 精确匹配Unity进程
        is_relevant_unity_process = false
        match_reasons = []

        # 1. 必须是Unity Editor主进程(排除VS Code等其他进程)
        is_unity_editor = command.match?(/Unity\.app.*\/Unity/i) || command.match?(/Unity\.exe/i)

        if is_unity_editor
          # 2. 检查是否是打开了指定项目的Unity进程
          # Unity 使用 -projectpath 参数指定项目路径
          if project_path
            # 标准化路径以确保匹配
            normalized_project_path = File.expand_path(project_path)
            # 精确匹配项目路径(必须完全匹配,不能只是包含)
            if command.match?(/-projectpath\s+#{Regexp.escape(normalized_project_path)}(\s|$)/i)
              # 只有当项目路径完全匹配时才认为是相关进程
              is_relevant_unity_process = true
              match_reasons << "Unity Editor打开了当前项目"

              # 如果还指定了Unity路径,也要验证
              if unity_exe_full_path
                if command.include?(unity_exe_full_path)
                  match_reasons << "使用指定的Unity版本"
                else
                  # Unity路径不匹配,不是我们要的进程
                  is_relevant_unity_process = false
                  match_reasons.clear
                end
              end
            end
          elsif unity_exe_full_path && command.include?(unity_exe_full_path)
            # 只提供了Unity路径,没有项目路径时才匹配
            # 这种情况下匹配所有使用该Unity版本的进程
            is_relevant_unity_process = true
            match_reasons << "使用指定的Unity版本"
          end
        end

        # 3. 如果没有提供路径参数,不匹配任何进程(由上层函数处理)

        if is_relevant_unity_process
          processes << { pid: pid, command: command }
        end
      end
    end
  end
  processes
end

#process_exists?(pid) ⇒ Boolean

检查进程是否存在

Returns:

  • (Boolean)


764
765
766
767
768
769
770
771
772
773
774
# File 'lib/pindo/module/build/unityhelper.rb', line 764

def process_exists?(pid)
  begin
    Process.kill(0, pid)
    true
  rescue Errno::ESRCH
    false
  rescue Errno::EPERM
    # 权限不足,但进程存在
    true
  end
end

#process_exists_and_active?(pid) ⇒ Boolean

检查进程是否真的存在且活跃

Returns:

  • (Boolean)


618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
# File 'lib/pindo/module/build/unityhelper.rb', line 618

def process_exists_and_active?(pid)
  begin
    # 使用更简单可靠的方式检查进程
    # 尝试发送信号0来检查进程是否存在
    Process.kill(0, pid)

    # 如果需要检查进程状态,使用不同的命令格式
    # -o pid=,stat= 去掉标题行
    process_info = `ps -p #{pid} -o pid=,stat= 2>/dev/null`.strip

    if process_info.empty?
      return false
    end

    # 解析进程状态
    parts = process_info.split(/\s+/)
    if parts.length >= 2
      stat = parts[1]
      # Z = 僵尸进程, T = 停止进程
      # 只过滤僵尸进程,不过滤停止进程(T可能是正常的暂停状态)
      if stat.include?('Z')
        puts "        进程 #{pid} 是僵尸进程,过滤掉"
        return false
      end
    end

    true
  rescue Errno::ESRCH
    # 进程不存在
    false
  rescue Errno::EPERM
    # 权限不足,但进程存在
    true
  rescue => e
    puts "检查进程 #{pid} 时出错: #{e.message}"
    # 如果出错,假设进程存在(保守处理)
    true
  end
end

#unity_project?(project_path) ⇒ Boolean

Returns:

  • (Boolean)


832
833
834
835
836
837
838
839
840
841
842
# File 'lib/pindo/module/build/unityhelper.rb', line 832

def unity_project?(project_path)
  # 检查关键Unity工程文件和目录是否存在
  project_settings_path = File.join(project_path, "ProjectSettings")
  assets_path = File.join(project_path, "Assets")
  packages_path = File.join(project_path, "Packages")

  File.directory?(project_settings_path) &&
  File.directory?(assets_path) &&
  File.directory?(packages_path) &&
  File.exist?(File.join(project_settings_path, "ProjectSettings.asset"))
end