Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions examples/rust_doc_merge/.buckconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[cells]
root = .
prelude = prelude
toolchains = toolchains
none = none

[cell_aliases]
config = prelude
ovr_config = prelude
fbcode = none
fbsource = none
buck = none

[external_cells]
prelude = bundled

[build]
execution_platforms = prelude//platforms:default

[parser]
target_platform_detector_spec = target:root//...->prelude//platforms:default \
target:prelude//...->prelude//platforms:default \
target:toolchains//...->prelude//platforms:default
Empty file.
31 changes: 31 additions & 0 deletions examples/rust_doc_merge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# rust_doc_merge example

Exercises RFC 3662 mergeable rustdoc support
(`prelude//rust:doc_merge.bxl`). Three crates: `crate_a` (leaf),
`crate_b` (depends on A), and `bin` (depends on both).

Uses the bundled prelude (`[external_cells] prelude = bundled`), so
edits to `../../prelude/` show up after the next `cargo build --bin
buck2`.

## Build per-crate HTML (existing behaviour)

```
buck2 build '//crate_a:crate_a[doc]' --show-output
```

## Produce a single merged HTML tree across all three crates

```
buck2 bxl prelude//rust:doc_merge.bxl:merge -- --targets //...
```

The BXL prints an absolute path to a directory containing merged HTML
with a cross-crate index. Serve it with e.g.:

```bash
env -C "$(buck2 bxl prelude//rust:doc_merge.bxl:merge -- --targets //...)" python3 -m http.server
```

Pass `--include-deps=true` to additionally pull in transitive rust
dependencies of the listed targets.
10 changes: 10 additions & 0 deletions examples/rust_doc_merge/bin/BUCK
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
rust_binary(
name = "bin",
srcs = glob(["src/**/*.rs"]),
crate_root = "src/main.rs",
deps = [
"//buck_resources_stub:buck_resources",
"//crate_a:crate_a",
"//crate_b:crate_b",
],
)
18 changes: 18 additions & 0 deletions examples/rust_doc_merge/bin/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//! A tiny binary that exercises all three library crates.

use crate_b::WidgetB;

/// Return a resource path from [`buck_resources::get`], demonstrating a
/// cross-crate reference whose docs link out to
/// <https://docs.rs/buck-resources/1.0.0/>.
pub fn resource_path() -> Option<std::path::PathBuf> {
buck_resources::get("root//some:target").ok()
}

fn main() {
let w = WidgetB::new("world", 3);
println!("{} ({})", w.greet(), w.count);
if let Some(p) = resource_path() {
println!("resource at {:?}", p);
}
}
8 changes: 8 additions & 0 deletions examples/rust_doc_merge/buck_resources_stub/BUCK
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
rust_library(
name = "buck_resources",
srcs = glob(["src/**/*.rs"]),
crate_root = "src/lib.rs",
crate = "buck_resources",
rustdoc_html_root_url = "https://docs.rs/buck-resources/1.0.0/",
visibility = ["PUBLIC"],
)
17 changes: 17 additions & 0 deletions examples/rust_doc_merge/buck_resources_stub/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//! Minimal stub of the `buck-resources` crate. The real crate lives on
//! [docs.rs](https://docs.rs/buck-resources); this local stub exists only
//! so that the example workspace has a target to compile. In the merged
//! rustdoc tree, cross-crate references to [`get`] from consumer crates
//! resolve to `https://docs.rs/buck-resources/1.0.0/`
//! thanks to `rustdoc_html_root_url` on the `rust_library` target.

use std::path::PathBuf;

/// Resolve a resource path by its buck2 target label.
pub fn get(_name: &str) -> Result<PathBuf, Error> {
Ok(PathBuf::new())
}

/// Error returned by [`get`] when a resource cannot be located.
#[derive(Debug)]
pub struct Error;
6 changes: 6 additions & 0 deletions examples/rust_doc_merge/crate_a/BUCK
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
rust_library(
name = "crate_a",
srcs = glob(["src/**/*.rs"]),
crate_root = "src/lib.rs",
visibility = ["PUBLIC"],
)
50 changes: 50 additions & 0 deletions examples/rust_doc_merge/crate_a/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//! Crate A: a demo crate for testing RFC 3662 merged rustdoc.

use std::fmt;

/// Greet someone from crate A.
///
/// # Examples
///
/// ```
/// assert_eq!(crate_a::greet("world"), "hello world");
/// ```
pub fn greet(who: &str) -> String {
format!("hello {}", who)
}

/// A simple struct defined in crate A.
#[derive(Debug, Default)]
pub struct WidgetA {
/// The widget's display name.
pub name: String,
}

