├── .gitignore ├── .python-version ├── .vscode └── launch.json ├── README.md ├── flutter ├── __init__.py ├── shorebird.py └── switch_flutter_integrate.py ├── iOS ├── __init__.py ├── get_build_config.py ├── push_dev_ipa.py └── save_build_config.py ├── requirements.txt └── utils ├── __init__.py ├── file_util.py ├── plist_util.py ├── release_version.py ├── str_util.py ├── upload_fir.py ├── upload_pgyer.py └── xcode_util.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom 2 | **/.idea 3 | **/*.pyc 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | # .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # poetry 102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 103 | # This is especially recommended for binary packages to ensure reproducibility, and is more 104 | # commonly ignored for libraries. 105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 106 | #poetry.lock 107 | 108 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 109 | __pypackages__/ 110 | 111 | # Celery stuff 112 | celerybeat-schedule 113 | celerybeat.pid 114 | 115 | # SageMath parsed files 116 | *.sage.py 117 | 118 | # Environments 119 | .env 120 | .venv 121 | env/ 122 | venv/ 123 | ENV/ 124 | env.bak/ 125 | venv.bak/ 126 | 127 | # Spyder project settings 128 | .spyderproject 129 | .spyproject 130 | 131 | # Rope project settings 132 | .ropeproject 133 | 134 | # mkdocs documentation 135 | /site 136 | 137 | # mypy 138 | .mypy_cache/ 139 | .dmypy.json 140 | dmypy.json 141 | 142 | # Pyre type checker 143 | .pyre/ 144 | 145 | # pytype static type analyzer 146 | .pytype/ 147 | 148 | # Cython debug symbols 149 | cython_debug/ 150 | 151 | # PyCharm 152 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can 153 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 154 | # and can be added to the global gitignore or merged into this file. For a more nuclear 155 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 156 | #.idea/ -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | env383_ScriptBox -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python: 当前文件", 6 | "type": "python", 7 | "request": "launch", 8 | "program": "${file}", 9 | "console": "integratedTerminal" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScriptBox 2 | 个人脚本工具箱 3 | 4 | 使用的 `python` 版本皆为 `Python3` 5 | 6 | 7 | 8 | ## 文章 9 | 10 | |标题|掘金|博客|公众号| 11 | |-|-|-|-| 12 | |iOS - 实现25秒内完成测试包出包|[【链接】](https://juejin.cn/post/7057177057637662728)|[【链接】](https://fullstackaction.com/pages/9b40a4/)|[【链接】](https://mp.weixin.qq.com/s/cZ8gUWuOt7gV74JBkqa8GQ)| 13 | 14 | 15 | 16 | ## 使用 17 | 18 | ### `push_dev_ipa.py` 19 | 20 | > 功能:上传 `.app` 文件至蒲公英或 `fir` 21 | 22 | 23 | 24 | 一、目录结构: 25 | 26 | ```shell 27 | 项目(项目路径是到我这一级) 28 | ├── LXFCardsLayout 29 | ├── LXFCardsLayout.xcodeproj 30 | ├── LXFCardsLayout.xcworkspace 31 | ├── Podfile 32 | ├── Podfile.lock 33 | ├── Pods 34 | ├── fastlane 35 | │   └── appack_set.json(配置文件) 36 | └── script 37 | ├── build_time_conf.ini 38 | └── save_build_config.py 39 | ``` 40 | 41 | `save_build_config.py` 和 `appack_set.json` 按如上结构放好,也可以自行存放和调整脚本 42 | 43 | 44 | 45 | 二、配置 46 | 47 | 在项目中的 `Run Script` 添加命令 48 | 49 | ```shell 50 | cd script 51 | python3 save_build_config.py # 记录编译时配置 52 | ``` 53 | 54 | 该操作的用意:在编译的过程中,将 `app` 包所在路径保持至 `script` 目录的 `build_time_conf.ini` 文件中,并使用 `build_dir_path` 做为其 `key`。 55 | 56 | 注:鉴于多人协作下,该 `build_time_conf.ini` 文件必定不可能相同,所以建议将该 `build_time_conf.ini` 文件添加至 `.gitignore` 中 57 | 58 | 59 | 60 | 三、脚本命令 61 | 62 | ```shell 63 | push_dev_ipa.py -p "项目路径" -t "target名" --platform="pgyer或fir" 64 | 65 | # platform 不传,则默认为 pgyer 66 | # 如: 67 | # python push_dev_ipa.py -p "/Users/lxf/Desktop/LXFCardsLayout/Example" -t "LXFCardsLayout_Example" 68 | # python push_dev_ipa.py -p "/Users/lxf/Desktop/LXFCardsLayout/Example" -t "LXFCardsLayout_Example" --platform="fir" 69 | ``` 70 | 71 | 72 | 73 | 74 | 75 | ## 配置文件 76 | 77 | ### `appack_set.json` 78 | 79 | ```json 80 | { 81 | "pgyer_api_key": "蒲公英api_key", 82 | "pgyer_user_key": "蒲公英user_key", 83 | "pgyer_api_password": "安装密码", 84 | "fir_type": "ios", 85 | "fir_api_token": "fir的api_token" 86 | } 87 | ``` 88 | 89 | 90 | 91 | ## 作者 92 | 93 | - LinXunFeng 94 | - email: [linxunfeng@yeah.net](mailto:linxunfeng@yeah.net) 95 | - Blogs: [全栈行动](https://fullstackaction.com/) | [掘金](https://juejin.im/user/58f8065e61ff4b006646c72d/posts) 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /flutter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinXunFeng/script_box/29335dd24ea68334b3c8744ce233fd8538dd7e21/flutter/__init__.py -------------------------------------------------------------------------------- /flutter/shorebird.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 4 | 5 | import getopt 6 | from enum import Enum 7 | import xml.etree.ElementTree as ET 8 | from utils import file_util as FileTool 9 | from utils import release_version as ReleaseVersionTool 10 | 11 | class ErrorCode(Enum): 12 | """错误代码""" 13 | NORMAL = 0 # 正常 14 | PARAMS_ERR = 1 # 入参错误 15 | UNKNOW = 2 # 未知 16 | PARSE_ERR = 3 # 解析 17 | NEED_RE_EXECUTE = 4 # 需要重新执行 18 | 19 | class Mode(Enum): 20 | """模式""" 21 | RELEASE = 0 # 发布 22 | PATCH = 1 # 补丁 23 | 24 | class Platform(Enum): 25 | """平台""" 26 | ANDROID = 0 # 安卓 27 | IOS = 1 # iOS 28 | 29 | def handle_ios(): 30 | """ 31 | 处理iOS项目 32 | """ 33 | # 1. 读取主版本号 34 | # 请将 OCProject 修改为你们自己的工程名 35 | xcodeproj_path = os.path.join(project_path, 'OCProject.xcodeproj') 36 | version = ReleaseVersionTool.fetch_project_version( 37 | xcodeproj_path=xcodeproj_path, 38 | target_name='OCProject', 39 | ) 40 | if version == None: 41 | print("未找到版本号") 42 | sys.exit(ErrorCode.PARSE_ERR.value) 43 | main_version, build_number = version 44 | # print("main_version -- ", main_version) 45 | # print("build_version -- ", build_number) 46 | # 2. 执行 release 命令 47 | # shorebird release ios-framework --release-version 7.0.0+1 48 | command = '' 49 | version_build = str(main_version) + '+' + str(build_number) 50 | if mode == Mode.RELEASE: 51 | command = 'shorebird release ios-framework --release-version ' + version_build 52 | if flutter_version is not None: 53 | command += ' --flutter-version=' + flutter_version 54 | elif mode == Mode.PATCH: 55 | command = 'shorebird patch ios-framework --release-version ' + version_build 56 | else: 57 | print("未知模式") 58 | sys.exit(ErrorCode.PARAMS_ERR.value) 59 | # command += ' -v' # 输出详细日志 60 | print("command -- ", command) 61 | os.chdir(shell_path) 62 | print("当前作业路径 -- ", os.getcwd()) 63 | result = os.system(command) 64 | if result != 0: 65 | sys.exit(ErrorCode.UNKNOW.value) 66 | 67 | # 3. 打补丁(放在最后是因为确保相应的Flutter源码已经拉下来了) 68 | patchFix() 69 | 70 | # 4. 创建 Flutter.podspec 71 | # 内容与 flutter_module/.ios/Flutter/Flutter.podspec 保持一致,仅修改 s.vendored_frameworks 72 | if mode == Mode.RELEASE: 73 | # 如果是 release 模式,则还需要创建对应的 Flutter.podspec 74 | flutter_module_path = os.path.join(project_path, '../flutter_module') 75 | flutter_module_path = os.path.abspath(flutter_module_path) # 转绝对路径 76 | ios_framework_path = os.path.join(flutter_module_path, 'build/ios/framework', 'Release') 77 | flutter_podspec = """ 78 | Pod::Spec.new do |s| 79 | s.name = 'Flutter' 80 | s.version = '1.0.0' 81 | s.summary = 'A UI toolkit for beautiful and fast apps.' 82 | s.homepage = 'https://flutter.dev' 83 | s.license = { :type => 'BSD' } 84 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } 85 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } 86 | s.ios.deployment_target = '11.0' 87 | # Framework linking is handled by Flutter tooling, not CocoaPods. 88 | # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs. 89 | s.vendored_frameworks = 'Flutter.xcframework', 'App.xcframework' 90 | end 91 | """ 92 | FileTool.rewrite_file(os.path.join(ios_framework_path, 'Flutter.podspec'), flutter_podspec) 93 | 94 | def handle_android(): 95 | """ 96 | 处理安卓项目 97 | """ 98 | # 1. 读取主版本号 99 | file_path = os.path.join(project_path, 'app/src/main/AndroidManifest.xml') 100 | # 解析XML文件 101 | tree = ET.parse(file_path) 102 | # 获取根元素 103 | root = tree.getroot() 104 | version = None 105 | build_number = 1 106 | if root.tag == 'manifest': 107 | # print("version_name -- ", root.attrib) 108 | for attr in root.attrib: 109 | if attr.__contains__('versionName'): 110 | # 找到主版本号 111 | version = root.attrib[attr] 112 | print("version_name -- ", version) 113 | if attr.__contains__('versionCode'): 114 | # 找到主版本号 115 | build_number = root.attrib[attr] 116 | print("versionCode -- ", build_number) 117 | if version == None: 118 | print("未找到主版本号") 119 | sys.exit(ErrorCode.PARSE_ERR.value) 120 | # 2. 执行 release 命令 121 | # shorebird release aar --release-version 7.0.0+1 122 | command = '' 123 | version_build = str(version) + '+' + str(build_number) 124 | if mode == Mode.RELEASE: 125 | command = 'shorebird release aar --release-version ' + version_build 126 | if flutter_version is not None: 127 | command += ' --flutter-version=' + flutter_version 128 | elif mode == Mode.PATCH: 129 | command = 'shorebird patch aar --release-version ' + version_build 130 | else: 131 | print("未知模式") 132 | sys.exit(ErrorCode.PARAMS_ERR.value) 133 | # command += ' -v' # 输出详细日志 134 | print("command -- ", command) 135 | os.chdir(shell_path) 136 | print("当前作业路径 -- ", os.getcwd()) 137 | result = os.system(command) 138 | if result != 0: 139 | sys.exit(ErrorCode.UNKNOW.value) 140 | # 3. 打补丁(放在最后是因为确保相应的Flutter源码已经拉下来了) 141 | patchFix() 142 | 143 | def patchFix(): 144 | # 打补丁 145 | if patch_fix: 146 | os.chdir(fetchFlutterDirPath()) 147 | # 补丁来自:【Flutter - 升级3.19之后页面多次rebuild?🤨】(https://juejin.cn/post/7349124917378695180#heading-5) 148 | # 下载补丁 149 | os.system('curl -O https://raw.githubusercontent.com/LinXunFeng/flutter_assets/main/patch/01_rollbak_3_19_routes_change/0001-Roll-back-changes-to-routes.dart.patch') 150 | # 应用补丁 151 | result = os.system('git apply 0001-Roll-back-changes-to-routes.dart.patch') 152 | if result == 0: 153 | # 打成功了,说明是头一次打补丁,可能是重新拉了最新的flutter代码,需要重新编译 154 | print("====== 📌 打 Flutter 3.19 的补丁成功了,需在 Shorebird Console 上删除相应版本后,再来 Jenkins 执行一次 📌 ======") 155 | sys.exit(ErrorCode.NEED_RE_EXECUTE.value) 156 | elif result == 256: 157 | # 说明已经打过补丁了 158 | print("====== ✅ 先前打过 Flutter 3.19 的补丁了 ✅ ======") 159 | else: 160 | print("====== 🚨 出现了未知的情况 🚨 ======") 161 | sys.exit(ErrorCode.UNKNOW.value) 162 | 163 | def fetchFlutterDirPath(): 164 | """ 165 | 获取Flutter的目录路径 166 | """ 167 | home_path = os.environ['HOME'] 168 | shorebird_dir_path = os.path.join(home_path, '.shorebird') 169 | return os.path.join(shorebird_dir_path, 'bin/cache/flutter', fetchFlutterRevision()) 170 | 171 | def fetchFlutterRevision(): 172 | """ 173 | 读取当前的Flutter魔改版的CommitID 174 | """ 175 | home_path = os.environ['HOME'] 176 | shorebird_dir_path = os.path.join(home_path, '.shorebird') 177 | flutter_version_path = os.path.join(shorebird_dir_path, 'bin/internal/flutter.version') 178 | # 读取最新 flutter 的 hash 179 | # 931d8d0d82f0c1a29731ca7601100804d09b3f33\n 180 | last_flutter_version_revision = FileTool.read_file(flutter_version_path) 181 | # 去除换行符 182 | last_flutter_version_revision = last_flutter_version_revision.strip('\n') 183 | 184 | # 如果没有指定 flutter 版本,则返回最新的 flutter 版本 commit id 185 | if flutter_version is None: 186 | return last_flutter_version_revision 187 | 188 | flutter_cache_dir_path = os.path.join(shorebird_dir_path, 'bin/cache/flutter') 189 | last_flutter_cache_dir_path = os.path.join(flutter_cache_dir_path, last_flutter_version_revision) 190 | # 切到对应的flutter版本的目录 191 | os.chdir(last_flutter_cache_dir_path) 192 | # 获取指定的flutter版本的hash 193 | # git rev-parse --verify refs/remotes/origin/flutter_release/3.19.3 194 | git_command = 'git rev-parse --verify refs/remotes/origin/flutter_release/' + flutter_version 195 | flutter_version_revision=os.popen(git_command).readlines()[0].strip('\n') 196 | return flutter_version_revision 197 | 198 | if __name__ == "__main__": 199 | argv = sys.argv[1:] 200 | target_name = None # target名称 201 | mode = Mode.RELEASE # 模式 202 | platform = Platform.ANDROID # 平台 203 | patch_fix = False # 是否打 Flutter 补丁 204 | flutter_version = None # Flutter 版本 205 | 206 | try: 207 | opts, args = getopt.getopt(argv, "p:s:m:f:v:x:", ["path=", "shell=", "mode=", "platform=", "flutter_version=", "patch_fix="]) 208 | except getopt.GetoptError: 209 | print('shorebird.py -p "原生项目路径" -s "Flutter壳项目路径" -m "模式(release|patch)" -f "平台(ios|android)" --flutter_version=3.19.3 --patch_fix=1') 210 | sys.exit(1) 211 | 212 | for opt, arg in opts: 213 | if opt in ["-p", "--path"]: 214 | project_path = arg 215 | if len(project_path) == 0: 216 | print('请输入原生工程地址') 217 | sys.exit(ErrorCode.PARAMS_ERR.value) 218 | if opt in ["-s", "--shell"]: 219 | shell_path = arg 220 | if len(shell_path) == 0: 221 | print('请输入壳工程地址') 222 | sys.exit(ErrorCode.PARAMS_ERR.value) 223 | if opt in ["-m", "--mode"]: 224 | if (arg != "release" and arg != "patch"): 225 | print('请输入正确的模式') 226 | sys.exit(ErrorCode.PARAMS_ERR.value) 227 | if (arg == 'release'): 228 | mode = Mode.RELEASE 229 | else: 230 | mode = Mode.PATCH 231 | if opt in ["-f", "--platform"]: 232 | if (arg != "ios" and arg != "android"): 233 | print('请输入正确的平台') 234 | sys.exit(ErrorCode.PARAMS_ERR.value) 235 | if (arg == 'ios'): 236 | platform = Platform.IOS 237 | else: 238 | platform = Platform.ANDROID 239 | if opt in ["-v", "--flutter_version"]: 240 | flutter_version = arg 241 | if opt in ["-x", "--patch_fix"]: 242 | patch_fix = bool(arg) 243 | 244 | if platform == Platform.IOS: 245 | handle_ios() 246 | else: 247 | handle_android() 248 | 249 | # release 250 | # python shorebird.py -p '安卓原生工程项目路径' -s 'Flutter壳项目路径' -m release -f android --flutter_version=3.19.3 --patch_fix=1 251 | # python shorebird.py -p 'iOS原生工程项目路径' -s 'Flutter壳项目路径' -m release -f ios --flutter_version=3.19.3 --patch_fix=1 252 | 253 | # patch 254 | # python shorebird.py -p '安卓原生工程项目路径' -s 'Flutter壳项目路径' -m patch -f android 255 | # python shorebird.py -p 'iOS原生工程项目路径' -s 'Flutter壳项目路径' -m patch -f ios 256 | -------------------------------------------------------------------------------- /flutter/switch_flutter_integrate.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 4 | 5 | import getopt 6 | from enum import Enum 7 | from utils import file_util as FileTool 8 | 9 | 10 | class ErrorCode(Enum): 11 | """错误代码""" 12 | NORMAL = 0 # 正常 13 | PARAMS_ERR = 1 # 入参错误 14 | UNKNOW = 2 # 未知 15 | PARSE_ERR = 3 # 解析 16 | 17 | class Mode(Enum): 18 | """集成模式""" 19 | BINARY = 0 # 二进制 20 | SOURCE = 1 # 源码 21 | 22 | class Platform(Enum): 23 | """平台""" 24 | ANDROID = 0 # 安卓 25 | IOS = 1 # iOS 26 | 27 | def handle_ios(): 28 | """ 29 | 处理iOS项目 30 | """ 31 | is_binary = flutter_build_mode == Mode.BINARY 32 | # 源码集成 33 | # install_all_flutter_pods(flutter_application_path) 34 | 35 | # 二进制集成 36 | # pod 'Flutter', path: 'xxx/flutter_module/build/ios/framework/Debug' 37 | # install_flutter_plugin_pods(flutter_application_path) 38 | 39 | podfile_path = os.path.join(project_path, 'Podfile') 40 | flutter_module_path = os.path.join(project_path, '../flutter_module') 41 | flutter_module_path = os.path.abspath(flutter_module_path) # 转绝对路径 42 | flutter_module_flutter_path = os.path.join(flutter_module_path, '.ios/Flutter') 43 | ios_framework_path = os.path.join(flutter_module_path, 'build/ios/framework', 'Release') 44 | ios_framework_path = os.path.abspath(ios_framework_path) # 转绝对路径 45 | # print('flutter_module_path -- ', flutter_module_path) 46 | # print('flutter_module_flutter_path -- ', flutter_module_flutter_path) 47 | 48 | # 1. 篡改 podhelper.rb 49 | flutter_module_podhelper_path = os.path.join(flutter_module_flutter_path, 'podhelper.rb') 50 | install_flutter_plugin_pods_method = 'def install_flutter_plugin_pods(flutter_application_path)' 51 | # 内部的 flutter_install_plugin_pods 方法在 flutter_tools 下,所以需要对应引入 52 | install_flutter_plugin_pods_method_new = install_flutter_plugin_pods_method + "\n\t" + "require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)" 53 | target_str = install_flutter_plugin_pods_method if is_binary else install_flutter_plugin_pods_method_new 54 | final_str = install_flutter_plugin_pods_method_new if is_binary else install_flutter_plugin_pods_method 55 | FileTool.replace(flutter_module_podhelper_path, target_str, final_str) 56 | 57 | # 2. 修改 Podfile,将Flutter的源码依赖改为二进制依赖 58 | source_deps = 'install_all_flutter_pods(flutter_application_path)' 59 | binary_deps = "pod 'Flutter', path: '{}'\n\t\tinstall_flutter_plugin_pods(flutter_application_path)".format(ios_framework_path) 60 | target_str = source_deps if is_binary else binary_deps 61 | final_str = binary_deps if is_binary else source_deps 62 | FileTool.replace(podfile_path, target_str, final_str) 63 | 64 | def handle_android(): 65 | """ 66 | 处理安卓项目 67 | """ 68 | print("项目路径:", project_path, "编译模式:", flutter_build_mode) 69 | is_binary = flutter_build_mode == Mode.BINARY 70 | comment_symbol = '//' # 注释符号 71 | # 1. 修改 build.gradle.kts 72 | file_path = os.path.join(project_path, 'build.gradle.kts') 73 | common_str = ' maven(url = "../../flutter_module/release")' 74 | target_str = (comment_symbol if is_binary else "") + common_str 75 | final_str = ("" if is_binary else comment_symbol) + common_str 76 | print("target_str -- ", target_str) 77 | print("final_str -- ", final_str) 78 | FileTool.replace(file_path, target_str, final_str) 79 | common_str = ' maven(url = "https://download.shorebird.dev/download.flutter.io")' 80 | target_str = (comment_symbol if is_binary else "") + common_str 81 | final_str = ("" if is_binary else comment_symbol) + common_str 82 | print("target_str -- ", target_str) 83 | print("final_str -- ", final_str) 84 | FileTool.replace(file_path, target_str, final_str) 85 | 86 | # 2. 修改 flutter_settings.gradle 87 | file_path = os.path.join(project_path, 'flutter_settings.gradle') 88 | target_str = '//evaluate(new File(\n// settingsDir,\n// "../flutter_module/.android/include_flutter.groovy"\n//))' 89 | final_str = 'evaluate(new File(\n settingsDir,\n "../flutter_module/.android/include_flutter.groovy"\n))' 90 | FileTool.replace(file_path, final_str if is_binary else target_str , target_str if is_binary else final_str) 91 | 92 | # 3. 修改 app/build.gradle.kts 93 | file_path = os.path.join(project_path, 'app', 'build.gradle.kts') 94 | common_str = ' implementation(project(LocalLib.flutter))' 95 | target_str = ("" if is_binary else comment_symbol) + common_str 96 | final_str = (comment_symbol if is_binary else "") + common_str 97 | print("target_str -- ", target_str) 98 | print("final_str -- ", final_str) 99 | FileTool.replace(file_path, target_str, final_str) 100 | common_str = ' releaseImplementation("com.lxf.flutter_modules:flutter_release:1.0")' 101 | target_str = (comment_symbol if is_binary else "") + common_str 102 | final_str = ("" if is_binary else comment_symbol) + common_str 103 | print("target_str -- ", target_str) 104 | print("final_str -- ", final_str) 105 | FileTool.replace(file_path, target_str, final_str) 106 | 107 | 108 | if __name__ == "__main__": 109 | argv = sys.argv[1:] 110 | target_name = None # target名称 111 | flutter_build_mode = Mode.BINARY # flutter编译模式 112 | platform = Platform.ANDROID # 平台 113 | 114 | try: 115 | opts, args = getopt.getopt(argv, "p:m:f:", ["path=", "mode=", "platform="]) 116 | except getopt.GetoptError: 117 | print('switch_flutter_integrate.py -p "项目路径" -m "模式(binary|source)" -f "平台(ios|android)"') 118 | sys.exit(1) 119 | 120 | for opt, arg in opts: 121 | if opt in ["-p", "--path"]: 122 | project_path = arg 123 | if len(project_path) == 0: 124 | print('请输入原生工程地址') 125 | sys.exit(ErrorCode.PARAMS_ERR.value) 126 | if opt in ["-m", "--mode"]: 127 | if (arg != "binary" and arg != "source"): 128 | print('请输入正确的模式') 129 | sys.exit(ErrorCode.PARAMS_ERR.value) 130 | if (arg == 'binary'): 131 | flutter_build_mode = Mode.BINARY 132 | else: 133 | flutter_build_mode = Mode.SOURCE 134 | if opt in ["-f", "--platform"]: 135 | if (arg != "ios" and arg != "android"): 136 | print('请输入正确的平台') 137 | sys.exit(ErrorCode.PARAMS_ERR.value) 138 | if (arg == 'ios'): 139 | platform = Platform.IOS 140 | else: 141 | platform = Platform.ANDROID 142 | 143 | if platform == Platform.IOS: 144 | handle_ios() 145 | else: 146 | handle_android() 147 | 148 | # 二进制依赖 149 | # python switch_flutter_integrate.py -p '安卓原生工程项目路径' -m 'binary' -f 'android' 150 | # python switch_flutter_integrate.py -p 'iOS原生工程项目路径' -m 'binary' -f 'ios' 151 | 152 | # 源码依赖 153 | # python switch_flutter_integrate.py -p '安卓原生工程项目路径' -m 'source' -f 'android' 154 | # python switch_flutter_integrate.py -p '安卓原生工程项目路径' -m 'source' -f 'ios' 155 | -------------------------------------------------------------------------------- /iOS/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinXunFeng/script_box/29335dd24ea68334b3c8744ce233fd8538dd7e21/iOS/__init__.py -------------------------------------------------------------------------------- /iOS/get_build_config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # -*- author: LinXunFeng -*- 3 | from configparser import ConfigParser 4 | 5 | 6 | def get_config(config_ini_path, section_name, key): 7 | """获取项目的编译目录路径""" 8 | config = ConfigParser() 9 | config.read(config_ini_path) 10 | if not config.has_section(section_name): 11 | return None 12 | else: 13 | return config.get(section_name, key) 14 | 15 | -------------------------------------------------------------------------------- /iOS/push_dev_ipa.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # -*- author: LinXunFeng -*- 3 | 4 | import getopt 5 | import json 6 | import os 7 | import shutil 8 | import sys 9 | import time 10 | from configparser import ConfigParser 11 | from enum import Enum 12 | from save_build_config import BuildConfigSection, BuildConfigProjectKey 13 | from get_build_config import get_config as GetBuildConfig 14 | 15 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 16 | sys.path.insert(0, BASE_DIR) 17 | 18 | from utils import file_util as FileUtil 19 | from utils import upload_pgyer as PgyerUtil 20 | from utils import upload_fir as FirUtil 21 | 22 | 23 | class UploadToPlatform(Enum): 24 | """上传至目的平台类型""" 25 | PGYER = "pgyer" 26 | FIR = "fir" 27 | 28 | 29 | class AppackSetKey(Enum): 30 | """appack_set的键""" 31 | PGYER_USER_KEY = "pgyer_user_key" 32 | PGYER_PASSWORD_KEY = "pgyer_api_password" 33 | FIR_TYPE_KEY = "fir_type" 34 | FIR_API_TOKEN_KEY = "fir_api_token" 35 | 36 | 37 | def get_build_dir_path(config_ini_path): 38 | """获取项目的编译目录路径""" 39 | section_name = BuildConfigSection.PROJECT.value 40 | key = BuildConfigProjectKey.BUILD_DIR.value 41 | return GetBuildConfig(config_ini_path, section_name, key) 42 | 43 | 44 | def get_configuration(config_ini_path): 45 | """获取项目的编译模式""" 46 | section_name = BuildConfigSection.PROJECT.value 47 | key = BuildConfigProjectKey.CONFIGURATION.value 48 | return GetBuildConfig(config_ini_path, section_name, key) 49 | 50 | 51 | def get_build_config_ini_path(project_path): 52 | """获取build_conf.ini文件路径""" 53 | return os.path.join(project_path, 'script', 'build_time_conf.ini') 54 | 55 | 56 | def get_config_dict(project_path): 57 | """获取appack_set.json文件路径""" 58 | config_set_json = os.path.join(project_path, 'fastlane', 'appack_set.json') 59 | return json.loads(FileUtil.read_file(config_set_json)) 60 | 61 | 62 | def get_pgyer_config(project_path): 63 | """获取蒲公英的相关配置""" 64 | json_data = get_config_dict(project_path) 65 | # print(json_data) 66 | pgyer_user_key = json_data[AppackSetKey.PGYER_USER_KEY.value] 67 | pgyer_password = json_data[AppackSetKey.PGYER_PASSWORD_KEY.value] 68 | return pgyer_user_key, pgyer_password 69 | 70 | 71 | def get_fir_config(project_path): 72 | """获取fir的相关配置""" 73 | json_data = get_config_dict(project_path) 74 | fir_type = json_data[AppackSetKey.FIR_TYPE_KEY.value] 75 | fir_api_token = json_data[AppackSetKey.FIR_API_TOKEN_KEY.value] 76 | return fir_type, fir_api_token 77 | 78 | 79 | def handle(project_path, target_name, upload_to_platform): 80 | app_name = target_name + '.app' 81 | 82 | config_ini_path = get_build_config_ini_path(project_path) 83 | build_dir_path = get_build_dir_path(config_ini_path) 84 | if build_dir_path is None: 85 | raise Exception('获取项目的编译目录路径失败') 86 | # print('build_dir_path -- ', build_dir_path) 87 | app_path = os.path.join(build_dir_path, 'Build/Products/{}-iphoneos'.format(get_configuration(config_ini_path)), app_name) 88 | # print(app_path) 89 | # cur_path = os.path.abspath('.') 90 | script_path = os.path.join(project_path, 'script') 91 | temp_path = os.path.join(script_path, 'temp') 92 | payload_path = os.path.join(temp_path, 'Payload') 93 | payload_app_path = os.path.join(payload_path, app_name) 94 | 95 | if os.path.exists(temp_path): 96 | shutil.rmtree(temp_path) # 移除Payload 97 | time.sleep(1) # 等删除完 98 | os.makedirs(payload_path) # 创建Payload 99 | new_app_path = shutil.copytree(app_path, payload_app_path) 100 | # print(new_app_path) 101 | ipa_path = shutil.make_archive(payload_path, 'zip', temp_path) 102 | ipa_path = shutil.move(ipa_path, os.path.join(temp_path, target_name + '.ipa')) 103 | # print(ipa_path) 104 | 105 | # 上传完成回调 106 | def upload_complete_callback(): 107 | shutil.rmtree(temp_path) # 删除temp目录 108 | 109 | if upload_to_platform == UploadToPlatform.PGYER.value: # 上传至蒲公英 110 | pgyer_user_key, pgyer_password = get_pgyer_config(project_path) 111 | PgyerUtil.upload_to_pgyer(ipa_path, pgyer_user_key, password=pgyer_password, 112 | callback=upload_complete_callback) 113 | elif upload_to_platform == UploadToPlatform.FIR.value: # 上传至fir 114 | fir_type, fir_api_token = get_fir_config(project_path) 115 | FirUtil.upload_to_fir(app_path=new_app_path, ipa_path=ipa_path, api_token=fir_api_token, type=fir_type, 116 | callbcak=upload_complete_callback) 117 | 118 | 119 | if __name__ == "__main__": 120 | argv = sys.argv[1:] 121 | project_path = "" # 项目路径 122 | target_name = "" # target名称 123 | upload_to_platform = UploadToPlatform.PGYER.value # 上传的平台 124 | 125 | try: 126 | opts, args = getopt.getopt(argv, "p:t:f:", ["path=", "target_name=", "platform="]) 127 | except getopt.GetoptError: 128 | print('push_dev_ipa.py -p "项目路径" -t "target名" --platform="pgyer或fir"') 129 | sys.exit(2) 130 | 131 | print(opts) 132 | for opt, arg in opts: 133 | if opt in ["-p", "--path"]: 134 | project_path = arg 135 | if len(project_path) == 0: 136 | print('请输入项目的地址') 137 | sys.exit('请输入项目的地址') 138 | if opt in ["-t", "--target_name"]: 139 | target_name = arg 140 | if opt in ["-f", "--platform"]: 141 | upload_to_platform = arg 142 | 143 | # print(project_path) 144 | handle(project_path, target_name, upload_to_platform) 145 | -------------------------------------------------------------------------------- /iOS/save_build_config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # -*- author: LinXunFeng -*- 3 | import os 4 | import enum 5 | from configparser import ConfigParser 6 | 7 | 8 | class BuildConfigSection(enum.Enum): 9 | PROJECT = 'project' 10 | 11 | 12 | class BuildConfigProjectKey(enum.Enum): 13 | BUILD_DIR = 'build_dir_path' 14 | CONFIGURATION = 'configuration' 15 | 16 | 17 | def handle_build_config(): 18 | """保存编译时的一些配置""" 19 | build_dir_path = os.getenv("BUILD_DIR") # 编译地址 20 | if build_dir_path is None: 21 | return 22 | 23 | build_str_index = build_dir_path.find('Build/') 24 | if build_str_index is not None: 25 | build_dir_path = build_dir_path[0:build_str_index] 26 | print(build_dir_path) 27 | save_config(BuildConfigProjectKey.BUILD_DIR.value, build_dir_path) 28 | 29 | configuration = os.getenv("CONFIGURATION") # 编译模式 30 | print(configuration) 31 | save_config(BuildConfigProjectKey.CONFIGURATION.value, configuration) 32 | 33 | 34 | def save_config(key, value): 35 | """ 36 | 保存配置 37 | :param key: 键 38 | :param value: 值 39 | :return: 40 | """ 41 | section_name = BuildConfigSection.PROJECT.value 42 | config_file_name = 'build_time_conf.ini' 43 | config = ConfigParser() 44 | config.read(config_file_name) 45 | if not config.has_section(section_name): 46 | config.add_section(section_name) 47 | config.set(section_name, key, value) 48 | with open(config_file_name, 'w') as f: 49 | config.write(f) 50 | 51 | 52 | if __name__ == '__main__': 53 | handle_build_config() 54 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2021.10.8 2 | charset-normalizer==2.0.10 3 | idna==3.3 4 | requests==2.27.1 5 | urllib3==1.26.8 6 | pbxproj==3.2.3 7 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinXunFeng/script_box/29335dd24ea68334b3c8744ce233fd8538dd7e21/utils/__init__.py -------------------------------------------------------------------------------- /utils/file_util.py: -------------------------------------------------------------------------------- 1 | def replace(file, old_content, new_content): 2 | # 传入文件(file),将旧内容(old_content)替换为新内容(new_content) 3 | content = read_file(file) 4 | content = content.replace(old_content, new_content) 5 | rewrite_file(file, content) 6 | 7 | 8 | def read_file(file): 9 | # 读文件内容 10 | with open(file, encoding='UTF-8') as f: 11 | read_all = f.read() 12 | f.close() 13 | 14 | return read_all 15 | 16 | 17 | def rewrite_file(file, data): 18 | # 写内容到文件 19 | with open(file, 'w', encoding='UTF-8') as f: 20 | f.write(data) 21 | f.close() 22 | -------------------------------------------------------------------------------- /utils/plist_util.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | 4 | class PlistUtil: 5 | def __init__(self, path): 6 | self.path = path 7 | 8 | def getValueForKey(self, key): 9 | pipe = subprocess.Popen(["/usr/libexec/PlistBuddy", "-c", "Print " + key, self.path], stdout=subprocess.PIPE) 10 | result, _ = pipe.communicate() 11 | return result 12 | 13 | def setValueForKey(self, key, value): 14 | subprocess.call(["/usr/libexec/PlistBuddy", "-c", "Set :" + key + " " + value, self.path]) 15 | 16 | def addValueForKey(self, key, type, value): 17 | subprocess.call(["/usr/libexec/PlistBuddy", "-c", "Add :" + key + " " + type + " " + value, self.path]) 18 | 19 | def delValueForKey(self, key): 20 | subprocess.call(["/usr/libexec/PlistBuddy", "-c", "Delete :" + key, self.path]) 21 | -------------------------------------------------------------------------------- /utils/release_version.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from utils import xcode_util as XcodeTool, file_util as FileTool 3 | 4 | 5 | class ErrorCode(Enum): 6 | """错误代码""" 7 | NORMAL = 0 # 正常 8 | PARAMS_ERR = 1 # 入参错误 9 | UNKNOW = 2 # 未知 10 | PARSE_ERR = 3 # 解析 11 | 12 | 13 | def fetch_project_version( 14 | xcodeproj_path, 15 | target_name, 16 | ): 17 | """ 18 | 获取主版本号 19 | :xcodeproj_path xcodeproj文件路径 20 | :target_name 项目Target名称 21 | """ 22 | # 读取版本号 23 | build_setting_dict = XcodeTool.get_build_settings(xcodeproj_path, target_name) 24 | main_version_key = XcodeTool.BuildSettingKey.MARKETING_VERSION.value 25 | build_version_key = XcodeTool.BuildSettingKey.CURRENT_PROJECT_VERSION.value 26 | if main_version_key not in build_setting_dict: 27 | return None 28 | if build_version_key not in build_setting_dict: 29 | return None 30 | main_version = build_setting_dict[main_version_key] # 主版本 31 | build_version = build_setting_dict[build_version_key] # build版本 32 | return (main_version, build_version) 33 | -------------------------------------------------------------------------------- /utils/str_util.py: -------------------------------------------------------------------------------- 1 | # 在字符串指定位置插入字符 2 | # str_origin:源字符串 pos:插入位置 str_add:待插入的字符串 3 | # 4 | def insert(str_origin, pos, str_add): 5 | str_list = list(str_origin) # 字符串转list 6 | str_list.insert(pos, str_add) # 在指定位置插入字符串 7 | str_out = ''.join(str_list) # 空字符连接 8 | return str_out -------------------------------------------------------------------------------- /utils/upload_fir.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import requests 4 | import json 5 | from .plist_util import PlistUtil 6 | 7 | 8 | def upload_to_fir(app_path, ipa_path, api_token, type='ios', callbcak=None): 9 | """ 10 | 上传到蒲公英 11 | :param app_path: app文件路径 12 | :param ipa_path: ipa文件路径 13 | :param api_token: fir 的 api token 14 | :param type: 15 | """ 16 | plist_path = os.path.join(app_path, "info.plist") 17 | plist = PlistUtil(plist_path) 18 | bundle_id = plist.getValueForKey("CFBundleIdentifier").decode().replace('\n', '') 19 | app_name = plist.getValueForKey("CFBundleName").decode().replace('\n', '') 20 | version = plist.getValueForKey("CFBundleShortVersionString").decode().replace('\n', '') 21 | build = plist.getValueForKey("CFBundleVersion").decode().replace('\n', '') 22 | 23 | binary = get_fir_params(type=type, bundle_id=bundle_id, api_token=api_token) 24 | if binary is None: 25 | sys.exit('获取请求参数失败,请检查配置') 26 | key = binary['key'] 27 | token = binary['token'] 28 | upload_url = binary['upload_url'] 29 | 30 | file = { 31 | 'file': open(ipa_path, 'rb') 32 | } 33 | param = { 34 | "key": key, # binary字段对应的key 35 | "token": token, # binary字段对应的token 36 | "x:name": app_name, # 应用名称 37 | "x:version": version, # 版本号 38 | "x:build": build, # Build 号 39 | # "x:changelog": '更新日志' 40 | } 41 | print("上传至fir中....") 42 | r = requests.post(upload_url, files=file, data=param) 43 | if r.status_code == requests.codes.ok: 44 | # result = r.json() 45 | # print(result) 46 | print("上传成功") 47 | else: 48 | print('HTTPError,Code:'+r.status_code) 49 | 50 | if callbcak is not None: 51 | callbcak() 52 | 53 | 54 | def get_fir_params(type, bundle_id, api_token): 55 | data = { 56 | 'type': type, 57 | 'bundle_id': bundle_id, 58 | 'api_token': api_token, 59 | } 60 | req = requests.post('http://api.bq04.com/apps', data=data) 61 | result = req.json() 62 | # print(json.dumps(result)) 63 | if req.status_code == requests.codes.created: 64 | return result["cert"]["binary"] 65 | else: 66 | print('HTTPError,Code:'+req.status_code) 67 | -------------------------------------------------------------------------------- /utils/upload_pgyer.py: -------------------------------------------------------------------------------- 1 | import time 2 | import requests 3 | 4 | # 官方文档 5 | # https://www.pgyer.com/doc/view/api#fastUploadApp 6 | 7 | def _getCOSToken(api_key, install_type=2, password='', update_description='', callback=None): 8 | """ 9 | 获取上传的 token 10 | """ 11 | headers = {'enctype': 'multipart/form-data'} 12 | payload = { 13 | '_api_key': api_key, # API Key 14 | 'buildType': 'ios', # 需要上传的应用类型,ios 或 android 15 | 'buildInstallType': install_type, # (选填)应用安装方式,值为(1,2,3,默认为1 公开安装)。1:公开安装,2:密码安装,3:邀请安装 16 | 'buildPassword': password, # (选填) 设置App安装密码,密码为空时默认公开安装 17 | 'buildUpdateDescription': update_description, 18 | } 19 | try: 20 | r = requests.post('https://www.pgyer.com/apiv2/app/getCOSToken', data=payload, headers=headers) 21 | if r.status_code == requests.codes.ok: 22 | result = r.json() 23 | # print(result) 24 | if callback is not None: 25 | callback(True, result) 26 | else: 27 | if callback is not None: 28 | callback(False, None) 29 | except Exception as e: 30 | print('蒲公英服务器抽风了~') 31 | 32 | 33 | def upload_to_pgyer(path, api_key, install_type=2, password='', update_description='', callback=None): 34 | """ 35 | 上传到蒲公英 36 | :param path: 文件路径 37 | :param api_key: API Key 38 | :param install_type: 应用安装方式,值为(1,2,3)。1:公开,2:密码安装,3:邀请安装。默认为1公开 39 | :param password: App安装密码 40 | :param update_description: 41 | :return: 版本更新描述 42 | """ 43 | 44 | def getCOSToken_callback(isSuccess, json): 45 | if isSuccess: 46 | _upload_url = json['data']['endpoint'] 47 | 48 | files = {'file': open(path, 'rb')} 49 | headers = {'enctype': 'multipart/form-data'} 50 | payload = json['data']['params'] 51 | print("上传中...") 52 | 53 | try: 54 | r = requests.post(_upload_url, data=payload, files=files, headers=headers) 55 | if r.status_code == 204: 56 | # result = r.json() 57 | # print(result) 58 | print("上传成功,正在获取包处理信息,请稍等...") 59 | _getBuildInfo(api_key=api_key, json=json, callback=callback) 60 | else: 61 | print('HTTPError,Code:'+ str(r.status_code)) 62 | if callback is not None: 63 | callback(False, None) 64 | except Exception as e: 65 | print('蒲公英服务器抽风了~') 66 | else: 67 | pass 68 | 69 | _getCOSToken( 70 | api_key=api_key, 71 | install_type=install_type, 72 | password=password, 73 | update_description=update_description, 74 | callback=getCOSToken_callback, 75 | ) 76 | 77 | def _getBuildInfo(api_key, json, callback=None): 78 | """ 79 | 检测应用是否发布完成,并获取发布应用的信息 80 | """ 81 | time.sleep(3) # 先等个几秒,上传完直接获取肯定app是还在处理中~ 82 | r1 = requests.get('https://www.pgyer.com/apiv2/app/buildInfo', params={ 83 | '_api_key': api_key, 84 | 'buildKey': json['data']['params']['key'], 85 | }) 86 | if r1.status_code == requests.codes.ok: 87 | result = r1.json() 88 | code = result['code'] 89 | if code == 1247 or code == 1246: # 1246 应用正在解析、1247 应用正在发布中 90 | _getBuildInfo(api_key=api_key, json=json, callback=callback) 91 | else: 92 | if callback is not None: 93 | callback(True, result) 94 | else: 95 | if callback is not None: 96 | callback(False, None) 97 | 98 | -------------------------------------------------------------------------------- /utils/xcode_util.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from enum import Enum, unique 3 | from pbxproj import XcodeProject 4 | 5 | 6 | @unique 7 | class BuildSettingKey(Enum): 8 | MARKETING_VERSION = "MARKETING_VERSION" 9 | CURRENT_PROJECT_VERSION = "CURRENT_PROJECT_VERSION" 10 | ARCHS = "ARCHS" 11 | DART_DEFINES = "DART_DEFINES" 12 | OTHER_SWIFT_FLAGS = "OTHER_SWIFT_FLAGS" 13 | 14 | 15 | def get_build_settings(proj_path, target=None, scheme=None): 16 | """ 17 | :proj_path xcodeproj文件的路径 18 | :target Target名称 19 | :scheme Scheme名称 20 | :desc 以字典的形式获取build_settings 21 | xcodebuild -project "xxx/Test.xcodeproj" -scheme schemeName -target targetName -showBuildSettings 22 | """ 23 | project_param = (" -project %s" %proj_path) if proj_path is not None else "" 24 | target_param = (" -target %s" % target) if target is not None else "" 25 | scheme_param = (" -scheme %s" % scheme) if scheme is not None else "" 26 | command = "xcodebuild%s%s%s -showBuildSettings" % (project_param, target_param, scheme_param) 27 | status, output = subprocess.getstatusoutput(command) 28 | arr_param = output.split("\n") 29 | dic_param = {} 30 | 31 | for param in arr_param: 32 | if " = " in param: 33 | split_list = param.split(" = ") 34 | if len(split_list) != 2: 35 | continue 36 | key, value = split_list 37 | key = key.strip() 38 | value = value.strip() 39 | dic_param[key] = value 40 | else: 41 | pass 42 | 43 | return dic_param 44 | 45 | 46 | def get_build_configurations(proj_path, target): 47 | """ 48 | 获取 Build Configurations 49 | :proj_path xcodeproj文件的路径 50 | :target Target名称 51 | """ 52 | project = XcodeProject.load(proj_path) 53 | return project.get_build_configurations_by_target(target) # ['Debug', 'Release', 'Test'] 54 | 55 | 56 | def get_build_settings_obj( 57 | proj_path, 58 | target, 59 | configuration='Debug', 60 | project=None 61 | ): 62 | """ 63 | 获取 BuildSetting 64 | :proj_path xcodeproj文件的路径 65 | :target Target名称 66 | :configuration Build Configuration 67 | :project 加载好的 XcodeProject 对象 68 | """ 69 | if (project is None): 70 | project = XcodeProject.load(proj_path) 71 | target = project.get_target_by_name(target) 72 | if target is None: 73 | return None; 74 | 75 | build_configuration_list = project.objects[target.buildConfigurationList] 76 | target_build_configurations = build_configuration_list['buildConfigurations'] 77 | 78 | for build_configuration in target_build_configurations: 79 | build_configuration_obj = project.objects[build_configuration] 80 | if build_configuration_obj is not None: 81 | if build_configuration_obj.name == configuration: 82 | return build_configuration_obj.buildSettings 83 | return None; 84 | 85 | def set_build_settings_obj( 86 | key, 87 | value, 88 | proj_path, 89 | target, 90 | configuration='Debug', 91 | project=None 92 | ): 93 | """ 94 | 设置 BuildSetting 95 | :proj_path xcodeproj文件的路径 96 | :target Target名称 97 | :configuration Build Configuration 98 | :project 加载好的 XcodeProject 对象 99 | """ 100 | if (project is None): 101 | project = XcodeProject.load(proj_path) 102 | build_settings = get_build_settings_obj( 103 | proj_path=proj_path, 104 | target=target, 105 | configuration=configuration, 106 | project=project 107 | ) 108 | if build_settings is None: return 109 | build_settings[key] = value 110 | project.save() 111 | --------------------------------------------------------------------------------