| # Copyright 2020 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/testing/config.gni") |
| |
| # Defines a set of golden file comparisons to be executed during the build. |
| # Fails if one of the provided `golden`'s contents is not equal to the |
| # associated `candidate`'s. |
| # |
| # If the `update_goldens` build arg is true, the goldens will be updated with |
| # the candidate files rather than failing the action. |
| # |
| # Parameters: |
| # |
| # * comparisons |
| # - Required: The list of golden file comparisons to perform. |
| # - Type: list(scope) |
| # |
| # Each scope contains: |
| # * golden |
| # - Required: The golden file against which to check. |
| # - Type: path |
| # |
| # * candidate |
| # - Required: The file under test. |
| # - Type: path |
| # |
| # * formatter |
| # - Optional: A formatting specification. This may be used to format the |
| # goldens before comparing with the candidates, making the diffs less |
| # sensitive to such changes. |
| # - Type: scope |
| # |
| # The scope contains: |
| # |
| # * script |
| # - Required: The path to the formatting executable. (Called "script" |
| # because of the `action()` precedent and the fact that "executable" |
| # is a GN keyword). The formatter takes a file via stdin and outputs |
| # its contents to stdout. |
| # - Type: path |
| # |
| # * args |
| # - Optional: The list of arguments to pass to the formatter. Any |
| # contained paths must be given relative to `root_build_dir`. |
| # - Type: list(string) |
| # |
| # * extensions |
| # - Optional: The list of file extensions to which the formatter should |
| # be restricted in application. An empty list is taken to mean that |
| # the formatter should be applied to every golden. |
| # - Type: list(string) |
| # - Default: [] |
| # |
| # * inputs |
| # - Optional: Any additional files that serve as inputs to the |
| # formatting invocation. The formatter itself need not be listed here. |
| # - Type: list(string) |
| # |
| # * deps |
| # - Optional: Any dependencies of the formatter. |
| # - Type: list(label) |
| # |
| # * warn_on_changes |
| # - Optional: If true, mismatches are treated as warnings rather than errors. |
| # - Type: bool |
| # - Default: false |
| # |
| # * visibility, testonly, deps, public_deps |
| # - Optional: Usual GN meanings. |
| # |
| template("golden_files") { |
| forward_variables_from(invoker, |
| [ |
| "comparisons", |
| "formatter", |
| ]) |
| |
| assert_context = "golden_files(\"$target_name\")" |
| assert(defined(comparisons) && comparisons != [], |
| "$assert_context: `comparisons` is required") |
| |
| if (defined(formatter)) { |
| assert( |
| defined(formatter.script), |
| "$assert_context: `formatter.script` is required if `formatter` is supplied") |
| |
| # Formatted versions of the goldens will be written to this directory and |
| # ultimately supplied to the diff action below, allowing for a degree of |
| # format-insensitivity. |
| formatted_goldens_dir = "$target_gen_dir/formatted-goldens" |
| } |
| |
| # In the actions below, we purposefully do not register the golden and |
| # candidate files as inputs; instead we record them within a depfile. Further |
| # the script will proactively create a blank golden file if the supplied one |
| # does not yet exist. These two measures allow (1) a user to not have to |
| # tediously create the files themselves when adding a new `golden_files()` |
| # target, and (2) ninja to execute this action without complaint before the |
| # files exist. |
| |
| main_target = target_name |
| comparison_json_target = "_golden_files.$target_name.generated_file" |
| |
| # We build up variations of `comparisons` to construct the JSON file we pass |
| # to verify_golden_files.py and for the metadata we'll attach to the action. |
| comparison_json_contents = [] |
| file_metadata = [] |
| verify_deps = [] |
| foreach(comparison, comparisons) { |
| assert(defined(comparison.golden), |
| "$assert_context: no `golden` given in comparison: $comparison") |
| assert(defined(comparison.candidate), |
| "$assert_context: no `candidate` given in comparison: $comparison") |
| |
| # Forward the rest to ensure that nothing else was defined, in which case |
| # GN will provide an "unused" error. |
| forward_variables_from(comparison, |
| "*", |
| [ |
| "golden", |
| "candidate", |
| ]) |
| |
| source_relative_golden = rebase_path(comparison.golden, "//") |
| golden = rebase_path(comparison.golden, root_build_dir) |
| candidate = rebase_path(comparison.candidate, root_build_dir) |
| |
| formatted_golden = "" # Clear from previous iteration. |
| if (defined(formatter)) { |
| extensions = [] # Clear from previous iteration. |
| if (defined(formatter.extensions)) { |
| extensions = formatter.extensions |
| } |
| |
| golden_ext = get_path_info(golden, "extension") |
| if (extensions + [ golden_ext ] - [ golden_ext ] != extensions || |
| extensions == []) { |
| format_golden_target = "_golden_files.$target_name.format.$golden" |
| formatted_golden = "$formatted_goldens_dir/$source_relative_golden" |
| action(format_golden_target) { |
| forward_variables_from(invoker, [ "testonly" ]) |
| visibility = [ ":$main_target" ] |
| |
| depfile = "$target_gen_dir/$target_name.d" |
| outputs = [ formatted_golden ] |
| |
| inputs = [ formatter.script ] |
| if (defined(formatter.inputs)) { |
| inputs += formatter.inputs |
| } |
| |
| forward_variables_from(invoker, |
| [ |
| "deps", |
| "public_deps", |
| ]) |
| if (defined(formatter.deps)) { |
| if (!defined(deps)) { |
| deps = [] |
| } |
| deps += formatter.deps |
| } |
| |
| script = "//build/testing/format_golden.sh" |
| args = [ |
| rebase_path(depfile, root_build_dir), |
| golden, |
| rebase_path(formatted_golden, root_build_dir), |
| rebase_path(formatter.script, root_build_dir), |
| ] |
| if (defined(formatter.args)) { |
| args += formatter.args |
| } |
| } |
| verify_deps += [ ":$format_golden_target" ] |
| } |
| } |
| |
| comparison_json_contents += [ |
| { |
| golden = golden |
| candidate = candidate |
| |
| # We don't want to supply the formatted golden under the `golden` key |
| # as the diff script needs to know the original location in order to |
| # auto-update it when a diff is detected. |
| if (formatted_golden != "") { |
| formatted_golden = rebase_path(formatted_golden, root_build_dir) |
| } |
| }, |
| ] |
| file_metadata += [ |
| { |
| golden = source_relative_golden |
| candidate = candidate |
| }, |
| ] |
| } |
| |
| comparison_json = "$target_gen_dir/$target_name.comparisons.json" |
| generated_file(comparison_json_target) { |
| output_conversion = "json" |
| contents = comparison_json_contents |
| outputs = [ comparison_json ] |
| } |
| |
| verify_deps += [ ":$comparison_json_target" ] |
| |
| action(main_target) { |
| forward_variables_from(invoker, |
| [ |
| "visibility", |
| "testonly", |
| "deps", |
| "public_deps", |
| ]) |
| |
| if (!defined(deps)) { |
| deps = [] |
| } |
| deps += verify_deps |
| |
| script = "//build/testing/verify_golden_files.py" |
| |
| stamp_file = "$target_gen_dir/$target_name.verified" |
| depfile = "${stamp_file}.d" |
| inputs = [ comparison_json ] |
| outputs = [ stamp_file ] |
| |
| args = [ |
| "--depfile", |
| rebase_path(depfile, root_build_dir), |
| "--stamp-file", |
| rebase_path(stamp_file, root_build_dir), |
| "--comparisons", |
| rebase_path(comparison_json, root_build_dir), |
| "--label", |
| get_label_info(":${target_name}", "label_with_toolchain"), |
| "--source-root", |
| rebase_path("//", root_build_dir), |
| ] |
| |
| if (update_goldens) { |
| args += [ "--bless" ] |
| } |
| |
| warn_on_changes = |
| defined(invoker.warn_on_changes) && invoker.warn_on_changes |
| if (warn_on_changes) { |
| args += [ "--warn" ] |
| } |
| |
| metadata = { |
| # Metadata for //:golden_files. |
| golden_files = [ |
| { |
| name = target_name |
| label = get_label_info(":$target_name", "label_with_toolchain") |
| stamp = rebase_path(stamp_file, root_build_dir) |
| files = file_metadata |
| }, |
| ] |
| } |
| } |
| } |