Error: getaddrinfo EAI_AGAIN github.com when using Nix, Parcel and Elm

I’m trying out Nix for my Elm app that I build with Parcel. I’m however getting the following error when trying to nix build:

npm ERR! Downloading Elm 0.19.1 from GitHub.
npm ERR! 
npm ERR! NOTE: You can avoid npm entirely by downloading directly from:
npm ERR! https://github.com/elm/compiler/releases/download/0.19.1/binary-for-linux-64-bit.gz
npm ERR! All this package does is download that file and put it somewhere.
npm ERR! 
npm ERR! --------------------------------------------------------------------------------
npm ERR! -- ERROR -----------------------------------------------------------------------
npm ERR! 
npm ERR! Something went wrong while fetching the following URL:
npm ERR! 
npm ERR! https://github.com/elm/compiler/releases/download/0.19.1/binary-for-linux-64-bit.gz
npm ERR! 
npm ERR! It is saying:
npm ERR! 
npm ERR! Error: getaddrinfo EAI_AGAIN github.com

From my understanding it’s saying Error: getaddrinfo EAI_AGAIN github.com because nix-build works in a sandbox and doesn’t allow downloading files as this would not be reproducible. Which makes sense.

To build this, in my flake.nix I have:

packages.default = pkgs.buildNpmPackage {
  name = "elmder";

  buildInputs = with pkgs; [
    nodejs_18
  ];

  src = ./.;
  npmDepsHash = "sha256-SvlklTgqGSoDyjlHRIjlhBuB4dyYl4Ro1Sc2aBgx76I=";

  npmBuild = "npm run build";

  installPhase = ''
    mkdir $out
    cp -r dist/ $out
  '';
};

And my package.json has:

"devDependencies": {
  "@parcel/packager-raw-url": "^2.8.3",
  "@parcel/transformer-elm": "^2.8.3",
  "@parcel/transformer-sass": "^2.8.3",
  "@parcel/transformer-webmanifest": "^2.8.3",
  "parcel": "^2.8.3"
},

To fix this I tried to install Elm directly through nix, by adding elmPackages.elm in my buildInputs. But that didn’t change anything, I assume because @parcel/transformer-elm has elm as a peerDependency so it still tries to install it through npm (and that just downloads the elm binary into node_modules).

I also read there is a way of disabling the nix sandbox, then the download would be possible, but I don’t want to do that really.

Has anybody experienced this problem before? And is there a good way of fixing it?

It needs to go to nativeBuildInputselm is used at build time and buildNpmPackage uses strictDeps.

You could probably use the following to avoid installing peer dependencies:

npmFlags = [
  "--legacy-peer-deps"
];

But you would have to make sure you have all peer dependencies you need installed explicitly.

Yeah, disabling sandbox is not a good idea. And it would probably not work on NixOS because ELF binaries for generic Linux do not really run there without patching.


Alternately, you might want to use --ignore-scripts npm flag but then this script from the elm npm package would probably still take precedence over elm from Nixpkgs.

Some npm packages support environment variables like ELECTRON_SKIP_BINARY_DOWNLOAD to allow using programs from PATH, rather than trying to download them, but that does not seem to be the case.

So perhaps the most reliable option would be disabling the scripts and using something like:

preBuild = ''
  substituteInPlace node_packages/.bin/elm \
    --replace 'var binaryPath = path.resolve' 'var binaryPath = "${lib.getExe elmPackages.elm}"; runCommand(); //'
'';

In the future, a different patching might be required: Free the npm package from third party dependencies by lydell · Pull Request #2287 · elm/compiler · GitHub

3 Likes

Hey! Getting closer I think :smiley:

For starters, I moved elm to the nativeBuildInputs, thank you!

Next, Indeed, just adding npmFlags = [ "--legacy-peer-deps" ]; resulted in the same error, so I guess your expectation of the other script still running was correct.

Then, adding npmFlags = [ "--ignore-scripts" ]; and

preBuild = ''
  substituteInPlace node_modules/.bin/elm \
    --replace 'var binaryPath = path.resolve' 'var binaryPath = "${pkgs.lib.getExe pkgs.elmPackages.elm}"; runCommand(); //'
'';

(Note at first I got an error stating it couldn’t find the file node_packages/.bin/elm but I assume you meant node_modules/.bin/elm so I changed that.)

resulted in a strange error from parcel when building:

building
Executing npmBuildHook

> elmder@0.0.1 build
> parcel build src/index.html

⠋ Building index.html...
⠙ Building index.html...
⠹ Building index.html...
⠸ Building site.webmanifest...
⠼ Building site.webmanifest...
⠴ Building site.webmanifest...
⠦ Building site.webmanifest...
🚨 Build failed.

