| from __future__ import print_function |
| |
| import util |
| |
| import argparse |
| import errno |
| import os |
| import re |
| import shutil |
| import subprocess |
| import sys |
| |
| from os.path import join as pjoin |
| |
| VSWHERE_PATH = "C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" |
| |
| def get_argument_parser(*args, **kwargs): |
| ap = argparse.ArgumentParser(*args, **kwargs) |
| ap.add_argument('--jobs', help='Number of concurrent jobs to run') |
| return ap |
| |
| |
| class AnnotatedBuilder: |
| |
| """ |
| Builder implementation that can be used with Buildbot's AnnotatedCommand. |
| Usage: |
| builder = AnnotatedBuilder() |
| builder.run_steps() |
| |
| See run_steps() for parameters that can be passed to alter the behavior. |
| """ |
| |
| def halt_on_failure(self): |
| util.report('@@@HALT_ON_FAILURE@@@') |
| |
| def report_build_step(self, step): |
| util.report('@@@BUILD_STEP %s@@@' % (step,)) |
| |
| def report_step_exception(self, exn=None): |
| # Don't print a stack trace if a command ('ninja check') exited with a |
| # non-zero exit code. That is non-exceptional expected behavior, so just |
| # print the return code and fail the step. |
| if exn and isinstance(exn, subprocess.CalledProcessError): |
| cmd = "" |
| try: |
| cmd = repr(exn.cmd[0]) |
| except: |
| pass |
| util.report("Command " + cmd + " failed with return code " + |
| str(exn.returncode)) |
| util.report('@@@STEP_FAILURE@@@') |
| return |
| |
| if exn: |
| util.report(str(exn)) |
| util.report('@@@STEP_EXCEPTION@@@') |
| |
| def build_and_check_stage( |
| self, |
| stage, |
| build_dir, |
| source_dir, |
| cmake_args, |
| check_targets=None, |
| clean=True, |
| jobs=None): |
| stage_name = 'stage %s' % (stage,) |
| if clean: |
| self.clean_build_dir(stage_name, build_dir) |
| self.cmake(stage_name, build_dir, source_dir, cmake_args=cmake_args) |
| self.build(stage_name, build_dir, jobs) |
| if check_targets is not None: |
| self.check(stage_name, build_dir, check_targets, jobs) |
| |
| def build_and_check_stages( |
| self, |
| stages, |
| build_dir, |
| source_dir, |
| cmake_args, |
| extra_cmake_args, |
| c_compiler, |
| cxx_compiler, |
| linker, |
| check_stages, |
| check_targets, |
| stage1_extra_cmake_args, |
| jobs=None): |
| if jobs: |
| cmake_args = [ '-DLLVM_LIT_ARGS=-sv -j %s' % (jobs,) ] + cmake_args |
| for stage in range(1, stages + 1): |
| stage_build_dir = pjoin(build_dir, 'stage%s' % (stage,)) |
| if stage == 1: |
| s_cmake_args = cmake_args + stage1_extra_cmake_args |
| stage_clean = str( |
| os.environ.get('BUILDBOT_CLOBBER', '')) != '' |
| else: |
| previous_stage_bin = pjoin( |
| build_dir, 'stage%s' % (stage - 1,), 'bin') |
| s_cmake_args = self.stage_cmake_args( |
| cmake_args, |
| extra_cmake_args, |
| c_compiler, |
| cxx_compiler, |
| linker, |
| previous_stage_bin) |
| stage_clean = True |
| if check_stages[stage - 1]: |
| stage_check_targets = check_targets |
| else: |
| stage_check_targets = None |
| self.build_and_check_stage( |
| stage, |
| stage_build_dir, |
| source_dir, |
| s_cmake_args, |
| stage_check_targets, |
| stage_clean, |
| jobs) |
| |
| def build(self, stage_name, build_dir, jobs=None): |
| self.report_build_step('%s build' % (stage_name,)) |
| self.halt_on_failure() |
| cmd = ['ninja'] |
| if jobs: |
| cmd += ['-j', str(jobs)] |
| util.report_run_cmd(cmd, cwd=build_dir) |
| |
| def check(self, stage_name, build_dir, check_targets, jobs=None): |
| self.report_build_step('%s check' % (stage_name,)) |
| self.halt_on_failure() |
| cmd = ['ninja'] |
| if jobs: |
| cmd += ['-j', str(jobs)] |
| cmd += check_targets |
| util.report_run_cmd(cmd, cwd=build_dir) |
| |
| def clean_build_dir(self, stage_name, build_dir): |
| self.report_build_step('%s clean' % (stage_name,)) |
| self.halt_on_failure() |
| try: |
| util.clean_dir(build_dir) |
| except Exception as e: |
| self.report_step_exception(e) |
| raise |
| |
| def cmake( |
| self, |
| stage_name, |
| build_dir, |
| source_dir, |
| cmake='cmake', |
| cmake_args=None): |
| self.report_build_step('%s cmake' % (stage_name,)) |
| self.halt_on_failure() |
| cmd = [cmake] |
| if cmake_args is not None: |
| cmd += cmake_args |
| cmd += [util.cmake_pjoin(source_dir, 'llvm')] |
| util.mkdirp(build_dir) |
| util.report_run_cmd(cmd, cwd=build_dir) |
| |
| def cmake_compiler_flags( |
| self, |
| c_compiler, |
| cxx_compiler, |
| linker=None, |
| path=None): |
| c_compiler_flag = '-DCMAKE_C_COMPILER=%s' % ( |
| util.cmake_pjoin(path, c_compiler),) |
| cxx_compiler_flag = '-DCMAKE_CXX_COMPILER=%s' % ( |
| util.cmake_pjoin(path, cxx_compiler),) |
| if linker is None: |
| linker_flag = '' |
| else: |
| linker_flag = '-DCMAKE_LINKER=%s' % ( |
| util.cmake_pjoin(path, linker),) |
| if os.name == 'nt': |
| c_compiler_flag += '.exe' |
| cxx_compiler_flag += '.exe' |
| linker_flag += '.exe' |
| flags = [ |
| c_compiler_flag, |
| cxx_compiler_flag, |
| ] |
| if linker is not None: |
| flags += [ |
| linker_flag, |
| ] |
| return flags |
| |
| def compiler_binaries(self, compiler): |
| """ |
| Given a symbolic compiler name, return a tuple |
| (c_compiler, cxx_compiler) with the names of the binaries |
| to invoke. |
| """ |
| if compiler == 'clang': |
| return ('clang', 'clang++') |
| elif compiler == 'clang-cl': |
| return ('clang-cl', 'clang-cl') |
| else: |
| raise ValueError('Unsupported compiler type: %s' % (compiler,)) |
| |
| def stage_cmake_args( |
| self, |
| cmake_args, |
| extra_cmake_args, |
| c_compiler, |
| cxx_compiler, |
| linker, |
| previous_stage_bin): |
| return ( |
| cmake_args + [ |
| '-DCMAKE_AR=%s' % ( |
| util.cmake_pjoin(previous_stage_bin, 'llvm-ar'),), |
| '-DCMAKE_RANLIB=%s' % ( |
| util.cmake_pjoin(previous_stage_bin, 'llvm-ranlib'),), |
| ] + self.cmake_compiler_flags( |
| c_compiler, cxx_compiler, linker, previous_stage_bin) + |
| extra_cmake_args) |
| |
| def set_environment(self, env=None, vs_tools=None, arch=None): |
| self.report_build_step('set-environment') |
| try: |
| new_env = { |
| 'TERM': 'dumb', |
| } |
| if os.name == 'nt': |
| new_env.update(get_vcvars(vs_tools, arch)) |
| |
| if env is not None: |
| new_env.epdate(env) |
| |
| for (var, val) in new_env.items(): |
| os.environ[var] = val |
| |
| for var in sorted(os.environ.keys()): |
| util.report('%s=%s' % (var, os.environ[var])) |
| except Exception as e: |
| self.report_step_exception(e) |
| raise |
| |
| def run_steps( |
| self, |
| stages=1, |
| projects=None, |
| check_targets=None, |
| check_stages=None, |
| extra_cmake_args=None, |
| stage1_extra_cmake_args=None, |
| compiler='clang', |
| linker='ld.lld', |
| env=None, |
| jobs=None): |
| """ |
| stages: number of stages to run (default: 1) |
| projects: which subprojects to enable |
| llvm must be first in the list (default: ['llvm', 'clang', 'lld']) |
| check_targets: targets to run during the check phase (default: ['check-all']) |
| check_stages: stages for which to run the check phase |
| (array of bool, default: all True) |
| extra_cmake_args: extra arguments to pass to cmake (default: []) |
| stage1_extra_cmake_args: extra arguments to pass to cmake for stage 1 |
| (default: use extra_cmake_args) |
| compiler: compiler to use after stage 1 |
| ('clang' or 'clang-cl'; default 'clang') |
| linker: linker to use after stage 1 |
| (None (let cmake choose) or 'lld' (default)) |
| env: environment overrides (map; default is no overrides) |
| jobs: number of jobs to run concurrently (default: determine automatically) |
| """ |
| |
| # Set defaults. |
| if check_targets is None: |
| check_targets = ['check-all'] |
| if check_stages is None: |
| check_stages = [True] * stages |
| if extra_cmake_args is None: |
| extra_cmake_args = [] |
| if stage1_extra_cmake_args is None: |
| stage1_extra_cmake_args = list(extra_cmake_args) |
| if projects is None: |
| projects = ['llvm', 'clang', 'lld'] |
| |
| c_compiler, cxx_compiler = self.compiler_binaries(compiler) |
| |
| self.set_environment(env) |
| |
| # On Windows, if we're building clang-cl, make sure stage1 is built with |
| # MSVC (cl.exe), and not gcc from mingw. CMake will prefer gcc if it is |
| # available. |
| if c_compiler == 'clang-cl': |
| stage1_extra_cmake_args += ['-DCMAKE_C_COMPILER=cl', |
| '-DCMAKE_CXX_COMPILER=cl'] |
| |
| cwd = os.getcwd() |
| source_dir = pjoin(cwd, '../llvm-project') |
| build_dir = cwd |
| cmake_args = ['-GNinja', '-DLLVM_ENABLE_PROJECTS=' + ';'.join(projects)] |
| |
| try: |
| # Build and check stages. |
| self.build_and_check_stages( |
| stages, |
| build_dir, |
| source_dir, |
| cmake_args, |
| extra_cmake_args, |
| c_compiler, |
| cxx_compiler, |
| linker, |
| check_stages, |
| check_targets, |
| stage1_extra_cmake_args, |
| jobs) |
| except Exception as e: |
| self.report_step_exception(e) |
| return 1 |
| |
| return 0 |
| |
| |
| def get_vcvars(vs_tools, arch): |
| """Get the VC tools environment using vswhere.exe from VS 2017 |
| |
| This code is following the guidelines from strategy 1 in this blog post: |
| https://blogs.msdn.microsoft.com/vcblog/2017/03/06/finding-the-visual-c-compiler-tools-in-visual-studio-2017/ |
| |
| It doesn't work when VS is not installed at the default location. |
| """ |
| if not arch: |
| # First check the wow64 processor architecture, since python is probably |
| # 32-bit, then fall back to PROCESSOR_ARCHITECTURE. |
| arch = os.environ.get('PROCESSOR_ARCHITEW6432', '').lower() |
| if not arch: |
| arch = os.environ.get('PROCESSOR_ARCHITECTURE', '').lower() |
| else: |
| arch = arch.lower() |
| |
| # Use vswhere.exe if it exists. |
| if os.path.exists(VSWHERE_PATH): |
| cmd = [VSWHERE_PATH, "-latest", "-property", "installationPath"] |
| vs_path = subprocess.check_output(cmd).strip() |
| util.report("Running vswhere to find VS: " + repr(cmd)) |
| util.report("vswhere output: " + vs_path) |
| if not os.path.isdir(vs_path): |
| raise ValueError("VS install path does not exist: " + vs_path) |
| vcvars_path = pjoin(vs_path, 'VC', 'Auxiliary', 'Build', |
| 'vcvarsall.bat') |
| elif vs_tools is None: |
| vs_tools = os.path.expandvars('%VS140COMNTOOLS%') |
| vcvars_path = pjoin(vs_tools, '..', '..', 'VC', 'vcvarsall.bat') |
| |
| # Newer vcvarsall.bat scripts aren't quiet, so direct them to NUL, aka |
| # Windows /dev/null. |
| cmd = util.shquote_cmd([vcvars_path, arch]) + ' > NUL && set' |
| util.report("Running vcvars: " + cmd) |
| output = subprocess.check_output(cmd, shell=True) |
| new_env = {} |
| for line in output.splitlines(): |
| var, val = line.split('=', 1) |
| new_env[var] = val |
| return new_env |
| |
| |
| def main(argv): |
| ap = get_argument_parser() |
| args = ap.parse_args(argv[1:]) |
| builder = AnnotatedBuilder() |
| builder.run_steps(jobs=args.jobs) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv)) |