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:
parent
bd91cac1f3
commit
df4500ea2b
2905 changed files with 34 additions and 493328 deletions
|
|
@ -1 +1 @@
|
|||
Profpatsch
|
||||
raitobezarius
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
;
|
||||
}
|
||||
|
|
@ -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
|
||||
];
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
(defpackage lib-example
|
||||
(:use :cl)
|
||||
(:export :who))
|
||||
(in-package :lib-example)
|
||||
|
||||
(defun who () "edef")
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
(defpackage example
|
||||
(:use :cl :lib-example)
|
||||
(:export :main))
|
||||
(in-package :example)
|
||||
|
||||
(defun main ()
|
||||
(format t "i <3 ~A~%" (who)))
|
||||
|
|
@ -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; };
|
||||
})
|
||||
|
|
@ -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;
|
||||
};
|
||||
})
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{ depot, lib, ... }:
|
||||
|
||||
depot.nix.dependency-analyzer.knownDependencyGraph "3p-lisp" (
|
||||
builtins.filter lib.isDerivation (builtins.attrValues depot.third_party.lisp)
|
||||
)
|
||||
|
|
@ -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)
|
||||
])
|
||||
]
|
||||
|
|
@ -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 it’s 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;
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
Profpatsch
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
]
|
||||
|
|
@ -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)
|
||||
|
|
@ -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},"
|
||||
|
|
@ -1 +1 @@
|
|||
sterni
|
||||
raitobezarius
|
||||
|
|
|
|||
|
|
@ -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>||</code> | Logical `OR` |
|
||||
| `e1 -> e2` | Logical implication (i.e. <code>!e1 || 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
|
||||
|
|
@ -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";
|
||||
};
|
||||
})
|
||||
|
|
@ -1 +1 @@
|
|||
sterni
|
||||
raitobezarius
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
@ -1 +1 @@
|
|||
sterni
|
||||
raitobezarius
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue