1# Copyright 2020 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14 15import("//build_overrides/pigweed.gni") 16 17import("$dir_pw_toolchain/static_analysis_toolchain.gni") 18import("$dir_pw_toolchain/universal_tools.gni") 19 20declare_args() { 21 # Scope defining the current toolchain. Contains all of the arguments required 22 # by the generate_toolchain template. This should NOT be manually modified. 23 pw_toolchain_SCOPE = { 24 } 25 26 # Prefix for compilation commands (e.g. the path to a Goma or CCache compiler 27 # launcher). Example for ccache: 28 # gn gen out --args='pw_command_launcher="ccache"' 29 pw_command_launcher = "" 30} 31 32# Creates a toolchain target. 33# 34# Args: 35# ar: (required) String indicating the archive tool to use. 36# cc: (required) String indicating the C compiler to use. 37# cxx: (required) String indicating the C++ compiler to use. 38# ld: (optional) String indicating the linking binary to use. 39# is_host_toolchain: (optional) Boolean indicating if the outputs are meant 40# for the $host_os. 41# final_binary_extension: (optional) The extension to apply to final linked 42# binaries. 43# link_whole_archive: (optional) Boolean indicating if the linker should load 44# all object files when resolving symbols. 45# link_group: (optional) Boolean indicating if the linker should use 46# a group to resolve circular dependencies between artifacts. 47# link_generate_map_file: (optional) Boolean indicating if to add linker 48# flags to generate a mapfile. Defaults to true. 49# generate_from: (optional) The full target name of the toolchain that can 50# trigger this toolchain to be generated. GN only allows one toolchain to 51# be generated at a given target path, so if multiple toolchains parse the 52# same generate_toolchain target only one should declare a toolchain. This 53# is primarily to allow generating sub-toolchains. Defaults to 54# default_toolchain. 55# defaults: (required) A scope setting GN build arg values to apply to GN 56# targets in this toolchain. These take precedence over args.gni settings. 57# static_analysis: (optional) A scope defining args to apply to the 58# static_analysis toolchain. If the scope is not defined, static analysis 59# will be disabled. If provided, static_analysis will be enabled iff 60# required enabled field in scope is declared true. See 61# static_analysis_toolchain.gni for more information on scope members. 62# 63# The defaults scope should contain values for builtin GN arguments: 64# current_cpu: The CPU of the toolchain. 65# Well known values include "arm", "arm64", "x64", "x86", and "mips". 66# current_os: The OS of the toolchain. Defaults to "". 67# Well known values include "win", "mac", "linux", "android", and "ios". 68# 69# TODO: b/234891809 - This should be renamed to pw_generate_toolchain. 70template("generate_toolchain") { 71 assert(defined(invoker.defaults), "toolchain is missing 'defaults'") 72 73 # On the default toolchain invocation, you typically need to generate all 74 # toolchains you encounter. For sub-toolchains, they must be generated from 75 # the context of their parent. 76 if (defined(invoker.generate_from)) { 77 _generate_toolchain = 78 get_label_info(invoker.generate_from, "label_no_toolchain") == 79 current_toolchain 80 } else { 81 _generate_toolchain = default_toolchain == current_toolchain 82 } 83 84 if (_generate_toolchain) { 85 # TODO(amontanez): This should be renamed to build_args as "defaults" isn't 86 # sufficiently descriptive. 87 invoker_toolchain_args = invoker.defaults 88 89 # These values should always be set as they influence toolchain 90 # behavior, but allow them to be unset as a transitional measure. 91 if (!defined(invoker_toolchain_args.current_cpu)) { 92 invoker_toolchain_args.current_cpu = "" 93 } 94 if (!defined(invoker_toolchain_args.current_os)) { 95 invoker_toolchain_args.current_os = "" 96 } 97 98 # Determine OS of toolchain, which is the builtin argument "current_os". 99 toolchain_os = invoker_toolchain_args.current_os 100 101 toolchain(target_name) { 102 # Uncomment this line to see which toolchains generate other toolchains. 103 # print("Generating toolchain: ${target_name} by ${current_toolchain}") 104 105 assert(defined(invoker.cc), "toolchain is missing 'cc'") 106 tool("asm") { 107 if (pw_command_launcher != "") { 108 command_launcher = pw_command_launcher 109 } 110 depfile = "{{output}}.d" 111 command = string_join(" ", 112 [ 113 invoker.cc, 114 "-MMD -MF $depfile", # Write out dependencies. 115 "{{asmflags}}", 116 "{{cflags}}", 117 "{{defines}}", 118 "{{include_dirs}}", 119 "-c {{source}}", 120 "-o {{output}}", 121 ]) 122 depsformat = "gcc" 123 description = "as {{output}}" 124 outputs = [ 125 # Use {{source_file_part}}, which includes the extension, instead of 126 # {{source_name_part}} so that object files created from <file_name>.c 127 # and <file_name>.cc sources are unique. 128 "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o", 129 ] 130 } 131 132 tool("cc") { 133 if (pw_command_launcher != "") { 134 command_launcher = pw_command_launcher 135 } 136 depfile = "{{output}}.d" 137 command = string_join(" ", 138 [ 139 invoker.cc, 140 "-MMD -MF $depfile", # Write out dependencies. 141 "{{cflags}}", 142 "{{cflags_c}}", # Must come after {{cflags}}. 143 "{{defines}}", 144 "{{include_dirs}}", 145 "-c {{source}}", 146 "-o {{output}}", 147 ]) 148 depsformat = "gcc" 149 description = "cc {{output}}" 150 outputs = [ 151 "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o", 152 ] 153 } 154 155 assert(defined(invoker.cxx), "toolchain is missing 'cxx'") 156 tool("cxx") { 157 if (pw_command_launcher != "") { 158 command_launcher = pw_command_launcher 159 } 160 depfile = "{{output}}.d" 161 command = string_join(" ", 162 [ 163 invoker.cxx, 164 "-MMD -MF $depfile", # Write out dependencies. 165 "{{cflags}}", 166 "{{cflags_cc}}", # Must come after {{cflags}}. 167 "{{defines}}", 168 "{{include_dirs}}", 169 "-c {{source}}", 170 "-o {{output}}", 171 ]) 172 depsformat = "gcc" 173 description = "c++ {{output}}" 174 outputs = [ 175 "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o", 176 ] 177 } 178 179 tool("objc") { 180 if (pw_command_launcher != "") { 181 command_launcher = pw_command_launcher 182 } 183 depfile = "{{output}}.d" 184 command = 185 string_join(" ", 186 [ 187 invoker.cc, 188 "-MMD -MF $depfile", # Write out dependencies. 189 "{{cflags}}", 190 "{{cflags_objc}}", # Must come after {{cflags}}. 191 "{{defines}}", 192 "{{include_dirs}}", 193 "{{framework_dirs}}", 194 "-c {{source}}", 195 "-o {{output}}", 196 ]) 197 depsformat = "gcc" 198 description = "objc {{output}}" 199 outputs = [ 200 "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o", 201 ] 202 } 203 204 tool("objcxx") { 205 if (pw_command_launcher != "") { 206 command_launcher = pw_command_launcher 207 } 208 depfile = "{{output}}.d" 209 command = 210 string_join(" ", 211 [ 212 invoker.cxx, 213 "-MMD -MF $depfile", # Write out dependencies. 214 "{{cflags}}", 215 "{{cflags_objcc}}", # Must come after {{cflags}}. 216 "{{defines}}", 217 "{{include_dirs}}", 218 "{{framework_dirs}}", 219 "-c {{source}}", 220 "-o {{output}}", 221 ]) 222 depsformat = "gcc" 223 description = "objc++ {{output}}" 224 outputs = [ 225 "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o", 226 ] 227 } 228 229 assert(defined(invoker.ar), "toolchain is missing 'ar'") 230 tool("alink") { 231 if (host_os == "win") { 232 rspfile = "{{output}}.rsp" 233 rspfile_content = "{{inputs}}" 234 rm_command = "del /F /Q \"{{output}}\" 2> NUL" 235 command = "cmd /c \"($rm_command) & ${invoker.ar} {{arflags}} rcs {{output}} @$rspfile\"" 236 } else { 237 command = "rm -f {{output}} && ${invoker.ar} {{arflags}} rcs {{output}} {{inputs}}" 238 } 239 240 description = "ar {{target_output_name}}{{output_extension}}" 241 outputs = 242 [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ] 243 default_output_extension = ".a" 244 default_output_dir = "{{target_out_dir}}/lib" 245 } 246 247 lib_switch = "-l" 248 lib_dir_switch = "-L" 249 250 _link_outfile = 251 "{{output_dir}}/{{target_output_name}}{{output_extension}}" 252 if (defined(invoker.ld)) { 253 _link_flags = [ 254 invoker.ld, 255 "{{ldflags}}", 256 ] 257 } else { 258 _link_flags = [ 259 invoker.cxx, 260 "{{ldflags}}", 261 ] 262 } 263 264 if (defined(invoker.link_generate_map_file)) { 265 _link_generate_map_file = invoker.link_generate_map_file 266 } else { 267 _link_generate_map_file = true 268 } 269 270 _link_outputs = [ _link_outfile ] 271 272 if (_link_generate_map_file) { 273 _link_mapfile = "{{output_dir}}/{{target_output_name}}.map" 274 275 if (toolchain_os == "mac" || toolchain_os == "ios") { 276 _link_flags += [ 277 # Output a map file that shows symbols and their location. 278 "-Wl,-map,$_link_mapfile", 279 ] 280 } else { 281 _link_flags += [ 282 # Output a map file that shows symbols and their location. 283 "-Wl,-Map,$_link_mapfile", 284 "-Wl,--cref", 285 ] 286 } 287 _link_outputs += [ _link_mapfile ] 288 } 289 290 _rsp_file = "$_link_outfile.rsp" 291 _rsp_contents = [] 292 293 _link_group = defined(invoker.link_group) && invoker.link_group 294 if (_link_group) { 295 _rsp_contents += [ "-Wl,--start-group" ] 296 } 297 _rsp_contents += [ "{{inputs}}" ] 298 _rsp_contents += [ "{{frameworks}}" ] 299 300 if (defined(invoker.link_whole_archive) && invoker.link_whole_archive) { 301 # Load all object files from all libraries to resolve symbols. 302 # Short of living in the ideal world where all dependency graphs 303 # among static libs are acyclic and all developers diligently 304 # express such graphs in terms that GN understands, this is the 305 # safest option. 306 # Make sure you use this with --gc-sections, otherwise the 307 # resulting binary will contain every symbol defined in every 308 # input file and every static library. That could be quite a lot. 309 _rsp_contents += [ 310 "-Wl,--whole-archive", 311 "{{libs}}", 312 "-Wl,--no-whole-archive", 313 ] 314 } else { 315 _rsp_contents += [ "{{libs}}" ] 316 } 317 318 if (_link_group) { 319 _rsp_contents += [ "-Wl,--end-group" ] 320 } 321 _rsp_command = string_join(" ", _rsp_contents) 322 323 _link_flags += [ "@$_rsp_file" ] 324 _link_flags += [ "-o $_link_outfile" ] 325 326 _link_command = string_join(" ", _link_flags) 327 328 tool("link") { 329 command = _link_command 330 rspfile = _rsp_file 331 rspfile_content = _rsp_command 332 description = "ld $_link_outfile" 333 outputs = _link_outputs 334 default_output_dir = "{{target_out_dir}}/bin" 335 336 if (defined(invoker.final_binary_extension)) { 337 default_output_extension = invoker.final_binary_extension 338 } else if (toolchain_os == "win") { 339 default_output_extension = ".exe" 340 } else { 341 default_output_extension = "" 342 } 343 } 344 345 tool("solink") { 346 command = _link_command + " -shared" 347 rspfile = _rsp_file 348 rspfile_content = _rsp_command 349 description = "ld -shared $_link_outfile" 350 outputs = _link_outputs 351 default_output_dir = "{{target_out_dir}}/lib" 352 default_output_extension = ".so" 353 } 354 355 tool("stamp") { 356 # GN-ism: GN gets mad if you directly forward the contents of 357 # pw_universal_stamp. 358 _stamp = pw_universal_stamp 359 forward_variables_from(_stamp, "*") 360 } 361 362 tool("copy") { 363 # GN-ism: GN gets mad if you directly forward the contents of 364 # pw_universal_copy. 365 _copy = pw_universal_copy 366 forward_variables_from(_copy, "*") 367 } 368 369 # Build arguments to be overridden when compiling cross-toolchain: 370 # 371 # pw_toolchain_defaults: A scope setting defaults to apply to GN targets 372 # in this toolchain. It is analogous to $pw_target_defaults in 373 # $dir_pigweed/pw_vars_default.gni. 374 # 375 # pw_toolchain_SCOPE: A copy of the invoker scope that defines the 376 # toolchain. Used for generating derivative toolchains. 377 # 378 toolchain_args = { 379 pw_toolchain_SCOPE = { 380 } 381 pw_toolchain_SCOPE = { 382 forward_variables_from(invoker, "*") 383 name = target_name 384 } 385 forward_variables_from(invoker_toolchain_args, "*") 386 } 387 388 _generate_rust_tools = defined(invoker.rustc) 389 if (_generate_rust_tools) { 390 if (toolchain_os == "mac") { 391 _dylib_extension = ".dylib" 392 } else if (toolchain_os == "win") { 393 _dylib_extension = ".dll" 394 } else { 395 _dylib_extension = ".so" 396 } 397 398 _rustc_command = string_join( 399 " ", 400 [ 401 # TODO: b/234872510 - Ensure this works with Windows. 402 "RUST_BACKTRACE=1", 403 "{{rustenv}}", 404 invoker.rustc, 405 "{{source}}", 406 "--crate-name {{crate_name}}", 407 "--crate-type {{crate_type}}", 408 "{{externs}}", 409 "{{rustdeps}}", 410 "{{rustflags}}", 411 "-D warnings", 412 "--color always", 413 "--emit=dep-info={{output}}.d,link", 414 "-o {{output_dir}}/{{target_output_name}}{{output_extension}}", 415 ]) 416 417 _output = "{{output_dir}}/{{target_output_name}}{{output_extension}}" 418 419 tool("rust_bin") { 420 description = "rustc {{output}}" 421 default_output_dir = "{{target_out_dir}}/bin" 422 depfile = "{{output}}.d" 423 command = _rustc_command 424 outputs = [ _output ] 425 } 426 427 tool("rust_rlib") { 428 description = "rustc {{output}}" 429 default_output_dir = "{{target_out_dir}}/lib" 430 depfile = "{{output}}.d" 431 output_prefix = "lib" 432 default_output_extension = ".rlib" 433 command = _rustc_command 434 outputs = [ _output ] 435 } 436 437 tool("rust_macro") { 438 description = "rustc {{output}}" 439 default_output_dir = "{{target_out_dir}}/lib" 440 depfile = "{{output}}.d" 441 output_prefix = "lib" 442 default_output_extension = _dylib_extension 443 command = _rustc_command 444 outputs = [ _output ] 445 } 446 } 447 } 448 449 _generate_static_analysis_toolchain = false 450 if (defined(invoker.static_analysis)) { 451 _static_analysis_args = invoker.static_analysis 452 assert(defined(_static_analysis_args.enabled), 453 "static_analysis.enabled missing from scope.") 454 _generate_static_analysis_toolchain = _static_analysis_args.enabled 455 } 456 if (_generate_static_analysis_toolchain) { 457 pw_static_analysis_toolchain(target_name + ".static_analysis") { 458 forward_variables_from(invoker, "*") 459 } 460 } 461 } else { 462 not_needed(invoker, "*") 463 group(target_name) { 464 } 465 } 466} 467 468# Creates a series of toolchain targets with common compiler options. 469# 470# Args: 471# toolchains: List of scopes defining each of the desired toolchains. 472# The scope must contain a "name" variable; other variables are forwarded to 473# $generate_toolchain. 474template("generate_toolchains") { 475 not_needed([ "target_name" ]) 476 assert(defined(invoker.toolchains), 477 "generate_toolchains must be called with a list of toolchains") 478 479 # Create a target for each of the desired toolchains, appending its own cflags 480 # and ldflags to the common ones. 481 foreach(_toolchain, invoker.toolchains) { 482 generate_toolchain(_toolchain.name) { 483 forward_variables_from(_toolchain, "*", [ "name" ]) 484 } 485 } 486} 487