/// <summary> /// /// </summary> /// <param name="ProjectFile"></param> /// <param name="Executable"></param> /// <param name="StageDirectory"></param> /// <param name="PlatformType"></param> public static void GenerateAssetCatalog(FileReference ProjectFile, string Executable, string StageDirectory, UnrealTargetPlatform PlatformType) { // Initialize the toolchain. IOSProjectSettings ProjectSettings = ((IOSPlatform)UEBuildPlatform.GetBuildPlatform(PlatformType)).ReadProjectSettings(null); IOSToolChain ToolChain = new IOSToolChain(ProjectFile, ProjectSettings); // Determine whether the user has modified icons that require a remote Mac to build. CppPlatform Platform = PlatformType == UnrealTargetPlatform.IOS ? CppPlatform.IOS : CppPlatform.TVOS; bool bUserImagesExist = false; ToolChain.GenerateAssetCatalog(Platform, ref bUserImagesExist); // Don't attempt to do anything remotely if the user is using the default UE4 images. if (!bUserImagesExist) { return; } // Also don't attempt to use a remote Mac if packaging for TVOS on PC. if (Platform == CppPlatform.TVOS && BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { return; } // Save off the current bUseRPCUtil setting to restore at the end of this function. // At this time, iPhonePackager needs to be called with bUseRPCUtil == true. bool bSaveUseRPCUtil = RemoteToolChain.bUseRPCUtil; // Initialize the remote calling environment, taking into account the user's SSH setting. ToolChain.SetUpGlobalEnvironment(false); // Build the asset catalog ActionGraph. ActionGraph ActionGraph = new ActionGraph(); List <FileItem> OutputFiles = new List <FileItem>(); ToolChain.CompileAssetCatalog(FileItem.GetItemByPath(Executable), Platform, ActionGraph, OutputFiles); ActionGraph.FinalizeActionGraph(); // I'm not sure how to derive the UE4Game and Development arguments programmatically. string[] Arguments = new string[] { "UE4Game", (PlatformType == UnrealTargetPlatform.IOS ? "IOS" : "TVOS"), "Development", "-UniqueBuildEnvironment" }; // Perform all of the setup necessary to actually execute the ActionGraph instance. ReadOnlyBuildVersion Version = new ReadOnlyBuildVersion(BuildVersion.ReadDefault()); List <string[]> TargetSettings = new List <string[]>(); TargetSettings.Add(Arguments); var Targets = new List <UEBuildTarget>(); Dictionary <UEBuildTarget, CPPHeaders> TargetToHeaders = new Dictionary <UEBuildTarget, CPPHeaders>(); List <TargetDescriptor> TargetDescs = new List <TargetDescriptor>(); foreach (string[] TargetSetting in TargetSettings) { TargetDescs.AddRange(TargetDescriptor.ParseCommandLine(TargetSetting, ref ProjectFile)); } foreach (TargetDescriptor TargetDesc in TargetDescs) { UEBuildTarget Target = UEBuildTarget.CreateTarget(TargetDesc, Arguments, false, Version); if (Target == null) { continue; } Targets.Add(Target); TargetToHeaders.Add(Target, null); } bool bIsRemoteCompile = BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac; // Create the build configuration object, and read the settings BuildConfiguration BuildConfiguration = new BuildConfiguration(); XmlConfig.ApplyTo(BuildConfiguration); CommandLine.ParseArguments(Arguments, BuildConfiguration); BuildConfiguration.bUseUBTMakefiles = false; Action[] PrerequisiteActions; { HashSet <Action> PrerequisiteActionsSet = new HashSet <Action>(); foreach (FileItem OutputFile in OutputFiles) { ActionGraph.GatherPrerequisiteActions(OutputFile, ref PrerequisiteActionsSet); } PrerequisiteActions = PrerequisiteActionsSet.ToArray(); } // Copy any asset catalog files to the remote Mac, if necessary. foreach (UEBuildTarget Target in Targets) { UEBuildPlatform.GetBuildPlatform(Target.Platform).PreBuildSync(); } // Begin execution of the ActionGraph. Dictionary <UEBuildTarget, List <FileItem> > TargetToOutdatedPrerequisitesMap; List <Action> ActionsToExecute = ActionGraph.GetActionsToExecute(BuildConfiguration, PrerequisiteActions, Targets, TargetToHeaders, true, true, out TargetToOutdatedPrerequisitesMap); string ExecutorName = "Unknown"; bool bSuccess = ActionGraph.ExecuteActions(BuildConfiguration, ActionsToExecute, bIsRemoteCompile, out ExecutorName, "", EHotReload.Disabled); if (bSuccess) { if (bIsRemoteCompile) { // Copy the remotely built AssetCatalog directory locally. foreach (FileItem OutputFile in OutputFiles) { string RemoteDirectory = System.IO.Path.GetDirectoryName(OutputFile.AbsolutePath).Replace("\\", "/"); FileItem LocalExecutable = ToolChain.RemoteToLocalFileItem(FileItem.GetItemByPath(Executable)); string LocalDirectory = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(LocalExecutable.AbsolutePath), "AssetCatalog"); LocalDirectory = StageDirectory; RPCUtilHelper.CopyDirectory(RemoteDirectory, LocalDirectory, RPCUtilHelper.ECopyOptions.DoNotReplace); } } else { // Copy the built AssetCatalog directory to the StageDirectory. foreach (FileItem OutputFile in OutputFiles) { string SourceDirectory = System.IO.Path.GetDirectoryName(OutputFile.AbsolutePath).Replace("\\", "/"); System.IO.DirectoryInfo SourceDirectoryInfo = new System.IO.DirectoryInfo(SourceDirectory); if (!System.IO.Directory.Exists(StageDirectory)) { System.IO.Directory.CreateDirectory(StageDirectory); } System.IO.FileInfo[] SourceFiles = SourceDirectoryInfo.GetFiles(); foreach (System.IO.FileInfo SourceFile in SourceFiles) { string DestinationPath = System.IO.Path.Combine(StageDirectory, SourceFile.Name); SourceFile.CopyTo(DestinationPath, true); } } } } // Restore the former bUseRPCUtil setting. RemoteToolChain.bUseRPCUtil = bSaveUseRPCUtil; }
/// <summary> /// Parse a list of target descriptors from the command line /// </summary> /// <param name="Arguments">Command-line arguments</param> /// <param name="ProjectFile">The project file, if already set. May be updated if not.</param> /// <returns>List of target descriptors</returns> public static List <TargetDescriptor> ParseCommandLine(string[] Arguments, ref FileReference ProjectFile) { UnrealTargetPlatform Platform = UnrealTargetPlatform.Unknown; UnrealTargetConfiguration Configuration = UnrealTargetConfiguration.Unknown; List <string> TargetNames = new List <string>(); List <TargetType> TargetTypes = new List <TargetType>(); string Architecture = null; List <OnlyModule> OnlyModules = new List <OnlyModule>(); FileReference ForeignPlugin = null; string ForceReceiptFileName = null; // Settings for creating/using static libraries for the engine for (int ArgumentIndex = 0; ArgumentIndex < Arguments.Length; ArgumentIndex++) { string Argument = Arguments[ArgumentIndex]; if (!Argument.StartsWith("-")) { UnrealTargetPlatform ParsedPlatform; if (Enum.TryParse(Argument, true, out ParsedPlatform) && ParsedPlatform != UnrealTargetPlatform.Unknown) { if (Platform != UnrealTargetPlatform.Unknown) { throw new BuildException("Multiple platforms specified on command line (first {0}, then {1})", Platform, ParsedPlatform); } Platform = ParsedPlatform; continue; } UnrealTargetConfiguration ParsedConfiguration; if (Enum.TryParse(Argument, true, out ParsedConfiguration) && ParsedConfiguration != UnrealTargetConfiguration.Unknown) { if (Configuration != UnrealTargetConfiguration.Unknown) { throw new BuildException("Multiple configurations specified on command line (first {0}, then {1})", Configuration, ParsedConfiguration); } Configuration = ParsedConfiguration; continue; } // Make sure the target name is valid. It may be the path to a project file. if (Argument.IndexOfAny(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, '.' }) == -1) { TargetNames.Add(Argument); } } else { string Value; if (ParseArgumentValue(Argument, "-TargetType=", out Value)) { TargetType Type; if (!Enum.TryParse(Value, true, out Type)) { throw new BuildException("Invalid target type: '{0}'", Value); } TargetTypes.Add(Type); } else if (ParseArgumentValue(Argument, "-Module=", out Value)) { OnlyModules.Add(new OnlyModule(Value)); } else if (ParseArgumentValue(Argument, "-ModuleWithSuffix=", out Value)) { int SuffixIdx = Value.LastIndexOf(','); if (SuffixIdx == -1) { throw new BuildException("Missing suffix argument from -ModuleWithSuffix=Name,Suffix"); } OnlyModules.Add(new OnlyModule(Value.Substring(0, SuffixIdx), Value.Substring(SuffixIdx + 1))); } else if (ParseArgumentValue(Argument, "-Plugin=", out Value)) { if (ForeignPlugin != null) { throw new BuildException("Only one foreign plugin to compile may be specified per invocation"); } ForeignPlugin = new FileReference(Value); } else if (ParseArgumentValue(Argument, "-Receipt=", out Value)) { ForceReceiptFileName = Value; } else { switch (Arguments[ArgumentIndex].ToUpperInvariant()) { case "-MODULE": throw new BuildException("'-Module <Name>' syntax is no longer supported on the command line. Use '-Module=<Name>' instead."); case "-MODULEWITHSUFFIX": throw new BuildException("'-ModuleWithSuffix <Name> <Suffix>' syntax is no longer supported on the command line. Use '-Module=<Name>,<Suffix>' instead."); case "-PLUGIN": throw new BuildException("'-Plugin <Path>' syntax is no longer supported on the command line. Use '-Plugin=<Path>' instead."); case "-RECEIPT": throw new BuildException("'-Receipt <Path>' syntax is no longer supported on the command line. Use '-Receipt=<Path>' instead."); } } } } if (Platform == UnrealTargetPlatform.Unknown) { throw new BuildException("Couldn't find platform name."); } if (Configuration == UnrealTargetConfiguration.Unknown) { throw new BuildException("Couldn't determine configuration name."); } if (Architecture == null) { Architecture = UEBuildPlatform.GetBuildPlatform(Platform).GetDefaultArchitecture(ProjectFile); } // Create all the target descriptors for targets specified by type foreach (TargetType Type in TargetTypes) { if (ProjectFile == null) { throw new BuildException("-TargetType=... requires a project file to be specified"); } else { TargetNames.Add(RulesCompiler.CreateProjectRulesAssembly(ProjectFile).GetTargetNameByType(Type, Platform, Configuration, Architecture, ProjectFile, new ReadOnlyBuildVersion(BuildVersion.ReadDefault()))); } } // Create all the target descriptor List <TargetDescriptor> Targets = new List <TargetDescriptor>(); foreach (string TargetName in TargetNames) { // If a project file was not specified see if we can find one if (ProjectFile == null && UProjectInfo.TryGetProjectForTarget(TargetName, out ProjectFile)) { Log.TraceVerbose("Found project file for {0} - {1}", TargetName, ProjectFile); } TargetDescriptor Target = new TargetDescriptor(ProjectFile, TargetName, Platform, Configuration, Architecture); Target.OnlyModules = OnlyModules; Target.ForeignPlugin = ForeignPlugin; Target.ForceReceiptFileName = ForceReceiptFileName; Targets.Add(Target); } // Make sure we could parse something if (Targets.Count == 0) { throw new BuildException("No target name was specified on the command-line."); } return(Targets); }
/// <summary> /// Build a target remotely /// </summary> /// <param name="TargetDesc">Descriptor for the target to build</param> /// <param name="RemoteLogFile">Path to store the remote log file</param> /// <returns>True if the build succeeded, false otherwise</returns> public bool Build(TargetDescriptor TargetDesc, FileReference RemoteLogFile) { // Get the directory for working files DirectoryReference BaseDir = DirectoryReference.FromFile(TargetDesc.ProjectFile) ?? UnrealBuildTool.EngineDirectory; DirectoryReference TempDir = DirectoryReference.Combine(BaseDir, "Intermediate", "Remote", TargetDesc.Name, TargetDesc.Platform.ToString(), TargetDesc.Configuration.ToString()); DirectoryReference.CreateDirectory(TempDir); bool bLogIsMapped = false; foreach (RemoteMapping Mapping in Mappings) { if (RemoteLogFile.Directory.FullName.Equals(Mapping.LocalDirectory.FullName, StringComparison.InvariantCultureIgnoreCase)) { bLogIsMapped = true; break; } } if (!bLogIsMapped) { Mappings.Add(new RemoteMapping(RemoteLogFile.Directory, GetRemotePath(RemoteLogFile.Directory))); } // Compile the rules assembly RulesAssembly RulesAssembly = RulesCompiler.CreateTargetRulesAssembly(TargetDesc.ProjectFile, TargetDesc.Name, false, false, TargetDesc.ForeignPlugin); // Create the target rules TargetRules Rules = RulesAssembly.CreateTargetRules(TargetDesc.Name, TargetDesc.Platform, TargetDesc.Configuration, TargetDesc.Architecture, TargetDesc.ProjectFile, new ReadOnlyBuildVersion(BuildVersion.ReadDefault()), new string[0]); // Check if we need to enable a nativized plugin, and compile the assembly for that if we do FileReference NativizedPluginFile = Rules.GetNativizedPlugin(); if (NativizedPluginFile != null) { RulesAssembly = RulesCompiler.CreatePluginRulesAssembly(NativizedPluginFile, false, RulesAssembly, false); } // Path to the local manifest file. This has to be translated from the remote format after the build is complete. List <FileReference> LocalManifestFiles = new List <FileReference>(); // Path to the remote manifest file FileReference RemoteManifestFile = FileReference.Combine(TempDir, "Manifest.xml"); // Prepare the arguments we will pass to the remote build List <string> RemoteArguments = new List <string>(); RemoteArguments.Add(TargetDesc.Name); RemoteArguments.Add(TargetDesc.Platform.ToString()); RemoteArguments.Add(TargetDesc.Configuration.ToString()); RemoteArguments.Add("-SkipRulesCompile"); // Use the rules assembly built locally RemoteArguments.Add(String.Format("-XmlConfigCache={0}", GetRemotePath(XmlConfig.CacheFile))); // Use the XML config cache built locally, since the remote won't have it RemoteArguments.Add(String.Format("-Log={0}", GetRemotePath(RemoteLogFile))); RemoteArguments.Add(String.Format("-Manifest={0}", GetRemotePath(RemoteManifestFile))); if (TargetDesc.ProjectFile != null) { RemoteArguments.Add(String.Format("-Project={0}", GetRemotePath(TargetDesc.ProjectFile))); } foreach (string LocalArgument in TargetDesc.AdditionalArguments) { int EqualsIdx = LocalArgument.IndexOf('='); if (EqualsIdx == -1) { RemoteArguments.Add(LocalArgument); continue; } string Key = LocalArgument.Substring(0, EqualsIdx); string Value = LocalArgument.Substring(EqualsIdx + 1); if (Key.Equals("-Log", StringComparison.InvariantCultureIgnoreCase)) { // We are already writing to the local log file. The remote will produce a different log (RemoteLogFile) continue; } if (Key.Equals("-Manifest", StringComparison.InvariantCultureIgnoreCase)) { LocalManifestFiles.Add(new FileReference(Value)); continue; } string RemoteArgument = LocalArgument; foreach (RemoteMapping Mapping in Mappings) { if (Value.StartsWith(Mapping.LocalDirectory.FullName, StringComparison.InvariantCultureIgnoreCase)) { RemoteArgument = String.Format("{0}={1}", Key, GetRemotePath(Value)); break; } } RemoteArguments.Add(RemoteArgument); } // Handle any per-platform setup that is required if (TargetDesc.Platform == UnrealTargetPlatform.IOS || TargetDesc.Platform == UnrealTargetPlatform.TVOS) { // Always generate a .stub RemoteArguments.Add("-CreateStub"); // Cannot use makefiles, since we need PostBuildSync() to generate the IPA (and that requires a TargetRules instance) RemoteArguments.Add("-NoUBTMakefiles"); // Get the provisioning data for this project IOSProvisioningData ProvisioningData = ((IOSPlatform)UEBuildPlatform.GetBuildPlatform(TargetDesc.Platform)).ReadProvisioningData(TargetDesc.ProjectFile); if (ProvisioningData == null || ProvisioningData.MobileProvisionFile == null) { throw new BuildException("Unable to find mobile provision for {0}. See log for more information.", TargetDesc.Name); } // Create a local copy of the provision FileReference MobileProvisionFile = FileReference.Combine(TempDir, ProvisioningData.MobileProvisionFile.GetFileName()); if (FileReference.Exists(MobileProvisionFile)) { FileReference.SetAttributes(MobileProvisionFile, FileAttributes.Normal); } FileReference.Copy(ProvisioningData.MobileProvisionFile, MobileProvisionFile, true); Log.TraceInformation("[Remote] Uploading {0}", MobileProvisionFile); UploadFile(MobileProvisionFile); // Extract the certificate for the project. Try to avoid calling IPP if we already have it. FileReference CertificateFile = FileReference.Combine(TempDir, "Certificate.p12"); FileReference CertificateInfoFile = FileReference.Combine(TempDir, "Certificate.txt"); string CertificateInfoContents = String.Format("{0}\n{1}", ProvisioningData.MobileProvisionFile, FileReference.GetLastWriteTimeUtc(ProvisioningData.MobileProvisionFile).Ticks); if (!FileReference.Exists(CertificateFile) || !FileReference.Exists(CertificateInfoFile) || FileReference.ReadAllText(CertificateInfoFile) != CertificateInfoContents) { Log.TraceInformation("[Remote] Exporting certificate for {0}...", ProvisioningData.MobileProvisionFile); StringBuilder Arguments = new StringBuilder("ExportCertificate"); if (TargetDesc.ProjectFile == null) { Arguments.AppendFormat(" \"{0}\"", UnrealBuildTool.EngineSourceDirectory); } else { Arguments.AppendFormat(" \"{0}\"", TargetDesc.ProjectFile.Directory); } Arguments.AppendFormat(" -provisionfile \"{0}\"", ProvisioningData.MobileProvisionFile); Arguments.AppendFormat(" -outputcertificate \"{0}\"", CertificateFile); if (TargetDesc.Platform == UnrealTargetPlatform.TVOS) { Arguments.Append(" -tvos"); } ProcessStartInfo StartInfo = new ProcessStartInfo(); StartInfo.FileName = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Binaries", "DotNET", "IOS", "IPhonePackager.exe").FullName; StartInfo.Arguments = Arguments.ToString(); if (Utils.RunLocalProcessAndLogOutput(StartInfo) != 0) { throw new BuildException("IphonePackager failed."); } FileReference.WriteAllText(CertificateInfoFile, CertificateInfoContents); } // Upload the certificate to the remote Log.TraceInformation("[Remote] Uploading {0}", CertificateFile); UploadFile(CertificateFile); // Tell the remote UBT instance to use them RemoteArguments.Add(String.Format("-ImportProvision={0}", GetRemotePath(MobileProvisionFile))); RemoteArguments.Add(String.Format("-ImportCertificate={0}", GetRemotePath(CertificateFile))); RemoteArguments.Add(String.Format("-ImportCertificatePassword=A")); } // Upload the workspace files UploadWorkspace(TempDir); // Fixup permissions on any shell scripts Execute(RemoteBaseDir, String.Format("chmod +x {0}/Build/BatchFiles/Mac/*.sh", EscapeShellArgument(GetRemotePath(UnrealBuildTool.EngineDirectory)))); // Execute the compile Log.TraceInformation("[Remote] Executing build"); StringBuilder BuildCommandLine = new StringBuilder("Engine/Build/BatchFiles/Mac/Build.sh"); foreach (string RemoteArgument in RemoteArguments) { BuildCommandLine.AppendFormat(" {0}", EscapeShellArgument(RemoteArgument)); } int Result = Execute(GetRemotePath(UnrealBuildTool.RootDirectory), BuildCommandLine.ToString()); if (Result != 0) { if (RemoteLogFile != null) { Log.TraceInformation("[Remote] Downloading {0}", RemoteLogFile); DownloadFile(RemoteLogFile); } return(false); } // Download the manifest Log.TraceInformation("[Remote] Downloading {0}", RemoteManifestFile); DownloadFile(RemoteManifestFile); // Convert the manifest to local form BuildManifest Manifest = Utils.ReadClass <BuildManifest>(RemoteManifestFile.FullName); for (int Idx = 0; Idx < Manifest.BuildProducts.Count; Idx++) { Manifest.BuildProducts[Idx] = GetLocalPath(Manifest.BuildProducts[Idx]).FullName; } // Download the files from the remote if (TargetDesc.AdditionalArguments.Any(x => x.Equals("-GenerateManifest", StringComparison.InvariantCultureIgnoreCase))) { LocalManifestFiles.Add(FileReference.Combine(UnrealBuildTool.EngineDirectory, "Intermediate", "Build", "Manifest.xml")); } else { Log.TraceInformation("[Remote] Downloading build products"); List <FileReference> FilesToDownload = new List <FileReference>(); FilesToDownload.Add(RemoteLogFile); FilesToDownload.AddRange(Manifest.BuildProducts.Select(x => new FileReference(x))); DownloadFiles(FilesToDownload); } // Write out all the local manifests foreach (FileReference LocalManifestFile in LocalManifestFiles) { Log.TraceInformation("[Remote] Writing {0}", LocalManifestFile); Utils.WriteClass <BuildManifest>(Manifest, LocalManifestFile.FullName, ""); } return(true); }