blob: 6896c7e2656e0c411a93dd50b91a257c0c7bbde3 [file] [log] [blame]
# Copyright 2020 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.
import("//build/config/clang/clang.gni")
import("//build/config/compiler.gni")
import("//build/config/zircon/levels.gni")
import("//build/toolchain/breakpad.gni")
import("//build/toolchain/ccache.gni")
import("//build/toolchain/default_tools.gni")
import("//build/toolchain/goma.gni")
import("//build/toolchain/zircon/clang.gni")
import("//build/toolchain/zircon/gcc.gni")
import("//build/zircon/build_args.gni")
_buildidtool =
rebase_path("//prebuilt/tools/buildidtool/${host_platform}/buildidtool",
root_build_dir)
# IMPORTANT: The zircon_toolchain() template does not use clang_toolchain()
# defined in //build/toolchain/clang_toolchain.gni, because there are several
# important differences between their tool() commands. A few examples:
#
# - clang_toolchain() does not support GCC builds.
#
# - The 'alink' command differs slightly between the build systems:
# GN: .../llvm-ar ${arflags} rcsD ${out} @"${out}.rsp"
# ZN: .../llvm-ar ${arflags} cqsD ${out} '@${out}.rsp'
#
# - The response file paths slightly differ:
# GN: ${output_dir}/${target_output_name}${output_extension}.rsp
# ZN: ${output_dir}/${target_output_name}.rsp
#
# - Stripping works differently as well, e.g. on host Linux:
# GN: .../llvm-objcopy --strip-all <src> <dst>
# ZN: .../llvm-strip -o <dst> <src>
#
# - Many trivial differences, like the position of option like '-o <output>'
# or {{cflags}}, {{ldflags}} etc, escaping some paths with quotes in one
# build but not the other make comparing final build commands difficult.
#
# - Static and shared libraries are ordered differently in link inputs:
# GN: -Wl,--start-group <inputs> {{solibs}} -Wl,--end-group {{libs}}
# ZN: -Wl,--start-group <inputs> {{libs}} -Wl,--end-group {{solibs}}
#
# - Slightly different flags to specify the link map file:
# GN: -Wl,--Map=<file>
# ZN: -Wl,-Map=<file>
#
# - Different naming conventions for debug binaries:
# GN: {{output_dir}}/exe.unstripped/{{target_output_name}}{{output_extension}}
# ZN: {{output_dir}}/{{target_output_name}}{{output_extension}}.debug
#
# For build unification, it is critical to ensure that the Fuchsia and
# Zircon builds generate the same set of commands, so the template defined
# here replicated the original Zircon commands. After unification completes,
# it might be worthwhile to refactor clang_toolchain() and zircon_toolchain()
# to use a common set of definitions instead.
#
# IMPORTANT: The zircon_toolchain global variable is defined as a scope for
# Zircon-specific toolchains, otherwise it is just 'false'. See below for the
# detailed schema for this scope.
# Defines a Clang-based Zircon toolchain.
#
# Parameters (same as the ZN build c_toolchain() template):
#
# cpu, os
# Required: $current_cpu and $current_os values for the toolchain.
# Type: string
#
# strip
# Optional: If not false, linked binaries will be stripped.
# If this is a string rather than true, it's a switch to the `objcopy`
# tool, e.g. "--strip-sections".
# Type: bool or string
# Default: false
#
# tool_dir, tool_prefix
# Optional: If one of these is provided, then both must be provided.
# $tool_dir is the directory where compiler tools are found, or "" if they
# are found by searching `PATH`. $tool_prefix is the prefix prepended to
# each tool's name. If $gcc is true, then $tool_dir defaults to "" if
# $host is true and $gcc_tool_dir otherwise. If $gcc is true and $host is
# not true or $cpu != $host_cpu or $os != $host_os, then $tool_prefix
# defaults to the "<tuple>-" prefix appropriate for $cpu and $os;
# otherwise it defaults to "".
# Type: string
#
# use_goma
# Optional: If defined, overrides the $use_goma build argument.
# If true, compilation commands are prefixed with "$goma_dir/gomacc".
# Type: bool
# Default: $use_goma
#
# use_ccache
# Optional: If defined, overrides the $use_ccache build argument.
# If true, compilation commands are prefixed with "ccache".
# Type: bool
# Default: $use_ccache
#
# deps, toolchain_args
# Optional: See toolchain().
#
# The following parameters are specific to the Fuchsia build and were not
# part of the original Zircon build.
#
# environment
# Required: A string describing the runtime environment of the code
# generated by this toolchain. This is a label like "kernel" or "multiboot"
# that will appear in as `zircon_toolchain.environment` and can be checked
# in build rules, as requested by several Zircon artefacts.
#
# The following parameters from the original Zircon builds are NOT SUPPORTED
# and documented here for historical reasons!
#
# gcc
# Optional: If true, this toolchain uses GCC rather than Clang.
# This template will instead look for 'gcc' in the list of tags.
# Type: bool
# Default: false
#
# host
# Optional: If true, this is doing "host" compiles. This only matters
# if $os == $host_os and $cpu == $host_cpu, i.e. native compilation for
# the build host. In that case, when using GCC, this says to use plain
# "gcc" et al rather than "<triple>-gcc" et al.
# Type: bool
# Default: false
#
# shlib
# Optional: If true, the toolchain will support the "solink" and
# "solink_module" (for shared_library() and loadable_module() targets,
# respectively). This is now determined by the values of with_shared
# and is_pic_default in toolchain_variant.
# Type: bool
# Default: false
#
# toolchain_vars
# Optional: Additional keys/values to export in the $zircon_toolchain scope.
# The $zircon_toolchain global variable is a scope that provides various\
# fields describing how things are done in this toolchain. Several standard
# fields are set automatically. This can supply others.
# Type: scope
# Default: {}
#
template("zircon_toolchain") {
# Translate GN cpu/os to GNU config tuple.
if (invoker.cpu == "arm64") {
tc_cpu = "aarch64"
} else if (invoker.cpu == "x64") {
tc_cpu = "x86_64"
} else {
tc_cpu = invoker.cpu
}
tc_target = "${tc_cpu}-"
if (invoker.os == "mac") {
tc_target += "apple-darwin"
} else if (invoker.os == "win") {
tc_target += "windows-msvc"
} else if (invoker.os == "linux") {
tc_target += "linux-gnu"
} else {
tc_target += invoker.os
}
assert(defined(invoker.environment), "'environment' argument is required!")
# Cached and distributed wrappers just go on the front of compiler commands.
compiler_prefix = ""
if (defined(invoker.use_goma)) {
use_goma = invoker.use_goma
}
if (use_goma) {
goma_dir = rebase_path(goma_dir)
compiler_prefix = "$goma_dir/gomacc "
} else {
if (defined(invoker.use_ccache)) {
use_ccache = invoker.use_ccache
}
if (use_ccache) {
compiler_prefix = "ccache "
}
}
# Compute what will be the value of `toolchain_variant` for this instance.
# This is invoker.toolchain_args.toolchain_variant, eventually augmented
# by default key values.
if (defined(invoker.toolchain_args)) {
_t_args = invoker.toolchain_args
if (defined(_t_args.toolchain_variant)) {
_tv_args = _t_args.toolchain_variant
}
}
_toolchain_variant = {
# Default non-list values
base = get_label_info(":$target_name", "label_no_toolchain")
name = ""
suffix = ""
is_pic_default = false
with_shared = false
instrumented = false
# Forward values from invoker.toolchain_args.toolchain_variant, if any.
if (defined(_tv_args)) {
forward_variables_from(_tv_args, "*")
}
# Create empty lists if they were not forwarded above.
if (!defined(configs)) {
configs = []
}
if (!defined(prefix_configs)) {
prefix_configs = []
}
if (!defined(remove_common_configs)) {
remove_common_configs = []
}
if (!defined(remove_shared_configs)) {
remove_shared_configs = []
}
if (!defined(tags)) {
tags = []
}
}
use_gcc =
_toolchain_variant.tags + [ "gcc" ] - [ "gcc" ] != _toolchain_variant.tags
use_strip = defined(invoker.strip) && invoker.strip != false
# These are used in some, but not all, of the if branches below.
if (clang_tool_dir == "") {
clangxx_exec_inputs = []
clangxx_exec_command = "clang++"
} else {
clangxx_exec_inputs = [ "${clang_tool_dir}/clang++" ]
clangxx_exec_command = rebase_path(clangxx_exec_inputs[0], root_build_dir)
}
clangxx_args = [
clangxx_exec_command,
"--target=$tc_target",
# This affects the multilib selection so that we get the right
# libc++.a binary. It's not really proper that we have to encode here
# the multilib-affecting switches we presume all configs actually will
# use in the toolchain being defined. But since everything in this
# codebase (certainly everything we support being compiled by GCC) is
# definitely compiled with -fno-exceptions, we can live with it. An
# alternative would be to add the lib_dirs in a config() instead and
# then somehow tie it to exceptions vs no-exceptions configs, but we
# have no present need for any such complexity.
"-fno-exceptions",
]
not_needed([ "clangxx_args" ])
# If either $tool_dir or $tool_prefix is supplied then both must be supplied.
if (defined(invoker.tool_dir) || defined(invoker.tool_prefix)) {
tc = {
tool_dir = invoker.tool_dir
tool_prefix = invoker.tool_prefix
}
} else if (use_gcc) {
if (defined(invoker.host) && invoker.host) {
tc = {
tool_dir = ""
if (invoker.cpu == host_cpu && invoker.os == host_os) {
tool_prefix = ""
} else {
tool_prefix = "${tc_target}-"
}
}
} else {
tc = {
tool_dir = gcc_tool_dir
tool_prefix = "${tc_cpu}-elf-"
version_string = gcc_version_string
include_dirs = [ rebase_path(
exec_script(
"/usr/bin/env",
clangxx_args + [ "-print-file-name=include/c++/v1" ],
"trim string",
clangxx_exec_inputs),
"",
root_build_dir) ]
lib_dirs = [ rebase_path(
get_path_info(
exec_script("/usr/bin/env",
clangxx_args + [ "-print-file-name=libc++.a" ],
"trim string",
clangxx_exec_inputs),
"dir"),
"",
root_build_dir) ]
}
}
} else {
tc = {
tool_dir = clang_tool_dir
tool_prefix = "llvm-"
version_string = clang_version_string
# TODO(fxbug.dev/27328): The MSVC target in the clang driver never uses the
# -stdlib=libc++ switch or the toolchain's own libc++ include path.
if (invoker.os == "win") {
include_dirs = [ rebase_path(
exec_script(
"/usr/bin/env",
clangxx_args + [ "-print-file-name=include/c++/v1" ],
"trim string",
clangxx_exec_inputs),
"",
root_build_dir) ]
}
}
}
if (use_gcc) {
tc.cc = "${tc.tool_prefix}gcc"
tc.cxx = "${tc.tool_prefix}g++"
} else {
tc.cc = "clang"
tc.cxx = "clang++"
}
if (tc.tool_dir == "") {
dir_prefix = ""
if (use_goma) {
# GOMA requires an absolute path to the compiler.
compiler_prefix = ""
}
} else {
dir_prefix = rebase_path(tc.tool_dir, root_build_dir) + "/"
}
cc = "$dir_prefix${tc.cc}"
cxx = "$dir_prefix${tc.cxx}"
ld = cxx
ar = "$dir_prefix${tc.tool_prefix}ar"
# All other OS possibilities are ELF and so might have ELF build IDs.
is_elf = invoker.os != "mac" && invoker.os != "win"
# buildidtool only supports ELF build IDs.
use_buildidtool = is_elf
# The prebuilt dump_syms tool is the one that only supports ELF.
# Breakpad has dump_syms tools for Mach-O (macOS) and PE-COFF
# (Windows/EFI) too, but the PE-COFF tool only runs on Windows. The
# Mach-O tool can be cross compiled, though we can't cross compile to
# macOS anyway. But we also don't have a prebuilt of the macOS
# self-targetting Breakpad dump_syms tool on hand.
use_breakpad = output_breakpad_syms && is_elf && use_buildidtool
if (use_breakpad) {
dump_syms = breakpad_dump_syms
dump_syms += " -r "
if (invoker.os == "fuchsia") {
dump_syms += " -n \"<_>\" -o Fuchsia"
} else {
# Use what's probably installation name, not the ".debug" name.
dump_syms +=
" -n \"{{output_dir}}/{{target_output_name}}{{output_extension}}\""
}
}
# TODO(fxbug.dev/58294): enable for other targets and binary formats.
use_gsym = output_gsym && is_elf && invoker.os == "fuchsia"
if (use_gsym) {
gsymutil = rebase_path("$clang_tool_dir/llvm-gsymutil", root_build_dir)
}
_compile_common = {
outputs =
[ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ]
depfile = "{{output}}.d"
depsformat = "gcc"
switches = "-MD -MF $depfile -o {{output}} {{defines}} {{include_dirs}}"
}
_link_common = {
lib_switch = "-l"
lib_dir_switch = "-L"
default_output_dir = "{{target_out_dir}}"
outfile = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
runtime_outputs = [ outfile ]
outputs = runtime_outputs
if (use_strip) {
unstripped_outfile = "${outfile}.debug"
outputs += [ unstripped_outfile ]
strip_command = " && "
_strip = invoker.strip
if (use_gcc && _strip == "--strip-sections") {
# GNU strip/objcopy doesn't support --strip-sections.
_strip = true
}
if (_strip == true) {
if (invoker.os == "mac") {
# Only the host native tool works on macOS.
strip_tool = "strip"
} else {
strip_tool = "$dir_prefix${tc.tool_prefix}strip"
}
strip_command += "$strip_tool -o \"$outfile\" \"$unstripped_outfile\""
} else {
strip_command += "$dir_prefix${tc.tool_prefix}objcopy ${_strip}" +
" \"$unstripped_outfile\" \"$outfile\""
}
} else {
unstripped_outfile = outfile
strip_command = ""
}
rspfile = "${outfile}.rsp"
switches = "-o $unstripped_outfile {{ldflags}}"
if (invoker.os == "mac") {
rspfile_content = "{{inputs_newline}}"
link_inputs = "-Wl,-filelist,$rspfile {{libs}} {{solibs}}"
} else {
rspfile_content = "{{inputs}}"
link_inputs = "'@$rspfile'"
if (invoker.os == "win") {
link_inputs += " {{libs}} {{solibs}}"
} else {
link_inputs = "-Wl,--start-group " + link_inputs +
" {{libs}} -Wl,--end-group {{solibs}}"
depfile = "$outfile.d"
depsformat = "gcc"
switches += " -Wl,--dependency-file=$depfile"
mapfile = "${outfile}.map"
outputs += [ mapfile ]
switches += " -Wl,-Map,$mapfile"
}
}
if (invoker.os == "win") {
# This assumes "-Wl,/debug" or suchlike is in ldflags. If it's not
# but nothing depends on the .pdb file, then it should be OK that
# Ninja thinks it's an additional output but it doesn't get created.
outputs += [ "{{output_dir}}/{{target_output_name}}.pdb" ]
}
if (use_buildidtool) {
buildid_stampfile = "${outfile}.build-id.stamp"
strip_command +=
" && $_buildidtool -build-id-dir \".build-id\"" +
" -stamp \"$buildid_stampfile\"" +
" -entry \"=$outfile\" -entry \".debug=$unstripped_outfile\""
}
if (use_breakpad) {
breakpad_outfile = "${outfile}.sym"
outputs += [ breakpad_outfile ]
# dump_syms will fail if there is no build ID. buildidtool succeeds and
# writes an empty stamp file for that case, so use it to tell whether
# dump_syms should be run. In any case always create a .sym because we
# told ninja we would create one above.
assert(use_buildidtool)
strip_command +=
" && { " + # The brace group is superfluous.
"test ! -s \"$buildid_stampfile\" && " +
"touch \"$breakpad_outfile\" || " +
"$dump_syms \"$unstripped_outfile\" > \"$breakpad_outfile\"" +
"; }" # But makes the control flow a little clearer.
}
if (use_gsym) {
gsym_outfile = "${outfile}.gsym"
outputs += [ gsym_outfile ]
strip_command += " && $gsymutil --convert=\"$unstripped_outfile\" --out-file=\"$gsym_outfile.gsym\""
}
}
not_needed(invoker, [ "propagates_configs" ])
toolchain(target_name) {
propagates_configs = true
forward_variables_from(invoker, [ "deps" ])
# Every toolchain needs the standard stamp and copy tools.
tool("stamp") {
command = stamp_command
description = stamp_description
}
tool("copy") {
# We use link instead of copy; the way the "copy" tool is used is
# compatible with links since Ninja tracks changes to the source.
command = copy_command
description = copy_description
}
tool("asm") {
description = "ASM {{output}}"
forward_variables_from(_compile_common, "*")
command = "$cc $switches {{asmflags}} -c {{source}}"
command_launcher = compiler_prefix
}
tool("cc") {
description = "CC {{output}}"
forward_variables_from(_compile_common, "*")
command = "$cc $switches {{cflags}} {{cflags_c}} -c {{source}}"
command_launcher = compiler_prefix
}
tool("cxx") {
description = "CXX {{output}}"
forward_variables_from(_compile_common, "*")
command = "$cxx $switches {{cflags}} {{cflags_cc}} -c {{source}}"
command_launcher = compiler_prefix
}
tool("alink") {
description = "AR {{output}}"
rspfile = "{{output}}.rsp"
command =
"rm -f {{output}} && $ar {{arflags}} cqsD {{output}} '@$rspfile'"
rspfile_content = "{{inputs}}"
default_output_dir = "{{target_out_dir}}"
default_output_extension = ".a"
output_prefix = "lib"
outputs = [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ]
}
tool("link") {
forward_variables_from(_link_common, "*")
description = "LINK $outfile"
if (invoker.os == "win") {
default_output_extension = ".exe"
}
command = "$ld $switches $link_inputs$strip_command"
}
if (_toolchain_variant.with_shared || _toolchain_variant.is_pic_default) {
# We don't support building shared libraries for host tools.
assert(invoker.os == "fuchsia",
"shared library creation not supported for ${invoker.os}")
solink_common = {
forward_variables_from(_link_common, "*")
depend_output = outfile
link_output = unstripped_outfile
}
tool("solink") {
forward_variables_from(solink_common, "*")
description = "LINK_SHLIB $outfile"
output_prefix = "lib"
if (current_os == "mac") {
default_output_extension = ".dylib"
} else if (current_os == "win") {
default_output_extension = ".dll"
} else {
default_output_extension = ".so"
}
# Put the automatic -soname first so that ldflags can override it.
soname = "{{target_output_name}}{{output_extension}}"
switches = "-shared -Wl,-soname,$soname $switches"
if (use_gcc && tc.tool_prefix == "${tc_cpu}-elf-") {
# The *-elf-gcc driver doesn't handle -shared properly.
# Just force it through to the linker.
switches = "-Wl,$switches"
}
command = "$ld $switches $link_inputs$strip_command"
# TODO: abi stubs
}
tool("solink_module") {
forward_variables_from(solink_common, "*")
description = "LINK_MODULE $outfile"
if (current_os == "mac") {
default_output_extension = ".dylib"
} else if (current_os == "win") {
default_output_extension = ".dll"
} else {
default_output_extension = ".so"
}
switches = "-shared $switches"
if (use_gcc && tc.tool_prefix == "${tc_cpu}-elf-") {
# The *-elf-gcc driver doesn't handle -shared properly.
# Just force it through to the linker.
switches = "-Wl,$switches"
}
command = "$ld $switches $link_inputs$strip_command"
}
}
# IMPORTANT: The toolchain_args variables in the Fuchsia and Zircon builds
# are very distinct.
# TODO(fxbug.dev/51585): Keep this in sync with the corresponding code in //BUILD.gn
if (optimize == "none" || optimize == "sanitizer" ||
optimize == "profile" || optimize == "size") {
_zircon_optimize = optimize
} else {
_zircon_optimize = zircon_optimize
}
# The assert level used by Zircon artifacts is controlled by the zx_assert_level
# build argument, except that, as a special case, zircon_kernel_disable_asserts
# can be set to true to disable them in the kernel itself.
#
# For legacy reasons, also recognize that zircon_extra_args.environment_args is
# set to a specific value to disable kernel assertions as well. For more details
# see https://fxbug.dev/67356.
_assert_level = zx_assert_level
if (defined(invoker.toolchain_tags) &&
invoker.toolchain_tags + [ "kernel" ] - [ "kernel" ] !=
invoker.toolchain_tags) {
if (zircon_kernel_disable_asserts) {
_assert_level = 0
}
if (defined(zircon_extra_args.environment_args)) {
_env_args = zircon_extra_args.environment_args
_expected_args = [
{
kernel = true
assert_level = 0
},
]
assert(
_env_args == _expected_args,
"Invalid zircon_extra_args.environment_args value (see https://fxbug.dev/67356): ${_env_args}")
_assert_level = 0
}
}
toolchain_args = {
current_cpu = invoker.cpu
current_os = invoker.os
# NOTE: The Zircon build uses "default" for the default optimization
# level, but the Fuchsia one uses either "debug" or "speed" if not
# set explicitly, depending on 'is_debug'.
#
# Until both defaults are unified, overwrite the Fuchsia value with
# the zircon one. This can also be overriden by a specific Zircon
# toolchain by setting it in its own toolchain_args scope.
#
# Note: when invoking the Zircon build, the Fuchsia build will set
# the 'optimize' build variable in the following cases:
# 'none', 'sanitizer', 'profile' and 'size'.
#
# Otherwise the default is used. Try to replicate this here.
#
optimize = _zircon_optimize
zx_assert_level = _assert_level
if (defined(invoker.toolchain_args)) {
forward_variables_from(invoker.toolchain_args,
"*",
[
"cpu",
"os",
"toolchain_variant",
])
}
# toolchain_variant is specific to the Fuchsia build, and documented in
# //build/config/BUILDCONFIG.gn.
toolchain_variant = {
}
toolchain_variant = _toolchain_variant
# 'zircon_toolchain' is a scope that will only be defined for Zircon
# toolchains, as a scope holding various keys used by misc targets to
# select build settings. This corresponds to the Zircon build
# 'toolchain' global toolchain variable, as well as a few Zircon global
# variables that have been moved here:
#
# cpu: [string] Target CPU using Clang conventions.
# target_tuple: [string] Target tuple using Clang conventions.
# environment: [string] The runtime environment that the binaries
# generated by this toolchain instance will run in. Used by the
# Zircon build rules in several places.
# link_output_suffix: [string] Suffix to append to linkable binaries
# with debug information.
# is_gcc: [boolean] True if this toolchain uses gcc.
#
# include_dirs: [list of paths]
# Supported to add an `-isystem <dir>` parameter per list item during
# the build, but never used by Zircon environments. In practice
# only set above (see definition of `tc` scope) for Win32 toolchains.
#
# lib_dirs: [list of paths]
# Library paths added to the "compiler" configuration, This is used
# to append the path of libc++.a's directory when gcc is used as
# a compiler.
#
# The following variables were used in the Zircon build but ignored here.
#
# runtime_deps_cflags: [list of strings]
# Only used in practice to implement linking to static libc++, and
# adding Asan/UBSan-related flags, which are handled differently in
# the Fuchsia build.
#
# executable_extension: [string]
# Either the empty string of "exe" for Windows. Handled differently
# in the Fuchsia build.
#
# output_name_suffix: [string]
# Suffix for output file names, never used by the Zircon build.
#
# breakpad_syms: [boolean]
# Override the value of the output_breakpad_syms build argument
# for a specific toolchain. Never used in the Zircon build.
#
# libprefix: [string]
# An optional prefix for shared libraries. Only used by the Zircon
# build to prepend "fuzzer." for fuzzed shared libraries in the
# "user" environment. All this is handled differently by the Fuchsia
# build in the fuchsia toolchains.
#
zircon_toolchain = {
}
zircon_toolchain = {
# This gets `tool_dir` et al as expected by c_utils.gni (set above).
forward_variables_from(tc, "*")
cpu = tc_cpu
target_tuple = tc_target
environment = invoker.environment
# This is used in setting metadata.link_output.
if (use_strip) {
link_output_suffix = ".debug"
} else {
link_output_suffix = ""
}
if (!defined(include_dirs)) {
include_dirs = []
}
if (!defined(lib_dirs)) {
lib_dirs = []
}
# NOTE: This was a global variable in the Zircon build.
is_gcc = use_gcc
}
}
}
}