Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dce54ac74f | ||
|
|
713d97efc0 | ||
|
|
44cfd237d9 |
262
.gitignore
vendored
262
.gitignore
vendored
@@ -1,262 +0,0 @@
|
||||
# START "git svn show-ignore"
|
||||
|
||||
# /
|
||||
/Makefile
|
||||
/Makefile.in
|
||||
/aclocal.m4
|
||||
/autom4te.cache
|
||||
/config.*
|
||||
/configure
|
||||
/nix.spec
|
||||
/stamp-h1
|
||||
/svn-revision
|
||||
/NEWS
|
||||
/libtool
|
||||
|
||||
# /config/
|
||||
/config/config.guess
|
||||
/config/config.sub
|
||||
/config/depcomp
|
||||
/config/install-sh
|
||||
/config/missing
|
||||
/config/mkinstalldirs
|
||||
/config/ltmain.sh
|
||||
|
||||
# /corepkgs/
|
||||
/corepkgs/Makefile
|
||||
/corepkgs/Makefile.in
|
||||
|
||||
# /corepkgs/buildenv/
|
||||
/corepkgs/buildenv/Makefile.in
|
||||
/corepkgs/buildenv/Makefile
|
||||
/corepkgs/buildenv/builder.pl
|
||||
|
||||
# /corepkgs/channels/
|
||||
/corepkgs/channels/Makefile.in
|
||||
/corepkgs/channels/Makefile
|
||||
/corepkgs/channels/unpack.sh
|
||||
|
||||
# /corepkgs/nar/
|
||||
/corepkgs/nar/Makefile
|
||||
/corepkgs/nar/Makefile.in
|
||||
/corepkgs/nar/nar.sh
|
||||
/corepkgs/nar/unnar.sh
|
||||
|
||||
# /doc/
|
||||
/doc/Makefile
|
||||
/doc/Makefile.in
|
||||
|
||||
# /doc/manual/
|
||||
/doc/manual/Makefile
|
||||
/doc/manual/Makefile.in
|
||||
/doc/manual/manual.html
|
||||
/doc/manual/manual.is-valid
|
||||
/doc/manual/*.1
|
||||
/doc/manual/*.8
|
||||
/doc/manual/images
|
||||
/doc/manual/version.txt
|
||||
/doc/manual/NEWS.html
|
||||
/doc/manual/NEWS.txt
|
||||
|
||||
# /externals/
|
||||
/externals/Makefile
|
||||
/externals/Makefile.in
|
||||
/externals/aterm-*
|
||||
/externals/have-aterm
|
||||
/externals/build-aterm
|
||||
/externals/inst-aterm
|
||||
/externals/bzip2-*
|
||||
/externals/have-bzip2
|
||||
/externals/build-bzip2
|
||||
/externals/inst-bzip2
|
||||
|
||||
# /make/examples/aterm/
|
||||
/make/examples/aterm/result*
|
||||
|
||||
# /make/examples/aterm/aterm/
|
||||
/make/examples/aterm/aterm/*
|
||||
|
||||
# /make/examples/aterm/test/
|
||||
/make/examples/aterm/test/*
|
||||
|
||||
# /misc/
|
||||
/misc/Makefile.in
|
||||
/misc/Makefile
|
||||
|
||||
# /misc/emacs/
|
||||
/misc/emacs/Makefile.in
|
||||
/misc/emacs/Makefile
|
||||
|
||||
# /scripts/
|
||||
/scripts/Makefile
|
||||
/scripts/Makefile.in
|
||||
/scripts/nix-profile.sh
|
||||
/scripts/nix-pull
|
||||
/scripts/nix-push
|
||||
/scripts/nix-switch
|
||||
/scripts/nix-collect-garbage
|
||||
/scripts/nix-prefetch-url
|
||||
/scripts/nix-install-package
|
||||
/scripts/nix-channel
|
||||
/scripts/nix-build
|
||||
/scripts/nix-copy-closure
|
||||
/scripts/readmanifest.pm
|
||||
/scripts/readconfig.pm
|
||||
/scripts/download-using-manifests.pl
|
||||
/scripts/copy-from-other-stores.pl
|
||||
/scripts/generate-patches.pl
|
||||
/scripts/find-runtime-roots.pl
|
||||
/scripts/build-remote.pl
|
||||
|
||||
# /src/
|
||||
/src/Makefile
|
||||
/src/Makefile.in
|
||||
|
||||
# /src/bin2c/
|
||||
/src/bin2c/Makefile.in
|
||||
/src/bin2c/Makefile
|
||||
/src/bin2c/bin2c
|
||||
/src/bin2c/.deps
|
||||
/src/bin2c/.libs
|
||||
|
||||
# /src/boost/
|
||||
/src/boost/Makefile
|
||||
/src/boost/Makefile.in
|
||||
|
||||
# /src/boost/format/
|
||||
/src/boost/format/Makefile
|
||||
/src/boost/format/Makefile.in
|
||||
/src/boost/format/.deps
|
||||
/src/boost/format/libformat.a
|
||||
/src/boost/format/.libs
|
||||
|
||||
# /src/bsdiff-4.3/
|
||||
/src/bsdiff-4.3/Makefile
|
||||
/src/bsdiff-4.3/Makefile.in
|
||||
/src/bsdiff-4.3/bsdiff
|
||||
/src/bsdiff-4.3/bspatch
|
||||
/src/bsdiff-4.3/.deps
|
||||
/src/bsdiff-4.3/.libs
|
||||
|
||||
# /src/libexpr/
|
||||
/src/libexpr/Makefile
|
||||
/src/libexpr/Makefile.in
|
||||
/src/libexpr/.deps
|
||||
/src/libexpr/libexpr.a
|
||||
/src/libexpr/lexer-tab.cc
|
||||
/src/libexpr/lexer-tab.hh
|
||||
/src/libexpr/parser-tab.cc
|
||||
/src/libexpr/parser-tab.hh
|
||||
/src/libexpr/parser-tab.output
|
||||
/src/libexpr/nixexpr-ast.hh
|
||||
/src/libexpr/nixexpr-ast.cc
|
||||
/src/libexpr/.libs
|
||||
/src/libexpr/nix.tbl
|
||||
|
||||
# /src/libmain/
|
||||
/src/libmain/Makefile
|
||||
/src/libmain/Makefile.in
|
||||
/src/libmain/.deps
|
||||
/src/libmain/libmain.a
|
||||
/src/libmain/.libs
|
||||
|
||||
# /src/libstore/
|
||||
/src/libstore/Makefile
|
||||
/src/libstore/Makefile.in
|
||||
/src/libstore/.deps
|
||||
/src/libstore/libstore.a
|
||||
/src/libstore/derivations-ast.cc
|
||||
/src/libstore/derivations-ast.hh
|
||||
/src/libstore/.libs
|
||||
|
||||
# /src/libutil/
|
||||
/src/libutil/Makefile
|
||||
/src/libutil/Makefile.in
|
||||
/src/libutil/.deps
|
||||
/src/libutil/libutil.a
|
||||
/src/libutil/.libs
|
||||
|
||||
# /src/nix-env/
|
||||
/src/nix-env/Makefile.in
|
||||
/src/nix-env/Makefile
|
||||
/src/nix-env/.deps
|
||||
/src/nix-env/nix-env
|
||||
/src/nix-env/help.txt.hh
|
||||
/src/nix-env/.libs
|
||||
|
||||
# /src/nix-hash/
|
||||
/src/nix-hash/Makefile
|
||||
/src/nix-hash/Makefile.in
|
||||
/src/nix-hash/.deps
|
||||
/src/nix-hash/.libs
|
||||
/src/nix-hash/nix-hash
|
||||
/src/nix-hash/help.txt.hh
|
||||
|
||||
# /src/nix-instantiate/
|
||||
/src/nix-instantiate/Makefile.in
|
||||
/src/nix-instantiate/Makefile
|
||||
/src/nix-instantiate/.deps
|
||||
/src/nix-instantiate/nix-instantiate
|
||||
/src/nix-instantiate/help.txt.hh
|
||||
/src/nix-instantiate/.libs
|
||||
|
||||
# /src/nix-log2xml/
|
||||
/src/nix-log2xml/Makefile.in
|
||||
/src/nix-log2xml/Makefile
|
||||
/src/nix-log2xml/.deps
|
||||
/src/nix-log2xml/nix-log2xml
|
||||
/src/nix-log2xml/test*.*
|
||||
/src/nix-log2xml/.libs
|
||||
/src/nix-log2xml/*.log
|
||||
/src/nix-log2xml/*.xml
|
||||
/src/nix-log2xml/*.html
|
||||
|
||||
# /src/nix-setuid-helper/
|
||||
/src/nix-setuid-helper/Makefile.in
|
||||
/src/nix-setuid-helper/Makefile
|
||||
/src/nix-setuid-helper/.deps
|
||||
/src/nix-setuid-helper/nix-setuid-helper
|
||||
/src/nix-setuid-helper/help.txt.hh
|
||||
/src/nix-setuid-helper/.libs
|
||||
|
||||
# /src/nix-store/
|
||||
/src/nix-store/Makefile
|
||||
/src/nix-store/Makefile.in
|
||||
/src/nix-store/.deps
|
||||
/src/nix-store/help.txt.hh
|
||||
/src/nix-store/nix-store
|
||||
/src/nix-store/.libs
|
||||
|
||||
# /src/nix-worker/
|
||||
/src/nix-worker/Makefile.in
|
||||
/src/nix-worker/Makefile
|
||||
/src/nix-worker/.deps
|
||||
/src/nix-worker/nix-worker
|
||||
/src/nix-worker/help.txt.hh
|
||||
/src/nix-worker/.libs
|
||||
|
||||
# /tests/
|
||||
/tests/Makefile
|
||||
/tests/Makefile.in
|
||||
/tests/test-tmp
|
||||
/tests/config.nix
|
||||
/tests/common.sh
|
||||
/tests/dummy
|
||||
|
||||
# /tests/lang/
|
||||
/tests/lang/*.out
|
||||
/tests/lang/*.out.xml
|
||||
/tests/lang/*.ast
|
||||
|
||||
# END "git svn show-ignore"
|
||||
|
||||
*.lo
|
||||
*.la
|
||||
*.o
|
||||
*~
|
||||
|
||||
# GNU Global
|
||||
GPATH
|
||||
GRTAGS
|
||||
GSYMS
|
||||
GTAGS
|
||||
24
Makefile.am
24
Makefile.am
@@ -1,13 +1,17 @@
|
||||
SUBDIRS = externals src scripts corepkgs doc misc tests
|
||||
EXTRA_DIST = substitute.mk nix.spec nix.spec.in bootstrap.sh \
|
||||
nix.conf.example NEWS version
|
||||
|
||||
pkginclude_HEADERS = config.h
|
||||
svn-revision nix.conf.example NEWS
|
||||
|
||||
include ./substitute.mk
|
||||
|
||||
nix.spec: nix.spec.in
|
||||
|
||||
rpm: nix.spec dist
|
||||
rpm $(EXTRA_RPM_FLAGS) -ta $(distdir).tar.gz
|
||||
|
||||
relname:
|
||||
echo -n $(distdir) > relname
|
||||
|
||||
install-data-local: init-state
|
||||
$(INSTALL) -d $(DESTDIR)$(sysconfdir)/nix
|
||||
$(INSTALL_DATA) $(srcdir)/nix.conf.example $(DESTDIR)$(sysconfdir)/nix
|
||||
@@ -31,18 +35,22 @@ init-state:
|
||||
$(INSTALL) $(INIT_FLAGS) -d $(DESTDIR)$(localstatedir)/nix/profiles
|
||||
$(INSTALL) $(INIT_FLAGS) -d $(DESTDIR)$(localstatedir)/nix/gcroots
|
||||
$(INSTALL) $(INIT_FLAGS) -d $(DESTDIR)$(localstatedir)/nix/temproots
|
||||
$(INSTALL) $(INIT_FLAGS) $(GROUP_WRITABLE) -d $(DESTDIR)$(localstatedir)/nix/gcroots/tmp
|
||||
$(INSTALL) $(INIT_FLAGS) $(GROUP_WRITABLE) -d $(DESTDIR)$(localstatedir)/nix/gcroots/channels
|
||||
ln -sfn $(localstatedir)/nix/profiles $(DESTDIR)$(localstatedir)/nix/gcroots/profiles
|
||||
$(INSTALL) $(INIT_FLAGS) -d $(DESTDIR)$(localstatedir)/nix/userpool
|
||||
-$(INSTALL) $(INIT_FLAGS) -m 1777 -d $(DESTDIR)$(storedir)
|
||||
$(INSTALL) $(INIT_FLAGS) -m 1777 -d $(DESTDIR)$(storedir)
|
||||
$(INSTALL) $(INIT_FLAGS) $(GROUP_WRITABLE) -d $(DESTDIR)$(localstatedir)/nix/manifests
|
||||
ln -sfn $(localstatedir)/nix/manifests $(DESTDIR)$(localstatedir)/nix/gcroots/manifests
|
||||
|
||||
else
|
||||
|
||||
init-state:
|
||||
|
||||
endif
|
||||
|
||||
NEWS:
|
||||
$(MAKE) -C doc/manual NEWS.txt
|
||||
svn-revision:
|
||||
svnversion . > svn-revision
|
||||
|
||||
all-local: NEWS
|
||||
|
||||
NEWS: doc/manual/NEWS.txt
|
||||
cp $(srcdir)/doc/manual/NEWS.txt NEWS
|
||||
|
||||
@@ -115,35 +115,3 @@
|
||||
fun:*
|
||||
fun:AT_collect
|
||||
}
|
||||
|
||||
{
|
||||
ATerm library conservatively scans for GC roots
|
||||
Memcheck:Value4
|
||||
fun:*
|
||||
fun:*
|
||||
fun:mark_phase
|
||||
}
|
||||
|
||||
{
|
||||
ATerm library conservatively scans for GC roots
|
||||
Memcheck:Cond
|
||||
fun:*
|
||||
fun:*
|
||||
fun:mark_phase
|
||||
}
|
||||
|
||||
{
|
||||
ATerm library conservatively scans for GC roots
|
||||
Memcheck:Value4
|
||||
fun:*
|
||||
fun:*
|
||||
fun:mark_phase_young
|
||||
}
|
||||
|
||||
{
|
||||
ATerm library conservatively scans for GC roots
|
||||
Memcheck:Cond
|
||||
fun:*
|
||||
fun:*
|
||||
fun:mark_phase_young
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#! /bin/sh -e
|
||||
rm -f aclocal.m4
|
||||
mkdir -p config
|
||||
libtoolize --copy
|
||||
aclocal
|
||||
|
||||
159
configure.ac
159
configure.ac
@@ -1,8 +1,21 @@
|
||||
AC_INIT(nix, m4_esyscmd([echo -n $(cat ./version)$VERSION_SUFFIX]))
|
||||
AC_INIT(nix, 0.12)
|
||||
AC_CONFIG_SRCDIR(README)
|
||||
AC_CONFIG_AUX_DIR(config)
|
||||
AM_INIT_AUTOMAKE([dist-bzip2 foreign])
|
||||
|
||||
# Change to `1' to produce a `stable' release (i.e., the `preREVISION'
|
||||
# suffix is not added).
|
||||
STABLE=1
|
||||
|
||||
# Put the revision number in the version.
|
||||
if test "$STABLE" != "1"; then
|
||||
if REVISION=`test -d $srcdir/.svn && svnversion -n $srcdir 2> /dev/null`; then
|
||||
VERSION=${VERSION}pre${REVISION}
|
||||
elif REVISION=`cat $srcdir/svn-revision 2> /dev/null`; then
|
||||
VERSION=${VERSION}pre${REVISION}
|
||||
fi
|
||||
fi
|
||||
|
||||
AC_DEFINE_UNQUOTED(NIX_VERSION, ["$VERSION"], [Nix version.])
|
||||
|
||||
AC_CANONICAL_HOST
|
||||
@@ -10,8 +23,8 @@ AC_CANONICAL_HOST
|
||||
|
||||
# Construct a Nix system name (like "i686-linux").
|
||||
AC_MSG_CHECKING([for the canonical Nix system name])
|
||||
cpu_name=$(uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' 'abcdefghijklmnopqrstuvwxyz_')
|
||||
machine_name=$(uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' 'abcdefghijklmnopqrstuvwxyz_')
|
||||
cpu_name=$(uname -p | tr 'A-Z ' 'a-z_')
|
||||
machine_name=$(uname -m | tr 'A-Z ' 'a-z_')
|
||||
|
||||
case $machine_name in
|
||||
i*86)
|
||||
@@ -30,7 +43,7 @@ case $machine_name in
|
||||
;;
|
||||
esac
|
||||
|
||||
sys_name=$(uname -s | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' 'abcdefghijklmnopqrstuvwxyz_')
|
||||
sys_name=$(uname -s | tr 'A-Z ' 'a-z_')
|
||||
|
||||
case $sys_name in
|
||||
cygwin*)
|
||||
@@ -50,52 +63,30 @@ AC_DEFINE_UNQUOTED(SYSTEM, ["$system"], [platform identifier (`cpu-os')])
|
||||
test "$localstatedir" = '${prefix}/var' && localstatedir=/nix/var
|
||||
|
||||
|
||||
# Windows-specific stuff. On Cygwin, dynamically linking against the
|
||||
# ATerm DLL works, except that it requires the ATerm "lib" directory
|
||||
# to be in $PATH, as Windows doesn't have anything like an RPATH
|
||||
# embedded in executable. Since this is kind of annoying, we use
|
||||
# static libraries for now.
|
||||
# Windows-specific stuff.
|
||||
if test "$sys_name" = "cygwin"; then
|
||||
# We cannot delete open files.
|
||||
AC_DEFINE(CANNOT_DELETE_OPEN_FILES, 1, [Whether it is impossible to delete open files.])
|
||||
|
||||
# Shared libraries don't work, currently.
|
||||
AC_DISABLE_SHARED
|
||||
AC_ENABLE_STATIC
|
||||
fi
|
||||
|
||||
|
||||
# Solaris-specific stuff.
|
||||
if test "$sys_name" = "sunos"; then
|
||||
# Solaris requires -lsocket -lnsl for network functions
|
||||
LIBS="-lsocket -lnsl $LIBS"
|
||||
fi
|
||||
|
||||
|
||||
AC_PROG_CC
|
||||
AC_PROG_CXX
|
||||
|
||||
# To build programs to be run in the build machine
|
||||
if test "$CC_FOR_BUILD" = ""; then
|
||||
if test "$cross_compiling" = "yes"; then
|
||||
AC_CHECK_PROGS(CC_FOR_BUILD, gcc cc)
|
||||
else
|
||||
CC_FOR_BUILD="$CC"
|
||||
fi
|
||||
fi
|
||||
AC_SUBST([CC_FOR_BUILD])
|
||||
|
||||
# We are going to use libtool.
|
||||
AC_DISABLE_STATIC
|
||||
AC_ENABLE_SHARED
|
||||
AC_PROG_LIBTOOL
|
||||
|
||||
if test "$enable_shared" = yes; then
|
||||
SUB_CONFIGURE_FLAGS="--enable-shared --disable-static"
|
||||
else
|
||||
SUB_CONFIGURE_FLAGS="--enable-static --disable-shared"
|
||||
fi
|
||||
AC_SUBST(SUB_CONFIGURE_FLAGS)
|
||||
|
||||
|
||||
# Use 64-bit file system calls so that we can support files > 2 GiB.
|
||||
AC_SYS_LARGEFILE
|
||||
CFLAGS="-D_FILE_OFFSET_BITS=64 $CFLAGS"
|
||||
CXXFLAGS="-D_FILE_OFFSET_BITS=64 $CXXFLAGS"
|
||||
|
||||
|
||||
# Check for pubsetbuf.
|
||||
@@ -112,8 +103,6 @@ AC_LANG_POP(C++)
|
||||
|
||||
# Check for chroot support (requires chroot() and bind mounts).
|
||||
AC_CHECK_FUNCS([chroot])
|
||||
AC_CHECK_FUNCS([unshare])
|
||||
AC_CHECK_HEADERS([sched.h], [], [], [])
|
||||
AC_CHECK_HEADERS([sys/param.h], [], [], [])
|
||||
AC_CHECK_HEADERS([sys/mount.h], [], [],
|
||||
[#ifdef HAVE_SYS_PARAM_H
|
||||
@@ -122,28 +111,12 @@ AC_CHECK_HEADERS([sys/mount.h], [], [],
|
||||
])
|
||||
|
||||
|
||||
# Check for <locale>.
|
||||
# Check for <locale>
|
||||
AC_LANG_PUSH(C++)
|
||||
AC_CHECK_HEADERS([locale], [], [], [])
|
||||
AC_LANG_POP(C++)
|
||||
|
||||
|
||||
# Check for <err.h>.
|
||||
AC_CHECK_HEADER([err.h], [], [bsddiff_compat_include="-Icompat-include"])
|
||||
AC_SUBST([bsddiff_compat_include])
|
||||
|
||||
|
||||
# Check whether we have the personality() syscall, which allows us to
|
||||
# do i686-linux builds on x86_64-linux machines.
|
||||
AC_CHECK_HEADERS([sys/personality.h])
|
||||
|
||||
|
||||
# Check for tr1/unordered_set.
|
||||
AC_LANG_PUSH(C++)
|
||||
AC_CHECK_HEADERS([tr1/unordered_set], [], [], [])
|
||||
AC_LANG_POP(C++)
|
||||
|
||||
|
||||
AC_DEFUN([NEED_PROG],
|
||||
[
|
||||
AC_PATH_PROG($1, $2)
|
||||
@@ -157,11 +130,11 @@ NEED_PROG(bash, bash)
|
||||
NEED_PROG(patch, patch)
|
||||
AC_PATH_PROG(xmllint, xmllint, false)
|
||||
AC_PATH_PROG(xsltproc, xsltproc, false)
|
||||
AC_PATH_PROG(jing, jing, false) # needed because xmllint --relaxng seems broken
|
||||
AC_PATH_PROG(w3m, w3m, false)
|
||||
AC_PATH_PROG(flex, flex, false)
|
||||
AC_PATH_PROG(bison, bison, false)
|
||||
NEED_PROG(perl, perl)
|
||||
NEED_PROG(sed, sed)
|
||||
NEED_PROG(tar, tar)
|
||||
AC_PATH_PROG(dot, dot)
|
||||
AC_PATH_PROG(dblatex, dblatex)
|
||||
@@ -206,6 +179,48 @@ AC_ARG_WITH(store-dir, AC_HELP_STRING([--with-store-dir=PATH],
|
||||
storedir=$withval, storedir='/nix/store')
|
||||
AC_SUBST(storedir)
|
||||
|
||||
AC_ARG_ENABLE(old-db-compat, AC_HELP_STRING([--disable-old-db-compat],
|
||||
[disable support for converting from old Berkeley DB-based Nix stores]),
|
||||
old_db_compat=$enableval, old_db_compat=yes)
|
||||
AM_CONDITIONAL(OLD_DB_COMPAT, test "$old_db_compat" = "yes")
|
||||
|
||||
AC_ARG_WITH(bdb, AC_HELP_STRING([--with-bdb=PATH],
|
||||
[prefix of Berkeley DB (for Nix <= 0.11 compatibility)]),
|
||||
bdb=$withval, bdb=)
|
||||
AM_CONDITIONAL(HAVE_BDB, test -n "$bdb")
|
||||
if test -z "$bdb"; then
|
||||
bdb_lib='-L${top_builddir}/externals/inst-bdb/lib -ldb_cxx'
|
||||
bdb_include='-I${top_builddir}/externals/inst-bdb/include'
|
||||
else
|
||||
bdb_lib="-L$bdb/lib -ldb_cxx"
|
||||
bdb_include="-I$bdb/include"
|
||||
fi
|
||||
if test "$old_db_compat" = "no"; then
|
||||
bdb_lib=
|
||||
bdb_include=
|
||||
else
|
||||
AC_DEFINE(OLD_DB_COMPAT, 1, [Whether to support converting from old Berkeley DB-based Nix stores.])
|
||||
fi
|
||||
AC_SUBST(bdb_lib)
|
||||
AC_SUBST(bdb_include)
|
||||
|
||||
AC_ARG_WITH(aterm, AC_HELP_STRING([--with-aterm=PATH],
|
||||
[prefix of CWI ATerm library]),
|
||||
aterm=$withval, aterm=)
|
||||
AM_CONDITIONAL(HAVE_ATERM, test -n "$aterm")
|
||||
if test -z "$aterm"; then
|
||||
aterm_lib='-L${top_builddir}/externals/inst-aterm/lib -lATerm'
|
||||
aterm_include='-I${top_builddir}/externals/inst-aterm/include'
|
||||
aterm_bin='${top_builddir}/externals/inst-aterm/bin'
|
||||
else
|
||||
aterm_lib="-L$aterm/lib -lATerm"
|
||||
aterm_include="-I$aterm/include"
|
||||
aterm_bin="$aterm/bin"
|
||||
fi
|
||||
AC_SUBST(aterm_lib)
|
||||
AC_SUBST(aterm_include)
|
||||
AC_SUBST(aterm_bin)
|
||||
|
||||
AC_ARG_WITH(openssl, AC_HELP_STRING([--with-openssl=PATH],
|
||||
[prefix of the OpenSSL library]),
|
||||
openssl=$withval, openssl=)
|
||||
@@ -221,8 +236,6 @@ AC_ARG_WITH(bzip2, AC_HELP_STRING([--with-bzip2=PATH],
|
||||
[prefix of bzip2]),
|
||||
bzip2=$withval, bzip2=)
|
||||
AM_CONDITIONAL(HAVE_BZIP2, test -n "$bzip2")
|
||||
ATERM_VERSION=2.5
|
||||
AC_SUBST(ATERM_VERSION)
|
||||
if test -z "$bzip2"; then
|
||||
# Headers and libraries will be used from the temporary installation
|
||||
# in externals/inst-bzip2.
|
||||
@@ -237,42 +250,14 @@ else
|
||||
bzip2_include="-I$bzip2/include"
|
||||
bzip2_bin="$bzip2/bin"
|
||||
bzip2_bin_test="$bzip2/bin"
|
||||
fi
|
||||
fi
|
||||
AC_SUBST(bzip2_lib)
|
||||
AC_SUBST(bzip2_include)
|
||||
AC_SUBST(bzip2_bin)
|
||||
AC_SUBST(bzip2_bin_test)
|
||||
|
||||
AC_ARG_WITH(sqlite, AC_HELP_STRING([--with-sqlite=PATH],
|
||||
[prefix of SQLite]),
|
||||
sqlite=$withval, sqlite=)
|
||||
AM_CONDITIONAL(HAVE_SQLITE, test -n "$sqlite")
|
||||
SQLITE_VERSION=3070500
|
||||
AC_SUBST(SQLITE_VERSION)
|
||||
if test -z "$sqlite"; then
|
||||
sqlite_lib='${top_builddir}/externals/sqlite-autoconf-$(SQLITE_VERSION)/libsqlite3.la'
|
||||
sqlite_include='-I${top_builddir}/externals/sqlite-autoconf-$(SQLITE_VERSION)'
|
||||
sqlite_bin='${top_builddir}/externals/sqlite-autoconf-$(SQLITE_VERSION)'
|
||||
else
|
||||
sqlite_lib="-L$sqlite/lib -lsqlite3"
|
||||
sqlite_include="-I$sqlite/include"
|
||||
sqlite_bin="$sqlite/bin"
|
||||
fi
|
||||
AC_SUBST(sqlite_lib)
|
||||
AC_SUBST(sqlite_include)
|
||||
AC_SUBST(sqlite_bin)
|
||||
|
||||
# Whether to use the Boehm garbage collector.
|
||||
AC_ARG_ENABLE(gc, AC_HELP_STRING([--enable-gc],
|
||||
[enable garbage collection in the Nix expression evaluator (requires Boehm GC)]),
|
||||
gc=$enableval, gc=)
|
||||
if test -n "$gc"; then
|
||||
PKG_CHECK_MODULES([BDW_GC], [bdw-gc])
|
||||
boehmgc_lib="-L$boehmgc/lib -lgc"
|
||||
CXXFLAGS="$BDW_GC_CFLAGS $CXXFLAGS"
|
||||
AC_DEFINE(HAVE_BOEHMGC, 1, [Whether to use the Boehm garbage collector.])
|
||||
fi
|
||||
AC_SUBST(boehmgc_lib)
|
||||
AC_CHECK_LIB(pthread, pthread_mutex_init)
|
||||
|
||||
|
||||
AC_ARG_ENABLE(init-state, AC_HELP_STRING([--disable-init-state],
|
||||
@@ -286,10 +271,10 @@ AC_CHECK_FUNCS([setresuid setreuid lchown])
|
||||
|
||||
|
||||
# Nice to have, but not essential.
|
||||
AC_CHECK_FUNCS([strsignal posix_fallocate nanosleep])
|
||||
AC_CHECK_FUNCS([strsignal])
|
||||
|
||||
|
||||
# This is needed if ATerm or bzip2 are static libraries,
|
||||
# This is needed if ATerm, Berkeley DB or bzip2 are static libraries,
|
||||
# and the Nix libraries are dynamic.
|
||||
if test "$(uname)" = "Darwin"; then
|
||||
LDFLAGS="-all_load $LDFLAGS"
|
||||
|
||||
@@ -29,18 +29,10 @@ sub createLinks {
|
||||
$baseName =~ s/^.*\///g; # strip directory
|
||||
my $dstFile = "$dstDir/$baseName";
|
||||
|
||||
# The files below are special-cased so that they don't show up
|
||||
# in user profiles, either because they are useless, or
|
||||
# because they would cause pointless collisions (e.g., each
|
||||
# Python package brings its own
|
||||
# `$out/lib/pythonX.Y/site-packages/easy-install.pth'.)
|
||||
# Urgh, hacky...
|
||||
if ($srcFile =~ /\/propagated-build-inputs$/ ||
|
||||
if ($srcFile =~ /\/propagated-build-inputs$/ ||
|
||||
$srcFile =~ /\/nix-support$/ ||
|
||||
$srcFile =~ /\/perllocal.pod$/ ||
|
||||
$srcFile =~ /\/easy-install.pth$/ ||
|
||||
$srcFile =~ /\/site.py$/ ||
|
||||
$srcFile =~ /\/site.pyc$/ ||
|
||||
$srcFile =~ /\/info\/dir$/ ||
|
||||
$srcFile =~ /\/log$/)
|
||||
{
|
||||
@@ -168,4 +160,4 @@ while (scalar(keys %postponed) > 0) {
|
||||
print STDERR "created $symlinks symlinks in user environment\n";
|
||||
|
||||
|
||||
symlink($ENV{"manifest"}, "$out/manifest.nix") or die "cannot create manifest";
|
||||
symlink($ENV{"manifest"}, "$out/manifest") or die "cannot create manifest";
|
||||
|
||||
@@ -11,8 +11,4 @@ derivation {
|
||||
paths = derivations;
|
||||
active = map (x: if x ? meta && x.meta ? active then x.meta.active else "true") derivations;
|
||||
priority = map (x: if x ? meta && x.meta ? priority then x.meta.priority else "5") derivations;
|
||||
|
||||
# Building user environments remotely just causes huge amounts of
|
||||
# network traffic, so don't do that.
|
||||
preferLocalBuild = true;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#! @shell@ -e
|
||||
|
||||
# Cygwin compatibility hack: bunzip2 expects cygwin.dll in $PATH.
|
||||
export PATH=@coreutils@
|
||||
|
||||
@coreutils@/mkdir $out
|
||||
@coreutils@/mkdir $out/tmp
|
||||
cd $out/tmp
|
||||
@@ -11,15 +8,9 @@ inputs=($inputs)
|
||||
for ((n = 0; n < ${#inputs[*]}; n += 2)); do
|
||||
channelName=${inputs[n]}
|
||||
channelTarball=${inputs[n+1]}
|
||||
|
||||
echo "unpacking channel $channelName"
|
||||
|
||||
@bunzip2@ < $channelTarball | @tar@ xf -
|
||||
|
||||
if test -e */channel-name; then
|
||||
channelName="$(@coreutils@/cat */channel-name)"
|
||||
fi
|
||||
|
||||
nr=1
|
||||
attrName=$(echo $channelName | @tr@ -- '- ' '__')
|
||||
dirName=$attrName
|
||||
|
||||
@@ -7,6 +7,8 @@ dst=$out/tmp.nar.bz2
|
||||
|
||||
@bzip2@ < tmp > $dst
|
||||
|
||||
@bindir@/nix-hash -vvvvv --flat --type $hashAlgo --base32 tmp > $out/nar-hash
|
||||
|
||||
@bindir@/nix-hash --flat --type $hashAlgo --base32 $dst > $out/narbz2-hash
|
||||
|
||||
@coreutils@/mv $out/tmp.nar.bz2 $out/$(@coreutils@/cat $out/narbz2-hash).nar.bz2
|
||||
|
||||
@@ -6,12 +6,7 @@ XSLTPROC = $(xsltproc) $(xmlflags) \
|
||||
--param xref.with.number.and.title 1 \
|
||||
--param toc.section.depth 3 \
|
||||
--param admon.style \'\' \
|
||||
--param callout.graphics.extension \'.gif\' \
|
||||
--param contrib.inline.enabled 0
|
||||
|
||||
dblatex_opts = \
|
||||
-P doc.collab.show=0 \
|
||||
-P latex.output.revhistory=0
|
||||
--param callout.graphics.extension \'.gif\'
|
||||
|
||||
# Note: we use GIF for now, since the PNGs shipped with Docbook aren't
|
||||
# transparent.
|
||||
@@ -34,9 +29,13 @@ MANUAL_SRCS = manual.xml introduction.xml installation.xml \
|
||||
conf-file.xml release-notes.xml \
|
||||
style.css images
|
||||
|
||||
# Note: RelaxNG validation requires xmllint >= 2.7.4.
|
||||
manual.is-valid: $(MANUAL_SRCS) version.txt
|
||||
$(XMLLINT) --noout --nonet --xinclude --noxincludenode --relaxng $(docbookrng)/docbook.rng $<
|
||||
# $(XMLLINT) --xinclude $< | $(XMLLINT) --noout --nonet --relaxng $(docbookrng)/docbook.rng -
|
||||
if test "$(jing)" != "false"; then \
|
||||
$(XMLLINT) --xinclude $< | $(jing) $(docbookrng)/docbook.rng /dev/fd/0; \
|
||||
else \
|
||||
echo "Not validating."; \
|
||||
fi
|
||||
touch $@
|
||||
|
||||
version.txt:
|
||||
@@ -51,7 +50,7 @@ manual.html: $(MANUAL_SRCS) manual.is-valid images
|
||||
|
||||
manual.pdf: $(MANUAL_SRCS) manual.is-valid images
|
||||
if test "$(dblatex)" != ""; then \
|
||||
$(dblatex) $(dblatex_opts) manual.xml; \
|
||||
$(dblatex) manual.xml; \
|
||||
else \
|
||||
echo "Please install dblatex and rerun configure."; \
|
||||
exit 1; \
|
||||
@@ -80,14 +79,10 @@ all-local: manual.html NEWS.html NEWS.txt
|
||||
install-data-local: manual.html
|
||||
$(INSTALL) -d $(DESTDIR)$(docdir)/manual
|
||||
$(INSTALL_DATA) manual.html $(DESTDIR)$(docdir)/manual
|
||||
ln -sf manual.html $(DESTDIR)$(docdir)/manual/index.html
|
||||
$(INSTALL_DATA) style.css $(DESTDIR)$(docdir)/manual
|
||||
cp -r images $(DESTDIR)$(docdir)/manual/images
|
||||
$(INSTALL) -d $(DESTDIR)$(docdir)/manual/figures
|
||||
$(INSTALL_DATA) $(FIGURES) $(DESTDIR)$(docdir)/manual/figures
|
||||
$(INSTALL) -d $(DESTDIR)$(docdir)/release-notes
|
||||
$(INSTALL_DATA) NEWS.html $(DESTDIR)$(docdir)/release-notes/index.html
|
||||
$(INSTALL_DATA) style.css $(DESTDIR)$(docdir)/release-notes/
|
||||
|
||||
images:
|
||||
mkdir images
|
||||
|
||||
@@ -77,8 +77,18 @@ attrValues = attrs: map (name: builtins.getAttr name attrs) (builtins.attrNames
|
||||
if builtins ? getEnv then builtins.getEnv "PATH" else ""</programlisting>
|
||||
|
||||
This allows a Nix expression to fall back gracefully on older Nix
|
||||
installations that don’t have the desired built-in
|
||||
function.</para></listitem>
|
||||
installations that don’t have the desired built-in function.
|
||||
However, in that case you should not write
|
||||
|
||||
<programlisting>
|
||||
if builtins ? getEnv then __getEnv "PATH" else ""</programlisting>
|
||||
|
||||
This Nix expression will trigger an “undefined variable” error on
|
||||
older Nix versions since <function>__getEnv</function> doesn’t
|
||||
exist. <literal>builtins.getEnv</literal>, on the other hand, is
|
||||
safe since <literal>builtins</literal> always exists and attribute
|
||||
selection is lazy, so it’s only performed if the test
|
||||
succeeds.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
@@ -324,16 +334,6 @@ x: x + 456</programlisting>
|
||||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry><term><function>builtins.intersectAttrs</function>
|
||||
<replaceable>e1</replaceable> <replaceable>e2</replaceable></term>
|
||||
|
||||
<listitem><para>Return an attribute set consisting of the
|
||||
attributes in the set <replaceable>e2</replaceable> that also
|
||||
exist in the set <replaceable>e1</replaceable>.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry><term><function>builtins.isAttrs</function>
|
||||
<replaceable>e</replaceable></term>
|
||||
|
||||
@@ -364,36 +364,6 @@ x: x + 456</programlisting>
|
||||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry><term><function>builtins.isString</function>
|
||||
<replaceable>e</replaceable></term>
|
||||
|
||||
<listitem><para>Return <literal>true</literal> if
|
||||
<replaceable>e</replaceable> evaluates to a string, and
|
||||
<literal>false</literal> otherwise.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry><term><function>builtins.isInt</function>
|
||||
<replaceable>e</replaceable></term>
|
||||
|
||||
<listitem><para>Return <literal>true</literal> if
|
||||
<replaceable>e</replaceable> evaluates to a int, and
|
||||
<literal>false</literal> otherwise.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry><term><function>builtins.isBool</function>
|
||||
<replaceable>e</replaceable></term>
|
||||
|
||||
<listitem><para>Return <literal>true</literal> if
|
||||
<replaceable>e</replaceable> evaluates to a bool, and
|
||||
<literal>false</literal> otherwise.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry><term><function>isNull</function>
|
||||
<replaceable>e</replaceable></term>
|
||||
|
||||
|
||||
@@ -97,25 +97,6 @@ env-keep-derivations = false
|
||||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry xml:id="conf-build-cores"><term><literal>build-cores</literal></term>
|
||||
|
||||
<listitem><para>Sets the value of the
|
||||
<envar>NIX_BUILD_CORES</envar> environment variable in the
|
||||
invocation of builders. Builders can use this variable at their
|
||||
discretion to control the maximum amount of parallelism. For
|
||||
instance, in Nixpkgs, if the derivation attribute
|
||||
<varname>enableParallelBuilding</varname> is set to
|
||||
<literal>true</literal>, the builder passes the
|
||||
<option>-j<replaceable>N</replaceable></option> flag to GNU Make.
|
||||
It can be overriden using the <option
|
||||
linkend='opt-cores'>--cores</option> command line switch and
|
||||
defaults to <literal>1</literal>. The value <literal>0</literal>
|
||||
means that the builder should use all available CPU cores in the
|
||||
system.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry xml:id="conf-build-max-silent-time"><term><literal>build-max-silent-time</literal></term>
|
||||
|
||||
<listitem>
|
||||
@@ -252,17 +233,7 @@ build-use-chroot = /dev /proc /bin</programlisting>
|
||||
<filename>configure</filename> at build time.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry><term><literal>fsync-metadata</literal></term>
|
||||
|
||||
<listitem><para>If set to <literal>true</literal>, changes to the
|
||||
Nix store metadata (in <filename>/nix/var/nix/db</filename>) are
|
||||
synchronously flushed to disk. This improves robustness in case
|
||||
of system crashes, but reduces performance. The default is
|
||||
<literal>true</literal>.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
|
||||
</variablelist>
|
||||
|
||||
|
||||
@@ -151,12 +151,12 @@ $ mount -o bind /mnt/otherdisk/nix /nix</screen>
|
||||
<para>On the basis of this information, and whatever persistent
|
||||
state the build hook keeps about other machines and their current
|
||||
load, it has to decide what to do with the build. It should print
|
||||
out on standard error one of the following responses (terminated by
|
||||
a newline, <literal>"\n"</literal>):
|
||||
out on file descriptor 3 one of the following responses (terminated
|
||||
by a newline, <literal>"\n"</literal>):
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry><term><literal># decline</literal></term>
|
||||
<varlistentry><term><literal>decline</literal></term>
|
||||
|
||||
<listitem><para>The build hook is not willing or able to perform
|
||||
the build; the calling Nix process should do the build itself,
|
||||
@@ -164,7 +164,7 @@ $ mount -o bind /mnt/otherdisk/nix /nix</screen>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry><term><literal># postpone</literal></term>
|
||||
<varlistentry><term><literal>postpone</literal></term>
|
||||
|
||||
<listitem><para>The build hook cannot perform the build now, but
|
||||
can do so in the future (e.g., because all available build slots
|
||||
@@ -174,7 +174,7 @@ $ mount -o bind /mnt/otherdisk/nix /nix</screen>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry><term><literal># accept</literal></term>
|
||||
<varlistentry><term><literal>accept</literal></term>
|
||||
|
||||
<listitem><para>The build hook has accepted the
|
||||
build.</para></listitem>
|
||||
@@ -185,12 +185,37 @@ $ mount -o bind /mnt/otherdisk/nix /nix</screen>
|
||||
|
||||
</para>
|
||||
|
||||
<para>After sending <literal># accept</literal>, the hook should
|
||||
read one line from standard input, which will be the string
|
||||
<literal>okay</literal>. It can then proceed with the build.
|
||||
Before sending <literal>okay</literal>, Nix will store in the hook’s
|
||||
current directory a number of text files that contain information
|
||||
about the derivation:
|
||||
<para>If the build hook accepts the build, it is possible that it is
|
||||
no longer necessary to do the build because some other process has
|
||||
performed the build in the meantime. To prevent races, the hook
|
||||
must read from file descriptor 4 a single line that tells it whether
|
||||
to continue:
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry><term><literal>cancel</literal></term>
|
||||
|
||||
<listitem><para>The build has already been done, so the hook
|
||||
should exit.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry><term><literal>okay</literal></term>
|
||||
|
||||
<listitem><para>The hook should proceed with the build. At this
|
||||
point, the calling Nix process has acquired locks on the output
|
||||
path, so no other Nix process will perform the
|
||||
build.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
|
||||
</para>
|
||||
|
||||
<para>If the hook has been told to proceed, Nix will store in the
|
||||
hook’s current directory a number of text files that contain
|
||||
information about the derivation:
|
||||
|
||||
<variablelist>
|
||||
|
||||
@@ -230,9 +255,7 @@ $ mount -o bind /mnt/otherdisk/nix /nix</screen>
|
||||
<para>The hook should copy the inputs to the remote machine,
|
||||
register the validity of the inputs, perform the remote build, and
|
||||
copy the outputs back to the local machine. An exit code other than
|
||||
<literal>0</literal> indicates that the hook has failed. An exit
|
||||
code equal to 100 means that the remote build failed (as opposed to,
|
||||
e.g., a network error).</para>
|
||||
<literal>0</literal> indicates that the hook has failed.</para>
|
||||
|
||||
</listitem>
|
||||
|
||||
@@ -271,17 +294,6 @@ $ mount -o bind /mnt/otherdisk/nix /nix</screen>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry><term><envar>GC_INITIAL_HEAP_SIZE</envar></term>
|
||||
|
||||
<listitem><para>If Nix has been configured to use the Boehm garbage
|
||||
collector, this variable sets the initial size of the heap in bytes.
|
||||
It defaults to 384 MiB. Setting it to a low value reduces memory
|
||||
consumption, but will increase runtime due to the overhead of
|
||||
garbage collection.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
|
||||
</variablelist>
|
||||
|
||||
|
||||
@@ -96,21 +96,23 @@ ubiquitous 2.5.4a won't. Note that these are only required if you
|
||||
modify the parser or when you are building from the Subversion
|
||||
repository.</para>
|
||||
|
||||
<para>Nix uses the bzip2 compressor (including the bzip2 library). It
|
||||
is included in the Nix source distribution. If you build from the
|
||||
Subversion repository, you must download it yourself and place it in
|
||||
the <filename>externals/</filename> directory. See
|
||||
<para>Nix uses CWI's ATerm library and the bzip2 compressor (including
|
||||
the bzip2 library). These are included in the Nix source
|
||||
distribution. If you build from the Subversion repository, you must
|
||||
download them yourself and place them in the
|
||||
<filename>externals/</filename> directory. See
|
||||
<filename>externals/Makefile.am</filename> for the precise URLs of
|
||||
this packages. Alternatively, if you already have it installed, you
|
||||
can use <command>configure</command>'s <option>--with-bzip2</option>
|
||||
these packages. Alternatively, if you already have them installed,
|
||||
you can use <command>configure</command>'s
|
||||
<option>--with-aterm</option> and <option>--with-bzip2</option>
|
||||
options to point to their respective locations.</para>
|
||||
|
||||
<para>Nix can optionally use the <link
|
||||
xlink:href="http://www.hpl.hp.com/personal/Hans_Boehm/gc/">Boehm
|
||||
garbage collector</link> to reduce the evaluator’s memory consumption.
|
||||
To enable it, install <literal>pkgconfig</literal> and the Boehm
|
||||
garbage collector, and pass the flag <option>--enable-gc</option> to
|
||||
<command>configure</command>.</para>
|
||||
<para>If you want to be able to upgrade Nix stores from before version
|
||||
0.12pre12020, you need Sleepycat's Berkeley DB version version 4.5.
|
||||
(Other versions may not have compatible database formats.). Berkeley
|
||||
DB 4.5 is included in the Nix source distribution. If you do not need
|
||||
this ability, you can build Nix with the
|
||||
<option>--disable-old-db-compat</option> configure option.</para>
|
||||
|
||||
</section>
|
||||
|
||||
@@ -131,32 +133,23 @@ $ make install</screen>
|
||||
preceded by the command:
|
||||
|
||||
<screen>
|
||||
$ ./bootstrap.sh</screen>
|
||||
$ ./bootstrap</screen>
|
||||
|
||||
</para>
|
||||
|
||||
<para>The installation path can be specified by passing the
|
||||
<option>--prefix=<replaceable>prefix</replaceable></option> to
|
||||
<command>configure</command>. The default installation directory is
|
||||
<filename>/usr/local</filename>. You can change this to any location
|
||||
you like. You must have write permission to the
|
||||
<filename>/nix</filename>. You can change this to any location you
|
||||
like. You must have write permission to the
|
||||
<replaceable>prefix</replaceable> path.</para>
|
||||
|
||||
<para>Nix keeps its <emphasis>store</emphasis> (the place where
|
||||
packages are stored) in <filename>/nix/store</filename> by default.
|
||||
This can be changed using
|
||||
<option>--with-store-dir=<replaceable>path</replaceable></option>.</para>
|
||||
<warning><para>It is best <emphasis>not</emphasis> to change the
|
||||
installation prefix from its default, since doing so makes it
|
||||
impossible to use pre-built binaries from the standard Nixpkgs
|
||||
channels.</para></warning>
|
||||
|
||||
<warning><para>It is best <emphasis>not</emphasis> to change the Nix
|
||||
store from its default, since doing so makes it impossible to use
|
||||
pre-built binaries from the standard Nixpkgs channels — that is, all
|
||||
packages will need to be built from source.</para></warning>
|
||||
|
||||
<para>Nix keeps state (such as its database and log files) in
|
||||
<filename>/nix/var</filename> by default. This can be changed using
|
||||
<option>--localstatedir=<replaceable>path</replaceable></option>.</para>
|
||||
|
||||
<para>If you want to rebuild the documentation, pass the full path to
|
||||
<para>If you want to rebuilt the documentation, pass the full path to
|
||||
the DocBook RELAX NG schemas and to the DocBook XSL stylesheets using
|
||||
the
|
||||
<option>--with-docbook-rng=<replaceable>path</replaceable></option>
|
||||
@@ -167,26 +160,27 @@ options.</para>
|
||||
</section>
|
||||
|
||||
|
||||
<section><title>Installing a binary distribution</title>
|
||||
<section><title>Installing from RPMs</title>
|
||||
|
||||
<para>RPM and Deb packages of Nix for a number of different versions
|
||||
of Fedora, openSUSE, Debian and Ubuntu can be downloaded from <link
|
||||
xlink:href="http://nixos.org/" />. Once downloaded, the RPMs can be
|
||||
installed or upgraded using <command>rpm -U</command>. For example,
|
||||
<para>RPM packages of Nix can be downloaded from <link
|
||||
xlink:href="http://nixos.org/" />. These RPMs should work for most
|
||||
fairly recent releases of SuSE and Red Hat Linux. They have been
|
||||
known to work work on SuSE Linux 8.1 and 9.0, and Red Hat 9.0. In
|
||||
fact, it should work on any RPM-based Linux distribution based on
|
||||
<literal>glibc</literal> 2.3 or later.</para>
|
||||
|
||||
<para>Once downloaded, the RPMs can be installed or upgraded using
|
||||
<command>rpm -U</command>. For example,
|
||||
|
||||
<screen>
|
||||
$ rpm -U nix-0.13pre18104-1.i386.rpm</screen>
|
||||
|
||||
Likewise, for a Deb package:
|
||||
|
||||
<screen>
|
||||
$ dpkg -i nix_0.13pre18104-1_amd64.deb</screen>
|
||||
$ rpm -U nix-0.5pre664-1.i386.rpm</screen>
|
||||
|
||||
</para>
|
||||
|
||||
<para>Nix can be uninstalled using <command>rpm -e nix</command> or
|
||||
<command>dpkg -r nix</command>. After this you should manually remove
|
||||
the Nix store and other auxiliary data, if desired:
|
||||
<para>The RPMs install into the directory <filename>/nix</filename>.
|
||||
Nix can be uninstalled using <command>rpm -e nix</command>. After
|
||||
this it will be necessary to manually remove the Nix store and other
|
||||
auxiliary data:
|
||||
|
||||
<screen>
|
||||
$ rm -rf /nix/store
|
||||
@@ -197,7 +191,6 @@ $ rm -rf /nix/var</screen>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- TODO: should be updated
|
||||
<section><title>Upgrading Nix through Nix</title>
|
||||
|
||||
<para>You can install the latest stable version of Nix through Nix
|
||||
@@ -210,7 +203,6 @@ installation</link> by clicking on the package links at <link
|
||||
xlink:href="http://nixos.org/releases/full-index-nix.html" />.</para>
|
||||
|
||||
</section>
|
||||
-->
|
||||
|
||||
|
||||
<section><title>Security</title>
|
||||
|
||||
@@ -113,7 +113,7 @@ $ nix-env --rollback
|
||||
|
||||
<simplesect><title>Garbage collection</title>
|
||||
|
||||
<para>When you uninstall a package like this…
|
||||
<para>When you install a package like this…
|
||||
|
||||
<screen>
|
||||
$ nix-env --uninstall firefox
|
||||
@@ -320,7 +320,7 @@ overview of NixOS is given in the HotOS XI paper <citetitle
|
||||
xlink:href="http://www.st.ewi.tudelft.nl/~dolstra/pubs/hotos-final.pdf">Purely
|
||||
Functional System Configuration Management</citetitle>. The Nix
|
||||
homepage has <link
|
||||
xlink:href="http://nixos.org/docs/papers.html">an up-to-date list
|
||||
xlink:href="http://nix.cs.uu.nl/docs/papers.html">an up-to-date list
|
||||
of Nix-related papers</link>.</para>
|
||||
|
||||
<para>Nix is the subject of Eelco Dolstra’s PhD thesis <citetitle
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<orgname>Delft University of Technology</orgname>
|
||||
<orgdiv>Department of Software Technology</orgdiv>
|
||||
</affiliation>
|
||||
<contrib>Author</contrib>
|
||||
</author>
|
||||
|
||||
<copyright>
|
||||
@@ -26,12 +25,10 @@
|
||||
<year>2006</year>
|
||||
<year>2007</year>
|
||||
<year>2008</year>
|
||||
<year>2009</year>
|
||||
<year>2010</year>
|
||||
<holder>Eelco Dolstra</holder>
|
||||
</copyright>
|
||||
|
||||
<date>August 2010</date>
|
||||
<date>November 2008</date>
|
||||
|
||||
</info>
|
||||
|
||||
@@ -52,29 +49,68 @@
|
||||
|
||||
<section>
|
||||
<title>Main commands</title>
|
||||
<xi:include href="nix-env.xml" />
|
||||
<xi:include href="nix-instantiate.xml" />
|
||||
<xi:include href="nix-store.xml" />
|
||||
<section xml:id="sec-nix-env">
|
||||
<title>nix-env</title>
|
||||
<xi:include href="nix-env.xml" />
|
||||
</section>
|
||||
<section xml:id="sec-nix-instantiate">
|
||||
<title>nix-instantiate</title>
|
||||
<xi:include href="nix-instantiate.xml" />
|
||||
</section>
|
||||
<section xml:id="sec-nix-store">
|
||||
<title>nix-store</title>
|
||||
<xi:include href="nix-store.xml" />
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Utilities</title>
|
||||
<xi:include href="nix-build.xml" />
|
||||
<xi:include href="nix-channel.xml" />
|
||||
<xi:include href="nix-collect-garbage.xml" />
|
||||
<xi:include href="nix-copy-closure.xml" />
|
||||
<xi:include href="nix-hash.xml" />
|
||||
<xi:include href="nix-install-package.xml" />
|
||||
<xi:include href="nix-prefetch-url.xml" />
|
||||
<xi:include href="nix-pull.xml" />
|
||||
<xi:include href="nix-push.xml" />
|
||||
<xi:include href="nix-worker.xml" />
|
||||
<section xml:id="sec-nix-build">
|
||||
<title>nix-build</title>
|
||||
<xi:include href="nix-build.xml" />
|
||||
</section>
|
||||
<section xml:id="sec-nix-channel">
|
||||
<title>nix-channel</title>
|
||||
<xi:include href="nix-channel.xml" />
|
||||
</section>
|
||||
<section xml:id="sec-nix-collect-garbage">
|
||||
<title>nix-collect-garbage</title>
|
||||
<xi:include href="nix-collect-garbage.xml" />
|
||||
</section>
|
||||
<section xml:id="sec-nix-copy-closure">
|
||||
<title>nix-copy-closure</title>
|
||||
<xi:include href="nix-copy-closure.xml" />
|
||||
</section>
|
||||
<section xml:id="sec-nix-hash">
|
||||
<title>nix-hash</title>
|
||||
<xi:include href="nix-hash.xml" />
|
||||
</section>
|
||||
<section xml:id="sec-nix-install-package">
|
||||
<title>nix-install-package</title>
|
||||
<xi:include href="nix-install-package.xml" />
|
||||
</section>
|
||||
<section xml:id="sec-nix-prefetch-url">
|
||||
<title>nix-prefetch-url</title>
|
||||
<xi:include href="nix-prefetch-url.xml" />
|
||||
</section>
|
||||
<section xml:id="sec-nix-pull">
|
||||
<title>nix-pull</title>
|
||||
<xi:include href="nix-pull.xml" />
|
||||
</section>
|
||||
<section xml:id="sec-nix-push">
|
||||
<title>nix-push</title>
|
||||
<xi:include href="nix-push.xml" />
|
||||
</section>
|
||||
<section xml:id="sec-nix-worker">
|
||||
<title>nix-worker</title>
|
||||
<xi:include href="nix-worker.xml" />
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
</appendix>
|
||||
|
||||
<xi:include href="troubleshooting.xml" />
|
||||
<!-- <xi:include href="bugs.xml" /> -->
|
||||
<xi:include href="bugs.xml" />
|
||||
<xi:include href="glossary.xml" />
|
||||
|
||||
<appendix>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<refentry xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xml:id="sec-nix-build">
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>nix-build</refentrytitle>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<refentry xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xml:id="sec-nix-channel">
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>nix-channel</refentrytitle>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<refentry xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xml:id="sec-nix-collect-garbage">
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>nix-collect-garbage</refentrytitle>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<refentry xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xml:id="sec-nix-copy-closure">
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>nix-copy-closure</refentrytitle>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<refentry xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xml:id="sec-nix-env">
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>nix-env</refentrytitle>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<refentry xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xml:id="sec-nix-hash">
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>nix-hash</refentrytitle>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<refentry xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xml:id="sec-nix-install-package">
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>nix-install-package</refentrytitle>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<refentry xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xml:id="sec-nix-instantiate">
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>nix-instantiate</refentrytitle>
|
||||
|
||||
@@ -178,5 +178,100 @@
|
||||
</productionset>
|
||||
|
||||
</sect1>
|
||||
|
||||
|
||||
|
||||
<sect1>
|
||||
<title>Semantics</title>
|
||||
|
||||
|
||||
|
||||
<sect2>
|
||||
<title>Built-in functions</title>
|
||||
|
||||
<para>
|
||||
The Nix language provides the following built-in function
|
||||
(<quote>primops</quote>):
|
||||
</para>
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry>
|
||||
<term><function>import</function>
|
||||
<replaceable>e</replaceable></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Evaluates the expression <replaceable>e</replaceable>,
|
||||
which must yield a path value. The Nix expression
|
||||
stored at this path in the file system is then read,
|
||||
parsed, and evaluated. Returns the result of the
|
||||
evaluation of the Nix expression just read.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Example: <literal>import ./foo.nix</literal> evaluates
|
||||
the expression stored in <filename>foo.nix</filename>
|
||||
(in the directory containing the expression in which the
|
||||
<function>import</function> occurs).
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><function>derivation</function>
|
||||
<replaceable>e</replaceable></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Evaluates the expression <replaceable>e</replaceable>,
|
||||
which must yield an attribute set. [...]
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><function>baseNameOf</function>
|
||||
<replaceable>e</replaceable></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Evaluates the expression <replaceable>e</replaceable>,
|
||||
which must yield a string value, and returns a string
|
||||
representing its <emphasis>base name</emphasis>. This
|
||||
is the substring following the last path separator
|
||||
(<literal>/</literal>).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Example: <literal>baseNameOf "/foo/bar"</literal>
|
||||
returns <literal>"bar"</literal>, and
|
||||
<literal>baseNameOf "/foo/bar/"</literal> returns
|
||||
<literal>""</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><function>toString</function>
|
||||
<replaceable>e</replaceable></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Evaluates the expression <replaceable>e</replaceable>
|
||||
and coerces it into a string, if possible. Only
|
||||
strings, paths, and URIs can be so coerced.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Example: <literal>toString
|
||||
http://www.cs.uu.nl/</literal> returns
|
||||
<literal>"http://www.cs.uu.nl/"</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
|
||||
</appendix>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<refentry xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xml:id="sec-nix-prefetch-url">
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>nix-prefetch-url</refentrytitle>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<refentry xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xml:id="sec-nix-pull">
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>nix-pull</refentrytitle>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<refentry xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xml:id="sec-nix-push">
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>nix-push</refentrytitle>
|
||||
@@ -120,7 +119,7 @@ dependencies used in the build, such as compilers).</para>
|
||||
dependencies, we can do:
|
||||
|
||||
<screen>
|
||||
$ nix-push <replaceable>urls</replaceable> $(nix-store -r $(nix-instantiate foo.nix))</screen>
|
||||
$ nix-push <replaceable>urls</replaceable> $(nix-instantiate $(nix-store -r foo.nix))</screen>
|
||||
|
||||
</para>
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<refentry xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xml:id="sec-nix-store">
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>nix-store</refentrytitle>
|
||||
@@ -213,6 +212,8 @@ linkend="sec-nix-build"><command>nix-build</command></link> does.</para>
|
||||
</group>
|
||||
<arg><option>--max-freed</option> <replaceable>bytes</replaceable></arg>
|
||||
<arg><option>--max-links</option> <replaceable>nrlinks</replaceable></arg>
|
||||
<arg><option>--max-atime</option> <replaceable>atime</replaceable></arg>
|
||||
<arg><option>--use-atime</option></arg>
|
||||
</cmdsynopsis>
|
||||
|
||||
</refsection>
|
||||
@@ -290,6 +291,42 @@ options control what gets deleted and in what order:
|
||||
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry><term><option>--max-atime</option> <replaceable>atime</replaceable></term>
|
||||
|
||||
<listitem><para>Only delete a store path if its last-accessed time
|
||||
is less than <replaceable>atime</replaceable>. This allows you to
|
||||
garbage-collect only packages that haven’t been used recently.
|
||||
The time is expressed as the number of seconds in the Unix epoch,
|
||||
i.e., since 1970-01-01 00:00:00 UTC. An easy way to convert to
|
||||
this format is <literal>date +%s -d "<replaceable>date
|
||||
specification</replaceable>"</literal>.</para>
|
||||
|
||||
<para>For directories, the last-accessed time is the highest
|
||||
last-accessed time of any regular file in the directory (or in any
|
||||
of its subdirectories). That is, the <literal>atime</literal>
|
||||
field maintained by the filesystem is ignored for directories.
|
||||
This is because operations such as rebuilding the
|
||||
<command>locate</command> database tend to update the
|
||||
<literal>atime</literal> values of all directories, so they’re not
|
||||
a useful indicator of whether a package was recently used.</para>
|
||||
|
||||
<para>Note that <command>nix-store --optimise</command> reads all
|
||||
regular files in the Nix store, and so causes all last-accessed
|
||||
times to be set to the present time. This makes
|
||||
<option>--max-atime</option> ineffective (for a while at
|
||||
least).</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry><term><option>--use-atime</option></term>
|
||||
|
||||
<listitem><para>Delete store paths in order of ascending
|
||||
last-accessed time. This is useful in conjunction with the other
|
||||
options to delete only the least recently used
|
||||
packages.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
|
||||
</para>
|
||||
@@ -320,6 +357,13 @@ deleting `/nix/store/kq82idx6g0nyzsp2s14gfsc38npai7lf-cairo-1.0.4.tar.gz.drv'
|
||||
|
||||
</para>
|
||||
|
||||
<para>To delete unreachable paths not accessed in the last two months:
|
||||
|
||||
<screen>
|
||||
$ nix-store --gc -v --max-atime $(date +%s -d "2 months ago")</screen>
|
||||
|
||||
</para>
|
||||
|
||||
<para>To delete at least 100 MiBs of unreachable paths:
|
||||
|
||||
<screen>
|
||||
@@ -342,7 +386,7 @@ $ nix-store --gc --max-freed $((100 * 1024 * 1024))</screen>
|
||||
|
||||
<cmdsynopsis>
|
||||
<command>nix-store</command>
|
||||
<arg choice='plain'><option>--delete</option></arg>
|
||||
<arg choice='plain'><option>--gc</option></arg>
|
||||
<arg><option>--ignore-liveness</option></arg>
|
||||
<arg choice='plain' rep='repeat'><replaceable>paths</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
@@ -404,8 +448,6 @@ error: cannot delete path `/nix/store/zq0h41l75vlb4z45kzgjjmsjxvcv1qk7-mesa-6.4'
|
||||
<arg choice='plain'><option>--tree</option></arg>
|
||||
<arg choice='plain'><option>--binding</option> <replaceable>name</replaceable></arg>
|
||||
<arg choice='plain'><option>--hash</option></arg>
|
||||
<arg choice='plain'><option>--size</option></arg>
|
||||
<arg choice='plain'><option>--roots</option></arg>
|
||||
</group>
|
||||
<arg><option>--use-output</option></arg>
|
||||
<arg><option>-u</option></arg>
|
||||
@@ -588,29 +630,9 @@ query is applied to the target of the symlink.</para>
|
||||
<varlistentry><term><option>--hash</option></term>
|
||||
|
||||
<listitem><para>Prints the SHA-256 hash of the contents of the
|
||||
store paths <replaceable>paths</replaceable> (that is, the hash of
|
||||
the output of <command>nix-store --dump</command> on the given
|
||||
paths). Since the hash is stored in the Nix database, this is a
|
||||
fast operation.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry><term><option>--size</option></term>
|
||||
|
||||
<listitem><para>Prints the size in bytes of the contents of the
|
||||
store paths <replaceable>paths</replaceable> — to be precise, the
|
||||
size of the output of <command>nix-store --dump</command> on the
|
||||
given paths. Note that the actual disk space required by the
|
||||
store paths may be higher, especially on filesystems with large
|
||||
cluster sizes.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry><term><option>--roots</option></term>
|
||||
|
||||
<listitem><para>Prints the garbage collector roots that point,
|
||||
directly or indirectly, at the store paths
|
||||
<replaceable>paths</replaceable>.</para></listitem>
|
||||
store path <replaceable>paths</replaceable>. Since the hash is
|
||||
stored in the Nix database, this is a fast
|
||||
operation.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
@@ -691,18 +713,6 @@ $ gv graph.ps</screen>
|
||||
|
||||
</para>
|
||||
|
||||
<para>Show every garbage collector root that points to a store path
|
||||
that depends on <command>svn</command>:
|
||||
|
||||
<screen>
|
||||
$ nix-store -q --roots $(which svn)
|
||||
/nix/var/nix/profiles/default-81-link
|
||||
/nix/var/nix/profiles/default-82-link
|
||||
/nix/var/nix/profiles/per-user/eelco/profile-97-link
|
||||
</screen>
|
||||
|
||||
</para>
|
||||
|
||||
</refsection>
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<refentry xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xml:id="sec-nix-worker">
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>nix-worker</refentrytitle>
|
||||
|
||||
@@ -13,10 +13,6 @@
|
||||
</group>
|
||||
<replaceable>number</replaceable>
|
||||
</arg>
|
||||
<arg>
|
||||
<arg><option>--cores</option></arg>
|
||||
<replaceable>number</replaceable>
|
||||
</arg>
|
||||
<arg>
|
||||
<arg><option>--max-silent-time</option></arg>
|
||||
<replaceable>number</replaceable>
|
||||
@@ -28,7 +24,6 @@
|
||||
<arg><option>--fallback</option></arg>
|
||||
<arg><option>--readonly-mode</option></arg>
|
||||
<arg><option>--log-type</option> <replaceable>type</replaceable></arg>
|
||||
<arg><option>--show-trace</option></arg>
|
||||
<sbr />
|
||||
|
||||
</nop>
|
||||
|
||||
@@ -98,25 +98,7 @@
|
||||
linkend='conf-build-max-jobs'><literal>build-max-jobs</literal></link>
|
||||
configuration setting, which itself defaults to
|
||||
<literal>1</literal>. A higher value is useful on SMP systems or to
|
||||
exploit I/O latency.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry xml:id="opt-cores"><term><option>--cores</option></term>
|
||||
|
||||
<listitem><para>Sets the value of the <envar>NIX_BUILD_CORES</envar>
|
||||
environment variable in the invocation of builders. Builders can
|
||||
use this variable at their discretion to control the maximum amount
|
||||
of parallelism. For instance, in Nixpkgs, if the derivation
|
||||
attribute <varname>enableParallelBuilding</varname> is set to
|
||||
<literal>true</literal>, the builder passes the
|
||||
<option>-j<replaceable>N</replaceable></option> flag to GNU Make.
|
||||
It defaults to the value of the <link
|
||||
linkend='conf-build-cores'><literal>build-cores</literal></link>
|
||||
configuration setting, if set, or <literal>1</literal> otherwise.
|
||||
The value <literal>0</literal> means that the builder should use all
|
||||
available CPU cores in the system.</para></listitem>
|
||||
exploit I/O latency. </para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
@@ -269,14 +251,14 @@
|
||||
|
||||
<programlisting>
|
||||
{ # The system (e.g., `i686-linux') for which to build the packages.
|
||||
system ? builtins.currentSystem
|
||||
system ? __currentSystem
|
||||
<replaceable>...</replaceable>
|
||||
}: <replaceable>...</replaceable></programlisting>
|
||||
|
||||
So if you call this Nix expression (e.g., when you do
|
||||
<literal>nix-env -i <replaceable>pkgname</replaceable></literal>),
|
||||
the function will be called automatically using the value <link
|
||||
linkend='builtin-currentSystem'><literal>builtins.currentSystem</literal></link>
|
||||
linkend='builtin-currentSystem'><literal>__currentSystem</literal></link>
|
||||
for the <literal>system</literal> argument. You can override this
|
||||
using <option>--arg</option>, e.g., <literal>nix-env -i
|
||||
<replaceable>pkgname</replaceable> --arg system
|
||||
@@ -323,14 +305,6 @@
|
||||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry><term><option>--show-trace</option></term>
|
||||
|
||||
<listitem><para>Causes Nix to print out a stack trace in case of Nix
|
||||
expression evaluation errors.</para></listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
|
||||
</variablelist>
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ to end-user applications like Mozilla Firefox. (Nix is however not
|
||||
tied to the Nix Package collection; you could write your own Nix
|
||||
expressions based on it, or completely new ones.) You can download
|
||||
the latest version from <link
|
||||
xlink:href='http://nixos.org/nixpkgs/download.html' />.</para>
|
||||
xlink:href='http://nixos.org/releases/full-index-nixpkgs.html' />.</para>
|
||||
|
||||
<para>Assuming that you have downloaded and unpacked a release of Nix
|
||||
Packages, you can view the set of available packages in the release:
|
||||
@@ -331,7 +331,7 @@ default profile, respectively. If the profile doesn’t exist, it will
|
||||
be created automatically. You should be careful about storing a
|
||||
profile in another location than the <filename>profiles</filename>
|
||||
directory, since otherwise it might not be used as a root of the
|
||||
garbage collector (see <xref linkend='sec-garbage-collection'
|
||||
garbage collector (see section <xref linkend='sec-garbage-collection'
|
||||
/>).</para>
|
||||
|
||||
<para>All <command>nix-env</command> operations work on the profile
|
||||
@@ -496,7 +496,7 @@ available in the subscribed channels.</para>
|
||||
|
||||
<para>Often, when you want to install a specific package (e.g., from
|
||||
the <link
|
||||
xlink:href="http://nixos.org/nixpkgs/">Nix
|
||||
xlink:href="http://nixos.org/releases/nixpkgs/nixpkgs-unstable/">Nix
|
||||
Packages collection</link>), subscribing to a channel is a bit
|
||||
cumbersome. And channels don’t help you at all if you want to install
|
||||
an older version of a package than the one provided by the current
|
||||
@@ -507,16 +507,19 @@ click on it, and it will be installed with all the necessary
|
||||
dependencies.</para>
|
||||
|
||||
<para>For instance, you can go to <link
|
||||
xlink:href="http://hydra.nixos.org/jobset/nixpkgs/trunk/channel/latest"
|
||||
/> and click on any link for the individual packages for your
|
||||
platform. The first time you do this, your browser will ask what to
|
||||
do with <literal>application/nix-package</literal> files. You should
|
||||
open them with <filename>/nix/bin/nix-install-package</filename>.
|
||||
This will open a window that asks you to confirm that you want to
|
||||
install the package. When you answer <literal>Y</literal>, the
|
||||
package and all its dependencies will be installed. This is a binary
|
||||
deployment mechanism — you get packages pre-compiled for the selected
|
||||
platform type.</para>
|
||||
xlink:href="http://nixos.org/releases/nixpkgs/nixpkgs-unstable/" /> —
|
||||
or to any older release of Nix Packages — and click on any link for
|
||||
the individual packages for your platform (say, <link
|
||||
xlink:href='http://nix.cs.uu.nl/dist/nix/nixpkgs-0.10pre6622/pkgs/subversion-1.4.0-i686-linux.nixpkg'><literal>subversion-1.4.0</literal>
|
||||
for <literal>i686-linux</literal></link>). The first time you do
|
||||
this, your browser will ask what to do with
|
||||
<literal>application/nix-package</literal> files. You should open
|
||||
them with <filename>/nix/bin/nix-install-package</filename>. This
|
||||
will open a window that asks you to confirm that you want to install
|
||||
the package. When you answer <literal>Y</literal>, the package and
|
||||
all its dependencies will be installed. This is a binary deployment
|
||||
mechanism — you get packages pre-compiled for the selected platform
|
||||
type.</para>
|
||||
|
||||
<para>You can also install <literal>application/nix-package</literal>
|
||||
files from the command line directly. See <xref
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<chapter xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xml:id="chap-quick-start">
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
<title>Quick Start</title>
|
||||
|
||||
@@ -11,9 +10,9 @@ to the following chapters.</para>
|
||||
|
||||
<orderedlist>
|
||||
|
||||
<listitem><para>Download a source tarball, RPM or Deb from <link
|
||||
xlink:href='http://nixos.org/'/>. Build source distributions using
|
||||
the regular sequence:
|
||||
<listitem><para>Download a source tarball or RPM from <link
|
||||
xlink:href='http://nixos.org/'/>. Build source
|
||||
distributions using the regular sequence:
|
||||
|
||||
<screen>
|
||||
$ tar xvfj nix-<replaceable>version</replaceable>.tar.bz2
|
||||
@@ -21,21 +20,13 @@ $ ./configure
|
||||
$ make
|
||||
$ make install <lineannotation>(as root)</lineannotation></screen>
|
||||
|
||||
This will install the Nix binaries in <filename>/usr/local</filename>
|
||||
and keep the Nix store and other state in <filename>/nix</filename>.
|
||||
You can change the former by specifying
|
||||
<option>--prefix=<replaceable>path</replaceable></option>. The
|
||||
location of the store can be changed using
|
||||
<option>--with-store-dir=<replaceable>path</replaceable></option>.
|
||||
However, you shouldn't change the store location, if at all possible,
|
||||
since that will make it impossible to use pre-built binaries from the
|
||||
Nixpkgs channel and other channels. The location of the state can be
|
||||
changed using
|
||||
<option>--localstatedir=<replaceable>path</replaceable>.</option></para></listitem>
|
||||
|
||||
<listitem><para>You should add
|
||||
<filename><replaceable>prefix</replaceable>/etc/profile.d/nix.sh</filename>
|
||||
to your <filename>~/.bashrc</filename> (or some other login
|
||||
This will install Nix in <filename>/nix</filename>. You shouldn't
|
||||
change the prefix if at all possible since that will make it
|
||||
impossible to use pre-built binaries from the Nixpkgs channel and
|
||||
other channels. Alternatively, you could grab an RPM if you're on an
|
||||
RPM-based system. You should also add
|
||||
<filename>/nix/etc/profile.d/nix.sh</filename> to your
|
||||
<filename>~/.bashrc</filename> (or some other login
|
||||
file).</para></listitem>
|
||||
|
||||
<listitem><para>Subscribe to the Nix Packages channel.
|
||||
@@ -60,7 +51,7 @@ available remotely.</para></listitem>
|
||||
in the channel:
|
||||
|
||||
<screen>
|
||||
$ nix-env -qa \*
|
||||
$ nix-env -qa ’*’ <lineannotation>(mind the quotes!)</lineannotation>
|
||||
docbook-xml-4.2
|
||||
firefox-1.0pre-PR-0.10.1
|
||||
hello-2.1.1
|
||||
@@ -108,7 +99,7 @@ numbers).</para></listitem>
|
||||
|
||||
<listitem><para>You can also install specific packages directly from
|
||||
your web browser. For instance, you can go to <link
|
||||
xlink:href="http://hydra.nixos.org/jobset/nixpkgs/trunk/channel/latest" />
|
||||
xlink:href="http://nix.cs.uu.nl/dist/nix/nixpkgs-unstable-latest/" />
|
||||
and click on any link for the individual packages for your platform.
|
||||
Associate <literal>application/nix-package</literal> with the program
|
||||
<filename>/nix/bin/nix-install-package</filename>. A window should
|
||||
|
||||
@@ -6,241 +6,6 @@
|
||||
|
||||
|
||||
|
||||
<!--==================================================================-->
|
||||
|
||||
<section xml:id="ssec-relnotes-1.0"><title>Release 1.0 (TBA)</title>
|
||||
|
||||
<para>This release has the following improvements:</para>
|
||||
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>Nix can now optionally use the Boehm garbage collector.
|
||||
This significantly reduces the Nix evaluator’s memory footprint,
|
||||
especially when evaluating large NixOS system configurations. It
|
||||
can be enabled using the <option>--enable-gc</option> configure
|
||||
option.</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<!--==================================================================-->
|
||||
|
||||
<section xml:id="ssec-relnotes-0.16"><title>Release 0.16 (August 17, 2010)</title>
|
||||
|
||||
<para>This release has the following improvements:</para>
|
||||
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>The Nix expression evaluator is now much faster in most
|
||||
cases: typically, <link
|
||||
xlink:href="http://www.mail-archive.com/nix-dev@cs.uu.nl/msg04113.html">3
|
||||
to 8 times compared to the old implementation</link>. It also
|
||||
uses less memory. It no longer depends on the ATerm
|
||||
library.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Support for configurable parallelism inside builders. Build
|
||||
scripts have always had the ability to perform multiple build
|
||||
actions in parallel (for instance, by running <command>make -j
|
||||
2</command>), but this was not desirable because the number of
|
||||
actions to be performed in parallel was not configurable. Nix
|
||||
now has an option <option>--cores
|
||||
<replaceable>N</replaceable></option> as well as a configuration
|
||||
setting <varname>build-cores =
|
||||
<replaceable>N</replaceable></varname> that causes the
|
||||
environment variable <envar>NIX_BUILD_CORES</envar> to be set to
|
||||
<replaceable>N</replaceable> when the builder is invoked. The
|
||||
builder can use this at its discretion to perform a parallel
|
||||
build, e.g., by calling <command>make -j
|
||||
<replaceable>N</replaceable></command>. In Nixpkgs, this can be
|
||||
enabled on a per-package basis by setting the derivation
|
||||
attribute <varname>enableParallelBuilding</varname> to
|
||||
<literal>true</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para><command>nix-store -q</command> now supports XML output
|
||||
through the <option>--xml</option> flag.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>Several bug fixes.</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<!--==================================================================-->
|
||||
|
||||
<section xml:id="ssec-relnotes-0.15"><title>Release 0.15 (March 17, 2010)</title>
|
||||
|
||||
<para>This is a bug-fix release. Among other things, it fixes
|
||||
building on Mac OS X (Snow Leopard), and improves the contents of
|
||||
<filename>/etc/passwd</filename> and <filename>/etc/group</filename>
|
||||
in <literal>chroot</literal> builds.</para>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<!--==================================================================-->
|
||||
|
||||
<section xml:id="ssec-relnotes-0.14"><title>Release 0.14 (February 4, 2010)</title>
|
||||
|
||||
<para>This release has the following improvements:</para>
|
||||
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>The garbage collector now starts deleting garbage much
|
||||
faster than before. It no longer determines liveness of all paths
|
||||
in the store, but does so on demand.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>Added a new operation, <command>nix-store --query
|
||||
--roots</command>, that shows the garbage collector roots that
|
||||
directly or indirectly point to the given store paths.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>Removed support for converting Berkeley DB-based Nix
|
||||
databases to the new schema.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>Removed the <option>--use-atime</option> and
|
||||
<option>--max-atime</option> garbage collector options. They were
|
||||
not very useful in practice.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>On Windows, Nix now requires Cygwin 1.7.x.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>A few bug fixes.</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<!--==================================================================-->
|
||||
|
||||
<section xml:id="ssec-relnotes-0.13"><title>Release 0.13 (November 5,
|
||||
2009)</title>
|
||||
|
||||
<para>This is primarily a bug fix release. It has some new
|
||||
features:</para>
|
||||
|
||||
<itemizedlist>
|
||||
|
||||
<listitem>
|
||||
<para>Syntactic sugar for writing nested attribute sets. Instead of
|
||||
|
||||
<programlisting>
|
||||
{
|
||||
foo = {
|
||||
bar = 123;
|
||||
xyzzy = true;
|
||||
};
|
||||
a = { b = { c = "d"; }; };
|
||||
}
|
||||
</programlisting>
|
||||
|
||||
you can write
|
||||
|
||||
<programlisting>
|
||||
{
|
||||
foo.bar = 123;
|
||||
foo.xyzzy = true;
|
||||
a.b.c = "d";
|
||||
}
|
||||
</programlisting>
|
||||
|
||||
This is useful, for instance, in NixOS configuration files.</para>
|
||||
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>Support for Nix channels generated by Hydra, the Nix-based
|
||||
continuous build system. (Hydra generates NAR archives on the
|
||||
fly, so the size and hash of these archives isn’t known in
|
||||
advance.)</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>Support <literal>i686-linux</literal> builds directly on
|
||||
<literal>x86_64-linux</literal> Nix installations. This is
|
||||
implemented using the <function>personality()</function> syscall,
|
||||
which causes <command>uname</command> to return
|
||||
<literal>i686</literal> in child processes.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>Various improvements to the <literal>chroot</literal>
|
||||
support. Building in a <literal>chroot</literal> works quite well
|
||||
now.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>Nix no longer blocks if it tries to build a path and another
|
||||
process is already building the same path. Instead it tries to
|
||||
build another buildable path first. This improves
|
||||
parallelism.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>Support for large (> 4 GiB) files in NAR archives.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>Various (performance) improvements to the remote build
|
||||
mechanism.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>New primops: <varname>builtins.addErrorContext</varname> (to
|
||||
add a string to stack traces — useful for debugging),
|
||||
<varname>builtins.isBool</varname>,
|
||||
<varname>builtins.isString</varname>,
|
||||
<varname>builtins.isInt</varname>,
|
||||
<varname>builtins.intersectAttrs</varname>.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>OpenSolaris support (Sander van der Burg).</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>Stack traces are no longer displayed unless the
|
||||
<option>--show-trace</option> option is used.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>The scoping rules for <literal>inherit
|
||||
(<replaceable>e</replaceable>) ...</literal> in recursive
|
||||
attribute sets have changed. The expression
|
||||
<replaceable>e</replaceable> can now refer to the attributes
|
||||
defined in the containing set.</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<!--==================================================================-->
|
||||
|
||||
<section xml:id="ssec-relnotes-0.12"><title>Release 0.12 (November 20,
|
||||
|
||||
@@ -1106,7 +1106,7 @@ used in the Nix expression for Subversion.</para>
|
||||
incompatibility might occur.</para>
|
||||
</callout>
|
||||
|
||||
<callout arearefs='ex-subversion-nix-co-3'>
|
||||
<callout arearefs='ex-subversion-nix-co-2'>
|
||||
<para>This assertion says that in order for Subversion to have SSL
|
||||
support (so that it can access <literal>https</literal> URLs), an
|
||||
OpenSSL library must be passed. Additionally, it says that
|
||||
@@ -1432,7 +1432,7 @@ command-line argument. See <xref linkend='sec-standard-environment'
|
||||
inputs.</para></listitem>
|
||||
|
||||
<listitem><para>After the build, Nix sets the last-modified
|
||||
timestamp on all files in the build result to 1 (00:00:01 1/1/1970
|
||||
timestamp on all files in the build result to 0 (00:00:00 1/1/1970
|
||||
UTC), sets the group to the default group, and sets the mode of the
|
||||
file to 0444 or 0555 (i.e., read-only, with execute permission
|
||||
enabled if the file was originally executable). Note that possible
|
||||
|
||||
132
externals/Makefile.am
vendored
132
externals/Makefile.am
vendored
@@ -1,3 +1,78 @@
|
||||
# Berkeley DB
|
||||
|
||||
DB = db-4.5.20
|
||||
|
||||
if OLD_DB_COMPAT
|
||||
|
||||
$(DB).tar.gz:
|
||||
@echo "Nix requires Berkeley DB to build."
|
||||
@echo "Please download version 4.5.20 from"
|
||||
@echo " http://download-east.oracle.com/berkeley-db/db-4.5.20.tar.gz"
|
||||
@echo "and place it in the externals/ directory."
|
||||
false
|
||||
|
||||
$(DB): $(DB).tar.gz
|
||||
gunzip < $(srcdir)/$(DB).tar.gz | tar xvf -
|
||||
(cd $(DB) && $(patch) -p1) < $(srcdir)/bdb-cygwin.patch
|
||||
|
||||
have-db:
|
||||
$(MAKE) $(DB)
|
||||
touch have-db
|
||||
|
||||
if HAVE_BDB
|
||||
build-db:
|
||||
else
|
||||
build-db: have-db
|
||||
(pfx=`pwd` && \
|
||||
cd $(DB)/build_unix && \
|
||||
CC="$(CC)" CXX="$(CXX)" CFLAGS="$(CFLAGS)" CXXFLAGS="$(CXXFLAGS)" \
|
||||
../dist/configure --prefix=$$pfx/inst-bdb \
|
||||
--enable-cxx --disable-shared --disable-cryptography \
|
||||
--disable-replication --disable-verify && \
|
||||
$(MAKE) && \
|
||||
$(MAKE) install_include install_lib)
|
||||
touch build-db
|
||||
endif
|
||||
|
||||
else
|
||||
|
||||
build-db:
|
||||
|
||||
endif
|
||||
|
||||
|
||||
# CWI ATerm
|
||||
|
||||
ATERM = aterm-2.4.2-fixes-r2
|
||||
|
||||
$(ATERM).tar.bz2:
|
||||
@echo "Nix requires the CWI ATerm library to build."
|
||||
@echo "Please download version 2.4.2-fixes-r2 from"
|
||||
@echo " http://losser.st-lab.cs.uu.nl/~eelco/dist/aterm-2.4.2-fixes-r2.tar.bz2"
|
||||
@echo "and place it in the externals/ directory."
|
||||
false
|
||||
|
||||
$(ATERM): $(ATERM).tar.bz2
|
||||
bunzip2 < $(srcdir)/$(ATERM).tar.bz2 | tar xvf -
|
||||
|
||||
have-aterm:
|
||||
$(MAKE) $(ATERM)
|
||||
touch have-aterm
|
||||
|
||||
if HAVE_ATERM
|
||||
build-aterm:
|
||||
else
|
||||
build-aterm: have-aterm
|
||||
(pfx=`pwd` && \
|
||||
cd $(ATERM) && \
|
||||
CC="$(CC)" ./configure --prefix=$$pfx/inst-aterm \
|
||||
--disable-shared --enable-static && \
|
||||
$(MAKE) && \
|
||||
$(MAKE) install)
|
||||
touch build-aterm
|
||||
endif
|
||||
|
||||
|
||||
# bzip2
|
||||
|
||||
BZIP2 = bzip2-1.0.5
|
||||
@@ -12,56 +87,31 @@ $(BZIP2).tar.gz:
|
||||
$(BZIP2): $(BZIP2).tar.gz
|
||||
gunzip < $(srcdir)/$(BZIP2).tar.gz | tar xvf -
|
||||
|
||||
have-bzip2:
|
||||
$(MAKE) $(BZIP2)
|
||||
touch have-bzip2
|
||||
|
||||
if HAVE_BZIP2
|
||||
build-bzip2:
|
||||
else
|
||||
build-bzip2: $(BZIP2)
|
||||
(cd $(BZIP2) && \
|
||||
$(MAKE) CC="$(CC)" && \
|
||||
$(MAKE) install PREFIX=$(abs_builddir)/inst-bzip2)
|
||||
build-bzip2: have-bzip2
|
||||
(pfx=`pwd` && \
|
||||
cd $(BZIP2) && \
|
||||
$(MAKE) && \
|
||||
$(MAKE) install PREFIX=$$pfx/inst-bzip2)
|
||||
touch build-bzip2
|
||||
|
||||
install-exec-local:: build-bzip2
|
||||
install:
|
||||
mkdir -p $(DESTDIR)${bzip2_bin}
|
||||
$(INSTALL_PROGRAM) $(bzip2_bin_test)/bzip2 $(bzip2_bin_test)/bunzip2 $(DESTDIR)${bzip2_bin}
|
||||
endif
|
||||
|
||||
|
||||
# SQLite
|
||||
all: build-db build-aterm build-bzip2
|
||||
|
||||
SQLITE = sqlite-autoconf-$(SQLITE_VERSION)
|
||||
SQLITE_TAR = sqlite-autoconf-$(SQLITE_VERSION).tar.gz
|
||||
EXTRA_DIST = $(DB).tar.gz $(ATERM).tar.bz2 $(BZIP2).tar.gz \
|
||||
bdb-cygwin.patch
|
||||
|
||||
$(SQLITE_TAR):
|
||||
@echo "Nix requires the SQLite library to build."
|
||||
@echo "Please download version $(SQLITE_VERSION) from"
|
||||
@echo " http://www.sqlite.org/$(SQLITE_TAR)"
|
||||
@echo "and place it in the externals/ directory."
|
||||
false
|
||||
|
||||
$(SQLITE): $(SQLITE_TAR)
|
||||
gzip -d < $(srcdir)/$(SQLITE_TAR) | tar xvf -
|
||||
|
||||
if HAVE_SQLITE
|
||||
build-sqlite:
|
||||
else
|
||||
build-sqlite: $(SQLITE)
|
||||
(cd $(SQLITE) && \
|
||||
CC="$(CC)" CFLAGS="-DSQLITE_ENABLE_COLUMN_METADATA=1" ./configure --disable-static --prefix=$(pkglibdir)/dummy --libdir=${pkglibdir} $(SUB_CONFIGURE_FLAGS) && \
|
||||
$(MAKE) )
|
||||
touch build-sqlite
|
||||
|
||||
install-exec-local:: build-sqlite
|
||||
cd $(SQLITE) && $(MAKE) install
|
||||
rm -rf "$(DESTDIR)/$(pkglibdir)/dummy"
|
||||
endif
|
||||
|
||||
|
||||
all: build-bzip2 build-sqlite
|
||||
|
||||
EXTRA_DIST = $(BZIP2).tar.gz $(SQLITE_TAR)
|
||||
|
||||
clean:
|
||||
$(RM) -f build-bzip2 build-sqlite
|
||||
$(RM) -rf $(BZIP2) $(SQLITE)
|
||||
$(RM) -rf inst-bzip2
|
||||
ext-clean:
|
||||
$(RM) -f have-db build-db have-aterm build-aterm have-bzip2 build-bzip2
|
||||
$(RM) -rf $(DB) $(ATERM) $(BZIP2)
|
||||
|
||||
22
externals/bdb-cygwin.patch
vendored
Normal file
22
externals/bdb-cygwin.patch
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
diff -rc db-4.5.20-orig/os/os_flock.c db-4.5.20/os/os_flock.c
|
||||
*** db-4.5.20-orig/os/os_flock.c 2006-10-13 12:36:12.000000000 +0200
|
||||
--- db-4.5.20/os/os_flock.c 2006-10-13 12:40:11.000000000 +0200
|
||||
***************
|
||||
*** 30,35 ****
|
||||
--- 30,44 ----
|
||||
|
||||
DB_ASSERT(dbenv, F_ISSET(fhp, DB_FH_OPENED) && fhp->fd != -1);
|
||||
|
||||
+ #ifdef __CYGWIN__
|
||||
+ /*
|
||||
+ * Windows file locking interferes with read/write operations, so we
|
||||
+ * map the ranges to an area past the end of the file.
|
||||
+ */
|
||||
+ DB_ASSERT(dbenv, offset < (off_t) 1 << 62);
|
||||
+ offset += (off_t) 1 << 62;
|
||||
+ #endif
|
||||
+
|
||||
fl.l_start = offset;
|
||||
fl.l_len = 1;
|
||||
fl.l_type = acquire ? F_WRLCK : F_UNLCK;
|
||||
Only in db-4.5.20/os: os_flock.c~
|
||||
@@ -76,10 +76,10 @@ The hook `nix-mode-hook' is run when Nix mode is started.
|
||||
("\\<baseNameOf\\>" . font-lock-builtin-face)
|
||||
("\\<toString\\>" . font-lock-builtin-face)
|
||||
("\\<isNull\\>" . font-lock-builtin-face)
|
||||
("\\<\\([a-zA-Z_][a-zA-Z0-9_']*\\)[ \t]*="
|
||||
(1 font-lock-variable-name-face nil nil))
|
||||
("[a-zA-Z][a-zA-Z0-9\\+-\\.]*:[a-zA-Z0-9%/\\?:@&=\\+\\$,_\\.!~\\*'-]+"
|
||||
. font-lock-constant-face)
|
||||
("\\<\\([a-zA-Z_][a-zA-Z0-9_'\.]*\\)[ \t]*="
|
||||
(1 font-lock-variable-name-face nil nil))
|
||||
("[a-zA-Z0-9._\\+-]*\\(/[a-zA-Z0-9._\\+-]+\\)+"
|
||||
. font-lock-constant-face)
|
||||
))
|
||||
|
||||
@@ -21,14 +21,12 @@ syn match nixFuncArg "\zs\w\+\ze\s*:"
|
||||
syn region nixStringParam start=+\${+ end=+}+
|
||||
syn region nixMultiLineComment start=+/\*+ skip=+\\"+ end=+\*/+
|
||||
syn match nixEndOfLineComment "#.*$"
|
||||
syn region nixStringIndented start=+''+ skip=+'''\|''${\|"+ end=+''+ contains=nixStringParam
|
||||
syn region nixString start=+"+ skip=+\\"+ end=+"+ contains=nixStringParam
|
||||
syn region nixString start=+"+ skip=+\\"+ end=+"+ contains=nixStringParam
|
||||
|
||||
hi def link nixKeyword Keyword
|
||||
hi def link nixConditional Conditional
|
||||
hi def link nixBrace Special
|
||||
hi def link nixString String
|
||||
hi def link nixStringIndented String
|
||||
hi def link nixBuiltin Special
|
||||
hi def link nixStringParam Macro
|
||||
hi def link nixMultiLineComment Comment
|
||||
|
||||
@@ -59,17 +59,6 @@
|
||||
#build-max-jobs = 1
|
||||
|
||||
|
||||
### Option `build-cores'
|
||||
#
|
||||
# This option defines the number of CPU cores to utilize in parallel
|
||||
# within a build job, i.e. by passing an appropriate `-jN' flag to GNU
|
||||
# Make. The default is 1, meaning that parallel building within jobs
|
||||
# is disabled. Passing the special value `0' causes Nix to try and
|
||||
# auto-detect the number of available cores on the local host. This
|
||||
# setting can be overridden using the `--cores' command line switch.
|
||||
#build-cores = 1
|
||||
|
||||
|
||||
### Option `build-max-silent-time'
|
||||
#
|
||||
# This option defines the maximum number of seconds that a builder can
|
||||
@@ -165,17 +154,21 @@
|
||||
#build-chroot-dirs = /dev /dev/pts /proc
|
||||
|
||||
|
||||
### Option `build-cache-failure'
|
||||
### Option `system'
|
||||
#
|
||||
# If this option is enabled, Nix will do negative caching; that is, it
|
||||
# will remember failed builds, and won't attempt to try to build them
|
||||
# again if you ask for it. Negative caching is disabled by default
|
||||
# because Nix cannot distinguish between permanent build errors (e.g.,
|
||||
# a syntax error in a source file) and transient build errors (e.g., a
|
||||
# full disk), as they both cause the builder to return a non-zero exit
|
||||
# code. You can clear the cache by doing `rm -f
|
||||
# /nix/var/nix/db/failed/*'.
|
||||
# This option specifies the canonical Nix system name of the current
|
||||
# installation, such as `i686-linux' or `powerpc-darwin'. Nix can
|
||||
# only build derivations whose `system' attribute equals the value
|
||||
# specified here. In general, it never makes sense to modify this
|
||||
# value from its default, since you can use it to `lie' about the
|
||||
# platform you are building on (e.g., perform a Mac OS build on a
|
||||
# Linux machine; the result would obviously be wrong). It only makes
|
||||
# sense if the Nix binaries can run on multiple platforms, e.g.,
|
||||
# `universal binaries' that run on `powerpc-darwin' and `i686-darwin'.
|
||||
#
|
||||
# It defaults to the canonical Nix system name detected by `configure'
|
||||
# at build time.
|
||||
#
|
||||
# Example:
|
||||
# build-cache-failure = true
|
||||
#build-cache-failure = false
|
||||
# system = i686-darwin
|
||||
#system =
|
||||
|
||||
177
release.nix
177
release.nix
@@ -1,177 +0,0 @@
|
||||
{ nixpkgs ? ../nixpkgs
|
||||
, nix ? { outPath = ./.; rev = 1234; }
|
||||
, officialRelease ? false
|
||||
}:
|
||||
|
||||
let
|
||||
|
||||
jobs = rec {
|
||||
|
||||
|
||||
tarball =
|
||||
with import nixpkgs {};
|
||||
|
||||
releaseTools.sourceTarball {
|
||||
name = "nix-tarball";
|
||||
version = builtins.readFile ./version;
|
||||
src = nix;
|
||||
inherit officialRelease;
|
||||
|
||||
buildInputs =
|
||||
[ curl bison24 flex2535 perl libxml2 libxslt w3m bzip2
|
||||
tetex dblatex nukeReferences pkgconfig
|
||||
];
|
||||
|
||||
configureFlags = ''
|
||||
--with-docbook-rng=${docbook5}/xml/rng/docbook
|
||||
--with-docbook-xsl=${docbook5_xsl}/xml/xsl/docbook
|
||||
--with-xml-flags=--nonet
|
||||
'';
|
||||
|
||||
# Include the Bzip2 tarball in the distribution.
|
||||
preConfigure = ''
|
||||
stripHash ${bzip2.src}
|
||||
cp -pv ${bzip2.src} externals/$strippedName
|
||||
|
||||
stripHash ${sqlite.src}
|
||||
cp -pv ${sqlite.src} externals/$strippedName
|
||||
|
||||
# TeX needs a writable font cache.
|
||||
export VARTEXFONTS=$TMPDIR/texfonts
|
||||
'';
|
||||
|
||||
preDist = ''
|
||||
make -C doc/manual install prefix=$out
|
||||
|
||||
make -C doc/manual manual.pdf prefix=$out
|
||||
cp doc/manual/manual.pdf $out/manual.pdf
|
||||
|
||||
# The PDF containes filenames of included graphics (see
|
||||
# http://www.tug.org/pipermail/pdftex/2007-August/007290.html).
|
||||
# This causes a retained dependency on dblatex, which Hydra
|
||||
# doesn't like (the output of the tarball job is distributed
|
||||
# to Windows and Macs, so there should be no Linux binaries
|
||||
# in the closure).
|
||||
nuke-refs $out/manual.pdf
|
||||
|
||||
echo "doc manual $out/share/doc/nix/manual" >> $out/nix-support/hydra-build-products
|
||||
echo "doc-pdf manual $out/manual.pdf" >> $out/nix-support/hydra-build-products
|
||||
echo "doc release-notes $out/share/doc/nix/release-notes" >> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
build =
|
||||
{ system ? "i686-linux" }:
|
||||
|
||||
with import nixpkgs { inherit system; };
|
||||
|
||||
releaseTools.nixBuild {
|
||||
name = "nix";
|
||||
src = tarball;
|
||||
|
||||
buildInputs = [ curl perl bzip2 openssl pkgconfig boehmgc ];
|
||||
|
||||
configureFlags = ''
|
||||
--disable-init-state
|
||||
--with-bzip2=${bzip2} --with-sqlite=${sqlite}
|
||||
--enable-gc
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
coverage =
|
||||
with import nixpkgs { system = "x86_64-linux"; };
|
||||
|
||||
releaseTools.coverageAnalysis {
|
||||
name = "nix-build";
|
||||
src = tarball;
|
||||
|
||||
buildInputs =
|
||||
[ curl perl bzip2 openssl
|
||||
# These are for "make check" only:
|
||||
graphviz libxml2 libxslt
|
||||
];
|
||||
|
||||
configureFlags = ''
|
||||
--disable-init-state --disable-shared
|
||||
--with-bzip2=${bzip2} --with-sqlite=${sqlite}
|
||||
'';
|
||||
|
||||
lcovFilter = [ "*/boost/*" "*-tab.*" ];
|
||||
|
||||
# We call `dot', and even though we just use it to
|
||||
# syntax-check generated dot files, it still requires some
|
||||
# fonts. So provide those.
|
||||
FONTCONFIG_FILE = texFunctions.fontsConf;
|
||||
};
|
||||
|
||||
|
||||
rpm_fedora5i386 = makeRPM_i686 (diskImages: diskImages.fedora5i386) 10;
|
||||
rpm_fedora9i386 = makeRPM_i686 (diskImages: diskImages.fedora9i386) 20;
|
||||
rpm_fedora9x86_64 = makeRPM_x86_64 (diskImages: diskImages.fedora9x86_64) 20;
|
||||
rpm_fedora10i386 = makeRPM_i686 (diskImages: diskImages.fedora10i386) 30;
|
||||
rpm_fedora10x86_64 = makeRPM_x86_64 (diskImages: diskImages.fedora10x86_64) 30;
|
||||
rpm_fedora11i386 = makeRPM_i686 (diskImages: diskImages.fedora11i386) 40;
|
||||
rpm_fedora11x86_64 = makeRPM_x86_64 (diskImages: diskImages.fedora11x86_64) 40;
|
||||
rpm_fedora12i386 = makeRPM_i686 (diskImages: diskImages.fedora12i386) 50;
|
||||
rpm_fedora12x86_64 = makeRPM_x86_64 (diskImages: diskImages.fedora12x86_64) 50;
|
||||
rpm_opensuse103i386 = makeRPM_i686 (diskImages: diskImages.opensuse103i386) 40;
|
||||
rpm_opensuse110i386 = makeRPM_i686 (diskImages: diskImages.opensuse110i386) 50;
|
||||
rpm_opensuse110x86_64 = makeRPM_x86_64 (diskImages: diskImages.opensuse110x86_64) 50;
|
||||
|
||||
|
||||
deb_debian40i386 = makeDeb_i686 (diskImages: diskImages.debian40i386) 40;
|
||||
deb_debian40x86_64 = makeDeb_x86_64 (diskImages: diskImages.debian40x86_64) 40;
|
||||
deb_debian50i386 = makeDeb_i686 (diskImages: diskImages.debian50i386) 50;
|
||||
deb_debian50x86_64 = makeDeb_x86_64 (diskImages: diskImages.debian50x86_64) 50;
|
||||
deb_ubuntu804i386 = makeDeb_i686 (diskImages: diskImages.ubuntu804i386) 20;
|
||||
deb_ubuntu804x86_64 = makeDeb_x86_64 (diskImages: diskImages.ubuntu804x86_64) 20;
|
||||
deb_ubuntu810i386 = makeDeb_i686 (diskImages: diskImages.ubuntu810i386) 30;
|
||||
deb_ubuntu810x86_64 = makeDeb_x86_64 (diskImages: diskImages.ubuntu810x86_64) 30;
|
||||
deb_ubuntu904i386 = makeDeb_i686 (diskImages: diskImages.ubuntu904i386) 40;
|
||||
deb_ubuntu904x86_64 = makeDeb_x86_64 (diskImages: diskImages.ubuntu904x86_64) 40;
|
||||
deb_ubuntu910i386 = makeDeb_i686 (diskImages: diskImages.ubuntu910i386) 50;
|
||||
deb_ubuntu910x86_64 = makeDeb_x86_64 (diskImages: diskImages.ubuntu910x86_64) 50;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
makeRPM_i686 = makeRPM "i686-linux";
|
||||
makeRPM_x86_64 = makeRPM "x86_64-linux";
|
||||
|
||||
makeRPM =
|
||||
system: diskImageFun: prio:
|
||||
|
||||
with import nixpkgs { inherit system; };
|
||||
|
||||
releaseTools.rpmBuild rec {
|
||||
name = "nix-rpm-${diskImage.name}";
|
||||
src = jobs.tarball;
|
||||
diskImage = diskImageFun vmTools.diskImages;
|
||||
memSize = 1024;
|
||||
meta.schedulingPriority = prio;
|
||||
};
|
||||
|
||||
|
||||
makeDeb_i686 = makeDeb "i686-linux";
|
||||
makeDeb_x86_64 = makeDeb "x86_64-linux";
|
||||
|
||||
makeDeb =
|
||||
system: diskImageFun: prio:
|
||||
|
||||
with import nixpkgs { inherit system; };
|
||||
|
||||
releaseTools.debBuild {
|
||||
name = "nix-deb";
|
||||
src = jobs.tarball;
|
||||
diskImage = diskImageFun vmTools.diskImages;
|
||||
memSize = 1024;
|
||||
meta.schedulingPriority = prio;
|
||||
configureFlags = "--sysconfdir=/etc";
|
||||
debRequires = [ "curl" ];
|
||||
};
|
||||
|
||||
|
||||
in jobs
|
||||
@@ -1,334 +0,0 @@
|
||||
#! @perl@ -w -I@libexecdir@/nix
|
||||
|
||||
use strict;
|
||||
use File::Temp qw(tempdir);
|
||||
|
||||
|
||||
# Some patch generations options.
|
||||
|
||||
# Max size of NAR archives to generate patches for.
|
||||
my $maxNarSize = $ENV{"NIX_MAX_NAR_SIZE"};
|
||||
$maxNarSize = 160 * 1024 * 1024 if !defined $maxNarSize;
|
||||
|
||||
# If patch is bigger than this fraction of full archive, reject.
|
||||
my $maxPatchFraction = $ENV{"NIX_PATCH_FRACTION"};
|
||||
$maxPatchFraction = 0.60 if !defined $maxPatchFraction;
|
||||
|
||||
my $timeLimit = $ENV{"NIX_BSDIFF_TIME_LIMIT"};
|
||||
$timeLimit = 180 if !defined $timeLimit;
|
||||
|
||||
my $hashAlgo = "sha256";
|
||||
|
||||
|
||||
sub findOutputPaths {
|
||||
my $narFiles = shift;
|
||||
|
||||
my %outPaths;
|
||||
|
||||
foreach my $p (keys %{$narFiles}) {
|
||||
|
||||
# Ignore derivations.
|
||||
next if ($p =~ /\.drv$/);
|
||||
|
||||
# Ignore builders (too much ambiguity -- they're all called
|
||||
# `builder.sh').
|
||||
next if ($p =~ /\.sh$/);
|
||||
next if ($p =~ /\.patch$/);
|
||||
|
||||
# Don't bother including tar files etc.
|
||||
next if ($p =~ /\.tar$/ || $p =~ /\.tar\.(gz|bz2|Z|lzma|xz)$/ || $p =~ /\.zip$/ || $p =~ /\.bin$/ || $p =~ /\.tgz$/ || $p =~ /\.rpm$/ || $p =~ /cvs-export$/ || $p =~ /fetchhg$/);
|
||||
|
||||
$outPaths{$p} = 1;
|
||||
}
|
||||
|
||||
return %outPaths;
|
||||
}
|
||||
|
||||
|
||||
sub getNameVersion {
|
||||
my $p = shift;
|
||||
$p =~ /\/[0-9a-z]+((?:-[a-zA-Z][^\/-]*)+)([^\/]*)$/;
|
||||
my $name = $1;
|
||||
my $version = $2;
|
||||
return undef unless defined $name && defined $version;
|
||||
$name =~ s/^-//;
|
||||
$version =~ s/^-//;
|
||||
return ($name, $version);
|
||||
}
|
||||
|
||||
|
||||
# A quick hack to get a measure of the `distance' between two
|
||||
# versions: it's just the position of the first character that differs
|
||||
# (or 999 if they are the same).
|
||||
sub versionDiff {
|
||||
my $s = shift;
|
||||
my $t = shift;
|
||||
my $i;
|
||||
return 999 if $s eq $t;
|
||||
for ($i = 0; $i < length $s; $i++) {
|
||||
return $i if $i >= length $t or
|
||||
substr($s, $i, 1) ne substr($t, $i, 1);
|
||||
}
|
||||
return $i;
|
||||
}
|
||||
|
||||
|
||||
sub getNarBz2 {
|
||||
my $narPath = shift;
|
||||
my $narFiles = shift;
|
||||
my $storePath = shift;
|
||||
|
||||
my $narFileList = $$narFiles{$storePath};
|
||||
die "missing path $storePath" unless defined $narFileList;
|
||||
|
||||
my $narFile = @{$narFileList}[0];
|
||||
die unless defined $narFile;
|
||||
|
||||
$narFile->{url} =~ /\/([^\/]+)$/;
|
||||
die unless defined $1;
|
||||
return "$narPath/$1";
|
||||
}
|
||||
|
||||
|
||||
sub containsPatch {
|
||||
my $patches = shift;
|
||||
my $storePath = shift;
|
||||
my $basePath = shift;
|
||||
my $patchList = $$patches{$storePath};
|
||||
return 0 if !defined $patchList;
|
||||
my $found = 0;
|
||||
foreach my $patch (@{$patchList}) {
|
||||
# !!! baseHash might differ
|
||||
return 1 if $patch->{basePath} eq $basePath;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
sub generatePatches {
|
||||
my ($srcNarFiles, $dstNarFiles, $srcPatches, $dstPatches, $narPath, $patchesPath, $patchesURL, $tmpDir) = @_;
|
||||
|
||||
my %srcOutPaths = findOutputPaths $srcNarFiles;
|
||||
my %dstOutPaths = findOutputPaths $dstNarFiles;
|
||||
|
||||
# For each output path in the destination, see if we need to / can
|
||||
# create a patch.
|
||||
|
||||
print STDERR "creating patches...\n";
|
||||
|
||||
foreach my $p (keys %dstOutPaths) {
|
||||
|
||||
# If exactly the same path already exists in the source, skip it.
|
||||
next if defined $srcOutPaths{$p};
|
||||
|
||||
print " $p\n";
|
||||
|
||||
# If not, then we should find the paths in the source that are
|
||||
# `most' likely to be present on a system that wants to
|
||||
# install this path.
|
||||
|
||||
(my $name, my $version) = getNameVersion $p;
|
||||
next unless defined $name && defined $version;
|
||||
|
||||
my @closest = ();
|
||||
my $closestVersion;
|
||||
my $minDist = -1; # actually, larger means closer
|
||||
|
||||
# Find all source paths with the same name.
|
||||
|
||||
foreach my $q (keys %srcOutPaths) {
|
||||
(my $name2, my $version2) = getNameVersion $q;
|
||||
next unless defined $name2 && defined $version2;
|
||||
|
||||
if ($name eq $name2) {
|
||||
|
||||
my $srcSystem = @{$$dstNarFiles{$p}}[0]->{system};
|
||||
my $dstSystem = @{$$srcNarFiles{$q}}[0]->{system};
|
||||
if (defined $srcSystem && defined $dstSystem && $srcSystem ne $dstSystem) {
|
||||
print " SKIPPING $q due to different systems ($srcSystem vs. $dstSystem)\n";
|
||||
next;
|
||||
}
|
||||
|
||||
# If the sizes differ too much, then skip. This
|
||||
# disambiguates between, e.g., a real component and a
|
||||
# wrapper component (cf. Firefox in Nixpkgs).
|
||||
my $srcSize = @{$$srcNarFiles{$q}}[0]->{size};
|
||||
my $dstSize = @{$$dstNarFiles{$p}}[0]->{size};
|
||||
my $ratio = $srcSize / $dstSize;
|
||||
$ratio = 1 / $ratio if $ratio < 1;
|
||||
# print " SIZE $srcSize $dstSize $ratio $q\n";
|
||||
|
||||
if ($ratio >= 3) {
|
||||
print " SKIPPING $q due to size ratio $ratio ($srcSize vs. $dstSize)\n";
|
||||
next;
|
||||
}
|
||||
|
||||
# If there are multiple matching names, include the
|
||||
# ones with the closest version numbers.
|
||||
my $dist = versionDiff $version, $version2;
|
||||
if ($dist > $minDist) {
|
||||
$minDist = $dist;
|
||||
@closest = ($q);
|
||||
$closestVersion = $version2;
|
||||
} elsif ($dist == $minDist) {
|
||||
push @closest, $q;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar(@closest) == 0) {
|
||||
print " NO BASE: $p\n";
|
||||
next;
|
||||
}
|
||||
|
||||
foreach my $closest (@closest) {
|
||||
|
||||
# Generate a patch between $closest and $p.
|
||||
print STDERR " $p <- $closest\n";
|
||||
|
||||
# If the patch already exists, skip it.
|
||||
if (containsPatch($srcPatches, $p, $closest) ||
|
||||
containsPatch($dstPatches, $p, $closest))
|
||||
{
|
||||
print " skipping, already exists\n";
|
||||
next;
|
||||
}
|
||||
|
||||
my $srcNarBz2 = getNarBz2 $narPath, $srcNarFiles, $closest;
|
||||
my $dstNarBz2 = getNarBz2 $narPath, $dstNarFiles, $p;
|
||||
|
||||
if (! -f $srcNarBz2) {
|
||||
warn "patch source archive $srcNarBz2 is missing\n";
|
||||
next;
|
||||
}
|
||||
|
||||
system("@bunzip2@ < $srcNarBz2 > $tmpDir/A") == 0
|
||||
or die "cannot unpack $srcNarBz2";
|
||||
|
||||
if ((stat "$tmpDir/A")[7] >= $maxNarSize) {
|
||||
print " skipping, source is too large\n";
|
||||
next;
|
||||
}
|
||||
|
||||
system("@bunzip2@ < $dstNarBz2 > $tmpDir/B") == 0
|
||||
or die "cannot unpack $dstNarBz2";
|
||||
|
||||
if ((stat "$tmpDir/B")[7] >= $maxNarSize) {
|
||||
print " skipping, destination is too large\n";
|
||||
next;
|
||||
}
|
||||
|
||||
my $time1 = time();
|
||||
my $res = system("ulimit -t $timeLimit; @libexecdir@/bsdiff $tmpDir/A $tmpDir/B $tmpDir/DIFF");
|
||||
my $time2 = time();
|
||||
if ($res) {
|
||||
warn "binary diff computation aborted after ", $time2 - $time1, " seconds\n";
|
||||
next;
|
||||
}
|
||||
|
||||
my $baseHash = `@bindir@/nix-hash --flat --type $hashAlgo --base32 $tmpDir/A` or die;
|
||||
chomp $baseHash;
|
||||
|
||||
my $narHash = `@bindir@/nix-hash --flat --type $hashAlgo --base32 $tmpDir/B` or die;
|
||||
chomp $narHash;
|
||||
|
||||
my $narDiffHash = `@bindir@/nix-hash --flat --type $hashAlgo --base32 $tmpDir/DIFF` or die;
|
||||
chomp $narDiffHash;
|
||||
|
||||
my $narDiffSize = (stat "$tmpDir/DIFF")[7];
|
||||
my $dstNarBz2Size = (stat $dstNarBz2)[7];
|
||||
|
||||
print " size $narDiffSize; full size $dstNarBz2Size; ", $time2 - $time1, " seconds\n";
|
||||
|
||||
if ($narDiffSize >= $dstNarBz2Size) {
|
||||
print " rejecting; patch bigger than full archive\n";
|
||||
next;
|
||||
}
|
||||
|
||||
if ($narDiffSize / $dstNarBz2Size >= $maxPatchFraction) {
|
||||
print " rejecting; patch too large relative to full archive\n";
|
||||
next;
|
||||
}
|
||||
|
||||
my $finalName = "$narDiffHash.nar-bsdiff";
|
||||
|
||||
if (-e "$patchesPath/$finalName") {
|
||||
print " not copying, already exists\n";
|
||||
}
|
||||
|
||||
else {
|
||||
system("cp '$tmpDir/DIFF' '$patchesPath/$finalName.tmp'") == 0
|
||||
or die "cannot copy diff";
|
||||
rename("$patchesPath/$finalName.tmp", "$patchesPath/$finalName")
|
||||
or die "cannot rename $patchesPath/$finalName.tmp";
|
||||
}
|
||||
|
||||
# Add the patch to the manifest.
|
||||
addPatch $dstPatches, $p,
|
||||
{ url => "$patchesURL/$finalName", hash => "$hashAlgo:$narDiffHash"
|
||||
, size => $narDiffSize, basePath => $closest, baseHash => "$hashAlgo:$baseHash"
|
||||
, narHash => "$hashAlgo:$narHash", patchType => "nar-bsdiff"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Propagate useful patches from $srcPatches to $dstPatches. A patch
|
||||
# is useful if it produces either paths in the $dstNarFiles or paths
|
||||
# that can be used as the base for other useful patches.
|
||||
sub propagatePatches {
|
||||
my ($srcPatches, $dstNarFiles, $dstPatches) = @_;
|
||||
|
||||
print STDERR "propagating patches...\n";
|
||||
|
||||
my $changed;
|
||||
do {
|
||||
# !!! we repeat this to reach the transitive closure; inefficient
|
||||
$changed = 0;
|
||||
|
||||
print STDERR "loop\n";
|
||||
|
||||
my %dstBasePaths;
|
||||
foreach my $q (keys %{$dstPatches}) {
|
||||
foreach my $patch (@{$$dstPatches{$q}}) {
|
||||
$dstBasePaths{$patch->{basePath}} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $p (keys %{$srcPatches}) {
|
||||
my $patchList = $$srcPatches{$p};
|
||||
|
||||
my $include = 0;
|
||||
|
||||
# Is path $p included in the destination? If so, include
|
||||
# patches that produce it.
|
||||
$include = 1 if defined $$dstNarFiles{$p};
|
||||
|
||||
# Is path $p a path that serves as a base for paths in the
|
||||
# destination? If so, include patches that produce it.
|
||||
# !!! check baseHash
|
||||
$include = 1 if defined $dstBasePaths{$p};
|
||||
|
||||
if ($include) {
|
||||
foreach my $patch (@{$patchList}) {
|
||||
$changed = 1 if addPatch $dstPatches, $p, $patch;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} while $changed;
|
||||
}
|
||||
|
||||
|
||||
# Add all new patches in $srcPatches to $dstPatches.
|
||||
sub copyPatches {
|
||||
my ($srcPatches, $dstPatches) = @_;
|
||||
foreach my $p (keys %{$srcPatches}) {
|
||||
addPatch $dstPatches, $p, $_ foreach @{$$srcPatches{$p}};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return 1;
|
||||
@@ -1,23 +1,22 @@
|
||||
bin_SCRIPTS = nix-collect-garbage \
|
||||
nix-pull nix-push nix-prefetch-url \
|
||||
nix-install-package nix-channel nix-build \
|
||||
nix-copy-closure nix-generate-patches
|
||||
nix-copy-closure
|
||||
|
||||
noinst_SCRIPTS = nix-profile.sh GeneratePatches.pm \
|
||||
noinst_SCRIPTS = nix-profile.sh generate-patches.pl \
|
||||
find-runtime-roots.pl build-remote.pl nix-reduce-build \
|
||||
copy-from-other-stores.pl nix-http-export.cgi
|
||||
|
||||
nix-pull nix-push: NixManifest.pm NixConfig.pm download-using-manifests.pl
|
||||
nix-pull nix-push: readmanifest.pm readconfig.pm download-using-manifests.pl
|
||||
|
||||
install-exec-local: NixManifest.pm GeneratePatches.pm download-using-manifests.pl copy-from-other-stores.pl find-runtime-roots.pl
|
||||
install-exec-local: readmanifest.pm download-using-manifests.pl copy-from-other-stores.pl find-runtime-roots.pl
|
||||
$(INSTALL) -d $(DESTDIR)$(sysconfdir)/profile.d
|
||||
$(INSTALL_PROGRAM) nix-profile.sh $(DESTDIR)$(sysconfdir)/profile.d/nix.sh
|
||||
$(INSTALL) -d $(DESTDIR)$(libexecdir)/nix
|
||||
$(INSTALL_DATA) NixManifest.pm $(DESTDIR)$(libexecdir)/nix
|
||||
$(INSTALL_DATA) NixConfig.pm $(DESTDIR)$(libexecdir)/nix
|
||||
$(INSTALL_DATA) SSH.pm $(DESTDIR)$(libexecdir)/nix
|
||||
$(INSTALL_DATA) GeneratePatches.pm $(DESTDIR)$(libexecdir)/nix
|
||||
$(INSTALL_DATA) readmanifest.pm $(DESTDIR)$(libexecdir)/nix
|
||||
$(INSTALL_DATA) readconfig.pm $(DESTDIR)$(libexecdir)/nix
|
||||
$(INSTALL_PROGRAM) find-runtime-roots.pl $(DESTDIR)$(libexecdir)/nix
|
||||
$(INSTALL_PROGRAM) generate-patches.pl $(DESTDIR)$(libexecdir)/nix
|
||||
$(INSTALL_PROGRAM) build-remote.pl $(DESTDIR)$(libexecdir)/nix
|
||||
$(INSTALL) -d $(DESTDIR)$(libexecdir)/nix/substituters
|
||||
$(INSTALL_PROGRAM) download-using-manifests.pl $(DESTDIR)$(libexecdir)/nix/substituters
|
||||
@@ -30,16 +29,14 @@ EXTRA_DIST = nix-collect-garbage.in \
|
||||
nix-pull.in nix-push.in nix-profile.sh.in \
|
||||
nix-prefetch-url.in nix-install-package.in \
|
||||
nix-channel.in \
|
||||
NixManifest.pm.in \
|
||||
NixConfig.pm.in \
|
||||
SSH.pm \
|
||||
GeneratePatches.pm.in \
|
||||
readmanifest.pm.in \
|
||||
readconfig.pm.in \
|
||||
nix-build.in \
|
||||
download-using-manifests.pl.in \
|
||||
copy-from-other-stores.pl.in \
|
||||
generate-patches.pl.in \
|
||||
nix-copy-closure.in \
|
||||
find-runtime-roots.pl.in \
|
||||
build-remote.pl.in \
|
||||
nix-reduce-build.in \
|
||||
nix-http-export.cgi.in \
|
||||
nix-generate-patches.in
|
||||
nix-http-export.cgi.in
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
use strict;
|
||||
use File::Temp qw(tempdir);
|
||||
|
||||
our @sshOpts = split ' ', ($ENV{"NIX_SSHOPTS"} or "");
|
||||
|
||||
push @sshOpts, "-x";
|
||||
|
||||
my $sshStarted = 0;
|
||||
my $sshHost;
|
||||
|
||||
# Open a master SSH connection to `host', unless there already is a
|
||||
# running master connection (as determined by `-O check').
|
||||
sub openSSHConnection {
|
||||
my ($host) = @_;
|
||||
die if $sshStarted;
|
||||
$sshHost = $host;
|
||||
return 1 if system("ssh $sshHost @sshOpts -O check 2> /dev/null") == 0;
|
||||
|
||||
my $tmpDir = tempdir("nix-ssh.XXXXXX", CLEANUP => 1, TMPDIR => 1)
|
||||
or die "cannot create a temporary directory";
|
||||
|
||||
push @sshOpts, "-S", "$tmpDir/control";
|
||||
|
||||
# Start the master. We can't use the `-f' flag (fork into
|
||||
# background after establishing the connection) because then the
|
||||
# child continues to run if we are killed. So instead make SSH
|
||||
# print "started" when it has established the connection, and wait
|
||||
# until we see that.
|
||||
open SSHPIPE, "ssh $sshHost @sshOpts -M -N -o LocalCommand='echo started' -o PermitLocalCommand=yes |" or die;
|
||||
|
||||
while (<SSHPIPE>) {
|
||||
chomp;
|
||||
if ($_ eq "started") {
|
||||
$sshStarted = 1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
# Tell the master SSH client to exit.
|
||||
sub closeSSHConnection {
|
||||
if ($sshStarted) {
|
||||
system("ssh $sshHost @sshOpts -O exit 2> /dev/null") == 0
|
||||
or warn "unable to stop SSH master: $?";
|
||||
}
|
||||
}
|
||||
|
||||
END { my $saved = $?; closeSSHConnection; $? = $saved; }
|
||||
|
||||
return 1;
|
||||
@@ -1,11 +1,8 @@
|
||||
#! @perl@ -w -I@libexecdir@/nix
|
||||
#! @perl@ -w
|
||||
|
||||
use strict;
|
||||
use Fcntl ':flock';
|
||||
use English '-no_match_vars';
|
||||
use IO::Handle;
|
||||
use SSH qw/sshOpts openSSHConnection/;
|
||||
no warnings('once');
|
||||
|
||||
|
||||
# General operation:
|
||||
#
|
||||
@@ -24,246 +21,188 @@ no warnings('once');
|
||||
# The nice thing about this scheme is that if we die prematurely, the
|
||||
# locks are released automatically.
|
||||
|
||||
my $loadIncreased = 0;
|
||||
|
||||
my $amWilling = shift @ARGV;
|
||||
my $localSystem = shift @ARGV;
|
||||
my $neededSystem = shift @ARGV;
|
||||
my $drvPath = shift @ARGV;
|
||||
|
||||
sub sendReply {
|
||||
my $reply = shift;
|
||||
open OUT, ">&3" or die;
|
||||
print OUT "$reply\n";
|
||||
close OUT;
|
||||
}
|
||||
|
||||
sub decline {
|
||||
sendReply "decline";
|
||||
exit 0;
|
||||
}
|
||||
|
||||
my $currentLoad = $ENV{"NIX_CURRENT_LOAD"};
|
||||
decline unless defined $currentLoad;
|
||||
mkdir $currentLoad, 0777 or die unless -d $currentLoad;
|
||||
|
||||
my $conf = $ENV{"NIX_REMOTE_SYSTEMS"};
|
||||
decline if !defined $conf || ! -e $conf;
|
||||
|
||||
# Decline if the local system can do the build.
|
||||
decline if $amWilling && ($localSystem eq $neededSystem);
|
||||
|
||||
|
||||
# Otherwise find a willing remote machine.
|
||||
my %machines;
|
||||
my %systemTypes;
|
||||
my %sshKeys;
|
||||
my %maxJobs;
|
||||
my %curJobs;
|
||||
|
||||
|
||||
# Read the list of machines.
|
||||
open CONF, "< $conf" or die;
|
||||
|
||||
while (<CONF>) {
|
||||
chomp;
|
||||
s/\#.*$//g;
|
||||
next if /^\s*$/;
|
||||
/^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\d+)\s*$/ or die;
|
||||
$machines{$1} = "";
|
||||
$systemTypes{$1} = $2;
|
||||
$sshKeys{$1} = $3;
|
||||
$maxJobs{$1} = $4;
|
||||
}
|
||||
|
||||
close CONF;
|
||||
|
||||
|
||||
# Acquire the exclusive lock on $currentLoad/main-lock.
|
||||
my $mainLock = "$currentLoad/main-lock";
|
||||
open MAINLOCK, ">>$mainLock" or die;
|
||||
flock(MAINLOCK, LOCK_EX) or die;
|
||||
|
||||
|
||||
# Find a suitable system.
|
||||
my $rightType = 0;
|
||||
my $machine;
|
||||
LOOP: foreach my $cur (keys %machines) {
|
||||
if ($neededSystem eq $systemTypes{$cur}) {
|
||||
$rightType = 1;
|
||||
|
||||
# We have a machine of the right type. Try to get a lock on
|
||||
# one of the machine's lock files.
|
||||
my $slot = 0;
|
||||
while ($slot < $maxJobs{$cur}) {
|
||||
my $slotLock = "$currentLoad/$cur-$slot";
|
||||
open SLOTLOCK, ">>$slotLock" or die;
|
||||
if (flock(SLOTLOCK, LOCK_EX | LOCK_NB)) {
|
||||
$machine = $cur;
|
||||
last LOOP;
|
||||
}
|
||||
close SLOTLOCK;
|
||||
$slot++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close MAINLOCK;
|
||||
|
||||
|
||||
# Didn't find one?
|
||||
if (!defined $machine) {
|
||||
if ($rightType) {
|
||||
sendReply "postpone";
|
||||
exit 0;
|
||||
} else {
|
||||
decline;
|
||||
}
|
||||
}
|
||||
|
||||
# Yes we did, accept.
|
||||
sendReply "accept";
|
||||
open IN, "<&4" or die;
|
||||
my $x = <IN>;
|
||||
chomp $x;
|
||||
#print "got $x\n";
|
||||
close IN;
|
||||
|
||||
if ($x ne "okay") {
|
||||
exit 0;
|
||||
}
|
||||
|
||||
|
||||
# Do the actual job.
|
||||
print "BUILDING REMOTE: $drvPath on $machine\n";
|
||||
|
||||
# Make sure that we don't get any SSH passphrase or host key popups -
|
||||
# if there is any problem it should fail, not do something
|
||||
# interactive.
|
||||
$ENV{"DISPLAY"} = "";
|
||||
$ENV{"SSH_ASKPASS"} = "";
|
||||
$ENV{"SSH_PASSWORD_FILE="} = "";
|
||||
$ENV{"SSH_ASKPASS="} = "";
|
||||
|
||||
my $sshOpts = "-i $sshKeys{$machine} -x";
|
||||
|
||||
sub sendReply {
|
||||
my $reply = shift;
|
||||
print STDERR "# $reply\n";
|
||||
# Hack to support Cygwin: if we login without a password, we don't
|
||||
# have exactly the same right as when we do. This causes the
|
||||
# Microsoft C compiler to fail with certain flags:
|
||||
#
|
||||
# http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=99676
|
||||
#
|
||||
# So as a workaround, we pass a verbatim password. ssh tries to makes
|
||||
# this very hard; the trick is to make it call SSH_ASKPASS to get the
|
||||
# password. (It only calls this command when there is no controlling
|
||||
# terminal, but Nix ensures that is is the case. When doing this
|
||||
# manually, use setsid(1).)
|
||||
if ($sshKeys{$machine} =~ /^password:/) {
|
||||
my $passwordFile = $sshKeys{$machine};
|
||||
$passwordFile =~ s/^password://;
|
||||
$sshOpts = "ssh -x";
|
||||
$ENV{"SSH_PASSWORD_FILE"} = $passwordFile;
|
||||
$ENV{"SSH_ASKPASS"} = "/tmp/writepass";
|
||||
|
||||
open WRITEPASS, ">/tmp/writepass" or die;
|
||||
print WRITEPASS "#! /bin/sh\ncat \"\$SSH_PASSWORD_FILE\"";
|
||||
close WRITEPASS;
|
||||
chmod 0755, "/tmp/writepass" or die;
|
||||
}
|
||||
|
||||
sub all { $_ || return 0 for @_; 1 }
|
||||
my $inputs = `cat inputs`; die if ($? != 0);
|
||||
$inputs =~ s/\n/ /g;
|
||||
|
||||
my $outputs = `cat outputs`; die if ($? != 0);
|
||||
$outputs =~ s/\n/ /g;
|
||||
|
||||
# Initialisation.
|
||||
my $loadIncreased = 0;
|
||||
|
||||
my ($localSystem, $maxSilentTime, $printBuildTrace) = @ARGV;
|
||||
$maxSilentTime = 0 unless defined $maxSilentTime;
|
||||
|
||||
my $currentLoad = $ENV{"NIX_CURRENT_LOAD"};
|
||||
my $conf = $ENV{"NIX_REMOTE_SYSTEMS"};
|
||||
|
||||
|
||||
sub openSlotLock {
|
||||
my ($machine, $slot) = @_;
|
||||
my $slotLockFn = "$currentLoad/" . (join '+', @{$machine->{systemTypes}}) . "-" . $machine->{hostName} . "-$slot";
|
||||
my $slotLock = new IO::Handle;
|
||||
open $slotLock, ">>$slotLockFn" or die;
|
||||
return $slotLock;
|
||||
}
|
||||
|
||||
|
||||
# Read the list of machines.
|
||||
my @machines;
|
||||
if (defined $conf && -e $conf) {
|
||||
open CONF, "< $conf" or die;
|
||||
while (<CONF>) {
|
||||
chomp;
|
||||
s/\#.*$//g;
|
||||
next if /^\s*$/;
|
||||
my @tokens = split /\s/, $_;
|
||||
push @machines,
|
||||
{ hostName => $tokens[0]
|
||||
, systemTypes => [ split(/,/, $tokens[1]) ]
|
||||
, sshKeys => $tokens[2]
|
||||
, maxJobs => int($tokens[3])
|
||||
, speedFactor => 1.0 * (defined $tokens[4] ? int($tokens[4]) : 1)
|
||||
, features => [ split(/,/, $tokens[5] || "") ]
|
||||
, enabled => 1
|
||||
};
|
||||
}
|
||||
close CONF;
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Wait for the calling process to ask us whether we can build some derivation.
|
||||
my ($drvPath, $hostName, $slotLock);
|
||||
|
||||
REQ: while (1) {
|
||||
$_ = <STDIN> || exit 0;
|
||||
my ($amWilling, $neededSystem);
|
||||
($amWilling, $neededSystem, $drvPath, $requiredFeatures) = split;
|
||||
my @requiredFeatures = split /,/, $requiredFeatures;
|
||||
|
||||
my $canBuildLocally = $amWilling && ($localSystem eq $neededSystem);
|
||||
|
||||
if (!defined $currentLoad) {
|
||||
sendReply "decline";
|
||||
next;
|
||||
}
|
||||
|
||||
# Acquire the exclusive lock on $currentLoad/main-lock.
|
||||
mkdir $currentLoad, 0777 or die unless -d $currentLoad;
|
||||
my $mainLock = "$currentLoad/main-lock";
|
||||
open MAINLOCK, ">>$mainLock" or die;
|
||||
flock(MAINLOCK, LOCK_EX) or die;
|
||||
|
||||
|
||||
while (1) {
|
||||
# Find all machine that can execute this build, i.e., that
|
||||
# support builds for the given platform and features, and are
|
||||
# not at their job limit.
|
||||
my $rightType = 0;
|
||||
my @available = ();
|
||||
LOOP: foreach my $cur (@machines) {
|
||||
if ($cur->{enabled}
|
||||
&& (grep { $neededSystem eq $_ } @{$cur->{systemTypes}})
|
||||
&& all(map { my $f = $_; 0 != grep { $f eq $_ } @{$cur->{features}} } @requiredFeatures))
|
||||
{
|
||||
$rightType = 1;
|
||||
|
||||
# We have a machine of the right type. Determine the load on
|
||||
# the machine.
|
||||
my $slot = 0;
|
||||
my $load = 0;
|
||||
my $free;
|
||||
while ($slot < $cur->{maxJobs}) {
|
||||
my $slotLock = openSlotLock($cur, $slot);
|
||||
if (flock($slotLock, LOCK_EX | LOCK_NB)) {
|
||||
$free = $slot unless defined $free;
|
||||
flock($slotLock, LOCK_UN) or die;
|
||||
} else {
|
||||
$load++;
|
||||
}
|
||||
close $slotLock;
|
||||
$slot++;
|
||||
}
|
||||
|
||||
push @available, { machine => $cur, load => $load, free => $free }
|
||||
if $load < $cur->{maxJobs};
|
||||
}
|
||||
}
|
||||
|
||||
if (defined $ENV{NIX_DEBUG_HOOK}) {
|
||||
print STDERR "load on " . $_->{machine}->{hostName} . " = " . $_->{load} . "\n"
|
||||
foreach @available;
|
||||
}
|
||||
|
||||
|
||||
# Didn't find any available machine? Then decline or postpone.
|
||||
if (scalar @available == 0) {
|
||||
# Postpone if we have a machine of the right type, except
|
||||
# if the local system can and wants to do the build.
|
||||
if ($rightType && !$canBuildLocally) {
|
||||
sendReply "postpone";
|
||||
} else {
|
||||
sendReply "decline";
|
||||
}
|
||||
close MAINLOCK;
|
||||
next REQ;
|
||||
}
|
||||
|
||||
|
||||
# Prioritise the available machines as follows:
|
||||
# - First by load divided by speed factor, rounded to the nearest
|
||||
# integer. This causes fast machines to be preferred over slow
|
||||
# machines with similar loads.
|
||||
# - Then by speed factor.
|
||||
# - Finally by load.
|
||||
sub lf { my $x = shift; return int($x->{load} / $x->{machine}->{speedFactor} + 0.4999); }
|
||||
@available = sort
|
||||
{ lf($a) <=> lf($b)
|
||||
|| $b->{machine}->{speedFactor} <=> $a->{machine}->{speedFactor}
|
||||
|| $a->{load} <=> $b->{load}
|
||||
} @available;
|
||||
|
||||
|
||||
# Select the best available machine and lock a free slot.
|
||||
my $selected = $available[0];
|
||||
my $machine = $selected->{machine};
|
||||
|
||||
$slotLock = openSlotLock($machine, $selected->{free});
|
||||
flock($slotLock, LOCK_EX | LOCK_NB) or die;
|
||||
utime undef, undef, $slotLock;
|
||||
|
||||
close MAINLOCK;
|
||||
|
||||
|
||||
# Connect to the selected machine.
|
||||
@sshOpts = ("-i", $machine->{sshKeys}, "-x");
|
||||
$hostName = $machine->{hostName};
|
||||
last REQ if openSSHConnection $hostName;
|
||||
|
||||
warn "unable to open SSH connection to $hostName, trying other available machines...\n";
|
||||
$machine->{enabled} = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Tell Nix we've accepted the build.
|
||||
sendReply "accept";
|
||||
my @inputs = split /\s/, readline(STDIN);
|
||||
my @outputs = split /\s/, readline(STDIN);
|
||||
|
||||
|
||||
print STDERR "building `$drvPath' on `$hostName'\n";
|
||||
print STDERR "@ build-remote $drvPath $hostName\n" if $printBuildTrace;
|
||||
|
||||
print "COPYING INPUTS...\n";
|
||||
|
||||
my $maybeSign = "";
|
||||
$maybeSign = "--sign" if -e "/nix/etc/nix/signing-key.sec";
|
||||
|
||||
system("NIX_SSHOPTS=\"$sshOpts\" nix-copy-closure $machine $maybeSign $drvPath $inputs") == 0
|
||||
or die "cannot copy inputs to $machine: $?";
|
||||
|
||||
# Register the derivation as a temporary GC root. Note that $PPID is
|
||||
# the PID of the remote SSH process, which, due to the use of a
|
||||
# persistant SSH connection, should be the same across all remote
|
||||
# command invocations for this session.
|
||||
my $rootsDir = "@localstatedir@/nix/gcroots/tmp";
|
||||
system("ssh $hostName @sshOpts 'mkdir -m 1777 -p $rootsDir; ln -sfn $drvPath $rootsDir/\$PPID.drv'");
|
||||
print "BUILDING...\n";
|
||||
|
||||
sub removeRoots {
|
||||
system("ssh $hostName @sshOpts 'rm -f $rootsDir/\$PPID.drv $rootsDir/\$PPID.out'");
|
||||
}
|
||||
system("ssh $sshOpts $machine 'nix-store -rvvK $drvPath'") == 0
|
||||
or die "remote build on $machine failed: $?";
|
||||
|
||||
print "REMOTE BUILD DONE: $drvPath on $machine\n";
|
||||
|
||||
# Copy the derivation and its dependencies to the build machine.
|
||||
system("NIX_SSHOPTS=\"@sshOpts\" @bindir@/nix-copy-closure $hostName $maybeSign $drvPath @inputs") == 0
|
||||
or die "cannot copy inputs to $hostName: $?";
|
||||
|
||||
|
||||
# Perform the build.
|
||||
my $buildFlags = "--max-silent-time $maxSilentTime --fallback --add-root $rootsDir/\$PPID.out --option verbosity 0";
|
||||
|
||||
# We let the remote side kill its process group when the connection is
|
||||
# closed unexpectedly. This is necessary to ensure that no processes
|
||||
# are left running on the remote system if the local Nix process is
|
||||
# killed. (SSH itself doesn't kill child processes if the connection
|
||||
# is interrupted unless the `-tt' flag is used to force a pseudo-tty,
|
||||
# in which case every child receives SIGHUP; however, `-tt' doesn't
|
||||
# work on some platforms when connection sharing is used.)
|
||||
pipe STDIN, DUMMY; # make sure we have a readable STDIN
|
||||
if (system("ssh $hostName @sshOpts '(read; kill -INT -\$\$) <&0 & nix-store -r $drvPath $buildFlags > /dev/null' 2>&4") != 0) {
|
||||
# Note that if we get exit code 100 from `nix-store -r', it
|
||||
# denotes a permanent build failure (as opposed to an SSH problem
|
||||
# or a temporary Nix problem). We propagate this to the caller to
|
||||
# allow it to distinguish between transient and permanent
|
||||
# failures.
|
||||
my $res = $? >> 8;
|
||||
print STDERR "build of `$drvPath' on `$hostName' failed with exit code $res\n";
|
||||
removeRoots;
|
||||
exit $res;
|
||||
}
|
||||
|
||||
#print "build of `$drvPath' on `$hostName' succeeded\n";
|
||||
|
||||
|
||||
# Copy the output from the build machine.
|
||||
foreach my $output (@outputs) {
|
||||
foreach my $output (split '\n', $outputs) {
|
||||
my $maybeSignRemote = "";
|
||||
$maybeSignRemote = "--sign" if $UID != 0;
|
||||
|
||||
system("ssh $hostName @sshOpts 'nix-store --export $maybeSignRemote $output'" .
|
||||
"| NIX_HELD_LOCKS=$output @bindir@/nix-store --import > /dev/null") == 0
|
||||
or die "cannot copy $output from $hostName: $?";
|
||||
system("ssh $sshOpts $machine 'nix-store --export $maybeSignRemote $output' > dump") == 0
|
||||
or die "cannot copy $output from $machine: $?";
|
||||
|
||||
# This doesn't work yet, since the caller has a lock on the output
|
||||
# path. We should move towards lock-free invocation of build
|
||||
# hooks and substitutes.
|
||||
#system("nix-store --import < dump") == 0
|
||||
# or die "cannot import $output: $?";
|
||||
|
||||
# Hack: skip the first 8 bytes (the nix-store --export next
|
||||
# archive marker). The archive follows.
|
||||
system("(dd bs=1 count=8 of=/dev/null && cat) < dump | nix-store --restore $output") == 0
|
||||
or die "cannot restore $output: $?";
|
||||
}
|
||||
|
||||
|
||||
# Get rid of the temporary GC roots.
|
||||
removeRoots;
|
||||
|
||||
@@ -17,19 +17,25 @@ foreach my $dir (@remoteStoresAll) {
|
||||
}
|
||||
|
||||
|
||||
$ENV{"NIX_REMOTE"} = "";
|
||||
|
||||
|
||||
sub findStorePath {
|
||||
my $storePath = shift;
|
||||
|
||||
my $storePathName = basename $storePath;
|
||||
|
||||
foreach my $store (@remoteStores) {
|
||||
my $sourcePath = "$store/store/" . basename $storePath;
|
||||
next unless -e $sourcePath || -l $sourcePath;
|
||||
$ENV{"NIX_DB_DIR"} = "$store/var/nix/db";
|
||||
return ($store, $sourcePath) if
|
||||
system("@bindir@/nix-store --check-validity $storePath") == 0;
|
||||
# Determine whether $storePath exists by looking for the
|
||||
# existence of the info file, and if so, get store path info
|
||||
# from that file. This rather breaks abstraction: we should
|
||||
# be using `nix-store' for that. But right now there is no
|
||||
# good way to tell nix-store to access a store mounted under a
|
||||
# different location (there's $NIX_STORE, but that only works
|
||||
# if the remote store is mounted under its "real" location).
|
||||
my $infoFile = "$store/var/nix/db/info/$storePathName";
|
||||
my $storePath2 = "$store/store/$storePathName";
|
||||
if (-f $infoFile && -e $storePath2) {
|
||||
return ($infoFile, $storePath2);
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,38 +46,37 @@ if ($ARGV[0] eq "--query") {
|
||||
|
||||
if ($cmd eq "have") {
|
||||
my $storePath = <STDIN>; chomp $storePath;
|
||||
print STDOUT (defined findStorePath($storePath) ? "1\n" : "0\n");
|
||||
(my $infoFile) = findStorePath $storePath;
|
||||
print STDOUT ($infoFile ? "1\n" : "0\n");
|
||||
}
|
||||
|
||||
elsif ($cmd eq "info") {
|
||||
my $storePath = <STDIN>; chomp $storePath;
|
||||
my ($store, $sourcePath) = findStorePath($storePath);
|
||||
if (!defined $store) {
|
||||
(my $infoFile) = findStorePath $storePath;
|
||||
if (!$infoFile) {
|
||||
print "0\n";
|
||||
next; # not an error
|
||||
}
|
||||
print "1\n";
|
||||
|
||||
$ENV{"NIX_DB_DIR"} = "$store/var/nix/db";
|
||||
|
||||
my $deriver = `@bindir@/nix-store --query --deriver $storePath`;
|
||||
die "cannot query deriver of `$storePath'" if $? != 0;
|
||||
chomp $deriver;
|
||||
$deriver = "" if $deriver eq "unknown-deriver";
|
||||
my $deriver = "";
|
||||
my @references = ();
|
||||
|
||||
my @references = split "\n",
|
||||
`@bindir@/nix-store --query --references $storePath`;
|
||||
die "cannot query references of `$storePath'" if $? != 0;
|
||||
|
||||
my $narSize = `@bindir@/nix-store --query --size $storePath`;
|
||||
die "cannot query size of `$storePath'" if $? != 0;
|
||||
chomp $narSize;
|
||||
open INFO, "<$infoFile" or die "cannot read info file $infoFile\n";
|
||||
while (<INFO>) {
|
||||
chomp;
|
||||
/^([\w-]+): (.*)$/ or die "bad info file";
|
||||
my $key = $1;
|
||||
my $value = $2;
|
||||
if ($key eq "Deriver") { $deriver = $value; }
|
||||
elsif ($key eq "References") { @references = split ' ', $value; }
|
||||
}
|
||||
close INFO;
|
||||
|
||||
print "$deriver\n";
|
||||
print scalar @references, "\n";
|
||||
print "$_\n" foreach @references;
|
||||
print "$narSize\n";
|
||||
print "$narSize\n";
|
||||
print "0\n"; # !!! showing size not supported (yet)
|
||||
}
|
||||
|
||||
else { die "unknown command `$cmd'"; }
|
||||
@@ -82,8 +87,8 @@ if ($ARGV[0] eq "--query") {
|
||||
elsif ($ARGV[0] eq "--substitute") {
|
||||
die unless scalar @ARGV == 2;
|
||||
my $storePath = $ARGV[1];
|
||||
my ($store, $sourcePath) = findStorePath $storePath;
|
||||
die unless $store;
|
||||
(my $infoFile, my $sourcePath) = findStorePath $storePath;
|
||||
die unless $infoFile;
|
||||
print "\n*** Copying `$storePath' from `$sourcePath'\n\n";
|
||||
system("$binDir/nix-store --dump $sourcePath | $binDir/nix-store --restore $storePath") == 0
|
||||
or die "cannot copy `$sourcePath' to `$storePath'";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#! @perl@ -w -I@libexecdir@/nix
|
||||
|
||||
use strict;
|
||||
use NixManifest;
|
||||
use readmanifest;
|
||||
use POSIX qw(strftime);
|
||||
use File::Temp qw(tempdir);
|
||||
|
||||
@@ -9,13 +9,9 @@ my $binDir = $ENV{"NIX_BIN_DIR"} || "@bindir@";
|
||||
|
||||
STDOUT->autoflush(1);
|
||||
|
||||
my $manifestDir = ($ENV{"NIX_MANIFESTS_DIR"} or "@localstatedir@/nix/manifests");
|
||||
my $manifestDir = "@localstatedir@/nix/manifests";
|
||||
my $logFile = "@localstatedir@/log/nix/downloads";
|
||||
|
||||
# For queries, skip expensive calls to nix-hash etc. We're just
|
||||
# estimating the expected download size.
|
||||
my $fast = 1;
|
||||
|
||||
|
||||
# Load all manifests.
|
||||
my %narFiles;
|
||||
@@ -23,160 +19,10 @@ my %localPaths;
|
||||
my %patches;
|
||||
|
||||
for my $manifest (glob "$manifestDir/*.nixmanifest") {
|
||||
my $version = readManifest($manifest, \%narFiles, \%localPaths, \%patches);
|
||||
if ($version < 3) {
|
||||
if (readManifest($manifest, \%narFiles, \%localPaths, \%patches) < 3) {
|
||||
print STDERR "you have an old-style manifest `$manifest'; please delete it\n";
|
||||
exit 1;
|
||||
}
|
||||
if ($version >= 10) {
|
||||
print STDERR "manifest `$manifest' is too new; please delete it or upgrade Nix\n";
|
||||
exit 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub isValidPath {
|
||||
my $p = shift;
|
||||
if ($fast) {
|
||||
return -e $p;
|
||||
} else {
|
||||
return system("$binDir/nix-store --check-validity '$p' 2> /dev/null") == 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub parseHash {
|
||||
my $hash = shift;
|
||||
if ($hash =~ /^(.+):(.+)$/) {
|
||||
return ($1, $2);
|
||||
} else {
|
||||
return ("md5", $hash);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Compute the most efficient sequence of downloads to produce the
|
||||
# given path.
|
||||
sub computeSmallestDownload {
|
||||
my $targetPath = shift;
|
||||
|
||||
# Build a graph of all store paths that might contribute to the
|
||||
# construction of $targetPath, and the special node "start". The
|
||||
# edges are either patch operations, or downloads of full NAR
|
||||
# files. The latter edges only occur between "start" and a store
|
||||
# path.
|
||||
my %graph;
|
||||
|
||||
$graph{"start"} = {d => 0, pred => undef, edges => []};
|
||||
|
||||
my @queue = ();
|
||||
my $queueFront = 0;
|
||||
my %done;
|
||||
|
||||
sub addNode {
|
||||
my $graph = shift;
|
||||
my $u = shift;
|
||||
$$graph{$u} = {d => 999999999999, pred => undef, edges => []}
|
||||
unless defined $$graph{$u};
|
||||
}
|
||||
|
||||
sub addEdge {
|
||||
my $graph = shift;
|
||||
my $u = shift;
|
||||
my $v = shift;
|
||||
my $w = shift;
|
||||
my $type = shift;
|
||||
my $info = shift;
|
||||
addNode $graph, $u;
|
||||
push @{$$graph{$u}->{edges}},
|
||||
{weight => $w, start => $u, end => $v, type => $type, info => $info};
|
||||
my $n = scalar @{$$graph{$u}->{edges}};
|
||||
}
|
||||
|
||||
push @queue, $targetPath;
|
||||
|
||||
while ($queueFront < scalar @queue) {
|
||||
my $u = $queue[$queueFront++];
|
||||
next if defined $done{$u};
|
||||
$done{$u} = 1;
|
||||
|
||||
addNode \%graph, $u;
|
||||
|
||||
# If the path already exists, it has distance 0 from the
|
||||
# "start" node.
|
||||
if (isValidPath($u)) {
|
||||
addEdge \%graph, "start", $u, 0, "present", undef;
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
# Add patch edges.
|
||||
my $patchList = $patches{$u};
|
||||
foreach my $patch (@{$patchList}) {
|
||||
if (isValidPath($patch->{basePath})) {
|
||||
# !!! this should be cached
|
||||
my ($baseHashAlgo, $baseHash) = parseHash $patch->{baseHash};
|
||||
my $format = "--base32";
|
||||
$format = "" if $baseHashAlgo eq "md5";
|
||||
my $hash = $fast && $baseHashAlgo eq "sha256"
|
||||
? `$binDir/nix-store -q --hash "$patch->{basePath}"`
|
||||
: `$binDir/nix-hash --type '$baseHashAlgo' $format "$patch->{basePath}"`;
|
||||
chomp $hash;
|
||||
$hash =~ s/.*://;
|
||||
next if $hash ne $baseHash;
|
||||
}
|
||||
push @queue, $patch->{basePath};
|
||||
addEdge \%graph, $patch->{basePath}, $u, $patch->{size}, "patch", $patch;
|
||||
}
|
||||
|
||||
# Add NAR file edges to the start node.
|
||||
my $narFileList = $narFiles{$u};
|
||||
foreach my $narFile (@{$narFileList}) {
|
||||
# !!! how to handle files whose size is not known in advance?
|
||||
# For now, assume some arbitrary size (1 MB).
|
||||
addEdge \%graph, "start", $u, ($narFile->{size} || 1000000), "narfile", $narFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Run Dijkstra's shortest path algorithm to determine the shortest
|
||||
# sequence of download and/or patch actions that will produce
|
||||
# $targetPath.
|
||||
|
||||
my @todo = keys %graph;
|
||||
|
||||
while (scalar @todo > 0) {
|
||||
|
||||
# Remove the closest element from the todo list.
|
||||
# !!! inefficient, use a priority queue
|
||||
@todo = sort { -($graph{$a}->{d} <=> $graph{$b}->{d}) } @todo;
|
||||
my $u = pop @todo;
|
||||
|
||||
my $u_ = $graph{$u};
|
||||
|
||||
foreach my $edge (@{$u_->{edges}}) {
|
||||
my $v_ = $graph{$edge->{end}};
|
||||
if ($v_->{d} > $u_->{d} + $edge->{weight}) {
|
||||
$v_->{d} = $u_->{d} + $edge->{weight};
|
||||
# Store the edge; to edge->start is actually the
|
||||
# predecessor.
|
||||
$v_->{pred} = $edge;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Retrieve the shortest path from "start" to $targetPath.
|
||||
my @path = ();
|
||||
my $cur = $targetPath;
|
||||
return () unless defined $graph{$targetPath}->{pred};
|
||||
while ($cur ne "start") {
|
||||
push @path, $graph{$cur}->{pred};
|
||||
$cur = $graph{$cur}->{pred}->{start};
|
||||
}
|
||||
|
||||
return @path;
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +41,6 @@ if ($ARGV[0] eq "--query") {
|
||||
|
||||
elsif ($cmd eq "info") {
|
||||
my $storePath = <STDIN>; chomp $storePath;
|
||||
|
||||
my $info;
|
||||
if (defined $narFiles{$storePath}) {
|
||||
$info = @{$narFiles{$storePath}}[0];
|
||||
@@ -207,32 +52,13 @@ if ($ARGV[0] eq "--query") {
|
||||
print "0\n";
|
||||
next; # not an error
|
||||
}
|
||||
|
||||
print "1\n";
|
||||
print "$info->{deriver}\n";
|
||||
my @references = split " ", $info->{references};
|
||||
print scalar @references, "\n";
|
||||
print "$_\n" foreach @references;
|
||||
|
||||
my @path = computeSmallestDownload $storePath;
|
||||
|
||||
my $downloadSize = 0;
|
||||
while (scalar @path > 0) {
|
||||
my $edge = pop @path;
|
||||
my $u = $edge->{start};
|
||||
my $v = $edge->{end};
|
||||
if ($edge->{type} eq "patch") {
|
||||
$downloadSize += $edge->{info}->{size} || 0;
|
||||
}
|
||||
elsif ($edge->{type} eq "narfile") {
|
||||
$downloadSize += $edge->{info}->{size} || 0;
|
||||
}
|
||||
}
|
||||
|
||||
print "$downloadSize\n";
|
||||
|
||||
my $narSize = $info->{narSize} || 0;
|
||||
print "$narSize\n";
|
||||
my $size = $info->{size} || 0;
|
||||
print "$size\n";
|
||||
}
|
||||
|
||||
else { die "unknown command `$cmd'"; }
|
||||
@@ -242,19 +68,20 @@ if ($ARGV[0] eq "--query") {
|
||||
}
|
||||
|
||||
elsif ($ARGV[0] ne "--substitute") {
|
||||
die;
|
||||
die "syntax: $0 [--query-paths | --query-info PATHS... | --substitute PATH]\n";
|
||||
}
|
||||
|
||||
|
||||
die unless scalar @ARGV == 2;
|
||||
my $targetPath = $ARGV[1];
|
||||
$fast = 0;
|
||||
|
||||
|
||||
# Create a temporary directory.
|
||||
my $tmpDir = tempdir("nix-download.XXXXXX", CLEANUP => 1, TMPDIR => 1)
|
||||
or die "cannot create a temporary directory";
|
||||
|
||||
chdir $tmpDir or die "cannot change to `$tmpDir': $!";
|
||||
|
||||
my $tmpNar = "$tmpDir/nar";
|
||||
my $tmpNar2 = "$tmpDir/nar2";
|
||||
|
||||
@@ -280,9 +107,151 @@ foreach my $localPath (@{$localPathList}) {
|
||||
}
|
||||
|
||||
|
||||
# Compute the shortest path.
|
||||
my @path = computeSmallestDownload $targetPath;
|
||||
die "don't know how to produce $targetPath\n" if scalar @path == 0;
|
||||
# Build a graph of all store paths that might contribute to the
|
||||
# construction of $targetPath, and the special node "start". The
|
||||
# edges are either patch operations, or downloads of full NAR files.
|
||||
# The latter edges only occur between "start" and a store path.
|
||||
|
||||
my %graph;
|
||||
|
||||
$graph{"start"} = {d => 0, pred => undef, edges => []};
|
||||
|
||||
my @queue = ();
|
||||
my $queueFront = 0;
|
||||
my %done;
|
||||
|
||||
sub addToQueue {
|
||||
my $v = shift;
|
||||
return if defined $done{$v};
|
||||
$done{$v} = 1;
|
||||
push @queue, $v;
|
||||
}
|
||||
|
||||
sub addNode {
|
||||
my $u = shift;
|
||||
$graph{$u} = {d => 999999999999, pred => undef, edges => []}
|
||||
unless defined $graph{$u};
|
||||
}
|
||||
|
||||
sub addEdge {
|
||||
my $u = shift;
|
||||
my $v = shift;
|
||||
my $w = shift;
|
||||
my $type = shift;
|
||||
my $info = shift;
|
||||
addNode $u;
|
||||
push @{$graph{$u}->{edges}},
|
||||
{weight => $w, start => $u, end => $v, type => $type, info => $info};
|
||||
my $n = scalar @{$graph{$u}->{edges}};
|
||||
}
|
||||
|
||||
addToQueue $targetPath;
|
||||
|
||||
sub isValidPath {
|
||||
my $p = shift;
|
||||
return system("$binDir/nix-store --check-validity '$p' 2> /dev/null") == 0;
|
||||
}
|
||||
|
||||
sub parseHash {
|
||||
my $hash = shift;
|
||||
if ($hash =~ /^(.+):(.+)$/) {
|
||||
return ($1, $2);
|
||||
} else {
|
||||
return ("md5", $hash);
|
||||
}
|
||||
}
|
||||
|
||||
while ($queueFront < scalar @queue) {
|
||||
my $u = $queue[$queueFront++];
|
||||
# print "$u\n";
|
||||
|
||||
addNode $u;
|
||||
|
||||
# If the path already exists, it has distance 0 from the "start"
|
||||
# node.
|
||||
if (isValidPath($u)) {
|
||||
addEdge "start", $u, 0, "present", undef;
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
# Add patch edges.
|
||||
my $patchList = $patches{$u};
|
||||
foreach my $patch (@{$patchList}) {
|
||||
if (isValidPath($patch->{basePath})) {
|
||||
# !!! this should be cached
|
||||
my ($baseHashAlgo, $baseHash) = parseHash $patch->{baseHash};
|
||||
my $format = "--base32";
|
||||
$format = "" if $baseHashAlgo eq "md5";
|
||||
my $hash = `$binDir/nix-hash --type '$baseHashAlgo' $format "$patch->{basePath}"`;
|
||||
chomp $hash;
|
||||
# print " MY HASH is $hash\n";
|
||||
if ($hash ne $baseHash) {
|
||||
print LOGFILE "$$ rejecting $patch->{basePath}\n";
|
||||
next;
|
||||
}
|
||||
}
|
||||
# print " PATCH from $patch->{basePath}\n";
|
||||
addToQueue $patch->{basePath};
|
||||
addEdge $patch->{basePath}, $u, $patch->{size}, "patch", $patch;
|
||||
}
|
||||
|
||||
# Add NAR file edges to the start node.
|
||||
my $narFileList = $narFiles{$u};
|
||||
foreach my $narFile (@{$narFileList}) {
|
||||
# print " NAR from $narFile->{url}\n";
|
||||
addEdge "start", $u, $narFile->{size}, "narfile", $narFile;
|
||||
if ($u eq $targetPath) {
|
||||
print LOGFILE "$$ full-download-would-be $narFile->{size}\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Run Dijkstra's shortest path algorithm to determine the shortest
|
||||
# sequence of download and/or patch actions that will produce
|
||||
# $targetPath.
|
||||
|
||||
sub byDistance { # sort by distance, reversed
|
||||
return -($graph{$a}->{d} <=> $graph{$b}->{d});
|
||||
}
|
||||
|
||||
my @todo = keys %graph;
|
||||
|
||||
while (scalar @todo > 0) {
|
||||
|
||||
# Remove the closest element from the todo list.
|
||||
@todo = sort byDistance @todo;
|
||||
my $u = pop @todo;
|
||||
|
||||
my $u_ = $graph{$u};
|
||||
|
||||
# print "IN $u $u_->{d}\n";
|
||||
|
||||
foreach my $edge (@{$u_->{edges}}) {
|
||||
my $v_ = $graph{$edge->{end}};
|
||||
if ($v_->{d} > $u_->{d} + $edge->{weight}) {
|
||||
$v_->{d} = $u_->{d} + $edge->{weight};
|
||||
# Store the edge; to edge->start is actually the
|
||||
# predecessor.
|
||||
$v_->{pred} = $edge;
|
||||
# print " RELAX $edge->{end} $v_->{d}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Retrieve the shortest path from "start" to $targetPath.
|
||||
my @path = ();
|
||||
my $cur = $targetPath;
|
||||
die "don't know how to produce $targetPath\n"
|
||||
unless defined $graph{$targetPath}->{pred};
|
||||
while ($cur ne "start") {
|
||||
push @path, $graph{$cur}->{pred};
|
||||
$cur = $graph{$cur}->{pred}->{start};
|
||||
}
|
||||
|
||||
|
||||
# Traverse the shortest path, perform the actions described by the
|
||||
@@ -290,18 +259,20 @@ die "don't know how to produce $targetPath\n" if scalar @path == 0;
|
||||
my $curStep = 1;
|
||||
my $maxStep = scalar @path;
|
||||
|
||||
sub downloadFile {
|
||||
my $url = shift;
|
||||
sub downloadFile {
|
||||
my $url = shift;
|
||||
my ($hashAlgo, $hash) = parseHash(shift);
|
||||
$ENV{"PRINT_PATH"} = 1;
|
||||
$ENV{"QUIET"} = 1;
|
||||
my ($hash, $path) = `$binDir/nix-prefetch-url '$url'`;
|
||||
$ENV{"NIX_HASH_ALGO"} = $hashAlgo;
|
||||
my ($hash2, $path) = `$binDir/nix-prefetch-url '$url' '$hash'`;
|
||||
die "download of `$url' failed" unless $? == 0;
|
||||
chomp $hash2;
|
||||
chomp $path;
|
||||
die "hash mismatch, expected $hash, got $hash2" if $hash ne $hash2;
|
||||
return $path;
|
||||
}
|
||||
|
||||
my $finalNarHash;
|
||||
|
||||
while (scalar @path > 0) {
|
||||
my $edge = pop @path;
|
||||
my $u = $edge->{start};
|
||||
@@ -331,7 +302,7 @@ while (scalar @path > 0) {
|
||||
|
||||
# Download the patch.
|
||||
print " downloading patch...\n";
|
||||
my $patchPath = downloadFile "$patch->{url}";
|
||||
my $patchPath = downloadFile "$patch->{url}", "$patch->{hash}";
|
||||
|
||||
# Apply the patch to the NAR archive produced in step 1 (for
|
||||
# the already present path) or a later step (for patch sequences).
|
||||
@@ -349,20 +320,17 @@ while (scalar @path > 0) {
|
||||
system("$binDir/nix-store --restore $v < $tmpNar2") == 0
|
||||
or die "cannot unpack $tmpNar2 into `$v'";
|
||||
}
|
||||
|
||||
$finalNarHash = $patch->{narHash};
|
||||
}
|
||||
|
||||
elsif ($edge->{type} eq "narfile") {
|
||||
my $narFile = $edge->{info};
|
||||
print "downloading `$narFile->{url}' into `$v'\n";
|
||||
|
||||
my $size = $narFile->{size} || -1;
|
||||
print LOGFILE "$$ narfile $narFile->{url} $size $v\n";
|
||||
print LOGFILE "$$ narfile $narFile->{url} $narFile->{size} $v\n";
|
||||
|
||||
# Download the archive.
|
||||
print " downloading archive...\n";
|
||||
my $narFilePath = downloadFile "$narFile->{url}";
|
||||
my $narFilePath = downloadFile "$narFile->{url}", "$narFile->{hash}";
|
||||
|
||||
if ($curStep < $maxStep) {
|
||||
# The archive will be used a base to a patch.
|
||||
@@ -374,36 +342,11 @@ while (scalar @path > 0) {
|
||||
system("@bunzip2@ < '$narFilePath' | $binDir/nix-store --restore '$v'") == 0
|
||||
or die "cannot unpack `$narFilePath' into `$v'";
|
||||
}
|
||||
|
||||
$finalNarHash = $narFile->{narHash};
|
||||
}
|
||||
|
||||
$curStep++;
|
||||
}
|
||||
|
||||
|
||||
# Make sure that the hash declared in the manifest matches what we
|
||||
# downloaded and unpacked.
|
||||
|
||||
if (defined $finalNarHash) {
|
||||
my ($hashAlgo, $hash) = parseHash $finalNarHash;
|
||||
|
||||
# The hash in the manifest can be either in base-16 or base-32.
|
||||
# Handle both.
|
||||
my $extraFlag =
|
||||
($hashAlgo eq "sha256" && length($hash) != 64)
|
||||
? "--base32" : "";
|
||||
|
||||
my $hash2 = `@bindir@/nix-hash --type $hashAlgo $extraFlag $targetPath`
|
||||
or die "cannot compute hash of path `$targetPath'";
|
||||
chomp $hash2;
|
||||
|
||||
die "hash mismatch in downloaded path $targetPath; expected $hash, got $hash2"
|
||||
if $hash ne $hash2;
|
||||
} else {
|
||||
die "cannot check integrity of the downloaded path since its hash is not known";
|
||||
}
|
||||
|
||||
|
||||
print LOGFILE "$$ success\n";
|
||||
close LOGFILE;
|
||||
|
||||
408
scripts/generate-patches.pl.in
Executable file
408
scripts/generate-patches.pl.in
Executable file
@@ -0,0 +1,408 @@
|
||||
#! @perl@ -w -I@libexecdir@/nix
|
||||
|
||||
use strict;
|
||||
use File::Temp qw(tempdir);
|
||||
use readmanifest;
|
||||
|
||||
|
||||
# Some patch generations options.
|
||||
|
||||
# Max size of NAR archives to generate patches for.
|
||||
my $maxNarSize = $ENV{"NIX_MAX_NAR_SIZE"};
|
||||
$maxNarSize = 100 * 1024 * 1024 if !defined $maxNarSize;
|
||||
|
||||
# If patch is bigger than this fraction of full archive, reject.
|
||||
my $maxPatchFraction = $ENV{"NIX_PATCH_FRACTION"};
|
||||
$maxPatchFraction = 0.60 if !defined $maxPatchFraction;
|
||||
|
||||
|
||||
die unless scalar @ARGV == 5;
|
||||
|
||||
my $hashAlgo = "sha256";
|
||||
|
||||
my $cacheDir = $ARGV[0];
|
||||
my $patchesDir = $ARGV[1];
|
||||
my $patchesURL = $ARGV[2];
|
||||
my $srcDir = $ARGV[3];
|
||||
my $dstDir = $ARGV[4];
|
||||
|
||||
my $tmpDir = tempdir("nix-generate-patches.XXXXXX", CLEANUP => 1, TMPDIR => 1)
|
||||
or die "cannot create a temporary directory";
|
||||
|
||||
print "TEMP = $tmpDir\n";
|
||||
|
||||
#END { rmdir $tmpDir; }
|
||||
|
||||
my %srcNarFiles;
|
||||
my %srcLocalPaths;
|
||||
my %srcPatches;
|
||||
|
||||
my %dstNarFiles;
|
||||
my %dstLocalPaths;
|
||||
my %dstPatches;
|
||||
|
||||
readManifest "$srcDir/MANIFEST",
|
||||
\%srcNarFiles, \%srcLocalPaths, \%srcPatches;
|
||||
|
||||
readManifest "$dstDir/MANIFEST",
|
||||
\%dstNarFiles, \%dstLocalPaths, \%dstPatches;
|
||||
|
||||
|
||||
sub findOutputPaths {
|
||||
my $narFiles = shift;
|
||||
|
||||
my %outPaths;
|
||||
|
||||
foreach my $p (keys %{$narFiles}) {
|
||||
|
||||
# Ignore store expressions.
|
||||
next if ($p =~ /\.store$/);
|
||||
next if ($p =~ /\.drv$/);
|
||||
|
||||
# Ignore builders (too much ambiguity -- they're all called
|
||||
# `builder.sh').
|
||||
next if ($p =~ /\.sh$/);
|
||||
next if ($p =~ /\.patch$/);
|
||||
|
||||
# Don't bother including tar files etc.
|
||||
next if ($p =~ /\.tar\.(gz|bz2)$/ || $p =~ /\.zip$/ || $p =~ /\.bin$/);
|
||||
|
||||
$outPaths{$p} = 1;
|
||||
}
|
||||
|
||||
return %outPaths;
|
||||
}
|
||||
|
||||
print "finding src output paths...\n";
|
||||
my %srcOutPaths = findOutputPaths \%srcNarFiles;
|
||||
|
||||
print "finding dst output paths...\n";
|
||||
my %dstOutPaths = findOutputPaths \%dstNarFiles;
|
||||
|
||||
|
||||
sub getNameVersion {
|
||||
my $p = shift;
|
||||
$p =~ /\/[0-9a-z]+((?:-[a-zA-Z][^\/-]*)+)([^\/]*)$/;
|
||||
my $name = $1;
|
||||
my $version = $2;
|
||||
$name =~ s/^-//;
|
||||
$version =~ s/^-//;
|
||||
return ($name, $version);
|
||||
}
|
||||
|
||||
|
||||
# A quick hack to get a measure of the `distance' between two
|
||||
# versions: it's just the position of the first character that differs
|
||||
# (or 999 if they are the same).
|
||||
sub versionDiff {
|
||||
my $s = shift;
|
||||
my $t = shift;
|
||||
my $i;
|
||||
return 999 if $s eq $t;
|
||||
for ($i = 0; $i < length $s; $i++) {
|
||||
return $i if $i >= length $t or
|
||||
substr($s, $i, 1) ne substr($t, $i, 1);
|
||||
}
|
||||
return $i;
|
||||
}
|
||||
|
||||
|
||||
sub getNarBz2 {
|
||||
my $narFiles = shift;
|
||||
my $storePath = shift;
|
||||
|
||||
my $narFileList = $$narFiles{$storePath};
|
||||
die "missing store expression $storePath" unless defined $narFileList;
|
||||
|
||||
my $narFile = @{$narFileList}[0];
|
||||
die unless defined $narFile;
|
||||
|
||||
$narFile->{url} =~ /\/([^\/]+)$/;
|
||||
die unless defined $1;
|
||||
return "$cacheDir/$1";
|
||||
}
|
||||
|
||||
|
||||
sub containsPatch {
|
||||
my $patches = shift;
|
||||
my $storePath = shift;
|
||||
my $basePath = shift;
|
||||
my $patchList = $$patches{$storePath};
|
||||
return 0 if !defined $patchList;
|
||||
my $found = 0;
|
||||
foreach my $patch (@{$patchList}) {
|
||||
# !!! baseHash might differ
|
||||
return 1 if $patch->{basePath} eq $basePath;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
# Compute the "weighted" number of uses of a path in the build graph.
|
||||
sub computeUses {
|
||||
my $narFiles = shift;
|
||||
my $path = shift;
|
||||
|
||||
# Find the deriver of $path.
|
||||
return 1 unless defined $$narFiles{$path};
|
||||
my $deriver = @{$$narFiles{$path}}[0]->{deriver};
|
||||
return 1 unless defined $deriver && $deriver ne "";
|
||||
|
||||
# print " DERIVER $deriver\n";
|
||||
|
||||
# Optimisation: build the referrers graph from the references
|
||||
# graph.
|
||||
my %referrers;
|
||||
foreach my $q (keys %{$narFiles}) {
|
||||
my @refs = split " ", @{$$narFiles{$q}}[0]->{references};
|
||||
foreach my $r (@refs) {
|
||||
$referrers{$r} = [] unless defined $referrers{$r};
|
||||
push @{$referrers{$r}}, $q;
|
||||
}
|
||||
}
|
||||
|
||||
# Determine the shortest path from $deriver to all other reachable
|
||||
# paths in the `referrers' graph.
|
||||
|
||||
my %dist;
|
||||
$dist{$deriver} = 0;
|
||||
|
||||
my @queue = ($deriver);
|
||||
my $pos = 0;
|
||||
|
||||
while ($pos < scalar @queue) {
|
||||
my $p = $queue[$pos];
|
||||
$pos++;
|
||||
|
||||
foreach my $q (@{$referrers{$p}}) {
|
||||
if (!defined $dist{$q}) {
|
||||
$dist{$q} = $dist{$p} + 1;
|
||||
# print " $q $dist{$q}\n";
|
||||
push @queue, $q;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $wuse = 1.0;
|
||||
foreach my $user (keys %dist) {
|
||||
next if $user eq $deriver;
|
||||
# print " $user $dist{$user}\n";
|
||||
$wuse += 1.0 / 2.0**$dist{$user};
|
||||
}
|
||||
|
||||
# print " XXX $path $wuse\n";
|
||||
|
||||
return $wuse;
|
||||
}
|
||||
|
||||
|
||||
# For each output path in the destination, see if we need to / can
|
||||
# create a patch.
|
||||
|
||||
print "creating patches...\n";
|
||||
|
||||
foreach my $p (keys %dstOutPaths) {
|
||||
|
||||
# If exactly the same path already exists in the source, skip it.
|
||||
next if defined $srcOutPaths{$p};
|
||||
|
||||
print " $p\n";
|
||||
|
||||
# If not, then we should find the paths in the source that are
|
||||
# `most' likely to be present on a system that wants to install
|
||||
# this path.
|
||||
|
||||
(my $name, my $version) = getNameVersion $p;
|
||||
|
||||
my @closest = ();
|
||||
my $closestVersion;
|
||||
my $minDist = -1; # actually, larger means closer
|
||||
|
||||
# Find all source paths with the same name.
|
||||
|
||||
foreach my $q (keys %srcOutPaths) {
|
||||
(my $name2, my $version2) = getNameVersion $q;
|
||||
if ($name eq $name2) {
|
||||
|
||||
# If the sizes differ too much, then skip. This
|
||||
# disambiguates between, e.g., a real component and a
|
||||
# wrapper component (cf. Firefox in Nixpkgs).
|
||||
my $srcSize = @{$srcNarFiles{$q}}[0]->{size};
|
||||
my $dstSize = @{$dstNarFiles{$p}}[0]->{size};
|
||||
my $ratio = $srcSize / $dstSize;
|
||||
$ratio = 1 / $ratio if $ratio < 1;
|
||||
# print " SIZE $srcSize $dstSize $ratio $q\n";
|
||||
|
||||
if ($ratio >= 3) {
|
||||
print " SKIPPING $q due to size ratio $ratio ($srcSize $dstSize)\n";
|
||||
next;
|
||||
}
|
||||
|
||||
# If the numbers of weighted uses differ too much, then
|
||||
# skip. This disambiguates between, e.g., the bootstrap
|
||||
# GCC and the final GCC in Nixpkgs.
|
||||
my $srcUses = computeUses \%srcNarFiles, $q;
|
||||
my $dstUses = computeUses \%dstNarFiles, $p;
|
||||
$ratio = $srcUses / $dstUses;
|
||||
$ratio = 1 / $ratio if $ratio < 1;
|
||||
print " USE $srcUses $dstUses $ratio $q\n";
|
||||
|
||||
# if ($ratio >= 2) {
|
||||
# print " SKIPPING $q due to use ratio $ratio ($srcUses $dstUses)\n";
|
||||
# next;
|
||||
# }
|
||||
|
||||
# If there are multiple matching names, include the ones
|
||||
# with the closest version numbers.
|
||||
my $dist = versionDiff $version, $version2;
|
||||
if ($dist > $minDist) {
|
||||
$minDist = $dist;
|
||||
@closest = ($q);
|
||||
$closestVersion = $version2;
|
||||
} elsif ($dist == $minDist) {
|
||||
push @closest, $q;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar(@closest) == 0) {
|
||||
print " NO BASE: $p\n";
|
||||
next;
|
||||
}
|
||||
|
||||
foreach my $closest (@closest) {
|
||||
|
||||
# Generate a patch between $closest and $p.
|
||||
print " $p <- $closest\n";
|
||||
|
||||
# If the patch already exists, skip it.
|
||||
if (containsPatch(\%srcPatches, $p, $closest) ||
|
||||
containsPatch(\%dstPatches, $p, $closest))
|
||||
{
|
||||
print " skipping, already exists\n";
|
||||
next;
|
||||
}
|
||||
|
||||
# next;
|
||||
|
||||
my $srcNarBz2 = getNarBz2 \%srcNarFiles, $closest;
|
||||
my $dstNarBz2 = getNarBz2 \%dstNarFiles, $p;
|
||||
|
||||
system("@bunzip2@ < $srcNarBz2 > $tmpDir/A") == 0
|
||||
or die "cannot unpack $srcNarBz2";
|
||||
|
||||
if ((stat "$tmpDir/A")[7] >= $maxNarSize) {
|
||||
print " skipping, source is too large\n";
|
||||
next;
|
||||
}
|
||||
|
||||
system("@bunzip2@ < $dstNarBz2 > $tmpDir/B") == 0
|
||||
or die "cannot unpack $dstNarBz2";
|
||||
|
||||
if ((stat "$tmpDir/B")[7] >= $maxNarSize) {
|
||||
print " skipping, destination is too large\n";
|
||||
next;
|
||||
}
|
||||
|
||||
system("@libexecdir@/bsdiff $tmpDir/A $tmpDir/B $tmpDir/DIFF") == 0
|
||||
or die "cannot compute binary diff";
|
||||
|
||||
my $baseHash = `@bindir@/nix-hash --flat --type $hashAlgo --base32 $tmpDir/A` or die;
|
||||
chomp $baseHash;
|
||||
|
||||
my $narHash = `@bindir@/nix-hash --flat --type $hashAlgo --base32 $tmpDir/B` or die;
|
||||
chomp $narHash;
|
||||
|
||||
my $narDiffHash = `@bindir@/nix-hash --flat --type $hashAlgo --base32 $tmpDir/DIFF` or die;
|
||||
chomp $narDiffHash;
|
||||
|
||||
my $narDiffSize = (stat "$tmpDir/DIFF")[7];
|
||||
my $dstNarBz2Size = (stat $dstNarBz2)[7];
|
||||
|
||||
print " size $narDiffSize; full size $dstNarBz2Size\n";
|
||||
|
||||
if ($narDiffSize >= $dstNarBz2Size) {
|
||||
print " rejecting; patch bigger than full archive\n";
|
||||
next;
|
||||
}
|
||||
|
||||
if ($narDiffSize / $dstNarBz2Size >= $maxPatchFraction) {
|
||||
print " rejecting; patch too large relative to full archive\n";
|
||||
next;
|
||||
}
|
||||
|
||||
my $finalName =
|
||||
"$narDiffHash.nar-bsdiff";
|
||||
|
||||
if (-e "$patchesDir/$finalName") {
|
||||
print " not copying, already exists\n";
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
system("cp '$tmpDir/DIFF' '$patchesDir/$finalName.tmp'") == 0
|
||||
or die "cannot copy diff";
|
||||
|
||||
rename("$patchesDir/$finalName.tmp", "$patchesDir/$finalName")
|
||||
or die "cannot rename $patchesDir/$finalName.tmp";
|
||||
|
||||
}
|
||||
|
||||
# Add the patch to the manifest.
|
||||
addPatch \%dstPatches, $p,
|
||||
{ url => "$patchesURL/$finalName", hash => "$hashAlgo:$narDiffHash"
|
||||
, size => $narDiffSize, basePath => $closest, baseHash => "$hashAlgo:$baseHash"
|
||||
, narHash => "$hashAlgo:$narHash", patchType => "nar-bsdiff"
|
||||
}, 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Add in any potentially useful patches in the source (namely, those
|
||||
# patches that produce either paths in the destination or paths that
|
||||
# can be used as the base for other useful patches).
|
||||
|
||||
print "propagating patches...\n";
|
||||
|
||||
my $changed;
|
||||
do {
|
||||
# !!! we repeat this to reach the transitive closure; inefficient
|
||||
$changed = 0;
|
||||
|
||||
print "loop\n";
|
||||
|
||||
my %dstBasePaths;
|
||||
foreach my $q (keys %dstPatches) {
|
||||
foreach my $patch (@{$dstPatches{$q}}) {
|
||||
$dstBasePaths{$patch->{basePath}} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $p (keys %srcPatches) {
|
||||
my $patchList = $srcPatches{$p};
|
||||
|
||||
my $include = 0;
|
||||
|
||||
# Is path $p included in the destination? If so, include
|
||||
# patches that produce it.
|
||||
$include = 1 if defined $dstNarFiles{$p};
|
||||
|
||||
# Is path $p a path that serves as a base for paths in the
|
||||
# destination? If so, include patches that produce it.
|
||||
# !!! check baseHash
|
||||
$include = 1 if defined $dstBasePaths{$p};
|
||||
|
||||
if ($include) {
|
||||
foreach my $patch (@{$patchList}) {
|
||||
$changed = 1 if addPatch \%dstPatches, $p, $patch;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} while $changed;
|
||||
|
||||
|
||||
# Rewrite the manifest of the destination (with the new patches).
|
||||
writeManifest "$dstDir/MANIFEST",
|
||||
\%dstNarFiles, \%dstPatches;
|
||||
@@ -12,7 +12,6 @@ my $outLink;
|
||||
my $drvLink;
|
||||
|
||||
my $dryRun = 0;
|
||||
my $verbose = 0;
|
||||
|
||||
my @instArgs = ();
|
||||
my @buildArgs = ();
|
||||
@@ -78,7 +77,7 @@ EOF
|
||||
|
||||
elsif ($arg eq "--attr" or $arg eq "-A") {
|
||||
$n++;
|
||||
die "$0: `$arg' requires an argument\n" unless $n < scalar @ARGV;
|
||||
die "$0: `--attr' requires an argument\n" unless $n < scalar @ARGV;
|
||||
push @instArgs, ("--attr", $ARGV[$n]);
|
||||
}
|
||||
|
||||
@@ -88,21 +87,7 @@ EOF
|
||||
$n += 2;
|
||||
}
|
||||
|
||||
elsif ($arg eq "--log-type") {
|
||||
$n++;
|
||||
die "$0: `$arg' requires an argument\n" unless $n < scalar @ARGV;
|
||||
push @instArgs, ($arg, $ARGV[$n]);
|
||||
push @buildArgs, ($arg, $ARGV[$n]);
|
||||
}
|
||||
|
||||
elsif ($arg eq "--option") {
|
||||
die "$0: `$arg' requires two arguments\n" unless $n + 2 < scalar @ARGV;
|
||||
push @instArgs, ($arg, $ARGV[$n + 1], $ARGV[$n + 2]);
|
||||
push @buildArgs, ($arg, $ARGV[$n + 1], $ARGV[$n + 2]);
|
||||
$n += 2;
|
||||
}
|
||||
|
||||
elsif ($arg eq "--max-jobs" or $arg eq "-j" or $arg eq "--max-silent-time" or $arg eq "--log-type" or $arg eq "--cores") {
|
||||
elsif ($arg eq "--max-jobs" or $arg eq "-j" or $arg eq "--max-silent-time") {
|
||||
$n++;
|
||||
die "$0: `$arg' requires an argument\n" unless $n < scalar @ARGV;
|
||||
push @buildArgs, ($arg, $ARGV[$n]);
|
||||
@@ -113,21 +98,6 @@ EOF
|
||||
$dryRun = 1;
|
||||
}
|
||||
|
||||
elsif ($arg eq "--show-trace") {
|
||||
push @instArgs, $arg;
|
||||
}
|
||||
|
||||
elsif ($arg eq "--verbose" or substr($arg, 0, 2) eq "-v") {
|
||||
push @buildArgs, $arg;
|
||||
push @instArgs, $arg;
|
||||
$verbose = 1;
|
||||
}
|
||||
|
||||
elsif ($arg eq "--quiet") {
|
||||
push @buildArgs, $arg;
|
||||
push @instArgs, $arg;
|
||||
}
|
||||
|
||||
elsif (substr($arg, 0, 1) eq "-") {
|
||||
push @buildArgs, $arg;
|
||||
}
|
||||
@@ -158,25 +128,19 @@ foreach my $expr (@exprs) {
|
||||
# !!! would prefer the perl 5.8.0 pipe open feature here.
|
||||
my $pid = open(DRVPATHS, "-|") || exec "$binDir/nix-instantiate", "--add-root", $drvLink, "--indirect", @instArgs, $expr;
|
||||
while (<DRVPATHS>) {chomp; push @drvPaths, $_;}
|
||||
if (!close DRVPATHS) {
|
||||
die "nix-instantiate killed by signal " . ($? & 127) . "\n" if ($? & 127);
|
||||
exit 1;
|
||||
}
|
||||
close DRVPATHS or exit 1;
|
||||
|
||||
foreach my $drvPath (@drvPaths) {
|
||||
my $target = readlink $drvPath or die "cannot read symlink `$drvPath'";
|
||||
print STDERR "derivation is $target\n" if $verbose;
|
||||
print STDERR "store derivation is $target\n";
|
||||
}
|
||||
|
||||
# Build.
|
||||
my @outPaths;
|
||||
$pid = open(OUTPATHS, "-|") || exec "$binDir/nix-store", "--add-root", $outLink, "--indirect", "-r",
|
||||
$pid = open(OUTPATHS, "-|") || exec "$binDir/nix-store", "--add-root", $outLink, "--indirect", "-rv",
|
||||
@buildArgs, @drvPaths;
|
||||
while (<OUTPATHS>) {chomp; push @outPaths, $_;}
|
||||
if (!close OUTPATHS) {
|
||||
die "nix-store killed by signal " . ($? & 127) . "\n" if ($? & 127);
|
||||
exit 1;
|
||||
}
|
||||
close OUTPATHS or exit 1;
|
||||
|
||||
next if $dryRun;
|
||||
|
||||
|
||||
@@ -78,9 +78,6 @@ sub removeChannel {
|
||||
sub update {
|
||||
readChannels;
|
||||
|
||||
# Create the manifests directory if it doesn't exist.
|
||||
mkdir "$stateDir/manifests", 0755 unless -e "$stateDir/manifests";
|
||||
|
||||
# Do we have write permission to the manifests directory? If not,
|
||||
# then just skip pulling the manifest and just download the Nix
|
||||
# expressions. If the user is a non-privileged user in a
|
||||
@@ -88,6 +85,11 @@ sub update {
|
||||
# source.
|
||||
if (-W "$stateDir/manifests") {
|
||||
|
||||
# Remove all the old manifests.
|
||||
for my $manifest (glob "$stateDir/manifests/*.nixmanifest") {
|
||||
unlink $manifest or die "cannot remove `$manifest': $!";
|
||||
}
|
||||
|
||||
# Pull cache manifests.
|
||||
foreach my $url (@channels) {
|
||||
#print "pulling cache manifest from `$url'\n";
|
||||
@@ -123,13 +125,15 @@ sub update {
|
||||
|
||||
my $rootFile = "$rootsDir/per-user/$userName/channels";
|
||||
|
||||
# Build the Nix expression.
|
||||
# Instantiate the Nix expression.
|
||||
print "unpacking channel Nix expressions...\n";
|
||||
my $outPath = `\\
|
||||
@bindir@/nix-build --out-link '$rootFile' --drv-link '$rootFile'.tmp \\
|
||||
@datadir@/nix/corepkgs/channels/unpack.nix \\
|
||||
--argstr system @system@ --arg inputs '$inputs'`
|
||||
or die "cannot unpack the channels";
|
||||
my $storeExpr = `@bindir@/nix-instantiate --add-root '$rootFile'.tmp @datadir@/nix/corepkgs/channels/unpack.nix --argstr system @system@ --arg inputs '$inputs'`
|
||||
or die "cannot instantiate Nix expression";
|
||||
chomp $storeExpr;
|
||||
|
||||
# Build the resulting derivation.
|
||||
my $outPath = `@bindir@/nix-store --add-root '$rootFile' -r '$storeExpr'`
|
||||
or die "cannot realise store expression";
|
||||
chomp $outPath;
|
||||
|
||||
unlink "$rootFile.tmp";
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#! @perl@ -w -I@libexecdir@/nix
|
||||
|
||||
use SSH;
|
||||
#! @perl@ -w
|
||||
|
||||
my $binDir = $ENV{"NIX_BIN_DIR"} || "@bindir@";
|
||||
|
||||
@@ -16,11 +14,12 @@ EOF
|
||||
|
||||
# Get the target host.
|
||||
my $sshHost;
|
||||
my @sshOpts = split ' ', ($ENV{"NIX_SSHOPTS"} or "");
|
||||
|
||||
my $sign = 0;
|
||||
|
||||
my $compressor = "";
|
||||
my $decompressor = "";
|
||||
my $compressor = "cat";
|
||||
my $decompressor = "cat";
|
||||
|
||||
my $toMode = 1;
|
||||
|
||||
@@ -35,8 +34,8 @@ while (@ARGV) {
|
||||
$sign = 1;
|
||||
}
|
||||
elsif ($arg eq "--gzip") {
|
||||
$compressor = "| gzip";
|
||||
$decompressor = "gunzip |";
|
||||
$compressor = "gzip";
|
||||
$decompressor = "gunzip";
|
||||
}
|
||||
elsif ($arg eq "--from") {
|
||||
$toMode = 0;
|
||||
@@ -53,30 +52,40 @@ while (@ARGV) {
|
||||
}
|
||||
|
||||
|
||||
openSSHConnection $sshHost or die "$0: unable to start SSH\n";
|
||||
|
||||
|
||||
if ($toMode) { # Copy TO the remote machine.
|
||||
|
||||
my @allStorePaths;
|
||||
my %storePathsSeen;
|
||||
|
||||
# Get the closure of this path.
|
||||
my $pid = open(READ, "set -f; $binDir/nix-store --query --requisites @storePaths|") or die;
|
||||
foreach my $storePath (@storePaths) {
|
||||
# $arg might be a symlink to the store, so resolve it.
|
||||
my $storePath2 = (`$binDir/nix-store --query --resolve '$storePath'`
|
||||
or die "cannot resolve `$storePath'");
|
||||
chomp $storePath2;
|
||||
|
||||
# Get the closure of this path.
|
||||
my $pid = open(READ,
|
||||
"$binDir/nix-store --query --requisites '$storePath2'|") or die;
|
||||
|
||||
while (<READ>) {
|
||||
chomp;
|
||||
die "bad: $_" unless /^\//;
|
||||
push @allStorePaths, $_;
|
||||
}
|
||||
while (<READ>) {
|
||||
chomp;
|
||||
die "bad: $_" unless /^\//;
|
||||
if (!defined $storePathsSeen{$_}) {
|
||||
push @allStorePaths, $_;
|
||||
$storePathsSeen{$_} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
close READ or die "nix-store failed: $?";
|
||||
close READ or die "nix-store failed: $?";
|
||||
}
|
||||
|
||||
|
||||
# Ask the remote host which paths are invalid.
|
||||
open(READ, "set -f; ssh $sshHost @sshOpts nix-store --check-validity --print-invalid @allStorePaths|");
|
||||
open(READ, "ssh @sshOpts $sshHost nix-store --check-validity --print-invalid @allStorePaths|");
|
||||
my @missing = ();
|
||||
while (<READ>) {
|
||||
chomp;
|
||||
print STDERR "target machine needs $_\n";
|
||||
push @missing, $_;
|
||||
}
|
||||
close READ or die;
|
||||
@@ -84,11 +93,9 @@ if ($toMode) { # Copy TO the remote machine.
|
||||
|
||||
# Export the store paths and import them on the remote machine.
|
||||
if (scalar @missing > 0) {
|
||||
print STDERR "copying these missing paths:\n";
|
||||
print STDERR " $_\n" foreach @missing;
|
||||
my $extraOpts = "";
|
||||
$extraOpts .= "--sign" if $sign == 1;
|
||||
system("set -f; nix-store --export $extraOpts @missing $compressor | ssh $sshHost @sshOpts '$decompressor nix-store --import'") == 0
|
||||
system("nix-store --export $extraOpts @missing | $compressor | ssh @sshOpts $sshHost '$decompressor | nix-store --import'") == 0
|
||||
or die "copying store paths to remote machine `$sshHost' failed: $?";
|
||||
}
|
||||
|
||||
@@ -101,24 +108,29 @@ else { # Copy FROM the remote machine.
|
||||
# machine. Paths are assumed to be store paths; there is no
|
||||
# resolution (following of symlinks).
|
||||
my $pid = open(READ,
|
||||
"set -f; ssh @sshOpts $sshHost nix-store --query --requisites @storePaths|") or die;
|
||||
"ssh @sshOpts $sshHost nix-store --query --requisites @storePaths|") or die;
|
||||
|
||||
my @allStorePaths;
|
||||
my %storePathsSeen;
|
||||
|
||||
while (<READ>) {
|
||||
chomp;
|
||||
die "bad: $_" unless /^\//;
|
||||
push @allStorePaths, $_;
|
||||
if (!defined $storePathsSeen{$_}) {
|
||||
push @allStorePaths, $_;
|
||||
$storePathsSeen{$_} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
close READ or die "nix-store on remote machine `$sshHost' failed: $?";
|
||||
|
||||
|
||||
# What paths are already valid locally?
|
||||
open(READ, "set -f; @bindir@/nix-store --check-validity --print-invalid @allStorePaths|");
|
||||
open(READ, "@bindir@/nix-store --check-validity --print-invalid @allStorePaths|");
|
||||
my @missing = ();
|
||||
while (<READ>) {
|
||||
chomp;
|
||||
print STDERR "local machine needs $_\n";
|
||||
push @missing, $_;
|
||||
}
|
||||
close READ or die;
|
||||
@@ -126,12 +138,10 @@ else { # Copy FROM the remote machine.
|
||||
|
||||
# Export the store paths on the remote machine and import them on locally.
|
||||
if (scalar @missing > 0) {
|
||||
print STDERR "copying these missing paths:\n";
|
||||
print STDERR " $_\n" foreach @missing;
|
||||
my $extraOpts = "";
|
||||
$extraOpts .= "--sign" if $sign == 1;
|
||||
system("set -f; ssh $sshHost @sshOpts 'nix-store --export $extraOpts @missing $compressor' | $decompressor @bindir@/nix-store --import") == 0
|
||||
or die "copying store paths from remote machine `$sshHost' failed: $?";
|
||||
system("ssh @sshOpts $sshHost 'nix-store --export $extraOpts @missing | $compressor' | $decompressor | @bindir@/nix-store --import") == 0
|
||||
or die "copying store paths to remote machine `$sshHost' failed: $?";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#! @perl@ -w -I@libexecdir@/nix
|
||||
|
||||
use strict;
|
||||
use File::Temp qw(tempdir);
|
||||
use NixManifest;
|
||||
use GeneratePatches;
|
||||
|
||||
if (scalar @ARGV != 5) {
|
||||
print STDERR <<EOF;
|
||||
Usage: nix-generate-patches NAR-DIR PATCH-DIR PATCH-URI OLD-MANIFEST NEW-MANIFEST
|
||||
|
||||
This command generates binary patches between NAR files listed in
|
||||
OLD-MANIFEST and NEW-MANIFEST. The patches are written to the
|
||||
directory PATCH-DIR, and the prefix PATCH-URI is used to generate URIs
|
||||
for the patches. The patches are added to NEW-MANIFEST. All NARs are
|
||||
required to exist in NAR-DIR. Patches are generated between
|
||||
succeeding versions of packages with the same name.
|
||||
EOF
|
||||
exit 1;
|
||||
}
|
||||
|
||||
my $narPath = $ARGV[0];
|
||||
my $patchesPath = $ARGV[1];
|
||||
my $patchesURL = $ARGV[2];
|
||||
my $srcManifest = $ARGV[3];
|
||||
my $dstManifest = $ARGV[4];
|
||||
|
||||
my (%srcNarFiles, %srcLocalPaths, %srcPatches);
|
||||
readManifest $srcManifest, \%srcNarFiles, \%srcLocalPaths, \%srcPatches;
|
||||
|
||||
my (%dstNarFiles, %dstLocalPaths, %dstPatches);
|
||||
readManifest $dstManifest, \%dstNarFiles, \%dstLocalPaths, \%dstPatches;
|
||||
|
||||
my $tmpDir = tempdir("nix-generate-patches.XXXXXX", CLEANUP => 1, TMPDIR => 1)
|
||||
or die "cannot create a temporary directory";
|
||||
|
||||
generatePatches \%srcNarFiles, \%dstNarFiles, \%srcPatches, \%dstPatches,
|
||||
$narPath, $patchesPath, $patchesURL, $tmpDir;
|
||||
|
||||
propagatePatches \%srcPatches, \%dstNarFiles, \%dstPatches;
|
||||
|
||||
writeManifest $dstManifest, \%dstNarFiles, \%dstPatches;
|
||||
@@ -12,8 +12,7 @@ needed_path="?$QUERY_STRING"
|
||||
needed_path="${needed_path#*[?&]needed_path=}"
|
||||
needed_path="${needed_path%%&*}"
|
||||
#needed_path="$(echo $needed_path | ./unhttp)"
|
||||
needed_path="${needed_path//%2B/+}"
|
||||
needed_path="${needed_path//%3D/=}"
|
||||
needed_path="$(echo $needed_path | sed -e 's/%2B/+/g; s/%3D/=/g')"
|
||||
|
||||
echo needed_path: "$needed_path" >&2
|
||||
|
||||
|
||||
@@ -123,11 +123,6 @@ if ($interactive) {
|
||||
}
|
||||
|
||||
|
||||
# Store the manifest in the temporary directory so that we don't
|
||||
# pollute /nix/var/nix/manifests.
|
||||
$ENV{NIX_MANIFESTS_DIR} = $tmpDir;
|
||||
|
||||
|
||||
print "\nPulling manifests...\n";
|
||||
system("$binDir/nix-pull", $manifestURL) == 0
|
||||
or barf "nix-pull failed: $?";
|
||||
|
||||
@@ -21,10 +21,7 @@ if test -z "$url"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Handle escaped characters in the URI. `+', `=' and `?' are the only
|
||||
# characters that are valid in Nix store path names but have a special
|
||||
# meaning in URIs.
|
||||
name=$(basename "$url" | @sed@ -e 's/%2b/+/g' -e 's/%3d/=/g' -e 's/%3f/\?/g')
|
||||
name=$(basename "$url")
|
||||
if test -z "$name"; then echo "invalid url"; exit 1; fi
|
||||
|
||||
|
||||
@@ -63,7 +60,7 @@ removeTempDir() {
|
||||
|
||||
|
||||
doDownload() {
|
||||
@curl@ $cacheFlags --fail --location --max-redirs 20 --disable-epsv \
|
||||
@curl@ $cacheFlags --fail -# --location --max-redirs 20 --disable-epsv \
|
||||
--cookie-jar $tmpPath/cookies "$url" -o $tmpFile
|
||||
}
|
||||
|
||||
|
||||
@@ -2,28 +2,27 @@
|
||||
|
||||
use strict;
|
||||
use File::Temp qw(tempdir);
|
||||
use NixManifest;
|
||||
use readmanifest;
|
||||
|
||||
my $tmpDir = tempdir("nix-pull.XXXXXX", CLEANUP => 1, TMPDIR => 1)
|
||||
or die "cannot create a temporary directory";
|
||||
|
||||
my $binDir = $ENV{"NIX_BIN_DIR"} || "@bindir@";
|
||||
my $libexecDir = ($ENV{"NIX_LIBEXEC_DIR"} or "@libexecdir@");
|
||||
my $storeDir = ($ENV{"NIX_STORE_DIR"} or "@storedir@");
|
||||
my $stateDir = ($ENV{"NIX_STATE_DIR"} or "@localstatedir@/nix");
|
||||
my $manifestDir = ($ENV{"NIX_MANIFESTS_DIR"} or "$stateDir/manifests");
|
||||
|
||||
my $libexecDir = $ENV{"NIX_LIBEXEC_DIR"};
|
||||
$libexecDir = "@libexecdir@" unless defined $libexecDir;
|
||||
|
||||
my $stateDir = $ENV{"NIX_STATE_DIR"};
|
||||
$stateDir = "@localstatedir@/nix" unless defined $stateDir;
|
||||
|
||||
my $storeDir = $ENV{"NIX_STORE_DIR"};
|
||||
$storeDir = "@storedir@" unless defined $storeDir;
|
||||
|
||||
|
||||
# Prevent access problems in shared-stored installations.
|
||||
umask 0022;
|
||||
|
||||
|
||||
# Create the manifests directory if it doesn't exist.
|
||||
if (! -e $manifestDir) {
|
||||
mkdir $manifestDir, 0755 or die "cannot create directory `$manifestDir'";
|
||||
}
|
||||
|
||||
|
||||
# Process the URLs specified on the command line.
|
||||
my %narFiles;
|
||||
my %localPaths;
|
||||
@@ -51,7 +50,7 @@ sub processURL {
|
||||
|
||||
# First see if a bzipped manifest is available.
|
||||
if (system("@curl@ --fail --silent --head '$url'.bz2 > /dev/null") == 0) {
|
||||
print "fetching list of Nix archives at `$url.bz2'...\n";
|
||||
print "obtaining list of Nix archives at `$url.bz2'...\n";
|
||||
my $bzipped = downloadFile "$url.bz2";
|
||||
|
||||
$manifest = "$tmpDir/MANIFEST";
|
||||
@@ -69,11 +68,10 @@ sub processURL {
|
||||
print "obtaining list of Nix archives at `$url'...\n";
|
||||
$manifest = downloadFile $url;
|
||||
}
|
||||
|
||||
my $version = readManifest($manifest, \%narFiles, \%localPaths, \%patches);
|
||||
|
||||
die "`$url' is not a manifest or it is too old (i.e., for Nix <= 0.7)\n" if $version < 3;
|
||||
die "manifest `$url' is too new\n" if $version >= 5;
|
||||
if (readManifest($manifest, \%narFiles, \%localPaths, \%patches) < 3) {
|
||||
die "`$url' is not manifest or it is too old (i.e., for Nix <= 0.7)\n";
|
||||
}
|
||||
|
||||
if ($skipWrongStore) {
|
||||
foreach my $path (keys %narFiles) {
|
||||
@@ -92,31 +90,11 @@ sub processURL {
|
||||
my $hash = `$binDir/nix-hash --flat '$manifest'`
|
||||
or die "cannot hash `$manifest'";
|
||||
chomp $hash;
|
||||
|
||||
my $urlFile = "$manifestDir/$baseName-$hash.url";
|
||||
open URL, ">$urlFile" or die "cannot create `$urlFile'";
|
||||
print URL "$url";
|
||||
close URL;
|
||||
|
||||
my $finalPath = "$manifestDir/$baseName-$hash.nixmanifest";
|
||||
|
||||
unlink $finalPath if -e $finalPath;
|
||||
|
||||
symlink("$manifest", "$finalPath")
|
||||
my $finalPath = "$stateDir/manifests/$baseName-$hash.nixmanifest";
|
||||
|
||||
system("@coreutils@/ln", "-sfn", "$manifest", "$finalPath") == 0
|
||||
or die "cannot link `$finalPath to `$manifest'";
|
||||
|
||||
# Delete all old manifests downloaded from this URL.
|
||||
for my $urlFile2 (glob "$manifestDir/*.url") {
|
||||
next if $urlFile eq $urlFile2;
|
||||
open URL, "<$urlFile2" or die;
|
||||
my $url2 = <URL>;
|
||||
chomp $url2;
|
||||
close URL;
|
||||
next unless $url eq $url2;
|
||||
my $base = $urlFile2; $base =~ s/.url$//;
|
||||
unlink "${base}.url";
|
||||
unlink "${base}.nixmanifest";
|
||||
}
|
||||
}
|
||||
|
||||
while (@ARGV) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use strict;
|
||||
use File::Temp qw(tempdir);
|
||||
use NixManifest;
|
||||
use readmanifest;
|
||||
|
||||
my $hashAlgo = "sha256";
|
||||
|
||||
@@ -128,7 +128,7 @@ while (<READ>) {
|
||||
close READ or die "nix-instantiate failed: $?";
|
||||
|
||||
|
||||
# Build the derivations.
|
||||
# Realise the store expressions.
|
||||
print STDERR "creating archives...\n";
|
||||
|
||||
my @narPaths;
|
||||
@@ -140,7 +140,12 @@ while (scalar @tmp > 0) {
|
||||
my @tmp2 = @tmp[0..$n - 1];
|
||||
@tmp = @tmp[$n..scalar @tmp - 1];
|
||||
|
||||
my $pid = open(READ, "$binDir/nix-store --realise @tmp2|")
|
||||
# Note: we disable build hooks because of the impure path
|
||||
# reference (see above). Even if that is fixed, using a hook
|
||||
# probably wouldn't make that much sense; pumping lots of data
|
||||
# around just to compress them won't gain that much.
|
||||
$ENV{"NIX_BUILD_HOOK"} = "";
|
||||
my $pid = open(READ, "$binDir/nix-store --no-build-hook --realise @tmp2|")
|
||||
or die "cannot run nix-store";
|
||||
while (<READ>) {
|
||||
chomp;
|
||||
@@ -172,6 +177,12 @@ for (my $n = 0; $n < scalar @storePaths; $n++) {
|
||||
$narbz2Hash =~ /^[0-9a-z]+$/ or die "invalid hash";
|
||||
close HASH;
|
||||
|
||||
open HASH, "$narDir/nar-hash" or die "cannot open nar-hash";
|
||||
my $narHash = <HASH>;
|
||||
chomp $narHash;
|
||||
$narHash =~ /^[0-9a-z]+$/ or die "invalid hash";
|
||||
close HASH;
|
||||
|
||||
my $narName = "$narbz2Hash.nar.bz2";
|
||||
|
||||
my $narFile = "$narDir/$narName";
|
||||
@@ -189,14 +200,6 @@ for (my $n = 0; $n < scalar @storePaths; $n++) {
|
||||
chomp $deriver;
|
||||
$deriver = "" if $deriver eq "unknown-deriver";
|
||||
|
||||
my $narHash = `$binDir/nix-store --query --hash '$storePath'`;
|
||||
die "cannot query hash for `$storePath'" if $? != 0;
|
||||
chomp $narHash;
|
||||
|
||||
my $narSize = `$binDir/nix-store --query --size '$storePath'`;
|
||||
die "cannot query size for `$storePath'" if $? != 0;
|
||||
chomp $narSize;
|
||||
|
||||
my $url;
|
||||
if ($localCopy) {
|
||||
$url = "$targetArchivesUrl/$narName";
|
||||
@@ -207,8 +210,7 @@ for (my $n = 0; $n < scalar @storePaths; $n++) {
|
||||
{ url => $url
|
||||
, hash => "$hashAlgo:$narbz2Hash"
|
||||
, size => $narbz2Size
|
||||
, narHash => "$narHash"
|
||||
, narSize => $narSize
|
||||
, narHash => "$hashAlgo:$narHash"
|
||||
, references => $references
|
||||
, deriver => $deriver
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ use strict;
|
||||
|
||||
|
||||
sub addPatch {
|
||||
my ($patches, $storePath, $patch) = @_;
|
||||
my $patches = shift;
|
||||
my $storePath = shift;
|
||||
my $patch = shift;
|
||||
my $allowConflicts = shift;
|
||||
|
||||
$$patches{$storePath} = []
|
||||
unless defined $$patches{$storePath};
|
||||
@@ -11,9 +14,15 @@ sub addPatch {
|
||||
|
||||
my $found = 0;
|
||||
foreach my $patch2 (@{$patchList}) {
|
||||
$found = 1 if
|
||||
$patch2->{url} eq $patch->{url} &&
|
||||
$patch2->{basePath} eq $patch->{basePath};
|
||||
if ($patch2->{url} eq $patch->{url}) {
|
||||
if ($patch2->{hash} eq $patch->{hash}) {
|
||||
$found = 1 if ($patch2->{basePath} eq $patch->{basePath});
|
||||
} else {
|
||||
die "conflicting hashes for URL $patch->{url}, " .
|
||||
"namely $patch2->{hash} and $patch->{hash}"
|
||||
unless $allowConflicts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
push @{$patchList}, $patch if !$found;
|
||||
@@ -23,7 +32,12 @@ sub addPatch {
|
||||
|
||||
|
||||
sub readManifest {
|
||||
my ($manifest, $narFiles, $localPaths, $patches) = @_;
|
||||
my $manifest = shift;
|
||||
my $narFiles = shift;
|
||||
my $localPaths = shift;
|
||||
my $patches = shift;
|
||||
my $allowConflicts = shift;
|
||||
$allowConflicts = 0 unless defined $allowConflicts;
|
||||
|
||||
open MANIFEST, "<$manifest"
|
||||
or die "cannot open `$manifest': $!";
|
||||
@@ -33,8 +47,18 @@ sub readManifest {
|
||||
|
||||
my $manifestVersion = 2;
|
||||
|
||||
my ($storePath, $url, $hash, $size, $basePath, $baseHash, $patchType);
|
||||
my ($narHash, $narSize, $references, $deriver, $hashAlgo, $copyFrom, $system);
|
||||
my $storePath;
|
||||
my $url;
|
||||
my $hash;
|
||||
my $size;
|
||||
my $basePath;
|
||||
my $baseHash;
|
||||
my $patchType;
|
||||
my $narHash;
|
||||
my $references;
|
||||
my $deriver;
|
||||
my $hashAlgo;
|
||||
my $copyFrom;
|
||||
|
||||
while (<MANIFEST>) {
|
||||
chomp;
|
||||
@@ -52,11 +76,9 @@ sub readManifest {
|
||||
undef $hash;
|
||||
undef $size;
|
||||
undef $narHash;
|
||||
undef $narSize;
|
||||
undef $basePath;
|
||||
undef $baseHash;
|
||||
undef $patchType;
|
||||
undef $system;
|
||||
$references = "";
|
||||
$deriver = "";
|
||||
$hashAlgo = "md5";
|
||||
@@ -76,15 +98,21 @@ sub readManifest {
|
||||
|
||||
my $found = 0;
|
||||
foreach my $narFile (@{$narFileList}) {
|
||||
$found = 1 if $narFile->{url} eq $url;
|
||||
if ($narFile->{url} eq $url) {
|
||||
if ($narFile->{hash} eq $hash) {
|
||||
$found = 1;
|
||||
} else {
|
||||
die "conflicting hashes for URL $url, " .
|
||||
"namely $narFile->{hash} and $hash"
|
||||
unless $allowConflicts;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
push @{$narFileList},
|
||||
{ url => $url, hash => $hash, size => $size
|
||||
, narHash => $narHash, narSize => $narSize
|
||||
, references => $references
|
||||
, narHash => $narHash, references => $references
|
||||
, deriver => $deriver, hashAlgo => $hashAlgo
|
||||
, system => $system
|
||||
};
|
||||
}
|
||||
|
||||
@@ -94,9 +122,9 @@ sub readManifest {
|
||||
addPatch $patches, $storePath,
|
||||
{ url => $url, hash => $hash, size => $size
|
||||
, basePath => $basePath, baseHash => $baseHash
|
||||
, narHash => $narHash, narSize => $narSize
|
||||
, patchType => $patchType, hashAlgo => $hashAlgo
|
||||
};
|
||||
, narHash => $narHash, patchType => $patchType
|
||||
, hashAlgo => $hashAlgo
|
||||
}, $allowConflicts;
|
||||
}
|
||||
|
||||
elsif ($type eq "localPath") {
|
||||
@@ -126,11 +154,9 @@ sub readManifest {
|
||||
elsif (/^\s*BaseHash:\s*(\S+)\s*$/) { $baseHash = $1; }
|
||||
elsif (/^\s*Type:\s*(\S+)\s*$/) { $patchType = $1; }
|
||||
elsif (/^\s*NarHash:\s*(\S+)\s*$/) { $narHash = $1; }
|
||||
elsif (/^\s*NarSize:\s*(\d+)\s*$/) { $narSize = $1; }
|
||||
elsif (/^\s*References:\s*(.*)\s*$/) { $references = $1; }
|
||||
elsif (/^\s*Deriver:\s*(\S+)\s*$/) { $deriver = $1; }
|
||||
elsif (/^\s*ManifestVersion:\s*(\d+)\s*$/) { $manifestVersion = $1; }
|
||||
elsif (/^\s*System:\s*(\S+)\s*$/) { $system = $1; }
|
||||
|
||||
# Compatibility;
|
||||
elsif (/^\s*NarURL:\s*(\S+)\s*$/) { $url = $1; }
|
||||
@@ -145,8 +171,12 @@ sub readManifest {
|
||||
}
|
||||
|
||||
|
||||
sub writeManifest {
|
||||
my ($manifest, $narFiles, $patches, $noCompress) = @_;
|
||||
sub writeManifest
|
||||
{
|
||||
my $manifest = shift;
|
||||
my $narFiles = shift;
|
||||
my $patches = shift;
|
||||
my $copySources = shift;
|
||||
|
||||
open MANIFEST, ">$manifest.tmp"; # !!! check exclusive
|
||||
|
||||
@@ -160,15 +190,13 @@ sub writeManifest {
|
||||
print MANIFEST "{\n";
|
||||
print MANIFEST " StorePath: $storePath\n";
|
||||
print MANIFEST " NarURL: $narFile->{url}\n";
|
||||
print MANIFEST " Hash: $narFile->{hash}\n" if defined $narFile->{hash};
|
||||
print MANIFEST " Size: $narFile->{size}\n" if defined $narFile->{size};
|
||||
print MANIFEST " Hash: $narFile->{hash}\n";
|
||||
print MANIFEST " NarHash: $narFile->{narHash}\n";
|
||||
print MANIFEST " NarSize: $narFile->{narSize}\n" if $narFile->{narSize};
|
||||
print MANIFEST " Size: $narFile->{size}\n";
|
||||
print MANIFEST " References: $narFile->{references}\n"
|
||||
if defined $narFile->{references} && $narFile->{references} ne "";
|
||||
print MANIFEST " Deriver: $narFile->{deriver}\n"
|
||||
if defined $narFile->{deriver} && $narFile->{deriver} ne "";
|
||||
print MANIFEST " System: $narFile->{system}\n" if defined $narFile->{system};
|
||||
print MANIFEST "}\n";
|
||||
}
|
||||
}
|
||||
@@ -180,9 +208,8 @@ sub writeManifest {
|
||||
print MANIFEST " StorePath: $storePath\n";
|
||||
print MANIFEST " NarURL: $patch->{url}\n";
|
||||
print MANIFEST " Hash: $patch->{hash}\n";
|
||||
print MANIFEST " Size: $patch->{size}\n";
|
||||
print MANIFEST " NarHash: $patch->{narHash}\n";
|
||||
print MANIFEST " NarSize: $patch->{narSize}\n" if $patch->{narSize};
|
||||
print MANIFEST " Size: $patch->{size}\n";
|
||||
print MANIFEST " BasePath: $patch->{basePath}\n";
|
||||
print MANIFEST " BaseHash: $patch->{baseHash}\n";
|
||||
print MANIFEST " Type: $patch->{patchType}\n";
|
||||
@@ -198,13 +225,11 @@ sub writeManifest {
|
||||
|
||||
|
||||
# Create a bzipped manifest.
|
||||
unless (defined $noCompress) {
|
||||
system("@bzip2@ < $manifest > $manifest.bz2.tmp") == 0
|
||||
or die "cannot compress manifest";
|
||||
system("@bzip2@ < $manifest > $manifest.bz2.tmp") == 0
|
||||
or die "cannot compress manifest";
|
||||
|
||||
rename("$manifest.bz2.tmp", "$manifest.bz2")
|
||||
or die "cannot rename $manifest.bz2.tmp: $!";
|
||||
}
|
||||
rename("$manifest.bz2.tmp", "$manifest.bz2")
|
||||
or die "cannot rename $manifest.bz2.tmp: $!";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SUBDIRS = bin2c boost libutil libstore libmain nix-store nix-hash \
|
||||
libexpr nix-instantiate nix-env nix-worker nix-setuid-helper \
|
||||
nix-log2xml bsdiff-4.3
|
||||
|
||||
EXTRA_DIST = aterm-helper.pl
|
||||
|
||||
178
src/aterm-helper.pl
Executable file
178
src/aterm-helper.pl
Executable file
@@ -0,0 +1,178 @@
|
||||
#! /usr/bin/perl -w
|
||||
|
||||
# This program generates C/C++ code for efficiently manipulating
|
||||
# ATerms. It generates functions to build and match ATerms according
|
||||
# to a set of constructor definitions defined in a file read from
|
||||
# standard input. A constructor is defined by a line with the
|
||||
# following format:
|
||||
#
|
||||
# SYM | ARGS | TYPE | FUN?
|
||||
#
|
||||
# where SYM is the name of the constructor, ARGS is a
|
||||
# whitespace-separated list of argument types, TYPE is the type of the
|
||||
# resulting ATerm (which should be `ATerm' or a type synonym for
|
||||
# `ATerm'), and the optional FUN is used to construct the names of the
|
||||
# build and match functions (it defaults to SYM; overriding it is
|
||||
# useful if there are overloaded constructors, e.g., with different
|
||||
# arities). Note that SYM may be empty.
|
||||
#
|
||||
# A line of the form
|
||||
#
|
||||
# VAR = EXPR
|
||||
#
|
||||
# causes a ATerm variable to be generated that is initialised to the
|
||||
# value EXPR.
|
||||
#
|
||||
# Finally, a line of the form
|
||||
#
|
||||
# init NAME
|
||||
#
|
||||
# causes the initialisation function to be called `NAME'. This
|
||||
# function must be called before any of the build/match functions or
|
||||
# the generated variables are used.
|
||||
|
||||
die if scalar @ARGV != 2;
|
||||
|
||||
my $syms = "";
|
||||
my $init = "";
|
||||
my $initFun = "init";
|
||||
|
||||
open HEADER, ">$ARGV[0]";
|
||||
open IMPL, ">$ARGV[1]";
|
||||
|
||||
print HEADER "#include <aterm2.h>\n";
|
||||
print HEADER "#ifdef __cplusplus\n";
|
||||
print HEADER "namespace nix {\n";
|
||||
print HEADER "#endif\n\n\n";
|
||||
print IMPL "namespace nix {\n";
|
||||
|
||||
while (<STDIN>) {
|
||||
s/\#.*//;
|
||||
next if (/^\s*$/);
|
||||
|
||||
if (/^\s*(\w*)\s*\|([^\|]*)\|\s*(\w+)\s*\|\s*(\w+)?/) {
|
||||
my $const = $1;
|
||||
my @types = split ' ', $2;
|
||||
my $result = $3;
|
||||
my $funname = $4;
|
||||
$funname = $const unless defined $funname;
|
||||
|
||||
my $formals = "";
|
||||
my $formals2 = "";
|
||||
my $args = "";
|
||||
my $unpack = "";
|
||||
my $n = 1;
|
||||
foreach my $type (@types) {
|
||||
my $realType = $type;
|
||||
$args .= ", ";
|
||||
if ($type eq "string") {
|
||||
# $args .= "(ATerm) ATmakeAppl0(ATmakeAFun((char *) e$n, 0, ATtrue))";
|
||||
# $type = "const char *";
|
||||
$type = "ATerm";
|
||||
$args .= "e$n";
|
||||
# !!! in the matcher, we should check that the
|
||||
# argument is a string (i.e., a nullary application).
|
||||
} elsif ($type eq "int") {
|
||||
$args .= "(ATerm) ATmakeInt(e$n)";
|
||||
} elsif ($type eq "ATermList" || $type eq "ATermBlob") {
|
||||
$args .= "(ATerm) e$n";
|
||||
} else {
|
||||
$args .= "e$n";
|
||||
}
|
||||
$formals .= ", " if $formals ne "";
|
||||
$formals .= "$type e$n";
|
||||
$formals2 .= ", ";
|
||||
$formals2 .= "$type & e$n";
|
||||
my $m = $n - 1;
|
||||
# !!! more checks here
|
||||
if ($type eq "int") {
|
||||
$unpack .= " e$n = ATgetInt((ATermInt) ATgetArgument(e, $m));\n";
|
||||
} elsif ($type eq "ATermList") {
|
||||
$unpack .= " e$n = (ATermList) ATgetArgument(e, $m);\n";
|
||||
} elsif ($type eq "ATermBlob") {
|
||||
$unpack .= " e$n = (ATermBlob) ATgetArgument(e, $m);\n";
|
||||
} elsif ($realType eq "string") {
|
||||
$unpack .= " e$n = ATgetArgument(e, $m);\n";
|
||||
$unpack .= " if (ATgetType(e$n) != AT_APPL) return false;\n";
|
||||
} else {
|
||||
$unpack .= " e$n = ATgetArgument(e, $m);\n";
|
||||
}
|
||||
$n++;
|
||||
}
|
||||
|
||||
my $arity = scalar @types;
|
||||
|
||||
print HEADER "extern AFun sym$funname;\n\n";
|
||||
|
||||
print IMPL "AFun sym$funname = 0;\n";
|
||||
|
||||
if ($arity == 0) {
|
||||
print HEADER "extern ATerm const$funname;\n\n";
|
||||
print IMPL "ATerm const$funname = 0;\n";
|
||||
}
|
||||
|
||||
print HEADER "static inline $result make$funname($formals) __attribute__ ((pure, nothrow));\n";
|
||||
print HEADER "static inline $result make$funname($formals) {\n";
|
||||
if ($arity == 0) {
|
||||
print HEADER " return const$funname;\n";
|
||||
}
|
||||
elsif ($arity <= 6) {
|
||||
print HEADER " return (ATerm) ATmakeAppl$arity(sym$funname$args);\n";
|
||||
} else {
|
||||
$args =~ s/^,//;
|
||||
print HEADER " ATerm array[$arity] = {$args};\n";
|
||||
print HEADER " return (ATerm) ATmakeApplArray(sym$funname, array);\n";
|
||||
}
|
||||
print HEADER "}\n\n";
|
||||
|
||||
print HEADER "#ifdef __cplusplus\n";
|
||||
print HEADER "static inline bool match$funname(ATerm e$formals2) {\n";
|
||||
print HEADER " if (ATgetType(e) != AT_APPL || (AFun) ATgetAFun(e) != sym$funname) return false;\n";
|
||||
print HEADER "$unpack";
|
||||
print HEADER " return true;\n";
|
||||
print HEADER "}\n";
|
||||
print HEADER "#endif\n\n\n";
|
||||
|
||||
$init .= " sym$funname = ATmakeAFun(\"$const\", $arity, ATfalse);\n";
|
||||
$init .= " ATprotectAFun(sym$funname);\n";
|
||||
if ($arity == 0) {
|
||||
$init .= " const$funname = (ATerm) ATmakeAppl0(sym$funname);\n";
|
||||
$init .= " ATprotect(&const$funname);\n";
|
||||
}
|
||||
}
|
||||
|
||||
elsif (/^\s*(\w+)\s*=\s*(.*)$/) {
|
||||
my $name = $1;
|
||||
my $value = $2;
|
||||
print HEADER "extern ATerm $name;\n";
|
||||
print IMPL "ATerm $name = 0;\n";
|
||||
$init .= " $name = $value;\n";
|
||||
}
|
||||
|
||||
elsif (/^\s*init\s+(\w+)\s*$/) {
|
||||
$initFun = $1;
|
||||
}
|
||||
|
||||
else {
|
||||
die "bad line: `$_'";
|
||||
}
|
||||
}
|
||||
|
||||
print HEADER "void $initFun();\n\n";
|
||||
|
||||
print HEADER "static inline const char * aterm2String(ATerm t) {\n";
|
||||
print HEADER " return (const char *) ATgetName(ATgetAFun(t));\n";
|
||||
print HEADER "}\n\n";
|
||||
|
||||
print IMPL "\n";
|
||||
print IMPL "void $initFun() {\n";
|
||||
print IMPL "$init";
|
||||
print IMPL "}\n";
|
||||
|
||||
print HEADER "#ifdef __cplusplus\n";
|
||||
print HEADER "}\n";
|
||||
print HEADER "#endif\n\n\n";
|
||||
print IMPL "}\n";
|
||||
|
||||
close HEADER;
|
||||
close IMPL;
|
||||
@@ -1,6 +1,3 @@
|
||||
noinst_PROGRAMS = bin2c
|
||||
|
||||
bin2c_SOURCES = bin2c.c
|
||||
|
||||
bin2c$(EXEEXT): bin2c.c
|
||||
$(CC_FOR_BUILD) $(CFLAGS_FOR_BUILD) -o bin2c bin2c.c
|
||||
|
||||
@@ -14,10 +14,10 @@ int main(int argc, char * * argv)
|
||||
{
|
||||
int c;
|
||||
if (argc != 2) abort();
|
||||
print("static unsigned char %s[] = { ", argv[1]);
|
||||
print("static unsigned char %s[] = {", argv[1]);
|
||||
while ((c = getchar()) != EOF) {
|
||||
print("0x%02x, ", (unsigned char) c);
|
||||
}
|
||||
print("0 };\n");
|
||||
print("};\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
SUBDIRS = format
|
||||
|
||||
nobase_pkginclude_HEADERS = assert.hpp checked_delete.hpp format.hpp \
|
||||
pkginclude_HEADERS = assert.hpp checked_delete.hpp format.hpp \
|
||||
shared_ptr.hpp weak_ptr.hpp throw_exception.hpp \
|
||||
enable_shared_from_this.hpp \
|
||||
detail/shared_count.hpp detail/workaround.hpp
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
EXTRA_DIST = compat-include
|
||||
|
||||
libexec_PROGRAMS = bsdiff bspatch
|
||||
|
||||
bsdiff_SOURCES = bsdiff.c
|
||||
@@ -10,4 +8,4 @@ bspatch_SOURCES = bspatch.c
|
||||
|
||||
bspatch_LDADD = ${bzip2_lib}
|
||||
|
||||
AM_CFLAGS = -O3 ${bzip2_include} ${bsddiff_compat_include}
|
||||
AM_CFLAGS = -O3 ${bzip2_include}
|
||||
|
||||
@@ -277,7 +277,6 @@ int main(int argc,char *argv[])
|
||||
for(scsc=scan+=len;scan<newsize;scan++) {
|
||||
len=search(I,old,oldsize,new+scan,newsize-scan,
|
||||
0,oldsize,&pos);
|
||||
if (len > 64 * 1024) break;
|
||||
|
||||
for(;scsc<scan+len;scsc++)
|
||||
if((scsc+lastoffset<oldsize) &&
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
/* Simulate BSD's <err.h> functionality. */
|
||||
|
||||
#ifndef COMPAT_ERR_H_INCLUDED
|
||||
#define COMPAT_ERR_H_INCLUDED 1
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define err(rc,...) do { fprintf(stderr,__VA_ARGS__); exit(rc); } while(0)
|
||||
#define errx(rc,...) do { fprintf(stderr,__VA_ARGS__); exit(rc); } while(0)
|
||||
|
||||
#endif
|
||||
@@ -2,25 +2,27 @@ pkglib_LTLIBRARIES = libexpr.la
|
||||
|
||||
libexpr_la_SOURCES = \
|
||||
nixexpr.cc eval.cc primops.cc lexer-tab.cc parser-tab.cc \
|
||||
get-drvs.cc attr-path.cc value-to-xml.cc common-opts.cc \
|
||||
get-drvs.cc attr-path.cc expr-to-xml.cc common-opts.cc \
|
||||
names.cc
|
||||
|
||||
pkginclude_HEADERS = \
|
||||
nixexpr.hh eval.hh parser.hh lexer-tab.hh parser-tab.hh \
|
||||
get-drvs.hh attr-path.hh value-to-xml.hh common-opts.hh \
|
||||
names.hh symbol-table.hh
|
||||
get-drvs.hh attr-path.hh expr-to-xml.hh common-opts.hh \
|
||||
names.hh
|
||||
|
||||
libexpr_la_LIBADD = ../libutil/libutil.la ../libstore/libstore.la \
|
||||
../boost/format/libformat.la @boehmgc_lib@
|
||||
../boost/format/libformat.la
|
||||
|
||||
BUILT_SOURCES = \
|
||||
BUILT_SOURCES = nixexpr-ast.cc nixexpr-ast.hh \
|
||||
parser-tab.hh lexer-tab.hh parser-tab.cc lexer-tab.cc
|
||||
|
||||
EXTRA_DIST = lexer.l parser.y
|
||||
EXTRA_DIST = lexer.l parser.y nixexpr-ast.def nixexpr-ast.cc
|
||||
|
||||
AM_CXXFLAGS = \
|
||||
-I$(srcdir)/.. \
|
||||
-I$(srcdir)/.. ${bdb_include} ${aterm_include} \
|
||||
-I$(srcdir)/../libutil -I$(srcdir)/../libstore
|
||||
AM_CFLAGS = \
|
||||
${aterm_include}
|
||||
|
||||
|
||||
# Parser generation.
|
||||
@@ -32,6 +34,15 @@ lexer-tab.cc lexer-tab.hh: lexer.l
|
||||
$(flex) --outfile lexer-tab.cc --header-file=lexer-tab.hh $(srcdir)/lexer.l
|
||||
|
||||
|
||||
# ATerm helper function generation.
|
||||
|
||||
nixexpr-ast.cc nixexpr-ast.hh: ../aterm-helper.pl nixexpr-ast.def
|
||||
$(perl) $(srcdir)/../aterm-helper.pl nixexpr-ast.hh nixexpr-ast.cc < $(srcdir)/nixexpr-ast.def
|
||||
|
||||
|
||||
CLEANFILES =
|
||||
|
||||
|
||||
# SDF stuff (not built by default).
|
||||
nix.tbl: nix.sdf
|
||||
sdf2table -m Nix -s -i nix.sdf -o nix.tbl
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
#include "attr-path.hh"
|
||||
#include "nixexpr-ast.hh"
|
||||
#include "util.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
// !!! Shouldn't we return a pointer to a Value?
|
||||
void findAlongAttrPath(EvalState & state, const string & attrPath,
|
||||
Bindings & autoArgs, Expr * e, Value & v)
|
||||
bool isAttrs(EvalState & state, Expr e, ATermMap & attrs)
|
||||
{
|
||||
e = evalExpr(state, e);
|
||||
ATermList dummy;
|
||||
if (!matchAttrs(e, dummy)) return false;
|
||||
queryAllAttrs(e, attrs, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Expr findAlongAttrPath(EvalState & state, const string & attrPath,
|
||||
const ATermMap & autoArgs, Expr e)
|
||||
{
|
||||
Strings tokens = tokenizeString(attrPath, ".");
|
||||
|
||||
@@ -15,10 +25,8 @@ void findAlongAttrPath(EvalState & state, const string & attrPath,
|
||||
Error(format("attribute selection path `%1%' does not match expression") % attrPath);
|
||||
|
||||
string curPath;
|
||||
|
||||
state.mkThunk_(v, e);
|
||||
|
||||
foreach (Strings::iterator, i, tokens) {
|
||||
for (Strings::iterator i = tokens.begin(); i != tokens.end(); ++i) {
|
||||
|
||||
if (!curPath.empty()) curPath += ".";
|
||||
curPath += *i;
|
||||
@@ -30,10 +38,7 @@ void findAlongAttrPath(EvalState & state, const string & attrPath,
|
||||
if (string2Int(attr, attrIndex)) apType = apIndex;
|
||||
|
||||
/* Evaluate the expression. */
|
||||
Value vTmp;
|
||||
state.autoCallFunction(autoArgs, v, vTmp);
|
||||
v = vTmp;
|
||||
state.forceValue(v);
|
||||
e = evalExpr(state, autoCallFunction(evalExpr(state, e), autoArgs));
|
||||
|
||||
/* It should evaluate to either an attribute set or an
|
||||
expression, according to what is specified in the
|
||||
@@ -41,31 +46,36 @@ void findAlongAttrPath(EvalState & state, const string & attrPath,
|
||||
|
||||
if (apType == apAttr) {
|
||||
|
||||
if (v.type != tAttrs)
|
||||
ATermMap attrs;
|
||||
|
||||
if (!isAttrs(state, e, attrs))
|
||||
throw TypeError(
|
||||
format("the expression selected by the selection path `%1%' should be an attribute set but is %2%")
|
||||
% curPath % showType(v));
|
||||
|
||||
Bindings::iterator a = v.attrs->find(state.symbols.create(attr));
|
||||
if (a == v.attrs->end())
|
||||
% curPath % showType(e));
|
||||
|
||||
e = attrs.get(toATerm(attr));
|
||||
if (!e)
|
||||
throw Error(format("attribute `%1%' in selection path `%2%' not found") % attr % curPath);
|
||||
v = *a->value;
|
||||
|
||||
}
|
||||
|
||||
else if (apType == apIndex) {
|
||||
|
||||
if (v.type != tList)
|
||||
ATermList es;
|
||||
if (!matchList(e, es))
|
||||
throw TypeError(
|
||||
format("the expression selected by the selection path `%1%' should be a list but is %2%")
|
||||
% curPath % showType(v));
|
||||
% curPath % showType(e));
|
||||
|
||||
if (attrIndex >= v.list.length)
|
||||
throw Error(format("list index %1% in selection path `%2%' is out of range") % attrIndex % curPath);
|
||||
|
||||
v = *v.list.elems[attrIndex];
|
||||
e = ATelementAt(es, attrIndex);
|
||||
if (!e)
|
||||
throw Error(format("list index %1% in selection path `%2%' not found") % attrIndex % curPath);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#ifndef __ATTR_PATH_H
|
||||
#define __ATTR_PATH_H
|
||||
|
||||
#include "eval.hh"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "eval.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
void findAlongAttrPath(EvalState & state, const string & attrPath,
|
||||
Bindings & autoArgs, Expr * e, Value & v);
|
||||
Expr findAlongAttrPath(EvalState & state, const string & attrPath,
|
||||
const ATermMap & autoArgs, Expr e);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace nix {
|
||||
|
||||
bool parseOptionArg(const string & arg, Strings::iterator & i,
|
||||
const Strings::iterator & argsEnd, EvalState & state,
|
||||
Bindings & autoArgs)
|
||||
ATermMap & autoArgs)
|
||||
{
|
||||
if (arg != "--arg" && arg != "--argstr") return false;
|
||||
|
||||
@@ -19,18 +19,12 @@ bool parseOptionArg(const string & arg, Strings::iterator & i,
|
||||
string name = *i++;
|
||||
if (i == argsEnd) throw error;
|
||||
string value = *i++;
|
||||
|
||||
/* !!! check for duplicates! */
|
||||
Value * v = state.allocValue();
|
||||
autoArgs.push_back(Attr(state.symbols.create(name), v));
|
||||
|
||||
if (arg == "--arg")
|
||||
state.mkThunk_(*v, parseExprFromString(state, value, absPath(".")));
|
||||
else
|
||||
mkString(*v, value);
|
||||
|
||||
autoArgs.sort(); // !!! inefficient
|
||||
|
||||
|
||||
Expr e = arg == "--arg"
|
||||
? parseExprFromString(state, value, absPath("."))
|
||||
: makeStr(value);
|
||||
autoArgs.set(toATerm(name), e);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace nix {
|
||||
/* Some common option parsing between nix-env and nix-instantiate. */
|
||||
bool parseOptionArg(const string & arg, Strings::iterator & i,
|
||||
const Strings::iterator & argsEnd, EvalState & state,
|
||||
Bindings & autoArgs);
|
||||
ATermMap & autoArgs);
|
||||
|
||||
}
|
||||
|
||||
|
||||
1736
src/libexpr/eval.cc
1736
src/libexpr/eval.cc
File diff suppressed because it is too large
Load Diff
@@ -1,193 +1,19 @@
|
||||
#ifndef __EVAL_H
|
||||
#define __EVAL_H
|
||||
|
||||
#include "nixexpr.hh"
|
||||
#include "symbol-table.hh"
|
||||
#include "hash.hh"
|
||||
|
||||
#include <map>
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
#include <gc/gc_allocator.h>
|
||||
#endif
|
||||
#include "aterm.hh"
|
||||
#include "nixexpr.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
class EvalState;
|
||||
struct Env;
|
||||
struct Value;
|
||||
struct Attr;
|
||||
|
||||
|
||||
/* Attribute sets are represented as a vector of attributes, sorted by
|
||||
symbol (i.e. pointer to the attribute name in the symbol table). */
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::vector<Attr, gc_allocator<Attr> > BindingsBase;
|
||||
#else
|
||||
typedef std::vector<Attr> BindingsBase;
|
||||
#endif
|
||||
|
||||
|
||||
class Bindings : public BindingsBase
|
||||
{
|
||||
public:
|
||||
iterator find(const Symbol & name);
|
||||
void sort();
|
||||
};
|
||||
|
||||
|
||||
typedef enum {
|
||||
tInt = 1,
|
||||
tBool,
|
||||
tString,
|
||||
tPath,
|
||||
tNull,
|
||||
tAttrs,
|
||||
tList,
|
||||
tThunk,
|
||||
tApp,
|
||||
tLambda,
|
||||
tBlackhole,
|
||||
tPrimOp,
|
||||
tPrimOpApp,
|
||||
} ValueType;
|
||||
|
||||
|
||||
typedef void (* PrimOpFun) (EvalState & state, Value * * args, Value & v);
|
||||
|
||||
|
||||
struct PrimOp
|
||||
{
|
||||
PrimOpFun fun;
|
||||
unsigned int arity;
|
||||
Symbol name;
|
||||
PrimOp(PrimOpFun fun, unsigned int arity, Symbol name)
|
||||
: fun(fun), arity(arity), name(name) { }
|
||||
};
|
||||
|
||||
|
||||
struct Value
|
||||
{
|
||||
ValueType type;
|
||||
union
|
||||
{
|
||||
int integer;
|
||||
bool boolean;
|
||||
|
||||
/* Strings in the evaluator carry a so-called `context' (the
|
||||
ATermList) which is a list of strings representing store
|
||||
paths. This is to allow users to write things like
|
||||
|
||||
"--with-freetype2-library=" + freetype + "/lib"
|
||||
|
||||
where `freetype' is a derivation (or a source to be copied
|
||||
to the store). If we just concatenated the strings without
|
||||
keeping track of the referenced store paths, then if the
|
||||
string is used as a derivation attribute, the derivation
|
||||
will not have the correct dependencies in its inputDrvs and
|
||||
inputSrcs.
|
||||
|
||||
The semantics of the context is as follows: when a string
|
||||
with context C is used as a derivation attribute, then the
|
||||
derivations in C will be added to the inputDrvs of the
|
||||
derivation, and the other store paths in C will be added to
|
||||
the inputSrcs of the derivations.
|
||||
|
||||
For canonicity, the store paths should be in sorted order. */
|
||||
struct {
|
||||
const char * s;
|
||||
const char * * context; // must be in sorted order
|
||||
} string;
|
||||
|
||||
const char * path;
|
||||
Bindings * attrs;
|
||||
struct {
|
||||
unsigned int length;
|
||||
Value * * elems;
|
||||
} list;
|
||||
struct {
|
||||
Env * env;
|
||||
Expr * expr;
|
||||
} thunk;
|
||||
struct {
|
||||
Value * left, * right;
|
||||
} app;
|
||||
struct {
|
||||
Env * env;
|
||||
ExprLambda * fun;
|
||||
} lambda;
|
||||
PrimOp * primOp;
|
||||
struct {
|
||||
Value * left, * right;
|
||||
} primOpApp;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
struct Env
|
||||
{
|
||||
Env * up;
|
||||
unsigned int prevWith; // nr of levels up to next `with' environment
|
||||
Value * values[0];
|
||||
};
|
||||
|
||||
|
||||
struct Attr
|
||||
{
|
||||
Symbol name;
|
||||
Value * value;
|
||||
Pos * pos;
|
||||
Attr(Symbol name, Value * value, Pos * pos = &noPos)
|
||||
: name(name), value(value), pos(pos) { };
|
||||
Attr() : pos(&noPos) { };
|
||||
bool operator < (const Attr & a) const
|
||||
{
|
||||
return name < a.name;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* After overwriting an app node, be sure to clear pointers in the
|
||||
Value to ensure that the target isn't kept alive unnecessarily. */
|
||||
static inline void clearValue(Value & v)
|
||||
{
|
||||
v.app.right = 0;
|
||||
}
|
||||
|
||||
|
||||
static inline void mkInt(Value & v, int n)
|
||||
{
|
||||
clearValue(v);
|
||||
v.type = tInt;
|
||||
v.integer = n;
|
||||
}
|
||||
|
||||
|
||||
static inline void mkBool(Value & v, bool b)
|
||||
{
|
||||
clearValue(v);
|
||||
v.type = tBool;
|
||||
v.boolean = b;
|
||||
}
|
||||
|
||||
|
||||
static inline void mkApp(Value & v, Value & left, Value & right)
|
||||
{
|
||||
v.type = tApp;
|
||||
v.app.left = &left;
|
||||
v.app.right = &right;
|
||||
}
|
||||
|
||||
|
||||
void mkString(Value & v, const char * s);
|
||||
void mkString(Value & v, const string & s, const PathSet & context = PathSet());
|
||||
void mkPath(Value & v, const char * s);
|
||||
|
||||
void copyContext(const Value & v, PathSet & context);
|
||||
|
||||
class Hash;
|
||||
|
||||
|
||||
typedef std::map<Path, PathSet> DrvRoots;
|
||||
typedef std::map<Path, Hash> DrvHashes;
|
||||
|
||||
/* Cache for calls to addToStore(); maps source paths to the store
|
||||
@@ -196,160 +22,73 @@ typedef std::map<Path, Path> SrcToStore;
|
||||
|
||||
struct EvalState;
|
||||
|
||||
|
||||
std::ostream & operator << (std::ostream & str, const Value & v);
|
||||
/* Note: using a ATermVector is safe here, since when we call a primop
|
||||
we also have an ATermList on the stack. */
|
||||
typedef Expr (* PrimOp) (EvalState &, const ATermVector & args);
|
||||
|
||||
|
||||
class EvalState
|
||||
struct EvalState
|
||||
{
|
||||
public:
|
||||
ATermMap normalForms;
|
||||
ATermMap primOps;
|
||||
DrvRoots drvRoots;
|
||||
DrvHashes drvHashes; /* normalised derivation hashes */
|
||||
|
||||
SymbolTable symbols;
|
||||
|
||||
const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName,
|
||||
sSystem, sOverrides;
|
||||
|
||||
private:
|
||||
SrcToStore srcToStore;
|
||||
|
||||
bool allowUnsafeEquality;
|
||||
unsigned int nrEvaluated;
|
||||
unsigned int nrCached;
|
||||
|
||||
std::map<Path, Expr *> parseTrees;
|
||||
|
||||
public:
|
||||
|
||||
EvalState();
|
||||
~EvalState();
|
||||
|
||||
/* Evaluate an expression read from the given file to normal
|
||||
form. */
|
||||
void evalFile(const Path & path, Value & v);
|
||||
|
||||
/* Evaluate an expression to normal form, storing the result in
|
||||
value `v'. */
|
||||
void eval(Expr * e, Value & v);
|
||||
void eval(Env & env, Expr * e, Value & v);
|
||||
|
||||
/* Evaluation the expression, then verify that it has the expected
|
||||
type. */
|
||||
bool evalBool(Env & env, Expr * e);
|
||||
void evalAttrs(Env & env, Expr * e, Value & v);
|
||||
|
||||
/* If `v' is a thunk, enter it and overwrite `v' with the result
|
||||
of the evaluation of the thunk. If `v' is a delayed function
|
||||
application, call the function and overwrite `v' with the
|
||||
result. Otherwise, this is a no-op. */
|
||||
void forceValue(Value & v);
|
||||
|
||||
/* Force a value, then recursively force list elements and
|
||||
attributes. */
|
||||
void strictForceValue(Value & v);
|
||||
|
||||
/* Force `v', and then verify that it has the expected type. */
|
||||
int forceInt(Value & v);
|
||||
bool forceBool(Value & v);
|
||||
void forceAttrs(Value & v);
|
||||
void forceList(Value & v);
|
||||
void forceFunction(Value & v); // either lambda or primop
|
||||
string forceString(Value & v);
|
||||
string forceString(Value & v, PathSet & context);
|
||||
string forceStringNoCtx(Value & v);
|
||||
|
||||
/* Return true iff the value `v' denotes a derivation (i.e. a
|
||||
set with attribute `type = "derivation"'). */
|
||||
bool isDerivation(Value & v);
|
||||
|
||||
/* String coercion. Converts strings, paths and derivations to a
|
||||
string. If `coerceMore' is set, also converts nulls, integers,
|
||||
booleans and lists to a string. If `copyToStore' is set,
|
||||
referenced paths are copied to the Nix store as a side effect.q */
|
||||
string coerceToString(Value & v, PathSet & context,
|
||||
bool coerceMore = false, bool copyToStore = true);
|
||||
|
||||
/* Path coercion. Converts strings, paths and derivations to a
|
||||
path. The result is guaranteed to be a canonicalised, absolute
|
||||
path. Nothing is copied to the store. */
|
||||
Path coerceToPath(Value & v, PathSet & context);
|
||||
|
||||
private:
|
||||
|
||||
/* The base environment, containing the builtin functions and
|
||||
values. */
|
||||
Env & baseEnv;
|
||||
|
||||
unsigned int baseEnvDispl;
|
||||
|
||||
public:
|
||||
|
||||
/* The same, but used during parsing to resolve variables. */
|
||||
StaticEnv staticBaseEnv; // !!! should be private
|
||||
|
||||
private:
|
||||
|
||||
void createBaseEnv();
|
||||
|
||||
void addConstant(const string & name, Value & v);
|
||||
|
||||
void addPrimOps();
|
||||
void addPrimOp(const string & name,
|
||||
unsigned int arity, PrimOpFun primOp);
|
||||
|
||||
Value * lookupVar(Env * env, const VarRef & var);
|
||||
|
||||
friend class ExprVar;
|
||||
friend class ExprAttrs;
|
||||
friend class ExprLet;
|
||||
|
||||
public:
|
||||
|
||||
/* Do a deep equality test between two values. That is, list
|
||||
elements and attributes are compared recursively. */
|
||||
bool eqValues(Value & v1, Value & v2);
|
||||
|
||||
void callFunction(Value & fun, Value & arg, Value & v);
|
||||
|
||||
/* Automatically call a function for which each argument has a
|
||||
default value or has a binding in the `args' map. */
|
||||
void autoCallFunction(Bindings & args, Value & fun, Value & res);
|
||||
|
||||
/* Allocation primitives. */
|
||||
Value * allocValue();
|
||||
Env & allocEnv(unsigned int size);
|
||||
|
||||
Value * allocAttr(Value & vAttrs, const Symbol & name);
|
||||
|
||||
void mkList(Value & v, unsigned int length);
|
||||
void mkAttrs(Value & v, unsigned int expected);
|
||||
void mkThunk_(Value & v, Expr * expr);
|
||||
|
||||
Value * maybeThunk(Env & env, Expr * expr);
|
||||
|
||||
/* Print statistics. */
|
||||
void printStats();
|
||||
|
||||
private:
|
||||
|
||||
unsigned long nrEnvs;
|
||||
unsigned long nrValuesInEnvs;
|
||||
unsigned long nrValues;
|
||||
unsigned long nrListElems;
|
||||
unsigned long nrEvaluated;
|
||||
unsigned long nrAttrsets;
|
||||
unsigned long nrOpUpdates;
|
||||
unsigned long nrOpUpdateValuesCopied;
|
||||
unsigned int recursionDepth;
|
||||
unsigned int maxRecursionDepth;
|
||||
char * deepestStack; /* for measuring stack usage */
|
||||
|
||||
friend class RecursionCounter;
|
||||
friend class ExprOpUpdate;
|
||||
unsigned int arity, PrimOp primOp);
|
||||
};
|
||||
|
||||
|
||||
/* Return a string representing the type of the value `v'. */
|
||||
string showType(const Value & v);
|
||||
/* Evaluate an expression to normal form. */
|
||||
Expr evalExpr(EvalState & state, Expr e);
|
||||
|
||||
/* Evaluate an expression read from the given file to normal form. */
|
||||
Expr evalFile(EvalState & state, const Path & path);
|
||||
|
||||
/* Evaluate an expression, and recursively evaluate list elements and
|
||||
attributes. If `canonicalise' is true, we remove things like
|
||||
position information and make sure that attribute sets are in
|
||||
sorded order. */
|
||||
Expr strictEvalExpr(EvalState & state, Expr e);
|
||||
|
||||
/* Specific results. */
|
||||
string evalString(EvalState & state, Expr e, PathSet & context);
|
||||
string evalStringNoCtx(EvalState & state, Expr e);
|
||||
int evalInt(EvalState & state, Expr e);
|
||||
bool evalBool(EvalState & state, Expr e);
|
||||
ATermList evalList(EvalState & state, Expr e);
|
||||
|
||||
/* Flatten nested lists into a single list (or expand a singleton into
|
||||
a list). */
|
||||
ATermList flattenList(EvalState & state, Expr e);
|
||||
|
||||
/* String coercion. Converts strings, paths and derivations to a
|
||||
string. If `coerceMore' is set, also converts nulls, integers,
|
||||
booleans and lists to a string. */
|
||||
string coerceToString(EvalState & state, Expr e, PathSet & context,
|
||||
bool coerceMore = false, bool copyToStore = true);
|
||||
|
||||
/* Path coercion. Converts strings, paths and derivations to a path.
|
||||
The result is guaranteed to be an canonicalised, absolute path.
|
||||
Nothing is copied to the store. */
|
||||
Path coerceToPath(EvalState & state, Expr e, PathSet & context);
|
||||
|
||||
/* Automatically call a function for which each argument has a default
|
||||
value or has a binding in the `args' map. Note: result is a call,
|
||||
not a normal form; it should be evaluated by calling evalExpr(). */
|
||||
Expr autoCallFunction(Expr e, const ATermMap & args);
|
||||
|
||||
/* Print statistics. */
|
||||
void printEvalStats(EvalState & state);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
156
src/libexpr/expr-to-xml.cc
Normal file
156
src/libexpr/expr-to-xml.cc
Normal file
@@ -0,0 +1,156 @@
|
||||
#include "expr-to-xml.hh"
|
||||
#include "xml-writer.hh"
|
||||
#include "nixexpr-ast.hh"
|
||||
#include "aterm.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
static XMLAttrs singletonAttrs(const string & name, const string & value)
|
||||
{
|
||||
XMLAttrs attrs;
|
||||
attrs[name] = value;
|
||||
return attrs;
|
||||
}
|
||||
|
||||
|
||||
/* set<Expr> is safe because all the expressions are also reachable
|
||||
from the stack, therefore can't be garbage-collected. */
|
||||
typedef set<Expr> ExprSet;
|
||||
|
||||
|
||||
static void printTermAsXML(Expr e, XMLWriter & doc, PathSet & context,
|
||||
ExprSet & drvsSeen);
|
||||
|
||||
|
||||
static void showAttrs(const ATermMap & attrs, XMLWriter & doc,
|
||||
PathSet & context, ExprSet & drvsSeen)
|
||||
{
|
||||
StringSet names;
|
||||
for (ATermMap::const_iterator i = attrs.begin(); i != attrs.end(); ++i)
|
||||
names.insert(aterm2String(i->key));
|
||||
for (StringSet::iterator i = names.begin(); i != names.end(); ++i) {
|
||||
XMLOpenElement _(doc, "attr", singletonAttrs("name", *i));
|
||||
printTermAsXML(attrs.get(toATerm(*i)), doc, context, drvsSeen);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void printPatternAsXML(Pattern pat, XMLWriter & doc)
|
||||
{
|
||||
ATerm name;
|
||||
ATermList formals;
|
||||
Pattern pat1, pat2;
|
||||
ATermBool ellipsis;
|
||||
if (matchVarPat(pat, name))
|
||||
doc.writeEmptyElement("varpat", singletonAttrs("name", aterm2String(name)));
|
||||
else if (matchAttrsPat(pat, formals, ellipsis)) {
|
||||
XMLOpenElement _(doc, "attrspat");
|
||||
for (ATermIterator i(formals); i; ++i) {
|
||||
Expr name; ATerm dummy;
|
||||
if (!matchFormal(*i, name, dummy)) abort();
|
||||
doc.writeEmptyElement("attr", singletonAttrs("name", aterm2String(name)));
|
||||
}
|
||||
if (ellipsis == eTrue) doc.writeEmptyElement("ellipsis");
|
||||
}
|
||||
else if (matchAtPat(pat, pat1, pat2)) {
|
||||
XMLOpenElement _(doc, "at");
|
||||
printPatternAsXML(pat1, doc);
|
||||
printPatternAsXML(pat2, doc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void printTermAsXML(Expr e, XMLWriter & doc, PathSet & context,
|
||||
ExprSet & drvsSeen)
|
||||
{
|
||||
XMLAttrs attrs;
|
||||
string s;
|
||||
ATerm s2;
|
||||
int i;
|
||||
ATermList as, es;
|
||||
ATerm pat, body, pos;
|
||||
|
||||
checkInterrupt();
|
||||
|
||||
if (matchStr(e, s, context)) /* !!! show the context? */
|
||||
doc.writeEmptyElement("string", singletonAttrs("value", s));
|
||||
|
||||
else if (matchPath(e, s2))
|
||||
doc.writeEmptyElement("path", singletonAttrs("value", aterm2String(s2)));
|
||||
|
||||
else if (matchNull(e))
|
||||
doc.writeEmptyElement("null");
|
||||
|
||||
else if (matchInt(e, i))
|
||||
doc.writeEmptyElement("int", singletonAttrs("value", (format("%1%") % i).str()));
|
||||
|
||||
else if (e == eTrue)
|
||||
doc.writeEmptyElement("bool", singletonAttrs("value", "true"));
|
||||
|
||||
else if (e == eFalse)
|
||||
doc.writeEmptyElement("bool", singletonAttrs("value", "false"));
|
||||
|
||||
else if (matchAttrs(e, as)) {
|
||||
ATermMap attrs;
|
||||
queryAllAttrs(e, attrs);
|
||||
|
||||
Expr a = attrs.get(toATerm("type"));
|
||||
if (a && matchStr(a, s, context) && s == "derivation") {
|
||||
|
||||
XMLAttrs xmlAttrs;
|
||||
Path outPath, drvPath;
|
||||
|
||||
a = attrs.get(toATerm("drvPath"));
|
||||
if (matchStr(a, drvPath, context))
|
||||
xmlAttrs["drvPath"] = drvPath;
|
||||
|
||||
a = attrs.get(toATerm("outPath"));
|
||||
if (matchStr(a, outPath, context))
|
||||
xmlAttrs["outPath"] = outPath;
|
||||
|
||||
XMLOpenElement _(doc, "derivation", xmlAttrs);
|
||||
|
||||
if (drvsSeen.find(e) == drvsSeen.end()) {
|
||||
drvsSeen.insert(e);
|
||||
showAttrs(attrs, doc, context, drvsSeen);
|
||||
} else
|
||||
doc.writeEmptyElement("repeated");
|
||||
}
|
||||
|
||||
else {
|
||||
XMLOpenElement _(doc, "attrs");
|
||||
showAttrs(attrs, doc, context, drvsSeen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (matchList(e, es)) {
|
||||
XMLOpenElement _(doc, "list");
|
||||
for (ATermIterator i(es); i; ++i)
|
||||
printTermAsXML(*i, doc, context, drvsSeen);
|
||||
}
|
||||
|
||||
else if (matchFunction(e, pat, body, pos)) {
|
||||
XMLOpenElement _(doc, "function");
|
||||
printPatternAsXML(pat, doc);
|
||||
}
|
||||
|
||||
else
|
||||
doc.writeEmptyElement("unevaluated");
|
||||
}
|
||||
|
||||
|
||||
void printTermAsXML(Expr e, std::ostream & out, PathSet & context)
|
||||
{
|
||||
XMLWriter doc(true, out);
|
||||
XMLOpenElement root(doc, "expr");
|
||||
ExprSet drvsSeen;
|
||||
printTermAsXML(e, doc, context, drvsSeen);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
16
src/libexpr/expr-to-xml.hh
Normal file
16
src/libexpr/expr-to-xml.hh
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef __EXPR_TO_XML_H
|
||||
#define __EXPR_TO_XML_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "nixexpr.hh"
|
||||
#include "aterm.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
void printTermAsXML(Expr e, std::ostream & out, PathSet & context);
|
||||
|
||||
}
|
||||
|
||||
#endif /* !__EXPR_TO_XML_H */
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "get-drvs.hh"
|
||||
#include "nixexpr-ast.hh"
|
||||
#include "util.hh"
|
||||
|
||||
|
||||
@@ -7,10 +8,17 @@ namespace nix {
|
||||
|
||||
string DrvInfo::queryDrvPath(EvalState & state) const
|
||||
{
|
||||
if (drvPath == "" && attrs) {
|
||||
Bindings::iterator i = attrs->find(state.sDrvPath);
|
||||
if (drvPath == "") {
|
||||
Expr a = attrs->get(toATerm("drvPath"));
|
||||
|
||||
/* Backwards compatibility hack with user environments made by
|
||||
Nix <= 0.10: these contain illegal Path("") expressions. */
|
||||
ATerm t;
|
||||
if (a && matchPath(evalExpr(state, a), t))
|
||||
return aterm2String(t);
|
||||
|
||||
PathSet context;
|
||||
(string &) drvPath = i != attrs->end() ? state.coerceToPath(*i->value, context) : "";
|
||||
(string &) drvPath = a ? coerceToPath(state, a, context) : "";
|
||||
}
|
||||
return drvPath;
|
||||
}
|
||||
@@ -18,10 +26,11 @@ string DrvInfo::queryDrvPath(EvalState & state) const
|
||||
|
||||
string DrvInfo::queryOutPath(EvalState & state) const
|
||||
{
|
||||
if (outPath == "" && attrs) {
|
||||
Bindings::iterator i = attrs->find(state.sOutPath);
|
||||
if (outPath == "") {
|
||||
Expr a = attrs->get(toATerm("outPath"));
|
||||
if (!a) throw TypeError("output path missing");
|
||||
PathSet context;
|
||||
(string &) outPath = i != attrs->end() ? state.coerceToPath(*i->value, context) : "";
|
||||
(string &) outPath = coerceToPath(state, a, context);
|
||||
}
|
||||
return outPath;
|
||||
}
|
||||
@@ -29,85 +38,93 @@ string DrvInfo::queryOutPath(EvalState & state) const
|
||||
|
||||
MetaInfo DrvInfo::queryMetaInfo(EvalState & state) const
|
||||
{
|
||||
if (metaInfoRead) return meta;
|
||||
MetaInfo meta;
|
||||
|
||||
(bool &) metaInfoRead = true;
|
||||
|
||||
Bindings::iterator a = attrs->find(state.sMeta);
|
||||
if (a == attrs->end()) return meta; /* fine, empty meta information */
|
||||
Expr a = attrs->get(toATerm("meta"));
|
||||
if (!a) return meta; /* fine, empty meta information */
|
||||
|
||||
state.forceAttrs(*a->value);
|
||||
ATermMap attrs2;
|
||||
queryAllAttrs(evalExpr(state, a), attrs2);
|
||||
|
||||
foreach (Bindings::iterator, i, *a->value->attrs) {
|
||||
MetaValue value;
|
||||
state.forceValue(*i->value);
|
||||
if (i->value->type == tString) {
|
||||
value.type = MetaValue::tpString;
|
||||
value.stringValue = i->value->string.s;
|
||||
} else if (i->value->type == tInt) {
|
||||
value.type = MetaValue::tpInt;
|
||||
value.intValue = i->value->integer;
|
||||
} else if (i->value->type == tList) {
|
||||
value.type = MetaValue::tpStrings;
|
||||
for (unsigned int j = 0; j < i->value->list.length; ++j)
|
||||
value.stringValues.push_back(state.forceStringNoCtx(*i->value->list.elems[j]));
|
||||
} else continue;
|
||||
((MetaInfo &) meta)[i->name] = value;
|
||||
for (ATermMap::const_iterator i = attrs2.begin(); i != attrs2.end(); ++i) {
|
||||
Expr e = evalExpr(state, i->value);
|
||||
string s;
|
||||
PathSet context;
|
||||
if (matchStr(e, s, context))
|
||||
meta[aterm2String(i->key)] = s;
|
||||
/* For future compatibility, ignore attribute values that are
|
||||
not strings. */
|
||||
}
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
|
||||
MetaValue DrvInfo::queryMetaInfo(EvalState & state, const string & name) const
|
||||
string DrvInfo::queryMetaInfo(EvalState & state, const string & name) const
|
||||
{
|
||||
/* !!! evaluates all meta attributes => inefficient */
|
||||
return queryMetaInfo(state)[name];
|
||||
MetaInfo meta = queryMetaInfo(state);
|
||||
MetaInfo::iterator i = meta.find(name);
|
||||
return i == meta.end() ? "" : i->second;
|
||||
}
|
||||
|
||||
|
||||
void DrvInfo::setMetaInfo(const MetaInfo & meta)
|
||||
{
|
||||
metaInfoRead = true;
|
||||
this->meta = meta;
|
||||
ATermMap metaAttrs;
|
||||
for (MetaInfo::const_iterator i = meta.begin(); i != meta.end(); ++i)
|
||||
metaAttrs.set(toATerm(i->first),
|
||||
makeAttrRHS(makeStr(i->second), makeNoPos()));
|
||||
attrs->set(toATerm("meta"), makeAttrs(metaAttrs));
|
||||
}
|
||||
|
||||
|
||||
/* Cache for already considered attrsets. */
|
||||
typedef set<Bindings *> Done;
|
||||
/* Cache for already evaluated derivations. Usually putting ATerms in
|
||||
a STL container is unsafe (they're not scanning for GC roots), but
|
||||
here it doesn't matter; everything in this set is reachable from
|
||||
the stack as well. */
|
||||
typedef set<Expr> Exprs;
|
||||
|
||||
|
||||
/* Evaluate value `v'. If it evaluates to an attribute set of type
|
||||
`derivation', then put information about it in `drvs' (unless it's
|
||||
already in `doneExprs'). The result boolean indicates whether it
|
||||
makes sense for the caller to recursively search for derivations in
|
||||
`v'. */
|
||||
static bool getDerivation(EvalState & state, Value & v,
|
||||
const string & attrPath, DrvInfos & drvs, Done & done)
|
||||
/* Evaluate expression `e'. If it evaluates to an attribute set of
|
||||
type `derivation', then put information about it in `drvs' (unless
|
||||
it's already in `doneExprs'). The result boolean indicates whether
|
||||
it makes sense for the caller to recursively search for derivations
|
||||
in `e'. */
|
||||
static bool getDerivation(EvalState & state, Expr e,
|
||||
const string & attrPath, DrvInfos & drvs, Exprs & doneExprs)
|
||||
{
|
||||
try {
|
||||
state.forceValue(v);
|
||||
if (!state.isDerivation(v)) return true;
|
||||
|
||||
ATermList es;
|
||||
e = evalExpr(state, e);
|
||||
if (!matchAttrs(e, es)) return true;
|
||||
|
||||
boost::shared_ptr<ATermMap> attrs(new ATermMap());
|
||||
queryAllAttrs(e, *attrs, false);
|
||||
|
||||
Expr a = attrs->get(toATerm("type"));
|
||||
if (!a || evalStringNoCtx(state, a) != "derivation") return true;
|
||||
|
||||
/* Remove spurious duplicates (e.g., an attribute set like
|
||||
`rec { x = derivation {...}; y = x;}'. */
|
||||
if (done.find(v.attrs) != done.end()) return false;
|
||||
done.insert(v.attrs);
|
||||
if (doneExprs.find(e) != doneExprs.end()) return false;
|
||||
doneExprs.insert(e);
|
||||
|
||||
DrvInfo drv;
|
||||
|
||||
Bindings::iterator i = v.attrs->find(state.sName);
|
||||
a = attrs->get(toATerm("name"));
|
||||
/* !!! We really would like to have a decent back trace here. */
|
||||
if (i == v.attrs->end()) throw TypeError("derivation name missing");
|
||||
drv.name = state.forceStringNoCtx(*i->value);
|
||||
if (!a) throw TypeError("derivation name missing");
|
||||
drv.name = evalStringNoCtx(state, a);
|
||||
|
||||
Bindings::iterator i2 = v.attrs->find(state.sSystem);
|
||||
if (i2 == v.attrs->end())
|
||||
a = attrs->get(toATerm("system"));
|
||||
if (!a)
|
||||
drv.system = "unknown";
|
||||
else
|
||||
drv.system = state.forceStringNoCtx(*i2->value);
|
||||
drv.system = evalStringNoCtx(state, a);
|
||||
|
||||
drv.attrs = v.attrs;
|
||||
drv.attrs = attrs;
|
||||
|
||||
drv.attrPath = attrPath;
|
||||
|
||||
@@ -120,11 +137,11 @@ static bool getDerivation(EvalState & state, Value & v,
|
||||
}
|
||||
|
||||
|
||||
bool getDerivation(EvalState & state, Value & v, DrvInfo & drv)
|
||||
bool getDerivation(EvalState & state, Expr e, DrvInfo & drv)
|
||||
{
|
||||
Done done;
|
||||
Exprs doneExprs;
|
||||
DrvInfos drvs;
|
||||
getDerivation(state, v, "", drvs, done);
|
||||
getDerivation(state, e, "", drvs, doneExprs);
|
||||
if (drvs.size() != 1) return false;
|
||||
drv = drvs.front();
|
||||
return true;
|
||||
@@ -137,73 +154,83 @@ static string addToPath(const string & s1, const string & s2)
|
||||
}
|
||||
|
||||
|
||||
static void getDerivations(EvalState & state, Value & vIn,
|
||||
const string & pathPrefix, Bindings & autoArgs,
|
||||
DrvInfos & drvs, Done & done)
|
||||
static void getDerivations(EvalState & state, Expr e,
|
||||
const string & pathPrefix, const ATermMap & autoArgs,
|
||||
DrvInfos & drvs, Exprs & doneExprs)
|
||||
{
|
||||
Value v;
|
||||
state.autoCallFunction(autoArgs, vIn, v);
|
||||
|
||||
e = evalExpr(state, autoCallFunction(evalExpr(state, e), autoArgs));
|
||||
|
||||
/* Process the expression. */
|
||||
ATermList es;
|
||||
DrvInfo drv;
|
||||
|
||||
if (!getDerivation(state, v, pathPrefix, drvs, done)) ;
|
||||
if (!getDerivation(state, e, pathPrefix, drvs, doneExprs))
|
||||
return;
|
||||
|
||||
else if (v.type == tAttrs) {
|
||||
if (matchAttrs(e, es)) {
|
||||
ATermMap drvMap(ATgetLength(es));
|
||||
queryAllAttrs(e, drvMap);
|
||||
|
||||
/* !!! undocumented hackery to support combining channels in
|
||||
nix-env.cc. */
|
||||
bool combineChannels = v.attrs->find(state.symbols.create("_combineChannels")) != v.attrs->end();
|
||||
bool combineChannels = drvMap.get(toATerm("_combineChannels"));
|
||||
|
||||
/* Consider the attributes in sorted order to get more
|
||||
deterministic behaviour in nix-env operations (e.g. when
|
||||
there are names clashes between derivations, the derivation
|
||||
bound to the attribute with the "lower" name should take
|
||||
precedence). */
|
||||
typedef std::map<string, Symbol> SortedSymbols;
|
||||
SortedSymbols attrs;
|
||||
foreach (Bindings::iterator, i, *v.attrs)
|
||||
attrs.insert(std::pair<string, Symbol>(i->name, i->name));
|
||||
typedef std::map<string, Expr> AttrsSorted;
|
||||
AttrsSorted attrsSorted;
|
||||
foreach (ATermMap::const_iterator, i, drvMap)
|
||||
attrsSorted[aterm2String(i->key)] = i->value;
|
||||
|
||||
foreach (SortedSymbols::iterator, i, attrs) {
|
||||
foreach (AttrsSorted::iterator, i, attrsSorted) {
|
||||
startNest(nest, lvlDebug, format("evaluating attribute `%1%'") % i->first);
|
||||
string pathPrefix2 = addToPath(pathPrefix, i->first);
|
||||
Value & v2(*v.attrs->find(i->second)->value);
|
||||
if (combineChannels)
|
||||
getDerivations(state, v2, pathPrefix2, autoArgs, drvs, done);
|
||||
else if (getDerivation(state, v2, pathPrefix2, drvs, done)) {
|
||||
getDerivations(state, i->second, pathPrefix2, autoArgs, drvs, doneExprs);
|
||||
else if (getDerivation(state, i->second, pathPrefix2, drvs, doneExprs)) {
|
||||
/* If the value of this attribute is itself an
|
||||
attribute set, should we recurse into it? => Only
|
||||
if it has a `recurseForDerivations = true'
|
||||
attribute. */
|
||||
if (v2.type == tAttrs) {
|
||||
Bindings::iterator j = v2.attrs->find(state.symbols.create("recurseForDerivations"));
|
||||
if (j != v2.attrs->end() && state.forceBool(*j->value))
|
||||
getDerivations(state, v2, pathPrefix2, autoArgs, drvs, done);
|
||||
ATermList es;
|
||||
Expr e = evalExpr(state, i->second), e2;
|
||||
if (matchAttrs(e, es)) {
|
||||
ATermMap attrs(ATgetLength(es));
|
||||
queryAllAttrs(e, attrs, false);
|
||||
if (((e2 = attrs.get(toATerm("recurseForDerivations")))
|
||||
&& evalBool(state, e2)))
|
||||
getDerivations(state, e, pathPrefix2, autoArgs, drvs, doneExprs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
else if (v.type == tList) {
|
||||
for (unsigned int n = 0; n < v.list.length; ++n) {
|
||||
if (matchList(e, es)) {
|
||||
int n = 0;
|
||||
for (ATermIterator i(es); i; ++i, ++n) {
|
||||
startNest(nest, lvlDebug,
|
||||
format("evaluating list element"));
|
||||
string pathPrefix2 = addToPath(pathPrefix, (format("%1%") % n).str());
|
||||
if (getDerivation(state, *v.list.elems[n], pathPrefix2, drvs, done))
|
||||
getDerivations(state, *v.list.elems[n], pathPrefix2, autoArgs, drvs, done);
|
||||
if (getDerivation(state, *i, pathPrefix2, drvs, doneExprs))
|
||||
getDerivations(state, *i, pathPrefix2, autoArgs, drvs, doneExprs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
else throw TypeError("expression does not evaluate to a derivation (or a set or list of those)");
|
||||
throw TypeError("expression does not evaluate to a derivation (or a set or list of those)");
|
||||
}
|
||||
|
||||
|
||||
void getDerivations(EvalState & state, Value & v, const string & pathPrefix,
|
||||
Bindings & autoArgs, DrvInfos & drvs)
|
||||
void getDerivations(EvalState & state, Expr e, const string & pathPrefix,
|
||||
const ATermMap & autoArgs, DrvInfos & drvs)
|
||||
{
|
||||
Done done;
|
||||
getDerivations(state, v, pathPrefix, autoArgs, drvs, done);
|
||||
Exprs doneExprs;
|
||||
getDerivations(state, e, pathPrefix, autoArgs, drvs, doneExprs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
#ifndef __GET_DRVS_H
|
||||
#define __GET_DRVS_H
|
||||
|
||||
#include "eval.hh"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include "eval.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
struct MetaValue
|
||||
{
|
||||
enum { tpNone, tpString, tpStrings, tpInt } type;
|
||||
string stringValue;
|
||||
Strings stringValues;
|
||||
int intValue;
|
||||
};
|
||||
|
||||
|
||||
typedef std::map<string, MetaValue> MetaInfo;
|
||||
typedef std::map<string, string> MetaInfo;
|
||||
|
||||
|
||||
struct DrvInfo
|
||||
@@ -29,24 +20,21 @@ struct DrvInfo
|
||||
private:
|
||||
string drvPath;
|
||||
string outPath;
|
||||
|
||||
bool metaInfoRead;
|
||||
MetaInfo meta;
|
||||
|
||||
public:
|
||||
string name;
|
||||
string attrPath; /* path towards the derivation */
|
||||
string system;
|
||||
|
||||
/* !!! make this private */
|
||||
Bindings * attrs;
|
||||
|
||||
DrvInfo() : metaInfoRead(false), attrs(0) { };
|
||||
/* !!! these should really be hidden, and setMetaInfo() should
|
||||
make a copy since the ATermMap can be shared between multiple
|
||||
DrvInfos. */
|
||||
boost::shared_ptr<ATermMap> attrs;
|
||||
|
||||
string queryDrvPath(EvalState & state) const;
|
||||
string queryOutPath(EvalState & state) const;
|
||||
MetaInfo queryMetaInfo(EvalState & state) const;
|
||||
MetaValue queryMetaInfo(EvalState & state, const string & name) const;
|
||||
string queryMetaInfo(EvalState & state, const string & name) const;
|
||||
|
||||
void setDrvPath(const string & s)
|
||||
{
|
||||
@@ -62,19 +50,16 @@ public:
|
||||
};
|
||||
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
typedef list<DrvInfo, traceable_allocator<DrvInfo> > DrvInfos;
|
||||
#else
|
||||
typedef list<DrvInfo> DrvInfos;
|
||||
#endif
|
||||
|
||||
|
||||
/* If value `v' denotes a derivation, store information about the
|
||||
derivation in `drv' and return true. Otherwise, return false. */
|
||||
bool getDerivation(EvalState & state, Value & v, DrvInfo & drv);
|
||||
/* Evaluate expression `e'. If it evaluates to a derivation, store
|
||||
information about the derivation in `drv' and return true.
|
||||
Otherwise, return false. */
|
||||
bool getDerivation(EvalState & state, Expr e, DrvInfo & drv);
|
||||
|
||||
void getDerivations(EvalState & state, Value & v, const string & pathPrefix,
|
||||
Bindings & autoArgs, DrvInfos & drvs);
|
||||
void getDerivations(EvalState & state, Expr e, const string & pathPrefix,
|
||||
const ATermMap & autoArgs, DrvInfos & drvs);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
|
||||
%{
|
||||
#include "aterm.hh"
|
||||
#include "nixexpr.hh"
|
||||
#include "nixexpr-ast.hh"
|
||||
#define BISON_HEADER_HACK
|
||||
#include "parser-tab.hh"
|
||||
|
||||
@@ -19,16 +21,13 @@ namespace nix {
|
||||
|
||||
static void initLoc(YYLTYPE * loc)
|
||||
{
|
||||
loc->first_line = loc->last_line = 1;
|
||||
loc->first_column = loc->last_column = 1;
|
||||
loc->first_line = 1;
|
||||
loc->first_column = 1;
|
||||
}
|
||||
|
||||
|
||||
static void adjustLoc(YYLTYPE * loc, const char * s, size_t len)
|
||||
{
|
||||
loc->first_line = loc->last_line;
|
||||
loc->first_column = loc->last_column;
|
||||
|
||||
while (len--) {
|
||||
switch (*s++) {
|
||||
case '\r':
|
||||
@@ -36,17 +35,17 @@ static void adjustLoc(YYLTYPE * loc, const char * s, size_t len)
|
||||
s++;
|
||||
/* fall through */
|
||||
case '\n':
|
||||
++loc->last_line;
|
||||
loc->last_column = 1;
|
||||
++loc->first_line;
|
||||
loc->first_column = 1;
|
||||
break;
|
||||
default:
|
||||
++loc->last_column;
|
||||
++loc->first_column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static Expr * unescapeStr(SymbolTable & symbols, const char * s)
|
||||
static Expr unescapeStr(const char * s)
|
||||
{
|
||||
string t;
|
||||
char c;
|
||||
@@ -66,7 +65,7 @@ static Expr * unescapeStr(SymbolTable & symbols, const char * s)
|
||||
}
|
||||
else t += c;
|
||||
}
|
||||
return new ExprString(symbols.create(t));
|
||||
return makeStr(toATerm(t), ATempty);
|
||||
}
|
||||
|
||||
|
||||
@@ -106,20 +105,19 @@ inherit { return INHERIT; }
|
||||
\/\/ { return UPDATE; }
|
||||
\+\+ { return CONCAT; }
|
||||
|
||||
{ID} { yylval->id = strdup(yytext); return ID; }
|
||||
{ID} { yylval->t = toATerm(yytext); return ID; /* !!! alloc */ }
|
||||
{INT} { int n = atoi(yytext); /* !!! overflow */
|
||||
yylval->n = n;
|
||||
yylval->t = ATmake("<int>", n);
|
||||
return INT;
|
||||
}
|
||||
|
||||
\" { BEGIN(STRING); return '"'; }
|
||||
<STRING>([^\$\"\\]|\$[^\{\"]|\\.)+ {
|
||||
/* !!! Not quite right: we want a follow restriction on
|
||||
"$", it shouldn't be followed by a "{". Right now
|
||||
"$\"" will be consumed as part of a string, rather
|
||||
than a "$" followed by the string terminator.
|
||||
Disallow "$\"" for now. */
|
||||
yylval->e = unescapeStr(data->symbols, yytext);
|
||||
/* !!! Not quite right: we want a follow restriction on "$", it
|
||||
shouldn't be followed by a "{". Right now "$\"" will be consumed
|
||||
as part of a string, rather than a "$" followed by the string
|
||||
terminator. Disallow "$\"" for now. */
|
||||
yylval->t = unescapeStr(yytext); /* !!! alloc */
|
||||
return STR;
|
||||
}
|
||||
<STRING>\$\{ { BEGIN(INITIAL); return DOLLAR_CURLY; }
|
||||
@@ -128,31 +126,31 @@ inherit { return INHERIT; }
|
||||
|
||||
\'\'(\ *\n)? { BEGIN(IND_STRING); return IND_STRING_OPEN; }
|
||||
<IND_STRING>([^\$\']|\$[^\{\']|\'[^\'\$])+ {
|
||||
yylval->e = new ExprIndStr(yytext);
|
||||
yylval->t = makeIndStr(toATerm(yytext));
|
||||
return IND_STR;
|
||||
}
|
||||
<IND_STRING>\'\'\$ {
|
||||
yylval->e = new ExprIndStr("$");
|
||||
yylval->t = makeIndStr(toATerm("$"));
|
||||
return IND_STR;
|
||||
}
|
||||
<IND_STRING>\'\'\' {
|
||||
yylval->e = new ExprIndStr("''");
|
||||
yylval->t = makeIndStr(toATerm("''"));
|
||||
return IND_STR;
|
||||
}
|
||||
<IND_STRING>\'\'\\. {
|
||||
yylval->e = unescapeStr(data->symbols, yytext + 2);
|
||||
yylval->t = unescapeStr(yytext + 2);
|
||||
return IND_STR;
|
||||
}
|
||||
<IND_STRING>\$\{ { BEGIN(INITIAL); return DOLLAR_CURLY; }
|
||||
<IND_STRING>\'\' { BEGIN(INITIAL); return IND_STRING_CLOSE; }
|
||||
<IND_STRING>\' {
|
||||
yylval->e = new ExprIndStr("'");
|
||||
yylval->t = makeIndStr(toATerm("'"));
|
||||
return IND_STR;
|
||||
}
|
||||
<IND_STRING>. return yytext[0]; /* just in case: shouldn't be reached */
|
||||
|
||||
{PATH} { yylval->path = strdup(yytext); return PATH; }
|
||||
{URI} { yylval->uri = strdup(yytext); return URI; }
|
||||
{PATH} { yylval->t = toATerm(yytext); return PATH; /* !!! alloc */ }
|
||||
{URI} { yylval->t = toATerm(yytext); return URI; /* !!! alloc */ }
|
||||
|
||||
[ \t\r\n]+ /* eat up whitespace */
|
||||
\#[^\r\n]* /* single-line comments */
|
||||
|
||||
96
src/libexpr/nixexpr-ast.def
Normal file
96
src/libexpr/nixexpr-ast.def
Normal file
@@ -0,0 +1,96 @@
|
||||
init initNixExprHelpers
|
||||
|
||||
Pos | string int int | Pos |
|
||||
NoPos | | Pos |
|
||||
|
||||
Function | Pattern Expr Pos | Expr |
|
||||
Assert | Expr Expr Pos | Expr |
|
||||
With | Expr Expr Pos | Expr |
|
||||
If | Expr Expr Expr | Expr |
|
||||
OpNot | Expr | Expr |
|
||||
OpEq | Expr Expr | Expr |
|
||||
OpNEq | Expr Expr | Expr |
|
||||
OpAnd | Expr Expr | Expr |
|
||||
OpOr | Expr Expr | Expr |
|
||||
OpImpl | Expr Expr | Expr |
|
||||
OpUpdate | Expr Expr | Expr |
|
||||
SubPath | Expr Expr | Expr |
|
||||
OpHasAttr | Expr string | Expr |
|
||||
OpPlus | Expr Expr | Expr |
|
||||
OpConcat | Expr Expr | Expr |
|
||||
ConcatStrings | ATermList | Expr |
|
||||
Call | Expr Expr | Expr |
|
||||
Select | Expr string | Expr |
|
||||
Var | string | Expr |
|
||||
Int | int | Expr |
|
||||
|
||||
# Strings in the evaluator carry a so-called `context' (the ATermList)
|
||||
# which is a list of strings representing store paths. This is to
|
||||
# allow users to write things like
|
||||
#
|
||||
# "--with-freetype2-library=" + freetype + "/lib"
|
||||
#
|
||||
# where `freetype' is a derivation (or a source to be copied to the
|
||||
# store). If we just concatenated the strings without keeping track
|
||||
# of the referenced store paths, then if the string is used as a
|
||||
# derivation attribute, the derivation will not have the correct
|
||||
# dependencies in its inputDrvs and inputSrcs.
|
||||
#
|
||||
# The semantics of the context is as follows: when a string with
|
||||
# context C is used as a derivation attribute, then the derivations in
|
||||
# C will be added to the inputDrvs of the derivation, and the other
|
||||
# store paths in C will be added to the inputSrcs of the derivations.
|
||||
#
|
||||
# For canonicity, the store paths should be in sorted order.
|
||||
Str | string ATermList | Expr |
|
||||
Str | string | Expr | ObsoleteStr
|
||||
|
||||
# Internal to the parser, doesn't occur in ASTs.
|
||||
IndStr | string | Expr |
|
||||
|
||||
# A path is a reference to a file system object that is to be copied
|
||||
# to the Nix store when used as a derivation attribute. When it is
|
||||
# concatenated to a string (i.e., `str + path'), it is also copied and
|
||||
# the resulting store path is concatenated to the string (with the
|
||||
# store path in the context). If a string or path is concatenated to
|
||||
# a path (i.e., `path + str' or `path + path'), the result is a new
|
||||
# path (if the right-hand side is a string, the context must be
|
||||
# empty).
|
||||
Path | string | Expr |
|
||||
|
||||
List | ATermList | Expr |
|
||||
BlackHole | | Expr |
|
||||
Undefined | | Expr |
|
||||
Removed | | Expr |
|
||||
PrimOp | int ATermBlob ATermList | Expr |
|
||||
Attrs | ATermList | Expr |
|
||||
Closed | Expr | Expr |
|
||||
Rec | ATermList ATermList | Expr |
|
||||
Bool | ATermBool | Expr |
|
||||
Null | | Expr |
|
||||
|
||||
Bind | string Expr Pos | ATerm |
|
||||
Bind | string Expr | ATerm | ObsoleteBind
|
||||
Inherit | Expr ATermList Pos | ATerm |
|
||||
|
||||
Scope | | Expr |
|
||||
|
||||
VarPat | string | Pattern |
|
||||
AttrsPat | ATermList ATermBool | Pattern | # bool = `...'
|
||||
AtPat | Pattern Pattern | Pattern |
|
||||
|
||||
Formal | string DefaultValue | ATerm |
|
||||
|
||||
DefaultValue | Expr | DefaultValue |
|
||||
NoDefaultValue | | DefaultValue |
|
||||
|
||||
True | | ATermBool |
|
||||
False | | ATermBool |
|
||||
|
||||
PrimOpDef | int ATermBlob | ATerm |
|
||||
|
||||
AttrRHS | Expr Pos | ATerm |
|
||||
|
||||
eTrue = makeBool(makeTrue())
|
||||
eFalse = makeBool(makeFalse())
|
||||
sOverrides = toATerm("__overrides")
|
||||
@@ -1,314 +1,407 @@
|
||||
#include "nixexpr.hh"
|
||||
#include "derivations.hh"
|
||||
#include "util.hh"
|
||||
#include "aterm.hh"
|
||||
|
||||
#include "nixexpr-ast.hh"
|
||||
#include "nixexpr-ast.cc"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
/* Displaying abstract syntax trees. */
|
||||
|
||||
std::ostream & operator << (std::ostream & str, Expr & e)
|
||||
{
|
||||
e.show(str);
|
||||
return str;
|
||||
}
|
||||
|
||||
void Expr::show(std::ostream & str)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
void ExprInt::show(std::ostream & str)
|
||||
{
|
||||
str << n;
|
||||
}
|
||||
|
||||
void ExprString::show(std::ostream & str)
|
||||
{
|
||||
str << "\"" << s << "\""; // !!! escaping
|
||||
}
|
||||
|
||||
void ExprPath::show(std::ostream & str)
|
||||
{
|
||||
str << s;
|
||||
}
|
||||
|
||||
void ExprVar::show(std::ostream & str)
|
||||
{
|
||||
str << info.name;
|
||||
}
|
||||
|
||||
void ExprSelect::show(std::ostream & str)
|
||||
{
|
||||
str << "(" << *e << ")." << name;
|
||||
}
|
||||
|
||||
void ExprOpHasAttr::show(std::ostream & str)
|
||||
{
|
||||
str << "(" << *e << ") ? " << name;
|
||||
}
|
||||
|
||||
void ExprAttrs::show(std::ostream & str)
|
||||
{
|
||||
if (recursive) str << "rec ";
|
||||
str << "{ ";
|
||||
foreach (AttrDefs::iterator, i, attrs)
|
||||
if (i->second.inherited)
|
||||
str << "inherit " << i->first << " " << "; ";
|
||||
else
|
||||
str << i->first << " = " << *i->second.e << "; ";
|
||||
str << "}";
|
||||
}
|
||||
|
||||
void ExprList::show(std::ostream & str)
|
||||
{
|
||||
str << "[ ";
|
||||
foreach (vector<Expr *>::iterator, i, elems)
|
||||
str << "(" << **i << ") ";
|
||||
str << "]";
|
||||
}
|
||||
|
||||
void ExprLambda::show(std::ostream & str)
|
||||
{
|
||||
str << "(";
|
||||
if (matchAttrs) {
|
||||
str << "{ ";
|
||||
bool first = true;
|
||||
foreach (Formals::Formals_::iterator, i, formals->formals) {
|
||||
if (first) first = false; else str << ", ";
|
||||
str << i->name;
|
||||
if (i->def) str << " ? " << *i->def;
|
||||
}
|
||||
str << " }";
|
||||
if (!arg.empty()) str << " @ ";
|
||||
}
|
||||
if (!arg.empty()) str << arg;
|
||||
str << ": " << *body << ")";
|
||||
}
|
||||
|
||||
void ExprLet::show(std::ostream & str)
|
||||
{
|
||||
str << "let ";
|
||||
foreach (ExprAttrs::AttrDefs::iterator, i, attrs->attrs)
|
||||
if (i->second.inherited)
|
||||
str << "inherit " << i->first << "; ";
|
||||
else
|
||||
str << i->first << " = " << *i->second.e << "; ";
|
||||
str << "in " << *body;
|
||||
}
|
||||
|
||||
void ExprWith::show(std::ostream & str)
|
||||
{
|
||||
str << "with " << *attrs << "; " << *body;
|
||||
}
|
||||
|
||||
void ExprIf::show(std::ostream & str)
|
||||
{
|
||||
str << "if " << *cond << " then " << *then << " else " << *else_;
|
||||
}
|
||||
|
||||
void ExprAssert::show(std::ostream & str)
|
||||
{
|
||||
str << "assert " << *cond << "; " << *body;
|
||||
}
|
||||
|
||||
void ExprOpNot::show(std::ostream & str)
|
||||
{
|
||||
str << "! " << *e;
|
||||
}
|
||||
|
||||
void ExprConcatStrings::show(std::ostream & str)
|
||||
{
|
||||
bool first = true;
|
||||
foreach (vector<Expr *>::iterator, i, *es) {
|
||||
if (first) first = false; else str << " + ";
|
||||
str << **i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::ostream & operator << (std::ostream & str, const Pos & pos)
|
||||
{
|
||||
if (!pos.line)
|
||||
str << "undefined position";
|
||||
else
|
||||
str << (format("`%1%:%2%:%3%'") % pos.file % pos.line % pos.column).str();
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
Pos noPos;
|
||||
|
||||
|
||||
/* Computing levels/displacements for variables. */
|
||||
|
||||
void Expr::bindVars(const StaticEnv & env)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
void ExprInt::bindVars(const StaticEnv & env)
|
||||
{
|
||||
}
|
||||
|
||||
void ExprString::bindVars(const StaticEnv & env)
|
||||
{
|
||||
}
|
||||
|
||||
void ExprPath::bindVars(const StaticEnv & env)
|
||||
{
|
||||
}
|
||||
|
||||
void VarRef::bind(const StaticEnv & env)
|
||||
{
|
||||
/* Check whether the variable appears in the environment. If so,
|
||||
set its level and displacement. */
|
||||
const StaticEnv * curEnv;
|
||||
unsigned int level;
|
||||
int withLevel = -1;
|
||||
for (curEnv = &env, level = 0; curEnv; curEnv = curEnv->up, level++) {
|
||||
if (curEnv->isWith) {
|
||||
if (withLevel == -1) withLevel = level;
|
||||
} else {
|
||||
StaticEnv::Vars::const_iterator i = curEnv->vars.find(name);
|
||||
if (i != curEnv->vars.end()) {
|
||||
fromWith = false;
|
||||
this->level = level;
|
||||
displ = i->second;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Otherwise, the variable must be obtained from the nearest
|
||||
enclosing `with'. If there is no `with', then we can issue an
|
||||
"undefined variable" error now. */
|
||||
if (withLevel == -1) throw EvalError(format("undefined variable `%1%'") % name);
|
||||
|
||||
fromWith = true;
|
||||
this->level = withLevel;
|
||||
}
|
||||
|
||||
void ExprVar::bindVars(const StaticEnv & env)
|
||||
{
|
||||
info.bind(env);
|
||||
}
|
||||
|
||||
void ExprSelect::bindVars(const StaticEnv & env)
|
||||
{
|
||||
e->bindVars(env);
|
||||
}
|
||||
|
||||
void ExprOpHasAttr::bindVars(const StaticEnv & env)
|
||||
{
|
||||
e->bindVars(env);
|
||||
}
|
||||
|
||||
void ExprAttrs::bindVars(const StaticEnv & env)
|
||||
{
|
||||
if (recursive) {
|
||||
StaticEnv newEnv(false, &env);
|
||||
|
||||
unsigned int displ = 0;
|
||||
foreach (AttrDefs::iterator, i, attrs)
|
||||
newEnv.vars[i->first] = i->second.displ = displ++;
|
||||
|
||||
string showPos(ATerm pos)
|
||||
{
|
||||
ATerm path;
|
||||
int line, column;
|
||||
if (matchNoPos(pos)) return "undefined position";
|
||||
if (!matchPos(pos, path, line, column))
|
||||
throw badTerm("position expected", pos);
|
||||
return (format("`%1%', line %2%") % aterm2String(path) % line).str();
|
||||
}
|
||||
|
||||
|
||||
ATerm bottomupRewrite(TermFun & f, ATerm e)
|
||||
{
|
||||
checkInterrupt();
|
||||
|
||||
if (ATgetType(e) == AT_APPL) {
|
||||
AFun fun = ATgetAFun(e);
|
||||
int arity = ATgetArity(fun);
|
||||
ATerm args[arity];
|
||||
|
||||
for (int i = 0; i < arity; ++i)
|
||||
args[i] = bottomupRewrite(f, ATgetArgument(e, i));
|
||||
|
||||
foreach (AttrDefs::iterator, i, attrs)
|
||||
if (i->second.inherited) i->second.var.bind(env);
|
||||
else i->second.e->bindVars(newEnv);
|
||||
e = (ATerm) ATmakeApplArray(fun, args);
|
||||
}
|
||||
|
||||
else
|
||||
foreach (AttrDefs::iterator, i, attrs)
|
||||
if (i->second.inherited) i->second.var.bind(env);
|
||||
else i->second.e->bindVars(env);
|
||||
}
|
||||
else if (ATgetType(e) == AT_LIST) {
|
||||
ATermList in = (ATermList) e;
|
||||
ATermList out = ATempty;
|
||||
|
||||
void ExprList::bindVars(const StaticEnv & env)
|
||||
{
|
||||
foreach (vector<Expr *>::iterator, i, elems)
|
||||
(*i)->bindVars(env);
|
||||
}
|
||||
for (ATermIterator i(in); i; ++i)
|
||||
out = ATinsert(out, bottomupRewrite(f, *i));
|
||||
|
||||
void ExprLambda::bindVars(const StaticEnv & env)
|
||||
{
|
||||
StaticEnv newEnv(false, &env);
|
||||
|
||||
unsigned int displ = 0;
|
||||
|
||||
if (!arg.empty()) newEnv.vars[arg] = displ++;
|
||||
|
||||
if (matchAttrs) {
|
||||
foreach (Formals::Formals_::iterator, i, formals->formals)
|
||||
newEnv.vars[i->name] = displ++;
|
||||
|
||||
foreach (Formals::Formals_::iterator, i, formals->formals)
|
||||
if (i->def) i->def->bindVars(newEnv);
|
||||
e = (ATerm) ATreverse(out);
|
||||
}
|
||||
|
||||
body->bindVars(newEnv);
|
||||
return f(e);
|
||||
}
|
||||
|
||||
void ExprLet::bindVars(const StaticEnv & env)
|
||||
|
||||
void queryAllAttrs(Expr e, ATermMap & attrs, bool withPos)
|
||||
{
|
||||
StaticEnv newEnv(false, &env);
|
||||
|
||||
unsigned int displ = 0;
|
||||
foreach (ExprAttrs::AttrDefs::iterator, i, attrs->attrs)
|
||||
newEnv.vars[i->first] = i->second.displ = displ++;
|
||||
|
||||
foreach (ExprAttrs::AttrDefs::iterator, i, attrs->attrs)
|
||||
if (i->second.inherited) i->second.var.bind(env);
|
||||
else i->second.e->bindVars(newEnv);
|
||||
|
||||
body->bindVars(newEnv);
|
||||
ATermList bnds;
|
||||
if (!matchAttrs(e, bnds))
|
||||
throw TypeError(format("value is %1% while an attribute set was expected") % showType(e));
|
||||
|
||||
for (ATermIterator i(bnds); i; ++i) {
|
||||
ATerm name;
|
||||
Expr e;
|
||||
ATerm pos;
|
||||
if (!matchBind(*i, name, e, pos)) abort(); /* can't happen */
|
||||
attrs.set(name, withPos ? makeAttrRHS(e, pos) : e);
|
||||
}
|
||||
}
|
||||
|
||||
void ExprWith::bindVars(const StaticEnv & env)
|
||||
|
||||
Expr queryAttr(Expr e, const string & name)
|
||||
{
|
||||
/* Does this `with' have an enclosing `with'? If so, record its
|
||||
level so that `lookupVar' can look up variables in the previous
|
||||
`with' if this one doesn't contain the desired attribute. */
|
||||
const StaticEnv * curEnv;
|
||||
unsigned int level;
|
||||
prevWith = 0;
|
||||
for (curEnv = &env, level = 1; curEnv; curEnv = curEnv->up, level++)
|
||||
if (curEnv->isWith) {
|
||||
prevWith = level;
|
||||
break;
|
||||
ATerm dummy;
|
||||
return queryAttr(e, name, dummy);
|
||||
}
|
||||
|
||||
|
||||
Expr queryAttr(Expr e, const string & name, ATerm & pos)
|
||||
{
|
||||
ATermList bnds;
|
||||
if (!matchAttrs(e, bnds))
|
||||
throw TypeError(format("value is %1% while an attribute set was expected") % showType(e));
|
||||
|
||||
for (ATermIterator i(bnds); i; ++i) {
|
||||
ATerm name2, pos2;
|
||||
Expr e;
|
||||
if (!matchBind(*i, name2, e, pos2))
|
||||
abort(); /* can't happen */
|
||||
if (aterm2String(name2) == name) {
|
||||
pos = pos2;
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Expr makeAttrs(const ATermMap & attrs)
|
||||
{
|
||||
ATermList bnds = ATempty;
|
||||
for (ATermMap::const_iterator i = attrs.begin(); i != attrs.end(); ++i) {
|
||||
Expr e;
|
||||
ATerm pos;
|
||||
if (!matchAttrRHS(i->value, e, pos))
|
||||
abort(); /* can't happen */
|
||||
bnds = ATinsert(bnds, makeBind(i->key, e, pos));
|
||||
}
|
||||
return makeAttrs(bnds);
|
||||
}
|
||||
|
||||
|
||||
static void varsBoundByPattern(ATermMap & map, Pattern pat)
|
||||
{
|
||||
ATerm name;
|
||||
ATermList formals;
|
||||
Pattern pat1, pat2;
|
||||
ATermBool ellipsis;
|
||||
/* Use makeRemoved() so that it can be used directly in
|
||||
substitute(). */
|
||||
if (matchVarPat(pat, name))
|
||||
map.set(name, makeRemoved());
|
||||
else if (matchAttrsPat(pat, formals, ellipsis)) {
|
||||
for (ATermIterator i(formals); i; ++i) {
|
||||
ATerm d1;
|
||||
if (!matchFormal(*i, name, d1)) abort();
|
||||
map.set(name, makeRemoved());
|
||||
}
|
||||
}
|
||||
else if (matchAtPat(pat, pat1, pat2)) {
|
||||
varsBoundByPattern(map, pat1);
|
||||
varsBoundByPattern(map, pat2);
|
||||
}
|
||||
else abort();
|
||||
}
|
||||
|
||||
|
||||
Expr substitute(const Substitution & subs, Expr e)
|
||||
{
|
||||
checkInterrupt();
|
||||
|
||||
//if (subs.size() == 0) return e;
|
||||
|
||||
ATerm name, pos, e2;
|
||||
|
||||
/* As an optimisation, don't substitute in subterms known to be
|
||||
closed. */
|
||||
if (matchClosed(e, e2)) return e;
|
||||
|
||||
if (matchVar(e, name)) {
|
||||
Expr sub = subs.lookup(name);
|
||||
if (sub == makeRemoved()) sub = 0;
|
||||
Expr wrapped;
|
||||
/* Add a "closed" wrapper around terms that aren't already
|
||||
closed. The check is necessary to prevent repeated
|
||||
wrapping, e.g., closed(closed(closed(...))), which kills
|
||||
caching. */
|
||||
return sub ? (matchClosed(sub, wrapped) ? sub : makeClosed(sub)) : e;
|
||||
}
|
||||
|
||||
/* In case of a function, filter out all variables bound by this
|
||||
function. */
|
||||
Pattern pat;
|
||||
ATerm body;
|
||||
if (matchFunction(e, pat, body, pos)) {
|
||||
ATermMap map(16);
|
||||
varsBoundByPattern(map, pat);
|
||||
Substitution subs2(&subs, &map);
|
||||
return makeFunction(
|
||||
(Pattern) substitute(subs2, (Expr) pat),
|
||||
substitute(subs2, body), pos);
|
||||
}
|
||||
|
||||
/* Idem for a mutually recursive attribute set. */
|
||||
ATermList rbnds, nrbnds;
|
||||
if (matchRec(e, rbnds, nrbnds)) {
|
||||
ATermMap map(ATgetLength(rbnds) + ATgetLength(nrbnds));
|
||||
for (ATermIterator i(rbnds); i; ++i)
|
||||
if (matchBind(*i, name, e2, pos)) map.set(name, makeRemoved());
|
||||
else abort(); /* can't happen */
|
||||
for (ATermIterator i(nrbnds); i; ++i)
|
||||
if (matchBind(*i, name, e2, pos)) map.set(name, makeRemoved());
|
||||
else abort(); /* can't happen */
|
||||
return makeRec(
|
||||
(ATermList) substitute(Substitution(&subs, &map), (ATerm) rbnds),
|
||||
(ATermList) substitute(subs, (ATerm) nrbnds));
|
||||
}
|
||||
|
||||
if (ATgetType(e) == AT_APPL) {
|
||||
AFun fun = ATgetAFun(e);
|
||||
int arity = ATgetArity(fun);
|
||||
ATerm args[arity];
|
||||
bool changed = false;
|
||||
|
||||
for (int i = 0; i < arity; ++i) {
|
||||
ATerm arg = ATgetArgument(e, i);
|
||||
args[i] = substitute(subs, arg);
|
||||
if (args[i] != arg) changed = true;
|
||||
}
|
||||
|
||||
return changed ? (ATerm) ATmakeApplArray(fun, args) : e;
|
||||
}
|
||||
|
||||
if (ATgetType(e) == AT_LIST) {
|
||||
unsigned int len = ATgetLength((ATermList) e);
|
||||
ATerm es[len];
|
||||
ATermIterator i((ATermList) e);
|
||||
for (unsigned int j = 0; i; ++i, ++j)
|
||||
es[j] = substitute(subs, *i);
|
||||
ATermList out = ATempty;
|
||||
for (unsigned int j = len; j; --j)
|
||||
out = ATinsert(out, es[j - 1]);
|
||||
return (ATerm) out;
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
|
||||
/* We use memoisation to prevent exponential complexity on heavily
|
||||
shared ATerms (remember, an ATerm is a graph, not a tree!). Note
|
||||
that using an STL set is fine here wrt to ATerm garbage collection
|
||||
since all the ATerms in the set are already reachable from
|
||||
somewhere else. */
|
||||
static void checkVarDefs2(set<Expr> & done, const ATermMap & defs, Expr e)
|
||||
{
|
||||
if (done.find(e) != done.end()) return;
|
||||
done.insert(e);
|
||||
|
||||
attrs->bindVars(env);
|
||||
StaticEnv newEnv(true, &env);
|
||||
body->bindVars(newEnv);
|
||||
ATerm name, pos, value;
|
||||
ATerm with, body;
|
||||
ATermList rbnds, nrbnds;
|
||||
Pattern pat;
|
||||
|
||||
/* Closed terms don't have free variables, so we don't have to
|
||||
check by definition. */
|
||||
if (matchClosed(e, value)) return;
|
||||
|
||||
else if (matchVar(e, name)) {
|
||||
if (!defs.get(name))
|
||||
throw EvalError(format("undefined variable `%1%'")
|
||||
% aterm2String(name));
|
||||
}
|
||||
|
||||
else if (matchFunction(e, pat, body, pos)) {
|
||||
ATermMap defs2(defs);
|
||||
varsBoundByPattern(defs2, pat);
|
||||
set<Expr> done2;
|
||||
checkVarDefs2(done2, defs2, pat);
|
||||
checkVarDefs2(done2, defs2, body);
|
||||
}
|
||||
|
||||
else if (matchRec(e, rbnds, nrbnds)) {
|
||||
checkVarDefs2(done, defs, (ATerm) nrbnds);
|
||||
ATermMap defs2(defs);
|
||||
for (ATermIterator i(rbnds); i; ++i) {
|
||||
if (!matchBind(*i, name, value, pos)) abort(); /* can't happen */
|
||||
defs2.set(name, (ATerm) ATempty);
|
||||
}
|
||||
for (ATermIterator i(nrbnds); i; ++i) {
|
||||
if (!matchBind(*i, name, value, pos)) abort(); /* can't happen */
|
||||
defs2.set(name, (ATerm) ATempty);
|
||||
}
|
||||
set<Expr> done2;
|
||||
checkVarDefs2(done2, defs2, (ATerm) rbnds);
|
||||
}
|
||||
|
||||
else if (matchWith(e, with, body, pos)) {
|
||||
/* We can't check the body without evaluating the definitions
|
||||
(which is an arbitrary expression), so we don't do that
|
||||
here but only when actually evaluating the `with'. */
|
||||
checkVarDefs2(done, defs, with);
|
||||
}
|
||||
|
||||
else if (ATgetType(e) == AT_APPL) {
|
||||
int arity = ATgetArity(ATgetAFun(e));
|
||||
for (int i = 0; i < arity; ++i)
|
||||
checkVarDefs2(done, defs, ATgetArgument(e, i));
|
||||
}
|
||||
|
||||
else if (ATgetType(e) == AT_LIST)
|
||||
for (ATermIterator i((ATermList) e); i; ++i)
|
||||
checkVarDefs2(done, defs, *i);
|
||||
}
|
||||
|
||||
void ExprIf::bindVars(const StaticEnv & env)
|
||||
|
||||
void checkVarDefs(const ATermMap & defs, Expr e)
|
||||
{
|
||||
cond->bindVars(env);
|
||||
then->bindVars(env);
|
||||
else_->bindVars(env);
|
||||
set<Expr> done;
|
||||
checkVarDefs2(done, defs, e);
|
||||
}
|
||||
|
||||
void ExprAssert::bindVars(const StaticEnv & env)
|
||||
|
||||
struct Canonicalise : TermFun
|
||||
{
|
||||
cond->bindVars(env);
|
||||
body->bindVars(env);
|
||||
}
|
||||
ATerm operator () (ATerm e)
|
||||
{
|
||||
/* Remove position info. */
|
||||
ATerm path;
|
||||
int line, column;
|
||||
if (matchPos(e, path, line, column))
|
||||
return makeNoPos();
|
||||
|
||||
void ExprOpNot::bindVars(const StaticEnv & env)
|
||||
/* Sort attribute sets. */
|
||||
ATermList _;
|
||||
if (matchAttrs(e, _)) {
|
||||
ATermMap attrs;
|
||||
queryAllAttrs(e, attrs);
|
||||
StringSet names;
|
||||
for (ATermMap::const_iterator i = attrs.begin(); i != attrs.end(); ++i)
|
||||
names.insert(aterm2String(i->key));
|
||||
|
||||
ATermList attrs2 = ATempty;
|
||||
for (StringSet::reverse_iterator i = names.rbegin(); i != names.rend(); ++i)
|
||||
attrs2 = ATinsert(attrs2,
|
||||
makeBind(toATerm(*i), attrs.get(toATerm(*i)), makeNoPos()));
|
||||
|
||||
return makeAttrs(attrs2);
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Expr canonicaliseExpr(Expr e)
|
||||
{
|
||||
e->bindVars(env);
|
||||
Canonicalise canonicalise;
|
||||
return bottomupRewrite(canonicalise, e);
|
||||
}
|
||||
|
||||
void ExprConcatStrings::bindVars(const StaticEnv & env)
|
||||
|
||||
Expr makeBool(bool b)
|
||||
{
|
||||
foreach (vector<Expr *>::iterator, i, *es)
|
||||
(*i)->bindVars(env);
|
||||
return b ? eTrue : eFalse;
|
||||
}
|
||||
|
||||
|
||||
bool matchStr(Expr e, string & s, PathSet & context)
|
||||
{
|
||||
ATermList l;
|
||||
ATerm s_;
|
||||
|
||||
if (!matchStr(e, s_, l)) return false;
|
||||
|
||||
s = aterm2String(s_);
|
||||
|
||||
for (ATermIterator i(l); i; ++i)
|
||||
context.insert(aterm2String(*i));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Expr makeStr(const string & s, const PathSet & context)
|
||||
{
|
||||
return makeStr(toATerm(s), toATermList(context));
|
||||
}
|
||||
|
||||
|
||||
string showType(Expr e)
|
||||
{
|
||||
ATerm t1, t2;
|
||||
ATermList l1;
|
||||
ATermBlob b1;
|
||||
int i1;
|
||||
Pattern p1;
|
||||
if (matchStr(e, t1, l1)) return "a string";
|
||||
if (matchPath(e, t1)) return "a path";
|
||||
if (matchNull(e)) return "null";
|
||||
if (matchInt(e, i1)) return "an integer";
|
||||
if (matchBool(e, t1)) return "a boolean";
|
||||
if (matchFunction(e, p1, t1, t2)) return "a function";
|
||||
if (matchAttrs(e, l1)) return "an attribute set";
|
||||
if (matchList(e, l1)) return "a list";
|
||||
if (matchPrimOp(e, i1, b1, l1)) return "a partially applied built-in function";
|
||||
return "an unknown type";
|
||||
}
|
||||
|
||||
|
||||
string showValue(Expr e)
|
||||
{
|
||||
PathSet context;
|
||||
string s;
|
||||
ATerm s2;
|
||||
int i;
|
||||
if (matchStr(e, s, context)) {
|
||||
string u;
|
||||
for (string::iterator i = s.begin(); i != s.end(); ++i)
|
||||
if (*i == '\"' || *i == '\\') u += "\\" + *i;
|
||||
else if (*i == '\n') u += "\\n";
|
||||
else if (*i == '\r') u += "\\r";
|
||||
else if (*i == '\t') u += "\\t";
|
||||
else u += *i;
|
||||
return "\"" + u + "\"";
|
||||
}
|
||||
if (matchPath(e, s2)) return aterm2String(s2);
|
||||
if (matchNull(e)) return "null";
|
||||
if (matchInt(e, i)) return (format("%1%") % i).str();
|
||||
if (e == eTrue) return "true";
|
||||
if (e == eFalse) return "false";
|
||||
/* !!! incomplete */
|
||||
return "<unknown>";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,278 +1,121 @@
|
||||
#ifndef __NIXEXPR_H
|
||||
#define __NIXEXPR_H
|
||||
|
||||
#include "symbol-table.hh"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "aterm-map.hh"
|
||||
#include "types.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
MakeError(EvalError, Error)
|
||||
MakeError(ParseError, Error)
|
||||
MakeError(AssertionError, EvalError)
|
||||
MakeError(ThrownError, AssertionError)
|
||||
MakeError(Abort, EvalError)
|
||||
MakeError(TypeError, EvalError)
|
||||
MakeError(ImportError, EvalError) // error building an imported derivation
|
||||
|
||||
|
||||
/* Position objects. */
|
||||
/* Nix expressions are represented as ATerms. The maximal sharing
|
||||
property of the ATerm library allows us to implement caching of
|
||||
normals forms efficiently. */
|
||||
typedef ATerm Expr;
|
||||
typedef ATerm DefaultValue;
|
||||
typedef ATerm Pos;
|
||||
typedef ATerm Pattern;
|
||||
typedef ATerm ATermBool;
|
||||
|
||||
struct Pos
|
||||
|
||||
/* A STL vector of ATerms. Should be used with great care since it's
|
||||
stored on the heap, and the elements are therefore not roots to the
|
||||
ATerm garbage collector. */
|
||||
typedef vector<ATerm> ATermVector;
|
||||
|
||||
|
||||
/* A substitution is a linked list of ATermMaps that map names to
|
||||
identifiers. We use a list of ATermMaps rather than a single to
|
||||
make it easy to grow or shrink a substitution when entering a
|
||||
scope. */
|
||||
struct Substitution
|
||||
{
|
||||
string file;
|
||||
unsigned int line, column;
|
||||
Pos() : line(0), column(0) { };
|
||||
Pos(const string & file, unsigned int line, unsigned int column)
|
||||
: file(file), line(line), column(column) { };
|
||||
};
|
||||
ATermMap * map;
|
||||
const Substitution * prev;
|
||||
|
||||
extern Pos noPos;
|
||||
|
||||
std::ostream & operator << (std::ostream & str, const Pos & pos);
|
||||
|
||||
|
||||
struct Env;
|
||||
struct Value;
|
||||
struct EvalState;
|
||||
struct StaticEnv;
|
||||
|
||||
|
||||
/* Abstract syntax of Nix expressions. */
|
||||
|
||||
struct Expr
|
||||
{
|
||||
virtual void show(std::ostream & str);
|
||||
virtual void bindVars(const StaticEnv & env);
|
||||
virtual void eval(EvalState & state, Env & env, Value & v);
|
||||
};
|
||||
|
||||
std::ostream & operator << (std::ostream & str, Expr & e);
|
||||
|
||||
#define COMMON_METHODS \
|
||||
void show(std::ostream & str); \
|
||||
void eval(EvalState & state, Env & env, Value & v); \
|
||||
void bindVars(const StaticEnv & env);
|
||||
|
||||
struct ExprInt : Expr
|
||||
{
|
||||
int n;
|
||||
ExprInt(int n) : n(n) { };
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
struct ExprString : Expr
|
||||
{
|
||||
Symbol s;
|
||||
ExprString(const Symbol & s) : s(s) { };
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
/* Temporary class used during parsing of indented strings. */
|
||||
struct ExprIndStr : Expr
|
||||
{
|
||||
string s;
|
||||
ExprIndStr(const string & s) : s(s) { };
|
||||
};
|
||||
|
||||
struct ExprPath : Expr
|
||||
{
|
||||
string s;
|
||||
ExprPath(const string & s) : s(s) { };
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
struct VarRef
|
||||
{
|
||||
Symbol name;
|
||||
|
||||
/* Whether the variable comes from an environment (e.g. a rec, let
|
||||
or function argument) or from a "with". */
|
||||
bool fromWith;
|
||||
|
||||
/* In the former case, the value is obtained by going `level'
|
||||
levels up from the current environment and getting the
|
||||
`displ'th value in that environment. In the latter case, the
|
||||
value is obtained by getting the attribute named `name' from
|
||||
the attribute set stored in the environment that is `level'
|
||||
levels up from the current one.*/
|
||||
unsigned int level;
|
||||
unsigned int displ;
|
||||
|
||||
VarRef() { };
|
||||
VarRef(const Symbol & name) : name(name) { };
|
||||
void bind(const StaticEnv & env);
|
||||
};
|
||||
|
||||
struct ExprVar : Expr
|
||||
{
|
||||
VarRef info;
|
||||
ExprVar(const Symbol & name) : info(name) { };
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
struct ExprSelect : Expr
|
||||
{
|
||||
Expr * e;
|
||||
Symbol name;
|
||||
ExprSelect(Expr * e, const Symbol & name) : e(e), name(name) { };
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
struct ExprOpHasAttr : Expr
|
||||
{
|
||||
Expr * e;
|
||||
Symbol name;
|
||||
ExprOpHasAttr(Expr * e, const Symbol & name) : e(e), name(name) { };
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
struct ExprAttrs : Expr
|
||||
{
|
||||
bool recursive;
|
||||
struct AttrDef {
|
||||
bool inherited;
|
||||
Expr * e; // if not inherited
|
||||
VarRef var; // if inherited
|
||||
Pos pos;
|
||||
unsigned int displ; // displacement
|
||||
AttrDef(Expr * e, const Pos & pos) : inherited(false), e(e), pos(pos) { };
|
||||
AttrDef(const Symbol & name, const Pos & pos) : inherited(true), var(name), pos(pos) { };
|
||||
AttrDef() { };
|
||||
};
|
||||
typedef std::map<Symbol, AttrDef> AttrDefs;
|
||||
AttrDefs attrs;
|
||||
ExprAttrs() : recursive(false) { };
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
struct ExprList : Expr
|
||||
{
|
||||
std::vector<Expr *> elems;
|
||||
ExprList() { };
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
struct Formal
|
||||
{
|
||||
Symbol name;
|
||||
Expr * def;
|
||||
Formal(const Symbol & name, Expr * def) : name(name), def(def) { };
|
||||
};
|
||||
|
||||
struct Formals
|
||||
{
|
||||
typedef std::list<Formal> Formals_;
|
||||
Formals_ formals;
|
||||
std::set<Symbol> argNames; // used during parsing
|
||||
bool ellipsis;
|
||||
};
|
||||
|
||||
struct ExprLambda : Expr
|
||||
{
|
||||
Pos pos;
|
||||
Symbol arg;
|
||||
bool matchAttrs;
|
||||
Formals * formals;
|
||||
Expr * body;
|
||||
ExprLambda(const Pos & pos, const Symbol & arg, bool matchAttrs, Formals * formals, Expr * body)
|
||||
: pos(pos), arg(arg), matchAttrs(matchAttrs), formals(formals), body(body)
|
||||
Substitution(const Substitution * prev, ATermMap * map)
|
||||
{
|
||||
if (!arg.empty() && formals && formals->argNames.find(arg) != formals->argNames.end())
|
||||
throw ParseError(format("duplicate formal function argument `%1%' at %2%")
|
||||
% arg % pos);
|
||||
};
|
||||
COMMON_METHODS
|
||||
this->prev = prev;
|
||||
this->map = map;
|
||||
}
|
||||
|
||||
Expr lookup(Expr name) const
|
||||
{
|
||||
Expr x;
|
||||
for (const Substitution * s(this); s; s = s->prev)
|
||||
if ((x = s->map->get(name))) return x;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct ExprLet : Expr
|
||||
|
||||
/* Show a position. */
|
||||
string showPos(ATerm pos);
|
||||
|
||||
/* Generic bottomup traversal over ATerms. The traversal first
|
||||
recursively descends into subterms, and then applies the given term
|
||||
function to the resulting term. */
|
||||
struct TermFun
|
||||
{
|
||||
ExprAttrs * attrs;
|
||||
Expr * body;
|
||||
ExprLet(ExprAttrs * attrs, Expr * body) : attrs(attrs), body(body) { };
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
struct ExprWith : Expr
|
||||
{
|
||||
Pos pos;
|
||||
Expr * attrs, * body;
|
||||
unsigned int prevWith;
|
||||
ExprWith(const Pos & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { };
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
struct ExprIf : Expr
|
||||
{
|
||||
Expr * cond, * then, * else_;
|
||||
ExprIf(Expr * cond, Expr * then, Expr * else_) : cond(cond), then(then), else_(else_) { };
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
struct ExprAssert : Expr
|
||||
{
|
||||
Pos pos;
|
||||
Expr * cond, * body;
|
||||
ExprAssert(const Pos & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { };
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
struct ExprOpNot : Expr
|
||||
{
|
||||
Expr * e;
|
||||
ExprOpNot(Expr * e) : e(e) { };
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
#define MakeBinOp(name, s) \
|
||||
struct Expr##name : Expr \
|
||||
{ \
|
||||
Expr * e1, * e2; \
|
||||
Expr##name(Expr * e1, Expr * e2) : e1(e1), e2(e2) { }; \
|
||||
void show(std::ostream & str) \
|
||||
{ \
|
||||
str << *e1 << " " s " " << *e2; \
|
||||
} \
|
||||
void bindVars(const StaticEnv & env) \
|
||||
{ \
|
||||
e1->bindVars(env); e2->bindVars(env); \
|
||||
} \
|
||||
void eval(EvalState & state, Env & env, Value & v); \
|
||||
};
|
||||
|
||||
MakeBinOp(App, "")
|
||||
MakeBinOp(OpEq, "==")
|
||||
MakeBinOp(OpNEq, "!=")
|
||||
MakeBinOp(OpAnd, "&&")
|
||||
MakeBinOp(OpOr, "||")
|
||||
MakeBinOp(OpImpl, "->")
|
||||
MakeBinOp(OpUpdate, "//")
|
||||
MakeBinOp(OpConcatLists, "++")
|
||||
|
||||
struct ExprConcatStrings : Expr
|
||||
{
|
||||
vector<Expr *> * es;
|
||||
ExprConcatStrings(vector<Expr *> * es) : es(es) { };
|
||||
COMMON_METHODS
|
||||
virtual ~TermFun() { }
|
||||
virtual ATerm operator () (ATerm e) = 0;
|
||||
};
|
||||
ATerm bottomupRewrite(TermFun & f, ATerm e);
|
||||
|
||||
|
||||
/* Static environments are used to map variable names onto (level,
|
||||
displacement) pairs used to obtain the value of the variable at
|
||||
runtime. */
|
||||
struct StaticEnv
|
||||
{
|
||||
bool isWith;
|
||||
const StaticEnv * up;
|
||||
typedef std::map<Symbol, unsigned int> Vars;
|
||||
Vars vars;
|
||||
StaticEnv(bool isWith, const StaticEnv * up) : isWith(isWith), up(up) { };
|
||||
};
|
||||
/* Query all attributes in an attribute set expression. The
|
||||
expression must be in normal form. */
|
||||
void queryAllAttrs(Expr e, ATermMap & attrs, bool withPos = false);
|
||||
|
||||
/* Query a specific attribute from an attribute set expression. The
|
||||
expression must be in normal form. */
|
||||
Expr queryAttr(Expr e, const string & name);
|
||||
Expr queryAttr(Expr e, const string & name, ATerm & pos);
|
||||
|
||||
/* Create an attribute set expression from an Attrs value. */
|
||||
Expr makeAttrs(const ATermMap & attrs);
|
||||
|
||||
|
||||
/* Perform a set of substitutions on an expression. */
|
||||
Expr substitute(const Substitution & subs, Expr e);
|
||||
|
||||
|
||||
/* Check whether all variables are defined in the given expression.
|
||||
Throw an exception if this isn't the case. */
|
||||
void checkVarDefs(const ATermMap & def, Expr e);
|
||||
|
||||
|
||||
/* Canonicalise a Nix expression by sorting attributes and removing
|
||||
location information. */
|
||||
Expr canonicaliseExpr(Expr e);
|
||||
|
||||
|
||||
/* Create an expression representing a boolean. */
|
||||
Expr makeBool(bool b);
|
||||
|
||||
|
||||
/* Manipulation of Str() nodes. Note: matchStr() does not clear
|
||||
context! */
|
||||
bool matchStr(Expr e, string & s, PathSet & context);
|
||||
|
||||
Expr makeStr(const string & s, const PathSet & context = PathSet());
|
||||
|
||||
|
||||
/* Showing types, values. */
|
||||
string showType(Expr e);
|
||||
|
||||
string showValue(Expr e);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,11 +8,12 @@ namespace nix {
|
||||
|
||||
|
||||
/* Parse a Nix expression from the specified file. If `path' refers
|
||||
to a directory, then "/default.nix" is appended. */
|
||||
Expr * parseExprFromFile(EvalState & state, Path path);
|
||||
to a directory, the "/default.nix" is appended. */
|
||||
Expr parseExprFromFile(EvalState & state, Path path);
|
||||
|
||||
/* Parse a Nix expression from the specified string. */
|
||||
Expr * parseExprFromString(EvalState & state, const string & s, const Path & basePath);
|
||||
Expr parseExprFromString(EvalState & state, const string & s,
|
||||
const Path & basePath);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -7,126 +7,70 @@
|
||||
%parse-param { yyscan_t scanner }
|
||||
%parse-param { ParseData * data }
|
||||
%lex-param { yyscan_t scanner }
|
||||
%lex-param { ParseData * data }
|
||||
|
||||
%code requires {
|
||||
|
||||
#ifndef BISON_HEADER
|
||||
#define BISON_HEADER
|
||||
|
||||
#include "util.hh"
|
||||
|
||||
#include "nixexpr.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct ParseData
|
||||
{
|
||||
SymbolTable & symbols;
|
||||
Expr * result;
|
||||
Path basePath;
|
||||
Path path;
|
||||
string error;
|
||||
Symbol sLetBody;
|
||||
ParseData(SymbolTable & symbols)
|
||||
: symbols(symbols)
|
||||
, sLetBody(symbols.create("<let-body>"))
|
||||
{ };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#define YY_DECL int yylex \
|
||||
(YYSTYPE * yylval_param, YYLTYPE * yylloc_param, yyscan_t yyscanner, nix::ParseData * data)
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
%{
|
||||
|
||||
#include "parser-tab.hh"
|
||||
#include "lexer-tab.hh"
|
||||
#define YYSTYPE YYSTYPE // workaround a bug in Bison 2.4
|
||||
|
||||
/* Newer versions of Bison copy the declarations below to
|
||||
parser-tab.hh, which sucks bigtime since lexer.l doesn't want that
|
||||
stuff. So allow it to be excluded. */
|
||||
#ifndef BISON_HEADER_HACK
|
||||
#define BISON_HEADER_HACK
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
YY_DECL;
|
||||
#include "aterm.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#include "parser-tab.hh"
|
||||
#include "lexer-tab.hh"
|
||||
|
||||
#include "nixexpr.hh"
|
||||
#include "nixexpr-ast.hh"
|
||||
|
||||
|
||||
using namespace nix;
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
|
||||
static string showAttrPath(const vector<Symbol> & attrPath)
|
||||
struct ParseData
|
||||
{
|
||||
string s;
|
||||
foreach (vector<Symbol>::const_iterator, i, attrPath) {
|
||||
if (!s.empty()) s += '.';
|
||||
s += *i;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
Expr result;
|
||||
Path basePath;
|
||||
Path path;
|
||||
string error;
|
||||
};
|
||||
|
||||
|
||||
static void dupAttr(const vector<Symbol> & attrPath, const Pos & pos, const Pos & prevPos)
|
||||
static Expr fixAttrs(int recursive, ATermList as)
|
||||
{
|
||||
throw ParseError(format("attribute `%1%' at %2% already defined at %3%")
|
||||
% showAttrPath(attrPath) % pos % prevPos);
|
||||
}
|
||||
|
||||
|
||||
static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos)
|
||||
{
|
||||
vector<Symbol> attrPath; attrPath.push_back(attr);
|
||||
throw ParseError(format("attribute `%1%' at %2% already defined at %3%")
|
||||
% showAttrPath(attrPath) % pos % prevPos);
|
||||
}
|
||||
|
||||
|
||||
static void addAttr(ExprAttrs * attrs, const vector<Symbol> & attrPath,
|
||||
Expr * e, const Pos & pos)
|
||||
{
|
||||
unsigned int n = 0;
|
||||
foreach (vector<Symbol>::const_iterator, i, attrPath) {
|
||||
n++;
|
||||
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(*i);
|
||||
if (j != attrs->attrs.end()) {
|
||||
if (!j->second.inherited) {
|
||||
ExprAttrs * attrs2 = dynamic_cast<ExprAttrs *>(j->second.e);
|
||||
if (!attrs2 || n == attrPath.size()) dupAttr(attrPath, pos, j->second.pos);
|
||||
attrs = attrs2;
|
||||
} else
|
||||
dupAttr(attrPath, pos, j->second.pos);
|
||||
} else {
|
||||
if (n == attrPath.size())
|
||||
attrs->attrs[*i] = ExprAttrs::AttrDef(e, pos);
|
||||
else {
|
||||
ExprAttrs * nested = new ExprAttrs;
|
||||
attrs->attrs[*i] = ExprAttrs::AttrDef(nested, pos);
|
||||
attrs = nested;
|
||||
ATermList bs = ATempty, cs = ATempty;
|
||||
ATermList * is = recursive ? &cs : &bs;
|
||||
for (ATermIterator i(as); i; ++i) {
|
||||
ATermList names;
|
||||
Expr src;
|
||||
ATerm pos;
|
||||
if (matchInherit(*i, src, names, pos)) {
|
||||
bool fromScope = matchScope(src);
|
||||
for (ATermIterator j(names); j; ++j) {
|
||||
Expr rhs = fromScope ? makeVar(*j) : makeSelect(src, *j);
|
||||
*is = ATinsert(*is, makeBind(*j, rhs, pos));
|
||||
}
|
||||
}
|
||||
} else bs = ATinsert(bs, *i);
|
||||
}
|
||||
if (recursive)
|
||||
return makeRec(bs, cs);
|
||||
else
|
||||
return makeAttrs(bs);
|
||||
}
|
||||
|
||||
|
||||
static void addFormal(const Pos & pos, Formals * formals, const Formal & formal)
|
||||
static Expr stripIndentation(ATermList es)
|
||||
{
|
||||
if (formals->argNames.find(formal.name) != formals->argNames.end())
|
||||
throw ParseError(format("duplicate formal function argument `%1%' at %2%")
|
||||
% formal.name % pos);
|
||||
formals->formals.push_front(formal);
|
||||
formals->argNames.insert(formal.name);
|
||||
}
|
||||
|
||||
|
||||
static Expr * stripIndentation(SymbolTable & symbols, vector<Expr *> & es)
|
||||
{
|
||||
if (es.empty()) return new ExprString(symbols.create(""));
|
||||
if (es == ATempty) return makeStr("");
|
||||
|
||||
/* Figure out the minimum indentation. Note that by design
|
||||
whitespace-only final lines are not taken into account. (So
|
||||
@@ -134,9 +78,9 @@ static Expr * stripIndentation(SymbolTable & symbols, vector<Expr *> & es)
|
||||
bool atStartOfLine = true; /* = seen only whitespace in the current line */
|
||||
unsigned int minIndent = 1000000;
|
||||
unsigned int curIndent = 0;
|
||||
foreach (vector<Expr *>::iterator, i, es) {
|
||||
ExprIndStr * e = dynamic_cast<ExprIndStr *>(*i);
|
||||
if (!e) {
|
||||
ATerm e;
|
||||
for (ATermIterator i(es); i; ++i) {
|
||||
if (!matchIndStr(*i, e)) {
|
||||
/* Anti-quotations end the current start-of-line whitespace. */
|
||||
if (atStartOfLine) {
|
||||
atStartOfLine = false;
|
||||
@@ -144,11 +88,12 @@ static Expr * stripIndentation(SymbolTable & symbols, vector<Expr *> & es)
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for (unsigned int j = 0; j < e->s.size(); ++j) {
|
||||
string s = aterm2String(e);
|
||||
for (unsigned int j = 0; j < s.size(); ++j) {
|
||||
if (atStartOfLine) {
|
||||
if (e->s[j] == ' ')
|
||||
if (s[j] == ' ')
|
||||
curIndent++;
|
||||
else if (e->s[j] == '\n') {
|
||||
else if (s[j] == '\n') {
|
||||
/* Empty line, doesn't influence minimum
|
||||
indentation. */
|
||||
curIndent = 0;
|
||||
@@ -156,7 +101,7 @@ static Expr * stripIndentation(SymbolTable & symbols, vector<Expr *> & es)
|
||||
atStartOfLine = false;
|
||||
if (curIndent < minIndent) minIndent = curIndent;
|
||||
}
|
||||
} else if (e->s[j] == '\n') {
|
||||
} else if (s[j] == '\n') {
|
||||
atStartOfLine = true;
|
||||
curIndent = 0;
|
||||
}
|
||||
@@ -164,52 +109,52 @@ static Expr * stripIndentation(SymbolTable & symbols, vector<Expr *> & es)
|
||||
}
|
||||
|
||||
/* Strip spaces from each line. */
|
||||
vector<Expr *> * es2 = new vector<Expr *>;
|
||||
ATermList es2 = ATempty;
|
||||
atStartOfLine = true;
|
||||
unsigned int curDropped = 0;
|
||||
unsigned int n = es.size();
|
||||
for (vector<Expr *>::iterator i = es.begin(); i != es.end(); ++i, --n) {
|
||||
ExprIndStr * e = dynamic_cast<ExprIndStr *>(*i);
|
||||
if (!e) {
|
||||
unsigned int n = ATgetLength(es);
|
||||
for (ATermIterator i(es); i; ++i, --n) {
|
||||
if (!matchIndStr(*i, e)) {
|
||||
atStartOfLine = false;
|
||||
curDropped = 0;
|
||||
es2->push_back(*i);
|
||||
es2 = ATinsert(es2, *i);
|
||||
continue;
|
||||
}
|
||||
|
||||
string s = aterm2String(e);
|
||||
string s2;
|
||||
for (unsigned int j = 0; j < e->s.size(); ++j) {
|
||||
for (unsigned int j = 0; j < s.size(); ++j) {
|
||||
if (atStartOfLine) {
|
||||
if (e->s[j] == ' ') {
|
||||
if (s[j] == ' ') {
|
||||
if (curDropped++ >= minIndent)
|
||||
s2 += e->s[j];
|
||||
s2 += s[j];
|
||||
}
|
||||
else if (e->s[j] == '\n') {
|
||||
else if (s[j] == '\n') {
|
||||
curDropped = 0;
|
||||
s2 += e->s[j];
|
||||
s2 += s[j];
|
||||
} else {
|
||||
atStartOfLine = false;
|
||||
curDropped = 0;
|
||||
s2 += e->s[j];
|
||||
s2 += s[j];
|
||||
}
|
||||
} else {
|
||||
s2 += e->s[j];
|
||||
if (e->s[j] == '\n') atStartOfLine = true;
|
||||
s2 += s[j];
|
||||
if (s[j] == '\n') atStartOfLine = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove the last line if it is empty and consists only of
|
||||
spaces. */
|
||||
if (n == 1) {
|
||||
string::size_type p = s2.find_last_of('\n');
|
||||
unsigned int p = s2.find_last_of('\n');
|
||||
if (p != string::npos && s2.find_first_not_of(' ', p + 1) == string::npos)
|
||||
s2 = string(s2, 0, p + 1);
|
||||
}
|
||||
|
||||
es2->push_back(new ExprString(symbols.create(s2)));
|
||||
|
||||
es2 = ATinsert(es2, makeStr(s2));
|
||||
}
|
||||
|
||||
return new ExprConcatStrings(es2);
|
||||
return makeConcatStrings(ATreverse(es2));
|
||||
}
|
||||
|
||||
|
||||
@@ -217,12 +162,13 @@ void backToString(yyscan_t scanner);
|
||||
void backToIndString(yyscan_t scanner);
|
||||
|
||||
|
||||
static Pos makeCurPos(const YYLTYPE & loc, ParseData * data)
|
||||
static Pos makeCurPos(YYLTYPE * loc, ParseData * data)
|
||||
{
|
||||
return Pos(data->path, loc.first_line, loc.first_column);
|
||||
return makePos(toATerm(data->path),
|
||||
loc->first_line, loc->first_column);
|
||||
}
|
||||
|
||||
#define CUR_POS makeCurPos(*yylocp, data)
|
||||
#define CUR_POS makeCurPos(yylocp, data)
|
||||
|
||||
|
||||
}
|
||||
@@ -230,40 +176,50 @@ static Pos makeCurPos(const YYLTYPE & loc, ParseData * data)
|
||||
|
||||
void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * error)
|
||||
{
|
||||
data->error = (format("%1%, at %2%")
|
||||
% error % makeCurPos(*loc, data)).str();
|
||||
data->error = (format("%1%, at `%2%':%3%:%4%")
|
||||
% error % data->path % loc->first_line % loc->first_column).str();
|
||||
}
|
||||
|
||||
|
||||
/* Make sure that the parse stack is scanned by the ATerm garbage
|
||||
collector. */
|
||||
static void * mallocAndProtect(size_t size)
|
||||
{
|
||||
void * p = malloc(size);
|
||||
if (p) ATprotectMemory(p, size);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void freeAndUnprotect(void * p)
|
||||
{
|
||||
ATunprotectMemory(p);
|
||||
free(p);
|
||||
}
|
||||
|
||||
#define YYMALLOC mallocAndProtect
|
||||
#define YYFREE freeAndUnprotect
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
%}
|
||||
|
||||
%union {
|
||||
nix::Expr * e;
|
||||
nix::ExprList * list;
|
||||
nix::ExprAttrs * attrs;
|
||||
nix::Formals * formals;
|
||||
nix::Formal * formal;
|
||||
int n;
|
||||
char * id; // !!! -> Symbol
|
||||
char * path;
|
||||
char * uri;
|
||||
std::vector<nix::Symbol> * ids;
|
||||
std::vector<nix::Expr *> * string_parts;
|
||||
ATerm t;
|
||||
ATermList ts;
|
||||
struct {
|
||||
ATermList formals;
|
||||
bool ellipsis;
|
||||
} formals;
|
||||
}
|
||||
|
||||
%type <e> start expr expr_function expr_if expr_op
|
||||
%type <e> expr_app expr_select expr_simple
|
||||
%type <list> expr_list
|
||||
%type <attrs> binds
|
||||
%type <t> start expr expr_function expr_if expr_op
|
||||
%type <t> expr_app expr_select expr_simple bind inheritsrc formal
|
||||
%type <t> pattern pattern2
|
||||
%type <ts> binds ids expr_list string_parts ind_string_parts
|
||||
%type <formals> formals
|
||||
%type <formal> formal
|
||||
%type <ids> ids attrpath
|
||||
%type <string_parts> string_parts ind_string_parts
|
||||
%token <id> ID ATTRPATH
|
||||
%token <e> STR IND_STR
|
||||
%token <n> INT
|
||||
%token <path> PATH
|
||||
%token <uri> URI
|
||||
%token <t> ID INT STR IND_STR PATH URI
|
||||
%token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL
|
||||
%token DOLLAR_CURLY /* == ${ */
|
||||
%token IND_STRING_OPEN IND_STRING_CLOSE
|
||||
@@ -287,170 +243,239 @@ start: expr { data->result = $1; };
|
||||
expr: expr_function;
|
||||
|
||||
expr_function
|
||||
: ID ':' expr_function
|
||||
{ $$ = new ExprLambda(CUR_POS, data->symbols.create($1), false, 0, $3); }
|
||||
| '{' formals '}' ':' expr_function
|
||||
{ $$ = new ExprLambda(CUR_POS, data->symbols.create(""), true, $2, $5); }
|
||||
| '{' formals '}' '@' ID ':' expr_function
|
||||
{ $$ = new ExprLambda(CUR_POS, data->symbols.create($5), true, $2, $7); }
|
||||
| ID '@' '{' formals '}' ':' expr_function
|
||||
{ $$ = new ExprLambda(CUR_POS, data->symbols.create($1), true, $4, $7); }
|
||||
: pattern ':' expr_function
|
||||
{ $$ = makeFunction($1, $3, CUR_POS); }
|
||||
| ASSERT expr ';' expr_function
|
||||
{ $$ = new ExprAssert(CUR_POS, $2, $4); }
|
||||
{ $$ = makeAssert($2, $4, CUR_POS); }
|
||||
| WITH expr ';' expr_function
|
||||
{ $$ = new ExprWith(CUR_POS, $2, $4); }
|
||||
{ $$ = makeWith($2, $4, CUR_POS); }
|
||||
| LET binds IN expr_function
|
||||
{ $$ = new ExprLet($2, $4); }
|
||||
{ $$ = makeSelect(fixAttrs(1, ATinsert($2, makeBind(toATerm("<let-body>"), $4, CUR_POS))), toATerm("<let-body>")); }
|
||||
| expr_if
|
||||
;
|
||||
|
||||
expr_if
|
||||
: IF expr THEN expr ELSE expr { $$ = new ExprIf($2, $4, $6); }
|
||||
: IF expr THEN expr ELSE expr
|
||||
{ $$ = makeIf($2, $4, $6); }
|
||||
| expr_op
|
||||
;
|
||||
|
||||
expr_op
|
||||
: '!' expr_op %prec NEG { $$ = new ExprOpNot($2); }
|
||||
| expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); }
|
||||
| expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); }
|
||||
| expr_op AND expr_op { $$ = new ExprOpAnd($1, $3); }
|
||||
| expr_op OR expr_op { $$ = new ExprOpOr($1, $3); }
|
||||
| expr_op IMPL expr_op { $$ = new ExprOpImpl($1, $3); }
|
||||
| expr_op UPDATE expr_op { $$ = new ExprOpUpdate($1, $3); }
|
||||
| expr_op '?' ID { $$ = new ExprOpHasAttr($1, data->symbols.create($3)); }
|
||||
| expr_op '+' expr_op
|
||||
{ vector<Expr *> * l = new vector<Expr *>;
|
||||
l->push_back($1);
|
||||
l->push_back($3);
|
||||
$$ = new ExprConcatStrings(l);
|
||||
}
|
||||
| expr_op CONCAT expr_op { $$ = new ExprOpConcatLists($1, $3); }
|
||||
: '!' expr_op %prec NEG { $$ = makeOpNot($2); }
|
||||
| expr_op EQ expr_op { $$ = makeOpEq($1, $3); }
|
||||
| expr_op NEQ expr_op { $$ = makeOpNEq($1, $3); }
|
||||
| expr_op AND expr_op { $$ = makeOpAnd($1, $3); }
|
||||
| expr_op OR expr_op { $$ = makeOpOr($1, $3); }
|
||||
| expr_op IMPL expr_op { $$ = makeOpImpl($1, $3); }
|
||||
| expr_op UPDATE expr_op { $$ = makeOpUpdate($1, $3); }
|
||||
| expr_op '~' expr_op { $$ = makeSubPath($1, $3); }
|
||||
| expr_op '?' ID { $$ = makeOpHasAttr($1, $3); }
|
||||
| expr_op '+' expr_op { $$ = makeOpPlus($1, $3); }
|
||||
| expr_op CONCAT expr_op { $$ = makeOpConcat($1, $3); }
|
||||
| expr_app
|
||||
;
|
||||
|
||||
expr_app
|
||||
: expr_app expr_select
|
||||
{ $$ = new ExprApp($1, $2); }
|
||||
{ $$ = makeCall($1, $2); }
|
||||
| expr_select { $$ = $1; }
|
||||
;
|
||||
|
||||
expr_select
|
||||
: expr_select '.' ID
|
||||
{ $$ = new ExprSelect($1, data->symbols.create($3)); }
|
||||
{ $$ = makeSelect($1, $3); }
|
||||
| expr_simple { $$ = $1; }
|
||||
;
|
||||
|
||||
expr_simple
|
||||
: ID { $$ = new ExprVar(data->symbols.create($1)); }
|
||||
| INT { $$ = new ExprInt($1); }
|
||||
: ID { $$ = makeVar($1); }
|
||||
| INT { $$ = makeInt(ATgetInt((ATermInt) $1)); }
|
||||
| '"' string_parts '"' {
|
||||
/* For efficiency, and to simplify parse trees a bit. */
|
||||
if ($2->empty()) $$ = new ExprString(data->symbols.create(""));
|
||||
else if ($2->size() == 1) $$ = $2->front();
|
||||
else $$ = new ExprConcatStrings($2);
|
||||
if ($2 == ATempty) $$ = makeStr(toATerm(""), ATempty);
|
||||
else if (ATgetNext($2) == ATempty) $$ = ATgetFirst($2);
|
||||
else $$ = makeConcatStrings(ATreverse($2));
|
||||
}
|
||||
| IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
|
||||
$$ = stripIndentation(data->symbols, *$2);
|
||||
$$ = stripIndentation(ATreverse($2));
|
||||
}
|
||||
| PATH { $$ = new ExprPath(absPath($1, data->basePath)); }
|
||||
| URI { $$ = new ExprString(data->symbols.create($1)); }
|
||||
| PATH { $$ = makePath(toATerm(absPath(aterm2String($1), data->basePath))); }
|
||||
| URI { $$ = makeStr($1, ATempty); }
|
||||
| '(' expr ')' { $$ = $2; }
|
||||
/* Let expressions `let {..., body = ...}' are just desugared
|
||||
into `(rec {..., body = ...}).body'. */
|
||||
| LET '{' binds '}'
|
||||
{ $3->recursive = true; $$ = new ExprSelect($3, data->symbols.create("body")); }
|
||||
{ $$ = makeSelect(fixAttrs(1, $3), toATerm("body")); }
|
||||
| REC '{' binds '}'
|
||||
{ $3->recursive = true; $$ = $3; }
|
||||
{ $$ = fixAttrs(1, $3); }
|
||||
| '{' binds '}'
|
||||
{ $$ = $2; }
|
||||
| '[' expr_list ']' { $$ = $2; }
|
||||
{ $$ = fixAttrs(0, $2); }
|
||||
| '[' expr_list ']' { $$ = makeList($2); }
|
||||
;
|
||||
|
||||
string_parts
|
||||
: string_parts STR { $$ = $1; $1->push_back($2); }
|
||||
| string_parts DOLLAR_CURLY expr '}' { backToString(scanner); $$ = $1; $1->push_back($3); }
|
||||
| { $$ = new vector<Expr *>; }
|
||||
: string_parts STR { $$ = ATinsert($1, $2); }
|
||||
| string_parts DOLLAR_CURLY expr '}' { backToString(scanner); $$ = ATinsert($1, $3); }
|
||||
| { $$ = ATempty; }
|
||||
;
|
||||
|
||||
ind_string_parts
|
||||
: ind_string_parts IND_STR { $$ = $1; $1->push_back($2); }
|
||||
| ind_string_parts DOLLAR_CURLY expr '}' { backToIndString(scanner); $$ = $1; $1->push_back($3); }
|
||||
| { $$ = new vector<Expr *>; }
|
||||
: ind_string_parts IND_STR { $$ = ATinsert($1, $2); }
|
||||
| ind_string_parts DOLLAR_CURLY expr '}' { backToIndString(scanner); $$ = ATinsert($1, $3); }
|
||||
| { $$ = ATempty; }
|
||||
;
|
||||
|
||||
pattern
|
||||
: pattern2 '@' pattern { $$ = makeAtPat($1, $3); }
|
||||
| pattern2
|
||||
;
|
||||
|
||||
pattern2
|
||||
: ID { $$ = makeVarPat($1); }
|
||||
| '{' formals '}' { $$ = makeAttrsPat($2.formals, $2.ellipsis ? eTrue : eFalse); }
|
||||
;
|
||||
|
||||
binds
|
||||
: binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data)); }
|
||||
| binds INHERIT ids ';'
|
||||
{ $$ = $1;
|
||||
foreach (vector<Symbol>::iterator, i, *$3) {
|
||||
if ($$->attrs.find(*i) != $$->attrs.end())
|
||||
dupAttr(*i, makeCurPos(@3, data), $$->attrs[*i].pos);
|
||||
Pos pos = makeCurPos(@3, data);
|
||||
$$->attrs[*i] = ExprAttrs::AttrDef(*i, pos);
|
||||
}
|
||||
}
|
||||
| binds INHERIT '(' expr ')' ids ';'
|
||||
{ $$ = $1;
|
||||
/* !!! Should ensure sharing of the expression in $4. */
|
||||
foreach (vector<Symbol>::iterator, i, *$6) {
|
||||
if ($$->attrs.find(*i) != $$->attrs.end())
|
||||
dupAttr(*i, makeCurPos(@6, data), $$->attrs[*i].pos);
|
||||
$$->attrs[*i] = ExprAttrs::AttrDef(new ExprSelect($4, *i), makeCurPos(@6, data));
|
||||
}}
|
||||
|
||||
| { $$ = new ExprAttrs; }
|
||||
: binds bind { $$ = ATinsert($1, $2); }
|
||||
| { $$ = ATempty; }
|
||||
;
|
||||
|
||||
ids
|
||||
: ids ID { $$ = $1; $1->push_back(data->symbols.create($2)); /* !!! dangerous */ }
|
||||
| { $$ = new vector<Symbol>; }
|
||||
bind
|
||||
: ID '=' expr ';'
|
||||
{ $$ = makeBind($1, $3, CUR_POS); }
|
||||
| INHERIT inheritsrc ids ';'
|
||||
{ $$ = makeInherit($2, $3, CUR_POS); }
|
||||
;
|
||||
|
||||
attrpath
|
||||
: attrpath '.' ID { $$ = $1; $1->push_back(data->symbols.create($3)); }
|
||||
| ID { $$ = new vector<Symbol>; $$->push_back(data->symbols.create($1)); }
|
||||
inheritsrc
|
||||
: '(' expr ')' { $$ = $2; }
|
||||
| { $$ = makeScope(); }
|
||||
;
|
||||
|
||||
ids: ids ID { $$ = ATinsert($1, $2); } | { $$ = ATempty; };
|
||||
|
||||
expr_list
|
||||
: expr_list expr_select { $$ = $1; $1->elems.push_back($2); /* !!! dangerous */ }
|
||||
| { $$ = new ExprList; }
|
||||
: expr_select expr_list { $$ = ATinsert($2, $1); }
|
||||
/* yes, this is right-recursive, but it doesn't matter since
|
||||
otherwise we would need ATreverse which requires unbounded
|
||||
stack space */
|
||||
| { $$ = ATempty; }
|
||||
;
|
||||
|
||||
formals
|
||||
: formal ',' formals
|
||||
{ $$ = $3; addFormal(CUR_POS, $$, *$1); }
|
||||
: formal ',' formals /* idem - right recursive */
|
||||
{ $$.formals = ATinsert($3.formals, $1); $$.ellipsis = $3.ellipsis; }
|
||||
| formal
|
||||
{ $$ = new Formals; addFormal(CUR_POS, $$, *$1); $$->ellipsis = false; }
|
||||
{ $$.formals = ATinsert(ATempty, $1); $$.ellipsis = false; }
|
||||
|
|
||||
{ $$ = new Formals; $$->ellipsis = false; }
|
||||
{ $$.formals = ATempty; $$.ellipsis = false; }
|
||||
| ELLIPSIS
|
||||
{ $$ = new Formals; $$->ellipsis = true; }
|
||||
{ $$.formals = ATempty; $$.ellipsis = true; }
|
||||
;
|
||||
|
||||
formal
|
||||
: ID { $$ = new Formal(data->symbols.create($1), 0); }
|
||||
| ID '?' expr { $$ = new Formal(data->symbols.create($1), $3); }
|
||||
: ID { $$ = makeFormal($1, makeNoDefaultValue()); }
|
||||
| ID '?' expr { $$ = makeFormal($1, makeDefaultValue($3)); }
|
||||
;
|
||||
|
||||
%%
|
||||
|
||||
|
||||
#include "eval.hh"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <eval.hh>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
static Expr * parse(EvalState & state, const char * text,
|
||||
const Path & path, const Path & basePath)
|
||||
static void checkAttrs(ATermMap & names, ATermList bnds)
|
||||
{
|
||||
for (ATermIterator i(bnds); i; ++i) {
|
||||
ATerm name;
|
||||
Expr e;
|
||||
ATerm pos;
|
||||
if (!matchBind(*i, name, e, pos)) abort(); /* can't happen */
|
||||
if (names.get(name))
|
||||
throw EvalError(format("duplicate attribute `%1%' at %2%")
|
||||
% aterm2String(name) % showPos(pos));
|
||||
names.set(name, name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void checkPatternVars(ATerm pos, ATermMap & map, Pattern pat)
|
||||
{
|
||||
ATerm name;
|
||||
ATermList formals;
|
||||
Pattern pat1, pat2;
|
||||
ATermBool ellipsis;
|
||||
if (matchVarPat(pat, name)) {
|
||||
if (map.get(name))
|
||||
throw EvalError(format("duplicate formal function argument `%1%' at %2%")
|
||||
% aterm2String(name) % showPos(pos));
|
||||
map.set(name, name);
|
||||
}
|
||||
else if (matchAttrsPat(pat, formals, ellipsis)) {
|
||||
for (ATermIterator i(formals); i; ++i) {
|
||||
ATerm d1;
|
||||
if (!matchFormal(*i, name, d1)) abort();
|
||||
if (map.get(name))
|
||||
throw EvalError(format("duplicate formal function argument `%1%' at %2%")
|
||||
% aterm2String(name) % showPos(pos));
|
||||
map.set(name, name);
|
||||
}
|
||||
}
|
||||
else if (matchAtPat(pat, pat1, pat2)) {
|
||||
checkPatternVars(pos, map, pat1);
|
||||
checkPatternVars(pos, map, pat2);
|
||||
}
|
||||
else abort();
|
||||
}
|
||||
|
||||
|
||||
static void checkAttrSets(ATerm e)
|
||||
{
|
||||
ATerm pat, body, pos;
|
||||
if (matchFunction(e, pat, body, pos)) {
|
||||
ATermMap map(16);
|
||||
checkPatternVars(pos, map, pat);
|
||||
}
|
||||
|
||||
ATermList bnds;
|
||||
if (matchAttrs(e, bnds)) {
|
||||
ATermMap names(ATgetLength(bnds));
|
||||
checkAttrs(names, bnds);
|
||||
}
|
||||
|
||||
ATermList rbnds, nrbnds;
|
||||
if (matchRec(e, rbnds, nrbnds)) {
|
||||
ATermMap names(ATgetLength(rbnds) + ATgetLength(nrbnds));
|
||||
checkAttrs(names, rbnds);
|
||||
checkAttrs(names, nrbnds);
|
||||
}
|
||||
|
||||
if (ATgetType(e) == AT_APPL) {
|
||||
int arity = ATgetArity(ATgetAFun(e));
|
||||
for (int i = 0; i < arity; ++i)
|
||||
checkAttrSets(ATgetArgument(e, i));
|
||||
}
|
||||
|
||||
else if (ATgetType(e) == AT_LIST)
|
||||
for (ATermIterator i((ATermList) e); i; ++i)
|
||||
checkAttrSets(*i);
|
||||
}
|
||||
|
||||
|
||||
static Expr parse(EvalState & state,
|
||||
const char * text, const Path & path,
|
||||
const Path & basePath)
|
||||
{
|
||||
yyscan_t scanner;
|
||||
ParseData data(state.symbols);
|
||||
ParseData data;
|
||||
data.basePath = basePath;
|
||||
data.path = path;
|
||||
|
||||
@@ -459,22 +484,30 @@ static Expr * parse(EvalState & state, const char * text,
|
||||
int res = yyparse(scanner, &data);
|
||||
yylex_destroy(scanner);
|
||||
|
||||
if (res) throw ParseError(data.error);
|
||||
if (res) throw EvalError(data.error);
|
||||
|
||||
try {
|
||||
data.result->bindVars(state.staticBaseEnv);
|
||||
checkVarDefs(state.primOps, data.result);
|
||||
} catch (Error & e) {
|
||||
throw ParseError(format("%1%, in `%2%'") % e.msg() % path);
|
||||
throw EvalError(format("%1%, in `%2%'") % e.msg() % path);
|
||||
}
|
||||
|
||||
checkAttrSets(data.result);
|
||||
|
||||
return data.result;
|
||||
}
|
||||
|
||||
|
||||
Expr * parseExprFromFile(EvalState & state, Path path)
|
||||
Expr parseExprFromFile(EvalState & state, Path path)
|
||||
{
|
||||
assert(path[0] == '/');
|
||||
|
||||
#if 0
|
||||
/* Perhaps this is already an imploded parse tree? */
|
||||
Expr e = ATreadFromNamedFile(path.c_str());
|
||||
if (e) return e;
|
||||
#endif
|
||||
|
||||
/* If `path' is a symlink, follow it. This is so that relative
|
||||
path references work. */
|
||||
struct stat st;
|
||||
@@ -486,6 +519,8 @@ Expr * parseExprFromFile(EvalState & state, Path path)
|
||||
}
|
||||
|
||||
/* If `path' refers to a directory, append `/default.nix'. */
|
||||
if (stat(path.c_str(), &st))
|
||||
throw SysError(format("getting status of `%1%'") % path);
|
||||
if (S_ISDIR(st.st_mode))
|
||||
path = canonPath(path + "/default.nix");
|
||||
|
||||
@@ -494,7 +529,7 @@ Expr * parseExprFromFile(EvalState & state, Path path)
|
||||
}
|
||||
|
||||
|
||||
Expr * parseExprFromString(EvalState & state,
|
||||
Expr parseExprFromString(EvalState & state,
|
||||
const string & s, const Path & basePath)
|
||||
{
|
||||
return parse(state, s.c_str(), "(string)", basePath);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,92 +0,0 @@
|
||||
#ifndef __SYMBOL_TABLE_H
|
||||
#define __SYMBOL_TABLE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#if HAVE_TR1_UNORDERED_SET
|
||||
#include <tr1/unordered_set>
|
||||
#endif
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* Symbol table used by the parser and evaluator to represent and look
|
||||
up identifiers and attribute sets efficiently.
|
||||
SymbolTable::create() converts a string into a symbol. Symbols
|
||||
have the property that they can be compared efficiently (using a
|
||||
pointer equality test), because the symbol table stores only one
|
||||
copy of each string. */
|
||||
|
||||
class Symbol
|
||||
{
|
||||
private:
|
||||
const string * s; // pointer into SymbolTable
|
||||
Symbol(const string * s) : s(s) { };
|
||||
friend class SymbolTable;
|
||||
|
||||
public:
|
||||
Symbol() : s(0) { };
|
||||
|
||||
bool operator == (const Symbol & s2) const
|
||||
{
|
||||
return s == s2.s;
|
||||
}
|
||||
|
||||
bool operator != (const Symbol & s2) const
|
||||
{
|
||||
return s != s2.s;
|
||||
}
|
||||
|
||||
bool operator < (const Symbol & s2) const
|
||||
{
|
||||
return s < s2.s;
|
||||
}
|
||||
|
||||
operator const string & () const
|
||||
{
|
||||
return *s;
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return s->empty();
|
||||
}
|
||||
|
||||
friend std::ostream & operator << (std::ostream & str, const Symbol & sym);
|
||||
};
|
||||
|
||||
inline std::ostream & operator << (std::ostream & str, const Symbol & sym)
|
||||
{
|
||||
str << *sym.s;
|
||||
return str;
|
||||
}
|
||||
|
||||
class SymbolTable
|
||||
{
|
||||
private:
|
||||
#if HAVE_TR1_UNORDERED_SET
|
||||
typedef std::tr1::unordered_set<string> Symbols;
|
||||
#else
|
||||
typedef std::set<string> Symbols;
|
||||
#endif
|
||||
Symbols symbols;
|
||||
|
||||
public:
|
||||
Symbol create(const string & s)
|
||||
{
|
||||
std::pair<Symbols::iterator, bool> res = symbols.insert(s);
|
||||
return Symbol(&*res.first);
|
||||
}
|
||||
|
||||
unsigned int size() const
|
||||
{
|
||||
return symbols.size();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* !__SYMBOL_TABLE_H */
|
||||
@@ -1,162 +0,0 @@
|
||||
#include "value-to-xml.hh"
|
||||
#include "xml-writer.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
static XMLAttrs singletonAttrs(const string & name, const string & value)
|
||||
{
|
||||
XMLAttrs attrs;
|
||||
attrs[name] = value;
|
||||
return attrs;
|
||||
}
|
||||
|
||||
|
||||
static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||
Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen);
|
||||
|
||||
|
||||
static void posToXML(XMLAttrs & xmlAttrs, const Pos & pos)
|
||||
{
|
||||
xmlAttrs["path"] = pos.file;
|
||||
xmlAttrs["line"] = (format("%1%") % pos.line).str();
|
||||
xmlAttrs["column"] = (format("%1%") % pos.column).str();
|
||||
}
|
||||
|
||||
|
||||
static void showAttrs(EvalState & state, bool strict, bool location,
|
||||
Bindings & attrs, XMLWriter & doc, PathSet & context, PathSet & drvsSeen)
|
||||
{
|
||||
StringSet names;
|
||||
|
||||
foreach (Bindings::iterator, i, attrs)
|
||||
names.insert(i->name);
|
||||
|
||||
foreach (StringSet::iterator, i, names) {
|
||||
Attr & a(*attrs.find(state.symbols.create(*i)));
|
||||
|
||||
XMLAttrs xmlAttrs;
|
||||
xmlAttrs["name"] = *i;
|
||||
if (location && a.pos != &noPos) posToXML(xmlAttrs, *a.pos);
|
||||
|
||||
XMLOpenElement _(doc, "attr", xmlAttrs);
|
||||
printValueAsXML(state, strict, location,
|
||||
*a.value, doc, context, drvsSeen);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||
Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen)
|
||||
{
|
||||
checkInterrupt();
|
||||
|
||||
if (strict) state.forceValue(v);
|
||||
|
||||
switch (v.type) {
|
||||
|
||||
case tInt:
|
||||
doc.writeEmptyElement("int", singletonAttrs("value", (format("%1%") % v.integer).str()));
|
||||
break;
|
||||
|
||||
case tBool:
|
||||
doc.writeEmptyElement("bool", singletonAttrs("value", v.boolean ? "true" : "false"));
|
||||
break;
|
||||
|
||||
case tString:
|
||||
/* !!! show the context? */
|
||||
copyContext(v, context);
|
||||
doc.writeEmptyElement("string", singletonAttrs("value", v.string.s));
|
||||
break;
|
||||
|
||||
case tPath:
|
||||
doc.writeEmptyElement("path", singletonAttrs("value", v.path));
|
||||
break;
|
||||
|
||||
case tNull:
|
||||
doc.writeEmptyElement("null");
|
||||
break;
|
||||
|
||||
case tAttrs:
|
||||
if (state.isDerivation(v)) {
|
||||
XMLAttrs xmlAttrs;
|
||||
|
||||
Bindings::iterator a = v.attrs->find(state.symbols.create("derivation"));
|
||||
|
||||
Path drvPath;
|
||||
a = v.attrs->find(state.sDrvPath);
|
||||
if (a != v.attrs->end()) {
|
||||
if (strict) state.forceValue(*a->value);
|
||||
if (a->value->type == tString)
|
||||
xmlAttrs["drvPath"] = drvPath = a->value->string.s;
|
||||
}
|
||||
|
||||
a = v.attrs->find(state.sOutPath);
|
||||
if (a != v.attrs->end()) {
|
||||
if (strict) state.forceValue(*a->value);
|
||||
if (a->value->type == tString)
|
||||
xmlAttrs["outPath"] = a->value->string.s;
|
||||
}
|
||||
|
||||
XMLOpenElement _(doc, "derivation", xmlAttrs);
|
||||
|
||||
if (drvPath != "" && drvsSeen.find(drvPath) == drvsSeen.end()) {
|
||||
drvsSeen.insert(drvPath);
|
||||
showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen);
|
||||
} else
|
||||
doc.writeEmptyElement("repeated");
|
||||
}
|
||||
|
||||
else {
|
||||
XMLOpenElement _(doc, "attrs");
|
||||
showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case tList: {
|
||||
XMLOpenElement _(doc, "list");
|
||||
for (unsigned int n = 0; n < v.list.length; ++n)
|
||||
printValueAsXML(state, strict, location, *v.list.elems[n], doc, context, drvsSeen);
|
||||
break;
|
||||
}
|
||||
|
||||
case tLambda: {
|
||||
XMLAttrs xmlAttrs;
|
||||
if (location) posToXML(xmlAttrs, v.lambda.fun->pos);
|
||||
XMLOpenElement _(doc, "function", xmlAttrs);
|
||||
|
||||
if (v.lambda.fun->matchAttrs) {
|
||||
XMLAttrs attrs;
|
||||
if (!v.lambda.fun->arg.empty()) attrs["name"] = v.lambda.fun->arg;
|
||||
if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1";
|
||||
XMLOpenElement _(doc, "attrspat", attrs);
|
||||
foreach (Formals::Formals_::iterator, i, v.lambda.fun->formals->formals)
|
||||
doc.writeEmptyElement("attr", singletonAttrs("name", i->name));
|
||||
} else
|
||||
doc.writeEmptyElement("varpat", singletonAttrs("name", v.lambda.fun->arg));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
doc.writeEmptyElement("unevaluated");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||
Value & v, std::ostream & out, PathSet & context)
|
||||
{
|
||||
XMLWriter doc(true, out);
|
||||
XMLOpenElement root(doc, "expr");
|
||||
PathSet drvsSeen;
|
||||
printValueAsXML(state, strict, location, v, doc, context, drvsSeen);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifndef __VALUE_TO_XML_H
|
||||
#define __VALUE_TO_XML_H
|
||||
|
||||
#include "nixexpr.hh"
|
||||
#include "eval.hh"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace nix {
|
||||
|
||||
void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||
Value & v, std::ostream & out, PathSet & context);
|
||||
|
||||
}
|
||||
|
||||
#endif /* !__VALUE_TO_XML_H */
|
||||
@@ -1,10 +1,6 @@
|
||||
pkglib_LTLIBRARIES = libmain.la
|
||||
|
||||
libmain_la_SOURCES = shared.cc
|
||||
|
||||
libmain_la_LIBADD = ../libstore/libstore.la @boehmgc_lib@
|
||||
|
||||
pkginclude_HEADERS = shared.hh
|
||||
libmain_la_SOURCES = shared.cc shared.hh
|
||||
|
||||
AM_CXXFLAGS = \
|
||||
-DNIX_STORE_DIR=\"$(storedir)\" \
|
||||
@@ -15,5 +11,5 @@ AM_CXXFLAGS = \
|
||||
-DNIX_LIBEXEC_DIR=\"$(libexecdir)\" \
|
||||
-DNIX_BIN_DIR=\"$(bindir)\" \
|
||||
-DNIX_VERSION=\"$(VERSION)\" \
|
||||
-I$(srcdir)/.. -I$(srcdir)/../libutil \
|
||||
-I$(srcdir)/.. ${aterm_include} -I$(srcdir)/../libutil \
|
||||
-I$(srcdir)/../libstore
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
#include <gc/gc.h>
|
||||
#endif
|
||||
#include <aterm2.h>
|
||||
|
||||
|
||||
namespace nix {
|
||||
@@ -54,26 +52,25 @@ void printGCWarning()
|
||||
|
||||
void printMissing(const PathSet & paths)
|
||||
{
|
||||
unsigned long long downloadSize, narSize;
|
||||
unsigned long long downloadSize;
|
||||
PathSet willBuild, willSubstitute, unknown;
|
||||
queryMissing(paths, willBuild, willSubstitute, unknown, downloadSize, narSize);
|
||||
queryMissing(paths, willBuild, willSubstitute, unknown, downloadSize);
|
||||
|
||||
if (!willBuild.empty()) {
|
||||
printMsg(lvlInfo, format("these derivations will be built:"));
|
||||
printMsg(lvlInfo, format("the following derivations will be built:"));
|
||||
foreach (PathSet::iterator, i, willBuild)
|
||||
printMsg(lvlInfo, format(" %1%") % *i);
|
||||
}
|
||||
|
||||
if (!willSubstitute.empty()) {
|
||||
printMsg(lvlInfo, format("these paths will be downloaded/copied (%.2f MiB download, %.2f MiB unpacked):")
|
||||
% (downloadSize / (1024.0 * 1024.0))
|
||||
% (narSize / (1024.0 * 1024.0)));
|
||||
printMsg(lvlInfo, format("the following paths will be downloaded/copied (%.2f MiB):") %
|
||||
(downloadSize / (1024.0 * 1024.0)));
|
||||
foreach (PathSet::iterator, i, willSubstitute)
|
||||
printMsg(lvlInfo, format(" %1%") % *i);
|
||||
}
|
||||
|
||||
if (!unknown.empty()) {
|
||||
printMsg(lvlInfo, format("don't know how to build these paths%1%:")
|
||||
printMsg(lvlInfo, format("don't know how to build the following paths%1%:")
|
||||
% (readOnlyMode ? " (may be caused by read-only store access)" : ""));
|
||||
foreach (PathSet::iterator, i, unknown)
|
||||
printMsg(lvlInfo, format(" %1%") % *i);
|
||||
@@ -90,6 +87,21 @@ static void setLogType(string lt)
|
||||
}
|
||||
|
||||
|
||||
unsigned long long getIntArg(const string & opt,
|
||||
Strings::iterator & i, const Strings::iterator & end)
|
||||
{
|
||||
++i;
|
||||
if (i == end) throw UsageError(format("`%1%' requires an argument") % opt);
|
||||
long long n;
|
||||
if (!string2Int(*i, n) || n < 0)
|
||||
throw UsageError(format("`%1%' requires a non-negative integer") % opt);
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
void initDerivationsHelpers();
|
||||
|
||||
|
||||
static void closeStore()
|
||||
{
|
||||
try {
|
||||
@@ -113,9 +125,6 @@ RemoveTempRoots::~RemoveTempRoots()
|
||||
}
|
||||
|
||||
|
||||
static bool showTrace = false;
|
||||
|
||||
|
||||
/* Initialize and reorder arguments, then call the actual argument
|
||||
processor. */
|
||||
static void initAndRun(int argc, char * * argv)
|
||||
@@ -129,6 +138,7 @@ static void initAndRun(int argc, char * * argv)
|
||||
nixConfDir = canonPath(getEnv("NIX_CONF_DIR", NIX_CONF_DIR));
|
||||
nixLibexecDir = canonPath(getEnv("NIX_LIBEXEC_DIR", NIX_LIBEXEC_DIR));
|
||||
nixBinDir = canonPath(getEnv("NIX_BIN_DIR", NIX_BIN_DIR));
|
||||
nixChrootsDir = canonPath(getEnv("NIX_CHROOTS_DIR", nixStateDir + "/chroots"));
|
||||
|
||||
string subs = getEnv("NIX_SUBSTITUTERS", "default");
|
||||
if (subs == "default") {
|
||||
@@ -140,33 +150,26 @@ static void initAndRun(int argc, char * * argv)
|
||||
/* Get some settings from the configuration file. */
|
||||
thisSystem = querySetting("system", SYSTEM);
|
||||
maxBuildJobs = queryIntSetting("build-max-jobs", 1);
|
||||
buildCores = queryIntSetting("build-cores", 1);
|
||||
maxSilentTime = queryIntSetting("build-max-silent-time", 0);
|
||||
|
||||
/* Catch SIGINT. */
|
||||
struct sigaction act;
|
||||
struct sigaction act, oact;
|
||||
act.sa_handler = sigintHandler;
|
||||
sigfillset(&act.sa_mask);
|
||||
act.sa_flags = 0;
|
||||
if (sigaction(SIGINT, &act, 0))
|
||||
if (sigaction(SIGINT, &act, &oact))
|
||||
throw SysError("installing handler for SIGINT");
|
||||
if (sigaction(SIGTERM, &act, 0))
|
||||
if (sigaction(SIGTERM, &act, &oact))
|
||||
throw SysError("installing handler for SIGTERM");
|
||||
if (sigaction(SIGHUP, &act, 0))
|
||||
if (sigaction(SIGHUP, &act, &oact))
|
||||
throw SysError("installing handler for SIGHUP");
|
||||
|
||||
/* Ignore SIGPIPE. */
|
||||
act.sa_handler = SIG_IGN;
|
||||
act.sa_flags = 0;
|
||||
if (sigaction(SIGPIPE, &act, 0))
|
||||
if (sigaction(SIGPIPE, &act, &oact))
|
||||
throw SysError("ignoring SIGPIPE");
|
||||
|
||||
/* Reset SIGCHLD to its default. */
|
||||
act.sa_handler = SIG_DFL;
|
||||
act.sa_flags = 0;
|
||||
if (sigaction(SIGCHLD, &act, 0))
|
||||
throw SysError("resetting SIGCHLD");
|
||||
|
||||
/* There is no privacy in the Nix system ;-) At least not for
|
||||
now. In particular, store objects should be readable by
|
||||
everybody. This prevents nasty surprises when using a shared
|
||||
@@ -177,6 +180,9 @@ static void initAndRun(int argc, char * * argv)
|
||||
string lt = getEnv("NIX_LOG_TYPE");
|
||||
if (lt != "") setLogType(lt);
|
||||
|
||||
/* ATerm stuff. !!! find a better place to put this */
|
||||
initDerivationsHelpers();
|
||||
|
||||
/* Put the arguments in a vector. */
|
||||
Strings args, remaining;
|
||||
while (argc--) args.push_back(*argv++);
|
||||
@@ -187,7 +193,7 @@ static void initAndRun(int argc, char * * argv)
|
||||
for (Strings::iterator i = args.begin(); i != args.end(); ++i) {
|
||||
string arg = *i;
|
||||
if (string(arg, 0, 4) == "-at-") ;
|
||||
else if (arg.length() > 2 && arg[0] == '-' && arg[1] != '-' && !isdigit(arg[1])) {
|
||||
else if (arg.length() > 2 && arg[0] == '-' && arg[1] != '-') {
|
||||
for (unsigned int j = 1; j < arg.length(); j++)
|
||||
if (isalpha(arg[j]))
|
||||
remaining.push_back((string) "-" + arg[j]);
|
||||
@@ -201,16 +207,17 @@ static void initAndRun(int argc, char * * argv)
|
||||
remaining.clear();
|
||||
|
||||
/* Process default options. */
|
||||
int verbosityDelta = 0;
|
||||
for (Strings::iterator i = args.begin(); i != args.end(); ++i) {
|
||||
string arg = *i;
|
||||
if (arg == "--verbose" || arg == "-v") verbosityDelta++;
|
||||
else if (arg == "--quiet") verbosityDelta--;
|
||||
if (arg == "--verbose" || arg == "-v")
|
||||
verbosity = (Verbosity) ((int) verbosity + 1);
|
||||
else if (arg == "--log-type") {
|
||||
++i;
|
||||
if (i == args.end()) throw UsageError("`--log-type' requires an argument");
|
||||
setLogType(*i);
|
||||
}
|
||||
else if (arg == "--build-output" || arg == "-B")
|
||||
; /* !!! obsolete - remove eventually */
|
||||
else if (arg == "--no-build-output" || arg == "-Q")
|
||||
buildVerbosity = lvlVomit;
|
||||
else if (arg == "--print-build-trace")
|
||||
@@ -230,30 +237,16 @@ static void initAndRun(int argc, char * * argv)
|
||||
else if (arg == "--fallback")
|
||||
tryFallback = true;
|
||||
else if (arg == "--max-jobs" || arg == "-j")
|
||||
maxBuildJobs = getIntArg<unsigned int>(arg, i, args.end());
|
||||
else if (arg == "--cores")
|
||||
buildCores = getIntArg<unsigned int>(arg, i, args.end());
|
||||
maxBuildJobs = getIntArg(arg, i, args.end());
|
||||
else if (arg == "--readonly-mode")
|
||||
readOnlyMode = true;
|
||||
else if (arg == "--max-silent-time")
|
||||
maxSilentTime = getIntArg<unsigned int>(arg, i, args.end());
|
||||
maxSilentTime = getIntArg(arg, i, args.end());
|
||||
else if (arg == "--no-build-hook")
|
||||
useBuildHook = false;
|
||||
else if (arg == "--show-trace")
|
||||
showTrace = true;
|
||||
else if (arg == "--option") {
|
||||
++i; if (i == args.end()) throw UsageError("`--option' requires two arguments");
|
||||
string name = *i;
|
||||
++i; if (i == args.end()) throw UsageError("`--option' requires two arguments");
|
||||
string value = *i;
|
||||
overrideSetting(name, tokenizeString(value));
|
||||
}
|
||||
else remaining.push_back(arg);
|
||||
}
|
||||
|
||||
verbosityDelta += queryIntSetting("verbosity", lvlInfo);
|
||||
verbosity = (Verbosity) (verbosityDelta < 0 ? 0 : verbosityDelta);
|
||||
|
||||
/* Automatically clean up the temporary roots file when we
|
||||
exit. */
|
||||
RemoveTempRoots removeTempRoots __attribute__((unused));
|
||||
@@ -321,14 +314,6 @@ static void setuidInit()
|
||||
}
|
||||
|
||||
|
||||
/* Called when the Boehm GC runs out of memory. */
|
||||
static void * oomHandler(size_t requested)
|
||||
{
|
||||
/* Convert this to a proper C++ exception. */
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -343,6 +328,10 @@ int main(int argc, char * * argv)
|
||||
if (argc == 0) abort();
|
||||
setuidInit();
|
||||
|
||||
/* ATerm setup. */
|
||||
ATerm bottomOfStack;
|
||||
ATinit(argc, argv, &bottomOfStack);
|
||||
|
||||
/* Turn on buffering for cerr. */
|
||||
#if HAVE_PUBSETBUF
|
||||
std::cerr.rdbuf()->pubsetbuf(buf, sizeof(buf));
|
||||
@@ -350,26 +339,6 @@ int main(int argc, char * * argv)
|
||||
|
||||
std::ios::sync_with_stdio(false);
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
/* Initialise the Boehm garbage collector. This isn't necessary
|
||||
on most platforms, but for portability we do it anyway. */
|
||||
GC_INIT();
|
||||
|
||||
GC_oom_fn = oomHandler;
|
||||
|
||||
/* Set the initial heap size to something fairly big (384 MiB) so
|
||||
that in most cases we don't need to garbage collect at all.
|
||||
(Collection has a fairly significant overhead, some.) The heap
|
||||
size can be overriden through libgc's GC_INITIAL_HEAP_SIZE
|
||||
environment variable. We should probably also provide a
|
||||
nix.conf setting for this. Note that GC_expand_hp() causes a
|
||||
lot of virtual, but not physical (resident) memory to be
|
||||
allocated. This might be a problem on systems that don't
|
||||
overcommit. */
|
||||
if (!getenv("GC_INITIAL_HEAP_SIZE"))
|
||||
GC_expand_hp(384 * 1024 * 1024);
|
||||
#endif
|
||||
|
||||
try {
|
||||
try {
|
||||
initAndRun(argc, argv);
|
||||
@@ -390,10 +359,8 @@ int main(int argc, char * * argv)
|
||||
% e.what() % programId);
|
||||
return 1;
|
||||
} catch (BaseError & e) {
|
||||
printMsg(lvlError, format("error: %1%%2%") % (showTrace ? e.prefix() : "") % e.msg());
|
||||
if (e.prefix() != "" && !showTrace)
|
||||
printMsg(lvlError, "(use `--show-trace' to show detailed location information)");
|
||||
return e.status;
|
||||
printMsg(lvlError, format("error: %1%") % e.msg());
|
||||
return 1;
|
||||
} catch (std::exception & e) {
|
||||
printMsg(lvlError, format("error: %1%") % e.what());
|
||||
return 1;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef __SHARED_H
|
||||
#define __SHARED_H
|
||||
|
||||
#include "util.hh"
|
||||
#include "types.hh"
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
@@ -22,30 +22,22 @@ extern std::string programId;
|
||||
|
||||
namespace nix {
|
||||
|
||||
MakeError(UsageError, nix::Error);
|
||||
|
||||
/* Ugh. No better place to put this. */
|
||||
Path makeRootName(const Path & gcRoot, int & counter);
|
||||
void printGCWarning();
|
||||
|
||||
void printMissing(const PathSet & paths);
|
||||
|
||||
template<class N> N getIntArg(const string & opt,
|
||||
Strings::iterator & i, const Strings::iterator & end)
|
||||
{
|
||||
++i;
|
||||
if (i == end) throw UsageError(format("`%1%' requires an argument") % opt);
|
||||
N n;
|
||||
if (!string2Int(*i, n))
|
||||
throw UsageError(format("`%1%' requires an integer argument") % opt);
|
||||
return n;
|
||||
}
|
||||
unsigned long long getIntArg(const string & opt,
|
||||
Strings::iterator & i, const Strings::iterator & end);
|
||||
|
||||
/* Whether we're running setuid. */
|
||||
extern bool setuidMode;
|
||||
|
||||
extern volatile ::sig_atomic_t blockInt;
|
||||
|
||||
MakeError(UsageError, nix::Error);
|
||||
|
||||
struct RemoveTempRoots
|
||||
{
|
||||
~RemoveTempRoots();
|
||||
|
||||
@@ -2,22 +2,22 @@ pkglib_LTLIBRARIES = libstore.la
|
||||
|
||||
libstore_la_SOURCES = \
|
||||
store-api.cc local-store.cc remote-store.cc derivations.cc build.cc misc.cc \
|
||||
globals.cc references.cc pathlocks.cc gc.cc \
|
||||
globals.cc db.cc references.cc pathlocks.cc gc.cc upgrade-schema.cc \
|
||||
optimise-store.cc
|
||||
|
||||
pkginclude_HEADERS = \
|
||||
store-api.hh local-store.hh remote-store.hh derivations.hh misc.hh \
|
||||
globals.hh references.hh pathlocks.hh \
|
||||
globals.hh db.hh references.hh pathlocks.hh \
|
||||
worker-protocol.hh
|
||||
|
||||
libstore_la_LIBADD = ../libutil/libutil.la ../boost/format/libformat.la ${aterm_lib} ${sqlite_lib}
|
||||
libstore_la_LIBADD = ../libutil/libutil.la ../boost/format/libformat.la
|
||||
|
||||
EXTRA_DIST = schema.sql
|
||||
BUILT_SOURCES = derivations-ast.cc derivations-ast.hh
|
||||
|
||||
EXTRA_DIST = derivations-ast.def derivations-ast.cc
|
||||
|
||||
AM_CXXFLAGS = -Wall \
|
||||
${sqlite_include} -I$(srcdir)/.. -I$(srcdir)/../libutil
|
||||
-I$(srcdir)/.. ${bdb_include} ${aterm_include} -I$(srcdir)/../libutil
|
||||
|
||||
local-store.lo: schema.sql.hh
|
||||
|
||||
%.sql.hh: %.sql
|
||||
../bin2c/bin2c schema < $< > $@ || (rm $@ && exit 1)
|
||||
derivations-ast.cc derivations-ast.hh: ../aterm-helper.pl derivations-ast.def
|
||||
$(perl) $(srcdir)/../aterm-helper.pl derivations-ast.hh derivations-ast.cc < $(srcdir)/derivations-ast.def
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
474
src/libstore/db.cc
Normal file
474
src/libstore/db.cc
Normal file
@@ -0,0 +1,474 @@
|
||||
#include "config.h"
|
||||
|
||||
#ifdef OLD_DB_COMPAT
|
||||
|
||||
#include "db.hh"
|
||||
#include "util.hh"
|
||||
#include "pathlocks.hh"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <db_cxx.h>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
/* Wrapper class to ensure proper destruction. */
|
||||
class DestroyDbc
|
||||
{
|
||||
Dbc * dbc;
|
||||
public:
|
||||
DestroyDbc(Dbc * _dbc) : dbc(_dbc) { }
|
||||
~DestroyDbc() { dbc->close(); /* close() frees dbc */ }
|
||||
};
|
||||
|
||||
|
||||
class DestroyDbEnv
|
||||
{
|
||||
DbEnv * dbenv;
|
||||
public:
|
||||
DestroyDbEnv(DbEnv * _dbenv) : dbenv(_dbenv) { }
|
||||
~DestroyDbEnv() {
|
||||
if (dbenv) {
|
||||
if (dbenv->get_DB_ENV()) dbenv->close(0);
|
||||
delete dbenv;
|
||||
}
|
||||
}
|
||||
void release() { dbenv = 0; };
|
||||
};
|
||||
|
||||
|
||||
static void rethrow(DbException & e)
|
||||
{
|
||||
throw Error(e.what());
|
||||
}
|
||||
|
||||
|
||||
Transaction::Transaction()
|
||||
: txn(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Transaction::Transaction(Database & db)
|
||||
: txn(0)
|
||||
{
|
||||
begin(db);
|
||||
}
|
||||
|
||||
|
||||
Transaction::~Transaction()
|
||||
{
|
||||
if (txn) abort();
|
||||
}
|
||||
|
||||
|
||||
void Transaction::begin(Database & db)
|
||||
{
|
||||
assert(txn == 0);
|
||||
db.requireEnv();
|
||||
try {
|
||||
db.env->txn_begin(0, &txn, 0);
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
}
|
||||
|
||||
|
||||
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 Transaction::moveTo(Transaction & t)
|
||||
{
|
||||
if (t.txn) throw Error("target txn already exists");
|
||||
t.txn = txn;
|
||||
txn = 0;
|
||||
}
|
||||
|
||||
|
||||
void Database::requireEnv()
|
||||
{
|
||||
checkInterrupt();
|
||||
if (!env) throw Error("database environment is not open "
|
||||
"(maybe you don't have sufficient permission?)");
|
||||
}
|
||||
|
||||
|
||||
Db * Database::getDb(TableId table)
|
||||
{
|
||||
if (table == 0)
|
||||
throw Error("database table is not open "
|
||||
"(maybe you don't have sufficient permission?)");
|
||||
std::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()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
void openEnv(DbEnv * & env, const string & path, u_int32_t flags)
|
||||
{
|
||||
try {
|
||||
createDirs(path);
|
||||
} catch (SysError & e) {
|
||||
if (e.errNo == EPERM || e.errNo == EACCES)
|
||||
throw DbNoPermission(format("cannot create the Nix database in `%1%'") % path);
|
||||
else
|
||||
throw;
|
||||
}
|
||||
|
||||
try {
|
||||
env->open(path.c_str(),
|
||||
DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN |
|
||||
DB_CREATE | flags,
|
||||
0666);
|
||||
} catch (DbException & e) {
|
||||
printMsg(lvlError, format("environment open failed: %1%") % e.what());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int my_fsync(int fd)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void errorPrinter(const DbEnv * env, const char * errpfx, const char * msg)
|
||||
{
|
||||
printMsg(lvlError, format("Berkeley DB error: %1%") % msg);
|
||||
}
|
||||
|
||||
|
||||
static void messagePrinter(const DbEnv * env, const char * msg)
|
||||
{
|
||||
printMsg(lvlError, format("Berkeley DB message: %1%") % msg);
|
||||
}
|
||||
|
||||
|
||||
void Database::open2(const string & path, bool removeOldEnv)
|
||||
{
|
||||
if (env) throw Error(format("environment already open"));
|
||||
|
||||
debug(format("opening database environment"));
|
||||
|
||||
|
||||
/* Create the database environment object. */
|
||||
DbEnv * env = new DbEnv(0);
|
||||
DestroyDbEnv deleteEnv(env);
|
||||
|
||||
env->set_errcall(errorPrinter);
|
||||
env->set_msgcall(messagePrinter);
|
||||
if (getEnv("NIX_DEBUG_DB_REGISTER") == "1")
|
||||
env->set_verbose(DB_VERB_REGISTER, 1);
|
||||
env->set_verbose(DB_VERB_RECOVERY, 1);
|
||||
|
||||
/* Smaller log files. */
|
||||
env->set_lg_bsize(32 * 1024); /* default */
|
||||
env->set_lg_max(256 * 1024); /* must be > 4 * lg_bsize */
|
||||
|
||||
/* Write the log, but don't sync. This protects transactions
|
||||
against application crashes, but if the system crashes, some
|
||||
transactions may be undone. An acceptable risk, I think. */
|
||||
env->set_flags(DB_TXN_WRITE_NOSYNC | DB_LOG_AUTOREMOVE, 1);
|
||||
|
||||
/* Increase the locking limits. If you ever get `Dbc::get: Cannot
|
||||
allocate memory' or similar, especially while running
|
||||
`nix-store --verify', just increase the following number, then
|
||||
run db_recover on the database to remove the existing DB
|
||||
environment (since changes only take effect on new
|
||||
environments). */
|
||||
env->set_lk_max_locks(10000);
|
||||
env->set_lk_max_lockers(10000);
|
||||
env->set_lk_max_objects(10000);
|
||||
env->set_lk_detect(DB_LOCK_DEFAULT);
|
||||
|
||||
/* Dangerous, probably, but from the docs it *seems* that BDB
|
||||
shouldn't sync when DB_TXN_WRITE_NOSYNC is used, but it still
|
||||
fsync()s sometimes. */
|
||||
db_env_set_func_fsync(my_fsync);
|
||||
|
||||
|
||||
if (removeOldEnv) {
|
||||
printMsg(lvlError, "removing old Berkeley DB database environment...");
|
||||
env->remove(path.c_str(), DB_FORCE);
|
||||
return;
|
||||
}
|
||||
|
||||
openEnv(env, path, DB_REGISTER | DB_RECOVER);
|
||||
|
||||
deleteEnv.release();
|
||||
this->env = env;
|
||||
}
|
||||
|
||||
|
||||
void Database::open(const string & path)
|
||||
{
|
||||
try {
|
||||
|
||||
open2(path, false);
|
||||
|
||||
} catch (DbException e) {
|
||||
|
||||
if (e.get_errno() == DB_VERSION_MISMATCH) {
|
||||
/* Remove the environment while we are holding the global
|
||||
lock. If things go wrong there, we bail out.
|
||||
!!! argh, we abolished the global lock :-( */
|
||||
open2(path, true);
|
||||
|
||||
/* Try again. */
|
||||
open2(path, false);
|
||||
|
||||
/* Force a checkpoint, as per the BDB docs. */
|
||||
env->txn_checkpoint(DB_FORCE, 0, 0);
|
||||
|
||||
printMsg(lvlError, "database succesfully upgraded to new version");
|
||||
}
|
||||
|
||||
#if 0
|
||||
else if (e.get_errno() == DB_RUNRECOVERY) {
|
||||
/* If recovery is needed, do it. */
|
||||
printMsg(lvlError, "running recovery...");
|
||||
open2(path, false, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
else
|
||||
rethrow(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Database::close()
|
||||
{
|
||||
if (!env) return;
|
||||
|
||||
/* Close the database environment. */
|
||||
debug(format("closing database environment"));
|
||||
|
||||
try {
|
||||
|
||||
for (std::map<TableId, Db *>::iterator i = tables.begin();
|
||||
i != tables.end(); )
|
||||
{
|
||||
std::map<TableId, Db *>::iterator j = i;
|
||||
++j;
|
||||
closeTable(i->first);
|
||||
i = j;
|
||||
}
|
||||
|
||||
/* Do a checkpoint every 128 kilobytes, or every 5 minutes. */
|
||||
env->txn_checkpoint(128, 5, 0);
|
||||
|
||||
env->close(0);
|
||||
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
|
||||
delete env;
|
||||
|
||||
env = 0;
|
||||
}
|
||||
|
||||
|
||||
TableId Database::openTable(const string & tableName, bool sorted)
|
||||
{
|
||||
requireEnv();
|
||||
TableId table = nextId++;
|
||||
|
||||
try {
|
||||
|
||||
Db * db = new Db(env, 0);
|
||||
|
||||
try {
|
||||
db->open(0, tableName.c_str(), 0,
|
||||
sorted ? DB_BTREE : DB_HASH,
|
||||
DB_CREATE | DB_AUTO_COMMIT, 0666);
|
||||
} catch (...) {
|
||||
delete db;
|
||||
throw;
|
||||
}
|
||||
|
||||
tables[table] = db;
|
||||
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
void Database::closeTable(TableId table)
|
||||
{
|
||||
try {
|
||||
Db * db = getDb(table);
|
||||
db->close(DB_NOSYNC);
|
||||
delete db;
|
||||
tables.erase(table);
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
}
|
||||
|
||||
|
||||
void Database::deleteTable(const string & table)
|
||||
{
|
||||
try {
|
||||
env->dbremove(0, table.c_str(), 0, DB_AUTO_COMMIT);
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
}
|
||||
|
||||
|
||||
bool Database::queryString(const Transaction & txn, TableId table,
|
||||
const string & key, string & data)
|
||||
{
|
||||
checkInterrupt();
|
||||
|
||||
try {
|
||||
Db * db = getDb(table);
|
||||
|
||||
Dbt kt((void *) key.c_str(), key.length());
|
||||
Dbt dt;
|
||||
|
||||
int err = db->get(txn.txn, &kt, &dt, 0);
|
||||
if (err) return false;
|
||||
|
||||
if (!dt.get_data())
|
||||
data = "";
|
||||
else
|
||||
data = string((char *) dt.get_data(), dt.get_size());
|
||||
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool Database::queryStrings(const Transaction & txn, TableId table,
|
||||
const string & key, Strings & data)
|
||||
{
|
||||
string d;
|
||||
if (!queryString(txn, table, key, d))
|
||||
return false;
|
||||
data = unpackStrings(d);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Database::setString(const Transaction & txn, TableId table,
|
||||
const string & key, const string & data)
|
||||
{
|
||||
checkInterrupt();
|
||||
try {
|
||||
Db * db = getDb(table);
|
||||
Dbt kt((void *) key.c_str(), key.length());
|
||||
Dbt dt((void *) data.c_str(), data.length());
|
||||
db->put(txn.txn, &kt, &dt, 0);
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
}
|
||||
|
||||
|
||||
void Database::setStrings(const Transaction & txn, TableId table,
|
||||
const string & key, const Strings & data, bool deleteEmpty)
|
||||
{
|
||||
if (deleteEmpty && data.size() == 0)
|
||||
delPair(txn, table, key);
|
||||
else
|
||||
setString(txn, table, key, packStrings(data));
|
||||
}
|
||||
|
||||
|
||||
void Database::delPair(const Transaction & txn, TableId table,
|
||||
const string & key)
|
||||
{
|
||||
checkInterrupt();
|
||||
try {
|
||||
Db * db = getDb(table);
|
||||
Dbt kt((void *) key.c_str(), key.length());
|
||||
db->del(txn.txn, &kt, 0);
|
||||
/* Non-existence of a pair with the given key is not an
|
||||
error. */
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
}
|
||||
|
||||
|
||||
void Database::enumTable(const Transaction & txn, TableId table,
|
||||
Strings & keys, const string & keyPrefix)
|
||||
{
|
||||
try {
|
||||
Db * db = getDb(table);
|
||||
|
||||
Dbc * dbc;
|
||||
db->cursor(txn.txn, &dbc, 0);
|
||||
DestroyDbc destroyDbc(dbc);
|
||||
|
||||
Dbt kt, dt;
|
||||
u_int32_t flags = DB_NEXT;
|
||||
|
||||
if (!keyPrefix.empty()) {
|
||||
flags = DB_SET_RANGE;
|
||||
kt = Dbt((void *) keyPrefix.c_str(), keyPrefix.size());
|
||||
}
|
||||
|
||||
while (dbc->get(&kt, &dt, flags) != DB_NOTFOUND) {
|
||||
checkInterrupt();
|
||||
string data((char *) kt.get_data(), kt.get_size());
|
||||
if (!keyPrefix.empty() &&
|
||||
string(data, 0, keyPrefix.size()) != keyPrefix)
|
||||
break;
|
||||
keys.push_back(data);
|
||||
flags = DB_NEXT;
|
||||
}
|
||||
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
}
|
||||
|
||||
|
||||
void Database::clearTable(const Transaction & txn, TableId table)
|
||||
{
|
||||
try {
|
||||
Db * db = getDb(table);
|
||||
u_int32_t count;
|
||||
db->truncate(txn.txn, &count, 0);
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
107
src/libstore/db.hh
Normal file
107
src/libstore/db.hh
Normal file
@@ -0,0 +1,107 @@
|
||||
#ifndef __DB_H
|
||||
#define __DB_H
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
#include <map>
|
||||
|
||||
|
||||
/* Defined externally. */
|
||||
class DbTxn;
|
||||
class DbEnv;
|
||||
class Db;
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
class Database;
|
||||
|
||||
|
||||
class Transaction
|
||||
{
|
||||
friend class Database;
|
||||
|
||||
private:
|
||||
DbTxn * txn;
|
||||
|
||||
public:
|
||||
Transaction();
|
||||
Transaction(Database & _db);
|
||||
~Transaction();
|
||||
|
||||
void begin(Database & db);
|
||||
void abort();
|
||||
void commit();
|
||||
|
||||
void moveTo(Transaction & t);
|
||||
};
|
||||
|
||||
|
||||
#define noTxn Transaction()
|
||||
|
||||
|
||||
typedef unsigned int TableId; /* table handles */
|
||||
|
||||
|
||||
class Database
|
||||
{
|
||||
friend class Transaction;
|
||||
|
||||
private:
|
||||
DbEnv * env;
|
||||
|
||||
TableId nextId;
|
||||
std::map<TableId, Db *> tables;
|
||||
|
||||
void requireEnv();
|
||||
|
||||
Db * getDb(TableId table);
|
||||
|
||||
void open2(const string & path, bool removeOldEnv);
|
||||
|
||||
public:
|
||||
Database();
|
||||
~Database();
|
||||
|
||||
void open(const string & path);
|
||||
void close();
|
||||
|
||||
TableId openTable(const string & table, bool sorted = false);
|
||||
void closeTable(TableId table);
|
||||
void deleteTable(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,
|
||||
bool deleteEmpty = true);
|
||||
|
||||
void delPair(const Transaction & txn, TableId table,
|
||||
const string & key);
|
||||
|
||||
void enumTable(const Transaction & txn, TableId table,
|
||||
Strings & keys, const string & keyPrefix = "");
|
||||
|
||||
void clearTable(const Transaction & txn, TableId table);
|
||||
};
|
||||
|
||||
|
||||
class DbNoPermission : public Error
|
||||
{
|
||||
public:
|
||||
DbNoPermission(const format & f) : Error(f) { };
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif /* !__DB_H */
|
||||
10
src/libstore/derivations-ast.def
Normal file
10
src/libstore/derivations-ast.def
Normal file
@@ -0,0 +1,10 @@
|
||||
init initDerivationsHelpers
|
||||
|
||||
Derive | ATermList ATermList ATermList string string ATermList ATermList | ATerm |
|
||||
|
||||
| string string | ATerm | EnvBinding |
|
||||
| string ATermList | ATerm | DerivationInput |
|
||||
| string string string string | ATerm | DerivationOutput |
|
||||
|
||||
Closure | ATermList ATermList | ATerm | OldClosure |
|
||||
| string ATermList | ATerm | OldClosureElem |
|
||||
@@ -1,167 +1,164 @@
|
||||
#include "derivations.hh"
|
||||
#include "store-api.hh"
|
||||
#include "aterm.hh"
|
||||
#include "globals.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#include "derivations-ast.hh"
|
||||
#include "derivations-ast.cc"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
Hash hashTerm(ATerm t)
|
||||
{
|
||||
return hashString(htSHA256, atPrint(t));
|
||||
}
|
||||
|
||||
|
||||
Path writeDerivation(const Derivation & drv, const string & name)
|
||||
{
|
||||
PathSet references;
|
||||
references.insert(drv.inputSrcs.begin(), drv.inputSrcs.end());
|
||||
foreach (DerivationInputs::const_iterator, i, drv.inputDrvs)
|
||||
for (DerivationInputs::const_iterator i = drv.inputDrvs.begin();
|
||||
i != drv.inputDrvs.end(); ++i)
|
||||
references.insert(i->first);
|
||||
/* Note that the outputs of a derivation are *not* references
|
||||
(that can be missing (of course) and should not necessarily be
|
||||
held during a garbage collection). */
|
||||
string suffix = name + drvExtension;
|
||||
string contents = unparseDerivation(drv);
|
||||
string contents = atPrint(unparseDerivation(drv));
|
||||
return readOnlyMode
|
||||
? computeStorePathForText(suffix, contents, references)
|
||||
: store->addTextToStore(suffix, contents, references);
|
||||
}
|
||||
|
||||
|
||||
static Path parsePath(std::istream & str)
|
||||
static void checkPath(const string & s)
|
||||
{
|
||||
string s = parseString(str);
|
||||
if (s.size() == 0 || s[0] != '/')
|
||||
throw Error(format("bad path `%1%' in derivation") % s);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
static StringSet parseStrings(std::istream & str, bool arePaths)
|
||||
static void parseStrings(ATermList paths, StringSet & out, bool arePaths)
|
||||
{
|
||||
StringSet res;
|
||||
while (!endOfList(str))
|
||||
res.insert(arePaths ? parsePath(str) : parseString(str));
|
||||
return res;
|
||||
for (ATermIterator i(paths); i; ++i) {
|
||||
if (ATgetType(*i) != AT_APPL)
|
||||
throw badTerm("not a path", *i);
|
||||
string s = aterm2String(*i);
|
||||
if (arePaths) checkPath(s);
|
||||
out.insert(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Derivation parseDerivation(const string & s)
|
||||
|
||||
/* Shut up warnings. */
|
||||
void throwBadDrv(ATerm t) __attribute__ ((noreturn));
|
||||
|
||||
void throwBadDrv(ATerm t)
|
||||
{
|
||||
throw badTerm("not a valid derivation", t);
|
||||
}
|
||||
|
||||
|
||||
Derivation parseDerivation(ATerm t)
|
||||
{
|
||||
Derivation drv;
|
||||
std::istringstream str(s);
|
||||
expect(str, "Derive([");
|
||||
ATermList outs, inDrvs, inSrcs, args, bnds;
|
||||
ATerm builder, platform;
|
||||
|
||||
/* Parse the list of outputs. */
|
||||
while (!endOfList(str)) {
|
||||
if (!matchDerive(t, outs, inDrvs, inSrcs, platform, builder, args, bnds))
|
||||
throwBadDrv(t);
|
||||
|
||||
for (ATermIterator i(outs); i; ++i) {
|
||||
ATerm id, path, hashAlgo, hash;
|
||||
if (!matchDerivationOutput(*i, id, path, hashAlgo, hash))
|
||||
throwBadDrv(t);
|
||||
DerivationOutput out;
|
||||
expect(str, "("); string id = parseString(str);
|
||||
expect(str, ","); out.path = parsePath(str);
|
||||
expect(str, ","); out.hashAlgo = parseString(str);
|
||||
expect(str, ","); out.hash = parseString(str);
|
||||
expect(str, ")");
|
||||
drv.outputs[id] = out;
|
||||
out.path = aterm2String(path);
|
||||
checkPath(out.path);
|
||||
out.hashAlgo = aterm2String(hashAlgo);
|
||||
out.hash = aterm2String(hash);
|
||||
drv.outputs[aterm2String(id)] = out;
|
||||
}
|
||||
|
||||
/* Parse the list of input derivations. */
|
||||
expect(str, ",[");
|
||||
while (!endOfList(str)) {
|
||||
expect(str, "(");
|
||||
Path drvPath = parsePath(str);
|
||||
expect(str, ",[");
|
||||
drv.inputDrvs[drvPath] = parseStrings(str, false);
|
||||
expect(str, ")");
|
||||
}
|
||||
|
||||
expect(str, ",["); drv.inputSrcs = parseStrings(str, true);
|
||||
expect(str, ","); drv.platform = parseString(str);
|
||||
expect(str, ","); drv.builder = parseString(str);
|
||||
|
||||
/* Parse the builder arguments. */
|
||||
expect(str, ",[");
|
||||
while (!endOfList(str))
|
||||
drv.args.push_back(parseString(str));
|
||||
|
||||
/* Parse the environment variables. */
|
||||
expect(str, ",[");
|
||||
while (!endOfList(str)) {
|
||||
expect(str, "("); string name = parseString(str);
|
||||
expect(str, ","); string value = parseString(str);
|
||||
expect(str, ")");
|
||||
drv.env[name] = value;
|
||||
for (ATermIterator i(inDrvs); i; ++i) {
|
||||
ATerm drvPath;
|
||||
ATermList ids;
|
||||
if (!matchDerivationInput(*i, drvPath, ids))
|
||||
throwBadDrv(t);
|
||||
Path drvPath2 = aterm2String(drvPath);
|
||||
checkPath(drvPath2);
|
||||
StringSet ids2;
|
||||
parseStrings(ids, ids2, false);
|
||||
drv.inputDrvs[drvPath2] = ids2;
|
||||
}
|
||||
|
||||
expect(str, ")");
|
||||
parseStrings(inSrcs, drv.inputSrcs, true);
|
||||
|
||||
drv.builder = aterm2String(builder);
|
||||
drv.platform = aterm2String(platform);
|
||||
|
||||
for (ATermIterator i(args); i; ++i) {
|
||||
if (ATgetType(*i) != AT_APPL)
|
||||
throw badTerm("string expected", *i);
|
||||
drv.args.push_back(aterm2String(*i));
|
||||
}
|
||||
|
||||
for (ATermIterator i(bnds); i; ++i) {
|
||||
ATerm s1, s2;
|
||||
if (!matchEnvBinding(*i, s1, s2))
|
||||
throw badTerm("tuple of strings expected", *i);
|
||||
drv.env[aterm2String(s1)] = aterm2String(s2);
|
||||
}
|
||||
|
||||
return drv;
|
||||
}
|
||||
|
||||
|
||||
static void printString(string & res, const string & s)
|
||||
ATerm unparseDerivation(const Derivation & drv)
|
||||
{
|
||||
res += '"';
|
||||
for (const char * i = s.c_str(); *i; i++)
|
||||
if (*i == '\"' || *i == '\\') { res += "\\"; res += *i; }
|
||||
else if (*i == '\n') res += "\\n";
|
||||
else if (*i == '\r') res += "\\r";
|
||||
else if (*i == '\t') res += "\\t";
|
||||
else res += *i;
|
||||
res += '"';
|
||||
}
|
||||
ATermList outputs = ATempty;
|
||||
for (DerivationOutputs::const_reverse_iterator i = drv.outputs.rbegin();
|
||||
i != drv.outputs.rend(); ++i)
|
||||
outputs = ATinsert(outputs,
|
||||
makeDerivationOutput(
|
||||
toATerm(i->first),
|
||||
toATerm(i->second.path),
|
||||
toATerm(i->second.hashAlgo),
|
||||
toATerm(i->second.hash)));
|
||||
|
||||
|
||||
template<class ForwardIterator>
|
||||
static void printStrings(string & res, ForwardIterator i, ForwardIterator j)
|
||||
{
|
||||
res += '[';
|
||||
bool first = true;
|
||||
for ( ; i != j; ++i) {
|
||||
if (first) first = false; else res += ',';
|
||||
printString(res, *i);
|
||||
}
|
||||
res += ']';
|
||||
}
|
||||
|
||||
|
||||
string unparseDerivation(const Derivation & drv)
|
||||
{
|
||||
string s;
|
||||
s.reserve(65536);
|
||||
s += "Derive([";
|
||||
|
||||
bool first = true;
|
||||
foreach (DerivationOutputs::const_iterator, i, drv.outputs) {
|
||||
if (first) first = false; else s += ',';
|
||||
s += '('; printString(s, i->first);
|
||||
s += ','; printString(s, i->second.path);
|
||||
s += ','; printString(s, i->second.hashAlgo);
|
||||
s += ','; printString(s, i->second.hash);
|
||||
s += ')';
|
||||
}
|
||||
|
||||
s += "],[";
|
||||
first = true;
|
||||
foreach (DerivationInputs::const_iterator, i, drv.inputDrvs) {
|
||||
if (first) first = false; else s += ',';
|
||||
s += '('; printString(s, i->first);
|
||||
s += ','; printStrings(s, i->second.begin(), i->second.end());
|
||||
s += ')';
|
||||
}
|
||||
|
||||
s += "],";
|
||||
printStrings(s, drv.inputSrcs.begin(), drv.inputSrcs.end());
|
||||
ATermList inDrvs = ATempty;
|
||||
for (DerivationInputs::const_reverse_iterator i = drv.inputDrvs.rbegin();
|
||||
i != drv.inputDrvs.rend(); ++i)
|
||||
inDrvs = ATinsert(inDrvs,
|
||||
makeDerivationInput(
|
||||
toATerm(i->first),
|
||||
toATermList(i->second)));
|
||||
|
||||
s += ','; printString(s, drv.platform);
|
||||
s += ','; printString(s, drv.builder);
|
||||
s += ','; printStrings(s, drv.args.begin(), drv.args.end());
|
||||
ATermList args = ATempty;
|
||||
for (Strings::const_reverse_iterator i = drv.args.rbegin();
|
||||
i != drv.args.rend(); ++i)
|
||||
args = ATinsert(args, toATerm(*i));
|
||||
|
||||
s += ",[";
|
||||
first = true;
|
||||
foreach (StringPairs::const_iterator, i, drv.env) {
|
||||
if (first) first = false; else s += ',';
|
||||
s += '('; printString(s, i->first);
|
||||
s += ','; printString(s, i->second);
|
||||
s += ')';
|
||||
}
|
||||
|
||||
s += "])";
|
||||
|
||||
return s;
|
||||
ATermList env = ATempty;
|
||||
for (StringPairs::const_reverse_iterator i = drv.env.rbegin();
|
||||
i != drv.env.rend(); ++i)
|
||||
env = ATinsert(env,
|
||||
makeEnvBinding(
|
||||
toATerm(i->first),
|
||||
toATerm(i->second)));
|
||||
|
||||
return makeDerive(
|
||||
outputs,
|
||||
inDrvs,
|
||||
toATermList(drv.inputSrcs),
|
||||
toATerm(drv.platform),
|
||||
toATerm(drv.builder),
|
||||
args,
|
||||
env);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#ifndef __DERIVATIONS_H
|
||||
#define __DERIVATIONS_H
|
||||
|
||||
#include <map>
|
||||
typedef struct _ATerm * ATerm;
|
||||
|
||||
#include "types.hh"
|
||||
#include "hash.hh"
|
||||
|
||||
#include <map>
|
||||
|
||||
|
||||
namespace nix {
|
||||
@@ -51,14 +53,17 @@ struct Derivation
|
||||
};
|
||||
|
||||
|
||||
/* Hash an aterm. */
|
||||
Hash hashTerm(ATerm t);
|
||||
|
||||
/* Write a derivation to the Nix store, and return its path. */
|
||||
Path writeDerivation(const Derivation & drv, const string & name);
|
||||
|
||||
/* Parse a derivation. */
|
||||
Derivation parseDerivation(const string & s);
|
||||
Derivation parseDerivation(ATerm t);
|
||||
|
||||
/* Print a derivation. */
|
||||
string unparseDerivation(const Derivation & drv);
|
||||
/* Parse a derivation. */
|
||||
ATerm unparseDerivation(const Derivation & drv);
|
||||
|
||||
/* Check whether a file name ends with the extensions for
|
||||
derivations. */
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "globals.hh"
|
||||
#include "misc.hh"
|
||||
#include "pathlocks.hh"
|
||||
#include "local-store.hh"
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
#include <algorithm>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -14,6 +14,11 @@
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef __CYGWIN__
|
||||
#include <windows.h>
|
||||
#include <sys/cygwin.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -30,7 +35,7 @@ static const int defaultGcLevel = 1000;
|
||||
read. To be precise: when they try to create a new temporary root
|
||||
file, they will block until the garbage collector has finished /
|
||||
yielded the GC lock. */
|
||||
int LocalStore::openGCLock(LockType lockType)
|
||||
static int openGCLock(LockType lockType)
|
||||
{
|
||||
Path fnGCLock = (format("%1%/%2%")
|
||||
% nixStateDir % gcLockName).str();
|
||||
@@ -126,7 +131,7 @@ Path addPermRoot(const Path & _storePath, const Path & _gcRoot,
|
||||
Instead of reading all the roots, it would be more efficient to
|
||||
check if the root is in a directory in or linked from the
|
||||
gcroots directory. */
|
||||
if (queryBoolSetting("gc-check-reachability", false)) {
|
||||
if (queryBoolSetting("gc-check-reachability", true)) {
|
||||
Roots roots = store->findRoots();
|
||||
if (roots.find(gcRoot) == roots.end())
|
||||
printMsg(lvlError,
|
||||
@@ -135,7 +140,7 @@ Path addPermRoot(const Path & _storePath, const Path & _gcRoot,
|
||||
"therefore, `%2%' might be removed by the garbage collector")
|
||||
% gcRoot % storePath);
|
||||
}
|
||||
|
||||
|
||||
/* Grab the global GC root, causing us to block while a GC is in
|
||||
progress. This prevents the set of permanent roots from
|
||||
increasing while a GC is in progress. */
|
||||
@@ -173,6 +178,15 @@ void LocalStore::addTempRoot(const Path & path)
|
||||
|
||||
fdGCLock.close();
|
||||
|
||||
/* Note that on Cygwin a lot of the following complexity
|
||||
is unnecessary, since we cannot delete open lock
|
||||
files. If we have the lock file open, then it's valid;
|
||||
if we can delete it, then it wasn't in use any more.
|
||||
|
||||
Also note that on Cygwin we cannot "upgrade" a lock
|
||||
from a read lock to a write lock. */
|
||||
|
||||
#ifndef __CYGWIN__
|
||||
debug(format("acquiring read lock on `%1%'") % fnTempRoots);
|
||||
lockFile(fdTempRoots, ltRead, true);
|
||||
|
||||
@@ -186,6 +200,10 @@ void LocalStore::addTempRoot(const Path & path)
|
||||
/* The garbage collector deleted this file before we could
|
||||
get a lock. (It won't delete the file after we get a
|
||||
lock.) Try again. */
|
||||
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
@@ -198,9 +216,14 @@ void LocalStore::addTempRoot(const Path & path)
|
||||
string s = path + '\0';
|
||||
writeFull(fdTempRoots, (const unsigned char *) s.c_str(), s.size());
|
||||
|
||||
#ifndef __CYGWIN__
|
||||
/* Downgrade to a read lock. */
|
||||
debug(format("downgrading to read lock on `%1%'") % fnTempRoots);
|
||||
lockFile(fdTempRoots, ltRead, true);
|
||||
#else
|
||||
debug(format("releasing write lock on `%1%'") % fnTempRoots);
|
||||
lockFile(fdTempRoots, ltNone, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -224,10 +247,25 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds)
|
||||
Strings tempRootFiles = readDirectory(
|
||||
(format("%1%/%2%") % nixStateDir % tempRootsDir).str());
|
||||
|
||||
foreach (Strings::iterator, i, tempRootFiles) {
|
||||
for (Strings::iterator i = tempRootFiles.begin();
|
||||
i != tempRootFiles.end(); ++i)
|
||||
{
|
||||
Path path = (format("%1%/%2%/%3%") % nixStateDir % tempRootsDir % *i).str();
|
||||
|
||||
debug(format("reading temporary root file `%1%'") % path);
|
||||
|
||||
#ifdef __CYGWIN__
|
||||
/* On Cygwin we just try to delete the lock file. */
|
||||
char win32Path[MAX_PATH];
|
||||
cygwin_conv_to_full_win32_path(path.c_str(), win32Path);
|
||||
if (DeleteFile(win32Path)) {
|
||||
printMsg(lvlError, format("removed stale temporary roots file `%1%'")
|
||||
% path);
|
||||
continue;
|
||||
} else
|
||||
debug(format("delete of `%1%' failed: %2%") % path % GetLastError());
|
||||
#endif
|
||||
|
||||
FDPtr fd(new AutoCloseFD(open(path.c_str(), O_RDWR, 0666)));
|
||||
if (*fd == -1) {
|
||||
/* It's okay if the file has disappeared. */
|
||||
@@ -239,6 +277,7 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds)
|
||||
//FDPtr fd(new AutoCloseFD(openLockFile(path, false)));
|
||||
//if (*fd == -1) continue;
|
||||
|
||||
#ifndef __CYGWIN__
|
||||
/* Try to acquire a write lock without blocking. This can
|
||||
only succeed if the owning process has died. In that case
|
||||
we don't care about its temporary roots. */
|
||||
@@ -249,6 +288,7 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds)
|
||||
writeFull(*fd, (const unsigned char *) "d", 1);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Acquire a read lock. This will prevent the owning process
|
||||
from upgrading to a write lock, therefore it will block in
|
||||
@@ -288,7 +328,7 @@ static void findRoots(const Path & path, bool recurseSymlinks,
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
Strings names = readDirectory(path);
|
||||
foreach (Strings::iterator, i, names)
|
||||
for (Strings::iterator i = names.begin(); i != names.end(); ++i)
|
||||
findRoots(path + "/" + *i, recurseSymlinks, deleteStale, roots);
|
||||
}
|
||||
|
||||
@@ -359,11 +399,11 @@ static void addAdditionalRoots(PathSet & roots)
|
||||
|
||||
Strings paths = tokenizeString(result, "\n");
|
||||
|
||||
foreach (Strings::iterator, i, paths) {
|
||||
for (Strings::iterator i = paths.begin(); i != paths.end(); ++i) {
|
||||
if (isInStore(*i)) {
|
||||
Path path = toStorePath(*i);
|
||||
if (roots.find(path) == roots.end() && store->isValidPath(path)) {
|
||||
debug(format("got additional root `%1%'") % path);
|
||||
debug(format("found additional root `%1%'") % path);
|
||||
roots.insert(path);
|
||||
}
|
||||
}
|
||||
@@ -381,7 +421,8 @@ static void dfsVisit(const PathSet & paths, const Path & path,
|
||||
if (store->isValidPath(path))
|
||||
store->queryReferences(path, references);
|
||||
|
||||
foreach (PathSet::iterator, i, references)
|
||||
for (PathSet::iterator i = references.begin();
|
||||
i != references.end(); ++i)
|
||||
/* Don't traverse into paths that don't exist. That can
|
||||
happen due to substitutes for non-existent paths. */
|
||||
if (*i != path && paths.find(*i) != paths.end())
|
||||
@@ -395,185 +436,156 @@ Paths topoSortPaths(const PathSet & paths)
|
||||
{
|
||||
Paths sorted;
|
||||
PathSet visited;
|
||||
foreach (PathSet::const_iterator, i, paths)
|
||||
for (PathSet::const_iterator i = paths.begin(); i != paths.end(); ++i)
|
||||
dfsVisit(paths, *i, visited, sorted);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
|
||||
static time_t lastFileAccessTime(const Path & path)
|
||||
{
|
||||
checkInterrupt();
|
||||
|
||||
struct stat st;
|
||||
if (lstat(path.c_str(), &st) == -1)
|
||||
throw SysError(format("statting `%1%'") % path);
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
time_t last = 0;
|
||||
Strings names = readDirectory(path);
|
||||
for (Strings::iterator i = names.begin(); i != names.end(); ++i) {
|
||||
time_t t = lastFileAccessTime(path + "/" + *i);
|
||||
if (t > last) last = t;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
else if (S_ISLNK(st.st_mode)) return 0;
|
||||
|
||||
else return st.st_atime;
|
||||
}
|
||||
|
||||
|
||||
struct GCLimitReached { };
|
||||
|
||||
|
||||
struct LocalStore::GCState
|
||||
void LocalStore::gcPath(const GCOptions & options, GCResults & results,
|
||||
const Path & path)
|
||||
{
|
||||
GCOptions options;
|
||||
GCResults & results;
|
||||
PathSet roots;
|
||||
PathSet tempRoots;
|
||||
PathSet deleted;
|
||||
PathSet live;
|
||||
PathSet busy;
|
||||
bool gcKeepOutputs;
|
||||
bool gcKeepDerivations;
|
||||
GCState(GCResults & results_) : results(results_)
|
||||
results.paths.insert(path);
|
||||
|
||||
if (!pathExists(path)) return;
|
||||
|
||||
#ifndef __CYGWIN__
|
||||
AutoCloseFD fdLock;
|
||||
|
||||
/* Only delete a lock file if we can acquire a write lock on it.
|
||||
That means that it's either stale, or the process that created
|
||||
it hasn't locked it yet. In the latter case the other process
|
||||
will detect that we deleted the lock, and retry (see
|
||||
pathlocks.cc). */
|
||||
if (path.size() >= 5 && string(path, path.size() - 5) == ".lock") {
|
||||
fdLock = openLockFile(path, false);
|
||||
if (fdLock != -1 && !lockFile(fdLock, ltWrite, false)) {
|
||||
debug(format("skipping active lock `%1%'") % path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Okay, it's safe to delete. */
|
||||
unsigned long long bytesFreed, blocksFreed;
|
||||
deleteFromStore(path, bytesFreed, blocksFreed);
|
||||
results.bytesFreed += bytesFreed;
|
||||
results.blocksFreed += blocksFreed;
|
||||
|
||||
if (results.bytesFreed > options.maxFreed) {
|
||||
printMsg(lvlInfo, format("deleted more than %1% bytes; stopping") % options.maxFreed);
|
||||
throw GCLimitReached();
|
||||
}
|
||||
|
||||
if (options.maxLinks) {
|
||||
struct stat st;
|
||||
if (stat(nixStore.c_str(), &st) == -1)
|
||||
throw SysError(format("statting `%1%'") % nixStore);
|
||||
if (st.st_nlink < options.maxLinks) {
|
||||
printMsg(lvlInfo, format("link count on the store has dropped below %1%; stopping") % options.maxLinks);
|
||||
throw GCLimitReached();
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef __CYGWIN__
|
||||
if (fdLock != -1)
|
||||
/* Write token to stale (deleted) lock file. */
|
||||
writeFull(fdLock, (const unsigned char *) "d", 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void LocalStore::gcPathRecursive(const GCOptions & options,
|
||||
GCResults & results, PathSet & done, const Path & path)
|
||||
{
|
||||
if (done.find(path) != done.end()) return;
|
||||
done.insert(path);
|
||||
|
||||
startNest(nest, lvlDebug, format("looking at `%1%'") % path);
|
||||
|
||||
/* Delete all the referrers first. They must be garbage too,
|
||||
since if they were live, then the current path would also be
|
||||
live. Note that deleteFromStore() below still makes sure that
|
||||
the referrer set has become empty, just in case. (However that
|
||||
doesn't guard against deleting top-level paths that are only
|
||||
reachable from GC roots.) */
|
||||
PathSet referrers;
|
||||
if (isValidPath(path))
|
||||
queryReferrers(path, referrers);
|
||||
foreach (PathSet::iterator, i, referrers)
|
||||
if (*i != path) gcPathRecursive(options, results, done, *i);
|
||||
|
||||
printMsg(lvlInfo, format("deleting `%1%'") % path);
|
||||
|
||||
gcPath(options, results, path);
|
||||
}
|
||||
|
||||
|
||||
struct CachingAtimeComparator : public std::binary_function<Path, Path, bool>
|
||||
{
|
||||
std::map<Path, time_t> cache;
|
||||
|
||||
time_t lookup(const Path & p)
|
||||
{
|
||||
std::map<Path, time_t>::iterator i = cache.find(p);
|
||||
if (i != cache.end()) return i->second;
|
||||
debug(format("computing atime of `%1%'") % p);
|
||||
cache[p] = lastFileAccessTime(p);
|
||||
assert(cache.find(p) != cache.end());
|
||||
return cache[p];
|
||||
}
|
||||
|
||||
bool operator () (const Path & p1, const Path & p2)
|
||||
{
|
||||
return lookup(p2) < lookup(p1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static bool shouldDelete(GCOptions::GCAction action)
|
||||
string showTime(const string & format, time_t t)
|
||||
{
|
||||
return action == GCOptions::gcDeleteDead
|
||||
|| action == GCOptions::gcDeleteSpecific;
|
||||
}
|
||||
|
||||
|
||||
bool LocalStore::isActiveTempFile(const GCState & state,
|
||||
const Path & path, const string & suffix)
|
||||
{
|
||||
return hasSuffix(path, suffix)
|
||||
&& state.tempRoots.find(string(path, 0, path.size() - suffix.size())) != state.tempRoots.end();
|
||||
}
|
||||
|
||||
|
||||
bool LocalStore::tryToDelete(GCState & state, const Path & path)
|
||||
{
|
||||
checkInterrupt();
|
||||
|
||||
if (!pathExists(path)) return true;
|
||||
if (state.deleted.find(path) != state.deleted.end()) return true;
|
||||
if (state.live.find(path) != state.live.end()) return false;
|
||||
|
||||
startNest(nest, lvlDebug, format("considering whether to delete `%1%'") % path);
|
||||
|
||||
if (state.roots.find(path) != state.roots.end()) {
|
||||
printMsg(lvlDebug, format("cannot delete `%1%' because it's a root") % path);
|
||||
goto isLive;
|
||||
}
|
||||
|
||||
if (isValidPath(path)) {
|
||||
|
||||
/* Recursively try to delete the referrers of this path. If
|
||||
any referrer can't be deleted, then this path can't be
|
||||
deleted either. */
|
||||
PathSet referrers;
|
||||
queryReferrers(path, referrers);
|
||||
foreach (PathSet::iterator, i, referrers)
|
||||
if (*i != path && !tryToDelete(state, *i)) {
|
||||
printMsg(lvlDebug, format("cannot delete `%1%' because it has live referrers") % path);
|
||||
goto isLive;
|
||||
}
|
||||
|
||||
/* If gc-keep-derivations is set and this is a derivation,
|
||||
then don't delete the derivation if any of the outputs are
|
||||
live. */
|
||||
if (state.gcKeepDerivations && isDerivation(path)) {
|
||||
PathSet outputs = queryDerivationOutputs(path);
|
||||
foreach (PathSet::iterator, i, outputs)
|
||||
if (!tryToDelete(state, *i)) {
|
||||
printMsg(lvlDebug, format("cannot delete derivation `%1%' because its output `%2%' is alive") % path % *i);
|
||||
goto isLive;
|
||||
}
|
||||
}
|
||||
|
||||
/* If gc-keep-derivations and gc-keep-outputs are both set,
|
||||
it's possible that the path has already been deleted (due
|
||||
to the recursion below), so bail out. */
|
||||
if (!pathExists(path)) return true;
|
||||
|
||||
/* If gc-keep-outputs is set, then don't delete this path if
|
||||
there are derivers of this path that are not garbage. */
|
||||
if (state.gcKeepOutputs) {
|
||||
PathSet derivers = queryValidDerivers(path);
|
||||
foreach (PathSet::iterator, deriver, derivers) {
|
||||
/* Break an infinite recursion if gc-keep-derivations
|
||||
and gc-keep-outputs are both set by tentatively
|
||||
assuming that this path is garbage. This is a safe
|
||||
assumption because at this point, the only thing
|
||||
that can prevent it from being garbage is the
|
||||
deriver. Since tryToDelete() works "upwards"
|
||||
through the dependency graph, it won't encouter
|
||||
this path except in the call to tryToDelete() in
|
||||
the gc-keep-derivation branch. */
|
||||
state.deleted.insert(path);
|
||||
if (!tryToDelete(state, *deriver)) {
|
||||
state.deleted.erase(path);
|
||||
printMsg(lvlDebug, format("cannot delete `%1%' because its deriver `%2%' is alive") % path % *deriver);
|
||||
goto isLive;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
/* A lock file belonging to a path that we're building right
|
||||
now isn't garbage. */
|
||||
if (isActiveTempFile(state, path, ".lock")) return false;
|
||||
|
||||
/* Don't delete .chroot directories for derivations that are
|
||||
currently being built. */
|
||||
if (isActiveTempFile(state, path, ".chroot")) return false;
|
||||
|
||||
}
|
||||
|
||||
/* The path is garbage, so delete it. */
|
||||
if (shouldDelete(state.options.action)) {
|
||||
printMsg(lvlInfo, format("deleting `%1%'") % path);
|
||||
|
||||
unsigned long long bytesFreed, blocksFreed;
|
||||
deleteFromStore(path, bytesFreed, blocksFreed);
|
||||
state.results.bytesFreed += bytesFreed;
|
||||
state.results.blocksFreed += blocksFreed;
|
||||
|
||||
if (state.options.maxFreed && state.results.bytesFreed > state.options.maxFreed) {
|
||||
printMsg(lvlInfo, format("deleted more than %1% bytes; stopping") % state.options.maxFreed);
|
||||
throw GCLimitReached();
|
||||
}
|
||||
|
||||
if (state.options.maxLinks) {
|
||||
struct stat st;
|
||||
if (stat(nixStore.c_str(), &st) == -1)
|
||||
throw SysError(format("statting `%1%'") % nixStore);
|
||||
if (st.st_nlink < state.options.maxLinks) {
|
||||
printMsg(lvlInfo, format("link count on the store has dropped below %1%; stopping") % state.options.maxLinks);
|
||||
throw GCLimitReached();
|
||||
}
|
||||
}
|
||||
|
||||
} else
|
||||
printMsg(lvlTalkative, format("would delete `%1%'") % path);
|
||||
|
||||
state.deleted.insert(path);
|
||||
if (state.options.action != GCOptions::gcReturnLive)
|
||||
state.results.paths.insert(path);
|
||||
return true;
|
||||
|
||||
isLive:
|
||||
state.live.insert(path);
|
||||
if (state.options.action == GCOptions::gcReturnLive)
|
||||
state.results.paths.insert(path);
|
||||
return false;
|
||||
char s[128];
|
||||
strftime(s, sizeof s, format.c_str(), localtime(&t));
|
||||
return string(s);
|
||||
}
|
||||
|
||||
|
||||
void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||
{
|
||||
GCState state(results);
|
||||
state.options = options;
|
||||
|
||||
state.gcKeepOutputs = queryBoolSetting("gc-keep-outputs", false);
|
||||
state.gcKeepDerivations = queryBoolSetting("gc-keep-derivations", true);
|
||||
bool gcKeepOutputs =
|
||||
queryBoolSetting("gc-keep-outputs", false);
|
||||
bool gcKeepDerivations =
|
||||
queryBoolSetting("gc-keep-derivations", true);
|
||||
int gcKeepOutputsThreshold =
|
||||
queryIntSetting ("gc-keep-outputs-threshold", defaultGcLevel);
|
||||
|
||||
/* Using `--ignore-liveness' with `--delete' can have unintended
|
||||
consequences if `gc-keep-outputs' or `gc-keep-derivations' are
|
||||
true (the garbage collector will recurse into deleting the
|
||||
outputs or derivers, respectively). So disable them. */
|
||||
if (options.action == GCOptions::gcDeleteSpecific && options.ignoreLiveness) {
|
||||
state.gcKeepOutputs = false;
|
||||
state.gcKeepDerivations = false;
|
||||
}
|
||||
|
||||
/* Acquire the global GC root. This prevents
|
||||
a) New roots from being added.
|
||||
b) Processes from creating new temporary root files. */
|
||||
@@ -584,60 +596,207 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||
printMsg(lvlError, format("finding garbage collector roots..."));
|
||||
Roots rootMap = options.ignoreLiveness ? Roots() : nix::findRoots(true);
|
||||
|
||||
foreach (Roots::iterator, i, rootMap) state.roots.insert(i->second);
|
||||
PathSet roots;
|
||||
for (Roots::iterator i = rootMap.begin(); i != rootMap.end(); ++i)
|
||||
roots.insert(i->second);
|
||||
|
||||
/* Add additional roots returned by the program specified by the
|
||||
NIX_ROOT_FINDER environment variable. This is typically used
|
||||
to add running programs to the set of roots (to prevent them
|
||||
from being garbage collected). */
|
||||
if (!options.ignoreLiveness)
|
||||
addAdditionalRoots(state.roots);
|
||||
addAdditionalRoots(roots);
|
||||
|
||||
if (options.action == GCOptions::gcReturnRoots) {
|
||||
results.paths = roots;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Determine the live paths which is just the closure of the
|
||||
roots under the `references' relation. */
|
||||
printMsg(lvlError, format("computing live paths..."));
|
||||
PathSet livePaths;
|
||||
for (PathSet::const_iterator i = roots.begin(); i != roots.end(); ++i)
|
||||
computeFSClosure(canonPath(*i), livePaths);
|
||||
|
||||
if (gcKeepDerivations) {
|
||||
for (PathSet::iterator i = livePaths.begin();
|
||||
i != livePaths.end(); ++i)
|
||||
{
|
||||
/* Note that the deriver need not be valid (e.g., if we
|
||||
previously ran the collector with `gcKeepDerivations'
|
||||
turned off). */
|
||||
Path deriver = queryDeriver(*i);
|
||||
if (deriver != "" && isValidPath(deriver))
|
||||
computeFSClosure(deriver, livePaths);
|
||||
}
|
||||
}
|
||||
|
||||
if (gcKeepOutputs) {
|
||||
/* Hmz, identical to storePathRequisites in nix-store. */
|
||||
for (PathSet::iterator i = livePaths.begin();
|
||||
i != livePaths.end(); ++i)
|
||||
if (isDerivation(*i)) {
|
||||
Derivation drv = derivationFromPath(*i);
|
||||
|
||||
string gcLevelStr = drv.env["__gcLevel"];
|
||||
int gcLevel;
|
||||
if (!string2Int(gcLevelStr, gcLevel))
|
||||
gcLevel = defaultGcLevel;
|
||||
|
||||
if (gcLevel >= gcKeepOutputsThreshold)
|
||||
for (DerivationOutputs::iterator j = drv.outputs.begin();
|
||||
j != drv.outputs.end(); ++j)
|
||||
if (isValidPath(j->second.path))
|
||||
computeFSClosure(j->second.path, livePaths);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.action == GCOptions::gcReturnLive) {
|
||||
results.paths = livePaths;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Read the temporary roots. This acquires read locks on all
|
||||
per-process temporary root files. So after this point no paths
|
||||
can be added to the set of temporary roots. */
|
||||
PathSet tempRoots;
|
||||
FDs fds;
|
||||
readTempRoots(state.tempRoots, fds);
|
||||
state.roots.insert(state.tempRoots.begin(), state.tempRoots.end());
|
||||
readTempRoots(tempRoots, fds);
|
||||
|
||||
/* Close the temporary roots. Note that we *cannot* do this in
|
||||
readTempRoots(), because there we may not have all locks yet,
|
||||
meaning that an invalid path can become valid (and thus add to
|
||||
the references graph) after we have added it to the closure
|
||||
(and computeFSClosure() assumes that the presence of a path
|
||||
means that it has already been closed). */
|
||||
PathSet tempRootsClosed;
|
||||
for (PathSet::iterator i = tempRoots.begin(); i != tempRoots.end(); ++i)
|
||||
if (isValidPath(*i))
|
||||
computeFSClosure(*i, tempRootsClosed);
|
||||
else
|
||||
tempRootsClosed.insert(*i);
|
||||
|
||||
/* After this point the set of roots or temporary roots cannot
|
||||
increase, since we hold locks on everything. So everything
|
||||
that is not reachable from `roots'. */
|
||||
|
||||
/* Now either delete all garbage paths, or just the specified
|
||||
paths (for gcDeleteSpecific). */
|
||||
|
||||
if (options.action == GCOptions::gcDeleteSpecific) {
|
||||
that is not currently in in `livePaths' or `tempRootsClosed'
|
||||
can be deleted. */
|
||||
|
||||
/* Read the Nix store directory to find all currently existing
|
||||
paths and filter out all live paths. */
|
||||
printMsg(lvlError, format("reading the Nix store..."));
|
||||
PathSet storePaths;
|
||||
|
||||
if (options.action != GCOptions::gcDeleteSpecific) {
|
||||
Paths entries = readDirectory(nixStore);
|
||||
foreach (Paths::iterator, i, entries) {
|
||||
Path path = canonPath(nixStore + "/" + *i);
|
||||
if (livePaths.find(path) == livePaths.end() &&
|
||||
tempRootsClosed.find(path) == tempRootsClosed.end())
|
||||
storePaths.insert(path);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
foreach (PathSet::iterator, i, options.pathsToDelete) {
|
||||
assertStorePath(*i);
|
||||
if (!tryToDelete(state, *i))
|
||||
storePaths.insert(*i);
|
||||
if (livePaths.find(*i) != livePaths.end())
|
||||
throw Error(format("cannot delete path `%1%' since it is still alive") % *i);
|
||||
if (tempRootsClosed.find(*i) != tempRootsClosed.end())
|
||||
throw Error(format("cannot delete path `%1%' since it is temporarily in use") % *i);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
printMsg(lvlError, format("reading the Nix store..."));
|
||||
Paths entries = readDirectory(nixStore);
|
||||
}
|
||||
|
||||
/* Randomise the order in which we delete entries to make the
|
||||
collector less biased towards deleting paths that come
|
||||
alphabetically first (e.g. /nix/store/000...). This
|
||||
matters when using --max-freed etc. */
|
||||
vector<Path> entries_(entries.begin(), entries.end());
|
||||
random_shuffle(entries_.begin(), entries_.end());
|
||||
if (options.action == GCOptions::gcReturnDead) {
|
||||
results.paths.insert(storePaths.begin(), storePaths.end());
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldDelete(state.options.action))
|
||||
/* Delete all dead store paths (or until one of the stop
|
||||
conditions is reached). */
|
||||
|
||||
PathSet done;
|
||||
try {
|
||||
|
||||
if (!options.useAtime) {
|
||||
/* Delete the paths, respecting the partial ordering
|
||||
determined by the references graph. */
|
||||
printMsg(lvlError, format("deleting garbage..."));
|
||||
else
|
||||
printMsg(lvlError, format("determining live/dead paths..."));
|
||||
|
||||
try {
|
||||
foreach (vector<Path>::iterator, i, entries_)
|
||||
tryToDelete(state, canonPath(nixStore + "/" + *i));
|
||||
} catch (GCLimitReached & e) {
|
||||
foreach (PathSet::iterator, i, storePaths)
|
||||
gcPathRecursive(options, results, done, *i);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
/* Delete in order of ascending last access time, still
|
||||
maintaining the partial ordering of the reference
|
||||
graph. Note that we can't use a topological sort for
|
||||
this because that takes time O(V+E), and in this case
|
||||
E=O(V^2) (i.e. the graph is dense because of the edges
|
||||
due to the atime ordering). So instead we put all
|
||||
deletable paths in a priority queue (ordered by atime),
|
||||
and after deleting a path, add additional paths that
|
||||
have become deletable to the priority queue. */
|
||||
|
||||
CachingAtimeComparator atimeComp;
|
||||
|
||||
/* Create a priority queue that orders paths by ascending
|
||||
atime. This is why C++ needs type inferencing... */
|
||||
std::priority_queue<Path, vector<Path>, binary_function_ref_adapter<CachingAtimeComparator> > prioQueue =
|
||||
std::priority_queue<Path, vector<Path>, binary_function_ref_adapter<CachingAtimeComparator> >(binary_function_ref_adapter<CachingAtimeComparator>(&atimeComp));
|
||||
|
||||
/* Initially put the paths that are invalid or have no
|
||||
referrers into the priority queue. */
|
||||
printMsg(lvlError, format("finding deletable paths..."));
|
||||
foreach (PathSet::iterator, i, storePaths) {
|
||||
checkInterrupt();
|
||||
/* We can safely delete a path if it's invalid or
|
||||
it has no referrers. Note that all the invalid
|
||||
paths will be deleted in the first round. */
|
||||
if (isValidPath(*i)) {
|
||||
if (queryReferrersNoSelf(*i).empty()) prioQueue.push(*i);
|
||||
} else prioQueue.push(*i);
|
||||
}
|
||||
|
||||
debug(format("%1% initially deletable paths") % prioQueue.size());
|
||||
|
||||
/* Now delete everything in the order of the priority
|
||||
queue until nothing is left. */
|
||||
printMsg(lvlError, format("deleting garbage..."));
|
||||
while (!prioQueue.empty()) {
|
||||
checkInterrupt();
|
||||
Path path = prioQueue.top(); prioQueue.pop();
|
||||
|
||||
if (options.maxAtime != (time_t) -1 &&
|
||||
atimeComp.lookup(path) > options.maxAtime)
|
||||
continue;
|
||||
|
||||
printMsg(lvlInfo, format("deleting `%1%' (last accessed %2%)") % path % showTime("%F %H:%M:%S", atimeComp.lookup(path)));
|
||||
|
||||
PathSet references;
|
||||
if (isValidPath(path)) references = queryReferencesNoSelf(path);
|
||||
|
||||
gcPath(options, results, path);
|
||||
|
||||
/* For each reference of the current path, see if the
|
||||
reference has now become deletable (i.e. is in the
|
||||
set of dead paths and has no referrers left). If
|
||||
so add it to the priority queue. */
|
||||
foreach (PathSet::iterator, i, references) {
|
||||
if (storePaths.find(*i) != storePaths.end() &&
|
||||
queryReferrersNoSelf(*i).empty())
|
||||
{
|
||||
debug(format("path `%1%' has become deletable") % *i);
|
||||
prioQueue.push(*i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (GCLimitReached & e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user