// 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();
}
}