Skip to content

Add doc coverage information #939

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

Merged
merged 10 commits into from
Aug 12, 2020
180 changes: 176 additions & 4 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ schemamama = "0.3"
schemamama_postgres = "0.3"
systemstat = "0.1.4"
prometheus = { version = "0.7.0", default-features = false }
rustwide = "0.7.1"
rustwide = "0.10.0"
mime_guess = "2"
dotenv = "0.15"
zstd = "0.5"
Expand Down
25 changes: 24 additions & 1 deletion src/db/add_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
};

use crate::{
docbuilder::BuildResult,
docbuilder::{BuildResult, DocCoverage},
error::Result,
index::api::{CrateData, CrateOwner, ReleaseData},
storage::CompressionAlgorithm,
Expand Down Expand Up @@ -130,6 +130,29 @@ pub(crate) fn add_package_into_database(
Ok(release_id)
}

pub(crate) fn add_doc_coverage(
conn: &mut Client,
release_id: i32,
doc_coverage: DocCoverage,
) -> Result<i32> {
debug!("Adding doc coverage into database");
let rows = conn.query(
"INSERT INTO doc_coverage (release_id, total_items, documented_items)
VALUES ($1, $2, $3)
ON CONFLICT (release_id) DO UPDATE
SET
total_items = $2,
documented_items = $3
RETURNING release_id",
&[
&release_id,
&doc_coverage.total_items,
&doc_coverage.documented_items,
],
)?;
Ok(rows[0].get(0))
}

/// Adds a build into database
pub(crate) fn add_build_into_database(
conn: &mut Client,
Expand Down
1 change: 1 addition & 0 deletions src/db/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const METADATA: &[(&str, &str)] = &[
("keyword_rels", "rid"),
("builds", "rid"),
("compression_rels", "release"),
("doc_coverage", "release_id"),
];

fn delete_version_from_database(conn: &mut Client, name: &str, version: &str) -> Result<(), Error> {
Expand Down
17 changes: 17 additions & 0 deletions src/db/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,23 @@ pub fn migrate(version: Option<Version>, conn: &mut Client) -> CratesfyiResult<(
-- Nope, this is a pure database fix, no going back.
"
),
migration!(
context,
// version
16,
// description
"Create new table for doc coverage",
// upgrade query
"
CREATE TABLE doc_coverage (
release_id INT UNIQUE REFERENCES releases(id),
total_items INT,
documented_items INT
);
",
// downgrade query
"DROP TABLE doc_coverage;"
),
];

for migration in migrations {
Expand Down
4 changes: 3 additions & 1 deletion src/db/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Database operations

pub use self::add_package::update_crate_data_in_database;
pub(crate) use self::add_package::{add_build_into_database, add_package_into_database};
pub(crate) use self::add_package::{
add_build_into_database, add_doc_coverage, add_package_into_database,
};
pub use self::delete::{delete_crate, delete_version};
pub use self::file::add_path_into_database;
pub use self::migrate::migrate;
Expand Down
2 changes: 1 addition & 1 deletion src/docbuilder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ mod rustwide_builder;

pub(crate) use self::limits::Limits;
pub(self) use self::metadata::Metadata;
pub(crate) use self::rustwide_builder::BuildResult;
pub use self::rustwide_builder::RustwideBuilder;
pub(crate) use self::rustwide_builder::{BuildResult, DocCoverage};

use crate::db::Pool;
use crate::error::Result;
Expand Down
215 changes: 154 additions & 61 deletions src/docbuilder/rustwide_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use super::Metadata;
use crate::db::blacklist::is_blacklisted;
use crate::db::file::add_path_into_database;
use crate::db::{
add_build_into_database, add_package_into_database, update_crate_data_in_database, Pool,
add_build_into_database, add_doc_coverage, add_package_into_database,
update_crate_data_in_database, Pool,
};
use crate::docbuilder::{crates::crates_from_path, Limits};
use crate::error::Result;
Expand All @@ -19,7 +20,7 @@ use rustwide::toolchain::ToolchainError;
use rustwide::{Build, Crate, Toolchain, Workspace, WorkspaceBuilder};
use serde_json::Value;
use std::borrow::Cow;
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::sync::Arc;

Expand Down Expand Up @@ -428,6 +429,10 @@ impl RustwideBuilder {
algs,
)?;

if let Some(doc_coverage) = res.result.doc_coverage {
add_doc_coverage(&mut conn, release_id, doc_coverage)?;
}

add_build_into_database(&mut conn, release_id, &res.result)?;

// Some crates.io crate data is mutable, so we proactively update it during a release
Expand Down Expand Up @@ -469,6 +474,55 @@ impl RustwideBuilder {
Ok(())
}

fn get_coverage(
&self,
target: &str,
build: &Build,
metadata: &Metadata,
limits: &Limits,
) -> Result<Option<DocCoverage>> {
let rustdoc_flags = vec![
"--output-format".to_string(),
"json".to_string(),
"--show-coverage".to_string(),
];

#[derive(serde::Deserialize)]
struct FileCoverage {
total: i32,
with_docs: i32,
}

let mut coverage = DocCoverage {
total_items: 0,
documented_items: 0,
};

self.prepare_command(build, target, metadata, limits, rustdoc_flags)?
.process_lines(&mut |line, _| {
if line.starts_with('{') && line.ends_with('}') {
let parsed = match serde_json::from_str::<HashMap<String, FileCoverage>>(line) {
Ok(parsed) => parsed,
Err(_) => return,
};
for file in parsed.values() {
coverage.total_items += file.total;
coverage.documented_items += file.with_docs;
}
}
})
.log_output(false)
.run()?;

Ok(
if coverage.total_items == 0 && coverage.documented_items == 0 {
None
} else {
Some(coverage)
},
)
}

fn execute_build(
&self,
target: &str,
Expand All @@ -480,16 +534,8 @@ impl RustwideBuilder {
let cargo_metadata =
CargoMetadata::load(&self.workspace, &self.toolchain, &build.host_source_dir())?;

let mut rustdoc_flags: Vec<String> = vec![
"-Z".to_string(),
"unstable-options".to_string(),
"--resource-suffix".to_string(),
format!("-{}", parse_rustc_version(&self.rustc_version)?),
"--static-root-path".to_string(),
"/".to_string(),
"--cap-lints".to_string(),
"warn".to_string(),
];
let mut rustdoc_flags = Vec::new();

for dep in &cargo_metadata.root_dependencies() {
rustdoc_flags.push("--extern-html-root-url".to_string());
rustdoc_flags.push(format!(
Expand All @@ -499,63 +545,25 @@ impl RustwideBuilder {
dep.version
));
}
if let Some(package_rustdoc_args) = &metadata.rustdoc_args {
rustdoc_flags.append(&mut package_rustdoc_args.iter().map(|s| s.to_owned()).collect());
}
let mut cargo_args = vec!["doc", "--lib", "--no-deps"];
if target != HOST_TARGET {
// If the explicit target is not a tier one target, we need to install it.
if !TARGETS.contains(&target) {
// This is a no-op if the target is already installed.
self.toolchain.add_target(&self.workspace, target)?;
}
cargo_args.push("--target");
cargo_args.push(target);
};

let tmp_jobs;
if let Some(cpu_limit) = self.cpu_limit {
tmp_jobs = format!("-j{}", cpu_limit);
cargo_args.push(&tmp_jobs);
}

let tmp;
if let Some(features) = &metadata.features {
cargo_args.push("--features");
tmp = features.join(" ");
cargo_args.push(&tmp);
}
if metadata.all_features {
cargo_args.push("--all-features");
}
if metadata.no_default_features {
cargo_args.push("--no-default-features");
}
rustdoc_flags.extend(vec![
"--resource-suffix".to_string(),
format!("-{}", parse_rustc_version(&self.rustc_version)?),
]);

let mut storage = LogStorage::new(LevelFilter::Info);
storage.set_max_size(limits.max_log_size());

let successful = logging::capture(&storage, || {
build
.cargo()
.timeout(Some(limits.timeout()))
.no_output_timeout(None)
.env(
"RUSTFLAGS",
metadata
.rustc_args
.as_ref()
.map(|args| args.join(" "))
.unwrap_or_default(),
)
.env("RUSTDOCFLAGS", rustdoc_flags.join(" "))
// For docs.rs detection from build script:
// https://github.com/rust-lang/docs.rs/issues/147
.env("DOCS_RS", "1")
.args(&cargo_args)
.run()
self.prepare_command(build, target, metadata, limits, rustdoc_flags)
.and_then(|command| command.run().map_err(failure::Error::from))
.is_ok()
});
let doc_coverage = if successful {
self.get_coverage(target, build, metadata, limits)?
} else {
None
};
// If we're passed a default_target which requires a cross-compile,
// cargo will put the output in `target/<target>/doc`.
// However, if this is the default build, we don't want it there,
Expand All @@ -575,12 +583,87 @@ impl RustwideBuilder {
rustc_version: self.rustc_version.clone(),
docsrs_version: format!("docsrs {}", crate::BUILD_VERSION),
successful,
doc_coverage,
},
cargo_metadata,
target: target.to_string(),
})
}

fn prepare_command<'ws, 'pl>(
&self,
build: &'ws Build,
target: &str,
metadata: &Metadata,
limits: &Limits,
rustdoc_flags_extras: Vec<String>,
) -> Result<Command<'ws, 'pl>> {
let mut cargo_args = vec!["doc", "--lib", "--no-deps"];
if target != HOST_TARGET {
// If the explicit target is not a tier one target, we need to install it.
if !TARGETS.contains(&target) {
// This is a no-op if the target is already installed.
self.toolchain.add_target(&self.workspace, target)?;
}
cargo_args.push("--target");
cargo_args.push(target);
};

let tmp;
if let Some(cpu_limit) = self.cpu_limit {
tmp = format!("-j{}", cpu_limit);
cargo_args.push(&tmp);
}

let tmp;
if let Some(features) = &metadata.features {
cargo_args.push("--features");
tmp = features.join(" ");
cargo_args.push(&tmp);
}
if metadata.all_features {
cargo_args.push("--all-features");
}
if metadata.no_default_features {
cargo_args.push("--no-default-features");
}

let mut rustdoc_flags = vec![
"-Z".to_string(),
"unstable-options".to_string(),
"--static-root-path".to_string(),
"/".to_string(),
"--cap-lints".to_string(),
"warn".to_string(),
];

if let Some(package_rustdoc_args) = &metadata.rustdoc_args {
rustdoc_flags.append(&mut package_rustdoc_args.clone());
}

rustdoc_flags.extend(rustdoc_flags_extras);

let command = build
.cargo()
.timeout(Some(limits.timeout()))
.no_output_timeout(None)
.env(
"RUSTFLAGS",
metadata
.rustc_args
.as_ref()
.map(|args| args.join(" "))
.unwrap_or_default(),
)
.env("RUSTDOCFLAGS", rustdoc_flags.join(" "))
// For docs.rs detection from build script:
// https://github.com/rust-lang/docs.rs/issues/147
.env("DOCS_RS", "1")
.args(&cargo_args);

Ok(command)
}

fn copy_docs(
&self,
target_dir: &Path,
Expand Down Expand Up @@ -626,9 +709,19 @@ struct FullBuildResult {
cargo_metadata: CargoMetadata,
}

#[derive(Clone, Copy)]
pub(crate) struct DocCoverage {
/// The total items that could be documented in the current crate, used to calculate
/// documentation coverage.
pub(crate) total_items: i32,
/// The items of the crate that are documented, used to calculate documentation coverage.
pub(crate) documented_items: i32,
}

pub(crate) struct BuildResult {
pub(crate) rustc_version: String,
pub(crate) docsrs_version: String,
pub(crate) build_log: String,
pub(crate) successful: bool,
pub(crate) doc_coverage: Option<DocCoverage>,
}
Loading