• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env vpython3
2#
3# Copyright 2022 The ANGLE Project Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6#
7# mesa_build.py:
8#   Helper script for building Mesa in an ANGLE checkout.
9
10import argparse
11import json
12import logging
13import os
14import shutil
15import subprocess
16import sys
17
18SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
19ANGLE_DIR = os.path.dirname(os.path.dirname(SCRIPT_DIR))
20DEFAULT_LOG_LEVEL = 'info'
21EXIT_SUCCESS = 0
22EXIT_FAILURE = 1
23MESON = os.path.join(ANGLE_DIR, 'third_party', 'meson', 'meson.py')
24MESA_SOURCE_DIR = os.path.join(ANGLE_DIR, 'third_party', 'mesa', 'src')
25LIBDRM_SOURCE_DIR = os.path.join(ANGLE_DIR, 'third_party', 'libdrm')
26LIBDRM_BUILD_DIR = os.path.join(ANGLE_DIR, 'out', 'libdrm-build')
27MESA_STAMP = 'mesa.stamp'
28LIBDRM_STAMP = 'libdrm.stamp'
29
30MESA_OPTIONS = [
31    '-Dzstd=disabled',
32    '-Dplatforms=x11',
33    '-Dgallium-drivers=zink',
34    '-Dvulkan-drivers=',
35    '-Dvalgrind=disabled',
36]
37LIBDRM_OPTIONS = [
38    '-Dtests=false',
39    '-Dintel=disabled',
40    '-Dnouveau=disabled',
41    '-Damdgpu=disabled',
42    '-Dradeon=disabled',
43    '-Dvmwgfx=disabled',
44    '-Dvalgrind=disabled',
45    '-Dman-pages=disabled',
46]
47
48
49def main(raw_args):
50    parser = argparse.ArgumentParser()
51    parser.add_argument(
52        '-l',
53        '--log',
54        '--log-level',
55        help='Logging level. Default is %s.' % DEFAULT_LOG_LEVEL,
56        default=DEFAULT_LOG_LEVEL)
57
58    subparser = parser.add_subparsers(dest='command')
59
60    mesa = subparser.add_parser('mesa')
61    mesa.add_argument('build_dir', help='Target build directory.')
62    mesa.add_argument('-j', '--jobs', help='Compile jobs.')
63
64    compile_parser = subparser.add_parser('compile')
65    compile_parser.add_argument('-j', '--jobs', help='Compile jobs.')
66    compile_parser.add_argument('build_dir', help='Target build directory.')
67
68    gni = subparser.add_parser('gni')
69    gni.add_argument('output', help='Output location for gni file.')
70    gni.add_argument('mesa_build_dir', help='Target Mesa build directory.')
71    gni.add_argument('libdrm_build_dir', help='Target libdrm build directory.')
72
73    libdrm = subparser.add_parser('libdrm')
74
75    runhook_parser = subparser.add_parser('runhook')
76    runhook_parser.add_argument(
77        '-o', '--output', help='Output location for stamp sha1 file.', default=MESA_STAMP)
78
79    setup_parser = subparser.add_parser('setup')
80    setup_parser.add_argument('target', help='Project: mesa or libdrm.')
81    setup_parser.add_argument('build_dir', help='Target build directory.')
82    setup_parser.add_argument('-w', '--wipe', help='Wipe output directory.', action='store_true')
83
84    args, extra_args = parser.parse_known_args(raw_args)
85
86    logging.basicConfig(level=args.log.upper())
87
88    assert os.path.exists(MESON), 'Could not find meson.py: %s' % MESON
89
90    if args.command == 'mesa':
91        SetupBuild(args.build_dir, MESA_SOURCE_DIR, MESA_OPTIONS)
92        Compile(args, args.build_dir)
93    elif args.command == 'compile':
94        Compile(args, args.build_dir)
95    elif args.command == 'gni':
96        GenerateGni(args)
97    elif args.command == 'libdrm':
98        SetupBuild(args.build_dir, LIBDRM_SOURCE_DIR, LIBDRM_OPTIONS)
99        Compile(args, args.build_dir)
100    elif args.command == 'runhook':
101        RunHook(args)
102    elif args.command == 'setup':
103        LazySetup(args, args.build_dir)
104
105    return EXIT_SUCCESS
106
107
108def SetupBuild(build_dir, source_dir, options, pkg_config_paths=[]):
109    if not os.path.exists(build_dir):
110        os.mkdir(build_dir)
111
112    sysroot_dir = os.path.join(ANGLE_DIR, 'build', 'linux', 'debian_bullseye_amd64-sysroot')
113
114    cflags = ' '.join([
115        '--sysroot=%s' % sysroot_dir,
116        '-Wno-constant-conversion',
117        '-Wno-deprecated-builtins',
118        '-Wno-deprecated-declarations',
119        '-Wno-deprecated-non-prototype',
120        '-Wno-enum-compare-conditional',
121        '-Wno-enum-conversion',
122        '-Wno-implicit-const-int-float-conversion',
123        '-Wno-implicit-function-declaration',
124        '-Wno-initializer-overrides',
125        '-Wno-sometimes-uninitialized',
126        '-Wno-unused-but-set-variable',
127        '-Wno-unused-function',
128    ])
129
130    pkg_config_paths += [
131        '%s/usr/share/pkgconfig' % sysroot_dir,
132        '%s/usr/lib/pkgconfig' % sysroot_dir
133    ]
134
135    extra_env = {
136        'CC': 'clang',
137        'CC_LD': 'lld',
138        'CXX': 'clang++',
139        'CXX_LD': 'lld',
140        'CFLAGS': cflags,
141        'CXXFLAGS': cflags,
142        'PKG_CONFIG_PATH': ':'.join(pkg_config_paths),
143    }
144
145    args = [source_dir, build_dir, '--cross-file',
146            os.path.join(SCRIPT_DIR, 'angle_cross.ini')] + options
147    if os.path.isdir(os.path.join(build_dir, 'meson-info')):
148        args += ['--wipe']
149
150    return Meson(build_dir, 'setup', args, extra_env)
151
152
153def Compile(args, build_dir):
154    return Meson(build_dir, 'compile', ['-C', build_dir])
155
156
157def MakeEnv():
158    clang_dir = os.path.join(ANGLE_DIR, 'third_party', 'llvm-build', 'Release+Asserts', 'bin')
159    flex_bison_dir = os.path.join(ANGLE_DIR, 'tools', 'flex-bison')
160
161    # TODO: Windows
162    flex_bison_platform = 'linux'
163    flex_bison_bin_dir = os.path.join(flex_bison_dir, flex_bison_platform)
164
165    depot_tools_dir = os.path.join(ANGLE_DIR, 'third_party', 'depot_tools')
166
167    env = os.environ.copy()
168    paths = [clang_dir, flex_bison_bin_dir, depot_tools_dir, env['PATH']]
169    env['PATH'] = ':'.join(paths)
170    env['BISON_PKGDATADIR'] = os.path.join(flex_bison_dir, 'third_party')
171    return env
172
173
174GNI_TEMPLATE = """\
175# GENERATED FILE - DO NOT EDIT.
176# Generated by {script_name}
177#
178# Copyright 2022 The ANGLE Project Authors. All rights reserved.
179# Use of this source code is governed by a BSD-style license that can be
180# found in the LICENSE file.
181#
182# {filename}: ANGLE build information for Mesa.
183
184angle_mesa_outputs = [
185{angle_mesa_outputs}]
186
187angle_mesa_sources = [
188{angle_mesa_sources}]
189
190angle_libdrm_outputs = [
191{angle_libdrm_outputs}]
192
193angle_libdrm_sources = [
194{angle_libdrm_sources}]
195"""
196
197
198def GenerateGni(args):
199    mesa_sources_filter = lambda target: target['type'] != 'shared library'
200    mesa_outputs_filter = lambda target: target['type'] == 'shared library'
201    mesa_sources, mesa_outputs = GetMesonSourcesAndOutputs(args.mesa_build_dir,
202                                                           mesa_sources_filter,
203                                                           mesa_outputs_filter)
204
205    libdrm_sources_filter = lambda target: True
206    libdrm_outputs_filter = lambda target: target['type'] == 'shared library'
207    libdrm_sources, libdrm_outputs = GetMesonSourcesAndOutputs(args.libdrm_build_dir,
208                                                               libdrm_sources_filter,
209                                                               libdrm_outputs_filter)
210
211    fmt_list = lambda l, rp: ''.join(
212        sorted(list(set(['  "%s",\n' % os.path.relpath(li, rp) for li in l]))))
213
214    format_args = {
215        'script_name': os.path.basename(__file__),
216        'filename': os.path.basename(args.output),
217        'angle_mesa_outputs': fmt_list(mesa_outputs, args.mesa_build_dir),
218        'angle_mesa_sources': fmt_list(mesa_sources, MESA_SOURCE_DIR),
219        'angle_libdrm_outputs': fmt_list(libdrm_outputs, args.libdrm_build_dir),
220        'angle_libdrm_sources': fmt_list(libdrm_sources, LIBDRM_SOURCE_DIR),
221    }
222
223    gni_text = GNI_TEMPLATE.format(**format_args)
224
225    with open(args.output, 'w') as outf:
226        outf.write(gni_text)
227        outf.close()
228        logging.info('Saved GNI data to %s' % args.output)
229
230
231def GetMesonSourcesAndOutputs(build_dir, sources_filter, output_filter):
232    text_data = Meson(build_dir, 'introspect', [build_dir, '--targets'], stdout=subprocess.PIPE)
233    json_data = json.loads(text_data)
234    outputs = []
235    all_sources = []
236    generated = []
237    for target in json_data:
238        generated += target['filename']
239        if output_filter(target):
240            outputs += target['filename']
241        if sources_filter(target):
242            for target_source in target['target_sources']:
243                all_sources += target_source['sources']
244
245    sources = list(filter(lambda s: (s not in generated), all_sources))
246
247    for source in sources:
248        assert os.path.exists(source), '%s does not exist' % source
249
250    return sources, outputs
251
252
253def Meson(build_dir, command, args, extra_env={}, stdout=None):
254    meson_cmd = [MESON, command] + args
255    env = MakeEnv()
256    for k, v in extra_env.items():
257        env[k] = v
258    # TODO: Remove when crbug.com/1373441 is fixed.
259    env['VPYTHON_DEFAULT_SPEC'] = os.path.join(ANGLE_DIR, '.vpython3')
260    logging.info(' '.join(['%s=%s' % (k, v) for (k, v) in extra_env.items()] + meson_cmd))
261    completed = subprocess.run(meson_cmd, env=env, stdout=stdout)
262    if completed.returncode != EXIT_SUCCESS:
263        logging.fatal('Got error from meson:')
264        with open(os.path.join(build_dir, 'meson-logs', 'meson-log.txt')) as logf:
265            lines = logf.readlines()
266            for line in lines[-10:]:
267                logging.fatal('  %s' % line.strip())
268        sys.exit(EXIT_FAILURE)
269    if stdout:
270        return completed.stdout
271
272
273def RunHook(args):
274    output = os.path.join(SCRIPT_DIR, args.output)
275    Stamp(args, MESA_SOURCE_DIR, output)
276    libdrm_out = os.path.join(SCRIPT_DIR, LIBDRM_STAMP)
277    Stamp(args, LIBDRM_SOURCE_DIR, libdrm_out)
278
279
280def Stamp(args, source_dir, output):
281    commit_id = GrabOutput('git rev-parse HEAD', source_dir)
282    with open(output, 'w') as outf:
283        outf.write(commit_id)
284        outf.close()
285        logging.info('Saved git hash data to %s' % output)
286
287
288def GrabOutput(command, cwd):
289    return subprocess.Popen(
290        command, stdout=subprocess.PIPE, shell=True, cwd=cwd).communicate()[0].strip().decode()
291
292
293def LazySetup(args, build_dir):
294    stamp = args.target + '.stamp'
295    in_stamp = os.path.join(SCRIPT_DIR, stamp)
296    out_stamp = os.path.join(build_dir, args.target, stamp)
297    if not args.wipe and SameStamps(in_stamp, out_stamp):
298        logging.info('%s setup up-to-date.' % args.target)
299        sys.exit(EXIT_SUCCESS)
300
301    if args.target == 'mesa':
302        source_dir = MESA_SOURCE_DIR
303        options = MESA_OPTIONS
304        pkg_config_paths = [os.path.join(build_dir, 'libdrm', 'meson-uninstalled')]
305    else:
306        assert (args.target == 'libdrm')
307        source_dir = LIBDRM_SOURCE_DIR
308        options = LIBDRM_OPTIONS
309        pkg_config_paths = []
310
311    SetupBuild(os.path.join(build_dir, args.target), source_dir, options, pkg_config_paths)
312    shutil.copyfile(in_stamp, out_stamp)
313    logging.info('Finished setup and updated %s.' % out_stamp)
314
315
316def SameStamps(in_stamp, out_stamp):
317    assert os.path.exists(in_stamp)
318    if not os.path.exists(out_stamp):
319        return False
320    in_data = ReadFile(in_stamp)
321    out_data = ReadFile(out_stamp)
322    return in_data == out_data
323
324
325def ReadFile(path):
326    with open(path, 'rt') as inf:
327        all_data = inf.read()
328        inf.close()
329        return all_data
330
331
332if __name__ == "__main__":
333    sys.exit(main(sys.argv[1:]))
334