rustdoc_assets/lib.rs
1//! Build script helper which copies media and assets from a source directory below your
2//! current crate to the target output directory of rustdoc.
3//!
4//! rustdoc currently does not support copying media files to the documentation output
5//! directory. Pictures can only be included if they can be referenced as an online resource.
6//!
7//! This crate mitigates the problem. Add a call to
8//! [`copy_assets_folder()`](fn.copy_assets_folder.html) to the build script `build.rs` of
9//! your crate. This will copy the specified source directory to the rustdoc output directory.
10//!
11//! Source: [https://mrh.io/cargo-build-images-in-rust-docs/](https://mrh.io/cargo-build-images-in-rust-docs/)
12//!
13//! # Example
14//!
15//! Consider the following directory structure of crate "foo":
16//! ```text
17//! .
18//! ├── build.rs
19//! ├── Cargo.toml
20//! ├── changelog.md
21//! ├── doc
22//! │ └── img
23//! │ └── it-works.svg
24//! ├── readme.md
25//! ├── src
26//! │ └── lib.rs
27//! └── target
28//! ```
29//!
30//! In this example, a call to `cargo doc` would create the API documentation in
31//! `./target/doc/foo`. We want to include the file `doc/img/it-works.svg` in the crate
32//! documentation directory.
33//!
34//! To do this, add a build dependency to `rustdoc-assets` in your `Cargo.toml`:
35//!
36//! ```toml
37//! [build-dependencies]
38//! rustdoc-assets = "0.2"
39//! ```
40//!
41//! In `build.rs` do:
42//! ```
43//! # // simulate cargo environment variables so that the test compiles
44//! # std::env::set_var("HOST", "x86_64-unknown-linux-gnu");
45//! # std::env::set_var("TARGET", "x86_64-unknown-linux-gnu");
46//! rustdoc_assets::copy_assets_folder("doc/img");
47//! ```
48//!
49//! This will copy `./doc/img` to `./target/doc/foo/img`. In the rustdoc comment the
50//! images can then be referenced through an HTML-tag as follows:
51//!
52//! ```html
53//! /// <div align="center">
54//! /// <img src="img/it-works.svg" width="200" />
55//! /// </div>
56//! ```
57//!
58//! <div align="center">
59//! <img src="img/it-works.svg" width="200" /><br/>
60//! <span style="font-size: small">Source: <a href="https://en.m.wikipedia.org/wiki/File:SMirC-thumbsup.svg">Wikipedia (CC)</a></span>
61//! </div>
62//!
63//! # Update 2021-10-16
64//!
65//! In Rust 1.55 cargo doc now auto-cleans the `target/doc`-directory before generating
66//! the documentation. However, rustdoc-assets uses the build script and only executes
67//! during calls to cargo build/check. If cargo doc is executed afterwards the folders
68//! subsequently get removed. I currently do not have a better solution than to at least
69//! run cargo check one more time after cargo doc.
70//!
71
72use std::convert::AsRef;
73use std::path::{Path, PathBuf};
74use std::{env, fs};
75
76const COPY_OPTS: fs_extra::dir::CopyOptions = fs_extra::dir::CopyOptions {
77 overwrite: true,
78 skip_exist: false,
79 buffer_size: 64000,
80 copy_inside: false,
81 content_only: false,
82 depth: 0,
83};
84
85/// Copy media files (images, etc) to your rustdoc output directory.
86///
87/// `source` is the path to your assets base-folder(i. e. "doc/images").
88/// The path is relative to the location of your <span style="text-decoration:
89/// underline">crate</span>'s manifest file Cargo.toml.
90///
91/// The default output directory for documentation is `target/doc/`.
92///
93/// rustdoc supports two ways of changing this output directory:
94/// The command line flag `--target-dir` and the environment variable `CARGO_TARGET_DIR`.
95/// `rustdoc-assets` cannot know about the runtime command line argument, but it does
96/// check the environment variable for changes to the default output directory.
97///
98/// If your package is part of a workspace, create the following file as part of your
99/// workspace `.cargo/config.toml`:
100///
101/// ```toml
102/// [env]
103/// ## points to the current working directory; required for rustdoc-assets
104/// CARGO_WORKSPACE_DIR = { value = "", relative = true }
105/// ```
106///
107/// This will cause `copy_assets_folder()` to use the workspace directory as base path,
108/// instead of your crate root directory.
109///
110/// See the [Crate documentation](index.html#example) for an example.
111///
112pub fn copy_assets_folder<T>(source: T)
113where
114 T: AsRef<Path>,
115{
116 // Should panic if these aren't a thing
117 let host = env::var("HOST").unwrap();
118 let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
119 let target = std::env::var("TARGET").unwrap();
120 let target_dir = std::env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| String::from("target"));
121 let workspace_dir = std::env::var("CARGO_WORKSPACE_DIR").ok();
122 println!("host: {}", host);
123 println!("target: {}", target);
124 println!("target_dir: {:?}", target_dir);
125 println!("workspace_dir: {:?}", workspace_dir);
126
127 // If the target is the same as the host AND no target is
128 // _explicitly specified, the directory will be ./target/doc,
129 // Elsewhere, the directory will be ./target/{triple}/doc.
130 // FIXME: target == host WITH explicitly specified --target
131 //
132 // Also, Gotta deal with crates that might have a dash in their name
133 let sanitized_name = crate_name.replace('-', "_");
134 let mut docs_dir = if let Some(ws) = workspace_dir {
135 vec![ws]
136 } else {
137 Vec::new()
138 };
139 if host != target {
140 docs_dir.extend_from_slice(&[target_dir, target, String::from("doc"), sanitized_name]);
141 } else {
142 docs_dir.extend_from_slice(&[target_dir, String::from("doc"), sanitized_name])
143 }
144 let docs_dir = docs_dir.join("/");
145 let docs_dir_path = Path::new(&docs_dir);
146
147 // Pre-emptively create the directory and copy ./doc/img into there
148 fs::create_dir_all(&docs_dir).unwrap();
149 fs_extra::copy_items(&[&source], &docs_dir, ©_OPTS).unwrap();
150
151 let mut change_source = PathBuf::new();
152 change_source.push(&source);
153 // source directory changed
154 println!(
155 "cargo:rerun-if-changed={}",
156 change_source.as_path().display()
157 );
158 // destination directory changed (i. e. if cargo-doc was executed)
159 println!("cargo:rerun-if-changed={}", docs_dir_path.display());
160}