1use axoproject::errors::AxoprojectError;
10use backtrace::Backtrace;
11use camino::Utf8PathBuf;
12use cargo_dist_schema::{target_lexicon::Triple, ArtifactId, TripleName};
13use color_backtrace::BacktracePrinter;
14use console::style;
15use miette::{Diagnostic, SourceOffset, SourceSpan};
16use thiserror::Error;
17
18pub type DistResult<T> = std::result::Result<T, DistError>;
20
21#[derive(Debug, Error, Diagnostic)]
23pub enum DistError {
24 #[error(transparent)]
26 Io(#[from] std::io::Error),
27
28 #[error(transparent)]
30 #[diagnostic(transparent)]
31 Asset(#[from] axoasset::AxoassetError),
32
33 #[error(transparent)]
35 #[diagnostic(transparent)]
36 Cmd(#[from] axoprocess::AxoprocessError),
37
38 #[error(transparent)]
40 #[diagnostic(transparent)]
41 Project(#[from] axoproject::errors::ProjectError),
42
43 #[error(transparent)]
45 FromUtf8Error(#[from] std::string::FromUtf8Error),
46
47 #[error(transparent)]
49 Goblin(#[from] goblin::error::Error),
50
51 #[error(transparent)]
53 FromPathBufError(#[from] camino::FromPathBufError),
54
55 #[error(transparent)]
57 DialoguerError(#[from] dialoguer::Error),
58
59 #[error(transparent)]
61 #[diagnostic(transparent)]
62 AxotagError(#[from] axotag::errors::TagError),
63
64 #[error(transparent)]
66 ParseIntError(#[from] std::num::ParseIntError),
67
68 #[error(transparent)]
70 TripleError(#[from] cargo_dist_schema::target_lexicon::ParseError),
71
72 #[error("Failed to render template")]
74 #[diagnostic(
75 help("this is a bug in dist, let us know and we'll fix it: https://github.com/axodotdev/cargo-dist/issues/new")
76 )]
77 Jinja {
78 #[source_code]
80 source: String,
81 #[label]
83 span: Option<miette::SourceSpan>,
84 #[source]
86 backtrace: JinjaErrorWithBacktrace,
87 },
88
89 #[error("WiX returned an error while building {msi}")]
91 Wix {
92 msi: String,
94 #[source]
96 details: wix::Error,
97 },
98
99 #[error("Couldn't generate main.wxs for {package}'s msi installer")]
101 WixInit {
102 package: String,
104 #[source]
106 details: wix::Error,
107 },
108
109 #[error("Malformed metadata.dist in\n{manifest_path}")]
111 #[diagnostic(help("you can find a reference for the configuration schema at https://axodotdev.github.io/cargo-dist/book/reference/config.html"))]
112 CargoTomlParse {
113 manifest_path: Utf8PathBuf,
115 #[source]
117 cause: serde_json::Error,
118 },
119
120 #[error("to update your dist config you must use the version your project is configured for")]
122 #[diagnostic(help(
123 "you're running {running_version} but the project is configured for {project_version}"
124 ))]
125 NoUpdateVersion {
126 project_version: semver::Version,
128 running_version: semver::Version,
130 },
131
132 #[error("Github CI support requires your crates to agree on the URL of your repository")]
134 CantEnableGithubUrlInconsistent {
135 #[diagnostic_source]
137 inner: AxoprojectError,
138 },
139
140 #[error("Github CI support requires you to specify the URL of your repository")]
142 #[diagnostic(help(
143 r#"Set the repository = "https://github.com/..." key in these manifests: {manifest_list}"#
144 ))]
145 CantEnableGithubNoUrl {
146 manifest_list: String,
148 },
149
150 #[error("GitHub CI support requires a GitHub repository")]
152 CantEnableGithubUrlNotGithub {
153 #[diagnostic_source]
155 inner: AxoprojectError,
156 },
157
158 #[error("The npm-scope field must be an all-lowercase value; the supplied value was {scope}")]
160 ScopeMustBeLowercase {
161 scope: String,
163 },
164
165 #[error(r#"install-path = "{path}" has an unknown format (it can either be "CARGO_HOME", "~/subdir/", or "$ENV_VAR/subdir/")"#)]
170 InstallPathInvalid {
171 path: String,
173 },
174
175 #[error(r#"install-path = "{path}" is missing a subdirectory (add a trailing slash if you want no subdirectory)"#)]
180 InstallPathEnvSlash {
181 path: String,
183 },
184
185 #[error(r#"install-path = "{path}" is missing a subdirectory (installing directly to home isn't allowed)"#)]
190 InstallPathHomeSubdir {
191 path: String,
193 },
194
195 #[error("precise-builds = false was set, but some packages have custom build features, making it impossible")]
197 #[diagnostic(help("these packages customized either features, no-default-features, or all-features:\n{packages:#?}"))]
198 PreciseImpossible {
199 packages: Vec<camino::Utf8PathBuf>,
201 },
202
203 #[error("different homebrew taps were set in your workspace, this is currently unsupported")]
205 #[diagnostic(help("these packages disagree:\n{packages:#?}"))]
206 MismatchedTaps {
207 packages: Vec<camino::Utf8PathBuf>,
209 },
210
211 #[error("different publisher settings were in your workspace, this is currently unuspported")]
213 #[diagnostic(help("these packages disagree:\n{packages:#?}"))]
214 MismatchedPublishers {
215 packages: Vec<camino::Utf8PathBuf>,
217 },
218
219 #[error("different publisher 'prereleases' settings were in your workspace, this is currently unsupported")]
221 MismatchedPrereleases,
222
223 #[error("This workspace doesn't have anything for dist to Release!")]
225 NothingToRelease {
226 #[help]
228 help: String,
229 },
230
231 #[error("There are too many unrelated apps in your workspace to coherently Announce!")]
233 TooManyUnrelatedApps {
234 #[help]
236 help: String,
237 },
238
239 #[error("{} has out of date contents and needs to be regenerated:\n{diff}", file.origin_path())]
241 #[diagnostic(help("run 'dist init' to update the file\n('allow-dirty' in Cargo.toml to ignore out of date contents)"))]
242 CheckFileMismatch {
243 file: axoasset::SourceFile,
245 diff: String,
247 },
248
249 #[error("'{generate_mode}' is marked as allow-dirty in your dist config, refusing to run")]
252 ContradictoryGenerateModes {
253 generate_mode: crate::config::GenerateMode,
255 },
256
257 #[error("{artifact_name} depends on multiple packages, which isn't yet supported")]
259 #[diagnostic(help("depends on {spec1} and {spec2}"))]
260 MultiPackage {
261 artifact_name: ArtifactId,
263 spec1: String,
265 spec2: String,
267 },
268
269 #[error("{artifact_name} has no binaries")]
271 #[diagnostic(help("This should be impossible, you did nothing wrong, please file an issue!"))]
272 NoPackage {
273 artifact_name: ArtifactId,
275 },
276
277 #[error("missing WiX GUIDs in {manifest_path}: {keys:?}")]
279 #[diagnostic(help("run 'dist init' to generate them"))]
280 MissingWixGuids {
281 manifest_path: Utf8PathBuf,
283 keys: &'static [&'static str],
285 },
286
287 #[error("{style} is not a recognized job value")]
289 #[diagnostic(help("Jobs that do not come with dist should be prefixed with ./"))]
290 UnrecognizedJobStyle {
291 style: String,
293 },
294
295 #[error("{style} is not a recognized release host")]
297 UnrecognizedHostingStyle {
298 style: String,
300 },
301
302 #[error("No GitHub hosting is defined!")]
304 #[diagnostic(help("Releases must have at least GitHub hosting for updates to be supported."))]
305 NoGitHubHosting {},
306
307 #[error("{style} is not a recognized ci provider")]
309 UnrecognizedCiStyle {
310 style: String,
312 },
313
314 #[error("{style} is not a recognized type of library")]
316 UnrecognizedLibraryStyle {
317 style: String,
319 },
320
321 #[error("unable to run linkage report for {target} on {host}")]
323 LinkageCheckInvalidOS {
324 host: String,
326 target: TripleName,
328 },
329
330 #[error("unable to run linkage report for this type of binary")]
332 LinkageCheckUnsupportedBinary,
333
334 #[error("Unable to parse environment variable as a key/value pair: {line}")]
337 #[diagnostic(help("This should be impossible, you did nothing wrong, please file an issue!"))]
338 EnvParseError {
339 line: String,
341 },
342
343 #[error("We failed to generate a source tarball for your project")]
345 #[diagnostic(help("This is probably not your fault, please file an issue!"))]
346 GitArchiveError {},
347
348 #[error("We failed to query information about the git submodule at\n{path}")]
350 #[diagnostic(help("Does a submodule exist at that path? Has it been fetched with `git submodule update --init`?"))]
351 GitSubmoduleCommitError {
352 path: String,
354 },
355
356 #[error("{tool}, required to run this task, is missing")]
358 #[diagnostic(help("Ensure {tool} is installed"))]
359 ToolMissing {
360 tool: String,
362 },
363
364 #[error("The following tools are required to run this task, but are missing:\n- {}", tools.join("\n- "))]
366 #[diagnostic(help("Please install the tools mentioned above and try again."))]
367 EnvToolsMissing {
368 tools: Vec<String>,
370 },
371
372 #[error(
374 "A build was requested for {target}, but the standalone updater isn't available for it."
375 )]
376 #[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."))]
377 NoAxoupdaterForTarget {
378 target: String,
380 },
381
382 #[error("Failed to check the latest release of axoupdater")]
384 #[diagnostic(help(
385 "Is your internet connection working? If not, this may be a bug; please file an issue!"
386 ))]
387 AxoupdaterReleaseCheckFailed {},
388
389 #[error("Failed to determine compression format")]
391 #[diagnostic(help("File extension of unrecognized file was {extension}"))]
392 UnrecognizedCompression {
393 extension: String,
395 },
396
397 #[error("failed to find bin {bin_name} for {pkg_name}")]
399 #[diagnostic(help("did the above build fail?"))]
400 MissingBinaries {
401 pkg_name: String,
403 bin_name: String,
405 },
406
407 #[error("`dist selfupdate` failed; the new version isn't in the place we expected")]
409 #[diagnostic(help("This is probably not your fault, please file an issue!"))]
410 UpdateFailed {},
411
412 #[error("`dist selfupdate` needs to be run in a project")]
414 #[diagnostic(help("If you just want to update dist and not your project, pass --skip-init"))]
415 UpdateNotInWorkspace {
416 #[diagnostic_source]
418 cause: axoproject::errors::ProjectError,
419 },
420
421 #[error("Incompatible install paths configured in Cargo.toml")]
423 #[diagnostic(help("The CargoHome `install-path` configuration can't be combined with other install path strategies."))]
424 IncompatibleInstallPathConfiguration,
425
426 #[error("You specified --artifacts, disabling host mode, but specified no targets to build!")]
428 #[diagnostic(help("try adding --target={host_target}"))]
429 CliMissingTargets {
430 host_target: TripleName,
432 },
433
434 #[error("please run 'dist init' before running any other commands!")]
436 NeedsInit,
437
438 #[error("You're running dist {running_version}, but 'cargo-dist-version = {config_version}' is set in your Cargo.toml")]
440 #[diagnostic(help("Rerun 'dist init' to update to this version."))]
441 MismatchedDistVersion {
442 config_version: String,
444 running_version: String,
446 },
447
448 #[error("Failed to get get toolchain version from 'cargo -vV'")]
450 FailedCargoVersion,
451
452 #[error("Failed to parse github repo: {pair}")]
454 #[diagnostic(help("should be 'owner/repo' format"))]
455 GithubRepoPairParse {
456 pair: String,
458 },
459
460 #[error("One or more unrecognized permissions levels were specified: {levels:?}")]
462 #[diagnostic(help("recognized values are: admin, write, read"))]
463 GithubUnknownPermission {
464 levels: Vec<String>,
466 },
467
468 #[error("Unrecognized target: {target}")]
470 #[diagnostic(help("The full list of supported targets can be found here: https://axodotdev.github.io/cargo-dist/book/reference/config.html#targets"))]
471 UnrecognizedTarget {
472 target: TripleName,
474 },
475
476 #[error("Installers were requested, but app contains no installable binaries")]
478 #[diagnostic(help(
479 "The only installable files are libraries, but `install-libraries` isn't enabled."
480 ))]
481 EmptyInstaller {},
482
483 #[error("failed to load github-build-setup file")]
485 GithubBuildSetupNotFound {
486 #[diagnostic_source]
488 details: axoasset::AxoassetError,
489 },
490
491 #[error("github-build-setup file wasn't valid yaml")]
493 GithubBuildSetupParse {
494 #[diagnostic_source]
496 details: axoasset::AxoassetError,
497 },
498
499 #[error("github-build-setup file at {file_path} was invalid: {message}")]
501 #[diagnostic(help(
502 "For more details about writing build steps see: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsid"
503 ))]
504 GithubBuildSetupNotValid {
505 file_path: Utf8PathBuf,
507 message: String,
509 },
510
511 #[error("The metadata.dist entry in this Cargo.toml isn't being used:\n{manifest_path}")]
513 #[diagnostic(help(
514 "You probably want to move them to the [dist] section in\n{dist_manifest_path}"
515 ))]
516 UnusedMetadata {
517 manifest_path: Utf8PathBuf,
519 dist_manifest_path: Utf8PathBuf,
521 },
522
523 #[error("Your build-command's arguments need to be split up\n{manifest}\ncommand was: [\"{command}\"]")]
525 #[diagnostic(help("the command should be split [\"like\", \"--this\", \"--array=here\"]"))]
526 SusBuildCommand {
527 manifest: Utf8PathBuf,
529 command: String,
531 },
532
533 #[error("package.json was missing a \"dist\" script\n{manifest}")]
535 #[diagnostic(help(
536 "https://axodotdev.github.io/cargo-dist/book/quickstart/javascript.html#adding-a-dist-script"
537 ))]
538 NoDistScript {
539 manifest: Utf8PathBuf,
541 },
542
543 #[error("Cross-compilation from {host} to {target} is not supported")]
545 #[diagnostic(help("{details}"))]
546 UnsupportedCrossCompile {
547 host: Triple,
549 target: Triple,
551 details: String,
553 },
554
555 #[error(
557 "Cross-compilation builds from {host} to {target} cannot be used with cargo-auditable"
558 )]
559 #[diagnostic(help("set cargo-auditable to false or don't do cross-compilation"))]
560 CannotDoCargoAuditableAndCrossCompile {
561 host: Triple,
563 target: Triple,
565 },
566
567 #[error("You're building a generic package but have a Cargo-only option enabled")]
569 #[diagnostic(help("Please disable the following from your configuration: {}", options.join(", ")))]
570 CargoOnlyBuildOptions {
571 options: Vec<String>,
573 },
574
575 #[error("dist package was missing a build-command\n{manifest}")]
577 #[diagnostic(help(
578 "https://axodotdev.github.io/cargo-dist/book/quickstart/everyone-else.html#setup"
579 ))]
580 NoBuildCommand {
581 manifest: Utf8PathBuf,
583 },
584
585 #[error(
587 "cargo package was overridden with a build-command, which isn't supported yet\n{manifest}"
588 )]
589 UnexpectedBuildCommand {
590 manifest: Utf8PathBuf,
592 },
593
594 #[error("We failed to decode the certificate stored in the CODESIGN_CERTIFICATE environment variable.")]
596 #[diagnostic(help("Is the value of this environment variable valid base64?"))]
597 CertificateDecodeError {},
598
599 #[error("A Mac .pkg installer was requested, but the config is missing")]
601 #[diagnostic(help("Please ensure a dist.mac-pkg-config section is present in your config. For more details see: https://example.com"))]
602 MacPkgConfigMissing {},
603
604 #[error("No bundle identifier was specified")]
606 #[diagnostic(help("Please either enter a bundle identifier, or disable the Mac .pkg"))]
607 MacPkgBundleIdentifierMissing {},
608
609 #[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.)")]
611 #[diagnostic(help(
612 "https://axodotdev.github.io/cargo-dist/book/installers/updater.html#minimum-supported-version-checking"
613 ))]
614 AxoupdaterTooOld {
615 package_name: String,
617 source_name: String,
619 minimum: semver::Version,
621 your_version: semver::Version,
623 },
624}
625
626impl From<minijinja::Error> for DistError {
627 fn from(details: minijinja::Error) -> Self {
628 let source: String = details.template_source().unwrap_or_default().to_owned();
629 let span = details.range().map(|r| r.into()).or_else(|| {
630 details.line().map(|line| {
631 let start = SourceOffset::from_location(&source, line, 0);
633 let end = SourceOffset::from_location(&source, line + 1, 0);
634 let len = (end.offset() - start.offset()).wrapping_sub(1);
635 SourceSpan::from((start, len))
636 })
637 });
638
639 DistError::Jinja {
640 source,
641 span,
642 backtrace: JinjaErrorWithBacktrace::new(details),
643 }
644 }
645}
646#[derive(Debug)]
649pub struct JinjaErrorWithBacktrace {
650 pub error: minijinja::Error,
652 backtrace: Backtrace,
654}
655
656impl JinjaErrorWithBacktrace {
657 fn new(error: minijinja::Error) -> Self {
658 Self {
659 error,
660 backtrace: Backtrace::new_unresolved(),
661 }
662 }
663}
664
665impl std::error::Error for JinjaErrorWithBacktrace {}
666
667impl std::fmt::Display for JinjaErrorWithBacktrace {
668 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
669 let mut bt = self.backtrace.clone();
670 bt.resolve();
671 let backtrace = BacktracePrinter::new()
672 .add_frame_filter(Box::new(|frames| {
673 if let Some(real_main_idx) = frames.iter().position(|f| {
675 f.name
676 .as_ref()
677 .map(|n| n.contains("real_main"))
678 .unwrap_or(false)
679 }) {
680 frames.splice(real_main_idx + 1.., vec![]);
681 }
682 }))
683 .format_trace_to_string(&bt)
684 .unwrap();
685 write!(
686 f,
687 "Backtrace:\n{}\n{}",
688 style(&backtrace).dim(),
689 style(&self.error).red()
690 )
691 }
692}