While reading the release notes of Nix v2.34, I learned about the lint-absolute-path-literals option (docs). I also learned about absolute path literals and how they are not portable.
So I set lint-absolute-path-literals to warn in my config. And now I got a bunch of warnings about absolute path literals being used in Nixpkgs.
So I set out to remove all absolute path literals from Nixpkgs. I made the following PRs so far:
- NixOS/nixpkgs#511569 - remove absolute path literals from
nixos/prometheus - NixOS/nixpkgs#513210 - remove absolute path literals from
nixos/oci-containers - NixOS/nixpkgs#513211 - remove absolute path literals from
nixos/duplicity - NixOS/nixpkgs#513212 - remove absolute path literals from
nixos/evcc - NixOS/nixpkgs#513213 - remove absolute path literals from
nixos/maddy - NixOS/nixpkgs#513214 - remove absolute path literals from
nixos/stargazer
I don’t think I can fix the remaining cases on my own. I need advice from someone more experienced.
Case 1: lib/path/default.nix
This is the relevant code from lib/path/default.nix:
hasStorePathPrefix =
path:
let
deconstructed = deconstructPath path;
in
assert assertMsg (isPath path)
"lib.path.hasStorePathPrefix: Argument is of type ${typeOf path}, but a path was expected";
assert assertMsg
# This function likely breaks or needs adjustment if used with other filesystem roots, if they ever get implemented.
# Let's try to error nicely in such a case, though it's unclear how an implementation would work even and whether this could be detected.
# See also https://github.com/NixOS/nix/pull/6530#discussion_r1422843117
(deconstructed.root == /. && toString deconstructed.root == "/")
"lib.path.hasStorePathPrefix: Argument has a filesystem root (${toString deconstructed.root}) that's not /, which is currently not supported.";
componentsHaveStorePathPrefix deconstructed.components;
To me, deconstructed.root == /. and toString deconstructed.root == "/" are the same condition repeated twice. But of course I’m wrong and there is a reason for this that invalidates my whole intuition about how paths and strings are related.
Case 2: lib/filesystem.nix
This is the relevant code from lib/filesystem.nix:
locateDominatingFile =
pattern: file:
let
go =
path:
let
files = builtins.attrNames (builtins.readDir path);
matches = builtins.filter (match: match != null) (map (builtins.match pattern) files);
in
if builtins.length matches != 0 then
{ inherit path matches; }
else if path == /. then
null
else
go (dirOf path);
parent = dirOf file;
isDir =
let
base = baseNameOf file;
type = (builtins.readDir parent).${base} or null;
in
file == /. || type == "directory";
in
go (if isDir then file else parent);
Here we can:
- Replace
path == /.withtoString path == "/" - Replace
file == /.withtoString file == "/"
Looks straightforward. I just need confirmation.
Case 3: lib/sources.nix
This is the relevant code from lib/sources.nix:
absolutePath =
base: path: if lib.hasPrefix "/" path then path else toString (/. + "${base}/${path}");
I’m assuming the conversion from string to path then again to a string is to guard against base not starting with /. If my assumption is correct we can remove the absolute path literal by:
absolutePath =
base: path:
if lib.hasPrefix "/" path then
path
else if lib.hasPrefix "/" base then
"${base}/${path}"
else
"/${base}/${path}";
Case 4: lib/types.nix
This is the relevant code from lib/types.nix:
isInStore = lib.path.hasStorePathPrefix (
if builtins.isPath x then
x
# Discarding string context is necessary to convert the value to
# a path and safe as the result is never used in any derivation.
else
/. + builtins.unsafeDiscardStringContext x
);
I have absolutely no idea on this one.
Sorry for the long post. I would appreciate any help with this.