Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/arch-accept-arbitrary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fnm": minor
---

Accept any user-provided `FNM_ARCH` / `--arch` value. The string is passed through to the download URL, allowing use of mirrors (such as `https://unofficial-builds.nodejs.org/download/release/`) that publish targets beyond Node's official set — for example `FNM_ARCH=linux-riscv64`. Well-known values (`x64`, `arm64`, …) keep their existing behaviour.
90 changes: 20 additions & 70 deletions src/arch.rs
Original file line number Diff line number Diff line change
@@ -1,76 +1,49 @@
use crate::version::Version;
use clap::ValueEnum;

#[derive(Clone, Copy, PartialEq, Eq, Debug, ValueEnum)]
pub enum Arch {
X86,
X64,
X64Musl,
X64Glibc217,
Arm64,
Armv7l,
Ppc64le,
Ppc64,
S390x,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Arch(String);

impl Arch {
pub fn as_str(self) -> &'static str {
match self {
Arch::X86 => "x86",
Arch::X64 => "x64",
Arch::X64Musl => "x64-musl",
Arch::X64Glibc217 => "x64-glibc-217",
Arch::Arm64 => "arm64",
Arch::Armv7l => "armv7l",
Arch::Ppc64le => "ppc64le",
Arch::Ppc64 => "ppc64",
Arch::S390x => "s390x",
}
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}

pub fn x64() -> Self {
Self::new("x64")
}

pub fn as_str(&self) -> &str {
&self.0
}
}

#[cfg(unix)]
/// handle common case: Apple Silicon / Node < 16
pub fn get_safe_arch(arch: Arch, version: &Version) -> Arch {
pub fn get_safe_arch(arch: &Arch, version: &Version) -> Arch {
use crate::system_info::{platform_arch, platform_name};

match (platform_name(), platform_arch(), version) {
("darwin", "arm64", Version::Semver(v)) if v.major < 16 => Arch::X64,
_ => arch,
("darwin", "arm64", Version::Semver(v)) if v.major < 16 => Arch::x64(),
_ => arch.clone(),
}
}

#[cfg(windows)]
/// handle common case: Apple Silicon / Node < 16
pub fn get_safe_arch(arch: Arch, _version: &Version) -> Arch {
arch
pub fn get_safe_arch(arch: &Arch, _version: &Version) -> Arch {
arch.clone()
}

impl Default for Arch {
fn default() -> Arch {
match crate::system_info::platform_arch().parse() {
Ok(arch) => arch,
Err(e) => panic!("{}", e.details),
}
Arch::new(crate::system_info::platform_arch())
}
}

impl std::str::FromStr for Arch {
type Err = ArchError;
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Arch, Self::Err> {
match s {
"x86" => Ok(Arch::X86),
"x64" => Ok(Arch::X64),
"x64-musl" => Ok(Arch::X64Musl),
"x64-glibc-217" => Ok(Arch::X64Glibc217),
"arm64" => Ok(Arch::Arm64),
"armv7l" => Ok(Arch::Armv7l),
"ppc64le" => Ok(Arch::Ppc64le),
"ppc64" => Ok(Arch::Ppc64),
"s390x" => Ok(Arch::S390x),
unknown => Err(ArchError::new(format!("Unknown Arch: {unknown}"))),
}
Ok(Arch::new(s))
}
}

Expand All @@ -79,26 +52,3 @@ impl std::fmt::Display for Arch {
f.write_str(self.as_str())
}
}

#[derive(Debug)]
pub struct ArchError {
details: String,
}

impl ArchError {
fn new(msg: String) -> ArchError {
ArchError { details: msg }
}
}

impl std::fmt::Display for ArchError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.details)
}
}

impl std::error::Error for ArchError {
fn description(&self) -> &str {
&self.details
}
}
4 changes: 2 additions & 2 deletions src/commands/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl Command for Install {
};

