blob: 487d4150a6559bb7a70928e5f2edd339470091a3 [file] [log] [blame]
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))