impl WidgetA {
/// Construct a new [`WidgetA`].
pub fn new(name: impl Into<String>) -> Self {
Self { name: name.into() }
}
}

impl fmt::Display for WidgetA {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "WidgetA({})", self.name)
}
}

/// A trait for things that can produce a greeting.
///
/// Implemented by [`WidgetA`] here and by `WidgetB` in `crate_b` — the
/// rustdoc merge step has to stitch both impls into the same
/// `trait.impl/crate_a/trait.Greeter.js` file.
pub trait Greeter {
/// Return a greeting string.
fn greeting(&self) -> String;
}

impl Greeter for WidgetA {
fn greeting(&self) -> String {
greet(&self.name)
}
}
7 changes: 7 additions & 0 deletions examples/rust_doc_merge/crate_b/BUCK
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
rust_library(
name = "crate_b",
srcs = glob(["src/**/*.rs"]),
crate_root = "src/lib.rs",
deps = ["//crate_a:crate_a"],
visibility = ["PUBLIC"],
)
42 changes: 42 additions & 0 deletions examples/rust_doc_merge/crate_b/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! Crate B: depends on crate A, re-exports and extends it.

use std::fmt;

use crate_a::Greeter;
use crate_a::WidgetA;

/// A widget that wraps a [`WidgetA`] with extra metadata.
#[derive(Debug, Default)]
pub struct WidgetB {
/// Inner widget from crate A.
pub inner: WidgetA,
/// Count of something.
pub count: u32,
}

impl WidgetB {
/// Construct a new [`WidgetB`] wrapping a freshly-made [`WidgetA`].
pub fn new(name: &str, count: u32) -> Self {
Self {
inner: WidgetA::new(name),
count,
}
}

/// Produce a greeting via [`crate_a::greet`].
pub fn greet(&self) -> String {
crate_a::greet(&self.inner.name)
}
}

impl fmt::Display for WidgetB {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "WidgetB({}, {})", self.inner.name, self.count)
}
}

impl Greeter for WidgetB {
fn greeting(&self) -> String {
format!("{} (x{})", self.inner.greeting(), self.count)
}
}
53 changes: 53 additions & 0 deletions examples/rust_doc_merge/toolchains/BUCK
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
load("@prelude//tests:test_toolchain.bzl", "noop_test_toolchain")
load("@prelude//toolchains:cxx.bzl", "system_cxx_toolchain")
load("@prelude//toolchains:python.bzl", "remote_python_toolchain", "system_python_wheel_toolchain")
load("@prelude//toolchains:remote_test_execution.bzl", "remote_test_execution_toolchain")
load("@prelude//toolchains:rust.bzl", "system_rust_toolchain")

# Minimal expansion of `system_demo_toolchains()` from
# `prelude//toolchains:demo.bzl`, trimmed to just what this rust-only example
# needs, so we can pass custom `rustdoc_flags` to `:rust`. Those flags are
# forwarded to per-crate rustdoc and to the `rustdoc --merge=finalize` step
# by `doc_merge.bzl`.

system_cxx_toolchain(
name = "cxx",
visibility = ["PUBLIC"],
)

remote_python_toolchain(
name = "python",
visibility = ["PUBLIC"],
)

system_python_wheel_toolchain(
name = "python_wheel",
visibility = ["PUBLIC"],
)

system_rust_toolchain(
name = "rust",
default_edition = "2021",
# Harmless rustdoc flags. Forwarded to per-crate rustdoc and also to
# the `rustdoc --merge=finalize` step by `doc_merge.bzl`.
rustdoc_flags = [
"--default-theme=ayu",
"--cap-lints=warn",
],
# Real theme CSS used by `[doc]` and `rustdoc --merge=finalize`. The
# per-crate rustdoc actions that feed the merge step instead see an
# empty stub with the same basename, so edits to this file don't
# invalidate every crate's rustdoc output.
rustdoc_themes = ["example_theme.css"],
visibility = ["PUBLIC"],
)

remote_test_execution_toolchain(
name = "remote_test_execution",
visibility = ["PUBLIC"],
)

