nix icon indicating copy to clipboard operation
nix copied to clipboard

Add builtins.canonicalPath

Open SFrijters opened this issue 7 months ago • 5 comments

Is your feature request related to a problem?

Symlinks in nixpkgs lib.fileset are causing issues: https://github.com/NixOS/nixpkgs/issues/393845 and this function would help resolve it.

Proposed solution

Add a a builtins.canonicalPath with the same behavior as the realpath command, as suggested by @infinisil .

Alternative solutions

Additional context

Possibly somewhat related: https://github.com/NixOS/nix/issues/2109 (but this feature request would only expose a new nix function, not change anything else inside the nixcpp code itself.

Checklist


Add :+1: to issues you find important.

SFrijters avatar Apr 29 '25 22:04 SFrijters

Returning a canonical path may not always be feasible. Instead of providing this high-level functionality, perhaps we could just expose enough to more or less match the "file system object" data.

Symbolic link

An arbitrary string. Nix does not assign any semantics to symbolic links.

— https://nix.dev/manual/nix/2.28/store/file-system-object

(note that that's in the context of the store layer – the level of abstraction I was thinking of)

Would a builtins.readLink function be sufficient for the purpose of filesets? It would behave like POSIX readlink, not realpath.

roberth avatar May 07 '25 21:05 roberth

Returning a canonical path may not always be feasible.

Would a builtins.readLink function be sufficient for the purpose of filesets?

Yes that would be sufficient (should be called builtins.readSymlink imo though) and independently useful, but only because it's possible to implement a canonicalPath based on it:

canonicalPath =
  path:
  let
    split = splitRoot path;
    components = subpath.components split.subpath;
    canonAppend =
      base: name:
      let
        appended = base + "/${name}";
      in
      if pathType appended == "symlink" then
        lib.foldl' canonAppend base (lib.splitString "/" (readSymlink appended))
      else
        appended;
    recurse =
      acc: i:
      if i == length components then acc else recurse (canonAppend acc (elemAt components i)) (i + 1);
  in
  recurse split.root 0;

This isn't as efficient of course, but I think that's fine for a start. Wouldn't mind having a builtins.canonicalPath that does this directly as well, but this can also be done later.

Btw here's a branch where I implemented a working WIP version of a symlink-compatible fileset based on an impure readSymlink: https://github.com/NixOS/nixpkgs/compare/master...tweag:nixpkgs:fileset-symlink

infinisil avatar May 07 '25 22:05 infinisil

A use case we recently had involved symlinks:

{
  lib,
  pkgs,
  dpLib,
  compName,
  service,
  ...
}:
let
  compDir = dpLib.component.getRootPath compName;

  # Add the OpenAPI files to the docker image.
  openapiJSON = pkgs.writeTextDir "openapi3.json" (
    builtins.readFile (compDir + "/api/openapi3.json")
  );
  openapiYAML = pkgs.writeTextDir "openapi3.yaml" (
    builtins.readFile (compDir + "/api/openapi3.yaml")
  );
in
pkgs.dockerTools.buildLayeredImage {
  name = "dac-portal/${service.pname}-service";
  tag = service.version;

  contents = [
    pkgs.cacert
    service

    # Additional files.
    openapiJSON
    openapiYAML
  ];

Here we build a docker image, and we need to go around builtins.readFile because the path is symlink, and there is currently no way to directly move symlinks (with followSymlinks or similar) to the /nix/store. This workaround is not that nice.

gabyx avatar May 28 '25 07:05 gabyx

From meeting, we are recommending a builtins.readSymlink as a first step. Currently unsure about the interaction should be between canonicalPath and virtual paths (and related WIP changes).

tomberek avatar Jun 04 '25 20:06 tomberek

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/2025-05-04-nix-team-meeting-minutes-230/65206/1

nixos-discourse avatar Jun 04 '25 22:06 nixos-discourse