// Automatically swap Apple Silicon to x64 arch for appropriate versions.
let safe_arch = get_safe_arch(config.arch, &version);
let safe_arch = get_safe_arch(&config.arch, &version);

let version_str = format!("Node {}", &version);
outln!(
Expand All @@ -148,7 +148,7 @@ impl Command for Install {
&version,
&config.node_dist_mirror,
config.installations_dir(),
safe_arch,
&safe_arch,
show_progress,
) {
Err(err @ DownloaderError::VersionAlreadyInstalled { .. }) => {
Expand Down
7 changes: 5 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ pub struct FnmConfig {
log_level: LogLevel,

/// Override the architecture of the installed Node binary.
/// Defaults to arch of fnm binary.
/// Defaults to arch of fnm binary. Any string is accepted (it is
/// substituted directly into the download URL); well-known values are
/// `x86`, `x64`, `x64-musl`, `x64-glibc-217`, `arm64`,
/// `armv7l`, `ppc64le`, `ppc64`, `s390x`.
#[clap(
long,
env = "FNM_ARCH",
Expand Down Expand Up @@ -103,7 +106,7 @@ pub struct FnmConfig {
impl Default for FnmConfig {
fn default() -> Self {
Self {
node_dist_mirror: Url::parse("https://nodejs.org/dist/").unwrap(),
node_dist_mirror: Url::parse("https://nodejs.org/dist").unwrap(),
base_dir: None,
multishell_path: None,
log_level: LogLevel::Info,
Expand Down
14 changes: 7 additions & 7 deletions src/downloader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub enum Error {
}

#[cfg(unix)]
fn filename_for_version(version: &Version, arch: Arch, ext: &str) -> String {
fn filename_for_version(version: &Version, arch: &Arch, ext: &str) -> String {
format!(
"node-{node_ver}-{platform}-{arch}.{ext}",
node_ver = &version,
Expand All @@ -47,7 +47,7 @@ fn filename_for_version(version: &Version, arch: Arch, ext: &str) -> String {
}

#[cfg(windows)]
fn filename_for_version(version: &Version, arch: Arch, ext: &str) -> String {
fn filename_for_version(version: &Version, arch: &Arch, ext: &str) -> String {
format!(
"node-{node_ver}-win-{arch}.{ext}",
node_ver = &version,
Expand All @@ -56,7 +56,7 @@ fn filename_for_version(version: &Version, arch: Arch, ext: &str) -> String {
)
}

fn download_url(base_url: &Url, version: &Version, arch: Arch, ext: &str) -> Url {
fn download_url(base_url: &Url, version: &Version, arch: &Arch, ext: &str) -> Url {
Url::parse(&format!(
"{}/{}/{}",
base_url.as_str().trim_end_matches('/'),
Expand All @@ -71,7 +71,7 @@ pub fn install_node_dist<P: AsRef<Path>>(
version: &Version,
node_dist_mirror: &Url,
installations_dir: P,
arch: Arch,
arch: &Arch,
show_progress: bool,
) -> Result<(), Error> {
let installation_dir = PathBuf::from(installations_dir.as_ref()).join(version.v_str());
Expand Down Expand Up @@ -125,7 +125,7 @@ pub fn install_node_dist<P: AsRef<Path>>(

Err(Error::VersionNotFound {
version: version.clone(),
arch,
arch: arch.clone(),
})
}

Expand Down Expand Up @@ -173,9 +173,9 @@ mod tests {

fn install_in(path: &Path) -> PathBuf {
let version = Version::parse("12.0.0").unwrap();
let arch = Arch::X64;
let arch = Arch::x64();
let node_dist_mirror = Url::parse("https://nodejs.org/dist/").unwrap();
install_node_dist(&version, &node_dist_mirror, path, arch, false)
install_node_dist(&version, &node_dist_mirror, path, &arch, false)
.expect("Can't install Node 12");

let mut location_path = path.join(version.v_str()).join("installation");
Expand Down