Add new VS proj generation logic that supports any platform that wants to opt in

Custom Visual Studio project generation logic that supports any platform that has a msvs.py
script, so Visual Studio can be used to run scons for any platform, with the right defines per target.

Invoked with `scons vsproj=yes`

To generate build configuration files for all platforms+targets+arch combinations, users should call

```
scons vsproj=yes platform=XXX target=YYY [other build flags]
```

for each combination of platform+target[+arch]. This will generate the relevant vs project files but
skip the build process, so that project files can be quickly generated without waiting for a command line
build. This lets project files be quickly generated even if there are build errors.

All possible combinations of platform+target are created in the solution file by default, but they
won't do anything until each one is set up with a scons vsproj=yes command for the respective platform
in the appropriate command line. This lets users only generate the combinations they need, and VS
won't have to parse settings for other combos.

Only platforms that opt in to vs proj generation by having a msvs.py file in the platform folder are included.
Platforms with a msvs.py file will be added to the solution, but only the current active platform+target+arch
will have a build configuration generated, because we only know what the right defines/includes/flags/etc are
on the active build target currently being processed by scons.

Platforms that don't support an editor target will have a dummy editor target that won't do anything on build,
but will have the files and configuration for the windows editor target.

To generate AND build from the command line, run

```
scons vsproj=yes vsproj_gen_only=no
```
This commit is contained in:
Andreia Gaita 2023-11-14 13:39:44 +01:00
parent 9adb7c7d13
commit 7638a6c981
10 changed files with 595 additions and 160 deletions

View File

@ -28,7 +28,7 @@ jobs:
target: editor target: editor
tests: true tests: true
# Skip debug symbols, they're way too big with MSVC. # Skip debug symbols, they're way too big with MSVC.
sconsflags: debug_symbols=no vsproj=yes windows_subsystem=console sconsflags: debug_symbols=no vsproj=yes vsproj_gen_only=no windows_subsystem=console
bin: "./bin/godot.windows.editor.x86_64.exe" bin: "./bin/godot.windows.editor.x86_64.exe"
- name: Template (target=template_release) - name: Template (target=template_release)

1
.gitignore vendored
View File

@ -367,3 +367,4 @@ $RECYCLE.BIN/
*.msm *.msm
*.msp *.msp
*.lnk *.lnk
*.generated.props

View File

@ -1000,9 +1000,6 @@ if selected_platform in platform_list:
# Microsoft Visual Studio Project Generation # Microsoft Visual Studio Project Generation
if env["vsproj"]: if env["vsproj"]:
if os.name != "nt":
print("Error: The `vsproj` option is only usable on Windows with Visual Studio.")
Exit(255)
env["CPPPATH"] = [Dir(path) for path in env["CPPPATH"]] env["CPPPATH"] = [Dir(path) for path in env["CPPPATH"]]
methods.generate_vs_project(env, ARGUMENTS, env["vsproj_name"]) methods.generate_vs_project(env, ARGUMENTS, env["vsproj_name"])
methods.generate_cpp_hint_file("cpp.hint") methods.generate_cpp_hint_file("cpp.hint")

View File

