summaryrefslogtreecommitdiffstats
path: root/mesonbuild/utils/vsenv.py
blob: d862e5acc63873594a7a0049b339849fd9e98792 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
from __future__ import annotations

import os
import subprocess
import json
import pathlib
import shutil
import tempfile

from .. import mlog
from .universal import MesonException, is_windows, windows_detect_native_arch


__all__ = [
    'setup_vsenv',
]


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 '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 instelled 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'])
    bat_separator = '---SPLIT---'
    bat_contents = bat_template.format(bat_path, bat_separator)
    bat_file = tempfile.NamedTemporaryFile('w', suffix='.bat', encoding='utf-8', delete=False)
    bat_file.write(bat_contents)
    bat_file.flush()
    bat_file.close()
    bat_output = subprocess.check_output(bat_file.name, universal_newlines=True)
    os.unlink(bat_file.name)
    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
        try:
            k, v = bat_line.split('=', 1)
        except ValueError:
            # there is no "=", ignore junk data
            pass
        else:
            os.environ[k] = v
    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