| # Copyright 2025 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/python/python_action.gni") |
| import("//build/python/python_binary.gni") |
| import("//build/python/python_library.gni") |
| import("//build/testing/golden_files.gni") |
| import("../libc.gni") |
| import("libc_headers.gni") |
| |
| # See README.md for a full explanation. |
| # |
| # This file (with libc_headers.gni) manages all the public libc header files. |
| # These go into the "sysroot" built by //zircon/public/sysroot; that's used to |
| # compile most Fuchsia user code, and it goes into the SDK. |
| # |
| # Any changes to these files require manual API review approval via update of |
| # the sysroot.api file. Some of these files come from llvm-libc. But the |
| # auto-roller that updates the llvm-libc code will never update sysroot.api; so |
| # llvm-libc header changes always require a manual roll that includes the |
| # sysroot.api update along with the //manifests update in a single commit. |
| # |
| # The libc_headers.include_dirs list drives everything done here. For each |
| # source directory ${set.dir} there with generated headers in `.yaml` files, |
| # there is a JSON file in _this_ directory (named by its ${set.json_file}, its |
| # data already read into ${set.json_data} by libc_headers.gni). Those files |
| # get regenerated here using the golden_files() mechanism. However, the golden |
| # JSON files in the source tree are what drive the libc_headers.gni lists that |
| # everything else uses. After changing this list, the next build with |
| # update_goldens=true will update the JSON file in the sources; that will |
| # necessitate another build to pick up the new lists and generate the headers |
| # correctly so that the sysroot can be updated. |
| |
| # Directory holding the hdrgen Python sources. |
| hdrgen_dir = "$llvm_libc/utils/hdrgen" |
| |
| # This target is in an sdk.deps list in sysroot_entries, to ensure that the |
| # generated headers are in place. This checking only ever happens in the |
| # $default_toolchain and is not repeated elsewhere. |
| group("include") { |
| public_deps = [] |
| foreach(set, libc_headers.include_dirs) { |
| if (defined(set.json_file)) { |
| # Each set of generated headers has these targets defined below. |
| public_deps += [ |
| ":check-${set.json_file}($default_toolchain)", |
| ":hdrgen-${set.json_file}($default_toolchain)", |
| ":hdrgen.deps-${set.json_file}($default_toolchain)", |
| ] |
| } |
| } |
| } |
| |
| if (current_toolchain == default_toolchain) { |
| reified_sets = [] |
| foreach(set, libc_headers.include_dirs) { |
| if (defined(set.json_file)) { |
| # Fill out some derived data from the set. The set.gen_dir formulation |
| # here must match that used in libc_headers.gni for sdk.include_dir! |
| set.gen_dir = get_label_info(set.dir, "target_gen_dir") |
| set.yaml = filter_include(set.public, [ "*.yaml" ]) |
| set.generated_public = [] |
| foreach(header, set.yaml) { |
| header_dir = get_path_info(header, "dir") |
| header_name = get_path_info(header, "name") |
| if (header_dir == ".") { |
| header_dir = "" |
| } else { |
| header_dir += "/" |
| } |
| assert(header == "$header_dir$header_name.yaml") |
| set.generated_public += [ "${set.gen_dir}/$header_dir$header_name.h" ] |
| } |
| |
| # Accumulate all the header dependencies from the JSON data for the set. |
| set.generated_sources = [] |
| foreach(header, set.json_data) { |
| header.relative_includes = filter_exclude(header.includes, [ "<*>" ]) |
| set.generated_sources += header.relative_includes |
| set.generated_sources -= header.relative_includes |
| set.generated_sources += header.relative_includes |
| } |
| |
| # This generates the public header files for each `.yaml` file listed. |
| # The single "foreach" target generates independent per-file targets for |
| # each `.h` file depending only on its particular `.yaml` file. |
| python_action_foreach("hdrgen-${set.json_file}") { |
| binary_label = ":hdrgen" |
| |
| # Each `.yaml` in $sources produces the corresponding `.h` in $outputs. |
| sources = rebase_path(set.yaml, ".", set.dir) |
| |
| # This is evaluated only in $default_toolchain, where $root_gen_dir is |
| # the prefix of ${set.gen_dir} before the full source-relative path |
| # in ${set.dir}. This isn't simpler because the action_foreach() path |
| # expansion logic only does a few fixed things, and it can't be used to |
| # distinguish the directory prefix that's part of the header name |
| # (e.g. sys/) from the rest of the path to the sources or outputs |
| # directory. |
| outputs = [ |
| "$root_gen_dir/{{source_root_relative_dir}}/{{source_name_part}}.h", |
| ] |
| |
| # That should wind up the same as "${set.gen_dir}/header" for each one. |
| _processed_sources = process_file_template(sources, outputs) |
| assert(_processed_sources == set.generated_public, |
| "process_file_template($sources, $outputs) => " + |
| "$_processed_sources != ${set.generated_public} in $set") |
| |
| depfile = "${outputs[0]}.d" |
| args = [ |
| "--output=" + rebase_path(outputs[0], root_build_dir), |
| "--depfile=" + rebase_path(depfile, root_build_dir), |
| "--write-if-changed", |
| "{{source}}", |
| ] |
| |
| metadata = { |
| # Populate build_api("generated_sources") to ensure all the headers |
| # are present for analysis. |
| generated_sources = rebase_path(set.generated_public, root_build_dir) |
| } |
| } |
| |
| # The header dependencies are referenced as `#include "..."` with |
| # relative paths to those internal headers from the referrer _as they |
| # will be installed_. But within the build, the _generated_ referrer |
| # is not in the same root "include" directory ${set.dir} as the |
| # referents--it's in ${set.gen_dir} instead. That is, unless it actually |
| # exists in a _previous_ set (i.e. $llvm_libc/include) instead. So each |
| # set must be checked in order for the one that has the real intended |
| # target of that relative `#include` path. |
| set.redirects = [] |
| foreach(header, set.generated_sources) { |
| foreach(previous, reified_sets) { |
| # There is no `break` in GN's `foreach`, so `header = false` will |
| # indicate the loop is done. |
| if (header != false && previous.generated_sources + [ header ] - |
| [ header ] != previous.generated_sources) { |
| set.redirects += [ |
| { |
| dir = previous.dir |
| file = header |
| }, |
| ] |
| header = false # No need to consult more sets. |
| } |
| } |
| if (header != false) { |
| # It's not from a previous set, so it's from this one. |
| set.redirects += [ |
| { |
| dir = set.dir |
| file = header |
| }, |
| ] |
| } |
| } |
| |
| # Now generate a redirecting `#include` file at the expected relative |
| # path in ${set.gen_dir} leading to whatever ${some_set.dir} has it. |
| set.redirect_labels = [] |
| set.gen_count = 0 |
| foreach(redirect, set.redirects) { |
| set.gen_count = set.gen_count + 1 |
| _label = "hdrgen.redirect.${set.gen_count}-${set.json_file}" |
| set.redirect_labels += [ ":$_label" ] |
| generated_file(_label) { |
| visibility = [ ":*" ] |
| output_conversion = "list lines" |
| outputs = [ "${set.gen_dir}/${redirect.file}" ] |
| _output_path = rebase_path(outputs[0], root_build_dir) |
| _redirect_path = |
| rebase_path("${redirect.dir}/${redirect.file}", root_build_dir) |
| _relative_path = rebase_path("${redirect.dir}/${redirect.file}", |
| get_path_info(outputs[0], "dir"), |
| root_build_dir) |
| deps = [ ":hdrgen-${set.json_file}" ] |
| contents = [ |
| # The comment line makes each file's contents unique. This works |
| # around an issue where RBE hard-links identical input files |
| # together and Clang generates differing depfile output based on |
| # whether two identical files are hard links or separate files. |
| "// ${_output_path} -> ${_redirect_path}", |
| "#include \"${_relative_path}\" // IWYU pragma: export", |
| ] |
| metadata = { |
| # Each target rolls up its own build_api("generated_sources") data. |
| generated_sources = rebase_path(outputs, root_build_dir) |
| } |
| } |
| } |
| |
| # This reaches all the generated_file() targets (and their metadata), |
| # to ensure they are all generated when they're needed. |
| group("hdrgen.deps-${set.json_file}") { |
| visibility = [ ":*" ] |
| public_deps = set.redirect_labels |
| } |
| |
| # Stash the elaborated set for later reference. The next iteration will |
| # consult this to define its redirects. |
| reified_sets += [ set ] |
| |
| # This regenerates the JSON file itself, in a $target_gen_dir location. |
| # This shows what the checked-in ${set.json_file} _should_ contain based |
| # on current libc_headers.include_dirs specs and `.yaml` file contents. |
| python_action(set.json_file) { |
| visibility = [ ":*" ] |
| binary_label = ":hdrgen" |
| |
| sources = |
| rebase_path(filter_include(set.public, [ "*.yaml" ]), ".", set.dir) |
| outputs = [ "$target_gen_dir/$target_name" ] |
| depfile = "${outputs[0]}.d" |
| args = [ |
| "--output=" + rebase_path(outputs[0], root_build_dir), |
| "--depfile=" + rebase_path(depfile, root_build_dir), |
| "--write-if-changed", |
| "--json", |
| ] + rebase_path(sources, root_build_dir) |
| } |
| |
| # This ensures that the checked-in file matches the regenerated file. |
| # The "golden" file is the source of truth for the rest of the build |
| # (i.e. for populating the sysroot), but what it's generated from is the |
| # source of truth for this directory's GN rules. |
| golden_files("check-${set.json_file}") { |
| visibility = [ ":*" ] |
| deps = [ ":${set.json_file}" ] |
| _json_outputs = get_target_outputs(deps[0]) |
| comparisons = [ |
| { |
| golden = set.json_file |
| candidate = _json_outputs[0] |
| }, |
| ] |
| } |
| } |
| } |
| } |
| |
| # This reaches all the public libc headers (ones that will be installed) |
| # directly at the source of truth. It's only used for compiling libc code. |
| # Note this includes the libc unit test code, as well as the hermetic partial |
| # libc bits built for special environments. |
| # |
| # source_set() is actually a template that injects additional deps...that |
| # reach back here. So this must use basic_source_set() to have only the |
| # direct effects spelled out here and nothing else implicit. |
| basic_source_set("headers") { |
| visibility = [ |
| "../*", |
| "//build/config/zircon:user_deps", |
| "//zircon/kernel/lib/userabi/userboot/*", |
| "//zircon/system/ulib/ldmsg/*", |
| "//zircon/system/utest/core/threads/*", |
| "//zircon/third_party/scudo/*", |
| "//zircon/third_party/ulib/musl/*", |
| ] |
| |
| # Make sure no set_defaults() configs contribute anything that might |
| # propagate up from here. |
| configs = [] |
| |
| public_configs = [ ":headers.config" ] |
| public_deps = [ "//zircon/system/public" ] |
| |
| deps = [] |
| public = [] |
| sources = [] |
| foreach(set, libc_headers.include_dirs) { |
| public += |
| rebase_path(filter_exclude(set.public, [ "*.yaml" ]), ".", set.dir) |
| sources += rebase_path(set.sources, ".", set.dir) |
| |
| if (defined(set.json_file)) { |
| public_deps += [ ":hdrgen-${set.json_file}($default_toolchain)" ] |
| deps += [ ":hdrgen.deps-${set.json_file}($default_toolchain)" ] |
| set.gen_dir = |
| get_label_info("${set.dir}($default_toolchain)", "target_gen_dir") |
| foreach(header, filter_include(set.public, [ "*.yaml" ])) { |
| header_dir = get_path_info(header, "dir") |
| header_name = get_path_info(header, "name") |
| if (header_dir == ".") { |
| header_dir = "" |
| } else { |
| header_dir += "/" |
| } |
| assert(header == "$header_dir$header_name.yaml") |
| public += [ "${set.gen_dir}/$header_dir$header_name.h" ] |
| } |
| } |
| } |
| } |
| |
| config("headers.config") { |
| visibility = [ ":*" ] |
| |
| dirs = [] |
| foreach(set, libc_headers.include_dirs) { |
| dirs += [ set.dir ] |
| if (defined(set.json_file)) { |
| dirs += |
| [ get_label_info("${set.dir}($default_toolchain)", "target_gen_dir") ] |
| } |
| } |
| |
| # The llvm-libc set is first in libc_headers.include_dirs so that its |
| # llvm-libc-*/... files are considered first when deciding where such deps of |
| # generated files will come from. But $llvm_libc/include itself must be last |
| # in the list because it includes non-generated header files that would |
| # preempt ones still being used from musl. |
| dirs -= [ "$llvm_libc/include" ] |
| dirs += [ "$llvm_libc/include" ] |
| |
| cflags = [] |
| foreach(dir, dirs) { |
| cflags += [ |
| "-idirafter", |
| rebase_path(dir, root_build_dir), |
| ] |
| } |
| asmflags = cflags |
| } |
| |
| python_binary("hdrgen") { |
| visibility = [ ":*" ] |
| |
| main_source = "$hdrgen_dir/main.py" |
| deps = [ ":hdrgen.library" ] |
| |
| enable_mypy = false |
| } |
| |
| python_library("hdrgen.library") { |
| visibility = [ ":*" ] |
| |
| library_name = "hdrgen" |
| source_root = "$hdrgen_dir/hdrgen" |
| sources = [ |
| "__init__.py", |
| "enumeration.py", |
| "function.py", |
| "gpu_headers.py", |
| "header.py", |
| "macro.py", |
| "main.py", |
| "object.py", |
| "symbol.py", |
| "type.py", |
| "yaml_to_classes.py", |
| ] |
| library_deps = [ "//third_party/pyyaml:yaml" ] |
| |
| enable_mypy = false |
| } |
| |
| # Generate standlaone "#include <foo.h>" compile-time tests for each public |
| # header file, and compile them in every known language mode. |
| header_test_stdc = [ |
| # TODO(https://fxbug.dev/376333113): musl vs llvm-libc repeated typedefs are |
| # invalid in C<11. |
| # |
| #89, 99, |
| 11, |
| 17, |
| 23, |
| ] |
| header_test_stdcxx = [ |
| # These can be either integers or strings, doesn't matter. But GN doesn't |
| # allow (nor would anything else preserve) a leading 0 on an integer and the |
| # result must be exactly "03", not "3". |
| # TODO(https://fxbug.dev/376333113): libc++ headers are not C++03 clean! |
| #"03", |
| 11, |
| 14, |
| 17, |
| 20, |
| 23, |
| 26, |
| ] |
| |
| header_test_c_deps = [] |
| header_test_c_sources = [] |
| header_test_cxx_deps = [] |
| header_test_cxx_sources = [] |
| header_test_zircon_c_deps = [] |
| header_test_zircon_c_sources = [] |
| header_test_zircon_cxx_deps = [] |
| header_test_zircon_cxx_sources = [] |
| foreach(header, libc_headers.all_public_headers) { |
| header_target = string_replace(header, "/", "-") |
| foreach(extension, |
| [ |
| "c", |
| "cc", |
| ]) { |
| generated_file("$header_target.$extension") { |
| visibility = [ ":*" ] |
| testonly = true |
| output_conversion = "list lines" |
| outputs = [ "$target_gen_dir/header-tests/$header.$extension" ] |
| contents = [ |
| "/* Generated by" + |
| get_label_info(":target_name", "label_no_toolchain") + |
| ". DO NOT EDIT! */", |
| "#include <$header>", |
| ] |
| } |
| } |
| |
| # The <zircon/*.h> headers go in a separate list that isn't tested in C89. |
| if (get_path_info(header, "dir") == "zircon") { |
| header_test_zircon_c_deps += [ ":$header_target.c" ] |
| header_test_zircon_c_sources += get_target_outputs(":$header_target.c") |
| header_test_zircon_cxx_deps += [ ":$header_target.cc" ] |
| header_test_zircon_cxx_sources += get_target_outputs(":$header_target.cc") |
| } else { |
| header_test_c_deps += [ ":$header_target.c" ] |
| header_test_c_sources += get_target_outputs(":$header_target.c") |
| header_test_cxx_deps += [ ":$header_target.cc" ] |
| header_test_cxx_sources += get_target_outputs(":$header_target.cc") |
| } |
| } |
| |
| unittest_deps = [] |
| foreach(version, header_test_stdc) { |
| foreach(prefix, |
| [ |
| "c", |
| "gnu", |
| ]) { |
| unittest_deps += [ ":header-tests.$prefix$version" ] |
| libc_test("header-tests.$prefix$version") { |
| sources = header_test_c_sources |
| deps = header_test_c_deps |
| |
| # The <zircon/*.h> headers (and their transitive dependencies on syscall |
| # headers) don't all support strict -std=c89 mode, though they do support |
| # -std=gnu89 mode. |
| if ("$prefix$version" != "c89") { |
| sources += header_test_zircon_c_sources |
| deps += header_test_zircon_c_deps |
| } |
| |
| deps += [ ":headers" ] |
| configs = [ |
| ":test.$prefix$version", |
| "//build/config:Wsystem-headers", |
| ] |
| } |
| |
| config("test.$prefix$version") { |
| visibility = [ ":*" ] |
| cflags_c = [ "-std=$prefix$version" ] |
| } |
| } |
| } |
| |
| foreach(version, header_test_stdcxx) { |
| foreach(prefix, |
| [ |
| "c", |
| "gnu", |
| ]) { |
| # libc++ provides wrapper headers that interpose on various libc headers. |
| # Those will be found implicitly in preference to the libc headers, unless |
| # -nostdinc++ is used. The libc headers should be usable and warning-clean |
| # when used in C++ language modes directly without the libc++ wrappers; |
| # they won't be standard C++ library compatible with `std::` namespace |
| # declarations and such, but they should not get compilation errors. |
| # |
| # The libc headers should also be usable (and warning-clean) when used via |
| # libc++'s wrapper headers. So test both ways. |
| foreach(libcxx, |
| [ |
| "", |
| "-libcxx", |
| ]) { |
| unittest_deps += [ ":header-tests.${prefix}xx$version$libcxx" ] |
| libc_test("header-tests.${prefix}xx$version$libcxx") { |
| sources = header_test_cxx_sources |
| deps = header_test_cxx_deps |
| |
| # The <zircon/*.h> headers (and their transitive dependencies on |
| # syscall headers) don't need to support C++ < 17. |
| if (version != "03" && version >= 17) { |
| sources += header_test_zircon_cxx_sources |
| deps += header_test_zircon_cxx_deps |
| } |
| |
| deps += [ ":headers" ] |
| configs = [ ":test.$prefix++$version" ] |
| if (libcxx == "") { |
| # Don't use the libc++ headers. |
| configs += [ "//build/config:no-libc++-include" ] |
| remove_configs = [ "//build/config:libc++-include" ] |
| |
| # The libc++ headers are not warning-clean, and instead rely on the |
| # system-headers exemption from warnings. This is an unfortunate and |
| # counterproductive choice by the libc++ maintainers and it should be |
| # changed, but it is unlikely to change soon. If it ever does |
| # change, then this should be added unconditionally. |
| configs += [ "//build/config:Wsystem-headers" ] |
| } |
| } |
| } |
| |
| config("test.$prefix++$version") { |
| visibility = [ ":*" ] |
| cflags_cc = [ "-std=$prefix++$version" ] |
| } |
| } |
| } |
| |
| libc_test("unittests") { |
| sources = [] |
| deps = unittest_deps |
| } |