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,135 @@
1
+ require 'pindo/base/funlog'
2
+
3
+ module Pindo
4
+ module TaskSystem
5
+ # TaskReporter - 任务报告输出
6
+ #
7
+ # 职责:
8
+ # - 输出任务执行计划
9
+ # - 输出任务进度信息
10
+ # - 输出任务执行摘要
11
+ # - 格式化任务状态显示
12
+ class TaskReporter
13
+ def initialize(queue)
14
+ @queue = queue
15
+ end
16
+
17
+ # 输出任务执行计划
18
+ # @param strategy [Object] 执行策略(响应 :name 方法)
19
+ def print_execution_plan(strategy)
20
+ # 获取待执行任务的快照(按类型分组)
21
+ tasks_by_type = @queue.pending_grouped_by_type
22
+
23
+ puts "\n"
24
+ puts "\e[34m" + "=" * 60
25
+ puts " 任务执行计划 (#{strategy.name})"
26
+ puts "=" * 60
27
+
28
+ tasks_by_type.each do |type, tasks|
29
+ # 直接从任务类获取显示名称
30
+ type_name = if tasks.first&.class&.respond_to?(:task_type_name)
31
+ tasks.first.class.task_type_name
32
+ else
33
+ type.to_s.capitalize
34
+ end
35
+ puts "\n #{type_name}: #{tasks.count} 个任务"
36
+
37
+ # 显示该类型下的每个任务名称
38
+ tasks.each do |task|
39
+ puts " - #{task.name}"
40
+ end
41
+ end
42
+
43
+ puts "\n 总计: #{@queue.pending_count} 个任务"
44
+ puts "=" * 60 + "\e[0m"
45
+ puts "\n"
46
+ end
47
+
48
+ # 输出任务执行头部
49
+ # @param task [PindoTask] 任务对象
50
+ def print_task_header(task)
51
+ puts ""
52
+ puts "\e[34m▶ #{task.name}\e[0m"
53
+ puts "─" * 60
54
+ end
55
+
56
+ # 输出任务成功信息
57
+ # @param task [PindoTask] 任务对象
58
+ def print_task_success(task)
59
+ time_str = task.execution_time ? task.execution_time.round(2) : 0
60
+ puts ""
61
+ puts "\e[32m✓ #{task.name} 完成 (#{time_str}s)\e[0m"
62
+ end
63
+
64
+ # 输出任务失败信息
65
+ # @param task [PindoTask] 任务对象
66
+ # @param error [Exception] 错误对象
67
+ def print_task_failure(task, error)
68
+ puts "\n"
69
+ Funlog.error("任务失败: #{task.name}")
70
+ Funlog.error("错误信息: #{error.message}")
71
+
72
+ # 打印堆栈信息用于调试(仅在 PINDO_DEBUG 模式下)
73
+ if ENV['PINDO_DEBUG'] && error.backtrace && !error.backtrace.empty?
74
+ puts "\n堆栈信息:"
75
+ error.backtrace.first(10).each do |line|
76
+ puts " #{line}"
77
+ end
78
+ end
79
+ end
80
+
81
+ # 输出任务底部分隔线
82
+ def print_task_footer
83
+ # 简化输出,不需要额外的分隔线
84
+ end
85
+
86
+ # 输出执行摘要
87
+ def print_execution_summary
88
+ completed = @queue.completed_snapshot
89
+ success_count = completed.count { |t| t.status == TaskStatus::SUCCESS }
90
+ failed_count = completed.count { |t| t.status == TaskStatus::FAILED }
91
+ cancelled_count = completed.count { |t| t.status == TaskStatus::CANCELLED }
92
+
93
+ # 获取失败和取消的任务
94
+ failed_tasks = completed.select { |t| t.status == TaskStatus::FAILED }
95
+ cancelled_tasks = completed.select { |t| t.status == TaskStatus::CANCELLED }
96
+
97
+ # 计算总耗时
98
+ total_time = completed.map(&:execution_time).compact.sum
99
+ minutes = (total_time / 60).to_i
100
+ seconds = (total_time % 60).to_i
101
+
102
+ puts "\n"
103
+ puts "\e[34m" + "=" * 60
104
+ puts "\e[34m" + " 任务执行完成"
105
+ puts "\e[34m" + "=" * 60
106
+
107
+ # 显示统计信息
108
+ puts "\e[34m" + " 成功: #{success_count} 个任务" + "\e[0m" if success_count > 0
109
+
110
+ if failed_count > 0
111
+ puts "\e[31m" + " 失败: #{failed_count} 个任务" + "\e[0m"
112
+ failed_tasks.each do |task|
113
+ puts "\e[31m" + " - #{task.name}" + "\e[0m"
114
+ end
115
+ end
116
+
117
+ if cancelled_count > 0
118
+ puts "\e[33m" + " 取消: #{cancelled_count} 个任务" + "\e[0m"
119
+ cancelled_tasks.each do |task|
120
+ puts "\e[33m" + " - #{task.name}" + "\e[0m"
121
+ end
122
+ end
123
+
124
+ if minutes > 0
125
+ puts "\e[34m" + " 总耗时: #{minutes}分#{seconds}秒" + "\e[0m"
126
+ else
127
+ puts "\e[34m" + " 总耗时: #{seconds}秒" + "\e[0m"
128
+ end
129
+
130
+ puts "\e[34m" + "=" * 60 + "\e[0m"
131
+ puts "\n"
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,90 @@
1
+ require_relative 'resource_type'
2
+
3
+ module Pindo
4
+ module TaskSystem
5
+ module TaskResources
6
+ # 资源实例 - 表示一个具体的资源占用
7
+ #
8
+ # 职责:
9
+ # - 包含资源类型和上下文信息
10
+ # - 提供资源冲突检查接口
11
+ #
12
+ # 示例:
13
+ # xcode_resource = ResourceInstance.new(xcode_type, { directory: "/path/to/project" })
14
+ # network_resource = ResourceInstance.new(network_type, {})
15
+ class ResourceInstance
16
+ attr_reader :type, :context
17
+
18
+ # @param type [ResourceType] 资源类型对象
19
+ # @param context [Hash] 上下文信息(如 { directory: "/path/to/project" })
20
+ def initialize(type, context = {})
21
+ raise ArgumentError, "type must be a ResourceType" unless type.is_a?(ResourceType)
22
+ @type = type
23
+ @context = context || {}
24
+ end
25
+
26
+ # 检查是否与另一个资源实例冲突
27
+ # @param other [ResourceInstance] 另一个资源实例
28
+ # @return [Boolean] 是否冲突
29
+ def conflicts_with?(other)
30
+ return false unless other.is_a?(ResourceInstance)
31
+ return false unless @type.name == other.type.name # 不同类型不冲突
32
+
33
+ # 委托给资源类型检查冲突逻辑
34
+ @type.conflicts?(self, other)
35
+ end
36
+
37
+ # 检查是否匹配另一个资源实例(用于释放资源时查找)
38
+ # @param other [ResourceInstance] 另一个资源实例
39
+ # @return [Boolean] 是否匹配
40
+ def matches?(other)
41
+ return false unless other.is_a?(ResourceInstance)
42
+ return false unless @type.name == other.type.name
43
+
44
+ # 上下文必须完全匹配
45
+ @context == other.context
46
+ end
47
+
48
+ # 检查是否匹配资源规格(Hash 格式)
49
+ # @param spec [Hash] 资源规格,如 { type: :xcode, directory: "/path" }
50
+ # @return [Boolean] 是否匹配
51
+ def matches_spec?(spec)
52
+ return false unless spec.is_a?(Hash)
53
+ return false unless spec[:type] == @type.name
54
+
55
+ # 提取上下文(除了 :type 之外的所有键)
56
+ spec_context = spec.reject { |k, _| k == :type }
57
+ @context == spec_context
58
+ end
59
+
60
+ # 资源实例的字符串表示
61
+ # @return [String]
62
+ def to_s
63
+ if @context.empty?
64
+ "#{@type.name}"
65
+ else
66
+ "#{@type.name}(#{@context.inspect})"
67
+ end
68
+ end
69
+
70
+ # 资源实例的详细描述
71
+ # @return [String]
72
+ def inspect
73
+ "#<ResourceInstance type=#{@type.name} strategy=#{@type.strategy} context=#{@context.inspect}>"
74
+ end
75
+
76
+ # 获取目录路径(如果有)
77
+ # @return [String, nil]
78
+ def directory
79
+ @context[:directory]
80
+ end
81
+
82
+ # 检查是否有目录上下文
83
+ # @return [Boolean]
84
+ def has_directory?
85
+ !@context[:directory].nil?
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,105 @@
1
+ require_relative 'types/directory_based_resource'
2
+ require_relative 'types/global_exclusive_resource'
3
+ require_relative 'types/global_shared_resource'
4
+
5
+ module Pindo
6
+ module TaskSystem
7
+ module TaskResources
8
+ # 资源类型注册表
9
+ #
10
+ # 职责:
11
+ # - 管理所有已注册的资源类型
12
+ # - 提供资源类型查询接口
13
+ # - 注册内置资源类型
14
+ class ResourceRegistry
15
+ def initialize
16
+ @types = {}
17
+ register_builtin_types
18
+ end
19
+
20
+ # 注册资源类型
21
+ # @param name [Symbol] 资源名称
22
+ # @param type [ResourceType] 资源类型对象
23
+ def register(name, type)
24
+ raise ArgumentError, "type must be a ResourceType" unless type.is_a?(ResourceType)
25
+ @types[name] = type
26
+ end
27
+
28
+ # 获取资源类型
29
+ # @param name [Symbol] 资源名称
30
+ # @return [ResourceType] 资源类型对象
31
+ # @raise [ArgumentError] 如果资源类型不存在
32
+ def get(name)
33
+ @types[name] or raise ArgumentError, "Unknown resource type: #{name}"
34
+ end
35
+
36
+ # 检查资源类型是否已注册
37
+ # @param name [Symbol] 资源名称
38
+ # @return [Boolean]
39
+ def registered?(name)
40
+ @types.key?(name)
41
+ end
42
+
43
+ # 获取所有已注册的资源名称
44
+ # @return [Array<Symbol>]
45
+ def all_names
46
+ @types.keys
47
+ end
48
+
49
+ # 获取所有资源类型对象
50
+ # @return [Array<ResourceType>]
51
+ def all_types
52
+ @types.values
53
+ end
54
+
55
+ private
56
+
57
+ # 注册内置资源类型
58
+ def register_builtin_types
59
+ # ========================================
60
+ # 基于目录的资源(相同目录互斥,不同目录可并行)
61
+ # ========================================
62
+
63
+ # 通用构建资源(锁定 git 仓库或项目目录)
64
+ # 确保同一项目下的所有构建任务串行执行
65
+ register(:build, DirectoryBasedResourceType.new(:build))
66
+
67
+ # Xcode 构建工具
68
+ register(:xcode, DirectoryBasedResourceType.new(:xcode))
69
+
70
+ # Git 版本控制
71
+ register(:git, DirectoryBasedResourceType.new(:git))
72
+
73
+ # Unity 编辑器
74
+ register(:unity, DirectoryBasedResourceType.new(:unity))
75
+
76
+ # Gradle 构建工具
77
+ register(:gradle, DirectoryBasedResourceType.new(:gradle))
78
+
79
+ # ========================================
80
+ # 全局互斥资源(任何时候只能一个任务使用)
81
+ # ========================================
82
+
83
+ # Keychain 操作
84
+ register(:keychain, GlobalExclusiveResourceType.new(:keychain))
85
+
86
+ # 证书操作
87
+ register(:certificate, GlobalExclusiveResourceType.new(:certificate))
88
+
89
+ # ========================================
90
+ # 全局共享资源(无限制并发)
91
+ # ========================================
92
+
93
+ # 网络操作
94
+ register(:network, GlobalSharedResourceType.new(:network))
95
+
96
+ # App Store Connect API
97
+ register(:appstore_connect, GlobalSharedResourceType.new(:appstore_connect))
98
+
99
+ # 文件系统操作
100
+ register(:file_system, GlobalSharedResourceType.new(:file_system))
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,59 @@
1
+ module Pindo
2
+ module TaskSystem
3
+ module TaskResources
4
+ # 资源类型基类
5
+ #
6
+ # 职责:
7
+ # - 定义资源的冲突检查逻辑
8
+ # - 子类实现不同的冲突策略
9
+ #
10
+ # 资源冲突策略:
11
+ # - :directory_based - 基于目录的资源(相同目录互斥,不同目录可并行)
12
+ # - :global_exclusive - 全局互斥资源(任何时候只能一个任务使用)
13
+ # - :global_shared - 全局共享资源(无限制并发)
14
+ class ResourceType
15
+ attr_reader :name, :strategy
16
+
17
+ # @param name [Symbol] 资源名称
18
+ # @param strategy [Symbol] 冲突策略
19
+ def initialize(name, strategy)
20
+ @name = name
21
+ @strategy = strategy
22
+ end
23
+
24
+ # 检查两个资源实例是否冲突(抽象方法,子类必须实现)
25
+ # @param instance1 [ResourceInstance] 资源实例1
26
+ # @param instance2 [ResourceInstance] 资源实例2
27
+ # @return [Boolean] 是否冲突
28
+ # @raise [NotImplementedError] 如果子类未实现
29
+ def conflicts?(instance1, instance2)
30
+ raise NotImplementedError, "#{self.class} must implement #conflicts?"
31
+ end
32
+
33
+ # 资源类型的字符串表示
34
+ # @return [String]
35
+ def to_s
36
+ "#{@name}(#{@strategy})"
37
+ end
38
+
39
+ # 检查是否为基于目录的资源
40
+ # @return [Boolean]
41
+ def directory_based?
42
+ @strategy == :directory_based
43
+ end
44
+
45
+ # 检查是否为全局互斥资源
46
+ # @return [Boolean]
47
+ def global_exclusive?
48
+ @strategy == :global_exclusive
49
+ end
50
+
51
+ # 检查是否为全局共享资源
52
+ # @return [Boolean]
53
+ def global_shared?
54
+ @strategy == :global_shared
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,63 @@
1
+ require_relative '../resource_type'
2
+
3
+ module Pindo
4
+ module TaskSystem
5
+ module TaskResources
6
+ # 基于目录的资源类型
7
+ #
8
+ # 特性:
9
+ # - 相同目录的资源互斥(不能并发)
10
+ # - 不同目录的资源可以并行
11
+ #
12
+ # 适用资源:
13
+ # - Xcode(相同项目目录互斥)
14
+ # - Git(相同仓库目录互斥)
15
+ # - Unity(相同项目目录互斥)
16
+ # - Gradle(相同项目目录互斥)
17
+ class DirectoryBasedResourceType < ResourceType
18
+ # @param name [Symbol] 资源名称
19
+ def initialize(name)
20
+ super(name, :directory_based)
21
+ end
22
+
23
+ # 检查两个资源实例是否冲突
24
+ # 规则:
25
+ # 1. 如果任一资源未指定目录,视为全局冲突(为了安全起见,防止漏锁)
26
+ # 2. 相同目录冲突
27
+ # 3. 不同目录不冲突
28
+ #
29
+ # @param instance1 [ResourceInstance]
30
+ # @param instance2 [ResourceInstance]
31
+ # @return [Boolean]
32
+ def conflicts?(instance1, instance2)
33
+ dir1 = instance1.context[:directory]
34
+ dir2 = instance2.context[:directory]
35
+
36
+ # 修改:如果任一资源没有指定目录,视为冲突(Fail-safe 策略)
37
+ # 这确保了如果某个任务忘记或者无法指定目录,它会独占该类型资源,而不是并发执行
38
+ return true if dir1.nil? || dir2.nil?
39
+
40
+ # 规范化路径后比较
41
+ normalize_path(dir1) == normalize_path(dir2)
42
+ end
43
+
44
+ private
45
+
46
+ # 规范化路径(展开相对路径、软链接等)
47
+ # @param path [String] 原始路径
48
+ # @return [String] 规范化后的绝对路径
49
+ def normalize_path(path)
50
+ return nil if path.nil?
51
+
52
+ # 尝试获取真实路径(解决软链接问题)
53
+ begin
54
+ File.realpath(File.expand_path(path))
55
+ rescue
56
+ # 如果文件不存在,回退到 expand_path
57
+ File.expand_path(path)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,33 @@
1
+ require_relative '../resource_type'
2
+
3
+ module Pindo
4
+ module TaskSystem
5
+ module TaskResources
6
+ # 全局互斥资源类型
7
+ #
8
+ # 特性:
9
+ # - 任何时候只能有一个任务占用
10
+ # - 任意两个实例都冲突
11
+ #
12
+ # 适用资源:
13
+ # - Keychain(系统级密钥链,全局唯一)
14
+ # - Certificate(证书操作,全局互斥)
15
+ class GlobalExclusiveResourceType < ResourceType
16
+ # @param name [Symbol] 资源名称
17
+ def initialize(name)
18
+ super(name, :global_exclusive)
19
+ end
20
+
21
+ # 检查两个资源实例是否冲突
22
+ # 规则:永远冲突(全局互斥)
23
+ #
24
+ # @param instance1 [ResourceInstance]
25
+ # @param instance2 [ResourceInstance]
26
+ # @return [Boolean] 永远返回 true
27
+ def conflicts?(instance1, instance2)
28
+ true # 全局互斥,任意两个实例都冲突
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ require_relative '../resource_type'
2
+
3
+ module Pindo
4
+ module TaskSystem
5
+ module TaskResources
6
+ # 全局共享资源类型
7
+ #
8
+ # 特性:
9
+ # - 无限制并发
10
+ # - 任意两个实例都不冲突
11
+ #
12
+ # 适用资源:
13
+ # - Network(网络操作,可以并发)
14
+ # - AppStore Connect(API 调用,可以并发)
15
+ # - File System(文件系统读取,可以并发)
16
+ class GlobalSharedResourceType < ResourceType
17
+ # @param name [Symbol] 资源名称
18
+ def initialize(name)
19
+ super(name, :global_shared)
20
+ end
21
+
22
+ # 检查两个资源实例是否冲突
23
+ # 规则:永不冲突(全局共享)
24
+ #
25
+ # @param instance1 [ResourceInstance]
26
+ # @param instance2 [ResourceInstance]
27
+ # @return [Boolean] 永远返回 false
28
+ def conflicts?(instance1, instance2)
29
+ false # 全局共享,永不冲突
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -452,14 +452,18 @@ module Pindo
452
452
  # 修复 Xcode 16 链接器兼容性问题
453
453
  fix_xcode16_linker_flags(project_dir: project_dir)
454
454
 
455
- # 清理旧的 build 目录
455
+ # 清理旧的 build 目录 (使用绝对路径)
456
456
  build_dir = File.join(project_dir, "build")
457
457
  FileUtils.rm_rf(build_dir) if File.exist?(build_dir)
458
+
459
+ # 手动创建输出目录
460
+ FileUtils.mkdir_p(build_dir)
458
461
 
459
- # 获取构建参数
462
+ # 获取构建参数 (传入 output_directory)
460
463
  gym_options = get_gym_build_options(
461
464
  project_fullname: project_fullname,
462
- icloud_id: icloud_id
465
+ icloud_id: icloud_id,
466
+ output_directory: build_dir
463
467
  )
464
468
 
465
469
  # 执行构建
@@ -468,7 +472,7 @@ module Pindo
468
472
  Gym::Manager.new.work(config)
469
473
 
470
474
  # 查找生成的 IPA 文件
471
- build_path = File.join(project_dir, "build", "*.ipa")
475
+ build_path = File.join(build_dir, "*.ipa")
472
476
  ipa_file = Dir.glob(build_path).max_by { |f| File.mtime(f) }
473
477
 
474
478
  ipa_file
@@ -477,14 +481,20 @@ module Pindo
477
481
  # 获取 Gym 构建参数
