chore(*): drop everything that is not required for Tvix

Co-Authored-By: edef <edef@edef.eu>
Co-Authored-By: Ryan Lahfa <raito@lix.systems>
Change-Id: I9817214c3122e49d694c5e41818622a08d9dfe45
This commit is contained in:
Florian Klink 2025-01-05 17:12:30 +01:00
parent bd91cac1f3
commit df4500ea2b
2905 changed files with 34 additions and 493328 deletions

View file

@ -1 +1 @@
Profpatsch
raitobezarius

View file

@ -1,254 +0,0 @@
buildLisp.nix
=============
This is a build system for Common Lisp, written in Nix.
It aims to offer an alternative to ASDF for users who live in a
Nix-based ecosystem. This offers several advantages over ASDF:
* Simpler (almost logic-less) package definitions
* Easy linking of native dependencies (from Nix)
* Composability with Nix tooling for other languages
* Effective, per-system caching strategies
* Easy overriding of dependencies and whatnot
* Convenient support for multiple Common Lisp implementations
* ... and more!
The project is still in its early stages and some important
restrictions should be highlighted:
* Extending `buildLisp` with support for a custom implementation
currently requires some knowledge of internals and may not be
considered stable yet.
* Parallel compilation is not possible: Since buildLisp doesn't encode
dependencies between components (i. e. source files) like ASDF,
it must compile source files in sequence to avoid errors due to
undefined symbols.
## Usage
`buildLisp` exposes four different functions:
* `buildLisp.library`: Builds a collection of Lisp files into a library.
| parameter | type | use | required? |
|-----------|--------------|-------------------------------|-----------|
| `name` | `string` | Name of the library | yes |
| `srcs` | `list<path>` | List of paths to source files | yes |
| `deps` | `list<drv>` | List of dependencies | no |
| `native` | `list<drv>` | List of native dependencies | no |
| `test` | see "Tests" | Specification for test suite | no |
| `implementation` | see "Implementations" | Common Lisp implementation to use | no |
The output of invoking this is a directory containing a FASL file
that is the concatenated result of all compiled sources.
* `buildLisp.program`: Builds an executable program out of Lisp files.
| parameter | type | use | required? |
|-----------|--------------|-------------------------------|-----------|
| `name` | `string` | Name of the program | yes |
| `srcs` | `list<path>` | List of paths to source files | yes |
| `deps` | `list<drv>` | List of dependencies | no |
| `native` | `list<drv>` | List of native dependencies | no |
| `main` | `string` | Entrypoint function | no |
| `test` | see "Tests" | Specification for test suite | no |
| `implementation` | see "Implementations" | Common Lisp implementation to use | no |
The `main` parameter should be the name of a function and defaults
to `${name}:main` (i.e. the *exported* `main` function of the
package named after the program).
The output of invoking this is a directory containing a
`bin/${name}`.
* `buildLisp.bundled`: Creates a virtual dependency on a built-in library.
Certain libraries ship with Lisp implementations, for example
UIOP/ASDF are commonly included but many implementations also ship
internals (such as SBCLs various `sb-*` libraries).
This function takes a single string argument that is the name of a
built-in library and returns a "package" that simply requires this
library.
## Tests
Both `buildLisp.library` and `buildLisp.program` take an optional argument
`tests`, which has the following supported fields:
| parameter | type | use | required? |
|--------------|--------------|-------------------------------|-----------|
| `name` | `string` | Name of the test suite | no |
| `expression` | `string` | Lisp expression to run tests | yes |
| `srcs` | `list<path>` | List of paths to source files | no |
| `native` | `list<drv>` | List of native dependencies | no |
the `expression` parameter should be a Lisp expression and will be evaluated
after loading all sources and dependencies (including library/program
dependencies). It must return a non-`NIL` value if the test suite has passed.
## Example
Using buildLisp could look like this:
```nix
{ buildLisp, lispPkgs }:
let libExample = buildLisp.library {
name = "lib-example";
srcs = [ ./lib.lisp ];
deps = with lispPkgs; [
(buildLisp.bundled "sb-posix")
iterate
cl-ppcre
];
};
in buildLisp.program {
name = "example";
deps = [ libExample ];
srcs = [ ./main.lisp ];
tests = {
deps = [ lispPkgs.fiveam ];
srcs = [ ./tests.lisp ];
expression = "(fiveam:run!)";
};
}
```
## Development REPLs
`buildLisp` builds loadable variants of both `program` and `library` derivations
(usually FASL files). Therefore it can provide a convenient way to obtain an
instance of any implementation preloaded with `buildLisp`-derivations. This
is especially useful to use as a host for Sly or SLIME.
* `buildLisp.sbcl.lispWith`, `buildLisp.ccl.lispWith`, ...:
Creates a wrapper script preloading a Lisp implementation with various dependencies.
This function takes a single argument which is a list of Lisp
libraries programs or programs. The desired Lisp implementation
will load all given derivations and all their dependencies on
startup.
The shortcut `buildLisp.sbclWith` for `buildLisp.sbcl.lispWith` is also provided.
* `repl` passthru attribute: `derivation.repl` is provided as a shortcut
for `buildLisp.${implementationName}.lispWith [ derivation ]`.
`derivation.ccl.repl`, `derivation.sbcl.repl` etc. work as well, of course
(see also "Implementations" section).
## Implementations
Both `buildLisp.library` and `buildLisp.program` allow specifying a different
Common Lisp implementation than the default one (which is SBCL). When an
implementation is passed, `buildLisp` makes sure all dependencies are built
with that implementation as well since build artifacts from different
implementation will be incompatible with each other.
The argument taken by `implementation` is a special attribute set which
describes how to do certain tasks for a given implementation, like building
or loading a library. In case you want to use a custom implementation
description, the precise structure needed is documented in `buildLisp`'s
source code for now. `buildLisp` also exposes the following already
working implementation sets:
* `buildLisp.sbcl`: [SBCL][sbcl], our default implementation
* `buildLisp.ccl`: [CCL][ccl], similar to SBCL, but with very good macOS support
* `buildLisp.ecl`: [ECL][ecl] setup to produce statically linked binaries and
libraries. Note that its runtime library is LGPL, so [extra conditions][lgpl-static]
must be fulfilled when distributing binaries produced this way.
* Support for ABCL is planned.
For every of these “known” implementations, `buildLisp` will create a `passthru`
attribute named like the implementation which points to a variant of the derivation
built with said implementation. Say we have a derivation, `myDrv`, built using SBCL:
While `myDrv` and `myDrv.sbcl` are built using SBCL, `myDrv.ecl`, `myDrv.ccl` etc.
build the derivation and all its dependencies using ECL and CCL respectively.
This is useful to test portability of your derivation, but is also used internally
to speed up the “normalization” of the dependency graph. Thus it is important to
make sure that your custom implementation's name doesn't clash with one of the
“known” ones.
## Handling Implementation Specifics
When targeting multiple Common Lisp implementation, it is often necessary to
handle differing interfaces for OS interaction or to make use of special
implementation features. For this reason, `buildLisp` allows specifying
dependencies and source files for specific implementations only. This can
be utilized by having an attribute set in the list for the `deps` or `srcs`
argument: `buildLisp` will pick the value of the attribute named like the
used implementation or `default` and ignore the set completely if both
are missing.
```nix
{ buildLisp, lispPkgs }:
buildLisp.library {
name = "mylib";
srcs = [
# These are included always of course
./package.lisp
./portable-lib.lisp
# Choose right impl-* file
{
sbcl = ./impl-sbcl.lisp;
ccl = ./impl-ccl.lisp;
ecl = ./impl-ecl.lisp;
}
# We can also use this to inject extra files
{ ecl = ./extra-ecl-optimizations.lisp; }
];
deps = [
# Use SBCL's special bundled package, flexi-streams otherwise
{
sbcl = buildLisp.bundled "sb-rotate-byte";
default = lispPkgs.flexi-streams;
}
];
}
```
Additionally a `brokenOn` parameter is accepted which takes a list of
implementation names on which the derivation is not expected to work.
This only influences `meta.ci.targets` which is read by depot's CI to
check which variants (see "Implementations") of the derivation to
build, so it may not be useful outside of depot.
## Influencing the Lisp Runtime
Lisp implementations which create an executable by dumping an image
usually parse a few implementation-specific command line options on
executable startup that influence runtime settings related to things
like GC. `buildLisp` generates a wrapper which makes sure that this
never interferes with the argument parsing implemented in the actual
application, but sometimes it is useful to run an executable with
special settings. To allow this, the content of `NIX_BUILDLISP_LISP_ARGS`
is passed to the lisp implementation.
For example, you can make the underlying SBCL print its version for
any executable built with `buildLisp` (and SBCL) like this:
```console
$ env NIX_BUILDLISP_LISP_ARGS="--version" ./result/bin/🕰️
SBCL 2.1.2.nixos
```
In practice you'd probably want to specify options like
`--dynamic-space-size` or `--tls-limit` (try passing `--help` for a
full list). Naturally, these options are completely different for
different implementations.
[sbcl]: http://www.sbcl.org/
[ccl]: https://ccl.clozure.com/
[ecl]: https://common-lisp.net/project/ecl/
[lgpl-static]: https://www.gnu.org/licenses/gpl-faq.en.html#LGPLStaticVsDynamic