@parcel/transformer-elm: Unexpected number in JSON at position 992

  SyntaxError: Unexpected number in JSON at position 992
      at JSON.parse (<anonymous>)
      at Object.transform 
  (/build/li4la4446ay1ww6gshr7jsiga9nbr27s-source/node_modules/@parcel/transformer-elm/lib/ElmTransformer.js:145:38)
      at process.processTicksAndRejections 
  (node:internal/process/task_queues:95:5)
      at async Transformation.runTransformer 
  (/build/li4la4446ay1ww6gshr7jsiga9nbr27s-source/node_modules/@parcel/core/lib/Transformation.js:653:5)
      at async Transformation.runPipeline 
  (/build/li4la4446ay1ww6gshr7jsiga9nbr27s-source/node_modules/@parcel/core/lib/Transformation.js:402:36)
      at async Transformation.runPipelines 
  (/build/li4la4446ay1ww6gshr7jsiga9nbr27s-source/node_modules/@parcel/core/lib/Transformation.js:265:40)
      at async Transformation.run 
  (/build/li4la4446ay1ww6gshr7jsiga9nbr27s-source/node_modules/@parcel/core/lib/Transformation.js:183:21)
      at async Child.handleRequest 
  (/build/li4la4446ay1ww6gshr7jsiga9nbr27s-source/node_modules/@parcel/workers/lib/child.js:216:9)

I took a stab in the dark, and thought, maybe it’s because of the rest of that elm script, so I changed the substitution to be:

substituteInPlace node_modules/.bin/elm \
  --replace 'var binaryPath = path.resolve' 'var binaryPath = "${pkgs.lib.getExe pkgs.elmPackages.elm}"; runCommand(); return; //'

(Notice the added return; so it doesn’t execute anything else in the script.)

That seems to have partially worked, as now the error I get when building is:

🚨 Build failed.

@parcel/elm-transformer: 
-- PROBLEM LOADING PACKAGE LIST ----------------------------------------------- 

I need the list of published packages to verify your dependencies, so I tried to
fetch:

    https://package.elm-lang.org/all-packages

But my HTTP library is giving me the following error message:

    ConnectionFailure Network.Socket.getAddrInfo (called with preferred socket 
type/protocol: AddrInfo {addrFlags = [AI_ADDRCONFIG], addrFamily = AF_UNSPEC, 
addrSocketType = Stream, addrProtocol = 0, addrAddress = 0.0.0.0:0, 
addrCanonName = Nothing}, host name: Just "package.elm-lang.org", service name: 
Just "443"): does not exist (Temporary failure in name resolution)

Are you somewhere with a slow internet connection? Or no internet? Does the link
I am trying to fetch work in your browser? Maybe the site is down? Does your
internet connection have a firewall that blocks certain domains? It is usually
something like that!

ERROR: `npm build` failed

That sounds like now it’s not the elm node package installer trying to download stuff from the internet, now it’s the parcel elm-transformer. And it’s running into the same problem of being in a sandbox and not being allowed out.

I now found elm2nix (heard of it before, but though I wouldn’t need it as I thought parcel/npm would handle it, but I did need it).

So I used elm2nix convert > elm-srcs.nix to generate elm-srcs.nix, then used elm2nix snapshot to generate a registry.dat, and then added

configurePhase = pkgs.elmPackages.fetchElmDeps {
  elmPackages = import ./elm-srcs.nix;
  elmVersion = "0.19.1";
  registryDat = ./registry.dat;
};

to my buildNpmPackage { ... }.

Now it seems to build! Giving me a result/dist folder with all the build code in :smiley:

I cleaned up my flake.nix a bit, and thought I’d post it in case it could help someone else, or if anyone has comments on how to improve it still:

