pindo 5.13.11 → 5.13.13

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/base/funlog.rb +62 -5
  3. data/lib/pindo/base/git_handler.rb +83 -22
  4. data/lib/pindo/base/output_sink.rb +69 -0
  5. data/lib/pindo/command/android/autobuild.rb +57 -8
  6. data/lib/pindo/command/appstore/autobuild.rb +10 -1
  7. data/lib/pindo/command/ios/autobuild.rb +59 -7
  8. data/lib/pindo/command/jps/media.rb +185 -58
  9. data/lib/pindo/command/jps/upload.rb +14 -9
  10. data/lib/pindo/command/unity/autobuild.rb +64 -10
  11. data/lib/pindo/command/unity/packpush.rb +27 -3
  12. data/lib/pindo/command/utils/tag.rb +9 -1
  13. data/lib/pindo/command/web/autobuild.rb +59 -10
  14. data/lib/pindo/module/android/android_build_helper.rb +6 -7
  15. data/lib/pindo/module/build/git_repo_helper.rb +29 -25
  16. data/lib/pindo/module/pgyer/pgyerhelper.rb +174 -77
  17. data/lib/pindo/module/task/core/concurrent_execution_strategy.rb +237 -0
  18. data/lib/pindo/module/task/core/dependency_checker.rb +123 -0
  19. data/lib/pindo/module/task/core/execution_strategy.rb +61 -0
  20. data/lib/pindo/module/task/core/resource_lock_manager.rb +190 -0
  21. data/lib/pindo/module/task/core/serial_execution_strategy.rb +60 -0
  22. data/lib/pindo/module/task/core/task_executor.rb +131 -0
  23. data/lib/pindo/module/task/core/task_queue.rb +221 -0
  24. data/lib/pindo/module/task/model/build/android_build_dev_task.rb +1 -1
  25. data/lib/pindo/module/task/model/build/android_build_task.rb +6 -2
  26. data/lib/pindo/module/task/model/build/ios_build_dev_task.rb +2 -3
  27. data/lib/pindo/module/task/model/build/ios_build_task.rb +6 -0
  28. data/lib/pindo/module/task/model/build_task.rb +22 -0
  29. data/lib/pindo/module/task/model/git/git_commit_task.rb +11 -2
  30. data/lib/pindo/module/task/model/git_task.rb +6 -0
  31. data/lib/pindo/module/task/model/jps/jps_message_task.rb +9 -11
  32. data/lib/pindo/module/task/model/jps/jps_upload_media_task.rb +264 -64
  33. data/lib/pindo/module/task/model/jps_task.rb +0 -1
  34. data/lib/pindo/module/task/model/unity_task.rb +38 -2
  35. data/lib/pindo/module/task/output/multi_line_output_manager.rb +380 -0
  36. data/lib/pindo/module/task/output/multi_line_task_display.rb +185 -0
  37. data/lib/pindo/module/task/output/stdout_redirector.rb +95 -0
  38. data/lib/pindo/module/task/pindo_task.rb +133 -9
  39. data/lib/pindo/module/task/task_manager.rb +98 -268
  40. data/lib/pindo/module/task/task_reporter.rb +135 -0
  41. data/lib/pindo/module/task/task_resources/resource_instance.rb +90 -0
  42. data/lib/pindo/module/task/task_resources/resource_registry.rb +105 -0
  43. data/lib/pindo/module/task/task_resources/resource_type.rb +59 -0
  44. data/lib/pindo/module/task/task_resources/types/directory_based_resource.rb +63 -0
  45. data/lib/pindo/module/task/task_resources/types/global_exclusive_resource.rb +33 -0
  46. data/lib/pindo/module/task/task_resources/types/global_shared_resource.rb +34 -0
  47. data/lib/pindo/module/xcode/xcode_build_helper.rb +26 -8
  48. data/lib/pindo/options/groups/jps_options.rb +10 -0
  49. data/lib/pindo/options/groups/task_options.rb +39 -0
  50. data/lib/pindo/version.rb +3 -2
  51. metadata +20 -1
