pindo 5.13.12 → 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 (50) 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 +164 -13
  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/utils/tag.rb +9 -1
  12. data/lib/pindo/command/web/autobuild.rb +59 -10
  13. data/lib/pindo/module/android/android_build_helper.rb +6 -7
  14. data/lib/pindo/module/build/git_repo_helper.rb +29 -25
  15. data/lib/pindo/module/pgyer/pgyerhelper.rb +174 -77
  16. data/lib/pindo/module/task/core/concurrent_execution_strategy.rb +237 -0
  17. data/lib/pindo/module/task/core/dependency_checker.rb +123 -0
  18. data/lib/pindo/module/task/core/execution_strategy.rb +61 -0
  19. data/lib/pindo/module/task/core/resource_lock_manager.rb +190 -0
  20. data/lib/pindo/module/task/core/serial_execution_strategy.rb +60 -0
  21. data/lib/pindo/module/task/core/task_executor.rb +131 -0
  22. data/lib/pindo/module/task/core/task_queue.rb +221 -0
  23. data/lib/pindo/module/task/model/build/android_build_dev_task.rb +1 -1
  24. data/lib/pindo/module/task/model/build/android_build_task.rb +6 -2
  25. data/lib/pindo/module/task/model/build/ios_build_dev_task.rb +2 -3
  26. data/lib/pindo/module/task/model/build/ios_build_task.rb +6 -0
  27. data/lib/pindo/module/task/model/build_task.rb +22 -0
  28. data/lib/pindo/module/task/model/git/git_commit_task.rb +11 -2
  29. data/lib/pindo/module/task/model/git_task.rb +6 -0
  30. data/lib/pindo/module/task/model/jps/jps_message_task.rb +9 -11
  31. data/lib/pindo/module/task/model/jps/jps_upload_media_task.rb +204 -103
  32. data/lib/pindo/module/task/model/jps_task.rb +0 -1
  33. data/lib/pindo/module/task/model/unity_task.rb +38 -2
  34. data/lib/pindo/module/task/output/multi_line_output_manager.rb +380 -0
  35. data/lib/pindo/module/task/output/multi_line_task_display.rb +185 -0
  36. data/lib/pindo/module/task/output/stdout_redirector.rb +95 -0
  37. data/lib/pindo/module/task/pindo_task.rb +133 -9
  38. data/lib/pindo/module/task/task_manager.rb +98 -268
  39. data/lib/pindo/module/task/task_reporter.rb +135 -0
  40. data/lib/pindo/module/task/task_resources/resource_instance.rb +90 -0
  41. data/lib/pindo/module/task/task_resources/resource_registry.rb +105 -0
  42. data/lib/pindo/module/task/task_resources/resource_type.rb +59 -0
  43. data/lib/pindo/module/task/task_resources/types/directory_based_resource.rb +63 -0
  44. data/lib/pindo/module/task/task_resources/types/global_exclusive_resource.rb +33 -0
  45. data/lib/pindo/module/task/task_resources/types/global_shared_resource.rb +34 -0
  46. data/lib/pindo/module/xcode/xcode_build_helper.rb +26 -8
  47. data/lib/pindo/options/groups/jps_options.rb +10 -0
  48. data/lib/pindo/options/groups/task_options.rb +39 -0
  49. data/lib/pindo/version.rb +3 -2
  50. metadata +20 -1
