use anyhow::{Context as _, Result};
use crate::context::Context;
use crate::target::map_target;
macro_rules! micro_arch_variant_suffix {
() => {
"{% if Arm %}v{{ Arm }}{% endif %}{% if Mips %}_{{ Mips }}{% endif %}{% if Amd64 and Amd64 != \"v1\" %}{{ Amd64 }}{% endif %}"
};
}
macro_rules! default_binary_name_template {
() => {
concat!(
"{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}",
$crate::archive_name::micro_arch_variant_suffix!()
)
};
}
pub(crate) use {default_binary_name_template, micro_arch_variant_suffix};
pub const DEFAULT_NAME_TEMPLATE: &str = concat!(
"{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}",
micro_arch_variant_suffix!()
);
pub const DEFAULT_NAME_TEMPLATE_MULTI_CRATE: &str = DEFAULT_NAME_TEMPLATE;
pub const DEFAULT_BINARY_NAME_TEMPLATE: &str = default_binary_name_template!();
pub const MICRO_ARCH_VARIANT_SUFFIX: &str = micro_arch_variant_suffix!();
pub const AMD64_BASELINE_VARIANT: &str = "v1";
pub const INSTALLER_AMD64_VARIANT_SUFFIX: &str =
"{% if Amd64 and Amd64 != \"v1\" %}{{ Amd64 }}{% endif %}";
pub fn seed_target_vars(ctx: &mut Context, target: &str) {
seed_target_vars_in(ctx.template_vars_mut(), target);
}
pub fn seed_target_vars_in(vars: &mut crate::template::TemplateVars, target: &str) {
let (os, arch) = map_target(target);
vars.set("Os", &os);
vars.set("Target", target);
reset_variant_vars(vars);
if let Some(version) = arch.strip_prefix("armv") {
vars.set("Arch", "arm");
vars.set("Arm", version);
} else {
if arch == "amd64" {
vars.set("Amd64", AMD64_BASELINE_VARIANT);
}
vars.set("Arch", &arch);
}
}
pub fn reset_variant_vars(vars: &mut crate::template::TemplateVars) {
vars.set("Arm", "");
vars.set("Arm64", "");
vars.set("Amd64", "");
vars.set("Mips", "");
vars.set("I386", "");
}
pub fn seed_variant_vars(
vars: &mut crate::template::TemplateVars,
target: &str,
amd64_variant: Option<&str>,
) {
reset_variant_vars(vars);
match target.split('-').next().unwrap_or("") {
"aarch64" => vars.set("Arm64", "v8"),
"x86_64" => vars.set("Amd64", amd64_variant.unwrap_or(AMD64_BASELINE_VARIANT)),
"i686" | "i386" | "i586" => vars.set("I386", "sse2"),
_ => {}
}
}
pub fn seed_amd64_variant_var(
vars: &mut crate::template::TemplateVars,
arch: &str,
amd64_variant: Option<&str>,
) {
let value = match amd64_variant {
Some(v) => v,
None if arch == "amd64" => AMD64_BASELINE_VARIANT,
None => "",
};
vars.set("Amd64", value);
}
pub fn seed_archive_name_vars(
vars: &mut crate::template::TemplateVars,
target: &str,
crate_name: &str,
binary: &str,
amd64_variant: Option<&str>,
) {
seed_target_vars_in(vars, target);
let (_, arch) = map_target(target);
seed_amd64_variant_var(vars, &arch, amd64_variant);
vars.set("CrateName", crate_name);
vars.set("Binary", binary);
}
pub fn default_archive_name_template(ctx: &Context) -> String {
if archives_more_than_one_crate(ctx) {
DEFAULT_NAME_TEMPLATE_MULTI_CRATE.to_string()
} else {
DEFAULT_NAME_TEMPLATE.to_string()
}
}
pub fn archives_more_than_one_crate(ctx: &Context) -> bool {
crate::archive_selection::archive_producing_crates(
&ctx.config,
&ctx.artifacts,
&ctx.options.selected_crates,
)
.len()
> 1
}
pub fn config_archives_more_than_one_crate(ctx: &Context) -> bool {
crate::archive_selection::archive_producing_crates(
&ctx.config,
&crate::artifact::ArtifactRegistry::new(),
&ctx.options.selected_crates,
)
.len()
> 1
}
pub fn global_default_archive_format(ctx: &Context) -> String {
ctx.config
.defaults
.as_ref()
.and_then(|d| d.archives.as_ref())
.and_then(|a| a.formats.as_ref())
.and_then(|f| f.first())
.cloned()
.unwrap_or_else(|| "tar.gz".to_string())
}
pub fn global_format_overrides(ctx: &Context) -> Vec<crate::config::FormatOverride> {
ctx.config
.defaults
.as_ref()
.and_then(|d| d.archives.as_ref())
.and_then(|a| a.format_overrides.clone())
.unwrap_or_default()
}
pub fn archive_formats_for_target(
archive: &crate::config::ArchiveConfig,
target: &str,
global_overrides: &[crate::config::FormatOverride],
global_default: &str,
) -> Vec<String> {
let (os, _arch) = map_target(target);
let overrides: &[crate::config::FormatOverride] = archive
.format_overrides
.as_deref()
.unwrap_or(global_overrides);
overrides
.iter()
.find(|ov| !ov.os.is_empty() && os.starts_with(&ov.os))
.and_then(|ov| ov.formats.as_ref().filter(|f| !f.is_empty()).cloned())
.or_else(|| archive.formats.as_ref().filter(|f| !f.is_empty()).cloned())
.unwrap_or_else(|| vec![global_default.to_string()])
}
pub fn archive_format_for_target(
archive: &crate::config::ArchiveConfig,
target: &str,
global_overrides: &[crate::config::FormatOverride],
global_default: &str,
) -> String {
archive_formats_for_target(archive, target, global_overrides, global_default)
.into_iter()
.next()
.unwrap_or_else(|| global_default.to_string())
}
pub fn render_archive_stem(ctx: &mut Context, name_template: &str, target: &str) -> Result<String> {
seed_target_vars(ctx, target);
ctx.render_template(name_template)
.with_context(|| format!("render archive name template for target '{target}'"))
}
pub fn render_archive_asset_name(
ctx: &mut Context,
name_template: &str,
target: &str,
format: &str,
) -> Result<String> {
render_archive_asset_name_with_variant(ctx, name_template, target, format, None)
}
pub fn render_archive_asset_name_with_variant(
ctx: &mut Context,
name_template: &str,
target: &str,
format: &str,
amd64_variant: Option<&str>,
) -> Result<String> {
let stem = if amd64_variant.is_some() {
seed_target_vars(ctx, target);
let (_, arch) = map_target(target);
seed_amd64_variant_var(ctx.template_vars_mut(), &arch, amd64_variant);
ctx.render_template(name_template)
.with_context(|| format!("render archive name template for target '{target}'"))?
} else {
render_archive_stem(ctx, name_template, target)?
};
if format == crate::artifact::FORMAT_BINARY {
return Ok(binary_output_name(stem, target));
}
Ok(format!("{stem}.{format}"))
}
pub fn binary_output_name(stem: String, target: &str) -> String {
if crate::target::is_windows(target) && !stem.ends_with(".exe") {
format!("{stem}.exe")
} else {
stem
}
}
pub fn binstall_pkg_fmt(format: &str) -> Option<&'static str> {
match format {
"tar.gz" | "tgz" => Some("tgz"),
"tar.xz" | "txz" => Some("txz"),
"tar.zst" | "tzst" => Some("tzstd"),
"tar.bz2" | "tbz2" => Some("tbz2"),
"tar" => Some("tar"),
"zip" => Some("zip"),
"bin" => Some("bin"),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
use crate::context::{Context, ContextOptions};
fn ctx() -> Context {
let config = Config {
project_name: "anodizer".to_string(),
..Default::default()
};
let mut ctx = Context::new(config, ContextOptions::default());
ctx.template_vars_mut().set("ProjectName", "anodizer");
ctx.template_vars_mut().set("Version", "1.2.3");
ctx
}
#[test]
fn a_binary_format_asset_name_carries_the_windows_suffix() {
let mut c = ctx();
let name = render_archive_asset_name_with_variant(
&mut c,
"{{ ProjectName }}_{{ Os }}_{{ Arch }}",
"x86_64-pc-windows-msvc",
crate::artifact::FORMAT_BINARY,
None,
)
.unwrap();
assert_eq!(name, "anodizer_windows_amd64.exe");
}
#[test]
fn a_stem_already_ending_in_exe_is_not_doubled() {
assert_eq!(
binary_output_name("app.exe".to_string(), "x86_64-pc-windows-msvc"),
"app.exe"
);
assert_eq!(
binary_output_name("app".to_string(), "x86_64-unknown-linux-gnu"),
"app"
);
}
#[test]
fn seeds_os_arch_target() {
let mut c = ctx();
seed_target_vars(&mut c, "x86_64-unknown-linux-gnu");
assert_eq!(c.template_vars().get("Os").unwrap(), "linux");
assert_eq!(c.template_vars().get("Arch").unwrap(), "amd64");
assert_eq!(
c.template_vars().get("Target").unwrap(),
"x86_64-unknown-linux-gnu"
);
assert_eq!(c.template_vars().get("Amd64").unwrap(), "v1");
assert_eq!(c.template_vars().get("Arm").unwrap(), "");
}
#[test]
fn armv7_splits_arch_and_arm() {
let mut c = ctx();
seed_target_vars(&mut c, "armv7-unknown-linux-gnueabihf");
assert_eq!(c.template_vars().get("Arch").unwrap(), "arm");
assert_eq!(c.template_vars().get("Arm").unwrap(), "7");
assert_eq!(c.template_vars().get("Amd64").unwrap(), "");
}
#[test]
fn mips_carries_no_variant_suffix() {
let mut c = ctx();
seed_target_vars(&mut c, "mips-unknown-linux-gnu");
assert_eq!(c.template_vars().get("Arch").unwrap(), "mips");
assert_eq!(c.template_vars().get("Mips").unwrap(), "");
}
#[test]
fn variant_vars_reset_between_targets() {
let mut c = ctx();
seed_target_vars(&mut c, "armv7-unknown-linux-gnueabihf");
assert_eq!(c.template_vars().get("Arm").unwrap(), "7");
seed_target_vars(&mut c, "x86_64-unknown-linux-gnu");
assert_eq!(c.template_vars().get("Arm").unwrap(), "");
assert_eq!(c.template_vars().get("Arch").unwrap(), "amd64");
}
#[test]
fn amd64_variant_var_is_arch_gated() {
let cases: [(&str, Option<&str>, &str); 4] = [
("arm64", None, ""),
("armv7", None, ""),
("amd64", None, "v1"),
("amd64", Some("v3"), "v3"),
];
let mut c = ctx();
for (arch, variant, expected) in cases {
seed_amd64_variant_var(c.template_vars_mut(), arch, variant);
assert_eq!(
c.template_vars().get("Amd64").unwrap(),
expected,
"Amd64 for arch={arch} variant={variant:?}"
);
}
}
#[test]
fn default_template_renders_goreleaser_stem() {
let mut c = ctx();
let stem =
render_archive_stem(&mut c, DEFAULT_NAME_TEMPLATE, "aarch64-apple-darwin").unwrap();
assert_eq!(stem, "anodizer_1.2.3_darwin_arm64");
}
#[test]
fn custom_template_renders_with_format_ext() {
let mut c = ctx();
let name = render_archive_asset_name(
&mut c,
"{{ ProjectName }}-{{ Version }}-{{ Os }}-{{ Arch }}",
"x86_64-pc-windows-msvc",
"zip",
)
.unwrap();
assert_eq!(name, "anodizer-1.2.3-windows-amd64.zip");
}
#[test]
fn default_template_byte_identical_to_head_for_every_arch() {
let cases: &[(&str, &str)] = &[
(
"armv7-unknown-linux-gnueabihf",
"anodizer_1.2.3_linux_armv7",
),
(
"armv6-unknown-linux-gnueabihf",
"anodizer_1.2.3_linux_armv6",
),
("mips-unknown-linux-gnu", "anodizer_1.2.3_linux_mips"),
("mipsel-unknown-linux-gnu", "anodizer_1.2.3_linux_mipsel"),
(
"mips64-unknown-linux-gnuabi64",
"anodizer_1.2.3_linux_mips64",
),
(
"mips64el-unknown-linux-gnuabi64",
"anodizer_1.2.3_linux_mips64el",
),
("i686-unknown-linux-gnu", "anodizer_1.2.3_linux_386"),
("x86_64-unknown-linux-gnu", "anodizer_1.2.3_linux_amd64"),
("aarch64-unknown-linux-gnu", "anodizer_1.2.3_linux_arm64"),
("x86_64-apple-darwin", "anodizer_1.2.3_darwin_amd64"),
("aarch64-apple-darwin", "anodizer_1.2.3_darwin_arm64"),
("x86_64-pc-windows-msvc", "anodizer_1.2.3_windows_amd64"),
("aarch64-pc-windows-msvc", "anodizer_1.2.3_windows_arm64"),
];
for (target, expected) in cases {
let mut c = ctx();
let stem = render_archive_stem(&mut c, DEFAULT_NAME_TEMPLATE, target).unwrap();
assert_eq!(
&stem, expected,
"default-template asset stem for {target} must match HEAD"
);
}
}
#[test]
fn new_seeding_equals_head_seeding_per_target() {
let targets = [
"armv7-unknown-linux-gnueabihf",
"armv6-unknown-linux-gnueabihf",
"mips-unknown-linux-gnu",
"mipsel-unknown-linux-gnu",
"mips64-unknown-linux-gnuabi64",
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
"aarch64-unknown-linux-gnu",
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"x86_64-pc-windows-msvc",
"aarch64-pc-windows-msvc",
];
for target in targets {
let (os, arch) = crate::target::map_target(target);
let mut head = ctx();
{
let v = head.template_vars_mut();
v.set("Os", &os);
v.set("Arch", &arch);
v.set("Target", target);
v.set("Arm", "");
v.set("Arm64", "");
v.set("Amd64", "");
v.set("Mips", "");
v.set("I386", "");
}
let head_stem = head.render_template(DEFAULT_NAME_TEMPLATE).unwrap();
let mut new = ctx();
let new_stem = render_archive_stem(&mut new, DEFAULT_NAME_TEMPLATE, target).unwrap();
assert_eq!(
head_stem, new_stem,
"new seed_target_vars must be byte-identical to HEAD for {target}"
);
}
}
#[test]
fn archive_defaults_end_with_full_micro_arch_suffix() {
assert!(DEFAULT_NAME_TEMPLATE.ends_with(MICRO_ARCH_VARIANT_SUFFIX));
assert!(DEFAULT_BINARY_NAME_TEMPLATE.ends_with(MICRO_ARCH_VARIANT_SUFFIX));
}
#[test]
fn the_baseline_amd64_level_is_spelled_once() {
let guard = format!("Amd64 != \"{AMD64_BASELINE_VARIANT}\"");
assert!(
INSTALLER_AMD64_VARIANT_SUFFIX.contains(&guard),
"installer suffix must guard on the baseline const: {INSTALLER_AMD64_VARIANT_SUFFIX}"
);
assert!(
MICRO_ARCH_VARIANT_SUFFIX.contains(&guard),
"full suffix must guard on the baseline const: {MICRO_ARCH_VARIANT_SUFFIX}"
);
}
#[test]
fn installer_amd64_suffix_is_the_tail_of_the_full_suffix() {
assert!(MICRO_ARCH_VARIANT_SUFFIX.ends_with(INSTALLER_AMD64_VARIANT_SUFFIX));
}
#[test]
fn variant_vars_mips_stays_empty_for_every_mips_goarch() {
use crate::template::TemplateVars;
for target in [
"mips-unknown-linux-gnu",
"mipsel-unknown-linux-gnu",
"mips64-unknown-linux-gnuabi64",
"mips64el-unknown-linux-gnuabi64",
] {
let mut v = TemplateVars::new();
seed_variant_vars(&mut v, target, None);
assert_eq!(
v.get("Mips").map(String::as_str),
Some(""),
"Mips must stay empty for {target}"
);
}
}
#[test]
fn variant_vars_untagged_x86_64_seeds_amd64_baseline() {
use crate::template::TemplateVars;
let mut v = TemplateVars::new();
seed_variant_vars(&mut v, "x86_64-unknown-linux-gnu", None);
assert_eq!(v.get("Amd64").map(String::as_str), Some("v1"));
seed_variant_vars(&mut v, "x86_64-unknown-linux-gnu", Some("v3"));
assert_eq!(v.get("Amd64").map(String::as_str), Some("v3"));
}
#[test]
fn variant_vars_family_levels_and_reset() {
use crate::template::TemplateVars;
let mut v = TemplateVars::new();
seed_variant_vars(&mut v, "aarch64-unknown-linux-gnu", None);
assert_eq!(v.get("Arm64").map(String::as_str), Some("v8"));
seed_variant_vars(&mut v, "i686-unknown-linux-gnu", None);
assert_eq!(v.get("I386").map(String::as_str), Some("sse2"));
assert_eq!(v.get("Arm64").map(String::as_str), Some(""));
}
#[test]
fn variant_vars_arm_stays_empty_for_every_arm_token() {
use crate::template::TemplateVars;
for target in [
"armv7-unknown-linux-gnueabihf",
"armv7l-unknown-linux-gnueabihf",
"armv6-unknown-linux-gnueabihf",
"arm-unknown-linux-gnueabi",
] {
let mut v = TemplateVars::new();
seed_variant_vars(&mut v, target, None);
assert_eq!(
v.get("Arm").map(String::as_str),
Some(""),
"Arm must stay empty for {target}"
);
}
}
#[test]
fn untagged_x86_64_renders_amd64_baseline_identically_across_policies() {
use crate::template::TemplateVars;
let target = "x86_64-unknown-linux-gnu";
let mut archive = ctx();
seed_target_vars(&mut archive, target);
let archive_val = archive.template_vars().get("Amd64").cloned().unwrap();
let mut build_vars = TemplateVars::new();
seed_variant_vars(&mut build_vars, target, None);
let build_val = build_vars.get("Amd64").cloned().unwrap();
let mut installer_vars = TemplateVars::new();
seed_amd64_variant_var(&mut installer_vars, "amd64", None);
let installer_val = installer_vars.get("Amd64").cloned().unwrap();
assert_eq!(archive_val, "v1");
assert_eq!(build_val, archive_val);
assert_eq!(installer_val, archive_val);
}
#[test]
fn untagged_x86_64_default_archive_name_stays_suffix_free() {
let mut c = ctx();
let stem =
render_archive_stem(&mut c, DEFAULT_NAME_TEMPLATE, "x86_64-unknown-linux-gnu").unwrap();
assert_eq!(stem, "anodizer_1.2.3_linux_amd64");
}
#[test]
fn pkg_fmt_maps_known_formats() {
assert_eq!(binstall_pkg_fmt("tar.gz"), Some("tgz"));
assert_eq!(binstall_pkg_fmt("zip"), Some("zip"));
assert_eq!(binstall_pkg_fmt("tar.xz"), Some("txz"));
assert_eq!(binstall_pkg_fmt("binary"), None);
assert_eq!(binstall_pkg_fmt("none"), None);
}
}