// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Reflection; using EpicGames.Core; using Microsoft.Extensions.Logging; using UnrealBuildBase; namespace UnrealBuildTool { /// /// Interface for toolchain operations that produce output /// interface IActionGraphBuilder { /// /// Adds an action to this graph /// /// Action to add void AddAction(IExternalAction Action); /// /// Creates a response file for use in the action graph /// /// Location of the response file /// Contents of the file /// Allows the backend to write the file in a separate task. /// New file item void CreateIntermediateTextFile(FileItem Location, string Contents, bool AllowAsync = true); /// /// Creates a response file for use in the action graph, with a newline between each string in ContentLines /// /// Location of the response file /// Contents of the file /// Allows the backend to write the file in a separate task. /// New file item void CreateIntermediateTextFile(FileItem Location, IEnumerable ContentLines, bool AllowAsync = true); /// /// Adds a file which is in the non-unity working set /// /// The file to add to the working set void AddFileToWorkingSet(FileItem File); /// /// Adds a file which is a candidate for being in the non-unity working set /// /// The file to add to the working set void AddCandidateForWorkingSet(FileItem File); /// /// Adds a source directory. These folders are scanned recursively for C++ source files. /// /// Base source directory void AddSourceDir(DirectoryItem SourceDir); /// /// Adds the given source files as dependencies /// /// Source directory containing files to build /// Contents of the directory void AddSourceFiles(DirectoryItem SourceDir, FileItem[] SourceFiles); /// /// Adds a list of known header files /// /// List of header files to track void AddHeaderFiles(FileItem[] HeaderFiles); /// /// Sets the output items which belong to a particular module /// /// Name of the module /// Array of output items for this module void SetOutputItemsForModule(string ModuleName, FileItem[] OutputItems); /// /// Adds a diagnostic message /// /// Message to display void AddDiagnostic(string Message); } /// /// Implementation of IActionGraphBuilder which discards all unnecessary operations /// sealed class NullActionGraphBuilder : IActionGraphBuilder { private readonly ILogger Logger; /// /// Constructor /// /// public NullActionGraphBuilder(ILogger InLogger) { Logger = InLogger; } /// public void AddAction(IExternalAction Action) { } /// public void CreateIntermediateTextFile(FileItem FileItem, string Contents, bool AllowAsync = true) { Utils.WriteFileIfChanged(FileItem, Contents, Logger); } /// public void CreateIntermediateTextFile(FileItem FileItem, IEnumerable ContentLines, bool AllowAsync = true) { Utils.WriteFileIfChanged(FileItem, ContentLines, Logger); } /// public void AddSourceDir(DirectoryItem SourceDir) { } /// public void AddSourceFiles(DirectoryItem SourceDir, FileItem[] SourceFiles) { } /// public void AddHeaderFiles(FileItem[] HeaderFiles) { } /// public void AddFileToWorkingSet(FileItem File) { } /// public void AddCandidateForWorkingSet(FileItem File) { } /// public void AddDiagnostic(string Message) { } /// public void SetOutputItemsForModule(string ModuleName, FileItem[] OutputItems) { } } /// /// Implementation of IActionGraphBuilder which forwards calls to an underlying implementation, allowing derived classes to intercept certain calls /// class ForwardingActionGraphBuilder : IActionGraphBuilder { /// /// The inner graph builder /// IActionGraphBuilder Inner; /// /// Constructor /// /// Builder to pass all calls to public ForwardingActionGraphBuilder(IActionGraphBuilder Inner) { this.Inner = Inner; } /// public virtual void AddAction(IExternalAction Action) { Inner.AddAction(Action); } /// public virtual void CreateIntermediateTextFile(FileItem FileItem, string Contents, bool AllowAsync = true) { Inner.CreateIntermediateTextFile(FileItem, Contents, AllowAsync); } /// public virtual void CreateIntermediateTextFile(FileItem FileItem, IEnumerable ContentLines, bool AllowAsync = true) { Inner.CreateIntermediateTextFile(FileItem, ContentLines, AllowAsync); } /// public virtual void AddSourceDir(DirectoryItem SourceDir) { Inner.AddSourceDir(SourceDir); } /// public virtual void AddSourceFiles(DirectoryItem SourceDir, FileItem[] SourceFiles) { Inner.AddSourceFiles(SourceDir, SourceFiles); } /// public virtual void AddHeaderFiles(FileItem[] HeaderFiles) { Inner.AddHeaderFiles(HeaderFiles); } /// public virtual void AddFileToWorkingSet(FileItem File) { Inner.AddFileToWorkingSet(File); } /// public virtual void AddCandidateForWorkingSet(FileItem File) { Inner.AddCandidateForWorkingSet(File); } /// public virtual void AddDiagnostic(string Message) { Inner.AddDiagnostic(Message); } /// public virtual void SetOutputItemsForModule(string ModuleName, FileItem[] OutputItems) { Inner.SetOutputItemsForModule(ModuleName, OutputItems); } } /// /// Extension methods for IActionGraphBuilder classes /// static class ActionGraphBuilderExtensions { /// /// Creates a new action to be built as part of this target /// /// Graph to add the action to /// Type of action to create /// New action public static Action CreateAction(this IActionGraphBuilder Graph, ActionType Type) { Action Action = new Action(Type); Graph.AddAction(Action); return Action; } /// /// Creates an action which copies a file from one location to another /// /// The action graph /// The source file location /// The target file location /// File item for the output file public static Action CreateCopyAction(this IActionGraphBuilder Graph, FileItem SourceFile, FileItem TargetFile) { Action CopyAction = Graph.CreateAction(ActionType.BuildProject); CopyAction.CommandDescription = "Copy"; CopyAction.CommandPath = BuildHostPlatform.Current.Shell; if (BuildHostPlatform.Current.ShellType == ShellType.Cmd) { CopyAction.CommandArguments = String.Format("/C \"copy /Y \"{0}\" \"{1}\" 1>nul\"", SourceFile.AbsolutePath, TargetFile.AbsolutePath); } else { CopyAction.CommandArguments = String.Format("-c \"cp -f \\\"{0}\\\" \\\"{1}\\\"\"", SourceFile.AbsolutePath, TargetFile.AbsolutePath); } CopyAction.WorkingDirectory = Unreal.EngineSourceDirectory; CopyAction.PrerequisiteItems.Add(SourceFile); CopyAction.ProducedItems.Add(TargetFile); CopyAction.DeleteItems.Add(TargetFile); CopyAction.StatusDescription = TargetFile.Location.GetFileName(); CopyAction.bCanExecuteRemotely = false; return CopyAction; } /// /// Creates an action which copies a file from one location to another /// /// List of actions to be executed. Additional actions will be added to this list. /// The source file location /// The target file location /// File item for the output file public static FileItem CreateCopyAction(this IActionGraphBuilder Graph, FileReference SourceFile, FileReference TargetFile) { FileItem SourceFileItem = FileItem.GetItemByFileReference(SourceFile); FileItem TargetFileItem = FileItem.GetItemByFileReference(TargetFile); Graph.CreateCopyAction(SourceFileItem, TargetFileItem); return TargetFileItem; } /// /// Creates an action which calls UBT recursively /// /// The action graph /// Type of the action /// Arguments for the action /// New action instance public static Action CreateRecursiveAction(this IActionGraphBuilder Graph, ActionType Type, string Arguments) where T : ToolMode { ToolModeAttribute? Attribute = typeof(T).GetCustomAttribute(); if (Attribute == null) { throw new BuildException("Missing ToolModeAttribute on {0}", typeof(T).Name); } Action NewAction = Graph.CreateAction(Type); NewAction.CommandPath = Unreal.DotnetPath; NewAction.WorkingDirectory = Unreal.EngineSourceDirectory; NewAction.CommandArguments = $"\"{Unreal.UnrealBuildToolDllPath}\" -Session=\"{UnrealBuildTool.SessionIdentifier}\" -Mode={Attribute.Name} {Arguments}"; NewAction.CommandDescription = Attribute.Name; NewAction.bCanExecuteRemotely = false; NewAction.bCanExecuteRemotelyWithSNDBS = false; NewAction.bCanExecuteInUBA = false; return NewAction; } /// /// Creates a text file with the given contents. If the contents of the text file aren't changed, it won't write the new contents to /// the file to avoid causing an action to be considered outdated. /// /// The action graph /// Path to the intermediate file to create /// Contents of the new file /// Allows the backend to write the file in a separate task. /// File item for the newly created file public static FileItem CreateIntermediateTextFile(this IActionGraphBuilder Graph, FileReference AbsolutePath, string Contents, bool AllowAsync = true) { FileItem FileItem = FileItem.GetItemByFileReference(AbsolutePath); Graph.CreateIntermediateTextFile(FileItem, Contents, AllowAsync); return FileItem; } /// /// Creates a text file with the given contents. If the contents of the text file aren't changed, it won't write the new contents to /// the file to avoid causing an action to be considered outdated. /// /// The action graph /// Path to the intermediate file to create /// Contents of the new file /// Allows the backend to write the file in a separate task. /// File item for the newly created file public static FileItem CreateIntermediateTextFile(this IActionGraphBuilder Graph, FileReference AbsolutePath, IEnumerable ContentLines, bool AllowAsync = true) { FileItem FileItem = UnrealBuildBase.FileItem.GetItemByFileReference(AbsolutePath); Graph.CreateIntermediateTextFile(FileItem, ContentLines, AllowAsync); return FileItem; } } }