Browse Source

Support Apple M1 by installing Rosetta Node builds (#417)

remotes/origin/add-with-shims
Patrick Kilgore 4 years ago committed by GitHub
parent
commit
68ba8c6457
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      README.md
  2. 94
      src/arch.rs
  3. 5
      src/commands/install.rs
  4. 7
      src/config.rs
  5. 39
      src/downloader.rs
  6. 1
      src/main.rs

14
README.md

@ -168,15 +168,25 @@ FOR /f "tokens=*" %i IN ('fnm env --use-on-cd') DO CALL %i @@ -168,15 +168,25 @@ FOR /f "tokens=*" %i IN ('fnm env --use-on-cd') DO CALL %i
### Global Options
```sh
fnm [--shell=fish|bash|zsh] [--node-dist-mirror=URI] [--fnm-dir=DIR] [--log-level=quiet|error|info] <command>
fnm [--shell=fish|bash|zsh] [--node-dist-mirror=URI] [--fnm-dir=DIR] [--log-level=quiet|error|info] [--arch=ARCH] <command>
```
- Providing `--shell=fish` will output the Fish-compliant version. Omitting it and `fnm` will try to infer the current shell based on the process tree
- Providing `--shell=fish` will output the Fish-compliant version. Omit it and `fnm` will try to infer the current shell based on the process tree
- Providing `--node-dist-mirror="https://npm.taobao.org/dist"` will use the Chinese mirror of Node.js
- Providing `--fnm-dir="/tmp/fnm"` will install and use versions in `/tmp/fnm` directory
- Providing `--arch=x64` will install Node binaries with `x86-64` architecture. Omit it and `fnm` will default to your computer's architecture.
You can always use `fnm --help` to read the docs:
#### Apple Silicon
Until [upstream support for darwin-arm64](https://github.com/nodejs/node/issues/37309) is complete, `fnm` defaults to installing the `darwin-x64` architecture for your selected version to be run with Rosetta 2.
Enable Rosetta 2 via terminal command:
```sh
softwareupdate --install-rosetta
```
The `--arch` option overrides this default.
### `fnm install [VERSION]`
Installs `[VERSION]`. If no version provided, it will install the version specified in the `.node-version` or `.nvmrc` files located in the current working directory.

94
src/arch.rs

@ -0,0 +1,94 @@ @@ -0,0 +1,94 @@
#[derive(Clone, Debug)]
pub enum Arch {
X86,
X64,
Arm64,
Armv7l,
Ppc64le,
Ppc64,
S390x,
}
#[cfg(unix)]
/// Get a sane default architecture for the platform.
pub fn default_str() -> &'static str {
use crate::system_info::{platform_arch, platform_name};
// TODO: Handle (arch, name, version) when Node v15+ supports darwin-arm64
match (platform_name(), platform_arch()) {
("darwin", "arm64") => "x64",
(_, arch) => arch,
}
}
#[cfg(windows)]
/// Get a sane default architecture for the platform.
pub fn default_str() -> &'static str {
return crate::system_info::platform_arch();
}
impl Default for Arch {
fn default() -> Arch {
match default_str().parse() {
Ok(arch) => arch,
Err(e) => panic!("{}", e.details),
}
}
}
impl std::str::FromStr for Arch {
type Err = ArchError;
fn from_str(s: &str) -> Result<Arch, Self::Err> {
match s {
"x86" => Ok(Arch::X86),
"x64" => Ok(Arch::X64),
"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))),
}
}
}
impl std::fmt::Display for Arch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let arch_str = match self {
Arch::X86 => String::from("x86"),
Arch::X64 => String::from("x64"),
Arch::Arm64 => String::from("arm64"),
Arch::Armv7l => String::from("armv7l"),
Arch::Ppc64le => String::from("ppc64le"),
Arch::Ppc64 => String::from("ppc64"),
Arch::S390x => String::from("s390x"),
};
write!(f, "{}", arch_str)
}
}
#[derive(Debug)]
pub struct ArchError {
details: String,
}
impl ArchError {
fn new(msg: &str) -> ArchError {
ArchError {
details: msg.to_string(),
}
}
}
impl std::fmt::Display for ArchError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.details)
}
}
impl std::error::Error for ArchError {
fn description(&self) -> &str {
&self.details
}
}

5
src/commands/install.rs

