718 lines
27 KiB
C#
718 lines
27 KiB
C#
/*
|
|
* 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 = "";
|
|
|
|
/// <summary>
|
|
/// The file name (no path) of the temporary mobile provision that will be placed on the remote mac for use in makeapp
|
|
/// </summary>
|
|
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";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logs out a line of stdout or stderr from RPCUtility.exe to the program log
|
|
/// </summary>
|
|
/// <param name="Sender"></param>
|
|
/// <param name="Line"></param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Export the certificate to a file
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy the files always needed (even in a stub IPA)
|
|
/// </summary>
|
|
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 <GameName>-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("<key>UUID</key>");
|
|
if (idx > 0)
|
|
{
|
|
idx = AllText.IndexOf("<string>", idx);
|
|
if (idx > 0)
|
|
{
|
|
idx += "<string>".Length;
|
|
UUID = AllText.Substring(idx, AllText.IndexOf("</string>", 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("<plist><dict><key>get-task-allow</key><{0}/></dict></plist>",
|
|
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;
|
|
}
|
|
}
|
|
}
|