/* * Copyright Epic Games, Inc. All Rights Reserved. */ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Windows.Forms; using Ionic.Zlib; namespace iPhonePackager { public class Settings { } /** * Operations done at compile time - these may involve the Mac */ public class CompileTime { /** * Location of the Xcode installation on the Mac. For example: * "/Applications/Xcode.app/Contents/Developer" */ private static string XcodeDeveloperDir = ""; private static string iPhone_SigningDevRootMac = ""; /// /// The file name (no path) of the temporary mobile provision that will be placed on the remote mac for use in makeapp /// private static string MacMobileProvisionFilename; private static string MacSigningIdentityFilename; private static string MacName = ""; private static string MacStagingRootDir = ""; private static string MacBinariesDir = ""; private static string MacXcodeStagingDir = ""; /** /MacStagingRootDir/Payload */ public static string RemoteAppPayloadDirectory { get { return MacStagingRootDir + "/Payload"; } } /** /MacStagingRootDir/Payload/GameName.app */ protected static string RemoteAppDirectory { get { return RemoteAppPayloadDirectory + "/" + Program.GameName + (Program.IsClient ? "Client" : "") + Program.Architecture + ".app"; } } /** /MacStagingRootDir/Payload/GameName.app/GameName */ protected static string RemoteExecutablePath { get { return RemoteAppDirectory + "/" + Program.GameName + (Program.IsClient ? "Client" : "") + Program.Architecture; } } private static string CurrentBaseXCodeCommandLine; /** * @return the commandline used to run Xcode (can add commands like "clean" as needed to the result) */ static private string GetBaseXcodeCommandline() { string CmdLine = XcodeDeveloperDir + "usr/bin/xcodebuild" + " -project UE4_FromPC.xcodeproj" + " -configuration \"" + Program.SchemeConfiguration + "\"" + " -target '" + Program.SchemeName + "'"; if (Config.OSString == "IOS") { CmdLine += " -destination generic/platform=iOS" +" -sdk " + ((Program.Architecture == "-simulator") ? "iphonesimulator" : "iphoneos"); } else { CmdLine += " -destination generic/platform=tvOS" + " -sdk " + ((Program.Architecture == "-simulator") ? "appletvsimulator" : "appletvos"); } // sign with the Distribution identity when packaging for distribution if (Config.bUseRPCUtil) { CmdLine += String.Format(" CODE_SIGN_IDENTITY=\\\"{0}\\\"", Config.CodeSigningIdentity); CmdLine += String.Format(" IPHONEOS_DEPLOYMENT_TARGET=\\\"{0}\\\"", Config.MinOSVersion); } else { CmdLine += String.Format(" CODE_SIGN_IDENTITY=\"{0}\"", Config.CodeSigningIdentity); CmdLine += String.Format(" IPHONEOS_DEPLOYMENT_TARGET=\"{0}\"", Config.MinOSVersion); } return CmdLine; } /** * Handle the plethora of environment variables required to remote to the Mac */ static public void ConfigurePaths() { string MachineName = System.Net.Dns.GetHostName(); XcodeDeveloperDir = Utilities.GetEnvironmentVariable("ue.XcodeDeveloperDir", "/Applications/Xcode.app/Contents/Developer/"); // MacName=%ue4.iPhone_SigningServerName% MacName = Config.OverrideMacName != null ? Config.OverrideMacName : Utilities.GetEnvironmentVariable("ue.IOSSigningServer", "a1487"); iPhone_SigningDevRootMac = Config.OverrideDevRoot != null ? Config.OverrideDevRoot : "/UE4/Builds"; if (!Config.bUseRPCUtil) { bool Results = SSHCommandHelper.Command(MacName, "xcode-select --print-path", "/usr/bin"); if (Results) { XcodeDeveloperDir = (string)SSHCommandHelper.SSHReturn["CommandOutput"] + "/"; XcodeDeveloperDir = XcodeDeveloperDir.TrimEnd(); } } // get the path to mirror into on the Mac string BinariesDir = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, @"..\..")); string Root = Path.GetPathRoot(BinariesDir); string BranchPath = MachineName + "/" + Root[0].ToString() + "/" + BinariesDir.Substring(Root.Length); BranchPath = BranchPath.Replace('\\', '/'); // similar for the game path (strip off the D:\ tpe root) BinariesDir = Path.GetFullPath(Path.Combine(Config.BinariesDirectory, "..")); Root = Path.GetPathRoot(BinariesDir); string GameBranchPath; if (Program.GameName == "UnrealGame") { GameBranchPath = BranchPath; } else { GameBranchPath = MachineName + "/" + Root[0].ToString() + "/" + BinariesDir.Substring(Root.Length); GameBranchPath = GameBranchPath.Replace('\\', '/'); } Console.WriteLine("BranchPath = {0} --- GameBranchPath = {1}", BranchPath, GameBranchPath); // generate the directories to recursively copy into later on MacStagingRootDir = string.Format("{0}/{1}/" + Config.OSString, iPhone_SigningDevRootMac, GameBranchPath); MacStagingRootDir = MacStagingRootDir.Replace("//", "/"); MacBinariesDir = string.Format("{0}/{1}/" + Config.OSString, iPhone_SigningDevRootMac, GameBranchPath); MacBinariesDir = MacBinariesDir.Replace("//", "/"); MacXcodeStagingDir = string.Format("{0}/{1}/" + Config.OSString + "/XcodeSupportFiles", iPhone_SigningDevRootMac, GameBranchPath); MacXcodeStagingDir = MacXcodeStagingDir.Replace("//", "/"); MacMobileProvisionFilename = MachineName + "_UE4Temp.mobileprovision"; MacSigningIdentityFilename = MachineName + "_UE4Temp.p12"; } /// /// Logs out a line of stdout or stderr from RPCUtility.exe to the program log /// /// /// static public void OutputReceivedRemoteProcessCall(Object Sender, DataReceivedEventArgs Line) { if ((Line != null) && (Line.Data != null) && (Line.Data != "")) { Program.Log("[RPC] " + Line.Data); if (Line.Data.Contains("** BUILD FAILED **")) { Program.Error("Xcode build failed!"); } } } static bool FindMobileProvision(string BundleIdentifier, out string OutFileName, bool bCheckCert = true) { bool bNameMatch; string ProvisionWithPrefix = MobileProvision.FindCompatibleProvision(BundleIdentifier, out bNameMatch, bCheckCert, true, false); if (!File.Exists(ProvisionWithPrefix)) { ProvisionWithPrefix = FileOperations.FindPrefixedFile(Config.BuildDirectory, Program.GameName + ".mobileprovision"); if (!File.Exists(ProvisionWithPrefix)) { ProvisionWithPrefix = FileOperations.FindPrefixedFile(Config.BuildDirectory + "/NotForLicensees/", Program.GameName + ".mobileprovision"); if (!File.Exists(ProvisionWithPrefix)) { ProvisionWithPrefix = FileOperations.FindPrefixedFile(Config.EngineBuildDirectory, "UnrealGame.mobileprovision"); if (!File.Exists(ProvisionWithPrefix)) { ProvisionWithPrefix = FileOperations.FindPrefixedFile(Config.EngineBuildDirectory + "/NotForLicensees/", "UnrealGame.mobileprovision"); if(!File.Exists(ProvisionWithPrefix)) { OutFileName = null; return false; } } } } } OutFileName = ProvisionWithPrefix; return true; } /// /// Export the certificate to a file /// static public void ExportCertificate() { if(Config.ProvisionFile == null) { Program.Error("Missing -ProvisionFile=... argument"); return; } if(Config.OutputCertificate == null) { Program.Error("Missing -OutputCertificate=... argument"); return; } // export the signing certificate to a file MobileProvision Provision = MobileProvisionParser.ParseFile(Config.ProvisionFile); X509Certificate2 Certificate = CodeSignatureBuilder.FindCertificate(Provision); if (Certificate == null) { Program.Error("Failed to find a valid certificate"); return; } byte[] Data = Certificate.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, "A"); File.WriteAllBytes(Config.OutputCertificate, Data); } /// /// Copy the files always needed (even in a stub IPA) /// static public void CopyFilesNeededForMakeApp() { // Copy Info.plist over (modifiying it as needed) string SourcePListFilename = Utilities.GetPrecompileSourcePListFilename(); Utilities.PListHelper Info = Utilities.PListHelper.CreateFromFile(SourcePListFilename); // Edit the plist CookTime.UpdateVersion(Info); // Write out the -Info.plist file to the xcode staging directory string TargetPListFilename = Path.Combine(Config.PCXcodeStagingDir, Program.GameName + "-Info.plist"); Directory.CreateDirectory(Path.GetDirectoryName(TargetPListFilename)); string OutString = Info.SaveToString(); OutString = OutString.Replace("${EXECUTABLE_NAME}", Program.GameName); OutString = OutString.Replace("${BUNDLE_IDENTIFIER}", Program.GameName.Replace("_", "")); byte[] RawInfoPList = Encoding.UTF8.GetBytes(OutString); File.WriteAllBytes(TargetPListFilename, RawInfoPList); Program.Log("Updating .plist: {0} --> {1}", SourcePListFilename, TargetPListFilename); string FinalMobileProvisionFilename = null; CurrentBaseXCodeCommandLine = GetBaseXcodeCommandline(); if (!Config.bAutomaticSigning) { // Copy the mobile provision file over string CFBundleIdentifier = null; Info.GetString("CFBundleIdentifier", out CFBundleIdentifier); string ProvisionWithPrefix; if(!FindMobileProvision(CFBundleIdentifier, out ProvisionWithPrefix)) { Program.Error("Unable to find mobileprovision"); return; } FinalMobileProvisionFilename = Path.Combine(Config.PCXcodeStagingDir, MacMobileProvisionFilename); FileOperations.CopyRequiredFile(ProvisionWithPrefix, FinalMobileProvisionFilename); // make sure this .mobileprovision file is newer than any other .mobileprovision file on the Mac (this file gets multiple games named the same file, // so the time stamp checking can fail when moving between games, a la the buildmachines!) File.SetLastWriteTime(FinalMobileProvisionFilename, DateTime.UtcNow); // copy the signing certificate over // export the signing certificate to a file MobileProvision Provision = MobileProvisionParser.ParseFile(ProvisionWithPrefix); var Certificate = CodeSignatureBuilder.FindCertificate(Provision); byte[] Data = Certificate.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, "A"); File.WriteAllBytes(Path.Combine(Config.PCXcodeStagingDir, MacSigningIdentityFilename), Data); Config.CodeSigningIdentity = Certificate.FriendlyName; // since the pipeline will use a temporary keychain that will contain only this certificate, this should be the only identity that will work if (Provision != null) { Config.bForDistribution = !Provision.bDebug; } // regenerate command with new found identity CurrentBaseXCodeCommandLine = GetBaseXcodeCommandline(); // get the UUID string AllText = File.ReadAllText(FinalMobileProvisionFilename); string UUID = ""; int idx = AllText.IndexOf("UUID"); if (idx > 0) { idx = AllText.IndexOf("", idx); if (idx > 0) { idx += "".Length; UUID = AllText.Substring(idx, AllText.IndexOf("", idx) - idx); } } CurrentBaseXCodeCommandLine += String.Format(" PROVISIONING_PROFILE=" + UUID); } else { CurrentBaseXCodeCommandLine += " DEVELOPMENT_TEAM=" + Config.TeamID + " CODE_SIGN_STYLE=\"Automatic\" -allowProvisioningUpdates"; } // look for an entitlements file (optional) string SourceEntitlements = FileOperations.FindPrefixedFile(Config.BuildDirectory, Program.GameName + ".entitlements"); // set where to make the entitlements file ( string TargetEntitlements = Path.Combine(Config.PCXcodeStagingDir, Program.GameName + ".entitlements"); if (File.Exists(SourceEntitlements)) { FileOperations.CopyRequiredFile(SourceEntitlements, TargetEntitlements); } else { // we need to have something so Xcode will compile, so we just set the get-task-allow, since we know the value, // which is based on distribution or not (true means debuggable) File.WriteAllText(TargetEntitlements, string.Format("get-task-allow<{0}/>", Config.bForDistribution ? "false" : "true")); } string ProjectFile = Config.RootRelativePath + @"Engine\Intermediate\ProjectFiles\UE4.xcodeproj\project.pbxproj"; if (Program.GameName != "UnrealGame") { ProjectFile = Path.GetDirectoryName(Config.IntermediateDirectory) + @"\ProjectFiles\" + Program.GameName + @".xcodeproj\project.pbxproj"; } FileOperations.CopyRequiredFile(ProjectFile, Path.Combine(Config.PCXcodeStagingDir, @"project.pbxproj.datecheck")); // needs Mac line endings so it can be executed string SrcPath = @"..\..\..\Build\" + Config.OSString + @"\XcodeSupportFiles\prepackage.sh"; string DestPath = Path.Combine(Config.PCXcodeStagingDir, @"prepackage.sh"); Program.Log(" ... '" + SrcPath + "' -> '" + DestPath + "'"); string SHContents = File.ReadAllText(SrcPath); SHContents = SHContents.Replace("\r\n", "\n"); File.WriteAllText(DestPath, SHContents); CookTime.CopySignedFiles(); } /** * Handle spawning of the RPCUtility with parameters */ public static bool RunRPCUtilty( string RPCCommand, bool bIsSilent = false ) { string CommandLine = ""; string WorkingFolder = ""; string DisplayCommandLine = ""; string TempKeychain = "$HOME/Library/Keychains/UE4TempKeychain.keychain"; string Certificate = "XcodeSupportFiles/" + MacSigningIdentityFilename; string LoginKeychain = "$HOME/Library/Keychains/login.keychain"; ErrorCodes Error = ErrorCodes.Error_Unknown; switch (RPCCommand.ToLowerInvariant()) { case "deletemacstagingfiles": Program.Log( " ... deleting staging files on the Mac" ); DisplayCommandLine = "rm -rf Payload"; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; case "ensureprovisiondirexists": Program.Log(" ... creating provisioning profiles directory"); DisplayCommandLine = String.Format("mkdir -p ~/Library/MobileDevice/Provisioning\\ Profiles"); CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "\""; break; case "installprovision": // Note: The provision must have already been copied over to the Mac Program.Log(" ... installing .mobileprovision"); DisplayCommandLine = String.Format("cp -f {0} ~/Library/MobileDevice/Provisioning\\ Profiles", MacMobileProvisionFilename); CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "\""; break; case "removeprovision": Program.Log(" ... removing .mobileprovision"); DisplayCommandLine = String.Format("rm -f ~/Library/MobileDevice/Provisioning\\ Profiles/{0}", MacMobileProvisionFilename); CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "\""; break; case "setexec": // Note: The executable must have already been copied over Program.Log(" ... setting executable bit"); DisplayCommandLine = "chmod a+x \'" + RemoteExecutablePath + "\'"; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; case "prepackage": Program.Log(" ... running prepackage script remotely "); DisplayCommandLine = String.Format("sh prepackage.sh {0} " + Config.OSString + " {1} {2}", Program.GameName, Program.GameConfiguration, Program.Architecture); CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "\""; break; case "makeapp": Program.Log(" ... making application (codesign, etc...)"); Program.Log(" Using signing identity '{0}'", Config.CodeSigningIdentity); DisplayCommandLine = "security -v unlock-keychain -p \"A\" \"" + TempKeychain + "\" && " + CurrentBaseXCodeCommandLine; CommandLine = "\"" + MacXcodeStagingDir + "/..\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "/..\""; Error = ErrorCodes.Error_RemoteCertificatesNotFound; break; case "createkeychain": Program.Log(" ... creating temporary key chain with signing certificate"); Program.Log(" Using signing identity '{0}'", Config.CodeSigningIdentity); if (Config.bAutomaticSigning) { DisplayCommandLine = "security dump-keychain -i login.keychain && security create-keychain -p \"A\" \"" + TempKeychain + "\" && security list-keychains -s \"" + TempKeychain + "\" && security list-keychains && security set-keychain-settings -t 3600 -l \"" + TempKeychain + "\" && security -v unlock-keychain -p \"A\" \"" + TempKeychain + "\" && security default-keychain -s \"" + TempKeychain + "\" && security import login.keychain -P \"A\" -T /usr/bin/codesign";//&& security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k \"A\" -t private " + TempKeychain; } else { DisplayCommandLine = "security create-keychain -p \"A\" \"" + TempKeychain + "\" && security list-keychains -s \"" + TempKeychain + "\" && security list-keychains && security set-keychain-settings -t 3600 -l \"" + TempKeychain + "\" && security -v unlock-keychain -p \"A\" \"" + TempKeychain + "\" && security import " + Certificate + " -k \"" + TempKeychain + "\" -P \"A\" -T /usr/bin/codesign -T /usr/bin/security -t agg && CERT_IDENTITY=$(security find-identity -v -p codesigning \"" + TempKeychain + "\" | head -1 | grep '\"' | sed -e 's/[^\"]*\"//' -e 's/\".*//') && security default-keychain -s \"" + TempKeychain + "\" && security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k \"A\" -D \"$CERT_IDENTITY\" -t private " + TempKeychain; } CommandLine = "\"" + MacXcodeStagingDir + "/..\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "/..\""; break; case "deletekeychain": Program.Log(" ... remove temporary key chain"); Program.Log(" Using signing identity '{0}'", Config.CodeSigningIdentity); DisplayCommandLine = "security list-keychains -s \"" + LoginKeychain + "\" && security delete-keychain \"" + TempKeychain + "\""; CommandLine = "\"" + MacXcodeStagingDir + "/..\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "/..\""; break; case "validation": Program.Log( " ... validating distribution package" ); DisplayCommandLine = XcodeDeveloperDir + "Platforms/iPhoneOS.platform/Developer/usr/bin/Validation " + RemoteAppDirectory; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; case "deleteipa": Program.Log(" ... deleting IPA on Mac"); DisplayCommandLine = "rm -f " + Config.IPAFilenameOnMac; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; case "kill": Program.Log( " ... killing" ); DisplayCommandLine = "killall " + Program.GameName; CommandLine = ". " + DisplayCommandLine; WorkingFolder = "."; break; case "strip": Program.Log( " ... stripping" ); DisplayCommandLine = "/usr/bin/xcrun strip '" + RemoteExecutablePath + "'"; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; case "resign": Program.Log("... resigning"); DisplayCommandLine = "bash -c '" + "chmod a+x ResignScript" + ";" + "./ResignScript" + "'"; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; case "zip": Program.Log( " ... zipping" ); // NOTE: -y preserves symbolic links which is needed for iOS distro builds // -x excludes a file (excluding the dSYM keeps sizes smaller, and it shouldn't be in the IPA anyways) string dSYMName = "Payload/" + Program.GameName + (Program.IsClient ? "Client" : "") + Program.Architecture + ".app.dSYM"; DisplayCommandLine = String.Format("zip -q -r -y -{0} -T {1} Payload iTunesArtwork -x {2}/ -x {2}/* " + "-x {2}/Contents/ -x {2}/Contents/* -x {2}/Contents/Resources/ -x {2}/Contents/Resources/* " + " -x {2}/Contents/Resources/DWARF/ -x {2}/Contents/Resources/DWARF/*", (int)Config.RecompressionSetting, Config.IPAFilenameOnMac, dSYMName); CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; case "gendsym": Program.Log( " ... generating DSYM" ); string ExePath = "Payload/" + Program.GameName + (Program.IsClient ? "Client" : "") + ".app/" + Program.GameName + (Program.IsClient ? "Client" : ""); string dSYMPath = Program.GameName + (Program.IsClient ? "Client" : "") + ".app.dSYM"; DisplayCommandLine = String.Format("dsymutil -o {0} {1}", dSYMPath, ExePath); CommandLine = "\"" + MacStagingRootDir + "\"" + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; default: Program.Error( "Unrecognized RPC command" ); return ( false ); } Program.Log( " ... working folder: " + WorkingFolder ); Program.Log( " ... " + DisplayCommandLine ); Program.Log(" ... full command: " + MacName + " " + CommandLine); bool bSuccess = false; if( Config.bUseRPCUtil ) { Program.Log( "Running RPC on " + MacName + " ... " ); Process RPCUtil = new Process(); RPCUtil.StartInfo.FileName = @"..\RPCUtility.exe"; RPCUtil.StartInfo.UseShellExecute = false; RPCUtil.StartInfo.Arguments = MacName + " " + CommandLine; RPCUtil.StartInfo.RedirectStandardOutput = true; RPCUtil.StartInfo.RedirectStandardError = true; RPCUtil.OutputDataReceived += new DataReceivedEventHandler(OutputReceivedRemoteProcessCall); RPCUtil.ErrorDataReceived += new DataReceivedEventHandler(OutputReceivedRemoteProcessCall); RPCUtil.Start(); RPCUtil.BeginOutputReadLine(); RPCUtil.BeginErrorReadLine(); RPCUtil.WaitForExit(); bSuccess = (RPCUtil.ExitCode == 0); if (bSuccess == false && !bIsSilent) { Program.Error("RPCCommand {0} failed with return code {1}", RPCCommand, RPCUtil.ExitCode); switch (RPCCommand.ToLowerInvariant()) { case "installprovision": Program.Error("Ensure your access permissions for '~/Library/MobileDevice/Provisioning Profiles' are set correctly."); break; default: break; } } } else { Program.Log("Running SSH on " + MacName + " ... "); bSuccess = SSHCommandHelper.Command(MacName, DisplayCommandLine, WorkingFolder); if (bSuccess == false && !bIsSilent) { Program.Error("RPCCommand {0} failed with return code {1}", RPCCommand, Error); Program.ReturnCode = (int)Error; } } return bSuccess; } /** * Creates the application directory on the Mac */ static public void CreateApplicationDirOnMac() { DateTime StartTime = DateTime.Now; // Cleans out the intermediate folders on both ends CompileTime.ExecuteRemoteCommand("DeleteIPA"); CompileTime.ExecuteRemoteCommand("DeleteMacStagingFiles"); Program.ExecuteCommand("Clean", null); //@TODO: mnoland 10/5/2010 // Need to come up with a way to prevent this from causing an error on the remote machine // CompileTime.ExecuteRemoteCommand("Clean"); // Stage files Program.Log("Staging files before copying to Mac ..."); CopyFilesNeededForMakeApp(); // Copy staged files from PC to Mac Program.ExecuteCommand("StageMacFiles", null); // Set the executable bit on the EXE CompileTime.ExecuteRemoteCommand("SetExec"); // Install the provision (necessary for MakeApp to succeed) CompileTime.ExecuteRemoteCommand("EnsureProvisionDirExists"); CompileTime.ExecuteRemoteCommand("InstallProvision"); // strip the symbols if desired or required if (Config.bForceStripSymbols || Config.bForDistribution) { CompileTime.ExecuteRemoteCommand("Strip"); } // sign the exe, etc... CompileTime.ExecuteRemoteCommand("PrePackage"); if (!Config.bUseRPCUtil) { CompileTime.ExecuteRemoteCommand("DeleteKeyChain", true); CompileTime.ExecuteRemoteCommand("CreateKeyChain"); } CompileTime.ExecuteRemoteCommand("MakeApp"); if (!Config.bUseRPCUtil) { CompileTime.ExecuteRemoteCommand("DeleteKeyChain"); } Program.Log(String.Format("Finished creating .app directory on Mac (took {0:0.00} s)", (DateTime.Now - StartTime).TotalSeconds)); } /** * Packages an IPA on the Mac */ static public void PackageIPAOnMac() { // Create the .app structure on the Mac (and codesign, etc...) CreateApplicationDirOnMac(); DateTime StartTime = DateTime.Now; // zip up CompileTime.ExecuteRemoteCommand("Zip"); // fetch the IPA if (Config.bCreateStubSet) { Program.ExecuteCommand("GetStubIPA", null); } else { Program.ExecuteCommand("GetIPA", null); } Program.Log(String.Format("Finished packaging into IPA (took {0:0.00} s)", (DateTime.Now - StartTime).TotalSeconds)); } static public void DangerouslyFastMode() { CompileTime.ExecuteRemoteCommand("MakeApp"); } static public void ExecuteRemoteCommand(string RemoteCommand, bool bIsSilent = false) { RunRPCUtilty(RemoteCommand, bIsSilent); } static public bool ExecuteCompileCommand(string Command, string RPCCommand) { switch (Command.ToLowerInvariant()) { case "clean": Program.Log("Cleaning temporary files from PC ... "); Program.Log(" ... cleaning: " + Config.PCStagingRootDir); FileOperations.DeleteDirectory(new DirectoryInfo(Config.PCStagingRootDir)); break; case "rpc": ExecuteRemoteCommand(RPCCommand); break; case "getipa": { Program.Log("Fetching IPA from Mac..."); string IpaDestFilename = Config.GetIPAPath(".ipa"); FileOperations.DownloadFile(MacName, MacStagingRootDir + "/" + Config.IPAFilenameOnMac, IpaDestFilename); Program.Log("... Saved IPA to '{0}'", Path.GetFullPath(IpaDestFilename)); } break; case "getstubipa": { Program.Log("Fetching stub IPA from Mac..."); string IpaDestFilename = Config.GetIPAPath(".stub"); FileOperations.DownloadFile(MacName, MacStagingRootDir + "/" + Config.IPAFilenameOnMac, IpaDestFilename); Program.Log("... Saved stub IPA to '{0}'", Path.GetFullPath(IpaDestFilename)); } break; case "stagemacfiles": Program.Log("Copying all staged files to Mac " + MacName + " ..."); FileOperations.BatchUploadFolder(MacName, Config.PCStagingRootDir, MacStagingRootDir, false); FileOperations.BatchUploadFolder(MacName, Config.PCXcodeStagingDir, MacXcodeStagingDir, false); break; case "exportcertificate": CompileTime.ExportCertificate(); break; default: return false; } return true; } } }