use std::path::PathBuf;
use anyhow::Result;
use clap::{Args, Subcommand};
use crate::cli::{
ConfigSelectionArgs, ResolveFormat, ToolchainSelectionArgs, WorkspaceSelectionArgs,
build_selection_request, build_workspace_selection, compiler_wrapper_override_from_args,
compute_feature_resolution, lockfile_path_for, profile_selection_for_metadata,
read_optional_lockfile, resolve_build_configurations, resolve_invocation_manifest,
toolchain_selection_from_args, workspace_compiler_wrapper_settings,
workspace_profile_definitions,
};
#[derive(Debug, Args)]
pub(crate) struct ExplainArgs {
#[arg(long, value_name = "PATH")]
pub manifest_path: Option<PathBuf>,
#[arg(long, value_name = "FORMAT", default_value = "human", global = true)]
pub format: ResolveFormat,
#[arg(long, value_name = "NAME", global = true)]
pub profile: Option<String>,
#[command(flatten)]
pub toolchain: ToolchainSelectionArgs,
#[command(flatten)]
pub selection: ConfigSelectionArgs,
#[command(flatten)]
pub workspace_selection: WorkspaceSelectionArgs,
#[arg(long, global = true)]
pub no_patches: bool,
#[command(subcommand)]
pub command: ExplainCommand,
}
#[derive(Debug, Subcommand)]
pub(crate) enum ExplainCommand {
Package {
name: String,
},
Target {
name: String,
},
Source {
name: String,
},
Feature {
query: String,
},
BuildConfig {
name: String,
},
}
pub(crate) fn explain(
args: &ExplainArgs,
reporter: crate::cli::term_verbosity::Reporter,
) -> Result<()> {
let manifest_path = resolve_invocation_manifest(args.manifest_path.as_deref())?;
let explain_selection = build_workspace_selection(&args.workspace_selection);
let (prepared_ports, initial_graph) = crate::cli::port::prepare_ports_and_load_initial_graph(
&manifest_path,
None,
true,
false,
false,
&explain_selection,
args.no_patches,
)?;
let port_sources: Vec<cabin_workspace::PortPackageSource> = prepared_ports
.iter()
.map(crate::cli::port::workspace_source)
.collect();
let effective_config = crate::cli::config::load_effective_config(&initial_graph)?;
let active_patches =
crate::cli::patch::load_active_patches(&initial_graph, &effective_config, args.no_patches)?;
let patched_sources = active_patches.workspace_sources();
let graph = crate::cli::patch::reload_for_patches(
&manifest_path,
initial_graph,
&patched_sources,
&port_sources,
)?;
let lockfile_path = lockfile_path_for(&manifest_path);
let lockfile = read_optional_lockfile(&lockfile_path)?;
let request = build_selection_request(
&args.selection.features,
args.selection.all_features,
args.selection.no_default_features,
);
let resolved_selection =
cabin_workspace::resolve_package_selection(&graph, &explain_selection)?;
let feature_resolution = compute_feature_resolution(
&graph,
&resolved_selection,
&request,
&std::collections::BTreeSet::new(),
)?;
let explanation = match &args.command {
ExplainCommand::Package { name } => {
let exp = cabin_explain::explain_package(
&graph,
&resolved_selection.packages,
name,
Some(&active_patches),
lockfile.as_ref(),
)?;
cabin_explain::Explanation::Package(exp)
}
ExplainCommand::Target { name } => {
let exp = cabin_explain::explain_target(&graph, &resolved_selection.packages, name)?;
cabin_explain::Explanation::Target(exp)
}
ExplainCommand::Source { name } => {
let exp = cabin_explain::explain_source(
&graph,
name,
Some(&active_patches),
lockfile.as_ref(),
&effective_config.source_replacements,
)?;
cabin_explain::Explanation::Source(exp)
}
ExplainCommand::Feature { query } => {
let pkg_name = query
.split_once('/')
.map_or_else(|| query.clone(), |(p, _)| p.to_owned());
let view = if let Some(idx) = graph.index_of(&pkg_name) {
let enabled = feature_resolution.for_package(idx).enabled_features.clone();
Some(cabin_explain::cabin_feature_per_package_view::FeatureView { enabled })
} else {
None
};
let exp = cabin_explain::explain_feature(&graph, view.as_ref(), query)?;
cabin_explain::Explanation::Feature(exp)
}
ExplainCommand::BuildConfig { name } => {
let manifest_profiles = workspace_profile_definitions(&graph);
let profile_selection =
profile_selection_for_metadata(args.profile.as_deref(), &effective_config)?;
let profile = cabin_core::resolve_profile(&profile_selection, &manifest_profiles)?;
let host_platform = cabin_core::TargetPlatform::current();
let toolchain_selection = toolchain_selection_from_args(&args.toolchain)?;
let toolchain = crate::cli::resolve_toolchain_layered(
&graph,
&toolchain_selection,
&effective_config,
&host_platform,
)?;
let detection_report = match cabin_toolchain::detect_toolchain(
&toolchain,
&cabin_toolchain::ProcessRunner,
) {
Ok(report) => Some(report),
Err(err) => {
reporter.warning(format_args!("toolchain detection failed: {err}"));
None
}
};
let manifest_compiler_wrapper = workspace_compiler_wrapper_settings(&graph);
let cli_compiler_wrapper = compiler_wrapper_override_from_args(&args.toolchain)?;
let dev_for: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
let prep = crate::cli::build_prep::resolve_build_prep(
crate::cli::build_prep::BuildConfigInputs {
graph: &graph,
host_platform: &host_platform,
toolchain: &toolchain,
detection: detection_report.as_ref(),
cli_compiler_wrapper,
manifest_compiler_wrapper: manifest_compiler_wrapper.as_ref(),
effective_config: &effective_config,
profile: &profile,
dev_for: &dev_for,
feature_resolution: &feature_resolution,
reporter,
},
)?;
let configurations = resolve_build_configurations(
&graph,
&request,
&resolved_selection.packages,
&profile,
&prep.toolchain_summary,
&prep.build_flags,
)?;
let config = cabin_explain::explain_build_config(&configurations, &graph, name)?;
return render_build_config(args.format, name, config);
}
};
match args.format {
ResolveFormat::Human => {
let rendered = cabin_explain::render_explanation_human(&explanation);
print!("{rendered}");
}
ResolveFormat::Json => {
let value = cabin_explain::render_explanation_json(&explanation);
crate::print_pretty_json(&value, "failed to serialize explanation as JSON")?;
}
}
Ok(())
}
fn render_build_config(
format: ResolveFormat,
name: &str,
config: &cabin_core::BuildConfiguration,
) -> Result<()> {
match format {
ResolveFormat::Json => {
let mut map = serde_json::Map::new();
map.insert(
"kind".to_owned(),
serde_json::Value::String("build-config".to_owned()),
);
map.insert(
"package".to_owned(),
serde_json::Value::String(name.to_owned()),
);
map.insert("configuration".to_owned(), config.as_json());
crate::print_pretty_json(
&serde_json::Value::Object(map),
"failed to serialize build-config explanation",
)?;
}
ResolveFormat::Human => {
println!("package: {name}");
println!("profile: {}", config.profile.name);
if let Some(c) = &config.language.c {
println!("c standard: {} ({})", c.standard, c.source.as_key());
}
if let Some(cxx) = &config.language.cxx {
println!("cxx standard: {} ({})", cxx.standard, cxx.source.as_key());
}
println!("fingerprint: {}", config.fingerprint);
}
}
Ok(())
}