Skip to content

Fix .sln project generation logic for Rider to support all OS and all C++ toolchains #103405

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ indent_style = space
[{*.{yml,yaml},.clang{-format,-tidy,d}}]
indent_size = 2
indent_style = space

[{*.props,*.vcxproj}]
indent_size = 2
indent_style = space
102 changes: 75 additions & 27 deletions methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from typing import Generator, List, Optional, Union, cast

from misc.utility.color import print_error, print_info, print_warning
from platform_methods import detect_arch

# Get the "Godot" folder name ahead of time
base_folder = Path(__file__).resolve().parent
Expand Down Expand Up @@ -1067,8 +1068,22 @@ def get_dependencies(file, env, exts, headers, sources, others):
platform = env["platform"]
target = env["target"]
arch = env["arch"]
host_arch = detect_arch()

host_platform = "windows"
if (
sys.platform.startswith("linux")
or sys.platform.startswith("dragonfly")
or sys.platform.startswith("freebsd")
or sys.platform.startswith("netbsd")
or sys.platform.startswith("openbsd")
):
host_platform = "linuxbsd"
elif sys.platform == "darwin":
host_platform = "macos"

vs_configuration = {}
host_vs_configuration = {}
common_build_prefix = []
confs = []
for x in sorted(glob.glob("platform/*")):
Expand Down Expand Up @@ -1097,6 +1112,12 @@ def get_dependencies(file, env, exts, headers, sources, others):
if platform == platform_name:
common_build_prefix = msvs.get_build_prefix(env)
vs_configuration = vsconf
if platform_name == host_platform:
host_vs_configuration = vsconf
for a in vsconf["arches"]:
if host_arch == a["architecture"]:
host_arch = a["platform"]
break
except Exception:
pass

Expand Down Expand Up @@ -1254,12 +1275,11 @@ def get_dependencies(file, env, exts, headers, sources, others):
properties.append(
"<ActiveProjectItemList_%s>;%s;</ActiveProjectItemList_%s>" % (x, ";".join(itemlist[x]), x)
)
output = f"bin\\godot{env['PROGSUFFIX']}"
output = os.path.join("bin", f"godot{env['PROGSUFFIX']}")

with open("misc/msvs/props.template", "r", encoding="utf-8") as file:
props_template = file.read()

props_template = props_template.replace("%%VSCONF%%", vsconf)
props_template = props_template.replace("%%CONDITION%%", condition)
props_template = props_template.replace("%%PROPERTIES%%", "\n ".join(properties))
props_template = props_template.replace("%%EXTRA_ITEMS%%", "\n ".join(extraItems))
Expand All @@ -1272,6 +1292,7 @@ def get_dependencies(file, env, exts, headers, sources, others):

proplist = [str(j) for j in env["CPPPATH"]]
proplist += [str(j) for j in env.get("VSHINT_INCLUDES", [])]
proplist += [str(j) for j in get_default_include_paths(env)]
props_template = props_template.replace("%%INCLUDES%%", ";".join(proplist))

proplist = env["CCFLAGS"]
Expand Down Expand Up @@ -1307,17 +1328,17 @@ def get_dependencies(file, env, exts, headers, sources, others):

commands = "scons"
if len(common_build_prefix) == 0:
commands = "echo Starting SCons &amp;&amp; cmd /V /C " + commands
commands = "echo Starting SCons &amp; " + commands
else:
common_build_prefix[0] = "echo Starting SCons &amp;&amp; cmd /V /C " + common_build_prefix[0]
common_build_prefix[0] = "echo Starting SCons &amp; " + common_build_prefix[0]

cmd = " ^&amp; ".join(common_build_prefix + [" ".join([commands] + common_build_postfix)])
cmd = " ".join(common_build_prefix + [" ".join([commands] + common_build_postfix)])
props_template = props_template.replace("%%BUILD%%", cmd)

cmd = " ^&amp; ".join(common_build_prefix + [" ".join([commands] + cmd_rebuild)])
cmd = " ".join(common_build_prefix + [" ".join([commands] + cmd_rebuild)])
props_template = props_template.replace("%%REBUILD%%", cmd)

cmd = " ^&amp; ".join(common_build_prefix + [" ".join([commands] + cmd_clean)])
cmd = " ".join(common_build_prefix + [" ".join([commands] + cmd_clean)])
props_template = props_template.replace("%%CLEAN%%", cmd)

