2026-06-04 17:16:17 +08:00
#if UNITY_IOS
using System.IO ;
using System.Linq ;
using System.Text ;
using UnityEditor ;
using UnityEditor.Callbacks ;
using UnityEditor.iOS.Xcode ;
using UnityEditor.iOS.Xcode.Extensions ;
using UnityEngine ;
namespace Dirichlet.Mediation.Editor
{
/// <summary>
/// iOS build post-processor for Dirichlet Mediation SDK.
/// Generates dynamic Podfile based on adapter settings and runs CocoaPods installation.
///
/// 重构说明( 2026-02-05) :
/// - 不再在 Podfile 内做 target 猜测,改为 Post Process 直接从 pbxproj 读取真实 target 名称
/// - 解析失败时直接中断并输出可诊断信息,不再静默兜底
///
/// 修复( 2026-03-12) :
/// - 所有 pods 统一放到 Framework target( UnityFramework) , 不再分层
/// - 原因: SDK 通过 NSClassFromString 动态查找 Adapter 类, Adapter 必须与 SDK 在同一 target,
/// 否则静态库符号会被 strip 导致运行时找不到适配器类
/// </summary>
public class DirichletMediationIOSPostProcessor
{
2026-06-12 16:05:13 +08:00
private const string DefaultIOSSDKVersion = "4.2.0.1" ;
2026-06-04 17:16:17 +08:00
private const string MinIOSVersion = "11.0" ;
2026-06-12 16:05:13 +08:00
private const string DefaultTrackingUsageDescription = "该标识符将用于向您投放个性化广告" ;
2026-06-04 17:16:17 +08:00
// 环境变量 override( 仅在解析失败或接入方工程极端定制时使用)
private const string ENV_FRAMEWORK_TARGET = "DIRICHLET_UNITY_FRAMEWORK_TARGET" ;
private const string ENV_APP_TARGET = "DIRICHLET_UNITY_APP_TARGET" ;
2026-06-12 16:05:13 +08:00
private const string ENV_IOS_SDK_VERSION = "DIRICHLET_IOS_SDK_VERSION" ;
private const string ENV_ATT_DESCRIPTION = "DIRICHLET_IOS_ATT_DESCRIPTION" ;
private const string PrefKeyIOSSDKVersion = "Dirichlet.iOS.SDKVersion" ;
private const string PrefKeyATTDescription = "Dirichlet.iOS.TrackingUsageDescription" ;
2026-06-13 06:28:03 +08:00
private const string IOSDeviceSlice = "ios-arm64" ;
private const string TquicIOSDeviceSlice = "ios-arm64_armv7" ;
private static readonly string [ ] GDTDynamicFrameworkNames = { "GDTMobSDK.framework" , "Tquic.framework" } ;
2026-06-04 17:16:17 +08:00
/// <summary>
/// 解析出的 target 信息
/// </summary>
private class TargetInfo
{
public string FrameworkTargetName { get ; set ; }
public string AppTargetName { get ; set ; }
public string FrameworkTargetGuid { get ; set ; }
public string AppTargetGuid { get ; set ; }
}
[PostProcessBuild(100)]
public static void OnPostprocessBuild ( BuildTarget buildTarget , string pathToBuiltProject )
{
if ( buildTarget ! = BuildTarget . iOS )
{
return ;
}
Debug . Log ( "[DirichletMediation] Starting iOS post-process..." ) ;
try
{
// 确保默认值: iOS 适配器默认勾选(避免历史版本的 EditorPrefs 默认值导致“默认不勾选”)
EnsureDefaultAdapterPrefs ( ) ;
// 0. 解析 target 信息(核心改动:不做猜测,直接从 pbxproj 读取)
var targetInfo = ResolveTargetInfo ( pathToBuiltProject ) ;
// 1. Generate Podfile dynamically based on adapter settings and resolved targets
GeneratePodfile ( pathToBuiltProject , targetInfo ) ;
// 2. Modify Xcode project settings (before pod install)
ModifyXcodeProject ( pathToBuiltProject , targetInfo ) ;
// 3. Modify Info.plist for required permissions
ModifyInfoPlist ( pathToBuiltProject ) ;
// 4. Run pod install
RunPodInstall ( pathToBuiltProject ) ;
2026-06-13 06:28:03 +08:00
// 5. Fix ad SDK framework/header search paths and embed GDT dynamic frameworks (must run after pod install)
FixAdSDKIOSFrameworkConfiguration ( pathToBuiltProject , targetInfo ) ;
2026-06-04 17:16:17 +08:00
Debug . Log ( "[DirichletMediation] iOS post-process completed successfully." ) ;
}
catch ( System . Exception ex )
{
Debug . LogError ( $"[DirichletMediation] iOS post-process failed: {ex.Message}" ) ;
Debug . LogError ( $"[DirichletMediation] Stack trace: {ex.StackTrace}" ) ;
throw ; // 重新抛出,让 Unity 构建失败(不静默跳过)
}
}
/// <summary>
/// 从 pbxproj 解析出 framework target 和 app target 的真实名称
/// 不做猜测,解析失败时直接中断
/// </summary>
private static TargetInfo ResolveTargetInfo ( string projectPath )
{
Debug . Log ( "[DirichletMediation] Resolving target info from pbxproj..." ) ;
// 检查环境变量 override
var envFrameworkTarget = System . Environment . GetEnvironmentVariable ( ENV_FRAMEWORK_TARGET ) ;
var envAppTarget = System . Environment . GetEnvironmentVariable ( ENV_APP_TARGET ) ;
if ( ! string . IsNullOrEmpty ( envFrameworkTarget ) & & ! string . IsNullOrEmpty ( envAppTarget ) )
{
Debug . Log ( $"[DirichletMediation] Using targets from environment variables:" ) ;
Debug . Log ( $" Framework target: {envFrameworkTarget}" ) ;
Debug . Log ( $" App target: {envAppTarget}" ) ;
return new TargetInfo
{
FrameworkTargetName = envFrameworkTarget ,
AppTargetName = envAppTarget ,
// GUID 在需要时从 pbxproj 反查
} ;
}
// 查找 .xcodeproj 文件
var xcodeProjectName = DetectXcodeProjectName ( projectPath ) ;
var projectFilePath = Path . Combine ( projectPath , $"{xcodeProjectName}.xcodeproj/project.pbxproj" ) ;
if ( ! File . Exists ( projectFilePath ) )
{
throw new System . Exception ( $"project.pbxproj not found at: {projectFilePath}" ) ;
}
var pbxProject = new PBXProject ( ) ;
pbxProject . ReadFromFile ( projectFilePath ) ;
#if UNITY_2019_3_OR_NEWER
var frameworkGuid = pbxProject . GetUnityFrameworkTargetGuid ( ) ;
var appGuid = pbxProject . GetUnityMainTargetGuid ( ) ;
#else
// Unity 2019.3 之前只有单一 target
var frameworkGuid = pbxProject . TargetGuidByName ( "Unity-iPhone" ) ;
var appGuid = frameworkGuid ;
#endif
// 直接从 pbxproj 解析 GUID -> name 映射(最稳健,避免 TargetGuidByName 行为差异)
var nativeTargetMap = GetNativeTargetGuidToNameMap ( projectFilePath ) ;
if ( string . IsNullOrEmpty ( frameworkGuid ) | | string . IsNullOrEmpty ( appGuid ) )
{
var allTargets = nativeTargetMap . Values . Distinct ( ) . ToArray ( ) ;
var targetList = allTargets . Any ( ) ? string . Join ( ", " , allTargets ) : "(none)" ;
throw new System . Exception (
$"Unity PBXProject API returned empty target GUID.\n" +
$" .xcodeproj: {xcodeProjectName}\n" +
$" Available targets: {targetList}\n" +
$" Framework GUID: {frameworkGuid ?? " ( null ) "}\n" +
$" App GUID: {appGuid ?? " ( null ) "}\n\n" +
$"解决方案:\n" +
$" 1. 升级 Unity 到 2019.3+(或团结引擎对应版本),确保导出包含 UnityFramework/App 双 target\n" +
$" 2. 或使用环境变量强制指定 target: \n" +
$" export {ENV_FRAMEWORK_TARGET}=YourFrameworkTarget\n" +
$" export {ENV_APP_TARGET}=YourAppTarget"
) ;
}
// 从 GUID 反查 target name
nativeTargetMap . TryGetValue ( frameworkGuid , out var frameworkTargetName ) ;
nativeTargetMap . TryGetValue ( appGuid , out var appTargetName ) ;
// 兜底:某些 Unity 版本可能返回非 PBXNativeTarget GUID, 这时再尝试旧逻辑反查
frameworkTargetName = frameworkTargetName ? ? GetTargetNameByGuid ( pbxProject , frameworkGuid , projectFilePath ) ;
appTargetName = appTargetName ? ? GetTargetNameByGuid ( pbxProject , appGuid , projectFilePath ) ;
// 验证解析结果
if ( string . IsNullOrEmpty ( frameworkTargetName ) | | string . IsNullOrEmpty ( appTargetName ) )
{
// 输出所有 targets 帮助诊断
var allTargets = nativeTargetMap . Values . Distinct ( ) . ToArray ( ) ;
var targetList = allTargets . Any ( ) ? string . Join ( ", " , allTargets ) : "(none)" ;
throw new System . Exception (
$"Failed to resolve target names from pbxproj.\n" +
$" .xcodeproj: {xcodeProjectName}\n" +
$" Available targets: {targetList}\n" +
$" Framework GUID: {frameworkGuid ?? " ( null ) "}\n" +
$" App GUID: {appGuid ?? " ( null ) "}\n\n" +
$"解决方案:\n" +
$" 1. 设置环境变量强制指定 target: \n" +
$" export {ENV_FRAMEWORK_TARGET}=YourFrameworkTarget\n" +
$" export {ENV_APP_TARGET}=YourAppTarget\n" +
$" 2. 检查导出工程是否包含 UnityFramework 和 Unity-iPhone( 或对应的团结引擎 target) "
) ;
}
Debug . Log ( $"[DirichletMediation] Resolved targets:" ) ;
Debug . Log ( $" Framework target: {frameworkTargetName} (GUID: {frameworkGuid})" ) ;
Debug . Log ( $" App target: {appTargetName} (GUID: {appGuid})" ) ;
return new TargetInfo
{
FrameworkTargetName = frameworkTargetName ,
AppTargetName = appTargetName ,
FrameworkTargetGuid = frameworkGuid ,
AppTargetGuid = appGuid
} ;
}
/// <summary>
/// 从 GUID 反查 target name
/// Unity PBXProject API 没有直接提供此方法,需要通过 TargetGuidByName 反向验证
/// </summary>
private static string GetTargetNameByGuid ( PBXProject pbxProject , string targetGuid , string projectFilePath )
{
if ( string . IsNullOrEmpty ( targetGuid ) )
{
return null ;
}
// 最可靠的方式:直接从 pbxproj 解析 GUID -> name
var nativeTargetMap = GetNativeTargetGuidToNameMap ( projectFilePath ) ;
if ( nativeTargetMap . TryGetValue ( targetGuid , out var parsedName ) & & ! string . IsNullOrEmpty ( parsedName ) )
{
return parsedName ;
}
// 常见的 Unity/Tuanjie target 名称
var commonTargetNames = new [ ]
{
"UnityFramework" , "Unity-iPhone" ,
"TuanjieFramework" , "Tuanjie-iPhone" ,
"GameAssembly" , // 部分定制工程
} ;
foreach ( var name in commonTargetNames )
{
try
{
var guid = pbxProject . TargetGuidByName ( name ) ;
if ( guid = = targetGuid )
{
return name ;
}
}
catch
{
// Target 不存在,继续尝试下一个
}
}
// 如果常见名称都不匹配,尝试从文件中解析所有 target
var allTargets = GetAllTargetNames ( projectFilePath ) ;
foreach ( var name in allTargets )
{
try
{
var guid = pbxProject . TargetGuidByName ( name ) ;
if ( guid = = targetGuid )
{
return name ;
}
}
catch
{
// 继续尝试
}
}
return null ;
}
/// <summary>
/// 从 project.pbxproj 解析 PBXNativeTarget 的 GUID -> name 映射
/// </summary>
private static System . Collections . Generic . Dictionary < string , string > GetNativeTargetGuidToNameMap ( string projectFilePath )
{
var result = new System . Collections . Generic . Dictionary < string , string > ( System . StringComparer . OrdinalIgnoreCase ) ;
try
{
// 形如:
// 9D25AB9C213FB47800354C27 /* UnityFramework */ = {
var entryStartRegex = new System . Text . RegularExpressions . Regex (
@"^\s*([0-9A-Fa-f]{24})\s*/\*\s*(.*?)\s*\*/\s*=\s*\{\s*$" ,
System . Text . RegularExpressions . RegexOptions . CultureInvariant
) ;
string currentGuid = null ;
string currentName = null ;
var braceDepth = 0 ;
var isNativeTarget = false ;
foreach ( var line in File . ReadLines ( projectFilePath ) )
{
if ( braceDepth = = 0 )
{
var m = entryStartRegex . Match ( line ) ;
if ( ! m . Success )
{
continue ;
}
currentGuid = m . Groups [ 1 ] . Value . Trim ( ) ;
currentName = m . Groups [ 2 ] . Value . Trim ( ) ;
braceDepth = CountBraceDelta ( line ) ; // start line includes '{'
isNativeTarget = false ;
continue ;
}
if ( ! isNativeTarget & & line . IndexOf ( "isa = PBXNativeTarget;" , System . StringComparison . Ordinal ) > = 0 )
{
isNativeTarget = true ;
}
braceDepth + = CountBraceDelta ( line ) ;
if ( braceDepth < = 0 )
{
if ( isNativeTarget & & ! string . IsNullOrEmpty ( currentGuid ) & & ! string . IsNullOrEmpty ( currentName ) )
{
result [ currentGuid ] = currentName ;
}
currentGuid = null ;
currentName = null ;
braceDepth = 0 ;
isNativeTarget = false ;
}
}
}
catch
{
// ignore
}
return result ;
}
private static int CountBraceDelta ( string line )
{
if ( string . IsNullOrEmpty ( line ) )
{
return 0 ;
}
var delta = 0 ;
foreach ( var c in line )
{
if ( c = = '{' ) delta + + ;
else if ( c = = '}' ) delta - - ;
}
return delta ;
}
/// <summary>
/// 从 pbxproj 文件解析所有 target 名称(用于诊断输出)
/// </summary>
private static string [ ] GetAllTargetNames ( string projectFilePath )
{
var map = GetNativeTargetGuidToNameMap ( projectFilePath ) ;
return map . Values . Distinct ( ) . ToArray ( ) ;
}
/// <summary>
/// 生成 Podfile
/// 所有 pods 统一放到 Framework target( UnityFramework) , 因为:
/// - Bridge (.mm) 和 DirichletMediationSDK 都编译/运行在 Framework target 中
/// - SDK 通过 NSClassFromString 动态查找 Adapter 类, Adapter 必须与 SDK 在同一 target
/// 否则静态库符号可能被 strip 或不在同一二进制中,导致运行时找不到类
/// </summary>
private static void GeneratePodfile ( string projectPath , TargetInfo targetInfo )
{
// Read adapter settings from EditorPrefs
// Note: DirichletAdSDK (DRA adapter) is always enabled as core SDK
var enableCsj = EditorPrefs . GetBool ( "Dirichlet.iOS.EnableCSJ" , true ) ;
var enableGdt = EditorPrefs . GetBool ( "Dirichlet.iOS.EnableGDT" , true ) ;
2026-06-12 16:05:13 +08:00
var sdkVersion = ResolveIOSSDKVersion ( ) ;
2026-06-04 17:16:17 +08:00
var podfileContent = new StringBuilder ( ) ;
podfileContent . AppendLine ( "# Generated by Dirichlet Mediation Unity Plugin" ) ;
podfileContent . AppendLine ( "#" ) ;
podfileContent . AppendLine ( $"# Framework target: {targetInfo.FrameworkTargetName}" ) ;
podfileContent . AppendLine ( $"# App target: {targetInfo.AppTargetName}" ) ;
podfileContent . AppendLine ( ) ;
podfileContent . AppendLine ( "source 'https://cdn.cocoapods.org/'" ) ;
podfileContent . AppendLine ( ) ;
podfileContent . AppendLine ( $"platform :ios, '{MinIOSVersion}'" ) ;
podfileContent . AppendLine ( "use_frameworks! :linkage => :static" ) ;
podfileContent . AppendLine ( ) ;
// 所有 pods 统一放到 Framework target
// SDK 通过 NSClassFromString 查找 Adapter 类,必须在同一 target 中
podfileContent . AppendLine ( $"target '{targetInfo.FrameworkTargetName}' do" ) ;
2026-06-12 16:05:13 +08:00
podfileContent . AppendLine ( $" pod 'DirichletMediationSDK', '{sdkVersion}'" ) ;
2026-06-04 17:16:17 +08:00
if ( enableCsj )
{
2026-06-12 16:05:13 +08:00
podfileContent . AppendLine ( $" pod 'DirichletMediationAdapterCSJ', '{sdkVersion}'" ) ;
2026-06-04 17:16:17 +08:00
}
if ( enableGdt )
{
2026-06-12 16:05:13 +08:00
podfileContent . AppendLine ( $" pod 'DirichletMediationAdapterGDT', '{sdkVersion}'" ) ;
2026-06-04 17:16:17 +08:00
}
// DirichletAdSDK (DRA adapter) is always included as core SDK
2026-06-12 16:05:13 +08:00
podfileContent . AppendLine ( $" pod 'DirichletMediationAdapterDRA', '{sdkVersion}'" ) ;
2026-06-04 17:16:17 +08:00
podfileContent . AppendLine ( "end" ) ;
podfileContent . AppendLine ( ) ;
// Post-install: 基础构建设置
podfileContent . AppendLine ( "post_install do |installer|" ) ;
podfileContent . AppendLine ( " installer.pods_project.targets.each do |target|" ) ;
podfileContent . AppendLine ( " target.build_configurations.each do |config|" ) ;
podfileContent . AppendLine ( $" config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '{MinIOSVersion}'" ) ;
podfileContent . AppendLine ( " config.build_settings['ENABLE_BITCODE'] = 'NO'" ) ;
podfileContent . AppendLine ( " end" ) ;
podfileContent . AppendLine ( " end" ) ;
podfileContent . AppendLine ( "end" ) ;
var podfilePath = Path . Combine ( projectPath , "Podfile" ) ;
File . WriteAllText ( podfilePath , podfileContent . ToString ( ) ) ;
Debug . Log ( $"[DirichletMediation] Generated Podfile at {podfilePath}" ) ;
2026-06-12 16:05:13 +08:00
Debug . Log ( $"[DirichletMediation] All pods allocated to {targetInfo.FrameworkTargetName} (version={sdkVersion}, CSJ={enableCsj}, GDT={enableGdt}, DRA=always)" ) ;
}
private static string ResolveIOSSDKVersion ( )
{
var envVersion = System . Environment . GetEnvironmentVariable ( ENV_IOS_SDK_VERSION ) ;
if ( ! string . IsNullOrWhiteSpace ( envVersion ) )
{
return envVersion . Trim ( ) ;
}
var prefVersion = EditorPrefs . GetString ( PrefKeyIOSSDKVersion , string . Empty ) ;
if ( ! string . IsNullOrWhiteSpace ( prefVersion ) )
{
return prefVersion . Trim ( ) ;
}
return DefaultIOSSDKVersion ;
2026-06-04 17:16:17 +08:00
}
/// <summary>
/// 检测 Xcode 项目名称,兼容 Unity/Tuanjie/自定义项目
/// 动态搜索目录下的 .xcodeproj 文件
/// </summary>
private static string DetectXcodeProjectName ( string projectPath )
{
// 搜索所有 .xcodeproj 目录
var xcodeprojDirs = Directory . GetDirectories ( projectPath , "*.xcodeproj" ) ;
if ( xcodeprojDirs . Length = = 0 )
{
throw new System . Exception (
$"No .xcodeproj found in: {projectPath}\n" +
"请确认 Unity 导出路径正确,且导出已完成。"
) ;
}
// 获取第一个 xcodeproj 的名称(不含扩展名)
var xcodeprojPath = xcodeprojDirs [ 0 ] ;
var xcodeprojName = Path . GetFileNameWithoutExtension ( xcodeprojPath ) ;
Debug . Log ( $"[DirichletMediation] Detected Xcode project: {xcodeprojName}" ) ;
return xcodeprojName ;
}
/// <summary>
/// 修改 Xcode 工程设置(使用已解析的 target 信息)
/// </summary>
private static void ModifyXcodeProject ( string projectPath , TargetInfo targetInfo )
{
var xcodeProjectName = DetectXcodeProjectName ( projectPath ) ;
var projectFilePath = Path . Combine ( projectPath , $"{xcodeProjectName}.xcodeproj/project.pbxproj" ) ;
var pbxProject = new PBXProject ( ) ;
pbxProject . ReadFromFile ( projectFilePath ) ;
// 使用已解析的 target GUID, 或通过名称反查
var targetGuid = targetInfo . FrameworkTargetGuid ;
var mainTargetGuid = targetInfo . AppTargetGuid ;
if ( string . IsNullOrEmpty ( targetGuid ) )
{
targetGuid = pbxProject . TargetGuidByName ( targetInfo . FrameworkTargetName ) ;
}
if ( string . IsNullOrEmpty ( mainTargetGuid ) )
{
mainTargetGuid = pbxProject . TargetGuidByName ( targetInfo . AppTargetName ) ;
}
Debug . Log ( $"[DirichletMediation] Modifying Xcode project:" ) ;
Debug . Log ( $" Framework target: {targetInfo.FrameworkTargetName} (GUID: {targetGuid})" ) ;
Debug . Log ( $" App target: {targetInfo.AppTargetName} (GUID: {mainTargetGuid})" ) ;
2026-06-12 16:05:13 +08:00
// NOTE: Most system frameworks (AdSupport, AVFoundation, WebKit, CoreVideo, etc.)
2026-06-04 17:16:17 +08:00
// are declared in SDK podspecs and will be automatically linked by CocoaPods.
2026-06-12 16:05:13 +08:00
// AppTrackingTransparency is referenced by the Unity bridge, so add it explicitly and weak-link it for iOS 11+.
2026-06-04 17:16:17 +08:00
// - DirichletAdSDK.podspec: AdSupport, SystemConfiguration, Security
// - DirichletCoreSDK.podspec: SystemConfiguration, Security
// - DirichletMediationAdapterCSJ.podspec: CoreVideo
// - Third-party SDKs (Ads-CN, GDTMobSDK) declare their own framework dependencies.
2026-06-12 16:05:13 +08:00
pbxProject . AddFrameworkToProject ( targetGuid , "AppTrackingTransparency.framework" , true ) ;
pbxProject . AddFrameworkToProject ( targetGuid , "AdSupport.framework" , true ) ;
2026-06-04 17:16:17 +08:00
// Set build settings for framework target
pbxProject . SetBuildProperty ( targetGuid , "ENABLE_BITCODE" , "NO" ) ;
pbxProject . SetBuildProperty ( targetGuid , "CLANG_ENABLE_MODULES" , "YES" ) ;
// Set build settings for main target
pbxProject . SetBuildProperty ( mainTargetGuid , "ENABLE_BITCODE" , "NO" ) ;
pbxProject . SetBuildProperty ( mainTargetGuid , "CLANG_ENABLE_MODULES" , "YES" ) ;
pbxProject . SetBuildProperty ( mainTargetGuid , "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES" , "YES" ) ;
// Set LD_RUNPATH_SEARCH_PATHS to allow dynamic frameworks to be found at runtime
pbxProject . SetBuildProperty ( mainTargetGuid , "LD_RUNPATH_SEARCH_PATHS" , "$(inherited) @executable_path/Frameworks" ) ;
pbxProject . SetBuildProperty ( targetGuid , "LD_RUNPATH_SEARCH_PATHS" , "$(inherited) @executable_path/Frameworks @loader_path/Frameworks" ) ;
pbxProject . WriteToFile ( projectFilePath ) ;
Debug . Log ( "[DirichletMediation] Modified Xcode project settings" ) ;
}
/// <summary>
2026-06-13 06:28:03 +08:00
/// UnityFramework 链接阶段需要显式搜索广告 SDK xcframework 内部的真机 slice,
/// 否则 Xcode 可能报 ld: framework 'GDTMobSDK' not found 或 XCFrameworkIntermediates 搜索路径缺失。
/// 同时 GDTMobSDK 提供的是预编译动态库( GDTMobSDK.framework, Tquic.framework) ,
/// 必须 embed 到 app bundle 中才能在运行时加载。由于所有 pods 都在 Framework target 上,
/// CocoaPods 不会自动 embed 到 app target, 需要在 pod install 之后手动处理。
2026-06-04 17:16:17 +08:00
/// 依赖 UnityEditor.iOS.Xcode.Extensions 中的 AddFileToEmbedFrameworks 扩展方法。
/// </summary>
2026-06-13 06:28:03 +08:00
private static void FixAdSDKIOSFrameworkConfiguration ( string projectPath , TargetInfo targetInfo )
2026-06-04 17:16:17 +08:00
{
var enableGdt = EditorPrefs . GetBool ( "Dirichlet.iOS.EnableGDT" , true ) ;
var xcodeProjectName = DetectXcodeProjectName ( projectPath ) ;
var projectFilePath = Path . Combine ( projectPath , $"{xcodeProjectName}.xcodeproj/project.pbxproj" ) ;
var pbxProject = new PBXProject ( ) ;
pbxProject . ReadFromFile ( projectFilePath ) ;
var mainTargetGuid = targetInfo . AppTargetGuid ;
if ( string . IsNullOrEmpty ( mainTargetGuid ) )
{
mainTargetGuid = pbxProject . TargetGuidByName ( targetInfo . AppTargetName ) ;
}
2026-06-13 06:28:03 +08:00
var targetGuid = ResolveUnityFrameworkTargetGuid ( pbxProject , targetInfo ) ;
var frameworkSearchPaths = GetAdSDKFrameworkSearchPaths ( ) ;
var headerSearchPaths = GetAdSDKHeaderSearchPaths ( frameworkSearchPaths ) ;
var searchPathsAdded = AddAdSDKSearchPaths ( pbxProject , targetGuid , frameworkSearchPaths , headerSearchPaths ) ;
PatchPodsXCConfigSearchPaths ( projectPath , targetInfo , frameworkSearchPaths , headerSearchPaths ) ;
2026-06-04 17:16:17 +08:00
var podsDir = Path . Combine ( projectPath , "Pods" ) ;
var embedded = 0 ;
2026-06-13 06:28:03 +08:00
if ( enableGdt )
{
foreach ( var frameworkName in GDTDynamicFrameworkNames )
{
var frameworkPath = FindDynamicFramework ( podsDir , frameworkName ) ;
if ( string . IsNullOrEmpty ( frameworkPath ) )
{
Debug . LogWarning ( $"[DirichletMediation] Dynamic framework not found: {frameworkName}" ) ;
continue ;
}
var relativePath = frameworkPath . StartsWith ( projectPath )
? frameworkPath . Substring ( projectPath . Length + 1 )
: frameworkPath ;
var fileGuid = pbxProject . AddFile ( relativePath , "Frameworks/" + frameworkName ) ;
PBXProjectExtensions . AddFileToEmbedFrameworks ( pbxProject , mainTargetGuid , fileGuid ) ;
embedded + + ;
Debug . Log ( $"[DirichletMediation] Embedded dynamic framework: {frameworkName}" ) ;
}
}
else
{
Debug . Log ( "[DirichletMediation] GDT adapter disabled, skipping dynamic framework embedding" ) ;
}
pbxProject . WriteToFile ( projectFilePath ) ;
if ( searchPathsAdded )
{
Debug . Log ( $"[DirichletMediation] Added ad SDK framework/header search paths to {targetInfo.FrameworkTargetName}" ) ;
}
Debug . Log ( $"[DirichletMediation] Embedded {embedded} GDT dynamic frameworks into {targetInfo.AppTargetName}" ) ;
}
private static string ResolveUnityFrameworkTargetGuid ( PBXProject pbxProject , TargetInfo targetInfo )
{
#if UNITY_2019_3_OR_NEWER
var targetGuid = pbxProject . GetUnityFrameworkTargetGuid ( ) ;
#else
var targetGuid = targetInfo . FrameworkTargetGuid ;
#endif
if ( string . IsNullOrEmpty ( targetGuid ) )
{
targetGuid = targetInfo . FrameworkTargetGuid ;
}
if ( string . IsNullOrEmpty ( targetGuid ) )
{
targetGuid = pbxProject . TargetGuidByName ( targetInfo . FrameworkTargetName ) ;
}
return targetGuid ;
}
private static bool AddAdSDKSearchPaths ( PBXProject pbxProject , string targetGuid , string [ ] frameworkSearchPaths , string [ ] headerSearchPaths )
{
if ( string . IsNullOrEmpty ( targetGuid ) )
{
Debug . LogWarning ( "[DirichletMediation] UnityFramework target GUID is empty, skipping ad SDK search paths" ) ;
return false ;
}
pbxProject . AddBuildProperty ( targetGuid , "FRAMEWORK_SEARCH_PATHS" , "$(inherited)" ) ;
foreach ( var searchPath in frameworkSearchPaths )
{
pbxProject . AddBuildProperty ( targetGuid , "FRAMEWORK_SEARCH_PATHS" , searchPath ) ;
}
pbxProject . AddBuildProperty ( targetGuid , "HEADER_SEARCH_PATHS" , "$(inherited)" ) ;
foreach ( var searchPath in headerSearchPaths )
{
pbxProject . AddBuildProperty ( targetGuid , "HEADER_SEARCH_PATHS" , searchPath ) ;
}
return true ;
}
private static void PatchPodsXCConfigSearchPaths (
string projectPath ,
TargetInfo targetInfo ,
string [ ] frameworkSearchPaths ,
string [ ] headerSearchPaths )
{
var targetSupportRoot = Path . Combine ( projectPath , "Pods" , "Target Support Files" ) ;
if ( ! Directory . Exists ( targetSupportRoot ) )
{
Debug . LogWarning ( "[DirichletMediation] CocoaPods target support files not found, skipping xcconfig search path patch" ) ;
return ;
}
var targetPrefix = "Pods-" + targetInfo . FrameworkTargetName ;
var xcconfigFiles = Directory . GetFiles ( targetSupportRoot , "*.xcconfig" , SearchOption . AllDirectories )
. Where ( path = > Path . GetFileNameWithoutExtension ( path ) . StartsWith ( targetPrefix , System . StringComparison . OrdinalIgnoreCase ) )
. ToArray ( ) ;
if ( xcconfigFiles . Length = = 0 )
{
Debug . LogWarning ( $"[DirichletMediation] No CocoaPods xcconfig found for {targetInfo.FrameworkTargetName}, skipping search path patch" ) ;
return ;
}
var patched = 0 ;
foreach ( var xcconfigPath in xcconfigFiles )
{
var lines = File . ReadAllLines ( xcconfigPath ) . ToList ( ) ;
var changed = UpsertXCConfigBuildSetting ( lines , "FRAMEWORK_SEARCH_PATHS" , frameworkSearchPaths ) ;
changed | = UpsertXCConfigBuildSetting ( lines , "HEADER_SEARCH_PATHS" , headerSearchPaths ) ;
if ( ! changed )
{
continue ;
}
File . WriteAllLines ( xcconfigPath , lines ) ;
patched + + ;
}
Debug . Log ( $"[DirichletMediation] Patched {patched} CocoaPods xcconfig search path file(s) for {targetInfo.FrameworkTargetName}" ) ;
}
private static bool UpsertXCConfigBuildSetting ( System . Collections . Generic . List < string > lines , string propertyName , string [ ] searchPaths )
{
var insertIndex = - 1 ;
var originalLines = new System . Collections . Generic . List < string > ( ) ;
var tokens = new System . Collections . Generic . List < string > ( ) ;
for ( var i = lines . Count - 1 ; i > = 0 ; i - - )
2026-06-04 17:16:17 +08:00
{
2026-06-13 06:28:03 +08:00
if ( ! IsXCConfigBuildSettingLine ( lines [ i ] , propertyName ) )
2026-06-04 17:16:17 +08:00
{
continue ;
}
2026-06-13 06:28:03 +08:00
insertIndex = i ;
originalLines . Add ( lines [ i ] ) ;
CollectXCConfigBuildSettingTokens ( lines [ i ] , tokens ) ;
lines . RemoveAt ( i ) ;
}
if ( ! ContainsXCConfigToken ( tokens , "$(inherited)" ) )
{
tokens . Insert ( 0 , "$(inherited)" ) ;
}
foreach ( var searchPath in searchPaths )
{
AddXCConfigTokenIfMissing ( tokens , searchPath ) ;
}
var newLine = $"{propertyName} = {string.Join(" ", tokens)}" ;
if ( insertIndex < 0 )
{
lines . Add ( newLine ) ;
return true ;
}
lines . Insert ( insertIndex , newLine ) ;
return originalLines . Count ! = 1 | | originalLines [ 0 ] ! = newLine ;
}
private static void CollectXCConfigBuildSettingTokens ( string line , System . Collections . Generic . List < string > tokens )
{
var separatorIndex = line . IndexOf ( '=' ) ;
if ( separatorIndex < 0 | | separatorIndex > = line . Length - 1 )
{
return ;
}
var value = line . Substring ( separatorIndex + 1 ) . Trim ( ) ;
var lineTokens = value . Split ( new [ ] { ' ' , '\t' } , System . StringSplitOptions . RemoveEmptyEntries ) ;
foreach ( var token in lineTokens )
{
if ( token . IndexOf ( "XCFrameworkIntermediates" , System . StringComparison . OrdinalIgnoreCase ) > = 0 )
{
continue ;
}
2026-06-04 17:16:17 +08:00
2026-06-13 06:28:03 +08:00
AddXCConfigTokenIfMissing ( tokens , token ) ;
2026-06-04 17:16:17 +08:00
}
2026-06-13 06:28:03 +08:00
}
2026-06-04 17:16:17 +08:00
2026-06-13 06:28:03 +08:00
private static bool ContainsXCConfigToken ( System . Collections . Generic . List < string > tokens , string value )
{
var normalizedValue = NormalizeXCConfigToken ( value ) ;
return tokens . Any ( token = > NormalizeXCConfigToken ( token ) = = normalizedValue ) ;
}
private static void AddXCConfigTokenIfMissing ( System . Collections . Generic . List < string > tokens , string value )
{
if ( ! ContainsXCConfigToken ( tokens , value ) )
2026-06-04 17:16:17 +08:00
{
2026-06-13 06:28:03 +08:00
tokens . Add ( value ) ;
2026-06-04 17:16:17 +08:00
}
}
2026-06-13 06:28:03 +08:00
private static string NormalizeXCConfigToken ( string token )
{
return ( token ? ? string . Empty ) . Trim ( ) . Trim ( '"' ) ;
}
private static bool IsXCConfigBuildSettingLine ( string line , string propertyName )
{
if ( string . IsNullOrWhiteSpace ( line ) )
{
return false ;
}
var trimmed = line . TrimStart ( ) ;
return trimmed . StartsWith ( propertyName + " =" , System . StringComparison . Ordinal )
| | trimmed . StartsWith ( propertyName + "=" , System . StringComparison . Ordinal ) ;
}
private static string [ ] GetAdSDKFrameworkSearchPaths ( )
{
var sdkVersion = ResolveIOSSDKVersion ( ) ;
var enableCsj = EditorPrefs . GetBool ( "Dirichlet.iOS.EnableCSJ" , true ) ;
var enableGdt = EditorPrefs . GetBool ( "Dirichlet.iOS.EnableGDT" , true ) ;
var paths = new System . Collections . Generic . List < string >
{
VersionedDirichletFrameworkSearchPath ( "DirichletAdSDK" , sdkVersion ) ,
VersionedDirichletFrameworkSearchPath ( "DirichletCoreSDK" , sdkVersion ) ,
VersionedDirichletFrameworkSearchPath ( "DirichletMediationSDK" , sdkVersion ) ,
VersionedDirichletFrameworkSearchPath ( "DirichletMediationAdapterDRA" , sdkVersion )
} ;
if ( enableCsj )
{
paths . Add ( VersionedDirichletFrameworkSearchPath ( "DirichletMediationAdapterCSJ" , sdkVersion ) ) ;
paths . Add ( "$(PODS_ROOT)/Ads-CN/SDK/BUAdSDK.xcframework/" + IOSDeviceSlice ) ;
}
if ( enableGdt )
{
paths . Add ( VersionedDirichletFrameworkSearchPath ( "DirichletMediationAdapterGDT" , sdkVersion ) ) ;
paths . Add ( "$(PODS_ROOT)/GDTMobSDK/GDTFramework/GDTMobSDK.xcframework/" + IOSDeviceSlice ) ;
paths . Add ( "$(PODS_ROOT)/GDTMobSDK/GDTFramework/Tquic.xcframework/" + TquicIOSDeviceSlice ) ;
}
return paths . ToArray ( ) ;
}
private static string [ ] GetAdSDKHeaderSearchPaths ( string [ ] frameworkSearchPaths )
{
return frameworkSearchPaths
. Select ( ToFrameworkHeaderSearchPath )
. Where ( path = > ! string . IsNullOrEmpty ( path ) )
. ToArray ( ) ;
}
private static string VersionedDirichletFrameworkSearchPath ( string frameworkName , string sdkVersion )
{
return $"$(PODS_ROOT)/{frameworkName}/{frameworkName}-{sdkVersion}/{frameworkName}.xcframework/{IOSDeviceSlice}" ;
}
private static string ToFrameworkHeaderSearchPath ( string frameworkSearchPath )
{
var parts = frameworkSearchPath . Split ( new [ ] { '/' } , System . StringSplitOptions . RemoveEmptyEntries ) ;
var xcframeworkPart = parts . LastOrDefault ( part = > part . EndsWith ( ".xcframework" , System . StringComparison . OrdinalIgnoreCase ) ) ;
if ( string . IsNullOrEmpty ( xcframeworkPart ) )
{
return null ;
}
var frameworkName = xcframeworkPart . Substring ( 0 , xcframeworkPart . Length - ".xcframework" . Length ) ;
return $"{frameworkSearchPath}/{frameworkName}.framework/Headers" ;
}
2026-06-04 17:16:17 +08:00
/// <summary>
/// 在 Pods 目录中递归搜索指定的 .framework( 优先 ios-arm64 真机 slice)
/// </summary>
private static string FindDynamicFramework ( string podsDir , string frameworkName )
{
if ( ! Directory . Exists ( podsDir ) )
{
return null ;
}
var candidates = Directory . GetDirectories ( podsDir , frameworkName , SearchOption . AllDirectories ) ;
foreach ( var candidate in candidates )
{
if ( candidate . Contains ( "ios-arm64" ) & & ! candidate . Contains ( "simulator" ) )
{
return candidate ;
}
}
foreach ( var candidate in candidates )
{
if ( ! candidate . Contains ( "simulator" ) )
{
return candidate ;
}
}
return candidates . Length > 0 ? candidates [ 0 ] : null ;
}
private static void ModifyInfoPlist ( string projectPath )
{
var plistPath = Path . Combine ( projectPath , "Info.plist" ) ;
var plist = new PlistDocument ( ) ;
plist . ReadFromFile ( plistPath ) ;
var rootDict = plist . root ;
// Note: The following Info.plist keys should be configured by the developer manually
// to avoid potential App Store review issues:
// - NSAppTransportSecurity: Configure based on your app's network requirements
// - NSLocationWhenInUseUsageDescription: Only add if your app uses location services
//
// Reference: https://ssp.dirichlet.cn/docs/dirichlet-mediation-sdk/dirichlet-mediation-sdk-guide-ios/
// Add SKAdNetwork identifiers for attribution tracking
AddSKAdNetworkIds ( rootDict ) ;
2026-06-12 16:05:13 +08:00
AddTrackingUsageDescription ( rootDict ) ;
2026-06-04 17:16:17 +08:00
plist . WriteToFile ( plistPath ) ;
2026-06-12 16:05:13 +08:00
Debug . Log ( "[DirichletMediation] Modified Info.plist (SKAdNetwork IDs + ATT description)" ) ;
2026-06-04 17:16:17 +08:00
}
private static void AddSKAdNetworkIds ( PlistElementDict rootDict )
{
if ( rootDict . values . ContainsKey ( "SKAdNetworkItems" ) )
{
Debug . Log ( "[DirichletMediation] SKAdNetworkItems already exists, skipping" ) ;
return ;
}
// Add SKAdNetwork IDs required by CSJ (Pangle/穿山甲)
// Reference: https://www.csjplatform.com/supportcenter/5377
var skAdNetworkArray = rootDict . CreateArray ( "SKAdNetworkItems" ) ;
var commonSkAdNetworkIds = new [ ]
{
"238da6jt44.skadnetwork" , // 穿山甲 SKAdNetwork ID
"x2jnk7ly8j.skadnetwork" , // 穿山甲 SKAdNetwork ID
"22mmun2rn5.skadnetwork" // 穿山甲 SKAdNetwork ID
} ;
foreach ( var skAdNetworkId in commonSkAdNetworkIds )
{
var dict = skAdNetworkArray . AddDict ( ) ;
dict . SetString ( "SKAdNetworkIdentifier" , skAdNetworkId ) ;
}
Debug . Log ( $"[DirichletMediation] Added {commonSkAdNetworkIds.Length} SKAdNetwork IDs to Info.plist" ) ;
}
2026-06-12 16:05:13 +08:00
private static void AddTrackingUsageDescription ( PlistElementDict rootDict )
{
if ( rootDict . values . ContainsKey ( "NSUserTrackingUsageDescription" ) )
{
Debug . Log ( "[DirichletMediation] NSUserTrackingUsageDescription already exists, skipping" ) ;
return ;
}
rootDict . SetString ( "NSUserTrackingUsageDescription" , ResolveTrackingUsageDescription ( ) ) ;
Debug . Log ( "[DirichletMediation] Added NSUserTrackingUsageDescription to Info.plist" ) ;
}
private static string ResolveTrackingUsageDescription ( )
{
var envDescription = System . Environment . GetEnvironmentVariable ( ENV_ATT_DESCRIPTION ) ;
if ( ! string . IsNullOrWhiteSpace ( envDescription ) )
{
return envDescription . Trim ( ) ;
}
var prefDescription = EditorPrefs . GetString ( PrefKeyATTDescription , string . Empty ) ;
if ( ! string . IsNullOrWhiteSpace ( prefDescription ) )
{
return prefDescription . Trim ( ) ;
}
return DefaultTrackingUsageDescription ;
}
2026-06-04 17:16:17 +08:00
private static void RunPodInstall ( string projectPath )
{
var podfilePath = Path . Combine ( projectPath , "Podfile" ) ;
if ( ! File . Exists ( podfilePath ) )
{
Debug . LogWarning ( "[DirichletMediation] Podfile not found, skipping pod install" ) ;
return ;
}
Debug . Log ( "[DirichletMediation] Running 'pod install'..." ) ;
Debug . Log ( "[DirichletMediation] Note: This may take a few minutes on first build or when adapters change." ) ;
try
{
// Try to find pod executable
var podPath = FindPodExecutable ( ) ;
if ( string . IsNullOrEmpty ( podPath ) )
{
Debug . LogWarning ( "[DirichletMediation] CocoaPods not found. Please install CocoaPods:" ) ;
Debug . LogWarning ( " sudo gem install cocoapods" ) ;
Debug . LogWarning ( "Then run 'pod install' manually in:" ) ;
Debug . LogWarning ( $" {projectPath}" ) ;
return ;
}
// 优先不做 repo update( 更快、更稳定) ; 如遇到“找不到 podspec”再退化为 --repo-update
var firstArgs = "install" ;
var exitCode = RunPodCommand ( podPath , firstArgs , projectPath , out var output , out var error ) ;
if ( exitCode ! = 0 & & ShouldRetryWithRepoUpdate ( output , error ) )
{
Debug . LogWarning ( "[DirichletMediation] pod install failed (missing specs suspected). Retrying with --repo-update..." ) ;
var retryArgs = "install --repo-update" ;
exitCode = RunPodCommand ( podPath , retryArgs , projectPath , out output , out error ) ;
}
if ( exitCode = = 0 )
{
Debug . Log ( "[DirichletMediation] pod install completed successfully" ) ;
if ( ! string . IsNullOrEmpty ( output ) & & output . Length < 2000 )
{
Debug . Log ( $"Output:\n{output}" ) ;
}
return ;
}
Debug . LogError ( $"[DirichletMediation] pod install failed with exit code {exitCode}" ) ;
if ( ! string . IsNullOrEmpty ( error ) )
{
Debug . LogError ( $"Error:\n{error}" ) ;
}
if ( ! string . IsNullOrEmpty ( output ) )
{
Debug . LogError ( $"Output:\n{output}" ) ;
}
if ( IsCocoaPodsCdnHttp2Error ( output , error ) )
{
Debug . LogWarning (
"[DirichletMediation] Detected CocoaPods CDN HTTP/2 error (e.g. 'Error in the HTTP2 framing layer'). " +
"This is usually a network/proxy/SSL issue. Try rerun without repo update, switch network, upgrade CocoaPods, " +
"or run 'pod install' manually. (Repo doc: docs/FAQ/CocoaPods-SSL证书问题排查SOP.md)"
) ;
}
// 失败即中断:避免导出工程处于“半配置”状态,后续 Xcode build 更难排查
throw new System . Exception ( $"pod install failed with exit code {exitCode}" ) ;
}
catch ( System . Exception ex )
{
Debug . LogError ( $"[DirichletMediation] Failed to run pod install: {ex.Message}" ) ;
Debug . LogWarning ( "[DirichletMediation] Please run 'pod install' manually in the Xcode project directory:" ) ;
Debug . LogWarning ( $" cd {projectPath}" ) ;
Debug . LogWarning ( " pod install" ) ;
throw ;
}
}
private static int RunPodCommand ( string podPath , string arguments , string projectPath , out string output , out string error )
{
var processInfo = new System . Diagnostics . ProcessStartInfo
{
FileName = podPath ,
Arguments = arguments ,
WorkingDirectory = projectPath ,
UseShellExecute = false ,
RedirectStandardOutput = true ,
RedirectStandardError = true ,
CreateNoWindow = true
} ;
// Set UTF-8 encoding to avoid CocoaPods encoding issues
processInfo . EnvironmentVariables [ "LANG" ] = "en_US.UTF-8" ;
processInfo . EnvironmentVariables [ "LC_ALL" ] = "en_US.UTF-8" ;
using ( var process = System . Diagnostics . Process . Start ( processInfo ) )
{
output = process . StandardOutput . ReadToEnd ( ) ;
error = process . StandardError . ReadToEnd ( ) ;
process . WaitForExit ( ) ;
return process . ExitCode ;
}
}
private static bool ShouldRetryWithRepoUpdate ( string output , string error )
{
var combined = ( output ? ? string . Empty ) + "\n" + ( error ? ? string . Empty ) ;
// 常见“本地 specs 未更新导致无法解析依赖”的报错关键字
return combined . Contains ( "Unable to find a specification for" )
| | combined . Contains ( "None of your spec sources contain a spec satisfying" )
| | combined . Contains ( "spec satisfying" )
| | combined . Contains ( "No podspec found for" )
| | combined . Contains ( "could not find compatible versions for pod" ) ;
}
private static bool IsCocoaPodsCdnHttp2Error ( string output , string error )
{
var combined = ( output ? ? string . Empty ) + "\n" + ( error ? ? string . Empty ) ;
return combined . Contains ( "CDN: trunk Repo update failed" )
| | combined . Contains ( "Error in the HTTP2 framing layer" )
| | combined . Contains ( "URL couldn't be downloaded: https://cdn.cocoapods.org/" ) ;
}
private static string FindPodExecutable ( )
{
// 1. POD_BINARY 环境变量优先,方便 CI 或接入方手动配置
var envValue = System . Environment . GetEnvironmentVariable ( "POD_BINARY" ) ;
if ( ! string . IsNullOrEmpty ( envValue ) )
{
var expanded = envValue . Trim ( ) ;
if ( File . Exists ( expanded ) )
{
Debug . Log ( $"[DirichletMediation] Found pod via POD_BINARY: {expanded}" ) ;
return expanded ;
}
Debug . LogWarning ( $"[DirichletMediation] POD_BINARY points to non-existing path: {expanded}" ) ;
}
var possiblePaths = new [ ]
{
"/usr/local/bin/pod" ,
"/opt/homebrew/bin/pod" ,
"/usr/bin/pod" ,
Path . Combine ( System . Environment . GetEnvironmentVariable ( "HOME" ) ? ? "" , ".rbenv/shims/pod" ) ,
Path . Combine ( System . Environment . GetEnvironmentVariable ( "HOME" ) ? ? "" , ".rvm/wrappers/default/pod" )
} ;
foreach ( var path in possiblePaths )
{
if ( File . Exists ( path ) )
{
Debug . Log ( $"[DirichletMediation] Found pod at: {path}" ) ;
return path ;
}
}
// Try using 'which pod'
try
{
var processInfo = new System . Diagnostics . ProcessStartInfo
{
FileName = "/usr/bin/env" ,
Arguments = "which pod" ,
UseShellExecute = false ,
RedirectStandardOutput = true ,
RedirectStandardError = true ,
CreateNoWindow = true
} ;
using ( var process = System . Diagnostics . Process . Start ( processInfo ) )
{
var output = process . StandardOutput . ReadToEnd ( ) . Trim ( ) ;
var error = process . StandardError . ReadToEnd ( ) . Trim ( ) ;
process . WaitForExit ( ) ;
if ( process . ExitCode = = 0 & & ! string . IsNullOrEmpty ( output ) & & File . Exists ( output ) )
{
Debug . Log ( $"[DirichletMediation] Found pod via 'which': {output}" ) ;
return output ;
}
if ( ! string . IsNullOrEmpty ( error ) )
{
Debug . LogWarning ( $"[DirichletMediation] 'which pod' failed: {error}" ) ;
}
}
}
catch ( System . Exception ex )
{
Debug . LogWarning ( $"[DirichletMediation] Failed to resolve pod via 'which': {ex.Message}" ) ;
}
return null ;
}
private const string PrefKeyDefaultsInitialized = "Dirichlet.Mediation.AdapterSettings.DefaultsInitialized.v1" ;
private static void EnsureDefaultAdapterPrefs ( )
{
if ( EditorPrefs . GetBool ( PrefKeyDefaultsInitialized , false ) )
{
return ;
}
// 默认“勾上”: CSJ/GDT( iOS)
// 注意:如接入方需要关闭,可在 Adapter Settings 窗口中手动取消勾选。
EditorPrefs . SetBool ( "Dirichlet.iOS.EnableCSJ" , true ) ;
EditorPrefs . SetBool ( "Dirichlet.iOS.EnableGDT" , true ) ;
// 同时保证 Android 默认一致(无副作用)
EditorPrefs . SetBool ( "Dirichlet.Android.EnableCSJ" , true ) ;
EditorPrefs . SetBool ( "Dirichlet.Android.EnableGDT" , true ) ;
EditorPrefs . SetBool ( PrefKeyDefaultsInitialized , true ) ;
}
}
}
#endif