1use std::collections::BTreeMap;
4
5use axoasset::{toml_edit, SourceFile};
6use axoproject::local_repo::LocalRepo;
7use camino::{Utf8Path, Utf8PathBuf};
8use cargo_dist_schema::{
9 AptPackageName, ChecksumExtensionRef, ChocolateyPackageName, HomebrewPackageName,
10 PackageVersion, TripleName, TripleNameRef,
11};
12use serde::{Deserialize, Serialize};
13
14use crate::announce::TagSettings;
15use crate::SortedMap;
16use crate::{
17 errors::{DistError, DistResult},
18 METADATA_DIST,
19};
20
21pub mod v0;
22pub mod v0_to_v1;
23pub mod v1;
24
25pub use v0::{DistMetadata, GenericConfig};
26
27pub type GithubPermissionMap = SortedMap<String, GithubPermission>;
29
30#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Ord, Eq)]
34#[serde(rename_all = "kebab-case")]
35pub enum GithubPermission {
36 Read,
38 Write,
40 Admin,
42}
43
44#[derive(Debug, Clone)]
46pub struct Config {
47 pub tag_settings: TagSettings,
49 pub create_hosting: bool,
53 pub artifact_mode: ArtifactMode,
55 pub no_local_paths: bool,
57 pub allow_all_dirty: bool,
59 pub targets: Vec<TripleName>,
61 pub ci: Vec<CiStyle>,
63 pub installers: Vec<InstallerStyle>,
65 pub root_cmd: String,
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub enum ArtifactMode {
72 Local,
74 Global,
76 Host,
78 All,
80 Lies,
82}
83
84impl std::fmt::Display for ArtifactMode {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 let string = match self {
87 ArtifactMode::Local => "local",
88 ArtifactMode::Global => "global",
89 ArtifactMode::Host => "host",
90 ArtifactMode::All => "all",
91 ArtifactMode::Lies => "lies",
92 };
93 string.fmt(f)
94 }
95}
96
97#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
99#[serde(rename_all = "kebab-case")]
100pub enum CiStyle {
101 Github,
103}
104impl CiStyle {
105 pub(crate) fn native_hosting(&self) -> Option<HostingStyle> {
107 match self {
108 CiStyle::Github => Some(HostingStyle::Github),
109 }
110 }
111}
112
113impl std::fmt::Display for CiStyle {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 let string = match self {
116 CiStyle::Github => "github",
117 };
118 string.fmt(f)
119 }
120}
121
122impl std::str::FromStr for CiStyle {
123 type Err = DistError;
124 fn from_str(val: &str) -> DistResult<Self> {
125 let res = match val {
126 "github" => CiStyle::Github,
127 s => {
128 return Err(DistError::UnrecognizedCiStyle {
129 style: s.to_string(),
130 })
131 }
132 };
133 Ok(res)
134 }
135}
136
137#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
139pub enum LibraryStyle {
140 #[serde(rename = "cdylib")]
142 CDynamic,
143 #[serde(rename = "cstaticlib")]
145 CStatic,
146}
147
148impl std::fmt::Display for LibraryStyle {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 let string = match self {
151 Self::CDynamic => "cdylib",
152 Self::CStatic => "cstaticlib",
153 };
154 string.fmt(f)
155 }
156}
157
158impl std::str::FromStr for LibraryStyle {
159 type Err = DistError;
160 fn from_str(val: &str) -> DistResult<Self> {
161 let res = match val {
162 "cdylib" => Self::CDynamic,
163 "cstaticlib" => Self::CStatic,
164 s => {
165 return Err(DistError::UnrecognizedLibraryStyle {
166 style: s.to_string(),
167 })
168 }
169 };
170 Ok(res)
171 }
172}
173
174#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
176#[serde(rename_all = "kebab-case")]
177pub enum InstallerStyle {
178 Shell,
180 Powershell,
182 Npm,
184 Homebrew,
186 Msi,
188 Pkg,
190}
191
192impl std::fmt::Display for InstallerStyle {
193 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194 let string = match self {
195 InstallerStyle::Shell => "shell",
196 InstallerStyle::Powershell => "powershell",
197 InstallerStyle::Npm => "npm",
198 InstallerStyle::Homebrew => "homebrew",
199 InstallerStyle::Msi => "msi",
200 InstallerStyle::Pkg => "pkg",
201 };
202 string.fmt(f)
203 }
204}
205
206#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
208#[serde(rename_all = "kebab-case")]
209pub enum GithubReleasePhase {
210 #[default]
212 Auto,
213 Host,
215 Announce,
217}
218
219impl std::fmt::Display for GithubReleasePhase {
220 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221 let string = match self {
222 GithubReleasePhase::Auto => "auto",
223 GithubReleasePhase::Host => "host",
224 GithubReleasePhase::Announce => "announce",
225 };
226 string.fmt(f)
227 }
228}
229
230#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
232#[serde(rename_all = "kebab-case")]
233pub enum HostingStyle {
234 Github,
236 Axodotdev,
238}
239
240impl std::fmt::Display for HostingStyle {
241 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242 let string = match self {
243 HostingStyle::Github => "github",
244 HostingStyle::Axodotdev => "axodotdev",
245 };
246 string.fmt(f)
247 }
248}
249
250impl std::str::FromStr for HostingStyle {
251 type Err = DistError;
252 fn from_str(val: &str) -> DistResult<Self> {
253 let res = match val {
254 "github" => HostingStyle::Github,
255 "axodotdev" => HostingStyle::Axodotdev,
256 s => {
257 return Err(DistError::UnrecognizedHostingStyle {
258 style: s.to_string(),
259 })
260 }
261 };
262 Ok(res)
263 }
264}
265
266#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
268#[serde(rename_all = "kebab-case")]
269pub enum PublishStyle {
270 Homebrew,
272 Npm,
274 User(String),
276}
277
278impl std::str::FromStr for PublishStyle {
279 type Err = DistError;
280 fn from_str(s: &str) -> DistResult<Self> {
281 if let Some(slug) = s.strip_prefix("./") {
282 Ok(Self::User(slug.to_owned()))
283 } else if s == "homebrew" {
284 Ok(Self::Homebrew)
285 } else if s == "npm" {
286 Ok(Self::Npm)
287 } else {
288 Err(DistError::UnrecognizedJobStyle {
289 style: s.to_owned(),
290 })
291 }
292 }
293}
294
295impl<'de> serde::Deserialize<'de> for PublishStyle {
296 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
297 where
298 D: serde::Deserializer<'de>,
299 {
300 use serde::de::Error;
301
302 let path = String::deserialize(deserializer)?;
303 path.parse().map_err(|e| D::Error::custom(format!("{e}")))
304 }
305}
306
307impl std::fmt::Display for PublishStyle {
308 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 match self {
310 PublishStyle::Homebrew => write!(f, "homebrew"),
311 PublishStyle::Npm => write!(f, "npm"),
312 PublishStyle::User(s) => write!(f, "./{s}"),
313 }
314 }
315}
316
317#[derive(Clone, Debug, PartialEq, Eq)]
319pub enum JobStyle {
320 User(String),
322}
323
324impl std::str::FromStr for JobStyle {
325 type Err = DistError;
326 fn from_str(s: &str) -> DistResult<Self> {
327 if let Some(slug) = s.strip_prefix("./") {
328 Ok(Self::User(slug.to_owned()))
329 } else {
330 Err(DistError::UnrecognizedJobStyle {
331 style: s.to_owned(),
332 })
333 }
334 }
335}
336
337impl serde::Serialize for JobStyle {
338 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
339 where
340 S: serde::Serializer,
341 {
342 let s = self.to_string();
343 s.serialize(serializer)
344 }
345}
346
347impl<'de> serde::Deserialize<'de> for JobStyle {
348 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
349 where
350 D: serde::Deserializer<'de>,
351 {
352 use serde::de::Error;
353
354 let path = String::deserialize(deserializer)?;
355 path.parse().map_err(|e| D::Error::custom(format!("{e}")))
356 }
357}
358
359impl std::fmt::Display for JobStyle {
360 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361 match self {
362 JobStyle::User(s) => write!(f, "./{s}"),
363 }
364 }
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq)]
369pub enum ZipStyle {
370 Zip,
372 Tar(CompressionImpl),
374 TempDir,
376}
377
378#[derive(Debug, Copy, Clone, PartialEq, Eq)]
380pub enum CompressionImpl {
381 Gzip,
383 Xzip,
385 Zstd,
387}
388impl ZipStyle {
389 pub fn ext(&self) -> &'static str {
391 match self {
392 ZipStyle::TempDir => "",
393 ZipStyle::Zip => ".zip",
394 ZipStyle::Tar(compression) => match compression {
395 CompressionImpl::Gzip => ".tar.gz",
396 CompressionImpl::Xzip => ".tar.xz",
397 CompressionImpl::Zstd => ".tar.zst",
398 },
399 }
400 }
401}
402
403impl Serialize for ZipStyle {
404 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
405 where
406 S: serde::Serializer,
407 {
408 serializer.serialize_str(self.ext())
409 }
410}
411
412impl<'de> Deserialize<'de> for ZipStyle {
413 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
414 where
415 D: serde::Deserializer<'de>,
416 {
417 use serde::de::Error;
418
419 let ext = String::deserialize(deserializer)?;
420 match &*ext {
421 ".zip" => Ok(ZipStyle::Zip),
422 ".tar.gz" => Ok(ZipStyle::Tar(CompressionImpl::Gzip)),
423 ".tar.xz" => Ok(ZipStyle::Tar(CompressionImpl::Xzip)),
424 ".tar.zstd" | ".tar.zst" => Ok(ZipStyle::Tar(CompressionImpl::Zstd)),
425 _ => Err(D::Error::custom(format!(
426 "unknown archive format {ext}, expected one of: .zip, .tar.gz, .tar.xz, .tar.zstd, .tar.zst"
427 ))),
428 }
429 }
430}
431
432const CARGO_HOME_INSTALL_PATH: &str = "CARGO_HOME";
434
435#[derive(Debug, Clone, PartialEq)]
437pub enum InstallPathStrategy {
438 CargoHome,
440 HomeSubdir {
444 subdir: String,
446 },
447 EnvSubdir {
451 env_key: String,
453 subdir: String,
455 },
456}
457
458impl InstallPathStrategy {
459 pub fn default_list() -> Vec<Self> {
461 vec![InstallPathStrategy::CargoHome]
462 }
463}
464
465impl std::str::FromStr for InstallPathStrategy {
466 type Err = DistError;
467 fn from_str(path: &str) -> DistResult<Self> {
468 if path == CARGO_HOME_INSTALL_PATH {
469 Ok(InstallPathStrategy::CargoHome)
470 } else if let Some(subdir) = path.strip_prefix("~/") {
471 if subdir.is_empty() {
472 Err(DistError::InstallPathHomeSubdir {
473 path: path.to_owned(),
474 })
475 } else {
476 Ok(InstallPathStrategy::HomeSubdir {
477 subdir: subdir.strip_suffix('/').unwrap_or(subdir).to_owned(),
479 })
480 }
481 } else if let Some(val) = path.strip_prefix('$') {
482 if let Some((env_key, subdir)) = val.split_once('/') {
483 Ok(InstallPathStrategy::EnvSubdir {
484 env_key: env_key.to_owned(),
485 subdir: subdir.strip_suffix('/').unwrap_or(subdir).to_owned(),
487 })
488 } else {
489 Err(DistError::InstallPathEnvSlash {
490 path: path.to_owned(),
491 })
492 }
493 } else {
494 Err(DistError::InstallPathInvalid {
495 path: path.to_owned(),
496 })
497 }
498 }
499}
500
501impl std::fmt::Display for InstallPathStrategy {
502 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
503 match self {
504 InstallPathStrategy::CargoHome => write!(f, "{}", CARGO_HOME_INSTALL_PATH),
505 InstallPathStrategy::HomeSubdir { subdir } => write!(f, "~/{subdir}"),
506 InstallPathStrategy::EnvSubdir { env_key, subdir } => write!(f, "${env_key}/{subdir}"),
507 }
508 }
509}
510
511impl serde::Serialize for InstallPathStrategy {
512 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
513 where
514 S: serde::Serializer,
515 {
516 serializer.serialize_str(&self.to_string())
517 }
518}
519
520impl<'de> serde::Deserialize<'de> for InstallPathStrategy {
521 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
522 where
523 D: serde::Deserializer<'de>,
524 {
525 use serde::de::Error;
526
527 let path = String::deserialize(deserializer)?;
528 path.parse().map_err(|e| D::Error::custom(format!("{e}")))
529 }
530}
531
532#[derive(Debug, Clone, PartialEq)]
534pub struct GithubRepoPair {
535 pub owner: String,
537 pub repo: String,
539}
540
541impl std::str::FromStr for GithubRepoPair {
542 type Err = DistError;
543 fn from_str(pair: &str) -> DistResult<Self> {
544 let Some((owner, repo)) = pair.split_once('/') else {
545 return Err(DistError::GithubRepoPairParse {
546 pair: pair.to_owned(),
547 });
548 };
549 Ok(GithubRepoPair {
550 owner: owner.to_owned(),
551 repo: repo.to_owned(),
552 })
553 }
554}
555
556impl std::fmt::Display for GithubRepoPair {
557 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
558 write!(f, "{}/{}", self.owner, self.repo)
559 }
560}
561
562impl serde::Serialize for GithubRepoPair {
563 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
564 where
565 S: serde::Serializer,
566 {
567 serializer.serialize_str(&self.to_string())
568 }
569}
570
571impl<'de> serde::Deserialize<'de> for GithubRepoPair {
572 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
573 where
574 D: serde::Deserializer<'de>,
575 {
576 use serde::de::Error;
577
578 let path = String::deserialize(deserializer)?;
579 path.parse().map_err(|e| D::Error::custom(format!("{e}")))
580 }
581}
582
583impl GithubRepoPair {
584 pub fn into_jinja(self) -> JinjaGithubRepoPair {
586 JinjaGithubRepoPair {
587 owner: self.owner,
588 repo: self.repo,
589 }
590 }
591}
592
593#[derive(Debug, Clone, Serialize)]
595pub struct JinjaGithubRepoPair {
596 pub owner: String,
598 pub repo: String,
600}
601
602#[derive(Debug, Clone, Serialize)]
609#[serde(tag = "kind")]
610pub enum JinjaInstallPathStrategy {
611 CargoHome,
613 HomeSubdir {
617 subdir: String,
619 },
620 EnvSubdir {
624 env_key: String,
626 subdir: String,
628 },
629}
630
631impl InstallPathStrategy {
632 pub fn into_jinja(self) -> JinjaInstallPathStrategy {
634 match self {
635 InstallPathStrategy::CargoHome => JinjaInstallPathStrategy::CargoHome,
636 InstallPathStrategy::HomeSubdir { subdir } => {
637 JinjaInstallPathStrategy::HomeSubdir { subdir }
638 }
639 InstallPathStrategy::EnvSubdir { env_key, subdir } => {
640 JinjaInstallPathStrategy::EnvSubdir { env_key, subdir }
641 }
642 }
643 }
644}
645
646#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
648#[serde(rename_all = "kebab-case")]
649pub enum ChecksumStyle {
650 Sha256,
652 Sha512,
654 Sha3_256,
656 Sha3_512,
658 Blake2s,
660 Blake2b,
662 False,
664}
665
666impl ChecksumStyle {
667 pub fn ext(self) -> &'static ChecksumExtensionRef {
669 ChecksumExtensionRef::from_str(match self {
670 ChecksumStyle::Sha256 => "sha256",
671 ChecksumStyle::Sha512 => "sha512",
672 ChecksumStyle::Sha3_256 => "sha3-256",
673 ChecksumStyle::Sha3_512 => "sha3-512",
674 ChecksumStyle::Blake2s => "blake2s",
675 ChecksumStyle::Blake2b => "blake2b",
676 ChecksumStyle::False => "false",
677 })
678 }
679}
680
681#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
683pub enum GenerateMode {
684 #[serde(rename = "ci")]
686 Ci,
687 #[serde(rename = "msi")]
689 Msi,
690}
691
692impl std::fmt::Display for GenerateMode {
693 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
694 match self {
695 GenerateMode::Ci => "ci".fmt(f),
696 GenerateMode::Msi => "msi".fmt(f),
697 }
698 }
699}
700
701#[derive(Clone, Debug)]
703pub struct HostArgs {
704 pub steps: Vec<HostStyle>,
706}
707
708#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
710pub enum HostStyle {
711 Check,
713 Create,
715 Upload,
717 Release,
719 Announce,
721}
722
723impl std::fmt::Display for HostStyle {
724 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
725 let string = match self {
726 HostStyle::Check => "check",
727 HostStyle::Create => "create",
728 HostStyle::Upload => "upload",
729 HostStyle::Release => "release",
730 HostStyle::Announce => "announce",
731 };
732 string.fmt(f)
733 }
734}
735
736#[derive(Debug, Clone, Deserialize, Serialize)]
738#[serde(rename_all = "kebab-case")]
739pub struct MacPkgConfig {
740 pub identifier: Option<String>,
742 #[serde(skip_serializing_if = "Option::is_none")]
745 pub install_location: Option<String>,
746}
747
748#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
750pub struct SystemDependencies {
751 #[serde(default)]
753 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
754 pub homebrew: BTreeMap<HomebrewPackageName, SystemDependency>,
755
756 #[serde(default)]
758 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
759 pub apt: BTreeMap<AptPackageName, SystemDependency>,
760
761 #[serde(default)]
763 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
764 pub chocolatey: BTreeMap<ChocolateyPackageName, SystemDependency>,
765}
766
767impl SystemDependencies {
768 pub fn append(&mut self, other: &mut Self) {
770 self.homebrew.append(&mut other.homebrew);
771 self.apt.append(&mut other.apt);
772 self.chocolatey.append(&mut other.chocolatey);
773 }
774}
775
776#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
779pub struct SystemDependency(pub SystemDependencyComplex);
780
781#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
783pub struct SystemDependencyComplex {
784 pub version: Option<PackageVersion>,
786 #[serde(default)]
788 pub stage: Vec<DependencyKind>,
789 #[serde(default)]
791 pub targets: Vec<TripleName>,
792}
793
794impl SystemDependencyComplex {
795 pub fn wanted_for_target(&self, target: &TripleNameRef) -> bool {
797 if self.targets.is_empty() {
798 true
799 } else {
800 self.targets.iter().any(|t| t == target)
801 }
802 }
803
804 pub fn stage_wanted(&self, stage: &DependencyKind) -> bool {
806 if self.stage.is_empty() {
807 match stage {
808 DependencyKind::Build => true,
809 DependencyKind::Run => false,
810 }
811 } else {
812 self.stage.contains(stage)
813 }
814 }
815}
816
817#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
819#[serde(untagged)]
820pub enum SystemDependencyKind {
821 Untagged(String),
824 Tagged(SystemDependencyComplex),
826}
827
828#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
830#[serde(rename_all = "kebab-case")]
831pub enum DependencyKind {
832 Build,
834 Run,
836}
837
838impl std::fmt::Display for DependencyKind {
839 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
840 match self {
841 DependencyKind::Build => "build".fmt(f),
842 DependencyKind::Run => "run".fmt(f),
843 }
844 }
845}
846
847impl<'de> Deserialize<'de> for SystemDependency {
848 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
849 where
850 D: serde::Deserializer<'de>,
851 {
852 let kind: SystemDependencyKind = SystemDependencyKind::deserialize(deserializer)?;
853
854 let res = match kind {
855 SystemDependencyKind::Untagged(version) => {
856 let v = if version == "*" { None } else { Some(version) };
857 SystemDependencyComplex {
858 version: v.map(PackageVersion::new),
859 stage: vec![],
860 targets: vec![],
861 }
862 }
863 SystemDependencyKind::Tagged(dep) => dep,
864 };
865
866 Ok(SystemDependency(res))
867 }
868}
869
870#[derive(Debug, Clone)]
872pub enum DirtyMode {
873 AllowList(Vec<GenerateMode>),
875 AllowAll,
877}
878
879impl DirtyMode {
880 pub fn should_run(&self, mode: GenerateMode) -> bool {
882 match self {
883 DirtyMode::AllowAll => false,
884 DirtyMode::AllowList(list) => !list.contains(&mode),
885 }
886 }
887}
888
889#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
891#[serde(rename_all = "kebab-case")]
892pub enum ProductionMode {
893 Test,
895 Prod,
897}
898
899#[derive(Debug, Clone, Deserialize, Serialize)]
902#[serde(rename_all = "kebab-case")]
903pub struct ExtraArtifact {
904 #[serde(default)]
908 #[serde(skip_serializing_if = "path_is_empty")]
909 pub working_dir: Utf8PathBuf,
910 #[serde(rename = "build")]
912 pub command: Vec<String>,
913 #[serde(rename = "artifacts")]
915 pub artifact_relpaths: Vec<Utf8PathBuf>,
916}
917
918fn path_is_empty(p: &Utf8PathBuf) -> bool {
920 p.as_str().is_empty()
921}
922
923impl std::fmt::Display for ProductionMode {
924 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
925 match self {
926 ProductionMode::Test => "test".fmt(f),
927 ProductionMode::Prod => "prod".fmt(f),
928 }
929 }
930}
931
932pub(crate) fn parse_metadata_table_or_manifest(
933 manifest_path: &Utf8Path,
934 dist_manifest_path: Option<&Utf8Path>,
935 metadata_table: Option<&serde_json::Value>,
936) -> DistResult<DistMetadata> {
937 if let Some(dist_manifest_path) = dist_manifest_path {
938 reject_metadata_table(manifest_path, dist_manifest_path, metadata_table)?;
939 let src = SourceFile::load_local(dist_manifest_path)?;
941 parse_generic_config(src)
942 } else {
943 parse_metadata_table(manifest_path, metadata_table)
945 }
946}
947
948pub(crate) fn parse_generic_config(src: SourceFile) -> DistResult<DistMetadata> {
949 let config: GenericConfig = src.deserialize_toml()?;
950 Ok(config.dist.unwrap_or_default())
951}
952
953pub(crate) fn reject_metadata_table(
954 manifest_path: &Utf8Path,
955 dist_manifest_path: &Utf8Path,
956 metadata_table: Option<&serde_json::Value>,
957) -> DistResult<()> {
958 let has_dist_metadata = metadata_table.and_then(|t| t.get(METADATA_DIST)).is_some();
959 if has_dist_metadata {
960 Err(DistError::UnusedMetadata {
961 manifest_path: manifest_path.to_owned(),
962 dist_manifest_path: dist_manifest_path.to_owned(),
963 })
964 } else {
965 Ok(())
966 }
967}
968
969pub(crate) fn parse_metadata_table(
970 manifest_path: &Utf8Path,
971 metadata_table: Option<&serde_json::Value>,
972) -> DistResult<DistMetadata> {
973 Ok(metadata_table
974 .and_then(|t| t.get(METADATA_DIST))
975 .map(DistMetadata::deserialize)
976 .transpose()
977 .map_err(|cause| DistError::CargoTomlParse {
978 manifest_path: manifest_path.to_owned(),
979 cause,
980 })?
981 .unwrap_or_default())
982}
983
984pub fn get_project() -> Result<axoproject::WorkspaceGraph, axoproject::errors::ProjectError> {
986 let start_dir = std::env::current_dir().expect("couldn't get current working dir!?");
987 let start_dir = Utf8PathBuf::from_path_buf(start_dir).expect("project path isn't utf8!?");
988 let repo = LocalRepo::new("git", &start_dir).ok();
989 let workspaces = axoproject::WorkspaceGraph::find_from_git(&start_dir, repo)?;
990 Ok(workspaces)
991}
992
993pub fn load_toml(manifest_path: &Utf8Path) -> DistResult<toml_edit::DocumentMut> {
995 let src = axoasset::SourceFile::load_local(manifest_path)?;
996 let toml = src.deserialize_toml_edit()?;
997 Ok(toml)
998}
999
1000pub fn write_toml(manifest_path: &Utf8Path, toml: toml_edit::DocumentMut) -> DistResult<()> {
1002 let toml_text = toml.to_string();
1003 axoasset::LocalAsset::write_new(&toml_text, manifest_path)?;
1004 Ok(())
1005}
1006
1007pub fn get_toml_metadata(
1009 toml: &mut toml_edit::DocumentMut,
1010 is_workspace: bool,
1011) -> &mut toml_edit::Item {
1012 let root_key = if is_workspace { "workspace" } else { "package" };
1014 let workspace = toml[root_key].or_insert(toml_edit::table());
1015 if let Some(t) = workspace.as_table_mut() {
1016 t.set_implicit(true)
1017 }
1018 let metadata = workspace["metadata"].or_insert(toml_edit::table());
1019 if let Some(t) = metadata.as_table_mut() {
1020 t.set_implicit(true)
1021 }
1022
1023 metadata
1024}
1025
1026mod opt_string_or_vec {
1041 use super::*;
1042 use serde::de::Error;
1043
1044 pub fn serialize<S, T>(v: &Option<Vec<T>>, s: S) -> Result<S::Ok, S::Error>
1045 where
1046 S: serde::Serializer,
1047 T: std::fmt::Display,
1048 {
1049 let Some(vec) = v else {
1051 return s.serialize_none();
1052 };
1053 if vec.len() == 1 {
1055 s.serialize_str(&vec[0].to_string())
1056 } else {
1058 let string_vec = Vec::from_iter(vec.iter().map(ToString::to_string));
1059 string_vec.serialize(s)
1060 }
1061 }
1062
1063 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<Vec<T>>, D::Error>
1064 where
1065 D: serde::Deserializer<'de>,
1066 T: std::str::FromStr,
1067 T::Err: std::fmt::Display,
1068 {
1069 struct StringOrVec<T>(std::marker::PhantomData<T>);
1070
1071 impl<'de, T> serde::de::Visitor<'de> for StringOrVec<T>
1072 where
1073 T: std::str::FromStr,
1074 T::Err: std::fmt::Display,
1075 {
1076 type Value = Option<Vec<T>>;
1077
1078 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1079 formatter.write_str("string or list of strings")
1080 }
1081
1082 fn visit_none<E>(self) -> Result<Self::Value, E>
1084 where
1085 E: Error,
1086 {
1087 Ok(None)
1088 }
1089
1090 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
1092 where
1093 E: Error,
1094 {
1095 Ok(Some(vec![s
1096 .parse()
1097 .map_err(|e| E::custom(format!("{e}")))?]))
1098 }
1099
1100 fn visit_seq<S>(self, seq: S) -> Result<Self::Value, S::Error>
1102 where
1103 S: serde::de::SeqAccess<'de>,
1104 {
1105 let vec: Vec<String> =
1106 Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))?;
1107 let parsed: Result<Vec<T>, S::Error> = vec
1108 .iter()
1109 .map(|s| s.parse::<T>().map_err(|e| S::Error::custom(format!("{e}"))))
1110 .collect();
1111 Ok(Some(parsed?))
1112 }
1113 }
1114
1115 deserializer.deserialize_any(StringOrVec::<T>(std::marker::PhantomData))
1116 }
1117}