Class: Pindo::AndroidProjectHelper
- Inherits:
-
Object
- Object
- Pindo::AndroidProjectHelper
- Defined in:
- lib/pindo/module/android/android_project_helper.rb
Class Method Summary collapse
- .add_unity_namespace(project_path) ⇒ Object
- .check_main_local_properties(project_path) ⇒ Object
-
.ensure_export_has_firebase_unity_aars!(unity_root_path:, export_path:) ⇒ Object
兼容旧调用名(实现已泛化为「按 Gradle 声明补齐 libs」)。.
-
.ensure_export_unity_library_aars_from_gradle!(unity_root_path:, export_path:) ⇒ Array<String>
根据导出工程 ‘unityLibrary/build.gradle` 中**实际声明**的本地 AAR 依赖,校验并补齐 `unityLibrary/libs`。.
-
.ensure_unity_il2cpp_jni_merge_depends_on!(project_path) ⇒ Boolean
永久修复 Unity IL2CPP 工程:将 mergeDebug/mergeReleaseJniLibFolders 对 BuildIl2CppTask 的依赖 从“硬编码 buildType”改为“匹配所有 merge*JniLibFolders 变体”,避免自定义 buildType(如 workflow) 或 Unity/AGP 版本差异导致漏掉依赖关系。.
- .find_android_subproject(project_path) ⇒ Object
- .get_build_tools ⇒ Object
- .get_expected_sdk_dir(project_path) ⇒ Object
- .get_main_module(project_path) ⇒ Object
- .modify_il2cpp_config(project_path) ⇒ Object
- .remove_desktop_google_service(project_path) ⇒ Object
-
.sync_gradle_properties_from_unity_to_main(project_path) ⇒ Object
在 Unity 作为 lib 的工程中,将 Unity 根目录下 gradle.properties 中的关键配置同步到主工程的 gradle.properties 中 当前仅同步以下键: - android.aapt2FromMavenOverride - org.gradle.java.home.
- .unity_android_project?(project_path) ⇒ Boolean
- .unity_as_lib_android_project?(project_path) ⇒ Boolean
Class Method Details
.add_unity_namespace(project_path) ⇒ Object
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/pindo/module/android/android_project_helper.rb', line 233 def add_unity_namespace(project_path) # 在 unityLibrary/build.gradle 中添加 namespace(AGP 7.x+ 需要) unity_build_gradle = File.join(project_path, "unityLibrary/build.gradle") return unless File.exist?(unity_build_gradle) content = File.read(unity_build_gradle) # 检查是否已经有 namespace if content =~ /namespace\s+['"][\w.]+['"]/ puts " ✓ unityLibrary 已配置 namespace" return end # 在 android { 块的开始位置添加 namespace if content =~ /(android\s*\{)/ # Unity 默认使用 com.unity3d.player 作为 namespace namespace_line = "\n namespace 'com.unity3d.player'\n" content.sub!(/(android\s*\{)/, "\\1#{namespace_line}") File.write(unity_build_gradle, content) puts " ✓ 已添加 namespace 到 unityLibrary/build.gradle" else puts " ⚠ 无法在 unityLibrary/build.gradle 中找到 android 块" end end |
.check_main_local_properties(project_path) ⇒ Object
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/pindo/module/android/android_project_helper.rb', line 259 def check_main_local_properties(project_path) # 检查并配置主工程的 local.properties 文件 main_local_properties = File.join(project_path, "local.properties") # 获取期望的 SDK 路径(从 Unity 模块、环境变量或默认路径) expected_sdk_dir = get_expected_sdk_dir(project_path) unless expected_sdk_dir puts " ⚠ 无法找到有效的 Android SDK 路径" return end if File.exist?(main_local_properties) # 文件存在,检查 sdk.dir 是否正确 puts " 检查主工程 local.properties..." content = File.read(main_local_properties) if content =~ /sdk\.dir\s*=\s*(.+)/ current_sdk_dir = $1.strip if current_sdk_dir == expected_sdk_dir puts " ✓ SDK 路径正确: #{current_sdk_dir}" elsif File.directory?(current_sdk_dir) puts " ✓ SDK 路径有效: #{current_sdk_dir}" else # SDK 路径无效,更新为期望的路径 puts " ⚠ SDK 路径无效: #{current_sdk_dir}" puts " 更新为: #{expected_sdk_dir}" content.gsub!(/sdk\.dir\s*=\s*.+/, "sdk.dir=#{expected_sdk_dir}") File.write(main_local_properties, content) puts " ✓ 已更新 SDK 路径" end else # 文件存在但没有 sdk.dir,添加它 puts " ⚠ local.properties 中未找到 sdk.dir" File.write(main_local_properties, "#{content}\nsdk.dir=#{expected_sdk_dir}\n") puts " ✓ 已添加 SDK 路径: #{expected_sdk_dir}" end else # 文件不存在,创建它 puts " 创建主工程 local.properties..." File.write(main_local_properties, "sdk.dir=#{expected_sdk_dir}\n") puts " ✓ 已创建 local.properties,SDK 路径: #{expected_sdk_dir}" end end |
.ensure_export_has_firebase_unity_aars!(unity_root_path:, export_path:) ⇒ Object
兼容旧调用名(实现已泛化为「按 Gradle 声明补齐 libs」)。
115 116 117 |
# File 'lib/pindo/module/android/android_project_helper.rb', line 115 def ensure_export_has_firebase_unity_aars!(unity_root_path:, export_path:) ensure_export_unity_library_aars_from_gradle!(unity_root_path: unity_root_path, export_path: export_path) end |
.ensure_export_unity_library_aars_from_gradle!(unity_root_path:, export_path:) ⇒ Array<String>
根据导出工程 ‘unityLibrary/build.gradle` 中**实际声明**的本地 AAR 依赖,校验并补齐 `unityLibrary/libs`。
EDM4U 常将部分 AAR 解析到 ‘Assets/GeneratedLocalRepo/**/m2repository`,导出后 Gradle 仍按 `name + ext:aar` 、`libs/某.aar` 或 `fileTree(dir: ’libs’, include: [‘*.aar’, …])‘ 引用;若只拷贝导出目录会缺文件。声明来源:`AndroidResolverDependencies.xml` + `GeneratedLocalRepo`(`fileTree` 模式会合并二者中的 .aar 清单)。
51 52 53 54 55 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 |
# File 'lib/pindo/module/android/android_project_helper.rb', line 51 def ensure_export_unity_library_aars_from_gradle!(unity_root_path:, export_path:) raise ArgumentError, "unity_root_path 不能为空" if unity_root_path.to_s.empty? raise ArgumentError, "export_path 不能为空" if export_path.to_s.empty? raise ArgumentError, "Unity 工程目录不存在: #{unity_root_path}" unless File.directory?(unity_root_path) raise ArgumentError, "导出目录不存在: #{export_path}" unless File.directory?(export_path) unity_library = File.join(export_path, "unityLibrary") gradle_path = %w[build.gradle build.gradle.kts].map { |n| File.join(unity_library, n) }.find { |p| File.file?(p) } libs_dir = File.join(unity_library, "libs") FileUtils.mkdir_p(libs_dir) unless gradle_path return [] end gradle_content = File.read(gradle_path, encoding: "UTF-8") resolver_xml = File.join(unity_root_path, "ProjectSettings", "AndroidResolverDependencies.xml") aar_index = build_android_resolver_aar_index(unity_root_path, resolver_xml) required_basenames = if gradle_declares_libs_file_tree_with_aar?(gradle_content) merged_aar_basenames_for_libs_file_tree(aar_index, unity_root_path) else extract_required_aar_basenames_from_unity_library_gradle(gradle_content) end return [] if required_basenames.empty? satisfied = [] missing_after_copy = [] required_basenames.each do |base| dst = File.join(libs_dir, base) if file_readable_nonbroken?(dst) satisfied << base next end src = resolve_aar_source_for_basename(base, aar_index, unity_root_path) unless src missing_after_copy << base next end FileUtils.cp(src, dst) satisfied << base end unless missing_after_copy.empty? raise Informative, <<~MSG Unity unityLibrary/libs 缺少 Gradle 已声明的 AAR,且无法在 Unity 工程内找到源文件: #{missing_after_copy.sort.join(', ')} Unity 工程: #{unity_root_path} 导出目录: #{export_path} Gradle: #{gradle_path} 请检查: #{resolver_xml} 并在 Unity 中执行:External Dependency Manager → Android Resolver → Force Resolve 后重新导出。 MSG end satisfied.uniq end |
.ensure_unity_il2cpp_jni_merge_depends_on!(project_path) ⇒ Boolean
永久修复 Unity IL2CPP 工程:将 mergeDebug/mergeReleaseJniLibFolders 对 BuildIl2CppTask 的依赖从“硬编码 buildType”改为“匹配所有 merge*JniLibFolders 变体”,避免自定义 buildType(如 workflow)或 Unity/AGP 版本差异导致漏掉依赖关系。
仅修改 Unity 导出工程(包含 unityLibrary/build.gradle)中的 unityLibrary 模块 Gradle 文件。幂等:已注入则不会重复写入。
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/pindo/module/android/android_project_helper.rb', line 16 def ensure_unity_il2cpp_jni_merge_depends_on!(project_path) raise ArgumentError, "project_path 不能为空" if project_path.to_s.empty? raise ArgumentError, "项目目录不存在: #{project_path}" unless File.directory?(project_path) unity_library = File.join(project_path, "unityLibrary") return false unless File.directory?(unity_library) gradle_path = File.join(unity_library, "build.gradle") kts_path = File.join(unity_library, "build.gradle.kts") target_file = File.file?(kts_path) ? kts_path : (File.file?(gradle_path) ? gradle_path : nil) return false unless target_file original = File.read(target_file, encoding: "UTF-8") # 仅在明显是 Unity IL2CPP 工程时介入 return false unless original.include?("BuildIl2CppTask") dsl = target_file.end_with?(".kts") ? :kts : :groovy updated = normalize_unity_il2cpp_jni_merge_depends_on_text(original) updated = migrate_unity_il2cpp_jni_merge_depends_on_marker_text(updated) updated = ensure_unity_il2cpp_jni_merge_depends_on_text(updated, dsl: dsl) return false if updated == original File.write(target_file, updated, encoding: "UTF-8") true end |
.find_android_subproject(project_path) ⇒ Object
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 |
# File 'lib/pindo/module/android/android_project_helper.rb', line 392 def find_android_subproject(project_path) android_dir = File.join(project_path, "Unity") return nil unless File.directory?(android_dir) main_module = get_main_module(android_dir) return nil unless main_module src_main = File.join(main_module, "src/main") return nil unless File.directory?(src_main) manifest = File.join(src_main, "AndroidManifest.xml") return nil unless File.exist?(manifest) android_dir end |
.get_build_tools ⇒ Object
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 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 209 210 211 212 213 |
# File 'lib/pindo/module/android/android_project_helper.rb', line 159 def get_build_tools # 获取 gem 资源文件路径 pindo_dir ||= File.(ENV['PINDO_DIR'] || '~/.pindo') pindo_common_configdir ||= File.join(pindo_dir, "pindo_common_config") tools_dir = File.join(pindo_common_configdir, 'android_tools') # 检查工具目录是否存在 unless File.directory?(tools_dir) Funlog.error("Android 构建工具目录不存在: #{tools_dir}") Funlog.error("请执行以下命令更新 Pindo 配置:") Funlog.error(" pindo setup") Funlog.error("或手动克隆配置仓库到 ~/.pindo/pindo_common_config") return nil end # 定义必要的工具 required_tools = { bundle_tool: 'bundletool.jar', gradlew: 'gradlew', gradle_wrapper: 'gradle-wrapper.jar' } tools = {} missing_tools = [] # 检查每个工具是否存在 required_tools.each do |key, filename| path = File.join(tools_dir, filename) if File.exist?(path) tools[key] = path else missing_tools << filename end end # 如果有缺失的工具,提供友好的错误信息 unless missing_tools.empty? Funlog.error("缺少以下 Android 构建工具:") missing_tools.each do |tool| Funlog.error(" - #{tool}") end Funlog.error("") Funlog.error("解决方案:") Funlog.error(" 1. 执行 'pindo setup' 更新配置") Funlog.error(" 2. 或手动下载缺失的工具到: #{tools_dir}") Funlog.error("") Funlog.error("如果问题持续存在,请检查:") Funlog.error(" - 网络连接是否正常") Funlog.error(" - Git 仓库是否可访问") Funlog.error(" - 目录权限是否正确") return nil end tools end |
.get_expected_sdk_dir(project_path) ⇒ Object
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/pindo/module/android/android_project_helper.rb', line 305 def get_expected_sdk_dir(project_path) # 优先使用 Android Studio 的 SDK 路径(主工程使用) android_studio_sdk = File.("~/Library/Android/sdk") return android_studio_sdk if File.directory?(android_studio_sdk) # 尝试从环境变量获取 sdk_dir = ENV['ANDROID_HOME'] || ENV['ANDROID_SDK_ROOT'] return sdk_dir if sdk_dir && File.directory?(sdk_dir) # 尝试从 Unity 模块的 local.properties 获取 SDK 路径 unity_local_properties = File.join(project_path, "Unity/local.properties") if File.exist?(unity_local_properties) unity_content = File.read(unity_local_properties) if unity_content =~ /sdk\.dir\s*=\s*(.+)/ sdk_dir = $1.strip return sdk_dir if File.directory?(sdk_dir) end end # 最后尝试 Unity 内置的 SDK 路径 unity_sdk = "/Applications/Unity/Hub/Editor/2022.3.61f1/PlaybackEngines/AndroidPlayer/SDK" return unity_sdk if File.directory?(unity_sdk) nil end |
.get_main_module(project_path) ⇒ Object
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
# File 'lib/pindo/module/android/android_project_helper.rb', line 332 def get_main_module(project_path) settings_gradle_path = File.join(project_path, "settings.gradle") settings_gradle_kts_path = File.join(project_path, "settings.gradle.kts") # 优先使用 settings.gradle.kts,如果不存在则使用 settings.gradle if File.exist?(settings_gradle_kts_path) settings_gradle_path = settings_gradle_kts_path elsif !File.exist?(settings_gradle_path) return nil end content = File.read(settings_gradle_path) modules = extract_modules_from_settings(content) project_dir_map = extract_module_project_dirs(content) main_module = modules.find do |m| module_name = m.split(':').last module_rel_path = project_dir_map[m] || module_name module_dir = File.join(project_path, module_rel_path) gradle_path = File.join(module_dir, "build.gradle") gradle_kts_path = File.join(module_dir, "build.gradle.kts") gradle_file = if File.exist?(gradle_kts_path) gradle_kts_path elsif File.exist?(gradle_path) gradle_path end next false unless gradle_file gradle_content = File.read(gradle_file) android_application_module?(gradle_content) end # 兜底:一些工程未声明标准 application 插件,优先尝试常见主模块名 if main_module.nil? %w[app launcher application].each do |candidate| candidate_module = modules.find { |m| m.split(':').last == candidate } next unless candidate_module module_rel_path = project_dir_map[candidate_module] || candidate module_dir = File.join(project_path, module_rel_path) has_gradle = File.exist?(File.join(module_dir, "build.gradle")) || File.exist?(File.join(module_dir, "build.gradle.kts")) has_manifest = File.exist?(File.join(module_dir, "src/main/AndroidManifest.xml")) if has_gradle && has_manifest main_module = candidate_module break end end end return nil unless main_module module_name = main_module.split(':').last module_rel_path = project_dir_map[main_module] || module_name File.join(project_path, module_rel_path) end |
.modify_il2cpp_config(project_path) ⇒ Object
215 216 217 218 219 220 221 222 223 224 |
# File 'lib/pindo/module/android/android_project_helper.rb', line 215 def modify_il2cpp_config(project_path) # 设置Il2CppOutputProject可执行权限 system("chmod", "-R", "777", File.join(project_path, "unityLibrary/src/main/Il2CppOutputProject")) il2cpp_config_path = File.join(project_path, "unityLibrary/src/main/Il2CppOutputProject/IL2CPP/libil2cpp/il2cpp-config.h") content = File.read(il2cpp_config_path) content.gsub!("il2cpp::vm::Exception::Raise", "//il2cpp::vm::Exception::Raise") File.write(il2cpp_config_path, content) end |
.remove_desktop_google_service(project_path) ⇒ Object
226 227 228 229 230 231 |
# File 'lib/pindo/module/android/android_project_helper.rb', line 226 def remove_desktop_google_service(project_path) # 删除google-services-desktop.json desktop_google_service_path = File.join(project_path, "unityLibrary/src/main/assets/google-services-desktop.json") File.delete(desktop_google_service_path) if File.exist?(desktop_google_service_path) end |
.sync_gradle_properties_from_unity_to_main(project_path) ⇒ Object
在 Unity 作为 lib 的工程中,将 Unity 根目录下 gradle.properties 中的关键配置同步到主工程的 gradle.properties 中当前仅同步以下键:
-
android.aapt2FromMavenOverride
-
org.gradle.java.home
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 |
# File 'lib/pindo/module/android/android_project_helper.rb', line 413 def sync_gradle_properties_from_unity_to_main(project_path) return unless unity_as_lib_android_project?(project_path) unity_gradle_properties = File.join(project_path, "Unity", "gradle.properties") return unless File.exist?(unity_gradle_properties) keys_to_sync = [ "android.aapt2FromMavenOverride", "org.gradle.java.home", ] unity_values = {} File.read(unity_gradle_properties).each_line do |line| stripped = line.strip next if stripped.empty? || stripped.start_with?("#", "!") keys_to_sync.each do |key| # 兼容前后有空格的 "key = value" 写法 if stripped =~ /^#{Regexp.escape(key)}\s*=\s*(.+)$/ unity_values[key] = Regexp.last_match(1).strip end end end return if unity_values.empty? main_gradle_properties = File.join(project_path, "gradle.properties") main_content = File.exist?(main_gradle_properties) ? File.read(main_gradle_properties) : "" original_content = main_content.dup keys_to_sync.each do |key| value = unity_values[key] next unless value key_regex = /^#{Regexp.escape(key)}\s*=.*$/ if main_content =~ key_regex # 替换已存在的配置行 main_content = main_content.gsub(key_regex, "#{key}=#{value}") else # 追加新的配置行 main_content << "\n" unless main_content.empty? || main_content.end_with?("\n") main_content << "#{key}=#{value}\n" end end return if main_content == original_content File.write(main_gradle_properties, main_content) end |
.unity_android_project?(project_path) ⇒ Boolean
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/pindo/module/android/android_project_helper.rb', line 119 def unity_android_project?(project_path) # 检查 unityLibrary 模块是否存在 unity_library_path = File.join(project_path, "unityLibrary") return false unless File.directory?(unity_library_path) # 检查 unityLibrary 的 build.gradle 或 build.gradle.kts 是否存在 unity_gradle_path = File.join(unity_library_path, "build.gradle") unity_gradle_kts_path = File.join(unity_library_path, "build.gradle.kts") if File.exist?(unity_gradle_kts_path) unity_gradle_path = unity_gradle_kts_path elsif !File.exist?(unity_gradle_path) return false end # 检查 build.gradle 中是否包含 Unity 特有的配置 content = File.read(unity_gradle_path) content.include?("com.android.library") && content.include?("BuildIl2Cpp") end |
.unity_as_lib_android_project?(project_path) ⇒ Boolean
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/pindo/module/android/android_project_helper.rb', line 138 def unity_as_lib_android_project?(project_path) # Unity 作为 lib 的判断条件简化版: # 1. 不是独立的 Unity 导出工程 return false if unity_android_project?(project_path) # 2. 检查是否存在 Unity 目录 unity_dir = File.join(project_path, "Unity") return false unless File.directory?(unity_dir) # 3. 检查 Unity 目录下是否有 unityLibrary 目录 unity_library_dir = File.join(unity_dir, "unityLibrary") return false unless File.directory?(unity_library_dir) # 4. 检查 Unity/unityLibrary 目录下是否有 build.gradle 或 build.gradle.kts gradle_path = File.join(unity_library_dir, "build.gradle") gradle_kts_path = File.join(unity_library_dir, "build.gradle.kts") File.exist?(gradle_path) || File.exist?(gradle_kts_path) end |