# 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