TuyooSDKPackage/Packages/com.bywaystudios.tuyoosdk/Editor/iOS/EngineSdkiOSBuildProcess.py
2025-12-06 10:50:57 +08:00

948 lines
44 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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_pathpodfile文件的路径
2. target_name项目名称
3. dependenciespod依赖信息的列表示例 ["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)