910e62b5创建于 1月15日历史提交
# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import("//build/rust/gni_impl/rustc_print_cfg.gni")
import("//build/rust/rust_executable.gni")
import("//build/rust/rust_macro.gni")
import("//build/rust/rust_static_library.gni")

# This template allows for building Cargo crates within gn.
#
# It is intended for use with pre-existing (third party) code, from `BUILD.gn`
# files auto-generated by the `gnrt` tool.
#
# First party code should prefer to use first-class gn targets, such as
# `//build/rust/rust_static_library.gni` or similar.  One reason is that the
# `cargo_crate` template is not very efficient (it will stall the build
# pipeline whilst it runs build scripts to work out what flags are needed).
#
# Arguments:
#  aliased_deps
#  allow_unsafe
#  build_native_rust_unit_tests
#  crate_name
#  crate_root
#  deps
#  edition
#  features
#  sources
#    All just as in rust_static_library.gni
#  library_configs/executable_configs
#    All just as in rust_target.gni
#
#  enabled (optional)
#    A boolean that controls whether the main target will be defined at all.
#    Can be used to avoid compiling some platform-specific crates on other
#    platforms.
#
#  epoch (optional)
#    The major version of the library, which is used to differentiate between
#    multiple versions of the same library name. This includes all leading 0s
#    and the first non-zero value in the crate's version. This should be left
#    as the default, which is "0", for first-party code unless there are
#    multiple versions of a crate present. For third-party code, the version
#    epoch (matching the directory it is found in) should be specified.
#
#    Examples:
#      1.0.2 => epoch = "1"
#      4.2.0 => epoch = "4"
#      0.2.7 => epoch = "0.2"
#      0.0.3 => epoch = "0.0.3"
#
#  dev_deps
#    Same meaning as test_deps in rust_static_library.gni, but called
#    dev_deps to match Cargo.toml better.
#
#  build_root (optional)
#    Filename of build.rs build script.
#
#  build_deps (optional)
#    Build script dependencies
#
#  build_sources (optional)
#    List of sources for build script. Must be specified if
#    build_root is specified.
#
#  build_script_outputs (optional)
#    List of .rs files generated by the build script, if any.
#    Fine to leave undefined even if you have a build script.
#    This doesn't directly correspond to any Cargo variable,
#    but unfortunately is necessary for gn to build its dependency
#    trees automatically.
#    Many build scripts just output --cfg directives, in which case
#    no source code is generated and this can remain empty.
#
#  build_script_inputs (optional)
#    If the build script reads any files generated by build_deps,
#    as opposed to merely linking against them, add a list of such
#    files here. Again, this doesn't correspond to a Cargo variable
#    but is necessary for gn.
#
#  native_libs (optional)
#    Paths to library files that need to be in the linking search path when
#    depending on the crate's library, as it links against them via #[link]
#    directives.
#
#  crate_type "bin", "proc-macro" or "rlib" (optional)
#    Whether to build an executable. The default is "rlib".
#    At present others are not supported.
#
#  cargo_pkg_authors
#  cargo_pkg_version
#  cargo_pkg_name
#  cargo_pkg_description
#  cargo_pkg_repository
#    Strings as found within 'version' and similar fields within Cargo.toml.
#    Converted to environment variables passed to rustc, in case the crate
#    uses clap `crate_version!` or `crate_authors!` macros (fairly common in
#    command line tool help)

