Add --vsenv command line option and active VS only when needed

pull/9385/head
Xavier Claessens 3 years ago committed by Jussi Pakkanen
parent 31bea202c9
commit 928078982c
  1. 14
      docs/markdown/snippets/vsenv.md
  2. 3
      mesonbuild/backend/ninjabackend.py
  3. 1
      mesonbuild/build.py
  4. 10
      mesonbuild/interpreter/interpreter.py
  5. 22
      mesonbuild/mcompile.py
  6. 3
      mesonbuild/mdevenv.py
  7. 3
      mesonbuild/mdist.py
  8. 1
      mesonbuild/mesonlib/__init__.py
  9. 100
      mesonbuild/mesonlib/vsenv.py
  10. 91
      mesonbuild/mesonmain.py
  11. 4
      mesonbuild/msetup.py
  12. 3
      run_project_tests.py
  13. 4
      run_tests.py
  14. 6
      run_unittests.py

@ -0,0 +1,14 @@
## Force Visual Studio environment activation
Since `0.59.0`, meson automatically activates Visual Studio environment on Windows
for all its subcommands, but only if no other compilers (e.g. `gcc` or `clang`)
are found, and silently continue if Visual Studio activation fails.
`meson setup --vsenv` command line argument can now be used to force Visual Studio
activation even when other compilers are found. It also make Meson abort with an
error message when activation fails. This is especially useful for Github Action
because their Windows images have gcc in their PATH by default.
`--vsenv` is set by default when using `vs` backend.
Only `setup`, `compile`, `dist` and `devenv` subcommands now activate Visual Studio.

@ -49,7 +49,6 @@ from ..mesonlib import get_compiler_for_source, has_path_sep, OptionKey
from .backends import CleanTrees
from ..build import GeneratedList, InvalidArguments, ExtractedObjects
from ..interpreter import Interpreter
from ..mesonmain import need_setup_vsenv
if T.TYPE_CHECKING:
from .._typing import ImmutableListProtocol
@ -513,7 +512,7 @@ class NinjaBackend(backends.Backend):
def generate(self):
ninja = environment.detect_ninja_command_and_version(log=True)
if need_setup_vsenv:
if self.build.need_vsenv:
builddir = Path(self.environment.get_build_dir())
try:
# For prettier printing, reduce to a relative path. If

@ -272,6 +272,7 @@ class Build:
environment.is_cross_build(), {}, {})
self.devenv: T.List[EnvironmentVariables] = []
self.modules: T.List[str] = []
self.need_vsenv = False
def get_build_targets(self):
build_targets = OrderedDict()

