Compare commits

...

56 Commits
0.2 ... 0.3

Author SHA1 Message Date
Eelco Dolstra
2615a6251a * Tagged version 0.3 because the format of slices is going to change. 2003-08-20 11:32:19 +00:00
Eelco Dolstra
710175e6a0 * Bumped the version number to 0.3. 2003-08-20 11:31:15 +00:00
Eelco Dolstra
ed0db2e0d8 * Fixed a serious bug in the computation of slices. Sometimes the slices
would not be properly closed under the path reference relation.
2003-08-20 11:30:45 +00:00
Eelco Dolstra
1472cc4825 * Pipe /dev/null into stdin. 2003-08-19 13:07:38 +00:00
Eelco Dolstra
2de8504791 * Delete the temporary directories of failed builds by default, and an
option `--keep-failed' to override this behaviour.
2003-08-19 09:04:47 +00:00
Eelco Dolstra
31e4aa6439 * Allow lists in package bindings, e.g.,
("srcs", [Relative("foo/bar.c"), Relative("foo/baz.h")])

  The result is an environment variable that contains the path names of the
  inputs separated by spaces (so this is not safe for values containing
  spaces).
2003-08-18 16:32:55 +00:00
Eelco Dolstra
ebbb6ce578 * Most shells initialise PATH to some default (/bin:/usr/bin:...)
when PATH is not set.  We don't want this, so fill it in with
  some dummy value.
2003-08-18 14:54:54 +00:00
Eelco Dolstra
c32e01eab2 * Revision 300!
* Put `@' in front of echo's in the Makefile.
2003-08-18 08:52:49 +00:00
Eelco Dolstra
08f9cfe267 * No longer automatically download Berkeley DB / ATerm. 2003-08-18 08:35:16 +00:00
Eelco Dolstra
96c7b98bf0 * Argument support in Fix. Arguments can be passed through the
builder using the `args' binding:

  ("args", ["bla", True, IncludeFix("aterm/aterm.fix")])

  Note that packages can also be declared as inputs by specifying them
  in the argument list.
2003-08-15 13:01:45 +00:00
Eelco Dolstra
555347744d * Derivation expressions now can specify arguments to be passed to the
builder.  Note that this unfortunately causes all Fix-computed
  hashes to change.
2003-08-15 12:32:37 +00:00
Eelco Dolstra
e374dbf89b * A script `nix-prefetch-url' to fetch a URL, place it in the Nix
store, and print its hash.
2003-08-15 10:13:41 +00:00
Eelco Dolstra
01e30360d4 * Don't use a temporary file. 2003-08-15 09:39:33 +00:00
Eelco Dolstra
163db7367f * Fix can now read expressions from stdin (by saying `fix -'). 2003-08-15 09:21:19 +00:00
Eelco Dolstra
161aab582b * Use a catalog when calling xsltproc. 2003-08-14 18:24:40 +00:00
Eelco Dolstra
a24cb19361 * Use xmllint instead of nsgmls to validate the manual. 2003-08-14 18:17:02 +00:00
Eelco Dolstra
9ee3b7a37a * Function application test cases. 2003-08-14 12:37:50 +00:00
Eelco Dolstra
dc0ef2ca98 * Detect infinite loops using blackholing. 2003-08-14 12:37:31 +00:00
Eelco Dolstra
2e16ff22ac * Fix man page. 2003-08-14 11:27:02 +00:00
Eelco Dolstra
5cde23f869 * Function() takes a list of formals. 2003-08-14 09:49:31 +00:00
Eelco Dolstra
0a2de7f543 * Lam -> Function. Doh! 2003-08-14 09:29:07 +00:00
Eelco Dolstra
95b49f8044 * Manual updates. 2003-08-13 15:17:57 +00:00
Eelco Dolstra
68022552d2 * Put the pre-built manual and man pages in the tar distribution. 2003-08-13 15:17:36 +00:00
Eelco Dolstra
c34a153ae5 * Documented the `--query' operation. 2003-08-13 10:45:01 +00:00
Eelco Dolstra
b4f88d0ec3 * Split the book.xml into several xml files. 2003-08-13 09:13:52 +00:00
Eelco Dolstra
469f1eba56 * Documented some Nix operations. 2003-08-12 15:06:49 +00:00
Eelco Dolstra
e405ca506e * Generate man pages from the manual. 2003-08-12 13:54:42 +00:00
Eelco Dolstra
c602930e08 * deletePath(): some operating systems (e.g., Mac OS X) don't like it
when we delete entries from a directory while we are reading it.
  So read the directory into memory, then delete its contents.
2003-08-08 14:55:56 +00:00
Eelco Dolstra
4b7b0bd12c * Started on the introduction. 2003-08-07 15:27:14 +00:00
Eelco Dolstra
74867e72f2 * Start of manual; installation instructions. 2003-08-07 14:17:18 +00:00
Eelco Dolstra
f8035d06f2 * Allow a name to be given to a system configuration through `--name
NAME'.  E.g., on the losser Subversion server, I do `nix-switch --name 
  svn $(fix ...)' to atomically upgrade the server (the SVN server 
  uses the Apache and Subversion installations in /nix/var/nix/links/svn).
2003-08-06 14:48:29 +00:00
Eelco Dolstra
9ad39df282 * `==' is not a valid operator. 2003-08-06 10:00:30 +00:00
Eelco Dolstra
d551062ec4 * Scan for wget and use the full path in fetchurl.sh.
* Use nix-hash (not md5sum) in fetchurl.sh.
2003-08-06 09:35:05 +00:00
Eelco Dolstra
236eb59293 * Allow locks on paths to be acquired recursively (that is, if the
process is already holding a lock on a path, it may acquire the lock
  again without blocking or failing).  (This might be dangerous, not
  sure).  Necessary for fast builds to work.
2003-08-06 09:34:04 +00:00
Eelco Dolstra
720f06e3b0 * A flag `--flat' to just compute the MD5 checksum of the contents of
a regular file.  I.e., `nix-hash --flat' is equivalent to the
  coreutils `md5sum' command (which doesn't exist on all systems).
2003-08-06 09:06:32 +00:00
Eelco Dolstra
37483672d4 * App -> Call.
* Allow booleans in package environment bindings (True maps to "1",
  False maps to "").
2003-08-06 09:05:04 +00:00
Eelco Dolstra
d34b4d4f28 * Conditionals. 2003-08-05 13:05:30 +00:00
Eelco Dolstra
b9c9b461ea * Made nix-push much faster. 2003-08-05 12:30:06 +00:00
Eelco Dolstra
4ce652640b * Cache result of fstatePaths(). TODO: do this in fstore.cc. 2003-08-05 12:29:47 +00:00
Eelco Dolstra
fd30f52cfc * Made nix-pull much faster by performing all Fix instantiations at
the same time.
2003-08-05 11:14:24 +00:00
Eelco Dolstra
17f05dba77 * Allow the top-level expression to be a list of expressions that
normalise to Nix expression.
2003-08-05 11:13:38 +00:00
Eelco Dolstra
d6b6b2d3a8 * Delete obstructed paths prior to building. 2003-08-05 09:47:20 +00:00
Eelco Dolstra
d2e963f7a3 * Path locking in addToStore() and expandPath(). 2003-08-04 07:09:36 +00:00
Eelco Dolstra
c95b4ad290 * In normaliseFState(), wrap registration of the output paths and the
normal form in a single transaction to ensure that if we crash,
  either everything is registered or nothing is.  This is for
  recoverability: unregistered paths in the store can be deleted
  arbitrarily, while registered paths can only be deleted by running
  the garbage collector.
2003-08-01 15:41:47 +00:00
Eelco Dolstra
d99d04e644 * Defensive programming against POSIX locking idiocy.
* Simplified realiseSlice().
2003-08-01 15:06:23 +00:00
Eelco Dolstra
545145cd58 * normaliseFState() now locks all output paths prior to building, thus
ensuring that simultaneous invocations of Nix don't clobber
  each other's  builds.

