pindo 5.2.4 → 5.3.7

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/base/aeshelper.rb +23 -2
  3. data/lib/pindo/base/pindocontext.rb +259 -0
  4. data/lib/pindo/client/pgyer_feishu_oauth_cli.rb +343 -80
  5. data/lib/pindo/client/pgyerclient.rb +30 -20
  6. data/lib/pindo/command/android/autobuild.rb +52 -22
  7. data/lib/pindo/command/android/build.rb +27 -16
  8. data/lib/pindo/command/android/debug.rb +25 -15
  9. data/lib/pindo/command/dev/debug.rb +2 -51
  10. data/lib/pindo/command/dev/feishu.rb +19 -2
  11. data/lib/pindo/command/ios/adhoc.rb +2 -1
  12. data/lib/pindo/command/ios/autobuild.rb +35 -8
  13. data/lib/pindo/command/ios/debug.rb +2 -132
  14. data/lib/pindo/command/lib/lint.rb +24 -1
  15. data/lib/pindo/command/setup.rb +24 -4
  16. data/lib/pindo/command/unity/apk.rb +15 -0
  17. data/lib/pindo/command/unity/ipa.rb +16 -0
  18. data/lib/pindo/command.rb +13 -2
  19. data/lib/pindo/module/android/android_build_config_helper.rb +425 -0
  20. data/lib/pindo/module/android/apk_helper.rb +23 -25
  21. data/lib/pindo/module/android/base_helper.rb +572 -0
  22. data/lib/pindo/module/android/build_helper.rb +8 -318
  23. data/lib/pindo/module/android/gp_compliance_helper.rb +668 -0
  24. data/lib/pindo/module/android/gradle_helper.rb +746 -3
  25. data/lib/pindo/module/appselect.rb +18 -5
  26. data/lib/pindo/module/build/buildhelper.rb +120 -29
  27. data/lib/pindo/module/build/unityhelper.rb +675 -18
  28. data/lib/pindo/module/build/versionhelper.rb +146 -0
  29. data/lib/pindo/module/cert/certhelper.rb +33 -2
  30. data/lib/pindo/module/cert/xcodecerthelper.rb +3 -1
  31. data/lib/pindo/module/pgyer/pgyerhelper.rb +114 -31
  32. data/lib/pindo/module/xcode/xcodebuildconfig.rb +232 -0
  33. data/lib/pindo/module/xcode/xcodebuildhelper.rb +0 -1
  34. data/lib/pindo/version.rb +356 -86
  35. data/lib/pindo.rb +72 -3
  36. metadata +5 -1