@@ -1,3 +1,4 @@
1
+ require 'fileutils'
1
2
  require 'pindo/module/task/model/jps_task'
2
3
  require 'pindo/module/task/task_config'
3
4
  require 'pindo/module/pgyer/pgyerhelper'
@@ -9,10 +10,10 @@ module Pindo
9
10
  #
10
11
  # 支持两种模式:
11
12
  # 1. 指定模式:传入 file_paths 和 git_commit_id
12
- # 2. 自动模式:只传入 project_path,自动查找 JPSMedia/ 目录和 git HEAD 信息
13
+ # 2. 自动模式:只传入 upload_path,自动查找 JPSMedia/ 目录和 git HEAD 信息
13
14
  class JPSUploadMediaTask < JPSTask
14
15
  attr_reader :file_paths, :git_commit_id, :git_commit_time, :git_commit_desc
15
- attr_reader :workflow_id, :project_path
16
+ attr_reader :upload_path
16
17
 
17
18
  # 支持的媒体文件扩展名
18
19
  MEDIA_EXTENSIONS = %w[png jpg jpeg gif bmp webp mp4 mov avi mkv webm].freeze
@@ -24,20 +25,20 @@ module Pindo
24
25
 
25
26
  # 初始化 Media 上传任务
26
27
  # @param file_paths [Array<String>] 要上传的文件路径列表(可选,为空时自动查找)
28
+ # @param upload_path [String] 项目路径(必需,用于查找 git 仓库和 JPSMedia 目录)
27
29
  # @param options [Hash] 选项
28
30
  # @option options [String] :git_commit_id Git commit SHA(可选,为空时自动获取 HEAD)
29
31
  # @option options [String] :git_commit_time Git commit 时间(可选)
30
32
  # @option options [String] :git_commit_desc Git commit 描述(可选)
31
- # @option options [Integer] :workflow_id 工作流ID(可选,用于筛选 commit_log)
32
- # @option options [String] :project_name 项目名称(可选)
33
- # @option options [String] :project_path 项目路径(必需,用于查找 git 仓库和 JPSMedia 目录)
34
- def initialize(file_paths, options = {})
33
+ # @option options [Hash] :app_info_obj JPS 应用信息对象(可选,继承自基类)
34
+ # @option options [Hash] :workflow_info 工作流信息(可选,继承自基类)
35
+ # @option options [String] :project_name 项目名称(可选,继承自基类)
36
+ def initialize(file_paths, upload_path, options = {})
35
37
  @file_paths = file_paths || [] # 要上传的文件路径列表
38
+ @upload_path = upload_path # 项目路径
36
39
  @git_commit_id = options[:git_commit_id] # Git commit SHA
37
40
  @git_commit_time = options[:git_commit_time] # Git commit 时间
38
41
  @git_commit_desc = options[:git_commit_desc] # Git commit 描述
39
- @workflow_id = options[:workflow_id] # 工作流ID
40
- @project_path = options[:project_path] # 项目路径
41
42
 
42
43
  # 设置任务优先级为 LOW,确保在其他任务之后执行
43
44
  options[:priority] ||= TaskPriority::LOW
@@ -48,12 +49,14 @@ module Pindo
48
49
  end
49
50
 
50
51
  def validate
51
- # 验证 project_path(必需参数)
52
- unless @project_path && !@project_path.empty? && Dir.exist?(@project_path)
53
- @error = "缺少必需参数: project_path(项目路径)或路径不存在"
52
+ # 验证 upload_path(必需参数)
53
+ unless @upload_path && !@upload_path.empty? && Dir.exist?(@upload_path)
54
+ @error = "缺少必需参数: upload_path(项目路径)或路径不存在"
54
55
  return false
55
56
  end
56
57
 
58
+ # workflow_info 可以延迟获取,不在这里验证
59
+
57
60
  true
58
61
  end
59
62
 
@@ -87,14 +90,14 @@ module Pindo
87
90
 
88
91
  def do_work
89
92
  # 1. 查找 git 仓库根目录
