Merge branch 'slnx' of https://github.com/Tryibion/FlaxEngine into Tryibion-slnx
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# Flax stuff
|
||||
*.sln
|
||||
*.slnx
|
||||
*.slnx.DotSettings
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
obj/
|
||||
@@ -19,6 +21,7 @@ PackagePlatforms_Cert.bat
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
*.slnx.docstates
|
||||
*.idb
|
||||
*.tlog
|
||||
*.lastbuilddate
|
||||
|
||||
@@ -31,7 +31,7 @@ Use these commands from the repo root.
|
||||
Generate project files:
|
||||
|
||||
```powershell
|
||||
.\GenerateProjectFiles.bat -vs2022 -log -verbose -printSDKs -dotnet=8
|
||||
.\GenerateProjectFiles.bat -vs2026 -log -verbose -printSDKs -dotnet=8
|
||||
```
|
||||
|
||||
Alternative default generation:
|
||||
@@ -54,7 +54,7 @@ Run the editor:
|
||||
|
||||
Visual Studio workflow after generation:
|
||||
|
||||
- Open `Flax.sln`.
|
||||
- Open `Flax.slnx`.
|
||||
- Use solution configuration `Editor.Development` and platform `Win64`.
|
||||
- Set `Flax` or `FlaxEngine` as the startup project.
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ Follow the instructions below to compile and run the engine from source.
|
||||
* Install Git with LFS
|
||||
* Clone repo (with LFS)
|
||||
* Run **GenerateProjectFiles.bat**
|
||||
* Open `Flax.sln` and set solution configuration to **Editor.Development** and solution platform to **Win64**
|
||||
* Open `Flax.slnx` and set solution configuration to **Editor.Development** and solution platform to **Win64**
|
||||
* Set Flax (C++) or FlaxEngine (C#) as startup project
|
||||
* Compile Flax project (hit F7 or CTRL+Shift+B)
|
||||
* Optionally set Debug Type to **Managed Only (.NET Core)** to debug C#-only, or **Mixed (.NET Core)** to debug both C++ and C#
|
||||
|
||||
@@ -126,6 +126,7 @@ bool Editor::CheckProjectUpgrade()
|
||||
|
||||
// Remove old project files
|
||||
FileSystem::DeleteFile(root / name + TEXT(".sln"));
|
||||
FileSystem::DeleteFile(root / name + TEXT(".slnx"));
|
||||
FileSystem::DeleteFile(root / name + TEXT(".csproj"));
|
||||
FileSystem::DeleteFile(root / name + TEXT(".csproj.user"));
|
||||
FileSystem::DeleteFile(root / name + TEXT(".Editor.csproj"));
|
||||
|
||||
@@ -175,7 +175,7 @@ bool sortInstallations(RiderInstallation* const& i1, RiderInstallation* const& i
|
||||
|
||||
RiderCodeEditor::RiderCodeEditor(const String& execPath)
|
||||
: _execPath(execPath)
|
||||
, _solutionPath(Globals::ProjectFolder / Editor::Project->Name + TEXT(".sln"))
|
||||
, _solutionPath(Globals::ProjectFolder / Editor::Project->Name + TEXT(".slnx"))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -278,15 +278,29 @@ String RiderCodeEditor::GetName() const
|
||||
|
||||
String RiderCodeEditor::GetGenerateProjectCustomArgs() const
|
||||
{
|
||||
return TEXT("-vs2022");
|
||||
return TEXT("-vs2026");
|
||||
}
|
||||
|
||||
String RiderCodeEditor::GetSolutionPath() const
|
||||
{
|
||||
// Rider prefers .slnx; fall back to .sln if only that exists
|
||||
String slnxPath = Globals::ProjectFolder / Editor::Project->Name + TEXT(".slnx");
|
||||
if (FileSystem::FileExists(slnxPath))
|
||||
return slnxPath;
|
||||
String slnPath = Globals::ProjectFolder / Editor::Project->Name + TEXT(".sln");
|
||||
if (FileSystem::FileExists(slnPath))
|
||||
return slnPath;
|
||||
return slnxPath; // Neither exists yet; target .slnx for generation
|
||||
}
|
||||
|
||||
void RiderCodeEditor::OpenFile(const String& path, int32 line)
|
||||
{
|
||||
// Generate project files if solution is missing
|
||||
if (!FileSystem::FileExists(_solutionPath))
|
||||
String solutionPath = GetSolutionPath();
|
||||
if (!FileSystem::FileExists(solutionPath))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
|
||||
solutionPath = GetSolutionPath();
|
||||
}
|
||||
|
||||
// Open file
|
||||
@@ -295,11 +309,11 @@ void RiderCodeEditor::OpenFile(const String& path, int32 line)
|
||||
|
||||
#if !PLATFORM_MAC
|
||||
procSettings.FileName = _execPath;
|
||||
procSettings.Arguments = String::Format(TEXT("\"{0}\" --line {2} \"{1}\""), _solutionPath, path, line);
|
||||
procSettings.Arguments = String::Format(TEXT("\"{0}\" --line {2} \"{1}\""), solutionPath, path, line);
|
||||
#else
|
||||
// This follows pretty much how all the other engines open rider which deals with cross architecture issues
|
||||
procSettings.FileName = "/usr/bin/open";
|
||||
procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" --args \"{1}\" --line {3} \"{2}\""), _execPath, _solutionPath, path, line);
|
||||
procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" --args \"{1}\" --line {3} \"{2}\""), _execPath, solutionPath, path, line);
|
||||
#endif
|
||||
|
||||
procSettings.HiddenWindow = false;
|
||||
@@ -312,20 +326,22 @@ void RiderCodeEditor::OpenFile(const String& path, int32 line)
|
||||
void RiderCodeEditor::OpenSolution()
|
||||
{
|
||||
// Generate project files if solution is missing
|
||||
if (!FileSystem::FileExists(_solutionPath))
|
||||
String solutionPath = GetSolutionPath();
|
||||
if (!FileSystem::FileExists(solutionPath))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
|
||||
solutionPath = GetSolutionPath();
|
||||
}
|
||||
|
||||
// Open solution
|
||||
CreateProcessSettings procSettings;
|
||||
#if !PLATFORM_MAC
|
||||
procSettings.FileName = _execPath;
|
||||
procSettings.Arguments = String::Format(TEXT("\"{0}\""), _solutionPath);
|
||||
procSettings.Arguments = String::Format(TEXT("\"{0}\""), solutionPath);
|
||||
#else
|
||||
// This follows pretty much how all the other engines open rider which deals with cross architecture issues
|
||||
procSettings.FileName = "/usr/bin/open";
|
||||
procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" \"{1}\""), _execPath, _solutionPath);
|
||||
procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" \"{1}\""), _execPath, solutionPath);
|
||||
#endif
|
||||
procSettings.HiddenWindow = false;
|
||||
procSettings.WaitForEnd = false;
|
||||
|
||||
@@ -14,6 +14,8 @@ private:
|
||||
String _execPath;
|
||||
String _solutionPath;
|
||||
|
||||
String GetSolutionPath() const;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
#include "SystemDefaultCodeEditor.h"
|
||||
#include "Engine/Platform/CreateProcessSettings.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/ProjectInfo.h"
|
||||
#include "Editor/Scripting/ScriptsBuilder.h"
|
||||
|
||||
CodeEditorTypes SystemDefaultCodeEditor::GetType() const
|
||||
{
|
||||
@@ -26,4 +31,24 @@ void SystemDefaultCodeEditor::OpenFile(const String& path, int32 line)
|
||||
|
||||
void SystemDefaultCodeEditor::OpenSolution()
|
||||
{
|
||||
String slnxPath = Globals::ProjectFolder / Editor::Project->Name + TEXT(".slnx");
|
||||
String slnPath = Globals::ProjectFolder / Editor::Project->Name + TEXT(".sln");
|
||||
String solutionPath;
|
||||
if (FileSystem::FileExists(slnxPath))
|
||||
solutionPath = slnxPath;
|
||||
else if (FileSystem::FileExists(slnPath))
|
||||
solutionPath = slnPath;
|
||||
else
|
||||
{
|
||||
ScriptsBuilder::GenerateProject();
|
||||
if (FileSystem::FileExists(slnxPath))
|
||||
solutionPath = slnxPath;
|
||||
else if (FileSystem::FileExists(slnPath))
|
||||
solutionPath = slnPath;
|
||||
}
|
||||
|
||||
if (FileSystem::FileExists(solutionPath))
|
||||
{
|
||||
OpenFile(solutionPath, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +157,23 @@ namespace VisualStudio
|
||||
return _wcsicmp(path1, path2) == 0;
|
||||
}
|
||||
|
||||
bool AreSolutionPathsEqual(const wchar_t* path1, const wchar_t* path2)
|
||||
{
|
||||
// Compare exact paths first (fast path)
|
||||
if (AreFilePathsEqual(path1, path2))
|
||||
return true;
|
||||
// Strip .sln/.slnx extension and compare stems (handles .sln vs .slnx mismatch)
|
||||
auto stripSolExt = [](const wchar_t* p, size_t& len) {
|
||||
if (len >= 5 && _wcsicmp(p + len - 5, L".slnx") == 0) len -= 5;
|
||||
else if (len >= 4 && _wcsicmp(p + len - 4, L".sln") == 0) len -= 4;
|
||||
};
|
||||
size_t len1 = wcslen(path1);
|
||||
size_t len2 = wcslen(path2);
|
||||
stripSolExt(path1, len1);
|
||||
stripSolExt(path2, len2);
|
||||
return len1 == len2 && _wcsnicmp(path1, path2, len1) == 0;
|
||||
}
|
||||
|
||||
class ConnectionInternal
|
||||
{
|
||||
public:
|
||||
@@ -226,7 +243,7 @@ namespace VisualStudio
|
||||
if (FAILED(solution->get_FullName(&fullName.Str)))
|
||||
continue;
|
||||
|
||||
if (AreFilePathsEqual(connection->SolutionPath, fullName))
|
||||
if (AreSolutionPathsEqual(connection->SolutionPath, fullName))
|
||||
{
|
||||
// Found
|
||||
connection->DTE = dte;
|
||||
|
||||
@@ -49,10 +49,28 @@ VisualStudioEditor::VisualStudioEditor(VisualStudioVersion version, const String
|
||||
default: CRASH;
|
||||
break;
|
||||
}
|
||||
_solutionPath = Globals::ProjectFolder / Editor::Project->Name + TEXT(".sln");
|
||||
// Initialize with the preferred extension; GetSolutionPath() will check both at runtime
|
||||
const Char* ext = (_version >= VisualStudioVersion::VS2022) ? TEXT(".slnx") : TEXT(".sln");
|
||||
_solutionPath = Globals::ProjectFolder / Editor::Project->Name + ext;
|
||||
_solutionPath.Replace('/', '\\'); // Use Windows-style path separators
|
||||
}
|
||||
|
||||
String VisualStudioEditor::GetSolutionPath() const
|
||||
{
|
||||
// VS2022+ prefers .slnx; older versions prefer .sln. Fall back to the other if preferred is missing.
|
||||
const String base = Globals::ProjectFolder / Editor::Project->Name;
|
||||
const bool preferSlnx = (_version >= VisualStudioVersion::VS2022);
|
||||
String preferred = base + (preferSlnx ? TEXT(".slnx") : TEXT(".sln"));
|
||||
preferred.Replace('/', '\\');
|
||||
if (FileSystem::FileExists(preferred))
|
||||
return preferred;
|
||||
String fallback = base + (preferSlnx ? TEXT(".sln") : TEXT(".slnx"));
|
||||
fallback.Replace('/', '\\');
|
||||
if (FileSystem::FileExists(fallback))
|
||||
return fallback;
|
||||
return preferred; // Neither exists yet; return preferred so generation targets the right format
|
||||
}
|
||||
|
||||
void VisualStudioEditor::FindEditors(Array<CodeEditor*>* output)
|
||||
{
|
||||
String installDir;
|
||||
@@ -71,22 +89,26 @@ void VisualStudioEditor::FindEditors(Array<CodeEditor*>* output)
|
||||
{
|
||||
auto& info = infos[i];
|
||||
VisualStudioVersion version;
|
||||
switch (info.VersionMajor)
|
||||
if (info.VersionMajor >= 18)
|
||||
{
|
||||
case 18:
|
||||
version = VisualStudioVersion::VS2026;
|
||||
break;
|
||||
case 17:
|
||||
version = VisualStudioVersion::VS2022;
|
||||
break;
|
||||
case 16:
|
||||
version = VisualStudioVersion::VS2019;
|
||||
break;
|
||||
case 15:
|
||||
version = VisualStudioVersion::VS2017;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (info.VersionMajor)
|
||||
{
|
||||
case 17:
|
||||
version = VisualStudioVersion::VS2022;
|
||||
break;
|
||||
case 16:
|
||||
version = VisualStudioVersion::VS2019;
|
||||
break;
|
||||
case 15:
|
||||
version = VisualStudioVersion::VS2017;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
String executablePath(info.ExecutablePath);
|
||||
@@ -184,19 +206,23 @@ String VisualStudioEditor::GetName() const
|
||||
|
||||
String VisualStudioEditor::GetGenerateProjectCustomArgs() const
|
||||
{
|
||||
if (_version >= VisualStudioVersion::VS2022)
|
||||
return TEXT("-vs2026");
|
||||
return String::Format(TEXT("-{0}"), String(ToString(_version)).ToLower());
|
||||
}
|
||||
|
||||
void VisualStudioEditor::OpenFile(const String& path, int32 line)
|
||||
{
|
||||
// Generate project files if solution is missing
|
||||
if (!FileSystem::FileExists(_solutionPath))
|
||||
String solutionPath = GetSolutionPath();
|
||||
if (!FileSystem::FileExists(solutionPath))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
|
||||
solutionPath = GetSolutionPath();
|
||||
}
|
||||
|
||||
// Open file
|
||||
const VisualStudio::Connection connection(*_CLSID, *_solutionPath);
|
||||
const VisualStudio::Connection connection(*_CLSID, *solutionPath);
|
||||
String tmp = path;
|
||||
tmp.Replace('/', '\\'); // Use Windows-style path separators
|
||||
const auto result = connection.OpenFile(*tmp, line);
|
||||
@@ -209,13 +235,15 @@ void VisualStudioEditor::OpenFile(const String& path, int32 line)
|
||||
void VisualStudioEditor::OpenSolution()
|
||||
{
|
||||
// Generate project files if solution is missing
|
||||
if (!FileSystem::FileExists(_solutionPath))
|
||||
String solutionPath = GetSolutionPath();
|
||||
if (!FileSystem::FileExists(solutionPath))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
|
||||
solutionPath = GetSolutionPath();
|
||||
}
|
||||
|
||||
// Open solution
|
||||
const VisualStudio::Connection connection(*_CLSID, *_solutionPath);
|
||||
const VisualStudio::Connection connection(*_CLSID, *solutionPath);
|
||||
const auto result = connection.OpenSolution();
|
||||
if (result.Failed())
|
||||
{
|
||||
@@ -227,35 +255,17 @@ void VisualStudioEditor::OnFileAdded(const String& path)
|
||||
{
|
||||
// TODO: finish dynamic files adding to the project - for now just regenerate it
|
||||
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
|
||||
return;
|
||||
if (!FileSystem::FileExists(_solutionPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Edit solution
|
||||
const VisualStudio::Connection connection(*_CLSID, *_solutionPath);
|
||||
if (connection.IsActive())
|
||||
{
|
||||
String tmp = path;
|
||||
tmp.Replace('/', '\\');
|
||||
String tmp2 = tmp.Substring(Globals::ProjectSourceFolder.Length() + 1);
|
||||
const auto result = connection.AddFile(*tmp, *tmp2);
|
||||
if (result.Failed())
|
||||
{
|
||||
LOG(Warning, "Cannot add file to project. {0}", String(result.Message.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VisualStudioEditor::UseAsyncForOpen() const
|
||||
{
|
||||
// Need to generate project files if missing first
|
||||
if (!FileSystem::FileExists(_solutionPath))
|
||||
String solutionPath = GetSolutionPath();
|
||||
if (!FileSystem::FileExists(solutionPath))
|
||||
return true;
|
||||
|
||||
// Open in async only when no solution opened
|
||||
const VisualStudio::Connection connection(*_CLSID, *_solutionPath);
|
||||
const VisualStudio::Connection connection(*_CLSID, *solutionPath);
|
||||
return !connection.IsActive();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ private:
|
||||
String _CLSID;
|
||||
String _solutionPath;
|
||||
|
||||
String GetSolutionPath() const;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -130,15 +130,16 @@ String VisualStudioCodeEditor::GetName() const
|
||||
|
||||
String VisualStudioCodeEditor::GetGenerateProjectCustomArgs() const
|
||||
{
|
||||
return TEXT("-vs2022 -vscode");
|
||||
return TEXT("-vs2026 -vscode");
|
||||
}
|
||||
|
||||
void VisualStudioCodeEditor::OpenFile(const String& path, int32 line)
|
||||
{
|
||||
// Generate VS solution files for intellisense
|
||||
if (!FileSystem::FileExists(Globals::ProjectFolder / Editor::Project->Name + TEXT(".sln")))
|
||||
if (!FileSystem::FileExists(Globals::ProjectFolder / Editor::Project->Name + TEXT(".sln")) &&
|
||||
!FileSystem::FileExists(Globals::ProjectFolder / Editor::Project->Name + TEXT(".slnx")))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(TEXT("-vs2022"));
|
||||
ScriptsBuilder::GenerateProject(TEXT("-vs2026"));
|
||||
}
|
||||
|
||||
// Generate project files if missing
|
||||
@@ -163,9 +164,10 @@ void VisualStudioCodeEditor::OpenFile(const String& path, int32 line)
|
||||
void VisualStudioCodeEditor::OpenSolution()
|
||||
{
|
||||
// Generate VS solution files for intellisense
|
||||
if (!FileSystem::FileExists(Globals::ProjectFolder / Editor::Project->Name + TEXT(".sln")))
|
||||
if (!FileSystem::FileExists(Globals::ProjectFolder / Editor::Project->Name + TEXT(".sln")) &&
|
||||
!FileSystem::FileExists(Globals::ProjectFolder / Editor::Project->Name + TEXT(".slnx")))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(TEXT("-vs2022"));
|
||||
ScriptsBuilder::GenerateProject(TEXT("-vs2026"));
|
||||
}
|
||||
|
||||
// Generate project files if solution is missing
|
||||
|
||||
@@ -190,5 +190,27 @@ namespace Flax.Build.Tests
|
||||
Assert.AreEqual("value1", TestConfig3.Option1[0]);
|
||||
Assert.AreEqual("value2", TestConfig3.Option1[1]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVisualStudioSolutionFileExtension()
|
||||
{
|
||||
var gen2026 = (Flax.Build.Projects.VisualStudio.VisualStudioProjectGenerator)Flax.Build.Projects.ProjectGenerator.Create(Flax.Build.Projects.ProjectFormat.VisualStudio2026, TargetType.NativeCpp);
|
||||
Assert.AreEqual("slnx", gen2026.SolutionFileExtension);
|
||||
|
||||
var genDefault = (Flax.Build.Projects.VisualStudio.VisualStudioProjectGenerator)Flax.Build.Projects.ProjectGenerator.Create(Flax.Build.Projects.ProjectFormat.VisualStudio, TargetType.NativeCpp);
|
||||
Assert.AreEqual("slnx", genDefault.SolutionFileExtension);
|
||||
|
||||
var gen2022 = (Flax.Build.Projects.VisualStudio.VisualStudioProjectGenerator)Flax.Build.Projects.ProjectGenerator.Create(Flax.Build.Projects.ProjectFormat.VisualStudio2022, TargetType.NativeCpp);
|
||||
Assert.AreEqual("sln", gen2022.SolutionFileExtension);
|
||||
|
||||
var gen2019 = (Flax.Build.Projects.VisualStudio.VisualStudioProjectGenerator)Flax.Build.Projects.ProjectGenerator.Create(Flax.Build.Projects.ProjectFormat.VisualStudio2019, TargetType.NativeCpp);
|
||||
Assert.AreEqual("sln", gen2019.SolutionFileExtension);
|
||||
|
||||
var gen2017 = (Flax.Build.Projects.VisualStudio.VisualStudioProjectGenerator)Flax.Build.Projects.ProjectGenerator.Create(Flax.Build.Projects.ProjectFormat.VisualStudio2017, TargetType.NativeCpp);
|
||||
Assert.AreEqual("sln", gen2017.SolutionFileExtension);
|
||||
|
||||
var gen2015 = (Flax.Build.Projects.VisualStudio.VisualStudioProjectGenerator)Flax.Build.Projects.ProjectGenerator.Create(Flax.Build.Projects.ProjectFormat.VisualStudio2015, TargetType.NativeCpp);
|
||||
Assert.AreEqual("sln", gen2015.SolutionFileExtension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ namespace Flax.Build
|
||||
if (Configuration.ProjectFormatCLion)
|
||||
projectFormats.Add(ProjectFormat.CMake);
|
||||
if (Configuration.ProjectFormatRider)
|
||||
projectFormats.Add(ProjectFormat.VisualStudio2022);
|
||||
projectFormats.Add(ProjectFormat.VisualStudio2026);
|
||||
if (!string.IsNullOrEmpty(Configuration.ProjectFormatCustom))
|
||||
projectFormats.Add(ProjectFormat.Custom);
|
||||
if (projectFormats.Count == 0)
|
||||
@@ -218,7 +218,7 @@ namespace Flax.Build
|
||||
!projectFormats.Contains(ProjectFormat.VisualStudio2022) &&
|
||||
!projectFormats.Contains(ProjectFormat.VisualStudio))
|
||||
{
|
||||
projectFormats.Add(ProjectFormat.VisualStudio2022);
|
||||
projectFormats.Add(ProjectFormat.VisualStudio2026);
|
||||
}
|
||||
|
||||
foreach (ProjectFormat projectFormat in projectFormats)
|
||||
@@ -692,6 +692,32 @@ namespace Flax.Build
|
||||
{
|
||||
Log.Verbose("Solution -> " + solution.Path);
|
||||
nativeProjectGenerator.GenerateSolution(solution);
|
||||
|
||||
// Clean up obsolete solution file with different extension if present
|
||||
if (solution.Path.EndsWith(".slnx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var oldSln = Path.ChangeExtension(solution.Path, "sln");
|
||||
if (File.Exists(oldSln))
|
||||
{
|
||||
Log.Verbose("Removing obsolete solution file " + oldSln);
|
||||
File.Delete(oldSln);
|
||||
}
|
||||
var oldDotSettingsUser = oldSln + ".DotSettings.user";
|
||||
if (File.Exists(oldDotSettingsUser))
|
||||
File.Delete(oldDotSettingsUser);
|
||||
}
|
||||
else if (solution.Path.EndsWith(".sln", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var oldSlnx = Path.ChangeExtension(solution.Path, "slnx");
|
||||
if (File.Exists(oldSlnx))
|
||||
{
|
||||
Log.Verbose("Removing obsolete solution file " + oldSlnx);
|
||||
File.Delete(oldSlnx);
|
||||
}
|
||||
var oldDotSettingsUser = oldSlnx + ".DotSettings.user";
|
||||
if (File.Exists(oldDotSettingsUser))
|
||||
File.Delete(oldDotSettingsUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,9 +220,9 @@ namespace Flax.Build
|
||||
public static bool ProjectFormatCLion = false;
|
||||
|
||||
/// <summary>
|
||||
/// Generates Visual Studio 2022 project format files for Rider. Valid only with -genproject option.
|
||||
/// Generates Visual Studio 2026 project format files for Rider. Valid only with -genproject option.
|
||||
/// </summary>
|
||||
[CommandLine("rider", "Generates Visual Studio 2022 project format files for Rider. Valid only with -genproject option.")]
|
||||
[CommandLine("rider", "Generates Visual Studio 2026 project format files for Rider. Valid only with -genproject option.")]
|
||||
public static bool ProjectFormatRider = false;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -244,6 +244,8 @@ namespace Flax.Deploy
|
||||
// CMake VS2026 generator prefers .slnx solution files, just swap the extension for CMake dependencies
|
||||
if (File.Exists(Path.ChangeExtension(solutionFile, "slnx")))
|
||||
solutionFile = Path.ChangeExtension(solutionFile, "slnx");
|
||||
else if (File.Exists(Path.ChangeExtension(solutionFile, "sln")))
|
||||
solutionFile = Path.ChangeExtension(solutionFile, "sln");
|
||||
else
|
||||
throw new Exception(string.Format("Unable to build solution {0}. Solution file not found.", solutionFile));
|
||||
}
|
||||
|
||||
@@ -82,34 +82,10 @@ namespace Flax.Build.Projects
|
||||
/// <returns>The generator.</returns>
|
||||
public static ProjectGenerator Create(ProjectFormat format, TargetType type)
|
||||
{
|
||||
// Pick the newest installed Visual Studio version
|
||||
// Visual Studio default project format is Visual Studio 2026 (.slnx)
|
||||
if (format == ProjectFormat.VisualStudio)
|
||||
{
|
||||
if (VisualStudioInstance.HasIDE(VisualStudioVersion.VisualStudio2026))
|
||||
{
|
||||
format = ProjectFormat.VisualStudio2026;
|
||||
}
|
||||
else if (VisualStudioInstance.HasIDE(VisualStudioVersion.VisualStudio2022))
|
||||
{
|
||||
format = ProjectFormat.VisualStudio2022;
|
||||
}
|
||||
else if (VisualStudioInstance.HasIDE(VisualStudioVersion.VisualStudio2019))
|
||||
{
|
||||
format = ProjectFormat.VisualStudio2019;
|
||||
}
|
||||
else if (VisualStudioInstance.HasIDE(VisualStudioVersion.VisualStudio2017))
|
||||
{
|
||||
format = ProjectFormat.VisualStudio2017;
|
||||
}
|
||||
else if (VisualStudioInstance.HasIDE(VisualStudioVersion.VisualStudio2015))
|
||||
{
|
||||
format = ProjectFormat.VisualStudio2015;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning("Failed to find default Visual Studio installation");
|
||||
format = ProjectFormat.VisualStudio2015;
|
||||
}
|
||||
format = ProjectFormat.VisualStudio2026;
|
||||
}
|
||||
|
||||
switch (format)
|
||||
|
||||
@@ -122,17 +122,18 @@ namespace Flax.Build.Projects.VisualStudio
|
||||
{
|
||||
VisualStudioVersion version;
|
||||
string displayName = instance.GetDisplayName();
|
||||
if (displayName.Contains("2017"))
|
||||
version = VisualStudioVersion.VisualStudio2017;
|
||||
else if (displayName.Contains("2019"))
|
||||
version = VisualStudioVersion.VisualStudio2019;
|
||||
else if (displayName.Contains("2022"))
|
||||
version = VisualStudioVersion.VisualStudio2022;
|
||||
else if (displayName.Contains("2026"))
|
||||
string installVer = instance.GetInstallationVersion() ?? string.Empty;
|
||||
if (displayName.Contains("2026") || installVer.StartsWith("18."))
|
||||
version = VisualStudioVersion.VisualStudio2026;
|
||||
else if (displayName.Contains("2022") || installVer.StartsWith("17."))
|
||||
version = VisualStudioVersion.VisualStudio2022;
|
||||
else if (displayName.Contains("2019") || installVer.StartsWith("16."))
|
||||
version = VisualStudioVersion.VisualStudio2019;
|
||||
else if (displayName.Contains("2017") || installVer.StartsWith("15."))
|
||||
version = VisualStudioVersion.VisualStudio2017;
|
||||
else
|
||||
{
|
||||
Log.Warning(string.Format("Unknown Visual Studio installation. Display name: {0}", displayName));
|
||||
Log.Warning(string.Format("Unknown Visual Studio installation. Display name: {0}, Version: {1}", displayName, installVer));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -189,12 +189,55 @@ namespace Flax.Build.Projects.VisualStudio
|
||||
// Hide errors
|
||||
}
|
||||
}
|
||||
if (File.Exists(path) && Path.GetExtension(path).Equals(".slnx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(path);
|
||||
XmlNodeList elements = doc.GetElementsByTagName("Project");
|
||||
foreach (XmlElement element in elements)
|
||||
{
|
||||
var projectPath = element.GetAttribute("Path");
|
||||
if (Path.GetFileNameWithoutExtension(projectPath) == projectName && element.HasAttribute("Id"))
|
||||
return Guid.ParseExact(element.GetAttribute("Id").Trim("{}".ToCharArray()), "D");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Hide errors
|
||||
}
|
||||
|
||||
// Fallback to existing .sln if present
|
||||
var slnPath = Path.ChangeExtension(path, "sln");
|
||||
if (File.Exists(slnPath))
|
||||
{
|
||||
var id = GetProjectGuid(slnPath, projectName);
|
||||
if (id != Guid.Empty)
|
||||
return id;
|
||||
}
|
||||
}
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
if (Path.GetExtension(path).Equals(".slnx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var slnPath = Path.ChangeExtension(path, "sln");
|
||||
if (File.Exists(slnPath))
|
||||
return GetProjectGuid(slnPath, projectName);
|
||||
}
|
||||
else if (Path.GetExtension(path).Equals(".sln", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var slnxPath = Path.ChangeExtension(path, "slnx");
|
||||
if (File.Exists(slnxPath))
|
||||
return GetProjectGuid(slnxPath, projectName);
|
||||
}
|
||||
}
|
||||
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string SolutionFileExtension => /*Version >= VisualStudioVersion.VisualStudio2026 ? "slnx" :*/ "sln";
|
||||
public override string SolutionFileExtension => Version >= VisualStudioVersion.VisualStudio2026 ? "slnx" : "sln";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Project CreateProject()
|
||||
@@ -282,12 +325,371 @@ namespace Flax.Build.Projects.VisualStudio
|
||||
GenerateXmlSolution(solution);
|
||||
else
|
||||
GenerateAsciiSolution(solution);
|
||||
|
||||
PostGenerateSolution(solution);
|
||||
}
|
||||
|
||||
private static string GetProjectFolder(VisualStudioProject project)
|
||||
{
|
||||
var folder = project.GroupName;
|
||||
if (project.SourceDirectories != null && project.SourceDirectories.Count == 1)
|
||||
{
|
||||
var subFolder = Utilities.NormalizePath(Utilities.MakePathRelativeTo(Path.GetDirectoryName(project.SourceDirectories[0]), project.WorkspaceRootPath));
|
||||
if (subFolder.StartsWith("Source/"))
|
||||
subFolder = subFolder.Substring("Source/".Length);
|
||||
if (subFolder.Length != 0)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(folder))
|
||||
folder += '/';
|
||||
folder += subFolder;
|
||||
}
|
||||
}
|
||||
return folder != null ? folder.Trim('/') : string.Empty;
|
||||
}
|
||||
|
||||
private static string EscapeXmlAttribute(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return string.Empty;
|
||||
return value
|
||||
.Replace("&", "&")
|
||||
.Replace("\"", """)
|
||||
.Replace("'", "'")
|
||||
.Replace("<", "<")
|
||||
.Replace(">", ">");
|
||||
}
|
||||
|
||||
private List<SolutionConfiguration> GetSolutionConfigurations(Solution solution, VisualStudioProject[] projects)
|
||||
{
|
||||
var configurations = new HashSet<SolutionConfiguration>();
|
||||
var mainArchitectures = solution.MainProject?.Targets?.SelectMany(x => x.Architectures).Distinct().ToArray();
|
||||
foreach (var project in projects)
|
||||
{
|
||||
if (project.Configurations == null || project.Configurations.Count == 0)
|
||||
throw new Exception("Missing configurations for project " + project.Name);
|
||||
|
||||
// Prevent generating default Debug|AnyCPU and Release|AnyCPU configurations from Flax projects
|
||||
if (project.Name == "BuildScripts" || project.Name == "Flax.Build" || project.Name == "Flax.Build.Tests")
|
||||
continue;
|
||||
|
||||
foreach (var configuration in project.Configurations)
|
||||
{
|
||||
// Skip architectures which are not included in the game project
|
||||
if (mainArchitectures != null && !mainArchitectures.Contains(configuration.Architecture))
|
||||
continue;
|
||||
|
||||
configurations.Add(new SolutionConfiguration(configuration));
|
||||
}
|
||||
}
|
||||
|
||||
// Add missing configurations (Visual Studio needs all permutations of configuration/platform pair)
|
||||
var configurationNames = configurations.Select(x => x.Configuration).Distinct().ToArray();
|
||||
var platformNames = configurations.Select(x => x.Platform).Distinct().ToArray();
|
||||
foreach (var configurationName in configurationNames)
|
||||
{
|
||||
foreach (var platformName in platformNames)
|
||||
{
|
||||
configurations.Add(new SolutionConfiguration(configurationName, platformName));
|
||||
}
|
||||
}
|
||||
|
||||
// Sort configurations
|
||||
var configurationsSorted = new List<SolutionConfiguration>(configurations);
|
||||
configurationsSorted.Sort();
|
||||
return configurationsSorted;
|
||||
}
|
||||
|
||||
private void GenerateXmlSolution(Solution solution)
|
||||
{
|
||||
// TODO: Generate the solution file in new format
|
||||
GenerateAsciiSolution(solution);
|
||||
var solutionDirectory = Path.GetDirectoryName(solution.Path);
|
||||
var projects = solution.Projects.Cast<VisualStudioProject>().ToArray();
|
||||
var configurationsSorted = GetSolutionConfigurations(solution, projects);
|
||||
|
||||
var distinctBuildTypes = configurationsSorted.Select(x => x.Configuration).Distinct().ToList();
|
||||
var distinctPlatforms = configurationsSorted.Select(x => x.Platform).Distinct().ToList();
|
||||
|
||||
var xml = new StringBuilder();
|
||||
xml.AppendLine("<Solution>");
|
||||
|
||||
// Solution Configurations
|
||||
xml.AppendLine(" <Configurations>");
|
||||
foreach (var buildType in distinctBuildTypes)
|
||||
xml.AppendLine($" <BuildType Name=\"{EscapeXmlAttribute(buildType)}\" />");
|
||||
foreach (var platform in distinctPlatforms)
|
||||
xml.AppendLine($" <Platform Name=\"{EscapeXmlAttribute(platform)}\" />");
|
||||
xml.AppendLine(" </Configurations>");
|
||||
|
||||
// Solution Folders and Projects
|
||||
var allFolders = new HashSet<string>();
|
||||
var folderProjects = new Dictionary<string, List<VisualStudioProject>>();
|
||||
var rootProjects = new List<VisualStudioProject>();
|
||||
|
||||
foreach (var project in projects)
|
||||
{
|
||||
var folder = GetProjectFolder(project);
|
||||
if (string.IsNullOrEmpty(folder))
|
||||
{
|
||||
rootProjects.Add(project);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!folderProjects.TryGetValue(folder, out var list))
|
||||
{
|
||||
list = new List<VisualStudioProject>();
|
||||
folderProjects.Add(folder, list);
|
||||
}
|
||||
list.Add(project);
|
||||
|
||||
// Register folder and all parent folders
|
||||
var folderParents = folder.Split('/');
|
||||
for (int i = 0; i < folderParents.Length; i++)
|
||||
{
|
||||
var folderPath = folderParents[0];
|
||||
for (int j = 1; j <= i; j++)
|
||||
folderPath += '/' + folderParents[j];
|
||||
allFolders.Add(folderPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sortedFolders = allFolders.OrderBy(x => x).ToList();
|
||||
foreach (var folder in sortedFolders)
|
||||
{
|
||||
if (folderProjects.TryGetValue(folder, out var projsInFolder) && projsInFolder.Count > 0)
|
||||
{
|
||||
xml.AppendLine($" <Folder Name=\"/{EscapeXmlAttribute(folder)}/\">");
|
||||
foreach (var project in projsInFolder)
|
||||
{
|
||||
GenerateXmlProject(xml, project, " ", solutionDirectory, configurationsSorted, solution);
|
||||
}
|
||||
xml.AppendLine(" </Folder>");
|
||||
}
|
||||
else
|
||||
{
|
||||
xml.AppendLine($" <Folder Name=\"/{EscapeXmlAttribute(folder)}/\" />");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var project in rootProjects)
|
||||
{
|
||||
GenerateXmlProject(xml, project, " ", solutionDirectory, configurationsSorted, solution);
|
||||
}
|
||||
|
||||
xml.AppendLine("</Solution>");
|
||||
|
||||
// Save the file
|
||||
Utilities.WriteFileIfChanged(solution.Path, xml.ToString());
|
||||
}
|
||||
|
||||
private void GenerateXmlProject(StringBuilder xml, VisualStudioProject project, string indent, string solutionDirectory, List<SolutionConfiguration> configurationsSorted, Solution solution)
|
||||
{
|
||||
var projectPath = Utilities.NormalizePath(Utilities.MakePathRelativeTo(project.Path, solutionDirectory));
|
||||
|
||||
string typeAttr = string.Empty;
|
||||
var ext = Path.GetExtension(project.Path);
|
||||
bool isStandardVcxproj = ext.Equals(".vcxproj", StringComparison.OrdinalIgnoreCase) &&
|
||||
(project.ProjectTypeGuid == Guid.Empty || project.ProjectTypeGuid == ProjectTypeGuids.WindowsVisualCpp);
|
||||
bool isStandardCsproj = ext.Equals(".csproj", StringComparison.OrdinalIgnoreCase) &&
|
||||
(project.ProjectTypeGuid == Guid.Empty || project.ProjectTypeGuid == ProjectTypeGuids.WindowsCSharp || project.ProjectTypeGuid == Guid.Parse("9A19103F-16F7-4668-BE54-9A1E7A4F7556"));
|
||||
if (!isStandardVcxproj && !isStandardCsproj && project.ProjectTypeGuid != Guid.Empty)
|
||||
{
|
||||
typeAttr = $" Type=\"{project.ProjectTypeGuid.ToString("D").ToLowerInvariant()}\"";
|
||||
}
|
||||
|
||||
var childLines = new List<string>();
|
||||
|
||||
// Build dependencies
|
||||
if (project.Dependencies.Count > 0)
|
||||
{
|
||||
foreach (var dependency in project.Dependencies.Cast<VisualStudioProject>())
|
||||
{
|
||||
var depPath = Utilities.NormalizePath(Utilities.MakePathRelativeTo(dependency.Path, solutionDirectory));
|
||||
childLines.Add($"<BuildDependency Project=\"{EscapeXmlAttribute(depPath)}\" />");
|
||||
}
|
||||
}
|
||||
|
||||
// Per-configuration mappings
|
||||
var projectMappings = new List<(SolutionConfiguration solConfig, SolutionConfiguration projConfig, bool build)>();
|
||||
foreach (var configuration in configurationsSorted)
|
||||
{
|
||||
SolutionConfiguration projectConfiguration;
|
||||
bool build = false;
|
||||
int firstFullMatch = -1, firstPlatformMatch = -1, firstEditorMatch = -1;
|
||||
for (int i = 0; i < project.Configurations.Count; i++)
|
||||
{
|
||||
var e = new SolutionConfiguration(project.Configurations[i]);
|
||||
if (e.Name == configuration.Name)
|
||||
{
|
||||
firstFullMatch = i;
|
||||
break;
|
||||
}
|
||||
if (firstPlatformMatch == -1 && e.Platform == configuration.Platform)
|
||||
{
|
||||
firstPlatformMatch = i;
|
||||
}
|
||||
if (firstEditorMatch == -1 && e.Configuration == configuration.Configuration)
|
||||
{
|
||||
firstEditorMatch = i;
|
||||
}
|
||||
}
|
||||
if (project is AndroidProject)
|
||||
{
|
||||
if (firstFullMatch != -1)
|
||||
projectConfiguration = new SolutionConfiguration(project.Configurations[firstFullMatch]);
|
||||
else
|
||||
projectConfiguration = new SolutionConfiguration(project.Configurations[0]);
|
||||
}
|
||||
else if (firstFullMatch != -1)
|
||||
{
|
||||
projectConfiguration = new SolutionConfiguration(project.Configurations[firstFullMatch]);
|
||||
build = solution.MainProject == project;
|
||||
build |= project.Type == TargetType.DotNetCore;
|
||||
build |= solution.MainProject == null && project.Name == solution.Name;
|
||||
}
|
||||
else if (firstPlatformMatch != -1 && !configuration.Name.StartsWith("Editor."))
|
||||
{
|
||||
projectConfiguration = new SolutionConfiguration(project.Configurations[firstPlatformMatch]);
|
||||
}
|
||||
else if (firstEditorMatch != -1 && configuration.Name.StartsWith("Editor."))
|
||||
{
|
||||
projectConfiguration = new SolutionConfiguration(project.Configurations[firstEditorMatch]);
|
||||
}
|
||||
else
|
||||
{
|
||||
projectConfiguration = new SolutionConfiguration(project.Configurations[0]);
|
||||
}
|
||||
|
||||
var originalName = projectConfiguration.OriginalName;
|
||||
var pipeIndex = originalName.IndexOf('|');
|
||||
var projConfigName = pipeIndex != -1 ? originalName.Substring(0, pipeIndex) : projectConfiguration.Configuration;
|
||||
var projPlatformName = pipeIndex != -1 ? originalName.Substring(pipeIndex + 1) : projectConfiguration.Platform;
|
||||
|
||||
projectMappings.Add((configuration, new SolutionConfiguration(projConfigName, projPlatformName), build));
|
||||
}
|
||||
|
||||
// BuildType mapping
|
||||
bool allSameProjConfig = projectMappings.All(m => m.projConfig.Configuration == projectMappings[0].projConfig.Configuration);
|
||||
if (allSameProjConfig)
|
||||
{
|
||||
var commonProjConfig = projectMappings[0].projConfig.Configuration;
|
||||
if (!projectMappings.All(m => m.solConfig.Configuration == commonProjConfig))
|
||||
{
|
||||
childLines.Add($"<BuildType Project=\"{EscapeXmlAttribute(commonProjConfig)}\" />");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var distinctSolConfigs = projectMappings.Select(m => m.solConfig.Configuration).Distinct().ToList();
|
||||
foreach (var solConfigName in distinctSolConfigs)
|
||||
{
|
||||
var mappingsForSolConfig = projectMappings.Where(m => m.solConfig.Configuration == solConfigName).ToList();
|
||||
bool allPlatformsSame = mappingsForSolConfig.All(m => m.projConfig.Configuration == mappingsForSolConfig[0].projConfig.Configuration);
|
||||
if (allPlatformsSame)
|
||||
{
|
||||
var projConfigName = mappingsForSolConfig[0].projConfig.Configuration;
|
||||
if (projConfigName != solConfigName)
|
||||
{
|
||||
childLines.Add($"<BuildType Solution=\"{EscapeXmlAttribute(solConfigName)}|*\" Project=\"{EscapeXmlAttribute(projConfigName)}\" />");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var m in mappingsForSolConfig)
|
||||
{
|
||||
if (m.projConfig.Configuration != m.solConfig.Configuration)
|
||||
{
|
||||
childLines.Add($"<BuildType Solution=\"{EscapeXmlAttribute(m.solConfig.Name)}\" Project=\"{EscapeXmlAttribute(m.projConfig.Configuration)}\" />");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Platform mapping
|
||||
bool isDotNetProject = project.Type == TargetType.DotNetCore || project.Type == TargetType.DotNet || project.Path.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase);
|
||||
bool allSameProjPlatform = projectMappings.All(m => m.projConfig.Platform == projectMappings[0].projConfig.Platform);
|
||||
if (allSameProjPlatform)
|
||||
{
|
||||
var commonProjPlatform = projectMappings[0].projConfig.Platform;
|
||||
bool isDefaultDotNetPlatform = isDotNetProject && (commonProjPlatform == "Any CPU" || commonProjPlatform == "AnyCPU");
|
||||
bool matchesAllSolPlatforms = projectMappings.All(m => m.solConfig.Platform == commonProjPlatform);
|
||||
if (!isDefaultDotNetPlatform && !matchesAllSolPlatforms)
|
||||
{
|
||||
childLines.Add($"<Platform Project=\"{EscapeXmlAttribute(commonProjPlatform)}\" />");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var distinctSolPlatforms = projectMappings.Select(m => m.solConfig.Platform).Distinct().ToList();
|
||||
foreach (var solPlatformName in distinctSolPlatforms)
|
||||
{
|
||||
var mappingsForSolPlatform = projectMappings.Where(m => m.solConfig.Platform == solPlatformName).ToList();
|
||||
bool allConfigsSame = mappingsForSolPlatform.All(m => m.projConfig.Platform == mappingsForSolPlatform[0].projConfig.Platform);
|
||||
if (allConfigsSame)
|
||||
{
|
||||
var projPlatformName = mappingsForSolPlatform[0].projConfig.Platform;
|
||||
bool isDefaultDotNetPlatform = isDotNetProject && (projPlatformName == "Any CPU" || projPlatformName == "AnyCPU");
|
||||
if (projPlatformName != solPlatformName && !isDefaultDotNetPlatform)
|
||||
{
|
||||
childLines.Add($"<Platform Solution=\"*|{EscapeXmlAttribute(solPlatformName)}\" Project=\"{EscapeXmlAttribute(projPlatformName)}\" />");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var m in mappingsForSolPlatform)
|
||||
{
|
||||
if (m.projConfig.Platform != m.solConfig.Platform)
|
||||
{
|
||||
childLines.Add($"<Platform Solution=\"{EscapeXmlAttribute(m.solConfig.Name)}\" Project=\"{EscapeXmlAttribute(m.projConfig.Platform)}\" />");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build flag mapping
|
||||
bool allBuildTrue = projectMappings.All(m => m.build);
|
||||
bool allBuildFalse = projectMappings.All(m => !m.build);
|
||||
if (allBuildFalse)
|
||||
{
|
||||
childLines.Add("<Build Project=\"false\" />");
|
||||
}
|
||||
else if (!allBuildTrue)
|
||||
{
|
||||
var distinctSolConfigs = projectMappings.Select(m => m.solConfig.Configuration).Distinct().ToList();
|
||||
foreach (var solConfigName in distinctSolConfigs)
|
||||
{
|
||||
var mappingsForSolConfig = projectMappings.Where(m => m.solConfig.Configuration == solConfigName).ToList();
|
||||
bool allPlatformsFalse = mappingsForSolConfig.All(m => !m.build);
|
||||
bool allPlatformsTrue = mappingsForSolConfig.All(m => m.build);
|
||||
if (allPlatformsFalse)
|
||||
{
|
||||
childLines.Add($"<Build Solution=\"{EscapeXmlAttribute(solConfigName)}|*\" Project=\"false\" />");
|
||||
}
|
||||
else if (!allPlatformsTrue)
|
||||
{
|
||||
foreach (var m in mappingsForSolConfig)
|
||||
{
|
||||
if (!m.build)
|
||||
{
|
||||
childLines.Add($"<Build Solution=\"{EscapeXmlAttribute(m.solConfig.Name)}\" Project=\"false\" />");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (childLines.Count == 0)
|
||||
{
|
||||
xml.AppendLine($"{indent}<Project Path=\"{EscapeXmlAttribute(projectPath)}\"{typeAttr} />");
|
||||
}
|
||||
else
|
||||
{
|
||||
xml.AppendLine($"{indent}<Project Path=\"{EscapeXmlAttribute(projectPath)}\"{typeAttr}>");
|
||||
foreach (var line in childLines)
|
||||
xml.AppendLine($"{indent} {line}");
|
||||
xml.AppendLine($"{indent}</Project>");
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateAsciiSolution(Solution solution)
|
||||
@@ -447,42 +849,7 @@ namespace Flax.Build.Projects.VisualStudio
|
||||
{
|
||||
vcSolutionFileContent.AppendLine("Global");
|
||||
|
||||
// Collect all unique configurations
|
||||
var configurations = new HashSet<SolutionConfiguration>();
|
||||
var mainArchitectures = solution.MainProject?.Targets?.SelectMany(x => x.Architectures).Distinct().ToArray();
|
||||
foreach (var project in projects)
|
||||
{
|
||||
if (project.Configurations == null || project.Configurations.Count == 0)
|
||||
throw new Exception("Missing configurations for project " + project.Name);
|
||||
|
||||
// Prevent generating default Debug|AnyCPU and Release|AnyCPU configurations from Flax projects
|
||||
if (project.Name == "BuildScripts" || project.Name == "Flax.Build" || project.Name == "Flax.Build.Tests")
|
||||
continue;
|
||||
|
||||
foreach (var configuration in project.Configurations)
|
||||
{
|
||||
// Skip architectures which are not included in the game project
|
||||
if (mainArchitectures != null && !mainArchitectures.Contains(configuration.Architecture))
|
||||
continue;
|
||||
|
||||
configurations.Add(new SolutionConfiguration(configuration));
|
||||
}
|
||||
}
|
||||
|
||||
// Add missing configurations (Visual Studio needs all permutations of configuration/platform pair)
|
||||
var configurationNames = configurations.Select(x => x.Configuration).Distinct().ToArray();
|
||||
var platformNames = configurations.Select(x => x.Platform).Distinct().ToArray();
|
||||
foreach (var configurationName in configurationNames)
|
||||
{
|
||||
foreach (var platformName in platformNames)
|
||||
{
|
||||
configurations.Add(new SolutionConfiguration(configurationName, platformName));
|
||||
}
|
||||
}
|
||||
|
||||
// Sort configurations
|
||||
var configurationsSorted = new List<SolutionConfiguration>(configurations);
|
||||
configurationsSorted.Sort();
|
||||
var configurationsSorted = GetSolutionConfigurations(solution, projects);
|
||||
|
||||
// Global configurations
|
||||
{
|
||||
@@ -624,6 +991,12 @@ namespace Flax.Build.Projects.VisualStudio
|
||||
|
||||
// Save the file
|
||||
Utilities.WriteFileIfChanged(solution.Path, vcSolutionFileContent.ToString());
|
||||
}
|
||||
|
||||
private void PostGenerateSolution(Solution solution)
|
||||
{
|
||||
var solutionDirectory = Path.GetDirectoryName(solution.Path);
|
||||
var projects = solution.Projects.Cast<VisualStudioProject>().ToArray();
|
||||
|
||||
// Generate launch profiles for C# projects
|
||||
if (Version >= VisualStudioVersion.VisualStudio2022)
|
||||
@@ -707,6 +1080,23 @@ namespace Flax.Build.Projects.VisualStudio
|
||||
}
|
||||
|
||||
Utilities.WriteFileIfChanged(dotSettingsUserFilePath, dotSettingsFileContent.ToString());
|
||||
|
||||
// Solution settings (team layer) - copy from alternate solution extension if not yet present
|
||||
string dotSettingsFilePath = solution.Path + ".DotSettings";
|
||||
string altDotSettingsFilePath = (solution.Path.EndsWith(".slnx", StringComparison.OrdinalIgnoreCase)
|
||||
? Path.ChangeExtension(solution.Path, "sln")
|
||||
: Path.ChangeExtension(solution.Path, "slnx")) + ".DotSettings";
|
||||
if (!File.Exists(dotSettingsFilePath) && File.Exists(altDotSettingsFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Copy(altDotSettingsFilePath, dotSettingsFilePath, overwrite: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"Failed to copy Rider settings from {altDotSettingsFilePath}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom MSBuild .targets file to prevent building Flax C#-projects directly with MSBuild
|
||||
|
||||
+2
-1
@@ -674,7 +674,8 @@ namespace Flax.Build.Projects.VisualStudioCode
|
||||
json.AddField("gulp.autoDetect", "off");
|
||||
json.AddField("jake.autoDetect", "off");
|
||||
json.AddField("grunt.autoDetect", "off");
|
||||
json.AddField("omnisharp.defaultLaunchSolution", solution.Name + ".sln");
|
||||
bool isLegacySln = !Configuration.ProjectFormatVS2026 && (Configuration.ProjectFormatVS2022 || Configuration.ProjectFormatVS2019 || Configuration.ProjectFormatVS2017 || Configuration.ProjectFormatVS2015);
|
||||
json.AddField("omnisharp.defaultLaunchSolution", solution.Name + (isLegacySln ? ".sln" : ".slnx"));
|
||||
json.AddField("omnisharp.useModernNet", true);
|
||||
json.EndObject();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user