#[cfg(test)]
mod tests;
mod workspace;
pub use workspace::workspace_root_for;
pub use cargo_metadata as schema;
use ic_host_process::tool::{CompletedExecution, OutputLimits, capture_command};
use serde::Deserialize;
use serde_json::Value;
use std::{collections::BTreeSet, path::Path, process::Command};
pub(crate) fn decode_metadata(value: &Value) -> Result<schema::Metadata, serde_json::Error> {
schema::Metadata::deserialize(value)
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CargoFeatureSelection {
pub features: BTreeSet<String>,
pub default_features: bool,
}
impl Default for CargoFeatureSelection {
fn default() -> Self {
Self {
features: BTreeSet::new(),
default_features: true,
}
}
}
impl CargoFeatureSelection {
fn configure(&self, command: &mut Command) {
if !self.default_features {
command.arg("--no-default-features");
}
if !self.features.is_empty() {
let mut features = String::new();
for (index, feature) in self.features.iter().enumerate() {
if index > 0 {
features.push(',');
}
features.push_str(feature);
}
command.arg("--features").arg(features);
}
}
}
pub struct CargoMetadataSpec<'a> {
pub manifest_path: &'a Path,
pub include_dependencies: bool,
pub filter_platform: Option<&'a str>,
pub locked: bool,
pub offline: bool,
pub features: &'a CargoFeatureSelection,
}
pub struct CargoTreeSpec<'a> {
pub manifest_path: &'a Path,
pub packages: &'a [&'a str],
pub target: &'a str,
pub edges: &'a str,
pub format: &'a str,
pub locked: bool,
pub offline: bool,
pub features: &'a CargoFeatureSelection,
}
pub fn observe_metadata(
spec: &CargoMetadataSpec<'_>,
limits: OutputLimits,
mut command: Command,
) -> Result<schema::Metadata, Box<dyn std::error::Error>> {
command.args(["metadata", "--format-version=1"]);
configure(
&mut command,
spec.manifest_path,
spec.locked,
spec.offline,
spec.features,
)?;
if !spec.include_dependencies {
command.arg("--no-deps");
}
if let Some(target) = spec.filter_platform {
command.arg("--filter-platform").arg(target);
}
let output = capture(&mut command, limits)?;
Ok(serde_json::from_slice(&output.stdout)?)
}
pub fn observe_tree(
spec: &CargoTreeSpec<'_>,
limits: OutputLimits,
mut command: Command,
) -> Result<String, Box<dyn std::error::Error>> {
if spec.packages.is_empty() || spec.packages.iter().any(|package| package.is_empty()) {
return Err("Cargo tree requires nonempty package selectors".into());
}
command.args([
"tree",
"--color",
"never",
"--target",
spec.target,
"--edges",
spec.edges,
"--prefix",
"depth",
"--no-dedupe",
"--charset",
"ascii",
"--format",
spec.format,
]);
configure(
&mut command,
spec.manifest_path,
spec.locked,
spec.offline,
spec.features,
)?;
for package in spec.packages {
command.arg("--package").arg(package);
}
let output = capture(&mut command, limits)?;
Ok(String::from_utf8(output.stdout)?)
}
fn configure(
command: &mut Command,
manifest: &Path,
locked: bool,
offline: bool,
features: &CargoFeatureSelection,
) -> std::io::Result<()> {
let manifest = if manifest.is_absolute() {
manifest.to_path_buf()
} else {
std::env::current_dir()?.join(manifest)
};
command.current_dir(manifest.parent().unwrap_or_else(|| Path::new(".")));
command.arg("--manifest-path").arg(manifest);
features.configure(command);
if locked {
command.arg("--locked");
}
if offline {
command.arg("--offline");
}
Ok(())
}
fn capture(
command: &mut Command,
limits: OutputLimits,
) -> Result<CompletedExecution, ic_host_process::tool::ToolError> {
capture_command(command, limits)
.and_then(CompletedExecution::require_success)
.and_then(CompletedExecution::require_complete)
}