@ -774,161 +774,6 @@ def add_to_vs_project(env, sources):
env.vs_srcs += [basename + ".cpp"] env.vs_srcs += [basename + ".cpp"]
def generate_vs_project(env, original_args, project_name="godot"):
batch_file = find_visual_c_batch_file(env)
filtered_args = original_args.copy()
# Ignore the "vsproj" option to not regenerate the VS project on every build
filtered_args.pop("vsproj", None)
# The "platform" option is ignored because only the Windows platform is currently supported for VS projects
filtered_args.pop("platform", None)
# The "target" option is ignored due to the way how targets configuration is performed for VS projects (there is a separate project configuration for each target)
filtered_args.pop("target", None)
# The "progress" option is ignored as the current compilation progress indication doesn't work in VS
filtered_args.pop("progress", None)
if batch_file:
class ModuleConfigs(Mapping):
# This version information (Win32, x64, Debug, Release) seems to be
# required for Visual Studio to understand that it needs to generate an NMAKE
# project. Do not modify without knowing what you are doing.
PLATFORMS = ["Win32", "x64"]
PLATFORM_IDS = ["x86_32", "x86_64"]
CONFIGURATIONS = ["editor", "template_release", "template_debug"]
DEV_SUFFIX = ".dev" if env["dev_build"] else ""
@staticmethod
def for_every_variant(value):
return [value for _ in range(len(ModuleConfigs.CONFIGURATIONS) * len(ModuleConfigs.PLATFORMS))]
def __init__(self):
shared_targets_array = []
self.names = []
self.arg_dict = {
"variant": [],
"runfile": shared_targets_array,
"buildtarget": shared_targets_array,
"cpppaths": [],
"cppdefines": [],
"cmdargs": [],
}
self.add_mode() # default
def add_mode(
self,
name: str = "",
includes: str = "",
cli_args: str = "",
defines=None,
):
if defines is None:
defines = []
self.names.append(name)
self.arg_dict["variant"] += [
f'{config}{f"_[{name}]" if name else ""}|{platform}'
for config in ModuleConfigs.CONFIGURATIONS
for platform in ModuleConfigs.PLATFORMS
]
self.arg_dict["runfile"] += [
f'bin\\godot.windows.{config}{ModuleConfigs.DEV_SUFFIX}{".double" if env["precision"] == "double" else ""}.{plat_id}{f".{name}" if name else ""}.exe'
for config in ModuleConfigs.CONFIGURATIONS
for plat_id in ModuleConfigs.PLATFORM_IDS
]
self.arg_dict["cpppaths"] += ModuleConfigs.for_every_variant(env["CPPPATH"] + [includes])
self.arg_dict["cppdefines"] += ModuleConfigs.for_every_variant(list(env["CPPDEFINES"]) + defines)
self.arg_dict["cmdargs"] += ModuleConfigs.for_every_variant(cli_args)
def build_commandline(self, commands):
configuration_getter = (
"$(Configuration"
+ "".join([f'.Replace("{name}", "")' for name in self.names[1:]])
+ '.Replace("_[]", "")'
+ ")"
)
common_build_prefix = [
'cmd /V /C set "plat=$(PlatformTarget)"',
'(if "$(PlatformTarget)"=="x64" (set "plat=x86_amd64"))',
'call "' + batch_file + '" !plat!',
]
# Windows allows us to have spaces in paths, so we need
# to double quote off the directory. However, the path ends
# in a backslash, so we need to remove this, lest it escape the
# last double quote off, confusing MSBuild
common_build_postfix = [
"--directory=\"$(ProjectDir.TrimEnd('\\'))\"",
"platform=windows",
f"target={configuration_getter}",
"progress=no",
]
for arg, value in filtered_args.items():
common_build_postfix.append(f"{arg}={value}")
result = " ^& ".join(common_build_prefix + [" ".join([commands] + common_build_postfix)])
return result
# Mappings interface definitions
def __iter__(self) -> Iterator[str]:
for x in self.arg_dict:
yield x
def __len__(self) -> int:
return len(self.names)
def __getitem__(self, k: str):
return self.arg_dict[k]
add_to_vs_project(env, env.core_sources)
add_to_vs_project(env, env.drivers_sources)
add_to_vs_project(env, env.main_sources)
add_to_vs_project(env, env.modules_sources)
add_to_vs_project(env, env.scene_sources)
add_to_vs_project(env, env.servers_sources)
if env["tests"]:
add_to_vs_project(env, env.tests_sources)
if env.editor_build:
add_to_vs_project(env, env.editor_sources)
for header in glob_recursive("**/*.h"):
env.vs_incs.append(str(header))
module_configs = ModuleConfigs()
if env.get("module_mono_enabled"):
mono_defines = [("GD_MONO_HOT_RELOAD",)] if env.editor_build else []
module_configs.add_mode(
"mono",
cli_args="module_mono_enabled=yes",
defines=mono_defines,
)
scons_cmd = "scons"
path_to_venv = os.getenv("VIRTUAL_ENV")
path_to_scons_exe = Path(str(path_to_venv)) / "Scripts" / "scons.exe"
if path_to_venv and path_to_scons_exe.exists():
scons_cmd = str(path_to_scons_exe)
env["MSVSBUILDCOM"] = module_configs.build_commandline(scons_cmd)
env["MSVSREBUILDCOM"] = module_configs.build_commandline(f"{scons_cmd} vsproj=yes")
env["MSVSCLEANCOM"] = module_configs.build_commandline(f"{scons_cmd} --clean")
if not env.get("MSVS"):
env["MSVS"]["PROJECTSUFFIX"] = ".vcxproj"
env["MSVS"]["SOLUTIONSUFFIX"] = ".sln"
env.MSVSProject(
target=["#" + project_name + env["MSVSPROJECTSUFFIX"]],
incs=env.vs_incs,
srcs=env.vs_srcs,
auto_build_solution=1,
**module_configs,
)
else:
print("Could not locate Visual Studio batch file to set up the build environment. Not generating VS project.")
def precious_program(env, program, sources, **args): def precious_program(env, program, sources, **args):
program = env.ProgramOriginal(program, sources, **args) program = env.ProgramOriginal(program, sources, **args)
env.Precious(program) env.Precious(program)
@ -1229,3 +1074,456 @@ def dump(env):
with open(".scons_env.json", "w") as f: with open(".scons_env.json", "w") as f:
dump(env.Dictionary(), f, indent=4, default=non_serializable) dump(env.Dictionary(), f, indent=4, default=non_serializable)
# Custom Visual Studio project generation logic that supports any platform that has a msvs.py
# script, so Visual Studio can be used to run scons for any platform, with the right defines per target.
# Invoked with scons vsproj=yes
#
# Only platforms that opt in to vs proj generation by having a msvs.py file in the platform folder are included.
# Platforms with a msvs.py file will be added to the solution, but only the current active platform+target+arch
# will have a build configuration generated, because we only know what the right defines/includes/flags/etc are
# on the active build target.
#
# Platforms that don't support an editor target will have a dummy editor target that won't do anything on build,
# but will have the files and configuration for the windows editor target.
#
# To generate build configuration files for all platforms+targets+arch combinations, users can call
# scons vsproj=yes
# for each combination of platform+target+arch. This will generate the relevant vs project files but
# skip the build process. This lets project files be quickly generated even if there are build errors.
#
# To generate AND build from the command line:
# scons vsproj=yes vsproj_gen_only=yes
def generate_vs_project(env, original_args, project_name="godot"):
# Augmented glob_recursive that also fills the dirs argument with traversed directories that have content.
def glob_recursive_2(pattern, dirs, node="."):
from SCons import Node
from SCons.Script import Glob
results = []
for f in Glob(str(node) + "/*", source=True):
if type(f) is Node.FS.Dir:
results += glob_recursive_2(pattern, dirs, f)
r = Glob(str(node) + "/" + pattern, source=True)
if len(r) > 0 and not str(node) in dirs:
d = ""
for part in str(node).split("\\"):
d += part
if not d in dirs:
dirs.append(d)
d += "\\"
results += r
return results
def get_bool(args, option, default):
from SCons.Variables.BoolVariable import _text2bool
val = args.get(option, default)
if val is not None:
try:
return _text2bool(val)
except:
return default
else:
return default
def format_key_value(v):
if type(v) in [tuple, list]:
return v[0] if len(v) == 1 else f"{v[0]}={v[1]}"
return v
filtered_args = original_args.copy()
# Ignore the "vsproj" option to not regenerate the VS project on every build
filtered_args.pop("vsproj", None)
# This flag allows users to regenerate the proj files but skip the building process.
# This lets projects be regenerated even if there are build errors.
filtered_args.pop("vsproj_gen_only", None)
# The "progress" option is ignored as the current compilation progress indication doesn't work in VS
filtered_args.pop("progress", None)
# We add these three manually because they might not be explicitly passed in, and it's important to always set them.
filtered_args.pop("platform", None)
filtered_args.pop("target", None)
filtered_args.pop("arch", None)
platform = env["platform"]
target = env["target"]
arch = env["arch"]
vs_configuration = {}
common_build_prefix = []
confs = []
for x in sorted(glob.glob("platform/*")):
# Only platforms that opt in to vs proj generation are included.
if not os.path.isdir(x) or not os.path.exists(x + "/msvs.py"):
continue
tmppath = "./" + x
sys.path.insert(0, tmppath)
import msvs
vs_plats = []
vs_confs = []
try:
platform_name = x[9:]
vs_plats = msvs.get_platforms()
vs_confs = msvs.get_configurations()
val = []
for plat in vs_plats:
val += [{"platform": plat[0], "architecture": plat[1]}]
vsconf = {"platform": platform_name, "targets": vs_confs, "arches": val}
confs += [vsconf]
# Save additional information about the configuration for the actively selected platform,
# so we can generate the platform-specific props file with all the build commands/defines/etc
if platform == platform_name:
common_build_prefix = msvs.get_build_prefix(env)
vs_configuration = vsconf
except Exception:
pass
sys.path.remove(tmppath)
sys.modules.pop("msvs")
headers = []
headers_dirs = []
for file in glob_recursive_2("*.h", headers_dirs):
headers.append(str(file).replace("/", "\\"))
for file in glob_recursive_2("*.hpp", headers_dirs):
headers.append(str(file).replace("/", "\\"))
sources = []
sources_dirs = []
for file in glob_recursive_2("*.cpp", sources_dirs):
sources.append(str(file).replace("/", "\\"))
for file in glob_recursive_2("*.c", sources_dirs):
sources.append(str(file).replace("/", "\\"))
others = []
others_dirs = []
for file in glob_recursive_2("*.natvis", others_dirs):
others.append(str(file).replace("/", "\\"))
for file in glob_recursive_2("*.glsl", others_dirs):
others.append(str(file).replace("/", "\\"))
skip_filters = False
import hashlib
import json
md5 = hashlib.md5(
json.dumps(headers + headers_dirs + sources + sources_dirs + others + others_dirs, sort_keys=True).encode(
"utf-8"
)
).hexdigest()
if os.path.exists(f"{project_name}.vcxproj.filters"):
existing_filters = open(f"{project_name}.vcxproj.filters", "r").read()
match = re.search(r"(?ms)^<!-- CHECKSUM$.([0-9a-f]{32})", existing_filters)
if match is not None and md5 == match.group(1):
skip_filters = True
import uuid
# Don't regenerate the filters file if nothing has changed, so we keep the existing UUIDs.
if not skip_filters:
print(f"Regenerating {project_name}.vcxproj.filters")
filters_template = open("misc/msvs/vcxproj.filters.template", "r").read()
for i in range(1, 10):
filters_template = filters_template.replace(f"%%UUID{i}%%", str(uuid.uuid4()))
filters = ""
for d in headers_dirs:
filters += f'<Filter Include="Header Files\\{d}"><UniqueIdentifier>{{{str(uuid.uuid4())}}}</UniqueIdentifier></Filter>\n'
for d in sources_dirs:
filters += f'<Filter Include="Source Files\\{d}"><UniqueIdentifier>{{{str(uuid.uuid4())}}}</UniqueIdentifier></Filter>\n'
for d in others_dirs:
filters += f'<Filter Include="Other Files\\{d}"><UniqueIdentifier>{{{str(uuid.uuid4())}}}</UniqueIdentifier></Filter>\n'
filters_template = filters_template.replace("%%FILTERS%%", filters)
filters = ""
for file in headers:
filters += (
f'<ClInclude Include="{file}"><Filter>Header Files\\{os.path.dirname(file)}</Filter></ClInclude>\n'
)
filters_template = filters_template.replace("%%INCLUDES%%", filters)
filters = ""
for file in sources:
filters += (
f'<ClCompile Include="{file}"><Filter>Source Files\\{os.path.dirname(file)}</Filter></ClCompile>\n'
)
filters_template = filters_template.replace("%%COMPILES%%", filters)
filters = ""
for file in others:
filters += f'<None Include="{file}"><Filter>Other Files\\{os.path.dirname(file)}</Filter></None>\n'
filters_template = filters_template.replace("%%OTHERS%%", filters)
filters_template = filters_template.replace("%%HASH%%", md5)
with open(f"{project_name}.vcxproj.filters", "w") as f:
f.write(filters_template)
envsources = []
envsources += env.core_sources
envsources += env.drivers_sources
envsources += env.main_sources
envsources += env.modules_sources
envsources += env.scene_sources
envsources += env.servers_sources
if env.editor_build:
envsources += env.editor_sources
envsources += env.platform_sources
headers_active = []
sources_active = []
others_active = []
for x in envsources:
fname = ""
if type(x) == type(""):
fname = env.File(x).path
else:
# Some object files might get added directly as a File object and not a list.
try:
fname = env.File(x)[0].path
except:
fname = x.path
pass
if fname:
fname = fname.replace("\\\\", "/")
parts = os.path.splitext(fname)
basename = parts[0]
ext = parts[1]
idx = fname.find(env["OBJSUFFIX"])
if ext in [".h", ".hpp"]:
headers_active += [fname]
elif ext in [".c", ".cpp"]:
sources_active += [fname]
elif idx > 0:
basename = fname[:idx]
if os.path.isfile(basename + ".h"):
headers_active += [basename + ".h"]
elif os.path.isfile(basename + ".hpp"):
headers_active += [basename + ".hpp"]
elif basename.endswith(".gen") and os.path.isfile(basename[:-4] + ".h"):
headers_active += [basename[:-4] + ".h"]
if os.path.isfile(basename + ".c"):
sources_active += [basename + ".c"]
elif os.path.isfile(basename + ".cpp"):
sources_active += [basename + ".cpp"]
else:
fname = os.path.relpath(os.path.abspath(fname), env.Dir("").abspath)
others_active += [fname]
all_items = []
properties = []
activeItems = []
extraItems = []
set_headers = set(headers_active)
set_sources = set(sources_active)
set_others = set(others_active)
for file in headers:
all_items.append(f'<ClInclude Include="{file}">')
all_items.append(
f" <ExcludedFromBuild Condition=\"!$(ActiveProjectItemList.Contains(';{file};'))\">true</ExcludedFromBuild>"
)
all_items.append("</ClInclude>")
if file in set_headers:
activeItems.append(file)
for file in sources:
all_items.append(f'<ClCompile Include="{file}">')
all_items.append(
f" <ExcludedFromBuild Condition=\"!$(ActiveProjectItemList.Contains(';{file};'))\">true</ExcludedFromBuild>"
)
all_items.append("</ClCompile>")
if file in set_sources:
activeItems.append(file)
for file in others:
all_items.append(f'<None Include="{file}">')
all_items.append(
f" <ExcludedFromBuild Condition=\"!$(ActiveProjectItemList.Contains(';{file};'))\">true</ExcludedFromBuild>"
)
all_items.append("</None>")
if file in set_others:
activeItems.append(file)
if vs_configuration:
vsconf = ""
for a in vs_configuration["arches"]:
if arch == a["architecture"]:
vsconf = f'{target}|{a["platform"]}'
break
condition = "'$(Configuration)|$(Platform)'=='" + vsconf + "'"
properties.append("<ActiveProjectItemList>;" + ";".join(activeItems) + ";</ActiveProjectItemList>")
output = f'bin\\godot{env["PROGSUFFIX"]}'
props_template = open("misc/msvs/props.template", "r").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))
props_template = props_template.replace("%%OUTPUT%%", output)
props_template = props_template.replace(
"%%DEFINES%%", ";".join([format_key_value(v) for v in list(env["CPPDEFINES"])])
)
props_template = props_template.replace("%%INCLUDES%%", ";".join([str(j) for j in env["CPPPATH"]]))
props_template = props_template.replace(
"%%OPTIONS%%",
" ".join(env["CCFLAGS"]) + " " + " ".join([x for x in env["CXXFLAGS"] if not x.startswith("$")]),
)
# Windows allows us to have spaces in paths, so we need
# to double quote off the directory. However, the path ends
# in a backslash, so we need to remove this, lest it escape the
# last double quote off, confusing MSBuild
common_build_postfix = [
"--directory=&quot;$(ProjectDir.TrimEnd(&apos;\\&apos;))&quot;",
"progress=no",
f"platform={platform}",
f"target={target}",
f"arch={arch}",
]
for arg, value in filtered_args.items():
common_build_postfix.append(f"{arg}={value}")
cmd_rebuild = [
"vsproj=yes",
f"vsproj_name={project_name}",
] + common_build_postfix
cmd_clean = [
"--clean",
] + common_build_postfix
commands = "scons"
if len(common_build_prefix) == 0:
commands = "echo Starting SCons &amp;&amp; cmd /V /C " + commands
else:
common_build_prefix[0] = "echo Starting SCons &amp;&amp; cmd /V /C " + common_build_prefix[0]
cmd = " ^&amp; ".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)])
props_template = props_template.replace("%%REBUILD%%", cmd)
cmd = " ^&amp; ".join(common_build_prefix + [" ".join([commands] + cmd_clean)])
props_template = props_template.replace("%%CLEAN%%", cmd)
with open(f"{project_name}.{platform}.{target}.{arch}.generated.props", "w") as f:
f.write(props_template)
proj_uuid = str(uuid.uuid4())
sln_uuid = str(uuid.uuid4())
if os.path.exists(f"{project_name}.sln"):
for line in open(f"{project_name}.sln", "r").read().splitlines():
if line.startswith('Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}")'):
proj_uuid = re.search(
r"\"{(\b[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-\b[0-9a-fA-F]{12}\b)}\"$",
line,
).group(1)
elif line.strip().startswith("SolutionGuid ="):
sln_uuid = re.search(
r"{(\b[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-\b[0-9a-fA-F]{12}\b)}", line
).group(1)
break
configurations = []
imports = []
properties = []
section1 = []
section2 = []
for conf in confs:
godot_platform = conf["platform"]
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.
# These configurations are disabled, so they show up but won't build.
if godot_platform != "windows":
section1 += [f"editor|{sln_plat} = editor|{proj_plat}"]
section2 += [
f"{{{proj_uuid}}}.editor|{proj_plat}.ActiveCfg = editor|{proj_plat}",
]
for t in conf["targets"]:
godot_target = t
# Windows x86 is a special little flower that requires a project platform == Win32 but a solution platform == x86.
if godot_platform == "windows" and godot_target == "editor" and godot_arch == "x86_32":
sln_plat = "x86"
configurations += [
f'<ProjectConfiguration Include="{godot_target}|{proj_plat}">',
f" <Configuration>{godot_target}</Configuration>",
f" <Platform>{proj_plat}</Platform>",
"</ProjectConfiguration>",
]
if godot_platform != "windows":
configurations += [
f'<ProjectConfiguration Include="editor|{proj_plat}">',
f" <Configuration>editor</Configuration>",
f" <Platform>{proj_plat}</Platform>",
"</ProjectConfiguration>",
]
p = f"{project_name}.{godot_platform}.{godot_target}.{godot_arch}.generated.props"
imports += [
f'<Import Project="$(MSBuildProjectDirectory)\\{p}" Condition="Exists(\'$(MSBuildProjectDirectory)\\{p}\')"/>'
]
section1 += [f"{godot_target}|{sln_plat} = {godot_target}|{sln_plat}"]
section2 += [
f"{{{proj_uuid}}}.{godot_target}|{sln_plat}.ActiveCfg = {godot_target}|{proj_plat}",
f"{{{proj_uuid}}}.{godot_target}|{sln_plat}.Build.0 = {godot_target}|{proj_plat}",
]
section1 = sorted(section1)
section2 = sorted(section2)
proj_template = open("misc/msvs/vcxproj.template", "r").read()
proj_template = proj_template.replace("%%UUID%%", proj_uuid)
proj_template = proj_template.replace("%%CONFS%%", "\n ".join(configurations))
proj_template = proj_template.replace("%%IMPORTS%%", "\n ".join(imports))
proj_template = proj_template.replace("%%DEFAULT_ITEMS%%", "\n ".join(all_items))
proj_template = proj_template.replace("%%PROPERTIES%%", "\n ".join(properties))
with open(f"{project_name}.vcxproj", "w") as f:
f.write(proj_template)
sln_template = open("misc/msvs/sln.template", "r").read()
sln_template = sln_template.replace("%%NAME%%", project_name)
sln_template = sln_template.replace("%%UUID%%", proj_uuid)
sln_template = sln_template.replace("%%SLNUUID%%", sln_uuid)
sln_template = sln_template.replace("%%SECTION1%%", "\n ".join(section1))
sln_template = sln_template.replace("%%SECTION2%%", "\n ".join(section2))
with open(f"{project_name}.sln", "w") as f:
f.write(sln_template)
if get_bool(original_args, "vsproj_gen_only", True):
sys.exit()

