about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRobert Hensing <roberth@users.noreply.github.com>2023-05-23 09:32:01 +0200
committerGitHub <noreply@github.com>2023-05-23 09:32:01 +0200
commita344acdc7fb898b90e6e1d7b7660246dbbda1940 (patch)
tree4b60f89d6b5516444c42b3fd08faf586f3e1be42
parent81a620111755cc8a9a651aed9480f2f45f658a86 (diff)
parent378bf1a6192a5d46ceb7e07af7be1d37b93b47c7 (diff)
Merge pull request #224834 from tweag/pathType-and-co
Improvements to pathType, pathIsDirectory and pathIsRegularFile
-rw-r--r--lib/default.nix5
-rw-r--r--lib/filesystem.nix82
-rw-r--r--lib/sources.nix37
-rwxr-xr-xlib/tests/filesystem.sh92
-rw-r--r--lib/tests/release.nix3
5 files changed, 197 insertions, 22 deletions
diff --git a/lib/default.nix b/lib/default.nix
index 0424db36b2e99..8fea4b8ad6374 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -117,10 +117,11 @@ let
     inherit (self.meta) addMetaAttrs dontDistribute setName updateName
       appendToName mapDerivationAttrset setPrio lowPrio lowPrioSet hiPrio
       hiPrioSet getLicenseFromSpdxId getExe;
-    inherit (self.sources) pathType pathIsDirectory cleanSourceFilter
+    inherit (self.filesystem) pathType pathIsDirectory pathIsRegularFile;
+    inherit (self.sources) cleanSourceFilter
       cleanSource sourceByRegex sourceFilesBySuffices
       commitIdFromGitRepo cleanSourceWith pathHasContext
-      canCleanSource pathIsRegularFile pathIsGitRepo;
+      canCleanSource pathIsGitRepo;
     inherit (self.modules) evalModules setDefaultModuleLocation
       unifyModuleSyntax applyModuleArgsIfFunction mergeModules
       mergeModules' mergeOptionDecls evalOptionValue mergeDefinitions
diff --git a/lib/filesystem.nix b/lib/filesystem.nix
index 9481960552031..4860d4d02a773 100644
--- a/lib/filesystem.nix
+++ b/lib/filesystem.nix
@@ -1,13 +1,93 @@
-# Functions for copying sources to the Nix store.
+# Functions for querying information about the filesystem
+# without copying any files to the Nix store.
 { lib }:
 
+# Tested in lib/tests/filesystem.sh
 let
+  inherit (builtins)
+    readDir
+    pathExists
+    ;
+
   inherit (lib.strings)
     hasPrefix
     ;
