the same value as the hello
variable of Figure 4, except for the version
and source
fields. Under the hood, again, this expands to a single make-struct
call with struct-ref
calls for fields whose value is reused.
The inherit
feature supports a very useful idiom. It allows new package variants to be created programmatically, concisely, and in a purely functional way. It is notably used to bootstrap the software distribution, where bootstrap variants of packages such as GCC or the GNU libc are built with different inputs and configuration flags than the final versions. Users can similarly define customized variants of the packages found in the distribution. This feature also allows high-level transformations to be implemented as pure functions. For instance, the static-package
procedure takes a <package>
instance, and returns a variant of that package that is statically linked. It operates by just adding the relevant configure
flags, and recursively applying itself to the package’s inputs.
Another application is the on-line auto-updater: when installing a GNU package defined in the distribution, the guix package
command automatically checks whether a newer version is available upstream from ftp.gnu.org
, and offers the option to substitute the package’s source with a fresh download of the new upstream version—all at run time. This kind of feature is hardly accessible to an external DSL implementation. Among other things, this feature requires networking primitives (for the FTP client), which are typically unavailable in an external DSL such as the Nix language. The feature could be implemented in a language other than the DSL—for instance, Nix can export its abstract syntax tree as XML to external programs. However, this approach is often inefficient, due to the format conversion, and lossy: the exported representation may be either be too distant from the source code, or too distant from the preferred abstraction level. The author’s own experience writing an off-line auto-updater for Nix revealed other specific issues; for instance, the Nix language is lazily evaluated, but to make use of its XML output, one has to force strict evaluation, which in turn may generate more data than needed. In Guix, <package>
instances have the expected level of abstraction, and they are readily accessible as first-class Scheme objects.
Sometimes it is desirable for the value of a field to depend on the system type targeted. For instance, for bootstrapping purposes, MIT/GNU Scheme’s build system depends on pre-compiled binaries, which are architecture-dependent; its input
field must be able to select the right binaries depending on thearchitecture. To allow field values to refer to the target system type, we resort to thunked fields, as shown on line 13 of Figure 5. These fields have their value automatically wrapped in a thunk (a zero-argument procedure); when accessing them with the associated accessor, the thunk is transparently invoked. Thus, the values of thunked fields are computed lazily; more to the point, they can refer to dynamic state in place at their invocation point. In particular, the package-derivation
procedure (shortly introduced) sets up a current-system dynamically-scoped parameter, which allows field values to know what the target system is.
Finally, both <package>
and <origin>
records have an associated "compiler" that turns them into a derivation. origin-derivation
takes an <origin>
instance and returns a derivation that downloads it, according to its method
field. Likewise, package-derivation
takes a package and returns a derivation that builds it, according to its build-system
and associated arguments
(more on that in Section 3.4). As we have seen on Figure 4, the inputs
field lists dependencies of a package, which are themselves <package>
objects; the package-derivation
procedure recursively applies to those inputs, such that their derivation is computed and passed as the inputs argument of the lower-level build-expression->derivation
.
Guix essentially implements deep embedding of DSLs, where the semantics of the packaging DSL is interpreted by a dedicated compiler.[1] Of course the DSLs defined here are simple, but they illustrate how Scheme’s primitive mechanisms, in particular macros, make it easy to implement such DSLs without requiring any special support from the Scheme implementation.
Build Programs
The value of the build-system
field, as shown on Figure 4, must be a build-system
object, which is essentially a wrapper around two procedure: one procedure to do a native build, and one to do a cross-build. When the aforementioned package-derivation
(or package-cross-derivation
, when cross-building) is called, it invokes the build system’s build procedure, passing it a connection to the build daemon, the system type, derivation name, and inputs. It is the build system’s responsibility to return a derivation that actually builds the software.
(define* (gnu-build #:key (phases %standard-phases)
#:allow-other-keys
#:rest args)
;; Run all the PHASES in order, passing them ARGS.
;; Return true on success.
(every (match-lambda
((name . proc)
(format #t "starting phase '~a'~ %" name)
(let ((result (apply proc args)))
(format #t "phase '~a' done~ %" name)
result)))
phases))
- Figure 7
- Entry point of the builder side code of
gnu-build-system
.
The gnu-build-system
object (line 10 of Figure 4) provides procedures to build and cross-build software that uses the GNU build system or similar. In a nutshell, it runs the following phases by default:
- unpack the source tarball, and change the current directory to the resulting directory;
- patch shebangs on installed files—e.g., replace
#!/-bin/sh
by#!/nix/store/…-bash-4.2/bin/sh
; this is required to allow scripts to work with our unusual file system layout; - run
./configure --prefix=/nix/store/
…, followed bymake
andmake check
- ↑ P. Hudak. Building domain-specific embedded languages. In ACM Computing Surveys, 28(4es) , New York, NY, USA, December 1996.