nvm icon indicating copy to clipboard operation
nvm copied to clipboard

Mac M1 Arm, and architecture override

Open bartoszhernas opened this issue 3 years ago • 16 comments

Hi,

I have used NVM just when the M1 processors came out. Back then the NVM was returning 404 on binary download and then compiling node by itself. This was not ideal (built binaries would be better) but still ok.

Fast forward to now, rest of my team got the M1 macbooks, they followed my steps of nvm and node installation. We are still on v14, because v15 is not supported by some of our packages, and v16 is not working with Realm.

Imagine my surprise when their environment does not work. We pinpointed the issue to them having x86 node binaries, instead of arm64 ones (as me and our M1 CI has had for months now).

After day of searching for the issue, and not understanding why even the 15.3 which is officially supported arm version of node is not properly installing.

Only then I have found out the culrpit: NVM is overriding architecture for any Node version below 16.

I would vote to remove this as this is hidden, unintended side-effect and changes previous behaviour. In my opinion if I want to install 15.3 and you cannot find binaries, it's better to compile it on device instead of returning me a wrong binary (Intel one).

bartoszhernas avatar Feb 14 '22 11:02 bartoszhernas

node itself doesn’t support arm below v16, except with Rosetta, afaik. If this is incorrect, the exact version logic can be tweaked.

I’m not sure why compiling it would be better than using a Rosetta binary?

ljharb avatar Feb 14 '22 15:02 ljharb

node itself doesn’t support arm below v16, except with Rosetta, afaik. If this is incorrect, the exact version logic can be tweaked.

From what we have seen node14 and up support arm, or at least works out of the box. Early versions of node14 had some issues with memory while running node in chromium (electron) and were crashing hard, but that was fixed in minor version and compiled binary is native arm one and works great. node 15.x supported: https://github.com/nodejs/build/issues/2474#issuecomment-820312698 node 14.x supported: https://github.com/nodejs/build/issues/2474#issuecomment-839683917

The problem for us is that we are running Electron application with Realm database. Electron supports arm64 out of the box. Realm requires native binaries that are the same arch as Electron.

While installing Node14 from source, or Node15 as well, the binary that comes out is arm64 binary:

> file /Users/bartoszhernas/.nvm/versions/node/v16.14.0/bin/node
/Users/bartoszhernas/.nvm/versions/node/v16.14.0/bin/node: Mach-O 64-bit executable arm64

If you try to install the same version from binary, because of NVM overriding the arch variable, the x86_64 version is being installed. Sounds good, it should work with Rosetta, and that being partially true, most of things do work ok.

In out case however, the Electron was starting, and then our main app was throwing error that Realm requires arm64 binaries, and got x86_64 instead. I believe it was due to node, and hence npm/yarn to install wrong binaries for Realm.

Error:
dlopen(/Users/tomaszwlodarczyk/Tomasz/Work/FYM/app/main/node_modules/realm/build/Release/realm.node, 0x0001): tried:
/Users/tomaszwlodarczyk/Tomasz/Work/FYM/app/main/node_modules/realm/build/Release/realm.n
ode' (mach-o file, but is an incompatible architecture (have '×86_64', need 'arm64e')),
/sr/local/lib/realm.node' (no such file), /sr/lib/realm.node' (no such file)

The problem then sums up to: some tooling takes the arch of the system, and some other tooling takes the arch of the node process and mess occurs. Seems that compiled version of node 14.x and 15.x do support M1 ARM

bartoszhernas avatar Feb 14 '22 16:02 bartoszhernas

This installation behavior did change on version 0.39.1, so if you need to compile from source, you can use this installation command, here I'll use lts/erbium as an example:

nvm install -s lts/erbium

njzydark avatar Mar 01 '22 14:03 njzydark

Yes, we switched to use -s flag for now, but the behaviour in my opinion is just wrong, and it should not override the architecture for nodes below 14.x as these versions have ARM support merged there, even though the binaries are not provided.

bartoszhernas avatar Mar 01 '22 15:03 bartoszhernas

I have recently learned that node ^14.17 (not all node 14s, and not node 15) can be compiled in a non-rosetta M1 env, even though binaries are provided only for 16+.

nvm should thus not alter the architecture for ^14.17 either.

ljharb avatar Mar 01 '22 15:03 ljharb