90
- git_root = find_git_root(@project_path)
93
+ git_root = find_git_root(@upload_path)
91
94
  unless git_root
92
- raise "未找到 git 仓库,请确保 project_path 在 git 仓库内"
95
+ raise "未找到 git 仓库,请确保 upload_path 在 git 仓库内"
93
96
  end
94
97
 
95
- # 2. 如果没有提供 git_commit_id,自动获取 HEAD 信息
98
+ # 2. 如果没有提供 git_commit_id,获取最新的符合规范的 commit
96
99
  if @git_commit_id.nil? || @git_commit_id.empty?
97
- git_info = get_git_head_info(git_root)
100
+ git_info = Pindo::GitHandler.get_latest_conventional_commit(project_dir: git_root)
98
101
  @git_commit_id = git_info[:commit_id]
99
102
  @git_commit_time = git_info[:commit_time]
100
103
  @git_commit_desc = git_info[:commit_desc]
@@ -106,24 +109,32 @@ module Pindo
106
109
  end
107
110
 
108
111
  # 3. 打印 git commit 信息
109
- puts
110
- puts " ===== Git Commit 信息 ====="
111
- puts " Commit ID: #{@git_commit_id}"
112
- puts " Commit Time: #{@git_commit_time}" if @git_commit_time
113
- puts " Commit Desc: #{@git_commit_desc}" if @git_commit_desc
114
- puts " " + "=" * 28
115
- puts
116
-
117
- # 4. 如果 file_paths 为空,自动查找 JPSMedia 目录
112
+ puts ""
113
+ puts " 📝 Commit: #{@git_commit_desc}" if @git_commit_desc
114
+ puts " ID: #{@git_commit_id[0..7]}"
115
+ puts " Time: #{@git_commit_time}" if @git_commit_time
116
+ puts ""
117
+
118
+ # 4. 处理文件路径
118
119
  if @file_paths.nil? || @file_paths.empty?
120
+ # 自动模式:从 JPSMedia 目录查找
119
121
  @file_paths = find_media_files(git_root)
122
+ else
123
+ # 指定模式:展开文件路径(通配符、目录)
124
+ @file_paths = expand_file_paths(@file_paths)
120
125
  end
121
126
 
122
- # 过滤存在的文件
127
+ # 5. 自动压缩文件(如果有文件需要上传)
128
+ if @file_paths.any?
129
+ @file_paths = compress_media_files(@file_paths, git_root)
130
+ end
131
+
132
+ # 6. 过滤存在的文件
123
133
  existing_files = @file_paths.select { |f| File.exist?(f) }
124
134
  if existing_files.empty?
125
- puts " 没有找到有效的 media 文件"
135
+ puts " 没有找到有效的 media 文件"
126
136
  puts " 提示: 请在项目根目录创建 JPSMedia/ 目录并放入要上传的图片或视频文件"
137
+ puts ""
127
138
  return {
128
139
  success: false,
129
140
  success_urls: [],
@@ -136,23 +147,23 @@ module Pindo
136
147
  }
137
148
  end
138
149
 
139
- puts " 准备上传 #{existing_files.size} 个文件"
140
- existing_files.each_with_index do |file, index|
141
- puts " #{index + 1}. #{File.basename(file)}"
142
- end
143
- puts
144
-
145
- # 5. 确保 PgyerHelper 已登录
150
+ # 7. 确保 PgyerHelper 已登录
146
151
  pgyer_helper = PgyerHelper.share_instace
147
152
  unless pgyer_helper.login
148
153
  raise "JPS 登录失败"
149
154
  end
150
155
 
151
- # 6. 调用 PgyerHelper start_media_upload 方法
156
+ # 8. workflow_info 提取 workflow_id
157
+ workflow_id = @workflow_info&.dig(:workflow_id) || @workflow_info&.dig('id')
158
+ unless workflow_id
159
+ raise "缺少 workflow_info,无法获取 workflow_id"
160
+ end
161
+
162
+ # 9. 调用 PgyerHelper 的 start_media_upload 方法
152
163
  upload_result = pgyer_helper.start_media_upload(
153
164
  file_paths: existing_files,
154
165
  git_commit_id: @git_commit_id,
155
- workflow_id: @workflow_id
166
+ workflow_id: workflow_id
156
167
  )
