Squashed 'third_party/rules_rust/' content from commit bf59038ca

git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
diff --git a/rust/private/rustdoc_test.bzl b/rust/private/rustdoc_test.bzl
new file mode 100644
index 0000000..663c65f
--- /dev/null
+++ b/rust/private/rustdoc_test.bzl
@@ -0,0 +1,225 @@
+# 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.
+
+"""Rules for performing `rustdoc --test` on Bazel built crates"""
+
+load("//rust/private:common.bzl", "rust_common")
+load("//rust/private:rustdoc.bzl", "rustdoc_compile_action")
+load("//rust/private:utils.bzl", "dedent", "find_toolchain")
+
+def _construct_writer_arguments(ctx, test_runner, action, crate_info):
+    """Construct arguments and environment variables specific to `rustdoc_test_writer`.
+
+    This is largely solving for the fact that tests run from a runfiles directory
+    where actions run in an execroot. But it also tracks what environment variables
+    were explicitly added to the action.
+
+    Args:
+        ctx (ctx): The rule's context object.
+        test_runner (File): The test_runner output file declared by `rustdoc_test`.
+        action (struct): Action arguments generated by `rustdoc_compile_action`.
+        crate_info (CrateInfo): The provider of the crate who's docs are being tested.
+
+    Returns:
+        tuple: A tuple of `rustdoc_test_writer` specific inputs
+            - Args: Arguments for the test writer
+            - dict: Required environment variables
+    """
+
+    writer_args = ctx.actions.args()
+
+    # Track the output path where the test writer should write the test
+    writer_args.add("--output={}".format(test_runner.path))
+
+    # Track what environment variables should be written to the test runner
+    writer_args.add("--action_env=DEVELOPER_DIR")
+    writer_args.add("--action_env=PATHEXT")
+    writer_args.add("--action_env=SDKROOT")
+    writer_args.add("--action_env=SYSROOT")
+    for var in action.env.keys():
+        writer_args.add("--action_env={}".format(var))
+
+    # Since the test runner will be running from a runfiles directory, the
+    # paths originally generated for the build action will not map to any
+    # files. To ensure rustdoc can find the appropriate dependencies, the
+    # file roots are identified and tracked for each dependency so it can be
+    # stripped from the test runner.
+    for dep in crate_info.deps.to_list():
+        dep_crate_info = getattr(dep, "crate_info", None)
+        dep_dep_info = getattr(dep, "dep_info", None)
+        if dep_crate_info:
+            root = dep_crate_info.output.root.path
+            writer_args.add("--strip_substring={}/".format(root))
+        if dep_dep_info:
+            for direct_dep in dep_dep_info.direct_crates.to_list():
+                root = direct_dep.dep.output.root.path
+                writer_args.add("--strip_substring={}/".format(root))
+            for transitive_dep in dep_dep_info.transitive_crates.to_list():
+                root = transitive_dep.output.root.path
+                writer_args.add("--strip_substring={}/".format(root))
+
+    # Indicate that the rustdoc_test args are over.
+    writer_args.add("--")
+
+    # Prepare for the process runner to ingest the rest of the arguments
+    # to match the expectations of `rustc_compile_action`.
+    writer_args.add(ctx.executable._process_wrapper.short_path)
+
+    return (writer_args, action.env)
+
+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
+    """
+
+    toolchain = find_toolchain(ctx)
+
+    crate = ctx.attr.crate
+    crate_info = crate[rust_common.crate_info]
+
+    if toolchain.os == "windows":
+        test_runner = ctx.actions.declare_file(ctx.label.name + ".rustdoc_test.bat")
+    else:
+        test_runner = ctx.actions.declare_file(ctx.label.name + ".rustdoc_test.sh")
+
+    # Add the current crate as an extern for the compile action
+    rustdoc_flags = [
+        "--extern",
+        "{}={}".format(crate_info.name, crate_info.output.short_path),
+        "--test",
+    ]
+
+    action = rustdoc_compile_action(
+        ctx = ctx,
+        toolchain = toolchain,
+        crate_info = crate_info,
+        rustdoc_flags = rustdoc_flags,
+        is_test = True,
+    )
+
+    tools = action.tools + [ctx.executable._process_wrapper]
+
+    writer_args, env = _construct_writer_arguments(
+        ctx = ctx,
+        test_runner = test_runner,
+        action = action,
+        crate_info = crate_info,
+    )
+
+    # Allow writer environment variables to override those from the action.
+    action.env.update(env)
+
+    ctx.actions.run(
+        mnemonic = "RustdocTestWriter",
+        progress_message = "Generating Rustdoc test runner for {}".format(crate.label),
+        executable = ctx.executable._test_writer,
+        inputs = action.inputs,
+        tools = tools,
+        arguments = [writer_args] + action.arguments,
+        env = action.env,
+        outputs = [test_runner],
+    )
+
+    return [DefaultInfo(
+        files = depset([test_runner]),
+        runfiles = ctx.runfiles(files = tools, transitive_files = action.inputs),
+        executable = test_runner,
+    )]
+
+rust_doc_test = rule(
+    implementation = _rust_doc_test_impl,
+    attrs = {
+        "crate": attr.label(
+            doc = (
+                "The label of the target to generate code documentation for. " +
+                "`rust_doc_test` can generate HTML code documentation for the " +
+                "source files of `rust_library` or `rust_binary` targets."
+            ),
+            providers = [rust_common.crate_info],
+            mandatory = True,
+        ),
+        "_cc_toolchain": attr.label(
+            doc = (
+                "In order to use find_cc_toolchain, your rule has to depend " +
+                "on C++ toolchain. See @rules_cc//cc:find_cc_toolchain.bzl " +
+                "docs for details."
+            ),
+            default = "@bazel_tools//tools/cpp:current_cc_toolchain",
+        ),
+        "_process_wrapper": attr.label(
+            doc = "A process wrapper for running rustdoc on all platforms",
+            cfg = "exec",
+            default = Label("//util/process_wrapper"),
+            executable = True,
+        ),
+        "_test_writer": attr.label(
+            doc = "A binary used for writing script for use as the test executable.",
+            cfg = "exec",
+            default = Label("//tools/rustdoc:rustdoc_test_writer"),
+            executable = True,
+        ),
+    },
+    test = True,
+    fragments = ["cpp"],
+    host_fragments = ["cpp"],
+    toolchains = [
+        str(Label("//rust:toolchain")),
+        "@bazel_tools//tools/cpp:toolchain_type",
+    ],
+    incompatible_use_toolchain_transition = True,
+    doc = dedent("""\
+        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:defs.bzl", "rust_library", "rust_doc_test")
+
+        rust_library(
+            name = "hello_lib",
+            srcs = ["src/lib.rs"],
+        )
+
+        rust_doc_test(
+            name = "hello_lib_doc_test",
+            crate = ":hello_lib",
+        )
+        ```
+
+        Running `bazel test //hello_lib:hello_lib_doc_test` will run all documentation tests for the `hello_lib` library crate.
+    """),
+)