Skip to content
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
35 changes: 35 additions & 0 deletions crates/cargo-census/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ pub(crate) struct Args {
pub(crate) workspace: PathBuf,
#[command(flatten)]
pub(crate) reference: ReferenceArgs,
#[command(flatten)]
pub(crate) registry: RegistryArgs,
#[command(subcommand)]
pub(crate) command: Command,
}
Expand Down Expand Up @@ -217,3 +219,36 @@ pub(crate) struct ReferenceArgs {
)]
pub(crate) unpinned: Option<PathBuf>,
}

/// Registry dependencies sealed from the lock file and Cargo's archive cache.
#[derive(ClapArgs)]
pub(crate) struct RegistryArgs {
/// Directory of Cargo's cached registry archives (`registry/cache`);
/// defaults to `$CARGO_HOME/registry/cache`, then `$HOME/.cargo/registry/cache`.
#[arg(
id = "registry_cache",
long = "registry-cache",
global = true,
value_name = "DIRECTORY"
)]
pub(crate) cache: Option<PathBuf>,
/// Registry package to compare against, sealed from the lock file: an
/// exact name or a `prefix-*` family. Repeatable; the ledger's reference
/// selectors are sealed as well.
#[arg(
id = "seal_registry",
long = "seal-registry",
global = true,
value_name = "SELECTOR"
)]
pub(crate) seal: Vec<String>,
/// Complete feature set the workspace enables on a sealed package,
/// `PACKAGE=feature,feature` (`PACKAGE=` for none). One flag per package.
#[arg(
id = "registry_features",
long = "registry-features",
global = true,
value_name = "PACKAGE=FEATURES"
)]
pub(crate) features: Vec<String>,
}
98 changes: 85 additions & 13 deletions crates/cargo-census/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@

//! Host input adapter; Census policy lives in the repository target and library.

use crate::cli::{Args, Command, MigrationArgs, QueryTarget, RdfFormat, ReferenceArgs};
use crate::cli::{
Args, Command, MigrationArgs, QueryTarget, RdfFormat, ReferenceArgs, RegistryArgs,
};
use raf_census::{
Census, CensusOptions, Error, ExternalReference, Gate, LEDGER_FILE, Ledger, MAX_INDEX_BYTES,
MAX_LEDGER_BYTES, MigrationInput, ReportError, migrate_index, migrate_legacy,
ArchiveCache, Census, CensusOptions, Error, ExternalReference, Gate, GateReport, LEDGER_FILE,
Ledger, LockedRegistry, MAX_INDEX_BYTES, MAX_LEDGER_BYTES, MigrationInput, ReportError,
migrate_index, migrate_legacy,
};
use raf_source::{DiskTree, RelPath, Tree as _};
use std::{io::Write, path::Path};

pub(crate) fn run(args: &Args, output: &mut dyn Write) -> Result<u8, crate::error::Error> {
let external = external(&args.reference)?;
if external.is_some()
let registry = registry(&args.registry)?;
if (external.is_some() || registry.is_some())
&& matches!(
args.command,
Command::SelfTest
Expand All @@ -23,7 +27,7 @@ pub(crate) fn run(args: &Args, output: &mut dyn Write) -> Result<u8, crate::erro
)
{
return Err(Error::Options(
"reference options apply only to check, index, job, count, fingerprint and debt".into(),
"reference options apply only to check, index, job, count, fingerprint and debt, and so do the registry options".into(),
)
.into());
}
Expand All @@ -50,41 +54,61 @@ pub(crate) fn run(args: &Args, output: &mut dyn Write) -> Result<u8, crate::erro
Ok(0)
}
Command::Check => {
let report = gate.check()?;
let report = evaluate(&gate, registry.as_ref())?;
for finding in report.findings() {
writeln!(output, "{finding}").map_err(stdout)?;
}
Ok(u8::from(!report.is_clean()))
}