As previously mentioned, all the node starting from 14.x and up do support M1 already, that's my whole point :)

bartoszhernas avatar Mar 01 '22 16:03 bartoszhernas

@bartoszhernas the node release team claims that only node 14.17+ in the 14 line actually supports it.

ljharb avatar Mar 01 '22 16:03 ljharb

Ahh, ok :)

bartoszhernas avatar Mar 01 '22 16:03 bartoszhernas

So what's the appropriate way to install it on Mac m1? The readme script and instructions still install intel version (node 16)

louis030195 avatar Jun 23 '22 12:06 louis030195

Is there a command that could instruct nvm to install arm version of Node V16.xx? I currently have the machine set up to use Rosetta compiled version. Something like nvm install 16.13.0 -arch arm

fyun89 avatar Jun 25 '22 18:06 fyun89

@fyun89 i believe if you're in a non-Rosetta terminal it should Just Work for node 16+ - make sure you're on the latest version of nvm.

ljharb avatar Jun 25 '22 20:06 ljharb

I would love an -arch flag.

Basically I am in arm64 zsh, I set arch to x86, but my node (via nvm) is still an arm binary, so process.arch is arm64 for node. I would love to just:

nvm use --arch x86_64

And it just switches my current default lts v16.16.0 to the x86 binary. I would LOVE this.

Maybe I can go spelunk in my nvm script to try to add this myself...

edit I couldn't even find the script that nvm runs from (which nvm just returns a literal bash script not the file path)...so I ended up using Rosetta

crisdosaygo avatar Aug 11 '22 07:08 crisdosaygo

@crisdosyago it’s an OS limitation, so I’m not sure how nvm would be able to accomplish that.

ljharb avatar Aug 11 '22 13:08 ljharb

@ljharb my model was it was as simple as:

nvm-internal-stuff.sh:

  # nvm magic ...
  # set arch
  arch -$(get_arch_arg)
  # check arch
  arch=$(uname -m)
  # get correct binary for arch
  wget -SOJl https://secret-nvm-binary-stash.example.nvm/node-$version-$platform-$arch.tar.gz
  # celebrate more nvm magic...

But I don't know anything about this 🙂 ¯\_(ツ)_/¯

crisdosaygo avatar Aug 11 '22 16:08 crisdosaygo

I believe that won't allow the binary to compile (if needed) nor to run. Certainly if there's a way for nvm to make this easier, I'm all for it, but I think M1 owners just have to "know" to switch to Rosetta for non-M1-compatible versions of any application.

ljharb avatar Aug 11 '22 16:08 ljharb

So in my rosetta zsh in ~/.nvm/versions/node just now I:

cris@-MacBook bin % ./node -p process.arch
x64
cris@-MacBook bin % arch
i386
cris@-MacBook bin % cd ../../v16.16.0/bin
cris@-MacBook bin % ./node -p process.arch
arm64

(because I had installed v16 when in regular shell, and install v18 when in rosetta), then in non rosetta zsh i:

cris@-MacBook node % cd v16.16.0
cris@-MacBook v16.16.0 % cd bin
cris@-MacBook bin % arch
arm64
cris@-MacBook bin % ./node -p process.arch
arm64
cris@-MacBook bin % cd ../../v18.7.0/bin
cris@-MacBook bin % ./node -p process.arch
x64

To me, I don't know anything about this, but it looks like both binaries (arm and x64) both run in both shells (rosetta and normal), and I just thought, maybe there's some way to say:

cris@-MacBook ~ % nvm use v16.16.0-arm64
Now using that other guy
cris@-MacBook ~ % node -p process.arch
arm64
cris@-MacBook ~ % nvm use v16.16.0-x64
Now using that guy
cris@-MacBook ~ % node -p process.arch
x64

¯\_(ツ)_/¯

Like I said, I don't know, but it seems cool 🙂

crisdosaygo avatar Aug 11 '22 17:08 crisdosaygo

thanks for addressing this issue! sharing what I did to get node v14.17.5 working on m1 without rosetta:

  1. updated nvm to the most recent version
  2. nvm install v14.17.5 failed with an error saying I have python 3.10 and need 3.9 or lower
  3. installed pyenv (nvm for python)
  4. installed python 3.9 (I think I set 3.9 as my global python version for it to work)
  5. nvm install v14.17.5 (check that the download says arm) took a while and had a bunch of bizarre logs but it worked 🤷