@@ -0,0 +1,668 @@
1
+ require 'fileutils'
2
+ require 'tempfile'
3
+ require 'nokogiri'
4
+ require_relative 'base_helper'
5
+
6
+ module Pindo
7
+ module GPComplianceHelper
8
+ include BaseAndroidHelper
9
+
10
+ # Google Play 合规检测结果
11
+ class ComplianceResult
12
+ attr_accessor :target_sdk_compliant, :target_sdk_version, :elf_alignment_compliant,
13
+ :unaligned_libs, :total_libs, :compliance_issues, :warnings,
14
+ :size_compliant, :aab_size_mb, :base_size_mb, :base_percent
15
+
16
+ def initialize
17
+ @target_sdk_compliant = false
18
+ @target_sdk_version = 0
19
+ @elf_alignment_compliant = false
20
+ @unaligned_libs = []
21
+ @total_libs = 0
22
+ @compliance_issues = []
23
+ @warnings = []
24
+ @size_compliant = false
25
+ @aab_size_mb = 0
26
+ @base_size_mb = 0
27
+ @base_percent = 0
28
+ end
29
+
30
+ def compliant?
31
+ @target_sdk_compliant && @elf_alignment_compliant && @size_compliant
32
+ end
33
+
34
+ def add_issue(issue)
35
+ @compliance_issues << issue
36
+ end
37
+
38
+ def add_warning(warning)
39
+ @warnings << warning
40
+ end
41
+ end
42
+
43
+ # 检测 AAB 文件的 Google Play 合规性
44
+ def self.check_aab_compliance(aab_path)
45
+ result = ComplianceResult.new
46
+
47
+ unless File.exist?(aab_path)
48
+ result.add_issue("AAB 文件不存在: #{aab_path}")
49
+ return result
50
+ end
51
+
52
+ puts "\n\e[1m=== Google Play 合规检测 ===\e[0m"
53
+ puts "检测文件: #{File.basename(aab_path)}"
54
+
55
+ # 创建临时目录用于解压 AAB
56
+ temp_dir = nil
57
+ begin
58
+ temp_dir = Dir.mktmpdir("aab_compliance_check_")
59
+
60
+ # 检查 unzip 工具是否可用
61
+ unless tool_available?('unzip')
62
+ result.add_issue("unzip 工具不可用,无法解压 AAB 文件")
63
+ puts "请安装 unzip 工具: brew install unzip (macOS) 或 apt-get install unzip (Ubuntu)"
64
+ return result
65
+ end
66
+
67
+ # 解压 AAB 文件
68
+ unless system("unzip", "-q", aab_path, "-d", temp_dir)
69
+ result.add_issue("无法解压 AAB 文件")
70
+ return result
71
+ end
72
+
73
+ # 检测 AAB 包体积
74
+ check_aab_size_compliance(aab_path, result)
75
+
76
+ # 检测 Target SDK 版本(传递 AAB 路径)
77
+ check_target_sdk_compliance(temp_dir, result, aab_path)
78
+
79
+ # 检测 ELF 对齐
80
+ check_elf_alignment_compliance(temp_dir, result)
81
+
82
+ # 输出检测结果
83
+ print_compliance_summary(result)
84
+
85
+ ensure
86
+ # 清理临时目录
87
+ FileUtils.rm_rf(temp_dir) if temp_dir && File.directory?(temp_dir)
88
+ end
89
+
90
+ result
91
+ end
92
+
93
+ private
94
+
95
+ # 检查工具是否可用
96
+ def self.tool_available?(tool_name)
97
+ case tool_name
98
+ when 'bundletool'
99
+ # 检查 bundletool 是否可用(可能是别名或直接命令)
100
+ # 使用 Open3 来正确检测命令是否可用
101
+ require 'open3'
102
+ begin
103
+ stdout, stderr, status = Open3.capture3('bundletool help')
104
+ status.success?
105
+ rescue
106
+ false
107
+ end
108
+ when 'aapt'
109
+ system("which aapt > /dev/null 2>&1") || system("aapt version > /dev/null 2>&1")
110
+ when 'aapt2'
111
+ system("which aapt2 > /dev/null 2>&1") || system("aapt2 version > /dev/null 2>&1")
112
+ when 'objdump'
113
+ system("which objdump > /dev/null 2>&1")
114
+ when 'readelf'
115
+ system("which readelf > /dev/null 2>&1")
116
+ when 'unzip'
117
+ system("which unzip > /dev/null 2>&1")
118
+ else
119
+ system("which #{tool_name} > /dev/null 2>&1")
120
+ end
121
+ end
122
+
123
+ # 检测 AAB 包体积合规性
124
+ def self.check_aab_size_compliance(aab_path, result)
125
+ puts "\n\e[1m--- AAB 包体积检测 ---\e[0m"
126
+
127
+ begin
128
+ # 获取 AAB 文件大小
129
+ aab_size = File.size(aab_path)
130
+ result.aab_size_mb = (aab_size.to_f / 1024 / 1024).round(2)
131
+
132
+ # 统计 base/ 文件夹压缩体积
133
+ base_size = 0
134
+ base_limit_bytes = 194615705 # 185MB
135
+ base_limit_mb = 185
136
+
137
+ unzip_out = `unzip -v "#{aab_path}"`
138
+ if unzip_out && !unzip_out.empty?
139
+ base_lines = unzip_out.lines.select { |l| l.include?(" base/") }
140
+ base_size = base_lines.map { |l| l.split[2].to_i }.sum
141
+ result.base_size_mb = (base_size.to_f / 1024 / 1024).round(2)
142
+ result.base_percent = aab_size > 0 ? ((base_size.to_f * 100) / aab_size).round(2) : 0
143
+ end
144
+
145
+ puts "AAB 包体积: #{result.aab_size_mb} MB"
146
+ puts "base 文件夹压缩体积: #{result.base_size_mb} MB (占比 #{result.base_percent}%)"
147
+
148
+ if base_size > base_limit_bytes
149
+ result.size_compliant = false
150
+ result.add_issue("base 文件夹已超出 Google Play 限制(#{base_limit_mb}MB),请优化资源或分包")
151
+ puts "\e[31m✗ base 文件夹已超出 Google Play 限制(#{base_limit_mb}MB),请优化资源或分包!\e[0m"
152
+ else
153
+ result.size_compliant = true
154
+ puts "\e[32m✓ AAB 包体积符合 Google Play 提交标准\e[0m"
155
+ end
156
+
157
+ rescue => e
158
+ result.add_issue("AAB 包体积检测失败: #{e.message}")
159
+ puts "✗ AAB 包体积检测失败: #{e.message}"
160
+ end
161
+ end
162
+
163
+ # 检测 Target SDK 版本合规性
164
+ def self.check_target_sdk_compliance(temp_dir, result, aab_path = nil)
165
+ puts "\n\e[1m--- Target SDK 版本检测 ---\e[0m"
166
+
167
+ target_sdk = 0
168
+
169
+ # 方法1: 使用 bundletool dump manifest(首要方法)
170
+ if aab_path && File.exist?(aab_path)
171
+ # puts "使用 bundletool 解析 AAB 文件: #{File.basename(aab_path)}"
172
+ target_sdk = extract_target_sdk_with_bundletool(aab_path)
173
+ end
174
+
175
+ # 方法2: 如果 bundletool 失败,使用二进制 XML 解析(备用方法)
176
+ if target_sdk == 0
177
+ puts "bundletool 解析失败,尝试二进制 XML 解析"
178
+ manifest_path = find_android_manifest(temp_dir)
179
+ if manifest_path
180
+ puts "找到 AndroidManifest.xml: #{manifest_path}"
181
+ target_sdk = extract_target_sdk_from_manifest(manifest_path)
182
+ end
183
+ end
184
+
185
+ result.target_sdk_version = target_sdk
186
+
187
+ if target_sdk >= 35
188
+ result.target_sdk_compliant = true
189
+ puts "\e[32m✓ Target SDK: #{target_sdk} (符合 Android 15 要求)\e[0m"
190
+ else
191
+ result.target_sdk_compliant = false
192
+ if target_sdk == 0
193
+ result.add_issue("无法检测到 Target SDK 版本,请检查 AAB 文件结构")
194
+ else
195
+ result.add_issue("Target SDK #{target_sdk} 不符合要求,需要至少 Target SDK 35 (Android 15)")
196
+ end
197
+ puts "\e[31m✗ Target SDK: #{target_sdk} (需要至少 35)\e[0m"
198
+ end
199
+ end
200
+
201
+
202
+ # 使用 bundletool 提取 Target SDK 版本
203
+ def self.extract_target_sdk_with_bundletool(aab_path)
204
+ begin
205
+ # 获取 Pindo 自带的 bundletool.jar 路径
206
+ bundletool_jar = get_pindo_bundletool_path
207
+ unless bundletool_jar && File.exist?(bundletool_jar)
208
+ puts "Pindo 自带的 bundletool.jar 不可用,尝试使用系统 bundletool 命令"
209
+ return extract_target_sdk_with_system_bundletool(aab_path)
210
+ end
211
+
212
+ # 使用 Pindo 自带的 bundletool.jar
213
+ # 确保使用正确的 Java 版本 (Java 11+)
214
+ java_cmd = get_java_command_for_bundletool
215
+ bundletool_cmd = "#{java_cmd} -jar \"#{bundletool_jar}\" dump manifest --bundle=\"#{aab_path}\" | grep \"targetSdkVersion\""
216
+ output = `#{bundletool_cmd} 2>/dev/null`
217
+
218
+ if output && !output.empty?
219
+ # puts "bundletool 输出: #{output.strip}"
220
+
221
+ # 解析输出格式: <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="34"/>
222
+ if output =~ /android:targetSdkVersion="(\d+)"/
223
+ target_sdk = $1.to_i
224
+ # puts "从 bundletool 中找到 targetSdkVersion: #{target_sdk}"
225
+ return target_sdk
226
+ end
227
+ end
228
+
229
+ # puts "bundletool 未找到 targetSdkVersion"
230
+ return 0
231
+ rescue => e
232
+ puts "bundletool 解析失败: #{e.message}"
233
+ return 0
234
+ end
235
+ end
236
+
237
+ # 使用系统 bundletool 命令(备用方法)
238
+ def self.extract_target_sdk_with_system_bundletool(aab_path)
239
+ begin
240
+ # 检查系统 bundletool 是否可用
241
+ unless tool_available?('bundletool')
242
+ puts "系统 bundletool 也不可用,请确保已安装 bundletool"
243
+ puts "安装方法: 下载 bundletool.jar 并添加到 PATH 或创建别名"
244
+ return 0
245
+ end
246
+
247
+ # 使用系统 bundletool 命令
248
+ bundletool_cmd = "bundletool dump manifest --bundle=\"#{aab_path}\" | grep \"targetSdkVersion\""
249
+ output = `#{bundletool_cmd} 2>/dev/null`
250
+
251
+ if output && !output.empty?
252
+ # puts "bundletool 输出: #{output.strip}"
253
+
254
+ # 解析输出格式: <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="34"/>
255
+ if output =~ /android:targetSdkVersion="(\d+)"/
256
+ target_sdk = $1.to_i
257
+ # puts "从系统 bundletool 中找到 targetSdkVersion: #{target_sdk}"
258
+ return target_sdk
259
+ end
260
+ end
261
+
262
+ # puts "系统 bundletool 未找到 targetSdkVersion"
263
+ return 0
264
+ rescue => e
265
+ puts "系统 bundletool 解析失败: #{e.message}"
266
+ return 0
267
+ end
268
+ end
269
+
270
+ # 获取 Pindo 自带的 bundletool.jar 路径
271
+ def self.get_pindo_bundletool_path
272
+ begin
273
+ # 使用与 apk_helper.rb 相同的方法获取工具路径
274
+ pindo_dir = File.expand_path(ENV['PINDO_DIR'] || '~/.pindo')
275
+ pindo_common_configdir = File.join(pindo_dir, "pindo_common_config")
276
+ tools_dir = File.join(pindo_common_configdir, 'android_tools')
277
+ bundletool_jar = File.join(tools_dir, 'bundletool.jar')
278
+
279
+ File.exist?(bundletool_jar) ? bundletool_jar : nil
280
+ rescue
281
+ nil
282
+ end
283
+ end
284
+
285
+ # 获取用于 bundletool 的 Java 命令
286
+ def self.get_java_command_for_bundletool
287
+ # 创建一个临时对象来调用 BaseAndroidHelper 的方法
288
+ helper = Object.new
289
+ helper.extend(BaseAndroidHelper)
290
+ helper.find_java_command
291
+ end
292
+
293
+ # 查找 AndroidManifest.xml 文件(备用方法)
294
+ def self.find_android_manifest(temp_dir)
295
+ # 查找可能的 AndroidManifest.xml 路径
296
+ possible_paths = [
297
+ # AAB 解压后的标准路径
298
+ File.join(temp_dir, "base", "manifest", "AndroidManifest.xml"),
299
+ File.join(temp_dir, "manifest", "AndroidManifest.xml"),
300
+ # 其他可能的路径
301
+ File.join(temp_dir, "AndroidManifest.xml"),
302
+ # 递归查找
303
+ Dir.glob(File.join(temp_dir, "**", "AndroidManifest.xml")).first
304
+ ]
305
+
306
+ possible_paths.each do |path|
307
+ return path if path && File.exist?(path)
308
+ end
309
+
310
+ nil
311
+ end
312
+
313
+ # 从 AndroidManifest.xml 中提取 Target SDK 版本
314
+ def self.extract_target_sdk_from_manifest(manifest_path)
315
+ begin
316
+ # 读取文件内容(二进制模式)
317
+ content = File.binread(manifest_path)
318
+
319
+ # 检查是否为二进制 XML
320
+ if content.start_with?("\x03\x00\x08\x00") || content.include?("\x00")
321
+ puts "检测到二进制 XML 格式,使用二进制解析方法"
322
+
323
+ # 在二进制 XML 中搜索 targetSdkVersion 后的数字
324
+ # 模式: targetSdkVersion + 一些二进制数据 + 数字
325
+ if content =~ /targetSdkVersion[^\d]*(\d{1,2})/
326
+ # puts "从二进制 XML 中找到 targetSdkVersion: #{$1}"
327
+ return $1.to_i
328
+ end
329
+
330
+ # 尝试更宽泛的搜索模式
331
+ if content =~ /targetSdkVersion.*?(\d{1,2})/
332
+ # puts "从二进制 XML 中找到 targetSdkVersion (宽泛模式): #{$1}"
333
+ return $1.to_i
334
+ end
335
+
336
+ # puts "二进制 XML 中未找到 targetSdkVersion"
337
+ return 0
338
+ end
339
+
340
+ # 尝试使用 aapt 工具解析(适用于文本格式)
341
+ if tool_available?('aapt')
342
+ aapt_output = `aapt dump badging "#{manifest_path}" 2>/dev/null`
343
+ if aapt_output && !aapt_output.empty?
344
+ if aapt_output =~ /targetSdkVersion:'(\d+)'/
345
+ # puts "从 aapt 中找到 targetSdkVersion: #{$1}"
346
+ return $1.to_i
347
+ end
348
+ end
349
+ end
350
+
351
+ # 尝试使用 aapt2 工具
352
+ if tool_available?('aapt2')
353
+ aapt2_output = `aapt2 dump badging "#{manifest_path}" 2>/dev/null`
354
+ if aapt2_output && !aapt2_output.empty?
355
+ if aapt2_output =~ /targetSdkVersion:'(\d+)'/
356
+ # puts "从 aapt2 中找到 targetSdkVersion: #{$1}"
357
+ return $1.to_i
358
+ end
359
+ end
360
+ end
361
+
362
+ # 尝试使用 aapt dump xmltree
363
+ if tool_available?('aapt')
364
+ aapt_xml_output = `aapt dump xmltree "#{manifest_path}" 2>/dev/null`
365
+ if aapt_xml_output && !aapt_xml_output.empty?
366
+ if aapt_xml_output =~ /targetSdkVersion.*?(\d+)/
367
+ # puts "从 aapt xmltree 中找到 targetSdkVersion: #{$1}"
368
+ return $1.to_i
369
+ end
370
+ end
371
+ end
372
+
373
+ # 解析文本 XML
374
+ doc = Nokogiri::XML(content)
375
+
376
+ # 处理命名空间
377
+ doc.remove_namespaces!
378
+
379
+ # 查找 uses-sdk 标签
380
+ uses_sdk = doc.at_xpath('//uses-sdk')
381
+ if uses_sdk
382
+ target_sdk = uses_sdk['targetSdkVersion']
383
+ if target_sdk
384
+ # puts "从 XML 中找到 targetSdkVersion: #{target_sdk}"
385
+ return target_sdk.to_i
386
+ end
387
+ end
388
+
389
+ # 如果没找到 uses-sdk 标签,尝试查找其他可能的标签
390
+ target_sdk_attrs = doc.xpath('//@targetSdkVersion')
391
+ if !target_sdk_attrs.empty?
392
+ target_sdk = target_sdk_attrs.first.value
393
+ # puts "从 XML 属性中找到 targetSdkVersion: #{target_sdk}"
394
+ return target_sdk.to_i
395
+ end
396
+
397
+ # 尝试查找所有包含 targetSdkVersion 的属性
398
+ all_attrs = doc.xpath('//@*[contains(name(), "targetSdkVersion")]')
399
+ if !all_attrs.empty?
400
+ target_sdk = all_attrs.first.value
401
+ # puts "从 XML 中找到 targetSdkVersion 属性: #{target_sdk}"
402
+ return target_sdk.to_i
403
+ end
404
+
405
+ # puts "未找到 targetSdkVersion,返回默认值 0"
406
+ return 0
407
+
408
+ rescue => e
409
+ puts "解析 AndroidManifest.xml 失败: #{e.message}"
410
+ return 0
411
+ end
412
+ end
413
+
414
+ # 检测 ELF 对齐合规性
415
+ def self.check_elf_alignment_compliance(temp_dir, result)
416
+ puts "\n\e[1m--- ELF 对齐检测 (16KB 页面大小) ---\e[0m"
417
+
418
+ # 查找所有 .so 文件
419
+ so_files = find_shared_libraries(temp_dir)
420
+ result.total_libs = so_files.length
421
+
422
+ if so_files.empty?
423
+ puts "未找到共享库文件"
424
+ result.elf_alignment_compliant = true
425
+ return
426
+ end
427
+
428
+ puts "找到 #{so_files.length} 个共享库文件"
429
+
430
+ unaligned_libs = []
431
+
432
+ so_files.each do |so_file|
433
+ alignment_status = check_elf_alignment(so_file)
434
+
435
+ if alignment_status[:aligned]
436
+ puts "\e[32m✓ #{File.basename(so_file)}: ALIGNED (#{alignment_status[:alignment]})\e[0m"
437
+ else
438
+ puts "\e[31m✗ #{File.basename(so_file)}: UNALIGNED (#{alignment_status[:alignment]})\e[0m"
439
+ unaligned_libs << {
440
+ file: so_file,
441
+ alignment: alignment_status[:alignment],
442
+ architecture: alignment_status[:architecture]
443
+ }
444
+ end
445
+ end
446
+
447
+ result.unaligned_libs = unaligned_libs
448
+
449
+ # 检查是否有 arm64-v8a 或 x86_64 架构的未对齐库
450
+ critical_unaligned = unaligned_libs.select do |lib|
451
+ arch = lib[:architecture]
452
+ arch == 'arm64-v8a' || arch == 'x86_64'
453
+ end
454
+
455
+ if critical_unaligned.empty?
456
+ result.elf_alignment_compliant = true
457
+ puts "\e[32m✓ 所有关键架构 (arm64-v8a/x86_64) 的共享库都已正确对齐\e[0m"
458
+ else
459
+ result.elf_alignment_compliant = false
460
+ critical_unaligned.each do |lib|
461
+ result.add_issue("#{lib[:architecture]} 架构的共享库 #{File.basename(lib[:file])} 未对齐 (16KB 页面大小要求)")
462
+ end
463
+ puts "\e[31m✗ 发现 #{critical_unaligned.length} 个关键架构的未对齐共享库\e[0m"
464
+ end
465
+ end
466
+
467
+ # 查找共享库文件
468
+ def self.find_shared_libraries(temp_dir)
469
+ so_files = []
470
+
471
+ # 在 temp_dir 中查找所有 .so 文件
472
+ Dir.glob(File.join(temp_dir, "**", "*.so")).each do |so_file|
473
+ so_files << so_file
474
+ end
475
+
476
+ # 也检查 base.apk 中的库文件
477
+ base_apk_path = File.join(temp_dir, "base.apk")
478
+ if File.exist?(base_apk_path)
479
+ base_temp_dir = nil
480
+ begin
481
+ base_temp_dir = Dir.mktmpdir("base_apk_libs_")
482
+
483
+ # 解压 base.apk 中的 lib 目录
484
+ if system("unzip", "-q", base_apk_path, "lib/*", "-d", base_temp_dir)
485
+ Dir.glob(File.join(base_temp_dir, "lib", "**", "*.so")).each do |so_file|
486
+ so_files << so_file
487
+ end
488
+ end
489
+ ensure
490
+ FileUtils.rm_rf(base_temp_dir) if base_temp_dir && File.directory?(base_temp_dir)
491
+ end
492
+ end
493
+
494
+ so_files.uniq
495
+ end
496
+
497
+ # 检查单个 ELF 文件的对齐状态
498
+ def self.check_elf_alignment(so_file)
499
+ begin
500
+ # 首先检查文件是否为有效的 ELF 文件
501
+ file_output = `file "#{so_file}" 2>/dev/null`
502
+ unless file_output && file_output.include?('ELF')
503
+ return {
504
+ aligned: false,
505
+ alignment: "not_elf",
506
+ architecture: determine_architecture(so_file)
507
+ }
508
+ end
509
+
510
+ # 使用 objdump 检查 ELF 文件的对齐
511
+ unless tool_available?('objdump')
512
+ puts "objdump 不可用,无法检查 ELF 对齐"
513
+ return {
514
+ aligned: false,
515
+ alignment: "tool_missing",
516
+ architecture: determine_architecture(so_file)
517
+ }
518
+ end
519
+
520
+ objdump_output = `objdump -p "#{so_file}" 2>/dev/null`
521
+
522
+ if objdump_output && !objdump_output.empty?
523
+ # 查找 LOAD 段的对齐信息
524
+ load_sections = objdump_output.lines.select { |line| line.include?('LOAD') }
525
+
526
+ if !load_sections.empty?
527
+ # 获取第一个 LOAD 段的对齐值
528
+ first_load = load_sections.first
529
+ # 更精确的正则表达式匹配对齐值
530
+ alignment_match = first_load.match(/LOAD\s+off\s+0x[0-9a-f]+\s+vaddr\s+0x[0-9a-f]+\s+paddr\s+0x[0-9a-f]+\s+align\s+2\*\*(\d+)/)
531
+
532
+ if alignment_match
533
+ alignment_power = alignment_match[1].to_i
534
+ alignment_value = 2 ** alignment_power
535
+
536
+ # 检查是否满足 16KB 对齐要求 (2^14 = 16384)
537
+ aligned = alignment_value >= 16384
538
+
539
+ # 确定架构
540
+ architecture = determine_architecture(so_file)
541
+
542
+ return {
543
+ aligned: aligned,
544
+ alignment: "2^#{alignment_power}",
545
+ architecture: architecture
546
+ }
547
+ end
548
+ end
549
+ end
550
+
551
+ # 如果 objdump 失败,尝试使用 readelf
552
+ unless tool_available?('readelf')
553
+ puts "readelf 不可用,无法检查 ELF 对齐"
554
+ return {
555
+ aligned: false,
556
+ alignment: "tool_missing",
557
+ architecture: determine_architecture(so_file)
558
+ }
559
+ end
560
+
561
+ readelf_output = `readelf -l "#{so_file}" 2>/dev/null`
562
+ if readelf_output && !readelf_output.empty?
563
+ # 查找 LOAD 段的对齐信息
564
+ load_sections = readelf_output.lines.select { |line| line.include?('LOAD') }
565
+
566
+ if !load_sections.empty?
567
+ first_load = load_sections.first
568
+ alignment_match = first_load.match(/LOAD\s+0x[0-9a-f]+\s+0x[0-9a-f]+\s+0x[0-9a-f]+\s+0x[0-9a-f]+\s+0x[0-9a-f]+\s+(\d+)/)
569
+
570
+ if alignment_match
571
+ alignment_value = alignment_match[1].to_i
572
+ alignment_power = Math.log2(alignment_value).to_i if alignment_value > 0
573
+
574
+ aligned = alignment_value >= 16384
575
+ architecture = determine_architecture(so_file)
576
+
577
+ return {
578
+ aligned: aligned,
579
+ alignment: alignment_value > 0 ? "2^#{alignment_power}" : "0",
580
+ architecture: architecture
581
+ }
582
+ end
583
+ end
584
+ end
585
+
586
+ # 如果所有方法都失败,返回未对齐状态
587
+ return {
588
+ aligned: false,
589
+ alignment: "unknown",
590
+ architecture: determine_architecture(so_file)
591
+ }
592
+
593
+ rescue => e
594
+ puts "检查 ELF 对齐失败 #{so_file}: #{e.message}"
595
+ return {
596
+ aligned: false,
597
+ alignment: "error",
598
+ architecture: determine_architecture(so_file)
599
+ }
600
+ end
601
+ end
602
+
603
+ # 确定共享库的架构
604
+ def self.determine_architecture(so_file)
605
+ # 从文件路径中提取架构信息
606
+ if so_file.include?('/arm64-v8a/')
607
+ 'arm64-v8a'
608
+ elsif so_file.include?('/x86_64/')
609
+ 'x86_64'
610
+ elsif so_file.include?('/armeabi-v7a/')
611
+ 'armeabi-v7a'
612
+ elsif so_file.include?('/x86/')
613
+ 'x86'
614
+ elsif so_file.include?('/armeabi/')
615
+ 'armeabi'
616
+ else
617
+ 'unknown'
618
+ end
619
+ end
620
+
621
+ # 打印合规检测摘要
622
+ def self.print_compliance_summary(result)
623
+ puts "\n\e[1m=== Google Play 合规检测摘要 ===\e[0m"
624
+
625
+ # AAB 包体积检测结果
626
+ if result.size_compliant
627
+ puts "\e[32m✓ AAB 包体积: #{result.aab_size_mb} MB (符合 Google Play 限制)\e[0m"
628
+ else
629
+ puts "\e[31m✗ AAB 包体积: #{result.aab_size_mb} MB (超出 Google Play 限制)\e[0m"
630
+ end
631
+
632
+ # Target SDK 检测结果
633
+ if result.target_sdk_compliant
634
+ puts "\e[32m✓ Target SDK: #{result.target_sdk_version} (符合 Android 15 要求)\e[0m"
635
+ else
636
+ puts "\e[31m✗ Target SDK: #{result.target_sdk_version} (需要至少 35)\e[0m"
637
+ end
638
+
639
+ # ELF 对齐检测结果
640
+ if result.elf_alignment_compliant
641
+ puts "\e[32m✓ ELF 对齐: 所有关键架构共享库已正确对齐 (16KB 页面大小)\e[0m"
642
+ else
643
+ puts "\e[31m✗ ELF 对齐: 发现 #{result.unaligned_libs.length} 个未对齐的共享库\e[0m"
644
+ end
645
+
646
+ # 总体合规状态
647
+ puts "\n\e[1m--- 总体合规状态 ---\e[0m"
648
+ if result.compliant?
649
+ puts "\e[32m\e[1m✓ 符合 Google Play 最新合规要求,可以正常提交\e[0m"
650
+ else
651
+ puts "\e[31m\e[1m✗ 不符合 Google Play 合规要求,需要修复以下问题:\e[0m"
652
+ result.compliance_issues.each do |issue|
653
+ puts "\e[31m - #{issue}\e[0m"
654
+ end
655
+ end
656
+
657
+ # 警告信息
658
+ if !result.warnings.empty?
659
+ puts "\n--- 警告信息 ---"
660
+ result.warnings.each do |warning|
661
+ puts "\e[33m ⚠ #{warning}\e[0m"
662
+ end
663
+ end
664
+
665
+ puts "================================"
666
+ end
667
+ end
668
+ end