nova-cache 0.11.1.0 → 0.11.1.1
raw patch · 5 files changed
+50/−5 lines, 5 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- CHANGELOG.md +4/−0
- README.md +1/−1
- nova-cache.cabal +1/−1
- src/NovaCache/NAR.hs +22/−2
- test/Main.hs +22/−1
CHANGELOG.md view
@@ -1,5 +1,9 @@ # Changelog +## 0.11.1.1 - 2026-08-26++- **A symlink target with a separator serialises to its POSIX spelling on every platform.** Windows stores a symlink's reparse point with backslashes, so a target written `bin/tool` reads back `bin\tool`, and `getSymbolicLinkTarget` returned that verbatim into the NAR. NAR targets are host-independent byte strings in the POSIX spelling, so the archive of the same logical tree diverged between Windows and POSIX, and an unpack-then-recheck round-trip failed its on-disk hash comparison on Windows for any symlink whose target had more than one component. Both producers now normalise the separator to `/` when reading a target on Windows, where a backslash is only ever a separator and encodes as the single byte `0x5C` that no multi-byte UTF-8 sequence contains, so the byte-level replacement is exact. POSIX is untouched, where a backslash is a legitimate filename byte.+ ## 0.11.1.0 - 2026-08-26 - **The default executable answer on POSIX is the file's own owner-execute bit, matching upstream's dump.** `defaultExecBitResolver` (and so both producers before any custom resolver) read `getPermissions`, which answers from `access(2)`: what the calling process may do. Root read any execute bit as executable, ACLs counted, and a file the caller does not own answered by the caller's groups; upstream reads `st_mode & S_IXUSR`, a property of the file alone, and the flag lands in NAR bytes, so the divergence moved a hash exactly where the caller was unusual. The POSIX default now reads the mode bit directly; Windows keeps the extension answer, which is all the platform has.
README.md view
@@ -118,7 +118,7 @@ cabal test ``` -Optional extras: `--flag server` builds the cache server, and the public `nova-cache:xz`, `nova-cache:bzip2`, and `nova-cache:zstandard` sublibraries carry the bounded codecs (liblzma, libbz2, and libzstd are bundled - no system libraries needed) - consumers depend on them with `build-depends: nova-cache:xz` and the like. Requires GHC 9.14+ and cabal-install 3.10+.+Optional extras: `--flag server` builds the cache server, and the public `nova-cache:xz`, `nova-cache:bzip2`, and `nova-cache:zstandard` sublibraries carry the bounded codecs - consumers depend on them with `build-depends: nova-cache:xz` and the like. libbz2 and libzstd are bundled; liblzma comes from the `xz` package, which prefers a pkg-config system liblzma and falls back to its bundled `xz-clib` (pin `constraints: xz -system-xz` to force the bundled copy, as this repo's cabal.project does). Requires GHC 9.14+ and cabal-install 3.10+. ---
nova-cache.cabal view
@@ -4,7 +4,7 @@ -- 3.0 an in-package component name shadowed its external namesake -- (the hazard the zstandard library's name dodges, documented there). name: nova-cache-version: 0.11.1.0+version: 0.11.1.1 synopsis: Pure-first Nix binary cache protocol library description: A pure-first library implementing the Nix binary cache protocol -
src/NovaCache/NAR.hs view
@@ -364,7 +364,7 @@ walkPath opts path = do isSym <- pathIsSymbolicLink path if isSym- then NarSymlink <$> (osPathBytes =<< getSymbolicLinkTarget path)+ then NarSymlink <$> (symlinkTargetBytes =<< getSymbolicLinkTarget path) else do isDir <- doesDirectoryExist path if isDir@@ -494,6 +494,26 @@ fail ("serialiseFromPath: undecodable name: " ++ show err) #endif +-- | A symlink target's NAR bytes. Identical to 'osPathBytes' for a name,+-- except that on Windows the separator is normalised to @\/@: Windows+-- stores a reparse point with backslashes, so a target written @bin\/tool@+-- reads back @bin\\tool@, and a NAR target is the POSIX spelling on every+-- platform so the archive stays host-independent. A backslash is only+-- ever a separator on Windows, never a filename byte, and it encodes as+-- the single byte @0x5C@ that no multi-byte UTF-8 sequence contains, so+-- the byte-level replacement is exact. On POSIX a backslash is a+-- legitimate filename byte and is left untouched.+symlinkTargetBytes :: OsPath -> IO ByteString+#ifdef mingw32_HOST_OS+symlinkTargetBytes path = BS.map normalizeSeparator <$> osPathBytes path+ where+ normalizeSeparator b = if b == backslashByte then forwardSlashByte else b+ backslashByte = 0x5C+ forwardSlashByte = 0x2F+#else+symlinkTargetBytes = osPathBytes+#endif+ -- --------------------------------------------------------------------------- -- Streaming filesystem serialisation (IO boundary) -- ---------------------------------------------------------------------------@@ -633,7 +653,7 @@ isSym <- pathIsSymbolicLink path if isSym then do- target <- osPathBytes =<< getSymbolicLinkTarget path+ target <- symlinkTargetBytes =<< getSymbolicLinkTarget path pure [PieceBytes (buildNode (NarSymlink target))] else do isDir <- doesDirectoryExist path
test/Main.hs view
@@ -31,7 +31,7 @@ import qualified NovaCache.Store as Store import qualified NovaCache.StorePath as StorePath import qualified NovaCache.Validate as Validate-import System.Directory (createDirectory, getPermissions, getTemporaryDirectory, listDirectory, removeDirectoryRecursive, setOwnerExecutable, setPermissions)+import System.Directory (createDirectory, createFileLink, getPermissions, getTemporaryDirectory, listDirectory, removeDirectoryRecursive, setOwnerExecutable, setPermissions) import System.Exit (exitFailure, exitSuccess) import System.IO (hFlush, stdout) import qualified System.Info@@ -573,6 +573,27 @@ expected (sort (map baseName planned)) pure (walkOk && planOk),+ -- A symlink target with a separator serialises to the POSIX+ -- spelling on every platform (nova-nix #112): Windows stores a+ -- reparse point with backslashes, so a target written bin/tool+ -- reads back bin\tool, and an unpack-then-recheck round-trip+ -- diverges from the archive unless the NAR boundary normalises+ -- it. Both producers must agree with the forward-slash form.+ -- Guarded on symlink privilege, which a Windows runner without+ -- developer mode lacks.+ test "a separator-bearing symlink target serialises with forward slashes" $ do+ dir <- caseHackFixture "nova-cache-test-symlinksep"+ created <- try (createFileLink "bin/tool" (dir <> "/link")) :: IO (Either SomeException ())+ case created of+ Left _ -> removeDirectoryRecursive dir >> pure True+ Right () -> do+ let expectedEntry = NAR.NarDirectory [("link", NAR.NarSymlink "bin/tool")]+ eager <- NAR.serialiseFromPathWith NAR.CaseHackDisabled dir+ streamed <- NAR.withNarSource NAR.CaseHackDisabled dir drainSource+ removeDirectoryRecursive dir+ eagerOk <- assertEqual "eager target spelling" expectedEntry eager+ streamOk <- assertEqual "streamed target spelling" (NAR.serialise expectedEntry) streamed+ pure (eagerOk && streamOk), -- The walk's boundary encoding: a Unicode disk name enters the -- archive as its UTF-8 bytes on every platform. test "serialiseFromPath encodes a Unicode disk name as UTF-8" $ do