// Copyright (c) Wojciech Figat. All rights reserved.
using Flax.Build.NativeCpp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Flax.Build.Projects.VisualStudio
{
///
/// The Visual Studio project generator for C++ projects (.vcxproj).
///
///
public class VCProjectGenerator : VisualStudioProjectGenerator
{
///
/// Gets the project file platform toolset version.
///
public string ProjectFilePlatformToolsetVersion
{
get
{
switch (Version)
{
case VisualStudioVersion.VisualStudio2015: return "v140";
case VisualStudioVersion.VisualStudio2017: return "v141";
case VisualStudioVersion.VisualStudio2019: return "v142";
case VisualStudioVersion.VisualStudio2022: return "v143";
case VisualStudioVersion.VisualStudio2026: return "v145";
}
return string.Empty;
}
}
///
public VCProjectGenerator(VisualStudioVersion version)
: base(version)
{
}
///
public override string ProjectFileExtension => "vcxproj";
///
public override TargetType? Type => TargetType.NativeCpp;
private static string FixPath(string path)
{
if (path.Contains(' '))
{
path = "\"" + path + "\"";
}
return path;
}
///
public override void GenerateProject(Project project, string solutionPath, bool isMainProject)
{
var vcProjectFileContent = new StringBuilder();
var vcFiltersFileContent = new StringBuilder();
var vcUserFileContent = new StringBuilder();
var vsProject = (VisualStudioProject)project;
var projectFileToolVersion = ProjectFileToolVersion;
var projectFilePlatformToolsetVersion = ProjectFilePlatformToolsetVersion;
var projectDirectory = Path.GetDirectoryName(project.Path);
var filtersDirectory = project.SourceFolderPath;
// Try to reuse the existing project guid from existing files
vsProject.ProjectGuid = GetProjectGuid(vsProject.Path, vsProject.Name);
if (vsProject.ProjectGuid == Guid.Empty)
vsProject.ProjectGuid = GetProjectGuid(solutionPath, vsProject.Name);
if (vsProject.ProjectGuid == Guid.Empty)
vsProject.ProjectGuid = Guid.NewGuid();
// Header
vcProjectFileContent.AppendLine("");
vcProjectFileContent.AppendLine(string.Format("", projectFileToolVersion));
vcFiltersFileContent.AppendLine("");
vcFiltersFileContent.AppendLine(string.Format("", projectFileToolVersion));
vcUserFileContent.AppendLine("");
vcUserFileContent.AppendLine(string.Format("", projectFileToolVersion));
// Project configurations
vcProjectFileContent.AppendLine(" ");
foreach (var configuration in project.Configurations)
{
vcProjectFileContent.AppendLine(string.Format(" ", configuration.Name));
vcProjectFileContent.AppendLine(string.Format(" {0}", configuration.Text));
vcProjectFileContent.AppendLine(string.Format(" {0}", configuration.ArchitectureName));
vcProjectFileContent.AppendLine(" ");
}
vcProjectFileContent.AppendLine(" ");
var platforms = vsProject.Configurations.Select(x => Platform.GetPlatform(x.Platform)).Distinct();
foreach (var platform in platforms)
{
if (platform is IVisualStudioProjectCustomizer customizer)
customizer.WriteVisualStudioBegin(vsProject, platform, vcProjectFileContent, vcFiltersFileContent, vcUserFileContent);
}
// Globals
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine(string.Format(" {0}", vsProject.ProjectGuid.ToString("B").ToUpperInvariant()));
vcProjectFileContent.AppendLine(string.Format(" {0}", project.BaseName));
vcProjectFileContent.AppendLine(string.Format(" {0}", projectFilePlatformToolsetVersion));
vcProjectFileContent.AppendLine(string.Format(" {0}", projectFileToolVersion));
vcProjectFileContent.AppendLine(" Native");
vcProjectFileContent.AppendLine(" Unicode");
vcProjectFileContent.AppendLine(" MakeFileProj");
if (Version >= VisualStudioVersion.VisualStudio2022)
vcProjectFileContent.AppendLine(" false");
vcProjectFileContent.AppendLine(" ./");
vcProjectFileContent.AppendLine(" ");
// Default properties
vcProjectFileContent.AppendLine(" ");
// Per configuration options
foreach (var configuration in project.Configurations)
{
vcProjectFileContent.AppendLine(string.Format(" ", configuration.Name));
vcProjectFileContent.AppendLine(" Makefile");
vcProjectFileContent.AppendLine(string.Format(" {0}", projectFilePlatformToolsetVersion));
vcProjectFileContent.AppendLine(" ");
}
// Other properties
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine(" ");
// Per configuration property sheets
foreach (var configuration in project.Configurations)
{
vcProjectFileContent.AppendLine(string.Format(" ", configuration.Name));
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine(" ");
}
// User macros
vcProjectFileContent.AppendLine(" ");
// Per configuration options
var buildToolPath = Path.ChangeExtension(Utilities.MakePathRelativeTo(typeof(Builder).Assembly.Location, projectDirectory), null);
var preprocessorDefinitions = new HashSet();
var includePaths = new HashSet();
foreach (var configuration in project.Configurations)
{
var platform = Platform.GetPlatform(configuration.Platform);
var toolchain = platform.GetToolchain(configuration.Architecture);
var target = configuration.Target;
var targetBuildOptions = configuration.TargetBuildOptions;
preprocessorDefinitions.Clear();
foreach (var e in targetBuildOptions.CompileEnv.PreprocessorDefinitions)
preprocessorDefinitions.Add(e);
includePaths.Clear();
foreach (var e in targetBuildOptions.CompileEnv.IncludePaths)
includePaths.Add(e);
var outputTargetFilePath = target.GetOutputFilePath(targetBuildOptions, project.OutputType);
// Build command for the build tool
var cmdLine = string.Format("{0} -log -mutex -workspace={1} -arch={2} -configuration={3} -platform={4} -buildTargets={5}",
FixPath(buildToolPath),
FixPath(project.WorkspaceRootPath),
configuration.Architecture,
configuration.Configuration,
configuration.Platform,
target.Name);
Configuration.PassArgs(ref cmdLine);
vcProjectFileContent.AppendLine(string.Format(" ", configuration.Name));
if (platform is IVisualStudioProjectCustomizer customizer)
customizer.WriteVisualStudioBuildProperties(vsProject, platform, toolchain, configuration, vcProjectFileContent, vcFiltersFileContent, vcUserFileContent);
vcProjectFileContent.AppendLine(string.Format(" {0}", targetBuildOptions.IntermediateFolder));
vcProjectFileContent.AppendLine(string.Format(" {0}", targetBuildOptions.OutputFolder));
if (includePaths.Count != 0)
vcProjectFileContent.AppendLine(string.Format(" $(IncludePath);{0}", string.Join(";", includePaths)));
else
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine(string.Format(" {0} -build", cmdLine));
vcProjectFileContent.AppendLine(string.Format(" {0} -rebuild", cmdLine));
vcProjectFileContent.AppendLine(string.Format(" {0} -clean", cmdLine));
vcProjectFileContent.AppendLine(string.Format(" {0}", outputTargetFilePath));
if (preprocessorDefinitions.Count != 0)
vcProjectFileContent.AppendLine(string.Format(" $(NMakePreprocessorDefinitions);{0}", string.Join(";", preprocessorDefinitions)));
if (includePaths.Count != 0)
vcProjectFileContent.AppendLine(string.Format(" $(NMakeIncludeSearchPath);{0}", string.Join(";", includePaths)));
var additionalOptions = new List();
additionalOptions.Add("$(AdditionalOptions)");
switch (configuration.TargetBuildOptions.CompileEnv.CppVersion)
{
case CppVersion.Cpp14:
additionalOptions.Add("/std:c++14");
break;
case CppVersion.Cpp17:
additionalOptions.Add("/std:c++17");
break;
case CppVersion.Cpp20:
additionalOptions.Add("/std:c++20");
break;
case CppVersion.Latest:
additionalOptions.Add("/std:c++latest");
break;
}
vcProjectFileContent.AppendLine(string.Format(" {0}", string.Join(" ", additionalOptions)));
vcProjectFileContent.AppendLine(" ");
}
// Files and folders
vcProjectFileContent.AppendLine(" ");
vcFiltersFileContent.AppendLine(" ");
var files = new List();
if (project.SourceFiles != null)
files.AddRange(project.SourceFiles);
if (project.SourceDirectories != null)
{
foreach (var folder in project.SourceDirectories)
{
files.AddRange(Directory.GetFiles(folder, "*", SearchOption.AllDirectories));
}
}
var filterDirectories = new HashSet();
foreach (var file in files)
{
string fileType;
if (file.EndsWith(".h", StringComparison.OrdinalIgnoreCase) || file.EndsWith(".inl", StringComparison.OrdinalIgnoreCase))
{
fileType = "ClInclude";
}
else if (file.EndsWith(".cpp", StringComparison.OrdinalIgnoreCase) || file.EndsWith(".cc", StringComparison.OrdinalIgnoreCase))
{
fileType = "ClCompile";
}
else if (file.EndsWith(".rc", StringComparison.OrdinalIgnoreCase))
{
fileType = "ResourceCompile";
}
else if (file.EndsWith(".manifest", StringComparison.OrdinalIgnoreCase))
{
fileType = "Manifest";
}
else if (file.EndsWith(".cs", StringComparison.OrdinalIgnoreCase))
{
// C# files are added to .csproj
continue;
}
else if (file.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase))
{
// Skip C# project files
continue;
}
else
{
fileType = "None";
}
var projectPath = Utilities.MakePathRelativeTo(file, projectDirectory);
vcProjectFileContent.AppendLine(string.Format(" <{0} Include=\"{1}\"/>", fileType, projectPath));
string filterPath = Utilities.MakePathRelativeTo(file, filtersDirectory);
int lastSeparatorIdx = filterPath.LastIndexOf(Path.DirectorySeparatorChar);
filterPath = lastSeparatorIdx == -1 ? string.Empty : filterPath.Substring(0, lastSeparatorIdx);
if (!string.IsNullOrWhiteSpace(filterPath))
{
// Add directories to the filters for this file
string pathRemaining = filterPath;
if (!filterDirectories.Contains(pathRemaining))
{
List allDirectoriesInPath = new List();
string currentPath = string.Empty;
while (true)
{
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 = string.Empty;
}
if (!string.IsNullOrEmpty(currentPath))
{
currentPath += Path.DirectorySeparatorChar;
}
currentPath += splitDirectory;
allDirectoriesInPath.Add(currentPath);
}
else
{
break;
}
}
for (var i = 0; i < allDirectoriesInPath.Count; i++)
{
string leadingDirectory = allDirectoriesInPath[i];
if (!filterDirectories.Contains(leadingDirectory))
{
filterDirectories.Add(leadingDirectory);
string filterGuid = Guid.NewGuid().ToString("B").ToUpperInvariant();
vcFiltersFileContent.AppendLine(string.Format(" ", leadingDirectory));
vcFiltersFileContent.AppendLine(string.Format(" {0}", filterGuid));
vcFiltersFileContent.AppendLine(" ");
}
}
}
vcFiltersFileContent.AppendLine(string.Format(" <{0} Include=\"{1}\">", fileType, projectPath));
vcFiltersFileContent.AppendLine(string.Format(" {0}", filterPath));
vcFiltersFileContent.AppendLine(string.Format(" {0}>", fileType));
}
else
{
vcFiltersFileContent.AppendLine(string.Format(" <{0} Include=\"{1}\" />", fileType, projectPath));
}
}
vcProjectFileContent.AppendLine(" ");
vcFiltersFileContent.AppendLine(" ");
{
// IntelliSense information
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine(string.Format(" $(NMakePreprocessorDefinitions){0}", (project.Defines.Count > 0 ? (";" + string.Join(";", project.Defines)) : "")));
vcProjectFileContent.AppendLine(string.Format(" $(NMakeIncludeSearchPath){0}", (project.SearchPaths.Length > 0 ? (";" + string.Join(";", project.SearchPaths)) : "")));
vcProjectFileContent.AppendLine(" $(NMakeForcedIncludes)");
vcProjectFileContent.AppendLine(" $(NMakeAssemblySearchPath)");
vcProjectFileContent.AppendLine(" $(NMakeForcedUsingAssemblies)");
vcProjectFileContent.AppendLine(" $(AdditionalOptions)");
vcProjectFileContent.AppendLine(" ");
}
foreach (var platform in platforms)
{
if (platform is IVisualStudioProjectCustomizer customizer)
customizer.WriteVisualStudioEnd(vsProject, platform, vcProjectFileContent, vcFiltersFileContent, vcUserFileContent);
}
// End
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine(" ");
vcProjectFileContent.AppendLine("");
vcFiltersFileContent.AppendLine("");
vcUserFileContent.AppendLine("");
if (platforms.Any(x => x.Target == TargetPlatform.Linux))
{
// Override MSBuild .targets file with one that runs NMake commands (workaround for Rider on Linux)
var cppTargetsFileContent = new StringBuilder();
cppTargetsFileContent.AppendLine("");
cppTargetsFileContent.AppendLine(" ");
cppTargetsFileContent.AppendLine(" ");
cppTargetsFileContent.AppendLine(" ");
cppTargetsFileContent.AppendLine(" ");
cppTargetsFileContent.AppendLine(" ");
cppTargetsFileContent.AppendLine(" ");
cppTargetsFileContent.AppendLine(" ");
cppTargetsFileContent.AppendLine(" ");
cppTargetsFileContent.AppendLine(" ");
cppTargetsFileContent.AppendLine(" ");
cppTargetsFileContent.AppendLine(" ");
cppTargetsFileContent.AppendLine(" $(RootNamespace)$(Configuration.Split('.')[0])");
cppTargetsFileContent.AppendLine(" $(OutDir)/$(TargetName)$(TargetExt)");
cppTargetsFileContent.AppendLine(" ");
cppTargetsFileContent.AppendLine("");
Utilities.WriteFileIfChanged(Path.Combine(projectDirectory, "Microsoft.Cpp.targets"), cppTargetsFileContent.ToString());
Utilities.WriteFileIfChanged(Path.Combine(projectDirectory, "Microsoft.Cpp.Default.props"), vcUserFileContent.ToString());
Utilities.WriteFileIfChanged(Path.Combine(projectDirectory, "Microsoft.Cpp.props"), vcUserFileContent.ToString());
}
// Save the files
Utilities.WriteFileIfChanged(project.Path, vcProjectFileContent.ToString());
Utilities.WriteFileIfChanged(project.Path + ".filters", vcFiltersFileContent.ToString());
Utilities.WriteFileIfChanged(project.Path + ".user", vcUserFileContent.ToString());
}
}
}