pindo 5.0.4 → 5.0.6
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.
- checksums.yaml +4 -4
- data/lib/pindo/base/githelper.rb +1 -1
- data/lib/pindo/client/pgyer_feishu_oauth_cli.rb +313 -0
- data/lib/pindo/client/pgyeruploadclient.rb +275 -153
- data/lib/pindo/command/android/autobuild.rb +121 -0
- data/lib/pindo/command/android/build.rb +113 -0
- data/lib/pindo/command/android/debug.rb +60 -14
- data/lib/pindo/command/android.rb +5 -2
- data/lib/pindo/command/ios/autobuild.rb +6 -0
- data/lib/pindo/command/ios/build.rb +7 -1
- data/lib/pindo/command/unity/apk.rb +69 -6
- data/lib/pindo/command/utils/renewcert.rb +2 -2
- data/lib/pindo/module/android/apk_helper.rb +91 -0
- data/lib/pindo/module/android/base_helper.rb +293 -0
- data/lib/pindo/module/android/build_helper.rb +112 -0
- data/lib/pindo/module/android/gradle_helper.rb +48 -0
- data/lib/pindo/module/android/so_helper.rb +18 -0
- data/lib/pindo/module/build/buildhelper.rb +50 -37
- data/lib/pindo/module/build/unityhelper.rb +16 -16
- data/lib/pindo/module/pgyer/pgyerhelper.rb +14 -11
- data/lib/pindo/module/xcode/xcodehelper.rb +73 -73
- data/lib/pindo/version.rb +1 -1
- metadata +71 -11
| @@ -0,0 +1,293 @@ | |
| 1 | 
            +
            module Pindo
         | 
| 2 | 
            +
              module BaseAndroidHelper
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def get_build_tools
         | 
| 5 | 
            +
                  # 获取 gem 资源文件路径
         | 
| 6 | 
            +
                  
         | 
| 7 | 
            +
                  pindo_dir ||= File.expand_path(ENV['PINDO_DIR'] || '~/.pindo')
         | 
| 8 | 
            +
                  pindo_common_configdir ||= File.join(pindo_dir, "pindo_common_config")
         | 
| 9 | 
            +
                  tools_dir = File.join(pindo_common_configdir, 'android_tools')
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  # 如果开发环境找不到,尝试在 gem 安装目录找
         | 
| 12 | 
            +
                  unless File.directory?(tools_dir)
         | 
| 13 | 
            +
                    raise "缺少必要的构建工具: #{tools_dir},请执行pindo setup更新pindo配置"
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  # 检查必要的工具是否存在
         | 
| 17 | 
            +
                  required_tools = {
         | 
| 18 | 
            +
                    bundle_tool: 'bundletool.jar',
         | 
| 19 | 
            +
                    gradlew: 'gradlew',
         | 
| 20 | 
            +
                    gradle_wrapper: 'gradle-wrapper.jar'
         | 
| 21 | 
            +
                  }
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  tools = {}
         | 
| 24 | 
            +
                  required_tools.each do |key, filename|
         | 
| 25 | 
            +
                    path = File.join(tools_dir, filename)
         | 
| 26 | 
            +
                    unless File.exist?(path)
         | 
| 27 | 
            +
                      raise "缺少必要的构建工具: #{filename},请执行pindo setup更新pindo配置"
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
                    tools[key] = path
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  tools
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def modify_il2cpp_config(project_path)
         | 
| 36 | 
            +
                  # 设置Il2CppOutputProject可执行权限
         | 
| 37 | 
            +
                  system("chmod", "-R", "777",
         | 
| 38 | 
            +
                  File.join(project_path, "unityLibrary/src/main/Il2CppOutputProject"))
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  il2cpp_config_path = File.join(project_path, "unityLibrary/src/main/Il2CppOutputProject/IL2CPP/libil2cpp/il2cpp-config.h")
         | 
| 41 | 
            +
                  content = File.read(il2cpp_config_path)
         | 
| 42 | 
            +
                  content.gsub!("il2cpp::vm::Exception::Raise", "//il2cpp::vm::Exception::Raise")
         | 
| 43 | 
            +
                  File.write(il2cpp_config_path, content)
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def remove_desktop_google_service(project_path)
         | 
| 47 | 
            +
                  # 删除google-services-desktop.json
         | 
| 48 | 
            +
                  desktop_google_service_path = File.join(project_path,
         | 
| 49 | 
            +
                    "unityLibrary/src/main/assets/google-services-desktop.json")
         | 
| 50 | 
            +
                  File.delete(desktop_google_service_path) if File.exist?(desktop_google_service_path)
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                # def get_app_name(project_path)
         | 
| 54 | 
            +
                #   strings_xml_path = File.join(project_path, "app/src/main/res/values/strings.xml")
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                #   unless File.exist?(strings_xml_path)
         | 
| 57 | 
            +
                #     strings_xml_path = File.join(project_path, "unityLibrary/src/main/res/values/strings.xml")
         | 
| 58 | 
            +
                #     unless File.exist?(strings_xml_path)
         | 
| 59 | 
            +
                #       return nil
         | 
| 60 | 
            +
                #     end
         | 
| 61 | 
            +
                #   end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                #   xml_content = File.read(strings_xml_path)
         | 
| 64 | 
            +
                #   doc = Nokogiri::XML(xml_content)
         | 
