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