{
  description = "Elmder, an experiment in making a dating-app-like interface in Elm";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-23.05";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};

        buildPackages = with pkgs; [
          nodejs_18
        ];

        devPackages = with pkgs; [
          elmPackages.elm-format # Formatter for Elm
          elmPackages.elm-json # elm.json management
          elm2nix # needed to build elm with nix
        ];

        elmParcelNixFix = {
          # To make Elm, Parcel and Nix work together we need do some some fixes:

          # 1. The Elm node package tries to download the binary when installing, this doesn't work in Nix's sandbox, so instead we add it to the nativeBuildPackages, set the npmFlag to not run install scripts, and alter this (https://github.com/elm/compiler/blob/047d5026fe6547c842db65f7196fed3f0b4743ee/installers/npm/bin/elm#L8-L30) script to instead of downloading the binary to just use the one we installed as the native build package
          nativeBuildPackages = [ pkgs.elmPackages.elm ];
          npmFlags = [ "--ignore-scripts" ];
          preBuild = ''
            substituteInPlace node_modules/.bin/elm \
              --replace 'var binaryPath = path.resolve' 'var binaryPath = "${pkgs.lib.getExe pkgs.elmPackages.elm}"; runCommand(); return; //'
          '';

          # 2. Manage the Elm dependencies through nix too, otherwise Parcel will try to download them and that is again not allowed in Nix's sandbox.
          # For this we had to use `elm2nix`, see README on how to generate the required files when updating dependencies.
          configurePhase = pkgs.elmPackages.fetchElmDeps {
            elmPackages = import ./elm-srcs.nix;
            elmVersion = "0.19.1";
            registryDat = ./registry.dat;
          };
        };
      in
      {
        devShells.default = pkgs.mkShell {
          buildInputs = buildPackages ++ devPackages;
        };

        packages.default = pkgs.buildNpmPackage {
          name = "elmder";
          buildInputs = buildPackages;
          src = ./.;
          npmDepsHash = "sha256-SvlklTgqGSoDyjlHRIjlhBuB4dyYl4Ro1Sc2aBgx76I=";

          npmBuild = "npm run build";
          installPhase = ''
            mkdir $out
            cp -r dist/ $out
          '';

          # Fixes for using Elm and Parcel with nix
          nativeBuildInputs = elmParcelNixFix.nativeBuildPackages;
          npmFlags = elmParcelNixFix.npmFlags;
          preBuild = elmParcelNixFix.preBuild;
          configurePhase = elmParcelNixFix.configurePhase;
        };
      }
    );
}

For other people that google “ParcelJS Elm Nix flake” like I did and arrive here, here is another flake option that uses mkElmDerivation and works as follows (thanks to TheOddler and others):

{
  inputs = {
    flake-utils.url = "github:numtide/flake-utils";
    mkElmDerivation.url = github:jeslie0/mkElmDerivation;
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix";
  };
  outputs = { self, flake-utils, nixpkgs, mkElmDerivation, pre-commit-hooks }:
    flake-utils.lib.eachDefaultSystem
      (system:
        let
          pkgs = import nixpkgs {
            inherit system;
            overlays = [ mkElmDerivation.overlays.mkDotElmDirectoryCmd ];
          };

          elmParcelNixFix = {
            # To make Elm, Parcel and Nix work together we need do some some fixes:
            # 1. The Elm node package tries to download the binary when installing, this doesn't work in Nix's sandbox, so instead we add it to the nativeBuildInputs, set the npmFlag to not run install scripts, and alter this (https://github.com/elm/compiler/blob/047d5026fe6547c842db65f7196fed3f0b4743ee/installers/npm/bin/elm#L8-L30) script to instead of downloading the binary to just use the one we installed as the native build package
            nativeBuildInputs = [ pkgs.elmPackages.elm ];
            npmFlags = [ "--ignore-scripts" ];
            preBuild = ''
              substituteInPlace node_modules/.bin/elm \
              --replace 'var binaryPath = path.resolve' 'var binaryPath = "${pkgs.lib.getExe (pkgs.elmPackages.elm // {meta.mainProgram="elm";})}"; runCommand(); return; //'
            '';
            # 2. Generate the .elm directory 
            generateElmJsonFiles = pkgs.mkDotElmDirectoryCmd ./elm.json;
          };

          buildInputs = with pkgs; [
            elmPackages.elm
            elmPackages.elm-live
            elmPackages.elm-test
            elmPackages.elm-format
            nodejs-18_x
          ];
        in
        {

          packages.default = self.packages.${system}.frontend;
          packages.frontend = pkgs.buildNpmPackage {
            name = "parceljs-with-elm-and-nix";
            src = ./.;

            nativeBuildInputs = elmParcelNixFix.nativeBuildInputs;
            npmFlags = elmParcelNixFix.npmFlags;
            npmPackFlags = [ "--ignore-scripts" ]; # The prepack script runs the build script, which we'd rather do in the build phase.
            prePatch = elmParcelNixFix.generateElmJsonFiles;
            # preConfigure = elmParcelNixFix.generateElmJsonFiles; # seems you can do either prePatch or preConfigure (maybe others? IDK -- I'm new to this)
            preBuild = elmParcelNixFix.preBuild;

            npmDepsHash = "sha256-Lpj6we8KXfdFNEQpWxeLwX0vTPeZRV0B5oRoh9Ur+y4=";
            # npmDepsHash = lib.fakeHash;

            npmBuild = "npm run build";

            installPhase = ''
              mkdir $out
              cp -R dist/* $out/
            '';

          };

          devShells.default = pkgs.mkShell {
            buildInputs = buildInputs ++ elmParcelNixFix.nativeBuildInputs;
            inherit (self.checks.${system}.pre-commit-check) shellHook;
          };

          checks = {
            frontend = self.packages.${system}.frontend;
            pre-commit-check = pre-commit-hooks.lib.${system}.run {
              src = ./.;
              hooks = {
                hlint.enable = true;
                nixpkgs-fmt.enable = true;
              };
              settings = { };
            };
          };
        }
      );
}
2 Likes