Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow crates to opt-in to building a single target #632

Merged
merged 21 commits into from
Mar 14, 2020
Merged
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
150 changes: 146 additions & 4 deletions src/docbuilder/metadata.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

use std::collections::HashSet;
use std::path::Path;
use toml::Value;
use error::Result;
Expand All @@ -20,6 +20,7 @@ use failure::err_msg;
/// all-features = true
/// no-default-features = true
/// default-target = "x86_64-unknown-linux-gnu"
/// targets = [ "x86_64-apple-darwin", "x86_64-pc-windows-msvc" ]
/// rustc-args = [ "--example-rustc-arg" ]
/// rustdoc-args = [ "--example-rustdoc-arg" ]
/// ```
Expand All @@ -39,18 +40,40 @@ pub struct Metadata {
/// Set `no-default-fatures` to `false` if you want to build only certain features.
pub no_default_features: bool,

/// Docs.rs is running on `x86_64-unknown-linux-gnu` target system and default documentation
/// is always built on this target. You can change default target by setting this.
/// docs.rs runs on `x86_64-unknown-linux-gnu`, which is the default target for documentation by default.
///
/// You can change the default target by setting this.
///
/// If `default_target` is unset and `targets` is non-empty,
/// the first element of `targets` will be used as the `default_target`.
pub default_target: Option<String>,

/// If you want a crate to build only for specific targets,
/// set `targets` to the list of targets to build, in addition to `default-target`.
///
/// If you do not set `targets`, all of the tier 1 supported targets will be built.
/// If you set `targets` to an empty array, only the default target will be built.
/// If you set `targets` to a non-empty array but do not set `default_target`,
/// the first element will be treated as the default.
pub targets: Option<Vec<String>>,

/// List of command line arguments for `rustc`.
pub rustc_args: Option<Vec<String>>,

/// List of command line arguments for `rustdoc`.
pub rustdoc_args: Option<Vec<String>>,
}


/// The targets that should be built for a crate.
///
/// The `default_target` is the target to be used as the home page for that crate.
///
/// # See also
/// - [`Metadata::targets`](struct.Metadata.html#method.targets)
pub(super) struct BuildTargets<'a> {
pub(super) default_target: &'a str,
pub(super) other_targets: HashSet<&'a str>,
}

impl Metadata {
pub(crate) fn from_source_dir(source_dir: &Path) -> Result<Metadata> {
Expand Down Expand Up @@ -87,6 +110,7 @@ impl Metadata {
default_target: None,
rustc_args: None,
rustdoc_args: None,
targets: None,
}
}

Expand All @@ -111,6 +135,8 @@ impl Metadata {
.and_then(|v| v.as_bool()).unwrap_or(metadata.all_features);
metadata.default_target = table.get("default-target")
.and_then(|v| v.as_str()).map(|v| v.to_owned());
metadata.targets = table.get("targets").and_then(|f| f.as_array())
.and_then(|f| f.iter().map(|v| v.as_str().map(|v| v.to_owned())).collect());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to do it in this PR, but it would be nice to just use #[derive(Deserialize)].

metadata.rustc_args = table.get("rustc-args").and_then(|f| f.as_array())
.and_then(|f| f.iter().map(|v| v.as_str().map(|v| v.to_owned())).collect());
metadata.rustdoc_args = table.get("rustdoc-args").and_then(|f| f.as_array())
Expand All @@ -119,6 +145,22 @@ impl Metadata {

metadata
}
pub(super) fn targets(&self) -> BuildTargets<'_> {
use super::rustwide_builder::{HOST_TARGET, TARGETS};

let default_target = self.default_target.as_deref()
// Use the first element of `targets` if `default_target` is unset and `targets` is non-empty
.or_else(|| self.targets.as_ref().and_then(|targets| targets.iter().next().map(String::as_str)))
.unwrap_or(HOST_TARGET);

// Let people opt-in to only having specific targets
let mut targets: HashSet<_> = self.targets.as_ref()
.map(|targets| targets.iter().map(String::as_str).collect())
.unwrap_or_else(|| TARGETS.iter().copied().collect());

targets.remove(&default_target);
BuildTargets { default_target, other_targets: targets }
}
}