478
482
  # @param project_fullname [String] 工程文件完整路径
479
483
  # @param icloud_id [String, nil] iCloud ID(可选)
484
+ # @param output_directory [String] 输出目录绝对路径
480
485
  # @return [Hash] Gym 构建参数
481
- def get_gym_build_options(project_fullname:, icloud_id: nil)
486
+ def get_gym_build_options(project_fullname:, icloud_id: nil, output_directory: nil)
482
487
  if project_fullname.nil?
483
488
  raise "请指定要编译的工程"
484
489
  end
485
490
 
486
491
  project_path = File.dirname(project_fullname)
487
492
  proj_name = File.basename(project_fullname, ".xcodeproj")
493
+
494
+ # 使用绝对路径
495
+ project_fullname_abs = File.expand_path(project_fullname)
496
+ project_path_abs = File.dirname(project_fullname_abs)
497
+
488
498
  project_obj = Xcodeproj::Project.open(project_fullname)
489
499
 
490
500
  project_build_platform = project_obj.root_object.build_configuration_list.get_setting("SDKROOT")["Release"]
@@ -533,14 +543,22 @@ module Pindo
533
543
  app_ipaname_temp = app_ipaname_temp.gsub(/ /, '')
534
544
  app_ipaname_temp = app_ipaname_temp.gsub(/\'/, '')
535
545
 
546
+ # 确保输出目录是绝对路径
547
+ output_directory ||= File.join(project_path_abs, "build")
548
+ output_directory = File.expand_path(output_directory)
549
+
536
550
  # 构建基本参数
537
551
  values = {
538
- project: "#{proj_name}.xcodeproj",
552
+ # 虽然 gym 支持 absolute path,但是为了保险起见,如果提供了 abs path project_fullname,
553
+ # 我们这里还是传 relative 给 gym,但是我们在调用 gym 之前不 chdir。
554
+ # 实际上 gym 的 behavior: 如果 project 是 relative,它相对于 cwd。
555
+ # 所以我们必须给 gym 传入 ABSOLUTE PATH 的 project 才能避免 chdir 问题。
556
+ project: project_fullname_abs,
539
557
  scheme: "#{proj_name}",
540
558
  configuration: "Release",
541
559
  clean: true,
542
560
  export_method: build_type,
543
- output_directory: "./build/",
561
+ output_directory: output_directory,
544
562
  output_name: "#{app_ipaname_temp}.ipa"
545
563
  }
546
564
 
@@ -552,7 +570,7 @@ module Pindo
552
570
 
553
571
  # 如果使用 CocoaPods
554
572
  if File.exist?(File.join(project_path, "Podfile"))
555
- values[:workspace] = "#{proj_name}.xcworkspace"
573
+ values[:workspace] = File.join(project_path_abs, "#{proj_name}.xcworkspace")
556
574
  values[:project] = nil
557
575
  end
558
576
 
@@ -69,6 +69,16 @@ module Pindo
69
69
  env_name: 'PINDO_CERT_ID',
70
70
  optional: true,
71
71
  example: 'pindo jps resign --certid=com.test.bundleid'
72
+ ),
73
+
74
+ media: OptionItem.new(
75
+ key: :media,
76
+ name: '上传媒体附件',
77
+ description: '上传完成后,从 JPSMedia/ 目录上传媒体附件(图片、视频)',
78
+ type: OptionItem::Boolean,
79
+ env_name: 'PINDO_UPLOAD_MEDIA',
80
+ optional: true,
81
+ example: 'pindo ios autobuild --media'
72
82
  )