Command::Index(target) => {
let index = native_index(&gate, target.max_index_bytes)?;
let index = native_index(&gate, registry.as_ref(), target.max_index_bytes)?;
let text = match target.format {
RdfFormat::Ntriples => index.to_ntriples()?,
RdfFormat::Turtle => index.to_turtle()?,
};
write_report(&gate.root()?, &text, target.out.as_deref(), output)?;
Ok(0)
}
Command::Job { id, target } => query(&gate, target, raf_census::Query::Job(id), output),
Command::Count { id, target } => query(&gate, target, raf_census::Query::Count(id), output),
Command::Job { id, target } => query(
&gate,
registry.as_ref(),
target,
raf_census::Query::Job(id),
output,
),
Command::Count { id, target } => query(
&gate,
registry.as_ref(),
target,
raf_census::Query::Count(id),
output,
),
Command::Fingerprint { symbol, target } => query(
&gate,
registry.as_ref(),
target,
raf_census::Query::Fingerprint(symbol),
output,
),
Command::Debt { write: _ } => {
let receipt = gate.write_debt()?;
let receipt = match registry.as_ref() {
Some(registry) => gate.write_debt_registry(registry)?,
None => gate.write_debt()?,
};
writeln!(output, "{receipt}").map_err(stdout)?;
Ok(0)
}
Command::Migrate(args) => migrate(&gate, args, output),
}
}

fn native_index(gate: &Gate, limit: usize) -> Result<raf_census::Index, crate::error::Error> {
let report = gate.check()?;
fn native_index(
gate: &Gate,
registry: Option<&LockedRegistry>,
limit: usize,
) -> Result<raf_census::Index, crate::error::Error> {
let report = evaluate(gate, registry)?;
let index = report.census().index(report.jobs())?;
let serialized = index.to_ntriples()?;
if serialized.len() > limit {
Expand All @@ -99,12 +123,13 @@ fn native_index(gate: &Gate, limit: usize) -> Result<raf_census::Index, crate::e

fn query(
gate: &Gate,
registry: Option<&LockedRegistry>,
target: &QueryTarget,
selector: raf_census::Query<'_>,
output: &mut dyn Write,
) -> Result<u8, crate::error::Error> {
let root = gate.root()?;
let index = native_index(gate, target.max_index_bytes)?;
let index = native_index(gate, registry, target.max_index_bytes)?;
let result = index.query(selector)?;
let text = match target.format {
RdfFormat::Ntriples => result.to_ntriples()?,
Expand All @@ -114,6 +139,53 @@ fn query(
Ok(0)
}

/// The one evaluation that `check`, `index`, `job`, `count` and `fingerprint`
/// share: the locked check with the sealed registry dependencies when the
/// options name any, otherwise the ordinary check.
fn evaluate(
gate: &Gate,
registry: Option<&LockedRegistry>,
) -> Result<GateReport, crate::error::Error> {
Ok(match registry {
Some(registry) => gate.check_registry(registry)?,
None => gate.check()?,
})
}

/// The sealed registry dependencies the options declare, or `None` when they
/// declare none. Every sealed package needs its complete feature set declared.
fn registry(args: &RegistryArgs) -> Result<Option<LockedRegistry>, Error> {
if args.cache.is_none() && args.seal.is_empty() && args.features.is_empty() {
return Ok(None);
}
let cache = match &args.cache {
Some(directory) => ArchiveCache::at(directory),
None => ArchiveCache::cargo_home()?,
};
let mut registry = LockedRegistry::new(cache).require_declared_features();
for selector in &args.seal {
registry = registry.seal_reference(selector);
}
let mut declared = std::collections::BTreeSet::new();
for declaration in &args.features {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
let (package, features) = declaration.split_once('=').ok_or_else(|| {
Error::Options(format!(
"--registry-features {declaration:?}: expected PACKAGE=FEATURE,FEATURE (PACKAGE= for none)"
))
})?;
if package.is_empty() || !declared.insert(package) {
return Err(Error::Options(format!(
"--registry-features {declaration:?}: name each package once, and name it"
)));
}
registry = registry.features(
package,
features.split(',').filter(|feature| !feature.is_empty()),
);
}
Ok(Some(registry))
}

/// The one caller-declared reference tree, when the options name one.
fn external(args: &ReferenceArgs) -> Result<Option<ExternalReference>, Error> {
Ok(Some(
Expand Down
Loading
Loading