template("cargo_crate") {
  _orig_target_name = target_name

  _crate_name = _orig_target_name
  if (defined(invoker.crate_name)) {
    _crate_name = invoker.crate_name
  }

  # Construct metadata from the crate epoch or an explicitly provided metadata
  # field.
  _rustc_metadata = ""
  if (defined(invoker.rustc_metadata)) {
    _rustc_metadata = invoker.rustc_metadata
  } else if (defined(invoker.epoch)) {
    _rustc_metadata = "${_crate_name}-${invoker.epoch}"
  }

  _enabled = true
  if (defined(invoker.enabled)) {
    _enabled = invoker.enabled
  }

  _epochlabel = "vunknown"
  if (defined(invoker.epoch)) {
    _tempepoch = string_replace(invoker.epoch, ".", "_")
    _epochlabel = "v${_tempepoch}"
  }

  # Executables need to have unique names. Work out a prefix.
  if (defined(invoker.build_root)) {
    # This name includes the target name to ensure it's unique for each possible
    # build target in the same BUILD.gn file.
    _build_script_name =
        "${_crate_name}_${target_name}_${_epochlabel}_build_script"

    # Where the OUT_DIR will point when running the build script exe, and
    # compiling the crate library/binaries. This directory must include the
    # target name to avoid collisions between multiple GN targets that exist
    # in the same BUILD.gn.
    _build_script_env_out_dir = "$target_gen_dir/$target_name"
  }

  _rustenv = []
  if (defined(invoker.rustenv)) {
    _rustenv = invoker.rustenv
  }
  if (defined(invoker.cargo_pkg_authors)) {
    _rustenv += [ "CARGO_PKG_AUTHORS=${invoker.cargo_pkg_authors}" ]
  }
  if (defined(invoker.cargo_pkg_version)) {
    _rustenv += [ "CARGO_PKG_VERSION=${invoker.cargo_pkg_version}" ]

    # Parse `<major>.<minor>.<patch>-<pre>` (the `-<pre>` part of optional,
    # other parts are mandatory).
    _cargo_pkg_version_parts = string_split(invoker.cargo_pkg_version, ".")
    _rustenv += [ "CARGO_PKG_VERSION_MAJOR=${_cargo_pkg_version_parts[0]}" ]
    _rustenv += [ "CARGO_PKG_VERSION_MINOR=${_cargo_pkg_version_parts[1]}" ]
    _cargo_pkg_version_subparts = string_split(_cargo_pkg_version_parts[2], "-")
    _cargo_pkg_version_subparts += [ "" ]  # In case there is no prerelease.
    _rustenv += [ "CARGO_PKG_VERSION_PATCH=${_cargo_pkg_version_subparts[0]}" ]
    _rustenv += [ "CARGO_PKG_VERSION_PRE=${_cargo_pkg_version_subparts[1]}" ]
  }
  if (defined(invoker.cargo_pkg_name)) {
    _rustenv += [ "CARGO_PKG_NAME=${invoker.cargo_pkg_name}" ]
  }
  if (defined(invoker.cargo_pkg_description)) {
    _rustenv += [ "CARGO_PKG_DESCRIPTION=${invoker.cargo_pkg_description}" ]
  }
  if (defined(invoker.cargo_pkg_repository)) {
    _rustenv += [ "CARGO_PKG_REPOSITORY=${invoker.cargo_pkg_repository}" ]
  }

  # Try to determine the CARGO_MANIFEST_DIR, preferring the directory
  # with build.rs and otherwise assuming that the target contains a
  # `crate/` subdirectory.
  if (defined(invoker.build_root)) {
    manifest_dir = "."
  } else {
    build_gn_dir = get_label_info(target_name, "dir")
    manifest_dir = rebase_path(build_gn_dir + "/crate", root_build_dir)
  }
  _rustenv += [ "CARGO_MANIFEST_DIR=${manifest_dir}" ]

  # cargo_crate() should set library_configs, executable_configs,
  # proc_macro_configs. Not configs.
  assert(!defined(invoker.configs))

  # Work out what we're building.
  _crate_type = "rlib"
  if (defined(invoker.crate_type)) {
    _crate_type = invoker.crate_type
  }
  if (_crate_type == "bin") {
    _target_type = "rust_executable"
    assert(!defined(invoker.epoch))
    _configs = invoker.executable_configs
  } else if (_crate_type == "proc-macro") {
    _target_type = "rust_macro"
    _configs = invoker.proc_macro_configs
  } else {
    assert(_crate_type == "rlib")
    _target_type = "rust_static_library"
    _configs = invoker.library_configs
  }

  if (defined(invoker.output_name)) {
    _output_name = invoker.output_name
    not_needed([ "_epochlabel" ])
  } else if (_crate_type == "proc-macro") {
    # Proc macros are output to a top level directory (for the host toolchain).
    # Since multiple versions of the same crate may coexist, the output file
    # name must include the epoch to disambiguate.
    _output_name = "${_crate_name}_${_epochlabel}_${_orig_target_name}"
  } else if (_crate_type != "bin") {
    # Note that file names of libraries must start with the crate name in
    # order for the compiler to find transitive dependencies in the
    # directory search paths (since they are not all explicitly specified).
    #
    # For bin targets, we expect the target name to be unique, and the name
    # of the exe should not add magic stuff to it. And bin crates can not be
    # transitive dependencies.
    _output_name = "${_crate_name}_${_orig_target_name}"
    not_needed([ "_epochlabel" ])
  } else {
    not_needed([ "_epochlabel" ])
  }

  _testonly = false
  if (defined(invoker.testonly)) {
    _testonly = invoker.testonly
  }

  if (defined(invoker.native_libs)) {
    _native_libs_action = "copy_${target_name}_native_libs"
    _native_libs_config = "config_${target_name}_native_libs"
    _native_libs_dir =
        "${root_out_dir}/rustlib/${_crate_name}_${target_name}_${_epochlabel}"

    copy(_native_libs_action) {
      testonly = _testonly
      visibility = [ ":$target_name" ]
      sources = invoker.native_libs
      outputs = [ "${_native_libs_dir}/{{source_file_part}}" ]
    }
    config(_native_libs_config) {
      lib_dirs = [ "${_native_libs_dir}" ]
    }
  }

  # The main target, either a Rust source set or an executable.
  if (_enabled) {
    target(_target_type, target_name) {
      forward_variables_from(invoker,
                             "*",
                             TESTONLY_AND_VISIBILITY + [
                                   "build_root",
                                   "build_deps",
                                   "build_sources",
                                   "build_script_inputs",
                                   "build_script_outputs",
                                   "epoch",
                                   "unit_test_target",
                                   "configs",
                                   "executable_configs",
                                   "library_configs",
                                   "proc_macro_configs",
                                   "rustenv",
                                   "dev_deps",
                                   "native_libs_dir",
                                 ])

      testonly = _testonly
      if (defined(invoker.visibility)) {
        visibility = invoker.visibility
      }
      crate_name = _crate_name

      if (defined(_output_name)) {
        output_name = _output_name
      }

      # Don't import the `chromium` crate into third-party code.
      no_chromium_prelude = true

      # Depend by default on the `//build/rust/allocator` library, but avoid this
      # dependency in `no_std` mode.  This is desirable for two reasons:
      # 1. It gives users of `cargo_crate` an indirect way to control presence of
      #    this dependency.
      # 2. More importantly, it avoid cyclic dependencies like the one below:
      #       //build/rust/allocator:allocator ->
      #       ...
      #       //build/rust/std:std ->
      #       //build/rust/std/rules:addr2line (or another stdlib crate) ->
      #       //build/rust/allocator:allocator
      no_allocator_crate = false
      if (defined(invoker.no_std) && invoker.no_std) {
        no_allocator_crate = true
      }

      rustc_metadata = _rustc_metadata

      configs = []
      configs = _configs
      if (_crate_type == "rlib") {
        # Forward configs for unit tests.
        executable_configs = invoker.executable_configs
      }

      if (!defined(rustflags)) {
        rustflags = []
      }
      rustenv = _rustenv

      # When the `invoker` doesn't specify `allow_unsafe`, then fall back to
      # allowing `unsafe`.  This avoids creating additional friction when a new
      # team experiments with Rust.  OTOH we enforce that when crates actually
      # land in Chromium, then they need to explicitly declare their
      # `allow_unsafe` needs - this enforcement is done via presubmits under
      # `third_party/rust/chromium_crates_io` (see
      # `CheckExplicitAllowUnsafeForAllCrates` in a `.py` module over there).
      allow_unsafe = true
      if (defined(invoker.allow_unsafe)) {
        allow_unsafe = invoker.allow_unsafe
      }
      configs += [ "//build/rust/gni_impl:allow_all_warnings" ]

      if (!defined(build_native_rust_unit_tests)) {
        build_native_rust_unit_tests = _crate_type != "proc-macro"
      }
      if (build_native_rust_unit_tests) {
        # Unit tests in a proc-macro crate type don't make sense, you can't
        # compile executables against the `proc_macro` crate.
        assert(_crate_type != "proc-macro")
      }

      # The unit tests for each target, if generated, should be unique as well.
      # a) It needs to be unique even if multiple build targets have the same
      #    `crate_name`, but different target names.
      # b) It needs to be unique even if multiple build targets have the same
      #    `crate_name` and target name, but different epochs.
      _unit_test_unique_target_name = ""
      if (_crate_name != _orig_target_name) {
        _unit_test_unique_target_name = "${_orig_target_name}_"
      }
      _unit_test_unique_epoch = ""
      if (defined(invoker.epoch)) {
        _epoch_str = string_replace(invoker.epoch, ".", "_")
        _unit_test_unique_epoch = "v${_epoch_str}_"
      }
      if (defined(output_dir) && output_dir != "") {
        unit_test_output_dir = output_dir
      }
      unit_test_target = "${_unit_test_unique_target_name}${_crate_name}_${_unit_test_unique_epoch}unittests"

      if ((!defined(output_dir) || output_dir == "") && _crate_type == "rlib") {
        # Cargo crate rlibs can be compiled differently for tests, and must not
        # collide with the production outputs. This does *not* override the
        # unit_test_output_dir, which is set above, as that target is not an rlib.
        output_dir = "$target_out_dir/$_orig_target_name"
      }

      if (defined(invoker.dev_deps)) {
        test_deps = invoker.dev_deps
      }

      if (defined(invoker.build_root)) {
        # Uh-oh, we have a build script
        if (!defined(deps)) {
          deps = []
        }
        if (!defined(sources)) {
          sources = []
        }
        if (!defined(inputs)) {
          inputs = []
        }

        # This... is a bit weird. We generate a file called cargo_flags.rs which
        # does not actually contain Rust code, but instead some flags to add
        # to the rustc command line. We need it to end in a .rs extension so that
        # we can include it in the 'sources' line and thus have dependency
        # calculation done correctly. data_deps won't work because targets don't
        # require them to be present until runtime.
        flags_file = "$_build_script_env_out_dir/cargo_flags.rs"
        rustflags += [ "@" + rebase_path(flags_file, root_build_dir) ]
        sources += [ flags_file ]
        if (defined(invoker.build_script_outputs)) {
          # Build scripts may output arbitrary files. They are usually included in
          # the main Rust target using include! or include_str! and therefore the
          # filename may be .rs or may be arbitrary. We want to educate ninja
          # about the dependency either way.
          foreach(extra_source,
                  filter_include(invoker.build_script_outputs, [ "*.rs" ])) {
            sources += [ "$_build_script_env_out_dir/$extra_source" ]
          }
          foreach(extra_source,
                  filter_exclude(invoker.build_script_outputs, [ "*.rs" ])) {
            inputs += [ "$_build_script_env_out_dir/$extra_source" ]
          }
        }
        deps += [ ":${_build_script_name}_output" ]
        if (defined(_native_libs_action)) {
          deps += [ ":${_native_libs_action}" ]
          configs += [ ":${_native_libs_config}" ]
        }
      }
    }
  } else {
    not_needed(invoker,
               "*",
               [
                 "build_root",
                 "build_deps",
                 "build_sources",
                 "build_script_inputs",
                 "build_script_outputs",
               ])
    not_needed("*")
  }

  if (defined(invoker.build_root)) {
    action("${_build_script_name}_write_rustflags") {
      _rustflags_txt = "$_build_script_env_out_dir/rustflags.txt"
      outputs = [ _rustflags_txt ]
      script = rebase_path("//build/rust/gni_impl/write_rustflags.py")
      args = [
        "--output",
        rebase_path(_rustflags_txt, root_build_dir),
        "--",
        "{{rustflags}}",
      ]

      # The configs are required to get `{{rustflags}}` so that the build script
      # is compiled with the same flags as the library/binary will be. The build
      # script is an executable so it also gets the executable configs. If this
      # is ever a problem we can add a separate build_script_configs to the
      # cargo_crate template and just have it default to the same thing as
      # executable_configs.
      configs = invoker.executable_configs
    }

    # Extra targets required to make build script work
    action("${_build_script_name}_output") {
      script = rebase_path("//build/rust/gni_impl/run_build_script.py")
      _write_rustflags_outputs =
          get_target_outputs(":${_build_script_name}_write_rustflags")
      _rustflags_txt = _write_rustflags_outputs[0]
      inputs = [
        "//build/action_helpers.py",
        "//build/gn_helpers.py",
        _rustflags_txt,
        rustc_print_cfg_path,
      ]
      build_script_target = ":${_build_script_name}($rust_macro_toolchain)"
      deps = [
        ":${_build_script_name}_write_rustflags",
        "//build/rust/gni_impl:rustc_print_cfg",
        build_script_target,
      ]
      testonly = _testonly
      if (defined(invoker.visibility)) {
        visibility = invoker.visibility
      }

      # The build script may be built with a different toolchain when
      # cross-compiling (the host toolchain) so we must find the path relative
      # to that.
      _build_script_root_out_dir =
          get_label_info(build_script_target, "root_out_dir")
      _build_script_exe = "$_build_script_root_out_dir/$_build_script_name"

      # The executable is always built with the `rust_macro_toolchain` which
      # targets the `host_os`. The rule here is on the `target_toolchain` which
      # can be different (e.g. compiling on Linux, targeting Windows).
      if (host_os == "win") {
        _build_script_exe = "${_build_script_exe}.exe"
      }

      _flags_file = "$_build_script_env_out_dir/cargo_flags.rs"

      inputs += [ _build_script_exe ]
      outputs = [ _flags_file ]
      args = [
        "--build-script",
        rebase_path(_build_script_exe, root_build_dir),
        "--output",
        rebase_path(_flags_file, root_build_dir),
        "--rust-prefix",
        rebase_path("${rust_sysroot}/bin", root_build_dir),
        "--out-dir",
        rebase_path(_build_script_env_out_dir, root_build_dir),
        "--src-dir",
        rebase_path(get_path_info(invoker.build_root, "dir"), root_build_dir),
        "--target",
        rust_abi_target,
        "--rustc-print-cfg-path",
        rebase_path(rustc_print_cfg_path, root_build_dir),
        "--rustflags",
        rebase_path(_rustflags_txt, root_build_dir),
      ]
      if (defined(invoker.features)) {
        args += [ "--features" ]
        args += invoker.features
      }
      if (defined(invoker.build_script_outputs)) {
        args += [ "--generated-files" ]
        args += invoker.build_script_outputs
        foreach(generated_file, invoker.build_script_outputs) {
          outputs += [ "$_build_script_env_out_dir/$generated_file" ]
        }
      }
      if (_rustenv != []) {
        args += [ "--env" ]
        args += _rustenv
      }
      if (defined(invoker.build_script_inputs)) {
        inputs += invoker.build_script_inputs
      }
    }

    if (toolchain_for_rust_host_build_tools) {
      # The build script is only available to be built on the host, and we use
      # the rust_macro_toolchain for it to unblock building them while the
      # Chromium stdlib is still being compiled.
      rust_executable(_build_script_name) {
        crate_name = _build_script_name
        sources = invoker.build_sources
        crate_root = invoker.build_root
        testonly = _testonly
        if (defined(invoker.visibility)) {
          visibility = invoker.visibility
        }
        if (defined(invoker.build_deps)) {
          deps = invoker.build_deps
        }
        if (defined(invoker.build_script_inputs)) {
          inputs = invoker.build_script_inputs
        }

        # Don't import the `chromium` crate into third-party code.
        no_chromium_prelude = true

        # Build scripts do not need to link to chrome's allocator.
        no_allocator_crate = true

        # The ${_build_script_name}_output target looks for the exe in this
        # location. Due to how the Windows component build works, this has to
        # be $root_out_dir for all EXEs. In component build, C++ links to the
        # CRT as a DLL, and if Rust does not match, we can't link mixed target
        # Rust EXE/DLLs, as the headers in C++ said something different than
        # what Rust links. Since the CRT DLL is placed in the $root_out_dir,
        # an EXE can find it if it's also placed in that dir.
        output_dir = root_out_dir
        rustenv = _rustenv
        forward_variables_from(invoker,
                               [
                                 "features",
                                 "edition",
                                 "rustflags",
                               ])
        configs -= [
          "//build/config/compiler:chromium_code",

          # Avoid generating profiling data for build scripts.
          #
          # TODO(crbug.com/40261306): determine for sure whether to remove this
          # config. I'm not sure of the overlap between PGO instrumentation and
          # code coverage instrumentation, but we definitely don't want build
          # script coverage for PGO, while we might for test coverage metrics.
          #
          # If we do include build script output in test metrics, it could be
          # misleading: exercising some code from a build script doesn't give us
          # the same signal as an actual test.
          "//build/config/coverage:default_coverage",
        ]
        configs += [ "//build/config/compiler:no_chromium_code" ]
      }
    } else {
      not_needed(invoker,
                 [
                   "build_sources",
                   "build_deps",
                   "build_root",
                   "build_script_inputs",
                   "build_script_outputs",
                 ])
    }
  } else {
    not_needed([
                 "_name_specific_output_dir",
                 "_orig_target_name",
               ])
  }
}

set_defaults("cargo_crate") {
  library_configs = default_compiler_configs
  executable_configs = default_executable_configs
  proc_macro_configs = default_rust_proc_macro_configs
}