cabinpkg 0.17.0

A package manager and build system for C/C++
Documentation
use super::{
    Context, PackageArgs, PathBuf, PublishArgs, Reporter, ResolveFormat, Result, absolutise, bail,
    resolve_invocation_manifest, select_single_package_manifest,
};

pub(super) fn package(args: &PackageArgs, _reporter: Reporter) -> Result<()> {
    let manifest_path = resolve_invocation_manifest(args.manifest_path.as_deref())?;
    let (manifest_path, resolved_project, workspace_dep_requirements) =
        select_single_package_manifest(&manifest_path, &args.workspace_selection, "package")?
            .into_parts();
    let output_dir = absolutise(&args.output_dir)
        .with_context(|| format!("failed to resolve {}", args.output_dir.display()))?;
    let artifact = cabin_package::package_with_project(
        cabin_package::PackageRequest {
            manifest_path: &manifest_path,
            output_dir: &output_dir,
        },
        resolved_project,
        &workspace_dep_requirements,
    )?;
    emit_package_output(&artifact, args.format)?;
    Ok(())
}

pub(super) fn publish(args: &PublishArgs, reporter: Reporter) -> Result<()> {
    // `--output-dir` is for the staging-only `dist/` flow; combining
    // it with `--registry-dir` is meaningless and almost always
    // means the user picked the wrong flag, so refuse loudly.
    if args.output_dir.is_some() && args.registry_dir.is_some() {
        bail!("--output-dir is not compatible with --registry-dir; pick one");
    }

    let manifest_path = resolve_invocation_manifest(args.manifest_path.as_deref())?;
    let (manifest_path, resolved_project, workspace_dep_requirements) =
        select_single_package_manifest(&manifest_path, &args.workspace_selection, "publish")?
            .into_parts();

    match (args.registry_dir.as_deref(), args.dry_run) {
        (Some(registry_dir), true) => {
            let registry_dir = absolutise(registry_dir)
                .with_context(|| format!("failed to resolve {}", registry_dir.display()))?;
            let report = cabin_publish::dry_run_against_file_registry(
                cabin_publish::RegistryPublishWorkflow {
                    manifest_path: &manifest_path,
                    registry_dir: &registry_dir,
                    resolved_project,
                    workspace_dep_requirements,
                },
            )?;
            emit_registry_publish_output(&report, args.format, reporter)?;
        }
        (Some(registry_dir), false) => {
            let registry_dir = absolutise(registry_dir)
                .with_context(|| format!("failed to resolve {}", registry_dir.display()))?;
            let report =
                cabin_publish::publish_to_file_registry(cabin_publish::RegistryPublishWorkflow {
                    manifest_path: &manifest_path,
                    registry_dir: &registry_dir,
                    resolved_project,
                    workspace_dep_requirements,
                })?;
            emit_registry_publish_output(&report, args.format, reporter)?;
        }
        (None, true) => {
            let output_dir = args
                .output_dir
                .clone()
                .unwrap_or_else(|| PathBuf::from("dist"));
            let output_dir = absolutise(&output_dir)
                .with_context(|| format!("failed to resolve {}", output_dir.display()))?;
            let report = cabin_publish::dry_run(cabin_publish::DryRunRequest {
                manifest_path: &manifest_path,
                output_dir: &output_dir,
                resolved_project,
                workspace_dep_requirements,
            })?;
            emit_dry_run_output(&report, args.format, reporter)?;
        }
        (None, false) => {
            return Err(cabin_publish::PublishError::DryRunRequired.into());
        }
    }
    Ok(())
}

pub(super) fn emit_package_output(
    artifact: &cabin_package::PackagedArtifact,
    format: ResolveFormat,
) -> Result<()> {
    match format {
        ResolveFormat::Human => {
            print_package_human(artifact);
            Ok(())
        }
        ResolveFormat::Json => print_package_json(artifact),
    }
}

pub(super) fn print_package_human(artifact: &cabin_package::PackagedArtifact) {
    println!("Packaged {} {}", artifact.name.as_str(), artifact.version);
    println!("  archive: {}", artifact.archive_path.display());
    println!("  metadata: {}", artifact.metadata_path.display());
    println!("  checksum: {}", artifact.checksum);
}

