blob: 755a6d121d38fb4dd2491729f9c59bb092d1a11b [file] [log] [blame]
# Copyright 2020 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
import("//build_overrides/pigweed.gni")
import("$dir_pw_toolchain/static_analysis_toolchain.gni")
import("$dir_pw_toolchain/universal_tools.gni")
declare_args() {
# Scope defining the current toolchain. Contains all of the arguments required
# by the generate_toolchain template. This should NOT be manually modified.
pw_toolchain_SCOPE = {
}
# Prefix for compilation commands (e.g. the path to a Goma or CCache compiler
# launcher). Example for ccache:
# gn gen out --args='pw_command_launcher="ccache"'
pw_command_launcher = ""
}
# Creates a toolchain target.
#
# Args:
# ar: (required) String indicating the archive tool to use.
# cc: (required) String indicating the C compiler to use.
# cxx: (required) String indicating the C++ compiler to use.
# ld: (optional) String indicating the linking binary to use.
# is_host_toolchain: (optional) Boolean indicating if the outputs are meant
# for the $host_os.
# final_binary_extension: (optional) The extension to apply to final linked
# binaries.
# link_whole_archive: (optional) Boolean indicating if the linker should load
# all object files when resolving symbols.
# link_group: (optional) Boolean indicating if the linker should use
# a group to resolve circular dependencies between artifacts.
# link_generate_map_file: (optional) Boolean indicating if to add linker
# flags to generate a mapfile. Defaults to true.
# generate_from: (optional) The full target name of the toolchain that can
# trigger this toolchain to be generated. GN only allows one toolchain to
# be generated at a given target path, so if multiple toolchains parse the
# same generate_toolchain target only one should declare a toolchain. This
# is primarily to allow generating sub-toolchains. Defaults to
# default_toolchain.
# defaults: (required) A scope setting GN build arg values to apply to GN
# targets in this toolchain. These take precedence over args.gni settings.
# static_analysis: (optional) A scope defining args to apply to the
# static_analysis toolchain. If the scope is not defined, static analysis
# will be disabled. If provided, static_analysis will be enabled iff
# required enabled field in scope is declared true. See
# static_analysis_toolchain.gni for more information on scope members.
#
# The defaults scope should contain values for builtin GN arguments:
# current_cpu: The CPU of the toolchain.
# Well known values include "arm", "arm64", "x64", "x86", and "mips".
# current_os: The OS of the toolchain. Defaults to "".
# Well known values include "win", "mac", "linux", "android", and "ios".
#
# TODO: b/234891809 - This should be renamed to pw_generate_toolchain.
template("generate_toolchain") {
assert(defined(invoker.defaults), "toolchain is missing 'defaults'")
# On the default toolchain invocation, you typically need to generate all
# toolchains you encounter. For sub-toolchains, they must be generated from
# the context of their parent.
if (defined(invoker.generate_from)) {
_generate_toolchain =
get_label_info(invoker.generate_from, "label_no_toolchain") ==
current_toolchain
} else {
_generate_toolchain = default_toolchain == current_toolchain
}
if (_generate_toolchain) {
# TODO(amontanez): This should be renamed to build_args as "defaults" isn't
# sufficiently descriptive.
invoker_toolchain_args = invoker.defaults
# These values should always be set as they influence toolchain
# behavior, but allow them to be unset as a transitional measure.
if (!defined(invoker_toolchain_args.current_cpu)) {
invoker_toolchain_args.current_cpu = ""
}
if (!defined(invoker_toolchain_args.current_os)) {
invoker_toolchain_args.current_os = ""
}
# Determine OS of toolchain, which is the builtin argument "current_os".
toolchain_os = invoker_toolchain_args.current_os
toolchain(target_name) {
# Uncomment this line to see which toolchains generate other toolchains.
# print("Generating toolchain: ${target_name} by ${current_toolchain}")
assert(defined(invoker.cc), "toolchain is missing 'cc'")
tool("asm") {
if (pw_command_launcher != "") {
command_launcher = pw_command_launcher
}
depfile = "{{output}}.d"
command = string_join(" ",
[
invoker.cc,
"-MMD -MF $depfile", # Write out dependencies.
"{{asmflags}}",
"{{cflags}}",
"{{defines}}",
"{{include_dirs}}",
"-c {{source}}",
"-o {{output}}",
])
depsformat = "gcc"
description = "as {{output}}"
outputs = [
# Use {{source_file_part}}, which includes the extension, instead of
# {{source_name_part}} so that object files created from <file_name>.c
# and <file_name>.cc sources are unique.
"{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o",
]
}
tool("cc") {
if (pw_command_launcher != "") {
command_launcher = pw_command_launcher
}
depfile = "{{output}}.d"
command = string_join(" ",
[
invoker.cc,
"-MMD -MF $depfile", # Write out dependencies.
"{{cflags}}",
"{{cflags_c}}", # Must come after {{cflags}}.
"{{defines}}",
"{{include_dirs}}",
"-c {{source}}",
"-o {{output}}",
])
depsformat = "gcc"
description = "cc {{output}}"
outputs = [
"{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o",
]
}
assert(defined(invoker.cxx), "toolchain is missing 'cxx'")
tool("cxx") {
if (pw_command_launcher != "") {
command_launcher = pw_command_launcher
}
depfile = "{{output}}.d"
command = string_join(" ",
[
invoker.cxx,
"-MMD -MF $depfile", # Write out dependencies.
"{{cflags}}",
"{{cflags_cc}}", # Must come after {{cflags}}.
"{{defines}}",
"{{include_dirs}}",
"-c {{source}}",
"-o {{output}}",
])
depsformat = "gcc"
description = "c++ {{output}}"
outputs = [
"{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o",
]
}
tool("objc") {
if (pw_command_launcher != "") {
command_launcher = pw_command_launcher
}
depfile = "{{output}}.d"
command =
string_join(" ",
[
invoker.cc,
"-MMD -MF $depfile", # Write out dependencies.
"{{cflags}}",
"{{cflags_objc}}", # Must come after {{cflags}}.
"{{defines}}",
"{{include_dirs}}",
"{{framework_dirs}}",
"-c {{source}}",
"-o {{output}}",
])
depsformat = "gcc"
description = "objc {{output}}"
outputs = [
"{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o",
]
}
tool("objcxx") {
if (pw_command_launcher != "") {
command_launcher = pw_command_launcher
}
depfile = "{{output}}.d"
command =
string_join(" ",
[
invoker.cxx,
"-MMD -MF $depfile", # Write out dependencies.
"{{cflags}}",
"{{cflags_objcc}}", # Must come after {{cflags}}.
"{{defines}}",
"{{include_dirs}}",
"{{framework_dirs}}",
"-c {{source}}",
"-o {{output}}",
])
depsformat = "gcc"
description = "objc++ {{output}}"
outputs = [
"{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o",
]
}
assert(defined(invoker.ar), "toolchain is missing 'ar'")
tool("alink") {
if (host_os == "win") {
rspfile = "{{output}}.rsp"
rspfile_content = "{{inputs}}"
rm_command = "del /F /Q \"{{output}}\" 2> NUL"
command = "cmd /c \"($rm_command) & ${invoker.ar} {{arflags}} rcs {{output}} @$rspfile\""
} else {
command = "rm -f {{output}} && ${invoker.ar} {{arflags}} rcs {{output}} {{inputs}}"
}
description = "ar {{target_output_name}}{{output_extension}}"
outputs =
[ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ]
default_output_extension = ".a"
default_output_dir = "{{target_out_dir}}/lib"
}
lib_switch = "-l"
lib_dir_switch = "-L"
_link_outfile =
"{{output_dir}}/{{target_output_name}}{{output_extension}}"
if (defined(invoker.ld)) {
_link_flags = [
invoker.ld,
"{{ldflags}}",
]
} else {
_link_flags = [
invoker.cxx,
"{{ldflags}}",
]
}
if (defined(invoker.link_generate_map_file)) {
_link_generate_map_file = invoker.link_generate_map_file
} else {
_link_generate_map_file = true
}
_link_outputs = [ _link_outfile ]
if (_link_generate_map_file) {
_link_mapfile = "{{output_dir}}/{{target_output_name}}.map"
if (toolchain_os == "mac" || toolchain_os == "ios") {
_link_flags += [
# Output a map file that shows symbols and their location.
"-Wl,-map,$_link_mapfile",
]
} else {
_link_flags += [
# Output a map file that shows symbols and their location.
"-Wl,-Map,$_link_mapfile",
"-Wl,--cref",
]
}
_link_outputs += [ _link_mapfile ]
}
_rsp_file = "$_link_outfile.rsp"
_rsp_contents = []
_link_group = defined(invoker.link_group) && invoker.link_group
if (_link_group) {
_rsp_contents += [ "-Wl,--start-group" ]
}
_rsp_contents += [ "{{inputs}}" ]
_rsp_contents += [ "{{frameworks}}" ]
if (defined(invoker.link_whole_archive) && invoker.link_whole_archive) {
# Load all object files from all libraries to resolve symbols.
# Short of living in the ideal world where all dependency graphs
# among static libs are acyclic and all developers diligently
# express such graphs in terms that GN understands, this is the
# safest option.
# Make sure you use this with --gc-sections, otherwise the
# resulting binary will contain every symbol defined in every
# input file and every static library. That could be quite a lot.
_rsp_contents += [
"-Wl,--whole-archive",
"{{libs}}",
"-Wl,--no-whole-archive",
]
} else {
_rsp_contents += [ "{{libs}}" ]
}
if (_link_group) {
_rsp_contents += [ "-Wl,--end-group" ]
}
_rsp_command = string_join(" ", _rsp_contents)
_link_flags += [ "@$_rsp_file" ]
_link_flags += [ "-o $_link_outfile" ]
_link_command = string_join(" ", _link_flags)
tool("link") {
command = _link_command
rspfile = _rsp_file
rspfile_content = _rsp_command
description = "ld $_link_outfile"
outputs = _link_outputs
default_output_dir = "{{target_out_dir}}/bin"
if (defined(invoker.final_binary_extension)) {
default_output_extension = invoker.final_binary_extension
} else if (toolchain_os == "win") {
default_output_extension = ".exe"
} else {
default_output_extension = ""
}
}
tool("solink") {
command = _link_command + " -shared"
rspfile = _rsp_file
rspfile_content = _rsp_command
description = "ld -shared $_link_outfile"
outputs = _link_outputs
default_output_dir = "{{target_out_dir}}/lib"
default_output_extension = ".so"
}
tool("stamp") {
# GN-ism: GN gets mad if you directly forward the contents of
# pw_universal_stamp.
_stamp = pw_universal_stamp
forward_variables_from(_stamp, "*")
}
tool("copy") {
# GN-ism: GN gets mad if you directly forward the contents of
# pw_universal_copy.
_copy = pw_universal_copy
forward_variables_from(_copy, "*")
}
# Build arguments to be overridden when compiling cross-toolchain:
#
# pw_toolchain_defaults: A scope setting defaults to apply to GN targets
# in this toolchain. It is analogous to $pw_target_defaults in
# $dir_pigweed/pw_vars_default.gni.
#
# pw_toolchain_SCOPE: A copy of the invoker scope that defines the
# toolchain. Used for generating derivative toolchains.
#
toolchain_args = {
pw_toolchain_SCOPE = {
}
pw_toolchain_SCOPE = {
forward_variables_from(invoker, "*")
name = target_name
}
forward_variables_from(invoker_toolchain_args, "*")
}
_generate_rust_tools = defined(invoker.rustc)
if (_generate_rust_tools) {
if (toolchain_os == "mac") {
_dylib_extension = ".dylib"
} else if (toolchain_os == "win") {
_dylib_extension = ".dll"
} else {
_dylib_extension = ".so"
}
_rustc_command = string_join(
" ",
[
# TODO: b/234872510 - Ensure this works with Windows.
"RUST_BACKTRACE=1",
"{{rustenv}}",
invoker.rustc,
"{{source}}",
"--crate-name {{crate_name}}",
"--crate-type {{crate_type}}",
"{{externs}}",
"{{rustdeps}}",
"{{rustflags}}",
"-D warnings",
"--color always",
"--emit=dep-info={{output}}.d,link",
"-o {{output_dir}}/{{target_output_name}}{{output_extension}}",
])
_output = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
tool("rust_bin") {
description = "rustc {{output}}"
default_output_dir = "{{target_out_dir}}/bin"
depfile = "{{output}}.d"
command = _rustc_command
outputs = [ _output ]
}
tool("rust_rlib") {
description = "rustc {{output}}"
default_output_dir = "{{target_out_dir}}/lib"
depfile = "{{output}}.d"
output_prefix = "lib"
default_output_extension = ".rlib"
command = _rustc_command
outputs = [ _output ]
}
tool("rust_macro") {
description = "rustc {{output}}"
default_output_dir = "{{target_out_dir}}/lib"
depfile = "{{output}}.d"
output_prefix = "lib"
default_output_extension = _dylib_extension
command = _rustc_command
outputs = [ _output ]
}
}
}
_generate_static_analysis_toolchain = false
if (defined(invoker.static_analysis)) {
_static_analysis_args = invoker.static_analysis
assert(defined(_static_analysis_args.enabled),
"static_analysis.enabled missing from scope.")
_generate_static_analysis_toolchain = _static_analysis_args.enabled
}
if (_generate_static_analysis_toolchain) {
pw_static_analysis_toolchain(target_name + ".static_analysis") {
forward_variables_from(invoker, "*")
}
}
} else {
not_needed(invoker, "*")
group(target_name) {
}
}
}
# Creates a series of toolchain targets with common compiler options.
#
# Args:
# toolchains: List of scopes defining each of the desired toolchains.
# The scope must contain a "name" variable; other variables are forwarded to
# $generate_toolchain.
template("generate_toolchains") {
not_needed([ "target_name" ])
assert(defined(invoker.toolchains),
"generate_toolchains must be called with a list of toolchains")
# Create a target for each of the desired toolchains, appending its own cflags
# and ldflags to the common ones.
foreach(_toolchain, invoker.toolchains) {
generate_toolchain(_toolchain.name) {
forward_variables_from(_toolchain, "*", [ "name" ])
}
}
}