noop_test_toolchain(
name = "test",
visibility = ["PUBLIC"],
)
107 changes: 107 additions & 0 deletions examples/rust_doc_merge/toolchains/example_theme.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/* https://github.com/rust-lang/rust/blob/main/src/librustdoc/html/static/css/rustdoc.css */
/* Begin theme: example_theme */
:root[data-theme="example_theme"] {
--main-background-color: white;
--main-color: black;
--settings-input-color: #2196f3;
--settings-input-border-color: #717171;
--settings-button-color: #000;
--settings-button-border-focus: #717171;
--sidebar-background-color: #f5f5f5;
--sidebar-background-color-hover: #e0e0e0;
--sidebar-border-color: #ddd;
--code-block-background-color: #f5f5f5;
--scrollbar-track-background-color: #dcdcdc;
--scrollbar-thumb-background-color: rgba(36, 37, 39, 0.6);
--scrollbar-color: rgba(36, 37, 39, 0.6) #d9d9d9;
--headings-border-bottom-color: #ddd;
--border-color: #e0e0e0;
--button-background-color: #fff;
--right-side-color: grey;
--code-attribute-color: #999;
--toggles-color: #999;
--toggle-filter: none;
--mobile-sidebar-menu-filter: none;
--search-input-focused-border-color: #66afe9;
--copy-path-button-color: #999;
--copy-path-img-filter: invert(50%);
--copy-path-img-hover-filter: invert(35%);
--code-example-button-color: #7f7f7f;
--code-example-button-hover-color: #595959;
--settings-menu-filter: invert(50%);
--settings-menu-hover-filter: invert(35%);
--codeblock-error-hover-color: rgb(255, 0, 0);
--codeblock-error-color: rgba(255, 0, 0, .5);
--codeblock-ignore-hover-color: rgb(255, 142, 0);
--codeblock-ignore-color: rgba(255, 142, 0, .6);
--warning-border-color: #ff8e00;
--type-link-color: red;
--trait-link-color: red;
--assoc-item-link-color: red;
--function-link-color: red;
--macro-link-color: red;
--keyword-link-color: red;
--attribute-link-color: red;
--mod-link-color: red;
--link-color: red;
--sidebar-link-color: red;
--sidebar-current-link-background-color: #fff;
--search-result-link-focus-background-color: #ccc;
--search-result-border-color: #aaa3;
--search-color: #000;
--search-error-code-background-color: #d0cccc;
--search-results-alias-color: #000;
--search-results-grey-color: #999;
--search-tab-title-count-color: #888;
--search-tab-button-not-selected-border-top-color: #e6e6e6;
--search-tab-button-not-selected-background: #e6e6e6;
--search-tab-button-selected-border-top-color: #0089ff;
--search-tab-button-selected-background: #fff;
--stab-background-color: #fff5d6;
--stab-code-color: #000;
--code-highlight-kw-color: #8959a8;
--code-highlight-kw-2-color: #4271ae;
--code-highlight-lifetime-color: #b76514;
--code-highlight-prelude-color: #4271ae;
--code-highlight-prelude-val-color: #c82829;
--code-highlight-number-color: #718c00;
--code-highlight-string-color: #718c00;
--code-highlight-literal-color: #c82829;
--code-highlight-attribute-color: #c82829;
--code-highlight-self-color: #c82829;
--code-highlight-macro-color: #3e999f;
--code-highlight-question-mark-color: #ff9011;
--code-highlight-comment-color: #8e908c;
--code-highlight-doc-comment-color: #4d4d4c;
--src-line-numbers-span-color: #c67e2d;
--src-line-number-highlighted-background-color: #fdffd3;
--target-background-color: #fdffd3;
--target-border-color: #ad7c37;
--kbd-color: #000;
--kbd-background: #fafbfc;
--kbd-box-shadow-color: #c6cbd1;
--rust-logo-filter: initial;
/* match border-color; uses https://codepen.io/sosuke/pen/Pjoqqp */
--crate-search-div-filter: invert(100%) sepia(0%) saturate(4223%) hue-rotate(289deg)
brightness(114%) contrast(76%);
--crate-search-div-hover-filter: invert(44%) sepia(18%) saturate(23%) hue-rotate(317deg)
brightness(96%) contrast(93%);
--crate-search-hover-border: #717171;
--src-sidebar-background-selected: #fff;
--src-sidebar-background-hover: #e0e0e0;
--table-alt-row-background-color: #f5f5f5;
--codeblock-link-background: #eee;
--scrape-example-toggle-line-background: #ccc;
--scrape-example-toggle-line-hover-background: #999;
--scrape-example-code-line-highlight: #fcffd6;
--scrape-example-code-line-highlight-focus: #f6fdb0;
--scrape-example-help-border-color: #555;
--scrape-example-help-color: #333;
--scrape-example-help-hover-border-color: #000;
--scrape-example-help-hover-color: #000;
--scrape-example-code-wrapper-background-start: rgba(255, 255, 255, 1);
--scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0);
--sidebar-resizer-hover: hsl(207, 90%, 66%);
--sidebar-resizer-active: hsl(207, 90%, 54%);
}
/* End theme: example_theme */
Loading
Loading