I ended up in an unexpected world of pain while trying to create a snap of a simple NodeJS package: https://github.com/blgm/jfq. Locally, the code installs without any issues, but for some reason snapcraft appears to have quite an aversion to it. Mostly for documentation’s sake, I’ll mention the few issues that I encountered, and via a meandering path managed to finally tackle.
Issue 1: Missing executable due to build script not being invoked
For a while I would consistently get the following message during the build process:
FileNotFoundError: [Errno 2] No such file or directory: '/build/jfq/parts/jfq/install/bin/jfq.js'
This was happening both on builds running on build.snapcraft.io as well as an equivalent error when running snapcraft locally. It just looked like the executable wasn’t being put in the right place
Eventually I worked out that the “build” script
rollup -c && chmod a+x ./bin/jfq.js
defined in the original package.json isn’t actually being run - and the rollup command is what actually copies over the executable js file. This is fixed by adding the same script under a “prepare” key in config.json. Snapcraft then does run it, and on my local machine the snap (from my fork) builds successfully.
It still wasn’t succeeding on build.snapcraft.io.
Issue 2: Permissions issue during build
Once the build/prepare command was in place, the following started appearing:
npm WARN lifecycle firstname.lastname@example.org~prepare: cannot run in wd email@example.com rollup -c && chmod a+x ./bin/jfq.js (wd=/build/jfq/parts/jfq/src)
This appears to be a fairly well-documented situation that occurs when npm is run as root (as I’m guessing it is in the build container), which causes it to drop privileges, and it therefore cannot run commands like this.
The most common solution seems to be to add the
--unsafe-perm command line parameter. If we don’t want to override the nodejs plugin’s build command, in snapcraft this can be addressed with an environment variable
parts: jfq: plugin: nodejs nodejs-package-manager: npm source-type: local source: . build-environment: - npm_config_unsafe_perm: 'true'
But now we get to…
Issue 3: Node appears to run the build script at the wrong time/place
Below are some extracts from the log. The prepare command is run three times (not entirely sure why?). The first two times it succeeds, and copies over the executable. But on the last go,
rollup appears to no longer be available. I was at a loss as to how to tackle this. In the end the solution appeared to be setting the script that invokes rollup to be under the “prepack” stage rather than the “prepare” (or “prepublish”) stage.
I don’t know enough about the NPM lifecycle to truly understand what’s going on here, but I thought I’d share in case this is helpful.
Anyway, I’m still very puzzled as to just how challenging this turned out to be, given that the package installs without a hitch in a regular NPM environment.
> firstname.lastname@example.org prepare /build/jfq/parts/jfq/src > rollup -c src/jfq.js → bin/jfq.js... created bin/jfq.js in 1.7s [...] > email@example.com prepare /build/jfq/parts/jfq/src > rollup -c src/jfq.js → bin/jfq.js... created bin/jfq.js in 1.2s npm notice npm notice 📦 firstname.lastname@example.org npm notice === Tarball Contents === npm notice 1.5kB package.json npm notice 1.1kB LICENSE npm notice 1.6kB README.md npm notice 5.0kB bin/jfq.js npm notice === Tarball Details === npm notice name: jfq npm notice version: 1.2.0 npm notice filename: jfq-1.2.0.tgz npm notice package size: 3.8 kB npm notice unpacked size: 9.2 kB npm notice shasum: 9e9c56c847bccbd0d601863ebfdd8501c8df24aa npm notice integrity: sha512-cxz1c4HetU3oT[...]2++fuiGSxJQ2Q== npm notice total files: 4 npm notice jfq-1.2.0.tgz [...] > email@example.com prepare /build/jfq/parts/jfq/src/package > rollup -c && chmod a+x ./bin/jfq.js Error: ENOENT: no such file or directory, lstat '/build/jfq/parts/jfq/src/package/rollup.config.js' at Object.realpathSync (fs.js:1657:15) at runRollup (/build/jfq/parts/jfq/src/package/node_modules/rollup/dist/bin/rollup:1312:29) at Object.<anonymous> (/build/jfq/parts/jfq/src/package/node_modules/rollup/dist/bin/rollup:1363:5) at Module._compile (module.js:653:30) at Object.Module._extensions..js (module.js:664:10) at Module.load (module.js:566:32) at tryModuleLoad (module.js:506:12) at Function.Module._load (module.js:498:3) at Function.Module.runMain (module.js:694:10) at startup (bootstrap_node.js:204:16) npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! firstname.lastname@example.org build: `rollup -c && chmod a+x ./bin/jfq.js` npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the email@example.com build script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm ERR! A complete log of this run can be found in: npm ERR! /root/.npm/_logs/2019-09-19T22_10_17_814Z-debug.log npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! firstname.lastname@example.org prepublish: `npm run build` npm ERR! Exit status 1