pindo 5.18.20 → 5.19.2

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/base/git_handler.rb +38 -60
  3. data/lib/pindo/command/android/autobuild.rb +8 -4
  4. data/lib/pindo/command/ios/autobuild.rb +8 -4
  5. data/lib/pindo/command/repo/create.rb +1 -1
  6. data/lib/pindo/command/repo/search.rb +1 -1
  7. data/lib/pindo/command/unity/autobuild.rb +1 -1
  8. data/lib/pindo/command/utils/installskills.rb +2 -2
  9. data/lib/pindo/command/utils/renewproj.rb +0 -1
  10. data/lib/pindo/command/web/autobuild.rb +8 -4
  11. data/lib/pindo/module/android/android_build_helper.rb +39 -16
  12. data/lib/pindo/module/android/gradle_helper.rb +8 -14
  13. data/lib/pindo/module/cert/cert_helper.rb +2 -2
  14. data/lib/pindo/module/pgyer/pgyerhelper.rb +18 -20
  15. data/lib/pindo/module/task/model/build/android_build_dev_task.rb +1 -37
  16. data/lib/pindo/module/task/model/build/android_build_task.rb +40 -0
  17. data/lib/pindo/module/task/model/build/ios_build_adhoc_task.rb +5 -5
  18. data/lib/pindo/module/task/model/build/ios_build_appstore_task.rb +5 -5
  19. data/lib/pindo/module/task/model/build/ios_build_dev_task.rb +2 -2
  20. data/lib/pindo/module/task/model/build_task.rb +2 -0
  21. data/lib/pindo/module/task/model/git/git_commit_task.rb +11 -1
  22. data/lib/pindo/module/task/model/git/git_tag_task.rb +2 -2
  23. data/lib/pindo/module/task/model/jps/jps_workflow_message_task.rb +8 -10
  24. data/lib/pindo/module/task/pindo_task.rb +2 -2
  25. data/lib/pindo/module/task/task_manager.rb +14 -6
  26. data/lib/pindo/module/unity/nuget_helper.rb +5 -20
  27. data/lib/pindo/module/utils/git_repo_helper.rb +40 -45
  28. data/lib/pindo/module/xcode/ipa_resign_helper.rb +33 -62
  29. data/lib/pindo/options/groups/unity_options.rb +1 -1
  30. data/lib/pindo/version.rb +1 -1
  31. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 004b03010300629da1e55b7c6b34eada7cd76e55247deb8f76865d7446ef3572
4
- data.tar.gz: 44757caa7ef3e6ae5253bd17d4a0c35ebe0c4cb6120d7ee2840c1a010b6800d2
3
+ metadata.gz: 68c6a51d50dad94212ebaf6360ce8a9f6a196b30aaf98a98e2114875ed6c0984
4
+ data.tar.gz: 34d0ca5bf09f50c5c3d2d25fa48cf761f526bd15381c57fca95c6b73567a41c1
5
5
  SHA512:
6
- metadata.gz: 05bd2c5264163a1ec12aa545e165f2bee0d832e2c038cd30d506c932842c024f9664d6ecd51c7aa80a71f061ffcaf4747a56caac6223b2c34fe4887845681fc6
7
- data.tar.gz: 03d477b345f080c1dfe28e5367786d2147029e86aac185c4aa698b8d17acc98064bee17958060be7d1f44ebb5a41e2790a228a9a4b4699d5d9e2bb6f99709a1d
6
+ metadata.gz: 7318ecbfadf6b0bee7febebff7f35e289984049f37b95760722abed6a59cc497b58f3fcb6786566a0f0ea71688da5434fc1c139cd68905f8b24dd326628a2187
7
+ data.tar.gz: c08a9a8fc36bdbe9850bff8601c1d4da3a371466fa2c7b95c24cdcdcbe9fa4e059d12e3cd692a7491c15f6faa7d5cbaac3f73b855b0b69470790a5c72e184f56
@@ -86,8 +86,7 @@ module Pindo
86
86
  end
87
87
 
88
88
  def add_branch(local_repo_dir: nil, branch: nil)
89
-
90
- current=Dir.pwd
89
+ # 命令已带 -C,无需 Dir.chdir(并发模式下 chdir 会污染全局 cwd)
91
90
  result = false
