use crate::{output::OutputContext, ExpectedError, OutputWriter, Result};
use camino::{Utf8Path, Utf8PathBuf};
use clap::{Args, ValueEnum};
use guppy::graph::PackageGraph;
use nextest_runner::{
errors::PathMapperConstructKind,
redact::Redactor,
reuse_build::{
ArchiveFormat, ArchiveReporter, ExtractDestination, MetadataKind, MetadataWithRemap,
PathMapper, ReuseBuildInfo, ReusedBinaryList, ReusedCargoMetadata,
},
};
use std::io::Write;
use tracing::warn;
#[derive(Debug, Default, Args)]
#[command(
next_help_heading = "Reuse build options",
group = clap::ArgGroup::new("cargo-metadata-sources"),
group = clap::ArgGroup::new("binaries-metadata-sources"),
group = clap::ArgGroup::new("target-dir-remap-sources"),
)]
pub(crate) struct ReuseBuildOpts {
#[arg(
long,
groups = &["cargo-metadata-sources", "binaries-metadata-sources", "target-dir-remap-sources"],
conflicts_with_all = &["cargo-opts", "binaries_metadata", "cargo_metadata"],
value_name = "PATH",
)]
pub(crate) archive_file: Option<Utf8PathBuf>,
#[arg(
long,
value_enum,
default_value_t,
requires = "archive_file",
value_name = "FORMAT"
)]
pub(crate) archive_format: ArchiveFormatOpt,
#[arg(
long,
conflicts_with = "cargo-opts",
requires = "archive_file",
value_name = "DIR"
)]
pub(crate) extract_to: Option<Utf8PathBuf>,
#[arg(long, conflicts_with = "cargo-opts", requires_all = &["archive_file", "extract_to"])]
pub(crate) extract_overwrite: bool,
#[arg(long, conflicts_with_all = &["cargo-opts", "extract_to"], requires = "archive_file")]
pub(crate) persist_extract_tempdir: bool,
#[arg(
long,
group = "cargo-metadata-sources",
conflicts_with = "manifest_path",
value_name = "PATH"
)]
pub(crate) cargo_metadata: Option<Utf8PathBuf>,
#[arg(long, requires = "cargo-metadata-sources", value_name = "PATH")]
pub(crate) workspace_remap: Option<Utf8PathBuf>,
#[arg(
long,
group = "binaries-metadata-sources",
conflicts_with = "cargo-opts",
value_name = "PATH"
)]
pub(crate) binaries_metadata: Option<Utf8PathBuf>,
#[arg(
long,
group = "target-dir-remap-sources",
requires = "binaries_metadata",
value_name = "PATH"
)]
pub(crate) target_dir_remap: Option<Utf8PathBuf>,
}
impl ReuseBuildOpts {
const EXPERIMENTAL_ENV: &'static str = "NEXTEST_EXPERIMENTAL_REUSE_BUILD";
pub(crate) fn check_experimental(&self, _output: OutputContext) {
if std::env::var(Self::EXPERIMENTAL_ENV).is_ok() {
warn!("build reuse is no longer experimental: NEXTEST_EXPERIMENTAL_REUSE_BUILD does not need to be set");
}
}
pub(crate) fn process(
&self,
output: OutputContext,
output_writer: &mut OutputWriter,
) -> Result<ReuseBuildInfo> {
if let Some(archive_file) = &self.archive_file {
let format = self.archive_format.to_archive_format(archive_file)?;
let dest = match &self.extract_to {
Some(dir) => ExtractDestination::Destination {
dir: dir.clone(),
overwrite: self.extract_overwrite,
},
None => ExtractDestination::TempDir {
persist: self.persist_extract_tempdir,
},
};
let redactor = Redactor::noop();
let mut reporter = ArchiveReporter::new(output.verbose, redactor);
if output.color.should_colorize(supports_color::Stream::Stderr) {
reporter.colorize();
}
let mut writer = output_writer.stderr_writer();
return ReuseBuildInfo::extract_archive(
archive_file,
format,
dest,
|event| {
reporter.report_event(event, &mut writer)?;
writer.flush()
},
self.workspace_remap.as_deref(),
)
.map_err(|err| ExpectedError::ArchiveExtractError {
archive_file: archive_file.clone(),
err: Box::new(err),
});
}
let cargo_metadata = self
.cargo_metadata
.as_ref()
.map(|path| {
Ok(MetadataWithRemap {
metadata: ReusedCargoMetadata::materialize(path)?,
remap: self.workspace_remap.clone(),
})
})
.transpose()
.map_err(|err| ExpectedError::metadata_materialize_error("cargo-metadata", err))?;
let binaries_metadata = self
.binaries_metadata
.as_ref()
.map(|path| {
Ok(MetadataWithRemap {
metadata: ReusedBinaryList::materialize(path)?,
remap: self.target_dir_remap.clone(),
})
})
.transpose()
.map_err(|err| ExpectedError::metadata_materialize_error("binaries-metadata", err))?;
Ok(ReuseBuildInfo::new(cargo_metadata, binaries_metadata))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
pub(crate) enum ArchiveFormatOpt {
Auto,
#[clap(alias = "tar-zstd")]
TarZst,
}
impl ArchiveFormatOpt {
pub(crate) fn to_archive_format(self, archive_file: &Utf8Path) -> Result<ArchiveFormat> {
match self {
Self::TarZst => Ok(ArchiveFormat::TarZst),
Self::Auto => ArchiveFormat::autodetect(archive_file).map_err(|err| {
ExpectedError::UnknownArchiveFormat {
archive_file: archive_file.to_owned(),
err,
}
}),
}
}
}
impl Default for ArchiveFormatOpt {
fn default() -> Self {
Self::Auto
}
}
pub(crate) fn make_path_mapper(
info: &ReuseBuildInfo,
graph: &PackageGraph,
orig_target_dir: &Utf8Path,
) -> Result<PathMapper> {
PathMapper::new(
graph.workspace().root(),
info.workspace_remap(),
orig_target_dir,
info.target_dir_remap(),
info.libdir_mapper.clone(),
)
.map_err(|err| {
let arg_name = match err.kind() {
PathMapperConstructKind::WorkspaceRoot => "workspace-remap",
PathMapperConstructKind::TargetDir => "target-dir-remap",
};
ExpectedError::PathMapperConstructError { arg_name, err }
})
}