@@ -0,0 +1,131 @@
1
+ require_relative '../output/stdout_redirector'
2
+
3
+ module Pindo
4
+ module TaskSystem
5
+ # TaskExecutor - 任务执行器
6
+ #
7
+ # 职责:
8
+ # - 执行单个任务
9
+ # - 处理输出管理器模式和传统模式
10
+ # - 管理任务回调和上下文
11
+ # - 管理任务资源锁定和释放
12
+ class TaskExecutor
13
+ def initialize(queue, reporter, resource_lock_manager, output_manager = nil)
14
+ @queue = queue
15
+ @reporter = reporter
16
+ @resource_lock_manager = resource_lock_manager
17
+ @output_manager = output_manager
18
+ end
19
+
20
+ # 设置输出管理器
21
+ # @param output_manager [MultiLineOutputManager] 输出管理器
22
+ def output_manager=(output_manager)
23
+ @output_manager = output_manager
24
+ end
25
+
26
+ # 执行单个任务
27
+ # @param task [PindoTask] 任务对象
28
+ # @param task_manager [TaskManager] 任务管理器(用于依赖注入)
29
+ def execute_task_sync(task, task_manager)
30
+ begin
31
+ # 注入 TaskManager 实例(依赖注入)
32
+ task.task_manager = task_manager
33
+
34
+ # 设置任务进度回调
35
+ setup_task_callbacks(task)
36
+
37
+ # 注意:资源锁已经在 ExecutionStrategy 的 find_and_remove 中原子获取
38
+ # 这里不需要再次获取,只需要在 ensure 中释放
39
+
40
+ # 根据是否有输出管理器选择执行模式
41
+ if @output_manager
42
+ execute_task_with_output_manager(task)
43
+ else
44
+ execute_task_legacy(task)
45
+ end
46
+
47
+ # 将任务移到完成队列
48
+ @queue.mark_completed(task)
49
+ ensure
50
+ # 自动释放任务占用的资源
51
+ @resource_lock_manager.release(task.id)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ # 设置任务回调
58
+ # @param task [PindoTask] 任务对象
59
+ def setup_task_callbacks(task)
60
+ # 只在第一次设置回调
61
+ return if task.callbacks_setup
62
+
63
+ # 主线程执行不需要进度回调
64
+
65
+ task.callbacks_setup = true
66
+ end
67
+
68
+ # 使用输出管理器执行任务(并发模式)
69
+ # @param task [PindoTask] 任务对象
70
+ def execute_task_with_output_manager(task)
71
+ # 注入输出上下文到线程局部变量
72
+ Thread.current[:output_sink] = @output_manager
73
+ Thread.current[:task_id] = task.id
74
+
75
+ begin
76
+ # 启动任务显示
77
+ @output_manager.start_task(task.id)
78
+
79
+ # 使用 stdout 重定向包裹任务执行
80
+ StdoutRedirector.redirect_for_task(task.id, @output_manager) do
81
+ task.do_task
82
+ end
83
+
84
+ # 任务成功
85
+ @output_manager.success_task(task.id, execution_time: task.execution_time)
86
+
87
+ rescue => e
88
+ # 任务失败
89
+ @output_manager.error_task(task.id, e.message)
90
+ task.status = TaskStatus::FAILED
91
+ task.error = e
92
+
93
+ # 如果在调试模式,打印堆栈信息
94
+ if ENV['PINDO_DEBUG'] && e.backtrace && !e.backtrace.empty?
95
+ @output_manager.log_detail(task.id, "\n堆栈信息:")
96
+ e.backtrace.first(10).each do |line|
97
+ @output_manager.log_detail(task.id, " #{line}")
98
+ end
99
+ end
100
+
101
+ ensure
102
+ # 清理线程上下文
103
+ Thread.current[:output_sink] = nil
104
+ Thread.current[:task_id] = nil
105
+ end
106
+ end
107
+
108
+ # 传统串行模式执行任务
109
+ # @param task [PindoTask] 任务对象
110
+ def execute_task_legacy(task)
111
+ # 显示任务开始信息
112
+ @reporter.print_task_header(task)
113
+
114
+ begin
115
+ # 在主线程中执行任务
116
+ task.do_task
117
+
118
+ # 任务成功
119
+ @reporter.print_task_success(task)
120
+
121
+ rescue => e
122
+ # 任务失败
123
+ @reporter.print_task_failure(task, e)
124
+ end
125
+
126
+ # 任务完成后输出分隔线
127
+ @reporter.print_task_footer
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,221 @@
1
+ require 'thread'
2
+
3
+ module Pindo
4
+ module TaskSystem
5
+ # TaskQueue - 任务队列管理
6
+ #
7
+ # 职责:
8
+ # - 管理待执行队列、已完成队列和当前任务
9
+ # - 提供线程安全的队列操作
10
+ # - 提供原子操作 API
11
+ # - 提供高级查询功能(分组、统计)
12
+ class TaskQueue
13
+ def initialize
14
+ @pending = [] # 待执行队列
15
+ @running = [] # 正在运行队列(用于并发执行)
16
+ @completed = [] # 已完成队列
17
+ @current = nil # 当前正在执行的任务(用于串行执行)
18
+ @mutex = Mutex.new # 线程安全锁
19
+ end
20
+
21
+ # ==================== 原子操作 ====================
22
+
23
+ # 添加任务(线程安全)
24
+ # @param task [PindoTask] 任务对象
25
+ # @param sort_by_priority [Boolean] 是否按优先级排序
26
+ # @return [String] 任务 ID
27
+ def add(task, sort_by_priority: true)
28
+ @mutex.synchronize do
29
+ @pending << task
30
+ @pending.sort_by! { |t| -t.priority } if sort_by_priority
31
+ task.id
32
+ end
33
+ end
34
+
35
+ # 批量添加任务
36
+ # @param tasks [Array<PindoTask>] 任务数组
37
+ # @return [Array<String>] 任务 ID 数组
38
+ def add_all(tasks)
39
+ @mutex.synchronize do
40
+ @pending.concat(tasks)
41
+ @pending.sort_by! { |t| -t.priority }
42
+ tasks.map(&:id)
43
+ end
44
+ end
45
+
46
+ # 查找并移除任务(原子操作)
47
+ # @yield [task] 查找条件
48
+ # @yieldparam task [PindoTask] 任务对象
49
+ # @yieldreturn [Boolean] 是否匹配
50
+ # @return [PindoTask, nil] 找到的任务
51
+ def find_and_remove(&block)
52
+ @mutex.synchronize do
53
+ task = @pending.find(&block)
54
+ @pending.delete(task) if task
55
+ task
56
+ end
57
+ end
58
+
59
+ # 设置当前任务(串行执行使用)
60
+ # @param task [PindoTask] 任务对象
61
+ def set_current(task)
62
+ @mutex.synchronize { @current = task }
63
+ end
64
+
65
+ # 获取当前任务
66
+ # @return [PindoTask, nil] 当前任务
67
+ def current
68
+ @mutex.synchronize { @current }
69
+ end
70
+
71
+ # 添加正在运行的任务(并发执行使用)
72
+ # @param task [PindoTask] 任务对象
73
+ def add_running(task)
74
+ @mutex.synchronize { @running << task unless @running.include?(task) }
75
+ end
76
+
77
+ # 移除正在运行的任务(并发执行使用)
78
+ # @param task [PindoTask] 任务对象
79
+ # @return [Boolean] 是否成功移除
80
+ def remove_running(task)
81
+ @mutex.synchronize { @running.delete(task) != nil }
82
+ end
83
+
84
+ # 完成当前任务(原子操作)
85
+ # @return [PindoTask, nil] 完成的任务
86
+ def complete_current
87
+ @mutex.synchronize do
88
+ return nil unless @current
89
+ task = @current
90
+ @completed << task
91
+ @current = nil
92
+ task
93
+ end
94
+ end
95
+
96
+ # 移除待执行任务(用于标记失败/取消)
97
+ # @param task [PindoTask] 任务对象
98
+ # @return [Boolean] 是否成功移除
99
+ def remove_pending(task)
100
+ @mutex.synchronize do
101
+ @pending.delete(task) != nil
102
+ end
103
+ end
104
+
105
+ # 添加到已完成队列
106
+ # @param task [PindoTask] 任务对象
107
+ def mark_completed(task)
108
+ @mutex.synchronize do
109
+ @completed << task unless @completed.include?(task)
110
+ end
111
+ end
112
+
113
+ # ==================== 查询操作 ====================
114
+
115
+ # 返回待执行队列快照(不持锁遍历)
116
+ # @return [Array<PindoTask>] 队列副本
117
+ def pending_snapshot
118
+ @mutex.synchronize { @pending.dup }
119
+ end
120
+
121
+ # 返回已完成队列快照
122
+ # @return [Array<PindoTask>] 队列副本
123
+ def completed_snapshot
124
+ @mutex.synchronize { @completed.dup }
125
+ end
126
+
127
+ # 按类型分组待执行任务
128
+ # @return [Hash{Symbol => Array<PindoTask>}] 分组结果
129
+ def pending_grouped_by_type
130
+ @mutex.synchronize { @pending.group_by(&:type) }
131
+ end
132
+
133
+ # 按状态统计已完成任务
134
+ # @return [Hash{Symbol => Integer}] 状态计数
135
+ def completed_count_by_status
136
+ @mutex.synchronize do
137
+ @completed.group_by(&:status).transform_values(&:count)
138
+ end
139
+ end
140
+
141
+ # 查找任务(在所有队列中)
142
+ # @param task_id [String] 任务 ID
143
+ # @return [PindoTask, nil] 任务对象
144
+ def find(task_id)
145
+ @mutex.synchronize do
146
+ all_tasks = @pending + @running + @completed
147
+ all_tasks << @current if @current
148
+ all_tasks.find { |t| t.id == task_id }
149
+ end
150
+ end
151
+
152
+ # 检查待执行队列是否为空
153
+ # @return [Boolean]
154
+ def pending_empty?
155
+ @mutex.synchronize { @pending.empty? }
156
+ end
157
+
158
+ # 检查是否有当前任务
159
+ # @return [Boolean]
160
+ def has_current?
161
+ @mutex.synchronize { !@current.nil? }
162
+ end
163
+
164
+ # 获取待执行任务数量
165
+ # @return [Integer]
166
+ def pending_count
167
+ @mutex.synchronize { @pending.size }
168
+ end
169
+
170
+ # 获取已完成任务数量
171
+ # @return [Integer]
172
+ def completed_count
173
+ @mutex.synchronize { @completed.size }
174
+ end
175
+
176
+ # ==================== 队列管理 ====================
177
+
178
+ # 清空所有队列
179
+ def clear_all
180
+ @mutex.synchronize do
181
+ @pending.clear
182
+ @running.clear
183
+ @completed.clear
184
+ @current = nil
185
+ end
186
+ end
187
+
188
+ # 获取所有任务
189
+ # @return [Array<PindoTask>] 所有任务
190
+ def all_tasks
191
+ @mutex.synchronize do
192
+ tasks = @pending + @running + @completed
193
+ tasks << @current if @current
194
+ tasks
195
+ end
196
+ end
197
+
198
+ # 条件查找(在待执行队列中)
199
+ # @yield [task] 查找条件
200
+ # @yieldparam task [PindoTask] 任务对象
201
+ # @yieldreturn [Boolean] 是否匹配
202
+ # @return [PindoTask, nil] 找到的任务(不移除)
203
+ def find_pending(&block)
204
+ @mutex.synchronize do
205
+ @pending.find(&block)
206
+ end
207
+ end
208
+
209
+ # 检查所有待执行任务是否满足条件
210
+ # @yield [task] 检查条件
211
+ # @yieldparam task [PindoTask] 任务对象
212
+ # @yieldreturn [Boolean] 是否满足
213
+ # @return [Boolean]
214
+ def all_pending?(&block)
215
+ @mutex.synchronize do
216
+ @pending.all?(&block)
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
@@ -39,7 +39,7 @@ module Pindo
39
39
 
