| # SPDX-License-Identifier: Apache-2.0 |
| # Copyright 2012-2025 The Meson development team |
| |
| from __future__ import annotations |
| |
| import os |
| import subprocess |
| import json |
| import pathlib |
| import shutil |
| import tempfile |
| import pickle |
| |
| from .. import mlog |
| from .core import MesonException |
| from .universal import (is_windows, windows_detect_native_arch, windows_proof_rm, |
| get_meson_command, join_args) |
| |
| |
| __all__ = [ |
| 'setup_vsenv', |
| ] |
| |
| |
| # 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 'MESON_FORCE_VSENV_FOR_UNITTEST' not in os.environ: |
| # 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', |
| '-requires', 'Microsoft.VisualStudio.Workload.WDExpress', |
| '-products', '*', |
| '-utf8', |
| '-format', |
| 'json' |
| ] |
| ) |
| bat_info = json.loads(bat_json) |
| if not bat_info: |
| # VS installer installed but not VS itself maybe? |
| raise MesonException('Could not parse vswhere.exe output') |
| bat_root = pathlib.Path(bat_info[0]['installationPath']) |
| if windows_detect_native_arch() == 'arm64': |
| bat_path = bat_root / 'VC/Auxiliary/Build/vcvarsarm64.bat' |
| if not bat_path.exists(): |
| bat_path = bat_root / 'VC/Auxiliary/Build/vcvarsx86_arm64.bat' |
| else: |
| bat_path = bat_root / 'VC/Auxiliary/Build/vcvars64.bat' |
| # if VS is not found try VS Express |
| if not bat_path.exists(): |
| bat_path = bat_root / 'VC/Auxiliary/Build/vcvarsx86_amd64.bat' |
| if not bat_path.exists(): |
| raise MesonException(f'Could not find {bat_path}') |
| |
| mlog.log('Activating VS', bat_info[0]['catalog']['productDisplayVersion']) |
| # Write a bat file that first activates VS environment, and then calls |
| # a Meson script that pickles the environment into a temp file. |
| with tempfile.NamedTemporaryFile(delete=False) as env_file: |
| pass |
| vcvars_cmd = ['call', str(bat_path)] |
| pickle_cmd = get_meson_command() + ['--internal', 'pickle_env', env_file.name] |
| with tempfile.NamedTemporaryFile('w', suffix='.bat', encoding='utf-8', delete=False) as bat_file: |
| bat_file.write(join_args(vcvars_cmd) + '\n') |
| bat_file.write(join_args(pickle_cmd)) |
| try: |
| subprocess.check_call([bat_file.name], stdout=subprocess.DEVNULL) |
| with open(env_file.name, 'rb') as f: |
| vsenv = pickle.load(f) |
| for k, v in vsenv.items(): |
| os.environ[k] = v |
| finally: |
| windows_proof_rm(env_file.name) |
| windows_proof_rm(bat_file.name) |
| return True |
| |
| def setup_vsenv(force: bool = False) -> bool: |
| try: |
| return _setup_vsenv(force) |
| except MesonException as e: |
| if force: |
| raise |
| mlog.warning('Failed to activate VS environment:', str(e)) |
| return False |