Files
FlaxEngine/Source/Tools/Flax.Build/Projects/CMake/CMakeProjectGenerator.cs
T
Andrei Gagua eaf06e9523 Add CLion CMake facade project generation
Add CLion as a supported editor workflow for native C++ development.
The new -clion generation option writes a CMake facade project under
Cache/Projects/CMake/<ProjectName>, exposing a CLion-friendly code model,
build presets, and launcher targets while keeping Flax.Build as the source
of truth for native builds.
2026-06-09 15:16:35 +03:00

625 lines
31 KiB
C#

// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Flax.Build.NativeCpp;
namespace Flax.Build.Projects.CMake
{
/// <summary>
/// Project generator for CMake-based IDE facade.
/// </summary>
public class CMakeProjectGenerator : ProjectGenerator
{
/// <inheritdoc />
public override string ProjectFileExtension => "cmake";
/// <inheritdoc />
public override string SolutionFileExtension => "cmake";
/// <inheritdoc />
public override TargetType? Type => null;
/// <inheritdoc />
public override void GenerateProject(Project project, string solutionPath, bool isMainProject)
{
// Not used, solution contains all CMake project definitions.
}
/// <inheritdoc />
public override void GenerateSolution(Solution solution)
{
var projectFolder = GetProjectFolder(solution);
if (!Directory.Exists(projectFolder))
Directory.CreateDirectory(projectFolder);
var generateCLionFiles = Configuration.ProjectFormatCLion;
var contents = new StringBuilder();
var buildPresets = new List<CMakeBuildPreset>();
var defaultBuildTarget = string.Empty;
var targetNames = new HashSet<string>();
var buildToolPath = Path.ChangeExtension(typeof(Builder).Assembly.Location, null) + Utilities.GetPlatformExecutableExt();
contents.AppendLine("cmake_minimum_required(VERSION 3.20)");
contents.AppendLine();
contents.AppendLine("# Generated by Flax.Build. Do not edit manually.");
contents.AppendLine("# CMake is used as an IDE facade. Actual builds are delegated to Flax.Build.");
contents.AppendLine($"project({EscapeIdentifier(solution.Name)} LANGUAGES C CXX)");
contents.AppendLine();
contents.AppendLine("set(CMAKE_EXPORT_COMPILE_COMMANDS ON)");
contents.AppendLine($"set(FLAX_WORKSPACE {QuotePath(solution.WorkspaceRootPath, solution.WorkspaceRootPath)})");
contents.AppendLine($"set(FLAX_BUILD_TOOL {QuotePath(buildToolPath, solution.WorkspaceRootPath)})");
if (DotNetSdk.Instance.IsValid)
contents.AppendLine($"set(FLAX_DOTNET_ROOT {QuotePath(DotNetSdk.Instance.RootPath, solution.WorkspaceRootPath)})");
contents.AppendLine();
foreach (var project in solution.Projects.Where(x => x.Type == TargetType.NativeCpp))
{
var visibleFiles = GetProjectFiles(project, generateCLionFiles);
var hasCodeModelConfiguration = TryGetCodeModelConfiguration(project, out var codeModelConfiguration);
// Use Flax.Build-selected compile units for the code model. The wider project scan is only for visible files.
var compileFiles = hasCodeModelConfiguration
? GetCodeModelCompileFiles(codeModelConfiguration, generateCLionFiles)
: visibleFiles.Where(x => IsCompileSourceFile(x, generateCLionFiles)).ToArray();
var files = new HashSet<string>(visibleFiles);
files.AddRange(compileFiles);
var allFiles = files.OrderBy(x => x).ToArray();
var compileFileSet = new HashSet<string>(compileFiles);
var nonCompileFiles = allFiles.Where(x => !compileFileSet.Contains(x)).ToArray();
var includePaths = hasCodeModelConfiguration ? GetIncludePaths(project, codeModelConfiguration) : Array.Empty<string>();
var definitions = hasCodeModelConfiguration ? GetPreprocessorDefinitions(project, codeModelConfiguration) : project.Defines.OrderBy(x => x).ToArray();
var cppStandard = hasCodeModelConfiguration ? GetCppStandard(codeModelConfiguration) : 14;
var ideTargetName = GetUniqueTargetName(targetNames, "IDE_" + project.Name);
contents.AppendLine($"# {project.Name}");
if (compileFiles.Length != 0)
{
contents.AppendLine($"add_library({ideTargetName} STATIC EXCLUDE_FROM_ALL)");
contents.AppendLine($"set_target_properties({ideTargetName} PROPERTIES EXCLUDE_FROM_ALL TRUE)");
AppendTargetSources(contents, ideTargetName, allFiles, solution.WorkspaceRootPath);
if (nonCompileFiles.Length != 0)
{
contents.AppendLine("set_source_files_properties(");
foreach (var file in nonCompileFiles)
contents.AppendLine($" {QuotePath(file, solution.WorkspaceRootPath)}");
contents.AppendLine(" PROPERTIES HEADER_FILE_ONLY TRUE");
contents.AppendLine(")");
}
contents.AppendLine($"set_target_properties({ideTargetName} PROPERTIES CXX_STANDARD {cppStandard} CXX_STANDARD_REQUIRED YES)");
AppendTargetPaths(contents, ideTargetName, "target_include_directories", "PRIVATE", includePaths, solution.WorkspaceRootPath);
AppendTargetValues(contents, ideTargetName, "target_compile_definitions", "PRIVATE", definitions);
}
else if (allFiles.Length != 0)
{
contents.AppendLine($"add_custom_target({ideTargetName} SOURCES");
foreach (var file in allFiles)
contents.AppendLine($" {QuotePath(file, solution.WorkspaceRootPath)}");
contents.AppendLine(")");
}
foreach (var configuration in project.Configurations)
{
if (generateCLionFiles && !IsCLionDevelopmentPlatform(configuration.Platform))
continue;
var buildTargetName = GetUniqueTargetName(targetNames, "Build_" + project.Name + "_" + configuration.Target.Name + "_" + configuration.PlatformName + "_" + configuration.ConfigurationName + "_" + configuration.ArchitectureName);
AppendBuildTarget(contents, buildTargetName, "-build", configuration);
var presetSuffix = project.Name + "-" + configuration.Target.Name + "-" + configuration.PlatformName + "-" + configuration.ConfigurationName + "-" + configuration.ArchitectureName;
if (IsHostConfiguration(configuration))
AddBuildPreset(buildPresets, "build-" + presetSuffix, "Build " + presetSuffix, buildTargetName);
if (defaultBuildTarget.Length == 0 && project == solution.MainProject && configuration.Platform == Platform.BuildPlatform.Target && configuration.Architecture == Platform.BuildTargetArchitecture && configuration.Configuration == TargetConfiguration.Development)
defaultBuildTarget = buildTargetName;
if (generateCLionFiles)
{
var runProgram = GetRunProgram(project, configuration, out var runArguments);
if (IsHostConfiguration(configuration) && !string.IsNullOrEmpty(runProgram))
{
var runTargetName = GetUniqueTargetName(targetNames, "App_" + project.Name + "_" + configuration.Target.Name + "_" + configuration.PlatformName + "_" + configuration.ConfigurationName + "_" + configuration.ArchitectureName);
AppendRunTarget(contents, runTargetName, runProgram, runArguments);
contents.AppendLine($"add_dependencies({runTargetName} {buildTargetName})");
contents.AppendLine();
}
}
}
contents.AppendLine();
}
if (!string.IsNullOrEmpty(defaultBuildTarget))
{
contents.AppendLine($"add_custom_target({EscapeIdentifier(solution.Name)}_Build ALL DEPENDS {defaultBuildTarget})");
contents.AppendLine();
}
Utilities.WriteFileIfChanged(Path.Combine(projectFolder, "CMakeLists.txt"), contents.ToString());
GeneratePresets(solution, projectFolder, buildPresets);
}
/// <summary>
/// Gets the directory that contains the generated CMake facade project.
/// </summary>
public static string GetProjectFolder(Solution solution)
{
return Path.Combine(solution.WorkspaceRootPath, "Cache", "Projects", "CMake", solution.Name);
}
private struct CMakeBuildPreset
{
public string Name;
public string DisplayName;
public string Target;
}
private static void AddBuildPreset(List<CMakeBuildPreset> presets, string name, string displayName, string target)
{
presets.Add(new CMakeBuildPreset
{
Name = EscapePresetName(name),
DisplayName = displayName,
Target = target,
});
}
private static void GeneratePresets(Solution solution, string projectFolder, List<CMakeBuildPreset> buildPresets)
{
var contents = new StringBuilder();
contents.AppendLine("{");
contents.AppendLine(" \"version\": 3,");
contents.AppendLine(" \"cmakeMinimumRequired\": {");
contents.AppendLine(" \"major\": 3,");
contents.AppendLine(" \"minor\": 20,");
contents.AppendLine(" \"patch\": 0");
contents.AppendLine(" },");
contents.AppendLine(" \"configurePresets\": [");
contents.AppendLine(" {");
contents.AppendLine(" \"name\": \"flax\",");
contents.AppendLine(" \"displayName\": \"Flax\",");
contents.AppendLine(" \"description\": \"Configure Flax CMake facade for IDE integration.\",");
contents.AppendLine(" \"generator\": \"Ninja\",");
contents.AppendLine(" \"binaryDir\": \"${sourceDir}/cmake-build-flax\",");
contents.AppendLine(" \"cacheVariables\": {");
contents.AppendLine(" \"CMAKE_BUILD_TYPE\": \"Debug\",");
contents.AppendLine(" \"CMAKE_EXPORT_COMPILE_COMMANDS\": \"ON\"");
contents.AppendLine(" }");
contents.AppendLine(" }");
contents.AppendLine(" ],");
contents.AppendLine(" \"buildPresets\": [");
for (var i = 0; i < buildPresets.Count; i++)
{
var preset = buildPresets[i];
contents.AppendLine(" {");
contents.AppendLine($" \"name\": {JsonString(preset.Name)},");
contents.AppendLine($" \"displayName\": {JsonString(preset.DisplayName)},");
contents.AppendLine(" \"configurePreset\": \"flax\",");
contents.AppendLine($" \"targets\": [{JsonString(preset.Target)}]");
contents.Append(" }");
if (i != buildPresets.Count - 1)
contents.Append(',');
contents.AppendLine();
}
contents.AppendLine(" ]");
contents.AppendLine("}");
Utilities.WriteFileIfChanged(Path.Combine(projectFolder, "CMakePresets.json"), contents.ToString());
}
private static void AppendBuildTarget(StringBuilder contents, string targetName, string action, Project.ConfigurationData configuration)
{
contents.AppendLine($"add_custom_target({targetName}");
contents.Append(" COMMAND ");
if (DotNetSdk.Instance.IsValid)
contents.Append("${CMAKE_COMMAND} -E env \"DOTNET_ROOT=${FLAX_DOTNET_ROOT}\" ");
contents.AppendLine($"\"${{FLAX_BUILD_TOOL}}\" {Quote(action)} \"-log\" \"-mutex\"");
contents.AppendLine(" \"-workspace=${FLAX_WORKSPACE}\"");
contents.AppendLine($" \"-arch={configuration.ArchitectureName}\"");
contents.AppendLine($" \"-configuration={configuration.ConfigurationName}\"");
contents.AppendLine($" \"-platform={configuration.PlatformName}\"");
contents.AppendLine($" \"-buildTargets={configuration.Target.Name}\"");
if (!string.IsNullOrEmpty(Configuration.Compiler))
contents.AppendLine($" \"-compiler={EscapeString(Configuration.Compiler)}\"");
if (!string.IsNullOrEmpty(Configuration.Dotnet))
contents.AppendLine($" \"-dotnet={EscapeString(Configuration.Dotnet)}\"");
if (Configuration.Sanitizers != Sanitizer.None)
contents.AppendLine($" \"-sanitizers={Configuration.Sanitizers}\"");
contents.AppendLine(" WORKING_DIRECTORY \"${FLAX_WORKSPACE}\"");
contents.AppendLine(" USES_TERMINAL");
contents.AppendLine(" VERBATIM");
contents.AppendLine(")");
contents.AppendLine();
}
private static void AppendRunTarget(StringBuilder contents, string targetName, string program, string[] arguments)
{
var sourcePath = $"${{CMAKE_CURRENT_BINARY_DIR}}/{targetName}.cpp";
contents.AppendLine($"file(WRITE \"{sourcePath}\" [=[");
contents.AppendLine("#include <cstdio>");
contents.AppendLine("#include <vector>");
contents.AppendLine("#ifdef _WIN32");
contents.AppendLine("#include <process.h>");
contents.AppendLine("#else");
contents.AppendLine("#include <unistd.h>");
contents.AppendLine("#endif");
contents.AppendLine("int main(int argc, char** argv) {");
contents.AppendLine($" const char* program = \"{CppString(program)}\";");
contents.AppendLine(" std::vector<const char*> args;");
contents.AppendLine(" args.reserve(argc + 1);");
contents.AppendLine(" args.push_back(program);");
foreach (var arg in arguments)
contents.AppendLine($" args.push_back(\"{CppString(arg)}\");");
contents.AppendLine(" for (int i = 1; i < argc; i++)");
contents.AppendLine(" args.push_back(argv[i]);");
contents.AppendLine(" args.push_back(nullptr);");
contents.AppendLine("#ifdef _WIN32");
contents.AppendLine(" _execv(args[0], args.data());");
contents.AppendLine("#else");
contents.AppendLine(" execv(args[0], const_cast<char* const*>(args.data()));");
contents.AppendLine("#endif");
contents.AppendLine(" perror(args[0]);");
contents.AppendLine(" return 1;");
contents.AppendLine("}");
contents.AppendLine("]=])");
contents.AppendLine($"add_executable({targetName} EXCLUDE_FROM_ALL \"{sourcePath}\")");
contents.AppendLine($"set_target_properties({targetName} PROPERTIES");
contents.AppendLine(" RUNTIME_OUTPUT_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}/FlaxLaunchers\"");
contents.AppendLine(")");
}
private static void AppendTargetSources(StringBuilder contents, string targetName, string[] files, string workspaceRoot)
{
contents.AppendLine($"target_sources({targetName} PRIVATE");
foreach (var file in files)
contents.AppendLine($" {QuotePath(file, workspaceRoot)}");
contents.AppendLine(")");
}
private static void AppendTargetPaths(StringBuilder contents, string targetName, string command, string visibility, string[] values, string workspaceRoot)
{
if (values.Length == 0)
return;
contents.AppendLine($"{command}({targetName} {visibility}");
foreach (var value in values)
contents.AppendLine($" {QuotePath(value, workspaceRoot)}");
contents.AppendLine(")");
}
private static void AppendTargetValues(StringBuilder contents, string targetName, string command, string visibility, string[] values)
{
if (values.Length == 0)
return;
contents.AppendLine($"{command}({targetName} {visibility}");
foreach (var value in values)
contents.AppendLine($" {Quote(value)}");
contents.AppendLine(")");
}
private static string[] GetProjectFiles(Project project, bool generateCLionFiles)
{
var files = new HashSet<string>();
if (project.SourceFiles != null)
files.AddRange(project.SourceFiles);
if (project.GeneratedSourceFiles != null)
files.AddRange(project.GeneratedSourceFiles);
if (project.SourceDirectories != null)
{
foreach (var folder in project.SourceDirectories)
{
if (Directory.Exists(folder))
files.AddRange(Directory.GetFiles(folder, "*", SearchOption.AllDirectories));
}
}
files.RemoveWhere(x =>
!IsProjectFile(x) ||
Path.GetFileName(x).Equals(".DS_Store", StringComparison.OrdinalIgnoreCase) ||
(generateCLionFiles && !IsCLionDevelopmentSourceFile(x)));
return files.OrderBy(x => x).ToArray();
}
private static string[] GetCodeModelCompileFiles(Project.ConfigurationData configuration, bool generateCLionFiles)
{
var files = new HashSet<string>();
var definitions = new HashSet<string>(configuration.TargetBuildOptions.CompileEnv.PreprocessorDefinitions);
AddCompileFiles(files, configuration.TargetBuildOptions.SourceFiles, generateCLionFiles, definitions);
if (configuration.Modules != null)
{
foreach (var module in configuration.Modules)
{
if (module.Key.BuildNativeCode)
AddCompileFiles(files, module.Value.SourceFiles, generateCLionFiles, definitions);
}
}
return files.OrderBy(x => x).ToArray();
}
private static void AddCompileFiles(HashSet<string> files, IEnumerable<string> sourceFiles, bool generateCLionFiles, HashSet<string> definitions = null)
{
if (sourceFiles == null)
return;
foreach (var file in sourceFiles)
{
if (IsCompileSourceFile(file, generateCLionFiles, definitions))
files.Add(file);
}
}
private static bool TryGetCodeModelConfiguration(Project project, out Project.ConfigurationData configuration)
{
foreach (var e in project.Configurations)
{
if (e.TargetBuildOptions != null &&
e.Platform == Platform.BuildPlatform.Target &&
e.Architecture == Platform.BuildTargetArchitecture &&
e.Configuration == TargetConfiguration.Development)
{
configuration = e;
return true;
}
}
foreach (var e in project.Configurations)
{
if (e.TargetBuildOptions != null)
{
configuration = e;
return true;
}
}
configuration = default;
return false;
}
private static bool IsHostConfiguration(Project.ConfigurationData configuration)
{
return configuration.Platform == Platform.BuildPlatform.Target &&
configuration.Architecture == Platform.BuildTargetArchitecture;
}
private static bool IsCLionDevelopmentPlatform(TargetPlatform platform)
{
switch (platform)
{
case TargetPlatform.Windows:
case TargetPlatform.Linux:
case TargetPlatform.Mac:
return true;
default:
return false;
}
}
private static string[] GetIncludePaths(Project project, Project.ConfigurationData configuration)
{
var result = new HashSet<string>();
if (project.SearchPaths != null)
result.AddRange(project.SearchPaths);
result.AddRange(configuration.TargetBuildOptions.CompileEnv.IncludePaths);
return result.OrderBy(x => x).ToArray();
}
private static string[] GetPreprocessorDefinitions(Project project, Project.ConfigurationData configuration)
{
var result = new HashSet<string>();
result.AddRange(project.Defines);
result.AddRange(configuration.TargetBuildOptions.CompileEnv.PreprocessorDefinitions);
return result.OrderBy(x => x).ToArray();
}
private static int GetCppStandard(Project.ConfigurationData configuration)
{
var result = 14;
switch (configuration.TargetBuildOptions.CompileEnv.CppVersion)
{
case CppVersion.Cpp17:
result = Math.Max(result, 17);
break;
case CppVersion.Cpp20:
case CppVersion.Latest:
result = Math.Max(result, 20);
break;
}
return result;
}
private static string GetRunProgram(Project project, Project.ConfigurationData configuration, out string[] arguments)
{
var target = configuration.Target;
var outputType = project.OutputType ?? target.OutputType;
if (ShouldRunEditorForProject(configuration, outputType))
{
var program = Path.Combine(Globals.EngineRoot, Platform.GetEditorBinaryDirectory(), configuration.ConfigurationName, "FlaxEditor" + Utilities.GetPlatformExecutableExt());
var args = new List<string>();
if (configuration.Platform == TargetPlatform.Linux || configuration.Platform == TargetPlatform.Mac)
args.Add("-std");
args.Add("-project");
args.Add(project.WorkspaceRootPath);
args.Add("-skipCompile");
arguments = args.ToArray();
return program;
}
arguments = configuration.Platform == TargetPlatform.Linux || configuration.Platform == TargetPlatform.Mac ? new[] { "--std" } : Array.Empty<string>();
return target.GetOutputFilePath(configuration.TargetBuildOptions, TargetOutputType.Executable);
}
private static bool ShouldRunEditorForProject(Project.ConfigurationData configuration, TargetOutputType outputType)
{
return outputType != TargetOutputType.Executable && configuration.Target.IsEditor;
}
private static bool IsCppCompileFile(string path)
{
var extension = Path.GetExtension(path);
return extension.Equals(".c", StringComparison.OrdinalIgnoreCase) ||
extension.Equals(".cc", StringComparison.OrdinalIgnoreCase) ||
extension.Equals(".cpp", StringComparison.OrdinalIgnoreCase) ||
extension.Equals(".cxx", StringComparison.OrdinalIgnoreCase);
}
private static bool IsCompileSourceFile(string path, bool generateCLionFiles, HashSet<string> definitions = null)
{
return IsCppCompileFile(path) &&
(!generateCLionFiles || (IsCLionDevelopmentSourceFile(path) && IsCLionSourceGuardActive(path, definitions)));
}
private static bool IsCLionDevelopmentSourceFile(string path)
{
path = Utilities.NormalizePath(path);
return !ContainsPathSegment(path, "Android") &&
!ContainsPathSegment(path, "AndroidJNI") &&
!ContainsPathSegment(path, "GDK") &&
!ContainsPathSegment(path, "iOS") &&
!ContainsPathSegment(path, "PS4") &&
!ContainsPathSegment(path, "PS5") &&
!ContainsPathSegment(path, "Switch") &&
!ContainsPathSegment(path, "UWP") &&
!ContainsPathSegment(path, "Web") &&
!ContainsPathSegment(path, "XboxOne") &&
!ContainsPathSegment(path, "XboxScarlett") &&
!Path.GetFileName(path).Contains(".Web.", StringComparison.OrdinalIgnoreCase);
}
private static bool ContainsPathSegment(string path, string segment)
{
return path.IndexOf("/" + segment + "/", StringComparison.OrdinalIgnoreCase) != -1;
}
private static bool IsCLionSourceGuardActive(string path, HashSet<string> definitions)
{
if (definitions == null)
return true;
path = Utilities.NormalizePath(path);
if (ContainsSourcePath(path, "Source/Editor/Cooker/Platform/Windows"))
return HasDefinition(definitions, "PLATFORM_TOOLS_WINDOWS");
if (ContainsSourcePath(path, "Source/Editor/Cooker/Platform/Linux"))
return HasDefinition(definitions, "PLATFORM_TOOLS_LINUX");
if (ContainsSourcePath(path, "Source/Editor/Cooker/Platform/Mac"))
return HasDefinition(definitions, "PLATFORM_TOOLS_MAC");
if (ContainsSourcePath(path, "Source/Engine/Engine/Windows"))
return HasDefinition(definitions, "PLATFORM_WINDOWS") && !HasDefinition(definitions, "USE_EDITOR");
if (ContainsSourcePath(path, "Source/Engine/Engine/Linux"))
return HasDefinition(definitions, "PLATFORM_LINUX") && !HasDefinition(definitions, "USE_EDITOR");
if (ContainsSourcePath(path, "Source/Engine/Engine/Mac"))
return HasDefinition(definitions, "PLATFORM_MAC") && !HasDefinition(definitions, "USE_EDITOR");
if (ContainsSourcePath(path, "Source/Engine/GraphicsDevice/Vulkan/Win32"))
return HasDefinition(definitions, "GRAPHICS_API_VULKAN") && (HasDefinition(definitions, "PLATFORM_WIN32") || HasDefinition(definitions, "PLATFORM_WINDOWS"));
if (ContainsSourcePath(path, "Source/Engine/GraphicsDevice/Vulkan/Linux"))
return HasDefinition(definitions, "GRAPHICS_API_VULKAN") && HasDefinition(definitions, "PLATFORM_LINUX");
if (ContainsSourcePath(path, "Source/Engine/GraphicsDevice/Vulkan/Mac"))
return HasDefinition(definitions, "GRAPHICS_API_VULKAN") && HasDefinition(definitions, "PLATFORM_MAC");
if (ContainsSourcePath(path, "Source/Engine/Platform/Win32"))
return HasDefinition(definitions, "PLATFORM_WIN32") || HasDefinition(definitions, "PLATFORM_WINDOWS");
if (ContainsSourcePath(path, "Source/Engine/Platform/Windows"))
return HasDefinition(definitions, "PLATFORM_WINDOWS");
if (ContainsSourcePath(path, "Source/Engine/Platform/Linux"))
return HasDefinition(definitions, "PLATFORM_LINUX");
if (ContainsSourcePath(path, "Source/Engine/Platform/Mac"))
return HasDefinition(definitions, "PLATFORM_MAC");
if (ContainsSourcePath(path, "Source/Engine/Platform/Apple"))
return HasDefinition(definitions, "PLATFORM_MAC") || HasDefinition(definitions, "PLATFORM_IOS");
if (ContainsSourcePath(path, "Source/Engine/Platform/Unix"))
return HasDefinition(definitions, "PLATFORM_UNIX");
return true;
}
private static bool ContainsSourcePath(string path, string sourcePath)
{
return path.IndexOf("/" + sourcePath + "/", StringComparison.OrdinalIgnoreCase) != -1;
}
private static bool HasDefinition(HashSet<string> definitions, string name)
{
return definitions.Contains(name) ||
definitions.Any(x => x.StartsWith(name + "=", StringComparison.Ordinal));
}
private static bool IsProjectFile(string path)
{
var extension = Path.GetExtension(path);
return IsCppCompileFile(path) ||
extension.Equals(".h", StringComparison.OrdinalIgnoreCase) ||
extension.Equals(".hh", StringComparison.OrdinalIgnoreCase) ||
extension.Equals(".hpp", StringComparison.OrdinalIgnoreCase) ||
extension.Equals(".hxx", StringComparison.OrdinalIgnoreCase) ||
extension.Equals(".inl", StringComparison.OrdinalIgnoreCase) ||
extension.Equals(".rc", StringComparison.OrdinalIgnoreCase) ||
extension.Equals(".manifest", StringComparison.OrdinalIgnoreCase) ||
extension.Equals(".natvis", StringComparison.OrdinalIgnoreCase);
}
private static string GetUniqueTargetName(HashSet<string> names, string name)
{
var result = EscapeIdentifier(name);
var baseName = result;
var index = 2;
while (!names.Add(result))
result = baseName + "_" + index++;
return result;
}
private static string EscapeIdentifier(string value)
{
if (string.IsNullOrEmpty(value))
return "_";
var result = new StringBuilder(value.Length);
foreach (var c in value)
result.Append(char.IsLetterOrDigit(c) || c == '_' ? c : '_');
if (char.IsDigit(result[0]))
result.Insert(0, '_');
return result.ToString();
}
private static string EscapePresetName(string value)
{
var result = new StringBuilder(value.Length);
foreach (var c in value)
result.Append(char.IsLetterOrDigit(c) || c == '-' || c == '_' || c == '.' ? char.ToLowerInvariant(c) : '-');
return result.ToString();
}
private static string QuotePath(string value, string workspaceRoot, bool includeQuotes = true)
{
value = Utilities.NormalizePath(value);
workspaceRoot = Utilities.NormalizePath(workspaceRoot).TrimEnd('/');
if (value.StartsWith(workspaceRoot + "/", StringComparison.Ordinal))
value = "${FLAX_WORKSPACE}/" + value.Substring(workspaceRoot.Length + 1);
return Quote(value, includeQuotes);
}
private static string Quote(string value, bool includeQuotes = true)
{
value = EscapeString(value);
return includeQuotes ? "\"" + value + "\"" : value;
}
private static string EscapeString(string value)
{
return value.Replace("\\", "\\\\").Replace(";", "\\;").Replace("\"", "\\\"");
}
private static string JsonString(string value)
{
return "\"" + value.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
}
private static string CppString(string value)
{
return value.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r");
}
}
}