text: # Copyright 2018 The Bazel Authors. All rights reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #    http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  # buildifier: disable=module-docstring
  load("//rust:private/rustc.bzl", "CrateInfo", "DepInfo")
  load("//rust:private/utils.bzl", "find_toolchain", "get_lib_name")
  
  def _rust_doc_test_impl(ctx):
      """The implementation for the `rust_doc_test` rule
  
      Args:
          ctx (ctx): The rule's context object
  
      Returns:
          list: A list containing a DefaultInfo provider
      """
      if CrateInfo not in ctx.attr.dep:
          fail("Expected rust library or binary.", "dep")
  
      crate = ctx.attr.dep[CrateInfo]
  
      toolchain = find_toolchain(ctx)
  
      dep_info = ctx.attr.dep[DepInfo]
  
      # Construct rustdoc test command, which will be written to a shell script
      # to be executed to run the test.
      flags = _build_rustdoc_flags(dep_info, crate)
      if toolchain.os != "windows":
          rust_doc_test = _build_rustdoc_test_bash_script(ctx, toolchain, flags, crate)
      else:
          rust_doc_test = _build_rustdoc_test_batch_script(ctx, toolchain, flags, crate)
  
      # The test script compiles the crate and runs it, so it needs both compile and runtime inputs.
      compile_inputs = depset(
          crate.srcs +
          [crate.output] +
          dep_info.transitive_libs +
          [toolchain.rust_doc] +
          [toolchain.rustc] +
          toolchain.crosstool_files,
          transitive = [
              toolchain.rustc_lib.files,
              toolchain.rust_lib.files,
          ],
      )
  
      return [DefaultInfo(
          runfiles = ctx.runfiles(
              files = compile_inputs.to_list(),
              collect_data = True,
          ),
          executable = rust_doc_test,
      )]
  
  # TODO: Replace with bazel-skylib's `path.dirname`. This requires addressing some dependency issues or
  # generating docs will break.
  def _dirname(path_str):
      """Returns the path of the direcotry from a unix path.
  
      Args:
          path_str (str): A string representing a unix path
  
      Returns:
          str: The parsed directory name of the provided path
      """
      return "/".join(path_str.split("/")[:-1])
  
  def _build_rustdoc_flags(dep_info, crate):
      """Constructs the rustdoc script used to test `crate`. 
  
      Args:
          dep_info (DepInfo): The DepInfo provider
          crate (CrateInfo): The CrateInfo provider
  
      Returns:
          list: A list of rustdoc flags (str)
      """
  
      d = dep_info
  
      # nb. Paths must be constructed wrt runfiles, so we construct relative link flags for doctest.
      link_flags = []
      link_search_flags = []
  
      link_flags.append("--extern=" + crate.name + "=" + crate.output.short_path)
      link_flags += ["--extern=" + c.name + "=" + c.dep.output.short_path for c in d.direct_crates.to_list()]
      link_search_flags += ["-Ldependency={}".format(_dirname(c.output.short_path)) for c in d.transitive_crates.to_list()]
  
      link_flags += ["-ldylib=" + get_lib_name(lib) for lib in d.transitive_dylibs.to_list()]
      link_search_flags += ["-Lnative={}".format(_dirname(lib.short_path)) for lib in d.transitive_dylibs.to_list()]
      link_flags += ["-lstatic=" + get_lib_name(lib) for lib in d.transitive_staticlibs.to_list()]
      link_search_flags += ["-Lnative={}".format(_dirname(lib.short_path)) for lib in d.transitive_staticlibs.to_list()]
  
      edition_flags = ["--edition={}".format(crate.edition)] if crate.edition != "2015" else []
  
      return link_search_flags + link_flags + edition_flags
  
  _rustdoc_test_bash_script = """\
  #!/usr/bin/env bash
  
  set -e;
  
  {rust_doc} --test \\
      {crate_root} \\
      --crate-name={crate_name} \\
      {flags}
  """
  
  def _build_rustdoc_test_bash_script(ctx, toolchain, flags, crate):
      """Generates a helper script for executing a rustdoc test for unix systems
  
      Args:
          ctx (ctx): The `rust_doc_test` rule's context object
          toolchain (ToolchainInfo): A rustdoc toolchain
          flags (list): A list of rustdoc flags (str)
          crate (CrateInfo): The CrateInfo provider
  
      Returns:
          File: An executable containing information for a rustdoc test
      """
      rust_doc_test = ctx.actions.declare_file(
          ctx.label.name + ".sh",
      )
      ctx.actions.write(
          output = rust_doc_test,
          content = _rustdoc_test_bash_script.format(
              rust_doc = toolchain.rust_doc.short_path,
              crate_root = crate.root.path,
              crate_name = crate.name,
              # TODO: Should be possible to do this with ctx.actions.Args, but can't seem to get them as a str and into the template.
              flags = " \\\n    ".join(flags),
          ),
          is_executable = True,
      )
      return rust_doc_test
  
  _rustdoc_test_batch_script = """\
  {rust_doc} --test ^
      {crate_root} ^
      --crate-name={crate_name} ^
      {flags}
  """
  
  def _build_rustdoc_test_batch_script(ctx, toolchain, flags, crate):
      """Generates a helper script for executing a rustdoc test for windows systems
  
      Args:
          ctx (ctx): The `rust_doc_test` rule's context object
          toolchain (ToolchainInfo): A rustdoc toolchain
          flags (list): A list of rustdoc flags (str)
          crate (CrateInfo): The CrateInfo provider
  
      Returns:
          File: An executable containing information for a rustdoc test
      """
      rust_doc_test = ctx.actions.declare_file(
          ctx.label.name + ".bat",
      )
      ctx.actions.write(
          output = rust_doc_test,
          content = _rustdoc_test_batch_script.format(
              rust_doc = toolchain.rust_doc.short_path.replace("/", "\\"),
              crate_root = crate.root.path,
              crate_name = crate.name,
              # TODO: Should be possible to do this with ctx.actions.Args, but can't seem to get them as a str and into the template.
              flags = " ^\n    ".join(flags),
          ),
          is_executable = True,
      )
      return rust_doc_test
  
  rust_doc_test = rule(
      implementation = _rust_doc_test_impl,
      attrs = {
          "dep": attr.label(
              doc = (
                  "The label of the target to run documentation tests for.\n" +
                  "\n" +
                  "`rust_doc_test` can run documentation tests for the source files of " +
                  "`rust_library` or `rust_binary` targets."
              ),
              mandatory = True,
              providers = [CrateInfo],
          ),
      },
      executable = True,
      test = True,
      toolchains = [str(Label("//rust:toolchain"))],
      doc = """Runs Rust documentation tests.
  
  Example:
  
  Suppose you have the following directory structure for a Rust library crate:
  
  ```output
  [workspace]/
    WORKSPACE
    hello_lib/
        BUILD
        src/
            lib.rs
  ```
  
  To run [documentation tests][doc-test] for the `hello_lib` crate, define a `rust_doc_test` \
  target that depends on the `hello_lib` `rust_library` target:
  
  [doc-test]: https://doc.rust-lang.org/book/documentation.html#documentation-as-tests
  
  ```python
  package(default_visibility = ["//visibility:public"])
  
  load("@rules_rust//rust:rust.bzl", "rust_library", "rust_doc_test")
  
  rust_library(
      name = "hello_lib",
      srcs = ["src/lib.rs"],
  )
  
  rust_doc_test(
      name = "hello_lib_doc_test",
      dep = ":hello_lib",
  )
  ```
  
  Running `bazel test //hello_lib:hello_lib_doc_test` will run all documentation tests for the `hello_lib` library crate.
  """,
  )
  