40
40
  def prepare_build
41
41
  # 校验必需的参数
42
- Dir.chdir(@project_path)
42
+ # Dir.chdir(@project_path) # Removed for thread safety
43
43
  validate_workflow_info
44
44
  update_project_config
45
45
  update_version_info
@@ -6,8 +6,12 @@ module Pindo
6
6
  # 所有 Android 平台构建任务的抽象基类
7
7
  # 子类包括:AndroidBuildDevTask、AndroidBuildAdhocTask、AndroidBuildGPlayTask
8
8
  class AndroidBuildTask < BuildTask
9
- # 空基类,仅用于类型标识
10
- # 所有具体实现由子类完成
9
+
10
+ # 声明所需资源 - 基于项目目录锁定 Gradle 构建
11
+ # 确保同一项目的 Gradle 构建任务串行执行,避免并发冲突
12
+ def required_resources
13
+ (super || []) + [{ type: :gradle, directory: @project_path }]
14
+ end
11
15
  end
12
16
  end
13
17
  end
@@ -47,7 +47,7 @@ module Pindo
47
47
  # 校验必需的参数
48
48
  validate_workflow_info
49
49
 
50
- Dir.chdir(@project_path)
50
+ # Dir.chdir(@project_path) # Removed for thread safety
51
51
  cleanup_firebase_shell
