// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Xml; using System.Xml.XPath; using System.Xml.Linq; using System.Linq; using System.Text; using Tools.DotNETCommon; namespace UnrealBuildTool { /// /// A single target within a project. A project may have any number of targets within it, which are basically compilable projects /// in themselves that the project wraps up. /// class ProjectTarget { /// The target rules file path on disk, if we have one public FileReference TargetFilePath; /// The project file path on disk public FileReference ProjectFilePath; /// /// Path to the .uproject file on disk /// public FileReference UnrealProjectFilePath; /// Optional target rules for this target. If the target came from a *.Target.cs file on disk, then it will have one of these. /// For targets that are synthetic (like UnrealBuildTool or other manually added project files) we won't have a rules object for those. public TargetRules TargetRules; /// Platforms supported by the target public UnrealTargetPlatform[] SupportedPlatforms; /// Extra supported build platforms. Normally the target rules determines these, but for synthetic targets we'll add them here. public List ExtraSupportedPlatforms = new List(); /// Extra supported build configurations. Normally the target rules determines these, but for synthetic targets we'll add them here. public List ExtraSupportedConfigurations = new List(); /// If true, forces Development configuration regardless of which configuration is set as the Solution Configuration public bool ForceDevelopmentConfiguration = false; /// Whether the project requires 'Deploy' option set (VC projects) public bool ProjectDeploys = false; /// Delegate for creating a rules instance for a given platform/configuration public Func CreateRulesDelegate = null; public string Name { get { return TargetFilePath.GetFileNameWithoutAnyExtensions(); } } public override string ToString() { return TargetFilePath.GetFileNameWithoutExtension(); } } /// /// Class that stores info about aliased file. /// struct AliasedFile { public AliasedFile(string FileSystemPath, string ProjectPath) { this.FileSystemPath = FileSystemPath; this.ProjectPath = ProjectPath; } // File system path. public readonly string FileSystemPath; // Project path. public readonly string ProjectPath; } 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; } public SourceFile() { } /// /// 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 master project file protected ProjectFile(FileReference InProjectFilePath) { ProjectFilePath = InProjectFilePath; ShouldBuildByDefaultForSolutionTargets = true; } /// 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 dumb containers for source files /// and are never actually "built" by the master 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; } /// /// For mod projects, contains the path to the plugin file /// public FileReference PluginFilePath { 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; } /// All of the targets in this project. All non-stub projects must have at least one target. public readonly List ProjectTargets = new List(); /// /// 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); } } /// 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) { 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) { // 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 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 /// /// List of include paths to add /// public void AddIntelliSenseIncludePaths(HashSet NewIncludePaths, bool bAddingSystemIncludes) { foreach (DirectoryReference CurPath in NewIncludePaths) { if (bAddingSystemIncludes ? KnownIntelliSenseSystemIncludeSearchPaths.Add(CurPath.FullName) : KnownIntelliSenseIncludeSearchPaths.Add(CurPath.FullName)) { // 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); // Make sure that it doesn't exist already bool AlreadyExists = false; List SearchPaths = bAddingSystemIncludes ? IntelliSenseSystemIncludeSearchPaths : IntelliSenseIncludeSearchPaths; foreach (string ExistingPath in SearchPaths) { if (PathRelativeToProjectFile == ExistingPath) { AlreadyExists = true; break; } } if (!AlreadyExists) { SearchPaths.Add(PathRelativeToProjectFile); } } } } // @ATG_CHANGE : BEGIN - winmd support /// /// Adds all of the specified winmds to this VCProject's list of winmds for all modules in the project /// /// List of winmds paths to add /// Compiler version currently in use (controls search path for winmds that are SDK contracts) public void AddIntelliSenseWinMDReferences(List NewWinMDReferences, WindowsCompiler Compiler) { foreach (var CurPath in NewWinMDReferences) { string ResolvedPath = CurPath; if (KnownIntelliSenseWinMDReferences.Add(ResolvedPath)) { string PathRelativeToProjectFile; // If the include string is an environment variable (e.g. $(DXSDK_DIR)), then we never want to // give it a relative path if (CurPath.StartsWith("$(")) { PathRelativeToProjectFile = ResolvedPath; } else { // WinMDs referenced by contract name should already have been resolved to files at this point // Incoming include paths are relative to the solution directory, but we need these paths to be // relative to the project file's directory PathRelativeToProjectFile = NormalizeProjectPath(ResolvedPath); } // Trim any trailing slash PathRelativeToProjectFile = PathRelativeToProjectFile.TrimEnd('/', '\\'); // Make sure that it doesn't exist already var AlreadyExists = false; foreach (var ExistingPath in IntelliSenseWinMDReferences) { if (PathRelativeToProjectFile == ExistingPath) { AlreadyExists = true; break; } } if (!AlreadyExists) { IntelliSenseWinMDReferences.Add(PathRelativeToProjectFile); } } } } // @ATG_CHANGE : END /// /// 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 /// True on success public virtual bool WriteProjectFile(List InPlatforms, List InConfigurations, PlatformProjectGeneratorCollection PlatformProjectGenerators) { 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 /// List of project files written public virtual List> WriteDebugProjectFiles(List InPlatforms, List InConfigurations, PlatformProjectGeneratorCollection PlatformProjectGenerators) { 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 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 string NormalizeProjectPath(FileSystemReference InputPath) { // Try to make it relative to the solution directory. if (InputPath.IsUnderDirectory(ProjectFileGenerator.MasterProjectPath)) { return InputPath.MakeRelativeTo(ProjectFileGenerator.IntermediateProjectFilesPath); } else { return InputPath.FullName; } } /// /// Takes the given path, normalizes it, and quotes it if necessary. /// public 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(); /// Include paths for every single module in the project file, merged together public readonly List IntelliSenseIncludeSearchPaths = new List(); public readonly List IntelliSenseSystemIncludeSearchPaths = new List(); public readonly HashSet KnownIntelliSenseIncludeSearchPaths = new HashSet(StringComparer.InvariantCultureIgnoreCase); public readonly HashSet KnownIntelliSenseSystemIncludeSearchPaths = new HashSet(StringComparer.InvariantCultureIgnoreCase); /// Preprocessor definitions for every single module in the project file, merged together public readonly List IntelliSensePreprocessorDefinitions = new List(); public readonly HashSet KnownIntelliSensePreprocessorDefinitions = new HashSet(StringComparer.InvariantCultureIgnoreCase); // @ATG_CHANGE : BEGIN - winmd support /// WinMD references for every single module in the project file, merged together public readonly List IntelliSenseWinMDReferences = new List(); public readonly HashSet KnownIntelliSenseWinMDReferences = new HashSet(StringComparer.InvariantCultureIgnoreCase); // @ATG_CHANGE : END /// Projects that this project is dependent on public readonly List DependsOnProjects = new List(); } }