Expand All @@ -140,6 +182,7 @@ mod test {
all-features = true
no-default-features = true
default-target = "x86_64-unknown-linux-gnu"
targets = [ "x86_64-apple-darwin", "x86_64-pc-windows-msvc" ]
rustc-args = [ "--example-rustc-arg" ]
rustdoc-args = [ "--example-rustdoc-arg" ]
"#;
Expand All @@ -159,6 +202,11 @@ mod test {

assert_eq!(metadata.default_target.unwrap(), "x86_64-unknown-linux-gnu".to_owned());

let targets = metadata.targets.expect("should have explicit target");
assert_eq!(targets.len(), 2);
assert_eq!(targets[0], "x86_64-apple-darwin");
assert_eq!(targets[1], "x86_64-pc-windows-msvc");

let rustc_args = metadata.rustc_args.unwrap();
assert_eq!(rustc_args.len(), 1);
assert_eq!(rustc_args[0], "--example-rustc-arg".to_owned());
Expand All @@ -167,4 +215,98 @@ mod test {
assert_eq!(rustdoc_args.len(), 1);
assert_eq!(rustdoc_args[0], "--example-rustdoc-arg".to_owned());
}

#[test]
fn test_no_targets() {
// metadata section but no targets
let manifest = r#"
[package]
name = "test"

[package.metadata.docs.rs]
features = [ "feature1", "feature2" ]
"#;
let metadata = Metadata::from_str(manifest);
assert!(metadata.targets.is_none());

// no package.metadata.docs.rs section
let metadata = Metadata::from_str(r#"
[package]
name = "test"
"#);
assert!(metadata.targets.is_none());

// targets explicitly set to empty array
let metadata = Metadata::from_str(r#"
[package.metadata.docs.rs]
targets = []
"#);
assert!(metadata.targets.unwrap().is_empty());
}
#[test]
fn test_select_targets() {
use crate::docbuilder::rustwide_builder::{HOST_TARGET, TARGETS};
use super::BuildTargets;

let mut metadata = Metadata::default();
// unchanged default_target, targets not specified
let BuildTargets { default_target: default, other_targets: tier_one } = metadata.targets();
assert_eq!(default, HOST_TARGET);
// should be equal to TARGETS \ {HOST_TARGET}
for actual in &tier_one {
assert!(TARGETS.contains(actual));
}
for expected in TARGETS {
if *expected == HOST_TARGET {
assert!(!tier_one.contains(&HOST_TARGET));
} else {
assert!(tier_one.contains(expected));
}
}

// unchanged default_target, targets specified to be empty
metadata.targets = Some(Vec::new());
let BuildTargets { default_target: default, other_targets: others } = metadata.targets();
assert_eq!(default, HOST_TARGET);
assert!(others.is_empty());

// unchanged default_target, targets non-empty
metadata.targets = Some(vec!["i686-pc-windows-msvc".into(), "i686-apple-darwin".into()]);
let BuildTargets { default_target: default, other_targets: others } = metadata.targets();
assert_eq!(default, "i686-pc-windows-msvc");
assert_eq!(others.len(), 1);
assert!(others.contains(&"i686-apple-darwin"));

// make sure that default_target is not built twice
metadata.targets = Some(vec![HOST_TARGET.into()]);
let BuildTargets { default_target: default, other_targets: others } = metadata.targets();
assert_eq!(default, HOST_TARGET);
assert!(others.is_empty());

// make sure that duplicates are removed
metadata.targets = Some(vec!["i686-pc-windows-msvc".into(), "i686-pc-windows-msvc".into()]);
let BuildTargets { default_target: default, other_targets: others } = metadata.targets();
assert_eq!(default, "i686-pc-windows-msvc");
assert!(others.is_empty());

// make sure that `default_target` always takes priority over `targets`
metadata.default_target = Some("i686-apple-darwin".into());
let BuildTargets { default_target: default, other_targets: others } = metadata.targets();
assert_eq!(default, "i686-apple-darwin");
assert_eq!(others.len(), 1);
assert!(others.contains(&"i686-pc-windows-msvc"));

// make sure that `default_target` takes priority over `HOST_TARGET`
metadata.targets = Some(vec![]);
let BuildTargets { default_target: default, other_targets: others } = metadata.targets();
assert_eq!(default, "i686-apple-darwin");
assert!(others.is_empty());

// and if `targets` is unset, it should still be set to `TARGETS`
metadata.targets = None;
let BuildTargets { default_target: default, other_targets: others } = metadata.targets();
assert_eq!(default, "i686-apple-darwin");
let tier_one_targets_no_default = TARGETS.iter().filter(|&&t| t != "i686-apple-darwin").copied().collect();
assert_eq!(others, tier_one_targets_no_default);
}
}
61 changes: 33 additions & 28 deletions src/docbuilder/rustwide_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ use super::Metadata;
const USER_AGENT: &str = "docs.rs builder (https://github.com/rust-lang/docs.rs)";
const DEFAULT_RUSTWIDE_WORKSPACE: &str = ".rustwide";

const DEFAULT_TARGET: &str = "x86_64-unknown-linux-gnu";
const TARGETS: &[&str] = &[
// It is crucial that this be the same as the host that `docs.rs` is being run on.
// Other values may cause strange and hard-to-debug errors.
// TODO: use `TARGET` instead? I think `TARGET` is only set for build scripts, though.
pub(super) const HOST_TARGET: &str = "x86_64-unknown-linux-gnu";
pub(super) const TARGETS: &[&str] = &[
"i686-pc-windows-msvc",
"i686-unknown-linux-gnu",
"x86_64-apple-darwin",
Expand Down Expand Up @@ -189,7 +192,9 @@ impl RustwideBuilder {
build_dir
.build(&self.toolchain, &krate, sandbox)
.run(|build| {
let res = self.execute_build(None, build, &limits)?;
let metadata = Metadata::from_source_dir(&build.host_source_dir())?;

let res = self.execute_build(HOST_TARGET, true, build, &limits, &metadata)?;
if !res.result.successful {
bail!("failed to build dummy crate for {}", self.rustc_version);
}
Expand Down Expand Up @@ -309,12 +314,16 @@ impl RustwideBuilder {
let res = build_dir
.build(&self.toolchain, &krate, sandbox)
.run(|build| {
use docbuilder::metadata::BuildTargets;

let mut files_list = None;
let mut has_docs = false;
let mut successful_targets = Vec::new();
let metadata = Metadata::from_source_dir(&build.host_source_dir())?;
let BuildTargets { default_target, other_targets } = metadata.targets();

// Do an initial build and then copy the sources in the database
let res = self.execute_build(None, &build, &limits)?;
let res = self.execute_build(default_target, true, &build, &limits, &metadata)?;
if res.result.successful {
debug!("adding sources into database");
let prefix = format!("sources/{}/{}", name, version);
Expand All @@ -340,18 +349,17 @@ impl RustwideBuilder {
)?;

successful_targets.push(res.target.clone());

// Then build the documentation for all the targets
for target in TARGETS {
if *target == res.target {
continue;
}
debug!("building package {} {} for {}", name, version, &target);
for target in other_targets {
debug!("building package {} {} for {}", name, version, target);
self.build_target(
&target,
target,
&build,
&limits,
&local_storage.path(),
&mut successful_targets,
&metadata,
)?;
}
self.upload_docs(&conn, name, version, local_storage.path())?;
Expand Down Expand Up @@ -396,8 +404,9 @@ impl RustwideBuilder {
limits: &Limits,
local_storage: &Path,
successful_targets: &mut Vec<String>,
metadata: &Metadata,
) -> Result<()> {
let target_res = self.execute_build(Some(target), build, limits)?;
let target_res = self.execute_build(target, false, build, limits, metadata)?;
if target_res.result.successful {
// Cargo is not giving any error and not generating documentation of some crates
// when we use a target compile options. Check documentation exists before
Expand All @@ -413,17 +422,15 @@ impl RustwideBuilder {

fn execute_build(
&self,
target: Option<&str>,
target: &str,
is_default_target: bool,
build: &Build,
limits: &Limits,
metadata: &Metadata,
) -> Result<FullBuildResult> {
let metadata = Metadata::from_source_dir(&build.host_source_dir())?;
let cargo_metadata =
CargoMetadata::load(&self.workspace, &self.toolchain, &build.host_source_dir())?;

let is_default_target = target.is_none();
let target = target.or_else(|| metadata.default_target.as_ref().map(|s| s.as_str()));

let mut rustdoc_flags: Vec<String> = vec![
"-Z".to_string(),
"unstable-options".to_string(),
Expand All @@ -445,9 +452,9 @@ impl RustwideBuilder {
rustdoc_flags.append(&mut package_rustdoc_args.iter().map(|s| s.to_owned()).collect());
}
let mut cargo_args = vec!["doc".to_owned(), "--lib".to_owned(), "--no-deps".to_owned()];
if let Some(explicit_target) = target {
if target != HOST_TARGET {
cargo_args.push("--target".to_owned());
cargo_args.push(explicit_target.to_owned());
cargo_args.push(target.to_owned());
};
if let Some(features) = &metadata.features {
cargo_args.push("--features".to_owned());
Expand Down Expand Up @@ -485,15 +492,13 @@ impl RustwideBuilder {
// cargo will put the output in `target/<target>/doc`.
// However, if this is the default build, we don't want it there,
// we want it in `target/doc`.
if let Some(explicit_target) = target {
if is_default_target {
// mv target/$explicit_target/doc target/doc
let target_dir = build.host_target_dir();
let old_dir = target_dir.join(explicit_target).join("doc");
let new_dir = target_dir.join("doc");
debug!("rename {} to {}", old_dir.display(), new_dir.display());
std::fs::rename(old_dir, new_dir)?;
}
if target != HOST_TARGET && is_default_target {
// mv target/$target/doc target/doc
let target_dir = build.host_target_dir();
let old_dir = target_dir.join(target).join("doc");
let new_dir = target_dir.join("doc");
debug!("rename {} to {}", old_dir.display(), new_dir.display());
std::fs::rename(old_dir, new_dir)?;
}

Ok(FullBuildResult {
Expand All @@ -504,7 +509,7 @@ impl RustwideBuilder {
successful,
},
cargo_metadata,
target: target.unwrap_or(DEFAULT_TARGET).to_string(),
target: target.to_string(),
})
}

Expand Down
11 changes: 11 additions & 0 deletions templates/about.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@ no-default-features = true
# - i686-pc-windows-msvc
default-target = "x86_64-unknown-linux-gnu"

# Targets to build (default: all tier 1 targets)
#
# Same available targets as `default-target`.
# Set this to `[]` to only build the default target.
#
# If `default-target` is unset, the first element of `targets` is treated as the default target.
# Otherwise, these `targets` are built in addition to the default target.
# If both `default-target` and `targets` are unset,
# all tier-one targets will be built and `x86_64-unknown-linux-gnu` will be used as the default target.
targets = [ "x86_64-apple-darwin", "x86_64-pc-windows-msvc" ]

# Additional `RUSTFLAGS` to set (default: none)
rustc-args = [ "--example-rustc-arg" ]

Expand Down