blob: 3cc9b4b9ce6dc26e41b4a25e1dcbc428fdcd75a4 [file]
# 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