env.py 8.49 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# cerbero - a multi-platform build system for Open Source software
# Copyright (C) 2016 Nirbheek Chauhan <nirbheek@centricular.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

import os
import subprocess
21
from pathlib import Path
22
from functools import lru_cache
23

24
from cerbero.enums import Architecture
25
from cerbero.errors import FatalError
26
from cerbero.utils.shell import check_output
27
28

# We only support Visual Studio 2015 as of now
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
VCVARSALLS = {
    'vs14': (
        (
            r'Microsoft Visual Studio 14.0',
        ),
        r'VC\vcvarsall.bat'
    ),
    'vs15': (
        (
            r'Microsoft Visual Studio\2017\Community',
            r'Microsoft Visual Studio\2017\Professional',
            r'Microsoft Visual Studio\2017\Enterprise',
            r'Microsoft Visual Studio\2017\Preview',
        ),
        r'VC\Auxiliary\Build\vcvarsall.bat'
    ),
    'vs16': (
        (
            r'Microsoft Visual Studio\2019\Community',
            r'Microsoft Visual Studio\2019\Professional',
            r'Microsoft Visual Studio\2019\Enterprise',
            r'Microsoft Visual Studio\2019\BuildTools',
            r'Microsoft Visual Studio\2019\Preview',
        ),
        r'VC\Auxiliary\Build\vcvarsall.bat'
    ),
55
}
56
57
58
59
60
61
62
63
64

def get_program_files_dir():
    if 'PROGRAMFILES(X86)' in os.environ:
        # Windows 64-bit
        return Path(os.environ['PROGRAMFILES(X86)'])
    elif 'PROGRAMFILES' in os.environ:
        # Windows 32-bit
        return Path(os.environ['PROGRAMFILES'])
    raise FatalError('Could not find path to 32-bit Program Files directory')
65

66
def get_vs_year_version(vcver):
67
68
69
70
    if vcver == 'vs14':
        return '2015'
    if vcver == 'vs15':
        return '2017'
71
72
    if vcver == 'vs16':
        return '2019'
73
74
    raise RuntimeError('Unknown toolset value {!r}'.format(vcver))

75
76
77
78
79
80
81
82
83
84
def _get_custom_vs_install(vs_version, vs_install_path):
    path = Path(vs_install_path)
    if not path.is_dir():
        raise FatalError('vs_install_path {!r} is not a directory'.format(path))
    suffix = VCVARSALLS[vs_version][1]
    path = path / suffix
    if not path.is_file():
        raise FatalError('Can\'t find vcvarsall.bat inside vs_install_path {!r}'.format(path))
    return path.as_posix(), vs_version

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
def _get_vswhere_vs_install(vswhere, vs_versions):
    import json
    vswhere_exe = str(vswhere)
    # Get a list of installation paths for all installed Visual Studio
    # instances, from VS 2013 to the latest one, sorted from newest to
    # oldest, and including preview releases.
    # Will not include BuildTools installations.
    out = check_output([vswhere_exe, '-legacy', '-prerelease', '-format',
                        'json', '-utf8', '-sort'])
    installs = json.loads(out)
    program_files = get_program_files_dir()
    for install in installs:
        version = install['installationVersion']
        vs_version = 'vs' + version.split('.', maxsplit=1)[0]
        if vs_version not in vs_versions:
            continue
        prefix = install['installationPath']
        suffix = VCVARSALLS[vs_version][1]
        path = program_files / prefix / suffix
        # Find the location of the Visual Studio installation
        if path.is_file():
            return path.as_posix(), vs_version
    raise FatalError('vswhere.exe could not find Visual Studio installation(s). '
                     'Looked for version(s): ' + ', '.join(vs_versions))

110
111
112
113
114
115
116
117
118
119
120
121
122
def get_vcvarsall(vs_version, vs_install_path):
    known_vs_versions = sorted(VCVARSALLS.keys(), reverse=True)
    if vs_version:
        if vs_version not in VCVARSALLS:
            raise FatalError('Requested Visual Studio version {} is not one of: '
                             '{}'.format(vs_version, ', '.join(known_vs_versions)))
    # Do we want to use a specific known Visual Studio installation?
    if vs_install_path:
        assert(vs_version)
        return _get_custom_vs_install(vs_version, vs_install_path)
    # Start searching.
    if vs_version:
        vs_versions = [vs_version]
123
    else:
