Skip to content

Commit

Permalink
mkBinaryCache: support different compression methods: xz (default), z…
Browse files Browse the repository at this point in the history
…std, none
  • Loading branch information
thomasjm committed Jan 24, 2025
1 parent 2f5bd17 commit 63b1dbe
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 19 deletions.
8 changes: 8 additions & 0 deletions doc/build-helpers/images/binarycache.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ It can also be a convenient way to make some Nix packages available inside a con
`rootPaths` must be a list of derivations.
The transitive closure of these derivations' outputs will be copied into the cache.

## Optional arguments {#sec-pkgs-binary-cache-arguments}

`compression` (`"none"` or `"xz"` or `"zstd"`; _optional_)

: The compression algorithm to use.

_Default value:_ `zstd`.

::: {.note}
This function is meant for advanced use cases.
The more idiomatic way to work with flat-file binary caches is via the [nix-copy-closure](https://nixos.org/manual/nix/stable/command-ref/nix-copy-closure.html) command.
Expand Down
3 changes: 3 additions & 0 deletions doc/redirects.json
Original file line number Diff line number Diff line change
Expand Up @@ -1959,6 +1959,9 @@
"sec-pkgs-binary-cache": [
"index.html#sec-pkgs-binary-cache"
],
"sec-pkgs-binary-cache-arguments": [
"index.html#sec-pkgs-binary-cache-arguments"
],
"sec-pkgs-binary-cache-example": [
"index.html#sec-pkgs-binary-cache-example"
],
Expand Down
4 changes: 3 additions & 1 deletion nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,9 @@ in {
beanstalkd = handleTest ./beanstalkd.nix {};
bees = handleTest ./bees.nix {};
benchexec = handleTest ./benchexec.nix {};
binary-cache = handleTest ./binary-cache.nix {};
binary-cache = handleTest ./binary-cache.nix { compression = "zstd"; };
binary-cache-no-compression = handleTest ./binary-cache.nix { compression = "none"; };
binary-cache-xz = handleTest ./binary-cache.nix { compression = "xz"; };
bind = handleTest ./bind.nix {};
bird = handleTest ./bird.nix {};
birdwatcher = handleTest ./birdwatcher.nix {};
Expand Down
8 changes: 5 additions & 3 deletions nixos/tests/binary-cache.nix
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{ compression, ... }@args:

import ./make-test-python.nix (
{ lib, pkgs, ... }:

{
name = "binary-cache";
name = "binary-cache-" + compression;
meta.maintainers = with lib.maintainers; [ thomasjm ];

nodes.machine =
Expand All @@ -24,7 +26,7 @@ import ./make-test-python.nix (
nativeBuildInputs = [ openssl ];
}
''
tar -czf tmp.tar.gz -C "${mkBinaryCache { rootPaths = [ hello ]; }}" .
tar -czf tmp.tar.gz -C "${mkBinaryCache { rootPaths = [ hello ]; inherit compression; }}" .
openssl enc -aes-256-cbc -salt -in tmp.tar.gz -out $out -k mysecretpassword
'';

Expand Down Expand Up @@ -78,4 +80,4 @@ import ./make-test-python.nix (
machine.succeed("[ -d %s ] || exit 1" % storePath)
'';
}
)
) args
16 changes: 13 additions & 3 deletions pkgs/build-support/binary-cache/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
python3,
nix,
xz,
zstd,
}:

# This function is for creating a flat-file binary cache, i.e. the kind created by
Expand All @@ -16,9 +17,16 @@

{
name ? "binary-cache",
compression ? "zstd", # one of ["none" "xz" "zstd"]
rootPaths,
}:

assert lib.elem compression [
"none"
"xz"
"zstd"
];

stdenv.mkDerivation {
inherit name;

Expand All @@ -33,13 +41,15 @@ stdenv.mkDerivation {
jq
python3
nix
xz
];
]
++ lib.optional (compression == "xz") xz
++ lib.optional (compression == "zstd") zstd
;

buildCommand = ''
mkdir -p $out/nar
python ${./make-binary-cache.py}
python ${./make-binary-cache.py} --compression "${compression}"
# These directories must exist, or Nix might try to create them in LocalBinaryCacheStore::init(),
# which fails if mounted read-only
Expand Down
52 changes: 40 additions & 12 deletions pkgs/build-support/binary-cache/make-binary-cache.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import argparse
from functools import partial
import json
from multiprocessing import Pool
Expand All @@ -10,44 +11,64 @@ def dropPrefix(path, nixPrefix):
return path[len(nixPrefix + "/") :]


def processItem(item, nixPrefix, outDir):
def processItem(
item, nixPrefix, outDir, compression, compressionCommand, compressionExtension
):
narInfoHash = dropPrefix(item["path"], nixPrefix).split("-")[0]

xzFile = outDir / "nar" / f"{narInfoHash}.nar.xz"
with open(xzFile, "wb") as f:
narFile = outDir / "nar" / f"{narInfoHash}{compressionExtension}"
with open(narFile, "wb") as f:
subprocess.run(
f"nix-store --dump {item['path']} | xz -c",
f"nix-store --dump {item['path']} {compressionCommand}",
stdout=f,
shell=True,
check=True,
)

fileHash = (
subprocess.run(
["nix-hash", "--base32", "--type", "sha256", "--flat", xzFile],
["nix-hash", "--base32", "--type", "sha256", "--flat", narFile],
capture_output=True,
check=True,
)
.stdout.decode()
.strip()
)
fileSize = os.path.getsize(xzFile)
fileSize = os.path.getsize(narFile)

finalXzFileName = Path("nar") / f"{fileHash}.nar.xz"
os.rename(xzFile, outDir / finalXzFileName)
finalNarFileName = Path("nar") / f"{fileHash}{compressionExtension}"
os.rename(narFile, outDir / finalNarFileName)

with open(outDir / f"{narInfoHash}.narinfo", "wt") as f:
f.write(f"StorePath: {item['path']}\n")
f.write(f"URL: {finalXzFileName}\n")
f.write("Compression: xz\n")
f.write(f"URL: {finalNarFileName}\n")
f.write(f"Compression: {compression}\n")
f.write(f"FileHash: sha256:{fileHash}\n")
f.write(f"FileSize: {fileSize}\n")
f.write(f"NarHash: {item['narHash']}\n")
f.write(f"NarSize: {item['narSize']}\n")
f.write(f"References: {' '.join(dropPrefix(ref, nixPrefix) for ref in item['references'])}\n")
f.write(
f"References: {' '.join(dropPrefix(ref, nixPrefix) for ref in item['references'])}\n"
)


def main():
parser = argparse.ArgumentParser()
parser.add_argument("--compression", choices=["none", "xz", "zstd"])
args = parser.parse_args()

compressionCommand = {
"none": "",
"xz": "| xz -c",
"zstd": "| zstd",
}[args.compression]

compressionExtension = {
"none": "",
"xz": ".xz",
"zstd": ".zst",
}[args.compression]

outDir = Path(os.environ["out"])
nixPrefix = os.environ["NIX_STORE"]
numWorkers = int(os.environ.get("NIX_BUILD_CORES", "4"))
Expand All @@ -61,7 +82,14 @@ def main():
f.write(f"StoreDir: {nixPrefix}\n")

with Pool(processes=numWorkers) as pool:
worker = partial(processItem, nixPrefix=nixPrefix, outDir=outDir)
worker = partial(
processItem,
nixPrefix=nixPrefix,
outDir=outDir,
compression=args.compression,
compressionCommand=compressionCommand,
compressionExtension=compressionExtension,
)
pool.map(worker, closures)


Expand Down

0 comments on commit 63b1dbe

Please sign in to comment.