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
692 693 694 695 696 697 698 |
# File 'lib/pindo/module/android/workflow_gradle_injector.rb', line 692 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
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/pindo/module/android/workflow_gradle_injector.rb', line 240 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")) next unless File.file?(backup_path) FileUtils.mkdir_p(File.dirname(path)) bin_copy(backup_path, path) end (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 |
.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
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/pindo/module/android/workflow_gradle_injector.rb', line 160 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 |
# 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) 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
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/pindo/module/android/workflow_gradle_injector.rb', line 179 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 |