pub(super) fn print_package_json(artifact: &cabin_package::PackagedArtifact) -> Result<()> {
    let value = serde_json::json!({
        "name": artifact.name.as_str(),
        "version": artifact.version.to_string(),
        "archive_path": artifact.archive_path,
        "metadata_path": artifact.metadata_path,
        "checksum": artifact.checksum,
    });
    crate::print_pretty_json(&value, "failed to serialize package output as JSON")
}

pub(super) fn emit_dry_run_output(
    report: &cabin_publish::DryRunReport,
    format: ResolveFormat,
    reporter: Reporter,
) -> Result<()> {
    match format {
        ResolveFormat::Human => {
            print_dry_run_human(report);
            print_lint_warnings(reporter, &report.warnings);
            Ok(())
        }
        ResolveFormat::Json => print_dry_run_json(report),
    }
}

pub(super) fn print_dry_run_human(report: &cabin_publish::DryRunReport) {
    println!(
        "Publish dry-run for {} {}",
        report.name.as_str(),
        report.version
    );
    println!();
    println!("Generated:");
    println!("  archive: {}", report.archive_path.display());
    println!("  metadata: {}", report.metadata_path.display());
    println!("  checksum: {}", report.checksum);
    println!();
    println!("This was a dry run. No registry was modified.");
    if report.standards_check_skipped {
        println!("Patch-release requirement check (PL3) skipped: no registry to compare against.");
    }
}

pub(super) fn print_dry_run_json(report: &cabin_publish::DryRunReport) -> Result<()> {
    let value = serde_json::json!({
        "dry_run": true,
        "name": report.name.as_str(),
        "version": report.version.to_string(),
        "archive_path": report.archive_path,
        "metadata_path": report.metadata_path,
        "checksum": report.checksum,
        "registry_modified": report.registry_modified,
        "warnings": report.warnings,
        "standards_check_skipped": report.standards_check_skipped,
    });
    crate::print_pretty_json(&value, "failed to serialize publish dry-run output as JSON")
}

pub(super) fn emit_registry_publish_output(
    report: &cabin_publish::RegistryPublishReport,
    format: ResolveFormat,
    reporter: Reporter,
) -> Result<()> {
    match format {
        ResolveFormat::Human => {
            print_registry_publish_human(report);
            print_lint_warnings(reporter, &report.warnings);
            Ok(())
        }
        ResolveFormat::Json => print_registry_publish_json(report),
    }
}

/// Print non-rejecting standard-compatibility lint warnings (PL2, PL3)
/// through the reporter's stderr warning channel, one per line, so
/// human-mode stdout stays the report and CI logs still capture the
/// advice.
fn print_lint_warnings(reporter: Reporter, warnings: &[String]) {
    for message in warnings {
        reporter.warning(format_args!("{message}"));
    }
}

pub(super) fn print_registry_publish_human(report: &cabin_publish::RegistryPublishReport) {
    if report.dry_run {
        println!(
            "Publish dry-run for {} {} against file registry",
            report.name.as_str(),
            report.version
        );
    } else {
        println!(
            "Published {} {} to file registry",
            report.name.as_str(),
            report.version
        );
    }
    println!("  registry: {}", report.registry_dir.display());
    println!("  package index: {}", report.package_index_path.display());
    println!("  artifact: {}", report.artifact_path.display());
    println!("  checksum: {}", report.checksum);
    if report.dry_run {
        println!();
        if report.registry_initialized {
            println!("Registry would be initialized at this path.");
        }
        println!("This was a dry run. No registry was modified.");
    } else if report.registry_initialized {
        println!();
        println!("Registry was initialized at this path.");
    }
}

pub(super) fn print_registry_publish_json(
    report: &cabin_publish::RegistryPublishReport,
) -> Result<()> {
    let value = serde_json::json!({
        "published": !report.dry_run,
        "dry_run": report.dry_run,
        "name": report.name.as_str(),
        "version": report.version.to_string(),
        "registry_dir": report.registry_dir,
        "package_index_path": report.package_index_path,
        "artifact_path": report.artifact_path,
        "checksum": report.checksum,
        "source_path": report.source_path,
        "registry_modified": report.registry_modified,
        "registry_initialized": report.registry_initialized,
        "warnings": report.warnings,
    });
    crate::print_pretty_json(&value, "failed to serialize publish output as JSON")
}