Using IFD to e.g. define a node or ruby package in a single file?

Defining a ruby bundlerApp package or a node package requires writing several files. For bundlerApp you need a Gemfile and a lockfile and a script that runs bundix to generate the gemset.nix. For a node package you need node-packages.json and a script that runs node2nix to spit out 3 other files. This can be tedious, especially the fact that you have to run the script up front, so you’re paying for the cost of e.g. generating the whole node package set even if you never use the node package.

In any case, I just had the passing thought, could I use Import From Derivation to have a package that runs e.g. node2nix to spit out the files, which are then used from a wrapper derivation? I’m not sure what the limitations on IFD are (beyond the fact that Hydra disables it); can I give the imported derivation network access, since it would need that in order to generate the files? Has anyone ever done anything with this?

TLDR: yes and no.

See Native support for import-from-derivation – Hercules CI blog

Using IFD you can do something like this quick hack I just made (not that I’d recommend it):

with import <nixpkgs> {};
let
  gemfile = builtins.toFile "Gemfile" ''
    source "https://rubygems.org"
    gem "bacon"
  '';

  myGems = stdenv.mkDerivation {
    name = "my-gems";

    nativeBuildInputs = [
      cacert
      (bundler.override { ruby = ruby_2_6; })
    ];

    outputHashAlgo = "sha256";
    outputHashMode = "recursive";
    outputHash = "sha256:1z61jz8w8gi08axgyqfyk43hbw3yds1jxlzsgfpdvf700ckavd0q";

    buildCommand = ''
      export HOME=$PWD
      cp ${gemfile} Gemfile
      bundle install
      mv .gem/ruby/2.6.0/ $out/
    '';

    passthru = {
      wrappedRuby = stdenv.mkDerivation {
        name = "wrapped-ruby";
        buildInputs = [ ruby_2_6 makeWrapper ];
        buildCommand = ''
          mkdir -p $out/bin
          ln -s ${ruby_2_6}/bin/ruby $out/bin/ruby
          wrapProgram $out/bin/ruby --prefix GEM_PATH : ${myGems}
        '';
      };
    };
  };
in mkShell { buildInputs = [ myGems.wrappedRuby ]; }
2 Likes

Looking at your hack, a problem I see is the hash will change any time a gem involved in the dependency tree is updated. I suppose that’s an issue with this approach in general, given that a fixed-output derivation is needed to allow network access, and if I’m generating a lockfile first then I’m doing something more than editing a Nix expression anyway.

I think I did something like this for Go, and yeah…changing hashes, changing machines changing hashes, etc…the usual problems. Though it did slightly alleviate my nixify everything urge for a little bit.
Theres some obscure “__impure” feature in some Eelco branch somewhere that might help with this but I’m not sure, I haven’t tried it.