157
168
 
158
169
  # 7. 判断是否成功
@@ -195,52 +206,241 @@ module Pindo
195
206
  nil
196
207
  end
197
208
 
198
- # 获取 git HEAD 信息
199
- # @param git_root [String] git 仓库根目录
200
- # @return [Hash] 包含 :commit_id, :commit_time, :commit_desc
201
- def get_git_head_info(git_root)
202
- result = {
203
- commit_id: nil,
204
- commit_time: nil,
205
- commit_desc: nil
206
- }
209
+ # 展开文件路径(支持通配符和目录)
210
+ # 自动过滤 3 小时以内的文件,超过 3 小时的文件归档
211
+ # @param paths [Array<String>] 原始路径列表
212
+ # @return [Array<String>] 展开后的文件路径列表(3 小时以内)
213
+ def expand_file_paths(paths)
214
+ return [] if paths.nil? || paths.empty?
215
+
216
+ expanded = []
217
+
218
+ paths.each do |path|
219
+ if path.include?('*')
220
+ # 通配符展开
221
+ expanded.concat(Dir.glob(path))
222
+ elsif File.directory?(path)
223
+ # 目录:查找所有媒体文件
224
+ pattern = File.join(path, "**", "*.{#{MEDIA_EXTENSIONS.join(',')}}")
225
+ expanded.concat(Dir.glob(pattern, File::FNM_CASEFOLD))
226
+ elsif File.exist?(path)
227
+ # 单个文件
228
+ expanded << path
229
+ else
230
+ Funlog.instance.warning("文件不存在: #{path}")
231
+ end
232
+ end
207
233
 
208
- Dir.chdir(git_root) do
209
- # 获取 HEAD commit id
210
- commit_id = `git rev-parse HEAD 2>/dev/null`.strip
211
- result[:commit_id] = commit_id unless commit_id.empty?
234
+ # 去重并过滤有效文件
235
+ valid_files = expanded.uniq.select { |f| File.file?(f) }
212
236
 
213
- # 获取 commit time (ISO 8601 格式)
214
- commit_time = `git log -1 --format=%ci 2>/dev/null`.strip
215
- result[:commit_time] = commit_time unless commit_time.empty?
237
+ # 应用时间过滤(3 小时以内)并自动归档超时文件
238
+ filter_files_by_time(valid_files)
239
+ end
216
240
 
217
- # 获取 commit message (第一行)
218
- commit_desc = `git log -1 --format=%s 2>/dev/null`.strip
219
- result[:commit_desc] = commit_desc unless commit_desc.empty?
241
+ # 将超时文件移动到备份目录
242
+ # @param files [Array<String>] 要移动的文件列表
243
+ def move_expired_files_to_backup(files)
244
+ return if files.empty?
245
+
246
+ # 备份目录名:backup_YYYY-MM-DD_HHMMSS
247
+ backup_dir_name = "backup_#{Time.now.strftime('%Y-%m-%d_%H%M%S')}"
248
+
249
+ puts " 📦 归档 #{files.size} 个超时文件 → JPSMedia/#{backup_dir_name}/"
250
+
251
+ files.each do |file_path|
252
+ begin
253
+ # 在文件所在目录下创建备份子目录
254
+ parent_dir = File.dirname(file_path)
255
+ backup_dir = File.join(parent_dir, backup_dir_name)
256
+
257
+ # 创建备份目录
258
+ FileUtils.mkdir_p(backup_dir) unless Dir.exist?(backup_dir)
259
+
260
+ # 移动文件
261
+ file_name = File.basename(file_path)
262
+ dest_path = File.join(backup_dir, file_name)
263
+
264
+ # 如果目标文件已存在,添加序号避免覆盖
265
+ if File.exist?(dest_path)
266
+ ext = File.extname(file_name)
267
+ base = File.basename(file_name, ext)
268
+ counter = 1
269
+ while File.exist?(dest_path)
270
+ dest_path = File.join(backup_dir, "#{base}_#{counter}#{ext}")
271
+ counter += 1
272
+ end
273
+ end
274
+
275
+ FileUtils.mv(file_path, dest_path)
276
+ puts " ✓ #{file_name}"
277
+ rescue => e
278
+ puts " ✗ #{file_name} - #{e.message}"
279
+ end
220
280
  end