examples of log warnings:

In file included from /Users/ryan/.nvm/.cache/src/node-v14.17.5/files/out/Release/obj/gen/torque-output-root/torque-generated/exported-macros-assembler-tq.cc:22:
In file included from /Users/ryan/.nvm/.cache/src/node-v14.17.5/files/out/Release/obj/gen/torque-output-root/torque-generated/exported-macros-assembler-tq.h:4:
In file included from ../deps/v8/src/compiler/code-assembler.h:17:
In file included from ../deps/v8/src/codegen/code-factory.h:8:
In file included from ../deps/v8/src/codegen/callable.h:8:
In file included from ../deps/v8/src/codegen/interface-descriptors.h:12:
../deps/v8/src/codegen/tnode.h:352:9: warning: definition of implicit copy constructor for 'TNode<v8::internal::Smi>' is deprecated because it has a user-provided copy assignment operator [-Wdeprecated-copy-with-user-provided-copy]
  TNode operator=(TNode other) {
        ^
../deps/v8/src/compiler/code-assembler.h:528:12: note: in implicit copy constructor for 'v8::internal::TNode<v8::internal::Smi>' first required here
    return SmiConstant(static_cast<int>(value));
c++ -o /Users/ryan/.nvm/.cache/src/node-v14.17.5/files/out/Release/obj.target/cctest/test/cctest/test_url.o ../test/cctest/test_url.cc '-DV8_DEPRECATION_WARNINGS' '-DV8_IMMINENT_DEPRECATION_WARNINGS' '-D_DARWIN_USE_64_BIT_INODE=1' '-DOPENSSL_NO_PINSHARED' '-DOPENSSL_THREADS' '-DNODE_ARCH="arm64"' '-DNODE_WANT_INTERNALS=1' '-DHAVE_OPENSSL=1' '-DHAVE_INSPECTOR=1' '-D__POSIX__' '-DNODE_USE_V8_PLATFORM=1' '-DNODE_HAVE_I18N_SUPPORT=1' '-DNODE_PLATFORM="darwin"' '-DUCONFIG_NO_SERVICE=1' '-DU_ENABLE_DYLOAD=0' '-DU_STATIC_IMPLEMENTATION=1' '-DU_HAVE_STD_STRING=1' '-DUCONFIG_NO_BREAK_ITERATION=0' '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-DNGHTTP2_STATICLIB' -I../src -I../tools/msvs/genfiles -I../deps/v8/include -I../deps/cares/include -I../deps/uv/include -I../deps/uvwasi/include -I../test/cctest -I../deps/histogram/src -I../deps/icu-small/source/i18n -I../deps/icu-small/source/common -I../deps/zlib -I../deps/llhttp/include -I../deps/cares/src/lib -I../deps/nghttp2/lib/includes -I../deps/brotli/c/include -I../deps/openssl/openssl/include  -O3 -gdwarf-2 -mmacosx-version-min=10.13 -arch arm64 -Wall -Wendif-labels -W -Wno-unused-parameter -Werror=undefined-inline -Wall -Wendif-labels -W -Wno-unused-parameter -std=gnu++1y -stdlib=libc++ -fno-rtti -fno-exceptions -fno-strict-aliasing -MMD -MF /Users/ryan/.nvm/.cache/src/node-v14.17.5/files/out/Release/.deps//Users/ryan/.nvm/.cache/src/node-v14.17.5/files/out/Release/obj.target/cctest/test/cctest/test_url.o.d.raw   -c

drizco avatar Dec 06 '22 15:12 drizco

Here is the solution I've found https://devzilla.io/using-nodejs-14-with-mac-silicon-m1

HelloAlberuni avatar Oct 05 '23 02:10 HelloAlberuni

Here is the solution I've found https://devzilla.io/using-nodejs-14-with-mac-silicon-m1

No, this installs the x86 version (which for node14 is the default). Even if you don't change the arch (ie, do NOT run arch -x86_64 zsh) it still installs the x86 version.

AFAIK there is still no easy way to install an arm64 version of node14

nick-verida avatar Oct 10 '23 00:10 nick-verida