blob: 8c9949b10eb4fb072854818d9daea33dfe40e702 [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.config import Enum, ReturnSchema, Single
from recipe_engine.recipe_api import Property, StepFailure
import re
DEPS = [
'infra/git',
'infra/gitiles',
'infra/goma',
'infra/hash',
'infra/jiri',
'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/tempfile',
'recipe_engine/url',
]
BINUTILS_GIT = 'https://gnu.googlesource.com/binutils-gdb'
BINUTILS_REF = 'refs/heads/binutils-2_31-branch'
GCC_GIT = 'https://gnu.googlesource.com/gcc'
GCC_REF = 'refs/tags/gcc-8_2_0-release'
PROPERTIES = {
'binutils_revision': Property(kind=str, help='Revision in binutils repo',
default=None),
'gcc_revision': Property(kind=str, help='Revision in GCC repo', default=None),
}
# binutils and gcc both have a daily robocommit to a specific file that
# just updates a date stamp. If that's the only change since the last
# revision we built, this new revision is not worth building.
def DoCheckout(api, url, checkout_dir, revision, last_revision, useless_file):
api.git.checkout(url, checkout_dir, revision)
with api.context(cwd=checkout_dir):
try:
step = api.git(
'diff', '--name-only', '%s..%s' % (last_revision, revision),
name='check for changes other than %s' % useless_file,
stdout=api.raw_io.output(name='changed files', add_output_log=True))
except StepFailure as error:
# If the old revision is no longer in the repo or something like that,
# git can fail. But that shouldn't make the build fail, it should just
# not trigger the "useless change" short-circuit.
return False
changed_files = set(step.stdout.splitlines()) - set([useless_file])
return changed_files == set()
def RunSteps(api, binutils_revision, gcc_revision):
api.gitiles.ensure_gitiles()
api.goma.ensure_goma()
if binutils_revision is None:
binutils_revision = api.gitiles.refs(
BINUTILS_GIT, step_name='binutils refs').get(BINUTILS_REF, None)
if gcc_revision is None:
gcc_revision = api.gitiles.refs(
GCC_GIT, step_name='gcc refs').get(GCC_REF, None)
cipd_pkg_name = 'fuchsia/gcc/${platform}'
cipd_git_revision = ','.join([gcc_revision, binutils_revision])
pins = api.cipd.search(cipd_pkg_name, 'git_revision:' + cipd_git_revision)
if len(pins) > 0:
api.step('Package is up-to-date', cmd=None)
return
with api.step.nest('ensure_packages'):
with api.context(infra_steps=True):
pkgs = api.cipd.EnsureFile()
pkgs.add_package('fuchsia/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?"
description = api.cipd.describe(
cipd_pkg_name, 'latest', test_data_tags=[
'git_revision:b824b5a2484202f6eabbe118b1cf7682ce77b76e,5898623efb820fc56123da2e316756a9a32cb59b'])
[last_tag] = [cipd_tag.tag
for cipd_tag in description.tags
if cipd_tag.tag.startswith('git_revision:')]
[last_gcc_revision, last_binutils_revision] = last_tag[
len('git_revision:'):].split(',')
with api.context(infra_steps=True):
binutils_dir = api.path['start_dir'].join('binutils-gdb')
binutils_no_change = DoCheckout(api, BINUTILS_GIT, binutils_dir,
binutils_revision, last_binutils_revision,
'bfd/version.h')
gcc_dir = api.path['start_dir'].join('gcc')
gcc_no_change = DoCheckout(api, GCC_GIT, gcc_dir,
gcc_revision, last_gcc_revision,
'gcc/DATESTAMP')
if binutils_no_change and gcc_no_change:
api.step('Revisions contain no useful changes', cmd=None)
return
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)
host_clang = '%s %s' % (api.goma.goma_dir.join('gomacc'),
cipd_dir.join('bin', 'clang'))
host_cflags = '-O3 --sysroot=%s' % host_sysroot
if api.platform.name != 'mac':
# LTO works for binutils on Linux but fails on macOS.
host_cflags += ' -flto'
host_compiler_args = {
'CC': host_clang,
'CXX': '%s++' % host_clang,
'CFLAGS': host_cflags,
'CXXFLAGS': host_cflags,
}
if api.platform.name != 'mac':
# TODO(mcgrathr): Our host toolchain is currently broken on non-Mac
# where we provide only static libc++.a that depends on other libraries
# but we require linking to them explicitly on the command line to
# satisfy libc++.a's implicit dependencies.
host_compiler_args['CXXFLAGS'] += ' -lpthread -ldl'
host_compiler_args = sorted('%s=%s' % item
for item in host_compiler_args.iteritems())
with 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, prefix, jobs, make_args=[], logs=[]):
return api.step('%s %s binutils' % (name, 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', 'all', api.goma.jobs)
try:
binutils_make_step('test', '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', '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, jobs, args):
cmd = [
'make',
'-j%s' % jobs,
'MAKEOVERRIDES=USE_GCC_STDINT=provide',
]
return api.step('%s %s gcc' % (name, 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', api.goma.jobs,
['all-%s' % goal for goal in gcc_goals])
try:
gcc_make_step('test', 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', 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'))
m = re.match(r'm4_define\(\[BFD_VERSION\], \[([^]]+)\]\)', binutils_version)
assert m and m.group(1), 'bfd/version.m4 has unexpected format'
binutils_version = m.group(1)
gcc_version = api.file.read_text('gcc version',
gcc_dir.join('gcc', 'BASE-VER')).rstrip()
pkg_def = api.cipd.PackageDefinition(
package_name=cipd_pkg_name,
package_root=pkg_dir,
install_mode='copy')
pkg_def.add_dir(pkg_dir)
pkg_def.add_version_file('.versions/gcc.cipd_version')
cipd_pkg_file = api.path['cleanup'].join('gcc.cipd')
api.cipd.build_from_pkg(
pkg_def=pkg_def,
output_package=cipd_pkg_file,
)
step_result = api.cipd.register(
package_name=cipd_pkg_name,
package_path=cipd_pkg_file,
refs=['latest'],
tags={
'version': ','.join([gcc_version, binutils_version]),
'git_repository': ','.join([GCC_GIT, BINUTILS_GIT]),
'git_revision': cipd_git_revision,
},
)
def GenTests(api):
binutils_revision = '3d861fdb826c2f5cf270dd5f585d0e6057e1bf4f'
gcc_revision = '4b5e15daff8b54440e3fda451c318ad31e532fab'
cipd_revision = ','.join([gcc_revision, binutils_revision])
for platform in ('linux', 'mac'):
yield (api.test(platform) +
api.platform.name(platform) +
api.gitiles.refs('binutils refs',
(BINUTILS_REF, binutils_revision)) +
api.gitiles.refs('gcc refs',
(GCC_REF, gcc_revision)))
yield (api.test(platform + '_new') +
api.platform.name(platform) +
api.gitiles.refs('binutils refs',
(BINUTILS_REF, binutils_revision)) +
api.gitiles.refs('gcc refs',
(GCC_REF, gcc_revision)) +
api.step_data('cipd search fuchsia/gcc/${platform} ' +
'git_revision:' + cipd_revision,
api.json.output({'result': []})) +
api.step_data('check for changes other than bfd/version.h',
api.raw_io.stream_output('bfd/version.h\nothers\n',
name='changed files')) +
api.step_data('check for changes other than gcc/DATESTAMP',
api.raw_io.stream_output('gcc/DATESTAMP\nothers\n',
name='changed files')) +
api.step_data('binutils version', api.file.read_text(
'm4_define([BFD_VERSION], [2.27.0])')) +
api.step_data('gcc version', api.file.read_text('7.1.2\n')))
yield (api.test(platform + '_binutils_test_fail') +
api.platform.name(platform) +
api.gitiles.refs('binutils refs',
(BINUTILS_REF, binutils_revision)) +
api.gitiles.refs('gcc refs',
(GCC_REF, gcc_revision)) +
api.step_data('cipd search fuchsia/gcc/${platform} ' +
'git_revision:' + cipd_revision,
api.json.output({'result': []})) +
api.step_data('check for changes other than bfd/version.h',
api.raw_io.stream_output('bfd/version.h\nothers\n',
name='changed files')) +
api.step_data('check for changes other than gcc/DATESTAMP',
api.raw_io.stream_output('gcc/DATESTAMP\nothers\n',
name='changed files')) +
api.step_data('test x86_64 binutils', retcode=1))
yield (api.test(platform + '_gcc_test_fail') +
api.platform.name(platform) +
api.gitiles.refs('binutils refs',
(BINUTILS_REF, binutils_revision)) +
api.gitiles.refs('gcc refs',
(GCC_REF, gcc_revision)) +
api.step_data('cipd search fuchsia/gcc/${platform} ' +
'git_revision:' + cipd_revision,
api.json.output({'result': []})) +
api.step_data('check for changes other than bfd/version.h',
api.raw_io.stream_output('bfd/version.h\nothers\n',
name='changed files')) +
api.step_data('check for changes other than gcc/DATESTAMP',
api.raw_io.stream_output('', retcode=1,
name='changed files')) +
api.step_data('test aarch64 gcc', retcode=1))
yield (api.test(platform + '_useless') +
api.platform.name(platform) +
api.gitiles.refs('binutils refs',
(BINUTILS_REF, binutils_revision)) +
api.gitiles.refs('gcc refs',
(GCC_REF, gcc_revision)) +
api.step_data('cipd search fuchsia/gcc/${platform} ' +
'git_revision:' + cipd_revision,
api.json.output({'result': []})) +
api.step_data('check for changes other than bfd/version.h',
api.raw_io.stream_output('bfd/version.h\n',
name='changed files')) +
api.step_data('check for changes other than gcc/DATESTAMP',
api.raw_io.stream_output('gcc/DATESTAMP\n',
name='changed files')))