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.
This commit is contained in:
@@ -80,7 +80,17 @@ Follow the instructions below to compile and run the engine from source.
|
||||
* Open workspace with XCode or Visual Studio Code
|
||||
* Build and run (configuration `Editor.Mac.Development`)
|
||||
|
||||
#### Troubleshooting
|
||||
## CLion
|
||||
|
||||
CLion support is provided through generated CMake facade project files for native C++ development on desktop host platforms: Windows, Linux, and Mac. Generate them by passing `-clion` to the project generation script, for example `GenerateProjectFiles.bat -clion`, `./GenerateProjectFiles.sh -clion`, or `GenerateProjectFiles.command -clion`.
|
||||
|
||||
The generated CMake project is written to `Cache/Projects/CMake/<ProjectName>`. Open that directory in CLion as a CMake project. Flax does not generate `.idea` files; CLion owns and updates its own local IDE settings after the project is opened.
|
||||
|
||||
This CMake project is an IDE facade, not the primary Flax build system. CMake is used to describe the code model and expose build presets, while actual native build steps are delegated to `Flax.Build`. Run configurations launch the generated engine executable after building it.
|
||||
|
||||
CLion project generation intentionally excludes platforms that are not supported by this workflow, including Android, iOS, UWP, Web, GDK/Xbox, PlayStation, and Switch. The generated CLion project does not configure C# debugging; use an IDE or editor with .NET debugging support for managed code.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* `Could not execute because the specified command or file was not found.`
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing
|
||||
var codeEditing = Editor.Instance.CodeEditing;
|
||||
var vsCode = codeEditing.GetInBuildEditor(CodeEditorTypes.VSCode);
|
||||
var rider = codeEditing.GetInBuildEditor(CodeEditorTypes.Rider);
|
||||
var clion = codeEditing.GetInBuildEditor(CodeEditorTypes.CLion);
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
// Favor the newest Visual Studio
|
||||
@@ -66,6 +67,8 @@ namespace FlaxEditor.Modules.SourceCodeEditing
|
||||
_currentEditor = vsCode;
|
||||
else if (rider != null)
|
||||
_currentEditor = rider;
|
||||
else if (clion != null)
|
||||
_currentEditor = clion;
|
||||
else
|
||||
_currentEditor = codeEditing.GetInBuildEditor(CodeEditorTypes.SystemDefault);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "ScriptsBuilder.h"
|
||||
#include "CodeEditors/VisualStudioCodeEditor.h"
|
||||
#include "CodeEditors/RiderCodeEditor.h"
|
||||
#include "CodeEditors/CLionCodeEditor.h"
|
||||
#if USE_VISUAL_STUDIO_DTE
|
||||
#include "CodeEditors/VisualStudio/VisualStudioEditor.h"
|
||||
#endif
|
||||
@@ -269,6 +270,7 @@ bool CodeEditingManagerService::Init()
|
||||
#endif
|
||||
VisualStudioCodeEditor::FindEditors(&CodeEditors);
|
||||
RiderCodeEditor::FindEditors(&CodeEditors);
|
||||
CLionCodeEditor::FindEditors(&CodeEditors);
|
||||
CodeEditors.Add(New<SystemDefaultCodeEditor>());
|
||||
|
||||
return false;
|
||||
|
||||
@@ -82,6 +82,11 @@ API_ENUM(Namespace="FlaxEditor", Attributes="HideInEditor") enum class CodeEdito
|
||||
/// </summary>
|
||||
Rider,
|
||||
|
||||
/// <summary>
|
||||
/// CLion
|
||||
/// </summary>
|
||||
CLion,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "CLionCodeEditor.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Core/Collections/Sorting.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Engine/Platform/CreateProcessSettings.h"
|
||||
#include "Engine/Platform/File.h"
|
||||
#include "Engine/Serialization/Json.h"
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/ProjectInfo.h"
|
||||
#include "Editor/Scripting/ScriptsBuilder.h"
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
|
||||
#elif PLATFORM_MAC
|
||||
#include "Engine/Platform/Apple/AppleUtils.h"
|
||||
#include <AppKit/AppKit.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
struct CLionInstallation
|
||||
{
|
||||
String path;
|
||||
String argumentsPrefix;
|
||||
String version;
|
||||
|
||||
CLionInstallation(const String& path_, const String& argumentsPrefix_, const String& version_)
|
||||
: path(path_)
|
||||
, argumentsPrefix(argumentsPrefix_)
|
||||
, version(version_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void AddInstallation(Array<CLionInstallation*>* installations, String path, const String& version, const String& argumentsPrefix = String::Empty)
|
||||
{
|
||||
if (path.IsEmpty())
|
||||
return;
|
||||
|
||||
StringUtils::PathRemoveRelativeParts(path);
|
||||
for (CLionInstallation* installation : *installations)
|
||||
{
|
||||
if (installation->path == path && installation->argumentsPrefix == argumentsPrefix)
|
||||
return;
|
||||
}
|
||||
installations->Add(New<CLionInstallation>(path, argumentsPrefix, version));
|
||||
}
|
||||
|
||||
bool IsCLionProduct(rapidjson_flax::Document& document)
|
||||
{
|
||||
auto productCodeMember = document.FindMember("productCode");
|
||||
if (productCodeMember != document.MemberEnd() && productCodeMember->value == "CL")
|
||||
return true;
|
||||
|
||||
auto nameMember = document.FindMember("name");
|
||||
if (nameMember == document.MemberEnd())
|
||||
return false;
|
||||
|
||||
const String name = nameMember->value.GetText();
|
||||
return name == TEXT("CLion") || name == TEXT("JetBrains CLion");
|
||||
}
|
||||
|
||||
void SearchDirectory(Array<CLionInstallation*>* installations, const String& directory, String launchOverridePath = String::Empty, String launchArgumentsPrefix = String::Empty)
|
||||
{
|
||||
if (!FileSystem::DirectoryExists(directory))
|
||||
return;
|
||||
|
||||
// Load product info
|
||||
Array<byte> productInfoData;
|
||||
const String productInfoPath = directory / TEXT("product-info.json");
|
||||
if (!FileSystem::FileExists(productInfoPath) || File::ReadAllBytes(productInfoPath, productInfoData))
|
||||
return;
|
||||
rapidjson_flax::Document document;
|
||||
document.Parse((char*)productInfoData.Get(), productInfoData.Count());
|
||||
if (document.HasParseError() || !IsCLionProduct(document))
|
||||
return;
|
||||
|
||||
// Find version
|
||||
auto versionMember = document.FindMember("version");
|
||||
if (versionMember == document.MemberEnd())
|
||||
return;
|
||||
|
||||
// Find executable file path
|
||||
auto launchMember = document.FindMember("launch");
|
||||
if (launchMember == document.MemberEnd() || !launchMember->value.IsArray() || launchMember->value.Size() == 0)
|
||||
return;
|
||||
|
||||
auto launcherPathMember = launchMember->value[0].FindMember("launcherPath");
|
||||
if (launcherPathMember == launchMember->value[0].MemberEnd())
|
||||
return;
|
||||
|
||||
auto launcherPath = launcherPathMember->value.GetText();
|
||||
auto exePath = directory / launcherPath;
|
||||
if (!launcherPath.HasChars() || !FileSystem::FileExists(exePath))
|
||||
return;
|
||||
|
||||
AddInstallation(installations, launchOverridePath != String::Empty ? launchOverridePath : exePath, versionMember->value.GetText(), launchArgumentsPrefix);
|
||||
}
|
||||
|
||||
void SearchExecutable(Array<CLionInstallation*>* installations, const String& path)
|
||||
{
|
||||
if (FileSystem::FileExists(path))
|
||||
AddInstallation(installations, path, TEXT("0.0.0"));
|
||||
}
|
||||
|
||||
void ParseVersion(const String& text, int32 version[3])
|
||||
{
|
||||
version[0] = 0;
|
||||
version[1] = 0;
|
||||
version[2] = 0;
|
||||
|
||||
Array<String> values;
|
||||
text.Split('.', values);
|
||||
for (int32 i = 0; i < values.Count() && i < 3; i++)
|
||||
StringUtils::Parse(values[i].Get(), &version[i]);
|
||||
}
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
bool FindRegistryKeyItems(HKEY hKey, Array<String>& results)
|
||||
{
|
||||
Char nameBuffer[256];
|
||||
for (int32 i = 0;; i++)
|
||||
{
|
||||
const LONG result = RegEnumKeyW(hKey, i, nameBuffer, ARRAY_COUNT(nameBuffer));
|
||||
if (result == ERROR_NO_MORE_ITEMS)
|
||||
break;
|
||||
if (result != ERROR_SUCCESS)
|
||||
return false;
|
||||
results.Add(nameBuffer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SearchRegistry(Array<CLionInstallation*>* installations, HKEY root, const Char* key, const Char* valueName = TEXT(""))
|
||||
{
|
||||
// Open key
|
||||
HKEY keyH;
|
||||
if (RegOpenKeyExW(root, key, 0, KEY_READ, &keyH) != ERROR_SUCCESS)
|
||||
return;
|
||||
|
||||
// Iterate over subkeys
|
||||
Array<String> subKeys;
|
||||
if (FindRegistryKeyItems(keyH, subKeys))
|
||||
{
|
||||
for (auto& subKey : subKeys)
|
||||
{
|
||||
HKEY subKeyH;
|
||||
if (RegOpenKeyExW(keyH, *subKey, 0, KEY_READ, &subKeyH) != ERROR_SUCCESS)
|
||||
continue;
|
||||
|
||||
// Read subkey value
|
||||
DWORD type;
|
||||
DWORD cbData;
|
||||
if (RegQueryValueExW(subKeyH, valueName, nullptr, &type, nullptr, &cbData) != ERROR_SUCCESS || type != REG_SZ)
|
||||
{
|
||||
RegCloseKey(subKeyH);
|
||||
continue;
|
||||
}
|
||||
Array<Char> data;
|
||||
data.Resize((int32)cbData / sizeof(Char));
|
||||
if (RegQueryValueExW(subKeyH, valueName, nullptr, nullptr, reinterpret_cast<LPBYTE>(data.Get()), &cbData) != ERROR_SUCCESS)
|
||||
{
|
||||
RegCloseKey(subKeyH);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if it's a valid installation path
|
||||
String path(data.Get(), data.Count() - 1);
|
||||
SearchDirectory(installations, path);
|
||||
|
||||
RegCloseKey(subKeyH);
|
||||
}
|
||||
}
|
||||
|
||||
RegCloseKey(keyH);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SortInstallations(CLionInstallation* const& i1, CLionInstallation* const& i2)
|
||||
{
|
||||
int32 version1[3], version2[3];
|
||||
ParseVersion(i1->version, version1);
|
||||
ParseVersion(i2->version, version2);
|
||||
|
||||
// Compare by MAJOR.MINOR.BUILD
|
||||
if (version1[0] == version2[0])
|
||||
{
|
||||
if (version1[1] == version2[1])
|
||||
return version1[2] > version2[2];
|
||||
return version1[1] > version2[1];
|
||||
}
|
||||
return version1[0] > version2[0];
|
||||
}
|
||||
|
||||
CLionCodeEditor::CLionCodeEditor(const String& execPath, const String& execArgsPrefix)
|
||||
: _execPath(execPath)
|
||||
, _execArgsPrefix(execArgsPrefix)
|
||||
, _projectPath(Globals::ProjectFolder / TEXT("Cache/Projects/CMake") / Editor::Project->Name)
|
||||
{
|
||||
}
|
||||
|
||||
String CLionCodeEditor::GetProcessArguments(const String& arguments) const
|
||||
{
|
||||
return _execArgsPrefix.HasChars() ? _execArgsPrefix + TEXT(" ") + arguments : arguments;
|
||||
}
|
||||
|
||||
void CLionCodeEditor::FindEditors(Array<CodeEditor*>* output)
|
||||
{
|
||||
Array<CLionInstallation*> installations;
|
||||
Array<String> subDirectories;
|
||||
|
||||
String localAppDataPath;
|
||||
FileSystem::GetSpecialFolderPath(SpecialFolder::LocalAppData, localAppDataPath);
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
// Lookup from all known registry locations
|
||||
SearchRegistry(&installations, HKEY_CURRENT_USER, TEXT("SOFTWARE\\JetBrains\\CLion"));
|
||||
SearchRegistry(&installations, HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\JetBrains\\CLion"));
|
||||
SearchRegistry(&installations, HKEY_CURRENT_USER, TEXT("SOFTWARE\\JetBrains\\CLion"), TEXT("InstallDir"));
|
||||
SearchRegistry(&installations, HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\JetBrains\\CLion"), TEXT("InstallDir"));
|
||||
SearchRegistry(&installations, HKEY_CURRENT_USER, TEXT("SOFTWARE\\WOW6432Node\\JetBrains\\CLion"));
|
||||
SearchRegistry(&installations, HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\WOW6432Node\\JetBrains\\CLion"));
|
||||
|
||||
// Versions installed via JetBrains Toolbox
|
||||
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("Programs"));
|
||||
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains\\Toolbox\\apps\\CLion\\ch-0\\"));
|
||||
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains\\Toolbox\\apps\\CLion\\ch-1\\")); // Beta versions
|
||||
#endif
|
||||
#if PLATFORM_LINUX
|
||||
// TODO: detect Snap installations by reading the desktop file from ~/.local/share/applications and /usr/share/applications.
|
||||
|
||||
SearchDirectory(&installations, TEXT("/usr/share/clion/"));
|
||||
FileSystem::GetChildDirectories(subDirectories, TEXT("/usr/share/clion"));
|
||||
|
||||
// Default suggested location for standalone installations
|
||||
FileSystem::GetChildDirectories(subDirectories, TEXT("/opt/"));
|
||||
|
||||
// Versions installed via JetBrains Toolbox
|
||||
SearchDirectory(&installations, localAppDataPath / TEXT("JetBrains/Toolbox/apps/clion/"));
|
||||
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains/Toolbox/apps/CLion/ch-0"));
|
||||
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains/Toolbox/apps/CLion/ch-1")); // Beta versions
|
||||
|
||||
// Detect Flatpak installations
|
||||
SearchDirectory(&installations,
|
||||
TEXT("/var/lib/flatpak/app/com.jetbrains.CLion/current/active/files/extra/clion/"),
|
||||
TEXT("flatpak"),
|
||||
TEXT("run com.jetbrains.CLion"));
|
||||
|
||||
SearchExecutable(&installations, TEXT("/usr/bin/clion"));
|
||||
SearchExecutable(&installations, TEXT("/snap/bin/clion"));
|
||||
#endif
|
||||
|
||||
#if PLATFORM_MAC
|
||||
String applicationSupportFolder;
|
||||
FileSystem::GetSpecialFolderPath(SpecialFolder::ProgramData, applicationSupportFolder);
|
||||
|
||||
NSURL* appURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:@"com.jetbrains.CLion"];
|
||||
if (appURL != nullptr)
|
||||
{
|
||||
const String appPath = AppleUtils::ToString((CFStringRef)[appURL path]);
|
||||
SearchDirectory(&installations, appPath / TEXT("Contents/Resources"), appPath);
|
||||
}
|
||||
|
||||
Array<String> subMacDirectories;
|
||||
FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/CLion/ch-0/"));
|
||||
FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/CLion/ch-1/"));
|
||||
for (const String& directory : subMacDirectories)
|
||||
{
|
||||
String clionAppPath = directory / TEXT("CLion.app");
|
||||
SearchDirectory(&installations, clionAppPath / TEXT("Contents/Resources"), clionAppPath);
|
||||
}
|
||||
|
||||
// Check the local installer version
|
||||
SearchDirectory(&installations, TEXT("/Applications/CLion.app/Contents/Resources"), TEXT("/Applications/CLion.app"));
|
||||
|
||||
String userFolder;
|
||||
FileSystem::GetSpecialFolderPath(SpecialFolder::Documents, userFolder);
|
||||
String clionAppPath = userFolder / TEXT("../Applications/CLion.app");
|
||||
SearchDirectory(&installations, clionAppPath / TEXT("Contents/Resources"), clionAppPath);
|
||||
#endif
|
||||
|
||||
for (const String& directory : subDirectories)
|
||||
SearchDirectory(&installations, directory);
|
||||
|
||||
// Sort found installations by version number
|
||||
Sorting::QuickSort(installations.Get(), installations.Count(), &SortInstallations);
|
||||
|
||||
for (CLionInstallation* installation : installations)
|
||||
{
|
||||
output->Add(New<CLionCodeEditor>(installation->path, installation->argumentsPrefix));
|
||||
Delete(installation);
|
||||
}
|
||||
}
|
||||
|
||||
CodeEditorTypes CLionCodeEditor::GetType() const
|
||||
{
|
||||
return CodeEditorTypes::CLion;
|
||||
}
|
||||
|
||||
String CLionCodeEditor::GetName() const
|
||||
{
|
||||
return TEXT("CLion");
|
||||
}
|
||||
|
||||
String CLionCodeEditor::GetGenerateProjectCustomArgs() const
|
||||
{
|
||||
return TEXT("-clion");
|
||||
}
|
||||
|
||||
void CLionCodeEditor::OpenFile(const String& path, int32 line)
|
||||
{
|
||||
// Generate project files if missing
|
||||
if (!FileSystem::FileExists(_projectPath / TEXT("CMakeLists.txt")) ||
|
||||
!FileSystem::FileExists(_projectPath / TEXT("CMakePresets.json")))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
|
||||
}
|
||||
|
||||
// Open file
|
||||
line = line > 0 ? line : 1;
|
||||
CreateProcessSettings procSettings;
|
||||
|
||||
#if !PLATFORM_MAC
|
||||
procSettings.FileName = _execPath;
|
||||
procSettings.Arguments = GetProcessArguments(String::Format(TEXT("\"{0}\" --line {2} \"{1}\""), _projectPath, path, line));
|
||||
#else
|
||||
procSettings.FileName = "/usr/bin/open";
|
||||
procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" --args \"{1}\" --line {3} \"{2}\""), _execPath, _projectPath, path, line);
|
||||
#endif
|
||||
|
||||
procSettings.HiddenWindow = false;
|
||||
procSettings.WaitForEnd = false;
|
||||
procSettings.LogOutput = false;
|
||||
procSettings.ShellExecute = true;
|
||||
Platform::CreateProcess(procSettings);
|
||||
}
|
||||
|
||||
void CLionCodeEditor::OpenSolution()
|
||||
{
|
||||
// Generate project files if missing
|
||||
if (!FileSystem::FileExists(_projectPath / TEXT("CMakeLists.txt")) ||
|
||||
!FileSystem::FileExists(_projectPath / TEXT("CMakePresets.json")))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
|
||||
}
|
||||
|
||||
// Open solution
|
||||
CreateProcessSettings procSettings;
|
||||
#if !PLATFORM_MAC
|
||||
procSettings.FileName = _execPath;
|
||||
procSettings.Arguments = GetProcessArguments(String::Format(TEXT("\"{0}\""), _projectPath));
|
||||
#else
|
||||
procSettings.FileName = "/usr/bin/open";
|
||||
procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" \"{1}\""), _execPath, _projectPath);
|
||||
#endif
|
||||
procSettings.HiddenWindow = false;
|
||||
procSettings.WaitForEnd = false;
|
||||
procSettings.LogOutput = false;
|
||||
procSettings.ShellExecute = true;
|
||||
Platform::CreateProcess(procSettings);
|
||||
}
|
||||
|
||||
void CLionCodeEditor::OnFileAdded(const String& path)
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Editor/Scripting/CodeEditor.h"
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of code editor utility that is using CLion from JetBrains.
|
||||
/// </summary>
|
||||
class CLionCodeEditor : public CodeEditor
|
||||
{
|
||||
private:
|
||||
|
||||
String _execPath;
|
||||
String _execArgsPrefix;
|
||||
String _projectPath;
|
||||
|
||||
String GetProcessArguments(const String& arguments) const;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CLionCodeEditor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="execPath">Executable file path</param>
|
||||
/// <param name="execArgsPrefix">Additional arguments to pass before CLion arguments.</param>
|
||||
CLionCodeEditor(const String& execPath, const String& execArgsPrefix = String::Empty);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find installed CLion instances. Adds them to the result list.
|
||||
/// </summary>
|
||||
/// <param name="output">The output editors.</param>
|
||||
static void FindEditors(Array<CodeEditor*>* output);
|
||||
|
||||
public:
|
||||
|
||||
// [CodeEditor]
|
||||
CodeEditorTypes GetType() const override;
|
||||
String GetName() const override;
|
||||
String GetGenerateProjectCustomArgs() const override;
|
||||
void OpenFile(const String& path, int32 line) override;
|
||||
void OpenSolution() override;
|
||||
void OnFileAdded(const String& path) override;
|
||||
};
|
||||
@@ -204,6 +204,8 @@ namespace Flax.Build
|
||||
projectFormats.Add(ProjectFormat.VisualStudio2015);
|
||||
if (Configuration.ProjectFormatVSCode)
|
||||
projectFormats.Add(ProjectFormat.VisualStudioCode);
|
||||
if (Configuration.ProjectFormatCLion)
|
||||
projectFormats.Add(ProjectFormat.CMake);
|
||||
if (Configuration.ProjectFormatRider)
|
||||
projectFormats.Add(ProjectFormat.VisualStudio2022);
|
||||
if (!string.IsNullOrEmpty(Configuration.ProjectFormatCustom))
|
||||
|
||||
@@ -213,6 +213,12 @@ namespace Flax.Build
|
||||
[CommandLine("vscode", "Generates Visual Studio Code project format files. Valid only with -genproject option.")]
|
||||
public static bool ProjectFormatVSCode = false;
|
||||
|
||||
/// <summary>
|
||||
/// Generates CMake facade project files for CLion. Valid only with -genproject option.
|
||||
/// </summary>
|
||||
[CommandLine("clion", "Generates CMake facade project files for CLion. Valid only with -genproject option.")]
|
||||
public static bool ProjectFormatCLion = false;
|
||||
|
||||
/// <summary>
|
||||
/// Generates Visual Studio 2022 project format files for Rider. Valid only with -genproject option.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,624 @@
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,11 @@ namespace Flax.Build.Projects
|
||||
/// </summary>
|
||||
VisualStudioCode,
|
||||
|
||||
/// <summary>
|
||||
/// CMake.
|
||||
/// </summary>
|
||||
CMake,
|
||||
|
||||
/// <summary>
|
||||
/// XCode.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Flax.Build.Projects.CMake;
|
||||
using Flax.Build.Projects.VisualStudio;
|
||||
using Flax.Build.Projects.VisualStudioCode;
|
||||
|
||||
@@ -150,6 +151,7 @@ namespace Flax.Build.Projects
|
||||
case ProjectFormat.VisualStudioCode: return type == TargetType.DotNet
|
||||
? (ProjectGenerator)new CSProjectGenerator(VisualStudioVersion.VisualStudio2015)
|
||||
: (ProjectGenerator)new VisualStudioCodeProjectGenerator();
|
||||
case ProjectFormat.CMake: return new CMakeProjectGenerator();
|
||||
case ProjectFormat.XCode: return new XCodeProjectGenerator();
|
||||
case ProjectFormat.Custom:
|
||||
if (CustomProjectTypes.TryGetValue(Configuration.ProjectFormatCustom, out var factory))
|
||||
|
||||
Reference in New Issue
Block a user