blob: fa863be4c5aa368d879a67e5cd84d9b7bcf75dc3 [file] [log] [blame]
# Copyright 2018 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Recipe for building GCC toolchain."""
from recipe_engine.recipe_api import StepFailure
import re
DEPS = [
'fuchsia/git',
'fuchsia/goma',
'fuchsia/hash',
'fuchsia/jiri',
'fuchsia/macos_sdk',
'fuchsia/upload',
'recipe_engine/buildbucket',
'recipe_engine/cipd',
'recipe_engine/context',
'recipe_engine/file',
'recipe_engine/json',
'recipe_engine/path',
'recipe_engine/platform',
'recipe_engine/properties',
'recipe_engine/raw_io',
'recipe_engine/step',
'recipe_engine/url',
]
BINUTILS_GIT = 'https://gnu.googlesource.com/binutils-gdb'
BINUTILS_REF = 'refs/tags/binutils-2_33_1'
GCC_GIT = 'https://gnu.googlesource.com/gcc'
GCC_REF = 'refs/tags/gcc-9_1_0-release'
# Bump this number whenever changing this recipe in ways that affect the
# package built without also changing any upstream revision pin.
# TODO(crbug.com/947158): Remove this when the recipes repo/rev are available.
RECIPE_SALT = '1'
def RunSteps(api):
api.goma.ensure()
binutils_dir = api.path['start_dir'].join('binutils-gdb')
gcc_dir = api.path['start_dir'].join('gcc')
projects = {
'binutils-gdb': (BINUTILS_GIT, binutils_dir, BINUTILS_REF),
'gcc': (GCC_GIT, gcc_dir, GCC_REF),
}
gitiles_commit = api.buildbucket.build_input.gitiles_commit
if gitiles_commit.host and gitiles_commit.project and gitiles_commit.id:
projects[gitiles_commit.project] = (
'https://%s/%s' % (gitiles_commit.host, gitiles_commit.project),
api.path['start_dir'].join(gitiles_commit.project), gitiles_commit.id)
with api.context(infra_steps=True):
with api.step.nest('binutils-gdb'):
binutils_revision = api.git.checkout(*projects['binutils-gdb'])
with api.step.nest('gcc'):
gcc_revision = api.git.checkout(*projects['gcc'])
with api.step.nest('ensure_packages'):
with api.context(infra_steps=True):
pkgs = api.cipd.EnsureFile()
pkgs.add_package('fuchsia/third_party/clang/${platform}', 'goma')
if api.platform.name == 'linux':
pkgs.add_package('fuchsia/sysroot/${platform}', 'latest')
cipd_dir = api.path['start_dir'].join('cipd')
api.cipd.ensure(cipd_dir, pkgs)
if api.platform.name == 'linux':
host_sysroot = cipd_dir
elif api.platform.name == 'mac':
# TODO(IN-148): Eventually use our own hermetic sysroot as for Linux.
step_result = api.step(
'xcrun', ['xcrun', '--show-sdk-path'],
stdout=api.raw_io.output(name='sdk-path', add_output_log=True),
step_test_data=lambda: api.raw_io.test_api.stream_output(
'/some/xcode/path'))
host_sysroot = step_result.stdout.strip()
else: # pragma: no cover
assert False, 'what platform?'
with api.context(cwd=gcc_dir):
# download GCC dependencies: GMP, ISL, MPC and MPFR libraries
api.step('download prerequisites',
[gcc_dir.join('contrib', 'download_prerequisites')])
staging_dir = api.path.mkdtemp('gcc')
pkg_name = 'gcc-%s' % api.platform.name.replace('mac', 'darwin')
pkg_dir = staging_dir.join(pkg_name)
api.file.ensure_directory('create pkg dir', pkg_dir)
# Some of the makefile logic splits $CC at its first word and injects
# a switch there. So make $CC and $CXX be single words by writing
# little scripts. Autoconf does some checks with CPPFLAGS but not
# CFLAGS and other checks with CFLAGS but not CPPFLAGS. The sysroot
# is necessary for all cases, so fold that into the script too so it's
# impossible to omit it in any $CC or $CXX invocation.
cc_path = staging_dir.join('host-cc')
cxx_path = staging_dir.join('host-cxx')
for script, compiler in [(cc_path, 'clang'), (cxx_path, 'clang++')]:
api.file.write_text(
'write %s script' % api.path.basename(script), script, """#!/bin/sh
exec %s %s --sysroot=%s "$@"
""" % (api.goma.goma_dir.join('gomacc'), cipd_dir.join('bin',
compiler), host_sysroot))
api.step('make %s executable' % api.path.basename(script),
['chmod', '+x', script])
host_cflags = '-O3'
if api.platform.name != 'mac':
# LTO works for binutils on Linux but fails on macOS.
host_cflags += ' -flto'
host_compiler_args = {
'CC': '%s' % cc_path,
'CXX': '%s' % cxx_path,
'CFLAGS': host_cflags,
'CXXFLAGS': host_cflags,
}
if api.platform.name == 'mac':
# Our host toolchain for Mac provides static libc++ but doesn't
# know how to link it in by itself.
host_compiler_args['CXXFLAGS'] += ' -nostdlib++'
host_compiler_args['LIBS'] = cipd_dir.join('lib', 'libc++.a')
else:
# Always link libc++ statically in case a shared library is available.
host_compiler_args['CXXFLAGS'] += ' -static-libstdc++'
host_compiler_args = sorted(
'%s=%s' % item for item in host_compiler_args.iteritems())
# We force LIMITS_H_TEST=true to tell the compiler to install a <limits.h>
# that requires a system <limits.h> it can find via #include_next. But we
# don't have any system headers for the just-built compiler building target
# code (i.e. libgcc). So mock up a little include directory that contains
# just an empty <limits.h> for it to find.
mock_include_dir = staging_dir.join('mock-include')
api.file.ensure_directory('create mock include dir', mock_include_dir)
api.file.write_text('write dummy <limits.h>',
mock_include_dir.join('limits.h'), '/* dummy */')
with api.macos_sdk(), api.goma.build_with_goma():
for target, enable_targets in [('aarch64', 'arm-eabi'),
('x86_64', 'x86_64-pep')]:
# configure arguments that are the same for binutils and gcc.
common_args = host_compiler_args + [
'--prefix=', # we're building a relocatable package
'--target=%s-elf' % target,
'--enable-initfini-array', # Fuchsia uses .init/.fini arrays
'--enable-gold', # Zircon uses gold for userspace build
# Enable plugins and threading for Gold.
# This also happens to make it explicitly link in -lpthread and -dl,
# which are required by host_clang's static libc++.
'--enable-plugins',
'--enable-threads',
'--disable-werror', # ignore warnings reported by Clang
'--disable-nls', # no need for localization
'--with-included-gettext', # use include gettext library
]
# build binutils
binutils_build_dir = staging_dir.join('binutils_%s_build_dir' % target)
api.file.ensure_directory('create binutils %s build dir' % target,
binutils_build_dir)
with api.context(cwd=binutils_build_dir):
def binutils_make_step(name, make_target, prefix, jobs, make_args=None):
# As of 2.32, gold/testsuite/Makefile.am unconditionally edits in
# a -B.../ switch to make the compiler use the just-built gold as
# the linker for test suite binaries. This is wrong when
# building a cross-linker. Force it to a no-op on the make
# command line to work around the bug.
# TODO(mcgrathr): Drop this when we roll to a binutils that has
# this fixed upstream.
make_args = make_args or []
make_args.append('MAKEOVERRIDES=editcc=-eb')
return api.step(
'%s %s binutils' % (name, make_target),
['make', '-j%s' % jobs] + make_args + [
'%s-%s' % (prefix, component)
for component in ['binutils', 'gas', 'ld', 'gold']
])
api.step(
'configure %s binutils' % target,
[
binutils_dir.join('configure'),
'--enable-deterministic-archives', # more deterministic builds
'--enable-targets=%s' % enable_targets,
] + common_args)
binutils_make_step('build', target, 'all', api.goma.jobs)
try:
binutils_make_step('test', target, 'check', api.platform.cpu_count,
['-k'])
except StepFailure as error:
logs = {
l[0]:
api.file.read_text('binutils %s %s' % (target, '/'.join(l)),
binutils_build_dir.join(*l)).splitlines()
for l in [
('gas', 'testsuite', 'gas.log'),
('binutils', 'binutils.log'),
('ld', 'ld.log'),
('gold', 'testsuite', 'test-suite.log'),
]
}
step_result = api.step('binutils test failure', cmd=None)
for name, text in logs.iteritems():
step_result.presentation.logs[name] = text
raise error
binutils_make_step('install', target, 'install-strip', 1,
['DESTDIR=%s' % pkg_dir])
# build gcc
gcc_build_dir = staging_dir.join('gcc_%s_build_dir' % target)
api.file.ensure_directory('create gcc %s build dir' % target,
gcc_build_dir)
with api.context(
cwd=gcc_build_dir, env_prefixes={'PATH': [pkg_dir.join('bin')]}):
gcc_goals = ['gcc', 'target-libgcc']
def gcc_make_step(name, make_target, jobs, args):
cmd = [
'make',
'-j%s' % jobs,
'MAKEOVERRIDES=USE_GCC_STDINT=provide LIMITS_H_TEST=true',
# Force flags for libgcc to get <limits.h> kludge.
'CPPFLAGS_FOR_TARGET=-idirafter %s' % mock_include_dir,
]
return api.step('%s %s gcc' % (name, make_target), cmd + args)
api.step(
'configure %s gcc' % target,
[
gcc_dir.join('configure'),
'--enable-languages=c,c++',
'--disable-libstdcxx', # we don't need libstdc++
'--disable-libssp', # we don't need libssp either
'--disable-libquadmath', # and nor do we need libquadmath
] + common_args)
gcc_make_step('build', target, api.goma.jobs,
['all-%s' % goal for goal in gcc_goals])
try:
gcc_make_step('test', target, api.platform.cpu_count, ['check-gcc'])
finally:
logs = {
l[-1]: api.file.read_text('gcc %s %s' % (target, '/'.join(l)),
gcc_build_dir.join(*l)).splitlines()
for l in [
('gcc', 'testsuite', 'gcc', 'gcc.log'),
('gcc', 'testsuite', 'g++', 'g++.log'),
]
}
step_result = api.step('test %s gcc logs' % target, cmd=None)
for name, text in logs.iteritems():
step_result.presentation.logs[name] = text
gcc_make_step('install', target, 1, ['DESTDIR=%s' % pkg_dir] +
['install-strip-%s' % goal for goal in gcc_goals])
binutils_version = api.file.read_text(
'binutils version',
binutils_dir.join('bfd', 'version.m4'),
test_data='m4_define([BFD_VERSION], [2.27.0])')
m = re.match(r'm4_define\(\[BFD_VERSION\], \[([^]]+)\]\)', binutils_version)
assert m and m.group(1), ('bfd/version.m4 has unexpected format: %r' %
binutils_version)
binutils_version = m.group(1)
gcc_version = api.file.read_text('gcc version',
gcc_dir.join('gcc', 'BASE-VER')).rstrip()
cipd_git_repository = ','.join([GCC_GIT, BINUTILS_GIT])
cipd_git_revision = ','.join([gcc_revision, binutils_revision])
# Add a bogus "salt" repository/revision to represent recipe changes when
# the upstream revisions haven't changed. Bump this number whenever
# changing this recipe in ways that affect the package built without also
# changing any upstream revision pin.
# TODO(crbug.com/947158): Replace this with the recipes repo/rev when
# infra makes that available here.
if RECIPE_SALT: # pragma: no cover
cipd_git_repository += ',<salt>'
cipd_git_revision += ',%s' % RECIPE_SALT
version = ','.join([gcc_version, binutils_version])
api.upload.cipd_package(
'fuchsia/gcc/${platform}',
pkg_dir, [api.upload.DirectoryPath(pkg_dir)],
{'git_revision': cipd_git_revision},
repository=cipd_git_repository,
extra_tags={'version': version})
def GenTests(api):
binutils_revision = '3d861fdb826c2f5cf270dd5f585d0e6057e1bf4f'
gcc_revision = '4b5e15daff8b54440e3fda451c318ad31e532fab'
cipd_revision = ','.join([gcc_revision, binutils_revision])
if RECIPE_SALT: # pragma: no cover
cipd_revision += ',%s' % RECIPE_SALT
for platform in ('linux', 'mac'):
yield (api.test(platform) + api.platform.name(platform) +
api.step_data('binutils-gdb.git rev-parse',
api.raw_io.stream_output(binutils_revision)) +
api.step_data('gcc.git rev-parse',
api.raw_io.stream_output(gcc_revision)))
for salt in ['', 999] + ([RECIPE_SALT] if RECIPE_SALT else []):
salt = '%s' % salt
yield (api.test(platform + '_new' + salt) + api.platform.name(platform) +
api.step_data('binutils-gdb.git rev-parse',
api.raw_io.stream_output(binutils_revision)) +
api.step_data('gcc.git rev-parse',
api.raw_io.stream_output(gcc_revision)) +
api.step_data(
'cipd.cipd search fuchsia/gcc/${platform} git_revision:' +
cipd_revision, api.json.output({'result': []})) +
api.step_data('gcc version', api.file.read_text('7.1.2\n')))
yield (api.test(platform + '_binutils_test_fail' + salt) +
api.buildbucket.ci_build(
git_repo=BINUTILS_GIT, revision=binutils_revision) +
api.step_data('gcc.git rev-parse',
api.raw_io.stream_output(gcc_revision)) +
api.platform.name(platform) +
api.step_data('test x86_64 binutils', retcode=1))
yield (api.test(platform + '_gcc_test_fail' + salt) +
api.buildbucket.ci_build(git_repo=GCC_GIT, revision=gcc_revision) +
api.step_data('binutils-gdb.git rev-parse',
api.raw_io.stream_output(binutils_revision)) +
api.platform.name(platform) +
api.step_data('test aarch64 gcc', retcode=1))