use crate::core::types::DistConfig;
use std::path::Path;
pub fn generate_nix(dist: &DistConfig) -> String {
let desc = if dist.description.is_empty() {
&dist.binary
} else {
&dist.description
};
let mut system_blocks = String::new();
for t in &dist.targets {
if t.libc.as_deref() == Some("musl") {
continue;
}
let nix_system = match (t.os.as_str(), t.arch.as_str()) {
("linux", "x86_64") => "x86_64-linux",
("linux", "aarch64") => "aarch64-linux",
("darwin", "x86_64") => "x86_64-darwin",
("darwin", "aarch64") => "aarch64-darwin",
_ => continue,
};
let url = format!(
"https://github.com/{}/releases/download/v${{version}}/{}",
dist.repo,
t.asset.replace("{version}", "${version}")
);
system_blocks.push_str(&format!(
r#" "{nix_system}" = pkgs.fetchurl {{
url = "{url}";
sha256 = "PLACEHOLDER_CHECKSUM";
}};
"#
));
}
format!(
r#"# Generated by forjar dist (do not edit)
{{
description = "{desc}";
inputs = {{
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
}};
outputs = {{ self, nixpkgs, flake-utils }}:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${{system}};
version = "VERSION";
src = {{
{system_blocks} }}.${{system}} or (throw "unsupported system: ${{system}}");
in {{
packages.default = pkgs.stdenv.mkDerivation {{
pname = "{binary}";
inherit version;
inherit src;
sourceRoot = ".";
unpackPhase = "tar xzf $src";
installPhase = ''
mkdir -p $out/bin
cp {binary} $out/bin/
'';
}};
}}
);
}}
"#,
binary = dist.binary,
)
}
pub fn generate_github_action(dist: &DistConfig) -> String {
let desc = if dist.description.is_empty() {
format!("Install {} CLI", dist.binary)
} else {
format!("Install {}", dist.description)
};
let mut target_cases = String::new();
for t in &dist.targets {
if t.os != "linux" {
continue;
}
if t.libc.as_deref() == Some("musl") {
continue;
}
let rust_triple = to_rust_triple(t);
let arch_pattern = match t.arch.as_str() {
"x86_64" => "x86_64",
"aarch64" => "aarch64",
_ => continue,
};
target_cases.push_str(&format!(
r#" {arch_pattern}) TARGET="{rust_triple}" ;;
"#
));
}
format!(
r#"# Generated by forjar dist (do not edit)
name: Setup {binary}
description: "{desc}"
inputs:
version:
description: "Version to install (default: latest)"
required: false
default: "latest"
runs:
using: composite
steps:
- name: Install {binary}
shell: bash
run: |
VERSION="${{{{ inputs.version }}}}"
if [ "$VERSION" = "latest" ]; then
VERSION=$(curl -sSf https://api.github.com/repos/{repo}/releases/latest | grep tag_name | cut -d'"' -f4)
fi
ARCH=$(uname -m)
case "$ARCH" in
{target_cases} *) echo "Unsupported architecture: $ARCH"; exit 1 ;;
esac
VERSION_NUM="${{VERSION#v}}"
curl -sSfL "https://github.com/{repo}/releases/download/${{VERSION}}/{binary}-${{VERSION_NUM}}-${{TARGET}}.tar.gz" | tar xz
sudo mv {binary} /usr/local/bin/
{binary} --version
"#,
binary = dist.binary,
repo = dist.repo,
)
}
pub fn generate_deb(dist: &DistConfig, dir: &Path) -> Result<(), String> {
std::fs::create_dir_all(dir).map_err(|e| format!("mkdir {}: {e}", dir.display()))?;
let control = format!(
r#"Package: {binary}
Version: VERSION-1
Section: utils
Priority: optional
Architecture: amd64
Maintainer: {maintainer}
Description: {description}
Homepage: {homepage}
"#,
binary = dist.binary,
maintainer = if dist.maintainer.is_empty() {
"Unknown"
} else {
&dist.maintainer
},
description = if dist.description.is_empty() {
&dist.binary
} else {
&dist.description
},
homepage = dist.homepage,
);
write_artifact(&dir.join("control"), &control)?;
let install = format!("{} usr/local/bin\n", dist.binary);
write_artifact(&dir.join("install"), &install)?;
let copyright = format!(
r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: {binary}
Source: {homepage}
Files: *
Copyright: {maintainer}
License: {license}
"#,
binary = dist.binary,
homepage = dist.homepage,
maintainer = if dist.maintainer.is_empty() {
"Unknown"
} else {
&dist.maintainer
},
license = if dist.license.is_empty() {
"MIT"
} else {
&dist.license
},
);
write_artifact(&dir.join("copyright"), ©right)?;
let rules = r#"#!/usr/bin/make -f
%:
dh $@
override_dh_auto_build:
@true
"#;
write_artifact(&dir.join("rules"), rules)?;
Ok(())
}
pub fn generate_rpm(dist: &DistConfig) -> String {
let source_asset = dist
.targets
.iter()
.find(|t| t.os == "linux" && t.arch == "x86_64" && t.libc.as_deref() != Some("musl"))
.map(|t| t.asset.as_str())
.unwrap_or("BINARY.tar.gz");
format!(
r#"# Generated by forjar dist (do not edit)
Name: {binary}
Version: VERSION
Release: 1
Summary: {description}
License: {license}
URL: {homepage}
Source0: {source_asset}
%description
{description}
%install
mkdir -p %{{buildroot}}/usr/local/bin
cp {binary} %{{buildroot}}/usr/local/bin/
%files
/usr/local/bin/{binary}
"#,
binary = dist.binary,
description = if dist.description.is_empty() {
&dist.binary
} else {
&dist.description
},
license = if dist.license.is_empty() {
"MIT"
} else {
&dist.license
},
homepage = dist.homepage,
source_asset = source_asset,
)
}
pub fn write_artifact(path: &Path, content: &str) -> Result<(), String> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| format!("mkdir {}: {e}", parent.display()))?;
}
std::fs::write(path, content).map_err(|e| format!("write {}: {e}", path.display()))
}
pub fn to_class_name(name: &str) -> String {
name.split('-')
.map(|part| {
let mut chars = part.chars();
match chars.next() {
Some(c) => {
let upper: String = c.to_uppercase().collect();
format!("{}{}", upper, chars.collect::<String>())
}
None => String::new(),
}
})
.collect()
}
pub fn to_rust_triple(t: &crate::core::types::DistBinaryTarget) -> String {
let os_part = match t.os.as_str() {
"darwin" => "apple-darwin",
"linux" => {
let libc = t.libc.as_deref().unwrap_or("gnu");
&format!("unknown-linux-{libc}")
}
other => other,
};
format!("{}-{}", t.arch, os_part)
}