chromium/tools/grit/grit_rule.gni

# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Instantiate grit. This will produce a script target to run grit (named
# ${target_name}_grit), and a static library that compiles the .cc files.
#
# In general, code should depend on the static library. However, if the
# generated files are only processed by other actions to generate other
# files, it is possible to depend on the script target directly.
#
# Parameters
#
#   source (required)
#       Path to .grd file.
#
#   enable_input_discovery_for_gn_analyze (default=true)
#       Runs grit_info.py via exec_script() when compute_inputs_for_analyze=true
#       in order to discover all files that affect this target.
#       Turn this off when the .grd file is generated, or an <include> with
#       flattenhtml=true points to a generated file.
#       For "gn analyze" to be correct with this arg disabled, all inputs
#       must be listed via |inputs|.
#
#   inputs  (optional)
#       List of additional files, required for grit to process source file.
#
#   outputs (required)
#       List of outputs from grit, relative to the target_gen_dir. Grit will
#       verify at build time that this list is correct and will fail if there
#       is a mismatch between the outputs specified by the .grd file and the
#       outputs list here.
#
#       To get this list, you can look in the .grd file for
#       <output filename="..." and put those filename here. The base directory
#       of the list in Grit and the output list specified in the GN grit target
#       are the same (the target_gen_dir) so you can generally copy the names
#       exactly.
#
#       To get the list of outputs programatically, run:
#           python tools/grit/grit_info.py --outputs . path/to/your.grd
#       And strip the leading "./" from the output files.
#
#   defines (optional)
#       Extra defines to pass to grit (on top of the global defines in the
#       grit_args list).
#
#   grit_flags (optional)
#       List of strings containing extra command-line flags to pass to Grit.
#
#   resource_ids (optional)
#       Path to a grit "firstidsfile". Default is
#       //tools/gritsettings/resource_ids. Set to "" to use the value specified
#       in the <grit> nodes of the processed files.
#
#   output_dir (optional)
#       Directory for generated files. If you specify this, you will often
#       want to specify output_name if the target name is not particularly
#       unique, since this can cause files from multiple grit targets to
#       overwrite each other.
#
#   output_name (optional)
#       Provide an alternate base name for the generated files, like the .d
#       files. Normally these are based on the target name and go in the
#       output_dir, but if multiple targets with the same name end up in
#       the same output_dir, they can collide.
#
#   configs (optional)
#       List of additional configs to be applied to the generated target.
#
#   deps  (optional)
#   testonly (optional)
#   visibility  (optional)
#       Normal meaning.
#
#
# Example
#
#   grit("my_resources") {
#     # Source and outputs are required.
#     source = "myfile.grd"
#     outputs = [
#       "foo_strings.h",
#       "foo_strings.pak",
#     ]
#
#     grit_flags = [ "-E", "foo=bar" ]  # Optional extra flags.
#     # You can also put deps here if the grit source depends on generated
#     # files.
#   }
import("//build/config/compiler/compiler.gni")
import("//build/config/compute_inputs_for_analyze.gni")
import("//build/config/sanitizers/sanitizers.gni")
import("//build/toolchain/gcc_toolchain.gni")
import("//tools/grit/grit_args.gni")
_strip_resource_files = is_android && is_official_build
_js_minifier = "//tools/grit/minify_js.py"
_css_minifier = "//tools/grit/minimize_css.py"

grit_resource_id_target = "//tools/gritsettings:default_resource_ids"
grit_resource_id_file =
    get_label_info(grit_resource_id_target, "target_gen_dir") +
    "/default_resource_ids"
grit_info_script = "//tools/grit/grit_info.py"

# TODO(asvitkine): Add predetermined ids files for other platforms.
grit_predetermined_resource_ids_file = ""
if (is_mac) {
  grit_predetermined_resource_ids_file =
      "//tools/gritsettings/startup_resources_mac.txt"
}
if (is_win) {
  grit_predetermined_resource_ids_file =
      "//tools/gritsettings/startup_resources_win.txt"
}

