public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL)
	{
		Log("Package {0}", Params.RawProjectPath);

		// ensure the ue4game binary exists, if applicable
		string FullExePath = CombinePaths(Path.GetDirectoryName(Params.ProjectGameExeFilename), SC.StageExecutables[0] + (UnrealBuildTool.BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac ? ".stub" : ""));
		if (!SC.IsCodeBasedProject && !FileExists_NoExceptions(FullExePath))
		{
			LogError("Failed to find game binary " + FullExePath);
			throw new AutomationException(ExitCode.Error_MissingExecutable, "Stage Failed. Could not find binary {0}. You may need to build the UE4 project with your target configuration and platform.", FullExePath);
		}

        if (SC.StageTargetConfigurations.Count != 1)
        {
            throw new AutomationException("iOS is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations", SC.StageTargetConfigurations.Count);
        }

        var TargetConfiguration = SC.StageTargetConfigurations[0];

        UnrealBuildTool.IOSPlatformContext BuildPlatContext = CreatePlatformContext(Params.RawProjectPath, Params.Distribution);
        BuildPlatContext.SetUpProjectEnvironment(TargetConfiguration);

        //@TODO: We should be able to use this code on both platforms, when the following issues are sorted:
        //   - Raw executable is unsigned & unstripped (need to investigate adding stripping to IPP)
        //   - IPP needs to be able to codesign a raw directory
        //   - IPP needs to be able to take a .app directory instead of a Payload directory when doing RepackageFromStage (which would probably be renamed)
        //   - Some discrepancy in the loading screen pngs that are getting packaged, which needs to be investigated
        //   - Code here probably needs to be updated to write 0 byte files as 1 byte (difference with IPP, was required at one point when using Ionic.Zip to prevent issues on device, maybe not needed anymore?)
        if (UnrealBuildTool.BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac)
		{
            // copy in all of the artwork and plist
            var DeployHandler = GetDeployHandler(Params.RawProjectPath, BuildPlatContext);

            DeployHandler.PrepForUATPackageOrDeploy(Params.RawProjectPath,
				Params.ShortProjectName,
				Path.GetDirectoryName(Params.RawProjectPath.FullName),
				CombinePaths(Path.GetDirectoryName(Params.ProjectGameExeFilename), SC.StageExecutables[0]),
				CombinePaths(SC.LocalRoot, "Engine"),
				Params.Distribution, 
				"",
				false);

			// figure out where to pop in the staged files
			string AppDirectory = string.Format("{0}/Payload/{1}.app",
				Path.GetDirectoryName(Params.ProjectGameExeFilename),
				Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename));

			// delete the old cookeddata
			InternalUtils.SafeDeleteDirectory(AppDirectory + "/cookeddata", true);
			InternalUtils.SafeDeleteFile(AppDirectory + "/ue4commandline.txt", true);

			if (!Params.IterativeDeploy)
			{
				// copy the Staged files to the AppDirectory
				string[] StagedFiles = Directory.GetFiles (SC.StageDirectory, "*", SearchOption.AllDirectories);
				foreach (string Filename in StagedFiles)
				{
					string DestFilename = Filename.Replace (SC.StageDirectory, AppDirectory);
					Directory.CreateDirectory (Path.GetDirectoryName (DestFilename));
					InternalUtils.SafeCopyFile (Filename, DestFilename, true);
				}
			}
			else
			{
				// copy just the root stage directory files
				string[] StagedFiles = Directory.GetFiles (SC.StageDirectory, "*", SearchOption.TopDirectoryOnly);
				foreach (string Filename in StagedFiles)
				{
					string DestFilename = Filename.Replace (SC.StageDirectory, AppDirectory);
					Directory.CreateDirectory (Path.GetDirectoryName (DestFilename));
					InternalUtils.SafeCopyFile (Filename, DestFilename, true);
				}
			}
		}

        bCreatedIPA = false;
		bool bNeedsIPA = false;
		if (Params.IterativeDeploy)
		{
            if (Params.Devices.Count != 1)
            {
                throw new AutomationException("Can only interatively deploy to a single device, but {0} were specified", Params.Devices.Count);
            }
            
            String NonUFSManifestPath = SC.GetNonUFSDeploymentDeltaPath(Params.DeviceNames[0]);
			// check to determine if we need to update the IPA
			if (File.Exists(NonUFSManifestPath))
			{
				string NonUFSFiles = File.ReadAllText(NonUFSManifestPath);
				string[] Lines = NonUFSFiles.Split('\n');
				bNeedsIPA = Lines.Length > 0 && !string.IsNullOrWhiteSpace(Lines[0]);
			}
		}

		if (String.IsNullOrEmpty(Params.Provision))
		{
			Params.Provision = BuildPlatContext.MobileProvision;
		}
		if (String.IsNullOrEmpty(Params.Certificate))
		{
			Params.Certificate = BuildPlatContext.SigningCertificate;
		}

		// Scheme name and configuration for code signing with Xcode project
		string SchemeName = Params.IsCodeBasedProject ? Params.RawProjectPath.GetFileNameWithoutExtension() : "UE4";
		string SchemeConfiguration = TargetConfiguration.ToString();
		if (Params.Client)
		{
			SchemeConfiguration += " Client";
		}

		if (UnrealBuildTool.BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
		{
			var ProjectIPA = MakeIPAFileName(TargetConfiguration, Params);
			var ProjectStub = Path.GetFullPath(Params.ProjectGameExeFilename);

			// package a .ipa from the now staged directory
			var IPPExe = CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/IOS/IPhonePackager.exe");

			LogLog("ProjectName={0}", Params.ShortProjectName);
			LogLog("ProjectStub={0}", ProjectStub);
			LogLog("ProjectIPA={0}", ProjectIPA);
			LogLog("IPPExe={0}", IPPExe);

			bool cookonthefly = Params.CookOnTheFly || Params.SkipCookOnTheFly;

			// if we are incremental check to see if we need to even update the IPA
			if (!Params.IterativeDeploy || !File.Exists(ProjectIPA) || bNeedsIPA)
			{
				// delete the .ipa to make sure it was made
				DeleteFile(ProjectIPA);

				bCreatedIPA = true;

				if (RemoteToolChain.bUseRPCUtil)
				{
					string IPPArguments = "RepackageFromStage \"" + (Params.IsCodeBasedProject ? Params.RawProjectPath.FullName : "Engine") + "\"";
					IPPArguments += " -config " + TargetConfiguration.ToString();
					IPPArguments += " -schemename " + SchemeName + " -schemeconfig \"" + SchemeConfiguration + "\"";

					if (TargetConfiguration == UnrealTargetConfiguration.Shipping)
					{
						IPPArguments += " -compress=best";
					}

					// Determine if we should sign
					bool bNeedToSign = GetCodeSignDesirability(Params);

					if (!String.IsNullOrEmpty(Params.BundleName))
					{
						// Have to sign when a bundle name is specified
						bNeedToSign = true;
						IPPArguments += " -bundlename " + Params.BundleName;
					}

					if (bNeedToSign)
					{
						IPPArguments += " -sign";
						if (Params.Distribution)
						{
							IPPArguments += " -distribution";
						}
					}

					IPPArguments += (cookonthefly ? " -cookonthefly" : "");
					IPPArguments += " -stagedir \"" + CombinePaths(Params.BaseStageDirectory, PlatformName) + "\"";
					IPPArguments += " -project \"" + Params.RawProjectPath + "\"";
					if (Params.IterativeDeploy)
					{
						IPPArguments += " -iterate";
					}
					if (!string.IsNullOrEmpty(Params.Provision))
					{
						IPPArguments += " -provision \"" + Params.Provision + "\""; 
					}
					if (!string.IsNullOrEmpty(Params.Certificate))
					{
						IPPArguments += " -certificate \"" + Params.Certificate + "\"";
					}
					if (PlatformName == "TVOS")
					{
						IPPArguments += " -tvos";
					}
					RunAndLog(CmdEnv, IPPExe, IPPArguments);
				}
				else
				{
					List<string> IPPArguments = new List<string>();
					IPPArguments.Add("RepackageFromStage");
					IPPArguments.Add(Params.IsCodeBasedProject ? Params.RawProjectPath.FullName : "Engine");
					IPPArguments.Add("-config");
					IPPArguments.Add(TargetConfiguration.ToString());
					IPPArguments.Add("-schemename");
					IPPArguments.Add(SchemeName);
					IPPArguments.Add("-schemeconfig");
					IPPArguments.Add("\"" + SchemeConfiguration + "\"");

					if (TargetConfiguration == UnrealTargetConfiguration.Shipping)
					{
						IPPArguments.Add("-compress=best");
					}

					// Determine if we should sign
					bool bNeedToSign = GetCodeSignDesirability(Params);

					if (!String.IsNullOrEmpty(Params.BundleName))
					{
						// Have to sign when a bundle name is specified
						bNeedToSign = true;
						IPPArguments.Add("-bundlename");
						IPPArguments.Add(Params.BundleName);
					}

					if (bNeedToSign)
					{
						IPPArguments.Add("-sign");
					}

					if (cookonthefly)
					{
						IPPArguments.Add(" -cookonthefly");
					}
					IPPArguments.Add(" -stagedir");
					IPPArguments.Add(CombinePaths(Params.BaseStageDirectory, "IOS"));
					IPPArguments.Add(" -project");
					IPPArguments.Add(Params.RawProjectPath.FullName);
					if (Params.IterativeDeploy)
					{
						IPPArguments.Add(" -iterate");
					}
					if (!string.IsNullOrEmpty(Params.Provision))
					{
						IPPArguments.Add(" -provision");
						IPPArguments.Add(Params.Provision);
					}
					if (!string.IsNullOrEmpty(Params.Certificate))
					{
						IPPArguments.Add(" -certificate");
						IPPArguments.Add(Params.Certificate);
					}

					if (RunIPP(IPPArguments.ToArray()) != 0)
					{
						throw new AutomationException("IPP Failed");
					}
				}
			}

			// verify the .ipa exists
			if (!FileExists(ProjectIPA))
			{
				throw new AutomationException(ExitCode.Error_FailedToCreateIPA, "PACKAGE FAILED - {0} was not created", ProjectIPA);
			}

			if (WorkingCL > 0)
			{
				// Open files for add or edit
				var ExtraFilesToCheckin = new List<string>
				{
					ProjectIPA
				};

				// check in the .ipa along with everything else
				UE4Build.AddBuildProductsToChangelist(WorkingCL, ExtraFilesToCheckin);
			}

			//@TODO: This automatically deploys after packaging, useful for testing on PC when iterating on IPP
			//Deploy(Params, SC);
		}
		else
		{
			// create the ipa
			string IPAName = CombinePaths(Path.GetDirectoryName(Params.RawProjectPath.FullName), "Binaries", PlatformName, (Params.Distribution ? "Distro_" : "") + Params.ShortProjectName + (SC.StageTargetConfigurations[0] != UnrealTargetConfiguration.Development ? ("-" + PlatformName + "-" + SC.StageTargetConfigurations[0].ToString()) : "") + ".ipa");

			if (!Params.IterativeDeploy || !File.Exists(IPAName) || bNeedsIPA)
			{
				bCreatedIPA = true;

				// code sign the app
				CodeSign(Path.GetDirectoryName(Params.ProjectGameExeFilename), Params.IsCodeBasedProject ? Params.ShortProjectName : Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename), Params.RawProjectPath, SC.StageTargetConfigurations[0], SC.LocalRoot, Params.ShortProjectName, Path.GetDirectoryName(Params.RawProjectPath.FullName), SC.IsCodeBasedProject, Params.Distribution, Params.Provision, Params.Certificate, SchemeName, SchemeConfiguration);

				// now generate the ipa
				PackageIPA(Path.GetDirectoryName(Params.ProjectGameExeFilename), Params.IsCodeBasedProject ? Params.ShortProjectName : Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename), Params.ShortProjectName, Path.GetDirectoryName(Params.RawProjectPath.FullName), SC.StageTargetConfigurations[0], Params.Distribution);
			}
		}

		PrintRunTime();
	}
	public override void Deploy(ProjectParams Params, DeploymentContext SC)
    {
        if (Params.Devices.Count != 1)
        {
            throw new AutomationException("Can only deploy to a single specified device, but {0} were specified", Params.Devices.Count);
        }

        if (SC.StageTargetConfigurations.Count != 1)
		{
			throw new AutomationException ("iOS is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations", SC.StageTargetConfigurations.Count);
		}
		if (Params.Distribution)
		{
			throw new AutomationException("iOS cannot deploy a package made for distribution.");
		}
		var TargetConfiguration = SC.StageTargetConfigurations[0];
		var ProjectIPA = MakeIPAFileName(TargetConfiguration, Params);
		var StagedIPA = SC.StageDirectory + "\\" + Path.GetFileName(ProjectIPA);

		// verify the .ipa exists
		if (!FileExists(StagedIPA))
		{
			StagedIPA = ProjectIPA;
			if(!FileExists(StagedIPA))
			{
				throw new AutomationException("DEPLOY FAILED - {0} was not found", ProjectIPA);
			}
		}

		// if iterative deploy, determine the file delta
		string BundleIdentifier = "";
		bool bNeedsIPA = true;
		if (Params.IterativeDeploy)
		{
			if (File.Exists(Params.BaseStageDirectory + "/" + PlatformName + "/Info.plist"))
			{
				string Contents = File.ReadAllText(SC.StageDirectory + "/Info.plist");
				int Pos = Contents.IndexOf("CFBundleIdentifier");
				Pos = Contents.IndexOf("<string>", Pos) + 8;
				int EndPos = Contents.IndexOf("</string>", Pos);
				BundleIdentifier = Contents.Substring(Pos, EndPos - Pos);
			}

			// check to determine if we need to update the IPA
			String NonUFSManifestPath = SC.GetNonUFSDeploymentDeltaPath(Params.DeviceNames[0]);
			if (File.Exists(NonUFSManifestPath))
			{
				string NonUFSFiles = File.ReadAllText(NonUFSManifestPath);
				string[] Lines = NonUFSFiles.Split('\n');
				bNeedsIPA = Lines.Length > 0 && !string.IsNullOrWhiteSpace(Lines[0]);
			}
		}

		// Add a commandline for this deploy, if the config allows it.
		string AdditionalCommandline = (Params.FileServer || Params.CookOnTheFly) ? "" : (" -additionalcommandline " + "\"" + Params.RunCommandline + "\"");

		// deploy the .ipa
		var DeployServer = CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/IOS/DeploymentServer.exe");

		// check for it in the stage directory
		string CurrentDir = Directory.GetCurrentDirectory();
		Directory.SetCurrentDirectory(CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/IOS/"));
		if (!Params.IterativeDeploy || bCreatedIPA || bNeedsIPA)
		{
			RunAndLog(CmdEnv, DeployServer, "Install -ipa \"" + Path.GetFullPath(StagedIPA) + "\"" + (String.IsNullOrEmpty(Params.DeviceNames[0]) ? "" : " -device " + Params.DeviceNames[0]) + AdditionalCommandline);
		}
		if (Params.IterativeDeploy)
		{
			// push over the changed files
			RunAndLog(CmdEnv, DeployServer, "Deploy -manifest \"" + CombinePaths(Params.BaseStageDirectory, PlatformName, DeploymentContext.UFSDeployDeltaFileName + (Params.Devices.Count == 0 ? "" : Params.DeviceNames[0])) + "\"" + (Params.Devices.Count == 0 ? "" : " -device " + Params.DeviceNames[0]) + AdditionalCommandline + " -bundle " + BundleIdentifier);
		}
		Directory.SetCurrentDirectory (CurrentDir);
        PrintRunTime();
    }
Пример #3
0
    public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL)
    {
        Log("Package {0}", Params.RawProjectPath);

        // ensure the ue4game binary exists, if applicable
        string FullExePath = CombinePaths(Path.GetDirectoryName(Params.ProjectGameExeFilename), SC.StageExecutables[0] + (UnrealBuildTool.BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac ? ".stub" : ""));

        if (!SC.IsCodeBasedProject && !FileExists_NoExceptions(FullExePath))
        {
            Log("Failed to find game binary " + FullExePath);
            throw new AutomationException(ErrorCodes.Error_MissingExecutable, "Stage Failed. Could not find binary {0}. You may need to build the UE4 project with your target configuration and platform.", FullExePath);
        }

        //@TODO: We should be able to use this code on both platforms, when the following issues are sorted:
        //   - Raw executable is unsigned & unstripped (need to investigate adding stripping to IPP)
        //   - IPP needs to be able to codesign a raw directory
        //   - IPP needs to be able to take a .app directory instead of a Payload directory when doing RepackageFromStage (which would probably be renamed)
        //   - Some discrepancy in the loading screen pngs that are getting packaged, which needs to be investigated
        //   - Code here probably needs to be updated to write 0 byte files as 1 byte (difference with IPP, was required at one point when using Ionic.Zip to prevent issues on device, maybe not needed anymore?)
        if (UnrealBuildTool.BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac)
        {
            // copy in all of the artwork and plist
            var DeployHandler = UEBuildDeploy.GetBuildDeploy(UnrealTargetPlatform.IOS);

            DeployHandler.PrepForUATPackageOrDeploy(Params.ShortProjectName,
                                                    Path.GetDirectoryName(Params.RawProjectPath),
                                                    CombinePaths(Path.GetDirectoryName(Params.ProjectGameExeFilename), SC.StageExecutables[0]),
                                                    CombinePaths(SC.LocalRoot, "Engine"),
                                                    Params.Distribution,
                                                    "",
                                                    false);

            // figure out where to pop in the staged files
            string AppDirectory = string.Format("{0}/Payload/{1}.app",
                                                Path.GetDirectoryName(Params.ProjectGameExeFilename),
                                                Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename));

            // delete the old cookeddata
            InternalUtils.SafeDeleteDirectory(AppDirectory + "/cookeddata", true);
            InternalUtils.SafeDeleteFile(AppDirectory + "/ue4commandline.txt", true);

            if (!Params.IterativeDeploy)
            {
                // copy the Staged files to the AppDirectory
                string[] StagedFiles = Directory.GetFiles(SC.StageDirectory, "*", SearchOption.AllDirectories);
                foreach (string Filename in StagedFiles)
                {
                    string DestFilename = Filename.Replace(SC.StageDirectory, AppDirectory);
                    Directory.CreateDirectory(Path.GetDirectoryName(DestFilename));
                    InternalUtils.SafeCopyFile(Filename, DestFilename, true);
                }
            }
            else
            {
                // copy just the root stage directory files
                string[] StagedFiles = Directory.GetFiles(SC.StageDirectory, "*", SearchOption.TopDirectoryOnly);
                foreach (string Filename in StagedFiles)
                {
                    string DestFilename = Filename.Replace(SC.StageDirectory, AppDirectory);
                    Directory.CreateDirectory(Path.GetDirectoryName(DestFilename));
                    InternalUtils.SafeCopyFile(Filename, DestFilename, true);
                }
            }
        }

        if (SC.StageTargetConfigurations.Count != 1)
        {
            throw new AutomationException("iOS is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations", SC.StageTargetConfigurations.Count);
        }
        bCreatedIPA = false;
        bool bNeedsIPA = false;

        if (Params.IterativeDeploy)
        {
            String NonUFSManifestPath = SC.GetNonUFSDeploymentDeltaPath();
            // check to determine if we need to update the IPA
            if (File.Exists(NonUFSManifestPath))
            {
                string   NonUFSFiles = File.ReadAllText(NonUFSManifestPath);
                string[] Lines       = NonUFSFiles.Split('\n');
                bNeedsIPA = Lines.Length > 0 && !string.IsNullOrWhiteSpace(Lines[0]);
            }
        }

        if (UnrealBuildTool.BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
        {
            var TargetConfiguration = SC.StageTargetConfigurations[0];
            var ProjectIPA          = MakeIPAFileName(TargetConfiguration, Params);
            var ProjectStub         = Path.GetFullPath(Params.ProjectGameExeFilename);

            // package a .ipa from the now staged directory
            var IPPExe = CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/IOS/IPhonePackager.exe");

            Log("ProjectName={0}", Params.ShortProjectName);
            Log("ProjectStub={0}", ProjectStub);
            Log("ProjectIPA={0}", ProjectIPA);
            Log("IPPExe={0}", IPPExe);

            bool cookonthefly = Params.CookOnTheFly || Params.SkipCookOnTheFly;

            // if we are incremental check to see if we need to even update the IPA
            if (!Params.IterativeDeploy || !File.Exists(ProjectIPA) || bNeedsIPA)
            {
                // delete the .ipa to make sure it was made
                DeleteFile(ProjectIPA);

                bCreatedIPA = true;

                if (RemoteToolChain.bUseRPCUtil)
                {
                    string IPPArguments = "RepackageFromStage \"" + (Params.IsCodeBasedProject ? Params.RawProjectPath : "Engine") + "\"";
                    IPPArguments += " -config " + TargetConfiguration.ToString();

                    if (TargetConfiguration == UnrealTargetConfiguration.Shipping)
                    {
                        IPPArguments += " -compress=best";
                    }

                    // Determine if we should sign
                    bool bNeedToSign = GetCodeSignDesirability(Params);

                    if (!String.IsNullOrEmpty(Params.BundleName))
                    {
                        // Have to sign when a bundle name is specified
                        bNeedToSign   = true;
                        IPPArguments += " -bundlename " + Params.BundleName;
                    }

                    if (bNeedToSign)
                    {
                        IPPArguments += " -sign";
                        if (Params.Distribution)
                        {
                            IPPArguments += " -distribution";
                        }
                    }

                    IPPArguments += (cookonthefly ? " -cookonthefly" : "");
                    IPPArguments += " -stagedir \"" + CombinePaths(Params.BaseStageDirectory, "IOS") + "\"";
                    IPPArguments += " -project \"" + Params.RawProjectPath + "\"";
                    if (Params.IterativeDeploy)
                    {
                        IPPArguments += " -iterate";
                    }

                    RunAndLog(CmdEnv, IPPExe, IPPArguments);
                }
                else
                {
                    List <string> IPPArguments = new List <string>();
                    IPPArguments.Add("RepackageFromStage");
                    IPPArguments.Add(Params.IsCodeBasedProject ? Params.RawProjectPath : "Engine");
                    IPPArguments.Add("-config");
                    IPPArguments.Add(TargetConfiguration.ToString());

                    if (TargetConfiguration == UnrealTargetConfiguration.Shipping)
                    {
                        IPPArguments.Add("-compress=best");
                    }

                    // Determine if we should sign
                    bool bNeedToSign = GetCodeSignDesirability(Params);

                    if (!String.IsNullOrEmpty(Params.BundleName))
                    {
                        // Have to sign when a bundle name is specified
                        bNeedToSign = true;
                        IPPArguments.Add("-bundlename");
                        IPPArguments.Add(Params.BundleName);
                    }

                    if (bNeedToSign)
                    {
                        IPPArguments.Add("-sign");
                    }

                    if (cookonthefly)
                    {
                        IPPArguments.Add(" -cookonthefly");
                    }
                    IPPArguments.Add(" -stagedir");
                    IPPArguments.Add(CombinePaths(Params.BaseStageDirectory, "IOS"));
                    IPPArguments.Add(" -project");
                    IPPArguments.Add(Params.RawProjectPath);
                    if (Params.IterativeDeploy)
                    {
                        IPPArguments.Add(" -iterate");
                    }

                    if (RunIPP(IPPArguments.ToArray()) != 0)
                    {
                        throw new AutomationException("IPP Failed");
                    }
                }
            }

            // verify the .ipa exists
            if (!FileExists(ProjectIPA))
            {
                throw new AutomationException(ErrorCodes.Error_FailedToCreateIPA, "PACKAGE FAILED - {0} was not created", ProjectIPA);
            }

            if (WorkingCL > 0)
            {
                // Open files for add or edit
                var ExtraFilesToCheckin = new List <string>
                {
                    ProjectIPA
                };

                // check in the .ipa along with everything else
                UE4Build.AddBuildProductsToChangelist(WorkingCL, ExtraFilesToCheckin);
            }

            //@TODO: This automatically deploys after packaging, useful for testing on PC when iterating on IPP
            //Deploy(Params, SC);
        }
        else
        {
            // create the ipa
            string IPAName = CombinePaths(Path.GetDirectoryName(Params.RawProjectPath), "Binaries", "IOS", (Params.Distribution ? "Distro_" : "") + Params.ShortProjectName + (SC.StageTargetConfigurations[0] != UnrealTargetConfiguration.Development ? ("-IOS-" + SC.StageTargetConfigurations[0].ToString()) : "") + ".ipa");

            if (!Params.IterativeDeploy || !File.Exists(IPAName) || bNeedsIPA)
            {
                bCreatedIPA = true;

                // code sign the app
                CodeSign(Path.GetDirectoryName(Params.ProjectGameExeFilename), Params.IsCodeBasedProject ? Params.ShortProjectName : Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename), Params.RawProjectPath, SC.StageTargetConfigurations[0], SC.LocalRoot, Params.ShortProjectName, Path.GetDirectoryName(Params.RawProjectPath), SC.IsCodeBasedProject, Params.Distribution);

                // now generate the ipa
                PackageIPA(Path.GetDirectoryName(Params.ProjectGameExeFilename), Params.IsCodeBasedProject ? Params.ShortProjectName : Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename), Params.ShortProjectName, Path.GetDirectoryName(Params.RawProjectPath), SC.StageTargetConfigurations[0], Params.Distribution);
            }
        }

        PrintRunTime();
    }
