What am I doing wrong here?

Hello guys, I’m packaging a custom version of NixOps but suddenly it
stopped working and I do not know what has gone wrong. Let me start
from the beginning. I’m doing this to use the “remote” capability for
libvirt introduced by PR #824.
My own fork of it it’s here GitHub - azazel75/nixops at remote-libvirt .

Now, up until before upgrading my NixOS this morning (I’m using the
unstable channel) I was using this snippet to package it as dependency
in my nix-shell:

let
  pkgs = import <nixpkgs> {};
  remoteVirtNixOpsPkg = builtins.fetchTarball {
    url = "https://github.com/azazel75/nixops/archive/remote-libvirt.tar.gz";
    sha256 = "1mg1bsgwrydyk2i0g5pzc35kr6ybjrihvfns8kd8d6b74qxnyh40";
  };
  nixOpsRelease = import "${remoteVirtNixOpsPkg}/release.nix" {};
in 
  nixOpsRelease.build.${pkgs.system}

but now if I do a nix-build on it I get an error, specifically:

error: aiohttp-3.5.4 not supported for interpreter python2.7

This is probably a problem of the packages in unstable that are
diverging too much from the expected versions present in current
stable release. Looking for a solution in the NixOps release.nix
source

I saw that it accepts a nixpkgs parameter, so I though of feeding it
with a stable nixpkgs tarball so I tried:

let
  # nixos-18.09-small Released on 2019-03-02
  stableTarball = builtins.fetchTarball {
    url = "https://github.com/NixOs/nixpkgs-channels/archive/80754f5cfd69d0caf8cff6795d3ce6d99479abde.tar.gz";
    sha256 = "1v8g68gqgij389dssh6ry5x1zlhpgcgwjac0rgrh8146gf9hq74f";
  };
  pkgs = import <nixpkgs> {};
  remoteVirtNixOpsPkg = builtins.fetchTarball {
    url = "https://github.com/azazel75/nixops/archive/remote-libvirt.tar.gz";
    sha256 = "1mg1bsgwrydyk2i0g5pzc35kr6ybjrihvfns8kd8d6b74qxnyh40";
  };
  nixOpsRelease = import "${remoteVirtNixOpsPkg}/release.nix" {nixpkgs=stableTarball;};
in 
  nixOpsRelease.build.${pkgs.system}

but trying to build it I get:

$ nix-build --show-trace /tmp/build_nixops.nix 
error: while evaluating the attribute 'buildInputs' of the derivation 'nixops-1.6.1pre0_abcdef' at /nix/store/927lfycfz9mh61rmwqr7ylpn45wy6xjz-source/pkgs/stdenv/generic/make-derivation.nix:177:11:
while evaluating 'chooseDevOutputs' at /nix/store/927lfycfz9mh61rmwqr7ylpn45wy6xjz-source/lib/attrsets.nix:462:22, called from undefined position:
while evaluating 'optional' at /nix/store/927lfycfz9mh61rmwqr7ylpn45wy6xjz-source/lib/lists.nix:241:20, called from /nix/store/927lfycfz9mh61rmwqr7ylpn45wy6xjz-source/pkgs/development/interpreters/python/mk-python-derivation.nix:77:8:
while evaluating 'hasSuffix' at /nix/store/927lfycfz9mh61rmwqr7ylpn45wy6xjz-source/lib/strings.nix:232:5, called from /nix/store/927lfycfz9mh61rmwqr7ylpn45wy6xjz-source/pkgs/development/interpreters/python/mk-python-derivation.nix:77:22:
while evaluating the attribute 'src.name' at /nix/store/651063fc6njk8ma6i88kyz5k78r8a2b3-source/release.nix:76:7:
while evaluating the attribute 'distPhase' of the derivation 'nixops-tarball-1.6.1pre0_abcdef' at /nix/store/927lfycfz9mh61rmwqr7ylpn45wy6xjz-source/pkgs/stdenv/generic/make-derivation.nix:177:11:
while evaluating the attribute 'buildCommand' of the derivation 'options-db.xml' at /nix/store/927lfycfz9mh61rmwqr7ylpn45wy6xjz-source/pkgs/stdenv/generic/make-derivation.nix:177:11:
getting attributes of path '/nixos/doc/manual/options-to-docbook.xsl': No such file or directory