template("grit") {
  if (defined(invoker.output_dir)) {
    _output_dir = invoker.output_dir
  } else {
    _output_dir = target_gen_dir
  }

  _grit_outputs =
      get_path_info(rebase_path(invoker.outputs, ".", _output_dir), "abspath")

  # Add .info output for all pak files
  _pak_info_outputs = []
  foreach(output, _grit_outputs) {
    if (get_path_info(output, "extension") == "pak") {
      _pak_info_outputs += [ output + ".info" ]
    }
  }

  if (defined(invoker.output_name)) {
    _grit_output_name = invoker.output_name
  } else {
    _grit_output_name = target_name
  }

  _grit_custom_target = target_name + "_grit"
  action(_grit_custom_target) {
    script = "//tools/grit/grit.py"
    inputs = [ invoker.source ]

    testonly = defined(invoker.testonly) && invoker.testonly

    depfile = "$target_gen_dir/$target_name.d"

    deps = [ "//tools/grit:grit_sources" ]
    outputs = [ "${depfile}.stamp" ] + _grit_outputs + _pak_info_outputs

    _grit_flags = grit_args

    # Add extra defines with -D flags.
    if (defined(invoker.defines)) {
      foreach(i, invoker.defines) {
        _grit_flags += [
          "-D",
          i,
        ]
      }
    }

    if (defined(invoker.grit_flags)) {
      _grit_flags += invoker.grit_flags
    }

    _rebased_source_path = rebase_path(invoker.source, root_build_dir)
    _enable_grit_info =
        !defined(invoker.enable_input_discovery_for_gn_analyze) ||
        invoker.enable_input_discovery_for_gn_analyze
    if (_enable_grit_info && compute_inputs_for_analyze) {
      # Only call exec_script when the user has explicitly opted into greater
      # precision at the expense of performance.
      _rel_inputs = exec_script("//tools/grit/grit_info.py",
                                [
                                      "--inputs",
                                      _rebased_source_path,
                                    ] + _grit_flags,
                                "list lines")
      inputs += rebase_path(_rel_inputs, ".", root_build_dir)
    }

    args = [
             "-i",
             _rebased_source_path,
             "build",
             "-o",
             rebase_path(_output_dir, root_build_dir),
             "--depdir",
             ".",
             "--depfile",
             rebase_path(depfile, root_build_dir),
             "--write-only-new=1",
             "--depend-on-stamp",
           ] + _grit_flags

    # Add brotli executable if using brotli.
    if (defined(invoker.use_brotli) && invoker.use_brotli) {
      _brotli_target = "//third_party/brotli:brotli($host_toolchain)"
      _brotli_executable = get_label_info(_brotli_target, "root_out_dir") +
                           "/" + get_label_info(_brotli_target, "name")
      if (host_os == "win") {
        _brotli_executable += ".exe"
      }

      inputs += [ _brotli_executable ]
      args += [
        "--brotli",
        rebase_path(_brotli_executable, root_build_dir),
      ]
    }

    _resource_ids = grit_resource_id_file
    if (defined(invoker.resource_ids)) {
      _resource_ids = invoker.resource_ids
    }

    if (_resource_ids != "") {
      inputs += [ _resource_ids ]
      args += [
        "-f",
        rebase_path(_resource_ids, root_build_dir),
      ]
      if (_resource_ids == grit_resource_id_file) {
        deps += [ grit_resource_id_target ]
      }
    }
    if (grit_predetermined_resource_ids_file != "") {
      inputs += [ grit_predetermined_resource_ids_file ]
      args += [
        "-p",
        rebase_path(grit_predetermined_resource_ids_file, root_build_dir),
      ]
    }

    # We want to make sure the declared outputs actually match what Grit is
    # writing. We write the list to a file (some of the output lists are long
    # enough to not fit on a Windows command line) and ask Grit to verify those
    # are the actual outputs at runtime.
    _asserted_list_file =
        "$target_out_dir/${_grit_output_name}_expected_outputs.txt"
    write_file(_asserted_list_file,
               rebase_path(invoker.outputs, root_build_dir, _output_dir))
    inputs += [ _asserted_list_file ]
    args += [
      "--assert-file-list",
      rebase_path(_asserted_list_file, root_build_dir),
    ]

    if (enable_resource_allowlist_generation) {
      _rc_grit_outputs = []
      foreach(output, _grit_outputs) {
        if (get_path_info(output, "extension") == "rc") {
          _rc_grit_outputs += [ output ]
        }
      }

      if (_rc_grit_outputs != []) {
        # Resource allowlisting cannot be used with .rc files.
        # Make sure that there aren't any .pak outputs which would require
        # allowlist annotations.
        assert(_pak_info_outputs == [], "can't combine .pak and .rc outputs")
      } else {
        args += [ "--allowlist-support" ]
      }
    }
    if (_strip_resource_files) {
      _js_minifier_command = rebase_path(_js_minifier, root_build_dir)
      _css_minifier_command = rebase_path(_css_minifier, root_build_dir)
      args += [
        "--js-minifier",
        _js_minifier_command,
        "--css-minifier",
        _css_minifier_command,
      ]
      inputs += [
        _js_minifier,
        _css_minifier,
      ]
    }

    if (defined(invoker.visibility)) {
      # This needs to include both what the invoker specified (since they
      # probably include generated headers from this target), as well as the
      # generated source set (since there's no guarantee that the visibility
      # specified by the invoker includes our target).
      #
      # Only define visibility at all if the invoker specified it. Otherwise,
      # we want to keep the public "no visibility specified" default.
      visibility = [ ":${invoker.target_name}" ] + invoker.visibility
    }

    if (defined(invoker.use_brotli) && invoker.use_brotli) {
      if (is_mac && is_asan) {
        deps += [ "//tools/grit:brotli_mac_asan_workaround" ]
      } else {
        deps += [ "//third_party/brotli:brotli($host_toolchain)" ]
      }
    }
    if (defined(invoker.deps)) {
      deps += invoker.deps
    }
    if (defined(invoker.inputs)) {
      inputs += invoker.inputs
    }
  }

  # This is the thing that people actually link with, it must be named the
  # same as the argument the template was invoked with.
  source_set(target_name) {
    testonly = defined(invoker.testonly) && invoker.testonly

    # Since we generate a file, we need to be run before the targets that
    # depend on us.
    sources = []
    foreach(_output, _grit_outputs) {
      _extension = get_path_info(_output, "extension")
      if (_extension != "json" && _extension != "gz" && _extension != "pak" &&
          _extension != "xml") {
        sources += [ _output ]
      }
    }

    # Deps set on the template invocation will go on the action that runs
    # grit above rather than this library. This target needs to depend on the
    # action publicly so other scripts can take the outputs from the grit
    # script as inputs.
    public_deps = [ ":$_grit_custom_target" ]

    if (defined(invoker.public_configs)) {
      public_configs += invoker.public_configs
    }

    configs += [ "//build/config/compiler:wexit_time_destructors" ]
    if (defined(invoker.configs)) {
      configs += invoker.configs
    }

    if (defined(invoker.visibility)) {
      visibility = invoker.visibility
    }
    output_name = _grit_output_name
  }
}