21
misc/msvs/props.template Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='%%VSCONF%%'">
<NMakeBuildCommandLine>%%BUILD%%</NMakeBuildCommandLine>
<NMakeReBuildCommandLine>%%REBUILD%%</NMakeReBuildCommandLine>
<NMakeCleanCommandLine>%%CLEAN%%</NMakeCleanCommandLine>
<NMakeOutput>%%OUTPUT%%</NMakeOutput>
<NMakePreprocessorDefinitions>%%DEFINES%%</NMakePreprocessorDefinitions>
<NMakeIncludeSearchPath>%%INCLUDES%%</NMakeIncludeSearchPath>
<NMakeForcedIncludes>$(NMakeForcedIncludes)</NMakeForcedIncludes>
<NMakeAssemblySearchPath>$(NMakeAssemblySearchPath)</NMakeAssemblySearchPath>
<NMakeForcedUsingAssemblies>$(NMakeForcedUsingAssemblies)</NMakeForcedUsingAssemblies>
<AdditionalOptions>%%OPTIONS%%</AdditionalOptions>
</PropertyGroup>
<PropertyGroup Condition="%%CONDITION%%">
%%PROPERTIES%%
</PropertyGroup>
<ItemGroup Condition="%%CONDITION%%">
%%EXTRA_ITEMS%%
</ItemGroup>
</Project>

