948 lines
44 KiB
Python
948 lines
44 KiB
Python
import os
|
||
import re
|
||
import json
|
||
import argparse
|
||
from pbxproj import XcodeProject
|
||
from pbxproj.pbxsections import PBXShellScriptBuildPhase
|
||
import subprocess
|
||
import plistlib
|
||
|
||
#TODO:待修改字段
|
||
IOSMINIVERSION = '12.0'
|
||
LOCAL_SOURCE_LINE = 'http://tygit.tuyoo.com/dev_platform/ios/frameworks/pods/private-pod-repo.git'
|
||
SOURCE_LINE = 'ssh://git@tygit.tuyoo.com:2222/dev_platform/ios/frameworks/pods/private-pod-repo.git'
|
||
CUSTOM_OSS_HTTP_PATH = 'https://sa201-ptsdk-res.oss-cn-beijing.aliyuncs.com'
|
||
|
||
build_config = {}
|
||
build_package_config = {}
|
||
build_sdk_names_list = []
|
||
dependencies = []
|
||
|
||
CHANNEL_AND_NAME_LIST = ["wechat",
|
||
"wangyiyidun",
|
||
"yidunloginv2",
|
||
"douyin",
|
||
"rongheguiyin",
|
||
"yidunzhiwen",
|
||
"yiduncaptcha",
|
||
"yidunlivedetect",
|
||
"appsflyer",
|
||
"firebaseanalytics",
|
||
"firebasecrashlytics",
|
||
"firebaseodm",
|
||
"yuenanlogin",
|
||
"facebook",
|
||
"google",
|
||
"line",
|
||
"twitter",
|
||
"idfa",
|
||
"haiwailogin",
|
||
"aihelp",
|
||
"bugly",
|
||
"push",
|
||
"fcm"]
|
||
|
||
BUNDLE_SOURCE = {
|
||
"yidunloginv2":["NTESResource.bundle","TDKYiDunLoginV2Bundle.bundle"],
|
||
"yidunzhiwen":["FPSupport.bundle"],
|
||
"yiduncaptcha":["NTESVerifyCodeResources.bundle","TDKYidunCaptchaBundle.bundle"],
|
||
"yidunlivedetect":["NTESLiveDetectBundle.bundle","TDKYidunLiveDetectBundle.bundle"],
|
||
"haiwailogin":["TDKHaiWaiLoginSource.bundle"],
|
||
"aihelp":["AIHelpSupportSDK.bundle"]
|
||
}
|
||
|
||
EMBEM_FRAMEWORK_SOURCE = {
|
||
"douyin":["UnionOpenPlatformCore.framework","UnionOpenPlatformCPS.framework"],
|
||
"yidunlivedetect":["NEYDnn.framework"]
|
||
}
|
||
|
||
def parse_build_config_json(build_config_json_path, coustom_plist_path):
|
||
print(f"1️⃣ parse_build_config_json, path = {build_config_json_path} begin")
|
||
abs_build_config_json_path = os.path.abspath(build_config_json_path)
|
||
if os.path.isfile(abs_build_config_json_path):
|
||
with open(abs_build_config_json_path, 'r', encoding='utf-8') as json_file:
|
||
data = json.loads(json_file.read())
|
||
# print(f"1️⃣ parse_build_config_json, json = {data} ing")
|
||
# 将数据写入 plist 文件
|
||
with open(coustom_plist_path, 'wb') as plist_file:
|
||
plistlib.dump(data, plist_file)
|
||
global coustom_plist_file_path
|
||
coustom_plist_file_path = coustom_plist_path
|
||
global build_config
|
||
build_config = data
|
||
global build_package_config
|
||
build_package_config = data.get('buildConfig')
|
||
global build_sdk_names_list
|
||
names = data.get('login').get('sdks')
|
||
build_sdk_names_list = list(set([name for name in names if name]))
|
||
fcm = data.get('fcm')
|
||
push = data.get('push')
|
||
if fcm:
|
||
build_sdk_names_list.append('fcm')
|
||
if push:
|
||
build_sdk_names_list.append('push')
|
||
if ('firebasecrashlytics' in build_sdk_names_list or 'firebaseodm' in build_sdk_names_list) and 'firebaseanalytics' not in build_sdk_names_list:
|
||
build_sdk_names_list.append('firebaseanalytics')
|
||
else:
|
||
print(f"❌ parse_build_config_json file error")
|
||
exit(1)
|
||
|
||
def get_add_project_capability_flag():
|
||
if build_package_config and "addCapability" in build_package_config:
|
||
return build_package_config.get("addCapability")
|
||
else:
|
||
return False
|
||
|
||
def get_git_access_method():
|
||
if build_package_config and "gitAccessMethod" in build_package_config:
|
||
return build_package_config.get("gitAccessMethod")
|
||
else:
|
||
return "ssh"
|
||
|
||
def get_sdk_source_method():
|
||
if build_package_config and "getSourceMethod" in build_package_config:
|
||
return build_package_config.get("getSourceMethod")
|
||
else:
|
||
return "git"
|
||
|
||
def add_file_to_project(project, file_path, skip_if_exsit=True, target_name = None):
|
||
print(f"💡 add_file_to_project {file_path}")
|
||
abs_file_path = os.path.abspath(file_path)
|
||
if os.path.exists(abs_file_path):
|
||
if not project.get_files_by_path(abs_file_path):
|
||
project.add_file(abs_file_path, target_name = target_name)
|
||
print(f"⚠️ add_file_to_project {abs_file_path} add")
|
||
elif not skip_if_exsit:
|
||
project.remove_files_by_path(abs_file_path)
|
||
print(f"🗑️ 已删除旧的 {abs_file_path}")
|
||
file_ref = project.add_file(abs_file_path, target_name = target_name)
|
||
print(f"✅ 已添加: {abs_file_path} -> {file_ref}")
|
||
else:
|
||
print(f"⚠️ add_file_to_project {abs_file_path} already exist")
|
||
else:
|
||
print(f"⚠️ add_file_to_project {abs_file_path} not exist")
|
||
|
||
def set_library_search_paths(project, search_paths, target_name=None):
|
||
print(f"💡 set_library_search_paths {search_paths}")
|
||
library_search_paths = None
|
||
for target in project.objects.get_targets():
|
||
build_configurations = project.objects.get_configurations_on_targets(target.name)
|
||
for config in build_configurations:
|
||
library_search_paths = config.buildSettings.get("LIBRARY_SEARCH_PATHS", [])
|
||
default_paths = ['$(inherited)']
|
||
if library_search_paths:
|
||
if not isinstance(library_search_paths, list):
|
||
library_search_paths = [library_search_paths]
|
||
print(f"💡 set_library_search_paths {library_search_paths}")
|
||
library_search_paths = default_paths + list(dict.fromkeys(search_paths + library_search_paths))
|
||
|
||
library_search_paths = default_paths + [item for item in library_search_paths if item not in default_paths]
|
||
else:
|
||
library_search_paths = default_paths
|
||
config.buildSettings["LIBRARY_SEARCH_PATHS"] = library_search_paths
|
||
print(f"💡 set_library_search_paths {target.name} - {library_search_paths}")
|
||
else:
|
||
print(f"💡 set_library_search_paths fail")
|
||
return None
|
||
|
||
# **处理 Info.plist Begin**
|
||
def add_bool_to_info_plist(plist_file_path, key, value, skip_existing_elements = True):
|
||
print(f"💡 add_bool_to_info_plist {plist_file_path} - {key} - {value}")
|
||
if not key and key == '':
|
||
print(f"⚠️ add_bool_to_info_plist {key} invalue")
|
||
return
|
||
if not isinstance(value, bool):
|
||
print(f"⚠️ add_bool_to_info_plist {value} invalue")
|
||
return
|
||
with open(plist_file_path, 'rb') as f:
|
||
plist_data = plistlib.load(f)
|
||
if not skip_existing_elements or key not in plist_data:
|
||
# 如果存在,但不跳过,则直接覆盖, 如果不存在,则创建一个新的列表并添加 schemes_to_add
|
||
plist_data[key] = value
|
||
|
||
# 将修改后的数据写回 info.plist 文件
|
||
with open(plist_file_path, 'wb') as f:
|
||
plistlib.dump(plist_data, f)
|
||
|
||
def add_string_to_info_plist(plist_file_path, key, value, skip_existing_elements = True):
|
||
print(f"💡 add_string_to_info_plist {plist_file_path} - {key} - {value}")
|
||
if not key and key == '':
|
||
print(f"⚠️ add_string_to_info_plist {key} invalue")
|
||
return
|
||
if not isinstance(value, str):
|
||
print(f"⚠️ add_array_to_info_plist {value} invalue")
|
||
return
|
||
with open(plist_file_path, 'rb') as f:
|
||
plist_data = plistlib.load(f)
|
||
if not skip_existing_elements or key not in plist_data:
|
||
# 如果存在,但不跳过,则直接覆盖, 如果不存在,则创建一个新的列表并添加 schemes_to_add
|
||
plist_data[key] = value
|
||
|
||
# 将修改后的数据写回 info.plist 文件
|
||
with open(plist_file_path, 'wb') as f:
|
||
plistlib.dump(plist_data, f)
|
||
|
||
def add_array_to_info_plist(plist_file_path, key, value, skip_existing_elements = True):
|
||
print(f"💡 add_array_to_info_plist {plist_file_path} - {key} - {value}")
|
||
if not os.path.exists(plist_file_path):
|
||
print(f"⚠️ add_array_to_info_plist {plist_file_path} invalue")
|
||
return
|
||
if not key or key == '':
|
||
print(f"⚠️ add_array_to_info_plist {key} invalue")
|
||
return
|
||
if not isinstance(value, list):
|
||
print(f"⚠️ add_array_to_info_plist {value} invalue")
|
||
return
|
||
|
||
with open(plist_file_path, 'rb') as f:
|
||
plist_data = plistlib.load(f)
|
||
|
||
# 检查 key 是否存在
|
||
if key not in plist_data:
|
||
plist_data[key] = []
|
||
current_array = plist_data[key]
|
||
for item in value:
|
||
if not skip_existing_elements or item not in current_array:
|
||
current_array.append(item)
|
||
with open(plist_file_path, 'wb') as f:
|
||
plistlib.dump(plist_data, f)
|
||
|
||
def is_json(variable):
|
||
try:
|
||
json.loads(variable)
|
||
return True
|
||
except (json.JSONDecodeError, TypeError):
|
||
return False
|
||
|
||
def add_dictionary_to_info_plist(plist_file_path, key, value, skip_existing_elements = True):
|
||
print(f"💡 add_dictionary_to_info_plist {plist_file_path} - {key} - {value}")
|
||
if not key and key == '':
|
||
print(f"⚠️ add_dictionary_to_info_plist {key} invalue")
|
||
return
|
||
if not is_json(value):
|
||
print(f"⚠️ add_dictionary_to_info_plist {value} invalue")
|
||
return
|
||
with open(plist_file_path, 'rb') as f:
|
||
plist_data = plistlib.load(f)
|
||
if not skip_existing_elements or key not in plist_data:
|
||
# 如果存在,但不跳过,则直接覆盖, 如果不存在,则创建一个新的列表并添加 schemes_to_add
|
||
plist_data[key] = value
|
||
|
||
# 将修改后的数据写回 info.plist 文件
|
||
with open(plist_file_path, 'wb') as f:
|
||
plistlib.dump(plist_data, f)
|
||
# **处理 Info.plist End**
|
||
|
||
# **处理 BuildSetting-ShellScript Begin**
|
||
def normalize_script(script: str):
|
||
if not script:
|
||
return ''
|
||
return script.strip().strip('"').strip("'")
|
||
|
||
def add_shell_script(project, run_script, input_files=None, output_files=None ,target_name=None, insert_before_compile=False, run_install_build=0):
|
||
# 检查是否已经存在该脚本,避免重复添加
|
||
for target in project.objects.get_targets(target_name):
|
||
print(f'target = {target}')
|
||
for build_phase_id in target.buildPhases:
|
||
build_phase = project.objects[build_phase_id]
|
||
print(f'build_phase = {build_phase_id} = {build_phase}')
|
||
if not isinstance(build_phase, PBXShellScriptBuildPhase):
|
||
continue
|
||
shellScript = build_phase.shellScript or ""
|
||
print(f'build_phase = start1 {shellScript}')
|
||
print(f'build_phase = start2 {run_script}')
|
||
if normalize_script(shellScript) == normalize_script(run_script):
|
||
print("⚠️ 已存在 先删除 ")
|
||
del project.objects[build_phase_id]
|
||
target.remove_build_phase(build_phase)
|
||
project.add_run_script(
|
||
script=run_script,
|
||
input_files=input_files,
|
||
output_files=output_files,
|
||
target_name=target_name,
|
||
insert_before_compile=insert_before_compile,
|
||
run_install_build=run_install_build
|
||
)
|
||
print("✅ 已添加 Run Script Phase。")
|
||
project.save()
|
||
# **处理 BuildSetting-ShellScript Begin**
|
||
|
||
# **处理 entitlements Begin**
|
||
def get_entitlements_file_path(project, skip_file_not_exist=True, app_target_name=None):
|
||
print(f"💡 get_entitlements_file_path ")
|
||
entitlements_path = None
|
||
for target in project.objects.get_targets():
|
||
build_configurations = project.objects.get_configurations_on_targets(target.name)
|
||
for config in build_configurations:
|
||
entitlements_path = config.buildSettings.get("CODE_SIGN_ENTITLEMENTS", "")
|
||
if entitlements_path:
|
||
print(f"💡 get_entitlements_file_path {entitlements_path}")
|
||
return os.path.abspath(entitlements_path)
|
||
if not skip_file_not_exist and app_target_name:
|
||
entitlements_file_path = f"{app_target_name}/{app_target_name}.entitlements"
|
||
with open(entitlements_file_path, "wb") as f:
|
||
plistlib.dump({}, f)
|
||
for target in project.objects.get_targets():
|
||
build_configurations = project.objects.get_configurations_on_targets(target.name)
|
||
for config in build_configurations:
|
||
config.buildSettings["CODE_SIGN_ENTITLEMENTS"] = entitlements_file_path
|
||
project.save()
|
||
return os.path.abspath(entitlements_file_path)
|
||
else:
|
||
return None
|
||
|
||
def add_sign_in_with_apple_capability(project, app_target_name):
|
||
print(f"💡 add_sign_in_with_apple_capability")
|
||
if not get_add_project_capability_flag():
|
||
print(f"💡 add_sign_in_with_apple_capability skip")
|
||
return
|
||
entitlements_file_path = get_entitlements_file_path(project, False, app_target_name)
|
||
SIGNIN_KEY = "com.apple.developer.applesignin"
|
||
if os.path.exists(entitlements_file_path):
|
||
with open(entitlements_file_path, "rb") as f:
|
||
entitlements = plistlib.load(f)
|
||
if SIGNIN_KEY in entitlements:
|
||
print(f"✅ add_sign_in_with_apple_capability 已启用")
|
||
else:
|
||
print(f"➕ add_sign_in_with_apple_capability 新增 ")
|
||
entitlements[SIGNIN_KEY] = ["Default"]
|
||
with open(entitlements_file_path, "wb") as f:
|
||
plistlib.dump(entitlements, f)
|
||
print(f"✅ add_sign_in_with_apple_capability `.entitlements` 文件已更新")
|
||
else:
|
||
print(f"⚠️ add_sign_in_with_apple_capability `.entitlements` not exist")
|
||
|
||
def add_notification_capability(project, app_target_name):
|
||
print(f"💡 add_notification_capability")
|
||
if not get_add_project_capability_flag():
|
||
print(f"💡 add_notification_capability skip")
|
||
return
|
||
entitlements_file_path = get_entitlements_file_path(project, False, app_target_name)
|
||
APS_ENV_KEY = "aps-environment"
|
||
if os.path.exists(entitlements_file_path):
|
||
with open(entitlements_file_path, "rb") as f:
|
||
entitlements = plistlib.load(f)
|
||
if APS_ENV_KEY in entitlements:
|
||
print(f"✅ add_notification_capability 已启用")
|
||
else:
|
||
print(f"➕ add_notification_capability 新增 ")
|
||
entitlements[APS_ENV_KEY] = "production"
|
||
with open(entitlements_file_path, "wb") as f:
|
||
plistlib.dump(entitlements, f)
|
||
print(f"✅ add_notification_capability `.entitlements` 文件已更新")
|
||
else:
|
||
print(f"⚠️ add_notification_capability `.entitlements` not exist")
|
||
|
||
def add_background_modes_capability(info_plist_path):
|
||
print(f"💡 add_background_modes_capability")
|
||
if not get_add_project_capability_flag():
|
||
print(f"💡 add_background_modes_capability skip")
|
||
return
|
||
print(f"✅ `info.plist` 文件已更新 UIBackgroundModes 和 remote-notification 权限")
|
||
add_array_to_info_plist(info_plist_path, 'UIBackgroundModes', ['remote-notification'])
|
||
|
||
def add_keychain_capability(project, app_target_name):
|
||
print(f"💡 add_keychain_capability")
|
||
if not get_add_project_capability_flag():
|
||
print(f"💡 add_keychain_capability skip")
|
||
return
|
||
entitlements_file_path = get_entitlements_file_path(project, False, app_target_name)
|
||
KEY_CHANIN_KEY = "keychain-access-groups"
|
||
if os.path.exists(entitlements_file_path):
|
||
with open(entitlements_file_path, "rb") as f:
|
||
entitlements = plistlib.load(f)
|
||
if KEY_CHANIN_KEY in entitlements:
|
||
print(f"✅ add_keychain_capability 已启用")
|
||
else:
|
||
print(f"➕ add_keychain_capability 新增 ")
|
||
entitlements[KEY_CHANIN_KEY] = [""]
|
||
with open(entitlements_file_path, "wb") as f:
|
||
plistlib.dump(entitlements, f)
|
||
print(f"✅ add_keychain_capability `.entitlements` 文件已更新")
|
||
else:
|
||
print(f"⚠️ add_keychain_capability `.entitlements` not exist")
|
||
|
||
def process_applinks(applinks):
|
||
"""
|
||
处理 applinks 列表,确保每个 URL 被转换为符合 Apple Universal Links 规范的格式。
|
||
"""
|
||
processed_applinks = []
|
||
for url in applinks:
|
||
if url.startswith("http://"):
|
||
domain = url[len("http://"):]
|
||
elif url.startswith("https://"):
|
||
domain = url[len("https://"):]
|
||
else:
|
||
domain = url
|
||
|
||
domain = domain.split('/')[0]
|
||
processed_applinks.append(f"applinks:{domain}")
|
||
|
||
return processed_applinks
|
||
|
||
def add_associated_domains_capability(project, applinks, app_target_name):
|
||
print(f"💡 add_associated_domains_capability")
|
||
if not get_add_project_capability_flag():
|
||
print(f"💡 add_associated_domains_capability skip")
|
||
return
|
||
entitlements_file_path = get_entitlements_file_path(project, False, app_target_name)
|
||
DOMAINS_KEY = "com.apple.developer.associated-domains"
|
||
processed_applinks = process_applinks(applinks)
|
||
if os.path.exists(entitlements_file_path):
|
||
with open(entitlements_file_path, "rb") as f:
|
||
entitlements = plistlib.load(f)
|
||
if DOMAINS_KEY in entitlements:
|
||
print(f"✅ 已启用 Associated Domains,正在补充信息")
|
||
current_domains = entitlements[DOMAINS_KEY]
|
||
# 合并两个列表,去重
|
||
updated_domains = list(set(current_domains + processed_applinks))
|
||
entitlements[DOMAINS_KEY] = updated_domains
|
||
else:
|
||
print(f"➕ add_associated_domains_capability 新增 ")
|
||
entitlements[DOMAINS_KEY] = processed_applinks
|
||
with open(entitlements_file_path, "wb") as f:
|
||
plistlib.dump(entitlements, f)
|
||
print(f"✅ add_associated_domains_capability `.entitlements` 文件已更新")
|
||
else:
|
||
print(f"⚠️ add_associated_domains_capability `.entitlements` not exist")
|
||
# **处理 entitlements End**
|
||
|
||
def add_dependencies_set(dependency):
|
||
if dependency and isinstance(dependency, list):
|
||
global dependencies
|
||
dependencies = list(set(dependencies+dependency))
|
||
|
||
def create_podfile(podfile_path, miniversion, app_target_name, target_name):
|
||
"""创建一个新的 Podfile 文件"""
|
||
print(f"💡 create_podfile {podfile_path} - {target_name}")
|
||
with open(podfile_path, 'w') as f:
|
||
f.write(f"platform :ios, '{miniversion}'\n")
|
||
# if app_target_name != target_name:
|
||
# f.write(f"target '{app_target_name}' do\n")
|
||
# f.write(f" use_frameworks!\n")
|
||
# f.write(f" target '{target_name}' do\n")
|
||
# f.write(f" use_frameworks!\n")
|
||
# f.write(" end\n")
|
||
# f.write("end\n")
|
||
# else:
|
||
f.write(f"target '{target_name}' do\n")
|
||
f.write(f" use_frameworks!\n")
|
||
f.write("end\n")
|
||
def read_podfile(podfile_path):
|
||
"""读取 Podfile 内容"""
|
||
with open(podfile_path, 'r') as f:
|
||
return f.readlines()
|
||
|
||
def write_podfile(podfile_path, lines):
|
||
"""写入更新后的 Podfile 内容"""
|
||
with open(podfile_path, 'w') as f:
|
||
f.writelines(lines)
|
||
|
||
def extract_pod_info(pod_line):
|
||
# 正则表达式模式,处理可能的多个空格
|
||
# 解释:
|
||
# \s* 匹配任意数量的空格(包括0个)
|
||
# '([^']+)' 匹配单引号内的内容(组件名和版本号)
|
||
pattern = r"pod\s+'([^']+)'\s*,\s*'([^']+)'"
|
||
|
||
# 执行匹配
|
||
match = re.match(pattern, pod_line.strip())
|
||
|
||
if match:
|
||
# 返回组件名和版本号
|
||
return match.group(1), match.group(2)
|
||
else:
|
||
return None, None
|
||
|
||
def update_dependencies(lines, app_target_name, target_name, dependencies):
|
||
"""更新 Podfile 中的依赖项 - 根据目标名称判断更新方式"""
|
||
|
||
# 检查 source 信息
|
||
source_found = False
|
||
# 检查 source 获取方式
|
||
if get_sdk_source_method() == 'http':
|
||
source_line = f"OSS_HTTP_PATH = '{CUSTOM_OSS_HTTP_PATH}'"
|
||
else:
|
||
if get_git_access_method() == 'ssh':
|
||
source_line = f"source '{SOURCE_LINE}'"
|
||
else:
|
||
source_line = f"source '{LOCAL_SOURCE_LINE}'"
|
||
for i, line in enumerate(lines):
|
||
if line.strip() == source_line.strip():
|
||
source_found = True
|
||
break
|
||
|
||
# 处理 target_name 和 app_target_name 是否相同的情况
|
||
# if app_target_name == target_name:
|
||
print(f"🔄 Updating dependencies for target '{target_name}'")
|
||
|
||
# 直接处理 target_name 闭包
|
||
target_found = False
|
||
target_index = -1
|
||
end_index = -1
|
||
# 查找target闭包
|
||
for i, line in enumerate(lines):
|
||
if f"target '{target_name}' do" in line:
|
||
target_found = True
|
||
target_index = i
|
||
# 在目标的 do 之前插入 source
|
||
if not source_found:
|
||
lines.insert(i, f'{source_line}\n')
|
||
target_index = target_index + 1
|
||
break
|
||
|
||
if not target_found:
|
||
print(f"➕ Adding new target '{target_name}'")
|
||
lines.append(f"\ntarget '{target_name}' do\n")
|
||
lines.append(" use_frameworks!\n")
|
||
lines.append("end\n")
|
||
target_index = len(lines) - 3
|
||
end_index = len(lines) - 1
|
||
else:
|
||
for j in range(target_index + 1, len(lines)):
|
||
if 'end' in lines[j]:
|
||
end_index = j
|
||
break
|
||
|
||
# 查找依赖项并更新
|
||
existing_deps = [
|
||
dep.strip().split(',')[0].strip() for dep in lines[target_index + 1:end_index]
|
||
if dep.strip().lower().startswith("pod ")
|
||
]
|
||
|
||
dealDependencies = []
|
||
# 处理 http 资源
|
||
if get_sdk_source_method() == 'http':
|
||
# "pod 'TYWeChatHandle', '1.1.0'"
|
||
for dep in dependencies:
|
||
podName, podVersion = extract_pod_info(dep)
|
||
if podName != None and podVersion != None:
|
||
# TODO: 得把test改掉!!
|
||
dealDependencies.append(f"pod '{podName}', :podspec => \"#{{OSS_HTTP_PATH}}/{podName}/test/{podVersion}/{podName}.podspec\"")
|
||
else :
|
||
dealDependencies = dependencies
|
||
|
||
new_lines = lines.copy()
|
||
for dep in dealDependencies:
|
||
dep_name = dep.strip().split(',')[0].strip()
|
||
if dep_name and dep_name not in existing_deps:
|
||
new_lines.insert(target_index + 1, f" {dep}\n")
|
||
end_index += 1
|
||
else:
|
||
for i in range(len(new_lines)):
|
||
if i > target_index and i < end_index:
|
||
line = new_lines[i]
|
||
if line.strip().startswith('pod ') and line.strip().split(',')[0].strip() == dep_name:
|
||
new_lines[i] = f" {dep}\n"
|
||
|
||
lines[:] = new_lines
|
||
return True
|
||
|
||
# else:
|
||
# # 如果 target_name 和 app_target_name 不同,则嵌套 target_name 在 app_target_name 内部
|
||
# print(f"🔄 Updating dependencies for target '{target_name}' inside the '{app_target_name}' target.")
|
||
|
||
# # 处理 app_target_name 闭包
|
||
# target_found = False
|
||
# app_target_index = -1
|
||
# app_end_index = -1
|
||
# for i, line in enumerate(lines):
|
||
# if f"target '{app_target_name}' do" in line:
|
||
# target_found = True
|
||
# app_target_index = i
|
||
# # 在目标的 do 之前插入 source
|
||
# if not source_found:
|
||
# lines.insert(i, f'{source_line}\n')
|
||
# break
|
||
|
||
# if not target_found:
|
||
# print(f"➕ Adding new target '{app_target_name}'")
|
||
# lines.append(f"\ntarget '{app_target_name}' do\n")
|
||
# lines.append(" use_frameworks!\n")
|
||
# lines.append("end\n")
|
||
# app_target_index = len(lines) - 3
|
||
# app_end_index = len(lines) - 1
|
||
# else:
|
||
# for j in range(app_target_index + 1, len(lines)):
|
||
# if 'end' in lines[j]:
|
||
# app_end_index = j
|
||
# break
|
||
|
||
# # 查找 app_target_name 下的子目标 target_name 的闭包
|
||
# child_target_found = False
|
||
# child_target_index = -1
|
||
# for i, line in enumerate(lines[app_target_index + 1: app_end_index]):
|
||
# if f"target '{target_name}' do" in line:
|
||
# child_target_found = True
|
||
# child_target_index = i + app_target_index + 1
|
||
# break
|
||
|
||
# if not child_target_found:
|
||
# print(f"➕ Adding target '{target_name}' inside '{app_target_name}'")
|
||
# lines.insert(app_end_index, f"\ntarget '{target_name}' do\n")
|
||
# lines.insert(app_end_index + 1, " use_frameworks!\n")
|
||
# lines.insert(app_end_index + 2, "end\n")
|
||
# child_target_index = app_end_index + 1
|
||
|
||
# # 在 target_name 的闭包中添加依赖项
|
||
# for dep in dependencies:
|
||
# lines.insert(child_target_index + 1, f" {dep}\n")
|
||
# child_target_index += 1
|
||
|
||
# return True
|
||
|
||
def process_podfile(podfile_path, app_target_name, target_name, miniversion, dependencies):
|
||
"""处理 Podfile
|
||
1. podfile_path:podfile文件的路径
|
||
2. target_name:项目名称
|
||
3. dependencies:pod依赖信息的列表(示例: ["pod 'XYSDK'", "pod 'TDKWechatHandle'"])
|
||
"""
|
||
print(f"process_podfile {podfile_path} - {target_name} - {miniversion} - {dependencies}")
|
||
if not os.path.exists(podfile_path):
|
||
create_podfile(podfile_path, miniversion, app_target_name, target_name)
|
||
|
||
lines = read_podfile(podfile_path)
|
||
|
||
# 更新依赖项
|
||
if update_dependencies(lines, app_target_name, target_name, dependencies):
|
||
write_podfile(podfile_path, lines)
|
||
|
||
def install_podfile(project_dir):
|
||
# 执行 pod 命令
|
||
print("执行 pod deintegrate && pod install")
|
||
result = subprocess.run(["pod", "deintegrate"], cwd=project_dir)
|
||
if result.returncode != 0:
|
||
print("❌ pod deintegrate 失败")
|
||
exit(4)
|
||
|
||
result = subprocess.run(["pod", "install", "--repo-update"], cwd=project_dir)
|
||
if result.returncode != 0:
|
||
print("❌ pod install 失败")
|
||
exit(4)
|
||
|
||
print("✅ Podfile 处理完成!")
|
||
|
||
def find_app_target_names(project):
|
||
print(f"💡 find_app_targets ")
|
||
# 遍历所有的targets,找到主工程target
|
||
app_targets = []
|
||
for target in project.objects.get_targets():
|
||
if target.productType == "com.apple.product-type.application":
|
||
app_targets.append(target.name)
|
||
return app_targets
|
||
|
||
def copy_bundle_to_main_projet(project, project_path):
|
||
print(f"💡 copy_bundle_to_main_projet {project_path}")
|
||
pod_source_path = os.path.join(project_path, "Pods")
|
||
if os.path.exists(pod_source_path):
|
||
bundle_paths = []
|
||
bundle_sources = [bundle for bundles in BUNDLE_SOURCE.values() for bundle in bundles]
|
||
for root, dirs, files in os.walk(pod_source_path):
|
||
folder_name = os.path.basename(root)
|
||
if "Pods" in root and root.endswith(".bundle") and folder_name in bundle_sources:
|
||
bundle_paths.append(os.path.abspath(root))
|
||
target_names = find_app_target_names(project)
|
||
print(f"💡 copy_bundle_to_main_projet target_names {target_names}")
|
||
for bundle in bundle_paths:
|
||
for target_name in target_names:
|
||
add_file_to_project(project, bundle, target_name=target_name, skip_if_exsit=False)
|
||
project.save()
|
||
|
||
def add_file_to_embed_frameworks(project, project_path):
|
||
print(f"💡 add_file_to_embed_frameworks {project_path}")
|
||
pod_source_path = os.path.join(project_path, "Pods")
|
||
if os.path.exists(pod_source_path):
|
||
embed_framework_paths = []
|
||
embed_framework_sources = [framework for frameworks in EMBEM_FRAMEWORK_SOURCE.values() for framework in frameworks]
|
||
for root, dirs, files in os.walk(pod_source_path):
|
||
folder_name = os.path.basename(root)
|
||
if "Pods" in root and (root.endswith(".framework") or root.endswith(".xcframework")) and folder_name in embed_framework_sources:
|
||
embed_framework_paths.append(os.path.abspath(root))
|
||
target_names = find_app_target_names(project)
|
||
print(f"💡 add_file_to_embed_frameworks target_names {embed_framework_paths}")
|
||
print(f"💡 add_file_to_embed_frameworks target_names {target_names}")
|
||
for framework in embed_framework_paths:
|
||
for target_name in target_names:
|
||
add_file_to_project(project, framework, target_name=target_name, skip_if_exsit=False)
|
||
project.save()
|
||
|
||
def process_wechat_setting(project,info_plist_path, app_target_name):
|
||
print(f"💡 process_wechat_setting ")
|
||
wechat_config = build_config.get('login').get('wechat')
|
||
if wechat_config.get('wxAppId') and wechat_config.get('wxUniversalLink'):
|
||
add_array_to_info_plist(info_plist_path, 'LSApplicationQueriesSchemes', ['weixin','weixinULAPI','weixinURLParamsAPI','wechat'])
|
||
url_scheme = {
|
||
'CFBundleURLName': 'wx',
|
||
'CFBundleURLSchemes': [wechat_config.get('wxAppId')]
|
||
}
|
||
add_array_to_info_plist(info_plist_path, 'CFBundleURLTypes', [url_scheme])
|
||
add_associated_domains_capability(project, wechat_config.get('wxUniversalLink'), app_target_name)
|
||
else:
|
||
print(f"❌ process_wechat_setting configs are incomplete ")
|
||
|
||
def process_yidunlivedetect_setting(project,info_plist_path):
|
||
print(f"💡 process_yidunlivedetect_setting ")
|
||
channel_config = build_config.get('login').get('yidunlivedetect')
|
||
if channel_config.get('businessId'):
|
||
add_string_to_info_plist(info_plist_path, 'kYiDunLiveDetectBusinessId', channel_config.get('businessId'), False)
|
||
if not channel_config.get('NSPhotoLibraryUsageDescription'):
|
||
add_string_to_info_plist(info_plist_path, 'NSPhotoLibraryUsageDescription', '相册权限文案')
|
||
else:
|
||
add_string_to_info_plist(info_plist_path, 'NSPhotoLibraryUsageDescription', channel_config.get('NSPhotoLibraryUsageDescription'))
|
||
if not channel_config.get('NSCameraUsageDescription'):
|
||
add_string_to_info_plist(info_plist_path, 'NSCameraUsageDescription', '相机权限文案')
|
||
else:
|
||
add_string_to_info_plist(info_plist_path, 'NSCameraUsageDescription', channel_config.get('NSCameraUsageDescription'))
|
||
else:
|
||
print(f"❌ process_yidunlivedetect_setting configs are incomplete ")
|
||
|
||
def process_facebook_setting(project,info_plist_path):
|
||
print(f"💡 process_facebook_setting ")
|
||
channel_config = build_config.get('login').get('facebook')
|
||
if channel_config.get('facebookAppId') and channel_config.get('facebookClientToken') and channel_config.get('facebookDisplayName'):
|
||
facebookAppId = channel_config.get('facebookAppId')
|
||
add_string_to_info_plist(info_plist_path, 'FacebookAppID', facebookAppId, False)
|
||
add_string_to_info_plist(info_plist_path, 'FacebookClientToken', channel_config.get('facebookClientToken'), False)
|
||
add_string_to_info_plist(info_plist_path, 'FacebookDisplayName', channel_config.get('facebookDisplayName'), False)
|
||
add_array_to_info_plist(info_plist_path, 'LSApplicationQueriesSchemes', ['fb-messenger-share-api','fbapi'])
|
||
url_scheme = {
|
||
'CFBundleURLName': 'fb',
|
||
'CFBundleURLSchemes': [f'fb{facebookAppId}']
|
||
}
|
||
add_array_to_info_plist(info_plist_path, 'CFBundleURLTypes', [url_scheme])
|
||
else:
|
||
print(f"❌ process_facebook_setting configs are incomplete ")
|
||
|
||
def process_google_setting(project,info_plist_path):
|
||
print(f"💡 process_google_setting ")
|
||
channel_config = build_config.get('login').get('google')
|
||
googleiOSClientId = channel_config.get('googleiOSClientId')
|
||
if googleiOSClientId:
|
||
add_string_to_info_plist(info_plist_path, 'GIDClientID', googleiOSClientId, False)
|
||
fanzhuan_google_client_id = ".".join(googleiOSClientId.split(".")[::-1])
|
||
url_scheme = {
|
||
'CFBundleURLName': 'google',
|
||
'CFBundleURLSchemes': [fanzhuan_google_client_id]
|
||
}
|
||
add_array_to_info_plist(info_plist_path, 'CFBundleURLTypes', [url_scheme])
|
||
else:
|
||
print(f"❌ process_google_setting configs are incomplete ")
|
||
|
||
def process_line_setting(project,info_plist_path):
|
||
print(f"💡 process_line_setting ")
|
||
channel_config = build_config.get('login').get('line')
|
||
if channel_config.get('TYLineChannelID') and channel_config.get('TYLineLinkURL'):
|
||
add_string_to_info_plist(info_plist_path, 'TYLineChannelID', channel_config.get('TYLineChannelID'), False)
|
||
add_string_to_info_plist(info_plist_path, 'TYLineLinkURL', channel_config.get('TYLineLinkURL'), False)
|
||
add_array_to_info_plist(info_plist_path, 'LSApplicationQueriesSchemes', ['lineauth2'])
|
||
package = build_config.get('package')
|
||
url_scheme = {
|
||
'CFBundleURLName': 'line',
|
||
'CFBundleURLSchemes': [f'line3rdp.{package}']
|
||
}
|
||
add_array_to_info_plist(info_plist_path, 'CFBundleURLTypes', [url_scheme])
|
||
try:
|
||
result = subprocess.run(["where", "carthage"], capture_output=True, text=True, check=True)
|
||
carthage_path = result.stdout.strip().split("\n")[0]
|
||
shell_scrip = f"{carthage_path} copy-frameworks"
|
||
target_names = find_app_target_names(project)
|
||
add_shell_script(project, shell_scrip, target_name=target_names)
|
||
except subprocess.CalledProcessError:
|
||
print(f"❌ process_line_setting Carthage not exist ")
|
||
exit(1)
|
||
else:
|
||
print(f"❌ process_line_setting configs are incomplete ")
|
||
|
||
def process_twitter_setting(project,info_plist_path):
|
||
print(f"💡 process_twitter_setting ")
|
||
channel_config = build_config.get('login').get('twitter')
|
||
if channel_config.get('twitterClientid') and channel_config.get('twitterSpoce'):
|
||
add_string_to_info_plist(info_plist_path, 'TYTwitterClientid', channel_config.get('twitterClientid'), False)
|
||
add_array_to_info_plist(info_plist_path, 'TYTwitterSpoce', list(channel_config.get('twitterSpoce')))
|
||
else:
|
||
print(f"❌ process_twitter_setting configs are incomplete ")
|
||
|
||
def process_rongheguiyin_setting(project, info_plist_path):
|
||
channel_config = build_config.get('login').get('rongheguiyin')
|
||
url_scheme = {
|
||
'CFBundleURLName': 'rongheguiyin',
|
||
'CFBundleURLSchemes': [build_config.get('package')]
|
||
}
|
||
add_array_to_info_plist(info_plist_path, 'CFBundleURLTypes', [url_scheme])
|
||
if channel_config and 'RongHeGuiYinStrategy' in channel_config:
|
||
add_bool_to_info_plist(info_plist_path, "RongHeGuiYinStrategy", channel_config.get('RongHeGuiYinStrategy'), False)
|
||
|
||
def process_aihelp_setting(project, info_plist_path):
|
||
channel_config = build_config.get('login').get('aihelp')
|
||
if channel_config.get('TYAIHelpAppID') and channel_config.get('TYAIHelpDomainName') and channel_config.get('TYAIHelpLanguage'):
|
||
add_string_to_info_plist(info_plist_path, 'TYAIHelpAppID', channel_config.get('TYAIHelpAppID'), False)
|
||
add_string_to_info_plist(info_plist_path, 'TYAIHelpDomainName', channel_config.get('TYAIHelpDomainName'), False)
|
||
add_string_to_info_plist(info_plist_path, 'TYAIHelpLanguage', channel_config.get('TYAIHelpLanguage'), False)
|
||
add_string_to_info_plist(info_plist_path, 'NSPhotoLibraryUsageDescription', channel_config.get('NSPhotoLibraryUsageDescription'), False)
|
||
add_bool_to_info_plist(info_plist_path, 'PHPhotoLibraryPreventAutomaticLimitedAccessAlert', channel_config.get('PHPhotoLibraryPreventAutomaticLimitedAccessAlert'), False)
|
||
else:
|
||
print(f"❌ process_aihelp_setting configs are incomplete ")
|
||
|
||
def process_bugly_setting(project, info_plist_path):
|
||
channel_config = build_config.get('login').get('bugly')
|
||
if channel_config.get('BuglyAppID'):
|
||
add_string_to_info_plist(info_plist_path, 'BuglyAppID', channel_config.get('BuglyAppID'), False)
|
||
else:
|
||
print(f"❌ process_bugly_setting configs are incomplete ")
|
||
|
||
def get_info_plist_path(project_path, target_name):
|
||
path = os.path.join(project_path, f'Info.plist')
|
||
if os.path.exists(path):
|
||
info_plist_path = path
|
||
else:
|
||
path = os.path.join(project_path, f'{target_name}/Info.plist')
|
||
if os.path.exists(path):
|
||
info_plist_path = path
|
||
print(f"💡 get_info_plist_path {info_plist_path}")
|
||
return info_plist_path
|
||
|
||
def process_project_setting(project_path, project_name, miniversion, target_name):
|
||
print("2️⃣ process_project_setting")
|
||
project_pbxproj_path = os.path.join(project_path, f'{project_name}/project.pbxproj')
|
||
# 工程
|
||
project = XcodeProject.load(project_pbxproj_path)
|
||
# plist文件
|
||
info_plist_path = get_info_plist_path(project_path, target_name)
|
||
target_names = find_app_target_names(project)
|
||
if len(target_names) != 0:
|
||
app_target_name = target_names[0]
|
||
else:
|
||
app_target_name = None
|
||
# 基础设置: 钥匙串 apple登录 applink C++兼容 swift
|
||
add_file_to_project(project, coustom_plist_file_path, skip_if_exsit=False)
|
||
project.add_other_ldflags(['$(inherited)', '-ObjC', '-all_load'])
|
||
add_sign_in_with_apple_capability(project, app_target_name)
|
||
set_library_search_paths(project, [])
|
||
# add_keychain_capability(project, app_target_name)
|
||
|
||
# 遍历设置每一个渠道所需信息
|
||
print(f"2️⃣ process_project_setting {build_sdk_names_list}")
|
||
for channel_name in build_sdk_names_list:
|
||
if channel_name in CHANNEL_AND_NAME_LIST:
|
||
if channel_name == "fcm" or channel_name == "push":
|
||
print(f"💡 process_{channel_name}_setting ")
|
||
channel_config = build_config.get(channel_name)
|
||
add_dependencies_set(channel_config.get('podDependencies'))
|
||
if channel_name == "push":
|
||
add_notification_capability(project, app_target_name)
|
||
add_background_modes_capability(info_plist_path)
|
||
else:
|
||
channel_config = build_config.get('login').get(channel_name)
|
||
add_dependencies_set(channel_config.get('podDependencies'))
|
||
if channel_name == "wechat":
|
||
process_wechat_setting(project, info_plist_path, app_target_name)
|
||
elif channel_name == "wangyiyidun" or channel_name == "yidunloginv2":
|
||
if channel_config.get('unionSpecialLogo'):
|
||
union_special_logo_path = os.path.join(project_path, 'union_special_logo.png')
|
||
add_file_to_project(project, union_special_logo_path, skip_if_exsit=False)
|
||
project.add_other_ldflags(['$(inherited)', '-fprofile-instr-generate'])
|
||
elif channel_name == "yidunzhiwen":
|
||
add_string_to_info_plist(info_plist_path, 'kYiDunDeviceBusinessId', channel_config.get('businessId'), False)
|
||
elif channel_name == "yiduncaptcha":
|
||
add_string_to_info_plist(info_plist_path, 'kYiDunCaptchaBusinessId', channel_config.get('businessId'), False)
|
||
if channel_config.get('kYiDunCaptchaCloseBtn'):
|
||
add_string_to_info_plist(info_plist_path, 'kYiDunCaptchaCloseBtn', "1", False)
|
||
elif channel_name == "yidunlivedetect":
|
||
process_yidunlivedetect_setting(project, info_plist_path)
|
||
elif channel_name == "appsflyer":
|
||
add_string_to_info_plist(info_plist_path, 'TYAppsFlyerAppleAppId', channel_config.get('appsFlyerAppleAppId'), False)
|
||
add_string_to_info_plist(info_plist_path, 'TYAppsFlyerDevkey', channel_config.get('appsFlyerDevkey'), False)
|
||
elif channel_name == "firebaseanalytics":
|
||
google_plist_path = os.path.join(project_path, 'GoogleService-Info.plist')
|
||
add_file_to_project(project, google_plist_path, skip_if_exsit=False)
|
||
elif channel_name == "facebook":
|
||
process_facebook_setting(project, info_plist_path)
|
||
elif channel_name == "google":
|
||
process_google_setting(project, info_plist_path)
|
||
elif channel_name == "line":
|
||
process_line_setting(project, info_plist_path)
|
||
elif channel_name == "twitter":
|
||
process_twitter_setting(project, info_plist_path)
|
||
elif channel_name == "rongheguiyin":
|
||
process_rongheguiyin_setting(project, info_plist_path)
|
||
elif channel_name == "douyin":
|
||
douyin_config_path = os.path.join(project_path, 'UOPSDKConfig.json')
|
||
add_file_to_project(project, douyin_config_path, skip_if_exsit=False)
|
||
elif channel_name == "aihelp":
|
||
process_aihelp_setting(project, info_plist_path)
|
||
elif channel_name == "bugly":
|
||
process_bugly_setting(project, info_plist_path)
|
||
elif channel_name == "firebasecrashlytics":
|
||
input_files = [
|
||
'${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}',
|
||
'$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)'
|
||
]
|
||
shell_scrip = '"$SRCROOT/Pods/FirebaseCrashlyticsSDK/run"'
|
||
target_names = find_app_target_names(project)
|
||
print(f'✅ target_namestarget_namestarget_names = {target_names}')
|
||
add_shell_script(project, shell_scrip, target_name=target_names, input_files=input_files)
|
||
project.set_flags('DEBUG_INFORMATION_FORMAT', 'dwarf-with-dsym')
|
||
elif channel_name == "firebaseodm":
|
||
add_bool_to_info_plist(info_plist_path, 'kFirebaseODM', True, False)
|
||
|
||
project.save()
|
||
# 处理pod文件,并install
|
||
podfile_path = os.path.join(project_path, "Podfile")
|
||
add_dependencies_set(build_config.get('tuyooParam').get('podDependencies'))
|
||
process_podfile(podfile_path, app_target_name, target_name, miniversion, dependencies)
|
||
install_podfile(project_path)
|
||
if app_target_name and app_target_name != target_name:
|
||
copy_bundle_to_main_projet(project, project_path)
|
||
add_file_to_embed_frameworks(project, project_path)
|
||
install_podfile(project_path)
|
||
|
||
if __name__ == "__main__":
|
||
print(f'✅ input info {argparse}')
|
||
|
||
parser = argparse.ArgumentParser(description="Process some integers.")
|
||
parser.add_argument('--configFile', type=str, required=True, help='build config json path')
|
||
parser.add_argument('--workspace', type=str, required=True, help='export ipa project path')
|
||
parser.add_argument('--minos', type=str, required=True, help='miniversion')
|
||
parser.add_argument('--targetName', type=str, help='target_name')
|
||
args = parser.parse_args()
|
||
print(f"args: {args}")
|
||
if args.configFile:
|
||
build_config_json_path = args.configFile
|
||
else:
|
||
print('❌ config json path not set')
|
||
exit(1)
|
||
|
||
if args.workspace:
|
||
export_ipa_project_path = args.workspace
|
||
if not export_ipa_project_path or not os.path.isdir(export_ipa_project_path):
|
||
print('❌ project path not set')
|
||
exit(1)
|
||
xcodeproj_path = ''
|
||
project_name = ''
|
||
for file in os.listdir(export_ipa_project_path):
|
||
xcodeproj_path = os.path.join(export_ipa_project_path, file)
|
||
if os.path.exists(xcodeproj_path) and xcodeproj_path.endswith('.xcodeproj'):
|
||
project_name = os.path.basename(xcodeproj_path)
|
||
print(f"✅ project 文件存在: {project_name} - {xcodeproj_path}")
|
||
break
|
||
|
||
if args.minos:
|
||
miniversion = args.minos
|
||
if args.targetName:
|
||
target_name = args.targetName
|
||
|
||
if not miniversion:
|
||
miniversion = IOSMINIVERSION
|
||
if not target_name:
|
||
target_name = target_name = os.path.splitext(project_name)[0]
|
||
|
||
print(f"✅ 打包基础信息:\n build_config_json_path - {build_config_json_path} \n export_ipa_project_path - {export_ipa_project_path} \n project_name - {project_name} \n miniversion - {miniversion} \n target_name - {target_name}")
|
||
if os.path.exists(xcodeproj_path) and project_name != '':
|
||
# 解析配置信息
|
||
costom_plist_file_path = os.path.join(export_ipa_project_path, 'EngineSdkBaseInfo.plist')
|
||
parse_build_config_json(build_config_json_path, costom_plist_file_path)
|
||
# SDK+渠道配置
|
||
process_project_setting(export_ipa_project_path, project_name, miniversion, target_name)
|
||
else:
|
||
print('❌ xcodeproj file not exist')
|
||
exit(1) |