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 ; 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 ; 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 ; 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