use std::collections::BTreeMap;
use crate::backend::installer::{ExecutableZipFragment, HomebrewImpl};
use crate::platform::targets::{
TARGET_ARM64_LINUX_GNU, TARGET_ARM64_MAC, TARGET_X64_LINUX_GNU, TARGET_X64_MAC,
};
use axoasset::AxoClient;
use axoprocess::Cmd;
use axoproject::{PackageId, PackageIdx, WorkspaceGraph};
use camino::{Utf8Path, Utf8PathBuf};
use cargo_dist_schema::target_lexicon::{OperatingSystem, Triple};
use cargo_dist_schema::{
ArtifactId, BuildEnvironment, DistManifest, HomebrewPackageName, SystemId, SystemInfo,
TripleName, TripleNameRef,
};
use semver::Version;
use serde::Serialize;
use tracing::{info, warn};
use crate::announce::{self, AnnouncementTag, TagMode};
use crate::backend::ci::github::GithubCiInfo;
use crate::backend::ci::CiInfo;
use crate::backend::installer::homebrew::{to_homebrew_license_format, HomebrewFragments};
use crate::backend::installer::macpkg::PkgInstallerInfo;
use crate::config::v1::builds::cargo::AppCargoBuildConfig;
use crate::config::v1::ci::CiConfig;
use crate::config::v1::installers::CommonInstallerConfig;
use crate::config::v1::publishers::PublisherConfig;
use crate::config::v1::{app_config, workspace_config, AppConfig, WorkspaceConfig};
use crate::config::{DependencyKind, DirtyMode, LibraryStyle};
use crate::linkage::determine_build_environment;
use crate::net::ClientSettings;
use crate::platform::{PlatformSupport, RuntimeConditions};
use crate::sign::Signing;
use crate::{
backend::{
installer::{
homebrew::{to_class_case, HomebrewInstallerInfo},
msi::MsiInstallerInfo,
npm::NpmInstallerInfo,
InstallerImpl, InstallerInfo,
},
templates::Templates,
},
config::{
self, ArtifactMode, ChecksumStyle, CompressionImpl, Config, HostingStyle, InstallerStyle,
ZipStyle,
},
errors::{DistError, DistResult},
};
pub const METADATA_DIST: &str = "dist";
pub const TARGET_DIST: &str = "distrib";
pub const PROFILE_DIST: &str = "dist";
pub const OS_LINUX: &str = "linux";
pub const OS_MACOS: &str = "macos";
pub const OS_WINDOWS: &str = "windows";
pub const CPU_X64: &str = "x86_64";
pub const CPU_X86: &str = "x86";
pub const CPU_ARM64: &str = "arm64";
pub const CPU_ARM: &str = "arm";
pub type FastMap<K, V> = std::collections::HashMap<K, V>;
pub type SortedMap<K, V> = std::collections::BTreeMap<K, V>;
pub type SortedSet<T> = std::collections::BTreeSet<T>;
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
pub struct ArtifactIdx(pub usize);
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
pub struct ReleaseVariantIdx(pub usize);
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
pub struct ReleaseIdx(pub usize);
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
pub struct BinaryIdx(pub usize);
#[derive(Clone, Debug)]
pub struct BinaryAliases(BTreeMap<String, Vec<String>>);
impl BinaryAliases {
pub fn for_target(&self, target: &TripleNameRef) -> BTreeMap<String, Vec<String>> {
if target.is_windows() {
BTreeMap::from_iter(self.0.iter().map(|(k, v)| {
(
format!("{k}.exe"),
v.iter().map(|name| format!("{name}.exe")).collect(),
)
}))
} else {
self.0.clone()
}
}
pub fn for_targets(
&self,
targets: &[TripleName],
) -> BTreeMap<TripleName, BTreeMap<String, Vec<String>>> {
BTreeMap::from_iter(
targets
.iter()
.map(|target| (target.to_owned(), self.for_target(target))),
)
}
}
#[derive(Debug)]
pub struct DistGraph {
pub system_id: SystemId,
pub is_init: bool,
pub allow_dirty: DirtyMode,
pub global_homebrew_tap: Option<String>,
pub global_publishers: Option<PublisherConfig>,
pub precise_cargo_builds: bool,
pub tools: Tools,
pub signer: Signing,
pub templates: Templates,
pub target_dir: Utf8PathBuf,
pub repo_dir: Utf8PathBuf,
pub workspace_dir: Utf8PathBuf,
pub dist_dir: Utf8PathBuf,
pub config: WorkspaceConfig,
pub local_build_steps: Vec<BuildStep>,
pub global_build_steps: Vec<BuildStep>,
pub artifacts: Vec<Artifact>,
pub binaries: Vec<Binary>,
pub variants: Vec<ReleaseVariant>,
pub releases: Vec<Release>,
pub ci: CiInfo,
pub hosting: Option<HostingInfo>,
pub local_builds_are_lies: bool,
pub client_settings: ClientSettings,
pub axoclient: AxoClient,
}
#[derive(Debug, Clone)]
pub struct HostingInfo {
pub hosts: Vec<HostingStyle>,
pub domain: String,
pub repo_path: String,
pub source_host: String,
pub owner: String,
pub project: String,
}
#[derive(Debug, Clone)]
pub struct Tools {
pub host_target: TripleName,
pub cargo: Option<CargoInfo>,
pub rustup: Option<Tool>,
pub brew: Option<Tool>,
pub git: Option<Tool>,
pub omnibor: Option<Tool>,
pub code_sign_tool: Option<Tool>,
pub cargo_auditable: Option<Tool>,
pub cargo_cyclonedx: Option<Tool>,
pub cargo_xwin: Option<Tool>,
pub cargo_zigbuild: Option<Tool>,
}
impl Tools {
pub fn cargo(&self) -> DistResult<&CargoInfo> {
self.cargo.as_ref().ok_or(DistError::ToolMissing {
tool: "cargo".to_owned(),
})
}
pub fn omnibor(&self) -> DistResult<&Tool> {
self.omnibor.as_ref().ok_or(DistError::ToolMissing {
tool: "omnibor-cli".to_owned(),
})
}
pub fn cargo_auditable(&self) -> DistResult<&Tool> {
self.cargo_auditable.as_ref().ok_or(DistError::ToolMissing {
tool: "cargo-auditable".to_owned(),
})
}
pub fn cargo_cyclonedx(&self) -> DistResult<&Tool> {
self.cargo_cyclonedx.as_ref().ok_or(DistError::ToolMissing {
tool: "cargo-cyclonedx".to_owned(),
})
}
pub fn cargo_xwin(&self) -> DistResult<&Tool> {
self.cargo_xwin.as_ref().ok_or(DistError::ToolMissing {
tool: "cargo-xwin".to_owned(),
})
}
pub fn cargo_zigbuild(&self) -> DistResult<&Tool> {
self.cargo_zigbuild.as_ref().ok_or(DistError::ToolMissing {
tool: "cargo-zigbuild".to_owned(),
})
}
}
#[derive(Debug, Clone)]
pub struct CargoInfo {
pub cmd: String,
pub version_line: Option<String>,
pub host_target: TripleName,
}
#[derive(Debug, Clone, Default)]
pub struct Tool {
pub cmd: String,
pub version: String,
}
#[derive(Debug)]
pub struct Binary {
pub id: String,
pub pkg_idx: PackageIdx,
pub pkg_id: Option<PackageId>,
pub pkg_spec: String,
pub name: String,
pub file_name: String,
pub target: TripleName,
pub symbols_artifact: Option<ArtifactIdx>,
pub copy_exe_to: Vec<Utf8PathBuf>,
pub copy_symbols_to: Vec<Utf8PathBuf>,
pub features: CargoTargetFeatures,
pub kind: BinaryKind,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum BinaryKind {
Executable,
DynamicLibrary,
StaticLibrary,
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum BuildStep {
Generic(GenericBuildStep),
Cargo(CargoBuildStep),
Extra(ExtraBuildStep),
Rustup(RustupStep),
CopyFile(CopyStep),
CopyDir(CopyStep),
CopyFileOrDir(CopyStep),
Zip(ZipDirStep),
GenerateInstaller(InstallerImpl),
GenerateSourceTarball(SourceTarballStep),
Checksum(ChecksumImpl),
UnifiedChecksum(UnifiedChecksumStep),
OmniborArtifactId(OmniborArtifactIdImpl),
Updater(UpdaterStep),
}
#[derive(Debug)]
pub struct CargoBuildStep {
pub target_triple: TripleName,
pub features: CargoTargetFeatures,
pub package: CargoTargetPackages,
pub profile: String,
pub rustflags: String,
pub expected_binaries: Vec<BinaryIdx>,
pub working_dir: Utf8PathBuf,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CargoBuildWrapper {
ZigBuild,
Xwin,
}
impl std::fmt::Display for CargoBuildWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.pad(match self {
CargoBuildWrapper::ZigBuild => "cargo-zigbuild",
CargoBuildWrapper::Xwin => "cargo-xwin",
})
}
}
pub fn build_wrapper_for_cross(
host: &Triple,
target: &Triple,
) -> DistResult<Option<CargoBuildWrapper>> {
if host.operating_system == target.operating_system && host.architecture == target.architecture
{
return Ok(None);
}
match target.operating_system {
OperatingSystem::Darwin(_) => match host.operating_system {
OperatingSystem::Darwin(_) => {
Ok(None)
}
_ => {
Err(DistError::UnsupportedCrossCompile {
host: host.clone(),
target: target.clone(),
details: "cross-compiling to macOS is a road paved with sadness — we cowardly refuse to walk it.".to_string(),
})
}
},
OperatingSystem::Linux => match host.operating_system {
OperatingSystem::Linux | OperatingSystem::Darwin(_) | OperatingSystem::Windows => {
Ok(Some(CargoBuildWrapper::ZigBuild))
}
_ => {
Err(DistError::UnsupportedCrossCompile {
host: host.clone(),
target: target.clone(),
details: format!("no idea how to cross-compile from {host} to linux"),
})
}
},
OperatingSystem::Windows => match host.operating_system {
OperatingSystem::Windows => {
Ok(None)
}
OperatingSystem::Linux | OperatingSystem::Darwin(_) => {
Ok(Some(CargoBuildWrapper::Xwin))
}
_ => {
Err(DistError::UnsupportedCrossCompile {
host: host.clone(),
target: target.clone(),
details: format!("no idea how to cross-compile from {host} to windows with architecture {}", target.architecture),
})
}
},
_ => {
Err(DistError::UnsupportedCrossCompile {
host: host.clone(),
target: target.clone(),
details: format!("no idea how to cross-compile from anything (including the current host, {host}) to {target}"),
})
}
}
}
#[derive(Debug)]
pub struct GenericBuildStep {
pub target_triple: TripleName,
pub expected_binaries: Vec<BinaryIdx>,
pub working_dir: Utf8PathBuf,
pub out_dir: Utf8PathBuf,
pub build_command: Vec<String>,
}
#[derive(Debug)]
pub struct ExtraBuildStep {
pub working_dir: Utf8PathBuf,
pub artifact_relpaths: Vec<Utf8PathBuf>,
pub build_command: Vec<String>,
}
#[derive(Debug)]
pub struct RustupStep {
pub rustup: Tool,
pub target: TripleName,
}
#[derive(Debug)]
pub struct ZipDirStep {
pub src_path: Utf8PathBuf,
pub dest_path: Utf8PathBuf,
pub with_root: Option<Utf8PathBuf>,
pub zip_style: ZipStyle,
}
#[derive(Debug)]
pub struct CopyStep {
pub src_path: Utf8PathBuf,
pub dest_path: Utf8PathBuf,
}
#[derive(Debug, Clone)]
pub struct ChecksumImpl {
pub checksum: ChecksumStyle,
pub src_path: Utf8PathBuf,
pub dest_path: Option<Utf8PathBuf>,
pub for_artifact: Option<ArtifactId>,
}
#[derive(Debug, Clone)]
pub struct UnifiedChecksumStep {
pub checksum: ChecksumStyle,
pub dest_path: Utf8PathBuf,
}
#[derive(Debug, Clone)]
pub struct OmniborArtifactIdImpl {
pub src_path: Utf8PathBuf,
pub dest_path: Utf8PathBuf,
}
#[derive(Debug, Clone)]
pub struct SourceTarballStep {
pub committish: String,
pub prefix: String,
pub target: Utf8PathBuf,
pub working_dir: Utf8PathBuf,
pub recursive: bool,
}
#[derive(Debug, Clone)]
pub struct UpdaterStep {
pub target_triple: TripleName,
pub target_filename: Utf8PathBuf,
pub use_latest: bool,
}
#[derive(Copy, Clone, Debug)]
pub enum SymbolKind {
Pdb,
Dsym,
Dwp,
}
impl SymbolKind {
pub fn ext(self) -> &'static str {
match self {
SymbolKind::Pdb => "pdb",
SymbolKind::Dsym => "dSYM",
SymbolKind::Dwp => "dwp",
}
}
}
#[derive(Clone, Debug)]
pub struct Artifact {
pub id: ArtifactId,
pub target_triples: Vec<TripleName>,
pub archive: Option<Archive>,
pub file_path: Utf8PathBuf,
pub required_binaries: FastMap<BinaryIdx, Utf8PathBuf>,
pub kind: ArtifactKind,
pub checksum: Option<ArtifactIdx>,
pub is_global: bool,
}
#[derive(Clone, Debug)]
pub struct Archive {
pub with_root: Option<Utf8PathBuf>,
pub dir_path: Utf8PathBuf,
pub zip_style: ZipStyle,
pub static_assets: Vec<(StaticAssetKind, Utf8PathBuf)>,
}
#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum ArtifactKind {
ExecutableZip(ExecutableZip),
Symbols(Symbols),
Installer(InstallerImpl),
Checksum(ChecksumImpl),
UnifiedChecksum(UnifiedChecksumStep),
SourceTarball(SourceTarball),
ExtraArtifact(ExtraArtifactImpl),
Updater(UpdaterImpl),
SBOM(SBOMImpl),
OmniborArtifactId(OmniborArtifactIdImpl),
}
#[derive(Clone, Debug)]
pub struct ExecutableZip {
}
#[derive(Clone, Debug)]
pub struct Symbols {
kind: SymbolKind,
}
#[derive(Clone, Debug)]
pub struct SourceTarball {
pub committish: String,
pub prefix: String,
pub target: Utf8PathBuf,
pub working_dir: Utf8PathBuf,
pub recursive: bool,
}
#[derive(Clone, Debug)]
pub struct ExtraArtifactImpl {
pub working_dir: Utf8PathBuf,
pub command: Vec<String>,
pub artifact_relpath: Utf8PathBuf,
}
#[derive(Clone, Debug)]
pub struct UpdaterImpl {
pub use_latest: bool,
}
#[derive(Clone, Debug)]
pub struct SBOMImpl {}
#[derive(Clone, Debug)]
pub struct Release {
pub app_name: String,
pub app_desc: Option<String>,
pub app_authors: Vec<String>,
pub app_license: Option<String>,
pub app_repository_url: Option<String>,
pub app_homepage_url: Option<String>,
pub app_keywords: Option<Vec<String>>,
pub pkg_idx: PackageIdx,
pub version: Version,
pub id: String,
pub config: AppConfig,
pub targets: Vec<TripleName>,
pub bins: Vec<(PackageIdx, String)>,
pub cdylibs: Vec<(PackageIdx, String)>,
pub cstaticlibs: Vec<(PackageIdx, String)>,
pub global_artifacts: Vec<ArtifactIdx>,
pub variants: Vec<ReleaseVariantIdx>,
pub changelog_body: Option<String>,
pub changelog_title: Option<String>,
pub static_assets: Vec<(StaticAssetKind, Utf8PathBuf)>,
pub platform_support: PlatformSupport,
}
#[derive(Debug)]
pub struct ReleaseVariant {
pub target: TripleName,
pub id: String,
pub binaries: Vec<BinaryIdx>,
pub static_assets: Vec<(StaticAssetKind, Utf8PathBuf)>,
pub local_artifacts: Vec<ArtifactIdx>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum StaticAssetKind {
Readme,
License,
Changelog,
Other,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct CargoTargetFeatures {
pub default_features: bool,
pub features: CargoTargetFeatureList,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum CargoTargetFeatureList {
All,
List(Vec<String>),
}
impl Default for CargoTargetFeatureList {
fn default() -> Self {
Self::List(vec![])
}
}
#[derive(Debug)]
pub enum CargoTargetPackages {
Workspace,
Package(String),
}
pub(crate) struct DistGraphBuilder<'pkg_graph> {
pub(crate) inner: DistGraph,
pub(crate) manifest: DistManifest,
pub(crate) workspaces: &'pkg_graph mut WorkspaceGraph,
artifact_mode: ArtifactMode,
binaries_by_id: FastMap<String, BinaryIdx>,
package_configs: Vec<AppConfig>,
}
impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
pub(crate) fn new(
system_id: SystemId,
tools: Tools,
workspaces: &'pkg_graph mut WorkspaceGraph,
artifact_mode: ArtifactMode,
allow_all_dirty: bool,
announcement_tag_is_implicit: bool,
) -> DistResult<Self> {
let root_workspace_idx = workspaces.root_workspace_idx();
let root_workspace = workspaces.workspace(root_workspace_idx);
if let Some(dist_manifest_path) = root_workspace.dist_manifest_path.as_deref() {
for workspace_idx in workspaces.all_workspace_indices() {
if workspace_idx == root_workspace_idx {
continue;
}
let workspace = workspaces.workspace(workspace_idx);
config::reject_metadata_table(
&workspace.manifest_path,
dist_manifest_path,
workspace.cargo_metadata_table.as_ref(),
)?;
}
}
let target_dir = root_workspace.target_dir.clone();
let workspace_dir = root_workspace.workspace_dir.clone();
let repo_dir = if let Some(repo) = &workspaces.repo {
repo.path.to_owned()
} else {
workspace_dir.clone()
};
let dist_dir = target_dir.join(TARGET_DIST);
let mut workspace_metadata =
config::parse_metadata_table_or_manifest(
&root_workspace.manifest_path,
root_workspace.dist_manifest_path.as_deref(),
root_workspace.cargo_metadata_table.as_ref(),
)?;
let workspace_layer = workspace_metadata.to_toml_layer(true);
workspace_metadata.make_relative_to(&root_workspace.workspace_dir);
let config = workspace_config(workspaces, workspace_layer.clone());
if config.builds.cargo.rust_toolchain_version.is_some() {
warn!("rust-toolchain-version is deprecated, use rust-toolchain.toml if you want pinned toolchains");
}
let local_builds_are_lies = artifact_mode == ArtifactMode::Lies;
let mut package_metadatas = vec![];
let mut package_configs = vec![];
for (pkg_idx, package) in workspaces.all_packages() {
let mut package_metadata = config::parse_metadata_table_or_manifest(
&package.manifest_path,
package.dist_manifest_path.as_deref(),
package.cargo_metadata_table.as_ref(),
)?;
package_configs.push(app_config(
workspaces,
pkg_idx,
workspace_layer.clone(),
package_metadata.to_toml_layer(false),
));
package_metadata.make_relative_to(&package.package_root);
package_metadata.merge_workspace_config(&workspace_metadata, &package.manifest_path);
package_metadata.validate_install_paths()?;
package_metadatas.push(package_metadata);
}
let mut global_cargo_build_config = None::<AppCargoBuildConfig>;
let mut packages_with_mismatched_features = vec![];
for ((_idx, package), package_config) in workspaces.all_packages().zip(&package_configs) {
if let Some(cargo_build_config) = &global_cargo_build_config {
if package_config.builds.cargo.features != cargo_build_config.features
|| package_config.builds.cargo.all_features != cargo_build_config.all_features
|| package_config.builds.cargo.default_features
!= cargo_build_config.default_features
{
packages_with_mismatched_features.push(
package
.dist_manifest_path
.clone()
.unwrap_or(package.manifest_path.clone()),
);
}
} else {
global_cargo_build_config = Some(package_config.builds.cargo.clone());
packages_with_mismatched_features.push(
package
.dist_manifest_path
.clone()
.unwrap_or(package.manifest_path.clone()),
);
};
}
let requires_precise = packages_with_mismatched_features.len() > 1;
let precise_cargo_builds = if let Some(precise_builds) = config.builds.cargo.precise_builds
{
if !precise_builds && requires_precise {
return Err(DistError::PreciseImpossible {
packages: packages_with_mismatched_features,
});
}
precise_builds
} else {
info!("force-enabling precise-builds to handle your build features");
requires_precise
};
let mut global_homebrew_tap = None;
let mut packages_with_mismatched_taps = vec![];
for ((_idx, package), package_config) in workspaces.all_packages().zip(&package_configs) {
if let Some(homebrew) = &package_config.installers.homebrew {
if let Some(new_tap) = &homebrew.tap {
if let Some(current_tap) = &global_homebrew_tap {
if current_tap != new_tap {
packages_with_mismatched_taps.push(
package
.dist_manifest_path
.clone()
.unwrap_or(package.manifest_path.clone()),
);
}
} else {
packages_with_mismatched_taps.push(
package
.dist_manifest_path
.clone()
.unwrap_or(package.manifest_path.clone()),
);
global_homebrew_tap = Some(new_tap.clone());
}
}
}
}
if packages_with_mismatched_taps.len() > 1 {
return Err(DistError::MismatchedTaps {
packages: packages_with_mismatched_taps,
});
}
let mut global_publishers = None;
let mut packages_with_mismatched_publishers = vec![];
for ((_idx, package), package_config) in workspaces.all_packages().zip(&package_configs) {
if let Some(cur_publishers) = &global_publishers {
if cur_publishers != &package_config.publishers {
packages_with_mismatched_publishers.push(
package
.dist_manifest_path
.clone()
.unwrap_or(package.manifest_path.clone()),
);
}
} else {
packages_with_mismatched_publishers.push(
package
.dist_manifest_path
.clone()
.unwrap_or(package.manifest_path.clone()),
);
global_publishers = Some(package_config.publishers.clone());
}
}
if packages_with_mismatched_publishers.len() > 1 {
return Err(DistError::MismatchedPublishers {
packages: packages_with_mismatched_publishers,
});
}
let global_publish_prereleases = global_publishers
.as_ref()
.map(|p| {
let PublisherConfig {
homebrew,
npm,
user,
} = p;
let h_pre = homebrew.as_ref().map(|p| p.prereleases);
let npm_pre = npm.as_ref().map(|p| p.prereleases);
let user_pre = user.as_ref().map(|p| p.prereleases);
let choices = [h_pre, npm_pre, user_pre];
let mut global_choice = None;
#[allow(clippy::manual_flatten)]
for choice in choices {
if let Some(choice) = choice {
if let Some(cur_choice) = global_choice {
if cur_choice != choice {
return Err(DistError::MismatchedPrereleases);
}
} else {
global_choice = Some(choice);
}
}
}
Ok(global_choice.unwrap_or(false))
})
.transpose()?
.unwrap_or(false);
let templates = Templates::new()?;
let allow_dirty = if allow_all_dirty {
DirtyMode::AllowAll
} else {
DirtyMode::AllowList(config.allow_dirty.clone())
};
let cargo_version_line = tools.cargo.as_ref().and_then(|c| c.version_line.to_owned());
let build_environment = if local_builds_are_lies {
BuildEnvironment::Indeterminate
} else {
determine_build_environment(&tools.host_target)
};
let system = SystemInfo {
id: system_id.clone(),
cargo_version_line,
build_environment,
};
let systems = SortedMap::from_iter([(system_id.clone(), system)]);
let client_settings = ClientSettings::new();
let axoclient = crate::net::create_axoasset_client(&client_settings)?;
let signer = Signing::new(
&axoclient,
&tools.host_target,
&dist_dir,
config.builds.ssldotcom_windows_sign.clone(),
config.builds.macos_sign,
)?;
let github_attestations = config
.hosts
.github
.as_ref()
.map(|g| g.attestations)
.unwrap_or(false);
let github_attestations_filters = config
.hosts
.github
.as_ref()
.map(|g| g.attestations_filters.clone())
.unwrap_or_default();
let github_attestations_phase = config
.hosts
.github
.as_ref()
.map(|g| g.attestations_phase)
.unwrap_or_default();
let force_latest = config.hosts.force_latest;
Ok(Self {
inner: DistGraph {
system_id,
is_init: config.dist_version.is_some(),
allow_dirty,
global_homebrew_tap,
global_publishers,
precise_cargo_builds,
target_dir,
repo_dir,
workspace_dir,
dist_dir,
config,
signer,
tools,
local_builds_are_lies,
templates,
local_build_steps: vec![],
global_build_steps: vec![],
artifacts: vec![],
binaries: vec![],
variants: vec![],
releases: vec![],
ci: CiInfo::default(),
hosting: None,
client_settings,
axoclient,
},
manifest: DistManifest {
dist_version: Some(env!("CARGO_PKG_VERSION").to_owned()),
system_info: None,
announcement_tag: None,
announcement_is_prerelease: false,
announcement_tag_is_implicit,
announcement_title: None,
announcement_changelog: None,
announcement_github_body: None,
releases: vec![],
artifacts: Default::default(),
systems,
assets: Default::default(),
publish_prereleases: global_publish_prereleases,
force_latest,
ci: None,
linkage: vec![],
upload_files: vec![],
github_attestations,
github_attestations_filters,
github_attestations_phase,
},
package_configs,
workspaces,
binaries_by_id: FastMap::new(),
artifact_mode,
})
}
fn add_release(&mut self, pkg_idx: PackageIdx) -> ReleaseIdx {
let package_info = self.workspaces.package(pkg_idx);
let config = self.package_config(pkg_idx).clone();
let version = package_info.version.as_ref().unwrap().semver().clone();
let app_name = package_info.name.clone();
let app_desc = package_info.description.clone();
let app_authors = package_info.authors.clone();
let app_license = package_info.license.clone();
let app_repository_url = package_info.repository_url.clone();
let app_homepage_url = package_info.homepage_url.clone();
let app_keywords = package_info.keywords.clone();
let mut static_assets = vec![];
if config.artifacts.archives.auto_includes {
if let Some(readme) = &package_info.readme_file {
static_assets.push((StaticAssetKind::Readme, readme.clone()));
}
if let Some(changelog) = &package_info.changelog_file {
static_assets.push((StaticAssetKind::Changelog, changelog.clone()));
}
for license in &package_info.license_files {
static_assets.push((StaticAssetKind::License, license.clone()));
}
}
for static_asset in &config.artifacts.archives.include {
static_assets.push((StaticAssetKind::Other, static_asset.clone()));
}
let platform_support = PlatformSupport::default();
let idx = ReleaseIdx(self.inner.releases.len());
let id = app_name.clone();
info!("added release {id}");
self.inner.releases.push(Release {
app_name,
app_desc,
app_authors,
app_license,
app_repository_url,
app_homepage_url,
app_keywords,
version,
id,
pkg_idx,
global_artifacts: vec![],
bins: vec![],
cdylibs: vec![],
cstaticlibs: vec![],
targets: vec![],
variants: vec![],
changelog_body: None,
changelog_title: None,
config,
static_assets,
platform_support,
});
idx
}
fn add_variant(
&mut self,
to_release: ReleaseIdx,
target: TripleName,
) -> DistResult<ReleaseVariantIdx> {
let idx = ReleaseVariantIdx(self.inner.variants.len());
let Release {
id: release_id,
variants,
targets,
static_assets,
bins,
cdylibs,
cstaticlibs,
config,
pkg_idx,
..
} = self.release_mut(to_release);
let static_assets = static_assets.clone();
let variant_id = format!("{release_id}-{target}");
info!("added variant {variant_id}");
let binaries_map = &config.artifacts.archives.binaries;
variants.push(idx);
targets.push(target.clone());
let mapped_bins = binaries_map
.get(target.as_str())
.or_else(|| binaries_map.get("*"));
let mut packageables: Vec<(PackageIdx, String, BinaryKind)> =
if let Some(mapped_bins) = mapped_bins {
mapped_bins
.iter()
.map(|b| (*pkg_idx, b.to_string(), BinaryKind::Executable))
.collect()
} else {
bins.clone()
.into_iter()
.map(|(idx, b)| (idx, b, BinaryKind::Executable))
.collect()
};
if config
.artifacts
.archives
.package_libraries
.contains(&LibraryStyle::CDynamic)
{
let all_dylibs = cdylibs
.clone()
.into_iter()
.map(|(idx, l)| (idx, l, BinaryKind::DynamicLibrary));
packageables = packageables.into_iter().chain(all_dylibs).collect();
}
if config
.artifacts
.archives
.package_libraries
.contains(&LibraryStyle::CStatic)
{
let all_cstaticlibs = cstaticlibs
.clone()
.into_iter()
.map(|(idx, l)| (idx, l, BinaryKind::StaticLibrary));
packageables = packageables.into_iter().chain(all_cstaticlibs).collect();
}
let mut binaries = vec![];
for (pkg_idx, binary_name, kind) in packageables {
let package = self.workspaces.package(pkg_idx);
let package_config = self.package_config(pkg_idx);
let pkg_id = package.cargo_package_id.clone();
let pkg_spec = package.true_name.clone();
let kind_label = match kind {
BinaryKind::Executable => "exe",
BinaryKind::DynamicLibrary => "cdylib",
BinaryKind::StaticLibrary => "cstaticlib",
};
let bin_id = format!("{variant_id}-{kind_label}-{binary_name}");
let idx = if let Some(&idx) = self.binaries_by_id.get(&bin_id) {
idx
} else {
let features = CargoTargetFeatures {
default_features: package_config.builds.cargo.default_features,
features: if package_config.builds.cargo.all_features {
CargoTargetFeatureList::All
} else {
CargoTargetFeatureList::List(package_config.builds.cargo.features.clone())
},
};
let target_is_windows = target.is_windows();
let platform_exe_ext;
let platform_lib_prefix;
if target_is_windows {
platform_exe_ext = ".exe";
platform_lib_prefix = "";
} else {
platform_exe_ext = "";
platform_lib_prefix = "lib";
};
let platform_lib_ext;
let platform_staticlib_ext;
if target_is_windows {
platform_lib_ext = ".dll";
platform_staticlib_ext = ".lib";
} else if target.is_linux() {
platform_lib_ext = ".so";
platform_staticlib_ext = ".a";
} else if target.is_darwin() {
platform_lib_ext = ".dylib";
platform_staticlib_ext = ".a";
} else {
return Err(DistError::UnrecognizedTarget { target });
};
let file_name = match kind {
BinaryKind::Executable => format!("{binary_name}{platform_exe_ext}"),
BinaryKind::DynamicLibrary => {
format!("{platform_lib_prefix}{binary_name}{platform_lib_ext}")
}
BinaryKind::StaticLibrary => {
format!("{platform_lib_prefix}{binary_name}{platform_staticlib_ext}")
}
};
info!("added binary {bin_id}");
let idx = BinaryIdx(self.inner.binaries.len());
let binary = Binary {
id: bin_id.clone(),
pkg_id,
pkg_spec,
pkg_idx,
name: binary_name,
file_name,
target: target.clone(),
copy_exe_to: vec![],
copy_symbols_to: vec![],
symbols_artifact: None,
features,
kind,
};
self.inner.binaries.push(binary);
self.binaries_by_id.insert(bin_id, idx);
idx
};
binaries.push(idx);
}
self.inner.variants.push(ReleaseVariant {
target,
id: variant_id,
local_artifacts: vec![],
binaries,
static_assets,
});
Ok(idx)
}
fn add_binary(&mut self, to_release: ReleaseIdx, pkg_idx: PackageIdx, binary_name: String) {
let release = self.release_mut(to_release);
release.bins.push((pkg_idx, binary_name));
}
fn add_library(&mut self, to_release: ReleaseIdx, pkg_idx: PackageIdx, binary_name: String) {
let release = self.release_mut(to_release);
release.cdylibs.push((pkg_idx, binary_name));
}
fn add_static_library(
&mut self,
to_release: ReleaseIdx,
pkg_idx: PackageIdx,
binary_name: String,
) {
let release = self.release_mut(to_release);
release.cstaticlibs.push((pkg_idx, binary_name));
}
fn add_executable_zip(&mut self, to_release: ReleaseIdx) {
if !self.local_artifacts_enabled() {
return;
}
info!(
"adding executable zip to release {}",
self.release(to_release).id
);
let release = self.release(to_release);
let variants = release.variants.clone();
let checksum = self.inner.config.artifacts.checksum;
for variant_idx in variants {
let (zip_artifact, built_assets) =
self.make_executable_zip_for_variant(to_release, variant_idx);
let zip_artifact_idx = self.add_local_artifact(variant_idx, zip_artifact);
for (binary, dest_path) in built_assets {
self.require_binary(zip_artifact_idx, variant_idx, binary, dest_path);
}
if checksum != ChecksumStyle::False {
self.add_artifact_checksum(variant_idx, zip_artifact_idx, checksum);
}
if self.inner.config.builds.omnibor {
let omnibor = self.create_omnibor_artifact(zip_artifact_idx, false);
self.add_local_artifact(variant_idx, omnibor);
}
}
}
fn add_extra_artifacts(&mut self, app_config: &AppConfig, to_release: ReleaseIdx) {
if !self.global_artifacts_enabled() {
return;
}
let dist_dir = &self.inner.dist_dir.to_owned();
let artifacts = app_config.artifacts.extra.clone();
for extra in artifacts {
for artifact_relpath in extra.artifact_relpaths {
let artifact_name = ArtifactId::new(
artifact_relpath
.file_name()
.expect("extra artifact had no name!?")
.to_owned(),
);
let target_path = dist_dir.join(artifact_name.as_str());
let artifact = Artifact {
id: artifact_name,
target_triples: vec![],
file_path: target_path.to_owned(),
required_binaries: FastMap::new(),
archive: None,
kind: ArtifactKind::ExtraArtifact(ExtraArtifactImpl {
working_dir: extra.working_dir.clone(),
command: extra.command.clone(),
artifact_relpath,
}),
checksum: None,
is_global: true,
};
self.add_global_artifact(to_release, artifact);
}
}
}
fn add_cyclonedx_sbom_file(&mut self, to_package: PackageIdx, to_release: ReleaseIdx) {
let release = self.release(to_release);
if !self.global_artifacts_enabled() || !release.config.builds.cargo.cargo_cyclonedx {
return;
}
let package = self.workspaces.package(to_package);
let file_name = format!("{}.cdx.xml", package.true_name);
let file_path = Utf8Path::new("target/distrib/").join(file_name.clone());
self.add_global_artifact(
to_release,
Artifact {
id: ArtifactId::new(file_name),
target_triples: Default::default(),
archive: None,
file_path: file_path.clone(),
required_binaries: Default::default(),
kind: ArtifactKind::SBOM(SBOMImpl {}),
checksum: None,
is_global: true,
},
);
}
fn create_omnibor_artifact(&mut self, artifact_idx: ArtifactIdx, is_global: bool) -> Artifact {
let artifact = self.artifact(artifact_idx);
let id = artifact.id.clone();
let src_path = artifact.file_path.clone();
let extension = src_path
.extension()
.map_or("omnibor".to_string(), |e| format!("{e}.omnibor"));
let dest_path = src_path.with_extension(extension);
let new_id = format!("{}.omnibor", id);
Artifact {
id: ArtifactId::new(new_id),
target_triples: Default::default(),
archive: None,
file_path: dest_path.clone(),
required_binaries: Default::default(),
kind: ArtifactKind::OmniborArtifactId(OmniborArtifactIdImpl {
src_path,
dest_path,
}),
checksum: None,
is_global,
}
}
fn add_unified_checksum_file(&mut self, to_release: ReleaseIdx) {
if !self.global_artifacts_enabled() {
return;
}
let dist_dir = &self.inner.dist_dir;
let checksum = self.inner.config.artifacts.checksum;
let file_name = ArtifactId::new(format!("{}.sum", checksum.ext()));
let file_path = dist_dir.join(file_name.as_str());
self.add_global_artifact(
to_release,
Artifact {
id: file_name,
target_triples: Default::default(),
archive: None,
file_path: file_path.clone(),
required_binaries: Default::default(),
kind: ArtifactKind::UnifiedChecksum(UnifiedChecksumStep {
checksum,
dest_path: file_path,
}),
checksum: None, is_global: true,
},
);
}
fn add_source_tarball(&mut self, _tag: &str, to_release: ReleaseIdx) {
if !self.global_artifacts_enabled() {
return;
}
if !self.inner.config.artifacts.source_tarball {
return;
}
if self.inner.tools.git.is_none() {
warn!("skipping source tarball; git not installed");
return;
}
let working_dir = self.inner.workspace_dir.clone();
let workspace_repo = &self.workspaces.repo;
let is_git_repo = if self.inner.local_builds_are_lies {
true
} else {
workspace_repo.is_some()
};
let has_head = if self.inner.local_builds_are_lies {
true
} else if let Some(repo) = workspace_repo {
repo.head.is_some()
} else {
false
};
if !is_git_repo {
warn!(
"skipping source tarball; no git repo found at {}",
self.inner.workspace_dir
);
return;
}
if !has_head {
warn!(
"skipping source tarball; git repo at {} has no commits yet",
self.inner.workspace_dir
);
return;
}
let release = self.release(to_release);
let checksum = self.inner.config.artifacts.checksum;
info!("adding source tarball to release {}", release.id);
let dist_dir = &self.inner.dist_dir.to_owned();
let artifact_name = ArtifactId::new("source.tar.gz".to_owned());
let target_path = dist_dir.join(artifact_name.as_str());
let prefix = format!("{}-{}/", release.app_name, release.version);
let recursive = self.inner.config.artifacts.recursive_tarball;
let artifact = Artifact {
id: artifact_name.to_owned(),
target_triples: vec![],
file_path: target_path.to_owned(),
required_binaries: FastMap::new(),
archive: None,
kind: ArtifactKind::SourceTarball(SourceTarball {
committish: "HEAD".to_owned(),
prefix,
target: target_path.to_owned(),
working_dir,
recursive,
}),
checksum: None,
is_global: true,
};
let for_artifact = Some(artifact.id.clone());
let artifact_idx = self.add_global_artifact(to_release, artifact);
if checksum != ChecksumStyle::False {
let checksum_id = ArtifactId::new(format!("{artifact_name}.{}", checksum.ext()));
let checksum_path = dist_dir.join(checksum_id.as_str());
let checksum = Artifact {
id: checksum_id.to_owned(),
target_triples: vec![],
file_path: checksum_path.to_owned(),
required_binaries: FastMap::new(),
archive: None,
kind: ArtifactKind::Checksum(ChecksumImpl {
checksum,
src_path: target_path,
dest_path: Some(checksum_path),
for_artifact,
}),
checksum: None,
is_global: true,
};
let checksum_idx = self.add_global_artifact(to_release, checksum);
self.artifact_mut(artifact_idx).checksum = Some(checksum_idx);
}
if self.inner.config.builds.omnibor {
let omnibor = self.create_omnibor_artifact(artifact_idx, true);
self.add_global_artifact(to_release, omnibor);
}
}
fn add_artifact_checksum(
&mut self,
to_variant: ReleaseVariantIdx,
artifact_idx: ArtifactIdx,
checksum: ChecksumStyle,
) -> ArtifactIdx {
let artifact = self.artifact(artifact_idx);
let checksum_artifact = {
let checksum_ext = checksum.ext();
let checksum_id = ArtifactId::new(format!("{}.{}", artifact.id, checksum_ext));
let checksum_path = artifact
.file_path
.parent()
.unwrap()
.join(checksum_id.as_str());
Artifact {
id: checksum_id,
kind: ArtifactKind::Checksum(ChecksumImpl {
checksum,
src_path: artifact.file_path.clone(),
dest_path: Some(checksum_path.clone()),
for_artifact: Some(artifact.id.clone()),
}),
target_triples: artifact.target_triples.clone(),
archive: None,
file_path: checksum_path,
required_binaries: Default::default(),
checksum: None,
is_global: false,
}
};
let checksum_idx = self.add_local_artifact(to_variant, checksum_artifact);
self.artifact_mut(artifact_idx).checksum = Some(checksum_idx);
checksum_idx
}
fn add_updater(&mut self, variant_idx: ReleaseVariantIdx) {
if !self.local_artifacts_enabled() {
return;
}
let artifact = self.make_updater_for_variant(variant_idx);
self.add_local_artifact(variant_idx, artifact);
}
pub(crate) fn make_updater_for_variant(&self, variant_idx: ReleaseVariantIdx) -> Artifact {
let variant = self.variant(variant_idx);
let filename = ArtifactId::new(format!("{}-update", variant.id));
let target_path = &self.inner.dist_dir.to_owned().join(filename.as_str());
Artifact {
id: filename.to_owned(),
target_triples: vec![variant.target.to_owned()],
file_path: target_path.to_owned(),
required_binaries: FastMap::new(),
archive: None,
kind: ArtifactKind::Updater(UpdaterImpl {
use_latest: self.inner.config.installers.always_use_latest_updater,
}),
checksum: None,
is_global: false,
}
}
pub(crate) fn make_executable_zip_for_variant(
&self,
release_idx: ReleaseIdx,
variant_idx: ReleaseVariantIdx,
) -> (Artifact, Vec<(BinaryIdx, Utf8PathBuf)>) {
let dist_dir = &self.inner.dist_dir;
let release = self.release(release_idx);
let variant = self.variant(variant_idx);
let target_is_windows = variant.target.is_windows();
let zip_style = if target_is_windows {
release.config.artifacts.archives.windows_archive
} else {
release.config.artifacts.archives.unix_archive
};
let artifact_dir_name = variant.id.clone();
let artifact_dir_path = dist_dir.join(&artifact_dir_name);
let artifact_ext = zip_style.ext();
let artifact_name = ArtifactId::new(format!("{artifact_dir_name}{artifact_ext}"));
let artifact_path = dist_dir.join(artifact_name.as_str());
let static_assets = variant.static_assets.clone();
let mut built_assets = Vec::new();
for &binary_idx in &variant.binaries {
let binary = self.binary(binary_idx);
built_assets.push((binary_idx, artifact_dir_path.join(&binary.file_name)));
}
let with_root = if let ZipStyle::Zip = zip_style {
None
} else {
Some(Utf8PathBuf::from(artifact_dir_name.clone()))
};
(
Artifact {
id: artifact_name,
target_triples: vec![variant.target.clone()],
file_path: artifact_path,
required_binaries: FastMap::new(),
archive: Some(Archive {
with_root,
dir_path: artifact_dir_path,
zip_style,
static_assets,
}),
kind: ArtifactKind::ExecutableZip(ExecutableZip {}),
checksum: None,
is_global: false,
},
built_assets,
)
}
fn require_binary(
&mut self,
for_artifact: ArtifactIdx,
for_variant: ReleaseVariantIdx,
binary_idx: BinaryIdx,
dest_path: Utf8PathBuf,
) {
let dist_dir = self.inner.dist_dir.clone();
let binary = self.binary_mut(binary_idx);
binary.copy_exe_to.push(dest_path.clone());
if binary.symbols_artifact.is_none() {
if let Some(symbol_kind) = target_symbol_kind(&binary.target) {
let dest_symbol_ext = symbol_kind.ext();
let binary_id = &binary.id;
let dest_symbol_name = ArtifactId::new(format!("{binary_id}.{dest_symbol_ext}"));
let artifact_path = dist_dir.join(dest_symbol_name.as_str());
let artifact = Artifact {
id: dest_symbol_name,
target_triples: vec![binary.target.clone()],
archive: None,
file_path: artifact_path.clone(),
required_binaries: FastMap::new(),
kind: ArtifactKind::Symbols(Symbols { kind: symbol_kind }),
checksum: None,
is_global: false,
};
let sym_artifact = self.add_local_artifact(for_variant, artifact);
let binary = self.binary_mut(binary_idx);
binary.symbols_artifact = Some(sym_artifact);
binary.copy_symbols_to.push(artifact_path);
}
}
self.artifact_mut(for_artifact)
.required_binaries
.insert(binary_idx, dest_path);
}
fn add_shell_installer(&mut self, to_release: ReleaseIdx) -> DistResult<()> {
if !self.global_artifacts_enabled() {
return Ok(());
}
let release = self.release(to_release);
let Some(config) = &release.config.installers.shell else {
return Ok(());
};
require_nonempty_installer(release, config)?;
let release_id = &release.id;
let schema_release = self
.manifest
.release_by_name(&release.app_name)
.expect("couldn't find the release!?");
let env_vars = schema_release.env.clone();
let download_urls = schema_release
.artifact_download_urls()
.expect("couldn't compute a URL to download artifacts from!?");
let hosting = schema_release.hosting.clone();
let artifact_name = ArtifactId::new(format!("{release_id}-installer.sh"));
let artifact_path = self.inner.dist_dir.join(artifact_name.as_str());
let best_download_url = download_urls
.first()
.expect("returned empty list of artifact URLs!?");
let installer_url = format!("{best_download_url}/{artifact_name}");
let hint = format!("curl --proto '=https' --tlsv1.2 -LsSf {installer_url} | sh");
let desc = "Install prebuilt binaries via shell script".to_owned();
let artifacts = release
.platform_support
.fragments()
.into_iter()
.filter(|a| !a.target_triple.is_windows_msvc())
.collect::<Vec<_>>();
let target_triples = artifacts
.iter()
.map(|a| a.target_triple.clone())
.collect::<Vec<_>>();
if artifacts.is_empty() {
warn!("skipping shell installer: not building any supported platforms (use --artifacts=global)");
return Ok(());
};
let bin_aliases = BinaryAliases(config.bin_aliases.clone()).for_targets(&target_triples);
let runtime_conditions = release.platform_support.safe_conflated_runtime_conditions();
let installer_artifact = Artifact {
id: artifact_name,
target_triples,
archive: None,
file_path: artifact_path.clone(),
required_binaries: FastMap::new(),
checksum: None,
kind: ArtifactKind::Installer(InstallerImpl::Shell(InstallerInfo {
release: to_release,
dest_path: artifact_path,
app_name: release.app_name.clone(),
app_version: release.version.to_string(),
install_paths: config
.install_path
.iter()
.map(|p| p.clone().into_jinja())
.collect(),
install_success_msg: config.install_success_msg.to_owned(),
base_urls: download_urls.to_owned(),
hosting,
artifacts,
hint,
desc,
receipt: InstallReceipt::from_metadata(&self.inner, release)?,
bin_aliases,
install_libraries: config.install_libraries.clone(),
runtime_conditions,
platform_support: None,
env_vars,
})),
is_global: true,
};
self.add_global_artifact(to_release, installer_artifact);
Ok(())
}
fn add_homebrew_installer(&mut self, to_release: ReleaseIdx) -> DistResult<()> {
if !self.global_artifacts_enabled() {
return Ok(());
}
let release = self.release(to_release);
let Some(config) = &release.config.installers.homebrew else {
return Ok(());
};
require_nonempty_installer(release, config)?;
let formula = if let Some(formula) = &config.formula {
formula
} else {
&release.id
};
let schema_release = self
.manifest
.release_by_name(&release.id)
.expect("couldn't find the release!?");
let download_urls = schema_release
.artifact_download_urls()
.expect("couldn't compute a URL to download artifacts from!?");
let hosting = schema_release.hosting.clone();
let artifact_name = ArtifactId::new(format!("{formula}.rb"));
let artifact_path = self.inner.dist_dir.join(artifact_name.as_str());
let install_target = if let Some(tap) = &self.inner.global_homebrew_tap {
let tap = tap.replace("/homebrew-", "/");
format!("{tap}/{formula}")
} else {
formula.clone()
};
let hint = format!("brew install {}", install_target);
let desc = "Install prebuilt binaries via Homebrew".to_owned();
let artifacts = release
.platform_support
.fragments()
.into_iter()
.filter(|a| !a.target_triple.is_windows_msvc())
.collect::<Vec<_>>();
if artifacts.is_empty() {
warn!("skipping Homebrew installer: not building any supported platforms (use --artifacts=global)");
return Ok(());
};
let target_triples = artifacts
.iter()
.map(|a| a.target_triple.clone())
.collect::<Vec<_>>();
let find_fragment = |triple: &TripleNameRef| -> Option<ExecutableZipFragment> {
artifacts
.iter()
.find(|a| a.target_triple == triple)
.cloned()
};
let fragments = HomebrewFragments {
x86_64_macos: find_fragment(TARGET_X64_MAC),
arm64_macos: find_fragment(TARGET_ARM64_MAC),
x86_64_linux: find_fragment(TARGET_X64_LINUX_GNU),
arm64_linux: find_fragment(TARGET_ARM64_LINUX_GNU),
};
let release = self.release(to_release);
let app_name = release.app_name.clone();
let app_desc = release.app_desc.clone().unwrap_or_else(|| {
warn!("The Homebrew publish job is enabled but no description was specified\n consider adding `description = ` to package in Cargo.toml");
format!("The {} application", release.app_name)
});
let app_license = release.app_license.clone();
let homebrew_dsl_license = app_license.as_ref().map(|app_license| {
to_homebrew_license_format(app_license).unwrap_or(format!("\"{app_license}\""))
});
let app_homepage_url = if release.app_homepage_url.is_none() {
warn!("The Homebrew publish job is enabled but no homepage was specified\n consider adding `homepage = ` to package in Cargo.toml");
release.app_repository_url.clone()
} else {
release.app_homepage_url.clone()
};
let tap = config.tap.clone();
if tap.is_some() && release.config.publishers.homebrew.is_none() {
warn!("A Homebrew tap was specified but the Homebrew publish job is disabled\n consider adding \"homebrew\" to publish-jobs in Cargo.toml");
}
if release.config.publishers.homebrew.is_some() && tap.is_none() {
warn!("The Homebrew publish job is enabled but no tap was specified\n consider setting the tap field in Cargo.toml");
}
let runtime_conditions = release.platform_support.safe_conflated_runtime_conditions();
let dependencies: Vec<HomebrewPackageName> = release
.config
.builds
.system_dependencies
.homebrew
.clone()
.into_iter()
.filter(|(_, package)| package.0.stage_wanted(&DependencyKind::Run))
.map(|(name, _)| name)
.collect();
let bin_aliases = BinaryAliases(config.bin_aliases.clone()).for_targets(&target_triples);
let inner = InstallerInfo {
release: to_release,
dest_path: artifact_path.clone(),
app_name: release.app_name.clone(),
app_version: release.version.to_string(),
install_paths: config
.install_path
.iter()
.map(|p| p.clone().into_jinja())
.collect(),
install_success_msg: config.install_success_msg.to_owned(),
base_urls: download_urls,
hosting,
artifacts,
hint,
desc,
receipt: None,
bin_aliases,
install_libraries: config.install_libraries.clone(),
runtime_conditions,
platform_support: None,
env_vars: None,
};
let installer_artifact = Artifact {
id: artifact_name,
target_triples,
archive: None,
file_path: artifact_path,
required_binaries: Default::default(),
checksum: None,
kind: ArtifactKind::Installer(InstallerImpl::Homebrew(HomebrewImpl {
info: HomebrewInstallerInfo {
name: app_name,
formula_class: to_class_case(formula),
desc: app_desc,
license: homebrew_dsl_license,
homepage: app_homepage_url,
tap,
dependencies,
inner,
install_libraries: config.install_libraries.clone(),
},
fragments,
})),
is_global: true,
};
self.add_global_artifact(to_release, installer_artifact);
Ok(())
}
fn add_powershell_installer(&mut self, to_release: ReleaseIdx) -> DistResult<()> {
if !self.global_artifacts_enabled() {
return Ok(());
}
let release = self.release(to_release);
let Some(config) = &release.config.installers.powershell else {
return Ok(());
};
require_nonempty_installer(release, config)?;
let release_id = &release.id;
let schema_release = self
.manifest
.release_by_name(&release.app_name)
.expect("couldn't find the release!?");
let env_vars = schema_release.env.clone();
let download_urls = schema_release
.artifact_download_urls()
.expect("couldn't compute a URL to download artifacts from!?");
let hosting = schema_release.hosting.clone();
let artifact_name = ArtifactId::new(format!("{release_id}-installer.ps1"));
let artifact_path = self.inner.dist_dir.join(artifact_name.as_str());
let best_download_url = download_urls
.first()
.expect("returned empty list of artifact URLs!?");
let installer_url = format!("{best_download_url}/{artifact_name}");
let hint = format!(r#"powershell -ExecutionPolicy Bypass -c "irm {installer_url} | iex""#);
let desc = "Install prebuilt binaries via powershell script".to_owned();
let artifacts = release
.platform_support
.fragments()
.into_iter()
.filter(|a| a.target_triple.is_windows())
.collect::<Vec<_>>();
let target_triples = artifacts
.iter()
.map(|a| a.target_triple.clone())
.collect::<Vec<_>>();
if artifacts.is_empty() {
warn!("skipping powershell installer: not building any supported platforms (use --artifacts=global)");
return Ok(());
};
let bin_aliases = BinaryAliases(config.bin_aliases.clone()).for_targets(&target_triples);
let installer_artifact = Artifact {
id: artifact_name,
target_triples,
file_path: artifact_path.clone(),
required_binaries: FastMap::new(),
archive: None,
checksum: None,
kind: ArtifactKind::Installer(InstallerImpl::Powershell(InstallerInfo {
release: to_release,
dest_path: artifact_path,
app_name: release.app_name.clone(),
app_version: release.version.to_string(),
install_paths: config
.install_path
.iter()
.map(|p| p.clone().into_jinja())
.collect(),
install_success_msg: config.install_success_msg.to_owned(),
base_urls: download_urls,
hosting,
artifacts,
hint,
desc,
receipt: InstallReceipt::from_metadata(&self.inner, release)?,
bin_aliases,
install_libraries: config.install_libraries.clone(),
runtime_conditions: RuntimeConditions::default(),
platform_support: None,
env_vars,
})),
is_global: true,
};
self.add_global_artifact(to_release, installer_artifact);
Ok(())
}
fn add_npm_installer(&mut self, to_release: ReleaseIdx) -> DistResult<()> {
if !self.global_artifacts_enabled() {
return Ok(());
}
let release = self.release(to_release);
let Some(config) = &release.config.installers.npm else {
return Ok(());
};
require_nonempty_installer(release, config)?;
let release_id = &release.id;
let schema_release = self
.manifest
.release_by_name(&release.app_name)
.expect("couldn't find the release!?");
let download_urls = schema_release
.artifact_download_urls()
.expect("couldn't compute a URL to download artifacts from!?");
let hosting = schema_release.hosting.clone();
let app_name = config.package.clone();
let npm_package_name = if let Some(scope) = &config.scope {
if scope.to_ascii_lowercase() != *scope {
return Err(DistError::ScopeMustBeLowercase {
scope: scope.to_owned(),
});
}
format!("{scope}/{}", app_name)
} else {
app_name.clone()
};
let npm_package_version = release.version.to_string();
let npm_package_desc = release.app_desc.clone();
let npm_package_authors = release.app_authors.clone();
let npm_package_license = release.app_license.clone();
let npm_package_repository_url = release.app_repository_url.clone();
let npm_package_homepage_url = release.app_homepage_url.clone();
let npm_package_keywords = release.app_keywords.clone();
let static_assets = release.static_assets.clone();
let dir_name = format!("{release_id}-npm-package");
let dir_path = self.inner.dist_dir.join(&dir_name);
let zip_style = ZipStyle::Tar(CompressionImpl::Gzip);
let zip_ext = zip_style.ext();
let artifact_name = ArtifactId::new(format!("{dir_name}{zip_ext}"));
let artifact_path = self.inner.dist_dir.join(artifact_name.as_str());
let hint = format!("npm install {npm_package_name}@{npm_package_version}");
let desc = "Install prebuilt binaries into your npm project".to_owned();
let artifacts = release.platform_support.fragments();
let target_triples = artifacts
.iter()
.map(|a| a.target_triple.clone())
.collect::<Vec<_>>();
if artifacts.is_empty() {
warn!("skipping npm installer: not building any supported platforms (use --artifacts=global)");
return Ok(());
};
let bin_aliases = BinaryAliases(config.bin_aliases.clone()).for_targets(&target_triples);
let runtime_conditions = release.platform_support.safe_conflated_runtime_conditions();
let installer_artifact = Artifact {
id: artifact_name,
target_triples,
archive: Some(Archive {
with_root: Some("package".into()),
dir_path: dir_path.clone(),
zip_style,
static_assets,
}),
file_path: artifact_path.clone(),
required_binaries: FastMap::new(),
checksum: None,
kind: ArtifactKind::Installer(InstallerImpl::Npm(NpmInstallerInfo {
npm_package_name,
npm_package_version,
npm_package_desc,
npm_package_authors,
npm_package_license,
npm_package_repository_url,
npm_package_homepage_url,
npm_package_keywords,
create_shrinkwrap: config.shrinkwrap,
package_dir: dir_path,
inner: InstallerInfo {
release: to_release,
dest_path: artifact_path,
app_name,
app_version: release.version.to_string(),
install_paths: config
.install_path
.iter()
.map(|p| p.clone().into_jinja())
.collect(),
install_success_msg: config.install_success_msg.to_owned(),
base_urls: download_urls,
hosting,
artifacts,
hint,
desc,
receipt: None,
bin_aliases,
install_libraries: config.install_libraries.clone(),
runtime_conditions,
platform_support: None,
env_vars: None,
},
})),
is_global: true,
};
self.add_global_artifact(to_release, installer_artifact);
Ok(())
}
fn add_msi_installer(&mut self, to_release: ReleaseIdx) -> DistResult<()> {
if !self.local_artifacts_enabled() {
return Ok(());
}
let release = self.release(to_release);
let Some(_config) = &release.config.installers.msi else {
return Ok(());
};
let variants = release.variants.clone();
let checksum = self.inner.config.artifacts.checksum;
for variant_idx in variants {
let variant = self.variant(variant_idx);
let binaries = variant.binaries.clone();
let target = &variant.target;
if !target.is_windows() {
continue;
}
let variant_id = &variant.id;
let artifact_name = ArtifactId::new(format!("{variant_id}.msi"));
let artifact_path = self.inner.dist_dir.join(artifact_name.as_str());
let dir_name = format!("{variant_id}_msi");
let dir_path = self.inner.dist_dir.join(&dir_name);
let mut package_info: Option<(String, PackageIdx)> = None;
for &binary_idx in &binaries {
let binary = self.binary(binary_idx);
if let Some((existing_spec, _)) = &package_info {
if existing_spec != &binary.pkg_spec {
return Err(DistError::MultiPackage {
artifact_name,
spec1: existing_spec.clone(),
spec2: binary.pkg_spec.clone(),
})?;
}
} else {
package_info = Some((binary.pkg_spec.clone(), binary.pkg_idx));
}
}
let Some((pkg_spec, pkg_idx)) = package_info else {
return Err(DistError::NoPackage { artifact_name })?;
};
let manifest_path = self.workspaces.package(pkg_idx).manifest_path.clone();
let wxs_path = manifest_path
.parent()
.expect("Cargo.toml had no parent dir!?")
.join("wix")
.join("main.wxs");
let installer_artifact = Artifact {
id: artifact_name,
target_triples: vec![target.clone()],
file_path: artifact_path.clone(),
required_binaries: FastMap::new(),
archive: Some(Archive {
with_root: None,
dir_path: dir_path.clone(),
zip_style: ZipStyle::TempDir,
static_assets: vec![],
}),
checksum: None,
kind: ArtifactKind::Installer(InstallerImpl::Msi(MsiInstallerInfo {
package_dir: dir_path.clone(),
pkg_spec,
target: target.clone(),
file_path: artifact_path.clone(),
wxs_path,
manifest_path,
})),
is_global: false,
};
let installer_idx = self.add_local_artifact(variant_idx, installer_artifact);
for binary_idx in binaries {
let binary = self.binary(binary_idx);
self.require_binary(
installer_idx,
variant_idx,
binary_idx,
dir_path.join(&binary.file_name),
);
}
if checksum != ChecksumStyle::False {
self.add_artifact_checksum(variant_idx, installer_idx, checksum);
}
if self.inner.config.builds.omnibor {
let omnibor = self.create_omnibor_artifact(installer_idx, false);
self.add_local_artifact(variant_idx, omnibor);
}
}
Ok(())
}
fn add_pkg_installer(&mut self, to_release: ReleaseIdx) -> DistResult<()> {
if !self.local_artifacts_enabled() {
return Ok(());
}
let release = self.release(to_release);
let Some(config) = release.config.installers.pkg.clone() else {
return Ok(());
};
require_nonempty_installer(release, &config)?;
let version = release.version.clone();
let fragments = release.platform_support.fragments();
let variants = release.variants.clone();
let checksum = self.inner.config.artifacts.checksum;
for variant_idx in variants {
let variant = self.variant(variant_idx);
let binaries = variant.binaries.clone();
let bin_aliases = BinaryAliases(config.bin_aliases.clone());
let target = &variant.target;
if !target.is_darwin() {
continue;
}
let variant_id = &variant.id;
let artifact_name = ArtifactId::new(format!("{variant_id}.pkg"));
let artifact_path = self.inner.dist_dir.join(artifact_name.as_str());
let dir_name = format!("{variant_id}_pkg");
let dir_path = self.inner.dist_dir.join(&dir_name);
let mut package_info: Option<(String, PackageIdx)> = None;
for &binary_idx in &binaries {
let binary = self.binary(binary_idx);
if let Some((existing_spec, _)) = &package_info {
if existing_spec != &binary.pkg_spec {
return Err(DistError::MultiPackage {
artifact_name,
spec1: existing_spec.clone(),
spec2: binary.pkg_spec.clone(),
})?;
}
} else {
package_info = Some((binary.pkg_spec.clone(), binary.pkg_idx));
}
}
let Some(artifact) = fragments
.clone()
.into_iter()
.find(|a| a.target_triple == variant.target)
else {
return Err(DistError::NoPackage { artifact_name })?;
};
let bin_aliases = bin_aliases.for_target(&variant.target);
let identifier = if let Some(id) = &config.identifier {
id.to_owned()
} else {
return Err(DistError::MacPkgBundleIdentifierMissing {});
};
let installer_artifact = Artifact {
id: artifact_name,
target_triples: vec![target.clone()],
file_path: artifact_path.clone(),
required_binaries: FastMap::new(),
archive: Some(Archive {
with_root: None,
dir_path: dir_path.clone(),
zip_style: ZipStyle::TempDir,
static_assets: vec![],
}),
checksum: None,
kind: ArtifactKind::Installer(InstallerImpl::Pkg(PkgInstallerInfo {
file_path: artifact_path.clone(),
artifact,
package_dir: dir_path.clone(),
identifier,
install_location: config.install_location.clone(),
version: version.to_string(),
bin_aliases,
})),
is_global: false,
};
let installer_idx = self.add_local_artifact(variant_idx, installer_artifact);
for binary_idx in binaries {
let binary = self.binary(binary_idx);
self.require_binary(
installer_idx,
variant_idx,
binary_idx,
dir_path.join(&binary.file_name),
);
}
if checksum != ChecksumStyle::False {
self.add_artifact_checksum(variant_idx, installer_idx, checksum);
}
if self.inner.config.builds.omnibor {
let omnibor = self.create_omnibor_artifact(installer_idx, false);
self.add_local_artifact(variant_idx, omnibor);
}
}
Ok(())
}
fn add_local_artifact(
&mut self,
to_variant: ReleaseVariantIdx,
artifact: Artifact,
) -> ArtifactIdx {
assert!(self.local_artifacts_enabled());
assert!(!artifact.is_global);
let idx = ArtifactIdx(self.inner.artifacts.len());
let ReleaseVariant {
local_artifacts, ..
} = self.variant_mut(to_variant);
local_artifacts.push(idx);
self.inner.artifacts.push(artifact);
idx
}
fn add_global_artifact(&mut self, to_release: ReleaseIdx, artifact: Artifact) -> ArtifactIdx {
assert!(self.global_artifacts_enabled());
assert!(artifact.is_global);
let idx = ArtifactIdx(self.inner.artifacts.len());
let Release {
global_artifacts, ..
} = self.release_mut(to_release);
global_artifacts.push(idx);
self.inner.artifacts.push(artifact);
idx
}
fn compute_build_steps(&mut self) -> DistResult<()> {
let mut local_build_steps = vec![];
let mut global_build_steps = vec![];
for workspace_idx in self.workspaces.all_workspace_indices() {
let workspace_kind = self.workspaces.workspace(workspace_idx).kind;
let builds = match workspace_kind {
axoproject::WorkspaceKind::Javascript => {
self.compute_generic_builds(workspace_idx)?
}
axoproject::WorkspaceKind::Generic => self.compute_generic_builds(workspace_idx)?,
axoproject::WorkspaceKind::Rust => self.compute_cargo_builds(workspace_idx)?,
};
local_build_steps.extend(builds);
}
global_build_steps.extend(self.compute_extra_builds());
Self::add_build_steps_for_artifacts(
&self
.inner
.artifacts
.iter()
.filter(|a| !a.is_global)
.collect(),
&mut local_build_steps,
);
Self::add_build_steps_for_artifacts(
&self
.inner
.artifacts
.iter()
.filter(|a| a.is_global)
.collect(),
&mut global_build_steps,
);
self.inner.local_build_steps = local_build_steps;
self.inner.global_build_steps = global_build_steps;
Ok(())
}
fn add_build_steps_for_artifacts(artifacts: &Vec<&Artifact>, build_steps: &mut Vec<BuildStep>) {
for artifact in artifacts {
match &artifact.kind {
ArtifactKind::ExecutableZip(_zip) => {
}
ArtifactKind::Symbols(symbols) => {
match symbols.kind {
SymbolKind::Pdb => {
}
SymbolKind::Dsym => {
}
SymbolKind::Dwp => {
}
}
}
ArtifactKind::Installer(installer) => {
build_steps.push(BuildStep::GenerateInstaller(installer.clone()));
}
ArtifactKind::Checksum(checksum) => {
build_steps.push(BuildStep::Checksum(checksum.clone()));
}
ArtifactKind::UnifiedChecksum(unified_checksum) => {
build_steps.push(BuildStep::UnifiedChecksum(unified_checksum.clone()));
}
ArtifactKind::SourceTarball(tarball) => {
build_steps.push(BuildStep::GenerateSourceTarball(SourceTarballStep {
committish: tarball.committish.to_owned(),
prefix: tarball.prefix.to_owned(),
target: tarball.target.to_owned(),
working_dir: tarball.working_dir.to_owned(),
recursive: tarball.recursive,
}));
}
ArtifactKind::ExtraArtifact(_) => {
}
ArtifactKind::Updater(UpdaterImpl { use_latest }) => {
build_steps.push(BuildStep::Updater(UpdaterStep {
target_triple: artifact.target_triples.first().unwrap().to_owned(),
target_filename: artifact.file_path.to_owned(),
use_latest: *use_latest,
}))
}
ArtifactKind::SBOM(_) => {
}
ArtifactKind::OmniborArtifactId(src) => {
let src_path = src.src_path.clone();
let old_extension = src_path.extension().unwrap_or("");
let dest_path = src_path.with_extension(format!("{}.omnibor", old_extension));
build_steps.push(BuildStep::OmniborArtifactId(OmniborArtifactIdImpl {
src_path,
dest_path,
}));
}
}
if let Some(archive) = &artifact.archive {
let artifact_dir = &archive.dir_path;
for (_, src_path) in &archive.static_assets {
let src_path = src_path.clone();
let file_name = src_path.file_name().unwrap();
let dest_path = artifact_dir.join(file_name);
build_steps.push(BuildStep::CopyFileOrDir(CopyStep {
src_path,
dest_path,
}))
}
build_steps.push(BuildStep::Zip(ZipDirStep {
src_path: artifact_dir.to_owned(),
dest_path: artifact.file_path.clone(),
with_root: archive.with_root.clone(),
zip_style: archive.zip_style,
}));
build_steps.push(BuildStep::Checksum(ChecksumImpl {
checksum: ChecksumStyle::Sha256,
src_path: artifact.file_path.clone(),
dest_path: None,
for_artifact: Some(artifact.id.clone()),
}))
}
}
}
fn validate_distable_packages(&self, announcing: &AnnouncementTag) -> DistResult<()> {
for release in &announcing.rust_releases {
let package = self.workspaces.package(release.package_idx);
let workspace_idx = self.workspaces.workspace_for_package(release.package_idx);
let package_workspace = self.workspaces.workspace(workspace_idx);
let package_kind = package_workspace.kind;
if announcing.package.is_none() {
match package_kind {
axoproject::WorkspaceKind::Generic | axoproject::WorkspaceKind::Javascript => {
if let Some(build_command) = &package.build_command {
if build_command.len() == 1
&& build_command.first().unwrap().contains(' ')
{
return Err(DistError::SusBuildCommand {
manifest: package
.dist_manifest_path
.clone()
.unwrap_or_else(|| package.manifest_path.clone()),
command: build_command[0].clone(),
});
} else if build_command.is_empty() {
return Err(DistError::NoBuildCommand {
manifest: package
.dist_manifest_path
.clone()
.unwrap_or_else(|| package.manifest_path.clone()),
});
}
} else if package_kind == axoproject::WorkspaceKind::Javascript {
return Err(DistError::NoDistScript {
manifest: package.manifest_path.clone(),
});
} else {
return Err(DistError::NoBuildCommand {
manifest: package
.dist_manifest_path
.clone()
.unwrap_or_else(|| package.manifest_path.clone()),
});
}
}
axoproject::WorkspaceKind::Rust => {
if package.build_command.is_some() {
return Err(DistError::UnexpectedBuildCommand {
manifest: package
.dist_manifest_path
.clone()
.unwrap_or_else(|| package.manifest_path.clone()),
});
}
}
}
}
}
Ok(())
}
fn compute_releases(
&mut self,
cfg: &Config,
announcing: &AnnouncementTag,
triples: &[TripleName],
bypass_package_target_prefs: bool,
) -> DistResult<()> {
for info in &announcing.rust_releases {
let app_config = self.package_config(info.package_idx).clone();
let release = self.add_release(info.package_idx);
if info.executables.is_empty() && info.cdylibs.is_empty() && info.cstaticlibs.is_empty()
{
continue;
}
for binary in &info.executables {
self.add_binary(release, info.package_idx, binary.to_owned());
}
for lib in &info.cdylibs {
self.add_library(release, info.package_idx, lib.to_owned());
}
for lib in &info.cstaticlibs {
self.add_static_library(release, info.package_idx, lib.to_owned());
}
for target in triples {
let use_target =
bypass_package_target_prefs || app_config.targets.iter().any(|t| t == target);
if !use_target {
continue;
}
let variant = self.add_variant(release, target.clone())?;
if self.inner.config.installers.updater {
self.add_updater(variant);
}
}
self.add_executable_zip(release);
self.compute_platform_support(release);
self.add_source_tarball(&announcing.tag, release);
self.add_extra_artifacts(&app_config, release);
let installers = if cfg.installers.is_empty() {
&[
InstallerStyle::Shell,
InstallerStyle::Powershell,
InstallerStyle::Homebrew,
InstallerStyle::Npm,
InstallerStyle::Msi,
InstallerStyle::Pkg,
]
} else {
&cfg.installers[..]
};
for installer in installers {
match installer {
InstallerStyle::Shell => self.add_shell_installer(release)?,
InstallerStyle::Powershell => self.add_powershell_installer(release)?,
InstallerStyle::Homebrew => self.add_homebrew_installer(release)?,
InstallerStyle::Npm => self.add_npm_installer(release)?,
InstallerStyle::Msi => self.add_msi_installer(release)?,
InstallerStyle::Pkg => self.add_pkg_installer(release)?,
}
}
self.add_cyclonedx_sbom_file(info.package_idx, release);
if self.inner.config.artifacts.checksum != ChecksumStyle::False {
self.add_unified_checksum_file(release);
}
}
crate::manifest::add_releases_to_manifest(cfg, &self.inner, &mut self.manifest)?;
Ok(())
}
fn compute_ci(&mut self) -> DistResult<()> {
let CiConfig { github } = &self.inner.config.ci;
let mut has_ci = false;
if let Some(github_config) = github {
has_ci = true;
self.inner.ci.github = Some(GithubCiInfo::new(&self.inner, github_config)?);
}
if has_ci {
let CiInfo { github } = &self.inner.ci;
let github = github.as_ref().map(|info| {
let external_repo_commit = info
.github_release
.as_ref()
.and_then(|r| r.external_repo_commit.clone());
cargo_dist_schema::GithubCiInfo {
artifacts_matrix: Some(info.artifacts_matrix.clone()),
pr_run_mode: Some(info.pr_run_mode),
external_repo_commit,
}
});
self.manifest.ci = Some(cargo_dist_schema::CiInfo { github });
}
Ok(())
}
fn compute_platform_support(&mut self, release: ReleaseIdx) {
let support = PlatformSupport::new(self, release);
self.release_mut(release).platform_support = support;
}
pub(crate) fn binary(&self, idx: BinaryIdx) -> &Binary {
&self.inner.binaries[idx.0]
}
pub(crate) fn binary_mut(&mut self, idx: BinaryIdx) -> &mut Binary {
&mut self.inner.binaries[idx.0]
}
pub(crate) fn artifact(&self, idx: ArtifactIdx) -> &Artifact {
&self.inner.artifacts[idx.0]
}
pub(crate) fn artifact_mut(&mut self, idx: ArtifactIdx) -> &mut Artifact {
&mut self.inner.artifacts[idx.0]
}
pub(crate) fn release(&self, idx: ReleaseIdx) -> &Release {
&self.inner.releases[idx.0]
}
pub(crate) fn release_mut(&mut self, idx: ReleaseIdx) -> &mut Release {
&mut self.inner.releases[idx.0]
}
pub(crate) fn variant(&self, idx: ReleaseVariantIdx) -> &ReleaseVariant {
&self.inner.variants[idx.0]
}
pub(crate) fn variant_mut(&mut self, idx: ReleaseVariantIdx) -> &mut ReleaseVariant {
&mut self.inner.variants[idx.0]
}
pub(crate) fn local_artifacts_enabled(&self) -> bool {
match self.artifact_mode {
ArtifactMode::Local => true,
ArtifactMode::Global => false,
ArtifactMode::Host => true,
ArtifactMode::All => true,
ArtifactMode::Lies => true,
}
}
pub(crate) fn global_artifacts_enabled(&self) -> bool {
match self.artifact_mode {
ArtifactMode::Local => false,
ArtifactMode::Global => true,
ArtifactMode::Host => true,
ArtifactMode::All => true,
ArtifactMode::Lies => true,
}
}
pub(crate) fn package_config(&self, pkg_idx: PackageIdx) -> &AppConfig {
&self.package_configs[pkg_idx.0]
}
}
impl DistGraph {
pub fn binary(&self, idx: BinaryIdx) -> &Binary {
&self.binaries[idx.0]
}
pub fn artifact(&self, idx: ArtifactIdx) -> &Artifact {
&self.artifacts[idx.0]
}
pub fn release(&self, idx: ReleaseIdx) -> &Release {
&self.releases[idx.0]
}
pub fn variant(&self, idx: ReleaseVariantIdx) -> &ReleaseVariant {
&self.variants[idx.0]
}
}
pub fn gather_work(cfg: &Config) -> DistResult<(DistGraph, DistManifest)> {
info!("analyzing workspace:");
let tools = tool_info()?;
let mut workspaces = crate::config::get_project()?;
let system_id = format!(
"{}:{}:{}",
cfg.root_cmd,
cfg.artifact_mode,
cfg.targets.join(",")
);
let mut graph = DistGraphBuilder::new(
system_id,
tools,
&mut workspaces,
cfg.artifact_mode,
cfg.allow_all_dirty,
matches!(cfg.tag_settings.tag, TagMode::Infer),
)?;
let host_target_triple = [graph.inner.tools.host_target.clone()];
let all_target_triples = graph
.workspaces
.all_packages()
.flat_map(|(id, _)| &graph.package_config(id).targets)
.collect::<SortedSet<_>>()
.into_iter()
.cloned()
.collect::<Vec<_>>();
let mut bypass_package_target_prefs = false;
let triples = if cfg.targets.is_empty() {
if matches!(cfg.artifact_mode, ArtifactMode::Host) {
info!("using host target-triple");
bypass_package_target_prefs = true;
&host_target_triple
} else if all_target_triples.is_empty() {
return Err(DistError::CliMissingTargets {
host_target: graph.inner.tools.host_target.clone(),
});
} else {
info!("using all target-triples");
&all_target_triples[..]
}
} else {
info!("using explicit target-triples");
&cfg.targets[..]
};
info!("selected triples: {:?}", triples);
let announcing = announce::select_tag(&mut graph, &cfg.tag_settings)?;
graph.validate_distable_packages(&announcing)?;
crate::manifest::load_and_merge_manifests(
&graph.inner.dist_dir,
&mut graph.manifest,
&announcing,
)?;
graph.compute_hosting(cfg, &announcing)?;
graph.compute_releases(cfg, &announcing, triples, bypass_package_target_prefs)?;
graph.compute_announcement_info(&announcing);
graph.compute_build_steps()?;
graph.compute_ci()?;
Ok((graph.inner, graph.manifest))
}
pub fn cargo() -> DistResult<String> {
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned());
Ok(cargo)
}
pub fn get_cargo_info(cargo: String) -> DistResult<CargoInfo> {
let mut command = Cmd::new(&cargo, "get your Rust toolchain's version");
command.arg("-vV");
let output = command.output()?;
let output = String::from_utf8(output.stdout).map_err(|_| DistError::FailedCargoVersion)?;
let mut lines = output.lines();
let version_line = lines.next().map(|s| s.to_owned());
for line in lines {
if let Some(target) = line.strip_prefix("host: ") {
info!("host target is {target}");
return Ok(CargoInfo {
cmd: cargo,
version_line,
host_target: TripleName::new(target.to_owned()),
});
}
}
Err(DistError::FailedCargoVersion)
}
fn target_symbol_kind(target: &TripleNameRef) -> Option<SymbolKind> {
#[allow(clippy::if_same_then_else)]
if target.is_windows_msvc() {
None
} else if target.is_apple() {
None
} else {
None
}
}
fn tool_info() -> DistResult<Tools> {
let cargo = if let Ok(cargo_cmd) = cargo() {
get_cargo_info(cargo_cmd).ok()
} else {
None
};
Ok(Tools {
host_target: TripleName::new(current_platform::CURRENT_PLATFORM.to_owned()),
cargo,
rustup: find_tool("rustup", "-V"),
brew: find_tool("brew", "--version"),
git: find_tool("git", "--version"),
omnibor: find_tool("omnibor", "--version"),
code_sign_tool: None,
cargo_auditable: find_cargo_subcommand("cargo", "auditable", "--version"),
cargo_cyclonedx: find_cargo_subcommand("cargo", "cyclonedx", "--version"),
cargo_xwin: find_cargo_subcommand("cargo", "xwin", "--version"),
cargo_zigbuild: find_tool("cargo-zigbuild", "--version"),
})
}
fn find_cargo_subcommand(name: &str, arg: &str, test_flag: &str) -> Option<Tool> {
let output = Cmd::new(name, "detect tool")
.arg(arg)
.arg(test_flag)
.check(false)
.output()
.ok()?;
let string_output = String::from_utf8(output.stdout).ok()?;
let version = string_output.lines().next()?;
Some(Tool {
cmd: format!("{} {}", name, arg),
version: version.to_owned(),
})
}
fn find_tool(name: &str, test_flag: &str) -> Option<Tool> {
let output = Cmd::new(name, "detect tool")
.arg(test_flag)
.check(false)
.output()
.ok()?;
let string_output = String::from_utf8(output.stdout).ok()?;
let version = string_output.lines().next()?;
Some(Tool {
cmd: name.to_owned(),
version: version.to_owned(),
})
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ReleaseSourceType {
GitHub,
Axo,
}
#[derive(Clone, Debug, Serialize)]
pub struct ReleaseSource {
pub release_type: ReleaseSourceType,
pub owner: String,
pub name: String,
pub app_name: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum ProviderSource {
CargoDist,
}
#[derive(Clone, Debug, Serialize)]
pub struct Provider {
pub source: ProviderSource,
pub version: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum InstallLayout {
Unspecified,
Flat,
Hierarchical,
CargoHome,
}
#[derive(Clone, Debug, Serialize)]
pub struct InstallReceipt {
pub install_prefix: String,
pub install_layout: InstallLayout,
pub binaries: Vec<String>,
pub cdylibs: Vec<String>,
pub cstaticlibs: Vec<String>,
pub source: ReleaseSource,
pub version: String,
pub provider: Provider,
pub binary_aliases: BTreeMap<String, Vec<String>>,
pub modify_path: bool,
}
impl InstallReceipt {
pub fn from_metadata(
manifest: &DistGraph,
release: &Release,
) -> DistResult<Option<InstallReceipt>> {
let hosting = if let Some(hosting) = &manifest.hosting {
hosting
} else {
return Ok(None);
};
let source_type = if hosting.hosts.contains(&HostingStyle::Github) {
ReleaseSourceType::GitHub
} else {
return Err(DistError::NoGitHubHosting {});
};
Ok(Some(InstallReceipt {
install_prefix: "AXO_INSTALL_PREFIX".to_owned(),
install_layout: InstallLayout::Unspecified,
binaries: vec!["CARGO_DIST_BINS".to_owned()],
cdylibs: vec!["CARGO_DIST_DYLIBS".to_owned()],
cstaticlibs: vec!["CARGO_DIST_STATICLIBS".to_owned()],
version: release.version.to_string(),
source: ReleaseSource {
release_type: source_type,
owner: hosting.owner.to_owned(),
name: hosting.project.to_owned(),
app_name: release.app_name.to_owned(),
},
provider: Provider {
source: ProviderSource::CargoDist,
version: env!("CARGO_PKG_VERSION").to_owned(),
},
binary_aliases: BTreeMap::default(),
modify_path: true,
}))
}
}
fn require_nonempty_installer(release: &Release, config: &CommonInstallerConfig) -> DistResult<()> {
if config.install_libraries.is_empty() && release.bins.is_empty() {
Err(DistError::EmptyInstaller {})
} else {
Ok(())
}
}