| 65 | 
            +
                #   doc.at_xpath("//string[@name='app_name']")&.text
         | 
| 66 | 
            +
                # end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def get_main_module(project_path)
         | 
| 69 | 
            +
                  settings_gradle_path = File.join(project_path, "settings.gradle")
         | 
| 70 | 
            +
                  return nil unless File.exist?(settings_gradle_path)
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  content = File.read(settings_gradle_path)
         | 
| 73 | 
            +
                  modules = content.scan(/include\s+['"]([^'"]+)['"]/).flatten
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  main_module = modules.find do |m|
         | 
| 76 | 
            +
                    module_name = m.split(':').last
         | 
| 77 | 
            +
                    gradle_path = File.join(project_path, module_name, "build.gradle")
         | 
| 78 | 
            +
                    if File.exist?(gradle_path)
         | 
| 79 | 
            +
                      File.read(gradle_path).include?("apply plugin: 'com.android.application'") ||
         | 
| 80 | 
            +
                        File.read(gradle_path).include?("id 'com.android.application'")
         | 
| 81 | 
            +
                    end
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
                  return nil unless main_module
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  module_name = main_module.split(':').last
         | 
| 86 | 
            +
                  File.join(project_path, module_name)
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                def get_keystore_config(project_path, debug = false)
         | 
| 90 | 
            +
                  main_module = get_main_module(project_path)
         | 
| 91 | 
            +
                  return nil unless main_module
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  gradle_path = File.join(main_module, "build.gradle")
         | 
| 94 | 
            +
                  return nil unless File.exist?(gradle_path)
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                  content = File.read(gradle_path)
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  # 直接读取整个文件内容,不使用正则表达式截取 android 块
         | 
| 99 | 
            +
                  puts "读取 build.gradle 文件"
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                  # 查找 signingConfigs 块的开始和结束位置
         | 
