use axoproject::errors::AxoprojectError;
use backtrace::Backtrace;
use camino::Utf8PathBuf;
use cargo_dist_schema::{target_lexicon::Triple, ArtifactId, TripleName};
use color_backtrace::BacktracePrinter;
use console::style;
use miette::{Diagnostic, SourceOffset, SourceSpan};
use thiserror::Error;
pub type DistResult<T> = std::result::Result<T, DistError>;
#[derive(Debug, Error, Diagnostic)]
pub enum DistError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
#[diagnostic(transparent)]
Asset(#[from] axoasset::AxoassetError),
#[error(transparent)]
#[diagnostic(transparent)]
Cmd(#[from] axoprocess::AxoprocessError),
#[error(transparent)]
#[diagnostic(transparent)]
Project(#[from] axoproject::errors::ProjectError),
#[error(transparent)]
FromUtf8Error(#[from] std::string::FromUtf8Error),
#[error(transparent)]
Goblin(#[from] goblin::error::Error),
#[error(transparent)]
FromPathBufError(#[from] camino::FromPathBufError),
#[error(transparent)]
DialoguerError(#[from] dialoguer::Error),
#[error(transparent)]
#[diagnostic(transparent)]
AxotagError(#[from] axotag::errors::TagError),
#[error(transparent)]
ParseIntError(#[from] std::num::ParseIntError),
#[error(transparent)]
TripleError(#[from] cargo_dist_schema::target_lexicon::ParseError),
#[error("Failed to render template")]
#[diagnostic(
help("this is a bug in dist, let us know and we'll fix it: https://github.com/axodotdev/cargo-dist/issues/new")
)]
Jinja {
#[source_code]
source: String,
#[label]
span: Option<miette::SourceSpan>,
#[source]
backtrace: JinjaErrorWithBacktrace,
},
#[error("WiX returned an error while building {msi}")]
Wix {
msi: String,
#[source]
details: wix::Error,
},
#[error("Couldn't generate main.wxs for {package}'s msi installer")]
WixInit {
package: String,
#[source]
details: wix::Error,
},
#[error("Malformed metadata.dist in\n{manifest_path}")]
#[diagnostic(help("you can find a reference for the configuration schema at https://axodotdev.github.io/cargo-dist/book/reference/config.html"))]
CargoTomlParse {
manifest_path: Utf8PathBuf,
#[source]
cause: serde_json::Error,
},
#[error("to update your dist config you must use the version your project is configured for")]
#[diagnostic(help(
"you're running {running_version} but the project is configured for {project_version}"
))]
NoUpdateVersion {
project_version: semver::Version,
running_version: semver::Version,
},
#[error("Github CI support requires your crates to agree on the URL of your repository")]
CantEnableGithubUrlInconsistent {
#[diagnostic_source]
inner: AxoprojectError,
},
#[error("Github CI support requires you to specify the URL of your repository")]
#[diagnostic(help(
r#"Set the repository = "https://github.com/..." key in these manifests: {manifest_list}"#
))]
CantEnableGithubNoUrl {
manifest_list: String,
},
#[error("GitHub CI support requires a GitHub repository")]
CantEnableGithubUrlNotGithub {
#[diagnostic_source]
inner: AxoprojectError,
},
#[error("The npm-scope field must be an all-lowercase value; the supplied value was {scope}")]
ScopeMustBeLowercase {
scope: String,
},
#[error(r#"install-path = "{path}" has an unknown format (it can either be "CARGO_HOME", "~/subdir/", or "$ENV_VAR/subdir/")"#)]
InstallPathInvalid {
path: String,
},
#[error(r#"install-path = "{path}" is missing a subdirectory (add a trailing slash if you want no subdirectory)"#)]
InstallPathEnvSlash {
path: String,
},
#[error(r#"install-path = "{path}" is missing a subdirectory (installing directly to home isn't allowed)"#)]
InstallPathHomeSubdir {
path: String,
},
#[error("precise-builds = false was set, but some packages have custom build features, making it impossible")]
#[diagnostic(help("these packages customized either features, no-default-features, or all-features:\n{packages:#?}"))]
PreciseImpossible {
packages: Vec<camino::Utf8PathBuf>,
},
#[error("different homebrew taps were set in your workspace, this is currently unsupported")]
#[diagnostic(help("these packages disagree:\n{packages:#?}"))]
MismatchedTaps {
packages: Vec<camino::Utf8PathBuf>,
},
#[error("different publisher settings were in your workspace, this is currently unuspported")]
#[diagnostic(help("these packages disagree:\n{packages:#?}"))]
MismatchedPublishers {
packages: Vec<camino::Utf8PathBuf>,
},
#[error("different publisher 'prereleases' settings were in your workspace, this is currently unsupported")]
MismatchedPrereleases,
#[error("This workspace doesn't have anything for dist to Release!")]
NothingToRelease {
#[help]
help: String,
},
#[error("There are too many unrelated apps in your workspace to coherently Announce!")]
TooManyUnrelatedApps {
#[help]
help: String,
},
#[error("{} has out of date contents and needs to be regenerated:\n{diff}", file.origin_path())]
#[diagnostic(help("run 'dist init' to update the file\n('allow-dirty' in Cargo.toml to ignore out of date contents)"))]
CheckFileMismatch {
file: axoasset::SourceFile,
diff: String,
},
#[error("'{generate_mode}' is marked as allow-dirty in your dist config, refusing to run")]
ContradictoryGenerateModes {
generate_mode: crate::config::GenerateMode,
},
#[error("{artifact_name} depends on multiple packages, which isn't yet supported")]
#[diagnostic(help("depends on {spec1} and {spec2}"))]
MultiPackage {
artifact_name: ArtifactId,
spec1: String,
spec2: String,
},
#[error("{artifact_name} has no binaries")]
#[diagnostic(help("This should be impossible, you did nothing wrong, please file an issue!"))]
NoPackage {
artifact_name: ArtifactId,
},
#[error("missing WiX GUIDs in {manifest_path}: {keys:?}")]
#[diagnostic(help("run 'dist init' to generate them"))]
MissingWixGuids {
manifest_path: Utf8PathBuf,
keys: &'static [&'static str],
},
#[error("{style} is not a recognized job value")]
#[diagnostic(help("Jobs that do not come with dist should be prefixed with ./"))]
UnrecognizedJobStyle {
style: String,
},
#[error("{style} is not a recognized release host")]
UnrecognizedHostingStyle {
style: String,
},
#[error("No GitHub hosting is defined!")]
#[diagnostic(help("Releases must have at least GitHub hosting for updates to be supported."))]
NoGitHubHosting {},
#[error("{style} is not a recognized ci provider")]
UnrecognizedCiStyle {
style: String,
},
#[error("{style} is not a recognized type of library")]
UnrecognizedLibraryStyle {
style: String,
},
#[error("unable to run linkage report for {target} on {host}")]
LinkageCheckInvalidOS {
host: String,
target: TripleName,
},
#[error("unable to run linkage report for this type of binary")]
LinkageCheckUnsupportedBinary,
#[error("Unable to parse environment variable as a key/value pair: {line}")]
#[diagnostic(help("This should be impossible, you did nothing wrong, please file an issue!"))]
EnvParseError {
line: String,
},
#[error("We failed to generate a source tarball for your project")]
#[diagnostic(help("This is probably not your fault, please file an issue!"))]
GitArchiveError {},
#[error("We failed to query information about the git submodule at\n{path}")]
#[diagnostic(help("Does a submodule exist at that path? Has it been fetched with `git submodule update --init`?"))]
GitSubmoduleCommitError {
path: String,
},
#[error("{tool}, required to run this task, is missing")]
#[diagnostic(help("Ensure {tool} is installed"))]
ToolMissing {
tool: String,
},
#[error("The following tools are required to run this task, but are missing:\n- {}", tools.join("\n- "))]
#[diagnostic(help("Please install the tools mentioned above and try again."))]
EnvToolsMissing {
tools: Vec<String>,
},
#[error(
"A build was requested for {target}, but the standalone updater isn't available for it."
)]
#[diagnostic(help("At the moment, we can only provide updater binaries for the core set of most common target triples. Please set `install-updater = false` in your config."))]
NoAxoupdaterForTarget {
target: String,
},
#[error("Failed to check the latest release of axoupdater")]
#[diagnostic(help(
"Is your internet connection working? If not, this may be a bug; please file an issue!"
))]
AxoupdaterReleaseCheckFailed {},
#[error("Failed to determine compression format")]
#[diagnostic(help("File extension of unrecognized file was {extension}"))]
UnrecognizedCompression {
extension: String,
},
#[error("failed to find bin {bin_name} for {pkg_name}")]
#[diagnostic(help("did the above build fail?"))]
MissingBinaries {
pkg_name: String,
bin_name: String,
},
#[error("`dist selfupdate` failed; the new version isn't in the place we expected")]
#[diagnostic(help("This is probably not your fault, please file an issue!"))]
UpdateFailed {},
#[error("`dist selfupdate` needs to be run in a project")]
#[diagnostic(help("If you just want to update dist and not your project, pass --skip-init"))]
UpdateNotInWorkspace {
#[diagnostic_source]
cause: axoproject::errors::ProjectError,
},
#[error("Incompatible install paths configured in Cargo.toml")]
#[diagnostic(help("The CargoHome `install-path` configuration can't be combined with other install path strategies."))]
IncompatibleInstallPathConfiguration,
#[error("You specified --artifacts, disabling host mode, but specified no targets to build!")]
#[diagnostic(help("try adding --target={host_target}"))]
CliMissingTargets {
host_target: TripleName,
},
#[error("please run 'dist init' before running any other commands!")]
NeedsInit,
#[error("You're running dist {running_version}, but 'cargo-dist-version = {config_version}' is set in your Cargo.toml")]
#[diagnostic(help("Rerun 'dist init' to update to this version."))]
MismatchedDistVersion {
config_version: String,
running_version: String,
},
#[error("Failed to get get toolchain version from 'cargo -vV'")]
FailedCargoVersion,
#[error("Failed to parse github repo: {pair}")]
#[diagnostic(help("should be 'owner/repo' format"))]
GithubRepoPairParse {
pair: String,
},
#[error("One or more unrecognized permissions levels were specified: {levels:?}")]
#[diagnostic(help("recognized values are: admin, write, read"))]
GithubUnknownPermission {
levels: Vec<String>,
},
#[error("Unrecognized target: {target}")]
#[diagnostic(help("The full list of supported targets can be found here: https://axodotdev.github.io/cargo-dist/book/reference/config.html#targets"))]
UnrecognizedTarget {
target: TripleName,
},
#[error("Installers were requested, but app contains no installable binaries")]
#[diagnostic(help(
"The only installable files are libraries, but `install-libraries` isn't enabled."
))]
EmptyInstaller {},
#[error("failed to load github-build-setup file")]
GithubBuildSetupNotFound {
#[diagnostic_source]
details: axoasset::AxoassetError,
},
#[error("github-build-setup file wasn't valid yaml")]
GithubBuildSetupParse {
#[diagnostic_source]
details: axoasset::AxoassetError,
},
#[error("github-build-setup file at {file_path} was invalid: {message}")]
#[diagnostic(help(
"For more details about writing build steps see: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsid"
))]
GithubBuildSetupNotValid {
file_path: Utf8PathBuf,
message: String,
},
#[error("The metadata.dist entry in this Cargo.toml isn't being used:\n{manifest_path}")]
#[diagnostic(help(
"You probably want to move them to the [dist] section in\n{dist_manifest_path}"
))]
UnusedMetadata {
manifest_path: Utf8PathBuf,
dist_manifest_path: Utf8PathBuf,
},
#[error("Your build-command's arguments need to be split up\n{manifest}\ncommand was: [\"{command}\"]")]
#[diagnostic(help("the command should be split [\"like\", \"--this\", \"--array=here\"]"))]
SusBuildCommand {
manifest: Utf8PathBuf,
command: String,
},
#[error("package.json was missing a \"dist\" script\n{manifest}")]
#[diagnostic(help(
"https://axodotdev.github.io/cargo-dist/book/quickstart/javascript.html#adding-a-dist-script"
))]
NoDistScript {
manifest: Utf8PathBuf,
},
#[error("Cross-compilation from {host} to {target} is not supported")]
#[diagnostic(help("{details}"))]
UnsupportedCrossCompile {
host: Triple,
target: Triple,
details: String,
},
#[error("You're building a generic package but have a Cargo-only option enabled")]
#[diagnostic(help("Please disable the following from your configuration: {}", options.join(", ")))]
CargoOnlyBuildOptions {
options: Vec<String>,
},
#[error("dist package was missing a build-command\n{manifest}")]
#[diagnostic(help(
"https://axodotdev.github.io/cargo-dist/book/quickstart/everyone-else.html#setup"
))]
NoBuildCommand {
manifest: Utf8PathBuf,
},
#[error(
"cargo package was overridden with a build-command, which isn't supported yet\n{manifest}"
)]
UnexpectedBuildCommand {
manifest: Utf8PathBuf,
},
#[error("We failed to decode the certificate stored in the CODESIGN_CERTIFICATE environment variable.")]
#[diagnostic(help("Is the value of this environment variable valid base64?"))]
CertificateDecodeError {},
#[error("A Mac .pkg installer was requested, but the config is missing")]
#[diagnostic(help("Please ensure a dist.mac-pkg-config section is present in your config. For more details see: https://example.com"))]
MacPkgConfigMissing {},
#[error("No bundle identifier was specified")]
#[diagnostic(help("Please either enter a bundle identifier, or disable the Mac .pkg"))]
MacPkgBundleIdentifierMissing {},
#[error("Your project ({package_name}) uses axoupdater as a library, but the version specified ({your_version}) is older than the minimum supported version ({minimum}). (The dependency comes via {source_name} in the dependency tree.)")]
#[diagnostic(help(
"https://axodotdev.github.io/cargo-dist/book/installers/updater.html#minimum-supported-version-checking"
))]
AxoupdaterTooOld {
package_name: String,
source_name: String,
minimum: semver::Version,
your_version: semver::Version,
},
}
impl From<minijinja::Error> for DistError {
fn from(details: minijinja::Error) -> Self {
let source: String = details.template_source().unwrap_or_default().to_owned();
let span = details.range().map(|r| r.into()).or_else(|| {
details.line().map(|line| {
let start = SourceOffset::from_location(&source, line, 0);
let end = SourceOffset::from_location(&source, line + 1, 0);
let len = (end.offset() - start.offset()).wrapping_sub(1);
SourceSpan::from((start, len))
})
});
DistError::Jinja {
source,
span,
backtrace: JinjaErrorWithBacktrace::new(details),
}
}
}
#[derive(Debug)]
pub struct JinjaErrorWithBacktrace {
pub error: minijinja::Error,
backtrace: Backtrace,
}
impl JinjaErrorWithBacktrace {
fn new(error: minijinja::Error) -> Self {
Self {
error,
backtrace: Backtrace::new_unresolved(),
}
}
}
impl std::error::Error for JinjaErrorWithBacktrace {}
impl std::fmt::Display for JinjaErrorWithBacktrace {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut bt = self.backtrace.clone();
bt.resolve();
let backtrace = BacktracePrinter::new()
.add_frame_filter(Box::new(|frames| {
if let Some(real_main_idx) = frames.iter().position(|f| {
f.name
.as_ref()
.map(|n| n.contains("real_main"))
.unwrap_or(false)
}) {
frames.splice(real_main_idx + 1.., vec![]);
}
}))
.format_trace_to_string(&bt)
.unwrap();
write!(
f,
"Backtrace:\n{}\n{}",
style(&backtrace).dim(),
style(&self.error).red()
)
}
}