diff --git a/docs/markdown/Builtin-options.md b/docs/markdown/Builtin-options.md
index c714dc707fed..fed893e104c7 100644
--- a/docs/markdown/Builtin-options.md
+++ b/docs/markdown/Builtin-options.md
@@ -76,6 +76,7 @@ machine](#specifying-options-per-machine) section for details.
| -------------------------------------- | ------------- | ----------- | -------------- | ----------------- |
| auto_features {enabled, disabled, auto} | auto | Override value of all 'auto' features | no | no |
| backend {ninja, vs,
vs2010, vs2012, vs2013, vs2015, vs2017, vs2019, vs2022, xcode, none} | ninja | Backend to use | no | no |
+| genvslite {vs2022} | vs2022 | Setup multi-builtype ninja build directories and Visual Studio solution | no | no |
| buildtype {plain, debug,
debugoptimized, release, minsize, custom} | debug | Build type to use | no | no |
| debug | true | Enable debug symbols and other information | no | no |
| default_library {shared, static, both} | shared | Default library type | no | yes |
@@ -106,6 +107,26 @@ configure with no backend at all, which is an error if you have targets to
build, but for projects that need configuration + testing + installation allows
for a lighter automated build pipeline.
+#### Details for `genvslite`
+
+Setup multiple buildtype-suffixed, ninja-backend build directories (e.g.
+[builddir]_[debug/release/etc.]) and generate [builddir]_vs containing a Visual
+Studio solution with multiple configurations that invoke a meson compile of the
+setup build directories, as appropriate for the current configuration (builtype).
+
+This has the effect of a simple setup macro of multiple 'meson setup ...'
+invocations with a set of different buildtype values. E.g.
+`meson setup ... --genvslite vs2022 somebuilddir` does the following -
+```
+meson setup ... --backend ninja --buildtype debug somebuilddir_debug
+meson setup ... --backend ninja --buildtype debugoptimized somebuilddir_debugoptimized
+meson setup ... --backend ninja --buildtype release somebuilddir_release
+```
+and additionally creates another 'somebuilddir_vs' directory that contains
+a generated multi-configuration visual studio solution and project(s) that are
+set to build/compile with the somebuilddir_[...] that's appropriate for the
+solution's selected buildtype configuration.
+
#### Details for `buildtype`
For setting optimization levels and
diff --git a/docs/markdown/snippets/gen_vslite.md b/docs/markdown/snippets/gen_vslite.md
new file mode 100644
index 000000000000..e647b0429dd1
--- /dev/null
+++ b/docs/markdown/snippets/gen_vslite.md
@@ -0,0 +1,11 @@
+## Added a new '--genvslite' option for use with 'meson setup ...'
+
+To facilitate a more usual visual studio work-flow of supporting and switching between
+multiple build configurations (buildtypes) within the same solution, among other
+[reasons](https://github.com/mesonbuild/meson/pull/11049), use of this new option
+has the effect of setting up multiple ninja back-end-configured build directories,
+named with their respective buildtype suffix. E.g. 'somebuilddir_debug',
+'somebuilddir_release', etc. as well as a '_vs'-suffixed directory that contains the
+generated multi-buildtype solution. Building/cleaning/rebuilding in the solution
+now launches the meson build (compile) of the corresponding buildtype-suffixed build
+directory, instead of using Visual Studio's native engine.
\ No newline at end of file
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index dec1e68cc53e..9e247b6fc479 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -256,6 +256,13 @@ def get_backend_from_name(backend: str, build: T.Optional[build.Build] = None, i
return nonebackend.NoneBackend(build, interpreter)
return None
+
+def get_genvslite_backend(genvsname: str, build: T.Optional[build.Build] = None, interpreter: T.Optional['Interpreter'] = None) -> T.Optional['Backend']:
+ if genvsname == 'vs2022':
+ from . import vs2022backend
+ return vs2022backend.Vs2022Backend(build, interpreter, gen_lite = True)
+ return None
+
# This class contains the basic functionality that is needed by all backends.
# Feel free to move stuff in and out of it as you see fit.
class Backend:
@@ -280,7 +287,17 @@ def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional['Inte
self.src_to_build = mesonlib.relpath(self.environment.get_build_dir(),
self.environment.get_source_dir())
- def generate(self) -> None:
+ # If requested via 'capture = True', returns captured compile args per
+ # target (e.g. captured_args[target]) that can be used later, for example,
+ # to populate things like intellisense fields in generated visual studio
+ # projects (as is the case when using '--genvslite').
+ #
+ # 'captured_compile_args_per_buildtype_and_target' is only provided when
+ # we expect this backend setup/generation to make use of previously captured
+ # compile args (as is the case when using '--genvslite').
+ def generate(self,
+ capture: bool = False,
+ captured_compile_args_per_buildtype_and_target: dict = None) -> T.Optional[dict]:
raise RuntimeError(f'generate is not implemented in {type(self).__name__}')
def get_target_filename(self, t: T.Union[build.Target, build.CustomTargetIndex], *, warn_multi_output: bool = True) -> str:
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index ba614fd564f7..21398476504d 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -38,7 +38,7 @@
from ..compilers import Compiler
from ..linkers import ArLikeLinker, RSPFileSyntax
from ..mesonlib import (
- File, LibType, MachineChoice, MesonException, OrderedSet, PerMachine,
+ File, LibType, MachineChoice, MesonBugException, MesonException, OrderedSet, PerMachine,
ProgressBar, quote_arg
)
from ..mesonlib import get_compiler_for_source, has_path_sep, OptionKey
@@ -575,7 +575,13 @@ def detect_prefix(out):
raise MesonException(f'Could not determine vs dep dependency prefix string. output: {stderr} {stdout}')
- def generate(self):
+ def generate(self,
+ capture: bool = False,
+ captured_compile_args_per_buildtype_and_target: dict = None) -> T.Optional[dict]:
+ if captured_compile_args_per_buildtype_and_target:
+ # We don't yet have a use case where we'd expect to make use of this,
+ # so no harm in catching and reporting something unexpected.
+ raise MesonBugException('We do not expect the ninja backend to be given a valid \'captured_compile_args_per_buildtype_and_target\'')
ninja = environment.detect_ninja_command_and_version(log=True)
if self.environment.coredata.get_option(OptionKey('vsenv')):
builddir = Path(self.environment.get_build_dir())
@@ -614,6 +620,14 @@ def generate(self):
self.build_elements = []
self.generate_phony()
self.add_build_comment(NinjaComment('Build rules for targets'))
+
+ # Optionally capture compile args per target, for later use (i.e. VisStudio project's NMake intellisense include dirs, defines, and compile options).
+ if capture:
+ captured_compile_args_per_target = {}
+ for target in self.build.get_targets().values():
+ if isinstance(target, build.BuildTarget):
+ captured_compile_args_per_target[target.get_id()] = self.generate_common_compile_args_per_src_type(target)
+
for t in ProgressBar(self.build.get_targets().values(), desc='Generating targets'):
self.generate_target(t)
self.add_build_comment(NinjaComment('Test rules'))
@@ -652,6 +666,9 @@ def generate(self):
self.generate_compdb()
self.generate_rust_project_json()
+ if capture:
+ return captured_compile_args_per_target
+
def generate_rust_project_json(self) -> None:
"""Generate a rust-analyzer compatible rust-project.json file."""
if not self.rust_crates:
@@ -2922,6 +2939,34 @@ def _generate_single_compile_target_args(self, target: build.BuildTarget, compil
commands += compiler.get_include_args(self.get_target_private_dir(target), False)
return commands
+ # Returns a dictionary, mapping from each compiler src type (e.g. 'c', 'cpp', etc.) to a list of compiler arg strings
+ # used for that respective src type.
+ # Currently used for the purpose of populating VisualStudio intellisense fields but possibly useful in other scenarios.
+ def generate_common_compile_args_per_src_type(self, target: build.BuildTarget) -> dict[str, list[str]]:
+ src_type_to_args = {}
+
+ use_pch = self.environment.coredata.options.get(OptionKey('b_pch'))
+
+ for src_type_str in target.compilers.keys():
+ compiler = target.compilers[src_type_str]
+ commands = self._generate_single_compile_base_args(target, compiler)
+
+ # Include PCH header as first thing as it must be the first one or it will be
+ # ignored by gcc https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100462
+ if use_pch and 'mw' not in compiler.id:
+ commands += self.get_pch_include_args(compiler, target)
+
+ commands += self._generate_single_compile_target_args(target, compiler, is_generated=False)
+
+ # Metrowerks compilers require PCH include args to come after intraprocedural analysis args
+ if use_pch and 'mw' in compiler.id:
+ commands += self.get_pch_include_args(compiler, target)
+
+ commands = commands.compiler.compiler_args(commands)
+
+ src_type_to_args[src_type_str] = commands.to_native()
+ return src_type_to_args
+
def generate_single_compile(self, target: build.BuildTarget, src,
is_generated=False, header_deps=None,
order_deps: T.Optional[T.List['mesonlib.FileOrString']] = None,
diff --git a/mesonbuild/backend/nonebackend.py b/mesonbuild/backend/nonebackend.py
index cf7c3dde93da..79ee7b2e1c31 100644
--- a/mesonbuild/backend/nonebackend.py
+++ b/mesonbuild/backend/nonebackend.py
@@ -14,6 +14,8 @@
from __future__ import annotations
+import typing as T
+
from .backends import Backend
from .. import mlog
from ..mesonlib import MesonBugException
@@ -23,7 +25,15 @@ class NoneBackend(Backend):
name = 'none'
- def generate(self):
+ def generate(self,
+ capture: bool = False,
+ captured_compile_args_per_buildtype_and_target: dict = None) -> T.Optional[dict]:
+ # Check for (currently) unexpected capture arg use cases -
+ if capture:
+ raise MesonBugException('We do not expect the none backend to generate with \'capture = True\'')
+ if captured_compile_args_per_buildtype_and_target:
+ raise MesonBugException('We do not expect the none backend to be given a valid \'captured_compile_args_per_buildtype_and_target\'')
+
if self.build.get_targets():
raise MesonBugException('None backend cannot generate target rules, but should have failed earlier.')
mlog.log('Generating simple install-only backend')
diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py
index d6b2f44c4102..501525a8f3de 100644
--- a/mesonbuild/backend/vs2010backend.py
+++ b/mesonbuild/backend/vs2010backend.py
@@ -22,15 +22,18 @@
import typing as T
from pathlib import Path, PurePath
import re
+from collections import Counter
from . import backends
from .. import build
from .. import mlog
from .. import compilers
+from .. import mesonlib
from ..mesonlib import (
- File, MesonException, replace_if_different, OptionKey, version_compare, MachineChoice
+ File, MesonBugException, MesonException, replace_if_different, OptionKey, version_compare, MachineChoice
)
from ..environment import Environment, build_filename
+from .. import coredata
if T.TYPE_CHECKING:
from ..arglist import CompilerArgs
@@ -94,18 +97,57 @@ def split_o_flags_args(args: T.List[str]) -> T.List[str]:
o_flags += ['/O' + f for f in flags]
return o_flags
-
def generate_guid_from_path(path, path_type) -> str:
return str(uuid.uuid5(uuid.NAMESPACE_URL, 'meson-vs-' + path_type + ':' + str(path))).upper()
def detect_microsoft_gdk(platform: str) -> bool:
return re.match(r'Gaming\.(Desktop|Xbox.XboxOne|Xbox.Scarlett)\.x64', platform, re.IGNORECASE)
+def filtered_src_langs_generator(sources: T.List[str]):
+ for src in sources:
+ ext = src.split('.')[-1]
+ if compilers.compilers.is_source_suffix(ext):
+ yield compilers.compilers.SUFFIX_TO_LANG[ext]
+
+# Returns the source language (i.e. a key from 'lang_suffixes') of the most frequent source language in the given
+# list of sources.
+# We choose the most frequent language as 'primary' because it means the most sources in a target/project can
+# simply refer to the project's shared intellisense define and include fields, rather than have to fill out their
+# own duplicate full set of defines/includes/opts intellisense fields. All of which helps keep the vcxproj file
+# size down.
+def get_primary_source_lang(target_sources: T.List[File], custom_sources: T.List[str]) -> T.Optional[str]:
+ lang_counts = Counter([compilers.compilers.SUFFIX_TO_LANG[src.suffix] for src in target_sources if compilers.compilers.is_source_suffix(src.suffix)])
+ lang_counts += Counter(filtered_src_langs_generator(custom_sources))
+ most_common_lang_list = lang_counts.most_common(1)
+ # It may be possible that we have a target with no actual src files of interest (e.g. a generator target),
+ # leaving us with an empty list, which we should handle -
+ return most_common_lang_list[0][0] if most_common_lang_list else None
+
+# Returns a dictionary (by [src type][build type]) that contains a tuple of -
+# (pre-processor defines, include paths, additional compiler options)
+# fields to use to fill in the respective intellisense fields of sources that can't simply
+# reference and re-use the shared 'primary' language intellisense fields of the vcxproj.
+def get_non_primary_lang_intellisense_fields(captured_compile_args_per_buildtype_and_target: dict,
+ target_id: str,
+ primary_src_lang: str) -> T.Dict[str, T.Dict[str, T.Tuple[str, str, str]]]:
+ defs_paths_opts_per_lang_and_buildtype = {}
+ for buildtype in coredata.get_genvs_default_buildtype_list():
+ captured_build_args = captured_compile_args_per_buildtype_and_target[buildtype][target_id] # Results in a 'Src types to compile args' dict
+ non_primary_build_args_per_src_lang = [(lang, build_args) for lang, build_args in captured_build_args.items() if lang != primary_src_lang] # Only need to individually populate intellisense fields for sources of non-primary types.
+ for src_lang, args_list in non_primary_build_args_per_src_lang:
+ if src_lang not in defs_paths_opts_per_lang_and_buildtype:
+ defs_paths_opts_per_lang_and_buildtype[src_lang] = {}
+ defs = Vs2010Backend.extract_nmake_preprocessor_defs(args_list)
+ paths = Vs2010Backend.extract_nmake_include_paths(args_list)
+ opts = Vs2010Backend.extract_intellisense_additional_compiler_options(args_list)
+ defs_paths_opts_per_lang_and_buildtype[src_lang][buildtype] = (defs, paths, opts)
+ return defs_paths_opts_per_lang_and_buildtype
+
class Vs2010Backend(backends.Backend):
name = 'vs2010'
- def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]):
+ def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter], gen_lite: bool = False):
super().__init__(build, interpreter)
self.project_file_version = '10.0.30319.1'
self.sln_file_version = '11.00'
@@ -115,6 +157,7 @@ def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Inter
self.windows_target_platform_version = None
self.subdirs = {}
self.handled_target_deps = {}
+ self.gen_lite = gen_lite # Synonymous with generating the simpler makefile-style multi-config projects that invoke 'meson compile' builds, avoiding native MSBuild complications
def get_target_private_dir(self, target):
return os.path.join(self.get_target_dir(target), target.get_id())
@@ -189,7 +232,12 @@ def generate_custom_generator_commands(self, target, parent_node):
self.generate_genlist_for_target(genlist, target, parent_node, generator_output_files, custom_target_include_dirs, custom_target_output_files)
return generator_output_files, custom_target_output_files, custom_target_include_dirs
- def generate(self) -> None:
+ def generate(self,
+ capture: bool = False,
+ captured_compile_args_per_buildtype_and_target: dict = None) -> T.Optional[dict]:
+ # Check for (currently) unexpected capture arg use cases -
+ if capture:
+ raise MesonBugException('We do not expect any vs backend to generate with \'capture = True\'')
target_machine = self.interpreter.builtin['target_machine'].cpu_family_method(None, None)
if target_machine in {'64', 'x86_64'}:
# amd64 or x86_64
@@ -238,10 +286,10 @@ def generate(self) -> None:
except MesonException:
self.sanitize = 'none'
sln_filename = os.path.join(self.environment.get_build_dir(), self.build.project_name + '.sln')
- projlist = self.generate_projects()
- self.gen_testproj('RUN_TESTS', os.path.join(self.environment.get_build_dir(), 'RUN_TESTS.vcxproj'))
- self.gen_installproj('RUN_INSTALL', os.path.join(self.environment.get_build_dir(), 'RUN_INSTALL.vcxproj'))
- self.gen_regenproj('REGEN', os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj'))
+ projlist = self.generate_projects(captured_compile_args_per_buildtype_and_target)
+ self.gen_testproj()
+ self.gen_installproj()
+ self.gen_regenproj()
self.generate_solution(sln_filename, projlist)
self.generate_regen_info()
Vs2010Backend.touch_regen_timestamp(self.environment.get_build_dir())
@@ -382,8 +430,7 @@ def generate_solution(self, sln_filename: str, projlist: T.List[Project]) -> Non
ofile.write('# Visual Studio %s\n' % self.sln_version_comment)
prj_templ = 'Project("{%s}") = "%s", "%s", "{%s}"\n'
for prj in projlist:
- coredata = self.environment.coredata
- if coredata.get_option(OptionKey('layout')) == 'mirror':
+ if self.environment.coredata.get_option(OptionKey('layout')) == 'mirror':
self.generate_solution_dirs(ofile, prj[1].parents)
target = self.build.targets[prj[0]]
lang = 'default'
@@ -409,8 +456,14 @@ def generate_solution(self, sln_filename: str, projlist: T.List[Project]) -> Non
self.environment.coredata.test_guid)
ofile.write(test_line)
ofile.write('EndProject\n')
+ if self.gen_lite: # REGEN is replaced by the lighter-weight RECONFIGURE utility, for now. See comment in 'gen_regenproj'
+ regen_proj_name = 'RECONFIGURE'
+ regen_proj_fname = 'RECONFIGURE.vcxproj'
+ else:
+ regen_proj_name = 'REGEN'
+ regen_proj_fname = 'REGEN.vcxproj'
regen_line = prj_templ % (self.environment.coredata.lang_guids['default'],
- 'REGEN', 'REGEN.vcxproj',
+ regen_proj_name, regen_proj_fname,
self.environment.coredata.regen_guid)
ofile.write(regen_line)
ofile.write('EndProject\n')
@@ -422,18 +475,23 @@ def generate_solution(self, sln_filename: str, projlist: T.List[Project]) -> Non
ofile.write('Global\n')
ofile.write('\tGlobalSection(SolutionConfigurationPlatforms) = '
'preSolution\n')
- ofile.write('\t\t%s|%s = %s|%s\n' %
- (self.buildtype, self.platform, self.buildtype,
- self.platform))
+ multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list() if self.gen_lite else [self.buildtype]
+ for buildtype in multi_config_buildtype_list:
+ ofile.write('\t\t%s|%s = %s|%s\n' %
+ (buildtype, self.platform, buildtype,
+ self.platform))
ofile.write('\tEndGlobalSection\n')
ofile.write('\tGlobalSection(ProjectConfigurationPlatforms) = '
'postSolution\n')
- ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
- (self.environment.coredata.regen_guid, self.buildtype,
- self.platform, self.buildtype, self.platform))
- ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' %
- (self.environment.coredata.regen_guid, self.buildtype,
- self.platform, self.buildtype, self.platform))
+ # REGEN project (multi-)configurations
+ for buildtype in multi_config_buildtype_list:
+ ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
+ (self.environment.coredata.regen_guid, buildtype,
+ self.platform, buildtype, self.platform))
+ if not self.gen_lite: # With a 'genvslite'-generated solution, the regen (i.e. reconfigure) utility is only intended to run when the user explicitly builds this proj.
+ ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' %
+ (self.environment.coredata.regen_guid, buildtype,
+ self.platform, buildtype, self.platform))
# Create the solution configuration
for p in projlist:
if p[3] is MachineChoice.BUILD:
@@ -441,21 +499,31 @@ def generate_solution(self, sln_filename: str, projlist: T.List[Project]) -> Non
else:
config_platform = self.platform
# Add to the list of projects in this solution
+ for buildtype in multi_config_buildtype_list:
+ ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
+ (p[2], buildtype, self.platform,
+ buildtype, config_platform))
+ # If we're building the solution with Visual Studio's build system, enable building of buildable
+ # projects. However, if we're building with meson (via --genvslite), then, since each project's
+ # 'build' action just ends up doing the same 'meson compile ...' we don't want the 'solution build'
+ # repeatedly going off and doing the same 'meson compile ...' multiple times over, so we just
+ # leave it up to the user to select or build just one project.
+ # FIXME: Would be slightly nicer if we could enable building of just one top level target/project,
+ # but not sure how to identify that.
+ if not self.gen_lite and \
+ p[0] in default_projlist and \
+ not isinstance(self.build.targets[p[0]], build.RunTarget):
+ ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' %
+ (p[2], buildtype, self.platform,
+ buildtype, config_platform))
+ # RUN_TESTS and RUN_INSTALL project (multi-)configurations
+ for buildtype in multi_config_buildtype_list:
ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
- (p[2], self.buildtype, self.platform,
- self.buildtype, config_platform))
- if p[0] in default_projlist and \
- not isinstance(self.build.targets[p[0]], build.RunTarget):
- # Add to the list of projects to be built
- ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' %
- (p[2], self.buildtype, self.platform,
- self.buildtype, config_platform))
- ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
- (self.environment.coredata.test_guid, self.buildtype,
- self.platform, self.buildtype, self.platform))
- ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
- (self.environment.coredata.install_guid, self.buildtype,
- self.platform, self.buildtype, self.platform))
+ (self.environment.coredata.test_guid, buildtype,
+ self.platform, buildtype, self.platform))
+ ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
+ (self.environment.coredata.install_guid, buildtype,
+ self.platform, buildtype, self.platform))
ofile.write('\tEndGlobalSection\n')
ofile.write('\tGlobalSection(SolutionProperties) = preSolution\n')
ofile.write('\t\tHideSolutionNode = FALSE\n')
@@ -473,7 +541,7 @@ def generate_solution(self, sln_filename: str, projlist: T.List[Project]) -> Non
ofile.write('EndGlobal\n')
replace_if_different(sln_filename, sln_filename_tmp)
- def generate_projects(self) -> T.List[Project]:
+ def generate_projects(self, captured_compile_args_per_buildtype_and_target: dict = None) -> T.List[Project]:
startup_project = self.environment.coredata.options[OptionKey('backend_startup_project')].value
projlist: T.List[Project] = []
startup_idx = 0
@@ -490,8 +558,9 @@ def generate_projects(self) -> T.List[Project]:
relname = target_dir / fname
projfile_path = outdir / fname
proj_uuid = self.environment.coredata.target_guids[name]
- self.gen_vcxproj(target, str(projfile_path), proj_uuid)
- projlist.append((name, relname, proj_uuid, target.for_machine))
+ generated = self.gen_vcxproj(target, str(projfile_path), proj_uuid, captured_compile_args_per_buildtype_and_target)
+ if generated:
+ projlist.append((name, relname, proj_uuid, target.for_machine))
# Put the startup project first in the project list
if startup_idx:
@@ -570,12 +639,13 @@ def create_basic_project(self, target_name, *,
confitems = ET.SubElement(root, 'ItemGroup', {'Label': 'ProjectConfigurations'})
if not target_platform:
target_platform = self.platform
- prjconf = ET.SubElement(confitems, 'ProjectConfiguration',
- {'Include': self.buildtype + '|' + target_platform})
- p = ET.SubElement(prjconf, 'Configuration')
- p.text = self.buildtype
- pl = ET.SubElement(prjconf, 'Platform')
- pl.text = target_platform
+
+ multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list() if self.gen_lite else [self.buildtype]
+ for buildtype in multi_config_buildtype_list:
+ prjconf = ET.SubElement(confitems, 'ProjectConfiguration',
+ {'Include': buildtype + '|' + target_platform})
+ ET.SubElement(prjconf, 'Configuration').text = buildtype
+ ET.SubElement(prjconf, 'Platform').text = target_platform
# Globals
globalgroup = ET.SubElement(root, 'PropertyGroup', Label='Globals')
@@ -583,46 +653,52 @@ def create_basic_project(self, target_name, *,
guidelem.text = '{%s}' % guid
kw = ET.SubElement(globalgroup, 'Keyword')
kw.text = self.platform + 'Proj'
- # XXX Wasn't here before for anything but gen_vcxproj , but seems fine?
- ns = ET.SubElement(globalgroup, 'RootNamespace')
- ns.text = target_name
-
- p = ET.SubElement(globalgroup, 'Platform')
- p.text = target_platform
- pname = ET.SubElement(globalgroup, 'ProjectName')
- pname.text = target_name
- if self.windows_target_platform_version:
- ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version
- ET.SubElement(globalgroup, 'UseMultiToolTask').text = 'true'
ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.Default.props')
- # Start configuration
+ # Configuration
type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration')
ET.SubElement(type_config, 'ConfigurationType').text = conftype
- ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte'
- # Fixme: wasn't here before for gen_vcxproj()
- ET.SubElement(type_config, 'UseOfMfc').text = 'false'
if self.platform_toolset:
ET.SubElement(type_config, 'PlatformToolset').text = self.platform_toolset
- # End configuration section (but it can be added to further via type_config)
+ # This must come AFTER the '' element; importing before the 'PlatformToolset' elt
+ # gets set leads to msbuild failures reporting -
+ # "The build tools for v142 (Platform Toolset = 'v142') cannot be found. ... please install v142 build tools."
+ # This is extremely unhelpful and misleading since the v14x build tools ARE installed.
ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.props')
- # Project information
- direlem = ET.SubElement(root, 'PropertyGroup')
- fver = ET.SubElement(direlem, '_ProjectFileVersion')
- fver.text = self.project_file_version
- outdir = ET.SubElement(direlem, 'OutDir')
- outdir.text = '.\\'
- intdir = ET.SubElement(direlem, 'IntDir')
- intdir.text = temp_dir + '\\'
-
- tname = ET.SubElement(direlem, 'TargetName')
- tname.text = target_name
-
- if target_ext:
- ET.SubElement(direlem, 'TargetExt').text = target_ext
+ if not self.gen_lite: # Plenty of elements aren't necessary for 'makefile'-style project that just redirects to meson builds
+ # XXX Wasn't here before for anything but gen_vcxproj , but seems fine?
+ ns = ET.SubElement(globalgroup, 'RootNamespace')
+ ns.text = target_name
+
+ p = ET.SubElement(globalgroup, 'Platform')
+ p.text = target_platform
+ pname = ET.SubElement(globalgroup, 'ProjectName')
+ pname.text = target_name
+ if self.windows_target_platform_version:
+ ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version
+ ET.SubElement(globalgroup, 'UseMultiToolTask').text = 'true'
+
+ ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte'
+ # Fixme: wasn't here before for gen_vcxproj()
+ ET.SubElement(type_config, 'UseOfMfc').text = 'false'
+
+ # Project information
+ direlem = ET.SubElement(root, 'PropertyGroup')
+ fver = ET.SubElement(direlem, '_ProjectFileVersion')
+ fver.text = self.project_file_version
+ outdir = ET.SubElement(direlem, 'OutDir')
+ outdir.text = '.\\'
+ intdir = ET.SubElement(direlem, 'IntDir')
+ intdir.text = temp_dir + '\\'
+
+ tname = ET.SubElement(direlem, 'TargetName')
+ tname.text = target_name
+
+ if target_ext:
+ ET.SubElement(direlem, 'TargetExt').text = target_ext
return (root, type_config)
@@ -780,6 +856,36 @@ def add_additional_options(self, lang, parent_node, file_args):
args.append(self.escape_additional_option(arg))
ET.SubElement(parent_node, "AdditionalOptions").text = ' '.join(args)
+ # Set up each project's source file ('CLCompile') element with appropriate preprocessor, include dir, and compile option values for correct intellisense.
+ def add_project_nmake_defs_incs_and_opts(self, parent_node, src: str, defs_paths_opts_per_lang_and_buildtype: dict, platform: str):
+ # For compactness, sources whose type matches the primary src type (i.e. most frequent in the set of source types used in the target/project,
+ # according to the 'captured_build_args' map), can simply reference the preprocessor definitions, include dirs, and compile option NMake fields of
+ # the project itself.
+ # However, if a src is of a non-primary type, it could have totally different defs/dirs/options so we're going to have to fill in the full, verbose
+ # set of values for these fields, which needs to be fully expanded per build type / configuration.
+ #
+ # FIXME: Suppose a project contains .cpp and .c src files with different compile defs/dirs/options, while also having .h files, some of which
+ # are included by .cpp sources and others included by .c sources: How do we know whether the .h source should be using the .cpp or .c src
+ # defs/dirs/options? Might it also be possible for a .h header to be shared between .cpp and .c sources? If so, I don't see how we can
+ # correctly configure these intellisense fields.
+ # For now, all sources/headers that fail to find their extension's language in the '...nmake_defs_paths_opts...' map will just adopt the project
+ # defs/dirs/opts that are set for the nominal 'primary' src type.
+ ext = src.split('.')[-1]
+ lang = compilers.compilers.SUFFIX_TO_LANG.get(ext, None)
+ if lang in defs_paths_opts_per_lang_and_buildtype.keys():
+ # This is a non-primary src type for which can't simply reference the project's nmake fields;
+ # we must laboriously fill in the fields for all buildtypes.
+ for buildtype in coredata.get_genvs_default_buildtype_list():
+ (defs, paths, opts) = defs_paths_opts_per_lang_and_buildtype[lang][buildtype]
+ condition = f'\'$(Configuration)|$(Platform)\'==\'{buildtype}|{platform}\''
+ ET.SubElement(parent_node, 'PreprocessorDefinitions', Condition=condition).text = defs
+ ET.SubElement(parent_node, 'AdditionalIncludeDirectories', Condition=condition).text = paths
+ ET.SubElement(parent_node, 'AdditionalOptions', Condition=condition).text = opts
+ else: # Can't find bespoke nmake defs/dirs/opts fields for this extention, so just reference the project's fields
+ ET.SubElement(parent_node, 'PreprocessorDefinitions').text = '$(NMakePreprocessorDefinitions)'
+ ET.SubElement(parent_node, 'AdditionalIncludeDirectories').text = '$(NMakeIncludeSearchPath)'
+ ET.SubElement(parent_node, 'AdditionalOptions').text = '$(AdditionalOptions)'
+
def add_preprocessor_defines(self, lang, parent_node, file_defines):
defines = []
for define in file_defines[lang]:
@@ -918,147 +1024,8 @@ def _prettyprint_vcxproj_xml(self, tree: ET.ElementTree, ofname: str) -> None:
of.write(doc.toprettyxml())
replace_if_different(ofname, ofname_tmp)
- def gen_vcxproj(self, target: build.BuildTarget, ofname: str, guid: str) -> None:
- mlog.debug(f'Generating vcxproj {target.name}.')
- subsystem = 'Windows'
- self.handled_target_deps[target.get_id()] = []
- if isinstance(target, build.Executable):
- conftype = 'Application'
- if target.gui_app is not None:
- if not target.gui_app:
- subsystem = 'Console'
- else:
- # If someone knows how to set the version properly,
- # please send a patch.
- subsystem = target.win_subsystem.split(',')[0]
- elif isinstance(target, build.StaticLibrary):
- conftype = 'StaticLibrary'
- elif isinstance(target, build.SharedLibrary):
- conftype = 'DynamicLibrary'
- elif isinstance(target, build.CustomTarget):
- return self.gen_custom_target_vcxproj(target, ofname, guid)
- elif isinstance(target, build.RunTarget):
- return self.gen_run_target_vcxproj(target, ofname, guid)
- elif isinstance(target, build.CompileTarget):
- return self.gen_compile_target_vcxproj(target, ofname, guid)
- else:
- raise MesonException(f'Unknown target type for {target.get_basename()}')
- assert isinstance(target, (build.Executable, build.SharedLibrary, build.StaticLibrary, build.SharedModule)), 'for mypy'
- # Prefix to use to access the build root from the vcxproj dir
- down = self.target_to_build_root(target)
- # Prefix to use to access the source tree's root from the vcxproj dir
- proj_to_src_root = os.path.join(down, self.build_to_src)
- # Prefix to use to access the source tree's subdir from the vcxproj dir
- proj_to_src_dir = os.path.join(proj_to_src_root, self.get_target_dir(target))
- (sources, headers, objects, languages) = self.split_sources(target.sources)
- if target.is_unity:
- sources = self.generate_unity_files(target, sources)
- compiler = self._get_cl_compiler(target)
- build_args = compiler.get_buildtype_args(self.buildtype)
- build_args += compiler.get_optimization_args(self.optimization)
- build_args += compiler.get_debug_args(self.debug)
- build_args += compiler.sanitizer_compile_args(self.sanitize)
- buildtype_link_args = compiler.get_buildtype_linker_args(self.buildtype)
- vscrt_type = self.environment.coredata.options[OptionKey('b_vscrt')]
- target_name = target.name
- if target.for_machine is MachineChoice.BUILD:
- platform = self.build_platform
- else:
- platform = self.platform
-
- tfilename = os.path.splitext(target.get_filename())
-
- (root, type_config) = self.create_basic_project(tfilename[0],
- temp_dir=target.get_id(),
- guid=guid,
- conftype=conftype,
- target_ext=tfilename[1],
- target_platform=platform)
- # vcxproj.filters file
- root_filter = self.create_basic_project_filters()
-
- # FIXME: Should these just be set in create_basic_project(), even if
- # irrelevant for current target?
-
- # FIXME: Meson's LTO support needs to be integrated here
- ET.SubElement(type_config, 'WholeProgramOptimization').text = 'false'
- # Let VS auto-set the RTC level
- ET.SubElement(type_config, 'BasicRuntimeChecks').text = 'Default'
- # Incremental linking increases code size
- if '/INCREMENTAL:NO' in buildtype_link_args:
- ET.SubElement(type_config, 'LinkIncremental').text = 'false'
-
- # Build information
- compiles = ET.SubElement(root, 'ItemDefinitionGroup')
- clconf = ET.SubElement(compiles, 'ClCompile')
- # CRT type; debug or release
- if vscrt_type.value == 'from_buildtype':
- if self.buildtype == 'debug':
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL'
- else:
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDLL'
- elif vscrt_type.value == 'static_from_buildtype':
- if self.buildtype == 'debug':
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebug'
- else:
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreaded'
- elif vscrt_type.value == 'mdd':
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL'
- elif vscrt_type.value == 'mt':
- # FIXME, wrong
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreaded'
- elif vscrt_type.value == 'mtd':
- # FIXME, wrong
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebug'
- else:
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDLL'
- # Sanitizers
- if '/fsanitize=address' in build_args:
- ET.SubElement(type_config, 'EnableASAN').text = 'true'
- # Debug format
- if '/ZI' in build_args:
- ET.SubElement(clconf, 'DebugInformationFormat').text = 'EditAndContinue'
- elif '/Zi' in build_args:
- ET.SubElement(clconf, 'DebugInformationFormat').text = 'ProgramDatabase'
- elif '/Z7' in build_args:
- ET.SubElement(clconf, 'DebugInformationFormat').text = 'OldStyle'
- else:
- ET.SubElement(clconf, 'DebugInformationFormat').text = 'None'
- # Runtime checks
- if '/RTC1' in build_args:
- ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'EnableFastChecks'
- elif '/RTCu' in build_args:
- ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'UninitializedLocalUsageCheck'
- elif '/RTCs' in build_args:
- ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'StackFrameRuntimeCheck'
- # Exception handling has to be set in the xml in addition to the "AdditionalOptions" because otherwise
- # cl will give warning D9025: overriding '/Ehs' with cpp_eh value
- if 'cpp' in target.compilers:
- eh = self.environment.coredata.options[OptionKey('eh', machine=target.for_machine, lang='cpp')]
- if eh.value == 'a':
- ET.SubElement(clconf, 'ExceptionHandling').text = 'Async'
- elif eh.value == 's':
- ET.SubElement(clconf, 'ExceptionHandling').text = 'SyncCThrow'
- elif eh.value == 'none':
- ET.SubElement(clconf, 'ExceptionHandling').text = 'false'
- else: # 'sc' or 'default'
- ET.SubElement(clconf, 'ExceptionHandling').text = 'Sync'
- generated_files, custom_target_output_files, generated_files_include_dirs = self.generate_custom_generator_commands(
- target, root)
- (gen_src, gen_hdrs, gen_objs, gen_langs) = self.split_sources(generated_files)
- (custom_src, custom_hdrs, custom_objs, custom_langs) = self.split_sources(custom_target_output_files)
- gen_src += custom_src
- gen_hdrs += custom_hdrs
- gen_langs += custom_langs
-
+ # Returns: (target_args,file_args), (target_defines,file_defines), (target_inc_dirs,file_inc_dirs)
+ def get_args_defines_and_inc_dirs(self, target, compiler, generated_files_include_dirs, proj_to_src_root, proj_to_src_dir, build_args):
# Arguments, include dirs, defines for all files in the current target
target_args = []
target_defines = []
@@ -1175,9 +1142,7 @@ def gen_vcxproj(self, target: build.BuildTarget, ofname: str, guid: str) -> None
for d in reversed(target.get_external_deps()):
# Cflags required by external deps might have UNIX-specific flags,
# so filter them out if needed
- if d.name == 'openmp':
- ET.SubElement(clconf, 'OpenMPSupport').text = 'true'
- else:
+ if d.name != 'openmp':
d_compile_args = compiler.unix_args_to_native(d.get_compile_args())
for arg in d_compile_args:
if arg.startswith(('-D', '/D')):
@@ -1194,9 +1159,263 @@ def gen_vcxproj(self, target: build.BuildTarget, ofname: str, guid: str) -> None
else:
target_args.append(arg)
- languages += gen_langs
if '/Gw' in build_args:
target_args.append('/Gw')
+
+ return (target_args, file_args), (target_defines, file_defines), (target_inc_dirs, file_inc_dirs)
+
+ @staticmethod
+ def get_build_args(compiler, buildtype: str, optimization_level: str, debug: bool, sanitize: str) -> T.List[str]:
+ build_args = compiler.get_buildtype_args(buildtype)
+ build_args += compiler.get_optimization_args(optimization_level)
+ build_args += compiler.get_debug_args(debug)
+ build_args += compiler.sanitizer_compile_args(sanitize)
+
+ return build_args
+
+ #Convert a list of compile arguments from -
+ # [ '-I..\\some\\dir\\include', '-I../../some/other/dir', '/MDd', '/W2', '/std:c++17', '/Od', '/Zi', '-DSOME_DEF=1', '-DANOTHER_DEF=someval', ...]
+ #to -
+ # 'SOME_DEF=1;ANOTHER_DEF=someval;'
+ #which is the format required by the visual studio project's NMakePreprocessorDefinitions field.
+ @staticmethod
+ def extract_nmake_preprocessor_defs(captured_build_args: list[str]) -> str:
+ defs = ''
+ for arg in captured_build_args:
+ if arg.startswith(('-D', '/D')):
+ defs += arg[2:] + ';'
+ return defs
+
+ #Convert a list of compile arguments from -
+ # [ '-I..\\some\\dir\\include', '-I../../some/other/dir', '/MDd', '/W2', '/std:c++17', '/Od', '/Zi', '-DSOME_DEF=1', '-DANOTHER_DEF=someval', ...]
+ #to -
+ # '..\\some\\dir\\include;../../some/other/dir;'
+ #which is the format required by the visual studio project's NMakePreprocessorDefinitions field.
+ @staticmethod
+ def extract_nmake_include_paths(captured_build_args: list[str]) -> str:
+ paths = ''
+ for arg in captured_build_args:
+ if arg.startswith(('-I', '/I')):
+ paths += arg[2:] + ';'
+ return paths
+
+ #Convert a list of compile arguments from -
+ # [ '-I..\\some\\dir\\include', '-I../../some/other/dir', '/MDd', '/W2', '/std:c++17', '/Od', '/Zi', '-DSOME_DEF=1', '-DANOTHER_DEF=someval', ...]
+ #to -
+ # '/MDd;/W2;/std:c++17;/Od/Zi'
+ #which is the format required by the visual studio project's NMakePreprocessorDefinitions field.
+ @staticmethod
+ def extract_intellisense_additional_compiler_options(captured_build_args: list[str]) -> str:
+ additional_opts = ''
+ for arg in captured_build_args:
+ if (not arg.startswith(('-D', '/D', '-I', '/I'))) and arg.startswith(('-', '/')):
+ additional_opts += arg + ';'
+ return additional_opts
+
+ @staticmethod
+ def get_nmake_base_meson_command_and_exe_search_paths() -> T.Tuple[str, str]:
+ meson_cmd_list = mesonlib.get_meson_command()
+ assert (len(meson_cmd_list) == 1) or (len(meson_cmd_list) == 2)
+ # We expect get_meson_command() to either be of the form -
+ # 1: ['path/to/meson.exe']
+ # or -
+ # 2: ['path/to/python.exe', 'and/path/to/meson.py']
+ # so we'd like to ensure our makefile-style project invokes the same meson executable or python src as this instance.
+ exe_search_paths = os.path.dirname(meson_cmd_list[0])
+ nmake_base_meson_command = os.path.basename(meson_cmd_list[0])
+ if len(meson_cmd_list) != 1:
+ # We expect to be dealing with case '2', shown above.
+ # With Windows, it's also possible that we get a path to the second element of meson_cmd_list that contains spaces
+ # (e.g. 'and/path to/meson.py'). So, because this will end up directly in the makefile/NMake command lines, we'd
+ # better always enclose it in quotes. Only strictly necessary for paths with spaces but no harm for paths without -
+ nmake_base_meson_command += ' \"' + meson_cmd_list[1] + '\"'
+ exe_search_paths += ';' + os.path.dirname(meson_cmd_list[1])
+
+ # Additionally, in some cases, we appear to have to add 'C:\Windows\system32;C:\Windows' to the 'Path' environment (via the
+ # ExecutablePath element), without which, the 'meson compile ...' (NMakeBuildCommandLine) command can fail (failure to find
+ # stdio.h and similar), so something is quietly switching some critical build behaviour based on the presence of these in
+ # the 'Path'.
+ # Not sure if this ultimately comes down to some 'find and guess' hidden behaviours within meson or within MSVC tools, but
+ # I guess some projects may implicitly rely on this behaviour.
+ # Things would be cleaner, more robust, repeatable, and portable if meson (and msvc tools) replaced all this kind of
+ # find/guess behaviour with the requirement that things just be explicitly specified by the user.
+ # An example of this can be seen with -
+ # 1: Download https://github.com/facebook/zstd source
+ # 2: cd to the 'zstd-dev\build\meson' dir
+ # 3: meson setup -Dbin_programs=true -Dbin_contrib=true --genvslite vs2022 builddir_vslite
+ # 4: Open the generated 'builddir_vslite_vs\zstd.sln' and build through a project, which should explicitly add the above to
+ # the project's 'Executable Directories' paths and build successfully.
+ # 5: Remove 'C:\Windows\system32;C:\Windows;' from the same project's 'Executable Directories' paths and rebuild.
+ # This should now fail.
+ # It feels uncomfortable to do this but what better alternative is there (and might this introduce new problems)? -
+ exe_search_paths += ';C:\\Windows\\system32;C:\\Windows'
+ # A meson project that explicitly specifies compiler/linker tools and sdk/include paths is not going to have any problems
+ # with this addition.
+
+ return (nmake_base_meson_command, exe_search_paths)
+
+ def add_gen_lite_makefile_vcxproj_elements(self,
+ root: ET.Element,
+ platform: str,
+ target_ext: str,
+ captured_compile_args_per_buildtype_and_target: dict,
+ target,
+ proj_to_build_root: str,
+ primary_src_lang: T.Optional[str]) -> None:
+ ET.SubElement(root, 'ImportGroup', Label='ExtensionSettings')
+ ET.SubElement(root, 'ImportGroup', Label='Shared')
+ prop_sheets_grp = ET.SubElement(root, 'ImportGroup', Label='PropertySheets')
+ ET.SubElement(prop_sheets_grp, 'Import', {'Project': r'$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props',
+ 'Condition': r"exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')",
+ 'Label': 'LocalAppDataPlatform'
+ })
+ ET.SubElement(root, 'PropertyGroup', Label='UserMacros')
+
+ (nmake_base_meson_command, exe_search_paths) = Vs2010Backend.get_nmake_base_meson_command_and_exe_search_paths()
+
+ # Relative path from this .vcxproj to the directory containing the set of '..._[debug/debugoptimized/release]' setup meson build dirs.
+ proj_to_multiconfigured_builds_parent_dir = os.path.join(proj_to_build_root, '..')
+
+ # Conditional property groups per configuration (buildtype). E.g. -
+ #
+ multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list()
+ for buildtype in multi_config_buildtype_list:
+ per_config_prop_group = ET.SubElement(root, 'PropertyGroup', Condition=f'\'$(Configuration)|$(Platform)\'==\'{buildtype}|{platform}\'')
+ (_, build_dir_tail) = os.path.split(self.src_to_build)
+ meson_build_dir_for_buildtype = build_dir_tail[:-2] + buildtype # Get the buildtype suffixed 'builddir_[debug/release/etc]' from 'builddir_vs', for example.
+ proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype))
+ ET.SubElement(per_config_prop_group, 'OutDir').text = f'{proj_to_build_dir_for_buildtype}\\'
+ ET.SubElement(per_config_prop_group, 'IntDir').text = f'{proj_to_build_dir_for_buildtype}\\'
+ ET.SubElement(per_config_prop_group, 'NMakeBuildCommandLine').text = f'{nmake_base_meson_command} compile -C "{proj_to_build_dir_for_buildtype}"'
+ ET.SubElement(per_config_prop_group, 'NMakeOutput').text = f'$(OutDir){target.name}{target_ext}'
+ captured_build_args = captured_compile_args_per_buildtype_and_target[buildtype][target.get_id()]
+ # 'captured_build_args' is a dictionary, mapping from each src file type to a list of compile args to use for that type.
+ # Usually, there's just one but we could have multiple src types. However, since there's only one field for the makefile
+ # project's NMake... preprocessor/include intellisense fields, we'll just use the first src type we have to fill in
+ # these fields. Then, any src files in this VS project that aren't of this first src type will then need to override
+ # its intellisense fields instead of simply referencing the values in the project.
+ ET.SubElement(per_config_prop_group, 'NMakeReBuildCommandLine').text = f'{nmake_base_meson_command} compile -C "{proj_to_build_dir_for_buildtype}" --clean && {nmake_base_meson_command} compile -C "{proj_to_build_dir_for_buildtype}"'
+ ET.SubElement(per_config_prop_group, 'NMakeCleanCommandLine').text = f'{nmake_base_meson_command} compile -C "{proj_to_build_dir_for_buildtype}" --clean'
+ # Need to set the 'ExecutablePath' element for the above NMake... commands to be able to invoke the meson command.
+ ET.SubElement(per_config_prop_group, 'ExecutablePath').text = exe_search_paths
+ # We may not have any src files and so won't have a primary src language. In which case, we've nothing to fill in for this target's intellisense fields -
+ if primary_src_lang:
+ primary_src_type_build_args = captured_build_args[primary_src_lang]
+ ET.SubElement(per_config_prop_group, 'NMakePreprocessorDefinitions').text = Vs2010Backend.extract_nmake_preprocessor_defs(primary_src_type_build_args)
+ ET.SubElement(per_config_prop_group, 'NMakeIncludeSearchPath').text = Vs2010Backend.extract_nmake_include_paths(primary_src_type_build_args)
+ ET.SubElement(per_config_prop_group, 'AdditionalOptions').text = Vs2010Backend.extract_intellisense_additional_compiler_options(primary_src_type_build_args)
+
+ # Unless we explicitly specify the following empty path elements, the project is assigned a load of nasty defaults that fill these
+ # with values like -
+ # $(VC_IncludePath);$(WindowsSDK_IncludePath);
+ # which are all based on the current install environment (a recipe for non-reproducibility problems), not the paths that will be used by
+ # the actual meson compile jobs. Although these elements look like they're only for MSBuild operations, they're not needed with our simple,
+ # lite/makefile-style projects so let's just remove them in case they do get used/confused by intellisense.
+ ET.SubElement(per_config_prop_group, 'IncludePath')
+ ET.SubElement(per_config_prop_group, 'ExternalIncludePath')
+ ET.SubElement(per_config_prop_group, 'ReferencePath')
+ ET.SubElement(per_config_prop_group, 'LibraryPath')
+ ET.SubElement(per_config_prop_group, 'LibraryWPath')
+ ET.SubElement(per_config_prop_group, 'SourcePath')
+ ET.SubElement(per_config_prop_group, 'ExcludePath')
+
+ def add_non_makefile_vcxproj_elements(
+ self,
+ root: ET.Element,
+ type_config: ET.Element,
+ target,
+ platform: str,
+ subsystem,
+ build_args,
+ target_args,
+ target_defines,
+ target_inc_dirs,
+ file_args
+ ) -> None:
+ compiler = self._get_cl_compiler(target)
+ buildtype_link_args = compiler.get_buildtype_linker_args(self.buildtype)
+
+ # Prefix to use to access the build root from the vcxproj dir
+ down = self.target_to_build_root(target)
+
+ # FIXME: Should the following just be set in create_basic_project(), even if
+ # irrelevant for current target?
+
+ # FIXME: Meson's LTO support needs to be integrated here
+ ET.SubElement(type_config, 'WholeProgramOptimization').text = 'false'
+ # Let VS auto-set the RTC level
+ ET.SubElement(type_config, 'BasicRuntimeChecks').text = 'Default'
+ # Incremental linking increases code size
+ if '/INCREMENTAL:NO' in buildtype_link_args:
+ ET.SubElement(type_config, 'LinkIncremental').text = 'false'
+
+ # Build information
+ compiles = ET.SubElement(root, 'ItemDefinitionGroup')
+ clconf = ET.SubElement(compiles, 'ClCompile')
+ if True in ((dep.name == 'openmp') for dep in target.get_external_deps()):
+ ET.SubElement(clconf, 'OpenMPSupport').text = 'true'
+ # CRT type; debug or release
+ vscrt_type = self.environment.coredata.options[OptionKey('b_vscrt')]
+ if vscrt_type.value == 'from_buildtype':
+ if self.buildtype == 'debug':
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL'
+ else:
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDLL'
+ elif vscrt_type.value == 'static_from_buildtype':
+ if self.buildtype == 'debug':
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebug'
+ else:
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreaded'
+ elif vscrt_type.value == 'mdd':
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL'
+ elif vscrt_type.value == 'mt':
+ # FIXME, wrong
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreaded'
+ elif vscrt_type.value == 'mtd':
+ # FIXME, wrong
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebug'
+ else:
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDLL'
+ # Sanitizers
+ if '/fsanitize=address' in build_args:
+ ET.SubElement(type_config, 'EnableASAN').text = 'true'
+ # Debug format
+ if '/ZI' in build_args:
+ ET.SubElement(clconf, 'DebugInformationFormat').text = 'EditAndContinue'
+ elif '/Zi' in build_args:
+ ET.SubElement(clconf, 'DebugInformationFormat').text = 'ProgramDatabase'
+ elif '/Z7' in build_args:
+ ET.SubElement(clconf, 'DebugInformationFormat').text = 'OldStyle'
+ else:
+ ET.SubElement(clconf, 'DebugInformationFormat').text = 'None'
+ # Runtime checks
+ if '/RTC1' in build_args:
+ ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'EnableFastChecks'
+ elif '/RTCu' in build_args:
+ ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'UninitializedLocalUsageCheck'
+ elif '/RTCs' in build_args:
+ ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'StackFrameRuntimeCheck'
+ # Exception handling has to be set in the xml in addition to the "AdditionalOptions" because otherwise
+ # cl will give warning D9025: overriding '/Ehs' with cpp_eh value
+ if 'cpp' in target.compilers:
+ eh = self.environment.coredata.options[OptionKey('eh', machine=target.for_machine, lang='cpp')]
+ if eh.value == 'a':
+ ET.SubElement(clconf, 'ExceptionHandling').text = 'Async'
+ elif eh.value == 's':
+ ET.SubElement(clconf, 'ExceptionHandling').text = 'SyncCThrow'
+ elif eh.value == 'none':
+ ET.SubElement(clconf, 'ExceptionHandling').text = 'false'
+ else: # 'sc' or 'default'
+ ET.SubElement(clconf, 'ExceptionHandling').text = 'Sync'
+
if len(target_args) > 0:
target_args.append('%(AdditionalOptions)')
ET.SubElement(clconf, "AdditionalOptions").text = ' '.join(target_args)
@@ -1233,25 +1452,6 @@ def gen_vcxproj(self, target: build.BuildTarget, ofname: str, guid: str) -> None
ET.SubElement(clconf, 'FavorSizeOrSpeed').text = 'Speed'
# Note: SuppressStartupBanner is /NOLOGO and is 'true' by default
self.generate_lang_standard_info(file_args, clconf)
- pch_sources = {}
- if self.environment.coredata.options.get(OptionKey('b_pch')):
- for lang in ['c', 'cpp']:
- pch = target.get_pch(lang)
- if not pch:
- continue
- if compiler.id == 'msvc':
- if len(pch) == 1:
- # Auto generate PCH.
- src = os.path.join(down, self.create_msvc_pch_implementation(target, lang, pch[0]))
- pch_header_dir = os.path.dirname(os.path.join(proj_to_src_dir, pch[0]))
- else:
- src = os.path.join(proj_to_src_dir, pch[1])
- pch_header_dir = None
- pch_sources[lang] = [pch[0], src, lang, pch_header_dir]
- else:
- # I don't know whether its relevant but let's handle other compilers
- # used with a vs backend
- pch_sources[lang] = [pch[0], None, lang, None]
resourcecompile = ET.SubElement(compiles, 'ResourceCompile')
ET.SubElement(resourcecompile, 'PreprocessorDefinitions')
@@ -1359,12 +1559,6 @@ def gen_vcxproj(self, target: build.BuildTarget, ofname: str, guid: str) -> None
additional_links.append(linkname)
for lib in self.get_custom_target_provided_libraries(target):
additional_links.append(self.relpath(lib, self.get_target_dir(target)))
- additional_objects = []
- for o in self.flatten_object_list(target, down)[0]:
- assert isinstance(o, str)
- additional_objects.append(o)
- for o in custom_objs:
- additional_objects.append(o)
if len(extra_link_args) > 0:
extra_link_args.append('%(AdditionalOptions)')
@@ -1390,7 +1584,7 @@ def gen_vcxproj(self, target: build.BuildTarget, ofname: str, guid: str) -> None
ET.SubElement(link, 'ModuleDefinitionFile').text = relpath
if self.debug:
pdb = ET.SubElement(link, 'ProgramDataBaseFileName')
- pdb.text = f'$(OutDir){target_name}.pdb'
+ pdb.text = f'$(OutDir){target.name}.pdb'
targetmachine = ET.SubElement(link, 'TargetMachine')
if target.for_machine is MachineChoice.BUILD:
targetplatform = platform.lower()
@@ -1414,6 +1608,116 @@ def gen_vcxproj(self, target: build.BuildTarget, ofname: str, guid: str) -> None
if not self.environment.coredata.get_option(OptionKey('debug')):
ET.SubElement(link, 'SetChecksum').text = 'true'
+ # Visual studio doesn't simply allow the src files of a project to be added with the 'Condition=...' attribute,
+ # to allow us to point to the different debug/debugoptimized/release sets of generated src files for each of
+ # the solution's configurations. Similarly, 'ItemGroup' also doesn't support 'Condition'. So, without knowing
+ # a better (simple) alternative, for now, we'll repoint these generated sources (which will be incorrectly
+ # pointing to non-existent files under our '[builddir]_vs' directory) to the appropriate location under one of
+ # our buildtype build directores (e.g. '[builddir]_debug').
+ # This will at least allow the user to open the files of generated sources listed in the solution explorer,
+ # once a build/compile has generated these sources.
+ #
+ # This modifies the paths in 'gen_files' in place, as opposed to returning a new list of modified paths.
+ def relocate_generated_file_paths_to_concrete_build_dir(self, gen_files: T.List[str], target: T.Union[build.Target, build.CustomTargetIndex]) -> None:
+ (_, build_dir_tail) = os.path.split(self.src_to_build)
+ meson_build_dir_for_buildtype = build_dir_tail[:-2] + coredata.get_genvs_default_buildtype_list()[0] # Get the first buildtype suffixed dir (i.e. '[builddir]_debug') from '[builddir]_vs'
+ # Relative path from this .vcxproj to the directory containing the set of '..._[debug/debugoptimized/release]' setup meson build dirs.
+ proj_to_build_root = self.target_to_build_root(target)
+ proj_to_multiconfigured_builds_parent_dir = os.path.join(proj_to_build_root, '..')
+ proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype))
+ relocate_to_concrete_builddir_target = os.path.normpath(os.path.join(proj_to_build_dir_for_buildtype, self.get_target_dir(target)))
+ for idx, file_path in enumerate(gen_files):
+ gen_files[idx] = os.path.normpath(os.path.join(relocate_to_concrete_builddir_target, file_path))
+
+ # Returns bool indicating whether the .vcxproj has been generated.
+ # Under some circumstances, it's unnecessary to create some .vcxprojs, so, when generating the .sln,
+ # we need to respect that not all targets will have generated a project.
+ def gen_vcxproj(self, target: build.BuildTarget, ofname: str, guid: str, captured_compile_args_per_buildtype_and_target: dict = None) -> bool:
+ mlog.debug(f'Generating vcxproj {target.name}.')
+ subsystem = 'Windows'
+ self.handled_target_deps[target.get_id()] = []
+
+ if self.gen_lite:
+ if not isinstance(target, build.BuildTarget):
+ # Since we're going to delegate all building to the one true meson build command, we don't need
+ # to generate .vcxprojs for targets that don't add any source files or just perform custom build
+ # commands. These are targets of types CustomTarget or RunTarget. So let's just skip generating
+ # these otherwise insubstantial non-BuildTarget targets.
+ return False
+ conftype = 'Makefile'
+ elif isinstance(target, build.Executable):
+ conftype = 'Application'
+ if target.gui_app is not None:
+ if not target.gui_app:
+ subsystem = 'Console'
+ else:
+ # If someone knows how to set the version properly,
+ # please send a patch.
+ subsystem = target.win_subsystem.split(',')[0]
+ elif isinstance(target, build.StaticLibrary):
+ conftype = 'StaticLibrary'
+ elif isinstance(target, build.SharedLibrary):
+ conftype = 'DynamicLibrary'
+ elif isinstance(target, build.CustomTarget):
+ self.gen_custom_target_vcxproj(target, ofname, guid)
+ return True
+ elif isinstance(target, build.RunTarget):
+ self.gen_run_target_vcxproj(target, ofname, guid)
+ return True
+ elif isinstance(target, build.CompileTarget):
+ self.gen_compile_target_vcxproj(target, ofname, guid)
+ return True
+ else:
+ raise MesonException(f'Unknown target type for {target.get_basename()}')
+
+ (sources, headers, objects, _languages) = self.split_sources(target.sources)
+ if target.is_unity:
+ sources = self.generate_unity_files(target, sources)
+ if target.for_machine is MachineChoice.BUILD:
+ platform = self.build_platform
+ else:
+ platform = self.platform
+
+ tfilename = os.path.splitext(target.get_filename())
+
+ (root, type_config) = self.create_basic_project(tfilename[0],
+ temp_dir=target.get_id(),
+ guid=guid,
+ conftype=conftype,
+ target_ext=tfilename[1],
+ target_platform=platform)
+
+ # vcxproj.filters file
+ root_filter = self.create_basic_project_filters()
+
+ generated_files, custom_target_output_files, generated_files_include_dirs = self.generate_custom_generator_commands(
+ target, root)
+ (gen_src, gen_hdrs, gen_objs, _gen_langs) = self.split_sources(generated_files)
+ (custom_src, custom_hdrs, custom_objs, _custom_langs) = self.split_sources(custom_target_output_files)
+ gen_src += custom_src
+ gen_hdrs += custom_hdrs
+
+ compiler = self._get_cl_compiler(target)
+ build_args = Vs2010Backend.get_build_args(compiler, self.buildtype, self.optimization, self.debug, self.sanitize)
+
+ assert isinstance(target, (build.Executable, build.SharedLibrary, build.StaticLibrary, build.SharedModule)), 'for mypy'
+ # Prefix to use to access the build root from the vcxproj dir
+ proj_to_build_root = self.target_to_build_root(target)
+ # Prefix to use to access the source tree's root from the vcxproj dir
+ proj_to_src_root = os.path.join(proj_to_build_root, self.build_to_src)
+ # Prefix to use to access the source tree's subdir from the vcxproj dir
+ proj_to_src_dir = os.path.join(proj_to_src_root, self.get_target_dir(target))
+
+ (target_args, file_args), (target_defines, file_defines), (target_inc_dirs, file_inc_dirs) = self.get_args_defines_and_inc_dirs(
+ target, compiler, generated_files_include_dirs, proj_to_src_root, proj_to_src_dir, build_args)
+
+ if self.gen_lite:
+ assert captured_compile_args_per_buildtype_and_target is not None
+ primary_src_lang = get_primary_source_lang(target.sources, custom_src)
+ self.add_gen_lite_makefile_vcxproj_elements(root, platform, tfilename[1], captured_compile_args_per_buildtype_and_target, target, proj_to_build_root, primary_src_lang)
+ else:
+ self.add_non_makefile_vcxproj_elements(root, type_config, target, platform, subsystem, build_args, target_args, target_defines, target_inc_dirs, file_args)
+
meson_file_group = ET.SubElement(root, 'ItemGroup')
ET.SubElement(meson_file_group, 'None', Include=os.path.join(proj_to_src_dir, build_filename))
@@ -1427,16 +1731,41 @@ def path_normalize_add(path, lis):
else:
return False
+ pch_sources = {}
+ if self.environment.coredata.options.get(OptionKey('b_pch')):
+ for lang in ['c', 'cpp']:
+ pch = target.get_pch(lang)
+ if not pch:
+ continue
+ if compiler.id == 'msvc':
+ if len(pch) == 1:
+ # Auto generate PCH.
+ src = os.path.join(proj_to_build_root, self.create_msvc_pch_implementation(target, lang, pch[0]))
+ pch_header_dir = os.path.dirname(os.path.join(proj_to_src_dir, pch[0]))
+ else:
+ src = os.path.join(proj_to_src_dir, pch[1])
+ pch_header_dir = None
+ pch_sources[lang] = [pch[0], src, lang, pch_header_dir]
+ else:
+ # I don't know whether its relevant but let's handle other compilers
+ # used with a vs backend
+ pch_sources[lang] = [pch[0], None, lang, None]
+
list_filters_path = set()
previous_includes = []
if len(headers) + len(gen_hdrs) + len(target.extra_files) + len(pch_sources) > 0:
+ if self.gen_lite and gen_hdrs:
+ # Although we're constructing our .vcxproj under our '..._vs' directory, we want to reference generated files
+ # in our concrete build directories (e.g. '..._debug'), where generated files will exist after building.
+ self.relocate_generated_file_paths_to_concrete_build_dir(gen_hdrs, target)
+
# Filter information
filter_group_include = ET.SubElement(root_filter, 'ItemGroup')
inc_hdrs = ET.SubElement(root, 'ItemGroup')
for h in headers:
- relpath = os.path.join(down, h.rel_to_builddir(self.build_to_src))
+ relpath = os.path.join(proj_to_build_root, h.rel_to_builddir(self.build_to_src))
if path_normalize_add(relpath, previous_includes):
self.add_filter_info(list_filters_path, filter_group_include, 'ClInclude', relpath, h.subdir)
ET.SubElement(inc_hdrs, 'CLInclude', Include=relpath)
@@ -1445,7 +1774,7 @@ def path_normalize_add(path, lis):
self.add_filter_info(list_filters_path, filter_group_include, 'ClInclude', h)
ET.SubElement(inc_hdrs, 'CLInclude', Include=h)
for h in target.extra_files:
- relpath = os.path.join(down, h.rel_to_builddir(self.build_to_src))
+ relpath = os.path.join(proj_to_build_root, h.rel_to_builddir(self.build_to_src))
if path_normalize_add(relpath, previous_includes):
self.add_filter_info(list_filters_path, filter_group_include, 'ClInclude', relpath, h.subdir)
ET.SubElement(inc_hdrs, 'CLInclude', Include=relpath)
@@ -1457,50 +1786,70 @@ def path_normalize_add(path, lis):
previous_sources = []
if len(sources) + len(gen_src) + len(pch_sources) > 0:
+ if self.gen_lite:
+ # Get data to fill in intellisense fields for sources that can't reference the project-wide values
+ defs_paths_opts_per_lang_and_buildtype = get_non_primary_lang_intellisense_fields(
+ captured_compile_args_per_buildtype_and_target,
+ target.get_id(),
+ primary_src_lang)
+ if gen_src:
+ # Although we're constructing our .vcxproj under our '..._vs' directory, we want to reference generated files
+ # in our concrete build directories (e.g. '..._debug'), where generated files will exist after building.
+ self.relocate_generated_file_paths_to_concrete_build_dir(gen_src, target)
+
# Filter information
filter_group_compile = ET.SubElement(root_filter, 'ItemGroup')
inc_src = ET.SubElement(root, 'ItemGroup')
for s in sources:
- relpath = os.path.join(down, s.rel_to_builddir(self.build_to_src))
+ relpath = os.path.join(proj_to_build_root, s.rel_to_builddir(self.build_to_src))
if path_normalize_add(relpath, previous_sources):
self.add_filter_info(list_filters_path, filter_group_compile, 'CLCompile', relpath, s.subdir)
inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=relpath)
- lang = Vs2010Backend.lang_from_source_file(s)
- self.add_pch(pch_sources, lang, inc_cl)
- self.add_additional_options(lang, inc_cl, file_args)
- self.add_preprocessor_defines(lang, inc_cl, file_defines)
- self.add_include_dirs(lang, inc_cl, file_inc_dirs)
- ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + \
- self.object_filename_from_source(target, s)
+ if self.gen_lite:
+ self.add_project_nmake_defs_incs_and_opts(inc_cl, relpath, defs_paths_opts_per_lang_and_buildtype, platform)
+ else:
+ lang = Vs2010Backend.lang_from_source_file(s)
+ self.add_pch(pch_sources, lang, inc_cl)
+ self.add_additional_options(lang, inc_cl, file_args)
+ self.add_preprocessor_defines(lang, inc_cl, file_defines)
+ self.add_include_dirs(lang, inc_cl, file_inc_dirs)
+ ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + \
+ self.object_filename_from_source(target, s)
for s in gen_src:
if path_normalize_add(s, previous_sources):
self.add_filter_info(list_filters_path, filter_group_compile, 'CLCompile', s)
inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=s)
- lang = Vs2010Backend.lang_from_source_file(s)
- self.add_pch(pch_sources, lang, inc_cl)
- self.add_additional_options(lang, inc_cl, file_args)
- self.add_preprocessor_defines(lang, inc_cl, file_defines)
- self.add_include_dirs(lang, inc_cl, file_inc_dirs)
- s = File.from_built_file(target.get_subdir(), s)
- ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + \
- self.object_filename_from_source(target, s)
+ if self.gen_lite:
+ self.add_project_nmake_defs_incs_and_opts(inc_cl, s, defs_paths_opts_per_lang_and_buildtype, platform)
+ else:
+ lang = Vs2010Backend.lang_from_source_file(s)
+ self.add_pch(pch_sources, lang, inc_cl)
+ self.add_additional_options(lang, inc_cl, file_args)
+ self.add_preprocessor_defines(lang, inc_cl, file_defines)
+ self.add_include_dirs(lang, inc_cl, file_inc_dirs)
+ s = File.from_built_file(target.get_subdir(), s)
+ ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + \
+ self.object_filename_from_source(target, s)
for lang, headers in pch_sources.items():
impl = headers[1]
if impl and path_normalize_add(impl, previous_sources):
self.add_filter_info(list_filters_path, filter_group_compile, 'CLCompile', impl, 'pch')
inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=impl)
self.create_pch(pch_sources, lang, inc_cl)
- self.add_additional_options(lang, inc_cl, file_args)
- self.add_preprocessor_defines(lang, inc_cl, file_defines)
- pch_header_dir = pch_sources[lang][3]
- if pch_header_dir:
- inc_dirs = copy.deepcopy(file_inc_dirs)
- inc_dirs[lang] = [pch_header_dir] + inc_dirs[lang]
+ if self.gen_lite:
+ self.add_project_nmake_defs_incs_and_opts(inc_cl, impl, defs_paths_opts_per_lang_and_buildtype, platform)
else:
- inc_dirs = file_inc_dirs
- self.add_include_dirs(lang, inc_cl, inc_dirs)
- # XXX: Do we need to set the object file name here too?
+ self.add_additional_options(lang, inc_cl, file_args)
+ self.add_preprocessor_defines(lang, inc_cl, file_defines)
+ pch_header_dir = pch_sources[lang][3]
+ if pch_header_dir:
+ inc_dirs = copy.deepcopy(file_inc_dirs)
+ inc_dirs[lang] = [pch_header_dir] + inc_dirs[lang]
+ else:
+ inc_dirs = file_inc_dirs
+ self.add_include_dirs(lang, inc_cl, inc_dirs)
+ # XXX: Do we need to set the object file name here too?
# Filter information
filter_group = ET.SubElement(root_filter, 'ItemGroup')
@@ -1508,11 +1857,18 @@ def path_normalize_add(path, lis):
filter = ET.SubElement(filter_group, 'Filter', Include=filter_dir)
ET.SubElement(filter, 'UniqueIdentifier').text = '{' + str(uuid.uuid4()) + '}'
+ additional_objects = []
+ for o in self.flatten_object_list(target, proj_to_build_root)[0]:
+ assert isinstance(o, str)
+ additional_objects.append(o)
+ for o in custom_objs:
+ additional_objects.append(o)
+
previous_objects = []
if self.has_objects(objects, additional_objects, gen_objs):
inc_objs = ET.SubElement(root, 'ItemGroup')
for s in objects:
- relpath = os.path.join(down, s.rel_to_builddir(self.build_to_src))
+ relpath = os.path.join(proj_to_build_root, s.rel_to_builddir(self.build_to_src))
if path_normalize_add(relpath, previous_objects):
ET.SubElement(inc_objs, 'Object', Include=relpath)
for s in additional_objects:
@@ -1522,80 +1878,191 @@ def path_normalize_add(path, lis):
ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
self.add_regen_dependency(root)
- self.add_target_deps(root, target)
+ if not self.gen_lite:
+ # Injecting further target dependencies into this vcxproj implies and forces a Visual Studio BUILD dependency,
+ # which we don't want when using 'genvslite'. A gen_lite build as little involvement with the visual studio's
+ # build system as possible.
+ self.add_target_deps(root, target)
self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
self._prettyprint_vcxproj_xml(ET.ElementTree(root_filter), ofname + '.filters')
+ return True
+
+ def gen_regenproj(self):
+ # To fully adapt the REGEN work for a 'genvslite' solution, to check timestamps, settings, and regenerate the
+ # '[builddir]_vs' solution/vcxprojs, as well as regenerating the accompanying buildtype-suffixed ninja build
+ # directories (from which we need to first collect correct, updated preprocessor defs and compiler options in
+ # order to fill in the regenerated solution's intellisense settings) would require some non-trivial intrusion
+ # into the 'meson --internal regencheck ./meson-private' execution path (and perhaps also the '--internal
+ # regenerate' and even 'meson setup --reconfigure' code). So, for now, we'll instead give the user a simpler
+ # 'reconfigure' utility project that just runs 'meson setup --reconfigure [builddir]_[buildtype] [srcdir]' on
+ # each of the ninja build dirs.
+ #
+ # FIXME: That will keep the building and compiling correctly configured but obviously won't update the
+ # solution and vcxprojs, which may allow solution src files and intellisense options to go out-of-date; the
+ # user would still have to manually 'meson setup --genvslite [vsxxxx] [builddir] [srcdir]' to fully regenerate
+ # a complete and correct solution.
+ if self.gen_lite:
+ project_name = 'RECONFIGURE'
+ ofname = os.path.join(self.environment.get_build_dir(), 'RECONFIGURE.vcxproj')
+ conftype = 'Makefile'
+ # I find the REGEN project doesn't work; it fails to invoke the appropriate -
+ # python meson.py --internal regencheck builddir\meson-private
+ # command, despite the fact that manually running such a command in a shell runs just fine.
+ # Running/building the regen project produces the error -
+ # ...Microsoft.CppBuild.targets(460,5): error MSB8020: The build tools for ClangCL (Platform Toolset = 'ClangCL') cannot be found. To build using the ClangCL build tools, please install ...
+ # Not sure why but a simple makefile-style project that executes the full '...regencheck...' command actually works (and seems a little simpler).
+ # Although I've limited this change to only happen under '--genvslite', perhaps ...
+ # FIXME : Should all utility projects use the simpler and less problematic makefile-style project?
+ else:
+ project_name = 'REGEN'
+ ofname = os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj')
+ conftype = 'Utility'
- def gen_regenproj(self, project_name, ofname):
guid = self.environment.coredata.regen_guid
(root, type_config) = self.create_basic_project(project_name,
temp_dir='regen-temp',
- guid=guid)
-
- action = ET.SubElement(root, 'ItemDefinitionGroup')
- midl = ET.SubElement(action, 'Midl')
- ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
- ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
- ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
- ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
- ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
- ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
- regen_command = self.environment.get_build_command() + ['--internal', 'regencheck']
- cmd_templ = '''call %s > NUL
+ guid=guid,
+ conftype=conftype
+ )
+
+ if self.gen_lite:
+ (nmake_base_meson_command, exe_search_paths) = Vs2010Backend.get_nmake_base_meson_command_and_exe_search_paths()
+ all_configs_prop_group = ET.SubElement(root, 'PropertyGroup')
+
+ # Multi-line command to reconfigure all buildtype-suffixed build dirs
+ multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list()
+ (_, build_dir_tail) = os.path.split(self.src_to_build)
+ proj_to_multiconfigured_builds_parent_dir = '..' # We know this RECONFIGURE.vcxproj will always be in the '[buildir]_vs' dir.
+ proj_to_src_dir = self.build_to_src
+ reconfigure_all_cmd = ''
+ for buildtype in multi_config_buildtype_list:
+ meson_build_dir_for_buildtype = build_dir_tail[:-2] + buildtype # Get the buildtype suffixed 'builddir_[debug/release/etc]' from 'builddir_vs', for example.
+ proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype))
+ reconfigure_all_cmd += f'{nmake_base_meson_command} setup --reconfigure "{proj_to_build_dir_for_buildtype}" "{proj_to_src_dir}"\n'
+ ET.SubElement(all_configs_prop_group, 'NMakeBuildCommandLine').text = reconfigure_all_cmd
+ ET.SubElement(all_configs_prop_group, 'NMakeReBuildCommandLine').text = reconfigure_all_cmd
+ ET.SubElement(all_configs_prop_group, 'NMakeCleanCommandLine').text = ''
+
+ #Need to set the 'ExecutablePath' element for the above NMake... commands to be able to execute
+ ET.SubElement(all_configs_prop_group, 'ExecutablePath').text = exe_search_paths
+ else:
+ action = ET.SubElement(root, 'ItemDefinitionGroup')
+ midl = ET.SubElement(action, 'Midl')
+ ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
+ ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
+ ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
+ ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
+ ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
+ ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
+ regen_command = self.environment.get_build_command() + ['--internal', 'regencheck']
+ cmd_templ = '''call %s > NUL
"%s" "%s"'''
- regen_command = cmd_templ % \
- (self.get_vcvars_command(), '" "'.join(regen_command), self.environment.get_scratch_dir())
- self.add_custom_build(root, 'regen', regen_command, deps=self.get_regen_filelist(),
- outputs=[Vs2010Backend.get_regen_stampfile(self.environment.get_build_dir())],
- msg='Checking whether solution needs to be regenerated.')
+ regen_command = cmd_templ % \
+ (self.get_vcvars_command(), '" "'.join(regen_command), self.environment.get_scratch_dir())
+ self.add_custom_build(root, 'regen', regen_command, deps=self.get_regen_filelist(),
+ outputs=[Vs2010Backend.get_regen_stampfile(self.environment.get_build_dir())],
+ msg='Checking whether solution needs to be regenerated.')
+
ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
ET.SubElement(root, 'ImportGroup', Label='ExtensionTargets')
self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
- def gen_testproj(self, target_name, ofname):
+ def gen_testproj(self):
+ project_name = 'RUN_TESTS'
+ ofname = os.path.join(self.environment.get_build_dir(), f'{project_name}.vcxproj')
guid = self.environment.coredata.test_guid
- (root, type_config) = self.create_basic_project(target_name,
- temp_dir='test-temp',
- guid=guid)
+ if self.gen_lite:
+ (root, type_config) = self.create_basic_project(project_name,
+ temp_dir='install-temp',
+ guid=guid,
+ conftype='Makefile'
+ )
+ (nmake_base_meson_command, exe_search_paths) = Vs2010Backend.get_nmake_base_meson_command_and_exe_search_paths()
+ multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list()
+ (_, build_dir_tail) = os.path.split(self.src_to_build)
+ proj_to_multiconfigured_builds_parent_dir = '..' # We know this .vcxproj will always be in the '[buildir]_vs' dir.
+ # Add appropriate 'test' commands for the 'build' action of this project, for all buildtypes
+ for buildtype in multi_config_buildtype_list:
+ meson_build_dir_for_buildtype = build_dir_tail[:-2] + buildtype # Get the buildtype suffixed 'builddir_[debug/release/etc]' from 'builddir_vs', for example.
+ proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype))
+ test_cmd = f'{nmake_base_meson_command} test -C "{proj_to_build_dir_for_buildtype}" --no-rebuild'
+ if not self.environment.coredata.get_option(OptionKey('stdsplit')):
+ test_cmd += ' --no-stdsplit'
+ if self.environment.coredata.get_option(OptionKey('errorlogs')):
+ test_cmd += ' --print-errorlogs'
+ condition = f'\'$(Configuration)|$(Platform)\'==\'{buildtype}|{self.platform}\''
+ prop_group = ET.SubElement(root, 'PropertyGroup', Condition=condition)
+ ET.SubElement(prop_group, 'NMakeBuildCommandLine').text = test_cmd
+ #Need to set the 'ExecutablePath' element for the NMake... commands to be able to execute
+ ET.SubElement(prop_group, 'ExecutablePath').text = exe_search_paths
+ else:
+ (root, type_config) = self.create_basic_project(project_name,
+ temp_dir='test-temp',
+ guid=guid)
+
+ action = ET.SubElement(root, 'ItemDefinitionGroup')
+ midl = ET.SubElement(action, 'Midl')
+ ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
+ ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
+ ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
+ ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
+ ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
+ ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
+ # FIXME: No benchmarks?
+ test_command = self.environment.get_build_command() + ['test', '--no-rebuild']
+ if not self.environment.coredata.get_option(OptionKey('stdsplit')):
+ test_command += ['--no-stdsplit']
+ if self.environment.coredata.get_option(OptionKey('errorlogs')):
+ test_command += ['--print-errorlogs']
+ self.serialize_tests()
+ self.add_custom_build(root, 'run_tests', '"%s"' % ('" "'.join(test_command)))
- action = ET.SubElement(root, 'ItemDefinitionGroup')
- midl = ET.SubElement(action, 'Midl')
- ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
- ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
- ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
- ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
- ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
- ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
- # FIXME: No benchmarks?
- test_command = self.environment.get_build_command() + ['test', '--no-rebuild']
- if not self.environment.coredata.get_option(OptionKey('stdsplit')):
- test_command += ['--no-stdsplit']
- if self.environment.coredata.get_option(OptionKey('errorlogs')):
- test_command += ['--print-errorlogs']
- self.serialize_tests()
- self.add_custom_build(root, 'run_tests', '"%s"' % ('" "'.join(test_command)))
ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
self.add_regen_dependency(root)
self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
- def gen_installproj(self, target_name, ofname):
- self.create_install_data_files()
-
+ def gen_installproj(self):
+ project_name = 'RUN_INSTALL'
+ ofname = os.path.join(self.environment.get_build_dir(), f'{project_name}.vcxproj')
guid = self.environment.coredata.install_guid
- (root, type_config) = self.create_basic_project(target_name,
- temp_dir='install-temp',
- guid=guid)
+ if self.gen_lite:
+ (root, type_config) = self.create_basic_project(project_name,
+ temp_dir='install-temp',
+ guid=guid,
+ conftype='Makefile'
+ )
+ (nmake_base_meson_command, exe_search_paths) = Vs2010Backend.get_nmake_base_meson_command_and_exe_search_paths()
+ multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list()
+ (_, build_dir_tail) = os.path.split(self.src_to_build)
+ proj_to_multiconfigured_builds_parent_dir = '..' # We know this .vcxproj will always be in the '[buildir]_vs' dir.
+ # Add appropriate 'install' commands for the 'build' action of this project, for all buildtypes
+ for buildtype in multi_config_buildtype_list:
+ meson_build_dir_for_buildtype = build_dir_tail[:-2] + buildtype # Get the buildtype suffixed 'builddir_[debug/release/etc]' from 'builddir_vs', for example.
+ proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype))
+ install_cmd = f'{nmake_base_meson_command} install -C "{proj_to_build_dir_for_buildtype}" --no-rebuild'
+ condition = f'\'$(Configuration)|$(Platform)\'==\'{buildtype}|{self.platform}\''
+ prop_group = ET.SubElement(root, 'PropertyGroup', Condition=condition)
+ ET.SubElement(prop_group, 'NMakeBuildCommandLine').text = install_cmd
+ #Need to set the 'ExecutablePath' element for the NMake... commands to be able to execute
+ ET.SubElement(prop_group, 'ExecutablePath').text = exe_search_paths
+ else:
+ self.create_install_data_files()
+
+ (root, type_config) = self.create_basic_project(project_name,
+ temp_dir='install-temp',
+ guid=guid)
+
+ action = ET.SubElement(root, 'ItemDefinitionGroup')
+ midl = ET.SubElement(action, 'Midl')
+ ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
+ ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
+ ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
+ ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
+ ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
+ ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
+ install_command = self.environment.get_build_command() + ['install', '--no-rebuild']
+ self.add_custom_build(root, 'run_install', '"%s"' % ('" "'.join(install_command)))
- action = ET.SubElement(root, 'ItemDefinitionGroup')
- midl = ET.SubElement(action, 'Midl')
- ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
- ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
- ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
- ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
- ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
- ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
- install_command = self.environment.get_build_command() + ['install', '--no-rebuild']
- self.add_custom_build(root, 'run_install', '"%s"' % ('" "'.join(install_command)))
ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
self.add_regen_dependency(root)
self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
@@ -1643,8 +2110,11 @@ def generate_debug_information(self, link: ET.Element) -> None:
ET.SubElement(link, 'GenerateDebugInformation').text = 'true'
def add_regen_dependency(self, root: ET.Element) -> None:
- regen_vcxproj = os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj')
- self.add_project_reference(root, regen_vcxproj, self.environment.coredata.regen_guid)
+ # For now, with 'genvslite' solutions, REGEN is replaced by the lighter-weight RECONFIGURE utility that is
+ # no longer a forced build dependency. See comment in 'gen_regenproj'
+ if not self.gen_lite:
+ regen_vcxproj = os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj')
+ self.add_project_reference(root, regen_vcxproj, self.environment.coredata.regen_guid)
def generate_lang_standard_info(self, file_args: T.Dict[str, CompilerArgs], clconf: ET.Element) -> None:
pass
diff --git a/mesonbuild/backend/vs2022backend.py b/mesonbuild/backend/vs2022backend.py
index ca35ac39df17..ea715d87d8e8 100644
--- a/mesonbuild/backend/vs2022backend.py
+++ b/mesonbuild/backend/vs2022backend.py
@@ -28,8 +28,8 @@ class Vs2022Backend(Vs2010Backend):
name = 'vs2022'
- def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]):
- super().__init__(build, interpreter)
+ def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter], gen_lite: bool = False):
+ super().__init__(build, interpreter, gen_lite=gen_lite)
self.sln_file_version = '12.00'
self.sln_version_comment = 'Version 17'
if self.environment is not None:
diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py
index 9c88eecb4b38..f8a5c2370634 100644
--- a/mesonbuild/backend/xcodebackend.py
+++ b/mesonbuild/backend/xcodebackend.py
@@ -21,7 +21,7 @@
from .. import dependencies
from .. import mesonlib
from .. import mlog
-from ..mesonlib import MesonException, OptionKey
+from ..mesonlib import MesonBugException, MesonException, OptionKey
if T.TYPE_CHECKING:
from ..interpreter import Interpreter
@@ -254,7 +254,14 @@ def object_filename_from_source(self, target, source):
obj_path = f'{project}.build/{buildtype}/{tname}.build/Objects-normal/{arch}/{stem}.o'
return obj_path
- def generate(self):
+ def generate(self,
+ capture: bool = False,
+ captured_compile_args_per_buildtype_and_target: dict = None) -> T.Optional[dict]:
+ # Check for (currently) unexpected capture arg use cases -
+ if capture:
+ raise MesonBugException('We do not expect the xcode backend to generate with \'capture = True\'')
+ if captured_compile_args_per_buildtype_and_target:
+ raise MesonBugException('We do not expect the xcode backend to be given a valid \'captured_compile_args_per_buildtype_and_target\'')
self.serialize_tests()
# Cache the result as the method rebuilds the array every time it is called.
self.build_targets = self.build.get_build_targets()
diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py
index b8f51322b8e4..dda0f38f212d 100644
--- a/mesonbuild/compilers/compilers.py
+++ b/mesonbuild/compilers/compilers.py
@@ -134,11 +134,14 @@ def is_header(fname: 'mesonlib.FileOrString') -> bool:
suffix = fname.split('.')[-1]
return suffix in header_suffixes
+def is_source_suffix(suffix: str) -> bool:
+ return suffix in source_suffixes
+
def is_source(fname: 'mesonlib.FileOrString') -> bool:
if isinstance(fname, mesonlib.File):
fname = fname.fname
suffix = fname.split('.')[-1].lower()
- return suffix in source_suffixes
+ return is_source_suffix(suffix)
def is_assembly(fname: 'mesonlib.FileOrString') -> bool:
if isinstance(fname, mesonlib.File):
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
index 27b1b91e4402..50763a313379 100644
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -72,6 +72,8 @@
stable_version = '.'.join(stable_version_array)
backendlist = ['ninja', 'vs', 'vs2010', 'vs2012', 'vs2013', 'vs2015', 'vs2017', 'vs2019', 'vs2022', 'xcode', 'none']
+genvslitelist = ['vs2022']
+buildtypelist = ['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom']
DEFAULT_YIELDING = False
@@ -79,6 +81,10 @@
_T = T.TypeVar('_T')
+def get_genvs_default_buildtype_list() -> list:
+ return buildtypelist[1:-2] # just debug, debugoptimized, and release for now but this should probably be configurable through some extra option, alongside --genvslite.
+
+
class MesonVersionMismatchException(MesonException):
'''Build directory generated with Meson version is incompatible with current version'''
def __init__(self, old_version: str, current_version: str) -> None:
@@ -1248,8 +1254,17 @@ def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffi
(OptionKey('auto_features'), BuiltinOption(UserFeatureOption, "Override value of all 'auto' features", 'auto')),
(OptionKey('backend'), BuiltinOption(UserComboOption, 'Backend to use', 'ninja', choices=backendlist,
readonly=True)),
+ (OptionKey('genvslite'),
+ BuiltinOption(
+ UserComboOption,
+ 'Setup multiple buildtype-suffixed ninja-backend build directories (e.g. builddir_[debug/release/etc.]) '
+ 'and generate [builddir]_vs containing a Visual Studio solution with multiple configurations that invoke a meson compile of the newly '
+ 'setup build directories, as appropriate for the current build configuration (buildtype)',
+ 'vs2022',
+ choices=genvslitelist)
+ ),
(OptionKey('buildtype'), BuiltinOption(UserComboOption, 'Build type to use', 'debug',
- choices=['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom'])),
+ choices=buildtypelist)),
(OptionKey('debug'), BuiltinOption(UserBooleanOption, 'Enable debug symbols and other information', True)),
(OptionKey('default_library'), BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both'],
yielding=False)),
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index 0526f9f0103f..f6133b73c151 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -1128,23 +1128,30 @@ def set_backend(self) -> None:
# The backend is already set when parsing subprojects
if self.backend is not None:
return
- backend = self.coredata.get_option(OptionKey('backend'))
from ..backend import backends
- self.backend = backends.get_backend_from_name(backend, self.build, self)
+
+ if OptionKey('genvslite') in self.user_defined_options.cmd_line_options.keys():
+ # Use of the '--genvslite vsxxxx' option ultimately overrides any '--backend xxx'
+ # option the user may specify.
+ backend_name = self.coredata.get_option(OptionKey('genvslite'))
+ self.backend = backends.get_genvslite_backend(backend_name, self.build, self)
+ else:
+ backend_name = self.coredata.get_option(OptionKey('backend'))
+ self.backend = backends.get_backend_from_name(backend_name, self.build, self)
if self.backend is None:
- raise InterpreterException(f'Unknown backend "{backend}".')
- if backend != self.backend.name:
+ raise InterpreterException(f'Unknown backend "{backend_name}".')
+ if backend_name != self.backend.name:
if self.backend.name.startswith('vs'):
mlog.log('Auto detected Visual Studio backend:', mlog.bold(self.backend.name))
if not self.environment.first_invocation:
- raise MesonBugException(f'Backend changed from {backend} to {self.backend.name}')
+ raise MesonBugException(f'Backend changed from {backend_name} to {self.backend.name}')
self.coredata.set_option(OptionKey('backend'), self.backend.name, first_invocation=True)
# Only init backend options on first invocation otherwise it would
# override values previously set from command line.
if self.environment.first_invocation:
- self.coredata.init_backend_options(backend)
+ self.coredata.init_backend_options(backend_name)
options = {k: v for k, v in self.environment.options.items() if k.is_backend()}
self.coredata.set_options(options)
diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py
index e7bf3c2a1673..dc6d97ede0d8 100644
--- a/mesonbuild/msetup.py
+++ b/mesonbuild/msetup.py
@@ -173,15 +173,21 @@ def validate_dirs(self, dir1: str, dir2: str, reconfigure: bool, wipe: bool) ->
raise MesonException(f'Directory is not empty and does not contain a previous build:\n{build_dir}')
return src_dir, build_dir
- def generate(self) -> None:
+ # See class Backend's 'generate' for comments on capture args and returned dictionary.
+ def generate(self,
+ capture: bool = False,
+ captured_compile_args_per_buildtype_and_target: dict = None) -> T.Optional[dict]:
env = environment.Environment(self.source_dir, self.build_dir, self.options)
mlog.initialize(env.get_log_dir(), self.options.fatal_warnings)
if self.options.profile:
mlog.set_timestamp_start(time.monotonic())
with mesonlib.BuildDirLock(self.build_dir):
- self._generate(env)
+ return self._generate(env, capture = capture, captured_compile_args_per_buildtype_and_target = captured_compile_args_per_buildtype_and_target)
- def _generate(self, env: environment.Environment) -> None:
+ def _generate(self,
+ env: environment.Environment,
+ capture: bool,
+ captured_compile_args_per_buildtype_and_target: dict) -> T.Optional[dict]:
# Get all user defined options, including options that have been defined
# during a previous invocation or using meson configure.
user_defined_options = argparse.Namespace(**vars(self.options))
@@ -230,6 +236,7 @@ def _generate(self, env: environment.Environment) -> None:
raise
cdf: T.Optional[str] = None
+ captured_compile_args = None
try:
dumpfile = os.path.join(env.get_scratch_dir(), 'build.dat')
# We would like to write coredata as late as possible since we use the existence of
@@ -246,7 +253,10 @@ def _generate(self, env: environment.Environment) -> None:
fname = os.path.join(self.build_dir, 'meson-logs', fname)
profile.runctx('intr.backend.generate()', globals(), locals(), filename=fname)
else:
- intr.backend.generate()
+ captured_compile_args = intr.backend.generate(
+ capture = capture,
+ captured_compile_args_per_buildtype_and_target = captured_compile_args_per_buildtype_and_target)
+
build.save(b, dumpfile)
if env.first_invocation:
# Use path resolved by coredata because they could have been
@@ -298,17 +308,56 @@ def _generate(self, env: environment.Environment) -> None:
os.unlink(cdf)
raise
+ return captured_compile_args
+
def finalize_postconf_hooks(self, b: build.Build, intr: interpreter.Interpreter) -> None:
b.devenv.append(intr.backend.get_devenv())
for mod in intr.modules.values():
mod.postconf_hook(b)
+def run_genvslite_setup(options: argparse.Namespace) -> None:
+ # With --genvslite, we essentially want to invoke multiple 'setup' iterations. I.e. -
+ # meson setup ... builddirprefix_debug
+ # meson setup ... builddirprefix_debugoptimized
+ # meson setup ... builddirprefix_release
+ # along with also setting up a new, thin/lite visual studio solution and projects with the multiple debug/opt/release configurations that
+ # invoke the appropriate 'meson compile ...' build commands upon the normal visual studio build/rebuild/clean actions, instead of using
+ # the native VS/msbuild system.
+ builddir_prefix = options.builddir
+ genvsliteval = options.cmd_line_options.pop(mesonlib.OptionKey('genvslite'))
+ # The command line may specify a '--backend' option, which doesn't make sense in conjunction with
+ # '--genvslite', where we always want to use a ninja back end -
+ k_backend = mesonlib.OptionKey('backend')
+ if k_backend in options.cmd_line_options.keys():
+ if options.cmd_line_options[k_backend] != 'ninja':
+ raise MesonException('Explicitly specifying a backend option with \'genvslite\' is not necessary (the ninja backend is always used) but specifying a non-ninja backend conflicts with a \'genvslite\' setup')
+ else:
+ options.cmd_line_options[k_backend] = 'ninja'
+ buildtypes_list = coredata.get_genvs_default_buildtype_list()
+ captured_compile_args_per_buildtype_and_target = {}
+
+ for buildtypestr in buildtypes_list:
+ options.builddir = f'{builddir_prefix}_{buildtypestr}' # E.g. builddir_release
+ options.cmd_line_options[mesonlib.OptionKey('buildtype')] = buildtypestr
+ app = MesonApp(options)
+ captured_compile_args_per_buildtype_and_target[buildtypestr] = app.generate(capture = True)
+ #Now for generating the 'lite' solution and project files, which will use these builds we've just set up, above.
+ options.builddir = f'{builddir_prefix}_vs'
+ options.cmd_line_options[mesonlib.OptionKey('genvslite')] = genvsliteval
+ app = MesonApp(options)
+ app.generate(capture = False, captured_compile_args_per_buildtype_and_target = captured_compile_args_per_buildtype_and_target)
+
def run(options: T.Union[argparse.Namespace, T.List[str]]) -> int:
if not isinstance(options, argparse.Namespace):
parser = argparse.ArgumentParser()
add_arguments(parser)
options = parser.parse_args(options)
coredata.parse_cmd_line_options(options)
- app = MesonApp(options)
- app.generate()
+
+ if mesonlib.OptionKey('genvslite') in options.cmd_line_options.keys():
+ run_genvslite_setup(options)
+ else:
+ app = MesonApp(options)
+ app.generate()
+
return 0
diff --git a/test cases/unit/114 genvslite/main.cpp b/test cases/unit/114 genvslite/main.cpp
new file mode 100644
index 000000000000..ca250bdd604d
--- /dev/null
+++ b/test cases/unit/114 genvslite/main.cpp
@@ -0,0 +1,10 @@
+#include
+
+int main() {
+#ifdef NDEBUG
+ printf("Non-debug\n");
+#else
+ printf("Debug\n");
+#endif
+ return 0;
+}
diff --git a/test cases/unit/114 genvslite/meson.build b/test cases/unit/114 genvslite/meson.build
new file mode 100644
index 000000000000..3445d7f33aa4
--- /dev/null
+++ b/test cases/unit/114 genvslite/meson.build
@@ -0,0 +1,5 @@
+project('genvslite', 'cpp',
+ default_options : ['b_ndebug=if-release']
+ )
+
+exe = executable('genvslite', 'main.cpp')
diff --git a/unittests/baseplatformtests.py b/unittests/baseplatformtests.py
index ea95b15ac50a..4b16e7d8253b 100644
--- a/unittests/baseplatformtests.py
+++ b/unittests/baseplatformtests.py
@@ -207,7 +207,8 @@ def init(self, srcdir, *,
extra_args = []
if not isinstance(extra_args, list):
extra_args = [extra_args]
- args = [srcdir, self.builddir]
+ build_and_src_dir_args = [self.builddir, srcdir]
+ args = []
if default_args:
args += ['--prefix', self.prefix]
if self.libdir:
@@ -219,7 +220,7 @@ def init(self, srcdir, *,
self.privatedir = os.path.join(self.builddir, 'meson-private')
if inprocess:
try:
- returncode, out, err = run_configure_inprocess(['setup'] + self.meson_args + args + extra_args, override_envvars)
+ returncode, out, err = run_configure_inprocess(['setup'] + self.meson_args + args + extra_args + build_and_src_dir_args, override_envvars)
except Exception as e:
if not allow_fail:
self._print_meson_log()
@@ -245,7 +246,7 @@ def init(self, srcdir, *,
raise RuntimeError('Configure failed')
else:
try:
- out = self._run(self.setup_command + args + extra_args, override_envvars=override_envvars, workdir=workdir)
+ out = self._run(self.setup_command + args + extra_args + build_and_src_dir_args, override_envvars=override_envvars, workdir=workdir)
except SkipTest:
raise SkipTest('Project requested skipping: ' + srcdir)
except Exception:
diff --git a/unittests/windowstests.py b/unittests/windowstests.py
index c81d924e8b37..36a1f3f105fa 100644
--- a/unittests/windowstests.py
+++ b/unittests/windowstests.py
@@ -184,6 +184,93 @@ def test_msvc_cpp17(self):
# to the right reason).
return
self.build()
+
+ @skipIf(is_cygwin(), 'Test only applicable to Windows')
+ def test_genvslite(self):
+ # The test framework itself might be forcing a specific, non-ninja backend across a set of tests, which
+ # includes this test. E.g. -
+ # > python.exe run_unittests.py --backend=vs WindowsTests
+ # Since that explicitly specifies a backend that's incompatible with (and essentially meaningless in
+ # conjunction with) 'genvslite', we should skip further genvslite testing.
+ if self.backend is not Backend.ninja:
+ raise SkipTest('Test only applies when using the Ninja backend')
+
+ testdir = os.path.join(self.unit_test_dir, '114 genvslite')
+
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ if cc.get_argument_syntax() != 'msvc':
+ raise SkipTest('Test only applies when MSVC tools are available.')
+
+ # We want to run the genvslite setup. I.e. -
+ # meson setup --genvslite vs2022 ...
+ # which we should expect to generate the set of _debug/_debugoptimized/_release suffixed
+ # build directories. Then we want to check that the solution/project build hooks (like clean,
+ # build, and rebuild) end up ultimately invoking the 'meson compile ...' of the appropriately
+ # suffixed build dir, for which we need to use 'msbuild.exe'
+
+ # Find 'msbuild.exe'
+ msbuildprog = ExternalProgram('msbuild.exe')
+ self.assertTrue(msbuildprog.found(), msg='msbuild.exe not found')
+
+ # Setup with '--genvslite ...'
+ self.new_builddir()
+
+ # Firstly, we'd like to check that meson errors if the user explicitly specifies a non-ninja backend
+ # during setup.
+ with self.assertRaises(subprocess.CalledProcessError) as cm:
+ self.init(testdir, extra_args=['--genvslite', 'vs2022', '--backend', 'vs'])
+ self.assertIn("specifying a non-ninja backend conflicts with a 'genvslite' setup", cm.exception.stdout)
+
+ # Wrap the following bulk of setup and msbuild invocation testing in a try-finally because any exception,
+ # failure, or success must always clean up any of the suffixed build dir folders that may have been generated.
+ try:
+ # Since this
+ self.init(testdir, extra_args=['--genvslite', 'vs2022'])
+ # We need to bear in mind that the BasePlatformTests framework creates and cleans up its own temporary
+ # build directory. However, 'genvslite' creates a set of suffixed build directories which we'll have
+ # to clean up ourselves. See 'finally' block below.
+
+ # We intentionally skip the -
+ # self.build()
+ # step because we're wanting to test compilation/building through the solution/project's interface.
+
+ # Execute the debug and release builds through the projects 'Build' hooks
+ genvslite_vcxproj_path = str(os.path.join(self.builddir+'_vs', 'genvslite@exe.vcxproj'))
+ # This use-case of invoking the .sln/.vcxproj build hooks, not through Visual Studio itself, but through
+ # 'msbuild.exe', in a VS tools command prompt environment (e.g. "x64 Native Tools Command Prompt for VS 2022"), is a
+ # problem: Such an environment sets the 'VSINSTALLDIR' variable which, mysteriously, has the side-effect of causing
+ # the spawned 'meson compile' command to fail to find 'ninja' (and even when ninja can be found elsewhere, all the
+ # compiler binaries that ninja wants to run also fail to be found). The PATH environment variable in the child python
+ # (and ninja) processes are fundamentally stripped down of all the critical search paths required to run the ninja
+ # compile work ... ONLY when 'VSINSTALLDIR' is set; without 'VSINSTALLDIR' set, the meson compile command does search
+ # for and find ninja (ironically, it finds it under the path where VSINSTALLDIR pointed!).
+ # For the above reason, this testing works around this bizarre behaviour by temporarily removing any 'VSINSTALLDIR'
+ # variable, prior to invoking the builds -
+ current_env = os.environ.copy()
+ current_env.pop('VSINSTALLDIR', None)
+ subprocess.check_call(
+ ['msbuild', '-target:Build', '-property:Configuration=debug', genvslite_vcxproj_path],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ env=current_env)
+ subprocess.check_call(
+ ['msbuild', '-target:Build', '-property:Configuration=release', genvslite_vcxproj_path],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ env=current_env)
+
+ # Check this has actually built the appropriate exes
+ output_debug = subprocess.check_output(str(os.path.join(self.builddir+'_debug', 'genvslite.exe')))
+ self.assertEqual( output_debug, b'Debug\r\n' )
+ output_release = subprocess.check_output(str(os.path.join(self.builddir+'_release', 'genvslite.exe')))
+ self.assertEqual( output_release, b'Non-debug\r\n' )
+
+ finally:
+ # Clean up our special suffixed temporary build dirs
+ suffixed_build_dirs = glob(self.builddir+'_*', recursive=False)
+ for build_dir in suffixed_build_dirs:
+ shutil.rmtree(build_dir)
def test_install_pdb_introspection(self):
testdir = os.path.join(self.platform_test_dir, '1 basic')