124
125
126
        # If no specific version was requested, look for all known versions and
        # pick the newest one found.
        vs_versions = known_vs_versions
127
    program_files = get_program_files_dir()
128
129
130
131
132
133
134
135
136
137
    # Try to find using vswhere.exe
    # vswhere is installed by Visual Studio 2017 and newer into a fixed
    # location, and can also be installed separately. For others:
    # - Visual Studio 2013 (can be found by vswhere -legacy, but we don't use it)
    # - Visual Studio 2015 (can be found by vswhere -legacy)
    # - Visual Studio 2019 Build Tools (cannot be found by vswhere)
    vswhere = program_files / 'Microsoft Visual Studio' / 'Installer' / 'vswhere.exe'
    if vswhere.is_file():
        return _get_vswhere_vs_install(vswhere, vs_versions)
    # Look in the default locations if vswhere.exe is not available.
138
139
140
141
    for vs_version in vs_versions:
        prefixes, suffix = VCVARSALLS[vs_version]
        for prefix in prefixes:
            path = program_files / prefix / suffix
142
143
            # Find the location of the Visual Studio installation
            if path.is_file():
144
145
146
                return path.as_posix(), vs_version
    raise FatalError('Microsoft Visual Studio not found. If you installed it, '
                     'please file a bug. We looked for: ' + ', '.join(versions))
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163

def append_path(var, path, sep=';'):
    if var and not var.endswith(sep):
        var += sep
    if path and not path.endswith(sep):
        path += sep
    var += path
    return var

def get_vcvarsall_arg(arch, target_arch):
    if target_arch == Architecture.X86:
        # If arch is x86_64, this will cause the WOW64 version of MSVC to be
        # used, which is how most people compile 32-bit apps on 64-bit.
        return 'x86'
    if arch == Architecture.X86:
        if target_arch == Architecture.X86_64:
            return 'x86_amd64'
164
165
        elif target_arch == Architecture.ARM64:
            return 'x86_arm64'
166
167
168
169
170
        elif target_arch.is_arm():
            return 'x86_arm'
    elif arch == Architecture.X86_64:
        if target_arch == Architecture.X86_64:
            return 'amd64'
171
172
173
        elif target_arch == Architecture.ARM64:
            return 'amd64_arm64'
        elif Architecture.is_arm(target_arch):
174
            return 'amd64_arm'
175
176
177
178
    # FIXME: Does Visual Studio support arm/arm64 as build machines?
    elif arch == Architecture.ARM64 and target_arch == Architecture.ARM64:
        return 'arm64'
    elif Architecture.is_arm(arch) and Architecture.is_arm(target_arch):
179
180
181
182
            return 'arm'
    raise FatalError('Unsupported arch/target_arch: {0}/{1}'.format(arch, target_arch))

def run_and_get_env(cmd):
183
184
185
186
187
188
189
190
191
192
    env = os.environ.copy()
    env['VSCMD_ARG_no_logo'] = '1'
    env['VSCMD_DEBUG'] = ''
    output = subprocess.check_output(cmd, shell=True, env=env,
                                     universal_newlines=True)
    lines = []
    for line in output.split('\n'):
        if '=' in line:
            lines.append(line)
    return lines
193

194
195
196
197
198
# For a specific env var, get only the values that were prepended to it by MSVC
def get_envvar_msvc_values(msvc, nomsvc, sep=';'):
    index = msvc.index(nomsvc)
    return msvc[0:index]

199
@lru_cache()
200
def get_msvc_env(arch, target_arch, version=None, vs_install_path=None):
201
    ret_env = {}
202
    vcvarsall, vsver = get_vcvarsall(version, vs_install_path)
203
204
205
206
207
208

    without_msvc = run_and_get_env('set')
    arg = get_vcvarsall_arg(arch, target_arch)
    vcvars_env_cmd = '"{0}" {1} & {2}'.format(vcvarsall, arg, 'set')
    with_msvc = run_and_get_env(vcvars_env_cmd)

209
210
211
    msvc = dict([each.split('=', 1) for each in with_msvc])
    nomsvc = dict([each.split('=', 1) for each in without_msvc])

212
    for each in set(with_msvc) - set(without_msvc):
213
214
215
216
217
218
        var = each.split('=', 1)[0]
        # Use the new values for all vars, except PATH
        if var == 'PATH':
            ret_env[var] = get_envvar_msvc_values(msvc[var], nomsvc[var])
        else:
            ret_env[var] = msvc[var]
219
    return ret_env, vsver