* Fixed a bug in `make install'.
2003-08-01 14:11:19 +00:00
Eelco Dolstra
9df93f30bd * Don't use substitutes in addToStore(). 2003-08-01 09:01:51 +00:00
Eelco Dolstra
06434072e7 * Put the database verifier in a transaction. 2003-07-31 19:49:11 +00:00
Eelco Dolstra
06d3d7355d * Enclose most operations that update the database in transactions.
* Open all database tables (Db objects) at initialisation time, not
  every time they are used.  This is necessary because tables have to
  outlive all transactions that refer to them.
2003-07-31 16:05:35 +00:00
Eelco Dolstra
177a7782ae * Use a more reasonable log file size (256 KB instead of 10 MB).
* Checkpoint on exit.
2003-07-31 14:28:49 +00:00
Eelco Dolstra
4a013962bd * Started using Berkeley DB environments. This is necessary for
transaction support (but we don't actually use transactions yet).
2003-07-31 13:47:13 +00:00
Eelco Dolstra
758bd4673a * Set execute bit. 2003-07-31 13:13:27 +00:00
Eelco Dolstra
9f4c19276d * Basic makefile. 2003-07-31 13:13:13 +00:00
Eelco Dolstra
26ff1cdf89 * A better test case for Nix race conditions. 2003-07-30 14:40:46 +00:00
Eelco Dolstra
64c617e984 * Directories for the manual. 2003-07-30 14:40:18 +00:00
Eelco Dolstra
2ac02440dc * Test cases for races. 2003-07-30 13:35:46 +00:00
54 changed files with 2096 additions and 341 deletions

View File

@@ -1,3 +1,3 @@
SUBDIRS = externals src scripts corepkgs
SUBDIRS = externals src scripts corepkgs doc
EXTRA_DIST = boost/*.hpp boost/format/*.hpp substitute.mk

View File

@@ -1,4 +1,4 @@
AC_INIT(nix, 0.2pre1)
AC_INIT(nix, 0.3)
AC_CONFIG_SRCDIR(src/nix.cc)
AC_CONFIG_AUX_DIR(config)
AM_INIT_AUTOMAKE
@@ -11,10 +11,13 @@ AC_PROG_CC
AC_PROG_CXX
AC_PROG_RANLIB
AC_PATH_PROG(wget, wget)
AC_CHECK_LIB(pthread, pthread_mutex_init)
AM_CONFIG_HEADER([config.h])
AC_CONFIG_FILES([Makefile externals/Makefile src/Makefile scripts/Makefile
corepkgs/Makefile corepkgs/fetchurl/Makefile
corepkgs/nar/Makefile])
corepkgs/nar/Makefile
doc/Makefile doc/manual/Makefile])
AC_OUTPUT

View File

@@ -1,10 +0,0 @@
#! /bin/sh
echo "downloading $url into $out..."
wget "$url" -O "$out" || exit 1
actual=$(md5sum -b $out | cut -c1-32)
if ! test "$actual" == "$md5"; then
echo "hash is $actual, expected $md5"
exit 1
fi

View File

@@ -0,0 +1,19 @@
#! /bin/sh
export PATH=/bin:/usr/bin
echo "downloading $url into $out..."
prefetch=@prefix@/store/nix-prefetch-url-$md5
if test -f "$prefetch"; then
echo "using prefetched $prefetch";
mv $prefetch $out || exit 1
else
@wget@ "$url" -O "$out" || exit 1
fi
actual=$(@bindir@/nix-hash --flat $out)
if test "$actual" != "$md5"; then
echo "hash is $actual, expected $md5"
exit 1
fi

1
doc/Makefile.am Normal file
View File

@@ -0,0 +1 @@
SUBDIRS = manual

30
doc/manual/Makefile.am Normal file
View File

@@ -0,0 +1,30 @@
DOCBOOK_DTD = /nix/current/xml/dtd/docbook
DOCBOOK_XSL = /nix/current/xml/xsl/docbook
ENV = SGML_CATALOG_FILES=$(DOCBOOK_DTD)/docbook.cat
XMLLINT = $(ENV) xmllint --catalogs
XSLTPROC = $(ENV) xsltproc --catalogs
SOURCES = book.xml introduction.xml installation.xml nix-reference.xml \
troubleshooting.xml bugs.xml
book.is-valid: $(SOURCES)
$(XMLLINT) --noout --valid book.xml
touch $@
man1_MANS = nix.1 fix.1
man nix.1 fix.1: $(SOURCES) book.is-valid
$(XSLTPROC) $(DOCBOOK_XSL)/manpages/docbook.xsl book.xml
book.html: $(SOURCES) book.is-valid
$(XSLTPROC) --output book.html $(DOCBOOK_XSL)/html/docbook.xsl book.xml
all-local: book.html
install-data-local: book.html
$(INSTALL) -d $(datadir)/nix/manual
$(INSTALL_DATA) book.html $(datadir)/nix/manual
EXTRA_DIST = $(SOURCES) book.html nix.1 fix.1 book.is-valid

64
doc/manual/book.xml Normal file
View File

@@ -0,0 +1,64 @@
<?xml version="1.0"?>
<!DOCTYPE book
PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[
<!ENTITY introduction SYSTEM "introduction.xml">
<!ENTITY installation SYSTEM "installation.xml">
<!ENTITY nix-reference SYSTEM "nix-reference.xml">
<!ENTITY fix-reference SYSTEM "fix-reference.xml">
<!ENTITY troubleshooting SYSTEM "troubleshooting.xml">
<!ENTITY bugs SYSTEM "bugs.xml">
]>
<book>
<title>Nix: The Manual</title>
<bookinfo>
<author>
<firstname>Eelco</firstname>
<surname>Dolstra</surname>
</author>
<copyright>
<year>2003</year>
<holder>Eelco Dolstra</holder>
</copyright>
</bookinfo>
&introduction;
&installation;
<chapter>
<title>A Guided Tour</title>
<para>
</para>
</chapter>
<chapter>
<title>Nix Syntax and Semantics</title>
<para>
</para>
</chapter>
<chapter>
<title>Fix Language Reference</title>
<para>
</para>
</chapter>
<chapter>
<title>Writing Builders</title>
<para>
</para>
</chapter>
<appendix>
<title>Command Reference</title>
&nix-reference;
&fix-reference;
</appendix>
&troubleshooting;
&bugs;
</book>

43
doc/manual/bugs.xml Normal file
View File

@@ -0,0 +1,43 @@
<appendix>
<title>Bugs</title>
<itemizedlist>
<listitem>
<para>
Nix should automatically recover the Berkeley DB database.
</para>
</listitem>
<listitem>
<para>
Nix should automatically remove Berkeley DB logfiles.
</para>
</listitem>
<listitem>
<para>
Unify the concepts of successors and substitutes into a general notion
of <emphasis>equivalent expressions</emphasis>. Expressions are
equivalent if they have the same target paths with the same
identifiers. However, even though they are functionally equivalent,
they may differ stronly with respect to their <emphasis>performance
characteristics</emphasis>. For example, realising a slice is more
efficient that realising the derivation from which that slice was
produced. On the other hand, distributing sources may be more
efficient (storage- or bandwidth-wise) than distributing binaries. So
we need to be able to attach weigths or priorities or performance
annotations to expressions; Nix can then choose the most efficient
expression dependent on the context.
</para>
</listitem>
</itemizedlist>
</appendix>
<!--
local variables:
sgml-parent-document: ("book.xml" "appendix")
end:
-->

View File

@@ -0,0 +1,37 @@
<refentry>
<refnamediv>
<refname>fix</refname>
<refpurpose>generate Nix expressions from a high-level description</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>fix</command>
<group choice='opt' rep='repeat'>
<arg><option>--verbose</option></arg>
<arg><option>-v</option></arg>
</group>
<arg rep='repeat'><replaceable>files</replaceable></arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
The command <command>fix</command> generates Nix expressions from
expressions is Fix's own high-level language. While Nix expressions are
very primitive and not intended to be written directly, Fix expressions
are quite easy to write.
</para>
</refsect1>
</refentry>
<!--
local variables:
sgml-parent-document: ("book.xml" "refentry")
end:
-->

View File

@@ -0,0 +1,80 @@
<chapter>
<title>Installation</title>
<sect1>
<title>Prerequisites</title>
<para>
Nix uses Sleepycat's Berkeley DB and CWI's ATerm library. However, these
are fetched automatically as part of the build process.
</para>
<para>
Other than that, you need a good C++ compiler. GCC 2.95 does not appear
to work; please use GCC 3.x.
</para>
</sect1>
<sect1>
<title>Obtaining Nix</title>
<para>
Nix can be obtained from its <ulink
url='http://losser.st-lab.cs.uu.nl:12080/repos/trace/nix/trunk'>Subversion
repository</ulink>. For example, the following command will check out
the latest revision into a directory called <filename>nix</filename>:
</para>
<screen>
$ svn checkout http://losser.st-lab.cs.uu.nl:12080/repos/trace/nix/trunk nix</screen>
<para>
Likewise, specific releases can be obtained from the <ulink
url='http://losser.st-lab.cs.uu.nl:12080/repos/trace/nix/tags'>tags
directory</ulink> of the repository. If you don't have Subversion, you
can download a <ulink
url='http://losser.st-lab.cs.uu.nl:12080/dist/trace/'>compressed
tar-file</ulink> of the latest revision of the repository.
</para>
</sect1>
<sect1>
<title>Building Nix</title>
<para>
To build Nix, do the following:
</para>
<screen>
$ autoreconf -i
$ ./configure <replaceable>options...</replaceable>
$ make
$ make install</screen>
<para>
Currently, the only useful switch for <command>configure</command> is
<option>--prefix=<replaceable>prefix</replaceable></option> to specify
where Nix is to be installed. The default installation directory is
<filename>/nix</filename>. You can change this to any location you like.
You should ensure that you have write permission to the installation
prefix.
</para>
<warning>
<para>
It is advisable <emphasis>not</emphasis> to change the installation
prefix, since doing so will in all likelihood make it impossible to use
derivates built on other systems.
</para>
</warning>
</sect1>
</chapter>
<!--
local variables:
sgml-parent-document: ("book.xml" "chapter")
end:
-->

184
doc/manual/introduction.xml Normal file
View File

@@ -0,0 +1,184 @@
<chapter>
<title>Introduction</title>
<sect1>
<title>The problem space</title>
<para>
Nix is a system for controlling the automatic creation and distribution
of data, such as computer programs and other software artifacts. This is
a very general problem, and there are many applications that fall under
this description.
</para>
<sect2>
<title>Build management</title>
<para>
Build management tools are used to perform <emphasis>software
builds</emphasis>, that is, the construction of derived products such
as executable programs from source code. A commonly used build tool is
Make, which is a standard tool on Unix systems. These tools have to
deal with several issues:
<itemizedlist>
<listitem>
<para>
</para>
</listitem>
</itemizedlist>
</para>
</sect2>
<sect2>
<title>Package management</title>
<para>
After software has been built, is must also be
<emphasis>deployed</emphasis> in the intended target environment, e.g.,
the user's workstation. Examples include the Red Hat package manager
(RPM), Microsoft's MSI, and so on. Here also we have to deal with
several issues:
<itemizedlist>
<listitem>
<para>
The <emphasis>creation</emphasis> of packages from some formal
description of what artifacts should be distributed in the
package.
</para>
</listitem>
<listitem>
<para>
The <emphasis>deployment</emphasis> of packages, that is, the
mechanism by which we get them onto the intended target
environment. This can be as simple as copying a file, but
complexity comes from the wide range of possible installation
media (such as a network install), and the scalability of the
process (if a program must be installed on a thousand systems, we
do not want to visit each system and perform some manual steps to
install the program on that system; that is, the complexity for
the system administrator should be constant, not linear).
</para>
</listitem>
</itemizedlist>
</para>
</sect2>
</sect1>
<!--######################################################################-->
<sect1>
<title>What Nix can do for you</title>
<para>
Here is a summary of what Nix provides:
</para>
<itemizedlist>
<listitem>
<para>
<emphasis>Reliable dependencies.</emphasis>
</para>
</listitem>
<listitem>
<para>
<emphasis>Support for variability.</emphasis>
</para>
</listitem>
<listitem>
<para>
<emphasis>Transparent source/binary deployment.</emphasis>
</para>
</listitem>
<listitem>
<para>
<emphasis>Easy configuration duplication.</emphasis>
</para>
</listitem>
<listitem>
<para>
<emphasis>Automatic storage management.</emphasis>
</para>
</listitem>
<listitem>
<para>
<emphasis>Atomic upgrades and rollbacks.</emphasis>
</para>
</listitem>
<listitem>
<para>
<emphasis>Support for many simultaneous configurations.</emphasis>
</para>
</listitem>
</itemizedlist>
<para>
Here is what Nix doesn't yet provide, but will:
</para>
<itemizedlist>
<listitem>
<para>
<emphasis>Build management.</emphasis> In principle it is already
possible to do build management using Fix (by writing builders that
perform appropriate build steps), but the Fix language is not yet
powerful enough to make this pleasant. The <ulink
url='http://www.cs.uu.nl/~eelco/maak/'>Maak build manager</ulink>
should be retargeted to produce Nix expressions, or alternatively,
extend Fix with Maak's semantics and concrete syntax (since Fix needs
a concrete syntax anyway). Another interesting idea is to write a
<command>make</command> implementation that uses Nix as a back-end to
support <ulink
url='http://www.research.att.com/~bs/bs_faq.html#legacy'>legacy</ulink>
build files.
</para>
</listitem>
</itemizedlist>
</sect1>
<!--######################################################################-->
<sect1>
<title>The Nix system</title>
<para>
...
</para>
<para>
Existing tools in this field generally both a underlying model (such as
the derivation graph of build tools, or the versioning scheme that
determines when two packages are <quote>compatible</quote> in a package
management system) and a formalism that allows ...
</para>
<para>
Following the principle of separation of mechanism and policy, the Nix
system separates the <emphasis>low-level aspect</emphasis> of file system
object management form the <emphasis>high-level aspect</emphasis> of the
...
</para>
</sect1>
</chapter>
<!--
local variables:
sgml-parent-document: ("book.xml" "chapter")
end:
-->

View File

@@ -0,0 +1,444 @@
<refentry>
<refnamediv>
<refname>nix</refname>
<refpurpose>manipulate or query the Nix store</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>nix</command>
<group choice='opt'>
<arg><option>--path</option></arg>
<arg><option>-p</option></arg>
</group>
<group choice='opt' rep='repeat'>
<arg><option>--verbose</option></arg>
<arg><option>-v</option></arg>
</group>
<group choice='opt' rep='repeat'>
<arg><option>--keep-failed</option></arg>
<arg><option>-K</option></arg>
</group>
<arg choice='plain'><replaceable>operation</replaceable></arg>
<arg rep='repeat'><replaceable>options</replaceable></arg>
<arg rep='repeat'><replaceable>arguments</replaceable></arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
The command <command>nix</command> provides access to the Nix store. This
is the (set of) path(s) where Nix expressions and the file system objects
built by them are stored.
</para>
<para>
<command>nix</command> has many subcommands called
<emphasis>operations</emphasis>. These are individually documented
below. Exactly one operation must always be provided.
</para>
</refsect1>
<refsect1>
<title>Common Options</title>
<para>
In this section the options that are common to all Nix operations are
listed. These options are allowed for every subcommand (although they
may not always have an effect).
</para>
<variablelist>
<varlistentry>
<term><option>--path</option></term>
<listitem>
<para>
Indicates that any identifier arguments to the operation are paths
in the store rather than identifiers.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--verbose</option></term>
<listitem>
<para>
Increases the level of verbosity of diagnostic messages printed on
standard error. For each Nix operation, the information printed on
standard output is well-defined and specified below in the
respective sections. Any diagnostic information is printed on
standard error, never on standard output.
</para>
<para>
This option may be specified repeatedly. Currently, the following
verbosity levels exist:
</para>
<variablelist>
<varlistentry>
<term>0</term>
<listitem>
<para>
Print error messages only.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>1</term>
<listitem>
<para>
Print informational messages.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>2</term>
<listitem>
<para>
Print even more informational messages.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>3</term>
<listitem>
<para>
Print messages that should only be useful for debugging.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>4</term>
<listitem>
<para>
<quote>Vomit mode</quote>: print vast amounts of debug
information.
</para>
</listitem>
</varlistentry>
</variablelist>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--keep-failed</option></term>
<listitem>
<para>
Specifies that in case of a build failure, the temporary directory
(usually in <filename>/tmp</filename>) in which the build takes
place should not be deleted. The path of the build directory is
printed as an informational message.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<!--######################################################################-->
<refsect1>
<title>Operation <option>--install</option></title>
<refsect2>
<title>Synopsis</title>
<cmdsynopsis>
<command>nix</command>
<group>
<arg><option>--install</option></arg>
<arg><option>-i</option></arg>
</group>
<arg choice='plain' rep='repeat'><replaceable>ids</replaceable></arg>
</cmdsynopsis>
</refsect2>
<refsect2>
<title>Description</title>
<para>
The operation <option>--install</option> realises the Nix expressions
identified by <replaceable>ids</replaceable> in the file system. If
these expressions are derivation expressions, they are first
normalised. That is, their target paths are are built, unless a normal
form is already known.
</para>
<para>
The identifiers of the normal forms of the given Nix expressions are
printed on standard output.
</para>
</refsect2>
</refsect1>
<!--######################################################################-->
<refsect1>
<title>Operation <option>--delete</option></title>
<refsect2>
<title>Synopsis</title>
<cmdsynopsis>
<command>nix</command>
<group>
<arg><option>--delete</option></arg>
<arg><option>-d</option></arg>
</group>
<arg choice='plain' rep='repeat'><replaceable>paths</replaceable></arg>
</cmdsynopsis>
</refsect2>
<refsect2>
<title>Description</title>
<para>
The operation <option>--delete</option> unconditionally deletes the
paths <replaceable>paths</replaceable> from the Nix store. It is an
error to attempt to delete paths outside of the store.
</para>
<warning>
<para>
This operation should almost never be called directly, since no
attempt is made to verify that no references exist to the paths to
be deleted. Therefore, careless deletion can result in an
inconsistent system. Deletion of paths in the store is done by the
garbage collector (which uses <option>--delete</option> to delete
unreferenced paths).
</para>
</warning>
</refsect2>
</refsect1>
<!--######################################################################-->
<refsect1>
<title>Operation <option>--query</option></title>
<refsect2>
<title>Synopsis</title>
<cmdsynopsis>
<command>nix</command>
<group>
<arg><option>--query</option></arg>
<arg><option>-q</option></arg>
</group>
<group>
<group>
<arg><option>--list</option></arg>
<arg><option>-l</option></arg>
</group>
<group>
<arg><option>--requisites</option></arg>
<arg><option>-r</option></arg>
</group>
<group>
<arg><option>--expansion</option></arg>
<arg><option>-e</option></arg>
</group>
<group>
<arg><option>--graph</option></arg>
<arg><option>-g</option></arg>
</group>
</group>
<arg choice='plain' rep='repeat'><replaceable>args</replaceable></arg>
</cmdsynopsis>
</refsect2>
<refsect2>
<title>Description</title>
<para>
The operation <option>--query</option> displays various bits of
information about Nix expressions or paths in the store. The queries
are described in <xref linkend='nixref-queries' />. At most one query
can be specified; the default query is <option>--list</option>.
</para>
</refsect2>
<refsect2 id='nixref-queries'>
<title>Queries</title>
<variablelist>
<varlistentry>
<term><option>--list</option></term>
<listitem>
<para>
Prints out the target paths of the Nix expressions indicated by
the identifiers <replaceable>args</replaceable>. In the case of
a derivation expression, these are the paths that will be
produced by the builder of the expression. In the case of a
slice expression, these are the root paths (which are generally
the paths that were produced by the builder of the derivation
expression of which the slice is a normal form).
</para>
<para>
This query has one option:
</para>
<variablelist>
<varlistentry>
<term><option>--normalise</option></term>
<listitem>
<para>
Causes the target paths of the <emphasis>normal
forms</emphasis> of the expressions to be printed, rather
than the target paths of the expressions themselves.
</para>
</listitem>
</varlistentry>
</variablelist>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--requisites</option></term>
<listitem>
<para>
Prints out the requisite paths of the Nix expressions indicated
by the identifiers <replaceable>args</replaceable>. The
requisite paths of a Nix expression are the paths that need to be
present in the system to be able to realise the expression. That
is, they form the <emphasis>closure</emphasis> of the expression
in the file system (i.e., no path in the set of requisite paths
points to anything outside the set of requisite paths).
</para>
<para>
The notion of requisite paths is very useful when one wants to
distribute Nix expressions. Since they form a closure, they are
the only paths one needs to distribute to another system to be
able to realise the expression on the other system.
</para>
<para>
This query is generally used to implement various kinds of
distribution. A <emphasis>source distribution</emphasis> is
obtained by distributing the requisite paths of a derivation
expression. A <emphasis>binary distribution</emphasis> is
obtained by distributing the requisite paths of a slice
expression (i.e., the normal form of a derivation expression; you
can directly specify the identifier of the slice expression, or
use <option>--normalise</option> and specify the identifier of a
derivation expression). A <emphasis>cache
distribution</emphasis> is obtained by distributing the
requisite paths of a derivation expression and specifying the
option <option>--include-successors</option>. This will include
not just the paths of a source and binary distribution, but also
all expressions and paths of subterms of the source. This is
useful if one wants to realise on the target system a Nix
expression that is similar but not quite the same as the one
being distributed, since any common subterms will be reused.
</para>
<para>
This query has a number of options:
</para>
<variablelist>
<varlistentry>
<term><option>--normalise</option></term>
<listitem>
<para>
Causes the requisite paths of the <emphasis>normal
forms</emphasis> of the expressions to be printed, rather
than the requisite paths of the expressions themselves.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--exclude-exprs</option></term>
<listitem>
<para>
Excludes the paths of Nix expressions. This causes the
closure property to be lost, that is, the resulting set of
paths is not enough to ensure realisibility.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--include-successors</option></term>
<listitem>
<para>
Also include the requisites of successors (normal forms).
Only the requisites of <emphasis>known</emphasis>
successors are included, i.e., the normal forms of
derivation expressions that have never been normalised will
not be included.
</para>
<para>
Note that not just the successor of a derivation expression
will be included, but also the successors of all input
expressions of that derivation expression. I.e., all
normal forms of subterms involved in the normalisation of
the top-level term are included.
</para>
</listitem>
</varlistentry>
</variablelist>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--expansion</option></term>
<listitem>
<para>
For each identifier in <replaceable>args</replaceable>, prints
all expansions of that identifier, that is, all paths whose
current content matches the identifier.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--graph</option></term>
<listitem>
<para>
Prints a graph of the closure of the expressions identified by
<replaceable>args</replaceable> in the format of the
<command>dot</command> tool of AT&amp;T's GraphViz package.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect2>
</refsect1>
</refentry>
<!--
local variables:
sgml-parent-document: ("book.xml" "refentry")
end:
-->

View File

@@ -0,0 +1,48 @@
<appendix>
<title>Troubleshooting</title>
<sect1>
<title>Database hangs</title>
<para>
If Nix or Fix appear to hang immediately after they are started, Nix's
database is probably <quote>wedged</quote>, i.e., some process died while
it held a lock on the database. The solution is to ensure that no other
processes are accessing the database and then run the following command:
</para>
<screen>
$ db_recover -e -h <replaceable>prefix</replaceable>/var/nix/db</screen>
<para>
Here, <replaceable>prefix</replaceable> should be replaced by Nix's
installation prefix.
</para>
</sect1>
<sect1>
<title>Database logfile removal</title>
<para>
Every time a Nix database transaction takes place, Nix writes a record of
this transaction to a <emphasis>log</emphasis> in its database directory
to ensure that the operation can be replayed in case of a application or
system crash. However, without manual intervention, the log grows
indefinitely. Hence, unused log files should be deleted periodically.
This can be accomplished using the following command:
</para>
<screen>
$ rm `db_archive -a -h <replaceable>prefix</replaceable>/var/nix/db`</screen>
</sect1>
</appendix>
<!--
local variables:
sgml-parent-document: ("book.xml" "appendix")
end:
-->

14
externals/Makefile.am vendored
View File

@@ -1,10 +1,13 @@
# Berkeley DB
DB = db-4.0.14
DB_URL = http://www.sleepycat.com/update/snapshot/db-4.0.14.tar.gz
$(DB).tar.gz:
wget $(DB_URL)
@echo "Nix requires Berkeley DB to build."
@echo "Please download version 4.0.14 from"
@echo " http://www.sleepycat.com/update/snapshot/db-4.0.14.tar.gz"
@echo "and place it in the externals/ directory."
false
$(DB): $(DB).tar.gz
gunzip < $(DB).tar.gz | tar xvf -
@@ -26,10 +29,13 @@ build-db: have-db
# CWI ATerm
ATERM = aterm-2.0
ATERM_URL = http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz
$(ATERM).tar.gz:
wget $(ATERM_URL)
@echo "Nix requires the CWI ATerm library to build."
@echo "Please download version 2.0 from"
@echo " http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz"
@echo "and place it in the externals/ directory."
false
$(ATERM): $(ATERM).tar.gz
gunzip < $(ATERM).tar.gz | tar xvf -

View File

@@ -1,5 +1,5 @@
bin_SCRIPTS = nix-switch nix-collect-garbage \
nix-pull nix-push
nix-pull nix-push nix-prefetch-url
noinst_SCRIPTS = nix-profile.sh
@@ -14,5 +14,6 @@ include ../substitute.mk
EXTRA_DIST = nix-switch.in nix-collect-garbage.in \
nix-pull.in nix-push.in nix-profile.sh.in \
nix-prefetch-url.in \
prebuilts.conf

View File

@@ -0,0 +1,48 @@
#! /usr/bin/perl -w
use strict;
use IPC::Open2;
my $url = shift @ARGV;
defined $url or die;
print "fetching $url...\n";
my $out = "@prefix@/store/nix-prefetch-url-$$";
system "@wget@ '$url' -O '$out'";
$? == 0 or die "unable to fetch $url";
my $hash=`@bindir@/nix-hash --flat $out`;
$? == 0 or die "unable to hash $out";
chomp $hash;
print "file has hash $hash\n";
my $out2 = "@prefix@/store/nix-prefetch-url-$hash";
rename $out, $out2;
# Create a Fix expression.
my $fixexpr =
"App(IncludeFix(\"fetchurl/fetchurl.fix\"), " .
"[(\"url\", \"$url\"), (\"md5\", \"$hash\")])";
# Instantiate a Nix expression.
print STDERR "running fix...\n";
my $pid = open2(\*READ, \*WRITE, "fix -") or die "cannot run fix";
print WRITE $fixexpr;
close WRITE;
my $id = <READ>;
chomp $id;
waitpid $pid, 0;
$? == 0 or die "fix failed";
# Run Nix.
print STDERR "running nix...\n";
system "nix --install $id > /dev/null";
$? == 0 or die "`nix --install' failed";
unlink $out2;