Trying to debug the situation, I think that the evaluation jumps from
/release.nix#39
to
/doc/manual/resource.nix#18
where it incredibly evaluates the expression ${nixpkgs + /nixos/doc/manual/options-to-docbook.xsl} to
/nixos/doc/manual/options-to-docbook.xsl, thus giving the error.

The fun/sad thing is that if you preload the tarball of the custom
NixOps with nix-prefetch-url:

$ nix-prefetch-url --unpack https://github.com/NixOs/nixpkgs-channels/archive/80754f5cfd69d0caf8cff6795d3ce6d99479abde.tar.gz
unpacking...
[14.4 MiB DL]
path is '/nix/store/wq5cwrk0lf5r217676ypcp3fppky3b1w-80754f5cfd69d0caf8cff6795d3ce6d99479abde.tar.gz'
1v8g68gqgij389dssh6ry5x1zlhpgcgwjac0rgrh8146gf9hq74f

and then you hardcode this path in the previous expression:

let
  # nixos-18.09-small Released on 2019-03-02
  stableTarball = /nix/store/wq5cwrk0lf5r217676ypcp3fppky3b1w-80754f5cfd69d0caf8cff6795d3ce6d99479abde.tar.gz;
  pkgs = import <nixpkgs> {};
  remoteVirtNixOpsPkg = builtins.fetchTarball {
    url = "https://github.com/azazel75/nixops/archive/remote-libvirt.tar.gz";
    sha256 = "1mg1bsgwrydyk2i0g5pzc35kr6ybjrihvfns8kd8d6b74qxnyh40";
  };
  nixOpsRelease = import "${remoteVirtNixOpsPkg}/release.nix" {nixpkgs=stableTarball;};
in 
  nixOpsRelease.build.${pkgs.system}

it builds:

$ nix-build --show-trace /tmp/build_nixops.nix 
/nix/store/zp4bhakb3vj826pnazy5bln26rgym77g-nixops-1.6.1pre0_abcdef

again if you pass in a stableTarball with the same path as string
it doesn’t build, with the same error above. What’s happening here?
Can somebody explain it to me?

P.S.
I tried to transform the output of fetchTarball from string to path
using the expression nixpkgs = (/. + stableTarball) (as suggested by @samueldr on IRC, now that
builtins.toPath is deprecated) but I’ve got the error “a string that
refers to a store path cannot be appended to a path” whose real
meaning I fail to understand? Why should it be an issue?

1 Like

I have also noticed issues with NixOps packing. It’s not easy to override, docs need to be built or bypassed, Azure library issues, the aiohttp problem, etc. Nixpkgs past Mar 20th (I’m on de0612c46cf17a368e92eaac91fd94affbe36488) - i’ve not git blame’d it yet - seems to have the aiotthp issue, which should just be resolved separately. Here’s what i’m using now:

Uses my fork of NixOps, a few commits ahead of master.

self: super : rec {

  python2Packages = super.python2Packages.override {
    overrides =
    (self: super2: let callPackage = super.newScope self; in {
      azure-mgmt-compute = callPackage <nixpkgs/pkgs/tools/package-management/nixops/azure-mgmt-compute> { };
      azure-mgmt-network = callPackage <nixpkgs/pkgs/tools/package-management/nixops/azure-mgmt-network> { };
      azure-mgmt-nspkg = callPackage <nixpkgs/pkgs/tools/package-management/nixops/azure-mgmt-nspkg> { };
      azure-mgmt-resource = callPackage <nixpkgs/pkgs/tools/package-management/nixops/azure-mgmt-resource> { };
      azure-mgmt-storage = callPackage <nixpkgs/pkgs/tools/package-management/nixops/azure-mgmt-storage> { };
    });
  };
  nixopsSrc = self.fetchFromGitHub {
        owner = "tomberek";
        repo = "nixops";
        rev = "3c007d6b3f6a1a931ade2562ba3f7cbca87e4222";
        sha256 = "167pyzfyvmn8d859yxhr6ysxf9i39p4g7zhdl4s0gr8qjzvvl9mj";
      };

  nixopsUnstable = (import "${nixopsSrc}/release.nix" {
    officialRelease = true;
  }).build.${builtins.currentSystem};
}
~

