Contact: Eelco Dolstra (Eelco Visser)
Develop a graphical user interface for the Nix system.
Nix (for the purposes of this project) is a package manager. Its job is to instantiate on a client system exactly those software components that the user asks for. So think RPM, the FreeBSD?
Ports collection, or the Microsoft Office installer.
In the Nix system, software components are described by expressions in a very simple (dare I say it) functional language called Fix. For example, here is an expression that describes how to build Perl 5.8.0:
[ ("name", "perl-5.8.0")
, ("build", Relative("perl/perl-build.sh"))
, ("src", Call(IncludeFix("fetchurl/fetchurl.fix"),
[ ("url", "http://www.perl.com/CPAN/src/5.0/perl-5.8.0.tar.gz")
, ("md5", "d9bdb180620306023fd35901a2878b62")
, ("stdenv", IncludeFix("stdenv/stdenv.fix"))
(Fix expressions don't have a real concrete syntax yet; the above is an ATerm representation of its abstract syntax).
This describes everything that goes into the build process: a build script (
), the source (
) which happens to be fetched from the network, and a "standard environment" (
) that contains the stuff you would expect in a minimal Unix build environment such as a C compiler and shell tools like
, and what have you. One useful property of Nix is that it automatically distinguishes variants
of a software system, such as different versions or builds using different parameters. E.g., in the example above, if anything changes in any of the inputs---such as the sources or the standard environment---Nix will instantiate a new component. These variants are kept isolated from each other in the file system by placing them in directory with names that include cryptographic hashes of all inputs (like
Things get more interesting when we express variability
of components in the Fix expressions. Variability means that a piece of software can be varied with respect to the feature set that it supports. For example, the Linux kernel has almost 2000 options ("variation points") that can be selected at build time (such as whether to include support for your favorite USB device). So a single source tree does not represent a single product but rather a (possibly very large) set of products. Managing the instantiation and deployment of many variants of a system can be a big problem.
Variability can be expressed in a Fix expression by turning it into a function. For example, the Subversion version management system has several build-time parameters, such as whether to support secure HTTP (those
URLs). The reason that one may wish to disable this is that it depends on another component called OpenSSL?
, and thus a variant that has this feature takes up more disk space. This can be expressed as follows:
[ ("name", "subversion-0.27.0")
, ("build", Relative("subversion/subversion-build.sh"))
, ("ssl", If(Var("httpsClient"), IncludeFix("openssl/openssl.fix"), Void))
and a variant can be instantiated by calling this function:
Call(IncludeFix("subversion.fix"), [("httpsClient", True)])
In general, of course, we can have many more variation points:
Function(["localServer", "httpsClient", "httpServer", "pythonBindings"],
[ ("name", "subversion-0.27.0") ...
A complete system (that is, the software environment as it appears to a user) can easily consist of hundreds with thousands of variation points. To make this manageable it would be very nice to be able to select the desired variant in a graphical user interface and then have Nix instantiate it. This is similar to what tools like the Linux kernel configurator
or certain graphical installers do. However, there a few requirements that make this an interesting problem.
* There are often constraints on the set of valid instantiations of a component since variation points are often interrelated. For example, in the Linux kernel you cannot enable SCSI disk support if you don't have SCSI support, and you cannot enable certain things if support for expiremental features is disabled. We can express such constraints in Fix:
Error("B requires A")
This says that feature B requires A. A GUI can of course show an error message when you try to select B without A, but it is much more intuitive if the presentation structure
prevents this in the first place. For example, it is intuitive to display this as a tree
with B as a child node of A (thus suggesting to the user that B is subordinate to A). This is usually solved by explicitly describing a presentation structure in the description of a package. However, this mixes model and presentation, which is not a good thing. So the problem is to automatically derive presentation structure from Fix expressions
* A GUI should give some indication to the user how long it will take to instantiate the selected variant. This is not a problem for binary installers, since they can just add up the size of all selected components (taking such things as speed of the installation medium (CD-ROM, network) into account). But as you can see from the expressions above, Nix operates on expressions that describe components in terms of their sources, and it is not easy to predict how long it is going to take to build something. However, Nix is not (exclusively) a source-based deployment system: it can do transparent source/binary deployments
. A distributor can pre-build certain variants and place them in a `cache' available to the clients (such a CD-ROM or an FTP site); clients will automatically use these if
they attempt to instantiate exactly
the same variant (and if not, they will be built on the client; thus a binary distribution transparently becomes a source distribution). So depending on the variant selected, it may be possible to provide feedback on the complexity of the instantiation.
* A GUI should try to minimise the size of the instantiated system. (TODO)
* The preferred implementation language is Java or C++. Nix itself is written in C++, so the latter might make integration a bit easier, but I don't have a strong preference.
* In the case of Java SWT
is the preferred toolkit. In the case of C++ it's wxWindows