#![deny(missing_docs)]
#![allow(clippy::single_match, clippy::result_large_err)]
#![allow(unused_assignments)]
use announce::TagSettings;
use axoasset::LocalAsset;
use axoprocess::Cmd;
use backend::{
ci::CiInfo,
installer::{
self, macpkg::PkgInstallerInfo, msi::MsiInstallerInfo, HomebrewImpl, InstallerImpl,
},
};
use build::generic::{build_generic_target, run_extra_artifacts_build};
use build::{
cargo::{build_cargo_target, rustup_toolchain},
fake::{build_fake_cargo_target, build_fake_generic_target},
};
use camino::{Utf8Path, Utf8PathBuf};
use cargo_dist_schema::{ArtifactId, ChecksumValue, ChecksumValueRef, DistManifest, TripleName};
use config::{
ArtifactMode, ChecksumStyle, CompressionImpl, Config, DirtyMode, GenerateMode, ZipStyle,
};
use semver::Version;
use temp_dir::TempDir;
use tracing::info;
use errors::*;
pub use init::{do_init, do_migrate, InitArgs};
pub use tasks::*;
pub mod announce;
pub mod backend;
pub mod build;
pub mod config;
pub mod env;
pub mod errors;
pub mod host;
mod init;
pub mod linkage;
pub mod manifest;
pub mod net;
pub mod platform;
pub mod sign;
pub mod tasks;
#[cfg(test)]
mod tests;
pub fn do_env_test(cfg: &Config) -> DistResult<()> {
let (dist, _manifest) = tasks::gather_work(cfg)?;
let local_builds = matches!(
cfg.artifact_mode,
ArtifactMode::Local | ArtifactMode::All | ArtifactMode::Host
);
let builds = dist.config.builds;
let need_cargo_auditable = builds.cargo.cargo_auditable && local_builds;
let need_omnibor = builds.omnibor;
let mut need_xwin = false;
let mut need_zigbuild = false;
let tools = dist.tools;
let host = tools.host_target.parse()?;
for step in dist.local_build_steps.iter() {
if cfg.artifact_mode == ArtifactMode::Lies {
break;
}
match step {
BuildStep::Cargo(step) => {
let target = step.target_triple.parse()?;
let wrapper = tasks::build_wrapper_for_cross(&host, &target)?;
match wrapper {
Some(CargoBuildWrapper::Xwin) => {
need_xwin = true;
}
Some(CargoBuildWrapper::ZigBuild) => {
need_zigbuild = true;
}
None => {}
}
}
_ => {}
}
}
let all_tools: Vec<Option<DistResult<&Tool>>> = vec![
need_cargo_auditable.then(|| tools.cargo_auditable()),
need_omnibor.then(|| tools.omnibor()),
need_xwin.then(|| tools.cargo_xwin()),
need_zigbuild.then(|| tools.cargo_zigbuild()),
];
let needed_tools = all_tools.into_iter().flatten();
let missing: Vec<String> = needed_tools
.filter_map(|t| match t {
Ok(_) => None,
Err(DistError::ToolMissing { tool: ref name }) => Some(name.to_owned()),
Err(_) => unreachable!(
"do_env_test() got an Err that wasn't DistError::ToolMissing. This is a dist bug."
),
})
.collect();
missing
.is_empty()
.then_some(())
.ok_or(DistError::EnvToolsMissing { tools: missing })
}
pub fn do_build(cfg: &Config) -> DistResult<DistManifest> {
do_env_test(cfg)?;
check_integrity(cfg)?;
let (dist, mut manifest) = tasks::gather_work(cfg)?;
if !dist.dist_dir.exists() {
LocalAsset::create_dir_all(&dist.dist_dir)?;
}
eprintln!("building artifacts:");
for artifact in &dist.artifacts {
eprintln!(" {}", artifact.id);
init_artifact_dir(&dist, artifact)?;
}
eprintln!();
for step in &dist.local_build_steps {
if dist.local_builds_are_lies {
build_fake(&dist, step, &mut manifest)?;
} else {
run_build_step(&dist, step, &mut manifest)?;
}
}
for step in &dist.global_build_steps {
if dist.local_builds_are_lies {
build_fake(&dist, step, &mut manifest)?;
} else {
run_build_step(&dist, step, &mut manifest)?;
}
}
Ok(manifest)
}
pub fn do_manifest(cfg: &Config) -> DistResult<DistManifest> {
check_integrity(cfg)?;
let (_dist, manifest) = gather_work(cfg)?;
Ok(manifest)
}
fn run_build_step(
dist_graph: &DistGraph,
target: &BuildStep,
manifest: &mut DistManifest,
) -> DistResult<()> {
match target {
BuildStep::Generic(target) => build_generic_target(dist_graph, manifest, target)?,
BuildStep::Cargo(target) => build_cargo_target(dist_graph, manifest, target)?,
BuildStep::Rustup(cmd) => rustup_toolchain(dist_graph, cmd)?,
BuildStep::CopyFile(CopyStep {
src_path,
dest_path,
}) => copy_file(src_path, dest_path)?,
BuildStep::CopyDir(CopyStep {
src_path,
dest_path,
}) => copy_dir(src_path, dest_path)?,
BuildStep::CopyFileOrDir(CopyStep {
src_path,
dest_path,
}) => copy_file_or_dir(src_path, dest_path)?,
BuildStep::Zip(ZipDirStep {
src_path,
dest_path,
zip_style,
with_root,
}) => zip_dir(src_path, dest_path, zip_style, with_root.as_deref())?,
BuildStep::GenerateInstaller(installer) => {
generate_installer(dist_graph, installer, manifest)?
}
BuildStep::Checksum(ChecksumImpl {
checksum,
src_path,
dest_path,
for_artifact,
}) => generate_and_write_checksum(
manifest,
checksum,
src_path,
dest_path.as_deref(),
for_artifact.as_ref(),
)?,
BuildStep::UnifiedChecksum(UnifiedChecksumStep {
checksum,
dest_path,
}) => generate_unified_checksum(manifest, *checksum, dest_path)?,
BuildStep::OmniborArtifactId(OmniborArtifactIdImpl {
src_path,
dest_path,
}) => generate_omnibor_artifact_id(dist_graph, src_path, dest_path)?,
BuildStep::GenerateSourceTarball(SourceTarballStep {
committish,
prefix,
target,
working_dir,
recursive,
}) => {
if *recursive {
generate_recursive_source_tarball(
dist_graph,
committish,
prefix,
target,
working_dir,
)?
} else {
generate_source_tarball(dist_graph, committish, prefix, target, working_dir)?
}
}
BuildStep::Extra(target) => run_extra_artifacts_build(dist_graph, target)?,
BuildStep::Updater(updater) => fetch_updater(dist_graph, updater)?,
};
Ok(())
}
const AXOUPDATER_ASSET_ROOT: &str = "https://github.com/axodotdev/axoupdater/releases";
const AXOUPDATER_MINIMUM_VERSION: &str = "0.9.0";
fn axoupdater_latest_asset_root() -> String {
format!("{AXOUPDATER_ASSET_ROOT}/latest/download")
}
fn axoupdater_asset_root() -> String {
format!("{AXOUPDATER_ASSET_ROOT}/download/v{}", axoupdater::VERSION)
}
pub fn fetch_updater(dist_graph: &DistGraph, updater: &UpdaterStep) -> DistResult<()> {
let ext = if updater.target_triple.is_windows() {
".zip"
} else {
".tar.xz"
};
let asset_root = if updater.use_latest {
axoupdater_latest_asset_root()
} else {
axoupdater_asset_root()
};
let expected_url = format!(
"{}/axoupdater-cli-{}{ext}",
asset_root, updater.target_triple
);
let handle = tokio::runtime::Handle::current();
let resp = handle
.block_on(dist_graph.axoclient.head(&expected_url))
.map_err(|_| DistError::AxoupdaterReleaseCheckFailed {})?;
if resp.status().is_success() {
fetch_updater_from_binary(dist_graph, updater, &expected_url)
} else if resp.status() == axoasset::reqwest::StatusCode::NOT_FOUND {
Err(DistError::NoAxoupdaterForTarget {
target: updater.target_triple.to_string(),
})
} else {
Err(DistError::AxoupdaterReleaseCheckFailed {})
}
}
pub fn create_tmp() -> DistResult<(TempDir, Utf8PathBuf)> {
let tmp_dir = TempDir::new()?;
let tmp_root =
Utf8PathBuf::from_path_buf(tmp_dir.path().to_owned()).expect("tempdir isn't utf8!?");
Ok((tmp_dir, tmp_root))
}
fn fetch_updater_from_binary(
dist_graph: &DistGraph,
updater: &UpdaterStep,
asset_url: &str,
) -> DistResult<()> {
let (_tmp_dir, tmp_root) = create_tmp()?;
let zipball_target = tmp_root.join("archive");
let handle = tokio::runtime::Handle::current();
handle.block_on(
dist_graph
.axoclient
.load_and_write_to_file(asset_url, &zipball_target),
)?;
let suffix = if updater.target_triple.is_windows() {
".exe"
} else {
""
};
let requested_filename = format!("axoupdater{suffix}");
let bytes = if asset_url.ends_with(".tar.xz") {
LocalAsset::untar_xz_file(&zipball_target, &requested_filename)?
} else if asset_url.ends_with(".tar.gz") {
LocalAsset::untar_gz_file(&zipball_target, &requested_filename)?
} else if asset_url.ends_with(".zip") {
LocalAsset::unzip_file(&zipball_target, &requested_filename)?
} else {
let extension = Utf8PathBuf::from(asset_url)
.extension()
.unwrap_or("unable to determine")
.to_owned();
return Err(DistError::UnrecognizedCompression { extension });
};
let target = dist_graph.target_dir.join(&updater.target_filename);
std::fs::write(target, bytes)?;
Ok(())
}
fn build_fake(
dist_graph: &DistGraph,
target: &BuildStep,
manifest: &mut DistManifest,
) -> DistResult<()> {
match target {
BuildStep::Generic(target) => build_fake_generic_target(dist_graph, manifest, target)?,
BuildStep::Cargo(target) => build_fake_cargo_target(dist_graph, manifest, target)?,
BuildStep::Rustup(_) => {}
BuildStep::CopyFile(CopyStep {
src_path,
dest_path,
}) => copy_file(src_path, dest_path)?,
BuildStep::CopyDir(CopyStep {
src_path,
dest_path,
}) => copy_dir(src_path, dest_path)?,
BuildStep::CopyFileOrDir(CopyStep {
src_path,
dest_path,
}) => copy_file_or_dir(src_path, dest_path)?,
BuildStep::Zip(ZipDirStep {
src_path,
dest_path,
zip_style,
with_root,
}) => zip_dir(src_path, dest_path, zip_style, with_root.as_deref())?,
BuildStep::GenerateInstaller(installer) => match installer {
InstallerImpl::Msi(msi) => generate_fake_msi(dist_graph, msi, manifest)?,
InstallerImpl::Pkg(pkg) => generate_fake_pkg(dist_graph, pkg, manifest)?,
_ => generate_installer(dist_graph, installer, manifest)?,
},
BuildStep::Checksum(ChecksumImpl {
checksum,
src_path,
dest_path,
for_artifact,
}) => generate_and_write_checksum(
manifest,
checksum,
src_path,
dest_path.as_deref(),
for_artifact.as_ref(),
)?,
BuildStep::UnifiedChecksum(UnifiedChecksumStep {
checksum,
dest_path,
}) => generate_unified_checksum(manifest, *checksum, dest_path)?,
BuildStep::OmniborArtifactId(OmniborArtifactIdImpl {
src_path,
dest_path,
}) => generate_omnibor_artifact_id(dist_graph, src_path, dest_path)?,
BuildStep::GenerateSourceTarball(SourceTarballStep {
committish,
prefix,
target,
working_dir,
recursive: _,
}) => generate_fake_source_tarball(dist_graph, committish, prefix, target, working_dir)?,
BuildStep::Extra(target) => run_fake_extra_artifacts_build(dist_graph, target)?,
BuildStep::Updater(_) => unimplemented!(),
}
Ok(())
}
fn run_fake_extra_artifacts_build(dist: &DistGraph, target: &ExtraBuildStep) -> DistResult<()> {
for artifact in &target.artifact_relpaths {
let path = dist.dist_dir.join(artifact);
LocalAsset::write_new_all("", &path)?;
}
Ok(())
}
fn generate_fake_msi(
_dist: &DistGraph,
msi: &MsiInstallerInfo,
_manifest: &DistManifest,
) -> DistResult<()> {
LocalAsset::write_new_all("", &msi.file_path)?;
Ok(())
}
fn generate_fake_pkg(
_dist: &DistGraph,
pkg: &PkgInstallerInfo,
_manifest: &DistManifest,
) -> DistResult<()> {
LocalAsset::write_new_all("", &pkg.file_path)?;
Ok(())
}
fn generate_omnibor_artifact_id(
dist_graph: &DistGraph,
src_path: &Utf8Path,
dest_path: &Utf8Path,
) -> DistResult<()> {
let omnibor = dist_graph.tools.omnibor()?;
let mut cmd = Cmd::new(&omnibor.cmd, "generate an OmniBOR Artifact ID");
cmd.arg("artifact")
.arg("id")
.arg("--format")
.arg("short")
.arg("--path")
.arg(src_path);
let output = cmd.output()?.stdout;
let output = String::from_utf8_lossy(&output);
LocalAsset::write_new_all(&output, dest_path)?;
Ok(())
}
fn generate_and_write_checksum(
manifest: &mut DistManifest,
checksum: &ChecksumStyle,
src_path: &Utf8Path,
dest_path: Option<&Utf8Path>,
for_artifact: Option<&ArtifactId>,
) -> DistResult<()> {
let output = generate_checksum(checksum, src_path)?;
if let Some(dest_path) = dest_path {
let name = src_path.file_name().expect("hashing file with no name!?");
write_checksum_file(&[(name, &output)], dest_path)?;
}
if let Some(artifact_id) = for_artifact {
if let Some(artifact) = manifest.artifacts.get_mut(artifact_id) {
artifact.checksums.insert(checksum.ext().to_owned(), output);
}
}
Ok(())
}
fn generate_unified_checksum(
manifest: &DistManifest,
checksum: ChecksumStyle,
dest_path: &Utf8Path,
) -> DistResult<()> {
let expected_checksum_ext = checksum.ext();
let mut entries: Vec<(&str, &ChecksumValueRef)> = vec![];
for artifact in manifest.artifacts.values() {
let artifact_name = if let Some(artifact_name) = artifact.name.as_deref() {
artifact_name
} else {
continue;
};
for (checksum_ext, checksum) in &artifact.checksums {
if checksum_ext == expected_checksum_ext {
entries.push((artifact_name.as_str(), checksum));
}
}
}
write_checksum_file(&entries, dest_path)?;
Ok(())
}
fn generate_checksum(checksum: &ChecksumStyle, src_path: &Utf8Path) -> DistResult<ChecksumValue> {
info!("generating {checksum:?} for {src_path}");
use sha2::Digest;
use std::fmt::Write;
let file_bytes = axoasset::LocalAsset::load_bytes(src_path.as_str())?;
let hash = match checksum {
ChecksumStyle::Sha256 => {
let mut hasher = sha2::Sha256::new();
hasher.update(&file_bytes);
hasher.finalize().as_slice().to_owned()
}
ChecksumStyle::Sha512 => {
let mut hasher = sha2::Sha512::new();
hasher.update(&file_bytes);
hasher.finalize().as_slice().to_owned()
}
ChecksumStyle::Sha3_256 => {
let mut hasher = sha3::Sha3_256::new();
hasher.update(&file_bytes);
hasher.finalize().as_slice().to_owned()
}
ChecksumStyle::Sha3_512 => {
let mut hasher = sha3::Sha3_512::new();
hasher.update(&file_bytes);
hasher.finalize().as_slice().to_owned()
}
ChecksumStyle::Blake2s => {
let mut hasher = blake2::Blake2s256::new();
hasher.update(&file_bytes);
hasher.finalize().as_slice().to_owned()
}
ChecksumStyle::Blake2b => {
let mut hasher = blake2::Blake2b512::new();
hasher.update(&file_bytes);
hasher.finalize().as_slice().to_owned()
}
ChecksumStyle::False => {
unreachable!()
}
};
let mut output = String::with_capacity(hash.len() * 2);
for byte in hash {
write!(&mut output, "{:02x}", byte).unwrap();
}
Ok(ChecksumValue::new(output))
}
fn generate_source_tarball(
graph: &DistGraph,
committish: &str,
prefix: &str,
target: &Utf8Path,
working_dir: &Utf8Path,
) -> DistResult<()> {
let git = if let Some(tool) = &graph.tools.git {
tool.cmd.to_owned()
} else {
return Err(DistError::ToolMissing {
tool: "git".to_owned(),
});
};
Cmd::new(git, "generate a source tarball for your project")
.arg("archive")
.arg(committish)
.arg("--format=tar.gz")
.arg("--prefix")
.arg(prefix)
.arg("--output")
.arg(target)
.current_dir(working_dir)
.run()?;
Ok(())
}
fn generate_recursive_source_tarball(
graph: &DistGraph,
_committish: &str,
prefix: &str,
target: &Utf8Path,
working_dir: &Utf8Path,
) -> DistResult<()> {
let git = if let Some(tool) = &graph.tools.git {
tool.cmd.to_owned()
} else {
return Err(DistError::ToolMissing {
tool: "git".to_owned(),
});
};
let output = Cmd::new(git, "generate a source tarball for your project")
.arg("ls-files")
.arg("--recurse-submodules")
.arg("-z")
.current_dir(working_dir)
.output()?;
let file_paths = output.stdout.split(|byte| *byte == 0);
let (_temp_dir, temp_path) = create_tmp()?;
for raw_relpath in file_paths {
let relpath = String::from_utf8(raw_relpath.to_owned()).expect("non-utf8 path");
let src_path = working_dir.join(&relpath);
let dest_path = temp_path.join(prefix).join(&relpath);
if !src_path.exists() {
continue;
}
if src_path.is_dir() {
axoasset::LocalAsset::create_dir_all(dest_path)?;
} else {
if let Some(dest_parent) = dest_path.parent() {
axoasset::LocalAsset::create_dir_all(dest_parent)?;
}
if src_path.is_symlink() {
if cfg!(unix) {
#[cfg(unix)]
{
let link_name = std::fs::read_link(&src_path)?;
std::os::unix::fs::symlink(link_name, dest_path).unwrap();
}
} else {
axoasset::LocalAsset::create_dir_all(dest_path)?;
}
} else {
axoasset::LocalAsset::copy_file_to_file(src_path, dest_path)?;
}
}
}
axoasset::LocalAsset::tar_gz_dir(temp_path.join(prefix), target, None::<&Utf8Path>)?;
Ok(())
}
fn generate_fake_source_tarball(
_graph: &DistGraph,
_committish: &str,
_prefix: &str,
target: &Utf8Path,
_working_dir: &Utf8Path,
) -> DistResult<()> {
LocalAsset::write_new_all("", target)?;
Ok(())
}
fn write_checksum_file(
entries: &[(&str, &ChecksumValueRef)],
dest_path: &Utf8Path,
) -> DistResult<()> {
let mut contents = String::new();
for (file_path, checksum) in entries {
use std::fmt::Write;
writeln!(&mut contents, "{checksum} *{file_path}",).unwrap();
}
contents.push('\n');
axoasset::LocalAsset::write_new(&contents, dest_path)?;
Ok(())
}
fn init_artifact_dir(_dist: &DistGraph, artifact: &Artifact) -> DistResult<()> {
if artifact.file_path.exists() {
LocalAsset::remove_file(&artifact.file_path)?;
}
let Some(archive) = &artifact.archive else {
return Ok(());
};
info!("recreating artifact dir: {}", archive.dir_path);
if archive.dir_path.exists() {
LocalAsset::remove_dir_all(&archive.dir_path)?;
}
LocalAsset::create_dir(&archive.dir_path)?;
Ok(())
}
pub(crate) fn copy_file(src_path: &Utf8Path, dest_path: &Utf8Path) -> DistResult<()> {
LocalAsset::copy_file_to_file(src_path, dest_path)?;
Ok(())
}
pub(crate) fn copy_dir(src_path: &Utf8Path, dest_path: &Utf8Path) -> DistResult<()> {
LocalAsset::copy_dir_to_dir(src_path, dest_path)?;
Ok(())
}
pub(crate) fn copy_file_or_dir(src_path: &Utf8Path, dest_path: &Utf8Path) -> DistResult<()> {
if src_path.is_dir() {
copy_dir(src_path, dest_path)
} else {
copy_file(src_path, dest_path)
}
}
fn zip_dir(
src_path: &Utf8Path,
dest_path: &Utf8Path,
zip_style: &ZipStyle,
with_root: Option<&Utf8Path>,
) -> DistResult<()> {
match zip_style {
ZipStyle::Zip => LocalAsset::zip_dir(src_path, dest_path, with_root)?,
ZipStyle::Tar(CompressionImpl::Gzip) => {
LocalAsset::tar_gz_dir(src_path, dest_path, with_root)?
}
ZipStyle::Tar(CompressionImpl::Xzip) => {
LocalAsset::tar_xz_dir(src_path, dest_path, with_root)?
}
ZipStyle::Tar(CompressionImpl::Zstd) => {
LocalAsset::tar_zstd_dir(src_path, dest_path, with_root)?
}
ZipStyle::TempDir => {
}
}
Ok(())
}
#[derive(Debug)]
pub struct GenerateArgs {
pub check: bool,
pub modes: Vec<GenerateMode>,
}
fn do_generate_preflight_checks(dist: &DistGraph) -> DistResult<()> {
if let Some(desired_version) = &dist.config.dist_version {
let current_version: Version = std::env!("CARGO_PKG_VERSION").parse().unwrap();
if desired_version != ¤t_version
&& !desired_version.pre.starts_with("github-")
&& !matches!(dist.allow_dirty, DirtyMode::AllowAll)
{
return Err(DistError::MismatchedDistVersion {
config_version: desired_version.to_string(),
running_version: current_version.to_string(),
});
}
}
if !dist.is_init {
return Err(DistError::NeedsInit);
}
Ok(())
}
pub fn do_generate(cfg: &Config, args: &GenerateArgs) -> DistResult<()> {
let (dist, _manifest) = gather_work(cfg)?;
run_generate(&dist, args)?;
Ok(())
}
pub fn run_generate(dist: &DistGraph, args: &GenerateArgs) -> DistResult<()> {
do_generate_preflight_checks(dist)?;
let inferred = args.modes.is_empty();
let modes = if inferred {
&[GenerateMode::Ci, GenerateMode::Msi]
} else {
for &mode in &args.modes {
if !dist.allow_dirty.should_run(mode)
&& matches!(dist.allow_dirty, DirtyMode::AllowList(..))
{
Err(DistError::ContradictoryGenerateModes {
generate_mode: mode,
})?;
}
}
&args.modes[..]
};
for &mode in modes {
if dist.allow_dirty.should_run(mode) {
match mode {
GenerateMode::Ci => {
let CiInfo { github } = &dist.ci;
if let Some(github) = github {
if args.check {
github.check(dist)?;
} else {
github.write_to_disk(dist)?;
}
}
}
GenerateMode::Msi => {
for artifact in &dist.artifacts {
if let ArtifactKind::Installer(InstallerImpl::Msi(msi)) = &artifact.kind {
if args.check {
msi.check_config()?;
} else {
msi.write_config_to_disk()?;
}
}
}
}
}
}
}
Ok(())
}
pub fn check_integrity(cfg: &Config) -> DistResult<()> {
let check_config = Config {
tag_settings: TagSettings {
needs_coherence: false,
tag: cfg.tag_settings.tag.clone(),
},
create_hosting: false,
artifact_mode: ArtifactMode::All,
no_local_paths: false,
allow_all_dirty: cfg.allow_all_dirty,
targets: vec![],
ci: vec![],
installers: vec![],
root_cmd: "check".to_owned(),
};
let (dist, _manifest) = tasks::gather_work(&check_config)?;
run_generate(
&dist,
&GenerateArgs {
modes: vec![],
check: true,
},
)
}
fn generate_installer(
dist: &DistGraph,
style: &InstallerImpl,
manifest: &DistManifest,
) -> DistResult<()> {
match style {
InstallerImpl::Shell(info) => {
installer::shell::write_install_sh_script(dist, info, manifest)?
}
InstallerImpl::Powershell(info) => {
installer::powershell::write_install_ps_script(dist, info)?
}
InstallerImpl::Npm(info) => installer::npm::write_npm_project(dist, info)?,
InstallerImpl::Homebrew(HomebrewImpl { info, fragments }) => {
installer::homebrew::write_homebrew_formula(dist, info, fragments, manifest)?
}
InstallerImpl::Msi(info) => info.build(dist)?,
InstallerImpl::Pkg(info) => info.build()?,
}
Ok(())
}
pub fn default_desktop_targets() -> Vec<TripleName> {
use crate::platform::targets as t;
vec![
t::TARGET_X64_LINUX_GNU.to_owned(),
t::TARGET_X64_WINDOWS.to_owned(),
t::TARGET_X64_MAC.to_owned(),
t::TARGET_ARM64_MAC.to_owned(),
t::TARGET_ARM64_LINUX_GNU.to_owned(),
]
}
pub fn known_desktop_targets() -> Vec<TripleName> {
use crate::platform::targets as t;
vec![
t::TARGET_X64_LINUX_GNU.to_owned(),
t::TARGET_X64_LINUX_MUSL.to_owned(),
t::TARGET_X64_WINDOWS.to_owned(),
t::TARGET_X64_MAC.to_owned(),
t::TARGET_ARM64_MAC.to_owned(),
t::TARGET_ARM64_LINUX_GNU.to_owned(),
t::TARGET_ARM64_WINDOWS.to_owned(),
]
}