// 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 System.Text.RegularExpressions; using System.Diagnostics; using System.Security; using Tools.DotNETCommon; namespace UnrealBuildTool { /// /// Specifies the context for building an MSBuild project /// class MSBuildProjectContext { /// /// Name of the configuration /// public string ConfigurationName; /// /// Name of the platform /// public string PlatformName; /// /// Whether this context should be built by default /// public bool bBuildByDefault; /// /// Whether this context should be deployed by default /// public bool bDeployByDefault; /// /// Constructor /// /// Name of this configuration /// Name of this platform public MSBuildProjectContext(string ConfigurationName, string PlatformName) { this.ConfigurationName = ConfigurationName; this.PlatformName = PlatformName; } /// /// The name of this context /// public string Name { get { return String.Format("{0}|{1}", ConfigurationName, PlatformName); } } /// /// Serializes this context to a string for debugging /// /// Name of this context public override string ToString() { return Name; } } /// /// Represents an arbitrary MSBuild project /// abstract class MSBuildProjectFile : ProjectFile { /// The project file version string static public readonly string VCProjectFileVersionString = "10.0.30319.1"; /// The build configuration name to use for stub project configurations. These are projects whose purpose /// is to make it easier for developers to find source files and to provide IntelliSense data for the module /// to Visual Studio static public readonly string StubProjectConfigurationName = "BuiltWithUnrealBuildTool"; /// The name of the Visual C++ platform to use for stub project configurations /// NOTE: We always use Win32 for the stub project's platform, since that is guaranteed to be supported by Visual Studio static public readonly string StubProjectPlatformName = "Win32"; /// /// The Guid representing the project type e.g. C# or C++ /// public virtual string ProjectTypeGUID { get { throw new BuildException("Unrecognized type of project file for Visual Studio solution"); } } /// /// Constructs a new project file object /// /// The path to the project file on disk public MSBuildProjectFile(FileReference InitFilePath) : base(InitFilePath) { // Each project gets its own GUID. This is stored in the project file and referenced in the solution file. // First, check to see if we have an existing file on disk. If we do, then we'll try to preserve the // GUID by loading it from the existing file. if (FileReference.Exists(ProjectFilePath)) { try { LoadGUIDFromExistingProject(); } catch (Exception) { // Failed to find GUID, so just create a new one ProjectGUID = Guid.NewGuid(); } } if (ProjectGUID == Guid.Empty) { // Generate a brand new GUID ProjectGUID = Guid.NewGuid(); } } /// /// Attempts to load the project's GUID from an existing project file on disk /// public override void LoadGUIDFromExistingProject() { // Only load GUIDs if we're in project generation mode. Regular builds don't need GUIDs for anything. if (ProjectFileGenerator.bGenerateProjectFiles) { XmlDocument Doc = new XmlDocument(); Doc.Load(ProjectFilePath.FullName); // @todo projectfiles: Ideally we could do a better job about preserving GUIDs when only minor changes are made // to the project (such as adding a single new file.) It would make diffing changes much easier! // @todo projectfiles: Can we "seed" a GUID based off the project path and generate consistent GUIDs each time? XmlNodeList Elements = Doc.GetElementsByTagName("ProjectGuid"); foreach (XmlElement Element in Elements) { ProjectGUID = Guid.ParseExact(Element.InnerText.Trim("{}".ToCharArray()), "D"); } } } /// /// Get the project context for the given solution context /// /// The solution target type /// The solution configuration /// The solution platform /// Set of platform project generators /// Project context matching the given solution context public abstract MSBuildProjectContext GetMatchingProjectContext(TargetType SolutionTarget, UnrealTargetConfiguration SolutionConfiguration, UnrealTargetPlatform SolutionPlatform, PlatformProjectGeneratorCollection PlatformProjectGenerators); static UnrealTargetConfiguration[] GetSupportedConfigurations(TargetRules Rules) { // Otherwise take the SupportedConfigurationsAttribute from the first type in the inheritance chain that supports it for (Type CurrentType = Rules.GetType(); CurrentType != null; CurrentType = CurrentType.BaseType) { object[] Attributes = Rules.GetType().GetCustomAttributes(typeof(SupportedConfigurationsAttribute), false); if (Attributes.Length > 0) { return Attributes.OfType().SelectMany(x => x.Configurations).Distinct().ToArray(); } } // Otherwise, get the default for the target type if(Rules.Type == TargetType.Editor) { return new[] { UnrealTargetConfiguration.Debug, UnrealTargetConfiguration.DebugGame, UnrealTargetConfiguration.Development }; } else { return ((UnrealTargetConfiguration[])Enum.GetValues(typeof(UnrealTargetConfiguration))).Where(x => x != UnrealTargetConfiguration.Unknown).ToArray(); } } /// /// Checks to see if the specified solution platform and configuration is able to map to this project /// /// The target that we're checking for a valid platform/config combination /// Platform /// Configuration /// Set of platform project generators /// True if this is a valid combination for this project, otherwise false public static bool IsValidProjectPlatformAndConfiguration(ProjectTarget ProjectTarget, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, PlatformProjectGeneratorCollection PlatformProjectGenerators) { if (!ProjectFileGenerator.bIncludeTestAndShippingConfigs) { if(Configuration == UnrealTargetConfiguration.Test || Configuration == UnrealTargetConfiguration.Shipping) { return false; } } UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatform(Platform, true); if (BuildPlatform == null) { return false; } if (BuildPlatform.HasRequiredSDKsInstalled() != SDKStatus.Valid) { return false; } List SupportedConfigurations = new List(); List SupportedPlatforms = new List(); if (ProjectTarget.TargetRules != null) { SupportedPlatforms.AddRange(ProjectTarget.SupportedPlatforms); SupportedConfigurations.AddRange(GetSupportedConfigurations(ProjectTarget.TargetRules)); } // Add all of the extra platforms/configurations for this target { foreach (UnrealTargetPlatform ExtraPlatform in ProjectTarget.ExtraSupportedPlatforms) { if (!SupportedPlatforms.Contains(ExtraPlatform)) { SupportedPlatforms.Add(ExtraPlatform); } } foreach (UnrealTargetConfiguration ExtraConfig in ProjectTarget.ExtraSupportedConfigurations) { if (!SupportedConfigurations.Contains(ExtraConfig)) { SupportedConfigurations.Add(ExtraConfig); } } } // Only build for supported platforms if (SupportedPlatforms.Contains(Platform) == false) { return false; } // Only build for supported configurations if (SupportedConfigurations.Contains(Configuration) == false) { return false; } return true; } /// /// Escapes characters in a filename so they can be stored in an XML attribute /// /// The filename to escape /// The escaped filename public static string EscapeFileName(string FileName) { return SecurityElement.Escape(FileName); } /// /// GUID for this Visual C++ project file /// public Guid ProjectGUID { get; protected set; } } class VCProjectFile : MSBuildProjectFile { FileReference OnlyGameProject; VCProjectFileFormat ProjectFileFormat; bool bUseFastPDB; bool bUsePerFileIntellisense; bool bUsePrecompiled; bool bEditorDependsOnShaderCompileWorker; bool bBuildLiveCodingConsole; string BuildToolOverride; /// This is the platform name that Visual Studio is always guaranteed to support. We'll use this as /// a platform for any project configurations where our actual platform is not supported by the /// installed version of Visual Studio (e.g, "iOS") public const string DefaultPlatformName = "Win32"; // This is the GUID that Visual Studio uses to identify a C++ project file in the solution public override string ProjectTypeGUID { get { return "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}"; } } /// /// Constructs a new project file object /// /// The path to the project file on disk /// /// Visual C++ project file version /// If true, adds the -FastPDB argument to build command lines /// If true, generates per-file intellisense data /// Whether to add the -UsePrecompiled argumemnt when building targets /// Whether editor targets should also build ShaderCompileWorker /// Whether targets using live coding should also build LiveCodingConsole /// Optional arguments to pass to UBT when building public VCProjectFile(FileReference InFilePath, FileReference InOnlyGameProject, VCProjectFileFormat InProjectFileFormat, bool bUseFastPDB, bool bUsePerFileIntellisense, bool bUsePrecompiled, bool bEditorDependsOnShaderCompileWorker, bool bBuildLiveCodingConsole, string BuildToolOverride) : base(InFilePath) { OnlyGameProject = InOnlyGameProject; ProjectFileFormat = InProjectFileFormat; this.bUseFastPDB = bUseFastPDB; this.bUsePerFileIntellisense = bUsePerFileIntellisense; this.bUsePrecompiled = bUsePrecompiled; this.bEditorDependsOnShaderCompileWorker = bEditorDependsOnShaderCompileWorker; this.bBuildLiveCodingConsole = bBuildLiveCodingConsole; this.BuildToolOverride = BuildToolOverride; } /// /// Given a target platform and configuration, generates a platform and configuration name string to use in Visual Studio projects. /// Unlike with solution configurations, Visual Studio project configurations only support certain types of platforms, so we'll /// generate a configuration name that has the platform "built in", and use a default platform type /// /// Actual platform /// Actual configuration /// The configuration name from the target rules, or null if we don't have one /// Set of platform project generators /// Name of platform string to use for Visual Studio project /// Name of configuration string to use for Visual Studio project private void MakeProjectPlatformAndConfigurationNames(UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, TargetType TargetConfigurationName, PlatformProjectGeneratorCollection PlatformProjectGenerators, out string ProjectPlatformName, out string ProjectConfigurationName) { PlatformProjectGenerator PlatformProjectGenerator = PlatformProjectGenerators.GetPlatformProjectGenerator(Platform, bInAllowFailure: true); // Check to see if this platform is supported directly by Visual Studio projects. bool HasActualVSPlatform = (PlatformProjectGenerator != null) ? PlatformProjectGenerator.HasVisualStudioSupport(Platform, Configuration, ProjectFileFormat) : false; if (HasActualVSPlatform) { // Great! Visual Studio supports this platform natively, so we don't need to make up // a fake project configuration name. // Allow the platform to specify the name used in VisualStudio. // Note that the actual name of the platform on the Visual Studio side may be different than what // UnrealBuildTool calls it (e.g. "Win64" -> "x64".) GetVisualStudioPlatformName() will figure this out. ProjectConfigurationName = Configuration.ToString(); ProjectPlatformName = PlatformProjectGenerator.GetVisualStudioPlatformName(Platform, Configuration); } else { // Visual Studio doesn't natively support this platform, so we fake it by mapping it to // a project configuration that has the platform name in that configuration as a suffix, // and then using "Win32" as the actual VS platform name ProjectConfigurationName = Platform.ToString() + "_" + Configuration.ToString(); ProjectPlatformName = DefaultPlatformName; } if(TargetConfigurationName != TargetType.Game) { ProjectConfigurationName += "_" + TargetConfigurationName.ToString(); } } /// /// Get the project context for the given solution context /// /// The solution target type /// The solution configuration /// The solution platform /// Set of platform project generations /// Project context matching the given solution context public override MSBuildProjectContext GetMatchingProjectContext(TargetType SolutionTarget, UnrealTargetConfiguration SolutionConfiguration, UnrealTargetPlatform SolutionPlatform, PlatformProjectGeneratorCollection PlatformProjectGenerators) { // Stub projects always build in the same configuration if(IsStubProject) { return new MSBuildProjectContext(StubProjectConfigurationName, StubProjectPlatformName); } // Have to match every solution configuration combination to a project configuration (or use the invalid one) string ProjectConfigurationName = "Invalid"; // Get the default platform. If there were not valid platforms for this project, just use one that will always be available in VS. string ProjectPlatformName = InvalidConfigPlatformNames.First(); // Whether the configuration should be built automatically as part of the solution bool bBuildByDefault = false; // Whether this configuration should deploy by default (requires bBuildByDefault) bool bDeployByDefault = false; // Programs are built in editor configurations (since the editor is like a desktop program too) and game configurations (since we omit the "game" qualification in the configuration name). bool IsProgramProject = ProjectTargets[0].TargetRules != null && ProjectTargets[0].TargetRules.Type == TargetType.Program; if(!IsProgramProject || SolutionTarget == TargetType.Game || SolutionTarget == TargetType.Editor) { // Get the target type we expect to find for this project TargetType TargetConfigurationName = SolutionTarget; if (IsProgramProject) { TargetConfigurationName = TargetType.Program; } // Now, we want to find a target in this project that maps to the current solution config combination. Only up to one target should // and every solution config combination should map to at least one target in one project (otherwise we shouldn't have added it!). List MatchingProjectTargets = new List(); foreach (ProjectTarget ProjectTarget in ProjectTargets) { if(VCProjectFile.IsValidProjectPlatformAndConfiguration(ProjectTarget, SolutionPlatform, SolutionConfiguration, PlatformProjectGenerators)) { if (ProjectTarget.TargetRules != null) { if (TargetConfigurationName == ProjectTarget.TargetRules.Type) { MatchingProjectTargets.Add(ProjectTarget); } } else { if (ShouldBuildForAllSolutionTargets || TargetConfigurationName == TargetType.Game) { MatchingProjectTargets.Add(ProjectTarget); } } } } // Always allow SCW and UnrealLighmass to build in editor configurations if (MatchingProjectTargets.Count == 0 && SolutionTarget == TargetType.Editor && SolutionPlatform == UnrealTargetPlatform.Win64) { foreach(ProjectTarget ProjectTarget in ProjectTargets) { string TargetName = ProjectTargets[0].TargetRules.Name; if(TargetName == "ShaderCompileWorker" || TargetName == "UnrealLightmass") { MatchingProjectTargets.Add(ProjectTarget); break; } } } // Make sure there's only one matching project target if(MatchingProjectTargets.Count > 1) { throw new BuildException("Not expecting more than one target for project {0} to match solution configuration {1} {2} {3}", ProjectFilePath, SolutionTarget, SolutionConfiguration, SolutionPlatform); } // If we found a matching project, get matching configuration if(MatchingProjectTargets.Count == 1) { // Get the matching target ProjectTarget MatchingProjectTarget = MatchingProjectTargets[0]; // If the project wants to always build in "Development", regardless of what the solution configuration is set to, then we'll do that here. UnrealTargetConfiguration ProjectConfiguration = SolutionConfiguration; if (MatchingProjectTarget.ForceDevelopmentConfiguration && SolutionTarget != TargetType.Game) { ProjectConfiguration = UnrealTargetConfiguration.Development; } // Get the matching project configuration UnrealTargetPlatform ProjectPlatform = SolutionPlatform; if (IsStubProject) { if (ProjectConfiguration != UnrealTargetConfiguration.Unknown) { throw new BuildException("Stub project was expecting platform and configuration type to be set to Unknown"); } ProjectConfigurationName = StubProjectConfigurationName; ProjectPlatformName = StubProjectPlatformName; } else { MakeProjectPlatformAndConfigurationNames(ProjectPlatform, ProjectConfiguration, TargetConfigurationName, PlatformProjectGenerators, out ProjectPlatformName, out ProjectConfigurationName); } // Set whether this project configuration should be built when the user initiates "build solution" if (ShouldBuildByDefaultForSolutionTargets) { // Some targets are "dummy targets"; they only exist to show user friendly errors in VS. Weed them out here, and don't set them to build by default. List SupportedPlatforms = null; if (MatchingProjectTarget.TargetRules != null) { SupportedPlatforms = new List(); SupportedPlatforms.AddRange(MatchingProjectTarget.SupportedPlatforms); } if (SupportedPlatforms == null || SupportedPlatforms.Contains(SolutionPlatform)) { bBuildByDefault = true; PlatformProjectGenerator ProjGen = PlatformProjectGenerators.GetPlatformProjectGenerator(SolutionPlatform, true); if (MatchingProjectTarget.ProjectDeploys || ((ProjGen != null) && (ProjGen.GetVisualStudioDeploymentEnabled(ProjectPlatform, ProjectConfiguration) == true))) { bDeployByDefault = true; } } } } } return new MSBuildProjectContext(ProjectConfigurationName, ProjectPlatformName){ bBuildByDefault = bBuildByDefault, bDeployByDefault = bDeployByDefault }; } class ProjectConfigAndTargetCombination { public UnrealTargetPlatform? Platform; public UnrealTargetConfiguration Configuration; public string ProjectPlatformName; public string ProjectConfigurationName; public ProjectTarget ProjectTarget; public ProjectConfigAndTargetCombination(UnrealTargetPlatform? InPlatform, UnrealTargetConfiguration InConfiguration, string InProjectPlatformName, string InProjectConfigurationName, ProjectTarget InProjectTarget) { Platform = InPlatform; Configuration = InConfiguration; ProjectPlatformName = InProjectPlatformName; ProjectConfigurationName = InProjectConfigurationName; ProjectTarget = InProjectTarget; } public string ProjectConfigurationAndPlatformName { get { return (ProjectPlatformName == null) ? null : (ProjectConfigurationName + "|" + ProjectPlatformName); } } public override string ToString() { return String.Format("{0} {1} {2}", ProjectTarget, Platform, Configuration); } } WindowsCompiler GetCompilerForIntellisense() { switch(ProjectFileFormat) { case VCProjectFileFormat.VisualStudio2019: return WindowsCompiler.VisualStudio2019; case VCProjectFileFormat.VisualStudio2017: return WindowsCompiler.VisualStudio2017; default: return WindowsCompiler.VisualStudio2015_DEPRECATED; } } HashSet InvalidConfigPlatformNames; List ProjectConfigAndTargetCombinations; private void BuildProjectConfigAndTargetCombinations(List InPlatforms, List InConfigurations, PlatformProjectGeneratorCollection PlatformProjectGenerators) { //no need to do this more than once if(ProjectConfigAndTargetCombinations == null) { // Build up a list of platforms and configurations this project will support. In this list, Unknown simply // means that we should use the default "stub" project platform and configuration name. // If this is a "stub" project, then only add a single configuration to the project ProjectConfigAndTargetCombinations = new List(); if (IsStubProject) { ProjectConfigAndTargetCombination StubCombination = new ProjectConfigAndTargetCombination(null, UnrealTargetConfiguration.Unknown, StubProjectPlatformName, StubProjectConfigurationName, null); ProjectConfigAndTargetCombinations.Add(StubCombination); } else { // Figure out all the desired configurations foreach (UnrealTargetConfiguration Configuration in InConfigurations) { //@todo.Rocket: Put this in a commonly accessible place? if (InstalledPlatformInfo.IsValidConfiguration(Configuration, EProjectType.Code) == false) { continue; } foreach (UnrealTargetPlatform Platform in InPlatforms) { if (InstalledPlatformInfo.IsValidPlatform(Platform, EProjectType.Code) == false) { continue; } UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatform(Platform, true); if ((BuildPlatform != null) && (BuildPlatform.HasRequiredSDKsInstalled() == SDKStatus.Valid)) { // Now go through all of the target types for this project if (ProjectTargets.Count == 0) { throw new BuildException("Expecting at least one ProjectTarget to be associated with project '{0}' in the TargetProjects list ", ProjectFilePath); } foreach (ProjectTarget ProjectTarget in ProjectTargets) { if (IsValidProjectPlatformAndConfiguration(ProjectTarget, Platform, Configuration, PlatformProjectGenerators)) { string ProjectPlatformName, ProjectConfigurationName; MakeProjectPlatformAndConfigurationNames(Platform, Configuration, ProjectTarget.TargetRules.Type, PlatformProjectGenerators, out ProjectPlatformName, out ProjectConfigurationName); ProjectConfigAndTargetCombination Combination = new ProjectConfigAndTargetCombination(Platform, Configuration, ProjectPlatformName, ProjectConfigurationName, ProjectTarget); ProjectConfigAndTargetCombinations.Add(Combination); } } } } } } // Create a list of platforms for the "invalid" configuration. We always require at least one of these. if(ProjectConfigAndTargetCombinations.Count == 0) { InvalidConfigPlatformNames = new HashSet{ DefaultPlatformName }; } else { InvalidConfigPlatformNames = new HashSet(ProjectConfigAndTargetCombinations.Select(x => x.ProjectPlatformName)); } } } /// /// If found writes a debug project file to disk /// /// True on success public override List> WriteDebugProjectFiles(List InPlatforms, List InConfigurations, PlatformProjectGeneratorCollection PlatformProjectGenerators) { string ProjectName = ProjectFilePath.GetFileNameWithoutExtension(); List ProjectPlatforms = new List(); List> ProjectFiles = new List>(); BuildProjectConfigAndTargetCombinations(InPlatforms, InConfigurations, PlatformProjectGenerators); foreach (ProjectConfigAndTargetCombination Combination in ProjectConfigAndTargetCombinations) { if (Combination.Platform != null && !ProjectPlatforms.Contains(Combination.Platform.Value)) { ProjectPlatforms.Add(Combination.Platform.Value); } } //write out any additional project files if (!IsStubProject && ProjectTargets.Any(x => x.TargetRules != null && x.TargetRules.Type != TargetType.Program)) { foreach (UnrealTargetPlatform Platform in ProjectPlatforms) { PlatformProjectGenerator ProjGenerator = PlatformProjectGenerators.GetPlatformProjectGenerator(Platform, true); if (ProjGenerator != null) { //write out additional prop file ProjGenerator.WriteAdditionalPropFile(); //write out additional project user files ProjGenerator.WriteAdditionalProjUserFile(this); //write out additional project files Tuple DebugProjectInfo = ProjGenerator.WriteAdditionalProjFile(this); if(DebugProjectInfo != null) { ProjectFiles.Add(DebugProjectInfo); } } } } return ProjectFiles; } /// Implements Project interface public override bool WriteProjectFile(List InPlatforms, List InConfigurations, PlatformProjectGeneratorCollection PlatformProjectGenerators) { string ProjectName = ProjectFilePath.GetFileNameWithoutExtension(); bool bSuccess = true; // Build up the new include search path string StringBuilder VCIncludeSearchPaths = new StringBuilder(); { foreach (string CurPath in IntelliSenseIncludeSearchPaths) { VCIncludeSearchPaths.Append(CurPath + ";"); } foreach (string CurPath in IntelliSenseSystemIncludeSearchPaths) { VCIncludeSearchPaths.Append(CurPath + ";"); } if (InPlatforms.Contains(UnrealTargetPlatform.Win64)) { VCIncludeSearchPaths.Append(VCToolChain.GetVCIncludePaths(UnrealTargetPlatform.Win64, GetCompilerForIntellisense(), null) + ";"); } else if (InPlatforms.Contains(UnrealTargetPlatform.Win32)) { VCIncludeSearchPaths.Append(VCToolChain.GetVCIncludePaths(UnrealTargetPlatform.Win32, GetCompilerForIntellisense(), null) + ";"); } } StringBuilder VCPreprocessorDefinitions = new StringBuilder(); foreach (string CurDef in IntelliSensePreprocessorDefinitions) { if (VCPreprocessorDefinitions.Length > 0) { VCPreprocessorDefinitions.Append(';'); } VCPreprocessorDefinitions.Append(CurDef); } // Setup VC project file content StringBuilder VCProjectFileContent = new StringBuilder(); StringBuilder VCFiltersFileContent = new StringBuilder(); StringBuilder VCUserFileContent = new StringBuilder(); // Visual Studio doesn't require a *.vcxproj.filters file to even exist alongside the project unless // it actually has something of substance in it. We'll avoid saving it out unless we need to. bool FiltersFileIsNeeded = false; // Project file header VCProjectFileContent.AppendLine(""); VCProjectFileContent.AppendLine("", VCProjectFileGenerator.GetProjectFileToolVersionString(ProjectFileFormat)); bool bGenerateUserFileContent = PlatformProjectGenerators.PlatformRequiresVSUserFileGeneration(InPlatforms, InConfigurations); if (bGenerateUserFileContent) { VCUserFileContent.AppendLine(""); VCUserFileContent.AppendLine("", VCProjectFileGenerator.GetProjectFileToolVersionString(ProjectFileFormat)); } BuildProjectConfigAndTargetCombinations(InPlatforms, InConfigurations, PlatformProjectGenerators); VCProjectFileContent.AppendLine(" "); // Make a list of the platforms and configs as project-format names List ProjectPlatforms = new List(); List> ProjectPlatformNameAndPlatforms = new List>(); // ProjectPlatformName, Platform List> ProjectConfigurationNameAndConfigurations = new List>(); // ProjectConfigurationName, Configuration foreach (ProjectConfigAndTargetCombination Combination in ProjectConfigAndTargetCombinations) { if (Combination.Platform == null) { continue; } if (!ProjectPlatforms.Contains(Combination.Platform.Value)) { ProjectPlatforms.Add(Combination.Platform.Value); } if (!ProjectPlatformNameAndPlatforms.Any(ProjectPlatformNameAndPlatformTuple => ProjectPlatformNameAndPlatformTuple.Item1 == Combination.ProjectPlatformName)) { ProjectPlatformNameAndPlatforms.Add(Tuple.Create(Combination.ProjectPlatformName, Combination.Platform.Value)); } if (!ProjectConfigurationNameAndConfigurations.Any(ProjectConfigurationNameAndConfigurationTuple => ProjectConfigurationNameAndConfigurationTuple.Item1 == Combination.ProjectConfigurationName)) { ProjectConfigurationNameAndConfigurations.Add(Tuple.Create(Combination.ProjectConfigurationName, Combination.Configuration)); } } // Add the "invalid" configuration for each platform. We use this when the solution configuration does not match any project configuration. foreach(string InvalidConfigPlatformName in InvalidConfigPlatformNames) { VCProjectFileContent.AppendLine(" ", InvalidConfigPlatformName); VCProjectFileContent.AppendLine(" Invalid"); VCProjectFileContent.AppendLine(" {0}", InvalidConfigPlatformName); VCProjectFileContent.AppendLine(" "); } // Output ALL the project's config-platform permutations (project files MUST do this) foreach (Tuple ConfigurationTuple in ProjectConfigurationNameAndConfigurations) { string ProjectConfigurationName = ConfigurationTuple.Item1; foreach (Tuple PlatformTuple in ProjectPlatformNameAndPlatforms) { string ProjectPlatformName = PlatformTuple.Item1; VCProjectFileContent.AppendLine(" ", ProjectConfigurationName, ProjectPlatformName); VCProjectFileContent.AppendLine(" {0}", ProjectConfigurationName); VCProjectFileContent.AppendLine(" {0}", ProjectPlatformName); VCProjectFileContent.AppendLine(" "); } } VCProjectFileContent.AppendLine(" "); VCFiltersFileContent.AppendLine(""); VCFiltersFileContent.AppendLine("", VCProjectFileGenerator.GetProjectFileToolVersionString(ProjectFileFormat)); // Platform specific PropertyGroups, etc. if (!IsStubProject) { foreach (UnrealTargetPlatform Platform in ProjectPlatforms) { PlatformProjectGenerator ProjGenerator = PlatformProjectGenerators.GetPlatformProjectGenerator(Platform, true); if (ProjGenerator != null && ProjGenerator.HasVisualStudioSupport(Platform, UnrealTargetConfiguration.Development, ProjectFileFormat)) { ProjGenerator.GetAdditionalVisualStudioPropertyGroups(Platform, ProjectFileFormat, VCProjectFileContent); } } } // Project globals (project GUID, project type, SCC bindings, etc) { VCProjectFileContent.AppendLine(" "); VCProjectFileContent.AppendLine(" {0}", ProjectGUID.ToString("B").ToUpperInvariant()); VCProjectFileContent.AppendLine(" MakeFileProj"); VCProjectFileContent.AppendLine(" {0}", ProjectName); VCProjectFileContent.AppendLine(" {0}", VCProjectFileGenerator.GetProjectFilePlatformToolsetVersionString(ProjectFileFormat)); VCProjectFileContent.AppendLine(" {0}", VCProjectFileGenerator.GetProjectFileToolVersionString(ProjectFileFormat)); VCProjectFileContent.AppendLine(" Native"); VCProjectFileContent.AppendLine(" "); } // look for additional import lines for all platforms for non stub projects if (!IsStubProject) { foreach (UnrealTargetPlatform Platform in ProjectPlatforms) { PlatformProjectGenerator ProjGenerator = PlatformProjectGenerators.GetPlatformProjectGenerator(Platform, true); if (ProjGenerator != null && ProjGenerator.HasVisualStudioSupport(Platform, UnrealTargetConfiguration.Development, ProjectFileFormat)) { ProjGenerator.GetVisualStudioGlobalProperties(Platform, VCProjectFileContent); } } } if (!IsStubProject) { // TODO: Restrict this to only the Lumin platform targets, routing via GetVisualStudioGlobalProperties(). // Currently hacking here because returning true from HasVisualStudioSupport() for lumin causes bunch of faiures in VS. VCProjectFileContent.AppendLine(" "); VCProjectFileContent.AppendLine(" "); VCProjectFileContent.AppendLine(" "); VCProjectFileContent.AppendLine(" "); } // Write each project configuration PreDefaultProps section foreach (Tuple ConfigurationTuple in ProjectConfigurationNameAndConfigurations) { string ProjectConfigurationName = ConfigurationTuple.Item1; UnrealTargetConfiguration TargetConfiguration = ConfigurationTuple.Item2; foreach (Tuple PlatformTuple in ProjectPlatformNameAndPlatforms) { string ProjectPlatformName = PlatformTuple.Item1; UnrealTargetPlatform TargetPlatform = PlatformTuple.Item2; WritePreDefaultPropsConfiguration(TargetPlatform, TargetConfiguration, ProjectPlatformName, ProjectConfigurationName, PlatformProjectGenerators, VCProjectFileContent); } } VCProjectFileContent.AppendLine(" "); // Write the invalid configuration data foreach(string InvalidConfigPlatformName in InvalidConfigPlatformNames) { VCProjectFileContent.AppendLine(" ", InvalidConfigPlatformName); VCProjectFileContent.AppendLine(" Makefile"); VCProjectFileContent.AppendLine(" "); } // Write each project configuration PreDefaultProps section foreach (Tuple ConfigurationTuple in ProjectConfigurationNameAndConfigurations) { string ProjectConfigurationName = ConfigurationTuple.Item1; UnrealTargetConfiguration TargetConfiguration = ConfigurationTuple.Item2; foreach (Tuple PlatformTuple in ProjectPlatformNameAndPlatforms) { string ProjectPlatformName = PlatformTuple.Item1; UnrealTargetPlatform TargetPlatform = PlatformTuple.Item2; WritePostDefaultPropsConfiguration(TargetPlatform, TargetConfiguration, ProjectPlatformName, ProjectConfigurationName, PlatformProjectGenerators, VCProjectFileContent); } } VCProjectFileContent.AppendLine(" "); VCProjectFileContent.AppendLine(" "); VCProjectFileContent.AppendLine(" "); // Write the invalid configuration foreach(string InvalidConfigPlatformName in InvalidConfigPlatformNames) { const string InvalidMessage = "echo The selected platform/configuration is not valid for this target."; string ProjectRelativeUnusedDirectory = NormalizeProjectPath(DirectoryReference.Combine(UnrealBuildTool.EngineDirectory, "Intermediate", "Build", "Unused")); VCProjectFileContent.AppendLine(" ", InvalidConfigPlatformName); VCProjectFileContent.AppendLine(" {0}", InvalidMessage); VCProjectFileContent.AppendLine(" {0}", InvalidMessage); VCProjectFileContent.AppendLine(" {0}", InvalidMessage); VCProjectFileContent.AppendLine(" Invalid Output", InvalidMessage); VCProjectFileContent.AppendLine(" {0}{1}", ProjectRelativeUnusedDirectory, Path.DirectorySeparatorChar); VCProjectFileContent.AppendLine(" {0}{1}", ProjectRelativeUnusedDirectory, Path.DirectorySeparatorChar); VCProjectFileContent.AppendLine(" "); } // Write each project configuration foreach (ProjectConfigAndTargetCombination Combination in ProjectConfigAndTargetCombinations) { WriteConfiguration(ProjectName, Combination, VCProjectFileContent, PlatformProjectGenerators, bGenerateUserFileContent ? VCUserFileContent : null); } // Source folders and files { List LocalAliasedFiles = new List(AliasedFiles); foreach (SourceFile CurFile in SourceFiles) { // We want all source file and directory paths in the project files to be relative to the project file's // location on the disk. Convert the path to be relative to the project file directory string ProjectRelativeSourceFile = CurFile.Reference.MakeRelativeTo(ProjectFilePath.Directory); // By default, files will appear relative to the project file in the solution. This is kind of the normal Visual // Studio way to do things, but because our generated project files are emitted to intermediate folders, if we always // did this it would yield really ugly paths int he solution explorer string FilterRelativeSourceDirectory; if (CurFile.BaseFolder == null) { FilterRelativeSourceDirectory = ProjectRelativeSourceFile; } else { FilterRelativeSourceDirectory = CurFile.Reference.MakeRelativeTo(CurFile.BaseFolder); } // Manually remove the filename for the filter. We run through this code path a lot, so just do it manually. int LastSeparatorIdx = FilterRelativeSourceDirectory.LastIndexOf(Path.DirectorySeparatorChar); if (LastSeparatorIdx == -1) { FilterRelativeSourceDirectory = ""; } else { FilterRelativeSourceDirectory = FilterRelativeSourceDirectory.Substring(0, LastSeparatorIdx); } LocalAliasedFiles.Add(new AliasedFile(ProjectRelativeSourceFile, FilterRelativeSourceDirectory)); } VCFiltersFileContent.AppendLine(" "); VCProjectFileContent.AppendLine(" "); // Add all file directories to the filters file as solution filters HashSet FilterDirectories = new HashSet(); UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatform(BuildHostPlatform.Current.Platform); foreach (AliasedFile AliasedFile in LocalAliasedFiles) { // No need to add the root directory relative to the project (it would just be an empty string!) if (!String.IsNullOrWhiteSpace(AliasedFile.ProjectPath)) { FiltersFileIsNeeded = EnsureFilterPathExists(AliasedFile.ProjectPath, VCFiltersFileContent, FilterDirectories); } string VCFileType = GetVCFileType(AliasedFile.FileSystemPath); VCProjectFileContent.AppendLine(" <{0} Include=\"{1}\"/>", VCFileType, EscapeFileName(AliasedFile.FileSystemPath)); if (!String.IsNullOrWhiteSpace(AliasedFile.ProjectPath)) { VCFiltersFileContent.AppendLine(" <{0} Include=\"{1}\">", VCFileType, EscapeFileName(AliasedFile.FileSystemPath)); VCFiltersFileContent.AppendLine(" {0}", Utils.CleanDirectorySeparators(EscapeFileName(AliasedFile.ProjectPath))); VCFiltersFileContent.AppendLine(" ", VCFileType); FiltersFileIsNeeded = true; } else { // No need to specify the root directory relative to the project (it would just be an empty string!) VCFiltersFileContent.AppendLine(" <{0} Include=\"{1}\" />", VCFileType, EscapeFileName(AliasedFile.FileSystemPath)); } } VCProjectFileContent.AppendLine(" "); VCFiltersFileContent.AppendLine(" "); } // For Installed engine builds, include engine source in the source search paths if it exists. We never build it locally, so the debugger can't find it. if (UnrealBuildTool.IsEngineInstalled() && !IsStubProject) { VCProjectFileContent.AppendLine(" "); VCProjectFileContent.Append(" "); foreach (string DirectoryName in Directory.EnumerateDirectories(UnrealBuildTool.EngineSourceDirectory.FullName, "*", SearchOption.AllDirectories)) { if (Directory.EnumerateFiles(DirectoryName, "*.cpp").Any()) { VCProjectFileContent.Append(DirectoryName); VCProjectFileContent.Append(";"); } } VCProjectFileContent.AppendLine(""); VCProjectFileContent.AppendLine(" "); } // Write IntelliSense info { // @todo projectfiles: Currently we are storing defines/include paths for ALL configurations rather than using ConditionString and storing // this data uniquely for each target configuration. IntelliSense may behave better if we did that, but it will result in a LOT more // data being stored into the project file, and might make the IDE perform worse when switching configurations! VCProjectFileContent.AppendLine(" "); VCProjectFileContent.AppendLine(" $(NMakePreprocessorDefinitions){0}", (VCPreprocessorDefinitions.Length > 0 ? (";" + VCPreprocessorDefinitions) : "")); VCProjectFileContent.AppendLine(" $(NMakeIncludeSearchPath){0}", (VCIncludeSearchPaths.Length > 0 ? (";" + VCIncludeSearchPaths) : "")); VCProjectFileContent.AppendLine(" $(NMakeForcedIncludes)"); VCProjectFileContent.AppendLine(" $(NMakeAssemblySearchPath)"); VCProjectFileContent.AppendLine(" $(NMakeForcedUsingAssemblies)"); VCProjectFileContent.AppendLine(" "); } string OutputManifestString = ""; if (!IsStubProject) { foreach (UnrealTargetPlatform Platform in ProjectPlatforms) { PlatformProjectGenerator ProjGenerator = PlatformProjectGenerators.GetPlatformProjectGenerator(Platform, true); if (ProjGenerator != null && ProjGenerator.HasVisualStudioSupport(Platform, UnrealTargetConfiguration.Development, ProjectFileFormat)) { // @todo projectfiles: Serious hacks here because we are trying to emit one-time platform-specific sections that need information // about a target type, but the project file may contain many types of targets! Some of this logic will need to move into // the per-target configuration writing code. TargetType HackTargetType = TargetType.Game; FileReference HackTargetFilePath = null; foreach (ProjectConfigAndTargetCombination Combination in ProjectConfigAndTargetCombinations) { if (Combination.Platform == Platform && Combination.ProjectTarget.TargetRules != null && Combination.ProjectTarget.TargetRules.Type == HackTargetType) { HackTargetFilePath = Combination.ProjectTarget.TargetFilePath;// ProjectConfigAndTargetCombinations[0].ProjectTarget.TargetFilePath; break; } } if (HackTargetFilePath != null) { OutputManifestString += ProjGenerator.GetVisualStudioOutputManifestSection(Platform, HackTargetType, HackTargetFilePath, ProjectFilePath, ProjectFileFormat); } } } } VCProjectFileContent.Append(OutputManifestString); // output manifest must come before the Cpp.targets file. VCProjectFileContent.AppendLine(" "); VCProjectFileContent.AppendLine(" "); VCProjectFileContent.AppendLine(" "); if (!IsStubProject) { foreach (UnrealTargetPlatform Platform in ProjectPlatforms) { PlatformProjectGenerator ProjGenerator = PlatformProjectGenerators.GetPlatformProjectGenerator(Platform, true); if (ProjGenerator != null && ProjGenerator.HasVisualStudioSupport(Platform, UnrealTargetConfiguration.Development, ProjectFileFormat)) { ProjGenerator.GetVisualStudioTargetOverrides(Platform, ProjectFileFormat, VCProjectFileContent); } } } VCProjectFileContent.AppendLine(" "); VCProjectFileContent.AppendLine(" "); VCProjectFileContent.AppendLine(""); VCFiltersFileContent.AppendLine(""); if (bGenerateUserFileContent) { VCUserFileContent.AppendLine(""); } // Save the project file if (bSuccess) { bSuccess = ProjectFileGenerator.WriteFileIfChanged(ProjectFilePath.FullName, VCProjectFileContent.ToString()); } // Save the filters file if (bSuccess) { // Create a path to the project file's filters file string VCFiltersFilePath = ProjectFilePath.FullName + ".filters"; if (FiltersFileIsNeeded) { bSuccess = ProjectFileGenerator.WriteFileIfChanged(VCFiltersFilePath, VCFiltersFileContent.ToString()); } else { Log.TraceVerbose("Deleting Visual C++ filters file which is no longer needed: " + VCFiltersFilePath); // Delete the filters file, if one exists. We no longer need it try { File.Delete(VCFiltersFilePath); } catch (Exception) { Log.TraceInformation("Error deleting filters file (file may not be writable): " + VCFiltersFilePath); } } } // Save the user file, if required if (VCUserFileContent.Length > 0) { // Create a path to the project file's user file string VCUserFilePath = ProjectFilePath.FullName + ".user"; // Never overwrite the existing user path as it will cause them to lose their settings if (File.Exists(VCUserFilePath) == false) { bSuccess = ProjectFileGenerator.WriteFileIfChanged(VCUserFilePath, VCUserFileContent.ToString()); } } return bSuccess; } private static bool EnsureFilterPathExists(string FilterRelativeSourceDirectory, StringBuilder VCFiltersFileContent, HashSet FilterDirectories) { // We only want each directory to appear once in the filters file string PathRemaining = Utils.CleanDirectorySeparators(FilterRelativeSourceDirectory); bool FiltersFileIsNeeded = false; if (!FilterDirectories.Contains(PathRemaining)) { // Make sure all subdirectories leading up to this directory each have their own filter, too! List AllDirectoriesInPath = new List(); string PathSoFar = ""; for (; ; ) { if (PathRemaining.Length > 0) { int SlashIndex = PathRemaining.IndexOf(Path.DirectorySeparatorChar); string SplitDirectory; if (SlashIndex != -1) { SplitDirectory = PathRemaining.Substring(0, SlashIndex); PathRemaining = PathRemaining.Substring(SplitDirectory.Length + 1); } else { SplitDirectory = PathRemaining; PathRemaining = ""; } if (!String.IsNullOrEmpty(PathSoFar)) { PathSoFar += Path.DirectorySeparatorChar; } PathSoFar += SplitDirectory; AllDirectoriesInPath.Add(PathSoFar); } else { break; } } foreach (string LeadingDirectory in AllDirectoriesInPath) { if (!FilterDirectories.Contains(LeadingDirectory)) { FilterDirectories.Add(LeadingDirectory); // Generate a unique GUID for this folder // NOTE: When saving generated project files, we ignore differences in GUIDs if every other part of the file // matches identically with the pre-existing file string FilterGUID = Guid.NewGuid().ToString("B").ToUpperInvariant(); VCFiltersFileContent.AppendLine(" ", EscapeFileName(LeadingDirectory)); VCFiltersFileContent.AppendLine(" {0}", FilterGUID); VCFiltersFileContent.AppendLine(" "); FiltersFileIsNeeded = true; } } } return FiltersFileIsNeeded; } /// /// Returns the VCFileType element name based on the file path. /// /// The path of the file to return type for. /// Name of the element in MSBuild project file for this file. private string GetVCFileType(string Path) { // What type of file is this? if (Path.EndsWith(".h", StringComparison.InvariantCultureIgnoreCase) || Path.EndsWith(".inl", StringComparison.InvariantCultureIgnoreCase)) { return "ClInclude"; } else if (Path.EndsWith(".cpp", StringComparison.InvariantCultureIgnoreCase)) { return "ClCompile"; } else if (Path.EndsWith(".rc", StringComparison.InvariantCultureIgnoreCase)) { return "ResourceCompile"; } else if (Path.EndsWith(".manifest", StringComparison.InvariantCultureIgnoreCase)) { return "Manifest"; } else { return "None"; } } // Anonymous function that writes pre-Default.props configuration data private void WritePreDefaultPropsConfiguration(UnrealTargetPlatform TargetPlatform, UnrealTargetConfiguration TargetConfiguration, string ProjectPlatformName, string ProjectConfigurationName, PlatformProjectGeneratorCollection PlatformProjectGenerators, StringBuilder VCProjectFileContent) { PlatformProjectGenerator ProjGenerator = PlatformProjectGenerators.GetPlatformProjectGenerator(TargetPlatform, true); if (ProjGenerator == null) { return; } string ProjectConfigurationAndPlatformName = ProjectConfigurationName + "|" + ProjectPlatformName; string ConditionString = "Condition=\"'$(Configuration)|$(Platform)'=='" + ProjectConfigurationAndPlatformName + "'\""; if(ProjGenerator != null) { StringBuilder PlatformToolsetString = new StringBuilder(); ProjGenerator.GetVisualStudioPreDefaultString(TargetPlatform, TargetConfiguration, PlatformToolsetString); if (PlatformToolsetString.Length > 0) { VCProjectFileContent.AppendLine(" ", ConditionString); VCProjectFileContent.Append(PlatformToolsetString); VCProjectFileContent.AppendLine(" "); } } } // Anonymous function that writes post-Default.props configuration data private void WritePostDefaultPropsConfiguration(UnrealTargetPlatform TargetPlatform, UnrealTargetConfiguration TargetConfiguration, string ProjectPlatformName, string ProjectConfigurationName, PlatformProjectGeneratorCollection PlatformProjectGenerators, StringBuilder VCProjectFileContent) { PlatformProjectGenerator ProjGenerator = PlatformProjectGenerators.GetPlatformProjectGenerator(TargetPlatform, true); string ProjectConfigurationAndPlatformName = ProjectConfigurationName + "|" + ProjectPlatformName; string ConditionString = "Condition=\"'$(Configuration)|$(Platform)'=='" + ProjectConfigurationAndPlatformName + "'\""; StringBuilder PlatformToolsetString = new StringBuilder(); if(ProjGenerator != null) { ProjGenerator.GetVisualStudioPlatformToolsetString(TargetPlatform, TargetConfiguration, ProjectFileFormat, PlatformToolsetString); } if (PlatformToolsetString.Length == 0) { PlatformToolsetString.AppendLine(" " + VCProjectFileGenerator.GetProjectFilePlatformToolsetVersionString(ProjectFileFormat) + ""); } string PlatformConfigurationType = (ProjGenerator == null) ? "Makefile" : ProjGenerator.GetVisualStudioPlatformConfigurationType(TargetPlatform, ProjectFileFormat); VCProjectFileContent.AppendLine(" ", ConditionString); VCProjectFileContent.AppendLine(" {0}", PlatformConfigurationType); VCProjectFileContent.Append(PlatformToolsetString); VCProjectFileContent.AppendLine(" "); } // Anonymous function that writes project configuration data private void WriteConfiguration(string ProjectName, ProjectConfigAndTargetCombination Combination, StringBuilder VCProjectFileContent, PlatformProjectGeneratorCollection PlatformProjectGenerators, StringBuilder VCUserFileContent) { UnrealTargetConfiguration Configuration = Combination.Configuration; PlatformProjectGenerator ProjGenerator = Combination.Platform != null ? PlatformProjectGenerators.GetPlatformProjectGenerator(Combination.Platform.Value, true) : null; string UProjectPath = ""; if (IsForeignProject) { UProjectPath = "\"$(SolutionDir)$(ProjectName).uproject\""; } string ConditionString = "Condition=\"'$(Configuration)|$(Platform)'=='" + Combination.ProjectConfigurationAndPlatformName + "'\""; { VCProjectFileContent.AppendLine(" ", ConditionString); VCProjectFileContent.AppendLine(" "); if(ProjGenerator != null) { ProjGenerator.GetVisualStudioImportGroupProperties(Combination.Platform.Value, VCProjectFileContent); } VCProjectFileContent.AppendLine(" "); DirectoryReference ProjectDirectory = ProjectFilePath.Directory; if (IsStubProject) { string ProjectRelativeUnusedDirectory = NormalizeProjectPath(DirectoryReference.Combine(UnrealBuildTool.EngineDirectory, "Intermediate", "Build", "Unused")); VCProjectFileContent.AppendLine(" ", ConditionString); VCProjectFileContent.AppendLine(" {0}{1}", ProjectRelativeUnusedDirectory, Path.DirectorySeparatorChar); VCProjectFileContent.AppendLine(" {0}{1}", ProjectRelativeUnusedDirectory, Path.DirectorySeparatorChar); VCProjectFileContent.AppendLine(" @rem Nothing to do."); VCProjectFileContent.AppendLine(" @rem Nothing to do."); VCProjectFileContent.AppendLine(" @rem Nothing to do."); VCProjectFileContent.AppendLine(" "); VCProjectFileContent.AppendLine(" "); } else if (UnrealBuildTool.IsEngineInstalled() && Combination.ProjectTarget != null && Combination.ProjectTarget.TargetRules != null && (Combination.Platform == null || !Combination.ProjectTarget.SupportedPlatforms.Contains(Combination.Platform.Value))) { string ProjectRelativeUnusedDirectory = NormalizeProjectPath(DirectoryReference.Combine(UnrealBuildTool.EngineDirectory, "Intermediate", "Build", "Unused")); string TargetName = Combination.ProjectTarget.TargetFilePath.GetFileNameWithoutAnyExtensions(); string ValidPlatforms = String.Join(", ", Combination.ProjectTarget.SupportedPlatforms.Select(x => x.ToString())); VCProjectFileContent.AppendLine(" ", ConditionString); VCProjectFileContent.AppendLine(" {0}{1}", ProjectRelativeUnusedDirectory, Path.DirectorySeparatorChar); VCProjectFileContent.AppendLine(" {0}{1}", ProjectRelativeUnusedDirectory, Path.DirectorySeparatorChar); VCProjectFileContent.AppendLine(" @echo {0} is not a supported platform for {1}. Valid platforms are {2}.", Combination.Platform, TargetName, ValidPlatforms); VCProjectFileContent.AppendLine(" @echo {0} is not a supported platform for {1}. Valid platforms are {2}.", Combination.Platform, TargetName, ValidPlatforms); VCProjectFileContent.AppendLine(" @echo {0} is not a supported platform for {1}. Valid platforms are {2}.", Combination.Platform, TargetName, ValidPlatforms); VCProjectFileContent.AppendLine(" "); VCProjectFileContent.AppendLine(" "); } else { UnrealTargetPlatform Platform = Combination.Platform.Value; TargetRules TargetRulesObject = Combination.ProjectTarget.TargetRules; FileReference TargetFilePath = Combination.ProjectTarget.TargetFilePath; string TargetName = TargetFilePath.GetFileNameWithoutAnyExtensions(); string UBTPlatformName = Platform.ToString(); string UBTConfigurationName = Configuration.ToString(); // Setup output path UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatform(Platform); // Figure out if this is a monolithic build bool bShouldCompileMonolithic = BuildPlatform.ShouldCompileMonolithicBinary(Platform); if(!bShouldCompileMonolithic) { bShouldCompileMonolithic = (Combination.ProjectTarget.CreateRulesDelegate(Platform, Configuration).LinkType == TargetLinkType.Monolithic); } // Get the output directory DirectoryReference RootDirectory = UnrealBuildTool.EngineDirectory; if (TargetRulesObject.Type != TargetType.Program && (bShouldCompileMonolithic || TargetRulesObject.BuildEnvironment == TargetBuildEnvironment.Unique)) { if(Combination.ProjectTarget.UnrealProjectFilePath != null) { RootDirectory = Combination.ProjectTarget.UnrealProjectFilePath.Directory; } } if (TargetRulesObject.Type == TargetType.Program && Combination.ProjectTarget.UnrealProjectFilePath != null) { RootDirectory = Combination.ProjectTarget.UnrealProjectFilePath.Directory; } // Get the output directory DirectoryReference OutputDirectory = DirectoryReference.Combine(RootDirectory, "Binaries", UBTPlatformName); if (!string.IsNullOrEmpty(TargetRulesObject.ExeBinariesSubFolder)) { OutputDirectory = DirectoryReference.Combine(OutputDirectory, TargetRulesObject.ExeBinariesSubFolder); } // Get the executable name (minus any platform or config suffixes) string BaseExeName = TargetName; if (!bShouldCompileMonolithic && TargetRulesObject.Type != TargetType.Program && TargetRulesObject.BuildEnvironment != TargetBuildEnvironment.Unique) { BaseExeName = "UE4" + TargetRulesObject.Type.ToString(); } // Make the output file path FileReference NMakePath = FileReference.Combine(OutputDirectory, BaseExeName); if (Configuration != TargetRulesObject.UndecoratedConfiguration) { NMakePath += "-" + UBTPlatformName + "-" + UBTConfigurationName; } NMakePath += TargetRulesObject.Architecture; NMakePath += BuildPlatform.GetBinaryExtension(UEBuildBinaryType.Executable); VCProjectFileContent.AppendLine(" ", ConditionString); StringBuilder PathsStringBuilder = new StringBuilder(); if(ProjGenerator != null) { ProjGenerator.GetVisualStudioPathsEntries(Platform, Configuration, TargetRulesObject.Type, TargetFilePath, ProjectFilePath, NMakePath, ProjectFileFormat, PathsStringBuilder); } string PathStrings = PathsStringBuilder.ToString(); if (string.IsNullOrEmpty(PathStrings) || (PathStrings.Contains("") == false)) { string ProjectRelativeUnusedDirectory = "$(ProjectDir)..\\Build\\Unused"; VCProjectFileContent.Append(PathStrings); VCProjectFileContent.AppendLine(" {0}{1}", ProjectRelativeUnusedDirectory, Path.DirectorySeparatorChar); VCProjectFileContent.AppendLine(" {0}{1}", ProjectRelativeUnusedDirectory, Path.DirectorySeparatorChar); } else { VCProjectFileContent.Append(PathStrings); } // This is the standard UE4 based project NMake build line: // ..\..\Build\BatchFiles\Build.bat // ie ..\..\Build\BatchFiles\Build.bat BlankProgram Win64 Debug StringBuilder BuildArguments = new StringBuilder(); BuildArguments.AppendFormat("{0} {1} {2}", TargetName, UBTPlatformName, UBTConfigurationName); if (IsForeignProject) { BuildArguments.AppendFormat(" -Project={0}", UProjectPath); } List ExtraTargets = new List(); if (!bUsePrecompiled) { if (TargetRulesObject.Type == TargetType.Editor && bEditorDependsOnShaderCompileWorker && !UnrealBuildTool.IsEngineInstalled()) { ExtraTargets.Add("ShaderCompileWorker Win64 Development"); } if (TargetRulesObject.bWithLiveCoding && bBuildLiveCodingConsole && !UnrealBuildTool.IsEngineInstalled()) { ExtraTargets.Add(TargetRulesObject.bUseDebugLiveCodingConsole? "LiveCodingConsole Win64 Debug" : "LiveCodingConsole Win64 Development"); } } if(ExtraTargets.Count > 0) { BuildArguments.Replace("\"", "\\\""); BuildArguments.Insert(0, "-Target=\""); BuildArguments.Append("\""); foreach(string ExtraTarget in ExtraTargets) { BuildArguments.AppendFormat(" -Target=\"{0}\"", ExtraTarget); } } if (bUsePrecompiled) { BuildArguments.Append(" -UsePrecompiled"); } // Always wait for the mutex between UBT invocations, so that building the whole solution doesn't fail. BuildArguments.Append(" -WaitMutex"); // Always include a flag to format log messages for MSBuild BuildArguments.Append(" -FromMsBuild"); if (bUseFastPDB) { // Pass Fast PDB option to make use of Visual Studio's /DEBUG:FASTLINK option BuildArguments.Append(" -FastPDB"); } DirectoryReference BatchFilesDirectory = DirectoryReference.Combine(UnrealBuildTool.EngineDirectory, "Build", "BatchFiles"); if(BuildToolOverride != null) { BuildArguments.AppendFormat(" {0}", BuildToolOverride); } // NMake Build command line VCProjectFileContent.AppendLine(" {0} {1}", EscapePath(NormalizeProjectPath(FileReference.Combine(BatchFilesDirectory, "Build.bat"))), BuildArguments.ToString()); VCProjectFileContent.AppendLine(" {0} {1}", EscapePath(NormalizeProjectPath(FileReference.Combine(BatchFilesDirectory, "Rebuild.bat"))), BuildArguments.ToString()); VCProjectFileContent.AppendLine(" {0} {1}", EscapePath(NormalizeProjectPath(FileReference.Combine(BatchFilesDirectory, "Clean.bat"))), BuildArguments.ToString()); VCProjectFileContent.AppendLine(" {0}", NormalizeProjectPath(NMakePath.FullName)); if (TargetRulesObject.Type == TargetType.Game || TargetRulesObject.Type == TargetType.Client || TargetRulesObject.Type == TargetType.Server) { // Allow platforms to add any special properties they require... like aumid override for Xbox One PlatformProjectGenerators.GenerateGamePlatformSpecificProperties(Platform, Configuration, TargetRulesObject.Type, VCProjectFileContent, RootDirectory, TargetFilePath); } VCProjectFileContent.AppendLine(" "); if(ProjGenerator != null) { VCProjectFileContent.Append(ProjGenerator.GetVisualStudioLayoutDirSection(Platform, Configuration, ConditionString, Combination.ProjectTarget.TargetRules.Type, Combination.ProjectTarget.TargetFilePath, ProjectFilePath, NMakePath, ProjectFileFormat)); } } if (VCUserFileContent != null && Combination.ProjectTarget != null) { TargetRules TargetRulesObject = Combination.ProjectTarget.TargetRules; if ((Combination.Platform == UnrealTargetPlatform.Win32) || (Combination.Platform == UnrealTargetPlatform.Win64)) { VCUserFileContent.AppendLine(" ", ConditionString); if (TargetRulesObject.Type != TargetType.Game) { string DebugOptions = ""; if (IsForeignProject) { DebugOptions += UProjectPath; DebugOptions += " -skipcompile"; } else if (TargetRulesObject.Type == TargetType.Editor && ProjectName != "UE4") { DebugOptions += ProjectName; } VCUserFileContent.AppendLine(" {0}", DebugOptions); } VCUserFileContent.AppendLine(" WindowsLocalDebugger"); VCUserFileContent.AppendLine(" "); } if(ProjGenerator != null) { VCUserFileContent.Append(ProjGenerator.GetVisualStudioUserFileStrings(Combination.Platform.Value, Configuration, ConditionString, TargetRulesObject, Combination.ProjectTarget.TargetFilePath, ProjectFilePath)); } } } } } /// /// A Visual C# project. /// class VCSharpProjectFile : MSBuildProjectFile { /// /// This is the GUID that Visual Studio uses to identify a C# project file in the solution /// public override string ProjectTypeGUID { get { return "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"; } } /// /// Platforms that this project supports /// public HashSet Platforms = new HashSet(); /// /// Configurations that this project supports /// public HashSet Configurations = new HashSet(); /// /// Constructs a new project file object /// /// The path to the project file on disk public VCSharpProjectFile(FileReference InitFilePath) : base(InitFilePath) { try { XmlDocument Document = new XmlDocument(); Document.Load(InitFilePath.FullName); // Check the root element is the right type if (Document.DocumentElement.Name != "Project") { throw new BuildException("Unexpected root element '{0}' in project file", Document.DocumentElement.Name); } // Parse all the configurations and platforms // Parse the basic structure of the document, updating properties and recursing into other referenced projects as we go foreach (XmlElement Element in Document.DocumentElement.ChildNodes.OfType()) { if(Element.Name == "PropertyGroup") { string Condition = Element.GetAttribute("Condition"); if(!String.IsNullOrEmpty(Condition)) { Match Match = Regex.Match(Condition, "^\\s*'\\$\\(Configuration\\)\\|\\$\\(Platform\\)'\\s*==\\s*'(.+)\\|(.+)'\\s*$"); if(Match.Success && Match.Groups.Count == 3) { Configurations.Add(Match.Groups[1].Value); Platforms.Add(Match.Groups[2].Value); } else { Log.TraceWarning("Unable to parse configuration/platform from condition '{0}': {1}", InitFilePath, Condition); } } } } } catch(Exception Ex) { Log.TraceWarning("Unable to parse {0}: {1}", InitFilePath, Ex.ToString()); } } /// /// Extract information from the csproj file based on the supplied configuration /// public CsProjectInfo GetProjectInfo(UnrealTargetConfiguration InConfiguration) { if (CachedProjectInfo.ContainsKey(InConfiguration)) { return CachedProjectInfo[InConfiguration]; } CsProjectInfo Info; Dictionary Properties = new Dictionary(); Properties.Add("Platform", "AnyCPU"); Properties.Add("Configuration", InConfiguration.ToString()); if (CsProjectInfo.TryRead(ProjectFilePath, Properties, out Info)) { CachedProjectInfo.Add(InConfiguration, Info); } return Info; } /// /// Determine if this project is a .NET Core project /// public bool IsDotNETCoreProject() { CsProjectInfo Info = GetProjectInfo(UnrealTargetConfiguration.Debug); return Info.IsDotNETCoreProject(); } /// /// Get the project context for the given solution context /// /// The solution target type /// The solution configuration /// The solution platform /// Set of platform project generators /// Project context matching the given solution context public override MSBuildProjectContext GetMatchingProjectContext(TargetType SolutionTarget, UnrealTargetConfiguration SolutionConfiguration, UnrealTargetPlatform SolutionPlatform, PlatformProjectGeneratorCollection PlatformProjectGenerators) { // Find the matching platform name string ProjectPlatformName; if(SolutionPlatform == UnrealTargetPlatform.Win32 && Platforms.Contains("x86")) { ProjectPlatformName = "x86"; } else if(Platforms.Contains("x64")) { ProjectPlatformName = "x64"; } else { ProjectPlatformName = "Any CPU"; } // Find the matching configuration string ProjectConfigurationName; if(Configurations.Contains(SolutionConfiguration.ToString())) { ProjectConfigurationName = SolutionConfiguration.ToString(); } else if(Configurations.Contains("Development")) { ProjectConfigurationName = "Development"; } else { ProjectConfigurationName = "Release"; } // Figure out whether to build it by default bool bBuildByDefault = ShouldBuildByDefaultForSolutionTargets; if(SolutionTarget == TargetType.Game || SolutionTarget == TargetType.Editor) { bBuildByDefault = true; } // Create the context return new MSBuildProjectContext(ProjectConfigurationName, ProjectPlatformName){ bBuildByDefault = bBuildByDefault }; } /// /// Basic csproj file support. Generates C# library project with one build config. /// /// Not used. /// Not Used. /// Set of platform project generators /// true if the opration was successful, false otherwise public override bool WriteProjectFile(List InPlatforms, List InConfigurations, PlatformProjectGeneratorCollection PlatformProjectGenerators) { throw new BuildException("Support for writing C# projects from UnrealBuildTool has been removed."); } /// Cache of parsed info about this project protected readonly Dictionary CachedProjectInfo = new Dictionary(); } }