Пример #4
0
    public override void Deploy(ProjectParams Params, DeploymentContext SC)
    {
        if (SC.StageTargetConfigurations.Count != 1)
        {
            throw new AutomationException("iOS is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations", SC.StageTargetConfigurations.Count);
        }
        if (Params.Distribution)
        {
            throw new AutomationException("iOS cannot deploy a package made for distribution.");
        }
        var TargetConfiguration = SC.StageTargetConfigurations[0];
        var ProjectIPA          = MakeIPAFileName(TargetConfiguration, Params);
        var StagedIPA           = SC.StageDirectory + "\\" + Path.GetFileName(ProjectIPA);

        // verify the .ipa exists
        if (!FileExists(StagedIPA))
        {
            StagedIPA = ProjectIPA;
            if (!FileExists(StagedIPA))
            {
                throw new AutomationException("DEPLOY FAILED - {0} was not found", ProjectIPA);
            }
        }

        // if iterative deploy, determine the file delta
        string BundleIdentifier = "";
        bool   bNeedsIPA        = true;

        if (Params.IterativeDeploy)
        {
            if (File.Exists(Params.BaseStageDirectory + "/IOS/Info.plist"))
            {
                string Contents = File.ReadAllText(SC.StageDirectory + "/Info.plist");
                int    Pos      = Contents.IndexOf("CFBundleIdentifier");
                Pos = Contents.IndexOf("<string>", Pos) + 8;
                int EndPos = Contents.IndexOf("</string>", Pos);
                BundleIdentifier = Contents.Substring(Pos, EndPos - Pos);
            }

            // check to determine if we need to update the IPA
            String NonUFSManifestPath = SC.GetNonUFSDeploymentDeltaPath();
            if (File.Exists(NonUFSManifestPath))
            {
                string   NonUFSFiles = File.ReadAllText(NonUFSManifestPath);
                string[] Lines       = NonUFSFiles.Split('\n');
                bNeedsIPA = Lines.Length > 0 && !string.IsNullOrWhiteSpace(Lines[0]);
            }
        }

        // Add a commandline for this deploy, if the config allows it.
        string AdditionalCommandline = (Params.FileServer || Params.CookOnTheFly) ? "" : (" -additionalcommandline " + "\"" + Params.RunCommandline + "\"");

        // deploy the .ipa
        var DeployServer = CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/IOS/DeploymentServer.exe");

        // check for it in the stage directory
        string CurrentDir = Directory.GetCurrentDirectory();

        Directory.SetCurrentDirectory(CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/IOS/"));
        if (!Params.IterativeDeploy || bCreatedIPA || bNeedsIPA)
        {
            RunAndLog(CmdEnv, DeployServer, "Install -ipa \"" + Path.GetFullPath(StagedIPA) + "\"" + (String.IsNullOrEmpty(Params.Device) ? "" : " -device " + Params.Device.Substring(4)) + AdditionalCommandline);
        }
        if (Params.IterativeDeploy)
        {
            // push over the changed files
            RunAndLog(CmdEnv, DeployServer, "Deploy -manifest \"" + CombinePaths(Params.BaseStageDirectory, "IOS", DeploymentContext.UFSDeployDeltaFileName) + "\"" + (String.IsNullOrEmpty(Params.Device) ? "" : " -device " + Params.Device.Substring(4)) + AdditionalCommandline + " -bundle " + BundleIdentifier);
        }
        Directory.SetCurrentDirectory(CurrentDir);
        PrintRunTime();
    }
Пример #5
0
    public override void Deploy(ProjectParams Params, DeploymentContext SC)
    {
        string DeviceArchitecture = GetBestDeviceArchitecture(Params);
        string GPUArchitecture    = GetBestGPUArchitecture(Params);

        string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, DeviceArchitecture, GPUArchitecture);

        // make sure APK is up to date (this is fast if so)
        var Deploy = UEBuildDeploy.GetBuildDeploy(UnrealTargetPlatform.Android);

        if (!Params.Prebuilt)
        {
            string CookFlavor = SC.FinalCookPlatform.IndexOf("_") > 0 ? SC.FinalCookPlatform.Substring(SC.FinalCookPlatform.IndexOf("_")) : "";
            string SOName     = GetSONameWithoutArchitecture(Params, SC.StageExecutables[0]);
            Deploy.PrepForUATPackageOrDeploy(Params.ShortProjectName, SC.ProjectRoot, SOName, SC.LocalRoot + "/Engine", Params.Distribution, CookFlavor, true);
        }

        // now we can use the apk to get more info
        string PackageName = GetPackageInfo(ApkName, false);

        // Setup the OBB name and add the storage path (queried from the device) to it
        string        DeviceStorageQueryCommand = GetStorageQueryCommand();
        ProcessResult Result          = RunAdbCommand(Params, DeviceStorageQueryCommand, null, ERunOptions.AppMustExist);
        String        StorageLocation = Result.Output.Trim();
        string        DeviceObbName   = StorageLocation + "/" + GetDeviceObbName(ApkName);
        string        RemoteDir       = StorageLocation + "/UE4Game/" + Params.ShortProjectName;

        // determine if APK out of date
        string APKLastUpdateTime = new FileInfo(ApkName).LastWriteTime.ToString();
        bool   bNeedAPKInstall   = true;

        if (Params.IterativeDeploy)
        {
            // Check for apk installed with this package name on the device
            ProcessResult InstalledResult = RunAdbCommand(Params, "shell pm list packages " + PackageName, null, ERunOptions.AppMustExist);
            if (InstalledResult.Output.Contains(PackageName))
            {
                // See if apk is up to date on device
                InstalledResult = RunAdbCommand(Params, "shell cat " + RemoteDir + "/APKFileStamp.txt", null, ERunOptions.AppMustExist);
                if (InstalledResult.Output.StartsWith("APK: "))
                {
                    if (InstalledResult.Output.Substring(5).Trim() == APKLastUpdateTime)
                    {
                        bNeedAPKInstall = false;
                    }

                    // Stop the previously running copy (uninstall/install did this before)
                    InstalledResult = RunAdbCommand(Params, "shell am force-stop " + PackageName, null, ERunOptions.AppMustExist);
                    if (InstalledResult.Output.Contains("Error"))
                    {
                        // force-stop not supported (Android < 3.0) so check if package is actually running
                        // Note: cannot use grep here since it may not be installed on device
                        InstalledResult = RunAdbCommand(Params, "shell ps", null, ERunOptions.AppMustExist);
                        if (InstalledResult.Output.Contains(PackageName))
                        {
                            // it is actually running so use the slow way to kill it (uninstall and reinstall)
                            bNeedAPKInstall = true;
                        }
                    }
                }
            }
        }

        // install new APK if needed
        if (bNeedAPKInstall)
        {
            // try uninstalling an old app with the same identifier.
            int    SuccessCode          = 0;
            string UninstallCommandline = "uninstall " + PackageName;
            RunAndLogAdbCommand(Params, UninstallCommandline, out SuccessCode);

            // install the apk
            string InstallCommandline = "install \"" + ApkName + "\"";
            string InstallOutput      = RunAndLogAdbCommand(Params, InstallCommandline, out SuccessCode);
            int    FailureIndex       = InstallOutput.IndexOf("Failure");

            // adb install doesn't always return an error code on failure, and instead prints "Failure", followed by an error code.
            if (SuccessCode != 0 || FailureIndex != -1)
            {
                string ErrorMessage = String.Format("Installation of apk '{0}' failed", ApkName);
                if (FailureIndex != -1)
                {
                    string FailureString = InstallOutput.Substring(FailureIndex + 7).Trim();
                    if (FailureString != "")
                    {
                        ErrorMessage += ": " + FailureString;
                    }
                }

                ErrorReporter.Error(ErrorMessage, (int)ErrorCodes.Error_AppInstallFailed);
                throw new AutomationException(ErrorMessage);
            }
        }

        // update the ue4commandline.txt
        // update and deploy ue4commandline.txt
        // always delete the existing commandline text file, so it doesn't reuse an old one
        string IntermediateCmdLineFile = CombinePaths(SC.StageDirectory, "UE4CommandLine.txt");

        Project.WriteStageCommandline(IntermediateCmdLineFile, Params, SC);

        // copy files to device if we were staging
        if (SC.Stage)
        {
            // cache some strings
            string BaseCommandline = "push";

            HashSet <string> EntriesToDeploy = new HashSet <string>();

            if (Params.IterativeDeploy)
            {
                // always send UE4CommandLine.txt (it was written above after delta checks applied)
                EntriesToDeploy.Add(IntermediateCmdLineFile);

                // Add non UFS files if any to deploy
                String NonUFSManifestPath = SC.GetNonUFSDeploymentDeltaPath();
                if (File.Exists(NonUFSManifestPath))
                {
                    string NonUFSFiles = File.ReadAllText(NonUFSManifestPath);
                    foreach (string Filename in NonUFSFiles.Split('\n'))
                    {
                        if (!string.IsNullOrEmpty(Filename) && !string.IsNullOrWhiteSpace(Filename))
                        {
                            EntriesToDeploy.Add(CombinePaths(SC.StageDirectory, Filename.Trim()));
                        }
                    }
                }

                // Add UFS files if any to deploy
                String UFSManifestPath = SC.GetUFSDeploymentDeltaPath();
                if (File.Exists(UFSManifestPath))
                {
                    string UFSFiles = File.ReadAllText(UFSManifestPath);
                    foreach (string Filename in UFSFiles.Split('\n'))
                    {
                        if (!string.IsNullOrEmpty(Filename) && !string.IsNullOrWhiteSpace(Filename))
                        {
                            EntriesToDeploy.Add(CombinePaths(SC.StageDirectory, Filename.Trim()));
                        }
                    }
                }

                // For now, if too many files may be better to just push them all
                if (EntriesToDeploy.Count > 500)
                {
                    // make sure device is at a clean state
                    RunAdbCommand(Params, "shell rm -r " + RemoteDir);

                    EntriesToDeploy.Clear();
                    EntriesToDeploy.TrimExcess();
                    EntriesToDeploy.Add(SC.StageDirectory);
                }
            }
            else
            {
                // make sure device is at a clean state
                RunAdbCommand(Params, "shell rm -r " + RemoteDir);

                // Copy UFS files..
                string[] Files = Directory.GetFiles(SC.StageDirectory, "*", SearchOption.AllDirectories);
                System.Array.Sort(Files);

                // Find all the files we exclude from copying. And include
                // the directories we need to individually copy.
                HashSet <string>   ExcludedFiles = new HashSet <string>();
                SortedSet <string> IndividualCopyDirectories
                    = new SortedSet <string>((IComparer <string>) new LongestFirst());
                foreach (string Filename in Files)
                {
                    bool Exclude = false;
                    // Don't push the apk, we install it
                    Exclude |= Path.GetExtension(Filename).Equals(".apk", StringComparison.InvariantCultureIgnoreCase);
                    // For excluded files we add the parent dirs to our
                    // tracking of stuff to individually copy.
                    if (Exclude)
                    {
                        ExcludedFiles.Add(Filename);
                        // We include all directories up to the stage root in having
                        // to individually copy the files.
                        for (string FileDirectory = Path.GetDirectoryName(Filename);
                             !FileDirectory.Equals(SC.StageDirectory);
                             FileDirectory = Path.GetDirectoryName(FileDirectory))
                        {
                            if (!IndividualCopyDirectories.Contains(FileDirectory))
                            {
                                IndividualCopyDirectories.Add(FileDirectory);
                            }
                        }
                        if (!IndividualCopyDirectories.Contains(SC.StageDirectory))
                        {
                            IndividualCopyDirectories.Add(SC.StageDirectory);
                        }
                    }
                }

                // The directories are sorted above in "deepest" first. We can
                // therefore start copying those individual dirs which will
                // recreate the tree. As the subtrees will get copied at each
                // possible individual level.
                foreach (string DirectoryName in IndividualCopyDirectories)
                {
                    string[] Entries
                        = Directory.GetFileSystemEntries(DirectoryName, "*", SearchOption.TopDirectoryOnly);
                    foreach (string Entry in Entries)
                    {
                        // We avoid excluded files and the individual copy dirs
                        // (the individual copy dirs will get handled as we iterate).
                        if (ExcludedFiles.Contains(Entry) || IndividualCopyDirectories.Contains(Entry))
                        {
                            continue;
                        }
                        else
                        {
                            EntriesToDeploy.Add(Entry);
                        }
                    }
                }

                if (EntriesToDeploy.Count == 0)
                {
                    EntriesToDeploy.Add(SC.StageDirectory);
                }
            }

            // We now have a minimal set of file & dir entries we need
            // to deploy. Files we deploy will get individually copied
            // and dirs will get the tree copies by default (that's
            // what ADB does).
            HashSet <ProcessResult> DeployCommands = new HashSet <ProcessResult>();
            foreach (string Entry in EntriesToDeploy)
            {
                string FinalRemoteDir = RemoteDir;
                string RemotePath     = Entry.Replace(SC.StageDirectory, FinalRemoteDir).Replace("\\", "/");
                string Commandline    = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, Entry, RemotePath);
                // We run deploy commands in parallel to maximize the connection
                // throughput.
                DeployCommands.Add(
                    RunAdbCommand(Params, Commandline, null,
                                  ERunOptions.Default | ERunOptions.NoWaitForExit));
                // But we limit the parallel commands to avoid overwhelming
                // memory resources.
                if (DeployCommands.Count == DeployMaxParallelCommands)
                {
                    while (DeployCommands.Count > DeployMaxParallelCommands / 2)
                    {
                        Thread.Sleep(1);
                        DeployCommands.RemoveWhere(
                            delegate(ProcessResult r)
                        {
                            return(r.HasExited);
                        });
                    }
                }
            }
            foreach (ProcessResult deploy_result in DeployCommands)
            {
                deploy_result.WaitForExit();
            }

            // delete the .obb file, since it will cause nothing we just deployed to be used
            RunAdbCommand(Params, "shell rm " + DeviceObbName);
        }
        else if (SC.Archive)
        {
            // deploy the obb if there is one
            string ObbPath = Path.Combine(SC.StageDirectory, GetFinalObbName(ApkName));
            if (File.Exists(ObbPath))
            {
                // cache some strings
                string BaseCommandline = "push";

                string Commandline = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, ObbPath, DeviceObbName);
                RunAdbCommand(Params, Commandline);
            }
        }
        else
        {
            // cache some strings
            string BaseCommandline = "push";

            string FinalRemoteDir = RemoteDir;

            /*
             * // handle the special case of the UE4Commandline.txt when using content only game (UE4Game)
             * if (!Params.IsCodeBasedProject)
             * {
             *      FinalRemoteDir = "/mnt/sdcard/UE4Game";
             * }
             */

            string RemoteFilename = IntermediateCmdLineFile.Replace(SC.StageDirectory, FinalRemoteDir).Replace("\\", "/");
            string Commandline    = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, IntermediateCmdLineFile, RemoteFilename);
            RunAdbCommand(Params, Commandline);
        }

        // write new timestamp for APK (do it here since RemoteDir will now exist)
        if (bNeedAPKInstall)
        {
            int SuccessCode = 0;
            RunAndLogAdbCommand(Params, "shell \"echo 'APK: " + APKLastUpdateTime + "' > " + RemoteDir + "/APKFileStamp.txt\"", out SuccessCode);
        }
    }