221
-
222
- result
223
281
  end
224
282
 
225
- # 在 JPSMedia 目录下查找 media 文件
283
+ # 在 JPSMedia 目录下查找 media 文件(仅查找根目录,不递归子目录)
284
+ # 自动过滤 3 小时以内的文件,超过 3 小时的文件归档
226
285
  # @param git_root [String] git 仓库根目录
227
- # @return [Array<String>] 找到的文件路径列表
286
+ # @return [Array<String>] 找到的文件路径列表(3 小时以内)
228
287
  def find_media_files(git_root)
229
288
  jps_media_dir = File.join(git_root, 'JPSMedia')
230
289
 
231
290
  unless File.directory?(jps_media_dir)
232
- puts " 未找到 JPSMedia/ 目录: #{jps_media_dir}"
291
+ puts " 未找到 JPSMedia/ 目录: #{jps_media_dir}"
233
292
  return []
234
293
  end
235
294
 
236
- puts " #{jps_media_dir} 目录查找 media 文件..."
295
+ puts " 🔍 查找 JPSMedia/ 目录下的媒体文件(3 小时以内)..."
237
296
 
238
- # 使用 glob 查找所有支持的媒体文件
239
- pattern = File.join(jps_media_dir, "**", "*.{#{MEDIA_EXTENSIONS.join(',')}}")
240
- files = Dir.glob(pattern, File::FNM_CASEFOLD)
297
+ # 只查找 JPSMedia/ 目录下的文件(不递归子目录)
298
+ pattern = File.join(jps_media_dir, "*.{#{MEDIA_EXTENSIONS.join(',')}}")
299
+ all_files = Dir.glob(pattern, File::FNM_CASEFOLD)
241
300
 
242
301
  # 过滤有效文件并去重
