mdbook: fix support for external repositories (#4027)
When any part of an `mdbook` target (either the `book.toml` or the
source files) comes from an external repository, the build was failing
because the rule's staging logic and its invocation logic handled
repository boundaries inconsistently.
This change:
- Updates `_map_inputs` in `mdbook.bzl` to map external repository paths
to `'external/'` instead of `'../'`.
- Hardens the process wrapper with a bounds check to ensure files are
not staged outside the shadow work directory.
- Adds an integration test to verify the fix and prevent regressions.
Made by Gemini, fixes #4026.
diff --git a/extensions/mdbook/private/mdbook.bzl b/extensions/mdbook/private/mdbook.bzl
index be30517..d4c63d5 100644
--- a/extensions/mdbook/private/mdbook.bzl
+++ b/extensions/mdbook/private/mdbook.bzl
@@ -10,7 +10,15 @@
)
def _map_inputs(file):
- return "{}={}".format(file.path, file.short_path)
+ dest = file.short_path
+ if dest.startswith("../"):
+ # External repositories have short_paths starting with '../'.
+ # We need them to be staged at 'external/' within our shadow
+ # directory to match how 'file.path' (and thus 'file.dirname')
+ # refers to them.
+ dest = "external/" + dest.removeprefix("../")
+
+ return "{}={}".format(file.path, dest)
def _mdbook_impl(ctx):
output = ctx.actions.declare_directory(ctx.label.name)
diff --git a/extensions/mdbook/private/process_wrapper.rs b/extensions/mdbook/private/process_wrapper.rs
index 99a8663..c354c57 100644
--- a/extensions/mdbook/private/process_wrapper.rs
+++ b/extensions/mdbook/private/process_wrapper.rs
@@ -73,6 +73,17 @@
for (src, dest) in inputs_manifest.iter() {
let abs_dest = workdir.join(dest);
+
+ // Ensure the destination is within the workdir
+ if !abs_dest.starts_with(&workdir) {
+ panic!(
+ "Attempted to stage file outside of workdir: {} -> {} (dest: {})",
+ src.display(),
+ abs_dest.display(),
+ dest.display()
+ );
+ }
+
fs::create_dir_all(abs_dest.parent().unwrap()).unwrap();
fs::copy(src, &abs_dest).unwrap_or_else(|e| {
panic!(
diff --git a/extensions/mdbook/test/external_srcs/BUILD.bazel b/extensions/mdbook/test/external_srcs/BUILD.bazel
new file mode 100644
index 0000000..0772240
--- /dev/null
+++ b/extensions/mdbook/test/external_srcs/BUILD.bazel
@@ -0,0 +1,35 @@
+load("@bazel_skylib//rules:build_test.bzl", "build_test")
+load("@rules_rust//rust:defs.bzl", "rust_test")
+load("//:defs.bzl", "mdbook")
+
+mdbook(
+ name = "external_srcs",
+ srcs = ["//test/external_srcs/content:srcs"],
+ book = "//test/external_srcs/content:book.toml",
+)
+
+mdbook(
+ name = "local_book_mixed",
+ srcs = ["//test/external_srcs/content:srcs"],
+ book = "//test/external_srcs/local_book_mixed:book.toml",
+)
+
+rust_test(
+ name = "external_srcs_test",
+ srcs = ["external_srcs_test.rs"],
+ data = [
+ ":external_srcs",
+ ":local_book_mixed",
+ ],
+ edition = "2021",
+ rustc_env = {
+ "MDBOOK_EXTERNAL_SRCS_OUTPUT": "$(rlocationpath :external_srcs)",
+ "MDBOOK_LOCAL_BOOK_MIXED_OUTPUT": "$(rlocationpath :local_book_mixed)",
+ },
+ deps = ["@rules_rust//rust/runfiles"],
+)
+
+build_test(
+ name = "external_srcs_build_test",
+ targets = [":external_srcs"],
+)
diff --git a/extensions/mdbook/test/external_srcs/content/BUILD.bazel b/extensions/mdbook/test/external_srcs/content/BUILD.bazel
new file mode 100644
index 0000000..0f1c9bd
--- /dev/null
+++ b/extensions/mdbook/test/external_srcs/content/BUILD.bazel
@@ -0,0 +1,7 @@
+exports_files(["book.toml"])
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["src/**/*.md"]),
+ visibility = ["//visibility:public"],
+)
diff --git a/extensions/mdbook/test/external_srcs/content/book.toml b/extensions/mdbook/test/external_srcs/content/book.toml
new file mode 100644
index 0000000..c261393
--- /dev/null
+++ b/extensions/mdbook/test/external_srcs/content/book.toml
@@ -0,0 +1,5 @@
+[book]
+authors = ["Test Author"]
+language = "en"
+src = "src"
+title = "External Book"
diff --git a/extensions/mdbook/test/external_srcs/content/src/SUMMARY.md b/extensions/mdbook/test/external_srcs/content/src/SUMMARY.md
new file mode 100644
index 0000000..57727bb
--- /dev/null
+++ b/extensions/mdbook/test/external_srcs/content/src/SUMMARY.md
@@ -0,0 +1,3 @@
+# Summary
+
+[Test Chapter](test.md)
diff --git a/extensions/mdbook/test/external_srcs/content/src/test.md b/extensions/mdbook/test/external_srcs/content/src/test.md
new file mode 100644
index 0000000..8fa9231
--- /dev/null
+++ b/extensions/mdbook/test/external_srcs/content/src/test.md
@@ -0,0 +1,3 @@
+# Test Chapter
+
+This is a test.
diff --git a/extensions/mdbook/test/external_srcs/external_srcs_test.rs b/extensions/mdbook/test/external_srcs/external_srcs_test.rs
new file mode 100644
index 0000000..0dc3df4
--- /dev/null
+++ b/extensions/mdbook/test/external_srcs/external_srcs_test.rs
@@ -0,0 +1,24 @@
+use runfiles::{rlocation, Runfiles};
+use std::fs;
+
+#[test]
+fn test_external_srcs_output() {
+ let r = Runfiles::create().unwrap();
+
+ let dir = rlocation!(r, env!("MDBOOK_EXTERNAL_SRCS_OUTPUT")).unwrap();
+
+ let test_chapter = dir.join("test.html");
+ let content = fs::read_to_string(test_chapter).unwrap();
+ assert!(content.contains("This is a test."));
+}
+
+#[test]
+fn test_local_book_mixed_output() {
+ let r = Runfiles::create().unwrap();
+
+ let dir = rlocation!(r, env!("MDBOOK_LOCAL_BOOK_MIXED_OUTPUT")).unwrap();
+
+ let test_chapter = dir.join("test.html");
+ let content = fs::read_to_string(test_chapter).unwrap();
+ assert!(content.contains("This is a test."));
+}
diff --git a/extensions/mdbook/test/external_srcs/local_book_mixed/BUILD.bazel b/extensions/mdbook/test/external_srcs/local_book_mixed/BUILD.bazel
new file mode 100644
index 0000000..20015d5
--- /dev/null
+++ b/extensions/mdbook/test/external_srcs/local_book_mixed/BUILD.bazel
@@ -0,0 +1 @@
+exports_files(["book.toml"])
diff --git a/extensions/mdbook/test/external_srcs/local_book_mixed/book.toml b/extensions/mdbook/test/external_srcs/local_book_mixed/book.toml
new file mode 100644
index 0000000..7d70307
--- /dev/null
+++ b/extensions/mdbook/test/external_srcs/local_book_mixed/book.toml
@@ -0,0 +1,5 @@
+[book]
+authors = ["Test Author"]
+language = "en"
+src = "../content/src"
+title = "Local Book Mixed"