1use std::collections::BTreeMap;
52
53use crate::backend::installer::{ExecutableZipFragment, HomebrewImpl};
54use crate::platform::targets::{
55 TARGET_ARM64_LINUX_GNU, TARGET_ARM64_MAC, TARGET_X64_LINUX_GNU, TARGET_X64_MAC,
56};
57use axoasset::AxoClient;
58use axoprocess::Cmd;
59use axoproject::{PackageId, PackageIdx, WorkspaceGraph};
60use camino::{Utf8Path, Utf8PathBuf};
61use cargo_dist_schema::target_lexicon::{OperatingSystem, Triple};
62use cargo_dist_schema::{
63 ArtifactId, BuildEnvironment, DistManifest, HomebrewPackageName, SystemId, SystemInfo,
64 TripleName, TripleNameRef,
65};
66use semver::Version;
67use serde::Serialize;
68use tracing::{info, warn};
69
70use crate::announce::{self, AnnouncementTag, TagMode};
71use crate::backend::ci::github::GithubCiInfo;
72use crate::backend::ci::CiInfo;
73use crate::backend::installer::homebrew::{to_homebrew_license_format, HomebrewFragments};
74use crate::backend::installer::macpkg::PkgInstallerInfo;
75use crate::config::v1::builds::cargo::AppCargoBuildConfig;
76use crate::config::v1::ci::CiConfig;
77use crate::config::v1::installers::CommonInstallerConfig;
78use crate::config::v1::publishers::PublisherConfig;
79use crate::config::v1::{app_config, workspace_config, AppConfig, WorkspaceConfig};
80use crate::config::{DependencyKind, DirtyMode, LibraryStyle};
81use crate::linkage::determine_build_environment;
82use crate::net::ClientSettings;
83use crate::platform::{PlatformSupport, RuntimeConditions};
84use crate::sign::Signing;
85use crate::{
86 backend::{
87 installer::{
88 homebrew::{to_class_case, HomebrewInstallerInfo},
89 msi::MsiInstallerInfo,
90 npm::NpmInstallerInfo,
91 InstallerImpl, InstallerInfo,
92 },
93 templates::Templates,
94 },
95 config::{
96 self, ArtifactMode, ChecksumStyle, CompressionImpl, Config, HostingStyle, InstallerStyle,
97 ZipStyle,
98 },
99 errors::{DistError, DistResult},
100};
101
102pub const METADATA_DIST: &str = "dist";
104pub const TARGET_DIST: &str = "distrib";
107pub const PROFILE_DIST: &str = "dist";
109
110pub const OS_LINUX: &str = "linux";
112pub const OS_MACOS: &str = "macos";
114pub const OS_WINDOWS: &str = "windows";
116
117pub const CPU_X64: &str = "x86_64";
119pub const CPU_X86: &str = "x86";
121pub const CPU_ARM64: &str = "arm64";
123pub const CPU_ARM: &str = "arm";
125
126pub type FastMap<K, V> = std::collections::HashMap<K, V>;
128pub type SortedMap<K, V> = std::collections::BTreeMap<K, V>;
130pub type SortedSet<T> = std::collections::BTreeSet<T>;
132
133#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
135pub struct ArtifactIdx(pub usize);
136
137#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
139pub struct ReleaseVariantIdx(pub usize);
140
141#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
143pub struct ReleaseIdx(pub usize);
144
145#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
147pub struct BinaryIdx(pub usize);
148
149#[derive(Clone, Debug)]
151pub struct BinaryAliases(BTreeMap<String, Vec<String>>);
152
153impl BinaryAliases {
154 pub fn for_target(&self, target: &TripleNameRef) -> BTreeMap<String, Vec<String>> {
157 if target.is_windows() {
158 BTreeMap::from_iter(self.0.iter().map(|(k, v)| {
159 (
160 format!("{k}.exe"),
161 v.iter().map(|name| format!("{name}.exe")).collect(),
162 )
163 }))
164 } else {
165 self.0.clone()
166 }
167 }
168
169 pub fn for_targets(
172 &self,
173 targets: &[TripleName],
174 ) -> BTreeMap<TripleName, BTreeMap<String, Vec<String>>> {
175 BTreeMap::from_iter(
176 targets
177 .iter()
178 .map(|target| (target.to_owned(), self.for_target(target))),
179 )
180 }
181}
182
183#[derive(Debug)]
189pub struct DistGraph {
190 pub system_id: SystemId,
196 pub is_init: bool,
198 pub allow_dirty: DirtyMode,
200 pub global_homebrew_tap: Option<String>,
202 pub global_publishers: Option<PublisherConfig>,
204 pub precise_cargo_builds: bool,
206
207 pub tools: Tools,
209 pub signer: Signing,
211 pub templates: Templates,
213
214 pub target_dir: Utf8PathBuf,
216 pub repo_dir: Utf8PathBuf,
218 pub workspace_dir: Utf8PathBuf,
220 pub dist_dir: Utf8PathBuf,
222 pub config: WorkspaceConfig,
224 pub local_build_steps: Vec<BuildStep>,
226 pub global_build_steps: Vec<BuildStep>,
228 pub artifacts: Vec<Artifact>,
230 pub binaries: Vec<Binary>,
232 pub variants: Vec<ReleaseVariant>,
234 pub releases: Vec<Release>,
236 pub ci: CiInfo,
238 pub hosting: Option<HostingInfo>,
240 pub local_builds_are_lies: bool,
242 pub client_settings: ClientSettings,
244 pub axoclient: AxoClient,
246}
247
248#[derive(Debug, Clone)]
250pub struct HostingInfo {
251 pub hosts: Vec<HostingStyle>,
253 pub domain: String,
255 pub repo_path: String,
257 pub source_host: String,
259 pub owner: String,
261 pub project: String,
263}
264
265#[derive(Debug, Clone)]
267pub struct Tools {
268 pub host_target: TripleName,
270 pub cargo: Option<CargoInfo>,
272 pub rustup: Option<Tool>,
274 pub brew: Option<Tool>,
276 pub git: Option<Tool>,
278 pub omnibor: Option<Tool>,
280 pub code_sign_tool: Option<Tool>,
284 pub cargo_auditable: Option<Tool>,
286 pub cargo_cyclonedx: Option<Tool>,
288 pub cargo_xwin: Option<Tool>,
290 pub cargo_zigbuild: Option<Tool>,
292}
293
294impl Tools {
295 pub fn cargo(&self) -> DistResult<&CargoInfo> {
297 self.cargo.as_ref().ok_or(DistError::ToolMissing {
298 tool: "cargo".to_owned(),
299 })
300 }
301
302 pub fn omnibor(&self) -> DistResult<&Tool> {
304 self.omnibor.as_ref().ok_or(DistError::ToolMissing {
305 tool: "omnibor-cli".to_owned(),
306 })
307 }
308
309 pub fn cargo_auditable(&self) -> DistResult<&Tool> {
311 self.cargo_auditable.as_ref().ok_or(DistError::ToolMissing {
312 tool: "cargo-auditable".to_owned(),
313 })
314 }
315
316 pub fn cargo_cyclonedx(&self) -> DistResult<&Tool> {
318 self.cargo_cyclonedx.as_ref().ok_or(DistError::ToolMissing {
319 tool: "cargo-cyclonedx".to_owned(),
320 })
321 }
322
323 pub fn cargo_xwin(&self) -> DistResult<&Tool> {
325 self.cargo_xwin.as_ref().ok_or(DistError::ToolMissing {
326 tool: "cargo-xwin".to_owned(),
327 })
328 }
329
330 pub fn cargo_zigbuild(&self) -> DistResult<&Tool> {
332 self.cargo_zigbuild.as_ref().ok_or(DistError::ToolMissing {
333 tool: "cargo-zigbuild".to_owned(),
334 })
335 }
336}
337
338#[derive(Debug, Clone)]
340pub struct CargoInfo {
341 pub cmd: String,
343 pub version_line: Option<String>,
345 pub host_target: TripleName,
347}
348
349#[derive(Debug, Clone, Default)]
351pub struct Tool {
352 pub cmd: String,
354 pub version: String,
356}
357
358#[derive(Debug)]
360pub struct Binary {
361 pub id: String,
365 pub pkg_idx: PackageIdx,
367 pub pkg_id: Option<PackageId>,
372 pub pkg_spec: String,
374 pub name: String,
376 pub file_name: String,
378 pub target: TripleName,
380 pub symbols_artifact: Option<ArtifactIdx>,
382 pub copy_exe_to: Vec<Utf8PathBuf>,
387 pub copy_symbols_to: Vec<Utf8PathBuf>,
389 pub features: CargoTargetFeatures,
391 pub kind: BinaryKind,
393}
394
395#[derive(Clone, Copy, Debug, PartialEq)]
397pub enum BinaryKind {
398 Executable,
400 DynamicLibrary,
402 StaticLibrary,
404}
405
406#[derive(Debug)]
408#[allow(clippy::large_enum_variant)]
409pub enum BuildStep {
410 Generic(GenericBuildStep),
412 Cargo(CargoBuildStep),
414 Extra(ExtraBuildStep),
416 Rustup(RustupStep),
418 CopyFile(CopyStep),
420 CopyDir(CopyStep),
422 CopyFileOrDir(CopyStep),
424 Zip(ZipDirStep),
426 GenerateInstaller(InstallerImpl),
428 GenerateSourceTarball(SourceTarballStep),
430 Checksum(ChecksumImpl),
432 UnifiedChecksum(UnifiedChecksumStep),
434 OmniborArtifactId(OmniborArtifactIdImpl),
436 Updater(UpdaterStep),
438 }
441
442#[derive(Debug)]
444pub struct CargoBuildStep {
445 pub target_triple: TripleName,
447 pub features: CargoTargetFeatures,
449 pub package: CargoTargetPackages,
451 pub profile: String,
453 pub rustflags: String,
455 pub expected_binaries: Vec<BinaryIdx>,
457 pub working_dir: Utf8PathBuf,
459}
460
461#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
463pub enum CargoBuildWrapper {
464 ZigBuild,
467
468 Xwin,
471}
472
473impl std::fmt::Display for CargoBuildWrapper {
474 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
475 f.pad(match self {
476 CargoBuildWrapper::ZigBuild => "cargo-zigbuild",
477 CargoBuildWrapper::Xwin => "cargo-xwin",
478 })
479 }
480}
481
482pub fn build_wrapper_for_cross(
484 host: &Triple,
485 target: &Triple,
486) -> DistResult<Option<CargoBuildWrapper>> {
487 if host.operating_system == target.operating_system && host.architecture == target.architecture
488 {
489 return Ok(None);
491 }
492
493 match target.operating_system {
494 OperatingSystem::Darwin(_) => match host.operating_system {
496 OperatingSystem::Darwin(_) => {
497 Ok(None)
500 }
501 _ => {
502 Err(DistError::UnsupportedCrossCompile {
503 host: host.clone(),
504 target: target.clone(),
505 details: "cross-compiling to macOS is a road paved with sadness — we cowardly refuse to walk it.".to_string(),
506 })
507 }
508 },
509 OperatingSystem::Linux => match host.operating_system {
511 OperatingSystem::Linux | OperatingSystem::Darwin(_) | OperatingSystem::Windows => {
512 Ok(Some(CargoBuildWrapper::ZigBuild))
514 }
515 _ => {
516 Err(DistError::UnsupportedCrossCompile {
517 host: host.clone(),
518 target: target.clone(),
519 details: format!("no idea how to cross-compile from {host} to linux"),
520 })
521 }
522 },
523 OperatingSystem::Windows => match host.operating_system {
525 OperatingSystem::Windows => {
526 Ok(None)
528 }
529 OperatingSystem::Linux | OperatingSystem::Darwin(_) => {
530 Ok(Some(CargoBuildWrapper::Xwin))
532 }
533 _ => {
534 Err(DistError::UnsupportedCrossCompile {
535 host: host.clone(),
536 target: target.clone(),
537 details: format!("no idea how to cross-compile from {host} to windows with architecture {}", target.architecture),
538 })
539 }
540 },
541 _ => {
542 Err(DistError::UnsupportedCrossCompile {
543 host: host.clone(),
544 target: target.clone(),
545 details: format!("no idea how to cross-compile from anything (including the current host, {host}) to {target}"),
546 })
547 }
548 }
549}
550
551#[derive(Debug)]
553pub struct GenericBuildStep {
554 pub target_triple: TripleName,
556 pub expected_binaries: Vec<BinaryIdx>,
558 pub working_dir: Utf8PathBuf,
560 pub out_dir: Utf8PathBuf,
562 pub build_command: Vec<String>,
564}
565
566#[derive(Debug)]
568pub struct ExtraBuildStep {
569 pub working_dir: Utf8PathBuf,
571 pub artifact_relpaths: Vec<Utf8PathBuf>,
573 pub build_command: Vec<String>,
575}
576
577#[derive(Debug)]
579pub struct RustupStep {
580 pub rustup: Tool,
582 pub target: TripleName,
584}
585
586#[derive(Debug)]
588pub struct ZipDirStep {
589 pub src_path: Utf8PathBuf,
591 pub dest_path: Utf8PathBuf,
593 pub with_root: Option<Utf8PathBuf>,
595 pub zip_style: ZipStyle,
597}
598
599#[derive(Debug)]
601pub struct CopyStep {
602 pub src_path: Utf8PathBuf,
604 pub dest_path: Utf8PathBuf,
606}
607
608#[derive(Debug, Clone)]
610pub struct ChecksumImpl {
611 pub checksum: ChecksumStyle,
613 pub src_path: Utf8PathBuf,
615 pub dest_path: Option<Utf8PathBuf>,
617 pub for_artifact: Option<ArtifactId>,
619}
620
621#[derive(Debug, Clone)]
631pub struct UnifiedChecksumStep {
632 pub checksum: ChecksumStyle,
634
635 pub dest_path: Utf8PathBuf,
637}
638
639#[derive(Debug, Clone)]
641pub struct OmniborArtifactIdImpl {
642 pub src_path: Utf8PathBuf,
644 pub dest_path: Utf8PathBuf,
646}
647
648#[derive(Debug, Clone)]
650pub struct SourceTarballStep {
651 pub committish: String,
653 pub prefix: String,
656 pub target: Utf8PathBuf,
658 pub working_dir: Utf8PathBuf,
660 pub recursive: bool,
662}
663
664#[derive(Debug, Clone)]
666pub struct UpdaterStep {
667 pub target_triple: TripleName,
669 pub target_filename: Utf8PathBuf,
671 pub use_latest: bool,
673}
674
675#[derive(Copy, Clone, Debug)]
677pub enum SymbolKind {
678 Pdb,
680 Dsym,
682 Dwp,
684}
685
686impl SymbolKind {
687 pub fn ext(self) -> &'static str {
689 match self {
690 SymbolKind::Pdb => "pdb",
691 SymbolKind::Dsym => "dSYM",
692 SymbolKind::Dwp => "dwp",
693 }
694 }
695}
696
697#[derive(Clone, Debug)]
699pub struct Artifact {
700 pub id: ArtifactId,
704 pub target_triples: Vec<TripleName>,
708 pub archive: Option<Archive>,
712 pub file_path: Utf8PathBuf,
716 pub required_binaries: FastMap<BinaryIdx, Utf8PathBuf>,
720 pub kind: ArtifactKind,
722 pub checksum: Option<ArtifactIdx>,
724 pub is_global: bool,
726}
727
728#[derive(Clone, Debug)]
731pub struct Archive {
732 pub with_root: Option<Utf8PathBuf>,
735 pub dir_path: Utf8PathBuf,
739 pub zip_style: ZipStyle,
741 pub static_assets: Vec<(StaticAssetKind, Utf8PathBuf)>,
745}
746
747#[derive(Clone, Debug)]
749#[allow(clippy::large_enum_variant)]
750pub enum ArtifactKind {
751 ExecutableZip(ExecutableZip),
753 Symbols(Symbols),
755 Installer(InstallerImpl),
757 Checksum(ChecksumImpl),
759 UnifiedChecksum(UnifiedChecksumStep),
761 SourceTarball(SourceTarball),
763 ExtraArtifact(ExtraArtifactImpl),
765 Updater(UpdaterImpl),
767 SBOM(SBOMImpl),
769 OmniborArtifactId(OmniborArtifactIdImpl),
771}
772
773#[derive(Clone, Debug)]
775pub struct ExecutableZip {
776 }
778
779#[derive(Clone, Debug)]
781pub struct Symbols {
782 kind: SymbolKind,
784}
785
786#[derive(Clone, Debug)]
788pub struct SourceTarball {
789 pub committish: String,
791 pub prefix: String,
794 pub target: Utf8PathBuf,
796 pub working_dir: Utf8PathBuf,
798 pub recursive: bool,
800}
801
802#[derive(Clone, Debug)]
804pub struct ExtraArtifactImpl {
805 pub working_dir: Utf8PathBuf,
807 pub command: Vec<String>,
809 pub artifact_relpath: Utf8PathBuf,
811}
812
813#[derive(Clone, Debug)]
815pub struct UpdaterImpl {
816 pub use_latest: bool,
818}
819
820#[derive(Clone, Debug)]
822pub struct SBOMImpl {}
823
824#[derive(Clone, Debug)]
826pub struct Release {
827 pub app_name: String,
829 pub app_desc: Option<String>,
831 pub app_authors: Vec<String>,
833 pub app_license: Option<String>,
835 pub app_repository_url: Option<String>,
837 pub app_homepage_url: Option<String>,
839 pub app_keywords: Option<Vec<String>>,
841 pub pkg_idx: PackageIdx,
843 pub version: Version,
845 pub id: String,
847 pub config: AppConfig,
849 pub targets: Vec<TripleName>,
851 pub bins: Vec<(PackageIdx, String)>,
855 pub cdylibs: Vec<(PackageIdx, String)>,
860 pub cstaticlibs: Vec<(PackageIdx, String)>,
865 pub global_artifacts: Vec<ArtifactIdx>,
868 pub variants: Vec<ReleaseVariantIdx>,
870 pub changelog_body: Option<String>,
872 pub changelog_title: Option<String>,
874 pub static_assets: Vec<(StaticAssetKind, Utf8PathBuf)>,
876 pub platform_support: PlatformSupport,
879}
880
881#[derive(Debug)]
883pub struct ReleaseVariant {
884 pub target: TripleName,
886 pub id: String,
889 pub binaries: Vec<BinaryIdx>,
891 pub static_assets: Vec<(StaticAssetKind, Utf8PathBuf)>,
893 pub local_artifacts: Vec<ArtifactIdx>,
895}
896
897#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
899pub enum StaticAssetKind {
900 Readme,
902 License,
904 Changelog,
906 Other,
908}
909
910#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
912pub struct CargoTargetFeatures {
913 pub default_features: bool,
915 pub features: CargoTargetFeatureList,
917}
918
919#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
921pub enum CargoTargetFeatureList {
922 All,
924 List(Vec<String>),
926}
927
928impl Default for CargoTargetFeatureList {
929 fn default() -> Self {
930 Self::List(vec![])
931 }
932}
933
934#[derive(Debug)]
936pub enum CargoTargetPackages {
937 Workspace,
939 Package(String),
943}
944
945pub(crate) struct DistGraphBuilder<'pkg_graph> {
946 pub(crate) inner: DistGraph,
947 pub(crate) manifest: DistManifest,
948 pub(crate) workspaces: &'pkg_graph mut WorkspaceGraph,
949 artifact_mode: ArtifactMode,
950 binaries_by_id: FastMap<String, BinaryIdx>,
951 package_configs: Vec<AppConfig>,
952}
953
954impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
955 pub(crate) fn new(
956 system_id: SystemId,
957 tools: Tools,
958 workspaces: &'pkg_graph mut WorkspaceGraph,
959 artifact_mode: ArtifactMode,
960 allow_all_dirty: bool,
961 announcement_tag_is_implicit: bool,
962 ) -> DistResult<Self> {
963 let root_workspace_idx = workspaces.root_workspace_idx();
964 let root_workspace = workspaces.workspace(root_workspace_idx);
965
966 if let Some(dist_manifest_path) = root_workspace.dist_manifest_path.as_deref() {
968 for workspace_idx in workspaces.all_workspace_indices() {
969 if workspace_idx == root_workspace_idx {
970 continue;
971 }
972 let workspace = workspaces.workspace(workspace_idx);
973 config::reject_metadata_table(
974 &workspace.manifest_path,
975 dist_manifest_path,
976 workspace.cargo_metadata_table.as_ref(),
977 )?;
978 }
979 }
980
981 let target_dir = root_workspace.target_dir.clone();
982 let workspace_dir = root_workspace.workspace_dir.clone();
983 let repo_dir = if let Some(repo) = &workspaces.repo {
984 repo.path.to_owned()
985 } else {
986 workspace_dir.clone()
988 };
989 let dist_dir = target_dir.join(TARGET_DIST);
990
991 let mut workspace_metadata =
992 config::parse_metadata_table_or_manifest(
994 &root_workspace.manifest_path,
995 root_workspace.dist_manifest_path.as_deref(),
996 root_workspace.cargo_metadata_table.as_ref(),
997 )?;
998
999 let workspace_layer = workspace_metadata.to_toml_layer(true);
1000 workspace_metadata.make_relative_to(&root_workspace.workspace_dir);
1001
1002 let config = workspace_config(workspaces, workspace_layer.clone());
1003
1004 if config.builds.cargo.rust_toolchain_version.is_some() {
1005 warn!("rust-toolchain-version is deprecated, use rust-toolchain.toml if you want pinned toolchains");
1006 }
1007
1008 let local_builds_are_lies = artifact_mode == ArtifactMode::Lies;
1009
1010 let mut package_metadatas = vec![];
1012 let mut package_configs = vec![];
1013
1014 for (pkg_idx, package) in workspaces.all_packages() {
1015 let mut package_metadata = config::parse_metadata_table_or_manifest(
1016 &package.manifest_path,
1017 package.dist_manifest_path.as_deref(),
1018 package.cargo_metadata_table.as_ref(),
1019 )?;
1020 package_configs.push(app_config(
1021 workspaces,
1022 pkg_idx,
1023 workspace_layer.clone(),
1024 package_metadata.to_toml_layer(false),
1025 ));
1026
1027 package_metadata.make_relative_to(&package.package_root);
1028 package_metadata.merge_workspace_config(&workspace_metadata, &package.manifest_path);
1029 package_metadata.validate_install_paths()?;
1030 package_metadatas.push(package_metadata);
1031 }
1032
1033 let mut global_cargo_build_config = None::<AppCargoBuildConfig>;
1035 let mut packages_with_mismatched_features = vec![];
1036 for ((_idx, package), package_config) in workspaces.all_packages().zip(&package_configs) {
1037 if let Some(cargo_build_config) = &global_cargo_build_config {
1038 if package_config.builds.cargo.features != cargo_build_config.features
1039 || package_config.builds.cargo.all_features != cargo_build_config.all_features
1040 || package_config.builds.cargo.default_features
1041 != cargo_build_config.default_features
1042 {
1043 packages_with_mismatched_features.push(
1044 package
1045 .dist_manifest_path
1046 .clone()
1047 .unwrap_or(package.manifest_path.clone()),
1048 );
1049 }
1050 } else {
1051 global_cargo_build_config = Some(package_config.builds.cargo.clone());
1052 packages_with_mismatched_features.push(
1056 package
1057 .dist_manifest_path
1058 .clone()
1059 .unwrap_or(package.manifest_path.clone()),
1060 );
1061 };
1062 }
1063 let requires_precise = packages_with_mismatched_features.len() > 1;
1065 let precise_cargo_builds = if let Some(precise_builds) = config.builds.cargo.precise_builds
1066 {
1067 if !precise_builds && requires_precise {
1068 return Err(DistError::PreciseImpossible {
1069 packages: packages_with_mismatched_features,
1070 });
1071 }
1072 precise_builds
1073 } else {
1074 info!("force-enabling precise-builds to handle your build features");
1075 requires_precise
1076 };
1077
1078 let mut global_homebrew_tap = None;
1082 let mut packages_with_mismatched_taps = vec![];
1083 for ((_idx, package), package_config) in workspaces.all_packages().zip(&package_configs) {
1084 if let Some(homebrew) = &package_config.installers.homebrew {
1085 if let Some(new_tap) = &homebrew.tap {
1086 if let Some(current_tap) = &global_homebrew_tap {
1087 if current_tap != new_tap {
1088 packages_with_mismatched_taps.push(
1089 package
1090 .dist_manifest_path
1091 .clone()
1092 .unwrap_or(package.manifest_path.clone()),
1093 );
1094 }
1095 } else {
1096 packages_with_mismatched_taps.push(
1100 package
1101 .dist_manifest_path
1102 .clone()
1103 .unwrap_or(package.manifest_path.clone()),
1104 );
1105 global_homebrew_tap = Some(new_tap.clone());
1106 }
1107 }
1108 }
1109 }
1110 if packages_with_mismatched_taps.len() > 1 {
1111 return Err(DistError::MismatchedTaps {
1112 packages: packages_with_mismatched_taps,
1113 });
1114 }
1115
1116 let mut global_publishers = None;
1120 let mut packages_with_mismatched_publishers = vec![];
1121 for ((_idx, package), package_config) in workspaces.all_packages().zip(&package_configs) {
1122 if let Some(cur_publishers) = &global_publishers {
1123 if cur_publishers != &package_config.publishers {
1124 packages_with_mismatched_publishers.push(
1125 package
1126 .dist_manifest_path
1127 .clone()
1128 .unwrap_or(package.manifest_path.clone()),
1129 );
1130 }
1131 } else {
1132 packages_with_mismatched_publishers.push(
1136 package
1137 .dist_manifest_path
1138 .clone()
1139 .unwrap_or(package.manifest_path.clone()),
1140 );
1141 global_publishers = Some(package_config.publishers.clone());
1142 }
1143 }
1144 if packages_with_mismatched_publishers.len() > 1 {
1145 return Err(DistError::MismatchedPublishers {
1146 packages: packages_with_mismatched_publishers,
1147 });
1148 }
1149 let global_publish_prereleases = global_publishers
1150 .as_ref()
1151 .map(|p| {
1152 let PublisherConfig {
1154 homebrew,
1155 npm,
1156 user,
1157 } = p;
1158 let h_pre = homebrew.as_ref().map(|p| p.prereleases);
1159 let npm_pre = npm.as_ref().map(|p| p.prereleases);
1160 let user_pre = user.as_ref().map(|p| p.prereleases);
1161 let choices = [h_pre, npm_pre, user_pre];
1162 let mut global_choice = None;
1163 #[allow(clippy::manual_flatten)]
1164 for choice in choices {
1165 if let Some(choice) = choice {
1166 if let Some(cur_choice) = global_choice {
1167 if cur_choice != choice {
1168 return Err(DistError::MismatchedPrereleases);
1169 }
1170 } else {
1171 global_choice = Some(choice);
1172 }
1173 }
1174 }
1175 Ok(global_choice.unwrap_or(false))
1176 })
1177 .transpose()?
1178 .unwrap_or(false);
1179
1180 let templates = Templates::new()?;
1181 let allow_dirty = if allow_all_dirty {
1182 DirtyMode::AllowAll
1183 } else {
1184 DirtyMode::AllowList(config.allow_dirty.clone())
1185 };
1186 let cargo_version_line = tools.cargo.as_ref().and_then(|c| c.version_line.to_owned());
1187 let build_environment = if local_builds_are_lies {
1188 BuildEnvironment::Indeterminate
1189 } else {
1190 determine_build_environment(&tools.host_target)
1191 };
1192
1193 let system = SystemInfo {
1194 id: system_id.clone(),
1195 cargo_version_line,
1196 build_environment,
1197 };
1198 let systems = SortedMap::from_iter([(system_id.clone(), system)]);
1199
1200 let client_settings = ClientSettings::new();
1201 let axoclient = crate::net::create_axoasset_client(&client_settings)?;
1202
1203 let signer = Signing::new(
1204 &axoclient,
1205 &tools.host_target,
1206 &dist_dir,
1207 config.builds.ssldotcom_windows_sign.clone(),
1208 config.builds.macos_sign,
1209 )?;
1210 let github_attestations = config
1211 .hosts
1212 .github
1213 .as_ref()
1214 .map(|g| g.attestations)
1215 .unwrap_or(false);
1216 let github_attestations_filters = config
1217 .hosts
1218 .github
1219 .as_ref()
1220 .map(|g| g.attestations_filters.clone())
1221 .unwrap_or_default();
1222 let github_attestations_phase = config
1223 .hosts
1224 .github
1225 .as_ref()
1226 .map(|g| g.attestations_phase)
1227 .unwrap_or_default();
1228 let force_latest = config.hosts.force_latest;
1229 Ok(Self {
1230 inner: DistGraph {
1231 system_id,
1232 is_init: config.dist_version.is_some(),
1233 allow_dirty,
1234 global_homebrew_tap,
1235 global_publishers,
1236 precise_cargo_builds,
1237 target_dir,
1238 repo_dir,
1239 workspace_dir,
1240 dist_dir,
1241 config,
1242 signer,
1243 tools,
1244 local_builds_are_lies,
1245 templates,
1246 local_build_steps: vec![],
1247 global_build_steps: vec![],
1248 artifacts: vec![],
1249 binaries: vec![],
1250 variants: vec![],
1251 releases: vec![],
1252 ci: CiInfo::default(),
1253 hosting: None,
1254 client_settings,
1255 axoclient,
1256 },
1257 manifest: DistManifest {
1258 dist_version: Some(env!("CARGO_PKG_VERSION").to_owned()),
1259 system_info: None,
1260 announcement_tag: None,
1261 announcement_is_prerelease: false,
1262 announcement_tag_is_implicit,
1263 announcement_title: None,
1264 announcement_changelog: None,
1265 announcement_github_body: None,
1266 releases: vec![],
1267 artifacts: Default::default(),
1268 systems,
1269 assets: Default::default(),
1270 publish_prereleases: global_publish_prereleases,
1271 force_latest,
1272 ci: None,
1273 linkage: vec![],
1274 upload_files: vec![],
1275 github_attestations,
1276 github_attestations_filters,
1277 github_attestations_phase,
1278 },
1279 package_configs,
1280 workspaces,
1281 binaries_by_id: FastMap::new(),
1282 artifact_mode,
1283 })
1284 }
1285
1286 fn add_release(&mut self, pkg_idx: PackageIdx) -> ReleaseIdx {
1287 let package_info = self.workspaces.package(pkg_idx);
1288 let config = self.package_config(pkg_idx).clone();
1289
1290 let version = package_info.version.as_ref().unwrap().semver().clone();
1291 let app_name = package_info.name.clone();
1292 let app_desc = package_info.description.clone();
1293 let app_authors = package_info.authors.clone();
1294 let app_license = package_info.license.clone();
1295 let app_repository_url = package_info.repository_url.clone();
1296 let app_homepage_url = package_info.homepage_url.clone();
1297 let app_keywords = package_info.keywords.clone();
1298
1299 let mut static_assets = vec![];
1301 if config.artifacts.archives.auto_includes {
1302 if let Some(readme) = &package_info.readme_file {
1303 static_assets.push((StaticAssetKind::Readme, readme.clone()));
1304 }
1305 if let Some(changelog) = &package_info.changelog_file {
1306 static_assets.push((StaticAssetKind::Changelog, changelog.clone()));
1307 }
1308 for license in &package_info.license_files {
1309 static_assets.push((StaticAssetKind::License, license.clone()));
1310 }
1311 }
1312 for static_asset in &config.artifacts.archives.include {
1313 static_assets.push((StaticAssetKind::Other, static_asset.clone()));
1314 }
1315
1316 let platform_support = PlatformSupport::default();
1317 let idx = ReleaseIdx(self.inner.releases.len());
1318 let id = app_name.clone();
1319 info!("added release {id}");
1320 self.inner.releases.push(Release {
1321 app_name,
1322 app_desc,
1323 app_authors,
1324 app_license,
1325 app_repository_url,
1326 app_homepage_url,
1327 app_keywords,
1328 version,
1329 id,
1330 pkg_idx,
1331 global_artifacts: vec![],
1332 bins: vec![],
1333 cdylibs: vec![],
1334 cstaticlibs: vec![],
1335 targets: vec![],
1336 variants: vec![],
1337 changelog_body: None,
1338 changelog_title: None,
1339 config,
1340 static_assets,
1341 platform_support,
1342 });
1343 idx
1344 }
1345
1346 fn add_variant(
1347 &mut self,
1348 to_release: ReleaseIdx,
1349 target: TripleName,
1350 ) -> DistResult<ReleaseVariantIdx> {
1351 let idx = ReleaseVariantIdx(self.inner.variants.len());
1352 let Release {
1353 id: release_id,
1354 variants,
1355 targets,
1356 static_assets,
1357 bins,
1358 cdylibs,
1359 cstaticlibs,
1360 config,
1361 pkg_idx,
1362 ..
1363 } = self.release_mut(to_release);
1364 let static_assets = static_assets.clone();
1365 let variant_id = format!("{release_id}-{target}");
1366 info!("added variant {variant_id}");
1367 let binaries_map = &config.artifacts.archives.binaries;
1368
1369 variants.push(idx);
1370 targets.push(target.clone());
1371
1372 let mapped_bins = binaries_map
1374 .get(target.as_str())
1375 .or_else(|| binaries_map.get("*"));
1376 let mut packageables: Vec<(PackageIdx, String, BinaryKind)> =
1377 if let Some(mapped_bins) = mapped_bins {
1378 mapped_bins
1379 .iter()
1380 .map(|b| (*pkg_idx, b.to_string(), BinaryKind::Executable))
1381 .collect()
1382 } else {
1383 bins.clone()
1384 .into_iter()
1385 .map(|(idx, b)| (idx, b, BinaryKind::Executable))
1386 .collect()
1387 };
1388
1389 if config
1392 .artifacts
1393 .archives
1394 .package_libraries
1395 .contains(&LibraryStyle::CDynamic)
1396 {
1397 let all_dylibs = cdylibs
1398 .clone()
1399 .into_iter()
1400 .map(|(idx, l)| (idx, l, BinaryKind::DynamicLibrary));
1401 packageables = packageables.into_iter().chain(all_dylibs).collect();
1402 }
1403 if config
1404 .artifacts
1405 .archives
1406 .package_libraries
1407 .contains(&LibraryStyle::CStatic)
1408 {
1409 let all_cstaticlibs = cstaticlibs
1410 .clone()
1411 .into_iter()
1412 .map(|(idx, l)| (idx, l, BinaryKind::StaticLibrary));
1413 packageables = packageables.into_iter().chain(all_cstaticlibs).collect();
1414 }
1415
1416 let mut binaries = vec![];
1418 for (pkg_idx, binary_name, kind) in packageables {
1419 let package = self.workspaces.package(pkg_idx);
1420 let package_config = self.package_config(pkg_idx);
1421 let pkg_id = package.cargo_package_id.clone();
1422 let pkg_spec = package.true_name.clone();
1427 let kind_label = match kind {
1428 BinaryKind::Executable => "exe",
1429 BinaryKind::DynamicLibrary => "cdylib",
1430 BinaryKind::StaticLibrary => "cstaticlib",
1431 };
1432 let bin_id = format!("{variant_id}-{kind_label}-{binary_name}");
1434
1435 let idx = if let Some(&idx) = self.binaries_by_id.get(&bin_id) {
1436 idx
1438 } else {
1439 let features = CargoTargetFeatures {
1441 default_features: package_config.builds.cargo.default_features,
1442 features: if package_config.builds.cargo.all_features {
1443 CargoTargetFeatureList::All
1444 } else {
1445 CargoTargetFeatureList::List(package_config.builds.cargo.features.clone())
1446 },
1447 };
1448
1449 let target_is_windows = target.is_windows();
1450 let platform_exe_ext;
1451 let platform_lib_prefix;
1452 if target_is_windows {
1453 platform_exe_ext = ".exe";
1454 platform_lib_prefix = "";
1455 } else {
1456 platform_exe_ext = "";
1457 platform_lib_prefix = "lib";
1458 };
1459
1460 let platform_lib_ext;
1461 let platform_staticlib_ext;
1462 if target_is_windows {
1463 platform_lib_ext = ".dll";
1464 platform_staticlib_ext = ".lib";
1465 } else if target.is_linux() {
1466 platform_lib_ext = ".so";
1467 platform_staticlib_ext = ".a";
1468 } else if target.is_darwin() {
1469 platform_lib_ext = ".dylib";
1470 platform_staticlib_ext = ".a";
1471 } else {
1472 return Err(DistError::UnrecognizedTarget { target });
1473 };
1474
1475 let file_name = match kind {
1476 BinaryKind::Executable => format!("{binary_name}{platform_exe_ext}"),
1477 BinaryKind::DynamicLibrary => {
1478 format!("{platform_lib_prefix}{binary_name}{platform_lib_ext}")
1479 }
1480 BinaryKind::StaticLibrary => {
1481 format!("{platform_lib_prefix}{binary_name}{platform_staticlib_ext}")
1482 }
1483 };
1484
1485 info!("added binary {bin_id}");
1486 let idx = BinaryIdx(self.inner.binaries.len());
1487 let binary = Binary {
1488 id: bin_id.clone(),
1489 pkg_id,
1490 pkg_spec,
1491 pkg_idx,
1492 name: binary_name,
1493 file_name,
1494 target: target.clone(),
1495 copy_exe_to: vec![],
1496 copy_symbols_to: vec![],
1497 symbols_artifact: None,
1498 features,
1499 kind,
1500 };
1501 self.inner.binaries.push(binary);
1502 self.binaries_by_id.insert(bin_id, idx);
1503 idx
1504 };
1505
1506 binaries.push(idx);
1507 }
1508
1509 self.inner.variants.push(ReleaseVariant {
1510 target,
1511 id: variant_id,
1512 local_artifacts: vec![],
1513 binaries,
1514 static_assets,
1515 });
1516 Ok(idx)
1517 }
1518
1519 fn add_binary(&mut self, to_release: ReleaseIdx, pkg_idx: PackageIdx, binary_name: String) {
1520 let release = self.release_mut(to_release);
1521 release.bins.push((pkg_idx, binary_name));
1522 }
1523
1524 fn add_library(&mut self, to_release: ReleaseIdx, pkg_idx: PackageIdx, binary_name: String) {
1525 let release = self.release_mut(to_release);
1526 release.cdylibs.push((pkg_idx, binary_name));
1527 }
1528
1529 fn add_static_library(
1530 &mut self,
1531 to_release: ReleaseIdx,
1532 pkg_idx: PackageIdx,
1533 binary_name: String,
1534 ) {
1535 let release = self.release_mut(to_release);
1536 release.cstaticlibs.push((pkg_idx, binary_name));
1537 }
1538
1539 fn add_executable_zip(&mut self, to_release: ReleaseIdx) {
1540 if !self.local_artifacts_enabled() {
1541 return;
1542 }
1543 info!(
1544 "adding executable zip to release {}",
1545 self.release(to_release).id
1546 );
1547
1548 let release = self.release(to_release);
1550 let variants = release.variants.clone();
1551 let checksum = self.inner.config.artifacts.checksum;
1552 for variant_idx in variants {
1553 let (zip_artifact, built_assets) =
1554 self.make_executable_zip_for_variant(to_release, variant_idx);
1555
1556 let zip_artifact_idx = self.add_local_artifact(variant_idx, zip_artifact);
1557 for (binary, dest_path) in built_assets {
1558 self.require_binary(zip_artifact_idx, variant_idx, binary, dest_path);
1559 }
1560
1561 if checksum != ChecksumStyle::False {
1562 self.add_artifact_checksum(variant_idx, zip_artifact_idx, checksum);
1563 }
1564
1565 if self.inner.config.builds.omnibor {
1566 let omnibor = self.create_omnibor_artifact(zip_artifact_idx, false);
1567 self.add_local_artifact(variant_idx, omnibor);
1568 }
1569 }
1570 }
1571
1572 fn add_extra_artifacts(&mut self, app_config: &AppConfig, to_release: ReleaseIdx) {
1573 if !self.global_artifacts_enabled() {
1574 return;
1575 }
1576 let dist_dir = &self.inner.dist_dir.to_owned();
1577 let artifacts = app_config.artifacts.extra.clone();
1578
1579 for extra in artifacts {
1580 for artifact_relpath in extra.artifact_relpaths {
1581 let artifact_name = ArtifactId::new(
1582 artifact_relpath
1583 .file_name()
1584 .expect("extra artifact had no name!?")
1585 .to_owned(),
1586 );
1587 let target_path = dist_dir.join(artifact_name.as_str());
1588 let artifact = Artifact {
1589 id: artifact_name,
1590 target_triples: vec![],
1591 file_path: target_path.to_owned(),
1592 required_binaries: FastMap::new(),
1593 archive: None,
1594 kind: ArtifactKind::ExtraArtifact(ExtraArtifactImpl {
1595 working_dir: extra.working_dir.clone(),
1596 command: extra.command.clone(),
1597 artifact_relpath,
1598 }),
1599 checksum: None,
1600 is_global: true,
1601 };
1602
1603 self.add_global_artifact(to_release, artifact);
1604 }
1605 }
1606 }
1607
1608 fn add_cyclonedx_sbom_file(&mut self, to_package: PackageIdx, to_release: ReleaseIdx) {
1609 let release = self.release(to_release);
1610
1611 if !self.global_artifacts_enabled() || !release.config.builds.cargo.cargo_cyclonedx {
1612 return;
1613 }
1614
1615 let package = self.workspaces.package(to_package);
1616
1617 let file_name = format!("{}.cdx.xml", package.true_name);
1618 let file_path = Utf8Path::new("target/distrib/").join(file_name.clone());
1619 self.add_global_artifact(
1620 to_release,
1621 Artifact {
1622 id: ArtifactId::new(file_name),
1623 target_triples: Default::default(),
1624 archive: None,
1625 file_path: file_path.clone(),
1626 required_binaries: Default::default(),
1627 kind: ArtifactKind::SBOM(SBOMImpl {}),
1628 checksum: None,
1629 is_global: true,
1630 },
1631 );
1632 }
1633
1634 fn create_omnibor_artifact(&mut self, artifact_idx: ArtifactIdx, is_global: bool) -> Artifact {
1635 let artifact = self.artifact(artifact_idx);
1636 let id = artifact.id.clone();
1637 let src_path = artifact.file_path.clone();
1638
1639 let extension = src_path
1640 .extension()
1641 .map_or("omnibor".to_string(), |e| format!("{e}.omnibor"));
1642 let dest_path = src_path.with_extension(extension);
1643
1644 let new_id = format!("{}.omnibor", id);
1645
1646 Artifact {
1647 id: ArtifactId::new(new_id),
1648 target_triples: Default::default(),
1649 archive: None,
1650 file_path: dest_path.clone(),
1651 required_binaries: Default::default(),
1652 kind: ArtifactKind::OmniborArtifactId(OmniborArtifactIdImpl {
1653 src_path,
1654 dest_path,
1655 }),
1656 checksum: None,
1657 is_global,
1658 }
1659 }
1660
1661 fn add_unified_checksum_file(&mut self, to_release: ReleaseIdx) {
1662 if !self.global_artifacts_enabled() {
1663 return;
1664 }
1665
1666 let dist_dir = &self.inner.dist_dir;
1667 let checksum = self.inner.config.artifacts.checksum;
1668 let file_name = ArtifactId::new(format!("{}.sum", checksum.ext()));
1669 let file_path = dist_dir.join(file_name.as_str());
1670
1671 self.add_global_artifact(
1672 to_release,
1673 Artifact {
1674 id: file_name,
1675 target_triples: Default::default(),
1676 archive: None,
1677 file_path: file_path.clone(),
1678 required_binaries: Default::default(),
1679 kind: ArtifactKind::UnifiedChecksum(UnifiedChecksumStep {
1680 checksum,
1681 dest_path: file_path,
1682 }),
1683 checksum: None, is_global: true,
1685 },
1686 );
1687 }
1688
1689 fn add_source_tarball(&mut self, _tag: &str, to_release: ReleaseIdx) {
1690 if !self.global_artifacts_enabled() {
1691 return;
1692 }
1693
1694 if !self.inner.config.artifacts.source_tarball {
1695 return;
1696 }
1697
1698 if self.inner.tools.git.is_none() {
1699 warn!("skipping source tarball; git not installed");
1700 return;
1701 }
1702
1703 let working_dir = self.inner.workspace_dir.clone();
1704
1705 let workspace_repo = &self.workspaces.repo;
1706
1707 let is_git_repo = if self.inner.local_builds_are_lies {
1709 true
1710 } else {
1711 workspace_repo.is_some()
1712 };
1713
1714 let has_head = if self.inner.local_builds_are_lies {
1715 true
1716 } else if let Some(repo) = workspace_repo {
1717 repo.head.is_some()
1718 } else {
1719 false
1720 };
1721
1722 if !is_git_repo {
1723 warn!(
1724 "skipping source tarball; no git repo found at {}",
1725 self.inner.workspace_dir
1726 );
1727 return;
1728 }
1729
1730 if !has_head {
1731 warn!(
1732 "skipping source tarball; git repo at {} has no commits yet",
1733 self.inner.workspace_dir
1734 );
1735 return;
1736 }
1737
1738 let release = self.release(to_release);
1739 let checksum = self.inner.config.artifacts.checksum;
1740 info!("adding source tarball to release {}", release.id);
1741
1742 let dist_dir = &self.inner.dist_dir.to_owned();
1743
1744 let artifact_name = ArtifactId::new("source.tar.gz".to_owned());
1745 let target_path = dist_dir.join(artifact_name.as_str());
1746 let prefix = format!("{}-{}/", release.app_name, release.version);
1747 let recursive = self.inner.config.artifacts.recursive_tarball;
1748
1749 let artifact = Artifact {
1750 id: artifact_name.to_owned(),
1751 target_triples: vec![],
1752 file_path: target_path.to_owned(),
1753 required_binaries: FastMap::new(),
1754 archive: None,
1755 kind: ArtifactKind::SourceTarball(SourceTarball {
1756 committish: "HEAD".to_owned(),
1761 prefix,
1762 target: target_path.to_owned(),
1763 working_dir,
1764 recursive,
1765 }),
1766 checksum: None,
1767 is_global: true,
1768 };
1769
1770 let for_artifact = Some(artifact.id.clone());
1771 let artifact_idx = self.add_global_artifact(to_release, artifact);
1772
1773 if checksum != ChecksumStyle::False {
1774 let checksum_id = ArtifactId::new(format!("{artifact_name}.{}", checksum.ext()));
1775 let checksum_path = dist_dir.join(checksum_id.as_str());
1776 let checksum = Artifact {
1777 id: checksum_id.to_owned(),
1778 target_triples: vec![],
1779 file_path: checksum_path.to_owned(),
1780 required_binaries: FastMap::new(),
1781 archive: None,
1782 kind: ArtifactKind::Checksum(ChecksumImpl {
1783 checksum,
1784 src_path: target_path,
1785 dest_path: Some(checksum_path),
1786 for_artifact,
1787 }),
1788 checksum: None,
1789 is_global: true,
1790 };
1791
1792 let checksum_idx = self.add_global_artifact(to_release, checksum);
1793 self.artifact_mut(artifact_idx).checksum = Some(checksum_idx);
1794 }
1795
1796 if self.inner.config.builds.omnibor {
1797 let omnibor = self.create_omnibor_artifact(artifact_idx, true);
1798 self.add_global_artifact(to_release, omnibor);
1799 }
1800 }
1801
1802 fn add_artifact_checksum(
1803 &mut self,
1804 to_variant: ReleaseVariantIdx,
1805 artifact_idx: ArtifactIdx,
1806 checksum: ChecksumStyle,
1807 ) -> ArtifactIdx {
1808 let artifact = self.artifact(artifact_idx);
1809 let checksum_artifact = {
1810 let checksum_ext = checksum.ext();
1811 let checksum_id = ArtifactId::new(format!("{}.{}", artifact.id, checksum_ext));
1812 let checksum_path = artifact
1813 .file_path
1814 .parent()
1815 .unwrap()
1816 .join(checksum_id.as_str());
1817 Artifact {
1818 id: checksum_id,
1819 kind: ArtifactKind::Checksum(ChecksumImpl {
1820 checksum,
1821 src_path: artifact.file_path.clone(),
1822 dest_path: Some(checksum_path.clone()),
1823 for_artifact: Some(artifact.id.clone()),
1824 }),
1825
1826 target_triples: artifact.target_triples.clone(),
1827 archive: None,
1828 file_path: checksum_path,
1829 required_binaries: Default::default(),
1830 checksum: None,
1832 is_global: false,
1833 }
1834 };
1835 let checksum_idx = self.add_local_artifact(to_variant, checksum_artifact);
1836 self.artifact_mut(artifact_idx).checksum = Some(checksum_idx);
1837 checksum_idx
1838 }
1839
1840 fn add_updater(&mut self, variant_idx: ReleaseVariantIdx) {
1841 if !self.local_artifacts_enabled() {
1842 return;
1843 }
1844
1845 let artifact = self.make_updater_for_variant(variant_idx);
1846
1847 self.add_local_artifact(variant_idx, artifact);
1851 }
1852
1853 pub(crate) fn make_updater_for_variant(&self, variant_idx: ReleaseVariantIdx) -> Artifact {
1854 let variant = self.variant(variant_idx);
1855 let filename = ArtifactId::new(format!("{}-update", variant.id));
1856 let target_path = &self.inner.dist_dir.to_owned().join(filename.as_str());
1857
1858 Artifact {
1859 id: filename.to_owned(),
1860 target_triples: vec![variant.target.to_owned()],
1861 file_path: target_path.to_owned(),
1862 required_binaries: FastMap::new(),
1863 archive: None,
1864 kind: ArtifactKind::Updater(UpdaterImpl {
1865 use_latest: self.inner.config.installers.always_use_latest_updater,
1866 }),
1867 checksum: None,
1868 is_global: false,
1869 }
1870 }
1871
1872 pub(crate) fn make_executable_zip_for_variant(
1876 &self,
1877 release_idx: ReleaseIdx,
1878 variant_idx: ReleaseVariantIdx,
1879 ) -> (Artifact, Vec<(BinaryIdx, Utf8PathBuf)>) {
1880 let dist_dir = &self.inner.dist_dir;
1882 let release = self.release(release_idx);
1883 let variant = self.variant(variant_idx);
1884
1885 let target_is_windows = variant.target.is_windows();
1886 let zip_style = if target_is_windows {
1887 release.config.artifacts.archives.windows_archive
1888 } else {
1889 release.config.artifacts.archives.unix_archive
1890 };
1891
1892 let artifact_dir_name = variant.id.clone();
1893 let artifact_dir_path = dist_dir.join(&artifact_dir_name);
1894 let artifact_ext = zip_style.ext();
1895 let artifact_name = ArtifactId::new(format!("{artifact_dir_name}{artifact_ext}"));
1896 let artifact_path = dist_dir.join(artifact_name.as_str());
1897
1898 let static_assets = variant.static_assets.clone();
1899 let mut built_assets = Vec::new();
1900 for &binary_idx in &variant.binaries {
1901 let binary = self.binary(binary_idx);
1902 built_assets.push((binary_idx, artifact_dir_path.join(&binary.file_name)));
1903 }
1904
1905 let with_root = if let ZipStyle::Zip = zip_style {
1909 None
1910 } else {
1911 Some(Utf8PathBuf::from(artifact_dir_name.clone()))
1912 };
1913
1914 (
1915 Artifact {
1916 id: artifact_name,
1917 target_triples: vec![variant.target.clone()],
1918 file_path: artifact_path,
1919 required_binaries: FastMap::new(),
1920 archive: Some(Archive {
1921 with_root,
1922 dir_path: artifact_dir_path,
1923 zip_style,
1924 static_assets,
1925 }),
1926 kind: ArtifactKind::ExecutableZip(ExecutableZip {}),
1927 checksum: None,
1929 is_global: false,
1930 },
1931 built_assets,
1932 )
1933 }
1934
1935 fn require_binary(
1947 &mut self,
1948 for_artifact: ArtifactIdx,
1949 for_variant: ReleaseVariantIdx,
1950 binary_idx: BinaryIdx,
1951 dest_path: Utf8PathBuf,
1952 ) {
1953 let dist_dir = self.inner.dist_dir.clone();
1954 let binary = self.binary_mut(binary_idx);
1955
1956 binary.copy_exe_to.push(dest_path.clone());
1958
1959 if binary.symbols_artifact.is_none() {
1961 if let Some(symbol_kind) = target_symbol_kind(&binary.target) {
1962 let dest_symbol_ext = symbol_kind.ext();
1972 let binary_id = &binary.id;
1974 let dest_symbol_name = ArtifactId::new(format!("{binary_id}.{dest_symbol_ext}"));
1976 let artifact_path = dist_dir.join(dest_symbol_name.as_str());
1977
1978 let artifact = Artifact {
1979 id: dest_symbol_name,
1980 target_triples: vec![binary.target.clone()],
1981 archive: None,
1982 file_path: artifact_path.clone(),
1983 required_binaries: FastMap::new(),
1984 kind: ArtifactKind::Symbols(Symbols { kind: symbol_kind }),
1985 checksum: None,
1986 is_global: false,
1987 };
1988
1989 let sym_artifact = self.add_local_artifact(for_variant, artifact);
1996
1997 let binary = self.binary_mut(binary_idx);
1999 binary.symbols_artifact = Some(sym_artifact);
2000 binary.copy_symbols_to.push(artifact_path);
2001 }
2002 }
2003
2004 self.artifact_mut(for_artifact)
2006 .required_binaries
2007 .insert(binary_idx, dest_path);
2008 }
2009
2010 fn add_shell_installer(&mut self, to_release: ReleaseIdx) -> DistResult<()> {
2011 if !self.global_artifacts_enabled() {
2012 return Ok(());
2013 }
2014 let release = self.release(to_release);
2015 let Some(config) = &release.config.installers.shell else {
2016 return Ok(());
2017 };
2018 require_nonempty_installer(release, config)?;
2019 let release_id = &release.id;
2020 let schema_release = self
2021 .manifest
2022 .release_by_name(&release.app_name)
2023 .expect("couldn't find the release!?");
2024
2025 let env_vars = schema_release.env.clone();
2026
2027 let download_urls = schema_release
2028 .artifact_download_urls()
2029 .expect("couldn't compute a URL to download artifacts from!?");
2030 let hosting = schema_release.hosting.clone();
2031 let artifact_name = ArtifactId::new(format!("{release_id}-installer.sh"));
2032 let artifact_path = self.inner.dist_dir.join(artifact_name.as_str());
2033 let best_download_url = download_urls
2034 .first()
2035 .expect("returned empty list of artifact URLs!?");
2036 let installer_url = format!("{best_download_url}/{artifact_name}");
2037 let hint = format!("curl --proto '=https' --tlsv1.2 -LsSf {installer_url} | sh");
2038 let desc = "Install prebuilt binaries via shell script".to_owned();
2039
2040 let artifacts = release
2042 .platform_support
2043 .fragments()
2044 .into_iter()
2045 .filter(|a| !a.target_triple.is_windows_msvc())
2046 .collect::<Vec<_>>();
2047 let target_triples = artifacts
2048 .iter()
2049 .map(|a| a.target_triple.clone())
2050 .collect::<Vec<_>>();
2051
2052 if artifacts.is_empty() {
2053 warn!("skipping shell installer: not building any supported platforms (use --artifacts=global)");
2054 return Ok(());
2055 };
2056 let bin_aliases = BinaryAliases(config.bin_aliases.clone()).for_targets(&target_triples);
2057
2058 let runtime_conditions = release.platform_support.safe_conflated_runtime_conditions();
2059
2060 let installer_artifact = Artifact {
2061 id: artifact_name,
2062 target_triples,
2063 archive: None,
2064 file_path: artifact_path.clone(),
2065 required_binaries: FastMap::new(),
2066 checksum: None,
2067 kind: ArtifactKind::Installer(InstallerImpl::Shell(InstallerInfo {
2068 release: to_release,
2069 dest_path: artifact_path,
2070 app_name: release.app_name.clone(),
2071 app_version: release.version.to_string(),
2072 install_paths: config
2073 .install_path
2074 .iter()
2075 .map(|p| p.clone().into_jinja())
2076 .collect(),
2077 install_success_msg: config.install_success_msg.to_owned(),
2078 base_urls: download_urls.to_owned(),
2079 hosting,
2080 artifacts,
2081 hint,
2082 desc,
2083 receipt: InstallReceipt::from_metadata(&self.inner, release)?,
2084 bin_aliases,
2085 install_libraries: config.install_libraries.clone(),
2086 runtime_conditions,
2087 platform_support: None,
2088 env_vars,
2089 })),
2090 is_global: true,
2091 };
2092
2093 self.add_global_artifact(to_release, installer_artifact);
2094 Ok(())
2095 }
2096
2097 fn add_homebrew_installer(&mut self, to_release: ReleaseIdx) -> DistResult<()> {
2098 if !self.global_artifacts_enabled() {
2099 return Ok(());
2100 }
2101 let release = self.release(to_release);
2102 let Some(config) = &release.config.installers.homebrew else {
2103 return Ok(());
2104 };
2105 require_nonempty_installer(release, config)?;
2106 let formula = if let Some(formula) = &config.formula {
2107 formula
2108 } else {
2109 &release.id
2110 };
2111 let schema_release = self
2112 .manifest
2113 .release_by_name(&release.id)
2114 .expect("couldn't find the release!?");
2115 let download_urls = schema_release
2116 .artifact_download_urls()
2117 .expect("couldn't compute a URL to download artifacts from!?");
2118 let hosting = schema_release.hosting.clone();
2119
2120 let artifact_name = ArtifactId::new(format!("{formula}.rb"));
2121 let artifact_path = self.inner.dist_dir.join(artifact_name.as_str());
2122
2123 let install_target = if let Some(tap) = &self.inner.global_homebrew_tap {
2125 let tap = tap.replace("/homebrew-", "/");
2127 format!("{tap}/{formula}")
2128 } else {
2129 formula.clone()
2130 };
2131
2132 let hint = format!("brew install {}", install_target);
2133 let desc = "Install prebuilt binaries via Homebrew".to_owned();
2134
2135 let artifacts = release
2136 .platform_support
2137 .fragments()
2138 .into_iter()
2139 .filter(|a| !a.target_triple.is_windows_msvc())
2140 .collect::<Vec<_>>();
2141 if artifacts.is_empty() {
2142 warn!("skipping Homebrew installer: not building any supported platforms (use --artifacts=global)");
2143 return Ok(());
2144 };
2145
2146 let target_triples = artifacts
2147 .iter()
2148 .map(|a| a.target_triple.clone())
2149 .collect::<Vec<_>>();
2150
2151 let find_fragment = |triple: &TripleNameRef| -> Option<ExecutableZipFragment> {
2152 artifacts
2153 .iter()
2154 .find(|a| a.target_triple == triple)
2155 .cloned()
2156 };
2157 let fragments = HomebrewFragments {
2158 x86_64_macos: find_fragment(TARGET_X64_MAC),
2159 arm64_macos: find_fragment(TARGET_ARM64_MAC),
2160 x86_64_linux: find_fragment(TARGET_X64_LINUX_GNU),
2161 arm64_linux: find_fragment(TARGET_ARM64_LINUX_GNU),
2162 };
2163
2164 let release = self.release(to_release);
2165 let app_name = release.app_name.clone();
2166 let app_desc = release.app_desc.clone().unwrap_or_else(|| {
2167 warn!("The Homebrew publish job is enabled but no description was specified\n consider adding `description = ` to package in Cargo.toml");
2168 format!("The {} application", release.app_name)
2169 });
2170 let app_license = release.app_license.clone();
2171 let homebrew_dsl_license = app_license.as_ref().map(|app_license| {
2172 to_homebrew_license_format(app_license).unwrap_or(format!("\"{app_license}\""))
2175 });
2176 let app_homepage_url = if release.app_homepage_url.is_none() {
2177 warn!("The Homebrew publish job is enabled but no homepage was specified\n consider adding `homepage = ` to package in Cargo.toml");
2178 release.app_repository_url.clone()
2179 } else {
2180 release.app_homepage_url.clone()
2181 };
2182 let tap = config.tap.clone();
2183
2184 if tap.is_some() && release.config.publishers.homebrew.is_none() {
2185 warn!("A Homebrew tap was specified but the Homebrew publish job is disabled\n consider adding \"homebrew\" to publish-jobs in Cargo.toml");
2186 }
2187 if release.config.publishers.homebrew.is_some() && tap.is_none() {
2188 warn!("The Homebrew publish job is enabled but no tap was specified\n consider setting the tap field in Cargo.toml");
2189 }
2190
2191 let runtime_conditions = release.platform_support.safe_conflated_runtime_conditions();
2192
2193 let dependencies: Vec<HomebrewPackageName> = release
2194 .config
2195 .builds
2196 .system_dependencies
2197 .homebrew
2198 .clone()
2199 .into_iter()
2200 .filter(|(_, package)| package.0.stage_wanted(&DependencyKind::Run))
2201 .map(|(name, _)| name)
2202 .collect();
2203 let bin_aliases = BinaryAliases(config.bin_aliases.clone()).for_targets(&target_triples);
2204
2205 let inner = InstallerInfo {
2206 release: to_release,
2207 dest_path: artifact_path.clone(),
2208 app_name: release.app_name.clone(),
2209 app_version: release.version.to_string(),
2210 install_paths: config
2211 .install_path
2212 .iter()
2213 .map(|p| p.clone().into_jinja())
2214 .collect(),
2215 install_success_msg: config.install_success_msg.to_owned(),
2216 base_urls: download_urls,
2217 hosting,
2218 artifacts,
2219 hint,
2220 desc,
2221 receipt: None,
2222 bin_aliases,
2223 install_libraries: config.install_libraries.clone(),
2224 runtime_conditions,
2225 platform_support: None,
2226 env_vars: None,
2228 };
2229
2230 let installer_artifact = Artifact {
2231 id: artifact_name,
2232 target_triples,
2233 archive: None,
2234 file_path: artifact_path,
2235 required_binaries: Default::default(),
2236 checksum: None,
2237 kind: ArtifactKind::Installer(InstallerImpl::Homebrew(HomebrewImpl {
2238 info: HomebrewInstallerInfo {
2239 name: app_name,
2240 formula_class: to_class_case(formula),
2241 desc: app_desc,
2242 license: homebrew_dsl_license,
2243 homepage: app_homepage_url,
2244 tap,
2245 dependencies,
2246 inner,
2247 install_libraries: config.install_libraries.clone(),
2248 },
2249 fragments,
2250 })),
2251 is_global: true,
2252 };
2253
2254 self.add_global_artifact(to_release, installer_artifact);
2255 Ok(())
2256 }
2257
2258 fn add_powershell_installer(&mut self, to_release: ReleaseIdx) -> DistResult<()> {
2259 if !self.global_artifacts_enabled() {
2260 return Ok(());
2261 }
2262
2263 let release = self.release(to_release);
2265 let Some(config) = &release.config.installers.powershell else {
2266 return Ok(());
2267 };
2268 require_nonempty_installer(release, config)?;
2269 let release_id = &release.id;
2270 let schema_release = self
2271 .manifest
2272 .release_by_name(&release.app_name)
2273 .expect("couldn't find the release!?");
2274
2275 let env_vars = schema_release.env.clone();
2276
2277 let download_urls = schema_release
2278 .artifact_download_urls()
2279 .expect("couldn't compute a URL to download artifacts from!?");
2280 let hosting = schema_release.hosting.clone();
2281 let artifact_name = ArtifactId::new(format!("{release_id}-installer.ps1"));
2282 let artifact_path = self.inner.dist_dir.join(artifact_name.as_str());
2283 let best_download_url = download_urls
2284 .first()
2285 .expect("returned empty list of artifact URLs!?");
2286 let installer_url = format!("{best_download_url}/{artifact_name}");
2287 let hint = format!(r#"powershell -ExecutionPolicy Bypass -c "irm {installer_url} | iex""#);
2288 let desc = "Install prebuilt binaries via powershell script".to_owned();
2289
2290 let artifacts = release
2292 .platform_support
2293 .fragments()
2294 .into_iter()
2295 .filter(|a| a.target_triple.is_windows())
2296 .collect::<Vec<_>>();
2297 let target_triples = artifacts
2298 .iter()
2299 .map(|a| a.target_triple.clone())
2300 .collect::<Vec<_>>();
2301 if artifacts.is_empty() {
2302 warn!("skipping powershell installer: not building any supported platforms (use --artifacts=global)");
2303 return Ok(());
2304 };
2305 let bin_aliases = BinaryAliases(config.bin_aliases.clone()).for_targets(&target_triples);
2306 let installer_artifact = Artifact {
2307 id: artifact_name,
2308 target_triples,
2309 file_path: artifact_path.clone(),
2310 required_binaries: FastMap::new(),
2311 archive: None,
2312 checksum: None,
2313 kind: ArtifactKind::Installer(InstallerImpl::Powershell(InstallerInfo {
2314 release: to_release,
2315 dest_path: artifact_path,
2316 app_name: release.app_name.clone(),
2317 app_version: release.version.to_string(),
2318 install_paths: config
2319 .install_path
2320 .iter()
2321 .map(|p| p.clone().into_jinja())
2322 .collect(),
2323 install_success_msg: config.install_success_msg.to_owned(),
2324 base_urls: download_urls,
2325 hosting,
2326 artifacts,
2327 hint,
2328 desc,
2329 receipt: InstallReceipt::from_metadata(&self.inner, release)?,
2330 bin_aliases,
2331 install_libraries: config.install_libraries.clone(),
2332 runtime_conditions: RuntimeConditions::default(),
2333 platform_support: None,
2334 env_vars,
2335 })),
2336 is_global: true,
2337 };
2338
2339 self.add_global_artifact(to_release, installer_artifact);
2340 Ok(())
2341 }
2342
2343 fn add_npm_installer(&mut self, to_release: ReleaseIdx) -> DistResult<()> {
2344 if !self.global_artifacts_enabled() {
2345 return Ok(());
2346 }
2347 let release = self.release(to_release);
2348 let Some(config) = &release.config.installers.npm else {
2349 return Ok(());
2350 };
2351 require_nonempty_installer(release, config)?;
2352 let release_id = &release.id;
2353 let schema_release = self
2354 .manifest
2355 .release_by_name(&release.app_name)
2356 .expect("couldn't find the release!?");
2357 let download_urls = schema_release
2358 .artifact_download_urls()
2359 .expect("couldn't compute a URL to download artifacts from!?");
2360 let hosting = schema_release.hosting.clone();
2361
2362 let app_name = config.package.clone();
2363 let npm_package_name = if let Some(scope) = &config.scope {
2364 if scope.to_ascii_lowercase() != *scope {
2365 return Err(DistError::ScopeMustBeLowercase {
2366 scope: scope.to_owned(),
2367 });
2368 }
2369
2370 format!("{scope}/{}", app_name)
2371 } else {
2372 app_name.clone()
2373 };
2374 let npm_package_version = release.version.to_string();
2375 let npm_package_desc = release.app_desc.clone();
2376 let npm_package_authors = release.app_authors.clone();
2377 let npm_package_license = release.app_license.clone();
2378 let npm_package_repository_url = release.app_repository_url.clone();
2379 let npm_package_homepage_url = release.app_homepage_url.clone();
2380 let npm_package_keywords = release.app_keywords.clone();
2381
2382 let static_assets = release.static_assets.clone();
2383 let dir_name = format!("{release_id}-npm-package");
2384 let dir_path = self.inner.dist_dir.join(&dir_name);
2385 let zip_style = ZipStyle::Tar(CompressionImpl::Gzip);
2386 let zip_ext = zip_style.ext();
2387 let artifact_name = ArtifactId::new(format!("{dir_name}{zip_ext}"));
2388 let artifact_path = self.inner.dist_dir.join(artifact_name.as_str());
2389 let hint = format!("npm install {npm_package_name}@{npm_package_version}");
2391 let desc = "Install prebuilt binaries into your npm project".to_owned();
2392
2393 let artifacts = release.platform_support.fragments();
2394 let target_triples = artifacts
2395 .iter()
2396 .map(|a| a.target_triple.clone())
2397 .collect::<Vec<_>>();
2398
2399 if artifacts.is_empty() {
2400 warn!("skipping npm installer: not building any supported platforms (use --artifacts=global)");
2401 return Ok(());
2402 };
2403 let bin_aliases = BinaryAliases(config.bin_aliases.clone()).for_targets(&target_triples);
2404
2405 let runtime_conditions = release.platform_support.safe_conflated_runtime_conditions();
2406
2407 let installer_artifact = Artifact {
2408 id: artifact_name,
2409 target_triples,
2410 archive: Some(Archive {
2411 with_root: Some("package".into()),
2413 dir_path: dir_path.clone(),
2414 zip_style,
2415 static_assets,
2416 }),
2417 file_path: artifact_path.clone(),
2418 required_binaries: FastMap::new(),
2419 checksum: None,
2420 kind: ArtifactKind::Installer(InstallerImpl::Npm(NpmInstallerInfo {
2421 npm_package_name,
2422 npm_package_version,
2423 npm_package_desc,
2424 npm_package_authors,
2425 npm_package_license,
2426 npm_package_repository_url,
2427 npm_package_homepage_url,
2428 npm_package_keywords,
2429 create_shrinkwrap: config.shrinkwrap,
2430 package_dir: dir_path,
2431 inner: InstallerInfo {
2432 release: to_release,
2433 dest_path: artifact_path,
2434 app_name,
2435 app_version: release.version.to_string(),
2436 install_paths: config
2437 .install_path
2438 .iter()
2439 .map(|p| p.clone().into_jinja())
2440 .collect(),
2441 install_success_msg: config.install_success_msg.to_owned(),
2442 base_urls: download_urls,
2443 hosting,
2444 artifacts,
2445 hint,
2446 desc,
2447 receipt: None,
2448 bin_aliases,
2449 install_libraries: config.install_libraries.clone(),
2450 runtime_conditions,
2451 platform_support: None,
2452 env_vars: None,
2454 },
2455 })),
2456 is_global: true,
2457 };
2458
2459 self.add_global_artifact(to_release, installer_artifact);
2460 Ok(())
2461 }
2462
2463 fn add_msi_installer(&mut self, to_release: ReleaseIdx) -> DistResult<()> {
2464 if !self.local_artifacts_enabled() {
2465 return Ok(());
2466 }
2467
2468 let release = self.release(to_release);
2470 let Some(_config) = &release.config.installers.msi else {
2474 return Ok(());
2475 };
2476 let variants = release.variants.clone();
2479 let checksum = self.inner.config.artifacts.checksum;
2480
2481 for variant_idx in variants {
2483 let variant = self.variant(variant_idx);
2484 let binaries = variant.binaries.clone();
2485 let target = &variant.target;
2486 if !target.is_windows() {
2487 continue;
2488 }
2489
2490 let variant_id = &variant.id;
2491 let artifact_name = ArtifactId::new(format!("{variant_id}.msi"));
2492 let artifact_path = self.inner.dist_dir.join(artifact_name.as_str());
2493 let dir_name = format!("{variant_id}_msi");
2494 let dir_path = self.inner.dist_dir.join(&dir_name);
2495
2496 let mut package_info: Option<(String, PackageIdx)> = None;
2498 for &binary_idx in &binaries {
2499 let binary = self.binary(binary_idx);
2500 if let Some((existing_spec, _)) = &package_info {
2501 if existing_spec != &binary.pkg_spec {
2503 return Err(DistError::MultiPackage {
2504 artifact_name,
2505 spec1: existing_spec.clone(),
2506 spec2: binary.pkg_spec.clone(),
2507 })?;
2508 }
2509 } else {
2510 package_info = Some((binary.pkg_spec.clone(), binary.pkg_idx));
2511 }
2512 }
2513 let Some((pkg_spec, pkg_idx)) = package_info else {
2514 return Err(DistError::NoPackage { artifact_name })?;
2515 };
2516 let manifest_path = self.workspaces.package(pkg_idx).manifest_path.clone();
2517 let wxs_path = manifest_path
2518 .parent()
2519 .expect("Cargo.toml had no parent dir!?")
2520 .join("wix")
2521 .join("main.wxs");
2522
2523 let installer_artifact = Artifact {
2525 id: artifact_name,
2526 target_triples: vec![target.clone()],
2527 file_path: artifact_path.clone(),
2528 required_binaries: FastMap::new(),
2529 archive: Some(Archive {
2530 with_root: None,
2531 dir_path: dir_path.clone(),
2532 zip_style: ZipStyle::TempDir,
2533 static_assets: vec![],
2534 }),
2535 checksum: None,
2536 kind: ArtifactKind::Installer(InstallerImpl::Msi(MsiInstallerInfo {
2537 package_dir: dir_path.clone(),
2538 pkg_spec,
2539 target: target.clone(),
2540 file_path: artifact_path.clone(),
2541 wxs_path,
2542 manifest_path,
2543 })),
2544 is_global: false,
2545 };
2546
2547 let installer_idx = self.add_local_artifact(variant_idx, installer_artifact);
2549 for binary_idx in binaries {
2550 let binary = self.binary(binary_idx);
2551 self.require_binary(
2552 installer_idx,
2553 variant_idx,
2554 binary_idx,
2555 dir_path.join(&binary.file_name),
2556 );
2557 }
2558 if checksum != ChecksumStyle::False {
2559 self.add_artifact_checksum(variant_idx, installer_idx, checksum);
2560 }
2561 if self.inner.config.builds.omnibor {
2562 let omnibor = self.create_omnibor_artifact(installer_idx, false);
2563 self.add_local_artifact(variant_idx, omnibor);
2564 }
2565 }
2566
2567 Ok(())
2568 }
2569
2570 fn add_pkg_installer(&mut self, to_release: ReleaseIdx) -> DistResult<()> {
2571 if !self.local_artifacts_enabled() {
2572 return Ok(());
2573 }
2574
2575 let release = self.release(to_release);
2577 let Some(config) = release.config.installers.pkg.clone() else {
2578 return Ok(());
2579 };
2580 require_nonempty_installer(release, &config)?;
2581 let version = release.version.clone();
2582 let fragments = release.platform_support.fragments();
2583
2584 let variants = release.variants.clone();
2585 let checksum = self.inner.config.artifacts.checksum;
2586
2587 for variant_idx in variants {
2589 let variant = self.variant(variant_idx);
2590 let binaries = variant.binaries.clone();
2591 let bin_aliases = BinaryAliases(config.bin_aliases.clone());
2592 let target = &variant.target;
2593 if !target.is_darwin() {
2594 continue;
2595 }
2596
2597 let variant_id = &variant.id;
2598 let artifact_name = ArtifactId::new(format!("{variant_id}.pkg"));
2599 let artifact_path = self.inner.dist_dir.join(artifact_name.as_str());
2600 let dir_name = format!("{variant_id}_pkg");
2601 let dir_path = self.inner.dist_dir.join(&dir_name);
2602
2603 let mut package_info: Option<(String, PackageIdx)> = None;
2605 for &binary_idx in &binaries {
2606 let binary = self.binary(binary_idx);
2607 if let Some((existing_spec, _)) = &package_info {
2608 if existing_spec != &binary.pkg_spec {
2610 return Err(DistError::MultiPackage {
2611 artifact_name,
2612 spec1: existing_spec.clone(),
2613 spec2: binary.pkg_spec.clone(),
2614 })?;
2615 }
2616 } else {
2617 package_info = Some((binary.pkg_spec.clone(), binary.pkg_idx));
2618 }
2619 }
2620
2621 let Some(artifact) = fragments
2622 .clone()
2623 .into_iter()
2624 .find(|a| a.target_triple == variant.target)
2625 else {
2626 return Err(DistError::NoPackage { artifact_name })?;
2627 };
2628
2629 let bin_aliases = bin_aliases.for_target(&variant.target);
2630
2631 let identifier = if let Some(id) = &config.identifier {
2632 id.to_owned()
2633 } else {
2634 return Err(DistError::MacPkgBundleIdentifierMissing {});
2635 };
2636
2637 let installer_artifact = Artifact {
2639 id: artifact_name,
2640 target_triples: vec![target.clone()],
2641 file_path: artifact_path.clone(),
2642 required_binaries: FastMap::new(),
2643 archive: Some(Archive {
2644 with_root: None,
2645 dir_path: dir_path.clone(),
2646 zip_style: ZipStyle::TempDir,
2647 static_assets: vec![],
2648 }),
2649 checksum: None,
2650 kind: ArtifactKind::Installer(InstallerImpl::Pkg(PkgInstallerInfo {
2651 file_path: artifact_path.clone(),
2652 artifact,
2653 package_dir: dir_path.clone(),
2654 identifier,
2655 install_location: config.install_location.clone(),
2656 version: version.to_string(),
2657 bin_aliases,
2658 })),
2659 is_global: false,
2660 };
2661
2662 let installer_idx = self.add_local_artifact(variant_idx, installer_artifact);
2664 for binary_idx in binaries {
2665 let binary = self.binary(binary_idx);
2666 self.require_binary(
2667 installer_idx,
2668 variant_idx,
2669 binary_idx,
2670 dir_path.join(&binary.file_name),
2671 );
2672 }
2673 if checksum != ChecksumStyle::False {
2674 self.add_artifact_checksum(variant_idx, installer_idx, checksum);
2675 }
2676 if self.inner.config.builds.omnibor {
2677 let omnibor = self.create_omnibor_artifact(installer_idx, false);
2678 self.add_local_artifact(variant_idx, omnibor);
2679 }
2680 }
2681
2682 Ok(())
2683 }
2684
2685 fn add_local_artifact(
2686 &mut self,
2687 to_variant: ReleaseVariantIdx,
2688 artifact: Artifact,
2689 ) -> ArtifactIdx {
2690 assert!(self.local_artifacts_enabled());
2691 assert!(!artifact.is_global);
2692
2693 let idx = ArtifactIdx(self.inner.artifacts.len());
2694 let ReleaseVariant {
2695 local_artifacts, ..
2696 } = self.variant_mut(to_variant);
2697 local_artifacts.push(idx);
2698
2699 self.inner.artifacts.push(artifact);
2700 idx
2701 }
2702
2703 fn add_global_artifact(&mut self, to_release: ReleaseIdx, artifact: Artifact) -> ArtifactIdx {
2704 assert!(self.global_artifacts_enabled());
2705 assert!(artifact.is_global);
2706
2707 let idx = ArtifactIdx(self.inner.artifacts.len());
2708 let Release {
2709 global_artifacts, ..
2710 } = self.release_mut(to_release);
2711 global_artifacts.push(idx);
2712
2713 self.inner.artifacts.push(artifact);
2714 idx
2715 }
2716
2717 fn compute_build_steps(&mut self) -> DistResult<()> {
2718 let mut local_build_steps = vec![];
2721 let mut global_build_steps = vec![];
2722
2723 for workspace_idx in self.workspaces.all_workspace_indices() {
2724 let workspace_kind = self.workspaces.workspace(workspace_idx).kind;
2725 let builds = match workspace_kind {
2726 axoproject::WorkspaceKind::Javascript => {
2727 self.compute_generic_builds(workspace_idx)?
2728 }
2729 axoproject::WorkspaceKind::Generic => self.compute_generic_builds(workspace_idx)?,
2730 axoproject::WorkspaceKind::Rust => self.compute_cargo_builds(workspace_idx)?,
2731 };
2732 local_build_steps.extend(builds);
2733 }
2734 global_build_steps.extend(self.compute_extra_builds());
2735
2736 Self::add_build_steps_for_artifacts(
2737 &self
2738 .inner
2739 .artifacts
2740 .iter()
2741 .filter(|a| !a.is_global)
2742 .collect(),
2743 &mut local_build_steps,
2744 );
2745 Self::add_build_steps_for_artifacts(
2746 &self
2747 .inner
2748 .artifacts
2749 .iter()
2750 .filter(|a| a.is_global)
2751 .collect(),
2752 &mut global_build_steps,
2753 );
2754
2755 self.inner.local_build_steps = local_build_steps;
2756 self.inner.global_build_steps = global_build_steps;
2757
2758 Ok(())
2759 }
2760
2761 fn add_build_steps_for_artifacts(artifacts: &Vec<&Artifact>, build_steps: &mut Vec<BuildStep>) {
2762 for artifact in artifacts {
2763 match &artifact.kind {
2764 ArtifactKind::ExecutableZip(_zip) => {
2765 }
2767 ArtifactKind::Symbols(symbols) => {
2768 match symbols.kind {
2769 SymbolKind::Pdb => {
2770 }
2772 SymbolKind::Dsym => {
2773 }
2775 SymbolKind::Dwp => {
2776 }
2778 }
2779 }
2780 ArtifactKind::Installer(installer) => {
2781 build_steps.push(BuildStep::GenerateInstaller(installer.clone()));
2783 }
2784 ArtifactKind::Checksum(checksum) => {
2785 build_steps.push(BuildStep::Checksum(checksum.clone()));
2786 }
2787 ArtifactKind::UnifiedChecksum(unified_checksum) => {
2788 build_steps.push(BuildStep::UnifiedChecksum(unified_checksum.clone()));
2789 }
2790 ArtifactKind::SourceTarball(tarball) => {
2791 build_steps.push(BuildStep::GenerateSourceTarball(SourceTarballStep {
2792 committish: tarball.committish.to_owned(),
2793 prefix: tarball.prefix.to_owned(),
2794 target: tarball.target.to_owned(),
2795 working_dir: tarball.working_dir.to_owned(),
2796 recursive: tarball.recursive,
2797 }));
2798 }
2799 ArtifactKind::ExtraArtifact(_) => {
2800 }
2802 ArtifactKind::Updater(UpdaterImpl { use_latest }) => {
2803 build_steps.push(BuildStep::Updater(UpdaterStep {
2804 target_triple: artifact.target_triples.first().unwrap().to_owned(),
2806 target_filename: artifact.file_path.to_owned(),
2807 use_latest: *use_latest,
2808 }))
2809 }
2810 ArtifactKind::SBOM(_) => {
2811 }
2813 ArtifactKind::OmniborArtifactId(src) => {
2814 let src_path = src.src_path.clone();
2815 let old_extension = src_path.extension().unwrap_or("");
2816 let dest_path = src_path.with_extension(format!("{}.omnibor", old_extension));
2817
2818 build_steps.push(BuildStep::OmniborArtifactId(OmniborArtifactIdImpl {
2819 src_path,
2820 dest_path,
2821 }));
2822 }
2823 }
2824
2825 if let Some(archive) = &artifact.archive {
2826 let artifact_dir = &archive.dir_path;
2827 for (_, src_path) in &archive.static_assets {
2829 let src_path = src_path.clone();
2830 let file_name = src_path.file_name().unwrap();
2831 let dest_path = artifact_dir.join(file_name);
2832 build_steps.push(BuildStep::CopyFileOrDir(CopyStep {
2835 src_path,
2836 dest_path,
2837 }))
2838 }
2839
2840 build_steps.push(BuildStep::Zip(ZipDirStep {
2842 src_path: artifact_dir.to_owned(),
2843 dest_path: artifact.file_path.clone(),
2844 with_root: archive.with_root.clone(),
2845 zip_style: archive.zip_style,
2846 }));
2847 build_steps.push(BuildStep::Checksum(ChecksumImpl {
2849 checksum: ChecksumStyle::Sha256,
2850 src_path: artifact.file_path.clone(),
2851 dest_path: None,
2852 for_artifact: Some(artifact.id.clone()),
2853 }))
2854 }
2855 }
2856 }
2857
2858 fn validate_distable_packages(&self, announcing: &AnnouncementTag) -> DistResult<()> {
2859 for release in &announcing.rust_releases {
2860 let package = self.workspaces.package(release.package_idx);
2861 let workspace_idx = self.workspaces.workspace_for_package(release.package_idx);
2862 let package_workspace = self.workspaces.workspace(workspace_idx);
2863 let package_kind = package_workspace.kind;
2864 if announcing.package.is_none() {
2865 match package_kind {
2866 axoproject::WorkspaceKind::Generic | axoproject::WorkspaceKind::Javascript => {
2867 if let Some(build_command) = &package.build_command {
2868 if build_command.len() == 1
2869 && build_command.first().unwrap().contains(' ')
2870 {
2871 return Err(DistError::SusBuildCommand {
2872 manifest: package
2873 .dist_manifest_path
2874 .clone()
2875 .unwrap_or_else(|| package.manifest_path.clone()),
2876 command: build_command[0].clone(),
2877 });
2878 } else if build_command.is_empty() {
2879 return Err(DistError::NoBuildCommand {
2880 manifest: package
2881 .dist_manifest_path
2882 .clone()
2883 .unwrap_or_else(|| package.manifest_path.clone()),
2884 });
2885 }
2886 } else if package_kind == axoproject::WorkspaceKind::Javascript {
2887 return Err(DistError::NoDistScript {
2888 manifest: package.manifest_path.clone(),
2889 });
2890 } else {
2891 return Err(DistError::NoBuildCommand {
2892 manifest: package
2893 .dist_manifest_path
2894 .clone()
2895 .unwrap_or_else(|| package.manifest_path.clone()),
2896 });
2897 }
2898 }
2899 axoproject::WorkspaceKind::Rust => {
2900 if package.build_command.is_some() {
2901 return Err(DistError::UnexpectedBuildCommand {
2902 manifest: package
2903 .dist_manifest_path
2904 .clone()
2905 .unwrap_or_else(|| package.manifest_path.clone()),
2906 });
2907 }
2908 }
2909 }
2910 }
2911 }
2912 Ok(())
2913 }
2914
2915 fn compute_releases(
2916 &mut self,
2917 cfg: &Config,
2918 announcing: &AnnouncementTag,
2919 triples: &[TripleName],
2920 bypass_package_target_prefs: bool,
2921 ) -> DistResult<()> {
2922 for info in &announcing.rust_releases {
2924 let app_config = self.package_config(info.package_idx).clone();
2926
2927 let release = self.add_release(info.package_idx);
2929
2930 if info.executables.is_empty() && info.cdylibs.is_empty() && info.cstaticlibs.is_empty()
2934 {
2935 continue;
2936 }
2937
2938 for binary in &info.executables {
2940 self.add_binary(release, info.package_idx, binary.to_owned());
2941 }
2942
2943 for lib in &info.cdylibs {
2944 self.add_library(release, info.package_idx, lib.to_owned());
2945 }
2946
2947 for lib in &info.cstaticlibs {
2948 self.add_static_library(release, info.package_idx, lib.to_owned());
2949 }
2950
2951 for target in triples {
2953 let use_target =
2956 bypass_package_target_prefs || app_config.targets.iter().any(|t| t == target);
2957 if !use_target {
2958 continue;
2959 }
2960
2961 let variant = self.add_variant(release, target.clone())?;
2963
2964 if self.inner.config.installers.updater {
2965 self.add_updater(variant);
2966 }
2967 }
2968 self.add_executable_zip(release);
2970
2971 self.compute_platform_support(release);
2973
2974 self.add_source_tarball(&announcing.tag, release);
2976
2977 self.add_extra_artifacts(&app_config, release);
2979
2980 let installers = if cfg.installers.is_empty() {
2983 &[
2984 InstallerStyle::Shell,
2985 InstallerStyle::Powershell,
2986 InstallerStyle::Homebrew,
2987 InstallerStyle::Npm,
2988 InstallerStyle::Msi,
2989 InstallerStyle::Pkg,
2990 ]
2991 } else {
2992 &cfg.installers[..]
2993 };
2994
2995 for installer in installers {
2996 match installer {
2997 InstallerStyle::Shell => self.add_shell_installer(release)?,
2998 InstallerStyle::Powershell => self.add_powershell_installer(release)?,
2999 InstallerStyle::Homebrew => self.add_homebrew_installer(release)?,
3000 InstallerStyle::Npm => self.add_npm_installer(release)?,
3001 InstallerStyle::Msi => self.add_msi_installer(release)?,
3002 InstallerStyle::Pkg => self.add_pkg_installer(release)?,
3003 }
3004 }
3005
3006 self.add_cyclonedx_sbom_file(info.package_idx, release);
3008
3009 if self.inner.config.artifacts.checksum != ChecksumStyle::False {
3011 self.add_unified_checksum_file(release);
3012 }
3013 }
3014
3015 crate::manifest::add_releases_to_manifest(cfg, &self.inner, &mut self.manifest)?;
3017
3018 Ok(())
3019 }
3020
3021 fn compute_ci(&mut self) -> DistResult<()> {
3022 let CiConfig { github } = &self.inner.config.ci;
3023
3024 let mut has_ci = false;
3025 if let Some(github_config) = github {
3026 has_ci = true;
3027 self.inner.ci.github = Some(GithubCiInfo::new(&self.inner, github_config)?);
3028 }
3029
3030 if has_ci {
3032 let CiInfo { github } = &self.inner.ci;
3033 let github = github.as_ref().map(|info| {
3034 let external_repo_commit = info
3035 .github_release
3036 .as_ref()
3037 .and_then(|r| r.external_repo_commit.clone());
3038 cargo_dist_schema::GithubCiInfo {
3039 artifacts_matrix: Some(info.artifacts_matrix.clone()),
3040 pr_run_mode: Some(info.pr_run_mode),
3041 external_repo_commit,
3042 }
3043 });
3044
3045 self.manifest.ci = Some(cargo_dist_schema::CiInfo { github });
3046 }
3047
3048 Ok(())
3049 }
3050
3051 fn compute_platform_support(&mut self, release: ReleaseIdx) {
3052 let support = PlatformSupport::new(self, release);
3053 self.release_mut(release).platform_support = support;
3054 }
3055
3056 pub(crate) fn binary(&self, idx: BinaryIdx) -> &Binary {
3057 &self.inner.binaries[idx.0]
3058 }
3059 pub(crate) fn binary_mut(&mut self, idx: BinaryIdx) -> &mut Binary {
3060 &mut self.inner.binaries[idx.0]
3061 }
3062 pub(crate) fn artifact(&self, idx: ArtifactIdx) -> &Artifact {
3063 &self.inner.artifacts[idx.0]
3064 }
3065 pub(crate) fn artifact_mut(&mut self, idx: ArtifactIdx) -> &mut Artifact {
3066 &mut self.inner.artifacts[idx.0]
3067 }
3068 pub(crate) fn release(&self, idx: ReleaseIdx) -> &Release {
3069 &self.inner.releases[idx.0]
3070 }
3071 pub(crate) fn release_mut(&mut self, idx: ReleaseIdx) -> &mut Release {
3072 &mut self.inner.releases[idx.0]
3073 }
3074 pub(crate) fn variant(&self, idx: ReleaseVariantIdx) -> &ReleaseVariant {
3075 &self.inner.variants[idx.0]
3076 }
3077 pub(crate) fn variant_mut(&mut self, idx: ReleaseVariantIdx) -> &mut ReleaseVariant {
3078 &mut self.inner.variants[idx.0]
3079 }
3080 pub(crate) fn local_artifacts_enabled(&self) -> bool {
3081 match self.artifact_mode {
3082 ArtifactMode::Local => true,
3083 ArtifactMode::Global => false,
3084 ArtifactMode::Host => true,
3085 ArtifactMode::All => true,
3086 ArtifactMode::Lies => true,
3087 }
3088 }
3089 pub(crate) fn global_artifacts_enabled(&self) -> bool {
3090 match self.artifact_mode {
3091 ArtifactMode::Local => false,
3092 ArtifactMode::Global => true,
3093 ArtifactMode::Host => true,
3094 ArtifactMode::All => true,
3095 ArtifactMode::Lies => true,
3096 }
3097 }
3098
3099 pub(crate) fn package_config(&self, pkg_idx: PackageIdx) -> &AppConfig {
3100 &self.package_configs[pkg_idx.0]
3101 }
3102}
3103
3104impl DistGraph {
3105 pub fn binary(&self, idx: BinaryIdx) -> &Binary {
3107 &self.binaries[idx.0]
3108 }
3109 pub fn artifact(&self, idx: ArtifactIdx) -> &Artifact {
3111 &self.artifacts[idx.0]
3112 }
3113 pub fn release(&self, idx: ReleaseIdx) -> &Release {
3115 &self.releases[idx.0]
3116 }
3117 pub fn variant(&self, idx: ReleaseVariantIdx) -> &ReleaseVariant {
3119 &self.variants[idx.0]
3120 }
3121}
3122
3123pub fn gather_work(cfg: &Config) -> DistResult<(DistGraph, DistManifest)> {
3125 info!("analyzing workspace:");
3126 let tools = tool_info()?;
3127 let mut workspaces = crate::config::get_project()?;
3128 let system_id = format!(
3129 "{}:{}:{}",
3130 cfg.root_cmd,
3131 cfg.artifact_mode,
3132 cfg.targets.join(",")
3133 );
3134 let mut graph = DistGraphBuilder::new(
3135 system_id,
3136 tools,
3137 &mut workspaces,
3138 cfg.artifact_mode,
3139 cfg.allow_all_dirty,
3140 matches!(cfg.tag_settings.tag, TagMode::Infer),
3141 )?;
3142
3143 let host_target_triple = [graph.inner.tools.host_target.clone()];
3145 let all_target_triples = graph
3148 .workspaces
3149 .all_packages()
3150 .flat_map(|(id, _)| &graph.package_config(id).targets)
3151 .collect::<SortedSet<_>>()
3152 .into_iter()
3153 .cloned()
3154 .collect::<Vec<_>>();
3155
3156 let mut bypass_package_target_prefs = false;
3158 let triples = if cfg.targets.is_empty() {
3159 if matches!(cfg.artifact_mode, ArtifactMode::Host) {
3160 info!("using host target-triple");
3161 bypass_package_target_prefs = true;
3167 &host_target_triple
3168 } else if all_target_triples.is_empty() {
3169 return Err(DistError::CliMissingTargets {
3170 host_target: graph.inner.tools.host_target.clone(),
3171 });
3172 } else {
3173 info!("using all target-triples");
3174 &all_target_triples[..]
3176 }
3177 } else {
3178 info!("using explicit target-triples");
3179 &cfg.targets[..]
3181 };
3182 info!("selected triples: {:?}", triples);
3183
3184 let announcing = announce::select_tag(&mut graph, &cfg.tag_settings)?;
3186
3187 graph.validate_distable_packages(&announcing)?;
3188
3189 crate::manifest::load_and_merge_manifests(
3193 &graph.inner.dist_dir,
3194 &mut graph.manifest,
3195 &announcing,
3196 )?;
3197
3198 graph.compute_hosting(cfg, &announcing)?;
3200
3201 graph.compute_releases(cfg, &announcing, triples, bypass_package_target_prefs)?;
3203
3204 graph.compute_announcement_info(&announcing);
3206
3207 graph.compute_build_steps()?;
3209
3210 graph.compute_ci()?;
3212
3213 Ok((graph.inner, graph.manifest))
3214}
3215
3216pub fn cargo() -> DistResult<String> {
3218 let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned());
3219 Ok(cargo)
3220}
3221
3222pub fn get_cargo_info(cargo: String) -> DistResult<CargoInfo> {
3224 let mut command = Cmd::new(&cargo, "get your Rust toolchain's version");
3225 command.arg("-vV");
3226 let output = command.output()?;
3227 let output = String::from_utf8(output.stdout).map_err(|_| DistError::FailedCargoVersion)?;
3228 let mut lines = output.lines();
3229 let version_line = lines.next().map(|s| s.to_owned());
3230 for line in lines {
3231 if let Some(target) = line.strip_prefix("host: ") {
3232 info!("host target is {target}");
3233 return Ok(CargoInfo {
3234 cmd: cargo,
3235 version_line,
3236 host_target: TripleName::new(target.to_owned()),
3237 });
3238 }
3239 }
3240 Err(DistError::FailedCargoVersion)
3241}
3242
3243fn target_symbol_kind(target: &TripleNameRef) -> Option<SymbolKind> {
3244 #[allow(clippy::if_same_then_else)]
3245 if target.is_windows_msvc() {
3246 None
3250 } else if target.is_apple() {
3251 None
3258 } else {
3259 None
3264 }
3265}
3266
3267fn tool_info() -> DistResult<Tools> {
3268 let cargo = if let Ok(cargo_cmd) = cargo() {
3269 get_cargo_info(cargo_cmd).ok()
3270 } else {
3271 None
3272 };
3273 Ok(Tools {
3274 host_target: TripleName::new(current_platform::CURRENT_PLATFORM.to_owned()),
3275 cargo,
3276 rustup: find_tool("rustup", "-V"),
3277 brew: find_tool("brew", "--version"),
3278 git: find_tool("git", "--version"),
3279 omnibor: find_tool("omnibor", "--version"),
3280 code_sign_tool: None,
3282
3283 cargo_auditable: find_cargo_subcommand("cargo", "auditable", "--version"),
3286
3287 cargo_cyclonedx: find_cargo_subcommand("cargo", "cyclonedx", "--version"),
3288 cargo_xwin: find_cargo_subcommand("cargo", "xwin", "--version"),
3289 cargo_zigbuild: find_tool("cargo-zigbuild", "--version"),
3290 })
3291}
3292
3293fn find_cargo_subcommand(name: &str, arg: &str, test_flag: &str) -> Option<Tool> {
3294 let output = Cmd::new(name, "detect tool")
3295 .arg(arg)
3296 .arg(test_flag)
3297 .check(false)
3298 .output()
3299 .ok()?;
3300 let string_output = String::from_utf8(output.stdout).ok()?;
3301 let version = string_output.lines().next()?;
3302 Some(Tool {
3303 cmd: format!("{} {}", name, arg),
3304 version: version.to_owned(),
3305 })
3306}
3307
3308fn find_tool(name: &str, test_flag: &str) -> Option<Tool> {
3309 let output = Cmd::new(name, "detect tool")
3310 .arg(test_flag)
3311 .check(false)
3312 .output()
3313 .ok()?;
3314 let string_output = String::from_utf8(output.stdout).ok()?;
3315 let version = string_output.lines().next()?;
3316 Some(Tool {
3317 cmd: name.to_owned(),
3318 version: version.to_owned(),
3319 })
3320}
3321
3322#[derive(Clone, Debug, Serialize)]
3324#[serde(rename_all = "lowercase")]
3325pub enum ReleaseSourceType {
3326 GitHub,
3328 Axo,
3330}
3331
3332#[derive(Clone, Debug, Serialize)]
3334pub struct ReleaseSource {
3335 pub release_type: ReleaseSourceType,
3337 pub owner: String,
3339 pub name: String,
3341 pub app_name: String,
3343}
3344
3345#[derive(Clone, Debug, Serialize)]
3347#[serde(rename_all = "kebab-case")]
3348pub enum ProviderSource {
3349 CargoDist,
3351}
3352
3353#[derive(Clone, Debug, Serialize)]
3355pub struct Provider {
3356 pub source: ProviderSource,
3358 pub version: String,
3360}
3361
3362#[derive(Clone, Debug, Serialize)]
3364#[serde(rename_all = "kebab-case")]
3365pub enum InstallLayout {
3366 Unspecified,
3368 Flat,
3370 Hierarchical,
3372 CargoHome,
3374}
3375
3376#[derive(Clone, Debug, Serialize)]
3378pub struct InstallReceipt {
3379 pub install_prefix: String,
3381 pub install_layout: InstallLayout,
3383 pub binaries: Vec<String>,
3385 pub cdylibs: Vec<String>,
3387 pub cstaticlibs: Vec<String>,
3389 pub source: ReleaseSource,
3391 pub version: String,
3393 pub provider: Provider,
3395 pub binary_aliases: BTreeMap<String, Vec<String>>,
3397 pub modify_path: bool,
3399}
3400
3401impl InstallReceipt {
3402 pub fn from_metadata(
3404 manifest: &DistGraph,
3405 release: &Release,
3406 ) -> DistResult<Option<InstallReceipt>> {
3407 let hosting = if let Some(hosting) = &manifest.hosting {
3408 hosting
3409 } else {
3410 return Ok(None);
3411 };
3412 let source_type = if hosting.hosts.contains(&HostingStyle::Github) {
3413 ReleaseSourceType::GitHub
3414 } else {
3415 return Err(DistError::NoGitHubHosting {});
3416 };
3417
3418 Ok(Some(InstallReceipt {
3419 install_prefix: "AXO_INSTALL_PREFIX".to_owned(),
3421 install_layout: InstallLayout::Unspecified,
3422 binaries: vec!["CARGO_DIST_BINS".to_owned()],
3423 cdylibs: vec!["CARGO_DIST_DYLIBS".to_owned()],
3424 cstaticlibs: vec!["CARGO_DIST_STATICLIBS".to_owned()],
3425 version: release.version.to_string(),
3426 source: ReleaseSource {
3427 release_type: source_type,
3428 owner: hosting.owner.to_owned(),
3429 name: hosting.project.to_owned(),
3430 app_name: release.app_name.to_owned(),
3431 },
3432 provider: Provider {
3433 source: ProviderSource::CargoDist,
3434 version: env!("CARGO_PKG_VERSION").to_owned(),
3435 },
3436 binary_aliases: BTreeMap::default(),
3437 modify_path: true,
3438 }))
3439 }
3440}
3441
3442fn require_nonempty_installer(release: &Release, config: &CommonInstallerConfig) -> DistResult<()> {
3443 if config.install_libraries.is_empty() && release.bins.is_empty() {
3444 Err(DistError::EmptyInstaller {})
3445 } else {
3446 Ok(())
3447 }
3448}