Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce mkBinaryCache function #194345

Merged
merged 2 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/builders/images.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
<xi:include href="images/snaptools.section.xml" />
<xi:include href="images/portableservice.section.xml" />
<xi:include href="images/makediskimage.section.xml" />
<xi:include href="images/binarycache.section.xml" />
</chapter>
49 changes: 49 additions & 0 deletions doc/builders/images/binarycache.section.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# pkgs.mkBinaryCache {#sec-pkgs-binary-cache}

`pkgs.mkBinaryCache` is a function for creating Nix flat-file binary caches. Such a cache exists as a directory on disk, and can be used as a Nix substituter by passing `--substituter file:///path/to/cache` to Nix commands.
thomasjm marked this conversation as resolved.
Show resolved Hide resolved

Nix packages are most commonly shared between machines using [HTTP, SSH, or S3](https://nixos.org/manual/nix/stable/package-management/sharing-packages.html), but a flat-file binary cache can still be useful in some situations. For example, you can copy it directly to another machine, or make it available on a network file system. It can also be a convenient way to make some Nix packages available inside a container via bind-mounting.

Note that 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. You may also want to consider [dockerTools](#sec-pkgs-dockerTools) for your containerization needs.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Note that 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. You may also want to consider [dockerTools](#sec-pkgs-dockerTools) for your containerization needs.
Note that 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. You may also want to consider [`dockerTools`](#sec-pkgs-dockerTools) for your containerization needs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually looks worse when rendered because the links no longer render as blue, so you can't easily tell it's a link.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a problem to be solved in CSS. The source should have rich semantics regardless of the representation du jour. Making things inline code tells the reader it's a code token and not some weird jargon term or other proper name.


## Example
fricklerhandwerk marked this conversation as resolved.
Show resolved Hide resolved

The following derivation will construct a flat-file binary cache containing the closure of `hello`.

```nix
mkBinaryCache {
rootPaths = [hello];
}
```

- `rootPaths` specifies a list of root derivations. The transitive closure of these derivations' outputs will be copied into the cache.
thomasjm marked this conversation as resolved.
Show resolved Hide resolved

Here's an example of building and using the cache.

Build the cache on one machine, `host1`:

```shellSession
nix-build -E 'with import <nixpkgs> {}; mkBinaryCache { rootPaths = [hello]; }'
```

```shellSession
/nix/store/cc0562q828rnjqjyfj23d5q162gb424g-binary-cache
```
thomasjm marked this conversation as resolved.
Show resolved Hide resolved

Copy the resulting directory to the other machine, `host2`:

```shellSession
scp result host2:/tmp/hello-cache
```

Substitute the derivation using the flat-file binary cache on the other machine, `host2`:
```shellSession
nix-build -A hello '<nixpkgs>' \
--option require-sigs false \
--option trusted-substituters file:///tmp/hello-cache \
--option substituters file:///tmp/hello-cache
```

```shellSession
/nix/store/gl5a41azbpsadfkfmbilh9yk40dh5dl0-hello-2.12.1
```
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ in {
bcachefs = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bcachefs.nix {};
beanstalkd = handleTest ./beanstalkd.nix {};
bees = handleTest ./bees.nix {};
binary-cache = handleTest ./binary-cache.nix {};
bind = handleTest ./bind.nix {};
bird = handleTest ./bird.nix {};
bitcoind = handleTest ./bitcoind.nix {};
Expand Down
62 changes: 62 additions & 0 deletions nixos/tests/binary-cache.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import ./make-test-python.nix ({ lib, ... }:

with lib;

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

nodes.machine =
{ pkgs, ... }: {
imports = [ ../modules/installer/cd-dvd/channel.nix ];
environment.systemPackages = with pkgs; [python3];
system.extraDependencies = with pkgs; [hello.inputDerivation];
nix.extraOptions = ''
experimental-features = nix-command
'';
};

testScript = ''
# Build the cache, then remove it from the store
cachePath = machine.succeed("nix-build --no-out-link -E 'with import <nixpkgs> {}; mkBinaryCache { rootPaths = [hello]; }'").strip()
machine.succeed("cp -r %s/. /tmp/cache" % cachePath)
machine.succeed("nix-store --delete " + cachePath)

# Sanity test of cache structure
status, stdout = machine.execute("ls /tmp/cache")
cache_files = stdout.split()
assert ("nix-cache-info" in cache_files)
assert ("nar" in cache_files)

# Nix store ping should work
machine.succeed("nix store ping --store file:///tmp/cache")

# Cache should contain a .narinfo referring to "hello"
grepLogs = machine.succeed("grep -l 'StorePath: /nix/store/[[:alnum:]]*-hello-.*' /tmp/cache/*.narinfo")

# Get the store path referenced by the .narinfo
narInfoFile = grepLogs.strip()
narInfoContents = machine.succeed("cat " + narInfoFile)
import re
match = re.match(r"^StorePath: (/nix/store/[a-z0-9]*-hello-.*)$", narInfoContents, re.MULTILINE)
if not match: raise Exception("Couldn't find hello store path in cache")
storePath = match[1]

# Delete the store path
machine.succeed("nix-store --delete " + storePath)
machine.succeed("[ ! -d %s ] || exit 1" % storePath)

thomasjm marked this conversation as resolved.
Show resolved Hide resolved
# Should be able to build hello using the cache
logs = machine.succeed("nix-build -A hello '<nixpkgs>' --option require-sigs false --option trusted-substituters file:///tmp/cache --option substituters file:///tmp/cache 2>&1")
logLines = logs.split("\n")
if not "this path will be fetched" in logLines[0]: raise Exception("Unexpected first log line")
def shouldBe(got, desired):
if got != desired: raise Exception("Expected '%s' but got '%s'" % (desired, got))
shouldBe(logLines[1], " " + storePath)
shouldBe(logLines[2], "copying path '%s' from 'file:///tmp/cache'..." % storePath)
shouldBe(logLines[3], storePath)

# Store path should exist in the store now
machine.succeed("[ -d %s ] || exit 1" % storePath)
'';
})
40 changes: 40 additions & 0 deletions pkgs/build-support/binary-cache/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{ stdenv, buildPackages }:

# This function is for creating a flat-file binary cache, i.e. the kind created by
# nix copy --to file:///some/path and usable as a substituter (with the file:// prefix).

# For example, in the Nixpkgs repo:
# nix-build -E 'with import ./. {}; mkBinaryCache { rootPaths = [hello]; }'

{ name ? "binary-cache"
, rootPaths
}:

stdenv.mkDerivation {
thomasjm marked this conversation as resolved.
Show resolved Hide resolved
inherit name;

__structuredAttrs = true;

exportReferencesGraph.closure = rootPaths;

preferLocalBuild = true;

PATH = "${buildPackages.coreutils}/bin:${buildPackages.jq}/bin:${buildPackages.python3}/bin:${buildPackages.nix}/bin:${buildPackages.xz}/bin";

builder = builtins.toFile "builder" ''
thomasjm marked this conversation as resolved.
Show resolved Hide resolved
. .attrs.sh

export out=''${outputs[out]}

mkdir $out
mkdir $out/nar

python ${./make-binary-cache.py}

# These directories must exist, or Nix might try to create them in LocalBinaryCacheStore::init(),
# which fails if mounted read-only
mkdir $out/realisations
mkdir $out/debuginfo
mkdir $out/log
'';
}
43 changes: 43 additions & 0 deletions pkgs/build-support/binary-cache/make-binary-cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

import json
import os
import subprocess

with open(".attrs.json", "r") as f:
closures = json.load(f)["closure"]

os.chdir(os.environ["out"])

nixPrefix = os.environ["NIX_STORE"] # Usually /nix/store

with open("nix-cache-info", "w") as f:
f.write("StoreDir: " + nixPrefix + "\n")

def dropPrefix(path):
return path[len(nixPrefix + "/"):]

for item in closures:
narInfoHash = dropPrefix(item["path"]).split("-")[0]

xzFile = "nar/" + narInfoHash + ".nar.xz"
with open(xzFile, "w") as f:
subprocess.run("nix-store --dump %s | xz -c" % item["path"], stdout=f, shell=True)

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

# Rename the .nar.xz file to its own hash to match "nix copy" behavior
finalXzFile = "nar/" + fileHash + ".nar.xz"
os.rename(xzFile, finalXzFile)

with open(narInfoHash + ".narinfo", "w") as f:
f.writelines((x + "\n" for x in [
"StorePath: " + item["path"],
"URL: " + finalXzFile,
"Compression: xz",
"FileHash: sha256:" + fileHash,
"FileSize: " + str(fileSize),
"NarHash: " + item["narHash"],
"NarSize: " + str(item["narSize"]),
"References: " + " ".join(dropPrefix(ref) for ref in item["references"]),
]))
2 changes: 2 additions & 0 deletions pkgs/top-level/all-packages.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,8 @@ with pkgs;
inherit kernel firmware rootModules allowMissing;
};

mkBinaryCache = callPackage ../build-support/binary-cache { };

mkShell = callPackage ../build-support/mkshell { };
mkShellNoCC = mkShell.override { stdenv = stdenvNoCC; };

Expand Down