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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/command/android/autobuild.rb +38 -7
  3. data/lib/pindo/command/appstore/adhocbuild.rb +52 -6
  4. data/lib/pindo/command/appstore/autobuild.rb +51 -5
  5. data/lib/pindo/command/appstore/autoresign.rb +2 -2
  6. data/lib/pindo/command/ios/autobuild.rb +62 -16
  7. data/lib/pindo/command/ios/fixproj.rb +92 -0
  8. data/lib/pindo/command/ios.rb +1 -0
  9. data/lib/pindo/command/jps/upload.rb +1 -1
  10. data/lib/pindo/command/unity/autobuild.rb +47 -10
  11. data/lib/pindo/command/web/autobuild.rb +44 -14
  12. data/lib/pindo/module/pgyer/pgyerhelper.rb +1 -1
  13. data/lib/pindo/module/task/model/git_tag_task.rb +1 -1
  14. data/lib/pindo/module/task/model/{jps_upload_task.rb → jps/jps_upload_task.rb} +2 -2
  15. data/lib/pindo/module/task/model/{ipa_local_resign_task.rb → resign/ipa_local_resign_task.rb} +2 -2
  16. data/lib/pindo/module/task/model/{jps_resign_task.rb → resign/jps_resign_task.rb} +2 -2
  17. data/lib/pindo/module/task/model/unity/unity_config_task.rb +107 -0
  18. data/lib/pindo/module/task/model/{unity_export_task.rb → unity/unity_export_task.rb} +71 -74
  19. data/lib/pindo/module/task/model/unity/unity_update_task.rb +94 -0
  20. data/lib/pindo/module/task/model/unity/unity_yoo_asset_task.rb +155 -0
  21. data/lib/pindo/module/task/model/unity_task.rb +113 -0
  22. data/lib/pindo/module/task/task_manager.rb +9 -1
  23. data/lib/pindo/module/unity/unity_command_helper.rb +188 -0
  24. data/lib/pindo/module/unity/unity_env_helper.rb +208 -0
  25. data/lib/pindo/module/unity/unity_helper.rb +189 -746
  26. data/lib/pindo/module/unity/unity_proc_helper.rb +390 -0
  27. data/lib/pindo/module/xcode/xcode_build_helper.rb +3 -3
  28. data/lib/pindo/version.rb +1 -1
  29. 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