@ -46,6 +46,7 @@ impl super::command::Command for Install { @@ -46,6 +46,7 @@ impl super::command::Command for Install {
fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> {
let current_dir = std::env::current_dir().unwrap();
let current_version = self
.version()?
.or_else(|| get_user_version_for_directory(current_dir))
@ -89,12 +90,14 @@ impl super::command::Command for Install { @@ -89,12 +90,14 @@ impl super::command::Command for Install {
.clone()
}
};
let version_str = format!("Node {}", &version);
outln!(config#Info, "Installing {}", version_str.cyan());
match install_node_dist(
&version,
&config.node_dist_mirror,
config.installations_dir(),
&config.arch,
) {
Err(err @ DownloaderError::VersionAlreadyInstalled { .. }) => {
outln!(config#Error, "{} {}", "warning:".bold().yellow(), err);
@ -123,7 +126,7 @@ impl super::command::Command for Install { @@ -123,7 +126,7 @@ impl super::command::Command for Install {
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("Can't download the requested version: {}", source))]
#[snafu(display("Can't download the requested binary: {}", source))]
DownloadError {
source: DownloaderError,
},

7
src/config.rs

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
use crate::arch;
use crate::log_level::LogLevel;
use dirs::home_dir;
use structopt::StructOpt;
@ -31,6 +32,11 @@ pub struct FnmConfig { @@ -31,6 +32,11 @@ pub struct FnmConfig {
/// The log level of fnm commands
#[structopt(long, env = "FNM_LOGLEVEL", default_value = "info", global = true)]
log_level: LogLevel,
/// Override the architecture of the installed Node binary.
/// Defaults to arch of fnm binary.
#[structopt(long, env = "FNM_ARCH", default_value, global = true)]
pub arch: arch::Arch,
}
impl Default for FnmConfig {
@ -40,6 +46,7 @@ impl Default for FnmConfig { @@ -40,6 +46,7 @@ impl Default for FnmConfig {
base_dir: None,
multishell_path: None,
log_level: LogLevel::Info,
arch: Default::default(),
}
}
}

39
src/downloader.rs

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
use crate::arch::Arch;
use crate::archive;
use crate::archive::{Error as ExtractError, Extract};
use crate::directory_portal::DirectoryPortal;
@ -22,8 +23,15 @@ pub enum Error { @@ -22,8 +23,15 @@ pub enum Error {
},
#[snafu(display("The downloaded archive is empty"))]
TarIsEmpty,
#[snafu(display("Can't find version upstream"))]
VersionNotFound,
#[snafu(display(
"{} for {} not found upstream.\nYou can `fnm ls-remote` to see available versions or try a different `--arch`.",
version,
arch
))]
VersionNotFound {
version: Version,
arch: Arch,
},
#[snafu(display("Version already installed at {:?}", path))]
VersionAlreadyInstalled {
path: PathBuf,
@ -31,31 +39,30 @@ pub enum Error { @@ -31,31 +39,30 @@ pub enum Error {
}
#[cfg(unix)]
fn filename_for_version(version: &Version) -> String {
use crate::system_info::{platform_arch, platform_name};
fn filename_for_version(version: &Version, arch: &Arch) -> String {
format!(
"node-{node_ver}-{platform}-{arch}.tar.xz",
node_ver = &version,
platform = platform_name(),
arch = platform_arch(),
platform = crate::system_info::platform_name(),
arch = arch,
)
}
#[cfg(windows)]
fn filename_for_version(version: &Version) -> String {
fn filename_for_version(version: &Version, arch: &Arch) -> String {
format!(
"node-{node_ver}-win-{arch}.zip",
node_ver = &version,
arch = crate::system_info::platform_arch(),
arch = arch,
)
}
fn download_url(base_url: &Url, version: &Version) -> Url {
fn download_url(base_url: &Url, version: &Version, arch: &Arch) -> Url {
Url::parse(&format!(
"{}/{}/{}",
base_url.as_str().trim_end_matches('/'),
version,
filename_for_version(version)
filename_for_version(version, arch)
))
.unwrap()
}
@ -77,6 +84,7 @@ pub fn install_node_dist<P: AsRef<Path>>( @@ -77,6 +84,7 @@ pub fn install_node_dist<P: AsRef<Path>>(
version: &Version,
node_dist_mirror: &Url,
installations_dir: P,
arch: &Arch,
) -> Result<(), Error> {
let installation_dir = PathBuf::from(installations_dir.as_ref()).join(version.v_str());
@ -94,12 +102,15 @@ pub fn install_node_dist<P: AsRef<Path>>( @@ -94,12 +102,15 @@ pub fn install_node_dist<P: AsRef<Path>>(
let portal = DirectoryPortal::new_in(&temp_installations_dir, installation_dir);
let url = download_url(node_dist_mirror, version);
let url = download_url(node_dist_mirror, version, arch);
debug!("Going to call for {}", &url);
let response = reqwest::blocking::get(url).context(HttpError)?;
if response.status() == 404 {
return Err(Error::VersionNotFound);
return Err(Error::VersionNotFound {
version: version.clone(),
arch: arch.clone(),
});
}
debug!("Extracting response...");
@ -167,8 +178,10 @@ mod tests { @@ -167,8 +178,10 @@ mod tests {
fn install_in(path: &Path) -> PathBuf {
let version = Version::parse("12.0.0").unwrap();
let arch = Arch::X64;
let node_dist_mirror = Url::parse("https://nodejs.org/dist/").unwrap();
install_node_dist(&version, &node_dist_mirror, &path).expect("Can't install Node 12");
install_node_dist(&version, &node_dist_mirror, &path, &arch)
.expect("Can't install Node 12");
let mut location_path = path.join(version.v_str()).join("installation");

1
src/main.rs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
mod alias;
mod arch;
mod archive;
mod choose_version_for_user_input;
mod cli;

Loading…
Cancel
Save