// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using EpicGames.Core; using Microsoft.Extensions.Logging; namespace UnrealBuildTool { abstract class ProjectFile { /// /// Represents a single source file (or other type of file) in a project /// public class SourceFile { /// /// Constructor /// /// Path to the source file on disk /// The directory on this the path within the project will be relative to public SourceFile(FileReference InReference, DirectoryReference? InBaseFolder) { Reference = InReference; BaseFolder = InBaseFolder; } /// /// File path to file on disk /// public FileReference Reference { get; private set; } /// /// Optional directory that overrides where files in this project are relative to when displayed in the IDE. If null, will default to the project's BaseFolder. /// public DirectoryReference? BaseFolder { get; private set; } /// /// Define ToString() so the debugger can show the name in watch windows /// public override string ToString() { return Reference.ToString(); } } /// /// Constructs a new project file object /// /// The path to the project file, relative to the primary project file /// The base directory for files within this project protected ProjectFile(FileReference ProjectFilePath, DirectoryReference BaseDir) { this.ProjectFilePath = ProjectFilePath; this.BaseDir = BaseDir; ShouldBuildByDefaultForSolutionTargets = true; IntelliSenseCppVersion = CppStandardVersion.Default; } /// Project file path public FileReference ProjectFilePath { get; protected set; } /// /// The base directory for files within this project /// public DirectoryReference BaseDir { get; set; } /// Returns true if this is a generated project (as opposed to an imported project) public bool IsGeneratedProject { get; set; } /// Returns true if this is a "stub" project. Stub projects function as simple containers for source files /// and are never actually "built" by the primary project. Stub projects are always "generated" projects. public bool IsStubProject { get; set; } /// Returns true if this is a foreign project, and requires UBT to be passed the path to the .uproject file /// on the command line. public bool IsForeignProject { get; set; } /// Returns true if this is a content only project, and will be re-using the engine targets public bool IsContentOnlyProject { get; set; } /// Returns true if this is a project without source code, but some build setting requires it to be compiled as code, which will make /// a unique executable, instead of using UnrealGame, etc /// @note: Currently unused by any generator, but a future generator may make use of it public bool IsHybridContentOnlyProject { get; set; } /// Whether this project should be built for all solution targets public bool ShouldBuildForAllSolutionTargets { get; set; } /// Whether this project should be built by default. Can still be built from the IDE through the context menu. public bool ShouldBuildByDefaultForSolutionTargets { get; set; } /// /// C++ version which is used in this project. /// public CppStandardVersion IntelliSenseCppVersion { get; protected set; } /// /// Information for IntelliSense: whether coroutines are enabled /// public bool IntelliSenseEnableCoroutines { get; protected set; } /// All of the targets in this project. All non-stub projects must have at least one target. public readonly List ProjectTargets = new List(); /// /// Checks if a file is already contained /// /// Files to check public bool Contains(FileReference File) { return SourceFileMap.ContainsKey(File); } /// /// Adds a list of files to this project, ignoring dupes /// /// Files to add /// The directory the path within the project will be relative to public void AddFilesToProject(List FilesToAdd, DirectoryReference BaseFolder) { foreach (FileReference CurFile in FilesToAdd) { AddFileToProject(CurFile, BaseFolder); } } /// /// Set of all files that have been added /// HashSet AliasedFilesSet = new HashSet(); /// Aliased (i.e. files is custom filter tree) in this project public readonly List AliasedFiles = new List(); /// /// Adds aliased file to the project. /// /// Aliased file. public void AddAliasedFileToProject(AliasedFile File) { if (AliasedFilesSet.Add(File.Location)) { AliasedFiles.Add(File); } } /// /// Adds a file to this project, ignoring dupes /// /// Path to the file on disk /// The directory the path within the project will be relative to public void AddFileToProject(FileReference FilePath, DirectoryReference BaseFolder) { // Check if hasn't already been added as an aliased file if (AliasedFilesSet.Contains(FilePath)) { return; } // Don't add duplicates SourceFile? ExistingFile = null; if (SourceFileMap.TryGetValue(FilePath, out ExistingFile)) { if (ExistingFile.BaseFolder != BaseFolder) { throw new BuildException("Trying to add file '" + FilePath + "' to project '" + ProjectFilePath + "' when the file already exists, but with a different relative base folder '" + BaseFolder + "' is different than the current file's '" + ExistingFile.BaseFolder + "'!"); } } else { SourceFile? File = AllocSourceFile(FilePath, BaseFolder); if (File != null) { SourceFileMap[FilePath] = File; SourceFiles.Add(File); } } } /// /// Splits the definition text into macro name and value (if any). /// /// Definition text /// Out: The definition name /// Out: The definition value or null if it has none /// Pair representing macro name and value. private void SplitDefinitionAndValue(string Definition, out string Key, out string Value) { int EqualsIndex = Definition.IndexOf('='); if (EqualsIndex >= 0) { Key = Definition.Substring(0, EqualsIndex); Value = Definition.Substring(EqualsIndex + 1); } else { Key = Definition; Value = ""; } } /// /// Adds information about a module to this project file /// /// The module to add /// Compile environment for this module public virtual void AddModuleForIntelliSense(UEBuildModuleCPP Module, CppCompileEnvironment CompileEnvironment) { AddIntelliSensePreprocessorDefinitions(CompileEnvironment.Definitions); AddIntelliSenseIncludePaths(SystemIncludePaths, CompileEnvironment.SystemIncludePaths); AddIntelliSenseIncludePaths(UserIncludePaths, CompileEnvironment.UserIncludePaths); foreach (DirectoryReference BaseDir in Module.ModuleDirectories) { BuildEnvironment? BuildEnvironment; if (!BaseDirToBuildEnvironment.TryGetValue(BaseDir, out BuildEnvironment)) { BuildEnvironment = new BuildEnvironment(); BaseDirToBuildEnvironment.Add(BaseDir, BuildEnvironment); } AddIntelliSenseIncludePaths(BuildEnvironment.SystemIncludePaths, CompileEnvironment.SystemIncludePaths); AddIntelliSenseIncludePaths(BuildEnvironment.UserIncludePaths, CompileEnvironment.UserIncludePaths); } SetIntelliSenseEnableCoroutines(CompileEnvironment.bEnableCoroutines); SetIntelliSenseCppVersion(Module.Rules.CppStandard ?? CompileEnvironment.CppStandard); } /// /// Adds all of the specified preprocessor definitions to this VCProject's list of preprocessor definitions for all modules in the project /// /// List of preprocessor definitons to add public void AddIntelliSensePreprocessorDefinitions(List NewPreprocessorDefinitions) { foreach (string NewPreprocessorDefinition in NewPreprocessorDefinitions) { // Don't add definitions and value combinations that have already been added for this project string CurDef = NewPreprocessorDefinition; if (KnownIntelliSensePreprocessorDefinitions.Add(CurDef)) { // Go ahead and check to see if the definition already exists, but the value is different bool AlreadyExists = false; string Def, Value; SplitDefinitionAndValue(CurDef, out Def, out Value); // Ignore any API macros being import/export; we'll assume they're valid across the whole project if (Def.EndsWith("_API", StringComparison.Ordinal)) { CurDef = Def + "="; Value = ""; } for (int DefineIndex = 0; DefineIndex < IntelliSensePreprocessorDefinitions.Count; ++DefineIndex) { string ExistingDef, ExistingValue; SplitDefinitionAndValue(IntelliSensePreprocessorDefinitions[DefineIndex], out ExistingDef, out ExistingValue); if (ExistingDef == Def) { // Already exists, but the value is changing. We don't bother clobbering values for existing defines for this project. AlreadyExists = true; break; } } if (!AlreadyExists) { IntelliSensePreprocessorDefinitions.Add(CurDef); } } } } /// /// Adds all of the specified include paths to this VCProject's list of include paths for all modules in the project /// /// The collection to add to /// List of include paths to add public void AddIntelliSenseIncludePaths(IncludePathsCollection Collection, IEnumerable NewIncludePaths) { foreach (DirectoryReference CurPath in NewIncludePaths) { if (Collection.AbsolutePaths.Add(CurPath)) { // Incoming include paths are relative to the solution directory, but we need these paths to be // relative to the project file's directory string PathRelativeToProjectFile = NormalizeProjectPath(CurPath); Collection.RelativePaths.Add(PathRelativeToProjectFile); } } } /// /// Sets highest C++ version which is used in this project /// /// Version public void SetIntelliSenseCppVersion(CppStandardVersion CppVersion) { if (CppVersion != CppStandardVersion.Default) { if (CppVersion > IntelliSenseCppVersion) { IntelliSenseCppVersion = CppVersion; } } } /// /// Sets whether coroutines should be enabled or not /// /// Should enable them or not public void SetIntelliSenseEnableCoroutines(bool Value) { IntelliSenseEnableCoroutines |= Value; } /// /// Add the given project to the DepondsOn project list. /// /// The project this project is dependent on public void AddDependsOnProject(ProjectFile InProjectFile) { // Make sure that it doesn't exist already bool AlreadyExists = false; foreach (ProjectFile ExistingDependentOn in DependsOnProjects) { if (ExistingDependentOn == InProjectFile) { AlreadyExists = true; break; } } if (AlreadyExists == false) { DependsOnProjects.Add(InProjectFile); } } /// /// Writes a project file to disk /// /// The platforms to write the project files for /// The configurations to add to the project files /// The registered platform project generators /// Logger for output /// True on success public virtual bool WriteProjectFile(List InPlatforms, List InConfigurations, PlatformProjectGeneratorCollection PlatformProjectGenerators, ILogger Logger) { throw new BuildException("UnrealBuildTool cannot automatically generate this project type because WriteProjectFile() was not overridden."); } /// /// If found writes a debug project file to disk /// /// The platforms to write the project files for /// The configurations to add to the project files /// The registered platform project generators /// Logger for output /// List of project files written public virtual List>? WriteDebugProjectFiles(List InPlatforms, List InConfigurations, PlatformProjectGeneratorCollection PlatformProjectGenerators, ILogger Logger) { return null; } public virtual void LoadGUIDFromExistingProject() { } /// /// Allocates a generator-specific source file object /// /// Path to the source file on disk /// Optional sub-folder to put the file in. If empty, this will be determined automatically from the file's path relative to the project file /// The newly allocated source file object public virtual SourceFile? AllocSourceFile(FileReference InitFilePath, DirectoryReference? InitProjectSubFolder = null) { return new SourceFile(InitFilePath, InitProjectSubFolder); } /// /// Takes the given path and tries to rebase it relative to the project or solution directory variables. /// public static string NormalizeProjectPath(string InputPath) { // If the path is rooted in an environment variable, leave it be. if (InputPath.StartsWith("$(")) { return InputPath; } else if (InputPath.EndsWith("\\") || InputPath.EndsWith("/")) { return NormalizeProjectPath(new DirectoryReference(InputPath)); } else { return NormalizeProjectPath(new FileReference(InputPath)); } } /// /// Takes the given path and tries to rebase it relative to the project. /// public static string NormalizeProjectPath(FileSystemReference InputPath) { // Try to make it relative to the solution directory. if (InputPath.IsUnderDirectory(ProjectFileGenerator.PrimaryProjectPath)) { return InputPath.MakeRelativeTo(ProjectFileGenerator.IntermediateProjectFilesPath); } else { return InputPath.FullName; } } /// /// Takes the given path, normalizes it, and quotes it if necessary. /// public static string EscapePath(string InputPath) { string Result = InputPath; if (Result.Contains(' ')) { Result = "\"" + Result + "\""; } return Result; } /// /// Visualizer for the debugger /// public override string ToString() { return ProjectFilePath.ToString(); } /// /// Map of file paths to files in the project. /// private readonly Dictionary SourceFileMap = new Dictionary(); /// /// Files in this project /// public readonly List SourceFiles = new List(); /// /// Collection of include paths /// public class IncludePathsCollection { public List RelativePaths = new List(); public HashSet AbsolutePaths = new HashSet(); } /// /// Build environment for a particular module /// public class BuildEnvironment { public IncludePathsCollection UserIncludePaths = new IncludePathsCollection(); public IncludePathsCollection SystemIncludePaths = new IncludePathsCollection(); } /// /// Merged list of include paths for the project /// IncludePathsCollection UserIncludePaths = new IncludePathsCollection(); /// /// Merged list of include paths for the project /// IncludePathsCollection SystemIncludePaths = new IncludePathsCollection(); /// /// Map of base directory to user include paths /// protected Dictionary BaseDirToBuildEnvironment = new Dictionary(); /// /// Legacy accessor for user search paths /// public List IntelliSenseIncludeSearchPaths => UserIncludePaths.RelativePaths; /// /// Legacy accessor for system include paths /// public List IntelliSenseSystemIncludeSearchPaths => SystemIncludePaths.RelativePaths; /// /// List of preprocessor definitions for the project /// public readonly List IntelliSensePreprocessorDefinitions = new List(); /// /// Set of unique preprocessor definitions /// HashSet KnownIntelliSensePreprocessorDefinitions = new HashSet(StringComparer.InvariantCultureIgnoreCase); /// /// Projects that this project is dependent on /// public readonly List DependsOnProjects = new List(); } }