Skip to content

Finally handle wrappers correctly to make them invisible to applications #150841

@tobiasBora

Description

@tobiasBora

Describe the problem

In Nix, wrappers are required to properly configure paths and can cause dirty issues #60260. In this thread, I focus on one issue of wrappers.

So far, wrapping is done using exec -a "$0" application-unwrapped. When wrapping an executable (i.e. not a script), I think it works nicely as the program cannot really detect that it is inside a wrapper (if the -a is forgotten, it can indeed cause troubles as I just experimented it #60260). However, for scripts the $0 variable is not propagated (this seems to be a quite fundamental issue due to the fact that the interpreter of the scripts does not care about $0): as a consequence, the program thinks that it's name is program-unwrapped. Most of the time it is not a great problem (except that help page may sounds funny with unwrapped paths), but sometimes it can cause more troubles. This is the case notably when the program tries to use $0 to start a new instance of himself (at runtime, or to respawn later, why not in a cron or as a new application...). This is done in carla #117781 but also in chromium #150826 (chromium is a binary, so it's not too much of an issue...) or in Awesome window manager #60229. It requires additional patches, from inside Nixpkgs that may be hard to maintain later and cause additional debugging time from the maintainer.

Steps To Reproduce

Steps to reproduce the behavior:

Create a file myscript.sh:

#!/usr/bin/env bash
echo "My name is $0, and MYENV is $MYENV"

the file derivation.nix:

{ stdenv, makeWrapper }:
stdenv.mkDerivation rec {
  name = "-${version}";
  version = "";

  src = ./.;

  nativeBuildInputs = [ makeWrapper ];

  installPhase = ''
    mkdir -p $out/bin
    cp myscript.sh $out/bin
    chmod +x $out/bin/myscript.sh
    patchShebangs $out/bin/myscript.sh
    wrapProgram $out/bin/myscript.sh --set MYENV "WIN :)"
  '';
}

the file default.nix:

{ pkgs ? import <nixpkgs> {} }:
pkgs.callPackage ./derivation.nix {}

and run:

$ nix-build
$ ./result/bin/myscript.sh 
My name is /nix/store/yc5nf2cdbjzn35n6vqlsmbbi0f5vhymf--/bin/.myscript.sh-wrapped, and MYENV is WIN :)

Expected behavior

I'd expect to see:

My name is ./result/myscript.sh, and MYENV is WIN :)

Proposed solution

A solution would be not to add a wrapper replacing the script, but to put the wrapper code directly in the interpreter of the script as I proposed it here:

{ stdenv, runtimeShell, makeWrapper, writeShellScriptBin }:
let
  # TODO: A maybe cleaner solution would be directly copy the first line of the file, remove the #! and take it as interpreter.
  # In case a patchshebang is required, we can maybe do the patchshebang before the wrapping so that the new interpreter.
  # directly takes the appropriate patchshebang.
  # For this example we know we are running a bash shell, so we just use runtimeShell directly.
  # (for python scripts, replace runtimeShell with something like ${python3}/bin/python3 instead)
  myWrapper = writeShellScriptBin "myWrapper" ''
    # Setup environment variables...
    export MYENV="WIN :-)"
    # Call the original wrapper, and forward all arguments.
    exec ${runtimeShell} "$@"
  '';
in
stdenv.mkDerivation rec {
  name = "-${version}";
  version = "";

  src = ./.;

  nativeBuildInputs = [ makeWrapper ];

  installPhase = ''
    mkdir -p $out/bin
    cp myscript.sh $out/bin
    chmod +x $out/bin/myscript.sh
    # TODO: find a more elegant solution to replace the shebang. (see discussion above comment)
    substituteInPlace $out/bin/myscript.sh --replace '#!/usr/bin/env bash' '#!${myWrapper}/bin/myWrapper'
  '';
}

Result:

My name is ./result/myscript.sh, and MYENV is WIN :-)

Metadata

Metadata

Assignees

No one assigned

    Labels

    0.kind: bugSomething is broken1.severity: significantNovel ideas, large API changes, notable refactorings, issues with RFC potential, etc.2.status: stalehttps://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions