1#![deny(missing_docs)]
2
3pub mod macros;
12pub use target_lexicon;
13
14use std::{collections::BTreeMap, str::FromStr};
15
16use schemars::JsonSchema;
17use semver::Version;
18use serde::{Deserialize, Serialize};
19use target_lexicon::Triple;
20
21declare_strongly_typed_string! {
22 pub struct TripleName => &TripleNameRef;
24}
25
26impl TripleNameRef {
27 pub fn parse(&self) -> Result<Triple, <Triple as FromStr>::Err> {
29 Triple::from_str(self.as_str())
30 }
31
32 pub fn is_musl(&self) -> bool {
34 self.0.contains("musl")
35 }
36
37 pub fn is_linux(&self) -> bool {
39 self.0.contains("linux")
40 }
41
42 pub fn is_apple(&self) -> bool {
44 self.0.contains("apple")
45 }
46
47 pub fn is_darwin(&self) -> bool {
49 self.0.contains("darwin")
50 }
51
52 pub fn is_windows(&self) -> bool {
54 self.0.contains("windows")
55 }
56
57 pub fn is_x86_64(&self) -> bool {
59 self.0.contains("x86_64")
60 }
61
62 pub fn is_aarch64(&self) -> bool {
64 self.0.contains("aarch64")
65 }
66
67 pub fn is_linux_musl(&self) -> bool {
72 self.0.contains("linux-musl")
73 }
74
75 pub fn is_windows_msvc(&self) -> bool {
77 self.0.contains("windows-msvc")
78 }
79}
80declare_strongly_typed_string! {
81 pub struct GithubRunner => &GithubRunnerRef;
83
84 pub struct ContainerImage => &ContainerImageRef;
86}
87
88pub type GithubRunners = BTreeMap<TripleName, GithubRunnerConfig>;
91
92impl GithubRunnerRef {
93 pub fn is_buildjet(&self) -> bool {
95 self.as_str().contains("buildjet")
96 }
97}
98
99#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
104#[serde(untagged)]
105pub enum StringLikeOr<S, T> {
106 StringLike(S),
108 Val(T),
110}
111
112impl<S, T> StringLikeOr<S, T> {
113 pub fn from_s(s: S) -> Self {
115 Self::StringLike(s)
116 }
117
118 pub fn from_t(t: T) -> Self {
120 Self::Val(t)
121 }
122}
123
124pub type LocalPath = String;
128pub type RelPath = String;
134
135declare_strongly_typed_string! {
136 pub struct ArtifactId => &ArtifactIdRef;
138}
139
140pub type SystemId = String;
142pub type AssetId = String;
144pub type SortedSet<T> = std::collections::BTreeSet<T>;
146
147#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
149pub struct DistManifest {
150 #[serde(default)]
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub dist_version: Option<String>,
154 #[serde(default)]
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub announcement_tag: Option<String>,
158 #[serde(default)]
162 pub announcement_tag_is_implicit: bool,
163 #[serde(default)]
165 pub announcement_is_prerelease: bool,
166 #[serde(default)]
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub announcement_title: Option<String>,
170 #[serde(default)]
172 #[serde(skip_serializing_if = "Option::is_none")]
173 pub announcement_changelog: Option<String>,
174 #[serde(default)]
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub announcement_github_body: Option<String>,
178 #[serde(default)]
182 #[serde(skip_serializing_if = "Option::is_none")]
183 pub system_info: Option<SystemInfo>,
184 #[serde(default)]
186 #[serde(skip_serializing_if = "Vec::is_empty")]
187 pub releases: Vec<Release>,
188 #[serde(default)]
190 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
191 pub artifacts: BTreeMap<ArtifactId, Artifact>,
192 #[serde(default)]
194 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
195 pub systems: BTreeMap<SystemId, SystemInfo>,
196 #[serde(default)]
198 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
199 pub assets: BTreeMap<AssetId, AssetInfo>,
200 #[serde(default)]
202 pub publish_prereleases: bool,
203 #[serde(default)]
205 pub force_latest: bool,
206 #[serde(default)]
208 #[serde(skip_serializing_if = "Option::is_none")]
209 pub ci: Option<CiInfo>,
210 #[serde(default)]
212 pub linkage: Vec<Linkage>,
218 #[serde(default)]
220 pub upload_files: Vec<String>,
223 #[serde(default)]
227 #[serde(skip_serializing_if = "std::ops::Not::not")]
228 pub github_attestations: bool,
229 #[serde(default)]
231 #[serde(skip_serializing_if = "GithubAttestationsFilters::is_default")]
232 pub github_attestations_filters: GithubAttestationsFilters,
233 #[serde(default)]
237 #[serde(skip_serializing_if = "GithubAttestationsPhase::is_default")]
238 pub github_attestations_phase: GithubAttestationsPhase,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
243pub enum BuildEnvironment {
244 #[serde(rename = "linux")]
246 Linux {
247 glibc_version: Option<GlibcVersion>,
250 },
251 #[serde(rename = "macos")]
253 MacOS {
254 os_version: String,
256 },
257 #[serde(rename = "windows")]
259 Windows,
260 #[serde(rename = "indeterminate")]
262 Indeterminate,
263}
264
265#[derive(
267 Debug, Clone, Serialize, Deserialize, JsonSchema, Hash, PartialEq, Eq, PartialOrd, Ord,
268)]
269pub struct GlibcVersion {
270 pub major: u64,
272 pub series: u64,
274}
275
276impl Default for GlibcVersion {
277 fn default() -> Self {
278 Self {
279 major: 2,
281 series: 31,
282 }
283 }
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
288pub struct AssetInfo {
289 pub id: AssetId,
291 pub name: String,
293 pub system: SystemId,
295 pub target_triples: Vec<TripleName>,
301 pub linkage: Option<Linkage>,
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
307pub struct CiInfo {
308 #[serde(skip_serializing_if = "Option::is_none")]
310 pub github: Option<GithubCiInfo>,
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
315pub struct GithubCiInfo {
316 #[serde(skip_serializing_if = "Option::is_none")]
318 pub artifacts_matrix: Option<GithubMatrix>,
319
320 #[serde(skip_serializing_if = "Option::is_none")]
322 pub pr_run_mode: Option<PrRunMode>,
323
324 #[serde(skip_serializing_if = "Option::is_none")]
326 pub external_repo_commit: Option<String>,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
331pub struct GithubMatrix {
332 #[serde(default)]
334 #[serde(skip_serializing_if = "Vec::is_empty")]
335 pub include: Vec<GithubLocalJobConfig>,
336}
337
338impl GithubMatrix {
339 pub fn is_empty(&self) -> bool {
343 self.include.is_empty()
344 }
345}
346
347declare_strongly_typed_string! {
348 pub struct PackageInstallScript => &PackageInstallScriptRef;
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
355pub struct GithubRunnerConfigInput {
356 pub runner: Option<GithubRunner>,
364
365 pub host: Option<TripleName>,
369
370 pub container: Option<StringLikeOr<ContainerImage, ContainerConfigInput>>,
378}
379
380#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, PartialOrd, Ord)]
382pub struct GithubRunnerConfig {
383 pub runner: GithubRunner,
389
390 pub host: TripleName,
394
395 #[serde(skip_serializing_if = "Option::is_none")]
401 pub container: Option<ContainerConfig>,
402}
403
404impl GithubRunnerConfig {
405 pub fn real_triple_name(&self) -> &TripleNameRef {
409 if let Some(container) = &self.container {
410 &container.host
411 } else {
412 &self.host
413 }
414 }
415
416 pub fn real_triple(&self) -> Triple {
418 self.real_triple_name().parse().unwrap()
419 }
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
424pub struct ContainerConfigInput {
425 pub image: ContainerImage,
428
429 pub host: Option<TripleName>,
432
433 #[serde(rename = "package-manager")]
435 pub package_manager: Option<PackageManager>,
436}
437
438#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, PartialOrd, Ord)]
440pub struct ContainerConfig {
441 pub image: ContainerImage,
444
445 pub host: TripleName,
448
449 pub package_manager: Option<PackageManager>,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
456pub struct GithubGlobalJobConfig {
457 #[serde(flatten)]
459 pub runner: GithubRunnerConfig,
460
461 pub install_dist: GhaRunStep,
463
464 pub dist_args: String,
466
467 #[serde(skip_serializing_if = "Option::is_none")]
469 pub install_cargo_cyclonedx: Option<GhaRunStep>,
470
471 #[serde(skip_serializing_if = "Option::is_none")]
472 pub install_omnibor: Option<GhaRunStep>,
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
478pub struct GithubLocalJobConfig {
479 #[serde(flatten)]
481 pub runner: GithubRunnerConfig,
482
483 pub install_dist: GhaRunStep,
485
486 pub dist_args: String,
488
489 #[serde(skip_serializing_if = "Option::is_none")]
491 pub targets: Option<Vec<TripleName>>,
492
493 #[serde(skip_serializing_if = "Option::is_none")]
495 pub install_cargo_auditable: Option<GhaRunStep>,
496
497 #[serde(skip_serializing_if = "Option::is_none")]
499 pub install_omnibor: Option<GhaRunStep>,
500
501 #[serde(skip_serializing_if = "Option::is_none")]
503 pub packages_install: Option<PackageInstallScript>,
504
505 #[serde(skip_serializing_if = "Option::is_none")]
507 pub cache_provider: Option<String>,
508}
509
510#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
512pub struct GithubAttestationsFilters(Vec<String>);
513
514impl Default for GithubAttestationsFilters {
515 fn default() -> Self {
516 Self(vec!["*".to_string()])
517 }
518}
519
520impl<'a> IntoIterator for &'a GithubAttestationsFilters {
521 type Item = &'a String;
522 type IntoIter = std::slice::Iter<'a, String>;
523
524 fn into_iter(self) -> Self::IntoIter {
525 self.0.iter()
526 }
527}
528
529impl GithubAttestationsFilters {
530 fn is_default(&self) -> bool {
531 *self == Default::default()
532 }
533}
534
535#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, Default)]
537pub enum GithubAttestationsPhase {
538 #[serde(rename = "announce")]
540 Announce,
541 #[serde(rename = "host")]
543 Host,
544 #[default]
546 #[serde(rename = "build-local-artifacts")]
547 BuildLocalArtifacts,
548}
549
550impl GithubAttestationsPhase {
551 fn is_default(&self) -> bool {
552 matches!(self, GithubAttestationsPhase::BuildLocalArtifacts)
553 }
554}
555
556impl std::fmt::Display for GithubAttestationsPhase {
557 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
558 match self {
559 GithubAttestationsPhase::Announce => write!(f, "announce"),
560 GithubAttestationsPhase::Host => write!(f, "host"),
561 GithubAttestationsPhase::BuildLocalArtifacts => write!(f, "build-local-artifacts"),
562 }
563 }
564}
565
566#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
568#[serde(tag = "shell", content = "run")]
572pub enum GhaRunStep {
573 #[serde(rename = "sh")]
575 Dash(DashScript),
576 #[serde(rename = "pwsh")]
578 Powershell(PowershellScript),
579}
580
581impl From<DashScript> for GhaRunStep {
582 fn from(bash: DashScript) -> Self {
583 Self::Dash(bash)
584 }
585}
586
587impl From<PowershellScript> for GhaRunStep {
588 fn from(powershell: PowershellScript) -> Self {
589 Self::Powershell(powershell)
590 }
591}
592
593declare_strongly_typed_string! {
594 pub struct DashScript => &DashScriptRef;
596
597 pub struct PowershellScript => &PowershellScriptRef;
599}
600
601#[derive(
603 Debug, Copy, Clone, Serialize, Deserialize, JsonSchema, Default, PartialEq, Eq, PartialOrd, Ord,
604)]
605pub enum PrRunMode {
606 #[serde(rename = "skip")]
608 Skip,
609 #[default]
611 #[serde(rename = "plan")]
612 Plan,
613 #[serde(rename = "upload")]
615 Upload,
616}
617
618impl std::fmt::Display for PrRunMode {
619 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
620 match self {
621 PrRunMode::Skip => write!(f, "skip"),
622 PrRunMode::Plan => write!(f, "plan"),
623 PrRunMode::Upload => write!(f, "upload"),
624 }
625 }
626}
627
628#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
630pub struct SystemInfo {
631 pub id: SystemId,
633 pub cargo_version_line: Option<String>,
635 pub build_environment: BuildEnvironment,
637}
638
639#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
641pub struct EnvironmentVariables {
642 pub install_dir_env_var: String,
644 pub unmanaged_dir_env_var: String,
646 pub disable_update_env_var: String,
648 pub no_modify_path_env_var: String,
650 pub print_quiet_env_var: String,
652 pub print_verbose_env_var: String,
654 pub download_url_env_var: String,
658 pub github_base_url_env_var: String,
663 pub ghe_base_url_env_var: String,
668 pub github_token_env_var: String,
670}
671
672#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
674pub struct Release {
675 pub app_name: String,
677 pub app_version: String,
680 #[serde(default)]
682 #[serde(skip_serializing_if = "Option::is_none")]
683 pub env: Option<EnvironmentVariables>,
684 #[serde(default)]
686 #[serde(skip_serializing_if = "Option::is_none")]
687 pub display_name: Option<String>,
688 #[serde(default)]
690 #[serde(skip_serializing_if = "Option::is_none")]
691 pub display: Option<bool>,
692 #[serde(default)]
694 #[serde(skip_serializing_if = "Vec::is_empty")]
695 pub artifacts: Vec<ArtifactId>,
696 #[serde(default)]
698 #[serde(skip_serializing_if = "Hosting::is_empty")]
699 pub hosting: Hosting,
700}
701
702declare_strongly_typed_string! {
703 pub struct ChecksumExtension => &ChecksumExtensionRef;
710
711 pub struct ChecksumValue => &ChecksumValueRef;
713}
714
715#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
719pub struct Artifact {
720 #[serde(skip_serializing_if = "Option::is_none")]
726 #[serde(default)]
727 pub name: Option<ArtifactId>,
728 #[serde(flatten)]
730 pub kind: ArtifactKind,
731 #[serde(skip_serializing_if = "Vec::is_empty")]
733 #[serde(default)]
734 pub target_triples: Vec<TripleName>,
735 #[serde(skip_serializing_if = "Option::is_none")]
737 #[serde(default)]
738 pub path: Option<LocalPath>,
739 #[serde(skip_serializing_if = "Vec::is_empty")]
741 #[serde(default)]
742 pub assets: Vec<Asset>,
743 #[serde(skip_serializing_if = "Option::is_none")]
745 #[serde(default)]
746 pub install_hint: Option<String>,
747 #[serde(skip_serializing_if = "Option::is_none")]
749 #[serde(default)]
750 pub description: Option<String>,
751 #[serde(skip_serializing_if = "Option::is_none")]
753 #[serde(default)]
754 pub checksum: Option<ArtifactId>,
755 #[serde(default)]
760 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
761 pub checksums: BTreeMap<ChecksumExtension, ChecksumValue>,
762}
763
764#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
766pub struct Asset {
767 #[serde(default)]
769 #[serde(skip_serializing_if = "Option::is_none")]
770 pub id: Option<String>,
771 #[serde(default)]
773 #[serde(skip_serializing_if = "Option::is_none")]
774 pub name: Option<String>,
775 #[serde(default)]
777 #[serde(skip_serializing_if = "Option::is_none")]
778 pub path: Option<RelPath>,
779 #[serde(flatten)]
781 pub kind: AssetKind,
782}
783
784#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
786#[serde(tag = "kind")]
787#[non_exhaustive]
788pub enum AssetKind {
789 #[serde(rename = "executable")]
791 Executable(ExecutableAsset),
792 #[serde(rename = "c_dynamic_library")]
794 CDynamicLibrary(DynamicLibraryAsset),
795 #[serde(rename = "c_static_library")]
797 CStaticLibrary(StaticLibraryAsset),
798 #[serde(rename = "readme")]
800 Readme,
801 #[serde(rename = "license")]
803 License,
804 #[serde(rename = "changelog")]
806 Changelog,
807 #[serde(other)]
811 #[serde(rename = "unknown")]
812 Unknown,
813}
814
815#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
817#[serde(tag = "kind")]
818#[non_exhaustive]
819pub enum ArtifactKind {
820 #[serde(rename = "executable-zip")]
822 ExecutableZip,
823 #[serde(rename = "symbols")]
825 Symbols,
826 #[serde(rename = "installer")]
828 Installer,
829 #[serde(rename = "checksum")]
831 Checksum,
832 #[serde(rename = "unified-checksum")]
834 UnifiedChecksum,
835 #[serde(rename = "source-tarball")]
837 SourceTarball,
838 #[serde(rename = "extra-artifact")]
840 ExtraArtifact,
841 #[serde(rename = "updater")]
843 Updater,
844 #[serde(rename = "sbom")]
846 SBOM,
847 #[serde(rename = "omnibor-artifact-id")]
849 OmniborArtifactId,
850 #[serde(other)]
854 #[serde(rename = "unknown")]
855 Unknown,
856}
857
858#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
860pub struct ExecutableAsset {
861 #[serde(skip_serializing_if = "Option::is_none")]
863 #[serde(default)]
864 pub symbols_artifact: Option<ArtifactId>,
865}
866
867#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
869pub struct DynamicLibraryAsset {
870 #[serde(skip_serializing_if = "Option::is_none")]
872 #[serde(default)]
873 pub symbols_artifact: Option<ArtifactId>,
874}
875
876#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
878pub struct StaticLibraryAsset {
879 #[serde(skip_serializing_if = "Option::is_none")]
881 #[serde(default)]
882 pub symbols_artifact: Option<ArtifactId>,
883}
884
885pub struct VersionInfo {
887 pub version: Version,
889 pub format: Format,
891}
892
893pub const SELF_VERSION: &str = env!("CARGO_PKG_VERSION");
895pub const DIST_EPOCH_1_MAX: &str = "0.0.3-prerelease8";
898pub const DIST_EPOCH_2_MAX: &str = "0.0.6-prerelease6";
901
902#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
904pub enum Format {
905 Epoch1,
907 Epoch2,
909 Epoch3,
912 Future,
915}
916
917impl Format {
918 pub fn unsupported(&self) -> bool {
920 self <= &Format::Epoch1
921 }
922 pub fn artifact_names_contain_versions(&self) -> bool {
924 self <= &Format::Epoch2
925 }
926}
927
928impl DistManifest {
929 pub fn new(releases: Vec<Release>, artifacts: BTreeMap<ArtifactId, Artifact>) -> Self {
931 Self {
932 dist_version: None,
933 announcement_tag: None,
934 announcement_tag_is_implicit: false,
935 announcement_is_prerelease: false,
936 announcement_title: None,
937 announcement_changelog: None,
938 announcement_github_body: None,
939 github_attestations: false,
940 github_attestations_filters: Default::default(),
941 github_attestations_phase: Default::default(),
942 system_info: None,
943 releases,
944 artifacts,
945 systems: Default::default(),
946 assets: Default::default(),
947 publish_prereleases: false,
948 force_latest: false,
949 ci: None,
950 linkage: vec![],
951 upload_files: vec![],
952 }
953 }
954
955 pub fn json_schema() -> schemars::Schema {
957 schemars::schema_for!(DistManifest)
958 }
959
960 pub fn format(&self) -> Format {
964 self.dist_version
965 .as_ref()
966 .and_then(|v| v.parse().ok())
967 .map(|v| format_of_version(&v))
968 .unwrap_or(Format::Future)
969 }
970
971 pub fn artifacts_for_release<'a>(
973 &'a self,
974 release: &'a Release,
975 ) -> impl Iterator<Item = (&'a ArtifactIdRef, &'a Artifact)> {
976 release
977 .artifacts
978 .iter()
979 .filter_map(|k| Some((&**k, self.artifacts.get(k)?)))
980 }
981
982 pub fn release_by_name(&self, name: &str) -> Option<&Release> {
984 self.releases.iter().find(|r| r.app_name == name)
985 }
986
987 pub fn ensure_release(&mut self, name: String, version: String) -> &mut Release {
990 if let Some(position) = self.releases.iter().position(|r| r.app_name == name) {
992 &mut self.releases[position]
993 } else {
994 let env_app_name = name.to_ascii_uppercase().replace('-', "_");
995 let install_dir_env_var = format!("{env_app_name}_INSTALL_DIR");
996 let download_url_env_var = format!("{env_app_name}_DOWNLOAD_URL");
997 let unmanaged_dir_env_var = format!("{env_app_name}_UNMANAGED_INSTALL");
998 let disable_update_env_var = format!("{env_app_name}_DISABLE_UPDATE");
999 let print_quiet_env_var = format!("{env_app_name}_PRINT_QUIET");
1000 let print_verbose_env_var = format!("{env_app_name}_PRINT_VERBOSE");
1001 let no_modify_path_env_var = format!("{env_app_name}_NO_MODIFY_PATH");
1002 let github_base_url_env_var = format!("{env_app_name}_INSTALLER_GITHUB_BASE_URL");
1003 let ghe_base_url_env_var = format!("{env_app_name}_INSTALLER_GHE_BASE_URL");
1004 let github_token_env_var = format!("{env_app_name}_GITHUB_TOKEN");
1005
1006 let environment_variables = EnvironmentVariables {
1007 install_dir_env_var,
1008 download_url_env_var,
1009 unmanaged_dir_env_var,
1010 disable_update_env_var,
1011 print_quiet_env_var,
1012 print_verbose_env_var,
1013 no_modify_path_env_var,
1014 github_base_url_env_var,
1015 ghe_base_url_env_var,
1016 github_token_env_var,
1017 };
1018
1019 self.releases.push(Release {
1020 app_name: name,
1021 app_version: version,
1022 env: Some(environment_variables),
1023 artifacts: vec![],
1024 hosting: Hosting::default(),
1025 display: None,
1026 display_name: None,
1027 });
1028 self.releases.last_mut().unwrap()
1029 }
1030 }
1031
1032 pub fn linkage_for_artifact(&self, artifact_id: &ArtifactId) -> Linkage {
1036 let mut output = Linkage::default();
1037
1038 let Some(artifact) = self.artifacts.get(artifact_id) else {
1039 return output;
1040 };
1041 for base_asset in &artifact.assets {
1042 let Some(asset_id) = &base_asset.id else {
1043 continue;
1044 };
1045 let Some(true_asset) = self.assets.get(asset_id) else {
1046 continue;
1047 };
1048 let Some(linkage) = &true_asset.linkage else {
1049 continue;
1050 };
1051 output.extend(linkage);
1052 }
1053
1054 output
1055 }
1056}
1057
1058impl Release {
1059 pub fn artifact_download_url(&self) -> Option<String> {
1061 self.hosting.artifact_download_url()
1062 }
1063}
1064
1065#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema)]
1067#[serde(rename_all = "kebab-case")]
1068pub struct Hosting {
1069 #[serde(default)]
1071 #[serde(skip_serializing_if = "Option::is_none")]
1072 pub github: Option<GithubHosting>,
1073}
1074
1075#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
1077pub struct GithubHosting {
1078 pub artifact_base_url: String,
1081 pub artifact_download_path: String,
1085 pub owner: String,
1087 pub repo: String,
1089}
1090
1091impl Hosting {
1092 pub fn artifact_download_url(&self) -> Option<String> {
1094 let Hosting { github } = &self;
1095 if let Some(host) = &github {
1096 return Some(format!(
1097 "{}{}",
1098 host.artifact_base_url, host.artifact_download_path
1099 ));
1100 }
1101 None
1102 }
1103 pub fn is_empty(&self) -> bool {
1105 let Hosting { github } = &self;
1106 github.is_none()
1107 }
1108}
1109
1110#[derive(Clone, Default, Debug, Deserialize, Serialize, JsonSchema)]
1112pub struct Linkage {
1113 #[serde(default)]
1115 #[serde(skip_serializing_if = "SortedSet::is_empty")]
1116 pub system: SortedSet<Library>,
1117 #[serde(default)]
1119 #[serde(skip_serializing_if = "SortedSet::is_empty")]
1120 pub homebrew: SortedSet<Library>,
1121 #[serde(default)]
1123 #[serde(skip_serializing_if = "SortedSet::is_empty")]
1124 pub public_unmanaged: SortedSet<Library>,
1125 #[serde(default)]
1127 #[serde(skip_serializing_if = "SortedSet::is_empty")]
1128 pub other: SortedSet<Library>,
1129 #[serde(default)]
1131 #[serde(skip_serializing_if = "SortedSet::is_empty")]
1132 pub frameworks: SortedSet<Library>,
1133}
1134
1135#[derive(
1137 Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, PartialOrd, Ord,
1138)]
1139#[serde(rename_all = "lowercase")]
1140pub enum PackageManager {
1141 Homebrew,
1143 Apt,
1145}
1146
1147declare_strongly_typed_string! {
1148 pub struct HomebrewPackageName => &HomebrewPackageNameRef;
1150
1151 pub struct AptPackageName => &AptPackageNameRef;
1153
1154 pub struct ChocolateyPackageName => &ChocolateyPackageNameRef;
1156
1157 pub struct PipPackageName => &PipPackageNameRef;
1159
1160 pub struct PackageVersion => &PackageVersionRef;
1162}
1163
1164#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, PartialOrd, Ord)]
1166pub struct Library {
1167 pub path: String,
1169 #[serde(skip_serializing_if = "Option::is_none")]
1171 pub source: Option<String>,
1172 pub package_manager: Option<PackageManager>,
1174 }
1178
1179impl Linkage {
1180 pub fn extend(&mut self, val: &Linkage) {
1182 let Linkage {
1183 system,
1184 homebrew,
1185 public_unmanaged,
1186 other,
1187 frameworks,
1188 } = val;
1189 self.system.extend(system.iter().cloned());
1190 self.homebrew.extend(homebrew.iter().cloned());
1191 self.public_unmanaged
1192 .extend(public_unmanaged.iter().cloned());
1193 self.other.extend(other.iter().cloned());
1194 self.frameworks.extend(frameworks.iter().cloned());
1195 }
1196}
1197
1198impl Library {
1199 pub fn new(path: String) -> Self {
1201 Self {
1202 path,
1203 source: None,
1204 package_manager: None,
1205 }
1206 }
1207
1208 pub fn is_glibc(&self) -> bool {
1210 if let Some(source) = &self.source {
1212 source == "libc6"
1213 } else {
1214 self.path.contains("libc.so.6") ||
1216 self.path.contains("libc-2")
1221 }
1222 }
1223}
1224
1225impl std::fmt::Display for Library {
1226 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1227 if let Some(package) = &self.source {
1228 write!(f, "{} ({package})", self.path)
1229 } else {
1230 write!(f, "{}", self.path)
1231 }
1232 }
1233}
1234
1235fn dist_version(input: &str) -> Option<Version> {
1237 #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1238 struct PartialDistManifest {
1239 #[serde(default)]
1241 #[serde(skip_serializing_if = "Option::is_none")]
1242 pub dist_version: Option<String>,
1243 }
1244
1245 let manifest: PartialDistManifest = serde_json::from_str(input).ok()?;
1246 let version: Version = manifest.dist_version?.parse().ok()?;
1247 Some(version)
1248}
1249
1250pub fn check_version(input: &str) -> Option<VersionInfo> {
1252 let version = dist_version(input)?;
1253 let format = format_of_version(&version);
1254 Some(VersionInfo { version, format })
1255}
1256
1257pub fn format_of_version(version: &Version) -> Format {
1259 let epoch1 = Version::parse(DIST_EPOCH_1_MAX).unwrap();
1260 let epoch2 = Version::parse(DIST_EPOCH_2_MAX).unwrap();
1261 let self_ver = Version::parse(SELF_VERSION).unwrap();
1262 if version > &self_ver {
1263 Format::Future
1264 } else if version > &epoch2 {
1265 Format::Epoch3
1266 } else if version > &epoch1 {
1267 Format::Epoch2
1268 } else {
1269 Format::Epoch1
1270 }
1271}
1272
1273#[test]
1274fn emit() {
1275 let schema = DistManifest::json_schema();
1276 let json_schema = serde_json::to_string_pretty(&schema).unwrap();
1277 insta::assert_snapshot!(json_schema);
1278}