Skip to content

Instantly share code, notes, and snippets.

@layus

layus/paths.nix

Created Mar 28, 2019
Embed
What would you like to do?
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;
}
@toraritte

This comment has been minimized.

Copy link

@toraritte toraritte commented Aug 13, 2020

Thank you so much for this write-up!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment