Issue packaging ruby app

TLDR; when building a ruby package, the various executables have their ruby shebangs replaced with one that reaches into the nix store. The issue I’m having is these are behaving like bash.

This is just for a little tool we use at work within our own organisation.

Here’s a condensed form of default.nix:

with import <nixpkgs> {};
let
  gems = bundlerEnv {
    name = "craftsman-env";
    inherit ruby;
    gemdir  = ./.;
  };
in stdenv.mkDerivation {
  name = "craftsman";
  src = ./.;
  buildInputs = [
    gems
    gems.wrappedRuby
    ruby
    git
    libpcap
    postgresql
    libxml2
    libxslt
    pkg-config
    bundix
    gnumake
    awscli2  
    neovim
  ];
  installPhase = ''
    mkdir -p $out/{bin,share/craftsman}
    cp -r * $out/share/craftsman
    binstub=$out/bin/craftsman
    cat > $binstub <<EOF
	cd $out/share/craftsman
	export _CRAFTSMAN_ROOT=$out/share/craftsman
	exec ./bin/craftsman "\$@"
EOF
	chmod +x $binstub
	'';
}

more detail…

In the resulting output the shebang in ruby executable files gets changed from “#!/usr/bin/env ruby” to “#!/nix/store/xrzi6sy9w6wlii7li63z9ldqqzhrr4ac-wrapped-ruby-craftsman-env/bin/ruby”, which is fair enough.

To show whats happening though, i’m going to make a couple of scripts using that shebang.

note, the ruby itself is fine:

/nix/store/xrzi6sy9w6wlii7li63z9ldqqzhrr4ac-wrapped-ruby-craftsman-env/bin/ruby -v
ruby 2.7.5p203 (2021-11-24) [arm64-darwin21]

However, when I run the file it behaves like the interpreter is bash…

cat ./xx
#!/nix/store/xrzi6sy9w6wlii7li63z9ldqqzhrr4ac-wrapped-ruby-craftsman-env/bin/ruby
puts "this should be ruby"

[nix-shell:~/w/r/craftsman/result/share/craftsman]$ ./xx
./xx: line 2: puts: command not found
cat x
#!/nix/store/xrzi6sy9w6wlii7li63z9ldqqzhrr4ac-wrapped-ruby-craftsman-env/bin/ruby
echo $0
echo "this appears to be bash"

[nix-shell:~/w/r/craftsman/result/share/craftsman]$ ./x
./x
this appears to be bash

I’m quite confused; is anyone able to help me understand whats happening here?

Thanks in adavance

I think you need to use makeBinaryWrapper instead of makeWrapper on darwin because it does not understand scripts in shebangs IIRC.

1 Like

Hey, thanks Sandro. As far as I can tell i’ve not engaged with makeWrapper, so I guess some dark magic is pulling it in from behind the scenes? Where/how do I coerce makeBinaryWrapper to be used instead?

Not directly but indirect. No dark magic.

Thanks again Sandro, I still don’t see how to encourage the use of wrappedRuby instead however, given I’m not explicitly calling makeWrapper. There’s a libexec directory with 30-40 scripts in, potentially of various scripting languages. all these changes seem to take part in the postFixup hook.

So, when I read the bundlerEnv source, there’s no specific defined postfixup, so I assume its using something more generic. this makes me wonder if the whole darwin setup can be tweaked to then call a more appropriate postFixup hook? Or failing that my default.nix can be configured to do so ?

Before the 22.05 branch off people started to convert some wrappers to always default to the binary wrapper. I am not sure if ruby was already part of that.

To your question where this is done: The default env has a fixup phase where it fixes shebangs. If the shebang has ruby in it, it gets rewritten to the first ruby in PATH. That might actually be a script wrapping around the real ruby. Thats why you don’t see it explicitly.

1 Like