Experimental 'genvslite' WIP. (#11049)

* Capture all compile args from the first round of ninja backend generation for all languages used in building the targets so that these args, defines, and include paths can be applied to the .vcxproj's intellisense fields for all buildtypes/configurations.

Solution generation is now set up for mutiple build configurations (buildtypes) when using '--genvslite'.

All generated vcxprojs invoke the same high-level meson compile to build all targets;  there's no selective target building (could add this later).  Related to this, we skip pointlessly generating vcxprojs for targets that aren't buildable (BuildTarget-derived), which aren't of interest to the user anyway.

When using --genvslite, no longer inject '<ProjectReference ...>' dependencies on which a generated .vcxproj depends because that imposes a forced visual studio build dependency, which we don't want, since we're essentially bypassing VS's build in favour of running 'meson compile ...'.

When populating the vcxproj's shared intellisense defines, include paths, and compiler options fields, we choose the most frequent src file language, since this means more project src files can simply reference the project shared fields and fewer files of non-primary language types need to populate their full set of intellisense fields.  This makes for smaller .vcxproj files.

Paths for generated source/header/etc files, left alone, would be added to solution projects relative to the '..._vs' build directory, where they're never generated;  they're generated under the respective '..._[debug/opt/release]' ninja build directories that correspond to the solution build configuration. Although VS doesn't allow conditional src/header listings in vcxprojs (at least not in a simple way that I'm aware of), we can ensure these generated sources get adjusted to at least reference locations under one of the concrete build directories (I've chosen '..._debug') under which they will be generated.

Testing with --genvslite has revealed that, in some cases, the presence of 'c:\windows\system32;c:\windows' on the 'Path' environment variable (via the make-style project's ExecutablePath element) is critical to getting the 'meson compile ...' build to succeed.  Not sure whether this is some 'find and guess' implicit defaults behaviour within meson or within the MSVC compiler that some projects may rely on. Feels weird but not sure of a better solution than forcibly adding these to the Path environment variable (the Executable Path property of the project).

Added a new windows-only test to windowstests.py ('test_genvslite') to exercise the --genvslite option along with checking that the 'msbuild' command invokes the 'meson compile ...' of the build-type-appropriate-suffixed temporary build dir and checks expected program output.

Check and report error if user specifies a non-ninja backend with a 'genvslite' setup, since that conflicts with the stated behaviour of genvslite.  Also added this test case to 'WindowsTests.test_genvslite'

I had problems tracking down some problematic environment variable behaviour, which appears to need a work-around. See further notes on VSINSTALLDIR, in windowstests.py, test_genvslite.
'meson setup --help' clearly states that positional arguments are ... [builddir] [sourcedir].  However, BasePlatformTests.init(...) was passing these in the order [sourcedir] [builddir].  This was producing failures, saying, "ERROR: Neither directory contains a build file meson.build." but when using the correct ordering, setup now succeeds.

Changed regen, run_tests, and run_install utility projects to be simpler makefile projects instead, with commands to invoke the appropriate '...meson.py --internal regencheck ...' (or install/test) on the '[builddir]_[buildtype]' as appropriate for the curent VS configuration.  Also, since the 'regen.vcxproj' utility didn't work correctly with '--genvslite' setup build dirs, and getting it to fully work would require more non-trivial intrusion into new parts of meson (i.e. '--internal regencheck', '--internal regenerate', and perhaps also 'setup --reconfigure'), for now, the REGEN project is replaced with a simpler, lighter-weight RECONFIGURE utility proj, which is unlinked from any solution build dependencies and which simply runs 'meson setup --reconfigure [builddir]_[buildtype] [srcdir]' on each of the ninja-backend build dirs for each buildtype.
Yes, although this will enable the building/compiling to be correctly configured, it can leave the solution/vcxprojs stale and out-of-date, it's simple for the user to 'meson setup --genvslite ...' to fully regenerate an updated, correct solution again. However, I've noted this down as a 'fixme' to consider implementing the full regen behaviour for the genvslite case.

* Review feedback changes -
- Avoid use of 'captured_compile_args_per_buildtype_and_target' as an 'out' param.
- Factored a little msetup.py, 'run(...)' macro/looping setup steps, for genvslite, out into a 'run_genvslite_setup' func.

* Review feedback:  Fixed missing spaces between multi-line strings.

* 'backend_name' assignment gets immediately overwritten in 'genvslite' case so moved it into else/non-genvslite block.

* Had to bump up 'test cases/unit/113 genvslites/...' up to 114; it collided with a newly added test dir again.

* Changed validation of 'capture' and 'captured_compile_args_...' to use MesonBugException instead of MesonException.

* Changed some function param and closing brace indentation.
pull/11912/merge
GertyP 1 year ago committed by GitHub
parent 9e1e4cafd5
commit 36bf53bdfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      docs/markdown/Builtin-options.md
  2. 11
      docs/markdown/snippets/gen_vslite.md
  3. 19
      mesonbuild/backend/backends.py
  4. 49
      mesonbuild/backend/ninjabackend.py
  5. 12
      mesonbuild/backend/nonebackend.py
  6. 1128
      mesonbuild/backend/vs2010backend.py
  7. 4
      mesonbuild/backend/vs2022backend.py
  8. 11
      mesonbuild/backend/xcodebackend.py
  9. 5
      mesonbuild/compilers/compilers.py
  10. 17
      mesonbuild/coredata.py
  11. 19
      mesonbuild/interpreter/interpreter.py
  12. 61
      mesonbuild/msetup.py
  13. 10
      test cases/unit/114 genvslite/main.cpp
  14. 5
      test cases/unit/114 genvslite/meson.build
  15. 7
      unittests/baseplatformtests.py
  16. 87
      unittests/windowstests.py

@ -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 | | auto_features {enabled, disabled, auto} | auto | Override value of all 'auto' features | no | no |
| backend {ninja, vs,<br>vs2010, vs2012, vs2013, vs2015, vs2017, vs2019, vs2022, xcode, none} | ninja | Backend to use | no | no | | backend {ninja, vs,<br>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,<br>debugoptimized, release, minsize, custom} | debug | Build type to use | no | no | | buildtype {plain, debug,<br>debugoptimized, release, minsize, custom} | debug | Build type to use | no | no |
| debug | true | Enable debug symbols and other information | no | no | | debug | true | Enable debug symbols and other information | no | no |
| default_library {shared, static, both} | shared | Default library type | no | yes | | 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 build, but for projects that need configuration + testing + installation allows
for a lighter automated build pipeline. 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` #### Details for `buildtype`
<a name="build-type-options"></a> For setting optimization levels and <a name="build-type-options"></a> For setting optimization levels and

@ -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.

@ -256,6 +256,13 @@ def get_backend_from_name(backend: str, build: T.Optional[build.Build] = None, i
return nonebackend.NoneBackend(build, interpreter) return nonebackend.NoneBackend(build, interpreter)
return None 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. # 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. # Feel free to move stuff in and out of it as you see fit.
class Backend: class Backend:
@ -280,7 +287,17 @@ class Backend:
self.src_to_build = mesonlib.relpath(self.environment.get_build_dir(), self.src_to_build = mesonlib.relpath(self.environment.get_build_dir(),
self.environment.get_source_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__}') 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: def get_target_filename(self, t: T.Union[build.Target, build.CustomTargetIndex], *, warn_multi_output: bool = True) -> str:

@ -38,7 +38,7 @@ from ..arglist import CompilerArgs
from ..compilers import Compiler from ..compilers import Compiler
from ..linkers import ArLikeLinker, RSPFileSyntax from ..linkers import ArLikeLinker, RSPFileSyntax
from ..mesonlib import ( from ..mesonlib import (
File, LibType, MachineChoice, MesonException, OrderedSet, PerMachine, File, LibType, MachineChoice, MesonBugException, MesonException, OrderedSet, PerMachine,
ProgressBar, quote_arg ProgressBar, quote_arg
) )
from ..mesonlib import get_compiler_for_source, has_path_sep, OptionKey from ..mesonlib import get_compiler_for_source, has_path_sep, OptionKey
@ -575,7 +575,13 @@ class NinjaBackend(backends.Backend):
raise MesonException(f'Could not determine vs dep dependency prefix string. output: {stderr} {stdout}') 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) ninja = environment.detect_ninja_command_and_version(log=True)
if self.environment.coredata.get_option(OptionKey('vsenv')): if self.environment.coredata.get_option(OptionKey('vsenv')):
builddir = Path(self.environment.get_build_dir()) builddir = Path(self.environment.get_build_dir())
@ -614,6 +620,14 @@ class NinjaBackend(backends.Backend):
self.build_elements = [] self.build_elements = []
self.generate_phony() self.generate_phony()
self.add_build_comment(NinjaComment('Build rules for targets')) 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'): for t in ProgressBar(self.build.get_targets().values(), desc='Generating targets'):
self.generate_target(t) self.generate_target(t)
self.add_build_comment(NinjaComment('Test rules')) self.add_build_comment(NinjaComment('Test rules'))
@ -652,6 +666,9 @@ class NinjaBackend(backends.Backend):
self.generate_compdb() self.generate_compdb()
self.generate_rust_project_json() self.generate_rust_project_json()
if capture:
return captured_compile_args_per_target
def generate_rust_project_json(self) -> None: def generate_rust_project_json(self) -> None:
"""Generate a rust-analyzer compatible rust-project.json file.""" """Generate a rust-analyzer compatible rust-project.json file."""
if not self.rust_crates: if not self.rust_crates:
@ -2922,6 +2939,34 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
commands += compiler.get_include_args(self.get_target_private_dir(target), False) commands += compiler.get_include_args(self.get_target_private_dir(target), False)
return commands 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, def generate_single_compile(self, target: build.BuildTarget, src,
is_generated=False, header_deps=None, is_generated=False, header_deps=None,
order_deps: T.Optional[T.List['mesonlib.FileOrString']] = None, order_deps: T.Optional[T.List['mesonlib.FileOrString']] = None,

@ -14,6 +14,8 @@
from __future__ import annotations from __future__ import annotations
import typing as T
from .backends import Backend from .backends import Backend
from .. import mlog from .. import mlog
from ..mesonlib import MesonBugException from ..mesonlib import MesonBugException
@ -23,7 +25,15 @@ class NoneBackend(Backend):
name = 'none' 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(): if self.build.get_targets():
raise MesonBugException('None backend cannot generate target rules, but should have failed earlier.') raise MesonBugException('None backend cannot generate target rules, but should have failed earlier.')
mlog.log('Generating simple install-only backend') mlog.log('Generating simple install-only backend')

File diff suppressed because it is too large Load Diff

@ -28,8 +28,8 @@ class Vs2022Backend(Vs2010Backend):
name = 'vs2022' name = 'vs2022'
def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]): def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter], gen_lite: bool = False):
super().__init__(build, interpreter) super().__init__(build, interpreter, gen_lite=gen_lite)
self.sln_file_version = '12.00' self.sln_file_version = '12.00'
self.sln_version_comment = 'Version 17' self.sln_version_comment = 'Version 17'
if self.environment is not None: if self.environment is not None:

@ -21,7 +21,7 @@ from .. import build
from .. import dependencies from .. import dependencies
from .. import mesonlib from .. import mesonlib
from .. import mlog from .. import mlog
from ..mesonlib import MesonException, OptionKey from ..mesonlib import MesonBugException, MesonException, OptionKey
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from ..interpreter import Interpreter from ..interpreter import Interpreter
@ -254,7 +254,14 @@ class XCodeBackend(backends.Backend):
obj_path = f'{project}.build/{buildtype}/{tname}.build/Objects-normal/{arch}/{stem}.o' obj_path = f'{project}.build/{buildtype}/{tname}.build/Objects-normal/{arch}/{stem}.o'
return obj_path 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() self.serialize_tests()
# Cache the result as the method rebuilds the array every time it is called. # Cache the result as the method rebuilds the array every time it is called.
self.build_targets = self.build.get_build_targets() self.build_targets = self.build.get_build_targets()

@ -134,11 +134,14 @@ def is_header(fname: 'mesonlib.FileOrString') -> bool:
suffix = fname.split('.')[-1] suffix = fname.split('.')[-1]
return suffix in header_suffixes return suffix in header_suffixes
def is_source_suffix(suffix: str) -> bool:
return suffix in source_suffixes
def is_source(fname: 'mesonlib.FileOrString') -> bool: def is_source(fname: 'mesonlib.FileOrString') -> bool:
if isinstance(fname, mesonlib.File): if isinstance(fname, mesonlib.File):
fname = fname.fname fname = fname.fname
suffix = fname.split('.')[-1].lower() suffix = fname.split('.')[-1].lower()
return suffix in source_suffixes return is_source_suffix(suffix)
def is_assembly(fname: 'mesonlib.FileOrString') -> bool: def is_assembly(fname: 'mesonlib.FileOrString') -> bool:
if isinstance(fname, mesonlib.File): if isinstance(fname, mesonlib.File):

@ -72,6 +72,8 @@ if stable_version.endswith('.99'):
stable_version = '.'.join(stable_version_array) stable_version = '.'.join(stable_version_array)
backendlist = ['ninja', 'vs', 'vs2010', 'vs2012', 'vs2013', 'vs2015', 'vs2017', 'vs2019', 'vs2022', 'xcode', 'none'] backendlist = ['ninja', 'vs', 'vs2010', 'vs2012', 'vs2013', 'vs2015', 'vs2017', 'vs2019', 'vs2022', 'xcode', 'none']
genvslitelist = ['vs2022']
buildtypelist = ['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom']
DEFAULT_YIELDING = False DEFAULT_YIELDING = False
@ -79,6 +81,10 @@ DEFAULT_YIELDING = False
_T = T.TypeVar('_T') _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): class MesonVersionMismatchException(MesonException):
'''Build directory generated with Meson version is incompatible with current version''' '''Build directory generated with Meson version is incompatible with current version'''
def __init__(self, old_version: str, current_version: str) -> None: def __init__(self, old_version: str, current_version: str) -> None:
@ -1248,8 +1254,17 @@ BUILTIN_CORE_OPTIONS: 'MutableKeyedOptionDictType' = OrderedDict([
(OptionKey('auto_features'), BuiltinOption(UserFeatureOption, "Override value of all 'auto' features", 'auto')), (OptionKey('auto_features'), BuiltinOption(UserFeatureOption, "Override value of all 'auto' features", 'auto')),
(OptionKey('backend'), BuiltinOption(UserComboOption, 'Backend to use', 'ninja', choices=backendlist, (OptionKey('backend'), BuiltinOption(UserComboOption, 'Backend to use', 'ninja', choices=backendlist,
readonly=True)), 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', (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('debug'), BuiltinOption(UserBooleanOption, 'Enable debug symbols and other information', True)),
(OptionKey('default_library'), BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both'], (OptionKey('default_library'), BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both'],
yielding=False)), yielding=False)),

@ -1128,23 +1128,30 @@ class Interpreter(InterpreterBase, HoldableObject):
# The backend is already set when parsing subprojects # The backend is already set when parsing subprojects
if self.backend is not None: if self.backend is not None:
return return
backend = self.coredata.get_option(OptionKey('backend'))
from ..backend import backends 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: if self.backend is None:
raise InterpreterException(f'Unknown backend "{backend}".') raise InterpreterException(f'Unknown backend "{backend_name}".')
if backend != self.backend.name: if backend_name != self.backend.name:
if self.backend.name.startswith('vs'): if self.backend.name.startswith('vs'):
mlog.log('Auto detected Visual Studio backend:', mlog.bold(self.backend.name)) mlog.log('Auto detected Visual Studio backend:', mlog.bold(self.backend.name))
if not self.environment.first_invocation: 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) self.coredata.set_option(OptionKey('backend'), self.backend.name, first_invocation=True)
# Only init backend options on first invocation otherwise it would # Only init backend options on first invocation otherwise it would
# override values previously set from command line. # override values previously set from command line.
if self.environment.first_invocation: 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()} options = {k: v for k, v in self.environment.options.items() if k.is_backend()}
self.coredata.set_options(options) self.coredata.set_options(options)

@ -173,15 +173,21 @@ class MesonApp:
raise MesonException(f'Directory is not empty and does not contain a previous build:\n{build_dir}') raise MesonException(f'Directory is not empty and does not contain a previous build:\n{build_dir}')
return src_dir, 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) env = environment.Environment(self.source_dir, self.build_dir, self.options)
mlog.initialize(env.get_log_dir(), self.options.fatal_warnings) mlog.initialize(env.get_log_dir(), self.options.fatal_warnings)
if self.options.profile: if self.options.profile:
mlog.set_timestamp_start(time.monotonic()) mlog.set_timestamp_start(time.monotonic())
with mesonlib.BuildDirLock(self.build_dir): 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 # Get all user defined options, including options that have been defined
# during a previous invocation or using meson configure. # during a previous invocation or using meson configure.
user_defined_options = argparse.Namespace(**vars(self.options)) user_defined_options = argparse.Namespace(**vars(self.options))
@ -230,6 +236,7 @@ class MesonApp:
raise raise
cdf: T.Optional[str] = None cdf: T.Optional[str] = None
captured_compile_args = None
try: try:
dumpfile = os.path.join(env.get_scratch_dir(), 'build.dat') 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 # We would like to write coredata as late as possible since we use the existence of
@ -246,7 +253,10 @@ class MesonApp:
fname = os.path.join(self.build_dir, 'meson-logs', fname) fname = os.path.join(self.build_dir, 'meson-logs', fname)
profile.runctx('intr.backend.generate()', globals(), locals(), filename=fname) profile.runctx('intr.backend.generate()', globals(), locals(), filename=fname)
else: 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) build.save(b, dumpfile)
if env.first_invocation: if env.first_invocation:
# Use path resolved by coredata because they could have been # Use path resolved by coredata because they could have been
@ -298,17 +308,56 @@ class MesonApp:
os.unlink(cdf) os.unlink(cdf)
raise raise
return captured_compile_args
def finalize_postconf_hooks(self, b: build.Build, intr: interpreter.Interpreter) -> None: def finalize_postconf_hooks(self, b: build.Build, intr: interpreter.Interpreter) -> None:
b.devenv.append(intr.backend.get_devenv()) b.devenv.append(intr.backend.get_devenv())
for mod in intr.modules.values(): for mod in intr.modules.values():
mod.postconf_hook(b) 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: def run(options: T.Union[argparse.Namespace, T.List[str]]) -> int:
if not isinstance(options, argparse.Namespace): if not isinstance(options, argparse.Namespace):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
add_arguments(parser) add_arguments(parser)
options = parser.parse_args(options) options = parser.parse_args(options)
coredata.parse_cmd_line_options(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 return 0

@ -0,0 +1,10 @@
#include<stdio.h>
int main() {
#ifdef NDEBUG
printf("Non-debug\n");
#else
printf("Debug\n");
#endif
return 0;
}

@ -0,0 +1,5 @@
project('genvslite', 'cpp',
default_options : ['b_ndebug=if-release']
)
exe = executable('genvslite', 'main.cpp')

@ -207,7 +207,8 @@ class BasePlatformTests(TestCase):
extra_args = [] extra_args = []
if not isinstance(extra_args, list): if not isinstance(extra_args, list):
extra_args = [extra_args] extra_args = [extra_args]
args = [srcdir, self.builddir] build_and_src_dir_args = [self.builddir, srcdir]
args = []
if default_args: if default_args:
args += ['--prefix', self.prefix] args += ['--prefix', self.prefix]
if self.libdir: if self.libdir:
@ -219,7 +220,7 @@ class BasePlatformTests(TestCase):
self.privatedir = os.path.join(self.builddir, 'meson-private') self.privatedir = os.path.join(self.builddir, 'meson-private')
if inprocess: if inprocess:
try: 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: except Exception as e:
if not allow_fail: if not allow_fail:
self._print_meson_log() self._print_meson_log()
@ -245,7 +246,7 @@ class BasePlatformTests(TestCase):
raise RuntimeError('Configure failed') raise RuntimeError('Configure failed')
else: else:
try: 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: except SkipTest:
raise SkipTest('Project requested skipping: ' + srcdir) raise SkipTest('Project requested skipping: ' + srcdir)
except Exception: except Exception:

@ -184,6 +184,93 @@ class WindowsTests(BasePlatformTests):
# to the right reason). # to the right reason).
return return
self.build() 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): def test_install_pdb_introspection(self):
testdir = os.path.join(self.platform_test_dir, '1 basic') testdir = os.path.join(self.platform_test_dir, '1 basic')

Loading…
Cancel
Save