View File

@@ -1,11 +1,18 @@
#! /usr/bin/perl -w
use strict;
use IPC::Open2;
my $tmpfile = "@localstatedir@/nix/pull.tmp";
my $conffile = "@sysconfdir@/nix/prebuilts.conf";
my @ids;
my @subs;
my @sucs;
my $fullexpr = "[";
my $first = 1;
open CONFFILE, "<$conffile";
while (<CONFFILE>) {
@@ -41,7 +48,7 @@ while (<CONFFILE>) {
$outname = "unnamed";
}
print "registering $id -> $url/$fn\n";
print STDERR "$id ($outname)\n";
# Construct a Fix expression that fetches and unpacks a
# Nix archive from the network.
@@ -55,23 +62,14 @@ while (<CONFFILE>) {
", (\"id\", \"$id\")" .
"])";
my $fixfile = "/tmp/nix-pull-tmp.fix";
open FIX, ">$fixfile";
print FIX $fixexpr;
close FIX;
if (!$first) { $fullexpr .= "," };
$first = 0;
$fullexpr .= $fixexpr; # !!! O(n^2)?
# Instantiate a Nix expression from the Fix expression.
my $nid = `fix $fixfile`;
$? and die "instantiating Nix archive expression";
chomp $nid;
die unless $nid =~ /^([0-9a-z]{32})$/;
push @subs, $id;
push @subs, $nid;
push @ids, $id;
# Does the name encode a successor relation?
if (defined $fsid) {
print "NORMAL $fsid -> $id\n";
push @sucs, $fsid;
push @sucs, $id;
}
@@ -84,8 +82,34 @@ while (<CONFFILE>) {
}
$fullexpr .= "]";
# Instantiate Nix expressions from the Fix expressions we created above.
print STDERR "running fix...\n";
my $pid = open2(\*READ, \*WRITE, "fix -") or die "cannot run fix";
print WRITE $fullexpr;
close WRITE;
my $i = 0;
while (<READ>) {
chomp;
die unless /^([0-9a-z]{32})$/;
my $nid = $1;
die unless ($i < scalar @ids);
my $id = $ids[$i++];
push @subs, $id;
push @subs, $nid;
}
waitpid $pid, 0;
$? == 0 or die "fix failed";
# Register all substitutes.
print STDERR "registering substitutes...\n";
system "nix --substitute @subs";
if ($?) { die "`nix --substitute' failed"; }
# Register all successors.
print STDERR "registering successors...\n";
system "nix --successor @sucs";
if ($?) { die "`nix --successor' failed"; }

View File

@@ -1,6 +1,9 @@
#! /usr/bin/perl -w
my @pushlist;
my $fixfile = "/tmp/nix-push-tmp.fix";
open FIX, ">$fixfile";
print FIX "[";
my $first = 1;
foreach my $id (@ARGV) {
@@ -35,6 +38,7 @@ foreach my $id (@ARGV) {
foreach my $path (@paths) {
next unless ($path =~ /\/([0-9a-z]{32})[^\/]*/);
print "$path\n";
my $pathid = $1;
# Construct a name for the Nix archive. If the file is an
@@ -51,30 +55,41 @@ foreach my $id (@ARGV) {
"[ (\"path\", Slice([\"$pathid\"], [(\"$path\", \"$pathid\", [])]))" .
"])";
my $fixfile = "/tmp/nix-push-tmp.fix";
open FIX, ">$fixfile";
print FIX "," unless ($first);
$first = 0;
print FIX $fixexpr;
close FIX;
# Instantiate a Nix expression from the Fix expression.
my $nid = `fix $fixfile`;
$? and die "instantiating Nix archive expression";
chomp $nid;
die unless $nid =~ /^([0-9a-z]{32})$/;
# Realise the Nix expression.
system "nix --install $nid";
if ($?) { die "`nix --install' failed"; }
my $npath = `nix --query --list $nid 2> /dev/null`;
$? and die "`nix --query --list' failed";
chomp $npath;
push @pushlist, "$npath/*";
print "$path -> $npath\n";
}
}
print FIX "]";
close FIX;
# Instantiate a Nix expression from the Fix expression.
my @nids;
print STDERR "running fix...\n";
open NIDS, "fix $fixfile |" or die "cannot run fix";
while (<NIDS>) {
chomp;
die unless /^([0-9a-z]{32})$/;
push @nids, $1;
}
# Realise the Nix expression.
my @pushlist;
print STDERR "creating archives...\n";
system "nix --install @nids > /dev/null";
if ($?) { die "`nix --install' failed"; }
open NIDS, "nix --query --list @nids |" or die "cannot run nix";
while (<NIDS>) {
chomp;
die unless (/^\//);
print "$_\n";
push @pushlist, "$_/*";
}
# Push the prebuilts to the server. !!! FIXME
if (scalar @pushlist > 0) {
system "rsync -av -e ssh @pushlist eelco\@losser.st-lab.cs.uu.nl:/home/eelco/public_html/nix-dist/";

View File

@@ -4,11 +4,15 @@ use strict;
my $keep = 0;
my $sourceroot = 0;
my $name = "current";
my $srcid;
foreach my $arg (@ARGV) {
my $argnr = 0;
while ($argnr < scalar @ARGV) {
my $arg = $ARGV[$argnr++];
if ($arg eq "--keep") { $keep = 1; }
elsif ($arg eq "--source-root") { $sourceroot = 1; }
elsif ($arg eq "--name") { $name = $ARGV[$argnr++]; }
elsif ($arg =~ /^([0-9a-z]{32})$/) { $srcid = $arg; }
else { die "unknown argument `$arg'" };
}
@@ -54,7 +58,7 @@ if ($sourceroot) {
close ID;
}
my $current = "$linkdir/current";
my $current = "$linkdir/$name";
# Read the current generation so that we can delete it (if --keep
# wasn't specified).
@@ -70,7 +74,7 @@ my $oldlink = readlink($current);
print "switching $current to $link\n";
my $tmplink = "$linkdir/new_current";
my $tmplink = "$linkdir/.new_$name";
symlink($link, $tmplink) or die "cannot create $tmplink";
rename($tmplink, $current) or die "cannot rename $tmplink";

View File

@@ -22,7 +22,7 @@ noinst_LIBRARIES = libnix.a libshared.a
libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c \
store.cc fstate.cc normalise.cc exec.cc \
globals.cc db.cc references.cc
globals.cc db.cc references.cc pathlocks.cc
libshared_a_SOURCES = shared.cc
@@ -42,7 +42,9 @@ nix.o: nix-help.txt.hh
install-data-local:
$(INSTALL) -d $(localstatedir)/nix
$(INSTALL) -d $(localstatedir)/nix/db
$(INSTALL) -d $(localstatedir)/nix/links
rm -f $(prefix)/current
ln -sf $(localstatedir)/nix/links/current $(prefix)/current
$(INSTALL) -d $(localstatedir)/log/nix
$(INSTALL) -d $(prefix)/store

209
src/db.cc
View File

@@ -3,66 +3,172 @@
#include <memory>
#include <db_cxx.h>
/* Wrapper classes that ensures that the database is closed upon
object destruction. */
class Db2 : public Db
/* Wrapper class to ensure proper destruction. */
class DestroyDbc
{
Dbc * dbc;
public:
Db2(DbEnv *env, u_int32_t flags) : Db(env, flags) { }
~Db2() { close(0); }
DestroyDbc(Dbc * _dbc) : dbc(_dbc) { }
~DestroyDbc() { dbc->close(); /* close() frees dbc */ }
};
class DbcClose
{
Dbc * cursor;
public:
DbcClose(Dbc * c) : cursor(c) { }
~DbcClose() { cursor->close(); }
};
static auto_ptr<Db2> openDB(const string & filename, const string & dbname,
bool readonly)
{
auto_ptr<Db2> db(new Db2(0, 0));
db->open(filename.c_str(), dbname.c_str(),
DB_HASH, readonly ? DB_RDONLY : DB_CREATE, 0666);
return db;
}
static void rethrow(DbException & e)
{
throw Error(e.what());
}
void createDB(const string & filename, const string & dbname)
Transaction::Transaction()
: txn(0)
{
}
Transaction::Transaction(Database & db)
{
db.requireEnv();
try {
openDB(filename, dbname, false);
db.env->txn_begin(0, &txn, 0);
} catch (DbException e) { rethrow(e); }
}
bool queryDB(const string & filename, const string & dbname,
Transaction::~Transaction()
{
if (txn) abort();
}
void Transaction::commit()
{
if (!txn) throw Error("commit called on null transaction");
debug(format("committing transaction %1%") % (void *) txn);
DbTxn * txn2 = txn;
txn = 0;
try {
txn2->commit(0);
} catch (DbException e) { rethrow(e); }
}
void Transaction::abort()
{
if (!txn) throw Error("abort called on null transaction");
debug(format("aborting transaction %1%") % (void *) txn);
DbTxn * txn2 = txn;
txn = 0;
try {
txn2->abort();
} catch (DbException e) { rethrow(e); }
}
void Database::requireEnv()
{
if (!env) throw Error("database environment not open");
}
Db * Database::getDb(TableId table)
{
map<TableId, Db *>::iterator i = tables.find(table);
if (i == tables.end())
throw Error("unknown table id");
return i->second;
}
Database::Database()
: env(0)
, nextId(1)
{
}
Database::~Database()
{
if (env) {
debug(format("closing database environment"));
try {
for (map<TableId, Db *>::iterator i = tables.begin();
i != tables.end(); i++)
{
debug(format("closing table %1%") % i->first);
Db * db = i->second;
db->close(0);
delete db;
}
env->txn_checkpoint(0, 0, 0);
env->close(0);
} catch (DbException e) { rethrow(e); }
delete env;
}
}
void Database::open(const string & path)
{
try {
if (env) throw Error(format("environment already open"));
env = new DbEnv(0);
env->set_lg_bsize(32 * 1024); /* default */
env->set_lg_max(256 * 1024); /* must be > 4 * lg_bsize */
env->set_lk_detect(DB_LOCK_DEFAULT);
env->open(path.c_str(),
DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN |
DB_CREATE,
0666);
} catch (DbException e) { rethrow(e); }
}
TableId Database::openTable(const string & tableName)
{
requireEnv();
TableId table = nextId++;
try {
Db * db = new Db(env, 0);
try {
// !!! fixme when switching to BDB 4.1: use txn.
db->open(tableName.c_str(), 0, DB_HASH, DB_CREATE, 0666);
} catch (...) {
delete db;
throw;
}
tables[table] = db;
} catch (DbException e) { rethrow(e); }
return table;
}
bool Database::queryString(const Transaction & txn, TableId table,
const string & key, string & data)
{
try {
int err;
auto_ptr<Db2> db = openDB(filename, dbname, true);
Db * db = getDb(table);
Dbt kt((void *) key.c_str(), key.length());
Dbt dt;
err = db->get(0, &kt, &dt, 0);
int err = db->get(txn.txn, &kt, &dt, 0);
if (err) return false;
if (!dt.get_data())
@@ -76,12 +182,12 @@ bool queryDB(const string & filename, const string & dbname,
}
bool queryListDB(const string & filename, const string & dbname,
bool Database::queryStrings(const Transaction & txn, TableId table,
const string & key, Strings & data)
{
string d;
if (!queryDB(filename, dbname, key, d))
if (!queryString(txn, table, key, d))
return false;
string::iterator it = d.begin();
@@ -110,19 +216,19 @@ bool queryListDB(const string & filename, const string & dbname,
}
void setDB(const string & filename, const string & dbname,
void Database::setString(const Transaction & txn, TableId table,
const string & key, const string & data)
{
try {
auto_ptr<Db2> db = openDB(filename, dbname, false);
Db * db = getDb(table);
Dbt kt((void *) key.c_str(), key.length());
Dbt dt((void *) data.c_str(), data.length());
db->put(0, &kt, &dt, 0);
db->put(txn.txn, &kt, &dt, 0);
} catch (DbException e) { rethrow(e); }
}
void setListDB(const string & filename, const string & dbname,
void Database::setStrings(const Transaction & txn, TableId table,
const string & key, const Strings & data)
{
string d;
@@ -141,34 +247,33 @@ void setListDB(const string & filename, const string & dbname,
d += s;
}
setDB(filename, dbname, key, d);
setString(txn, table, key, d);
}
void delDB(const string & filename, const string & dbname,
void Database::delPair(const Transaction & txn, TableId table,
const string & key)
{
try {
auto_ptr<Db2> db = openDB(filename, dbname, false);
Db * db = getDb(table);
Dbt kt((void *) key.c_str(), key.length());
db->del(0, &kt, 0);
db->del(txn.txn, &kt, 0);
} catch (DbException e) { rethrow(e); }
}
void enumDB(const string & filename, const string & dbname,
void Database::enumTable(const Transaction & txn, TableId table,
Strings & keys)
{
try {
Db * db = getDb(table);
auto_ptr<Db2> db = openDB(filename, dbname, true);
Dbc * cursor;
db->cursor(0, &cursor, 0);
DbcClose cursorCloser(cursor);
Dbc * dbc;
db->cursor(txn.txn, &dbc, 0);
DestroyDbc destroyDbc(dbc);
Dbt kt, dt;
while (cursor->get(&kt, &dt, DB_NEXT) != DB_NOTFOUND)
while (dbc->get(&kt, &dt, DB_NEXT) != DB_NOTFOUND)
keys.push_back(
string((char *) kt.get_data(), kt.get_size()));

View File

@@ -3,29 +3,81 @@
#include <string>
#include <list>
#include <map>
#include <db_cxx.h>
#include "util.hh"
using namespace std;
void createDB(const string & filename, const string & dbname);
bool queryDB(const string & filename, const string & dbname,
const string & key, string & data);
class Database;
bool queryListDB(const string & filename, const string & dbname,
const string & key, Strings & data);
void setDB(const string & filename, const string & dbname,
const string & key, const string & data);
class Transaction
{
friend class Database;
void setListDB(const string & filename, const string & dbname,
const string & key, const Strings & data);
private:
DbTxn * txn;
public:
Transaction();
Transaction(Database & _db);
~Transaction();
void delDB(const string & filename, const string & dbname,
const string & key);
void abort();
void commit();
};
#define noTxn Transaction()
typedef unsigned int TableId; /* table handles */
class Database
{
friend class Transaction;
private:
DbEnv * env;
TableId nextId;
map<TableId, Db *> tables;
void requireEnv();
Db * getDb(TableId table);
public:
Database();
~Database();
void open(const string & path);
TableId openTable(const string & table);
bool queryString(const Transaction & txn, TableId table,
const string & key, string & data);
bool queryStrings(const Transaction & txn, TableId table,
const string & key, Strings & data);
void setString(const Transaction & txn, TableId table,
const string & key, const string & data);
void setStrings(const Transaction & txn, TableId table,
const string & key, const Strings & data);
void delPair(const Transaction & txn, TableId table,
const string & key);
void enumTable(const Transaction & txn, TableId table,
Strings & keys);
};
void enumDB(const string & filename, const string & dbname,
Strings & keys);
#endif /* !__DB_H */

View File

@@ -34,8 +34,12 @@ public:
};
static string pathNullDevice = "/dev/null";
/* Run a program. */
void runProgram(const string & program, Environment env)
void runProgram(const string & program,
const Strings & args, const Environment & env)
{
/* Create a log file. */
string logFileName = nixLogDir + "/run.log";
@@ -68,15 +72,25 @@ void runProgram(const string & program, Environment env)
if (chdir(tmpDir.c_str()) == -1)
throw SysError(format("changing into to `%1%'") % tmpDir);
/* Fill in the environment. We don't bother freeing
the strings, since we'll exec or die soon
anyway. */
const char * env2[env.size() + 1];
int i = 0;
for (Environment::iterator it = env.begin();
it != env.end(); it++, i++)
env2[i] = (new string(it->first + "=" + it->second))->c_str();
env2[i] = 0;
/* Fill in the arguments. */
const char * argArr[args.size() + 2];
const char * * p = argArr;
string progName = baseNameOf(program);
*p++ = progName.c_str();
for (Strings::const_iterator i = args.begin();
i != args.end(); i++)
*p++ = i->c_str();
*p = 0;
/* Fill in the environment. */
Strings envStrs;
const char * envArr[env.size() + 1];
p = envArr;
for (Environment::const_iterator i = env.begin();
i != env.end(); i++)
*p++ = envStrs.insert(envStrs.end(),
i->first + "=" + i->second)->c_str();
*p = 0;
/* Dup the log handle into stderr. */
if (dup2(fileno(logFile), STDERR_FILENO) == -1)
@@ -86,8 +100,15 @@ void runProgram(const string & program, Environment env)
if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1)
throw SysError("cannot dup stderr into stdout");
/* Reroute stdin to /dev/null. */
int fdDevNull = open(pathNullDevice.c_str(), O_RDWR);
if (fdDevNull == -1)
throw SysError(format("cannot open `%1%'") % pathNullDevice);
if (dup2(fdDevNull, STDIN_FILENO) == -1)
throw SysError("cannot dup null device into stdin");
/* Execute the program. This should not return. */
execle(program.c_str(), baseNameOf(program).c_str(), 0, env2);
execve(program.c_str(), (char * *) argArr, (char * *) envArr);
throw SysError(format("unable to execute %1%") % program);
@@ -111,7 +132,11 @@ void runProgram(const string & program, Environment env)
throw Error("unable to wait for child");
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
delTmpDir.cancel();
if (keepFailed) {
msg(lvlTalkative,
format("build failed; keeping build directory `%1%'") % tmpDir);
delTmpDir.cancel();
}
throw Error("unable to build package");
}
}

View File

@@ -4,6 +4,8 @@
#include <string>
#include <map>
#include "util.hh"
using namespace std;
@@ -12,7 +14,8 @@ typedef map<string, string> Environment;
/* Run a program. */
void runProgram(const string & program, Environment env);
void runProgram(const string & program,
const Strings & args, const Environment & env);
#endif /* !__EXEC_H */

View File

@@ -9,13 +9,22 @@
typedef ATerm Expr;
typedef map<ATerm, ATerm> NormalForms;
typedef map<FSId, Strings> PkgPaths;
typedef map<FSId, Hash> PkgHashes;
struct EvalState
{
Strings searchDirs;
NormalForms normalForms;
PkgPaths pkgPaths;
PkgHashes pkgHashes; /* normalised package hashes */
Expr blackHole;
EvalState()
{
blackHole = ATmake("BlackHole()");
if (!blackHole) throw Error("cannot build black hole");
}
};
@@ -51,10 +60,16 @@ static Expr substExpr(string x, Expr rep, Expr e)
else
return e;
if (ATmatch(e, "Lam(<str>, <term>)", &s, &e2))
if (x == s)
return e;
/* !!! unfair substitutions */
ATermList formals;
if (ATmatch(e, "Function([<list>], <term>)", &formals, &e2)) {
while (!ATisEmpty(formals)) {
if (!ATmatch(ATgetFirst(formals), "<str>", &s))
throw badTerm("not a list of formals", (ATerm) formals);
if (x == (string) s)
return e;
formals = ATgetNext(formals);
}
}
/* Generically substitute in subterms. */
@@ -106,7 +121,20 @@ static Expr substExprMany(ATermList formals, ATermList args, Expr body)
}
Hash hashPackage(EvalState & state, FState fs)
static Strings fstatePathsCached(EvalState & state, const FSId & id)
{
PkgPaths::iterator i = state.pkgPaths.find(id);
if (i != state.pkgPaths.end())
return i->second;
else {
Strings paths = fstatePaths(id);
state.pkgPaths[id] = paths;
return paths;
}
}
static Hash hashPackage(EvalState & state, FState fs)
{
if (fs.type == FState::fsDerive) {
for (FSIds::iterator i = fs.derive.inputs.begin();
@@ -122,6 +150,42 @@ Hash hashPackage(EvalState & state, FState fs)
}
static string processBinding(EvalState & state, Expr e, FState & fs)
{
char * s1;
if (ATmatch(e, "FSId(<str>)", &s1)) {
FSId id = parseHash(s1);
Strings paths = fstatePathsCached(state, id);
if (paths.size() != 1) abort();
string path = *(paths.begin());
fs.derive.inputs.push_back(id);
return path;
}
if (ATmatch(e, "<str>", &s1))
return s1;
if (ATmatch(e, "True")) return "1";
if (ATmatch(e, "False")) return "";
ATermList l;
if (ATmatch(e, "[<list>]", &l)) {
string s;
bool first = true;
while (!ATisEmpty(l)) {
if (!first) s = s + " "; else first = false;
s += processBinding(state, evalExpr(state, ATgetFirst(l)), fs);
l = ATgetNext(l);
}
return s;
}
throw badTerm("invalid package binding", e);
}
static Expr evalExpr2(EvalState & state, Expr e)
{
char * s1;
@@ -130,6 +194,9 @@ static Expr evalExpr2(EvalState & state, Expr e)
/* Normal forms. */
if (ATmatch(e, "<str>", &s1) ||
ATmatch(e, "[<list>]", &e1) ||
ATmatch(e, "True") ||
ATmatch(e, "False") ||
ATmatch(e, "Function([<list>], <term>)", &e1, &e2) ||
ATmatch(e, "FSId(<str>)", &s1))
return e;
@@ -143,7 +210,8 @@ static Expr evalExpr2(EvalState & state, Expr e)
}
/* Application. */
if (ATmatch(e, "App(<term>, [<list>])", &e1, &e2)) {
if (ATmatch(e, "Call(<term>, [<list>])", &e1, &e2) ||
ATmatch(e, "App(<term>, [<list>])", &e1, &e2)) {
e1 = evalExpr(state, e1);
if (!ATmatch(e1, "Function([<list>], <term>)", &e3, &e4))
throw badTerm("expecting a function", e1);
@@ -151,6 +219,37 @@ static Expr evalExpr2(EvalState & state, Expr e)
substExprMany((ATermList) e3, (ATermList) e2, e4));
}
/* Conditional. */
if (ATmatch(e, "If(<term>, <term>, <term>)", &e1, &e2, &e3)) {
e1 = evalExpr(state, e1);
Expr x;
if (ATmatch(e1, "True")) x = e2;
else if (ATmatch(e1, "False")) x = e3;
else throw badTerm("expecting a boolean", e1);
return evalExpr(state, x);
}
/* Ad-hoc function for string matching. */
if (ATmatch(e, "HasSubstr(<term>, <term>)", &e1, &e2)) {
e1 = evalExpr(state, e1);
e2 = evalExpr(state, e2);
char * s1, * s2;
if (!ATmatch(e1, "<str>", &s1))
throw badTerm("expecting a string", e1);
if (!ATmatch(e2, "<str>", &s2))
throw badTerm("expecting a string", e2);
return
string(s1).find(string(s2)) != string::npos ?
ATmake("True") : ATmake("False");
}
/* Platform constant. */
if (ATmatch(e, "Platform")) {
return ATmake("<str>", SYSTEM);
}
/* Fix inclusion. */
if (ATmatch(e, "IncludeFix(<str>)", &s1)) {
string fileName(s1);
@@ -211,24 +310,29 @@ static Expr evalExpr2(EvalState & state, Expr e)
string key = it->first;
ATerm value = it->second;
if (ATmatch(value, "FSId(<str>)", &s1)) {
FSId id = parseHash(s1);
Strings paths = fstatePaths(id);
if (paths.size() != 1) abort();
string path = *(paths.begin());
fs.derive.inputs.push_back(id);
fs.derive.env.push_back(StringPair(key, path));
if (key == "build") fs.derive.builder = path;
}
else if (ATmatch(value, "<str>", &s1)) {
if (key == "name") name = s1;
if (key == "args") {
ATermList args;
if (!ATmatch(value, "[<list>]", &args))
throw badTerm("list expected", value);
while (!ATisEmpty(args)) {
Expr arg = evalExpr(state, ATgetFirst(args));
fs.derive.args.push_back(processBinding(state, arg, fs));
args = ATgetNext(args);
}
}
else {
string s = processBinding(state, value, fs);
fs.derive.env.push_back(StringPair(key, s));
if (key == "build") fs.derive.builder = s;
if (key == "name") name = s;
if (key == "id") {
outId = parseHash(s1);
outId = parseHash(s);
outIdGiven = true;
}
fs.derive.env.push_back(StringPair(key, s1));
}
else throw badTerm("invalid package argument", value);
bnds = ATinsert(bnds,
ATmake("(<str>, <term>)", key.c_str(), value));
@@ -276,12 +380,19 @@ static Expr evalExpr2(EvalState & state, Expr e)
static Expr evalExpr(EvalState & state, Expr e)
{
Nest nest(lvlVomit, format("evaluating expression: %1%") % printTerm(e));
/* Consult the memo table to quickly get the normal form of
previously evaluated expressions. */
NormalForms::iterator i = state.normalForms.find(e);
if (i != state.normalForms.end()) return i->second;
if (i != state.normalForms.end()) {
if (i->second == state.blackHole)
throw badTerm("infinite recursion", e);
return i->second;
}
/* Otherwise, evaluate and memoize. */
state.normalForms[e] = state.blackHole;
Expr nf = evalExpr2(state, e);
state.normalForms[e] = nf;
return nf;
@@ -299,10 +410,40 @@ static Expr evalFile(EvalState & state, string relPath)
}
static Expr evalStdin(EvalState & state)
{
Nest nest(lvlTalkative, format("evaluating standard input"));
Expr e = ATreadFromFile(stdin);
if (!e)
throw Error(format("unable to read a term from stdin"));
return evalExpr(state, e);
}
static void printFSId(EvalState & state, Expr e)
{
ATermList es;
char * s;
if (ATmatch(e, "FSId(<str>)", &s)) {
cout << format("%1%\n") % s;
}
else if (ATmatch(e, "[<list>]", &es)) {
while (!ATisEmpty(es)) {
printFSId(state, evalExpr(state, ATgetFirst(es)));
es = ATgetNext(es);
}
}
else throw badTerm("top level does not evaluate to a (list of) Nix expression(s)", e);
}
void run(Strings args)
{
openDB();
EvalState state;
Strings files;
bool readStdin = false;
state.searchDirs.push_back(".");
state.searchDirs.push_back(nixDataDir + "/fix");
@@ -319,23 +460,24 @@ void run(Strings args)
}
else if (arg == "--verbose" || arg == "-v")
verbosity = (Verbosity) ((int) verbosity + 1);
else if (arg == "-")
readStdin = true;
else if (arg[0] == '-')
throw UsageError(format("unknown flag `%1%`") % arg);
else
files.push_back(arg);
}
if (files.empty()) throw UsageError("no files specified");
if (readStdin) {
Expr e = evalStdin(state);
printFSId(state, e);
}
for (Strings::iterator it = files.begin();
it != files.end(); it++)
{
Expr e = evalFile(state, *it);
char * s;
if (ATmatch(e, "FSId(<str>)", &s)) {
cout << format("%1%\n") % s;
}
else throw badTerm("top level is not a package", e);
printFSId(state, e);
}
}

View File

@@ -44,7 +44,10 @@ FSId writeTerm(ATerm t, const string & suffix, FSId id)
// debug(format("written term %1% = %2%") % (string) id %
// printTerm(t));
registerPath(path, id);
Transaction txn(nixDB);
registerPath(txn, path, id);
txn.commit();
return id;
}
@@ -117,13 +120,19 @@ static bool parseSlice(ATerm t, Slice & slice)
static bool parseDerive(ATerm t, Derive & derive)
{
ATermList outs, ins, bnds;
ATermList outs, ins, args, bnds;
char * builder;
char * platform;
if (!ATmatch(t, "Derive([<list>], [<list>], <str>, <str>, [<list>])",
&outs, &ins, &builder, &platform, &bnds))
return false;
if (!ATmatch(t, "Derive([<list>], [<list>], <str>, <str>, [<list>], [<list>])",
&outs, &ins, &platform, &builder, &args, &bnds))
{
/* !!! compatibility -> remove eventually */
if (!ATmatch(t, "Derive([<list>], [<list>], <str>, <str>, [<list>])",
&outs, &ins, &builder, &platform, &bnds))
return false;
args = ATempty;
}
while (!ATisEmpty(outs)) {
char * s1, * s2;
@@ -139,6 +148,15 @@ static bool parseDerive(ATerm t, Derive & derive)
derive.builder = builder;
derive.platform = platform;
while (!ATisEmpty(args)) {
char * s;
ATerm arg = ATgetFirst(args);
if (!ATmatch(arg, "<str>", &s))
throw badTerm("string expected", arg);
derive.args.push_back(s);
args = ATgetNext(args);
}
while (!ATisEmpty(bnds)) {
char * s1, * s2;
ATerm bnd = ATgetFirst(bnds);
@@ -201,6 +219,11 @@ static ATerm unparseDerive(const Derive & derive)
ATmake("(<str>, <str>)",
i->first.c_str(), ((string) i->second).c_str()));
ATermList args = ATempty;
for (Strings::const_iterator i = derive.args.begin();
i != derive.args.end(); i++)
args = ATinsert(args, ATmake("<str>", i->c_str()));
ATermList env = ATempty;
for (StringPairs::const_iterator i = derive.env.begin();
i != derive.env.end(); i++)
@@ -208,11 +231,12 @@ static ATerm unparseDerive(const Derive & derive)
ATmake("(<str>, <str>)",
i->first.c_str(), i->second.c_str()));
return ATmake("Derive(<term>, <term>, <str>, <str>, <term>)",
return ATmake("Derive(<term>, <term>, <str>, <str>, <term>, <term>)",
ATreverse(outs),
unparseIds(derive.inputs),
derive.builder.c_str(),
derive.platform.c_str(),
derive.builder.c_str(),
ATreverse(args),
ATreverse(env));
}

View File

@@ -36,8 +36,9 @@ struct Derive
{
DeriveOutputs outputs;
FSIds inputs;
string builder;
string platform;
string builder;
Strings args;
StringPairs env;
};

View File

@@ -2,22 +2,34 @@
#include "db.hh"
string dbPath2Id = "path2id";
string dbId2Paths = "id2paths";
string dbSuccessors = "successors";
string dbSubstitutes = "substitutes";
Database nixDB;
TableId dbPath2Id;
TableId dbId2Paths;
TableId dbSuccessors;
TableId dbSubstitutes;
string nixStore = "/UNINIT";
string nixDataDir = "/UNINIT";
string nixLogDir = "/UNINIT";
string nixDB = "/UNINIT";
string nixDBPath = "/UNINIT";
bool keepFailed = false;
void openDB()
{
nixDB.open(nixDBPath);
dbPath2Id = nixDB.openTable("path2id");
dbId2Paths = nixDB.openTable("id2paths");
dbSuccessors = nixDB.openTable("successors");
dbSubstitutes = nixDB.openTable("substitutes");
}
void initDB()
{
createDB(nixDB, dbPath2Id);
createDB(nixDB, dbId2Paths);
createDB(nixDB, dbSuccessors);
createDB(nixDB, dbSubstitutes);
}

View File

@@ -3,22 +3,27 @@
#include <string>
#include "db.hh"
using namespace std;
/* Database names. */
extern Database nixDB;
/* Database tables. */
/* dbPath2Id :: Path -> FSId
Each pair (p, id) records that path $p$ contains an expansion of
$id$. */
extern string dbPath2Id;
extern TableId dbPath2Id;
/* dbId2Paths :: FSId -> [Path]
A mapping from ids to lists of paths. */
extern string dbId2Paths;
extern TableId dbId2Paths;
/* dbSuccessors :: FSId -> FSId
@@ -30,7 +35,7 @@ extern string dbId2Paths;
Note that a term $y$ is successor of $x$ iff there exists a
sequence of rewrite steps that rewrites $x$ into $y$.
*/
extern string dbSuccessors;
extern TableId dbSuccessors;
/* dbSubstitutes :: FSId -> [FSId]
@@ -46,7 +51,7 @@ extern string dbSuccessors;
this case might be an fstate expression that fetches the Nix
archive.
*/
extern string dbSubstitutes;
extern TableId dbSubstitutes;
/* Path names. */
@@ -60,12 +65,20 @@ extern string nixDataDir; /* !!! fix */
/* nixLogDir is the directory where we log various operations. */
extern string nixLogDir;
/* nixDB is the file name of the Berkeley DB database where we
maintain the dbXXX mappings. */
extern string nixDB;
/* nixDBPath is the path name of our Berkeley DB environment. */
extern string nixDBPath;
/* Initialize the databases. */
/* Misc. global flags. */
/* Whether to keep temporary directories of failed builds. */
extern bool keepFailed;
/* Open the database environment. */
void openDB();
/* Create the required database tables. */
void initDB();

View File

@@ -6,9 +6,13 @@
void run(Strings args)
{
for (Strings::iterator it = args.begin();
it != args.end(); it++)
cout << format("%1%\n") % (string) hashPath(*it);
bool flat = false;
for (Strings::iterator i = args.begin();
i != args.end(); i++)
if (*i == "--flat") flat = true;
else
cout << format("%1%\n") % (string)
(flat ? hashFile(*i) : hashPath(*i));
}

View File

@@ -34,3 +34,4 @@ Query flags:
Options:
--verbose / -v: verbose operation (may be repeated)
--keep-failed / -K: keep temporary directories of failed builds

View File

@@ -239,14 +239,16 @@ static void opSuccessor(Strings opFlags, Strings opArgs)
{
if (!opFlags.empty()) throw UsageError("unknown flag");
if (opArgs.size() % 2) throw UsageError("expecting even number of arguments");
Transaction txn(nixDB); /* !!! this could be a big transaction */
for (Strings::iterator i = opArgs.begin();
i != opArgs.end(); )
{
FSId id1 = parseHash(*i++);
FSId id2 = parseHash(*i++);
registerSuccessor(id1, id2);
registerSuccessor(txn, id1, id2);
}
txn.commit();
}
@@ -335,6 +337,8 @@ static void opVerify(Strings opFlags, Strings opArgs)
list. */
void run(Strings args)
{
openDB();
Strings opFlags, opArgs;
Operation op = 0;
@@ -368,6 +372,8 @@ void run(Strings args)
pathArgs = true;
else if (arg == "--verbose" || arg == "-v")
verbosity = (Verbosity) ((int) verbosity + 1);
else if (arg == "--keep-failed" || arg == "-K")
keepFailed = true;
else if (arg == "--help")
printHelp();
else if (arg[0] == '-')

View File

@@ -4,24 +4,40 @@
#include "references.hh"
#include "db.hh"
#include "exec.hh"
#include "pathlocks.hh"
#include "globals.hh"
void registerSuccessor(const FSId & id1, const FSId & id2)
void registerSuccessor(const Transaction & txn,
const FSId & id1, const FSId & id2)
{
setDB(nixDB, dbSuccessors, id1, id2);
nixDB.setString(txn, dbSuccessors, id1, id2);
}
static FSId storeSuccessor(const FSId & id1, ATerm sc)
static FSId useSuccessor(const FSId & id)
{
FSId id2 = writeTerm(sc, "-s-" + (string) id1);
registerSuccessor(id1, id2);
return id2;
string idSucc;
if (nixDB.queryString(noTxn, dbSuccessors, id, idSucc)) {
debug(format("successor %1% -> %2%") % (string) id % idSucc);
return parseHash(idSucc);
} else
return id;
}
typedef set<FSId> FSIdSet;
typedef map<string, FSId> OutPaths;
typedef map<string, SliceElem> ElemMap;
Strings pathsFromOutPaths(const OutPaths & ps)
{
Strings ss;
for (OutPaths::const_iterator i = ps.begin();
i != ps.end(); i++)
ss.push_back(i->first);
return ss;
}
FSId normaliseFState(FSId id, FSIdSet pending)
@@ -30,19 +46,66 @@ FSId normaliseFState(FSId id, FSIdSet pending)
/* Try to substitute $id$ by any known successors in order to
speed up the rewrite process. */
string idSucc;
while (queryDB(nixDB, dbSuccessors, id, idSucc)) {
debug(format("successor %1% -> %2%") % (string) id % idSucc);
id = parseHash(idSucc);
}
id = useSuccessor(id);
/* Get the fstate expression. */
FState fs = parseFState(termFromId(id));
/* It this is a normal form (i.e., a slice) we are done. */
/* If this is a normal form (i.e., a slice) we are done. */
if (fs.type == FState::fsSlice) return id;
if (fs.type != FState::fsDerive) abort();
/* Otherwise, it's a derivation. */
/* Otherwise, it's a derive expression, and we have to build it to
determine its normal form. */
/* Some variables. */
/* Output paths, with their ids. */
OutPaths outPaths;
/* Input paths, with their slice elements. */
ElemMap inMap;
/* Referencable paths (i.e., input and output paths). */
Strings allPaths;
/* The environment to be passed to the builder. */
Environment env;
/* Parse the outputs. */
for (DeriveOutputs::iterator i = fs.derive.outputs.begin();
i != fs.derive.outputs.end(); i++)
{
debug(format("building %1% in `%2%'") % (string) i->second % i->first);
outPaths[i->first] = i->second;
allPaths.push_back(i->first);
}
/* Obtain locks on all output paths. The locks are automatically
released when we exit this function or Nix crashes. */
PathLocks outputLocks(pathsFromOutPaths(outPaths));
/* Now check again whether there is a successor. This is because
another process may have started building in parallel. After
it has finished and released the locks, we can (and should)
reuse its results. (Strictly speaking the first successor
check above can be omitted, but that would be less efficient.)
Note that since we now hold the locks on the output paths, no
other process can build this expression, so no further checks
are necessary. */
{
FSId id2 = useSuccessor(id);
if (id2 != id) {
FState fs = parseFState(termFromId(id2));
debug(format("skipping build of %1%, someone beat us to it")
% (string) id);
if (fs.type != FState::fsSlice) abort();
return id2;
}
}
/* Right platform? */
if (fs.derive.platform != thisSystem)
@@ -50,14 +113,12 @@ FSId normaliseFState(FSId id, FSIdSet pending)
% fs.derive.platform % thisSystem);
/* Realise inputs (and remember all input paths). */
typedef map<string, SliceElem> ElemMap;
ElemMap inMap;
for (FSIds::iterator i = fs.derive.inputs.begin();
i != fs.derive.inputs.end(); i++) {
FSId nf = normaliseFState(*i, pending);
realiseSlice(nf, pending);
/* !!! nf should be a root of the garbage collector while we
are building */
FState fs = parseFState(termFromId(nf));
if (fs.type != FState::fsSlice) abort();
for (SliceElems::iterator j = fs.slice.elems.begin();
@@ -65,38 +126,30 @@ FSId normaliseFState(FSId id, FSIdSet pending)
inMap[j->path] = *j;
}
Strings inPaths;
for (ElemMap::iterator i = inMap.begin(); i != inMap.end(); i++)
inPaths.push_back(i->second.path);
allPaths.push_back(i->second.path);
/* Most shells initialise PATH to some default (/bin:/usr/bin:...) when
PATH is not set. We don't want this, so we fill it in with some dummy
value. */
env["PATH"] = "/path-not-set";
/* Build the environment. */
Environment env;
for (StringPairs::iterator i = fs.derive.env.begin();
i != fs.derive.env.end(); i++)
env[i->first] = i->second;
/* Parse the outputs. */
typedef map<string, FSId> OutPaths;
OutPaths outPaths;
for (DeriveOutputs::iterator i = fs.derive.outputs.begin();
i != fs.derive.outputs.end(); i++)
{
debug(format("building %1% in `%2%'") % (string) i->second % i->first);
outPaths[i->first] = i->second;
inPaths.push_back(i->first);
}
/* We can skip running the builder if we can expand all output
paths from their ids. */
bool fastBuild = true;
for (OutPaths::iterator i = outPaths.begin();
for (OutPaths::iterator i = outPaths.begin();
i != outPaths.end(); i++)
{
try {
expandId(i->second, i->first, "/", pending);
} catch (Error & e) {
debug(format("fast build failed for `%1%': %2%")
% i->first % e.what());
% i->first % e.what());
fastBuild = false;
break;
}
@@ -104,73 +157,139 @@ FSId normaliseFState(FSId id, FSIdSet pending)
if (!fastBuild) {
/* Check that none of the outputs exist. */
/* If any of the outputs already exist but are not registered,
delete them. */
for (OutPaths::iterator i = outPaths.begin();
i != outPaths.end(); i++)
if (pathExists(i->first))
throw Error(format("path `%1%' exists") % i->first);
{
string path = i->first;
FSId id;
if (queryPathId(path, id))
throw Error(format("obstructed build: path `%1%' exists") % path);
if (pathExists(path)) {
debug(format("removing unregistered path `%1%'") % path);
deletePath(path);
}
}
/* Run the builder. */
msg(lvlChatty, format("building..."));
runProgram(fs.derive.builder, env);
runProgram(fs.derive.builder, fs.derive.args, env);
msg(lvlChatty, format("build completed"));
} else
msg(lvlChatty, format("fast build succesful"));
/* Check whether the output paths were created, and register each
one. */
FSIdSet used;
/* Check whether the output paths were created, and grep each
output path to determine what other paths it references. */
StringSet usedPaths;
for (OutPaths::iterator i = outPaths.begin();
i != outPaths.end(); i++)
{
string path = i->first;
if (!pathExists(path))
throw Error(format("path `%1%' does not exist") % path);
registerPath(path, i->second);
fs.slice.roots.push_back(i->second);
Strings refs = filterReferences(path, inPaths);
/* For this output path, find the references to other paths contained
in it. */
Strings refPaths = filterReferences(path, allPaths);
/* Construct a slice element for this output path. */
SliceElem elem;
elem.path = path;
elem.id = i->second;
for (Strings::iterator j = refs.begin(); j != refs.end(); j++) {
/* For each path referenced by this output path, add its id to the
slice element and add the id to the `used' set (so that the
elements referenced by *its* slice are added below). */
for (Strings::iterator j = refPaths.begin();
j != refPaths.end(); j++)
{
string path = *j;
ElemMap::iterator k;
OutPaths::iterator l;
if ((k = inMap.find(*j)) != inMap.end()) {
/* Is it an input path? */
if ((k = inMap.find(path)) != inMap.end()) {
elem.refs.push_back(k->second.id);
used.insert(k->second.id);
for (FSIds::iterator m = k->second.refs.begin();
m != k->second.refs.end(); m++)
used.insert(*m);
} else if ((l = outPaths.find(*j)) != outPaths.end()) {
usedPaths.insert(k->second.path);
}
/* Or an output path? */
else if ((l = outPaths.find(path)) != outPaths.end())
elem.refs.push_back(l->second);
used.insert(l->second);
} else
throw Error(format("unknown referenced path `%1%'") % *j);
/* Can't happen. */
else abort();
}
fs.slice.elems.push_back(elem);
}
/* Close the slice. That is, for any referenced path, add the paths
referenced by it. */
FSIdSet donePaths;
while (!usedPaths.empty()) {
StringSet::iterator i = usedPaths.begin();
string path = *i;
usedPaths.erase(i);
ElemMap::iterator j = inMap.find(path);
if (j == inMap.end()) abort();
donePaths.insert(j->second.id);
fs.slice.elems.push_back(j->second);
for (FSIds::iterator k = j->second.refs.begin();
k != j->second.refs.end(); k++)
if (donePaths.find(*k) == donePaths.end()) {
/* !!! performance */
bool found = false;
for (ElemMap::iterator l = inMap.begin();
l != inMap.end(); l++)
if (l->second.id == *k) {
usedPaths.insert(l->first);
found = true;
}
if (!found) abort();
}
}
/* For debugging, print out the referenced and unreferenced paths. */
for (ElemMap::iterator i = inMap.begin();
i != inMap.end(); i++)
{
FSIdSet::iterator j = used.find(i->second.id);
if (j == used.end())
FSIdSet::iterator j = donePaths.find(i->second.id);
if (j == donePaths.end())
debug(format("NOT referenced: `%1%'") % i->second.path);
else {
else
debug(format("referenced: `%1%'") % i->second.path);
fs.slice.elems.push_back(i->second);
}
}
/* Write the normal form. This does not have to occur in the
transaction below because writing terms is idem-potent. */
fs.type = FState::fsSlice;
ATerm nf = unparseFState(fs);
msg(lvlVomit, format("normal form: %1%") % printTerm(nf));
return storeSuccessor(id, nf);
FSId idNF = writeTerm(nf, "-s-" + (string) id);
/* Register each outpat path, and register the normal form. This
is wrapped in one database transaction to ensure that if we
crash, either everything is registered or nothing is. This is
for recoverability: unregistered paths in the store can be
deleted arbitrarily, while registered paths can only be deleted
by running the garbage collector. */
Transaction txn(nixDB);
for (OutPaths::iterator i = outPaths.begin();
i != outPaths.end(); i++)
registerPath(txn, i->first, i->second);
registerSuccessor(txn, id, idNF);
txn.commit();
return idNF;
}
@@ -183,35 +302,10 @@ void realiseSlice(const FSId & id, FSIdSet pending)
if (fs.type != FState::fsSlice)
throw Error(format("expected slice in %1%") % (string) id);
/* Perhaps all paths already contain the right id? */
bool missing = false;
for (SliceElems::const_iterator i = fs.slice.elems.begin();
i != fs.slice.elems.end(); i++)
{
SliceElem elem = *i;
string id;
if (!queryDB(nixDB, dbPath2Id, elem.path, id)) {
if (pathExists(elem.path))
throw Error(format("path `%1%' obstructed") % elem.path);
missing = true;
break;
}
if (parseHash(id) != elem.id)
throw Error(format("path `%1%' obstructed") % elem.path);
}
if (!missing) {
debug(format("already installed"));
return;
}
/* For each element, expand its id at its path. */
for (SliceElems::const_iterator i = fs.slice.elems.begin();
i != fs.slice.elems.end(); i++)
{
SliceElem elem = *i;
debug(format("expanding %1% in `%2%'") % (string) elem.id % elem.path);
expandId(elem.id, elem.path, "/", pending);
}
}
@@ -269,7 +363,7 @@ static void fstateRequisitesSet(const FSId & id,
string idSucc;
if (includeSuccessors &&
queryDB(nixDB, dbSuccessors, id, idSucc))
nixDB.queryString(noTxn, dbSuccessors, id, idSucc))
fstateRequisitesSet(parseHash(idSucc),
includeExprs, includeSuccessors, paths);
}
@@ -293,13 +387,13 @@ FSIds findGenerators(const FSIds & _ids)
mappings, since we know that those are Nix expressions. */
Strings sucs;
enumDB(nixDB, dbSuccessors, sucs);
nixDB.enumTable(noTxn, dbSuccessors, sucs);
for (Strings::iterator i = sucs.begin();
i != sucs.end(); i++)
{
string s;
if (!queryDB(nixDB, dbSuccessors, *i, s)) continue;
if (!nixDB.queryString(noTxn, dbSuccessors, *i, s)) continue;
FSId id = parseHash(s);
FState fs;

View File

@@ -29,7 +29,8 @@ Strings fstateRequisites(const FSId & id,
FSIds findGenerators(const FSIds & ids);
/* Register a successor. */
void registerSuccessor(const FSId & id1, const FSId & id2);
void registerSuccessor(const Transaction & txn,
const FSId & id1, const FSId & id2);
#endif /* !__NORMALISE_H */

67
src/pathlocks.cc Normal file
View File

@@ -0,0 +1,67 @@
#include <fcntl.h>
#include "pathlocks.hh"
/* This enables us to check whether are not already holding a lock on
a file ourselves. POSIX locks (fcntl) suck in this respect: if we
close a descriptor, the previous lock will be closed as well. And
there is no way to query whether we already have a lock (F_GETLK
only works on locks held by other processes). */
static StringSet lockedPaths; /* !!! not thread-safe */
PathLocks::PathLocks(const Strings & _paths)
{
/* Note that `fds' is built incrementally so that the destructor
will only release those locks that we have already acquired. */
/* Sort the paths. This assures that locks are always acquired in
the same order, thus preventing deadlocks. */
Strings paths(_paths);
paths.sort();
/* Acquire the lock for each path. */
for (Strings::iterator i = paths.begin(); i != paths.end(); i++) {
string path = *i;
string lockPath = path + ".lock";
debug(format("locking path `%1%'") % path);
if (lockedPaths.find(lockPath) != lockedPaths.end()) {
debug(format("already holding lock on `%1%'") % lockPath);
continue;
}
/* Open/create the lock file. */
int fd = open(lockPath.c_str(), O_WRONLY | O_CREAT, 0666);
if (fd == -1)
throw SysError(format("opening lock file `%1%'") % lockPath);
fds.push_back(fd);
this->paths.push_back(lockPath);
/* Lock it. */
struct flock lock;
lock.l_type = F_WRLCK; /* exclusive lock */
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0; /* entire file */
while (fcntl(fd, F_SETLKW, &lock) == -1)
if (errno != EINTR)
throw SysError(format("acquiring lock on `%1%'") % lockPath);
lockedPaths.insert(lockPath);
}
}
PathLocks::~PathLocks()
{
for (list<int>::iterator i = fds.begin(); i != fds.end(); i++)
close(*i);
for (Strings::iterator i = paths.begin(); i != paths.end(); i++)
lockedPaths.erase(*i);
}

19
src/pathlocks.hh Normal file
View File

@@ -0,0 +1,19 @@
#ifndef __PATHLOCKS_H
#define __PATHLOCKS_H
#include "util.hh"
class PathLocks
{
private:
list<int> fds;
Strings paths;
public:
PathLocks(const Strings & _paths);
~PathLocks();
};
#endif /* !__PATHLOCKS_H */

View File

@@ -19,7 +19,7 @@ static void initAndRun(int argc, char * * argv)
nixStore = NIX_STORE_DIR;
nixDataDir = NIX_DATA_DIR;
nixLogDir = NIX_LOG_DIR;
nixDB = (string) NIX_STATE_DIR + "/nixstate.db";
nixDBPath = (string) NIX_STATE_DIR + "/db";
/* Put the arguments in a vector. */
Strings args;

View File

@@ -7,6 +7,7 @@
#include "globals.hh"
#include "db.hh"
#include "archive.hh"
#include "pathlocks.hh"
#include "normalise.hh"
@@ -32,6 +33,8 @@ struct CopySource : RestoreSource
void copyPath(string src, string dst)
{
debug(format("copying `%1%' to `%2%'") % src % dst);
/* Unfortunately C++ doesn't support coprocedures, so we have no
nice way to chain CopySink and CopySource together. Instead we
fork off a child to run the sink. (Fork-less platforms should
@@ -96,62 +99,73 @@ void registerSubstitute(const FSId & srcId, const FSId & subId)
/* For now, accept only one substitute per id. */
Strings subs;
subs.push_back(subId);
setListDB(nixDB, dbSubstitutes, srcId, subs);
Transaction txn(nixDB);
nixDB.setStrings(txn, dbSubstitutes, srcId, subs);
txn.commit();
}
void registerPath(const string & _path, const FSId & id)
void registerPath(const Transaction & txn,
const string & _path, const FSId & id)
{
string path(canonPath(_path));
setDB(nixDB, dbPath2Id, path, id);
debug(format("registering path `%1%' with id %2%")
% path % (string) id);
string oldId;
if (nixDB.queryString(txn, dbPath2Id, path, oldId)) {
if (id != parseHash(oldId))
throw Error(format("path `%1%' already contains id %2%")
% path % oldId);
return;
}
nixDB.setString(txn, dbPath2Id, path, id);
Strings paths;
queryListDB(nixDB, dbId2Paths, id, paths); /* non-existence = ok */
for (Strings::iterator it = paths.begin();
it != paths.end(); it++)
if (*it == path) return;
nixDB.queryStrings(txn, dbId2Paths, id, paths); /* non-existence = ok */
paths.push_back(path);
setListDB(nixDB, dbId2Paths, id, paths);
nixDB.setStrings(txn, dbId2Paths, id, paths);
}
void unregisterPath(const string & _path)
{
string path(canonPath(_path));
Transaction txn(nixDB);
debug(format("unregistering path `%1%'") % path);
string _id;
if (!queryDB(nixDB, dbPath2Id, path, _id))
if (!nixDB.queryString(txn, dbPath2Id, path, _id)) {
txn.abort();
return;
}
FSId id(parseHash(_id));
delDB(nixDB, dbPath2Id, path);
nixDB.delPair(txn, dbPath2Id, path);
/* begin transaction */
Strings paths, paths2;
queryListDB(nixDB, dbId2Paths, id, paths); /* non-existence = ok */
nixDB.queryStrings(txn, dbId2Paths, id, paths); /* non-existence = ok */
bool changed = false;
for (Strings::iterator it = paths.begin();
it != paths.end(); it++)
if (*it != path) paths2.push_back(*it); else changed = true;
if (*it != path) paths2.push_back(*it);
if (changed)
setListDB(nixDB, dbId2Paths, id, paths2);
/* end transaction */
nixDB.setStrings(txn, dbId2Paths, id, paths2);
txn.commit();
}
bool queryPathId(const string & path, FSId & id)
{
string s;
if (!queryDB(nixDB, dbPath2Id, absPath(path), s)) return false;
if (!nixDB.queryString(noTxn, dbPath2Id, absPath(path), s)) return false;
id = parseHash(s);
return true;
}
@@ -165,7 +179,7 @@ bool isInPrefix(const string & path, const string & _prefix)
string expandId(const FSId & id, const string & target,
const string & prefix, FSIdSet pending)
const string & prefix, FSIdSet pending, bool ignoreSubstitutes)
{
Nest nest(lvlDebug, format("expanding %1%") % (string) id);
@@ -174,7 +188,7 @@ string expandId(const FSId & id, const string & target,
if (!target.empty() && !isInPrefix(target, prefix))
abort();
queryListDB(nixDB, dbId2Paths, id, paths);
nixDB.queryStrings(noTxn, dbId2Paths, id, paths);
/* Pick one equal to `target'. */
if (!target.empty()) {
@@ -198,30 +212,45 @@ string expandId(const FSId & id, const string & target,
if (target.empty())
return path;
else {
/* Acquire a lock on the target path. */
Strings lockPaths;
lockPaths.push_back(target);
PathLocks outputLock(lockPaths);
/* Copy. */
copyPath(path, target);
registerPath(target, id);
/* Register the target path. */
Transaction txn(nixDB);
registerPath(txn, target, id);
txn.commit();
return target;
}
}
}
if (pending.find(id) != pending.end())
throw Error(format("id %1% already being expanded") % (string) id);
pending.insert(id);
if (!ignoreSubstitutes) {
if (pending.find(id) != pending.end())
throw Error(format("id %1% already being expanded") % (string) id);
pending.insert(id);
/* Try to realise the substitutes, but only if this id is not
already being realised by a substitute. */
Strings subs;
queryListDB(nixDB, dbSubstitutes, id, subs); /* non-existence = ok */
/* Try to realise the substitutes, but only if this id is not
already being realised by a substitute. */
Strings subs;
nixDB.queryStrings(noTxn, dbSubstitutes, id, subs); /* non-existence = ok */
for (Strings::iterator it = subs.begin(); it != subs.end(); it++) {
FSId subId = parseHash(*it);
for (Strings::iterator it = subs.begin(); it != subs.end(); it++) {
FSId subId = parseHash(*it);
debug(format("trying substitute %1%") % (string) subId);
debug(format("trying substitute %1%") % (string) subId);
realiseSlice(normaliseFState(subId, pending), pending);
realiseSlice(normaliseFState(subId, pending), pending);
return expandId(id, target, prefix, pending);
}
return expandId(id, target, prefix, pending);
}
throw Error(format("cannot expand id `%1%'") % (string) id);
@@ -231,6 +260,8 @@ string expandId(const FSId & id, const string & target,
void addToStore(string srcPath, string & dstPath, FSId & id,
bool deterministicName)
{
debug(format("adding `%1%' to the store") % srcPath);
srcPath = absPath(srcPath);
id = hashPath(srcPath);
@@ -238,14 +269,21 @@ void addToStore(string srcPath, string & dstPath, FSId & id,
dstPath = canonPath(nixStore + "/" + (string) id + "-" + baseName);
try {
/* !!! should not use the substitutes! */
dstPath = expandId(id, deterministicName ? dstPath : "", nixStore);
dstPath = expandId(id, deterministicName ? dstPath : "",
nixStore, FSIdSet(), true);
return;
} catch (...) {
}
Strings lockPaths;
lockPaths.push_back(dstPath);
PathLocks outputLock(lockPaths);
copyPath(srcPath, dstPath);
registerPath(dstPath, id);
Transaction txn(nixDB);
registerPath(txn, dstPath, id);
txn.commit();
}
@@ -263,8 +301,12 @@ void deleteFromStore(const string & path)
void verifyStore()
{
Transaction txn(nixDB);
/* !!! verify that the result is consistent */
Strings paths;
enumDB(nixDB, dbPath2Id, paths);
nixDB.enumTable(txn, dbPath2Id, paths);
for (Strings::iterator i = paths.begin();
i != paths.end(); i++)
@@ -278,10 +320,10 @@ void verifyStore()
else {
string id;
if (!queryDB(nixDB, dbPath2Id, path, id)) abort();
if (!nixDB.queryString(txn, dbPath2Id, path, id)) abort();
Strings idPaths;
queryListDB(nixDB, dbId2Paths, id, idPaths);
nixDB.queryStrings(txn, dbId2Paths, id, idPaths);
bool found = false;
for (Strings::iterator j = idPaths.begin();
@@ -298,11 +340,11 @@ void verifyStore()
debug(format("reverse mapping for path `%1%' missing") % path);
}
if (erase) delDB(nixDB, dbPath2Id, path);
if (erase) nixDB.delPair(txn, dbPath2Id, path);
}
Strings ids;
enumDB(nixDB, dbId2Paths, ids);
nixDB.enumTable(txn, dbId2Paths, ids);
for (Strings::iterator i = ids.begin();
i != ids.end(); i++)
@@ -310,13 +352,13 @@ void verifyStore()
FSId id = parseHash(*i);
Strings idPaths;
queryListDB(nixDB, dbId2Paths, id, idPaths);
nixDB.queryStrings(txn, dbId2Paths, id, idPaths);
for (Strings::iterator j = idPaths.begin();
j != idPaths.end(); )
{
string id2;
if (!queryDB(nixDB, dbPath2Id, *j, id2) ||
if (!nixDB.queryString(txn, dbPath2Id, *j, id2) ||
id != parseHash(id2)) {
debug(format("erasing path `%1%' from mapping for id %2%")
% *j % (string) id);
@@ -324,12 +366,12 @@ void verifyStore()
} else j++;
}
setListDB(nixDB, dbId2Paths, id, idPaths);
nixDB.setStrings(txn, dbId2Paths, id, idPaths);
}
Strings subs;
enumDB(nixDB, dbSubstitutes, subs);
nixDB.enumTable(txn, dbSubstitutes, subs);
for (Strings::iterator i = subs.begin();
i != subs.end(); i++)
@@ -337,7 +379,7 @@ void verifyStore()
FSId srcId = parseHash(*i);
Strings subIds;
queryListDB(nixDB, dbSubstitutes, srcId, subIds);
nixDB.queryStrings(txn, dbSubstitutes, srcId, subIds);
for (Strings::iterator j = subIds.begin();
j != subIds.end(); )
@@ -345,7 +387,7 @@ void verifyStore()
FSId subId = parseHash(*j);
Strings subPaths;
queryListDB(nixDB, dbId2Paths, subId, subPaths);
nixDB.queryStrings(txn, dbId2Paths, subId, subPaths);
if (subPaths.size() == 0) {
debug(format("erasing substitute %1% for %2%")
% (string) subId % (string) srcId);
@@ -353,11 +395,11 @@ void verifyStore()
} else j++;
}
setListDB(nixDB, dbSubstitutes, srcId, subIds);
nixDB.setStrings(txn, dbSubstitutes, srcId, subIds);
}
Strings sucs;
enumDB(nixDB, dbSuccessors, sucs);
nixDB.enumTable(txn, dbSuccessors, sucs);
for (Strings::iterator i = sucs.begin();
i != sucs.end(); i++)
@@ -365,18 +407,20 @@ void verifyStore()
FSId id1 = parseHash(*i);
string id2;
if (!queryDB(nixDB, dbSuccessors, id1, id2)) abort();
if (!nixDB.queryString(txn, dbSuccessors, id1, id2)) abort();
Strings id2Paths;
queryListDB(nixDB, dbId2Paths, id2, id2Paths);
nixDB.queryStrings(txn, dbId2Paths, id2, id2Paths);
if (id2Paths.size() == 0) {
Strings id2Subs;
queryListDB(nixDB, dbSubstitutes, id2, id2Subs);
nixDB.queryStrings(txn, dbSubstitutes, id2, id2Subs);
if (id2Subs.size() == 0) {
debug(format("successor %1% for %2% missing")
% id2 % (string) id1);
delDB(nixDB, dbSuccessors, (string) id1);
nixDB.delPair(txn, dbSuccessors, (string) id1);
}
}
}
txn.commit();
}

View File

@@ -4,6 +4,7 @@
#include <string>
#include "hash.hh"
#include "db.hh"
using namespace std;
@@ -20,7 +21,8 @@ void copyPath(string src, string dst);
void registerSubstitute(const FSId & srcId, const FSId & subId);
/* Register a path keyed on its id. */
void registerPath(const string & path, const FSId & id);
void registerPath(const Transaction & txn,
const string & path, const FSId & id);
/* Query the id of a path. */
bool queryPathId(const string & path, FSId & id);
@@ -35,7 +37,8 @@ bool queryPathId(const string & path, FSId & id);
substitute (since when we build the substitute, we would first try
to expand the id... kaboom!). */
string expandId(const FSId & id, const string & target = "",
const string & prefix = "/", FSIdSet pending = FSIdSet());
const string & prefix = "/", FSIdSet pending = FSIdSet(),
bool ignoreSubstitutes = false);
/* Copy a file to the nixStore directory and register it in dbRefs.
Return the hash code of the value. */

0
src/test-builder-1.sh Normal file → Executable file
View File

0
src/test-builder-2.sh Normal file → Executable file
View File

View File

@@ -51,6 +51,8 @@ struct MySource : RestoreSource
void runTests()
{
verbosity = (Verbosity) 100;
/* Hashing. */
string s = "0b0ffd0538622bfe20b92c4aa57254d9";
Hash h = parseHash(s);
@@ -94,14 +96,16 @@ void runTests()
/* Set up the test environment. */
mkdir("scratch", 0777);
mkdir("scratch/db", 0777);
string testDir = absPath("scratch");
cout << testDir << endl;
nixStore = testDir;
nixLogDir = testDir;
nixDB = testDir + "/db";
nixDBPath = testDir + "/db";
openDB();
initDB();
/* Expression evaluation. */

View File

@@ -108,21 +108,28 @@ bool pathExists(const string & path)
void deletePath(string path)
{
msg(lvlVomit, format("deleting path `%1%'") % path);
struct stat st;
if (lstat(path.c_str(), &st))
throw SysError(format("getting attributes of path %1%") % path);
if (S_ISDIR(st.st_mode)) {
Strings names;
DIR * dir = opendir(path.c_str());
struct dirent * dirent;
while (errno = 0, dirent = readdir(dir)) {
string name = dirent->d_name;
if (name == "." || name == "..") continue;
deletePath(path + "/" + name);
names.push_back(name);
}
closedir(dir); /* !!! close on exception */
for (Strings::iterator i = names.begin(); i != names.end(); i++)
deletePath(path + "/" + *i);
}
if (remove(path.c_str()) == -1)

View File

@@ -4,5 +4,6 @@
-e s^@bindir\@^$(bindir)^g \
-e s^@sysconfdir\@^$(sysconfdir)^g \
-e s^@localstatedir\@^$(localstatedir)^g \
-e s^@wget\@^$(wget)^g \
< $< > $@ || rm $@
chmod +x $@

11
testpkgs/args/args-build.sh Executable file
View File

@@ -0,0 +1,11 @@
#! /bin/sh
IFS=
echo "printing list of args"
for i in $@; do
echo "arg: $i"
done
touch $out

7
testpkgs/args/args.fix Normal file
View File

@@ -0,0 +1,7 @@
Package(
[ ("name", "args")
, ("build", Relative("args/args-build.sh"))
, ("args", ["1", "2", "3", IncludeFix("slow2/slow.fix")])
]
)

9
testpkgs/fun/fun1.fix Normal file
View File

@@ -0,0 +1,9 @@
Call(
Function(["x"],
Call(
Function(["x"], Var("x")),
[ ("x", Var("x")) ]
)
),
[ ("x", True) ]
)

9
testpkgs/fun/fun2.fix Normal file
View File

@@ -0,0 +1,9 @@
Call(
Function(["x"],
Call(
Function(["y", "z"], Var("y")),
[ ("y", Var("x")) ]
)
),
[ ("x", True) ]
)

9
testpkgs/fun/fun3.fix Normal file
View File

@@ -0,0 +1,9 @@
Call(
Function(["x"],
Call(
Function(["x"], Var("x")),
[ ("x", False) ]
)
),
[ ("x", True) ]
)

View File

@@ -0,0 +1 @@
IncludeFix("infrec/infrec.fix")

14
testpkgs/slow/slow-build.sh Executable file
View File

@@ -0,0 +1,14 @@
#! /bin/sh
echo "builder started..."
mkdir $out
for i in $(seq 1 30); do
echo $i
sleep 1
done
echo "done" > $out/bla
echo "builder finished"

5
testpkgs/slow/slow.fix Normal file
View File

@@ -0,0 +1,5 @@
Package(
[ ("name", "slow")
, ("build", Relative("slow/slow-build.sh"))
]
)

14
testpkgs/slow2/slow-build.sh Executable file
View File

@@ -0,0 +1,14 @@
#! /bin/sh
echo "builder started..."
for i in $(seq 1 10); do
echo $i
sleep 1
done
mkdir $out
echo "done" >> $out/bla
echo "builder finished"

5
testpkgs/slow2/slow.fix Normal file
View File

@@ -0,0 +1,5 @@
Package(
[ ("name", "slow")
, ("build", Relative("slow2/slow-build.sh"))
]
)