Hi @tomberek, and thanks.
I’m using something like yours too:

let
  # nixos-18.09-small Released on 2019-03-02
  stableTarball = builtins.fetchTarball {
    url = "https://github.com/NixOs/nixpkgs-channels/archive/80754f5cfd69d0caf8cff6795d3ce6d99479abde.tar.gz";
    sha256 = "1v8g68gqgij389dssh6ry5x1zlhpgcgwjac0rgrh8146gf9hq74f";
  };
  pkgs = import <nixpkgs> {};
  stablePkgs = import stableTarball {};
  remoteVirtNixOpsPkg = builtins.fetchTarball {
    url = "https://github.com/azazel75/nixops/archive/remote-libvirt.tar.gz";
    sha256 = "1mg1bsgwrydyk2i0g5pzc35kr6ybjrihvfns8kd8d6b74qxnyh40";
  };
  nixOpsVersion = "1.6.1-remoteLibvirt";
  nixOpsRelease = rec {
    tarball = pkgs.releaseTools.sourceTarball {
      name = "nixops-tarball";

      src = remoteVirtNixOpsPkg;

      version = nixOpsVersion;

      officialRelease = true; # hack

      buildInputs = [ pkgs.git pkgs.libxslt pkgs.docbook5_xsl ];

      postUnpack = ''
        # Clean up when building from a working tree.
        if [ -d $sourceRoot/.git ]; then
          (cd $sourceRoot && (git ls-files -o | xargs -r rm -v))
        fi
      '';

      distPhase = ''
        # IMPORTANT: when adding a file here, also populate doc/manual/manual.xml

        for i in scripts/nixops setup.py; do
          substituteInPlace $i --subst-var-by version ${nixOpsVersion}
        done

        releaseName=nixops-$VERSION
        mkdir ../$releaseName
        cp -prd . ../$releaseName
        rm -rf ../$releaseName/.git
        mkdir $out/tarballs
        tar  cvfj $out/tarballs/$releaseName.tar.bz2 -C .. $releaseName
      '';
    };

    build = pkgs.lib.genAttrs [ "x86_64-linux" "i686-linux" "x86_64-darwin" ] (system:
      with import stableTarball { inherit system; };

    python2Packages.buildPythonApplication rec {
      name = "nixops-${nixOpsVersion}";

      src = "${tarball}/tarballs/*.tar.bz2";

      buildInputs = [ python2Packages.nose python2Packages.coverage ];

      nativeBuildInputs = [ pkgs.mypy ];

      propagatedBuildInputs = with python2Packages;
      [
        prettytable
        boto
        boto3
        hetzner
        libcloud
        libvirt
        azure-storage
        azure-mgmt-compute
        azure-mgmt-network
        azure-mgmt-resource
        azure-mgmt-storage
        adal
        # Go back to sqlite once Python 2.7.13 is released
        pysqlite
        datadog
        digital-ocean
        typing
      ];

      # For "nix-build --run-env".
      shellHook = ''
        export PYTHONPATH=$(pwd):$PYTHONPATH
        export PATH=$(pwd)/scripts:${openssh}/bin:$PATH
      '';

      doCheck = true;

      postCheck = ''
        # We have to unset PYTHONPATH here since it will pick enum34 which collides
        # with python3 own module. This can be removed when nixops is ported to python3.
        PYTHONPATH= mypy --cache-dir=/dev/null nixops

        # smoke test
        HOME=$TMPDIR $out/bin/nixops --version
      '';

      # Needed by libcloud during tests
      SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";

      # Add openssh to nixops' PATH. On some platforms, e.g. CentOS and RHEL
      # the version of openssh is causing errors when have big networks (40+)
      makeWrapperArgs = ["--prefix" "PATH" ":" "${openssh}/bin" "--set" "PYTHONPATH" ":"];

      postInstall =
        ''
          mkdir -p $out/share/nix/nixops
          cp -av nix/* $out/share/nix/nixops
        '';

      meta.description = "Nix package for ${stdenv.system}";
    });
  };
in
  nixOpsRelease.build.${pkgs.system}

This is an edited copy of NixOps’ release.nix to avoid building the
manual, that I stated above seems to be the problem.

I really was curious to know if the problem was with the code or is a
bug of Nix…

Your immediate problem is different. You are trying to build a path, but without the builtins.toPath you can only append your way there. There is some checking to ensure strings with contexts don’t get appended to paths, but there is a hacky way around it:

nix-repl> a=lib.strings.addContextFrom pkgs.hello "/nix/store/0s6b01r2wwysf1hzclkm5sz7xhv2knyf-nixpkgs"

nix-repl> /. + a
error: a string that refers to a store path cannot be appended to a path, at (string):1:1

nix-repl> /. + "/nix/store" + builtins.elemAt (builtins.split "/nix/store" a) 2
/nix/store/0s6b01r2wwysf1hzclkm5sz7xhv2knyf-nixpkgs

Just my two cents here, but you can also use builtins.unsafeDiscardStringContext if you want to be explicit about (unsafely) discarding the context of a string.

1 Like

But @layus, the following expression shouldn’t be an issue with a nixpkgs variable that is of either type string or path:

it’s possible to add a path to a string… to obtain an error for a missing file with path /nixos/doc/manual/options-to-docbook.xsl means that in that context the nixpkgs variable is evaluated to the empty string, and that is what surprises me, it seems to me that the nixpkgs variable is inherited correctly in the code that from release.nix imports /doc/manual/resource.nix so what’s really happening there?

@azazel75 I got a bit carried away while replying :wink:

A gist available here https://gist.github.com/layus/e2024dea364ff0dc7112d8cffe388cdf, with the exact same content as the file below.

with import <nixpkgs> {};

let
  path = ./mwe;
  string = "./mwe";
  pathPath = path + /test/file.nix;
  pathString = path + "/test/file.nix";
  pathStringPath = path + "/test" + /file.nix;
  stringPath = string + /test/file.nix;
  stringPathWorking = string + /nix/var/nix/db/schema;
  stringString = string + "/test/file.nix";

  toPathRelative = p: ./. + p;
  toPathAbsolute = p: /. + p;
  isString = v: builtins.typeOf v == "string";
  isPath = v: builtins.typeOf v == "path";
in
  # Strings, paths and `+`
  # ======================
  #
  # a short-turned-long primer ;-)
  # ------------------------------
  #
  # Rule 1:
  # The result of a `+` is of the same type as the first element added
  # string + any -> string, path + any -> path.
  #
  # Rule 2:
  # A path represents a path on the local filesystem.
  # When turned into a string, it must therefore be copied to the store, and be replaced by the resulting store path.
  # Otherwise, when generating configuration files & such, the path may not exist on the deployment machine, or not exist anymore locally.
  # This explains why stringPath fails. Moving '/test/file.nix' to the store is doomed to fail.

  # '/test/file.nix' obviously does not exist.
  #assert isString stringPath; #=> error: getting attributes of path '/test/file.nix': No such file or directory
  assert isString stringPathWorking;
  assert isString stringString;

  # Exception to rule 2:
  # When a path is appended to a path, no such check occurs. The rationale
  # being that the check will happen later on, when the path is really needed
  # (this preserves lazyness, and avoids the error above in most cases.)
  #
  # This allows to write things as ${nixpkgs + /nixos/doc/manual/options-to-docbook.xsl}
  # without having to copy the whole ${nixpkgs} to the store, and only the resulting path.
  assert isPath pathPath;
  assert isPath pathString;
  assert isPath pathStringPath;

  assert pathString == pathPath;
  assert pathStringPath == pathPath;

  # It should be clear by now that an expression such as
  #
  #     ${nixpkgs + /nixos/doc/manual/options-to-docbook.xsl}
  #
  # will only work when `nixpkgs` is a path, because `/nixos/doc/manual/options-to-docbook.xsl`r
  # cannot be expected to exist in any local filesystem.
  #
  # The strange thing is, builtins.fetchtarball returns a string, and not a
  # path. This is valid because that string carries itself as a store path
  # context.

  # To fix your issue, you need to ensure that `nixpkgs` is a path.
  # To that end, builtins.toPath fails to deliver because it turns strings into... strings,
  # and only works with absolute paths. It could be renamed to throwIfRelative ;-).
  assert isString (builtins.toPath "/nix/var/nix/db/schema");
  #assert isString (builtins.toPath "./mwe"); #=> error: string './mwe' doesn't represent an absolute path

  # In fact, the only way to coerce to a path is to append to a path.
  assert isPath (toPathAbsolute "/nix/var/nix/db/schema");
  assert isPath (toPathRelative "./mwe/result/file.nix");

  # As for the long-term fix, we should turn
  #
  #     ${nixpkgs + /nixos/doc/manual/options-to-docbook.xsl}
  #
  # to
  #
  #     ${nixpkgs + "/nixos/doc/manual/options-to-docbook.xsl"}
  #
  # Do you want to make the PR yourself ?

  # I guess this all boils down to an unfinished work at getting rid of paths.
  # They have mostly indistinguishable from strings nowadays, but still
  # persist here and there.


  # Appendix
  # --------
  #
  # There are misunderstandings in your code
  #
  #     stableTarball = /nix/store/wq5cwrk0lf5r217676ypcp3fppky3b1w-80754f5cfd69d0caf8cff6795d3ce6d99479abde.tar.gz;
  #
  # This is wrong because it creates a copy of that path in the nix store.
  # You end up with a new derivation with the exact same content and a longer name.
  # (See the three hashes in the resulting name ?)
  #
  #     $ nix-instantiate --expr --eval 'let x = /nix/store/wq5cwrk0lf5r217676ypcp3fppky3b1w-80754f5cfd69d0caf8cff6795d3ce6d99479abde.tar.gz; in "${x}"'
  #     "/nix/store/11dbw5s7r1zcqvq6pwz0d30z6g5b1nky-wq5cwrk0lf5r217676ypcp3fppky3b1w-80754f5cfd69d0caf8cff6795d3ce6d99479abde.tar.gz"
  #
  # Now, I guess you ended up there mostly out of despair :-D.
  # To do that, you should rather use storePath
  #
  #     $ nix-instantiate --expr --eval 'let x = builtins.storePath /nix/store/wq5cwrk0lf5r217676ypcp3fppky3b1w-80754f5cfd69d0caf8cff6795d3ce6d99479abde.tar.gz; in "${x}"'
  #     "/nix/store/wq5cwrk0lf5r217676ypcp3fppky3b1w-80754f5cfd69d0caf8cff6795d3ce6d99479abde.tar.gz"
  #
  # but... like builtins.fetchtarball, it returns a string, and not a path, so
  # in your particular case, it was the "right" way to go.

  {
    # If you want to inspect the values defined here, use nix-instantiate.
    #
    #     $ nix-instantiate --eval <this-file>.nix --json --strict | yq .
    #     {
    #       "path": "/nix/store/mr3bwp0537dsi662hzacww621z4avzhp-mwe",
    #       "pathPath": "/nix/store/vylng2dfxdy6m3dwasscpgvjj50yxjqa-file.nix",
    #       "pathString": "/nix/store/vylng2dfxdy6m3dwasscpgvjj50yxjqa-file.nix",
    #       "pathStringPath": "/nix/store/vylng2dfxdy6m3dwasscpgvjj50yxjqa-file.nix",
    #       "string": "./mwe",
    #       "stringPathWorking": "./mwe/nix/store/yr1nmfgr6ifs1lgqyafcizwxm2rp8y9k-schema",
    #       "stringString": "./mwe/test/file.nix"
    #     }
    #
    inherit string path pathPath pathString pathStringPath /*stringPath*/ stringPathWorking stringString;
  }
3 Likes

Hi @layus and sorry for being so late. Thanks for showing that off :smiley:
, it’s very appreciated. Answering some points that you make:

As for the long-term fix, we should turn

${nixpkgs + /nixos/doc/manual/options-to-docbook.xsl}

to

${nixpkgs + “/nixos/doc/manual/options-to-docbook.xsl”}

Do you want to make the PR yourself ?

Yes, I did it here Allow to add a nixpkgs prefix that isn't a path but a string by azazel75 · Pull Request #1123 · NixOS/nixops · GitHub

There are misunderstandings in your code

stableTarball = /nix/store/wq5cwrk0lf5r217676ypcp3fppky3b1w-80754f5cfd69d0caf8cff6795d3ce6d99479abde.tar.gz;

This is wrong because it creates a copy of that path in the nix store.

You end up with a new derivation with the exact same content and a longer name.

(See the three hashes in the resulting name ?)

$ nix-instantiate --expr --eval ‘let x = /nix/store/wq5cwrk0lf5r217676ypcp3fppky3b1w-80754f5cfd69d0caf8cff6795d3ce6d99479abd>e.tar.gz; in “${x}”’

“/nix/store/11dbw5s7r1zcqvq6pwz0d30z6g5b1nky-wq5cwrk0lf5r217676ypcp3fppky3b1w-80754f5cfd69d0caf8cff6795d3ce6d99479abde.tar.g>z”

Now, I guess you ended up there mostly out of despair :-D.

To do that, you should rather use storePath

$ nix-instantiate --expr --eval ‘let x = builtins.storePath /nix/store/wq5cwrk0lf5r217676ypcp3fppky3b1w-80754f5cfd69d0caf8cf>f6795d3ce6d99479abde.tar.gz; in “${x}”’

“/nix/store/wq5cwrk0lf5r217676ypcp3fppky3b1w-80754f5cfd69d0caf8cff6795d3ce6d99479abde.tar.gz”

but… like builtins.fetchtarball, it returns a string, and not a path, so

in your particular case, it was the “right” way to go.

:wink: I did use it just to explain my point in my post.

Thanks again,

Alberto

1 Like

Here’s my shell.nix:

{
  pkgsSrc ? fetchGit {
    url = https://github.com/nixos/nixpkgs-channels;
    ref = "nixos-19.03";
    rev = "2f1eacc949fe31f34d7af4ebfd97c01a02ceee16";
  },
  nixopsSrc ? fetchTarball {
    url = https://github.com/asymmetric/nixops/archive/hetzner-cloud.tar.gz;
    sha256 = "0xdaxpaalggbcsbn5r4n7svl59ycrfpy2cgfjgvkxf75i6vr6x47";
  } + "/release.nix"
}:

let
  pkgs = (import pkgsSrc {});
  nixops = (import nixopsSrc { nixpkgs = pkgs.path; }).build.x86_64-linux;
in
  pkgs.mkShell {
    name = "nixops-env";
    buildInputs = [ nixops ];
    NIX_PATH="nixpkgs=${pkgs.path}";
  }

As you can see I use pkgs.path.

I can then override the version of nixops I’m building with nix-shell --arg nixopsSrc ./foo/bar.