Class: Pindo::Android::WorkflowGradleInjector
- Inherits:
-
Object
- Object
- Pindo::Android::WorkflowGradleInjector
- Defined in:
- lib/pindo/module/android/workflow_gradle_injector.rb
Constant Summary collapse
- TMP_ROOT_RELATIVE =
".pindo_tmp".freeze
- LOCK_RELATIVE_PATH =
File.join(TMP_ROOT_RELATIVE, "locks", "gradle_inject.lock").freeze
- RUNS_RELATIVE_DIR =
File.join(TMP_ROOT_RELATIVE, "gradle_inject").freeze
- MARK_BEGIN =
"PINDO_WORKFLOW_GRADLE_INJECTOR_BEGIN".freeze
- MARK_END =
"PINDO_WORKFLOW_GRADLE_INJECTOR_END".freeze
- RELEASE_SIGNING_ENV_VARS =
%w[ RELEASE_KEYSTORE_PATH RELEASE_KEYSTORE_PASSWORD RELEASE_KEY_ALIAS RELEASE_KEY_PASSWORD ].freeze
Class Method Summary collapse
- .bin_copy(src, dst) ⇒ Object
-
.remove_project_pindo_tmp_dir!(project_dir) ⇒ Object
删除工程根下打包过程生成的 ‘.pindo_tmp`(锁文件、gradle_inject 等)。 须在 `cleanup!` 释放文件锁之后调用。.
- .restore_from_manifest_data!(project_dir, data) ⇒ Object
-
.with_injection(project_dir:, workflow_name:, workflow_build_type: nil, enable_pad: true, main_module: nil) ⇒ Object
仅执行 prepare! 并 yield(context)。不在此自动 cleanup!: Gradle 注入与 JKS 的清理时机由调用方在「AAB→APK(universal.apk)成功产出后」统一处理; 构建失败时调用方应在 ensure 中执行 cleanup! 以恢复 gradle,但不应删除尚未完成转换流程所需的 JKS。.
Instance Method Summary collapse
- #cleanup!(context) ⇒ Object
- #prepare!(project_dir:, workflow_name:, workflow_build_type: nil, enable_pad: true, main_module: nil) ⇒ Object
- #sanitize_workflow_build_type(workflow_name) ⇒ Object
Class Method Details
.bin_copy(src, dst) ⇒ Object
804 805 806 807 808 809 810 |
# File 'lib/pindo/module/android/workflow_gradle_injector.rb', line 804 def self.bin_copy(src, dst) File.open(src, "rb") do |r| File.open(dst, "wb") do |w| IO.copy_stream(r, w) end end end |
.remove_project_pindo_tmp_dir!(project_dir) ⇒ Object
删除工程根下打包过程生成的 ‘.pindo_tmp`(锁文件、gradle_inject 等)。须在 `cleanup!` 释放文件锁之后调用。
43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/pindo/module/android/workflow_gradle_injector.rb', line 43 def remove_project_pindo_tmp_dir!(project_dir) return if project_dir.to_s.empty? base = File.(project_dir) return unless File.directory?(base) target = File.(File.join(base, TMP_ROOT_RELATIVE)) return unless File.dirname(target) == base FileUtils.rm_rf(target) if File.exist?(target) end |
.restore_from_manifest_data!(project_dir, data) ⇒ Object
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/pindo/module/android/workflow_gradle_injector.rb', line 250 def self.restore_from_manifest_data!(project_dir, data) backups = data["backups"] || [] backups.each do |item| path = File.join(project_dir, item.fetch("path")) backup_path = File.join(project_dir, item.fetch("backup_path")) if item["type"] == "dir" next unless File.directory?(backup_path) FileUtils.rm_rf(path) if File.exist?(path) FileUtils.mkdir_p(File.dirname(path)) FileUtils.cp_r(backup_path, path) else next unless File.file?(backup_path) FileUtils.mkdir_p(File.dirname(path)) bin_copy(backup_path, path) end end # 临时 PAD 默认会清理 created_dirs(通常是 *_pack)。 # 若显式开启 PINDO_PAD_KEEP_REFS=1,则保留 pack 目录与引用,避免构建结束后被外部基线覆盖而丢引用。 unless ENV.fetch("PINDO_PAD_KEEP_REFS", "").to_s.strip.downcase.match?(/\A(1|true|yes|on)\z/) (data["created_dirs"] || []).each do |rel| abs = File.join(project_dir, rel) FileUtils.rm_rf(abs) if abs.start_with?(project_dir.to_s) end end end |
.with_injection(project_dir:, workflow_name:, workflow_build_type: nil, enable_pad: true, main_module: nil) ⇒ Object
仅执行 prepare! 并 yield(context)。不在此自动 cleanup!:Gradle 注入与 JKS 的清理时机由调用方在「AAB→APK(universal.apk)成功产出后」统一处理;构建失败时调用方应在 ensure 中执行 cleanup! 以恢复 gradle,但不应删除尚未完成转换流程所需的 JKS。
30 31 32 33 34 35 36 37 38 39 |
# File 'lib/pindo/module/android/workflow_gradle_injector.rb', line 30 def with_injection(project_dir:, workflow_name:, workflow_build_type: nil, enable_pad: true, main_module: nil) injector = new injector.prepare!( project_dir: project_dir, workflow_name: workflow_name, workflow_build_type: workflow_build_type, enable_pad: enable_pad, main_module: main_module ).tap { |context| yield(context) } end |
Instance Method Details
#cleanup!(context) ⇒ Object
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/pindo/module/android/workflow_gradle_injector.rb', line 170 def cleanup!(context) project_dir = context[:project_dir] tmp_run_dir = context[:tmp_run_dir] restore_from_manifest!(context) FileUtils.rm_f(context[:marker_path]) if context[:marker_path] FileUtils.rm_rf(tmp_run_dir) if tmp_run_dir && tmp_run_dir.start_with?(project_dir.to_s) ensure begin if (lock_io = context[:lock_io]) lock_io.flock(File::LOCK_UN) lock_io.close end rescue # ignore end end |
#prepare!(project_dir:, workflow_name:, workflow_build_type: nil, enable_pad: true, main_module: nil) ⇒ Object
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/pindo/module/android/workflow_gradle_injector.rb', line 56 def prepare!(project_dir:, workflow_name:, workflow_build_type: nil, enable_pad: true, main_module: nil) raise ArgumentError, "project_dir 不能为空" if project_dir.to_s.empty? raise ArgumentError, "项目目录不存在: #{project_dir}" unless File.directory?(project_dir) raise ArgumentError, "workflow_name 不能为空" if workflow_name.to_s.empty? run_id = SecureRandom.uuid workflow_build_type ||= sanitize_workflow_build_type(workflow_name) signing_config_name = workflow_build_type tmp_run_dir = File.join(project_dir, RUNS_RELATIVE_DIR, run_id) backup_dir = File.join(tmp_run_dir, "backup") marker_path = File.join(tmp_run_dir, "marker") manifest_path = File.join(tmp_run_dir, "manifest.json") FileUtils.mkdir_p(backup_dir) lock_file = File.join(project_dir, LOCK_RELATIVE_PATH) FileUtils.mkdir_p(File.dirname(lock_file)) lock_io = File.open(lock_file, File::RDWR | File::CREAT, 0o644) lock_io.flock(File::LOCK_EX) begin self_check_restore!(project_dir) context = { project_dir: project_dir, run_id: run_id, workflow_name: workflow_name, workflow_build_type: workflow_build_type, signing_config_name: signing_config_name, tmp_run_dir: tmp_run_dir, backup_dir: backup_dir, marker_path: marker_path, manifest_path: manifest_path, lock_file: lock_file, lock_io: lock_io, backups: [], created_dirs: [], } # 1) 先备份/再 PAD(PAD 会改 settings.gradle/launcher build.gradle/新增 pack 目录/移动 assets) if enable_pad pad_backup!(context) run_pad!(context) # 临时 PAD 默认会在 cleanup! 中恢复备份并删除 created_dirs(即 *_pack),以实现回滚。 # 但某些工程会在构建结束后用“基线文件/外部备份”覆盖当前 build.gradle, # 若该基线不包含 assetPacks/include,会导致引用丢失。 # # 当显式开启 PINDO_PAD_KEEP_REFS=1 时: # - 将 PAD 写入后的 settings.gradle / launcher/build.gradle 同步进备份快照, # 以确保 cleanup! 恢复后仍保留 assetPacks/include; # - 同时 cleanup! 将跳过删除 created_dirs,保留 *_pack 目录,保持工程可 sync。 sync_pad_refs_to_backups!(context) if pad_keep_refs_enabled? end # 2) 注入 buildType/signingConfig(对所有 Android modules) gradle_files = discover_gradle_module_files(project_dir) main_module_gradle = resolve_main_module_gradle_file(project_dir, gradle_files, main_module: main_module) # 需要改写的文件清单(幂等:已包含标记则不会重复注入) to_inject = gradle_files.dup to_inject << main_module_gradle if main_module_gradle && !to_inject.include?(main_module_gradle) to_inject.compact! to_inject.uniq! to_inject.each { |path| backup_file!(context, path) } File.write(marker_path, JSON.pretty_generate({ run_id: run_id, workflow_name: workflow_name, workflow_build_type: workflow_build_type, signing_config_name: signing_config_name, started_at: Time.now.to_i, })) to_inject.each do |gradle_path| inject_into_gradle_file!( gradle_path: gradle_path, workflow_name: workflow_name, workflow_build_type: workflow_build_type, signing_config_name: signing_config_name, is_main_module: (gradle_path == main_module_gradle) ) end File.write(manifest_path, JSON.pretty_generate({ run_id: run_id, marker_path: marker_path, backups: context[:backups], created_dirs: context[:created_dirs], })) context rescue # 如果 prepare! 中途失败,也尽可能清理掉 marker/恢复备份,避免污染后续运行 begin cleanup!({ project_dir: project_dir, tmp_run_dir: tmp_run_dir, marker_path: marker_path, manifest_path: manifest_path, lock_io: lock_io, backups: [], created_dirs: [], }) rescue # ignore end raise end end |
#sanitize_workflow_build_type(workflow_name) ⇒ Object
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/pindo/module/android/workflow_gradle_injector.rb', line 189 def sanitize_workflow_build_type(workflow_name) raw = workflow_name.to_s parts = raw.gsub(/[^A-Za-z0-9_]+/, " ").strip.split(/\s+/) # 去掉开头的 "test" 词:AGP 禁止 BuildType 名以 test 开头;如 "test demo" 应生成 demo 而非 testDemo while parts.first && parts.first.casecmp("test").zero? parts.shift end base = if parts.empty? "workflow" else first = parts.first.downcase rest = parts.drop(1).map { |p| p[0].to_s.upcase + p[1..].to_s.downcase } ([first] + rest).join end base = base.gsub(/[^A-Za-z0-9_]/, "") base = "w_#{base}" unless base.match?(/\A[A-Za-z]/) # AGP:BuildType 名称不能以 test 开头(保留字),否则报 BuildType names cannot start with 'test' base = "wf_#{base}" if base.match?(/\Atest/i) max_len = 40 if base.length > max_len digest = Digest::SHA256.hexdigest(raw)[0, 8] base = "#{base[0, max_len - 9]}_#{digest}" end base end |