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 https://blog.hercules-ci.com/2019/08/30/native-support-for-import-for-derivation/

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 ]; }
1 Like

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.