使用jenkins打包unity工程
Apache配置
- 安装:arch arm64 brew install httpd
- 开启:brew services start httpd
- 重启:brew services restart httpd
- 停止:brew services stop httpd
- 配置文件路径:/opt/homebrew/etc/httpd/httpd.conf,默认监听8080端口,更改端口号后,要重启一下brew services restart httpd
- SSL文件路径 /opt/homebrew/etc/httpd/extra/httpd-ssl.conf,默认监听 8443,Apache 默认使用 443 端口来处理 HTTPS 请求。由于 1024 以下的端口通常需要超级用户权限才能绑定,因此如果您希望 Apache 在没有 `sudo` 权限的情况下运行,您需要将其配置为使用高于 1024 的端口(如 8443)
- 服务器文件存放目录 /opt/homebrew/var/www,把文件放到这个文件夹下,别人可以通过访问ip+port+相对地址访问
Jenkins配置
- 安装:Jenkins自动打包并部署到远程服务器_jenkins客户端打包好处-CSDN博客
- 局域网ip访问不了jenkins问题:jenkins局域网无法访问 - 简书
- 批量删除构建历史:
Jenkins.instance.getItemByFullName(jobName).builds.findAll { it.number <= maxNumber }.each { it.delete() }
- Jenkins的参数一切皆字符串,bool类型参数也是字符串,通过when{expression{return BoolParam.toBoolean()}}判断
- Unity 访问命令行参数:
string[] args = System.Environment.GetCommandLineArgs(); //每个空格都是一个参数 比如 -project testPath customParam1:111 customParam2:222 static string GetSingleCommandlineArgs(string[] args, string key) { string value = String.Empty; foreach (var arg in args) { Debug.Log("命令行参数:args:" + arg); if (arg.Contains(key)) { value = arg; break; } } return value; }
- Unity iOS后处理方法,包括更改Build Phase的顺序:
#if UNITY_IOS using UnityEngine; using UnityEditor; using UnityEditor.Callbacks; using System.IO; using System; using System.Collections.Generic; using System.Reflection; using AMPSDK.Utils; using UnityEditor.iOS.Xcode; using UnityEditor.iOS.Xcode.Extensions; namespace EditorBuildTool { public static class AviaXCodeSetter { private const string BtApplePayMerchantId = ""; private const string UtApplePayMerchantId = ""; private const string TeamId = ""; private const string AppGroup = ""; private const string DeepLinkDomain = ""; private const string CrashlyticsShellScriptPhaseName = "Crashlytics Run Script"; private const string EmbedAppExtensionsPhaseName = "Embed App Extensions"; public static bool IsUploadIpa = false; private static string TrustFrameworkPath => Path.Combine(Application.dataPath, "../Archive/iOS/Trustly"); private static string GoogleInfoPlistPath => Path.Combine(Application.dataPath, "../Archive/iOS/Test/GoogleService-Info.plist"); [PostProcessBuild(Int32.MaxValue - 1)] public static void OnPostProcessBuild(BuildTarget target, string path2BuildProject) { if (target != BuildTarget.iOS) return; Debug.LogWarning(path2BuildProject); ModifyProjectSettings(path2BuildProject); ModifyPodProjectSettings(path2BuildProject); Process(path2BuildProject); } static void ModifyProjectSettings(string path2BuildProject) { string projPath = path2BuildProject + "/Unity-iPhone.xcodeproj/project.pbxproj"; PBXProject project = new PBXProject(); project.ReadFromFile(projPath); AddNotificationTargets(project, path2BuildProject); //临时屏蔽 SubModifyProjectSettings(path2BuildProject, project); File.WriteAllText(projPath, project.WriteToString()); } static void AddNotificationTargets(PBXProject pbxProject, string path2BuildProject) { string notificationTestPath = Path.Combine(Application.dataPath, "../Archive/iOS/NotificationTest/NotificationService"); Directory.CreateDirectory(Path.Combine(path2BuildProject, "NotificationService")); CopyFileToPath(path2BuildProject, $"{notificationTestPath}/Info.plist", "NotificationService/Info.plist", pbxProject, true); CopyFileToPath(path2BuildProject, $"{notificationTestPath}/NotificationService.h", "NotificationService/NotificationService.h", pbxProject, true); string mPath = CopyFileToPath(path2BuildProject, $"{notificationTestPath}/NotificationService.m", "NotificationService/NotificationService.m", pbxProject, true); string guid = pbxProject.AddAppExtension(pbxProject.GetUnityMainTargetGuid(), "NotificationService", $"{Application.identifier}.NotificationServices", "NotificationService/Info.plist"); pbxProject.AddFileToBuild(guid, mPath); #region 子模块的build setting pbxProject.SetBuildProperty(guid, "CODE_SIGN_IDENTITY", "Apple Development"); pbxProject.SetBuildProperty(guid, "IPHONEOS_DEPLOYMENT_TARGET", "12.0"); pbxProject.SetBuildProperty(guid, "ARCHS", "arm64"); pbxProject.SetBuildProperty(guid, "GENERATE_INFOPLIST_FILE", "YES"); pbxProject.SetBuildProperty(guid, "CURRENT_PROJECT_VERSION", "0"); pbxProject.SetBuildProperty(guid, "MARKETING_VERSION", "1.2"); pbxProject.SetBuildProperty(guid, "GCC_ENABLE_OBJC_EXCEPTIONS", "YES"); pbxProject.SetTeamId(guid, TeamId); pbxProject.AddFrameworkToProject(guid, "UserNotifications.framework", false); CopyFileToPath(path2BuildProject, $"{notificationTestPath}/NotificationService.entitlements", "NotificationService/NotificationService.entitlements", pbxProject, true); pbxProject.AddBuildPropertyForConfig(pbxProject.BuildConfigByName(guid, "Debug"), "CODE_SIGN_ENTITLEMENTS", "NotificationService/NotificationService.entitlements"); pbxProject.AddBuildPropertyForConfig(pbxProject.BuildConfigByName(guid, "Release"), "CODE_SIGN_ENTITLEMENTS", "NotificationService/NotificationService.entitlements"); pbxProject.AddBuildPropertyForConfig(pbxProject.BuildConfigByName(guid, "ReleaseForRunning"), "CODE_SIGN_ENTITLEMENTS", "NotificationService/NotificationService.entitlements"); pbxProject.AddBuildPropertyForConfig(pbxProject.BuildConfigByName(guid, "ReleaseForProfiling"), "CODE_SIGN_ENTITLEMENTS", "NotificationService/NotificationService.entitlements"); if (IsUploadIpa) { pbxProject.SetBuildProperty(guid, "CODE_SIGN_STYLE", "Manual"); pbxProject.SetBuildProperty(guid, "CODE_SIGN_IDENTITY[sdk=iphoneos*]", "钥匙串中证书的名字"); pbxProject.SetBuildProperty(guid, "CODE_SIGN_IDENTITY", "钥匙串中证书的名字"); pbxProject.SetBuildProperty(guid, "PROVISIONING_PROFILE_SPECIFIER", "Unity SDK Demo Provision Notification"); } else { pbxProject.SetBuildProperty(guid, "CODE_SIGN_STYLE", "Automatic"); } #endregion #region 子模块的entitlement string relativeEntitlementFilePath = "NotificationService/NotificationService.entitlements"; string absoluteEntitlementFilePath = path2BuildProject + "/" + relativeEntitlementFilePath; PlistDocument notifyEntitlement = new PlistDocument(); if (!string.IsNullOrEmpty(AppGroup)) { pbxProject.AddCapability(guid, PBXCapabilityType.AppGroups); string appGroupPlist = "com.apple.security.application-groups"; var appGroupArray = new PlistElementArray(); appGroupArray.AddString(AppGroup); notifyEntitlement.root[appGroupPlist] = appGroupArray; } else { AviaLogger.AMPLogWarn("app group 为空"); } notifyEntitlement.WriteToFile(absoluteEntitlementFilePath); ModifyEntitlementFile(absoluteEntitlementFilePath); #endregion #region 子模块的info.plist string plistPath = $"{path2BuildProject}/NotificationService/Info.plist"; PlistDocument plist = new PlistDocument(); plist.ReadFromString(File.ReadAllText(plistPath)); PlistElementDict infoDict = plist.root; PlistElementDict bmDict; if (!infoDict.values.ContainsKey("NSAppTransportSecurity")) bmDict = infoDict.CreateDict("NSAppTransportSecurity"); else bmDict = infoDict.values["NSAppTransportSecurity"].AsDict(); bmDict.SetBoolean("NSAllowsArbitraryLoads", true); infoDict.SetString("CFBundleDisplayName", "AviaNotificationServiceExtension"); infoDict.SetString("CFBundleVersion", PlayerSettings.iOS.buildNumber); File.WriteAllText(plistPath, plist.WriteToString()); #endregion } private static void ModifyPodProjectSettings(string path2BuildProject) { string projPath = path2BuildProject + "/Pods/Pods.xcodeproj/project.pbxproj"; PBXProject project = new PBXProject(); project.ReadFromFile(projPath); SubModifyPodProjectSettings(path2BuildProject, project); File.WriteAllText(projPath, project.WriteToString()); } private static string CopyFileToPath(string path2BuildProject, string fileAbsolutePath, string fileReactivePath, PBXProject project = null, bool addToProject = true) { string newPath = Path.Combine(path2BuildProject, fileReactivePath); if (File.Exists(fileAbsolutePath)) { if (File.Exists(newPath)) { File.Delete(newPath); } File.Copy(fileAbsolutePath, newPath); if (addToProject) { return project?.AddFile(newPath, fileReactivePath, PBXSourceTree.Source); } } else { Debug.LogWarning("文件不存在:" + fileAbsolutePath); } return ""; } private static string CopyDirectoryToPath(string path2BuildProject, string fileAbsolutePath, string fileReactivePath, PBXProject project = null, bool addToProject = true) { string newPath = Path.Combine(path2BuildProject, fileReactivePath); if (Directory.Exists(fileAbsolutePath)) { if (Directory.Exists(newPath)) { Directory.Delete(newPath); } FileUtil.CopyFileOrDirectory(fileAbsolutePath, newPath); if (addToProject) { return project?.AddFile(newPath, fileReactivePath, PBXSourceTree.Source); } } else { Debug.LogWarning("文件夹不存在:" + fileAbsolutePath); } return ""; } private static void SubModifyPodProjectSettings(string path2BuildProject, PBXProject project) { string brainTreeDropInGuid = project.TargetGuidByName("BraintreeDropIn-BraintreeDropIn-Localization"); project.SetTeamId(brainTreeDropInGuid, TeamId); string checkoutFrameGuid = project.TargetGuidByName("Frames-Frames"); if (!string.IsNullOrEmpty(checkoutFrameGuid)) { project.SetTeamId(checkoutFrameGuid, TeamId); } string adyenFrameGuid = project.TargetGuidByName("Adyen-Adyen"); if (!string.IsNullOrEmpty(adyenFrameGuid)) { project.SetTeamId(adyenFrameGuid, TeamId); string adyenActionFrameGuid = project.TargetGuidByName("Adyen-AdyenActions"); project.SetTeamId(adyenActionFrameGuid, TeamId); string adyenCardFrameGuid = project.TargetGuidByName("Adyen-AdyenCard"); project.SetTeamId(adyenCardFrameGuid, TeamId); } string awsCoreGuid = project.TargetGuidByName("AWSCore"); if (!string.IsNullOrEmpty(awsCoreGuid)) project.SetBuildProperty(awsCoreGuid, "IPHONEOS_DEPLOYMENT_TARGET", "12.0"); string awsS3Guid = project.TargetGuidByName("AWSS3"); if (!string.IsNullOrEmpty(awsS3Guid)) project.SetBuildProperty(awsS3Guid, "IPHONEOS_DEPLOYMENT_TARGET", "12.0"); string masonryGuid = project.TargetGuidByName("Masonry"); if (!string.IsNullOrEmpty(masonryGuid)) project.SetBuildProperty(masonryGuid, "IPHONEOS_DEPLOYMENT_TARGET", "12.0"); string openUdidGuid = project.TargetGuidByName("OpenUDID"); if (!string.IsNullOrEmpty(openUdidGuid)) project.SetBuildProperty(openUdidGuid, "IPHONEOS_DEPLOYMENT_TARGET", "12.0"); string reachabilityGuid = project.TargetGuidByName("Reachability"); if (!string.IsNullOrEmpty(reachabilityGuid)) project.SetBuildProperty(reachabilityGuid, "IPHONEOS_DEPLOYMENT_TARGET", "12.0"); string uicKeyChainStoreGuid = project.TargetGuidByName("UICKeyChainStore"); if (!string.IsNullOrEmpty(uicKeyChainStoreGuid)) project.SetBuildProperty(uicKeyChainStoreGuid, "IPHONEOS_DEPLOYMENT_TARGET", "12.0"); } private static void SubModifyProjectSettings(string path2BuildProject, PBXProject project) { string unityFrameworkTargetGuid = project.GetUnityFrameworkTargetGuid(); string unityMainTargetGuid = project.GetUnityMainTargetGuid(); string unityProjectGuid = project.ProjectGuid(); string iosFolderPath = Path.Combine(Application.dataPath, $"../Archive/iOS/Test"); if (!Directory.Exists(iosFolderPath)) { Debug.LogError($"haven't found folder:{iosFolderPath}"); return; } //var info = new DirectoryInfo(iosFolderPath); //iosFolderPath = info.FullName; // 拷贝Podfile文件到工程 //CopyToPath(path2BuildProject, $"{iosFolderPath}/Podfile", "Podfile"); // 签名信息(可以没有,打包机上ExportOptions里有配置,但不用打包机的情况下,还是可以加一下的)ps:加了也没关系,还是打包机优先级最高 if (!string.IsNullOrEmpty(TeamId)) project.SetTeamId(unityMainTargetGuid, TeamId); else AviaLogger.AMPLogWarn("team id 为空"); CopyFileToPath(path2BuildProject, GoogleInfoPlistPath, "GoogleService-Info.plist", project, true); #region 添加framework project.AddFrameworkToProject(unityFrameworkTargetGuid, "AdSupport.framework", false); project.AddFrameworkToProject(unityFrameworkTargetGuid, "AppTrackingTransparency.framework", false); project.AddFrameworkToProject(unityFrameworkTargetGuid, "Photos.framework", false); project.AddFrameworkToProject(unityFrameworkTargetGuid, "UserNotifications.framework", false); // 添加非系统框架,及文件 string payFrameworkGuid = CopyDirectoryToPath(path2BuildProject, $"{TrustFrameworkPath}/PayWithMyBank.xcframework", "Frameworks/PayWithMyBank.xcframework", project, true); // if (!string.IsNullOrEmpty(payFrameworkGuid)) // { // project.AddFileToEmbedFrameworks(unityIPhoneGUID, payFrameworkGuid); // project.AddFileToEmbedFrameworks(unityFrameworkGUID, payFrameworkGuid); // } // else // { // Debug.LogWarning("trust frame work 不存在"); // } #endregion #region 调整BuildSettings project.SetBuildProperty(unityMainTargetGuid, "CODE_SIGN_IDENTITY", "Apple Development"); project.SetBuildProperty(unityMainTargetGuid, "IPHONEOS_DEPLOYMENT_TARGET", "12.0"); project.SetBuildProperty(unityFrameworkTargetGuid, "IPHONEOS_DEPLOYMENT_TARGET", "12.0"); project.SetBuildProperty(unityProjectGuid, "IPHONEOS_DEPLOYMENT_TARGET", "12.0"); project.SetBuildProperty(unityMainTargetGuid, "GCC_C_LANGUAGE_STANDARD", "c99"); project.SetBuildProperty(unityFrameworkTargetGuid, "GCC_C_LANGUAGE_STANDARD", "c99"); project.SetBuildProperty(unityProjectGuid, "GCC_C_LANGUAGE_STANDARD", "c99"); project.SetBuildProperty(unityFrameworkTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "NO"); Debug.Log("----------build ipa--------------"+IsUploadIpa); if (IsUploadIpa) { project.SetBuildProperty(unityProjectGuid, "CODE_SIGN_STYLE", "Manual"); project.SetBuildProperty(unityMainTargetGuid, "CODE_SIGN_STYLE", "Manual"); project.SetBuildProperty(unityMainTargetGuid, "CODE_SIGN_IDENTITY[sdk=iphoneos*]", "钥匙串中证书的名字"); project.SetBuildProperty(unityMainTargetGuid, "CODE_SIGN_IDENTITY", "钥匙串中证书的名字"); project.SetBuildProperty(unityMainTargetGuid, "PROVISIONING_PROFILE_SPECIFIER", "provision 文件的名字,不带后缀"); } else { project.SetBuildProperty(unityMainTargetGuid, "CODE_SIGN_STYLE", "Automatic"); } var token = project.GetBuildPropertyForAnyConfig(unityProjectGuid, "USYM_UPLOAD_AUTH_TOKEN"); if (string.IsNullOrEmpty(token)) { token = "FakeToken"; } project.SetBuildProperty(unityMainTargetGuid, "USYM_UPLOAD_AUTH_TOKEN", token); project.SetBuildProperty(unityProjectGuid, "USYM_UPLOAD_AUTH_TOKEN", token); project.SetBuildProperty(unityFrameworkTargetGuid, "USYM_UPLOAD_AUTH_TOKEN", token); project.SetBuildProperty(unityMainTargetGuid, "GCC_ENABLE_OBJC_EXCEPTIONS", "YES"); project.SetBuildProperty(unityFrameworkTargetGuid, "GCC_ENABLE_OBJC_EXCEPTIONS", "YES"); project.SetBuildProperty(unityMainTargetGuid, "ENABLE_BITCODE", "FALSE"); project.SetBuildProperty(unityFrameworkTargetGuid, "ENABLE_BITCODE", "FALSE"); project.AddBuildProperty(unityProjectGuid, "OTHER_LDFLAGS", "-ObjC -ld_classic"); project.SetBuildProperty(unityMainTargetGuid, "CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES", "YES"); project.SetBuildProperty(unityFrameworkTargetGuid, "CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES", "YES"); // entitlements : Apple Pay Keychain ... CopyFileToPath(path2BuildProject, $"{iosFolderPath}/Unity-iPhoneDebug.entitlements", "Unity-iPhoneDebug.entitlements", project, true); project.AddBuildPropertyForConfig(project.BuildConfigByName(unityMainTargetGuid, "Debug"), "CODE_SIGN_ENTITLEMENTS", "Unity-iPhoneDebug.entitlements"); CopyFileToPath(path2BuildProject, $"{iosFolderPath}/Unity-iPhoneRelease.entitlements", "Unity-iPhoneRelease.entitlements", project, true); project.AddBuildPropertyForConfig(project.BuildConfigByName(unityMainTargetGuid, "Release"), "CODE_SIGN_ENTITLEMENTS", "Unity-iPhoneRelease.entitlements"); project.AddBuildPropertyForConfig(project.BuildConfigByName(unityMainTargetGuid, "ReleaseForProfiling"), "CODE_SIGN_ENTITLEMENTS", "Unity-iPhoneRelease.entitlements"); project.AddBuildPropertyForConfig(project.BuildConfigByName(unityMainTargetGuid, "ReleaseForRunning"), "CODE_SIGN_ENTITLEMENTS", "Unity-iPhoneRelease.entitlements"); #endregion // Info.plist AddCapability(project, path2BuildProject); } private static void AddCapability(PBXProject project, string pathToBuiltProject) { string target = project.GetUnityMainTargetGuid(); // Need Create entitlements #region 修改info.plist string plistPath = pathToBuiltProject + "/Info.plist"; PlistDocument plist = new PlistDocument(); plist.ReadFromString(File.ReadAllText(plistPath)); PlistElementDict infoDict = plist.root; project.AddCapability(target, PBXCapabilityType.BackgroundModes); PlistElementArray bmArray; if (!infoDict.values.ContainsKey("UIBackgroundModes")) bmArray = infoDict.CreateArray("UIBackgroundModes"); else bmArray = infoDict.values["UIBackgroundModes"].AsArray(); bmArray.values.Clear(); bmArray.AddString("remote-notification"); PlistElementDict bmDict; if (!infoDict.values.ContainsKey("NSAppTransportSecurity")) bmDict = infoDict.CreateDict("NSAppTransportSecurity"); else bmDict = infoDict.values["NSAppTransportSecurity"].AsDict(); if (bmDict.values.ContainsKey("NSAllowsArbitraryLoadsInWebContent")) bmDict.values.Remove("NSAllowsArbitraryLoadsInWebContent"); if (AviaLogger.IsOpenLog) { if (infoDict.values.ContainsKey("UIFileSharingEnabled")) infoDict.values.Remove("UIFileSharingEnabled"); infoDict.SetBoolean("UIFileSharingEnabled", true); } infoDict.SetString("NSUserTrackingUsageDescription", "广告追踪权限"); infoDict.SetString("NSLocationWhenInUseUsageDescription", "地理位置权限"); infoDict.SetString("NSPhotoLibraryUsageDescription", "相册权限"); infoDict.SetString("NSCameraUsageDescription", "相机权限"); infoDict.SetBoolean("ITSAppUsesNonExemptEncryption", false); infoDict.CreateDict("NSLocationTemporaryUsageDescriptionDictionary") .SetString("GetPreciseLocation", "此应用程序需要临时访问您的位置信息以提供更准确的服务。"); if (!infoDict.values.ContainsKey("LSApplicationQueriesSchemes")) infoDict.CreateArray("LSApplicationQueriesSchemes").AddString("com.venmo.touch.v2"); else infoDict.values["LSApplicationQueriesSchemes"].AsArray().AddString("com.venmo.touch.v2"); if (!infoDict.values.ContainsKey("CFBundleURLTypes")) infoDict.CreateArray("CFBundleURLTypes").AddDict().CreateArray("CFBundleURLSchemes"); else infoDict.values["CFBundleURLTypes"].AsArray().AddDict().CreateArray("CFBundleURLSchemes"); PlistElementArray urlSchemes = infoDict["CFBundleURLTypes"].AsArray().values[1].AsDict()["CFBundleURLSchemes"].AsArray(); urlSchemes.AddString("${PRODUCT_BUNDLE_IDENTIFIER}.payments"); urlSchemes.AddString("${PRODUCT_BUNDLE_IDENTIFIER}.trustly"); urlSchemes.AddString("${PRODUCT_BUNDLE_IDENTIFIER}.adyenCashApp"); urlSchemes.AddString("${PRODUCT_BUNDLE_IDENTIFIER}.afLink"); infoDict.SetString("AppIdentifierPrefix", "$(AppIdentifierPrefix)"); //infoDict.SetString("CFBundleIdentifier", _bundleId); File.WriteAllText(plistPath, plist.WriteToString()); #endregion #region 修改entitlement string releaseEntitlementFilePath = pathToBuiltProject + "/Unity-iPhoneRelease.entitlements"; string absoluteEntitlementFilePath = pathToBuiltProject + "/Unity-iPhoneDebug.entitlements"; PlistDocument tempEntitlements = new PlistDocument(); string keychainAccessGroups = "keychain-access-groups"; var arr = new PlistElementArray(); arr.values.Add(new PlistElementString($"$(AppIdentifierPrefix){Application.identifier}")); arr.values.Add(new PlistElementString("$(AppIdentifierPrefix)AviagamesUniqueDevice")); tempEntitlements.root[keychainAccessGroups] = arr; project.AddCapability(target, PBXCapabilityType.InAppPurchase); if (!string.IsNullOrEmpty(BtApplePayMerchantId)) { string applePayments = "com.apple.developer.in-app-payments"; var payArr = (tempEntitlements.root[applePayments] = new PlistElementArray()) as PlistElementArray; payArr?.values.Add(new PlistElementString(BtApplePayMerchantId)); payArr?.values.Add(new PlistElementString(UtApplePayMerchantId)); } else { AviaLogger.AMPLogWarn("merchant Id 为空"); } string keyPushNotifications = "aps-environment"; tempEntitlements.root[keyPushNotifications] = new PlistElementString("development"); project.AddCapability(target, PBXCapabilityType.PushNotifications); project.AddCapability(target, PBXCapabilityType.KeychainSharing); project.AddCapability(target, PBXCapabilityType.AccessWiFiInformation); tempEntitlements.root["com.apple.developer.networking.wifi-info"] = new PlistElementBoolean(true); if (!string.IsNullOrEmpty(DeepLinkDomain)) { project.AddCapability(target, PBXCapabilityType.AssociatedDomains); var associateDomains = new PlistElementArray(); associateDomains.AddString(DeepLinkDomain); tempEntitlements.root["com.apple.developer.associated-domains"] = associateDomains; } else { AviaLogger.AMPLogWarn("deep link 为空"); } if (!string.IsNullOrEmpty(AppGroup)) { project.AddCapability(target, PBXCapabilityType.AppGroups); string appGroupKey = "com.apple.security.application-groups"; var appGroupArr = new PlistElementArray(); appGroupArr.values.Add(new PlistElementString(AppGroup)); tempEntitlements.root[appGroupKey] = appGroupArr; } else { AviaLogger.AMPLogWarn("app group 为空"); } tempEntitlements.WriteToFile(absoluteEntitlementFilePath); tempEntitlements.root[keyPushNotifications] = new PlistElementString("production"); tempEntitlements.WriteToFile(releaseEntitlementFilePath); ModifyEntitlementFile(absoluteEntitlementFilePath); #endregion } private static void ModifyEntitlementFile(string absoluteEntitlementFilePath) { if (!File.Exists(absoluteEntitlementFilePath)) return; try { StreamReader reader = new StreamReader(absoluteEntitlementFilePath); var content = reader.ReadToEnd().Trim(); reader.Close(); var needFindString = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"; var changeString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"; Debug.Log("entitlement更改之前: " + content); content = content.Replace(needFindString, changeString); Debug.Log("entitlement更改之后: " + content); StreamWriter writer = new StreamWriter(new FileStream(absoluteEntitlementFilePath, FileMode.Create)); writer.WriteLine(content); writer.Flush(); writer.Close(); } catch (Exception e) { Debug.Log("ModifyEntitlementFile - Failed: " + e.Message); } } #region 修改build phase private static void Process(string path) { string projectPath = PBXProject.GetPBXProjectPath(path); PBXProject project = new PBXProject(); project.ReadFromFile(projectPath); string mainTargetGuild = project.GetUnityMainTargetGuid(); //获取所有的build phase guid string[] buildPhases = project.GetAllBuildPhasesForTarget(mainTargetGuild); //根据build phase name获取 guid string crashlyticsShellScriptPhaseGuid = GetBuildPhaseGuid(project, buildPhases, CrashlyticsShellScriptPhaseName); if (string.IsNullOrEmpty(crashlyticsShellScriptPhaseGuid)) { DebugLog($"\"{CrashlyticsShellScriptPhaseName}\" phase guid not found."); //return; } string embedAppExtensionsPhaseGuid = GetBuildPhaseGuid(project, buildPhases, EmbedAppExtensionsPhaseName); if (string.IsNullOrEmpty(embedAppExtensionsPhaseGuid)) { DebugLog($"\"{EmbedAppExtensionsPhaseName}\" phase guid not found."); //return; } Type projectType = project.GetType(); //获取nativeTargets属性 PropertyInfo nativeTargetsProperty = projectType.GetProperty("nativeTargets", BindingFlags.NonPublic | BindingFlags.Instance); //获取nativeTargets属性值,是个数组 object nativeTargets = nativeTargetsProperty?.GetValue(project); if (nativeTargets == null) { DebugLog($"属性'nativeTargets'没找到"); return; } DebugLog("nativeTargets 值为:" + nativeTargets); //获取nativeTargets的类 Type nativeTargetsType = nativeTargets.GetType(); DebugLog("nativeTargets 类名为:" + nativeTargetsType.FullName); //获取nativeTargets的类的索引器方法 MethodInfo indexerMethod = nativeTargetsType.GetMethod("get_Item"); if (indexerMethod == null) { DebugLog($"Method 'get_Item' of {nativeTargetsType.FullName} type not found."); return; } //调用nativeTargets的类的索引器方法,参数为target guid,返回为PBXNativeTargetData,也就是target的数据,比如Unity-iPhone、UnityFramework object pbxNativeTargetData = indexerMethod.Invoke(nativeTargets, new object[] { mainTargetGuild }); //获取main target(PBXNativeTargetData)的phase 字段,它是GUIDList类型,也就是所有phases的所有GUID FieldInfo phasesField = pbxNativeTargetData.GetType().GetField("phases"); object phases = phasesField?.GetValue(pbxNativeTargetData); //获取GUIDList的m_List private字段 FieldInfo listField = phases?.GetType().GetField("m_List", BindingFlags.NonPublic | BindingFlags.Instance); if (!(listField?.GetValue(phases) is List<string> guidList)) { DebugLog($"Field 'm_List' not found."); return; } //------下面开始调整顺序,前面只是做校验多一点------- //build phase在xcode中的顺序,就是它在GUIDList中的顺序 guidList.Remove(crashlyticsShellScriptPhaseGuid); guidList.Insert(guidList.IndexOf(embedAppExtensionsPhaseGuid) + 1, crashlyticsShellScriptPhaseGuid); DebugLog( $"Insert {CrashlyticsShellScriptPhaseName} phase {crashlyticsShellScriptPhaseGuid} after {EmbedAppExtensionsPhaseName} phase {embedAppExtensionsPhaseGuid}"); project.WriteToFile(projectPath); void DebugLog(string message) => Debug.Log($"[更改build phase 顺序] {message}"); } /// <summary> /// 根据 /// </summary> /// <param name="project"></param> /// <param name="buildPhases"></param> /// <param name="buildPhaseName"></param> /// <returns></returns> static string GetBuildPhaseGuid(PBXProject project, string[] buildPhases, string buildPhaseName) { foreach (string buildPhaseGuid in buildPhases) { if (project.GetBuildPhaseName(buildPhaseGuid) == buildPhaseName) { return buildPhaseGuid; } } return null; } #endregion } } #endif
打包命令行
def PROJECT_PATH = "${GIT_PRO_PATH}/${UNITY_PROJECT_NAME}" def BUILD_IOS_PATH = "${PROJECT_PATH}/BuildTool/buildios.sh" def XCODE_ARCHIVE_NAME = 'unity_sdk.xcarchive' def XCODE_ARCHIVE_PROJECT_PATH = "${IPA_PATH}/../archive_proj/${XCODE_ARCHIVE_NAME}" def XCODE_BUILD_PROJECT_PATH = "${IPA_PATH}/../xcode_proj" def PACKAGE_SHARE_PATH = "/opt/homebrew/var/www/unity_sdk_output" def UNITY_LOG_FILE = "${PROJECT_PATH}/Logs/unity_sdk_log.txt" def ARCHIVE_LOG_FILE = "${PROJECT_PATH}/Logs/archive.txt" def SHARE_URL = "http://10.240.0.216:8090/unity_sdk_output" def BUNDLE_NUM_FILE="${PROJECT_PATH}/Archive/Setting/BuildVersion.user" def FAIRGUARD = "${PROJECT_PATH}/BuildTool/FairGuard_iOS_3.3.4/fairguardbuild" def user node { //需要安装插件 build var user wrap([$class: 'BuildUser']) { user = env.BUILD_USER_ID } } pipeline { agent any options { //lock(label: 'DevLock', quantity: 1) disableConcurrentBuilds() timeout(time: 45, unit: 'MINUTES') } environment { BUILD_TIMESTAMP = sh(script: 'echo $(date +"%Y_%m_%d-%H_%M_%S")', returnStdout: true).trim() TEST_VALUE = "${Test}" } stages { stage('git拉取代码更新') { steps { sh """ cd ${GIT_PRO_PATH} git clean -fd git stash push ${BUNDLE_NUM_FILE} git reset --hard HEAD git stash pop git checkout ${BRANCH} git clean -fd git pull --force git submodule foreach --recursive 'git reset HEAD . || :' git submodule foreach --recursive 'git checkout -- . || :' git submodule update --init --recursive git submodule foreach --recursive git clean -d -f -f -x """ } } stage('生成共享地址') { steps{ sh """ rm -rf ${PACKAGE_SHARE_PATH}/${BUILD_TIMESTAMP} mkdir ${PACKAGE_SHARE_PATH}/${BUILD_TIMESTAMP} echo ${PACKAGE_SHARE_PATH}/${BUILD_TIMESTAMP} """ } } stage('Unity打包') { when{ expression{return IS_BUILD_IPA.toBoolean()} //expression{return false} } steps { sh """ set -e ${UNITY_2020_3_33} -projectPath $PROJECT_PATH -executeMethod BuildTools.BuildIOS xcodeProjectPath:${XCODE_BUILD_PROJECT_PATH} env:${ENV} isUploadIpa:${IS_UPLOAD_IPA} -quit -batchmode -logFile ${UNITY_LOG_FILE} 2>&1 | tee ${UNITY_LOG_FILE} -buildTarget iOS cp ${UNITY_LOG_FILE} ${PACKAGE_SHARE_PATH}/${BUILD_TIMESTAMP}/unity_sdk_log.txt """ } } stage('Xcode工程 Clean') { when{ expression{return IS_BUILD_IPA.toBoolean()} } steps { sh """ cd ${XCODE_BUILD_PROJECT_PATH} xcodebuild -workspace Unity-iPhone.xcworkspace -scheme "Unity-iPhone" clean """ } } stage('XCODE加固') { when{ expression{return false} } steps { script { if("${jiagu}" == 'true'){ XCODE_BUILD_PATH = "${XCODE_DIR_PATH}/BuildSuccess" sh """ ${FAIRGUARD} -p ${XCODE_DIR_PATH}/Unity-iPhone.xcworkspace -s Unity-iPhone mv ${XCODE_BUILD_PATH}/RTB-* ${XCODE_BUILD_PATH}/rtb.xcarchive """ } } } } stage('Xcode工程 Archive') { when{ expression{return IS_BUILD_IPA.toBoolean()} //expression{return false} } steps { sh """ set -e cd ${XCODE_BUILD_PROJECT_PATH} rm -f ${ARCHIVE_LOG_FILE} rm -rf ${XCODE_ARCHIVE_PROJECT_PATH} xcodebuild archive -workspace Unity-iPhone.xcworkspace -scheme "Unity-iPhone" -configuration "Release" -archivePath "${XCODE_ARCHIVE_PROJECT_PATH}" -destination "generic/platform=iOS" -allowProvisioningUpdates -allowProvisioningDeviceRegistration | tee ${ARCHIVE_LOG_FILE} 2>&1 cp ${ARCHIVE_LOG_FILE} ${PACKAGE_SHARE_PATH}/${BUILD_TIMESTAMP}/Archive.txt cat ${PACKAGE_SHARE_PATH}/${BUILD_TIMESTAMP}/Archive.txt """ } } stage('Xcode工程 Export') { when{ expression{return IS_BUILD_IPA.toBoolean()} //expression{return false} } steps { script{ def exportionPlist if (IS_UPLOAD_IPA.toBoolean()) { exportionPlist = "${PROJECT_PATH}/BuildTool/ExportOptions_Upload.plist" } else { exportionPlist = "${PROJECT_PATH}/BuildTool/ExportOptions_Dev.plist" } sh """ set -e cd ${XCODE_BUILD_PROJECT_PATH} rm -rf "${IPA_PATH}/*" xcodebuild -exportArchive -archivePath "${XCODE_ARCHIVE_PROJECT_PATH}" -exportPath ${IPA_PATH} -exportOptionsPlist ${exportionPlist} -allowProvisioningUpdates -allowProvisioningDeviceRegistration """ } } } stage('IPA 拷贝') { when{ expression{return IS_BUILD_IPA.toBoolean()} //expression{return false} } steps { script{ def ipa = sh(script: 'find ${IPA_PATH} -name "*.ipa" -print -quit', returnStdout: true).trim() sh """ echo "${ipa}" cp ${ipa} "${PACKAGE_SHARE_PATH}/${BUILD_TIMESTAMP}/" """ } } } stage('IPA 上传TestFlight') { when{ //expression{return IS_UPLOAD_IPA.toBoolean()} expression{return false} } steps { script{ def sharePath= "${PACKAGE_SHARE_PATH}/${BUILD_TIMESTAMP}" echo "${sharePath}" def ipa = sh(script: "find '${sharePath}' -name '*.ipa' -print -quit", returnStdout: true).trim() sh """ set -e xcrun altool --validate-app --type ios -f ${ipa} -u zhangruiguo@aviagames.com -p faoy-vcit-xyub-wqmr --verbose | tee "${PACKAGE_SHARE_PATH}/${BUILD_TIMESTAMP}/validate_log.txt" if !xcrun altool --upload-app --type ios -f ${ipa} -u zhangruiguo@aviagames.com -p faoy-vcit-xyub-wqmr --verbose | tee "${PACKAGE_SHARE_PATH}/${BUILD_TIMESTAMP}/upload_log.txt"; then echo "上传TestFlight失败" exit 1 fi """ } } } } post { always { echo "用户信息:${user}" } success{ echo "构建成功" script{ def ipa = sh(script: "find '${PACKAGE_SHARE_PATH}/${BUILD_TIMESTAMP}' -name '*.ipa' -print -quit", returnStdout: true).trim() def ipaUrl = sh(script: "echo '${ipa}' | sed 's|^${PACKAGE_SHARE_PATH}|${SHARE_URL}|'", returnStdout: true).trim() def BUNDLE_NUM=sh(script:"cat ${BUNDLE_NUM_FILE}", returnStdout: true).trim() sh """ curl -X POST -H "Content-Type: application/json" \ -d '{ "msg_type": "post", "content": { "post": { "zh_cn": { "title": "构建成功", "content": [ [{"tag":"a", "text":"包体地址","href":"${ipaUrl}"}], [{"tag":"text", "text":"构建环境:${ENV} 版本号[1.2(${BUNDLE_NUM})]"}], [{"tag":"text", "text":"构建人:${user}"}] ] } } } }' \ https地址 """ } } failure { echo "构建失败" script{ sh """ curl -X POST -H "Content-Type: application/json" \ -d '{ "msg_type": "post", "content": { "post": { "zh_cn": { "title": "构建失败", "content": [ [{"tag":"a", "text":"失败详情","href":"${env.BUILD_URL}/console"}], [{"tag":"text", "text":"构建环境:${ENV} 版本号[1.2(${BUNDLE_NUM})]"}], [{"tag":"text", "text":"构建人:${user}"}] ] } } } }' \ https地址 """ } } } }
xcode
ExportOptions.plist文件的作用,就是在export包时的配置,这里面可以配置成导出后,并上传到TestFlight,就不用再单独上传了
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- 如果不上传,设为export -->
<key>destination</key>
<string>upload</string>
<key>generateAppStoreInformation</key>
<false/>
<key>manageAppVersionAndBuildNumber</key>
<true/>
<key>method</key>
<!-- 如果不上传,设为app-store/ad-hoc/enterprise -->
<string>app-store-connect</string>
<key>provisioningProfiles</key>
<dict>
<key>包的bundle id</key>
<string>provision名字</string>
<key>内嵌包的bundle id</key>
<string>provision名字</string>
</dict>
<key>signingStyle</key>
<string>manual</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string></string>
<key>testFlightInternalTestingOnly</key>
<false/>
<!-- 如果不上传,设为false -->
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
Groovy语法
✅ jenkins的bool类型参数,在groovy里面是字符串,如果要判断,使用BoolParam.toBoolean()✅ when{expression{return BoolParam.toBoolean()}}表示该阶段执不执行
✅def 变量赋值需要双引号"",比如def ttt="${test}"
✅sh""" """,""" """表示可写多行shell,且shell中不能定义变量,需要在script{}里面通过def定义变量,然后在sh里面通过${}引用
✅script里面的脚本,通过双引号包裹命令,单引号引用变量方式
比如 def ipa = sh(script: "find '${sharePath}' -name '*.ipa' -print -quit", returnStdout: true).trim()
✅unity_sdk.xcarchive 是个文件夹,删除用rm -rf
✅git update-index --assume-unchanged <file> 对本地文件忽略跟踪,比如分支上有这个文件,但是忽略本地的跟踪,但是git reset --hard HEAD 之后,还是会更改,所以先stash 再pop