use super::BuildError;
use std::{
path::{Path, PathBuf},
process::Command,
};
const OVERRIDDEN_TOOLCHAIN: Option<&str> = option_env!("RUSTDOC_JSON_OVERRIDDEN_TOOLCHAIN_HACK");
pub fn run_cargo_rustdoc(options: Builder) -> Result<PathBuf, BuildError> {
let mut cmd = cargo_rustdoc_command(&options);
if cmd.status()?.success() {
rustdoc_json_path_for_manifest_path(
options.manifest_path,
options.package.as_deref(),
&options.package_target,
options.target_dir.as_deref(),
options.target.as_deref(),
)
} else {
let manifest = cargo_manifest::Manifest::from_path(&options.manifest_path)?;
if manifest.package.is_none() && manifest.workspace.is_some() {
Err(BuildError::VirtualManifest(options.manifest_path))
} else {
Err(BuildError::General(String::from("See above")))
}
}
}
fn cargo_rustdoc_command(options: &Builder) -> Command {
let Builder {
toolchain: requested_toolchain,
manifest_path,
target_dir,
target,
quiet,
silent,
no_default_features,
all_features,
features,
package,
package_target,
document_private_items,
cap_lints,
} = options;
let mut command = OVERRIDDEN_TOOLCHAIN
.or(requested_toolchain.as_deref())
.map_or_else(
|| Command::new("cargo"),
|toolchain| {
let mut cmd = Command::new("rustup");
cmd.args(["run", toolchain, "cargo"]);
cmd
},
);
command.arg("rustdoc");
match package_target {
PackageTarget::Lib => command.arg("--lib"),
PackageTarget::Bin(target) => command.args(["--bin", target]),
PackageTarget::Example(target) => command.args(["--example", target]),
PackageTarget::Test(target) => command.args(["--test", target]),
PackageTarget::Bench(target) => command.args(["--bench", target]),
};
if let Some(target_dir) = target_dir {
command.arg("--target-dir");
command.arg(target_dir);
}
if *quiet {
command.arg("--quiet");
}
if *silent {
command.stdout(std::process::Stdio::null());
command.stderr(std::process::Stdio::null());
}
command.arg("--manifest-path");
command.arg(manifest_path);
if let Some(target) = target {
command.arg("--target");
command.arg(target);
}
if *no_default_features {
command.arg("--no-default-features");
}
if *all_features {
command.arg("--all-features");
}
for feature in features {
command.args(["--features", feature]);
}
if let Some(package) = package {
command.args(["--package", package]);
}
command.arg("--");
command.args(["-Z", "unstable-options"]);
command.args(["--output-format", "json"]);
if *document_private_items {
command.arg("--document-private-items");
}
if let Some(cap_lints) = cap_lints {
command.args(["--cap-lints", cap_lints]);
}
command
}
fn rustdoc_json_path_for_manifest_path(
manifest_path: impl AsRef<Path>,
package: Option<&str>,
package_target: &PackageTarget,
target_dir: Option<&Path>,
target: Option<&str>,
) -> Result<PathBuf, BuildError> {
let target_dir = match target_dir {
Some(target_dir) => target_dir.to_owned(),
None => target_directory(&manifest_path)?,
};
let package_target_name = match package_target {
PackageTarget::Lib => {
let lib_name = match package {
Some(package) => {
package
.split_once('@')
.map_or(package, |(start, _end)| start)
.to_owned()
}
None => package_name(&manifest_path)?,
};
lib_name.replace('-', "_")
}
PackageTarget::Bin(package)
| PackageTarget::Example(package)
| PackageTarget::Test(package)
| PackageTarget::Bench(package) => package.clone(),
};
let mut rustdoc_json_path = target_dir;
if let Some(target) = target {
rustdoc_json_path.push(target);
}
rustdoc_json_path.push("doc");
rustdoc_json_path.push(package_target_name);
rustdoc_json_path.set_extension("json");
Ok(rustdoc_json_path)
}
fn target_directory(manifest_path: impl AsRef<Path>) -> Result<PathBuf, BuildError> {
let mut metadata_cmd = cargo_metadata::MetadataCommand::new();
metadata_cmd.manifest_path(manifest_path.as_ref());
let metadata = metadata_cmd.exec()?;
Ok(metadata.target_directory.as_std_path().to_owned())
}
fn package_name(manifest_path: impl AsRef<Path>) -> Result<String, BuildError> {
let manifest = cargo_manifest::Manifest::from_path(manifest_path.as_ref())?;
Ok(manifest
.package
.ok_or_else(|| BuildError::VirtualManifest(manifest_path.as_ref().to_owned()))?
.name)
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Clone, Debug)]
pub struct Builder {
toolchain: Option<String>,
manifest_path: PathBuf,
target_dir: Option<PathBuf>,
target: Option<String>,
quiet: bool,
silent: bool,
no_default_features: bool,
all_features: bool,
features: Vec<String>,
package: Option<String>,
package_target: PackageTarget,
document_private_items: bool,
cap_lints: Option<String>,
}
impl Default for Builder {
fn default() -> Self {
Self {
toolchain: None,
manifest_path: PathBuf::from("Cargo.toml"),
target_dir: None,
target: None,
quiet: false,
silent: false,
no_default_features: false,
all_features: false,
features: vec![],
package: None,
package_target: PackageTarget::default(),
document_private_items: false,
cap_lints: Some(String::from("warn")),
}
}
}
impl Builder {
#[must_use]
pub fn toolchain(mut self, toolchain: impl Into<String>) -> Self {
self.toolchain = Some(toolchain.into());
self
}
#[must_use]
pub fn clear_toolchain(mut self) -> Self {
self.target_dir = None;
self
}
#[must_use]
pub fn manifest_path(mut self, manifest_path: impl AsRef<Path>) -> Self {
self.manifest_path = manifest_path.as_ref().to_owned();
self
}
#[must_use]
pub fn target_dir(mut self, target_dir: impl AsRef<Path>) -> Self {
self.target_dir = Some(target_dir.as_ref().to_owned());
self
}
#[must_use]
pub fn clear_target_dir(mut self) -> Self {
self.target_dir = None;
self
}
#[must_use]
pub const fn quiet(mut self, quiet: bool) -> Self {
self.quiet = quiet;
self
}
#[must_use]
pub const fn silent(mut self, silent: bool) -> Self {
self.silent = silent;
self
}
#[must_use]
pub fn target(mut self, target: String) -> Self {
self.target = Some(target);
self
}
#[must_use]
pub const fn no_default_features(mut self, no_default_features: bool) -> Self {
self.no_default_features = no_default_features;
self
}
#[must_use]
pub const fn all_features(mut self, all_features: bool) -> Self {
self.all_features = all_features;
self
}
#[must_use]
pub fn features<I: IntoIterator<Item = S>, S: AsRef<str>>(mut self, features: I) -> Self {
self.features = features
.into_iter()
.map(|item| item.as_ref().to_owned())
.collect();
self
}
#[must_use]
pub fn package(mut self, package: impl AsRef<str>) -> Self {
self.package = Some(package.as_ref().to_owned());
self
}
#[must_use]
pub fn package_target(mut self, package_target: PackageTarget) -> Self {
self.package_target = package_target;
self
}
#[must_use]
pub fn document_private_items(mut self, document_private_items: bool) -> Self {
self.document_private_items = document_private_items;
self
}
#[must_use]
pub fn cap_lints(mut self, cap_lints: Option<impl AsRef<str>>) -> Self {
self.cap_lints = cap_lints.map(|c| c.as_ref().to_owned());
self
}
pub fn build(self) -> Result<PathBuf, BuildError> {
run_cargo_rustdoc(self)
}
}
#[derive(Default, Debug, Clone)]
#[non_exhaustive]
pub enum PackageTarget {
#[default]
Lib,
Bin(String),
Example(String),
Test(String),
Bench(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ensure_toolchain_not_overridden() {
if option_env!("RUSTDOC_JSON_OVERRIDDEN_TOOLCHAIN_HACK").is_none() {
assert!(OVERRIDDEN_TOOLCHAIN.is_none());
}
}
}