+
+  inherit (lib.filesystem)
+    pathType
+    ;
 in
 
 {
+
+  /*
+    The type of a path. The path needs to exist and be accessible.
+    The result is either "directory" for a directory, "regular" for a regular file, "symlink" for a symlink, or "unknown" for anything else.
+
+    Type:
+      pathType :: Path -> String
+
+    Example:
+      pathType /.
+      => "directory"
+
+      pathType /some/file.nix
+      => "regular"
+  */
+  pathType =
+    builtins.readFileType or
+    # Nix <2.14 compatibility shim
+    (path:
+      if ! pathExists path
+      # Fail irrecoverably to mimic the historic behavior of this function and
+      # the new builtins.readFileType
+      then abort "lib.filesystem.pathType: Path ${toString path} does not exist."
+      # The filesystem root is the only path where `dirOf / == /` and
+      # `baseNameOf /` is not valid. We can detect this and directly return
+      # "directory", since we know the filesystem root can't be anything else.
+      else if dirOf path == path
+      then "directory"
+      else (readDir (dirOf path)).${baseNameOf path}
+    );
+
+  /*
+    Whether a path exists and is a directory.
+
+    Type:
+      pathIsDirectory :: Path -> Bool
+
+    Example:
+      pathIsDirectory /.
+      => true
+
+      pathIsDirectory /this/does/not/exist
+      => false
+
+      pathIsDirectory /some/file.nix
+      => false
+  */
+  pathIsDirectory = path:
+    pathExists path && pathType path == "directory";
+
+  /*
+    Whether a path exists and is a regular file, meaning not a symlink or any other special file type.
+
+    Type:
+      pathIsRegularFile :: Path -> Bool
+
+    Example:
+      pathIsRegularFile /.
+      => false
+
+      pathIsRegularFile /this/does/not/exist
+      => false
+
+      pathIsRegularFile /some/file.nix
+      => true
+  */
+  pathIsRegularFile = path:
+    pathExists path && pathType path == "regular";
+
   /*
     A map of all haskell packages defined in the given path,
     identified by having a cabal file with the same name as the
diff --git a/lib/sources.nix b/lib/sources.nix
index 3ad7dc6335549..d990777c6fcc7 100644
--- a/lib/sources.nix
+++ b/lib/sources.nix
@@ -18,21 +18,11 @@ let
     pathExists
     readFile
     ;
-
-  /*
-    Returns the type of a path: regular (for file), symlink, or directory.
-  */
-  pathType = path: getAttr (baseNameOf path) (readDir (dirOf path));
-
-  /*
-    Returns true if the path exists and is a directory, false otherwise.
-  */
-  pathIsDirectory = path: if pathExists path then (pathType path) == "directory" else false;
-
-  /*
-    Returns true if the path exists and is a regular file, false otherwise.
-  */
-  pathIsRegularFile = path: if pathExists path then (pathType path) == "regular" else false;
+  inherit (lib.filesystem)
+    pathType
+    pathIsDirectory
+    pathIsRegularFile
+    ;
 
   /*
     A basic filter for `cleanSourceWith` that removes
@@ -271,11 +261,20 @@ let
     };
 
 in {
-  inherit
-    pathType
-    pathIsDirectory
-    pathIsRegularFile
 
+  pathType = lib.warnIf (lib.isInOldestRelease 2305)
+    "lib.sources.pathType has been moved to lib.filesystem.pathType."
+    lib.filesystem.pathType;
+
+  pathIsDirectory = lib.warnIf (lib.isInOldestRelease 2305)
+    "lib.sources.pathIsDirectory has been moved to lib.filesystem.pathIsDirectory."
+    lib.filesystem.pathIsDirectory;
+
+  pathIsRegularFile = lib.warnIf (lib.isInOldestRelease 2305)
+    "lib.sources.pathIsRegularFile has been moved to lib.filesystem.pathIsRegularFile."
+    lib.filesystem.pathIsRegularFile;
+
+  inherit
     pathIsGitRepo
     commitIdFromGitRepo
 
diff --git a/lib/tests/filesystem.sh b/lib/tests/filesystem.sh
new file mode 100755
index 0000000000000..4a5ffeb124312
--- /dev/null
+++ b/lib/tests/filesystem.sh
@@ -0,0 +1,92 @@
+#!/usr/bin/env bash
+
+# Tests lib/filesystem.nix
+# Run:
+# [nixpkgs]$ lib/tests/filesystem.sh
+# or:
+# [nixpkgs]$ nix-build lib/tests/release.nix
+
+set -euo pipefail
+shopt -s inherit_errexit
+
+# Use
+#     || die
+die() {
+  echo >&2 "test case failed: " "$@"
+  exit 1
+}
+
+if test -n "${TEST_LIB:-}"; then
+  NIX_PATH=nixpkgs="$(dirname "$TEST_LIB")"
+else
+  NIX_PATH=nixpkgs="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.."; pwd)"
+fi
+export NIX_PATH
+
+work="$(mktemp -d)"
+clean_up() {
+  rm -rf "$work"
+}
+trap clean_up EXIT
+cd "$work"
+
+mkdir directory
+touch regular
+ln -s target symlink
+mkfifo fifo
+
+checkPathType() {
+    local path=$1
+    local expectedPathType=$2
+    local actualPathType=$(nix-instantiate --eval --strict --json 2>&1 \
+        -E '{ path }: let lib = import <nixpkgs/lib>; in lib.filesystem.pathType path' \
+        --argstr path "$path")
+    if [[ "$actualPathType" != "$expectedPathType" ]]; then
+        die "lib.filesystem.pathType \"$path\" == $actualPathType, but $expectedPathType was expected"
+    fi
+}
+
+checkPathType "/" '"directory"'
+checkPathType "$PWD/directory" '"directory"'
+checkPathType "$PWD/regular" '"regular"'
+checkPathType "$PWD/symlink" '"symlink"'
+checkPathType "$PWD/fifo" '"unknown"'
+checkPathType "$PWD/non-existent" "error: evaluation aborted with the following error message: 'lib.filesystem.pathType: Path $PWD/non-existent does not exist.'"
+
+checkPathIsDirectory() {
+    local path=$1
+    local expectedIsDirectory=$2
+    local actualIsDirectory=$(nix-instantiate --eval --strict --json 2>&1 \
+        -E '{ path }: let lib = import <nixpkgs/lib>; in lib.filesystem.pathIsDirectory path' \
+        --argstr path "$path")
+    if [[ "$actualIsDirectory" != "$expectedIsDirectory" ]]; then
+        die "lib.filesystem.pathIsDirectory \"$path\" == $actualIsDirectory, but $expectedIsDirectory was expected"
+    fi
+}
+
+checkPathIsDirectory "/" "true"
+checkPathIsDirectory "$PWD/directory" "true"
+checkPathIsDirectory "$PWD/regular" "false"
+checkPathIsDirectory "$PWD/symlink" "false"
+checkPathIsDirectory "$PWD/fifo" "false"
+checkPathIsDirectory "$PWD/non-existent" "false"
+
+checkPathIsRegularFile() {
+    local path=$1
+    local expectedIsRegularFile=$2
+    local actualIsRegularFile=$(nix-instantiate --eval --strict --json 2>&1 \
+        -E '{ path }: let lib = import <nixpkgs/lib>; in lib.filesystem.pathIsRegularFile path' \
+        --argstr path "$path")
+    if [[ "$actualIsRegularFile" != "$expectedIsRegularFile" ]]; then
+        die "lib.filesystem.pathIsRegularFile \"$path\" == $actualIsRegularFile, but $expectedIsRegularFile was expected"
+    fi
+}
+
+checkPathIsRegularFile "/" "false"
+checkPathIsRegularFile "$PWD/directory" "false"
+checkPathIsRegularFile "$PWD/regular" "true"
+checkPathIsRegularFile "$PWD/symlink" "false"
+checkPathIsRegularFile "$PWD/fifo" "false"
+checkPathIsRegularFile "$PWD/non-existent" "false"
+
+echo >&2 tests ok
diff --git a/lib/tests/release.nix b/lib/tests/release.nix
index dbf6683d49a85..f5c6e81030caf 100644
--- a/lib/tests/release.nix
+++ b/lib/tests/release.nix
@@ -44,6 +44,9 @@ pkgs.runCommand "nixpkgs-lib-tests" {
     echo "Running lib/tests/modules.sh"
     bash lib/tests/modules.sh
 
+    echo "Running lib/tests/filesystem.sh"
+    TEST_LIB=$PWD/lib bash lib/tests/filesystem.sh
+
     echo "Running lib/tests/sources.sh"
     TEST_LIB=$PWD/lib bash lib/tests/sources.sh