// Copyright 1998-2014 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;
namespace UnrealBuildTool
{
public interface IntelliSenseGatherer
{
///
/// 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
void AddIntelliSensePreprocessorDefinitions( List NewPreprocessorDefinitions );
///
/// 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
/// Are the include paths to add system include paths
void AddInteliiSenseIncludePaths( List NewIncludePaths, bool bAddingSystemIncludes );
}
///
/// 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.
///
public class ProjectTarget
{
/// The target rules file path on disk, if we have one
public string TargetFilePath;
/// 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;
/// 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;
public override string ToString()
{
return Path.GetFileNameWithoutExtension(TargetFilePath);
}
}
///
/// Class that stores info about aliased file.
///
public class AliasedFile
{
public AliasedFile(string FileSystemPath, string ProjectPath)
{
this.FileSystemPath = FileSystemPath;
this.ProjectPath = ProjectPath;
}
// File system path.
public string FileSystemPath { get; private set; }
// Project path.
public string ProjectPath { get; private set; }
}
public abstract class ProjectFile : IntelliSenseGatherer
{
///
/// 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( string InitFilePath, string InitRelativeBaseFolder )
{
FilePath = InitFilePath;
RelativeBaseFolder = InitRelativeBaseFolder;
}
public SourceFile()
{
}
///
/// File path to file on disk
///
public string FilePath
{
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 RelativeBaseFolder.
///
public string RelativeBaseFolder
{
get;
private set;
}
}
///
/// Constructs a new project file object
///
/// The path to the project file, relative to the master project file
protected ProjectFile( string InitRelativeFilePath )
{
RelativeProjectFilePath = InitRelativeFilePath;
}
/// Full path to the project file on disk
public string ProjectFilePath
{
get
{
return Path.Combine( ProjectFileGenerator.MasterProjectRelativePath, RelativeProjectFilePath );
}
}
/// Project file path, relative to the master project
public string RelativeProjectFilePath
{
get;
private 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;
}
/// 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, string RelativeBaseFolder )
{
foreach( var CurFile in FilesToAdd )
{
AddFileToProject( CurFile, RelativeBaseFolder );
}
}
/// 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( string FilePath, string RelativeBaseFolder )
{
// Don't add duplicates
SourceFile ExistingFile = null;
if (SourceFileMap.TryGetValue(FilePath, out ExistingFile))
{
if( ExistingFile.RelativeBaseFolder != RelativeBaseFolder )
{
if( ( ExistingFile.RelativeBaseFolder != null ) != ( RelativeBaseFolder != null ) ||
!ExistingFile.RelativeBaseFolder.Equals( RelativeBaseFolder, StringComparison.InvariantCultureIgnoreCase ) )
{
throw new BuildException( "Trying to add file '" + FilePath + "' to project '" + ProjectFilePath + "' when the file already exists, but with a different relative base folder '" + RelativeBaseFolder + "' is different than the current file's '" + ExistingFile.RelativeBaseFolder + "'!" );
}
else
{
throw new BuildException( "Trying to add file '" + FilePath + "' to project '" + ProjectFilePath + "' when the file already exists, but the specified project relative base folder is different than the current file's!" );
}
}
}
else
{
SourceFile File = AllocSourceFile( FilePath, RelativeBaseFolder );
if( File != null )
{
SourceFileMap[FilePath] = File;
SourceFiles.Add( File );
}
}
}
///
/// Splits the definition text into macro name and value (if any).
///
/// Definition text
/// Pair representing macro name and value.
private KeyValuePair SplitDefinitionAndValue(string Definition)
{
int EqualsIndex = Definition.IndexOf('=');
KeyValuePair DefineAndValue;
if (EqualsIndex >= 0)
{
DefineAndValue = new KeyValuePair(Definition.Substring(0, EqualsIndex), Definition.Substring(EqualsIndex + 1));
}
else
{
DefineAndValue = new KeyValuePair(Definition, "");
}
return DefineAndValue;
}
///
/// 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( var CurDef in NewPreprocessorDefinitions )
{
if( KnownIntelliSensePreprocessorDefinitions.Add( CurDef ) )
{
// Make sure that it doesn't exist already
var AlreadyExists = false;
var CurDefAndValue = SplitDefinitionAndValue(CurDef);
for (int DefineIndex = 0; DefineIndex < IntelliSensePreprocessorDefinitions.Count; ++DefineIndex)
{
var ExistingDefAndValue = SplitDefinitionAndValue(IntelliSensePreprocessorDefinitions[DefineIndex]);
if (ExistingDefAndValue.Key == CurDefAndValue.Key)
{
// Favor defines that set their value to 1 and replace the old one if it was set to "0"
if (CurDefAndValue.Value == "1" && ExistingDefAndValue.Value == "0")
{
IntelliSensePreprocessorDefinitions[DefineIndex] = CurDef;
}
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 AddInteliiSenseIncludePaths( List NewIncludePaths, bool bAddingSystemIncludes )
{
foreach( var CurPath in NewIncludePaths )
{
if( bAddingSystemIncludes ? KnownIntelliSenseSystemIncludeSearchPaths.Add( CurPath ) : KnownIntelliSenseIncludeSearchPaths.Add( CurPath ) )
{
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 = CurPath;
}
else
{
// 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( CurPath );
}
// Trim any trailing slash
PathRelativeToProjectFile = PathRelativeToProjectFile.TrimEnd('/', '\\');
// Make sure that it doesn't exist already
var AlreadyExists = false;
List SearchPaths = bAddingSystemIncludes ? IntelliSenseSystemIncludeSearchPaths : IntelliSenseIncludeSearchPaths;
foreach( var ExistingPath in SearchPaths )
{
if( PathRelativeToProjectFile == ExistingPath )
{
AlreadyExists = true;
break;
}
}
if( !AlreadyExists )
{
SearchPaths.Add( PathRelativeToProjectFile );
}
}
}
}
///
/// 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
var AlreadyExists = false;
foreach (var 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
/// True on success
public virtual bool WriteProjectFile(List InPlatforms, List InConfigurations)
{
throw new BuildException( "UnrealBuildTool cannot automatically generate this project type because WriteProjectFile() was not overridden." );
}
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( string InitFilePath, string 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;
}
// Otherwise make sure it's absolute
string FullInputPath = Utils.CleanDirectorySeparators(Path.GetFullPath(InputPath));
// Try to make it relative to the solution directory.
string FullSolutionPath = Utils.CleanDirectorySeparators(Path.GetFullPath(ProjectFileGenerator.MasterProjectRelativePath));
if (FullSolutionPath.Last() != Path.DirectorySeparatorChar)
{
FullSolutionPath += Path.DirectorySeparatorChar;
}
if (FullInputPath.StartsWith(FullSolutionPath))
{
FullInputPath = Utils.MakePathRelativeTo(Utils.CleanDirectorySeparators(FullInputPath), Path.GetDirectoryName(Path.GetFullPath(ProjectFilePath)));
}
else
{
FullInputPath = Utils.CleanDirectorySeparators(FullInputPath);
}
// Otherwise return the input
return FullInputPath;
}
/** 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;
}
/// Map of file paths to files in the project.
private readonly Dictionary SourceFileMap = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
/// 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 );
/// Projects that this project is dependent on
public readonly List DependsOnProjects = new List();
}
}