with open(
Expand Down Expand Up @@ -1348,18 +1369,45 @@ def get_dependencies(file, env, exts, headers, sources, others):
section2 = []
for conf in confs:
godot_platform = conf["platform"]
has_editor = "editor" in conf["targets"]

# Skip any platforms that can build the editor and don't match the host platform.
#
# When both Windows and Mac define an editor target, it's defined as platform+target+arch (windows+editor+x64 for example).
# VS only supports two attributes, a "Configuration" and a "Platform", and we currently map our target to the Configuration
# (i.e. editor/template_debug/template_release), and our architecture to the "Platform" (i.e. x64, arm64, etc).
# Those two are not enough to disambiguate multiple godot targets for different godot platforms with the same architecture,
# i.e. editor|x64 would currently match both windows editor intel 64 and linux editor intel 64.
#
# TODO: More work is needed in order to support generating VS projects that unambiguously support all platform+target+arch variations.
# The VS "Platform" has to be a known architecture that VS recognizes, so we can only play around with the "Configuration" part of the combo.
if has_editor and godot_platform != host_vs_configuration["platform"]:
continue

for p in conf["arches"]:
sln_plat = p["platform"]
proj_plat = sln_plat
godot_arch = p["architecture"]

# Redirect editor configurations for non-Windows platforms to the Windows one, so the solution has all the permutations
# and VS doesn't complain about missing project configurations.
# Redirect editor configurations for platforms that don't support the editor target to the default editor target on the
# active host platform, so the solution has all the permutations and VS doesn't complain about missing project configurations.
# These configurations are disabled, so they show up but won't build.
if godot_platform != "windows":
if not has_editor:
section1 += [f"editor|{sln_plat} = editor|{proj_plat}"]
section2 += [
f"{{{proj_uuid}}}.editor|{proj_plat}.ActiveCfg = editor|{proj_plat}",
section2 += [f"{{{proj_uuid}}}.editor|{proj_plat}.ActiveCfg = editor|{host_arch}"]

configurations += [
f'<ProjectConfiguration Include="editor|{proj_plat}">',
" <Configuration>editor</Configuration>",
f" <Platform>{proj_plat}</Platform>",
"</ProjectConfiguration>",
]

properties += [
f"<PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='editor|{proj_plat}'\">",
" <GodotConfiguration>editor</GodotConfiguration>",
f" <GodotPlatform>{proj_plat}</GodotPlatform>",
"</PropertyGroup>",
]

for t in conf["targets"]:
Expand All @@ -1383,21 +1431,6 @@ def get_dependencies(file, env, exts, headers, sources, others):
"</PropertyGroup>",
]

if godot_platform != "windows":
configurations += [
f'<ProjectConfiguration Include="editor|{proj_plat}">',
" <Configuration>editor</Configuration>",
f" <Platform>{proj_plat}</Platform>",
"</ProjectConfiguration>",
]

properties += [
f"<PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='editor|{proj_plat}'\">",
" <GodotConfiguration>editor</GodotConfiguration>",
f" <GodotPlatform>{proj_plat}</GodotPlatform>",
"</PropertyGroup>",
]

p = f"{project_name}.{godot_platform}.{godot_target}.{godot_arch}.generated.props"
imports += [
f'<Import Project="$(MSBuildProjectDirectory)\\{p}" Condition="Exists(\'$(MSBuildProjectDirectory)\\{p}\')"/>'
Expand Down Expand Up @@ -1606,3 +1639,18 @@ def to_raw_cstring(value: Union[str, List[str]]) -> str:
else:
# Wrap multiple segments in parenthesis to suppress `string-concatenation` warnings on clang.
return "({})".format(" ".join(f'R"<!>({segment.decode()})<!>"' for segment in split))


def get_default_include_paths(env):
if env.msvc:
return []
compiler = env.subst("$CXX")
target = os.path.join(env.Dir("#main").abspath, "main.cpp")
args = [compiler, target, "-x", "c++", "-v"]
ret = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
output = ret.stdout
match = re.search(r"#include <\.\.\.> search starts here:([\S\s]*)End of search list.", output)
if not match:
print_warning("Failed to find the include paths in the compiler output.")
return []
return [x.strip() for x in match[1].strip().splitlines()]
19 changes: 19 additions & 0 deletions misc/msvs/nmake.substitution.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- override the PlatformToolset, which is set in the godot.vcxproj-->
<!-- Unknown matches to a set of conservative rules for the code analysis-->
<PlatformToolset>Unknown</PlatformToolset>
<LocalDebuggerCommand Condition="'$(LocalDebuggerCommand)' == ''">$(NMakeOutput)</LocalDebuggerCommand>
</PropertyGroup>
<!-- Build/Rebuild/Clean targets for NMake are defined in MSVC, so we need to provide them, when using MSBuild without MSVC targets -->
<Target Name="Build">
<Exec Command="$(NMakeBuildCommandLine)"/>
</Target>
<Target Name="Rebuild">
<Exec Command="$(NMakeReBuildCommandLine)"/>
</Target>
<Target Name="Clean">
<Exec Command="$(NMakeCleanCommandLine)"/>
</Target>
</Project>
5 changes: 4 additions & 1 deletion misc/msvs/props.template
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(GodotConfiguration)|$(GodotPlatform)'=='%%VSCONF%%'">
<PropertyGroup Condition="%%CONDITION%%">
<NMakeBuildCommandLine>%%BUILD%%</NMakeBuildCommandLine>
<NMakeReBuildCommandLine>%%REBUILD%%</NMakeReBuildCommandLine>
<NMakeCleanCommandLine>%%CLEAN%%</NMakeCleanCommandLine>
Expand All @@ -18,6 +18,9 @@
<ItemGroup Condition="%%CONDITION%%">
%%EXTRA_ITEMS%%
</ItemGroup>

<!-- Build/Rebuild/Clean targets for NMake are defined in MSVC, so we need to provide them, when using MSBuild without MSVC targets -->
<Import Project="$(MSBuildProjectDirectory)\misc\msvs\nmake.substitution.props" Condition="%%CONDITION%% And !Exists('$(VCTargetsPath)\Microsoft.Cpp.targets')" />
</Project>
<!-- CHECKSUM
%%HASH%%
Expand Down
13 changes: 7 additions & 6 deletions misc/msvs/vcxproj.template
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,32 @@
<VCProjectUpgraderObjectName>NoUpgrade</VCProjectUpgraderObjectName>
</PropertyGroup>
%%PROPERTIES%%
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" Condition="Exists('$(VCTargetsPath)\Microsoft.Cpp.Default.props') "/>
<PropertyGroup Label="Configuration">
<ConfigurationType>Makefile</ConfigurationType>
<UseOfMfc>false</UseOfMfc>
<PlatformToolset>v143</PlatformToolset>
<PlatformToolset>v143</PlatformToolset> <!--Might be overridden in the platform specific import or Microsoft.Cpp.$(GodotPlatform).user.props -->
<DefaultPlatformToolset Condition="'$(DefaultPlatformToolset)'==''"/> <!--Workaround until https://youtrack.jetbrains.com/issue/RIDER-123783 is resolved. -->
<OutDir>$(SolutionDir)\bin\$(GodotPlatform)\$(GodotConfiguration)\</OutDir>
<IntDir>obj\$(GodotPlatform)\$(GodotConfiguration)\</IntDir>
<LayoutDir>$(OutDir)\Layout</LayoutDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" Condition="Exists('$(VCTargetsPath)\Microsoft.Cpp.props') "/>
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(GodotPlatform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(GodotPlatform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(GodotPlatform).user.props" Condition="Exists('$(UserRootDir)\Microsoft.Cpp.$(GodotPlatform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
%%IMPORTS%%
<PropertyGroup>
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
<ActiveProjectItemList></ActiveProjectItemList>
</PropertyGroup>
%%IMPORTS%%
<ItemGroup Condition="'$(IncludeListImported)'==''">
%%DEFAULT_ITEMS%%
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" Condition="Exists('$(VCTargetsPath)\Microsoft.Cpp.targets') "/>
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
Expand Down
11 changes: 11 additions & 0 deletions platform/linuxbsd/msvs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Tuples with the name of the arch
def get_platforms():
return [("x64", "x86_64")]


def get_configurations():
return ["editor", "template_debug", "template_release"]


def get_build_prefix(env):
return []
11 changes: 11 additions & 0 deletions platform/macos/msvs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Tuples with the name of the arch
def get_platforms():
return [("arm64", "arm64"), ("x64", "x86_64")]


def get_configurations():
return ["editor", "template_debug", "template_release"]


def get_build_prefix(env):
return []
8 changes: 6 additions & 2 deletions platform/windows/msvs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ def get_configurations():


def get_build_prefix(env):
if not env.msvc:
return []
batch_file = methods.find_visual_c_batch_file(env)
return [
"cmd /V /C",
"set &quot;plat=$(PlatformTarget)&quot;",
"(if &quot;$(PlatformTarget)&quot;==&quot;x64&quot; (set &quot;plat=x86_amd64&quot;))",
f"call &quot;{batch_file}&quot; !plat!",
"^&amp; (if &quot;$(PlatformTarget)&quot;==&quot;x64&quot; (set &quot;plat=x86_amd64&quot;))",
f"^&amp; call &quot;{batch_file}&quot; !plat!",
"^&amp;",
]