| 102 | 
            +
                  signing_configs_start = content.index(/signingConfigs\s*\{/)
         | 
| 103 | 
            +
                  return nil unless signing_configs_start
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  # 从开始位置查找匹配的闭合大括号
         | 
| 106 | 
            +
                  open_braces = 0
         | 
| 107 | 
            +
                  signing_configs_end = nil
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  content[signing_configs_start..-1].each_char.with_index do |char, i|
         | 
| 110 | 
            +
                    if char == '{'
         | 
| 111 | 
            +
                      open_braces += 1
         | 
| 112 | 
            +
                    elsif char == '}'
         | 
| 113 | 
            +
                      open_braces -= 1
         | 
| 114 | 
            +
                      if open_braces == 0
         | 
| 115 | 
            +
                        signing_configs_end = signing_configs_start + i
         | 
| 116 | 
            +
                        break
         | 
| 117 | 
            +
                      end
         | 
| 118 | 
            +
                    end
         | 
| 119 | 
            +
                  end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                  return nil unless signing_configs_end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  # 提取完整的 signingConfigs 块
         | 
| 124 | 
            +
                  signing_configs_block = content[signing_configs_start..signing_configs_end]
         | 
| 125 | 
            +
             | 
| 126 | 
            +
             | 
| 127 | 
            +
                  # 查找配置名称,如 Android_Sign
         | 
| 128 | 
            +
                  config_name_match = signing_configs_block.match(/signingConfigs\s*\{\s*(\w+)\s*\{/)
         | 
| 129 | 
            +
                  config_name = config_name_match ? config_name_match[1] : nil
         | 
| 130 | 
            +
                  return nil unless config_name
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                  # 检查 buildTypes 中使用的签名配置
         | 
| 133 | 
            +
                  config_type = debug ? 'debug' : 'release'
         | 
| 134 | 
            +
                  build_type_match = content.match(/buildTypes\s*\{.*?#{config_type}\s*\{(.*?)}/m)
         | 
| 135 | 
            +
                  if build_type_match && build_type_match[1] =~ /signingConfig\s+signingConfigs\.(\w+)/
         | 
| 136 | 
            +
                    config_name = $1
         | 
| 137 | 
            +
                  end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                  # 提取特定配置块的内容
         | 
| 140 | 
            +
                  config_start = signing_configs_block.index(/#{config_name}\s*\{/)
         | 
| 141 | 
            +
                  return nil unless config_start
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                  # 从配置开始位置查找匹配的闭合大括号
         | 
| 144 | 
            +
                  open_braces = 0
         | 
| 145 | 
            +
                  config_end = nil
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                  signing_configs_block[config_start..-1].each_char.with_index do |char, i|
         | 
| 148 | 
            +
                    if char == '{'
         | 
| 149 | 
            +
                      open_braces += 1
         | 
| 150 | 
            +
                    elsif char == '}'
         | 
| 151 | 
            +
                      open_braces -= 1
         | 
| 152 | 
            +
                      if open_braces == 0
         | 
| 153 | 
            +
                        config_end = config_start + i
         | 
| 154 | 
            +
                        break
         | 
| 155 | 
            +
                      end
         | 
| 156 | 
            +
                    end
         | 
| 157 | 
            +
                  end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                  return nil unless config_end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                  # 提取完整的配置块
         | 
| 162 | 
            +
                  config_block = signing_configs_block[config_start..config_end]
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                  puts "解析 signing config:"
         | 
| 165 | 
            +
                  puts "=" * 50
         | 
| 166 | 
            +
                  puts config_block
         | 
| 167 | 
            +
                  puts "=" * 50
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                  # 从配置块中提取各项参数
         | 
| 170 | 
            +
                  store_file_match = config_block.match(/storeFile\s+file\(["']?\${([^}]+)}["']?\)/m)
         | 
| 171 | 
            +
                  store_password_match = config_block.match(/storePassword\s+["']?\${([^}]+)}["']?/m)
         | 
| 172 | 
            +
                  key_alias_match = config_block.match(/keyAlias\s+["']?\${([^}]+)}["']?/m)
         | 
| 173 | 
            +
                  key_password_match = config_block.match(/keyPassword\s+["']?\${([^}]+)}["']?/m)
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                  # 如果使用了外部变量,尝试从 ext_signing.gradle 或其他配置文件中获取实际值
         | 
| 176 | 
            +
                  if store_file_match || store_password_match || key_alias_match || key_password_match
         | 
| 177 | 
            +
                    ext_values = get_ext_values(project_path)
         | 
| 178 | 
            +
                    puts "尝试从 ext values 中获取"
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                    # 获取 store_file 并处理 $rootDir 路径
         | 
| 181 | 
            +
                    store_file = store_file_match ? ext_values[store_file_match[1]] : nil
         | 
| 182 | 
            +
                    if store_file && store_file.include?('$rootDir')
         | 
| 183 | 
            +
                      # 替换 $rootDir 为项目根目录的绝对路径
         | 
| 184 | 
            +
                      # 查找项目根目录(包含settings.gradle的目录)
         | 
| 185 | 
            +
                      root_dir = project_path
         | 
| 186 | 
            +
                      while root_dir && !File.exist?(File.join(root_dir, "settings.gradle"))
         | 
| 187 | 
            +
                        parent_dir = File.dirname(root_dir)
         | 
| 188 | 
            +
                        # 防止无限循环
         | 
| 189 | 
            +
                        break if parent_dir == root_dir
         | 
| 190 | 
            +
                        root_dir = parent_dir
         | 
| 191 | 
            +
                      end
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                      store_file = store_file.gsub('$rootDir', root_dir)
         | 
| 194 | 
            +
                      puts "处理后的 store_file 路径: #{store_file}"
         | 
| 195 | 
            +
                    end
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                    keystore_config = {
         | 
| 198 | 
            +
                      store_file: store_file,
         | 
| 199 | 
            +
                      store_password: store_password_match ? ext_values[store_password_match[1]] : nil,
         | 
| 200 | 
            +
                      key_alias: key_alias_match ? ext_values[key_alias_match[1]] : nil,
         | 
| 201 | 
            +
                      key_password: key_password_match ? ext_values[key_password_match[1]] : nil
         | 
| 202 | 
            +
                    }
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                    # 确保所有值都不为 nil
         | 
| 205 | 
            +
                    if keystore_config.values.any?(&:nil?)
         | 
| 206 | 
            +
                      puts "警告: 部分 keystore 配置为 nil: #{keystore_config}"
         | 
| 207 | 
            +
                    end
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                    return keystore_config
         | 
| 210 | 
            +
                  else
         | 
| 211 | 
            +
                    # 直接从配置块中提取硬编码的值
         | 
| 212 | 
            +
                    store_file = config_block.match(/storeFile\s+file\(['"]([^'"]+)['"]\)/m)&.[](1)
         | 
| 213 | 
            +
                    store_password = config_block.match(/storePassword\s+['"]([^'"]+)['"]/m)&.[](1)
         | 
| 214 | 
            +
                    key_alias = config_block.match(/keyAlias\s+['"]([^'"]+)['"]/m)&.[](1)
         | 
| 215 | 
            +
                    key_password = config_block.match(/keyPassword\s+['"]([^'"]+)['"]/m)&.[](1)
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                    keystore_config = {
         | 
| 218 | 
            +
                      store_file: store_file,
         | 
| 219 | 
            +
                      store_password: store_password,
         | 
| 220 | 
            +
                      key_alias: key_alias,
         | 
| 221 | 
            +
                      key_password: key_password
         | 
| 222 | 
            +
                    }
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                    # 确保所有值都不为 nil
         | 
| 225 | 
            +
                    if keystore_config.values.any?(&:nil?)
         | 
| 226 | 
            +
                      puts "警告: 部分 keystore 配置为 nil: #{keystore_config}"
         | 
| 227 | 
            +
                    end
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                    return keystore_config
         | 
| 230 | 
            +
                  end
         | 
| 231 | 
            +
                end
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                # 新增方法:从 ext 配置文件中获取变量值
         | 
| 234 | 
            +
                def get_ext_values(project_path)
         | 
| 235 | 
            +
                  ext_values = {}
         | 
| 236 | 
            +
             | 
| 237 | 
            +
                  # 查找可能的 ext 配置文件
         | 
| 238 | 
            +
                  ext_files = [
         | 
| 239 | 
            +
                    File.join(project_path, "buildScripts/cfg/ext_signing.gradle"),
         | 
| 240 | 
            +
                    File.join(project_path, "gradle.properties"),
         | 
| 241 | 
            +
                    File.join(project_path, "local.properties")
         | 
| 242 | 
            +
                  ]
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                  ext_files.each do |file_path|
         | 
| 245 | 
            +
                    next unless File.exist?(file_path)
         | 
| 246 | 
            +
             | 
| 247 | 
            +
                    content = File.read(file_path)
         | 
| 248 | 
            +
             | 
| 249 | 
            +
                    # 解析 ext.KEY = "VALUE" 格式
         | 
| 250 | 
            +
                    content.scan(/ext\.(\w+)\s*=\s*["']([^"']+)["']/m).each do |key, value|
         | 
| 251 | 
            +
                      ext_values[key] = value
         | 
| 252 | 
            +
                    end
         | 
| 253 | 
            +
             | 
| 254 | 
            +
                    # 解析 KEY=VALUE 格式
         | 
| 255 | 
            +
                    content.scan(/^(\w+)\s*=\s*["']?([^"'\n]+)["']?/m).each do |key, value|
         | 
| 256 | 
            +
                      ext_values[key] = value
         | 
| 257 | 
            +
                    end
         | 
| 258 | 
            +
                  end
         | 
| 259 | 
            +
             | 
| 260 | 
            +
                  ext_values
         | 
| 261 | 
            +
                end
         | 
| 262 | 
            +
             | 
| 263 | 
            +
                def unity_android_project?(project_path)
         | 
| 264 | 
            +
                  # 检查 unityLibrary 模块是否存在
         | 
| 265 | 
            +
                  unity_library_path = File.join(project_path, "unityLibrary")
         | 
| 266 | 
            +
                  return false unless File.directory?(unity_library_path)
         | 
| 267 | 
            +
             | 
| 268 | 
            +
                  # 检查 unityLibrary 的 build.gradle 是否存在
         | 
| 269 | 
            +
                  unity_gradle_path = File.join(unity_library_path, "build.gradle")
         | 
| 270 | 
            +
                  return false unless File.exist?(unity_gradle_path)
         | 
| 271 | 
            +
             | 
| 272 | 
            +
                  # 检查 build.gradle 中是否包含 Unity 特有的配置
         | 
| 273 | 
            +
                  content = File.read(unity_gradle_path)
         | 
| 274 | 
            +
                  content.include?("com.android.library") && content.include?("BuildIl2Cpp")
         | 
| 275 | 
            +
                end
         | 
| 276 | 
            +
             | 
| 277 | 
            +
                def find_android_subproject(project_path)
         | 
| 278 | 
            +
                  android_dir = File.join(project_path, "Unity")
         | 
| 279 | 
            +
                  return nil unless File.directory?(android_dir)
         | 
| 280 | 
            +
             | 
| 281 | 
            +
                  main_module = get_main_module(android_dir)
         | 
| 282 | 
            +
                  return nil unless main_module
         | 
| 283 | 
            +
             | 
| 284 | 
            +
                  src_main = File.join(main_module, "src/main")
         | 
| 285 | 
            +
                  return nil unless File.directory?(src_main)
         | 
| 286 | 
            +
             | 
| 287 | 
            +
                  manifest = File.join(src_main, "AndroidManifest.xml")
         | 
| 288 | 
            +
                  return nil unless File.exist?(manifest)
         | 
| 289 | 
            +
             | 
| 290 | 
            +
                  android_dir
         | 
| 291 | 
            +
                end
         | 
| 292 | 
            +
              end
         | 
| 293 | 
            +
            end
         | 
| @@ -0,0 +1,112 @@ | |
| 1 | 
            +
            require 'singleton'
         | 
| 2 | 
            +
            require_relative 'base_helper'
         | 
| 3 | 
            +
            require_relative 'gradle_helper'
         | 
| 4 | 
            +
            require_relative 'so_helper'
         | 
| 5 | 
            +
            require_relative 'apk_helper'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Pindo
         | 
| 8 | 
            +
              class AndroidBuildHelper
         | 
| 9 | 
            +
                include BaseAndroidHelper
         | 
| 10 | 
            +
                include GradleHelper
         | 
| 11 | 
            +
                include SoHelper
         | 
| 12 | 
            +
                include ApkHelper
         | 
| 13 | 
            +
                include Singleton
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                class << self
         | 
| 16 | 
            +
                  def share_instance
         | 
| 17 | 
            +
                    instance
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def auto_build_apk(project_dir, debug = false, ignore_sub = false)
         | 
| 22 | 
            +
                  # 检查 gradle.properties 中是否设置了 Java Home
         | 
| 23 | 
            +
                  gradle_properties_path = File.join(project_dir, "gradle.properties")
         | 
| 24 | 
            +
                  if File.exist?(gradle_properties_path)
         | 
| 25 | 
            +
                    puts "检查 gradle.properties 中的 Java Home 设置..."
         | 
| 26 | 
            +
                    content = File.read(gradle_properties_path)
         | 
| 27 | 
            +
                    java_home_match = content.match(/org\.gradle\.java\.home\s*=\s*(.+)/)
         | 
| 28 | 
            +
                    if java_home_match && !java_home_match[1].empty?
         | 
| 29 | 
            +
                      java_home_path = java_home_match[1].strip
         | 
| 30 | 
            +
                      puts "找到 Java Home 路径: #{java_home_path}"
         | 
| 31 | 
            +
                      # 设置环境变量
         | 
| 32 | 
            +
                      ENV['JAVA_HOME'] = java_home_path
         | 
| 33 | 
            +
                      ENV['PATH'] = "#{java_home_path}/bin:#{ENV['PATH']}"
         | 
| 34 | 
            +
                      puts "已设置 JAVA_HOME 环境变量为: #{java_home_path}"
         | 
| 35 | 
            +
                      puts "Java 版本信息:"
         | 
| 36 | 
            +
                      system("java -version")
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  # 检查
         | 
| 41 | 
            +
                  if !ignore_sub
         | 
| 42 | 
            +
                    sub_android_dir = find_android_subproject(project_dir)
         | 
| 43 | 
            +
                    if sub_android_dir
         | 
| 44 | 
            +
                      prepare_proj(sub_android_dir)
         | 
| 45 | 
            +
                      # 构建 AAB 文件
         | 
| 46 | 
            +
                      unless build_so_library(sub_android_dir)
         | 
| 47 | 
            +
                        raise RuntimeError, "编译SO库失败:"
         | 
| 48 | 
            +
                      end
         | 
| 49 | 
            +
                      copy_so_files(sub_android_dir, project_dir)
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  prepare_proj(project_dir)
         | 
| 54 | 
            +
                  build_apk(project_dir, debug)
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def get_application_id(project_path)
         | 
| 58 | 
            +
                  main_module = get_main_module(project_path)
         | 
| 59 | 
            +
                  return nil unless main_module
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  # 尝试从 build.gradle 获取
         | 
| 62 | 
            +
                  gradle_path = File.join(main_module, "build.gradle")
         | 
| 63 | 
            +
                  if File.exist?(gradle_path)
         | 
| 64 | 
            +
                    content = File.read(gradle_path)
         | 
| 65 | 
            +
                    if content =~ /applicationId\s+['"]([^'"]+)['"]/
         | 
| 66 | 
            +
                      return $1
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  # 如果 build.gradle 中没有,尝试从 AndroidManifest.xml 获取
         | 
| 71 | 
            +
                  manifest_path = File.join(main_module, "src", "main", "AndroidManifest.xml")
         | 
| 72 | 
            +
                  if File.exist?(manifest_path)
         | 
| 73 | 
            +
                    require 'nokogiri'
         | 
| 74 | 
            +
                    doc = Nokogiri::XML(File.read(manifest_path))
         | 
| 75 | 
            +
                    package = doc.at_xpath('//manifest/@package')&.value
         | 
| 76 | 
            +
                    return package if package
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  nil
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                def dsign(project_path, debug)
         | 
| 83 | 
            +
                  keystore_config = get_keystore_config(project_path, debug)
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  ks = keystore_config[:store_file]
         | 
| 86 | 
            +
                  puts "读取 keystore path = #{ks}"
         | 
| 87 | 
            +
                  ks_pass = keystore_config[:store_password]
         | 
| 88 | 
            +
                  puts "读取 keystore pass = #{ks_pass}"
         | 
| 89 | 
            +
                  key_alias = keystore_config[:key_alias]
         | 
| 90 | 
            +
                  puts "读取 key alias = #{key_alias}"
         | 
| 91 | 
            +
                  key_pass = keystore_config[:key_password]
         | 
| 92 | 
            +
                  puts "读取 key pass = #{key_pass}"
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                private
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                def prepare_proj(project_dir)
         | 
| 98 | 
            +
                  raise ArgumentError, "项目目录不能为空" if project_dir.nil?
         | 
| 99 | 
            +
                  raise ArgumentError, "项目目录不存在" unless File.directory?(project_dir)
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                  check_gradle_files(project_dir)
         | 
| 102 | 
            +
                  if unity_android_project?(project_dir)
         | 
| 103 | 
            +
                    update_build_gradle(project_dir)
         | 
| 104 | 
            +
                    update_gradle_version(project_dir)
         | 
| 105 | 
            +
                    modify_il2cpp_config(project_dir)
         | 
| 106 | 
            +
                    remove_desktop_google_service(project_dir)
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
                rescue StandardError => e
         | 
| 109 | 
            +
                  raise Informative, "准备项目失败: #{e.message}"
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
              end
         | 
| 112 | 
            +
            end
         | 
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            require_relative 'base_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Pindo
         | 
| 4 | 
            +
              module GradleHelper
         | 
| 5 | 
            +
                include BaseAndroidHelper
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def check_gradle_files(project_path)
         | 
| 8 | 
            +
                  # 检查是否存在 gradlew 文件 如果没有则执行复制操作
         | 
| 9 | 
            +
                  gradlew_path = File.join(project_path, "gradlew")
         | 
| 10 | 
            +
                  if File.exist?(gradlew_path)
         | 
| 11 | 
            +
                    puts "gradlew file already exists, skip copying."
         | 
| 12 | 
            +
                  else
         | 
| 13 | 
            +
                     # 复制gradle相关文件
         | 
| 14 | 
            +
                    gradlew_content = File.read(get_build_tools[:gradlew])
         | 
| 15 | 
            +
                    File.write(gradlew_path, gradlew_content)
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                  # 设置gradlew可执行权限
         | 
| 18 | 
            +
                  system("chmod", "777", gradlew_path)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  wrapper_jar_path = File.join(project_path, "gradle/wrapper/gradle-wrapper.jar")
         | 
| 21 | 
            +
                  if File.exist?(wrapper_jar_path)
         | 
| 22 | 
            +
                    puts "gradle-wrapper.jar file already exists, skip copying."
         | 
| 23 | 
            +
                  else
         | 
| 24 | 
            +
                    FileUtils.mkdir_p(File.dirname(wrapper_jar_path))
         | 
| 25 | 
            +
                    gradle_wrapper_content = File.read(get_build_tools[:gradle_wrapper])
         | 
| 26 | 
            +
                    File.write(wrapper_jar_path, gradle_wrapper_content)
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def update_build_gradle(project_path)
         | 
| 31 | 
            +
                  # 更新build.gradle
         | 
| 32 | 
            +
                  build_gradle_path = File.join(project_path, "build.gradle")
         | 
| 33 | 
            +
                  content = File.read(build_gradle_path)
         | 
| 34 | 
            +
                  content.gsub!("classpath 'com.android.tools.build:gradle:4.0.1'",
         | 
| 35 | 
            +
                                "classpath 'com.android.tools.build:gradle:4.2.2'")
         | 
| 36 | 
            +
                  File.write(build_gradle_path, content)
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def update_gradle_version(project_path)
         | 
| 40 | 
            +
                  # 更新gradle wrapper版本
         | 
| 41 | 
            +
                  wrapper_path = File.join(project_path, "gradle/wrapper/gradle-wrapper.properties")
         | 
| 42 | 
            +
                  content = File.read(wrapper_path)
         | 
| 43 | 
            +
                  content.gsub!("gradle-6.1.1-bin.zip", "gradle-7.2-bin.zip")
         | 
| 44 | 
            +
                  File.write(wrapper_path, content)
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            module Pindo
         | 
| 2 | 
            +
              module SoHelper
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def build_so_library(project_path)
         | 
| 5 | 
            +
                  # 编译so库
         | 
| 6 | 
            +
                  Dir.chdir(project_path) do
         | 
| 7 | 
            +
                    system("./gradlew unityLibrary:BuildIl2CppTask")
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def copy_so_files(source_path, target_path)
         | 
| 12 | 
            +
                  # 复制so文件到正确的目录
         | 
| 13 | 
            +
                  src_dir = File.join(source_path, "unityLibrary/src/main/assets")
         | 
| 14 | 
            +
                  dst_dir = File.join(target_path, "unityLibrary/src/main/assets")
         | 
| 15 | 
            +
                  FileUtils.cp_r(src_dir, dst_dir) if File.directory?(src_dir)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| @@ -1,11 +1,12 @@ | |
| 1 1 | 
             
            require 'singleton'
         | 
| 2 2 | 
             
            require 'fileutils'
         | 
| 3 3 | 
             
            require 'xcodeproj' # 用于iOS项目检查
         | 
| 4 | 
            -
             | 
| 4 | 
            +
            require_relative '../android/base_helper'
         | 
| 5 5 | 
             
            module Pindo
         | 
| 6 6 |  | 
| 7 7 | 
             
                  class BuildHelper
         | 
| 8 8 | 
             
                    include Singleton
         | 
| 9 | 
            +
                    include BaseAndroidHelper
         | 
| 9 10 | 
             
                    include Pindo::Githelper
         | 
| 10 11 |  | 
| 11 12 | 
             
                    class << self
         | 
| @@ -14,6 +15,23 @@ module Pindo | |
| 14 15 | 
             
                      end
         | 
| 15 16 | 
             
                    end
         | 
| 16 17 |  | 
| 18 | 
            +
                    def delete_libtarget_firebase_shell(project_path)
         | 
| 19 | 
            +
                      if File.directory?(File.join(project_path, 'Unity')) && File.exist?(File.join(project_path, 'Unity', 'Unity-iPhone.xcodeproj'))
         | 
| 20 | 
            +
                        unity_project_path = File.join(project_path, 'Unity', 'Unity-iPhone.xcodeproj')
         | 
| 21 | 
            +
                        xcdoe_unitylib_project = Xcodeproj::Project::open(unity_project_path)
         | 
| 22 | 
            +
                        xcdoe_unitylib_project.targets.each do |target|
         | 
| 23 | 
            +
                          if target.name == 'Unity-iPhone'
         | 
| 24 | 
            +
                            target.shell_script_build_phases.each do |phase|
         | 
| 25 | 
            +
                              if phase.name.eql?("Crashlytics Run Script")
         | 
| 26 | 
            +
                                target.remove_build_phase(phase)
         | 
| 27 | 
            +
                              end
         | 
| 28 | 
            +
                            end
         | 
| 29 | 
            +
                          end
         | 
| 30 | 
            +
                        end
         | 
| 31 | 
            +
                        xcdoe_unitylib_project.save()
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 17 35 | 
             
                    def check_check_and_install_cliff(project_path)
         | 
| 18 36 | 
             
                      if is_git_directory?(local_repo_dir: project_path)
         | 
| 19 37 | 
             
                        current_git_root_path = git_root_directory(local_repo_dir: project_path)
         | 
| @@ -39,7 +57,7 @@ module Pindo | |
| 39 57 | 
             
                        build_ios.log
         | 
| 40 58 | 
             
                        feishu.json
         | 
| 41 59 | 
             
                        CHANGELOG.md
         | 
| 42 | 
            -
             | 
| 60 | 
            +
             | 
| 43 61 | 
             
                        # Platform specific directories
         | 
| 44 62 | 
             
                        /GoodPlatform/iOS/config.json
         | 
| 45 63 | 
             
                        /GoodPlatform/iOS/*
         | 
| @@ -81,7 +99,7 @@ module Pindo | |
| 81 99 | 
             
                        commit_message = "docs: 添加日志变更配置".encode('UTF-8')
         | 
| 82 100 | 
             
                        git!(%W(-C #{current_git_root_path} commit -m #{commit_message}))
         | 
| 83 101 | 
             
                        git!(%W(-C #{current_git_root_path} push origin #{current_branch}))
         | 
| 84 | 
            -
             | 
| 102 | 
            +
             | 
| 85 103 | 
             
                      else
         | 
| 86 104 | 
             
                        Funlog.instance.fancyinfo_error("当前目录不是git仓库,请在git仓库根目录下执行此命令")
         | 
| 87 105 | 
             
                        raise Informative, "当前目录不是git仓库,请在git仓库根目录下执行此命令"
         | 
| @@ -109,33 +127,33 @@ module Pindo | |
| 109 127 | 
             
                    def check_is_need_add_tag?(project_path)
         | 
| 110 128 | 
             
                      tag_action_parms = nil
         | 
| 111 129 | 
             
                      is_need_add_tag = false
         | 
| 112 | 
            -
             | 
| 130 | 
            +
             | 
| 113 131 | 
             
                      if is_git_directory?(local_repo_dir: project_path)
         | 
| 114 132 | 
             
                        current_git_root_path = git_root_directory(local_repo_dir: project_path)
         | 
| 115 133 | 
             
                        latest_tag = get_latest_version_tag(project_dir: current_git_root_path)
         | 
| 116 | 
            -
             | 
| 134 | 
            +
             | 
| 117 135 | 
             
                        unless is_tag_at_head?(git_root_dir: current_git_root_path, tag_name: latest_tag)
         | 
| 118 136 | 
             
                          cli = HighLine.new
         | 
| 119 137 | 
             
                          menu_options = {
         | 
| 120 | 
            -
                            "新增版本号,打新Tag" => -> { | 
| 138 | 
            +
                            "新增版本号,打新Tag" => -> {
         | 
| 121 139 | 
             
                              tag_action_parms = []
         | 
| 122 | 
            -
                              :new_tag | 
| 140 | 
            +
                              :new_tag
         | 
| 123 141 | 
             
                            },
         | 
| 124 | 
            -
                            "将上次的Tag删除重新打Tag" => -> { | 
| 142 | 
            +
                            "将上次的Tag删除重新打Tag" => -> {
         | 
| 125 143 | 
             
                              tag_action_parms = []
         | 
| 126 144 | 
             
                              tag_action_parms << "--retag"
         | 
| 127 | 
            -
                              :recreate_tag | 
| 145 | 
            +
                              :recreate_tag
         | 
| 128 146 | 
             
                            },
         | 
| 129 | 
            -
                            "不需要Tag继续编译且上传,手动修改上传备注" => -> { | 
| 147 | 
            +
                            "不需要Tag继续编译且上传,手动修改上传备注" => -> {
         | 
| 130 148 | 
             
                              puts ""
         | 
| 131 | 
            -
                              :continue_without_tag | 
| 149 | 
            +
                              :continue_without_tag
         | 
| 132 150 | 
             
                            },
         | 
| 133 | 
            -
                            "终止退出编译" => -> { | 
| 151 | 
            +
                            "终止退出编译" => -> {
         | 
| 134 152 | 
             
                              raise Informative, "终止退出编译!"
         | 
| 135 | 
            -
                              :exit | 
| 153 | 
            +
                              :exit
         | 
| 136 154 | 
             
                            }
         | 
| 137 155 | 
             
                          }
         | 
| 138 | 
            -
             | 
| 156 | 
            +
             | 
| 139 157 | 
             
                          result = cli.choose do |menu|
         | 
| 140 158 | 
             
                            menu.header = "当前代码并没有打Tag,上传的Changelog需要Tag"
         | 
| 141 159 | 
             
                            menu.prompt = "请选中打Tag的方式, 请输入选项(1/2/3...):"
         | 
| @@ -143,11 +161,11 @@ module Pindo | |
| 143 161 | 
             
                              menu.choice(option) { action.call }
         | 
| 144 162 | 
             
                            end
         | 
| 145 163 | 
             
                          end
         | 
| 146 | 
            -
             | 
| 164 | 
            +
             | 
| 147 165 | 
             
                          is_need_add_tag = !tag_action_parms.nil?
         | 
| 148 166 | 
             
                        end
         | 
| 149 167 | 
             
                      end
         | 
| 150 | 
            -
             | 
| 168 | 
            +
             | 
| 151 169 | 
             
                      return [is_need_add_tag, tag_action_parms]
         | 
| 152 170 | 
             
                    end
         | 
| 153 171 |  | 
| @@ -156,10 +174,10 @@ module Pindo | |
| 156 174 | 
             
                      project_settings_path = File.join(project_path, "ProjectSettings")
         | 
| 157 175 | 
             
                      assets_path = File.join(project_path, "Assets")
         | 
| 158 176 | 
             
                      packages_path = File.join(project_path, "Packages")
         | 
| 159 | 
            -
             | 
| 177 | 
            +
             | 
| 160 178 | 
             
                      # Unity工程必须包含这些目录和文件
         | 
| 161 | 
            -
                      File.directory?(project_settings_path) && | 
| 162 | 
            -
                      File.directory?(assets_path) && | 
| 179 | 
            +
                      File.directory?(project_settings_path) &&
         | 
| 180 | 
            +
                      File.directory?(assets_path) &&
         | 
| 163 181 | 
             
                      File.directory?(packages_path) &&
         | 
| 164 182 | 
             
                      File.exist?(File.join(project_settings_path, "ProjectSettings.asset"))
         | 
| 165 183 | 
             
                    end
         | 
| @@ -168,22 +186,22 @@ module Pindo | |
| 168 186 | 
             
                      # 检查iOS工程的关键文件
         | 
| 169 187 | 
             
                      xcodeproj_files = Dir.glob(File.join(project_path, "*.xcodeproj"))
         | 
| 170 188 | 
             
                      workspace_files = Dir.glob(File.join(project_path, "*.xcworkspace"))
         | 
| 171 | 
            -
             | 
| 189 | 
            +
             | 
| 172 190 | 
             
                      # 至少要有.xcodeproj文件或.xcworkspace文件
         | 
| 173 191 | 
             
                      return false if xcodeproj_files.empty? && workspace_files.empty?
         | 
| 174 | 
            -
             | 
| 192 | 
            +
             | 
| 175 193 | 
             
                      if !xcodeproj_files.empty?
         | 
| 176 194 | 
             
                        # 检查.xcodeproj内部结构
         | 
| 177 195 | 
             
                        project_file = File.join(xcodeproj_files.first, "project.pbxproj")
         | 
| 178 196 | 
             
                        return true if File.exist?(project_file)
         | 
| 179 197 | 
             
                      end
         | 
| 180 | 
            -
             | 
| 198 | 
            +
             | 
| 181 199 | 
             
                      if !workspace_files.empty?
         | 
| 182 200 | 
             
                        # 检查.xcworkspace内部结构
         | 
| 183 201 | 
             
                        contents_file = File.join(workspace_files.first, "contents.xcworkspacedata")
         | 
| 184 202 | 
             
                        return true if File.exist?(contents_file)
         | 
| 185 203 | 
             
                      end
         | 
| 186 | 
            -
             | 
| 204 | 
            +
             | 
| 187 205 | 
             
                      false
         | 
| 188 206 | 
             
                    end
         | 
| 189 207 |  | 
| @@ -191,28 +209,23 @@ module Pindo | |
| 191 209 | 
             
                      # 检查Android工程的关键文件和目录
         | 
| 192 210 | 
             
                      gradle_file = File.exist?(File.join(project_path, "build.gradle"))
         | 
| 193 211 | 
             
                      settings_gradle = File.exist?(File.join(project_path, "settings.gradle"))
         | 
| 194 | 
            -
             | 
| 195 | 
            -
                      
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                      main_module = get_main_module(project_path)
         | 
| 214 | 
            +
             | 
| 196 215 | 
             
                      # Android Studio项目结构
         | 
| 197 | 
            -
                      if gradle_file && settings_gradle &&  | 
| 198 | 
            -
                        app_gradle = File.exist?(File.join( | 
| 199 | 
            -
                        app_manifest = File.exist?(File.join( | 
| 216 | 
            +
                      if gradle_file && settings_gradle && main_module
         | 
| 217 | 
            +
                        app_gradle = File.exist?(File.join(main_module, "build.gradle"))
         | 
| 218 | 
            +
                        app_manifest = File.exist?(File.join(main_module, "src", "main", "AndroidManifest.xml"))
         | 
| 200 219 | 
             
                        return true if app_gradle && app_manifest
         | 
| 201 220 | 
             
                      end
         | 
| 202 | 
            -
             | 
| 203 | 
            -
                      # 传统Eclipse项目结构
         | 
| 204 | 
            -
                      if File.directory?(File.join(project_path, "src"))
         | 
| 205 | 
            -
                        manifest = File.join(project_path, "AndroidManifest.xml")
         | 
| 206 | 
            -
                        return true if File.exist?(manifest)
         | 
| 207 | 
            -
                      end
         | 
| 208 | 
            -
                      
         | 
| 221 | 
            +
             | 
| 209 222 | 
             
                      false
         | 
| 210 223 | 
             
                    end
         | 
| 211 224 |  | 
| 212 225 | 
             
                    def project_type(project_path)
         | 
| 213 226 | 
             
                      raise ArgumentError, "项目路径不能为空" if project_path.nil? || project_path.empty?
         | 
| 214 227 | 
             
                      raise ArgumentError, "项目路径不存在: #{project_path}" unless File.directory?(project_path)
         | 
| 215 | 
            -
             | 
| 228 | 
            +
             | 
| 216 229 | 
             
                      return :unity if unity_project?(project_path)
         | 
| 217 230 | 
             
                      return :ios if ios_project?(project_path)
         | 
| 218 231 | 
             
                      return :android if android_project?(project_path)
         | 
| @@ -276,4 +289,4 @@ module Pindo | |
| 276 289 | 
             
                    end
         | 
| 277 290 | 
             
                  end
         | 
| 278 291 |  | 
| 279 | 
            -
            end | 
| 292 | 
            +
            end
         |