View file

@ -1,778 +0,0 @@
# buildLisp provides Nix functions to build Common Lisp packages,
# targeting SBCL.
#
# buildLisp is designed to enforce conventions and do away with the
# free-for-all of existing Lisp build systems.
{ pkgs ? import <nixpkgs> { }, ... }:
let
inherit (builtins) map elemAt match filter;
inherit (pkgs) lib runCommand makeWrapper writeText writeShellScriptBin sbcl ecl-static ccl;
inherit (pkgs.stdenv) targetPlatform;
#
# Internal helper definitions
#
defaultImplementation = impls.sbcl;
# Many Common Lisp implementations (like ECL and CCL) will occasionally drop
# you into an interactive debugger even when executing something as a script.
# In nix builds we don't want such a situation: Any error should make the
# script exit non-zero. Luckily the ANSI standard specifies *debugger-hook*
# which is invoked before the debugger letting us just do that.
disableDebugger = writeText "disable-debugger.lisp" ''
(setf *debugger-hook*
(lambda (error hook)
(declare (ignore hook))
(format *error-output* "~%Unhandled error: ~a~%" error)
#+ccl (quit 1)
#+ecl (ext:quit 1)))
'';
# Process a list of arbitrary values which also contains “implementation
# filter sets” which describe conditonal inclusion of elements depending
# on the CL implementation used. Elements are processed in the following
# manner:
#
# * Paths, strings, derivations are left as is
# * A non-derivation attribute set is processed like this:
# 1. If it has an attribute equal to impl.name, replace with its value.
# 2. Alternatively use the value of the "default" attribute.
# 3. In all other cases delete the element from the list.
#
# This can be used to express dependencies or source files which are specific
# to certain implementations:
#
# srcs = [
# # mixable with unconditional entries
# ./package.lisp
#
# # implementation specific source files
# {
# ccl = ./impl-ccl.lisp;
# sbcl = ./impl-sbcl.lisp;
# ecl = ./impl-ecl.lisp;
# }
# ];
#
# deps = [
# # this dependency is ignored if impl.name != "sbcl"
# { sbcl = buildLisp.bundled "sb-posix"; }
#
# # only special casing for a single implementation
# {
# sbcl = buildLisp.bundled "uiop";
# default = buildLisp.bundled "asdf";
# }
# ];
implFilter = impl: xs:
let
isFilterSet = x: builtins.isAttrs x && !(lib.isDerivation x);
in
builtins.map
(
x: if isFilterSet x then x.${impl.name} or x.default else x
)
(builtins.filter
(
x: !(isFilterSet x) || x ? ${impl.name} || x ? default
)
xs);
# Generates lisp code which instructs the given lisp implementation to load
# all the given dependencies.
genLoadLispGeneric = impl: deps:
lib.concatStringsSep "\n"
(map (lib: "(load \"${lib}/${lib.lispName}.${impl.faslExt}\")")
(allDeps impl deps));
# 'genTestLispGeneric' generates a Lisp file that loads all sources and deps
# and executes expression for a given implementation description.
genTestLispGeneric = impl: { name, srcs, deps, expression }: writeText "${name}.lisp" ''
;; Dependencies
${impl.genLoadLisp deps}
;; Sources
${lib.concatStringsSep "\n" (map (src: "(load \"${src}\")") srcs)}
;; Test expression
(unless ${expression}
(exit :code 1))
'';
# 'dependsOn' determines whether Lisp library 'b' depends on 'a'.
dependsOn = a: b: builtins.elem a b.lispDeps;
# 'allDeps' flattens the list of dependencies (and their
# dependencies) into one ordered list of unique deps which
# all use the given implementation.
allDeps = impl: deps:
let
# The override _should_ propagate itself recursively, as every derivation
# would only expose its actually used dependencies. Use implementation
# attribute created by withExtras if present, override in all other cases
# (mainly bundled).
deps' = builtins.map
(dep: dep."${impl.name}" or (dep.overrideLisp (_: {
implementation = impl;
})))
deps;
in
(lib.toposort dependsOn (lib.unique (
lib.flatten (deps' ++ (map (d: d.lispDeps) deps'))
))).result;
# 'allNative' extracts all native dependencies of a dependency list
# to ensure that library load paths are set correctly during all
# compilations and program assembly.
allNative = native: deps: lib.unique (
lib.flatten (native ++ (map (d: d.lispNativeDeps) deps))
);
# Add an `overrideLisp` attribute to a function result that works
# similar to `overrideAttrs`, but is used specifically for the
# arguments passed to Lisp builders.
makeOverridable = f: orig: (f orig) // {
overrideLisp = new: makeOverridable f (orig // (new orig));
};
# This is a wrapper arround 'makeOverridable' which performs its
# function, but also adds a the following additional attributes to the
# resulting derivation, namely a repl attribute which builds a `lispWith`
# derivation for the current implementation and additional attributes for
# every all implementations. So `drv.sbcl` would build the derivation
# with SBCL regardless of what was specified in the initial arguments.
withExtras = f: args:
let
drv = (makeOverridable f) args;
in
lib.fix (self:
drv.overrideLisp
(old:
let
implementation = old.implementation or defaultImplementation;
brokenOn = old.brokenOn or [ ];
targets = lib.subtractLists (brokenOn ++ [ implementation.name ])
(builtins.attrNames impls);
in
{
passthru = (old.passthru or { }) // {
repl = implementation.lispWith [ self ];
# meta is done via passthru to minimize rebuilds caused by overriding
meta = (old.passthru.meta or { }) // {
ci = (old.passthru.meta.ci or { }) // {
inherit targets;
};
};
} // builtins.listToAttrs (builtins.map
(impl: {
inherit (impl) name;
value = self.overrideLisp (_: {
implementation = impl;
});
})
(builtins.attrValues impls));
}) // {
overrideLisp = new: withExtras f (args // new args);
});
# 'testSuite' builds a Common Lisp test suite that loads all of srcs and deps,
# and then executes expression to check its result
testSuite = { name, expression, srcs, deps ? [ ], native ? [ ], implementation }:
let
lispDeps = allDeps implementation (implFilter implementation deps);
lispNativeDeps = allNative native lispDeps;
filteredSrcs = implFilter implementation srcs;
in
runCommand name
{
LD_LIBRARY_PATH = lib.makeLibraryPath lispNativeDeps;
LANG = "C.UTF-8";
} ''
echo "Running test suite ${name}"
${implementation.runScript} ${
implementation.genTestLisp {
inherit name expression;
srcs = filteredSrcs;
deps = lispDeps;
}
} | tee $out
echo "Test suite ${name} succeeded"
'';
# 'impls' is an attribute set of attribute sets which describe how to do common
# tasks when building for different Common Lisp implementations. Each
# implementation set has the following members:
#
# Required members:
#
# - runScript :: string
# Describes how to invoke the implementation from the shell, so it runs a
# lisp file as a script and exits.
# - faslExt :: string
# File extension of the implementations loadable (FASL) files.
# Implementations are free to generate native object files, but with the way
# buildLisp works it is required that we can also 'load' libraries, so
# (additionally) building a FASL or equivalent is required.
# - genLoadLisp :: [ dependency ] -> string
# Returns lisp code to 'load' the given dependencies. 'genLoadLispGeneric'
# should work for most dependencies.
# - genCompileLisp :: { name, srcs, deps } -> file
# Builds a lisp file which instructs the implementation to build a library
# from the given source files when executed. After running at least
# the file "$out/${name}.${impls.${implementation}.faslExt}" should have
# been created.
# - genDumpLisp :: { name, main, deps } -> file
# Builds a lisp file which instructs the implementation to build an
# executable which runs 'main' (and exits) where 'main' is available from
# 'deps'. The executable should be created as "$out/bin/${name}", usually
# by dumping the lisp image with the replaced toplevel function replaced.
# - wrapProgram :: boolean
# Whether to wrap the resulting binary / image with a wrapper script setting
# `LD_LIBRARY_PATH`.
# - genTestLisp :: { name, srcs, deps, expression } -> file
# Builds a lisp file which loads the given 'deps' and 'srcs' files and
# then evaluates 'expression'. Depending on whether 'expression' returns
# true or false, the script must exit with a zero or non-zero exit code.
# 'genTestLispGeneric' will work for most implementations.
# - lispWith :: [ dependency ] -> drv
# Builds a script (or dumped image) which when executed loads (or has
# loaded) all given dependencies. When built this should create an executable
# at "$out/bin/${implementation}".
#
# Optional members:
#
# - bundled :: string -> library
# Allows giving an implementation specific builder for a bundled library.
# This function is used as a replacement for the internal defaultBundled
# function and only needs to support one implementation. The returned derivation
# must behave like one built by 'library' (in particular have the same files
# available in "$out" and the same 'passthru' attributes), but may be built
# completely differently.
impls = lib.mapAttrs (name: v: { inherit name; } // v) {
sbcl = {
runScript = "${sbcl}/bin/sbcl --script";
faslExt = "fasl";
# 'genLoadLisp' generates Lisp code that instructs SBCL to load all
# the provided Lisp libraries.
genLoadLisp = genLoadLispGeneric impls.sbcl;
# 'genCompileLisp' generates a Lisp file that instructs SBCL to
# compile the provided list of Lisp source files to "$out/${name}.fasl".
genCompileLisp = { name, srcs, deps }: writeText "sbcl-compile.lisp" ''
;; This file compiles the specified sources into the Nix build
;; directory, creating one FASL file for each source.
(require 'sb-posix)
${impls.sbcl.genLoadLisp deps}
(defun nix-compile-lisp (srcfile)
(let ((outfile (make-pathname :type "fasl"
:directory (or (sb-posix:getenv "NIX_BUILD_TOP")
(error "not running in a Nix build"))
:name (substitute #\- #\/ srcfile))))
(multiple-value-bind (out-truename _warnings-p failure-p)
(compile-file srcfile :output-file outfile)
(if failure-p (sb-posix:exit 1)
(progn
;; For the case of multiple files belonging to the same
;; library being compiled, load them in order:
(load out-truename)
;; Return pathname as a string for cat-ting it later
(namestring out-truename))))))
(let ((*compile-verbose* t)
(catted-fasl (make-pathname :type "fasl"
:directory (or (sb-posix:getenv "out")
(error "not running in a Nix build"))
:name "${name}")))
(with-open-file (file catted-fasl
:direction :output
:if-does-not-exist :create)
;; SBCL's FASL files can just be bundled together using cat
(sb-ext:run-program "cat"
(mapcar #'nix-compile-lisp
;; These forms were inserted by the Nix build:
'(${
lib.concatMapStringsSep "\n" (src: "\"${src}\"") srcs
}))
:output file :search t)))
'';
# 'genDumpLisp' generates a Lisp file that instructs SBCL to dump
# the currently loaded image as an executable to $out/bin/$name.
#
# TODO(tazjin): Compression is currently unsupported because the
# SBCL in nixpkgs is, by default, not compiled with zlib support.
genDumpLisp = { name, main, deps }: writeText "sbcl-dump.lisp" ''
(require 'sb-posix)
${impls.sbcl.genLoadLisp deps}
(let* ((bindir (concatenate 'string (sb-posix:getenv "out") "/bin"))
(outpath (make-pathname :name "${name}"
:directory bindir)))
;; Tell UIOP that argv[0] will refer to running image, not the lisp impl
(when (find-package :uiop)
(eval `(setq ,(find-symbol "*IMAGE-DUMPED-P*" :uiop) :executable)))
(save-lisp-and-die outpath
:executable t
:toplevel
(lambda ()
;; Filter out everything prior to the `--` we
;; insert in the wrapper to prevent SBCL from
;; parsing arguments at startup
(setf sb-ext:*posix-argv*
(delete "--" sb-ext:*posix-argv*
:test #'string= :count 1))
(${main}))
:purify t))
'';
wrapProgram = true;
genTestLisp = genTestLispGeneric impls.sbcl;
lispWith = deps:
let lispDeps = filter (d: !d.lispBinary) (allDeps impls.sbcl deps);
in writeShellScriptBin "sbcl" ''
export LD_LIBRARY_PATH="${lib.makeLibraryPath (allNative [] lispDeps)}"
export LANG="C.UTF-8"
exec ${sbcl}/bin/sbcl ${
lib.optionalString (deps != [])
"--load ${writeText "load.lisp" (impls.sbcl.genLoadLisp lispDeps)}"
} $@
'';
};
ecl = {
runScript = "${ecl-static}/bin/ecl --load ${disableDebugger} --shell";
faslExt = "fasc";
genLoadLisp = genLoadLispGeneric impls.ecl;
genCompileLisp = { name, srcs, deps }: writeText "ecl-compile.lisp" ''
;; This seems to be required to bring make the 'c' package available
;; early, otherwise ECL tends to fail with a read failure
(ext:install-c-compiler)
;; Load dependencies
${impls.ecl.genLoadLisp deps}
(defun getenv-or-fail (var)
(or (ext:getenv var)
(error (format nil "Missing expected environment variable ~A" var))))
(defun nix-compile-file (srcfile &key native)
"Compile the given srcfile into a compilation unit in :out-dir using
a unique name based on srcfile as the filename which is returned after
compilation. If :native is true, create an native object file,
otherwise a byte-compile fasc file is built and immediately loaded."
(let* ((unique-name (substitute #\_ #\/ srcfile))
(out-file (make-pathname :type (if native "o" "fasc")
:directory (getenv-or-fail "NIX_BUILD_TOP")
:name unique-name)))
(multiple-value-bind (out-truename _warnings-p failure-p)
(compile-file srcfile :system-p native
:load (not native)
:output-file out-file
:verbose t :print t)
(if failure-p (ext:quit 1) out-truename))))
(let* ((out-dir (getenv-or-fail "out"))
(nix-build-dir (getenv-or-fail "NIX_BUILD_TOP"))
(srcs
;; These forms are inserted by the Nix build
'(${lib.concatMapStringsSep "\n" (src: "\"${src}\"") srcs})))
;; First, we'll byte compile loadable FASL files and load them
;; immediately. Since we are using a statically linked ECL, there's
;; no way to load native objects, so we rely on byte compilation
;; for all our loading which is crucial in compilation of course.
(ext:install-bytecodes-compiler)
;; ECL's bytecode FASLs can just be concatenated to create a bundle
;; at least since a recent bugfix which we apply as a patch.
;; See also: https://gitlab.com/embeddable-common-lisp/ecl/-/issues/649
(let ((bundle-out (make-pathname :type "fasc" :name "${name}"
:directory out-dir)))
(with-open-file (fasc-stream bundle-out :direction :output)
(ext:run-program "cat"
(mapcar (lambda (f)
(namestring
(nix-compile-file f :native nil)))
srcs)
:output fasc-stream)))
(ext:install-c-compiler)
;; Build a (natively compiled) static archive (.a) file. We want to
;; use this for (statically) linking an executable later. The bytecode
;; dance is only required because we can't load such archives.
(c:build-static-library
(make-pathname :type "a" :name "${name}" :directory out-dir)
:lisp-files (mapcar (lambda (x)
(nix-compile-file x :native t))
srcs)))
'';
genDumpLisp = { name, main, deps }: writeText "ecl-dump.lisp" ''
(defun getenv-or-fail (var)
(or (ext:getenv var)
(error (format nil "Missing expected environment variable ~A" var))))
${impls.ecl.genLoadLisp deps}
;; makes a 'c' package available that can link executables
(ext:install-c-compiler)
(c:build-program
(merge-pathnames (make-pathname :directory '(:relative "bin")
:name "${name}")
(truename (getenv-or-fail "out")))
:epilogue-code `(progn
;; UIOP doesn't understand ECL, so we need to make it
;; aware that we are a proper executable, causing it
;; to handle argument parsing and such properly. Since
;; this needs to work even when we're not using UIOP,
;; we need to do some compile-time acrobatics.
,(when (find-package :uiop)
`(setf ,(find-symbol "*IMAGE-DUMPED-P*" :uiop) :executable))
;; Run the actual application
(${main})
;; and exit.
(ext:quit))
;; ECL can't remember these from its own build
:ld-flags '("-static")
:lisp-files
;; The following forms are inserted by the Nix build
'(${
lib.concatMapStrings (dep: ''
"${dep}/${dep.lispName}.a"
'') (allDeps impls.ecl deps)
}))
'';
wrapProgram = false;
genTestLisp = genTestLispGeneric impls.ecl;
lispWith = deps:
let lispDeps = filter (d: !d.lispBinary) (allDeps impls.ecl deps);
in writeShellScriptBin "ecl" ''
exec ${ecl-static}/bin/ecl ${
lib.optionalString (deps != [])
"--load ${writeText "load.lisp" (impls.ecl.genLoadLisp lispDeps)}"
} $@
'';
bundled = name: runCommand "${name}-cllib"
{
passthru = {
lispName = name;
lispNativeDeps = [ ];
lispDeps = [ ];
lispBinary = false;
repl = impls.ecl.lispWith [ (impls.ecl.bundled name) ];
};
} ''
mkdir -p "$out"
ln -s "${ecl-static}/lib/ecl-${ecl-static.version}/${name}.${impls.ecl.faslExt}" -t "$out"
ln -s "${ecl-static}/lib/ecl-${ecl-static.version}/lib${name}.a" "$out/${name}.a"
'';
};
ccl = {
# Relatively bespoke wrapper script necessary to make CCL just™ execute
# a lisp file as a script.
runScript = pkgs.writers.writeBash "ccl" ''
# don't print intro message etc.
args=("--quiet")
# makes CCL crash on error instead of entering the debugger
args+=("--load" "${disableDebugger}")
# load files from command line in order
for f in "$@"; do
args+=("--load" "$f")
done
# Exit if everything was processed successfully
args+=("--eval" "(quit)")
exec ${ccl}/bin/ccl ''${args[@]}
'';
# See https://ccl.clozure.com/docs/ccl.html#building-definitions
faslExt =
if targetPlatform.isPower && targetPlatform.is32bit then "pfsl"
else if targetPlatform.isPower && targetPlatform.is64bit then "p64fsl"
else if targetPlatform.isx86_64 && targetPlatform.isLinux then "lx64fsl"
else if targetPlatform.isx86_32 && targetPlatform.isLinux then "lx32fsl"
else if targetPlatform.isAarch32 && targetPlatform.isLinux then "lafsl"
else if targetPlatform.isx86_32 && targetPlatform.isDarwin then "dx32fsl"
else if targetPlatform.isx86_64 && targetPlatform.isDarwin then "dx64fsl"
else if targetPlatform.isx86_64 && targetPlatform.isDarwin then "dx64fsl"
else if targetPlatform.isx86_32 && targetPlatform.isFreeBSD then "fx32fsl"
else if targetPlatform.isx86_64 && targetPlatform.isFreeBSD then "fx64fsl"
else if targetPlatform.isx86_32 && targetPlatform.isWindows then "wx32fsl"
else if targetPlatform.isx86_64 && targetPlatform.isWindows then "wx64fsl"
else builtins.throw "Don't know what FASLs are called for this platform: "
+ pkgs.stdenv.targetPlatform.system;
genLoadLisp = genLoadLispGeneric impls.ccl;
genCompileLisp = { name, srcs, deps }: writeText "ccl-compile.lisp" ''
${impls.ccl.genLoadLisp deps}
(defun getenv-or-fail (var)
(or (getenv var)
(error (format nil "Missing expected environment variable ~A" var))))
(defun nix-compile-file (srcfile)
"Trivial wrapper around COMPILE-FILE which causes CCL to exit if
compilation fails and LOADs the compiled file on success."
(let ((output (make-pathname :name (substitute #\_ #\/ srcfile)
:type "${impls.ccl.faslExt}"
:directory (getenv-or-fail "NIX_BUILD_TOP"))))
(multiple-value-bind (out-truename _warnings-p failure-p)
(compile-file srcfile :output-file output :print t :verbose t)
(declare (ignore _warnings-p))
(if failure-p (quit 1)
(progn (load out-truename) out-truename)))))
(fasl-concatenate (make-pathname :name "${name}" :type "${impls.ccl.faslExt}"
:directory (getenv-or-fail "out"))
(mapcar #'nix-compile-file
;; These forms where inserted by the Nix build
'(${
lib.concatMapStrings (src: ''
"${src}"
'') srcs
})))
'';
genDumpLisp = { name, main, deps }: writeText "ccl-dump.lisp" ''
${impls.ccl.genLoadLisp deps}
(let* ((out (or (getenv "out") (error "Not running in a Nix build")))
(bindir (concatenate 'string out "/bin/"))
(executable (make-pathname :directory bindir :name "${name}")))
;; Tell UIOP that argv[0] will refer to running image, not the lisp impl
(when (find-package :uiop)
(eval `(setf ,(find-symbol "*IMAGE-DUMPED-P*" :uiop) :executable)))
(save-application executable
:purify t
:error-handler :quit
:toplevel-function
(lambda ()
;; Filter out everything prior to the `--` we
;; insert in the wrapper to prevent SBCL from
;; parsing arguments at startup
(setf ccl:*command-line-argument-list*
(delete "--" ccl:*command-line-argument-list*
:test #'string= :count 1))
(${main}))
:mode #o755
;; TODO(sterni): use :native t on macOS
:prepend-kernel t))
'';
wrapProgram = true;
genTestLisp = genTestLispGeneric impls.ccl;
lispWith = deps:
let lispDeps = filter (d: !d.lispBinary) (allDeps impls.ccl deps);
in writeShellScriptBin "ccl" ''
export LD_LIBRARY_PATH="${lib.makeLibraryPath (allNative [] lispDeps)}"
exec ${ccl}/bin/ccl ${
lib.optionalString (deps != [])
"--load ${writeText "load.lisp" (impls.ccl.genLoadLisp lispDeps)}"
} "$@"
'';
};
};
#
# Public API functions
#
# 'library' builds a list of Common Lisp files into an implementation
# specific library format, usually a single FASL file, which can then be
# loaded and built into an executable via 'program'.
library =
{ name
, implementation ? defaultImplementation
, brokenOn ? [ ] # TODO(sterni): make this a warning
, srcs
, deps ? [ ]
, native ? [ ]
, tests ? null
, passthru ? { }
}:
let
filteredDeps = implFilter implementation deps;
filteredSrcs = implFilter implementation srcs;
lispNativeDeps = (allNative native filteredDeps);
lispDeps = allDeps implementation filteredDeps;
testDrv =
if ! isNull tests
then
testSuite
{
name = tests.name or "${name}-test";
srcs = filteredSrcs ++ (tests.srcs or [ ]);
deps = filteredDeps ++ (tests.deps or [ ]);
expression = tests.expression;
inherit implementation;
}
else null;
in
lib.fix (self: runCommand "${name}-cllib"
{
LD_LIBRARY_PATH = lib.makeLibraryPath lispNativeDeps;
LANG = "C.UTF-8";
passthru = passthru // {
inherit lispNativeDeps lispDeps;
lispName = name;
lispBinary = false;
tests = testDrv;
};
} ''
${if ! isNull testDrv
then "echo 'Test ${testDrv} succeeded'"
else "echo 'No tests run'"}
mkdir $out
${implementation.runScript} ${
implementation.genCompileLisp {
srcs = filteredSrcs;
inherit name;
deps = lispDeps;
}
}
'');
# 'program' creates an executable, usually containing a dumped image of the
# specified sources and dependencies.
program =
{ name
, implementation ? defaultImplementation
, brokenOn ? [ ] # TODO(sterni): make this a warning
, main ? "${name}:main"
, srcs
, deps ? [ ]
, native ? [ ]
, tests ? null
, passthru ? { }
}:
let
filteredSrcs = implFilter implementation srcs;
filteredDeps = implFilter implementation deps;
lispDeps = allDeps implementation filteredDeps;
libPath = lib.makeLibraryPath (allNative native lispDeps);
# overriding is used internally to propagate the implementation to use
selfLib = (makeOverridable library) {
inherit name native brokenOn;
deps = lispDeps;
srcs = filteredSrcs;
};
testDrv =
if ! isNull tests
then
testSuite
{
name = tests.name or "${name}-test";
srcs =
(
# testSuite does run implFilter as well
filteredSrcs ++ (tests.srcs or [ ])
);
deps = filteredDeps ++ (tests.deps or [ ]);
expression = tests.expression;
inherit implementation;
}
else null;
in
lib.fix (self: runCommand "${name}"
{
nativeBuildInputs = [ makeWrapper ];
LD_LIBRARY_PATH = libPath;
LANG = "C.UTF-8";
passthru = passthru // {
lispName = name;
lispDeps = [ selfLib ];
lispNativeDeps = native;
lispBinary = true;
tests = testDrv;
};
}
(''
${if ! isNull testDrv
then "echo 'Test ${testDrv} succeeded'"
else ""}
mkdir -p $out/bin
${implementation.runScript} ${
implementation.genDumpLisp {
inherit name main;
deps = ([ selfLib ] ++ lispDeps);
}
}
'' + lib.optionalString implementation.wrapProgram ''
wrapProgram $out/bin/${name} \
--prefix LD_LIBRARY_PATH : "${libPath}" \
--add-flags "\$NIX_BUILDLISP_LISP_ARGS --"
''));
# 'bundled' creates a "library" which makes a built-in package available,
# such as any of SBCL's sb-* packages or ASDF. By default this is done
# by calling 'require', but implementations are free to provide their
# own specific bundled function.
bundled = name:
let
# TODO(sterni): allow overriding args to underlying 'library' (e. g. srcs)
defaultBundled = implementation: name: library {
inherit name implementation;
srcs = lib.singleton (builtins.toFile "${name}.lisp" "(require '${name})");
};
bundled' =
{ implementation ? defaultImplementation
, name
}:
implementation.bundled or (defaultBundled implementation) name;
in
(makeOverridable bundled') {
inherit name;
};
in
{
library = withExtras library;
program = withExtras program;
inherit bundled;
# 'sbclWith' creates an image with the specified libraries /
# programs loaded in SBCL.
sbclWith = impls.sbcl.lispWith;
inherit (impls)
sbcl
ecl
ccl
;
}

View file

@ -1,33 +0,0 @@
{ depot, ... }:
let
inherit (depot.nix) buildLisp;
# Example Lisp library.
#
# Currently the `name` attribute is only used for the derivation
# itself, it has no practical implications.
libExample = buildLisp.library {
name = "lib-example";
srcs = [
./lib.lisp
];
};
# Example Lisp program.
#
# This builds & writes an executable for a program using the library
# above to disk.
#
# By default, buildLisp.program expects the entry point to be
# `$name:main`. This can be overridden by configuring the `main`
# attribute.
in
buildLisp.program {
name = "example";
deps = [ libExample ];
srcs = [
./main.lisp
];
}

View file

@ -1,6 +0,0 @@
(defpackage lib-example
(:use :cl)
(:export :who))
(in-package :lib-example)
(defun who () "edef")

View file

@ -1,7 +0,0 @@
(defpackage example
(:use :cl :lib-example)
(:export :main))
(in-package :example)
(defun main ()
(format t "i <3 ~A~%" (who)))

View file

@ -1,58 +0,0 @@
{ depot, pkgs, lib, ... }:
let
# Trivial test program that outputs argv[0] and exits
prog =
depot.nix.buildLisp.program {
name = "argv0-test";
srcs = [
(pkgs.writeText "argv0-test.lisp" ''
(defpackage :argv0-test (:use :common-lisp :uiop) (:export :main))
(in-package :argv0-test)
(defun main ()
(format t "~A~%" (uiop:argv0)))
'')
];
deps = [
{
sbcl = depot.nix.buildLisp.bundled "uiop";
default = depot.nix.buildLisp.bundled "asdf";
}
];
};
# Extract verify argv[0] output for given buildLisp program
checkImplementation = prog:
pkgs.runCommand "check-argv0" { } ''
set -eux
checkInvocation() {
invocation="$1"
test "$invocation" = "$("$invocation")"
}
checkInvocation "${prog}/bin/argv0-test"
cd ${prog}
checkInvocation "./bin/argv0-test"
cd bin
checkInvocation ./argv0-test
set +x
touch "$out"
'';
inherit (prog.meta.ci) targets;
in
(checkImplementation prog).overrideAttrs (_: {
# Wire up a subtarget all (active) non-default implementations
passthru = lib.genAttrs targets (name: checkImplementation prog.${name});
meta.ci = { inherit targets; };
})

View file

@ -1,12 +0,0 @@
{ depot, lib, ... }:
(
depot.nix.dependency-analyzer.knownDependencyGraph
"depot"
depot.ci.targets
).overrideAttrs (old: {
# Causes an infinite recursion via ci.targets otherwise
meta = lib.recursiveUpdate (old.meta or { }) {
ci.skip = true;
};
})

View file

@ -1,5 +0,0 @@
{ depot, lib, ... }:
depot.nix.dependency-analyzer.knownDependencyGraph "3p-lisp" (
builtins.filter lib.isDerivation (builtins.attrValues depot.third_party.lisp)
)

View file

@ -1,36 +0,0 @@
{ depot, lib, ... }:
let
inherit (depot.nix.runTestsuite)
runTestsuite
assertEq
it
;
inherit (depot.nix.dependency-analyzer)
plainDrvDepMap
drvsToPaths
;
knownDrvs = drvsToPaths (
builtins.filter lib.isDerivation (builtins.attrValues depot.third_party.lisp)
);
exampleMap = plainDrvDepMap knownDrvs;
# These will be needed to index into the attribute set which can't have context
# in the attribute names.
knownDrvsNoContext = builtins.map builtins.unsafeDiscardStringContext knownDrvs;
in
runTestsuite "dependency-analyzer" [
(it "checks plainDrvDepMap properties" [
(assertEq "all known drvs are marked known"
(builtins.all (drv: exampleMap.${drv}.known) knownDrvsNoContext)
true)
(assertEq "no unknown drv is marked known"
(builtins.all (entry: !entry.known) (
builtins.attrValues (builtins.removeAttrs exampleMap knownDrvsNoContext)
))
true)
])
]

View file

@ -1,47 +0,0 @@
{ depot, lib, pkgs, ... }:
let
inherit (depot.nix.yants)
defun
list
drv
;
/* Realize drvDeps, then return drvOut if that succeds.
* This can be used to make drvOut depend on the
* build success of all drvDeps without making each drvDep
* a dependency of drvOut.
* => drvOut is not rebuilt if drvDep changes
*/
drvSeqL = defun [ (list drv) drv drv ]
(drvDeps: drvOut:
let
drvOutOutputs = drvOut.outputs or [ "out" ];
in
pkgs.runCommandLocal drvOut.name
{
# we inherit all attributes in order to replicate
# the original derivation as much as possible
outputs = drvOutOutputs;
passthru = drvOut.drvAttrs;
# depend on drvDeps (by putting it in builder context)
inherit drvDeps;
}
# the outputs of the original derivation are replicated
# by creating a symlink to the old output path
(lib.concatMapStrings
(output: ''
target=${lib.escapeShellArg drvOut.${output}}
# if the target is already a symlink, follow it until its not;
# this is done to prevent too many dereferences
target=$(readlink -e "$target")
# link to the output
ln -s "$target" "${"$"}${output}"
'')
drvOutOutputs));
in
{
__functor = _: drvSeqL;
}

View file

@ -1 +0,0 @@
Profpatsch

View file

@ -1,22 +0,0 @@
{ depot, pkgs, localSystem, ... }:
let
emptyDerivation = import ./emptyDerivation.nix {
inherit pkgs;
inherit (pkgs) stdenv;
inherit (depot.nix) getBins;
system = localSystem;
};
tests = import ./tests.nix {
inherit emptyDerivation;
inherit pkgs;
inherit (depot.nix) writeExecline getBins;
inherit (depot.nix.runTestsuite) runTestsuite it assertEq;
};
in
{
__functor = _: emptyDerivation;
inherit tests;
}

View file

@ -1,36 +0,0 @@
{ stdenv, system, pkgs, getBins }:
# The empty derivation. All it does is touch $out.
# Basically the unit value for derivations.
#
# In addition to simple test situations which require
# a derivation, we set __functor, so you can call it
# as a function and pass an attrset. The set you pass
# is `//`-merged with the attrset before calling derivation,
# so you can use this to add more fields.
let
bins = getBins pkgs.s6-portable-utils [ "s6-touch" ]
// getBins pkgs.execline [ "importas" "exec" ];
emptiness = {
name = "empty-derivation";
inherit system;
builder = bins.exec;
args = [
bins.importas
"out"
"out"
bins.s6-touch
"$out"
];
};
in
(derivation emptiness) // {
# This allows us to call the empty derivation
# like a function and override fields/add new fields.
__functor = _: overrides:
derivation (emptiness // overrides);
}

View file

@ -1,40 +0,0 @@
{ emptyDerivation, getBins, pkgs, writeExecline, runTestsuite, it, assertEq }:
let
bins = getBins pkgs.s6-portable-utils [ "s6-echo" ];
empty = it "is just an empty path" [
(assertEq "path empty"
(builtins.readFile emptyDerivation)
"")
];
fooOut = emptyDerivation {
builder = writeExecline "foo-builder" { } [
"importas"
"out"
"out"
"redirfd"
"-w"
"1"
"$out"
bins.s6-echo
"-n"
"foo"
];
};
overrideBuilder = it "can override the builder" [
(assertEq "output is foo"
(builtins.readFile fooOut)
"foo")
(assertEq "can add new drv variables"
(emptyDerivation { foo = "bar"; }).foo
"bar")
];
in
runTestsuite "emptyDerivation" [
empty
overrideBuilder
]

View file

@ -1,33 +0,0 @@
{ depot, lib, ... }:
# Convert an attrset of strings to a list of key/value netstring pairs.
# A good minimally viable json replacement if all you need is to iterate.
# You can use e.g. `forstdin -Ed '' item` in execline to split the items
# and then get the key and value via `multidefine -d '' $item { key value }`
#
# Example:
# { foo = "bar"; x = "abc"; }
# => "12:3:foo,3:bar,,10:1:x,3:abc,,"
#
# Example with runExecline:
# nix.runExecline "test" {
# stdin = nix.netstring.attrsToKeyValList {
# foo = "bar";
# x = "abc";
# };
# } [
# "forstdin" "-Ed" "" "item"
# "multidefine" "-d" "" "$item" [ "key" "value" ]
# "${pkgs.coreutils}/bin/echo" "\${key} -> \${value}"
# ]
# will print:
# foo -> bar
# x -> abc
attrs:
lib.concatStrings
(lib.mapAttrsToList
(k: v: depot.nix.netstring.fromString
(depot.nix.netstring.fromString k
+ depot.nix.netstring.fromString v))
attrs)

View file

@ -1,10 +0,0 @@
{ ... }:
# convert any nix string into a netstring
# (prefixed by its length) according to https://en.wikipedia.org/wiki/Netstring
#
# Examples:
# netstring.fromString "foo"
# => "3:foo,"
# netstring.fromString ""
# => "0:,"
s: "${toString (builtins.stringLength s)}:${s},"

View file

@ -1 +1 @@
sterni
raitobezarius

View file

@ -1,648 +0,0 @@
> [!TIP]
> Are you interested in hacking on Nix projects for a week, together
> with other Nix users? Do you have time at the end of August? Great,
> come join us at [Volga Sprint](https://volgasprint.org/)!
Nix - A One Pager
=================
[Nix](https://nixos.org/nix/), the package manager, is built on and with Nix,
the language. This page serves as a fast intro to most of the (small) language.
Unless otherwise specified, the word "Nix" refers only to the language below.
Please file an issue if something in here confuses you or you think something
important is missing.
If you have Nix installed, you can try the examples below by running `nix repl`
and entering code snippets there.
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->
**Table of Contents**
- [Overview](#overview)
- [Language constructs](#language-constructs)
- [Primitives / literals](#primitives--literals)
- [Operators](#operators)
- [`//` (merge) operator](#-merge-operator)
- [Variable bindings](#variable-bindings)
- [Functions](#functions)
- [Multiple arguments (currying)](#multiple-arguments-currying)
- [Multiple arguments (attribute sets)](#multiple-arguments-attribute-sets)
- [`if ... then ... else ...`](#if--then--else-)
- [`inherit` keyword](#inherit-keyword)
- [`with` statements](#with-statements)
- [`import` / `NIX_PATH` / `<entry>`](#import--nix_path--entry)
- [`or` expressions](#or-expressions)
- [Standard libraries](#standard-libraries)
- [`builtins`](#builtins)
- [`pkgs.lib`](#pkgslib)
- [`pkgs` itself](#pkgs-itself)
- [Derivations](#derivations)
- [Nix Idioms](#nix-idioms)
- [File lambdas](#file-lambdas)
- [`callPackage`](#callpackage)
- [Overrides / Overlays](#overrides--overlays)
<!-- markdown-toc end -->
# Overview
Nix is:
* **purely functional**. It has no concept of sequential steps being executed,
any dependency between operations is established by depending on *data* from
previous operations.
Any valid piece of Nix code is an *expression* that returns a value.
Evaluating a Nix expression *yields a single data structure*, it does not
execute a sequence of operations.
Every Nix file evaluates to a *single expression*.
* **lazy**. It will only evaluate expressions when their result is actually
requested.
For example, the builtin function `throw` causes evaluation to stop.
Entering the following expression works fine however, because we never
actually ask for the part of the structure that causes the `throw`.
```nix
let attrs = { a = 15; b = builtins.throw "Oh no!"; };
in "The value of 'a' is ${toString attrs.a}"
```
* **purpose-built**. Nix only exists to be the language for Nix, the package
manager. While people have occasionally used it for other use-cases, it is
explicitly not a general-purpose language.
# Language constructs
This section describes the language constructs in Nix. It is a small language
and most of these should be self-explanatory.
## Primitives / literals
Nix has a handful of data types which can be represented literally in source
code, similar to many other languages.
```nix
# numbers
42
1.72394
# strings & paths
"hello"
./some-file.json
# strings support interpolation
"Hello ${name}"
# multi-line strings (common prefix whitespace is dropped)
''
first line
second line
''
# lists (note: no commas!)
[ 1 2 3 ]
# attribute sets (field access with dot syntax)
{ a = 15; b = "something else"; }
# recursive attribute sets (fields can reference each other)
rec { a = 15; b = a * 2; }
```
## Operators
Nix has several operators, most of which are unsurprising:
| Syntax | Description |
|---------------------------|-----------------------------------------------------------------------------|
| `+`, `-`, `*`, `/` | Numerical operations |
| `+` | String concatenation |
| `++` | List concatenation |
| `==` | Equality |
| `>`, `>=`, `<`, `<=` | Ordering comparators |
| `&&` | Logical `AND` |
| <code>&vert;&vert;</code> | Logical `OR` |
| `e1 -> e2` | Logical implication (i.e. <code>!e1 &vert;&vert; e2</code>) |
| `!` | Boolean negation |
| `set.attr` | Access attribute `attr` in attribute set `set` |
| `set ? attribute` | Test whether attribute set contains an attribute |
| `left // right` | Merge `left` & `right` attribute sets, with the right set taking precedence |
### `//` (merge) operator
The `//`-operator is used pervasively in Nix code. You should familiarise
yourself with it, as it is likely also the least familiar one.
It merges the left and right attribute sets given to it:
```nix
{ a = 1; } // { b = 2; }
# yields { a = 1; b = 2; }
```
Values from the right side take precedence:
```nix
{ a = "left"; } // { a = "right"; }
# yields { a = "right"; }
```
The merge operator does *not* recursively merge attribute sets;
```nix
{ a = { b = 1; }; } // { a = { c = 2; }; }
# yields { a = { c = 2; }; }
```
Helper functions for recursive merging exist in the [`lib` library](#pkgslib).
## Variable bindings
Bindings in Nix are introduced locally via `let` expressions, which make some
variables available within a given scope.
For example:
```nix
let
a = 15;
b = 2;
in a * b
# yields 30
```
Variables are immutable. This means that after defining what `a` or `b` are, you
can not *modify* their value in the scope in which they are available.
You can nest `let`-expressions to shadow variables.
Variables are *not* available outside of the scope of the `let` expression.
There are no global variables.
## Functions
All functions in Nix are anonymous lambdas. This means that they are treated
just like data. Giving them names is accomplished by assigning them to
variables, or setting them as values in an attribute set (more on that below).
```
# simple function
# declaration is simply the argument followed by a colon
name: "Hello ${name}"
```
### Multiple arguments (currying)
Technically any Nix function can only accept **one argument**. Sometimes
however, a function needs multiple arguments. This is achieved in Nix via
[currying][], which means to create a function with one argument, that returns a
function with another argument, that returns ... and so on.
For example:
```nix
name: age: "${name} is ${toString age} years old"
```
An additional benefit of this approach is that you can pass one parameter to a
curried function, and receive back a function that you can re-use (similar to
partial application):
```nix
let
multiply = a: b: a * b;
doubleIt = multiply 2; # at this point we have passed in the value for 'a' and
# receive back another function that still expects 'b'
in
doubleIt 15
# yields 30
```
### Multiple arguments (attribute sets)
Another way of specifying multiple arguments to a function in Nix is to make it
accept an attribute set, which enables multiple other features:
```nix
{ name, age }: "${name} is ${toString age} years old"
```
Using this method, we gain the ability to specify default arguments (so that
callers can omit them):
```nix
{ name, age ? 42 }: "${name} is ${toString age} years old"
```
Or in practice:
```nix
let greeter = { name, age ? 42 }: "${name} is ${toString age} years old";
in greeter { name = "Slartibartfast"; }
# yields "Slartibartfast is 42 years old"
# (note: Slartibartfast is actually /significantly/ older)
```
Additionally we can introduce an ellipsis using `...`, meaning that we can
accept an attribute set as our input that contains more variables than are
needed for the function.
```nix
let greeter = { name, age, ... }: "${name} is ${toString age} years old";
person = {
name = "Slartibartfast";
age = 42;
# the 'email' attribute is not expected by the 'greeter' function ...
email = "slartibartfast@magrath.ea";
};
in greeter person # ... but the call works due to the ellipsis.
```
Nix also supports binding the whole set of passed in attributes to a
parameter using the `@` syntax:
```nix
let func = { name, age, ... }@args: builtins.attrNames args;
in func {
name = "Slartibartfast";
age = 42;
email = "slartibartfast@magrath.ea";
}
# yields: [ "age" "email" "name" ]
```
**Warning:** Combining the `@` syntax with default arguments can lead
to surprising behaviour, as the passed attributes are bound verbatim.
This means that defaulted arguments are not included in the bound
attribute set:
```nix
({ a ? 1, b }@args: args.a) { b = 1; }
# throws: error: attribute 'a' missing
({ a ? 1, b }@args: args.a) { b = 1; a = 2; }
# => 2
```
## `if ... then ... else ...`
Nix has simple conditional support. Note that `if` is an **expression** in Nix,
which means that both branches must be specified.
```nix
if someCondition
then "it was true"
else "it was false"
```
## `inherit` keyword
The `inherit` keyword is used in attribute sets or `let` bindings to "inherit"
variables from the parent scope.
In short, a statement like `inherit foo;` expands to `foo = foo;`.
Consider this example:
```nix
let
name = "Slartibartfast";
# ... other variables
in {
name = name; # set the attribute set key 'name' to the value of the 'name' var
# ... other attributes
}
```
The `name = name;` line can be replaced with `inherit name;`:
```nix
let
name = "Slartibartfast";
# ... other variables
in {
inherit name;
# ... other attributes
}
```
This is often convenient, especially because inherit supports multiple variables
at the same time as well as "inheritance" from other attribute sets:
```nix
{
inherit name age; # equivalent to `name = name; age = age;`
inherit (otherAttrs) email; # equivalent to `email = otherAttrs.email`;
}
```
## `with` statements
The `with` statement "imports" all attributes from an attribute set into
variables of the same name:
```nix
let attrs = { a = 15; b = 2; };
in with attrs; a + b # 'a' and 'b' become variables in the scope following 'with'
```
The scope of a `with`-"block" is the expression immediately following the
semicolon, i.e.:
```nix
let attrs = { /* some attributes */ };
in with attrs; (/* this is the scope of the `with` */)
```
## `import` / `NIX_PATH` / `<entry>`
Nix files can import each other by using the builtin `import` function and a
literal path:
```nix
# assuming there is a file lib.nix with some useful functions
let myLib = import ./lib.nix;
in myLib.usefulFunction 42
```
The `import` function will read and evaluate the file, and return its Nix value.
Nix files often begin with a function header to pass parameters into the rest of
the file, so you will often see imports of the form `import ./some-file { ... }`.
Nix has a concept of a `NIX_PATH` (similar to the standard `PATH` environment
variable) which contains named aliases for file paths containing Nix
expressions.
In a standard Nix installation, several [channels][] will be present (for
example `nixpkgs` or `nixos-unstable`) on the `NIX_PATH`.
`NIX_PATH` entries can be accessed using the `<entry>` syntax, which simply
evaluates to their file path:
```nix
<nixpkgs>
# might yield something like `/home/tazjin/.nix-defexpr/channels/nixpkgs`
```
This is commonly used to import from channels:
```nix
let pkgs = import <nixpkgs> {};
in pkgs.something
```
## `or` expressions
Nix has a keyword called `or` which can be used to access a value from an
attribute set while providing a fallback to a default value.
The syntax is simple:
```nix
# Access an existing attribute
let set = { a = 42; };
in set.a or 23
```
Since the attribute `a` exists, this will return `42`.
```nix
# ... or fall back to a default if there is no such key
let set = { };
in set.a or 23
```
Since the attribute `a` does not exist, this will fall back to returning the
default value `23`.
Note that `or` expressions also work for nested attribute set access.
# Standard libraries
Yes, libraries, plural.
Nix has three major things that could be considered its standard library and
while there's a lot of debate to be had about this point, you still need to know
all three.
## `builtins`
Nix comes with several functions that are baked into the language. These work
regardless of which other Nix code you may or may not have imported.
Most of these functions are implemented in the Nix interpreter itself, which
means that they are rather fast when compared to some of the equivalents which
are implemented in Nix itself.
The Nix manual has [a section listing all `builtins`][builtins] and their usage.
Examples of builtins that you will commonly encounter include, but are not
limited to:
* `derivation` (see [Derivations](#derivations))
* `toJSON` / `fromJSON`
* `toString`
* `toPath` / `fromPath`
The builtins also include several functions that have the (spooky) ability to
break Nix' evaluation purity. No functions written in Nix itself can do this.
Examples of those include:
* `fetchGit` which can fetch a git-repository using the environment's default
git/ssh configuration
* `fetchTarball` which can fetch & extract archives without having to specify
hashes
Read through the manual linked above to get the full overview.
## `pkgs.lib`
The Nix package set, commonly referred to by Nixers simply as [nixpkgs][],
contains a child attribute set called `lib` which provides a large number of
useful functions.
The canonical definition of these functions is [their source code][lib-src]. I
wrote a tool ([nixdoc][]) in 2018 which generates manual entries for these
functions, however not all of the files are included as of July 2019.
See the [Nixpkgs manual entry on `lib`][lib-manual] for the documentation.
These functions include various utilities for dealing with the data types in Nix
(lists, attribute sets, strings etc.) and it is useful to at least skim through
them to familiarise yourself with what is available.
```nix
{ pkgs ? import <nixpkgs> {} }:
with pkgs.lib; # bring contents pkgs.lib into scope
strings.toUpper "hello"
# yields "HELLO"
```
## `pkgs` itself
The Nix package set itself does not just contain packages, but also many useful
functions which you might run into while creating new Nix packages.
One particular subset of these that stands out are the [trivial builders][],
which provide utilities for writing text files or shell scripts, running shell
commands and capturing their output and so on.
```nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.writeText "hello.txt" "Hello dear reader!"
# yields a derivation which creates a text file with the above content
```
# Derivations
When a Nix expression is evaluated it may yield one or more *derivations*.
Derivations describe a single build action that, when run, places one or more
outputs (whether they be files or folders) in the Nix store.
The builtin function `derivation` is responsible for creating derivations at a
lower level. Usually when Nix users create derivations they will use the
higher-level functions such as [stdenv.mkDerivation][smkd].
Please see the manual [on derivations][drv-manual] for more information, as the
general build logic is out of scope for this document.
# Nix Idioms
There are several idioms in Nix which are not technically part of the language
specification, but will commonly be encountered in the wild.
This section is an (incomplete) list of them.
## File lambdas
It is customary to start every file with a function header that receives the
files dependencies, instead of importing them directly in the file.
Sticking to this pattern lets users of your code easily change out, for example,
the specific version of `nixpkgs` that is used.
A common file header pattern would look like this:
```nix
{ pkgs ? import <nixpkgs> {} }:
# ... 'pkgs' is then used in the code
```
In some sense, you might consider the function header of a file to be its "API".
## `callPackage`
Building on the previous pattern, there is a custom in nixpkgs of specifying the
dependencies of your file explicitly instead of accepting the entire package
set.
For example, a file containing build instructions for a tool that needs the
standard build environment and `libsvg` might start like this:
```nix
# my-funky-program.nix
{ stdenv, libsvg }:
stdenv.mkDerivation { ... }
```
Any time a file follows this header pattern it is probably meant to be imported
using a special function called `callPackage` which is part of the top-level
package set (as well as certain subsets, such as `haskellPackages`).
```nix
{ pkgs ? import <nixpkgs> {} }:
let my-funky-program = pkgs.callPackage ./my-funky-program.nix {};
in # ... something happens with my-funky-program
```
The `callPackage` function looks at the expected arguments (via
`builtins.functionArgs`) and passes the appropriate keys from the set in which
it is defined as the values for each corresponding argument.
## Overrides / Overlays
One of the most powerful features of Nix is that the representation of all build
instructions as data means that they can easily be *overridden* to get a
different result.
For example, assuming there is a package `someProgram` which is built without
our favourite configuration flag (`--mimic-threaten-tag`) we might override it
like this:
```nix
someProgram.overrideAttrs(old: {
configureFlags = old.configureFlags or [] ++ ["--mimic-threaten-tag"];
})
```
This pattern has a variety of applications of varying complexity. The top-level
package set itself can have an `overlays` argument passed to it which may add
new packages to the imported set.
Note the use of the `or` operator to default to an empty list if the
original flags do not include `configureFlags`. This is required in
case a package does not set any flags by itself.
Since this can change in a package over time, it is useful to guard
against it using `or`.
For a slightly more advanced example, assume that we want to import `<nixpkgs>`
but have the modification above be reflected in the imported package set:
```nix
let
overlay = (final: prev: {
someProgram = prev.someProgram.overrideAttrs(old: {
configureFlags = old.configureFlags or [] ++ ["--mimic-threaten-tag"];
});
});
in import <nixpkgs> { overlays = [ overlay ]; }
```
The overlay function receives two arguments, `final` and `prev`. `final` is
the [fixed point][fp] of the overlay's evaluation, i.e. the package set
*including* the new packages and `prev` is the "original" package set.
See the Nix manual sections [on overrides][] and [on overlays][] for more
details (note: the convention has moved away from using `self` in favor of
`final`, and `prev` instead of `super`, but the documentation has not been
updated to reflect this).
[currying]: https://en.wikipedia.org/wiki/Currying
[builtins]: https://nixos.org/manual/nix/stable/language/builtins
[nixpkgs]: https://github.com/NixOS/nixpkgs
[lib-src]: https://github.com/NixOS/nixpkgs/tree/master/lib
[nixdoc]: https://github.com/tazjin/nixdoc
[lib-manual]: https://nixos.org/manual/nixpkgs/stable/#sec-functions-library
[channels]: https://nixos.org/manual/nix/stable/command-ref/files/channels
[trivial builders]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/trivial-builders/default.nix
[smkd]: https://nixos.org/manual/nixpkgs/stable/#chap-stdenv
[drv-manual]: https://nixos.org/manual/nix/stable/language/derivations
[fp]: https://github.com/NixOS/nixpkgs/blob/master/lib/fixed-points.nix
[on overrides]: https://nixos.org/manual/nixpkgs/stable/#chap-overrides
[on overlays]: https://nixos.org/manual/nixpkgs/stable/#chap-overlays

View file

@ -1,16 +0,0 @@
# The canonical source location of nix-1p is //nix/nix-1p in the TVL
# depot: https://code.tvl.fyi/about/nix/nix-1p
#
# This file configures TVL CI to mirror the subtree to GitHub.
{ depot ? { }, pkgs ? import <nixpkgs> { }, ... }:
(pkgs.runCommandLocal "nix-1p" { } ''
mkdir $out
cp ${./README.md} $out/README.md
'').overrideAttrs (_: {
meta.ci.extraSteps.github = depot.tools.releases.filteredGitPush {
filter = ":/nix/nix-1p";
remote = "git@github.com:tazjin/nix-1p.git";
ref = "refs/heads/master";
};
})

View file

@ -1 +1 @@
sterni
raitobezarius

View file

@ -1,31 +0,0 @@
# This file defines a Nix helper function to create Tailscale ACL files.
#
# https://tailscale.com/kb/1018/install-acls
{ depot, pkgs, ... }:
with depot.nix.yants;
let
inherit (builtins) toFile toJSON;
acl = struct "acl" {
Action = enum [ "accept" "reject" ];
Users = list string;
Ports = list string;
};
acls = list entry;
aclConfig = struct "aclConfig" {
# Static group mappings from group names to lists of users
Groups = option (attrs (list string));
# Hostname aliases to use in place of IPs
Hosts = option (attrs string);
# Actual ACL entries
ACLs = list acl;
};
in
config: pkgs.writeText "tailscale-acl.json" (toJSON (aclConfig config))

View file

@ -1 +1 @@
sterni
raitobezarius