Closed lydell closed 9 months ago
Thanks for suggesting these code changes. To set expectations:
Finally, please be patient with the core team. They are trying their best with limited resources.
Hey @lydell 👋
I've been testing out the @lydell/elm
NPM package for the Elm 0.18.0 release, and noticed that GitHub action builds started failing recently:
The strange part is that this started happening about a week ago!
Those passing builds from 1 month ago were also using @lydell/elm
(version 0.19.1-9
), and they successfully installed. 🤯
Here's the error that started getting reported last week:
Run npm install
npm ERR! code 1
npm ERR! path /home/runner/work/elm-land/elm-land/projects/cli/node_modules/@lydell/elm
npm ERR! command failed
npm ERR! command sh -c -- node install.js
npm ERR! -- ERROR -----------------------------------------------------------------------
npm ERR!
npm ERR! I support your platform, but I could not find the binary package (@lydell/elm_linux_x64) for it!
npm ERR!
npm ERR! This can happen if you use the "--omit=optional" (or "--no-optional") npm flag.
npm ERR! The "optionalDependencies" package.json feature is used by Elm to install the correct
npm ERR! binary executable for your current platform. Remove that flag to use Elm.
npm ERR!
npm ERR! This can also happen if the "node_modules" folder was copied between two operating systems
npm ERR! that need different binaries - including "virtual" operating systems like Docker and WSL.
npm ERR! If so, try installing with npm rather than copying "node_modules".
npm ERR!
npm ERR! NOTE: You can avoid npm entirely by downloading directly from:
npm ERR! https://github.com/elm/compiler/releases/tag/0.19.1
npm ERR! All this package does is distributing a file from there.
npm ERR!
npm ERR! --------------------------------------------------------------------------------
Here are some links if you're curious about more details:
Could this be an issue with caching node_modules
or something?
For others following along, me and @ryannhg talked about his installation problem on Slack instead of here.
I verified that installing @lydell/elm
works on all platforms on GitHub Actions here: https://github.com/lydell/elm-github-actions-test/actions/runs/3722282755
Any updates on this?
@cyberglot No, the status update at the top is still the latest news!
Does that mean the elm
package is now officially unmaintained and development has moved to @lydell/elm
?
@alshdavid no it does not mean that, it just means that the community has worked together to provide an unofficial binary for the missing platform until Evan has the time to prioritise this.
For example Evan committed support for the M1 arm binaries last week: https://github.com/elm/compiler/commit/ad9d9d42c4f623c2903ecf35daefaff011075977, though the npm package has not yet been updated there has been no official indication that Evan intends to no longer maintain the npm package.
Status update
This is now merged, and released to npm as version 0.19.1-6!
The
@lydell/elm
package continues to exist as an “unofficial elm package”. The only difference is that it ships more binaries than the official ones.If you use elm-webpack-loader, which depends on the
elm
npm package, you can use the following package.json to overrideelm
to@lydell/elm
:Linked issues
Background
Me, @supermario and @evancz (with the help of some more folks) have been working on a new version of the
elm
npm package.elm
npm package.We have successfully tested all of the above using a temporary npm package (
@lydell/elm
): https://discourse.elm-lang.org/t/help-test-the-new-npm-elm-package/8761. elm-format is also successfully using this npm package technique: https://github.com/avh4/elm-format/pull/781This PR is the result of my work. If you want to avoid getting into Linux ARM compilation, it’s easy to remove the Linux ARM package.
The current elm npm package
The current
elm
npm package downloads the Elm binary for the current platform using the request package. If there is no binary for the current platform, it exits with a helpful message.Upsides of this approach:
Downsides of this approach:
request
is deprecated.request
results in 47 packages innode_modules
, weighing in at 5 MB.elm
npm package is cached.The reason for using
request
rather than Node.js’https
module directly, is becauserequest
supports theHTTPS_PROXY
environment variable, which is important to people behind corporate proxies or firewalls.The new elm npm package
This PR switches to a different technique for distributing the binaries.
elm
.@evancz/elm_darwin_arm64
.The binary packages declare which OS and CPU they are compatible with. For example:
The main npm package depend on the binary packages via optional dependencies:
When installing,
npm
fetches the metadata for all the optional dependencies and only installs the one with a matching OS and CPU. If none of them match,npm
still considers the install successful. The main npm package still contains an install script that gives a helpful error.Upsides of this approach:
request
package.package-lock.json
contains SHA-512 hashes of every package. So once installed, you can be sure that you get exactly the same Elm binary every time. The current npm package downloads the binaries from GitHub Releases without checking what it got. GitHub Releases is mutable, while the npm registry is immutable.@lydell/elm
reported that the new package works behind a corporate firewall while the old one does not: https://discourse.elm-lang.org/t/help-test-the-new-npm-elm-package/8761/11?u=lydellDownsides of this approach:
Practically, none. No issues were reported during the test run of
@lydell/elm
, and none have been reported during the 9 months of usage of that package since then either. For completeness, here are the factual differences to the current npm package:The
--omit=optional
(or--no-optional
) npm flag can no longer be used. It tells npm to skip all"optionalDependencies"
. But that is used by the newelm
npm package to install the correct binary for each platform.npm 9.x, 8.x and 7.x all behave correctly, as does Yarn 1.x and pnpm 7.x and >=6.14.0. In short: Installing the new
elm
npm package will still work with old, unsupported package managers, but is non-ideal. The solution is to upgrade your package manager.Here are some notes about older (unsupported) npm versions:
npm@6
prints a “warning” for every binary package that isn’t used on the current platform:That’s noisy but harmless.
npm@7.0.0
prints no warnings but installs all optional dependencies. That’s wasteful but harmless. The latest 7.x works correctly though.Yarn 2+ (“Berry”) is now supported (the current
elm
npm package does not support that). Yarn 2+ always executes the"bin"
of a package usingnode
(regardless of installation mode: pnp, pnpm, node-modules). Luckily, it’s easy to detect Yarn 2+ and do the same bailout as we do on Windows anyway. Note: If a user is on macOS or Linux, installs Elm using Yarn 2+, and executeselm
often via their editor, then it’s an anti-pattern to use Yarn 2+ since everyelm
invocation has to pay for thenode
startup cost as well. But that’s up to Yarn to fix: https://github.com/yarnpkg/berry/issues/882What stayed the same
In short: Most things. The main difference is that the new
elm
npm package gets the Elm binary from other npm packages instead of from GitHub Releases.bin/elm
written in Node.js. The binary packages can technically have"bin"
fields in their package.json files, but it’s not reliable – and does not work in pnpm by design. So there needs to be a"bin"
field in the main npm package. And that needs to point to something in the main npm package itself. So, a Node.js file, which finds the correct binary for the current platform and executes it usingchild_process.spawn
.elm
npm package downloads the Elm binary on install. And on top of that, on Linux and Windows it downloads it to the path of thebin/elm
Node.js file, so that you would be invoking the Elm binary directly with no overhead. The newelm
npm package also does that optimization on install (with hard links). (On Windows, npm forces executing"bin"
with Node.js so that optimization is not possible.)elm
npm package downloads the Elm binary on the first run, if--ignore-scripts
was used during install. The newelm
npm package does the above optimization on the first run if--ignore-scripts
was used.Diff tip
I renamed
download.js
tobinary.js
and then edited it. By default,git
considers a file to be renamed if there’s at least 50 % similarity. However,binary.js
is less than 50 % similar todownload.js
so the GitHub diff showsdownload.js
as deleted andbinary.js
as a new file.Locally you can run:
And then type
/download.js
and press Enter to jump to the relevant part. I think that diff helps to see that the new approach isn’t all that different from before.Things left out from this PR
The approach of this PR was inspired by esbuild:
If you read through those files, you’ll see that they are much more elaborate than
binary.js
in this PR.--omit=optional
was used, and if so try to manually download the binary first from the npm registry, then from GitHub. It’s unclear why that complexity would be necessary. This PR instead shows a helpful error message in this situation instead. Nobody complained about this when testing the@lydell/elm
package.ESBUILD_BINARY_PATH
which basically bypasses its installer and turns its"bin"
into a script that executes the binary atESBUILD_BINARY_PATH
. Something like this was mentioned in a discussion in#core-coordination
on Slack. Me and Evan discussed it, and decided to leave it out since it wasn’t as clear-cut as one might first think. (More details in the aside below.)Aside about unsupported platforms
@alshdavid posted in `#core-coordination` on Slack, which got the ball rolling on this work: > Hi Elm team, David here. I am a contributor of Parcel and we are having the issue where the recently introduced `verifyPlatform()` function in elm/compiler is preventing the Parcel monorepo (contributors) and Parcel+Elm (consumers) from being `npm install`ed when running on `linux-arm64` devices. > > This is an increasingly common use-case with Parcel contributors and consumers due to the rise of M1 MacBooks (Docker & VMs) and Linux ARM hosts being used more frequently as build agents. > > **For contributors of Parcel:** The Parcel monorepo fails to `npm install` at all on `linux-arm64` hosts, which is pretty critical given the use of Docker in development. > > **For consumers of Parcel and Elm:** Projects using Parcel and Elm and are building on `linux-arm64` machines are unable to `npm install`/build their projects. This happens locally when developers are using Parcel+Elm in Docker on an M1 MacBook and happens on build agents, but there is increasing interest in arm based devices so we anticipate this will only become more common. > > I'm happy to raise some PRs to address these issues. Given this is a pretty serious blocker, would there be someone available to review my work as I raise PRs? > > **Just spitballing some ideas:** > > I was thinking of initially introducing an env variable (e.g. `ELM_BINARY_LOCATION=/usr/local/bin/elm`) which, if set, will skip the download and platform verification. > > This would allow consumers who are on `linux-arm64` devices and using a translation layer like Box64 or Rosetta2 to use the `amd64` version while being aware of the limitations. It's also an extensible solution given it might work for `win-arm64` and other platforms with translation layers and without prescribed binaries. > > Secondly, I was thinking of adding some GitHub actions to automate the compilation of binaries for the various platforms and produce a GitHub release with those binaries. Supporting Linux ARM solves their immediate problem, so the discussion becomes more about a hypothetical future scenario which could happen as a new currently unsupported platform becomes popular. What happens then is that `npm install` fails due to the `elm` npm package erroring by design due to the unsupported platform. This fails the entire `npm install`, not just the install of the `elm` package. In Parcel’s case, it means contributors can’t install the project even if they didn’t plan to work on the Elm parts of Parcel. Here are some workarounds: - `npm install --ignore-scripts`. That will skip our install script, so no failure there. (If you then try to run `npx elm`, it’ll fail with the same message as the install script.) However, if you’re unlucky there’s some other dependency that does require install scripts. - `npm install --legacy-peer-deps` can help if `elm` is a peer dependency. For example, [elm-webpack-loader has `elm` as a peer dependency](https://github.com/elm-community/elm-webpack-loader/blob/a597c3f06711324318a2247a394001fdad4f1270/package.json#L36). That will skip automatic installation of “peer dependencies”, which will then skip installing the `elm` npm package. However, there might be other peer dependencies of the project that you now need to find and install yourself. Also, this only works if elm is actually specified as a peer dependency. If the project had been using [elm-land](https://elm.land/) instead of webpack, you’re out of luck: It has a [hard dependency on the elm npm package](https://github.com/elm-land/elm-land/blob/bad0e43140cd1da61b17344725d2c6429ebd4aad/projects/cli/package.json#L39). Here are some ideas we discussed for handling this situation: - Allow disabling the install script via an env var. - Allow also pointing to your elm binary via that env var. - Have `npm install` succeed on unsupported platforms, but `npx elm` (executing the installed elm “binary”) fail with the current error message. - Remove the install script completely. The first run will do the platform check and optimization for Linux and macOS anyway. - Parcel could release their own npm package, like `@parcel/elm`. Note: The only need to replace the main `elm` npm package, and can still re-use the binary packages (like `@evancz/elm_darwin_arm64`). **Note:** This one is doable without any changes to the `elm` npm packages (unlike the above points). For people running `npm install elm` themselves it’s ideal if it fails during installation on unsupported platforms. They should know about the problem, have a nice experience, and the npm package should be as simple as possible. From the perspective of Parcel, they want it to fail silently, maybe only when someone tries to run elm at some point or whatever. It’s not clear those two use cases are incompatible within a single package. We decided we just want to keep it simple and not make any design changes like the env var business.