blob: 7456453aa2802b854eeb1ff882b5f46711bd09e2 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import json
import os
import platform
import re
import shutil
import subprocess
import sys
CMAKE_DEFAULTS = [
'-DCMAKE_EXPORT_COMPILE_COMMANDS=ON', '-GNinja',
'-DLLVM_DEFAULT_TARGET_TRIPLE=wasm32-unknown-unknown',
'-DLLVM_ENABLE_ZLIB=OFF', '-DLLVM_ENABLE_TERMINFO=OFF',
'-DLLDB_ENABLE_CURSES=OFF', '-DLLDB_ENABLE_LIBXML2=OFF',
'-DLLDB_ENABLE_LZMA=OFF'
]
def devtools_dir(source_dir):
return os.path.dirname(os.path.dirname(source_dir))
def node_path(source_dir):
return os.path.join(
devtools_dir(source_dir), 'third_party', 'node', *{
'Darwin': ('mac', 'node-darwin-x64', 'bin'),
'Linux': ('linux', 'node-linux-x64', 'bin'),
'Windows': ('win', ),
}[platform.system()])
def is_windows():
return sys.platform == 'cygwin' or sys.platform.startswith('win')
def exec_extension():
return ".exe" if is_windows() else ""
def call(cmd, verbose=False, **kwargs):
if verbose:
sys.stderr.write("Running '{}' ({})\n".format(
' '.join(cmd),
', '.join('{}={}'.format(k, v) for k, v in kwargs.items())))
subprocess.check_call(cmd, **kwargs)
def stage1(sysroot_dir, source_dir, OPTIONS):
sys.stdout.write('Building Stage 1.\n')
binary_dir = os.path.abspath(
os.path.join(OPTIONS.build_dir, 'DevTools_CXX_Debugging.stage1'))
if not os.path.exists(binary_dir):
os.makedirs(binary_dir)
emscripten_install_dir = os.path.join(devtools_dir(source_dir),
'third_party',
'emscripten-releases', 'install')
libcxx_dir = os.path.join(emscripten_install_dir, 'lib')
cmake_settings = {
'build_shared': 'OFF' if OPTIONS.static else 'ON',
'libcxx_dir': libcxx_dir,
}
cmake_args = [
OPTIONS.cmake,
OPTIONS.extension_source,
*CMAKE_DEFAULTS,
'-DBUILD_SHARED_LIBS={build_shared}'.format(**cmake_settings),
'-DCMAKE_BUILD_TYPE=Release',
'-DCMAKE_CXX_FLAGS=-stdlib=libc++ -pthread -I{libcxx_dir}/include/c++/v1'
.format(**cmake_settings),
'-DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++ -L{libcxx_dir} -fuse-ld=lld'.
format(**cmake_settings),
]
if not OPTIONS.no_sysroot:
cmake_args.extend(('-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY',
'-DCMAKE_SYSROOT={}'.format(sysroot_dir)))
if OPTIONS.cc:
cmake_args.append('-DCMAKE_C_COMPILER={}'.format(OPTIONS.cc))
if OPTIONS.cxx:
cmake_args.append('-DCMAKE_CXX_COMPILER={}'.format(OPTIONS.cxx))
maybe_cmake(binary_dir, cmake_args, OPTIONS.verbose)
ninja = os.path.join(devtools_dir(source_dir), 'third_party', 'ninja',
'ninja')
call([
ninja, 'lldb-tblgen', 'clang-tblgen', 'llvm-tblgen', 'llvm-dwp',
'llvm-mc'
],
verbose=OPTIONS.verbose,
cwd=binary_dir)
return binary_dir
def stage2(source_dir, stage1_dir, OPTIONS):
sys.stdout.write('Building Stage 2.\n')
llvm_tools_dir = os.path.abspath(
os.path.join(stage1_dir, 'third_party', 'llvm', 'src', 'llvm', 'bin'))
emcc = os.path.join(devtools_dir(source_dir), 'third_party',
'emscripten-releases', 'install', 'emscripten', 'emcc')
wasm_ld_dir = os.path.join(devtools_dir(source_dir), 'third_party',
'emscripten-releases', 'install', 'bin')
binary_dir = os.path.abspath(
os.path.join(OPTIONS.build_dir, 'DevTools_CXX_Debugging.stage2'))
if not os.path.exists(binary_dir):
os.makedirs(binary_dir)
cmake_settings = {
'toolchain_file':
os.path.join(os.path.dirname(emcc), 'cmake', 'Modules', 'Platform',
'Emscripten.cmake'),
'wasm_ld':
os.path.join(wasm_ld_dir, 'wasm-ld' + exec_extension()),
'llvm_dwp':
os.path.join(llvm_tools_dir, 'llvm-dwp' + exec_extension()),
'llvm_tblgen':
os.path.join(llvm_tools_dir, 'llvm-tblgen' + exec_extension()),
'clang_tblgen':
os.path.join(llvm_tools_dir, 'clang-tblgen' + exec_extension()),
'lldb_tblgen':
os.path.join(llvm_tools_dir, 'lldb-tblgen' + exec_extension()),
'build_type':
_build_type(OPTIONS),
}
cmake_args = [
OPTIONS.cmake, OPTIONS.extension_source, *CMAKE_DEFAULTS,
'-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=-O1 -g -DNDEBUG',
'-DCMAKE_C_FLAGS_RELWITHDEBINFO=-O1 -g -DNDEBUG',
'-DCMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO=-O1 -g -DNDEBUG -gseparate-dwarf',
'-DCMAKE_CXX_FLAGS_DEBUG=-O0 -g -DNDEBUG',
'-DCMAKE_EXE_LINKER_FLAGS_DEBUG=-O0 -g -gseparate-dwarf',
'-DHAVE_POSIX_REGEX=0', '-Derrc_exit_code=0',
'-Derrc_exit_code__TRYRUN_OUTPUT=0',
'-DCMAKE_BUILD_TYPE={build_type}'.format(**cmake_settings),
'-DCMAKE_TOOLCHAIN_FILE={toolchain_file}'.format(**cmake_settings),
'-DLLVM_DWP={llvm_dwp}'.format(**cmake_settings),
'-DLLVM_TABLEGEN={llvm_tblgen}'.format(**cmake_settings),
'-DCLANG_TABLEGEN={clang_tblgen}'.format(**cmake_settings),
'-DLLDB_TABLEGEN={lldb_tblgen}'.format(**cmake_settings),
'-DWASM_LD={wasm_ld}'.format(**cmake_settings)
]
if is_windows():
cmake_args.append('-DLLVM_HOST_TRIPLE=x86_64')
if OPTIONS.split_dwarf:
cmake_args.extend([
'-DCXX_DEBUGGING_USE_SPLIT_DWARF=ON',
'-DLLVM_USE_SPLIT_DWARF=ON',
])
else:
cmake_args.extend([
'-DCXX_DEBUGGING_USE_SPLIT_DWARF=OFF',
'-DLLVM_USE_SPLIT_DWARF=OFF',
])
if OPTIONS.gdwarf_5:
cmake_args.extend(['-DCXX_DEBUGGING_ENABLE_DWARF5=ON'])
else:
cmake_args.extend(['-DCXX_DEBUGGING_ENABLE_DWARF5=OFF'])
if OPTIONS.pubnames:
cmake_args.extend(['-DCXX_DEBUGGING_ENABLE_PUBNAMES=ON'])
else:
cmake_args.extend(['-DCXX_DEBUGGING_ENABLE_PUBNAMES=OFF'])
if OPTIONS.skip_dwp:
cmake_args.extend(['-DCXX_DEBUGGING_DWO_ONLY=ON'])
else:
cmake_args.extend(['-DCXX_DEBUGGING_DWO_ONLY=OFF'])
if OPTIONS.release_version or OPTIONS.release:
cmake_args.extend([
'-DCXX_DEBUGGING_BUILD_REVISION={0}'.format(OPTIONS.release_version
or 0)
])
cmake_args.extend([
'-DCXX_DEBUGGING_BUILD_PATCH={0}'.format(OPTIONS.patch_level or 0)
])
if OPTIONS.sanitize:
cmake_args.append('-DCXX_DEBUGGING_USE_SANITIZERS=ON')
else:
cmake_args.append('-DCXX_DEBUGGING_USE_SANITIZERS=OFF')
maybe_cmake(binary_dir, cmake_args, OPTIONS.verbose)
num_cores = os.cpu_count()
env = os.environ.copy()
ninja = os.path.join(devtools_dir(source_dir), 'third_party', 'ninja',
'ninja')
if not OPTIONS.no_check:
call([ninja, '-j%d' % num_cores, 'all', 'check-extension'],
verbose=OPTIONS.verbose,
cwd=binary_dir,
env=env)
else:
call([ninja, '-j%d' % num_cores, 'all'],
verbose=OPTIONS.verbose,
cwd=binary_dir,
env=env)
return binary_dir
def maybe_cmake(binary_dir, cmake_args, verbose):
# Re-run `cmake` when the build.ninja doesn't exist, or when cmake args have changed
build_ninja = os.path.abspath(os.path.join(binary_dir, 'build.ninja'))
cmake_args_file = os.path.abspath(
os.path.join(binary_dir, 'cmake.args.json'))
if os.path.exists(build_ninja) and os.path.exists(cmake_args_file):
with open(cmake_args_file, 'r') as f:
try:
prev_args = json.load(f)
if prev_args == cmake_args:
# Nothing changed; cmake unnecessary
return
except json.JSONDecodeError:
pass # Ignore invalid json file
sys.stdout.write('cmake args have changed.\n')
call(cmake_args, verbose=verbose, cwd=binary_dir)
with open(cmake_args_file, 'w') as f:
json.dump(cmake_args, f)
def _build_type(options):
if options.release or options.release_version:
return 'Release'
elif options.debug:
return 'Debug'
else:
return 'RelWithDebInfo'
def script_main(args):
source_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
repo_dir = os.path.dirname(os.path.dirname(source_dir))
third_party = os.path.join(repo_dir, 'third_party')
clang_dir = os.path.join(third_party, 'emscripten-releases', 'install',
'bin')
cmake_dir = os.path.join(third_party, 'cmake', 'bin')
sysroot_dir = find_sysroot(repo_dir)
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('-cmake',
default=shutil.which('cmake', path=cmake_dir),
help='Path to the cmake configure tool.')
parser.add_argument('-cc',
default=shutil.which('clang', path=clang_dir),
help='The C compiler.')
parser.add_argument('-cxx',
default=shutil.which('clang++', path=clang_dir),
help='The C++ compiler.')
parser.add_argument('-extension-source',
default=source_dir,
help='Path to alternate repo for source.')
parser.add_argument('-verbose', action='store_true')
parser.add_argument('-stage1', help='Path to a pre-built stage 1')
parser.add_argument('-static',
action='store_true',
default=True,
help='Link the first stage statically.')
parser.add_argument('-dynamic',
action='store_false',
default=True,
dest='static',
help='Link the first stage dynamically.')
parser.add_argument('-no-sysroot',
action='store_true',
help='Disable sysroot.')
parser.add_argument('-no-check',
action='store_true',
help='Skip running tests.')
parser.add_argument('-infra',
action='store_true',
help='Configure the build for the buildbots.')
parser.add_argument('-debug',
action='store_true',
help='Build a debug version.')
parser.add_argument(
'-release',
action='store_true',
help='Build a release instead of a debug version. (deprecated)')
parser.add_argument(
'-release-version',
type=int,
default=None,
help='Provide a version number for building a release,'
'instead of building a debug version. (deprecates -release)')
parser.add_argument(
'-patch-level',
type=int,
default=0,
help='Provide a version patch level for building a release,'
'instead of building a debug version. (deprecates -release)')
parser.add_argument('-split-dwarf',
action='store_true',
help='Build with split-dwarf support.')
parser.add_argument('-pubnames',
action='store_true',
help='Build with split-dwarf support.')
parser.add_argument('-gdwarf-5',
action='store_true',
help='Build with DWARF5 debug info.')
parser.add_argument(
'-skip-dwp',
action='store_true',
help='In combination with -split-dwarf, builds dwos but not the dwp.')
parser.add_argument('-sanitize',
action='store_true',
help='Enable sanitizers')
parser.add_argument('build_dir')
OPTIONS = parser.parse_args(args)
if OPTIONS.infra:
OPTIONS.static = True
OPTIONS.verbose = True
if OPTIONS.no_check:
sys.stderr.write('-infra overrides -no-check')
OPTIONS.no_check = False
if OPTIONS.no_sysroot:
sys.stderr.write('-infra overrides -no-sysroot')
OPTIONS.no_sysroot = False
if OPTIONS.stage1:
stage1_dir = OPTIONS.stage1
else:
stage1_dir = stage1(sysroot_dir, source_dir, OPTIONS)
stage2(source_dir, stage1_dir, OPTIONS)
def find_sysroot(repo_dir):
build_linux = os.path.join(repo_dir, 'build', 'linux')
sysroots = [
os.path.join(build_linux, f) for f in os.listdir(build_linux)
if os.path.isdir(os.path.join(build_linux, f))
and re.match('debian_.*_amd64-sysroot', f)
]
assert len(sysroots) >= 1, 'No sysroot found!'
assert len(sysroots) <= 1, 'Too many sysroots found!'
return sysroots[0]
if __name__ == '__main__':
script_main(sys.argv[1:])