52
52
  update_project_config
53
53
  update_version_info
@@ -214,7 +214,6 @@ module Pindo
214
214
  @macos_flag = true
215
215
  end
216
216
  end
217
-
218
217
  # 更新 URL Schemes(基于最终 Bundle ID)
219
218
  def update_url_schemes_final
220
219
  workflow_packname = @workflow_info[:package_name]
@@ -226,7 +225,7 @@ module Pindo
226
225
 
227
226
  # 编译 iOS 工程
228
227
  def build_ios_project
229
- Dir.chdir(@project_path)
228
+ # Dir.chdir(@project_path) # Removed for thread safety
230
229
 
231
230
  # 使用 XcodeBuildHelper 进行构建
232
231
  ipa_file = Pindo::XcodeBuildHelper.build_project(
@@ -8,6 +8,12 @@ module Pindo
8
8
  # 子类包括:IosBuildDevTask、IosBuildAdhocTask、IosBuildAppStoreTask
9
9
  class IosBuildTask < BuildTask
10
10
 
11
+ # 声明所需资源 - 基于项目目录锁定 Xcode 构建
12
+ # 确保同一项目的 Xcode 构建任务串行执行,避免并发冲突
13
+ def required_resources
14
+ (super || []) + [{ type: :xcode, directory: @project_path }]
15
+ end
16
+
11
17
  protected
12
18
 
13
19
  # 清理 Firebase Shell Script
@@ -1,5 +1,6 @@
1
1
  require_relative '../pindo_task'
2
2
  require_relative '../task_config'
3
+ require 'pindo/base/git_handler'
3
4
 
4
5
  module Pindo
5
6
  module TaskSystem
@@ -31,6 +32,13 @@ module Pindo
31
32
  10
32
33
  end
33
34
 
35
+ # 声明所需资源 - 添加通用的 build 资源锁
36
+ # 确保同一项目(git 仓库或目录)下的所有构建任务串行执行
37
+ def required_resources
38
+ build_lock_dir = get_build_lock_directory
39
+ (super || []) + [{ type: :build, directory: build_lock_dir }]
40
+ end
41
+
34
42
  # ========== 静态工厂方法 ==========
35
43
 
36
44
  # 根据 platform 和 mode 创建对应的构建任务实例
@@ -146,6 +154,20 @@ module Pindo
146
154
 
147
155
  protected
148
156
 
157
+ # 获取 build 资源锁定的目录
158
+ # 优先使用 git 根目录(如果在 git 仓库内),否则使用项目目录
159
+ # 这样可以确保同一 git 仓库下的所有构建任务串行执行
160
+ def get_build_lock_directory
161
+ return @project_path unless @project_path
162
+
163
+ begin
164
+ git_root = Pindo::GitHandler.git_root_directory(local_repo_dir: @project_path)
165
+ git_root || @project_path
166
+ rescue
167
+ @project_path
168
+ end
169
+ end
170
+
149
171
  # 主执行流程
150
172
  def do_work
151
173
  # 准备构建
@@ -28,12 +28,16 @@ module Pindo
28
28
  # - tag_type: 创建tag类型,默认 :new
29
29
  # - tag_pre: tag前缀,默认 'v'
30
30
  # - fixed_version: 外部指定的固定版本号,如果设置则跳过版本号计算
31
+ # - process_type: 未提交文件的处理方式 ('skip', 'commit', 'reset', 'stash'),默认 'skip'
32
+ # - commit_message: 提交消息,默认 'build: 构建产生提交'
31
33
  def initialize(project_path, options = {})
32
34
  @release_branch = options[:release_branch] || 'master'
33
35
  @ver_inc = options[:ver_inc] || Pindo::VersionIncreaseType::MINI
34
36
  @tag_type = options[:tag_type] || Pindo::CreateTagType::NEW
35
37
  @tag_pre = options[:tag_pre] || 'v'
36
38
  @fixed_version = options[:fixed_version] # 外部指定的固定版本号
39
+ @process_type = options[:process_type] || 'skip' # 默认跳过
40
+ @commit_message = options[:commit_message] || 'build: 构建产生提交' # 默认提交消息
37
41
 
38
42
  @build_version = nil
39
43
  @build_number = nil
@@ -70,14 +74,19 @@ module Pindo
70
74
  git_repo_helper.check_gitignore(root_dir)
71
75
 
72
76
  # 2. 检查并处理未提交的文件(使用 GitHandler)
77
+ # process_type 已经在初始化时确定(由外部传入或默认为 skip)
73
78
  begin
74
- Pindo::GitHandler.check_uncommitted_files(project_dir: root_dir)
79
+ Pindo::GitHandler.process_need_add_files(
80
+ project_dir: root_dir,
81
+ process_type: @process_type || 'skip',
82
+ commit_message: @commit_message
83
+ )
75
84
  rescue Informative => e
76
85
  # 用户选择手动处理时,不需要重试
77
86
  if e.message.include?("请手动处理")
78
87
  @retry_count = 0
79
88
  end
80
- raise
89
+ raise Informative "GitCommitTask 任务失败!"
81
90
  end
82
91
 
83
92
  # 3. 获取当前分支
@@ -52,6 +52,12 @@ module Pindo
52
52
  true
53
53
  end
54
54
 
55
+ # 声明所需资源 - 基于仓库路径锁定 Git 操作
56
+ # 确保同一仓库的 Git 任务串行执行,避免并发冲突
57
+ def required_resources
58
+ (super || []) + [{ type: :git, directory: @git_root_path }]
59
+ end
60
+
55
61
  protected
56
62
 
57
63
  # 检查是否为 Git 仓库
@@ -102,19 +102,17 @@ module Pindo
102
102
 
103
103
  # 从依赖任务获取数据
104
104
  def fetch_data_from_dependencies
105
- # 优先从 data_dependencies 获取数据(新机制)
106
- unless @data_dependencies.empty?
107
- upload_data = get_data_param_by_key(:jps_upload)
108
-
109
- if upload_data && upload_data[:task_param]
110
- param = upload_data[:task_param]
111
- @app_version_info = param[:app_version_info] if @app_version_info.nil?
112
- @app_info_obj = param[:app_info_obj] if @app_info_obj.nil?
113
- return
114
- end
105
+ # dependencies 获取数据
106
+ upload_data = get_data_param_by_key(:jps_upload)
107
+
108
+ if upload_data && upload_data[:task_param]
109
+ param = upload_data[:task_param]
110
+ @app_version_info = param[:app_version_info] if @app_version_info.nil?
111
+ @app_info_obj = param[:app_info_obj] if @app_info_obj.nil?
112
+ return
115
113
  end
116
114
 
117
- # 兼容旧机制:从 dependencies 获取数据(向后兼容)
115
+ # 兼容旧机制:从第一个依赖获取结果
118
116
  unless @dependencies.empty?
119
117
  upload_result = get_dependency_result(@dependencies.first)
120
118