What am I doing wrong here?

@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