20
misc/msvs/sln.template Normal file
View File

@ -0,0 +1,20 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34221.43
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "%%NAME%%", "%%NAME%%.vcxproj", "{%%UUID%%}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
%%SECTION1%%
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
%%SECTION2%%
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {%%SLNUUID%%}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>%%UUID1%%</UniqueIdentifier>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>%%UUID2%%</UniqueIdentifier>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>%%UUID3%%</UniqueIdentifier>
</Filter>
<Filter Include="Scripts">
<UniqueIdentifier>%%UUID4%%</UniqueIdentifier>
</Filter>
%%FILTERS%%
</ItemGroup>
<ItemGroup>
%%COMPILES%%
</ItemGroup>
<ItemGroup>
%%INCLUDES%%
</ItemGroup>
<ItemGroup>
%%OTHERS%%
</ItemGroup>
</Project>
<!-- CHECKSUM
%%HASH%%
-->

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
%%CONFS%%
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{%%UUID%%}</ProjectGuid>
<RootNamespace>godot</RootNamespace>
<Keyword>MakeFileProj</Keyword>
<VCProjectUpgraderObjectName>NoUpgrade</VCProjectUpgraderObjectName>
</PropertyGroup>
<PropertyGroup>
%%PROPERTIES%%
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Makefile</ConfigurationType>
<UseOfMfc>false</UseOfMfc>
<PlatformToolset>v143</PlatformToolset>
<OutDir>$(SolutionDir)\bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
<LayoutDir>$(OutDir)\Layout</LayoutDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
<ActiveProjectItemList></ActiveProjectItemList>
</PropertyGroup>
%%IMPORTS%%
<ItemGroup Condition="'$(IncludeListImported)'==''">
%%DEFAULT_ITEMS%%
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -7,6 +7,8 @@ from pathlib import Path
from platform_methods import run_in_subprocess from platform_methods import run_in_subprocess
import platform_windows_builders import platform_windows_builders
sources = []
common_win = [ common_win = [
"godot_windows.cpp", "godot_windows.cpp",
"crash_handler_windows.cpp", "crash_handler_windows.cpp",
@ -43,7 +45,8 @@ res_file = "godot_res.rc"
res_target = "godot_res" + env["OBJSUFFIX"] res_target = "godot_res" + env["OBJSUFFIX"]
res_obj = env.RES(res_target, res_file) res_obj = env.RES(res_target, res_file)
sources = common_win + res_obj env.add_source_files(sources, common_win)
sources += res_obj
prog = env.add_program("#bin/godot", sources, PROGSUFFIX=env["PROGSUFFIX"]) prog = env.add_program("#bin/godot", sources, PROGSUFFIX=env["PROGSUFFIX"])
arrange_program_clean(prog) arrange_program_clean(prog)
@ -65,6 +68,7 @@ if env["windows_subsystem"] == "gui":
prog_wrap = env_wrap.add_program("#bin/godot", common_win_wrap + res_wrap_obj, PROGSUFFIX=env["PROGSUFFIX_WRAP"]) prog_wrap = env_wrap.add_program("#bin/godot", common_win_wrap + res_wrap_obj, PROGSUFFIX=env["PROGSUFFIX_WRAP"])
arrange_program_clean(prog_wrap) arrange_program_clean(prog_wrap)
env_wrap.Depends(prog_wrap, prog) env_wrap.Depends(prog_wrap, prog)
sources += common_win_wrap + res_wrap_obj
# Microsoft Visual Studio Project Generation # Microsoft Visual Studio Project Generation
if env["vsproj"]: if env["vsproj"]:
@ -134,3 +138,5 @@ if not os.getenv("VCINSTALLDIR"):
env.AddPostAction(prog, run_in_subprocess(platform_windows_builders.make_debug_mingw)) env.AddPostAction(prog, run_in_subprocess(platform_windows_builders.make_debug_mingw))
if env["windows_subsystem"] == "gui": if env["windows_subsystem"] == "gui":
env.AddPostAction(prog_wrap, run_in_subprocess(platform_windows_builders.make_debug_mingw)) env.AddPostAction(prog_wrap, run_in_subprocess(platform_windows_builders.make_debug_mingw))
env.platform_sources += sources

20
platform/windows/msvs.py Normal file
View File

@ -0,0 +1,20 @@
import methods
# Tuples with the name of the arch that will be used in VS, mapped to our internal arch names.
# For Windows platforms, Win32 is what VS wants. For other platforms, it can be different.
def get_platforms():
return [("Win32", "x86_32"), ("x64", "x86_64")]
def get_configurations():
return ["editor", "template_debug", "template_release"]
def get_build_prefix(env):
batch_file = methods.find_visual_c_batch_file(env)
return [
"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!",
]