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.
- checksums.yaml +4 -4
- data/lib/pindo/base/funlog.rb +62 -5
- data/lib/pindo/base/git_handler.rb +83 -22
- data/lib/pindo/base/output_sink.rb +69 -0
- data/lib/pindo/command/android/autobuild.rb +57 -8
- data/lib/pindo/command/appstore/autobuild.rb +10 -1
- data/lib/pindo/command/ios/autobuild.rb +59 -7
- data/lib/pindo/command/jps/media.rb +164 -13
- data/lib/pindo/command/jps/upload.rb +14 -9
- data/lib/pindo/command/unity/autobuild.rb +64 -10
- data/lib/pindo/command/utils/tag.rb +9 -1
- data/lib/pindo/command/web/autobuild.rb +59 -10
- data/lib/pindo/module/android/android_build_helper.rb +6 -7
- data/lib/pindo/module/build/git_repo_helper.rb +29 -25
- data/lib/pindo/module/pgyer/pgyerhelper.rb +174 -77
- data/lib/pindo/module/task/core/concurrent_execution_strategy.rb +237 -0
- data/lib/pindo/module/task/core/dependency_checker.rb +123 -0
- data/lib/pindo/module/task/core/execution_strategy.rb +61 -0
- data/lib/pindo/module/task/core/resource_lock_manager.rb +190 -0
- data/lib/pindo/module/task/core/serial_execution_strategy.rb +60 -0
- data/lib/pindo/module/task/core/task_executor.rb +131 -0
- data/lib/pindo/module/task/core/task_queue.rb +221 -0
- data/lib/pindo/module/task/model/build/android_build_dev_task.rb +1 -1
- data/lib/pindo/module/task/model/build/android_build_task.rb +6 -2
- data/lib/pindo/module/task/model/build/ios_build_dev_task.rb +2 -3
- data/lib/pindo/module/task/model/build/ios_build_task.rb +6 -0
- data/lib/pindo/module/task/model/build_task.rb +22 -0
- data/lib/pindo/module/task/model/git/git_commit_task.rb +11 -2
- data/lib/pindo/module/task/model/git_task.rb +6 -0
- data/lib/pindo/module/task/model/jps/jps_message_task.rb +9 -11
- data/lib/pindo/module/task/model/jps/jps_upload_media_task.rb +204 -103
- data/lib/pindo/module/task/model/jps_task.rb +0 -1
- data/lib/pindo/module/task/model/unity_task.rb +38 -2
- data/lib/pindo/module/task/output/multi_line_output_manager.rb +380 -0
- data/lib/pindo/module/task/output/multi_line_task_display.rb +185 -0
- data/lib/pindo/module/task/output/stdout_redirector.rb +95 -0
- data/lib/pindo/module/task/pindo_task.rb +133 -9
- data/lib/pindo/module/task/task_manager.rb +98 -268
- data/lib/pindo/module/task/task_reporter.rb +135 -0
- data/lib/pindo/module/task/task_resources/resource_instance.rb +90 -0
- data/lib/pindo/module/task/task_resources/resource_registry.rb +105 -0
- data/lib/pindo/module/task/task_resources/resource_type.rb +59 -0
- data/lib/pindo/module/task/task_resources/types/directory_based_resource.rb +63 -0
- data/lib/pindo/module/task/task_resources/types/global_exclusive_resource.rb +33 -0
- data/lib/pindo/module/task/task_resources/types/global_shared_resource.rb +34 -0
- data/lib/pindo/module/xcode/xcode_build_helper.rb +26 -8
- data/lib/pindo/options/groups/jps_options.rb +10 -0
- data/lib/pindo/options/groups/task_options.rb +39 -0
- data/lib/pindo/version.rb +3 -2
- metadata +20 -1
|
@@ -5,6 +5,9 @@ module Pindo
|
|
|
5
5
|
# 任务取消异常
|
|
6
6
|
class TaskCancelledException < StandardError; end
|
|
7
7
|
|
|
8
|
+
# 资源锁超时异常
|
|
9
|
+
class ResourceLockTimeout < StandardError; end
|
|
10
|
+
|
|
8
11
|
# 任务状态
|
|
9
12
|
module TaskStatus
|
|
10
13
|
PENDING = :pending # 待执行
|
|
@@ -32,13 +35,47 @@ module Pindo
|
|
|
32
35
|
# PindoTask 基类(简化版,所有任务在主线程中执行)
|
|
33
36
|
class PindoTask
|
|
34
37
|
attr_accessor :id, :name, :status, :error, :result
|
|
35
|
-
attr_reader :type, :task_key, :priority, :dependencies
|
|
38
|
+
attr_reader :type, :task_key, :priority, :dependencies
|
|
36
39
|
attr_accessor :context, :metadata
|
|
37
40
|
attr_reader :created_at, :started_at, :finished_at
|
|
38
41
|
attr_accessor :retry_count # 剩余重试次数
|
|
39
42
|
attr_reader :retry_mode, :retry_delay, :max_retry_count # max_retry_count: 初始最大重试次数
|
|
40
43
|
attr_accessor :callbacks_setup # 标记回调是否已经设置
|
|
41
44
|
attr_accessor :task_manager # TaskManager 实例(依赖注入)
|
|
45
|
+
attr_accessor :skip_count # 因资源不足被跳过的次数
|
|
46
|
+
|
|
47
|
+
# 获取任务需要的资源(子类覆盖以声明所需资源)
|
|
48
|
+
# @return [Array<Hash>] 资源规格数组
|
|
49
|
+
#
|
|
50
|
+
# 资源规格格式:
|
|
51
|
+
# { type: :xcode, directory: "/path/to/project" } # 基于目录的资源
|
|
52
|
+
# { type: :keychain } # 全局资源
|
|
53
|
+
#
|
|
54
|
+
# @example 基于目录的资源(相同目录互斥)
|
|
55
|
+
# class IOSBuildTask < PindoTask
|
|
56
|
+
# def initialize(name:, project_path:)
|
|
57
|
+
# super(name)
|
|
58
|
+
# @project_path = project_path
|
|
59
|
+
# end
|
|
60
|
+
#
|
|
61
|
+
# def required_resources
|
|
62
|
+
# [
|
|
63
|
+
# { type: :xcode, directory: @project_path },
|
|
64
|
+
# { type: :git, directory: @project_path },
|
|
65
|
+
# { type: :keychain }
|
|
66
|
+
# ]
|
|
67
|
+
# end
|
|
68
|
+
# end
|
|
69
|
+
#
|
|
70
|
+
# @example 全局资源
|
|
71
|
+
# class UploadTask < PindoTask
|
|
72
|
+
# def required_resources
|
|
73
|
+
# [{ type: :network }]
|
|
74
|
+
# end
|
|
75
|
+
# end
|
|
76
|
+
def required_resources
|
|
77
|
+
[] # 默认不需要资源
|
|
78
|
+
end
|
|
42
79
|
|
|
43
80
|
def initialize(name, options = {})
|
|
44
81
|
@id = SecureRandom.uuid
|
|
@@ -48,7 +85,6 @@ module Pindo
|
|
|
48
85
|
@priority = options[:priority] || TaskPriority::HIGH
|
|
49
86
|
@status = TaskStatus::PENDING
|
|
50
87
|
@dependencies = options[:dependencies] || []
|
|
51
|
-
@data_dependencies = options[:data_dependencies] || []
|
|
52
88
|
@context = options[:context] || {}
|
|
53
89
|
@metadata = options[:metadata] || {}
|
|
54
90
|
@error = nil
|
|
@@ -69,6 +105,7 @@ module Pindo
|
|
|
69
105
|
@retry_count = options[:retry_count] || self.class.default_retry_count
|
|
70
106
|
@max_retry_count = @retry_count # 保存初始最大重试次数
|
|
71
107
|
@retry_delay = options[:retry_delay] || self.class.default_retry_delay
|
|
108
|
+
@skip_count = 0 # 记录因资源不足被跳过的次数(用于解决饥饿问题)
|
|
72
109
|
end
|
|
73
110
|
|
|
74
111
|
# 子类必须实现的方法
|
|
@@ -196,17 +233,17 @@ module Pindo
|
|
|
196
233
|
dep_task.data_param
|
|
197
234
|
end
|
|
198
235
|
|
|
199
|
-
#
|
|
236
|
+
# 获取所有依赖任务的数据参数
|
|
200
237
|
# @return [Array<Hash>] 数据参数数组
|
|
201
238
|
def get_all_data_params
|
|
202
|
-
@
|
|
239
|
+
@dependencies.map { |task_id| get_data_param(task_id) }.compact
|
|
203
240
|
end
|
|
204
241
|
|
|
205
|
-
# 根据 task_key
|
|
242
|
+
# 根据 task_key 获取依赖任务的数据参数
|
|
206
243
|
# @param task_key [Symbol] 任务键
|
|
207
244
|
# @return [Hash, nil] 第一个匹配的任务数据参数
|
|
208
245
|
def get_data_param_by_key(task_key)
|
|
209
|
-
@
|
|
246
|
+
@dependencies.each do |task_id|
|
|
210
247
|
param = get_data_param(task_id)
|
|
211
248
|
return param if param && param[:task_key] == task_key
|
|
212
249
|
end
|
|
@@ -220,11 +257,98 @@ module Pindo
|
|
|
220
257
|
get_all_data_params.select { |param| param[:task_key] == task_key }
|
|
221
258
|
end
|
|
222
259
|
|
|
223
|
-
#
|
|
260
|
+
# 获取主数据参数(第一个依赖任务的参数)
|
|
224
261
|
# @return [Hash, nil] 主数据参数
|
|
225
262
|
def primary_data_param
|
|
226
|
-
return nil if @
|
|
227
|
-
get_data_param(@
|
|
263
|
+
return nil if @dependencies.empty?
|
|
264
|
+
get_data_param(@dependencies.first)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# ========== 动态资源管理 ==========
|
|
268
|
+
|
|
269
|
+
# 临时锁定资源(块语法,推荐使用)
|
|
270
|
+
# 在代码块执行期间锁定资源,自动释放
|
|
271
|
+
#
|
|
272
|
+
# @param resource_spec [Hash, Array<Hash>] 资源规格
|
|
273
|
+
# @param timeout [Integer] 超时时间(秒)
|
|
274
|
+
# @yield 持有资源时执行的代码块
|
|
275
|
+
# @raise [ResourceLockTimeout] 如果无法获取资源
|
|
276
|
+
# @raise [RuntimeError] 如果任务未运行或未提供代码块
|
|
277
|
+
#
|
|
278
|
+
# @example 单个资源
|
|
279
|
+
# with_resource({ type: :unity, directory: @path }) do
|
|
280
|
+
# build_unity
|
|
281
|
+
# end
|
|
282
|
+
#
|
|
283
|
+
# @example 多个资源
|
|
284
|
+
# with_resources([
|
|
285
|
+
# { type: :xcode, directory: @path },
|
|
286
|
+
# { type: :keychain }
|
|
287
|
+
# ]) do
|
|
288
|
+
# sign_ipa
|
|
289
|
+
# end
|
|
290
|
+
def with_resource(resource_spec, timeout: 30)
|
|
291
|
+
raise ArgumentError, "Block required" unless block_given?
|
|
292
|
+
raise RuntimeError, "Task not running (task_manager is nil)" unless @task_manager
|
|
293
|
+
|
|
294
|
+
resource_manager = @task_manager.resource_lock_manager
|
|
295
|
+
resource_specs = [resource_spec].flatten
|
|
296
|
+
|
|
297
|
+
# 阻塞式获取资源
|
|
298
|
+
success = resource_manager.acquire_blocking(resource_specs, @id, timeout: timeout)
|
|
299
|
+
|
|
300
|
+
unless success
|
|
301
|
+
raise ResourceLockTimeout, "无法获取资源: #{resource_specs.inspect}(超时#{timeout}秒)"
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
begin
|
|
305
|
+
yield # 执行代码块
|
|
306
|
+
ensure
|
|
307
|
+
# 自动释放临时资源
|
|
308
|
+
resource_manager.release_partial(resource_specs, @id)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# 批量临时锁定资源(with_resource 的别名)
|
|
313
|
+
# @param resource_specs [Array<Hash>] 资源规格数组
|
|
314
|
+
# @param timeout [Integer] 超时时间(秒)
|
|
315
|
+
# @yield 持有资源时执行的代码块
|
|
316
|
+
def with_resources(resource_specs, timeout: 30, &block)
|
|
317
|
+
with_resource(resource_specs, timeout: timeout, &block)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# 提前释放资源
|
|
321
|
+
# 释放 required_resources 中声明的资源,允许其他任务使用
|
|
322
|
+
#
|
|
323
|
+
# @param resource_spec [Hash, Array<Hash>] 要释放的资源规格
|
|
324
|
+
# @raise [RuntimeError] 如果任务未运行
|
|
325
|
+
#
|
|
326
|
+
# @example 释放单个资源
|
|
327
|
+
# def do_work
|
|
328
|
+
# git_pull
|
|
329
|
+
# release_resource({ type: :git, directory: @path })
|
|
330
|
+
#
|
|
331
|
+
# xcode_build # Git 已释放,其他任务可以使用
|
|
332
|
+
# end
|
|
333
|
+
#
|
|
334
|
+
# @example 释放多个资源
|
|
335
|
+
# release_resources([
|
|
336
|
+
# { type: :git, directory: @path },
|
|
337
|
+
# { type: :xcode, directory: @path }
|
|
338
|
+
# ])
|
|
339
|
+
def release_resource(resource_spec)
|
|
340
|
+
raise RuntimeError, "Task not running (task_manager is nil)" unless @task_manager
|
|
341
|
+
|
|
342
|
+
resource_manager = @task_manager.resource_lock_manager
|
|
343
|
+
resource_specs = [resource_spec].flatten
|
|
344
|
+
|
|
345
|
+
resource_manager.release_partial(resource_specs, @id)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# 批量释放资源(release_resource 的别名)
|
|
349
|
+
# @param resource_specs [Array<Hash>] 资源规格数组
|
|
350
|
+
def release_resources(resource_specs)
|
|
351
|
+
release_resource(resource_specs)
|
|
228
352
|
end
|
|
229
353
|
|
|
230
354
|
# ========== 回调方法 ==========
|
|
@@ -1,82 +1,124 @@
|
|
|
1
1
|
require 'singleton'
|
|
2
2
|
require 'pindo/base/funlog'
|
|
3
|
+
require_relative 'core/task_queue'
|
|
4
|
+
require_relative 'core/dependency_checker'
|
|
5
|
+
require_relative 'core/task_executor'
|
|
6
|
+
require_relative 'core/execution_strategy'
|
|
7
|
+
require_relative 'core/resource_lock_manager'
|
|
8
|
+
require_relative 'task_reporter'
|
|
9
|
+
require_relative 'output/multi_line_output_manager'
|
|
3
10
|
|
|
4
11
|
module Pindo
|
|
5
12
|
module TaskSystem
|
|
6
|
-
#
|
|
7
|
-
#
|
|
13
|
+
# TaskManager - 任务管理器
|
|
14
|
+
#
|
|
15
|
+
# 职责:
|
|
16
|
+
# - 提供公共 API
|
|
17
|
+
# - 组合各个模块(队列、依赖检查、执行器、报告、资源锁)
|
|
18
|
+
# - 创建执行策略
|
|
8
19
|
class TaskManager
|
|
9
20
|
include Singleton
|
|
10
21
|
|
|
22
|
+
attr_reader :queue, :dependency_checker, :reporter, :resource_lock_manager
|
|
23
|
+
|
|
11
24
|
def initialize
|
|
12
|
-
@
|
|
13
|
-
@
|
|
14
|
-
@
|
|
25
|
+
@queue = TaskQueue.new # 队列管理
|
|
26
|
+
@resource_lock_manager = ResourceLockManager.new # 资源锁管理
|
|
27
|
+
@dependency_checker = DependencyChecker.new(@queue, @resource_lock_manager) # 依赖检查
|
|
28
|
+
@reporter = TaskReporter.new(@queue) # 报告输出
|
|
29
|
+
@output_manager = nil # 输出管理器
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# ==================== 公共 API ====================
|
|
33
|
+
|
|
34
|
+
# 获取执行器(延迟初始化)
|
|
35
|
+
# @return [TaskExecutor] 任务执行器
|
|
36
|
+
def executor
|
|
37
|
+
@executor ||= TaskExecutor.new(@queue, @reporter, @resource_lock_manager, @output_manager)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# 启用输出管理系统
|
|
41
|
+
# @param options [Hash] 配置选项
|
|
42
|
+
def enable_output_management(options = {})
|
|
43
|
+
@output_manager = MultiLineOutputManager.new(
|
|
44
|
+
log_dir: options[:log_dir] || './pindo_logs',
|
|
45
|
+
max_lines_per_task: options[:max_lines_per_task] || 5,
|
|
46
|
+
max_recent_completed: options[:max_recent_completed] || 3,
|
|
47
|
+
auto_adjust: options.fetch(:auto_adjust, true)
|
|
48
|
+
)
|
|
49
|
+
# 更新执行器的输出管理器(如果已初始化)
|
|
50
|
+
@executor.output_manager = @output_manager if @executor
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# 禁用输出管理系统
|
|
54
|
+
def disable_output_management
|
|
55
|
+
@output_manager = nil
|
|
56
|
+
@executor.output_manager = nil if @executor
|
|
15
57
|
end
|
|
16
58
|
|
|
17
59
|
# 添加任务
|
|
60
|
+
# @param task [PindoTask] 任务对象
|
|
61
|
+
# @param options [Hash] 选项
|
|
62
|
+
# @return [String] 任务 ID
|
|
18
63
|
def add_task(task, options = {})
|
|
19
64
|
raise ArgumentError, "Task must be a PindoTask" unless task.is_a?(PindoTask)
|
|
20
65
|
|
|
21
|
-
# 验证任务
|
|
22
66
|
unless task.validate
|
|
23
67
|
raise ArgumentError, "Task validation failed: #{task.name}"
|
|
24
68
|
end
|
|
25
69
|
|
|
26
|
-
# 处理依赖
|
|
27
70
|
if options[:wait_for]
|
|
28
71
|
task.dependencies.concat(Array(options[:wait_for]))
|
|
29
72
|
end
|
|
30
73
|
|
|
31
|
-
@
|
|
32
|
-
# 按优先级排序
|
|
33
|
-
@pending_queue.sort_by! { |t| -t.priority }
|
|
34
|
-
|
|
35
|
-
task.id
|
|
74
|
+
@queue.add(task, sort_by_priority: true)
|
|
36
75
|
end
|
|
37
76
|
|
|
38
77
|
# 批量添加任务
|
|
78
|
+
# @param tasks [Array<PindoTask>] 任务数组
|
|
79
|
+
# @return [Array<String>] 任务 ID 数组
|
|
39
80
|
def add_tasks(tasks)
|
|
40
81
|
tasks.map { |task| add_task(task) }
|
|
41
82
|
end
|
|
42
83
|
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
84
|
+
# 开始执行任务
|
|
85
|
+
# @param options [Hash] 执行选项
|
|
86
|
+
# @option options [Symbol] :mode 执行模式 (:serial, :concurrent)
|
|
87
|
+
# @option options [Boolean] :concurrent 快捷参数
|
|
88
|
+
# @option options [Integer] :max_workers 最大工作线程数
|
|
89
|
+
def start(options = {})
|
|
90
|
+
mode = parse_execution_mode(options)
|
|
91
|
+
strategy = ExecutionStrategy.create(mode, options)
|
|
51
92
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
break
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# 没有可执行的任务,短暂休眠后重试
|
|
60
|
-
sleep(0.1)
|
|
61
|
-
next
|
|
93
|
+
# 如果配置了输出管理器,注册所有任务
|
|
94
|
+
if @output_manager
|
|
95
|
+
@queue.pending_snapshot.each do |task|
|
|
96
|
+
@output_manager.register_task(task)
|
|
62
97
|
end
|
|
63
|
-
|
|
64
|
-
# 执行任务
|
|
65
|
-
execute_task(task)
|
|
66
98
|
end
|
|
67
99
|
|
|
100
|
+
# 输出任务执行计划
|
|
101
|
+
@reporter.print_execution_plan(strategy)
|
|
102
|
+
|
|
103
|
+
# 执行策略
|
|
104
|
+
strategy.execute(self)
|
|
105
|
+
|
|
68
106
|
# 输出执行摘要
|
|
69
|
-
print_execution_summary
|
|
107
|
+
@reporter.print_execution_summary
|
|
70
108
|
end
|
|
71
109
|
|
|
72
110
|
# 获取执行报告
|
|
111
|
+
# @return [Hash] 执行报告
|
|
73
112
|
def execution_report
|
|
113
|
+
pending = @queue.pending_snapshot
|
|
114
|
+
completed = @queue.completed_snapshot
|
|
115
|
+
|
|
74
116
|
{
|
|
75
|
-
pending:
|
|
76
|
-
completed:
|
|
77
|
-
success:
|
|
78
|
-
failed:
|
|
79
|
-
tasks: (
|
|
117
|
+
pending: pending.count,
|
|
118
|
+
completed: completed.count,
|
|
119
|
+
success: completed.count { |t| t.status == TaskStatus::SUCCESS },
|
|
120
|
+
failed: completed.count { |t| t.status == TaskStatus::FAILED },
|
|
121
|
+
tasks: (pending + completed).map do |task|
|
|
80
122
|
{
|
|
81
123
|
id: task.id,
|
|
82
124
|
name: task.name,
|
|
@@ -89,260 +131,48 @@ module Pindo
|
|
|
89
131
|
}
|
|
90
132
|
end
|
|
91
133
|
|
|
92
|
-
#
|
|
134
|
+
# 清空所有队列
|
|
93
135
|
def clear_all
|
|
94
|
-
@
|
|
95
|
-
@completed_tasks.clear
|
|
96
|
-
@current_task = nil
|
|
136
|
+
@queue.clear_all
|
|
97
137
|
end
|
|
98
138
|
|
|
99
139
|
# 获取任务状态
|
|
140
|
+
# @param task_id [String] 任务 ID
|
|
141
|
+
# @return [Symbol, nil] 任务状态
|
|
100
142
|
def task_status(task_id)
|
|
101
143
|
task = find_task(task_id)
|
|
102
144
|
task&.status
|
|
103
145
|
end
|
|
104
146
|
|
|
105
147
|
# 取消任务
|
|
148
|
+
# @param task_id [String] 任务 ID
|
|
106
149
|
def cancel_task(task_id)
|
|
107
150
|
task = find_task(task_id)
|
|
108
151
|
task&.cancel
|
|
109
152
|
end
|
|
110
153
|
|
|
111
|
-
#
|
|
154
|
+
# 查找任务
|
|
112
155
|
# @param task_id [String] 任务 ID
|
|
113
156
|
# @return [PindoTask, nil] 任务对象
|
|
114
157
|
def find_task(task_id)
|
|
115
|
-
|
|
116
|
-
all_tasks << @current_task if @current_task
|
|
117
|
-
all_tasks.find { |t| t.id == task_id }
|
|
158
|
+
@queue.find(task_id)
|
|
118
159
|
end
|
|
119
160
|
|
|
120
161
|
private
|
|
121
162
|
|
|
122
|
-
#
|
|
123
|
-
def get_next_executable_task
|
|
124
|
-
task = @pending_queue.find { |t| can_execute?(t) }
|
|
125
|
-
@pending_queue.delete(task) if task
|
|
126
|
-
task
|
|
127
|
-
end
|
|
163
|
+
# ==================== 辅助方法(私有)====================
|
|
128
164
|
|
|
129
|
-
#
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return false
|
|
138
|
-
elsif dependency_check == :cancelled
|
|
139
|
-
# 依赖被取消,标记任务为取消
|
|
140
|
-
mark_task_cancelled(task, "依赖任务被取消")
|
|
141
|
-
return false
|
|
142
|
-
elsif dependency_check == :waiting
|
|
143
|
-
return false # 依赖未完成,继续等待
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# 依赖已满足,可以执行
|
|
147
|
-
true
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# 检查依赖状态
|
|
151
|
-
def check_dependencies(task)
|
|
152
|
-
return :ready if task.dependencies.empty?
|
|
153
|
-
|
|
154
|
-
task.dependencies.each do |dep_id|
|
|
155
|
-
dep_task = find_task(dep_id)
|
|
156
|
-
|
|
157
|
-
# 依赖任务不存在
|
|
158
|
-
return :failed unless dep_task
|
|
159
|
-
|
|
160
|
-
# 检查依赖任务状态
|
|
161
|
-
case dep_task.status
|
|
162
|
-
when TaskStatus::SUCCESS
|
|
163
|
-
next # 这个依赖已完成
|
|
164
|
-
when TaskStatus::FAILED
|
|
165
|
-
return :failed # 依赖失败
|
|
166
|
-
when TaskStatus::CANCELLED
|
|
167
|
-
return :cancelled # 依赖被取消
|
|
168
|
-
else
|
|
169
|
-
return :waiting # 依赖未完成
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
:ready # 所有依赖都成功完成
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
# 执行任务
|
|
177
|
-
def execute_task(task)
|
|
178
|
-
@current_task = task
|
|
179
|
-
|
|
180
|
-
# 注入 TaskManager 实例(依赖注入)
|
|
181
|
-
task.task_manager = self
|
|
182
|
-
|
|
183
|
-
# 设置任务进度回调
|
|
184
|
-
setup_task_callbacks(task)
|
|
185
|
-
|
|
186
|
-
# 显示任务开始信息
|
|
187
|
-
print_task_header(task)
|
|
188
|
-
|
|
189
|
-
begin
|
|
190
|
-
# 在主线程中执行任务
|
|
191
|
-
task.do_task
|
|
192
|
-
|
|
193
|
-
# 任务成功
|
|
194
|
-
print_task_success(task)
|
|
195
|
-
|
|
196
|
-
rescue => e
|
|
197
|
-
# 任务失败
|
|
198
|
-
print_task_failure(task, e)
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
# 将任务移到完成队列
|
|
202
|
-
@completed_tasks << task
|
|
203
|
-
@current_task = nil
|
|
204
|
-
|
|
205
|
-
# 任务完成后输出分隔线
|
|
206
|
-
print_task_footer
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
# 设置任务回调
|
|
210
|
-
def setup_task_callbacks(task)
|
|
211
|
-
# 只在第一次设置回调
|
|
212
|
-
return if task.callbacks_setup
|
|
213
|
-
|
|
214
|
-
# 主线程执行不需要进度回调
|
|
215
|
-
|
|
216
|
-
task.callbacks_setup = true
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
# 标记任务为失败
|
|
220
|
-
def mark_task_failed(task, reason)
|
|
221
|
-
@pending_queue.delete(task)
|
|
222
|
-
task.status = TaskStatus::FAILED
|
|
223
|
-
task.error = RuntimeError.new(reason)
|
|
224
|
-
@completed_tasks << task
|
|
225
|
-
|
|
226
|
-
Funlog.error("任务 #{task.name} 因#{reason}而被标记为失败")
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
# 标记任务为取消
|
|
230
|
-
def mark_task_cancelled(task, reason)
|
|
231
|
-
@pending_queue.delete(task)
|
|
232
|
-
task.status = TaskStatus::CANCELLED
|
|
233
|
-
task.error = RuntimeError.new(reason)
|
|
234
|
-
@completed_tasks << task
|
|
235
|
-
|
|
236
|
-
Funlog.warning("任务 #{task.name} 因#{reason}而被取消")
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
# 输出任务执行计划
|
|
240
|
-
def print_execution_plan
|
|
241
|
-
# 按类型分组统计
|
|
242
|
-
tasks_by_type = @pending_queue.group_by(&:type)
|
|
243
|
-
|
|
244
|
-
puts "\n"
|
|
245
|
-
puts "\e[34m" + "=" * 60
|
|
246
|
-
puts " 任务执行计划"
|
|
247
|
-
puts "=" * 60
|
|
248
|
-
|
|
249
|
-
tasks_by_type.each do |type, tasks|
|
|
250
|
-
# 直接从任务类获取显示名称
|
|
251
|
-
type_name = tasks.first&.class&.task_type_name || type.to_s.capitalize
|
|
252
|
-
puts "\n #{type_name}: #{tasks.count} 个任务"
|
|
253
|
-
|
|
254
|
-
# 显示该类型下的每个任务名称
|
|
255
|
-
tasks.each do |task|
|
|
256
|
-
puts " - #{task.name}"
|
|
257
|
-
end
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
puts "\n 总计: #{@pending_queue.count} 个任务"
|
|
261
|
-
puts "=" * 60 + "\e[0m"
|
|
262
|
-
puts "\n"
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
# 输出任务执行头部
|
|
266
|
-
def print_task_header(task)
|
|
267
|
-
puts "\n"
|
|
268
|
-
puts "\e[34m" + "*" * 60
|
|
269
|
-
puts " ▶ #{task.name}"
|
|
270
|
-
puts "*" * 60 + "\e[0m"
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
# 输出任务成功信息
|
|
274
|
-
def print_task_success(task)
|
|
275
|
-
time_str = task.execution_time ? task.execution_time.round(2) : 0
|
|
276
|
-
puts "\n"
|
|
277
|
-
puts "\e[34m" + " ✓ 任务完成: #{task.name} (耗时: #{time_str}秒)" + "\e[0m"
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
# 输出任务失败信息
|
|
281
|
-
def print_task_failure(task, error)
|
|
282
|
-
puts "\n"
|
|
283
|
-
Funlog.error("任务失败: #{task.name}")
|
|
284
|
-
Funlog.error("错误信息: #{error.message}")
|
|
285
|
-
|
|
286
|
-
# 打印堆栈信息用于调试(仅在 PINDO_DEBUG 模式下)
|
|
287
|
-
if ENV['PINDO_DEBUG'] && error.backtrace && !error.backtrace.empty?
|
|
288
|
-
puts "\n堆栈信息:"
|
|
289
|
-
error.backtrace.first(10).each do |line|
|
|
290
|
-
puts " #{line}"
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
# 输出任务底部分隔线
|
|
296
|
-
def print_task_footer
|
|
297
|
-
puts "\e[34m" + "*" * 60 + "\e[0m"
|
|
298
|
-
puts ""
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
# 输出执行摘要
|
|
302
|
-
def print_execution_summary
|
|
303
|
-
success_count = @completed_tasks.count { |t| t.status == TaskStatus::SUCCESS }
|
|
304
|
-
failed_count = @completed_tasks.count { |t| t.status == TaskStatus::FAILED }
|
|
305
|
-
cancelled_count = @completed_tasks.count { |t| t.status == TaskStatus::CANCELLED }
|
|
306
|
-
|
|
307
|
-
# 获取失败和取消的任务
|
|
308
|
-
failed_tasks = @completed_tasks.select { |t| t.status == TaskStatus::FAILED }
|
|
309
|
-
cancelled_tasks = @completed_tasks.select { |t| t.status == TaskStatus::CANCELLED }
|
|
310
|
-
|
|
311
|
-
# 计算总耗时
|
|
312
|
-
total_time = @completed_tasks.map(&:execution_time).compact.sum
|
|
313
|
-
minutes = (total_time / 60).to_i
|
|
314
|
-
seconds = (total_time % 60).to_i
|
|
315
|
-
|
|
316
|
-
puts "\n"
|
|
317
|
-
puts "\e[34m" + "=" * 60
|
|
318
|
-
puts "\e[34m" + " 任务执行完成"
|
|
319
|
-
puts "\e[34m" + "=" * 60
|
|
320
|
-
|
|
321
|
-
# 显示统计信息
|
|
322
|
-
puts "\e[34m" + " 成功: #{success_count} 个任务" + "\e[0m" if success_count > 0
|
|
323
|
-
|
|
324
|
-
if failed_count > 0
|
|
325
|
-
puts "\e[31m" + " 失败: #{failed_count} 个任务" + "\e[0m"
|
|
326
|
-
failed_tasks.each do |task|
|
|
327
|
-
puts "\e[31m" + " - #{task.name}" + "\e[0m"
|
|
328
|
-
end
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
if cancelled_count > 0
|
|
332
|
-
puts "\e[33m" + " 取消: #{cancelled_count} 个任务" + "\e[0m"
|
|
333
|
-
cancelled_tasks.each do |task|
|
|
334
|
-
puts "\e[33m" + " - #{task.name}" + "\e[0m"
|
|
335
|
-
end
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
if minutes > 0
|
|
339
|
-
puts "\e[34m" + " 总耗时: #{minutes}分#{seconds}秒" + "\e[0m"
|
|
165
|
+
# 解析执行模式
|
|
166
|
+
# @param options [Hash] 选项
|
|
167
|
+
# @return [Symbol] 执行模式
|
|
168
|
+
def parse_execution_mode(options)
|
|
169
|
+
if options[:mode]
|
|
170
|
+
options[:mode]
|
|
171
|
+
elsif options.key?(:concurrent)
|
|
172
|
+
options[:concurrent] ? :concurrent : :serial
|
|
340
173
|
else
|
|
341
|
-
|
|
174
|
+
:serial # 默认串行
|
|
342
175
|
end
|
|
343
|
-
|
|
344
|
-
puts "\e[34m" + "=" * 60 + "\e[0m"
|
|
345
|
-
puts "\n"
|
|
346
176
|
end
|
|
347
177
|
end
|
|
348
178
|
end
|