243
- files.uniq.select { |f| File.file?(f) }
302
+ valid_files = all_files.uniq.select { |f| File.file?(f) }
303
+
304
+ if valid_files.empty?
305
+ puts " ⚠️ 目录为空或没有支持的媒体文件"
306
+ return []
307
+ end
308
+
309
+ # 应用时间过滤(3 小时以内)并自动归档超时文件
310
+ filter_files_by_time(valid_files)
311
+ end
312
+
313
+ # 压缩媒体文件(批量压缩)
314
+ # @param files [Array<String>] 要压缩的文件列表
315
+ # @param git_root [String] git 仓库根目录
316
+ # @return [Array<String>] 压缩后的文件路径列表
317
+ def compress_media_files(files, git_root)
318
+ return [] if files.nil? || files.empty?
319
+
320
+ # 创建临时压缩输出目录: JPSMedia/compress_cli_YYYY-MM-DD_HHMMSS
321
+ # 放在 JPSMedia 目录下,避免污染 git 根目录
322
+ compress_dir_name = "compress_cli_#{Time.now.strftime('%Y-%m-%d_%H%M%S')}"
323
+ jps_media_dir = File.join(git_root, "JPSMedia")
324
+ compress_dir = File.join(jps_media_dir, compress_dir_name)
325
+
326
+ FileUtils.mkdir_p(compress_dir) unless Dir.exist?(compress_dir)
327
+
328
+ puts ""
329
+ puts " 📁 待压缩文件(共 #{files.size} 个)"
330
+ files.each_with_index do |file, index|
331
+ size_str = format_file_size(File.size(file))
332
+ file_name = File.basename(file)
333
+ puts " #{index + 1}. #{file_name} (#{size_str})"
334
+ end
335
+ puts ""
336
+
337
+ # MacCompressCliTool 路径
338
+ pindo_dir = File.expand_path(Pindoconfig.instance.pindo_dir)
339
+ compress_tool = File.join(pindo_dir, "pindo_common_config", "MacCompressCliTool")
340
+
341
+ # 检查压缩工具是否存在
342
+ unless File.exist?(compress_tool)
343
+ raise "未找到压缩工具: #{compress_tool}"
344
+ end
345
+
346
+ # 计算原始文件总大小
347
+ original_total_size = files.sum { |f| File.size(f) }
348
+
349
+ # 构建文件列表参数: -f file1.mp4,file2.png,file3.mov
350
+ file_list = files.map { |f| "\"#{f}\"" }.join(',')
351
+
352
+ # 调用压缩工具: compress-cli -s -f file1,file2,file3 -o output_dir/
353
+ # -s: 静默模式
354
+ # -f: 文件列表
355
+ # -o: 输出目录
356
+ # 使用默认参数: 分辨率等级 3 (1280x720), 质量等级 3
357
+ cmd = "#{compress_tool} -s -f #{file_list} -o \"#{compress_dir}\""
358
+
359
+ Funlog.instance.fancyinfo_start("正在压缩 #{files.size} 个文件...")
360
+ output = `#{cmd} 2>&1`
361
+ exit_code = $?.exitstatus
362
+
363
+ if exit_code != 0
364
+ Funlog.instance.fancyinfo_error("压缩失败")
365
+ puts " 错误信息: #{output.strip}" if output && !output.strip.empty?
366
+ raise "媒体文件批量压缩失败: #{output.strip}"
367
+ end
368
+
369
+ # 从临时目录获取所有压缩后的媒体文件
370
+ compressed_files = get_media_files_from_dir(compress_dir)
371
+
372
+ if compressed_files.empty?
373
+ Funlog.instance.fancyinfo_error("压缩失败:未找到压缩结果")
374
+ raise "压缩失败:未在输出目录中找到压缩结果"
375
+ end
376
+
377
+ # 计算压缩后总大小
378
+ compressed_total_size = compressed_files.sum { |f| File.size(f) }
379
+ total_ratio = ((1 - compressed_total_size.to_f / original_total_size) * 100).round(1)
380
+
381
+ # 显示压缩统计
382
+ Funlog.instance.fancyinfo_success("压缩完成:#{format_file_size(original_total_size)} → #{format_file_size(compressed_total_size)}(减少 #{total_ratio}%)")
383
+ puts ""
384
+
385
+ compressed_files
386
+ end
387
+
388
+ # 从目录获取所有媒体文件
389
+ # @param dir [String] 目录路径
390
+ # @return [Array<String>] 媒体文件路径列表
391
+ def get_media_files_from_dir(dir)
392
+ return [] unless Dir.exist?(dir)
393
+
394
+ # 使用 MEDIA_EXTENSIONS 常量构建匹配模式
395
+ pattern = File.join(dir, "*.{#{MEDIA_EXTENSIONS.join(',')}}")
396
+ Dir.glob(pattern).select { |f| File.file?(f) }.sort
397
+ end
398
+
399
+ # 格式化文件大小
400
+ # @param size [Integer] 字节数
401
+ # @return [String] 格式化后的大小
402
+ def format_file_size(size)
403
+ if size < 1024
404
+ "#{size}B"
405
+ elsif size < 1024 * 1024
406
+ "#{(size / 1024.0).round(1)}KB"
407
+ else
408
+ "#{(size / 1024.0 / 1024.0).round(1)}MB"
409
+ end
410
+ end
411
+
412
+ # 按时间过滤文件(3 小时以内)
413
+ # 超过 3 小时的文件自动移动到备份目录
414
+ # @param files [Array<String>] 文件列表
415
+ # @return [Array<String>] 3 小时以内的文件列表
416
+ def filter_files_by_time(files)
417
+ return [] if files.nil? || files.empty?
418
+
419
+ time_threshold = Time.now - (3 * 60 * 60) # 3 小时前
420
+
421
+ # 分离:3 小时以内的文件 和 超时的文件
422
+ recent_files = []
423
+ expired_files = []
424
+
425
+ files.each do |f|
426
+ mtime = File.mtime(f)
427
+ ctime = File.ctime(f)
428
+ # 取创建时间和更新时间中较新的一个
429
+ latest_time = [mtime, ctime].max
430
+
431
+ if latest_time >= time_threshold
432
+ recent_files << f
433
+ else
434
+ expired_files << f
435
+ end
436
+ end
437
+
438
+ # 移动超时文件到备份目录
439
+ if expired_files.any?
440
+ move_expired_files_to_backup(expired_files)
441
+ end
442
+
443
+ recent_files
244
444
  end
