/// <summary> /// Read a receipt from disk. /// </summary> /// <param name="Location">Filename to read from</param> /// <param name="EngineDir">Engine directory for expanded variables</param> public static TargetReceipt Read(FileReference Location, DirectoryReference EngineDir) { JsonObject RawObject = JsonObject.Read(Location); // Read the initial fields string TargetName = RawObject.GetStringField("TargetName"); TargetType TargetType = RawObject.GetEnumField <TargetType>("TargetType"); UnrealTargetPlatform Platform = UnrealTargetPlatform.Parse(RawObject.GetStringField("Platform")); UnrealTargetConfiguration Configuration = RawObject.GetEnumField <UnrealTargetConfiguration>("Configuration"); // Try to read the build version BuildVersion Version; if (!BuildVersion.TryParse(RawObject.GetObjectField("Version"), out Version)) { throw new JsonParseException("Invalid 'Version' field"); } // Read the project path FileReference ProjectFile; string RelativeProjectFile; if (RawObject.TryGetStringField("Project", out RelativeProjectFile)) { ProjectFile = FileReference.Combine(Location.Directory, RelativeProjectFile); } else { ProjectFile = null; } // Create the receipt TargetReceipt Receipt = new TargetReceipt(ProjectFile, TargetName, TargetType, Platform, Configuration, Version); // Get the project directory DirectoryReference ProjectDir = Receipt.ProjectDir; // Read the launch executable string Launch; if (RawObject.TryGetStringField("Launch", out Launch)) { Receipt.Launch = ExpandPathVariables(Launch, EngineDir, ProjectDir); } // Read the build products JsonObject[] BuildProductObjects; if (RawObject.TryGetObjectArrayField("BuildProducts", out BuildProductObjects)) { foreach (JsonObject BuildProductObject in BuildProductObjects) { string Path; BuildProductType Type; if (BuildProductObject.TryGetStringField("Path", out Path) && BuildProductObject.TryGetEnumField("Type", out Type)) { FileReference File = ExpandPathVariables(Path, EngineDir, ProjectDir); string Module; BuildProductObject.TryGetStringField("Module", out Module); Receipt.AddBuildProduct(File, Type); } } } // Read the runtime dependencies JsonObject[] RuntimeDependencyObjects; if (RawObject.TryGetObjectArrayField("RuntimeDependencies", out RuntimeDependencyObjects)) { foreach (JsonObject RuntimeDependencyObject in RuntimeDependencyObjects) { string Path; if (RuntimeDependencyObject.TryGetStringField("Path", out Path)) { FileReference File = ExpandPathVariables(Path, EngineDir, ProjectDir); StagedFileType Type; if (!RuntimeDependencyObject.TryGetEnumField("Type", out Type)) { // Previous format included an optional IgnoreIfMissing flag, which was only used for debug files. We can explicitly reference them as DebugNonUFS files now. bool bIgnoreIfMissing; if (RuntimeDependencyObject.TryGetBoolField("IgnoreIfMissing", out bIgnoreIfMissing)) { bIgnoreIfMissing = false; } Type = bIgnoreIfMissing? StagedFileType.DebugNonUFS : StagedFileType.NonUFS; } Receipt.RuntimeDependencies.Add(File, Type); } } } // Read the additional properties JsonObject[] AdditionalPropertyObjects; if (RawObject.TryGetObjectArrayField("AdditionalProperties", out AdditionalPropertyObjects)) { foreach (JsonObject AdditionalPropertyObject in AdditionalPropertyObjects) { string Name; if (AdditionalPropertyObject.TryGetStringField("Name", out Name)) { string Value; if (AdditionalPropertyObject.TryGetStringField("Value", out Value)) { Receipt.AdditionalProperties.Add(new ReceiptProperty(Name, Value)); } } } } return(Receipt); }
/// <summary> /// Constructor /// </summary> /// <param name="Inner">The writable build version instance</param> public ReadOnlyBuildVersion(BuildVersion Inner) { this.Inner = Inner; }
/// <summary> /// Constructor /// </summary> /// <param name="InProjectFile">Path to the project file for this target</param> /// <param name="InTargetName">The name of the target being compiled</param> /// <param name="InTargetType">The type of target</param> /// <param name="InPlatform">Platform for the target being compiled</param> /// <param name="InConfiguration">Configuration of the target being compiled</param> /// <param name="InVersion">Version information for the target</param> public TargetReceipt(FileReference InProjectFile, string InTargetName, TargetType InTargetType, UnrealTargetPlatform InPlatform, UnrealTargetConfiguration InConfiguration, BuildVersion InVersion) { ProjectFile = InProjectFile; ProjectDir = DirectoryReference.FromFile(InProjectFile); TargetName = InTargetName; Platform = InPlatform; Configuration = InConfiguration; TargetType = InTargetType; Version = InVersion; }
public static void WriteDocumentation(Type RulesType, FileReference OutputFile) { // Get the path to the XML documentation FileReference InputDocumentationFile = new FileReference(Assembly.GetExecutingAssembly().Location).ChangeExtension(".xml"); if (!FileReference.Exists(InputDocumentationFile)) { throw new BuildException("Generated assembly documentation not found at {0}.", InputDocumentationFile); } // Get the current engine version for versioning the page BuildVersion Version; if (!BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out Version)) { throw new BuildException("Unable to read the current build version"); } // Read the documentation XmlDocument InputDocumentation = new XmlDocument(); InputDocumentation.Load(InputDocumentationFile.FullName); // Filter the properties into read-only and read/write lists List <FieldInfo> ReadOnlyFields = new List <FieldInfo>(); List <FieldInfo> ReadWriteFields = new List <FieldInfo>(); foreach (FieldInfo Field in RulesType.GetFields(BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.Public)) { if (!Field.FieldType.IsClass || !Field.FieldType.Name.EndsWith("TargetRules")) { if (Field.IsInitOnly) { ReadOnlyFields.Add(Field); } else { ReadWriteFields.Add(Field); } } } // Make sure the output file is writable FileReference.MakeWriteable(OutputFile); // Generate the UDN documentation file using (StreamWriter Writer = new StreamWriter(OutputFile.FullName)) { Writer.WriteLine("Availability: NoPublish"); Writer.WriteLine("Title: Build Configuration Properties Page"); Writer.WriteLine("Crumbs:"); Writer.WriteLine("Description: This is a procedurally generated markdown page."); Writer.WriteLine("Version: {0}.{1}", Version.MajorVersion, Version.MinorVersion); Writer.WriteLine(""); if (ReadOnlyFields.Count > 0) { Writer.WriteLine("### Read-Only Properties"); Writer.WriteLine(); foreach (FieldInfo Field in ReadOnlyFields) { OutputField(InputDocumentation, Field, Writer); } Writer.WriteLine(); } if (ReadWriteFields.Count > 0) { Writer.WriteLine("### Read/Write Properties"); foreach (FieldInfo Field in ReadWriteFields) { OutputField(InputDocumentation, Field, Writer); } Writer.WriteLine(""); } } // Success! Log.TraceInformation("Written documentation to {0}.", OutputFile); }
/// <summary> /// Loads a UBTMakefile from disk /// </summary> /// <param name="MakefilePath">Path to the makefile to load</param> /// <param name="ProjectFile">Path to the project file</param> /// <param name="ReasonNotLoaded">If the function returns null, this string will contain the reason why</param> /// <param name="WorkingSet">Interface to query which source files are in the working set</param> /// <returns>The loaded makefile, or null if it failed for some reason. On failure, the 'ReasonNotLoaded' variable will contain information about why</returns> public static UBTMakefile LoadUBTMakefile(FileReference MakefilePath, FileReference ProjectFile, ISourceFileWorkingSet WorkingSet, out string ReasonNotLoaded) { // Check the directory timestamp on the project files directory. If the user has generated project files more // recently than the UBTMakefile, then we need to consider the file to be out of date FileInfo UBTMakefileInfo = new FileInfo(MakefilePath.FullName); if (!UBTMakefileInfo.Exists) { // UBTMakefile doesn't even exist, so we won't bother loading it ReasonNotLoaded = "no existing makefile"; return(null); } // Check the build version FileInfo BuildVersionFileInfo = new FileInfo(BuildVersion.GetDefaultFileName().FullName); if (BuildVersionFileInfo.Exists && UBTMakefileInfo.LastWriteTime.CompareTo(BuildVersionFileInfo.LastWriteTime) < 0) { Log.TraceVerbose("Existing makefile is older than Build.version, ignoring it"); ReasonNotLoaded = "Build.version is newer"; return(null); } // @todo ubtmake: This will only work if the directory timestamp actually changes with every single GPF. Force delete existing files before creating new ones? Eh... really we probably just want to delete + create a file in that folder // -> UPDATE: Seems to work OK right now though on Windows platform, maybe due to GUID changes // @todo ubtmake: Some platforms may not save any files into this folder. We should delete + generate a "touch" file to force the directory timestamp to be updated (or just check the timestamp file itself. We could put it ANYWHERE, actually) // Installed Build doesn't need to check engine projects for outdatedness if (!UnrealBuildTool.IsEngineInstalled()) { if (DirectoryReference.Exists(ProjectFileGenerator.IntermediateProjectFilesPath)) { DateTime EngineProjectFilesLastUpdateTime = new FileInfo(ProjectFileGenerator.ProjectTimestampFile).LastWriteTime; if (UBTMakefileInfo.LastWriteTime.CompareTo(EngineProjectFilesLastUpdateTime) < 0) { // Engine project files are newer than UBTMakefile Log.TraceVerbose("Existing makefile is older than generated engine project files, ignoring it"); ReasonNotLoaded = "project files are newer"; return(null); } } } // Check the game project directory too if (ProjectFile != null) { string ProjectFilename = ProjectFile.FullName; FileInfo ProjectFileInfo = new FileInfo(ProjectFilename); if (!ProjectFileInfo.Exists || UBTMakefileInfo.LastWriteTime.CompareTo(ProjectFileInfo.LastWriteTime) < 0) { // .uproject file is newer than UBTMakefile Log.TraceVerbose("Makefile is older than .uproject file, ignoring it"); ReasonNotLoaded = ".uproject file is newer"; return(null); } DirectoryReference MasterProjectRelativePath = ProjectFile.Directory; string GameIntermediateProjectFilesPath = Path.Combine(MasterProjectRelativePath.FullName, "Intermediate", "ProjectFiles"); if (Directory.Exists(GameIntermediateProjectFilesPath)) { DateTime GameProjectFilesLastUpdateTime = new DirectoryInfo(GameIntermediateProjectFilesPath).LastWriteTime; if (UBTMakefileInfo.LastWriteTime.CompareTo(GameProjectFilesLastUpdateTime) < 0) { // Game project files are newer than UBTMakefile Log.TraceVerbose("Makefile is older than generated game project files, ignoring it"); ReasonNotLoaded = "game project files are newer"; return(null); } } } // Check to see if UnrealBuildTool.exe was compiled more recently than the UBTMakefile DateTime UnrealBuildToolTimestamp = new FileInfo(Assembly.GetExecutingAssembly().Location).LastWriteTime; if (UBTMakefileInfo.LastWriteTime.CompareTo(UnrealBuildToolTimestamp) < 0) { // UnrealBuildTool.exe was compiled more recently than the UBTMakefile Log.TraceVerbose("Makefile is older than UnrealBuildTool.exe, ignoring it"); ReasonNotLoaded = "UnrealBuildTool.exe is newer"; return(null); } // Check to see if any BuildConfiguration files have changed since the last build List <XmlConfig.InputFile> InputFiles = XmlConfig.FindInputFiles(); foreach (XmlConfig.InputFile InputFile in InputFiles) { FileInfo InputFileInfo = new FileInfo(InputFile.Location.FullName); if (InputFileInfo.LastWriteTime > UBTMakefileInfo.LastWriteTime) { Log.TraceVerbose("Makefile is older than BuildConfiguration.xml, ignoring it"); ReasonNotLoaded = "BuildConfiguration.xml is newer"; return(null); } } UBTMakefile LoadedUBTMakefile = null; try { DateTime LoadUBTMakefileStartTime = DateTime.UtcNow; using (FileStream Stream = new FileStream(UBTMakefileInfo.FullName, FileMode.Open, FileAccess.Read)) { BinaryFormatter Formatter = new BinaryFormatter(); LoadedUBTMakefile = Formatter.Deserialize(Stream) as UBTMakefile; } if (UnrealBuildTool.bPrintPerformanceInfo) { double LoadUBTMakefileTime = (DateTime.UtcNow - LoadUBTMakefileStartTime).TotalSeconds; Log.TraceInformation("LoadUBTMakefile took " + LoadUBTMakefileTime + "s"); } } catch (Exception Ex) { Log.TraceWarning("Failed to read makefile: {0}", Ex.Message); ReasonNotLoaded = "couldn't read existing makefile"; return(null); } if (!LoadedUBTMakefile.IsValidMakefile()) { Log.TraceWarning("Loaded makefile appears to have invalid contents, ignoring it ({0})", UBTMakefileInfo.FullName); ReasonNotLoaded = "existing makefile appears to be invalid"; return(null); } // Check if any of the target's Build.cs files are newer than the makefile foreach (UEBuildTarget Target in LoadedUBTMakefile.Targets) { string TargetCsFilename = Target.TargetRulesFile.FullName; if (TargetCsFilename != null) { FileInfo TargetCsFile = new FileInfo(TargetCsFilename); bool bTargetCsFileExists = TargetCsFile.Exists; if (!bTargetCsFileExists || TargetCsFile.LastWriteTime > UBTMakefileInfo.LastWriteTime) { Log.TraceVerbose("{0} has been {1} since makefile was built, ignoring it ({2})", TargetCsFilename, bTargetCsFileExists ? "changed" : "deleted", UBTMakefileInfo.FullName); ReasonNotLoaded = string.Format("changes to target files"); return(null); } } IEnumerable <string> BuildCsFilenames = Target.GetAllModuleBuildCsFilenames(); foreach (string BuildCsFilename in BuildCsFilenames) { if (BuildCsFilename != null) { FileInfo BuildCsFile = new FileInfo(BuildCsFilename); bool bBuildCsFileExists = BuildCsFile.Exists; if (!bBuildCsFileExists || BuildCsFile.LastWriteTime > UBTMakefileInfo.LastWriteTime) { Log.TraceVerbose("{0} has been {1} since makefile was built, ignoring it ({2})", BuildCsFilename, bBuildCsFileExists ? "changed" : "deleted", UBTMakefileInfo.FullName); ReasonNotLoaded = string.Format("changes to module files"); return(null); } } } foreach (FlatModuleCsDataType FlatCsModuleData in Target.FlatModuleCsData.Values) { if (FlatCsModuleData.BuildCsFilename != null && FlatCsModuleData.ExternalDependencies.Count > 0) { string BaseDir = Path.GetDirectoryName(FlatCsModuleData.BuildCsFilename); foreach (string ExternalDependency in FlatCsModuleData.ExternalDependencies) { FileInfo DependencyFile = new FileInfo(Path.Combine(BaseDir, ExternalDependency)); bool bDependencyFileExists = DependencyFile.Exists; if (!bDependencyFileExists || DependencyFile.LastWriteTime > UBTMakefileInfo.LastWriteTime) { Log.TraceVerbose("{0} has been {1} since makefile was built, ignoring it ({2})", DependencyFile.FullName, bDependencyFileExists ? "changed" : "deleted", UBTMakefileInfo.FullName); ReasonNotLoaded = string.Format("changes to external dependency"); return(null); } } } } } // We do a check to see if any modules' headers have changed which have // acquired or lost UHT types. If so, which should be rare, // we'll just invalidate the entire makefile and force it to be rebuilt. foreach (UEBuildTarget Target in LoadedUBTMakefile.Targets) { // Get all H files in processed modules newer than the makefile itself HashSet <string> HFilesNewerThanMakefile = new HashSet <string>( Target.FlatModuleCsData .SelectMany(x => x.Value.ModuleSourceFolder != null ? Directory.EnumerateFiles(x.Value.ModuleSourceFolder.FullName, "*.h", SearchOption.AllDirectories) : Enumerable.Empty <string>()) .Where(y => Directory.GetLastWriteTimeUtc(y) > UBTMakefileInfo.LastWriteTimeUtc) .OrderBy(z => z).Distinct() ); // Get all H files in all modules processed in the last makefile build HashSet <string> AllUHTHeaders = new HashSet <string>(Target.FlatModuleCsData.Select(x => x.Value).SelectMany(x => x.UHTHeaderNames)); // Check whether any headers have been deleted. If they have, we need to regenerate the makefile since the module might now be empty. If we don't, // and the file has been moved to a different module, we may include stale generated headers. foreach (string FileName in AllUHTHeaders) { if (!File.Exists(FileName)) { Log.TraceVerbose("File processed by UHT was deleted ({0}); invalidating makefile", FileName); ReasonNotLoaded = string.Format("UHT file was deleted"); return(null); } } // Makefile is invalid if: // * There are any newer files which contain no UHT data, but were previously in the makefile // * There are any newer files contain data which needs processing by UHT, but weren't not previously in the makefile foreach (string Filename in HFilesNewerThanMakefile) { bool bContainsUHTData = CPPHeaders.DoesFileContainUObjects(Filename); bool bWasProcessed = AllUHTHeaders.Contains(Filename); if (bContainsUHTData != bWasProcessed) { Log.TraceVerbose("{0} {1} contain UHT types and now {2} , ignoring it ({3})", Filename, bWasProcessed ? "used to" : "didn't", bWasProcessed ? "doesn't" : "does", UBTMakefileInfo.FullName); ReasonNotLoaded = string.Format("new files with reflected types"); return(null); } } } // If adaptive unity build is enabled, do a check to see if there are any source files that became part of the // working set since the Makefile was created (or, source files were removed from the working set.) If anything // changed, then we'll force a new Makefile to be created so that we have fresh unity build blobs. We always // want to make sure that source files in the working set are excluded from those unity blobs (for fastest possible // iteration times.) if (LoadedUBTMakefile.bUseAdaptiveUnityBuild) { // Check if any source files in the working set no longer belong in it foreach (FileItem SourceFile in LoadedUBTMakefile.SourceFileWorkingSet) { if (!WorkingSet.Contains(SourceFile.Location) && File.GetLastWriteTimeUtc(SourceFile.AbsolutePath) > UBTMakefileInfo.LastWriteTimeUtc) { Log.TraceVerbose("{0} was part of source working set and now is not; invalidating makefile ({1})", SourceFile.AbsolutePath, UBTMakefileInfo.FullName); ReasonNotLoaded = string.Format("working set of source files changed"); return(null); } } // Check if any source files that are eligible for being in the working set have been modified foreach (FileItem SourceFile in LoadedUBTMakefile.CandidateSourceFilesForWorkingSet) { if (WorkingSet.Contains(SourceFile.Location) && File.GetLastWriteTimeUtc(SourceFile.AbsolutePath) > UBTMakefileInfo.LastWriteTimeUtc) { Log.TraceVerbose("{0} was part of source working set and now is not; invalidating makefile ({1})", SourceFile.AbsolutePath, UBTMakefileInfo.FullName); ReasonNotLoaded = string.Format("working set of source files changed"); return(null); } } } ReasonNotLoaded = null; return(LoadedUBTMakefile); }
/// <summary> /// Read a receipt from disk. /// </summary> /// <param name="FileName">Filename to read from</param> public static TargetReceipt Read(string FileName) { JsonObject RawObject = JsonObject.Read(FileName); // Read the initial fields string TargetName = RawObject.GetStringField("TargetName"); UnrealTargetPlatform Platform = RawObject.GetEnumField <UnrealTargetPlatform>("Platform"); UnrealTargetConfiguration Configuration = RawObject.GetEnumField <UnrealTargetConfiguration>("Configuration"); string BuildId = RawObject.GetStringField("BuildId"); // Try to read the build version BuildVersion Version; if (!BuildVersion.TryParse(RawObject.GetObjectField("Version"), out Version)) { throw new JsonParseException("Invalid 'Version' field"); } // Create the receipt TargetReceipt Receipt = new TargetReceipt(TargetName, Platform, Configuration, BuildId, Version); // Read the build products JsonObject[] BuildProductObjects; if (RawObject.TryGetObjectArrayField("BuildProducts", out BuildProductObjects)) { foreach (JsonObject BuildProductObject in BuildProductObjects) { string Path; BuildProductType Type; if (BuildProductObject.TryGetStringField("Path", out Path) && BuildProductObject.TryGetEnumField("Type", out Type)) { string Module; BuildProductObject.TryGetStringField("Module", out Module); BuildProduct NewBuildProduct = Receipt.AddBuildProduct(Path, Type); bool IsPrecompiled; if (BuildProductObject.TryGetBoolField("IsPrecompiled", out IsPrecompiled)) { NewBuildProduct.IsPrecompiled = IsPrecompiled; } } } } // Read the runtime dependencies JsonObject[] RuntimeDependencyObjects; if (RawObject.TryGetObjectArrayField("RuntimeDependencies", out RuntimeDependencyObjects)) { foreach (JsonObject RuntimeDependencyObject in RuntimeDependencyObjects) { string Path; if (RuntimeDependencyObject.TryGetStringField("Path", out Path)) { StagedFileType Type; if (!RuntimeDependencyObject.TryGetEnumField("Type", out Type)) { // Previous format included an optional IgnoreIfMissing flag, which was only used for debug files. We can explicitly reference them as DebugNonUFS files now. bool bIgnoreIfMissing; if (RuntimeDependencyObject.TryGetBoolField("IgnoreIfMissing", out bIgnoreIfMissing)) { bIgnoreIfMissing = false; } Type = bIgnoreIfMissing? StagedFileType.DebugNonUFS : StagedFileType.NonUFS; } Receipt.RuntimeDependencies.Add(Path, Type); } } } // Read the additional properties JsonObject[] AdditionalPropertyObjects; if (RawObject.TryGetObjectArrayField("AdditionalProperties", out AdditionalPropertyObjects)) { foreach (JsonObject AdditionalPropertyObject in AdditionalPropertyObjects) { string Name; if (AdditionalPropertyObject.TryGetStringField("Name", out Name)) { string Value; if (AdditionalPropertyObject.TryGetStringField("Value", out Value)) { Receipt.AdditionalProperties.Add(new ReceiptProperty(Name, Value)); } } } } // Read the precompiled dependencies string[] PrecompiledBuildDependencies; if (RawObject.TryGetStringArrayField("PrecompiledBuildDependencies", out PrecompiledBuildDependencies)) { Receipt.PrecompiledBuildDependencies.UnionWith(PrecompiledBuildDependencies); } // Read the precompiled dependencies string[] PrecompiledRuntimeDependencies; if (RawObject.TryGetStringArrayField("PrecompiledRuntimeDependencies", out PrecompiledRuntimeDependencies)) { Receipt.PrecompiledRuntimeDependencies.UnionWith(PrecompiledRuntimeDependencies); } return(Receipt); }
/// <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> /// Loads a makefile from disk /// </summary> /// <param name="MakefilePath">Path to the makefile to load</param> /// <param name="ProjectFile">Path to the project file</param> /// <param name="Platform">Platform for this makefile</param> /// <param name="Arguments">Command line arguments for this target</param> /// <param name="ReasonNotLoaded">If the function returns null, this string will contain the reason why</param> /// <returns>The loaded makefile, or null if it failed for some reason. On failure, the 'ReasonNotLoaded' variable will contain information about why</returns> public static TargetMakefile Load(FileReference MakefilePath, FileReference ProjectFile, UnrealTargetPlatform Platform, string[] Arguments, out string ReasonNotLoaded) { using (Timeline.ScopeEvent("Checking dependent timestamps")) { // Check the directory timestamp on the project files directory. If the user has generated project files more recently than the makefile, then we need to consider the file to be out of date FileInfo MakefileInfo = new FileInfo(MakefilePath.FullName); if (!MakefileInfo.Exists) { // Makefile doesn't even exist, so we won't bother loading it ReasonNotLoaded = "no existing makefile"; return(null); } // Check the build version FileInfo BuildVersionFileInfo = new FileInfo(BuildVersion.GetDefaultFileName().FullName); if (BuildVersionFileInfo.Exists && MakefileInfo.LastWriteTime.CompareTo(BuildVersionFileInfo.LastWriteTime) < 0) { Log.TraceLog("Existing makefile is older than Build.version, ignoring it"); ReasonNotLoaded = "Build.version is newer"; return(null); } // @todo ubtmake: This will only work if the directory timestamp actually changes with every single GPF. Force delete existing files before creating new ones? Eh... really we probably just want to delete + create a file in that folder // -> UPDATE: Seems to work OK right now though on Windows platform, maybe due to GUID changes // @todo ubtmake: Some platforms may not save any files into this folder. We should delete + generate a "touch" file to force the directory timestamp to be updated (or just check the timestamp file itself. We could put it ANYWHERE, actually) // Installed Build doesn't need to check engine projects for outdatedness if (!UnrealBuildTool.IsEngineInstalled()) { if (DirectoryReference.Exists(ProjectFileGenerator.IntermediateProjectFilesPath)) { DateTime EngineProjectFilesLastUpdateTime = new FileInfo(ProjectFileGenerator.ProjectTimestampFile).LastWriteTime; if (MakefileInfo.LastWriteTime.CompareTo(EngineProjectFilesLastUpdateTime) < 0) { // Engine project files are newer than makefile Log.TraceLog("Existing makefile is older than generated engine project files, ignoring it"); ReasonNotLoaded = "project files are newer"; return(null); } } } // Check the game project directory too if (ProjectFile != null) { string ProjectFilename = ProjectFile.FullName; FileInfo ProjectFileInfo = new FileInfo(ProjectFilename); if (!ProjectFileInfo.Exists || MakefileInfo.LastWriteTime.CompareTo(ProjectFileInfo.LastWriteTime) < 0) { // .uproject file is newer than makefile Log.TraceLog("Makefile is older than .uproject file, ignoring it"); ReasonNotLoaded = ".uproject file is newer"; return(null); } DirectoryReference MasterProjectRelativePath = ProjectFile.Directory; string GameIntermediateProjectFilesPath = Path.Combine(MasterProjectRelativePath.FullName, "Intermediate", "ProjectFiles"); if (Directory.Exists(GameIntermediateProjectFilesPath)) { DateTime GameProjectFilesLastUpdateTime = new DirectoryInfo(GameIntermediateProjectFilesPath).LastWriteTime; if (MakefileInfo.LastWriteTime.CompareTo(GameProjectFilesLastUpdateTime) < 0) { // Game project files are newer than makefile Log.TraceLog("Makefile is older than generated game project files, ignoring it"); ReasonNotLoaded = "game project files are newer"; return(null); } } } // Check to see if UnrealBuildTool.exe was compiled more recently than the makefile DateTime UnrealBuildToolTimestamp = new FileInfo(Assembly.GetExecutingAssembly().Location).LastWriteTime; if (MakefileInfo.LastWriteTime.CompareTo(UnrealBuildToolTimestamp) < 0) { // UnrealBuildTool.exe was compiled more recently than the makefile Log.TraceLog("Makefile is older than UnrealBuildTool.exe, ignoring it"); ReasonNotLoaded = "UnrealBuildTool.exe is newer"; return(null); } // Check to see if any BuildConfiguration files have changed since the last build List <XmlConfig.InputFile> InputFiles = XmlConfig.FindInputFiles(); foreach (XmlConfig.InputFile InputFile in InputFiles) { FileInfo InputFileInfo = new FileInfo(InputFile.Location.FullName); if (InputFileInfo.LastWriteTime > MakefileInfo.LastWriteTime) { Log.TraceLog("Makefile is older than BuildConfiguration.xml, ignoring it"); ReasonNotLoaded = "BuildConfiguration.xml is newer"; return(null); } } } TargetMakefile Makefile; using (Timeline.ScopeEvent("Loading makefile")) { try { using (BinaryArchiveReader Reader = new BinaryArchiveReader(MakefilePath)) { int Version = Reader.ReadInt(); if (Version != CurrentVersion) { ReasonNotLoaded = "makefile version does not match"; return(null); } Makefile = new TargetMakefile(Reader); } } catch (Exception Ex) { Log.TraceWarning("Failed to read makefile: {0}", Ex.Message); Log.TraceLog("Exception: {0}", Ex.ToString()); ReasonNotLoaded = "couldn't read existing makefile"; return(null); } } using (Timeline.ScopeEvent("Checking makefile validity")) { // Check if the arguments are different if (!Enumerable.SequenceEqual(Makefile.AdditionalArguments, Arguments)) { ReasonNotLoaded = "command line arguments changed"; return(null); } // Check if ini files are newer. Ini files contain build settings too. DirectoryReference ProjectDirectory = DirectoryReference.FromFile(ProjectFile); foreach (ConfigHierarchyType IniType in (ConfigHierarchyType[])Enum.GetValues(typeof(ConfigHierarchyType))) { foreach (FileReference IniFilename in ConfigHierarchy.EnumerateConfigFileLocations(IniType, ProjectDirectory, Platform)) { FileInfo IniFileInfo = new FileInfo(IniFilename.FullName); if (IniFileInfo.LastWriteTimeUtc > Makefile.CreateTimeUtc) { // Ini files are newer than makefile ReasonNotLoaded = "ini files are newer than makefile"; return(null); } } } // Get the current build metadata from the platform string CurrentExternalMetadata = UEBuildPlatform.GetBuildPlatform(Platform).GetExternalBuildMetadata(ProjectFile); if (String.Compare(CurrentExternalMetadata, Makefile.ExternalMetadata, StringComparison.Ordinal) != 0) { Log.TraceLog("Old metadata:\n", Makefile.ExternalMetadata); Log.TraceLog("New metadata:\n", CurrentExternalMetadata); ReasonNotLoaded = "build metadata has changed"; return(null); } } // The makefile is ok ReasonNotLoaded = null; return(Makefile); }
/// <summary> /// Constructor /// </summary> /// <param name="InTargetName">The name of the target being compiled</param> /// <param name="InPlatform">Platform for the target being compiled</param> /// <param name="InConfiguration">Configuration of the target being compiled</param> public TargetReceipt(string InTargetName, UnrealTargetPlatform InPlatform, UnrealTargetConfiguration InConfiguration, string InBuildId, BuildVersion InVersion) { TargetName = InTargetName; Platform = InPlatform; Configuration = InConfiguration; BuildId = InBuildId; Version = InVersion; }
/// <summary> /// Generates documentation files for the available settings, by merging the XML documentation from the compiler. /// </summary> /// <param name="OutputFile">The documentation file to write</param> public static void WriteDocumentation(FileReference OutputFile) { // Find all the configurable types List <Type> ConfigTypes = FindConfigurableTypes(); // Find all the configurable fields from the given types Dictionary <string, Dictionary <string, FieldInfo> > CategoryToFields = new Dictionary <string, Dictionary <string, FieldInfo> >(); FindConfigurableFields(ConfigTypes, CategoryToFields); // Get the path to the XML documentation FileReference InputDocumentationFile = new FileReference(Assembly.GetExecutingAssembly().Location).ChangeExtension(".xml"); if (!FileReference.Exists(InputDocumentationFile)) { throw new BuildException("Generated assembly documentation not found at {0}.", InputDocumentationFile); } // Get the current engine version for versioning the page BuildVersion Version; if (!BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out Version)) { throw new BuildException("Unable to read the current build version"); } // Read the documentation XmlDocument InputDocumentation = new XmlDocument(); InputDocumentation.Load(InputDocumentationFile.FullName); // Make sure we can write to the output file FileReference.MakeWriteable(OutputFile); // Generate the UDN documentation file using (StreamWriter Writer = new StreamWriter(OutputFile.FullName)) { Writer.WriteLine("Availability: NoPublish"); Writer.WriteLine("Title: Build Configuration Properties Page"); Writer.WriteLine("Crumbs:"); Writer.WriteLine("Description: This is a procedurally generated markdown page."); Writer.WriteLine("Version: {0}.{1}", Version.MajorVersion, Version.MinorVersion); Writer.WriteLine(""); foreach (KeyValuePair <string, Dictionary <string, FieldInfo> > CategoryPair in CategoryToFields) { string CategoryName = CategoryPair.Key; Writer.WriteLine("### {0}", CategoryName); Writer.WriteLine(); Dictionary <string, FieldInfo> Fields = CategoryPair.Value; foreach (KeyValuePair <string, FieldInfo> FieldPair in Fields) { string FieldName = FieldPair.Key; FieldInfo Field = FieldPair.Value; XmlNode Node = InputDocumentation.SelectSingleNode(String.Format("//member[@name='F:{0}.{1}']/summary", Field.DeclaringType.FullName, Field.Name)); if (Node != null) { // Reflow the comments into paragraphs, assuming that each paragraph will be separated by a blank line List <string> Lines = new List <string>(Node.InnerText.Trim().Split('\n').Select(x => x.Trim())); for (int Idx = Lines.Count - 1; Idx > 0; Idx--) { if (Lines[Idx - 1].Length > 0 && !Lines[Idx].StartsWith("*") && !Lines[Idx].StartsWith("-")) { Lines[Idx - 1] += " " + Lines[Idx]; Lines.RemoveAt(Idx); } } // Write the result to the .udn file if (Lines.Count > 0) { Writer.WriteLine("$ {0} : {1}", FieldName, Lines[0]); for (int Idx = 1; Idx < Lines.Count; Idx++) { if (Lines[Idx].StartsWith("*") || Lines[Idx].StartsWith("-")) { Writer.WriteLine(" * {0}", Lines[Idx].Substring(1).TrimStart()); } else { Writer.WriteLine(" * {0}", Lines[Idx]); } } Writer.WriteLine(); } } } } } // Success! Log.TraceInformation("Written documentation to {0}.", OutputFile); }
/// <summary> /// Execute the command, having obtained the appropriate mutex /// </summary> /// <param name="Arguments">Command line arguments</param> /// <returns>Exit code</returns> private int ExecuteInternal(CommandLineArguments Arguments) { // Read the target info WriteMetadataTargetInfo TargetInfo = BinaryFormatterUtils.Load <WriteMetadataTargetInfo>(Arguments.GetFileReference("-Input=")); bool bNoManifestChanges = Arguments.HasOption("-NoManifestChanges"); int VersionNumber = Arguments.GetInteger("-Version="); Arguments.CheckAllArgumentsUsed(); // Make sure the version number is correct if (VersionNumber != CurrentVersionNumber) { throw new BuildException("Version number to WriteMetadataMode is incorrect (expected {0}, got {1})", CurrentVersionNumber, VersionNumber); } // Check if we need to set a build id TargetReceipt Receipt = TargetInfo.Receipt; if (String.IsNullOrEmpty(Receipt.Version.BuildId)) { // Check if there's an existing version file. If it exists, try to merge in any manifests that are valid (and reuse the existing build id) BuildVersion PreviousVersion; if (TargetInfo.VersionFile != null && BuildVersion.TryRead(TargetInfo.VersionFile, out PreviousVersion)) { // Check if we can reuse the existing manifests. This prevents unnecessary builds when switching between projects. Dictionary <FileReference, ModuleManifest> PreviousFileToManifest = new Dictionary <FileReference, ModuleManifest>(); if (TryRecyclingManifests(PreviousVersion.BuildId, TargetInfo.FileToManifest.Keys, PreviousFileToManifest)) { // Merge files from the existing manifests with the new ones foreach (KeyValuePair <FileReference, ModuleManifest> Pair in PreviousFileToManifest) { ModuleManifest TargetManifest = TargetInfo.FileToManifest[Pair.Key]; MergeManifests(Pair.Value, TargetManifest); } // Update the build id to use the current one Receipt.Version.BuildId = PreviousVersion.BuildId; } } // If the build id is still not set, generate a new one from a GUID if (String.IsNullOrEmpty(Receipt.Version.BuildId)) { Receipt.Version.BuildId = Guid.NewGuid().ToString(); } } else { // Read all the manifests and merge them into the new ones, if they have the same build id foreach (KeyValuePair <FileReference, ModuleManifest> Pair in TargetInfo.FileToManifest) { ModuleManifest SourceManifest; if (TryReadManifest(Pair.Key, out SourceManifest) && SourceManifest.BuildId == Receipt.Version.BuildId) { MergeManifests(SourceManifest, Pair.Value); } } } // Update the build id in all the manifests, and write them out foreach (KeyValuePair <FileReference, ModuleManifest> Pair in TargetInfo.FileToManifest) { FileReference ManifestFile = Pair.Key; if (!UnrealBuildTool.IsFileInstalled(ManifestFile)) { ModuleManifest Manifest = Pair.Value; Manifest.BuildId = Receipt.Version.BuildId; if (!FileReference.Exists(ManifestFile)) { // If the file doesn't already exist, just write it out DirectoryReference.CreateDirectory(ManifestFile.Directory); Manifest.Write(ManifestFile); } else { // Otherwise write it to a buffer first string OutputText; using (StringWriter Writer = new StringWriter()) { Manifest.Write(Writer); OutputText = Writer.ToString(); } // And only write it to disk if it's been modified. Note that if a manifest is out of date, we should have generated a new build id causing the contents to differ. string CurrentText = FileReference.ReadAllText(ManifestFile); if (CurrentText != OutputText) { if (bNoManifestChanges) { Log.TraceError("Build modifies {0}. This is not permitted. Before:\n {1}\nAfter:\n {2}", ManifestFile, CurrentText.Replace("\n", "\n "), OutputText.Replace("\n", "\n ")); } else { FileReference.WriteAllText(ManifestFile, OutputText); } } } } } // Write out the version file, if it's changed. Since this file is next to the executable, it may be used by multiple targets, and we should avoid modifying it unless necessary. if (TargetInfo.VersionFile != null && !UnrealBuildTool.IsFileInstalled(TargetInfo.VersionFile)) { DirectoryReference.CreateDirectory(TargetInfo.VersionFile.Directory); StringWriter Writer = new StringWriter(); Receipt.Version.Write(Writer); string Text = Writer.ToString(); if (!FileReference.Exists(TargetInfo.VersionFile) || File.ReadAllText(TargetInfo.VersionFile.FullName) != Text) { File.WriteAllText(TargetInfo.VersionFile.FullName, Text); } } // Write out the receipt if (!UnrealBuildTool.IsFileInstalled(TargetInfo.ReceiptFile)) { DirectoryReference.CreateDirectory(TargetInfo.ReceiptFile.Directory); Receipt.Write(TargetInfo.ReceiptFile); } return(0); }
/// <summary> /// Constructor. Compiles a rules assembly from the given source files. /// </summary> /// <param name="Plugins">All the plugins included in this assembly</param> /// <param name="ModuleFiles">List of module files to compile</param> /// <param name="TargetFiles">List of target files to compile</param> /// <param name="ModuleFileToPluginInfo">Mapping of module file to the plugin that contains it</param> /// <param name="AssemblyFileName">The output path for the compiled assembly</param> /// <param name="Parent">The parent rules assembly</param> public RulesAssembly(IReadOnlyList <PluginInfo> Plugins, List <FileReference> ModuleFiles, List <FileReference> TargetFiles, Dictionary <FileReference, PluginInfo> ModuleFileToPluginInfo, FileReference AssemblyFileName, RulesAssembly Parent) { this.Plugins = Plugins; this.ModuleFileToPluginInfo = ModuleFileToPluginInfo; this.Parent = Parent; // Find all the source files List <FileReference> AssemblySourceFiles = new List <FileReference>(); AssemblySourceFiles.AddRange(ModuleFiles); AssemblySourceFiles.AddRange(TargetFiles); // Compile the assembly if (AssemblySourceFiles.Count > 0) { List <string> PreprocessorDefines = new List <string>(); PreprocessorDefines.Add("WITH_FORWARDED_MODULE_RULES_CTOR"); PreprocessorDefines.Add("WITH_FORWARDED_TARGET_RULES_CTOR"); // Define macros for the UE4 version, starting with 4.17 BuildVersion Version; if (BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out Version)) { for (int MinorVersion = 17; MinorVersion <= Version.MinorVersion; MinorVersion++) { PreprocessorDefines.Add(String.Format("UE_4_{0}_OR_LATER", MinorVersion)); } } CompiledAssembly = DynamicCompilation.CompileAndLoadAssembly(AssemblyFileName, AssemblySourceFiles, PreprocessorDefines: PreprocessorDefines); } // Setup the module map foreach (FileReference ModuleFile in ModuleFiles) { string ModuleName = ModuleFile.GetFileNameWithoutAnyExtensions(); if (!ModuleNameToModuleFile.ContainsKey(ModuleName)) { ModuleNameToModuleFile.Add(ModuleName, ModuleFile); } } // Setup the target map foreach (FileReference TargetFile in TargetFiles) { string TargetName = TargetFile.GetFileNameWithoutAnyExtensions(); if (!TargetNameToTargetFile.ContainsKey(TargetName)) { TargetNameToTargetFile.Add(TargetName, TargetFile); } } // Write any deprecation warnings for methods overriden from a base with the [ObsoleteOverride] attribute. Unlike the [Obsolete] attribute, this ensures the message // is given because the method is implemented, not because it's called. if (CompiledAssembly != null) { foreach (Type CompiledType in CompiledAssembly.GetTypes()) { foreach (MethodInfo Method in CompiledType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)) { ObsoleteOverrideAttribute Attribute = Method.GetCustomAttribute <ObsoleteOverrideAttribute>(true); if (Attribute != null) { FileReference Location; if (!TryGetFileNameFromType(CompiledType, out Location)) { Location = new FileReference(CompiledAssembly.Location); } Log.TraceWarning("{0}: warning: {1}", Location, Attribute.Message); } } if (CompiledType.BaseType == typeof(ModuleRules)) { ConstructorInfo Constructor = CompiledType.GetConstructor(new Type[] { typeof(TargetInfo) }); if (Constructor != null) { FileReference Location; if (!TryGetFileNameFromType(CompiledType, out Location)) { Location = new FileReference(CompiledAssembly.Location); } Log.TraceWarning("{0}: warning: Module constructors should take a ReadOnlyTargetRules argument (rather than a TargetInfo argument) and pass it to the base class constructor from 4.15 onwards. Please update the method signature.", Location); } } } } }
/// <summary> /// Try to read a version file from disk /// </summary> /// <param name="Version">The version information</param> /// <returns>True if the version was read sucessfully, false otherwise</returns> public static bool TryRead(out BuildVersion Version) { return(TryRead(GetDefaultFileName(), out Version)); }
/// <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> /// 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); }
/// <summary> /// Read a receipt from disk. /// </summary> /// <param name="FileName">Filename to read from</param> public static TargetReceipt Read(string FileName) { JsonObject RawObject = JsonObject.Read(FileName); // Read the initial fields string TargetName = RawObject.GetStringField("TargetName"); UnrealTargetPlatform Platform = RawObject.GetEnumField <UnrealTargetPlatform>("Platform"); UnrealTargetConfiguration Configuration = RawObject.GetEnumField <UnrealTargetConfiguration>("Configuration"); string BuildId = RawObject.GetStringField("BuildId"); // Try to read the build version BuildVersion Version; if (!BuildVersion.TryParse(RawObject.GetObjectField("Version"), out Version)) { throw new JsonParseException("Invalid 'Version' field"); } // Create the receipt TargetReceipt Receipt = new TargetReceipt(TargetName, Platform, Configuration, BuildId, Version); // Read the build products JsonObject[] BuildProductObjects; if (RawObject.TryGetObjectArrayField("BuildProducts", out BuildProductObjects)) { foreach (JsonObject BuildProductObject in BuildProductObjects) { string Path; BuildProductType Type; if (BuildProductObject.TryGetStringField("Path", out Path) && BuildProductObject.TryGetEnumField("Type", out Type)) { string Module; BuildProductObject.TryGetStringField("Module", out Module); BuildProduct NewBuildProduct = Receipt.AddBuildProduct(Path, Type); bool IsPrecompiled; if (BuildProductObject.TryGetBoolField("IsPrecompiled", out IsPrecompiled)) { NewBuildProduct.IsPrecompiled = IsPrecompiled; } } } } // Read the runtime dependencies JsonObject[] RuntimeDependencyObjects; if (RawObject.TryGetObjectArrayField("RuntimeDependencies", out RuntimeDependencyObjects)) { foreach (JsonObject RuntimeDependencyObject in RuntimeDependencyObjects) { string Path; if (RuntimeDependencyObject.TryGetStringField("Path", out Path)) { string StagePath; if (!RuntimeDependencyObject.TryGetStringField("StagePath", out StagePath)) { StagePath = null; } bool bIgnoreIfMissing; if (!RuntimeDependencyObject.TryGetBoolField("IgnoreIfMissing", out bIgnoreIfMissing)) { bIgnoreIfMissing = false; } Receipt.AddRuntimeDependency(Path, StagePath, bIgnoreIfMissing); } } } // Read the additional properties JsonObject[] AdditionalPropertyObjects; if (RawObject.TryGetObjectArrayField("AdditionalProperties", out AdditionalPropertyObjects)) { foreach (JsonObject AdditionalPropertyObject in AdditionalPropertyObjects) { string Name; if (AdditionalPropertyObject.TryGetStringField("Name", out Name)) { string Value; if (AdditionalPropertyObject.TryGetStringField("Value", out Value)) { Receipt.AdditionalProperties.Add(new ReceiptProperty(Name, Value)); } } } } return(Receipt); }