| # Copyright 2024 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/group_with_inputs.gni") |
| |
| # Generate the product assembly config file, after rebasing all input files in |
| # the 'platform' and 'product' configuration scopes, tracking them as 'inputs' |
| # to GN. |
| # |
| # NOTE: This creates a separate target, "$target_name.inputs", which MUST be |
| # depended upon (separately) by users of this template to ensure that the |
| # input files found in 'config_file_potential_inputs' are either source files or |
| # are created by the 'deps' that are passed to this template. |
| # |
| # This is a build optimization so that the json file generated by this template |
| # can be used with validation code that compares it with Bazel-generated files, |
| # without those validation targets taking compile-time dependencies on all the |
| # inputs / deps. |
| # |
| # Params: |
| # |
| # product_assembly_config |
| # [scope] The product assembly config (platform, product, packages, |
| # base_drivers, etc.) to include in the generated file. |
| # |
| # deps (optional) |
| # [list, gn labels] These are the deps for the inputs named by path in the |
| # platform and product scopes, only. |
| # |
| # GN Usual: |
| # testonly |
| # visibility |
| # |
| template("product_assembly_config_file") { |
| # Platform configuration schema fields that are paths to files. These need to |
| # be tracked as GN action `inputs`. |
| # |
| # Each is a path to a 'node' in the schema, and then a list of field names on |
| # that node: |
| # |
| # platform: { |
| # foo: { |
| # bar: { |
| # field: "../../path/to/source/filename.json" |
| # } |
| # } |
| # } |
| # |
| # is the following: |
| # |
| # { |
| # node_path = "foo.bar" |
| # items = [ "field" ] |
| # } |
| |
| config_file_potential_inputs = [ |
| { |
| node_path = "platform.ui" |
| fields = [ "sensor_config" ] |
| }, |
| { |
| node_path = "platform.development_support" |
| fields = [ |
| "authorized_ssh_keys_path", |
| "authorized_ssh_ca_certs_path", |
| ] |
| }, |
| { |
| node_path = "product.component_policy" |
| fields = [ "product_policies" ] |
| is_list = true |
| }, |
| { |
| node_path = "platform.diagnostics.sampler" |
| fields = [ |
| "fire_configs", |
| "metrics_configs", |
| ] |
| is_list = true |
| }, |
| { |
| node_path = "platform.forensics.cobalt" |
| fields = [ "registry" ] |
| }, |
| { |
| node_path = "platform.storage.component_id_index" |
| fields = [ "product_index" ] |
| }, |
| { |
| node_path = "platform.connectivity.network" |
| fields = [ "netcfg_config_path" ] |
| }, |
| { |
| node_path = "product.build_info" |
| fields = [ |
| "version", |
| "jiri_snapshot", |
| "latest_commit_date", |
| "minimum_utc_stamp", |
| ] |
| }, |
| ] |
| |
| # These paths need to be present in the generated assembly configuration json |
| # file as _rebased_ paths. In the example above, the GN label in GN for that |
| # file is: `//path/to/source/filename.json` |
| # |
| # Assembly runs in a root_build_dir such as `//out/default`, and so the |
| # rebased path from the root_build_dir to the file is |
| # `../../path/to/source/filename.json` |
| # |
| # The GN label for the file needs to be passed to GN in the `inputs` list for |
| # the `target_name` action defined by this template (below). |
| # |
| # However, because GN doesn't allow us to set the value of a variable in a |
| # scope using the `scope_name[var_name]` syntax, only read it, we cannot do |
| # any sort of programmatic field replacement, at least not without hard-coded |
| # logic that's repeated for each field (which prior versions of this file did |
| # when there was only field to deal with), e.g.: |
| # |
| # _p = invoker.platform |
| # |
| # # platform configuration with rebased contents: |
| # _new_platform_config = { |
| # forward_variables_from(_p, "*", [ "foo" ]) |
| # if (defined(_p.foo)) { |
| # _foo = _p.foo |
| # |
| # # 'foo' with rebased contents: |
| # foo = { |
| # forward_variables_from(_p.foo, "*", ["bar"]) |
| # |
| # if (defined(_foo.bar)) { |
| # _bar = _foo.bar |
| # |
| # # 'bar' with rebased contents: |
| # bar = { |
| # forward_variables_from(_bar, "*", ["field"]) |
| # if (defined(_bar.field)) { |
| # field = rebase_path(_bar.field, root_build_dir) |
| # } |
| # } |
| # } |
| # } |
| # } |
| # } |
| # |
| # The original invoker.platform scope would need to be carefully reconstructed |
| # at every level using this pattern of forwarding variables that weren't part |
| # of the newly-constructed ones, and then assigning the newly-constructed ones |
| # to the appropriate variables in each of the nested scopes. |
| |
| # The assembly configuration |
| config_inputs = [] |
| |
| config_contents = invoker.product_assembly_config |
| |
| # Iterate over each item in the list above. |
| foreach(potential_input, config_file_potential_inputs) { |
| _node = { |
| } |
| _node = config_contents |
| |
| # Since we can't use recursion, iteratively change `_node` to the next |
| # child in the path, e.g. 'foo', then 'bar'. The `continue` var is used |
| # to signal that some node along the path has been omitted, and this will |
| # be a no-op for the rest of the iteration (since it can't exit early) |
| _continue = true |
| foreach(_node_name, string_split(potential_input.node_path, ".")) { |
| if (_continue && defined(_node[_node_name])) { |
| # because GN will not let you replace a non-empty scope, this does a |
| # little workaround to what would be the following, if it was allowed: |
| # |
| # _node = _node[_node_name] |
| # |
| # Instead, we need to create a _next_node temporary to hold the next |
| # node, but even that temporary lives across iterations of the loop, |
| # and must be reset to an empty scope before re-assigning to it. |
| # |
| |
| # Clear the previous _next_node, and then cache the next node to look |
| # at in _next_node. |
| _next_node = { |
| } |
| _next_node = _node[_node_name] |
| |
| # Clear the _node var, so that we can assign _next_node to it. |
| _node = { |
| } |
| _node = _next_node |
| } else { |
| # The node wasn't found, so do nothing for the rest of the iteration. |
| _continue = false |
| } |
| } |
| |
| # 'continue' will still be true if the all the nodes in the path were |
| # found. |
| if (_continue) { |
| foreach(_field_name, potential_input.fields) { |
| if (defined(_node[_field_name])) { |
| _file_paths = [] |
| if (defined(potential_input.is_list) && potential_input.is_list) { |
| _file_paths += _node[_field_name] |
| } else { |
| _file_paths += [ _node[_field_name] ] |
| } |
| |
| # Here the rebased path is converted back in to an absolute GN |
| # file label, from the GN root dir `//`: |
| # |
| # ../../path/to/filename.json |
| # |
| # becomes: |
| # |
| # //path/to/filename.json |
| # |
| # and if a file in root_build_dir: |
| # |
| # path/to/filename.json |
| # |
| # it becomes (for `root_build_dir` == "//out/default"): |
| # |
| # //out/default/path/to/filename.json |
| # |
| # And the resultant (non-rebased) path can be used in the GN |
| # action's `inputs` list |
| foreach(_file_path, _file_paths) { |
| _gn_path = "//" + rebase_path(_file_path, "//", root_build_dir) |
| config_inputs += [ _gn_path ] |
| } |
| } |
| } |
| } |
| } |
| |
| # Generate the Product Assembly Overrides file itself. |
| # |
| # This does _not_ have deps on any of the passed in targets, which is why the |
| # separate target for the inputs is created. |
| # |
| generated_file(target_name) { |
| forward_variables_from(invoker, |
| [ |
| "outputs", |
| "testonly", |
| "visibility", |
| ]) |
| output_conversion = "json" |
| contents = config_contents |
| } |
| |
| group_with_inputs(target_name + ".inputs") { |
| forward_variables_from(invoker, |
| [ |
| "deps", |
| "public_deps", |
| "testonly", |
| "visibility", |
| ]) |
| |
| # in the event that neither deps nor public_deps are passed in, then pass |
| # an empty deps list so the template doesn't throw an error. |
| if (!defined(deps)) { |
| deps = [] |
| } |
| |
| # Add any inputs found by looking at the known config schema items |
| # that contain paths to files, so that they are guaranteed to be added to |
| # the ninja deps for the product assembly configuration. |
| inputs = config_inputs |
| } |
| } |