245
445
 
246
446
  end
@@ -1,5 +1,4 @@
1
1
  require 'pindo/module/task/pindo_task'
2
- require 'pindo/module/pgyer/pgyerhelper'
3
2
 
4
3
  module Pindo
5
4
  module TaskSystem
@@ -1,6 +1,7 @@
1
1
  require 'pindo/module/task/pindo_task'
2
2
  require 'pindo/module/unity/unity_helper'
3
3
  require 'pindo/module/unity/unity_proc_helper'
4
+ require 'pindo/base/git_handler'
4
5
 
5
6
  module Pindo
6
7
  module TaskSystem
@@ -10,12 +11,22 @@ module Pindo
10
11
  attr_reader :project_path, :unity_root_path
11
12
 
12
13
  def initialize(name, options = {})
13
- @project_path = options[:project_path] ? File.expand_path(options[:project_path]) : nil
14
- @unity_root_path = options[:unity_root_path] || @project_path
14
+ @project_path = options[:project_path] ? normalize_path(options[:project_path]) : nil
15
+ @unity_root_path = options[:unity_root_path] ? normalize_path(options[:unity_root_path]) : @project_path
15
16
 
16
17
  super(name, options)
17
18
  end
18
19
 
20
+ # 规范化路径辅助方法
21
+ def normalize_path(path)
22
+ return nil if path.nil?
23
+ begin
24
+ File.realpath(File.expand_path(path))
25
+ rescue
26
+ File.expand_path(path)
27
+ end
28
+ end
29
+
19
30
  # Unity 任务类型
20
31
  def self.task_type
21
32
  :unity
@@ -59,6 +70,17 @@ module Pindo
59
70
  @unity_helper ||= Pindo::Unity::UnityHelper.share_instance
60
71
  end
61
72
 
73
+ # 声明所需资源(锁住项目目录,防止并发执行)
74
+ def required_resources
75
+ # 1. Unity 资源:锁定 Unity 项目目录
76
+ # 2. Build 资源:锁定构建目录(git 仓库或项目目录)
77
+ build_lock_dir = get_build_lock_directory
78
+ (super || []) + [
79
+ { type: :unity, directory: @unity_root_path },
80
+ { type: :build, directory: build_lock_dir }
81
+ ]
82
+ end
83
+
62
84
  # 重试前清理 Unity 进程
63
85
  def before_retry
64
86
  super
@@ -67,6 +89,20 @@ module Pindo
67
89
 
68
90
  protected
69
91
 
92
+ # 获取 build 资源锁定的目录
93
+ # 优先使用 git 根目录(如果在 git 仓库内),否则使用 Unity 根目录
94
+ # 这样可以确保同一 git 仓库下的所有构建任务串行执行
95
+ def get_build_lock_directory
96
+ return @unity_root_path unless @unity_root_path
97
+
98
+ begin
99
+ git_root = Pindo::GitHandler.git_root_directory(local_repo_dir: @unity_root_path)
100
+ git_root || @unity_root_path
101
+ rescue
102
+ @unity_root_path
103
+ end
104
+ end
105
+
70
106
  # 检查 Unity 项目是否有效
71
107
  def valid_unity_project?
72
108
  # 检查是否存在 Assets 和 ProjectSettings 目录