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