if (is_android) {
  import("//build/config/android/rules.gni")

  # Declare a target that generates localized strings.xml from a .grd file.
  #
  # If this target is included in the deps of an android resources/library/apk,
  # the strings.xml will be included with that target.
  #
  # Variables
  #   deps: Specifies the dependencies of this target.
  #   grd_file: Path to the .grd file to generate strings.xml from.
  #   outputs: Expected grit outputs (see grit rule).
  #
  # Example
  #  java_strings_grd("foo_strings_grd") {
  #    grd_file = "foo_strings.grd"
  #  }
  template("java_strings_grd") {
    forward_variables_from(invoker, [ "testonly" ])

    _resources_zip = "$target_out_dir/$target_name.resources.zip"
    _grit_target_name = "${target_name}__grit"
    _grit_output_dir = "$target_gen_dir/${target_name}_grit_output"

    grit(_grit_target_name) {
      forward_variables_from(invoker,
                             [
                               "deps",
                               "defines",
                             ])
      grit_flags = [
        "-E",
        "ANDROID_JAVA_TAGGED_ONLY=false",
      ]
      output_dir = _grit_output_dir
      resource_ids = ""
      source = invoker.grd_file
      outputs = invoker.outputs
    }

    _zip_target_name = "${target_name}__zip"

    zip(_zip_target_name) {
      base_dir = _grit_output_dir

      # No need to depend on the source_set().
      _grit_dep = ":${_grit_target_name}_grit"
      deps = [ _grit_dep ]
      inputs = filter_exclude(get_target_outputs(_grit_dep), [ "*.stamp" ])
      output = _resources_zip
    }

    android_generated_resources(target_name) {
      forward_variables_from(invoker,
                             [
                               "resource_overlay",
                               "visibility",
                             ])
      generating_target = ":$_zip_target_name"
      generated_resources_zip = _resources_zip
    }
  }

  # Declare a target that packages strings.xml generated from a grd file.
  #
  # If this target is included in the deps of an android resources/library/apk,
  # the strings.xml will be included with that target.
  #
  # Variables
  #  grit_output_dir: directory containing grit-generated files.
  #  generated_files: list of android resource files to package.
  #
  # Example
  #  java_strings_grd_prebuilt("foo_strings_grd") {
  #    grit_output_dir = "$root_gen_dir/foo/grit"
  #    generated_files = [
  #      "values/strings.xml"
  #    ]
  #  }
  template("java_strings_grd_prebuilt") {
    forward_variables_from(invoker, [ "testonly" ])

    _resources_zip = "$target_out_dir/$target_name.resources.zip"
    _zip_target_name = "${target_name}__zip"

    zip(_zip_target_name) {
      base_dir = invoker.grit_output_dir
      inputs = rebase_path(invoker.generated_files, ".", base_dir)
      output = _resources_zip
      if (defined(invoker.deps)) {
        deps = []
        foreach(_dep, invoker.deps) {
          deps += [ "${_dep}_grit" ]
        }
      }
    }

    android_generated_resources(target_name) {
      forward_variables_from(invoker,
                             [
                               "resource_overlay",
                               "visibility",
                             ])
      generating_target = ":$_zip_target_name"
      generated_resources_zip = _resources_zip
    }
  }
}