使用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配置
Groovy 复制代码
Jenkins.instance.getItemByFullName(jobName).builds.findAll {
  it.number <= maxNumber
}.each {
  it.delete()
}
  • Jenkins的参数一切皆字符串,bool类型参数也是字符串,通过when{expression{return BoolParam.toBoolean()}}判断
  • Unity 访问命令行参数:
cs 复制代码
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的顺序:
cs 复制代码
#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
打包命令行
Groovy 复制代码
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 复制代码
<?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

相关推荐
_平凡之路_9 分钟前
解决ubuntu22.04 gnome-terminal 无法启动的问题
linux·运维·python
凯子坚持 c12 分钟前
0基础带你入门Linux之使用
linux·运维·服务器
Fan19 分钟前
Elasticsearch 下载安装及使用总结
大数据·elasticsearch·jenkins
EterNity_TiMe_29 分钟前
【Linux基础IO】深入Linux文件描述符与重定向:解锁高效IO操作的秘密
linux·运维·服务器·学习·性能优化·学习方法
python-码博士29 分钟前
Rosetta 一:手把手教你用Linux安装Rosetta(全网最简洁)
linux·运维·服务器
神秘的土鸡1 小时前
Linux中Docker容器构建MariaDB数据库教程
linux·运维·服务器·数据库·docker·mariadb
coisini.cn1 小时前
Windows10、CentOS Stream9 环境下安装kafka_2.12-3.6.2记录
运维·zookeeper·kafka·windows10·centos stream 9
路溪非溪1 小时前
Linux内核启动流程
linux·运维·服务器
anddddoooo2 小时前
vulnhub(11):derpnstink(hydra爆破用户名和密码、验证的文件上传)
linux·运维·服务器·安全·web安全·网络安全
Xinan_____2 小时前
Linux——k8s认识
linux·运维·kubernetes