92
91
 
93
92
  if !local_branch_exists?(local_repo_dir: local_repo_dir, branch: branch)
@@ -97,13 +96,11 @@ module Pindo
97
96
 
98
97
  git_remote!(local_repo_dir, %W(-C #{local_repo_dir} push origin #{branch}:#{branch}))
99
98
 
100
- Dir.chdir(current)
101
99
  return result
102
100
  end
103
101
 
104
102
  def remove_branch(local_repo_dir: nil, branch: nil)
105
- current=Dir.pwd
106
-
103
+ # 命令已带 -C,无需 Dir.chdir(并发模式下 chdir 会污染全局 cwd)
107
104
  result = false
108
105
  if File.exist?(local_repo_dir)
109
106
  current_branch = git!(%W(-C #{local_repo_dir} rev-parse --abbrev-ref HEAD)).strip
@@ -120,51 +117,40 @@ module Pindo
120
117
  else
121
118
  result = false
122
119
  end
123
- Dir.chdir(current)
124
120
  return result
125
121
  end
126
122
 
127
123
 
128
124
  def local_branch_exists?(local_repo_dir: nil, branch: nil)
129
- current=Dir.pwd
125
+ # 命令改带 -C,去掉 Dir.chdir(并发模式下 chdir 会污染全局 cwd)
130
126
  result = false
131
127
 
132
128
  if File.exist?(local_repo_dir)
133
- Dir.chdir(local_repo_dir)
134
-
135
- res_data = Executable.capture_command('git', %W(rev-parse --verify #{branch}), :capture => :out)
136
- # puts "=====1"
137
- # puts res_data
138
- # res_data = git!(%W(-C #{local_repo_dir} --no-pager branch --list origin/#{branch} --no-color -r))
129
+ res_data = Executable.capture_command('git', %W(-C #{local_repo_dir} rev-parse --verify #{branch}), :capture => :out)
139
130
  result = !res_data.nil? && !res_data.empty?
140
131
  else
141
132
  result = false
142
133
  end
143
- Dir.chdir(current)
144
134
  return result
145
135
  end
146
136
 
147
137
  def remote_branch_exists?(local_repo_dir: nil, branch: nil)
148
- current=Dir.pwd
138
+ # 命令已带 -C,无需 Dir.chdir(并发模式下 chdir 会污染全局 cwd)
149
139
  result = false
150
140
  if File.exist?(local_repo_dir)
151
- Dir.chdir(local_repo_dir)
152
141
  res_data = git_remote!(local_repo_dir, %W(-C #{local_repo_dir} ls-remote --heads origin refs/heads/#{branch}))
153
142
  result = !res_data.nil? && !res_data.empty?
154
143
  else
155
144
  result = false
156
145
  end
157
- Dir.chdir(current)
158
146
  return result
159
147
  end
160
148
 
161
149
 
162
150
  def add_tag(local_repo_dir: nil, tag_name: nil)
163
-
164
- current=Dir.pwd
151
+ # 命令已带 -C,无需 Dir.chdir(并发模式下 chdir 会污染全局 cwd)
165
152
  result = false
166
153
  if File.exist?(local_repo_dir)
167
- Dir.chdir(local_repo_dir)
168
154
  if !local_tag_exists?(local_repo_dir: local_repo_dir, tag_name: tag_name)
169
155
  git!(%W(-C #{local_repo_dir} tag #{tag_name}))
170
156
  result = true
@@ -173,7 +159,6 @@ module Pindo
173
159
  else
174
160
  result = false
175
161
  end
176
- Dir.chdir(current)
177
162
  return result
178
163
  end
179
164
 
@@ -207,11 +192,9 @@ module Pindo
207
192
  end
208
193
 
209
194
  def remove_tag(local_repo_dir: nil, tag_name: nil)
210
-
195
+ # 命令已带 -C,无需 Dir.chdir(并发模式下 chdir 会污染全局 cwd)
211
196
  result = false
212
- current=Dir.pwd
213
197
  if File.exist?(local_repo_dir)
214
- Dir.chdir(local_repo_dir)
215
198
  if local_tag_exists?(local_repo_dir: local_repo_dir, tag_name: tag_name)
216
199
  git!(%W(-C #{local_repo_dir} tag -d #{tag_name}))
217
200
  result = true
@@ -223,7 +206,6 @@ module Pindo
223
206
  else
224
207
  result = false
225
208
  end
226
- Dir.chdir(current)
227
209
  return result
228
210
 
229
211
  end
@@ -234,12 +216,17 @@ module Pindo
234
216
  def remote_tag_commit(local_repo_dir: nil, tag_name: nil)
235
217
  return nil if local_repo_dir.nil? || !File.exist?(local_repo_dir)
236
218
  tag_ref = "refs/tags/#{tag_name}"
237
- res_data = git_remote!(local_repo_dir, %W(-C #{local_repo_dir} ls-remote --tags origin #{tag_ref}))
238
- lines = res_data.to_s.lines.map(&:strip).reject(&:empty?)
239
- return nil if lines.empty?
240
- # annotated tag ^{} 解引用行才是真实 commit,优先取它
241
- deref = lines.find { |l| l.include?("^{}") }
242
- (deref || lines.first).split(/\s+/).first
219
+ peeled_ref = "#{tag_ref}^{}"
220
+ # annotated tag 需要 ^{} 解引用行才能拿到真实 commit;但只传 tag_ref 精确过滤时,
221
+ # git ls-remote 不会附带 ^{} 行,必须显式把 peeled_ref 一起传入才能拿到它
222
+ res_data = git_remote!(local_repo_dir, %W(-C #{local_repo_dir} ls-remote --tags origin #{tag_ref} #{peeled_ref}))
223
+ entries = res_data.to_s.lines.map { |l| l.strip.split(/\s+/, 2) }
224
+ .select { |sha, ref| sha && ref }
225
+ return nil if entries.empty?
226
+ # 优先取 ^{} 行(annotated tag 的真实 commit);lightweight tag 只有 tag_ref 行且本身即 commit
227
+ peeled = entries.find { |_sha, ref| ref == peeled_ref }
228
+ plain = entries.find { |_sha, ref| ref == tag_ref }
229
+ (peeled || plain)&.first
243
230
  end
244
231
 
245
232
  def remote_tag_exists?(local_repo_dir: nil, tag_name: nil)
@@ -253,9 +240,7 @@ module Pindo
253
240
  end
254
241
 
255
242
  def git_latest_commit_id(local_repo_dir:nil)
256
-
257
- current=Dir.pwd
258
-
243
+ # 命令已带 -C,无需 Dir.chdir(并发模式下 chdir 会污染全局 cwd)
259
244
  unless File.exist?(File::join(local_repo_dir, ".git"))
260
245
  return nil
261
246
  end
@@ -263,16 +248,9 @@ module Pindo
263
248
  commit_id = nil
264
249
 
265
250
  if File.exist?(local_repo_dir)
266
- Dir.chdir(local_repo_dir)
267
251
  current_branch = git!(%W(-C #{local_repo_dir} rev-parse --abbrev-ref HEAD)).strip
268
- # puts "current_branch: #{current_branch}"
269
-
270
- # git log -n 1 --pretty=format:"commit %H"
271
- # format_str = "commit %H"
272
- # latest_log_info = git!(%W(-C #{current} log -n 1 --pretty=#{format_str} #{current_branch})).strip
273
252
  commit_id = git!(%W(-C #{local_repo_dir} rev-parse #{current_branch})).strip
274
253
  end
275
- Dir.chdir(current)
276
254
 
277
255
  return commit_id
278
256
  end
@@ -362,9 +340,11 @@ module Pindo
362
340
  rescue StandardError => e
363
341
  Funlog.instance.fancyinfo_error("仓库#{local_repo_dir}更新失败!")
364
342
  raise Informative, e.to_s
343
+ ensure
344
+ # 无论成功失败都还原 cwd,避免异常路径下污染全局工作目录
345
+ Dir.chdir(current)
365
346
  end
366
347
 
367
- Dir.chdir(current)
368
348
  return local_repo_dir
369
349
  end
370
350
 
@@ -464,12 +444,8 @@ module Pindo
464
444
  end
465
445
 
466
446
  def git_addpush_repo(path:nil, message:"res", commit_file_params:nil)
467
- current=Dir.pwd
468
- Dir.chdir(path)
469
-
470
- # gitee.com 仓库自动去除代理
447
+ # gitee.com 仓库自动去除代理;is_gitee_repo? 命令已带 -C,无需 chdir
471
448
  if is_gitee_repo?(local_repo_dir: path)
472
- Dir.chdir(current)
473
449
  return without_proxy { git_addpush_repo_impl(path: path, message: message, commit_file_params: commit_file_params) }
474
450
  end
475
451
 
@@ -512,10 +488,10 @@ module Pindo
512
488
  rescue => error
513
489
  # puts(error.to_s)
514
490
  raise Informative, "\n#{path}\n 仓库失败 !!!"
491
+ ensure
492
+ # 无论成功失败都还原 cwd,避免异常路径下污染全局工作目录
493
+ Dir.chdir(current)
515
494
  end
516
-
517
-
518
- Dir.chdir(current)
519
495
  end
520
496
 
521
497
 
@@ -548,8 +524,11 @@ module Pindo
548
524
  return nil
549
525
  end
550
526
 
551
- # 将字符串转换为数组
552
- files_list.is_a?(String) ? files_list.split("\n").reject(&:empty?) : files_list
527
+ # git!/IO#readpartial 返回的是 ASCII-8BIT 字符串,含非 ASCII 字节(如中文路径、
528
+ # core.quotePath=false 时的原始多字节)会与下游 UTF-8 字面量拼接失败,这里统一
529
+ # 标成 UTF-8 并 scrub 无效字节,避免上层 `puts "...#{file}"` 抛 Encoding::CompatibilityError
530
+ raw_list = files_list.is_a?(String) ? files_list.split("\n") : Array(files_list)
531
+ raw_list.map { |f| f.dup.force_encoding('UTF-8').scrub }.reject(&:empty?)
553
532
  rescue => e
554
533
  Funlog.instance.fancyinfo_error("获取未提交文件列表失败: #{e.message}")
555
534
  nil
@@ -624,14 +603,13 @@ module Pindo
624
603
  puts
625
604
 
626
605
  # 将文件列表分行并用红色显示
627
- if files.is_a?(String)
628
- files.split("\n").each do |file|
629
- puts " • #{file}".red unless file.empty?
630
- end
631
- else
632
- files.each do |file|
633
- puts " • #{file}".red unless file.empty?
634
- end
606
+ # 防御性编码归一化:git! 返回的是 ASCII-8BIT,与 UTF-8 字面量 " • " 拼接会抛
607
+ # Encoding::CompatibilityError;此处再兜一层,避免其它调用方传入 BINARY 字符串时崩溃
608
+ file_lines = files.is_a?(String) ? files.split("\n") : Array(files)
609
+ file_lines.each do |file|
610
+ next if file.nil? || file.empty?
611
+ safe_file = file.dup.force_encoding('UTF-8').scrub
612
+ puts " • #{safe_file}".red
635
613
  end
636
614
 
637
615
  puts
@@ -98,7 +98,7 @@ module Pindo
98
98
  @option_items ||= Pindo::Options::OptionGroup.merge(
99
99
  Pindo::Options::BuildOptions.select(:bundle_name, :release),
100
100
  Pindo::Options::JPSOptions.select(:conf, :upload, :media, :bind, :send),
101
- Pindo::Options::UnityOptions.select(:skiplib, :skipyoo),
101
+ Pindo::Options::UnityOptions.select(:skiplib, :skipyoo, :kill_unity),
102
102
  Pindo::Options::UnityOptions.select_with_defaults(skipmacro: true),
103
103
  Pindo::Options::TaskOptions.select(:multi),
104
104
  Pindo::Options::GitOptions.all
@@ -130,6 +130,7 @@ module Pindo
130
130
  # Unity 参数
131
131
  @args_skip_lib = @options[:skiplib] || false
132
132
  @args_skip_yoo = @options[:skipyoo] || false
133
+ @args_kill_unity = @options[:kill_unity] || false
133
134
 
134
135
  # Task 参数
135
136
  @args_multi_flag = @options[:multi] || false
@@ -313,7 +314,8 @@ module Pindo
313
314
  # 2.1 Unity 更新必备库任务(可选)
314
315
  unless @args_skip_lib
315
316
  unity_update_task = Pindo::TaskSystem::UnityUpdateTask.new(
316
- project_path: config[:project_path]
317
+ project_path: config[:project_path],
318
+ auto_kill: @args_kill_unity
317
319
  )
318
320
  unity_update_task.dependencies << last_task.id
319
321
  tasks << unity_update_task
@@ -324,7 +326,8 @@ module Pindo
324
326
  unless @args_skip_yoo
325
327
  unity_yoo_task = Pindo::TaskSystem::UnityYooAssetTask.new(
326
328
  'android',
327
- project_path: config[:project_path]
329
+ project_path: config[:project_path],
330
+ auto_kill: @args_kill_unity
328
331
  )
329
332
  unity_yoo_task.dependencies << last_task.id
330
333
  tasks << unity_yoo_task
@@ -339,7 +342,8 @@ module Pindo
339
342
  'android',
340
343
  project_path: config[:project_path],
341
344
  export_path: android_export_path,
342
- context: unity_context
345
+ context: unity_context,
346
+ auto_kill: @args_kill_unity
343
347
  )
344
348
  unity_export_task.dependencies << last_task.id
345
349
  tasks << unity_export_task
@@ -101,7 +101,7 @@ module Pindo
101
101
  @option_items ||= Pindo::Options::OptionGroup.merge(
102
102
  Pindo::Options::BuildOptions.select(:bundleid, :build_type),
103
103
  Pindo::Options::JPSOptions.select(:conf, :upload, :media, :bind, :send),
104
- Pindo::Options::UnityOptions.select(:skiplib, :skipyoo),
104
+ Pindo::Options::UnityOptions.select(:skiplib, :skipyoo, :kill_unity),
105
105
  Pindo::Options::UnityOptions.select_with_defaults(skipmacro: true),
106
106
  Pindo::Options::TaskOptions.select(:multi),
107
107
  Pindo::Options::GitOptions.all
@@ -141,6 +141,7 @@ module Pindo
141
141
  # Unity 参数
142
142
  @args_skip_lib = @options[:skiplib] || false
143
143
  @args_skip_yoo = @options[:skipyoo] || false
144
+ @args_kill_unity = @options[:kill_unity] || false
144
145
 
145
146
  # Task 参数
146
147
  @args_multi_flag = @options[:multi] || false
@@ -282,7 +283,8 @@ module Pindo
282
283
  # 2.1 Unity 更新必备库任务(可选)
283
284
  unless @args_skip_lib
284
285
  unity_update_task = Pindo::TaskSystem::UnityUpdateTask.new(
285
- project_path: config[:project_path]
286
+ project_path: config[:project_path],
287
+ auto_kill: @args_kill_unity
286
288
  )
287
289
  unity_update_task.dependencies << last_task.id
288
290
  tasks << unity_update_task
@@ -293,7 +295,8 @@ module Pindo
293
295
  unless @args_skip_yoo
294
296
  unity_yoo_task = Pindo::TaskSystem::UnityYooAssetTask.new(
295
297
  'ios',
296
- project_path: config[:project_path]
298
+ project_path: config[:project_path],
299
+ auto_kill: @args_kill_unity
297
300
  )
298
301
  unity_yoo_task.dependencies << last_task.id
299
302
  tasks << unity_yoo_task
@@ -309,7 +312,8 @@ module Pindo
309
312
  project_path: config[:project_path],
310
313
  export_path: ios_export_path,
311
314
  deploy_mode: @build_type, # 根据 build_type 传递部署模式
312
- context: unity_context
315
+ context: unity_context,
316
+ auto_kill: @args_kill_unity
313
317
  )
314
318
  unity_export_task.dependencies << last_task.id
315
319
  tasks << unity_export_task
@@ -43,7 +43,7 @@ module Pindo
43
43
  def initialize(argv)
44
44
 
45
45
  @args_repo_name = argv.shift_argument
46
- @args_login_flag = argv.flag?('force', false)
46
+ @args_login_flag = argv.flag?('login', false)
47
47
  @repo_name = argv.option('repo')
48
48
  @org_name = argv.option('org')
49
49
 
@@ -48,7 +48,7 @@ module Pindo
48
48
  def initialize(argv)
49
49
 
50
50
  @args_repo_name = argv.shift_argument
51
- @args_login_flag = argv.flag?('force', false)
51
+ @args_login_flag = argv.flag?('login', false)
52
52
  @repo_name = argv.option('repo')
53
53
  @org_name = argv.option('org')
54
54
 
@@ -90,7 +90,7 @@ module Pindo
90
90
 
91
91
  $ pindo unity autobuild --multi # 使用并发模式执行
92
92
 
93
- $ pindo unity autobuild --kill-unity # 自动关闭 Unity 进程(不询问确认)
93
+ $ pindo unity autobuild --kill_unity # 自动关闭 Unity 进程(不询问确认)
94
94
  DESC
95
95
 
96
96
  # 命令参数
@@ -159,8 +159,8 @@ module Pindo
159
159
  Funlog.fancyinfo_start("克隆仓库: #{url}...")
160
160
  Funlog.info("仓库 URL: #{url}") if ENV['PINDO_DEBUG']
161
161
 
162
- # 克隆仓库
163
- success = system("git clone #{url} #{temp_dir} --quiet 2>/dev/null")
162
+ # 克隆仓库(数组参数形式,避免 URL 中的 shell 元字符注入)
163
+ success = system("git", "clone", url, temp_dir, "--quiet", err: File::NULL)
164
164
 
165
165
  unless success && File.exist?(File.join(temp_dir, '.git'))
166
166
  Funlog.fancyinfo_error("仓库克隆失败: #{url}")
@@ -142,7 +142,6 @@ module Pindo
142
142
  new_project_dir = File.join(File::expand_path(current_dir + "/../") , temp_dir_name)
143
143
 
144
144
  if File.exist?(new_project_dir)
145
- system 'sh rm -rf ' + new_project_dir
146
145
  FileUtils.rm_rf(new_project_dir)
147
146
  end
148
147
  FileUtils.mkdir(new_project_dir)
@@ -94,7 +94,7 @@ module Pindo
94
94
  @option_items ||= Pindo::Options::OptionGroup.merge(
95
95
  Pindo::Options::BuildOptions.select(:bundleid),
96
96
  Pindo::Options::JPSOptions.select(:conf, :upload, :send, :media, :bind),
97
- Pindo::Options::UnityOptions.select(:skiplib, :skipyoo),
97
+ Pindo::Options::UnityOptions.select(:skiplib, :skipyoo, :kill_unity),
98
98
  Pindo::Options::UnityOptions.select_with_defaults(skipmacro: true),
99
99
  Pindo::Options::TaskOptions.select(:multi),
100
100
  Pindo::Options::UtilsOptions.select(:run),
@@ -128,6 +128,7 @@ module Pindo
128
128
  # Unity 参数
129
129
  @args_skip_lib = @options[:skiplib] || false
130
130
  @args_skip_yoo = @options[:skipyoo] || false
131
+ @args_kill_unity = @options[:kill_unity] || false
131
132
 
132
133
  # Task 参数
133
134
  @args_multi_flag = @options[:multi] || false
@@ -283,7 +284,8 @@ module Pindo
283
284
  # 3. Unity 更新必备库任务(可选)
284
285
  unless @args_skip_lib
285
286
  unity_update_task = Pindo::TaskSystem::UnityUpdateTask.new(
286
- project_path: config[:project_path]
287
+ project_path: config[:project_path],
288
+ auto_kill: @args_kill_unity
287
289
  )
288
290
  unity_update_task.dependencies << last_task.id if last_task
289
291
  tasks << unity_update_task
@@ -294,7 +296,8 @@ module Pindo
294
296
  unless @args_skip_yoo
295
297
  unity_yoo_task = Pindo::TaskSystem::UnityYooAssetTask.new(
296
298
  'web',
297
- project_path: config[:project_path]
299
+ project_path: config[:project_path],
300
+ auto_kill: @args_kill_unity
298
301
  )
299
302
  unity_yoo_task.dependencies << last_task.id if last_task
300
303
  tasks << unity_yoo_task
@@ -313,7 +316,8 @@ module Pindo
313
316
  'web',
314
317
  project_path: config[:project_path],
315
318
  export_path: web_export_path,
316
- context: unity_context
319
+ context: unity_context,
320
+ auto_kill: @args_kill_unity
317
321
  )
318
322
  unity_export_task.dependencies << last_task.id if last_task
319
323
  tasks << unity_export_task
@@ -6,6 +6,7 @@ require_relative 'java_env_helper'
6
6
  require_relative 'keystore_helper'
7
7
  require_relative 'workflow_gradle_injector'
8
8
  require 'fileutils'
9
+ require 'find'
9
10
 
10
11
  module Pindo
11
12
  class AndroidBuildHelper
@@ -24,6 +25,10 @@ module Pindo
24
25
  raise ArgumentError, "项目目录不能为空" if project_dir.nil?
25
26
  raise ArgumentError, "项目目录不存在" unless File.directory?(project_dir)
26
27
 
28
+ # 构建前先清理 macOS 自动生成的 .DS_Store
29
+ # 避免其被带入 base / asset pack 模块的 assets/ 导致冲突,或引起 Gradle clean 在遍历删除时出现竞态
30
+ prune_ds_store_files!(project_dir)
31
+
27
32
  # 设置 Java Home 环境变量
28
33
  Pindo::JavaEnvHelper.setup_java_home_from_project(project_dir)
29
34
 
@@ -396,20 +401,19 @@ module Pindo
396
401
  puts ' AAB 构建前先执行 Gradle clean'
397
402
  gradle_clean_resilient!(project_path)
398
403
 
399
- cmd = "cd \"#{project_path}\" && ./gradlew --no-daemon #{bundle_task}"
400
- system(cmd)
404
+ # 数组参数 + chdir,避免 project_path 拼接进 shell
405
+ system("./gradlew", "--no-daemon", bundle_task, chdir: project_path)
401
406
  end
402
407
 
403
408
  # clean 失败(占用、.DS_Store 竞态等)时:停 Daemon → 再删 .DS_Store → 物理删除各模块 build/,与 Gradle clean 产物一致,不中断后续 bundle
404
409
  private def gradle_clean_resilient!(project_path)
405
- prune_ds_store_before_gradle_clean!(project_path)
406
- cmd = %(cd "#{project_path}" && ./gradlew --no-daemon clean)
407
- return if system(cmd)
410
+ prune_ds_store_files!(project_path)
411
+ return if system("./gradlew", "--no-daemon", "clean", chdir: project_path)
408
412
 
409
413
  puts ' ⚠ Gradle clean 失败,正在自动恢复:--stop、清理 .DS_Store、删除各模块 build/ ...'
410
- system(%(cd "#{project_path}" && ./gradlew --stop), out: File::NULL, err: File::NULL)
414
+ system("./gradlew", "--stop", chdir: project_path, out: File::NULL, err: File::NULL)
411
415
  sleep 0.3
412
- prune_ds_store_before_gradle_clean!(project_path)
416
+ prune_ds_store_files!(project_path)
413
417
  remove_android_module_build_dirs!(project_path)
414
418
  end
415
419
 
@@ -445,18 +449,37 @@ module Pindo
445
449
  puts " ⚠ 物理删除 build 目录时出现异常(将继续尝试 bundle): #{e.message}"
446
450
  end
447
451
 
448
- # macOS clean 遍历删除 build 目录时 Finder 可能写入 .DS_Store,Gradle 会报 Unable to delete / New files were found
449
- private def prune_ds_store_before_gradle_clean!(project_path)
450
- return unless project_path && File.directory?(project_path)
451
-
452
- system('find', project_path, '-name', '.DS_Store', '-type', 'f', '-delete',
453
- out: File::NULL, err: File::NULL)
452
+ # 清理 macOS 自动生成的 .DS_Store 文件
453
+ # 作用:
454
+ # 1) 避免被带入 base 与 asset pack 模块的 assets/ 造成打包冲突
455
+ # 2) 避免 Gradle clean 遍历删除 build 目录时 Finder 回写 .DS_Store 触发
456
+ # Unable to delete / New files were found 的竞态错误
457
+ private def prune_ds_store_files!(project_path)
458
+ return 0 unless project_path && File.directory?(project_path)
459
+
460
+ count = 0
461
+ Find.find(project_path) do |path|
462
+ next unless File.basename(path) == '.DS_Store'
463
+ next unless File.file?(path)
464
+
465
+ begin
466
+ File.delete(path)
467
+ count += 1
468
+ rescue StandardError
469
+ # 个别文件删除失败不影响整体流程
470
+ next
471
+ end
472
+ end
473
+ puts " 已清理 #{count} 个 .DS_Store 文件" if count.positive?
474
+ count
475
+ rescue StandardError => e
476
+ puts " ⚠ 清理 .DS_Store 异常:#{e.message}"
477
+ 0
454
478
  end
455
479
 
456
480
  def build_so_library(project_path)
457
- # 编译so库 (Thread Safe)
458
- cmd = "cd \"#{project_path}\" && ./gradlew unityLibrary:BuildIl2CppTask"
459
- system(cmd)
481
+ # 编译so库 (Thread Safe);数组参数 + chdir,避免 project_path 拼接进 shell
482
+ system("./gradlew", "unityLibrary:BuildIl2CppTask", chdir: project_path)
460
483
  end
461
484
 
462
485
  def copy_so_files(source_path, target_path)
@@ -518,18 +518,16 @@ module Pindo
518
518
  return false
519
519
  end
520
520
 
521
- # 3. 使用unzip -t测试ZIP文件完整性
522
- test_cmd = "unzip -t '#{zip_file}' > /dev/null 2>&1"
523
- system(test_cmd)
521
+ # 3. 使用unzip -t测试ZIP文件完整性(数组参数,避免路径注入)
522
+ system("unzip", "-t", zip_file, out: File::NULL, err: File::NULL)
524
523
  rescue => e
525
524
  false
526
525
  end
527
526
 
528
527
  # 使用curl下载文件
529
528
  def download_with_curl(url, output_path)
530
- # 简化的curl命令,避免特殊字符问题
531
- cmd = "curl -L -o '#{output_path}' '#{url}'"
532
- system(cmd)
529
+ # 数组参数形式,避免 URL/路径中的 shell 元字符注入
530
+ system("curl", "-L", "-o", output_path, url)
533
531
  rescue => e
534
532
  false
535
533
  end
@@ -537,9 +535,8 @@ module Pindo
537
535
  # 解压Gradle分发包
538
536
  def extract_gradle_distribution(zip_file, extract_dir, gradle_version)
539
537
  begin
540
- # 使用unzip命令解压
541
- extract_cmd = "cd '#{File.dirname(extract_dir)}' && unzip -q '#{zip_file}'"
542
- if system(extract_cmd)
538
+ # 使用unzip命令解压(数组参数 + chdir,避免路径注入)
539
+ if system("unzip", "-q", zip_file, chdir: File.dirname(extract_dir))
543
540
  # 设置执行权限
544
541
  gradle_script = File.join(extract_dir, "bin/gradle")
545
542
  if File.exist?(gradle_script)
@@ -577,11 +574,8 @@ module Pindo
577
574
 
578
575
  # 使用官方Gradle命令生成wrapper
579
576
  def generate_gradle_wrapper_official(project_path, gradle_version)
580
- Dir.chdir(project_path) do
581
- # 使用gradle wrapper命令
582
- cmd = "gradle wrapper"
583
- system(cmd)
584
- end
577
+ # 数组参数 + chdir,避免 shell 解析
578
+ system("gradle", "wrapper", chdir: project_path)
585
579
  rescue => e
586
580
  false
587
581
  end
@@ -165,9 +165,9 @@ module Pindo
165
165
  puts "找到 #{identity_ids.size} 个证书:"
166
166
  puts identity_ids
167
167
 
168
- # 删除证书
168
+ # 删除证书(数组参数形式,避免 identity_id 拼接进 shell)
169
169
  identity_ids.each do |identity_id|
170
- system "security delete-certificate -Z #{identity_id}"
170
+ system("security", "delete-certificate", "-Z", identity_id)
171
171
  end
172
172
 
173
173
  # 清理 Provisioning Profiles