@ -1115,6 +1115,16 @@ external dependencies (including libraries) must go to "dependencies".''')
mlog.log('Project name:', mlog.bold(proj_name))
mlog.log('Project version:', mlog.bold(self.project_version))
if not self.is_subproject():
# We have to activate VS before adding languages and before calling
# self.set_backend() otherwise it wouldn't be able to detect which
# vs backend version we need. But after setting default_options in case
# the project sets vs backend by default.
backend = self.coredata.get_option(OptionKey('backend'))
force_vsenv = self.user_defined_options.vsenv or backend.startswith('vs')
if mesonlib.setup_vsenv(force_vsenv):
self.build.need_vsenv = True
self.add_languages(proj_langs, True, MachineChoice.HOST)
self.add_languages(proj_langs, False, MachineChoice.BUILD)

@ -26,9 +26,10 @@ from pathlib import Path
from . import mlog
from . import mesonlib
from . import coredata
from .mesonlib import MesonException
from .mesonlib import MesonException, RealPathAction, setup_vsenv
from mesonbuild.environment import detect_ninja
from mesonbuild.coredata import UserArrayOption
from mesonbuild import build
if T.TYPE_CHECKING:
import argparse
@ -286,14 +287,9 @@ def add_arguments(parser: 'argparse.ArgumentParser') -> None:
action='store_true',
help='Clean the build directory.'
)
parser.add_argument(
'-C',
action='store',
dest='builddir',
type=Path,
default='.',
help='The directory containing build files to be built.'
)
parser.add_argument('-C', dest='wd', action=RealPathAction,
help='directory to cd into before running')
parser.add_argument(
'-j', '--jobs',
action='store',
@ -333,8 +329,12 @@ def add_arguments(parser: 'argparse.ArgumentParser') -> None:
)
def run(options: 'argparse.Namespace') -> int:
bdir = options.builddir # type: Path
validate_builddir(bdir.resolve())
bdir = Path(options.wd)
buildfile = bdir / 'meson-private' / 'build.dat'
if not buildfile.is_file():
raise MesonException(f'Directory {options.wd!r} does not seem to be a Meson build directory.')
b = build.load(options.wd)
setup_vsenv(b.need_vsenv)
cmd = [] # type: T.List[str]
env = None # type: T.Optional[T.Dict[str, str]]

@ -4,7 +4,7 @@ import tempfile
from pathlib import Path
from . import build
from .mesonlib import MesonException, RealPathAction, is_windows
from .mesonlib import MesonException, RealPathAction, is_windows, setup_vsenv
import typing as T
@ -41,6 +41,7 @@ def run(options: argparse.Namespace) -> int:
if not buildfile.is_file():
raise MesonException(f'Directory {options.wd!r} does not seem to be a Meson build directory.')
b = build.load(options.wd)
setup_vsenv(b.need_vsenv)
devenv = get_env(b, options.wd)

@ -26,7 +26,7 @@ from glob import glob
from pathlib import Path
from mesonbuild.environment import detect_ninja
from mesonbuild.mesonlib import (MesonException, RealPathAction, quiet_git,
windows_proof_rmtree)
windows_proof_rmtree, setup_vsenv)
from mesonbuild.wrap import wrap
from mesonbuild import mlog, build
from .scripts.meson_exe import run_exe
@ -281,6 +281,7 @@ def run(options):
if not buildfile.is_file():
raise MesonException(f'Directory {options.wd!r} does not seem to be a Meson build directory.')
b = build.load(options.wd)
setup_vsenv(b.need_vsenv)
# This import must be load delayed, otherwise it will get the default
# value of None.
from mesonbuild.mesonlib import get_meson_command

@ -19,6 +19,7 @@
import os
from .universal import *
from .vsenv import setup_vsenv
# Here we import either the posix implementations, the windows implementations,
# or a generic no-op implementation

@ -0,0 +1,100 @@
import os
import subprocess
import json
import pathlib
import shutil
import tempfile
from .. import mlog
from .universal import MesonException, is_windows
bat_template = '''@ECHO OFF
call "{}"
ECHO {}
SET
'''
# If on Windows and VS is installed but not set up in the environment,
# set it to be runnable. In this way Meson can be directly invoked
# from any shell, VS Code etc.
def _setup_vsenv(force: bool) -> bool:
if not is_windows():
return False
if os.environ.get('OSTYPE') == 'cygwin':
return False
if 'Visual Studio' in os.environ['PATH']:
return False
# VSINSTALL is set when running setvars from a Visual Studio installation
# Tested with Visual Studio 2012 and 2017
if 'VSINSTALLDIR' in os.environ:
return False
# Check explicitly for cl when on Windows
if shutil.which('cl.exe'):
return False
if not force:
if shutil.which('cc'):
return False
if shutil.which('gcc'):
return False
if shutil.which('clang'):
return False
if shutil.which('clang-cl'):
return False
root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")
bat_locator_bin = pathlib.Path(root, 'Microsoft Visual Studio/Installer/vswhere.exe')
if not bat_locator_bin.exists():
raise MesonException(f'Could not find {bat_locator_bin}')
bat_json = subprocess.check_output(
[
str(bat_locator_bin),
'-latest',
'-prerelease',
'-requiresAny',
'-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
'-products', '*',
'-utf8',
'-format',
'json'
]
)
bat_info = json.loads(bat_json)
if not bat_info:
# VS installer instelled but not VS itself maybe?
raise MesonException(f'Could not parse vswhere.exe output')
bat_root = pathlib.Path(bat_info[0]['installationPath'])
bat_path = bat_root / 'VC/Auxiliary/Build/vcvars64.bat'
if not bat_path.exists():
raise MesonException(f'Could not find {bat_path}')
mlog.log('Activating VS', bat_info[0]['catalog']['productDisplayVersion'])
bat_separator = '---SPLIT---'
bat_contents = bat_template.format(bat_path, bat_separator)
bat_file = tempfile.NamedTemporaryFile('w', suffix='.bat', encoding='utf-8')
bat_file.write(bat_contents)
bat_file.flush()
bat_output = subprocess.check_output(str(bat_file), universal_newlines=True)
bat_lines = bat_output.split('\n')
bat_separator_seen = False
for bat_line in bat_lines:
if bat_line == bat_separator:
bat_separator_seen = True
continue
if not bat_separator_seen:
continue
if not bat_line:
continue
k, v = bat_line.split('=', 1)
os.environ[k] = v
return True
def setup_vsenv(force: bool = False):
try:
_setup_vsenv(force)
except MesonException:
if force:
raise
mlog.warning('Failed to activate VS environment:', str(e))

@ -31,96 +31,6 @@ from .mesonlib import MesonException
from .environment import detect_msys2_arch
from .wrap import wraptool
need_setup_vsenv = False
bat_template = '''@ECHO OFF
call "{}"
ECHO {}
SET
'''
# If on Windows and VS is installed but not set up in the environment,
# set it to be runnable. In this way Meson can be directly invoked
# from any shell, VS Code etc.
def setup_vsenv() -> None:
import subprocess, json, pathlib
if not mesonlib.is_windows():
return
bat_placeholder = 'nananananananananananananananana'
# If an existing build tool chain exists in PATH -> do nothing.
if shutil.which('cc'):
return
if shutil.which('gcc'):
return
if shutil.which('clang'):
return
if shutil.which('clang-cl'):
return
if os.environ.get('OSTYPE', bat_placeholder) == 'cygwin':
return
if 'Visual Studio' in os.environ['PATH']:
return
# VSINSTALL is set when running setvars from a Visual Studio installation
# Tested with Visual Studio 2012 and 2017
if 'VSINSTALLDIR' in os.environ:
return
# Check explicitly for cl when on Windows
if shutil.which('cl.exe'):
return
root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")
bat_locator_bin = pathlib.Path(root, 'Microsoft Visual Studio/Installer/vswhere.exe')
if not bat_locator_bin.exists():
return
bat_json = subprocess.check_output(
[
str(bat_locator_bin),
'-latest',
'-prerelease',
'-requiresAny',
'-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
'-products', '*',
'-utf8',
'-format',
'json'
]
)
bat_info = json.loads(bat_json)
if not bat_info:
# VS installer instelled but not VS itself maybe?
return
print('Activating VS', bat_info[0]['catalog']['productDisplayVersion'])
bat_root = pathlib.Path(bat_info[0]['installationPath'])
bat_path = bat_root / 'VC/Auxiliary/Build/vcvars64.bat'
if not bat_path.exists():
return
bat_file = pathlib.Path.home() / 'vsdetect.bat'
bat_separator = '---SPLIT---'
bat_contents = bat_template.format(bat_path, bat_separator)
bat_file.write_text(bat_contents, encoding='utf-8')
try:
bat_output = subprocess.check_output(str(bat_file), universal_newlines=True)
finally:
bat_file.unlink()
bat_lines = bat_output.split('\n')
bat_separator_seen = False
for bat_line in bat_lines:
if bat_line == bat_separator:
bat_separator_seen = True
continue
if not bat_separator_seen:
continue
if not bat_line:
continue
k, v = bat_line.split('=', 1)
os.environ[k] = v
global need_setup_vsenv
need_setup_vsenv = True
# Note: when adding arguments, please also add them to the completion
# scripts in $MESONSRC/data/shell-completions/
@ -316,7 +226,6 @@ def run(original_args, mainfile):
return CommandLineParser().run(args)
def main():
setup_vsenv()
# Always resolve the command path so Ninja can find it for regen, tests, etc.
if 'meson.exe' in sys.executable:
assert os.path.isabs(sys.executable)

@ -50,6 +50,10 @@ def add_arguments(parser: argparse.ArgumentParser) -> None:
default=[],
action='append',
help='File describing cross compilation environment.')
parser.add_argument('--vsenv', action='store_true',
help='Setup Visual Studio environment even when other compilers are found, ' +
'abort if Visual Studio is not found. This option has no effect on other ' +
'platforms than Windows. Defaults to True when using "vs" backend.')
parser.add_argument('-v', '--version', action='version',
version=coredata.version)
parser.add_argument('--profile-self', action='store_true', dest='profile',

@ -48,10 +48,9 @@ from mesonbuild import mlog
from mesonbuild import mtest
from mesonbuild.compilers import compiler_from_language, detect_objc_compiler, detect_objcpp_compiler
from mesonbuild.build import ConfigurationData
from mesonbuild.mesonlib import MachineChoice, Popen_safe, TemporaryDirectoryWinProof
from mesonbuild.mesonlib import MachineChoice, Popen_safe, TemporaryDirectoryWinProof, setup_vsenv
from mesonbuild.mlog import blue, bold, cyan, green, red, yellow, normal_green
from mesonbuild.coredata import backendlist, version as meson_version
from mesonbuild.mesonmain import setup_vsenv
from mesonbuild.modules.python import PythonExternalProgram
from run_tests import get_fake_options, run_configure, get_meson_script
from run_tests import get_backend_commands, get_backend_args_for_dir, Backend

@ -42,7 +42,7 @@ from mesonbuild import mtest
from mesonbuild import mlog
from mesonbuild.environment import Environment, detect_ninja
from mesonbuild.coredata import backendlist, version as meson_version
from mesonbuild.mesonlib import OptionKey
from mesonbuild.mesonlib import OptionKey, setup_vsenv
NINJA_1_9_OR_NEWER = False
NINJA_CMD = None
@ -382,6 +382,6 @@ def main():
return returncode
if __name__ == '__main__':
mesonmain.setup_vsenv()
setup_vsenv()
print('Meson build system', meson_version, 'Project and Unit Tests')
raise SystemExit(main())

@ -33,14 +33,10 @@ import mesonbuild.environment
import mesonbuild.mesonlib
import mesonbuild.coredata
import mesonbuild.modules.gnome
from mesonbuild.mesonlib import (
python_command
)
from mesonbuild.mesonlib import python_command, setup_vsenv
import mesonbuild.dependencies.base
import mesonbuild.modules.pkgconfig
from mesonbuild.mesonmain import setup_vsenv
from unittests.allplatformstests import AllPlatformTests
from unittests.darwintests import DarwinTests
from unittests.failuretests import FailureTests

Loading…
Cancel
Save