Add a script in an existing project

Hello,

I’d like to add a script to an existing project, in the bin folder, that calls some executable that lies in the same bin folder. I’ve tried to use writeShellScriptBin with something like:

let
  mywrapper = binaryfile: writeShellScript "XX" ''
    bla;
    ${binaryfile}&
    blo
  '';
in
mkDerivation rec {
  # ...
  installPhase = ''
    install -Dt $out/bin mainbinary ${myWrapper (out + "/bin/mainbinary")}";
}

Unfortunately, this does not compile since out variable does not exist. How could I make this script work? How could I refer to the current derivation? Is there a better way to proceed?

PS: for reference here is the script I’m trying to use:

{ stdenv, fetchFromGitHub, pkgconfig, libusbmuxd, libplist, speex, libav, alsaLib, gtk3, libappindicator-gtk3, libjpeg_turbo, writeShellScriptBin }:
let
  myWrapper = binaryfile: writeShellScriptBin "droidcam-with-modules" ''
    if ! modinfo v4l2loopback_dc; then
      sudo modprobe v4l2loopback-dc
    fi
    if ! modinfo videodev; then
      sudo modprobe videodev
    fi
    if ! modinfo snd_aloop; then
      sudo modprobe snd_aloop
    fi
    ${binaryfile} &
    pacmd load-module module-alsa-source device=hw:Loopback,1,0
    wait
  '';
in
stdenv.mkDerivation rec {
  pname = "droidcam";
  version = "1.5";

  src = fetchFromGitHub {
    owner = "aramg";
    repo = "droidcam";
    rev = "v1.5";
    sha256 = "tIb7wqzAjSHoT9169NiUO+z6w5DrJVYvkQ3OxDqI1DA=";
  };

  sourceRoot = "source/linux";

  buildInputs = [ pkgconfig ];
  nativeBuildInputs = [ libappindicator-gtk3 speex libav gtk3 libjpeg_turbo libusbmuxd libplist alsaLib ];

  makeFlags = [ "JPEG_DIR=${libjpeg_turbo.out}" "JPEG_LIB=${libjpeg_turbo.out}/lib" ];
  postPatch = ''
    sed -i -e 's:(JPEG_LIB)/libturbojpeg.a:(JPEG_LIB)/libturbojpeg.so:' Makefile
    substituteInPlace src/droidcam.c --replace "/opt/droidcam-icon.png" "$out/share/icons/hicolor/droidcam.png"
  '';

  installPhase = ''
    # Copy the executables in the bin folder, creating automatically the subfolders
    install -Dt $out/bin droidcam droidcam-cli (myWrapper "${out}/bin/droidcam")
    # Same for pictures
    install -D icon2.png $out/share/icons/hicolor/droidcam.png
  '';

  meta = with stdenv.lib; {
    description = "DroidCam Linux client";
    homepage = https://github.com/aramg/droidcam;
  };
}

It’s a runtime variable in shell (or other programs running during the build). On nix-language level one can now also use placeholder "out" instead.

Ah… you can’t do it this way at all. You’re trying to define two derivations (one running writeShellScript and one with the result) and those are getting a cyclic reference. I think it will be easiest to do create the script directly during installPhase. Utilities like wrapProgram might be useful, though perhaps not in your case… perhaps cat << EOF idiom or something similar.

Oh, I though that placeholder was the solution indeed… but unfortunately because it’s evaluated at build time I can’t crossref between derivations. I created another thread to understand more things concerning placeholders and cyclic dependencies here.

Yeah, usually it seems pretty useful, but in my specific case I need to add stuff after the execution script (and I need to add a &), and wrapProgram does not allow that, no idea why.

Actually, I found in this cookbook a great alternative using symlinkJoin that basically allows you to merge several derivations by linking the corresponding out files of all derivations in a final folder, and it’s very simple to use:

symlinkJoin {
  name = "droidcam";
  paths = [ droidcam droidcamWrapper ];
}

Here is the complete example:

{ stdenv, fetchFromGitHub, pkgconfig, libusbmuxd, libplist, speex, libav, alsaLib, gtk3, libappindicator-gtk3, libjpeg_turbo, writeShellScriptBin, symlinkJoin }:
let
  droidcam = stdenv.mkDerivation rec {
    pname = "droidcam";
    version = "1.5";
  
    src = fetchFromGitHub {
      owner = "aramg";
      repo = "droidcam";
      rev = "v1.5";
      sha256 = "tIb7wqzAjSHoT9169NiUO+z6w5DrJVYvkQ3OxDqI1DA=";
    };
  
    sourceRoot = "source/linux";
  
    buildInputs = [ pkgconfig ];
    nativeBuildInputs = [ libappindicator-gtk3 speex libav gtk3 libjpeg_turbo libusbmuxd libplist alsaLib ];
  
    makeFlags = [ "JPEG_DIR=${libjpeg_turbo.out}" "JPEG_LIB=${libjpeg_turbo.out}/lib" ];
    postPatch = ''
      sed -i -e 's:(JPEG_LIB)/libturbojpeg.a:(JPEG_LIB)/libturbojpeg.so:' Makefile
      substituteInPlace src/droidcam.c --replace "/opt/droidcam-icon.png" "$out/share/icons/hicolor/droidcam.png"
    '';
  
    installPhase = ''
      # Copy the executables in the bin folder, creating automatically the subfolders
      install -Dt $out/bin droidcam droidcam-cli
      # Same for pictures
      install -D icon2.png $out/share/icons/hicolor/droidcam.png
    '';
  
    meta = with stdenv.lib; {
      description = "DroidCam Linux client";
      homepage = https://github.com/aramg/droidcam;
    };
  };
  droidcamWrapper = writeShellScriptBin "droidcam-with-modules" ''
    if ! modinfo v4l2loopback_dc &>/dev/null; then
      sudo modprobe v4l2loopback-dc
    fi
    if ! modinfo videodev &>/dev/null; then
      sudo modprobe videodev
    fi
    if ! modinfo snd_aloop &>/dev/null; then
      sudo modprobe snd_aloop
    fi
    ${droidcam}/bin/droidcam &
    pacmd load-module module-alsa-source device=hw:Loopback,1,0
    wait
  '';
in
# Merges the two derivations into just one
symlinkJoin {
  name = "droidcam";
  paths = [ droidcam droidcamWrapper ];
}

I guess I’ll stick to that solution, that is both simple and elegant when makeProgram/makeWrapper are not enough, but if you have other ideas, let me know I’m curious!

1 Like

Yes, these approaches with splitting the wrapping into a separate derivation are nice especially if the binary build is expensive, relatively to your need of re-plugging different modules (in this case).

Just for reference I found another method (thanks infinisil) if you don’t want to use symlinkJoin (in my specific example however I do think it’s better since the big project compilation is cached):

{stdenv, runtimeShell}:
stdenv.mkDerivation rec {
  myWrapper = ''
    #!${runtimeShell}
    ${placeholder "out"}/bin/mybinary
  '';
  # Make sure that myWrapper is turned into a file and not transmitted in environment variables
  # Refer to the file with myWrapperFile later.
  passAsFile = [ "myWrapper" ]; 
  installPhase = ''
    cp $myWrapperPath $out/bin/mybinary_wrapped
    chmod +x $out/bin/mybinary_wrapped
  '';
}

Basically, ${runtimeShell} is like ${bash}/bin/bash, then we create a new attribute myWrapper containing our script: a derivation can contain any attribute that is given later using environment variables… except if you use the passAsFile directive! If you use it, the attribue is turned into a file in the nix store, and then you can refer to that file by adding Path at the end: here it’s myWrapperPath!