73
83
  }
74
84
  end
@@ -0,0 +1,39 @@
1
+ require 'pindo/options/core/option_item'
2
+ require 'pindo/options/groups/option_group'
3
+
4
+ module Pindo
5
+ module Options
6
+ # Task 任务参数组
7
+ # 定义任务系统特有的参数
8
+ module TaskOptions
9
+ extend OptionGroup
10
+
11
+ def self.all_options
12
+ @all_options ||= {
13
+ multi: OptionItem.new(
14
+ key: :multi,
15
+ name: '并发执行',
16
+ description: '使用并发任务系统执行构建(提高构建速度)',
17
+ type: OptionItem::Boolean,
18
+ env_name: 'PINDO_TASK_MULTI',
19
+ default_value: false,
20
+ optional: true,
21
+ cache: false, # 不缓存该参数,每次都需要显式指定
22
+ example: 'pindo unity autobuild --multi'
23
+ ),
24
+
25
+ select: OptionItem.new(
26
+ key: :select,
27
+ name: '选择提交',
28
+ description: '交互式选择历史 git commit(而不是使用 HEAD)',
29
+ type: OptionItem::Boolean,
30
+ env_name: 'PINDO_TASK_SELECT',
31
+ default_value: false,
32
+ optional: true,
33
+ example: 'pindo jps media --select'
34
+ )
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
data/lib/pindo/version.rb CHANGED
@@ -6,13 +6,13 @@ require 'time'
6
6
 
7
7
  module Pindo
8
8
 
9
- VERSION = "5.13.12"
9
+ VERSION = "5.13.13"
10
10
 
11
11
  class VersionCheck
12
12
  RUBYGEMS_API = 'https://rubygems.org/api/v1/gems/pindo.json'
13
13
  VERSION_INFO_FILE = File.expand_path('~/.pindo/version_info.yml')
14
14
  CHECK_INTERVAL = 5 * 60 * 60 # 5小时检查一次
15
- CONFIG_MIN_VERSION = '1.1.0' # 硬编码的配置版本要求
15
+ CONFIG_MIN_VERSION = '1.2.0' # 硬编码的配置版本要求
16
16
 
17
17
  class << self
18
18
  # 主版本检查方法(保持向后兼容)
@@ -187,6 +187,7 @@ module Pindo
187
187
  # 创建 HTTP 连接,禁用代理
188
188
  http = Net::HTTP.new(uri.host, uri.port, nil, nil)
189
189
  http.use_ssl = true
190
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE # 忽略 SSL 证书验证
190
191
  http.open_timeout = 3 # 3秒超时
191
192
  http.read_timeout = 3
192
193