| # 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)) |