From 841fcbd04755c7a2865c51c1e2d3b045976b7452 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 12 Mar 2003 13:32:24 +0000 Subject: [PATCH 0001/6440] * And a trunk to go along with that. From 75d788b0f24e8de033a22c0869032549d602d4f6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 13 Mar 2003 14:24:49 +0000 Subject: [PATCH 0002/6440] * Initial version of nix. --- src/Makefile | 2 + src/nix.c | 314 ++++++++++++++++++++++++++++++++++++++++++++++++++ sys/bootstrap | 86 ++++++++++++++ sys/makedisk | 11 ++ sys/mountloop | 8 ++ sys/runsystem | 5 + sys/settings | 2 + sys/start | 40 +++++++ 8 files changed, 468 insertions(+) create mode 100644 src/Makefile create mode 100644 src/nix.c create mode 100755 sys/bootstrap create mode 100755 sys/makedisk create mode 100755 sys/mountloop create mode 100755 sys/runsystem create mode 100644 sys/settings create mode 100755 sys/start diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 000000000..5f42afd04 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,2 @@ +nix: nix.c + gcc -g -Wall -o nix nix.c -ldb-4 diff --git a/src/nix.c b/src/nix.c new file mode 100644 index 000000000..e8b18934b --- /dev/null +++ b/src/nix.c @@ -0,0 +1,314 @@ +#include +#include +#include +#include +#include +#include + +#include + + +#define PKGINFO_PATH "/pkg/sys/var/pkginfo" + + +typedef enum {true = 1, false = 0} bool; + + +static char * prog; +static char * dbfile = PKGINFO_PATH; + + +DB * openDB(char * dbname, bool readonly) +{ + int err; + DB * db; + + err = db_create(&db, 0, 0); + if (err) { + fprintf(stderr, "error creating db handle: %s\n", + db_strerror(err)); + return 0; + } + + err = db->open(db, dbfile, dbname, + DB_HASH, readonly ? DB_RDONLY : DB_CREATE, 0666); + if (err) { + fprintf(stderr, "creating opening %s: %s\n", + dbfile, db_strerror(err)); + db->close(db, 0); + return 0; + } + + return db; +} + + +bool queryDB(char * dbname, char * key, char * * data) +{ + DB * db = 0; + DBT kt, dt; + int err; + + db = openDB(dbname, true); + if (!db) goto bad; + + *data = 0; + + memset(&kt, 0, sizeof(kt)); + memset(&dt, 0, sizeof(dt)); + kt.size = strlen(key); + kt.data = key; + + err = db->get(db, 0, &kt, &dt, 0); + if (!err) { + *data = malloc(dt.size + 1); + memcpy(*data, dt.data, dt.size); + (*data)[dt.size] = 0; + } else if (err != DB_NOTFOUND) { + fprintf(stderr, "creating opening %s: %s\n", + dbfile, db_strerror(err)); + goto bad; + } + + db->close(db, 0); + return true; + + bad: + if (db) db->close(db, 0); + return false; +} + + +bool setDB(char * dbname, char * key, char * data) +{ + DB * db = 0; + DBT kt, dt; + int err; + + db = openDB(dbname, false); + if (!db) goto bad; + + memset(&kt, 0, sizeof(kt)); + memset(&dt, 0, sizeof(dt)); + kt.size = strlen(key); + kt.data = key; + dt.size = strlen(data); + dt.data = data; + + err = db->put(db, 0, &kt, &dt, 0); + if (err) { + fprintf(stderr, "error storing data in %s: %s\n", + dbfile, db_strerror(err)); + goto bad; + } + + db->close(db, 0); + return true; + + bad: + if (db) db->close(db, 0); + return false; +} + + +bool delDB(char * dbname, char * key) +{ + DB * db = 0; + DBT kt; + int err; + + db = openDB(dbname, false); + if (!db) goto bad; + + memset(&kt, 0, sizeof(kt)); + kt.size = strlen(key); + kt.data = key; + + err = db->del(db, 0, &kt, 0); + if (err) { + fprintf(stderr, "error deleting data from %s: %s\n", + dbfile, db_strerror(err)); + goto bad; + } + + db->close(db, 0); + return true; + + bad: + if (db) db->close(db, 0); + return false; +} + + +bool getPkg(int argc, char * * argv) +{ + char * pkg; + char * src = 0; + char * inst = 0; + char inst2[1024]; + char cmd[2048]; + int res; + + if (argc != 1) { + fprintf(stderr, "arguments missing in get-pkg\n"); + return false; + } + + pkg = argv[0]; + + if (!queryDB("pkginst", pkg, &inst)) return false; + + if (inst) { + printf("%s\n", inst); + free(inst); + } else { + + fprintf(stderr, "package %s is not yet installed\n", pkg); + + if (!queryDB("pkgsrc", pkg, &src)) return false; + + if (!src) { + fprintf(stderr, "source of package %s is not known\n", pkg); + return false; + } + + if (snprintf(inst2, sizeof(inst2), "/pkg/%s", pkg) >= sizeof(inst2)) { + fprintf(stderr, "buffer overflow\n"); + free(src); + return false; + } + + if (snprintf(cmd, sizeof(cmd), "rsync -a \"%s\"/ \"%s\"", + src, inst2) >= sizeof(cmd)) + { + fprintf(stderr, "buffer overflow\n"); + free(src); + return false; + } + + res = system(cmd); + if (!WIFEXITED(res) || WEXITSTATUS(res) != 0) { + fprintf(stderr, "unable to copy sources\n"); + free(src); + return false; + } + + if (chdir(inst2)) { + fprintf(stderr, "unable to chdir to package directory\n"); + free(src); + return false; + } + + /* Prepare for building. Clean the environment so that the + build process does not inherit things it shouldn't. */ + setenv("PATH", "/pkg/sys/bin", 1); + + res = system("./buildme"); + if (!WIFEXITED(res) || WEXITSTATUS(res) != 0) { + fprintf(stderr, "unable to build package\n"); + free(src); + return false; + } + + setDB("pkginst", pkg, inst2); + + free(src); + + printf("%s\n", inst2); + } + + return true; +} + + +bool registerPkg(int argc, char * * argv) +{ + char * pkg; + char * src; + + if (argc != 2) { + fprintf(stderr, "arguments missing in register-pkg\n"); + return false; + } + + pkg = argv[0]; + src = argv[1]; + + return setDB("pkgsrc", pkg, src); +} + + +/* This is primarily used for bootstrapping. */ +bool registerInstalledPkg(int argc, char * * argv) +{ + char * pkg; + char * inst; + + if (argc != 2) { + fprintf(stderr, "arguments missing in register-installed-pkg\n"); + return false; + } + + pkg = argv[0]; + inst = argv[1]; + + if (strcmp(inst, "") == 0) + return delDB("pkginst", pkg); + else + return setDB("pkginst", pkg, inst); +} + + +bool run(int argc, char * * argv) +{ + char * cmd; + + if (argc < 1) { + fprintf(stderr, "command not specified\n"); + return false; + } + + cmd = argv[0]; + argc--, argv++; + + if (strcmp(cmd, "get-pkg") == 0) + return getPkg(argc, argv); + else if (strcmp(cmd, "register-pkg") == 0) + return registerPkg(argc, argv); + else if (strcmp(cmd, "register-installed-pkg") == 0) + return registerInstalledPkg(argc, argv); + else { + fprintf(stderr, "unknown command: %s\n", cmd); + return false; + } +} + + +int main(int argc, char * * argv) +{ + int c; + + prog = argv[0]; + + while ((c = getopt(argc, argv, "d:")) != EOF) { + + switch (c) { + + case 'd': + dbfile = optarg; + break; + + default: + fprintf(stderr, "unknown option\n"); + break; + } + + } + + argc -= optind, argv += optind; + + if (!run(argc, argv)) + return 1; + else + return 0; +} diff --git a/sys/bootstrap b/sys/bootstrap new file mode 100755 index 000000000..e2265c1dc --- /dev/null +++ b/sys/bootstrap @@ -0,0 +1,86 @@ +#! /bin/sh + +. ./settings + +if ! ./mountloop; then + exit 1 +fi + +# Cleanup. +rm -rf $target/dev +rm -rf $target/proc + +# Create the basic directory structure. +mkdir $target +mkdir $target/dev +mkdir $target/proc +mkdir $target/pkg +mkdir $target/pkg/sys +mkdir $target/pkg/sys/bin +mkdir $target/pkg/sys/var +mkdir $target/mnt +mkdir $target/mnt/host +mkdir -m 1777 $target/tmp + +# Make package registrations. +pkgdb=$target/pkg/sys/var/pkginfo + +# Copy some programs and its libraries. +utils="/usr/bin/vi /bin/sh /bin/mount /bin/umount /bin/ls /bin/ln /bin/cp /bin/mv /bin/rm /bin/cat /bin/df /bin/pwd /usr/bin/ld /usr/bin/as /bin/sed /bin/chmod /bin/chown /usr/bin/expr /bin/mkdir /bin/rmdir /usr/bin/sort /usr/bin/uniq /bin/uname /usr/bin/grep /bin/sleep /usr/bin/rsync /usr/bin/make /usr/bin/cmp /bin/date /usr/bin/tr /usr/bin/ar /usr/bin/ranlib /usr/bin/basename /usr/bin/less ../src/nix" +bootlib=/pkg/prog-bootstrap/lib +bootbin=/pkg/prog-bootstrap/bin +mkdir -p $target/$bootlib +mkdir -p $target/$bootbin +cp -p $utils $target/$bootbin +libs=`ldd $utils | awk '{ print $3 }' | sort | uniq` +echo $libs +cp -p $libs $target/$bootlib +for i in libc.so.6 libdl.so.2 libpthread.so.0 librt.so.1 libresolv.so.2 ld-linux.so.2; do rm $target/$bootlib/$i; done +../src/nix -d $pkgdb register-installed-pkg prog-bootstrap /pkg/prog-bootstrap + +mv $target/$bootbin/nix $target/pkg/sys/bin +../src/nix -d $pkgdb register-installed-pkg sys /pkg/sys + +# Copy the bootstrap gcc. +echo Copying gcc... +rsync -a ../bootstrap/gcc/inst/pkg $target +../src/nix -d $pkgdb register-installed-pkg gcc-bootstrap /pkg/gcc-bootstrap + +# Copy the bootstrap glibc. +echo Copying glibc... +glibcdir=/pkg/glibc-bootstrap +rsync -a ../bootstrap/glibc/inst/pkg $target +../src/nix -d $pkgdb register-installed-pkg glibc-bootstrap $glibcdir + +# Copy the bootstrap kernel header files. +echo Copying kernel headers... +kerneldir=/pkg/kernel-bootstrap +rsync -a ../bootstrap/kernel/inst/pkg $target +../src/nix -d $pkgdb register-installed-pkg kernel-bootstrap $kerneldir + +# Compatibility. +rm -rf $target/lib +mkdir $target/lib +ln -sf $glibcdir/lib/ld-linux.so.2 $target/lib/ld-linux.so.2 + +rm -rf $target/bin +mkdir $target/bin +ln -sf $bootbin/sh $target/bin/sh + +# Build ld.so.cache. +ldsoconf=$target/$glibcdir/etc/ld.so.conf +echo $glibcdir/lib > $ldsoconf +echo $bootlib >> $ldsoconf +$target/$glibcdir/sbin/ldconfig -r $target + +# Source repository. +rm -f $target/src +ln -sf /mnt/host/`pwd`/../src $target/src + +# Copy boot script. +cp -p ./start $target/pkg/sys/bin + +# Done. +echo Done! +umount $target +rmdir $target diff --git a/sys/makedisk b/sys/makedisk new file mode 100755 index 000000000..3d6ec9a2c --- /dev/null +++ b/sys/makedisk @@ -0,0 +1,11 @@ +#! /bin/sh + +. ./settings + +rm $image + +dd if=/dev/zero of=$image bs=1M count=1 seek=256 + +/sbin/mke2fs -F -j $image +/sbin/tune2fs -c 0 $image +/sbin/tune2fs -i 0 $image diff --git a/sys/mountloop b/sys/mountloop new file mode 100755 index 000000000..1d5fe32fc --- /dev/null +++ b/sys/mountloop @@ -0,0 +1,8 @@ +#! /bin/sh + +. ./settings + +mkdir $target +if ! mount -o loop -t ext3 $image $target; then + exit 1 +fi diff --git a/sys/runsystem b/sys/runsystem new file mode 100755 index 000000000..fd634bdcc --- /dev/null +++ b/sys/runsystem @@ -0,0 +1,5 @@ +#! /bin/sh + +. ./settings + +linux ubd0=$image init=/pkg/sys/bin/start diff --git a/sys/settings b/sys/settings new file mode 100644 index 000000000..3968b609a --- /dev/null +++ b/sys/settings @@ -0,0 +1,2 @@ +image=/var/tmp/nix.img +target=./loop diff --git a/sys/start b/sys/start new file mode 100755 index 000000000..f822b2fa7 --- /dev/null +++ b/sys/start @@ -0,0 +1,40 @@ +#! /pkg/prog-bootstrap/bin/sh + +# This directory contains nix. +export PATH=/pkg/sys/bin + +# Add in the utilities needed for booting. +export PATH=$PATH:`nix get-pkg prog-bootstrap`/bin + +echo +echo Starting up... + +echo Mounting file systems... +mount -n -o remount,rw /dev/root / +mount -n -t proc none /proc +mount -n -t hostfs none /mnt/host + +echo Registering available src packages... +( cd /src + for i in *; do + if test -d $i; then + echo " $i" + nix register-pkg $i /src/$i + fi + done +) + +export PATH=`nix get-pkg coreutils-4.5.7`/bin:$PATH + +echo +echo "=== starting interactive shell ===" + +sh + +echo +echo Shutting down... + +umount /proc +#sync +mount -n -o remount,ro /dev/root / +#sync From 18ebf518de325c7059648bfd6df464d8d5204bb3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 13 Mar 2003 16:28:32 +0000 Subject: [PATCH 0003/6440] * Converted to C++. --- src/Makefile | 4 +- src/nix.c | 314 --------------------------------------------------- src/nix.cc | 220 ++++++++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+), 316 deletions(-) delete mode 100644 src/nix.c create mode 100644 src/nix.cc diff --git a/src/Makefile b/src/Makefile index 5f42afd04..4fd293059 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,2 +1,2 @@ -nix: nix.c - gcc -g -Wall -o nix nix.c -ldb-4 +nix: nix.cc + g++ -g -Wall -o nix nix.cc -ldb_cxx-4 diff --git a/src/nix.c b/src/nix.c deleted file mode 100644 index e8b18934b..000000000 --- a/src/nix.c +++ /dev/null @@ -1,314 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - - -#define PKGINFO_PATH "/pkg/sys/var/pkginfo" - - -typedef enum {true = 1, false = 0} bool; - - -static char * prog; -static char * dbfile = PKGINFO_PATH; - - -DB * openDB(char * dbname, bool readonly) -{ - int err; - DB * db; - - err = db_create(&db, 0, 0); - if (err) { - fprintf(stderr, "error creating db handle: %s\n", - db_strerror(err)); - return 0; - } - - err = db->open(db, dbfile, dbname, - DB_HASH, readonly ? DB_RDONLY : DB_CREATE, 0666); - if (err) { - fprintf(stderr, "creating opening %s: %s\n", - dbfile, db_strerror(err)); - db->close(db, 0); - return 0; - } - - return db; -} - - -bool queryDB(char * dbname, char * key, char * * data) -{ - DB * db = 0; - DBT kt, dt; - int err; - - db = openDB(dbname, true); - if (!db) goto bad; - - *data = 0; - - memset(&kt, 0, sizeof(kt)); - memset(&dt, 0, sizeof(dt)); - kt.size = strlen(key); - kt.data = key; - - err = db->get(db, 0, &kt, &dt, 0); - if (!err) { - *data = malloc(dt.size + 1); - memcpy(*data, dt.data, dt.size); - (*data)[dt.size] = 0; - } else if (err != DB_NOTFOUND) { - fprintf(stderr, "creating opening %s: %s\n", - dbfile, db_strerror(err)); - goto bad; - } - - db->close(db, 0); - return true; - - bad: - if (db) db->close(db, 0); - return false; -} - - -bool setDB(char * dbname, char * key, char * data) -{ - DB * db = 0; - DBT kt, dt; - int err; - - db = openDB(dbname, false); - if (!db) goto bad; - - memset(&kt, 0, sizeof(kt)); - memset(&dt, 0, sizeof(dt)); - kt.size = strlen(key); - kt.data = key; - dt.size = strlen(data); - dt.data = data; - - err = db->put(db, 0, &kt, &dt, 0); - if (err) { - fprintf(stderr, "error storing data in %s: %s\n", - dbfile, db_strerror(err)); - goto bad; - } - - db->close(db, 0); - return true; - - bad: - if (db) db->close(db, 0); - return false; -} - - -bool delDB(char * dbname, char * key) -{ - DB * db = 0; - DBT kt; - int err; - - db = openDB(dbname, false); - if (!db) goto bad; - - memset(&kt, 0, sizeof(kt)); - kt.size = strlen(key); - kt.data = key; - - err = db->del(db, 0, &kt, 0); - if (err) { - fprintf(stderr, "error deleting data from %s: %s\n", - dbfile, db_strerror(err)); - goto bad; - } - - db->close(db, 0); - return true; - - bad: - if (db) db->close(db, 0); - return false; -} - - -bool getPkg(int argc, char * * argv) -{ - char * pkg; - char * src = 0; - char * inst = 0; - char inst2[1024]; - char cmd[2048]; - int res; - - if (argc != 1) { - fprintf(stderr, "arguments missing in get-pkg\n"); - return false; - } - - pkg = argv[0]; - - if (!queryDB("pkginst", pkg, &inst)) return false; - - if (inst) { - printf("%s\n", inst); - free(inst); - } else { - - fprintf(stderr, "package %s is not yet installed\n", pkg); - - if (!queryDB("pkgsrc", pkg, &src)) return false; - - if (!src) { - fprintf(stderr, "source of package %s is not known\n", pkg); - return false; - } - - if (snprintf(inst2, sizeof(inst2), "/pkg/%s", pkg) >= sizeof(inst2)) { - fprintf(stderr, "buffer overflow\n"); - free(src); - return false; - } - - if (snprintf(cmd, sizeof(cmd), "rsync -a \"%s\"/ \"%s\"", - src, inst2) >= sizeof(cmd)) - { - fprintf(stderr, "buffer overflow\n"); - free(src); - return false; - } - - res = system(cmd); - if (!WIFEXITED(res) || WEXITSTATUS(res) != 0) { - fprintf(stderr, "unable to copy sources\n"); - free(src); - return false; - } - - if (chdir(inst2)) { - fprintf(stderr, "unable to chdir to package directory\n"); - free(src); - return false; - } - - /* Prepare for building. Clean the environment so that the - build process does not inherit things it shouldn't. */ - setenv("PATH", "/pkg/sys/bin", 1); - - res = system("./buildme"); - if (!WIFEXITED(res) || WEXITSTATUS(res) != 0) { - fprintf(stderr, "unable to build package\n"); - free(src); - return false; - } - - setDB("pkginst", pkg, inst2); - - free(src); - - printf("%s\n", inst2); - } - - return true; -} - - -bool registerPkg(int argc, char * * argv) -{ - char * pkg; - char * src; - - if (argc != 2) { - fprintf(stderr, "arguments missing in register-pkg\n"); - return false; - } - - pkg = argv[0]; - src = argv[1]; - - return setDB("pkgsrc", pkg, src); -} - - -/* This is primarily used for bootstrapping. */ -bool registerInstalledPkg(int argc, char * * argv) -{ - char * pkg; - char * inst; - - if (argc != 2) { - fprintf(stderr, "arguments missing in register-installed-pkg\n"); - return false; - } - - pkg = argv[0]; - inst = argv[1]; - - if (strcmp(inst, "") == 0) - return delDB("pkginst", pkg); - else - return setDB("pkginst", pkg, inst); -} - - -bool run(int argc, char * * argv) -{ - char * cmd; - - if (argc < 1) { - fprintf(stderr, "command not specified\n"); - return false; - } - - cmd = argv[0]; - argc--, argv++; - - if (strcmp(cmd, "get-pkg") == 0) - return getPkg(argc, argv); - else if (strcmp(cmd, "register-pkg") == 0) - return registerPkg(argc, argv); - else if (strcmp(cmd, "register-installed-pkg") == 0) - return registerInstalledPkg(argc, argv); - else { - fprintf(stderr, "unknown command: %s\n", cmd); - return false; - } -} - - -int main(int argc, char * * argv) -{ - int c; - - prog = argv[0]; - - while ((c = getopt(argc, argv, "d:")) != EOF) { - - switch (c) { - - case 'd': - dbfile = optarg; - break; - - default: - fprintf(stderr, "unknown option\n"); - break; - } - - } - - argc -= optind, argv += optind; - - if (!run(argc, argv)) - return 1; - else - return 0; -} diff --git a/src/nix.cc b/src/nix.cc new file mode 100644 index 000000000..0c7865270 --- /dev/null +++ b/src/nix.cc @@ -0,0 +1,220 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace std; + + +#define PKGINFO_PATH "/pkg/sys/var/pkginfo" + + +static string prog; +static string dbfile = PKGINFO_PATH; + + +class Db2 : public Db +{ +public: + Db2(DbEnv *env, u_int32_t flags) + : Db(env, flags) + { + } + + ~Db2() + { + close(0); + } +}; + + +auto_ptr openDB(const string & dbname, bool readonly) +{ + auto_ptr db; + + db = auto_ptr(new Db2(0, 0)); + + db->open(dbfile.c_str(), dbname.c_str(), + DB_HASH, readonly ? DB_RDONLY : DB_CREATE, 0666); + + return db; +} + + +bool queryDB(const string & dbname, const string & key, string & data) +{ + int err; + auto_ptr db = openDB(dbname, true); + + Dbt kt((void *) key.c_str(), key.length()); + Dbt dt; + + err = db->get(0, &kt, &dt, 0); + if (err) return false; + + data = string((char *) dt.get_data(), dt.get_size()); + + return true; +} + + +void setDB(const string & dbname, const string & key, const string & data) +{ + auto_ptr db = openDB(dbname, false); + Dbt kt((void *) key.c_str(), key.length()); + Dbt dt((void *) data.c_str(), data.length()); + db->put(0, &kt, &dt, 0); +} + + +void delDB(const string & dbname, const string & key) +{ + auto_ptr db = openDB(dbname, false); + Dbt kt((void *) key.c_str(), key.length()); + db->del(0, &kt, 0); +} + + +void getPkg(int argc, char * * argv) +{ + string pkg; + string src; + string inst; + string cmd; + int res; + + if (argc != 1) + throw string("arguments missing in get-pkg"); + + pkg = argv[0]; + + if (queryDB("pkginst", pkg, inst)) { + cout << inst << endl; + return; + } + + cerr << "package " << pkg << " is not yet installed\n"; + + if (!queryDB("pkgsrc", pkg, src)) + throw string("source of package " + string(pkg) + " is not known"); + + inst = "/pkg/" + pkg; + + cmd = "rsync -a \"" + src + "\"/ \"" + inst + "\""; + + res = system(cmd.c_str()); + if (!WIFEXITED(res) || WEXITSTATUS(res) != 0) + throw string("unable to copy sources"); + + if (chdir(inst.c_str())) + throw string("unable to chdir to package directory"); + + /* Prepare for building. Clean the environment so that the + build process does not inherit things it shouldn't. */ + setenv("PATH", "/pkg/sys/bin", 1); + + res = system("./buildme"); + if (!WIFEXITED(res) || WEXITSTATUS(res) != 0) + throw string("unable to build package"); + + setDB("pkginst", pkg, inst); + + cout << inst << endl; +} + + +void registerPkg(int argc, char * * argv) +{ + char * pkg; + char * src; + + if (argc != 2) + throw string("arguments missing in register-pkg"); + + pkg = argv[0]; + src = argv[1]; + + setDB("pkgsrc", pkg, src); +} + + +/* This is primarily used for bootstrapping. */ +void registerInstalledPkg(int argc, char * * argv) +{ + string pkg; + string inst; + + if (argc != 2) + throw string("arguments missing in register-installed-pkg"); + + pkg = argv[0]; + inst = argv[1]; + + if (inst == "") + delDB("pkginst", pkg); + else + setDB("pkginst", pkg, inst); +} + + +void run(int argc, char * * argv) +{ + string cmd; + + if (argc < 1) + throw string("command not specified\n"); + + cmd = argv[0]; + argc--, argv++; + + if (cmd == "get-pkg") + getPkg(argc, argv); + else if (cmd == "register-pkg") + registerPkg(argc, argv); + else if (cmd == "register-installed-pkg") + registerInstalledPkg(argc, argv); + else + throw string("unknown command: " + string(cmd)); +} + + +int main(int argc, char * * argv) +{ + int c; + + prog = argv[0]; + + while ((c = getopt(argc, argv, "d:")) != EOF) { + + switch (c) { + + case 'd': + dbfile = optarg; + break; + + default: + cerr << "unknown option\n"; + break; + } + + } + + argc -= optind, argv += optind; + + try { + run(argc, argv); + return 0; + } catch (DbException e) { + cerr << "db exception: " << e.what() << endl; + return 1; + } catch (string s) { + cerr << s << endl; + return 1; + } +} From 8999f923ea1a459b3e4d404745b001323647711a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 14 Mar 2003 16:43:14 +0000 Subject: [PATCH 0004/6440] * Improved Nix. Resources (package descriptors and other source files) are now referenced using their cryptographic hashes. This ensures that if two package descriptors have the same contents, then they describe the same package. This property is not as trivial as it sounds: generally import relations cause this property not to hold w.r.t. temporality. But since imports also use hashes to reference other packages, equality follows by induction. --- pkg/aterm-2.0-build.sh | 13 ++ pkg/aterm-2.0.nix | 11 ++ src/nix.cc | 371 +++++++++++++++++++++++++++++++---------- sys/bootstrap | 14 +- sys/start | 18 +- 5 files changed, 321 insertions(+), 106 deletions(-) create mode 100755 pkg/aterm-2.0-build.sh create mode 100644 pkg/aterm-2.0.nix diff --git a/pkg/aterm-2.0-build.sh b/pkg/aterm-2.0-build.sh new file mode 100755 index 000000000..5d65b7878 --- /dev/null +++ b/pkg/aterm-2.0-build.sh @@ -0,0 +1,13 @@ +#! /bin/sh + +export PATH=$utils/bin +export LIBRARY_PATH=$glibc/lib +export CC=$gcc/bin/gcc +export CFLAGS="-isystem $glibc/include -isystem $kernel/include" + +top=`pwd` +tar xvfz $src +cd aterm-2.0 +./configure --prefix=$top +make +make install diff --git a/pkg/aterm-2.0.nix b/pkg/aterm-2.0.nix new file mode 100644 index 000000000..2484001be --- /dev/null +++ b/pkg/aterm-2.0.nix @@ -0,0 +1,11 @@ +# Dependencies. +utils <- 5703121fe19cbeeaee7edd659cf4a25b # prog-bootstrap +gcc <- 02212b3dc4e50349376975367d433929 # gcc-bootstrap +glibc <- c0ce03ee0bab298babbe7e3b6159d36c # glibc-bootstrap +kernel <- 3dc8333a2c2b4d627b892755417acf89 # kernel-bootstrap + +# Original sources. +src = 853474e4bcf4a85f7d38a0676b36bded # aterm-2.0.tar.gz + +# Build script. +build = ee7ae4ade69f846d2385871bf532ef7e # aterm-2.0-build.sh diff --git a/src/nix.cc b/src/nix.cc index 0c7865270..8108c2fca 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -1,6 +1,10 @@ #include +#include #include #include +#include +#include +#include #include #include @@ -15,10 +19,16 @@ using namespace std; #define PKGINFO_PATH "/pkg/sys/var/pkginfo" +static string dbRefs = "refs"; +static string dbInstPkgs = "pkginst"; + + static string prog; static string dbfile = PKGINFO_PATH; +/* Wrapper class that ensures that the database is closed upon + object destruction. */ class Db2 : public Db { public: @@ -81,85 +91,250 @@ void delDB(const string & dbname, const string & key) } -void getPkg(int argc, char * * argv) +/* Verify that a reference is valid (that is, is a MD5 hash code). */ +void checkRef(const string & s) { - string pkg; - string src; - string inst; - string cmd; - int res; - - if (argc != 1) - throw string("arguments missing in get-pkg"); - - pkg = argv[0]; - - if (queryDB("pkginst", pkg, inst)) { - cout << inst << endl; - return; + string err = "invalid reference: " + s; + if (s.length() != 32) + throw err; + for (int i = 0; i < 32; i++) { + char c = s[i]; + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f'))) + throw err; } - - cerr << "package " << pkg << " is not yet installed\n"; - - if (!queryDB("pkgsrc", pkg, src)) - throw string("source of package " + string(pkg) + " is not known"); - - inst = "/pkg/" + pkg; - - cmd = "rsync -a \"" + src + "\"/ \"" + inst + "\""; - - res = system(cmd.c_str()); - if (!WIFEXITED(res) || WEXITSTATUS(res) != 0) - throw string("unable to copy sources"); - - if (chdir(inst.c_str())) - throw string("unable to chdir to package directory"); - - /* Prepare for building. Clean the environment so that the - build process does not inherit things it shouldn't. */ - setenv("PATH", "/pkg/sys/bin", 1); - - res = system("./buildme"); - if (!WIFEXITED(res) || WEXITSTATUS(res) != 0) - throw string("unable to build package"); - - setDB("pkginst", pkg, inst); - - cout << inst << endl; } -void registerPkg(int argc, char * * argv) +/* Compute the MD5 hash of a file. */ +string makeRef(string filename) { - char * pkg; - char * src; + char hash[33]; + + FILE * pipe = popen(("md5sum " + filename).c_str(), "r"); + if (!pipe) throw string("cannot execute md5sum"); + + if (fread(hash, 32, 1, pipe) != 1) + throw string("cannot read hash from md5sum"); + hash[32] = 0; + + pclose(pipe); + + checkRef(hash); + return hash; +} + + +struct Dep +{ + string name; + string ref; + Dep(string _name, string _ref) + { + name = _name; + ref = _ref; + } +}; + +typedef list DepList; + + +void readPkgDescr(const string & pkgfile, + DepList & pkgImports, DepList & fileImports) +{ + ifstream file; + file.exceptions(ios::badbit); + file.open(pkgfile.c_str()); - if (argc != 2) - throw string("arguments missing in register-pkg"); + while (!file.eof()) { + string line; + getline(file, line); - pkg = argv[0]; - src = argv[1]; + int n = line.find('#'); + if (n >= 0) line = line.erase(n); - setDB("pkgsrc", pkg, src); + if ((int) line.find_first_not_of(" ") < 0) continue; + + istringstream str(line); + + string name, op, ref; + str >> name >> op >> ref; + + checkRef(ref); + + if (op == "<-") + pkgImports.push_back(Dep(name, ref)); + else if (op == "=") + fileImports.push_back(Dep(name, ref)); + else throw string("invalid operator " + op); + } +} + + +string getPkg(string pkgref); + + +typedef pair EnvPair; +typedef list Environment; + + +void installPkg(string pkgref) +{ + string pkgfile; + string src; + string path; + string cmd; + string builder; + + if (!queryDB("refs", pkgref, pkgfile)) + throw string("unknown package " + pkgref); + + cerr << "installing package " + pkgref + " from " + pkgfile + "\n"; + + /* Verify that the file hasn't changed. !!! race */ + if (makeRef(pkgfile) != pkgref) + throw string("file " + pkgfile + " is stale"); + + /* Read the package description file. */ + DepList pkgImports, fileImports; + readPkgDescr(pkgfile, pkgImports, fileImports); + + /* Recursively fetch all the dependencies, filling in the + environment as we go along. */ + Environment env; + + for (DepList::iterator it = pkgImports.begin(); + it != pkgImports.end(); it++) + { + cerr << "fetching package dependency " + << it->name << " <- " << it->ref + << endl; + env.push_back(EnvPair(it->name, getPkg(it->ref))); + } + + for (DepList::iterator it = fileImports.begin(); + it != fileImports.end(); it++) + { + cerr << "fetching file dependency " + << it->name << " = " << it->ref + << endl; + + string file; + + if (!queryDB("refs", it->ref, file)) + throw string("unknown file " + it->ref); + + if (makeRef(file) != it->ref) + throw string("file " + file + " is stale"); + + if (it->name == "build") + builder = file; + else + env.push_back(EnvPair(it->name, file)); + } + + if (builder == "") + throw string("no builder specified"); + + /* Construct a path for the installed package. */ + path = "/pkg/" + pkgref; + + /* Create the path. */ + if (mkdir(path.c_str(), 0777)) + throw string("unable to create directory " + path); + + /* Fork a child to build the package. */ + pid_t pid; + switch (pid = fork()) { + + case -1: + throw string("unable to fork"); + + case 0: /* child */ + + /* Go to the build directory. */ + if (chdir(path.c_str())) { + cout << "unable to chdir to package directory\n"; + _exit(1); + } + + /* Fill in the environment. We don't bother freeing the + strings, since we'll exec or die soon anyway. */ + const char * env2[env.size() + 1]; + int i = 0; + for (Environment::iterator it = env.begin(); + it != env.end(); it++, i++) + env2[i] = (new string(it->first + "=" + it->second))->c_str(); + env2[i] = 0; + + /* Execute the builder. This should not return. */ + execle(builder.c_str(), builder.c_str(), 0, env2); + + cout << strerror(errno) << endl; + + cout << "unable to execute builder\n"; + _exit(1); + } + + /* parent */ + + /* Wait for the child to finish. */ + int status; + if (waitpid(pid, &status, 0) != pid) + throw string("unable to wait for child"); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + throw string("unable to build package"); + + setDB(dbInstPkgs, pkgref, path); +} + + +string getPkg(string pkgref) +{ + string path; + checkRef(pkgref); + while (!queryDB(dbInstPkgs, pkgref, path)) + installPkg(pkgref); + return path; +} + + +string absPath(string filename) +{ + if (filename[0] != '/') { + char buf[PATH_MAX]; + if (!getcwd(buf, sizeof(buf))) + throw string("cannot get cwd"); + filename = string(buf) + "/" + filename; + /* !!! canonicalise */ + } + return filename; +} + + +void registerFile(string filename) +{ + filename = absPath(filename); + setDB(dbRefs, makeRef(filename), filename); } /* This is primarily used for bootstrapping. */ -void registerInstalledPkg(int argc, char * * argv) +void registerInstalledPkg(string pkgref, string path) { - string pkg; - string inst; - - if (argc != 2) - throw string("arguments missing in register-installed-pkg"); - - pkg = argv[0]; - inst = argv[1]; - - if (inst == "") - delDB("pkginst", pkg); + checkRef(pkgref); + if (path == "") + delDB(dbInstPkgs, pkgref); else - setDB("pkginst", pkg, inst); + setDB(dbInstPkgs, pkgref, path); +} + + +void initDB() +{ + openDB(dbRefs, false); + openDB(dbInstPkgs, false); } @@ -168,18 +343,29 @@ void run(int argc, char * * argv) string cmd; if (argc < 1) - throw string("command not specified\n"); + throw string("command not specified"); cmd = argv[0]; argc--, argv++; - if (cmd == "get-pkg") - getPkg(argc, argv); - else if (cmd == "register-pkg") - registerPkg(argc, argv); - else if (cmd == "register-installed-pkg") - registerInstalledPkg(argc, argv); - else + if (cmd == "init") { + if (argc != 0) + throw string("init doesn't have arguments"); + initDB(); + } else if (cmd == "getpkg") { + if (argc != 1) + throw string("arguments missing in getpkg"); + string path = getPkg(argv[0]); + cout << path << endl; + } else if (cmd == "reg") { + if (argc != 1) + throw string("arguments missing in reg"); + registerFile(argv[0]); + } else if (cmd == "regpkg") { + if (argc != 2) + throw string("arguments missing in regpkg"); + registerInstalledPkg(argv[0], argv[1]); + } else throw string("unknown command: " + string(cmd)); } @@ -190,31 +376,38 @@ int main(int argc, char * * argv) prog = argv[0]; - while ((c = getopt(argc, argv, "d:")) != EOF) { - - switch (c) { - - case 'd': - dbfile = optarg; - break; - - default: - cerr << "unknown option\n"; - break; - } - - } - - argc -= optind, argv += optind; + umask(0022); try { + + while ((c = getopt(argc, argv, "d:")) != EOF) { + + switch (c) { + + case 'd': + dbfile = optarg; + break; + + default: + throw string("unknown option"); + break; + + } + } + + argc -= optind, argv += optind; run(argc, argv); - return 0; + } catch (DbException e) { cerr << "db exception: " << e.what() << endl; return 1; + } catch (exception e) { + cerr << e.what() << endl; + return 1; } catch (string s) { cerr << s << endl; return 1; } + + return 0; } diff --git a/sys/bootstrap b/sys/bootstrap index e2265c1dc..f6aa16c86 100755 --- a/sys/bootstrap +++ b/sys/bootstrap @@ -26,7 +26,7 @@ mkdir -m 1777 $target/tmp pkgdb=$target/pkg/sys/var/pkginfo # Copy some programs and its libraries. -utils="/usr/bin/vi /bin/sh /bin/mount /bin/umount /bin/ls /bin/ln /bin/cp /bin/mv /bin/rm /bin/cat /bin/df /bin/pwd /usr/bin/ld /usr/bin/as /bin/sed /bin/chmod /bin/chown /usr/bin/expr /bin/mkdir /bin/rmdir /usr/bin/sort /usr/bin/uniq /bin/uname /usr/bin/grep /bin/sleep /usr/bin/rsync /usr/bin/make /usr/bin/cmp /bin/date /usr/bin/tr /usr/bin/ar /usr/bin/ranlib /usr/bin/basename /usr/bin/less ../src/nix" +utils="/usr/bin/vi /bin/sh /bin/mount /bin/umount /bin/ls /bin/ln /bin/cp /bin/mv /bin/rm /bin/cat /bin/df /bin/pwd /usr/bin/ld /usr/bin/as /bin/sed /bin/chmod /bin/chown /usr/bin/expr /bin/mkdir /bin/rmdir /usr/bin/sort /usr/bin/uniq /bin/uname /usr/bin/grep /bin/sleep /bin/gzip /usr/bin/make /usr/bin/cmp /bin/date /usr/bin/tr /usr/bin/ar /usr/bin/ranlib /usr/bin/basename /usr/bin/less /usr/bin/md5sum /bin/tar ../src/nix" bootlib=/pkg/prog-bootstrap/lib bootbin=/pkg/prog-bootstrap/bin mkdir -p $target/$bootlib @@ -36,27 +36,27 @@ libs=`ldd $utils | awk '{ print $3 }' | sort | uniq` echo $libs cp -p $libs $target/$bootlib for i in libc.so.6 libdl.so.2 libpthread.so.0 librt.so.1 libresolv.so.2 ld-linux.so.2; do rm $target/$bootlib/$i; done -../src/nix -d $pkgdb register-installed-pkg prog-bootstrap /pkg/prog-bootstrap +../src/nix -d $pkgdb regpkg 5703121fe19cbeeaee7edd659cf4a25b /pkg/prog-bootstrap mv $target/$bootbin/nix $target/pkg/sys/bin -../src/nix -d $pkgdb register-installed-pkg sys /pkg/sys +../src/nix -d $pkgdb regpkg 36bcbb801f5052739af8220c6ea51434 /pkg/sys # Copy the bootstrap gcc. echo Copying gcc... rsync -a ../bootstrap/gcc/inst/pkg $target -../src/nix -d $pkgdb register-installed-pkg gcc-bootstrap /pkg/gcc-bootstrap +../src/nix -d $pkgdb regpkg 02212b3dc4e50349376975367d433929 /pkg/gcc-bootstrap # Copy the bootstrap glibc. echo Copying glibc... glibcdir=/pkg/glibc-bootstrap rsync -a ../bootstrap/glibc/inst/pkg $target -../src/nix -d $pkgdb register-installed-pkg glibc-bootstrap $glibcdir +../src/nix -d $pkgdb regpkg c0ce03ee0bab298babbe7e3b6159d36c $glibcdir # Copy the bootstrap kernel header files. echo Copying kernel headers... kerneldir=/pkg/kernel-bootstrap rsync -a ../bootstrap/kernel/inst/pkg $target -../src/nix -d $pkgdb register-installed-pkg kernel-bootstrap $kerneldir +../src/nix -d $pkgdb regpkg 3dc8333a2c2b4d627b892755417acf89 $kerneldir # Compatibility. rm -rf $target/lib @@ -75,7 +75,7 @@ $target/$glibcdir/sbin/ldconfig -r $target # Source repository. rm -f $target/src -ln -sf /mnt/host/`pwd`/../src $target/src +ln -sf /mnt/host/`pwd`/../pkg $target/src # Copy boot script. cp -p ./start $target/pkg/sys/bin diff --git a/sys/start b/sys/start index f822b2fa7..b36cde297 100755 --- a/sys/start +++ b/sys/start @@ -4,7 +4,7 @@ export PATH=/pkg/sys/bin # Add in the utilities needed for booting. -export PATH=$PATH:`nix get-pkg prog-bootstrap`/bin +export PATH=$PATH:`nix getpkg 5703121fe19cbeeaee7edd659cf4a25b`/bin echo echo Starting up... @@ -14,17 +14,15 @@ mount -n -o remount,rw /dev/root / mount -n -t proc none /proc mount -n -t hostfs none /mnt/host -echo Registering available src packages... -( cd /src - for i in *; do - if test -d $i; then - echo " $i" - nix register-pkg $i /src/$i - fi - done +echo Registering available sources... +( if cd /src; then + for i in *; do + nix reg $i + done + fi ) -export PATH=`nix get-pkg coreutils-4.5.7`/bin:$PATH +export PATH=`nix getpkg coreutils-4.5.7`/bin:$PATH echo echo "=== starting interactive shell ===" From b3594e9eaf2b80c7c585035c5538ee59c608688f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 20 Mar 2003 16:52:30 +0000 Subject: [PATCH 0005/6440] * A script to instantiate package descriptors from templates. --- src/nix-instantiate | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100755 src/nix-instantiate diff --git a/src/nix-instantiate b/src/nix-instantiate new file mode 100755 index 000000000..242bcfaf9 --- /dev/null +++ b/src/nix-instantiate @@ -0,0 +1,23 @@ +#! /usr/bin/perl -w + +my $descr = $ARGV[0]; + +open DESCR, "< $descr"; + +while () { + chomp; + + if (/^(\w+)\s*=\s*([\w\d\.\/-]+)\s*(\#.*)?$/) { + my $name = $1; + my $file = $2; + my $out = `md5sum $file`; + $out =~ /^([0-9a-f]+)\s/; + my $hash = $1; + print "$name = $hash\n"; + } else { + print "$_\n"; + } + +} + +close DESCR; From f7a98e081dac20858cda21a6190f8d0222e59728 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 20 Mar 2003 16:53:00 +0000 Subject: [PATCH 0006/6440] * Various updates. --- src/nix.cc | 230 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 152 insertions(+), 78 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index 8108c2fca..db9967537 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -16,8 +16,11 @@ using namespace std; +#define PKGINFO_ENVVAR "NIX_DB" #define PKGINFO_PATH "/pkg/sys/var/pkginfo" +#define PKGHOME_ENVVAR "NIX_PKGHOME" + static string dbRefs = "refs"; static string dbInstPkgs = "pkginst"; @@ -27,6 +30,25 @@ static string prog; static string dbfile = PKGINFO_PATH; +static string pkgHome = "/pkg"; + + +class Error : public exception +{ + string err; +public: + Error(string _err) { err = _err; } + ~Error() throw () { }; + const char * what() const throw () { return err.c_str(); } +}; + +class UsageError : public Error +{ +public: + UsageError(string _err) : Error(_err) { }; +}; + + /* Wrapper class that ensures that the database is closed upon object destruction. */ class Db2 : public Db @@ -96,12 +118,12 @@ void checkRef(const string & s) { string err = "invalid reference: " + s; if (s.length() != 32) - throw err; + throw Error(err); for (int i = 0; i < 32; i++) { char c = s[i]; if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) - throw err; + throw Error(err); } } @@ -112,10 +134,10 @@ string makeRef(string filename) char hash[33]; FILE * pipe = popen(("md5sum " + filename).c_str(), "r"); - if (!pipe) throw string("cannot execute md5sum"); + if (!pipe) throw Error("cannot execute md5sum"); if (fread(hash, 32, 1, pipe) != 1) - throw string("cannot read hash from md5sum"); + throw Error("cannot read hash from md5sum"); hash[32] = 0; pclose(pipe); @@ -166,7 +188,7 @@ void readPkgDescr(const string & pkgfile, pkgImports.push_back(Dep(name, ref)); else if (op == "=") fileImports.push_back(Dep(name, ref)); - else throw string("invalid operator " + op); + else throw Error("invalid operator " + op); } } @@ -187,13 +209,13 @@ void installPkg(string pkgref) string builder; if (!queryDB("refs", pkgref, pkgfile)) - throw string("unknown package " + pkgref); + throw Error("unknown package " + pkgref); cerr << "installing package " + pkgref + " from " + pkgfile + "\n"; /* Verify that the file hasn't changed. !!! race */ if (makeRef(pkgfile) != pkgref) - throw string("file " + pkgfile + " is stale"); + throw Error("file " + pkgfile + " is stale"); /* Read the package description file. */ DepList pkgImports, fileImports; @@ -222,10 +244,10 @@ void installPkg(string pkgref) string file; if (!queryDB("refs", it->ref, file)) - throw string("unknown file " + it->ref); + throw Error("unknown file " + it->ref); if (makeRef(file) != it->ref) - throw string("file " + file + " is stale"); + throw Error("file " + file + " is stale"); if (it->name == "build") builder = file; @@ -234,57 +256,66 @@ void installPkg(string pkgref) } if (builder == "") - throw string("no builder specified"); + throw Error("no builder specified"); /* Construct a path for the installed package. */ - path = "/pkg/" + pkgref; + path = pkgHome + "/" + pkgref; /* Create the path. */ if (mkdir(path.c_str(), 0777)) - throw string("unable to create directory " + path); + throw Error("unable to create directory " + path); - /* Fork a child to build the package. */ - pid_t pid; - switch (pid = fork()) { + try { - case -1: - throw string("unable to fork"); + /* Fork a child to build the package. */ + pid_t pid; + switch (pid = fork()) { + + case -1: + throw Error("unable to fork"); - case 0: /* child */ + case 0: { /* child */ - /* Go to the build directory. */ - if (chdir(path.c_str())) { - cout << "unable to chdir to package directory\n"; + /* Go to the build directory. */ + if (chdir(path.c_str())) { + cout << "unable to chdir to package directory\n"; + _exit(1); + } + + /* Fill in the environment. We don't bother freeing the + strings, since we'll exec or die soon anyway. */ + const char * env2[env.size() + 1]; + int i = 0; + for (Environment::iterator it = env.begin(); + it != env.end(); it++, i++) + env2[i] = (new string(it->first + "=" + it->second))->c_str(); + env2[i] = 0; + + /* Execute the builder. This should not return. */ + execle(builder.c_str(), builder.c_str(), 0, env2); + + cout << strerror(errno) << endl; + + cout << "unable to execute builder\n"; _exit(1); } - /* Fill in the environment. We don't bother freeing the - strings, since we'll exec or die soon anyway. */ - const char * env2[env.size() + 1]; - int i = 0; - for (Environment::iterator it = env.begin(); - it != env.end(); it++, i++) - env2[i] = (new string(it->first + "=" + it->second))->c_str(); - env2[i] = 0; + } - /* Execute the builder. This should not return. */ - execle(builder.c_str(), builder.c_str(), 0, env2); + /* parent */ - cout << strerror(errno) << endl; - - cout << "unable to execute builder\n"; - _exit(1); - } - - /* parent */ - - /* Wait for the child to finish. */ - int status; - if (waitpid(pid, &status, 0) != pid) - throw string("unable to wait for child"); + /* Wait for the child to finish. */ + int status; + if (waitpid(pid, &status, 0) != pid) + throw Error("unable to wait for child"); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) - throw string("unable to build package"); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + throw Error("unable to build package"); + + } catch (exception &) { + system(("rm -rf " + path).c_str()); + throw; + } setDB(dbInstPkgs, pkgref, path); } @@ -305,7 +336,7 @@ string absPath(string filename) if (filename[0] != '/') { char buf[PATH_MAX]; if (!getcwd(buf, sizeof(buf))) - throw string("cannot get cwd"); + throw Error("cannot get cwd"); filename = string(buf) + "/" + filename; /* !!! canonicalise */ } @@ -343,69 +374,112 @@ void run(int argc, char * * argv) string cmd; if (argc < 1) - throw string("command not specified"); + throw UsageError("no command specified"); cmd = argv[0]; argc--, argv++; if (cmd == "init") { if (argc != 0) - throw string("init doesn't have arguments"); + throw UsageError("wrong number of arguments"); initDB(); } else if (cmd == "getpkg") { if (argc != 1) - throw string("arguments missing in getpkg"); + throw UsageError("wrong number of arguments"); string path = getPkg(argv[0]); cout << path << endl; - } else if (cmd == "reg") { + } else if (cmd == "regfile") { if (argc != 1) - throw string("arguments missing in reg"); + throw UsageError("wrong number of arguments"); registerFile(argv[0]); - } else if (cmd == "regpkg") { + } else if (cmd == "reginst") { if (argc != 2) - throw string("arguments missing in regpkg"); + throw UsageError("wrong number of arguments"); registerInstalledPkg(argv[0], argv[1]); } else - throw string("unknown command: " + string(cmd)); + throw UsageError("unknown command: " + string(cmd)); } - - -int main(int argc, char * * argv) + + +void printUsage() +{ + cerr << +"Usage: nix SUBCOMMAND OPTIONS... + +Subcommands: + + init + Initialize the database. + + regfile FILENAME + Register FILENAME keyed by its hash. + + reginst HASH PATH + Register an installed package. + + getpkg HASH + Ensure that the package referenced by HASH is installed. Prints + out the path of the package on stdout. +"; +} + + +void main2(int argc, char * * argv) { int c; - prog = argv[0]; - umask(0022); - try { + if (getenv(PKGINFO_ENVVAR)) + dbfile = getenv(PKGINFO_ENVVAR); - while ((c = getopt(argc, argv, "d:")) != EOF) { + if (getenv(PKGHOME_ENVVAR)) + pkgHome = getenv(PKGHOME_ENVVAR); + + opterr = 0; + + while ((c = getopt(argc, argv, "hd:")) != EOF) { - switch (c) { + switch (c) { - case 'd': - dbfile = optarg; - break; + case 'h': + printUsage(); + return; - default: - throw string("unknown option"); - break; + case 'd': + dbfile = optarg; + break; - } + default: + throw UsageError("invalid option `" + string(1, optopt) + "'"); + break; } + } - argc -= optind, argv += optind; - run(argc, argv); + argc -= optind, argv += optind; + run(argc, argv); +} - } catch (DbException e) { - cerr << "db exception: " << e.what() << endl; + +int main(int argc, char * * argv) +{ + prog = argv[0]; + + try { + try { + + main2(argc, argv); + + } catch (DbException e) { + throw Error(e.what()); + } + + } catch (UsageError & e) { + cerr << "error: " << e.what() << endl + << "Try `nix -h' for more information.\n"; return 1; - } catch (exception e) { - cerr << e.what() << endl; - return 1; - } catch (string s) { - cerr << s << endl; + } catch (exception & e) { + cerr << "error: " << e.what() << endl; return 1; } From cadc3852e44bc625872ef18f4407bff6797ac5dd Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 20 Mar 2003 22:23:48 +0000 Subject: [PATCH 0007/6440] * nix-instantiate now instantiantes the closure of the set of descriptor templates under the import relation. I.e., we can now say: nix-instantiate outdir foo.nix which will create descriptors for foo.nix and all imported packages in outdir/. --- src/nix-instantiate | 66 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/src/nix-instantiate b/src/nix-instantiate index 242bcfaf9..4823d2212 100755 --- a/src/nix-instantiate +++ b/src/nix-instantiate @@ -1,23 +1,61 @@ #! /usr/bin/perl -w -my $descr = $ARGV[0]; +use strict; +use FileHandle; +use File::Spec; -open DESCR, "< $descr"; +my $outdir = $ARGV[0]; -while () { - chomp; +my %donetmpls = (); - if (/^(\w+)\s*=\s*([\w\d\.\/-]+)\s*(\#.*)?$/) { - my $name = $1; - my $file = $2; - my $out = `md5sum $file`; - $out =~ /^([0-9a-f]+)\s/; - my $hash = $1; - print "$name = $hash\n"; - } else { - print "$_\n"; +sub convert { + my $descr = shift; + + if (defined $donetmpls{$descr}) { + return $donetmpls{$descr}; } + my ($x, $dir, $fn) = File::Spec->splitpath($descr); + + print "$descr\n"; + + my $IN = new FileHandle; + my $OUT = new FileHandle; + my $outfile = "$outdir/$fn"; + open $IN, "< $descr" or die "cannot open $descr"; + open $OUT, "> $outfile" or die "cannot create $outfile"; + + while (<$IN>) { + chomp; + + if (/^(\w+)\s*=\s*([+\w\d\.\/-]+)\s*(\#.*)?$/) { + my $name = $1; + my $file = $2; + $file = File::Spec->rel2abs($file, $dir); + my $out = `md5sum $file`; + die unless ($? == 0); + $out =~ /^([0-9a-f]+)\s/; + my $hash = $1; + print $OUT "$name = $hash\n"; + } elsif (/^(\w+)\s*<-\s*([+\w\d\.\/-]+)\s*(\#.*)?$/) { + my $name = $1; + my $file = $2; + $file = File::Spec->rel2abs($file, $dir); + $file = convert($file); + my $out = `md5sum $file`; + die unless ($? == 0); + $out =~ /^([0-9a-f]+)\s/; + my $hash = $1; + print $OUT "$name <- $hash\n"; + } else { + print $OUT "$_\n"; + } + } + + $donetmpls{$descr} = $outfile; + return $outfile; } -close DESCR; +for (my $i = 1; $i < scalar @ARGV; $i++) { + convert(File::Spec->rel2abs($ARGV[$i])); +} From 4c43711810c73a3899066b9401a9517f53e1b0f1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 20 Mar 2003 22:25:01 +0000 Subject: [PATCH 0008/6440] * Descriptor templates for the Pan newsreader and all its dependencies. --- test/dist/aterm-build.sh | 10 ++++++++++ test/dist/atk-build.sh | 12 ++++++++++++ test/dist/glib-build.sh | 10 ++++++++++ test/dist/gnet-build.sh | 12 ++++++++++++ test/dist/gtk+-build.sh | 12 ++++++++++++ test/dist/pan-build.sh | 12 ++++++++++++ test/dist/pango-build.sh | 12 ++++++++++++ test/dist/pkgconfig-build.sh | 10 ++++++++++ test/register | 18 ++++++++++++++++++ test/tmpl/aterm-2.0.nix | 5 +++++ test/tmpl/atk-1.2.0.nix | 6 ++++++ test/tmpl/glib-2.2.1.nix | 5 +++++ test/tmpl/gnet-1.1.8.nix | 6 ++++++ test/tmpl/gtk+-2.2.1.nix | 8 ++++++++ test/tmpl/pan-0.13.4.nix | 10 ++++++++++ test/tmpl/pango-1.2.1.nix | 6 ++++++ test/tmpl/pkgconfig-0.15.0.nix | 3 +++ 17 files changed, 157 insertions(+) create mode 100755 test/dist/aterm-build.sh create mode 100755 test/dist/atk-build.sh create mode 100755 test/dist/glib-build.sh create mode 100755 test/dist/gnet-build.sh create mode 100755 test/dist/gtk+-build.sh create mode 100755 test/dist/pan-build.sh create mode 100755 test/dist/pango-build.sh create mode 100755 test/dist/pkgconfig-build.sh create mode 100755 test/register create mode 100644 test/tmpl/aterm-2.0.nix create mode 100644 test/tmpl/atk-1.2.0.nix create mode 100644 test/tmpl/glib-2.2.1.nix create mode 100644 test/tmpl/gnet-1.1.8.nix create mode 100644 test/tmpl/gtk+-2.2.1.nix create mode 100644 test/tmpl/pan-0.13.4.nix create mode 100644 test/tmpl/pango-1.2.1.nix create mode 100644 test/tmpl/pkgconfig-0.15.0.nix diff --git a/test/dist/aterm-build.sh b/test/dist/aterm-build.sh new file mode 100755 index 000000000..cfc83806b --- /dev/null +++ b/test/dist/aterm-build.sh @@ -0,0 +1,10 @@ +#! /bin/sh + +export PATH=/bin:/usr/bin + +top=`pwd` +tar xvfz $src +cd aterm-* +./configure --prefix=$top +make +make install diff --git a/test/dist/atk-build.sh b/test/dist/atk-build.sh new file mode 100755 index 000000000..df881cbef --- /dev/null +++ b/test/dist/atk-build.sh @@ -0,0 +1,12 @@ +#! /bin/sh + +export PATH=$pkgconfig/bin:/bin:/usr/bin +export PKG_CONFIG_PATH=$glib/lib/pkgconfig +export LD_LIBRARY_PATH=$glib/lib + +top=`pwd` +tar xvfj $src +cd atk-* +./configure --prefix=$top +make +make install diff --git a/test/dist/glib-build.sh b/test/dist/glib-build.sh new file mode 100755 index 000000000..2100052be --- /dev/null +++ b/test/dist/glib-build.sh @@ -0,0 +1,10 @@ +#! /bin/sh + +export PATH=$pkgconfig/bin:/bin:/usr/bin + +top=`pwd` +tar xvfj $src +cd glib-* +./configure --prefix=$top +make +make install diff --git a/test/dist/gnet-build.sh b/test/dist/gnet-build.sh new file mode 100755 index 000000000..ec614b4bf --- /dev/null +++ b/test/dist/gnet-build.sh @@ -0,0 +1,12 @@ +#! /bin/sh + +export PATH=$pkgconfig/bin:/bin:/usr/bin +export PKG_CONFIG_PATH=$glib/lib/pkgconfig +export LD_LIBRARY_PATH=$glib/lib + +top=`pwd` +tar xvfz $src +cd gnet-* +./configure --prefix=$top +make +make install diff --git a/test/dist/gtk+-build.sh b/test/dist/gtk+-build.sh new file mode 100755 index 000000000..8c887fec4 --- /dev/null +++ b/test/dist/gtk+-build.sh @@ -0,0 +1,12 @@ +#! /bin/sh + +export PATH=$pkgconfig/bin:/bin:/usr/bin +export PKG_CONFIG_PATH=$glib/lib/pkgconfig:$atk/lib/pkgconfig:$pango/lib/pkgconfig +export LD_LIBRARY_PATH=$glib/lib + +top=`pwd` +tar xvfj $src +cd gtk+-* +./configure --prefix=$top +make +make install diff --git a/test/dist/pan-build.sh b/test/dist/pan-build.sh new file mode 100755 index 000000000..468814ff6 --- /dev/null +++ b/test/dist/pan-build.sh @@ -0,0 +1,12 @@ +#! /bin/sh + +export PATH=$pkgconfig/bin:$gnet/bin:/bin:/usr/bin +export PKG_CONFIG_PATH=$glib/lib/pkgconfig:$atk/lib/pkgconfig:$pango/lib/pkgconfig:$gtk/lib/pkgconfig +export LD_LIBRARY_PATH=$gnet/lib + +top=`pwd` +tar xvfj $src +cd pan-* +./configure --prefix=$top +make +make install diff --git a/test/dist/pango-build.sh b/test/dist/pango-build.sh new file mode 100755 index 000000000..fd43c274b --- /dev/null +++ b/test/dist/pango-build.sh @@ -0,0 +1,12 @@ +#! /bin/sh + +export PATH=$pkgconfig/bin:/bin:/usr/bin +export PKG_CONFIG_PATH=$glib/lib/pkgconfig +export LD_LIBRARY_PATH=$glib/lib + +top=`pwd` +tar xvfj $src +cd pango-* +./configure --prefix=$top +make +make install diff --git a/test/dist/pkgconfig-build.sh b/test/dist/pkgconfig-build.sh new file mode 100755 index 000000000..522a05716 --- /dev/null +++ b/test/dist/pkgconfig-build.sh @@ -0,0 +1,10 @@ +#! /bin/sh + +export PATH=/bin:/usr/bin + +top=`pwd` +tar xvfz $src +cd pkgconfig-* +./configure --prefix=$top +make +make install diff --git a/test/register b/test/register new file mode 100755 index 000000000..a50dd051a --- /dev/null +++ b/test/register @@ -0,0 +1,18 @@ +#! /bin/sh + +nix init + +root=/home/eelco/Dev/nix/test + +cd $root/tmpl + +if ! nix-instantiate $root/descr $root/tmpl/*.nix; then + exit 1; +fi + +for i in $root/dist/*; do nix regfile $i; done +for i in $root/descr/*; do + md5sum $i + nix regfile $i +done + diff --git a/test/tmpl/aterm-2.0.nix b/test/tmpl/aterm-2.0.nix new file mode 100644 index 000000000..084f27510 --- /dev/null +++ b/test/tmpl/aterm-2.0.nix @@ -0,0 +1,5 @@ +# Original sources. +src = ../dist/aterm-2.0.tar.gz + +# Build script. +build = ../dist/aterm-build.sh diff --git a/test/tmpl/atk-1.2.0.nix b/test/tmpl/atk-1.2.0.nix new file mode 100644 index 000000000..bbe63670c --- /dev/null +++ b/test/tmpl/atk-1.2.0.nix @@ -0,0 +1,6 @@ +pkgconfig <- ./pkgconfig-0.15.0.nix +glib <- ./glib-2.2.1.nix + +src = ../dist/atk-1.2.0.tar.bz2 + +build = ../dist/atk-build.sh diff --git a/test/tmpl/glib-2.2.1.nix b/test/tmpl/glib-2.2.1.nix new file mode 100644 index 000000000..f1d0d0847 --- /dev/null +++ b/test/tmpl/glib-2.2.1.nix @@ -0,0 +1,5 @@ +pkgconfig <- ./pkgconfig-0.15.0.nix + +src = ../dist/glib-2.2.1.tar.bz2 + +build = ../dist/glib-build.sh diff --git a/test/tmpl/gnet-1.1.8.nix b/test/tmpl/gnet-1.1.8.nix new file mode 100644 index 000000000..4d6ba1f51 --- /dev/null +++ b/test/tmpl/gnet-1.1.8.nix @@ -0,0 +1,6 @@ +pkgconfig <- ./pkgconfig-0.15.0.nix +glib <- ./glib-2.2.1.nix + +src = ../dist/gnet-1.1.8.tar.gz + +build = ../dist/gnet-build.sh diff --git a/test/tmpl/gtk+-2.2.1.nix b/test/tmpl/gtk+-2.2.1.nix new file mode 100644 index 000000000..83c2835df --- /dev/null +++ b/test/tmpl/gtk+-2.2.1.nix @@ -0,0 +1,8 @@ +pkgconfig <- ./pkgconfig-0.15.0.nix +glib <- ./glib-2.2.1.nix +atk <- ./atk-1.2.0.nix +pango <- ./pango-1.2.1.nix + +src = ../dist/gtk+-2.2.1.tar.bz2 + +build = ../dist/gtk+-build.sh diff --git a/test/tmpl/pan-0.13.4.nix b/test/tmpl/pan-0.13.4.nix new file mode 100644 index 000000000..574fb6f40 --- /dev/null +++ b/test/tmpl/pan-0.13.4.nix @@ -0,0 +1,10 @@ +pkgconfig <- ./pkgconfig-0.15.0.nix +glib <- ./glib-2.2.1.nix +atk <- ./atk-1.2.0.nix +pango <- ./pango-1.2.1.nix +gtk <- ./gtk+-2.2.1.nix +gnet <- ./gnet-1.1.8.nix + +src = ../dist/pan-0.13.4.tar.bz2 + +build = ../dist/pan-build.sh diff --git a/test/tmpl/pango-1.2.1.nix b/test/tmpl/pango-1.2.1.nix new file mode 100644 index 000000000..4c7e1afe7 --- /dev/null +++ b/test/tmpl/pango-1.2.1.nix @@ -0,0 +1,6 @@ +pkgconfig <- ./pkgconfig-0.15.0.nix +glib <- ./glib-2.2.1.nix + +src = ../dist/pango-1.2.1.tar.bz2 + +build = ../dist/pango-build.sh diff --git a/test/tmpl/pkgconfig-0.15.0.nix b/test/tmpl/pkgconfig-0.15.0.nix new file mode 100644 index 000000000..f93d40e3d --- /dev/null +++ b/test/tmpl/pkgconfig-0.15.0.nix @@ -0,0 +1,3 @@ +src = ../dist/pkgconfig-0.15.0.tar.gz + +build = ../dist/pkgconfig-build.sh From 88d257b17f0f668798568d68e2d2063f31fe8a2f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 21 Mar 2003 09:58:01 +0000 Subject: [PATCH 0009/6440] * Renamed dist -> build. --- test/{dist => build}/aterm-build.sh | 0 test/{dist => build}/atk-build.sh | 0 test/{dist => build}/glib-build.sh | 0 test/{dist => build}/gnet-build.sh | 0 test/{dist => build}/gtk+-build.sh | 0 test/{dist => build}/pan-build.sh | 0 test/{dist => build}/pango-build.sh | 0 test/{dist => build}/pkgconfig-build.sh | 0 test/tmpl/aterm-2.0.nix | 2 +- test/tmpl/atk-1.2.0.nix | 2 +- test/tmpl/glib-2.2.1.nix | 2 +- test/tmpl/gnet-1.1.8.nix | 2 +- test/tmpl/gtk+-2.2.1.nix | 2 +- test/tmpl/pan-0.13.4.nix | 2 +- test/tmpl/pango-1.2.1.nix | 2 +- test/tmpl/pkgconfig-0.15.0.nix | 2 +- 16 files changed, 8 insertions(+), 8 deletions(-) rename test/{dist => build}/aterm-build.sh (100%) rename test/{dist => build}/atk-build.sh (100%) rename test/{dist => build}/glib-build.sh (100%) rename test/{dist => build}/gnet-build.sh (100%) rename test/{dist => build}/gtk+-build.sh (100%) rename test/{dist => build}/pan-build.sh (100%) rename test/{dist => build}/pango-build.sh (100%) rename test/{dist => build}/pkgconfig-build.sh (100%) diff --git a/test/dist/aterm-build.sh b/test/build/aterm-build.sh similarity index 100% rename from test/dist/aterm-build.sh rename to test/build/aterm-build.sh diff --git a/test/dist/atk-build.sh b/test/build/atk-build.sh similarity index 100% rename from test/dist/atk-build.sh rename to test/build/atk-build.sh diff --git a/test/dist/glib-build.sh b/test/build/glib-build.sh similarity index 100% rename from test/dist/glib-build.sh rename to test/build/glib-build.sh diff --git a/test/dist/gnet-build.sh b/test/build/gnet-build.sh similarity index 100% rename from test/dist/gnet-build.sh rename to test/build/gnet-build.sh diff --git a/test/dist/gtk+-build.sh b/test/build/gtk+-build.sh similarity index 100% rename from test/dist/gtk+-build.sh rename to test/build/gtk+-build.sh diff --git a/test/dist/pan-build.sh b/test/build/pan-build.sh similarity index 100% rename from test/dist/pan-build.sh rename to test/build/pan-build.sh diff --git a/test/dist/pango-build.sh b/test/build/pango-build.sh similarity index 100% rename from test/dist/pango-build.sh rename to test/build/pango-build.sh diff --git a/test/dist/pkgconfig-build.sh b/test/build/pkgconfig-build.sh similarity index 100% rename from test/dist/pkgconfig-build.sh rename to test/build/pkgconfig-build.sh diff --git a/test/tmpl/aterm-2.0.nix b/test/tmpl/aterm-2.0.nix index 084f27510..b65714e6a 100644 --- a/test/tmpl/aterm-2.0.nix +++ b/test/tmpl/aterm-2.0.nix @@ -2,4 +2,4 @@ src = ../dist/aterm-2.0.tar.gz # Build script. -build = ../dist/aterm-build.sh +build = ../build/aterm-build.sh diff --git a/test/tmpl/atk-1.2.0.nix b/test/tmpl/atk-1.2.0.nix index bbe63670c..d540c90d8 100644 --- a/test/tmpl/atk-1.2.0.nix +++ b/test/tmpl/atk-1.2.0.nix @@ -3,4 +3,4 @@ glib <- ./glib-2.2.1.nix src = ../dist/atk-1.2.0.tar.bz2 -build = ../dist/atk-build.sh +build = ../build/atk-build.sh diff --git a/test/tmpl/glib-2.2.1.nix b/test/tmpl/glib-2.2.1.nix index f1d0d0847..5e5c6efdd 100644 --- a/test/tmpl/glib-2.2.1.nix +++ b/test/tmpl/glib-2.2.1.nix @@ -2,4 +2,4 @@ pkgconfig <- ./pkgconfig-0.15.0.nix src = ../dist/glib-2.2.1.tar.bz2 -build = ../dist/glib-build.sh +build = ../build/glib-build.sh diff --git a/test/tmpl/gnet-1.1.8.nix b/test/tmpl/gnet-1.1.8.nix index 4d6ba1f51..672cfdf5a 100644 --- a/test/tmpl/gnet-1.1.8.nix +++ b/test/tmpl/gnet-1.1.8.nix @@ -3,4 +3,4 @@ glib <- ./glib-2.2.1.nix src = ../dist/gnet-1.1.8.tar.gz -build = ../dist/gnet-build.sh +build = ../build/gnet-build.sh diff --git a/test/tmpl/gtk+-2.2.1.nix b/test/tmpl/gtk+-2.2.1.nix index 83c2835df..30c77aec3 100644 --- a/test/tmpl/gtk+-2.2.1.nix +++ b/test/tmpl/gtk+-2.2.1.nix @@ -5,4 +5,4 @@ pango <- ./pango-1.2.1.nix src = ../dist/gtk+-2.2.1.tar.bz2 -build = ../dist/gtk+-build.sh +build = ../build/gtk+-build.sh diff --git a/test/tmpl/pan-0.13.4.nix b/test/tmpl/pan-0.13.4.nix index 574fb6f40..3aeca04ed 100644 --- a/test/tmpl/pan-0.13.4.nix +++ b/test/tmpl/pan-0.13.4.nix @@ -7,4 +7,4 @@ gnet <- ./gnet-1.1.8.nix src = ../dist/pan-0.13.4.tar.bz2 -build = ../dist/pan-build.sh +build = ../build/pan-build.sh diff --git a/test/tmpl/pango-1.2.1.nix b/test/tmpl/pango-1.2.1.nix index 4c7e1afe7..2436bc49a 100644 --- a/test/tmpl/pango-1.2.1.nix +++ b/test/tmpl/pango-1.2.1.nix @@ -3,4 +3,4 @@ glib <- ./glib-2.2.1.nix src = ../dist/pango-1.2.1.tar.bz2 -build = ../dist/pango-build.sh +build = ../build/pango-build.sh diff --git a/test/tmpl/pkgconfig-0.15.0.nix b/test/tmpl/pkgconfig-0.15.0.nix index f93d40e3d..882a69234 100644 --- a/test/tmpl/pkgconfig-0.15.0.nix +++ b/test/tmpl/pkgconfig-0.15.0.nix @@ -1,3 +1,3 @@ src = ../dist/pkgconfig-0.15.0.tar.gz -build = ../dist/pkgconfig-build.sh +build = ../build/pkgconfig-build.sh From e582ee67cd682c13667daccf33e8071189ef946c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 21 Mar 2003 14:10:06 +0000 Subject: [PATCH 0010/6440] * Fetch sources from the network. --- pkg/aterm-2.0-build.sh | 2 +- pkg/aterm-2.0.nix | 3 ++- src/nix-instantiate | 34 +++++++++++++++++++++++++++++----- test/build/gtk+-build.sh | 2 +- test/build/pan-build.sh | 2 +- test/register | 19 ++++++++++++------- test/tmpl/aterm-2.0.nix | 2 +- test/tmpl/atk-1.2.0.nix | 2 +- test/tmpl/glib-2.2.1.nix | 2 +- test/tmpl/gnet-1.1.8.nix | 2 +- test/tmpl/gtk+-2.2.1.nix | 2 +- test/tmpl/pango-1.2.1.nix | 2 +- test/tmpl/pkgconfig-0.15.0.nix | 2 +- 13 files changed, 53 insertions(+), 23 deletions(-) diff --git a/pkg/aterm-2.0-build.sh b/pkg/aterm-2.0-build.sh index 5d65b7878..872e7aac8 100755 --- a/pkg/aterm-2.0-build.sh +++ b/pkg/aterm-2.0-build.sh @@ -1,6 +1,6 @@ #! /bin/sh -export PATH=$utils/bin +export PATH=$sys1/bin:$sys2/bin export LIBRARY_PATH=$glibc/lib export CC=$gcc/bin/gcc export CFLAGS="-isystem $glibc/include -isystem $kernel/include" diff --git a/pkg/aterm-2.0.nix b/pkg/aterm-2.0.nix index 2484001be..da2494ad8 100644 --- a/pkg/aterm-2.0.nix +++ b/pkg/aterm-2.0.nix @@ -1,5 +1,6 @@ # Dependencies. -utils <- 5703121fe19cbeeaee7edd659cf4a25b # prog-bootstrap +sys1 <- 1e80cb7e0fbfc9f5c0509a465ecdf6cf # sys1-bootstrap +sys2 <- 7512824c50c61ea8d89d0f193a4f72d1 # sys2-bootstrap gcc <- 02212b3dc4e50349376975367d433929 # gcc-bootstrap glibc <- c0ce03ee0bab298babbe7e3b6159d36c # glibc-bootstrap kernel <- 3dc8333a2c2b4d627b892755417acf89 # kernel-bootstrap diff --git a/src/nix-instantiate b/src/nix-instantiate index 4823d2212..63c858864 100755 --- a/src/nix-instantiate +++ b/src/nix-instantiate @@ -4,10 +4,34 @@ use strict; use FileHandle; use File::Spec; -my $outdir = $ARGV[0]; +my $outdir = File::Spec->rel2abs($ARGV[0]); +my $netdir = File::Spec->rel2abs($ARGV[1]); my %donetmpls = (); +sub fetchFile { + my $loc = shift; + + if ($loc =~ /^([+\w\d\.\/-]+)$/) { + return $1; + } elsif ($loc =~ /^url\((.*)\)$/) { + my $url = $1; + $url =~ /\/([^\/]+)$/ || die "invalid url $url"; + my $fn = "$netdir/$1"; + if (! -f $fn) { + print "fetching $url...\n"; + system "cd $netdir; wget --quiet -N $url"; + if ($? != 0) { + unlink($fn); + die; + } + } + return $fn; + } else { + die "invalid file specified $loc"; + } +} + sub convert { my $descr = shift; @@ -28,9 +52,9 @@ sub convert { while (<$IN>) { chomp; - if (/^(\w+)\s*=\s*([+\w\d\.\/-]+)\s*(\#.*)?$/) { - my $name = $1; - my $file = $2; + if (/^(\w+)\s*=\s*([^\#\s]*)\s*(\#.*)?$/) { + my ($name, $loc) = ($1, $2); + my $file = fetchFile($loc); $file = File::Spec->rel2abs($file, $dir); my $out = `md5sum $file`; die unless ($? == 0); @@ -56,6 +80,6 @@ sub convert { return $outfile; } -for (my $i = 1; $i < scalar @ARGV; $i++) { +for (my $i = 2; $i < scalar @ARGV; $i++) { convert(File::Spec->rel2abs($ARGV[$i])); } diff --git a/test/build/gtk+-build.sh b/test/build/gtk+-build.sh index 8c887fec4..d2b3d694a 100755 --- a/test/build/gtk+-build.sh +++ b/test/build/gtk+-build.sh @@ -2,7 +2,7 @@ export PATH=$pkgconfig/bin:/bin:/usr/bin export PKG_CONFIG_PATH=$glib/lib/pkgconfig:$atk/lib/pkgconfig:$pango/lib/pkgconfig -export LD_LIBRARY_PATH=$glib/lib +export LD_LIBRARY_PATH=$glib/lib:$atk/lib:$pango/lib top=`pwd` tar xvfj $src diff --git a/test/build/pan-build.sh b/test/build/pan-build.sh index 468814ff6..a29255881 100755 --- a/test/build/pan-build.sh +++ b/test/build/pan-build.sh @@ -2,7 +2,7 @@ export PATH=$pkgconfig/bin:$gnet/bin:/bin:/usr/bin export PKG_CONFIG_PATH=$glib/lib/pkgconfig:$atk/lib/pkgconfig:$pango/lib/pkgconfig:$gtk/lib/pkgconfig -export LD_LIBRARY_PATH=$gnet/lib +export LD_LIBRARY_PATH=$gnet/lib:$atk/lib:$pango/lib:$gtk/lib:$gnet/lib top=`pwd` tar xvfj $src diff --git a/test/register b/test/register index a50dd051a..60cabd292 100755 --- a/test/register +++ b/test/register @@ -1,17 +1,22 @@ #! /bin/sh +root=/home/eelco/Dev/nix/test +cd $root + +mkdir -p db +mkdir -p pkg +mkdir -p descr +mkdir -p netcache + nix init -root=/home/eelco/Dev/nix/test - -cd $root/tmpl - -if ! nix-instantiate $root/descr $root/tmpl/*.nix; then +if ! nix-instantiate descr netcache tmpl/*.nix; then exit 1; fi -for i in $root/dist/*; do nix regfile $i; done -for i in $root/descr/*; do +for i in netcache/*; do nix regfile $i; done +for i in build/*; do nix regfile $i; done +for i in descr/*; do md5sum $i nix regfile $i done diff --git a/test/tmpl/aterm-2.0.nix b/test/tmpl/aterm-2.0.nix index b65714e6a..24c427faf 100644 --- a/test/tmpl/aterm-2.0.nix +++ b/test/tmpl/aterm-2.0.nix @@ -1,5 +1,5 @@ # Original sources. -src = ../dist/aterm-2.0.tar.gz +src = url(http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz) # Build script. build = ../build/aterm-build.sh diff --git a/test/tmpl/atk-1.2.0.nix b/test/tmpl/atk-1.2.0.nix index d540c90d8..9899a95b1 100644 --- a/test/tmpl/atk-1.2.0.nix +++ b/test/tmpl/atk-1.2.0.nix @@ -1,6 +1,6 @@ pkgconfig <- ./pkgconfig-0.15.0.nix glib <- ./glib-2.2.1.nix -src = ../dist/atk-1.2.0.tar.bz2 +src = url(ftp://ftp.gtk.org/pub/gtk/v2.2/atk-1.2.0.tar.bz2) build = ../build/atk-build.sh diff --git a/test/tmpl/glib-2.2.1.nix b/test/tmpl/glib-2.2.1.nix index 5e5c6efdd..d9fdf0f1f 100644 --- a/test/tmpl/glib-2.2.1.nix +++ b/test/tmpl/glib-2.2.1.nix @@ -1,5 +1,5 @@ pkgconfig <- ./pkgconfig-0.15.0.nix -src = ../dist/glib-2.2.1.tar.bz2 +src = url(ftp://ftp.gtk.org/pub/gtk/v2.2/glib-2.2.1.tar.bz2) build = ../build/glib-build.sh diff --git a/test/tmpl/gnet-1.1.8.nix b/test/tmpl/gnet-1.1.8.nix index 672cfdf5a..7f2b33cab 100644 --- a/test/tmpl/gnet-1.1.8.nix +++ b/test/tmpl/gnet-1.1.8.nix @@ -1,6 +1,6 @@ pkgconfig <- ./pkgconfig-0.15.0.nix glib <- ./glib-2.2.1.nix -src = ../dist/gnet-1.1.8.tar.gz +src = url(http://www.gnetlibrary.org/src/gnet-1.1.8.tar.gz) build = ../build/gnet-build.sh diff --git a/test/tmpl/gtk+-2.2.1.nix b/test/tmpl/gtk+-2.2.1.nix index 30c77aec3..b1a6fcbfb 100644 --- a/test/tmpl/gtk+-2.2.1.nix +++ b/test/tmpl/gtk+-2.2.1.nix @@ -3,6 +3,6 @@ glib <- ./glib-2.2.1.nix atk <- ./atk-1.2.0.nix pango <- ./pango-1.2.1.nix -src = ../dist/gtk+-2.2.1.tar.bz2 +src = url(ftp://ftp.gtk.org/pub/gtk/v2.2/gtk+-2.2.1.tar.bz2) build = ../build/gtk+-build.sh diff --git a/test/tmpl/pango-1.2.1.nix b/test/tmpl/pango-1.2.1.nix index 2436bc49a..4be4c8991 100644 --- a/test/tmpl/pango-1.2.1.nix +++ b/test/tmpl/pango-1.2.1.nix @@ -1,6 +1,6 @@ pkgconfig <- ./pkgconfig-0.15.0.nix glib <- ./glib-2.2.1.nix -src = ../dist/pango-1.2.1.tar.bz2 +src = url(ftp://ftp.gtk.org/pub/gtk/v2.2/pango-1.2.1.tar.bz2) build = ../build/pango-build.sh diff --git a/test/tmpl/pkgconfig-0.15.0.nix b/test/tmpl/pkgconfig-0.15.0.nix index 882a69234..3ee7c13cd 100644 --- a/test/tmpl/pkgconfig-0.15.0.nix +++ b/test/tmpl/pkgconfig-0.15.0.nix @@ -1,3 +1,3 @@ -src = ../dist/pkgconfig-0.15.0.tar.gz +src = url(http://www.freedesktop.org/software/pkgconfig/releases/pkgconfig-0.15.0.tar.gz) build = ../build/pkgconfig-build.sh From fa51d6fcd9ee7efc897e8e07a2db7a41f974d6a6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 21 Mar 2003 14:11:44 +0000 Subject: [PATCH 0011/6440] * Forgot to commit this one. --- test/tmpl/pan-0.13.4.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tmpl/pan-0.13.4.nix b/test/tmpl/pan-0.13.4.nix index 3aeca04ed..b206657fd 100644 --- a/test/tmpl/pan-0.13.4.nix +++ b/test/tmpl/pan-0.13.4.nix @@ -5,6 +5,6 @@ pango <- ./pango-1.2.1.nix gtk <- ./gtk+-2.2.1.nix gnet <- ./gnet-1.1.8.nix -src = ../dist/pan-0.13.4.tar.bz2 +src = url(http://pan.rebelbase.com/download/releases/0.13.4/SOURCE/pan-0.13.4.tar.bz2) build = ../build/pan-build.sh From 2e59698b78d3fcba6908d8478c15943834d9635f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 21 Mar 2003 15:53:35 +0000 Subject: [PATCH 0012/6440] * Added a command to verify the consistency of the database. --- src/nix.cc | 115 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 93 insertions(+), 22 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index db9967537..9f4733f5e 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -48,21 +48,29 @@ public: UsageError(string _err) : Error(_err) { }; }; +class BadRefError : public Error +{ +public: + BadRefError(string _err) : Error(_err) { }; +}; -/* Wrapper class that ensures that the database is closed upon + +/* Wrapper classes that ensures that the database is closed upon object destruction. */ class Db2 : public Db { public: - Db2(DbEnv *env, u_int32_t flags) - : Db(env, flags) - { - } + Db2(DbEnv *env, u_int32_t flags) : Db(env, flags) { } + ~Db2() { close(0); } +}; - ~Db2() - { - close(0); - } + +class DbcClose +{ + Dbc * cursor; +public: + DbcClose(Dbc * c) : cursor(c) { } + ~DbcClose() { cursor->close(); } }; @@ -113,17 +121,38 @@ void delDB(const string & dbname, const string & key) } +typedef pair DBPair; +typedef list DBPairs; + + +void enumDB(const string & dbname, DBPairs & contents) +{ + auto_ptr db = openDB(dbname, false); + + Dbc * cursor; + db->cursor(0, &cursor, 0); + DbcClose cursorCloser(cursor); + + Dbt kt, dt; + while (cursor->get(&kt, &dt, DB_NEXT) != DB_NOTFOUND) { + string key((char *) kt.get_data(), kt.get_size()); + string data((char *) dt.get_data(), dt.get_size()); + contents.push_back(DBPair(key, data)); + } +} + + /* Verify that a reference is valid (that is, is a MD5 hash code). */ void checkRef(const string & s) { string err = "invalid reference: " + s; if (s.length() != 32) - throw Error(err); + throw BadRefError(err); for (int i = 0; i < 32; i++) { char c = s[i]; if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) - throw Error(err); + throw BadRefError(err); } } @@ -133,11 +162,11 @@ string makeRef(string filename) { char hash[33]; - FILE * pipe = popen(("md5sum " + filename).c_str(), "r"); - if (!pipe) throw Error("cannot execute md5sum"); + FILE * pipe = popen(("md5sum " + filename + " 2> /dev/null").c_str(), "r"); + if (!pipe) throw BadRefError("cannot execute md5sum"); if (fread(hash, 32, 1, pipe) != 1) - throw Error("cannot read hash from md5sum"); + throw BadRefError("cannot read hash from md5sum"); hash[32] = 0; pclose(pipe); @@ -369,8 +398,48 @@ void initDB() } +void verifyDB() +{ + /* Check that all file references are still valid. */ + DBPairs fileRefs; + + enumDB(dbRefs, fileRefs); + + for (DBPairs::iterator it = fileRefs.begin(); + it != fileRefs.end(); it++) + { + try { + if (makeRef(it->second) != it->first) + delDB(dbRefs, it->first); + } catch (BadRefError e) { /* !!! better error check */ + cerr << "file " << it->second << " has disappeared\n"; + delDB(dbRefs, it->first); + } + } + + /* Check that all installed packages are still there. */ + DBPairs instPkgs; + + enumDB(dbInstPkgs, instPkgs); + + for (DBPairs::iterator it = instPkgs.begin(); + it != instPkgs.end(); it++) + { + struct stat st; + if (stat(it->second.c_str(), &st) == -1) { + cerr << "package " << it->first << " has disappeared\n"; + delDB(dbInstPkgs, it->first); + } + } + + /* TODO: check that all directories in pkgHome are installed + packages. */ +} + + void run(int argc, char * * argv) { + UsageError argcError("wrong number of arguments"); string cmd; if (argc < 1) @@ -380,21 +449,20 @@ void run(int argc, char * * argv) argc--, argv++; if (cmd == "init") { - if (argc != 0) - throw UsageError("wrong number of arguments"); + if (argc != 0) throw argcError; initDB(); + } else if (cmd == "verify") { + if (argc != 0) throw argcError; + verifyDB(); } else if (cmd == "getpkg") { - if (argc != 1) - throw UsageError("wrong number of arguments"); + if (argc != 1) throw argcError; string path = getPkg(argv[0]); cout << path << endl; } else if (cmd == "regfile") { - if (argc != 1) - throw UsageError("wrong number of arguments"); + if (argc != 1) throw argcError; registerFile(argv[0]); } else if (cmd == "reginst") { - if (argc != 2) - throw UsageError("wrong number of arguments"); + if (argc != 2) throw argcError; registerInstalledPkg(argv[0], argv[1]); } else throw UsageError("unknown command: " + string(cmd)); @@ -411,6 +479,9 @@ Subcommands: init Initialize the database. + verify + Removes stale entries from the database. + regfile FILENAME Register FILENAME keyed by its hash. From 800d8e950f13b9cb9099c5d1270a4385d5ae55da Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 21 Mar 2003 15:58:40 +0000 Subject: [PATCH 0013/6440] * Added a command to list installed packages. --- src/nix.cc | 24 ++++++++++++++++++++++-- test/build/pan-build.sh | 2 +- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index 9f4733f5e..cc81260e3 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -237,7 +237,7 @@ void installPkg(string pkgref) string cmd; string builder; - if (!queryDB("refs", pkgref, pkgfile)) + if (!queryDB(dbRefs, pkgref, pkgfile)) throw Error("unknown package " + pkgref); cerr << "installing package " + pkgref + " from " + pkgfile + "\n"; @@ -272,7 +272,7 @@ void installPkg(string pkgref) string file; - if (!queryDB("refs", it->ref, file)) + if (!queryDB(dbRefs, it->ref, file)) throw Error("unknown file " + it->ref); if (makeRef(file) != it->ref) @@ -437,6 +437,23 @@ void verifyDB() } +void listInstalledPkgs() +{ + DBPairs instPkgs; + + enumDB(dbInstPkgs, instPkgs); + + for (DBPairs::iterator it = instPkgs.begin(); + it != instPkgs.end(); it++) + { + string descr; + if (!queryDB(dbRefs, it->first, descr)) + descr = "descriptor missing"; + cout << it->first << " " << descr << endl; + } +} + + void run(int argc, char * * argv) { UsageError argcError("wrong number of arguments"); @@ -464,6 +481,9 @@ void run(int argc, char * * argv) } else if (cmd == "reginst") { if (argc != 2) throw argcError; registerInstalledPkg(argv[0], argv[1]); + } else if (cmd == "listinst") { + if (argc != 0) throw argcError; + listInstalledPkgs(); } else throw UsageError("unknown command: " + string(cmd)); } diff --git a/test/build/pan-build.sh b/test/build/pan-build.sh index a29255881..907215f37 100755 --- a/test/build/pan-build.sh +++ b/test/build/pan-build.sh @@ -2,7 +2,7 @@ export PATH=$pkgconfig/bin:$gnet/bin:/bin:/usr/bin export PKG_CONFIG_PATH=$glib/lib/pkgconfig:$atk/lib/pkgconfig:$pango/lib/pkgconfig:$gtk/lib/pkgconfig -export LD_LIBRARY_PATH=$gnet/lib:$atk/lib:$pango/lib:$gtk/lib:$gnet/lib +export LD_LIBRARY_PATH=$glib/lib:$atk/lib:$pango/lib:$gtk/lib:$gnet/lib top=`pwd` tar xvfj $src From 20d165c34467338f07c4808783cd50318c38a47b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 23 Mar 2003 23:24:09 +0000 Subject: [PATCH 0014/6440] * A command to run programs in Nix packages, that is, to execute a run action. Run actions are described by uniquely hashed descriptors, just like build actions. Therefore run actions can have dependencies, but these need not be the same as the build time dependencies (e.g., at runtime we can link against a different version of a dynamic library). Example: nix run 31d6bf4c171282367065e0deecd7c579 will run the Pan 0.13.91 newsreader with gtkspell support. --- src/nix.cc | 143 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 123 insertions(+), 20 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index cc81260e3..40f41eb5a 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -166,7 +166,7 @@ string makeRef(string filename) if (!pipe) throw BadRefError("cannot execute md5sum"); if (fread(hash, 32, 1, pipe) != 1) - throw BadRefError("cannot read hash from md5sum"); + throw BadRefError("cannot read hash from md5sum of " + filename); hash[32] = 0; pclose(pipe); @@ -222,14 +222,14 @@ void readPkgDescr(const string & pkgfile, } -string getPkg(string pkgref); +string getPkg(string hash); typedef pair EnvPair; typedef list Environment; -void installPkg(string pkgref) +void installPkg(string hash) { string pkgfile; string src; @@ -237,13 +237,13 @@ void installPkg(string pkgref) string cmd; string builder; - if (!queryDB(dbRefs, pkgref, pkgfile)) - throw Error("unknown package " + pkgref); + if (!queryDB(dbRefs, hash, pkgfile)) + throw Error("unknown package " + hash); - cerr << "installing package " + pkgref + " from " + pkgfile + "\n"; + cerr << "installing package " + hash + " from " + pkgfile + "\n"; /* Verify that the file hasn't changed. !!! race */ - if (makeRef(pkgfile) != pkgref) + if (makeRef(pkgfile) != hash) throw Error("file " + pkgfile + " is stale"); /* Read the package description file. */ @@ -288,7 +288,7 @@ void installPkg(string pkgref) throw Error("no builder specified"); /* Construct a path for the installed package. */ - path = pkgHome + "/" + pkgref; + path = pkgHome + "/" + hash; /* Create the path. */ if (mkdir(path.c_str(), 0777)) @@ -326,8 +326,7 @@ void installPkg(string pkgref) cout << strerror(errno) << endl; cout << "unable to execute builder\n"; - _exit(1); - } + _exit(1); } } @@ -346,20 +345,118 @@ void installPkg(string pkgref) throw; } - setDB(dbInstPkgs, pkgref, path); + setDB(dbInstPkgs, hash, path); } -string getPkg(string pkgref) +string getPkg(string hash) { string path; - checkRef(pkgref); - while (!queryDB(dbInstPkgs, pkgref, path)) - installPkg(pkgref); + checkRef(hash); + while (!queryDB(dbInstPkgs, hash, path)) + installPkg(hash); return path; } +void runPkg(string hash) +{ + string pkgfile; + string src; + string path; + string cmd; + string runner; + + if (!queryDB(dbRefs, hash, pkgfile)) + throw Error("unknown package " + hash); + + cerr << "running package " + hash + " from " + pkgfile + "\n"; + + /* Verify that the file hasn't changed. !!! race */ + if (makeRef(pkgfile) != hash) + throw Error("file " + pkgfile + " is stale"); + + /* Read the package description file. */ + DepList pkgImports, fileImports; + readPkgDescr(pkgfile, pkgImports, fileImports); + + /* Recursively fetch all the dependencies, filling in the + environment as we go along. */ + Environment env; + + for (DepList::iterator it = pkgImports.begin(); + it != pkgImports.end(); it++) + { + cerr << "fetching package dependency " + << it->name << " <- " << it->ref + << endl; + env.push_back(EnvPair(it->name, getPkg(it->ref))); + } + + for (DepList::iterator it = fileImports.begin(); + it != fileImports.end(); it++) + { + cerr << "fetching file dependency " + << it->name << " = " << it->ref + << endl; + + string file; + + if (!queryDB(dbRefs, it->ref, file)) + throw Error("unknown file " + it->ref); + + if (makeRef(file) != it->ref) + throw Error("file " + file + " is stale"); + + if (it->name == "run") + runner = file; + else + env.push_back(EnvPair(it->name, file)); + } + + if (runner == "") + throw Error("no runner specified"); + + /* Fork a child to build the package. */ + pid_t pid; + switch (pid = fork()) { + + case -1: + throw Error("unable to fork"); + + case 0: { /* child */ + + /* Fill in the environment. We don't bother freeing the + strings, since we'll exec or die soon anyway. */ + for (Environment::iterator it = env.begin(); + it != env.end(); it++) + { + string * s = new string(it->first + "=" + it->second); + putenv((char *) s->c_str()); + } + + /* Execute the runner. This should not return. */ + execl(runner.c_str(), runner.c_str(), 0); + + cout << strerror(errno) << endl; + + cout << "unable to execute runner\n"; + _exit(1); } + + } + + /* parent */ + + /* Wait for the child to finish. */ + int status; + if (waitpid(pid, &status, 0) != pid) + throw Error("unable to wait for child"); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + throw Error("unable to run package"); +} + + string absPath(string filename) { if (filename[0] != '/') { @@ -381,13 +478,13 @@ void registerFile(string filename) /* This is primarily used for bootstrapping. */ -void registerInstalledPkg(string pkgref, string path) +void registerInstalledPkg(string hash, string path) { - checkRef(pkgref); + checkRef(hash); if (path == "") - delDB(dbInstPkgs, pkgref); + delDB(dbInstPkgs, hash); else - setDB(dbInstPkgs, pkgref, path); + setDB(dbInstPkgs, hash, path); } @@ -475,6 +572,9 @@ void run(int argc, char * * argv) if (argc != 1) throw argcError; string path = getPkg(argv[0]); cout << path << endl; + } else if (cmd == "run") { + if (argc != 1) throw argcError; + runPkg(argv[0]); } else if (cmd == "regfile") { if (argc != 1) throw argcError; registerFile(argv[0]); @@ -500,7 +600,7 @@ Subcommands: Initialize the database. verify - Removes stale entries from the database. + Remove stale entries from the database. regfile FILENAME Register FILENAME keyed by its hash. @@ -511,6 +611,9 @@ Subcommands: getpkg HASH Ensure that the package referenced by HASH is installed. Prints out the path of the package on stdout. + + run HASH + Run the descriptor referenced by HASH. "; } From 8d682ba551c44daecd427999114b9c520eef0296 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 23 Mar 2003 23:28:28 +0000 Subject: [PATCH 0015/6440] * A descriptor for running the Pan newsreader. * Added descriptors for gtkspell and its support package pspell. Gtkspell is an optional dependency of Pan, so we should add the ability to nix-instantiate to instantiate variants of a package based on a selection of features. --- test/build/gtkspell-build.sh | 13 +++++++++++++ test/build/pan-build-2.sh | 20 ++++++++++++++++++++ test/build/pan-run.sh | 7 +++++++ test/build/pspell-build.sh | 10 ++++++++++ test/tmpl/gtkspell-2.0.2.nix | 10 ++++++++++ test/tmpl/pan-0.13.91-run.nix | 11 +++++++++++ test/tmpl/pan-0.13.91.nix | 12 ++++++++++++ test/tmpl/pspell-.12.2.nix | 3 +++ 8 files changed, 86 insertions(+) create mode 100755 test/build/gtkspell-build.sh create mode 100755 test/build/pan-build-2.sh create mode 100755 test/build/pan-run.sh create mode 100755 test/build/pspell-build.sh create mode 100644 test/tmpl/gtkspell-2.0.2.nix create mode 100644 test/tmpl/pan-0.13.91-run.nix create mode 100644 test/tmpl/pan-0.13.91.nix create mode 100644 test/tmpl/pspell-.12.2.nix diff --git a/test/build/gtkspell-build.sh b/test/build/gtkspell-build.sh new file mode 100755 index 000000000..d1e56943f --- /dev/null +++ b/test/build/gtkspell-build.sh @@ -0,0 +1,13 @@ +#! /bin/sh + +export PATH=$pkgconfig/bin:/bin:/usr/bin +export PKG_CONFIG_PATH=$glib/lib/pkgconfig:$atk/lib/pkgconfig:$pango/lib/pkgconfig:$gtk/lib/pkgconfig +export LD_LIBRARY_PATH=$glib/lib:$atk/lib:$pango/lib:$gtk/lib:$pspell/lib +export C_INCLUDE_PATH=$pspell/include + +top=`pwd` +tar xvfz $src +cd gtkspell-* +./configure --prefix=$top +make +make install diff --git a/test/build/pan-build-2.sh b/test/build/pan-build-2.sh new file mode 100755 index 000000000..87dd4e6a1 --- /dev/null +++ b/test/build/pan-build-2.sh @@ -0,0 +1,20 @@ +#! /bin/sh + +export PATH=$pkgconfig/bin:$gnet/bin:/bin:/usr/bin +export PKG_CONFIG_PATH=$glib/lib/pkgconfig:$atk/lib/pkgconfig:$pango/lib/pkgconfig:$gtk/lib/pkgconfig:$gtkspell/lib/pkgconfig +export LD_LIBRARY_PATH=$glib/lib:$atk/lib:$pango/lib:$gtk/lib:$gnet/lib:$pspell/lib:$gtkspell/lib + +# A bug in gtkspell: the pspell library path is not exported +# through pkgconfig. +export LIBRARY_PATH=$pspell/lib + +export LDFLAGS=-s + +top=`pwd` +tar xvfj $src +cd pan-* +./configure --prefix=$top +make +make install +cd .. +rm -rf pan-* diff --git a/test/build/pan-run.sh b/test/build/pan-run.sh new file mode 100755 index 000000000..923a23292 --- /dev/null +++ b/test/build/pan-run.sh @@ -0,0 +1,7 @@ +#! /bin/sh + +export LD_LIBRARY_PATH=$glib/lib:$atk/lib:$pango/lib:$gtk/lib:$gnet/lib:$pspell/lib:$gtkspell/lib + +ldd $pan/bin/pan + +$pan/bin/pan \ No newline at end of file diff --git a/test/build/pspell-build.sh b/test/build/pspell-build.sh new file mode 100755 index 000000000..588c2f1a0 --- /dev/null +++ b/test/build/pspell-build.sh @@ -0,0 +1,10 @@ +#! /bin/sh + +export PATH=/bin:/usr/bin + +top=`pwd` +tar xvfz $src +cd pspell-* +./configure --prefix=$top +make +make install diff --git a/test/tmpl/gtkspell-2.0.2.nix b/test/tmpl/gtkspell-2.0.2.nix new file mode 100644 index 000000000..844a75867 --- /dev/null +++ b/test/tmpl/gtkspell-2.0.2.nix @@ -0,0 +1,10 @@ +pkgconfig <- ./pkgconfig-0.15.0.nix +glib <- ./glib-2.2.1.nix +atk <- ./atk-1.2.0.nix +pango <- ./pango-1.2.1.nix +gtk <- ./gtk+-2.2.1.nix +pspell <- ./pspell-.12.2.nix + +src = url(http://pan.rebelbase.com/download/extras/gtkspell/SOURCES/gtkspell-2.0.2.tar.gz) + +build = ../build/gtkspell-build.sh diff --git a/test/tmpl/pan-0.13.91-run.nix b/test/tmpl/pan-0.13.91-run.nix new file mode 100644 index 000000000..9fe803def --- /dev/null +++ b/test/tmpl/pan-0.13.91-run.nix @@ -0,0 +1,11 @@ +pan <- ./pan-0.13.91.nix + +glib <- ./glib-2.2.1.nix +atk <- ./atk-1.2.0.nix +pango <- ./pango-1.2.1.nix +gtk <- ./gtk+-2.2.1.nix +gnet <- ./gnet-1.1.8.nix +pspell <- ./pspell-.12.2.nix +gtkspell <- ./gtkspell-2.0.2.nix + +run = ../build/pan-run.sh diff --git a/test/tmpl/pan-0.13.91.nix b/test/tmpl/pan-0.13.91.nix new file mode 100644 index 000000000..e6baf7629 --- /dev/null +++ b/test/tmpl/pan-0.13.91.nix @@ -0,0 +1,12 @@ +pkgconfig <- ./pkgconfig-0.15.0.nix +glib <- ./glib-2.2.1.nix +atk <- ./atk-1.2.0.nix +pango <- ./pango-1.2.1.nix +gtk <- ./gtk+-2.2.1.nix +gnet <- ./gnet-1.1.8.nix +pspell <- ./pspell-.12.2.nix +gtkspell <- ./gtkspell-2.0.2.nix + +src = url(http://pan.rebelbase.com/download/releases/0.13.91/SOURCE/pan-0.13.91.tar.bz2) + +build = ../build/pan-build-2.sh diff --git a/test/tmpl/pspell-.12.2.nix b/test/tmpl/pspell-.12.2.nix new file mode 100644 index 000000000..33e63b85a --- /dev/null +++ b/test/tmpl/pspell-.12.2.nix @@ -0,0 +1,3 @@ +src = url(http://unc.dl.sourceforge.net/sourceforge/pspell/pspell-.12.2.tar.gz) + +build = ../build/pspell-build.sh From 9d2f128252ea9dc9b706bec2bfdaa35600190385 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 24 Mar 2003 11:50:20 +0000 Subject: [PATCH 0016/6440] * Refactoring. --- src/nix.cc | 103 +++++++++++++++++++---------------------------------- 1 file changed, 37 insertions(+), 66 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index 40f41eb5a..6fdc88b2f 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -225,22 +226,17 @@ void readPkgDescr(const string & pkgfile, string getPkg(string hash); -typedef pair EnvPair; -typedef list Environment; +typedef map Environment; -void installPkg(string hash) +void fetchDeps(string hash, Environment & env) { string pkgfile; - string src; - string path; - string cmd; - string builder; if (!queryDB(dbRefs, hash, pkgfile)) throw Error("unknown package " + hash); - cerr << "installing package " + hash + " from " + pkgfile + "\n"; + cerr << "reading information about " + hash + " from " + pkgfile + "\n"; /* Verify that the file hasn't changed. !!! race */ if (makeRef(pkgfile) != hash) @@ -252,15 +248,13 @@ void installPkg(string hash) /* Recursively fetch all the dependencies, filling in the environment as we go along. */ - Environment env; - for (DepList::iterator it = pkgImports.begin(); it != pkgImports.end(); it++) { cerr << "fetching package dependency " << it->name << " <- " << it->ref << endl; - env.push_back(EnvPair(it->name, getPkg(it->ref))); + env[it->name] = getPkg(it->ref); } for (DepList::iterator it = fileImports.begin(); @@ -278,15 +272,34 @@ void installPkg(string hash) if (makeRef(file) != it->ref) throw Error("file " + file + " is stale"); - if (it->name == "build") - builder = file; - else - env.push_back(EnvPair(it->name, file)); + env[it->name] = file; } +} - if (builder == "") - throw Error("no builder specified"); +string getFromEnv(const Environment & env, const string & key) +{ + Environment::const_iterator it = env.find(key); + if (it == env.end()) + throw Error("key " + key + " not found in the environment"); + return it->second; +} + + +void installPkg(string hash) +{ + string pkgfile; + string src; + string path; + string cmd; + string builder; + Environment env; + + /* Fetch dependencies. */ + fetchDeps(hash, env); + + builder = getFromEnv(env, "build"); + /* Construct a path for the installed package. */ path = pkgHome + "/" + hash; @@ -361,62 +374,17 @@ string getPkg(string hash) void runPkg(string hash) { - string pkgfile; string src; string path; string cmd; string runner; - - if (!queryDB(dbRefs, hash, pkgfile)) - throw Error("unknown package " + hash); - - cerr << "running package " + hash + " from " + pkgfile + "\n"; - - /* Verify that the file hasn't changed. !!! race */ - if (makeRef(pkgfile) != hash) - throw Error("file " + pkgfile + " is stale"); - - /* Read the package description file. */ - DepList pkgImports, fileImports; - readPkgDescr(pkgfile, pkgImports, fileImports); - - /* Recursively fetch all the dependencies, filling in the - environment as we go along. */ Environment env; - for (DepList::iterator it = pkgImports.begin(); - it != pkgImports.end(); it++) - { - cerr << "fetching package dependency " - << it->name << " <- " << it->ref - << endl; - env.push_back(EnvPair(it->name, getPkg(it->ref))); - } - - for (DepList::iterator it = fileImports.begin(); - it != fileImports.end(); it++) - { - cerr << "fetching file dependency " - << it->name << " = " << it->ref - << endl; - - string file; - - if (!queryDB(dbRefs, it->ref, file)) - throw Error("unknown file " + it->ref); - - if (makeRef(file) != it->ref) - throw Error("file " + file + " is stale"); - - if (it->name == "run") - runner = file; - else - env.push_back(EnvPair(it->name, file)); - } - - if (runner == "") - throw Error("no runner specified"); + /* Fetch dependencies. */ + fetchDeps(hash, env); + runner = getFromEnv(env, "run"); + /* Fork a child to build the package. */ pid_t pid; switch (pid = fork()) { @@ -612,6 +580,9 @@ Subcommands: Ensure that the package referenced by HASH is installed. Prints out the path of the package on stdout. + listinst + Prints a list of installed packages. + run HASH Run the descriptor referenced by HASH. "; From 2dc84e556911407fe75e1ceb6a9fe34ed21725db Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 24 Mar 2003 12:49:40 +0000 Subject: [PATCH 0017/6440] * Descriptors now have a "system" field specifying the platform that the build or run action should be perfomed on. This ensures that descriptors have different hashes on different platforms. --- src/Makefile | 13 +- src/config.guess | 1400 +++++++++++++++++++ src/{nix-instantiate => nix-instantiate.in} | 4 + src/nix.cc | 71 +- test/tmpl/aterm-2.0.nix | 2 + test/tmpl/atk-1.2.0.nix | 2 + test/tmpl/glib-2.2.1.nix | 2 + test/tmpl/gnet-1.1.8.nix | 2 + test/tmpl/gtk+-2.2.1.nix | 2 + test/tmpl/gtkspell-2.0.2.nix | 2 + test/tmpl/pan-0.13.4.nix | 2 + test/tmpl/pan-0.13.91-run.nix | 2 + test/tmpl/pan-0.13.91.nix | 2 + test/tmpl/pango-1.2.1.nix | 2 + test/tmpl/pkgconfig-0.15.0.nix | 2 + test/tmpl/pspell-.12.2.nix | 2 + 16 files changed, 1480 insertions(+), 32 deletions(-) create mode 100755 src/config.guess rename src/{nix-instantiate => nix-instantiate.in} (96%) diff --git a/src/Makefile b/src/Makefile index 4fd293059..6b4c792bb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,2 +1,13 @@ +all: nix nix-instantiate + +SYSTEM = $(shell ./config.guess) + nix: nix.cc - g++ -g -Wall -o nix nix.cc -ldb_cxx-4 + g++ -g -Wall -o nix nix.cc -ldb_cxx-4 -DSYSTEM=\"$(SYSTEM)\" + +nix-instantiate: nix-instantiate.in + sed "s/@SYSTEM@/$(SYSTEM)/" < $^ > $@ + chmod +x $@ + +clean: + rm -f *.o nix nix-instantiate diff --git a/src/config.guess b/src/config.guess new file mode 100755 index 000000000..9b1384be4 --- /dev/null +++ b/src/config.guess @@ -0,0 +1,1400 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002 Free Software Foundation, Inc. + +timestamp='2002-11-30' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Originally written by Per Bothner . +# Please send patches to . Submit a context +# diff and a properly formatted ChangeLog entry. +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# The plan is that this can be called by configure scripts if you +# don't specify an explicit build system type. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit 0 ;; + --version | -v ) + echo "$version" ; exit 0 ;; + --help | --h* | -h ) + echo "$usage"; exit 0 ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# This shell variable is my proudest work .. or something. --bje + +set_cc_for_build='tmpdir=${TMPDIR-/tmp}/config-guess-$$ ; +(old=`umask` && umask 077 && mkdir $tmpdir && umask $old && unset old) + || (echo "$me: cannot create $tmpdir" >&2 && exit 1) ; +dummy=$tmpdir/dummy ; +files="$dummy.c $dummy.o $dummy.rel $dummy" ; +trap '"'"'rm -f $files; rmdir $tmpdir; exit 1'"'"' 1 2 15 ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > $dummy.c ; + for c in cc gcc c89 c99 ; do + if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + rm -f $files ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ; +unset files' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ + /usr/sbin/$sysctl 2>/dev/null || echo unknown)` + case "${UNAME_MACHINE_ARCH}" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + *) machine=${UNAME_MACHINE_ARCH}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE_ARCH}" in + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep __ELF__ >/dev/null + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "${UNAME_VERSION}" in + Debian*) + release='-gnu' + ;; + *) + release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}" + exit 0 ;; + amiga:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + arc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + hp300:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mac68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + macppc:OpenBSD:*:*) + echo powerpc-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme88k:OpenBSD:*:*) + echo m88k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvmeppc:OpenBSD:*:*) + echo powerpc-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + pmax:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sgi:OpenBSD:*:*) + echo mipseb-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sun3:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + wgrisc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:OpenBSD:*:*) + echo ${UNAME_MACHINE}-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + alpha:OSF1:*:*) + if test $UNAME_RELEASE = "V4.0"; then + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + fi + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + eval $set_cc_for_build + cat <$dummy.s + .data +\$Lformat: + .byte 37,100,45,37,120,10,0 # "%d-%x\n" + + .text + .globl main + .align 4 + .ent main +main: + .frame \$30,16,\$26,0 + ldgp \$29,0(\$27) + .prologue 1 + .long 0x47e03d80 # implver \$0 + lda \$2,-1 + .long 0x47e20c21 # amask \$2,\$1 + lda \$16,\$Lformat + mov \$0,\$17 + not \$1,\$18 + jsr \$26,printf + ldgp \$29,0(\$26) + mov 0,\$16 + jsr \$26,exit + .end main +EOF + $CC_FOR_BUILD -o $dummy $dummy.s 2>/dev/null + if test "$?" = 0 ; then + case `$dummy` in + 0-0) + UNAME_MACHINE="alpha" + ;; + 1-0) + UNAME_MACHINE="alphaev5" + ;; + 1-1) + UNAME_MACHINE="alphaev56" + ;; + 1-101) + UNAME_MACHINE="alphapca56" + ;; + 2-303) + UNAME_MACHINE="alphaev6" + ;; + 2-307) + UNAME_MACHINE="alphaev67" + ;; + 2-1307) + UNAME_MACHINE="alphaev68" + ;; + 3-1307) + UNAME_MACHINE="alphaev7" + ;; + esac + fi + rm -f $dummy.s $dummy && rmdir $tmpdir + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[VTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + exit 0 ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit 0 ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit 0 ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit 0;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit 0 ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-morphos + exit 0 ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit 0 ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit 0;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit 0;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit 0 ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit 0 ;; + DRS?6000:UNIX_SV:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7 && exit 0 ;; + esac ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + i86pc:SunOS:5.*:*) + echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit 0 ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit 0 ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit 0 ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit 0 ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit 0 ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit 0 ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit 0 ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit 0 ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit 0 ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit 0 ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c \ + && $dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ + && rm -f $dummy.c $dummy && rmdir $tmpdir && exit 0 + rm -f $dummy.c $dummy && rmdir $tmpdir + echo mips-mips-riscos${UNAME_RELEASE} + exit 0 ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit 0 ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit 0 ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit 0 ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit 0 ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit 0 ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit 0 ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit 0 ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit 0 ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit 0 ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit 0 ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit 0 ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit 0 ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit 0 ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit 0 ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && rm -f $dummy.c $dummy && rmdir $tmpdir && exit 0 + rm -f $dummy.c $dummy && rmdir $tmpdir + echo rs6000-ibm-aix3.2.5 + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit 0 ;; + *:AIX:*:[45]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit 0 ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit 0 ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit 0 ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit 0 ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit 0 ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit 0 ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit 0 ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "${HP_ARCH}" = "" ]; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` + if test -z "$HP_ARCH"; then HP_ARCH=hppa; fi + rm -f $dummy.c $dummy && rmdir $tmpdir + fi ;; + esac + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit 0 ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit 0 ;; + 3050*:HI-UX:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && rm -f $dummy.c $dummy && rmdir $tmpdir && exit 0 + rm -f $dummy.c $dummy && rmdir $tmpdir + echo unknown-hitachi-hiuxwe2 + exit 0 ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit 0 ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit 0 ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit 0 ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit 0 ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit 0 ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit 0 ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit 0 ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit 0 ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit 0 ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit 0 ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit 0 ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*T3D:*:*:*) + echo alpha-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit 0 ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit 0 ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:FreeBSD:*:*) + # Determine whether the default compiler uses glibc. + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + #if __GLIBC__ >= 2 + LIBC=gnu + #else + LIBC= + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` + rm -f $dummy.c && rmdir $tmpdir + echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`${LIBC:+-$LIBC} + exit 0 ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit 0 ;; + i*:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit 0 ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit 0 ;; + x86:Interix*:3*) + echo i586-pc-interix3 + exit 0 ;; + [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) + echo i${UNAME_MACHINE}-pc-mks + exit 0 ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i586-pc-interix + exit 0 ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit 0 ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit 0 ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + *:GNU:*:*) + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit 0 ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit 0 ;; + arm*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + mips:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips + #undef mipsel + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mipsel + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` + rm -f $dummy.c && rmdir $tmpdir + test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 + ;; + mips64:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips64 + #undef mips64el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mips64el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips64 + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` + rm -f $dummy.c && rmdir $tmpdir + test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 + ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-gnu + exit 0 ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-gnu + exit 0 ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null + if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi + echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} + exit 0 ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-gnu ;; + PA8*) echo hppa2.0-unknown-linux-gnu ;; + *) echo hppa-unknown-linux-gnu ;; + esac + exit 0 ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-gnu + exit 0 ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux + exit 0 ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + x86_64:Linux:*:*) + echo x86_64-unknown-linux-gnu + exit 0 ;; + i*86:Linux:*:*) + # The BFD linker knows what the default object file format is, so + # first see if it will tell us. cd to the root directory to prevent + # problems with other programs or directories called `ld' in the path. + # Set LC_ALL=C to ensure ld outputs messages in English. + ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ + | sed -ne '/supported targets:/!d + s/[ ][ ]*/ /g + s/.*supported targets: *// + s/ .*// + p'` + case "$ld_supported_targets" in + elf32-i386) + TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" + ;; + a.out-i386-linux) + echo "${UNAME_MACHINE}-pc-linux-gnuaout" + exit 0 ;; + coff-i386) + echo "${UNAME_MACHINE}-pc-linux-gnucoff" + exit 0 ;; + "") + # Either a pre-BFD a.out linker (linux-gnuoldld) or + # one that does not give us useful --help. + echo "${UNAME_MACHINE}-pc-linux-gnuoldld" + exit 0 ;; + esac + # Determine whether the default compiler is a.out or elf + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + #ifdef __ELF__ + # ifdef __GLIBC__ + # if __GLIBC__ >= 2 + LIBC=gnu + # else + LIBC=gnulibc1 + # endif + # else + LIBC=gnulibc1 + # endif + #else + #ifdef __INTEL_COMPILER + LIBC=gnu + #else + LIBC=gnuaout + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` + rm -f $dummy.c && rmdir $tmpdir + test x"${LIBC}" != x && echo "${UNAME_MACHINE}-pc-linux-${LIBC}" && exit 0 + test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0 + ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit 0 ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit 0 ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit 0 ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit 0 ;; + i*86:atheos:*:*) + echo ${UNAME_MACHINE}-unknown-atheos + exit 0 ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit 0 ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit 0 ;; + i*86:*:5:[78]*) + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit 0 ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit 0 ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i386. + echo i386-pc-msdosdjgpp + exit 0 ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit 0 ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit 0 ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit 0 ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit 0 ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit 0 ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit 0 ;; + M68*:*:R3V[567]*:*) + test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; + 3[34]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4.3${OS_REL} && exit 0 + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4 && exit 0 ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit 0 ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit 0 ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit 0 ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit 0 ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit 0 ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit 0 ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit 0 ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit 0 ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit 0 ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit 0 ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit 0 ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit 0 ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit 0 ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit 0 ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit 0 ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux${UNAME_RELEASE} + exit 0 ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Darwin:*:*) + echo `uname -p`-apple-darwin${UNAME_RELEASE} + exit 0 ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = "x86"; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} + exit 0 ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit 0 ;; + NSR-[DGKLNPTVW]:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit 0 ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit 0 ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit 0 ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit 0 ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit 0 ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit 0 ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit 0 ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit 0 ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit 0 ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit 0 ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit 0 ;; +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +eval $set_cc_for_build +cat >$dummy.c < +# include +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +# if !defined (ultrix) +# include +# if defined (BSD) +# if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +# else +# if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# endif +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# else + printf ("vax-dec-ultrix\n"); exit (0); +# endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && $dummy && rm -f $dummy.c $dummy && rmdir $tmpdir && exit 0 +rm -f $dummy.c $dummy && rmdir $tmpdir + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit 0 ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + c34*) + echo c34-convex-bsd + exit 0 ;; + c38*) + echo c38-convex-bsd + exit 0 ;; + c4*) + echo c4-convex-bsd + exit 0 ;; + esac +fi + +cat >&2 < in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/src/nix-instantiate b/src/nix-instantiate.in similarity index 96% rename from src/nix-instantiate rename to src/nix-instantiate.in index 63c858864..e501a2c08 100755 --- a/src/nix-instantiate +++ b/src/nix-instantiate.in @@ -4,6 +4,8 @@ use strict; use FileHandle; use File::Spec; +my $system = "@SYSTEM@"; + my $outdir = File::Spec->rel2abs($ARGV[0]); my $netdir = File::Spec->rel2abs($ARGV[1]); @@ -49,6 +51,8 @@ sub convert { open $IN, "< $descr" or die "cannot open $descr"; open $OUT, "> $outfile" or die "cannot create $outfile"; + print $OUT "system : $system\n"; + while (<$IN>) { chomp; diff --git a/src/nix.cc b/src/nix.cc index 6fdc88b2f..d53a809b7 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -34,6 +34,9 @@ static string dbfile = PKGINFO_PATH; static string pkgHome = "/pkg"; +static string thisSystem = SYSTEM; + + class Error : public exception { string err; @@ -177,22 +180,12 @@ string makeRef(string filename) } -struct Dep -{ - string name; - string ref; - Dep(string _name, string _ref) - { - name = _name; - ref = _ref; - } -}; - -typedef list DepList; +typedef pair Param; +typedef list Params; void readPkgDescr(const string & pkgfile, - DepList & pkgImports, DepList & fileImports) + Params & pkgImports, Params & fileImports, Params & arguments) { ifstream file; file.exceptions(ios::badbit); @@ -212,12 +205,14 @@ void readPkgDescr(const string & pkgfile, string name, op, ref; str >> name >> op >> ref; - checkRef(ref); - - if (op == "<-") - pkgImports.push_back(Dep(name, ref)); - else if (op == "=") - fileImports.push_back(Dep(name, ref)); + if (op == "<-") { + checkRef(ref); + pkgImports.push_back(Param(name, ref)); + } else if (op == "=") { + checkRef(ref); + fileImports.push_back(Param(name, ref)); + } else if (op == ":") + arguments.push_back(Param(name, ref)); else throw Error("invalid operator " + op); } } @@ -243,37 +238,51 @@ void fetchDeps(string hash, Environment & env) throw Error("file " + pkgfile + " is stale"); /* Read the package description file. */ - DepList pkgImports, fileImports; - readPkgDescr(pkgfile, pkgImports, fileImports); + Params pkgImports, fileImports, arguments; + readPkgDescr(pkgfile, pkgImports, fileImports, arguments); /* Recursively fetch all the dependencies, filling in the environment as we go along. */ - for (DepList::iterator it = pkgImports.begin(); + for (Params::iterator it = pkgImports.begin(); it != pkgImports.end(); it++) { cerr << "fetching package dependency " - << it->name << " <- " << it->ref + << it->first << " <- " << it->second << endl; - env[it->name] = getPkg(it->ref); + env[it->first] = getPkg(it->second); } - for (DepList::iterator it = fileImports.begin(); + for (Params::iterator it = fileImports.begin(); it != fileImports.end(); it++) { cerr << "fetching file dependency " - << it->name << " = " << it->ref + << it->first << " = " << it->second << endl; string file; - if (!queryDB(dbRefs, it->ref, file)) - throw Error("unknown file " + it->ref); + if (!queryDB(dbRefs, it->second, file)) + throw Error("unknown file " + it->second); - if (makeRef(file) != it->ref) + if (makeRef(file) != it->second) throw Error("file " + file + " is stale"); - env[it->name] = file; + env[it->first] = file; } + + string buildSystem; + + for (Params::iterator it = arguments.begin(); + it != arguments.end(); it++) + { + env[it->first] = it->second; + if (it->first == "system") + buildSystem = it->second; + } + + if (buildSystem != thisSystem) + throw Error("descriptor requires a `" + buildSystem + + "' but I am a `" + thisSystem + "'"); } @@ -299,7 +308,7 @@ void installPkg(string hash) fetchDeps(hash, env); builder = getFromEnv(env, "build"); - + /* Construct a path for the installed package. */ path = pkgHome + "/" + hash; diff --git a/test/tmpl/aterm-2.0.nix b/test/tmpl/aterm-2.0.nix index 24c427faf..20832dfcd 100644 --- a/test/tmpl/aterm-2.0.nix +++ b/test/tmpl/aterm-2.0.nix @@ -1,3 +1,5 @@ +id : aterm-2.0 + # Original sources. src = url(http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz) diff --git a/test/tmpl/atk-1.2.0.nix b/test/tmpl/atk-1.2.0.nix index 9899a95b1..46a6cf2c7 100644 --- a/test/tmpl/atk-1.2.0.nix +++ b/test/tmpl/atk-1.2.0.nix @@ -1,3 +1,5 @@ +id : atk-1.2.0 + pkgconfig <- ./pkgconfig-0.15.0.nix glib <- ./glib-2.2.1.nix diff --git a/test/tmpl/glib-2.2.1.nix b/test/tmpl/glib-2.2.1.nix index d9fdf0f1f..4ba80c65e 100644 --- a/test/tmpl/glib-2.2.1.nix +++ b/test/tmpl/glib-2.2.1.nix @@ -1,3 +1,5 @@ +id : glib-2.2.1 + pkgconfig <- ./pkgconfig-0.15.0.nix src = url(ftp://ftp.gtk.org/pub/gtk/v2.2/glib-2.2.1.tar.bz2) diff --git a/test/tmpl/gnet-1.1.8.nix b/test/tmpl/gnet-1.1.8.nix index 7f2b33cab..2d602a456 100644 --- a/test/tmpl/gnet-1.1.8.nix +++ b/test/tmpl/gnet-1.1.8.nix @@ -1,3 +1,5 @@ +id : gnet-1.1.8 + pkgconfig <- ./pkgconfig-0.15.0.nix glib <- ./glib-2.2.1.nix diff --git a/test/tmpl/gtk+-2.2.1.nix b/test/tmpl/gtk+-2.2.1.nix index b1a6fcbfb..cf34fd768 100644 --- a/test/tmpl/gtk+-2.2.1.nix +++ b/test/tmpl/gtk+-2.2.1.nix @@ -1,3 +1,5 @@ +id : gtk+-2.2.1 + pkgconfig <- ./pkgconfig-0.15.0.nix glib <- ./glib-2.2.1.nix atk <- ./atk-1.2.0.nix diff --git a/test/tmpl/gtkspell-2.0.2.nix b/test/tmpl/gtkspell-2.0.2.nix index 844a75867..c61539def 100644 --- a/test/tmpl/gtkspell-2.0.2.nix +++ b/test/tmpl/gtkspell-2.0.2.nix @@ -1,3 +1,5 @@ +id : gtkspell-2.0.2 + pkgconfig <- ./pkgconfig-0.15.0.nix glib <- ./glib-2.2.1.nix atk <- ./atk-1.2.0.nix diff --git a/test/tmpl/pan-0.13.4.nix b/test/tmpl/pan-0.13.4.nix index b206657fd..e8bd16e26 100644 --- a/test/tmpl/pan-0.13.4.nix +++ b/test/tmpl/pan-0.13.4.nix @@ -1,3 +1,5 @@ +id : pan-0.13.4 + pkgconfig <- ./pkgconfig-0.15.0.nix glib <- ./glib-2.2.1.nix atk <- ./atk-1.2.0.nix diff --git a/test/tmpl/pan-0.13.91-run.nix b/test/tmpl/pan-0.13.91-run.nix index 9fe803def..a28459916 100644 --- a/test/tmpl/pan-0.13.91-run.nix +++ b/test/tmpl/pan-0.13.91-run.nix @@ -1,3 +1,5 @@ +id : pan-0.13.91-run + pan <- ./pan-0.13.91.nix glib <- ./glib-2.2.1.nix diff --git a/test/tmpl/pan-0.13.91.nix b/test/tmpl/pan-0.13.91.nix index e6baf7629..61eae6708 100644 --- a/test/tmpl/pan-0.13.91.nix +++ b/test/tmpl/pan-0.13.91.nix @@ -1,3 +1,5 @@ +id : pan-0.13.91-run + pkgconfig <- ./pkgconfig-0.15.0.nix glib <- ./glib-2.2.1.nix atk <- ./atk-1.2.0.nix diff --git a/test/tmpl/pango-1.2.1.nix b/test/tmpl/pango-1.2.1.nix index 4be4c8991..cf9eda70f 100644 --- a/test/tmpl/pango-1.2.1.nix +++ b/test/tmpl/pango-1.2.1.nix @@ -1,3 +1,5 @@ +id : pango-1.2.1 + pkgconfig <- ./pkgconfig-0.15.0.nix glib <- ./glib-2.2.1.nix diff --git a/test/tmpl/pkgconfig-0.15.0.nix b/test/tmpl/pkgconfig-0.15.0.nix index 3ee7c13cd..7a5b80d39 100644 --- a/test/tmpl/pkgconfig-0.15.0.nix +++ b/test/tmpl/pkgconfig-0.15.0.nix @@ -1,3 +1,5 @@ +id : pkgconfig-0.15.0 + src = url(http://www.freedesktop.org/software/pkgconfig/releases/pkgconfig-0.15.0.tar.gz) build = ../build/pkgconfig-build.sh diff --git a/test/tmpl/pspell-.12.2.nix b/test/tmpl/pspell-.12.2.nix index 33e63b85a..c0d44302d 100644 --- a/test/tmpl/pspell-.12.2.nix +++ b/test/tmpl/pspell-.12.2.nix @@ -1,3 +1,5 @@ +id : pspell-.12.2 + src = url(http://unc.dl.sourceforge.net/sourceforge/pspell/pspell-.12.2.tar.gz) build = ../build/pspell-build.sh From eeab86e0acbb32cdb360443dd6efe7031fed7295 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 24 Mar 2003 16:43:52 +0000 Subject: [PATCH 0018/6440] * Typo fix. --- test/tmpl/pan-0.13.91.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tmpl/pan-0.13.91.nix b/test/tmpl/pan-0.13.91.nix index 61eae6708..13287f99d 100644 --- a/test/tmpl/pan-0.13.91.nix +++ b/test/tmpl/pan-0.13.91.nix @@ -1,4 +1,4 @@ -id : pan-0.13.91-run +id : pan-0.13.91 pkgconfig <- ./pkgconfig-0.15.0.nix glib <- ./glib-2.2.1.nix From 73c53935d00660301e9408beabf1c80d6ef48610 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 24 Mar 2003 17:49:56 +0000 Subject: [PATCH 0019/6440] * For efficiency: md5 integrated into nix. * Command `nix ensure' which is like `nix getpkg' except that if the has refers to a run action it will just ensure that the imports are there. * Command `nix closure' to print out the closure of the set of descriptors under the import relation, starting at a set of roots. This can be used for garbage collection (e.g., given a list of `activated' packages, we can delete all packages not reachable from those). * Command `nix graph' to print out a Dot graph of the dependency graph. * `nix-addroot' adds a root for the (unimplemented) garbage collector. --- src/Makefile | 12 +- src/md5.c | 435 ++++++++++++++++++++++++++++++++++++++++++++++++ src/md5.h | 151 +++++++++++++++++ src/nix-addroot | 18 ++ src/nix.cc | 260 ++++++++++++++++++++++------- 5 files changed, 810 insertions(+), 66 deletions(-) create mode 100644 src/md5.c create mode 100644 src/md5.h create mode 100755 src/nix-addroot diff --git a/src/Makefile b/src/Makefile index 6b4c792bb..237257275 100644 --- a/src/Makefile +++ b/src/Makefile @@ -2,8 +2,16 @@ all: nix nix-instantiate SYSTEM = $(shell ./config.guess) -nix: nix.cc - g++ -g -Wall -o nix nix.cc -ldb_cxx-4 -DSYSTEM=\"$(SYSTEM)\" +nix: nix.o md5.o + g++ -g -o $@ $^ -ldb_cxx-4 + +%.o: %.cc + g++ -g -Wall -o $@ -c $< -DSYSTEM=\"$(SYSTEM)\" + +%.o: %.c + gcc -g -Wall -o $@ -c $< -DSYSTEM=\"$(SYSTEM)\" + +md5.o: md5.c md5.h nix-instantiate: nix-instantiate.in sed "s/@SYSTEM@/$(SYSTEM)/" < $^ > $@ diff --git a/src/md5.c b/src/md5.c new file mode 100644 index 000000000..64ade3c6f --- /dev/null +++ b/src/md5.c @@ -0,0 +1,435 @@ +/* Functions to compute MD5 message digest of files or memory blocks. + according to the definition of MD5 in RFC 1321 from April 1992. + Copyright (C) 1995,1996,1997,1999,2000,2001 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +/* Written by Ulrich Drepper , 1995. */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include +#include + +#include "md5.h" + +#ifdef WORDS_BIGENDIAN +# define SWAP(n) \ + (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) +#else +# define SWAP(n) (n) +#endif + + +/* This array contains the bytes used to pad the buffer to the next + 64-byte boundary. (RFC 1321, 3.1: Step 1) */ +static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ }; + + +/* Initialize structure containing state of computation. + (RFC 1321, 3.3: Step 3) */ +void +md5_init_ctx (ctx) + struct md5_ctx *ctx; +{ + ctx->A = 0x67452301; + ctx->B = 0xefcdab89; + ctx->C = 0x98badcfe; + ctx->D = 0x10325476; + + ctx->total[0] = ctx->total[1] = 0; + ctx->buflen = 0; +} + +/* Put result from CTX in first 16 bytes following RESBUF. The result + must be in little endian byte order. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +void * +md5_read_ctx (ctx, resbuf) + const struct md5_ctx *ctx; + void *resbuf; +{ + ((md5_uint32 *) resbuf)[0] = SWAP (ctx->A); + ((md5_uint32 *) resbuf)[1] = SWAP (ctx->B); + ((md5_uint32 *) resbuf)[2] = SWAP (ctx->C); + ((md5_uint32 *) resbuf)[3] = SWAP (ctx->D); + + return resbuf; +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +void * +md5_finish_ctx (ctx, resbuf) + struct md5_ctx *ctx; + void *resbuf; +{ + /* Take yet unprocessed bytes into account. */ + md5_uint32 bytes = ctx->buflen; + size_t pad; + + /* Now count remaining bytes. */ + ctx->total[0] += bytes; + if (ctx->total[0] < bytes) + ++ctx->total[1]; + + pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes; + memcpy (&ctx->buffer[bytes], fillbuf, pad); + + /* Put the 64-bit file length in *bits* at the end of the buffer. */ + *(md5_uint32 *) &ctx->buffer[bytes + pad] = SWAP (ctx->total[0] << 3); + *(md5_uint32 *) &ctx->buffer[bytes + pad + 4] = SWAP ((ctx->total[1] << 3) | + (ctx->total[0] >> 29)); + + /* Process last bytes. */ + md5_process_block (ctx->buffer, bytes + pad + 8, ctx); + + return md5_read_ctx (ctx, resbuf); +} + +/* Compute MD5 message digest for bytes read from STREAM. The + resulting message digest number will be written into the 16 bytes + beginning at RESBLOCK. */ +int +md5_stream (stream, resblock) + FILE *stream; + void *resblock; +{ + /* Important: BLOCKSIZE must be a multiple of 64. */ +#define BLOCKSIZE 4096 + struct md5_ctx ctx; + char buffer[BLOCKSIZE + 72]; + size_t sum; + + /* Initialize the computation context. */ + md5_init_ctx (&ctx); + + /* Iterate over full file contents. */ + while (1) + { + /* We read the file in blocks of BLOCKSIZE bytes. One call of the + computation function processes the whole buffer so that with the + next round of the loop another block can be read. */ + size_t n; + sum = 0; + + /* Read block. Take care for partial reads. */ + do + { + n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream); + + sum += n; + } + while (sum < BLOCKSIZE && n != 0); + if (n == 0 && ferror (stream)) + return 1; + + /* If end of file is reached, end the loop. */ + if (n == 0) + break; + + /* Process buffer with BLOCKSIZE bytes. Note that + BLOCKSIZE % 64 == 0 + */ + md5_process_block (buffer, BLOCKSIZE, &ctx); + } + + /* Add the last bytes if necessary. */ + if (sum > 0) + md5_process_bytes (buffer, sum, &ctx); + + /* Construct result in desired memory. */ + md5_finish_ctx (&ctx, resblock); + return 0; +} + +/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +void * +md5_buffer (buffer, len, resblock) + const char *buffer; + size_t len; + void *resblock; +{ + struct md5_ctx ctx; + + /* Initialize the computation context. */ + md5_init_ctx (&ctx); + + /* Process whole buffer but last len % 64 bytes. */ + md5_process_bytes (buffer, len, &ctx); + + /* Put result in desired memory area. */ + return md5_finish_ctx (&ctx, resblock); +} + + +void +md5_process_bytes (buffer, len, ctx) + const void *buffer; + size_t len; + struct md5_ctx *ctx; +{ + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + if (ctx->buflen != 0) + { + size_t left_over = ctx->buflen; + size_t add = 128 - left_over > len ? len : 128 - left_over; + + memcpy (&ctx->buffer[left_over], buffer, add); + ctx->buflen += add; + + if (ctx->buflen > 64) + { + md5_process_block (ctx->buffer, ctx->buflen & ~63, ctx); + + ctx->buflen &= 63; + /* The regions in the following copy operation cannot overlap. */ + memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63], + ctx->buflen); + } + + buffer = (const char *) buffer + add; + len -= add; + } + + /* Process available complete blocks. */ + if (len >= 64) + { +#if !_STRING_ARCH_unaligned +/* To check alignment gcc has an appropriate operator. Other + compilers don't. */ +# if __GNUC__ >= 2 +# define UNALIGNED_P(p) (((md5_uintptr) p) % __alignof__ (md5_uint32) != 0) +# else +# define UNALIGNED_P(p) (((md5_uintptr) p) % sizeof (md5_uint32) != 0) +# endif + if (UNALIGNED_P (buffer)) + while (len > 64) + { + md5_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx); + buffer = (const char *) buffer + 64; + len -= 64; + } + else +#endif + { + md5_process_block (buffer, len & ~63, ctx); + buffer = (const char *) buffer + (len & ~63); + len &= 63; + } + } + + /* Move remaining bytes in internal buffer. */ + if (len > 0) + { + size_t left_over = ctx->buflen; + + memcpy (&ctx->buffer[left_over], buffer, len); + left_over += len; + if (left_over >= 64) + { + md5_process_block (ctx->buffer, 64, ctx); + left_over -= 64; + memcpy (ctx->buffer, &ctx->buffer[64], left_over); + } + ctx->buflen = left_over; + } +} + + +/* These are the four functions used in the four steps of the MD5 algorithm + and defined in the RFC 1321. The first function is a little bit optimized + (as found in Colin Plumbs public domain implementation). */ +/* #define FF(b, c, d) ((b & c) | (~b & d)) */ +#define FF(b, c, d) (d ^ (b & (c ^ d))) +#define FG(b, c, d) FF (d, b, c) +#define FH(b, c, d) (b ^ c ^ d) +#define FI(b, c, d) (c ^ (b | ~d)) + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 64 == 0. */ + +void +md5_process_block (buffer, len, ctx) + const void *buffer; + size_t len; + struct md5_ctx *ctx; +{ + md5_uint32 correct_words[16]; + const md5_uint32 *words = buffer; + size_t nwords = len / sizeof (md5_uint32); + const md5_uint32 *endp = words + nwords; + md5_uint32 A = ctx->A; + md5_uint32 B = ctx->B; + md5_uint32 C = ctx->C; + md5_uint32 D = ctx->D; + + /* First increment the byte count. RFC 1321 specifies the possible + length of the file up to 2^64 bits. Here we only compute the + number of bytes. Do a double word increment. */ + ctx->total[0] += len; + if (ctx->total[0] < len) + ++ctx->total[1]; + + /* Process all bytes in the buffer with 64 bytes in each round of + the loop. */ + while (words < endp) + { + md5_uint32 *cwp = correct_words; + md5_uint32 A_save = A; + md5_uint32 B_save = B; + md5_uint32 C_save = C; + md5_uint32 D_save = D; + + /* First round: using the given function, the context and a constant + the next context is computed. Because the algorithms processing + unit is a 32-bit word and it is determined to work on words in + little endian byte order we perhaps have to change the byte order + before the computation. To reduce the work for the next steps + we store the swapped words in the array CORRECT_WORDS. */ + +#define OP(a, b, c, d, s, T) \ + do \ + { \ + a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T; \ + ++words; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* It is unfortunate that C does not provide an operator for + cyclic rotation. Hope the C compiler is smart enough. */ +#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s))) + + /* Before we start, one word to the strange constants. + They are defined in RFC 1321 as + + T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64 + */ + + /* Round 1. */ + OP (A, B, C, D, 7, 0xd76aa478); + OP (D, A, B, C, 12, 0xe8c7b756); + OP (C, D, A, B, 17, 0x242070db); + OP (B, C, D, A, 22, 0xc1bdceee); + OP (A, B, C, D, 7, 0xf57c0faf); + OP (D, A, B, C, 12, 0x4787c62a); + OP (C, D, A, B, 17, 0xa8304613); + OP (B, C, D, A, 22, 0xfd469501); + OP (A, B, C, D, 7, 0x698098d8); + OP (D, A, B, C, 12, 0x8b44f7af); + OP (C, D, A, B, 17, 0xffff5bb1); + OP (B, C, D, A, 22, 0x895cd7be); + OP (A, B, C, D, 7, 0x6b901122); + OP (D, A, B, C, 12, 0xfd987193); + OP (C, D, A, B, 17, 0xa679438e); + OP (B, C, D, A, 22, 0x49b40821); + + /* For the second to fourth round we have the possibly swapped words + in CORRECT_WORDS. Redefine the macro to take an additional first + argument specifying the function to use. */ +#undef OP +#define OP(f, a, b, c, d, k, s, T) \ + do \ + { \ + a += f (b, c, d) + correct_words[k] + T; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* Round 2. */ + OP (FG, A, B, C, D, 1, 5, 0xf61e2562); + OP (FG, D, A, B, C, 6, 9, 0xc040b340); + OP (FG, C, D, A, B, 11, 14, 0x265e5a51); + OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa); + OP (FG, A, B, C, D, 5, 5, 0xd62f105d); + OP (FG, D, A, B, C, 10, 9, 0x02441453); + OP (FG, C, D, A, B, 15, 14, 0xd8a1e681); + OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8); + OP (FG, A, B, C, D, 9, 5, 0x21e1cde6); + OP (FG, D, A, B, C, 14, 9, 0xc33707d6); + OP (FG, C, D, A, B, 3, 14, 0xf4d50d87); + OP (FG, B, C, D, A, 8, 20, 0x455a14ed); + OP (FG, A, B, C, D, 13, 5, 0xa9e3e905); + OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8); + OP (FG, C, D, A, B, 7, 14, 0x676f02d9); + OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a); + + /* Round 3. */ + OP (FH, A, B, C, D, 5, 4, 0xfffa3942); + OP (FH, D, A, B, C, 8, 11, 0x8771f681); + OP (FH, C, D, A, B, 11, 16, 0x6d9d6122); + OP (FH, B, C, D, A, 14, 23, 0xfde5380c); + OP (FH, A, B, C, D, 1, 4, 0xa4beea44); + OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9); + OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60); + OP (FH, B, C, D, A, 10, 23, 0xbebfbc70); + OP (FH, A, B, C, D, 13, 4, 0x289b7ec6); + OP (FH, D, A, B, C, 0, 11, 0xeaa127fa); + OP (FH, C, D, A, B, 3, 16, 0xd4ef3085); + OP (FH, B, C, D, A, 6, 23, 0x04881d05); + OP (FH, A, B, C, D, 9, 4, 0xd9d4d039); + OP (FH, D, A, B, C, 12, 11, 0xe6db99e5); + OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8); + OP (FH, B, C, D, A, 2, 23, 0xc4ac5665); + + /* Round 4. */ + OP (FI, A, B, C, D, 0, 6, 0xf4292244); + OP (FI, D, A, B, C, 7, 10, 0x432aff97); + OP (FI, C, D, A, B, 14, 15, 0xab9423a7); + OP (FI, B, C, D, A, 5, 21, 0xfc93a039); + OP (FI, A, B, C, D, 12, 6, 0x655b59c3); + OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92); + OP (FI, C, D, A, B, 10, 15, 0xffeff47d); + OP (FI, B, C, D, A, 1, 21, 0x85845dd1); + OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f); + OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0); + OP (FI, C, D, A, B, 6, 15, 0xa3014314); + OP (FI, B, C, D, A, 13, 21, 0x4e0811a1); + OP (FI, A, B, C, D, 4, 6, 0xf7537e82); + OP (FI, D, A, B, C, 11, 10, 0xbd3af235); + OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb); + OP (FI, B, C, D, A, 9, 21, 0xeb86d391); + + /* Add the starting values of the context. */ + A += A_save; + B += B_save; + C += C_save; + D += D_save; + } + + /* Put checksum in context given as argument. */ + ctx->A = A; + ctx->B = B; + ctx->C = C; + ctx->D = D; +} diff --git a/src/md5.h b/src/md5.h new file mode 100644 index 000000000..6301e4558 --- /dev/null +++ b/src/md5.h @@ -0,0 +1,151 @@ +/* Declaration of functions and data types used for MD5 sum computing + library functions. + Copyright (C) 1995,1996,1997,1999,2000,2001 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#ifndef _MD5_H +#define _MD5_H 1 + +#include + +#if defined HAVE_LIMITS_H || _LIBC +# include +#endif + +/* The following contortions are an attempt to use the C preprocessor + to determine an unsigned integral type that is 32 bits wide. An + alternative approach is to use autoconf's AC_CHECK_SIZEOF macro, but + doing that would require that the configure script compile and *run* + the resulting executable. Locally running cross-compiled executables + is usually not possible. */ + +#ifdef _LIBC +# include +typedef uint32_t md5_uint32; +typedef uintptr_t md5_uintptr; +#else +# if defined __STDC__ && __STDC__ +# define UINT_MAX_32_BITS 4294967295U +# else +# define UINT_MAX_32_BITS 0xFFFFFFFF +# endif + +/* If UINT_MAX isn't defined, assume it's a 32-bit type. + This should be valid for all systems GNU cares about because + that doesn't include 16-bit systems, and only modern systems + (that certainly have ) have 64+-bit integral types. */ + +# ifndef UINT_MAX +# define UINT_MAX UINT_MAX_32_BITS +# endif + +# if UINT_MAX == UINT_MAX_32_BITS + typedef unsigned int md5_uint32; +# else +# if USHRT_MAX == UINT_MAX_32_BITS + typedef unsigned short md5_uint32; +# else +# if ULONG_MAX == UINT_MAX_32_BITS + typedef unsigned long md5_uint32; +# else + /* The following line is intended to evoke an error. + Using #error is not portable enough. */ + "Cannot determine unsigned 32-bit data type." +# endif +# endif +# endif +/* We have to make a guess about the integer type equivalent in size + to pointers which should always be correct. */ +typedef unsigned long int md5_uintptr; +#endif + +#undef __P +#if defined (__STDC__) && __STDC__ +# define __P(x) x +#else +# define __P(x) () +#endif + +/* Structure to save state of computation between the single steps. */ +struct md5_ctx +{ + md5_uint32 A; + md5_uint32 B; + md5_uint32 C; + md5_uint32 D; + + md5_uint32 total[2]; + md5_uint32 buflen; + char buffer[128] __attribute__ ((__aligned__ (__alignof__ (md5_uint32)))); +}; + +/* + * The following three functions are build up the low level used in + * the functions `md5_stream' and `md5_buffer'. + */ + +/* Initialize structure containing state of computation. + (RFC 1321, 3.3: Step 3) */ +extern void md5_init_ctx __P ((struct md5_ctx *ctx)); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is necessary that LEN is a multiple of 64!!! */ +extern void md5_process_block __P ((const void *buffer, size_t len, + struct md5_ctx *ctx)); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of 64. */ +extern void md5_process_bytes __P ((const void *buffer, size_t len, + struct md5_ctx *ctx)); + +/* Process the remaining bytes in the buffer and put result from CTX + in first 16 bytes following RESBUF. The result is always in little + endian byte order, so that a byte-wise output yields to the wanted + ASCII representation of the message digest. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +extern void *md5_finish_ctx __P ((struct md5_ctx *ctx, void *resbuf)); + + +/* Put result from CTX in first 16 bytes following RESBUF. The result is + always in little endian byte order, so that a byte-wise output yields + to the wanted ASCII representation of the message digest. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +extern void *md5_read_ctx __P ((const struct md5_ctx *ctx, void *resbuf)); + + +/* Compute MD5 message digest for bytes read from STREAM. The + resulting message digest number will be written into the 16 bytes + beginning at RESBLOCK. */ +extern int md5_stream __P ((FILE *stream, void *resblock)); + +/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +extern void *md5_buffer __P ((const char *buffer, size_t len, + void *resblock)); + +#endif /* md5.h */ diff --git a/src/nix-addroot b/src/nix-addroot new file mode 100755 index 000000000..3ab9e8a25 --- /dev/null +++ b/src/nix-addroot @@ -0,0 +1,18 @@ +#! /bin/sh + +ROOTLIST=~/.nixroots + +if ! test -f $ROOTLIST; then + touch $ROOTLIST +fi + +for i in $*; do + if nix ensure $i > /dev/null; then + if grep -q $i $ROOTLIST; then + echo $i already is a root + else + echo adding root $i + echo $i >> $ROOTLIST + fi + fi +done diff --git a/src/nix.cc b/src/nix.cc index d53a809b7..4ff49eabd 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include @@ -14,6 +16,10 @@ #include +extern "C" { +#include "md5.h" +} + using namespace std; @@ -146,8 +152,20 @@ void enumDB(const string & dbname, DBPairs & contents) } +string printHash(unsigned char * buf) +{ + ostringstream str; + for (int i = 0; i < 16; i++) { + str.fill('0'); + str.width(2); + str << hex << (int) buf[i]; + } + return str.str(); +} + + /* Verify that a reference is valid (that is, is a MD5 hash code). */ -void checkRef(const string & s) +void checkHash(const string & s) { string err = "invalid reference: " + s; if (s.length() != 32) @@ -162,31 +180,36 @@ void checkRef(const string & s) /* Compute the MD5 hash of a file. */ -string makeRef(string filename) +string hashFile(string filename) { - char hash[33]; - - FILE * pipe = popen(("md5sum " + filename + " 2> /dev/null").c_str(), "r"); - if (!pipe) throw BadRefError("cannot execute md5sum"); - - if (fread(hash, 32, 1, pipe) != 1) - throw BadRefError("cannot read hash from md5sum of " + filename); - hash[32] = 0; - - pclose(pipe); - - checkRef(hash); - return hash; + unsigned char hash[16]; + FILE * file = fopen(filename.c_str(), "rb"); + if (!file) + throw BadRefError("file `" + filename + "' does not exist"); + int err = md5_stream(file, hash); + fclose(file); + if (err) throw BadRefError("cannot hash file"); + return printHash(hash); } -typedef pair Param; -typedef list Params; +typedef map Params; -void readPkgDescr(const string & pkgfile, +void readPkgDescr(const string & hash, Params & pkgImports, Params & fileImports, Params & arguments) { + string pkgfile; + + if (!queryDB(dbRefs, hash, pkgfile)) + throw Error("unknown package " + hash); + + // cerr << "reading information about " + hash + " from " + pkgfile + "\n"; + + /* Verify that the file hasn't changed. !!! race */ + if (hashFile(pkgfile) != hash) + throw Error("file " + pkgfile + " is stale"); + ifstream file; file.exceptions(ios::badbit); file.open(pkgfile.c_str()); @@ -206,13 +229,13 @@ void readPkgDescr(const string & pkgfile, str >> name >> op >> ref; if (op == "<-") { - checkRef(ref); - pkgImports.push_back(Param(name, ref)); + checkHash(ref); + pkgImports[name] = ref; } else if (op == "=") { - checkRef(ref); - fileImports.push_back(Param(name, ref)); + checkHash(ref); + fileImports[name] = ref; } else if (op == ":") - arguments.push_back(Param(name, ref)); + arguments[name] = ref; else throw Error("invalid operator " + op); } } @@ -226,20 +249,9 @@ typedef map Environment; void fetchDeps(string hash, Environment & env) { - string pkgfile; - - if (!queryDB(dbRefs, hash, pkgfile)) - throw Error("unknown package " + hash); - - cerr << "reading information about " + hash + " from " + pkgfile + "\n"; - - /* Verify that the file hasn't changed. !!! race */ - if (makeRef(pkgfile) != hash) - throw Error("file " + pkgfile + " is stale"); - /* Read the package description file. */ Params pkgImports, fileImports, arguments; - readPkgDescr(pkgfile, pkgImports, fileImports, arguments); + readPkgDescr(hash, pkgImports, fileImports, arguments); /* Recursively fetch all the dependencies, filling in the environment as we go along. */ @@ -264,7 +276,7 @@ void fetchDeps(string hash, Environment & env) if (!queryDB(dbRefs, it->second, file)) throw Error("unknown file " + it->second); - if (makeRef(file) != it->second) + if (hashFile(file) != it->second) throw Error("file " + file + " is stale"); env[it->first] = file; @@ -374,7 +386,7 @@ void installPkg(string hash) string getPkg(string hash) { string path; - checkRef(hash); + checkHash(hash); while (!queryDB(dbInstPkgs, hash, path)) installPkg(hash); return path; @@ -434,6 +446,20 @@ void runPkg(string hash) } +void ensurePkg(string hash) +{ + Params pkgImports, fileImports, arguments; + readPkgDescr(hash, pkgImports, fileImports, arguments); + + if (fileImports.find("build") != fileImports.end()) + getPkg(hash); + else if (fileImports.find("run") != fileImports.end()) { + Environment env; + fetchDeps(hash, env); + } else throw Error("invalid descriptor"); +} + + string absPath(string filename) { if (filename[0] != '/') { @@ -450,14 +476,14 @@ string absPath(string filename) void registerFile(string filename) { filename = absPath(filename); - setDB(dbRefs, makeRef(filename), filename); + setDB(dbRefs, hashFile(filename), filename); } /* This is primarily used for bootstrapping. */ void registerInstalledPkg(string hash, string path) { - checkRef(hash); + checkHash(hash); if (path == "") delDB(dbInstPkgs, hash); else @@ -483,8 +509,10 @@ void verifyDB() it != fileRefs.end(); it++) { try { - if (makeRef(it->second) != it->first) + if (hashFile(it->second) != it->first) { + cerr << "file " << it->second << " has changed\n"; delDB(dbRefs, it->first); + } } catch (BadRefError e) { /* !!! better error check */ cerr << "file " << it->second << " has disappeared\n"; delDB(dbRefs, it->first); @@ -519,48 +547,136 @@ void listInstalledPkgs() for (DBPairs::iterator it = instPkgs.begin(); it != instPkgs.end(); it++) + cout << it->first << endl; +} + + +void printInfo(vector hashes) +{ + for (vector::iterator it = hashes.begin(); + it != hashes.end(); it++) { - string descr; - if (!queryDB(dbRefs, it->first, descr)) - descr = "descriptor missing"; - cout << it->first << " " << descr << endl; + try { + Params pkgImports, fileImports, arguments; + readPkgDescr(*it, pkgImports, fileImports, arguments); + cout << *it << " " << getFromEnv(arguments, "id") << endl; + } catch (Error & e) { + cout << *it << " (descriptor missing)\n"; + } } } -void run(int argc, char * * argv) +void computeClosure(const vector & rootHashes, + set & result) +{ + list workList(rootHashes.begin(), rootHashes.end()); + set doneSet; + + while (!workList.empty()) { + string hash = workList.front(); + workList.pop_front(); + + if (doneSet.find(hash) == doneSet.end()) { + doneSet.insert(hash); + + Params pkgImports, fileImports, arguments; + readPkgDescr(hash, pkgImports, fileImports, arguments); + + for (Params::iterator it = pkgImports.begin(); + it != pkgImports.end(); it++) + workList.push_back(it->second); + } + } + + result = doneSet; +} + + +void printClosure(const vector & rootHashes) +{ + set allHashes; + computeClosure(rootHashes, allHashes); + for (set::iterator it = allHashes.begin(); + it != allHashes.end(); it++) + cout << *it << endl; +} + + +string dotQuote(const string & s) +{ + return "\"" + s + "\""; +} + + +void printGraph(vector rootHashes) +{ + set allHashes; + computeClosure(rootHashes, allHashes); + + cout << "digraph G {\n"; + + for (set::iterator it = allHashes.begin(); + it != allHashes.end(); it++) + { + Params pkgImports, fileImports, arguments; + readPkgDescr(*it, pkgImports, fileImports, arguments); + + cout << dotQuote(*it) << "[label = \"" + << getFromEnv(arguments, "id") + << "\"];\n"; + + for (Params::iterator it2 = pkgImports.begin(); + it2 != pkgImports.end(); it2++) + cout << dotQuote(it2->second) << " -> " + << dotQuote(*it) << ";\n"; + } + + cout << "}\n"; +} + + +void run(vector args) { UsageError argcError("wrong number of arguments"); string cmd; - if (argc < 1) - throw UsageError("no command specified"); - - cmd = argv[0]; - argc--, argv++; + if (args.size() < 1) throw UsageError("no command specified"); + + cmd = args[0]; + args.erase(args.begin()); // O(n) if (cmd == "init") { - if (argc != 0) throw argcError; + if (args.size() != 0) throw argcError; initDB(); } else if (cmd == "verify") { - if (argc != 0) throw argcError; + if (args.size() != 0) throw argcError; verifyDB(); } else if (cmd == "getpkg") { - if (argc != 1) throw argcError; - string path = getPkg(argv[0]); + if (args.size() != 1) throw argcError; + string path = getPkg(args[0]); cout << path << endl; } else if (cmd == "run") { - if (argc != 1) throw argcError; - runPkg(argv[0]); + if (args.size() != 1) throw argcError; + runPkg(args[0]); + } else if (cmd == "ensure") { + if (args.size() != 1) throw argcError; + ensurePkg(args[0]); } else if (cmd == "regfile") { - if (argc != 1) throw argcError; - registerFile(argv[0]); + if (args.size() != 1) throw argcError; + registerFile(args[0]); } else if (cmd == "reginst") { - if (argc != 2) throw argcError; - registerInstalledPkg(argv[0], argv[1]); + if (args.size() != 2) throw argcError; + registerInstalledPkg(args[0], args[1]); } else if (cmd == "listinst") { - if (argc != 0) throw argcError; + if (args.size() != 0) throw argcError; listInstalledPkgs(); + } else if (cmd == "info") { + printInfo(args); + } else if (cmd == "closure") { + printClosure(args); + } else if (cmd == "graph") { + printGraph(args); } else throw UsageError("unknown command: " + string(cmd)); } @@ -594,6 +710,20 @@ Subcommands: run HASH Run the descriptor referenced by HASH. + + ensure HASH + Like getpkg, but if HASH refers to a run descriptor, fetch only + the dependencies. + + info HASH... + Print information about the specified descriptors. + + closure HASH... + Determine the closure of the set of descriptors under the import + relation, starting at the given roots. + + graph HASH... + Like closure, but print a dot graph specification. "; } @@ -630,11 +760,13 @@ void main2(int argc, char * * argv) } } - argc -= optind, argv += optind; - run(argc, argv); + vector args; + argc--, argv++; + while (argc--) args.push_back(*argv++); + run(args); } - + int main(int argc, char * * argv) { prog = argv[0]; From 3f1a1457e9ad91f93151200fe43c7c33ea95417b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 25 Mar 2003 11:39:51 +0000 Subject: [PATCH 0020/6440] * Integrate hash into instantiated descriptor file names. * Use MD5::Digest. --- src/nix-instantiate.in | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/nix-instantiate.in b/src/nix-instantiate.in index e501a2c08..508b9eb28 100755 --- a/src/nix-instantiate.in +++ b/src/nix-instantiate.in @@ -3,6 +3,7 @@ use strict; use FileHandle; use File::Spec; +use Digest::MD5; my $system = "@SYSTEM@"; @@ -34,6 +35,15 @@ sub fetchFile { } } +sub hashFile { + my $file = shift; + open FILE, "< $file" or die "cannot open $file"; + # !!! error checking + my $hash = Digest::MD5->new->addfile(*FILE)->hexdigest; + close FILE; + return $hash; +} + sub convert { my $descr = shift; @@ -47,9 +57,9 @@ sub convert { my $IN = new FileHandle; my $OUT = new FileHandle; - my $outfile = "$outdir/$fn"; + my $tmpfile = "$outdir/$fn-tmp"; open $IN, "< $descr" or die "cannot open $descr"; - open $OUT, "> $outfile" or die "cannot create $outfile"; + open $OUT, "> $tmpfile" or die "cannot create $tmpfile"; print $OUT "system : $system\n"; @@ -60,26 +70,28 @@ sub convert { my ($name, $loc) = ($1, $2); my $file = fetchFile($loc); $file = File::Spec->rel2abs($file, $dir); - my $out = `md5sum $file`; - die unless ($? == 0); - $out =~ /^([0-9a-f]+)\s/; - my $hash = $1; + my $hash = hashFile($file); print $OUT "$name = $hash\n"; } elsif (/^(\w+)\s*<-\s*([+\w\d\.\/-]+)\s*(\#.*)?$/) { my $name = $1; my $file = $2; $file = File::Spec->rel2abs($file, $dir); $file = convert($file); - my $out = `md5sum $file`; - die unless ($? == 0); - $out =~ /^([0-9a-f]+)\s/; - my $hash = $1; + my $hash = hashFile($file); print $OUT "$name <- $hash\n"; } else { print $OUT "$_\n"; } } + close $OUT; + close $IN; + + my $hash = hashFile($tmpfile); + + my $outfile = "$outdir/$hash-$fn"; + rename($tmpfile, $outfile) or die "cannot rename $tmpfile to $outfile"; + $donetmpls{$descr} = $outfile; return $outfile; } From 0f40a560cab23f70881e5af405ea112a869dc39a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 25 Mar 2003 16:36:25 +0000 Subject: [PATCH 0021/6440] * Added a script nix-activate which builds a list of "activated" packages (i.e., the packages that should appear in the user's $PATH, and so on). Based on this list, the script nix-populate creates a hierarchy of symlinks to the relevant files in those packages (e.g., for pkg/bin and pkg/lib). A nice property of nix-populate is that on each run it creates a *new* tree, rather than updating the old one. It then atomically switches over to the new tree. This allows atomic upgrades or rollbacks on the set of activated packages. --- src/nix-activate | 22 +++++++++++++ src/nix-addroot | 18 ---------- src/nix-populate | 86 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 18 deletions(-) create mode 100755 src/nix-activate delete mode 100755 src/nix-addroot create mode 100755 src/nix-populate diff --git a/src/nix-activate b/src/nix-activate new file mode 100755 index 000000000..9fe646686 --- /dev/null +++ b/src/nix-activate @@ -0,0 +1,22 @@ +#! /usr/bin/perl -w + +use strict; + +my $pkglist = "/home/eelco/.nixactivations"; + +if (!-f $pkglist) { + system "touch $pkglist"; +} + +my $hash; +foreach $hash (@ARGV) { + system "grep -q $hash $pkglist"; + if ($?) { + print STDERR "activating $hash\n"; + system "nix getpkg $hash > /dev/null"; + if ($?) { die "`nix getpkg' failed"; } + system "echo $hash >> $pkglist"; + } +} + +system "nix-populate"; diff --git a/src/nix-addroot b/src/nix-addroot deleted file mode 100755 index 3ab9e8a25..000000000 --- a/src/nix-addroot +++ /dev/null @@ -1,18 +0,0 @@ -#! /bin/sh - -ROOTLIST=~/.nixroots - -if ! test -f $ROOTLIST; then - touch $ROOTLIST -fi - -for i in $*; do - if nix ensure $i > /dev/null; then - if grep -q $i $ROOTLIST; then - echo $i already is a root - else - echo adding root $i - echo $i >> $ROOTLIST - fi - fi -done diff --git a/src/nix-populate b/src/nix-populate new file mode 100755 index 000000000..294ded893 --- /dev/null +++ b/src/nix-populate @@ -0,0 +1,86 @@ +#! /usr/bin/perl -w + +use strict; + +my $pkglist = $ENV{"NIX_ACTIVATIONS"}; +$pkglist or die "NIX_ACTIVATIONS not set"; +my $linkdir = $ENV{"NIX_LINKS"}; +$linkdir or die "NIX_LINKS not set"; +my @dirs = ("bin", "sbin", "lib"); + +# Figure out a generation number. +my $nr = 1; +while (-e "$linkdir/$nr") { $nr++; } +my $gendir = "$linkdir/$nr"; +print "populating $gendir\n"; + +# Create the subdirectories. +mkdir $gendir; +foreach my $dir (@dirs) { + mkdir "$gendir/$dir"; +} + +# For each activated package, create symlinks. + +sub createLinks { + my $srcdir = shift; + my $dstdir = shift; + + my @srcfiles = glob("$srcdir/*"); + + foreach my $srcfile (@srcfiles) { + my $basename = $srcfile; + $basename =~ s/^.*\///g; # strip directory + my $dstfile = "$dstdir/$basename"; + if (-d $srcfile) { + # !!! hack for resolving name clashes + if (!-e $dstfile) { + mkdir($dstfile) or + die "error creating directory $dstfile"; + } + -d $dstfile or die "$dstfile is not a directory"; + createLinks($srcfile, $dstfile); + } else { + print "linking $dstfile to $srcfile\n"; + symlink($srcfile, $dstfile) or + die "error creating link $dstfile"; + } + } +} + + +open PKGS, "< $pkglist"; + +while () { + chomp; + my $hash = $_; + + my $pkgdir = `nix getpkg $hash`; + if ($?) { die "`nix getpkg' failed"; } + chomp $pkgdir; + + print "merging $pkgdir\n"; + + foreach my $dir (@dirs) { + createLinks("$pkgdir/$dir", "$gendir/$dir"); + } +} + +close PKGS; + +# Make $gendir the current generation by pointing $linkdir/current to +# it. The rename() system call is supposed to be essentially atomic +# on Unix. That is, if we have links `current -> X' and `new_current +# -> Y', and we rename new_current to current, a process accessing +# current will see X or Y, but never a file-not-found or other error +# condition. This is sufficient to atomically switch the current link +# tree. + +my $current = "$linkdir/current"; + +print "switching $current to $gendir\n"; + +my $tmplink = "$linkdir/new_current"; +symlink($gendir, $tmplink) or die "cannot create $tmplink"; +rename($tmplink, $current) or die "cannot rename $tmplink"; + From f915f773495e9675a6cd514742666c8c12f005e6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 28 Mar 2003 09:53:22 +0000 Subject: [PATCH 0022/6440] * Allow arguments to be passed to programs in `nix run'. --- src/nix.cc | 52 ++++++++++++++++++++++--------------------- test/build/pan-run.sh | 2 +- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index 4ff49eabd..c24cb1fd7 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -393,7 +393,7 @@ string getPkg(string hash) } -void runPkg(string hash) +void runPkg(string hash, const vector & args) { string src; string path; @@ -424,8 +424,17 @@ void runPkg(string hash) putenv((char *) s->c_str()); } + /* Create the list of arguments. */ + const char * args2[env.size() + 2]; + int i = 0; + args2[i++] = runner.c_str(); + for (vector::const_iterator it = args.begin(); + it != args.end(); it++, i++) + args2[i] = it->c_str(); + args2[i] = 0; + /* Execute the runner. This should not return. */ - execl(runner.c_str(), runner.c_str(), 0); + execv(runner.c_str(), (char * *) args2); cout << strerror(errno) << endl; @@ -657,8 +666,8 @@ void run(vector args) string path = getPkg(args[0]); cout << path << endl; } else if (cmd == "run") { - if (args.size() != 1) throw argcError; - runPkg(args[0]); + if (args.size() < 1) throw argcError; + runPkg(args[0], vector(args.begin() + 1, args.end())); } else if (cmd == "ensure") { if (args.size() != 1) throw argcError; ensurePkg(args[0]); @@ -708,8 +717,8 @@ Subcommands: listinst Prints a list of installed packages. - run HASH - Run the descriptor referenced by HASH. + run HASH ARGS... + Run the descriptor referenced by HASH with the given arguments. ensure HASH Like getpkg, but if HASH refers to a run descriptor, fetch only @@ -730,8 +739,6 @@ Subcommands: void main2(int argc, char * * argv) { - int c; - umask(0022); if (getenv(PKGINFO_ENVVAR)) @@ -740,28 +747,23 @@ void main2(int argc, char * * argv) if (getenv(PKGHOME_ENVVAR)) pkgHome = getenv(PKGHOME_ENVVAR); - opterr = 0; - - while ((c = getopt(argc, argv, "hd:")) != EOF) { - - switch (c) { - - case 'h': + /* Parse the global flags. */ + while (argc) { + string arg(*argv); + cout << arg << endl; + if (arg == "-h" || arg == "--help") { printUsage(); return; - - case 'd': + } else if (arg == "-d") { dbfile = optarg; - break; - - default: - throw UsageError("invalid option `" + string(1, optopt) + "'"); - break; - } + } else if (arg[0] == '-') { + throw UsageError("invalid option `" + arg + "'"); + } else break; + argv++, argc--; } + /* Put the remainder in a vector and pass it to run2(). */ vector args; - argc--, argv++; while (argc--) args.push_back(*argv++); run(args); } @@ -769,7 +771,7 @@ void main2(int argc, char * * argv) int main(int argc, char * * argv) { - prog = argv[0]; + prog = *argv++, argc--; try { try { diff --git a/test/build/pan-run.sh b/test/build/pan-run.sh index 923a23292..c933a3803 100755 --- a/test/build/pan-run.sh +++ b/test/build/pan-run.sh @@ -4,4 +4,4 @@ export LD_LIBRARY_PATH=$glib/lib:$atk/lib:$pango/lib:$gtk/lib:$gnet/lib:$pspell/ ldd $pan/bin/pan -$pan/bin/pan \ No newline at end of file +$pan/bin/pan $* \ No newline at end of file From 278ea4097e4deca33da1a08d746e8d80a620ce95 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 28 Mar 2003 10:33:34 +0000 Subject: [PATCH 0023/6440] * Don't fork in `nix run'. --- src/nix.cc | 64 +++++++++++------------------------ test/build/pan-run.sh | 5 ++- test/tmpl/pan-0.13.91-run.nix | 2 +- 3 files changed, 25 insertions(+), 46 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index c24cb1fd7..275a37bea 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -406,52 +406,29 @@ void runPkg(string hash, const vector & args) runner = getFromEnv(env, "run"); - /* Fork a child to build the package. */ - pid_t pid; - switch (pid = fork()) { - - case -1: - throw Error("unable to fork"); - - case 0: { /* child */ - - /* Fill in the environment. We don't bother freeing the - strings, since we'll exec or die soon anyway. */ - for (Environment::iterator it = env.begin(); - it != env.end(); it++) - { - string * s = new string(it->first + "=" + it->second); - putenv((char *) s->c_str()); - } - - /* Create the list of arguments. */ - const char * args2[env.size() + 2]; - int i = 0; - args2[i++] = runner.c_str(); - for (vector::const_iterator it = args.begin(); - it != args.end(); it++, i++) - args2[i] = it->c_str(); - args2[i] = 0; - - /* Execute the runner. This should not return. */ - execv(runner.c_str(), (char * *) args2); - - cout << strerror(errno) << endl; - - cout << "unable to execute runner\n"; - _exit(1); } - + /* Fill in the environment. We don't bother freeing the + strings, since we'll exec or die soon anyway. */ + for (Environment::iterator it = env.begin(); + it != env.end(); it++) + { + string * s = new string(it->first + "=" + it->second); + putenv((char *) s->c_str()); } - /* parent */ + /* Create the list of arguments. */ + const char * args2[env.size() + 2]; + int i = 0; + args2[i++] = runner.c_str(); + for (vector::const_iterator it = args.begin(); + it != args.end(); it++, i++) + args2[i] = it->c_str(); + args2[i] = 0; - /* Wait for the child to finish. */ - int status; - if (waitpid(pid, &status, 0) != pid) - throw Error("unable to wait for child"); - - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) - throw Error("unable to run package"); + /* Execute the runner. This should not return. */ + execv(runner.c_str(), (char * *) args2); + + cout << strerror(errno) << endl; + throw Error("unable to execute runner"); } @@ -750,7 +727,6 @@ void main2(int argc, char * * argv) /* Parse the global flags. */ while (argc) { string arg(*argv); - cout << arg << endl; if (arg == "-h" || arg == "--help") { printUsage(); return; diff --git a/test/build/pan-run.sh b/test/build/pan-run.sh index c933a3803..1d9db5377 100755 --- a/test/build/pan-run.sh +++ b/test/build/pan-run.sh @@ -4,4 +4,7 @@ export LD_LIBRARY_PATH=$glib/lib:$atk/lib:$pango/lib:$gtk/lib:$gnet/lib:$pspell/ ldd $pan/bin/pan -$pan/bin/pan $* \ No newline at end of file +prog=$1 +shift + +$pan/bin/$prog $* diff --git a/test/tmpl/pan-0.13.91-run.nix b/test/tmpl/pan-0.13.91-run.nix index a28459916..f9c13e64d 100644 --- a/test/tmpl/pan-0.13.91-run.nix +++ b/test/tmpl/pan-0.13.91-run.nix @@ -1,4 +1,4 @@ -id : pan-0.13.91-run +id : pan-0.13.91-run-2 pan <- ./pan-0.13.91.nix From 31f177ef0a7463f59a28342032eb8948994ce1a6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 28 Mar 2003 16:27:23 +0000 Subject: [PATCH 0024/6440] * Check for collissions. --- src/nix-populate | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nix-populate b/src/nix-populate index 294ded893..fa70ace80 100755 --- a/src/nix-populate +++ b/src/nix-populate @@ -40,6 +40,9 @@ sub createLinks { } -d $dstfile or die "$dstfile is not a directory"; createLinks($srcfile, $dstfile); + } elsif (-l $dstfile) { + my $target = readlink($dstfile); + die "collission between $srcfile and $target"; } else { print "linking $dstfile to $srcfile\n"; symlink($srcfile, $dstfile) or From ced20f187e36927adc88ab628b67838f51155244 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 31 Mar 2003 14:28:15 +0000 Subject: [PATCH 0025/6440] * Nix descriptor for Subversion. --- test/build/subversion-build.sh | 14 ++++++++++++++ test/tmpl/subversion-0.20.1.nix | 5 +++++ 2 files changed, 19 insertions(+) create mode 100755 test/build/subversion-build.sh create mode 100644 test/tmpl/subversion-0.20.1.nix diff --git a/test/build/subversion-build.sh b/test/build/subversion-build.sh new file mode 100755 index 000000000..300c94a2f --- /dev/null +++ b/test/build/subversion-build.sh @@ -0,0 +1,14 @@ +#! /bin/sh + +export PATH=/bin:/usr/bin + +export LDFLAGS=-s + +top=`pwd` +tar xvfz $src +cd subversion-* +./configure --prefix=$top --with-ssl +make +make install +cd .. +rm -rf subversion-* diff --git a/test/tmpl/subversion-0.20.1.nix b/test/tmpl/subversion-0.20.1.nix new file mode 100644 index 000000000..37a5d7ea3 --- /dev/null +++ b/test/tmpl/subversion-0.20.1.nix @@ -0,0 +1,5 @@ +id : subversion-0.20.1 + +src = url(http://subversion.tigris.org/files/documents/15/3440/subversion-0.20.1.tar.gz) + +build = ../build/subversion-build.sh From 383f9bb0f19f76fa4cdbdfb5327d82e77212c1b6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 1 Apr 2003 14:00:47 +0000 Subject: [PATCH 0026/6440] * Use ATerms for Nix descriptors. --- src/Makefile | 2 +- src/nix-instantiate.in | 34 +++++++++++++++++++++------ src/nix-populate | 2 +- src/nix.cc | 53 ++++++++++++++++++++++-------------------- 4 files changed, 57 insertions(+), 34 deletions(-) diff --git a/src/Makefile b/src/Makefile index 237257275..3398b8f3a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -3,7 +3,7 @@ all: nix nix-instantiate SYSTEM = $(shell ./config.guess) nix: nix.o md5.o - g++ -g -o $@ $^ -ldb_cxx-4 + g++ -g -o $@ $^ -ldb_cxx-4 -lATerm %.o: %.cc g++ -g -Wall -o $@ -c $< -DSYSTEM=\"$(SYSTEM)\" diff --git a/src/nix-instantiate.in b/src/nix-instantiate.in index 508b9eb28..b5093391b 100755 --- a/src/nix-instantiate.in +++ b/src/nix-instantiate.in @@ -59,37 +59,57 @@ sub convert { my $OUT = new FileHandle; my $tmpfile = "$outdir/$fn-tmp"; open $IN, "< $descr" or die "cannot open $descr"; - open $OUT, "> $tmpfile" or die "cannot create $tmpfile"; - print $OUT "system : $system\n"; +# Descr([Bind("x", Str("y")), Bind("x", File("1234")), Bind("x", Pkg("1234"))]) +# bindings alphabetisch gesorteerd + + my %bindings; while (<$IN>) { chomp; + s/\s*#.*$//; + next if (/^$/); if (/^(\w+)\s*=\s*([^\#\s]*)\s*(\#.*)?$/) { my ($name, $loc) = ($1, $2); my $file = fetchFile($loc); $file = File::Spec->rel2abs($file, $dir); my $hash = hashFile($file); - print $OUT "$name = $hash\n"; + $bindings{$name} = "File(\"$hash\")"; } elsif (/^(\w+)\s*<-\s*([+\w\d\.\/-]+)\s*(\#.*)?$/) { my $name = $1; my $file = $2; $file = File::Spec->rel2abs($file, $dir); $file = convert($file); my $hash = hashFile($file); - print $OUT "$name <- $hash\n"; + $bindings{$name} = "Pkg(\"$hash\")"; + } elsif (/^(\w+)\s*:\s*([+\w\d\.\/-]+)\s*(\#.*)?$/) { + my $name = $1; + my $value = $2; + $bindings{$name} = "Str(\"$value\")"; } else { - print $OUT "$_\n"; + die "syntax error: $_"; } } - close $OUT; close $IN; + $bindings{"system"} = "Str(\"$system\")"; + + open $OUT, "| baffle -wt > $tmpfile" or die "cannot create $tmpfile"; + print $OUT "Descr(["; + my $first = 1; + foreach my $name (sort (keys %bindings)) { + if (!$first) { print $OUT ","; }; + print $OUT "Bind(\"$name\",$bindings{$name})"; + $first = 0; + } + print $OUT "])"; + close $OUT; + my $hash = hashFile($tmpfile); - my $outfile = "$outdir/$hash-$fn"; + my $outfile = "$outdir/$fn-$hash"; rename($tmpfile, $outfile) or die "cannot rename $tmpfile to $outfile"; $donetmpls{$descr} = $outfile; diff --git a/src/nix-populate b/src/nix-populate index fa70ace80..50819d666 100755 --- a/src/nix-populate +++ b/src/nix-populate @@ -6,7 +6,7 @@ my $pkglist = $ENV{"NIX_ACTIVATIONS"}; $pkglist or die "NIX_ACTIVATIONS not set"; my $linkdir = $ENV{"NIX_LINKS"}; $linkdir or die "NIX_LINKS not set"; -my @dirs = ("bin", "sbin", "lib"); +my @dirs = ("bin", "sbin", "lib", "include"); # Figure out a generation number. my $nr = 1; diff --git a/src/nix.cc b/src/nix.cc index 275a37bea..c9091ef7a 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -16,6 +16,10 @@ #include +extern "C" { +#include +} + extern "C" { #include "md5.h" } @@ -210,33 +214,29 @@ void readPkgDescr(const string & hash, if (hashFile(pkgfile) != hash) throw Error("file " + pkgfile + " is stale"); - ifstream file; - file.exceptions(ios::badbit); - file.open(pkgfile.c_str()); - - while (!file.eof()) { - string line; - getline(file, line); + ATerm term = ATreadFromNamedFile(pkgfile.c_str()); + if (!term) throw Error("cannot read aterm " + pkgfile); - int n = line.find('#'); - if (n >= 0) line = line.erase(n); + ATerm bindings; + if (!ATmatch(term, "Descr()", &bindings)) + throw Error("invalid term in " + pkgfile); - if ((int) line.find_first_not_of(" ") < 0) continue; - - istringstream str(line); - - string name, op, ref; - str >> name >> op >> ref; - - if (op == "<-") { - checkHash(ref); - pkgImports[name] = ref; - } else if (op == "=") { - checkHash(ref); - fileImports[name] = ref; - } else if (op == ":") - arguments[name] = ref; - else throw Error("invalid operator " + op); + char * cname; + ATerm value; + while (ATmatch(bindings, "[Bind(, ), ]", + &cname, &value, &bindings)) + { + string name(cname); + char * arg; + if (ATmatch(value, "Pkg()", &arg)) { + checkHash(arg); + pkgImports[name] = arg; + } else if (ATmatch(value, "File()", &arg)) { + checkHash(arg); + fileImports[name] = arg; + } else if (ATmatch(value, "Str()", &arg)) + arguments[name] = arg; + else throw Error("invalid binding in " + pkgfile); } } @@ -747,6 +747,9 @@ void main2(int argc, char * * argv) int main(int argc, char * * argv) { + ATerm bottomOfStack; + ATinit(argc, argv, &bottomOfStack); + prog = *argv++, argc--; try { From 5bc26fb73fe997b05c2e43593d17de102c4249b7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 2 Apr 2003 15:34:05 +0000 Subject: [PATCH 0027/6440] * Importing and exporting of pre-built packages. --- src/nix.cc | 116 ++++++++++++++++++++++++++++++++++---- test/build/aterm-build.sh | 2 + test/register | 18 +++--- 3 files changed, 115 insertions(+), 21 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index c9091ef7a..d44647928 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -35,6 +35,7 @@ using namespace std; static string dbRefs = "refs"; static string dbInstPkgs = "pkginst"; +static string dbPrebuilts = "prebuilts"; static string prog; @@ -307,6 +308,14 @@ string getFromEnv(const Environment & env, const string & key) } +string queryPkgId(const string & hash) +{ + Params pkgImports, fileImports, arguments; + readPkgDescr(hash, pkgImports, fileImports, arguments); + return getFromEnv(arguments, "id"); +} + + void installPkg(string hash) { string pkgfile; @@ -321,8 +330,10 @@ void installPkg(string hash) builder = getFromEnv(env, "build"); + string id = getFromEnv(env, "id"); + /* Construct a path for the installed package. */ - path = pkgHome + "/" + hash; + path = pkgHome + "/" + id + "-" + hash; /* Create the path. */ if (mkdir(path.c_str(), 0777)) @@ -341,10 +352,33 @@ void installPkg(string hash) /* Go to the build directory. */ if (chdir(path.c_str())) { - cout << "unable to chdir to package directory\n"; + cerr << "unable to chdir to package directory\n"; _exit(1); } + /* Try to use a prebuilt. */ + string prebuiltHash, prebuiltFile; + if (queryDB(dbPrebuilts, hash, prebuiltHash) && + queryDB(dbRefs, prebuiltHash, prebuiltFile)) + { + cerr << "substituting prebuilt " << prebuiltFile << endl; + + if (hashFile(prebuiltFile) != prebuiltHash) { + cerr << "prebuilt " + prebuiltFile + " is stale\n"; + goto build; + } + + int res = system(("tar xvfj " + prebuiltFile).c_str()); // !!! escaping + if (WEXITSTATUS(res) != 0) + /* This is a fatal error, because path may now + have clobbered. */ + throw Error("cannot unpack " + prebuiltFile); + + _exit(0); + } + +build: + /* Fill in the environment. We don't bother freeing the strings, since we'll exec or die soon anyway. */ const char * env2[env.size() + 1]; @@ -357,9 +391,9 @@ void installPkg(string hash) /* Execute the builder. This should not return. */ execle(builder.c_str(), builder.c_str(), 0, env2); - cout << strerror(errno) << endl; + cerr << strerror(errno) << endl; - cout << "unable to execute builder\n"; + cerr << "unable to execute builder\n"; _exit(1); } } @@ -427,7 +461,7 @@ void runPkg(string hash, const vector & args) /* Execute the runner. This should not return. */ execv(runner.c_str(), (char * *) args2); - cout << strerror(errno) << endl; + cerr << strerror(errno) << endl; throw Error("unable to execute runner"); } @@ -446,6 +480,50 @@ void ensurePkg(string hash) } +void delPkg(string hash) +{ + string path; + checkHash(hash); + if (queryDB(dbInstPkgs, hash, path)) { + int res = system(("rm -rf " + path).c_str()); // !!! escaping + delDB(dbInstPkgs, hash); // not a bug + if (WEXITSTATUS(res) != 0) + throw Error("cannot delete " + path); + } +} + + +void exportPkgs(string outDir, vector hashes) +{ + for (vector::iterator it = hashes.begin(); + it != hashes.end(); it++) + { + string hash = *it; + string pkgDir = getPkg(hash); + string tmpFile = outDir + "/export_tmp"; + + int res = system(("cd " + pkgDir + " && tar cvfj " + tmpFile + " .").c_str()); // !!! escaping + if (WEXITSTATUS(res) != 0) + throw Error("cannot tar " + pkgDir); + + string prebuiltHash = hashFile(tmpFile); + string pkgId = queryPkgId(hash); + string prebuiltFile = outDir + "/" + + pkgId + "-" + hash + "-" + prebuiltHash + ".tar.bz2"; + + rename(tmpFile.c_str(), prebuiltFile.c_str()); + } +} + + +void regPrebuilt(string pkgHash, string prebuiltHash) +{ + checkHash(pkgHash); + checkHash(prebuiltHash); + setDB(dbPrebuilts, pkgHash, prebuiltHash); +} + + string absPath(string filename) { if (filename[0] != '/') { @@ -481,6 +559,7 @@ void initDB() { openDB(dbRefs, false); openDB(dbInstPkgs, false); + openDB(dbPrebuilts, false); } @@ -543,10 +622,8 @@ void printInfo(vector hashes) it != hashes.end(); it++) { try { - Params pkgImports, fileImports, arguments; - readPkgDescr(*it, pkgImports, fileImports, arguments); - cout << *it << " " << getFromEnv(arguments, "id") << endl; - } catch (Error & e) { + cout << *it << " " << queryPkgId(*it) << endl; + } catch (Error & e) { // !!! more specific cout << *it << " (descriptor missing)\n"; } } @@ -642,12 +719,21 @@ void run(vector args) if (args.size() != 1) throw argcError; string path = getPkg(args[0]); cout << path << endl; + } else if (cmd == "delpkg") { + if (args.size() != 1) throw argcError; + delPkg(args[0]); } else if (cmd == "run") { if (args.size() < 1) throw argcError; runPkg(args[0], vector(args.begin() + 1, args.end())); } else if (cmd == "ensure") { if (args.size() != 1) throw argcError; ensurePkg(args[0]); + } else if (cmd == "export") { + if (args.size() < 1) throw argcError; + exportPkgs(args[0], vector(args.begin() + 1, args.end())); + } else if (cmd == "regprebuilt") { + if (args.size() != 2) throw argcError; + regPrebuilt(args[0], args[1]); } else if (cmd == "regfile") { if (args.size() != 1) throw argcError; registerFile(args[0]); @@ -688,9 +774,13 @@ Subcommands: Register an installed package. getpkg HASH - Ensure that the package referenced by HASH is installed. Prints + Ensure that the package referenced by HASH is installed. Print out the path of the package on stdout. + delpkg HASH + Uninstall the package referenced by HASH, disregarding any + dependencies that other packages may have on HASH. + listinst Prints a list of installed packages. @@ -701,6 +791,12 @@ Subcommands: Like getpkg, but if HASH refers to a run descriptor, fetch only the dependencies. + export DIR HASH... + Export installed packages to DIR. + + regprebuilt HASH1 HASH2 + Inform Nix that an export HASH2 can be used to fast-build HASH1. + info HASH... Print information about the specified descriptors. diff --git a/test/build/aterm-build.sh b/test/build/aterm-build.sh index cfc83806b..f8cca71aa 100755 --- a/test/build/aterm-build.sh +++ b/test/build/aterm-build.sh @@ -8,3 +8,5 @@ cd aterm-* ./configure --prefix=$top make make install +cd .. +rm -rf aterm-* diff --git a/test/register b/test/register index 60cabd292..e03296495 100755 --- a/test/register +++ b/test/register @@ -1,23 +1,19 @@ #! /bin/sh -root=/home/eelco/Dev/nix/test -cd $root - -mkdir -p db -mkdir -p pkg -mkdir -p descr -mkdir -p netcache +mkdir -p $NIX/db +mkdir -p $NIX/pkg +mkdir -p $NIX/descr +mkdir -p $NIX/netcache nix init -if ! nix-instantiate descr netcache tmpl/*.nix; then +if ! nix-instantiate $NIX/descr $NIX/netcache tmpl/*.nix; then exit 1; fi -for i in netcache/*; do nix regfile $i; done +for i in $NIX/netcache/*; do nix regfile $i; done for i in build/*; do nix regfile $i; done -for i in descr/*; do +for i in $NIX/descr/*; do md5sum $i nix regfile $i done - From c68dca5dac87f710c880bcf78710a7be9609d29c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 2 Apr 2003 15:34:44 +0000 Subject: [PATCH 0028/6440] * Script to register pre-built packages. --- src/nix-regprebuilts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100755 src/nix-regprebuilts diff --git a/src/nix-regprebuilts b/src/nix-regprebuilts new file mode 100755 index 000000000..af643bf7e --- /dev/null +++ b/src/nix-regprebuilts @@ -0,0 +1,17 @@ +#! /usr/bin/perl -w + +my $dir = $ENV{"NIX"} . "/prebuilts"; + +foreach my $prebuilt (glob("$dir/*.tar.bz2")) { + + $prebuilt =~ /-([a-z0-9]+)-([a-z0-9]+).tar.bz2$/ + || die "invalid file name: $prebuilt"; + + my $pkgHash = $1; + my $prebuiltHash = $2; + + print "$pkgHash -> $prebuiltHash\n"; + + system "nix regprebuilt $pkgHash $prebuiltHash"; + system "nix regfile $prebuilt"; +} From ab723e341aab021624e93b7687c252acaeef9394 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 4 Apr 2003 12:02:40 +0000 Subject: [PATCH 0029/6440] * Minor refactoring: use iterators to process arguments. --- src/nix.cc | 159 ++++++++++++++++++++++++++--------------------------- 1 file changed, 79 insertions(+), 80 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index d44647928..74c0687d5 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -70,6 +70,9 @@ public: }; +typedef vector Strings; + + /* Wrapper classes that ensures that the database is closed upon object destruction. */ class Db2 : public Db @@ -427,7 +430,9 @@ string getPkg(string hash) } -void runPkg(string hash, const vector & args) +void runPkg(string hash, + Strings::iterator firstArg, + Strings::iterator lastArg) { string src; string path; @@ -453,8 +458,7 @@ void runPkg(string hash, const vector & args) const char * args2[env.size() + 2]; int i = 0; args2[i++] = runner.c_str(); - for (vector::const_iterator it = args.begin(); - it != args.end(); it++, i++) + for (Strings::const_iterator it = firstArg; it != lastArg; it++, i++) args2[i] = it->c_str(); args2[i] = 0; @@ -493,11 +497,11 @@ void delPkg(string hash) } -void exportPkgs(string outDir, vector hashes) +void exportPkgs(string outDir, + Strings::iterator firstHash, + Strings::iterator lastHash) { - for (vector::iterator it = hashes.begin(); - it != hashes.end(); it++) - { + for (Strings::iterator it = firstHash; it != lastHash; it++) { string hash = *it; string pkgDir = getPkg(hash); string tmpFile = outDir + "/export_tmp"; @@ -616,10 +620,9 @@ void listInstalledPkgs() } -void printInfo(vector hashes) +void printInfo(Strings::iterator first, Strings::iterator last) { - for (vector::iterator it = hashes.begin(); - it != hashes.end(); it++) + for (Strings::iterator it = first; it != last; it++) { try { cout << *it << " " << queryPkgId(*it) << endl; @@ -630,10 +633,10 @@ void printInfo(vector hashes) } -void computeClosure(const vector & rootHashes, +void computeClosure(Strings::iterator first, Strings::iterator last, set & result) { - list workList(rootHashes.begin(), rootHashes.end()); + list workList(first, last); set doneSet; while (!workList.empty()) { @@ -656,10 +659,10 @@ void computeClosure(const vector & rootHashes, } -void printClosure(const vector & rootHashes) +void printClosure(Strings::iterator first, Strings::iterator last) { set allHashes; - computeClosure(rootHashes, allHashes); + computeClosure(first, last, allHashes); for (set::iterator it = allHashes.begin(); it != allHashes.end(); it++) cout << *it << endl; @@ -672,10 +675,10 @@ string dotQuote(const string & s) } -void printGraph(vector rootHashes) +void printGraph(Strings::iterator first, Strings::iterator last) { set allHashes; - computeClosure(rootHashes, allHashes); + computeClosure(first, last, allHashes); cout << "digraph G {\n"; @@ -699,58 +702,8 @@ void printGraph(vector rootHashes) } -void run(vector args) +void run(Strings args) { - UsageError argcError("wrong number of arguments"); - string cmd; - - if (args.size() < 1) throw UsageError("no command specified"); - - cmd = args[0]; - args.erase(args.begin()); // O(n) - - if (cmd == "init") { - if (args.size() != 0) throw argcError; - initDB(); - } else if (cmd == "verify") { - if (args.size() != 0) throw argcError; - verifyDB(); - } else if (cmd == "getpkg") { - if (args.size() != 1) throw argcError; - string path = getPkg(args[0]); - cout << path << endl; - } else if (cmd == "delpkg") { - if (args.size() != 1) throw argcError; - delPkg(args[0]); - } else if (cmd == "run") { - if (args.size() < 1) throw argcError; - runPkg(args[0], vector(args.begin() + 1, args.end())); - } else if (cmd == "ensure") { - if (args.size() != 1) throw argcError; - ensurePkg(args[0]); - } else if (cmd == "export") { - if (args.size() < 1) throw argcError; - exportPkgs(args[0], vector(args.begin() + 1, args.end())); - } else if (cmd == "regprebuilt") { - if (args.size() != 2) throw argcError; - regPrebuilt(args[0], args[1]); - } else if (cmd == "regfile") { - if (args.size() != 1) throw argcError; - registerFile(args[0]); - } else if (cmd == "reginst") { - if (args.size() != 2) throw argcError; - registerInstalledPkg(args[0], args[1]); - } else if (cmd == "listinst") { - if (args.size() != 0) throw argcError; - listInstalledPkgs(); - } else if (cmd == "info") { - printInfo(args); - } else if (cmd == "closure") { - printClosure(args); - } else if (cmd == "graph") { - printGraph(args); - } else - throw UsageError("unknown command: " + string(cmd)); } @@ -810,7 +763,7 @@ Subcommands: } -void main2(int argc, char * * argv) +void run(Strings::iterator argCur, Strings::iterator argEnd) { umask(0022); @@ -821,8 +774,8 @@ void main2(int argc, char * * argv) pkgHome = getenv(PKGHOME_ENVVAR); /* Parse the global flags. */ - while (argc) { - string arg(*argv); + for ( ; argCur != argEnd; argCur++) { + string arg(*argCur); if (arg == "-h" || arg == "--help") { printUsage(); return; @@ -831,13 +784,57 @@ void main2(int argc, char * * argv) } else if (arg[0] == '-') { throw UsageError("invalid option `" + arg + "'"); } else break; - argv++, argc--; } - /* Put the remainder in a vector and pass it to run2(). */ - vector args; - while (argc--) args.push_back(*argv++); - run(args); + UsageError argcError("wrong number of arguments"); + + /* Parse the command. */ + if (argCur == argEnd) throw UsageError("no command specified"); + string cmd = *argCur++; + int argc = argEnd - argCur; + + if (cmd == "init") { + if (argc != 0) throw argcError; + initDB(); + } else if (cmd == "verify") { + if (argc != 0) throw argcError; + verifyDB(); + } else if (cmd == "getpkg") { + if (argc != 1) throw argcError; + string path = getPkg(*argCur); + cout << path << endl; + } else if (cmd == "delpkg") { + if (argc != 1) throw argcError; + delPkg(*argCur); + } else if (cmd == "run") { + if (argc < 1) throw argcError; + runPkg(*argCur, argCur + 1, argEnd); + } else if (cmd == "ensure") { + if (argc != 1) throw argcError; + ensurePkg(*argCur); + } else if (cmd == "export") { + if (argc < 1) throw argcError; + exportPkgs(*argCur, argCur + 1, argEnd); + } else if (cmd == "regprebuilt") { + if (argc != 2) throw argcError; + regPrebuilt(*argCur, argCur[1]); + } else if (cmd == "regfile") { + if (argc != 1) throw argcError; + registerFile(*argCur); + } else if (cmd == "reginst") { + if (argc != 2) throw argcError; + registerInstalledPkg(*argCur, argCur[1]); + } else if (cmd == "listinst") { + if (argc != 0) throw argcError; + listInstalledPkgs(); + } else if (cmd == "info") { + printInfo(argCur, argEnd); + } else if (cmd == "closure") { + printClosure(argCur, argEnd); + } else if (cmd == "graph") { + printGraph(argCur, argEnd); + } else + throw UsageError("unknown command: " + string(cmd)); } @@ -846,17 +843,19 @@ int main(int argc, char * * argv) ATerm bottomOfStack; ATinit(argc, argv, &bottomOfStack); - prog = *argv++, argc--; + /* Put the arguments in a vector. */ + Strings args; + while (argc--) args.push_back(*argv++); + Strings::iterator argCur = args.begin(), argEnd = args.end(); + + prog = *argCur++; try { try { - - main2(argc, argv); - + run(argCur, argEnd); } catch (DbException e) { throw Error(e.what()); } - } catch (UsageError & e) { cerr << "error: " << e.what() << endl << "Try `nix -h' for more information.\n"; From 136c00e881dd290d470923b0ce7760de2df5e0ad Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 4 Apr 2003 16:14:56 +0000 Subject: [PATCH 0030/6440] * Autoconf / Automake configuration and building. --- AUTHORS | 0 COPYING | 340 ++++++++++ ChangeLog | 0 INSTALL | 229 +++++++ Makefile.am | 1 + NEWS | 0 README | 0 configure.ac | 14 + src/Makefile | 21 - src/Makefile.am | 5 + src/config.guess | 1400 ---------------------------------------- src/nix-instantiate.in | 2 +- src/nix-regprebuilts | 2 +- src/nix.cc | 61 +- src/util.hh | 34 + 15 files changed, 639 insertions(+), 1470 deletions(-) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 configure.ac delete mode 100644 src/Makefile create mode 100644 src/Makefile.am delete mode 100755 src/config.guess create mode 100644 src/util.hh diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..e69de29bb diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..d60c31a97 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 000000000..e69de29bb diff --git a/INSTALL b/INSTALL new file mode 100644 index 000000000..a4b34144d --- /dev/null +++ b/INSTALL @@ -0,0 +1,229 @@ +Copyright 1994, 1995, 1996, 1999, 2000, 2001, 2002 Free Software +Foundation, Inc. + + This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. (Caching is +disabled by default to prevent problems with accidental use of stale +cache files.) + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You only need +`configure.ac' if you want to change it or regenerate `configure' using +a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not support the `VPATH' +variable, you have to compile the package for one architecture at a +time in the source code directory. After you have installed the +package for one architecture, use `make distclean' before reconfiguring +for another architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=PATH' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the `--target=TYPE' option to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +will cause the specified gcc to be used as the C compiler (unless it is +overridden in the site shell script). + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 000000000..af437a64d --- /dev/null +++ b/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = src diff --git a/NEWS b/NEWS new file mode 100644 index 000000000..e69de29bb diff --git a/README b/README new file mode 100644 index 000000000..e69de29bb diff --git a/configure.ac b/configure.ac new file mode 100644 index 000000000..e488ed78f --- /dev/null +++ b/configure.ac @@ -0,0 +1,14 @@ +AC_INIT(nix, 0.1) +AC_CONFIG_SRCDIR(src/nix.cc) +AC_CONFIG_AUX_DIR(config) +AM_INIT_AUTOMAKE + +AC_PREFIX_DEFAULT(/nix) + +AC_CANONICAL_HOST + +AC_PROG_CC +AC_PROG_CXX + +AC_CONFIG_FILES(Makefile src/Makefile src/nix-instantiate) +AC_OUTPUT \ No newline at end of file diff --git a/src/Makefile b/src/Makefile deleted file mode 100644 index 3398b8f3a..000000000 --- a/src/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -all: nix nix-instantiate - -SYSTEM = $(shell ./config.guess) - -nix: nix.o md5.o - g++ -g -o $@ $^ -ldb_cxx-4 -lATerm - -%.o: %.cc - g++ -g -Wall -o $@ -c $< -DSYSTEM=\"$(SYSTEM)\" - -%.o: %.c - gcc -g -Wall -o $@ -c $< -DSYSTEM=\"$(SYSTEM)\" - -md5.o: md5.c md5.h - -nix-instantiate: nix-instantiate.in - sed "s/@SYSTEM@/$(SYSTEM)/" < $^ > $@ - chmod +x $@ - -clean: - rm -f *.o nix nix-instantiate diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 000000000..17d801fbf --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,5 @@ +bin_PROGRAMS = nix + +nix_SOURCES = nix.cc md5.c +nix_CXXFLAGS = -DSYSTEM=\"@host@\" +nix_LDADD = -ldb_cxx-4 -lATerm diff --git a/src/config.guess b/src/config.guess deleted file mode 100755 index 9b1384be4..000000000 --- a/src/config.guess +++ /dev/null @@ -1,1400 +0,0 @@ -#! /bin/sh -# Attempt to guess a canonical system name. -# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, -# 2000, 2001, 2002 Free Software Foundation, Inc. - -timestamp='2002-11-30' - -# This file is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -# Originally written by Per Bothner . -# Please send patches to . Submit a context -# diff and a properly formatted ChangeLog entry. -# -# This script attempts to guess a canonical system name similar to -# config.sub. If it succeeds, it prints the system name on stdout, and -# exits with 0. Otherwise, it exits with 1. -# -# The plan is that this can be called by configure scripts if you -# don't specify an explicit build system type. - -me=`echo "$0" | sed -e 's,.*/,,'` - -usage="\ -Usage: $0 [OPTION] - -Output the configuration name of the system \`$me' is run on. - -Operation modes: - -h, --help print this help, then exit - -t, --time-stamp print date of last modification, then exit - -v, --version print version number, then exit - -Report bugs and patches to ." - -version="\ -GNU config.guess ($timestamp) - -Originally written by Per Bothner. -Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 -Free Software Foundation, Inc. - -This is free software; see the source for copying conditions. There is NO -warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." - -help=" -Try \`$me --help' for more information." - -# Parse command line -while test $# -gt 0 ; do - case $1 in - --time-stamp | --time* | -t ) - echo "$timestamp" ; exit 0 ;; - --version | -v ) - echo "$version" ; exit 0 ;; - --help | --h* | -h ) - echo "$usage"; exit 0 ;; - -- ) # Stop option processing - shift; break ;; - - ) # Use stdin as input. - break ;; - -* ) - echo "$me: invalid option $1$help" >&2 - exit 1 ;; - * ) - break ;; - esac -done - -if test $# != 0; then - echo "$me: too many arguments$help" >&2 - exit 1 -fi - -trap 'exit 1' 1 2 15 - -# CC_FOR_BUILD -- compiler used by this script. Note that the use of a -# compiler to aid in system detection is discouraged as it requires -# temporary files to be created and, as you can see below, it is a -# headache to deal with in a portable fashion. - -# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still -# use `HOST_CC' if defined, but it is deprecated. - -# This shell variable is my proudest work .. or something. --bje - -set_cc_for_build='tmpdir=${TMPDIR-/tmp}/config-guess-$$ ; -(old=`umask` && umask 077 && mkdir $tmpdir && umask $old && unset old) - || (echo "$me: cannot create $tmpdir" >&2 && exit 1) ; -dummy=$tmpdir/dummy ; -files="$dummy.c $dummy.o $dummy.rel $dummy" ; -trap '"'"'rm -f $files; rmdir $tmpdir; exit 1'"'"' 1 2 15 ; -case $CC_FOR_BUILD,$HOST_CC,$CC in - ,,) echo "int x;" > $dummy.c ; - for c in cc gcc c89 c99 ; do - if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then - CC_FOR_BUILD="$c"; break ; - fi ; - done ; - rm -f $files ; - if test x"$CC_FOR_BUILD" = x ; then - CC_FOR_BUILD=no_compiler_found ; - fi - ;; - ,,*) CC_FOR_BUILD=$CC ;; - ,*,*) CC_FOR_BUILD=$HOST_CC ;; -esac ; -unset files' - -# This is needed to find uname on a Pyramid OSx when run in the BSD universe. -# (ghazi@noc.rutgers.edu 1994-08-24) -if (test -f /.attbin/uname) >/dev/null 2>&1 ; then - PATH=$PATH:/.attbin ; export PATH -fi - -UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown -UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown -UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown -UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown - -# Note: order is significant - the case branches are not exclusive. - -case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in - *:NetBSD:*:*) - # NetBSD (nbsd) targets should (where applicable) match one or - # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, - # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently - # switched to ELF, *-*-netbsd* would select the old - # object file format. This provides both forward - # compatibility and a consistent mechanism for selecting the - # object file format. - # - # Note: NetBSD doesn't particularly care about the vendor - # portion of the name. We always set it to "unknown". - sysctl="sysctl -n hw.machine_arch" - UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ - /usr/sbin/$sysctl 2>/dev/null || echo unknown)` - case "${UNAME_MACHINE_ARCH}" in - armeb) machine=armeb-unknown ;; - arm*) machine=arm-unknown ;; - sh3el) machine=shl-unknown ;; - sh3eb) machine=sh-unknown ;; - *) machine=${UNAME_MACHINE_ARCH}-unknown ;; - esac - # The Operating System including object format, if it has switched - # to ELF recently, or will in the future. - case "${UNAME_MACHINE_ARCH}" in - arm*|i386|m68k|ns32k|sh3*|sparc|vax) - eval $set_cc_for_build - if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ - | grep __ELF__ >/dev/null - then - # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). - # Return netbsd for either. FIX? - os=netbsd - else - os=netbsdelf - fi - ;; - *) - os=netbsd - ;; - esac - # The OS release - # Debian GNU/NetBSD machines have a different userland, and - # thus, need a distinct triplet. However, they do not need - # kernel version information, so it can be replaced with a - # suitable tag, in the style of linux-gnu. - case "${UNAME_VERSION}" in - Debian*) - release='-gnu' - ;; - *) - release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` - ;; - esac - # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: - # contains redundant information, the shorter form: - # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. - echo "${machine}-${os}${release}" - exit 0 ;; - amiga:OpenBSD:*:*) - echo m68k-unknown-openbsd${UNAME_RELEASE} - exit 0 ;; - arc:OpenBSD:*:*) - echo mipsel-unknown-openbsd${UNAME_RELEASE} - exit 0 ;; - hp300:OpenBSD:*:*) - echo m68k-unknown-openbsd${UNAME_RELEASE} - exit 0 ;; - mac68k:OpenBSD:*:*) - echo m68k-unknown-openbsd${UNAME_RELEASE} - exit 0 ;; - macppc:OpenBSD:*:*) - echo powerpc-unknown-openbsd${UNAME_RELEASE} - exit 0 ;; - mvme68k:OpenBSD:*:*) - echo m68k-unknown-openbsd${UNAME_RELEASE} - exit 0 ;; - mvme88k:OpenBSD:*:*) - echo m88k-unknown-openbsd${UNAME_RELEASE} - exit 0 ;; - mvmeppc:OpenBSD:*:*) - echo powerpc-unknown-openbsd${UNAME_RELEASE} - exit 0 ;; - pmax:OpenBSD:*:*) - echo mipsel-unknown-openbsd${UNAME_RELEASE} - exit 0 ;; - sgi:OpenBSD:*:*) - echo mipseb-unknown-openbsd${UNAME_RELEASE} - exit 0 ;; - sun3:OpenBSD:*:*) - echo m68k-unknown-openbsd${UNAME_RELEASE} - exit 0 ;; - wgrisc:OpenBSD:*:*) - echo mipsel-unknown-openbsd${UNAME_RELEASE} - exit 0 ;; - *:OpenBSD:*:*) - echo ${UNAME_MACHINE}-unknown-openbsd${UNAME_RELEASE} - exit 0 ;; - alpha:OSF1:*:*) - if test $UNAME_RELEASE = "V4.0"; then - UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` - fi - # A Vn.n version is a released version. - # A Tn.n version is a released field test version. - # A Xn.n version is an unreleased experimental baselevel. - # 1.2 uses "1.2" for uname -r. - eval $set_cc_for_build - cat <$dummy.s - .data -\$Lformat: - .byte 37,100,45,37,120,10,0 # "%d-%x\n" - - .text - .globl main - .align 4 - .ent main -main: - .frame \$30,16,\$26,0 - ldgp \$29,0(\$27) - .prologue 1 - .long 0x47e03d80 # implver \$0 - lda \$2,-1 - .long 0x47e20c21 # amask \$2,\$1 - lda \$16,\$Lformat - mov \$0,\$17 - not \$1,\$18 - jsr \$26,printf - ldgp \$29,0(\$26) - mov 0,\$16 - jsr \$26,exit - .end main -EOF - $CC_FOR_BUILD -o $dummy $dummy.s 2>/dev/null - if test "$?" = 0 ; then - case `$dummy` in - 0-0) - UNAME_MACHINE="alpha" - ;; - 1-0) - UNAME_MACHINE="alphaev5" - ;; - 1-1) - UNAME_MACHINE="alphaev56" - ;; - 1-101) - UNAME_MACHINE="alphapca56" - ;; - 2-303) - UNAME_MACHINE="alphaev6" - ;; - 2-307) - UNAME_MACHINE="alphaev67" - ;; - 2-1307) - UNAME_MACHINE="alphaev68" - ;; - 3-1307) - UNAME_MACHINE="alphaev7" - ;; - esac - fi - rm -f $dummy.s $dummy && rmdir $tmpdir - echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[VTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` - exit 0 ;; - Alpha\ *:Windows_NT*:*) - # How do we know it's Interix rather than the generic POSIX subsystem? - # Should we change UNAME_MACHINE based on the output of uname instead - # of the specific Alpha model? - echo alpha-pc-interix - exit 0 ;; - 21064:Windows_NT:50:3) - echo alpha-dec-winnt3.5 - exit 0 ;; - Amiga*:UNIX_System_V:4.0:*) - echo m68k-unknown-sysv4 - exit 0;; - *:[Aa]miga[Oo][Ss]:*:*) - echo ${UNAME_MACHINE}-unknown-amigaos - exit 0 ;; - *:[Mm]orph[Oo][Ss]:*:*) - echo ${UNAME_MACHINE}-unknown-morphos - exit 0 ;; - *:OS/390:*:*) - echo i370-ibm-openedition - exit 0 ;; - arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) - echo arm-acorn-riscix${UNAME_RELEASE} - exit 0;; - SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) - echo hppa1.1-hitachi-hiuxmpp - exit 0;; - Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) - # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. - if test "`(/bin/universe) 2>/dev/null`" = att ; then - echo pyramid-pyramid-sysv3 - else - echo pyramid-pyramid-bsd - fi - exit 0 ;; - NILE*:*:*:dcosx) - echo pyramid-pyramid-svr4 - exit 0 ;; - DRS?6000:UNIX_SV:4.2*:7*) - case `/usr/bin/uname -p` in - sparc) echo sparc-icl-nx7 && exit 0 ;; - esac ;; - sun4H:SunOS:5.*:*) - echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` - exit 0 ;; - sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) - echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` - exit 0 ;; - i86pc:SunOS:5.*:*) - echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` - exit 0 ;; - sun4*:SunOS:6*:*) - # According to config.sub, this is the proper way to canonicalize - # SunOS6. Hard to guess exactly what SunOS6 will be like, but - # it's likely to be more like Solaris than SunOS4. - echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` - exit 0 ;; - sun4*:SunOS:*:*) - case "`/usr/bin/arch -k`" in - Series*|S4*) - UNAME_RELEASE=`uname -v` - ;; - esac - # Japanese Language versions have a version number like `4.1.3-JL'. - echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` - exit 0 ;; - sun3*:SunOS:*:*) - echo m68k-sun-sunos${UNAME_RELEASE} - exit 0 ;; - sun*:*:4.2BSD:*) - UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` - test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 - case "`/bin/arch`" in - sun3) - echo m68k-sun-sunos${UNAME_RELEASE} - ;; - sun4) - echo sparc-sun-sunos${UNAME_RELEASE} - ;; - esac - exit 0 ;; - aushp:SunOS:*:*) - echo sparc-auspex-sunos${UNAME_RELEASE} - exit 0 ;; - # The situation for MiNT is a little confusing. The machine name - # can be virtually everything (everything which is not - # "atarist" or "atariste" at least should have a processor - # > m68000). The system name ranges from "MiNT" over "FreeMiNT" - # to the lowercase version "mint" (or "freemint"). Finally - # the system name "TOS" denotes a system which is actually not - # MiNT. But MiNT is downward compatible to TOS, so this should - # be no problem. - atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) - echo m68k-atari-mint${UNAME_RELEASE} - exit 0 ;; - atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) - echo m68k-atari-mint${UNAME_RELEASE} - exit 0 ;; - *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) - echo m68k-atari-mint${UNAME_RELEASE} - exit 0 ;; - milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) - echo m68k-milan-mint${UNAME_RELEASE} - exit 0 ;; - hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) - echo m68k-hades-mint${UNAME_RELEASE} - exit 0 ;; - *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) - echo m68k-unknown-mint${UNAME_RELEASE} - exit 0 ;; - powerpc:machten:*:*) - echo powerpc-apple-machten${UNAME_RELEASE} - exit 0 ;; - RISC*:Mach:*:*) - echo mips-dec-mach_bsd4.3 - exit 0 ;; - RISC*:ULTRIX:*:*) - echo mips-dec-ultrix${UNAME_RELEASE} - exit 0 ;; - VAX*:ULTRIX*:*:*) - echo vax-dec-ultrix${UNAME_RELEASE} - exit 0 ;; - 2020:CLIX:*:* | 2430:CLIX:*:*) - echo clipper-intergraph-clix${UNAME_RELEASE} - exit 0 ;; - mips:*:*:UMIPS | mips:*:*:RISCos) - eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c -#ifdef __cplusplus -#include /* for printf() prototype */ - int main (int argc, char *argv[]) { -#else - int main (argc, argv) int argc; char *argv[]; { -#endif - #if defined (host_mips) && defined (MIPSEB) - #if defined (SYSTYPE_SYSV) - printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); - #endif - #if defined (SYSTYPE_SVR4) - printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); - #endif - #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) - printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); - #endif - #endif - exit (-1); - } -EOF - $CC_FOR_BUILD -o $dummy $dummy.c \ - && $dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ - && rm -f $dummy.c $dummy && rmdir $tmpdir && exit 0 - rm -f $dummy.c $dummy && rmdir $tmpdir - echo mips-mips-riscos${UNAME_RELEASE} - exit 0 ;; - Motorola:PowerMAX_OS:*:*) - echo powerpc-motorola-powermax - exit 0 ;; - Motorola:*:4.3:PL8-*) - echo powerpc-harris-powermax - exit 0 ;; - Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) - echo powerpc-harris-powermax - exit 0 ;; - Night_Hawk:Power_UNIX:*:*) - echo powerpc-harris-powerunix - exit 0 ;; - m88k:CX/UX:7*:*) - echo m88k-harris-cxux7 - exit 0 ;; - m88k:*:4*:R4*) - echo m88k-motorola-sysv4 - exit 0 ;; - m88k:*:3*:R3*) - echo m88k-motorola-sysv3 - exit 0 ;; - AViiON:dgux:*:*) - # DG/UX returns AViiON for all architectures - UNAME_PROCESSOR=`/usr/bin/uname -p` - if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] - then - if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ - [ ${TARGET_BINARY_INTERFACE}x = x ] - then - echo m88k-dg-dgux${UNAME_RELEASE} - else - echo m88k-dg-dguxbcs${UNAME_RELEASE} - fi - else - echo i586-dg-dgux${UNAME_RELEASE} - fi - exit 0 ;; - M88*:DolphinOS:*:*) # DolphinOS (SVR3) - echo m88k-dolphin-sysv3 - exit 0 ;; - M88*:*:R3*:*) - # Delta 88k system running SVR3 - echo m88k-motorola-sysv3 - exit 0 ;; - XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) - echo m88k-tektronix-sysv3 - exit 0 ;; - Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) - echo m68k-tektronix-bsd - exit 0 ;; - *:IRIX*:*:*) - echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` - exit 0 ;; - ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. - echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id - exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' - i*86:AIX:*:*) - echo i386-ibm-aix - exit 0 ;; - ia64:AIX:*:*) - if [ -x /usr/bin/oslevel ] ; then - IBM_REV=`/usr/bin/oslevel` - else - IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} - fi - echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} - exit 0 ;; - *:AIX:2:3) - if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then - eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c - #include - - main() - { - if (!__power_pc()) - exit(1); - puts("powerpc-ibm-aix3.2.5"); - exit(0); - } -EOF - $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && rm -f $dummy.c $dummy && rmdir $tmpdir && exit 0 - rm -f $dummy.c $dummy && rmdir $tmpdir - echo rs6000-ibm-aix3.2.5 - elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then - echo rs6000-ibm-aix3.2.4 - else - echo rs6000-ibm-aix3.2 - fi - exit 0 ;; - *:AIX:*:[45]) - IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` - if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then - IBM_ARCH=rs6000 - else - IBM_ARCH=powerpc - fi - if [ -x /usr/bin/oslevel ] ; then - IBM_REV=`/usr/bin/oslevel` - else - IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} - fi - echo ${IBM_ARCH}-ibm-aix${IBM_REV} - exit 0 ;; - *:AIX:*:*) - echo rs6000-ibm-aix - exit 0 ;; - ibmrt:4.4BSD:*|romp-ibm:BSD:*) - echo romp-ibm-bsd4.4 - exit 0 ;; - ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and - echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to - exit 0 ;; # report: romp-ibm BSD 4.3 - *:BOSX:*:*) - echo rs6000-bull-bosx - exit 0 ;; - DPX/2?00:B.O.S.:*:*) - echo m68k-bull-sysv3 - exit 0 ;; - 9000/[34]??:4.3bsd:1.*:*) - echo m68k-hp-bsd - exit 0 ;; - hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) - echo m68k-hp-bsd4.4 - exit 0 ;; - 9000/[34678]??:HP-UX:*:*) - HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` - case "${UNAME_MACHINE}" in - 9000/31? ) HP_ARCH=m68000 ;; - 9000/[34]?? ) HP_ARCH=m68k ;; - 9000/[678][0-9][0-9]) - if [ -x /usr/bin/getconf ]; then - sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` - sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` - case "${sc_cpu_version}" in - 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 - 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 - 532) # CPU_PA_RISC2_0 - case "${sc_kernel_bits}" in - 32) HP_ARCH="hppa2.0n" ;; - 64) HP_ARCH="hppa2.0w" ;; - '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 - esac ;; - esac - fi - if [ "${HP_ARCH}" = "" ]; then - eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c - - #define _HPUX_SOURCE - #include - #include - - int main () - { - #if defined(_SC_KERNEL_BITS) - long bits = sysconf(_SC_KERNEL_BITS); - #endif - long cpu = sysconf (_SC_CPU_VERSION); - - switch (cpu) - { - case CPU_PA_RISC1_0: puts ("hppa1.0"); break; - case CPU_PA_RISC1_1: puts ("hppa1.1"); break; - case CPU_PA_RISC2_0: - #if defined(_SC_KERNEL_BITS) - switch (bits) - { - case 64: puts ("hppa2.0w"); break; - case 32: puts ("hppa2.0n"); break; - default: puts ("hppa2.0"); break; - } break; - #else /* !defined(_SC_KERNEL_BITS) */ - puts ("hppa2.0"); break; - #endif - default: puts ("hppa1.0"); break; - } - exit (0); - } -EOF - (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` - if test -z "$HP_ARCH"; then HP_ARCH=hppa; fi - rm -f $dummy.c $dummy && rmdir $tmpdir - fi ;; - esac - echo ${HP_ARCH}-hp-hpux${HPUX_REV} - exit 0 ;; - ia64:HP-UX:*:*) - HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` - echo ia64-hp-hpux${HPUX_REV} - exit 0 ;; - 3050*:HI-UX:*:*) - eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c - #include - int - main () - { - long cpu = sysconf (_SC_CPU_VERSION); - /* The order matters, because CPU_IS_HP_MC68K erroneously returns - true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct - results, however. */ - if (CPU_IS_PA_RISC (cpu)) - { - switch (cpu) - { - case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; - case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; - case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; - default: puts ("hppa-hitachi-hiuxwe2"); break; - } - } - else if (CPU_IS_HP_MC68K (cpu)) - puts ("m68k-hitachi-hiuxwe2"); - else puts ("unknown-hitachi-hiuxwe2"); - exit (0); - } -EOF - $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && rm -f $dummy.c $dummy && rmdir $tmpdir && exit 0 - rm -f $dummy.c $dummy && rmdir $tmpdir - echo unknown-hitachi-hiuxwe2 - exit 0 ;; - 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) - echo hppa1.1-hp-bsd - exit 0 ;; - 9000/8??:4.3bsd:*:*) - echo hppa1.0-hp-bsd - exit 0 ;; - *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) - echo hppa1.0-hp-mpeix - exit 0 ;; - hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) - echo hppa1.1-hp-osf - exit 0 ;; - hp8??:OSF1:*:*) - echo hppa1.0-hp-osf - exit 0 ;; - i*86:OSF1:*:*) - if [ -x /usr/sbin/sysversion ] ; then - echo ${UNAME_MACHINE}-unknown-osf1mk - else - echo ${UNAME_MACHINE}-unknown-osf1 - fi - exit 0 ;; - parisc*:Lites*:*:*) - echo hppa1.1-hp-lites - exit 0 ;; - C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) - echo c1-convex-bsd - exit 0 ;; - C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) - if getsysinfo -f scalar_acc - then echo c32-convex-bsd - else echo c2-convex-bsd - fi - exit 0 ;; - C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) - echo c34-convex-bsd - exit 0 ;; - C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) - echo c38-convex-bsd - exit 0 ;; - C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) - echo c4-convex-bsd - exit 0 ;; - CRAY*Y-MP:*:*:*) - echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' - exit 0 ;; - CRAY*[A-Z]90:*:*:*) - echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ - | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ - -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ - -e 's/\.[^.]*$/.X/' - exit 0 ;; - CRAY*TS:*:*:*) - echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' - exit 0 ;; - CRAY*T3D:*:*:*) - echo alpha-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' - exit 0 ;; - CRAY*T3E:*:*:*) - echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' - exit 0 ;; - CRAY*SV1:*:*:*) - echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' - exit 0 ;; - F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) - FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` - FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` - FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` - echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" - exit 0 ;; - i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) - echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} - exit 0 ;; - sparc*:BSD/OS:*:*) - echo sparc-unknown-bsdi${UNAME_RELEASE} - exit 0 ;; - *:BSD/OS:*:*) - echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} - exit 0 ;; - *:FreeBSD:*:*) - # Determine whether the default compiler uses glibc. - eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c - #include - #if __GLIBC__ >= 2 - LIBC=gnu - #else - LIBC= - #endif -EOF - eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` - rm -f $dummy.c && rmdir $tmpdir - echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`${LIBC:+-$LIBC} - exit 0 ;; - i*:CYGWIN*:*) - echo ${UNAME_MACHINE}-pc-cygwin - exit 0 ;; - i*:MINGW*:*) - echo ${UNAME_MACHINE}-pc-mingw32 - exit 0 ;; - i*:PW*:*) - echo ${UNAME_MACHINE}-pc-pw32 - exit 0 ;; - x86:Interix*:3*) - echo i586-pc-interix3 - exit 0 ;; - [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) - echo i${UNAME_MACHINE}-pc-mks - exit 0 ;; - i*:Windows_NT*:* | Pentium*:Windows_NT*:*) - # How do we know it's Interix rather than the generic POSIX subsystem? - # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we - # UNAME_MACHINE based on the output of uname instead of i386? - echo i586-pc-interix - exit 0 ;; - i*:UWIN*:*) - echo ${UNAME_MACHINE}-pc-uwin - exit 0 ;; - p*:CYGWIN*:*) - echo powerpcle-unknown-cygwin - exit 0 ;; - prep*:SunOS:5.*:*) - echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` - exit 0 ;; - *:GNU:*:*) - echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` - exit 0 ;; - i*86:Minix:*:*) - echo ${UNAME_MACHINE}-pc-minix - exit 0 ;; - arm*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-gnu - exit 0 ;; - ia64:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-gnu - exit 0 ;; - m68*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-gnu - exit 0 ;; - mips:Linux:*:*) - eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c - #undef CPU - #undef mips - #undef mipsel - #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) - CPU=mipsel - #else - #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) - CPU=mips - #else - CPU= - #endif - #endif -EOF - eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` - rm -f $dummy.c && rmdir $tmpdir - test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 - ;; - mips64:Linux:*:*) - eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c - #undef CPU - #undef mips64 - #undef mips64el - #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) - CPU=mips64el - #else - #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) - CPU=mips64 - #else - CPU= - #endif - #endif -EOF - eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` - rm -f $dummy.c && rmdir $tmpdir - test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 - ;; - ppc:Linux:*:*) - echo powerpc-unknown-linux-gnu - exit 0 ;; - ppc64:Linux:*:*) - echo powerpc64-unknown-linux-gnu - exit 0 ;; - alpha:Linux:*:*) - case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in - EV5) UNAME_MACHINE=alphaev5 ;; - EV56) UNAME_MACHINE=alphaev56 ;; - PCA56) UNAME_MACHINE=alphapca56 ;; - PCA57) UNAME_MACHINE=alphapca56 ;; - EV6) UNAME_MACHINE=alphaev6 ;; - EV67) UNAME_MACHINE=alphaev67 ;; - EV68*) UNAME_MACHINE=alphaev68 ;; - esac - objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null - if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi - echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} - exit 0 ;; - parisc:Linux:*:* | hppa:Linux:*:*) - # Look for CPU level - case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in - PA7*) echo hppa1.1-unknown-linux-gnu ;; - PA8*) echo hppa2.0-unknown-linux-gnu ;; - *) echo hppa-unknown-linux-gnu ;; - esac - exit 0 ;; - parisc64:Linux:*:* | hppa64:Linux:*:*) - echo hppa64-unknown-linux-gnu - exit 0 ;; - s390:Linux:*:* | s390x:Linux:*:*) - echo ${UNAME_MACHINE}-ibm-linux - exit 0 ;; - sh*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-gnu - exit 0 ;; - sparc:Linux:*:* | sparc64:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-gnu - exit 0 ;; - x86_64:Linux:*:*) - echo x86_64-unknown-linux-gnu - exit 0 ;; - i*86:Linux:*:*) - # The BFD linker knows what the default object file format is, so - # first see if it will tell us. cd to the root directory to prevent - # problems with other programs or directories called `ld' in the path. - # Set LC_ALL=C to ensure ld outputs messages in English. - ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ - | sed -ne '/supported targets:/!d - s/[ ][ ]*/ /g - s/.*supported targets: *// - s/ .*// - p'` - case "$ld_supported_targets" in - elf32-i386) - TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" - ;; - a.out-i386-linux) - echo "${UNAME_MACHINE}-pc-linux-gnuaout" - exit 0 ;; - coff-i386) - echo "${UNAME_MACHINE}-pc-linux-gnucoff" - exit 0 ;; - "") - # Either a pre-BFD a.out linker (linux-gnuoldld) or - # one that does not give us useful --help. - echo "${UNAME_MACHINE}-pc-linux-gnuoldld" - exit 0 ;; - esac - # Determine whether the default compiler is a.out or elf - eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c - #include - #ifdef __ELF__ - # ifdef __GLIBC__ - # if __GLIBC__ >= 2 - LIBC=gnu - # else - LIBC=gnulibc1 - # endif - # else - LIBC=gnulibc1 - # endif - #else - #ifdef __INTEL_COMPILER - LIBC=gnu - #else - LIBC=gnuaout - #endif - #endif -EOF - eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` - rm -f $dummy.c && rmdir $tmpdir - test x"${LIBC}" != x && echo "${UNAME_MACHINE}-pc-linux-${LIBC}" && exit 0 - test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0 - ;; - i*86:DYNIX/ptx:4*:*) - # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. - # earlier versions are messed up and put the nodename in both - # sysname and nodename. - echo i386-sequent-sysv4 - exit 0 ;; - i*86:UNIX_SV:4.2MP:2.*) - # Unixware is an offshoot of SVR4, but it has its own version - # number series starting with 2... - # I am not positive that other SVR4 systems won't match this, - # I just have to hope. -- rms. - # Use sysv4.2uw... so that sysv4* matches it. - echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} - exit 0 ;; - i*86:OS/2:*:*) - # If we were able to find `uname', then EMX Unix compatibility - # is probably installed. - echo ${UNAME_MACHINE}-pc-os2-emx - exit 0 ;; - i*86:XTS-300:*:STOP) - echo ${UNAME_MACHINE}-unknown-stop - exit 0 ;; - i*86:atheos:*:*) - echo ${UNAME_MACHINE}-unknown-atheos - exit 0 ;; - i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) - echo i386-unknown-lynxos${UNAME_RELEASE} - exit 0 ;; - i*86:*DOS:*:*) - echo ${UNAME_MACHINE}-pc-msdosdjgpp - exit 0 ;; - i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) - UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` - if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then - echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} - else - echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} - fi - exit 0 ;; - i*86:*:5:[78]*) - case `/bin/uname -X | grep "^Machine"` in - *486*) UNAME_MACHINE=i486 ;; - *Pentium) UNAME_MACHINE=i586 ;; - *Pent*|*Celeron) UNAME_MACHINE=i686 ;; - esac - echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} - exit 0 ;; - i*86:*:3.2:*) - if test -f /usr/options/cb.name; then - UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then - UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` - (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 - (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ - && UNAME_MACHINE=i586 - (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ - && UNAME_MACHINE=i686 - (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ - && UNAME_MACHINE=i686 - echo ${UNAME_MACHINE}-pc-sco$UNAME_REL - else - echo ${UNAME_MACHINE}-pc-sysv32 - fi - exit 0 ;; - pc:*:*:*) - # Left here for compatibility: - # uname -m prints for DJGPP always 'pc', but it prints nothing about - # the processor, so we play safe by assuming i386. - echo i386-pc-msdosdjgpp - exit 0 ;; - Intel:Mach:3*:*) - echo i386-pc-mach3 - exit 0 ;; - paragon:*:*:*) - echo i860-intel-osf1 - exit 0 ;; - i860:*:4.*:*) # i860-SVR4 - if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then - echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 - else # Add other i860-SVR4 vendors below as they are discovered. - echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 - fi - exit 0 ;; - mini*:CTIX:SYS*5:*) - # "miniframe" - echo m68010-convergent-sysv - exit 0 ;; - mc68k:UNIX:SYSTEM5:3.51m) - echo m68k-convergent-sysv - exit 0 ;; - M680?0:D-NIX:5.3:*) - echo m68k-diab-dnix - exit 0 ;; - M68*:*:R3V[567]*:*) - test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; - 3[34]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0) - OS_REL='' - test -r /etc/.relid \ - && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` - /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ - && echo i486-ncr-sysv4.3${OS_REL} && exit 0 - /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ - && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; - 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) - /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ - && echo i486-ncr-sysv4 && exit 0 ;; - m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) - echo m68k-unknown-lynxos${UNAME_RELEASE} - exit 0 ;; - mc68030:UNIX_System_V:4.*:*) - echo m68k-atari-sysv4 - exit 0 ;; - TSUNAMI:LynxOS:2.*:*) - echo sparc-unknown-lynxos${UNAME_RELEASE} - exit 0 ;; - rs6000:LynxOS:2.*:*) - echo rs6000-unknown-lynxos${UNAME_RELEASE} - exit 0 ;; - PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) - echo powerpc-unknown-lynxos${UNAME_RELEASE} - exit 0 ;; - SM[BE]S:UNIX_SV:*:*) - echo mips-dde-sysv${UNAME_RELEASE} - exit 0 ;; - RM*:ReliantUNIX-*:*:*) - echo mips-sni-sysv4 - exit 0 ;; - RM*:SINIX-*:*:*) - echo mips-sni-sysv4 - exit 0 ;; - *:SINIX-*:*:*) - if uname -p 2>/dev/null >/dev/null ; then - UNAME_MACHINE=`(uname -p) 2>/dev/null` - echo ${UNAME_MACHINE}-sni-sysv4 - else - echo ns32k-sni-sysv - fi - exit 0 ;; - PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort - # says - echo i586-unisys-sysv4 - exit 0 ;; - *:UNIX_System_V:4*:FTX*) - # From Gerald Hewes . - # How about differentiating between stratus architectures? -djm - echo hppa1.1-stratus-sysv4 - exit 0 ;; - *:*:*:FTX*) - # From seanf@swdc.stratus.com. - echo i860-stratus-sysv4 - exit 0 ;; - *:VOS:*:*) - # From Paul.Green@stratus.com. - echo hppa1.1-stratus-vos - exit 0 ;; - mc68*:A/UX:*:*) - echo m68k-apple-aux${UNAME_RELEASE} - exit 0 ;; - news*:NEWS-OS:6*:*) - echo mips-sony-newsos6 - exit 0 ;; - R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) - if [ -d /usr/nec ]; then - echo mips-nec-sysv${UNAME_RELEASE} - else - echo mips-unknown-sysv${UNAME_RELEASE} - fi - exit 0 ;; - BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. - echo powerpc-be-beos - exit 0 ;; - BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. - echo powerpc-apple-beos - exit 0 ;; - BePC:BeOS:*:*) # BeOS running on Intel PC compatible. - echo i586-pc-beos - exit 0 ;; - SX-4:SUPER-UX:*:*) - echo sx4-nec-superux${UNAME_RELEASE} - exit 0 ;; - SX-5:SUPER-UX:*:*) - echo sx5-nec-superux${UNAME_RELEASE} - exit 0 ;; - SX-6:SUPER-UX:*:*) - echo sx6-nec-superux${UNAME_RELEASE} - exit 0 ;; - Power*:Rhapsody:*:*) - echo powerpc-apple-rhapsody${UNAME_RELEASE} - exit 0 ;; - *:Rhapsody:*:*) - echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} - exit 0 ;; - *:Darwin:*:*) - echo `uname -p`-apple-darwin${UNAME_RELEASE} - exit 0 ;; - *:procnto*:*:* | *:QNX:[0123456789]*:*) - UNAME_PROCESSOR=`uname -p` - if test "$UNAME_PROCESSOR" = "x86"; then - UNAME_PROCESSOR=i386 - UNAME_MACHINE=pc - fi - echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} - exit 0 ;; - *:QNX:*:4*) - echo i386-pc-qnx - exit 0 ;; - NSR-[DGKLNPTVW]:NONSTOP_KERNEL:*:*) - echo nsr-tandem-nsk${UNAME_RELEASE} - exit 0 ;; - *:NonStop-UX:*:*) - echo mips-compaq-nonstopux - exit 0 ;; - BS2000:POSIX*:*:*) - echo bs2000-siemens-sysv - exit 0 ;; - DS/*:UNIX_System_V:*:*) - echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} - exit 0 ;; - *:Plan9:*:*) - # "uname -m" is not consistent, so use $cputype instead. 386 - # is converted to i386 for consistency with other x86 - # operating systems. - if test "$cputype" = "386"; then - UNAME_MACHINE=i386 - else - UNAME_MACHINE="$cputype" - fi - echo ${UNAME_MACHINE}-unknown-plan9 - exit 0 ;; - *:TOPS-10:*:*) - echo pdp10-unknown-tops10 - exit 0 ;; - *:TENEX:*:*) - echo pdp10-unknown-tenex - exit 0 ;; - KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) - echo pdp10-dec-tops20 - exit 0 ;; - XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) - echo pdp10-xkl-tops20 - exit 0 ;; - *:TOPS-20:*:*) - echo pdp10-unknown-tops20 - exit 0 ;; - *:ITS:*:*) - echo pdp10-unknown-its - exit 0 ;; -esac - -#echo '(No uname command or uname output not recognized.)' 1>&2 -#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 - -eval $set_cc_for_build -cat >$dummy.c < -# include -#endif -main () -{ -#if defined (sony) -#if defined (MIPSEB) - /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, - I don't know.... */ - printf ("mips-sony-bsd\n"); exit (0); -#else -#include - printf ("m68k-sony-newsos%s\n", -#ifdef NEWSOS4 - "4" -#else - "" -#endif - ); exit (0); -#endif -#endif - -#if defined (__arm) && defined (__acorn) && defined (__unix) - printf ("arm-acorn-riscix"); exit (0); -#endif - -#if defined (hp300) && !defined (hpux) - printf ("m68k-hp-bsd\n"); exit (0); -#endif - -#if defined (NeXT) -#if !defined (__ARCHITECTURE__) -#define __ARCHITECTURE__ "m68k" -#endif - int version; - version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; - if (version < 4) - printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); - else - printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); - exit (0); -#endif - -#if defined (MULTIMAX) || defined (n16) -#if defined (UMAXV) - printf ("ns32k-encore-sysv\n"); exit (0); -#else -#if defined (CMU) - printf ("ns32k-encore-mach\n"); exit (0); -#else - printf ("ns32k-encore-bsd\n"); exit (0); -#endif -#endif -#endif - -#if defined (__386BSD__) - printf ("i386-pc-bsd\n"); exit (0); -#endif - -#if defined (sequent) -#if defined (i386) - printf ("i386-sequent-dynix\n"); exit (0); -#endif -#if defined (ns32000) - printf ("ns32k-sequent-dynix\n"); exit (0); -#endif -#endif - -#if defined (_SEQUENT_) - struct utsname un; - - uname(&un); - - if (strncmp(un.version, "V2", 2) == 0) { - printf ("i386-sequent-ptx2\n"); exit (0); - } - if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ - printf ("i386-sequent-ptx1\n"); exit (0); - } - printf ("i386-sequent-ptx\n"); exit (0); - -#endif - -#if defined (vax) -# if !defined (ultrix) -# include -# if defined (BSD) -# if BSD == 43 - printf ("vax-dec-bsd4.3\n"); exit (0); -# else -# if BSD == 199006 - printf ("vax-dec-bsd4.3reno\n"); exit (0); -# else - printf ("vax-dec-bsd\n"); exit (0); -# endif -# endif -# else - printf ("vax-dec-bsd\n"); exit (0); -# endif -# else - printf ("vax-dec-ultrix\n"); exit (0); -# endif -#endif - -#if defined (alliant) && defined (i860) - printf ("i860-alliant-bsd\n"); exit (0); -#endif - - exit (1); -} -EOF - -$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && $dummy && rm -f $dummy.c $dummy && rmdir $tmpdir && exit 0 -rm -f $dummy.c $dummy && rmdir $tmpdir - -# Apollos put the system type in the environment. - -test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } - -# Convex versions that predate uname can use getsysinfo(1) - -if [ -x /usr/convex/getsysinfo ] -then - case `getsysinfo -f cpu_type` in - c1*) - echo c1-convex-bsd - exit 0 ;; - c2*) - if getsysinfo -f scalar_acc - then echo c32-convex-bsd - else echo c2-convex-bsd - fi - exit 0 ;; - c34*) - echo c34-convex-bsd - exit 0 ;; - c38*) - echo c38-convex-bsd - exit 0 ;; - c4*) - echo c4-convex-bsd - exit 0 ;; - esac -fi - -cat >&2 < in order to provide the needed -information to handle your system. - -config.guess timestamp = $timestamp - -uname -m = `(uname -m) 2>/dev/null || echo unknown` -uname -r = `(uname -r) 2>/dev/null || echo unknown` -uname -s = `(uname -s) 2>/dev/null || echo unknown` -uname -v = `(uname -v) 2>/dev/null || echo unknown` - -/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` -/bin/uname -X = `(/bin/uname -X) 2>/dev/null` - -hostinfo = `(hostinfo) 2>/dev/null` -/bin/universe = `(/bin/universe) 2>/dev/null` -/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` -/bin/arch = `(/bin/arch) 2>/dev/null` -/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` -/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` - -UNAME_MACHINE = ${UNAME_MACHINE} -UNAME_RELEASE = ${UNAME_RELEASE} -UNAME_SYSTEM = ${UNAME_SYSTEM} -UNAME_VERSION = ${UNAME_VERSION} -EOF - -exit 1 - -# Local variables: -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "timestamp='" -# time-stamp-format: "%:y-%02m-%02d" -# time-stamp-end: "'" -# End: diff --git a/src/nix-instantiate.in b/src/nix-instantiate.in index b5093391b..7551e9d24 100755 --- a/src/nix-instantiate.in +++ b/src/nix-instantiate.in @@ -5,7 +5,7 @@ use FileHandle; use File::Spec; use Digest::MD5; -my $system = "@SYSTEM@"; +my $system = "@host@"; my $outdir = File::Spec->rel2abs($ARGV[0]); my $netdir = File::Spec->rel2abs($ARGV[1]); diff --git a/src/nix-regprebuilts b/src/nix-regprebuilts index af643bf7e..6c9c981ee 100755 --- a/src/nix-regprebuilts +++ b/src/nix-regprebuilts @@ -1,6 +1,6 @@ #! /usr/bin/perl -w -my $dir = $ENV{"NIX"} . "/prebuilts"; +my $dir = $ARGV[0]; foreach my $prebuilt (glob("$dir/*.tar.bz2")) { diff --git a/src/nix.cc b/src/nix.cc index 74c0687d5..eb0706774 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -24,53 +24,25 @@ extern "C" { #include "md5.h" } +#include "util.hh" + using namespace std; -#define PKGINFO_ENVVAR "NIX_DB" -#define PKGINFO_PATH "/pkg/sys/var/pkginfo" - -#define PKGHOME_ENVVAR "NIX_PKGHOME" - - +/* Database names. */ static string dbRefs = "refs"; static string dbInstPkgs = "pkginst"; static string dbPrebuilts = "prebuilts"; -static string prog; -static string dbfile = PKGINFO_PATH; - - -static string pkgHome = "/pkg"; - - +/* The canonical system name, as returned by config.guess. */ static string thisSystem = SYSTEM; -class Error : public exception -{ - string err; -public: - Error(string _err) { err = _err; } - ~Error() throw () { }; - const char * what() const throw () { return err.c_str(); } -}; - -class UsageError : public Error -{ -public: - UsageError(string _err) : Error(_err) { }; -}; - -class BadRefError : public Error -{ -public: - BadRefError(string _err) : Error(_err) { }; -}; - - -typedef vector Strings; +/* The prefix of the Nix installation, and the environment variable + that can be used to override the default. */ +static string nixHomeDir = "/nix"; +static string nixHomeDirEnvVar = "NIX"; /* Wrapper classes that ensures that the database is closed upon @@ -98,7 +70,7 @@ auto_ptr openDB(const string & dbname, bool readonly) db = auto_ptr(new Db2(0, 0)); - db->open(dbfile.c_str(), dbname.c_str(), + db->open((nixHomeDir + "/var/nix/pkginfo.db").c_str(), dbname.c_str(), DB_HASH, readonly ? DB_RDONLY : DB_CREATE, 0666); return db; @@ -336,7 +308,7 @@ void installPkg(string hash) string id = getFromEnv(env, "id"); /* Construct a path for the installed package. */ - path = pkgHome + "/" + id + "-" + hash; + path = nixHomeDir + "/pkg/" + id + "-" + hash; /* Create the path. */ if (mkdir(path.c_str(), 0777)) @@ -767,11 +739,8 @@ void run(Strings::iterator argCur, Strings::iterator argEnd) { umask(0022); - if (getenv(PKGINFO_ENVVAR)) - dbfile = getenv(PKGINFO_ENVVAR); - - if (getenv(PKGHOME_ENVVAR)) - pkgHome = getenv(PKGHOME_ENVVAR); + char * homeDir = getenv(nixHomeDirEnvVar.c_str()); + if (homeDir) nixHomeDir = homeDir; /* Parse the global flags. */ for ( ; argCur != argEnd; argCur++) { @@ -779,8 +748,6 @@ void run(Strings::iterator argCur, Strings::iterator argEnd) if (arg == "-h" || arg == "--help") { printUsage(); return; - } else if (arg == "-d") { - dbfile = optarg; } else if (arg[0] == '-') { throw UsageError("invalid option `" + arg + "'"); } else break; @@ -848,9 +815,9 @@ int main(int argc, char * * argv) while (argc--) args.push_back(*argv++); Strings::iterator argCur = args.begin(), argEnd = args.end(); - prog = *argCur++; + argCur++; - try { + try { try { run(argCur, argEnd); } catch (DbException e) { diff --git a/src/util.hh b/src/util.hh new file mode 100644 index 000000000..8d82c80c1 --- /dev/null +++ b/src/util.hh @@ -0,0 +1,34 @@ +#ifndef __UTIL_H +#define __UTIL_H + +#include + +using namespace std; + + +class Error : public exception +{ + string err; +public: + Error(string _err) { err = _err; } + ~Error() throw () { }; + const char * what() const throw () { return err.c_str(); } +}; + +class UsageError : public Error +{ +public: + UsageError(string _err) : Error(_err) { }; +}; + +class BadRefError : public Error +{ +public: + BadRefError(string _err) : Error(_err) { }; +}; + + +typedef vector Strings; + + +#endif /* !__UTIL_H */ From 1447cf35bd633c365bb7584dedb41327951d0c07 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 6 Apr 2003 22:09:47 +0000 Subject: [PATCH 0031/6440] * Delete source after building. --- test/build/atk-build.sh | 2 ++ test/build/glib-build.sh | 2 ++ test/build/gnet-build.sh | 2 ++ test/build/gtk+-build.sh | 2 ++ test/build/gtkspell-build.sh | 2 ++ test/build/pango-build.sh | 2 ++ test/build/pkgconfig-build.sh | 2 ++ test/build/pspell-build.sh | 2 ++ 8 files changed, 16 insertions(+) diff --git a/test/build/atk-build.sh b/test/build/atk-build.sh index df881cbef..58d190aef 100755 --- a/test/build/atk-build.sh +++ b/test/build/atk-build.sh @@ -10,3 +10,5 @@ cd atk-* ./configure --prefix=$top make make install +cd .. +rm -rf atk-* diff --git a/test/build/glib-build.sh b/test/build/glib-build.sh index 2100052be..92736f701 100755 --- a/test/build/glib-build.sh +++ b/test/build/glib-build.sh @@ -8,3 +8,5 @@ cd glib-* ./configure --prefix=$top make make install +cd .. +rm -rf glib-* diff --git a/test/build/gnet-build.sh b/test/build/gnet-build.sh index ec614b4bf..9752994a1 100755 --- a/test/build/gnet-build.sh +++ b/test/build/gnet-build.sh @@ -10,3 +10,5 @@ cd gnet-* ./configure --prefix=$top make make install +cd .. +rm -rf gnet-* diff --git a/test/build/gtk+-build.sh b/test/build/gtk+-build.sh index d2b3d694a..8451a2d4e 100755 --- a/test/build/gtk+-build.sh +++ b/test/build/gtk+-build.sh @@ -10,3 +10,5 @@ cd gtk+-* ./configure --prefix=$top make make install +cd .. +rm -rf gtk+-* diff --git a/test/build/gtkspell-build.sh b/test/build/gtkspell-build.sh index d1e56943f..009e1133c 100755 --- a/test/build/gtkspell-build.sh +++ b/test/build/gtkspell-build.sh @@ -11,3 +11,5 @@ cd gtkspell-* ./configure --prefix=$top make make install +cd .. +rm -rf gtkspell-* diff --git a/test/build/pango-build.sh b/test/build/pango-build.sh index fd43c274b..4ed76f76a 100755 --- a/test/build/pango-build.sh +++ b/test/build/pango-build.sh @@ -10,3 +10,5 @@ cd pango-* ./configure --prefix=$top make make install +cd .. +rm -rf pango-* diff --git a/test/build/pkgconfig-build.sh b/test/build/pkgconfig-build.sh index 522a05716..ff418056f 100755 --- a/test/build/pkgconfig-build.sh +++ b/test/build/pkgconfig-build.sh @@ -8,3 +8,5 @@ cd pkgconfig-* ./configure --prefix=$top make make install +cd .. +rm -rf pkgconfig-* diff --git a/test/build/pspell-build.sh b/test/build/pspell-build.sh index 588c2f1a0..57fb1dcbd 100755 --- a/test/build/pspell-build.sh +++ b/test/build/pspell-build.sh @@ -8,3 +8,5 @@ cd pspell-* ./configure --prefix=$top make make install +cd .. +rm -rf pspell-* From 2eea8832f0bc5f15979b5f091c3ac5f04593f0ca Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 6 Apr 2003 22:17:47 +0000 Subject: [PATCH 0032/6440] * The latest version of Pan. --- test/tmpl/pan-0.13.93.nix | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 test/tmpl/pan-0.13.93.nix diff --git a/test/tmpl/pan-0.13.93.nix b/test/tmpl/pan-0.13.93.nix new file mode 100644 index 000000000..9cc8da7ee --- /dev/null +++ b/test/tmpl/pan-0.13.93.nix @@ -0,0 +1,14 @@ +id : pan-0.13.93 + +pkgconfig <- ./pkgconfig-0.15.0.nix +glib <- ./glib-2.2.1.nix +atk <- ./atk-1.2.0.nix +pango <- ./pango-1.2.1.nix +gtk <- ./gtk+-2.2.1.nix +gnet <- ./gnet-1.1.8.nix +pspell <- ./pspell-.12.2.nix +gtkspell <- ./gtkspell-2.0.2.nix + +src = url(http://pan.rebelbase.com/download/releases/0.13.93/SOURCE/pan-0.13.93.tar.bz2) + +build = ../build/pan-build-2.sh From 814b256da43ebdad79fe9544a59f6ae680da7cb9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 6 Apr 2003 22:19:26 +0000 Subject: [PATCH 0033/6440] * Better installation: make directories, create database. * Fixed the register script. --- configure.ac | 3 ++- src/Makefile.am | 7 +++++++ test/register | 18 +++++++++--------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/configure.ac b/configure.ac index e488ed78f..22e320a7c 100644 --- a/configure.ac +++ b/configure.ac @@ -10,5 +10,6 @@ AC_CANONICAL_HOST AC_PROG_CC AC_PROG_CXX -AC_CONFIG_FILES(Makefile src/Makefile src/nix-instantiate) +AC_CONFIG_FILES([Makefile src/Makefile]) +AC_CONFIG_FILES([src/nix-instantiate], [chmod +x src/nix-instantiate]) AC_OUTPUT \ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am index 17d801fbf..e1db3c4eb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,3 +3,10 @@ bin_PROGRAMS = nix nix_SOURCES = nix.cc md5.c nix_CXXFLAGS = -DSYSTEM=\"@host@\" nix_LDADD = -ldb_cxx-4 -lATerm + +install-data-local: + $(INSTALL) -d $(localstatedir)/nix + $(INSTALL) -d $(localstatedir)/nix/descriptors + $(INSTALL) -d $(localstatedir)/nix/sources + $(INSTALL) -d $(prefix)/pkg + $(bindir)/nix init diff --git a/test/register b/test/register index e03296495..57fe5b6c0 100755 --- a/test/register +++ b/test/register @@ -1,19 +1,19 @@ #! /bin/sh -mkdir -p $NIX/db -mkdir -p $NIX/pkg -mkdir -p $NIX/descr -mkdir -p $NIX/netcache +if test -z "$NIX"; then NIX=/nix; fi -nix init +echo target $NIX -if ! nix-instantiate $NIX/descr $NIX/netcache tmpl/*.nix; then +if ! nix-instantiate $NIX/var/nix/descriptors $NIX/var/nix/sources tmpl/*.nix; then exit 1; fi -for i in $NIX/netcache/*; do nix regfile $i; done -for i in build/*; do nix regfile $i; done -for i in $NIX/descr/*; do +rm -f build/*~ +cp -p build/* $NIX/var/nix/sources + +for i in $NIX/var/nix/sources/*; do nix regfile $i; done + +for i in $NIX/var/nix/descriptors/*; do md5sum $i nix regfile $i done From 0d2b24cdd103f21861ad42fd6d98e5d1cb252646 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Apr 2003 12:00:51 +0000 Subject: [PATCH 0034/6440] * `Fix' is a high-level descriptor instantiator for Nix. It replaces nix-instantiate. --- configure.ac | 1 - src/Makefile.am | 8 +- src/fix.cc | 295 ++++++++++++++++++++++++++++++ src/nix-instantiate.in | 121 ------------ src/nix.cc | 86 ++------- src/util.hh | 82 +++++++++ test/fixdescriptors/aterm-2.0.fix | 10 + test/register | 19 -- 8 files changed, 405 insertions(+), 217 deletions(-) create mode 100644 src/fix.cc delete mode 100755 src/nix-instantiate.in create mode 100644 test/fixdescriptors/aterm-2.0.fix delete mode 100755 test/register diff --git a/configure.ac b/configure.ac index 22e320a7c..0a0d491a0 100644 --- a/configure.ac +++ b/configure.ac @@ -11,5 +11,4 @@ AC_PROG_CC AC_PROG_CXX AC_CONFIG_FILES([Makefile src/Makefile]) -AC_CONFIG_FILES([src/nix-instantiate], [chmod +x src/nix-instantiate]) AC_OUTPUT \ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am index e1db3c4eb..2113b9620 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,9 +1,13 @@ -bin_PROGRAMS = nix +bin_PROGRAMS = nix fix nix_SOURCES = nix.cc md5.c -nix_CXXFLAGS = -DSYSTEM=\"@host@\" +nix_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall nix_LDADD = -ldb_cxx-4 -lATerm +fix_SOURCES = fix.cc md5.c +fix_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall +fix_LDADD = -lATerm + install-data-local: $(INSTALL) -d $(localstatedir)/nix $(INSTALL) -d $(localstatedir)/nix/descriptors diff --git a/src/fix.cc b/src/fix.cc new file mode 100644 index 000000000..cb1990928 --- /dev/null +++ b/src/fix.cc @@ -0,0 +1,295 @@ +#include +#include + +extern "C" { +#include +} + +#include "util.hh" + + +static string nixDescriptorDir; +static string nixSourcesDir; + + +typedef map DescriptorMap; + + +void registerFile(string filename) +{ + int res = system(("nix regfile " + filename).c_str()); + if (WEXITSTATUS(res) != 0) + throw Error("cannot register " + filename + " with Nix"); +} + + +/* Download object referenced by the given URL into the sources + directory. Return the file name it was downloaded to. */ +string fetchURL(string url) +{ + unsigned int pos = url.rfind('/'); + if (pos == string::npos) throw Error("invalid url"); + string filename(url, pos + 1); + string fullname = nixSourcesDir + "/" + filename; + /* !!! quoting */ + string shellCmd = + "cd " + nixSourcesDir + " && wget --quiet -N \"" + url + "\""; + int res = system(shellCmd.c_str()); + if (WEXITSTATUS(res) != 0) + throw Error("cannot fetch " + url); + return fullname; +} + + +/* Return the directory part of the given path, i.e., everything + before the final `/'. */ +string dirOf(string s) +{ + unsigned int pos = s.rfind('/'); + if (pos == string::npos) throw Error("invalid file name"); + return string(s, 0, pos); +} + + +/* Term evaluation functions. */ + +string evaluateStr(ATerm e) +{ + char * s; + if (ATmatch(e, "", &s)) + return s; + else throw Error("invalid string expression"); +} + + +ATerm evaluateBool(ATerm e) +{ + if (ATmatch(e, "True") || ATmatch(e, "False")) + return e; + else throw Error("invalid boolean expression"); +} + + +string evaluateFile(ATerm e, string dir) +{ + char * s; + ATerm t; + if (ATmatch(e, "", &s)) { + checkHash(s); + return s; + } else if (ATmatch(e, "Url()", &t)) { + string url = evaluateStr(t); + string filename = fetchURL(url); + registerFile(filename); + return hashFile(filename); + } else if (ATmatch(e, "Local()", &t)) { + string filename = absPath(evaluateStr(t), dir); /* !!! */ + string cmd = "cp -p " + filename + " " + nixSourcesDir; + int res = system(cmd.c_str()); + if (WEXITSTATUS(res) != 0) + throw Error("cannot copy " + filename); + return hashFile(filename); + } else throw Error("invalid hash expression"); +} + + +ATerm evaluatePkg(ATerm e, DescriptorMap & done) +{ + char * s; + if (ATmatch(e, "", &s)) { + checkHash(s); + return s; + } else throw Error("invalid hash expression"); +} + + +ATerm evaluate(ATerm e, string dir, DescriptorMap & done) +{ + ATerm t; + if (ATmatch(e, "Str()", &t)) + return ATmake("Str()", evaluateStr(t).c_str()); + else if (ATmatch(e, "Bool()", &t)) + return ATmake("Bool()", evaluateBool(t)); + else if (ATmatch(e, "File()", &t)) + return ATmake("File()", evaluateFile(t, dir).c_str()); + else if (ATmatch(e, "Pkg()", &t)) + return ATmake("Pkg()", evaluatePkg(t, done)); + else throw Error("invalid expression type"); +} + + +typedef map BindingsMap; + + +string getStringFromMap(BindingsMap & bindingsMap, + const string & name) +{ + ATerm e = bindingsMap[name]; + if (!e) throw Error("binding " + name + " is not set"); + char * s; + if (ATmatch(e, "Str()", &s)) + return s; + else + throw Error("binding " + name + " is not a string"); +} + + +/* Instantiate a Fix descriptors into a Nix descriptor, recursively + instantiating referenced descriptors as well. */ +string instantiateDescriptor(string filename, + DescriptorMap & done) +{ + /* Already done? */ + DescriptorMap::iterator isInMap = done.find(filename); + if (isInMap != done.end()) return isInMap->second; + + /* No. */ + string dir = dirOf(filename); + + /* Read the Fix descriptor as an ATerm. */ + ATerm inTerm = ATreadFromNamedFile(filename.c_str()); + if (!inTerm) throw Error("cannot read aterm " + filename); + + ATerm bindings; + if (!ATmatch(inTerm, "Descr()", &bindings)) + throw Error("invalid term in " + filename); + + /* Iterate over the bindings and evaluate them to normal form. */ + BindingsMap bindingsMap; /* the normal forms */ + + char * cname; + ATerm value; + while (ATmatch(bindings, "[Bind(, ), ]", + &cname, &value, &bindings)) + { + string name(cname); + ATerm e = evaluate(value, dir, done); + bindingsMap[name] = e; + } + + /* Construct a descriptor identifier by concatenating the package + and release ids. */ + string pkgId = getStringFromMap(bindingsMap, "pkgId"); + string releaseId = getStringFromMap(bindingsMap, "releaseId"); + string id = pkgId + "-" + releaseId; + bindingsMap["id"] = ATmake("Str()", id.c_str()); + + /* Add a system name. */ + bindingsMap["system"] = ATmake("Str()", thisSystem.c_str()); + + /* Construct the resulting ATerm. Note that iterating over the + map yields the bindings in sorted order, which is exactly the + canonical form for Nix descriptors. */ + ATermList bindingsList = ATempty; + for (BindingsMap::iterator it = bindingsMap.begin(); + it != bindingsMap.end(); it++) + /* !!! O(n^2) */ + bindingsList = ATappend(bindingsList, + ATmake("Bind(, )", it->first.c_str(), it->second)); + ATerm outTerm = ATmake("Descr()", bindingsList); + + /* Write out the resulting ATerm. */ + string tmpFilename = nixDescriptorDir + "/tmp"; + if (!ATwriteToNamedTextFile(outTerm, tmpFilename.c_str())) + throw Error("cannot write aterm to " + tmpFilename); + + string outHash = hashFile(tmpFilename); + string outFilename = nixDescriptorDir + "/" + id + "-" + outHash + ".nix"; + if (rename(tmpFilename.c_str(), outFilename.c_str())) + throw Error("cannot rename " + tmpFilename + " to " + outFilename); + + cout << outFilename << endl; + + /* Register it with Nix. */ + registerFile(outFilename); + + done[filename] = outFilename; + return outFilename; +} + + +/* Instantiate a set of Fix descriptors into Nix descriptors. */ +void instantiateDescriptors(Strings filenames) +{ + DescriptorMap done; + + for (Strings::iterator it = filenames.begin(); + it != filenames.end(); it++) + { + string filename = absPath(*it); + instantiateDescriptor(filename, done); + } +} + + +/* Print help. */ +void printUsage() +{ + cerr << +"Usage: fix ... +"; +} + + +/* Parse the command-line arguments, call the right operation. */ +void run(Strings::iterator argCur, Strings::iterator argEnd) +{ + Strings extraArgs; + enum { cmdUnknown, cmdInstantiate } command = cmdUnknown; + + char * homeDir = getenv(nixHomeDirEnvVar.c_str()); + if (homeDir) nixHomeDir = homeDir; + + nixDescriptorDir = nixHomeDir + "/var/nix/descriptors"; + nixSourcesDir = nixHomeDir + "/var/nix/sources"; + + for ( ; argCur != argEnd; argCur++) { + string arg(*argCur); + if (arg == "-h" || arg == "--help") { + printUsage(); + return; + } if (arg == "--instantiate" || arg == "-i") { + command = cmdInstantiate; + } else if (arg[0] == '-') + throw UsageError("invalid option `" + arg + "'"); + else + extraArgs.push_back(arg); + } + + switch (command) { + + case cmdInstantiate: + instantiateDescriptors(extraArgs); + break; + + default: + throw UsageError("no operation specified"); + } +} + + +int main(int argc, char * * argv) +{ + ATerm bottomOfStack; + ATinit(argc, argv, &bottomOfStack); + + /* Put the arguments in a vector. */ + Strings args; + while (argc--) args.push_back(*argv++); + Strings::iterator argCur = args.begin(), argEnd = args.end(); + + argCur++; + + try { + run(argCur, argEnd); + } catch (UsageError & e) { + cerr << "error: " << e.what() << endl + << "Try `fix -h' for more information.\n"; + return 1; + } catch (exception & e) { + cerr << "error: " << e.what() << endl; + return 1; + } + + return 0; +} diff --git a/src/nix-instantiate.in b/src/nix-instantiate.in deleted file mode 100755 index 7551e9d24..000000000 --- a/src/nix-instantiate.in +++ /dev/null @@ -1,121 +0,0 @@ -#! /usr/bin/perl -w - -use strict; -use FileHandle; -use File::Spec; -use Digest::MD5; - -my $system = "@host@"; - -my $outdir = File::Spec->rel2abs($ARGV[0]); -my $netdir = File::Spec->rel2abs($ARGV[1]); - -my %donetmpls = (); - -sub fetchFile { - my $loc = shift; - - if ($loc =~ /^([+\w\d\.\/-]+)$/) { - return $1; - } elsif ($loc =~ /^url\((.*)\)$/) { - my $url = $1; - $url =~ /\/([^\/]+)$/ || die "invalid url $url"; - my $fn = "$netdir/$1"; - if (! -f $fn) { - print "fetching $url...\n"; - system "cd $netdir; wget --quiet -N $url"; - if ($? != 0) { - unlink($fn); - die; - } - } - return $fn; - } else { - die "invalid file specified $loc"; - } -} - -sub hashFile { - my $file = shift; - open FILE, "< $file" or die "cannot open $file"; - # !!! error checking - my $hash = Digest::MD5->new->addfile(*FILE)->hexdigest; - close FILE; - return $hash; -} - -sub convert { - my $descr = shift; - - if (defined $donetmpls{$descr}) { - return $donetmpls{$descr}; - } - - my ($x, $dir, $fn) = File::Spec->splitpath($descr); - - print "$descr\n"; - - my $IN = new FileHandle; - my $OUT = new FileHandle; - my $tmpfile = "$outdir/$fn-tmp"; - open $IN, "< $descr" or die "cannot open $descr"; - -# Descr([Bind("x", Str("y")), Bind("x", File("1234")), Bind("x", Pkg("1234"))]) -# bindings alphabetisch gesorteerd - - my %bindings; - - while (<$IN>) { - chomp; - s/\s*#.*$//; - next if (/^$/); - - if (/^(\w+)\s*=\s*([^\#\s]*)\s*(\#.*)?$/) { - my ($name, $loc) = ($1, $2); - my $file = fetchFile($loc); - $file = File::Spec->rel2abs($file, $dir); - my $hash = hashFile($file); - $bindings{$name} = "File(\"$hash\")"; - } elsif (/^(\w+)\s*<-\s*([+\w\d\.\/-]+)\s*(\#.*)?$/) { - my $name = $1; - my $file = $2; - $file = File::Spec->rel2abs($file, $dir); - $file = convert($file); - my $hash = hashFile($file); - $bindings{$name} = "Pkg(\"$hash\")"; - } elsif (/^(\w+)\s*:\s*([+\w\d\.\/-]+)\s*(\#.*)?$/) { - my $name = $1; - my $value = $2; - $bindings{$name} = "Str(\"$value\")"; - } else { - die "syntax error: $_"; - } - } - - close $IN; - - $bindings{"system"} = "Str(\"$system\")"; - - open $OUT, "| baffle -wt > $tmpfile" or die "cannot create $tmpfile"; - print $OUT "Descr(["; - my $first = 1; - foreach my $name (sort (keys %bindings)) { - if (!$first) { print $OUT ","; }; - print $OUT "Bind(\"$name\",$bindings{$name})"; - $first = 0; - } - print $OUT "])"; - close $OUT; - - my $hash = hashFile($tmpfile); - - my $outfile = "$outdir/$fn-$hash"; - rename($tmpfile, $outfile) or die "cannot rename $tmpfile to $outfile"; - - $donetmpls{$descr} = $outfile; - return $outfile; -} - -for (my $i = 2; $i < scalar @ARGV; $i++) { - convert(File::Spec->rel2abs($ARGV[$i])); -} diff --git a/src/nix.cc b/src/nix.cc index eb0706774..9e42917a4 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -1,15 +1,11 @@ #include -#include #include -#include -#include #include #include #include #include #include -#include #include #include #include @@ -20,10 +16,6 @@ extern "C" { #include } -extern "C" { -#include "md5.h" -} - #include "util.hh" using namespace std; @@ -35,16 +27,6 @@ static string dbInstPkgs = "pkginst"; static string dbPrebuilts = "prebuilts"; -/* The canonical system name, as returned by config.guess. */ -static string thisSystem = SYSTEM; - - -/* The prefix of the Nix installation, and the environment variable - that can be used to override the default. */ -static string nixHomeDir = "/nix"; -static string nixHomeDirEnvVar = "NIX"; - - /* Wrapper classes that ensures that the database is closed upon object destruction. */ class Db2 : public Db @@ -132,47 +114,6 @@ void enumDB(const string & dbname, DBPairs & contents) } -string printHash(unsigned char * buf) -{ - ostringstream str; - for (int i = 0; i < 16; i++) { - str.fill('0'); - str.width(2); - str << hex << (int) buf[i]; - } - return str.str(); -} - - -/* Verify that a reference is valid (that is, is a MD5 hash code). */ -void checkHash(const string & s) -{ - string err = "invalid reference: " + s; - if (s.length() != 32) - throw BadRefError(err); - for (int i = 0; i < 32; i++) { - char c = s[i]; - if (!((c >= '0' && c <= '9') || - (c >= 'a' && c <= 'f'))) - throw BadRefError(err); - } -} - - -/* Compute the MD5 hash of a file. */ -string hashFile(string filename) -{ - unsigned char hash[16]; - FILE * file = fopen(filename.c_str(), "rb"); - if (!file) - throw BadRefError("file `" + filename + "' does not exist"); - int err = md5_stream(file, hash); - fclose(file); - if (err) throw BadRefError("cannot hash file"); - return printHash(hash); -} - - typedef map Params; @@ -212,7 +153,14 @@ void readPkgDescr(const string & hash, fileImports[name] = arg; } else if (ATmatch(value, "Str()", &arg)) arguments[name] = arg; - else throw Error("invalid binding in " + pkgfile); + else if (ATmatch(value, "Bool(True)")) + arguments[name] = "1"; + else if (ATmatch(value, "Bool(False)")) + arguments[name] = ""; + else { + ATprintf("%t\n", value); + throw Error("invalid binding in " + pkgfile); + } } } @@ -473,12 +421,15 @@ void exportPkgs(string outDir, Strings::iterator firstHash, Strings::iterator lastHash) { + outDir = absPath(outDir); + for (Strings::iterator it = firstHash; it != lastHash; it++) { string hash = *it; string pkgDir = getPkg(hash); string tmpFile = outDir + "/export_tmp"; - int res = system(("cd " + pkgDir + " && tar cvfj " + tmpFile + " .").c_str()); // !!! escaping + string cmd = "cd " + pkgDir + " && tar cvfj " + tmpFile + " ."; + int res = system(cmd.c_str()); // !!! escaping if (WEXITSTATUS(res) != 0) throw Error("cannot tar " + pkgDir); @@ -500,19 +451,6 @@ void regPrebuilt(string pkgHash, string prebuiltHash) } -string absPath(string filename) -{ - if (filename[0] != '/') { - char buf[PATH_MAX]; - if (!getcwd(buf, sizeof(buf))) - throw Error("cannot get cwd"); - filename = string(buf) + "/" + filename; - /* !!! canonicalise */ - } - return filename; -} - - void registerFile(string filename) { filename = absPath(filename); diff --git a/src/util.hh b/src/util.hh index 8d82c80c1..fb405b0f1 100644 --- a/src/util.hh +++ b/src/util.hh @@ -1,7 +1,15 @@ #ifndef __UTIL_H #define __UTIL_H +#include #include +#include + +#include + +extern "C" { +#include "md5.h" +} using namespace std; @@ -31,4 +39,78 @@ public: typedef vector Strings; +/* !!! the following shouldn't be here; abuse of the preprocessor */ + + +/* The canonical system name, as returned by config.guess. */ +static string thisSystem = SYSTEM; + + +/* The prefix of the Nix installation, and the environment variable + that can be used to override the default. */ +static string nixHomeDir = "/nix"; +static string nixHomeDirEnvVar = "NIX"; + + +string absPath(string filename, string dir = "") +{ + if (filename[0] != '/') { + if (dir == "") { + char buf[PATH_MAX]; + if (!getcwd(buf, sizeof(buf))) + throw Error("cannot get cwd"); + dir = buf; + } + filename = dir + "/" + filename; + /* !!! canonicalise */ + char resolved[PATH_MAX]; + if (!realpath(filename.c_str(), resolved)) + throw Error("cannot canonicalise path " + filename); + filename = resolved; + } + return filename; +} + + +string printHash(unsigned char * buf) +{ + ostringstream str; + for (int i = 0; i < 16; i++) { + str.fill('0'); + str.width(2); + str << hex << (int) buf[i]; + } + return str.str(); +} + + +/* Verify that a reference is valid (that is, is a MD5 hash code). */ +void checkHash(const string & s) +{ + string err = "invalid reference: " + s; + if (s.length() != 32) + throw BadRefError(err); + for (int i = 0; i < 32; i++) { + char c = s[i]; + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f'))) + throw BadRefError(err); + } +} + + +/* Compute the MD5 hash of a file. */ +string hashFile(string filename) +{ + unsigned char hash[16]; + FILE * file = fopen(filename.c_str(), "rb"); + if (!file) + throw BadRefError("file `" + filename + "' does not exist"); + int err = md5_stream(file, hash); + fclose(file); + if (err) throw BadRefError("cannot hash file"); + return printHash(hash); +} + + #endif /* !__UTIL_H */ diff --git a/test/fixdescriptors/aterm-2.0.fix b/test/fixdescriptors/aterm-2.0.fix new file mode 100644 index 000000000..0362314d6 --- /dev/null +++ b/test/fixdescriptors/aterm-2.0.fix @@ -0,0 +1,10 @@ +Descr( + [ Bind("pkgId", Str("aterm-2.0")) + , Bind("releaseId", Str("1")) + + , Bind("createGCC", Bool(True)) + + , Bind("src", File(Url("http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz"))) + , Bind("build", File(Local("../build/aterm-build.sh"))) + ] +) \ No newline at end of file diff --git a/test/register b/test/register deleted file mode 100755 index 57fe5b6c0..000000000 --- a/test/register +++ /dev/null @@ -1,19 +0,0 @@ -#! /bin/sh - -if test -z "$NIX"; then NIX=/nix; fi - -echo target $NIX - -if ! nix-instantiate $NIX/var/nix/descriptors $NIX/var/nix/sources tmpl/*.nix; then - exit 1; -fi - -rm -f build/*~ -cp -p build/* $NIX/var/nix/sources - -for i in $NIX/var/nix/sources/*; do nix regfile $i; done - -for i in $NIX/var/nix/descriptors/*; do - md5sum $i - nix regfile $i -done From f7ef88df1fd6087deae02a53b8f18b64050f75db Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Apr 2003 12:13:57 +0000 Subject: [PATCH 0035/6440] * Added some installation instructions to the readme. --- README | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README b/README index e69de29bb..d3d9d603e 100644 --- a/README +++ b/README @@ -0,0 +1,36 @@ +Overview +======== + +Nix is a package manager, deployment system, and component glue +mechanism. + + +Prerequisites +============= + +* Berkeley DB 4.0.14 +* CWI ATerm 2.0 + + +Installation +============ + +* When building from the Subversion repository, first do: + + autoreconf -i + +* To build, do: + + ./configure + make + make install + + Note that this will install to /nix, which is the default prefix. + You can specify another prefix, but this is not recommended if you + want to use prebuilt packages from other sources. + + +Usage +===== + +TODO \ No newline at end of file From cc6eafb3d0a53c8bb02746411d3d41403ce9eb83 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Apr 2003 14:05:15 +0000 Subject: [PATCH 0036/6440] * FreeBSD / gcc 2.95 compatibility fixes. * A script to generate prebuilt registration scripts. --- scripts/nix-generate-regscript | 20 ++++++++++++++++ src/fix.cc | 42 ++++++++++++++++++++++------------ src/nix-regprebuilts | 17 -------------- src/nix.cc | 7 +++--- 4 files changed, 50 insertions(+), 36 deletions(-) create mode 100755 scripts/nix-generate-regscript delete mode 100755 src/nix-regprebuilts diff --git a/scripts/nix-generate-regscript b/scripts/nix-generate-regscript new file mode 100755 index 000000000..bf370f8d7 --- /dev/null +++ b/scripts/nix-generate-regscript @@ -0,0 +1,20 @@ +#! /usr/bin/perl -w + +my $dir = shift @ARGV; +$dir || die "missing directory"; +my $url = shift @ARGV; +$url || die "missing base url"; + +chdir $dir || die "cannot chdir to $dir"; + +foreach my $prebuilt (glob("*.tar.bz2")) { + + $prebuilt =~ /-([a-z0-9]+)-([a-z0-9]+).tar.bz2$/ + || die "invalid file name: $prebuilt"; + + my $pkgHash = $1; + my $prebuiltHash = $2; + + print "regprebuilt $pkgHash $prebuiltHash\n"; + print "regurl $prebuiltHash $url/$prebuilt\n"; +} diff --git a/src/fix.cc b/src/fix.cc index cb1990928..bf335bc36 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -1,6 +1,9 @@ #include #include +#include +#include + extern "C" { #include } @@ -23,13 +26,31 @@ void registerFile(string filename) } +/* Return the directory part of the given path, i.e., everything + before the final `/'. */ +string dirOf(string s) +{ + unsigned int pos = s.rfind('/'); + if (pos == string::npos) throw Error("invalid file name"); + return string(s, 0, pos); +} + + +/* Return the base name of the given path, i.e., everything following + the final `/'. */ +string baseNameOf(string s) +{ + unsigned int pos = s.rfind('/'); + if (pos == string::npos) throw Error("invalid file name"); + return string(s, pos + 1); +} + + /* Download object referenced by the given URL into the sources directory. Return the file name it was downloaded to. */ string fetchURL(string url) { - unsigned int pos = url.rfind('/'); - if (pos == string::npos) throw Error("invalid url"); - string filename(url, pos + 1); + string filename = baseNameOf(url); string fullname = nixSourcesDir + "/" + filename; /* !!! quoting */ string shellCmd = @@ -41,16 +62,6 @@ string fetchURL(string url) } -/* Return the directory part of the given path, i.e., everything - before the final `/'. */ -string dirOf(string s) -{ - unsigned int pos = s.rfind('/'); - if (pos == string::npos) throw Error("invalid file name"); - return string(s, 0, pos); -} - - /* Term evaluation functions. */ string evaluateStr(ATerm e) @@ -88,12 +99,13 @@ string evaluateFile(ATerm e, string dir) int res = system(cmd.c_str()); if (WEXITSTATUS(res) != 0) throw Error("cannot copy " + filename); + registerFile(nixSourcesDir + "/" + baseNameOf(filename)); return hashFile(filename); } else throw Error("invalid hash expression"); } -ATerm evaluatePkg(ATerm e, DescriptorMap & done) +string evaluatePkg(ATerm e, DescriptorMap & done) { char * s; if (ATmatch(e, "", &s)) { @@ -113,7 +125,7 @@ ATerm evaluate(ATerm e, string dir, DescriptorMap & done) else if (ATmatch(e, "File()", &t)) return ATmake("File()", evaluateFile(t, dir).c_str()); else if (ATmatch(e, "Pkg()", &t)) - return ATmake("Pkg()", evaluatePkg(t, done)); + return ATmake("Pkg()", evaluatePkg(t, done).c_str()); else throw Error("invalid expression type"); } diff --git a/src/nix-regprebuilts b/src/nix-regprebuilts deleted file mode 100755 index 6c9c981ee..000000000 --- a/src/nix-regprebuilts +++ /dev/null @@ -1,17 +0,0 @@ -#! /usr/bin/perl -w - -my $dir = $ARGV[0]; - -foreach my $prebuilt (glob("$dir/*.tar.bz2")) { - - $prebuilt =~ /-([a-z0-9]+)-([a-z0-9]+).tar.bz2$/ - || die "invalid file name: $prebuilt"; - - my $pkgHash = $1; - my $prebuiltHash = $2; - - print "$pkgHash -> $prebuiltHash\n"; - - system "nix regprebuilt $pkgHash $prebuiltHash"; - system "nix regfile $prebuilt"; -} diff --git a/src/nix.cc b/src/nix.cc index 9e42917a4..9364baf6c 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -6,11 +6,12 @@ #include #include +#include #include #include #include -#include +#include extern "C" { #include @@ -48,9 +49,7 @@ public: auto_ptr openDB(const string & dbname, bool readonly) { - auto_ptr db; - - db = auto_ptr(new Db2(0, 0)); + auto_ptr db(new Db2(0, 0)); db->open((nixHomeDir + "/var/nix/pkginfo.db").c_str(), dbname.c_str(), DB_HASH, readonly ? DB_RDONLY : DB_CREATE, 0666); From f56b7312b273546871a1eca7d34c60474d3c4050 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Apr 2003 15:36:54 +0000 Subject: [PATCH 0037/6440] * Descriptor importing in Fix. --- configure.ac | 2 +- {src => scripts}/nix-populate | 0 src/fix.cc | 40 +++++++++++++++++------- test/fixdescriptors/aterm-2.0.fix | 2 +- test/fixdescriptors/glib-2.2.1.fix | 10 ++++++ test/fixdescriptors/pkgconfig-0.15.0.fix | 8 +++++ 6 files changed, 48 insertions(+), 14 deletions(-) rename {src => scripts}/nix-populate (100%) create mode 100644 test/fixdescriptors/glib-2.2.1.fix create mode 100644 test/fixdescriptors/pkgconfig-0.15.0.fix diff --git a/configure.ac b/configure.ac index 0a0d491a0..a1dc0f72f 100644 --- a/configure.ac +++ b/configure.ac @@ -11,4 +11,4 @@ AC_PROG_CC AC_PROG_CXX AC_CONFIG_FILES([Makefile src/Makefile]) -AC_OUTPUT \ No newline at end of file +AC_OUTPUT diff --git a/src/nix-populate b/scripts/nix-populate similarity index 100% rename from src/nix-populate rename to scripts/nix-populate diff --git a/src/fix.cc b/src/fix.cc index bf335bc36..8d5cc6bf8 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -2,6 +2,7 @@ #include #include +#include #include extern "C" { @@ -15,9 +16,17 @@ static string nixDescriptorDir; static string nixSourcesDir; +/* Mapping of Fix file names to the hashes of the resulting Nix + descriptors. */ typedef map DescriptorMap; +/* Forward declarations. */ + +string instantiateDescriptor(string filename, + DescriptorMap & done); + + void registerFile(string filename) { int res = system(("nix regfile " + filename).c_str()); @@ -52,12 +61,15 @@ string fetchURL(string url) { string filename = baseNameOf(url); string fullname = nixSourcesDir + "/" + filename; - /* !!! quoting */ - string shellCmd = - "cd " + nixSourcesDir + " && wget --quiet -N \"" + url + "\""; - int res = system(shellCmd.c_str()); - if (WEXITSTATUS(res) != 0) - throw Error("cannot fetch " + url); + struct stat st; + if (stat(fullname.c_str(), &st)) { + /* !!! quoting */ + string shellCmd = + "cd " + nixSourcesDir + " && wget --quiet -N \"" + url + "\""; + int res = system(shellCmd.c_str()); + if (WEXITSTATUS(res) != 0) + throw Error("cannot fetch " + url); + } return fullname; } @@ -101,17 +113,21 @@ string evaluateFile(ATerm e, string dir) throw Error("cannot copy " + filename); registerFile(nixSourcesDir + "/" + baseNameOf(filename)); return hashFile(filename); - } else throw Error("invalid hash expression"); + } else throw Error("invalid file expression"); } -string evaluatePkg(ATerm e, DescriptorMap & done) +string evaluatePkg(ATerm e, string dir, DescriptorMap & done) { char * s; + ATerm t; if (ATmatch(e, "", &s)) { checkHash(s); return s; - } else throw Error("invalid hash expression"); + } else if (ATmatch(e, "Fix()", &t)) { + string filename = absPath(evaluateStr(t), dir); /* !!! */ + return instantiateDescriptor(filename, done); + } else throw Error("invalid pkg expression"); } @@ -125,7 +141,7 @@ ATerm evaluate(ATerm e, string dir, DescriptorMap & done) else if (ATmatch(e, "File()", &t)) return ATmake("File()", evaluateFile(t, dir).c_str()); else if (ATmatch(e, "Pkg()", &t)) - return ATmake("Pkg()", evaluatePkg(t, done).c_str()); + return ATmake("Pkg()", evaluatePkg(t, dir, done).c_str()); else throw Error("invalid expression type"); } @@ -215,8 +231,8 @@ string instantiateDescriptor(string filename, /* Register it with Nix. */ registerFile(outFilename); - done[filename] = outFilename; - return outFilename; + done[filename] = outHash; + return outHash; } diff --git a/test/fixdescriptors/aterm-2.0.fix b/test/fixdescriptors/aterm-2.0.fix index 0362314d6..17e90ca8b 100644 --- a/test/fixdescriptors/aterm-2.0.fix +++ b/test/fixdescriptors/aterm-2.0.fix @@ -7,4 +7,4 @@ Descr( , Bind("src", File(Url("http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz"))) , Bind("build", File(Local("../build/aterm-build.sh"))) ] -) \ No newline at end of file +) diff --git a/test/fixdescriptors/glib-2.2.1.fix b/test/fixdescriptors/glib-2.2.1.fix new file mode 100644 index 000000000..2585c7ffb --- /dev/null +++ b/test/fixdescriptors/glib-2.2.1.fix @@ -0,0 +1,10 @@ +Descr( + [ Bind("pkgId", Str("glib-2.2.1")) + , Bind("releaseId", Str("1")) + + , Bind("pkgconfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) + + , Bind("src", File(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/glib-2.2.1.tar.bz2"))) + , Bind("build", File(Local("../build/glib-build.sh"))) + ] +) diff --git a/test/fixdescriptors/pkgconfig-0.15.0.fix b/test/fixdescriptors/pkgconfig-0.15.0.fix new file mode 100644 index 000000000..27f32aba6 --- /dev/null +++ b/test/fixdescriptors/pkgconfig-0.15.0.fix @@ -0,0 +1,8 @@ +Descr( + [ Bind("pkgId", Str("pkgconfig-0.15.0")) + , Bind("releaseId", Str("1")) + + , Bind("src", File(Url("http://www.freedesktop.org/software/pkgconfig/releases/pkgconfig-0.15.0.tar.gz"))) + , Bind("build", File(Local("../build/pkgconfig-build.sh"))) + ] +) From aa8fda4b54fbd84b7bc6b11904c156367683e8f6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 9 Apr 2003 12:26:48 +0000 Subject: [PATCH 0038/6440] * We no longer use nix-populate standalone, rather we use it as a build action for `system' packages (like system.fix) that have dependencies on all packages we want to activate. So the command sequence to switch to a new activation configuration of the system would be: $ fix -i .../fixdescriptors/system.fix ... system.fix -> 89cf4713b37cc66989304abeb9ea189f $ nix-switch 89cf4713b37cc66989304abeb9ea189f * A nix-profile.sh script that can be included in .bashrc. --- Makefile.am | 2 +- configure.ac | 2 +- scripts/Makefile.am | 5 ++++ scripts/nix-populate | 49 +++++++--------------------------- scripts/nix-profile.sh | 15 +++++++++++ scripts/nix-switch | 42 +++++++++++++++++++++++++++++ src/Makefile.am | 1 + test/fixdescriptors/system.fix | 11 ++++++++ 8 files changed, 85 insertions(+), 42 deletions(-) create mode 100644 scripts/Makefile.am create mode 100644 scripts/nix-profile.sh create mode 100755 scripts/nix-switch create mode 100644 test/fixdescriptors/system.fix diff --git a/Makefile.am b/Makefile.am index af437a64d..83a04399a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1 +1 @@ -SUBDIRS = src +SUBDIRS = src scripts diff --git a/configure.ac b/configure.ac index a1dc0f72f..a25ea785c 100644 --- a/configure.ac +++ b/configure.ac @@ -10,5 +10,5 @@ AC_CANONICAL_HOST AC_PROG_CC AC_PROG_CXX -AC_CONFIG_FILES([Makefile src/Makefile]) +AC_CONFIG_FILES([Makefile src/Makefile scripts/Makefile]) AC_OUTPUT diff --git a/scripts/Makefile.am b/scripts/Makefile.am new file mode 100644 index 000000000..4e2eada86 --- /dev/null +++ b/scripts/Makefile.am @@ -0,0 +1,5 @@ +bin_SCRIPTS = nix-generate-regscript nix-switch + +install-exec-local: + $(INSTALL) -d $(sysconfdir)/profile.d + $(INSTALL_PROGRAM) nix-profile.sh $(sysconfdir)/profile.d/nix.sh diff --git a/scripts/nix-populate b/scripts/nix-populate index 50819d666..d375caa7d 100755 --- a/scripts/nix-populate +++ b/scripts/nix-populate @@ -1,23 +1,16 @@ #! /usr/bin/perl -w use strict; +use Cwd; + +my $selfdir = cwd; -my $pkglist = $ENV{"NIX_ACTIVATIONS"}; -$pkglist or die "NIX_ACTIVATIONS not set"; -my $linkdir = $ENV{"NIX_LINKS"}; -$linkdir or die "NIX_LINKS not set"; my @dirs = ("bin", "sbin", "lib", "include"); -# Figure out a generation number. -my $nr = 1; -while (-e "$linkdir/$nr") { $nr++; } -my $gendir = "$linkdir/$nr"; -print "populating $gendir\n"; - # Create the subdirectories. -mkdir $gendir; +mkdir $selfdir; foreach my $dir (@dirs) { - mkdir "$gendir/$dir"; + mkdir "$selfdir/$dir"; } # For each activated package, create symlinks. @@ -51,39 +44,15 @@ sub createLinks { } } +foreach my $name (keys %ENV) { -open PKGS, "< $pkglist"; + next unless ($name =~ /^act.*$/); -while () { - chomp; - my $hash = $_; - - my $pkgdir = `nix getpkg $hash`; - if ($?) { die "`nix getpkg' failed"; } - chomp $pkgdir; + my $pkgdir = $ENV{$name}; print "merging $pkgdir\n"; foreach my $dir (@dirs) { - createLinks("$pkgdir/$dir", "$gendir/$dir"); + createLinks("$pkgdir/$dir", "$selfdir/$dir"); } } - -close PKGS; - -# Make $gendir the current generation by pointing $linkdir/current to -# it. The rename() system call is supposed to be essentially atomic -# on Unix. That is, if we have links `current -> X' and `new_current -# -> Y', and we rename new_current to current, a process accessing -# current will see X or Y, but never a file-not-found or other error -# condition. This is sufficient to atomically switch the current link -# tree. - -my $current = "$linkdir/current"; - -print "switching $current to $gendir\n"; - -my $tmplink = "$linkdir/new_current"; -symlink($gendir, $tmplink) or die "cannot create $tmplink"; -rename($tmplink, $current) or die "cannot rename $tmplink"; - diff --git a/scripts/nix-profile.sh b/scripts/nix-profile.sh new file mode 100644 index 000000000..977210165 --- /dev/null +++ b/scripts/nix-profile.sh @@ -0,0 +1,15 @@ +if test -z "$NIX_SET"; then + + export NIX_SET=1 + + NIX_LINKS=/nix/var/nix/links/current + + export PATH=$NIX_LINKS/bin:/nix/bin:$PATH + + export LD_LIBRARY_PATH=$NIX_LINKS/lib:$LD_LIBRARY_PATH + + export LIBRARY_PATH=$NIX_LINKS/lib:$LIBRARY_PATH + + export C_INCLUDE_PATH=$NIX_LINKS/include:$C_INCLUDE_PATH + +fi \ No newline at end of file diff --git a/scripts/nix-switch b/scripts/nix-switch new file mode 100755 index 000000000..74bcef856 --- /dev/null +++ b/scripts/nix-switch @@ -0,0 +1,42 @@ +#! /usr/bin/perl -w + +use strict; +my $hash = $ARGV[0]; +$hash || die "no package hash specified"; + +my $prefix = $ENV{"NIX"} || "/nix"; # !!! use prefix +my $linkdir = "$prefix/var/nix/links"; + +# Build the specified package, and all its dependencies. +my $pkgdir = `nix getpkg $hash`; +if ($?) { die "`nix getpkg' failed"; } +chomp $pkgdir; + +my $id = `nix info $hash | cut -c 34-`; +if ($?) { die "`nix info' failed"; } +chomp $id; + +# Figure out a generation number. +my $nr = 0; +while (-e "$linkdir/$id-$nr") { $nr++; } +my $link = "$linkdir/$id-$nr"; +print "$pkgdir\n"; + +# Create a symlink from $link to $pkgdir. +symlink($pkgdir, $link) or die "cannot create $link"; + +# Make $link the current generation by pointing $linkdir/current to +# it. The rename() system call is supposed to be essentially atomic +# on Unix. That is, if we have links `current -> X' and `new_current +# -> Y', and we rename new_current to current, a process accessing +# current will see X or Y, but never a file-not-found or other error +# condition. This is sufficient to atomically switch the current link +# tree. + +my $current = "$linkdir/current"; + +print "switching $current to $link\n"; + +my $tmplink = "$linkdir/new_current"; +symlink($link, $tmplink) or die "cannot create $tmplink"; +rename($tmplink, $current) or die "cannot rename $tmplink"; diff --git a/src/Makefile.am b/src/Makefile.am index 2113b9620..d6dbdcb73 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,5 +12,6 @@ install-data-local: $(INSTALL) -d $(localstatedir)/nix $(INSTALL) -d $(localstatedir)/nix/descriptors $(INSTALL) -d $(localstatedir)/nix/sources + $(INSTALL) -d $(localstatedir)/nix/links $(INSTALL) -d $(prefix)/pkg $(bindir)/nix init diff --git a/test/fixdescriptors/system.fix b/test/fixdescriptors/system.fix new file mode 100644 index 000000000..e864cfac3 --- /dev/null +++ b/test/fixdescriptors/system.fix @@ -0,0 +1,11 @@ +Descr( + [ Bind("pkgId", Str("system")) + , Bind("releaseId", Str("1")) + + , Bind("actATerm", Pkg(Fix("./aterm-2.0.fix"))) + , Bind("actPkgConfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) + , Bind("actGlib", Pkg(Fix("./glib-2.2.1.fix"))) + + , Bind("build", File(Local("../../scripts/nix-populate"))) + ] +) From b762f4df7f6b8d0a4b306fc7e1c2633c4802d1c2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 9 Apr 2003 12:37:49 +0000 Subject: [PATCH 0039/6440] * In `fix --instantiate', only print out the hashes of the Nix descriptors generated out of Fix descriptors specified on the command line. This allows us to say: nix-switch $(fix -i ./test/fixdescriptors/system.fix) --- src/fix.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index 8d5cc6bf8..0d3ae9bff 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -226,8 +226,6 @@ string instantiateDescriptor(string filename, if (rename(tmpFilename.c_str(), outFilename.c_str())) throw Error("cannot rename " + tmpFilename + " to " + outFilename); - cout << outFilename << endl; - /* Register it with Nix. */ registerFile(outFilename); @@ -245,7 +243,7 @@ void instantiateDescriptors(Strings filenames) it != filenames.end(); it++) { string filename = absPath(*it); - instantiateDescriptor(filename, done); + cout << instantiateDescriptor(filename, done) << endl; } } From 30a6122f8061e8c3ac2d96078b75aafa63101f02 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 9 Apr 2003 12:46:44 +0000 Subject: [PATCH 0040/6440] * When we activate a descriptor in nix-switch, remember its hash. This allows us to find out all `live' packages on the system by doing nix closure $(cat /nix/var/nix/links/*.hash) which will print out the activated configurations and all packages referenced by them. We could then garbage collect unused packages by deleting the difference between `nix listinst' and the set returned by `nix closure ...'. --- scripts/nix-switch | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/nix-switch b/scripts/nix-switch index 74bcef856..aec61cbfe 100755 --- a/scripts/nix-switch +++ b/scripts/nix-switch @@ -20,11 +20,17 @@ chomp $id; my $nr = 0; while (-e "$linkdir/$id-$nr") { $nr++; } my $link = "$linkdir/$id-$nr"; -print "$pkgdir\n"; # Create a symlink from $link to $pkgdir. symlink($pkgdir, $link) or die "cannot create $link"; +# Also store the hash of $pkgdir. This is useful for garbage +# collection and the like. +my $hashfile = "$linkdir/$id-$nr.hash"; +open HASH, "> $hashfile" or die "cannot create $hashfile"; +print HASH "$hash\n"; +close HASH; + # Make $link the current generation by pointing $linkdir/current to # it. The rename() system call is supposed to be essentially atomic # on Unix. That is, if we have links `current -> X' and `new_current From f7526febe4e60e3da61664a5fb58ff19a5882ded Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 9 Apr 2003 13:03:00 +0000 Subject: [PATCH 0041/6440] * A garbage collector for installed packages. nix-collect-garbage doesn't actually delete any packages, it just prints their descriptor hashes. So we can do nix info $(nix-collect-garbage) to print out the ids of the packages that would be deleted, and nix delpkg $(nix-collect-garbage) to actually delete them. --- scripts/Makefile.am | 2 +- scripts/nix-collect-garbage | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100755 scripts/nix-collect-garbage diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 4e2eada86..4140cdf5b 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -1,4 +1,4 @@ -bin_SCRIPTS = nix-generate-regscript nix-switch +bin_SCRIPTS = nix-generate-regscript nix-switch nix-collect-garbage install-exec-local: $(INSTALL) -d $(sysconfdir)/profile.d diff --git a/scripts/nix-collect-garbage b/scripts/nix-collect-garbage new file mode 100755 index 000000000..adaba5b7c --- /dev/null +++ b/scripts/nix-collect-garbage @@ -0,0 +1,22 @@ +#! /usr/bin/perl -w + +my $prefix = $ENV{"NIX"} || "/nix"; # !!! use prefix +my $linkdir = "$prefix/var/nix/links"; + +my %alive; + +open HASHES, "nix closure \$(cat $linkdir/*.hash) |"; +while () { + chomp; + $alive{$_} = 1; +} +close HASHES; + +open HASHES, "nix listinst |"; +while () { + chomp; + if (!$alive{$_}) { + print "$_\n"; + } +} +close HASHES; From e59c3246b96492b84c77aebe293ec68d96fe9305 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 23 Apr 2003 07:21:57 +0000 Subject: [PATCH 0042/6440] * Redirect stdout to stderr when executing the build script. --- src/nix.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nix.cc b/src/nix.cc index 9364baf6c..abd2fd778 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -310,6 +310,9 @@ build: env2[i] = (new string(it->first + "=" + it->second))->c_str(); env2[i] = 0; + /* Dup stderr to stdin. */ + dup2(STDERR_FILENO, STDOUT_FILENO); + /* Execute the builder. This should not return. */ execle(builder.c_str(), builder.c_str(), 0, env2); From 6faa154c89220792afbee534e4d7e26b6cba7e90 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 23 Apr 2003 07:23:01 +0000 Subject: [PATCH 0043/6440] * Add "... || exit 1" to every command to catch failure. --- test/build/pkgconfig-build.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/build/pkgconfig-build.sh b/test/build/pkgconfig-build.sh index ff418056f..beceddfb6 100755 --- a/test/build/pkgconfig-build.sh +++ b/test/build/pkgconfig-build.sh @@ -3,10 +3,10 @@ export PATH=/bin:/usr/bin top=`pwd` -tar xvfz $src -cd pkgconfig-* -./configure --prefix=$top -make -make install -cd .. -rm -rf pkgconfig-* +tar xvfz $src || exit 1 +cd pkgconfig-* || exit 1 +./configure --prefix=$top || exit 1 +make || exit 1 +make install || exit 1 +cd .. || exit 1 +rm -rf pkgconfig-* || exit 1 From f546e0cda450642177da02895f39f34e3ce1a6ff Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 23 Apr 2003 09:02:53 +0000 Subject: [PATCH 0044/6440] * Fix descriptor for Subversion 0.21.0. --- test/build/subversion-build.sh | 14 +++++++------- test/fixdescriptors/subversion-0.21.0.fix | 8 ++++++++ test/fixdescriptors/system.fix | 1 + 3 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 test/fixdescriptors/subversion-0.21.0.fix diff --git a/test/build/subversion-build.sh b/test/build/subversion-build.sh index 300c94a2f..cc1eba214 100755 --- a/test/build/subversion-build.sh +++ b/test/build/subversion-build.sh @@ -5,10 +5,10 @@ export PATH=/bin:/usr/bin export LDFLAGS=-s top=`pwd` -tar xvfz $src -cd subversion-* -./configure --prefix=$top --with-ssl -make -make install -cd .. -rm -rf subversion-* +tar xvfz $src || exit 1 +cd subversion-* || exit 1 +./configure --prefix=$top --with-ssl || exit 1 +make || exit 1 +make install || exit 1 +cd $top || exit 1 +rm -rf subversion-* || exit 1 diff --git a/test/fixdescriptors/subversion-0.21.0.fix b/test/fixdescriptors/subversion-0.21.0.fix new file mode 100644 index 000000000..90d9d8d42 --- /dev/null +++ b/test/fixdescriptors/subversion-0.21.0.fix @@ -0,0 +1,8 @@ +Descr( + [ Bind("pkgId", Str("subversion-0.21.0")) + , Bind("releaseId", Str("1")) + + , Bind("src", File(Url("http://subversion.tigris.org/files/documents/15/3712/subversion-0.21.0.tar.gz"))) + , Bind("build", File(Local("../build/subversion-build.sh"))) + ] +) diff --git a/test/fixdescriptors/system.fix b/test/fixdescriptors/system.fix index e864cfac3..62c056007 100644 --- a/test/fixdescriptors/system.fix +++ b/test/fixdescriptors/system.fix @@ -5,6 +5,7 @@ Descr( , Bind("actATerm", Pkg(Fix("./aterm-2.0.fix"))) , Bind("actPkgConfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) , Bind("actGlib", Pkg(Fix("./glib-2.2.1.fix"))) + , Bind("actSubversion", Pkg(Fix("./subversion-0.21.0.fix"))) , Bind("build", File(Local("../../scripts/nix-populate"))) ] From 49e0d743d7348ba15f6c8125138c4e17b271d8c3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 23 Apr 2003 21:21:06 +0000 Subject: [PATCH 0045/6440] * Fix descriptors for Pan and its dependencies. --- test/build/atk-build.sh | 16 ++++++++-------- test/build/gnet-build.sh | 14 +++++++------- test/build/gtk+-build.sh | 14 +++++++------- test/build/gtkspell-build.sh | 14 +++++++------- test/build/pan-build-2.sh | 14 +++++++------- test/build/pango-build.sh | 16 ++++++++-------- test/build/pspell-build.sh | 14 +++++++------- test/fixdescriptors/atk-1.2.0.fix | 11 +++++++++++ test/fixdescriptors/gnet-1.1.8.fix | 11 +++++++++++ test/fixdescriptors/gtk+-2.2.1.fix | 13 +++++++++++++ test/fixdescriptors/gtkspell-2.0.2.fix | 15 +++++++++++++++ test/fixdescriptors/pan-0.13.95.fix | 17 +++++++++++++++++ test/fixdescriptors/pango-1.2.1.fix | 11 +++++++++++ test/fixdescriptors/pspell-.12.2.fix | 8 ++++++++ test/fixdescriptors/system.fix | 9 ++++++++- 15 files changed, 145 insertions(+), 52 deletions(-) create mode 100644 test/fixdescriptors/atk-1.2.0.fix create mode 100644 test/fixdescriptors/gnet-1.1.8.fix create mode 100644 test/fixdescriptors/gtk+-2.2.1.fix create mode 100644 test/fixdescriptors/gtkspell-2.0.2.fix create mode 100644 test/fixdescriptors/pan-0.13.95.fix create mode 100644 test/fixdescriptors/pango-1.2.1.fix create mode 100644 test/fixdescriptors/pspell-.12.2.fix diff --git a/test/build/atk-build.sh b/test/build/atk-build.sh index 58d190aef..632dbe12f 100755 --- a/test/build/atk-build.sh +++ b/test/build/atk-build.sh @@ -4,11 +4,11 @@ export PATH=$pkgconfig/bin:/bin:/usr/bin export PKG_CONFIG_PATH=$glib/lib/pkgconfig export LD_LIBRARY_PATH=$glib/lib -top=`pwd` -tar xvfj $src -cd atk-* -./configure --prefix=$top -make -make install -cd .. -rm -rf atk-* +top=`pwd` || exit 1 +tar xvfj $src || exit 1 +cd atk-* || exit 1 +./configure --prefix=$top || exit 1 +make || exit 1 +make install || exit 1 +cd $top || exit 1 +rm -rf atk-* || exit 1 diff --git a/test/build/gnet-build.sh b/test/build/gnet-build.sh index 9752994a1..72141c268 100755 --- a/test/build/gnet-build.sh +++ b/test/build/gnet-build.sh @@ -5,10 +5,10 @@ export PKG_CONFIG_PATH=$glib/lib/pkgconfig export LD_LIBRARY_PATH=$glib/lib top=`pwd` -tar xvfz $src -cd gnet-* -./configure --prefix=$top -make -make install -cd .. -rm -rf gnet-* +tar xvfz $src || exit 1 +cd gnet-* || exit 1 +./configure --prefix=$top || exit 1 +make || exit 1 +make install || exit 1 +cd $top || exit 1 +rm -rf gnet-* || exit 1 diff --git a/test/build/gtk+-build.sh b/test/build/gtk+-build.sh index 8451a2d4e..3b663fec5 100755 --- a/test/build/gtk+-build.sh +++ b/test/build/gtk+-build.sh @@ -5,10 +5,10 @@ export PKG_CONFIG_PATH=$glib/lib/pkgconfig:$atk/lib/pkgconfig:$pango/lib/pkgconf export LD_LIBRARY_PATH=$glib/lib:$atk/lib:$pango/lib top=`pwd` -tar xvfj $src -cd gtk+-* -./configure --prefix=$top -make -make install -cd .. -rm -rf gtk+-* +tar xvfj $src || exit 1 +cd gtk+-* || exit 1 +./configure --prefix=$top || exit 1 +make || exit 1 +make install || exit 1 +cd $top || exit 1 +rm -rf gtk+-* || exit 1 diff --git a/test/build/gtkspell-build.sh b/test/build/gtkspell-build.sh index 009e1133c..d4267b302 100755 --- a/test/build/gtkspell-build.sh +++ b/test/build/gtkspell-build.sh @@ -6,10 +6,10 @@ export LD_LIBRARY_PATH=$glib/lib:$atk/lib:$pango/lib:$gtk/lib:$pspell/lib export C_INCLUDE_PATH=$pspell/include top=`pwd` -tar xvfz $src -cd gtkspell-* -./configure --prefix=$top -make -make install -cd .. -rm -rf gtkspell-* +tar xvfz $src || exit 1 +cd gtkspell-* || exit 1 +./configure --prefix=$top || exit 1 +make || exit 1 +make install || exit 1 +cd $top || exit 1 +rm -rf gtkspell-* || exit 1 diff --git a/test/build/pan-build-2.sh b/test/build/pan-build-2.sh index 87dd4e6a1..8320cb010 100755 --- a/test/build/pan-build-2.sh +++ b/test/build/pan-build-2.sh @@ -11,10 +11,10 @@ export LIBRARY_PATH=$pspell/lib export LDFLAGS=-s top=`pwd` -tar xvfj $src -cd pan-* -./configure --prefix=$top -make -make install -cd .. -rm -rf pan-* +tar xvfj $src || exit 1 +cd pan-* || exit 1 +./configure --prefix=$top || exit 1 +make || exit 1 +make install || exit 1 +cd $top || exit 1 +rm -rf pan-* || exit 1 diff --git a/test/build/pango-build.sh b/test/build/pango-build.sh index 4ed76f76a..42a275802 100755 --- a/test/build/pango-build.sh +++ b/test/build/pango-build.sh @@ -4,11 +4,11 @@ export PATH=$pkgconfig/bin:/bin:/usr/bin export PKG_CONFIG_PATH=$glib/lib/pkgconfig export LD_LIBRARY_PATH=$glib/lib -top=`pwd` -tar xvfj $src -cd pango-* -./configure --prefix=$top -make -make install -cd .. -rm -rf pango-* +top=`pwd` || exit 1 +tar xvfj $src || exit 1 +cd pango-* || exit 1 +./configure --prefix=$top || exit 1 +make || exit 1 +make install || exit 1 +cd $top || exit 1 +rm -rf pango-* || exit 1 diff --git a/test/build/pspell-build.sh b/test/build/pspell-build.sh index 57fb1dcbd..862bb25e6 100755 --- a/test/build/pspell-build.sh +++ b/test/build/pspell-build.sh @@ -3,10 +3,10 @@ export PATH=/bin:/usr/bin top=`pwd` -tar xvfz $src -cd pspell-* -./configure --prefix=$top -make -make install -cd .. -rm -rf pspell-* +tar xvfz $src || exit 1 +cd pspell-* || exit 1 +./configure --prefix=$top || exit 1 +make || exit 1 +make install || exit 1 +cd $top || exit 1 +rm -rf pspell-* || exit 1 diff --git a/test/fixdescriptors/atk-1.2.0.fix b/test/fixdescriptors/atk-1.2.0.fix new file mode 100644 index 000000000..74e560327 --- /dev/null +++ b/test/fixdescriptors/atk-1.2.0.fix @@ -0,0 +1,11 @@ +Descr( + [ Bind("pkgId", Str("atk-1.2.0")) + , Bind("releaseId", Str("1")) + + , Bind("pkgconfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) + , Bind("glib", Pkg(Fix("./glib-2.2.1.fix"))) + + , Bind("src", File(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/atk-1.2.0.tar.bz2"))) + , Bind("build", File(Local("../build/atk-build.sh"))) + ] +) diff --git a/test/fixdescriptors/gnet-1.1.8.fix b/test/fixdescriptors/gnet-1.1.8.fix new file mode 100644 index 000000000..34d9d10cd --- /dev/null +++ b/test/fixdescriptors/gnet-1.1.8.fix @@ -0,0 +1,11 @@ +Descr( + [ Bind("pkgId", Str("gnet-1.1.8")) + , Bind("releaseId", Str("1")) + + , Bind("pkgconfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) + , Bind("glib", Pkg(Fix("./glib-2.2.1.fix"))) + + , Bind("src", File(Url("http://www.gnetlibrary.org/src/gnet-1.1.8.tar.gz"))) + , Bind("build", File(Local("../build/gnet-build.sh"))) + ] +) diff --git a/test/fixdescriptors/gtk+-2.2.1.fix b/test/fixdescriptors/gtk+-2.2.1.fix new file mode 100644 index 000000000..6793cac45 --- /dev/null +++ b/test/fixdescriptors/gtk+-2.2.1.fix @@ -0,0 +1,13 @@ +Descr( + [ Bind("pkgId", Str("gtk+-2.2.1")) + , Bind("releaseId", Str("1")) + + , Bind("pkgconfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) + , Bind("glib", Pkg(Fix("./glib-2.2.1.fix"))) + , Bind("atk", Pkg(Fix("./atk-1.2.0.fix"))) + , Bind("pango", Pkg(Fix("./pango-1.2.1.fix"))) + + , Bind("src", File(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/gtk+-2.2.1.tar.bz2"))) + , Bind("build", File(Local("../build/gtk+-build.sh"))) + ] +) diff --git a/test/fixdescriptors/gtkspell-2.0.2.fix b/test/fixdescriptors/gtkspell-2.0.2.fix new file mode 100644 index 000000000..e6d614f64 --- /dev/null +++ b/test/fixdescriptors/gtkspell-2.0.2.fix @@ -0,0 +1,15 @@ +Descr( + [ Bind("pkgId", Str("gtkspell-2.0.2")) + , Bind("releaseId", Str("1")) + + , Bind("pkgconfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) + , Bind("glib", Pkg(Fix("./glib-2.2.1.fix"))) + , Bind("atk", Pkg(Fix("./atk-1.2.0.fix"))) + , Bind("pango", Pkg(Fix("./pango-1.2.1.fix"))) + , Bind("gtk", Pkg(Fix("./gtk+-2.2.1.fix"))) + , Bind("pspell", Pkg(Fix("./pspell-.12.2.fix"))) + + , Bind("src", File(Url("http://pan.rebelbase.com/download/extras/gtkspell/SOURCES/gtkspell-2.0.2.tar.gz"))) + , Bind("build", File(Local("../build/gtkspell-build.sh"))) + ] +) diff --git a/test/fixdescriptors/pan-0.13.95.fix b/test/fixdescriptors/pan-0.13.95.fix new file mode 100644 index 000000000..3a32756b6 --- /dev/null +++ b/test/fixdescriptors/pan-0.13.95.fix @@ -0,0 +1,17 @@ +Descr( + [ Bind("pkgId", Str("pan-0.13.95")) + , Bind("releaseId", Str("2")) + + , Bind("pkgconfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) + , Bind("glib", Pkg(Fix("./glib-2.2.1.fix"))) + , Bind("atk", Pkg(Fix("./atk-1.2.0.fix"))) + , Bind("pango", Pkg(Fix("./pango-1.2.1.fix"))) + , Bind("gtk", Pkg(Fix("./gtk+-2.2.1.fix"))) + , Bind("gnet", Pkg(Fix("./gnet-1.1.8.fix"))) + , Bind("pspell", Pkg(Fix("./pspell-.12.2.fix"))) + , Bind("gtkspell", Pkg(Fix("./gtkspell-2.0.2.fix"))) + + , Bind("src", File(Url("http://pan.rebelbase.com/download/releases/0.13.95/SOURCE/pan-0.13.95.tar.bz2"))) + , Bind("build", File(Local("../build/pan-build-2.sh"))) + ] +) diff --git a/test/fixdescriptors/pango-1.2.1.fix b/test/fixdescriptors/pango-1.2.1.fix new file mode 100644 index 000000000..7ee2486a6 --- /dev/null +++ b/test/fixdescriptors/pango-1.2.1.fix @@ -0,0 +1,11 @@ +Descr( + [ Bind("pkgId", Str("pango-1.2.1")) + , Bind("releaseId", Str("1")) + + , Bind("pkgconfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) + , Bind("glib", Pkg(Fix("./glib-2.2.1.fix"))) + + , Bind("src", File(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/pango-1.2.1.tar.bz2"))) + , Bind("build", File(Local("../build/pango-build.sh"))) + ] +) diff --git a/test/fixdescriptors/pspell-.12.2.fix b/test/fixdescriptors/pspell-.12.2.fix new file mode 100644 index 000000000..f19600c25 --- /dev/null +++ b/test/fixdescriptors/pspell-.12.2.fix @@ -0,0 +1,8 @@ +Descr( + [ Bind("pkgId", Str("pspell-.12.2")) + , Bind("releaseId", Str("1")) + + , Bind("src", File(Url("http://unc.dl.sourceforge.net/sourceforge/pspell/pspell-.12.2.tar.gz"))) + , Bind("build", File(Local("../build/pspell-build.sh"))) + ] +) diff --git a/test/fixdescriptors/system.fix b/test/fixdescriptors/system.fix index 62c056007..62d4eae40 100644 --- a/test/fixdescriptors/system.fix +++ b/test/fixdescriptors/system.fix @@ -1,10 +1,17 @@ Descr( [ Bind("pkgId", Str("system")) - , Bind("releaseId", Str("1")) + , Bind("releaseId", Str("2")) , Bind("actATerm", Pkg(Fix("./aterm-2.0.fix"))) , Bind("actPkgConfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) , Bind("actGlib", Pkg(Fix("./glib-2.2.1.fix"))) + , Bind("actAtk", Pkg(Fix("./atk-1.2.0.fix"))) + , Bind("actPango", Pkg(Fix("./pango-1.2.1.fix"))) + , Bind("actGtk", Pkg(Fix("./gtk+-2.2.1.fix"))) + , Bind("actGnet", Pkg(Fix("./gnet-1.1.8.fix"))) + , Bind("actPspell", Pkg(Fix("./pspell-.12.2.fix"))) + , Bind("actGtkspell", Pkg(Fix("./gtkspell-2.0.2.fix"))) + , Bind("actPan", Pkg(Fix("./pan-0.13.95.fix"))) , Bind("actSubversion", Pkg(Fix("./subversion-0.21.0.fix"))) , Bind("build", File(Local("../../scripts/nix-populate"))) From 9713e8577f752ef70c18a9cad62a4b0e88c769de Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 24 Apr 2003 11:41:24 +0000 Subject: [PATCH 0046/6440] * getpkg, delpkg, and so on now accept multiple arguments. --- src/nix.cc | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index abd2fd778..a8d2d7880 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -534,8 +534,7 @@ void listInstalledPkgs() void printInfo(Strings::iterator first, Strings::iterator last) { - for (Strings::iterator it = first; it != last; it++) - { + for (Strings::iterator it = first; it != last; it++) { try { cout << *it << " " << queryPkgId(*it) << endl; } catch (Error & e) { // !!! more specific @@ -632,18 +631,18 @@ Subcommands: verify Remove stale entries from the database. - regfile FILENAME - Register FILENAME keyed by its hash. + regfile FILENAME... + Register each FILENAME keyed by its hash. reginst HASH PATH Register an installed package. - getpkg HASH - Ensure that the package referenced by HASH is installed. Print - out the path of the package on stdout. + getpkg HASH... + For each HASH, ensure that the package referenced by HASH is + installed. Print out the path of the installation on stdout. - delpkg HASH - Uninstall the package referenced by HASH, disregarding any + delpkg HASH... + Uninstall the package referenced by each HASH, disregarding any dependencies that other packages may have on HASH. listinst @@ -652,7 +651,7 @@ Subcommands: run HASH ARGS... Run the descriptor referenced by HASH with the given arguments. - ensure HASH + ensure HASH... Like getpkg, but if HASH refers to a run descriptor, fetch only the dependencies. @@ -707,18 +706,17 @@ void run(Strings::iterator argCur, Strings::iterator argEnd) if (argc != 0) throw argcError; verifyDB(); } else if (cmd == "getpkg") { - if (argc != 1) throw argcError; - string path = getPkg(*argCur); - cout << path << endl; + for (Strings::iterator it = argCur; it != argEnd; it++) { + string path = getPkg(*it); + cout << path << endl; + } } else if (cmd == "delpkg") { - if (argc != 1) throw argcError; - delPkg(*argCur); + for_each(argCur, argEnd, delPkg); } else if (cmd == "run") { if (argc < 1) throw argcError; runPkg(*argCur, argCur + 1, argEnd); } else if (cmd == "ensure") { - if (argc != 1) throw argcError; - ensurePkg(*argCur); + for_each(argCur, argEnd, ensurePkg); } else if (cmd == "export") { if (argc < 1) throw argcError; exportPkgs(*argCur, argCur + 1, argEnd); @@ -726,8 +724,7 @@ void run(Strings::iterator argCur, Strings::iterator argEnd) if (argc != 2) throw argcError; regPrebuilt(*argCur, argCur[1]); } else if (cmd == "regfile") { - if (argc != 1) throw argcError; - registerFile(*argCur); + for_each(argCur, argEnd, registerFile); } else if (cmd == "reginst") { if (argc != 2) throw argcError; registerInstalledPkg(*argCur, argCur[1]); From 24b3d0759e864fdf92fee1085e234535311029ef Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 24 Apr 2003 11:41:50 +0000 Subject: [PATCH 0047/6440] * File removed. --- src/nix-activate | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100755 src/nix-activate diff --git a/src/nix-activate b/src/nix-activate deleted file mode 100755 index 9fe646686..000000000 --- a/src/nix-activate +++ /dev/null @@ -1,22 +0,0 @@ -#! /usr/bin/perl -w - -use strict; - -my $pkglist = "/home/eelco/.nixactivations"; - -if (!-f $pkglist) { - system "touch $pkglist"; -} - -my $hash; -foreach $hash (@ARGV) { - system "grep -q $hash $pkglist"; - if ($?) { - print STDERR "activating $hash\n"; - system "nix getpkg $hash > /dev/null"; - if ($?) { die "`nix getpkg' failed"; } - system "echo $hash >> $pkglist"; - } -} - -system "nix-populate"; From 243370bc52b6ecc706cd7ad3a3c8075f74ac1fc0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 24 Apr 2003 11:43:11 +0000 Subject: [PATCH 0048/6440] * nix-switch now removes the link to the previously activated system package as a root of the garbage collector, unless `--keep' is specified. --- scripts/nix-switch | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/scripts/nix-switch b/scripts/nix-switch index aec61cbfe..8dea38564 100755 --- a/scripts/nix-switch +++ b/scripts/nix-switch @@ -1,6 +1,14 @@ #! /usr/bin/perl -w use strict; + +my $keep = 0; + +if (scalar @ARGV > 0 && $ARGV[0] eq "--keep") { + shift @ARGV; + $keep = 1; +} + my $hash = $ARGV[0]; $hash || die "no package hash specified"; @@ -31,6 +39,12 @@ open HASH, "> $hashfile" or die "cannot create $hashfile"; print HASH "$hash\n"; close HASH; +my $current = "$linkdir/current"; + +# Read the current generation so that we can delete it (if --keep +# wasn't specified). +my $oldlink = readlink($current); + # Make $link the current generation by pointing $linkdir/current to # it. The rename() system call is supposed to be essentially atomic # on Unix. That is, if we have links `current -> X' and `new_current @@ -39,10 +53,14 @@ close HASH; # condition. This is sufficient to atomically switch the current link # tree. -my $current = "$linkdir/current"; - print "switching $current to $link\n"; my $tmplink = "$linkdir/new_current"; symlink($link, $tmplink) or die "cannot create $tmplink"; rename($tmplink, $current) or die "cannot rename $tmplink"; + +if (!$keep && defined $oldlink) { + print "deleting old $oldlink\n"; + unlink $tmplink || print "cannot delete $tmplink"; + unlink "$tmplink.hash" || print "cannot delete $tmplink.hash"; +} From 76205df09cd6ac700f002f22e285440364d96ccd Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 24 Apr 2003 11:45:23 +0000 Subject: [PATCH 0049/6440] * Removed old Nix descriptors. --- test/tmpl/aterm-2.0.nix | 7 ------- test/tmpl/atk-1.2.0.nix | 8 -------- test/tmpl/glib-2.2.1.nix | 7 ------- test/tmpl/gnet-1.1.8.nix | 8 -------- test/tmpl/gtk+-2.2.1.nix | 10 ---------- test/tmpl/gtkspell-2.0.2.nix | 12 ------------ test/tmpl/pan-0.13.4.nix | 12 ------------ test/tmpl/pan-0.13.91-run.nix | 13 ------------- test/tmpl/pan-0.13.91.nix | 14 -------------- test/tmpl/pan-0.13.93.nix | 14 -------------- test/tmpl/pango-1.2.1.nix | 8 -------- test/tmpl/pkgconfig-0.15.0.nix | 5 ----- test/tmpl/pspell-.12.2.nix | 5 ----- test/tmpl/subversion-0.20.1.nix | 5 ----- 14 files changed, 128 deletions(-) delete mode 100644 test/tmpl/aterm-2.0.nix delete mode 100644 test/tmpl/atk-1.2.0.nix delete mode 100644 test/tmpl/glib-2.2.1.nix delete mode 100644 test/tmpl/gnet-1.1.8.nix delete mode 100644 test/tmpl/gtk+-2.2.1.nix delete mode 100644 test/tmpl/gtkspell-2.0.2.nix delete mode 100644 test/tmpl/pan-0.13.4.nix delete mode 100644 test/tmpl/pan-0.13.91-run.nix delete mode 100644 test/tmpl/pan-0.13.91.nix delete mode 100644 test/tmpl/pan-0.13.93.nix delete mode 100644 test/tmpl/pango-1.2.1.nix delete mode 100644 test/tmpl/pkgconfig-0.15.0.nix delete mode 100644 test/tmpl/pspell-.12.2.nix delete mode 100644 test/tmpl/subversion-0.20.1.nix diff --git a/test/tmpl/aterm-2.0.nix b/test/tmpl/aterm-2.0.nix deleted file mode 100644 index 20832dfcd..000000000 --- a/test/tmpl/aterm-2.0.nix +++ /dev/null @@ -1,7 +0,0 @@ -id : aterm-2.0 - -# Original sources. -src = url(http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz) - -# Build script. -build = ../build/aterm-build.sh diff --git a/test/tmpl/atk-1.2.0.nix b/test/tmpl/atk-1.2.0.nix deleted file mode 100644 index 46a6cf2c7..000000000 --- a/test/tmpl/atk-1.2.0.nix +++ /dev/null @@ -1,8 +0,0 @@ -id : atk-1.2.0 - -pkgconfig <- ./pkgconfig-0.15.0.nix -glib <- ./glib-2.2.1.nix - -src = url(ftp://ftp.gtk.org/pub/gtk/v2.2/atk-1.2.0.tar.bz2) - -build = ../build/atk-build.sh diff --git a/test/tmpl/glib-2.2.1.nix b/test/tmpl/glib-2.2.1.nix deleted file mode 100644 index 4ba80c65e..000000000 --- a/test/tmpl/glib-2.2.1.nix +++ /dev/null @@ -1,7 +0,0 @@ -id : glib-2.2.1 - -pkgconfig <- ./pkgconfig-0.15.0.nix - -src = url(ftp://ftp.gtk.org/pub/gtk/v2.2/glib-2.2.1.tar.bz2) - -build = ../build/glib-build.sh diff --git a/test/tmpl/gnet-1.1.8.nix b/test/tmpl/gnet-1.1.8.nix deleted file mode 100644 index 2d602a456..000000000 --- a/test/tmpl/gnet-1.1.8.nix +++ /dev/null @@ -1,8 +0,0 @@ -id : gnet-1.1.8 - -pkgconfig <- ./pkgconfig-0.15.0.nix -glib <- ./glib-2.2.1.nix - -src = url(http://www.gnetlibrary.org/src/gnet-1.1.8.tar.gz) - -build = ../build/gnet-build.sh diff --git a/test/tmpl/gtk+-2.2.1.nix b/test/tmpl/gtk+-2.2.1.nix deleted file mode 100644 index cf34fd768..000000000 --- a/test/tmpl/gtk+-2.2.1.nix +++ /dev/null @@ -1,10 +0,0 @@ -id : gtk+-2.2.1 - -pkgconfig <- ./pkgconfig-0.15.0.nix -glib <- ./glib-2.2.1.nix -atk <- ./atk-1.2.0.nix -pango <- ./pango-1.2.1.nix - -src = url(ftp://ftp.gtk.org/pub/gtk/v2.2/gtk+-2.2.1.tar.bz2) - -build = ../build/gtk+-build.sh diff --git a/test/tmpl/gtkspell-2.0.2.nix b/test/tmpl/gtkspell-2.0.2.nix deleted file mode 100644 index c61539def..000000000 --- a/test/tmpl/gtkspell-2.0.2.nix +++ /dev/null @@ -1,12 +0,0 @@ -id : gtkspell-2.0.2 - -pkgconfig <- ./pkgconfig-0.15.0.nix -glib <- ./glib-2.2.1.nix -atk <- ./atk-1.2.0.nix -pango <- ./pango-1.2.1.nix -gtk <- ./gtk+-2.2.1.nix -pspell <- ./pspell-.12.2.nix - -src = url(http://pan.rebelbase.com/download/extras/gtkspell/SOURCES/gtkspell-2.0.2.tar.gz) - -build = ../build/gtkspell-build.sh diff --git a/test/tmpl/pan-0.13.4.nix b/test/tmpl/pan-0.13.4.nix deleted file mode 100644 index e8bd16e26..000000000 --- a/test/tmpl/pan-0.13.4.nix +++ /dev/null @@ -1,12 +0,0 @@ -id : pan-0.13.4 - -pkgconfig <- ./pkgconfig-0.15.0.nix -glib <- ./glib-2.2.1.nix -atk <- ./atk-1.2.0.nix -pango <- ./pango-1.2.1.nix -gtk <- ./gtk+-2.2.1.nix -gnet <- ./gnet-1.1.8.nix - -src = url(http://pan.rebelbase.com/download/releases/0.13.4/SOURCE/pan-0.13.4.tar.bz2) - -build = ../build/pan-build.sh diff --git a/test/tmpl/pan-0.13.91-run.nix b/test/tmpl/pan-0.13.91-run.nix deleted file mode 100644 index f9c13e64d..000000000 --- a/test/tmpl/pan-0.13.91-run.nix +++ /dev/null @@ -1,13 +0,0 @@ -id : pan-0.13.91-run-2 - -pan <- ./pan-0.13.91.nix - -glib <- ./glib-2.2.1.nix -atk <- ./atk-1.2.0.nix -pango <- ./pango-1.2.1.nix -gtk <- ./gtk+-2.2.1.nix -gnet <- ./gnet-1.1.8.nix -pspell <- ./pspell-.12.2.nix -gtkspell <- ./gtkspell-2.0.2.nix - -run = ../build/pan-run.sh diff --git a/test/tmpl/pan-0.13.91.nix b/test/tmpl/pan-0.13.91.nix deleted file mode 100644 index 13287f99d..000000000 --- a/test/tmpl/pan-0.13.91.nix +++ /dev/null @@ -1,14 +0,0 @@ -id : pan-0.13.91 - -pkgconfig <- ./pkgconfig-0.15.0.nix -glib <- ./glib-2.2.1.nix -atk <- ./atk-1.2.0.nix -pango <- ./pango-1.2.1.nix -gtk <- ./gtk+-2.2.1.nix -gnet <- ./gnet-1.1.8.nix -pspell <- ./pspell-.12.2.nix -gtkspell <- ./gtkspell-2.0.2.nix - -src = url(http://pan.rebelbase.com/download/releases/0.13.91/SOURCE/pan-0.13.91.tar.bz2) - -build = ../build/pan-build-2.sh diff --git a/test/tmpl/pan-0.13.93.nix b/test/tmpl/pan-0.13.93.nix deleted file mode 100644 index 9cc8da7ee..000000000 --- a/test/tmpl/pan-0.13.93.nix +++ /dev/null @@ -1,14 +0,0 @@ -id : pan-0.13.93 - -pkgconfig <- ./pkgconfig-0.15.0.nix -glib <- ./glib-2.2.1.nix -atk <- ./atk-1.2.0.nix -pango <- ./pango-1.2.1.nix -gtk <- ./gtk+-2.2.1.nix -gnet <- ./gnet-1.1.8.nix -pspell <- ./pspell-.12.2.nix -gtkspell <- ./gtkspell-2.0.2.nix - -src = url(http://pan.rebelbase.com/download/releases/0.13.93/SOURCE/pan-0.13.93.tar.bz2) - -build = ../build/pan-build-2.sh diff --git a/test/tmpl/pango-1.2.1.nix b/test/tmpl/pango-1.2.1.nix deleted file mode 100644 index cf9eda70f..000000000 --- a/test/tmpl/pango-1.2.1.nix +++ /dev/null @@ -1,8 +0,0 @@ -id : pango-1.2.1 - -pkgconfig <- ./pkgconfig-0.15.0.nix -glib <- ./glib-2.2.1.nix - -src = url(ftp://ftp.gtk.org/pub/gtk/v2.2/pango-1.2.1.tar.bz2) - -build = ../build/pango-build.sh diff --git a/test/tmpl/pkgconfig-0.15.0.nix b/test/tmpl/pkgconfig-0.15.0.nix deleted file mode 100644 index 7a5b80d39..000000000 --- a/test/tmpl/pkgconfig-0.15.0.nix +++ /dev/null @@ -1,5 +0,0 @@ -id : pkgconfig-0.15.0 - -src = url(http://www.freedesktop.org/software/pkgconfig/releases/pkgconfig-0.15.0.tar.gz) - -build = ../build/pkgconfig-build.sh diff --git a/test/tmpl/pspell-.12.2.nix b/test/tmpl/pspell-.12.2.nix deleted file mode 100644 index c0d44302d..000000000 --- a/test/tmpl/pspell-.12.2.nix +++ /dev/null @@ -1,5 +0,0 @@ -id : pspell-.12.2 - -src = url(http://unc.dl.sourceforge.net/sourceforge/pspell/pspell-.12.2.tar.gz) - -build = ../build/pspell-build.sh diff --git a/test/tmpl/subversion-0.20.1.nix b/test/tmpl/subversion-0.20.1.nix deleted file mode 100644 index 37a5d7ea3..000000000 --- a/test/tmpl/subversion-0.20.1.nix +++ /dev/null @@ -1,5 +0,0 @@ -id : subversion-0.20.1 - -src = url(http://subversion.tigris.org/files/documents/15/3440/subversion-0.20.1.tar.gz) - -build = ../build/subversion-build.sh From fcc5ae151bb78006e7acc5ab8bf6b54692281777 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 25 Apr 2003 15:01:15 +0000 Subject: [PATCH 0050/6440] * Remove build directory from a package directory after building it. --- src/nix.cc | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index a8d2d7880..973c36727 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -332,6 +332,11 @@ build: if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) throw Error("unable to build package"); + + /* Remove write permission from the build directory. */ + int res = system(("chmod -R -w " + path).c_str()); // !!! escaping + if (WEXITSTATUS(res) != 0) + throw Error("cannot remove write permission from " + path); } catch (exception &) { system(("rm -rf " + path).c_str()); @@ -411,10 +416,10 @@ void delPkg(string hash) string path; checkHash(hash); if (queryDB(dbInstPkgs, hash, path)) { - int res = system(("rm -rf " + path).c_str()); // !!! escaping - delDB(dbInstPkgs, hash); // not a bug + int res = system(("chmod -R +w " + path + " && rm -rf " + path).c_str()); // !!! escaping + delDB(dbInstPkgs, hash); // not a bug ??? if (WEXITSTATUS(res) != 0) - throw Error("cannot delete " + path); + cerr << "errors deleting " + path + ", ignoring" << endl; } } From d6d930a975cf0bfacb8a3117752452b89921b6ee Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 25 Apr 2003 15:20:05 +0000 Subject: [PATCH 0051/6440] * Bug fix: deleting the old links didn't work properly. --- scripts/nix-switch | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/nix-switch b/scripts/nix-switch index 8dea38564..570f7e9e9 100755 --- a/scripts/nix-switch +++ b/scripts/nix-switch @@ -61,6 +61,6 @@ rename($tmplink, $current) or die "cannot rename $tmplink"; if (!$keep && defined $oldlink) { print "deleting old $oldlink\n"; - unlink $tmplink || print "cannot delete $tmplink"; - unlink "$tmplink.hash" || print "cannot delete $tmplink.hash"; + unlink($oldlink) == 1 || print "cannot delete $oldlink\n"; + unlink("$oldlink.hash") == 1 || print "cannot delete $oldlink.hash\n"; } From 0ef4b6d0f8dcaec093e3db366b6dfb6ba47f73a6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 25 Apr 2003 15:33:50 +0000 Subject: [PATCH 0052/6440] * Cleaned up the semantics of Fix expressions. * Conditionals and variables in Fix expressions. This allows, e.g., Descr( [ Bind("pkgId", "subversion-0.21.0") , Bind("httpsClient", Bool(True)) , Bind("httpServer", Bool(True)) , Bind("ssl", If(Var("httpsClient"), Fix("./openssl-0.9.7b.fix"), "")) , Bind("httpd", If(Var("httpServer"), Fix("./httpd-2.0.45.fix"), "")) ... ]) which introduces domain feature variables httpsClient and httpServer (i.e., whether Subversion is built with https client and webdav server support); the values of the variables influences package dependencies and the build scripts. The next step is to allow that packages can express constraints on each other. E.g., StrategoXT is dependent on an ATerm library with the "gcc" variant enabled. In fact, this may cause several Nix instantiations to be created from a single Fix descriptor. If possible, Fix should try to find the least set of instantiations that obeys the constraints. --- src/fix.cc | 224 ++++++++++++++-------- test/build/httpd-build.sh | 12 ++ test/build/openssl-build.sh | 12 ++ test/build/subversion-build.sh | 16 +- test/fixdescriptors/aterm-2.0.fix | 10 +- test/fixdescriptors/atk-1.2.0.fix | 12 +- test/fixdescriptors/glib-2.2.1.fix | 10 +- test/fixdescriptors/gnet-1.1.8.fix | 12 +- test/fixdescriptors/gtk+-2.2.1.fix | 16 +- test/fixdescriptors/gtkspell-2.0.2.fix | 20 +- test/fixdescriptors/httpd-2.0.45.fix | 10 + test/fixdescriptors/openssl-0.9.7b.fix | 8 + test/fixdescriptors/pan-0.13.95.fix | 24 +-- test/fixdescriptors/pango-1.2.1.fix | 12 +- test/fixdescriptors/pkgconfig-0.15.0.fix | 8 +- test/fixdescriptors/pspell-.12.2.fix | 8 +- test/fixdescriptors/subversion-0.21.0.fix | 16 +- test/fixdescriptors/system.fix | 24 +-- 18 files changed, 290 insertions(+), 164 deletions(-) create mode 100755 test/build/httpd-build.sh create mode 100755 test/build/openssl-build.sh create mode 100644 test/fixdescriptors/httpd-2.0.45.fix create mode 100644 test/fixdescriptors/openssl-0.9.7b.fix diff --git a/src/fix.cc b/src/fix.cc index 0d3ae9bff..052c1d4c9 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -16,6 +16,9 @@ static string nixDescriptorDir; static string nixSourcesDir; +static bool verbose = false; + + /* Mapping of Fix file names to the hashes of the resulting Nix descriptors. */ typedef map DescriptorMap; @@ -23,10 +26,6 @@ typedef map DescriptorMap; /* Forward declarations. */ -string instantiateDescriptor(string filename, - DescriptorMap & done); - - void registerFile(string filename) { int res = system(("nix regfile " + filename).c_str()); @@ -74,80 +73,137 @@ string fetchURL(string url) } -/* Term evaluation functions. */ - -string evaluateStr(ATerm e) +Error badTerm(const string & msg, ATerm e) { - char * s; - if (ATmatch(e, "", &s)) - return s; - else throw Error("invalid string expression"); + char * s = ATwriteToString(e); + return Error(msg + ", in `" + s + "'"); } -ATerm evaluateBool(ATerm e) -{ - if (ATmatch(e, "True") || ATmatch(e, "False")) - return e; - else throw Error("invalid boolean expression"); -} - - -string evaluateFile(ATerm e, string dir) -{ - char * s; - ATerm t; - if (ATmatch(e, "", &s)) { - checkHash(s); - return s; - } else if (ATmatch(e, "Url()", &t)) { - string url = evaluateStr(t); - string filename = fetchURL(url); - registerFile(filename); - return hashFile(filename); - } else if (ATmatch(e, "Local()", &t)) { - string filename = absPath(evaluateStr(t), dir); /* !!! */ - string cmd = "cp -p " + filename + " " + nixSourcesDir; - int res = system(cmd.c_str()); - if (WEXITSTATUS(res) != 0) - throw Error("cannot copy " + filename); - registerFile(nixSourcesDir + "/" + baseNameOf(filename)); - return hashFile(filename); - } else throw Error("invalid file expression"); -} - - -string evaluatePkg(ATerm e, string dir, DescriptorMap & done) -{ - char * s; - ATerm t; - if (ATmatch(e, "", &s)) { - checkHash(s); - return s; - } else if (ATmatch(e, "Fix()", &t)) { - string filename = absPath(evaluateStr(t), dir); /* !!! */ - return instantiateDescriptor(filename, done); - } else throw Error("invalid pkg expression"); -} - - -ATerm evaluate(ATerm e, string dir, DescriptorMap & done) -{ - ATerm t; - if (ATmatch(e, "Str()", &t)) - return ATmake("Str()", evaluateStr(t).c_str()); - else if (ATmatch(e, "Bool()", &t)) - return ATmake("Bool()", evaluateBool(t)); - else if (ATmatch(e, "File()", &t)) - return ATmake("File()", evaluateFile(t, dir).c_str()); - else if (ATmatch(e, "Pkg()", &t)) - return ATmake("Pkg()", evaluatePkg(t, dir, done).c_str()); - else throw Error("invalid expression type"); -} - +/* Term evaluation. */ typedef map BindingsMap; +struct EvalContext +{ + string dir; + DescriptorMap * done; + BindingsMap * vars; +}; + + +ATerm evaluate(ATerm e, EvalContext ctx); +string instantiateDescriptor(string filename, EvalContext ctx); + + +string evaluateStr(ATerm e, EvalContext ctx) +{ + e = evaluate(e, ctx); + char * s; + if (ATmatch(e, "Str()", &s)) + return s; + else throw badTerm("string value expected", e); +} + + +bool evaluateBool(ATerm e, EvalContext ctx) +{ + e = evaluate(e, ctx); + if (ATmatch(e, "Bool(True)")) + return true; + else if (ATmatch(e, "Bool(False)")) + return false; + else throw badTerm("boolean value expected", e); +} + + +ATerm evaluate(ATerm e, EvalContext ctx) +{ + char * s; + ATerm e2; + ATerm eCond, eTrue, eFalse; + + /* Check for normal forms first. */ + + if (ATmatch(e, "Str()", &s) || + ATmatch(e, "Bool(True)") || ATmatch(e, "Bool(False)")) + return e; + + else if ( + ATmatch(e, "Pkg()", &s) || + ATmatch(e, "File()", &s)) + { + checkHash(s); + return e; + } + + /* Short-hands. */ + + else if (ATmatch(e, "", &s)) + return ATmake("Str()", s); + + else if (ATmatch(e, "True", &s)) + return ATmake("Bool(True)", s); + + else if (ATmatch(e, "False", &s)) + return ATmake("Bool(False)", s); + + /* Functions. */ + + /* `Var' looks up a variable. */ + else if (ATmatch(e, "Var()", &s)) { + string name(s); + ATerm e2 = (*ctx.vars)[name]; + if (!e2) throw Error("undefined variable " + name); + return evaluate(e2, ctx); /* !!! update binding */ + } + + /* `Fix' recursively instantiates a Fix descriptor, returning the + hash of the generated Nix descriptor. */ + else if (ATmatch(e, "Fix()", &e2)) { + string filename = absPath(evaluateStr(e2, ctx), ctx.dir); /* !!! */ + return ATmake("Pkg()", + instantiateDescriptor(filename, ctx).c_str()); + } + + /* `Source' copies the specified file to nixSourcesDir, registers + it with Nix, and returns the hash of the file. */ + else if (ATmatch(e, "Source()", &e2)) { + string source = absPath(evaluateStr(e2, ctx), ctx.dir); /* !!! */ + string target = nixSourcesDir + "/" + baseNameOf(source); + + // Don't copy if filename is already in nixSourcesDir. + if (source != target) { + if (verbose) + cerr << "copying source " << source << endl; + string cmd = "cp -p " + source + " " + target; + int res = system(cmd.c_str()); + if (WEXITSTATUS(res) != 0) + throw Error("cannot copy " + source + " to " + target); + } + + registerFile(target); + return ATmake("File()", hashFile(target).c_str()); + } + + /* `Url' fetches a file from the network, caching it in + nixSourcesDir and returning the file name. */ + else if (ATmatch(e, "Url()", &e2)) { + string url = evaluateStr(e2, ctx); + if (verbose) + cerr << "fetching " << url << endl; + string filename = fetchURL(url); + return ATmake("Str()", filename.c_str()); + } + + /* `If' provides conditional evaluation. */ + else if (ATmatch(e, "If(, , )", + &eCond, &eTrue, &eFalse)) + return evaluate(evaluateBool(eCond, ctx) ? eTrue : eFalse, ctx); + + else throw badTerm("invalid expression", e); +} + string getStringFromMap(BindingsMap & bindingsMap, const string & name) @@ -164,15 +220,14 @@ string getStringFromMap(BindingsMap & bindingsMap, /* Instantiate a Fix descriptors into a Nix descriptor, recursively instantiating referenced descriptors as well. */ -string instantiateDescriptor(string filename, - DescriptorMap & done) +string instantiateDescriptor(string filename, EvalContext ctx) { /* Already done? */ - DescriptorMap::iterator isInMap = done.find(filename); - if (isInMap != done.end()) return isInMap->second; + DescriptorMap::iterator isInMap = ctx.done->find(filename); + if (isInMap != ctx.done->end()) return isInMap->second; /* No. */ - string dir = dirOf(filename); + ctx.dir = dirOf(filename); /* Read the Fix descriptor as an ATerm. */ ATerm inTerm = ATreadFromNamedFile(filename.c_str()); @@ -184,6 +239,7 @@ string instantiateDescriptor(string filename, /* Iterate over the bindings and evaluate them to normal form. */ BindingsMap bindingsMap; /* the normal forms */ + ctx.vars = &bindingsMap; char * cname; ATerm value; @@ -191,7 +247,7 @@ string instantiateDescriptor(string filename, &cname, &value, &bindings)) { string name(cname); - ATerm e = evaluate(value, dir, done); + ATerm e = evaluate(value, ctx); bindingsMap[name] = e; } @@ -229,7 +285,10 @@ string instantiateDescriptor(string filename, /* Register it with Nix. */ registerFile(outFilename); - done[filename] = outHash; + if (verbose) + cerr << "instantiated " << outHash << " from " << filename << endl; + + (*ctx.done)[filename] = outHash; return outHash; } @@ -239,11 +298,14 @@ void instantiateDescriptors(Strings filenames) { DescriptorMap done; + EvalContext ctx; + ctx.done = &done; + for (Strings::iterator it = filenames.begin(); it != filenames.end(); it++) { string filename = absPath(*it); - cout << instantiateDescriptor(filename, done) << endl; + cout << instantiateDescriptor(filename, ctx) << endl; } } @@ -274,7 +336,9 @@ void run(Strings::iterator argCur, Strings::iterator argEnd) if (arg == "-h" || arg == "--help") { printUsage(); return; - } if (arg == "--instantiate" || arg == "-i") { + } else if (arg == "-v" || arg == "--verbose") { + verbose = true; + } else if (arg == "--instantiate" || arg == "-i") { command = cmdInstantiate; } else if (arg[0] == '-') throw UsageError("invalid option `" + arg + "'"); diff --git a/test/build/httpd-build.sh b/test/build/httpd-build.sh new file mode 100755 index 000000000..a5b43d744 --- /dev/null +++ b/test/build/httpd-build.sh @@ -0,0 +1,12 @@ +#! /bin/sh + +export PATH=/bin:/usr/bin + +top=`pwd` +tar xvfz $src || exit 1 +cd httpd-* || exit 1 +./configure --prefix=$top --enable-ssl --with-ssl=$ssl --enable-mods-shared=all || exit 1 +make || exit 1 +make install || exit 1 +cd $top || exit 1 +rm -rf httpd-* || exit 1 diff --git a/test/build/openssl-build.sh b/test/build/openssl-build.sh new file mode 100755 index 000000000..23437a37b --- /dev/null +++ b/test/build/openssl-build.sh @@ -0,0 +1,12 @@ +#! /bin/sh + +export PATH=/bin:/usr/bin + +top=`pwd` +tar xvfz $src || exit 1 +cd openssl-* || exit 1 +./config --prefix=$top || exit 1 +make || exit 1 +make install || exit 1 +cd $top || exit 1 +rm -rf openssl-* || exit 1 diff --git a/test/build/subversion-build.sh b/test/build/subversion-build.sh index cc1eba214..3d1922c98 100755 --- a/test/build/subversion-build.sh +++ b/test/build/subversion-build.sh @@ -5,10 +5,22 @@ export PATH=/bin:/usr/bin export LDFLAGS=-s top=`pwd` + +if test $httpsClient; then + extraflags="--with-ssl --with-libs=$ssl $extraflags" +fi + +if test $httpServer; then + extraflags="--with-apxs=$httpd/bin/apxs --with-apr=$httpd --with-apr-util=$httpd $extraflags" + extrainst="APACHE_LIBEXECDIR=$top/modules $extrainst" +fi + +echo "extra flags: $extraflags" + tar xvfz $src || exit 1 cd subversion-* || exit 1 -./configure --prefix=$top --with-ssl || exit 1 +./configure --prefix=$top $extraflags || exit 1 make || exit 1 -make install || exit 1 +make install $extrainst || exit 1 cd $top || exit 1 rm -rf subversion-* || exit 1 diff --git a/test/fixdescriptors/aterm-2.0.fix b/test/fixdescriptors/aterm-2.0.fix index 17e90ca8b..8b1ba8560 100644 --- a/test/fixdescriptors/aterm-2.0.fix +++ b/test/fixdescriptors/aterm-2.0.fix @@ -1,10 +1,10 @@ Descr( - [ Bind("pkgId", Str("aterm-2.0")) - , Bind("releaseId", Str("1")) + [ Bind("pkgId", "aterm-2.0") + , Bind("releaseId", "1") - , Bind("createGCC", Bool(True)) + , Bind("createGCC", True) - , Bind("src", File(Url("http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz"))) - , Bind("build", File(Local("../build/aterm-build.sh"))) + , Bind("src", Source(Url("http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz"))) + , Bind("build", Source("../build/aterm-build.sh")) ] ) diff --git a/test/fixdescriptors/atk-1.2.0.fix b/test/fixdescriptors/atk-1.2.0.fix index 74e560327..ab302a5ed 100644 --- a/test/fixdescriptors/atk-1.2.0.fix +++ b/test/fixdescriptors/atk-1.2.0.fix @@ -1,11 +1,11 @@ Descr( - [ Bind("pkgId", Str("atk-1.2.0")) - , Bind("releaseId", Str("1")) + [ Bind("pkgId", "atk-1.2.0") + , Bind("releaseId", "1") - , Bind("pkgconfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) - , Bind("glib", Pkg(Fix("./glib-2.2.1.fix"))) + , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) + , Bind("glib", Fix("./glib-2.2.1.fix")) - , Bind("src", File(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/atk-1.2.0.tar.bz2"))) - , Bind("build", File(Local("../build/atk-build.sh"))) + , Bind("src", Source(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/atk-1.2.0.tar.bz2"))) + , Bind("build", Source("../build/atk-build.sh")) ] ) diff --git a/test/fixdescriptors/glib-2.2.1.fix b/test/fixdescriptors/glib-2.2.1.fix index 2585c7ffb..86a266707 100644 --- a/test/fixdescriptors/glib-2.2.1.fix +++ b/test/fixdescriptors/glib-2.2.1.fix @@ -1,10 +1,10 @@ Descr( - [ Bind("pkgId", Str("glib-2.2.1")) - , Bind("releaseId", Str("1")) + [ Bind("pkgId", "glib-2.2.1") + , Bind("releaseId", "1") - , Bind("pkgconfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) + , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) - , Bind("src", File(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/glib-2.2.1.tar.bz2"))) - , Bind("build", File(Local("../build/glib-build.sh"))) + , Bind("src", Source(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/glib-2.2.1.tar.bz2"))) + , Bind("build", Source("../build/glib-build.sh")) ] ) diff --git a/test/fixdescriptors/gnet-1.1.8.fix b/test/fixdescriptors/gnet-1.1.8.fix index 34d9d10cd..526c2e865 100644 --- a/test/fixdescriptors/gnet-1.1.8.fix +++ b/test/fixdescriptors/gnet-1.1.8.fix @@ -1,11 +1,11 @@ Descr( - [ Bind("pkgId", Str("gnet-1.1.8")) - , Bind("releaseId", Str("1")) + [ Bind("pkgId", "gnet-1.1.8") + , Bind("releaseId", "1") - , Bind("pkgconfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) - , Bind("glib", Pkg(Fix("./glib-2.2.1.fix"))) + , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) + , Bind("glib", Fix("./glib-2.2.1.fix")) - , Bind("src", File(Url("http://www.gnetlibrary.org/src/gnet-1.1.8.tar.gz"))) - , Bind("build", File(Local("../build/gnet-build.sh"))) + , Bind("src", Source(Url("http://www.gnetlibrary.org/src/gnet-1.1.8.tar.gz"))) + , Bind("build", Source("../build/gnet-build.sh")) ] ) diff --git a/test/fixdescriptors/gtk+-2.2.1.fix b/test/fixdescriptors/gtk+-2.2.1.fix index 6793cac45..7fc3da2d7 100644 --- a/test/fixdescriptors/gtk+-2.2.1.fix +++ b/test/fixdescriptors/gtk+-2.2.1.fix @@ -1,13 +1,13 @@ Descr( - [ Bind("pkgId", Str("gtk+-2.2.1")) - , Bind("releaseId", Str("1")) + [ Bind("pkgId", "gtk+-2.2.1") + , Bind("releaseId", "1") - , Bind("pkgconfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) - , Bind("glib", Pkg(Fix("./glib-2.2.1.fix"))) - , Bind("atk", Pkg(Fix("./atk-1.2.0.fix"))) - , Bind("pango", Pkg(Fix("./pango-1.2.1.fix"))) + , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) + , Bind("glib", Fix("./glib-2.2.1.fix")) + , Bind("atk", Fix("./atk-1.2.0.fix")) + , Bind("pango", Fix("./pango-1.2.1.fix")) - , Bind("src", File(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/gtk+-2.2.1.tar.bz2"))) - , Bind("build", File(Local("../build/gtk+-build.sh"))) + , Bind("src", Source(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/gtk+-2.2.1.tar.bz2"))) + , Bind("build", Source("../build/gtk+-build.sh")) ] ) diff --git a/test/fixdescriptors/gtkspell-2.0.2.fix b/test/fixdescriptors/gtkspell-2.0.2.fix index e6d614f64..3cb0b0bf1 100644 --- a/test/fixdescriptors/gtkspell-2.0.2.fix +++ b/test/fixdescriptors/gtkspell-2.0.2.fix @@ -1,15 +1,15 @@ Descr( - [ Bind("pkgId", Str("gtkspell-2.0.2")) - , Bind("releaseId", Str("1")) + [ Bind("pkgId", "gtkspell-2.0.2") + , Bind("releaseId", "1") - , Bind("pkgconfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) - , Bind("glib", Pkg(Fix("./glib-2.2.1.fix"))) - , Bind("atk", Pkg(Fix("./atk-1.2.0.fix"))) - , Bind("pango", Pkg(Fix("./pango-1.2.1.fix"))) - , Bind("gtk", Pkg(Fix("./gtk+-2.2.1.fix"))) - , Bind("pspell", Pkg(Fix("./pspell-.12.2.fix"))) + , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) + , Bind("glib", Fix("./glib-2.2.1.fix")) + , Bind("atk", Fix("./atk-1.2.0.fix")) + , Bind("pango", Fix("./pango-1.2.1.fix")) + , Bind("gtk", Fix("./gtk+-2.2.1.fix")) + , Bind("pspell", Fix("./pspell-.12.2.fix")) - , Bind("src", File(Url("http://pan.rebelbase.com/download/extras/gtkspell/SOURCES/gtkspell-2.0.2.tar.gz"))) - , Bind("build", File(Local("../build/gtkspell-build.sh"))) + , Bind("src", Source(Url("http://pan.rebelbase.com/download/extras/gtkspell/SOURCES/gtkspell-2.0.2.tar.gz"))) + , Bind("build", Source("../build/gtkspell-build.sh")) ] ) diff --git a/test/fixdescriptors/httpd-2.0.45.fix b/test/fixdescriptors/httpd-2.0.45.fix new file mode 100644 index 000000000..bb8365b45 --- /dev/null +++ b/test/fixdescriptors/httpd-2.0.45.fix @@ -0,0 +1,10 @@ +Descr( + [ Bind("pkgId", "httpd-2.0.45") + , Bind("releaseId", "1") + + , Bind("ssl", Fix("./openssl-0.9.7b.fix")) + + , Bind("src", Source(Url("http://apache.cs.uu.nl/dist/httpd/httpd-2.0.45.tar.gz"))) + , Bind("build", Source("../build/httpd-build.sh")) + ] +) diff --git a/test/fixdescriptors/openssl-0.9.7b.fix b/test/fixdescriptors/openssl-0.9.7b.fix new file mode 100644 index 000000000..a0df665e5 --- /dev/null +++ b/test/fixdescriptors/openssl-0.9.7b.fix @@ -0,0 +1,8 @@ +Descr( + [ Bind("pkgId", "openssl-0.9.7b") + , Bind("releaseId", "1") + + , Bind("src", Source(Url("http://www.openssl.org/source/openssl-0.9.7b.tar.gz"))) + , Bind("build", Source("../build/openssl-build.sh")) + ] +) diff --git a/test/fixdescriptors/pan-0.13.95.fix b/test/fixdescriptors/pan-0.13.95.fix index 3a32756b6..c9e5b9ab6 100644 --- a/test/fixdescriptors/pan-0.13.95.fix +++ b/test/fixdescriptors/pan-0.13.95.fix @@ -1,17 +1,17 @@ Descr( - [ Bind("pkgId", Str("pan-0.13.95")) - , Bind("releaseId", Str("2")) + [ Bind("pkgId", "pan-0.13.95") + , Bind("releaseId", "2") - , Bind("pkgconfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) - , Bind("glib", Pkg(Fix("./glib-2.2.1.fix"))) - , Bind("atk", Pkg(Fix("./atk-1.2.0.fix"))) - , Bind("pango", Pkg(Fix("./pango-1.2.1.fix"))) - , Bind("gtk", Pkg(Fix("./gtk+-2.2.1.fix"))) - , Bind("gnet", Pkg(Fix("./gnet-1.1.8.fix"))) - , Bind("pspell", Pkg(Fix("./pspell-.12.2.fix"))) - , Bind("gtkspell", Pkg(Fix("./gtkspell-2.0.2.fix"))) + , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) + , Bind("glib", Fix("./glib-2.2.1.fix")) + , Bind("atk", Fix("./atk-1.2.0.fix")) + , Bind("pango", Fix("./pango-1.2.1.fix")) + , Bind("gtk", Fix("./gtk+-2.2.1.fix")) + , Bind("gnet", Fix("./gnet-1.1.8.fix")) + , Bind("pspell", Fix("./pspell-.12.2.fix")) + , Bind("gtkspell", Fix("./gtkspell-2.0.2.fix")) - , Bind("src", File(Url("http://pan.rebelbase.com/download/releases/0.13.95/SOURCE/pan-0.13.95.tar.bz2"))) - , Bind("build", File(Local("../build/pan-build-2.sh"))) + , Bind("src", Source(Url("http://pan.rebelbase.com/download/releases/0.13.95/SOURCE/pan-0.13.95.tar.bz2"))) + , Bind("build", Source("../build/pan-build-2.sh")) ] ) diff --git a/test/fixdescriptors/pango-1.2.1.fix b/test/fixdescriptors/pango-1.2.1.fix index 7ee2486a6..419c4f690 100644 --- a/test/fixdescriptors/pango-1.2.1.fix +++ b/test/fixdescriptors/pango-1.2.1.fix @@ -1,11 +1,11 @@ Descr( - [ Bind("pkgId", Str("pango-1.2.1")) - , Bind("releaseId", Str("1")) + [ Bind("pkgId", "pango-1.2.1") + , Bind("releaseId", "1") - , Bind("pkgconfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) - , Bind("glib", Pkg(Fix("./glib-2.2.1.fix"))) + , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) + , Bind("glib", Fix("./glib-2.2.1.fix")) - , Bind("src", File(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/pango-1.2.1.tar.bz2"))) - , Bind("build", File(Local("../build/pango-build.sh"))) + , Bind("src", Source(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/pango-1.2.1.tar.bz2"))) + , Bind("build", Source("../build/pango-build.sh")) ] ) diff --git a/test/fixdescriptors/pkgconfig-0.15.0.fix b/test/fixdescriptors/pkgconfig-0.15.0.fix index 27f32aba6..28fe34a4b 100644 --- a/test/fixdescriptors/pkgconfig-0.15.0.fix +++ b/test/fixdescriptors/pkgconfig-0.15.0.fix @@ -1,8 +1,8 @@ Descr( - [ Bind("pkgId", Str("pkgconfig-0.15.0")) - , Bind("releaseId", Str("1")) + [ Bind("pkgId", "pkgconfig-0.15.0") + , Bind("releaseId", "1") - , Bind("src", File(Url("http://www.freedesktop.org/software/pkgconfig/releases/pkgconfig-0.15.0.tar.gz"))) - , Bind("build", File(Local("../build/pkgconfig-build.sh"))) + , Bind("src", Source(Url("http://www.freedesktop.org/software/pkgconfig/releases/pkgconfig-0.15.0.tar.gz"))) + , Bind("build", Source("../build/pkgconfig-build.sh")) ] ) diff --git a/test/fixdescriptors/pspell-.12.2.fix b/test/fixdescriptors/pspell-.12.2.fix index f19600c25..617414079 100644 --- a/test/fixdescriptors/pspell-.12.2.fix +++ b/test/fixdescriptors/pspell-.12.2.fix @@ -1,8 +1,8 @@ Descr( - [ Bind("pkgId", Str("pspell-.12.2")) - , Bind("releaseId", Str("1")) + [ Bind("pkgId", "pspell-.12.2") + , Bind("releaseId", "1") - , Bind("src", File(Url("http://unc.dl.sourceforge.net/sourceforge/pspell/pspell-.12.2.tar.gz"))) - , Bind("build", File(Local("../build/pspell-build.sh"))) + , Bind("src", Source(Url("http://unc.dl.sourceforge.net/sourceforge/pspell/pspell-.12.2.tar.gz"))) + , Bind("build", Source("../build/pspell-build.sh")) ] ) diff --git a/test/fixdescriptors/subversion-0.21.0.fix b/test/fixdescriptors/subversion-0.21.0.fix index 90d9d8d42..dfe1f2482 100644 --- a/test/fixdescriptors/subversion-0.21.0.fix +++ b/test/fixdescriptors/subversion-0.21.0.fix @@ -1,8 +1,16 @@ Descr( - [ Bind("pkgId", Str("subversion-0.21.0")) - , Bind("releaseId", Str("1")) + [ Bind("pkgId", "subversion-0.21.0") + , Bind("releaseId", "1") - , Bind("src", File(Url("http://subversion.tigris.org/files/documents/15/3712/subversion-0.21.0.tar.gz"))) - , Bind("build", File(Local("../build/subversion-build.sh"))) + , Bind("httpsClient", Bool(True)) + , Bind("httpServer", Bool(True)) + , Bind("httpsServer", Bool(True)) + + , Bind("ssl", If(Var("httpsClient"), Fix("./openssl-0.9.7b.fix"), "")) + + , Bind("httpd", If(Var("httpServer"), Fix("./httpd-2.0.45.fix"), "")) + + , Bind("src", Source(Url("http://subversion.tigris.org/files/documents/15/3712/subversion-0.21.0.tar.gz"))) + , Bind("build", Source("../build/subversion-build.sh")) ] ) diff --git a/test/fixdescriptors/system.fix b/test/fixdescriptors/system.fix index 62d4eae40..5e9f497d1 100644 --- a/test/fixdescriptors/system.fix +++ b/test/fixdescriptors/system.fix @@ -2,18 +2,18 @@ Descr( [ Bind("pkgId", Str("system")) , Bind("releaseId", Str("2")) - , Bind("actATerm", Pkg(Fix("./aterm-2.0.fix"))) - , Bind("actPkgConfig", Pkg(Fix("./pkgconfig-0.15.0.fix"))) - , Bind("actGlib", Pkg(Fix("./glib-2.2.1.fix"))) - , Bind("actAtk", Pkg(Fix("./atk-1.2.0.fix"))) - , Bind("actPango", Pkg(Fix("./pango-1.2.1.fix"))) - , Bind("actGtk", Pkg(Fix("./gtk+-2.2.1.fix"))) - , Bind("actGnet", Pkg(Fix("./gnet-1.1.8.fix"))) - , Bind("actPspell", Pkg(Fix("./pspell-.12.2.fix"))) - , Bind("actGtkspell", Pkg(Fix("./gtkspell-2.0.2.fix"))) - , Bind("actPan", Pkg(Fix("./pan-0.13.95.fix"))) - , Bind("actSubversion", Pkg(Fix("./subversion-0.21.0.fix"))) + , Bind("actATerm", Fix("./aterm-2.0.fix")) + , Bind("actPkgConfig", Fix("./pkgconfig-0.15.0.fix")) + , Bind("actGlib", Fix("./glib-2.2.1.fix")) + , Bind("actAtk", Fix("./atk-1.2.0.fix")) + , Bind("actPango", Fix("./pango-1.2.1.fix")) + , Bind("actGtk", Fix("./gtk+-2.2.1.fix")) + , Bind("actGnet", Fix("./gnet-1.1.8.fix")) + , Bind("actPspell", Fix("./pspell-.12.2.fix")) + , Bind("actGtkspell", Fix("./gtkspell-2.0.2.fix")) + , Bind("actPan", Fix("./pan-0.13.95.fix")) + , Bind("actSubversion", Fix("./subversion-0.21.0.fix")) - , Bind("build", File(Local("../../scripts/nix-populate"))) + , Bind("build", Source("../../scripts/nix-populate")) ] ) From 7dd91d3779b4f806ac0085e0ccc60416d81c1148 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 25 May 2003 22:42:19 +0000 Subject: [PATCH 0053/6440] * Prebuilt package sharing. We allow transparent binary deployment by sharing package directories (i.e., the result of building a Nix descriptor). `nix-pull-prebuilts' obtains a list of all known prebuilts by consulting the paths and URLs specified in $prefix/etc/nix/prebuilts.conf. The mappings ($pkghash, $prebuilthash) and ($prebuilthash, $location) are registered with Nix so that it can use the prebuilt with hash $prebuilthash when installing a package with hash $pkghash by downloading and unpacking $location. `nix-push-prebuilts' creates prebuilts for all packages for which no prebuilt is known to exist. It can then optionally upload these to the network through rsync. `nix-[pull|push]-prebuilts' just provide a policy. Nix provides the mechanism through the `nix [export|regprebuilt|regurl]' commands. --- scripts/Makefile.am | 6 ++- scripts/nix-generate-regscript | 20 ---------- scripts/nix-pull-prebuilts | 69 ++++++++++++++++++++++++++++++++++ scripts/nix-push-prebuilts | 40 ++++++++++++++++++++ scripts/prebuilts.conf | 4 ++ src/Makefile.am | 3 ++ src/fix.cc | 69 +++++++++++----------------------- src/nix.cc | 62 +++++++++++++++++++++++++++--- src/util.hh | 36 +++++++++++++++--- 9 files changed, 230 insertions(+), 79 deletions(-) delete mode 100755 scripts/nix-generate-regscript create mode 100755 scripts/nix-pull-prebuilts create mode 100755 scripts/nix-push-prebuilts create mode 100644 scripts/prebuilts.conf diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 4140cdf5b..f1d008f1e 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -1,5 +1,9 @@ -bin_SCRIPTS = nix-generate-regscript nix-switch nix-collect-garbage +bin_SCRIPTS = nix-generate-regscript nix-switch nix-collect-garbage \ + nix-pull-prebuilts nix-push-prebuilts install-exec-local: $(INSTALL) -d $(sysconfdir)/profile.d $(INSTALL_PROGRAM) nix-profile.sh $(sysconfdir)/profile.d/nix.sh + $(INSTALL) -d $(sysconfdir)/nix + # !!! don't overwrite local modifications + $(INSTALL_PROGRAM) prebuilts.conf $(sysconfdir)/nix/prebuilts.conf diff --git a/scripts/nix-generate-regscript b/scripts/nix-generate-regscript deleted file mode 100755 index bf370f8d7..000000000 --- a/scripts/nix-generate-regscript +++ /dev/null @@ -1,20 +0,0 @@ -#! /usr/bin/perl -w - -my $dir = shift @ARGV; -$dir || die "missing directory"; -my $url = shift @ARGV; -$url || die "missing base url"; - -chdir $dir || die "cannot chdir to $dir"; - -foreach my $prebuilt (glob("*.tar.bz2")) { - - $prebuilt =~ /-([a-z0-9]+)-([a-z0-9]+).tar.bz2$/ - || die "invalid file name: $prebuilt"; - - my $pkgHash = $1; - my $prebuiltHash = $2; - - print "regprebuilt $pkgHash $prebuiltHash\n"; - print "regurl $prebuiltHash $url/$prebuilt\n"; -} diff --git a/scripts/nix-pull-prebuilts b/scripts/nix-pull-prebuilts new file mode 100755 index 000000000..91bbf8082 --- /dev/null +++ b/scripts/nix-pull-prebuilts @@ -0,0 +1,69 @@ +#! /usr/bin/perl -w + +my $prefix = $ENV{"NIX"} || "/nix"; # !!! use prefix +my $etcdir = "$prefix/etc/nix"; +my $knowns = "$prefix/var/nix/known-prebuilts"; +my $tmpfile = "$prefix/var/nix/prebuilts.tmp"; + +my $conffile = "$etcdir/prebuilts.conf"; + +sub register { + my $fn = shift; + return unless $fn =~ /([^\/]*)-([0-9a-z]{32})-([0-9a-z]{32})\.tar\.bz2/; + my $id = $1; + my $pkghash = $2; + my $prebuilthash = $3; + print "$pkghash => $prebuilthash ($id)\n"; + system "nix regprebuilt $pkghash $prebuilthash"; + if ($?) { die "`nix regprebuilt' failed"; } + print KNOWNS "$pkghash\n"; +} + +open KNOWNS, ">$knowns"; + +open CONFFILE, "<$conffile"; + +while () { + chomp; + if (/^\s*(\S+)\s*(\#.*)?$/) { + my $url = $1; + + print "obtaining prebuilt list from $url...\n"; + + if ($url =~ /^\//) { + + # It's a local path. + + foreach my $fn (glob "$url/*") { + register $fn; + } + + } else { + + # It's a URL. + + system "wget '$url' -O '$tmpfile' 2> /dev/null"; # !!! escape + if ($?) { die "`wget' failed"; } + + open INDEX, "<$tmpfile"; + + while () { + # Get all links to prebuilts, that is, file names of the + # form foo-HASH-HASH.tar.bz2. + next unless (/HREF=\"([^\"]*)\"/); + my $fn = $1; + next if $fn =~ /\.\./; + next if $fn =~ /\//; + register $fn; + } + + close INDEX; + + unlink $tmpfile; + } + } +} + +close CONFFILE; + +close KNOWNS; diff --git a/scripts/nix-push-prebuilts b/scripts/nix-push-prebuilts new file mode 100755 index 000000000..2e3029b16 --- /dev/null +++ b/scripts/nix-push-prebuilts @@ -0,0 +1,40 @@ +#! /usr/bin/perl -w + +my $prefix = $ENV{"NIX"} || "/nix"; # !!! use prefix +my $etcdir = "$prefix/etc/nix"; +my $exportdir = "$prefix/var/nix/prebuilts/exports"; +my $knowns = "$prefix/var/nix/known-prebuilts"; + +# For performance, put the known hashes in an associative array. +my %knowns = (); +open KNOWNS, "<$knowns"; +while () { + next unless /([0-9a-z]{32})/; + $knowns{$1} = 1; +} +close KNOWNS; + +# For each installed package, check whether a prebuilt is known. + +open PKGS, "nix listinst|"; +open KNOWNS, ">>$knowns"; + +while () { + chomp; + next unless /([0-9a-z]{32})/; + my $pkghash = $1; + if (!defined $knowns{$1}) { + # No known prebuilt exists for this package; so export it. + print "exporting $pkghash...\n"; + system "nix export '$exportdir' $pkghash"; + if ($?) { die "`nix export' failed"; } + print KNOWNS "$pkghash\n"; + } +} + +close KNOWNS; +close PKGS; + +# Push the prebuilts to the server. !!! FIXME + +system "rsync -av -e ssh '$exportdir' losser:/home/eelco/public_html/nix-prebuilts/"; diff --git a/scripts/prebuilts.conf b/scripts/prebuilts.conf new file mode 100644 index 000000000..9b950cad4 --- /dev/null +++ b/scripts/prebuilts.conf @@ -0,0 +1,4 @@ +# A list of URLs or local paths from where we obtain prebuilts. +/nix/var/nix/prebuilts/imports +/nix/var/nix/prebuilts/exports +http://losser.st-lab.cs.uu.nl/~eelco/nix-prebuilts/ diff --git a/src/Makefile.am b/src/Makefile.am index d6dbdcb73..31fad8925 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,5 +13,8 @@ install-data-local: $(INSTALL) -d $(localstatedir)/nix/descriptors $(INSTALL) -d $(localstatedir)/nix/sources $(INSTALL) -d $(localstatedir)/nix/links + $(INSTALL) -d $(localstatedir)/nix/prebuilts + $(INSTALL) -d $(localstatedir)/nix/prebuilts/imports + $(INSTALL) -d $(localstatedir)/nix/prebuilts/exports $(INSTALL) -d $(prefix)/pkg $(bindir)/nix init diff --git a/src/fix.cc b/src/fix.cc index 052c1d4c9..286b552c3 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -13,7 +13,6 @@ extern "C" { static string nixDescriptorDir; -static string nixSourcesDir; static bool verbose = false; @@ -33,46 +32,6 @@ void registerFile(string filename) throw Error("cannot register " + filename + " with Nix"); } - -/* Return the directory part of the given path, i.e., everything - before the final `/'. */ -string dirOf(string s) -{ - unsigned int pos = s.rfind('/'); - if (pos == string::npos) throw Error("invalid file name"); - return string(s, 0, pos); -} - - -/* Return the base name of the given path, i.e., everything following - the final `/'. */ -string baseNameOf(string s) -{ - unsigned int pos = s.rfind('/'); - if (pos == string::npos) throw Error("invalid file name"); - return string(s, pos + 1); -} - - -/* Download object referenced by the given URL into the sources - directory. Return the file name it was downloaded to. */ -string fetchURL(string url) -{ - string filename = baseNameOf(url); - string fullname = nixSourcesDir + "/" + filename; - struct stat st; - if (stat(fullname.c_str(), &st)) { - /* !!! quoting */ - string shellCmd = - "cd " + nixSourcesDir + " && wget --quiet -N \"" + url + "\""; - int res = system(shellCmd.c_str()); - if (WEXITSTATUS(res) != 0) - throw Error("cannot fetch " + url); - } - return fullname; -} - - Error badTerm(const string & msg, ATerm e) { char * s = ATwriteToString(e); @@ -120,7 +79,7 @@ bool evaluateBool(ATerm e, EvalContext ctx) ATerm evaluate(ATerm e, EvalContext ctx) { char * s; - ATerm e2; + ATerm e2, e3; ATerm eCond, eTrue, eFalse; /* Check for normal forms first. */ @@ -166,6 +125,7 @@ ATerm evaluate(ATerm e, EvalContext ctx) instantiateDescriptor(filename, ctx).c_str()); } +#if 0 /* `Source' copies the specified file to nixSourcesDir, registers it with Nix, and returns the hash of the file. */ else if (ATmatch(e, "Source()", &e2)) { @@ -185,15 +145,29 @@ ATerm evaluate(ATerm e, EvalContext ctx) registerFile(target); return ATmake("File()", hashFile(target).c_str()); } +#endif - /* `Url' fetches a file from the network, caching it in - nixSourcesDir and returning the file name. */ - else if (ATmatch(e, "Url()", &e2)) { - string url = evaluateStr(e2, ctx); + /* `Local' registers a file with Nix, and returns the file's + hash. */ + else if (ATmatch(e, "Local()", &e2)) { + string filename = absPath(evaluateStr(e2, ctx), ctx.dir); /* !!! */ + string hash = hashFile(filename); + return ATmake("File()", hash.c_str()); + } + + /* `Url' registers a mapping from a hash to an url with Nix, and + returns the hash. */ + else if (ATmatch(e, "Url(, )", &e2, &e3)) { + string hash = evaluateStr(e2, ctx); + checkHash(hash); + string url = evaluateStr(e3, ctx); +#if 0 if (verbose) cerr << "fetching " << url << endl; string filename = fetchURL(url); - return ATmake("Str()", filename.c_str()); +#endif + /* !!! register */ + return ATmake("File()", hash.c_str()); } /* `If' provides conditional evaluation. */ @@ -329,7 +303,6 @@ void run(Strings::iterator argCur, Strings::iterator argEnd) if (homeDir) nixHomeDir = homeDir; nixDescriptorDir = nixHomeDir + "/var/nix/descriptors"; - nixSourcesDir = nixHomeDir + "/var/nix/sources"; for ( ; argCur != argEnd; argCur++) { string arg(*argCur); diff --git a/src/nix.cc b/src/nix.cc index 973c36727..cfe879952 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -28,6 +28,9 @@ static string dbInstPkgs = "pkginst"; static string dbPrebuilts = "prebuilts"; +static string nixSourcesDir; + + /* Wrapper classes that ensures that the database is closed upon object destruction. */ class Db2 : public Db @@ -435,9 +438,9 @@ void exportPkgs(string outDir, string pkgDir = getPkg(hash); string tmpFile = outDir + "/export_tmp"; - string cmd = "cd " + pkgDir + " && tar cvfj " + tmpFile + " ."; + string cmd = "cd " + pkgDir + " && tar cfj " + tmpFile + " ."; int res = system(cmd.c_str()); // !!! escaping - if (WEXITSTATUS(res) != 0) + if (!WIFEXITED(res) || WEXITSTATUS(res) != 0) throw Error("cannot tar " + pkgDir); string prebuiltHash = hashFile(tmpFile); @@ -458,10 +461,12 @@ void regPrebuilt(string pkgHash, string prebuiltHash) } -void registerFile(string filename) +string registerFile(string filename) { filename = absPath(filename); - setDB(dbRefs, hashFile(filename), filename); + string hash = hashFile(filename); + setDB(dbRefs, hash, filename); + return hash; } @@ -618,8 +623,46 @@ void printGraph(Strings::iterator first, Strings::iterator last) } -void run(Strings args) +/* Download object referenced by the given URL into the sources + directory. Return the file name it was downloaded to. */ +string fetchURL(string url) { + string filename = baseNameOf(url); + string fullname = nixSourcesDir + "/" + filename; + struct stat st; + if (stat(fullname.c_str(), &st)) { + /* !!! quoting */ + string shellCmd = + "cd " + nixSourcesDir + " && wget --quiet -N \"" + url + "\""; + int res = system(shellCmd.c_str()); + if (WEXITSTATUS(res) != 0) + throw Error("cannot fetch " + url); + } + return fullname; +} + + +void fetch(string id) +{ + string fn; + + /* Fetch the object referenced by id. */ + if (isHash(id)) { + throw Error("not implemented"); + } else { + fn = fetchURL(id); + } + + /* Register it by hash. */ + string hash = registerFile(fn); + cout << hash << endl; +} + + +void fetch(Strings::iterator first, Strings::iterator last) +{ + for (Strings::iterator it = first; it != last; it++) + fetch(*it); } @@ -675,6 +718,11 @@ Subcommands: graph HASH... Like closure, but print a dot graph specification. + + fetch ID... + Fetch the objects identified by ID and place them in the Nix + sources directory. ID can be a hash or URL. Print out the hash + of the object. "; } @@ -686,6 +734,8 @@ void run(Strings::iterator argCur, Strings::iterator argEnd) char * homeDir = getenv(nixHomeDirEnvVar.c_str()); if (homeDir) nixHomeDir = homeDir; + nixSourcesDir = nixHomeDir + "/var/nix/sources"; + /* Parse the global flags. */ for ( ; argCur != argEnd; argCur++) { string arg(*argCur); @@ -742,6 +792,8 @@ void run(Strings::iterator argCur, Strings::iterator argEnd) printClosure(argCur, argEnd); } else if (cmd == "graph") { printGraph(argCur, argEnd); + } else if (cmd == "fetch") { + fetch(argCur, argEnd); } else throw UsageError("unknown command: " + string(cmd)); } diff --git a/src/util.hh b/src/util.hh index fb405b0f1..9b3f212de 100644 --- a/src/util.hh +++ b/src/util.hh @@ -85,17 +85,22 @@ string printHash(unsigned char * buf) /* Verify that a reference is valid (that is, is a MD5 hash code). */ -void checkHash(const string & s) +bool isHash(const string & s) { - string err = "invalid reference: " + s; - if (s.length() != 32) - throw BadRefError(err); + if (s.length() != 32) return false; for (int i = 0; i < 32; i++) { char c = s[i]; if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) - throw BadRefError(err); + return false; } + return true; +} + + +void checkHash(const string & s) +{ + if (!isHash(s)) throw BadRefError("invalid reference: " + s); } @@ -113,4 +118,25 @@ string hashFile(string filename) } + +/* Return the directory part of the given path, i.e., everything + before the final `/'. */ +string dirOf(string s) +{ + unsigned int pos = s.rfind('/'); + if (pos == string::npos) throw Error("invalid file name"); + return string(s, 0, pos); +} + + +/* Return the base name of the given path, i.e., everything following + the final `/'. */ +string baseNameOf(string s) +{ + unsigned int pos = s.rfind('/'); + if (pos == string::npos) throw Error("invalid file name"); + return string(s, pos + 1); +} + + #endif /* !__UTIL_H */ From 13176d74cc522951e2c8ed6a878a04ddfce778ca Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 25 May 2003 22:43:33 +0000 Subject: [PATCH 0054/6440] * Updated Fix descriptors to reflect the new Fix abstract syntax. --- test/fixdescriptors/aterm-2.0.fix | 4 ++-- test/fixdescriptors/atk-1.2.0.fix | 4 ++-- test/fixdescriptors/glib-2.2.1.fix | 4 ++-- test/fixdescriptors/gnet-1.1.8.fix | 4 ++-- test/fixdescriptors/gtk+-2.2.1.fix | 4 ++-- test/fixdescriptors/gtkspell-2.0.2.fix | 4 ++-- test/fixdescriptors/httpd-2.0.45.fix | 4 ++-- test/fixdescriptors/openssl-0.9.7b.fix | 4 ++-- test/fixdescriptors/{pan-0.13.95.fix => pan-0.14.0.fix} | 8 ++++---- test/fixdescriptors/pango-1.2.1.fix | 4 ++-- test/fixdescriptors/pkgconfig-0.15.0.fix | 4 ++-- test/fixdescriptors/pspell-.12.2.fix | 4 ++-- test/fixdescriptors/subversion-0.21.0.fix | 4 ++-- test/fixdescriptors/system.fix | 6 +++--- 14 files changed, 31 insertions(+), 31 deletions(-) rename test/fixdescriptors/{pan-0.13.95.fix => pan-0.14.0.fix} (60%) diff --git a/test/fixdescriptors/aterm-2.0.fix b/test/fixdescriptors/aterm-2.0.fix index 8b1ba8560..2fdf43434 100644 --- a/test/fixdescriptors/aterm-2.0.fix +++ b/test/fixdescriptors/aterm-2.0.fix @@ -4,7 +4,7 @@ Descr( , Bind("createGCC", True) - , Bind("src", Source(Url("http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz"))) - , Bind("build", Source("../build/aterm-build.sh")) + , Bind("src", Url("853474e4bcf4a85f7d38a0676b36bded", "http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz")) + , Bind("build", Local("../build/aterm-build.sh")) ] ) diff --git a/test/fixdescriptors/atk-1.2.0.fix b/test/fixdescriptors/atk-1.2.0.fix index ab302a5ed..9f880be11 100644 --- a/test/fixdescriptors/atk-1.2.0.fix +++ b/test/fixdescriptors/atk-1.2.0.fix @@ -5,7 +5,7 @@ Descr( , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) , Bind("glib", Fix("./glib-2.2.1.fix")) - , Bind("src", Source(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/atk-1.2.0.tar.bz2"))) - , Bind("build", Source("../build/atk-build.sh")) + , Bind("src", Url("06a84758129554ae044af8865ecb6f1c", "ftp://ftp.gtk.org/pub/gtk/v2.2/atk-1.2.0.tar.bz2")) + , Bind("build", Local("../build/atk-build.sh")) ] ) diff --git a/test/fixdescriptors/glib-2.2.1.fix b/test/fixdescriptors/glib-2.2.1.fix index 86a266707..9dc0f4e29 100644 --- a/test/fixdescriptors/glib-2.2.1.fix +++ b/test/fixdescriptors/glib-2.2.1.fix @@ -4,7 +4,7 @@ Descr( , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) - , Bind("src", Source(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/glib-2.2.1.tar.bz2"))) - , Bind("build", Source("../build/glib-build.sh")) + , Bind("src", Url("42406a17819080326e105f8333963b97", "ftp://ftp.gtk.org/pub/gtk/v2.2/glib-2.2.1.tar.bz2")) + , Bind("build", Local("../build/glib-build.sh")) ] ) diff --git a/test/fixdescriptors/gnet-1.1.8.fix b/test/fixdescriptors/gnet-1.1.8.fix index 526c2e865..ef1f1b1da 100644 --- a/test/fixdescriptors/gnet-1.1.8.fix +++ b/test/fixdescriptors/gnet-1.1.8.fix @@ -5,7 +5,7 @@ Descr( , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) , Bind("glib", Fix("./glib-2.2.1.fix")) - , Bind("src", Source(Url("http://www.gnetlibrary.org/src/gnet-1.1.8.tar.gz"))) - , Bind("build", Source("../build/gnet-build.sh")) + , Bind("src", Url("da2b5de278e96a5b907c2e2304bf6542", "http://www.gnetlibrary.org/src/gnet-1.1.8.tar.gz")) + , Bind("build", Local("../build/gnet-build.sh")) ] ) diff --git a/test/fixdescriptors/gtk+-2.2.1.fix b/test/fixdescriptors/gtk+-2.2.1.fix index 7fc3da2d7..9563df0a9 100644 --- a/test/fixdescriptors/gtk+-2.2.1.fix +++ b/test/fixdescriptors/gtk+-2.2.1.fix @@ -7,7 +7,7 @@ Descr( , Bind("atk", Fix("./atk-1.2.0.fix")) , Bind("pango", Fix("./pango-1.2.1.fix")) - , Bind("src", Source(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/gtk+-2.2.1.tar.bz2"))) - , Bind("build", Source("../build/gtk+-build.sh")) + , Bind("src", Url("dfd5755fddb26a46c96bfaa813280ac4", "ftp://ftp.gtk.org/pub/gtk/v2.2/gtk+-2.2.1.tar.bz2")) + , Bind("build", Local("../build/gtk+-build.sh")) ] ) diff --git a/test/fixdescriptors/gtkspell-2.0.2.fix b/test/fixdescriptors/gtkspell-2.0.2.fix index 3cb0b0bf1..ef7e185c9 100644 --- a/test/fixdescriptors/gtkspell-2.0.2.fix +++ b/test/fixdescriptors/gtkspell-2.0.2.fix @@ -9,7 +9,7 @@ Descr( , Bind("gtk", Fix("./gtk+-2.2.1.fix")) , Bind("pspell", Fix("./pspell-.12.2.fix")) - , Bind("src", Source(Url("http://pan.rebelbase.com/download/extras/gtkspell/SOURCES/gtkspell-2.0.2.tar.gz"))) - , Bind("build", Source("../build/gtkspell-build.sh")) + , Bind("src", Url("385daba9bebfdc7fdbdf524e07deb920", "http://pan.rebelbase.com/download/extras/gtkspell/SOURCES/gtkspell-2.0.2.tar.gz")) + , Bind("build", Local("../build/gtkspell-build.sh")) ] ) diff --git a/test/fixdescriptors/httpd-2.0.45.fix b/test/fixdescriptors/httpd-2.0.45.fix index bb8365b45..e0db5ab2e 100644 --- a/test/fixdescriptors/httpd-2.0.45.fix +++ b/test/fixdescriptors/httpd-2.0.45.fix @@ -4,7 +4,7 @@ Descr( , Bind("ssl", Fix("./openssl-0.9.7b.fix")) - , Bind("src", Source(Url("http://apache.cs.uu.nl/dist/httpd/httpd-2.0.45.tar.gz"))) - , Bind("build", Source("../build/httpd-build.sh")) + , Bind("src", Url("1f33e9a2e2de06da190230fa72738d75", "http://apache.cs.uu.nl/dist/httpd/httpd-2.0.45.tar.gz")) + , Bind("build", Local("../build/httpd-build.sh")) ] ) diff --git a/test/fixdescriptors/openssl-0.9.7b.fix b/test/fixdescriptors/openssl-0.9.7b.fix index a0df665e5..f55d0cca1 100644 --- a/test/fixdescriptors/openssl-0.9.7b.fix +++ b/test/fixdescriptors/openssl-0.9.7b.fix @@ -2,7 +2,7 @@ Descr( [ Bind("pkgId", "openssl-0.9.7b") , Bind("releaseId", "1") - , Bind("src", Source(Url("http://www.openssl.org/source/openssl-0.9.7b.tar.gz"))) - , Bind("build", Source("../build/openssl-build.sh")) + , Bind("src", Url("fae4bec090fa78e20f09d76d55b6ccff", "http://www.openssl.org/source/openssl-0.9.7b.tar.gz")) + , Bind("build", Local("../build/openssl-build.sh")) ] ) diff --git a/test/fixdescriptors/pan-0.13.95.fix b/test/fixdescriptors/pan-0.14.0.fix similarity index 60% rename from test/fixdescriptors/pan-0.13.95.fix rename to test/fixdescriptors/pan-0.14.0.fix index c9e5b9ab6..1fbee1179 100644 --- a/test/fixdescriptors/pan-0.13.95.fix +++ b/test/fixdescriptors/pan-0.14.0.fix @@ -1,6 +1,6 @@ Descr( - [ Bind("pkgId", "pan-0.13.95") - , Bind("releaseId", "2") + [ Bind("pkgId", "pan-0.14.0") + , Bind("releaseId", "1") , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) , Bind("glib", Fix("./glib-2.2.1.fix")) @@ -11,7 +11,7 @@ Descr( , Bind("pspell", Fix("./pspell-.12.2.fix")) , Bind("gtkspell", Fix("./gtkspell-2.0.2.fix")) - , Bind("src", Source(Url("http://pan.rebelbase.com/download/releases/0.13.95/SOURCE/pan-0.13.95.tar.bz2"))) - , Bind("build", Source("../build/pan-build-2.sh")) + , Bind("src", Url("b2702adadb84c2e0d52d2bb029c05206", "http://pan.rebelbase.com/download/releases/0.14.0/SOURCE/pan-0.14.0.tar.bz2")) + , Bind("build", Local("../build/pan-build-2.sh")) ] ) diff --git a/test/fixdescriptors/pango-1.2.1.fix b/test/fixdescriptors/pango-1.2.1.fix index 419c4f690..886616d0d 100644 --- a/test/fixdescriptors/pango-1.2.1.fix +++ b/test/fixdescriptors/pango-1.2.1.fix @@ -5,7 +5,7 @@ Descr( , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) , Bind("glib", Fix("./glib-2.2.1.fix")) - , Bind("src", Source(Url("ftp://ftp.gtk.org/pub/gtk/v2.2/pango-1.2.1.tar.bz2"))) - , Bind("build", Source("../build/pango-build.sh")) + , Bind("src", Url("6b354ef14e75739a92b5b78f4ca3165a", "ftp://ftp.gtk.org/pub/gtk/v2.2/pango-1.2.1.tar.bz2")) + , Bind("build", Local("../build/pango-build.sh")) ] ) diff --git a/test/fixdescriptors/pkgconfig-0.15.0.fix b/test/fixdescriptors/pkgconfig-0.15.0.fix index 28fe34a4b..bf895b0f5 100644 --- a/test/fixdescriptors/pkgconfig-0.15.0.fix +++ b/test/fixdescriptors/pkgconfig-0.15.0.fix @@ -2,7 +2,7 @@ Descr( [ Bind("pkgId", "pkgconfig-0.15.0") , Bind("releaseId", "1") - , Bind("src", Source(Url("http://www.freedesktop.org/software/pkgconfig/releases/pkgconfig-0.15.0.tar.gz"))) - , Bind("build", Source("../build/pkgconfig-build.sh")) + , Bind("src", Url("a7e4f60a6657dbc434334deb594cc242", "http://www.freedesktop.org/software/pkgconfig/releases/pkgconfig-0.15.0.tar.gz")) + , Bind("build", Local("../build/pkgconfig-build.sh")) ] ) diff --git a/test/fixdescriptors/pspell-.12.2.fix b/test/fixdescriptors/pspell-.12.2.fix index 617414079..945cb62a8 100644 --- a/test/fixdescriptors/pspell-.12.2.fix +++ b/test/fixdescriptors/pspell-.12.2.fix @@ -2,7 +2,7 @@ Descr( [ Bind("pkgId", "pspell-.12.2") , Bind("releaseId", "1") - , Bind("src", Source(Url("http://unc.dl.sourceforge.net/sourceforge/pspell/pspell-.12.2.tar.gz"))) - , Bind("build", Source("../build/pspell-build.sh")) + , Bind("src", Url("cfd3816b2372932a1b71c0ce4e9f881e", "http://unc.dl.sourceforge.net/sourceforge/pspell/pspell-.12.2.tar.gz")) + , Bind("build", Local("../build/pspell-build.sh")) ] ) diff --git a/test/fixdescriptors/subversion-0.21.0.fix b/test/fixdescriptors/subversion-0.21.0.fix index dfe1f2482..de2edbcbb 100644 --- a/test/fixdescriptors/subversion-0.21.0.fix +++ b/test/fixdescriptors/subversion-0.21.0.fix @@ -10,7 +10,7 @@ Descr( , Bind("httpd", If(Var("httpServer"), Fix("./httpd-2.0.45.fix"), "")) - , Bind("src", Source(Url("http://subversion.tigris.org/files/documents/15/3712/subversion-0.21.0.tar.gz"))) - , Bind("build", Source("../build/subversion-build.sh")) + , Bind("src", Url("b2ad91127fb652e764b750f4c0002528", "http://subversion.tigris.org/files/documents/15/3712/subversion-0.21.0.tar.gz")) + , Bind("build", Local("../build/subversion-build.sh")) ] ) diff --git a/test/fixdescriptors/system.fix b/test/fixdescriptors/system.fix index 5e9f497d1..7ca8c8797 100644 --- a/test/fixdescriptors/system.fix +++ b/test/fixdescriptors/system.fix @@ -1,6 +1,6 @@ Descr( [ Bind("pkgId", Str("system")) - , Bind("releaseId", Str("2")) + , Bind("releaseId", Str("3")) , Bind("actATerm", Fix("./aterm-2.0.fix")) , Bind("actPkgConfig", Fix("./pkgconfig-0.15.0.fix")) @@ -11,9 +11,9 @@ Descr( , Bind("actGnet", Fix("./gnet-1.1.8.fix")) , Bind("actPspell", Fix("./pspell-.12.2.fix")) , Bind("actGtkspell", Fix("./gtkspell-2.0.2.fix")) - , Bind("actPan", Fix("./pan-0.13.95.fix")) + , Bind("actPan", Fix("./pan-0.14.0.fix")) , Bind("actSubversion", Fix("./subversion-0.21.0.fix")) - , Bind("build", Source("../../scripts/nix-populate")) + , Bind("build", Local("../../scripts/nix-populate")) ] ) From f8d91f20e6c88510282263715a1b87c99afad5a1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 26 May 2003 09:44:18 +0000 Subject: [PATCH 0055/6440] * Nix can now fetch prebuilts (and other files) from the network, iff a mapping from the hash to a url has been registered through `nix regurl'. * Bug fix in nix: don't pollute stdout when running tar, it made nix-switch barf. * Bug fix in nix-push-prebuilts: don't create a subdirectory on the target when rsync'ing. --- scripts/Makefile.am | 2 +- scripts/nix-pull-prebuilts | 16 ++++- scripts/nix-push-prebuilts | 10 +-- scripts/nix-switch | 2 +- src/fix.cc | 23 ++++--- src/nix.cc | 126 +++++++++++++++++++++++++------------ 6 files changed, 121 insertions(+), 58 deletions(-) diff --git a/scripts/Makefile.am b/scripts/Makefile.am index f1d008f1e..cf70f1574 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -1,4 +1,4 @@ -bin_SCRIPTS = nix-generate-regscript nix-switch nix-collect-garbage \ +bin_SCRIPTS = nix-switch nix-collect-garbage \ nix-pull-prebuilts nix-push-prebuilts install-exec-local: diff --git a/scripts/nix-pull-prebuilts b/scripts/nix-pull-prebuilts index 91bbf8082..9cc668337 100755 --- a/scripts/nix-pull-prebuilts +++ b/scripts/nix-pull-prebuilts @@ -9,13 +9,25 @@ my $conffile = "$etcdir/prebuilts.conf"; sub register { my $fn = shift; + my $url = shift; return unless $fn =~ /([^\/]*)-([0-9a-z]{32})-([0-9a-z]{32})\.tar\.bz2/; my $id = $1; my $pkghash = $2; my $prebuilthash = $3; + print "$pkghash => $prebuilthash ($id)\n"; + system "nix regprebuilt $pkghash $prebuilthash"; if ($?) { die "`nix regprebuilt' failed"; } + + if ($url =~ /^\//) { + system "nix regfile $url"; + if ($?) { die "`nix regfile' failed"; } + } else { + system "nix regurl $prebuilthash $url"; + if ($?) { die "`nix regurl' failed"; } + } + print KNOWNS "$pkghash\n"; } @@ -35,7 +47,7 @@ while () { # It's a local path. foreach my $fn (glob "$url/*") { - register $fn; + register($fn, $fn); } } else { @@ -54,7 +66,7 @@ while () { my $fn = $1; next if $fn =~ /\.\./; next if $fn =~ /\//; - register $fn; + register($fn, "$url/$fn"); } close INDEX; diff --git a/scripts/nix-push-prebuilts b/scripts/nix-push-prebuilts index 2e3029b16..952897879 100755 --- a/scripts/nix-push-prebuilts +++ b/scripts/nix-push-prebuilts @@ -17,7 +17,6 @@ close KNOWNS; # For each installed package, check whether a prebuilt is known. open PKGS, "nix listinst|"; -open KNOWNS, ">>$knowns"; while () { chomp; @@ -28,13 +27,16 @@ while () { print "exporting $pkghash...\n"; system "nix export '$exportdir' $pkghash"; if ($?) { die "`nix export' failed"; } - print KNOWNS "$pkghash\n"; } } -close KNOWNS; close PKGS; # Push the prebuilts to the server. !!! FIXME -system "rsync -av -e ssh '$exportdir' losser:/home/eelco/public_html/nix-prebuilts/"; +system "rsync -av -e ssh '$exportdir'/ losser:/home/eelco/public_html/nix-prebuilts/"; + +# Rerun `nix-pull-prebuilts' to rescan the prebuilt source locations. + +print "running nix-pull-prebuilts..."; +system "nix-pull-prebuilts"; diff --git a/scripts/nix-switch b/scripts/nix-switch index 570f7e9e9..d58a5f249 100755 --- a/scripts/nix-switch +++ b/scripts/nix-switch @@ -30,7 +30,7 @@ while (-e "$linkdir/$id-$nr") { $nr++; } my $link = "$linkdir/$id-$nr"; # Create a symlink from $link to $pkgdir. -symlink($pkgdir, $link) or die "cannot create $link"; +symlink($pkgdir, $link) or die "cannot create $link: $!"; # Also store the hash of $pkgdir. This is useful for garbage # collection and the like. diff --git a/src/fix.cc b/src/fix.cc index 286b552c3..b26f8eb59 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -23,15 +23,24 @@ static bool verbose = false; typedef map DescriptorMap; -/* Forward declarations. */ - void registerFile(string filename) { - int res = system(("nix regfile " + filename).c_str()); + int res = system(("nix regfile " + filename).c_str()); + /* !!! escape */ if (WEXITSTATUS(res) != 0) throw Error("cannot register " + filename + " with Nix"); } + +void registerURL(string hash, string url) +{ + int res = system(("nix regurl " + hash + " " + url).c_str()); + /* !!! escape */ + if (WEXITSTATUS(res) != 0) + throw Error("cannot register " + hash + " -> " + url + " with Nix"); +} + + Error badTerm(const string & msg, ATerm e) { char * s = ATwriteToString(e); @@ -152,6 +161,7 @@ ATerm evaluate(ATerm e, EvalContext ctx) else if (ATmatch(e, "Local()", &e2)) { string filename = absPath(evaluateStr(e2, ctx), ctx.dir); /* !!! */ string hash = hashFile(filename); + registerFile(filename); /* !!! */ return ATmake("File()", hash.c_str()); } @@ -161,12 +171,7 @@ ATerm evaluate(ATerm e, EvalContext ctx) string hash = evaluateStr(e2, ctx); checkHash(hash); string url = evaluateStr(e3, ctx); -#if 0 - if (verbose) - cerr << "fetching " << url << endl; - string filename = fetchURL(url); -#endif - /* !!! register */ + registerURL(hash, url); return ATmake("File()", hash.c_str()); } diff --git a/src/nix.cc b/src/nix.cc index cfe879952..38643e3d6 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -26,6 +26,7 @@ using namespace std; static string dbRefs = "refs"; static string dbInstPkgs = "pkginst"; static string dbPrebuilts = "prebuilts"; +static string dbNetSources = "netsources"; static string nixSourcesDir; @@ -116,6 +117,65 @@ void enumDB(const string & dbname, DBPairs & contents) } +/* Download object referenced by the given URL into the sources + directory. Return the file name it was downloaded to. */ +string fetchURL(string url) +{ + string filename = baseNameOf(url); + string fullname = nixSourcesDir + "/" + filename; + struct stat st; + if (stat(fullname.c_str(), &st)) { + cerr << "fetching " << url << endl; + /* !!! quoting */ + string shellCmd = + "cd " + nixSourcesDir + " && wget --quiet -N \"" + url + "\""; + int res = system(shellCmd.c_str()); + if (WEXITSTATUS(res) != 0) + throw Error("cannot fetch " + url); + } + return fullname; +} + + +/* Obtain an object with the given hash. If a file with that hash is + known to exist in the local file system (as indicated by the dbRefs + database), we use that. Otherwise, we attempt to fetch it from the + network (using dbNetSources). We verify that the file has the + right hash. */ +string getFile(string hash) +{ + bool checkedNet = false; + + while (1) { + + string fn, url; + + if (queryDB(dbRefs, hash, fn)) { + + /* Verify that the file hasn't changed. !!! race */ + if (hashFile(fn) != hash) + throw Error("file " + fn + " is stale"); + + return fn; + } + + if (checkedNet) + throw Error("consistency problem: file fetched from " + url + + " should have hash " + hash + ", but it doesn't"); + + if (!queryDB(dbNetSources, hash, url)) + throw Error("a file with hash " + hash + " is requested, " + "but it is not known to exist locally or on the network"); + + checkedNet = true; + + fn = fetchURL(url); + + setDB(dbRefs, hash, fn); + } +} + + typedef map Params; @@ -124,14 +184,7 @@ void readPkgDescr(const string & hash, { string pkgfile; - if (!queryDB(dbRefs, hash, pkgfile)) - throw Error("unknown package " + hash); - - // cerr << "reading information about " + hash + " from " + pkgfile + "\n"; - - /* Verify that the file hasn't changed. !!! race */ - if (hashFile(pkgfile) != hash) - throw Error("file " + pkgfile + " is stale"); + pkgfile = getFile(hash); ATerm term = ATreadFromNamedFile(pkgfile.c_str()); if (!term) throw Error("cannot read aterm " + pkgfile); @@ -199,11 +252,7 @@ void fetchDeps(string hash, Environment & env) string file; - if (!queryDB(dbRefs, it->second, file)) - throw Error("unknown file " + it->second); - - if (hashFile(file) != it->second) - throw Error("file " + file + " is stale"); + file = getFile(it->second); env[it->first] = file; } @@ -283,17 +332,18 @@ void installPkg(string hash) /* Try to use a prebuilt. */ string prebuiltHash, prebuiltFile; - if (queryDB(dbPrebuilts, hash, prebuiltHash) && - queryDB(dbRefs, prebuiltHash, prebuiltFile)) - { - cerr << "substituting prebuilt " << prebuiltFile << endl; + if (queryDB(dbPrebuilts, hash, prebuiltHash)) { - if (hashFile(prebuiltFile) != prebuiltHash) { - cerr << "prebuilt " + prebuiltFile + " is stale\n"; + try { + prebuiltFile = getFile(prebuiltHash); + } catch (Error e) { + cerr << "cannot obtain prebuilt (ignoring): " << e.what() << endl; goto build; } + + cerr << "substituting prebuilt " << prebuiltFile << endl; - int res = system(("tar xvfj " + prebuiltFile).c_str()); // !!! escaping + int res = system(("tar xfj " + prebuiltFile + " 1>&2").c_str()); // !!! escaping if (WEXITSTATUS(res) != 0) /* This is a fatal error, because path may now have clobbered. */ @@ -302,6 +352,8 @@ void installPkg(string hash) _exit(0); } + throw Error("no prebuilt available"); + build: /* Fill in the environment. We don't bother freeing the @@ -453,7 +505,7 @@ void exportPkgs(string outDir, } -void regPrebuilt(string pkgHash, string prebuiltHash) +void registerPrebuilt(string pkgHash, string prebuiltHash) { checkHash(pkgHash); checkHash(prebuiltHash); @@ -470,6 +522,14 @@ string registerFile(string filename) } +void registerURL(string hash, string url) +{ + checkHash(hash); + setDB(dbNetSources, hash, url); + /* !!! currently we allow only one network source per hash */ +} + + /* This is primarily used for bootstrapping. */ void registerInstalledPkg(string hash, string path) { @@ -486,6 +546,7 @@ void initDB() openDB(dbRefs, false); openDB(dbInstPkgs, false); openDB(dbPrebuilts, false); + openDB(dbNetSources, false); } @@ -623,25 +684,6 @@ void printGraph(Strings::iterator first, Strings::iterator last) } -/* Download object referenced by the given URL into the sources - directory. Return the file name it was downloaded to. */ -string fetchURL(string url) -{ - string filename = baseNameOf(url); - string fullname = nixSourcesDir + "/" + filename; - struct stat st; - if (stat(fullname.c_str(), &st)) { - /* !!! quoting */ - string shellCmd = - "cd " + nixSourcesDir + " && wget --quiet -N \"" + url + "\""; - int res = system(shellCmd.c_str()); - if (WEXITSTATUS(res) != 0) - throw Error("cannot fetch " + url); - } - return fullname; -} - - void fetch(string id) { string fn; @@ -777,9 +819,11 @@ void run(Strings::iterator argCur, Strings::iterator argEnd) exportPkgs(*argCur, argCur + 1, argEnd); } else if (cmd == "regprebuilt") { if (argc != 2) throw argcError; - regPrebuilt(*argCur, argCur[1]); + registerPrebuilt(*argCur, argCur[1]); } else if (cmd == "regfile") { for_each(argCur, argEnd, registerFile); + } else if (cmd == "regurl") { + registerURL(argCur[0], argCur[1]); } else if (cmd == "reginst") { if (argc != 2) throw argcError; registerInstalledPkg(*argCur, argCur[1]); From 8b930a0c94ba9013d015c735cfc38c40b151f491 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 26 May 2003 13:45:00 +0000 Subject: [PATCH 0056/6440] * Some refactoring. --- pkg/aterm-2.0-build.sh | 13 ---- pkg/aterm-2.0.nix | 12 ---- src/Makefile.am | 8 +-- src/db.cc | 116 +++++++++++++++++++++++++++++++++ src/db.hh | 26 ++++++++ src/nix.cc | 142 ++++++++--------------------------------- src/util.cc | 94 +++++++++++++++++++++++++++ src/util.hh | 100 +++-------------------------- 8 files changed, 276 insertions(+), 235 deletions(-) delete mode 100755 pkg/aterm-2.0-build.sh delete mode 100644 pkg/aterm-2.0.nix create mode 100644 src/db.cc create mode 100644 src/db.hh create mode 100644 src/util.cc diff --git a/pkg/aterm-2.0-build.sh b/pkg/aterm-2.0-build.sh deleted file mode 100755 index 872e7aac8..000000000 --- a/pkg/aterm-2.0-build.sh +++ /dev/null @@ -1,13 +0,0 @@ -#! /bin/sh - -export PATH=$sys1/bin:$sys2/bin -export LIBRARY_PATH=$glibc/lib -export CC=$gcc/bin/gcc -export CFLAGS="-isystem $glibc/include -isystem $kernel/include" - -top=`pwd` -tar xvfz $src -cd aterm-2.0 -./configure --prefix=$top -make -make install diff --git a/pkg/aterm-2.0.nix b/pkg/aterm-2.0.nix deleted file mode 100644 index da2494ad8..000000000 --- a/pkg/aterm-2.0.nix +++ /dev/null @@ -1,12 +0,0 @@ -# Dependencies. -sys1 <- 1e80cb7e0fbfc9f5c0509a465ecdf6cf # sys1-bootstrap -sys2 <- 7512824c50c61ea8d89d0f193a4f72d1 # sys2-bootstrap -gcc <- 02212b3dc4e50349376975367d433929 # gcc-bootstrap -glibc <- c0ce03ee0bab298babbe7e3b6159d36c # glibc-bootstrap -kernel <- 3dc8333a2c2b4d627b892755417acf89 # kernel-bootstrap - -# Original sources. -src = 853474e4bcf4a85f7d38a0676b36bded # aterm-2.0.tar.gz - -# Build script. -build = ee7ae4ade69f846d2385871bf532ef7e # aterm-2.0-build.sh diff --git a/src/Makefile.am b/src/Makefile.am index 31fad8925..ffbaaeb83 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,11 +1,11 @@ bin_PROGRAMS = nix fix -nix_SOURCES = nix.cc md5.c -nix_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall +CXXFLAGS = -DSYSTEM=\"@host@\" -Wall + +nix_SOURCES = nix.cc db.cc util.cc md5.c nix_LDADD = -ldb_cxx-4 -lATerm -fix_SOURCES = fix.cc md5.c -fix_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall +fix_SOURCES = fix.cc util.cc md5.c fix_LDADD = -lATerm install-data-local: diff --git a/src/db.cc b/src/db.cc new file mode 100644 index 000000000..e9f3a0f9e --- /dev/null +++ b/src/db.cc @@ -0,0 +1,116 @@ +#include "db.hh" +#include "util.hh" + +#include + + +/* Wrapper classes that ensures that the database is closed upon + object destruction. */ +class Db2 : public Db +{ +public: + Db2(DbEnv *env, u_int32_t flags) : Db(env, flags) { } + ~Db2() { close(0); } +}; + + +class DbcClose +{ + Dbc * cursor; +public: + DbcClose(Dbc * c) : cursor(c) { } + ~DbcClose() { cursor->close(); } +}; + + +static auto_ptr openDB(const string & filename, const string & dbname, + bool readonly) +{ + auto_ptr db(new Db2(0, 0)); + + db->open(filename.c_str(), dbname.c_str(), + DB_HASH, readonly ? DB_RDONLY : DB_CREATE, 0666); + + return db; +} + + +static void rethrow(DbException & e) +{ + throw Error(e.what()); +} + + +void createDB(const string & filename, const string & dbname) +{ + try { + openDB(filename, dbname, false); + } catch (DbException e) { rethrow(e); } +} + + +bool queryDB(const string & filename, const string & dbname, + const string & key, string & data) +{ + try { + + int err; + auto_ptr db = openDB(filename, dbname, true); + + Dbt kt((void *) key.c_str(), key.length()); + Dbt dt; + + err = db->get(0, &kt, &dt, 0); + if (err) return false; + + data = string((char *) dt.get_data(), dt.get_size()); + + } catch (DbException e) { rethrow(e); } + + return true; +} + + +void setDB(const string & filename, const string & dbname, + const string & key, const string & data) +{ + try { + auto_ptr db = openDB(filename, dbname, false); + Dbt kt((void *) key.c_str(), key.length()); + Dbt dt((void *) data.c_str(), data.length()); + db->put(0, &kt, &dt, 0); + } catch (DbException e) { rethrow(e); } +} + + +void delDB(const string & filename, const string & dbname, + const string & key) +{ + try { + auto_ptr db = openDB(filename, dbname, false); + Dbt kt((void *) key.c_str(), key.length()); + db->del(0, &kt, 0); + } catch (DbException e) { rethrow(e); } +} + + +void enumDB(const string & filename, const string & dbname, + DBPairs & contents) +{ + try { + + auto_ptr db = openDB(filename, dbname, false); + + Dbc * cursor; + db->cursor(0, &cursor, 0); + DbcClose cursorCloser(cursor); + + Dbt kt, dt; + while (cursor->get(&kt, &dt, DB_NEXT) != DB_NOTFOUND) { + string key((char *) kt.get_data(), kt.get_size()); + string data((char *) dt.get_data(), dt.get_size()); + contents.push_back(DBPair(key, data)); + } + + } catch (DbException e) { rethrow(e); } +} diff --git a/src/db.hh b/src/db.hh new file mode 100644 index 000000000..0054dbec1 --- /dev/null +++ b/src/db.hh @@ -0,0 +1,26 @@ +#ifndef __DB_H +#define __DB_H + +#include +#include + +using namespace std; + +typedef pair DBPair; +typedef list DBPairs; + +void createDB(const string & filename, const string & dbname); + +bool queryDB(const string & filename, const string & dbname, + const string & key, string & data); + +void setDB(const string & filename, const string & dbname, + const string & key, const string & data); + +void delDB(const string & filename, const string & dbname, + const string & key); + +void enumDB(const string & filename, const string & dbname, + DBPairs & contents); + +#endif /* !__DB_H */ diff --git a/src/nix.cc b/src/nix.cc index 38643e3d6..5904bf82d 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -11,13 +11,12 @@ #include #include -#include - extern "C" { #include } #include "util.hh" +#include "db.hh" using namespace std; @@ -30,91 +29,7 @@ static string dbNetSources = "netsources"; static string nixSourcesDir; - - -/* Wrapper classes that ensures that the database is closed upon - object destruction. */ -class Db2 : public Db -{ -public: - Db2(DbEnv *env, u_int32_t flags) : Db(env, flags) { } - ~Db2() { close(0); } -}; - - -class DbcClose -{ - Dbc * cursor; -public: - DbcClose(Dbc * c) : cursor(c) { } - ~DbcClose() { cursor->close(); } -}; - - -auto_ptr openDB(const string & dbname, bool readonly) -{ - auto_ptr db(new Db2(0, 0)); - - db->open((nixHomeDir + "/var/nix/pkginfo.db").c_str(), dbname.c_str(), - DB_HASH, readonly ? DB_RDONLY : DB_CREATE, 0666); - - return db; -} - - -bool queryDB(const string & dbname, const string & key, string & data) -{ - int err; - auto_ptr db = openDB(dbname, true); - - Dbt kt((void *) key.c_str(), key.length()); - Dbt dt; - - err = db->get(0, &kt, &dt, 0); - if (err) return false; - - data = string((char *) dt.get_data(), dt.get_size()); - - return true; -} - - -void setDB(const string & dbname, const string & key, const string & data) -{ - auto_ptr db = openDB(dbname, false); - Dbt kt((void *) key.c_str(), key.length()); - Dbt dt((void *) data.c_str(), data.length()); - db->put(0, &kt, &dt, 0); -} - - -void delDB(const string & dbname, const string & key) -{ - auto_ptr db = openDB(dbname, false); - Dbt kt((void *) key.c_str(), key.length()); - db->del(0, &kt, 0); -} - - -typedef pair DBPair; -typedef list DBPairs; - - -void enumDB(const string & dbname, DBPairs & contents) -{ - auto_ptr db = openDB(dbname, false); - - Dbc * cursor; - db->cursor(0, &cursor, 0); - DbcClose cursorCloser(cursor); - - Dbt kt, dt; - while (cursor->get(&kt, &dt, DB_NEXT) != DB_NOTFOUND) { - string key((char *) kt.get_data(), kt.get_size()); - string data((char *) dt.get_data(), dt.get_size()); - contents.push_back(DBPair(key, data)); - } -} +static string nixDB; /* Download object referenced by the given URL into the sources @@ -150,7 +65,7 @@ string getFile(string hash) string fn, url; - if (queryDB(dbRefs, hash, fn)) { + if (queryDB(nixDB, dbRefs, hash, fn)) { /* Verify that the file hasn't changed. !!! race */ if (hashFile(fn) != hash) @@ -163,7 +78,7 @@ string getFile(string hash) throw Error("consistency problem: file fetched from " + url + " should have hash " + hash + ", but it doesn't"); - if (!queryDB(dbNetSources, hash, url)) + if (!queryDB(nixDB, dbNetSources, hash, url)) throw Error("a file with hash " + hash + " is requested, " "but it is not known to exist locally or on the network"); @@ -171,7 +86,7 @@ string getFile(string hash) fn = fetchURL(url); - setDB(dbRefs, hash, fn); + setDB(nixDB, dbRefs, hash, fn); } } @@ -332,7 +247,7 @@ void installPkg(string hash) /* Try to use a prebuilt. */ string prebuiltHash, prebuiltFile; - if (queryDB(dbPrebuilts, hash, prebuiltHash)) { + if (queryDB(nixDB, dbPrebuilts, hash, prebuiltHash)) { try { prebuiltFile = getFile(prebuiltHash); @@ -398,7 +313,7 @@ build: throw; } - setDB(dbInstPkgs, hash, path); + setDB(nixDB, dbInstPkgs, hash, path); } @@ -406,7 +321,7 @@ string getPkg(string hash) { string path; checkHash(hash); - while (!queryDB(dbInstPkgs, hash, path)) + while (!queryDB(nixDB, dbInstPkgs, hash, path)) installPkg(hash); return path; } @@ -470,9 +385,9 @@ void delPkg(string hash) { string path; checkHash(hash); - if (queryDB(dbInstPkgs, hash, path)) { + if (queryDB(nixDB, dbInstPkgs, hash, path)) { int res = system(("chmod -R +w " + path + " && rm -rf " + path).c_str()); // !!! escaping - delDB(dbInstPkgs, hash); // not a bug ??? + delDB(nixDB, dbInstPkgs, hash); // not a bug ??? if (WEXITSTATUS(res) != 0) cerr << "errors deleting " + path + ", ignoring" << endl; } @@ -509,7 +424,7 @@ void registerPrebuilt(string pkgHash, string prebuiltHash) { checkHash(pkgHash); checkHash(prebuiltHash); - setDB(dbPrebuilts, pkgHash, prebuiltHash); + setDB(nixDB, dbPrebuilts, pkgHash, prebuiltHash); } @@ -517,7 +432,7 @@ string registerFile(string filename) { filename = absPath(filename); string hash = hashFile(filename); - setDB(dbRefs, hash, filename); + setDB(nixDB, dbRefs, hash, filename); return hash; } @@ -525,7 +440,7 @@ string registerFile(string filename) void registerURL(string hash, string url) { checkHash(hash); - setDB(dbNetSources, hash, url); + setDB(nixDB, dbNetSources, hash, url); /* !!! currently we allow only one network source per hash */ } @@ -535,18 +450,18 @@ void registerInstalledPkg(string hash, string path) { checkHash(hash); if (path == "") - delDB(dbInstPkgs, hash); + delDB(nixDB, dbInstPkgs, hash); else - setDB(dbInstPkgs, hash, path); + setDB(nixDB, dbInstPkgs, hash, path); } void initDB() { - openDB(dbRefs, false); - openDB(dbInstPkgs, false); - openDB(dbPrebuilts, false); - openDB(dbNetSources, false); + createDB(nixDB, dbRefs); + createDB(nixDB, dbInstPkgs); + createDB(nixDB, dbPrebuilts); + createDB(nixDB, dbNetSources); } @@ -555,7 +470,7 @@ void verifyDB() /* Check that all file references are still valid. */ DBPairs fileRefs; - enumDB(dbRefs, fileRefs); + enumDB(nixDB, dbRefs, fileRefs); for (DBPairs::iterator it = fileRefs.begin(); it != fileRefs.end(); it++) @@ -563,18 +478,18 @@ void verifyDB() try { if (hashFile(it->second) != it->first) { cerr << "file " << it->second << " has changed\n"; - delDB(dbRefs, it->first); + delDB(nixDB, dbRefs, it->first); } } catch (BadRefError e) { /* !!! better error check */ cerr << "file " << it->second << " has disappeared\n"; - delDB(dbRefs, it->first); + delDB(nixDB, dbRefs, it->first); } } /* Check that all installed packages are still there. */ DBPairs instPkgs; - enumDB(dbInstPkgs, instPkgs); + enumDB(nixDB, dbInstPkgs, instPkgs); for (DBPairs::iterator it = instPkgs.begin(); it != instPkgs.end(); it++) @@ -582,7 +497,7 @@ void verifyDB() struct stat st; if (stat(it->second.c_str(), &st) == -1) { cerr << "package " << it->first << " has disappeared\n"; - delDB(dbInstPkgs, it->first); + delDB(nixDB, dbInstPkgs, it->first); } } @@ -595,7 +510,7 @@ void listInstalledPkgs() { DBPairs instPkgs; - enumDB(dbInstPkgs, instPkgs); + enumDB(nixDB, dbInstPkgs, instPkgs); for (DBPairs::iterator it = instPkgs.begin(); it != instPkgs.end(); it++) @@ -777,6 +692,7 @@ void run(Strings::iterator argCur, Strings::iterator argEnd) if (homeDir) nixHomeDir = homeDir; nixSourcesDir = nixHomeDir + "/var/nix/sources"; + nixDB = nixHomeDir + "/var/nix/pkginfo.db"; /* Parse the global flags. */ for ( ; argCur != argEnd; argCur++) { @@ -856,11 +772,7 @@ int main(int argc, char * * argv) argCur++; try { - try { - run(argCur, argEnd); - } catch (DbException e) { - throw Error(e.what()); - } + run(argCur, argEnd); } catch (UsageError & e) { cerr << "error: " << e.what() << endl << "Try `nix -h' for more information.\n"; diff --git a/src/util.cc b/src/util.cc new file mode 100644 index 000000000..4b7bbac3d --- /dev/null +++ b/src/util.cc @@ -0,0 +1,94 @@ +#include "util.hh" + + +string thisSystem = SYSTEM; +string nixHomeDir = "/nix"; +string nixHomeDirEnvVar = "NIX"; + + + +string absPath(string filename, string dir) +{ + if (filename[0] != '/') { + if (dir == "") { + char buf[PATH_MAX]; + if (!getcwd(buf, sizeof(buf))) + throw Error("cannot get cwd"); + dir = buf; + } + filename = dir + "/" + filename; + /* !!! canonicalise */ + char resolved[PATH_MAX]; + if (!realpath(filename.c_str(), resolved)) + throw Error("cannot canonicalise path " + filename); + filename = resolved; + } + return filename; +} + + +static string printHash(unsigned char * buf) +{ + ostringstream str; + for (int i = 0; i < 16; i++) { + str.fill('0'); + str.width(2); + str << hex << (int) buf[i]; + } + return str.str(); +} + + +/* Verify that a reference is valid (that is, is a MD5 hash code). */ +bool isHash(const string & s) +{ + if (s.length() != 32) return false; + for (int i = 0; i < 32; i++) { + char c = s[i]; + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f'))) + return false; + } + return true; +} + + +void checkHash(const string & s) +{ + if (!isHash(s)) throw BadRefError("invalid reference: " + s); +} + + +/* Compute the MD5 hash of a file. */ +string hashFile(string filename) +{ + unsigned char hash[16]; + FILE * file = fopen(filename.c_str(), "rb"); + if (!file) + throw BadRefError("file `" + filename + "' does not exist"); + int err = md5_stream(file, hash); + fclose(file); + if (err) throw BadRefError("cannot hash file"); + return printHash(hash); +} + + + +/* Return the directory part of the given path, i.e., everything + before the final `/'. */ +string dirOf(string s) +{ + unsigned int pos = s.rfind('/'); + if (pos == string::npos) throw Error("invalid file name"); + return string(s, 0, pos); +} + + +/* Return the base name of the given path, i.e., everything following + the final `/'. */ +string baseNameOf(string s) +{ + unsigned int pos = s.rfind('/'); + if (pos == string::npos) throw Error("invalid file name"); + return string(s, pos + 1); +} diff --git a/src/util.hh b/src/util.hh index 9b3f212de..2c09efc4d 100644 --- a/src/util.hh +++ b/src/util.hh @@ -39,104 +39,22 @@ public: typedef vector Strings; -/* !!! the following shouldn't be here; abuse of the preprocessor */ - - /* The canonical system name, as returned by config.guess. */ -static string thisSystem = SYSTEM; +extern string thisSystem; /* The prefix of the Nix installation, and the environment variable that can be used to override the default. */ -static string nixHomeDir = "/nix"; -static string nixHomeDirEnvVar = "NIX"; +extern string nixHomeDir; +extern string nixHomeDirEnvVar; -string absPath(string filename, string dir = "") -{ - if (filename[0] != '/') { - if (dir == "") { - char buf[PATH_MAX]; - if (!getcwd(buf, sizeof(buf))) - throw Error("cannot get cwd"); - dir = buf; - } - filename = dir + "/" + filename; - /* !!! canonicalise */ - char resolved[PATH_MAX]; - if (!realpath(filename.c_str(), resolved)) - throw Error("cannot canonicalise path " + filename); - filename = resolved; - } - return filename; -} - - -string printHash(unsigned char * buf) -{ - ostringstream str; - for (int i = 0; i < 16; i++) { - str.fill('0'); - str.width(2); - str << hex << (int) buf[i]; - } - return str.str(); -} - - -/* Verify that a reference is valid (that is, is a MD5 hash code). */ -bool isHash(const string & s) -{ - if (s.length() != 32) return false; - for (int i = 0; i < 32; i++) { - char c = s[i]; - if (!((c >= '0' && c <= '9') || - (c >= 'a' && c <= 'f'))) - return false; - } - return true; -} - - -void checkHash(const string & s) -{ - if (!isHash(s)) throw BadRefError("invalid reference: " + s); -} - - -/* Compute the MD5 hash of a file. */ -string hashFile(string filename) -{ - unsigned char hash[16]; - FILE * file = fopen(filename.c_str(), "rb"); - if (!file) - throw BadRefError("file `" + filename + "' does not exist"); - int err = md5_stream(file, hash); - fclose(file); - if (err) throw BadRefError("cannot hash file"); - return printHash(hash); -} - - - -/* Return the directory part of the given path, i.e., everything - before the final `/'. */ -string dirOf(string s) -{ - unsigned int pos = s.rfind('/'); - if (pos == string::npos) throw Error("invalid file name"); - return string(s, 0, pos); -} - - -/* Return the base name of the given path, i.e., everything following - the final `/'. */ -string baseNameOf(string s) -{ - unsigned int pos = s.rfind('/'); - if (pos == string::npos) throw Error("invalid file name"); - return string(s, pos + 1); -} +string absPath(string filename, string dir = ""); +bool isHash(const string & s); +void checkHash(const string & s); +string hashFile(string filename); +string dirOf(string s); +string baseNameOf(string s); #endif /* !__UTIL_H */ From a9f2928ed6edb15faa1ad5fc563662a08a92ced1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 26 May 2003 13:57:19 +0000 Subject: [PATCH 0057/6440] * Moved the package descriptors and build scripts out of the Nix tree. --- test/build/aterm-build.sh | 12 ----------- test/build/atk-build.sh | 14 ------------ test/build/glib-build.sh | 12 ----------- test/build/gnet-build.sh | 14 ------------ test/build/gtk+-build.sh | 14 ------------ test/build/gtkspell-build.sh | 15 ------------- test/build/httpd-build.sh | 12 ----------- test/build/openssl-build.sh | 12 ----------- test/build/pan-build-2.sh | 20 ----------------- test/build/pan-build.sh | 12 ----------- test/build/pan-run.sh | 10 --------- test/build/pango-build.sh | 14 ------------ test/build/pkgconfig-build.sh | 12 ----------- test/build/pspell-build.sh | 12 ----------- test/build/subversion-build.sh | 26 ----------------------- test/fixdescriptors/aterm-2.0.fix | 10 --------- test/fixdescriptors/atk-1.2.0.fix | 11 ---------- test/fixdescriptors/glib-2.2.1.fix | 10 --------- test/fixdescriptors/gnet-1.1.8.fix | 11 ---------- test/fixdescriptors/gtk+-2.2.1.fix | 13 ------------ test/fixdescriptors/gtkspell-2.0.2.fix | 15 ------------- test/fixdescriptors/httpd-2.0.45.fix | 10 --------- test/fixdescriptors/openssl-0.9.7b.fix | 8 ------- test/fixdescriptors/pan-0.14.0.fix | 17 --------------- test/fixdescriptors/pango-1.2.1.fix | 11 ---------- test/fixdescriptors/pkgconfig-0.15.0.fix | 8 ------- test/fixdescriptors/pspell-.12.2.fix | 8 ------- test/fixdescriptors/subversion-0.21.0.fix | 16 -------------- test/fixdescriptors/system.fix | 19 ----------------- 29 files changed, 378 deletions(-) delete mode 100755 test/build/aterm-build.sh delete mode 100755 test/build/atk-build.sh delete mode 100755 test/build/glib-build.sh delete mode 100755 test/build/gnet-build.sh delete mode 100755 test/build/gtk+-build.sh delete mode 100755 test/build/gtkspell-build.sh delete mode 100755 test/build/httpd-build.sh delete mode 100755 test/build/openssl-build.sh delete mode 100755 test/build/pan-build-2.sh delete mode 100755 test/build/pan-build.sh delete mode 100755 test/build/pan-run.sh delete mode 100755 test/build/pango-build.sh delete mode 100755 test/build/pkgconfig-build.sh delete mode 100755 test/build/pspell-build.sh delete mode 100755 test/build/subversion-build.sh delete mode 100644 test/fixdescriptors/aterm-2.0.fix delete mode 100644 test/fixdescriptors/atk-1.2.0.fix delete mode 100644 test/fixdescriptors/glib-2.2.1.fix delete mode 100644 test/fixdescriptors/gnet-1.1.8.fix delete mode 100644 test/fixdescriptors/gtk+-2.2.1.fix delete mode 100644 test/fixdescriptors/gtkspell-2.0.2.fix delete mode 100644 test/fixdescriptors/httpd-2.0.45.fix delete mode 100644 test/fixdescriptors/openssl-0.9.7b.fix delete mode 100644 test/fixdescriptors/pan-0.14.0.fix delete mode 100644 test/fixdescriptors/pango-1.2.1.fix delete mode 100644 test/fixdescriptors/pkgconfig-0.15.0.fix delete mode 100644 test/fixdescriptors/pspell-.12.2.fix delete mode 100644 test/fixdescriptors/subversion-0.21.0.fix delete mode 100644 test/fixdescriptors/system.fix diff --git a/test/build/aterm-build.sh b/test/build/aterm-build.sh deleted file mode 100755 index f8cca71aa..000000000 --- a/test/build/aterm-build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/sh - -export PATH=/bin:/usr/bin - -top=`pwd` -tar xvfz $src -cd aterm-* -./configure --prefix=$top -make -make install -cd .. -rm -rf aterm-* diff --git a/test/build/atk-build.sh b/test/build/atk-build.sh deleted file mode 100755 index 632dbe12f..000000000 --- a/test/build/atk-build.sh +++ /dev/null @@ -1,14 +0,0 @@ -#! /bin/sh - -export PATH=$pkgconfig/bin:/bin:/usr/bin -export PKG_CONFIG_PATH=$glib/lib/pkgconfig -export LD_LIBRARY_PATH=$glib/lib - -top=`pwd` || exit 1 -tar xvfj $src || exit 1 -cd atk-* || exit 1 -./configure --prefix=$top || exit 1 -make || exit 1 -make install || exit 1 -cd $top || exit 1 -rm -rf atk-* || exit 1 diff --git a/test/build/glib-build.sh b/test/build/glib-build.sh deleted file mode 100755 index 92736f701..000000000 --- a/test/build/glib-build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/sh - -export PATH=$pkgconfig/bin:/bin:/usr/bin - -top=`pwd` -tar xvfj $src -cd glib-* -./configure --prefix=$top -make -make install -cd .. -rm -rf glib-* diff --git a/test/build/gnet-build.sh b/test/build/gnet-build.sh deleted file mode 100755 index 72141c268..000000000 --- a/test/build/gnet-build.sh +++ /dev/null @@ -1,14 +0,0 @@ -#! /bin/sh - -export PATH=$pkgconfig/bin:/bin:/usr/bin -export PKG_CONFIG_PATH=$glib/lib/pkgconfig -export LD_LIBRARY_PATH=$glib/lib - -top=`pwd` -tar xvfz $src || exit 1 -cd gnet-* || exit 1 -./configure --prefix=$top || exit 1 -make || exit 1 -make install || exit 1 -cd $top || exit 1 -rm -rf gnet-* || exit 1 diff --git a/test/build/gtk+-build.sh b/test/build/gtk+-build.sh deleted file mode 100755 index 3b663fec5..000000000 --- a/test/build/gtk+-build.sh +++ /dev/null @@ -1,14 +0,0 @@ -#! /bin/sh - -export PATH=$pkgconfig/bin:/bin:/usr/bin -export PKG_CONFIG_PATH=$glib/lib/pkgconfig:$atk/lib/pkgconfig:$pango/lib/pkgconfig -export LD_LIBRARY_PATH=$glib/lib:$atk/lib:$pango/lib - -top=`pwd` -tar xvfj $src || exit 1 -cd gtk+-* || exit 1 -./configure --prefix=$top || exit 1 -make || exit 1 -make install || exit 1 -cd $top || exit 1 -rm -rf gtk+-* || exit 1 diff --git a/test/build/gtkspell-build.sh b/test/build/gtkspell-build.sh deleted file mode 100755 index d4267b302..000000000 --- a/test/build/gtkspell-build.sh +++ /dev/null @@ -1,15 +0,0 @@ -#! /bin/sh - -export PATH=$pkgconfig/bin:/bin:/usr/bin -export PKG_CONFIG_PATH=$glib/lib/pkgconfig:$atk/lib/pkgconfig:$pango/lib/pkgconfig:$gtk/lib/pkgconfig -export LD_LIBRARY_PATH=$glib/lib:$atk/lib:$pango/lib:$gtk/lib:$pspell/lib -export C_INCLUDE_PATH=$pspell/include - -top=`pwd` -tar xvfz $src || exit 1 -cd gtkspell-* || exit 1 -./configure --prefix=$top || exit 1 -make || exit 1 -make install || exit 1 -cd $top || exit 1 -rm -rf gtkspell-* || exit 1 diff --git a/test/build/httpd-build.sh b/test/build/httpd-build.sh deleted file mode 100755 index a5b43d744..000000000 --- a/test/build/httpd-build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/sh - -export PATH=/bin:/usr/bin - -top=`pwd` -tar xvfz $src || exit 1 -cd httpd-* || exit 1 -./configure --prefix=$top --enable-ssl --with-ssl=$ssl --enable-mods-shared=all || exit 1 -make || exit 1 -make install || exit 1 -cd $top || exit 1 -rm -rf httpd-* || exit 1 diff --git a/test/build/openssl-build.sh b/test/build/openssl-build.sh deleted file mode 100755 index 23437a37b..000000000 --- a/test/build/openssl-build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/sh - -export PATH=/bin:/usr/bin - -top=`pwd` -tar xvfz $src || exit 1 -cd openssl-* || exit 1 -./config --prefix=$top || exit 1 -make || exit 1 -make install || exit 1 -cd $top || exit 1 -rm -rf openssl-* || exit 1 diff --git a/test/build/pan-build-2.sh b/test/build/pan-build-2.sh deleted file mode 100755 index 8320cb010..000000000 --- a/test/build/pan-build-2.sh +++ /dev/null @@ -1,20 +0,0 @@ -#! /bin/sh - -export PATH=$pkgconfig/bin:$gnet/bin:/bin:/usr/bin -export PKG_CONFIG_PATH=$glib/lib/pkgconfig:$atk/lib/pkgconfig:$pango/lib/pkgconfig:$gtk/lib/pkgconfig:$gtkspell/lib/pkgconfig -export LD_LIBRARY_PATH=$glib/lib:$atk/lib:$pango/lib:$gtk/lib:$gnet/lib:$pspell/lib:$gtkspell/lib - -# A bug in gtkspell: the pspell library path is not exported -# through pkgconfig. -export LIBRARY_PATH=$pspell/lib - -export LDFLAGS=-s - -top=`pwd` -tar xvfj $src || exit 1 -cd pan-* || exit 1 -./configure --prefix=$top || exit 1 -make || exit 1 -make install || exit 1 -cd $top || exit 1 -rm -rf pan-* || exit 1 diff --git a/test/build/pan-build.sh b/test/build/pan-build.sh deleted file mode 100755 index 907215f37..000000000 --- a/test/build/pan-build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/sh - -export PATH=$pkgconfig/bin:$gnet/bin:/bin:/usr/bin -export PKG_CONFIG_PATH=$glib/lib/pkgconfig:$atk/lib/pkgconfig:$pango/lib/pkgconfig:$gtk/lib/pkgconfig -export LD_LIBRARY_PATH=$glib/lib:$atk/lib:$pango/lib:$gtk/lib:$gnet/lib - -top=`pwd` -tar xvfj $src -cd pan-* -./configure --prefix=$top -make -make install diff --git a/test/build/pan-run.sh b/test/build/pan-run.sh deleted file mode 100755 index 1d9db5377..000000000 --- a/test/build/pan-run.sh +++ /dev/null @@ -1,10 +0,0 @@ -#! /bin/sh - -export LD_LIBRARY_PATH=$glib/lib:$atk/lib:$pango/lib:$gtk/lib:$gnet/lib:$pspell/lib:$gtkspell/lib - -ldd $pan/bin/pan - -prog=$1 -shift - -$pan/bin/$prog $* diff --git a/test/build/pango-build.sh b/test/build/pango-build.sh deleted file mode 100755 index 42a275802..000000000 --- a/test/build/pango-build.sh +++ /dev/null @@ -1,14 +0,0 @@ -#! /bin/sh - -export PATH=$pkgconfig/bin:/bin:/usr/bin -export PKG_CONFIG_PATH=$glib/lib/pkgconfig -export LD_LIBRARY_PATH=$glib/lib - -top=`pwd` || exit 1 -tar xvfj $src || exit 1 -cd pango-* || exit 1 -./configure --prefix=$top || exit 1 -make || exit 1 -make install || exit 1 -cd $top || exit 1 -rm -rf pango-* || exit 1 diff --git a/test/build/pkgconfig-build.sh b/test/build/pkgconfig-build.sh deleted file mode 100755 index beceddfb6..000000000 --- a/test/build/pkgconfig-build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/sh - -export PATH=/bin:/usr/bin - -top=`pwd` -tar xvfz $src || exit 1 -cd pkgconfig-* || exit 1 -./configure --prefix=$top || exit 1 -make || exit 1 -make install || exit 1 -cd .. || exit 1 -rm -rf pkgconfig-* || exit 1 diff --git a/test/build/pspell-build.sh b/test/build/pspell-build.sh deleted file mode 100755 index 862bb25e6..000000000 --- a/test/build/pspell-build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/sh - -export PATH=/bin:/usr/bin - -top=`pwd` -tar xvfz $src || exit 1 -cd pspell-* || exit 1 -./configure --prefix=$top || exit 1 -make || exit 1 -make install || exit 1 -cd $top || exit 1 -rm -rf pspell-* || exit 1 diff --git a/test/build/subversion-build.sh b/test/build/subversion-build.sh deleted file mode 100755 index 3d1922c98..000000000 --- a/test/build/subversion-build.sh +++ /dev/null @@ -1,26 +0,0 @@ -#! /bin/sh - -export PATH=/bin:/usr/bin - -export LDFLAGS=-s - -top=`pwd` - -if test $httpsClient; then - extraflags="--with-ssl --with-libs=$ssl $extraflags" -fi - -if test $httpServer; then - extraflags="--with-apxs=$httpd/bin/apxs --with-apr=$httpd --with-apr-util=$httpd $extraflags" - extrainst="APACHE_LIBEXECDIR=$top/modules $extrainst" -fi - -echo "extra flags: $extraflags" - -tar xvfz $src || exit 1 -cd subversion-* || exit 1 -./configure --prefix=$top $extraflags || exit 1 -make || exit 1 -make install $extrainst || exit 1 -cd $top || exit 1 -rm -rf subversion-* || exit 1 diff --git a/test/fixdescriptors/aterm-2.0.fix b/test/fixdescriptors/aterm-2.0.fix deleted file mode 100644 index 2fdf43434..000000000 --- a/test/fixdescriptors/aterm-2.0.fix +++ /dev/null @@ -1,10 +0,0 @@ -Descr( - [ Bind("pkgId", "aterm-2.0") - , Bind("releaseId", "1") - - , Bind("createGCC", True) - - , Bind("src", Url("853474e4bcf4a85f7d38a0676b36bded", "http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz")) - , Bind("build", Local("../build/aterm-build.sh")) - ] -) diff --git a/test/fixdescriptors/atk-1.2.0.fix b/test/fixdescriptors/atk-1.2.0.fix deleted file mode 100644 index 9f880be11..000000000 --- a/test/fixdescriptors/atk-1.2.0.fix +++ /dev/null @@ -1,11 +0,0 @@ -Descr( - [ Bind("pkgId", "atk-1.2.0") - , Bind("releaseId", "1") - - , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) - , Bind("glib", Fix("./glib-2.2.1.fix")) - - , Bind("src", Url("06a84758129554ae044af8865ecb6f1c", "ftp://ftp.gtk.org/pub/gtk/v2.2/atk-1.2.0.tar.bz2")) - , Bind("build", Local("../build/atk-build.sh")) - ] -) diff --git a/test/fixdescriptors/glib-2.2.1.fix b/test/fixdescriptors/glib-2.2.1.fix deleted file mode 100644 index 9dc0f4e29..000000000 --- a/test/fixdescriptors/glib-2.2.1.fix +++ /dev/null @@ -1,10 +0,0 @@ -Descr( - [ Bind("pkgId", "glib-2.2.1") - , Bind("releaseId", "1") - - , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) - - , Bind("src", Url("42406a17819080326e105f8333963b97", "ftp://ftp.gtk.org/pub/gtk/v2.2/glib-2.2.1.tar.bz2")) - , Bind("build", Local("../build/glib-build.sh")) - ] -) diff --git a/test/fixdescriptors/gnet-1.1.8.fix b/test/fixdescriptors/gnet-1.1.8.fix deleted file mode 100644 index ef1f1b1da..000000000 --- a/test/fixdescriptors/gnet-1.1.8.fix +++ /dev/null @@ -1,11 +0,0 @@ -Descr( - [ Bind("pkgId", "gnet-1.1.8") - , Bind("releaseId", "1") - - , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) - , Bind("glib", Fix("./glib-2.2.1.fix")) - - , Bind("src", Url("da2b5de278e96a5b907c2e2304bf6542", "http://www.gnetlibrary.org/src/gnet-1.1.8.tar.gz")) - , Bind("build", Local("../build/gnet-build.sh")) - ] -) diff --git a/test/fixdescriptors/gtk+-2.2.1.fix b/test/fixdescriptors/gtk+-2.2.1.fix deleted file mode 100644 index 9563df0a9..000000000 --- a/test/fixdescriptors/gtk+-2.2.1.fix +++ /dev/null @@ -1,13 +0,0 @@ -Descr( - [ Bind("pkgId", "gtk+-2.2.1") - , Bind("releaseId", "1") - - , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) - , Bind("glib", Fix("./glib-2.2.1.fix")) - , Bind("atk", Fix("./atk-1.2.0.fix")) - , Bind("pango", Fix("./pango-1.2.1.fix")) - - , Bind("src", Url("dfd5755fddb26a46c96bfaa813280ac4", "ftp://ftp.gtk.org/pub/gtk/v2.2/gtk+-2.2.1.tar.bz2")) - , Bind("build", Local("../build/gtk+-build.sh")) - ] -) diff --git a/test/fixdescriptors/gtkspell-2.0.2.fix b/test/fixdescriptors/gtkspell-2.0.2.fix deleted file mode 100644 index ef7e185c9..000000000 --- a/test/fixdescriptors/gtkspell-2.0.2.fix +++ /dev/null @@ -1,15 +0,0 @@ -Descr( - [ Bind("pkgId", "gtkspell-2.0.2") - , Bind("releaseId", "1") - - , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) - , Bind("glib", Fix("./glib-2.2.1.fix")) - , Bind("atk", Fix("./atk-1.2.0.fix")) - , Bind("pango", Fix("./pango-1.2.1.fix")) - , Bind("gtk", Fix("./gtk+-2.2.1.fix")) - , Bind("pspell", Fix("./pspell-.12.2.fix")) - - , Bind("src", Url("385daba9bebfdc7fdbdf524e07deb920", "http://pan.rebelbase.com/download/extras/gtkspell/SOURCES/gtkspell-2.0.2.tar.gz")) - , Bind("build", Local("../build/gtkspell-build.sh")) - ] -) diff --git a/test/fixdescriptors/httpd-2.0.45.fix b/test/fixdescriptors/httpd-2.0.45.fix deleted file mode 100644 index e0db5ab2e..000000000 --- a/test/fixdescriptors/httpd-2.0.45.fix +++ /dev/null @@ -1,10 +0,0 @@ -Descr( - [ Bind("pkgId", "httpd-2.0.45") - , Bind("releaseId", "1") - - , Bind("ssl", Fix("./openssl-0.9.7b.fix")) - - , Bind("src", Url("1f33e9a2e2de06da190230fa72738d75", "http://apache.cs.uu.nl/dist/httpd/httpd-2.0.45.tar.gz")) - , Bind("build", Local("../build/httpd-build.sh")) - ] -) diff --git a/test/fixdescriptors/openssl-0.9.7b.fix b/test/fixdescriptors/openssl-0.9.7b.fix deleted file mode 100644 index f55d0cca1..000000000 --- a/test/fixdescriptors/openssl-0.9.7b.fix +++ /dev/null @@ -1,8 +0,0 @@ -Descr( - [ Bind("pkgId", "openssl-0.9.7b") - , Bind("releaseId", "1") - - , Bind("src", Url("fae4bec090fa78e20f09d76d55b6ccff", "http://www.openssl.org/source/openssl-0.9.7b.tar.gz")) - , Bind("build", Local("../build/openssl-build.sh")) - ] -) diff --git a/test/fixdescriptors/pan-0.14.0.fix b/test/fixdescriptors/pan-0.14.0.fix deleted file mode 100644 index 1fbee1179..000000000 --- a/test/fixdescriptors/pan-0.14.0.fix +++ /dev/null @@ -1,17 +0,0 @@ -Descr( - [ Bind("pkgId", "pan-0.14.0") - , Bind("releaseId", "1") - - , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) - , Bind("glib", Fix("./glib-2.2.1.fix")) - , Bind("atk", Fix("./atk-1.2.0.fix")) - , Bind("pango", Fix("./pango-1.2.1.fix")) - , Bind("gtk", Fix("./gtk+-2.2.1.fix")) - , Bind("gnet", Fix("./gnet-1.1.8.fix")) - , Bind("pspell", Fix("./pspell-.12.2.fix")) - , Bind("gtkspell", Fix("./gtkspell-2.0.2.fix")) - - , Bind("src", Url("b2702adadb84c2e0d52d2bb029c05206", "http://pan.rebelbase.com/download/releases/0.14.0/SOURCE/pan-0.14.0.tar.bz2")) - , Bind("build", Local("../build/pan-build-2.sh")) - ] -) diff --git a/test/fixdescriptors/pango-1.2.1.fix b/test/fixdescriptors/pango-1.2.1.fix deleted file mode 100644 index 886616d0d..000000000 --- a/test/fixdescriptors/pango-1.2.1.fix +++ /dev/null @@ -1,11 +0,0 @@ -Descr( - [ Bind("pkgId", "pango-1.2.1") - , Bind("releaseId", "1") - - , Bind("pkgconfig", Fix("./pkgconfig-0.15.0.fix")) - , Bind("glib", Fix("./glib-2.2.1.fix")) - - , Bind("src", Url("6b354ef14e75739a92b5b78f4ca3165a", "ftp://ftp.gtk.org/pub/gtk/v2.2/pango-1.2.1.tar.bz2")) - , Bind("build", Local("../build/pango-build.sh")) - ] -) diff --git a/test/fixdescriptors/pkgconfig-0.15.0.fix b/test/fixdescriptors/pkgconfig-0.15.0.fix deleted file mode 100644 index bf895b0f5..000000000 --- a/test/fixdescriptors/pkgconfig-0.15.0.fix +++ /dev/null @@ -1,8 +0,0 @@ -Descr( - [ Bind("pkgId", "pkgconfig-0.15.0") - , Bind("releaseId", "1") - - , Bind("src", Url("a7e4f60a6657dbc434334deb594cc242", "http://www.freedesktop.org/software/pkgconfig/releases/pkgconfig-0.15.0.tar.gz")) - , Bind("build", Local("../build/pkgconfig-build.sh")) - ] -) diff --git a/test/fixdescriptors/pspell-.12.2.fix b/test/fixdescriptors/pspell-.12.2.fix deleted file mode 100644 index 945cb62a8..000000000 --- a/test/fixdescriptors/pspell-.12.2.fix +++ /dev/null @@ -1,8 +0,0 @@ -Descr( - [ Bind("pkgId", "pspell-.12.2") - , Bind("releaseId", "1") - - , Bind("src", Url("cfd3816b2372932a1b71c0ce4e9f881e", "http://unc.dl.sourceforge.net/sourceforge/pspell/pspell-.12.2.tar.gz")) - , Bind("build", Local("../build/pspell-build.sh")) - ] -) diff --git a/test/fixdescriptors/subversion-0.21.0.fix b/test/fixdescriptors/subversion-0.21.0.fix deleted file mode 100644 index de2edbcbb..000000000 --- a/test/fixdescriptors/subversion-0.21.0.fix +++ /dev/null @@ -1,16 +0,0 @@ -Descr( - [ Bind("pkgId", "subversion-0.21.0") - , Bind("releaseId", "1") - - , Bind("httpsClient", Bool(True)) - , Bind("httpServer", Bool(True)) - , Bind("httpsServer", Bool(True)) - - , Bind("ssl", If(Var("httpsClient"), Fix("./openssl-0.9.7b.fix"), "")) - - , Bind("httpd", If(Var("httpServer"), Fix("./httpd-2.0.45.fix"), "")) - - , Bind("src", Url("b2ad91127fb652e764b750f4c0002528", "http://subversion.tigris.org/files/documents/15/3712/subversion-0.21.0.tar.gz")) - , Bind("build", Local("../build/subversion-build.sh")) - ] -) diff --git a/test/fixdescriptors/system.fix b/test/fixdescriptors/system.fix deleted file mode 100644 index 7ca8c8797..000000000 --- a/test/fixdescriptors/system.fix +++ /dev/null @@ -1,19 +0,0 @@ -Descr( - [ Bind("pkgId", Str("system")) - , Bind("releaseId", Str("3")) - - , Bind("actATerm", Fix("./aterm-2.0.fix")) - , Bind("actPkgConfig", Fix("./pkgconfig-0.15.0.fix")) - , Bind("actGlib", Fix("./glib-2.2.1.fix")) - , Bind("actAtk", Fix("./atk-1.2.0.fix")) - , Bind("actPango", Fix("./pango-1.2.1.fix")) - , Bind("actGtk", Fix("./gtk+-2.2.1.fix")) - , Bind("actGnet", Fix("./gnet-1.1.8.fix")) - , Bind("actPspell", Fix("./pspell-.12.2.fix")) - , Bind("actGtkspell", Fix("./gtkspell-2.0.2.fix")) - , Bind("actPan", Fix("./pan-0.14.0.fix")) - , Bind("actSubversion", Fix("./subversion-0.21.0.fix")) - - , Bind("build", Local("../../scripts/nix-populate")) - ] -) From d8bdf5b06e50ea4a618f2b69c4839f92086ebb29 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 26 May 2003 14:19:25 +0000 Subject: [PATCH 0058/6440] * Removed some debug code that prevented packages from building. --- scripts/nix-populate | 58 -------------------------------------------- src/nix.cc | 2 -- 2 files changed, 60 deletions(-) delete mode 100755 scripts/nix-populate diff --git a/scripts/nix-populate b/scripts/nix-populate deleted file mode 100755 index d375caa7d..000000000 --- a/scripts/nix-populate +++ /dev/null @@ -1,58 +0,0 @@ -#! /usr/bin/perl -w - -use strict; -use Cwd; - -my $selfdir = cwd; - -my @dirs = ("bin", "sbin", "lib", "include"); - -# Create the subdirectories. -mkdir $selfdir; -foreach my $dir (@dirs) { - mkdir "$selfdir/$dir"; -} - -# For each activated package, create symlinks. - -sub createLinks { - my $srcdir = shift; - my $dstdir = shift; - - my @srcfiles = glob("$srcdir/*"); - - foreach my $srcfile (@srcfiles) { - my $basename = $srcfile; - $basename =~ s/^.*\///g; # strip directory - my $dstfile = "$dstdir/$basename"; - if (-d $srcfile) { - # !!! hack for resolving name clashes - if (!-e $dstfile) { - mkdir($dstfile) or - die "error creating directory $dstfile"; - } - -d $dstfile or die "$dstfile is not a directory"; - createLinks($srcfile, $dstfile); - } elsif (-l $dstfile) { - my $target = readlink($dstfile); - die "collission between $srcfile and $target"; - } else { - print "linking $dstfile to $srcfile\n"; - symlink($srcfile, $dstfile) or - die "error creating link $dstfile"; - } - } -} - -foreach my $name (keys %ENV) { - - next unless ($name =~ /^act.*$/); - - my $pkgdir = $ENV{$name}; - - print "merging $pkgdir\n"; - - foreach my $dir (@dirs) { - createLinks("$pkgdir/$dir", "$selfdir/$dir"); - } -} diff --git a/src/nix.cc b/src/nix.cc index 5904bf82d..d068e9616 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -267,8 +267,6 @@ void installPkg(string hash) _exit(0); } - throw Error("no prebuilt available"); - build: /* Fill in the environment. We don't bother freeing the From 9efad7659568ad2eeee5e2cf9cf1df9322d9eb33 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 26 May 2003 15:09:10 +0000 Subject: [PATCH 0059/6440] * FreeBSD / ISO C++ compatibility fixes. --- src/db.cc | 2 ++ src/nix.cc | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/db.cc b/src/db.cc index e9f3a0f9e..4178f6ebe 100644 --- a/src/db.cc +++ b/src/db.cc @@ -1,6 +1,8 @@ #include "db.hh" #include "util.hh" +#include + #include diff --git a/src/nix.cc b/src/nix.cc index d068e9616..667f452aa 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -1,9 +1,9 @@ #include -#include #include #include #include #include +#include #include #include From 4d21cda0cd44db75f6b9f5942440a07303c06b4a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 27 May 2003 09:55:47 +0000 Subject: [PATCH 0060/6440] * Fix for big-endian platforms: check for endianness in MD5 computations. This is done at runtime, which is inefficient, but I can't be bothered to write an Autoconf test right now. --- src/md5.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/md5.c b/src/md5.c index 64ade3c6f..fa67ebfcd 100644 --- a/src/md5.c +++ b/src/md5.c @@ -31,12 +31,25 @@ #include "md5.h" -#ifdef WORDS_BIGENDIAN -# define SWAP(n) \ - (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) -#else -# define SWAP(n) (n) -#endif + +static md5_uint32 SWAP(md5_uint32 n) +{ + static int checked = 0; + static int bigendian = 0; + static md5_uint32 test; + + if (!checked) { + test = 1; + if (* (char *) &test == 0) + bigendian = 1; + checked = 1; + } + + if (bigendian) + return (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)); + else + return n; +} /* This array contains the bytes used to pad the buffer to the next From 5e01b220b363524e02ec07da3943e02042218167 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 27 May 2003 11:58:14 +0000 Subject: [PATCH 0061/6440] * Fix the rsync destination. --- scripts/nix-push-prebuilts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/nix-push-prebuilts b/scripts/nix-push-prebuilts index 952897879..c0e3b45c5 100755 --- a/scripts/nix-push-prebuilts +++ b/scripts/nix-push-prebuilts @@ -34,7 +34,7 @@ close PKGS; # Push the prebuilts to the server. !!! FIXME -system "rsync -av -e ssh '$exportdir'/ losser:/home/eelco/public_html/nix-prebuilts/"; +system "rsync -av -e ssh '$exportdir'/ eelco\@losser.st-lab.cs.uu.nl:/home/eelco/public_html/nix-prebuilts/"; # Rerun `nix-pull-prebuilts' to rescan the prebuilt source locations. From 84e235eae8581403716a87b37e9dc6210b8ad515 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 30 May 2003 11:49:06 +0000 Subject: [PATCH 0062/6440] * Set MANPATH and PKG_CONFIG_PATH. --- scripts/nix-profile.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/nix-profile.sh b/scripts/nix-profile.sh index 977210165..68a33ecc6 100644 --- a/scripts/nix-profile.sh +++ b/scripts/nix-profile.sh @@ -12,4 +12,8 @@ if test -z "$NIX_SET"; then export C_INCLUDE_PATH=$NIX_LINKS/include:$C_INCLUDE_PATH -fi \ No newline at end of file + export PKG_CONFIG_PATH=$NIX_LINKS/lib/pkgconfig:$PKG_CONFIG_PATH + + export MANPATH=$NIX_LINKS/man:$MANPATH + +fi From 383297e0e835b69e0f4d7c97a9b8d441c2872251 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 30 May 2003 15:17:40 +0000 Subject: [PATCH 0063/6440] * Don't set MANPATH. It's not necessary. --- scripts/nix-profile.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/nix-profile.sh b/scripts/nix-profile.sh index 68a33ecc6..fc5c13152 100644 --- a/scripts/nix-profile.sh +++ b/scripts/nix-profile.sh @@ -14,6 +14,6 @@ if test -z "$NIX_SET"; then export PKG_CONFIG_PATH=$NIX_LINKS/lib/pkgconfig:$PKG_CONFIG_PATH - export MANPATH=$NIX_LINKS/man:$MANPATH +# export MANPATH=$NIX_LINKS/man:$MANPATH fi From 64582f54be099eb92a75ed301c72681afa4db741 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 30 May 2003 15:18:30 +0000 Subject: [PATCH 0064/6440] * Open the database read-only when enumerating tables. --- src/db.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db.cc b/src/db.cc index 4178f6ebe..b33591c8b 100644 --- a/src/db.cc +++ b/src/db.cc @@ -101,7 +101,7 @@ void enumDB(const string & filename, const string & dbname, { try { - auto_ptr db = openDB(filename, dbname, false); + auto_ptr db = openDB(filename, dbname, true); Dbc * cursor; db->cursor(0, &cursor, 0); From d1f5fd7216066d4ed04df27546e3d3e0d362c71b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 30 May 2003 15:21:52 +0000 Subject: [PATCH 0065/6440] * Log the output of builders to $prefix/var/log/nix. --- src/Makefile.am | 3 +- src/nix.cc | 123 +++++++++++++++++++++++++++++------------------- 2 files changed, 76 insertions(+), 50 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index ffbaaeb83..3b519aa55 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,6 +1,6 @@ bin_PROGRAMS = nix fix -CXXFLAGS = -DSYSTEM=\"@host@\" -Wall +AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall nix_SOURCES = nix.cc db.cc util.cc md5.c nix_LDADD = -ldb_cxx-4 -lATerm @@ -16,5 +16,6 @@ install-data-local: $(INSTALL) -d $(localstatedir)/nix/prebuilts $(INSTALL) -d $(localstatedir)/nix/prebuilts/imports $(INSTALL) -d $(localstatedir)/nix/prebuilts/exports + $(INSTALL) -d $(localstatedir)/log/nix $(INSTALL) -d $(prefix)/pkg $(bindir)/nix init diff --git a/src/nix.cc b/src/nix.cc index 667f452aa..7d6b3c97a 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -29,6 +29,7 @@ static string dbNetSources = "netsources"; static string nixSourcesDir; +static string nixLogDir; static string nixDB; @@ -228,6 +229,13 @@ void installPkg(string hash) if (mkdir(path.c_str(), 0777)) throw Error("unable to create directory " + path); + /* Create a log file. */ + string logFileName = nixLogDir + "/" + id + "-" + hash + ".log"; + /* !!! auto-pclose on exit */ + FILE * logFile = popen(("tee " + logFileName).c_str(), "w"); /* !!! escaping */ + if (!logFile) + throw Error("unable to create log file " + logFileName); + try { /* Fork a child to build the package. */ @@ -237,62 +245,78 @@ void installPkg(string hash) case -1: throw Error("unable to fork"); - case 0: { /* child */ + case 0: - /* Go to the build directory. */ - if (chdir(path.c_str())) { - cerr << "unable to chdir to package directory\n"; + try { /* child */ + + /* Go to the build directory. */ + if (chdir(path.c_str())) { + cerr << "unable to chdir to package directory\n"; + _exit(1); + } + + /* Try to use a prebuilt. */ + string prebuiltHash, prebuiltFile; + if (queryDB(nixDB, dbPrebuilts, hash, prebuiltHash)) { + + try { + prebuiltFile = getFile(prebuiltHash); + } catch (Error e) { + cerr << "cannot obtain prebuilt (ignoring): " << e.what() << endl; + goto build; + } + + cerr << "substituting prebuilt " << prebuiltFile << endl; + + int res = system(("tar xfj " + prebuiltFile + " 1>&2").c_str()); // !!! escaping + if (WEXITSTATUS(res) != 0) + /* This is a fatal error, because path may now + have clobbered. */ + throw Error("cannot unpack " + prebuiltFile); + + _exit(0); + } + + build: + + /* Fill in the environment. We don't bother freeing + the strings, since we'll exec or die soon + anyway. */ + const char * env2[env.size() + 1]; + int i = 0; + for (Environment::iterator it = env.begin(); + it != env.end(); it++, i++) + env2[i] = (new string(it->first + "=" + it->second))->c_str(); + env2[i] = 0; + + /* Dup the log handle into stderr. */ + if (dup2(fileno(logFile), STDERR_FILENO) == -1) + throw Error("cannot pipe standard error into log file: " + string(strerror(errno))); + + /* Dup stderr to stdin. */ + if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1) + throw Error("cannot dup stderr into stdout"); + + /* Execute the builder. This should not return. */ + execle(builder.c_str(), builder.c_str(), 0, env2); + + throw Error("unable to execute builder: " + + string(strerror(errno))); + + } catch (exception & e) { + cerr << "build error: " << e.what() << endl; _exit(1); } - /* Try to use a prebuilt. */ - string prebuiltHash, prebuiltFile; - if (queryDB(nixDB, dbPrebuilts, hash, prebuiltHash)) { - - try { - prebuiltFile = getFile(prebuiltHash); - } catch (Error e) { - cerr << "cannot obtain prebuilt (ignoring): " << e.what() << endl; - goto build; - } - - cerr << "substituting prebuilt " << prebuiltFile << endl; - - int res = system(("tar xfj " + prebuiltFile + " 1>&2").c_str()); // !!! escaping - if (WEXITSTATUS(res) != 0) - /* This is a fatal error, because path may now - have clobbered. */ - throw Error("cannot unpack " + prebuiltFile); - - _exit(0); - } - -build: - - /* Fill in the environment. We don't bother freeing the - strings, since we'll exec or die soon anyway. */ - const char * env2[env.size() + 1]; - int i = 0; - for (Environment::iterator it = env.begin(); - it != env.end(); it++, i++) - env2[i] = (new string(it->first + "=" + it->second))->c_str(); - env2[i] = 0; - - /* Dup stderr to stdin. */ - dup2(STDERR_FILENO, STDOUT_FILENO); - - /* Execute the builder. This should not return. */ - execle(builder.c_str(), builder.c_str(), 0, env2); - - cerr << strerror(errno) << endl; - - cerr << "unable to execute builder\n"; - _exit(1); } - } /* parent */ + /* Close the logging pipe. Note that this should not cause + the logger to exit until builder exits (because the latter + has an open file handle to the former). */ + pclose(logFile); + /* Wait for the child to finish. */ int status; if (waitpid(pid, &status, 0) != pid) @@ -305,7 +329,7 @@ build: int res = system(("chmod -R -w " + path).c_str()); // !!! escaping if (WEXITSTATUS(res) != 0) throw Error("cannot remove write permission from " + path); - + } catch (exception &) { system(("rm -rf " + path).c_str()); throw; @@ -690,6 +714,7 @@ void run(Strings::iterator argCur, Strings::iterator argEnd) if (homeDir) nixHomeDir = homeDir; nixSourcesDir = nixHomeDir + "/var/nix/sources"; + nixLogDir = nixHomeDir + "/var/log/nix"; nixDB = nixHomeDir + "/var/nix/pkginfo.db"; /* Parse the global flags. */ From 5908663f4209eed632b3973c97b25ff5a3dd3b17 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 30 May 2003 17:01:21 +0000 Subject: [PATCH 0066/6440] * Send log output to stderr. --- src/nix.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index 7d6b3c97a..7f5ca927b 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -232,7 +232,7 @@ void installPkg(string hash) /* Create a log file. */ string logFileName = nixLogDir + "/" + id + "-" + hash + ".log"; /* !!! auto-pclose on exit */ - FILE * logFile = popen(("tee " + logFileName).c_str(), "w"); /* !!! escaping */ + FILE * logFile = popen(("tee " + logFileName + " >&2").c_str(), "w"); /* !!! escaping */ if (!logFile) throw Error("unable to create log file " + logFileName); @@ -331,7 +331,7 @@ void installPkg(string hash) throw Error("cannot remove write permission from " + path); } catch (exception &) { - system(("rm -rf " + path).c_str()); +// system(("rm -rf " + path).c_str()); throw; } From f66055fa1ef3eb208666b5ace7b5ab16bf7e8980 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 30 May 2003 20:53:36 +0000 Subject: [PATCH 0067/6440] * Set umask to 0022 on startup. --- scripts/nix-pull-prebuilts | 2 ++ scripts/nix-push-prebuilts | 2 ++ src/fix.cc | 2 ++ 3 files changed, 6 insertions(+) diff --git a/scripts/nix-pull-prebuilts b/scripts/nix-pull-prebuilts index 9cc668337..3d045b463 100755 --- a/scripts/nix-pull-prebuilts +++ b/scripts/nix-pull-prebuilts @@ -7,6 +7,8 @@ my $tmpfile = "$prefix/var/nix/prebuilts.tmp"; my $conffile = "$etcdir/prebuilts.conf"; +umask 0022; + sub register { my $fn = shift; my $url = shift; diff --git a/scripts/nix-push-prebuilts b/scripts/nix-push-prebuilts index c0e3b45c5..2d44e7cda 100755 --- a/scripts/nix-push-prebuilts +++ b/scripts/nix-push-prebuilts @@ -5,6 +5,8 @@ my $etcdir = "$prefix/etc/nix"; my $exportdir = "$prefix/var/nix/prebuilts/exports"; my $knowns = "$prefix/var/nix/known-prebuilts"; +umask 0022; + # For performance, put the known hashes in an associative array. my %knowns = (); open KNOWNS, "<$knowns"; diff --git a/src/fix.cc b/src/fix.cc index b26f8eb59..30303322a 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -301,6 +301,8 @@ void printUsage() /* Parse the command-line arguments, call the right operation. */ void run(Strings::iterator argCur, Strings::iterator argEnd) { + umask(0022); + Strings extraArgs; enum { cmdUnknown, cmdInstantiate } command = cmdUnknown; From 21fe717ce2027187e553d1edec65ef68b5d3c702 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 15 Jun 2003 13:41:32 +0000 Subject: [PATCH 0068/6440] * Refactoring: hash class. --- src/Makefile.am | 7 +- src/fix.cc | 39 ++++----- src/hash.cc | 87 ++++++++++++++++++++ src/hash.hh | 34 ++++++++ src/nix.cc | 207 ++++++++++++++++++++++++------------------------ src/test.cc | 16 ++++ src/util.cc | 47 ----------- src/util.hh | 13 --- 8 files changed, 266 insertions(+), 184 deletions(-) create mode 100644 src/hash.cc create mode 100644 src/hash.hh create mode 100644 src/test.cc diff --git a/src/Makefile.am b/src/Makefile.am index 3b519aa55..a56a0ae0e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,13 +1,16 @@ bin_PROGRAMS = nix fix +noinst_PROGRAMS = test AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -nix_SOURCES = nix.cc db.cc util.cc md5.c +nix_SOURCES = nix.cc db.cc util.cc hash.cc md5.c nix_LDADD = -ldb_cxx-4 -lATerm -fix_SOURCES = fix.cc util.cc md5.c +fix_SOURCES = fix.cc util.cc hash.cc md5.c fix_LDADD = -lATerm +test_SOURCES = test.cc util.cc hash.cc md5.c + install-data-local: $(INSTALL) -d $(localstatedir)/nix $(INSTALL) -d $(localstatedir)/nix/descriptors diff --git a/src/fix.cc b/src/fix.cc index 30303322a..3c4c8bf53 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -10,6 +10,7 @@ extern "C" { } #include "util.hh" +#include "hash.hh" static string nixDescriptorDir; @@ -20,7 +21,7 @@ static bool verbose = false; /* Mapping of Fix file names to the hashes of the resulting Nix descriptors. */ -typedef map DescriptorMap; +typedef map DescriptorMap; void registerFile(string filename) @@ -32,12 +33,13 @@ void registerFile(string filename) } -void registerURL(string hash, string url) +void registerURL(Hash hash, string url) { - int res = system(("nix regurl " + hash + " " + url).c_str()); + int res = system(("nix regurl " + (string) hash + " " + url).c_str()); /* !!! escape */ if (WEXITSTATUS(res) != 0) - throw Error("cannot register " + hash + " -> " + url + " with Nix"); + throw Error("cannot register " + + (string) hash + " -> " + url + " with Nix"); } @@ -61,7 +63,7 @@ struct EvalContext ATerm evaluate(ATerm e, EvalContext ctx); -string instantiateDescriptor(string filename, EvalContext ctx); +Hash instantiateDescriptor(string filename, EvalContext ctx); string evaluateStr(ATerm e, EvalContext ctx) @@ -101,7 +103,7 @@ ATerm evaluate(ATerm e, EvalContext ctx) ATmatch(e, "Pkg()", &s) || ATmatch(e, "File()", &s)) { - checkHash(s); + parseHash(s); return e; } @@ -131,7 +133,7 @@ ATerm evaluate(ATerm e, EvalContext ctx) else if (ATmatch(e, "Fix()", &e2)) { string filename = absPath(evaluateStr(e2, ctx), ctx.dir); /* !!! */ return ATmake("Pkg()", - instantiateDescriptor(filename, ctx).c_str()); + ((string) instantiateDescriptor(filename, ctx)).c_str()); } #if 0 @@ -160,19 +162,18 @@ ATerm evaluate(ATerm e, EvalContext ctx) hash. */ else if (ATmatch(e, "Local()", &e2)) { string filename = absPath(evaluateStr(e2, ctx), ctx.dir); /* !!! */ - string hash = hashFile(filename); + Hash hash = hashFile(filename); registerFile(filename); /* !!! */ - return ATmake("File()", hash.c_str()); + return ATmake("File()", ((string) hash).c_str()); } /* `Url' registers a mapping from a hash to an url with Nix, and returns the hash. */ else if (ATmatch(e, "Url(, )", &e2, &e3)) { - string hash = evaluateStr(e2, ctx); - checkHash(hash); + Hash hash = parseHash(evaluateStr(e2, ctx)); string url = evaluateStr(e3, ctx); registerURL(hash, url); - return ATmake("File()", hash.c_str()); + return ATmake("File()", ((string) hash).c_str()); } /* `If' provides conditional evaluation. */ @@ -199,7 +200,7 @@ string getStringFromMap(BindingsMap & bindingsMap, /* Instantiate a Fix descriptors into a Nix descriptor, recursively instantiating referenced descriptors as well. */ -string instantiateDescriptor(string filename, EvalContext ctx) +Hash instantiateDescriptor(string filename, EvalContext ctx) { /* Already done? */ DescriptorMap::iterator isInMap = ctx.done->find(filename); @@ -256,8 +257,9 @@ string instantiateDescriptor(string filename, EvalContext ctx) if (!ATwriteToNamedTextFile(outTerm, tmpFilename.c_str())) throw Error("cannot write aterm to " + tmpFilename); - string outHash = hashFile(tmpFilename); - string outFilename = nixDescriptorDir + "/" + id + "-" + outHash + ".nix"; + Hash outHash = hashFile(tmpFilename); + string outFilename = nixDescriptorDir + "/" + + id + "-" + (string) outHash + ".nix"; if (rename(tmpFilename.c_str(), outFilename.c_str())) throw Error("cannot rename " + tmpFilename + " to " + outFilename); @@ -265,7 +267,8 @@ string instantiateDescriptor(string filename, EvalContext ctx) registerFile(outFilename); if (verbose) - cerr << "instantiated " << outHash << " from " << filename << endl; + cerr << "instantiated " << (string) outHash + << " from " << filename << endl; (*ctx.done)[filename] = outHash; return outHash; @@ -284,7 +287,7 @@ void instantiateDescriptors(Strings filenames) it != filenames.end(); it++) { string filename = absPath(*it); - cout << instantiateDescriptor(filename, ctx) << endl; + cout << (string) instantiateDescriptor(filename, ctx) << endl; } } @@ -293,7 +296,7 @@ void instantiateDescriptors(Strings filenames) void printUsage() { cerr << -"Usage: fix ... +"Usage: fix ...\n\ "; } diff --git a/src/hash.cc b/src/hash.cc new file mode 100644 index 000000000..25d76bd15 --- /dev/null +++ b/src/hash.cc @@ -0,0 +1,87 @@ +extern "C" { +#include "md5.h" +} + +#include "hash.hh" +#include + + +/* Create a zeroed hash object. */ +Hash::Hash() +{ + memset(hash, 0, sizeof(hash)); +} + + +/* Check whether two hash are equal. */ +bool Hash::operator == (Hash & h2) +{ + for (unsigned int i = 0; i < hashSize; i++) + if (hash[i] != h2.hash[i]) return false; + return true; +} + + +/* Check whether two hash are not equal. */ +bool Hash::operator != (Hash & h2) +{ + return !(*this == h2); +} + + +/* Convert a hash code into a hexadecimal representation. */ +Hash::operator string() const +{ + ostringstream str; + for (unsigned int i = 0; i < hashSize; i++) { + str.fill('0'); + str.width(2); + str << hex << (int) hash[i]; + } + return str.str(); +} + + +/* Parse a hexadecimal representation of a hash code. */ +Hash parseHash(const string & s) +{ + Hash hash; + for (unsigned int i = 0; i < Hash::hashSize; i++) { + string s2(s, i * 2, 2); + if (!isxdigit(s2[0]) || !isxdigit(s2[1])) + throw BadRefError("invalid hash: " + s); + istringstream str(s2); + int n; + str >> hex >> n; + hash.hash[i] = n; + } + return hash; +} + + +/* Verify that a reference is valid (that is, is a MD5 hash code). */ +bool isHash(const string & s) +{ + if (s.length() != 32) return false; + for (int i = 0; i < 32; i++) { + char c = s[i]; + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f'))) + return false; + } + return true; +} + + +/* Compute the MD5 hash of a file. */ +Hash hashFile(const string & fileName) +{ + Hash hash; + FILE * file = fopen(fileName.c_str(), "rb"); + if (!file) + throw Error("file `" + fileName + "' does not exist"); + int err = md5_stream(file, hash.hash); + fclose(file); + if (err) throw Error("cannot hash file"); + return hash; +} diff --git a/src/hash.hh b/src/hash.hh new file mode 100644 index 000000000..162b2b1c8 --- /dev/null +++ b/src/hash.hh @@ -0,0 +1,34 @@ +#ifndef __HASH_H +#define __HASH_H + +#include + +#include "util.hh" + +using namespace std; + + +struct Hash +{ + static const unsigned int hashSize = 16; + unsigned char hash[hashSize]; + + Hash(); + bool operator == (Hash & h2); + bool operator != (Hash & h2); + operator string() const; +}; + + +class BadRefError : public Error +{ +public: + BadRefError(string _err) : Error(_err) { }; +}; + + +Hash parseHash(const string & s); +bool isHash(const string & s); +Hash hashFile(const string & fileName); + +#endif /* !__HASH_H */ diff --git a/src/nix.cc b/src/nix.cc index 7f5ca927b..ea33e5e28 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -16,6 +16,7 @@ extern "C" { } #include "util.hh" +#include "hash.hh" #include "db.hh" using namespace std; @@ -58,7 +59,7 @@ string fetchURL(string url) database), we use that. Otherwise, we attempt to fetch it from the network (using dbNetSources). We verify that the file has the right hash. */ -string getFile(string hash) +string getFile(Hash hash) { bool checkedNet = false; @@ -77,10 +78,10 @@ string getFile(string hash) if (checkedNet) throw Error("consistency problem: file fetched from " + url + - " should have hash " + hash + ", but it doesn't"); + " should have hash " + (string) hash + ", but it doesn't"); if (!queryDB(nixDB, dbNetSources, hash, url)) - throw Error("a file with hash " + hash + " is requested, " + throw Error("a file with hash " + (string) hash + " is requested, " "but it is not known to exist locally or on the network"); checkedNet = true; @@ -95,7 +96,7 @@ string getFile(string hash) typedef map Params; -void readPkgDescr(const string & hash, +void readPkgDescr(Hash hash, Params & pkgImports, Params & fileImports, Params & arguments) { string pkgfile; @@ -112,15 +113,15 @@ void readPkgDescr(const string & hash, char * cname; ATerm value; while (ATmatch(bindings, "[Bind(, ), ]", - &cname, &value, &bindings)) + &cname, &value, &bindings)) { string name(cname); char * arg; if (ATmatch(value, "Pkg()", &arg)) { - checkHash(arg); + parseHash(arg); pkgImports[name] = arg; } else if (ATmatch(value, "File()", &arg)) { - checkHash(arg); + parseHash(arg); fileImports[name] = arg; } else if (ATmatch(value, "Str()", &arg)) arguments[name] = arg; @@ -136,13 +137,13 @@ void readPkgDescr(const string & hash, } -string getPkg(string hash); +string getPkg(Hash hash); typedef map Environment; -void fetchDeps(string hash, Environment & env) +void fetchDeps(Hash hash, Environment & env) { /* Read the package description file. */ Params pkgImports, fileImports, arguments; @@ -156,7 +157,7 @@ void fetchDeps(string hash, Environment & env) cerr << "fetching package dependency " << it->first << " <- " << it->second << endl; - env[it->first] = getPkg(it->second); + env[it->first] = getPkg(parseHash(it->second)); } for (Params::iterator it = fileImports.begin(); @@ -168,7 +169,7 @@ void fetchDeps(string hash, Environment & env) string file; - file = getFile(it->second); + file = getFile(parseHash(it->second)); env[it->first] = file; } @@ -198,7 +199,7 @@ string getFromEnv(const Environment & env, const string & key) } -string queryPkgId(const string & hash) +string queryPkgId(Hash hash) { Params pkgImports, fileImports, arguments; readPkgDescr(hash, pkgImports, fileImports, arguments); @@ -206,7 +207,7 @@ string queryPkgId(const string & hash) } -void installPkg(string hash) +void installPkg(Hash hash) { string pkgfile; string src; @@ -223,14 +224,15 @@ void installPkg(string hash) string id = getFromEnv(env, "id"); /* Construct a path for the installed package. */ - path = nixHomeDir + "/pkg/" + id + "-" + hash; + path = nixHomeDir + "/pkg/" + id + "-" + (string) hash; /* Create the path. */ if (mkdir(path.c_str(), 0777)) throw Error("unable to create directory " + path); /* Create a log file. */ - string logFileName = nixLogDir + "/" + id + "-" + hash + ".log"; + string logFileName = + nixLogDir + "/" + id + "-" + (string) hash + ".log"; /* !!! auto-pclose on exit */ FILE * logFile = popen(("tee " + logFileName + " >&2").c_str(), "w"); /* !!! escaping */ if (!logFile) @@ -256,11 +258,11 @@ void installPkg(string hash) } /* Try to use a prebuilt. */ - string prebuiltHash, prebuiltFile; - if (queryDB(nixDB, dbPrebuilts, hash, prebuiltHash)) { + string prebuiltHashS, prebuiltFile; + if (queryDB(nixDB, dbPrebuilts, hash, prebuiltHashS)) { try { - prebuiltFile = getFile(prebuiltHash); + prebuiltFile = getFile(parseHash(prebuiltHashS)); } catch (Error e) { cerr << "cannot obtain prebuilt (ignoring): " << e.what() << endl; goto build; @@ -339,17 +341,16 @@ void installPkg(string hash) } -string getPkg(string hash) +string getPkg(Hash hash) { string path; - checkHash(hash); while (!queryDB(nixDB, dbInstPkgs, hash, path)) installPkg(hash); return path; } -void runPkg(string hash, +void runPkg(Hash hash, Strings::iterator firstArg, Strings::iterator lastArg) { @@ -389,7 +390,7 @@ void runPkg(string hash, } -void ensurePkg(string hash) +void ensurePkg(Hash hash) { Params pkgImports, fileImports, arguments; readPkgDescr(hash, pkgImports, fileImports, arguments); @@ -403,10 +404,9 @@ void ensurePkg(string hash) } -void delPkg(string hash) +void delPkg(Hash hash) { string path; - checkHash(hash); if (queryDB(nixDB, dbInstPkgs, hash, path)) { int res = system(("chmod -R +w " + path + " && rm -rf " + path).c_str()); // !!! escaping delDB(nixDB, dbInstPkgs, hash); // not a bug ??? @@ -423,7 +423,7 @@ void exportPkgs(string outDir, outDir = absPath(outDir); for (Strings::iterator it = firstHash; it != lastHash; it++) { - string hash = *it; + Hash hash = parseHash(*it); string pkgDir = getPkg(hash); string tmpFile = outDir + "/export_tmp"; @@ -435,42 +435,38 @@ void exportPkgs(string outDir, string prebuiltHash = hashFile(tmpFile); string pkgId = queryPkgId(hash); string prebuiltFile = outDir + "/" + - pkgId + "-" + hash + "-" + prebuiltHash + ".tar.bz2"; + pkgId + "-" + (string) hash + "-" + prebuiltHash + ".tar.bz2"; rename(tmpFile.c_str(), prebuiltFile.c_str()); } } -void registerPrebuilt(string pkgHash, string prebuiltHash) +void registerPrebuilt(Hash pkgHash, Hash prebuiltHash) { - checkHash(pkgHash); - checkHash(prebuiltHash); setDB(nixDB, dbPrebuilts, pkgHash, prebuiltHash); } -string registerFile(string filename) +Hash registerFile(string filename) { filename = absPath(filename); - string hash = hashFile(filename); + Hash hash = hashFile(filename); setDB(nixDB, dbRefs, hash, filename); return hash; } -void registerURL(string hash, string url) +void registerURL(Hash hash, string url) { - checkHash(hash); setDB(nixDB, dbNetSources, hash, url); /* !!! currently we allow only one network source per hash */ } /* This is primarily used for bootstrapping. */ -void registerInstalledPkg(string hash, string path) +void registerInstalledPkg(Hash hash, string path) { - checkHash(hash); if (path == "") delDB(nixDB, dbInstPkgs, hash); else @@ -498,12 +494,13 @@ void verifyDB() it != fileRefs.end(); it++) { try { - if (hashFile(it->second) != it->first) { + Hash hash = parseHash(it->first); + if (hashFile(it->second) != hash) { cerr << "file " << it->second << " has changed\n"; delDB(nixDB, dbRefs, it->first); } - } catch (BadRefError e) { /* !!! better error check */ - cerr << "file " << it->second << " has disappeared\n"; + } catch (Error e) { /* !!! better error check */ + cerr << "error: " << e.what() << endl; delDB(nixDB, dbRefs, it->first); } } @@ -544,7 +541,7 @@ void printInfo(Strings::iterator first, Strings::iterator last) { for (Strings::iterator it = first; it != last; it++) { try { - cout << *it << " " << queryPkgId(*it) << endl; + cout << *it << " " << queryPkgId(parseHash(*it)) << endl; } catch (Error & e) { // !!! more specific cout << *it << " (descriptor missing)\n"; } @@ -559,7 +556,7 @@ void computeClosure(Strings::iterator first, Strings::iterator last, set doneSet; while (!workList.empty()) { - string hash = workList.front(); + Hash hash = parseHash(workList.front()); workList.pop_front(); if (doneSet.find(hash) == doneSet.end()) { @@ -605,7 +602,7 @@ void printGraph(Strings::iterator first, Strings::iterator last) it != allHashes.end(); it++) { Params pkgImports, fileImports, arguments; - readPkgDescr(*it, pkgImports, fileImports, arguments); + readPkgDescr(parseHash(*it), pkgImports, fileImports, arguments); cout << dotQuote(*it) << "[label = \"" << getFromEnv(arguments, "id") @@ -633,8 +630,8 @@ void fetch(string id) } /* Register it by hash. */ - string hash = registerFile(fn); - cout << hash << endl; + Hash hash = registerFile(fn); + cout << (string) hash << endl; } @@ -648,60 +645,60 @@ void fetch(Strings::iterator first, Strings::iterator last) void printUsage() { cerr << -"Usage: nix SUBCOMMAND OPTIONS... - -Subcommands: - - init - Initialize the database. - - verify - Remove stale entries from the database. - - regfile FILENAME... - Register each FILENAME keyed by its hash. - - reginst HASH PATH - Register an installed package. - - getpkg HASH... - For each HASH, ensure that the package referenced by HASH is - installed. Print out the path of the installation on stdout. - - delpkg HASH... - Uninstall the package referenced by each HASH, disregarding any - dependencies that other packages may have on HASH. - - listinst - Prints a list of installed packages. - - run HASH ARGS... - Run the descriptor referenced by HASH with the given arguments. - - ensure HASH... - Like getpkg, but if HASH refers to a run descriptor, fetch only - the dependencies. - - export DIR HASH... - Export installed packages to DIR. - - regprebuilt HASH1 HASH2 - Inform Nix that an export HASH2 can be used to fast-build HASH1. - - info HASH... - Print information about the specified descriptors. - - closure HASH... - Determine the closure of the set of descriptors under the import - relation, starting at the given roots. - - graph HASH... - Like closure, but print a dot graph specification. - - fetch ID... - Fetch the objects identified by ID and place them in the Nix - sources directory. ID can be a hash or URL. Print out the hash - of the object. +"Usage: nix SUBCOMMAND OPTIONS...\n\ +\n\ +Subcommands:\n\ +\n\ + init\n\ + Initialize the database.\n\ +\n\ + verify\n\ + Remove stale entries from the database.\n\ +\n\ + regfile FILENAME...\n\ + Register each FILENAME keyed by its hash.\n\ +\n\ + reginst HASH PATH\n\ + Register an installed package.\n\ +\n\ + getpkg HASH...\n\ + For each HASH, ensure that the package referenced by HASH is\n\ + installed. Print out the path of the installation on stdout.\n\ +\n\ + delpkg HASH...\n\ + Uninstall the package referenced by each HASH, disregarding any\n\ + dependencies that other packages may have on HASH.\n\ +\n\ + listinst\n\ + Prints a list of installed packages.\n\ +\n\ + run HASH ARGS...\n\ + Run the descriptor referenced by HASH with the given arguments.\n\ +\n\ + ensure HASH...\n\ + Like getpkg, but if HASH refers to a run descriptor, fetch only\n\ + the dependencies.\n\ +\n\ + export DIR HASH...\n\ + Export installed packages to DIR.\n\ +\n\ + regprebuilt HASH1 HASH2\n\ + Inform Nix that an export HASH2 can be used to fast-build HASH1.\n\ +\n\ + info HASH...\n\ + Print information about the specified descriptors.\n\ +\n\ + closure HASH...\n\ + Determine the closure of the set of descriptors under the import\n\ + relation, starting at the given roots.\n\ +\n\ + graph HASH...\n\ + Like closure, but print a dot graph specification.\n\ +\n\ + fetch ID...\n\ + Fetch the objects identified by ID and place them in the Nix\n\ + sources directory. ID can be a hash or URL. Print out the hash\n\ + of the object.\n\ "; } @@ -743,29 +740,31 @@ void run(Strings::iterator argCur, Strings::iterator argEnd) verifyDB(); } else if (cmd == "getpkg") { for (Strings::iterator it = argCur; it != argEnd; it++) { - string path = getPkg(*it); + string path = getPkg(parseHash(*it)); cout << path << endl; } } else if (cmd == "delpkg") { - for_each(argCur, argEnd, delPkg); + for (Strings::iterator it = argCur; it != argEnd; it++) + delPkg(parseHash(*it)); } else if (cmd == "run") { if (argc < 1) throw argcError; - runPkg(*argCur, argCur + 1, argEnd); + runPkg(parseHash(*argCur), argCur + 1, argEnd); } else if (cmd == "ensure") { - for_each(argCur, argEnd, ensurePkg); + for (Strings::iterator it = argCur; it != argEnd; it++) + ensurePkg(parseHash(*it)); } else if (cmd == "export") { if (argc < 1) throw argcError; exportPkgs(*argCur, argCur + 1, argEnd); } else if (cmd == "regprebuilt") { if (argc != 2) throw argcError; - registerPrebuilt(*argCur, argCur[1]); + registerPrebuilt(parseHash(argCur[0]), parseHash(argCur[1])); } else if (cmd == "regfile") { for_each(argCur, argEnd, registerFile); } else if (cmd == "regurl") { - registerURL(argCur[0], argCur[1]); + registerURL(parseHash(argCur[0]), argCur[1]); } else if (cmd == "reginst") { if (argc != 2) throw argcError; - registerInstalledPkg(*argCur, argCur[1]); + registerInstalledPkg(parseHash(argCur[0]), argCur[1]); } else if (cmd == "listinst") { if (argc != 0) throw argcError; listInstalledPkgs(); diff --git a/src/test.cc b/src/test.cc new file mode 100644 index 000000000..cce226ba9 --- /dev/null +++ b/src/test.cc @@ -0,0 +1,16 @@ +#include + +#include "hash.hh" + +int main(int argc, char * * argv) +{ + Hash h = hashFile("/etc/passwd"); + + cout << (string) h << endl; + + h = parseHash("0b0ffd0538622bfe20b92c4aa57254d9"); + + cout << (string) h << endl; + + return 0; +} diff --git a/src/util.cc b/src/util.cc index 4b7bbac3d..299fc942f 100644 --- a/src/util.cc +++ b/src/util.cc @@ -27,53 +27,6 @@ string absPath(string filename, string dir) } -static string printHash(unsigned char * buf) -{ - ostringstream str; - for (int i = 0; i < 16; i++) { - str.fill('0'); - str.width(2); - str << hex << (int) buf[i]; - } - return str.str(); -} - - -/* Verify that a reference is valid (that is, is a MD5 hash code). */ -bool isHash(const string & s) -{ - if (s.length() != 32) return false; - for (int i = 0; i < 32; i++) { - char c = s[i]; - if (!((c >= '0' && c <= '9') || - (c >= 'a' && c <= 'f'))) - return false; - } - return true; -} - - -void checkHash(const string & s) -{ - if (!isHash(s)) throw BadRefError("invalid reference: " + s); -} - - -/* Compute the MD5 hash of a file. */ -string hashFile(string filename) -{ - unsigned char hash[16]; - FILE * file = fopen(filename.c_str(), "rb"); - if (!file) - throw BadRefError("file `" + filename + "' does not exist"); - int err = md5_stream(file, hash); - fclose(file); - if (err) throw BadRefError("cannot hash file"); - return printHash(hash); -} - - - /* Return the directory part of the given path, i.e., everything before the final `/'. */ string dirOf(string s) diff --git a/src/util.hh b/src/util.hh index 2c09efc4d..d1a195609 100644 --- a/src/util.hh +++ b/src/util.hh @@ -7,10 +7,6 @@ #include -extern "C" { -#include "md5.h" -} - using namespace std; @@ -29,12 +25,6 @@ public: UsageError(string _err) : Error(_err) { }; }; -class BadRefError : public Error -{ -public: - BadRefError(string _err) : Error(_err) { }; -}; - typedef vector Strings; @@ -50,9 +40,6 @@ extern string nixHomeDirEnvVar; string absPath(string filename, string dir = ""); -bool isHash(const string & s); -void checkHash(const string & s); -string hashFile(string filename); string dirOf(string s); string baseNameOf(string s); From b9f09b3268bf0c3d9ecd512dd3a0aa1247550cc2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 16 Jun 2003 07:03:40 +0000 Subject: [PATCH 0069/6440] * AST for Nix expressions. --- src/nix.cc | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index ea33e5e28..7990cde3a 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -23,17 +23,81 @@ using namespace std; /* Database names. */ + +/* dbRefs :: Hash -> FileName + + Maintains a mapping from hashes to filenames within the NixValues + directory. This mapping is for performance only; it can be + reconstructed unambiguously from the nixValues directory. The + reason is that names in this directory are not printed hashes but + also might carry some descriptive element (e.g., + "aterm-2.0-ae749a..."). Without this mapping, looking up a value + would take O(n) time because we would need to read the entire + directory. */ static string dbRefs = "refs"; -static string dbInstPkgs = "pkginst"; -static string dbPrebuilts = "prebuilts"; + +/* dbNFs :: Hash -> Hash + + Each pair (h1, h2) in this mapping records the fact that h2 is a + normal form obtained by evaluating the value h1. + + We would really like to have h2 be the hash of the object + referenced by h2. However, that gives a cyclic dependency: to + compute the hash (and thus the file name) of the object, we need to + compute the object, but to do that, we need the file name of the + object. + + So for now we abandon the requirement that + + hashFile(dbRefs[h]) == h. + + I.e., this property does not hold for computed normal forms. + Rather, we use h2 = hash(h1). This allows dbNFs to be + reconstructed. Perhaps using a pseudo random number would be + better to prevent the system from being subverted in some way. +*/ +static string dbNFs = "nfs"; + +/* dbNetSources :: Hash -> URL + + Each pair (hash, url) in this mapping states that the object + identified by hash can be obtained by fetching the object pointed + to by url. + + TODO: this should be Hash -> [URL] + + TODO: factor this out into a separate tool? */ static string dbNetSources = "netsources"; -static string nixSourcesDir; +/* Path names. */ + +/* nixValues is the directory where all Nix values (both files and + directories, and both normal and non-normal forms) live. */ +static string nixValues; + +/* nixLogDir is the directory where we log evaluations. */ static string nixLogDir; + +/* nixDB is the file name of the Berkeley DB database where we + maintain the dbXXX mappings. */ static string nixDB; +/* Abstract syntax of Nix values: + + e := Hash(h) -- external reference + | Str(s) -- string constant + | Bool(b) -- boolean constant + | Name(e) -- "&" operator; pointer (file name) formation + | App(e, e) -- application + | Lam(x, e) -- lambda abstraction + | Exec(platform, e, e*) + -- primitive; execute e with args e* on platform + ; +*/ + + /* Download object referenced by the given URL into the sources directory. Return the file name it was downloaded to. */ string fetchURL(string url) From 822794001cb4260b8c04a7bd2d50d890edae709a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 16 Jun 2003 13:33:38 +0000 Subject: [PATCH 0070/6440] * Started implementing the new evaluation model. * Lots of refactorings. * Unit tests. --- src/Makefile.am | 13 +- src/eval.cc | 297 ++++++++++++++++++++++++++++++++++++++++++ src/eval.hh | 86 ++++++++++++ src/globals.cc | 19 +++ src/globals.hh | 60 +++++++++ src/hash.cc | 15 ++- src/hash.hh | 1 + src/nix.cc | 156 +--------------------- src/test-builder-1.sh | 3 + src/test-builder-2.sh | 5 + src/test.cc | 82 ++++++++++-- src/util.cc | 54 ++++---- src/util.hh | 29 +++-- src/values.cc | 100 ++++++++++++++ src/values.hh | 24 ++++ 15 files changed, 742 insertions(+), 202 deletions(-) create mode 100644 src/eval.cc create mode 100644 src/eval.hh create mode 100644 src/globals.cc create mode 100644 src/globals.hh create mode 100644 src/test-builder-1.sh create mode 100644 src/test-builder-2.sh create mode 100644 src/values.cc create mode 100644 src/values.hh diff --git a/src/Makefile.am b/src/Makefile.am index a56a0ae0e..80d9e4af8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,16 +9,15 @@ nix_LDADD = -ldb_cxx-4 -lATerm fix_SOURCES = fix.cc util.cc hash.cc md5.c fix_LDADD = -lATerm -test_SOURCES = test.cc util.cc hash.cc md5.c +test_SOURCES = test.cc util.cc hash.cc md5.c eval.cc values.cc globals.cc db.cc +test_LDADD = -ldb_cxx-4 -lATerm install-data-local: $(INSTALL) -d $(localstatedir)/nix - $(INSTALL) -d $(localstatedir)/nix/descriptors - $(INSTALL) -d $(localstatedir)/nix/sources $(INSTALL) -d $(localstatedir)/nix/links - $(INSTALL) -d $(localstatedir)/nix/prebuilts - $(INSTALL) -d $(localstatedir)/nix/prebuilts/imports - $(INSTALL) -d $(localstatedir)/nix/prebuilts/exports +# $(INSTALL) -d $(localstatedir)/nix/prebuilts +# $(INSTALL) -d $(localstatedir)/nix/prebuilts/imports +# $(INSTALL) -d $(localstatedir)/nix/prebuilts/exports $(INSTALL) -d $(localstatedir)/log/nix - $(INSTALL) -d $(prefix)/pkg + $(INSTALL) -d $(prefix)/values $(bindir)/nix init diff --git a/src/eval.cc b/src/eval.cc new file mode 100644 index 000000000..14577c873 --- /dev/null +++ b/src/eval.cc @@ -0,0 +1,297 @@ +#include +#include + +#include +#include +#include +#include + +#include "eval.hh" +#include "globals.hh" +#include "values.hh" +#include "db.hh" + + +/* A Unix environment is a mapping from strings to strings. */ +typedef map Environment; + + +/* Return true iff the given path exists. */ +bool pathExists(string path) +{ + int res; + struct stat st; + res = stat(path.c_str(), &st); + if (!res) return true; + if (errno != ENOENT) + throw SysError("getting status of " + path); + return false; +} + + +/* Compute a derived value by running a program. */ +static Hash computeDerived(Hash sourceHash, string targetName, + string platform, Hash prog, Environment env) +{ + string targetPath = nixValues + "/" + + (string) sourceHash + "-nf"; + + /* Check whether the target already exists. */ + if (pathExists(targetPath)) + throw Error("derived value in " + targetPath + " already exists"); + + /* Find the program corresponding to the hash `prog'. */ + string progPath = queryValuePath(prog); + + /* Finalize the environment. */ + env["out"] = targetPath; + + /* Create a log file. */ + string logFileName = + nixLogDir + "/" + baseNameOf(targetPath) + ".log"; + /* !!! auto-pclose on exit */ + FILE * logFile = popen(("tee " + logFileName + " >&2").c_str(), "w"); /* !!! escaping */ + if (!logFile) + throw SysError("unable to create log file " + logFileName); + + try { + + /* Fork a child to build the package. */ + pid_t pid; + switch (pid = fork()) { + + case -1: + throw SysError("unable to fork"); + + case 0: + + try { /* child */ + +#if 0 + /* Try to use a prebuilt. */ + string prebuiltHashS, prebuiltFile; + if (queryDB(nixDB, dbPrebuilts, hash, prebuiltHashS)) { + + try { + prebuiltFile = getFile(parseHash(prebuiltHashS)); + } catch (Error e) { + cerr << "cannot obtain prebuilt (ignoring): " << e.what() << endl; + goto build; + } + + cerr << "substituting prebuilt " << prebuiltFile << endl; + + int res = system(("tar xfj " + prebuiltFile + " 1>&2").c_str()); // !!! escaping + if (WEXITSTATUS(res) != 0) + /* This is a fatal error, because path may now + have clobbered. */ + throw Error("cannot unpack " + prebuiltFile); + + _exit(0); + } +#endif + + build: + + /* Fill in the environment. We don't bother freeing + the strings, since we'll exec or die soon + anyway. */ + const char * env2[env.size() + 1]; + int i = 0; + for (Environment::iterator it = env.begin(); + it != env.end(); it++, i++) + env2[i] = (new string(it->first + "=" + it->second))->c_str(); + env2[i] = 0; + + /* Dup the log handle into stderr. */ + if (dup2(fileno(logFile), STDERR_FILENO) == -1) + throw Error("cannot pipe standard error into log file: " + string(strerror(errno))); + + /* Dup stderr to stdin. */ + if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1) + throw Error("cannot dup stderr into stdout"); + + /* Make the program executable. !!! hack. */ + if (chmod(progPath.c_str(), 0755)) + throw Error("cannot make program executable"); + + /* Execute the program. This should not return. */ + execle(progPath.c_str(), baseNameOf(progPath).c_str(), 0, env2); + + throw Error("unable to execute builder: " + + string(strerror(errno))); + + } catch (exception & e) { + cerr << "build error: " << e.what() << endl; + _exit(1); + } + + } + + /* parent */ + + /* Close the logging pipe. Note that this should not cause + the logger to exit until builder exits (because the latter + has an open file handle to the former). */ + pclose(logFile); + + /* Wait for the child to finish. */ + int status; + if (waitpid(pid, &status, 0) != pid) + throw Error("unable to wait for child"); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + throw Error("unable to build package"); + + /* Check whether the result was created. */ + if (!pathExists(targetPath)) + throw Error("program " + progPath + + " failed to create a result in " + targetPath); + + /* Remove write permission from the value. */ + int res = system(("chmod -R -w " + targetPath).c_str()); // !!! escaping + if (WEXITSTATUS(res) != 0) + throw Error("cannot remove write permission from " + targetPath); + + } catch (exception &) { +// system(("rm -rf " + targetPath).c_str()); + throw; + } + + /* Hash the result. */ + Hash targetHash = hashFile(targetPath); + + /* Register targetHash -> targetPath. !!! this should be in + values.cc. */ + setDB(nixDB, dbNFs, sourceHash, targetName); + + /* Register that targetHash was produced by evaluating + sourceHash; i.e., that targetHash is a normal form of + sourceHash. !!! this shouldn't be here */ + setDB(nixDB, dbNFs, sourceHash, targetHash); + + return targetHash; +} + + +/* Throw an exception if the given platform string is not supported by + the platform we are executing on. */ +static void checkPlatform(string platform) +{ + if (platform != thisSystem) + throw Error("a `" + platform + + "' is required, but I am a `" + thisSystem + "'"); +} + + +/* Throw an exception with an error message containing the given + aterm. */ +static Error badTerm(const string & msg, Expr e) +{ + char * s = ATwriteToString(e); + return Error(msg + ", in `" + s + "'"); +} + + +/* Hash an expression. Hopefully the representation used by + ATwriteToString() won't change, otherwise all hashes will + change. */ +static Hash hashExpr(Expr e) +{ + char * s = ATwriteToString(e); + debug(s); + return hashString(s); +} + + +/* Evaluate an expression; the result must be a string. */ +static string evalString(Expr e) +{ + e = evalValue(e).e; + char * s; + if (ATmatch(e, "Str()", &s)) return s; + else throw badTerm("string value expected", e); +} + + +/* Evaluate an expression; the result must be a external + non-expression reference. */ +static Hash evalExternal(Expr e) +{ + EvalResult r = evalValue(e); + char * s; + if (ATmatch(r.e, "External()", &s)) return r.h; + else throw badTerm("external non-expression value expected", r.e); +} + + +/* Evaluate an expression. */ +EvalResult evalValue(Expr e) +{ + EvalResult r; + char * s; + Expr eBuildPlatform, eProg; + ATermList args; + + /* Normal forms. */ + if (ATmatch(e, "Str()", &s) || + ATmatch(e, "Bool(True)") || + ATmatch(e, "Bool(False)")) + { + r.e = e; + } + + /* External expressions. */ + + /* External non-expressions. */ + else if (ATmatch(e, "External()", &s)) { + r.e = e; + r.h = parseHash(s); + } + + /* Execution primitive. */ + + else if (ATmatch(e, "Exec(, , [])", + &eBuildPlatform, &eProg, &args)) + { + string buildPlatform = evalString(eBuildPlatform); + + checkPlatform(buildPlatform); + + Hash prog = evalExternal(eProg); + + Environment env; + while (!ATisEmpty(args)) { + debug("arg"); + Expr arg = ATgetFirst(args); + throw badTerm("foo", arg); + args = ATgetNext(args); + } + + Hash sourceHash = hashExpr( + ATmake("Exec(Str(), External(), [])", + buildPlatform.c_str(), ((string) prog).c_str())); + + /* Do we know a normal form for sourceHash? */ + Hash targetHash; + string targetHashS; + if (queryDB(nixDB, dbNFs, sourceHash, targetHashS)) { + /* Yes. */ + targetHash = parseHash(targetHashS); + debug("already built: " + (string) sourceHash + + " -> " + (string) targetHash); + } else { + /* No, so we compute one. */ + targetHash = computeDerived(sourceHash, + (string) sourceHash + "-nf", buildPlatform, prog, env); + } + + r.e = ATmake("External()", ((string) targetHash).c_str()); + r.h = targetHash; + } + + /* Barf. */ + else throw badTerm("invalid expression", e); + + return r; +} diff --git a/src/eval.hh b/src/eval.hh new file mode 100644 index 000000000..bddc9f5d9 --- /dev/null +++ b/src/eval.hh @@ -0,0 +1,86 @@ +#ifndef __EVAL_H +#define __EVAL_H + +extern "C" { +#include +} + +#include "hash.hh" + +using namespace std; + + +/* Abstract syntax of Nix values: + + e := Hash(h) -- reference to expression value + | External(h) -- reference to non-expression value + | Str(s) -- string constant + | Bool(b) -- boolean constant + | App(e, e) -- application + | Lam(x, e) -- lambda abstraction + | Exec(platform, e, [(s, e)]) + -- primitive; execute e with args e* on platform + ; + + Semantics + + Each rules given as eval(e) => (e', h'), i.e., expression e has a + normal form e' with hash code h'. evalE = fst . eval. evalH = snd + . eval. + + eval(Hash(h)) => eval(loadExpr(h)) + + eval(External(h)) => (External(h), h) + + eval(Str(s)@e) => (e, 0) # idem for Bool + + eval(App(e1, e2)) => eval(App(e1', e2)) + where e1' = evalE(e1) + + eval(App(Lam(var, body), arg)@in) => + eval(subst(var, arg, body))@out + [AND write out to storage, and dbNFs[hash(in)] = hash(out) ???] + + eval(Exec(platform, prog, args)@e) => + (External(h), h) + where + hIn = hashExpr(e) + + fn = ... form name involving hIn ... + + h = + if exec(evalE(platform) => Str(...) + , getFile(evalH(prog)) + , map(makeArg . eval, args) + ) then + hashExternal(fn) + else + undef + + makeArg((argn, (External(h), h))) => (argn, getFile(h)) + makeArg((argn, (Str(s), _))) => (argn, s) + makeArg((argn, (Bool(True), _))) => (argn, "1") + makeArg((argn, (Bool(False), _))) => (argn, undef) + + getFile :: Hash -> FileName + loadExpr :: Hash -> FileName + hashExpr :: Expr -> Hash + hashExternal :: FileName -> Hash + exec :: Platform -> FileName -> [(String, String)] -> Status +*/ + +typedef ATerm Expr; + + +struct EvalResult +{ + Expr e; + Hash h; +}; + + +/* Evaluate an expression. */ +EvalResult evalValue(Expr e); + + +#endif /* !__EVAL_H */ diff --git a/src/globals.cc b/src/globals.cc new file mode 100644 index 000000000..14fb431d8 --- /dev/null +++ b/src/globals.cc @@ -0,0 +1,19 @@ +#include "globals.hh" +#include "db.hh" + + +string dbRefs = "refs"; +string dbNFs = "nfs"; +string dbNetSources = "netsources"; + +string nixValues = "/UNINIT"; +string nixLogDir = "/UNINIT"; +string nixDB = "/UNINIT"; + + +void initDB() +{ + createDB(nixDB, dbRefs); + createDB(nixDB, dbNFs); + createDB(nixDB, dbNetSources); +} diff --git a/src/globals.hh b/src/globals.hh new file mode 100644 index 000000000..d4fe4b370 --- /dev/null +++ b/src/globals.hh @@ -0,0 +1,60 @@ +#ifndef __GLOBALS_H +#define __GLOBALS_H + +#include + +using namespace std; + + +/* Database names. */ + +/* dbRefs :: Hash -> FileName + + Maintains a mapping from hashes to filenames within the NixValues + directory. This mapping is for performance only; it can be + reconstructed unambiguously. The reason is that names in this + directory are not printed hashes but also might carry some + descriptive element (e.g., "aterm-2.0-ae749a..."). Without this + mapping, looking up a value would take O(n) time because we would + need to read the entire directory. */ +extern string dbRefs; + +/* dbNFs :: Hash -> Hash + + Each pair (h1, h2) in this mapping records the fact that the value + referenced by h2 is a normal form obtained by evaluating the value + referenced by value h1. +*/ +extern string dbNFs; + +/* dbNetSources :: Hash -> URL + + Each pair (hash, url) in this mapping states that the value + identified by hash can be obtained by fetching the value pointed + to by url. + + TODO: this should be Hash -> [URL] + + TODO: factor this out into a separate tool? */ +extern string dbNetSources; + + +/* Path names. */ + +/* nixValues is the directory where all Nix values (both files and + directories, and both normal and non-normal forms) live. */ +extern string nixValues; + +/* nixLogDir is the directory where we log evaluations. */ +extern string nixLogDir; + +/* nixDB is the file name of the Berkeley DB database where we + maintain the dbXXX mappings. */ +extern string nixDB; + + +/* Initialize the databases. */ +void initDB(); + + +#endif /* !__GLOBALS_H */ diff --git a/src/hash.cc b/src/hash.cc index 25d76bd15..bb25c5168 100644 --- a/src/hash.cc +++ b/src/hash.cc @@ -46,6 +46,8 @@ Hash::operator string() const Hash parseHash(const string & s) { Hash hash; + if (s.length() != Hash::hashSize * 2) + throw BadRefError("invalid hash: " + s); for (unsigned int i = 0; i < Hash::hashSize; i++) { string s2(s, i * 2, 2); if (!isxdigit(s2[0]) || !isxdigit(s2[1])) @@ -73,15 +75,24 @@ bool isHash(const string & s) } +/* Compute the MD5 hash of a file. */ +Hash hashString(const string & s) +{ + Hash hash; + md5_buffer(s.c_str(), s.length(), hash.hash); + return hash; +} + + /* Compute the MD5 hash of a file. */ Hash hashFile(const string & fileName) { Hash hash; FILE * file = fopen(fileName.c_str(), "rb"); if (!file) - throw Error("file `" + fileName + "' does not exist"); + throw SysError("file `" + fileName + "' does not exist"); int err = md5_stream(file, hash.hash); fclose(file); - if (err) throw Error("cannot hash file"); + if (err) throw SysError("cannot hash file " + fileName); return hash; } diff --git a/src/hash.hh b/src/hash.hh index 162b2b1c8..6e20b3cbc 100644 --- a/src/hash.hh +++ b/src/hash.hh @@ -29,6 +29,7 @@ public: Hash parseHash(const string & s); bool isHash(const string & s); +Hash hashString(const string & s); Hash hashFile(const string & fileName); #endif /* !__HASH_H */ diff --git a/src/nix.cc b/src/nix.cc index 7990cde3a..db9f187e2 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -11,155 +11,15 @@ #include #include -extern "C" { -#include -} - #include "util.hh" #include "hash.hh" #include "db.hh" +#include "nix.hh" +#include "eval.hh" using namespace std; -/* Database names. */ - -/* dbRefs :: Hash -> FileName - - Maintains a mapping from hashes to filenames within the NixValues - directory. This mapping is for performance only; it can be - reconstructed unambiguously from the nixValues directory. The - reason is that names in this directory are not printed hashes but - also might carry some descriptive element (e.g., - "aterm-2.0-ae749a..."). Without this mapping, looking up a value - would take O(n) time because we would need to read the entire - directory. */ -static string dbRefs = "refs"; - -/* dbNFs :: Hash -> Hash - - Each pair (h1, h2) in this mapping records the fact that h2 is a - normal form obtained by evaluating the value h1. - - We would really like to have h2 be the hash of the object - referenced by h2. However, that gives a cyclic dependency: to - compute the hash (and thus the file name) of the object, we need to - compute the object, but to do that, we need the file name of the - object. - - So for now we abandon the requirement that - - hashFile(dbRefs[h]) == h. - - I.e., this property does not hold for computed normal forms. - Rather, we use h2 = hash(h1). This allows dbNFs to be - reconstructed. Perhaps using a pseudo random number would be - better to prevent the system from being subverted in some way. -*/ -static string dbNFs = "nfs"; - -/* dbNetSources :: Hash -> URL - - Each pair (hash, url) in this mapping states that the object - identified by hash can be obtained by fetching the object pointed - to by url. - - TODO: this should be Hash -> [URL] - - TODO: factor this out into a separate tool? */ -static string dbNetSources = "netsources"; - - -/* Path names. */ - -/* nixValues is the directory where all Nix values (both files and - directories, and both normal and non-normal forms) live. */ -static string nixValues; - -/* nixLogDir is the directory where we log evaluations. */ -static string nixLogDir; - -/* nixDB is the file name of the Berkeley DB database where we - maintain the dbXXX mappings. */ -static string nixDB; - - -/* Abstract syntax of Nix values: - - e := Hash(h) -- external reference - | Str(s) -- string constant - | Bool(b) -- boolean constant - | Name(e) -- "&" operator; pointer (file name) formation - | App(e, e) -- application - | Lam(x, e) -- lambda abstraction - | Exec(platform, e, e*) - -- primitive; execute e with args e* on platform - ; -*/ - - -/* Download object referenced by the given URL into the sources - directory. Return the file name it was downloaded to. */ -string fetchURL(string url) -{ - string filename = baseNameOf(url); - string fullname = nixSourcesDir + "/" + filename; - struct stat st; - if (stat(fullname.c_str(), &st)) { - cerr << "fetching " << url << endl; - /* !!! quoting */ - string shellCmd = - "cd " + nixSourcesDir + " && wget --quiet -N \"" + url + "\""; - int res = system(shellCmd.c_str()); - if (WEXITSTATUS(res) != 0) - throw Error("cannot fetch " + url); - } - return fullname; -} - - -/* Obtain an object with the given hash. If a file with that hash is - known to exist in the local file system (as indicated by the dbRefs - database), we use that. Otherwise, we attempt to fetch it from the - network (using dbNetSources). We verify that the file has the - right hash. */ -string getFile(Hash hash) -{ - bool checkedNet = false; - - while (1) { - - string fn, url; - - if (queryDB(nixDB, dbRefs, hash, fn)) { - - /* Verify that the file hasn't changed. !!! race */ - if (hashFile(fn) != hash) - throw Error("file " + fn + " is stale"); - - return fn; - } - - if (checkedNet) - throw Error("consistency problem: file fetched from " + url + - " should have hash " + (string) hash + ", but it doesn't"); - - if (!queryDB(nixDB, dbNetSources, hash, url)) - throw Error("a file with hash " + (string) hash + " is requested, " - "but it is not known to exist locally or on the network"); - - checkedNet = true; - - fn = fetchURL(url); - - setDB(nixDB, dbRefs, hash, fn); - } -} - - -typedef map Params; - - void readPkgDescr(Hash hash, Params & pkgImports, Params & fileImports, Params & arguments) { @@ -204,9 +64,6 @@ void readPkgDescr(Hash hash, string getPkg(Hash hash); -typedef map Environment; - - void fetchDeps(Hash hash, Environment & env) { /* Read the package description file. */ @@ -538,15 +395,6 @@ void registerInstalledPkg(Hash hash, string path) } -void initDB() -{ - createDB(nixDB, dbRefs); - createDB(nixDB, dbInstPkgs); - createDB(nixDB, dbPrebuilts); - createDB(nixDB, dbNetSources); -} - - void verifyDB() { /* Check that all file references are still valid. */ diff --git a/src/test-builder-1.sh b/src/test-builder-1.sh new file mode 100644 index 000000000..80e23354c --- /dev/null +++ b/src/test-builder-1.sh @@ -0,0 +1,3 @@ +#! /bin/sh + +echo "Hello World" > $out diff --git a/src/test-builder-2.sh b/src/test-builder-2.sh new file mode 100644 index 000000000..25a66532f --- /dev/null +++ b/src/test-builder-2.sh @@ -0,0 +1,5 @@ +#! /bin/sh + +mkdir $out || exit 1 +cd $out || exit 1 +echo "Hello World" > bla diff --git a/src/test.cc b/src/test.cc index cce226ba9..79468182e 100644 --- a/src/test.cc +++ b/src/test.cc @@ -1,16 +1,82 @@ #include +#include +#include + #include "hash.hh" +#include "util.hh" +#include "eval.hh" +#include "values.hh" +#include "globals.hh" + + +void evalTest(Expr e) +{ + EvalResult r = evalValue(e); + + char * s = ATwriteToString(r.e); + cout << (string) r.h << ": " << s << endl; +} + + +void runTests() +{ + /* Hashing. */ + string s = "0b0ffd0538622bfe20b92c4aa57254d9"; + Hash h = parseHash(s); + if ((string) h != s) abort(); + + try { + h = parseHash("blah blah"); + abort(); + } catch (BadRefError err) { }; + + try { + h = parseHash("0b0ffd0538622bfe20b92c4aa57254d99"); + abort(); + } catch (BadRefError err) { }; + + + /* Set up the test environment. */ + + mkdir("scratch", 0777); + + string testDir = absPath("scratch"); + cout << testDir << endl; + + nixValues = testDir; + nixLogDir = testDir; + nixDB = testDir + "/db"; + + initDB(); + + /* Expression evaluation. */ + + evalTest(ATmake("Str(\"Hello World\")")); + evalTest(ATmake("Bool(True)")); + evalTest(ATmake("Bool(False)")); + + Hash builder1 = addValue("./test-builder-1.sh"); + + evalTest(ATmake("Exec(Str(), External(), [])", + thisSystem.c_str(), ((string) builder1).c_str())); + + Hash builder2 = addValue("./test-builder-2.sh"); + + evalTest(ATmake("Exec(Str(), External(), [])", + thisSystem.c_str(), ((string) builder2).c_str())); +} + int main(int argc, char * * argv) { - Hash h = hashFile("/etc/passwd"); - - cout << (string) h << endl; + ATerm bottomOfStack; + ATinit(argc, argv, &bottomOfStack); - h = parseHash("0b0ffd0538622bfe20b92c4aa57254d9"); - - cout << (string) h << endl; - - return 0; + try { + runTests(); + } catch (exception & e) { + cerr << "error: " << e.what() << endl; + return 1; + } } diff --git a/src/util.cc b/src/util.cc index 299fc942f..8c397aace 100644 --- a/src/util.cc +++ b/src/util.cc @@ -1,47 +1,55 @@ +#include + #include "util.hh" string thisSystem = SYSTEM; -string nixHomeDir = "/nix"; -string nixHomeDirEnvVar = "NIX"; - -string absPath(string filename, string dir) +SysError::SysError(string msg) { - if (filename[0] != '/') { + char * sysMsg = strerror(errno); + err = msg + ": " + sysMsg; +} + + +string absPath(string path, string dir) +{ + if (path[0] != '/') { if (dir == "") { char buf[PATH_MAX]; if (!getcwd(buf, sizeof(buf))) - throw Error("cannot get cwd"); + throw SysError("cannot get cwd"); dir = buf; } - filename = dir + "/" + filename; + path = dir + "/" + path; /* !!! canonicalise */ char resolved[PATH_MAX]; - if (!realpath(filename.c_str(), resolved)) - throw Error("cannot canonicalise path " + filename); - filename = resolved; + if (!realpath(path.c_str(), resolved)) + throw SysError("cannot canonicalise path " + path); + path = resolved; } - return filename; + return path; } -/* Return the directory part of the given path, i.e., everything - before the final `/'. */ -string dirOf(string s) +string dirOf(string path) { - unsigned int pos = s.rfind('/'); - if (pos == string::npos) throw Error("invalid file name"); - return string(s, 0, pos); + unsigned int pos = path.rfind('/'); + if (pos == string::npos) throw Error("invalid file name: " + path); + return string(path, 0, pos); } -/* Return the base name of the given path, i.e., everything following - the final `/'. */ -string baseNameOf(string s) +string baseNameOf(string path) { - unsigned int pos = s.rfind('/'); - if (pos == string::npos) throw Error("invalid file name"); - return string(s, pos + 1); + unsigned int pos = path.rfind('/'); + if (pos == string::npos) throw Error("invalid file name: " + path); + return string(path, pos + 1); +} + + +void debug(string s) +{ + cerr << "debug: " << s << endl; } diff --git a/src/util.hh b/src/util.hh index d1a195609..5b41fcea8 100644 --- a/src/util.hh +++ b/src/util.hh @@ -12,13 +12,21 @@ using namespace std; class Error : public exception { +protected: string err; public: + Error() { } Error(string _err) { err = _err; } - ~Error() throw () { }; + ~Error() throw () { } const char * what() const throw () { return err.c_str(); } }; +class SysError : public Error +{ +public: + SysError(string msg); +}; + class UsageError : public Error { public: @@ -33,15 +41,20 @@ typedef vector Strings; extern string thisSystem; -/* The prefix of the Nix installation, and the environment variable - that can be used to override the default. */ -extern string nixHomeDir; -extern string nixHomeDirEnvVar; +/* Return an absolutized path, resolving paths relative to the + specified directory, or the current directory otherwise. */ +string absPath(string path, string dir = ""); + +/* Return the directory part of the given path, i.e., everything + before the final `/'. */ +string dirOf(string path); + +/* Return the base name of the given path, i.e., everything following + the final `/'. */ +string baseNameOf(string path); -string absPath(string filename, string dir = ""); -string dirOf(string s); -string baseNameOf(string s); +void debug(string s); #endif /* !__UTIL_H */ diff --git a/src/values.cc b/src/values.cc new file mode 100644 index 000000000..064203ae2 --- /dev/null +++ b/src/values.cc @@ -0,0 +1,100 @@ +#include "values.hh" +#include "globals.hh" +#include "db.hh" + + +static void copyFile(string src, string dst) +{ + int res = system(("cat " + src + " > " + dst).c_str()); /* !!! escape */ + if (WEXITSTATUS(res) != 0) + throw Error("cannot copy " + src + " to " + dst); +} + + +static string absValuePath(string s) +{ + return nixValues + "/" + s; +} + + +Hash addValue(string path) +{ + Hash hash = hashFile(path); + + string name; + if (queryDB(nixDB, dbRefs, hash, name)) { + debug((string) hash + " already known"); + return hash; + } + + string baseName = baseNameOf(path); + + string targetName = (string) hash + "-" + baseName; + + copyFile(path, absValuePath(targetName)); + + setDB(nixDB, dbRefs, hash, targetName); + + return hash; +} + + +#if 0 +/* Download object referenced by the given URL into the sources + directory. Return the file name it was downloaded to. */ +string fetchURL(string url) +{ + string filename = baseNameOf(url); + string fullname = nixSourcesDir + "/" + filename; + struct stat st; + if (stat(fullname.c_str(), &st)) { + cerr << "fetching " << url << endl; + /* !!! quoting */ + string shellCmd = + "cd " + nixSourcesDir + " && wget --quiet -N \"" + url + "\""; + int res = system(shellCmd.c_str()); + if (WEXITSTATUS(res) != 0) + throw Error("cannot fetch " + url); + } + return fullname; +} +#endif + + +string queryValuePath(Hash hash) +{ + bool checkedNet = false; + + while (1) { + + string name, url; + + if (queryDB(nixDB, dbRefs, hash, name)) { + string fn = absValuePath(name); + + /* Verify that the file hasn't changed. !!! race */ + if (hashFile(fn) != hash) + throw Error("file " + fn + " is stale"); + + return fn; + } + + throw Error("a file with hash " + (string) hash + " is requested, " + "but it is not known to exist locally or on the network"); +#if 0 + if (checkedNet) + throw Error("consistency problem: file fetched from " + url + + " should have hash " + (string) hash + ", but it doesn't"); + + if (!queryDB(nixDB, dbNetSources, hash, url)) + throw Error("a file with hash " + (string) hash + " is requested, " + "but it is not known to exist locally or on the network"); + + checkedNet = true; + + fn = fetchURL(url); + + setDB(nixDB, dbRefs, hash, fn); +#endif + } +} diff --git a/src/values.hh b/src/values.hh new file mode 100644 index 000000000..5dd7b89c4 --- /dev/null +++ b/src/values.hh @@ -0,0 +1,24 @@ +#ifndef __VALUES_H +#define __VALUES_H + +#include + +#include "hash.hh" + +using namespace std; + + +/* Copy a value to the nixValues directory and register it in dbRefs. + Return the hash code of the value. */ +Hash addValue(string pathName); + + +/* Obtain the path of a value with the given hash. If a file with + that hash is known to exist in the local file system (as indicated + by the dbRefs database), we use that. Otherwise, we attempt to + fetch it from the network (using dbNetSources). We verify that the + file has the right hash. */ +string queryValuePath(Hash hash); + + +#endif /* !__VALUES_H */ From a09e66da5af348dc25e3b372ec9f518d3532f863 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 16 Jun 2003 14:19:32 +0000 Subject: [PATCH 0071/6440] * Description of path hashing algorithm. --- src/hash.cc | 8 -------- src/hash.hh | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/hash.cc b/src/hash.cc index bb25c5168..9451ac8d8 100644 --- a/src/hash.cc +++ b/src/hash.cc @@ -6,14 +6,12 @@ extern "C" { #include -/* Create a zeroed hash object. */ Hash::Hash() { memset(hash, 0, sizeof(hash)); } -/* Check whether two hash are equal. */ bool Hash::operator == (Hash & h2) { for (unsigned int i = 0; i < hashSize; i++) @@ -22,14 +20,12 @@ bool Hash::operator == (Hash & h2) } -/* Check whether two hash are not equal. */ bool Hash::operator != (Hash & h2) { return !(*this == h2); } -/* Convert a hash code into a hexadecimal representation. */ Hash::operator string() const { ostringstream str; @@ -42,7 +38,6 @@ Hash::operator string() const } -/* Parse a hexadecimal representation of a hash code. */ Hash parseHash(const string & s) { Hash hash; @@ -61,7 +56,6 @@ Hash parseHash(const string & s) } -/* Verify that a reference is valid (that is, is a MD5 hash code). */ bool isHash(const string & s) { if (s.length() != 32) return false; @@ -75,7 +69,6 @@ bool isHash(const string & s) } -/* Compute the MD5 hash of a file. */ Hash hashString(const string & s) { Hash hash; @@ -84,7 +77,6 @@ Hash hashString(const string & s) } -/* Compute the MD5 hash of a file. */ Hash hashFile(const string & fileName) { Hash hash; diff --git a/src/hash.hh b/src/hash.hh index 6e20b3cbc..9d72e66db 100644 --- a/src/hash.hh +++ b/src/hash.hh @@ -13,9 +13,16 @@ struct Hash static const unsigned int hashSize = 16; unsigned char hash[hashSize]; + /* Create a zeroed hash object. */ Hash(); + + /* Check whether two hash are equal. */ bool operator == (Hash & h2); + + /* Check whether two hash are not equal. */ bool operator != (Hash & h2); + + /* Convert a hash code into a hexadecimal representation. */ operator string() const; }; @@ -27,9 +34,55 @@ public: }; +/* Parse a hexadecimal representation of a hash code. */ Hash parseHash(const string & s); + +/* Verify that the given string is a valid hash code. */ bool isHash(const string & s); + +/* Compute the hash of the given string. */ Hash hashString(const string & s); + +/* Compute the hash of the given file. */ Hash hashFile(const string & fileName); +/* Compute the hash of the given path. The hash is defined as + follows: + + hash(path) = md5(dump(path)) + + IF path points to a REGULAR FILE: + dump(path) = attrs( + [ ("type", "regular") + , ("contents", contents(path)) + ]) + + IF path points to a DIRECTORY: + dump(path) = attrs( + [ ("type", "directory") + , ("entries", concat(map(f, entries(path)))) + ]) + where f(fn) = attrs( + [ ("name", fn) + , ("file", dump(path + "/" + fn)) + ]) + + where: + + attrs(as) = concat(map(attr, as)) + encN(0) + attrs((a, b)) = encS(a) + encS(b) + + encS(s) = encN(len(s)) + s + + encN(n) = 64-bit little-endian encoding of n. + + contents(path) = the contents of a regular file. + + entries(path) = the entries of a directory, without `.' and + `..'. + + `+' denotes string concatenation. */ +Hash hashPath(const string & path); + + #endif /* !__HASH_H */ From 2f04e7102eaad3159073019af96e6e5c4f2c9bcf Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 16 Jun 2003 15:59:23 +0000 Subject: [PATCH 0072/6440] * Path hashing. --- src/eval.cc | 4 +- src/hash.cc | 141 +++++++++++++++++++++++++++++++++++++++++++++++++- src/hash.hh | 19 +++++-- src/test.cc | 16 ++++++ src/values.cc | 6 +-- 5 files changed, 178 insertions(+), 8 deletions(-) diff --git a/src/eval.cc b/src/eval.cc index 14577c873..c96cf6467 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -148,10 +148,12 @@ static Hash computeDerived(Hash sourceHash, string targetName, throw Error("program " + progPath + " failed to create a result in " + targetPath); +#if 0 /* Remove write permission from the value. */ int res = system(("chmod -R -w " + targetPath).c_str()); // !!! escaping if (WEXITSTATUS(res) != 0) throw Error("cannot remove write permission from " + targetPath); +#endif } catch (exception &) { // system(("rm -rf " + targetPath).c_str()); @@ -159,7 +161,7 @@ static Hash computeDerived(Hash sourceHash, string targetName, } /* Hash the result. */ - Hash targetHash = hashFile(targetPath); + Hash targetHash = hashPath(targetPath); /* Register targetHash -> targetPath. !!! this should be in values.cc. */ diff --git a/src/hash.cc b/src/hash.cc index 9451ac8d8..9558d3670 100644 --- a/src/hash.cc +++ b/src/hash.cc @@ -1,9 +1,16 @@ +#include + +#include +#include +#include +#include +#include + extern "C" { #include "md5.h" } #include "hash.hh" -#include Hash::Hash() @@ -88,3 +95,135 @@ Hash hashFile(const string & fileName) if (err) throw SysError("cannot hash file " + fileName); return hash; } + + +struct HashSink : DumpSink +{ + struct md5_ctx ctx; + virtual void operator () + (const unsigned char * data, unsigned int len) + { + md5_process_bytes(data, len, &ctx); + } +}; + + +Hash hashPath(const string & path) +{ + Hash hash; + HashSink sink; + md5_init_ctx(&sink.ctx); + dumpPath(path, sink); + md5_finish_ctx(&sink.ctx, hash.hash); + return hash; +} + + +static void pad(unsigned int len, DumpSink & sink) +{ + if (len % 8) { + unsigned char zero[8]; + memset(zero, 0, sizeof(zero)); + sink(zero, 8 - (len % 8)); + } +} + + +static void writeInt(unsigned int n, DumpSink & sink) +{ + unsigned char buf[8]; + memset(buf, 0, sizeof(buf)); + buf[0] = n & 0xff; + buf[1] = (n >> 8) & 0xff; + buf[2] = (n >> 16) & 0xff; + buf[3] = (n >> 24) & 0xff; + sink(buf, sizeof(buf)); +} + + +static void writeString(const string & s, DumpSink & sink) +{ + unsigned int len = s.length(); + writeInt(len, sink); + sink((const unsigned char *) s.c_str(), len); + pad(len, sink); +} + + +static void dumpEntries(const string & path, DumpSink & sink) +{ + DIR * dir = opendir(path.c_str()); + if (!dir) throw SysError("opening directory " + path); + + struct dirent * dirent; + + /* !!! sort entries */ + + while (errno = 0, dirent = readdir(dir)) { + string name = dirent->d_name; + if (name == "." || name == "..") continue; + writeString("entry", sink); + writeString("(", sink); + writeString("name", sink); + writeString(name, sink); + writeString("file", sink); + dumpPath(path + "/" + name, sink); + writeString(")", sink); + } + + if (errno) throw SysError("reading directory " + path); + + closedir(dir); /* !!! close on exception */ +} + + +static void dumpContents(const string & path, unsigned int size, + DumpSink & sink) +{ + writeString("contents", sink); + writeInt(size, sink); + + int fd = open(path.c_str(), O_RDONLY); + if (!fd) throw SysError("opening file " + path); + + unsigned char buf[16384]; + + unsigned int total = 0; + ssize_t n; + while ((n = read(fd, buf, sizeof(buf)))) { + if (n == -1) throw SysError("reading file " + path); + total += n; + sink(buf, n); + } + + if (total != size) + throw SysError("file changed while reading it: " + path); + + pad(size, sink); + + close(fd); /* !!! close on exception */ +} + + +void dumpPath(const string & path, DumpSink & sink) +{ + cerr << path << endl; + + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError("getting attributes of path " + path); + + writeString("(", sink); + + if (S_ISREG(st.st_mode)) { + writeString("type", sink); + writeString("regular", sink); + dumpContents(path, st.st_size, sink); + } else if (S_ISDIR(st.st_mode)) { + writeString("type", sink); + writeString("directory", sink); + dumpEntries(path, sink); + } else throw Error("unknown file type: " + path); + + writeString(")", sink); +} diff --git a/src/hash.hh b/src/hash.hh index 9d72e66db..13c5275b4 100644 --- a/src/hash.hh +++ b/src/hash.hh @@ -50,6 +50,11 @@ Hash hashFile(const string & fileName); follows: hash(path) = md5(dump(path)) +*/ +Hash hashPath(const string & path); + + +/* Dump a path as follows: IF path points to a REGULAR FILE: dump(path) = attrs( @@ -60,7 +65,7 @@ Hash hashFile(const string & fileName); IF path points to a DIRECTORY: dump(path) = attrs( [ ("type", "directory") - , ("entries", concat(map(f, entries(path)))) + , ("entries", concat(map(f, sort(entries(path))))) ]) where f(fn) = attrs( [ ("name", fn) @@ -72,17 +77,25 @@ Hash hashFile(const string & fileName); attrs(as) = concat(map(attr, as)) + encN(0) attrs((a, b)) = encS(a) + encS(b) - encS(s) = encN(len(s)) + s + encS(s) = encN(len(s)) + s + (padding until next 64-bit boundary) encN(n) = 64-bit little-endian encoding of n. contents(path) = the contents of a regular file. + sort(strings) = lexicographic sort by 8-bit value (strcmp). + entries(path) = the entries of a directory, without `.' and `..'. `+' denotes string concatenation. */ -Hash hashPath(const string & path); + +struct DumpSink +{ + virtual void operator () (const unsigned char * data, unsigned int len) = 0; +}; + +void dumpPath(const string & path, DumpSink & sink); #endif /* !__HASH_H */ diff --git a/src/test.cc b/src/test.cc index 79468182e..b37a16a1f 100644 --- a/src/test.cc +++ b/src/test.cc @@ -19,6 +19,15 @@ void evalTest(Expr e) } +struct MySink : DumpSink +{ + virtual void operator () (const unsigned char * data, unsigned int len) + { + cout.write((char *) data, len); + } +}; + + void runTests() { /* Hashing. */ @@ -36,6 +45,13 @@ void runTests() abort(); } catch (BadRefError err) { }; + /* Dumping. */ + +#if 0 + MySink sink; + dumpPath("scratch", sink); + cout << (string) hashPath("scratch") << endl; +#endif /* Set up the test environment. */ diff --git a/src/values.cc b/src/values.cc index 064203ae2..77a6f928e 100644 --- a/src/values.cc +++ b/src/values.cc @@ -19,7 +19,7 @@ static string absValuePath(string s) Hash addValue(string path) { - Hash hash = hashFile(path); + Hash hash = hashPath(path); string name; if (queryDB(nixDB, dbRefs, hash, name)) { @@ -72,8 +72,8 @@ string queryValuePath(Hash hash) if (queryDB(nixDB, dbRefs, hash, name)) { string fn = absValuePath(name); - /* Verify that the file hasn't changed. !!! race */ - if (hashFile(fn) != hash) + /* Verify that the file hasn't changed. !!! race !!! slow */ + if (hashPath(fn) != hash) throw Error("file " + fn + " is stale"); return fn; From 727beb798a701ff546adc65030f1562b87283947 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 16 Jun 2003 16:16:09 +0000 Subject: [PATCH 0073/6440] * Canonicalization: when hashing directories, sort the directory entries by name. --- src/hash.cc | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/hash.cc b/src/hash.cc index 9558d3670..37f6104fb 100644 --- a/src/hash.cc +++ b/src/hash.cc @@ -154,24 +154,30 @@ static void dumpEntries(const string & path, DumpSink & sink) { DIR * dir = opendir(path.c_str()); if (!dir) throw SysError("opening directory " + path); - + + Strings names; + struct dirent * dirent; - - /* !!! sort entries */ - while (errno = 0, dirent = readdir(dir)) { string name = dirent->d_name; if (name == "." || name == "..") continue; + names.push_back(name); + } + if (errno) throw SysError("reading directory " + path); + + sort(names.begin(), names.end()); + + for (Strings::iterator it = names.begin(); + it != names.end(); it++) + { writeString("entry", sink); writeString("(", sink); writeString("name", sink); - writeString(name, sink); + writeString(*it, sink); writeString("file", sink); - dumpPath(path + "/" + name, sink); + dumpPath(path + "/" + *it, sink); writeString(")", sink); } - - if (errno) throw SysError("reading directory " + path); closedir(dir); /* !!! close on exception */ } From c739e2058560ad018dcf68e16fa683ca404d548c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 16 Jun 2003 21:01:18 +0000 Subject: [PATCH 0074/6440] * Argument processing. --- src/eval.cc | 38 +++++++++++++++++++++++++++++++------- src/test-builder-2.sh | 5 ++++- src/test.cc | 13 +++++++++---- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/eval.cc b/src/eval.cc index c96cf6467..e6a3478a1 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -165,7 +165,7 @@ static Hash computeDerived(Hash sourceHash, string targetName, /* Register targetHash -> targetPath. !!! this should be in values.cc. */ - setDB(nixDB, dbNFs, sourceHash, targetName); + setDB(nixDB, dbRefs, targetHash, targetName); /* Register that targetHash was produced by evaluating sourceHash; i.e., that targetHash is a normal form of @@ -227,6 +227,34 @@ static Hash evalExternal(Expr e) } +/* Evaluate a list of arguments into normal form. */ +void evalArgs(ATermList args, ATermList & argsNF, Environment & env) +{ + argsNF = ATempty; + + while (!ATisEmpty(args)) { + ATerm eName, eVal, arg = ATgetFirst(args); + if (!ATmatch(arg, "Tup(, )", &eName, &eVal)) + throw badTerm("invalid argument", arg); + + string name = evalString(eName); + eVal = evalValue(eVal).e; + + char * s; + if (ATmatch(eVal, "Str()", &s)) { + env[name] = s; + } else if (ATmatch(eVal, "External()", &s)) { + env[name] = queryValuePath(parseHash(s)); + } else throw badTerm("invalid argument value", eVal); + + argsNF = ATappend(argsNF, + ATmake("Tup(Str(), )", name.c_str(), eVal)); + + args = ATgetNext(args); + } +} + + /* Evaluate an expression. */ EvalResult evalValue(Expr e) { @@ -263,12 +291,8 @@ EvalResult evalValue(Expr e) Hash prog = evalExternal(eProg); Environment env; - while (!ATisEmpty(args)) { - debug("arg"); - Expr arg = ATgetFirst(args); - throw badTerm("foo", arg); - args = ATgetNext(args); - } + ATermList argsNF; + evalArgs(args, argsNF, env); Hash sourceHash = hashExpr( ATmake("Exec(Str(), External(), [])", diff --git a/src/test-builder-2.sh b/src/test-builder-2.sh index 25a66532f..010e1c805 100644 --- a/src/test-builder-2.sh +++ b/src/test-builder-2.sh @@ -1,5 +1,8 @@ #! /bin/sh +echo "builder 2" + mkdir $out || exit 1 cd $out || exit 1 -echo "Hello World" > bla +echo "Hallo Wereld" > bla +cat $src >> bla \ No newline at end of file diff --git a/src/test.cc b/src/test.cc index b37a16a1f..bf7ee191f 100644 --- a/src/test.cc +++ b/src/test.cc @@ -74,13 +74,18 @@ void runTests() Hash builder1 = addValue("./test-builder-1.sh"); - evalTest(ATmake("Exec(Str(), External(), [])", - thisSystem.c_str(), ((string) builder1).c_str())); + Expr e1 = ATmake("Exec(Str(), External(), [])", + thisSystem.c_str(), ((string) builder1).c_str()); + + evalTest(e1); Hash builder2 = addValue("./test-builder-2.sh"); - evalTest(ATmake("Exec(Str(), External(), [])", - thisSystem.c_str(), ((string) builder2).c_str())); + Expr e2 = ATmake( + "Exec(Str(), External(), [Tup(Str(\"src\"), )])", + thisSystem.c_str(), ((string) builder2).c_str(), e1); + + evalTest(e2); } From a7ab242fb42dad81dc1bccdca4b432587e0957dd Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 17 Jun 2003 13:37:44 +0000 Subject: [PATCH 0075/6440] * Simplify the evaluator. --- src/eval.cc | 51 ++++++++++++++++++++++-------------------------- src/eval.hh | 56 ++++++++++++++++++++++++----------------------------- src/test.cc | 6 ++---- 3 files changed, 50 insertions(+), 63 deletions(-) diff --git a/src/eval.cc b/src/eval.cc index e6a3478a1..ad6f5ae2a 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -186,30 +186,31 @@ static void checkPlatform(string platform) } +string printExpr(Expr e) +{ + char * s = ATwriteToString(e); + return s; +} + + /* Throw an exception with an error message containing the given aterm. */ static Error badTerm(const string & msg, Expr e) { - char * s = ATwriteToString(e); - return Error(msg + ", in `" + s + "'"); + return Error(msg + ", in `" + printExpr(e) + "'"); } -/* Hash an expression. Hopefully the representation used by - ATwriteToString() won't change, otherwise all hashes will - change. */ -static Hash hashExpr(Expr e) +Hash hashExpr(Expr e) { - char * s = ATwriteToString(e); - debug(s); - return hashString(s); + return hashString(printExpr(e)); } /* Evaluate an expression; the result must be a string. */ static string evalString(Expr e) { - e = evalValue(e).e; + e = evalValue(e); char * s; if (ATmatch(e, "Str()", &s)) return s; else throw badTerm("string value expected", e); @@ -220,10 +221,10 @@ static string evalString(Expr e) non-expression reference. */ static Hash evalExternal(Expr e) { - EvalResult r = evalValue(e); + e = evalValue(e); char * s; - if (ATmatch(r.e, "External()", &s)) return r.h; - else throw badTerm("external non-expression value expected", r.e); + if (ATmatch(e, "External()", &s)) return parseHash(s); + else throw badTerm("external non-expression value expected", e); } @@ -238,7 +239,7 @@ void evalArgs(ATermList args, ATermList & argsNF, Environment & env) throw badTerm("invalid argument", arg); string name = evalString(eName); - eVal = evalValue(eVal).e; + eVal = evalValue(eVal); char * s; if (ATmatch(eVal, "Str()", &s)) { @@ -256,9 +257,8 @@ void evalArgs(ATermList args, ATermList & argsNF, Environment & env) /* Evaluate an expression. */ -EvalResult evalValue(Expr e) +Expr evalValue(Expr e) { - EvalResult r; char * s; Expr eBuildPlatform, eProg; ATermList args; @@ -267,21 +267,19 @@ EvalResult evalValue(Expr e) if (ATmatch(e, "Str()", &s) || ATmatch(e, "Bool(True)") || ATmatch(e, "Bool(False)")) - { - r.e = e; - } + return e; /* External expressions. */ /* External non-expressions. */ - else if (ATmatch(e, "External()", &s)) { - r.e = e; - r.h = parseHash(s); + if (ATmatch(e, "External()", &s)) { + parseHash(s); /* i.e., throw exception if not valid */ + return e; } /* Execution primitive. */ - else if (ATmatch(e, "Exec(, , [])", + if (ATmatch(e, "Exec(, , [])", &eBuildPlatform, &eProg, &args)) { string buildPlatform = evalString(eBuildPlatform); @@ -312,12 +310,9 @@ EvalResult evalValue(Expr e) (string) sourceHash + "-nf", buildPlatform, prog, env); } - r.e = ATmake("External()", ((string) targetHash).c_str()); - r.h = targetHash; + return ATmake("External()", ((string) targetHash).c_str()); } /* Barf. */ - else throw badTerm("invalid expression", e); - - return r; + throw badTerm("invalid expression", e); } diff --git a/src/eval.hh b/src/eval.hh index bddc9f5d9..1a8edcfde 100644 --- a/src/eval.hh +++ b/src/eval.hh @@ -18,69 +18,63 @@ using namespace std; | Bool(b) -- boolean constant | App(e, e) -- application | Lam(x, e) -- lambda abstraction - | Exec(platform, e, [(s, e)]) + | Exec(platform, e, [Arg(e, e)]) -- primitive; execute e with args e* on platform ; Semantics - Each rules given as eval(e) => (e', h'), i.e., expression e has a - normal form e' with hash code h'. evalE = fst . eval. evalH = snd - . eval. + Each rule given as eval(e) => e', i.e., expression e has a normal + form e'. eval(Hash(h)) => eval(loadExpr(h)) - eval(External(h)) => (External(h), h) - - eval(Str(s)@e) => (e, 0) # idem for Bool + eval(External(h)) => External(h) # idem for Str, Bool eval(App(e1, e2)) => eval(App(e1', e2)) - where e1' = evalE(e1) + where e1' = eval(e1) - eval(App(Lam(var, body), arg)@in) => - eval(subst(var, arg, body))@out - [AND write out to storage, and dbNFs[hash(in)] = hash(out) ???] + eval(App(Lam(var, body), arg)) => + eval(subst(var, arg, body)) - eval(Exec(platform, prog, args)@e) => + eval(Exec(platform, prog, args)) => (External(h), h) where - hIn = hashExpr(e) - - fn = ... form name involving hIn ... - + fn = ... name of the output (random or by hashing expr) ... h = - if exec(evalE(platform) => Str(...) - , getFile(evalH(prog)) + if exec( fn + , eval(platform) => Str(...) + , getFile(eval(prog)) , map(makeArg . eval, args) ) then hashExternal(fn) else undef + ... register ... - makeArg((argn, (External(h), h))) => (argn, getFile(h)) - makeArg((argn, (Str(s), _))) => (argn, s) - makeArg((argn, (Bool(True), _))) => (argn, "1") - makeArg((argn, (Bool(False), _))) => (argn, undef) + makeArg(Arg(Str(nm), (External(h), h))) => (nm, getFile(h)) + makeArg(Arg(Str(nm), (Str(s), _))) => (nm, s) + makeArg(Arg(Str(nm), (Bool(True), _))) => (nm, "1") + makeArg(Arg(Str(nm), (Bool(False), _))) => (nm, undef) getFile :: Hash -> FileName loadExpr :: Hash -> FileName hashExpr :: Expr -> Hash hashExternal :: FileName -> Hash - exec :: Platform -> FileName -> [(String, String)] -> Status + exec :: FileName -> Platform -> FileName -> [(String, String)] -> Status */ typedef ATerm Expr; -struct EvalResult -{ - Expr e; - Hash h; -}; - - /* Evaluate an expression. */ -EvalResult evalValue(Expr e); +Expr evalValue(Expr e); + +/* Return a canonical textual representation of an expression. */ +string printExpr(Expr e); + +/* Hash an expression. */ +Hash hashExpr(Expr e); #endif /* !__EVAL_H */ diff --git a/src/test.cc b/src/test.cc index bf7ee191f..564149562 100644 --- a/src/test.cc +++ b/src/test.cc @@ -12,10 +12,8 @@ void evalTest(Expr e) { - EvalResult r = evalValue(e); - - char * s = ATwriteToString(r.e); - cout << (string) r.h << ": " << s << endl; + e = evalValue(e); + cout << (string) hashExpr(e) << ": " << printExpr(e) << endl; } From 6656993f83fa125e7b72de3962fbb5dd71cc31a4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 17 Jun 2003 15:45:43 +0000 Subject: [PATCH 0076/6440] * Derefencing of hashed expressions. --- src/eval.cc | 33 +++++++++++++++++++-------------- src/eval.hh | 21 ++++++++++++--------- src/globals.hh | 10 +++++++--- src/test.cc | 9 +++++++-- 4 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/eval.cc b/src/eval.cc index ad6f5ae2a..3f8aa7b23 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -217,14 +217,13 @@ static string evalString(Expr e) } -/* Evaluate an expression; the result must be a external - non-expression reference. */ -static Hash evalExternal(Expr e) +/* Evaluate an expression; the result must be a value reference. */ +static Hash evalHash(Expr e) { e = evalValue(e); char * s; - if (ATmatch(e, "External()", &s)) return parseHash(s); - else throw badTerm("external non-expression value expected", e); + if (ATmatch(e, "Hash()", &s)) return parseHash(s); + else throw badTerm("value reference expected", e); } @@ -244,7 +243,7 @@ void evalArgs(ATermList args, ATermList & argsNF, Environment & env) char * s; if (ATmatch(eVal, "Str()", &s)) { env[name] = s; - } else if (ATmatch(eVal, "External()", &s)) { + } else if (ATmatch(eVal, "Hash()", &s)) { env[name] = queryValuePath(parseHash(s)); } else throw badTerm("invalid argument value", eVal); @@ -260,7 +259,7 @@ void evalArgs(ATermList args, ATermList & argsNF, Environment & env) Expr evalValue(Expr e) { char * s; - Expr eBuildPlatform, eProg; + Expr eBuildPlatform, eProg, e2; ATermList args; /* Normal forms. */ @@ -269,14 +268,20 @@ Expr evalValue(Expr e) ATmatch(e, "Bool(False)")) return e; - /* External expressions. */ - - /* External non-expressions. */ - if (ATmatch(e, "External()", &s)) { + /* Value references. */ + if (ATmatch(e, "Hash()", &s)) { parseHash(s); /* i.e., throw exception if not valid */ return e; } + /* External expression. */ + if (ATmatch(e, "Deref()", &e2)) { + string fn = queryValuePath(evalHash(e2)); + ATerm e3 = ATreadFromNamedFile(fn.c_str()); + if (!e3) throw Error("reading aterm from " + fn); + return e3; + } + /* Execution primitive. */ if (ATmatch(e, "Exec(, , [])", @@ -286,14 +291,14 @@ Expr evalValue(Expr e) checkPlatform(buildPlatform); - Hash prog = evalExternal(eProg); + Hash prog = evalHash(eProg); Environment env; ATermList argsNF; evalArgs(args, argsNF, env); Hash sourceHash = hashExpr( - ATmake("Exec(Str(), External(), [])", + ATmake("Exec(Str(), Hash(), [])", buildPlatform.c_str(), ((string) prog).c_str())); /* Do we know a normal form for sourceHash? */ @@ -310,7 +315,7 @@ Expr evalValue(Expr e) (string) sourceHash + "-nf", buildPlatform, prog, env); } - return ATmake("External()", ((string) targetHash).c_str()); + return ATmake("Hash()", ((string) targetHash).c_str()); } /* Barf. */ diff --git a/src/eval.hh b/src/eval.hh index 1a8edcfde..719edb143 100644 --- a/src/eval.hh +++ b/src/eval.hh @@ -12,24 +12,28 @@ using namespace std; /* Abstract syntax of Nix values: - e := Hash(h) -- reference to expression value - | External(h) -- reference to non-expression value + e := Deref(e) -- external expression + | Hash(h) -- value reference | Str(s) -- string constant | Bool(b) -- boolean constant + | Var(x) -- variable | App(e, e) -- application | Lam(x, e) -- lambda abstraction | Exec(platform, e, [Arg(e, e)]) -- primitive; execute e with args e* on platform ; + TODO: Deref(e) allows computed external expressions, which might be + too expressive; perhaps this should be Deref(h). + Semantics Each rule given as eval(e) => e', i.e., expression e has a normal form e'. - eval(Hash(h)) => eval(loadExpr(h)) + eval(Deref(Hash(h))) => eval(loadExpr(h)) - eval(External(h)) => External(h) # idem for Str, Bool + eval(Hash(h)) => Hash(h) # idem for Str, Bool eval(App(e1, e2)) => eval(App(e1', e2)) where e1' = eval(e1) @@ -37,8 +41,7 @@ using namespace std; eval(App(Lam(var, body), arg)) => eval(subst(var, arg, body)) - eval(Exec(platform, prog, args)) => - (External(h), h) + eval(Exec(platform, prog, args)) => Hash(h) where fn = ... name of the output (random or by hashing expr) ... h = @@ -47,12 +50,12 @@ using namespace std; , getFile(eval(prog)) , map(makeArg . eval, args) ) then - hashExternal(fn) + hashPath(fn) else undef ... register ... - makeArg(Arg(Str(nm), (External(h), h))) => (nm, getFile(h)) + makeArg(Arg(Str(nm), (Hash(h), h))) => (nm, getFile(h)) makeArg(Arg(Str(nm), (Str(s), _))) => (nm, s) makeArg(Arg(Str(nm), (Bool(True), _))) => (nm, "1") makeArg(Arg(Str(nm), (Bool(False), _))) => (nm, undef) @@ -60,7 +63,7 @@ using namespace std; getFile :: Hash -> FileName loadExpr :: Hash -> FileName hashExpr :: Expr -> Hash - hashExternal :: FileName -> Hash + hashPath :: FileName -> Hash exec :: FileName -> Platform -> FileName -> [(String, String)] -> Status */ diff --git a/src/globals.hh b/src/globals.hh index d4fe4b370..b81a78714 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -21,9 +21,13 @@ extern string dbRefs; /* dbNFs :: Hash -> Hash - Each pair (h1, h2) in this mapping records the fact that the value - referenced by h2 is a normal form obtained by evaluating the value - referenced by value h1. + Each pair (h1, h2) in this mapping records the fact that the normal + form of an expression with hash h1 is Hash(h2). + + TODO: maybe this should be that the normal form of an expression + with hash h1 is an expression with hash h2; this would be more + general, but would require us to store lots of small expressions in + the file system just to support the caching mechanism. */ extern string dbNFs; diff --git a/src/test.cc b/src/test.cc index 564149562..39d4b333f 100644 --- a/src/test.cc +++ b/src/test.cc @@ -72,7 +72,7 @@ void runTests() Hash builder1 = addValue("./test-builder-1.sh"); - Expr e1 = ATmake("Exec(Str(), External(), [])", + Expr e1 = ATmake("Exec(Str(), Hash(), [])", thisSystem.c_str(), ((string) builder1).c_str()); evalTest(e1); @@ -80,10 +80,15 @@ void runTests() Hash builder2 = addValue("./test-builder-2.sh"); Expr e2 = ATmake( - "Exec(Str(), External(), [Tup(Str(\"src\"), )])", + "Exec(Str(), Hash(), [Tup(Str(\"src\"), )])", thisSystem.c_str(), ((string) builder2).c_str(), e1); evalTest(e2); + + Hash h3 = addValue("./test-expr.nix"); + Expr e3 = ATmake("Deref(Hash())", ((string) h3).c_str()); + + evalTest(e3); } From 7a96da3627220d11a985662446e8a75fb8cc2d40 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 17 Jun 2003 15:47:25 +0000 Subject: [PATCH 0077/6440] * Test for expression dereferencing. --- src/test-expr-1.nix | 1 + src/test.cc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 src/test-expr-1.nix diff --git a/src/test-expr-1.nix b/src/test-expr-1.nix new file mode 100644 index 000000000..a94ee7382 --- /dev/null +++ b/src/test-expr-1.nix @@ -0,0 +1 @@ +Str("Hello World") diff --git a/src/test.cc b/src/test.cc index 39d4b333f..334e409de 100644 --- a/src/test.cc +++ b/src/test.cc @@ -85,7 +85,7 @@ void runTests() evalTest(e2); - Hash h3 = addValue("./test-expr.nix"); + Hash h3 = addValue("./test-expr-1.nix"); Expr e3 = ATmake("Deref(Hash())", ((string) h3).c_str()); evalTest(e3); From 34fcf5fa0c0cc02edc6820b99d98e7ae278c6c00 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 17 Jun 2003 21:12:58 +0000 Subject: [PATCH 0078/6440] * Started integrating the new evaluation model into Nix. * Cleaned up command-line syntax. --- configure.ac | 6 + src/Makefile.am | 8 +- src/eval.cc | 6 +- src/nix.cc | 796 ++++++++---------------------------------------- src/values.cc | 6 +- 5 files changed, 143 insertions(+), 679 deletions(-) diff --git a/configure.ac b/configure.ac index a25ea785c..ff9b5beb7 100644 --- a/configure.ac +++ b/configure.ac @@ -10,5 +10,11 @@ AC_CANONICAL_HOST AC_PROG_CC AC_PROG_CXX +# Unix shell scripting should die a slow and painful death. +AC_DEFINE_UNQUOTED(NIX_VALUES_DIR, "$(eval echo $prefix/values)", Nix values directory.) +AC_DEFINE_UNQUOTED(NIX_STATE_DIR, "$(eval echo $localstatedir/nix)", Nix state directory.) +AC_DEFINE_UNQUOTED(NIX_LOG_DIR, "$(eval echo $localstatedir/log/nix)", Nix log file directory.) + +AC_CONFIG_HEADER([config.h]) AC_CONFIG_FILES([Makefile src/Makefile scripts/Makefile]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index 80d9e4af8..8c8ba3271 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,9 +1,9 @@ -bin_PROGRAMS = nix fix +bin_PROGRAMS = nix # fix noinst_PROGRAMS = test -AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall +AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. -nix_SOURCES = nix.cc db.cc util.cc hash.cc md5.c +nix_SOURCES = nix.cc util.cc hash.cc md5.c eval.cc values.cc globals.cc db.cc nix_LDADD = -ldb_cxx-4 -lATerm fix_SOURCES = fix.cc util.cc hash.cc md5.c @@ -20,4 +20,4 @@ install-data-local: # $(INSTALL) -d $(localstatedir)/nix/prebuilts/exports $(INSTALL) -d $(localstatedir)/log/nix $(INSTALL) -d $(prefix)/values - $(bindir)/nix init + $(bindir)/nix --init diff --git a/src/eval.cc b/src/eval.cc index 3f8aa7b23..7cb13d9cc 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -279,7 +279,7 @@ Expr evalValue(Expr e) string fn = queryValuePath(evalHash(e2)); ATerm e3 = ATreadFromNamedFile(fn.c_str()); if (!e3) throw Error("reading aterm from " + fn); - return e3; + return evalValue(e3); } /* Execution primitive. */ @@ -298,8 +298,8 @@ Expr evalValue(Expr e) evalArgs(args, argsNF, env); Hash sourceHash = hashExpr( - ATmake("Exec(Str(), Hash(), [])", - buildPlatform.c_str(), ((string) prog).c_str())); + ATmake("Exec(Str(), Hash(), )", + buildPlatform.c_str(), ((string) prog).c_str(), argsNF)); /* Do we know a normal form for sourceHash? */ Hash targetHash; diff --git a/src/nix.cc b/src/nix.cc index db9f187e2..fae3175ba 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -1,715 +1,171 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "config.h" -#include "util.hh" -#include "hash.hh" -#include "db.hh" -#include "nix.hh" +#include "globals.hh" +#include "values.hh" #include "eval.hh" -using namespace std; + +typedef void (* Operation) (Strings opFlags, Strings opArgs); -void readPkgDescr(Hash hash, - Params & pkgImports, Params & fileImports, Params & arguments) +/* Parse a supposed value argument. This can be a hash (the simple + case), a symbolic name (in which case we do a lookup to obtain the + hash), or a file name (which we import to obtain the hash). Note + that in order to disambiguate between symbolic names and file + names, a file name should contain at least one `/'. */ +Hash parseValueArg(string s) { - string pkgfile; - - pkgfile = getFile(hash); - - ATerm term = ATreadFromNamedFile(pkgfile.c_str()); - if (!term) throw Error("cannot read aterm " + pkgfile); - - ATerm bindings; - if (!ATmatch(term, "Descr()", &bindings)) - throw Error("invalid term in " + pkgfile); - - char * cname; - ATerm value; - while (ATmatch(bindings, "[Bind(, ), ]", - &cname, &value, &bindings)) - { - string name(cname); - char * arg; - if (ATmatch(value, "Pkg()", &arg)) { - parseHash(arg); - pkgImports[name] = arg; - } else if (ATmatch(value, "File()", &arg)) { - parseHash(arg); - fileImports[name] = arg; - } else if (ATmatch(value, "Str()", &arg)) - arguments[name] = arg; - else if (ATmatch(value, "Bool(True)")) - arguments[name] = "1"; - else if (ATmatch(value, "Bool(False)")) - arguments[name] = ""; - else { - ATprintf("%t\n", value); - throw Error("invalid binding in " + pkgfile); - } - } -} - - -string getPkg(Hash hash); - - -void fetchDeps(Hash hash, Environment & env) -{ - /* Read the package description file. */ - Params pkgImports, fileImports, arguments; - readPkgDescr(hash, pkgImports, fileImports, arguments); - - /* Recursively fetch all the dependencies, filling in the - environment as we go along. */ - for (Params::iterator it = pkgImports.begin(); - it != pkgImports.end(); it++) - { - cerr << "fetching package dependency " - << it->first << " <- " << it->second - << endl; - env[it->first] = getPkg(parseHash(it->second)); - } - - for (Params::iterator it = fileImports.begin(); - it != fileImports.end(); it++) - { - cerr << "fetching file dependency " - << it->first << " = " << it->second - << endl; - - string file; - - file = getFile(parseHash(it->second)); - - env[it->first] = file; - } - - string buildSystem; - - for (Params::iterator it = arguments.begin(); - it != arguments.end(); it++) - { - env[it->first] = it->second; - if (it->first == "system") - buildSystem = it->second; - } - - if (buildSystem != thisSystem) - throw Error("descriptor requires a `" + buildSystem + - "' but I am a `" + thisSystem + "'"); -} - - -string getFromEnv(const Environment & env, const string & key) -{ - Environment::const_iterator it = env.find(key); - if (it == env.end()) - throw Error("key " + key + " not found in the environment"); - return it->second; -} - - -string queryPkgId(Hash hash) -{ - Params pkgImports, fileImports, arguments; - readPkgDescr(hash, pkgImports, fileImports, arguments); - return getFromEnv(arguments, "id"); -} - - -void installPkg(Hash hash) -{ - string pkgfile; - string src; - string path; - string cmd; - string builder; - Environment env; - - /* Fetch dependencies. */ - fetchDeps(hash, env); - - builder = getFromEnv(env, "build"); - - string id = getFromEnv(env, "id"); - - /* Construct a path for the installed package. */ - path = nixHomeDir + "/pkg/" + id + "-" + (string) hash; - - /* Create the path. */ - if (mkdir(path.c_str(), 0777)) - throw Error("unable to create directory " + path); - - /* Create a log file. */ - string logFileName = - nixLogDir + "/" + id + "-" + (string) hash + ".log"; - /* !!! auto-pclose on exit */ - FILE * logFile = popen(("tee " + logFileName + " >&2").c_str(), "w"); /* !!! escaping */ - if (!logFile) - throw Error("unable to create log file " + logFileName); - try { + return parseHash(s); + } catch (BadRefError e) { }; - /* Fork a child to build the package. */ - pid_t pid; - switch (pid = fork()) { - - case -1: - throw Error("unable to fork"); - - case 0: - - try { /* child */ - - /* Go to the build directory. */ - if (chdir(path.c_str())) { - cerr << "unable to chdir to package directory\n"; - _exit(1); - } - - /* Try to use a prebuilt. */ - string prebuiltHashS, prebuiltFile; - if (queryDB(nixDB, dbPrebuilts, hash, prebuiltHashS)) { - - try { - prebuiltFile = getFile(parseHash(prebuiltHashS)); - } catch (Error e) { - cerr << "cannot obtain prebuilt (ignoring): " << e.what() << endl; - goto build; - } - - cerr << "substituting prebuilt " << prebuiltFile << endl; - - int res = system(("tar xfj " + prebuiltFile + " 1>&2").c_str()); // !!! escaping - if (WEXITSTATUS(res) != 0) - /* This is a fatal error, because path may now - have clobbered. */ - throw Error("cannot unpack " + prebuiltFile); - - _exit(0); - } - - build: - - /* Fill in the environment. We don't bother freeing - the strings, since we'll exec or die soon - anyway. */ - const char * env2[env.size() + 1]; - int i = 0; - for (Environment::iterator it = env.begin(); - it != env.end(); it++, i++) - env2[i] = (new string(it->first + "=" + it->second))->c_str(); - env2[i] = 0; - - /* Dup the log handle into stderr. */ - if (dup2(fileno(logFile), STDERR_FILENO) == -1) - throw Error("cannot pipe standard error into log file: " + string(strerror(errno))); - - /* Dup stderr to stdin. */ - if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1) - throw Error("cannot dup stderr into stdout"); - - /* Execute the builder. This should not return. */ - execle(builder.c_str(), builder.c_str(), 0, env2); - - throw Error("unable to execute builder: " + - string(strerror(errno))); - - } catch (exception & e) { - cerr << "build error: " << e.what() << endl; - _exit(1); - } - - } - - /* parent */ - - /* Close the logging pipe. Note that this should not cause - the logger to exit until builder exits (because the latter - has an open file handle to the former). */ - pclose(logFile); - - /* Wait for the child to finish. */ - int status; - if (waitpid(pid, &status, 0) != pid) - throw Error("unable to wait for child"); - - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) - throw Error("unable to build package"); - - /* Remove write permission from the build directory. */ - int res = system(("chmod -R -w " + path).c_str()); // !!! escaping - if (WEXITSTATUS(res) != 0) - throw Error("cannot remove write permission from " + path); - - } catch (exception &) { -// system(("rm -rf " + path).c_str()); - throw; - } - - setDB(nixDB, dbInstPkgs, hash, path); -} - - -string getPkg(Hash hash) -{ - string path; - while (!queryDB(nixDB, dbInstPkgs, hash, path)) - installPkg(hash); - return path; -} - - -void runPkg(Hash hash, - Strings::iterator firstArg, - Strings::iterator lastArg) -{ - string src; - string path; - string cmd; - string runner; - Environment env; - - /* Fetch dependencies. */ - fetchDeps(hash, env); - - runner = getFromEnv(env, "run"); - - /* Fill in the environment. We don't bother freeing the - strings, since we'll exec or die soon anyway. */ - for (Environment::iterator it = env.begin(); - it != env.end(); it++) - { - string * s = new string(it->first + "=" + it->second); - putenv((char *) s->c_str()); - } - - /* Create the list of arguments. */ - const char * args2[env.size() + 2]; - int i = 0; - args2[i++] = runner.c_str(); - for (Strings::const_iterator it = firstArg; it != lastArg; it++, i++) - args2[i] = it->c_str(); - args2[i] = 0; - - /* Execute the runner. This should not return. */ - execv(runner.c_str(), (char * *) args2); - - cerr << strerror(errno) << endl; - throw Error("unable to execute runner"); -} - - -void ensurePkg(Hash hash) -{ - Params pkgImports, fileImports, arguments; - readPkgDescr(hash, pkgImports, fileImports, arguments); - - if (fileImports.find("build") != fileImports.end()) - getPkg(hash); - else if (fileImports.find("run") != fileImports.end()) { - Environment env; - fetchDeps(hash, env); - } else throw Error("invalid descriptor"); -} - - -void delPkg(Hash hash) -{ - string path; - if (queryDB(nixDB, dbInstPkgs, hash, path)) { - int res = system(("chmod -R +w " + path + " && rm -rf " + path).c_str()); // !!! escaping - delDB(nixDB, dbInstPkgs, hash); // not a bug ??? - if (WEXITSTATUS(res) != 0) - cerr << "errors deleting " + path + ", ignoring" << endl; - } -} - - -void exportPkgs(string outDir, - Strings::iterator firstHash, - Strings::iterator lastHash) -{ - outDir = absPath(outDir); - - for (Strings::iterator it = firstHash; it != lastHash; it++) { - Hash hash = parseHash(*it); - string pkgDir = getPkg(hash); - string tmpFile = outDir + "/export_tmp"; - - string cmd = "cd " + pkgDir + " && tar cfj " + tmpFile + " ."; - int res = system(cmd.c_str()); // !!! escaping - if (!WIFEXITED(res) || WEXITSTATUS(res) != 0) - throw Error("cannot tar " + pkgDir); - - string prebuiltHash = hashFile(tmpFile); - string pkgId = queryPkgId(hash); - string prebuiltFile = outDir + "/" + - pkgId + "-" + (string) hash + "-" + prebuiltHash + ".tar.bz2"; - - rename(tmpFile.c_str(), prebuiltFile.c_str()); - } -} - - -void registerPrebuilt(Hash pkgHash, Hash prebuiltHash) -{ - setDB(nixDB, dbPrebuilts, pkgHash, prebuiltHash); -} - - -Hash registerFile(string filename) -{ - filename = absPath(filename); - Hash hash = hashFile(filename); - setDB(nixDB, dbRefs, hash, filename); - return hash; -} - - -void registerURL(Hash hash, string url) -{ - setDB(nixDB, dbNetSources, hash, url); - /* !!! currently we allow only one network source per hash */ -} - - -/* This is primarily used for bootstrapping. */ -void registerInstalledPkg(Hash hash, string path) -{ - if (path == "") - delDB(nixDB, dbInstPkgs, hash); - else - setDB(nixDB, dbInstPkgs, hash, path); -} - - -void verifyDB() -{ - /* Check that all file references are still valid. */ - DBPairs fileRefs; - - enumDB(nixDB, dbRefs, fileRefs); - - for (DBPairs::iterator it = fileRefs.begin(); - it != fileRefs.end(); it++) - { - try { - Hash hash = parseHash(it->first); - if (hashFile(it->second) != hash) { - cerr << "file " << it->second << " has changed\n"; - delDB(nixDB, dbRefs, it->first); - } - } catch (Error e) { /* !!! better error check */ - cerr << "error: " << e.what() << endl; - delDB(nixDB, dbRefs, it->first); - } - } - - /* Check that all installed packages are still there. */ - DBPairs instPkgs; - - enumDB(nixDB, dbInstPkgs, instPkgs); - - for (DBPairs::iterator it = instPkgs.begin(); - it != instPkgs.end(); it++) - { - struct stat st; - if (stat(it->second.c_str(), &st) == -1) { - cerr << "package " << it->first << " has disappeared\n"; - delDB(nixDB, dbInstPkgs, it->first); - } - } - - /* TODO: check that all directories in pkgHome are installed - packages. */ -} - - -void listInstalledPkgs() -{ - DBPairs instPkgs; - - enumDB(nixDB, dbInstPkgs, instPkgs); - - for (DBPairs::iterator it = instPkgs.begin(); - it != instPkgs.end(); it++) - cout << it->first << endl; -} - - -void printInfo(Strings::iterator first, Strings::iterator last) -{ - for (Strings::iterator it = first; it != last; it++) { - try { - cout << *it << " " << queryPkgId(parseHash(*it)) << endl; - } catch (Error & e) { // !!! more specific - cout << *it << " (descriptor missing)\n"; - } - } -} - - -void computeClosure(Strings::iterator first, Strings::iterator last, - set & result) -{ - list workList(first, last); - set doneSet; - - while (!workList.empty()) { - Hash hash = parseHash(workList.front()); - workList.pop_front(); - - if (doneSet.find(hash) == doneSet.end()) { - doneSet.insert(hash); - - Params pkgImports, fileImports, arguments; - readPkgDescr(hash, pkgImports, fileImports, arguments); - - for (Params::iterator it = pkgImports.begin(); - it != pkgImports.end(); it++) - workList.push_back(it->second); - } - } - - result = doneSet; -} - - -void printClosure(Strings::iterator first, Strings::iterator last) -{ - set allHashes; - computeClosure(first, last, allHashes); - for (set::iterator it = allHashes.begin(); - it != allHashes.end(); it++) - cout << *it << endl; -} - - -string dotQuote(const string & s) -{ - return "\"" + s + "\""; -} - - -void printGraph(Strings::iterator first, Strings::iterator last) -{ - set allHashes; - computeClosure(first, last, allHashes); - - cout << "digraph G {\n"; - - for (set::iterator it = allHashes.begin(); - it != allHashes.end(); it++) - { - Params pkgImports, fileImports, arguments; - readPkgDescr(parseHash(*it), pkgImports, fileImports, arguments); - - cout << dotQuote(*it) << "[label = \"" - << getFromEnv(arguments, "id") - << "\"];\n"; - - for (Params::iterator it2 = pkgImports.begin(); - it2 != pkgImports.end(); it2++) - cout << dotQuote(it2->second) << " -> " - << dotQuote(*it) << ";\n"; - } - - cout << "}\n"; -} - - -void fetch(string id) -{ - string fn; - - /* Fetch the object referenced by id. */ - if (isHash(id)) { - throw Error("not implemented"); + if (s.find('/') != string::npos) { + return addValue(s); } else { - fn = fetchURL(id); + throw Error("not implemented"); } - - /* Register it by hash. */ - Hash hash = registerFile(fn); - cout << (string) hash << endl; } -void fetch(Strings::iterator first, Strings::iterator last) +/* Evaluate values. */ +static void opEvaluate(Strings opFlags, Strings opArgs) { - for (Strings::iterator it = first; it != last; it++) - fetch(*it); + if (!opFlags.empty()) throw UsageError("unknown flag"); + + for (Strings::iterator it = opArgs.begin(); + it != opArgs.end(); it++) + { + Hash hash = parseValueArg(*it); + Expr e = ATmake("Deref(Hash())", ((string) hash).c_str()); + cerr << printExpr(evalValue(e)) << endl; + } } -void printUsage() +static void opDelete(Strings opFlags, Strings opArgs) { - cerr << -"Usage: nix SUBCOMMAND OPTIONS...\n\ -\n\ -Subcommands:\n\ -\n\ - init\n\ - Initialize the database.\n\ -\n\ - verify\n\ - Remove stale entries from the database.\n\ -\n\ - regfile FILENAME...\n\ - Register each FILENAME keyed by its hash.\n\ -\n\ - reginst HASH PATH\n\ - Register an installed package.\n\ -\n\ - getpkg HASH...\n\ - For each HASH, ensure that the package referenced by HASH is\n\ - installed. Print out the path of the installation on stdout.\n\ -\n\ - delpkg HASH...\n\ - Uninstall the package referenced by each HASH, disregarding any\n\ - dependencies that other packages may have on HASH.\n\ -\n\ - listinst\n\ - Prints a list of installed packages.\n\ -\n\ - run HASH ARGS...\n\ - Run the descriptor referenced by HASH with the given arguments.\n\ -\n\ - ensure HASH...\n\ - Like getpkg, but if HASH refers to a run descriptor, fetch only\n\ - the dependencies.\n\ -\n\ - export DIR HASH...\n\ - Export installed packages to DIR.\n\ -\n\ - regprebuilt HASH1 HASH2\n\ - Inform Nix that an export HASH2 can be used to fast-build HASH1.\n\ -\n\ - info HASH...\n\ - Print information about the specified descriptors.\n\ -\n\ - closure HASH...\n\ - Determine the closure of the set of descriptors under the import\n\ - relation, starting at the given roots.\n\ -\n\ - graph HASH...\n\ - Like closure, but print a dot graph specification.\n\ -\n\ - fetch ID...\n\ - Fetch the objects identified by ID and place them in the Nix\n\ - sources directory. ID can be a hash or URL. Print out the hash\n\ - of the object.\n\ -"; + cerr << "delete!\n"; } +/* Add values to the Nix values directory and print the hashes of + those values. */ +static void opAdd(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + + for (Strings::iterator it = opArgs.begin(); + it != opArgs.end(); it++) + cout << (string) addValue(*it) << endl; +} + + +/* Initialise the Nix databases. */ +static void opInit(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (!opArgs.empty()) + throw UsageError("--init does not have arguments"); + initDB(); +} + + +/* Nix syntax: + + nix [OPTIONS...] [ARGUMENTS...] + + Operations: + + --evaluate / -e: evaluate values + --delete / -d: delete values + --query / -q: query stored values + --add: add values + --verify: verify Nix structures + --dump: dump a value + --init: initialise the Nix database + --version: output version information + --help: display help + + Operations that work on values accept the hash code of a value, the + symbolic name of a value, or a file name of a external value that + will be added prior to the operation. + + Query suboptions: + + Selection: + + --all / -a: query all stored values, otherwise given values + + Information: + + --info / -i: general value information + + Options: + + --verbose / -v: verbose operation +*/ + +/* Initialize, process arguments, and dispatch to the right + operation. */ void run(Strings::iterator argCur, Strings::iterator argEnd) { - umask(0022); + Strings opFlags, opArgs; + Operation op = 0; - char * homeDir = getenv(nixHomeDirEnvVar.c_str()); - if (homeDir) nixHomeDir = homeDir; + /* Setup Nix paths. */ + nixValues = NIX_VALUES_DIR; + nixLogDir = NIX_LOG_DIR; + nixDB = (string) NIX_STATE_DIR + "/nixstate.db"; - nixSourcesDir = nixHomeDir + "/var/nix/sources"; - nixLogDir = nixHomeDir + "/var/log/nix"; - nixDB = nixHomeDir + "/var/nix/pkginfo.db"; + /* Scan the arguments; find the operation, set global flags, put + all other flags in a list, and put all other arguments in + another list. */ - /* Parse the global flags. */ - for ( ; argCur != argEnd; argCur++) { - string arg(*argCur); - if (arg == "-h" || arg == "--help") { - printUsage(); - return; - } else if (arg[0] == '-') { - throw UsageError("invalid option `" + arg + "'"); - } else break; + while (argCur != argEnd) { + string arg = *argCur++; + + Operation oldOp = op; + + if (arg == "--evaluate" || arg == "-e") + op = opEvaluate; + else if (arg == "--delete" || arg == "-d") + op = opDelete; + else if (arg == "--add") + op = opAdd; + else if (arg == "--init") + op = opInit; + else if (arg[0] == '-') + opFlags.push_back(arg); + else + opArgs.push_back(arg); + + if (oldOp && oldOp != op) + throw UsageError("only one operation may be specified"); } - UsageError argcError("wrong number of arguments"); + if (!op) throw UsageError("no operation specified"); - /* Parse the command. */ - if (argCur == argEnd) throw UsageError("no command specified"); - string cmd = *argCur++; - int argc = argEnd - argCur; - - if (cmd == "init") { - if (argc != 0) throw argcError; - initDB(); - } else if (cmd == "verify") { - if (argc != 0) throw argcError; - verifyDB(); - } else if (cmd == "getpkg") { - for (Strings::iterator it = argCur; it != argEnd; it++) { - string path = getPkg(parseHash(*it)); - cout << path << endl; - } - } else if (cmd == "delpkg") { - for (Strings::iterator it = argCur; it != argEnd; it++) - delPkg(parseHash(*it)); - } else if (cmd == "run") { - if (argc < 1) throw argcError; - runPkg(parseHash(*argCur), argCur + 1, argEnd); - } else if (cmd == "ensure") { - for (Strings::iterator it = argCur; it != argEnd; it++) - ensurePkg(parseHash(*it)); - } else if (cmd == "export") { - if (argc < 1) throw argcError; - exportPkgs(*argCur, argCur + 1, argEnd); - } else if (cmd == "regprebuilt") { - if (argc != 2) throw argcError; - registerPrebuilt(parseHash(argCur[0]), parseHash(argCur[1])); - } else if (cmd == "regfile") { - for_each(argCur, argEnd, registerFile); - } else if (cmd == "regurl") { - registerURL(parseHash(argCur[0]), argCur[1]); - } else if (cmd == "reginst") { - if (argc != 2) throw argcError; - registerInstalledPkg(parseHash(argCur[0]), argCur[1]); - } else if (cmd == "listinst") { - if (argc != 0) throw argcError; - listInstalledPkgs(); - } else if (cmd == "info") { - printInfo(argCur, argEnd); - } else if (cmd == "closure") { - printClosure(argCur, argEnd); - } else if (cmd == "graph") { - printGraph(argCur, argEnd); - } else if (cmd == "fetch") { - fetch(argCur, argEnd); - } else - throw UsageError("unknown command: " + string(cmd)); + op(opFlags, opArgs); } int main(int argc, char * * argv) { + /* ATerm setup. */ ATerm bottomOfStack; ATinit(argc, argv, &bottomOfStack); - /* Put the arguments in a vector. */ - Strings args; - while (argc--) args.push_back(*argv++); - Strings::iterator argCur = args.begin(), argEnd = args.end(); - - argCur++; - try { - run(argCur, argEnd); + + Strings args; + while (argc--) args.push_back(*argv++); + run(args.begin() + 1, args.end()); + } catch (UsageError & e) { cerr << "error: " << e.what() << endl - << "Try `nix -h' for more information.\n"; + << "Try `nix --help' for more information.\n"; return 1; } catch (exception & e) { cerr << "error: " << e.what() << endl; diff --git a/src/values.cc b/src/values.cc index 77a6f928e..7cef5d3de 100644 --- a/src/values.cc +++ b/src/values.cc @@ -19,6 +19,8 @@ static string absValuePath(string s) Hash addValue(string path) { + path = absPath(path); + Hash hash = hashPath(path); string name; @@ -79,7 +81,7 @@ string queryValuePath(Hash hash) return fn; } - throw Error("a file with hash " + (string) hash + " is requested, " + throw Error("a file with hash " + (string) hash + " is required, " "but it is not known to exist locally or on the network"); #if 0 if (checkedNet) @@ -87,7 +89,7 @@ string queryValuePath(Hash hash) " should have hash " + (string) hash + ", but it doesn't"); if (!queryDB(nixDB, dbNetSources, hash, url)) - throw Error("a file with hash " + (string) hash + " is requested, " + throw Error("a file with hash " + (string) hash + " is required, " "but it is not known to exist locally or on the network"); checkedNet = true; From fab7b128b9890cf563fa3cab5578757241902ac2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 18 Jun 2003 08:07:28 +0000 Subject: [PATCH 0079/6440] * Automake sucks. --- configure.ac | 2 +- src/Makefile.am | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index ff9b5beb7..1aa1a6656 100644 --- a/configure.ac +++ b/configure.ac @@ -15,6 +15,6 @@ AC_DEFINE_UNQUOTED(NIX_VALUES_DIR, "$(eval echo $prefix/values)", Nix values dir AC_DEFINE_UNQUOTED(NIX_STATE_DIR, "$(eval echo $localstatedir/nix)", Nix state directory.) AC_DEFINE_UNQUOTED(NIX_LOG_DIR, "$(eval echo $localstatedir/log/nix)", Nix log file directory.) -AC_CONFIG_HEADER([config.h]) +AM_CONFIG_HEADER([config.h]) AC_CONFIG_FILES([Makefile src/Makefile scripts/Makefile]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index 8c8ba3271..09c2f9d09 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -6,8 +6,8 @@ AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. nix_SOURCES = nix.cc util.cc hash.cc md5.c eval.cc values.cc globals.cc db.cc nix_LDADD = -ldb_cxx-4 -lATerm -fix_SOURCES = fix.cc util.cc hash.cc md5.c -fix_LDADD = -lATerm +#fix_SOURCES = fix.cc util.cc hash.cc md5.c +#fix_LDADD = -lATerm test_SOURCES = test.cc util.cc hash.cc md5.c eval.cc values.cc globals.cc db.cc test_LDADD = -ldb_cxx-4 -lATerm From bc57eb3c8a54df819bad9c300ff5569762f15c28 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 18 Jun 2003 12:35:32 +0000 Subject: [PATCH 0080/6440] * Set CPLUS_INCLUDE_PATH as well as C_INCLUDE_PATH. Otherwise g++ won't see header files under Nix control. --- scripts/nix-profile.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/nix-profile.sh b/scripts/nix-profile.sh index fc5c13152..fb526a239 100644 --- a/scripts/nix-profile.sh +++ b/scripts/nix-profile.sh @@ -11,6 +11,7 @@ if test -z "$NIX_SET"; then export LIBRARY_PATH=$NIX_LINKS/lib:$LIBRARY_PATH export C_INCLUDE_PATH=$NIX_LINKS/include:$C_INCLUDE_PATH + export CPLUS_INCLUDE_PATH=$NIX_LINKS/include:$CPLUS_INCLUDE_PATH export PKG_CONFIG_PATH=$NIX_LINKS/lib/pkgconfig:$PKG_CONFIG_PATH From 94cf1f86bb5d8516583f0d39ad22dbc853019798 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 18 Jun 2003 12:36:12 +0000 Subject: [PATCH 0081/6440] * Lambdas, applications, substitutions. --- src/eval.cc | 63 +++++++++++++++++++++++++++++++++++++++++++++++++---- src/eval.hh | 9 ++++++++ src/test.cc | 3 +++ 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/eval.cc b/src/eval.cc index 7cb13d9cc..3b95588d7 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -247,25 +247,72 @@ void evalArgs(ATermList args, ATermList & argsNF, Environment & env) env[name] = queryValuePath(parseHash(s)); } else throw badTerm("invalid argument value", eVal); - argsNF = ATappend(argsNF, + argsNF = ATinsert(argsNF, ATmake("Tup(Str(), )", name.c_str(), eVal)); args = ATgetNext(args); } + + argsNF = ATreverse(argsNF); +} + + +Expr substExpr(string x, Expr rep, Expr e) +{ + char * s; + Expr e2; + + if (ATmatch(e, "Var()", &s)) + if (x == s) + return rep; + else + return e; + + if (ATmatch(e, "Lam(, )", &s, &e2)) + if (x == s) + return e; + /* !!! unfair substitutions */ + + /* Generically substitute in subterms. */ + + if (ATgetType(e) == AT_APPL) { + AFun fun = ATgetAFun(e); + int arity = ATgetArity(fun); + ATermList args = ATempty; + + for (int i = arity - 1; i >= 0; i--) + args = ATinsert(args, substExpr(x, rep, ATgetArgument(e, i))); + + return (ATerm) ATmakeApplList(fun, args); + } + + if (ATgetType(e) == AT_LIST) { + ATermList in = (ATermList) e; + ATermList out = ATempty; + + while (!ATisEmpty(in)) { + out = ATinsert(out, substExpr(x, rep, ATgetFirst(in))); + in = ATgetNext(in); + } + + return (ATerm) ATreverse(out); + } + + throw badTerm("do not know how to substitute", e); } -/* Evaluate an expression. */ Expr evalValue(Expr e) { char * s; - Expr eBuildPlatform, eProg, e2; + Expr eBuildPlatform, eProg, e2, e3, e4; ATermList args; /* Normal forms. */ if (ATmatch(e, "Str()", &s) || ATmatch(e, "Bool(True)") || - ATmatch(e, "Bool(False)")) + ATmatch(e, "Bool(False)") || + ATmatch(e, "Lam(, )", &s, &e2)) return e; /* Value references. */ @@ -282,6 +329,14 @@ Expr evalValue(Expr e) return evalValue(e3); } + /* Application. */ + if (ATmatch(e, "App(, )", &e2, &e3)) { + e2 = evalValue(e2); + if (!ATmatch(e2, "Lam(, )", &s, &e4)) + throw badTerm("expecting lambda", e2); + return evalValue(substExpr(s, e3, e4)); + } + /* Execution primitive. */ if (ATmatch(e, "Exec(, , [])", diff --git a/src/eval.hh b/src/eval.hh index 719edb143..2e764b1bd 100644 --- a/src/eval.hh +++ b/src/eval.hh @@ -60,6 +60,12 @@ using namespace std; makeArg(Arg(Str(nm), (Bool(True), _))) => (nm, "1") makeArg(Arg(Str(nm), (Bool(False), _))) => (nm, undef) + subst(x, e1, e2) is defined as a generic topdown term + traversal of e2, replacing each `Var(x)' with e1, and not + descending into `Lam(x, _)'. + + Note: all stored expressions must be closed. !!! ugly + getFile :: Hash -> FileName loadExpr :: Hash -> FileName hashExpr :: Expr -> Hash @@ -76,6 +82,9 @@ Expr evalValue(Expr e); /* Return a canonical textual representation of an expression. */ string printExpr(Expr e); +/* Perform variable substitution. */ +Expr substExpr(string x, Expr rep, Expr e); + /* Hash an expression. */ Hash hashExpr(Expr e); diff --git a/src/test.cc b/src/test.cc index 334e409de..019c7bf7e 100644 --- a/src/test.cc +++ b/src/test.cc @@ -69,6 +69,9 @@ void runTests() evalTest(ATmake("Str(\"Hello World\")")); evalTest(ATmake("Bool(True)")); evalTest(ATmake("Bool(False)")); + evalTest(ATmake("App(Lam(\"x\", Var(\"x\")), Str(\"Hello World\"))")); + evalTest(ATmake("App(App(Lam(\"x\", Lam(\"y\", Var(\"x\"))), Str(\"Hello World\")), Str(\"Hallo Wereld\"))")); + evalTest(ATmake("App(Lam(\"sys\", Lam(\"x\", [Var(\"x\"), Var(\"sys\")])), Str(\"i686-suse-linux\"))")); Hash builder1 = addValue("./test-builder-1.sh"); From aeaffec7857301a6d20d7901041918484666d567 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 18 Jun 2003 14:34:03 +0000 Subject: [PATCH 0082/6440] * Dump symlinks. --- src/hash.cc | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/hash.cc b/src/hash.cc index 37f6104fb..fa016c835 100644 --- a/src/hash.cc +++ b/src/hash.cc @@ -192,7 +192,7 @@ static void dumpContents(const string & path, unsigned int size, int fd = open(path.c_str(), O_RDONLY); if (!fd) throw SysError("opening file " + path); - unsigned char buf[16384]; + unsigned char buf[65536]; unsigned int total = 0; ssize_t n; @@ -213,8 +213,6 @@ static void dumpContents(const string & path, unsigned int size, void dumpPath(const string & path, DumpSink & sink) { - cerr << path << endl; - struct stat st; if (lstat(path.c_str(), &st)) throw SysError("getting attributes of path " + path); @@ -225,11 +223,25 @@ void dumpPath(const string & path, DumpSink & sink) writeString("type", sink); writeString("regular", sink); dumpContents(path, st.st_size, sink); - } else if (S_ISDIR(st.st_mode)) { + } + + else if (S_ISDIR(st.st_mode)) { writeString("type", sink); writeString("directory", sink); dumpEntries(path, sink); - } else throw Error("unknown file type: " + path); + } + + else if (S_ISLNK(st.st_mode)) { + writeString("type", sink); + writeString("symlink", sink); + char buf[st.st_size]; + if (readlink(path.c_str(), buf, st.st_size) != st.st_size) + throw SysError("reading symbolic link " + path); + writeString("target", sink); + writeString(string(buf, st.st_size), sink); + } + + else throw Error("unknown file type: " + path); writeString(")", sink); } From 38e12df631cc45fda97942070d7ebea1bb9c7f2f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 18 Jun 2003 14:34:43 +0000 Subject: [PATCH 0083/6440] * `nix --dump' command. --- src/nix.cc | 29 ++++++++++++++++++++++++++--- src/test.cc | 3 ++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index fae3175ba..b2bb3bb1a 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -62,6 +62,29 @@ static void opAdd(Strings opFlags, Strings opArgs) } +/* A sink that writes dump output to stdout. */ +struct StdoutSink : DumpSink +{ + virtual void operator () + (const unsigned char * data, unsigned int len) + { + /* Don't use cout, it's slow as hell! */ + write(STDOUT_FILENO, (char *) data, len); + } +}; + + +/* Dump a value to standard output */ +static void opDump(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (opArgs.size() != 1) throw UsageError("only one argument allowed"); + + StdoutSink sink; + dumpPath(opArgs[0], sink); +} + + /* Initialise the Nix databases. */ static void opInit(Strings opFlags, Strings opArgs) { @@ -83,7 +106,7 @@ static void opInit(Strings opFlags, Strings opArgs) --query / -q: query stored values --add: add values --verify: verify Nix structures - --dump: dump a value + --dump: dump a file or value --init: initialise the Nix database --version: output version information --help: display help @@ -134,6 +157,8 @@ void run(Strings::iterator argCur, Strings::iterator argEnd) op = opDelete; else if (arg == "--add") op = opAdd; + else if (arg == "--dump") + op = opDump; else if (arg == "--init") op = opInit; else if (arg[0] == '-') @@ -158,11 +183,9 @@ int main(int argc, char * * argv) ATinit(argc, argv, &bottomOfStack); try { - Strings args; while (argc--) args.push_back(*argv++); run(args.begin() + 1, args.end()); - } catch (UsageError & e) { cerr << "error: " << e.what() << endl << "Try `nix --help' for more information.\n"; diff --git a/src/test.cc b/src/test.cc index 019c7bf7e..a3706472e 100644 --- a/src/test.cc +++ b/src/test.cc @@ -21,7 +21,8 @@ struct MySink : DumpSink { virtual void operator () (const unsigned char * data, unsigned int len) { - cout.write((char *) data, len); + /* Don't use cout, it's slow as hell! */ + write(STDOUT_FILENO, (char *) data, len); } }; From 1849aa2a72d7f530e2c18d640528075bcdf8991c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 20 Jun 2003 10:40:25 +0000 Subject: [PATCH 0084/6440] * Refactoring: move dump function into archive.cc. --- src/Makefile.am | 4 +- src/archive.cc | 136 ++++++++++++++++++++++++++++++++++++++++++++++++ src/archive.hh | 48 +++++++++++++++++ src/hash.cc | 135 +---------------------------------------------- src/hash.hh | 48 +---------------- src/nix.cc | 1 + src/test.cc | 1 + 7 files changed, 190 insertions(+), 183 deletions(-) create mode 100644 src/archive.cc create mode 100644 src/archive.hh diff --git a/src/Makefile.am b/src/Makefile.am index 09c2f9d09..20f172819 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,13 +3,13 @@ noinst_PROGRAMS = test AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. -nix_SOURCES = nix.cc util.cc hash.cc md5.c eval.cc values.cc globals.cc db.cc +nix_SOURCES = nix.cc util.cc hash.cc archive.cc md5.c eval.cc values.cc globals.cc db.cc nix_LDADD = -ldb_cxx-4 -lATerm #fix_SOURCES = fix.cc util.cc hash.cc md5.c #fix_LDADD = -lATerm -test_SOURCES = test.cc util.cc hash.cc md5.c eval.cc values.cc globals.cc db.cc +test_SOURCES = test.cc util.cc hash.cc archive.cc md5.c eval.cc values.cc globals.cc db.cc test_LDADD = -ldb_cxx-4 -lATerm install-data-local: diff --git a/src/archive.cc b/src/archive.cc new file mode 100644 index 000000000..2fdbfb476 --- /dev/null +++ b/src/archive.cc @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include + +#include "archive.hh" +#include "util.hh" + + +static void pad(unsigned int len, DumpSink & sink) +{ + if (len % 8) { + unsigned char zero[8]; + memset(zero, 0, sizeof(zero)); + sink(zero, 8 - (len % 8)); + } +} + + +static void writeInt(unsigned int n, DumpSink & sink) +{ + unsigned char buf[8]; + memset(buf, 0, sizeof(buf)); + buf[0] = n & 0xff; + buf[1] = (n >> 8) & 0xff; + buf[2] = (n >> 16) & 0xff; + buf[3] = (n >> 24) & 0xff; + sink(buf, sizeof(buf)); +} + + +static void writeString(const string & s, DumpSink & sink) +{ + unsigned int len = s.length(); + writeInt(len, sink); + sink((const unsigned char *) s.c_str(), len); + pad(len, sink); +} + + +static void dumpEntries(const string & path, DumpSink & sink) +{ + DIR * dir = opendir(path.c_str()); + if (!dir) throw SysError("opening directory " + path); + + Strings names; + + struct dirent * dirent; + while (errno = 0, dirent = readdir(dir)) { + string name = dirent->d_name; + if (name == "." || name == "..") continue; + names.push_back(name); + } + if (errno) throw SysError("reading directory " + path); + + sort(names.begin(), names.end()); + + for (Strings::iterator it = names.begin(); + it != names.end(); it++) + { + writeString("entry", sink); + writeString("(", sink); + writeString("name", sink); + writeString(*it, sink); + writeString("file", sink); + dumpPath(path + "/" + *it, sink); + writeString(")", sink); + } + + closedir(dir); /* !!! close on exception */ +} + + +static void dumpContents(const string & path, unsigned int size, + DumpSink & sink) +{ + writeString("contents", sink); + writeInt(size, sink); + + int fd = open(path.c_str(), O_RDONLY); + if (!fd) throw SysError("opening file " + path); + + unsigned char buf[65536]; + + unsigned int total = 0; + ssize_t n; + while ((n = read(fd, buf, sizeof(buf)))) { + if (n == -1) throw SysError("reading file " + path); + total += n; + sink(buf, n); + } + + if (total != size) + throw SysError("file changed while reading it: " + path); + + pad(size, sink); + + close(fd); /* !!! close on exception */ +} + + +void dumpPath(const string & path, DumpSink & sink) +{ + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError("getting attributes of path " + path); + + writeString("(", sink); + + if (S_ISREG(st.st_mode)) { + writeString("type", sink); + writeString("regular", sink); + dumpContents(path, st.st_size, sink); + } + + else if (S_ISDIR(st.st_mode)) { + writeString("type", sink); + writeString("directory", sink); + dumpEntries(path, sink); + } + + else if (S_ISLNK(st.st_mode)) { + writeString("type", sink); + writeString("symlink", sink); + char buf[st.st_size]; + if (readlink(path.c_str(), buf, st.st_size) != st.st_size) + throw SysError("reading symbolic link " + path); + writeString("target", sink); + writeString(string(buf, st.st_size), sink); + } + + else throw Error("unknown file type: " + path); + + writeString(")", sink); +} diff --git a/src/archive.hh b/src/archive.hh new file mode 100644 index 000000000..bfd96b45c --- /dev/null +++ b/src/archive.hh @@ -0,0 +1,48 @@ +#include + +using namespace std; + + +/* dumpPath creates a Nix archive of the specified path. The format + is as follows: + + IF path points to a REGULAR FILE: + dump(path) = attrs( + [ ("type", "regular") + , ("contents", contents(path)) + ]) + + IF path points to a DIRECTORY: + dump(path) = attrs( + [ ("type", "directory") + , ("entries", concat(map(f, sort(entries(path))))) + ]) + where f(fn) = attrs( + [ ("name", fn) + , ("file", dump(path + "/" + fn)) + ]) + + where: + + attrs(as) = concat(map(attr, as)) + encN(0) + attrs((a, b)) = encS(a) + encS(b) + + encS(s) = encN(len(s)) + s + (padding until next 64-bit boundary) + + encN(n) = 64-bit little-endian encoding of n. + + contents(path) = the contents of a regular file. + + sort(strings) = lexicographic sort by 8-bit value (strcmp). + + entries(path) = the entries of a directory, without `.' and + `..'. + + `+' denotes string concatenation. */ + +struct DumpSink +{ + virtual void operator () (const unsigned char * data, unsigned int len) = 0; +}; + +void dumpPath(const string & path, DumpSink & sink); diff --git a/src/hash.cc b/src/hash.cc index fa016c835..765b7ba04 100644 --- a/src/hash.cc +++ b/src/hash.cc @@ -1,16 +1,11 @@ #include -#include -#include -#include -#include -#include - extern "C" { #include "md5.h" } #include "hash.hh" +#include "archive.hh" Hash::Hash() @@ -117,131 +112,3 @@ Hash hashPath(const string & path) md5_finish_ctx(&sink.ctx, hash.hash); return hash; } - - -static void pad(unsigned int len, DumpSink & sink) -{ - if (len % 8) { - unsigned char zero[8]; - memset(zero, 0, sizeof(zero)); - sink(zero, 8 - (len % 8)); - } -} - - -static void writeInt(unsigned int n, DumpSink & sink) -{ - unsigned char buf[8]; - memset(buf, 0, sizeof(buf)); - buf[0] = n & 0xff; - buf[1] = (n >> 8) & 0xff; - buf[2] = (n >> 16) & 0xff; - buf[3] = (n >> 24) & 0xff; - sink(buf, sizeof(buf)); -} - - -static void writeString(const string & s, DumpSink & sink) -{ - unsigned int len = s.length(); - writeInt(len, sink); - sink((const unsigned char *) s.c_str(), len); - pad(len, sink); -} - - -static void dumpEntries(const string & path, DumpSink & sink) -{ - DIR * dir = opendir(path.c_str()); - if (!dir) throw SysError("opening directory " + path); - - Strings names; - - struct dirent * dirent; - while (errno = 0, dirent = readdir(dir)) { - string name = dirent->d_name; - if (name == "." || name == "..") continue; - names.push_back(name); - } - if (errno) throw SysError("reading directory " + path); - - sort(names.begin(), names.end()); - - for (Strings::iterator it = names.begin(); - it != names.end(); it++) - { - writeString("entry", sink); - writeString("(", sink); - writeString("name", sink); - writeString(*it, sink); - writeString("file", sink); - dumpPath(path + "/" + *it, sink); - writeString(")", sink); - } - - closedir(dir); /* !!! close on exception */ -} - - -static void dumpContents(const string & path, unsigned int size, - DumpSink & sink) -{ - writeString("contents", sink); - writeInt(size, sink); - - int fd = open(path.c_str(), O_RDONLY); - if (!fd) throw SysError("opening file " + path); - - unsigned char buf[65536]; - - unsigned int total = 0; - ssize_t n; - while ((n = read(fd, buf, sizeof(buf)))) { - if (n == -1) throw SysError("reading file " + path); - total += n; - sink(buf, n); - } - - if (total != size) - throw SysError("file changed while reading it: " + path); - - pad(size, sink); - - close(fd); /* !!! close on exception */ -} - - -void dumpPath(const string & path, DumpSink & sink) -{ - struct stat st; - if (lstat(path.c_str(), &st)) - throw SysError("getting attributes of path " + path); - - writeString("(", sink); - - if (S_ISREG(st.st_mode)) { - writeString("type", sink); - writeString("regular", sink); - dumpContents(path, st.st_size, sink); - } - - else if (S_ISDIR(st.st_mode)) { - writeString("type", sink); - writeString("directory", sink); - dumpEntries(path, sink); - } - - else if (S_ISLNK(st.st_mode)) { - writeString("type", sink); - writeString("symlink", sink); - char buf[st.st_size]; - if (readlink(path.c_str(), buf, st.st_size) != st.st_size) - throw SysError("reading symbolic link " + path); - writeString("target", sink); - writeString(string(buf, st.st_size), sink); - } - - else throw Error("unknown file type: " + path); - - writeString(")", sink); -} diff --git a/src/hash.hh b/src/hash.hh index 13c5275b4..cbc195c1f 100644 --- a/src/hash.hh +++ b/src/hash.hh @@ -47,55 +47,9 @@ Hash hashString(const string & s); Hash hashFile(const string & fileName); /* Compute the hash of the given path. The hash is defined as - follows: - - hash(path) = md5(dump(path)) + md5(dump(path)). */ Hash hashPath(const string & path); -/* Dump a path as follows: - - IF path points to a REGULAR FILE: - dump(path) = attrs( - [ ("type", "regular") - , ("contents", contents(path)) - ]) - - IF path points to a DIRECTORY: - dump(path) = attrs( - [ ("type", "directory") - , ("entries", concat(map(f, sort(entries(path))))) - ]) - where f(fn) = attrs( - [ ("name", fn) - , ("file", dump(path + "/" + fn)) - ]) - - where: - - attrs(as) = concat(map(attr, as)) + encN(0) - attrs((a, b)) = encS(a) + encS(b) - - encS(s) = encN(len(s)) + s + (padding until next 64-bit boundary) - - encN(n) = 64-bit little-endian encoding of n. - - contents(path) = the contents of a regular file. - - sort(strings) = lexicographic sort by 8-bit value (strcmp). - - entries(path) = the entries of a directory, without `.' and - `..'. - - `+' denotes string concatenation. */ - -struct DumpSink -{ - virtual void operator () (const unsigned char * data, unsigned int len) = 0; -}; - -void dumpPath(const string & path, DumpSink & sink); - - #endif /* !__HASH_H */ diff --git a/src/nix.cc b/src/nix.cc index b2bb3bb1a..4f0b97854 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -5,6 +5,7 @@ #include "globals.hh" #include "values.hh" #include "eval.hh" +#include "archive.hh" typedef void (* Operation) (Strings opFlags, Strings opArgs); diff --git a/src/test.cc b/src/test.cc index a3706472e..2eab91d43 100644 --- a/src/test.cc +++ b/src/test.cc @@ -4,6 +4,7 @@ #include #include "hash.hh" +#include "archive.hh" #include "util.hh" #include "eval.hh" #include "values.hh" From 5079ccb45537fe8de4b9579e274523734a3f634e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 20 Jun 2003 10:53:04 +0000 Subject: [PATCH 0085/6440] * Move most of Nix into a library (libnix.a). * Run `test' on `make check'. --- configure.ac | 1 + src/Makefile.am | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index 1aa1a6656..9b36f90a9 100644 --- a/configure.ac +++ b/configure.ac @@ -9,6 +9,7 @@ AC_CANONICAL_HOST AC_PROG_CC AC_PROG_CXX +AC_PROG_RANLIB # Unix shell scripting should die a slow and painful death. AC_DEFINE_UNQUOTED(NIX_VALUES_DIR, "$(eval echo $prefix/values)", Nix values directory.) diff --git a/src/Makefile.am b/src/Makefile.am index 20f172819..afe34ba5f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,16 +1,22 @@ bin_PROGRAMS = nix # fix -noinst_PROGRAMS = test +check_PROGRAMS = test AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. -nix_SOURCES = nix.cc util.cc hash.cc archive.cc md5.c eval.cc values.cc globals.cc db.cc -nix_LDADD = -ldb_cxx-4 -lATerm +nix_SOURCES = nix.cc +nix_LDADD = libnix.a -ldb_cxx-4 -lATerm #fix_SOURCES = fix.cc util.cc hash.cc md5.c #fix_LDADD = -lATerm -test_SOURCES = test.cc util.cc hash.cc archive.cc md5.c eval.cc values.cc globals.cc db.cc -test_LDADD = -ldb_cxx-4 -lATerm +TESTS = test + +test_SOURCES = test.cc +test_LDADD = libnix.a -ldb_cxx-4 -lATerm + +noinst_LIBRARIES = libnix.a + +libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c eval.cc values.cc globals.cc db.cc install-data-local: $(INSTALL) -d $(localstatedir)/nix From 85effedca3e4cc3c10ccd835c9ea4fb712418cb9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 20 Jun 2003 14:11:31 +0000 Subject: [PATCH 0086/6440] * Flags to indicate how values are specified on the command line (--hash, --file, --name). --- src/archive.cc | 11 +++- src/archive.hh | 10 +++ src/nix.cc | 176 ++++++++++++++++++++++++++++++++----------------- src/util.hh | 4 +- 4 files changed, 137 insertions(+), 64 deletions(-) diff --git a/src/archive.cc b/src/archive.cc index 2fdbfb476..591939bb6 100644 --- a/src/archive.cc +++ b/src/archive.cc @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -44,7 +46,7 @@ static void dumpEntries(const string & path, DumpSink & sink) DIR * dir = opendir(path.c_str()); if (!dir) throw SysError("opening directory " + path); - Strings names; + vector names; struct dirent * dirent; while (errno = 0, dirent = readdir(dir)) { @@ -56,7 +58,7 @@ static void dumpEntries(const string & path, DumpSink & sink) sort(names.begin(), names.end()); - for (Strings::iterator it = names.begin(); + for (vector::iterator it = names.begin(); it != names.end(); it++) { writeString("entry", sink); @@ -134,3 +136,8 @@ void dumpPath(const string & path, DumpSink & sink) writeString(")", sink); } + + +void restorePath(const string & path, ReadSource & source) +{ +} diff --git a/src/archive.hh b/src/archive.hh index bfd96b45c..d351c6bf6 100644 --- a/src/archive.hh +++ b/src/archive.hh @@ -46,3 +46,13 @@ struct DumpSink }; void dumpPath(const string & path, DumpSink & sink); + + +struct ReadSource +{ + /* The callee should store exactly *len bytes in the buffer + pointed to by data. It should block if that much data is not + yet available, or throw an error if it is not going to be + available. */ + virtual void operator () (const unsigned char * data, unsigned int len) = 0; +}; diff --git a/src/nix.cc b/src/nix.cc index 4f0b97854..8380abc20 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -11,34 +11,94 @@ typedef void (* Operation) (Strings opFlags, Strings opArgs); -/* Parse a supposed value argument. This can be a hash (the simple - case), a symbolic name (in which case we do a lookup to obtain the - hash), or a file name (which we import to obtain the hash). Note - that in order to disambiguate between symbolic names and file - names, a file name should contain at least one `/'. */ -Hash parseValueArg(string s) -{ - try { - return parseHash(s); - } catch (BadRefError e) { }; +typedef enum { atpHash, atpName, atpPath, atpUnknown } ArgType; - if (s.find('/') != string::npos) { - return addValue(s); - } else { - throw Error("not implemented"); +static ArgType argType = atpUnknown; + + +/* Nix syntax: + + nix [OPTIONS...] [ARGUMENTS...] + + Operations: + + --evaluate / -e: evaluate values + --delete / -d: delete values + --query / -q: query stored values + --add: add values + --verify: verify Nix structures + --dump: dump a file or value + --init: initialise the Nix database + --version: output version information + --help: display help + + Source selection for operations that work on values: + + --file / -f: by file name + --hash / -h: by hash + --name / -n: by symbolic name + + Query suboptions: + + Selection: + + --all / -a: query all stored values, otherwise given values + + Information: + + --info / -i: general value information + + Options: + + --verbose / -v: verbose operation +*/ + + +/* Parse the `-f' / `-h' / `-n' flags, i.e., the type of value + arguments. These flags are deleted from the referenced vector. */ +void getArgType(Strings & flags) +{ + for (Strings::iterator it = flags.begin(); + it != flags.end(); ) + { + string arg = *it; + ArgType tp; + if (arg == "--hash" || arg == "-h") + tp = atpHash; + else if (arg == "--name" || arg == "-n") + tp = atpName; + else if (arg == "--file" || arg == "-f") + tp = atpPath; + else { + it++; + continue; + } + if (argType != atpUnknown) + throw UsageError("only one argument type specified may be specified"); + argType = tp; + it = flags.erase(it); } + if (argType == atpUnknown) + throw UsageError("argument type not specified"); } /* Evaluate values. */ static void opEvaluate(Strings opFlags, Strings opArgs) { + getArgType(opFlags); if (!opFlags.empty()) throw UsageError("unknown flag"); for (Strings::iterator it = opArgs.begin(); it != opArgs.end(); it++) { - Hash hash = parseValueArg(*it); + Hash hash; + if (argType == atpHash) + hash = parseHash(*it); + else if (argType == atpName) + throw Error("not implemented"); + else if (argType == atpPath) + hash = addValue(*it); Expr e = ATmake("Deref(Hash())", ((string) hash).c_str()); cerr << printExpr(evalValue(e)) << endl; } @@ -47,6 +107,8 @@ static void opEvaluate(Strings opFlags, Strings opArgs) static void opDelete(Strings opFlags, Strings opArgs) { + getArgType(opFlags); + cerr << "delete!\n"; } @@ -55,6 +117,7 @@ static void opDelete(Strings opFlags, Strings opArgs) those values. */ static void opAdd(Strings opFlags, Strings opArgs) { + getArgType(opFlags); if (!opFlags.empty()) throw UsageError("unknown flag"); for (Strings::iterator it = opArgs.begin(); @@ -78,11 +141,22 @@ struct StdoutSink : DumpSink /* Dump a value to standard output */ static void opDump(Strings opFlags, Strings opArgs) { + getArgType(opFlags); if (!opFlags.empty()) throw UsageError("unknown flag"); if (opArgs.size() != 1) throw UsageError("only one argument allowed"); StdoutSink sink; - dumpPath(opArgs[0], sink); + string arg = *opArgs.begin(); + string path; + + if (argType == atpHash) + path = queryValuePath(parseHash(arg)); + else if (argType == atpName) + throw Error("not implemented"); + else if (argType == atpPath) + path = arg; + + dumpPath(*opArgs.begin(), sink); } @@ -96,59 +170,43 @@ static void opInit(Strings opFlags, Strings opArgs) } -/* Nix syntax: - - nix [OPTIONS...] [ARGUMENTS...] - - Operations: - - --evaluate / -e: evaluate values - --delete / -d: delete values - --query / -q: query stored values - --add: add values - --verify: verify Nix structures - --dump: dump a file or value - --init: initialise the Nix database - --version: output version information - --help: display help - - Operations that work on values accept the hash code of a value, the - symbolic name of a value, or a file name of a external value that - will be added prior to the operation. - - Query suboptions: - - Selection: - - --all / -a: query all stored values, otherwise given values - - Information: - - --info / -i: general value information - - Options: - - --verbose / -v: verbose operation -*/ - /* Initialize, process arguments, and dispatch to the right operation. */ -void run(Strings::iterator argCur, Strings::iterator argEnd) +void run(int argc, char * * argv) { - Strings opFlags, opArgs; - Operation op = 0; - /* Setup Nix paths. */ nixValues = NIX_VALUES_DIR; nixLogDir = NIX_LOG_DIR; nixDB = (string) NIX_STATE_DIR + "/nixstate.db"; + /* Put the arguments in a vector. */ + Strings args; + while (argc--) args.push_back(*argv++); + args.erase(args.begin()); + + /* Expand compound dash options (i.e., `-qlf' -> `-q -l -f'). */ + for (Strings::iterator it = args.begin(); + it != args.end(); ) + { + string arg = *it; + if (arg.length() > 2 && arg[0] == '-' && arg[1] != '-') { + for (unsigned int i = 1; i < arg.length(); i++) + args.insert(it, (string) "-" + arg[i]); + it = args.erase(it); + } else it++; + } + + Strings opFlags, opArgs; + Operation op = 0; + /* Scan the arguments; find the operation, set global flags, put all other flags in a list, and put all other arguments in another list. */ - while (argCur != argEnd) { - string arg = *argCur++; + for (Strings::iterator it = args.begin(); + it != args.end(); it++) + { + string arg = *it; Operation oldOp = op; @@ -184,9 +242,7 @@ int main(int argc, char * * argv) ATinit(argc, argv, &bottomOfStack); try { - Strings args; - while (argc--) args.push_back(*argv++); - run(args.begin() + 1, args.end()); + run(argc, argv); } catch (UsageError & e) { cerr << "error: " << e.what() << endl << "Try `nix --help' for more information.\n"; diff --git a/src/util.hh b/src/util.hh index 5b41fcea8..45719e701 100644 --- a/src/util.hh +++ b/src/util.hh @@ -2,7 +2,7 @@ #define __UTIL_H #include -#include +#include #include #include @@ -34,7 +34,7 @@ public: }; -typedef vector Strings; +typedef list Strings; /* The canonical system name, as returned by config.guess. */ From 5f5cab0ac7c26783a4544feb31708d4f8e0f4a51 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 23 Jun 2003 13:27:59 +0000 Subject: [PATCH 0087/6440] * A function to restore from a Nix archive. * addValue() can now import any dumpable FS object. --- src/archive.cc | 196 +++++++++++++++++++++++++++++++++++++++++++++++-- src/archive.hh | 4 +- src/eval.cc | 2 +- src/nix.cc | 3 +- src/test.cc | 24 +++++- src/values.cc | 78 +++++++++++++++++++- 6 files changed, 293 insertions(+), 14 deletions(-) diff --git a/src/archive.cc b/src/archive.cc index 591939bb6..d0cf6ca34 100644 --- a/src/archive.cc +++ b/src/archive.cc @@ -10,7 +10,10 @@ #include "util.hh" -static void pad(unsigned int len, DumpSink & sink) +static string archiveVersion1 = "nix-archive-1"; + + +static void writePadding(unsigned int len, DumpSink & sink) { if (len % 8) { unsigned char zero[8]; @@ -37,10 +40,13 @@ static void writeString(const string & s, DumpSink & sink) unsigned int len = s.length(); writeInt(len, sink); sink((const unsigned char *) s.c_str(), len); - pad(len, sink); + writePadding(len, sink); } +static void dump(const string & path, DumpSink & sink); + + static void dumpEntries(const string & path, DumpSink & sink) { DIR * dir = opendir(path.c_str()); @@ -65,8 +71,8 @@ static void dumpEntries(const string & path, DumpSink & sink) writeString("(", sink); writeString("name", sink); writeString(*it, sink); - writeString("file", sink); - dumpPath(path + "/" + *it, sink); + writeString("node", sink); + dump(path + "/" + *it, sink); writeString(")", sink); } @@ -96,13 +102,13 @@ static void dumpContents(const string & path, unsigned int size, if (total != size) throw SysError("file changed while reading it: " + path); - pad(size, sink); + writePadding(size, sink); close(fd); /* !!! close on exception */ } -void dumpPath(const string & path, DumpSink & sink) +static void dump(const string & path, DumpSink & sink) { struct stat st; if (lstat(path.c_str(), &st)) @@ -138,6 +144,182 @@ void dumpPath(const string & path, DumpSink & sink) } -void restorePath(const string & path, ReadSource & source) +void dumpPath(const string & path, DumpSink & sink) { + writeString(archiveVersion1, sink); + dump(path, sink); } + + +static Error badArchive(string s) +{ + return Error("bad archive: " + s); +} + + +static void readPadding(unsigned int len, RestoreSource & source) +{ + if (len % 8) { + unsigned char zero[8]; + unsigned int n = 8 - (len % 8); + source(zero, n); + for (unsigned int i = 0; i < n; i++) + if (zero[i]) throw badArchive("non-zero padding"); + } +} + +static unsigned int readInt(RestoreSource & source) +{ + unsigned char buf[8]; + source(buf, sizeof(buf)); + if (buf[4] || buf[5] || buf[6] || buf[7]) + throw Error("implementation cannot deal with > 32-bit integers"); + return + buf[0] | + (buf[1] << 8) | + (buf[2] << 16) | + (buf[3] << 24); +} + + +static string readString(RestoreSource & source) +{ + unsigned int len = readInt(source); + char buf[len]; + source((const unsigned char *) buf, len); + readPadding(len, source); + return string(buf, len); +} + + +static void skipGeneric(RestoreSource & source) +{ + if (readString(source) == "(") { + while (readString(source) != ")") + skipGeneric(source); + } +} + + +static void restore(const string & path, RestoreSource & source); + + +static void restoreEntry(const string & path, RestoreSource & source) +{ + string s, name; + + s = readString(source); + if (s != "(") throw badArchive("expected open tag"); + + while (1) { + s = readString(source); + + if (s == ")") { + break; + } else if (s == "name") { + name = readString(source); + } else if (s == "node") { + if (s == "") throw badArchive("entry name missing"); + restore(path + "/" + name, source); + } else { + throw badArchive("unknown field " + s); + skipGeneric(source); + } + } +} + + +static void restoreContents(int fd, const string & path, RestoreSource & source) +{ + unsigned int size = readInt(source); + unsigned int left = size; + unsigned char buf[65536]; + + while (left) { + unsigned int n = sizeof(buf); + if (n > left) n = left; + source(buf, n); + if (write(fd, buf, n) != (ssize_t) n) + throw SysError("writing file " + path); + left -= n; + } + + readPadding(size, source); +} + + +static void restore(const string & path, RestoreSource & source) +{ + string s; + + s = readString(source); + if (s != "(") throw badArchive("expected open tag"); + + enum { tpUnknown, tpRegular, tpDirectory, tpSymlink } type = tpUnknown; + int fd = -1; /* !!! close on exception */ + + while (1) { + s = readString(source); + + if (s == ")") { + break; + } + + else if (s == "type") { + if (type != tpUnknown) + throw badArchive("multiple type fields"); + string t = readString(source); + + if (t == "regular") { + type = tpRegular; + fd = open(path.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd == -1) + throw SysError("creating file " + path); + } + + else if (t == "directory") { + type = tpDirectory; + if (mkdir(path.c_str(), 0777) == -1) + throw SysError("creating directory " + path); + } + + else if (t == "symlink") { + type = tpSymlink; + } + + else throw badArchive("unknown file type " + t); + + } + + else if (s == "contents" && type == tpRegular) { + restoreContents(fd, path, source); + } + + else if (s == "entry" && type == tpDirectory) { + restoreEntry(path, source); + } + + else if (s == "target" && type == tpSymlink) { + string target = readString(source); + if (symlink(target.c_str(), path.c_str()) == -1) + throw SysError("creating symlink " + path); + } + + else { + throw badArchive("unknown field " + s); + skipGeneric(source); + } + + } + + if (fd != -1) close(fd); +} + + +void restorePath(const string & path, RestoreSource & source) +{ + if (readString(source) != archiveVersion1) + throw badArchive("expected Nix archive"); + restore(path, source); +} + diff --git a/src/archive.hh b/src/archive.hh index d351c6bf6..7d9b1e2b5 100644 --- a/src/archive.hh +++ b/src/archive.hh @@ -48,7 +48,7 @@ struct DumpSink void dumpPath(const string & path, DumpSink & sink); -struct ReadSource +struct RestoreSource { /* The callee should store exactly *len bytes in the buffer pointed to by data. It should block if that much data is not @@ -56,3 +56,5 @@ struct ReadSource available. */ virtual void operator () (const unsigned char * data, unsigned int len) = 0; }; + +void restorePath(const string & path, RestoreSource & source); diff --git a/src/eval.cc b/src/eval.cc index 3b95588d7..dc1fe3157 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -123,8 +123,8 @@ static Hash computeDerived(Hash sourceHash, string targetName, } catch (exception & e) { cerr << "build error: " << e.what() << endl; - _exit(1); } + _exit(1); } diff --git a/src/nix.cc b/src/nix.cc index 8380abc20..de16ed982 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -133,7 +133,8 @@ struct StdoutSink : DumpSink (const unsigned char * data, unsigned int len) { /* Don't use cout, it's slow as hell! */ - write(STDOUT_FILENO, (char *) data, len); + if (write(STDOUT_FILENO, (char *) data, len) != len) + throw SysError("writing to stdout"); } }; diff --git a/src/test.cc b/src/test.cc index 2eab91d43..c8e3292e3 100644 --- a/src/test.cc +++ b/src/test.cc @@ -23,7 +23,21 @@ struct MySink : DumpSink virtual void operator () (const unsigned char * data, unsigned int len) { /* Don't use cout, it's slow as hell! */ - write(STDOUT_FILENO, (char *) data, len); + if (write(STDOUT_FILENO, (char *) data, len) != (ssize_t) len) + throw SysError("writing to stdout"); + } +}; + + +struct MySource : RestoreSource +{ + virtual void operator () (const unsigned char * data, unsigned int len) + { + ssize_t res = read(STDIN_FILENO, (char *) data, len); + if (res == -1) + throw SysError("reading from stdin"); + if (res != (ssize_t) len) + throw Error("not enough data available on stdin"); } }; @@ -53,6 +67,14 @@ void runTests() cout << (string) hashPath("scratch") << endl; #endif + /* Restoring. */ +#if 1 + MySource source; + restorePath("outdir", source); + cout << (string) hashPath("outdir") << endl; + return; +#endif + /* Set up the test environment. */ mkdir("scratch", 0777); diff --git a/src/values.cc b/src/values.cc index 7cef5d3de..22f84c83e 100644 --- a/src/values.cc +++ b/src/values.cc @@ -1,13 +1,85 @@ +#include + +#include +#include + #include "values.hh" #include "globals.hh" #include "db.hh" +#include "archive.hh" + + +struct CopySink : DumpSink +{ + int fd; + virtual void operator () (const unsigned char * data, unsigned int len) + { + if (write(fd, (char *) data, len) != (ssize_t) len) + throw SysError("writing to child"); + } +}; + + +struct CopySource : RestoreSource +{ + int fd; + virtual void operator () (const unsigned char * data, unsigned int len) + { + ssize_t res = read(fd, (char *) data, len); + if (res == -1) + throw SysError("reading from parent"); + if (res != (ssize_t) len) + throw Error("not enough data available on parent"); + } +}; static void copyFile(string src, string dst) { - int res = system(("cat " + src + " > " + dst).c_str()); /* !!! escape */ - if (WEXITSTATUS(res) != 0) - throw Error("cannot copy " + src + " to " + dst); + /* Unfortunately C++ doesn't support coprocedures, so we have no + nice way to chain CopySink and CopySource together. Instead we + fork off a child to run the sink. (Fork-less platforms should + use a thread). */ + + /* Create a pipe. */ + int fds[2]; + if (pipe(fds) == -1) throw SysError("creating pipe"); + + /* Fork. */ + pid_t pid; + switch (pid = fork()) { + + case -1: + throw SysError("unable to fork"); + + case 0: /* child */ + try { + close(fds[1]); + CopySource source; + source.fd = fds[0]; + restorePath(dst, source); + _exit(0); + } catch (exception & e) { + cerr << "error: " << e.what() << endl; + } + _exit(1); + } + + close(fds[0]); + + /* Parent. */ + + CopySink sink; + sink.fd = fds[1]; + dumpPath(src, sink); + + /* Wait for the child to finish. */ + int status; + if (waitpid(pid, &status, 0) != pid) + throw SysError("waiting for child"); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + throw Error("cannot copy file: child died"); } From c0cbaef4bece0c2447828739dd9622c329948064 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 23 Jun 2003 14:08:34 +0000 Subject: [PATCH 0088/6440] * `nix --restore' command. --- src/nix.cc | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index de16ed982..bd3565810 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -26,9 +26,13 @@ static ArgType argType = atpUnknown; --delete / -d: delete values --query / -q: query stored values --add: add values - --verify: verify Nix structures - --dump: dump a file or value + + --dump: dump a value as a Nix archive + --restore: restore a value from a Nix archive + --init: initialise the Nix database + --verify: verify Nix structures + --version: output version information --help: display help @@ -132,14 +136,14 @@ struct StdoutSink : DumpSink virtual void operator () (const unsigned char * data, unsigned int len) { - /* Don't use cout, it's slow as hell! */ - if (write(STDOUT_FILENO, (char *) data, len) != len) + if (write(STDOUT_FILENO, (char *) data, len) != (ssize_t) len) throw SysError("writing to stdout"); } }; -/* Dump a value to standard output */ +/* Dump a value as a Nix archive. The archive is written to standard + output. */ static void opDump(Strings opFlags, Strings opArgs) { getArgType(opFlags); @@ -157,7 +161,33 @@ static void opDump(Strings opFlags, Strings opArgs) else if (argType == atpPath) path = arg; - dumpPath(*opArgs.begin(), sink); + dumpPath(path, sink); +} + + +/* A source that read restore intput to stdin. */ +struct StdinSource : RestoreSource +{ + virtual void operator () (const unsigned char * data, unsigned int len) + { + ssize_t res = read(STDIN_FILENO, (char *) data, len); + if (res == -1) + throw SysError("reading from stdin"); + if (res != (ssize_t) len) + throw Error("not enough data available on stdin"); + } +}; + + +/* Restore a value from a Nix archive. The archive is written to + standard input. */ +static void opRestore(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (opArgs.size() != 1) throw UsageError("only one argument allowed"); + + StdinSource source; + restorePath(*opArgs.begin(), source); } @@ -219,6 +249,8 @@ void run(int argc, char * * argv) op = opAdd; else if (arg == "--dump") op = opDump; + else if (arg == "--restore") + op = opRestore; else if (arg == "--init") op = opInit; else if (arg[0] == '-') From 692b562342ac7ead43ef06497f6a8b4b6e724ae5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 23 Jun 2003 14:40:49 +0000 Subject: [PATCH 0089/6440] * `nix --delete' command. --- src/nix.cc | 14 +++++++++++++- src/test.cc | 4 +++- src/util.cc | 29 +++++++++++++++++++++++++++++ src/util.hh | 5 +++++ src/values.cc | 12 ++++++++++++ src/values.hh | 4 ++++ 6 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index bd3565810..9b21f0379 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -112,8 +112,20 @@ static void opEvaluate(Strings opFlags, Strings opArgs) static void opDelete(Strings opFlags, Strings opArgs) { getArgType(opFlags); + if (!opFlags.empty()) throw UsageError("unknown flag"); - cerr << "delete!\n"; + for (Strings::iterator it = opArgs.begin(); + it != opArgs.end(); it++) + { + Hash hash; + if (argType == atpHash) + hash = parseHash(*it); + else if (argType == atpName) + throw Error("not implemented"); + else + throw Error("invalid argument type"); + deleteValue(hash); + } } diff --git a/src/test.cc b/src/test.cc index c8e3292e3..268b35d89 100644 --- a/src/test.cc +++ b/src/test.cc @@ -68,7 +68,7 @@ void runTests() #endif /* Restoring. */ -#if 1 +#if 0 MySource source; restorePath("outdir", source); cout << (string) hashPath("outdir") << endl; @@ -116,6 +116,8 @@ void runTests() Expr e3 = ATmake("Deref(Hash())", ((string) h3).c_str()); evalTest(e3); + + deleteValue(h3); } diff --git a/src/util.cc b/src/util.cc index 8c397aace..4dada48ba 100644 --- a/src/util.cc +++ b/src/util.cc @@ -1,5 +1,10 @@ #include +#include +#include +#include +#include + #include "util.hh" @@ -49,6 +54,30 @@ string baseNameOf(string path) } +void deletePath(string path) +{ + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError("getting attributes of path " + path); + + if (S_ISDIR(st.st_mode)) { + DIR * dir = opendir(path.c_str()); + + struct dirent * dirent; + while (errno = 0, dirent = readdir(dir)) { + string name = dirent->d_name; + if (name == "." || name == "..") continue; + deletePath(path + "/" + name); + } + + closedir(dir); /* !!! close on exception */ + } + + if (remove(path.c_str()) == -1) + throw SysError("cannot unlink " + path); +} + + void debug(string s) { cerr << "debug: " << s << endl; diff --git a/src/util.hh b/src/util.hh index 45719e701..7d5f00a2e 100644 --- a/src/util.hh +++ b/src/util.hh @@ -54,6 +54,11 @@ string dirOf(string path); string baseNameOf(string path); +/* Delete a path; i.e., in the case of a directory, it is deleted + recursively. Don't use this at home, kids. */ +void deletePath(string path); + + void debug(string s); diff --git a/src/values.cc b/src/values.cc index 22f84c83e..c8a3b58cb 100644 --- a/src/values.cc +++ b/src/values.cc @@ -135,6 +135,18 @@ string fetchURL(string url) #endif +void deleteValue(Hash hash) +{ + string name; + if (queryDB(nixDB, dbRefs, hash, name)) { + string fn = absValuePath(name); + deletePath(fn); + delDB(nixDB, dbRefs, hash); + } +} + + +/* !!! bad name, "query" implies no side effect => getValuePath() */ string queryValuePath(Hash hash) { bool checkedNet = false; diff --git a/src/values.hh b/src/values.hh index 5dd7b89c4..d66ae770f 100644 --- a/src/values.hh +++ b/src/values.hh @@ -13,6 +13,10 @@ using namespace std; Hash addValue(string pathName); +/* Delete a value from the nixValues directory. */ +void deleteValue(Hash hash); + + /* Obtain the path of a value with the given hash. If a file with that hash is known to exist in the local file system (as indicated by the dbRefs database), we use that. Otherwise, we attempt to From 2b07b0e7ebee69e6a64013dcdda363c393d3f4fc Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 25 Jun 2003 14:58:56 +0000 Subject: [PATCH 0090/6440] * Minor cleanups. --- src/Makefile.am | 8 +++----- src/nix.cc | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index afe34ba5f..4d8cd4229 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,4 @@ -bin_PROGRAMS = nix # fix +bin_PROGRAMS = nix check_PROGRAMS = test AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. @@ -6,9 +6,6 @@ AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. nix_SOURCES = nix.cc nix_LDADD = libnix.a -ldb_cxx-4 -lATerm -#fix_SOURCES = fix.cc util.cc hash.cc md5.c -#fix_LDADD = -lATerm - TESTS = test test_SOURCES = test.cc @@ -16,7 +13,8 @@ test_LDADD = libnix.a -ldb_cxx-4 -lATerm noinst_LIBRARIES = libnix.a -libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c eval.cc values.cc globals.cc db.cc +libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c \ + eval.cc values.cc globals.cc db.cc install-data-local: $(INSTALL) -d $(localstatedir)/nix diff --git a/src/nix.cc b/src/nix.cc index 9b21f0379..fe9ab453b 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -60,7 +60,7 @@ static ArgType argType = atpUnknown; /* Parse the `-f' / `-h' / `-n' flags, i.e., the type of value arguments. These flags are deleted from the referenced vector. */ -void getArgType(Strings & flags) +static void getArgType(Strings & flags) { for (Strings::iterator it = flags.begin(); it != flags.end(); ) @@ -215,7 +215,7 @@ static void opInit(Strings opFlags, Strings opArgs) /* Initialize, process arguments, and dispatch to the right operation. */ -void run(int argc, char * * argv) +static void run(int argc, char * * argv) { /* Setup Nix paths. */ nixValues = NIX_VALUES_DIR; From 3ec525258258ea50a411eb6b7d3c6aa7ecac708b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 25 Jun 2003 15:50:37 +0000 Subject: [PATCH 0091/6440] * Improved syntax and semantics for Nix expressions. --- src/eval.hh | 91 +++++++++++++++++++++++++---------------------------- 1 file changed, 43 insertions(+), 48 deletions(-) diff --git a/src/eval.hh b/src/eval.hh index 2e764b1bd..c1b2f2139 100644 --- a/src/eval.hh +++ b/src/eval.hh @@ -10,67 +10,62 @@ extern "C" { using namespace std; -/* Abstract syntax of Nix values: +/* Abstract syntax of Nix expressions. - e := Deref(e) -- external expression - | Hash(h) -- value reference - | Str(s) -- string constant - | Bool(b) -- boolean constant - | Var(x) -- variable - | App(e, e) -- application - | Lam(x, e) -- lambda abstraction - | Exec(platform, e, [Arg(e, e)]) - -- primitive; execute e with args e* on platform - ; + An expression describes a (partial) state of the file system in a + referentially transparent way. The operational effect of + evaluating an expression is that the state described by the + expression is realised. - TODO: Deref(e) allows computed external expressions, which might be - too expressive; perhaps this should be Deref(h). + File : String * Content * [Expr] -> Expr - Semantics + File(path, content, refs) specifies a file object (its full path + and contents), along with all file objects referenced by it (that + is, that it has pointers to). We assume that all files are + self-referential. This prevents us from having to deal with + cycles. - Each rule given as eval(e) => e', i.e., expression e has a normal - form e'. + Derive : String * Expr * [Expr] * [String] -> Expr - eval(Deref(Hash(h))) => eval(loadExpr(h)) + Derive(platform, builder, ins, outs) specifies the creation of new + file objects (in paths declared by `outs') by the execution of a + program `builder' on a platform `platform'. This execution takes + place in a file system state and in an environment given by `ins'. - eval(Hash(h)) => Hash(h) # idem for Str, Bool + Str : String -> Expr - eval(App(e1, e2)) => eval(App(e1', e2)) - where e1' = eval(e1) + A string constant. - eval(App(Lam(var, body), arg)) => - eval(subst(var, arg, body)) + Tup : Expr * Expr -> Expr - eval(Exec(platform, prog, args)) => Hash(h) - where - fn = ... name of the output (random or by hashing expr) ... - h = - if exec( fn - , eval(platform) => Str(...) - , getFile(eval(prog)) - , map(makeArg . eval, args) - ) then - hashPath(fn) - else - undef - ... register ... + Tuples of expressions. - makeArg(Arg(Str(nm), (Hash(h), h))) => (nm, getFile(h)) - makeArg(Arg(Str(nm), (Str(s), _))) => (nm, s) - makeArg(Arg(Str(nm), (Bool(True), _))) => (nm, "1") - makeArg(Arg(Str(nm), (Bool(False), _))) => (nm, undef) + Regular : String -> Content + Directory : [(String, Content)] -> Content + Hash : String -> Content - subst(x, e1, e2) is defined as a generic topdown term - traversal of e2, replacing each `Var(x)' with e1, and not - descending into `Lam(x, _)'. + File content, given either explicitly or implicitly through a cryptographic hash. - Note: all stored expressions must be closed. !!! ugly + The set of expressions in {\em $f$-normal form} is as follows: + + File : String * Content * [FExpr] -> FExpr + + These are completely evaluated Nix expressions. + + The set of expressions in {\em $d$-normal form} is as follows: + + File : String * Content * [DExpr] -> DExpr + Derive : String * DExpr * [Tup] * [String] -> DExpr + + Tup : Str * DExpr -> Tup + Tup : Str * Str -> Tup + + Str : String -> Str + + These are Nix expressions in which the file system result of Derive + expressions has not yet been computed. This is useful for, e.g., + querying dependencies. - getFile :: Hash -> FileName - loadExpr :: Hash -> FileName - hashExpr :: Expr -> Hash - hashPath :: FileName -> Hash - exec :: FileName -> Platform -> FileName -> [(String, String)] -> Status */ typedef ATerm Expr; From d4c3edfaba91a0e5e1e9528749e5b1e178511a6d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 27 Jun 2003 09:55:31 +0000 Subject: [PATCH 0092/6440] * Normalisation. --- src/eval.cc | 142 +++++++++++++++++++++++++++++++++++++++++++++------- src/eval.hh | 71 +++++++++++++++++++------- src/test.cc | 66 ++++++++++++++++++++---- 3 files changed, 233 insertions(+), 46 deletions(-) diff --git a/src/eval.cc b/src/eval.cc index dc1fe3157..831464c18 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include "eval.hh" #include "globals.hh" @@ -91,7 +92,7 @@ static Hash computeDerived(Hash sourceHash, string targetName, } #endif - build: +// build: /* Fill in the environment. We don't bother freeing the strings, since we'll exec or die soon @@ -210,13 +211,14 @@ Hash hashExpr(Expr e) /* Evaluate an expression; the result must be a string. */ static string evalString(Expr e) { - e = evalValue(e); + e = whNormalise(e); char * s; if (ATmatch(e, "Str()", &s)) return s; else throw badTerm("string value expected", e); } +#if 0 /* Evaluate an expression; the result must be a value reference. */ static Hash evalHash(Expr e) { @@ -225,8 +227,10 @@ static Hash evalHash(Expr e) if (ATmatch(e, "Hash()", &s)) return parseHash(s); else throw badTerm("value reference expected", e); } +#endif +#if 0 /* Evaluate a list of arguments into normal form. */ void evalArgs(ATermList args, ATermList & argsNF, Environment & env) { @@ -255,6 +259,7 @@ void evalArgs(ATermList args, ATermList & argsNF, Environment & env) argsNF = ATreverse(argsNF); } +#endif Expr substExpr(string x, Expr rep, Expr e) @@ -302,19 +307,13 @@ Expr substExpr(string x, Expr rep, Expr e) } +#if 0 Expr evalValue(Expr e) { char * s; Expr eBuildPlatform, eProg, e2, e3, e4; ATermList args; - /* Normal forms. */ - if (ATmatch(e, "Str()", &s) || - ATmatch(e, "Bool(True)") || - ATmatch(e, "Bool(False)") || - ATmatch(e, "Lam(, )", &s, &e2)) - return e; - /* Value references. */ if (ATmatch(e, "Hash()", &s)) { parseHash(s); /* i.e., throw exception if not valid */ @@ -329,14 +328,6 @@ Expr evalValue(Expr e) return evalValue(e3); } - /* Application. */ - if (ATmatch(e, "App(, )", &e2, &e3)) { - e2 = evalValue(e2); - if (!ATmatch(e2, "Lam(, )", &s, &e4)) - throw badTerm("expecting lambda", e2); - return evalValue(substExpr(s, e3, e4)); - } - /* Execution primitive. */ if (ATmatch(e, "Exec(, , [])", @@ -376,3 +367,120 @@ Expr evalValue(Expr e) /* Barf. */ throw badTerm("invalid expression", e); } +#endif + + +Expr whNormalise(Expr e) +{ + char * s; + Expr e2, e3, e4, e5; + + /* Normal forms. */ + if (ATmatch(e, "Str()", &s) || + ATmatch(e, "Bool(True)") || + ATmatch(e, "Bool(False)") || + ATmatch(e, "Lam(, )", &s, &e2) || + ATmatch(e, "File(, , )", &s, &e2, &e3) || + ATmatch(e, "Derive(, , , )", &e2, &e3, &e4, &e5)) + return e; + + /* Application. */ + if (ATmatch(e, "App(, )", &e2, &e3)) { + e2 = whNormalise(e2); + if (!ATmatch(e2, "Lam(, )", &s, &e4)) + throw badTerm("expecting lambda", e2); + return whNormalise(substExpr(s, e3, e4)); + } + + throw badTerm("invalid expression", e); +} + + +Expr dNormalise(Expr e) +{ + e = whNormalise(e); + /* !!! todo */ + return e; +} + + +Expr fNormalise(Expr e) +{ + e = dNormalise(e); + + char * s; + Expr e2, e3; + + if (ATmatch(e, "File(, , [])", &s, &e2, &e3)) { + + ATermList refs = (ATermList) e3, refs2 = ATempty; + while (!ATisEmpty(refs)) { + ATerm ref = ATgetFirst(refs); + refs2 = ATinsert(refs2, fNormalise(ref)); + refs = ATgetNext(refs); + } + refs2 = ATreverse(refs2); + + return ATmake("File(, , )", s, e2, refs2); + + } + + else return e; +} + + +void writeContent(string path, Content content) +{ + char * s; + + if (ATmatch(content, "Regular()", &s)) { + + int fd; /* !!! close on exception */ + fd = open(path.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd == -1) + throw SysError("creating file " + path); + + int len = strlen(s); + if (write(fd, s, len) != len) + throw SysError("writing file " + path); + + close(fd); + } + + else throw badTerm("ill-formed content", content); +} + + +struct RStatus +{ + /* !!! the comparator of this hash should match the semantics of + the file system */ + map paths; +}; + + +static void realise2(RStatus & status, Expr e) +{ + char * s; + Content content; + ATermList refs; + + if (!ATmatch(e, "File(, , [])", &s, &content, &refs)) + throw badTerm("not f-normalised", e); + + string path(s); + + while (!ATisEmpty(refs)) { + realise2(status, ATgetFirst(refs)); + refs = ATgetNext(refs); + } + + writeContent(path, content); +} + + +void realise(Expr e) +{ + RStatus status; + realise2(status, e); +} diff --git a/src/eval.hh b/src/eval.hh index c1b2f2139..807f98f85 100644 --- a/src/eval.hh +++ b/src/eval.hh @@ -10,14 +10,14 @@ extern "C" { using namespace std; -/* Abstract syntax of Nix expressions. +/* \section{Abstract syntax of Nix expressions} An expression describes a (partial) state of the file system in a referentially transparent way. The operational effect of evaluating an expression is that the state described by the expression is realised. - File : String * Content * [Expr] -> Expr + File : Path * Content * [Expr] -> Expr File(path, content, refs) specifies a file object (its full path and contents), along with all file objects referenced by it (that @@ -25,12 +25,13 @@ using namespace std; self-referential. This prevents us from having to deal with cycles. - Derive : String * Expr * [Expr] * [String] -> Expr + Derive : String * Path * [Expr] * [Expr] * [Expr] -> Expr - Derive(platform, builder, ins, outs) specifies the creation of new - file objects (in paths declared by `outs') by the execution of a - program `builder' on a platform `platform'. This execution takes - place in a file system state and in an environment given by `ins'. + Derive(platform, builder, ins, outs, env) specifies the creation of + new file objects (in paths declared by `outs') by the execution of + a program `builder' on a platform `platform'. This execution takes + place in a file system state given by `ins'. `env' specifies a + mapping of strings to strings. Str : String -> Expr @@ -40,39 +41,73 @@ using namespace std; Tuples of expressions. - Regular : String -> Content - Directory : [(String, Content)] -> Content - Hash : String -> Content + [ !!! NOT IMPLEMENTED + Regular : String -> Content + Directory : [(String, Content)] -> Content + (this complicates unambiguous normalisation) + ] + CHash : Hash -> Content - File content, given either explicitly or implicitly through a cryptographic hash. + File content, given either in situ, or through an external reference + to the file system or url-space decorated with a hash to preserve purity. - The set of expressions in {\em $f$-normal form} is as follows: + DISCUSSION: the idea is that a Regular/Directory is interchangeable + with its CHash. This would appear to break referential + transparency, e.g., Derive(..., ..., [...CHash(h)...], ...) can + only be reduced in a context were the Regular/Directory equivalent + of Hash(h) is known. However, CHash should be viewed strictly as a + shorthand; that is, when we export an expression containing a + CHash, we should also export the file object referenced by that + CHash. - File : String * Content * [FExpr] -> FExpr - These are completely evaluated Nix expressions. + \section{Reduction rules} - The set of expressions in {\em $d$-normal form} is as follows: + ... + + + \section{Normals forms} + + An expression is in {\em weak head normal form} if it is a lambda, + a string or boolean value, or a File or Derive value. + + An expression is in {\em $d$-normal form} if it matches the + signature FExpr: File : String * Content * [DExpr] -> DExpr - Derive : String * DExpr * [Tup] * [String] -> DExpr + Derive : String * Path * [Tup] * [Tup2] -> DExpr Tup : Str * DExpr -> Tup Tup : Str * Str -> Tup + Tup : Str * Str -> Tup2 + Str : String -> Str These are Nix expressions in which the file system result of Derive expressions has not yet been computed. This is useful for, e.g., querying dependencies. + An expression is in {\em $f$-normal form} if it matches the + signature FExpr: + + File : String * Content * [FExpr] -> FExpr + + These are completely evaluated Nix expressions. + */ typedef ATerm Expr; +typedef ATerm Content; -/* Evaluate an expression. */ -Expr evalValue(Expr e); +/* Expression normalisation. */ +Expr whNormalise(Expr e); +Expr dNormalise(Expr e); +Expr fNormalise(Expr e); + +/* Realise a $f$-normalised expression in the file system. */ +void realise(Expr e); /* Return a canonical textual representation of an expression. */ string printExpr(Expr e); diff --git a/src/test.cc b/src/test.cc index 268b35d89..d912eaa6a 100644 --- a/src/test.cc +++ b/src/test.cc @@ -11,13 +11,27 @@ #include "globals.hh" -void evalTest(Expr e) +typedef Expr (* Normaliser) (Expr); + + +void eval(Normaliser n, Expr e) { - e = evalValue(e); + e = n(e); cout << (string) hashExpr(e) << ": " << printExpr(e) << endl; } +void evalFail(Normaliser n, Expr e) +{ + try { + e = n(e); + abort(); + } catch (Error e) { + cout << "error (expected): " << e.what() << endl; + } +} + + struct MySink : DumpSink { virtual void operator () (const unsigned char * data, unsigned int len) @@ -90,19 +104,48 @@ void runTests() /* Expression evaluation. */ - evalTest(ATmake("Str(\"Hello World\")")); - evalTest(ATmake("Bool(True)")); - evalTest(ATmake("Bool(False)")); - evalTest(ATmake("App(Lam(\"x\", Var(\"x\")), Str(\"Hello World\"))")); - evalTest(ATmake("App(App(Lam(\"x\", Lam(\"y\", Var(\"x\"))), Str(\"Hello World\")), Str(\"Hallo Wereld\"))")); - evalTest(ATmake("App(Lam(\"sys\", Lam(\"x\", [Var(\"x\"), Var(\"sys\")])), Str(\"i686-suse-linux\"))")); + eval(whNormalise, + ATmake("Str(\"Hello World\")")); + eval(whNormalise, + ATmake("Bool(True)")); + eval(whNormalise, + ATmake("Bool(False)")); + eval(whNormalise, + ATmake("App(Lam(\"x\", Var(\"x\")), Str(\"Hello World\"))")); + eval(whNormalise, + ATmake("App(App(Lam(\"x\", Lam(\"y\", Var(\"x\"))), Str(\"Hello World\")), Str(\"Hallo Wereld\"))")); + eval(whNormalise, + ATmake("App(Lam(\"sys\", Lam(\"x\", [Var(\"x\"), Var(\"sys\")])), Str(\"i686-suse-linux\"))")); + evalFail(whNormalise, + ATmake("Foo(123)")); + + string builder1fn = absPath("./test-builder-1.sh"); + Hash builder1h = hashFile(builder1fn); + + string fn1 = nixValues + "/builder-1.sh"; + Expr e1 = ATmake("File(, ExtFile(, ), [])", + fn1.c_str(), + builder1h.c_str(), + builder1fn.c_str()); + eval(fNormalise, e1); + + string fn2 = nixValues + "/refer.txt"; + Expr e2 = ATmake("File(, Regular(), [])", + fn2.c_str(), + ("I refer to " + fn1).c_str(), + e1); + eval(fNormalise, e2); + + realise(e2); + +#if 0 Hash builder1 = addValue("./test-builder-1.sh"); Expr e1 = ATmake("Exec(Str(), Hash(), [])", thisSystem.c_str(), ((string) builder1).c_str()); - evalTest(e1); + eval(e1); Hash builder2 = addValue("./test-builder-2.sh"); @@ -110,14 +153,15 @@ void runTests() "Exec(Str(), Hash(), [Tup(Str(\"src\"), )])", thisSystem.c_str(), ((string) builder2).c_str(), e1); - evalTest(e2); + eval(e2); Hash h3 = addValue("./test-expr-1.nix"); Expr e3 = ATmake("Deref(Hash())", ((string) h3).c_str()); - evalTest(e3); + eval(e3); deleteValue(h3); +#endif } From bb03c45ca03e038c8b74fc1410f48d02ade4c59b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 27 Jun 2003 13:41:42 +0000 Subject: [PATCH 0093/6440] * Added the Boost format library which provides a safe printf replacement. --- boost/format.hpp | 68 +++ boost/format/exceptions.hpp | 96 ++++ boost/format/feed_args.hpp | 248 +++++++++ boost/format/format_class.hpp | 140 +++++ boost/format/format_fwd.hpp | 55 ++ boost/format/format_implementation.hpp | 268 ++++++++++ boost/format/free_funcs.hpp | 72 +++ boost/format/group.hpp | 680 +++++++++++++++++++++++++ boost/format/internals.hpp | 169 ++++++ boost/format/internals_fwd.hpp | 65 +++ boost/format/macros_default.hpp | 48 ++ boost/format/parsing.hpp | 457 +++++++++++++++++ 12 files changed, 2366 insertions(+) create mode 100644 boost/format.hpp create mode 100644 boost/format/exceptions.hpp create mode 100644 boost/format/feed_args.hpp create mode 100644 boost/format/format_class.hpp create mode 100644 boost/format/format_fwd.hpp create mode 100644 boost/format/format_implementation.hpp create mode 100644 boost/format/free_funcs.hpp create mode 100644 boost/format/group.hpp create mode 100644 boost/format/internals.hpp create mode 100644 boost/format/internals_fwd.hpp create mode 100644 boost/format/macros_default.hpp create mode 100644 boost/format/parsing.hpp diff --git a/boost/format.hpp b/boost/format.hpp new file mode 100644 index 000000000..f5bdada05 --- /dev/null +++ b/boost/format.hpp @@ -0,0 +1,68 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream + +// ---------------------------------------------------------------------------- +// format.hpp : primary header +// ---------------------------------------------------------------------------- + +#ifndef BOOST_FORMAT_HPP +#define BOOST_FORMAT_HPP + +#include +#include +#include +#include + +#include + +#include + +namespace boost +{ + template void throw_exception(E const & e) + { + throw e; + } +} + +#define BOOST_ASSERT(expr) assert(expr) + + +// **** Forward declarations ---------------------------------- +#include // basic_format, and other frontends +#include // misc forward declarations for internal use + + +// **** Auxiliary structs (stream_format_state , and format_item ) +#include + +// **** Format class interface -------------------------------- +#include + +// **** Exceptions ----------------------------------------------- +#include + +// **** Implementation ------------------------------------------- +#include // member functions + +#include // class for grouping arguments + +#include // argument-feeding functions +#include // format-string parsing (member-)functions + +// **** Implementation of the free functions ---------------------- +#include + + +#endif // BOOST_FORMAT_HPP diff --git a/boost/format/exceptions.hpp b/boost/format/exceptions.hpp new file mode 100644 index 000000000..79e452449 --- /dev/null +++ b/boost/format/exceptions.hpp @@ -0,0 +1,96 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing) + +// ------------------------------------------------------------------------------ +// exceptions.hpp +// ------------------------------------------------------------------------------ + + +#ifndef BOOST_FORMAT_EXCEPTIONS_HPP +#define BOOST_FORMAT_EXCEPTIONS_HPP + + +#include + + +namespace boost { + +namespace io { + +// **** exceptions ----------------------------------------------- + +class format_error : public std::exception +{ +public: + format_error() {} + virtual const char *what() const throw() + { + return "boost::format_error: " + "format generic failure"; + } +}; + +class bad_format_string : public format_error +{ +public: + bad_format_string() {} + virtual const char *what() const throw() + { + return "boost::bad_format_string: " + "format-string is ill-formed"; + } +}; + +class too_few_args : public format_error +{ +public: + too_few_args() {} + virtual const char *what() const throw() + { + return "boost::too_few_args: " + "format-string refered to more arguments than were passed"; + } +}; + +class too_many_args : public format_error +{ +public: + too_many_args() {} + virtual const char *what() const throw() + { + return "boost::too_many_args: " + "format-string refered to less arguments than were passed"; + } +}; + + +class out_of_range : public format_error +{ +public: + out_of_range() {} + virtual const char *what() const throw() + { + return "boost::out_of_range: " + "tried to refer to an argument (or item) number which is out of range, " + "according to the format string."; + } +}; + + +} // namespace io + +} // namespace boost + + +#endif // BOOST_FORMAT_EXCEPTIONS_HPP diff --git a/boost/format/feed_args.hpp b/boost/format/feed_args.hpp new file mode 100644 index 000000000..2e678ca3b --- /dev/null +++ b/boost/format/feed_args.hpp @@ -0,0 +1,248 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream + +// ---------------------------------------------------------------------------- +// feed_args.hpp : functions for processing each argument +// (feed, feed_manip, and distribute) +// ---------------------------------------------------------------------------- + + +#ifndef BOOST_FORMAT_FEED_ARGS_HPP +#define BOOST_FORMAT_FEED_ARGS_HPP + +#include "boost/format/format_class.hpp" +#include "boost/format/group.hpp" + +//#include "boost/throw_exception.hpp" + +namespace boost { +namespace io { +namespace detail { +namespace { + + template inline + void empty_buf(BOOST_IO_STD basic_ostringstream & os) { + static const std::basic_string emptyStr; + os.str(emptyStr); + } + + template + void do_pad( std::basic_string & s, + std::streamsize w, + const Ch c, + std::ios_base::fmtflags f, + bool center) + // applies centered / left / right padding to the string s. + // Effects : string s is padded. + { + std::streamsize n=w-s.size(); + if(n<=0) { + return; + } + if(center) + { + s.reserve(w); // allocate once for the 2 inserts + const std::streamsize n1 = n /2, n0 = n - n1; + s.insert(s.begin(), n0, c); + s.append(n1, c); + } + else + { + if(f & std::ios_base::left) { + s.append(n, c); + } + else { + s.insert(s.begin(), n, c); + } + } + } // -do_pad(..) + + + template< class Ch, class Tr, class T> inline + void put_head(BOOST_IO_STD basic_ostream& , const T& ) { + } + + template< class Ch, class Tr, class T> inline + void put_head( BOOST_IO_STD basic_ostream& os, const group1& x ) { + os << group_head(x.a1_); // send the first N-1 items, not the last + } + + template< class Ch, class Tr, class T> inline + void put_last( BOOST_IO_STD basic_ostream& os, const T& x ) { + os << x ; + } + + template< class Ch, class Tr, class T> inline + void put_last( BOOST_IO_STD basic_ostream& os, const group1& x ) { + os << group_last(x.a1_); // this selects the last element + } + +#ifndef BOOST_NO_OVERLOAD_FOR_NON_CONST + template< class Ch, class Tr, class T> inline + void put_head( BOOST_IO_STD basic_ostream& , T& ) { + } + + template< class Ch, class Tr, class T> inline + void put_last( BOOST_IO_STD basic_ostream& os, T& x ) { + os << x ; + } +#endif + + + + +template< class Ch, class Tr, class T> +void put( T x, + const format_item& specs, + std::basic_string & res, + BOOST_IO_STD basic_ostringstream& oss_ ) +{ + // does the actual conversion of x, with given params, into a string + // using the *supplied* strinstream. (the stream state is important) + + typedef std::basic_string string_t; + typedef format_item format_item_t; + + stream_format_state prev_state(oss_); + + specs.state_.apply_on(oss_); + + // in case x is a group, apply the manip part of it, + // in order to find width + put_head( oss_, x ); + empty_buf( oss_); + + const std::streamsize w=oss_.width(); + const std::ios_base::fmtflags fl=oss_.flags(); + const bool internal = (fl & std::ios_base::internal) != 0; + const bool two_stepped_padding = internal + && ! ( specs.pad_scheme_ & format_item_t::spacepad ) + && specs.truncate_ < 0 ; + + + if(! two_stepped_padding) + { + if(w>0) // handle simple padding via do_pad, not natively in stream + oss_.width(0); + put_last( oss_, x); + res = oss_.str(); + + if (specs.truncate_ >= 0) + res.erase(specs.truncate_); + + // complex pads : + if(specs.pad_scheme_ & format_item_t::spacepad) + { + if( res.size()==0 || ( res[0]!='+' && res[0]!='-' )) + { + res.insert(res.begin(), 1, ' '); // insert 1 space at pos 0 + } + } + if(w > 0) // need do_pad + { + do_pad(res,w,oss_.fill(), fl, (specs.pad_scheme_ & format_item_t::centered) !=0 ); + } + } + else // 2-stepped padding + { + put_last( oss_, x); // oss_.width() may result in padding. + res = oss_.str(); + + if (specs.truncate_ >= 0) + res.erase(specs.truncate_); + + if( res.size() - w > 0) + { // length w exceeded + // either it was multi-output with first output padding up all width.. + // either it was one big arg and we are fine. + empty_buf( oss_); + oss_.width(0); + put_last(oss_, x ); + string_t tmp = oss_.str(); // minimal-length output + std::streamsize d; + if( (d=w - tmp.size()) <=0 ) + { + // minimal length is already >= w, so no padding (cool!) + res.swap(tmp); + } + else + { // hum.. we need to pad (it was necessarily multi-output) + typedef typename string_t::size_type size_type; + size_type i = 0; + while( i( d ), oss_.fill()); + res.swap( tmp ); + } + } + else + { // okay, only one thing was printed and padded, so res is fine. + } + } + + prev_state.apply_on(oss_); + empty_buf( oss_); + oss_.clear(); +} // end- put(..) + + +} // local namespace + + + + + +template< class Ch, class Tr, class T> +void distribute(basic_format& self, T x) + // call put(x, ..) on every occurence of the current argument : +{ + if(self.cur_arg_ >= self.num_args_) + { + if( self.exceptions() & too_many_args_bit ) + boost::throw_exception(too_many_args()); // too many variables have been supplied ! + else return; + } + for(unsigned long i=0; i < self.items_.size(); ++i) + { + if(self.items_[i].argN_ == self.cur_arg_) + { + put (x, self.items_[i], self.items_[i].res_, self.oss_ ); + } + } +} + +template +basic_format& feed(basic_format& self, T x) +{ + if(self.dumped_) self.clear(); + distribute (self, x); + ++self.cur_arg_; + if(self.bound_.size() != 0) + { + while( self.cur_arg_ < self.num_args_ && self.bound_[self.cur_arg_] ) + ++self.cur_arg_; + } + + // this arg is finished, reset the stream's format state + self.state0_.apply_on(self.oss_); + return self; +} + + +} // namespace detail +} // namespace io +} // namespace boost + + +#endif // BOOST_FORMAT_FEED_ARGS_HPP diff --git a/boost/format/format_class.hpp b/boost/format/format_class.hpp new file mode 100644 index 000000000..9126bfad3 --- /dev/null +++ b/boost/format/format_class.hpp @@ -0,0 +1,140 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing) + +// ------------------------------------------------------------------------------ +// format_class.hpp : class interface +// ------------------------------------------------------------------------------ + + +#ifndef BOOST_FORMAT_CLASS_HPP +#define BOOST_FORMAT_CLASS_HPP + +#include +#include + +#include +#include + +#include + +namespace boost { + +template +class basic_format +{ +public: + typedef Ch CharT; // those 2 are necessary for borland compatibilty, + typedef Tr Traits; // in the body of the operator% template. + + + typedef std::basic_string string_t; + typedef BOOST_IO_STD basic_ostringstream internal_stream_t; +private: + typedef BOOST_IO_STD basic_ostream stream_t; + typedef io::detail::stream_format_state stream_format_state; + typedef io::detail::format_item format_item_t; + +public: + basic_format(const Ch* str); + basic_format(const string_t& s); +#ifndef BOOST_NO_STD_LOCALE + basic_format(const Ch* str, const std::locale & loc); + basic_format(const string_t& s, const std::locale & loc); +#endif // no locale + basic_format(const basic_format& x); + basic_format& operator= (const basic_format& x); + + basic_format& clear(); // empty the string buffers (except bound arguments, see clear_binds() ) + + // pass arguments through those operators : + template basic_format& operator%(const T& x) + { + return io::detail::feed(*this,x); + } + +#ifndef BOOST_NO_OVERLOAD_FOR_NON_CONST + template basic_format& operator%(T& x) + { + return io::detail::feed(*this,x); + } +#endif + + + // system for binding arguments : + template + basic_format& bind_arg(int argN, const T& val) + { + return io::detail::bind_arg_body(*this, argN, val); + } + basic_format& clear_bind(int argN); + basic_format& clear_binds(); + + // modify the params of a directive, by applying a manipulator : + template + basic_format& modify_item(int itemN, const T& manipulator) + { + return io::detail::modify_item_body(*this, itemN, manipulator) ; + } + + // Choosing which errors will throw exceptions : + unsigned char exceptions() const; + unsigned char exceptions(unsigned char newexcept); + + // final output + string_t str() const; + friend BOOST_IO_STD basic_ostream& + operator<< ( BOOST_IO_STD basic_ostream& , const basic_format& ); + + + template friend basic_format& + io::detail::feed(basic_format&, T); + + template friend + void io::detail::distribute(basic_format&, T); + + template friend + basic_format& io::detail::modify_item_body(basic_format&, int, const T&); + + template friend + basic_format& io::detail::bind_arg_body(basic_format&, int, const T&); + +// make the members private only if the friend templates are supported +private: + + // flag bits, used for style_ + enum style_values { ordered = 1, // set only if all directives are positional directives + special_needs = 4 }; + + // parse the format string : + void parse(const string_t&); + + int style_; // style of format-string : positional or not, etc + int cur_arg_; // keep track of wich argument will come + int num_args_; // number of expected arguments + mutable bool dumped_; // true only after call to str() or << + std::vector items_; // vector of directives (aka items) + string_t prefix_; // piece of string to insert before first item + + std::vector bound_; // stores which arguments were bound + // size = num_args OR zero + internal_stream_t oss_; // the internal stream. + stream_format_state state0_; // reference state for oss_ + unsigned char exceptions_; +}; // class basic_format + + +} // namespace boost + + +#endif // BOOST_FORMAT_CLASS_HPP diff --git a/boost/format/format_fwd.hpp b/boost/format/format_fwd.hpp new file mode 100644 index 000000000..bad2f7238 --- /dev/null +++ b/boost/format/format_fwd.hpp @@ -0,0 +1,55 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing) + +// ------------------------------------------------------------------------------ +// format_fwd.hpp : forward declarations, for primary header format.hpp +// ------------------------------------------------------------------------------ + +#ifndef BOOST_FORMAT_FWD_HPP +#define BOOST_FORMAT_FWD_HPP + +#include +#include + +namespace boost { + +template > class basic_format; + +typedef basic_format format; + +#if !defined(BOOST_NO_STD_WSTRING) && !defined(BOOST_NO_STD_WSTREAMBUF) +typedef basic_format wformat; +#endif + +namespace io { +enum format_error_bits { bad_format_string_bit = 1, + too_few_args_bit = 2, too_many_args_bit = 4, + out_of_range_bit = 8, + all_error_bits = 255, no_error_bits=0 }; + +// Convertion: format to string +template +std::basic_string str(const basic_format& ) ; + +} // namespace io + + +template< class Ch, class Tr> +BOOST_IO_STD basic_ostream& +operator<<( BOOST_IO_STD basic_ostream&, const basic_format&); + + +} // namespace boost + +#endif // BOOST_FORMAT_FWD_HPP diff --git a/boost/format/format_implementation.hpp b/boost/format/format_implementation.hpp new file mode 100644 index 000000000..58372bb13 --- /dev/null +++ b/boost/format/format_implementation.hpp @@ -0,0 +1,268 @@ +// -*- C++ -*- +// Boost general library format --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream + +// ---------------------------------------------------------------------------- +// format_implementation.hpp Implementation of the basic_format class +// ---------------------------------------------------------------------------- + + +#ifndef BOOST_FORMAT_IMPLEMENTATION_HPP +#define BOOST_FORMAT_IMPLEMENTATION_HPP + +//#include +//#include +#include + +namespace boost { + +// -------- format:: ------------------------------------------- +template< class Ch, class Tr> +basic_format ::basic_format(const Ch* str) + : style_(0), cur_arg_(0), num_args_(0), dumped_(false), + items_(), oss_(), exceptions_(io::all_error_bits) +{ + state0_.set_by_stream(oss_); + string_t emptyStr; + if( !str) str = emptyStr.c_str(); + parse( str ); +} + +#ifndef BOOST_NO_STD_LOCALE +template< class Ch, class Tr> +basic_format ::basic_format(const Ch* str, const std::locale & loc) + : style_(0), cur_arg_(0), num_args_(0), dumped_(false), + items_(), oss_(), exceptions_(io::all_error_bits) +{ + oss_.imbue( loc ); + state0_.set_by_stream(oss_); + string_t emptyStr; + if( !str) str = emptyStr.c_str(); + parse( str ); +} + +template< class Ch, class Tr> +basic_format ::basic_format(const string_t& s, const std::locale & loc) + : style_(0), cur_arg_(0), num_args_(0), dumped_(false), + items_(), oss_(), exceptions_(io::all_error_bits) +{ + oss_.imbue( loc ); + state0_.set_by_stream(oss_); + parse(s); +} +#endif //BOOST_NO_STD_LOCALE + +template< class Ch, class Tr> +basic_format ::basic_format(const string_t& s) + : style_(0), cur_arg_(0), num_args_(0), dumped_(false), + items_(), oss_(), exceptions_(io::all_error_bits) +{ + state0_.set_by_stream(oss_); + parse(s); +} + +template< class Ch, class Tr> +basic_format :: basic_format(const basic_format& x) + : style_(x.style_), cur_arg_(x.cur_arg_), num_args_(x.num_args_), dumped_(false), + items_(x.items_), prefix_(x.prefix_), bound_(x.bound_), + oss_(), // <- we obviously can't copy x.oss_ + state0_(x.state0_), exceptions_(x.exceptions_) +{ + state0_.apply_on(oss_); +} + +template< class Ch, class Tr> +basic_format& basic_format ::operator= (const basic_format& x) +{ + if(this == &x) + return *this; + state0_ = x.state0_; + state0_.apply_on(oss_); + + // plus all the other (trivial) assignments : + exceptions_ = x.exceptions_; + items_ = x.items_; + prefix_ = x.prefix_; + bound_=x.bound_; + style_=x.style_; + cur_arg_=x.cur_arg_; + num_args_=x.num_args_; + dumped_=x.dumped_; + return *this; +} + + +template< class Ch, class Tr> +unsigned char basic_format ::exceptions() const +{ + return exceptions_; +} + +template< class Ch, class Tr> +unsigned char basic_format ::exceptions(unsigned char newexcept) +{ + unsigned char swp = exceptions_; + exceptions_ = newexcept; + return swp; +} + + +template< class Ch, class Tr> +basic_format& basic_format ::clear() + // empty the string buffers (except bound arguments, see clear_binds() ) + // and make the format object ready for formatting a new set of arguments +{ + BOOST_ASSERT( bound_.size()==0 || num_args_ == static_cast(bound_.size()) ); + + for(unsigned long i=0; i +basic_format& basic_format ::clear_binds() + // cancel all bindings, and clear() +{ + bound_.resize(0); + clear(); + return *this; +} + +template< class Ch, class Tr> +basic_format& basic_format ::clear_bind(int argN) + // cancel the binding of ONE argument, and clear() +{ + if(argN<1 || argN > num_args_ || bound_.size()==0 || !bound_[argN-1] ) + { + if( exceptions() & io::out_of_range_bit ) + boost::throw_exception(io::out_of_range()); // arg not in range. + else return *this; + } + bound_[argN-1]=false; + clear(); + return *this; +} + + + +template< class Ch, class Tr> +std::basic_string basic_format ::str() const +{ + dumped_=true; + if(items_.size()==0) + return prefix_; + if( cur_arg_ < num_args_) + if( exceptions() & io::too_few_args_bit ) + boost::throw_exception(io::too_few_args()); // not enough variables have been supplied ! + + unsigned long sz = prefix_.size(); + unsigned long i; + for(i=0; i < items_.size(); ++i) + sz += items_[i].res_.size() + items_[i].appendix_.size(); + string_t res; + res.reserve(sz); + + res += prefix_; + for(i=0; i < items_.size(); ++i) + { + const format_item_t& item = items_[i]; + res += item.res_; + if( item.argN_ == format_item_t::argN_tabulation) + { + BOOST_ASSERT( item.pad_scheme_ & format_item_t::tabulation); + std::streamsize n = item.state_.width_ - res.size(); + if( n > 0 ) + res.append( n, item.state_.fill_ ); + } + res += item.appendix_; + } + return res; +} + +namespace io { +namespace detail { + +template +basic_format& bind_arg_body( basic_format& self, + int argN, + const T& val) + // bind one argument to a fixed value + // this is persistent over clear() calls, thus also over str() and << +{ + if(self.dumped_) self.clear(); // needed, because we will modify cur_arg_.. + if(argN<1 || argN > self.num_args_) + { + if( self.exceptions() & io::out_of_range_bit ) + boost::throw_exception(io::out_of_range()); // arg not in range. + else return self; + } + if(self.bound_.size()==0) + self.bound_.assign(self.num_args_,false); + else + BOOST_ASSERT( self.num_args_ == static_cast(self.bound_.size()) ); + int o_cur_arg = self.cur_arg_; + self.cur_arg_ = argN-1; // arrays begin at 0 + + self.bound_[self.cur_arg_]=false; // if already set, we unset and re-sets.. + self.operator%(val); // put val at the right place, because cur_arg is set + + + // Now re-position cur_arg before leaving : + self.cur_arg_ = o_cur_arg; + self.bound_[argN-1]=true; + if(self.cur_arg_ == argN-1 ) + // hum, now this arg is bound, so move to next free arg + { + while(self.cur_arg_ < self.num_args_ && self.bound_[self.cur_arg_]) ++self.cur_arg_; + } + // In any case, we either have all args, or are on a non-binded arg : + BOOST_ASSERT( self.cur_arg_ >= self.num_args_ || ! self.bound_[self.cur_arg_]); + return self; +} + +template +basic_format& modify_item_body( basic_format& self, + int itemN, + const T& manipulator) + // applies a manipulator to the format_item describing a given directive. + // this is a permanent change, clear or clear_binds won't cancel that. +{ + if(itemN<1 || itemN >= static_cast(self.items_.size() )) + { + if( self.exceptions() & io::out_of_range_bit ) + boost::throw_exception(io::out_of_range()); // item not in range. + else return self; + } + self.items_[itemN-1].ref_state_.apply_manip( manipulator ); + self.items_[itemN-1].state_ = self.items_[itemN-1].ref_state_; + return self; +} + +} // namespace detail + +} // namespace io + +} // namespace boost + + + +#endif // BOOST_FORMAT_IMPLEMENTATION_HPP diff --git a/boost/format/free_funcs.hpp b/boost/format/free_funcs.hpp new file mode 100644 index 000000000..d552e8baa --- /dev/null +++ b/boost/format/free_funcs.hpp @@ -0,0 +1,72 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing) + +// ------------------------------------------------------------------------------ +// free_funcs.hpp : implementation of the free functions declared in namespace format +// ------------------------------------------------------------------------------ + +#ifndef BOOST_FORMAT_FUNCS_HPP +#define BOOST_FORMAT_FUNCS_HPP + +#include "boost/format/format_class.hpp" +//#include "boost/throw_exception.hpp" + +namespace boost { + +namespace io { + template inline + std::basic_string str(const basic_format& f) + // adds up all pieces of strings and converted items, and return the formatted string + { + return f.str(); + } +} // - namespace io + +template< class Ch, class Tr> +BOOST_IO_STD basic_ostream& +operator<<( BOOST_IO_STD basic_ostream& os, + const boost::basic_format& f) + // effect: "return os << str(f);" but we can try to do it faster +{ + typedef boost::basic_format format_t; + if(f.items_.size()==0) + os << f.prefix_; + else { + if(f.cur_arg_ < f.num_args_) + if( f.exceptions() & io::too_few_args_bit ) + boost::throw_exception(io::too_few_args()); // not enough variables have been supplied ! + if(f.style_ & format_t::special_needs) + os << f.str(); + else { + // else we dont have to count chars output, so we dump directly to os : + os << f.prefix_; + for(unsigned long i=0; i +inline +BOOST_IO_STD basic_ostream& +operator << ( BOOST_IO_STD basic_ostream& os, + const group0& ) +{ + return os; +} + +template +struct group1 +{ + T1 a1_; + group1(T1 a1) + : a1_(a1) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group1& x) +{ + os << x.a1_; + return os; +} + + + + +template +struct group2 +{ + T1 a1_; + T2 a2_; + group2(T1 a1,T2 a2) + : a1_(a1),a2_(a2) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group2& x) +{ + os << x.a1_<< x.a2_; + return os; +} + +template +struct group3 +{ + T1 a1_; + T2 a2_; + T3 a3_; + group3(T1 a1,T2 a2,T3 a3) + : a1_(a1),a2_(a2),a3_(a3) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group3& x) +{ + os << x.a1_<< x.a2_<< x.a3_; + return os; +} + +template +struct group4 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + group4(T1 a1,T2 a2,T3 a3,T4 a4) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group4& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_; + return os; +} + +template +struct group5 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + group5(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group5& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_; + return os; +} + +template +struct group6 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + group6(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group6& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_; + return os; +} + +template +struct group7 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + T7 a7_; + group7(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6),a7_(a7) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group7& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_; + return os; +} + +template +struct group8 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + T7 a7_; + T8 a8_; + group8(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6),a7_(a7),a8_(a8) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group8& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_<< x.a8_; + return os; +} + +template +struct group9 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + T7 a7_; + T8 a8_; + T9 a9_; + group9(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8,T9 a9) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6),a7_(a7),a8_(a8),a9_(a9) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group9& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_<< x.a8_<< x.a9_; + return os; +} + +template +struct group10 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + T7 a7_; + T8 a8_; + T9 a9_; + T10 a10_; + group10(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8,T9 a9,T10 a10) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6),a7_(a7),a8_(a8),a9_(a9),a10_(a10) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group10& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_<< x.a8_<< x.a9_<< x.a10_; + return os; +} + + + + +template +inline +group1 +group_head( group2 const& x) +{ + return group1 (x.a1_); +} + +template +inline +group1 +group_last( group2 const& x) +{ + return group1 (x.a2_); +} + + + +template +inline +group2 +group_head( group3 const& x) +{ + return group2 (x.a1_,x.a2_); +} + +template +inline +group1 +group_last( group3 const& x) +{ + return group1 (x.a3_); +} + + + +template +inline +group3 +group_head( group4 const& x) +{ + return group3 (x.a1_,x.a2_,x.a3_); +} + +template +inline +group1 +group_last( group4 const& x) +{ + return group1 (x.a4_); +} + + + +template +inline +group4 +group_head( group5 const& x) +{ + return group4 (x.a1_,x.a2_,x.a3_,x.a4_); +} + +template +inline +group1 +group_last( group5 const& x) +{ + return group1 (x.a5_); +} + + + +template +inline +group5 +group_head( group6 const& x) +{ + return group5 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_); +} + +template +inline +group1 +group_last( group6 const& x) +{ + return group1 (x.a6_); +} + + + +template +inline +group6 +group_head( group7 const& x) +{ + return group6 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_,x.a6_); +} + +template +inline +group1 +group_last( group7 const& x) +{ + return group1 (x.a7_); +} + + + +template +inline +group7 +group_head( group8 const& x) +{ + return group7 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_,x.a6_,x.a7_); +} + +template +inline +group1 +group_last( group8 const& x) +{ + return group1 (x.a8_); +} + + + +template +inline +group8 +group_head( group9 const& x) +{ + return group8 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_,x.a6_,x.a7_,x.a8_); +} + +template +inline +group1 +group_last( group9 const& x) +{ + return group1 (x.a9_); +} + + + +template +inline +group9 +group_head( group10 const& x) +{ + return group9 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_,x.a6_,x.a7_,x.a8_,x.a9_); +} + +template +inline +group1 +group_last( group10 const& x) +{ + return group1 (x.a10_); +} + + + + + +} // namespace detail + + + +// helper functions + + +inline detail::group1< detail::group0 > +group() { return detail::group1< detail::group0 > ( detail::group0() ); } + +template +inline +detail::group1< detail::group2 > + group(T1 a1, Var const& var) +{ + return detail::group1< detail::group2 > + ( detail::group2 + (a1, var) + ); +} + +template +inline +detail::group1< detail::group3 > + group(T1 a1,T2 a2, Var const& var) +{ + return detail::group1< detail::group3 > + ( detail::group3 + (a1,a2, var) + ); +} + +template +inline +detail::group1< detail::group4 > + group(T1 a1,T2 a2,T3 a3, Var const& var) +{ + return detail::group1< detail::group4 > + ( detail::group4 + (a1,a2,a3, var) + ); +} + +template +inline +detail::group1< detail::group5 > + group(T1 a1,T2 a2,T3 a3,T4 a4, Var const& var) +{ + return detail::group1< detail::group5 > + ( detail::group5 + (a1,a2,a3,a4, var) + ); +} + +template +inline +detail::group1< detail::group6 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5, Var const& var) +{ + return detail::group1< detail::group6 > + ( detail::group6 + (a1,a2,a3,a4,a5, var) + ); +} + +template +inline +detail::group1< detail::group7 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6, Var const& var) +{ + return detail::group1< detail::group7 > + ( detail::group7 + (a1,a2,a3,a4,a5,a6, var) + ); +} + +template +inline +detail::group1< detail::group8 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7, Var const& var) +{ + return detail::group1< detail::group8 > + ( detail::group8 + (a1,a2,a3,a4,a5,a6,a7, var) + ); +} + +template +inline +detail::group1< detail::group9 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8, Var const& var) +{ + return detail::group1< detail::group9 > + ( detail::group9 + (a1,a2,a3,a4,a5,a6,a7,a8, var) + ); +} + +template +inline +detail::group1< detail::group10 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8,T9 a9, Var const& var) +{ + return detail::group1< detail::group10 > + ( detail::group10 + (a1,a2,a3,a4,a5,a6,a7,a8,a9, var) + ); +} + + +#ifndef BOOST_NO_OVERLOAD_FOR_NON_CONST + +template +inline +detail::group1< detail::group2 > + group(T1 a1, Var& var) +{ + return detail::group1< detail::group2 > + ( detail::group2 + (a1, var) + ); +} + +template +inline +detail::group1< detail::group3 > + group(T1 a1,T2 a2, Var& var) +{ + return detail::group1< detail::group3 > + ( detail::group3 + (a1,a2, var) + ); +} + +template +inline +detail::group1< detail::group4 > + group(T1 a1,T2 a2,T3 a3, Var& var) +{ + return detail::group1< detail::group4 > + ( detail::group4 + (a1,a2,a3, var) + ); +} + +template +inline +detail::group1< detail::group5 > + group(T1 a1,T2 a2,T3 a3,T4 a4, Var& var) +{ + return detail::group1< detail::group5 > + ( detail::group5 + (a1,a2,a3,a4, var) + ); +} + +template +inline +detail::group1< detail::group6 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5, Var& var) +{ + return detail::group1< detail::group6 > + ( detail::group6 + (a1,a2,a3,a4,a5, var) + ); +} + +template +inline +detail::group1< detail::group7 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6, Var& var) +{ + return detail::group1< detail::group7 > + ( detail::group7 + (a1,a2,a3,a4,a5,a6, var) + ); +} + +template +inline +detail::group1< detail::group8 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7, Var& var) +{ + return detail::group1< detail::group8 > + ( detail::group8 + (a1,a2,a3,a4,a5,a6,a7, var) + ); +} + +template +inline +detail::group1< detail::group9 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8, Var& var) +{ + return detail::group1< detail::group9 > + ( detail::group9 + (a1,a2,a3,a4,a5,a6,a7,a8, var) + ); +} + +template +inline +detail::group1< detail::group10 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8,T9 a9, Var& var) +{ + return detail::group1< detail::group10 > + ( detail::group10 + (a1,a2,a3,a4,a5,a6,a7,a8,a9, var) + ); +} + + +#endif //end- #ifndef BOOST_NO_OVERLOAD_FOR_NON_CONST + + +} // namespace io + +} // namespace boost + + +#endif // BOOST_FORMAT_GROUP_HPP diff --git a/boost/format/internals.hpp b/boost/format/internals.hpp new file mode 100644 index 000000000..52448b731 --- /dev/null +++ b/boost/format/internals.hpp @@ -0,0 +1,169 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream + +// ---------------------------------------------------------------------------- +// internals.hpp : internal structs. included by format.hpp +// stream_format_state, and format_item +// ---------------------------------------------------------------------------- + + +#ifndef BOOST_FORMAT_INTERNALS_HPP +#define BOOST_FORMAT_INTERNALS_HPP + + +#include +#include + +namespace boost { +namespace io { +namespace detail { + + +// -------------- +// set of params that define the format state of a stream + +template +struct stream_format_state +{ + typedef BOOST_IO_STD basic_ios basic_ios; + + std::streamsize width_; + std::streamsize precision_; + Ch fill_; + std::ios_base::fmtflags flags_; + + stream_format_state() : width_(-1), precision_(-1), fill_(0), flags_(std::ios_base::dec) {} + stream_format_state(basic_ios& os) {set_by_stream(os); } + + void apply_on(basic_ios & os) const; //- applies format_state to the stream + template void apply_manip(T manipulator) //- modifies state by applying manipulator. + { apply_manip_body( *this, manipulator) ; } + void reset(); //- sets to default state. + void set_by_stream(const basic_ios& os); //- sets to os's state. +}; + + + +// -------------- +// format_item : stores all parameters that can be defined by directives in the format-string + +template +struct format_item +{ + enum pad_values { zeropad = 1, spacepad =2, centered=4, tabulation = 8 }; + + enum arg_values { argN_no_posit = -1, // non-positional directive. argN will be set later. + argN_tabulation = -2, // tabulation directive. (no argument read) + argN_ignored = -3 // ignored directive. (no argument read) + }; + typedef BOOST_IO_STD basic_ios basic_ios; + typedef detail::stream_format_state stream_format_state; + typedef std::basic_string string_t; + typedef BOOST_IO_STD basic_ostringstream internal_stream_t; + + + int argN_; //- argument number (starts at 0, eg : %1 => argN=0) + // negative values are used for items that don't process + // an argument + string_t res_; //- result of the formatting of this item + string_t appendix_; //- piece of string between this item and the next + + stream_format_state ref_state_;// set by parsing the format_string, is only affected by modify_item + stream_format_state state_; // always same as ref_state, _unless_ modified by manipulators 'group(..)' + + // non-stream format-state parameters + signed int truncate_; //- is >=0 for directives like %.5s (take 5 chars from the string) + unsigned int pad_scheme_; //- several possible padding schemes can mix. see pad_values + + format_item() : argN_(argN_no_posit), truncate_(-1), pad_scheme_(0) {} + + void compute_states(); // sets states according to truncate and pad_scheme. +}; + + + +// ----------------------------------------------------------- +// Definitions +// ----------------------------------------------------------- + +// --- stream_format_state:: ------------------------------------------- +template inline +void stream_format_state ::apply_on(basic_ios & os) const + // set the state of this stream according to our params +{ + if(width_ != -1) + os.width(width_); + if(precision_ != -1) + os.precision(precision_); + if(fill_ != 0) + os.fill(fill_); + os.flags(flags_); +} + +template inline +void stream_format_state ::set_by_stream(const basic_ios& os) + // set our params according to the state of this stream +{ + flags_ = os.flags(); + width_ = os.width(); + precision_ = os.precision(); + fill_ = os.fill(); +} + +template inline +void apply_manip_body( stream_format_state& self, + T manipulator) + // modify our params according to the manipulator +{ + BOOST_IO_STD basic_stringstream ss; + self.apply_on( ss ); + ss << manipulator; + self.set_by_stream( ss ); +} + +template inline +void stream_format_state ::reset() + // set our params to standard's default state +{ + width_=-1; precision_=-1; fill_=0; + flags_ = std::ios_base::dec; +} + + +// --- format_items:: ------------------------------------------- +template inline +void format_item ::compute_states() + // reflect pad_scheme_ on state_ and ref_state_ + // because some pad_schemes has complex consequences on several state params. +{ + if(pad_scheme_ & zeropad) + { + if(ref_state_.flags_ & std::ios_base::left) + { + pad_scheme_ = pad_scheme_ & (~zeropad); // ignore zeropad in left alignment + } + else + { + ref_state_.fill_='0'; + ref_state_.flags_ |= std::ios_base::internal; + } + } + state_ = ref_state_; +} + + +} } } // namespaces boost :: io :: detail + + +#endif // BOOST_FORMAT_INTERNALS_HPP diff --git a/boost/format/internals_fwd.hpp b/boost/format/internals_fwd.hpp new file mode 100644 index 000000000..f260e6dec --- /dev/null +++ b/boost/format/internals_fwd.hpp @@ -0,0 +1,65 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing) + +// ------------------------------------------------------------------------------ +// internals_fwd.hpp : forward declarations, for internal headers +// ------------------------------------------------------------------------------ + +#ifndef BOOST_FORMAT_INTERNAL_FWD_HPP +#define BOOST_FORMAT_INTERNAL_FWD_HPP + +#include "boost/format/format_fwd.hpp" + + +namespace boost { +namespace io { + +namespace detail { + template struct stream_format_state; + template struct format_item; +} + + +namespace detail { + + // these functions were intended as methods, + // but MSVC have problems with template member functions : + + // defined in format_implementation.hpp : + template + basic_format& modify_item_body( basic_format& self, + int itemN, const T& manipulator); + + template + basic_format& bind_arg_body( basic_format& self, + int argN, const T& val); + + template + void apply_manip_body( stream_format_state& self, + T manipulator); + + // argument feeding (defined in feed_args.hpp ) : + template + void distribute(basic_format& self, T x); + + template + basic_format& feed(basic_format& self, T x); + +} // namespace detail + +} // namespace io +} // namespace boost + + +#endif // BOOST_FORMAT_INTERNAL_FWD_HPP diff --git a/boost/format/macros_default.hpp b/boost/format/macros_default.hpp new file mode 100644 index 000000000..4fd84a163 --- /dev/null +++ b/boost/format/macros_default.hpp @@ -0,0 +1,48 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing) + +// ------------------------------------------------------------------------------ +// macros_default.hpp : configuration for the format library +// provides default values for the stl workaround macros +// ------------------------------------------------------------------------------ + +#ifndef BOOST_FORMAT_MACROS_DEFAULT_HPP +#define BOOST_FORMAT_MACROS_DEFAULT_HPP + +// *** This should go to "boost/config/suffix.hpp". + +#ifndef BOOST_IO_STD +# define BOOST_IO_STD std:: +#endif + +// **** Workaround for io streams, stlport and msvc. +#ifdef BOOST_IO_NEEDS_USING_DECLARATION +namespace boost { + using std::char_traits; + using std::basic_ostream; + using std::basic_ostringstream; + namespace io { + using std::basic_ostream; + namespace detail { + using std::basic_ios; + using std::basic_ostream; + using std::basic_ostringstream; + } + } +} +#endif + +// ------------------------------------------------------------------------------ + +#endif // BOOST_FORMAT_MACROS_DEFAULT_HPP diff --git a/boost/format/parsing.hpp b/boost/format/parsing.hpp new file mode 100644 index 000000000..a461314f9 --- /dev/null +++ b/boost/format/parsing.hpp @@ -0,0 +1,457 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rudiger Loos's format class +// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing) + +// ------------------------------------------------------------------------------ +// parsing.hpp : implementation of the parsing member functions +// ( parse, parse_printf_directive) +// ------------------------------------------------------------------------------ + + +#ifndef BOOST_FORMAT_PARSING_HPP +#define BOOST_FORMAT_PARSING_HPP + + +#include +//#include +//#include + + +namespace boost { +namespace io { +namespace detail { + + template inline + bool wrap_isdigit(Ch c, Stream &os) + { +#ifndef BOOST_NO_LOCALE_ISIDIGIT + return std::isdigit(c, os.rdbuf()->getloc() ); +# else + using namespace std; + return isdigit(c); +#endif + } //end- wrap_isdigit(..) + + template inline + Res str2int(const std::basic_string& s, + typename std::basic_string::size_type start, + BOOST_IO_STD basic_ios &os, + const Res = Res(0) ) + // Input : char string, with starting index + // a basic_ios& merely to call its widen/narrow member function in the desired locale. + // Effects : reads s[start:] and converts digits into an integral n, of type Res + // Returns : n + { + Res n = 0; + while(start + void skip_asterisk(const std::basic_string & buf, + typename std::basic_string::size_type * pos_p, + BOOST_IO_STD basic_ios &os) + // skip printf's "asterisk-fields" directives in the format-string buf + // Input : char string, with starting index *pos_p + // a basic_ios& merely to call its widen/narrow member function in the desired locale. + // Effects : advance *pos_p by skipping printf's asterisk fields. + // Returns : nothing + { + using namespace std; + BOOST_ASSERT( pos_p != 0); + if(*pos_p >= buf.size() ) return; + if(buf[ *pos_p]==os.widen('*')) { + ++ (*pos_p); + while (*pos_p < buf.size() && wrap_isdigit(buf[*pos_p],os)) ++(*pos_p); + if(buf[*pos_p]==os.widen('$')) ++(*pos_p); + } + } + + + inline void maybe_throw_exception( unsigned char exceptions) + // auxiliary func called by parse_printf_directive + // for centralising error handling + // it either throws if user sets the corresponding flag, or does nothing. + { + if(exceptions & io::bad_format_string_bit) + boost::throw_exception(io::bad_format_string()); + } + + + + template + bool parse_printf_directive(const std::basic_string & buf, + typename std::basic_string::size_type * pos_p, + detail::format_item * fpar, + BOOST_IO_STD basic_ios &os, + unsigned char exceptions) + // Input : a 'printf-directive' in the format-string, starting at buf[ *pos_p ] + // a basic_ios& merely to call its widen/narrow member function in the desired locale. + // a bitset'excpetions' telling whether to throw exceptions on errors. + // Returns : true if parse somehow succeeded (possibly ignoring errors if exceptions disabled) + // false if it failed so bad that the directive should be printed verbatim + // Effects : - *pos_p is incremented so that buf[*pos_p] is the first char after the directive + // - *fpar is set with the parameters read in the directive + { + typedef format_item format_item_t; + BOOST_ASSERT( pos_p != 0); + typename std::basic_string::size_type &i1 = *pos_p, + i0; + fpar->argN_ = format_item_t::argN_no_posit; // if no positional-directive + + bool in_brackets=false; + if(buf[i1]==os.widen('|')) + { + in_brackets=true; + if( ++i1 >= buf.size() ) { + maybe_throw_exception(exceptions); + return false; + } + } + + // the flag '0' would be picked as a digit for argument order, but here it's a flag : + if(buf[i1]==os.widen('0')) + goto parse_flags; + + // handle argument order (%2$d) or possibly width specification: %2d + i0 = i1; // save position before digits + while (i1 < buf.size() && wrap_isdigit(buf[i1], os)) + ++i1; + if (i1!=i0) + { + if( i1 >= buf.size() ) { + maybe_throw_exception(exceptions); + return false; + } + int n=str2int(buf,i0, os, int(0) ); + + // %N% case : this is already the end of the directive + if( buf[i1] == os.widen('%') ) + { + fpar->argN_ = n-1; + ++i1; + if( in_brackets) + maybe_throw_exception(exceptions); + // but don't return. maybe "%" was used in lieu of '$', so we go on. + else return true; + } + + if ( buf[i1]==os.widen('$') ) + { + fpar->argN_ = n-1; + ++i1; + } + else + { + // non-positionnal directive + fpar->ref_state_.width_ = n; + fpar->argN_ = format_item_t::argN_no_posit; + goto parse_precision; + } + } + + parse_flags: + // handle flags + while ( i1 ref_state_.flags_ |= std::ios_base::left; + break; + case '=': + fpar->pad_scheme_ |= format_item_t::centered; + break; + case ' ': + fpar->pad_scheme_ |= format_item_t::spacepad; + break; + case '+': + fpar->ref_state_.flags_ |= std::ios_base::showpos; + break; + case '0': + fpar->pad_scheme_ |= format_item_t::zeropad; + // need to know alignment before really setting flags, + // so just add 'zeropad' flag for now, it will be processed later. + break; + case '#': + fpar->ref_state_.flags_ |= std::ios_base::showpoint | std::ios_base::showbase; + break; + default: + goto parse_width; + } + ++i1; + } // loop on flag. + if( i1>=buf.size()) { + maybe_throw_exception(exceptions); + return true; + } + + parse_width: + // handle width spec + skip_asterisk(buf, &i1, os); // skips 'asterisk fields' : *, or *N$ + i0 = i1; // save position before digits + while (i1ref_state_.width_ = str2int( buf,i0, os, std::streamsize(0) ); } + + parse_precision: + if( i1>=buf.size()) { + maybe_throw_exception(exceptions); + return true; + } + // handle precision spec + if (buf[i1]==os.widen('.')) + { + ++i1; + skip_asterisk(buf, &i1, os); + i0 = i1; // save position before digits + while (i1ref_state_.precision_ = 0; + else + fpar->ref_state_.precision_ = str2int(buf,i0, os, std::streamsize(0) ); + } + + // handle formatting-type flags : + while( i1=buf.size()) { + maybe_throw_exception(exceptions); + return true; + } + + if( in_brackets && buf[i1]==os.widen('|') ) + { + ++i1; + return true; + } + switch (os.narrow(buf[i1], 0) ) + { + case 'X': + fpar->ref_state_.flags_ |= std::ios_base::uppercase; + case 'p': // pointer => set hex. + case 'x': + fpar->ref_state_.flags_ &= ~std::ios_base::basefield; + fpar->ref_state_.flags_ |= std::ios_base::hex; + break; + + case 'o': + fpar->ref_state_.flags_ &= ~std::ios_base::basefield; + fpar->ref_state_.flags_ |= std::ios_base::oct; + break; + + case 'E': + fpar->ref_state_.flags_ |= std::ios_base::uppercase; + case 'e': + fpar->ref_state_.flags_ &= ~std::ios_base::floatfield; + fpar->ref_state_.flags_ |= std::ios_base::scientific; + + fpar->ref_state_.flags_ &= ~std::ios_base::basefield; + fpar->ref_state_.flags_ |= std::ios_base::dec; + break; + + case 'f': + fpar->ref_state_.flags_ &= ~std::ios_base::floatfield; + fpar->ref_state_.flags_ |= std::ios_base::fixed; + case 'u': + case 'd': + case 'i': + fpar->ref_state_.flags_ &= ~std::ios_base::basefield; + fpar->ref_state_.flags_ |= std::ios_base::dec; + break; + + case 'T': + ++i1; + if( i1 >= buf.size()) + maybe_throw_exception(exceptions); + else + fpar->ref_state_.fill_ = buf[i1]; + fpar->pad_scheme_ |= format_item_t::tabulation; + fpar->argN_ = format_item_t::argN_tabulation; + break; + case 't': + fpar->ref_state_.fill_ = os.widen(' '); + fpar->pad_scheme_ |= format_item_t::tabulation; + fpar->argN_ = format_item_t::argN_tabulation; + break; + + case 'G': + fpar->ref_state_.flags_ |= std::ios_base::uppercase; + break; + case 'g': // 'g' conversion is default for floats. + fpar->ref_state_.flags_ &= ~std::ios_base::basefield; + fpar->ref_state_.flags_ |= std::ios_base::dec; + + // CLEAR all floatield flags, so stream will CHOOSE + fpar->ref_state_.flags_ &= ~std::ios_base::floatfield; + break; + + case 'C': + case 'c': + fpar->truncate_ = 1; + break; + case 'S': + case 's': + fpar->truncate_ = fpar->ref_state_.precision_; + fpar->ref_state_.precision_ = -1; + break; + case 'n' : + fpar->argN_ = format_item_t::argN_ignored; + break; + default: + maybe_throw_exception(exceptions); + } + ++i1; + + if( in_brackets ) + { + if( i1 +void basic_format ::parse(const string_t & buf) + // parse the format-string +{ + using namespace std; + const Ch arg_mark = oss_.widen('%'); + bool ordered_args=true; + int max_argN=-1; + typename string_t::size_type i1=0; + int num_items=0; + + // A: find upper_bound on num_items and allocates arrays + i1=0; + while( (i1=buf.find(arg_mark,i1)) != string::npos ) + { + if( i1+1 >= buf.size() ) { + if(exceptions() & io::bad_format_string_bit) + boost::throw_exception(io::bad_format_string()); // must not end in "bla bla %" + else break; // stop there, ignore last '%' + } + if(buf[i1+1] == buf[i1] ) { i1+=2; continue; } // escaped "%%" / "##" + ++i1; + + // in case of %N% directives, dont count it double (wastes allocations..) : + while(i1 < buf.size() && io::detail::wrap_isdigit(buf[i1],oss_)) ++i1; + if( i1 < buf.size() && buf[i1] == arg_mark ) ++ i1; + + ++num_items; + } + items_.assign( num_items, format_item_t() ); + + // B: Now the real parsing of the format string : + num_items=0; + i1 = 0; + typename string_t::size_type i0 = i1; + bool special_things=false; + int cur_it=0; + while( (i1=buf.find(arg_mark,i1)) != string::npos ) + { + string_t & piece = (cur_it==0) ? prefix_ : items_[cur_it-1].appendix_; + + if( buf[i1+1] == buf[i1] ) // escaped mark, '%%' + { + piece += buf.substr(i0, i1-i0) + buf[i1]; + i1+=2; i0=i1; + continue; + } + BOOST_ASSERT( static_cast(cur_it) < items_.size() || cur_it==0); + + if(i1!=i0) piece += buf.substr(i0, i1-i0); + ++i1; + + bool parse_ok; + parse_ok = io::detail::parse_printf_directive(buf, &i1, &items_[cur_it], oss_, exceptions()); + if( ! parse_ok ) continue; // the directive will be printed verbatim + + i0=i1; + items_[cur_it].compute_states(); // process complex options, like zeropad, into stream params. + + int argN=items_[cur_it].argN_; + if(argN == format_item_t::argN_ignored) + continue; + if(argN ==format_item_t::argN_no_posit) + ordered_args=false; + else if(argN == format_item_t::argN_tabulation) special_things=true; + else if(argN > max_argN) max_argN = argN; + ++num_items; + ++cur_it; + } // loop on %'s + BOOST_ASSERT(cur_it == num_items); + + // store the final piece of string + string_t & piece = (cur_it==0) ? prefix_ : items_[cur_it-1].appendix_; + piece += buf.substr(i0); + + if( !ordered_args) + { + if(max_argN >= 0 ) // dont mix positional with non-positionnal directives + { + if(exceptions() & io::bad_format_string_bit) + boost::throw_exception(io::bad_format_string()); + // else do nothing. => positionnal arguments are processed as non-positionnal + } + // set things like it would have been with positional directives : + int non_ordered_items = 0; + for(int i=0; i< num_items; ++i) + if(items_[i].argN_ == format_item_t::argN_no_posit) + { + items_[i].argN_ = non_ordered_items; + ++non_ordered_items; + } + max_argN = non_ordered_items-1; + } + + // C: set some member data : + items_.resize(num_items); + + if(special_things) style_ |= special_needs; + num_args_ = max_argN + 1; + if(ordered_args) style_ |= ordered; + else style_ &= ~ordered; +} + +} // namespace boost + + +#endif // BOOST_FORMAT_PARSING_HPP From 3da9687854e029e9df3b612fd592d2d5a622bb20 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 27 Jun 2003 13:55:12 +0000 Subject: [PATCH 0094/6440] * Realisation of File(...) expressions. --- src/eval.cc | 280 +++++++++---------------------------------------- src/eval.hh | 79 +++----------- src/globals.cc | 2 +- src/globals.hh | 19 ++-- src/hash.cc | 4 +- src/hash.hh | 4 +- src/test.cc | 36 +++++-- src/util.cc | 4 +- src/util.hh | 7 +- src/values.cc | 50 ++++----- src/values.hh | 20 ++-- 11 files changed, 137 insertions(+), 368 deletions(-) diff --git a/src/eval.cc b/src/eval.cc index 831464c18..4f59bcc21 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -30,6 +30,7 @@ bool pathExists(string path) } +#if 0 /* Compute a derived value by running a program. */ static Hash computeDerived(Hash sourceHash, string targetName, string platform, Hash prog, Environment env) @@ -175,6 +176,7 @@ static Hash computeDerived(Hash sourceHash, string targetName, return targetHash; } +#endif /* Throw an exception if the given platform string is not supported by @@ -182,54 +184,32 @@ static Hash computeDerived(Hash sourceHash, string targetName, static void checkPlatform(string platform) { if (platform != thisSystem) - throw Error("a `" + platform + - "' is required, but I am a `" + thisSystem + "'"); + throw Error(format("a `%1%' is required, but I am a `%2%'") + % platform % thisSystem); } -string printExpr(Expr e) +string printTerm(ATerm t) { - char * s = ATwriteToString(e); + char * s = ATwriteToString(t); return s; } /* Throw an exception with an error message containing the given aterm. */ -static Error badTerm(const string & msg, Expr e) +static Error badTerm(const format & f, ATerm t) { - return Error(msg + ", in `" + printExpr(e) + "'"); + return Error(format("%1%, in `%2%'") % f.str() % printTerm(t)); } -Hash hashExpr(Expr e) +Hash hashTerm(ATerm t) { - return hashString(printExpr(e)); + return hashString(printTerm(t)); } -/* Evaluate an expression; the result must be a string. */ -static string evalString(Expr e) -{ - e = whNormalise(e); - char * s; - if (ATmatch(e, "Str()", &s)) return s; - else throw badTerm("string value expected", e); -} - - -#if 0 -/* Evaluate an expression; the result must be a value reference. */ -static Hash evalHash(Expr e) -{ - e = evalValue(e); - char * s; - if (ATmatch(e, "Hash()", &s)) return parseHash(s); - else throw badTerm("value reference expected", e); -} -#endif - - #if 0 /* Evaluate a list of arguments into normal form. */ void evalArgs(ATermList args, ATermList & argsNF, Environment & env) @@ -262,225 +242,63 @@ void evalArgs(ATermList args, ATermList & argsNF, Environment & env) #endif -Expr substExpr(string x, Expr rep, Expr e) -{ - char * s; - Expr e2; - - if (ATmatch(e, "Var()", &s)) - if (x == s) - return rep; - else - return e; - - if (ATmatch(e, "Lam(, )", &s, &e2)) - if (x == s) - return e; - /* !!! unfair substitutions */ - - /* Generically substitute in subterms. */ - - if (ATgetType(e) == AT_APPL) { - AFun fun = ATgetAFun(e); - int arity = ATgetArity(fun); - ATermList args = ATempty; - - for (int i = arity - 1; i >= 0; i--) - args = ATinsert(args, substExpr(x, rep, ATgetArgument(e, i))); - - return (ATerm) ATmakeApplList(fun, args); - } - - if (ATgetType(e) == AT_LIST) { - ATermList in = (ATermList) e; - ATermList out = ATempty; - - while (!ATisEmpty(in)) { - out = ATinsert(out, substExpr(x, rep, ATgetFirst(in))); - in = ATgetNext(in); - } - - return (ATerm) ATreverse(out); - } - - throw badTerm("do not know how to substitute", e); -} - - -#if 0 -Expr evalValue(Expr e) -{ - char * s; - Expr eBuildPlatform, eProg, e2, e3, e4; - ATermList args; - - /* Value references. */ - if (ATmatch(e, "Hash()", &s)) { - parseHash(s); /* i.e., throw exception if not valid */ - return e; - } - - /* External expression. */ - if (ATmatch(e, "Deref()", &e2)) { - string fn = queryValuePath(evalHash(e2)); - ATerm e3 = ATreadFromNamedFile(fn.c_str()); - if (!e3) throw Error("reading aterm from " + fn); - return evalValue(e3); - } - - /* Execution primitive. */ - - if (ATmatch(e, "Exec(, , [])", - &eBuildPlatform, &eProg, &args)) - { - string buildPlatform = evalString(eBuildPlatform); - - checkPlatform(buildPlatform); - - Hash prog = evalHash(eProg); - - Environment env; - ATermList argsNF; - evalArgs(args, argsNF, env); - - Hash sourceHash = hashExpr( - ATmake("Exec(Str(), Hash(), )", - buildPlatform.c_str(), ((string) prog).c_str(), argsNF)); - - /* Do we know a normal form for sourceHash? */ - Hash targetHash; - string targetHashS; - if (queryDB(nixDB, dbNFs, sourceHash, targetHashS)) { - /* Yes. */ - targetHash = parseHash(targetHashS); - debug("already built: " + (string) sourceHash - + " -> " + (string) targetHash); - } else { - /* No, so we compute one. */ - targetHash = computeDerived(sourceHash, - (string) sourceHash + "-nf", buildPlatform, prog, env); - } - - return ATmake("Hash()", ((string) targetHash).c_str()); - } - - /* Barf. */ - throw badTerm("invalid expression", e); -} -#endif - - -Expr whNormalise(Expr e) -{ - char * s; - Expr e2, e3, e4, e5; - - /* Normal forms. */ - if (ATmatch(e, "Str()", &s) || - ATmatch(e, "Bool(True)") || - ATmatch(e, "Bool(False)") || - ATmatch(e, "Lam(, )", &s, &e2) || - ATmatch(e, "File(, , )", &s, &e2, &e3) || - ATmatch(e, "Derive(, , , )", &e2, &e3, &e4, &e5)) - return e; - - /* Application. */ - if (ATmatch(e, "App(, )", &e2, &e3)) { - e2 = whNormalise(e2); - if (!ATmatch(e2, "Lam(, )", &s, &e4)) - throw badTerm("expecting lambda", e2); - return whNormalise(substExpr(s, e3, e4)); - } - - throw badTerm("invalid expression", e); -} - - -Expr dNormalise(Expr e) -{ - e = whNormalise(e); - /* !!! todo */ - return e; -} - - -Expr fNormalise(Expr e) -{ - e = dNormalise(e); - - char * s; - Expr e2, e3; - - if (ATmatch(e, "File(, , [])", &s, &e2, &e3)) { - - ATermList refs = (ATermList) e3, refs2 = ATempty; - while (!ATisEmpty(refs)) { - ATerm ref = ATgetFirst(refs); - refs2 = ATinsert(refs2, fNormalise(ref)); - refs = ATgetNext(refs); - } - refs2 = ATreverse(refs2); - - return ATmake("File(, , )", s, e2, refs2); - - } - - else return e; -} - - -void writeContent(string path, Content content) -{ - char * s; - - if (ATmatch(content, "Regular()", &s)) { - - int fd; /* !!! close on exception */ - fd = open(path.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666); - if (fd == -1) - throw SysError("creating file " + path); - - int len = strlen(s); - if (write(fd, s, len) != len) - throw SysError("writing file " + path); - - close(fd); - } - - else throw badTerm("ill-formed content", content); -} - - struct RStatus { /* !!! the comparator of this hash should match the semantics of the file system */ - map paths; +// map paths; }; -static void realise2(RStatus & status, Expr e) +static void realise(RStatus & status, FState fs) { char * s; Content content; ATermList refs; - if (!ATmatch(e, "File(, , [])", &s, &content, &refs)) - throw badTerm("not f-normalised", e); + if (ATmatch(fs, "File(, , [])", &s, &content, &refs)) { + string path(s); - string path(s); + if (path[0] != '/') throw Error("absolute path expected: " + path); - while (!ATisEmpty(refs)) { - realise2(status, ATgetFirst(refs)); - refs = ATgetNext(refs); + /* Realise referenced paths. */ + while (!ATisEmpty(refs)) { + realise(status, ATgetFirst(refs)); + refs = ATgetNext(refs); + } + + if (!ATmatch(content, "Hash()", &s)) + throw badTerm("hash expected", content); + Hash hash = parseHash(s); + + /* Perhaps the path already exists and has the right hash? */ + if (pathExists(path)) { + if (hash == hashPath(path)) { + debug(format("path %1% already has hash %2%") + % path % (string) hash); + return; + } + + throw Error(format("path %1% exists, but does not have hash %2%") + % path % (string) hash); + } + + /* Do we know a path with that hash? If so, copy it. */ + string path2 = queryFromStore(hash); + copyFile(path2, path); } - - writeContent(path, content); + + else if (ATmatch(fs, "Derive()")) { + + + } + + else throw badTerm("bad file system state expression", fs); } -void realise(Expr e) +void realiseFState(FState fs) { RStatus status; - realise2(status, e); + realise(status, fs); } diff --git a/src/eval.hh b/src/eval.hh index 807f98f85..f90d5ba02 100644 --- a/src/eval.hh +++ b/src/eval.hh @@ -10,14 +10,12 @@ extern "C" { using namespace std; -/* \section{Abstract syntax of Nix expressions} +/* \section{Abstract syntax of Nix file system state expressions} - An expression describes a (partial) state of the file system in a - referentially transparent way. The operational effect of - evaluating an expression is that the state described by the - expression is realised. + A Nix file system state expression, or FState, describes a + (partial) state of the file system. - File : Path * Content * [Expr] -> Expr + File : Path * Content * [FState] -> FState File(path, content, refs) specifies a file object (its full path and contents), along with all file objects referenced by it (that @@ -25,7 +23,7 @@ using namespace std; self-referential. This prevents us from having to deal with cycles. - Derive : String * Path * [Expr] * [Expr] * [Expr] -> Expr + Derive : String * Path * [FState] * [Path] * [(String, String)] -> [FState] Derive(platform, builder, ins, outs, env) specifies the creation of new file objects (in paths declared by `outs') by the execution of @@ -33,14 +31,6 @@ using namespace std; place in a file system state given by `ins'. `env' specifies a mapping of strings to strings. - Str : String -> Expr - - A string constant. - - Tup : Expr * Expr -> Expr - - Tuples of expressions. - [ !!! NOT IMPLEMENTED Regular : String -> Content Directory : [(String, Content)] -> Content @@ -49,7 +39,11 @@ using namespace std; CHash : Hash -> Content File content, given either in situ, or through an external reference - to the file system or url-space decorated with a hash to preserve purity. + to the file system or url-space decorated with a hash to preserve + purity. + + A FState expression is in {\em $f$-normal form} if all Derive nodes + have been reduced to File nodes. DISCUSSION: the idea is that a Regular/Directory is interchangeable with its CHash. This would appear to break referential @@ -60,63 +54,20 @@ using namespace std; CHash, we should also export the file object referenced by that CHash. - - \section{Reduction rules} - - ... - - - \section{Normals forms} - - An expression is in {\em weak head normal form} if it is a lambda, - a string or boolean value, or a File or Derive value. - - An expression is in {\em $d$-normal form} if it matches the - signature FExpr: - - File : String * Content * [DExpr] -> DExpr - Derive : String * Path * [Tup] * [Tup2] -> DExpr - - Tup : Str * DExpr -> Tup - Tup : Str * Str -> Tup - - Tup : Str * Str -> Tup2 - - Str : String -> Str - - These are Nix expressions in which the file system result of Derive - expressions has not yet been computed. This is useful for, e.g., - querying dependencies. - - An expression is in {\em $f$-normal form} if it matches the - signature FExpr: - - File : String * Content * [FExpr] -> FExpr - - These are completely evaluated Nix expressions. - */ -typedef ATerm Expr; +typedef ATerm FState; typedef ATerm Content; -/* Expression normalisation. */ -Expr whNormalise(Expr e); -Expr dNormalise(Expr e); -Expr fNormalise(Expr e); - /* Realise a $f$-normalised expression in the file system. */ -void realise(Expr e); +void realiseFState(FState fs); /* Return a canonical textual representation of an expression. */ -string printExpr(Expr e); +string printTerm(ATerm t); -/* Perform variable substitution. */ -Expr substExpr(string x, Expr rep, Expr e); - -/* Hash an expression. */ -Hash hashExpr(Expr e); +/* Hash an aterm. */ +Hash hashTerm(ATerm t); #endif /* !__EVAL_H */ diff --git a/src/globals.cc b/src/globals.cc index 14fb431d8..640e960b1 100644 --- a/src/globals.cc +++ b/src/globals.cc @@ -6,7 +6,7 @@ string dbRefs = "refs"; string dbNFs = "nfs"; string dbNetSources = "netsources"; -string nixValues = "/UNINIT"; +string nixStore = "/UNINIT"; string nixLogDir = "/UNINIT"; string nixDB = "/UNINIT"; diff --git a/src/globals.hh b/src/globals.hh index b81a78714..3cb231ee2 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -8,15 +8,10 @@ using namespace std; /* Database names. */ -/* dbRefs :: Hash -> FileName +/* dbRefs :: Hash -> Path - Maintains a mapping from hashes to filenames within the NixValues - directory. This mapping is for performance only; it can be - reconstructed unambiguously. The reason is that names in this - directory are not printed hashes but also might carry some - descriptive element (e.g., "aterm-2.0-ae749a..."). Without this - mapping, looking up a value would take O(n) time because we would - need to read the entire directory. */ + Maintains a mapping from hashes to paths. This is what we use to + resolve CHash(hash) content descriptors. */ extern string dbRefs; /* dbNFs :: Hash -> Hash @@ -45,11 +40,11 @@ extern string dbNetSources; /* Path names. */ -/* nixValues is the directory where all Nix values (both files and - directories, and both normal and non-normal forms) live. */ -extern string nixValues; +/* nixStore is the directory where we generally store atomic and + derived files. */ +extern string nixStore; -/* nixLogDir is the directory where we log evaluations. */ +/* nixLogDir is the directory where we log various operations. */ extern string nixLogDir; /* nixDB is the file name of the Berkeley DB database where we diff --git a/src/hash.cc b/src/hash.cc index 765b7ba04..72dcd790c 100644 --- a/src/hash.cc +++ b/src/hash.cc @@ -14,7 +14,7 @@ Hash::Hash() } -bool Hash::operator == (Hash & h2) +bool Hash::operator == (Hash h2) { for (unsigned int i = 0; i < hashSize; i++) if (hash[i] != h2.hash[i]) return false; @@ -22,7 +22,7 @@ bool Hash::operator == (Hash & h2) } -bool Hash::operator != (Hash & h2) +bool Hash::operator != (Hash h2) { return !(*this == h2); } diff --git a/src/hash.hh b/src/hash.hh index cbc195c1f..509a27912 100644 --- a/src/hash.hh +++ b/src/hash.hh @@ -17,10 +17,10 @@ struct Hash Hash(); /* Check whether two hash are equal. */ - bool operator == (Hash & h2); + bool operator == (Hash h2); /* Check whether two hash are not equal. */ - bool operator != (Hash & h2); + bool operator != (Hash h2); /* Convert a hash code into a hexadecimal representation. */ operator string() const; diff --git a/src/test.cc b/src/test.cc index d912eaa6a..aafae8ee3 100644 --- a/src/test.cc +++ b/src/test.cc @@ -11,20 +11,16 @@ #include "globals.hh" -typedef Expr (* Normaliser) (Expr); - - -void eval(Normaliser n, Expr e) +void realise(FState fs) { - e = n(e); - cout << (string) hashExpr(e) << ": " << printExpr(e) << endl; + realiseFState(fs); } -void evalFail(Normaliser n, Expr e) +void realiseFail(FState fs) { try { - e = n(e); + realiseFState(fs); abort(); } catch (Error e) { cout << "error (expected): " << e.what() << endl; @@ -96,7 +92,7 @@ void runTests() string testDir = absPath("scratch"); cout << testDir << endl; - nixValues = testDir; + nixStore = testDir; nixLogDir = testDir; nixDB = testDir + "/db"; @@ -104,6 +100,7 @@ void runTests() /* Expression evaluation. */ +#if 0 eval(whNormalise, ATmake("Str(\"Hello World\")")); eval(whNormalise, @@ -138,10 +135,27 @@ void runTests() eval(fNormalise, e2); realise(e2); +#endif + + Hash builder1h; + string builder1fn; + addToStore("./test-builder-1.sh", builder1fn, builder1h); + + FState fs1 = ATmake( + "File(, Hash(), [])", + builder1fn.c_str(), + ((string) builder1h).c_str()); + realiseFState(fs1); + realiseFState(fs1); + + FState fs2 = ATmake( + "File(, Hash(), [])", + (builder1fn + "_bla").c_str(), + ((string) builder1h).c_str()); + realiseFState(fs2); + realiseFState(fs2); #if 0 - Hash builder1 = addValue("./test-builder-1.sh"); - Expr e1 = ATmake("Exec(Str(), Hash(), [])", thisSystem.c_str(), ((string) builder1).c_str()); diff --git a/src/util.cc b/src/util.cc index 4dada48ba..c6a0c1199 100644 --- a/src/util.cc +++ b/src/util.cc @@ -78,7 +78,7 @@ void deletePath(string path) } -void debug(string s) +void debug(const format & f) { - cerr << "debug: " << s << endl; + cerr << format("debug: %1%\n") % f.str(); } diff --git a/src/util.hh b/src/util.hh index 7d5f00a2e..3efac928b 100644 --- a/src/util.hh +++ b/src/util.hh @@ -7,7 +7,10 @@ #include +#include + using namespace std; +using namespace boost; class Error : public exception @@ -16,7 +19,7 @@ protected: string err; public: Error() { } - Error(string _err) { err = _err; } + Error(format f) { err = f.str(); } ~Error() throw () { } const char * what() const throw () { return err.c_str(); } }; @@ -59,7 +62,7 @@ string baseNameOf(string path); void deletePath(string path); -void debug(string s); +void debug(const format & f); #endif /* !__UTIL_H */ diff --git a/src/values.cc b/src/values.cc index c8a3b58cb..e23624ce5 100644 --- a/src/values.cc +++ b/src/values.cc @@ -34,7 +34,7 @@ struct CopySource : RestoreSource }; -static void copyFile(string src, string dst) +void copyFile(string src, string dst) { /* Unfortunately C++ doesn't support coprocedures, so we have no nice way to chain CopySink and CopySource together. Instead we @@ -83,33 +83,25 @@ static void copyFile(string src, string dst) } -static string absValuePath(string s) +void addToStore(string srcPath, string & dstPath, Hash & hash) { - return nixValues + "/" + s; -} + srcPath = absPath(srcPath); + hash = hashPath(srcPath); -Hash addValue(string path) -{ - path = absPath(path); - - Hash hash = hashPath(path); - - string name; - if (queryDB(nixDB, dbRefs, hash, name)) { + string path; + if (queryDB(nixDB, dbRefs, hash, path)) { debug((string) hash + " already known"); - return hash; + dstPath = path; + return; } - string baseName = baseNameOf(path); - - string targetName = (string) hash + "-" + baseName; + string baseName = baseNameOf(srcPath); + dstPath = nixStore + "/" + (string) hash + "-" + baseName; - copyFile(path, absValuePath(targetName)); + copyFile(srcPath, dstPath); - setDB(nixDB, dbRefs, hash, targetName); - - return hash; + setDB(nixDB, dbRefs, hash, dstPath); } @@ -135,28 +127,28 @@ string fetchURL(string url) #endif -void deleteValue(Hash hash) +void deleteFromStore(Hash hash) { - string name; - if (queryDB(nixDB, dbRefs, hash, name)) { - string fn = absValuePath(name); + string fn; + if (queryDB(nixDB, dbRefs, hash, fn)) { + string prefix = nixStore + "/"; + if (string(fn, prefix.size()) != prefix) + throw Error("path " + fn + " is not in the store"); deletePath(fn); delDB(nixDB, dbRefs, hash); } } -/* !!! bad name, "query" implies no side effect => getValuePath() */ -string queryValuePath(Hash hash) +string queryFromStore(Hash hash) { bool checkedNet = false; while (1) { - string name, url; + string fn, url; - if (queryDB(nixDB, dbRefs, hash, name)) { - string fn = absValuePath(name); + if (queryDB(nixDB, dbRefs, hash, fn)) { /* Verify that the file hasn't changed. !!! race !!! slow */ if (hashPath(fn) != hash) diff --git a/src/values.hh b/src/values.hh index d66ae770f..1bb00a9dd 100644 --- a/src/values.hh +++ b/src/values.hh @@ -8,21 +8,17 @@ using namespace std; -/* Copy a value to the nixValues directory and register it in dbRefs. +void copyFile(string src, string dst); + +/* Copy a file to the nixStore directory and register it in dbRefs. Return the hash code of the value. */ -Hash addValue(string pathName); +void addToStore(string srcPath, string & dstPath, Hash & hash); +/* Delete a value from the nixStore directory. */ +void deleteFromStore(Hash hash); -/* Delete a value from the nixValues directory. */ -void deleteValue(Hash hash); - - -/* Obtain the path of a value with the given hash. If a file with - that hash is known to exist in the local file system (as indicated - by the dbRefs database), we use that. Otherwise, we attempt to - fetch it from the network (using dbNetSources). We verify that the - file has the right hash. */ -string queryValuePath(Hash hash); +/* !!! */ +string queryFromStore(Hash hash); #endif /* !__VALUES_H */ From 40b5936691fe2448dea0080e2319cc340bc7c65c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 27 Jun 2003 14:56:12 +0000 Subject: [PATCH 0095/6440] * Realisation of Derive(...) expressions. --- src/eval.cc | 295 ++++++++++++++++++++++++++------------------------ src/eval.hh | 4 +- src/test.cc | 22 +++- src/util.cc | 11 +- src/util.hh | 9 +- src/values.cc | 15 +-- src/values.hh | 2 +- 7 files changed, 193 insertions(+), 165 deletions(-) diff --git a/src/eval.cc b/src/eval.cc index 4f59bcc21..a1b8db6e0 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -18,7 +18,7 @@ typedef map Environment; /* Return true iff the given path exists. */ -bool pathExists(string path) +bool pathExists(const string & path) { int res; struct stat st; @@ -30,158 +30,107 @@ bool pathExists(string path) } -#if 0 -/* Compute a derived value by running a program. */ -static Hash computeDerived(Hash sourceHash, string targetName, - string platform, Hash prog, Environment env) +/* Run a program. */ +static void runProgram(const string & program, Environment env) { - string targetPath = nixValues + "/" + - (string) sourceHash + "-nf"; - - /* Check whether the target already exists. */ - if (pathExists(targetPath)) - throw Error("derived value in " + targetPath + " already exists"); - - /* Find the program corresponding to the hash `prog'. */ - string progPath = queryValuePath(prog); - - /* Finalize the environment. */ - env["out"] = targetPath; - /* Create a log file. */ - string logFileName = - nixLogDir + "/" + baseNameOf(targetPath) + ".log"; + string logFileName = nixLogDir + "/run.log"; /* !!! auto-pclose on exit */ FILE * logFile = popen(("tee " + logFileName + " >&2").c_str(), "w"); /* !!! escaping */ if (!logFile) - throw SysError("unable to create log file " + logFileName); + throw SysError(format("unable to create log file %1%") % logFileName); - try { - - /* Fork a child to build the package. */ - pid_t pid; - switch (pid = fork()) { + /* Fork a child to build the package. */ + pid_t pid; + switch (pid = fork()) { - case -1: - throw SysError("unable to fork"); + case -1: + throw SysError("unable to fork"); - case 0: + case 0: - try { /* child */ + try { /* child */ #if 0 - /* Try to use a prebuilt. */ - string prebuiltHashS, prebuiltFile; - if (queryDB(nixDB, dbPrebuilts, hash, prebuiltHashS)) { + /* Try to use a prebuilt. */ + string prebuiltHashS, prebuiltFile; + if (queryDB(nixDB, dbPrebuilts, hash, prebuiltHashS)) { - try { - prebuiltFile = getFile(parseHash(prebuiltHashS)); - } catch (Error e) { - cerr << "cannot obtain prebuilt (ignoring): " << e.what() << endl; - goto build; - } - - cerr << "substituting prebuilt " << prebuiltFile << endl; - - int res = system(("tar xfj " + prebuiltFile + " 1>&2").c_str()); // !!! escaping - if (WEXITSTATUS(res) != 0) - /* This is a fatal error, because path may now - have clobbered. */ - throw Error("cannot unpack " + prebuiltFile); - - _exit(0); + try { + prebuiltFile = getFile(parseHash(prebuiltHashS)); + } catch (Error e) { + cerr << "cannot obtain prebuilt (ignoring): " << e.what() << endl; + goto build; } -#endif + + cerr << "substituting prebuilt " << prebuiltFile << endl; -// build: + int res = system(("tar xfj " + prebuiltFile + " 1>&2").c_str()); // !!! escaping + if (WEXITSTATUS(res) != 0) + /* This is a fatal error, because path may now + have clobbered. */ + throw Error("cannot unpack " + prebuiltFile); - /* Fill in the environment. We don't bother freeing - the strings, since we'll exec or die soon - anyway. */ - const char * env2[env.size() + 1]; - int i = 0; - for (Environment::iterator it = env.begin(); - it != env.end(); it++, i++) - env2[i] = (new string(it->first + "=" + it->second))->c_str(); - env2[i] = 0; - - /* Dup the log handle into stderr. */ - if (dup2(fileno(logFile), STDERR_FILENO) == -1) - throw Error("cannot pipe standard error into log file: " + string(strerror(errno))); - - /* Dup stderr to stdin. */ - if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1) - throw Error("cannot dup stderr into stdout"); - - /* Make the program executable. !!! hack. */ - if (chmod(progPath.c_str(), 0755)) - throw Error("cannot make program executable"); - - /* Execute the program. This should not return. */ - execle(progPath.c_str(), baseNameOf(progPath).c_str(), 0, env2); - - throw Error("unable to execute builder: " + - string(strerror(errno))); - - } catch (exception & e) { - cerr << "build error: " << e.what() << endl; + _exit(0); } - _exit(1); - - } - - /* parent */ - - /* Close the logging pipe. Note that this should not cause - the logger to exit until builder exits (because the latter - has an open file handle to the former). */ - pclose(logFile); - - /* Wait for the child to finish. */ - int status; - if (waitpid(pid, &status, 0) != pid) - throw Error("unable to wait for child"); - - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) - throw Error("unable to build package"); - - /* Check whether the result was created. */ - if (!pathExists(targetPath)) - throw Error("program " + progPath + - " failed to create a result in " + targetPath); - -#if 0 - /* Remove write permission from the value. */ - int res = system(("chmod -R -w " + targetPath).c_str()); // !!! escaping - if (WEXITSTATUS(res) != 0) - throw Error("cannot remove write permission from " + targetPath); #endif - } catch (exception &) { -// system(("rm -rf " + targetPath).c_str()); - throw; + // build: + + /* Fill in the environment. We don't bother freeing + the strings, since we'll exec or die soon + anyway. */ + const char * env2[env.size() + 1]; + int i = 0; + for (Environment::iterator it = env.begin(); + it != env.end(); it++, i++) + env2[i] = (new string(it->first + "=" + it->second))->c_str(); + env2[i] = 0; + + /* Dup the log handle into stderr. */ + if (dup2(fileno(logFile), STDERR_FILENO) == -1) + throw SysError("cannot pipe standard error into log file"); + + /* Dup stderr to stdin. */ + if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1) + throw SysError("cannot dup stderr into stdout"); + + /* Make the program executable. !!! hack. */ + if (chmod(program.c_str(), 0755)) + throw SysError("cannot make program executable"); + + /* Execute the program. This should not return. */ + execle(program.c_str(), baseNameOf(program).c_str(), 0, env2); + + throw SysError(format("unable to execute %1%") % program); + + } catch (exception & e) { + cerr << "build error: " << e.what() << endl; + } + _exit(1); + } - /* Hash the result. */ - Hash targetHash = hashPath(targetPath); + /* parent */ - /* Register targetHash -> targetPath. !!! this should be in - values.cc. */ - setDB(nixDB, dbRefs, targetHash, targetName); - - /* Register that targetHash was produced by evaluating - sourceHash; i.e., that targetHash is a normal form of - sourceHash. !!! this shouldn't be here */ - setDB(nixDB, dbNFs, sourceHash, targetHash); - - return targetHash; + /* Close the logging pipe. Note that this should not cause + the logger to exit until builder exits (because the latter + has an open file handle to the former). */ + pclose(logFile); + + /* Wait for the child to finish. */ + int status; + if (waitpid(pid, &status, 0) != pid) + throw Error("unable to wait for child"); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + throw Error("unable to build package"); } -#endif /* Throw an exception if the given platform string is not supported by the platform we are executing on. */ -static void checkPlatform(string platform) +static void checkPlatform(const string & platform) { if (platform != thisSystem) throw Error(format("a `%1%' is required, but I am a `%2%'") @@ -250,33 +199,39 @@ struct RStatus }; -static void realise(RStatus & status, FState fs) +static FState realise(RStatus & status, FState fs) { - char * s; + char * s1, * s2, * s3; Content content; - ATermList refs; + ATermList refs, ins, outs, bnds; - if (ATmatch(fs, "File(, , [])", &s, &content, &refs)) { - string path(s); + if (ATmatch(fs, "File(, , [])", &s1, &content, &refs)) { + string path(s1); if (path[0] != '/') throw Error("absolute path expected: " + path); /* Realise referenced paths. */ + ATermList refs2 = ATempty; while (!ATisEmpty(refs)) { - realise(status, ATgetFirst(refs)); + refs2 = ATappend(refs2, realise(status, ATgetFirst(refs))); refs = ATgetNext(refs); } + refs2 = ATreverse(refs2); - if (!ATmatch(content, "Hash()", &s)) + if (!ATmatch(content, "Hash()", &s1)) throw badTerm("hash expected", content); - Hash hash = parseHash(s); + Hash hash = parseHash(s1); + + /* Normal form. */ + ATerm nf = ATmake("File(, , )", + path.c_str(), content, refs2); /* Perhaps the path already exists and has the right hash? */ if (pathExists(path)) { if (hash == hashPath(path)) { debug(format("path %1% already has hash %2%") % path % (string) hash); - return; + return nf; } throw Error(format("path %1% exists, but does not have hash %2%") @@ -286,19 +241,79 @@ static void realise(RStatus & status, FState fs) /* Do we know a path with that hash? If so, copy it. */ string path2 = queryFromStore(hash); copyFile(path2, path); + + return nf; } - else if (ATmatch(fs, "Derive()")) { + else if (ATmatch(fs, "Derive(, , [], , [])", + &s1, &s2, &ins, &s3, &bnds)) + { + string platform(s1), builder(s2), outPath(s3); + + checkPlatform(platform); + /* Realise inputs. */ + ATermList ins2 = ATempty; + while (!ATisEmpty(ins)) { + ins2 = ATappend(ins2, realise(status, ATgetFirst(ins))); + ins = ATgetNext(ins); + } + ins2 = ATreverse(ins2); + + /* Build the environment. */ + Environment env; + while (!ATisEmpty(bnds)) { + ATerm bnd = ATgetFirst(bnds); + if (!ATmatch(bnd, "(, )", &s1, &s2)) + throw badTerm("string expected", bnd); + env[s1] = s2; + bnds = ATgetNext(bnds); + } + + /* Check whether the target already exists. */ + if (pathExists(outPath)) + deleteFromStore(outPath); +// throw Error(format("path %1% already exists") % outPath); + + /* Run the builder. */ + runProgram(builder, env); + /* Check whether the result was created. */ + if (!pathExists(outPath)) + throw Error(format("program %1% failed to create a result in %2%") + % builder % outPath); + +#if 0 + /* Remove write permission from the value. */ + int res = system(("chmod -R -w " + targetPath).c_str()); // !!! escaping + if (WEXITSTATUS(res) != 0) + throw Error("cannot remove write permission from " + targetPath); +#endif + + /* Hash the result. */ + Hash outHash = hashPath(outPath); + + /* Register targetHash -> targetPath. !!! this should be in + values.cc. */ + setDB(nixDB, dbRefs, outHash, outPath); + +#if 0 + /* Register that targetHash was produced by evaluating + sourceHash; i.e., that targetHash is a normal form of + sourceHash. !!! this shouldn't be here */ + setDB(nixDB, dbNFs, sourceHash, targetHash); +#endif + + return ATmake("File(, Hash(), )", + outPath.c_str(), ((string) outHash).c_str(), ins2); } - else throw badTerm("bad file system state expression", fs); + throw badTerm("bad file system state expression", fs); } -void realiseFState(FState fs) +FState realiseFState(FState fs) { RStatus status; - realise(status, fs); + return realise(status, fs); } diff --git a/src/eval.hh b/src/eval.hh index f90d5ba02..553c7c40b 100644 --- a/src/eval.hh +++ b/src/eval.hh @@ -23,7 +23,7 @@ using namespace std; self-referential. This prevents us from having to deal with cycles. - Derive : String * Path * [FState] * [Path] * [(String, String)] -> [FState] + Derive : String * Path * [FState] * Path * [(String, String)] -> FState Derive(platform, builder, ins, outs, env) specifies the creation of new file objects (in paths declared by `outs') by the execution of @@ -61,7 +61,7 @@ typedef ATerm Content; /* Realise a $f$-normalised expression in the file system. */ -void realiseFState(FState fs); +FState realiseFState(FState fs); /* Return a canonical textual representation of an expression. */ string printTerm(ATerm t); diff --git a/src/test.cc b/src/test.cc index aafae8ee3..639bd5ccf 100644 --- a/src/test.cc +++ b/src/test.cc @@ -13,7 +13,9 @@ void realise(FState fs) { - realiseFState(fs); + cout << format("%1% => %2%\n") + % printTerm(fs) + % printTerm(realiseFState(fs)); } @@ -145,15 +147,25 @@ void runTests() "File(, Hash(), [])", builder1fn.c_str(), ((string) builder1h).c_str()); - realiseFState(fs1); - realiseFState(fs1); + realise(fs1); + realise(fs1); FState fs2 = ATmake( "File(, Hash(), [])", (builder1fn + "_bla").c_str(), ((string) builder1h).c_str()); - realiseFState(fs2); - realiseFState(fs2); + realise(fs2); + realise(fs2); + + string out1fn = nixStore + "/hello.txt"; + FState fs3 = ATmake( + "Derive(, , [], , [(\"out\", )])", + thisSystem.c_str(), + builder1fn.c_str(), + fs1, + out1fn.c_str(), + out1fn.c_str()); + realise(fs3); #if 0 Expr e1 = ATmake("Exec(Str(), Hash(), [])", diff --git a/src/util.cc b/src/util.cc index c6a0c1199..a042a65b0 100644 --- a/src/util.cc +++ b/src/util.cc @@ -11,10 +11,15 @@ string thisSystem = SYSTEM; -SysError::SysError(string msg) +Error::Error(const format & f) +{ + err = f.str(); +} + + +SysError::SysError(const format & f) + : Error(format("%1%: %2%") % f.str() % strerror(errno)) { - char * sysMsg = strerror(errno); - err = msg + ": " + sysMsg; } diff --git a/src/util.hh b/src/util.hh index 3efac928b..cf6f7d0c1 100644 --- a/src/util.hh +++ b/src/util.hh @@ -18,22 +18,21 @@ class Error : public exception protected: string err; public: - Error() { } - Error(format f) { err = f.str(); } - ~Error() throw () { } + Error(const format & f); + ~Error() throw () { }; const char * what() const throw () { return err.c_str(); } }; class SysError : public Error { public: - SysError(string msg); + SysError(const format & f); }; class UsageError : public Error { public: - UsageError(string _err) : Error(_err) { }; + UsageError(const format & f) : Error(f) { }; }; diff --git a/src/values.cc b/src/values.cc index e23624ce5..fe65b977e 100644 --- a/src/values.cc +++ b/src/values.cc @@ -127,16 +127,13 @@ string fetchURL(string url) #endif -void deleteFromStore(Hash hash) +void deleteFromStore(const string & path) { - string fn; - if (queryDB(nixDB, dbRefs, hash, fn)) { - string prefix = nixStore + "/"; - if (string(fn, prefix.size()) != prefix) - throw Error("path " + fn + " is not in the store"); - deletePath(fn); - delDB(nixDB, dbRefs, hash); - } + string prefix = nixStore + "/"; + if (string(path, 0, prefix.size()) != prefix) + throw Error(format("path %1% is not in the store") % path); + deletePath(path); +// delDB(nixDB, dbRefs, hash); } diff --git a/src/values.hh b/src/values.hh index 1bb00a9dd..79ef48671 100644 --- a/src/values.hh +++ b/src/values.hh @@ -15,7 +15,7 @@ void copyFile(string src, string dst); void addToStore(string srcPath, string & dstPath, Hash & hash); /* Delete a value from the nixStore directory. */ -void deleteFromStore(Hash hash); +void deleteFromStore(const string & path); /* !!! */ string queryFromStore(Hash hash); From 207ff2caf0f48db0fb539e228ec5c3938a279f2a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 4 Jul 2003 12:18:06 +0000 Subject: [PATCH 0096/6440] * Caching of expression successors. --- configure.ac | 2 +- src/Makefile.am | 2 +- src/eval.cc | 80 +++++++++++++++++++++++++++++++++++++++++-------- src/globals.cc | 4 +-- src/globals.hh | 16 +++++----- src/nix.cc | 33 +++++++++++++------- src/util.cc | 39 ++++++++++++++++++++---- src/util.hh | 12 ++++++++ 8 files changed, 145 insertions(+), 43 deletions(-) diff --git a/configure.ac b/configure.ac index 9b36f90a9..e3b0f0c6e 100644 --- a/configure.ac +++ b/configure.ac @@ -12,7 +12,7 @@ AC_PROG_CXX AC_PROG_RANLIB # Unix shell scripting should die a slow and painful death. -AC_DEFINE_UNQUOTED(NIX_VALUES_DIR, "$(eval echo $prefix/values)", Nix values directory.) +AC_DEFINE_UNQUOTED(NIX_STORE_DIR, "$(eval echo $prefix/store)", Nix store directory.) AC_DEFINE_UNQUOTED(NIX_STATE_DIR, "$(eval echo $localstatedir/nix)", Nix state directory.) AC_DEFINE_UNQUOTED(NIX_LOG_DIR, "$(eval echo $localstatedir/log/nix)", Nix log file directory.) diff --git a/src/Makefile.am b/src/Makefile.am index 4d8cd4229..573e84eb8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,5 +23,5 @@ install-data-local: # $(INSTALL) -d $(localstatedir)/nix/prebuilts/imports # $(INSTALL) -d $(localstatedir)/nix/prebuilts/exports $(INSTALL) -d $(localstatedir)/log/nix - $(INSTALL) -d $(prefix)/values + $(INSTALL) -d $(prefix)/store $(bindir)/nix --init diff --git a/src/eval.cc b/src/eval.cc index a1b8db6e0..4eb222197 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -25,7 +25,7 @@ bool pathExists(const string & path) res = stat(path.c_str(), &st); if (!res) return true; if (errno != ENOENT) - throw SysError("getting status of " + path); + throw SysError(format("getting status of %1%") % path); return false; } @@ -105,7 +105,7 @@ static void runProgram(const string & program, Environment env) throw SysError(format("unable to execute %1%") % program); } catch (exception & e) { - cerr << "build error: " << e.what() << endl; + cerr << format("build error: %1%\n") % e.what(); } _exit(1); @@ -199,15 +199,62 @@ struct RStatus }; +static ATerm termFromHash(const Hash & hash) +{ + string path = queryFromStore(hash); + ATerm t = ATreadFromNamedFile(path.c_str()); + if (!t) throw Error(format("cannot read aterm %1%") % path); + return t; +} + + +static Hash writeTerm(ATerm t) +{ + string path = nixStore + "/tmp.nix"; /* !!! */ + if (!ATwriteToNamedTextFile(t, path.c_str())) + throw Error(format("cannot write aterm %1%") % path); + Hash hash = hashPath(path); + string path2 = nixStore + "/" + (string) hash + ".nix"; + if (rename(path.c_str(), path2.c_str()) == -1) + throw SysError(format("renaming %1% to %2%") % path % path2); + setDB(nixDB, dbRefs, hash, path2); + return hash; +} + + static FState realise(RStatus & status, FState fs) { char * s1, * s2, * s3; Content content; - ATermList refs, ins, outs, bnds; + ATermList refs, ins, bnds; + + /* First repeatedly try to substitute $fs$ by any known successors + in order to speed up the rewrite process. */ + { + string fsHash, scHash; + while (queryDB(nixDB, dbSuccessors, fsHash = hashTerm(fs), scHash)) { + debug(format("successor %1% -> %2%") % (string) fsHash % scHash); + FState fs2 = termFromHash(parseHash(scHash)); + if (fs == fs2) { + debug(format("successor cycle detected in %1%") % printTerm(fs)); + break; + } + fs = fs2; + } + } + + /* Fall through. */ + + if (ATmatch(fs, "Include()", &s1)) { + return realise(status, termFromHash(parseHash(s1))); + } - if (ATmatch(fs, "File(, , [])", &s1, &content, &refs)) { + else if (ATmatch(fs, "File(, , [])", &s1, &content, &refs)) { string path(s1); + msg(format("realising atomic path %1%") % path); + Nest nest(true); + if (path[0] != '/') throw Error("absolute path expected: " + path); /* Realise referenced paths. */ @@ -223,9 +270,15 @@ static FState realise(RStatus & status, FState fs) Hash hash = parseHash(s1); /* Normal form. */ - ATerm nf = ATmake("File(, , )", + ATerm nf = ATmake("File(, , )", path.c_str(), content, refs2); + /* Register the normal form. */ + if (fs != nf) { + Hash nfHash = writeTerm(nf); + setDB(nixDB, dbSuccessors, hashTerm(fs), nfHash); + } + /* Perhaps the path already exists and has the right hash? */ if (pathExists(path)) { if (hash == hashPath(path)) { @@ -250,6 +303,9 @@ static FState realise(RStatus & status, FState fs) { string platform(s1), builder(s2), outPath(s3); + msg(format("realising derivate path %1%") % outPath); + Nest nest(true); + checkPlatform(platform); /* Realise inputs. */ @@ -297,15 +353,13 @@ static FState realise(RStatus & status, FState fs) values.cc. */ setDB(nixDB, dbRefs, outHash, outPath); -#if 0 - /* Register that targetHash was produced by evaluating - sourceHash; i.e., that targetHash is a normal form of - sourceHash. !!! this shouldn't be here */ - setDB(nixDB, dbNFs, sourceHash, targetHash); -#endif - - return ATmake("File(, Hash(), )", + /* Register the normal form of fs. */ + FState nf = ATmake("File(, Hash(), )", outPath.c_str(), ((string) outHash).c_str(), ins2); + Hash nfHash = writeTerm(nf); + setDB(nixDB, dbSuccessors, hashTerm(fs), nfHash); + + return nf; } throw badTerm("bad file system state expression", fs); diff --git a/src/globals.cc b/src/globals.cc index 640e960b1..a81c90caa 100644 --- a/src/globals.cc +++ b/src/globals.cc @@ -3,7 +3,7 @@ string dbRefs = "refs"; -string dbNFs = "nfs"; +string dbSuccessors = "successors"; string dbNetSources = "netsources"; string nixStore = "/UNINIT"; @@ -14,6 +14,6 @@ string nixDB = "/UNINIT"; void initDB() { createDB(nixDB, dbRefs); - createDB(nixDB, dbNFs); + createDB(nixDB, dbSuccessors); createDB(nixDB, dbNetSources); } diff --git a/src/globals.hh b/src/globals.hh index 3cb231ee2..8597ae2f8 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -14,17 +14,15 @@ using namespace std; resolve CHash(hash) content descriptors. */ extern string dbRefs; -/* dbNFs :: Hash -> Hash +/* dbSuccessors :: Hash -> Hash - Each pair (h1, h2) in this mapping records the fact that the normal - form of an expression with hash h1 is Hash(h2). + Each pair (h1, h2) in this mapping records the fact that a + successor of an fstate expression with hash h1 is stored in a file + with hash h2. - TODO: maybe this should be that the normal form of an expression - with hash h1 is an expression with hash h2; this would be more - general, but would require us to store lots of small expressions in - the file system just to support the caching mechanism. -*/ -extern string dbNFs; + Note that a term $y$ is successor of $x$ iff there exists a + sequence of rewrite steps that rewrites $x$ into $y$. */ +extern string dbSuccessors; /* dbNetSources :: Hash -> URL diff --git a/src/nix.cc b/src/nix.cc index fe9ab453b..9c9936f4b 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -22,7 +22,7 @@ static ArgType argType = atpUnknown; Operations: - --evaluate / -e: evaluate values + --realise / -r: realise values --delete / -d: delete values --query / -q: query stored values --add: add values @@ -87,8 +87,8 @@ static void getArgType(Strings & flags) } -/* Evaluate values. */ -static void opEvaluate(Strings opFlags, Strings opArgs) +/* Realise values. */ +static void opRealise(Strings opFlags, Strings opArgs) { getArgType(opFlags); if (!opFlags.empty()) throw UsageError("unknown flag"); @@ -101,16 +101,19 @@ static void opEvaluate(Strings opFlags, Strings opArgs) hash = parseHash(*it); else if (argType == atpName) throw Error("not implemented"); - else if (argType == atpPath) - hash = addValue(*it); - Expr e = ATmake("Deref(Hash())", ((string) hash).c_str()); - cerr << printExpr(evalValue(e)) << endl; + else if (argType == atpPath) { + string path; + addToStore(*it, path, hash); + } + FState fs = ATmake("Include()", ((string) hash).c_str()); + realiseFState(fs); } } static void opDelete(Strings opFlags, Strings opArgs) { +#if 0 getArgType(opFlags); if (!opFlags.empty()) throw UsageError("unknown flag"); @@ -126,6 +129,7 @@ static void opDelete(Strings opFlags, Strings opArgs) throw Error("invalid argument type"); deleteValue(hash); } +#endif } @@ -138,7 +142,12 @@ static void opAdd(Strings opFlags, Strings opArgs) for (Strings::iterator it = opArgs.begin(); it != opArgs.end(); it++) - cout << (string) addValue(*it) << endl; + { + string path; + Hash hash; + addToStore(*it, path, hash); + cout << format("%1% %2%\n") % (string) hash % path; + } } @@ -158,6 +167,7 @@ struct StdoutSink : DumpSink output. */ static void opDump(Strings opFlags, Strings opArgs) { +#if 0 getArgType(opFlags); if (!opFlags.empty()) throw UsageError("unknown flag"); if (opArgs.size() != 1) throw UsageError("only one argument allowed"); @@ -174,6 +184,7 @@ static void opDump(Strings opFlags, Strings opArgs) path = arg; dumpPath(path, sink); +#endif } @@ -218,7 +229,7 @@ static void opInit(Strings opFlags, Strings opArgs) static void run(int argc, char * * argv) { /* Setup Nix paths. */ - nixValues = NIX_VALUES_DIR; + nixStore = NIX_STORE_DIR; nixLogDir = NIX_LOG_DIR; nixDB = (string) NIX_STATE_DIR + "/nixstate.db"; @@ -253,8 +264,8 @@ static void run(int argc, char * * argv) Operation oldOp = op; - if (arg == "--evaluate" || arg == "-e") - op = opEvaluate; + if (arg == "--realise" || arg == "-r") + op = opRealise; else if (arg == "--delete" || arg == "-d") op = opDelete; else if (arg == "--add") diff --git a/src/util.cc b/src/util.cc index a042a65b0..65ceea938 100644 --- a/src/util.cc +++ b/src/util.cc @@ -36,7 +36,7 @@ string absPath(string path, string dir) /* !!! canonicalise */ char resolved[PATH_MAX]; if (!realpath(path.c_str(), resolved)) - throw SysError("cannot canonicalise path " + path); + throw SysError(format("cannot canonicalise path %1%") % path); path = resolved; } return path; @@ -46,7 +46,8 @@ string absPath(string path, string dir) string dirOf(string path) { unsigned int pos = path.rfind('/'); - if (pos == string::npos) throw Error("invalid file name: " + path); + if (pos == string::npos) + throw Error(format("invalid file name: %1%") % path); return string(path, 0, pos); } @@ -54,7 +55,8 @@ string dirOf(string path) string baseNameOf(string path) { unsigned int pos = path.rfind('/'); - if (pos == string::npos) throw Error("invalid file name: " + path); + if (pos == string::npos) + throw Error(format("invalid file name %1% ") % path); return string(path, pos + 1); } @@ -63,7 +65,7 @@ void deletePath(string path) { struct stat st; if (lstat(path.c_str(), &st)) - throw SysError("getting attributes of path " + path); + throw SysError(format("getting attributes of path %1%") % path); if (S_ISDIR(st.st_mode)) { DIR * dir = opendir(path.c_str()); @@ -79,11 +81,36 @@ void deletePath(string path) } if (remove(path.c_str()) == -1) - throw SysError("cannot unlink " + path); + throw SysError(format("cannot unlink %1%") % path); +} + + +static int nestingLevel = 0; + + +Nest::Nest(bool nest) +{ + this->nest = nest; + if (nest) nestingLevel++; +} + + +Nest::~Nest() +{ + if (nest) nestingLevel--; +} + + +void msg(const format & f) +{ + string spaces; + for (int i = 0; i < nestingLevel; i++) + spaces += " "; + cerr << format("%1%%2%\n") % spaces % f.str(); } void debug(const format & f) { - cerr << format("debug: %1%\n") % f.str(); + msg(format("debug: %1%") % f.str()); } diff --git a/src/util.hh b/src/util.hh index cf6f7d0c1..6242fcb11 100644 --- a/src/util.hh +++ b/src/util.hh @@ -61,6 +61,18 @@ string baseNameOf(string path); void deletePath(string path); +/* Messages. */ + +class Nest +{ +private: + bool nest; +public: + Nest(bool nest); + ~Nest(); +}; + +void msg(const format & f); void debug(const format & f); From 01b34fe5843df9888737699ee9f9fe2f161a1fa3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 4 Jul 2003 15:29:58 +0000 Subject: [PATCH 0097/6440] * Cleanup. --- src/eval.cc | 36 +---- src/fix.cc | 368 ------------------------------------------------- src/globals.hh | 8 +- 3 files changed, 9 insertions(+), 403 deletions(-) delete mode 100644 src/fix.cc diff --git a/src/eval.cc b/src/eval.cc index 4eb222197..be8b70a03 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -106,7 +106,7 @@ static void runProgram(const string & program, Environment env) } catch (exception & e) { cerr << format("build error: %1%\n") % e.what(); - } + } _exit(1); } @@ -159,38 +159,6 @@ Hash hashTerm(ATerm t) } -#if 0 -/* Evaluate a list of arguments into normal form. */ -void evalArgs(ATermList args, ATermList & argsNF, Environment & env) -{ - argsNF = ATempty; - - while (!ATisEmpty(args)) { - ATerm eName, eVal, arg = ATgetFirst(args); - if (!ATmatch(arg, "Tup(, )", &eName, &eVal)) - throw badTerm("invalid argument", arg); - - string name = evalString(eName); - eVal = evalValue(eVal); - - char * s; - if (ATmatch(eVal, "Str()", &s)) { - env[name] = s; - } else if (ATmatch(eVal, "Hash()", &s)) { - env[name] = queryValuePath(parseHash(s)); - } else throw badTerm("invalid argument value", eVal); - - argsNF = ATinsert(argsNF, - ATmake("Tup(Str(), )", name.c_str(), eVal)); - - args = ATgetNext(args); - } - - argsNF = ATreverse(argsNF); -} -#endif - - struct RStatus { /* !!! the comparator of this hash should match the semantics of @@ -362,7 +330,7 @@ static FState realise(RStatus & status, FState fs) return nf; } - throw badTerm("bad file system state expression", fs); + throw badTerm("bad fstate expression", fs); } diff --git a/src/fix.cc b/src/fix.cc deleted file mode 100644 index 3c4c8bf53..000000000 --- a/src/fix.cc +++ /dev/null @@ -1,368 +0,0 @@ -#include -#include - -#include -#include -#include - -extern "C" { -#include -} - -#include "util.hh" -#include "hash.hh" - - -static string nixDescriptorDir; - - -static bool verbose = false; - - -/* Mapping of Fix file names to the hashes of the resulting Nix - descriptors. */ -typedef map DescriptorMap; - - -void registerFile(string filename) -{ - int res = system(("nix regfile " + filename).c_str()); - /* !!! escape */ - if (WEXITSTATUS(res) != 0) - throw Error("cannot register " + filename + " with Nix"); -} - - -void registerURL(Hash hash, string url) -{ - int res = system(("nix regurl " + (string) hash + " " + url).c_str()); - /* !!! escape */ - if (WEXITSTATUS(res) != 0) - throw Error("cannot register " + - (string) hash + " -> " + url + " with Nix"); -} - - -Error badTerm(const string & msg, ATerm e) -{ - char * s = ATwriteToString(e); - return Error(msg + ", in `" + s + "'"); -} - - -/* Term evaluation. */ - -typedef map BindingsMap; - -struct EvalContext -{ - string dir; - DescriptorMap * done; - BindingsMap * vars; -}; - - -ATerm evaluate(ATerm e, EvalContext ctx); -Hash instantiateDescriptor(string filename, EvalContext ctx); - - -string evaluateStr(ATerm e, EvalContext ctx) -{ - e = evaluate(e, ctx); - char * s; - if (ATmatch(e, "Str()", &s)) - return s; - else throw badTerm("string value expected", e); -} - - -bool evaluateBool(ATerm e, EvalContext ctx) -{ - e = evaluate(e, ctx); - if (ATmatch(e, "Bool(True)")) - return true; - else if (ATmatch(e, "Bool(False)")) - return false; - else throw badTerm("boolean value expected", e); -} - - -ATerm evaluate(ATerm e, EvalContext ctx) -{ - char * s; - ATerm e2, e3; - ATerm eCond, eTrue, eFalse; - - /* Check for normal forms first. */ - - if (ATmatch(e, "Str()", &s) || - ATmatch(e, "Bool(True)") || ATmatch(e, "Bool(False)")) - return e; - - else if ( - ATmatch(e, "Pkg()", &s) || - ATmatch(e, "File()", &s)) - { - parseHash(s); - return e; - } - - /* Short-hands. */ - - else if (ATmatch(e, "", &s)) - return ATmake("Str()", s); - - else if (ATmatch(e, "True", &s)) - return ATmake("Bool(True)", s); - - else if (ATmatch(e, "False", &s)) - return ATmake("Bool(False)", s); - - /* Functions. */ - - /* `Var' looks up a variable. */ - else if (ATmatch(e, "Var()", &s)) { - string name(s); - ATerm e2 = (*ctx.vars)[name]; - if (!e2) throw Error("undefined variable " + name); - return evaluate(e2, ctx); /* !!! update binding */ - } - - /* `Fix' recursively instantiates a Fix descriptor, returning the - hash of the generated Nix descriptor. */ - else if (ATmatch(e, "Fix()", &e2)) { - string filename = absPath(evaluateStr(e2, ctx), ctx.dir); /* !!! */ - return ATmake("Pkg()", - ((string) instantiateDescriptor(filename, ctx)).c_str()); - } - -#if 0 - /* `Source' copies the specified file to nixSourcesDir, registers - it with Nix, and returns the hash of the file. */ - else if (ATmatch(e, "Source()", &e2)) { - string source = absPath(evaluateStr(e2, ctx), ctx.dir); /* !!! */ - string target = nixSourcesDir + "/" + baseNameOf(source); - - // Don't copy if filename is already in nixSourcesDir. - if (source != target) { - if (verbose) - cerr << "copying source " << source << endl; - string cmd = "cp -p " + source + " " + target; - int res = system(cmd.c_str()); - if (WEXITSTATUS(res) != 0) - throw Error("cannot copy " + source + " to " + target); - } - - registerFile(target); - return ATmake("File()", hashFile(target).c_str()); - } -#endif - - /* `Local' registers a file with Nix, and returns the file's - hash. */ - else if (ATmatch(e, "Local()", &e2)) { - string filename = absPath(evaluateStr(e2, ctx), ctx.dir); /* !!! */ - Hash hash = hashFile(filename); - registerFile(filename); /* !!! */ - return ATmake("File()", ((string) hash).c_str()); - } - - /* `Url' registers a mapping from a hash to an url with Nix, and - returns the hash. */ - else if (ATmatch(e, "Url(, )", &e2, &e3)) { - Hash hash = parseHash(evaluateStr(e2, ctx)); - string url = evaluateStr(e3, ctx); - registerURL(hash, url); - return ATmake("File()", ((string) hash).c_str()); - } - - /* `If' provides conditional evaluation. */ - else if (ATmatch(e, "If(, , )", - &eCond, &eTrue, &eFalse)) - return evaluate(evaluateBool(eCond, ctx) ? eTrue : eFalse, ctx); - - else throw badTerm("invalid expression", e); -} - - -string getStringFromMap(BindingsMap & bindingsMap, - const string & name) -{ - ATerm e = bindingsMap[name]; - if (!e) throw Error("binding " + name + " is not set"); - char * s; - if (ATmatch(e, "Str()", &s)) - return s; - else - throw Error("binding " + name + " is not a string"); -} - - -/* Instantiate a Fix descriptors into a Nix descriptor, recursively - instantiating referenced descriptors as well. */ -Hash instantiateDescriptor(string filename, EvalContext ctx) -{ - /* Already done? */ - DescriptorMap::iterator isInMap = ctx.done->find(filename); - if (isInMap != ctx.done->end()) return isInMap->second; - - /* No. */ - ctx.dir = dirOf(filename); - - /* Read the Fix descriptor as an ATerm. */ - ATerm inTerm = ATreadFromNamedFile(filename.c_str()); - if (!inTerm) throw Error("cannot read aterm " + filename); - - ATerm bindings; - if (!ATmatch(inTerm, "Descr()", &bindings)) - throw Error("invalid term in " + filename); - - /* Iterate over the bindings and evaluate them to normal form. */ - BindingsMap bindingsMap; /* the normal forms */ - ctx.vars = &bindingsMap; - - char * cname; - ATerm value; - while (ATmatch(bindings, "[Bind(, ), ]", - &cname, &value, &bindings)) - { - string name(cname); - ATerm e = evaluate(value, ctx); - bindingsMap[name] = e; - } - - /* Construct a descriptor identifier by concatenating the package - and release ids. */ - string pkgId = getStringFromMap(bindingsMap, "pkgId"); - string releaseId = getStringFromMap(bindingsMap, "releaseId"); - string id = pkgId + "-" + releaseId; - bindingsMap["id"] = ATmake("Str()", id.c_str()); - - /* Add a system name. */ - bindingsMap["system"] = ATmake("Str()", thisSystem.c_str()); - - /* Construct the resulting ATerm. Note that iterating over the - map yields the bindings in sorted order, which is exactly the - canonical form for Nix descriptors. */ - ATermList bindingsList = ATempty; - for (BindingsMap::iterator it = bindingsMap.begin(); - it != bindingsMap.end(); it++) - /* !!! O(n^2) */ - bindingsList = ATappend(bindingsList, - ATmake("Bind(, )", it->first.c_str(), it->second)); - ATerm outTerm = ATmake("Descr()", bindingsList); - - /* Write out the resulting ATerm. */ - string tmpFilename = nixDescriptorDir + "/tmp"; - if (!ATwriteToNamedTextFile(outTerm, tmpFilename.c_str())) - throw Error("cannot write aterm to " + tmpFilename); - - Hash outHash = hashFile(tmpFilename); - string outFilename = nixDescriptorDir + "/" + - id + "-" + (string) outHash + ".nix"; - if (rename(tmpFilename.c_str(), outFilename.c_str())) - throw Error("cannot rename " + tmpFilename + " to " + outFilename); - - /* Register it with Nix. */ - registerFile(outFilename); - - if (verbose) - cerr << "instantiated " << (string) outHash - << " from " << filename << endl; - - (*ctx.done)[filename] = outHash; - return outHash; -} - - -/* Instantiate a set of Fix descriptors into Nix descriptors. */ -void instantiateDescriptors(Strings filenames) -{ - DescriptorMap done; - - EvalContext ctx; - ctx.done = &done; - - for (Strings::iterator it = filenames.begin(); - it != filenames.end(); it++) - { - string filename = absPath(*it); - cout << (string) instantiateDescriptor(filename, ctx) << endl; - } -} - - -/* Print help. */ -void printUsage() -{ - cerr << -"Usage: fix ...\n\ -"; -} - - -/* Parse the command-line arguments, call the right operation. */ -void run(Strings::iterator argCur, Strings::iterator argEnd) -{ - umask(0022); - - Strings extraArgs; - enum { cmdUnknown, cmdInstantiate } command = cmdUnknown; - - char * homeDir = getenv(nixHomeDirEnvVar.c_str()); - if (homeDir) nixHomeDir = homeDir; - - nixDescriptorDir = nixHomeDir + "/var/nix/descriptors"; - - for ( ; argCur != argEnd; argCur++) { - string arg(*argCur); - if (arg == "-h" || arg == "--help") { - printUsage(); - return; - } else if (arg == "-v" || arg == "--verbose") { - verbose = true; - } else if (arg == "--instantiate" || arg == "-i") { - command = cmdInstantiate; - } else if (arg[0] == '-') - throw UsageError("invalid option `" + arg + "'"); - else - extraArgs.push_back(arg); - } - - switch (command) { - - case cmdInstantiate: - instantiateDescriptors(extraArgs); - break; - - default: - throw UsageError("no operation specified"); - } -} - - -int main(int argc, char * * argv) -{ - ATerm bottomOfStack; - ATinit(argc, argv, &bottomOfStack); - - /* Put the arguments in a vector. */ - Strings args; - while (argc--) args.push_back(*argv++); - Strings::iterator argCur = args.begin(), argEnd = args.end(); - - argCur++; - - try { - run(argCur, argEnd); - } catch (UsageError & e) { - cerr << "error: " << e.what() << endl - << "Try `fix -h' for more information.\n"; - return 1; - } catch (exception & e) { - cerr << "error: " << e.what() << endl; - return 1; - } - - return 0; -} diff --git a/src/globals.hh b/src/globals.hh index 8597ae2f8..8c6a763c4 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -21,7 +21,13 @@ extern string dbRefs; with hash h2. Note that a term $y$ is successor of $x$ iff there exists a - sequence of rewrite steps that rewrites $x$ into $y$. */ + sequence of rewrite steps that rewrites $x$ into $y$. + + Also note that instead of a successor, $y$ can be any term + equivalent to $x$, that is, reducing to the same result, as long as + $x$ is equal to or a successor of $y$. (This is useful, e.g., for + shared derivate caching over the network). +*/ extern string dbSuccessors; /* dbNetSources :: Hash -> URL From f826e432aa442e569faaf3cb04d83bfa28bcf260 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 4 Jul 2003 15:42:03 +0000 Subject: [PATCH 0098/6440] * Refactoring: move initialisation and argument parsing into a shared file. --- src/Makefile.am | 2 +- src/eval.cc | 2 +- src/nix.cc | 56 +++++-------------------------------------- src/shared.cc | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ src/shared.hh | 15 ++++++++++++ 5 files changed, 86 insertions(+), 52 deletions(-) create mode 100644 src/shared.cc create mode 100644 src/shared.hh diff --git a/src/Makefile.am b/src/Makefile.am index 573e84eb8..a3cd46ca2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,7 +3,7 @@ check_PROGRAMS = test AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. -nix_SOURCES = nix.cc +nix_SOURCES = nix.cc shared.cc nix_LDADD = libnix.a -ldb_cxx-4 -lATerm TESTS = test diff --git a/src/eval.cc b/src/eval.cc index be8b70a03..4f4d419f5 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -106,7 +106,7 @@ static void runProgram(const string & program, Environment env) } catch (exception & e) { cerr << format("build error: %1%\n") % e.what(); - } + } _exit(1); } diff --git a/src/nix.cc b/src/nix.cc index 9c9936f4b..f2d00394f 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -1,11 +1,10 @@ #include -#include "config.h" - #include "globals.hh" #include "values.hh" #include "eval.hh" #include "archive.hh" +#include "shared.hh" typedef void (* Operation) (Strings opFlags, Strings opArgs); @@ -224,39 +223,14 @@ static void opInit(Strings opFlags, Strings opArgs) } -/* Initialize, process arguments, and dispatch to the right - operation. */ -static void run(int argc, char * * argv) +/* Scan the arguments; find the operation, set global flags, put all + other flags in a list, and put all other arguments in another + list. */ +void run(Strings args) { - /* Setup Nix paths. */ - nixStore = NIX_STORE_DIR; - nixLogDir = NIX_LOG_DIR; - nixDB = (string) NIX_STATE_DIR + "/nixstate.db"; - - /* Put the arguments in a vector. */ - Strings args; - while (argc--) args.push_back(*argv++); - args.erase(args.begin()); - - /* Expand compound dash options (i.e., `-qlf' -> `-q -l -f'). */ - for (Strings::iterator it = args.begin(); - it != args.end(); ) - { - string arg = *it; - if (arg.length() > 2 && arg[0] == '-' && arg[1] != '-') { - for (unsigned int i = 1; i < arg.length(); i++) - args.insert(it, (string) "-" + arg[i]); - it = args.erase(it); - } else it++; - } - Strings opFlags, opArgs; Operation op = 0; - /* Scan the arguments; find the operation, set global flags, put - all other flags in a list, and put all other arguments in - another list. */ - for (Strings::iterator it = args.begin(); it != args.end(); it++) { @@ -291,22 +265,4 @@ static void run(int argc, char * * argv) } -int main(int argc, char * * argv) -{ - /* ATerm setup. */ - ATerm bottomOfStack; - ATinit(argc, argv, &bottomOfStack); - - try { - run(argc, argv); - } catch (UsageError & e) { - cerr << "error: " << e.what() << endl - << "Try `nix --help' for more information.\n"; - return 1; - } catch (exception & e) { - cerr << "error: " << e.what() << endl; - return 1; - } - - return 0; -} +string programId = "nix"; diff --git a/src/shared.cc b/src/shared.cc new file mode 100644 index 000000000..6d157766e --- /dev/null +++ b/src/shared.cc @@ -0,0 +1,63 @@ +#include + +extern "C" { +#include +} + +#include "globals.hh" +#include "shared.hh" + +#include "config.h" + + +/* Initialize and reorder arguments, then call the actual argument + processor. */ +static void initAndRun(int argc, char * * argv) +{ + /* Setup Nix paths. */ + nixStore = NIX_STORE_DIR; + nixLogDir = NIX_LOG_DIR; + nixDB = (string) NIX_STATE_DIR + "/nixstate.db"; + + /* Put the arguments in a vector. */ + Strings args; + while (argc--) args.push_back(*argv++); + args.erase(args.begin()); + + /* Expand compound dash options (i.e., `-qlf' -> `-q -l -f'). */ + for (Strings::iterator it = args.begin(); + it != args.end(); ) + { + string arg = *it; + if (arg.length() > 2 && arg[0] == '-' && arg[1] != '-') { + for (unsigned int i = 1; i < arg.length(); i++) + args.insert(it, (string) "-" + arg[i]); + it = args.erase(it); + } else it++; + } + + run(args); +} + + +int main(int argc, char * * argv) +{ + /* ATerm setup. */ + ATerm bottomOfStack; + ATinit(argc, argv, &bottomOfStack); + + try { + initAndRun(argc, argv); + } catch (UsageError & e) { + cerr << format( + "error: %1%\n" + "Try `%2% --help' for more information.\n") + % e.what() % programId; + return 1; + } catch (exception & e) { + cerr << format("error: %1%\n") % e.what(); + return 1; + } + + return 0; +} diff --git a/src/shared.hh b/src/shared.hh new file mode 100644 index 000000000..8ea637fd2 --- /dev/null +++ b/src/shared.hh @@ -0,0 +1,15 @@ +#ifndef __SHARED_H +#define __SHARED_H + +#include + +#include "util.hh" + + +void run(Strings args); + + +extern string programId; + + +#endif /* !__SHARED_H */ From 82e3d8fafe0ac08589349094e3ea11022d995959 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 6 Jul 2003 14:20:47 +0000 Subject: [PATCH 0099/6440] * Got Fix working again. --- src/Makefile.am | 5 +- src/eval.cc | 23 ++--- src/eval.hh | 11 +- src/fix.cc | 266 ++++++++++++++++++++++++++++++++++++++++++++++++ src/test.cc | 10 +- src/values.cc | 4 +- src/values.hh | 2 +- 7 files changed, 298 insertions(+), 23 deletions(-) create mode 100644 src/fix.cc diff --git a/src/Makefile.am b/src/Makefile.am index a3cd46ca2..7d12719ac 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,4 @@ -bin_PROGRAMS = nix +bin_PROGRAMS = nix fix check_PROGRAMS = test AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. @@ -6,6 +6,9 @@ AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. nix_SOURCES = nix.cc shared.cc nix_LDADD = libnix.a -ldb_cxx-4 -lATerm +fix_SOURCES = fix.cc shared.cc +fix_LDADD = libnix.a -ldb_cxx-4 -lATerm + TESTS = test test_SOURCES = test.cc diff --git a/src/eval.cc b/src/eval.cc index 4f4d419f5..354ecb6cc 100644 --- a/src/eval.cc +++ b/src/eval.cc @@ -145,9 +145,7 @@ string printTerm(ATerm t) } -/* Throw an exception with an error message containing the given - aterm. */ -static Error badTerm(const format & f, ATerm t) +Error badTerm(const format & f, ATerm t) { return Error(format("%1%, in `%2%'") % f.str() % printTerm(t)); } @@ -176,7 +174,7 @@ static ATerm termFromHash(const Hash & hash) } -static Hash writeTerm(ATerm t) +Hash writeTerm(ATerm t) { string path = nixStore + "/tmp.nix"; /* !!! */ if (!ATwriteToNamedTextFile(t, path.c_str())) @@ -217,18 +215,19 @@ static FState realise(RStatus & status, FState fs) return realise(status, termFromHash(parseHash(s1))); } - else if (ATmatch(fs, "File(, , [])", &s1, &content, &refs)) { + else if (ATmatch(fs, "Path(, , [])", &s1, &content, &refs)) { string path(s1); msg(format("realising atomic path %1%") % path); Nest nest(true); - if (path[0] != '/') throw Error("absolute path expected: " + path); + if (path[0] != '/') + throw Error(format("path `%1% is not absolute") % path); /* Realise referenced paths. */ ATermList refs2 = ATempty; while (!ATisEmpty(refs)) { - refs2 = ATappend(refs2, realise(status, ATgetFirst(refs))); + refs2 = ATinsert(refs2, realise(status, ATgetFirst(refs))); refs = ATgetNext(refs); } refs2 = ATreverse(refs2); @@ -238,7 +237,7 @@ static FState realise(RStatus & status, FState fs) Hash hash = parseHash(s1); /* Normal form. */ - ATerm nf = ATmake("File(, , )", + ATerm nf = ATmake("Path(, , )", path.c_str(), content, refs2); /* Register the normal form. */ @@ -261,7 +260,7 @@ static FState realise(RStatus & status, FState fs) /* Do we know a path with that hash? If so, copy it. */ string path2 = queryFromStore(hash); - copyFile(path2, path); + copyPath(path2, path); return nf; } @@ -279,7 +278,7 @@ static FState realise(RStatus & status, FState fs) /* Realise inputs. */ ATermList ins2 = ATempty; while (!ATisEmpty(ins)) { - ins2 = ATappend(ins2, realise(status, ATgetFirst(ins))); + ins2 = ATinsert(ins2, realise(status, ATgetFirst(ins))); ins = ATgetNext(ins); } ins2 = ATreverse(ins2); @@ -289,7 +288,7 @@ static FState realise(RStatus & status, FState fs) while (!ATisEmpty(bnds)) { ATerm bnd = ATgetFirst(bnds); if (!ATmatch(bnd, "(, )", &s1, &s2)) - throw badTerm("string expected", bnd); + throw badTerm("tuple of strings expected", bnd); env[s1] = s2; bnds = ATgetNext(bnds); } @@ -322,7 +321,7 @@ static FState realise(RStatus & status, FState fs) setDB(nixDB, dbRefs, outHash, outPath); /* Register the normal form of fs. */ - FState nf = ATmake("File(, Hash(), )", + FState nf = ATmake("Path(, Hash(), )", outPath.c_str(), ((string) outHash).c_str(), ins2); Hash nfHash = writeTerm(nf); setDB(nixDB, dbSuccessors, hashTerm(fs), nfHash); diff --git a/src/eval.hh b/src/eval.hh index 553c7c40b..b04588e7b 100644 --- a/src/eval.hh +++ b/src/eval.hh @@ -15,9 +15,9 @@ using namespace std; A Nix file system state expression, or FState, describes a (partial) state of the file system. - File : Path * Content * [FState] -> FState + Path : Path * Content * [FState] -> FState - File(path, content, refs) specifies a file object (its full path + Path(path, content, refs) specifies a file object (its full path and contents), along with all file objects referenced by it (that is, that it has pointers to). We assume that all files are self-referential. This prevents us from having to deal with @@ -66,8 +66,15 @@ FState realiseFState(FState fs); /* Return a canonical textual representation of an expression. */ string printTerm(ATerm t); +/* Throw an exception with an error message containing the given + aterm. */ +Error badTerm(const format & f, ATerm t); + /* Hash an aterm. */ Hash hashTerm(ATerm t); +/* Write an aterm to the Nix store directory, and return its hash. */ +Hash writeTerm(ATerm t); + #endif /* !__EVAL_H */ diff --git a/src/fix.cc b/src/fix.cc new file mode 100644 index 000000000..cb42aca6b --- /dev/null +++ b/src/fix.cc @@ -0,0 +1,266 @@ +#include +#include + +#include "globals.hh" +#include "eval.hh" +#include "values.hh" +#include "shared.hh" + + +typedef ATerm Expr; + + +static Expr evalFile(string fileName); + + +static bool isFState(Expr e, string & path) +{ + char * s1, * s2, * s3; + Expr e1, e2; + if (ATmatch(e, "Path(, , [])", &s1, &e1, &e2)) { + path = s1; + return true; + } + else if (ATmatch(e, "Derive(, , [], , [])", + &s1, &s2, &e1, &s3, &e2)) + { + path = s3; + return true; + } + else if (ATmatch(e, "Include()", &s1)) + { + string fn = queryFromStore(parseHash(s1)); + return isFState(evalFile(fn), path); + } + else return false; +} + + +static Expr substExpr(string x, Expr rep, Expr e) +{ + char * s; + Expr e2; + + if (ATmatch(e, "Var()", &s)) + if (x == s) + return rep; + else + return e; + + if (ATmatch(e, "Lam(, )", &s, &e2)) + if (x == s) + return e; + /* !!! unfair substitutions */ + + /* Generically substitute in subterms. */ + + if (ATgetType(e) == AT_APPL) { + AFun fun = ATgetAFun(e); + int arity = ATgetArity(fun); + ATermList args = ATempty; + + for (int i = arity - 1; i >= 0; i--) + args = ATinsert(args, substExpr(x, rep, ATgetArgument(e, i))); + + return (ATerm) ATmakeApplList(fun, args); + } + + if (ATgetType(e) == AT_LIST) { + ATermList in = (ATermList) e; + ATermList out = ATempty; + + while (!ATisEmpty(in)) { + out = ATinsert(out, substExpr(x, rep, ATgetFirst(in))); + in = ATgetNext(in); + } + + return (ATerm) ATreverse(out); + } + + throw badTerm("do not know how to substitute", e); +} + + +static Expr substExprMany(ATermList formals, ATermList args, Expr body) +{ + char * s; + Expr e; + + /* !!! check args against formals */ + + while (!ATisEmpty(args)) { + ATerm tup = ATgetFirst(args); + if (!ATmatch(tup, "(, )", &s, &e)) + throw badTerm("expected an argument tuple", tup); + + body = substExpr(s, e, body); + + args = ATgetNext(args); + } + + return body; +} + + +static Expr evalExpr(Expr e) +{ + char * s1; + Expr e1, e2, e3, e4; + ATermList bnds; + + /* Normal forms. */ + if (ATmatch(e, "", &s1) || + ATmatch(e, "Function([], )", &e1, &e2)) + return e; + + string dummy; + if (isFState(e, dummy)) return e; + + /* Application. */ + if (ATmatch(e, "App(, [])", &e1, &e2)) { + e1 = evalExpr(e1); + if (!ATmatch(e1, "Function([], )", &e3, &e4)) + throw badTerm("expecting a function", e1); + return evalExpr(substExprMany((ATermList) e3, (ATermList) e2, e4)); + } + + /* Fix inclusion. */ + if (ATmatch(e, "IncludeFix()", &s1)) { + string fileName(s1); + return evalFile(s1); + } + + /* Relative files. */ + if (ATmatch(e, "Relative()", &s1)) { + string srcPath = s1; + string dstPath; + Hash hash; + addToStore(srcPath, dstPath, hash); + return ATmake("Path(, Hash(), [])", + dstPath.c_str(), ((string) hash).c_str()); + } + + /* Packages are transformed into Derive fstate expressions. */ + if (ATmatch(e, "Package([])", &bnds)) { + + /* Evaluate the bindings and put them in a map. */ + map bndMap; + bndMap["platform"] = ATmake("", SYSTEM); + while (!ATisEmpty(bnds)) { + ATerm bnd = ATgetFirst(bnds); + if (!ATmatch(bnd, "(, )", &s1, &e1)) + throw badTerm("binding expected", bnd); + bndMap[s1] = evalExpr(e1); + bnds = ATgetNext(bnds); + } + + /* Gather information for building the Derive expression. */ + ATermList ins = ATempty, env = ATempty; + string builder, id; + bnds = ATempty; + + for (map::iterator it = bndMap.begin(); + it != bndMap.end(); it++) + { + string key = it->first; + ATerm value = it->second; + + string path; + if (isFState(value, path)) { + ins = ATinsert(ins, value); + env = ATinsert(env, ATmake("(, )", + key.c_str(), path.c_str())); + if (key == "build") builder = path; + } + else if (ATmatch(value, "", &s1)) { + if (key == "id") id = s1; + env = ATinsert(env, + ATmake("(, )", key.c_str(), s1)); + } + else throw badTerm("invalid package argument", value); + + bnds = ATinsert(bnds, + ATmake("(, )", key.c_str(), value)); + } + + /* Hash the normal form to produce a unique but deterministic + path name for this package. */ + ATerm nf = ATmake("Package()", ATreverse(bnds)); + debug(printTerm(nf)); + Hash hash = hashTerm(nf); + + if (builder == "") + throw badTerm("no builder specified", nf); + + if (id == "") + throw badTerm("no package identifier specified", nf); + + string out = nixStore + "/" + ((string) hash).c_str() + "-" + id; + + env = ATinsert(env, ATmake("(, )", "out", out.c_str())); + + /* Construct the result. */ + e = ATmake("Derive(, , , , )", + SYSTEM, builder.c_str(), ins, out.c_str(), env); + debug(printTerm(e)); + + /* Write the resulting term into the Nix store directory. */ + Hash eHash = writeTerm(e); + + return ATmake("Include()", ((string) eHash).c_str()); + } + + /* Barf. */ + throw badTerm("invalid expression", e); +} + + +static Strings searchPath; + + +static Expr evalFile(string fileName) +{ + Expr e = ATreadFromNamedFile(fileName.c_str()); + if (!e) throw Error(format("cannot read aterm `%1%'") % fileName); + return evalExpr(e); +} + + +void run(Strings args) +{ + Strings files; + + searchPath.push_back("."); + + for (Strings::iterator it = args.begin(); + it != args.end(); ) + { + string arg = *it++; + + if (arg == "--includedir" || arg == "-I") { + if (it == args.end()) + throw UsageError(format("argument required in `%1%'") % arg); + searchPath.push_back(*it++); + } + else if (arg[0] == '-') + throw UsageError(format("unknown flag `%1%`") % arg); + else + files.push_back(arg); + } + + if (files.empty()) throw UsageError("no files specified"); + + for (Strings::iterator it = files.begin(); + it != files.end(); it++) + { + Expr e = evalFile(*it); + char * s; + if (ATmatch(e, "Include()", &s)) { + cout << format("%1%\n") % s; + } + else throw badTerm("top level is not a package", e); + } +} + + +string programId = "fix"; diff --git a/src/test.cc b/src/test.cc index 639bd5ccf..3851ef867 100644 --- a/src/test.cc +++ b/src/test.cc @@ -120,17 +120,17 @@ void runTests() ATmake("Foo(123)")); string builder1fn = absPath("./test-builder-1.sh"); - Hash builder1h = hashFile(builder1fn); + Hash builder1h = hashPath(builder1fn); string fn1 = nixValues + "/builder-1.sh"; - Expr e1 = ATmake("File(, ExtFile(, ), [])", + Expr e1 = ATmake("Path(, ExtFile(, ), [])", fn1.c_str(), builder1h.c_str(), builder1fn.c_str()); eval(fNormalise, e1); string fn2 = nixValues + "/refer.txt"; - Expr e2 = ATmake("File(, Regular(), [])", + Expr e2 = ATmake("Path(, Regular(), [])", fn2.c_str(), ("I refer to " + fn1).c_str(), e1); @@ -144,14 +144,14 @@ void runTests() addToStore("./test-builder-1.sh", builder1fn, builder1h); FState fs1 = ATmake( - "File(, Hash(), [])", + "Path(, Hash(), [])", builder1fn.c_str(), ((string) builder1h).c_str()); realise(fs1); realise(fs1); FState fs2 = ATmake( - "File(, Hash(), [])", + "Path(, Hash(), [])", (builder1fn + "_bla").c_str(), ((string) builder1h).c_str()); realise(fs2); diff --git a/src/values.cc b/src/values.cc index fe65b977e..5db04036c 100644 --- a/src/values.cc +++ b/src/values.cc @@ -34,7 +34,7 @@ struct CopySource : RestoreSource }; -void copyFile(string src, string dst) +void copyPath(string src, string dst) { /* Unfortunately C++ doesn't support coprocedures, so we have no nice way to chain CopySink and CopySource together. Instead we @@ -99,7 +99,7 @@ void addToStore(string srcPath, string & dstPath, Hash & hash) string baseName = baseNameOf(srcPath); dstPath = nixStore + "/" + (string) hash + "-" + baseName; - copyFile(srcPath, dstPath); + copyPath(srcPath, dstPath); setDB(nixDB, dbRefs, hash, dstPath); } diff --git a/src/values.hh b/src/values.hh index 79ef48671..b96fa30ba 100644 --- a/src/values.hh +++ b/src/values.hh @@ -8,7 +8,7 @@ using namespace std; -void copyFile(string src, string dst); +void copyPath(string src, string dst); /* Copy a file to the nixStore directory and register it in dbRefs. Return the hash code of the value. */ From bfa5d77211385cd8abe5d0833f84a8151ccab37d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 6 Jul 2003 15:08:39 +0000 Subject: [PATCH 0100/6440] * Bug fix: properly check result of open(). --- src/archive.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/archive.cc b/src/archive.cc index d0cf6ca34..7e07b8a08 100644 --- a/src/archive.cc +++ b/src/archive.cc @@ -87,7 +87,7 @@ static void dumpContents(const string & path, unsigned int size, writeInt(size, sink); int fd = open(path.c_str(), O_RDONLY); - if (!fd) throw SysError("opening file " + path); + if (fd == -1) throw SysError("opening file " + path); unsigned char buf[65536]; From 7952a8053c474e771d6ee14e3ab6dc15c9ddd895 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 6 Jul 2003 15:11:02 +0000 Subject: [PATCH 0101/6440] * A utility `nix-hash' to compute Nix path hashes. --- src/Makefile.am | 5 ++++- src/fix.cc | 4 +--- src/nix-hash.cc | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 src/nix-hash.cc diff --git a/src/Makefile.am b/src/Makefile.am index 7d12719ac..22b27d88a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,4 @@ -bin_PROGRAMS = nix fix +bin_PROGRAMS = nix nix-hash fix check_PROGRAMS = test AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. @@ -6,6 +6,9 @@ AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. nix_SOURCES = nix.cc shared.cc nix_LDADD = libnix.a -ldb_cxx-4 -lATerm +nix_hash_SOURCES = nix-hash.cc shared.cc +nix_hash_LDADD = libnix.a -ldb_cxx-4 -lATerm + fix_SOURCES = fix.cc shared.cc fix_LDADD = libnix.a -ldb_cxx-4 -lATerm diff --git a/src/fix.cc b/src/fix.cc index cb42aca6b..33bcdd9a1 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -186,7 +186,6 @@ static Expr evalExpr(Expr e) /* Hash the normal form to produce a unique but deterministic path name for this package. */ ATerm nf = ATmake("Package()", ATreverse(bnds)); - debug(printTerm(nf)); Hash hash = hashTerm(nf); if (builder == "") @@ -202,7 +201,6 @@ static Expr evalExpr(Expr e) /* Construct the result. */ e = ATmake("Derive(, , , , )", SYSTEM, builder.c_str(), ins, out.c_str(), env); - debug(printTerm(e)); /* Write the resulting term into the Nix store directory. */ Hash eHash = writeTerm(e); @@ -257,7 +255,7 @@ void run(Strings args) char * s; if (ATmatch(e, "Include()", &s)) { cout << format("%1%\n") % s; - } + } else throw badTerm("top level is not a package", e); } } diff --git a/src/nix-hash.cc b/src/nix-hash.cc new file mode 100644 index 000000000..e35d0a1fe --- /dev/null +++ b/src/nix-hash.cc @@ -0,0 +1,16 @@ +#include + +#include "hash.hh" +#include "shared.hh" + + +void run(Strings args) +{ + for (Strings::iterator it = args.begin(); + it != args.end(); it++) + cout << format("%1%\n") % (string) hashPath(*it); +} + + +string programId = "nix-hash"; + From 224c585aba5e450fa47e41c4cc19dac2d0c6fe2a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 7 Jul 2003 07:43:58 +0000 Subject: [PATCH 0102/6440] * Refactoring on the file names. --- src/Makefile.am | 2 +- src/fix.cc | 4 +-- src/{eval.cc => fstate.cc} | 4 +-- src/{eval.hh => fstate.hh} | 0 src/nix.cc | 4 +-- src/{values.cc => store.cc} | 63 ++++++------------------------------- src/{values.hh => store.hh} | 0 7 files changed, 17 insertions(+), 60 deletions(-) rename src/{eval.cc => fstate.cc} (99%) rename src/{eval.hh => fstate.hh} (100%) rename src/{values.cc => store.cc} (62%) rename src/{values.hh => store.hh} (100%) diff --git a/src/Makefile.am b/src/Makefile.am index 22b27d88a..5488e133a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,7 +20,7 @@ test_LDADD = libnix.a -ldb_cxx-4 -lATerm noinst_LIBRARIES = libnix.a libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c \ - eval.cc values.cc globals.cc db.cc + fstate.cc store.cc globals.cc db.cc install-data-local: $(INSTALL) -d $(localstatedir)/nix diff --git a/src/fix.cc b/src/fix.cc index 33bcdd9a1..fdf12ffef 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -2,8 +2,8 @@ #include #include "globals.hh" -#include "eval.hh" -#include "values.hh" +#include "fstate.hh" +#include "store.hh" #include "shared.hh" diff --git a/src/eval.cc b/src/fstate.cc similarity index 99% rename from src/eval.cc rename to src/fstate.cc index 354ecb6cc..8003a1b38 100644 --- a/src/eval.cc +++ b/src/fstate.cc @@ -7,9 +7,9 @@ #include #include -#include "eval.hh" +#include "fstate.hh" #include "globals.hh" -#include "values.hh" +#include "store.hh" #include "db.hh" diff --git a/src/eval.hh b/src/fstate.hh similarity index 100% rename from src/eval.hh rename to src/fstate.hh diff --git a/src/nix.cc b/src/nix.cc index f2d00394f..dc9d04148 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -1,8 +1,8 @@ #include #include "globals.hh" -#include "values.hh" -#include "eval.hh" +#include "store.hh" +#include "fstate.hh" #include "archive.hh" #include "shared.hh" diff --git a/src/values.cc b/src/store.cc similarity index 62% rename from src/values.cc rename to src/store.cc index 5db04036c..68a1cd1e2 100644 --- a/src/values.cc +++ b/src/store.cc @@ -3,7 +3,7 @@ #include #include -#include "values.hh" +#include "store.hh" #include "globals.hh" #include "db.hh" #include "archive.hh" @@ -105,28 +105,6 @@ void addToStore(string srcPath, string & dstPath, Hash & hash) } -#if 0 -/* Download object referenced by the given URL into the sources - directory. Return the file name it was downloaded to. */ -string fetchURL(string url) -{ - string filename = baseNameOf(url); - string fullname = nixSourcesDir + "/" + filename; - struct stat st; - if (stat(fullname.c_str(), &st)) { - cerr << "fetching " << url << endl; - /* !!! quoting */ - string shellCmd = - "cd " + nixSourcesDir + " && wget --quiet -N \"" + url + "\""; - int res = system(shellCmd.c_str()); - if (WEXITSTATUS(res) != 0) - throw Error("cannot fetch " + url); - } - return fullname; -} -#endif - - void deleteFromStore(const string & path) { string prefix = nixStore + "/"; @@ -139,37 +117,16 @@ void deleteFromStore(const string & path) string queryFromStore(Hash hash) { - bool checkedNet = false; + string fn, url; - while (1) { - - string fn, url; - - if (queryDB(nixDB, dbRefs, hash, fn)) { - - /* Verify that the file hasn't changed. !!! race !!! slow */ - if (hashPath(fn) != hash) - throw Error("file " + fn + " is stale"); - - return fn; - } - - throw Error("a file with hash " + (string) hash + " is required, " - "but it is not known to exist locally or on the network"); -#if 0 - if (checkedNet) - throw Error("consistency problem: file fetched from " + url + - " should have hash " + (string) hash + ", but it doesn't"); - - if (!queryDB(nixDB, dbNetSources, hash, url)) - throw Error("a file with hash " + (string) hash + " is required, " - "but it is not known to exist locally or on the network"); - - checkedNet = true; + if (queryDB(nixDB, dbRefs, hash, fn)) { - fn = fetchURL(url); - - setDB(nixDB, dbRefs, hash, fn); -#endif + /* Verify that the file hasn't changed. !!! race !!! slow */ + if (hashPath(fn) != hash) + throw Error("file " + fn + " is stale"); + + return fn; } + + throw Error(format("don't know a path with hash `%1%'") % (string) hash); } diff --git a/src/values.hh b/src/store.hh similarity index 100% rename from src/values.hh rename to src/store.hh From 609a224848dd08cea35a89b03b64274c82f0a2a7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 7 Jul 2003 07:44:57 +0000 Subject: [PATCH 0103/6440] * Fixed `make check' as well. --- src/test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test.cc b/src/test.cc index 3851ef867..fb7900ca9 100644 --- a/src/test.cc +++ b/src/test.cc @@ -6,8 +6,8 @@ #include "hash.hh" #include "archive.hh" #include "util.hh" -#include "eval.hh" -#include "values.hh" +#include "fstate.hh" +#include "store.hh" #include "globals.hh" From 5895c160c466c0a97716ffdf5ef654eb1c3c6009 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 7 Jul 2003 09:25:26 +0000 Subject: [PATCH 0104/6440] * Make dbRefs a mapping from Hash to [Path]. --- src/Makefile.am | 2 +- src/db.cc | 57 ++++++++++++++++++++++++++++++ src/db.hh | 8 +++++ src/fix.cc | 2 +- src/fstate.cc | 8 ++--- src/shared.cc | 3 ++ src/store.cc | 93 +++++++++++++++++++++++++++++++++++++------------ src/store.hh | 9 +++-- src/test.cc | 15 +++----- src/util.cc | 17 +++++---- src/util.hh | 7 +++- 11 files changed, 172 insertions(+), 49 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 5488e133a..b22a56e3a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,7 +14,7 @@ fix_LDADD = libnix.a -ldb_cxx-4 -lATerm TESTS = test -test_SOURCES = test.cc +test_SOURCES = test.cc shared.cc test_LDADD = libnix.a -ldb_cxx-4 -lATerm noinst_LIBRARIES = libnix.a diff --git a/src/db.cc b/src/db.cc index b33591c8b..89cee32ba 100644 --- a/src/db.cc +++ b/src/db.cc @@ -73,6 +73,40 @@ bool queryDB(const string & filename, const string & dbname, } +bool queryListDB(const string & filename, const string & dbname, + const string & key, Strings & data) +{ + string d; + + if (!queryDB(filename, dbname, key, d)) + return false; + + string::iterator it = d.begin(); + + while (it != d.end()) { + + if (it + 4 > d.end()) + throw Error(format("short db entry: `%1%'") % d); + + unsigned int len; + len = (unsigned char) *it++; + len |= ((unsigned char) *it++) << 8; + len |= ((unsigned char) *it++) << 16; + len |= ((unsigned char) *it++) << 24; + + if (it + len > d.end()) + throw Error(format("short db entry: `%1%'") % d); + + string s; + while (len--) s += *it++; + + data.push_back(s); + } + + return true; +} + + void setDB(const string & filename, const string & dbname, const string & key, const string & data) { @@ -85,6 +119,29 @@ void setDB(const string & filename, const string & dbname, } +void setListDB(const string & filename, const string & dbname, + const string & key, const Strings & data) +{ + string d; + + for (Strings::const_iterator it = data.begin(); + it != data.end(); it++) + { + string s = *it; + unsigned int len = s.size(); + + d += len & 0xff; + d += (len >> 8) & 0xff; + d += (len >> 16) & 0xff; + d += (len >> 24) & 0xff; + + d += s; + } + + setDB(filename, dbname, key, d); +} + + void delDB(const string & filename, const string & dbname, const string & key) { diff --git a/src/db.hh b/src/db.hh index 0054dbec1..aee6ce9bf 100644 --- a/src/db.hh +++ b/src/db.hh @@ -4,6 +4,8 @@ #include #include +#include "util.hh" + using namespace std; typedef pair DBPair; @@ -14,9 +16,15 @@ void createDB(const string & filename, const string & dbname); bool queryDB(const string & filename, const string & dbname, const string & key, string & data); +bool queryListDB(const string & filename, const string & dbname, + const string & key, Strings & data); + void setDB(const string & filename, const string & dbname, const string & key, const string & data); +void setListDB(const string & filename, const string & dbname, + const string & key, const Strings & data); + void delDB(const string & filename, const string & dbname, const string & key); diff --git a/src/fix.cc b/src/fix.cc index fdf12ffef..eb77a4942 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -29,7 +29,7 @@ static bool isFState(Expr e, string & path) } else if (ATmatch(e, "Include()", &s1)) { - string fn = queryFromStore(parseHash(s1)); + string fn = queryPathByHash(parseHash(s1)); return isFState(evalFile(fn), path); } else return false; diff --git a/src/fstate.cc b/src/fstate.cc index 8003a1b38..2e3ffd639 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -167,7 +167,7 @@ struct RStatus static ATerm termFromHash(const Hash & hash) { - string path = queryFromStore(hash); + string path = queryPathByHash(hash); ATerm t = ATreadFromNamedFile(path.c_str()); if (!t) throw Error(format("cannot read aterm %1%") % path); return t; @@ -183,7 +183,7 @@ Hash writeTerm(ATerm t) string path2 = nixStore + "/" + (string) hash + ".nix"; if (rename(path.c_str(), path2.c_str()) == -1) throw SysError(format("renaming %1% to %2%") % path % path2); - setDB(nixDB, dbRefs, hash, path2); + registerPath(path2, hash); return hash; } @@ -259,7 +259,7 @@ static FState realise(RStatus & status, FState fs) } /* Do we know a path with that hash? If so, copy it. */ - string path2 = queryFromStore(hash); + string path2 = queryPathByHash(hash); copyPath(path2, path); return nf; @@ -318,7 +318,7 @@ static FState realise(RStatus & status, FState fs) /* Register targetHash -> targetPath. !!! this should be in values.cc. */ - setDB(nixDB, dbRefs, outHash, outPath); + registerPath(outPath, outHash); /* Register the normal form of fs. */ FState nf = ATmake("Path(, Hash(), )", diff --git a/src/shared.cc b/src/shared.cc index 6d157766e..bd165ce97 100644 --- a/src/shared.cc +++ b/src/shared.cc @@ -54,6 +54,9 @@ int main(int argc, char * * argv) "Try `%2% --help' for more information.\n") % e.what() % programId; return 1; + } catch (Error & e) { + cerr << format("error: %1%\n") % e.msg(); + return 1; } catch (exception & e) { cerr << format("error: %1%\n") % e.what(); return 1; diff --git a/src/store.cc b/src/store.cc index 68a1cd1e2..73713201d 100644 --- a/src/store.cc +++ b/src/store.cc @@ -83,25 +83,89 @@ void copyPath(string src, string dst) } +Hash registerPath(const string & _path, Hash hash) +{ + string path(canonPath(_path)); + + if (hash == Hash()) hash = hashPath(path); + + Strings paths; + queryListDB(nixDB, dbRefs, hash, paths); /* non-existence = ok */ + + for (Strings::iterator it = paths.begin(); + it != paths.end(); it++) + if (*it == path) goto exists; + + paths.push_back(path); + + setListDB(nixDB, dbRefs, hash, paths); + + exists: + return hash; +} + + +bool isInPrefix(const string & path, const string & _prefix) +{ + string prefix = canonPath(_prefix + "/"); + return string(path, 0, prefix.size()) == prefix; +} + + +static string queryPathByHashPrefix(Hash hash, const string & prefix) +{ + Strings paths; + + if (!queryListDB(nixDB, dbRefs, hash, paths)) + throw Error(format("no paths known with hash `%1%'") % (string) hash); + + /* Arbitrarily pick the first one that exists and still hash the + right hash. */ + + for (Strings::iterator it = paths.begin(); + it != paths.end(); it++) + { + debug(*it); + string path = *it; + try { + debug(path); + debug(prefix); + if (isInPrefix(path, prefix) && hashPath(path) == hash) + return path; + } catch (Error & e) { + debug(format("checking hash: %1%") % e.msg()); + /* try next one */ + } + } + + throw Error(format("all paths with hash `%1%' are stale") % (string) hash); +} + + +string queryPathByHash(Hash hash) +{ + return queryPathByHashPrefix(hash, "/"); +} + + void addToStore(string srcPath, string & dstPath, Hash & hash) { srcPath = absPath(srcPath); hash = hashPath(srcPath); - string path; - if (queryDB(nixDB, dbRefs, hash, path)) { - debug((string) hash + " already known"); - dstPath = path; + try { + dstPath = queryPathByHashPrefix(hash, nixStore); return; + } catch (...) { } - + string baseName = baseNameOf(srcPath); dstPath = nixStore + "/" + (string) hash + "-" + baseName; copyPath(srcPath, dstPath); - setDB(nixDB, dbRefs, hash, dstPath); + registerPath(dstPath, hash); } @@ -113,20 +177,3 @@ void deleteFromStore(const string & path) deletePath(path); // delDB(nixDB, dbRefs, hash); } - - -string queryFromStore(Hash hash) -{ - string fn, url; - - if (queryDB(nixDB, dbRefs, hash, fn)) { - - /* Verify that the file hasn't changed. !!! race !!! slow */ - if (hashPath(fn) != hash) - throw Error("file " + fn + " is stale"); - - return fn; - } - - throw Error(format("don't know a path with hash `%1%'") % (string) hash); -} diff --git a/src/store.hh b/src/store.hh index b96fa30ba..a83fa0304 100644 --- a/src/store.hh +++ b/src/store.hh @@ -10,6 +10,12 @@ using namespace std; void copyPath(string src, string dst); +/* Register a path keyed on its hash. */ +Hash registerPath(const string & path, Hash hash = Hash()); + +/* Query a path (any path) through its hash. */ +string queryPathByHash(Hash hash); + /* Copy a file to the nixStore directory and register it in dbRefs. Return the hash code of the value. */ void addToStore(string srcPath, string & dstPath, Hash & hash); @@ -17,8 +23,5 @@ void addToStore(string srcPath, string & dstPath, Hash & hash); /* Delete a value from the nixStore directory. */ void deleteFromStore(const string & path); -/* !!! */ -string queryFromStore(Hash hash); - #endif /* !__VALUES_H */ diff --git a/src/test.cc b/src/test.cc index fb7900ca9..b30a5b0e9 100644 --- a/src/test.cc +++ b/src/test.cc @@ -191,15 +191,10 @@ void runTests() } -int main(int argc, char * * argv) +void run(Strings args) { - ATerm bottomOfStack; - ATinit(argc, argv, &bottomOfStack); - - try { - runTests(); - } catch (exception & e) { - cerr << "error: " << e.what() << endl; - return 1; - } + runTests(); } + + +string programId = "test"; diff --git a/src/util.cc b/src/util.cc index 65ceea938..2f9c43e55 100644 --- a/src/util.cc +++ b/src/util.cc @@ -33,13 +33,18 @@ string absPath(string path, string dir) dir = buf; } path = dir + "/" + path; - /* !!! canonicalise */ - char resolved[PATH_MAX]; - if (!realpath(path.c_str(), resolved)) - throw SysError(format("cannot canonicalise path %1%") % path); - path = resolved; } - return path; + return canonPath(path); +} + + +string canonPath(const string & path) +{ + char resolved[PATH_MAX]; + if (!realpath(path.c_str(), resolved)) + throw SysError(format("cannot canonicalise path `%1%'") % path); + /* !!! check that this removes trailing slashes */ + return resolved; } diff --git a/src/util.hh b/src/util.hh index 6242fcb11..a8f801b30 100644 --- a/src/util.hh +++ b/src/util.hh @@ -21,6 +21,7 @@ public: Error(const format & f); ~Error() throw () { }; const char * what() const throw () { return err.c_str(); } + const string & msg() const throw () { return err; } }; class SysError : public Error @@ -44,9 +45,13 @@ extern string thisSystem; /* Return an absolutized path, resolving paths relative to the - specified directory, or the current directory otherwise. */ + specified directory, or the current directory otherwise. The path + is also canonicalised. */ string absPath(string path, string dir = ""); +/* Canonicalise a path (as in realpath(3)). */ +string canonPath(const string & path); + /* Return the directory part of the given path, i.e., everything before the final `/'. */ string dirOf(string path); From be96c2189ca017612277ab6301164a5e2facfca5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 7 Jul 2003 09:29:40 +0000 Subject: [PATCH 0105/6440] * `--realise' -> `--install'. --- src/nix.cc | 10 +++++----- src/store.cc | 3 --- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index dc9d04148..0cdce2adc 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -21,7 +21,7 @@ static ArgType argType = atpUnknown; Operations: - --realise / -r: realise values + --install / -i: install (or `realise') values --delete / -d: delete values --query / -q: query stored values --add: add values @@ -86,8 +86,8 @@ static void getArgType(Strings & flags) } -/* Realise values. */ -static void opRealise(Strings opFlags, Strings opArgs) +/* Install values. */ +static void opInstall(Strings opFlags, Strings opArgs) { getArgType(opFlags); if (!opFlags.empty()) throw UsageError("unknown flag"); @@ -238,8 +238,8 @@ void run(Strings args) Operation oldOp = op; - if (arg == "--realise" || arg == "-r") - op = opRealise; + if (arg == "--install" || arg == "-i") + op = opInstall; else if (arg == "--delete" || arg == "-d") op = opDelete; else if (arg == "--add") diff --git a/src/store.cc b/src/store.cc index 73713201d..977cd2594 100644 --- a/src/store.cc +++ b/src/store.cc @@ -125,11 +125,8 @@ static string queryPathByHashPrefix(Hash hash, const string & prefix) for (Strings::iterator it = paths.begin(); it != paths.end(); it++) { - debug(*it); string path = *it; try { - debug(path); - debug(prefix); if (isInPrefix(path, prefix) && hashPath(path) == hash) return path; } catch (Error & e) { From a5a90f501e471383a8dfccfe8af3c804cefa77cf Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Jul 2003 08:35:06 +0000 Subject: [PATCH 0106/6440] * Get rid of the `netsources' database. * Rename the `refs' database to `hash2paths'. --- src/globals.cc | 6 ++---- src/globals.hh | 19 ++++--------------- src/store.cc | 8 ++++---- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/globals.cc b/src/globals.cc index a81c90caa..40a5a981b 100644 --- a/src/globals.cc +++ b/src/globals.cc @@ -2,9 +2,8 @@ #include "db.hh" -string dbRefs = "refs"; +string dbHash2Paths = "hash2paths"; string dbSuccessors = "successors"; -string dbNetSources = "netsources"; string nixStore = "/UNINIT"; string nixLogDir = "/UNINIT"; @@ -13,7 +12,6 @@ string nixDB = "/UNINIT"; void initDB() { - createDB(nixDB, dbRefs); + createDB(nixDB, dbHash2Paths); createDB(nixDB, dbSuccessors); - createDB(nixDB, dbNetSources); } diff --git a/src/globals.hh b/src/globals.hh index 8c6a763c4..2fb9fe747 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -8,11 +8,11 @@ using namespace std; /* Database names. */ -/* dbRefs :: Hash -> Path +/* dbHash2Paths :: Hash -> [Path] - Maintains a mapping from hashes to paths. This is what we use to - resolve CHash(hash) content descriptors. */ -extern string dbRefs; + Maintains a mapping from hashes to lists of paths. This is what we + use to resolve Hash(hash) content descriptors. */ +extern string dbHash2Paths; /* dbSuccessors :: Hash -> Hash @@ -30,17 +30,6 @@ extern string dbRefs; */ extern string dbSuccessors; -/* dbNetSources :: Hash -> URL - - Each pair (hash, url) in this mapping states that the value - identified by hash can be obtained by fetching the value pointed - to by url. - - TODO: this should be Hash -> [URL] - - TODO: factor this out into a separate tool? */ -extern string dbNetSources; - /* Path names. */ diff --git a/src/store.cc b/src/store.cc index 977cd2594..2ea7586e9 100644 --- a/src/store.cc +++ b/src/store.cc @@ -90,7 +90,7 @@ Hash registerPath(const string & _path, Hash hash) if (hash == Hash()) hash = hashPath(path); Strings paths; - queryListDB(nixDB, dbRefs, hash, paths); /* non-existence = ok */ + queryListDB(nixDB, dbHash2Paths, hash, paths); /* non-existence = ok */ for (Strings::iterator it = paths.begin(); it != paths.end(); it++) @@ -98,7 +98,7 @@ Hash registerPath(const string & _path, Hash hash) paths.push_back(path); - setListDB(nixDB, dbRefs, hash, paths); + setListDB(nixDB, dbHash2Paths, hash, paths); exists: return hash; @@ -116,7 +116,7 @@ static string queryPathByHashPrefix(Hash hash, const string & prefix) { Strings paths; - if (!queryListDB(nixDB, dbRefs, hash, paths)) + if (!queryListDB(nixDB, dbHash2Paths, hash, paths)) throw Error(format("no paths known with hash `%1%'") % (string) hash); /* Arbitrarily pick the first one that exists and still hash the @@ -172,5 +172,5 @@ void deleteFromStore(const string & path) if (string(path, 0, prefix.size()) != prefix) throw Error(format("path %1% is not in the store") % path); deletePath(path); -// delDB(nixDB, dbRefs, hash); +// delDB(nixDB, dbHash2Paths, hash); } From ab644ad10b00a5fd23e8d8a705a7a8a8aaf53c57 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Jul 2003 09:53:46 +0000 Subject: [PATCH 0107/6440] * BaseName() primitive for the generation of more sensible names (especially in fetchurl.fix). --- src/fix.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/fix.cc b/src/fix.cc index eb77a4942..0797362fd 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -208,6 +208,14 @@ static Expr evalExpr(Expr e) return ATmake("Include()", ((string) eHash).c_str()); } + /* BaseName primitive function. */ + if (ATmatch(e, "BaseName()", &e1)) { + e1 = evalExpr(e1); + if (!ATmatch(e1, "", &s1)) + throw badTerm("string expected", e1); + return ATmake("", baseNameOf(s1).c_str()); + } + /* Barf. */ throw badTerm("invalid expression", e); } From 0b38b43bab28dd733e057d42853d57e44ec9a7c9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Jul 2003 09:54:47 +0000 Subject: [PATCH 0108/6440] * deletePath() now removes the path from the hash2paths mapping. --- src/store.cc | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/store.cc b/src/store.cc index 2ea7586e9..bb945e037 100644 --- a/src/store.cc +++ b/src/store.cc @@ -105,6 +105,25 @@ Hash registerPath(const string & _path, Hash hash) } +void unregisterPath(const string & _path) +{ + string path(canonPath(_path)); + + Hash hash = hashPath(path); + + Strings paths, paths2; + queryListDB(nixDB, dbHash2Paths, hash, paths); /* non-existence = ok */ + + bool changed = false; + for (Strings::iterator it = paths.begin(); + it != paths.end(); it++) + if (*it != path) paths2.push_back(*it); else changed = true; + + if (changed) + setListDB(nixDB, dbHash2Paths, hash, paths2); +} + + bool isInPrefix(const string & path, const string & _prefix) { string prefix = canonPath(_prefix + "/"); @@ -171,6 +190,8 @@ void deleteFromStore(const string & path) string prefix = nixStore + "/"; if (string(path, 0, prefix.size()) != prefix) throw Error(format("path %1% is not in the store") % path); + + unregisterPath(path); + deletePath(path); -// delDB(nixDB, dbHash2Paths, hash); } From 85a913a3e78e43f7f90ef46ac041350bb5d61d1f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Jul 2003 09:59:00 +0000 Subject: [PATCH 0109/6440] * Renamed `id' -> `name' to remove the implication of uniqueness. --- src/fix.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index 0797362fd..87ce7c775 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -156,7 +156,7 @@ static Expr evalExpr(Expr e) /* Gather information for building the Derive expression. */ ATermList ins = ATempty, env = ATempty; - string builder, id; + string builder, name; bnds = ATempty; for (map::iterator it = bndMap.begin(); @@ -173,7 +173,7 @@ static Expr evalExpr(Expr e) if (key == "build") builder = path; } else if (ATmatch(value, "", &s1)) { - if (key == "id") id = s1; + if (key == "name") name = s1; env = ATinsert(env, ATmake("(, )", key.c_str(), s1)); } @@ -191,10 +191,10 @@ static Expr evalExpr(Expr e) if (builder == "") throw badTerm("no builder specified", nf); - if (id == "") - throw badTerm("no package identifier specified", nf); + if (name == "") + throw badTerm("no package name specified", nf); - string out = nixStore + "/" + ((string) hash).c_str() + "-" + id; + string out = nixStore + "/" + ((string) hash).c_str() + "-" + name; env = ATinsert(env, ATmake("(, )", "out", out.c_str())); From a279137327ad5762bb26a23ce8ed7863812254ae Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Jul 2003 10:00:46 +0000 Subject: [PATCH 0110/6440] * Get `--dump' and `--delete' to work again. --- src/nix.cc | 68 ++++++++++++++---------------------------------------- 1 file changed, 17 insertions(+), 51 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index 0cdce2adc..0e5154572 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -10,7 +10,7 @@ typedef void (* Operation) (Strings opFlags, Strings opArgs); -typedef enum { atpHash, atpName, atpPath, atpUnknown } ArgType; +typedef enum { atpHash, atpPath, atpUnknown } ArgType; static ArgType argType = atpUnknown; @@ -21,13 +21,12 @@ static ArgType argType = atpUnknown; Operations: - --install / -i: install (or `realise') values - --delete / -d: delete values - --query / -q: query stored values - --add: add values + --install / -i: realise a Nix expression + --delete / -d: delete paths from the Nix store + --add / -A: copy a path to the Nix store - --dump: dump a value as a Nix archive - --restore: restore a value from a Nix archive + --dump: dump a path as a Nix archive + --restore: restore a path from a Nix archive --init: initialise the Nix database --verify: verify Nix structures @@ -35,21 +34,10 @@ static ArgType argType = atpUnknown; --version: output version information --help: display help - Source selection for operations that work on values: + Source selection for --install, --dump: --file / -f: by file name --hash / -h: by hash - --name / -n: by symbolic name - - Query suboptions: - - Selection: - - --all / -a: query all stored values, otherwise given values - - Information: - - --info / -i: general value information Options: @@ -57,8 +45,8 @@ static ArgType argType = atpUnknown; */ -/* Parse the `-f' / `-h' / `-n' flags, i.e., the type of value - arguments. These flags are deleted from the referenced vector. */ +/* Parse the `-f' / `-h' / flags, i.e., the type of arguments. These + flags are deleted from the referenced vector. */ static void getArgType(Strings & flags) { for (Strings::iterator it = flags.begin(); @@ -68,14 +56,9 @@ static void getArgType(Strings & flags) ArgType tp; if (arg == "--hash" || arg == "-h") tp = atpHash; - else if (arg == "--name" || arg == "-n") - tp = atpName; else if (arg == "--file" || arg == "-f") tp = atpPath; - else { - it++; - continue; - } + else { it++; continue; } if (argType != atpUnknown) throw UsageError("only one argument type specified may be specified"); argType = tp; @@ -86,7 +69,8 @@ static void getArgType(Strings & flags) } -/* Install values. */ +/* Realise (or install) paths from the given Nix fstate + expressions. */ static void opInstall(Strings opFlags, Strings opArgs) { getArgType(opFlags); @@ -98,8 +82,6 @@ static void opInstall(Strings opFlags, Strings opArgs) Hash hash; if (argType == atpHash) hash = parseHash(*it); - else if (argType == atpName) - throw Error("not implemented"); else if (argType == atpPath) { string path; addToStore(*it, path, hash); @@ -112,28 +94,16 @@ static void opInstall(Strings opFlags, Strings opArgs) static void opDelete(Strings opFlags, Strings opArgs) { -#if 0 - getArgType(opFlags); if (!opFlags.empty()) throw UsageError("unknown flag"); for (Strings::iterator it = opArgs.begin(); it != opArgs.end(); it++) - { - Hash hash; - if (argType == atpHash) - hash = parseHash(*it); - else if (argType == atpName) - throw Error("not implemented"); - else - throw Error("invalid argument type"); - deleteValue(hash); - } -#endif + deleteFromStore(absPath(*it)); } -/* Add values to the Nix values directory and print the hashes of - those values. */ +/* Add paths to the Nix values directory and print the hashes of those + paths. */ static void opAdd(Strings opFlags, Strings opArgs) { getArgType(opFlags); @@ -162,11 +132,10 @@ struct StdoutSink : DumpSink }; -/* Dump a value as a Nix archive. The archive is written to standard +/* Dump a path as a Nix archive. The archive is written to standard output. */ static void opDump(Strings opFlags, Strings opArgs) { -#if 0 getArgType(opFlags); if (!opFlags.empty()) throw UsageError("unknown flag"); if (opArgs.size() != 1) throw UsageError("only one argument allowed"); @@ -176,14 +145,11 @@ static void opDump(Strings opFlags, Strings opArgs) string path; if (argType == atpHash) - path = queryValuePath(parseHash(arg)); - else if (argType == atpName) - throw Error("not implemented"); + path = queryPathByHash(parseHash(arg)); else if (argType == atpPath) path = arg; dumpPath(path, sink); -#endif } From 40274c1f4f763e634dd031f7a6b4ba8ce2de7a82 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Jul 2003 13:22:08 +0000 Subject: [PATCH 0111/6440] * A command to query the paths referenced by an fstate expression. * Use a temporary directory for build actions. --- src/fix.cc | 30 +--------- src/fstate.cc | 148 ++++++++++++++++++++++++++++++++------------------ src/fstate.hh | 13 ++++- src/nix.cc | 90 ++++++++++++++++++++++++------ src/util.cc | 12 ++++ src/util.hh | 2 + 6 files changed, 198 insertions(+), 97 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index 87ce7c775..508a44116 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -13,29 +13,6 @@ typedef ATerm Expr; static Expr evalFile(string fileName); -static bool isFState(Expr e, string & path) -{ - char * s1, * s2, * s3; - Expr e1, e2; - if (ATmatch(e, "Path(, , [])", &s1, &e1, &e2)) { - path = s1; - return true; - } - else if (ATmatch(e, "Derive(, , [], , [])", - &s1, &s2, &e1, &s3, &e2)) - { - path = s3; - return true; - } - else if (ATmatch(e, "Include()", &s1)) - { - string fn = queryPathByHash(parseHash(s1)); - return isFState(evalFile(fn), path); - } - else return false; -} - - static Expr substExpr(string x, Expr rep, Expr e) { char * s; @@ -113,8 +90,7 @@ static Expr evalExpr(Expr e) ATmatch(e, "Function([], )", &e1, &e2)) return e; - string dummy; - if (isFState(e, dummy)) return e; + if (fstatePath(e) != "") return e; /* !!! hack */ /* Application. */ if (ATmatch(e, "App(, [])", &e1, &e2)) { @@ -165,8 +141,8 @@ static Expr evalExpr(Expr e) string key = it->first; ATerm value = it->second; - string path; - if (isFState(value, path)) { + string path = fstatePath(value); + if (path != "") { ins = ATinsert(ins, value); env = ATinsert(env, ATmake("(, )", key.c_str(), path.c_str())); diff --git a/src/fstate.cc b/src/fstate.cc index 2e3ffd639..fa677a257 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -1,4 +1,5 @@ #include +#include #include #include @@ -17,17 +18,20 @@ typedef map Environment; -/* Return true iff the given path exists. */ -bool pathExists(const string & path) +class AutoDelete { - int res; - struct stat st; - res = stat(path.c_str(), &st); - if (!res) return true; - if (errno != ENOENT) - throw SysError(format("getting status of %1%") % path); - return false; -} + string path; +public: + + AutoDelete(const string & p) : path(p) + { + } + + ~AutoDelete() + { + deletePath(path); + } +}; /* Run a program. */ @@ -36,9 +40,19 @@ static void runProgram(const string & program, Environment env) /* Create a log file. */ string logFileName = nixLogDir + "/run.log"; /* !!! auto-pclose on exit */ - FILE * logFile = popen(("tee " + logFileName + " >&2").c_str(), "w"); /* !!! escaping */ + FILE * logFile = popen(("tee -a " + logFileName + " >&2").c_str(), "w"); /* !!! escaping */ if (!logFile) - throw SysError(format("unable to create log file %1%") % logFileName); + throw SysError(format("creating log file `%1%'") % logFileName); + + /* Create a temporary directory where the build will take + place. */ + static int counter = 0; + string tmpDir = (format("/tmp/nix-%1%-%2%") % getpid() % counter++).str(); + + if (mkdir(tmpDir.c_str(), 0777) == -1) + throw SysError(format("creating directory `%1%'") % tmpDir); + + AutoDelete delTmpDir(tmpDir); /* Fork a child to build the package. */ pid_t pid; @@ -51,31 +65,8 @@ static void runProgram(const string & program, Environment env) try { /* child */ -#if 0 - /* Try to use a prebuilt. */ - string prebuiltHashS, prebuiltFile; - if (queryDB(nixDB, dbPrebuilts, hash, prebuiltHashS)) { - - try { - prebuiltFile = getFile(parseHash(prebuiltHashS)); - } catch (Error e) { - cerr << "cannot obtain prebuilt (ignoring): " << e.what() << endl; - goto build; - } - - cerr << "substituting prebuilt " << prebuiltFile << endl; - - int res = system(("tar xfj " + prebuiltFile + " 1>&2").c_str()); // !!! escaping - if (WEXITSTATUS(res) != 0) - /* This is a fatal error, because path may now - have clobbered. */ - throw Error("cannot unpack " + prebuiltFile); - - _exit(0); - } -#endif - - // build: + if (chdir(tmpDir.c_str()) == -1) + throw SysError(format("changing into to `%1%'") % tmpDir); /* Fill in the environment. We don't bother freeing the strings, since we'll exec or die soon @@ -157,15 +148,7 @@ Hash hashTerm(ATerm t) } -struct RStatus -{ - /* !!! the comparator of this hash should match the semantics of - the file system */ -// map paths; -}; - - -static ATerm termFromHash(const Hash & hash) +ATerm termFromHash(const Hash & hash) { string path = queryPathByHash(hash); ATerm t = ATreadFromNamedFile(path.c_str()); @@ -188,7 +171,7 @@ Hash writeTerm(ATerm t) } -static FState realise(RStatus & status, FState fs) +static FState realise(FState fs) { char * s1, * s2, * s3; Content content; @@ -212,7 +195,7 @@ static FState realise(RStatus & status, FState fs) /* Fall through. */ if (ATmatch(fs, "Include()", &s1)) { - return realise(status, termFromHash(parseHash(s1))); + return realise(termFromHash(parseHash(s1))); } else if (ATmatch(fs, "Path(, , [])", &s1, &content, &refs)) { @@ -227,7 +210,7 @@ static FState realise(RStatus & status, FState fs) /* Realise referenced paths. */ ATermList refs2 = ATempty; while (!ATisEmpty(refs)) { - refs2 = ATinsert(refs2, realise(status, ATgetFirst(refs))); + refs2 = ATinsert(refs2, realise(ATgetFirst(refs))); refs = ATgetNext(refs); } refs2 = ATreverse(refs2); @@ -278,7 +261,7 @@ static FState realise(RStatus & status, FState fs) /* Realise inputs. */ ATermList ins2 = ATempty; while (!ATisEmpty(ins)) { - ins2 = ATinsert(ins2, realise(status, ATgetFirst(ins))); + ins2 = ATinsert(ins2, realise(ATgetFirst(ins))); ins = ATgetNext(ins); } ins2 = ATreverse(ins2); @@ -335,6 +318,67 @@ static FState realise(RStatus & status, FState fs) FState realiseFState(FState fs) { - RStatus status; - return realise(status, fs); + return realise(fs); +} + + +string fstatePath(FState fs) +{ + char * s1, * s2, * s3; + FState e1, e2; + if (ATmatch(fs, "Path(, , [])", &s1, &e1, &e2)) + return s1; + else if (ATmatch(fs, "Derive(, , [], , [])", + &s1, &s2, &e1, &s3, &e2)) + return s3; + else if (ATmatch(fs, "Include()", &s1)) + return fstatePath(termFromHash(parseHash(s1))); + else + return ""; +} + + +typedef set StringSet; + + +void fstateRefs2(FState fs, StringSet & paths) +{ + char * s1, * s2, * s3; + FState e1, e2; + ATermList refs, ins; + + if (ATmatch(fs, "Path(, , [])", &s1, &e1, &refs)) { + paths.insert(s1); + + while (!ATisEmpty(refs)) { + fstateRefs2(ATgetFirst(refs), paths); + refs = ATgetNext(refs); + } + } + + else if (ATmatch(fs, "Derive(, , [], , [])", + &s1, &s2, &ins, &s3, &e2)) + { + paths.insert(s3); + + while (!ATisEmpty(ins)) { + fstateRefs2(ATgetFirst(ins), paths); + ins = ATgetNext(ins); + } + } + + else if (ATmatch(fs, "Include()", &s1)) + fstateRefs2(termFromHash(parseHash(s1)), paths); + + else throw badTerm("bad fstate expression", fs); +} + + +Strings fstateRefs(FState fs) +{ + StringSet paths; + fstateRefs2(fs, paths); + Strings paths2(paths.size()); + copy(paths.begin(), paths.end(), paths2.begin()); + return paths2; } diff --git a/src/fstate.hh b/src/fstate.hh index b04588e7b..de6303dca 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -60,9 +60,17 @@ typedef ATerm FState; typedef ATerm Content; -/* Realise a $f$-normalised expression in the file system. */ +/* Realise an fstate expression in the file system. This requires + execution of all Derive() nodes. */ FState realiseFState(FState fs); +/* Return the path of an fstate expression. An empty string is + returned if the term is not a valid fstate expression. (!!!) */ +string fstatePath(FState fs); + +/* Return the paths referenced by fstate expression. */ +Strings fstateRefs(FState fs); + /* Return a canonical textual representation of an expression. */ string printTerm(ATerm t); @@ -73,6 +81,9 @@ Error badTerm(const format & f, ATerm t); /* Hash an aterm. */ Hash hashTerm(ATerm t); +/* Read an aterm from disk, given its hash. */ +ATerm termFromHash(const Hash & hash); + /* Write an aterm to the Nix store directory, and return its hash. */ Hash writeTerm(ATerm t); diff --git a/src/nix.cc b/src/nix.cc index 0e5154572..ec4c91788 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -21,9 +21,10 @@ static ArgType argType = atpUnknown; Operations: - --install / -i: realise a Nix expression + --install / -i: realise an fstate --delete / -d: delete paths from the Nix store --add / -A: copy a path to the Nix store + --query / -q: query information --dump: dump a path as a Nix archive --restore: restore a path from a Nix archive @@ -39,6 +40,11 @@ static ArgType argType = atpUnknown; --file / -f: by file name --hash / -h: by hash + Query flags: + + --path / -p: query the path of an fstate + --refs / -r: query paths referenced by an fstate + Options: --verbose / -v: verbose operation @@ -54,10 +60,8 @@ static void getArgType(Strings & flags) { string arg = *it; ArgType tp; - if (arg == "--hash" || arg == "-h") - tp = atpHash; - else if (arg == "--file" || arg == "-f") - tp = atpPath; + if (arg == "--hash" || arg == "-h") tp = atpHash; + else if (arg == "--file" || arg == "-f") tp = atpPath; else { it++; continue; } if (argType != atpUnknown) throw UsageError("only one argument type specified may be specified"); @@ -69,6 +73,20 @@ static void getArgType(Strings & flags) } +static Hash argToHash(const string & arg) +{ + if (argType == atpHash) + return parseHash(arg); + else if (argType == atpPath) { + string path; + Hash hash; + addToStore(arg, path, hash); + return hash; + } + else abort(); +} + + /* Realise (or install) paths from the given Nix fstate expressions. */ static void opInstall(Strings opFlags, Strings opArgs) @@ -78,20 +96,11 @@ static void opInstall(Strings opFlags, Strings opArgs) for (Strings::iterator it = opArgs.begin(); it != opArgs.end(); it++) - { - Hash hash; - if (argType == atpHash) - hash = parseHash(*it); - else if (argType == atpPath) { - string path; - addToStore(*it, path, hash); - } - FState fs = ATmake("Include()", ((string) hash).c_str()); - realiseFState(fs); - } + realiseFState(termFromHash(argToHash(*it))); } +/* Delete a path in the Nix store directory. */ static void opDelete(Strings opFlags, Strings opArgs) { if (!opFlags.empty()) throw UsageError("unknown flag"); @@ -120,6 +129,51 @@ static void opAdd(Strings opFlags, Strings opArgs) } +/* Perform various sorts of queries. */ +static void opQuery(Strings opFlags, Strings opArgs) +{ + enum { qPath, qRefs, qUnknown } query = qPath; + + for (Strings::iterator it = opFlags.begin(); + it != opFlags.end(); ) + { + string arg = *it; + if (arg == "--path" || arg == "-p") query = qPath; + else if (arg == "--refs" || arg == "-r") query = qRefs; + else { it++; continue; } + it = opFlags.erase(it); + } + + getArgType(opFlags); + if (!opFlags.empty()) throw UsageError("unknown flag"); + + for (Strings::iterator it = opArgs.begin(); + it != opArgs.end(); it++) + { + Hash hash = argToHash(*it); + + switch (query) { + + case qPath: + cout << format("%s\n") % + (string) fstatePath(termFromHash(hash)); + break; + + case qRefs: { + Strings refs = fstateRefs(termFromHash(hash)); + for (Strings::iterator j = refs.begin(); + j != refs.end(); j++) + cout << format("%s\n") % *j; + break; + } + + default: + abort(); + } + } +} + + /* A sink that writes dump output to stdout. */ struct StdoutSink : DumpSink { @@ -208,8 +262,10 @@ void run(Strings args) op = opInstall; else if (arg == "--delete" || arg == "-d") op = opDelete; - else if (arg == "--add") + else if (arg == "--add" || arg == "-A") op = opAdd; + else if (arg == "--query" || arg == "-q") + op = opQuery; else if (arg == "--dump") op = opDump; else if (arg == "--restore") diff --git a/src/util.cc b/src/util.cc index 2f9c43e55..8ccd3c152 100644 --- a/src/util.cc +++ b/src/util.cc @@ -66,6 +66,18 @@ string baseNameOf(string path) } +bool pathExists(const string & path) +{ + int res; + struct stat st; + res = stat(path.c_str(), &st); + if (!res) return true; + if (errno != ENOENT) + throw SysError(format("getting status of %1%") % path); + return false; +} + + void deletePath(string path) { struct stat st; diff --git a/src/util.hh b/src/util.hh index a8f801b30..684bafbb5 100644 --- a/src/util.hh +++ b/src/util.hh @@ -60,6 +60,8 @@ string dirOf(string path); the final `/'. */ string baseNameOf(string path); +/* Return true iff the given path exists. */ +bool pathExists(const string & path); /* Delete a path; i.e., in the case of a directory, it is deleted recursively. Don't use this at home, kids. */ From 333f4963de6d174d852774a88ada852f77f57994 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Jul 2003 15:33:06 +0000 Subject: [PATCH 0112/6440] * The output of a Derive() node is not a referenced path. --- src/fstate.cc | 2 -- src/nix.cc | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/fstate.cc b/src/fstate.cc index fa677a257..731493fd3 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -359,8 +359,6 @@ void fstateRefs2(FState fs, StringSet & paths) else if (ATmatch(fs, "Derive(, , [], , [])", &s1, &s2, &ins, &s3, &e2)) { - paths.insert(s3); - while (!ATisEmpty(ins)) { fstateRefs2(ATgetFirst(ins), paths); ins = ATgetNext(ins); diff --git a/src/nix.cc b/src/nix.cc index ec4c91788..f3549ead8 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -160,7 +160,7 @@ static void opQuery(Strings opFlags, Strings opArgs) break; case qRefs: { - Strings refs = fstateRefs(termFromHash(hash)); + Strings refs = fstateRefs(realiseFState(termFromHash(hash))); for (Strings::iterator j = refs.begin(); j != refs.end(); j++) cout << format("%s\n") % *j; From cab3f4977a412681a77767ec7307ee642b61332d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Jul 2003 19:58:41 +0000 Subject: [PATCH 0113/6440] * A path canonicaliser that doesn't depend on the existence of paths (i.e., it doesn't use realpath(3), which is broken in any case). Therefore it doesn't resolve symlinks. --- src/test.cc | 9 +++++++++ src/util.cc | 38 +++++++++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/test.cc b/src/test.cc index b30a5b0e9..c2a1cd3bf 100644 --- a/src/test.cc +++ b/src/test.cc @@ -71,6 +71,15 @@ void runTests() abort(); } catch (BadRefError err) { }; + /* Path canonicalisation. */ + cout << canonPath("/./../././//") << endl; + cout << canonPath("/foo/bar") << endl; + cout << canonPath("///foo/////bar//") << endl; + cout << canonPath("/././/foo/////bar//.") << endl; + cout << canonPath("/foo////bar//..///x/") << endl; + cout << canonPath("/foo////bar//..//..//x/y/../z/") << endl; + cout << canonPath("/foo/bar/../../../..///") << endl; + /* Dumping. */ #if 0 diff --git a/src/util.cc b/src/util.cc index 8ccd3c152..00a3063d6 100644 --- a/src/util.cc +++ b/src/util.cc @@ -40,11 +40,39 @@ string absPath(string path, string dir) string canonPath(const string & path) { - char resolved[PATH_MAX]; - if (!realpath(path.c_str(), resolved)) - throw SysError(format("cannot canonicalise path `%1%'") % path); - /* !!! check that this removes trailing slashes */ - return resolved; + string s; + + if (path[0] != '/') + throw Error(format("not an absolute path: `%1%'") % path); + + string::const_iterator i = path.begin(), end = path.end(); + + while (1) { + + /* Skip slashes. */ + while (i != end && *i == '/') i++; + if (i == end) break; + + /* Ignore `.'. */ + if (*i == '.' && (i + 1 == end || i[1] == '/')) + i++; + + /* If `..', delete the last component. */ + else if (*i == '.' && i + 1 < end && i[1] == '.' && + (i + 2 == end || i[2] == '/')) + { + if (!s.empty()) s.erase(s.rfind('/')); + i += 2; + } + + /* Normal component; copy it. */ + else { + s += '/'; + while (i != end && *i != '/') s += *i++; + } + } + + return s.empty() ? "/" : s; } From 9a99dc736d814f41d2b3ceb92da2435ae2dd5632 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Jul 2003 20:26:22 +0000 Subject: [PATCH 0114/6440] * Canonicalise paths so that Fix produces identical Nix expressions for identical inputs. --- src/fix.cc | 3 ++- src/fstate.cc | 2 +- src/store.cc | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index 508a44116..b4626f71f 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -170,7 +170,8 @@ static Expr evalExpr(Expr e) if (name == "") throw badTerm("no package name specified", nf); - string out = nixStore + "/" + ((string) hash).c_str() + "-" + name; + string out = + canonPath(nixStore + "/" + ((string) hash).c_str() + "-" + name); env = ATinsert(env, ATmake("(, )", "out", out.c_str())); diff --git a/src/fstate.cc b/src/fstate.cc index 731493fd3..344197748 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -163,7 +163,7 @@ Hash writeTerm(ATerm t) if (!ATwriteToNamedTextFile(t, path.c_str())) throw Error(format("cannot write aterm %1%") % path); Hash hash = hashPath(path); - string path2 = nixStore + "/" + (string) hash + ".nix"; + string path2 = canonPath(nixStore + "/" + (string) hash + ".nix"); if (rename(path.c_str(), path2.c_str()) == -1) throw SysError(format("renaming %1% to %2%") % path % path2); registerPath(path2, hash); diff --git a/src/store.cc b/src/store.cc index bb945e037..095d20430 100644 --- a/src/store.cc +++ b/src/store.cc @@ -177,7 +177,7 @@ void addToStore(string srcPath, string & dstPath, Hash & hash) } string baseName = baseNameOf(srcPath); - dstPath = nixStore + "/" + (string) hash + "-" + baseName; + dstPath = canonPath(nixStore + "/" + (string) hash + "-" + baseName); copyPath(srcPath, dstPath); From 2b95a9dc05d0a943859ba92bb301c294473758f1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 9 Jul 2003 15:02:03 +0000 Subject: [PATCH 0115/6440] * When computing the set of paths referenced by an expression, also include the paths of the subterms. --- src/fix.cc | 2 +- src/fstate.cc | 87 ++++++++++++++++++++++++++++++--------------------- src/fstate.hh | 12 ++++--- src/nix.cc | 11 +++++-- 4 files changed, 68 insertions(+), 44 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index b4626f71f..d17e7b550 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -180,7 +180,7 @@ static Expr evalExpr(Expr e) SYSTEM, builder.c_str(), ins, out.c_str(), env); /* Write the resulting term into the Nix store directory. */ - Hash eHash = writeTerm(e); + Hash eHash = writeTerm(e, "-d-" + name); return ATmake("Include()", ((string) eHash).c_str()); } diff --git a/src/fstate.cc b/src/fstate.cc index 344197748..2f0e50fb2 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -1,5 +1,4 @@ #include -#include #include #include @@ -148,30 +147,50 @@ Hash hashTerm(ATerm t) } -ATerm termFromHash(const Hash & hash) +ATerm termFromHash(const Hash & hash, string * p) { string path = queryPathByHash(hash); + if (p) *p = path; ATerm t = ATreadFromNamedFile(path.c_str()); if (!t) throw Error(format("cannot read aterm %1%") % path); return t; } -Hash writeTerm(ATerm t) +Hash writeTerm(ATerm t, const string & suffix, string * p) { string path = nixStore + "/tmp.nix"; /* !!! */ if (!ATwriteToNamedTextFile(t, path.c_str())) throw Error(format("cannot write aterm %1%") % path); Hash hash = hashPath(path); - string path2 = canonPath(nixStore + "/" + (string) hash + ".nix"); + string path2 = canonPath(nixStore + "/" + + (string) hash + suffix + ".nix"); if (rename(path.c_str(), path2.c_str()) == -1) throw SysError(format("renaming %1% to %2%") % path % path2); registerPath(path2, hash); + if (p) *p = path2; return hash; } -static FState realise(FState fs) +FState storeSuccessor(FState fs, FState sc, StringSet & paths) +{ + if (fs == sc) return sc; + + string path; + Hash fsHash = hashTerm(fs); + Hash scHash = writeTerm(sc, "-s-" + (string) fsHash, &path); + setDB(nixDB, dbSuccessors, fsHash, scHash); + paths.insert(path); + +#if 0 + return ATmake("Include()", ((string) scHash).c_str()); +#endif + return sc; +} + + +static FState realise(FState fs, StringSet & paths) { char * s1, * s2, * s3; Content content; @@ -183,7 +202,9 @@ static FState realise(FState fs) string fsHash, scHash; while (queryDB(nixDB, dbSuccessors, fsHash = hashTerm(fs), scHash)) { debug(format("successor %1% -> %2%") % (string) fsHash % scHash); - FState fs2 = termFromHash(parseHash(scHash)); + string path; + FState fs2 = termFromHash(parseHash(scHash), &path); + paths.insert(path); if (fs == fs2) { debug(format("successor cycle detected in %1%") % printTerm(fs)); break; @@ -195,7 +216,10 @@ static FState realise(FState fs) /* Fall through. */ if (ATmatch(fs, "Include()", &s1)) { - return realise(termFromHash(parseHash(s1))); + string path; + fs = termFromHash(parseHash(s1), &path); + paths.insert(path); + return realise(fs, paths); } else if (ATmatch(fs, "Path(, , [])", &s1, &content, &refs)) { @@ -210,7 +234,7 @@ static FState realise(FState fs) /* Realise referenced paths. */ ATermList refs2 = ATempty; while (!ATisEmpty(refs)) { - refs2 = ATinsert(refs2, realise(ATgetFirst(refs))); + refs2 = ATinsert(refs2, realise(ATgetFirst(refs), paths)); refs = ATgetNext(refs); } refs2 = ATreverse(refs2); @@ -224,27 +248,26 @@ static FState realise(FState fs) path.c_str(), content, refs2); /* Register the normal form. */ - if (fs != nf) { - Hash nfHash = writeTerm(nf); - setDB(nixDB, dbSuccessors, hashTerm(fs), nfHash); - } - + nf = storeSuccessor(fs, nf, paths); + /* Perhaps the path already exists and has the right hash? */ if (pathExists(path)) { - if (hash == hashPath(path)) { - debug(format("path %1% already has hash %2%") + + if (hash != hashPath(path)) + throw Error(format("path %1% exists, but does not have hash %2%") % path % (string) hash); - return nf; - } - throw Error(format("path %1% exists, but does not have hash %2%") + debug(format("path %1% already has hash %2%") % path % (string) hash); + + } else { + + /* Do we know a path with that hash? If so, copy it. */ + string path2 = queryPathByHash(hash); + copyPath(path2, path); + } - - /* Do we know a path with that hash? If so, copy it. */ - string path2 = queryPathByHash(hash); - copyPath(path2, path); - + return nf; } @@ -261,7 +284,7 @@ static FState realise(FState fs) /* Realise inputs. */ ATermList ins2 = ATempty; while (!ATisEmpty(ins)) { - ins2 = ATinsert(ins2, realise(ATgetFirst(ins))); + ins2 = ATinsert(ins2, realise(ATgetFirst(ins), paths)); ins = ATgetNext(ins); } ins2 = ATreverse(ins2); @@ -306,8 +329,7 @@ static FState realise(FState fs) /* Register the normal form of fs. */ FState nf = ATmake("Path(, Hash(), )", outPath.c_str(), ((string) outHash).c_str(), ins2); - Hash nfHash = writeTerm(nf); - setDB(nixDB, dbSuccessors, hashTerm(fs), nfHash); + nf = storeSuccessor(fs, nf, paths); return nf; } @@ -316,9 +338,9 @@ static FState realise(FState fs) } -FState realiseFState(FState fs) +FState realiseFState(FState fs, StringSet & paths) { - return realise(fs); + return realise(fs, paths); } @@ -338,9 +360,6 @@ string fstatePath(FState fs) } -typedef set StringSet; - - void fstateRefs2(FState fs, StringSet & paths) { char * s1, * s2, * s3; @@ -372,11 +391,7 @@ void fstateRefs2(FState fs, StringSet & paths) } -Strings fstateRefs(FState fs) +void fstateRefs(FState fs, StringSet & paths) { - StringSet paths; fstateRefs2(fs, paths); - Strings paths2(paths.size()); - copy(paths.begin(), paths.end(), paths2.begin()); - return paths2; } diff --git a/src/fstate.hh b/src/fstate.hh index de6303dca..159c7ba46 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -1,6 +1,8 @@ #ifndef __EVAL_H #define __EVAL_H +#include + extern "C" { #include } @@ -59,17 +61,19 @@ using namespace std; typedef ATerm FState; typedef ATerm Content; +typedef set StringSet; + /* Realise an fstate expression in the file system. This requires execution of all Derive() nodes. */ -FState realiseFState(FState fs); +FState realiseFState(FState fs, StringSet & paths); /* Return the path of an fstate expression. An empty string is returned if the term is not a valid fstate expression. (!!!) */ string fstatePath(FState fs); /* Return the paths referenced by fstate expression. */ -Strings fstateRefs(FState fs); +void fstateRefs(FState fs, StringSet & paths); /* Return a canonical textual representation of an expression. */ string printTerm(ATerm t); @@ -82,10 +86,10 @@ Error badTerm(const format & f, ATerm t); Hash hashTerm(ATerm t); /* Read an aterm from disk, given its hash. */ -ATerm termFromHash(const Hash & hash); +ATerm termFromHash(const Hash & hash, string * p = 0); /* Write an aterm to the Nix store directory, and return its hash. */ -Hash writeTerm(ATerm t); +Hash writeTerm(ATerm t, const string & suffix, string * p = 0); #endif /* !__EVAL_H */ diff --git a/src/nix.cc b/src/nix.cc index f3549ead8..d6f2db4fe 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -96,7 +96,10 @@ static void opInstall(Strings opFlags, Strings opArgs) for (Strings::iterator it = opArgs.begin(); it != opArgs.end(); it++) - realiseFState(termFromHash(argToHash(*it))); + { + StringSet paths; + realiseFState(termFromHash(argToHash(*it)), paths); + } } @@ -160,8 +163,10 @@ static void opQuery(Strings opFlags, Strings opArgs) break; case qRefs: { - Strings refs = fstateRefs(realiseFState(termFromHash(hash))); - for (Strings::iterator j = refs.begin(); + StringSet refs; + FState fs = ATmake("Include()", ((string) hash).c_str()); + fstateRefs(realiseFState(fs, refs), refs); + for (StringSet::iterator j = refs.begin(); j != refs.end(); j++) cout << format("%s\n") % *j; break; From 6011bd0da24c100f86239ed826fa7b496bbdddf8 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 9 Jul 2003 16:12:40 +0000 Subject: [PATCH 0116/6440] * Outline of the new scheme for derivate distribution. --- src/fstate.cc | 5 +++++ src/globals.hh | 15 +++++++++++++++ src/store.cc | 7 +++++-- src/store.hh | 12 ++++++++++-- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/fstate.cc b/src/fstate.cc index 2f0e50fb2..e289ca7b1 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -250,6 +250,10 @@ static FState realise(FState fs, StringSet & paths) /* Register the normal form. */ nf = storeSuccessor(fs, nf, paths); + /* Expand the hash into the target path. */ + expandHash(hash, path); + +#if 0 /* Perhaps the path already exists and has the right hash? */ if (pathExists(path)) { @@ -267,6 +271,7 @@ static FState realise(FState fs, StringSet & paths) copyPath(path2, path); } +#endif return nf; } diff --git a/src/globals.hh b/src/globals.hh index 2fb9fe747..8d8c63bd7 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -30,6 +30,21 @@ extern string dbHash2Paths; */ extern string dbSuccessors; +/* dbSubstitutes :: Hash -> [Hash] + + Each pair $(h, [hs])$ tells Nix that it can realise any of the + fstate expressions referenced by the hashes in $hs$ to obtain a Nix + archive that, when unpacked, will produce a path with hash $h$. + + The main purpose of this is for distributed caching of derivates. + One system can compute a derivate with hash $h$ and put it on a + website (as a Nix archive), for instance, and then another system + can register a substitute for that derivate. The substitute in + this case might be an fstate expression that fetches the Nix + archive. +*/ +extern string dbSubstitutes; + /* Path names. */ diff --git a/src/store.cc b/src/store.cc index 095d20430..38e059a29 100644 --- a/src/store.cc +++ b/src/store.cc @@ -158,6 +158,9 @@ static string queryPathByHashPrefix(Hash hash, const string & prefix) } +string expandHash(const Hash & hash, const string & outPath = "") +{ + string queryPathByHash(Hash hash) { return queryPathByHashPrefix(hash, "/"); @@ -187,8 +190,8 @@ void addToStore(string srcPath, string & dstPath, Hash & hash) void deleteFromStore(const string & path) { - string prefix = nixStore + "/"; - if (string(path, 0, prefix.size()) != prefix) + string prefix = + "/"; + if (!isInPrefix(path, nixStore)) throw Error(format("path %1% is not in the store") % path); unregisterPath(path); diff --git a/src/store.hh b/src/store.hh index a83fa0304..f747b7ee3 100644 --- a/src/store.hh +++ b/src/store.hh @@ -13,8 +13,16 @@ void copyPath(string src, string dst); /* Register a path keyed on its hash. */ Hash registerPath(const string & path, Hash hash = Hash()); -/* Query a path (any path) through its hash. */ -string queryPathByHash(Hash hash); +/* Return a path whose contents have the given hash. If outPath is + not empty, ensure that such a path is realised in outPath (if + necessary by copying from another location). If prefix is not + empty, only return a path that is an descendent of prefix. + + If no path with the given hash is known to exist in the file + system, ... +*/ +string expandHash(const Hash & hash, const string & outPath = "", + const string & prefix = "/"); /* Copy a file to the nixStore directory and register it in dbRefs. Return the hash code of the value. */ From 9ebd78144a9c996e39ffc209c05a511f119f55ae Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Jul 2003 09:09:48 +0000 Subject: [PATCH 0117/6440] * Added a directory for standard Fix descriptors. From b96239c65703afba195a952d9f21b9588c136ac7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Jul 2003 09:11:30 +0000 Subject: [PATCH 0118/6440] * Moved the fetchutl package to corepkgs. --- corepkgs/fetchurl/fetchurl.fix | 9 +++++++++ corepkgs/fetchurl/fetchurl.sh | 3 +++ 2 files changed, 12 insertions(+) create mode 100644 corepkgs/fetchurl/fetchurl.fix create mode 100644 corepkgs/fetchurl/fetchurl.sh diff --git a/corepkgs/fetchurl/fetchurl.fix b/corepkgs/fetchurl/fetchurl.fix new file mode 100644 index 000000000..a3b3d46e1 --- /dev/null +++ b/corepkgs/fetchurl/fetchurl.fix @@ -0,0 +1,9 @@ +Function(["url", "hash"], + Package( + [ ("build", Relative("fetchurl/fetchurl.sh")) + , ("url", Var("url")) + , ("hash", Var("hash")) + , ("name", BaseName(Var("url"))) + ] + ) +) diff --git a/corepkgs/fetchurl/fetchurl.sh b/corepkgs/fetchurl/fetchurl.sh new file mode 100644 index 000000000..a92092c6e --- /dev/null +++ b/corepkgs/fetchurl/fetchurl.sh @@ -0,0 +1,3 @@ +#! /bin/sh + +wget "$url" -O "$out" From 089b43617501b19b94523b2211877841ed09e70e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Jul 2003 09:12:52 +0000 Subject: [PATCH 0119/6440] * Deleted the sys directory. --- sys/bootstrap | 86 --------------------------------------------------- sys/makedisk | 11 ------- sys/mountloop | 8 ----- sys/runsystem | 5 --- sys/settings | 2 -- sys/start | 38 ----------------------- 6 files changed, 150 deletions(-) delete mode 100755 sys/bootstrap delete mode 100755 sys/makedisk delete mode 100755 sys/mountloop delete mode 100755 sys/runsystem delete mode 100644 sys/settings delete mode 100755 sys/start diff --git a/sys/bootstrap b/sys/bootstrap deleted file mode 100755 index f6aa16c86..000000000 --- a/sys/bootstrap +++ /dev/null @@ -1,86 +0,0 @@ -#! /bin/sh - -. ./settings - -if ! ./mountloop; then - exit 1 -fi - -# Cleanup. -rm -rf $target/dev -rm -rf $target/proc - -# Create the basic directory structure. -mkdir $target -mkdir $target/dev -mkdir $target/proc -mkdir $target/pkg -mkdir $target/pkg/sys -mkdir $target/pkg/sys/bin -mkdir $target/pkg/sys/var -mkdir $target/mnt -mkdir $target/mnt/host -mkdir -m 1777 $target/tmp - -# Make package registrations. -pkgdb=$target/pkg/sys/var/pkginfo - -# Copy some programs and its libraries. -utils="/usr/bin/vi /bin/sh /bin/mount /bin/umount /bin/ls /bin/ln /bin/cp /bin/mv /bin/rm /bin/cat /bin/df /bin/pwd /usr/bin/ld /usr/bin/as /bin/sed /bin/chmod /bin/chown /usr/bin/expr /bin/mkdir /bin/rmdir /usr/bin/sort /usr/bin/uniq /bin/uname /usr/bin/grep /bin/sleep /bin/gzip /usr/bin/make /usr/bin/cmp /bin/date /usr/bin/tr /usr/bin/ar /usr/bin/ranlib /usr/bin/basename /usr/bin/less /usr/bin/md5sum /bin/tar ../src/nix" -bootlib=/pkg/prog-bootstrap/lib -bootbin=/pkg/prog-bootstrap/bin -mkdir -p $target/$bootlib -mkdir -p $target/$bootbin -cp -p $utils $target/$bootbin -libs=`ldd $utils | awk '{ print $3 }' | sort | uniq` -echo $libs -cp -p $libs $target/$bootlib -for i in libc.so.6 libdl.so.2 libpthread.so.0 librt.so.1 libresolv.so.2 ld-linux.so.2; do rm $target/$bootlib/$i; done -../src/nix -d $pkgdb regpkg 5703121fe19cbeeaee7edd659cf4a25b /pkg/prog-bootstrap - -mv $target/$bootbin/nix $target/pkg/sys/bin -../src/nix -d $pkgdb regpkg 36bcbb801f5052739af8220c6ea51434 /pkg/sys - -# Copy the bootstrap gcc. -echo Copying gcc... -rsync -a ../bootstrap/gcc/inst/pkg $target -../src/nix -d $pkgdb regpkg 02212b3dc4e50349376975367d433929 /pkg/gcc-bootstrap - -# Copy the bootstrap glibc. -echo Copying glibc... -glibcdir=/pkg/glibc-bootstrap -rsync -a ../bootstrap/glibc/inst/pkg $target -../src/nix -d $pkgdb regpkg c0ce03ee0bab298babbe7e3b6159d36c $glibcdir - -# Copy the bootstrap kernel header files. -echo Copying kernel headers... -kerneldir=/pkg/kernel-bootstrap -rsync -a ../bootstrap/kernel/inst/pkg $target -../src/nix -d $pkgdb regpkg 3dc8333a2c2b4d627b892755417acf89 $kerneldir - -# Compatibility. -rm -rf $target/lib -mkdir $target/lib -ln -sf $glibcdir/lib/ld-linux.so.2 $target/lib/ld-linux.so.2 - -rm -rf $target/bin -mkdir $target/bin -ln -sf $bootbin/sh $target/bin/sh - -# Build ld.so.cache. -ldsoconf=$target/$glibcdir/etc/ld.so.conf -echo $glibcdir/lib > $ldsoconf -echo $bootlib >> $ldsoconf -$target/$glibcdir/sbin/ldconfig -r $target - -# Source repository. -rm -f $target/src -ln -sf /mnt/host/`pwd`/../pkg $target/src - -# Copy boot script. -cp -p ./start $target/pkg/sys/bin - -# Done. -echo Done! -umount $target -rmdir $target diff --git a/sys/makedisk b/sys/makedisk deleted file mode 100755 index 3d6ec9a2c..000000000 --- a/sys/makedisk +++ /dev/null @@ -1,11 +0,0 @@ -#! /bin/sh - -. ./settings - -rm $image - -dd if=/dev/zero of=$image bs=1M count=1 seek=256 - -/sbin/mke2fs -F -j $image -/sbin/tune2fs -c 0 $image -/sbin/tune2fs -i 0 $image diff --git a/sys/mountloop b/sys/mountloop deleted file mode 100755 index 1d5fe32fc..000000000 --- a/sys/mountloop +++ /dev/null @@ -1,8 +0,0 @@ -#! /bin/sh - -. ./settings - -mkdir $target -if ! mount -o loop -t ext3 $image $target; then - exit 1 -fi diff --git a/sys/runsystem b/sys/runsystem deleted file mode 100755 index fd634bdcc..000000000 --- a/sys/runsystem +++ /dev/null @@ -1,5 +0,0 @@ -#! /bin/sh - -. ./settings - -linux ubd0=$image init=/pkg/sys/bin/start diff --git a/sys/settings b/sys/settings deleted file mode 100644 index 3968b609a..000000000 --- a/sys/settings +++ /dev/null @@ -1,2 +0,0 @@ -image=/var/tmp/nix.img -target=./loop diff --git a/sys/start b/sys/start deleted file mode 100755 index b36cde297..000000000 --- a/sys/start +++ /dev/null @@ -1,38 +0,0 @@ -#! /pkg/prog-bootstrap/bin/sh - -# This directory contains nix. -export PATH=/pkg/sys/bin - -# Add in the utilities needed for booting. -export PATH=$PATH:`nix getpkg 5703121fe19cbeeaee7edd659cf4a25b`/bin - -echo -echo Starting up... - -echo Mounting file systems... -mount -n -o remount,rw /dev/root / -mount -n -t proc none /proc -mount -n -t hostfs none /mnt/host - -echo Registering available sources... -( if cd /src; then - for i in *; do - nix reg $i - done - fi -) - -export PATH=`nix getpkg coreutils-4.5.7`/bin:$PATH - -echo -echo "=== starting interactive shell ===" - -sh - -echo -echo Shutting down... - -umount /proc -#sync -mount -n -o remount,ro /dev/root / -#sync From 5d4b90b689b09965be39c69aceddaf0b165598d0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Jul 2003 09:21:40 +0000 Subject: [PATCH 0120/6440] * Actually go through the search directories when looking for files. --- src/fix.cc | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index d17e7b550..9e5b8b5d4 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -10,6 +10,24 @@ typedef ATerm Expr; +static Strings searchDirs; + + +static string searchPath(string relPath) +{ + for (Strings::iterator i = searchDirs.begin(); + i != searchDirs.end(); i++) + { + string path = *i + "/" + relPath; + if (pathExists(path)) return path; + } + + throw Error( + format("path `%1%' not found in any of the search directories") + % relPath); +} + + static Expr evalFile(string fileName); @@ -108,7 +126,7 @@ static Expr evalExpr(Expr e) /* Relative files. */ if (ATmatch(e, "Relative()", &s1)) { - string srcPath = s1; + string srcPath = searchPath(s1); string dstPath; Hash hash; addToStore(srcPath, dstPath, hash); @@ -198,13 +216,9 @@ static Expr evalExpr(Expr e) } -static Strings searchPath; - - -static Expr evalFile(string fileName) +static Expr evalFile(string relPath) { - Expr e = ATreadFromNamedFile(fileName.c_str()); - if (!e) throw Error(format("cannot read aterm `%1%'") % fileName); + Expr e = ATreadFromNamedFile(searchPath(relPath).c_str()); return evalExpr(e); } @@ -213,7 +227,7 @@ void run(Strings args) { Strings files; - searchPath.push_back("."); + searchDirs.push_back("."); for (Strings::iterator it = args.begin(); it != args.end(); ) @@ -223,7 +237,7 @@ void run(Strings args) if (arg == "--includedir" || arg == "-I") { if (it == args.end()) throw UsageError(format("argument required in `%1%'") % arg); - searchPath.push_back(*it++); + searchDirs.push_back(*it++); } else if (arg[0] == '-') throw UsageError(format("unknown flag `%1%`") % arg); From d072485d2895d01dbbab1d899418726e3349343f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Jul 2003 13:41:28 +0000 Subject: [PATCH 0121/6440] * Get `nix-push' working again. It now uses Nix/Fix to create Nix archives (using the package in corepkgs/nar). * queryPathByHash -> expandHash, and it takes an argument specifying the target path (which may be empty). * Install the core Fix packages in $prefix/share/fix. TODO: bootstrap Nix and install Nix as a Fix package. --- Makefile.am | 2 +- configure.ac | 3 ++- corepkgs/Makefile.am | 8 ++++++ corepkgs/nar/nar.fix | 8 ++++++ corepkgs/nar/nar.sh | 3 +++ scripts/Makefile.am | 2 +- scripts/nix-pull | 2 ++ scripts/nix-push | 60 ++++++++++++++++++++++++++++++++++++++++++++ src/fix.cc | 8 +++++- src/fstate.cc | 22 +--------------- src/globals.cc | 4 +++ src/globals.hh | 2 ++ src/nix.cc | 24 +++++++++++------- src/shared.cc | 1 + src/store.cc | 51 +++++++++++++++++++++++++------------ src/store.hh | 8 +++--- 16 files changed, 154 insertions(+), 54 deletions(-) create mode 100644 corepkgs/Makefile.am create mode 100644 corepkgs/nar/nar.fix create mode 100644 corepkgs/nar/nar.sh create mode 100644 scripts/nix-pull create mode 100644 scripts/nix-push diff --git a/Makefile.am b/Makefile.am index 83a04399a..2bed3f3bb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1 +1 @@ -SUBDIRS = src scripts +SUBDIRS = src scripts corepkgs diff --git a/configure.ac b/configure.ac index e3b0f0c6e..110d8f0e1 100644 --- a/configure.ac +++ b/configure.ac @@ -13,9 +13,10 @@ AC_PROG_RANLIB # Unix shell scripting should die a slow and painful death. AC_DEFINE_UNQUOTED(NIX_STORE_DIR, "$(eval echo $prefix/store)", Nix store directory.) +AC_DEFINE_UNQUOTED(NIX_DATA_DIR, "$(eval echo $datadir)", Nix data directory.) AC_DEFINE_UNQUOTED(NIX_STATE_DIR, "$(eval echo $localstatedir/nix)", Nix state directory.) AC_DEFINE_UNQUOTED(NIX_LOG_DIR, "$(eval echo $localstatedir/log/nix)", Nix log file directory.) AM_CONFIG_HEADER([config.h]) -AC_CONFIG_FILES([Makefile src/Makefile scripts/Makefile]) +AC_CONFIG_FILES([Makefile src/Makefile scripts/Makefile corepkgs/Makefile]) AC_OUTPUT diff --git a/corepkgs/Makefile.am b/corepkgs/Makefile.am new file mode 100644 index 000000000..9ce9c8c79 --- /dev/null +++ b/corepkgs/Makefile.am @@ -0,0 +1,8 @@ +install-data-local: + $(INSTALL) -d $(datadir)/fix + $(INSTALL) -d $(datadir)/fix/fetchurl + $(INSTALL_DATA) fetchurl/fetchurl.fix $(datadir)/fix/fetchurl + $(INSTALL_DATA) fetchurl/fetchurl.sh $(datadir)/fix/fetchurl + $(INSTALL) -d $(datadir)/fix/nar + $(INSTALL_DATA) nar/nar.fix $(datadir)/fix/nar + $(INSTALL_DATA) nar/nar.sh $(datadir)/fix/nar diff --git a/corepkgs/nar/nar.fix b/corepkgs/nar/nar.fix new file mode 100644 index 000000000..3db6a48a0 --- /dev/null +++ b/corepkgs/nar/nar.fix @@ -0,0 +1,8 @@ +Function(["path", "name"], + Package( + [ ("name", Var("name")) + , ("build", Relative("nar/nar.sh")) + , ("path", Var("path")) + ] + ) +) \ No newline at end of file diff --git a/corepkgs/nar/nar.sh b/corepkgs/nar/nar.sh new file mode 100644 index 000000000..6ffcf6322 --- /dev/null +++ b/corepkgs/nar/nar.sh @@ -0,0 +1,3 @@ +#! /bin/sh + +/tmp/nix/bin/nix --dump --file "$path" > $out || exit 1 diff --git a/scripts/Makefile.am b/scripts/Makefile.am index cf70f1574..e4602f2a1 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -1,5 +1,5 @@ bin_SCRIPTS = nix-switch nix-collect-garbage \ - nix-pull-prebuilts nix-push-prebuilts + nix-pull nix-push install-exec-local: $(INSTALL) -d $(sysconfdir)/profile.d diff --git a/scripts/nix-pull b/scripts/nix-pull new file mode 100644 index 000000000..0b09c8e00 --- /dev/null +++ b/scripts/nix-pull @@ -0,0 +1,2 @@ +#! /usr/bin/perl -w + diff --git a/scripts/nix-push b/scripts/nix-push new file mode 100644 index 000000000..14b7e2834 --- /dev/null +++ b/scripts/nix-push @@ -0,0 +1,60 @@ +#! /usr/bin/perl -w + +my @pushlist; + +foreach my $hash (@ARGV) { + + die unless $hash =~ /^([0-9a-z]{32})$/; + + # Get all paths referenced by the normalisation of the given + # fstate expression. + my @paths; + open PATHS, "nix -qrh $hash 2> /dev/null |" or die "nix -qrh"; + while () { + chomp; + next unless /^\//; + push @paths, $_; + } + close PATHS; + + # For each path, create a Fix expression that turns the path into + # a Nix archive. + foreach my $path (@paths) { + + # Hash the path. + my $phash = `nix-hash $path`; + $? and die "nix-hash"; + chomp $phash; + die unless $phash =~ /^([0-9a-z]{32})$/; + + # Construct a Fix expression that creates a Nar archive. + my $fixexpr = + "App(IncludeFix(\"nar/nar.fix\"), " . + "[ (\"path\", Path(\"$path\", Hash(\"$phash\"), [Include(\"$hash\")]))" . + ", (\"name\", \"$phash.nar\")" . + "])"; + + my $fixfile = "/tmp/nix-push-tmp.fix"; + open FIX, ">$fixfile"; + print FIX $fixexpr; + close FIX; + + # Instantiate a Nix expression from the Fix expression. + my $nhash = `fix $fixfile`; + $? and die "instantiating Nix archive expression"; + chomp $nhash; + die unless $nhash =~ /^([0-9a-z]{32})$/; + + # Realise the Nix expression. + my $npath = `nix -qph $nhash 2> /dev/null`; + $? and die "creating Nix archive"; + chomp $npath; + + push @pushlist, $npath; + + print "$path -> $npath\n"; + } +} + +# Push the prebuilts to the server. !!! FIXME +system "rsync -av -e ssh @pushlist eelco\@losser.st-lab.cs.uu.nl:/home/eelco/public_html/nix-dist/"; diff --git a/src/fix.cc b/src/fix.cc index 9e5b8b5d4..5c4297bfb 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -15,6 +15,8 @@ static Strings searchDirs; static string searchPath(string relPath) { + if (string(relPath, 0, 1) == "/") return relPath; + for (Strings::iterator i = searchDirs.begin(); i != searchDirs.end(); i++) { @@ -218,7 +220,10 @@ static Expr evalExpr(Expr e) static Expr evalFile(string relPath) { - Expr e = ATreadFromNamedFile(searchPath(relPath).c_str()); + string path = searchPath(relPath); + Expr e = ATreadFromNamedFile(path.c_str()); + if (!e) + throw Error(format("unable to read a term from `%1%'") % path); return evalExpr(e); } @@ -228,6 +233,7 @@ void run(Strings args) Strings files; searchDirs.push_back("."); + searchDirs.push_back(nixDataDir + "/fix"); for (Strings::iterator it = args.begin(); it != args.end(); ) diff --git a/src/fstate.cc b/src/fstate.cc index e289ca7b1..fdd43d1b1 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -149,7 +149,7 @@ Hash hashTerm(ATerm t) ATerm termFromHash(const Hash & hash, string * p) { - string path = queryPathByHash(hash); + string path = expandHash(hash); if (p) *p = path; ATerm t = ATreadFromNamedFile(path.c_str()); if (!t) throw Error(format("cannot read aterm %1%") % path); @@ -253,26 +253,6 @@ static FState realise(FState fs, StringSet & paths) /* Expand the hash into the target path. */ expandHash(hash, path); -#if 0 - /* Perhaps the path already exists and has the right hash? */ - if (pathExists(path)) { - - if (hash != hashPath(path)) - throw Error(format("path %1% exists, but does not have hash %2%") - % path % (string) hash); - - debug(format("path %1% already has hash %2%") - % path % (string) hash); - - } else { - - /* Do we know a path with that hash? If so, copy it. */ - string path2 = queryPathByHash(hash); - copyPath(path2, path); - - } -#endif - return nf; } diff --git a/src/globals.cc b/src/globals.cc index 40a5a981b..9893d7ad2 100644 --- a/src/globals.cc +++ b/src/globals.cc @@ -4,8 +4,11 @@ string dbHash2Paths = "hash2paths"; string dbSuccessors = "successors"; +string dbSubstitutes = "substitutes"; + string nixStore = "/UNINIT"; +string nixDataDir = "/UNINIT"; string nixLogDir = "/UNINIT"; string nixDB = "/UNINIT"; @@ -14,4 +17,5 @@ void initDB() { createDB(nixDB, dbHash2Paths); createDB(nixDB, dbSuccessors); + createDB(nixDB, dbSubstitutes); } diff --git a/src/globals.hh b/src/globals.hh index 8d8c63bd7..0668ac40e 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -52,6 +52,8 @@ extern string dbSubstitutes; derived files. */ extern string nixStore; +extern string nixDataDir; /* !!! fix */ + /* nixLogDir is the directory where we log various operations. */ extern string nixLogDir; diff --git a/src/nix.cc b/src/nix.cc index d6f2db4fe..4721563fd 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -37,7 +37,7 @@ static ArgType argType = atpUnknown; Source selection for --install, --dump: - --file / -f: by file name + --file / -f: by file name !!! -> path --hash / -h: by hash Query flags: @@ -87,6 +87,12 @@ static Hash argToHash(const string & arg) } +static FState hash2fstate(Hash hash) +{ + return ATmake("Include()", ((string) hash).c_str()); +} + + /* Realise (or install) paths from the given Nix fstate expressions. */ static void opInstall(Strings opFlags, Strings opArgs) @@ -98,7 +104,7 @@ static void opInstall(Strings opFlags, Strings opArgs) it != opArgs.end(); it++) { StringSet paths; - realiseFState(termFromHash(argToHash(*it)), paths); + realiseFState(hash2fstate(argToHash(*it)), paths); } } @@ -157,14 +163,16 @@ static void opQuery(Strings opFlags, Strings opArgs) switch (query) { - case qPath: + case qPath: { + StringSet refs; cout << format("%s\n") % - (string) fstatePath(termFromHash(hash)); + (string) fstatePath(realiseFState(termFromHash(hash), refs)); break; + } case qRefs: { StringSet refs; - FState fs = ATmake("Include()", ((string) hash).c_str()); + FState fs = hash2fstate(hash); fstateRefs(realiseFState(fs, refs), refs); for (StringSet::iterator j = refs.begin(); j != refs.end(); j++) @@ -203,10 +211,8 @@ static void opDump(Strings opFlags, Strings opArgs) string arg = *opArgs.begin(); string path; - if (argType == atpHash) - path = queryPathByHash(parseHash(arg)); - else if (argType == atpPath) - path = arg; + if (argType == atpHash) path = expandHash(parseHash(arg)); + else if (argType == atpPath) path = arg; dumpPath(path, sink); } diff --git a/src/shared.cc b/src/shared.cc index bd165ce97..bfd7498de 100644 --- a/src/shared.cc +++ b/src/shared.cc @@ -16,6 +16,7 @@ static void initAndRun(int argc, char * * argv) { /* Setup Nix paths. */ nixStore = NIX_STORE_DIR; + nixDataDir = NIX_DATA_DIR; nixLogDir = NIX_LOG_DIR; nixDB = (string) NIX_STATE_DIR + "/nixstate.db"; diff --git a/src/store.cc b/src/store.cc index 38e059a29..5a3a4e067 100644 --- a/src/store.cc +++ b/src/store.cc @@ -131,25 +131,53 @@ bool isInPrefix(const string & path, const string & _prefix) } -static string queryPathByHashPrefix(Hash hash, const string & prefix) +string expandHash(const Hash & hash, const string & target, + const string & prefix) { Strings paths; + if (!target.empty() && !isInPrefix(target, prefix)) + abort(); + if (!queryListDB(nixDB, dbHash2Paths, hash, paths)) throw Error(format("no paths known with hash `%1%'") % (string) hash); - /* Arbitrarily pick the first one that exists and still hash the - right hash. */ + /* !!! we shouldn't check for staleness by default --- too slow */ + /* Pick one equal to `target'. */ + if (!target.empty()) { + + for (Strings::iterator i = paths.begin(); + i != paths.end(); i++) + { + string path = *i; + try { + if (path == target && hashPath(path) == hash) + return path; + } catch (Error & e) { + debug(format("stale path: %1%") % e.msg()); + /* try next one */ + } + } + + } + + /* Arbitrarily pick the first one that exists and isn't stale. */ for (Strings::iterator it = paths.begin(); it != paths.end(); it++) { string path = *it; try { - if (isInPrefix(path, prefix) && hashPath(path) == hash) - return path; + if (isInPrefix(path, prefix) && hashPath(path) == hash) { + if (target.empty()) + return path; + else { + copyPath(path, target); + return target; + } + } } catch (Error & e) { - debug(format("checking hash: %1%") % e.msg()); + debug(format("stale path: %1%") % e.msg()); /* try next one */ } } @@ -157,16 +185,7 @@ static string queryPathByHashPrefix(Hash hash, const string & prefix) throw Error(format("all paths with hash `%1%' are stale") % (string) hash); } - -string expandHash(const Hash & hash, const string & outPath = "") -{ -string queryPathByHash(Hash hash) -{ - return queryPathByHashPrefix(hash, "/"); -} - - void addToStore(string srcPath, string & dstPath, Hash & hash) { srcPath = absPath(srcPath); @@ -174,7 +193,7 @@ void addToStore(string srcPath, string & dstPath, Hash & hash) hash = hashPath(srcPath); try { - dstPath = queryPathByHashPrefix(hash, nixStore); + dstPath = expandHash(hash, "", nixStore); return; } catch (...) { } diff --git a/src/store.hh b/src/store.hh index f747b7ee3..8b02cba99 100644 --- a/src/store.hh +++ b/src/store.hh @@ -13,15 +13,15 @@ void copyPath(string src, string dst); /* Register a path keyed on its hash. */ Hash registerPath(const string & path, Hash hash = Hash()); -/* Return a path whose contents have the given hash. If outPath is - not empty, ensure that such a path is realised in outPath (if +/* Return a path whose contents have the given hash. If target is + not empty, ensure that such a path is realised in target (if necessary by copying from another location). If prefix is not empty, only return a path that is an descendent of prefix. If no path with the given hash is known to exist in the file - system, ... + system, */ -string expandHash(const Hash & hash, const string & outPath = "", +string expandHash(const Hash & hash, const string & target = "", const string & prefix = "/"); /* Copy a file to the nixStore directory and register it in dbRefs. From 1d1c3691d2fdf5aad0baceadd8596f23c1e0e1fa Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Jul 2003 15:11:48 +0000 Subject: [PATCH 0122/6440] * The policy-free derivate sharing now *almost* works. :-) For any hash for which no local expansion is available, Nix can execute a `substitute' which should produce a path with such a hash. This is policy-free since Nix does not in any way specify how the substitute should work, i.e., it's an arbitrary (unnormalised) fstate expression. For example, `nix-pull' registers substitutes that fetch Nix archives from the network (through `wget') and unpack them, but any other method is possible as well. This is an improvement over the old Nix sharing scheme, which had a policy (fetching through `wget') built in. The sharing scheme doesn't work completely yet because successors from fstate rewriting have to be registered on the receiving side. Probably the whole successor stuff can be folded up into the substitute mechanism; this would be a nice simplification. --- corepkgs/Makefile.am | 2 + corepkgs/nar/unnar.fix | 8 ++++ corepkgs/nar/unnar.sh | 3 ++ scripts/Makefile.am | 4 +- scripts/nix-pull | 65 +++++++++++++++++++++++++++++ scripts/nix-pull-prebuilts | 83 -------------------------------------- scripts/nix-push-prebuilts | 44 -------------------- scripts/prebuilts.conf | 6 +-- src/fstate.cc | 6 +++ src/fstate.hh | 2 + src/nix.cc | 25 +++++++++--- src/store.cc | 45 +++++++++++++++++++-- src/store.hh | 4 ++ 13 files changed, 155 insertions(+), 142 deletions(-) create mode 100644 corepkgs/nar/unnar.fix create mode 100644 corepkgs/nar/unnar.sh delete mode 100755 scripts/nix-pull-prebuilts delete mode 100755 scripts/nix-push-prebuilts diff --git a/corepkgs/Makefile.am b/corepkgs/Makefile.am index 9ce9c8c79..9298865bf 100644 --- a/corepkgs/Makefile.am +++ b/corepkgs/Makefile.am @@ -6,3 +6,5 @@ install-data-local: $(INSTALL) -d $(datadir)/fix/nar $(INSTALL_DATA) nar/nar.fix $(datadir)/fix/nar $(INSTALL_DATA) nar/nar.sh $(datadir)/fix/nar + $(INSTALL_DATA) nar/unnar.fix $(datadir)/fix/nar + $(INSTALL_DATA) nar/unnar.sh $(datadir)/fix/nar diff --git a/corepkgs/nar/unnar.fix b/corepkgs/nar/unnar.fix new file mode 100644 index 000000000..db97750aa --- /dev/null +++ b/corepkgs/nar/unnar.fix @@ -0,0 +1,8 @@ +Function(["nar", "name"], + Package( + [ ("name", Var("name")) + , ("build", Relative("nar/unnar.sh")) + , ("nar", Var("nar")) + ] + ) +) \ No newline at end of file diff --git a/corepkgs/nar/unnar.sh b/corepkgs/nar/unnar.sh new file mode 100644 index 000000000..e6a3f3c1f --- /dev/null +++ b/corepkgs/nar/unnar.sh @@ -0,0 +1,3 @@ +#! /bin/sh + +/tmp/nix/bin/nix --restore "$out" < $nar || exit 1 diff --git a/scripts/Makefile.am b/scripts/Makefile.am index e4602f2a1..2f4dbacc9 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -5,5 +5,5 @@ install-exec-local: $(INSTALL) -d $(sysconfdir)/profile.d $(INSTALL_PROGRAM) nix-profile.sh $(sysconfdir)/profile.d/nix.sh $(INSTALL) -d $(sysconfdir)/nix - # !!! don't overwrite local modifications - $(INSTALL_PROGRAM) prebuilts.conf $(sysconfdir)/nix/prebuilts.conf +# !!! don't overwrite local modifications + $(INSTALL_DATA) prebuilts.conf $(sysconfdir)/nix/prebuilts.conf diff --git a/scripts/nix-pull b/scripts/nix-pull index 0b09c8e00..59773a2ba 100644 --- a/scripts/nix-pull +++ b/scripts/nix-pull @@ -1,2 +1,67 @@ #! /usr/bin/perl -w +my $prefix = $ENV{"NIX"} || "/tmp/nix"; # !!! use prefix +my $etcdir = "$prefix/etc/nix"; +my $tmpfile = "$prefix/var/nix/pull.tmp"; + +my $conffile = "$etcdir/prebuilts.conf"; + +open CONFFILE, "<$conffile"; + +while () { + + chomp; + if (/^\s*(\S+)\s*(\#.*)?$/) { + my $url = $1; + + print "obtaining list of Nix archives at $url...\n"; + + system "wget '$url' -O '$tmpfile' 2> /dev/null"; # !!! escape + if ($?) { die "`wget' failed"; } + + open INDEX, "<$tmpfile"; + + while () { + # Get all links to prebuilts, that is, file names of the + # form foo-HASH-HASH.tar.bz2. + next unless (/HREF=\"([^\"]*)\"/); + my $fn = $1; + next if $fn =~ /\.\./; + next if $fn =~ /\//; + next unless $fn =~ /([0-9a-z]{32})-([0-9a-z]{32})\.nar/; + my $hash = $2; + + print "registering $hash -> $url/$fn\n"; + + # Construct a Fix expression that fetches and unpacks a + # Nix archive from the network. + my $fetch = + "App(IncludeFix(\"fetchurl/fetchurl.fix\"), " . + "[(\"url\", \"$url/$fn\"), (\"hash\", \"\")])"; + my $fixexpr = + "App(IncludeFix(\"nar/unnar.fix\"), " . + "[ (\"nar\", $fetch)" . + ", (\"name\", \"fetched-$hash\")" . + "])"; + + my $fixfile = "/tmp/nix-pull-tmp.fix"; + open FIX, ">$fixfile"; + print FIX $fixexpr; + close FIX; + + # Instantiate a Nix expression from the Fix expression. + my $nhash = `fix $fixfile`; + $? and die "instantiating Nix archive expression"; + chomp $nhash; + die unless $nhash =~ /^([0-9a-z]{32})$/; + + system "nix --substitute $hash $nhash"; + if ($?) { die "`nix --substitute' failed"; } + } + + close INDEX; + + unlink $tmpfile; + } + +} diff --git a/scripts/nix-pull-prebuilts b/scripts/nix-pull-prebuilts deleted file mode 100755 index 3d045b463..000000000 --- a/scripts/nix-pull-prebuilts +++ /dev/null @@ -1,83 +0,0 @@ -#! /usr/bin/perl -w - -my $prefix = $ENV{"NIX"} || "/nix"; # !!! use prefix -my $etcdir = "$prefix/etc/nix"; -my $knowns = "$prefix/var/nix/known-prebuilts"; -my $tmpfile = "$prefix/var/nix/prebuilts.tmp"; - -my $conffile = "$etcdir/prebuilts.conf"; - -umask 0022; - -sub register { - my $fn = shift; - my $url = shift; - return unless $fn =~ /([^\/]*)-([0-9a-z]{32})-([0-9a-z]{32})\.tar\.bz2/; - my $id = $1; - my $pkghash = $2; - my $prebuilthash = $3; - - print "$pkghash => $prebuilthash ($id)\n"; - - system "nix regprebuilt $pkghash $prebuilthash"; - if ($?) { die "`nix regprebuilt' failed"; } - - if ($url =~ /^\//) { - system "nix regfile $url"; - if ($?) { die "`nix regfile' failed"; } - } else { - system "nix regurl $prebuilthash $url"; - if ($?) { die "`nix regurl' failed"; } - } - - print KNOWNS "$pkghash\n"; -} - -open KNOWNS, ">$knowns"; - -open CONFFILE, "<$conffile"; - -while () { - chomp; - if (/^\s*(\S+)\s*(\#.*)?$/) { - my $url = $1; - - print "obtaining prebuilt list from $url...\n"; - - if ($url =~ /^\//) { - - # It's a local path. - - foreach my $fn (glob "$url/*") { - register($fn, $fn); - } - - } else { - - # It's a URL. - - system "wget '$url' -O '$tmpfile' 2> /dev/null"; # !!! escape - if ($?) { die "`wget' failed"; } - - open INDEX, "<$tmpfile"; - - while () { - # Get all links to prebuilts, that is, file names of the - # form foo-HASH-HASH.tar.bz2. - next unless (/HREF=\"([^\"]*)\"/); - my $fn = $1; - next if $fn =~ /\.\./; - next if $fn =~ /\//; - register($fn, "$url/$fn"); - } - - close INDEX; - - unlink $tmpfile; - } - } -} - -close CONFFILE; - -close KNOWNS; diff --git a/scripts/nix-push-prebuilts b/scripts/nix-push-prebuilts deleted file mode 100755 index 2d44e7cda..000000000 --- a/scripts/nix-push-prebuilts +++ /dev/null @@ -1,44 +0,0 @@ -#! /usr/bin/perl -w - -my $prefix = $ENV{"NIX"} || "/nix"; # !!! use prefix -my $etcdir = "$prefix/etc/nix"; -my $exportdir = "$prefix/var/nix/prebuilts/exports"; -my $knowns = "$prefix/var/nix/known-prebuilts"; - -umask 0022; - -# For performance, put the known hashes in an associative array. -my %knowns = (); -open KNOWNS, "<$knowns"; -while () { - next unless /([0-9a-z]{32})/; - $knowns{$1} = 1; -} -close KNOWNS; - -# For each installed package, check whether a prebuilt is known. - -open PKGS, "nix listinst|"; - -while () { - chomp; - next unless /([0-9a-z]{32})/; - my $pkghash = $1; - if (!defined $knowns{$1}) { - # No known prebuilt exists for this package; so export it. - print "exporting $pkghash...\n"; - system "nix export '$exportdir' $pkghash"; - if ($?) { die "`nix export' failed"; } - } -} - -close PKGS; - -# Push the prebuilts to the server. !!! FIXME - -system "rsync -av -e ssh '$exportdir'/ eelco\@losser.st-lab.cs.uu.nl:/home/eelco/public_html/nix-prebuilts/"; - -# Rerun `nix-pull-prebuilts' to rescan the prebuilt source locations. - -print "running nix-pull-prebuilts..."; -system "nix-pull-prebuilts"; diff --git a/scripts/prebuilts.conf b/scripts/prebuilts.conf index 9b950cad4..c7bc89c61 100644 --- a/scripts/prebuilts.conf +++ b/scripts/prebuilts.conf @@ -1,4 +1,2 @@ -# A list of URLs or local paths from where we obtain prebuilts. -/nix/var/nix/prebuilts/imports -/nix/var/nix/prebuilts/exports -http://losser.st-lab.cs.uu.nl/~eelco/nix-prebuilts/ +# A list of URLs from where we obtain Nix archives. +http://losser.st-lab.cs.uu.nl/~eelco/nix-dist/ diff --git a/src/fstate.cc b/src/fstate.cc index fdd43d1b1..97532c162 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -147,6 +147,12 @@ Hash hashTerm(ATerm t) } +FState hash2fstate(Hash hash) +{ + return ATmake("Include()", ((string) hash).c_str()); +} + + ATerm termFromHash(const Hash & hash, string * p) { string path = expandHash(hash); diff --git a/src/fstate.hh b/src/fstate.hh index 159c7ba46..8a873a5ac 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -85,6 +85,8 @@ Error badTerm(const format & f, ATerm t); /* Hash an aterm. */ Hash hashTerm(ATerm t); +FState hash2fstate(Hash hash); + /* Read an aterm from disk, given its hash. */ ATerm termFromHash(const Hash & hash, string * p = 0); diff --git a/src/nix.cc b/src/nix.cc index 4721563fd..53057328d 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -26,6 +26,8 @@ static ArgType argType = atpUnknown; --add / -A: copy a path to the Nix store --query / -q: query information + --substitute: register a substitute expression + --dump: dump a path as a Nix archive --restore: restore a path from a Nix archive @@ -87,12 +89,6 @@ static Hash argToHash(const string & arg) } -static FState hash2fstate(Hash hash) -{ - return ATmake("Include()", ((string) hash).c_str()); -} - - /* Realise (or install) paths from the given Nix fstate expressions. */ static void opInstall(Strings opFlags, Strings opArgs) @@ -187,6 +183,21 @@ static void opQuery(Strings opFlags, Strings opArgs) } +static void opSubstitute(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (opArgs.size() % 2) throw UsageError("expecting even number of arguments"); + + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ) + { + Hash srcHash = parseHash(*i++); + Hash subHash = parseHash(*i++); + registerSubstitute(srcHash, subHash); + } +} + + /* A sink that writes dump output to stdout. */ struct StdoutSink : DumpSink { @@ -277,6 +288,8 @@ void run(Strings args) op = opAdd; else if (arg == "--query" || arg == "-q") op = opQuery; + else if (arg == "--substitute") + op = opSubstitute; else if (arg == "--dump") op = opDump; else if (arg == "--restore") diff --git a/src/store.cc b/src/store.cc index 5a3a4e067..435ac5cc6 100644 --- a/src/store.cc +++ b/src/store.cc @@ -7,6 +7,7 @@ #include "globals.hh" #include "db.hh" #include "archive.hh" +#include "fstate.hh" struct CopySink : DumpSink @@ -83,6 +84,20 @@ void copyPath(string src, string dst) } +void registerSubstitute(const Hash & srcHash, const Hash & subHash) +{ + Strings subs; + queryListDB(nixDB, dbSubstitutes, srcHash, subs); /* non-existence = ok */ + + for (Strings::iterator it = subs.begin(); it != subs.end(); it++) + if (parseHash(*it) == subHash) return; + + subs.push_back(subHash); + + setListDB(nixDB, dbSubstitutes, srcHash, subs); +} + + Hash registerPath(const string & _path, Hash hash) { string path(canonPath(_path)); @@ -139,8 +154,7 @@ string expandHash(const Hash & hash, const string & target, if (!target.empty() && !isInPrefix(target, prefix)) abort(); - if (!queryListDB(nixDB, dbHash2Paths, hash, paths)) - throw Error(format("no paths known with hash `%1%'") % (string) hash); + queryListDB(nixDB, dbHash2Paths, hash, paths); /* !!! we shouldn't check for staleness by default --- too slow */ @@ -181,8 +195,32 @@ string expandHash(const Hash & hash, const string & target, /* try next one */ } } + + /* Try to realise the substitutes. */ + + Strings subs; + queryListDB(nixDB, dbSubstitutes, hash, subs); /* non-existence = ok */ + + for (Strings::iterator it = subs.begin(); it != subs.end(); it++) { + StringSet dummy; + FState nf = realiseFState(hash2fstate(parseHash(*it)), dummy); + string path = fstatePath(nf); + + if (hashPath(path) != hash) + throw Error(format("bad substitute in `%1%'") % (string) path); + + if (target.empty()) + return path; /* !!! prefix */ + else { + if (path != target) { + copyPath(path, target); + registerPath(target, hash); + } + return target; + } + } - throw Error(format("all paths with hash `%1%' are stale") % (string) hash); + throw Error(format("cannot expand hash `%1%'") % (string) hash); } @@ -193,6 +231,7 @@ void addToStore(string srcPath, string & dstPath, Hash & hash) hash = hashPath(srcPath); try { + /* !!! should not use the substitutes! */ dstPath = expandHash(hash, "", nixStore); return; } catch (...) { diff --git a/src/store.hh b/src/store.hh index 8b02cba99..8b41478a2 100644 --- a/src/store.hh +++ b/src/store.hh @@ -8,8 +8,12 @@ using namespace std; +/* Copy a path recursively. */ void copyPath(string src, string dst); +/* Register a substitute. */ +void registerSubstitute(const Hash & srcHash, const Hash & subHash); + /* Register a path keyed on its hash. */ Hash registerPath(const string & path, Hash hash = Hash()); From 8511571f653fcfbb724061dac330c544b6048722 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Jul 2003 15:24:50 +0000 Subject: [PATCH 0123/6440] * Performance enhancement. --- scripts/nix-pull | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/nix-pull b/scripts/nix-pull index 59773a2ba..a15af6302 100644 --- a/scripts/nix-pull +++ b/scripts/nix-pull @@ -6,6 +6,8 @@ my $tmpfile = "$prefix/var/nix/pull.tmp"; my $conffile = "$etcdir/prebuilts.conf"; +my @subs; + open CONFFILE, "<$conffile"; while () { @@ -55,8 +57,9 @@ while () { chomp $nhash; die unless $nhash =~ /^([0-9a-z]{32})$/; - system "nix --substitute $hash $nhash"; - if ($?) { die "`nix --substitute' failed"; } + push @subs, $hash; + push @subs, $nhash; + } close INDEX; @@ -65,3 +68,6 @@ while () { } } + +system "nix --substitute @subs"; +if ($?) { die "`nix --substitute' failed"; } From e5fbf5804192fa62d0edab0f6b323cc0c8d890f9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Jul 2003 18:48:11 +0000 Subject: [PATCH 0124/6440] * A command to register successor fstate expressions. Unifying substitutes and successors isn't very feasible for now, since substitutes are only used when no path with a certain is known. Therefore, a normal form of some expression stored as a substitute would not be used unless the expression itself was missing. --- src/fstate.cc | 8 +++++++- src/fstate.hh | 3 +++ src/nix.cc | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/fstate.cc b/src/fstate.cc index 97532c162..a597b6df6 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -179,6 +179,12 @@ Hash writeTerm(ATerm t, const string & suffix, string * p) } +void registerSuccessor(const Hash & fsHash, const Hash & scHash) +{ + setDB(nixDB, dbSuccessors, fsHash, scHash); +} + + FState storeSuccessor(FState fs, FState sc, StringSet & paths) { if (fs == sc) return sc; @@ -186,7 +192,7 @@ FState storeSuccessor(FState fs, FState sc, StringSet & paths) string path; Hash fsHash = hashTerm(fs); Hash scHash = writeTerm(sc, "-s-" + (string) fsHash, &path); - setDB(nixDB, dbSuccessors, fsHash, scHash); + registerSuccessor(fsHash, scHash); paths.insert(path); #if 0 diff --git a/src/fstate.hh b/src/fstate.hh index 8a873a5ac..9a8955aeb 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -93,5 +93,8 @@ ATerm termFromHash(const Hash & hash, string * p = 0); /* Write an aterm to the Nix store directory, and return its hash. */ Hash writeTerm(ATerm t, const string & suffix, string * p = 0); +/* Register a successor. */ +void registerSuccessor(const Hash & fsHash, const Hash & scHash); + #endif /* !__EVAL_H */ diff --git a/src/nix.cc b/src/nix.cc index 53057328d..19c73165c 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -26,6 +26,7 @@ static ArgType argType = atpUnknown; --add / -A: copy a path to the Nix store --query / -q: query information + --successor: register a successor expression --substitute: register a substitute expression --dump: dump a path as a Nix archive @@ -183,6 +184,21 @@ static void opQuery(Strings opFlags, Strings opArgs) } +static void opSuccessor(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + if (opArgs.size() % 2) throw UsageError("expecting even number of arguments"); + + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ) + { + Hash fsHash = parseHash(*i++); + Hash scHash = parseHash(*i++); + registerSuccessor(fsHash, scHash); + } +} + + static void opSubstitute(Strings opFlags, Strings opArgs) { if (!opFlags.empty()) throw UsageError("unknown flag"); @@ -288,6 +304,8 @@ void run(Strings args) op = opAdd; else if (arg == "--query" || arg == "-q") op = opQuery; + else if (arg == "--successor") + op = opSuccessor; else if (arg == "--substitute") op = opSubstitute; else if (arg == "--dump") From 81304a6bb595e64d868ef4eb4bfcc08014ced939 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Jul 2003 19:27:46 +0000 Subject: [PATCH 0125/6440] * Convert tabs to spaces. --- scripts/nix-pull | 74 ++++++++++++++++++++++++------------------------ scripts/nix-push | 50 ++++++++++++++++---------------- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/scripts/nix-pull b/scripts/nix-pull index a15af6302..86d9f4b11 100644 --- a/scripts/nix-pull +++ b/scripts/nix-pull @@ -18,53 +18,53 @@ while () { print "obtaining list of Nix archives at $url...\n"; - system "wget '$url' -O '$tmpfile' 2> /dev/null"; # !!! escape - if ($?) { die "`wget' failed"; } - - open INDEX, "<$tmpfile"; + system "wget '$url' -O '$tmpfile' 2> /dev/null"; # !!! escape + if ($?) { die "`wget' failed"; } + + open INDEX, "<$tmpfile"; - while () { - # Get all links to prebuilts, that is, file names of the - # form foo-HASH-HASH.tar.bz2. - next unless (/HREF=\"([^\"]*)\"/); - my $fn = $1; - next if $fn =~ /\.\./; - next if $fn =~ /\//; - next unless $fn =~ /([0-9a-z]{32})-([0-9a-z]{32})\.nar/; - my $hash = $2; + while () { + # Get all links to prebuilts, that is, file names of the + # form foo-HASH-HASH.tar.bz2. + next unless (/HREF=\"([^\"]*)\"/); + my $fn = $1; + next if $fn =~ /\.\./; + next if $fn =~ /\//; + next unless $fn =~ /([0-9a-z]{32})-([0-9a-z]{32})\.nar/; + my $hash = $2; - print "registering $hash -> $url/$fn\n"; + print "registering $hash -> $url/$fn\n"; - # Construct a Fix expression that fetches and unpacks a - # Nix archive from the network. - my $fetch = + # Construct a Fix expression that fetches and unpacks a + # Nix archive from the network. + my $fetch = "App(IncludeFix(\"fetchurl/fetchurl.fix\"), " . "[(\"url\", \"$url/$fn\"), (\"hash\", \"\")])"; - my $fixexpr = - "App(IncludeFix(\"nar/unnar.fix\"), " . - "[ (\"nar\", $fetch)" . - ", (\"name\", \"fetched-$hash\")" . - "])"; - - my $fixfile = "/tmp/nix-pull-tmp.fix"; - open FIX, ">$fixfile"; - print FIX $fixexpr; - close FIX; + my $fixexpr = + "App(IncludeFix(\"nar/unnar.fix\"), " . + "[ (\"nar\", $fetch)" . + ", (\"name\", \"fetched-$hash\")" . + "])"; + + my $fixfile = "/tmp/nix-pull-tmp.fix"; + open FIX, ">$fixfile"; + print FIX $fixexpr; + close FIX; - # Instantiate a Nix expression from the Fix expression. - my $nhash = `fix $fixfile`; - $? and die "instantiating Nix archive expression"; - chomp $nhash; - die unless $nhash =~ /^([0-9a-z]{32})$/; + # Instantiate a Nix expression from the Fix expression. + my $nhash = `fix $fixfile`; + $? and die "instantiating Nix archive expression"; + chomp $nhash; + die unless $nhash =~ /^([0-9a-z]{32})$/; - push @subs, $hash; - push @subs, $nhash; + push @subs, $hash; + push @subs, $nhash; - } + } - close INDEX; + close INDEX; - unlink $tmpfile; + unlink $tmpfile; } } diff --git a/scripts/nix-push b/scripts/nix-push index 14b7e2834..731532f1e 100644 --- a/scripts/nix-push +++ b/scripts/nix-push @@ -11,9 +11,9 @@ foreach my $hash (@ARGV) { my @paths; open PATHS, "nix -qrh $hash 2> /dev/null |" or die "nix -qrh"; while () { - chomp; - next unless /^\//; - push @paths, $_; + chomp; + next unless /^\//; + push @paths, $_; } close PATHS; @@ -21,38 +21,38 @@ foreach my $hash (@ARGV) { # a Nix archive. foreach my $path (@paths) { - # Hash the path. - my $phash = `nix-hash $path`; - $? and die "nix-hash"; - chomp $phash; - die unless $phash =~ /^([0-9a-z]{32})$/; + # Hash the path. + my $phash = `nix-hash $path`; + $? and die "nix-hash"; + chomp $phash; + die unless $phash =~ /^([0-9a-z]{32})$/; - # Construct a Fix expression that creates a Nar archive. - my $fixexpr = + # Construct a Fix expression that creates a Nar archive. + my $fixexpr = "App(IncludeFix(\"nar/nar.fix\"), " . "[ (\"path\", Path(\"$path\", Hash(\"$phash\"), [Include(\"$hash\")]))" . ", (\"name\", \"$phash.nar\")" . "])"; - my $fixfile = "/tmp/nix-push-tmp.fix"; - open FIX, ">$fixfile"; - print FIX $fixexpr; - close FIX; + my $fixfile = "/tmp/nix-push-tmp.fix"; + open FIX, ">$fixfile"; + print FIX $fixexpr; + close FIX; - # Instantiate a Nix expression from the Fix expression. - my $nhash = `fix $fixfile`; - $? and die "instantiating Nix archive expression"; - chomp $nhash; - die unless $nhash =~ /^([0-9a-z]{32})$/; + # Instantiate a Nix expression from the Fix expression. + my $nhash = `fix $fixfile`; + $? and die "instantiating Nix archive expression"; + chomp $nhash; + die unless $nhash =~ /^([0-9a-z]{32})$/; - # Realise the Nix expression. - my $npath = `nix -qph $nhash 2> /dev/null`; - $? and die "creating Nix archive"; - chomp $npath; + # Realise the Nix expression. + my $npath = `nix -qph $nhash 2> /dev/null`; + $? and die "creating Nix archive"; + chomp $npath; - push @pushlist, $npath; + push @pushlist, $npath; - print "$path -> $npath\n"; + print "$path -> $npath\n"; } } From 9bcc31c94168717c8bd27b83bfab686264f63745 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Jul 2003 20:13:32 +0000 Subject: [PATCH 0126/6440] * Working derivate sharing. --- scripts/nix-pull | 15 +++++++++++++-- scripts/nix-push | 12 ++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/scripts/nix-pull b/scripts/nix-pull index 86d9f4b11..6404cb79a 100644 --- a/scripts/nix-pull +++ b/scripts/nix-pull @@ -7,6 +7,7 @@ my $tmpfile = "$prefix/var/nix/pull.tmp"; my $conffile = "$etcdir/prebuilts.conf"; my @subs; +my @sucs; open CONFFILE, "<$conffile"; @@ -30,8 +31,9 @@ while () { my $fn = $1; next if $fn =~ /\.\./; next if $fn =~ /\//; - next unless $fn =~ /([0-9a-z]{32})-([0-9a-z]{32})\.nar/; - my $hash = $2; + next unless $fn =~ /-([0-9a-z]{32})(-s-([0-9a-z]{32}))?\.nar/; + my $hash = $1; + my $fshash = $3; print "registering $hash -> $url/$fn\n"; @@ -60,6 +62,12 @@ while () { push @subs, $hash; push @subs, $nhash; + # Does the name encode a successor relation? + if (defined $fshash) { + print "NORMAL $fshash -> $hash\n"; + push @sucs, $fshash; + push @sucs, $hash; + } } close INDEX; @@ -71,3 +79,6 @@ while () { system "nix --substitute @subs"; if ($?) { die "`nix --substitute' failed"; } + +system "nix --successor @sucs"; +if ($?) { die "`nix --successor' failed"; } diff --git a/scripts/nix-push b/scripts/nix-push index 731532f1e..248a3b917 100644 --- a/scripts/nix-push +++ b/scripts/nix-push @@ -27,11 +27,19 @@ foreach my $hash (@ARGV) { chomp $phash; die unless $phash =~ /^([0-9a-z]{32})$/; - # Construct a Fix expression that creates a Nar archive. + # Construct a name for the Nix archive. If the file is an + # fstate successor, encode this into the name. + my $name = $phash; + if ($path =~ /-s-([0-9a-z]{32}).nix$/) { + $name = "$name-s-$1"; + } + $name = $name . ".nar"; + + # Construct a Fix expression that creates a Nix archive. my $fixexpr = "App(IncludeFix(\"nar/nar.fix\"), " . "[ (\"path\", Path(\"$path\", Hash(\"$phash\"), [Include(\"$hash\")]))" . - ", (\"name\", \"$phash.nar\")" . + ", (\"name\", \"$name\")" . "])"; my $fixfile = "/tmp/nix-push-tmp.fix"; From 822c072cfa0f1e4ac304343d78e024ba19da34a8 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Jul 2003 20:34:29 +0000 Subject: [PATCH 0127/6440] * Compress Nix archives when pushing them. --- corepkgs/nar/nar.sh | 2 +- corepkgs/nar/unnar.sh | 2 +- scripts/nix-pull | 2 +- scripts/nix-push | 6 ++++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/corepkgs/nar/nar.sh b/corepkgs/nar/nar.sh index 6ffcf6322..3dbeed029 100644 --- a/corepkgs/nar/nar.sh +++ b/corepkgs/nar/nar.sh @@ -1,3 +1,3 @@ #! /bin/sh -/tmp/nix/bin/nix --dump --file "$path" > $out || exit 1 +/tmp/nix/bin/nix --dump --file "$path" | bzip2 > $out || exit 1 diff --git a/corepkgs/nar/unnar.sh b/corepkgs/nar/unnar.sh index e6a3f3c1f..01b6a3ebe 100644 --- a/corepkgs/nar/unnar.sh +++ b/corepkgs/nar/unnar.sh @@ -1,3 +1,3 @@ #! /bin/sh -/tmp/nix/bin/nix --restore "$out" < $nar || exit 1 +bunzip2 < $nar | /tmp/nix/bin/nix --restore "$out" || exit 1 diff --git a/scripts/nix-pull b/scripts/nix-pull index 6404cb79a..320322585 100644 --- a/scripts/nix-pull +++ b/scripts/nix-pull @@ -31,7 +31,7 @@ while () { my $fn = $1; next if $fn =~ /\.\./; next if $fn =~ /\//; - next unless $fn =~ /-([0-9a-z]{32})(-s-([0-9a-z]{32}))?\.nar/; + next unless $fn =~ /-([0-9a-z]{32})(-s-([0-9a-z]{32}))?\.nar.bz2$/; my $hash = $1; my $fshash = $3; diff --git a/scripts/nix-push b/scripts/nix-push index 248a3b917..bf30f3a49 100644 --- a/scripts/nix-push +++ b/scripts/nix-push @@ -33,7 +33,7 @@ foreach my $hash (@ARGV) { if ($path =~ /-s-([0-9a-z]{32}).nix$/) { $name = "$name-s-$1"; } - $name = $name . ".nar"; + $name = $name . ".nar.bz2"; # Construct a Fix expression that creates a Nix archive. my $fixexpr = @@ -65,4 +65,6 @@ foreach my $hash (@ARGV) { } # Push the prebuilts to the server. !!! FIXME -system "rsync -av -e ssh @pushlist eelco\@losser.st-lab.cs.uu.nl:/home/eelco/public_html/nix-dist/"; +if (scalar @pushlist > 0) { + system "rsync -av -e ssh @pushlist eelco\@losser.st-lab.cs.uu.nl:/home/eelco/public_html/nix-dist/"; +} From c834a5c5975b9a62413b4aa9446f73d1c573c909 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 11 Jul 2003 08:16:15 +0000 Subject: [PATCH 0128/6440] * Fix handling of pipes (read(2) may not return the required number of bytes in one call). --- src/nix.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index 19c73165c..8fc0fa2c3 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -250,11 +250,13 @@ struct StdinSource : RestoreSource { virtual void operator () (const unsigned char * data, unsigned int len) { - ssize_t res = read(STDIN_FILENO, (char *) data, len); - if (res == -1) - throw SysError("reading from stdin"); - if (res != (ssize_t) len) - throw Error("not enough data available on stdin"); + while (len) { + ssize_t res = read(STDIN_FILENO, (char *) data, len); + if (res == -1) throw SysError("reading from stdin"); + if (res == 0) throw SysError("unexpected end-of-file on stdin"); + len -= res; + data += res; + } } }; From 73b163c1a10f2ce675d9fc3d7ad02fad4bc6511f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 11 Jul 2003 08:41:03 +0000 Subject: [PATCH 0129/6440] * Fix a bug that caused Fix not to be deterministic (due to addToStore returning different paths if the hash of the path to be added was already available in the store under a different name). --- src/fix.cc | 2 +- src/store.cc | 13 ++++++------- src/store.hh | 3 ++- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index 5c4297bfb..445d68283 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -131,7 +131,7 @@ static Expr evalExpr(Expr e) string srcPath = searchPath(s1); string dstPath; Hash hash; - addToStore(srcPath, dstPath, hash); + addToStore(srcPath, dstPath, hash, true); return ATmake("Path(, Hash(), [])", dstPath.c_str(), ((string) hash).c_str()); } diff --git a/src/store.cc b/src/store.cc index 435ac5cc6..12de50ec6 100644 --- a/src/store.cc +++ b/src/store.cc @@ -224,24 +224,23 @@ string expandHash(const Hash & hash, const string & target, } -void addToStore(string srcPath, string & dstPath, Hash & hash) +void addToStore(string srcPath, string & dstPath, Hash & hash, + bool deterministicName) { srcPath = absPath(srcPath); - hash = hashPath(srcPath); + string baseName = baseNameOf(srcPath); + dstPath = canonPath(nixStore + "/" + (string) hash + "-" + baseName); + try { /* !!! should not use the substitutes! */ - dstPath = expandHash(hash, "", nixStore); + dstPath = expandHash(hash, deterministicName ? dstPath : "", nixStore); return; } catch (...) { } - string baseName = baseNameOf(srcPath); - dstPath = canonPath(nixStore + "/" + (string) hash + "-" + baseName); - copyPath(srcPath, dstPath); - registerPath(dstPath, hash); } diff --git a/src/store.hh b/src/store.hh index 8b41478a2..82fb2e12a 100644 --- a/src/store.hh +++ b/src/store.hh @@ -30,7 +30,8 @@ string expandHash(const Hash & hash, const string & target = "", /* Copy a file to the nixStore directory and register it in dbRefs. Return the hash code of the value. */ -void addToStore(string srcPath, string & dstPath, Hash & hash); +void addToStore(string srcPath, string & dstPath, Hash & hash, + bool deterministicName = false); /* Delete a value from the nixStore directory. */ void deleteFromStore(const string & path); From 5304a1eb3a2bbcc379924d3f5a58b64ce77f4849 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 12 Jul 2003 11:03:14 +0000 Subject: [PATCH 0130/6440] * Fetchurl: check md5 checksum. --- corepkgs/fetchurl/fetchurl.fix | 4 ++-- corepkgs/fetchurl/fetchurl.sh | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/corepkgs/fetchurl/fetchurl.fix b/corepkgs/fetchurl/fetchurl.fix index a3b3d46e1..f798c0bec 100644 --- a/corepkgs/fetchurl/fetchurl.fix +++ b/corepkgs/fetchurl/fetchurl.fix @@ -1,8 +1,8 @@ -Function(["url", "hash"], +Function(["url", "md5"], Package( [ ("build", Relative("fetchurl/fetchurl.sh")) , ("url", Var("url")) - , ("hash", Var("hash")) + , ("md5", Var("md5")) , ("name", BaseName(Var("url"))) ] ) diff --git a/corepkgs/fetchurl/fetchurl.sh b/corepkgs/fetchurl/fetchurl.sh index a92092c6e..7b6243974 100644 --- a/corepkgs/fetchurl/fetchurl.sh +++ b/corepkgs/fetchurl/fetchurl.sh @@ -1,3 +1,10 @@ #! /bin/sh -wget "$url" -O "$out" +echo "downloading $url into $out..." +wget "$url" -O "$out" || exit 1 + +actual=$(md5sum -b $out | cut -c1-32) +if ! test "$actual" == "$md5"; then + echo "hash is $actual, expected $md5" + exit 1 +fi From 9c620e4afa03e63ddaff2979396144de8d9298a5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 13 Jul 2003 18:58:03 +0000 Subject: [PATCH 0131/6440] * Generate the scripts so that we can substitute the prefix etc. correctly. * Fixed nix-switch. --- scripts/Makefile.am | 10 ++++++++++ scripts/{nix-profile.sh => nix-profile.sh.in} | 10 +++++----- scripts/{nix-pull => nix-pull.in} | 7 ++----- scripts/{nix-push => nix-push.in} | 0 scripts/{nix-switch => nix-switch.in} | 17 ++++++----------- src/Makefile.am | 3 --- 6 files changed, 23 insertions(+), 24 deletions(-) rename scripts/{nix-profile.sh => nix-profile.sh.in} (70%) rename scripts/{nix-pull => nix-pull.in} (92%) rename scripts/{nix-push => nix-push.in} (100%) rename scripts/{nix-switch => nix-switch.in} (81%) diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 2f4dbacc9..a8cbe8222 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -1,9 +1,19 @@ bin_SCRIPTS = nix-switch nix-collect-garbage \ nix-pull nix-push +noinst_SCRIPTS = nix-profile.sh + install-exec-local: $(INSTALL) -d $(sysconfdir)/profile.d $(INSTALL_PROGRAM) nix-profile.sh $(sysconfdir)/profile.d/nix.sh $(INSTALL) -d $(sysconfdir)/nix # !!! don't overwrite local modifications $(INSTALL_DATA) prebuilts.conf $(sysconfdir)/nix/prebuilts.conf + +%: %.in Makefile + sed \ + -e s^@prefix\@^$(prefix)^g \ + -e s^@sysconfdir\@^$(sysconfdir)^g \ + -e s^@localstatedir\@^$(localstatedir)^g \ + < $< > $@ || rm $@ + chmod +x $@ diff --git a/scripts/nix-profile.sh b/scripts/nix-profile.sh.in similarity index 70% rename from scripts/nix-profile.sh rename to scripts/nix-profile.sh.in index fb526a239..3a64caa04 100644 --- a/scripts/nix-profile.sh +++ b/scripts/nix-profile.sh.in @@ -1,10 +1,10 @@ -if test -z "$NIX_SET"; then +#if test -z "$NIX_SET"; then - export NIX_SET=1 +# export NIX_SET=1 - NIX_LINKS=/nix/var/nix/links/current + NIX_LINKS=@localstatedir@/nix/links/current - export PATH=$NIX_LINKS/bin:/nix/bin:$PATH + export PATH=$NIX_LINKS/bin:@prefix@/bin:$PATH export LD_LIBRARY_PATH=$NIX_LINKS/lib:$LD_LIBRARY_PATH @@ -17,4 +17,4 @@ if test -z "$NIX_SET"; then # export MANPATH=$NIX_LINKS/man:$MANPATH -fi +#fi diff --git a/scripts/nix-pull b/scripts/nix-pull.in similarity index 92% rename from scripts/nix-pull rename to scripts/nix-pull.in index 320322585..a75c1f258 100644 --- a/scripts/nix-pull +++ b/scripts/nix-pull.in @@ -1,10 +1,7 @@ #! /usr/bin/perl -w -my $prefix = $ENV{"NIX"} || "/tmp/nix"; # !!! use prefix -my $etcdir = "$prefix/etc/nix"; -my $tmpfile = "$prefix/var/nix/pull.tmp"; - -my $conffile = "$etcdir/prebuilts.conf"; +my $tmpfile = "@localstatedir@/nix/pull.tmp"; +my $conffile = "@sysconfdir@/nix/prebuilts.conf"; my @subs; my @sucs; diff --git a/scripts/nix-push b/scripts/nix-push.in similarity index 100% rename from scripts/nix-push rename to scripts/nix-push.in diff --git a/scripts/nix-switch b/scripts/nix-switch.in similarity index 81% rename from scripts/nix-switch rename to scripts/nix-switch.in index d58a5f249..55305418c 100755 --- a/scripts/nix-switch +++ b/scripts/nix-switch.in @@ -12,29 +12,24 @@ if (scalar @ARGV > 0 && $ARGV[0] eq "--keep") { my $hash = $ARGV[0]; $hash || die "no package hash specified"; -my $prefix = $ENV{"NIX"} || "/nix"; # !!! use prefix -my $linkdir = "$prefix/var/nix/links"; +my $linkdir = "@localstatedir@/nix/links"; # Build the specified package, and all its dependencies. -my $pkgdir = `nix getpkg $hash`; -if ($?) { die "`nix getpkg' failed"; } +my $pkgdir = `nix -qph $hash`; +if ($?) { die "`nix -qph' failed"; } chomp $pkgdir; -my $id = `nix info $hash | cut -c 34-`; -if ($?) { die "`nix info' failed"; } -chomp $id; - # Figure out a generation number. my $nr = 0; -while (-e "$linkdir/$id-$nr") { $nr++; } -my $link = "$linkdir/$id-$nr"; +while (-e "$linkdir/$nr") { $nr++; } +my $link = "$linkdir/$nr"; # Create a symlink from $link to $pkgdir. symlink($pkgdir, $link) or die "cannot create $link: $!"; # Also store the hash of $pkgdir. This is useful for garbage # collection and the like. -my $hashfile = "$linkdir/$id-$nr.hash"; +my $hashfile = "$linkdir/$nr.hash"; open HASH, "> $hashfile" or die "cannot create $hashfile"; print HASH "$hash\n"; close HASH; diff --git a/src/Makefile.am b/src/Makefile.am index b22a56e3a..4b21f12b3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -25,9 +25,6 @@ libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c \ install-data-local: $(INSTALL) -d $(localstatedir)/nix $(INSTALL) -d $(localstatedir)/nix/links -# $(INSTALL) -d $(localstatedir)/nix/prebuilts -# $(INSTALL) -d $(localstatedir)/nix/prebuilts/imports -# $(INSTALL) -d $(localstatedir)/nix/prebuilts/exports $(INSTALL) -d $(localstatedir)/log/nix $(INSTALL) -d $(prefix)/store $(bindir)/nix --init From e6363b05ae72ffd9d977ec3f0981ff9123c404a6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 13 Jul 2003 19:26:00 +0000 Subject: [PATCH 0132/6440] * Pass $(prefix) and other variables through -D..., not through config.h, to prevent silly Autoconf problems. --- configure.ac | 6 ------ src/Makefile.am | 26 +++++++++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/configure.ac b/configure.ac index 110d8f0e1..77a5f1f1f 100644 --- a/configure.ac +++ b/configure.ac @@ -11,12 +11,6 @@ AC_PROG_CC AC_PROG_CXX AC_PROG_RANLIB -# Unix shell scripting should die a slow and painful death. -AC_DEFINE_UNQUOTED(NIX_STORE_DIR, "$(eval echo $prefix/store)", Nix store directory.) -AC_DEFINE_UNQUOTED(NIX_DATA_DIR, "$(eval echo $datadir)", Nix data directory.) -AC_DEFINE_UNQUOTED(NIX_STATE_DIR, "$(eval echo $localstatedir/nix)", Nix state directory.) -AC_DEFINE_UNQUOTED(NIX_LOG_DIR, "$(eval echo $localstatedir/log/nix)", Nix log file directory.) - AM_CONFIG_HEADER([config.h]) AC_CONFIG_FILES([Makefile src/Makefile scripts/Makefile corepkgs/Makefile]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index 4b21f12b3..d8ec50f0b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,25 +3,33 @@ check_PROGRAMS = test AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. -nix_SOURCES = nix.cc shared.cc -nix_LDADD = libnix.a -ldb_cxx-4 -lATerm +nix_SOURCES = nix.cc +nix_LDADD = libshared.a libnix.a -ldb_cxx-4 -lATerm -nix_hash_SOURCES = nix-hash.cc shared.cc -nix_hash_LDADD = libnix.a -ldb_cxx-4 -lATerm +nix_hash_SOURCES = nix-hash.cc +nix_hash_LDADD = libshared.a libnix.a -ldb_cxx-4 -lATerm -fix_SOURCES = fix.cc shared.cc -fix_LDADD = libnix.a -ldb_cxx-4 -lATerm +fix_SOURCES = fix.cc +fix_LDADD = libshared.a libnix.a -ldb_cxx-4 -lATerm TESTS = test -test_SOURCES = test.cc shared.cc -test_LDADD = libnix.a -ldb_cxx-4 -lATerm +test_SOURCES = test.cc +test_LDADD = libshared.a libnix.a -ldb_cxx-4 -lATerm -noinst_LIBRARIES = libnix.a +noinst_LIBRARIES = libnix.a libshared.a libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c \ fstate.cc store.cc globals.cc db.cc +libshared_a_SOURCES = shared.cc + +libshared_a_CXXFLAGS = \ + -DNIX_STORE_DIR=\"$(prefix)/store\" \ + -DNIX_DATA_DIR=\"$(datadir)\" \ + -DNIX_STATE_DIR=\"$(localstatedir)/nix\" \ + -DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" + install-data-local: $(INSTALL) -d $(localstatedir)/nix $(INSTALL) -d $(localstatedir)/nix/links From 135b7d54db4e0ca56bda67946432fcf9d4f3ac5c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 13 Jul 2003 21:43:57 +0000 Subject: [PATCH 0133/6440] * Don't check for staleness by default. --- src/store.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/store.cc b/src/store.cc index 12de50ec6..5feb80c9d 100644 --- a/src/store.cc +++ b/src/store.cc @@ -166,7 +166,10 @@ string expandHash(const Hash & hash, const string & target, { string path = *i; try { +#if 0 if (path == target && hashPath(path) == hash) +#endif + if (path == target && pathExists(path)) return path; } catch (Error & e) { debug(format("stale path: %1%") % e.msg()); From 3509299aca833ed50faab146f985853255041cb2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 14 Jul 2003 10:23:11 +0000 Subject: [PATCH 0134/6440] * After building, scan for actual file system references as opposed to declared references. This prunes the reference graph, thus allowing better garbage collection and more efficient derivate distribution. --- src/Makefile.am | 2 +- src/archive.cc | 4 +- src/fstate.cc | 35 +++++++++++++++-- src/fstate.hh | 6 +-- src/references.cc | 98 +++++++++++++++++++++++++++++++++++++++++++++++ src/references.hh | 10 +++++ src/store.hh | 6 +-- 7 files changed, 149 insertions(+), 12 deletions(-) create mode 100644 src/references.cc create mode 100644 src/references.hh diff --git a/src/Makefile.am b/src/Makefile.am index d8ec50f0b..3c590f4c0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,7 +20,7 @@ test_LDADD = libshared.a libnix.a -ldb_cxx-4 -lATerm noinst_LIBRARIES = libnix.a libshared.a libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c \ - fstate.cc store.cc globals.cc db.cc + fstate.cc store.cc globals.cc db.cc references.cc libshared_a_SOURCES = shared.cc diff --git a/src/archive.cc b/src/archive.cc index 7e07b8a08..c9b78824e 100644 --- a/src/archive.cc +++ b/src/archive.cc @@ -87,7 +87,7 @@ static void dumpContents(const string & path, unsigned int size, writeInt(size, sink); int fd = open(path.c_str(), O_RDONLY); - if (fd == -1) throw SysError("opening file " + path); + if (fd == -1) throw SysError(format("opening file `%1%'") % path); unsigned char buf[65536]; @@ -112,7 +112,7 @@ static void dump(const string & path, DumpSink & sink) { struct stat st; if (lstat(path.c_str(), &st)) - throw SysError("getting attributes of path " + path); + throw SysError(format("getting attributes of path `%1%'") % path); writeString("(", sink); diff --git a/src/fstate.cc b/src/fstate.cc index a597b6df6..36f7482ac 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -11,6 +11,7 @@ #include "globals.hh" #include "store.hh" #include "db.hh" +#include "references.hh" /* A Unix environment is a mapping from strings to strings. */ @@ -279,12 +280,15 @@ static FState realise(FState fs, StringSet & paths) checkPlatform(platform); /* Realise inputs. */ + Strings inPaths; ATermList ins2 = ATempty; while (!ATisEmpty(ins)) { - ins2 = ATinsert(ins2, realise(ATgetFirst(ins), paths)); + FState in = realise(ATgetFirst(ins), paths); + inPaths.push_back(fstatePath(in)); + ins2 = ATinsert(ins2, in); ins = ATgetNext(ins); } - ins2 = ATreverse(ins2); + ins = ATreverse(ins2); /* Build the environment. */ Environment env; @@ -323,9 +327,34 @@ static FState realise(FState fs, StringSet & paths) values.cc. */ registerPath(outPath, outHash); + /* Filter out inputs that are not referenced in the output. */ + for (Strings::iterator i = inPaths.begin(); + i != inPaths.end(); i++) + debug(format("in: %1%") % *i); + + Strings outPaths = filterReferences(outPath, inPaths); + + for (Strings::iterator i = outPaths.begin(); + i != outPaths.end(); i++) + debug(format("out: %1%") % *i); + + ins2 = ATempty; + while (!ATisEmpty(ins)) { + FState in = ATgetFirst(ins); + string path = fstatePath(in); + for (Strings::iterator i = outPaths.begin(); + i != outPaths.end(); i++) + if (path.find(*i) != string::npos) { + debug(format("out2: %1%") % path); + ins2 = ATinsert(ins2, in); + } + ins = ATgetNext(ins); + } + ins = ATreverse(ins2); + /* Register the normal form of fs. */ FState nf = ATmake("Path(, Hash(), )", - outPath.c_str(), ((string) outHash).c_str(), ins2); + outPath.c_str(), ((string) outHash).c_str(), ins); nf = storeSuccessor(fs, nf, paths); return nf; diff --git a/src/fstate.hh b/src/fstate.hh index 9a8955aeb..9d789c834 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -1,5 +1,5 @@ -#ifndef __EVAL_H -#define __EVAL_H +#ifndef __FSTATE_H +#define __FSTATE_H #include @@ -97,4 +97,4 @@ Hash writeTerm(ATerm t, const string & suffix, string * p = 0); void registerSuccessor(const Hash & fsHash, const Hash & scHash); -#endif /* !__EVAL_H */ +#endif /* !__FSTATE_H */ diff --git a/src/references.cc b/src/references.cc new file mode 100644 index 000000000..de7a4b339 --- /dev/null +++ b/src/references.cc @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include + +#include "references.hh" +#include "hash.hh" + + +static void search(const string & s, + Strings & refs, Strings & seen) +{ + for (Strings::iterator i = refs.begin(); + i != refs.end(); ) + { + if (s.find(*i) == string::npos) + i++; + else { + debug(format("found reference to `%1%'") % *i); + seen.push_back(*i); + i = refs.erase(i); + } + } +} + + +void checkPath(const string & path, + Strings & refs, Strings & seen) +{ + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting attributes of path `%1%'") % path); + + if (S_ISDIR(st.st_mode)) { + DIR * dir = opendir(path.c_str()); + + struct dirent * dirent; + while (errno = 0, dirent = readdir(dir)) { + string name = dirent->d_name; + if (name == "." || name == "..") continue; + search(name, refs, seen); + checkPath(path + "/" + name, refs, seen); + } + + closedir(dir); /* !!! close on exception */ + } + + else if (S_ISREG(st.st_mode)) { + + debug(format("checking `%1%'") % path); + + int fd = open(path.c_str(), O_RDONLY); + if (fd == -1) throw SysError(format("opening file `%1%'") % path); + + char * buf = new char[st.st_size]; + + if (read(fd, buf, st.st_size) != st.st_size) + throw SysError(format("reading file %1%") % path); + + search(string(buf, st.st_size), refs, seen); + + delete buf; /* !!! autodelete */ + + close(fd); /* !!! close on exception */ + } + + else if (S_ISLNK(st.st_mode)) { + char buf[st.st_size]; + if (readlink(path.c_str(), buf, st.st_size) != st.st_size) + throw SysError(format("reading symbolic link `%1%'") % path); + search(string(buf, st.st_size), refs, seen); + } + + else throw Error(format("unknown file type: %1%") % path); +} + + +Strings filterReferences(const string & path, const Strings & _refs) +{ + Strings refs; + Strings seen; + + /* For efficiency (and a higher hit rate), just search for the + hash part of the file name. (This assumes that all references + have the form `HASH-bla'). */ + for (Strings::const_iterator i = _refs.begin(); + i != _refs.end(); i++) + { + string s = string(baseNameOf(*i), 0, 32); + parseHash(s); + refs.push_back(s); + } + + checkPath(path, refs, seen); + + return seen; +} diff --git a/src/references.hh b/src/references.hh new file mode 100644 index 000000000..b19fbf72c --- /dev/null +++ b/src/references.hh @@ -0,0 +1,10 @@ +#ifndef __VALUES_H +#define __VALUES_H + +#include "util.hh" + + +Strings filterReferences(const string & path, const Strings & refs); + + +#endif /* !__VALUES_H */ diff --git a/src/store.hh b/src/store.hh index 82fb2e12a..b6ed43ff6 100644 --- a/src/store.hh +++ b/src/store.hh @@ -1,5 +1,5 @@ -#ifndef __VALUES_H -#define __VALUES_H +#ifndef __STORE_H +#define __STORE_H #include @@ -37,4 +37,4 @@ void addToStore(string srcPath, string & dstPath, Hash & hash, void deleteFromStore(const string & path); -#endif /* !__VALUES_H */ +#endif /* !__STORE_H */ From 8898e86b4fe1ecf8b34a5cca2a7b9b38d395678c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 14 Jul 2003 10:45:04 +0000 Subject: [PATCH 0135/6440] * Get the garbage collector to work again. --- scripts/nix-collect-garbage | 22 ---------------------- scripts/nix-collect-garbage.in | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 22 deletions(-) delete mode 100755 scripts/nix-collect-garbage create mode 100755 scripts/nix-collect-garbage.in diff --git a/scripts/nix-collect-garbage b/scripts/nix-collect-garbage deleted file mode 100755 index adaba5b7c..000000000 --- a/scripts/nix-collect-garbage +++ /dev/null @@ -1,22 +0,0 @@ -#! /usr/bin/perl -w - -my $prefix = $ENV{"NIX"} || "/nix"; # !!! use prefix -my $linkdir = "$prefix/var/nix/links"; - -my %alive; - -open HASHES, "nix closure \$(cat $linkdir/*.hash) |"; -while () { - chomp; - $alive{$_} = 1; -} -close HASHES; - -open HASHES, "nix listinst |"; -while () { - chomp; - if (!$alive{$_}) { - print "$_\n"; - } -} -close HASHES; diff --git a/scripts/nix-collect-garbage.in b/scripts/nix-collect-garbage.in new file mode 100755 index 000000000..1506416b2 --- /dev/null +++ b/scripts/nix-collect-garbage.in @@ -0,0 +1,25 @@ +#! /usr/bin/perl -w + +my $linkdir = "@localstatedir@/nix/links"; +my $storedir = "@prefix@/store"; + +my %alive; + +open HASHES, "nix -qrh \$(cat $linkdir/*.hash) |" or die "in `nix -qrh'"; +while () { + chomp; + $alive{$_} = 1; +} +close HASHES; + +opendir(DIR, $storedir) or die "cannot opendir $storedir: $!"; +my @names = readdir(DIR); +closedir DIR; + +foreach my $name (@names) { + next if ($name eq "." || $name eq ".."); + $name = "$storedir/$name"; + if (!$alive{$name}) { + print "$name\n"; + } +} From f5b6fa5256efce5f7a963386cd16e441446f5746 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 15 Jul 2003 16:28:54 +0000 Subject: [PATCH 0136/6440] * Basic work on allowing derive expressions to build multiple paths. This is not entirely trivial since this introduces the possibility of mutual recursion. * Made normal forms self-contained. * Use unique ids, not content hashes, for content referencing. --- src/fstate.cc | 240 +++++++++++++++++++++++++++++++++++++++++++------ src/fstate.hh | 57 ++++++++---- src/globals.cc | 6 +- src/globals.hh | 37 ++++---- src/store.cc | 91 +++++++++---------- src/store.hh | 19 ++-- src/test.cc | 111 +++++++++-------------- 7 files changed, 367 insertions(+), 194 deletions(-) diff --git a/src/fstate.cc b/src/fstate.cc index 36f7482ac..596d63a5f 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -154,55 +154,47 @@ FState hash2fstate(Hash hash) } -ATerm termFromHash(const Hash & hash, string * p) +ATerm termFromId(const FSId & id, string * p) { - string path = expandHash(hash); + string path = expandId(id); if (p) *p = path; ATerm t = ATreadFromNamedFile(path.c_str()); - if (!t) throw Error(format("cannot read aterm %1%") % path); + if (!t) throw Error(format("cannot read aterm from `%1%'") % path); return t; } -Hash writeTerm(ATerm t, const string & suffix, string * p) +FSId writeTerm(ATerm t, const string & suffix, string * p) { - string path = nixStore + "/tmp.nix"; /* !!! */ + FSId id = hashTerm(t); + + string path = canonPath(nixStore + "/" + + (string) id + suffix + ".nix"); if (!ATwriteToNamedTextFile(t, path.c_str())) throw Error(format("cannot write aterm %1%") % path); - Hash hash = hashPath(path); - string path2 = canonPath(nixStore + "/" + - (string) hash + suffix + ".nix"); - if (rename(path.c_str(), path2.c_str()) == -1) - throw SysError(format("renaming %1% to %2%") % path % path2); - registerPath(path2, hash); - if (p) *p = path2; - return hash; + + registerPath(path, id); + if (p) *p = path; + + return id; } -void registerSuccessor(const Hash & fsHash, const Hash & scHash) +void registerSuccessor(const FSId & id1, const FSId & id2) { - setDB(nixDB, dbSuccessors, fsHash, scHash); + setDB(nixDB, dbSuccessors, id1, id2); } -FState storeSuccessor(FState fs, FState sc, StringSet & paths) +static FSId storeSuccessor(const FSId & id1, FState sc) { - if (fs == sc) return sc; - - string path; - Hash fsHash = hashTerm(fs); - Hash scHash = writeTerm(sc, "-s-" + (string) fsHash, &path); - registerSuccessor(fsHash, scHash); - paths.insert(path); + FSId id2 = writeTerm(sc, "-s-" + (string) id1, 0); + registerSuccessor(id1, id2); + return id2; +} + #if 0 - return ATmake("Include()", ((string) scHash).c_str()); -#endif - return sc; -} - - static FState realise(FState fs, StringSet & paths) { char * s1, * s2, * s3; @@ -421,3 +413,193 @@ void fstateRefs(FState fs, StringSet & paths) { fstateRefs2(fs, paths); } +#endif + + +static void parseIds(ATermList ids, FSIds & out) +{ + while (!ATisEmpty(ids)) { + char * s; + ATerm id = ATgetFirst(ids); + if (!ATmatch(id, "", &s)) + throw badTerm("not an id", id); + out.push_back(parseHash(s)); + debug(s); + ids = ATgetNext(ids); + } +} + + +/* Parse a slice. */ +static Slice parseSlice(FState fs) +{ + Slice slice; + ATermList roots, elems; + + if (!ATmatch(fs, "Slice([], [])", &roots, &elems)) + throw badTerm("not a slice", fs); + + parseIds(roots, slice.roots); + + while (!ATisEmpty(elems)) { + char * s1, * s2; + ATermList refs; + ATerm t = ATgetFirst(elems); + if (!ATmatch(t, "(, , [])", &s1, &s2, &refs)) + throw badTerm("not a slice element", t); + SliceElem elem; + elem.path = s1; + elem.id = parseHash(s2); + parseIds(refs, elem.refs); + slice.elems.push_back(elem); + elems = ATgetNext(elems); + } + + return slice; +} + + +Slice normaliseFState(FSId id) +{ + debug(format("normalising fstate")); + Nest nest(true); + + /* Try to substitute $id$ by any known successors in order to + speed up the rewrite process. */ + string idSucc; + while (queryDB(nixDB, dbSuccessors, id, idSucc)) { + debug(format("successor %1% -> %2%") % (string) id % idSucc); + id = parseHash(idSucc); + } + + /* Get the fstate expression. */ + FState fs = termFromId(id); + + /* Already in normal form (i.e., a slice)? */ + if (ATgetType(fs) == AT_APPL && + (string) ATgetName(ATgetAFun(fs)) == "Slice") + return parseSlice(fs); + + /* Then we it's a Derive node. */ + ATermList outs, ins, bnds; + char * builder; + char * platform; + if (!ATmatch(fs, "Derive([], [], , , [])", + &outs, &ins, &builder, &platform, &bnds)) + throw badTerm("not a derive", fs); + + /* Right platform? */ + checkPlatform(platform); + + /* Realise inputs (and remember all input paths). */ + FSIds inIds; + parseIds(ins, inIds); + + SliceElems inElems; /* !!! duplicates */ + StringSet inPathsSet; + for (FSIds::iterator i = inIds.begin(); i != inIds.end(); i++) { + Slice slice = normaliseFState(*i); + realiseSlice(slice); + + for (SliceElems::iterator j = slice.elems.begin(); + j != slice.elems.end(); j++) + { + inElems.push_back(*j); + inPathsSet.insert(j->path); + } + } + + Strings inPaths; + copy(inPathsSet.begin(), inPathsSet.end(), + inserter(inPaths, inPaths.begin())); + + /* Build the environment. */ + Environment env; + while (!ATisEmpty(bnds)) { + char * s1, * s2; + ATerm bnd = ATgetFirst(bnds); + if (!ATmatch(bnd, "(, )", &s1, &s2)) + throw badTerm("tuple of strings expected", bnd); + env[s1] = s2; + bnds = ATgetNext(bnds); + } + + /* Check that none of the output paths exist. */ + typedef pair OutPath; + list outPaths; + while (!ATisEmpty(outs)) { + ATerm t = ATgetFirst(outs); + char * s1, * s2; + if (!ATmatch(t, "(, )", &s1, &s2)) + throw badTerm("string expected", t); + outPaths.push_back(OutPath(s1, parseHash(s2))); + outs = ATgetNext(outs); + } + + for (list::iterator i = outPaths.begin(); + i != outPaths.end(); i++) + if (pathExists(i->first)) + throw Error(format("path `%1%' exists") % i->first); + + /* Run the builder. */ + runProgram(builder, env); + + Slice slice; + + /* Check whether the output paths were created, and register each + one. */ + for (list::iterator i = outPaths.begin(); + i != outPaths.end(); i++) + { + string path = i->first; + if (!pathExists(path)) + throw Error(format("path `%1%' does not exist") % path); + registerPath(path, i->second); + slice.roots.push_back(i->second); + + Strings outPaths = filterReferences(path, inPaths); + } + + return slice; +} + + +void realiseSlice(Slice slice) +{ + debug(format("realising slice")); + Nest nest(true); + + if (slice.elems.size() == 0) + throw Error("empty slice"); + + /* Perhaps all paths already contain the right id? */ + + bool missing = false; + for (SliceElems::iterator i = slice.elems.begin(); + i != slice.elems.end(); i++) + { + SliceElem elem = *i; + string id; + if (!queryDB(nixDB, dbPath2Id, elem.path, id)) { + if (pathExists(elem.path)) + throw Error(format("path `%1%' obstructed") % elem.path); + missing = true; + break; + } + if (parseHash(id) != elem.id) + throw Error(format("path `%1%' obstructed") % elem.path); + } + + if (!missing) { + debug(format("already installed")); + return; + } + + /* For each element, expand its id at its path. */ + for (SliceElems::iterator i = slice.elems.begin(); + i != slice.elems.end(); i++) + { + SliceElem elem = *i; + expandId(elem.id, elem.path); + } +} diff --git a/src/fstate.hh b/src/fstate.hh index 9d789c834..f06a4807e 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -8,6 +8,7 @@ extern "C" { } #include "hash.hh" +#include "store.hh" using namespace std; @@ -17,33 +18,24 @@ using namespace std; A Nix file system state expression, or FState, describes a (partial) state of the file system. - Path : Path * Content * [FState] -> FState + Slice : [Id] * [(Path, Id, [Id])] -> FState + (update) Path(path, content, refs) specifies a file object (its full path and contents), along with all file objects referenced by it (that is, that it has pointers to). We assume that all files are self-referential. This prevents us from having to deal with cycles. - Derive : String * Path * [FState] * Path * [(String, String)] -> FState + Derive : [(Path, Id)] * [FStateId] * Path * [(String, String)] -> FState + (update) Derive(platform, builder, ins, outs, env) specifies the creation of new file objects (in paths declared by `outs') by the execution of a program `builder' on a platform `platform'. This execution takes place in a file system state given by `ins'. `env' specifies a mapping of strings to strings. - [ !!! NOT IMPLEMENTED - Regular : String -> Content - Directory : [(String, Content)] -> Content - (this complicates unambiguous normalisation) - ] - CHash : Hash -> Content - - File content, given either in situ, or through an external reference - to the file system or url-space decorated with a hash to preserve - purity. - A FState expression is in {\em $f$-normal form} if all Derive nodes have been reduced to File nodes. @@ -63,7 +55,26 @@ typedef ATerm Content; typedef set StringSet; +typedef list FSIds; + +struct SliceElem +{ + string path; + FSId id; + FSIds refs; +}; + +typedef list SliceElems; + +struct Slice +{ + FSIds roots; + SliceElems elems; +}; + + +#if 0 /* Realise an fstate expression in the file system. This requires execution of all Derive() nodes. */ FState realiseFState(FState fs, StringSet & paths); @@ -74,6 +85,8 @@ string fstatePath(FState fs); /* Return the paths referenced by fstate expression. */ void fstateRefs(FState fs, StringSet & paths); +#endif + /* Return a canonical textual representation of an expression. */ string printTerm(ATerm t); @@ -85,16 +98,22 @@ Error badTerm(const format & f, ATerm t); /* Hash an aterm. */ Hash hashTerm(ATerm t); -FState hash2fstate(Hash hash); - -/* Read an aterm from disk, given its hash. */ -ATerm termFromHash(const Hash & hash, string * p = 0); +/* Read an aterm from disk, given its id. */ +ATerm termFromId(const FSId & id, string * p = 0); /* Write an aterm to the Nix store directory, and return its hash. */ -Hash writeTerm(ATerm t, const string & suffix, string * p = 0); +FSId writeTerm(ATerm t, const string & suffix, string * p = 0); /* Register a successor. */ -void registerSuccessor(const Hash & fsHash, const Hash & scHash); +void registerSuccessor(const FSId & id1, const FSId & id2); + + +/* Normalise an fstate-expression, that is, return an equivalent + Slice. */ +Slice normaliseFState(FSId id); + +/* Realise a Slice in the file system. */ +void realiseSlice(Slice slice); #endif /* !__FSTATE_H */ diff --git a/src/globals.cc b/src/globals.cc index 9893d7ad2..1edb38f74 100644 --- a/src/globals.cc +++ b/src/globals.cc @@ -2,7 +2,8 @@ #include "db.hh" -string dbHash2Paths = "hash2paths"; +string dbPath2Id = "path2id"; +string dbId2Paths = "id2paths"; string dbSuccessors = "successors"; string dbSubstitutes = "substitutes"; @@ -15,7 +16,8 @@ string nixDB = "/UNINIT"; void initDB() { - createDB(nixDB, dbHash2Paths); + createDB(nixDB, dbPath2Id); + createDB(nixDB, dbId2Paths); createDB(nixDB, dbSuccessors); createDB(nixDB, dbSubstitutes); } diff --git a/src/globals.hh b/src/globals.hh index 0668ac40e..fbb020df7 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -8,33 +8,36 @@ using namespace std; /* Database names. */ -/* dbHash2Paths :: Hash -> [Path] +/* dbPath2Id :: Path -> FSId - Maintains a mapping from hashes to lists of paths. This is what we - use to resolve Hash(hash) content descriptors. */ -extern string dbHash2Paths; + Each pair (p, id) records that path $p$ contains an expansion of + $id$. */ +extern string dbPath2Id; -/* dbSuccessors :: Hash -> Hash - Each pair (h1, h2) in this mapping records the fact that a - successor of an fstate expression with hash h1 is stored in a file - with hash h2. +/* dbId2Paths :: FSId -> [Path] + + A mapping from ids to lists of paths. */ +extern string dbId2Paths; + + +/* dbSuccessors :: FSId -> FSId + + Each pair $(id_1, id_2)$ in this mapping records the fact that a + successor of an fstate expression stored in a file with identifier + $id_1$ is stored in a file with identifier $id_2$. Note that a term $y$ is successor of $x$ iff there exists a sequence of rewrite steps that rewrites $x$ into $y$. - - Also note that instead of a successor, $y$ can be any term - equivalent to $x$, that is, reducing to the same result, as long as - $x$ is equal to or a successor of $y$. (This is useful, e.g., for - shared derivate caching over the network). */ extern string dbSuccessors; -/* dbSubstitutes :: Hash -> [Hash] - Each pair $(h, [hs])$ tells Nix that it can realise any of the - fstate expressions referenced by the hashes in $hs$ to obtain a Nix - archive that, when unpacked, will produce a path with hash $h$. +/* dbSubstitutes :: FSId -> [FSId] + + Each pair $(id, [ids])$ tells Nix that it can realise any of the + fstate expressions referenced by the identifiers in $ids$ to + generate a path with identifier $id$. The main purpose of this is for distributed caching of derivates. One system can compute a derivate with hash $h$ and put it on a diff --git a/src/store.cc b/src/store.cc index 5feb80c9d..c8b3bd37f 100644 --- a/src/store.cc +++ b/src/store.cc @@ -84,39 +84,36 @@ void copyPath(string src, string dst) } -void registerSubstitute(const Hash & srcHash, const Hash & subHash) +void registerSubstitute(const FSId & srcId, const FSId & subId) { Strings subs; - queryListDB(nixDB, dbSubstitutes, srcHash, subs); /* non-existence = ok */ + queryListDB(nixDB, dbSubstitutes, srcId, subs); /* non-existence = ok */ for (Strings::iterator it = subs.begin(); it != subs.end(); it++) - if (parseHash(*it) == subHash) return; + if (parseHash(*it) == subId) return; - subs.push_back(subHash); + subs.push_back(subId); - setListDB(nixDB, dbSubstitutes, srcHash, subs); + setListDB(nixDB, dbSubstitutes, srcId, subs); } -Hash registerPath(const string & _path, Hash hash) +void registerPath(const string & _path, const FSId & id) { string path(canonPath(_path)); - if (hash == Hash()) hash = hashPath(path); + setDB(nixDB, dbPath2Id, path, id); Strings paths; - queryListDB(nixDB, dbHash2Paths, hash, paths); /* non-existence = ok */ + queryListDB(nixDB, dbId2Paths, id, paths); /* non-existence = ok */ for (Strings::iterator it = paths.begin(); it != paths.end(); it++) - if (*it == path) goto exists; + if (*it == path) return; paths.push_back(path); - setListDB(nixDB, dbHash2Paths, hash, paths); - - exists: - return hash; + setListDB(nixDB, dbId2Paths, id, paths); } @@ -124,10 +121,15 @@ void unregisterPath(const string & _path) { string path(canonPath(_path)); - Hash hash = hashPath(path); + string _id; + if (!queryDB(nixDB, dbPath2Id, path, _id)) + return; + FSId id(parseHash(_id)); + + /* begin transaction */ Strings paths, paths2; - queryListDB(nixDB, dbHash2Paths, hash, paths); /* non-existence = ok */ + queryListDB(nixDB, dbId2Paths, id, paths); /* non-existence = ok */ bool changed = false; for (Strings::iterator it = paths.begin(); @@ -135,7 +137,9 @@ void unregisterPath(const string & _path) if (*it != path) paths2.push_back(*it); else changed = true; if (changed) - setListDB(nixDB, dbHash2Paths, hash, paths2); + setListDB(nixDB, dbId2Paths, id, paths2); + + /* end transaction */ } @@ -146,7 +150,7 @@ bool isInPrefix(const string & path, const string & _prefix) } -string expandHash(const Hash & hash, const string & target, +string expandId(const FSId & id, const string & target, const string & prefix) { Strings paths; @@ -154,9 +158,7 @@ string expandHash(const Hash & hash, const string & target, if (!target.empty() && !isInPrefix(target, prefix)) abort(); - queryListDB(nixDB, dbHash2Paths, hash, paths); - - /* !!! we shouldn't check for staleness by default --- too slow */ + queryListDB(nixDB, dbId2Paths, id, paths); /* Pick one equal to `target'. */ if (!target.empty()) { @@ -165,16 +167,8 @@ string expandHash(const Hash & hash, const string & target, i != paths.end(); i++) { string path = *i; - try { -#if 0 - if (path == target && hashPath(path) == hash) -#endif - if (path == target && pathExists(path)) - return path; - } catch (Error & e) { - debug(format("stale path: %1%") % e.msg()); - /* try next one */ - } + if (path == target && pathExists(path)) + return path; } } @@ -184,28 +178,26 @@ string expandHash(const Hash & hash, const string & target, it != paths.end(); it++) { string path = *it; - try { - if (isInPrefix(path, prefix) && hashPath(path) == hash) { - if (target.empty()) - return path; - else { - copyPath(path, target); - return target; - } + if (isInPrefix(path, prefix) && pathExists(path)) { + if (target.empty()) + return path; + else { + copyPath(path, target); + registerPath(target, id); + return target; } - } catch (Error & e) { - debug(format("stale path: %1%") % e.msg()); - /* try next one */ } } +#if 0 /* Try to realise the substitutes. */ Strings subs; - queryListDB(nixDB, dbSubstitutes, hash, subs); /* non-existence = ok */ + queryListDB(nixDB, dbSubstitutes, id, subs); /* non-existence = ok */ for (Strings::iterator it = subs.begin(); it != subs.end(); it++) { - StringSet dummy; + realiseSlice(normaliseFState(*it)); + FState nf = realiseFState(hash2fstate(parseHash(*it)), dummy); string path = fstatePath(nf); @@ -222,29 +214,30 @@ string expandHash(const Hash & hash, const string & target, return target; } } +#endif - throw Error(format("cannot expand hash `%1%'") % (string) hash); + throw Error(format("cannot expand id `%1%'") % (string) id); } -void addToStore(string srcPath, string & dstPath, Hash & hash, +void addToStore(string srcPath, string & dstPath, FSId & id, bool deterministicName) { srcPath = absPath(srcPath); - hash = hashPath(srcPath); + id = hashPath(srcPath); string baseName = baseNameOf(srcPath); - dstPath = canonPath(nixStore + "/" + (string) hash + "-" + baseName); + dstPath = canonPath(nixStore + "/" + (string) id + "-" + baseName); try { /* !!! should not use the substitutes! */ - dstPath = expandHash(hash, deterministicName ? dstPath : "", nixStore); + dstPath = expandId(id, deterministicName ? dstPath : "", nixStore); return; } catch (...) { } copyPath(srcPath, dstPath); - registerPath(dstPath, hash); + registerPath(dstPath, id); } diff --git a/src/store.hh b/src/store.hh index b6ed43ff6..7dd0f72e6 100644 --- a/src/store.hh +++ b/src/store.hh @@ -8,29 +8,28 @@ using namespace std; +typedef Hash FSId; + + /* Copy a path recursively. */ void copyPath(string src, string dst); /* Register a substitute. */ -void registerSubstitute(const Hash & srcHash, const Hash & subHash); +void registerSubstitute(const FSId & srcId, const FSId & subId); -/* Register a path keyed on its hash. */ -Hash registerPath(const string & path, Hash hash = Hash()); +/* Register a path keyed on its id. */ +void registerPath(const string & path, const FSId & id); /* Return a path whose contents have the given hash. If target is not empty, ensure that such a path is realised in target (if necessary by copying from another location). If prefix is not - empty, only return a path that is an descendent of prefix. - - If no path with the given hash is known to exist in the file - system, -*/ -string expandHash(const Hash & hash, const string & target = "", + empty, only return a path that is an descendent of prefix. */ +string expandId(const FSId & id, const string & target = "", const string & prefix = "/"); /* Copy a file to the nixStore directory and register it in dbRefs. Return the hash code of the value. */ -void addToStore(string srcPath, string & dstPath, Hash & hash, +void addToStore(string srcPath, string & dstPath, FSId & id, bool deterministicName = false); /* Delete a value from the nixStore directory. */ diff --git a/src/test.cc b/src/test.cc index c2a1cd3bf..3437650ac 100644 --- a/src/test.cc +++ b/src/test.cc @@ -11,14 +11,15 @@ #include "globals.hh" -void realise(FState fs) +void realise(FSId id) { - cout << format("%1% => %2%\n") - % printTerm(fs) - % printTerm(realiseFState(fs)); + cout << format("realising %1%\n") % (string) id; + Slice slice = normaliseFState(id); + realiseSlice(slice); } +#if 0 void realiseFail(FState fs) { try { @@ -28,6 +29,7 @@ void realiseFail(FState fs) cout << "error (expected): " << e.what() << endl; } } +#endif struct MySink : DumpSink @@ -111,54 +113,47 @@ void runTests() /* Expression evaluation. */ -#if 0 - eval(whNormalise, - ATmake("Str(\"Hello World\")")); - eval(whNormalise, - ATmake("Bool(True)")); - eval(whNormalise, - ATmake("Bool(False)")); - eval(whNormalise, - ATmake("App(Lam(\"x\", Var(\"x\")), Str(\"Hello World\"))")); - eval(whNormalise, - ATmake("App(App(Lam(\"x\", Lam(\"y\", Var(\"x\"))), Str(\"Hello World\")), Str(\"Hallo Wereld\"))")); - eval(whNormalise, - ATmake("App(Lam(\"sys\", Lam(\"x\", [Var(\"x\"), Var(\"sys\")])), Str(\"i686-suse-linux\"))")); - - evalFail(whNormalise, - ATmake("Foo(123)")); - - string builder1fn = absPath("./test-builder-1.sh"); - Hash builder1h = hashPath(builder1fn); - - string fn1 = nixValues + "/builder-1.sh"; - Expr e1 = ATmake("Path(, ExtFile(, ), [])", - fn1.c_str(), - builder1h.c_str(), - builder1fn.c_str()); - eval(fNormalise, e1); - - string fn2 = nixValues + "/refer.txt"; - Expr e2 = ATmake("Path(, Regular(), [])", - fn2.c_str(), - ("I refer to " + fn1).c_str(), - e1); - eval(fNormalise, e2); - - realise(e2); -#endif - - Hash builder1h; + FSId builder1id; string builder1fn; - addToStore("./test-builder-1.sh", builder1fn, builder1h); + addToStore("./test-builder-1.sh", builder1fn, builder1id); FState fs1 = ATmake( - "Path(, Hash(), [])", + "Slice([], [(, , [])])", + ((string) builder1id).c_str(), builder1fn.c_str(), - ((string) builder1h).c_str()); - realise(fs1); - realise(fs1); + ((string) builder1id).c_str()); + FSId fs1id = writeTerm(fs1, "", 0); + realise(fs1id); + realise(fs1id); + + FState fs2 = ATmake( + "Slice([], [(, , [])])", + ((string) builder1id).c_str(), + (builder1fn + "_bla").c_str(), + ((string) builder1id).c_str()); + FSId fs2id = writeTerm(fs2, "", 0); + + realise(fs2id); + realise(fs2id); + + string out1fn = nixStore + "/hello.txt"; + string out1id = hashString("foo"); /* !!! bad */ + FState fs3 = ATmake( + "Derive([(, )], [], , , [(\"out\", )])", + out1fn.c_str(), + ((string) out1id).c_str(), + ((string) fs1id).c_str(), + ((string) builder1fn).c_str(), + thisSystem.c_str(), + out1fn.c_str()); + debug(printTerm(fs3)); + FSId fs3id = writeTerm(fs3, "", 0); + + realise(fs3id); + realise(fs3id); + +#if 0 FState fs2 = ATmake( "Path(, Hash(), [])", (builder1fn + "_bla").c_str(), @@ -175,28 +170,8 @@ void runTests() out1fn.c_str(), out1fn.c_str()); realise(fs3); - -#if 0 - Expr e1 = ATmake("Exec(Str(), Hash(), [])", - thisSystem.c_str(), ((string) builder1).c_str()); - - eval(e1); - - Hash builder2 = addValue("./test-builder-2.sh"); - - Expr e2 = ATmake( - "Exec(Str(), Hash(), [Tup(Str(\"src\"), )])", - thisSystem.c_str(), ((string) builder2).c_str(), e1); - - eval(e2); - - Hash h3 = addValue("./test-expr-1.nix"); - Expr e3 = ATmake("Deref(Hash())", ((string) h3).c_str()); - - eval(e3); - - deleteValue(h3); #endif + } From 7b3f44e05baa49b26dd7c1ed71265c6bbc946aa4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 15 Jul 2003 21:24:05 +0000 Subject: [PATCH 0137/6440] * The new normaliser now passes the unit tests. --- src/fstate.cc | 118 ++++++++++++++++++++++++++++++++++++------ src/fstate.hh | 2 +- src/hash.cc | 10 ++++ src/hash.hh | 3 ++ src/references.cc | 44 ++++++++++------ src/test-builder-2.sh | 2 +- src/test.cc | 34 +++++++++++- 7 files changed, 179 insertions(+), 34 deletions(-) diff --git a/src/fstate.cc b/src/fstate.cc index 596d63a5f..aad961ef5 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -459,6 +459,37 @@ static Slice parseSlice(FState fs) } +static ATermList unparseIds(const FSIds & ids) +{ + ATermList l = ATempty; + for (FSIds::const_iterator i = ids.begin(); + i != ids.end(); i++) + l = ATinsert(l, + ATmake("", ((string) *i).c_str())); + return ATreverse(l); +} + + +static FState unparseSlice(const Slice & slice) +{ + ATermList roots = unparseIds(slice.roots); + + ATermList elems = ATempty; + for (SliceElems::const_iterator i = slice.elems.begin(); + i != slice.elems.end(); i++) + elems = ATinsert(elems, + ATmake("(, , )", + i->path.c_str(), + ((string) i->id).c_str(), + unparseIds(i->refs))); + + return ATmake("Slice(, )", roots, elems); +} + + +typedef set FSIdSet; + + Slice normaliseFState(FSId id) { debug(format("normalising fstate")); @@ -494,24 +525,23 @@ Slice normaliseFState(FSId id) /* Realise inputs (and remember all input paths). */ FSIds inIds; parseIds(ins, inIds); - - SliceElems inElems; /* !!! duplicates */ - StringSet inPathsSet; + + typedef map ElemMap; + + ElemMap inMap; + for (FSIds::iterator i = inIds.begin(); i != inIds.end(); i++) { Slice slice = normaliseFState(*i); realiseSlice(slice); for (SliceElems::iterator j = slice.elems.begin(); j != slice.elems.end(); j++) - { - inElems.push_back(*j); - inPathsSet.insert(j->path); - } + inMap[j->path] = *j; } Strings inPaths; - copy(inPathsSet.begin(), inPathsSet.end(), - inserter(inPaths, inPaths.begin())); + for (ElemMap::iterator i = inMap.begin(); i != inMap.end(); i++) + inPaths.push_back(i->second.path); /* Build the environment. */ Environment env; @@ -533,6 +563,7 @@ Slice normaliseFState(FSId id) if (!ATmatch(t, "(, )", &s1, &s2)) throw badTerm("string expected", t); outPaths.push_back(OutPath(s1, parseHash(s2))); + inPaths.push_back(s1); outs = ATgetNext(outs); } @@ -548,6 +579,7 @@ Slice normaliseFState(FSId id) /* Check whether the output paths were created, and register each one. */ + FSIdSet used; for (list::iterator i = outPaths.begin(); i != outPaths.end(); i++) { @@ -557,25 +589,81 @@ Slice normaliseFState(FSId id) registerPath(path, i->second); slice.roots.push_back(i->second); - Strings outPaths = filterReferences(path, inPaths); + Strings refs = filterReferences(path, inPaths); + + SliceElem elem; + elem.path = path; + elem.id = i->second; + + for (Strings::iterator j = refs.begin(); j != refs.end(); j++) { + ElemMap::iterator k; + if ((k = inMap.find(*j)) != inMap.end()) { + elem.refs.push_back(k->second.id); + used.insert(k->second.id); + } else abort(); /* fix! check in created paths */ + } + + slice.elems.push_back(elem); } + for (ElemMap::iterator i = inMap.begin(); + i != inMap.end(); i++) + { + FSIdSet::iterator j = used.find(i->second.id); + if (j == used.end()) + debug(format("NOT referenced: `%1%'") % i->second.path); + else { + debug(format("referenced: `%1%'") % i->second.path); + slice.elems.push_back(i->second); + } + } + + FState nf = unparseSlice(slice); + debug(printTerm(nf)); + storeSuccessor(id, nf); + return slice; } -void realiseSlice(Slice slice) +static void checkSlice(const Slice & slice) +{ + if (slice.elems.size() == 0) + throw Error("empty slice"); + + FSIdSet decl; + for (SliceElems::const_iterator i = slice.elems.begin(); + i != slice.elems.end(); i++) + { + debug((string) i->id); + decl.insert(i->id); + } + + for (FSIds::const_iterator i = slice.roots.begin(); + i != slice.roots.end(); i++) + if (decl.find(*i) == decl.end()) + throw Error(format("undefined id: %1%") % (string) *i); + + for (SliceElems::const_iterator i = slice.elems.begin(); + i != slice.elems.end(); i++) + for (FSIds::const_iterator j = i->refs.begin(); + j != i->refs.end(); j++) + if (decl.find(*j) == decl.end()) + throw Error(format("undefined id: %1%") % (string) *j); +} + + +void realiseSlice(const Slice & slice) { debug(format("realising slice")); Nest nest(true); - if (slice.elems.size() == 0) - throw Error("empty slice"); + checkSlice(slice); /* Perhaps all paths already contain the right id? */ bool missing = false; - for (SliceElems::iterator i = slice.elems.begin(); + for (SliceElems::const_iterator i = slice.elems.begin(); i != slice.elems.end(); i++) { SliceElem elem = *i; @@ -596,7 +684,7 @@ void realiseSlice(Slice slice) } /* For each element, expand its id at its path. */ - for (SliceElems::iterator i = slice.elems.begin(); + for (SliceElems::const_iterator i = slice.elems.begin(); i != slice.elems.end(); i++) { SliceElem elem = *i; diff --git a/src/fstate.hh b/src/fstate.hh index f06a4807e..a19528164 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -113,7 +113,7 @@ void registerSuccessor(const FSId & id1, const FSId & id2); Slice normaliseFState(FSId id); /* Realise a Slice in the file system. */ -void realiseSlice(Slice slice); +void realiseSlice(const Slice & slice); #endif /* !__FSTATE_H */ diff --git a/src/hash.cc b/src/hash.cc index 72dcd790c..97bf8f785 100644 --- a/src/hash.cc +++ b/src/hash.cc @@ -28,6 +28,16 @@ bool Hash::operator != (Hash h2) } +bool Hash::operator < (const Hash & h) const +{ + for (unsigned int i = 0; i < hashSize; i++) { + if (hash[i] < h.hash[i]) return true; + if (hash[i] > h.hash[i]) return false; + } + return false; +} + + Hash::operator string() const { ostringstream str; diff --git a/src/hash.hh b/src/hash.hh index 509a27912..5ae5dc22a 100644 --- a/src/hash.hh +++ b/src/hash.hh @@ -22,6 +22,9 @@ struct Hash /* Check whether two hash are not equal. */ bool operator != (Hash h2); + /* For sorting. */ + bool operator < (const Hash & h) const; + /* Convert a hash code into a hexadecimal representation. */ operator string() const; }; diff --git a/src/references.cc b/src/references.cc index de7a4b339..a42c1aed0 100644 --- a/src/references.cc +++ b/src/references.cc @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -9,24 +11,24 @@ static void search(const string & s, - Strings & refs, Strings & seen) + Strings & ids, Strings & seen) { - for (Strings::iterator i = refs.begin(); - i != refs.end(); ) + for (Strings::iterator i = ids.begin(); + i != ids.end(); ) { if (s.find(*i) == string::npos) i++; else { debug(format("found reference to `%1%'") % *i); seen.push_back(*i); - i = refs.erase(i); + i = ids.erase(i); } } } void checkPath(const string & path, - Strings & refs, Strings & seen) + Strings & ids, Strings & seen) { struct stat st; if (lstat(path.c_str(), &st)) @@ -39,8 +41,8 @@ void checkPath(const string & path, while (errno = 0, dirent = readdir(dir)) { string name = dirent->d_name; if (name == "." || name == "..") continue; - search(name, refs, seen); - checkPath(path + "/" + name, refs, seen); + search(name, ids, seen); + checkPath(path + "/" + name, ids, seen); } closedir(dir); /* !!! close on exception */ @@ -58,7 +60,7 @@ void checkPath(const string & path, if (read(fd, buf, st.st_size) != st.st_size) throw SysError(format("reading file %1%") % path); - search(string(buf, st.st_size), refs, seen); + search(string(buf, st.st_size), ids, seen); delete buf; /* !!! autodelete */ @@ -69,30 +71,40 @@ void checkPath(const string & path, char buf[st.st_size]; if (readlink(path.c_str(), buf, st.st_size) != st.st_size) throw SysError(format("reading symbolic link `%1%'") % path); - search(string(buf, st.st_size), refs, seen); + search(string(buf, st.st_size), ids, seen); } else throw Error(format("unknown file type: %1%") % path); } -Strings filterReferences(const string & path, const Strings & _refs) +Strings filterReferences(const string & path, const Strings & paths) { - Strings refs; + map backMap; + Strings ids; Strings seen; /* For efficiency (and a higher hit rate), just search for the hash part of the file name. (This assumes that all references have the form `HASH-bla'). */ - for (Strings::const_iterator i = _refs.begin(); - i != _refs.end(); i++) + for (Strings::const_iterator i = paths.begin(); + i != paths.end(); i++) { string s = string(baseNameOf(*i), 0, 32); parseHash(s); - refs.push_back(s); + ids.push_back(s); + backMap[s] = *i; } - checkPath(path, refs, seen); + checkPath(path, ids, seen); - return seen; + Strings found; + for (Strings::iterator i = seen.begin(); i != seen.end(); i++) + { + map::iterator j; + if ((j = backMap.find(*i)) == backMap.end()) abort(); + found.push_back(j->second); + } + + return found; } diff --git a/src/test-builder-2.sh b/src/test-builder-2.sh index 010e1c805..a12fa27a6 100644 --- a/src/test-builder-2.sh +++ b/src/test-builder-2.sh @@ -5,4 +5,4 @@ echo "builder 2" mkdir $out || exit 1 cd $out || exit 1 echo "Hallo Wereld" > bla -cat $src >> bla \ No newline at end of file +echo $builder >> bla diff --git a/src/test.cc b/src/test.cc index 3437650ac..219281c8b 100644 --- a/src/test.cc +++ b/src/test.cc @@ -137,8 +137,8 @@ void runTests() realise(fs2id); realise(fs2id); - string out1fn = nixStore + "/hello.txt"; string out1id = hashString("foo"); /* !!! bad */ + string out1fn = nixStore + "/" + (string) out1id + "-hello.txt"; FState fs3 = ATmake( "Derive([(, )], [], , , [(\"out\", )])", out1fn.c_str(), @@ -153,6 +153,38 @@ void runTests() realise(fs3id); realise(fs3id); + + FSId builder4id; + string builder4fn; + addToStore("./test-builder-2.sh", builder4fn, builder4id); + + FState fs4 = ATmake( + "Slice([], [(, , [])])", + ((string) builder4id).c_str(), + builder4fn.c_str(), + ((string) builder4id).c_str()); + FSId fs4id = writeTerm(fs4, "", 0); + + realise(fs4id); + + string out5id = hashString("bar"); /* !!! bad */ + string out5fn = nixStore + "/" + (string) out5id + "-hello2"; + FState fs5 = ATmake( + "Derive([(, )], [], , , [(\"out\", ), (\"builder\", )])", + out5fn.c_str(), + ((string) out5id).c_str(), + ((string) fs4id).c_str(), + ((string) builder4fn).c_str(), + thisSystem.c_str(), + out5fn.c_str(), + ((string) builder4fn).c_str()); + debug(printTerm(fs5)); + FSId fs5id = writeTerm(fs5, "", 0); + + realise(fs5id); + realise(fs5id); + + #if 0 FState fs2 = ATmake( "Path(, Hash(), [])", From d41d085b771d0f87658fe22512178603b3a0c633 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 15 Jul 2003 22:28:27 +0000 Subject: [PATCH 0138/6440] * Get Fix and Nix to work again. --- src/fix.cc | 47 +++++---- src/fstate.cc | 265 ++++++++------------------------------------------ src/fstate.hh | 2 + src/hash.cc | 4 +- src/hash.hh | 4 +- src/nix.cc | 39 ++++---- 6 files changed, 93 insertions(+), 268 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index 445d68283..f5f92a87b 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -107,11 +107,10 @@ static Expr evalExpr(Expr e) /* Normal forms. */ if (ATmatch(e, "", &s1) || - ATmatch(e, "Function([], )", &e1, &e2)) + ATmatch(e, "Function([], )", &e1, &e2) || + ATmatch(e, "FSId()", &s1)) return e; - if (fstatePath(e) != "") return e; /* !!! hack */ - /* Application. */ if (ATmatch(e, "App(, [])", &e1, &e2)) { e1 = evalExpr(e1); @@ -130,10 +129,12 @@ static Expr evalExpr(Expr e) if (ATmatch(e, "Relative()", &s1)) { string srcPath = searchPath(s1); string dstPath; - Hash hash; - addToStore(srcPath, dstPath, hash, true); - return ATmake("Path(, Hash(), [])", - dstPath.c_str(), ((string) hash).c_str()); + FSId id; + addToStore(srcPath, dstPath, id, true); + FState fs = ATmake("Slice([], [(, , [])])", + ((string) id).c_str(), dstPath.c_str(), ((string) id).c_str()); + return ATmake("FSId()", + ((string) writeTerm(fs, "", 0)).c_str()); } /* Packages are transformed into Derive fstate expressions. */ @@ -160,10 +161,13 @@ static Expr evalExpr(Expr e) { string key = it->first; ATerm value = it->second; + char * id; - string path = fstatePath(value); - if (path != "") { - ins = ATinsert(ins, value); + if (ATmatch(value, "FSId()", &id)) { + Strings paths = fstatePaths(parseHash(id)); + if (paths.size() != 1) abort(); + string path = *(paths.begin()); + ins = ATinsert(ins, ATmake("", id)); env = ATinsert(env, ATmake("(, )", key.c_str(), path.c_str())); if (key == "build") builder = path; @@ -182,7 +186,7 @@ static Expr evalExpr(Expr e) /* Hash the normal form to produce a unique but deterministic path name for this package. */ ATerm nf = ATmake("Package()", ATreverse(bnds)); - Hash hash = hashTerm(nf); + FSId outId = hashTerm(nf); if (builder == "") throw badTerm("no builder specified", nf); @@ -190,19 +194,20 @@ static Expr evalExpr(Expr e) if (name == "") throw badTerm("no package name specified", nf); - string out = - canonPath(nixStore + "/" + ((string) hash).c_str() + "-" + name); - - env = ATinsert(env, ATmake("(, )", "out", out.c_str())); + string outPath = + canonPath(nixStore + "/" + ((string) outId).c_str() + "-" + name); + env = ATinsert(env, ATmake("(, )", "out", outPath.c_str())); + /* Construct the result. */ - e = ATmake("Derive(, , , , )", - SYSTEM, builder.c_str(), ins, out.c_str(), env); + FState fs = + ATmake("Derive([(, )], , , , )", + outPath.c_str(), ((string) outId).c_str(), + ins, builder.c_str(), SYSTEM, env); /* Write the resulting term into the Nix store directory. */ - Hash eHash = writeTerm(e, "-d-" + name); - - return ATmake("Include()", ((string) eHash).c_str()); + return ATmake("FSId()", + ((string) writeTerm(fs, "-d-" + name, 0)).c_str()); } /* BaseName primitive function. */ @@ -258,7 +263,7 @@ void run(Strings args) { Expr e = evalFile(*it); char * s; - if (ATmatch(e, "Include()", &s)) { + if (ATmatch(e, "FSId()", &s)) { cout << format("%1%\n") % s; } else throw badTerm("top level is not a package", e); diff --git a/src/fstate.cc b/src/fstate.cc index aad961ef5..d15219344 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -194,228 +194,6 @@ static FSId storeSuccessor(const FSId & id1, FState sc) } -#if 0 -static FState realise(FState fs, StringSet & paths) -{ - char * s1, * s2, * s3; - Content content; - ATermList refs, ins, bnds; - - /* First repeatedly try to substitute $fs$ by any known successors - in order to speed up the rewrite process. */ - { - string fsHash, scHash; - while (queryDB(nixDB, dbSuccessors, fsHash = hashTerm(fs), scHash)) { - debug(format("successor %1% -> %2%") % (string) fsHash % scHash); - string path; - FState fs2 = termFromHash(parseHash(scHash), &path); - paths.insert(path); - if (fs == fs2) { - debug(format("successor cycle detected in %1%") % printTerm(fs)); - break; - } - fs = fs2; - } - } - - /* Fall through. */ - - if (ATmatch(fs, "Include()", &s1)) { - string path; - fs = termFromHash(parseHash(s1), &path); - paths.insert(path); - return realise(fs, paths); - } - - else if (ATmatch(fs, "Path(, , [])", &s1, &content, &refs)) { - string path(s1); - - msg(format("realising atomic path %1%") % path); - Nest nest(true); - - if (path[0] != '/') - throw Error(format("path `%1% is not absolute") % path); - - /* Realise referenced paths. */ - ATermList refs2 = ATempty; - while (!ATisEmpty(refs)) { - refs2 = ATinsert(refs2, realise(ATgetFirst(refs), paths)); - refs = ATgetNext(refs); - } - refs2 = ATreverse(refs2); - - if (!ATmatch(content, "Hash()", &s1)) - throw badTerm("hash expected", content); - Hash hash = parseHash(s1); - - /* Normal form. */ - ATerm nf = ATmake("Path(, , )", - path.c_str(), content, refs2); - - /* Register the normal form. */ - nf = storeSuccessor(fs, nf, paths); - - /* Expand the hash into the target path. */ - expandHash(hash, path); - - return nf; - } - - else if (ATmatch(fs, "Derive(, , [], , [])", - &s1, &s2, &ins, &s3, &bnds)) - { - string platform(s1), builder(s2), outPath(s3); - - msg(format("realising derivate path %1%") % outPath); - Nest nest(true); - - checkPlatform(platform); - - /* Realise inputs. */ - Strings inPaths; - ATermList ins2 = ATempty; - while (!ATisEmpty(ins)) { - FState in = realise(ATgetFirst(ins), paths); - inPaths.push_back(fstatePath(in)); - ins2 = ATinsert(ins2, in); - ins = ATgetNext(ins); - } - ins = ATreverse(ins2); - - /* Build the environment. */ - Environment env; - while (!ATisEmpty(bnds)) { - ATerm bnd = ATgetFirst(bnds); - if (!ATmatch(bnd, "(, )", &s1, &s2)) - throw badTerm("tuple of strings expected", bnd); - env[s1] = s2; - bnds = ATgetNext(bnds); - } - - /* Check whether the target already exists. */ - if (pathExists(outPath)) - deleteFromStore(outPath); -// throw Error(format("path %1% already exists") % outPath); - - /* Run the builder. */ - runProgram(builder, env); - - /* Check whether the result was created. */ - if (!pathExists(outPath)) - throw Error(format("program %1% failed to create a result in %2%") - % builder % outPath); - -#if 0 - /* Remove write permission from the value. */ - int res = system(("chmod -R -w " + targetPath).c_str()); // !!! escaping - if (WEXITSTATUS(res) != 0) - throw Error("cannot remove write permission from " + targetPath); -#endif - - /* Hash the result. */ - Hash outHash = hashPath(outPath); - - /* Register targetHash -> targetPath. !!! this should be in - values.cc. */ - registerPath(outPath, outHash); - - /* Filter out inputs that are not referenced in the output. */ - for (Strings::iterator i = inPaths.begin(); - i != inPaths.end(); i++) - debug(format("in: %1%") % *i); - - Strings outPaths = filterReferences(outPath, inPaths); - - for (Strings::iterator i = outPaths.begin(); - i != outPaths.end(); i++) - debug(format("out: %1%") % *i); - - ins2 = ATempty; - while (!ATisEmpty(ins)) { - FState in = ATgetFirst(ins); - string path = fstatePath(in); - for (Strings::iterator i = outPaths.begin(); - i != outPaths.end(); i++) - if (path.find(*i) != string::npos) { - debug(format("out2: %1%") % path); - ins2 = ATinsert(ins2, in); - } - ins = ATgetNext(ins); - } - ins = ATreverse(ins2); - - /* Register the normal form of fs. */ - FState nf = ATmake("Path(, Hash(), )", - outPath.c_str(), ((string) outHash).c_str(), ins); - nf = storeSuccessor(fs, nf, paths); - - return nf; - } - - throw badTerm("bad fstate expression", fs); -} - - -FState realiseFState(FState fs, StringSet & paths) -{ - return realise(fs, paths); -} - - -string fstatePath(FState fs) -{ - char * s1, * s2, * s3; - FState e1, e2; - if (ATmatch(fs, "Path(, , [])", &s1, &e1, &e2)) - return s1; - else if (ATmatch(fs, "Derive(, , [], , [])", - &s1, &s2, &e1, &s3, &e2)) - return s3; - else if (ATmatch(fs, "Include()", &s1)) - return fstatePath(termFromHash(parseHash(s1))); - else - return ""; -} - - -void fstateRefs2(FState fs, StringSet & paths) -{ - char * s1, * s2, * s3; - FState e1, e2; - ATermList refs, ins; - - if (ATmatch(fs, "Path(, , [])", &s1, &e1, &refs)) { - paths.insert(s1); - - while (!ATisEmpty(refs)) { - fstateRefs2(ATgetFirst(refs), paths); - refs = ATgetNext(refs); - } - } - - else if (ATmatch(fs, "Derive(, , [], , [])", - &s1, &s2, &ins, &s3, &e2)) - { - while (!ATisEmpty(ins)) { - fstateRefs2(ATgetFirst(ins), paths); - ins = ATgetNext(ins); - } - } - - else if (ATmatch(fs, "Include()", &s1)) - fstateRefs2(termFromHash(parseHash(s1)), paths); - - else throw badTerm("bad fstate expression", fs); -} - - -void fstateRefs(FState fs, StringSet & paths) -{ - fstateRefs2(fs, paths); -} -#endif - - static void parseIds(ATermList ids, FSIds & out) { while (!ATisEmpty(ids)) { @@ -424,7 +202,6 @@ static void parseIds(ATermList ids, FSIds & out) if (!ATmatch(id, "", &s)) throw badTerm("not an id", id); out.push_back(parseHash(s)); - debug(s); ids = ATgetNext(ids); } } @@ -691,3 +468,45 @@ void realiseSlice(const Slice & slice) expandId(elem.id, elem.path); } } + + +Strings fstatePaths(FSId id) +{ + Strings paths; + + FState fs = termFromId(id); + + ATermList outs, ins, bnds; + char * builder; + char * platform; + + if (ATgetType(fs) == AT_APPL && + (string) ATgetName(ATgetAFun(fs)) == "Slice") + { + Slice slice = parseSlice(fs); + + /* !!! fix complexity */ + for (FSIds::const_iterator i = slice.roots.begin(); + i != slice.roots.end(); i++) + for (SliceElems::const_iterator j = slice.elems.begin(); + j != slice.elems.end(); j++) + if (*i == j->id) paths.push_back(j->path); + } + + else if (ATmatch(fs, "Derive([], [], , , [])", + &outs, &ins, &builder, &platform, &bnds)) + { + while (!ATisEmpty(outs)) { + ATerm t = ATgetFirst(outs); + char * s1, * s2; + if (!ATmatch(t, "(, )", &s1, &s2)) + throw badTerm("string expected", t); + paths.push_back(s1); + outs = ATgetNext(outs); + } + } + + else throw badTerm("in fstatePaths", fs); + + return paths; +} diff --git a/src/fstate.hh b/src/fstate.hh index a19528164..afbf34dab 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -115,5 +115,7 @@ Slice normaliseFState(FSId id); /* Realise a Slice in the file system. */ void realiseSlice(const Slice & slice); +Strings fstatePaths(FSId id); + #endif /* !__FSTATE_H */ diff --git a/src/hash.cc b/src/hash.cc index 97bf8f785..b59c4f214 100644 --- a/src/hash.cc +++ b/src/hash.cc @@ -14,7 +14,7 @@ Hash::Hash() } -bool Hash::operator == (Hash h2) +bool Hash::operator == (const Hash & h2) const { for (unsigned int i = 0; i < hashSize; i++) if (hash[i] != h2.hash[i]) return false; @@ -22,7 +22,7 @@ bool Hash::operator == (Hash h2) } -bool Hash::operator != (Hash h2) +bool Hash::operator != (const Hash & h2) const { return !(*this == h2); } diff --git a/src/hash.hh b/src/hash.hh index 5ae5dc22a..387939e93 100644 --- a/src/hash.hh +++ b/src/hash.hh @@ -17,10 +17,10 @@ struct Hash Hash(); /* Check whether two hash are equal. */ - bool operator == (Hash h2); + bool operator == (const Hash & h2) const; /* Check whether two hash are not equal. */ - bool operator != (Hash h2); + bool operator != (const Hash & h2) const; /* For sorting. */ bool operator < (const Hash & h) const; diff --git a/src/nix.cc b/src/nix.cc index 8fc0fa2c3..9c77c68f4 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -41,7 +41,7 @@ static ArgType argType = atpUnknown; Source selection for --install, --dump: --file / -f: by file name !!! -> path - --hash / -h: by hash + --hash / -h: by hash (identifier) Query flags: @@ -76,15 +76,15 @@ static void getArgType(Strings & flags) } -static Hash argToHash(const string & arg) +static FSId argToId(const string & arg) { if (argType == atpHash) return parseHash(arg); else if (argType == atpPath) { string path; - Hash hash; - addToStore(arg, path, hash); - return hash; + FSId id; + addToStore(arg, path, id); + return id; } else abort(); } @@ -99,10 +99,7 @@ static void opInstall(Strings opFlags, Strings opArgs) for (Strings::iterator it = opArgs.begin(); it != opArgs.end(); it++) - { - StringSet paths; - realiseFState(hash2fstate(argToHash(*it)), paths); - } + realiseSlice(normaliseFState(argToId(*it))); } @@ -128,9 +125,9 @@ static void opAdd(Strings opFlags, Strings opArgs) it != opArgs.end(); it++) { string path; - Hash hash; - addToStore(*it, path, hash); - cout << format("%1% %2%\n") % (string) hash % path; + FSId id; + addToStore(*it, path, id); + cout << format("%1% %2%\n") % (string) id % path; } } @@ -156,10 +153,11 @@ static void opQuery(Strings opFlags, Strings opArgs) for (Strings::iterator it = opArgs.begin(); it != opArgs.end(); it++) { - Hash hash = argToHash(*it); + FSId id = argToId(*it); switch (query) { +#if 0 case qPath: { StringSet refs; cout << format("%s\n") % @@ -176,6 +174,7 @@ static void opQuery(Strings opFlags, Strings opArgs) cout << format("%s\n") % *j; break; } +#endif default: abort(); @@ -192,9 +191,9 @@ static void opSuccessor(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ) { - Hash fsHash = parseHash(*i++); - Hash scHash = parseHash(*i++); - registerSuccessor(fsHash, scHash); + FSId id1 = parseHash(*i++); + FSId id2 = parseHash(*i++); + registerSuccessor(id1, id2); } } @@ -207,9 +206,9 @@ static void opSubstitute(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ) { - Hash srcHash = parseHash(*i++); - Hash subHash = parseHash(*i++); - registerSubstitute(srcHash, subHash); + FSId src = parseHash(*i++); + FSId sub = parseHash(*i++); + registerSubstitute(src, sub); } } @@ -238,7 +237,7 @@ static void opDump(Strings opFlags, Strings opArgs) string arg = *opArgs.begin(); string path; - if (argType == atpHash) path = expandHash(parseHash(arg)); + if (argType == atpHash) path = expandId(parseHash(arg)); else if (argType == atpPath) path = arg; dumpPath(path, sink); From c11bbcfd26e554ca044c1cce293097e4e87ef31e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 16 Jul 2003 08:30:26 +0000 Subject: [PATCH 0139/6440] * Fix self-referential outputs. * Fix -qp query. --- src/fix.cc | 2 +- src/fstate.cc | 30 ++++++++++++++++++++---------- src/fstate.hh | 16 +--------------- src/nix.cc | 9 +++++---- src/test-builder-2.sh | 1 + 5 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index f5f92a87b..d954abfe4 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -164,7 +164,7 @@ static Expr evalExpr(Expr e) char * id; if (ATmatch(value, "FSId()", &id)) { - Strings paths = fstatePaths(parseHash(id)); + Strings paths = fstatePaths(parseHash(id), false); if (paths.size() != 1) abort(); string path = *(paths.begin()); ins = ATinsert(ins, ATmake("", id)); diff --git a/src/fstate.cc b/src/fstate.cc index d15219344..60a8d475c 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -332,19 +332,19 @@ Slice normaliseFState(FSId id) } /* Check that none of the output paths exist. */ - typedef pair OutPath; - list outPaths; + typedef map OutPaths; + OutPaths outPaths; while (!ATisEmpty(outs)) { ATerm t = ATgetFirst(outs); char * s1, * s2; if (!ATmatch(t, "(, )", &s1, &s2)) throw badTerm("string expected", t); - outPaths.push_back(OutPath(s1, parseHash(s2))); + outPaths[s1] = parseHash(s2); inPaths.push_back(s1); outs = ATgetNext(outs); } - for (list::iterator i = outPaths.begin(); + for (OutPaths::iterator i = outPaths.begin(); i != outPaths.end(); i++) if (pathExists(i->first)) throw Error(format("path `%1%' exists") % i->first); @@ -357,7 +357,7 @@ Slice normaliseFState(FSId id) /* Check whether the output paths were created, and register each one. */ FSIdSet used; - for (list::iterator i = outPaths.begin(); + for (OutPaths::iterator i = outPaths.begin(); i != outPaths.end(); i++) { string path = i->first; @@ -374,10 +374,15 @@ Slice normaliseFState(FSId id) for (Strings::iterator j = refs.begin(); j != refs.end(); j++) { ElemMap::iterator k; + OutPaths::iterator l; if ((k = inMap.find(*j)) != inMap.end()) { elem.refs.push_back(k->second.id); used.insert(k->second.id); - } else abort(); /* fix! check in created paths */ + } else if ((l = outPaths.find(*j)) != outPaths.end()) { + elem.refs.push_back(l->second); + used.insert(l->second); + } else + throw Error(format("unknown referenced path `%1%'") % *j); } slice.elems.push_back(elem); @@ -470,7 +475,7 @@ void realiseSlice(const Slice & slice) } -Strings fstatePaths(FSId id) +Strings fstatePaths(FSId id, bool normalise) { Strings paths; @@ -480,10 +485,15 @@ Strings fstatePaths(FSId id) char * builder; char * platform; - if (ATgetType(fs) == AT_APPL && - (string) ATgetName(ATgetAFun(fs)) == "Slice") + if (normalise || + (ATgetType(fs) == AT_APPL && + (string) ATgetName(ATgetAFun(fs)) == "Slice")) { - Slice slice = parseSlice(fs); + Slice slice; + if (normalise) + slice = normaliseFState(id); + else + slice = parseSlice(fs); /* !!! fix complexity */ for (FSIds::const_iterator i = slice.roots.begin(); diff --git a/src/fstate.hh b/src/fstate.hh index afbf34dab..72fc52805 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -74,20 +74,6 @@ struct Slice }; -#if 0 -/* Realise an fstate expression in the file system. This requires - execution of all Derive() nodes. */ -FState realiseFState(FState fs, StringSet & paths); - -/* Return the path of an fstate expression. An empty string is - returned if the term is not a valid fstate expression. (!!!) */ -string fstatePath(FState fs); - -/* Return the paths referenced by fstate expression. */ -void fstateRefs(FState fs, StringSet & paths); -#endif - - /* Return a canonical textual representation of an expression. */ string printTerm(ATerm t); @@ -115,7 +101,7 @@ Slice normaliseFState(FSId id); /* Realise a Slice in the file system. */ void realiseSlice(const Slice & slice); -Strings fstatePaths(FSId id); +Strings fstatePaths(FSId id, bool normalise); #endif /* !__FSTATE_H */ diff --git a/src/nix.cc b/src/nix.cc index 9c77c68f4..22928880f 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -157,14 +157,15 @@ static void opQuery(Strings opFlags, Strings opArgs) switch (query) { -#if 0 case qPath: { - StringSet refs; - cout << format("%s\n") % - (string) fstatePath(realiseFState(termFromHash(hash), refs)); + Strings paths = fstatePaths(id, true); + for (Strings::iterator i = paths.begin(); + i != paths.end(); i++) + cout << format("%s\n") % *i; break; } +#if 0 case qRefs: { StringSet refs; FState fs = hash2fstate(hash); diff --git a/src/test-builder-2.sh b/src/test-builder-2.sh index a12fa27a6..1c4ac8494 100644 --- a/src/test-builder-2.sh +++ b/src/test-builder-2.sh @@ -6,3 +6,4 @@ mkdir $out || exit 1 cd $out || exit 1 echo "Hallo Wereld" > bla echo $builder >> bla +echo $out >> bla From b9ecadee6e32eddac07d09a228f0dda2b340c7ac Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 16 Jul 2003 11:05:59 +0000 Subject: [PATCH 0140/6440] * Fix the -qr query. --- src/fstate.cc | 36 ++++++++++++++++++++++++++++++------ src/fstate.hh | 7 ++++++- src/nix.cc | 12 ++++-------- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/fstate.cc b/src/fstate.cc index 60a8d475c..cdd620cf1 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -186,9 +186,10 @@ void registerSuccessor(const FSId & id1, const FSId & id2) } -static FSId storeSuccessor(const FSId & id1, FState sc) +static FSId storeSuccessor(const FSId & id1, FState sc, + string * p) { - FSId id2 = writeTerm(sc, "-s-" + (string) id1, 0); + FSId id2 = writeTerm(sc, "-s-" + (string) id1, p); registerSuccessor(id1, id2); return id2; } @@ -267,7 +268,7 @@ static FState unparseSlice(const Slice & slice) typedef set FSIdSet; -Slice normaliseFState(FSId id) +static Slice normaliseFState2(FSId id, StringSet & usedPaths) { debug(format("normalising fstate")); Nest nest(true); @@ -281,12 +282,16 @@ Slice normaliseFState(FSId id) } /* Get the fstate expression. */ - FState fs = termFromId(id); + string fsPath; + FState fs = termFromId(id, &fsPath); /* Already in normal form (i.e., a slice)? */ if (ATgetType(fs) == AT_APPL && (string) ATgetName(ATgetAFun(fs)) == "Slice") + { + usedPaths.insert(fsPath); return parseSlice(fs); + } /* Then we it's a Derive node. */ ATermList outs, ins, bnds; @@ -402,12 +407,20 @@ Slice normaliseFState(FSId id) FState nf = unparseSlice(slice); debug(printTerm(nf)); - storeSuccessor(id, nf); + storeSuccessor(id, nf, &fsPath); + usedPaths.insert(fsPath); return slice; } +Slice normaliseFState(FSId id) +{ + StringSet dummy; + return normaliseFState2(id, dummy); +} + + static void checkSlice(const Slice & slice) { if (slice.elems.size() == 0) @@ -475,7 +488,7 @@ void realiseSlice(const Slice & slice) } -Strings fstatePaths(FSId id, bool normalise) +Strings fstatePaths(const FSId & id, bool normalise) { Strings paths; @@ -520,3 +533,14 @@ Strings fstatePaths(FSId id, bool normalise) return paths; } + + +StringSet fstateRefs(const FSId & id) +{ + StringSet paths; + Slice slice = normaliseFState2(id, paths); + for (SliceElems::const_iterator i = slice.elems.begin(); + i != slice.elems.end(); i++) + paths.insert(i->path); + return paths; +} diff --git a/src/fstate.hh b/src/fstate.hh index 72fc52805..2ae876b7c 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -101,7 +101,12 @@ Slice normaliseFState(FSId id); /* Realise a Slice in the file system. */ void realiseSlice(const Slice & slice); -Strings fstatePaths(FSId id, bool normalise); +/* Get the list of root (output) paths of the given + fstate-expression. */ +Strings fstatePaths(const FSId & id, bool normalise); + +/* Get the list of paths referenced by the given fstate-expression. */ +StringSet fstateRefs(const FSId & id); #endif /* !__FSTATE_H */ diff --git a/src/nix.cc b/src/nix.cc index 22928880f..ae016824d 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -159,23 +159,19 @@ static void opQuery(Strings opFlags, Strings opArgs) case qPath: { Strings paths = fstatePaths(id, true); - for (Strings::iterator i = paths.begin(); - i != paths.end(); i++) - cout << format("%s\n") % *i; + for (Strings::iterator j = paths.begin(); + j != paths.end(); j++) + cout << format("%s\n") % *j; break; } -#if 0 case qRefs: { - StringSet refs; - FState fs = hash2fstate(hash); - fstateRefs(realiseFState(fs, refs), refs); + StringSet refs = fstateRefs(id); for (StringSet::iterator j = refs.begin(); j != refs.end(); j++) cout << format("%s\n") % *j; break; } -#endif default: abort(); From 9d56ca219fb7af1c209458f81a8ce35a1b6afd28 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 16 Jul 2003 20:00:51 +0000 Subject: [PATCH 0141/6440] * Substitute fixes. --- corepkgs/fetchurl/fetchurl.sh | 8 ++++--- corepkgs/nar/nar.sh | 2 +- scripts/nix-pull.in | 2 +- scripts/nix-push.in | 25 +++++++++---------- src/db.cc | 5 +++- src/fix.cc | 8 +++++++ src/fstate.cc | 45 +++++++++++++++++++++++++++++------ src/store.cc | 27 ++++++++++++++------- 8 files changed, 86 insertions(+), 36 deletions(-) diff --git a/corepkgs/fetchurl/fetchurl.sh b/corepkgs/fetchurl/fetchurl.sh index 7b6243974..1479e898b 100644 --- a/corepkgs/fetchurl/fetchurl.sh +++ b/corepkgs/fetchurl/fetchurl.sh @@ -4,7 +4,9 @@ echo "downloading $url into $out..." wget "$url" -O "$out" || exit 1 actual=$(md5sum -b $out | cut -c1-32) -if ! test "$actual" == "$md5"; then - echo "hash is $actual, expected $md5" - exit 1 +if ! test "$md5" == "ignore"; then + if ! test "$actual" == "$md5"; then + echo "hash is $actual, expected $md5" + exit 1 + fi fi diff --git a/corepkgs/nar/nar.sh b/corepkgs/nar/nar.sh index 3dbeed029..059bca8ba 100644 --- a/corepkgs/nar/nar.sh +++ b/corepkgs/nar/nar.sh @@ -1,3 +1,3 @@ #! /bin/sh -/tmp/nix/bin/nix --dump --file "$path" | bzip2 > $out || exit 1 +/nix/bin/nix --dump --file "$path" | bzip2 > $out || exit 1 diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index a75c1f258..47762e857 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -38,7 +38,7 @@ while () { # Nix archive from the network. my $fetch = "App(IncludeFix(\"fetchurl/fetchurl.fix\"), " . - "[(\"url\", \"$url/$fn\"), (\"hash\", \"\")])"; + "[(\"url\", \"$url/$fn\"), (\"md5\", \"ignore\")])"; my $fixexpr = "App(IncludeFix(\"nar/unnar.fix\"), " . "[ (\"nar\", $fetch)" . diff --git a/scripts/nix-push.in b/scripts/nix-push.in index bf30f3a49..fdb432303 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -2,14 +2,14 @@ my @pushlist; -foreach my $hash (@ARGV) { +foreach my $id (@ARGV) { - die unless $hash =~ /^([0-9a-z]{32})$/; + die unless $id =~ /^([0-9a-z]{32})$/; # Get all paths referenced by the normalisation of the given # fstate expression. my @paths; - open PATHS, "nix -qrh $hash 2> /dev/null |" or die "nix -qrh"; + open PATHS, "nix -qrh $id 2> /dev/null |" or die "nix -qrh"; while () { chomp; next unless /^\//; @@ -21,15 +21,12 @@ foreach my $hash (@ARGV) { # a Nix archive. foreach my $path (@paths) { - # Hash the path. - my $phash = `nix-hash $path`; - $? and die "nix-hash"; - chomp $phash; - die unless $phash =~ /^([0-9a-z]{32})$/; + next unless ($path =~ /\/([0-9a-z]{32})[^\/]*/); + my $pathid = $1; # Construct a name for the Nix archive. If the file is an # fstate successor, encode this into the name. - my $name = $phash; + my $name = $pathid; if ($path =~ /-s-([0-9a-z]{32}).nix$/) { $name = "$name-s-$1"; } @@ -38,7 +35,7 @@ foreach my $hash (@ARGV) { # Construct a Fix expression that creates a Nix archive. my $fixexpr = "App(IncludeFix(\"nar/nar.fix\"), " . - "[ (\"path\", Path(\"$path\", Hash(\"$phash\"), [Include(\"$hash\")]))" . + "[ (\"path\", Slice([\"$pathid\"], [(\"$path\", \"$pathid\", [])]))" . ", (\"name\", \"$name\")" . "])"; @@ -48,13 +45,13 @@ foreach my $hash (@ARGV) { close FIX; # Instantiate a Nix expression from the Fix expression. - my $nhash = `fix $fixfile`; + my $nid = `fix $fixfile`; $? and die "instantiating Nix archive expression"; - chomp $nhash; - die unless $nhash =~ /^([0-9a-z]{32})$/; + chomp $nid; + die unless $nid =~ /^([0-9a-z]{32})$/; # Realise the Nix expression. - my $npath = `nix -qph $nhash 2> /dev/null`; + my $npath = `nix -qph $nid 2> /dev/null`; $? and die "creating Nix archive"; chomp $npath; diff --git a/src/db.cc b/src/db.cc index 89cee32ba..64f9813a4 100644 --- a/src/db.cc +++ b/src/db.cc @@ -65,7 +65,10 @@ bool queryDB(const string & filename, const string & dbname, err = db->get(0, &kt, &dt, 0); if (err) return false; - data = string((char *) dt.get_data(), dt.get_size()); + if (!dt.get_data()) + data = ""; + else + data = string((char *) dt.get_data(), dt.get_size()); } catch (DbException e) { rethrow(e); } diff --git a/src/fix.cc b/src/fix.cc index d954abfe4..f55074907 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -111,6 +111,14 @@ static Expr evalExpr(Expr e) ATmatch(e, "FSId()", &s1)) return e; + if (ATgetType(e) == AT_APPL && + ((string) ATgetName(ATgetAFun(e)) == "Slice" || + (string) ATgetName(ATgetAFun(e)) == "Derive")) + { + return ATmake("FSId()", + ((string) writeTerm(e, "", 0)).c_str()); + } + /* Application. */ if (ATmatch(e, "App(, [])", &e1, &e2)) { e1 = evalExpr(e1); diff --git a/src/fstate.cc b/src/fstate.cc index cdd620cf1..11a91cffc 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -21,15 +21,22 @@ typedef map Environment; class AutoDelete { string path; + bool del; public: AutoDelete(const string & p) : path(p) { + del = true; } ~AutoDelete() { - deletePath(path); + if (del) deletePath(path); + } + + void cancel() + { + del = false; } }; @@ -114,8 +121,10 @@ static void runProgram(const string & program, Environment env) if (waitpid(pid, &status, 0) != pid) throw Error("unable to wait for child"); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + delTmpDir.cancel(); throw Error("unable to build package"); + } } @@ -336,7 +345,7 @@ static Slice normaliseFState2(FSId id, StringSet & usedPaths) bnds = ATgetNext(bnds); } - /* Check that none of the output paths exist. */ + /* Parse the outputs. */ typedef map OutPaths; OutPaths outPaths; while (!ATisEmpty(outs)) { @@ -349,14 +358,36 @@ static Slice normaliseFState2(FSId id, StringSet & usedPaths) outs = ATgetNext(outs); } + /* We can skip running the builder if we can expand all output + paths from their ids. */ + bool fastBuild = false; +#if 0 for (OutPaths::iterator i = outPaths.begin(); i != outPaths.end(); i++) - if (pathExists(i->first)) - throw Error(format("path `%1%' exists") % i->first); + { + try { + expandId(i->second, i->first); + } catch (...) { + fastBuild = false; + break; + } + } +#endif - /* Run the builder. */ - runProgram(builder, env); + if (!fastBuild) { + + /* Check that none of the outputs exist. */ + for (OutPaths::iterator i = outPaths.begin(); + i != outPaths.end(); i++) + if (pathExists(i->first)) + throw Error(format("path `%1%' exists") % i->first); + + /* Run the builder. */ + runProgram(builder, env); + } else + debug(format("skipping build")); + Slice slice; /* Check whether the output paths were created, and register each diff --git a/src/store.cc b/src/store.cc index c8b3bd37f..d75458d8a 100644 --- a/src/store.cc +++ b/src/store.cc @@ -86,6 +86,7 @@ void copyPath(string src, string dst) void registerSubstitute(const FSId & srcId, const FSId & subId) { +#if 0 Strings subs; queryListDB(nixDB, dbSubstitutes, srcId, subs); /* non-existence = ok */ @@ -94,6 +95,12 @@ void registerSubstitute(const FSId & srcId, const FSId & subId) subs.push_back(subId); + setListDB(nixDB, dbSubstitutes, srcId, subs); +#endif + + /* For now, accept only one substitute per id. */ + Strings subs; + subs.push_back(subId); setListDB(nixDB, dbSubstitutes, srcId, subs); } @@ -126,6 +133,8 @@ void unregisterPath(const string & _path) return; FSId id(parseHash(_id)); + delDB(nixDB, dbPath2Id, path); + /* begin transaction */ Strings paths, paths2; @@ -140,6 +149,7 @@ void unregisterPath(const string & _path) setListDB(nixDB, dbId2Paths, id, paths2); /* end transaction */ + } @@ -189,32 +199,31 @@ string expandId(const FSId & id, const string & target, } } -#if 0 /* Try to realise the substitutes. */ Strings subs; queryListDB(nixDB, dbSubstitutes, id, subs); /* non-existence = ok */ for (Strings::iterator it = subs.begin(); it != subs.end(); it++) { - realiseSlice(normaliseFState(*it)); + FSId subId = parseHash(*it); + Slice slice = normaliseFState(subId); + realiseSlice(slice); - FState nf = realiseFState(hash2fstate(parseHash(*it)), dummy); - string path = fstatePath(nf); - - if (hashPath(path) != hash) - throw Error(format("bad substitute in `%1%'") % (string) path); + Strings paths = fstatePaths(subId, true); + if (paths.size() != 1) + throw Error("substitute created more than 1 path"); + string path = *(paths.begin()); if (target.empty()) return path; /* !!! prefix */ else { if (path != target) { copyPath(path, target); - registerPath(target, hash); + registerPath(target, id); } return target; } } -#endif throw Error(format("cannot expand id `%1%'") % (string) id); } From 6822fd7bf472c9edc27c0e851f3efd67c2a99952 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 16 Jul 2003 20:33:29 +0000 Subject: [PATCH 0142/6440] * Bug fix: slices are transitive, so if we detect that an input path is referenced in an output paths, we also have to add all ids referenced by that input path. * Better debug assertions to catch these sorts of errors. --- src/fstate.cc | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/fstate.cc b/src/fstate.cc index 11a91cffc..31dd17582 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -217,6 +217,9 @@ static void parseIds(ATermList ids, FSIds & out) } +static void checkSlice(const Slice & slice); + + /* Parse a slice. */ static Slice parseSlice(FState fs) { @@ -242,6 +245,8 @@ static Slice parseSlice(FState fs) elems = ATgetNext(elems); } + checkSlice(slice); + return slice; } @@ -414,6 +419,9 @@ static Slice normaliseFState2(FSId id, StringSet & usedPaths) if ((k = inMap.find(*j)) != inMap.end()) { elem.refs.push_back(k->second.id); used.insert(k->second.id); + for (FSIds::iterator m = k->second.refs.begin(); + m != k->second.refs.end(); m++) + used.insert(*m); } else if ((l = outPaths.find(*j)) != outPaths.end()) { elem.refs.push_back(l->second); used.insert(l->second); @@ -441,6 +449,8 @@ static Slice normaliseFState2(FSId id, StringSet & usedPaths) storeSuccessor(id, nf, &fsPath); usedPaths.insert(fsPath); + parseSlice(nf); /* check */ + return slice; } @@ -460,10 +470,7 @@ static void checkSlice(const Slice & slice) FSIdSet decl; for (SliceElems::const_iterator i = slice.elems.begin(); i != slice.elems.end(); i++) - { - debug((string) i->id); decl.insert(i->id); - } for (FSIds::const_iterator i = slice.roots.begin(); i != slice.roots.end(); i++) @@ -484,8 +491,6 @@ void realiseSlice(const Slice & slice) debug(format("realising slice")); Nest nest(true); - checkSlice(slice); - /* Perhaps all paths already contain the right id? */ bool missing = false; From 335aa1c35d8835619b465df3f5629b435bac157d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 16 Jul 2003 20:49:59 +0000 Subject: [PATCH 0143/6440] * Doh! --- corepkgs/nar/unnar.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/corepkgs/nar/unnar.sh b/corepkgs/nar/unnar.sh index 01b6a3ebe..cc21efb2b 100644 --- a/corepkgs/nar/unnar.sh +++ b/corepkgs/nar/unnar.sh @@ -1,3 +1,4 @@ #! /bin/sh -bunzip2 < $nar | /tmp/nix/bin/nix --restore "$out" || exit 1 +echo "unpacking $nar to $out..." +bunzip2 < $nar | /nix/bin/nix --restore "$out" || exit 1 From 54664b6fb74e964d70530d13e25459751d0c63fb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 16 Jul 2003 21:24:02 +0000 Subject: [PATCH 0144/6440] * The write() system call can write less than the requested number of bytes, e.g., in case of a signal like SIGSTOP. This caused `nix --dump' to fail sometimes. Note that this bug went unnoticed because the call to `nix --dump' is in a pipeline, and the shell ignores non-zero exit codes from all but the last element in the pipeline. Is there any way to check the result of the initial elements in the pipeline? (In other words, is it at all possible to write reliable shell scripts?) --- corepkgs/nar/nar.sh | 2 ++ scripts/nix-push.in | 4 ++++ src/nix.cc | 10 +++++++--- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/corepkgs/nar/nar.sh b/corepkgs/nar/nar.sh index 059bca8ba..a7b6be8aa 100644 --- a/corepkgs/nar/nar.sh +++ b/corepkgs/nar/nar.sh @@ -1,3 +1,5 @@ #! /bin/sh +echo "packing $path into $out..." /nix/bin/nix --dump --file "$path" | bzip2 > $out || exit 1 + diff --git a/scripts/nix-push.in b/scripts/nix-push.in index fdb432303..bb25019e8 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -8,6 +8,8 @@ foreach my $id (@ARGV) { # Get all paths referenced by the normalisation of the given # fstate expression. + system "nix -ih $id"; + if ($?) { die "`nix -ih' failed"; } my @paths; open PATHS, "nix -qrh $id 2> /dev/null |" or die "nix -qrh"; while () { @@ -51,6 +53,8 @@ foreach my $id (@ARGV) { die unless $nid =~ /^([0-9a-z]{32})$/; # Realise the Nix expression. + system "nix -ih $nid"; + if ($?) { die "`nix -ih' failed"; } my $npath = `nix -qph $nid 2> /dev/null`; $? and die "creating Nix archive"; chomp $npath; diff --git a/src/nix.cc b/src/nix.cc index ae016824d..fe762798e 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -216,8 +216,12 @@ struct StdoutSink : DumpSink virtual void operator () (const unsigned char * data, unsigned int len) { - if (write(STDOUT_FILENO, (char *) data, len) != (ssize_t) len) - throw SysError("writing to stdout"); + while (len) { + ssize_t res = write(STDOUT_FILENO, (char *) data, len); + if (res == -1) throw SysError("writing to stdout"); + len -= res; + data += res; + } } }; @@ -249,7 +253,7 @@ struct StdinSource : RestoreSource while (len) { ssize_t res = read(STDIN_FILENO, (char *) data, len); if (res == -1) throw SysError("reading from stdin"); - if (res == 0) throw SysError("unexpected end-of-file on stdin"); + if (res == 0) throw Error("unexpected end-of-file on stdin"); len -= res; data += res; } From 71cc3ceae5c5dd97a0faa2dab3da3dd0c479b0b5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 17 Jul 2003 11:25:14 +0000 Subject: [PATCH 0145/6440] * Preserve the executable bit. --- src/archive.cc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/archive.cc b/src/archive.cc index c9b78824e..73fc75fa2 100644 --- a/src/archive.cc +++ b/src/archive.cc @@ -119,6 +119,10 @@ static void dump(const string & path, DumpSink & sink) if (S_ISREG(st.st_mode)) { writeString("type", sink); writeString("regular", sink); + if (st.st_mode & S_IXUSR) { + writeString("executable", sink); + writeString("", sink); + } dumpContents(path, st.st_size, sink); } @@ -295,6 +299,15 @@ static void restore(const string & path, RestoreSource & source) restoreContents(fd, path, source); } + else if (s == "executable" && type == tpRegular) { + readString(source); + struct stat st; + if (fstat(fd, &st) == -1) + throw SysError("fstat"); + if (fchmod(fd, st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH)) == -1) + throw SysError("fchmod"); + } + else if (s == "entry" && type == tpDirectory) { restoreEntry(path, source); } From b3fc38bf6a407f962b83089b2e13cbc90dd53042 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 17 Jul 2003 12:27:55 +0000 Subject: [PATCH 0146/6440] * For debugging: `nix --verify' to check the consistency of the database and store. --- src/db.cc | 10 ++--- src/db.hh | 5 +-- src/fstate.hh | 4 -- src/nix.cc | 9 ++++ src/store.cc | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/store.hh | 2 + src/util.hh | 2 + 7 files changed, 139 insertions(+), 14 deletions(-) diff --git a/src/db.cc b/src/db.cc index 64f9813a4..a8741342c 100644 --- a/src/db.cc +++ b/src/db.cc @@ -157,7 +157,7 @@ void delDB(const string & filename, const string & dbname, void enumDB(const string & filename, const string & dbname, - DBPairs & contents) + Strings & keys) { try { @@ -168,11 +168,9 @@ void enumDB(const string & filename, const string & dbname, DbcClose cursorCloser(cursor); Dbt kt, dt; - while (cursor->get(&kt, &dt, DB_NEXT) != DB_NOTFOUND) { - string key((char *) kt.get_data(), kt.get_size()); - string data((char *) dt.get_data(), dt.get_size()); - contents.push_back(DBPair(key, data)); - } + while (cursor->get(&kt, &dt, DB_NEXT) != DB_NOTFOUND) + keys.push_back( + string((char *) kt.get_data(), kt.get_size())); } catch (DbException e) { rethrow(e); } } diff --git a/src/db.hh b/src/db.hh index aee6ce9bf..6f13eb30c 100644 --- a/src/db.hh +++ b/src/db.hh @@ -8,9 +8,6 @@ using namespace std; -typedef pair DBPair; -typedef list DBPairs; - void createDB(const string & filename, const string & dbname); bool queryDB(const string & filename, const string & dbname, @@ -29,6 +26,6 @@ void delDB(const string & filename, const string & dbname, const string & key); void enumDB(const string & filename, const string & dbname, - DBPairs & contents); + Strings & keys); #endif /* !__DB_H */ diff --git a/src/fstate.hh b/src/fstate.hh index 2ae876b7c..0d89e7e36 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -1,8 +1,6 @@ #ifndef __FSTATE_H #define __FSTATE_H -#include - extern "C" { #include } @@ -53,8 +51,6 @@ using namespace std; typedef ATerm FState; typedef ATerm Content; -typedef set StringSet; - typedef list FSIds; diff --git a/src/nix.cc b/src/nix.cc index fe762798e..5785cd6b4 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -283,6 +283,13 @@ static void opInit(Strings opFlags, Strings opArgs) } +/* Verify the consistency of the Nix environment. */ +static void opVerify(Strings opFlags, Strings opArgs) +{ + verifyStore(); +} + + /* Scan the arguments; find the operation, set global flags, put all other flags in a list, and put all other arguments in another list. */ @@ -316,6 +323,8 @@ void run(Strings args) op = opRestore; else if (arg == "--init") op = opInit; + else if (arg == "--verify") + op = opVerify; else if (arg[0] == '-') opFlags.push_back(arg); else diff --git a/src/store.cc b/src/store.cc index d75458d8a..864213692 100644 --- a/src/store.cc +++ b/src/store.cc @@ -260,3 +260,124 @@ void deleteFromStore(const string & path) deletePath(path); } + + +void verifyStore() +{ + Strings paths; + enumDB(nixDB, dbPath2Id, paths); + + for (Strings::iterator i = paths.begin(); + i != paths.end(); i++) + { + bool erase = true; + string path = *i; + + if (!pathExists(path)) { + debug(format("path `%1%' disappeared") % path); + } + + else { + string id; + if (!queryDB(nixDB, dbPath2Id, path, id)) abort(); + + Strings idPaths; + queryListDB(nixDB, dbId2Paths, id, idPaths); + + bool found = false; + for (Strings::iterator j = idPaths.begin(); + j != idPaths.end(); j++) + if (path == *j) { + found = true; + break; + } + + if (found) + erase = false; + else + /* !!! perhaps we should add path to idPaths? */ + debug(format("reverse mapping for path `%1%' missing") % path); + } + + if (erase) delDB(nixDB, dbPath2Id, path); + } + + Strings ids; + enumDB(nixDB, dbId2Paths, ids); + + for (Strings::iterator i = ids.begin(); + i != ids.end(); i++) + { + FSId id = parseHash(*i); + + Strings idPaths; + queryListDB(nixDB, dbId2Paths, id, idPaths); + + for (Strings::iterator j = idPaths.begin(); + j != idPaths.end(); ) + { + string id2; + if (!queryDB(nixDB, dbPath2Id, *j, id2) || + id != parseHash(id2)) { + debug(format("erasing path `%1%' from mapping for id %2%") + % *j % (string) id); + j = idPaths.erase(j); + } else j++; + } + + setListDB(nixDB, dbId2Paths, id, idPaths); + } + + + Strings subs; + enumDB(nixDB, dbSubstitutes, subs); + + for (Strings::iterator i = subs.begin(); + i != subs.end(); i++) + { + FSId srcId = parseHash(*i); + + Strings subIds; + queryListDB(nixDB, dbSubstitutes, srcId, subIds); + + for (Strings::iterator j = subIds.begin(); + j != subIds.end(); ) + { + FSId subId = parseHash(*j); + + Strings subPaths; + queryListDB(nixDB, dbId2Paths, subId, subPaths); + if (subPaths.size() == 0) { + debug(format("erasing substitute %1% for %2%") + % (string) subId % (string) srcId); + j = subIds.erase(j); + } else j++; + } + + setListDB(nixDB, dbSubstitutes, srcId, subIds); + } + + Strings sucs; + enumDB(nixDB, dbSuccessors, sucs); + + for (Strings::iterator i = sucs.begin(); + i != sucs.end(); i++) + { + FSId id1 = parseHash(*i); + + string id2; + if (!queryDB(nixDB, dbSuccessors, id1, id2)) abort(); + + Strings id2Paths; + queryListDB(nixDB, dbId2Paths, id2, id2Paths); + if (id2Paths.size() == 0) { + Strings id2Subs; + queryListDB(nixDB, dbSubstitutes, id2, id2Subs); + if (id2Subs.size() == 0) { + debug(format("successor %1% for %2% missing") + % id2 % (string) id1); + delDB(nixDB, dbSuccessors, (string) id1); + } + } + } +} diff --git a/src/store.hh b/src/store.hh index 7dd0f72e6..78d5529e7 100644 --- a/src/store.hh +++ b/src/store.hh @@ -35,5 +35,7 @@ void addToStore(string srcPath, string & dstPath, FSId & id, /* Delete a value from the nixStore directory. */ void deleteFromStore(const string & path); +void verifyStore(); + #endif /* !__STORE_H */ diff --git a/src/util.hh b/src/util.hh index 684bafbb5..611612a58 100644 --- a/src/util.hh +++ b/src/util.hh @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -38,6 +39,7 @@ public: typedef list Strings; +typedef set StringSet; /* The canonical system name, as returned by config.guess. */ From ab350eafd2c1a98ea98090fdb3bd9b7ae4f7336b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 18 Jul 2003 07:42:57 +0000 Subject: [PATCH 0147/6440] * Generate nar.sh, fetchurl.sh. --- configure.ac | 4 +++- corepkgs/Makefile.am | 11 +---------- corepkgs/fetchurl/Makefile.am | 8 ++++++++ corepkgs/nar/Makefile.am | 10 ++++++++++ corepkgs/nar/nar.sh | 5 ----- corepkgs/nar/nar.sh.in | 5 +++++ corepkgs/nar/unnar.sh | 4 ---- corepkgs/nar/unnar.sh.in | 4 ++++ scripts/Makefile.am | 8 +------- 9 files changed, 32 insertions(+), 27 deletions(-) create mode 100644 corepkgs/fetchurl/Makefile.am create mode 100644 corepkgs/nar/Makefile.am delete mode 100644 corepkgs/nar/nar.sh create mode 100644 corepkgs/nar/nar.sh.in delete mode 100644 corepkgs/nar/unnar.sh create mode 100644 corepkgs/nar/unnar.sh.in diff --git a/configure.ac b/configure.ac index 77a5f1f1f..9db0a8807 100644 --- a/configure.ac +++ b/configure.ac @@ -12,5 +12,7 @@ AC_PROG_CXX AC_PROG_RANLIB AM_CONFIG_HEADER([config.h]) -AC_CONFIG_FILES([Makefile src/Makefile scripts/Makefile corepkgs/Makefile]) +AC_CONFIG_FILES([Makefile src/Makefile scripts/Makefile + corepkgs/Makefile corepkgs/fetchurl/Makefile + corepkgs/nar/Makefile]) AC_OUTPUT diff --git a/corepkgs/Makefile.am b/corepkgs/Makefile.am index 9298865bf..e5b892bfc 100644 --- a/corepkgs/Makefile.am +++ b/corepkgs/Makefile.am @@ -1,10 +1 @@ -install-data-local: - $(INSTALL) -d $(datadir)/fix - $(INSTALL) -d $(datadir)/fix/fetchurl - $(INSTALL_DATA) fetchurl/fetchurl.fix $(datadir)/fix/fetchurl - $(INSTALL_DATA) fetchurl/fetchurl.sh $(datadir)/fix/fetchurl - $(INSTALL) -d $(datadir)/fix/nar - $(INSTALL_DATA) nar/nar.fix $(datadir)/fix/nar - $(INSTALL_DATA) nar/nar.sh $(datadir)/fix/nar - $(INSTALL_DATA) nar/unnar.fix $(datadir)/fix/nar - $(INSTALL_DATA) nar/unnar.sh $(datadir)/fix/nar +SUBDIRS = fetchurl nar diff --git a/corepkgs/fetchurl/Makefile.am b/corepkgs/fetchurl/Makefile.am new file mode 100644 index 000000000..6bae43907 --- /dev/null +++ b/corepkgs/fetchurl/Makefile.am @@ -0,0 +1,8 @@ +all-local: fetchurl.sh + +install-exec-local: + $(INSTALL) -d $(datadir)/fix/fetchurl + $(INSTALL_DATA) fetchurl.fix $(datadir)/fix/fetchurl + $(INSTALL_DATA) fetchurl.sh $(datadir)/fix/fetchurl + +include ../../substitute.mk diff --git a/corepkgs/nar/Makefile.am b/corepkgs/nar/Makefile.am new file mode 100644 index 000000000..508eeff7c --- /dev/null +++ b/corepkgs/nar/Makefile.am @@ -0,0 +1,10 @@ +all-local: nar.sh unnar.sh + +install-exec-local: + $(INSTALL) -d $(datadir)/fix/nar + $(INSTALL_DATA) nar.fix $(datadir)/fix/nar + $(INSTALL_DATA) nar.sh $(datadir)/fix/nar + $(INSTALL_DATA) unnar.fix $(datadir)/fix/nar + $(INSTALL_DATA) unnar.sh $(datadir)/fix/nar + +include ../../substitute.mk diff --git a/corepkgs/nar/nar.sh b/corepkgs/nar/nar.sh deleted file mode 100644 index a7b6be8aa..000000000 --- a/corepkgs/nar/nar.sh +++ /dev/null @@ -1,5 +0,0 @@ -#! /bin/sh - -echo "packing $path into $out..." -/nix/bin/nix --dump --file "$path" | bzip2 > $out || exit 1 - diff --git a/corepkgs/nar/nar.sh.in b/corepkgs/nar/nar.sh.in new file mode 100644 index 000000000..bffbbaf5e --- /dev/null +++ b/corepkgs/nar/nar.sh.in @@ -0,0 +1,5 @@ +#! /bin/sh + +echo "packing $path into $out..." +@bindir@/nix --dump --file "$path" | bzip2 > $out || exit 1 + diff --git a/corepkgs/nar/unnar.sh b/corepkgs/nar/unnar.sh deleted file mode 100644 index cc21efb2b..000000000 --- a/corepkgs/nar/unnar.sh +++ /dev/null @@ -1,4 +0,0 @@ -#! /bin/sh - -echo "unpacking $nar to $out..." -bunzip2 < $nar | /nix/bin/nix --restore "$out" || exit 1 diff --git a/corepkgs/nar/unnar.sh.in b/corepkgs/nar/unnar.sh.in new file mode 100644 index 000000000..4b00cb981 --- /dev/null +++ b/corepkgs/nar/unnar.sh.in @@ -0,0 +1,4 @@ +#! /bin/sh + +echo "unpacking $nar to $out..." +bunzip2 < $nar | @bindir@/nix --restore "$out" || exit 1 diff --git a/scripts/Makefile.am b/scripts/Makefile.am index a8cbe8222..fb2ef566d 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -10,10 +10,4 @@ install-exec-local: # !!! don't overwrite local modifications $(INSTALL_DATA) prebuilts.conf $(sysconfdir)/nix/prebuilts.conf -%: %.in Makefile - sed \ - -e s^@prefix\@^$(prefix)^g \ - -e s^@sysconfdir\@^$(sysconfdir)^g \ - -e s^@localstatedir\@^$(localstatedir)^g \ - < $< > $@ || rm $@ - chmod +x $@ +include ../substitute.mk From 6f1a0f948dc5a98f2efcdafb0fdde96bebbf90da Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 20 Jul 2003 19:29:38 +0000 Subject: [PATCH 0148/6440] * Refactorings. --- src/Makefile.am | 3 +- src/exec.cc | 123 ++++++++++ src/exec.hh | 18 ++ src/fix.cc | 74 +++--- src/fstate.cc | 591 ++++++++++------------------------------------- src/fstate.hh | 89 +++---- src/nix.cc | 3 +- src/normalise.cc | 265 +++++++++++++++++++++ src/normalise.hh | 25 ++ src/normalise.o | Bin 0 -> 888596 bytes src/store.cc | 2 +- src/test.cc | 37 +-- src/util.cc | 2 +- substitute.mk | 8 + 14 files changed, 635 insertions(+), 605 deletions(-) create mode 100644 src/exec.cc create mode 100644 src/exec.hh create mode 100644 src/normalise.cc create mode 100644 src/normalise.hh create mode 100644 src/normalise.o create mode 100644 substitute.mk diff --git a/src/Makefile.am b/src/Makefile.am index 3c590f4c0..92aee2f55 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,7 +20,8 @@ test_LDADD = libshared.a libnix.a -ldb_cxx-4 -lATerm noinst_LIBRARIES = libnix.a libshared.a libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c \ - fstate.cc store.cc globals.cc db.cc references.cc + store.cc fstate.cc normalise.cc exec.cc \ + globals.cc db.cc references.cc libshared_a_SOURCES = shared.cc diff --git a/src/exec.cc b/src/exec.cc new file mode 100644 index 000000000..016dbaedd --- /dev/null +++ b/src/exec.cc @@ -0,0 +1,123 @@ +#include + +#include +#include +#include +#include +#include + +#include "exec.hh" +#include "util.hh" +#include "globals.hh" + + +class AutoDelete +{ + string path; + bool del; +public: + + AutoDelete(const string & p) : path(p) + { + del = true; + } + + ~AutoDelete() + { + if (del) deletePath(path); + } + + void cancel() + { + del = false; + } +}; + + +/* Run a program. */ +void runProgram(const string & program, Environment env) +{ + /* Create a log file. */ + string logFileName = nixLogDir + "/run.log"; + /* !!! auto-pclose on exit */ + FILE * logFile = popen(("tee -a " + logFileName + " >&2").c_str(), "w"); /* !!! escaping */ + if (!logFile) + throw SysError(format("creating log file `%1%'") % logFileName); + + /* Create a temporary directory where the build will take + place. */ + static int counter = 0; + string tmpDir = (format("/tmp/nix-%1%-%2%") % getpid() % counter++).str(); + + if (mkdir(tmpDir.c_str(), 0777) == -1) + throw SysError(format("creating directory `%1%'") % tmpDir); + + AutoDelete delTmpDir(tmpDir); + + /* Fork a child to build the package. */ + pid_t pid; + switch (pid = fork()) { + + case -1: + throw SysError("unable to fork"); + + case 0: + + try { /* child */ + + if (chdir(tmpDir.c_str()) == -1) + throw SysError(format("changing into to `%1%'") % tmpDir); + + /* Fill in the environment. We don't bother freeing + the strings, since we'll exec or die soon + anyway. */ + const char * env2[env.size() + 1]; + int i = 0; + for (Environment::iterator it = env.begin(); + it != env.end(); it++, i++) + env2[i] = (new string(it->first + "=" + it->second))->c_str(); + env2[i] = 0; + + /* Dup the log handle into stderr. */ + if (dup2(fileno(logFile), STDERR_FILENO) == -1) + throw SysError("cannot pipe standard error into log file"); + + /* Dup stderr to stdin. */ + if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1) + throw SysError("cannot dup stderr into stdout"); + + /* Make the program executable. !!! hack. */ + if (chmod(program.c_str(), 0755)) + throw SysError("cannot make program executable"); + + /* Execute the program. This should not return. */ + execle(program.c_str(), baseNameOf(program).c_str(), 0, env2); + + throw SysError(format("unable to execute %1%") % program); + + } catch (exception & e) { + cerr << format("build error: %1%\n") % e.what(); + } + _exit(1); + + } + + /* parent */ + + /* Close the logging pipe. Note that this should not cause + the logger to exit until builder exits (because the latter + has an open file handle to the former). */ + pclose(logFile); + + /* Wait for the child to finish. */ + int status; + if (waitpid(pid, &status, 0) != pid) + throw Error("unable to wait for child"); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + delTmpDir.cancel(); + throw Error("unable to build package"); + } +} + + diff --git a/src/exec.hh b/src/exec.hh new file mode 100644 index 000000000..9dc8e0cd0 --- /dev/null +++ b/src/exec.hh @@ -0,0 +1,18 @@ +#ifndef __EXEC_H +#define __EXEC_H + +#include +#include + +using namespace std; + + +/* A Unix environment is a mapping from strings to strings. */ +typedef map Environment; + + +/* Run a program. */ +void runProgram(const string & program, Environment env); + + +#endif /* !__EXEC_H */ diff --git a/src/fix.cc b/src/fix.cc index f55074907..cf6d5617a 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -2,8 +2,7 @@ #include #include "globals.hh" -#include "fstate.hh" -#include "store.hh" +#include "normalise.hh" #include "shared.hh" @@ -111,12 +110,11 @@ static Expr evalExpr(Expr e) ATmatch(e, "FSId()", &s1)) return e; - if (ATgetType(e) == AT_APPL && - ((string) ATgetName(ATgetAFun(e)) == "Slice" || - (string) ATgetName(ATgetAFun(e)) == "Derive")) - { + try { + parseFState(e); return ATmake("FSId()", - ((string) writeTerm(e, "", 0)).c_str()); + ((string) writeTerm(e, "")).c_str()); + } catch (...) { /* !!! catch parse errors only */ } /* Application. */ @@ -139,10 +137,17 @@ static Expr evalExpr(Expr e) string dstPath; FSId id; addToStore(srcPath, dstPath, id, true); - FState fs = ATmake("Slice([], [(, , [])])", - ((string) id).c_str(), dstPath.c_str(), ((string) id).c_str()); + + SliceElem elem; + elem.path = dstPath; + elem.id = id; + FState fs; + fs.type = FState::fsSlice; + fs.slice.roots.push_back(id); + fs.slice.elems.push_back(elem); + return ATmake("FSId()", - ((string) writeTerm(fs, "", 0)).c_str()); + ((string) writeTerm(unparseFState(fs), "")).c_str()); } /* Packages are transformed into Derive fstate expressions. */ @@ -160,8 +165,10 @@ static Expr evalExpr(Expr e) } /* Gather information for building the Derive expression. */ - ATermList ins = ATempty, env = ATempty; - string builder, name; + FState fs; + fs.type = FState::fsDerive; + fs.derive.platform = SYSTEM; + string name; bnds = ATempty; for (map::iterator it = bndMap.begin(); @@ -169,21 +176,19 @@ static Expr evalExpr(Expr e) { string key = it->first; ATerm value = it->second; - char * id; - if (ATmatch(value, "FSId()", &id)) { - Strings paths = fstatePaths(parseHash(id), false); + if (ATmatch(value, "FSId()", &s1)) { + FSId id = parseHash(s1); + Strings paths = fstatePaths(id, false); if (paths.size() != 1) abort(); string path = *(paths.begin()); - ins = ATinsert(ins, ATmake("", id)); - env = ATinsert(env, ATmake("(, )", - key.c_str(), path.c_str())); - if (key == "build") builder = path; + fs.derive.inputs.push_back(id); + fs.derive.env.push_back(StringPair(key, path)); + if (key == "build") fs.derive.builder = path; } else if (ATmatch(value, "", &s1)) { if (key == "name") name = s1; - env = ATinsert(env, - ATmake("(, )", key.c_str(), s1)); + fs.derive.env.push_back(StringPair(key, s1)); } else throw badTerm("invalid package argument", value); @@ -191,31 +196,24 @@ static Expr evalExpr(Expr e) ATmake("(, )", key.c_str(), value)); } - /* Hash the normal form to produce a unique but deterministic - path name for this package. */ - ATerm nf = ATmake("Package()", ATreverse(bnds)); - FSId outId = hashTerm(nf); - - if (builder == "") - throw badTerm("no builder specified", nf); + if (fs.derive.builder == "") + throw badTerm("no builder specified", e); if (name == "") - throw badTerm("no package name specified", nf); + throw badTerm("no package name specified", e); + /* Hash the fstate-expression with no outputs to produce a + unique but deterministic path name for this package. */ + Hash outId = hashTerm(unparseFState(fs)); string outPath = canonPath(nixStore + "/" + ((string) outId).c_str() + "-" + name); - - env = ATinsert(env, ATmake("(, )", "out", outPath.c_str())); - - /* Construct the result. */ - FState fs = - ATmake("Derive([(, )], , , , )", - outPath.c_str(), ((string) outId).c_str(), - ins, builder.c_str(), SYSTEM, env); + fs.derive.env.push_back(StringPair("out", outPath)); + fs.derive.outputs.push_back(DeriveOutput(outPath, outId)); + debug(format("%1%: %2%") % (string) outId % name); /* Write the resulting term into the Nix store directory. */ return ATmake("FSId()", - ((string) writeTerm(fs, "-d-" + name, 0)).c_str()); + ((string) writeTerm(unparseFState(fs), "-d-" + name)).c_str()); } /* BaseName primitive function. */ diff --git a/src/fstate.cc b/src/fstate.cc index 31dd17582..85f8c75cc 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -1,141 +1,6 @@ -#include -#include - -#include -#include -#include -#include -#include - #include "fstate.hh" #include "globals.hh" #include "store.hh" -#include "db.hh" -#include "references.hh" - - -/* A Unix environment is a mapping from strings to strings. */ -typedef map Environment; - - -class AutoDelete -{ - string path; - bool del; -public: - - AutoDelete(const string & p) : path(p) - { - del = true; - } - - ~AutoDelete() - { - if (del) deletePath(path); - } - - void cancel() - { - del = false; - } -}; - - -/* Run a program. */ -static void runProgram(const string & program, Environment env) -{ - /* Create a log file. */ - string logFileName = nixLogDir + "/run.log"; - /* !!! auto-pclose on exit */ - FILE * logFile = popen(("tee -a " + logFileName + " >&2").c_str(), "w"); /* !!! escaping */ - if (!logFile) - throw SysError(format("creating log file `%1%'") % logFileName); - - /* Create a temporary directory where the build will take - place. */ - static int counter = 0; - string tmpDir = (format("/tmp/nix-%1%-%2%") % getpid() % counter++).str(); - - if (mkdir(tmpDir.c_str(), 0777) == -1) - throw SysError(format("creating directory `%1%'") % tmpDir); - - AutoDelete delTmpDir(tmpDir); - - /* Fork a child to build the package. */ - pid_t pid; - switch (pid = fork()) { - - case -1: - throw SysError("unable to fork"); - - case 0: - - try { /* child */ - - if (chdir(tmpDir.c_str()) == -1) - throw SysError(format("changing into to `%1%'") % tmpDir); - - /* Fill in the environment. We don't bother freeing - the strings, since we'll exec or die soon - anyway. */ - const char * env2[env.size() + 1]; - int i = 0; - for (Environment::iterator it = env.begin(); - it != env.end(); it++, i++) - env2[i] = (new string(it->first + "=" + it->second))->c_str(); - env2[i] = 0; - - /* Dup the log handle into stderr. */ - if (dup2(fileno(logFile), STDERR_FILENO) == -1) - throw SysError("cannot pipe standard error into log file"); - - /* Dup stderr to stdin. */ - if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1) - throw SysError("cannot dup stderr into stdout"); - - /* Make the program executable. !!! hack. */ - if (chmod(program.c_str(), 0755)) - throw SysError("cannot make program executable"); - - /* Execute the program. This should not return. */ - execle(program.c_str(), baseNameOf(program).c_str(), 0, env2); - - throw SysError(format("unable to execute %1%") % program); - - } catch (exception & e) { - cerr << format("build error: %1%\n") % e.what(); - } - _exit(1); - - } - - /* parent */ - - /* Close the logging pipe. Note that this should not cause - the logger to exit until builder exits (because the latter - has an open file handle to the former). */ - pclose(logFile); - - /* Wait for the child to finish. */ - int status; - if (waitpid(pid, &status, 0) != pid) - throw Error("unable to wait for child"); - - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - delTmpDir.cancel(); - throw Error("unable to build package"); - } -} - - -/* Throw an exception if the given platform string is not supported by - the platform we are executing on. */ -static void checkPlatform(const string & platform) -{ - if (platform != thisSystem) - throw Error(format("a `%1%' is required, but I am a `%2%'") - % platform % thisSystem); -} string printTerm(ATerm t) @@ -157,23 +22,16 @@ Hash hashTerm(ATerm t) } -FState hash2fstate(Hash hash) -{ - return ATmake("Include()", ((string) hash).c_str()); -} - - -ATerm termFromId(const FSId & id, string * p) +ATerm termFromId(const FSId & id) { string path = expandId(id); - if (p) *p = path; ATerm t = ATreadFromNamedFile(path.c_str()); if (!t) throw Error(format("cannot read aterm from `%1%'") % path); return t; } -FSId writeTerm(ATerm t, const string & suffix, string * p) +FSId writeTerm(ATerm t, const string & suffix) { FSId id = hashTerm(t); @@ -182,28 +40,14 @@ FSId writeTerm(ATerm t, const string & suffix, string * p) if (!ATwriteToNamedTextFile(t, path.c_str())) throw Error(format("cannot write aterm %1%") % path); +// debug(format("written term %1% = %2%") % (string) id % +// printTerm(t)); + registerPath(path, id); - if (p) *p = path; - return id; } -void registerSuccessor(const FSId & id1, const FSId & id2) -{ - setDB(nixDB, dbSuccessors, id1, id2); -} - - -static FSId storeSuccessor(const FSId & id1, FState sc, - string * p) -{ - FSId id2 = writeTerm(sc, "-s-" + (string) id1, p); - registerSuccessor(id1, id2); - return id2; -} - - static void parseIds(ATermList ids, FSIds & out) { while (!ATisEmpty(ids)) { @@ -217,251 +61,9 @@ static void parseIds(ATermList ids, FSIds & out) } -static void checkSlice(const Slice & slice); - - -/* Parse a slice. */ -static Slice parseSlice(FState fs) -{ - Slice slice; - ATermList roots, elems; - - if (!ATmatch(fs, "Slice([], [])", &roots, &elems)) - throw badTerm("not a slice", fs); - - parseIds(roots, slice.roots); - - while (!ATisEmpty(elems)) { - char * s1, * s2; - ATermList refs; - ATerm t = ATgetFirst(elems); - if (!ATmatch(t, "(, , [])", &s1, &s2, &refs)) - throw badTerm("not a slice element", t); - SliceElem elem; - elem.path = s1; - elem.id = parseHash(s2); - parseIds(refs, elem.refs); - slice.elems.push_back(elem); - elems = ATgetNext(elems); - } - - checkSlice(slice); - - return slice; -} - - -static ATermList unparseIds(const FSIds & ids) -{ - ATermList l = ATempty; - for (FSIds::const_iterator i = ids.begin(); - i != ids.end(); i++) - l = ATinsert(l, - ATmake("", ((string) *i).c_str())); - return ATreverse(l); -} - - -static FState unparseSlice(const Slice & slice) -{ - ATermList roots = unparseIds(slice.roots); - - ATermList elems = ATempty; - for (SliceElems::const_iterator i = slice.elems.begin(); - i != slice.elems.end(); i++) - elems = ATinsert(elems, - ATmake("(, , )", - i->path.c_str(), - ((string) i->id).c_str(), - unparseIds(i->refs))); - - return ATmake("Slice(, )", roots, elems); -} - - typedef set FSIdSet; -static Slice normaliseFState2(FSId id, StringSet & usedPaths) -{ - debug(format("normalising fstate")); - Nest nest(true); - - /* Try to substitute $id$ by any known successors in order to - speed up the rewrite process. */ - string idSucc; - while (queryDB(nixDB, dbSuccessors, id, idSucc)) { - debug(format("successor %1% -> %2%") % (string) id % idSucc); - id = parseHash(idSucc); - } - - /* Get the fstate expression. */ - string fsPath; - FState fs = termFromId(id, &fsPath); - - /* Already in normal form (i.e., a slice)? */ - if (ATgetType(fs) == AT_APPL && - (string) ATgetName(ATgetAFun(fs)) == "Slice") - { - usedPaths.insert(fsPath); - return parseSlice(fs); - } - - /* Then we it's a Derive node. */ - ATermList outs, ins, bnds; - char * builder; - char * platform; - if (!ATmatch(fs, "Derive([], [], , , [])", - &outs, &ins, &builder, &platform, &bnds)) - throw badTerm("not a derive", fs); - - /* Right platform? */ - checkPlatform(platform); - - /* Realise inputs (and remember all input paths). */ - FSIds inIds; - parseIds(ins, inIds); - - typedef map ElemMap; - - ElemMap inMap; - - for (FSIds::iterator i = inIds.begin(); i != inIds.end(); i++) { - Slice slice = normaliseFState(*i); - realiseSlice(slice); - - for (SliceElems::iterator j = slice.elems.begin(); - j != slice.elems.end(); j++) - inMap[j->path] = *j; - } - - Strings inPaths; - for (ElemMap::iterator i = inMap.begin(); i != inMap.end(); i++) - inPaths.push_back(i->second.path); - - /* Build the environment. */ - Environment env; - while (!ATisEmpty(bnds)) { - char * s1, * s2; - ATerm bnd = ATgetFirst(bnds); - if (!ATmatch(bnd, "(, )", &s1, &s2)) - throw badTerm("tuple of strings expected", bnd); - env[s1] = s2; - bnds = ATgetNext(bnds); - } - - /* Parse the outputs. */ - typedef map OutPaths; - OutPaths outPaths; - while (!ATisEmpty(outs)) { - ATerm t = ATgetFirst(outs); - char * s1, * s2; - if (!ATmatch(t, "(, )", &s1, &s2)) - throw badTerm("string expected", t); - outPaths[s1] = parseHash(s2); - inPaths.push_back(s1); - outs = ATgetNext(outs); - } - - /* We can skip running the builder if we can expand all output - paths from their ids. */ - bool fastBuild = false; -#if 0 - for (OutPaths::iterator i = outPaths.begin(); - i != outPaths.end(); i++) - { - try { - expandId(i->second, i->first); - } catch (...) { - fastBuild = false; - break; - } - } -#endif - - if (!fastBuild) { - - /* Check that none of the outputs exist. */ - for (OutPaths::iterator i = outPaths.begin(); - i != outPaths.end(); i++) - if (pathExists(i->first)) - throw Error(format("path `%1%' exists") % i->first); - - /* Run the builder. */ - runProgram(builder, env); - - } else - debug(format("skipping build")); - - Slice slice; - - /* Check whether the output paths were created, and register each - one. */ - FSIdSet used; - for (OutPaths::iterator i = outPaths.begin(); - i != outPaths.end(); i++) - { - string path = i->first; - if (!pathExists(path)) - throw Error(format("path `%1%' does not exist") % path); - registerPath(path, i->second); - slice.roots.push_back(i->second); - - Strings refs = filterReferences(path, inPaths); - - SliceElem elem; - elem.path = path; - elem.id = i->second; - - for (Strings::iterator j = refs.begin(); j != refs.end(); j++) { - ElemMap::iterator k; - OutPaths::iterator l; - if ((k = inMap.find(*j)) != inMap.end()) { - elem.refs.push_back(k->second.id); - used.insert(k->second.id); - for (FSIds::iterator m = k->second.refs.begin(); - m != k->second.refs.end(); m++) - used.insert(*m); - } else if ((l = outPaths.find(*j)) != outPaths.end()) { - elem.refs.push_back(l->second); - used.insert(l->second); - } else - throw Error(format("unknown referenced path `%1%'") % *j); - } - - slice.elems.push_back(elem); - } - - for (ElemMap::iterator i = inMap.begin(); - i != inMap.end(); i++) - { - FSIdSet::iterator j = used.find(i->second.id); - if (j == used.end()) - debug(format("NOT referenced: `%1%'") % i->second.path); - else { - debug(format("referenced: `%1%'") % i->second.path); - slice.elems.push_back(i->second); - } - } - - FState nf = unparseSlice(slice); - debug(printTerm(nf)); - storeSuccessor(id, nf, &fsPath); - usedPaths.insert(fsPath); - - parseSlice(nf); /* check */ - - return slice; -} - - -Slice normaliseFState(FSId id) -{ - StringSet dummy; - return normaliseFState2(id, dummy); -} - - static void checkSlice(const Slice & slice) { if (slice.elems.size() == 0) @@ -486,97 +88,142 @@ static void checkSlice(const Slice & slice) } -void realiseSlice(const Slice & slice) +/* Parse a slice. */ +static bool parseSlice(ATerm t, Slice & slice) { - debug(format("realising slice")); - Nest nest(true); + ATermList roots, elems; + + if (!ATmatch(t, "Slice([], [])", &roots, &elems)) + return false; - /* Perhaps all paths already contain the right id? */ + parseIds(roots, slice.roots); - bool missing = false; - for (SliceElems::const_iterator i = slice.elems.begin(); - i != slice.elems.end(); i++) - { - SliceElem elem = *i; - string id; - if (!queryDB(nixDB, dbPath2Id, elem.path, id)) { - if (pathExists(elem.path)) - throw Error(format("path `%1%' obstructed") % elem.path); - missing = true; - break; - } - if (parseHash(id) != elem.id) - throw Error(format("path `%1%' obstructed") % elem.path); + while (!ATisEmpty(elems)) { + char * s1, * s2; + ATermList refs; + ATerm t = ATgetFirst(elems); + if (!ATmatch(t, "(, , [])", &s1, &s2, &refs)) + throw badTerm("not a slice element", t); + SliceElem elem; + elem.path = s1; + elem.id = parseHash(s2); + parseIds(refs, elem.refs); + slice.elems.push_back(elem); + elems = ATgetNext(elems); } - if (!missing) { - debug(format("already installed")); - return; - } - - /* For each element, expand its id at its path. */ - for (SliceElems::const_iterator i = slice.elems.begin(); - i != slice.elems.end(); i++) - { - SliceElem elem = *i; - expandId(elem.id, elem.path); - } + checkSlice(slice); + return true; } -Strings fstatePaths(const FSId & id, bool normalise) +static bool parseDerive(ATerm t, Derive & derive) { - Strings paths; - - FState fs = termFromId(id); - ATermList outs, ins, bnds; char * builder; char * platform; - if (normalise || - (ATgetType(fs) == AT_APPL && - (string) ATgetName(ATgetAFun(fs)) == "Slice")) - { - Slice slice; - if (normalise) - slice = normaliseFState(id); - else - slice = parseSlice(fs); + if (!ATmatch(t, "Derive([], [], , , [])", + &outs, &ins, &builder, &platform, &bnds)) + return false; - /* !!! fix complexity */ - for (FSIds::const_iterator i = slice.roots.begin(); - i != slice.roots.end(); i++) - for (SliceElems::const_iterator j = slice.elems.begin(); - j != slice.elems.end(); j++) - if (*i == j->id) paths.push_back(j->path); + while (!ATisEmpty(outs)) { + char * s1, * s2; + ATerm t = ATgetFirst(outs); + if (!ATmatch(t, "(, )", &s1, &s2)) + throw badTerm("not a derive output", t); + derive.outputs.push_back(DeriveOutput(s1, parseHash(s2))); + outs = ATgetNext(outs); } - else if (ATmatch(fs, "Derive([], [], , , [])", - &outs, &ins, &builder, &platform, &bnds)) - { - while (!ATisEmpty(outs)) { - ATerm t = ATgetFirst(outs); - char * s1, * s2; - if (!ATmatch(t, "(, )", &s1, &s2)) - throw badTerm("string expected", t); - paths.push_back(s1); - outs = ATgetNext(outs); - } - } + parseIds(ins, derive.inputs); + + derive.builder = builder; + derive.platform = platform; - else throw badTerm("in fstatePaths", fs); + while (!ATisEmpty(bnds)) { + char * s1, * s2; + ATerm bnd = ATgetFirst(bnds); + if (!ATmatch(bnd, "(, )", &s1, &s2)) + throw badTerm("tuple of strings expected", bnd); + derive.env.push_back(StringPair(s1, s2)); + bnds = ATgetNext(bnds); + } - return paths; + return true; } -StringSet fstateRefs(const FSId & id) +FState parseFState(ATerm t) { - StringSet paths; - Slice slice = normaliseFState2(id, paths); + FState fs; + if (parseSlice(t, fs.slice)) + fs.type = FState::fsSlice; + else if (parseDerive(t, fs.derive)) + fs.type = FState::fsDerive; + else throw badTerm("not an fstate-expression", t); + return fs; +} + + +static ATermList unparseIds(const FSIds & ids) +{ + ATermList l = ATempty; + for (FSIds::const_iterator i = ids.begin(); + i != ids.end(); i++) + l = ATinsert(l, + ATmake("", ((string) *i).c_str())); + return ATreverse(l); +} + + +static ATerm unparseSlice(const Slice & slice) +{ + ATermList roots = unparseIds(slice.roots); + + ATermList elems = ATempty; for (SliceElems::const_iterator i = slice.elems.begin(); i != slice.elems.end(); i++) - paths.insert(i->path); - return paths; + elems = ATinsert(elems, + ATmake("(, , )", + i->path.c_str(), + ((string) i->id).c_str(), + unparseIds(i->refs))); + + return ATmake("Slice(, )", roots, elems); +} + + +static ATerm unparseDerive(const Derive & derive) +{ + ATermList outs = ATempty; + for (DeriveOutputs::const_iterator i = derive.outputs.begin(); + i != derive.outputs.end(); i++) + outs = ATinsert(outs, + ATmake("(, )", + i->first.c_str(), ((string) i->second).c_str())); + + ATermList env = ATempty; + for (StringPairs::const_iterator i = derive.env.begin(); + i != derive.env.end(); i++) + env = ATinsert(env, + ATmake("(, )", + i->first.c_str(), i->second.c_str())); + + return ATmake("Derive(, , , , )", + ATreverse(outs), + unparseIds(derive.inputs), + derive.builder.c_str(), + derive.platform.c_str(), + ATreverse(env)); +} + + +ATerm unparseFState(const FState & fs) +{ + if (fs.type == FState::fsSlice) + return unparseSlice(fs.slice); + else if (fs.type == FState::fsDerive) + return unparseDerive(fs.derive); + else abort(); } diff --git a/src/fstate.hh b/src/fstate.hh index 0d89e7e36..681a8d094 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -5,55 +5,13 @@ extern "C" { #include } -#include "hash.hh" #include "store.hh" -using namespace std; - -/* \section{Abstract syntax of Nix file system state expressions} - - A Nix file system state expression, or FState, describes a - (partial) state of the file system. - - Slice : [Id] * [(Path, Id, [Id])] -> FState - - (update) - Path(path, content, refs) specifies a file object (its full path - and contents), along with all file objects referenced by it (that - is, that it has pointers to). We assume that all files are - self-referential. This prevents us from having to deal with - cycles. - - Derive : [(Path, Id)] * [FStateId] * Path * [(String, String)] -> FState - - (update) - Derive(platform, builder, ins, outs, env) specifies the creation of - new file objects (in paths declared by `outs') by the execution of - a program `builder' on a platform `platform'. This execution takes - place in a file system state given by `ins'. `env' specifies a - mapping of strings to strings. - - A FState expression is in {\em $f$-normal form} if all Derive nodes - have been reduced to File nodes. - - DISCUSSION: the idea is that a Regular/Directory is interchangeable - with its CHash. This would appear to break referential - transparency, e.g., Derive(..., ..., [...CHash(h)...], ...) can - only be reduced in a context were the Regular/Directory equivalent - of Hash(h) is known. However, CHash should be viewed strictly as a - shorthand; that is, when we export an expression containing a - CHash, we should also export the file object referenced by that - CHash. - -*/ - -typedef ATerm FState; -typedef ATerm Content; +/* Abstract syntax of fstate-expressions. */ typedef list FSIds; - struct SliceElem { string path; @@ -69,6 +27,27 @@ struct Slice SliceElems elems; }; +typedef pair DeriveOutput; +typedef pair StringPair; +typedef list DeriveOutputs; +typedef list StringPairs; + +struct Derive +{ + DeriveOutputs outputs; + FSIds inputs; + string builder; + string platform; + StringPairs env; +}; + +struct FState +{ + enum { fsSlice, fsDerive } type; + Slice slice; + Derive derive; +}; + /* Return a canonical textual representation of an expression. */ string printTerm(ATerm t); @@ -81,28 +60,16 @@ Error badTerm(const format & f, ATerm t); Hash hashTerm(ATerm t); /* Read an aterm from disk, given its id. */ -ATerm termFromId(const FSId & id, string * p = 0); +ATerm termFromId(const FSId & id); /* Write an aterm to the Nix store directory, and return its hash. */ -FSId writeTerm(ATerm t, const string & suffix, string * p = 0); +FSId writeTerm(ATerm t, const string & suffix); -/* Register a successor. */ -void registerSuccessor(const FSId & id1, const FSId & id2); +/* Parse an fstate-expression. */ +FState parseFState(ATerm t); - -/* Normalise an fstate-expression, that is, return an equivalent - Slice. */ -Slice normaliseFState(FSId id); - -/* Realise a Slice in the file system. */ -void realiseSlice(const Slice & slice); - -/* Get the list of root (output) paths of the given - fstate-expression. */ -Strings fstatePaths(const FSId & id, bool normalise); - -/* Get the list of paths referenced by the given fstate-expression. */ -StringSet fstateRefs(const FSId & id); +/* Parse an fstate-expression. */ +ATerm unparseFState(const FState & fs); #endif /* !__FSTATE_H */ diff --git a/src/nix.cc b/src/nix.cc index 5785cd6b4..f5ca0b4d8 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -1,8 +1,7 @@ #include #include "globals.hh" -#include "store.hh" -#include "fstate.hh" +#include "normalise.hh" #include "archive.hh" #include "shared.hh" diff --git a/src/normalise.cc b/src/normalise.cc new file mode 100644 index 000000000..bdeddf08d --- /dev/null +++ b/src/normalise.cc @@ -0,0 +1,265 @@ +#include + +#include "normalise.hh" +#include "references.hh" +#include "db.hh" +#include "exec.hh" +#include "globals.hh" + + +void registerSuccessor(const FSId & id1, const FSId & id2) +{ + setDB(nixDB, dbSuccessors, id1, id2); +} + + +static FSId storeSuccessor(const FSId & id1, ATerm sc) +{ + FSId id2 = writeTerm(sc, "-s-" + (string) id1); + registerSuccessor(id1, id2); + return id2; +} + + +typedef set FSIdSet; + + +Slice normaliseFState(FSId id) +{ + debug(format("normalising fstate %1%") % (string) id); + Nest nest(true); + + /* Try to substitute $id$ by any known successors in order to + speed up the rewrite process. */ + string idSucc; + while (queryDB(nixDB, dbSuccessors, id, idSucc)) { + debug(format("successor %1% -> %2%") % (string) id % idSucc); + id = parseHash(idSucc); + } + + /* Get the fstate expression. */ + FState fs = parseFState(termFromId(id)); + + /* It this is a normal form (i.e., a slice) we are done. */ + if (fs.type == FState::fsSlice) return fs.slice; + + /* Otherwise, it's a derivation. */ + + /* Right platform? */ + if (fs.derive.platform != thisSystem) + throw Error(format("a `%1%' is required, but I am a `%2%'") + % fs.derive.platform % thisSystem); + + /* Realise inputs (and remember all input paths). */ + typedef map ElemMap; + + ElemMap inMap; + + for (FSIds::iterator i = fs.derive.inputs.begin(); + i != fs.derive.inputs.end(); i++) { + Slice slice = normaliseFState(*i); + realiseSlice(slice); + + for (SliceElems::iterator j = slice.elems.begin(); + j != slice.elems.end(); j++) + inMap[j->path] = *j; + } + + Strings inPaths; + for (ElemMap::iterator i = inMap.begin(); i != inMap.end(); i++) + inPaths.push_back(i->second.path); + + /* Build the environment. */ + Environment env; + for (StringPairs::iterator i = fs.derive.env.begin(); + i != fs.derive.env.end(); i++) + env[i->first] = i->second; + + /* Parse the outputs. */ + typedef map OutPaths; + OutPaths outPaths; + for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); + i != fs.derive.outputs.end(); i++) + { + debug(format("building %1% in %2%") % (string) i->second % i->first); + outPaths[i->first] = i->second; + inPaths.push_back(i->first); + } + + /* We can skip running the builder if we can expand all output + paths from their ids. */ + bool fastBuild = false; +#if 0 + for (OutPaths::iterator i = outPaths.begin(); + i != outPaths.end(); i++) + { + try { + expandId(i->second, i->first); + } catch (...) { + fastBuild = false; + break; + } + } +#endif + + if (!fastBuild) { + + /* Check that none of the outputs exist. */ + for (OutPaths::iterator i = outPaths.begin(); + i != outPaths.end(); i++) + if (pathExists(i->first)) + throw Error(format("path `%1%' exists") % i->first); + + /* Run the builder. */ + debug(format("building...")); + runProgram(fs.derive.builder, env); + debug(format("build completed")); + + } else + debug(format("skipping build")); + + /* Check whether the output paths were created, and register each + one. */ + FSIdSet used; + for (OutPaths::iterator i = outPaths.begin(); + i != outPaths.end(); i++) + { + string path = i->first; + if (!pathExists(path)) + throw Error(format("path `%1%' does not exist") % path); + registerPath(path, i->second); + fs.slice.roots.push_back(i->second); + + Strings refs = filterReferences(path, inPaths); + + SliceElem elem; + elem.path = path; + elem.id = i->second; + + for (Strings::iterator j = refs.begin(); j != refs.end(); j++) { + ElemMap::iterator k; + OutPaths::iterator l; + if ((k = inMap.find(*j)) != inMap.end()) { + elem.refs.push_back(k->second.id); + used.insert(k->second.id); + for (FSIds::iterator m = k->second.refs.begin(); + m != k->second.refs.end(); m++) + used.insert(*m); + } else if ((l = outPaths.find(*j)) != outPaths.end()) { + elem.refs.push_back(l->second); + used.insert(l->second); + } else + throw Error(format("unknown referenced path `%1%'") % *j); + } + + fs.slice.elems.push_back(elem); + } + + for (ElemMap::iterator i = inMap.begin(); + i != inMap.end(); i++) + { + FSIdSet::iterator j = used.find(i->second.id); + if (j == used.end()) + debug(format("NOT referenced: `%1%'") % i->second.path); + else { + debug(format("referenced: `%1%'") % i->second.path); + fs.slice.elems.push_back(i->second); + } + } + + fs.type = FState::fsSlice; + ATerm nf = unparseFState(fs); + debug(format("normal form: %1%") % printTerm(nf)); + storeSuccessor(id, nf); + + return fs.slice; +} + + +void realiseSlice(const Slice & slice) +{ + debug(format("realising slice")); + Nest nest(true); + + /* Perhaps all paths already contain the right id? */ + + bool missing = false; + for (SliceElems::const_iterator i = slice.elems.begin(); + i != slice.elems.end(); i++) + { + SliceElem elem = *i; + string id; + if (!queryDB(nixDB, dbPath2Id, elem.path, id)) { + if (pathExists(elem.path)) + throw Error(format("path `%1%' obstructed") % elem.path); + missing = true; + break; + } + if (parseHash(id) != elem.id) + throw Error(format("path `%1%' obstructed") % elem.path); + } + + if (!missing) { + debug(format("already installed")); + return; + } + + /* For each element, expand its id at its path. */ + for (SliceElems::const_iterator i = slice.elems.begin(); + i != slice.elems.end(); i++) + { + SliceElem elem = *i; + debug(format("expanding %1% in %2%") % (string) elem.id % elem.path); + expandId(elem.id, elem.path); + } +} + + +Strings fstatePaths(const FSId & id, bool normalise) +{ + Strings paths; + + FState fs; + + if (normalise) { + fs.slice = normaliseFState(id); + fs.type = FState::fsSlice; + } else + fs = parseFState(termFromId(id)); + + if (fs.type == FState::fsSlice) { + /* !!! fix complexity */ + for (FSIds::const_iterator i = fs.slice.roots.begin(); + i != fs.slice.roots.end(); i++) + for (SliceElems::const_iterator j = fs.slice.elems.begin(); + j != fs.slice.elems.end(); j++) + if (*i == j->id) paths.push_back(j->path); + } + + else if (fs.type == FState::fsDerive) { + for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); + i != fs.derive.outputs.end(); i++) + paths.push_back(i->first); + } + + else abort(); + + return paths; +} + + +StringSet fstateRefs(const FSId & id) +{ + StringSet paths; + Slice slice = normaliseFState(id); + for (SliceElems::const_iterator i = slice.elems.begin(); + i != slice.elems.end(); i++) + paths.insert(i->path); + return paths; +} + + +void findGenerators(const FSIds & ids) +{ + +} diff --git a/src/normalise.hh b/src/normalise.hh new file mode 100644 index 000000000..85dbca5ef --- /dev/null +++ b/src/normalise.hh @@ -0,0 +1,25 @@ +#ifndef __NORMALISE_H +#define __NORMALISE_H + +#include "fstate.hh" + + +/* Normalise an fstate-expression, that is, return an equivalent + Slice. */ +Slice normaliseFState(FSId id); + +/* Realise a Slice in the file system. */ +void realiseSlice(const Slice & slice); + +/* Get the list of root (output) paths of the given + fstate-expression. */ +Strings fstatePaths(const FSId & id, bool normalise); + +/* Get the list of paths referenced by the given fstate-expression. */ +StringSet fstateRefs(const FSId & id); + +/* Register a successor. */ +void registerSuccessor(const FSId & id1, const FSId & id2); + + +#endif /* !__NORMALISE_H */ diff --git a/src/normalise.o b/src/normalise.o new file mode 100644 index 0000000000000000000000000000000000000000..c23482c54d1cca28fb9b92faa46c432091840f27 GIT binary patch literal 888596 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�XjP# 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 literal 0 HcmV?d00001 diff --git a/src/store.cc b/src/store.cc index 864213692..0ce5f2604 100644 --- a/src/store.cc +++ b/src/store.cc @@ -7,7 +7,7 @@ #include "globals.hh" #include "db.hh" #include "archive.hh" -#include "fstate.hh" +#include "normalise.hh" struct CopySink : DumpSink diff --git a/src/test.cc b/src/test.cc index 219281c8b..fa7c93820 100644 --- a/src/test.cc +++ b/src/test.cc @@ -6,14 +6,14 @@ #include "hash.hh" #include "archive.hh" #include "util.hh" -#include "fstate.hh" -#include "store.hh" +#include "normalise.hh" #include "globals.hh" void realise(FSId id) { - cout << format("realising %1%\n") % (string) id; + debug(format("TEST: realising %1%") % (string) id); + Nest nest(true); Slice slice = normaliseFState(id); realiseSlice(slice); } @@ -117,7 +117,7 @@ void runTests() string builder1fn; addToStore("./test-builder-1.sh", builder1fn, builder1id); - FState fs1 = ATmake( + ATerm fs1 = ATmake( "Slice([], [(, , [])])", ((string) builder1id).c_str(), builder1fn.c_str(), @@ -127,7 +127,7 @@ void runTests() realise(fs1id); realise(fs1id); - FState fs2 = ATmake( + ATerm fs2 = ATmake( "Slice([], [(, , [])])", ((string) builder1id).c_str(), (builder1fn + "_bla").c_str(), @@ -139,7 +139,7 @@ void runTests() string out1id = hashString("foo"); /* !!! bad */ string out1fn = nixStore + "/" + (string) out1id + "-hello.txt"; - FState fs3 = ATmake( + ATerm fs3 = ATmake( "Derive([(, )], [], , , [(\"out\", )])", out1fn.c_str(), ((string) out1id).c_str(), @@ -158,7 +158,7 @@ void runTests() string builder4fn; addToStore("./test-builder-2.sh", builder4fn, builder4id); - FState fs4 = ATmake( + ATerm fs4 = ATmake( "Slice([], [(, , [])])", ((string) builder4id).c_str(), builder4fn.c_str(), @@ -169,7 +169,7 @@ void runTests() string out5id = hashString("bar"); /* !!! bad */ string out5fn = nixStore + "/" + (string) out5id + "-hello2"; - FState fs5 = ATmake( + ATerm fs5 = ATmake( "Derive([(, )], [], , , [(\"out\", ), (\"builder\", )])", out5fn.c_str(), ((string) out5id).c_str(), @@ -183,27 +183,6 @@ void runTests() realise(fs5id); realise(fs5id); - - -#if 0 - FState fs2 = ATmake( - "Path(, Hash(), [])", - (builder1fn + "_bla").c_str(), - ((string) builder1h).c_str()); - realise(fs2); - realise(fs2); - - string out1fn = nixStore + "/hello.txt"; - FState fs3 = ATmake( - "Derive(, , [], , [(\"out\", )])", - thisSystem.c_str(), - builder1fn.c_str(), - fs1, - out1fn.c_str(), - out1fn.c_str()); - realise(fs3); -#endif - } diff --git a/src/util.cc b/src/util.cc index 00a3063d6..d7c1fe60e 100644 --- a/src/util.cc +++ b/src/util.cc @@ -150,7 +150,7 @@ void msg(const format & f) { string spaces; for (int i = 0; i < nestingLevel; i++) - spaces += " "; + spaces += "| "; cerr << format("%1%%2%\n") % spaces % f.str(); } diff --git a/substitute.mk b/substitute.mk new file mode 100644 index 000000000..af3549253 --- /dev/null +++ b/substitute.mk @@ -0,0 +1,8 @@ +%: %.in Makefile + sed \ + -e s^@prefix\@^$(prefix)^g \ + -e s^@bindir\@^$(bindir)^g \ + -e s^@sysconfdir\@^$(sysconfdir)^g \ + -e s^@localstatedir\@^$(localstatedir)^g \ + < $< > $@ || rm $@ + chmod +x $@ From 667a6afb9dabcb3e5c851b910705b7eb1c87c9b6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 20 Jul 2003 19:30:53 +0000 Subject: [PATCH 0149/6440] * Remove accidentally added file. --- src/normalise.o | Bin 888596 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/normalise.o diff --git a/src/normalise.o b/src/normalise.o deleted file mode 100644 index c23482c54d1cca28fb9b92faa46c432091840f27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 888596 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�XjP# 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 From 7984cfc7c18c85c5db42c5c7d57927b12c846ce0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 20 Jul 2003 21:11:43 +0000 Subject: [PATCH 0150/6440] * Argh, another short-write problem. Added wrappers around read()/write() to fix this once and for all. --- scripts/nix-switch.in | 3 +++ src/archive.cc | 2 +- src/archive.hh | 2 +- src/nix.cc | 17 +++-------------- src/references.cc | 7 +++---- src/store.cc | 11 +++-------- src/test.cc | 11 +++-------- src/util.cc | 23 +++++++++++++++++++++++ src/util.hh | 6 ++++++ 9 files changed, 46 insertions(+), 36 deletions(-) diff --git a/scripts/nix-switch.in b/scripts/nix-switch.in index 55305418c..2ccb6b4e5 100755 --- a/scripts/nix-switch.in +++ b/scripts/nix-switch.in @@ -15,6 +15,9 @@ $hash || die "no package hash specified"; my $linkdir = "@localstatedir@/nix/links"; # Build the specified package, and all its dependencies. +system "nix -ih $hash"; +if ($?) { die "`nix -ih' failed"; } + my $pkgdir = `nix -qph $hash`; if ($?) { die "`nix -qph' failed"; } chomp $pkgdir; diff --git a/src/archive.cc b/src/archive.cc index 73fc75fa2..9170ca3ad 100644 --- a/src/archive.cc +++ b/src/archive.cc @@ -190,7 +190,7 @@ static string readString(RestoreSource & source) { unsigned int len = readInt(source); char buf[len]; - source((const unsigned char *) buf, len); + source((unsigned char *) buf, len); readPadding(len, source); return string(buf, len); } diff --git a/src/archive.hh b/src/archive.hh index 7d9b1e2b5..e6006e454 100644 --- a/src/archive.hh +++ b/src/archive.hh @@ -54,7 +54,7 @@ struct RestoreSource pointed to by data. It should block if that much data is not yet available, or throw an error if it is not going to be available. */ - virtual void operator () (const unsigned char * data, unsigned int len) = 0; + virtual void operator () (unsigned char * data, unsigned int len) = 0; }; void restorePath(const string & path, RestoreSource & source); diff --git a/src/nix.cc b/src/nix.cc index f5ca0b4d8..ad4e6a468 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -215,12 +215,7 @@ struct StdoutSink : DumpSink virtual void operator () (const unsigned char * data, unsigned int len) { - while (len) { - ssize_t res = write(STDOUT_FILENO, (char *) data, len); - if (res == -1) throw SysError("writing to stdout"); - len -= res; - data += res; - } + writeFull(STDOUT_FILENO, data, len); } }; @@ -247,15 +242,9 @@ static void opDump(Strings opFlags, Strings opArgs) /* A source that read restore intput to stdin. */ struct StdinSource : RestoreSource { - virtual void operator () (const unsigned char * data, unsigned int len) + virtual void operator () (unsigned char * data, unsigned int len) { - while (len) { - ssize_t res = read(STDIN_FILENO, (char *) data, len); - if (res == -1) throw SysError("reading from stdin"); - if (res == 0) throw Error("unexpected end-of-file on stdin"); - len -= res; - data += res; - } + readFull(STDIN_FILENO, data, len); } }; diff --git a/src/references.cc b/src/references.cc index a42c1aed0..8934d5306 100644 --- a/src/references.cc +++ b/src/references.cc @@ -55,12 +55,11 @@ void checkPath(const string & path, int fd = open(path.c_str(), O_RDONLY); if (fd == -1) throw SysError(format("opening file `%1%'") % path); - char * buf = new char[st.st_size]; + unsigned char * buf = new unsigned char[st.st_size]; - if (read(fd, buf, st.st_size) != st.st_size) - throw SysError(format("reading file %1%") % path); + readFull(fd, buf, st.st_size); - search(string(buf, st.st_size), ids, seen); + search(string((char *) buf, st.st_size), ids, seen); delete buf; /* !!! autodelete */ diff --git a/src/store.cc b/src/store.cc index 0ce5f2604..6f1a2fc39 100644 --- a/src/store.cc +++ b/src/store.cc @@ -15,8 +15,7 @@ struct CopySink : DumpSink int fd; virtual void operator () (const unsigned char * data, unsigned int len) { - if (write(fd, (char *) data, len) != (ssize_t) len) - throw SysError("writing to child"); + writeFull(fd, data, len); } }; @@ -24,13 +23,9 @@ struct CopySink : DumpSink struct CopySource : RestoreSource { int fd; - virtual void operator () (const unsigned char * data, unsigned int len) + virtual void operator () (unsigned char * data, unsigned int len) { - ssize_t res = read(fd, (char *) data, len); - if (res == -1) - throw SysError("reading from parent"); - if (res != (ssize_t) len) - throw Error("not enough data available on parent"); + readFull(fd, data, len); } }; diff --git a/src/test.cc b/src/test.cc index fa7c93820..5c7559af6 100644 --- a/src/test.cc +++ b/src/test.cc @@ -37,21 +37,16 @@ struct MySink : DumpSink virtual void operator () (const unsigned char * data, unsigned int len) { /* Don't use cout, it's slow as hell! */ - if (write(STDOUT_FILENO, (char *) data, len) != (ssize_t) len) - throw SysError("writing to stdout"); + writeFull(STDOUT_FILENO, data, len); } }; struct MySource : RestoreSource { - virtual void operator () (const unsigned char * data, unsigned int len) + virtual void operator () (unsigned char * data, unsigned int len) { - ssize_t res = read(STDIN_FILENO, (char *) data, len); - if (res == -1) - throw SysError("reading from stdin"); - if (res != (ssize_t) len) - throw Error("not enough data available on stdin"); + readFull(STDIN_FILENO, data, len); } }; diff --git a/src/util.cc b/src/util.cc index d7c1fe60e..a16643022 100644 --- a/src/util.cc +++ b/src/util.cc @@ -159,3 +159,26 @@ void debug(const format & f) { msg(format("debug: %1%") % f.str()); } + + +void readFull(int fd, unsigned char * buf, size_t count) +{ + while (count) { + ssize_t res = read(fd, (char *) buf, count); + if (res == -1) throw SysError("reading from file"); + if (res == 0) throw Error("unexpected end-of-file"); + count -= res; + buf += res; + } +} + + +void writeFull(int fd, const unsigned char * buf, size_t count) +{ + while (count) { + ssize_t res = write(fd, (char *) buf, count); + if (res == -1) throw SysError("writing to file"); + count -= res; + buf += res; + } +} diff --git a/src/util.hh b/src/util.hh index 611612a58..8b23bee00 100644 --- a/src/util.hh +++ b/src/util.hh @@ -85,4 +85,10 @@ void msg(const format & f); void debug(const format & f); +/* Wrappers arount read()/write() that read/write exactly the + requested number of bytes. */ +void readFull(int fd, unsigned char * buf, size_t count); +void writeFull(int fd, const unsigned char * buf, size_t count); + + #endif /* !__UTIL_H */ From 401452e57ae897d3e5829ed12bfcccac82548e91 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Jul 2003 08:55:49 +0000 Subject: [PATCH 0151/6440] * Memoize the evaluation of Fix expressions to speed up computation. --- src/fix.cc | 62 ++++++++++++++++++++++++++++++++++++----------------- src/test.cc | 10 ++++----- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index cf6d5617a..6e3809f82 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -8,15 +8,24 @@ typedef ATerm Expr; +typedef map NormalForms; -static Strings searchDirs; +struct EvalState +{ + Strings searchDirs; + NormalForms normalForms; +}; -static string searchPath(string relPath) +static Expr evalFile(EvalState & state, string fileName); +static Expr evalExpr(EvalState & state, Expr e); + + +static string searchPath(const Strings & searchDirs, string relPath) { if (string(relPath, 0, 1) == "/") return relPath; - for (Strings::iterator i = searchDirs.begin(); + for (Strings::const_iterator i = searchDirs.begin(); i != searchDirs.end(); i++) { string path = *i + "/" + relPath; @@ -29,9 +38,6 @@ static string searchPath(string relPath) } -static Expr evalFile(string fileName); - - static Expr substExpr(string x, Expr rep, Expr e) { char * s; @@ -98,7 +104,7 @@ static Expr substExprMany(ATermList formals, ATermList args, Expr body) } -static Expr evalExpr(Expr e) +static Expr evalExpr2(EvalState & state, Expr e) { char * s1; Expr e1, e2, e3, e4; @@ -119,21 +125,22 @@ static Expr evalExpr(Expr e) /* Application. */ if (ATmatch(e, "App(, [])", &e1, &e2)) { - e1 = evalExpr(e1); + e1 = evalExpr(state, e1); if (!ATmatch(e1, "Function([], )", &e3, &e4)) throw badTerm("expecting a function", e1); - return evalExpr(substExprMany((ATermList) e3, (ATermList) e2, e4)); + return evalExpr(state, + substExprMany((ATermList) e3, (ATermList) e2, e4)); } /* Fix inclusion. */ if (ATmatch(e, "IncludeFix()", &s1)) { string fileName(s1); - return evalFile(s1); + return evalFile(state, s1); } /* Relative files. */ if (ATmatch(e, "Relative()", &s1)) { - string srcPath = searchPath(s1); + string srcPath = searchPath(state.searchDirs, s1); string dstPath; FSId id; addToStore(srcPath, dstPath, id, true); @@ -160,7 +167,7 @@ static Expr evalExpr(Expr e) ATerm bnd = ATgetFirst(bnds); if (!ATmatch(bnd, "(, )", &s1, &e1)) throw badTerm("binding expected", bnd); - bndMap[s1] = evalExpr(e1); + bndMap[s1] = evalExpr(state, e1); bnds = ATgetNext(bnds); } @@ -218,7 +225,7 @@ static Expr evalExpr(Expr e) /* BaseName primitive function. */ if (ATmatch(e, "BaseName()", &e1)) { - e1 = evalExpr(e1); + e1 = evalExpr(state, e1); if (!ATmatch(e1, "", &s1)) throw badTerm("string expected", e1); return ATmake("", baseNameOf(s1).c_str()); @@ -229,22 +236,37 @@ static Expr evalExpr(Expr e) } -static Expr evalFile(string relPath) +static Expr evalExpr(EvalState & state, Expr e) { - string path = searchPath(relPath); + /* Consult the memo table to quickly get the normal form of + previously evaluated expressions. */ + NormalForms::iterator i = state.normalForms.find(e); + if (i != state.normalForms.end()) return i->second; + + /* Otherwise, evaluate and memoize. */ + Expr nf = evalExpr2(state, e); + state.normalForms[e] = nf; + return nf; +} + + +static Expr evalFile(EvalState & state, string relPath) +{ + string path = searchPath(state.searchDirs, relPath); Expr e = ATreadFromNamedFile(path.c_str()); if (!e) throw Error(format("unable to read a term from `%1%'") % path); - return evalExpr(e); + return evalExpr(state, e); } void run(Strings args) { + EvalState state; Strings files; - searchDirs.push_back("."); - searchDirs.push_back(nixDataDir + "/fix"); + state.searchDirs.push_back("."); + state.searchDirs.push_back(nixDataDir + "/fix"); for (Strings::iterator it = args.begin(); it != args.end(); ) @@ -254,7 +276,7 @@ void run(Strings args) if (arg == "--includedir" || arg == "-I") { if (it == args.end()) throw UsageError(format("argument required in `%1%'") % arg); - searchDirs.push_back(*it++); + state.searchDirs.push_back(*it++); } else if (arg[0] == '-') throw UsageError(format("unknown flag `%1%`") % arg); @@ -267,7 +289,7 @@ void run(Strings args) for (Strings::iterator it = files.begin(); it != files.end(); it++) { - Expr e = evalFile(*it); + Expr e = evalFile(state, *it); char * s; if (ATmatch(e, "FSId()", &s)) { cout << format("%1%\n") % s; diff --git a/src/test.cc b/src/test.cc index 5c7559af6..6b567abe0 100644 --- a/src/test.cc +++ b/src/test.cc @@ -117,7 +117,7 @@ void runTests() ((string) builder1id).c_str(), builder1fn.c_str(), ((string) builder1id).c_str()); - FSId fs1id = writeTerm(fs1, "", 0); + FSId fs1id = writeTerm(fs1, ""); realise(fs1id); realise(fs1id); @@ -127,7 +127,7 @@ void runTests() ((string) builder1id).c_str(), (builder1fn + "_bla").c_str(), ((string) builder1id).c_str()); - FSId fs2id = writeTerm(fs2, "", 0); + FSId fs2id = writeTerm(fs2, ""); realise(fs2id); realise(fs2id); @@ -143,7 +143,7 @@ void runTests() thisSystem.c_str(), out1fn.c_str()); debug(printTerm(fs3)); - FSId fs3id = writeTerm(fs3, "", 0); + FSId fs3id = writeTerm(fs3, ""); realise(fs3id); realise(fs3id); @@ -158,7 +158,7 @@ void runTests() ((string) builder4id).c_str(), builder4fn.c_str(), ((string) builder4id).c_str()); - FSId fs4id = writeTerm(fs4, "", 0); + FSId fs4id = writeTerm(fs4, ""); realise(fs4id); @@ -174,7 +174,7 @@ void runTests() out5fn.c_str(), ((string) builder4fn).c_str()); debug(printTerm(fs5)); - FSId fs5id = writeTerm(fs5, "", 0); + FSId fs5id = writeTerm(fs5, ""); realise(fs5id); realise(fs5id); From 49231fbe419d37717b0d951377fbfc9bf445dd55 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Jul 2003 14:46:01 +0000 Subject: [PATCH 0152/6440] * Changes to the command line syntax of Nix. * A function to find all Nix expressions whose output ids are completely contained in some set. Useful for uploading relevant Nix expressions to a shared cache. --- src/nix.cc | 124 +++++++++++++++++++++-------------------------- src/normalise.cc | 49 +++++++++++++++++-- src/normalise.hh | 6 ++- src/store.cc | 9 ++++ src/store.hh | 3 ++ 5 files changed, 115 insertions(+), 76 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index ad4e6a468..de60c28c5 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -9,9 +9,7 @@ typedef void (* Operation) (Strings opFlags, Strings opArgs); -typedef enum { atpHash, atpPath, atpUnknown } ArgType; - -static ArgType argType = atpUnknown; +static bool pathArgs = false; /* Nix syntax: @@ -39,12 +37,11 @@ static ArgType argType = atpUnknown; Source selection for --install, --dump: - --file / -f: by file name !!! -> path - --hash / -h: by hash (identifier) + --path / -p: by file name !!! -> path Query flags: - --path / -p: query the path of an fstate + --list / -l: query the output paths (roots) of an fstate --refs / -r: query paths referenced by an fstate Options: @@ -53,39 +50,16 @@ static ArgType argType = atpUnknown; */ -/* Parse the `-f' / `-h' / flags, i.e., the type of arguments. These - flags are deleted from the referenced vector. */ -static void getArgType(Strings & flags) -{ - for (Strings::iterator it = flags.begin(); - it != flags.end(); ) - { - string arg = *it; - ArgType tp; - if (arg == "--hash" || arg == "-h") tp = atpHash; - else if (arg == "--file" || arg == "-f") tp = atpPath; - else { it++; continue; } - if (argType != atpUnknown) - throw UsageError("only one argument type specified may be specified"); - argType = tp; - it = flags.erase(it); - } - if (argType == atpUnknown) - throw UsageError("argument type not specified"); -} - - static FSId argToId(const string & arg) { - if (argType == atpHash) + if (!pathArgs) return parseHash(arg); - else if (argType == atpPath) { - string path; + else { FSId id; - addToStore(arg, path, id); + if (!queryPathId(arg, id)) + throw Error(format("don't know id of `%1%'") % arg); return id; } - else abort(); } @@ -93,7 +67,6 @@ static FSId argToId(const string & arg) expressions. */ static void opInstall(Strings opFlags, Strings opArgs) { - getArgType(opFlags); if (!opFlags.empty()) throw UsageError("unknown flag"); for (Strings::iterator it = opArgs.begin(); @@ -117,7 +90,6 @@ static void opDelete(Strings opFlags, Strings opArgs) paths. */ static void opAdd(Strings opFlags, Strings opArgs) { - getArgType(opFlags); if (!opFlags.empty()) throw UsageError("unknown flag"); for (Strings::iterator it = opArgs.begin(); @@ -134,47 +106,61 @@ static void opAdd(Strings opFlags, Strings opArgs) /* Perform various sorts of queries. */ static void opQuery(Strings opFlags, Strings opArgs) { - enum { qPath, qRefs, qUnknown } query = qPath; + enum { qPaths, qRefs, qGenerators, qUnknown } query = qPaths; - for (Strings::iterator it = opFlags.begin(); - it != opFlags.end(); ) - { - string arg = *it; - if (arg == "--path" || arg == "-p") query = qPath; - else if (arg == "--refs" || arg == "-r") query = qRefs; - else { it++; continue; } - it = opFlags.erase(it); - } + for (Strings::iterator i = opFlags.begin(); + i != opFlags.end(); i++) + if (*i == "--list" || *i == "-l") query = qPaths; + else if (*i == "--refs" || *i == "-r") query = qRefs; + else if (*i == "--generators" || *i == "-g") query = qGenerators; + else throw UsageError(format("unknown flag `%1%'") % *i); - getArgType(opFlags); - if (!opFlags.empty()) throw UsageError("unknown flag"); - - for (Strings::iterator it = opArgs.begin(); - it != opArgs.end(); it++) - { - FSId id = argToId(*it); - - switch (query) { - - case qPath: { - Strings paths = fstatePaths(id, true); - for (Strings::iterator j = paths.begin(); - j != paths.end(); j++) - cout << format("%s\n") % *j; + switch (query) { + + case qPaths: { + StringSet paths; + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); i++) + { + Strings paths2 = fstatePaths(argToId(*i), true); + paths.insert(paths2.begin(), paths2.end()); + } + for (StringSet::iterator i = paths.begin(); + i != paths.end(); i++) + cout << format("%s\n") % *i; break; } case qRefs: { - StringSet refs = fstateRefs(id); - for (StringSet::iterator j = refs.begin(); - j != refs.end(); j++) - cout << format("%s\n") % *j; + StringSet paths; + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); i++) + { + Strings paths2 = fstateRefs(argToId(*i)); + paths.insert(paths2.begin(), paths2.end()); + } + for (StringSet::iterator i = paths.begin(); + i != paths.end(); i++) + cout << format("%s\n") % *i; + break; + } + + case qGenerators: { + FSIds outIds; + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); i++) + outIds.push_back(argToId(*i)); + + FSIds genIds = findGenerators(outIds); + + for (FSIds::iterator i = genIds.begin(); + i != genIds.end(); i++) + cout << format("%s\n") % (string) *i; break; } default: abort(); - } } } @@ -224,16 +210,12 @@ struct StdoutSink : DumpSink output. */ static void opDump(Strings opFlags, Strings opArgs) { - getArgType(opFlags); if (!opFlags.empty()) throw UsageError("unknown flag"); if (opArgs.size() != 1) throw UsageError("only one argument allowed"); StdoutSink sink; string arg = *opArgs.begin(); - string path; - - if (argType == atpHash) path = expandId(parseHash(arg)); - else if (argType == atpPath) path = arg; + string path = pathArgs ? arg : expandId(parseHash(arg)); dumpPath(path, sink); } @@ -313,6 +295,8 @@ void run(Strings args) op = opInit; else if (arg == "--verify") op = opVerify; + else if (arg == "--path" || arg == "-p") + pathArgs = true; else if (arg[0] == '-') opFlags.push_back(arg); else diff --git a/src/normalise.cc b/src/normalise.cc index bdeddf08d..fcb9c4d0d 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -248,18 +248,57 @@ Strings fstatePaths(const FSId & id, bool normalise) } -StringSet fstateRefs(const FSId & id) +Strings fstateRefs(const FSId & id) { - StringSet paths; + Strings paths; Slice slice = normaliseFState(id); for (SliceElems::const_iterator i = slice.elems.begin(); i != slice.elems.end(); i++) - paths.insert(i->path); + paths.push_back(i->path); return paths; } -void findGenerators(const FSIds & ids) +FSIds findGenerators(const FSIds & _ids) { - + FSIdSet ids(_ids.begin(), _ids.end()); + FSIds generators; + + /* !!! hack; for performance, we just look at the rhs of successor + mappings, since we know that those are Nix expressions. */ + + Strings sucs; + enumDB(nixDB, dbSuccessors, sucs); + + for (Strings::iterator i = sucs.begin(); + i != sucs.end(); i++) + { + string s; + queryDB(nixDB, dbSuccessors, *i, s); + FSId id = parseHash(s); + + FState fs; + try { + /* !!! should substitutes be used? */ + fs = parseFState(termFromId(id)); + } catch (...) { /* !!! only catch parse errors */ + continue; + } + + if (fs.type != FState::fsSlice) continue; + + bool okay = true; + for (SliceElems::const_iterator i = fs.slice.elems.begin(); + i != fs.slice.elems.end(); i++) + if (ids.find(i->id) == ids.end()) { + okay = false; + break; + } + + if (!okay) continue; + + generators.push_back(id); + } + + return generators; } diff --git a/src/normalise.hh b/src/normalise.hh index 85dbca5ef..49f9e68ee 100644 --- a/src/normalise.hh +++ b/src/normalise.hh @@ -16,7 +16,11 @@ void realiseSlice(const Slice & slice); Strings fstatePaths(const FSId & id, bool normalise); /* Get the list of paths referenced by the given fstate-expression. */ -StringSet fstateRefs(const FSId & id); +Strings fstateRefs(const FSId & id); + +/* Return the list of the ids of all known fstate-expressions whose + output ids are completely contained in `ids'. */ +FSIds findGenerators(const FSIds & ids); /* Register a successor. */ void registerSuccessor(const FSId & id1, const FSId & id2); diff --git a/src/store.cc b/src/store.cc index 6f1a2fc39..6157b4bc2 100644 --- a/src/store.cc +++ b/src/store.cc @@ -148,6 +148,15 @@ void unregisterPath(const string & _path) } +bool queryPathId(const string & path, FSId & id) +{ + string s; + if (!queryDB(nixDB, dbPath2Id, path, s)) return false; + id = parseHash(s); + return true; +} + + bool isInPrefix(const string & path, const string & _prefix) { string prefix = canonPath(_prefix + "/"); diff --git a/src/store.hh b/src/store.hh index 78d5529e7..faac76009 100644 --- a/src/store.hh +++ b/src/store.hh @@ -20,6 +20,9 @@ void registerSubstitute(const FSId & srcId, const FSId & subId); /* Register a path keyed on its id. */ void registerPath(const string & path, const FSId & id); +/* Query the id of a path. */ +bool queryPathId(const string & path, FSId & id); + /* Return a path whose contents have the given hash. If target is not empty, ensure that such a path is realised in target (if necessary by copying from another location). If prefix is not From 249988a787d26046bf7b389594ff25029229e3d9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Jul 2003 20:07:12 +0000 Subject: [PATCH 0153/6440] * Allow the output/expression id to be forced to a certain value; this potentially dangerous feature enables better sharing for those paths for which the content is known in advance (e.g., because a content hash is given). * Fast builds: if we can expand all output paths of a derive expression, we don't have to build. --- corepkgs/fetchurl/fetchurl.fix | 1 + corepkgs/fetchurl/fetchurl.sh | 8 +++----- src/fix.cc | 17 ++++++++++++----- src/fstate.cc | 5 +++-- src/fstate.hh | 2 +- src/normalise.cc | 7 +++---- 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/corepkgs/fetchurl/fetchurl.fix b/corepkgs/fetchurl/fetchurl.fix index f798c0bec..0221b612c 100644 --- a/corepkgs/fetchurl/fetchurl.fix +++ b/corepkgs/fetchurl/fetchurl.fix @@ -4,6 +4,7 @@ Function(["url", "md5"], , ("url", Var("url")) , ("md5", Var("md5")) , ("name", BaseName(Var("url"))) + , ("id", Var("md5")) ] ) ) diff --git a/corepkgs/fetchurl/fetchurl.sh b/corepkgs/fetchurl/fetchurl.sh index 1479e898b..7b6243974 100644 --- a/corepkgs/fetchurl/fetchurl.sh +++ b/corepkgs/fetchurl/fetchurl.sh @@ -4,9 +4,7 @@ echo "downloading $url into $out..." wget "$url" -O "$out" || exit 1 actual=$(md5sum -b $out | cut -c1-32) -if ! test "$md5" == "ignore"; then - if ! test "$actual" == "$md5"; then - echo "hash is $actual, expected $md5" - exit 1 - fi +if ! test "$actual" == "$md5"; then + echo "hash is $actual, expected $md5" + exit 1 fi diff --git a/src/fix.cc b/src/fix.cc index 6e3809f82..93cc27cfc 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -153,8 +153,10 @@ static Expr evalExpr2(EvalState & state, Expr e) fs.slice.roots.push_back(id); fs.slice.elems.push_back(elem); - return ATmake("FSId()", - ((string) writeTerm(unparseFState(fs), "")).c_str()); + FSId termId = hashString("producer-" + (string) id + + "-" + dstPath); + writeTerm(unparseFState(fs), "", termId); + return ATmake("FSId()", ((string) termId).c_str()); } /* Packages are transformed into Derive fstate expressions. */ @@ -176,6 +178,7 @@ static Expr evalExpr2(EvalState & state, Expr e) fs.type = FState::fsDerive; fs.derive.platform = SYSTEM; string name; + FSId outId; bnds = ATempty; for (map::iterator it = bndMap.begin(); @@ -195,6 +198,7 @@ static Expr evalExpr2(EvalState & state, Expr e) } else if (ATmatch(value, "", &s1)) { if (key == "name") name = s1; + if (key == "id") outId = parseHash(s1); fs.derive.env.push_back(StringPair(key, s1)); } else throw badTerm("invalid package argument", value); @@ -211,7 +215,8 @@ static Expr evalExpr2(EvalState & state, Expr e) /* Hash the fstate-expression with no outputs to produce a unique but deterministic path name for this package. */ - Hash outId = hashTerm(unparseFState(fs)); + if (outId == FSId()) + outId = hashTerm(unparseFState(fs)); string outPath = canonPath(nixStore + "/" + ((string) outId).c_str() + "-" + name); fs.derive.env.push_back(StringPair("out", outPath)); @@ -219,8 +224,10 @@ static Expr evalExpr2(EvalState & state, Expr e) debug(format("%1%: %2%") % (string) outId % name); /* Write the resulting term into the Nix store directory. */ - return ATmake("FSId()", - ((string) writeTerm(unparseFState(fs), "-d-" + name)).c_str()); + FSId termId = hashString("producer-" + (string) outId + + "-" + outPath); + writeTerm(unparseFState(fs), "-d-" + name, termId); + return ATmake("FSId()", ((string) termId).c_str()); } /* BaseName primitive function. */ diff --git a/src/fstate.cc b/src/fstate.cc index 85f8c75cc..1c8c6776f 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -31,9 +31,10 @@ ATerm termFromId(const FSId & id) } -FSId writeTerm(ATerm t, const string & suffix) +FSId writeTerm(ATerm t, const string & suffix, FSId id) { - FSId id = hashTerm(t); + /* By default, the id of a term is its hash. */ + if (id == FSId()) id = hashTerm(t); string path = canonPath(nixStore + "/" + (string) id + suffix + ".nix"); diff --git a/src/fstate.hh b/src/fstate.hh index 681a8d094..969abe9e0 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -63,7 +63,7 @@ Hash hashTerm(ATerm t); ATerm termFromId(const FSId & id); /* Write an aterm to the Nix store directory, and return its hash. */ -FSId writeTerm(ATerm t, const string & suffix); +FSId writeTerm(ATerm t, const string & suffix, FSId id = FSId()); /* Parse an fstate-expression. */ FState parseFState(ATerm t); diff --git a/src/normalise.cc b/src/normalise.cc index fcb9c4d0d..d06c8bf36 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -88,19 +88,18 @@ Slice normaliseFState(FSId id) /* We can skip running the builder if we can expand all output paths from their ids. */ - bool fastBuild = false; -#if 0 + bool fastBuild = true; for (OutPaths::iterator i = outPaths.begin(); i != outPaths.end(); i++) { try { expandId(i->second, i->first); - } catch (...) { + } catch (Error & e) { + debug(format("fast build failed: %1%") % e.what()); fastBuild = false; break; } } -#endif if (!fastBuild) { From 9f4ad99e92096981c5dc9401aa241a2314078b47 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Jul 2003 20:58:21 +0000 Subject: [PATCH 0154/6440] * Canonicalise path. --- src/store.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store.cc b/src/store.cc index 6157b4bc2..65c44ca37 100644 --- a/src/store.cc +++ b/src/store.cc @@ -151,7 +151,7 @@ void unregisterPath(const string & _path) bool queryPathId(const string & path, FSId & id) { string s; - if (!queryDB(nixDB, dbPath2Id, path, s)) return false; + if (!queryDB(nixDB, dbPath2Id, absPath(path), s)) return false; id = parseHash(s); return true; } From 2616e6a6f3f8c39e62071cf1c22cce5be90a1d9d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Jul 2003 20:58:34 +0000 Subject: [PATCH 0155/6440] * Check for errors. --- src/normalise.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/normalise.cc b/src/normalise.cc index d06c8bf36..8da940aa6 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -273,7 +273,7 @@ FSIds findGenerators(const FSIds & _ids) i != sucs.end(); i++) { string s; - queryDB(nixDB, dbSuccessors, *i, s); + if (!queryDB(nixDB, dbSuccessors, *i, s)) continue; FSId id = parseHash(s); FState fs; From d5ee6f8700c7225a4ce34f6d92aae0d57bee3355 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Jul 2003 21:31:03 +0000 Subject: [PATCH 0156/6440] * In `--query --generators', print out paths, not ids. (There should really be a switch for this). --- src/nix.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix.cc b/src/nix.cc index de60c28c5..f8e019eb4 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -155,7 +155,7 @@ static void opQuery(Strings opFlags, Strings opArgs) for (FSIds::iterator i = genIds.begin(); i != genIds.end(); i++) - cout << format("%s\n") % (string) *i; + cout << format("%s\n") % expandId(*i); break; } From c7bdb76fe461e2335caeea01c16b39a2784fa506 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Jul 2003 21:34:56 +0000 Subject: [PATCH 0157/6440] * Syntax fixes. * When pushing, put the hash in the file name so that the client can verify (proof-carrying file names?). --- corepkgs/nar/nar.fix | 4 ++-- corepkgs/nar/nar.sh.in | 7 ++++++- scripts/nix-push.in | 30 ++++++++++++++++++++---------- scripts/nix-switch.in | 8 ++++---- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/corepkgs/nar/nar.fix b/corepkgs/nar/nar.fix index 3db6a48a0..429e7b549 100644 --- a/corepkgs/nar/nar.fix +++ b/corepkgs/nar/nar.fix @@ -1,6 +1,6 @@ -Function(["path", "name"], +Function(["path"], Package( - [ ("name", Var("name")) + [ ("name", "nar") , ("build", Relative("nar/nar.sh")) , ("path", Var("path")) ] diff --git a/corepkgs/nar/nar.sh.in b/corepkgs/nar/nar.sh.in index bffbbaf5e..d21668553 100644 --- a/corepkgs/nar/nar.sh.in +++ b/corepkgs/nar/nar.sh.in @@ -1,5 +1,10 @@ #! /bin/sh echo "packing $path into $out..." -@bindir@/nix --dump --file "$path" | bzip2 > $out || exit 1 +mkdir $out || exit 1 +tmp=$out/tmp +@bindir@/nix --dump --path "$path" | bzip2 > $out/tmp || exit 1 +md5=$(md5sum -b $tmp | cut -c1-32) +if test $? != 0; then exit 1; fi +mv $out/tmp $out/$md5-`basename $path`.nar.bz2 || exit 1 diff --git a/scripts/nix-push.in b/scripts/nix-push.in index bb25019e8..4a6426fe0 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -8,17 +8,28 @@ foreach my $id (@ARGV) { # Get all paths referenced by the normalisation of the given # fstate expression. - system "nix -ih $id"; - if ($?) { die "`nix -ih' failed"; } + system "nix --install $id"; + if ($?) { die "`nix --install' failed"; } + my @paths; - open PATHS, "nix -qrh $id 2> /dev/null |" or die "nix -qrh"; + + open PATHS, "nix --query --refs $id 2> /dev/null |" or die "nix -qr"; while () { chomp; - next unless /^\//; + die "bad: $_" unless /^\//; push @paths, $_; } close PATHS; + # Also add all normal forms that are contained in these paths. + open PATHS, "nix --query --generators --path @paths |" or die "nix -qg"; + while () { + chomp; + die "bad: $_" unless /^\//; + push @paths, $_; + } + close PATHS; + # For each path, create a Fix expression that turns the path into # a Nix archive. foreach my $path (@paths) { @@ -38,7 +49,6 @@ foreach my $id (@ARGV) { my $fixexpr = "App(IncludeFix(\"nar/nar.fix\"), " . "[ (\"path\", Slice([\"$pathid\"], [(\"$path\", \"$pathid\", [])]))" . - ", (\"name\", \"$name\")" . "])"; my $fixfile = "/tmp/nix-push-tmp.fix"; @@ -53,13 +63,13 @@ foreach my $id (@ARGV) { die unless $nid =~ /^([0-9a-z]{32})$/; # Realise the Nix expression. - system "nix -ih $nid"; - if ($?) { die "`nix -ih' failed"; } - my $npath = `nix -qph $nid 2> /dev/null`; - $? and die "creating Nix archive"; + system "nix --install $nid"; + if ($?) { die "`nix --install' failed"; } + my $npath = `nix --query --list $nid 2> /dev/null`; + $? and die "`nix --query --list' failed"; chomp $npath; - push @pushlist, $npath; + push @pushlist, "$npath/*"; print "$path -> $npath\n"; } diff --git a/scripts/nix-switch.in b/scripts/nix-switch.in index 2ccb6b4e5..ddaca4e22 100755 --- a/scripts/nix-switch.in +++ b/scripts/nix-switch.in @@ -15,11 +15,11 @@ $hash || die "no package hash specified"; my $linkdir = "@localstatedir@/nix/links"; # Build the specified package, and all its dependencies. -system "nix -ih $hash"; -if ($?) { die "`nix -ih' failed"; } +system "nix --install $hash"; +if ($?) { die "`nix --install' failed"; } -my $pkgdir = `nix -qph $hash`; -if ($?) { die "`nix -qph' failed"; } +my $pkgdir = `nix --query --list $hash`; +if ($?) { die "`nix --query --list' failed"; } chomp $pkgdir; # Figure out a generation number. From d84931ee5607c152b4bc7eb23b7ab94ded9f85c7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Jul 2003 22:05:19 +0000 Subject: [PATCH 0158/6440] * Changed nix-pull to match nix-push. --- scripts/nix-collect-garbage.in | 2 +- scripts/nix-pull.in | 29 +++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/scripts/nix-collect-garbage.in b/scripts/nix-collect-garbage.in index 1506416b2..8f54ba20f 100755 --- a/scripts/nix-collect-garbage.in +++ b/scripts/nix-collect-garbage.in @@ -5,7 +5,7 @@ my $storedir = "@prefix@/store"; my %alive; -open HASHES, "nix -qrh \$(cat $linkdir/*.hash) |" or die "in `nix -qrh'"; +open HASHES, "nix --query --refs \$(cat $linkdir/*.hash) |" or die "in `nix -qrh'"; while () { chomp; $alive{$_} = 1; diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index 47762e857..9a1c1b6b5 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -28,21 +28,22 @@ while () { my $fn = $1; next if $fn =~ /\.\./; next if $fn =~ /\//; - next unless $fn =~ /-([0-9a-z]{32})(-s-([0-9a-z]{32}))?\.nar.bz2$/; + next unless $fn =~ /^([0-9a-z]{32})-([0-9a-z]{32})(-s-([0-9a-z]{32}))?.*\.nar\.bz2$/; my $hash = $1; - my $fshash = $3; + my $id = $2; + my $fsid = $4; - print "registering $hash -> $url/$fn\n"; + print "registering $id -> $url/$fn\n"; # Construct a Fix expression that fetches and unpacks a # Nix archive from the network. my $fetch = "App(IncludeFix(\"fetchurl/fetchurl.fix\"), " . - "[(\"url\", \"$url/$fn\"), (\"md5\", \"ignore\")])"; + "[(\"url\", \"$url/$fn\"), (\"md5\", \"$hash\")])"; my $fixexpr = "App(IncludeFix(\"nar/unnar.fix\"), " . "[ (\"nar\", $fetch)" . - ", (\"name\", \"fetched-$hash\")" . + ", (\"name\", \"fetched-$id\")" . "])"; my $fixfile = "/tmp/nix-pull-tmp.fix"; @@ -51,19 +52,19 @@ while () { close FIX; # Instantiate a Nix expression from the Fix expression. - my $nhash = `fix $fixfile`; + my $nid = `fix $fixfile`; $? and die "instantiating Nix archive expression"; - chomp $nhash; - die unless $nhash =~ /^([0-9a-z]{32})$/; + chomp $nid; + die unless $nid =~ /^([0-9a-z]{32})$/; - push @subs, $hash; - push @subs, $nhash; + push @subs, $id; + push @subs, $nid; # Does the name encode a successor relation? - if (defined $fshash) { - print "NORMAL $fshash -> $hash\n"; - push @sucs, $fshash; - push @sucs, $hash; + if (defined $fsid) { + print "NORMAL $fsid -> $id\n"; + push @sucs, $fsid; + push @sucs, $id; } } From df648c4967af7298fe55f75c7616e39e5b5e7d37 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 22 Jul 2003 10:24:22 +0000 Subject: [PATCH 0159/6440] * `nix --query --expansion' (`-qe') to get any path with content corresponding to the given id. --- src/nix.cc | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index f8e019eb4..e08854227 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -106,18 +106,19 @@ static void opAdd(Strings opFlags, Strings opArgs) /* Perform various sorts of queries. */ static void opQuery(Strings opFlags, Strings opArgs) { - enum { qPaths, qRefs, qGenerators, qUnknown } query = qPaths; + enum { qList, qRefs, qGenerators, qExpansion } query = qList; for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); i++) - if (*i == "--list" || *i == "-l") query = qPaths; + if (*i == "--list" || *i == "-l") query = qList; else if (*i == "--refs" || *i == "-r") query = qRefs; else if (*i == "--generators" || *i == "-g") query = qGenerators; + else if (*i == "--expansion" || *i == "-e") query = qExpansion; else throw UsageError(format("unknown flag `%1%'") % *i); switch (query) { - case qPaths: { + case qList: { StringSet paths; for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) @@ -159,6 +160,15 @@ static void opQuery(Strings opFlags, Strings opArgs) break; } + case qExpansion: { + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); i++) + /* !!! should not use substitutes; this is a query, + it should not have side-effects */ + cout << format("%s\n") % expandId(parseHash(*i)); + break; + } + default: abort(); } From e877c69d78fe75ae3531b3ed3cb4a4d7b390ccec Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 22 Jul 2003 15:15:15 +0000 Subject: [PATCH 0160/6440] * Substitutes now should produce a path with the same id as they are substituting for (obvious, really). * For greater efficiency, nix-pull/unnar will place the output in a path that is probably the same as what is actually needed, thus preventing a path copy. * Even if a output id is given in a Fix package expression, ensure that the resulting Nix derive expression has a different id. This is because Nix expressions that are semantically equivalent (i.e., build the same result) might be different w.r.t. efficiency or divergence. It is absolutely vital for the substitute mechanism that such expressions are not used interchangeably. --- corepkgs/nar/unnar.fix | 1 + scripts/nix-pull.in | 17 ++++++++++---- src/fix.cc | 53 +++++++++++++++++++++++++++++++----------- src/fstate.cc | 3 --- src/normalise.cc | 12 +++++----- src/normalise.hh | 6 ++--- src/store.cc | 33 ++++++++++++-------------- src/store.hh | 11 +++++++-- 8 files changed, 86 insertions(+), 50 deletions(-) diff --git a/corepkgs/nar/unnar.fix b/corepkgs/nar/unnar.fix index db97750aa..315be5873 100644 --- a/corepkgs/nar/unnar.fix +++ b/corepkgs/nar/unnar.fix @@ -3,6 +3,7 @@ Function(["nar", "name"], [ ("name", Var("name")) , ("build", Relative("nar/unnar.sh")) , ("nar", Var("nar")) + , ("id", Var("id")) ] ) ) \ No newline at end of file diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index 9a1c1b6b5..f584b6abd 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -28,10 +28,18 @@ while () { my $fn = $1; next if $fn =~ /\.\./; next if $fn =~ /\//; - next unless $fn =~ /^([0-9a-z]{32})-([0-9a-z]{32})(-s-([0-9a-z]{32}))?.*\.nar\.bz2$/; + next unless $fn =~ /^([0-9a-z]{32})-([0-9a-z]{32})(.*)\.nar\.bz2$/; my $hash = $1; - my $id = $2; - my $fsid = $4; + my $id = $2; + my $outname = $3; + my $fsid; + if ($outname =~ /^-/) { + next unless $outname =~ /^-((s-([0-9a-z]{32}))?.*)$/; + $outname = $1; + $fsid = $3; + } else { + $outname = ""; + } print "registering $id -> $url/$fn\n"; @@ -43,7 +51,8 @@ while () { my $fixexpr = "App(IncludeFix(\"nar/unnar.fix\"), " . "[ (\"nar\", $fetch)" . - ", (\"name\", \"fetched-$id\")" . + ", (\"name\", \"$outname\")" . + ", (\"id\", \"$id\")" . "])"; my $fixfile = "/tmp/nix-pull-tmp.fix"; diff --git a/src/fix.cc b/src/fix.cc index 93cc27cfc..afa0167ec 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -9,11 +9,13 @@ typedef ATerm Expr; typedef map NormalForms; +typedef map PkgHashes; struct EvalState { Strings searchDirs; NormalForms normalForms; + PkgHashes pkgHashes; /* normalised package hashes */ }; @@ -104,6 +106,23 @@ static Expr substExprMany(ATermList formals, ATermList args, Expr body) } +Hash hashPackage(EvalState & state, FState fs) +{ + if (fs.type == FState::fsDerive) { + for (FSIds::iterator i = fs.derive.inputs.begin(); + i != fs.derive.inputs.end(); i++) + { + PkgHashes::iterator j = state.pkgHashes.find(*i); + if (j == state.pkgHashes.end()) + throw Error(format("unknown package id %1%") % (string) *i); + *i = j->second; + } + } + debug(printTerm(unparseFState(fs))); + return hashTerm(unparseFState(fs)); +} + + static Expr evalExpr2(EvalState & state, Expr e) { char * s1; @@ -117,9 +136,10 @@ static Expr evalExpr2(EvalState & state, Expr e) return e; try { - parseFState(e); - return ATmake("FSId()", - ((string) writeTerm(e, "")).c_str()); + Hash pkgHash = hashPackage(state, parseFState(e)); + FSId pkgId = writeTerm(e, ""); + state.pkgHashes[pkgId] = pkgHash; + return ATmake("FSId()", ((string) pkgId).c_str()); } catch (...) { /* !!! catch parse errors only */ } @@ -153,10 +173,10 @@ static Expr evalExpr2(EvalState & state, Expr e) fs.slice.roots.push_back(id); fs.slice.elems.push_back(elem); - FSId termId = hashString("producer-" + (string) id - + "-" + dstPath); - writeTerm(unparseFState(fs), "", termId); - return ATmake("FSId()", ((string) termId).c_str()); + Hash pkgHash = hashPackage(state, fs); + FSId pkgId = writeTerm(unparseFState(fs), ""); + state.pkgHashes[pkgId] = pkgHash; + return ATmake("FSId()", ((string) pkgId).c_str()); } /* Packages are transformed into Derive fstate expressions. */ @@ -179,6 +199,7 @@ static Expr evalExpr2(EvalState & state, Expr e) fs.derive.platform = SYSTEM; string name; FSId outId; + bool outIdGiven = false; bnds = ATempty; for (map::iterator it = bndMap.begin(); @@ -198,7 +219,10 @@ static Expr evalExpr2(EvalState & state, Expr e) } else if (ATmatch(value, "", &s1)) { if (key == "name") name = s1; - if (key == "id") outId = parseHash(s1); + if (key == "id") { + outId = parseHash(s1); + outIdGiven = true; + } fs.derive.env.push_back(StringPair(key, s1)); } else throw badTerm("invalid package argument", value); @@ -215,8 +239,7 @@ static Expr evalExpr2(EvalState & state, Expr e) /* Hash the fstate-expression with no outputs to produce a unique but deterministic path name for this package. */ - if (outId == FSId()) - outId = hashTerm(unparseFState(fs)); + if (!outIdGiven) outId = hashPackage(state, fs); string outPath = canonPath(nixStore + "/" + ((string) outId).c_str() + "-" + name); fs.derive.env.push_back(StringPair("out", outPath)); @@ -224,10 +247,12 @@ static Expr evalExpr2(EvalState & state, Expr e) debug(format("%1%: %2%") % (string) outId % name); /* Write the resulting term into the Nix store directory. */ - FSId termId = hashString("producer-" + (string) outId - + "-" + outPath); - writeTerm(unparseFState(fs), "-d-" + name, termId); - return ATmake("FSId()", ((string) termId).c_str()); + Hash pkgHash = outIdGiven + ? hashString((string) outId + outPath) + : hashPackage(state, fs); + FSId pkgId = writeTerm(unparseFState(fs), "-d-" + name); + state.pkgHashes[pkgId] = pkgHash; + return ATmake("FSId()", ((string) pkgId).c_str()); } /* BaseName primitive function. */ diff --git a/src/fstate.cc b/src/fstate.cc index 1c8c6776f..5da3d8358 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -62,9 +62,6 @@ static void parseIds(ATermList ids, FSIds & out) } -typedef set FSIdSet; - - static void checkSlice(const Slice & slice) { if (slice.elems.size() == 0) diff --git a/src/normalise.cc b/src/normalise.cc index 8da940aa6..f463457e4 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -24,7 +24,7 @@ static FSId storeSuccessor(const FSId & id1, ATerm sc) typedef set FSIdSet; -Slice normaliseFState(FSId id) +Slice normaliseFState(FSId id, FSIdSet pending) { debug(format("normalising fstate %1%") % (string) id); Nest nest(true); @@ -57,8 +57,8 @@ Slice normaliseFState(FSId id) for (FSIds::iterator i = fs.derive.inputs.begin(); i != fs.derive.inputs.end(); i++) { - Slice slice = normaliseFState(*i); - realiseSlice(slice); + Slice slice = normaliseFState(*i, pending); + realiseSlice(slice, pending); for (SliceElems::iterator j = slice.elems.begin(); j != slice.elems.end(); j++) @@ -93,7 +93,7 @@ Slice normaliseFState(FSId id) i != outPaths.end(); i++) { try { - expandId(i->second, i->first); + expandId(i->second, i->first, "/", pending); } catch (Error & e) { debug(format("fast build failed: %1%") % e.what()); fastBuild = false; @@ -175,7 +175,7 @@ Slice normaliseFState(FSId id) } -void realiseSlice(const Slice & slice) +void realiseSlice(const Slice & slice, FSIdSet pending) { debug(format("realising slice")); Nest nest(true); @@ -209,7 +209,7 @@ void realiseSlice(const Slice & slice) { SliceElem elem = *i; debug(format("expanding %1% in %2%") % (string) elem.id % elem.path); - expandId(elem.id, elem.path); + expandId(elem.id, elem.path, "/", pending); } } diff --git a/src/normalise.hh b/src/normalise.hh index 49f9e68ee..72ee1d089 100644 --- a/src/normalise.hh +++ b/src/normalise.hh @@ -5,11 +5,11 @@ /* Normalise an fstate-expression, that is, return an equivalent - Slice. */ -Slice normaliseFState(FSId id); + Slice. (For the meaning of `pending', see expandId()). */ +Slice normaliseFState(FSId id, FSIdSet pending = FSIdSet()); /* Realise a Slice in the file system. */ -void realiseSlice(const Slice & slice); +void realiseSlice(const Slice & slice, FSIdSet pending = FSIdSet()); /* Get the list of root (output) paths of the given fstate-expression. */ diff --git a/src/store.cc b/src/store.cc index 65c44ca37..013bd2e2a 100644 --- a/src/store.cc +++ b/src/store.cc @@ -165,8 +165,11 @@ bool isInPrefix(const string & path, const string & _prefix) string expandId(const FSId & id, const string & target, - const string & prefix) + const string & prefix, FSIdSet pending) { + debug(format("expanding %1%") % (string) id); + Nest nest(true); + Strings paths; if (!target.empty() && !isInPrefix(target, prefix)) @@ -203,30 +206,24 @@ string expandId(const FSId & id, const string & target, } } - /* Try to realise the substitutes. */ + if (pending.find(id) != pending.end()) + throw Error(format("id %1% already being expanded") % (string) id); + pending.insert(id); + /* Try to realise the substitutes, but only if this id is not + already being realised by a substitute. */ Strings subs; queryListDB(nixDB, dbSubstitutes, id, subs); /* non-existence = ok */ for (Strings::iterator it = subs.begin(); it != subs.end(); it++) { FSId subId = parseHash(*it); - Slice slice = normaliseFState(subId); - realiseSlice(slice); - - Strings paths = fstatePaths(subId, true); - if (paths.size() != 1) - throw Error("substitute created more than 1 path"); - string path = *(paths.begin()); - if (target.empty()) - return path; /* !!! prefix */ - else { - if (path != target) { - copyPath(path, target); - registerPath(target, id); - } - return target; - } + debug(format("trying substitute %1%") % (string) subId); + + Slice slice = normaliseFState(subId, pending); + realiseSlice(slice, pending); + + return expandId(id, target, prefix, pending); } throw Error(format("cannot expand id `%1%'") % (string) id); diff --git a/src/store.hh b/src/store.hh index faac76009..b2cdc41f1 100644 --- a/src/store.hh +++ b/src/store.hh @@ -10,6 +10,8 @@ using namespace std; typedef Hash FSId; +typedef set FSIdSet; + /* Copy a path recursively. */ void copyPath(string src, string dst); @@ -26,9 +28,14 @@ bool queryPathId(const string & path, FSId & id); /* Return a path whose contents have the given hash. If target is not empty, ensure that such a path is realised in target (if necessary by copying from another location). If prefix is not - empty, only return a path that is an descendent of prefix. */ + empty, only return a path that is an descendent of prefix. + + The list of pending ids are those that already being expanded. + This prevents infinite recursion for ids realised through a + substitute (since when we build the substitute, we would first try + to expand the id... kaboom!). */ string expandId(const FSId & id, const string & target = "", - const string & prefix = "/"); + const string & prefix = "/", FSIdSet pending = FSIdSet()); /* Copy a file to the nixStore directory and register it in dbRefs. Return the hash code of the value. */ From 9202570f8c40f58f6444c2ec512104b305058977 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 22 Jul 2003 20:02:33 +0000 Subject: [PATCH 0161/6440] * libdb_cxx-4 -> libdb_cxx --- src/Makefile.am | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 92aee2f55..eb5177d13 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,18 +4,18 @@ check_PROGRAMS = test AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. nix_SOURCES = nix.cc -nix_LDADD = libshared.a libnix.a -ldb_cxx-4 -lATerm +nix_LDADD = libshared.a libnix.a -ldb_cxx -lATerm nix_hash_SOURCES = nix-hash.cc -nix_hash_LDADD = libshared.a libnix.a -ldb_cxx-4 -lATerm +nix_hash_LDADD = libshared.a libnix.a -ldb_cxx -lATerm fix_SOURCES = fix.cc -fix_LDADD = libshared.a libnix.a -ldb_cxx-4 -lATerm +fix_LDADD = libshared.a libnix.a -ldb_cxx -lATerm TESTS = test test_SOURCES = test.cc -test_LDADD = libshared.a libnix.a -ldb_cxx-4 -lATerm +test_LDADD = libshared.a libnix.a -ldb_cxx -lATerm noinst_LIBRARIES = libnix.a libshared.a From 39ce70025b59a545127d1ffdefa83b7cbfcd8be1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 23 Jul 2003 15:53:34 +0000 Subject: [PATCH 0162/6440] * Incorporated Berkeley DB and ATerm into the source tree. * `make dist'. --- Makefile.am | 4 +++- configure.ac | 4 ++-- corepkgs/fetchurl/Makefile.am | 2 ++ corepkgs/nar/Makefile.am | 2 ++ externals/Makefile.am | 43 +++++++++++++++++++++++++++++++++++ scripts/Makefile.am | 5 ++++ src/Makefile.am | 8 +++++-- 7 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 externals/Makefile.am diff --git a/Makefile.am b/Makefile.am index 2bed3f3bb..70aa1ba96 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1 +1,3 @@ -SUBDIRS = src scripts corepkgs +SUBDIRS = externals src scripts corepkgs + +EXTRA_DIST = boost/*.hpp boost/format/*.hpp substitute.mk \ No newline at end of file diff --git a/configure.ac b/configure.ac index 9db0a8807..e60895ff7 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT(nix, 0.1) +AC_INIT(nix, 0.2pre1) AC_CONFIG_SRCDIR(src/nix.cc) AC_CONFIG_AUX_DIR(config) AM_INIT_AUTOMAKE @@ -12,7 +12,7 @@ AC_PROG_CXX AC_PROG_RANLIB AM_CONFIG_HEADER([config.h]) -AC_CONFIG_FILES([Makefile src/Makefile scripts/Makefile +AC_CONFIG_FILES([Makefile externals/Makefile src/Makefile scripts/Makefile corepkgs/Makefile corepkgs/fetchurl/Makefile corepkgs/nar/Makefile]) AC_OUTPUT diff --git a/corepkgs/fetchurl/Makefile.am b/corepkgs/fetchurl/Makefile.am index 6bae43907..44e07280f 100644 --- a/corepkgs/fetchurl/Makefile.am +++ b/corepkgs/fetchurl/Makefile.am @@ -6,3 +6,5 @@ install-exec-local: $(INSTALL_DATA) fetchurl.sh $(datadir)/fix/fetchurl include ../../substitute.mk + +EXTRA_DIST = fetchurl.fix fetchurl.sh diff --git a/corepkgs/nar/Makefile.am b/corepkgs/nar/Makefile.am index 508eeff7c..4cb59ae39 100644 --- a/corepkgs/nar/Makefile.am +++ b/corepkgs/nar/Makefile.am @@ -8,3 +8,5 @@ install-exec-local: $(INSTALL_DATA) unnar.sh $(datadir)/fix/nar include ../../substitute.mk + +EXTRA_DIST = nar.fix nar.sh unnar.fix unnar.sh diff --git a/externals/Makefile.am b/externals/Makefile.am new file mode 100644 index 000000000..cd95832f6 --- /dev/null +++ b/externals/Makefile.am @@ -0,0 +1,43 @@ +# Berkeley DB + +DB = db-4.0.14 +DB_URL = http://www.sleepycat.com/update/snapshot/db-4.0.14.tar.gz + +$(DB).tar.gz: + wget $(DB_URL) + +$(DB): $(DB).tar.gz + gunzip < $(DB).tar.gz | tar xvf - + +build-db: $(DB) + (pfx=`pwd` && \ + cd $(DB)/build_unix && \ + CC=$(CC) CXX=$(CXX) ../dist/configure --prefix=$$pfx/inst --enable-cxx --disable-shared && \ + make && \ + make install) + touch build-db + + +# CWI ATerm + +ATERM = aterm-2.0 +ATERM_URL = http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz + +$(ATERM).tar.gz: + wget $(ATERM_URL) + +$(ATERM): $(ATERM).tar.gz + gunzip < $(ATERM).tar.gz | tar xvf - + +build-aterm: $(ATERM) + (pfx=`pwd` && \ + cd $(ATERM) && \ + ./configure --prefix=$$pfx/inst && \ + make && \ + make install) + touch build-aterm + + +all: build-db build-aterm + +EXTRA_DIST = $(DB).tar.gz $(ATERM).tar.gz diff --git a/scripts/Makefile.am b/scripts/Makefile.am index fb2ef566d..d1ab6e4cd 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -11,3 +11,8 @@ install-exec-local: $(INSTALL_DATA) prebuilts.conf $(sysconfdir)/nix/prebuilts.conf include ../substitute.mk + +EXTRA_DIST = nix-switch.in nix-collect-garbage.in \ + nix-pull.in nix-push.in nix-profile.sh.in \ + prebuilts.conf + diff --git a/src/Makefile.am b/src/Makefile.am index eb5177d13..98d76b753 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,8 @@ bin_PROGRAMS = nix nix-hash fix check_PROGRAMS = test -AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. +AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. -I../externals/inst/include $(CXXFLAGS) +AM_LDFLAGS = -L../externals/inst/lib -ldb_cxx -lATerm $(LDFLAGS) nix_SOURCES = nix.cc nix_LDADD = libshared.a libnix.a -ldb_cxx -lATerm @@ -29,7 +30,8 @@ libshared_a_CXXFLAGS = \ -DNIX_STORE_DIR=\"$(prefix)/store\" \ -DNIX_DATA_DIR=\"$(datadir)\" \ -DNIX_STATE_DIR=\"$(localstatedir)/nix\" \ - -DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" + -DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \ + $(AM_CXXFLAGS) install-data-local: $(INSTALL) -d $(localstatedir)/nix @@ -37,3 +39,5 @@ install-data-local: $(INSTALL) -d $(localstatedir)/log/nix $(INSTALL) -d $(prefix)/store $(bindir)/nix --init + +EXTRA_DIST = *.hh *.h \ No newline at end of file From b75719b98457c61857689ab135559a17034dd8ec Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 24 Jul 2003 08:24:32 +0000 Subject: [PATCH 0163/6440] * Don't sync the database on close. This was killing performance. (Of course, the real problem is that we open the database for *every* operation; we should only open it once. And we should use transactions.) --- src/db.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db.cc b/src/db.cc index a8741342c..e3a6659bb 100644 --- a/src/db.cc +++ b/src/db.cc @@ -12,7 +12,7 @@ class Db2 : public Db { public: Db2(DbEnv *env, u_int32_t flags) : Db(env, flags) { } - ~Db2() { close(0); } + ~Db2() { close(DB_NOSYNC); } }; From 1a7468a57a11288a007c40d50ed28718d757a546 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 24 Jul 2003 08:53:43 +0000 Subject: [PATCH 0164/6440] * Debug levels. Use `--verbose / -v LEVEL' to display only messages up to the given verbosity levels. These currently are: lvlError = 0, lvlNormal = 5, lvlDebug = 10, lvlDebugMore = 15 although only lvlError and lvlDebug are actually used right now. --- src/fix.cc | 10 ++++++++++ src/nix.cc | 16 +++++++++++++--- src/normalise.cc | 6 ++---- src/shared.cc | 23 ++++++++++++++++------- src/store.cc | 2 +- src/test.cc | 3 +-- src/util.cc | 18 +++++++++++++----- src/util.hh | 15 ++++++++++++--- 8 files changed, 68 insertions(+), 25 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index afa0167ec..8463c0ddb 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -310,6 +310,16 @@ void run(Strings args) throw UsageError(format("argument required in `%1%'") % arg); state.searchDirs.push_back(*it++); } + else if (arg == "--verbose" || arg == "-v") { + if (it == args.end()) throw UsageError( + format("`%1%' requires an argument") % arg); + istringstream str(*it++); + int lvl; + str >> lvl; + if (str.fail()) throw UsageError( + format("`%1%' requires an integer argument") % arg); + verbosity = (Verbosity) lvl; + } else if (arg[0] == '-') throw UsageError(format("unknown flag `%1%`") % arg); else diff --git a/src/nix.cc b/src/nix.cc index e08854227..f672c42a8 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -1,4 +1,5 @@ #include +#include #include "globals.hh" #include "normalise.hh" @@ -278,10 +279,9 @@ void run(Strings args) Strings opFlags, opArgs; Operation op = 0; - for (Strings::iterator it = args.begin(); - it != args.end(); it++) + for (Strings::iterator it = args.begin(); it != args.end(); ) { - string arg = *it; + string arg = *it++; Operation oldOp = op; @@ -307,6 +307,16 @@ void run(Strings args) op = opVerify; else if (arg == "--path" || arg == "-p") pathArgs = true; + else if (arg == "--verbose" || arg == "-v") { + if (it == args.end()) throw UsageError( + format("`%1%' requires an argument") % arg); + istringstream str(*it++); + int lvl; + str >> lvl; + if (str.fail()) throw UsageError( + format("`%1%' requires an integer argument") % arg); + verbosity = (Verbosity) lvl; + } else if (arg[0] == '-') opFlags.push_back(arg); else diff --git a/src/normalise.cc b/src/normalise.cc index f463457e4..6ce73d1ac 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -26,8 +26,7 @@ typedef set FSIdSet; Slice normaliseFState(FSId id, FSIdSet pending) { - debug(format("normalising fstate %1%") % (string) id); - Nest nest(true); + Nest nest(lvlDebug, format("normalising fstate %1%") % (string) id); /* Try to substitute $id$ by any known successors in order to speed up the rewrite process. */ @@ -177,8 +176,7 @@ Slice normaliseFState(FSId id, FSIdSet pending) void realiseSlice(const Slice & slice, FSIdSet pending) { - debug(format("realising slice")); - Nest nest(true); + Nest nest(lvlDebug, format("realising slice")); /* Perhaps all paths already contain the right id? */ diff --git a/src/shared.cc b/src/shared.cc index bfd7498de..75145f6db 100644 --- a/src/shared.cc +++ b/src/shared.cc @@ -1,4 +1,5 @@ #include +#include extern "C" { #include @@ -32,7 +33,12 @@ static void initAndRun(int argc, char * * argv) string arg = *it; if (arg.length() > 2 && arg[0] == '-' && arg[1] != '-') { for (unsigned int i = 1; i < arg.length(); i++) - args.insert(it, (string) "-" + arg[i]); + if (isalpha(arg[i])) + args.insert(it, (string) "-" + arg[i]); + else { + args.insert(it, string(arg, i)); + break; + } it = args.erase(it); } else it++; } @@ -50,18 +56,21 @@ int main(int argc, char * * argv) try { initAndRun(argc, argv); } catch (UsageError & e) { - cerr << format( - "error: %1%\n" - "Try `%2% --help' for more information.\n") - % e.what() % programId; + msg(lvlError, + format( + "error: %1%\n" + "Try `%2% --help' for more information.") + % e.what() % programId); return 1; } catch (Error & e) { - cerr << format("error: %1%\n") % e.msg(); + msg(lvlError, format("error: %1%") % e.msg()); return 1; } catch (exception & e) { - cerr << format("error: %1%\n") % e.what(); + msg(lvlError, format("error: %1%") % e.what()); return 1; } return 0; } + + diff --git a/src/store.cc b/src/store.cc index 013bd2e2a..50932d806 100644 --- a/src/store.cc +++ b/src/store.cc @@ -168,7 +168,7 @@ string expandId(const FSId & id, const string & target, const string & prefix, FSIdSet pending) { debug(format("expanding %1%") % (string) id); - Nest nest(true); + Nest nest(lvlDebug, format("expanding %1%") % (string) id); Strings paths; diff --git a/src/test.cc b/src/test.cc index 6b567abe0..a2431273e 100644 --- a/src/test.cc +++ b/src/test.cc @@ -12,8 +12,7 @@ void realise(FSId id) { - debug(format("TEST: realising %1%") % (string) id); - Nest nest(true); + Nest nest(lvlDebug, format("TEST: realising %1%") % (string) id); Slice slice = normaliseFState(id); realiseSlice(slice); } diff --git a/src/util.cc b/src/util.cc index a16643022..8510bb7a6 100644 --- a/src/util.cc +++ b/src/util.cc @@ -130,13 +130,20 @@ void deletePath(string path) } +Verbosity verbosity = lvlNormal; + static int nestingLevel = 0; -Nest::Nest(bool nest) +Nest::Nest(Verbosity level, const format & f) { - this->nest = nest; - if (nest) nestingLevel++; + if (level > verbosity) + nest = false; + else { + msg(level, f); + nest = true; + nestingLevel++; + } } @@ -146,8 +153,9 @@ Nest::~Nest() } -void msg(const format & f) +void msg(Verbosity level, const format & f) { + if (level > verbosity) return; string spaces; for (int i = 0; i < nestingLevel; i++) spaces += "| "; @@ -157,7 +165,7 @@ void msg(const format & f) void debug(const format & f) { - msg(format("debug: %1%") % f.str()); + msg(lvlDebug, format("debug: %1%") % f.str()); } diff --git a/src/util.hh b/src/util.hh index 8b23bee00..6d87898b5 100644 --- a/src/util.hh +++ b/src/util.hh @@ -72,17 +72,26 @@ void deletePath(string path); /* Messages. */ +typedef enum { + lvlError = 0, + lvlNormal = 5, + lvlDebug = 10, + lvlDebugMore = 15 +} Verbosity; + +extern Verbosity verbosity; /* supress msgs > this */ + class Nest { private: bool nest; public: - Nest(bool nest); + Nest(Verbosity level, const format & f); ~Nest(); }; -void msg(const format & f); -void debug(const format & f); +void msg(Verbosity level, const format & f); +void debug(const format & f); /* shorthand */ /* Wrappers arount read()/write() that read/write exactly the From 3b521bb1bd53479896de89e7a24938039f92aace Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 24 Jul 2003 13:35:17 +0000 Subject: [PATCH 0165/6440] * Do sync the database, since not doing so caused database changes not to reach the disk at all. Looks like a bug. --- src/db.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db.cc b/src/db.cc index e3a6659bb..a8741342c 100644 --- a/src/db.cc +++ b/src/db.cc @@ -12,7 +12,7 @@ class Db2 : public Db { public: Db2(DbEnv *env, u_int32_t flags) : Db(env, flags) { } - ~Db2() { close(DB_NOSYNC); } + ~Db2() { close(0); } }; From 0a0c1fcb4d0e42577ac0c7ac23bd9b908ecde49f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 24 Jul 2003 13:43:16 +0000 Subject: [PATCH 0166/6440] * The `-v' flag no longer takes an argument; it should be repeated instead (e.g., `-vvvv' for lots of output). Default is to only print error messages. --- src/fix.cc | 23 +++++++++++------------ src/nix.cc | 12 ++---------- src/normalise.cc | 16 ++++++++-------- src/store.cc | 1 - src/util.cc | 4 ++-- src/util.hh | 11 ++++++----- 6 files changed, 29 insertions(+), 38 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index 8463c0ddb..10f0e4413 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -118,7 +118,6 @@ Hash hashPackage(EvalState & state, FState fs) *i = j->second; } } - debug(printTerm(unparseFState(fs))); return hashTerm(unparseFState(fs)); } @@ -176,6 +175,10 @@ static Expr evalExpr2(EvalState & state, Expr e) Hash pkgHash = hashPackage(state, fs); FSId pkgId = writeTerm(unparseFState(fs), ""); state.pkgHashes[pkgId] = pkgHash; + + msg(lvlChatty, format("copied `%1%' -> %2%") + % srcPath % (string) pkgId); + return ATmake("FSId()", ((string) pkgId).c_str()); } @@ -244,7 +247,6 @@ static Expr evalExpr2(EvalState & state, Expr e) canonPath(nixStore + "/" + ((string) outId).c_str() + "-" + name); fs.derive.env.push_back(StringPair("out", outPath)); fs.derive.outputs.push_back(DeriveOutput(outPath, outId)); - debug(format("%1%: %2%") % (string) outId % name); /* Write the resulting term into the Nix store directory. */ Hash pkgHash = outIdGiven @@ -252,6 +254,10 @@ static Expr evalExpr2(EvalState & state, Expr e) : hashPackage(state, fs); FSId pkgId = writeTerm(unparseFState(fs), "-d-" + name); state.pkgHashes[pkgId] = pkgHash; + + msg(lvlChatty, format("instantiated `%1%' -> %2%") + % name % (string) pkgId); + return ATmake("FSId()", ((string) pkgId).c_str()); } @@ -285,6 +291,7 @@ static Expr evalExpr(EvalState & state, Expr e) static Expr evalFile(EvalState & state, string relPath) { string path = searchPath(state.searchDirs, relPath); + Nest nest(lvlTalkative, format("evaluating file `%1%'") % path); Expr e = ATreadFromNamedFile(path.c_str()); if (!e) throw Error(format("unable to read a term from `%1%'") % path); @@ -310,16 +317,8 @@ void run(Strings args) throw UsageError(format("argument required in `%1%'") % arg); state.searchDirs.push_back(*it++); } - else if (arg == "--verbose" || arg == "-v") { - if (it == args.end()) throw UsageError( - format("`%1%' requires an argument") % arg); - istringstream str(*it++); - int lvl; - str >> lvl; - if (str.fail()) throw UsageError( - format("`%1%' requires an integer argument") % arg); - verbosity = (Verbosity) lvl; - } + else if (arg == "--verbose" || arg == "-v") + verbosity = (Verbosity) ((int) verbosity + 1); else if (arg[0] == '-') throw UsageError(format("unknown flag `%1%`") % arg); else diff --git a/src/nix.cc b/src/nix.cc index f672c42a8..3345f983f 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -307,16 +307,8 @@ void run(Strings args) op = opVerify; else if (arg == "--path" || arg == "-p") pathArgs = true; - else if (arg == "--verbose" || arg == "-v") { - if (it == args.end()) throw UsageError( - format("`%1%' requires an argument") % arg); - istringstream str(*it++); - int lvl; - str >> lvl; - if (str.fail()) throw UsageError( - format("`%1%' requires an integer argument") % arg); - verbosity = (Verbosity) lvl; - } + else if (arg == "--verbose" || arg == "-v") + verbosity = (Verbosity) ((int) verbosity + 1); else if (arg[0] == '-') opFlags.push_back(arg); else diff --git a/src/normalise.cc b/src/normalise.cc index 6ce73d1ac..80a615227 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -26,7 +26,7 @@ typedef set FSIdSet; Slice normaliseFState(FSId id, FSIdSet pending) { - Nest nest(lvlDebug, format("normalising fstate %1%") % (string) id); + Nest nest(lvlTalkative, format("normalising fstate %1%") % (string) id); /* Try to substitute $id$ by any known successors in order to speed up the rewrite process. */ @@ -80,7 +80,7 @@ Slice normaliseFState(FSId id, FSIdSet pending) for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); i != fs.derive.outputs.end(); i++) { - debug(format("building %1% in %2%") % (string) i->second % i->first); + debug(format("building %1% in `%2%'") % (string) i->second % i->first); outPaths[i->first] = i->second; inPaths.push_back(i->first); } @@ -94,7 +94,7 @@ Slice normaliseFState(FSId id, FSIdSet pending) try { expandId(i->second, i->first, "/", pending); } catch (Error & e) { - debug(format("fast build failed: %1%") % e.what()); + debug(format("fast build failed for %1%") % e.what()); fastBuild = false; break; } @@ -109,12 +109,12 @@ Slice normaliseFState(FSId id, FSIdSet pending) throw Error(format("path `%1%' exists") % i->first); /* Run the builder. */ - debug(format("building...")); + msg(lvlChatty, format("building...")); runProgram(fs.derive.builder, env); - debug(format("build completed")); + msg(lvlChatty, format("build completed")); } else - debug(format("skipping build")); + msg(lvlChatty, format("fast build succesful")); /* Check whether the output paths were created, and register each one. */ @@ -167,7 +167,7 @@ Slice normaliseFState(FSId id, FSIdSet pending) fs.type = FState::fsSlice; ATerm nf = unparseFState(fs); - debug(format("normal form: %1%") % printTerm(nf)); + msg(lvlVomit, format("normal form: %1%") % printTerm(nf)); storeSuccessor(id, nf); return fs.slice; @@ -206,7 +206,7 @@ void realiseSlice(const Slice & slice, FSIdSet pending) i != slice.elems.end(); i++) { SliceElem elem = *i; - debug(format("expanding %1% in %2%") % (string) elem.id % elem.path); + debug(format("expanding %1% in `%2%'") % (string) elem.id % elem.path); expandId(elem.id, elem.path, "/", pending); } } diff --git a/src/store.cc b/src/store.cc index 50932d806..fe7e1406f 100644 --- a/src/store.cc +++ b/src/store.cc @@ -167,7 +167,6 @@ bool isInPrefix(const string & path, const string & _prefix) string expandId(const FSId & id, const string & target, const string & prefix, FSIdSet pending) { - debug(format("expanding %1%") % (string) id); Nest nest(lvlDebug, format("expanding %1%") % (string) id); Strings paths; diff --git a/src/util.cc b/src/util.cc index 8510bb7a6..3c9a31acc 100644 --- a/src/util.cc +++ b/src/util.cc @@ -130,7 +130,7 @@ void deletePath(string path) } -Verbosity verbosity = lvlNormal; +Verbosity verbosity = lvlError; static int nestingLevel = 0; @@ -165,7 +165,7 @@ void msg(Verbosity level, const format & f) void debug(const format & f) { - msg(lvlDebug, format("debug: %1%") % f.str()); + msg(lvlDebug, f); } diff --git a/src/util.hh b/src/util.hh index 6d87898b5..2863085c1 100644 --- a/src/util.hh +++ b/src/util.hh @@ -73,10 +73,11 @@ void deletePath(string path); /* Messages. */ typedef enum { - lvlError = 0, - lvlNormal = 5, - lvlDebug = 10, - lvlDebugMore = 15 + lvlError, + lvlTalkative, + lvlChatty, + lvlDebug, + lvlVomit } Verbosity; extern Verbosity verbosity; /* supress msgs > this */ @@ -91,7 +92,7 @@ public: }; void msg(Verbosity level, const format & f); -void debug(const format & f); /* shorthand */ +void debug(const format & f); /* short-hand for msg(lvlDebug, ...) */ /* Wrappers arount read()/write() that read/write exactly the From 5d7a20dac3c7fd728fa885dd8dab6e170b860db9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 24 Jul 2003 14:31:39 +0000 Subject: [PATCH 0167/6440] * Prevent spurious rebuilds of db/aterm. --- externals/Makefile.am | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/externals/Makefile.am b/externals/Makefile.am index cd95832f6..543c93f6d 100644 --- a/externals/Makefile.am +++ b/externals/Makefile.am @@ -9,7 +9,11 @@ $(DB).tar.gz: $(DB): $(DB).tar.gz gunzip < $(DB).tar.gz | tar xvf - -build-db: $(DB) +have-db: + $(MAKE) $(DB) + touch have-db + +build-db: have-db (pfx=`pwd` && \ cd $(DB)/build_unix && \ CC=$(CC) CXX=$(CXX) ../dist/configure --prefix=$$pfx/inst --enable-cxx --disable-shared && \ @@ -29,7 +33,11 @@ $(ATERM).tar.gz: $(ATERM): $(ATERM).tar.gz gunzip < $(ATERM).tar.gz | tar xvf - -build-aterm: $(ATERM) +have-aterm: + $(MAKE) $(ATERM) + touch have-aterm + +build-aterm: have-aterm (pfx=`pwd` && \ cd $(ATERM) && \ ./configure --prefix=$$pfx/inst && \ From f21b3419575eec2b5bbcc12b035f21c23d57e24d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 24 Jul 2003 15:03:36 +0000 Subject: [PATCH 0168/6440] * Fix message. --- src/normalise.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/normalise.cc b/src/normalise.cc index 80a615227..a814a993b 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -94,7 +94,8 @@ Slice normaliseFState(FSId id, FSIdSet pending) try { expandId(i->second, i->first, "/", pending); } catch (Error & e) { - debug(format("fast build failed for %1%") % e.what()); + debug(format("fast build failed for `%1%': %2%") + % i->first % e.what()); fastBuild = false; break; } From 949c4fa1a863a804bdf1f985b55d5259f18838ae Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 28 Jul 2003 12:19:23 +0000 Subject: [PATCH 0169/6440] * `nix --help'. * `nix --query --graph' to print a dot dependency graph of derive expressions. --- src/Makefile.am | 9 +++- src/nix-help.txt | 36 ++++++++++++++++ src/nix.cc | 108 +++++++++++++++++++++++++++++++---------------- 3 files changed, 116 insertions(+), 37 deletions(-) create mode 100644 src/nix-help.txt diff --git a/src/Makefile.am b/src/Makefile.am index 98d76b753..23e242919 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -33,6 +33,13 @@ libshared_a_CXXFLAGS = \ -DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \ $(AM_CXXFLAGS) +nix.o: nix-help.txt.hh + +%.hh: % + echo -n '"' > $@ + sed 's|\(.*\)|\1\\n\\|' < $< >> $@ + echo '"' >> $@ + install-data-local: $(INSTALL) -d $(localstatedir)/nix $(INSTALL) -d $(localstatedir)/nix/links @@ -40,4 +47,4 @@ install-data-local: $(INSTALL) -d $(prefix)/store $(bindir)/nix --init -EXTRA_DIST = *.hh *.h \ No newline at end of file +EXTRA_DIST = *.hh *.h diff --git a/src/nix-help.txt b/src/nix-help.txt new file mode 100644 index 000000000..ecf9b5c16 --- /dev/null +++ b/src/nix-help.txt @@ -0,0 +1,36 @@ +nix [OPTIONS...] [ARGUMENTS...] + +Operations: + + --install / -i: realise an fstate + --delete / -d: delete paths from the Nix store + --add / -A: copy a path to the Nix store + --query / -q: query information + + --successor: register a successor expression + --substitute: register a substitute expression + + --dump: dump a path as a Nix archive + --restore: restore a path from a Nix archive + + --init: initialise the Nix database + --verify: verify Nix structures + + --version: output version information + --help: display help + +Source selection for --install, --dump: + + --path / -p: by file name !!! -> path + +Query flags: + + --list / -l: query the output paths (roots) of an fstate (default) + --refs / -r: query paths referenced by an fstate + --generators / -g: find expressions producing a subset of given ids + --expansion / -e: print a path containing id + --graph: print a dot graph rooted at given ids + +Options: + + --verbose / -v: verbose operation (may be repeated) diff --git a/src/nix.cc b/src/nix.cc index 3345f983f..9e62fa395 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -13,42 +13,14 @@ typedef void (* Operation) (Strings opFlags, Strings opArgs); static bool pathArgs = false; -/* Nix syntax: +static void printHelp() +{ + cout << +#include "nix-help.txt.hh" + ; + exit(0); +} - nix [OPTIONS...] [ARGUMENTS...] - - Operations: - - --install / -i: realise an fstate - --delete / -d: delete paths from the Nix store - --add / -A: copy a path to the Nix store - --query / -q: query information - - --successor: register a successor expression - --substitute: register a substitute expression - - --dump: dump a path as a Nix archive - --restore: restore a path from a Nix archive - - --init: initialise the Nix database - --verify: verify Nix structures - - --version: output version information - --help: display help - - Source selection for --install, --dump: - - --path / -p: by file name !!! -> path - - Query flags: - - --list / -l: query the output paths (roots) of an fstate - --refs / -r: query paths referenced by an fstate - - Options: - - --verbose / -v: verbose operation -*/ static FSId argToId(const string & arg) @@ -104,10 +76,17 @@ static void opAdd(Strings opFlags, Strings opArgs) } +string dotQuote(const string & s) +{ + return "\"" + s + "\""; +} + + /* Perform various sorts of queries. */ static void opQuery(Strings opFlags, Strings opArgs) { - enum { qList, qRefs, qGenerators, qExpansion } query = qList; + enum { qList, qRefs, qGenerators, qExpansion, qGraph + } query = qList; for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); i++) @@ -115,6 +94,7 @@ static void opQuery(Strings opFlags, Strings opArgs) else if (*i == "--refs" || *i == "-r") query = qRefs; else if (*i == "--generators" || *i == "-g") query = qGenerators; else if (*i == "--expansion" || *i == "-e") query = qExpansion; + else if (*i == "--graph") query = qGraph; else throw UsageError(format("unknown flag `%1%'") % *i); switch (query) { @@ -170,6 +150,60 @@ static void opQuery(Strings opFlags, Strings opArgs) break; } + case qGraph: { + + FSIds workList; + + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); i++) + workList.push_back(argToId(*i)); + + FSIdSet doneSet; + + cout << "digraph G {\n"; + + while (!workList.empty()) { + FSId id = workList.front(); + workList.pop_front(); + + if (doneSet.find(id) == doneSet.end()) { + doneSet.insert(id); + + FState fs = parseFState(termFromId(id)); + + string label; + + if (fs.type == FState::fsDerive) { + for (FSIds::iterator i = fs.derive.inputs.begin(); + i != fs.derive.inputs.end(); i++) + { + workList.push_back(*i); + cout << dotQuote(*i) << " -> " + << dotQuote(id) << ";\n"; + } + + label = "derive"; + for (StringPairs::iterator i = fs.derive.env.begin(); + i != fs.derive.env.end(); i++) + if (i->first == "name") label = i->second; + } + + else if (fs.type == FState::fsSlice) { + label = baseNameOf((*fs.slice.elems.begin()).path); + } + + else abort(); + + cout << dotQuote(id) << "[label = " + << dotQuote(label) + << "];\n"; + } + } + + cout << "}\n"; + break; + } + default: abort(); } @@ -309,6 +343,8 @@ void run(Strings args) pathArgs = true; else if (arg == "--verbose" || arg == "-v") verbosity = (Verbosity) ((int) verbosity + 1); + else if (arg == "--help") + printHelp(); else if (arg[0] == '-') opFlags.push_back(arg); else From dec8fbc52bab9cc19ac97c422e79e40fa70c2b13 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 28 Jul 2003 14:13:42 +0000 Subject: [PATCH 0170/6440] * Check for the pthread library (db4 needs it on some platforms). --- configure.ac | 2 ++ externals/Makefile.am | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index e60895ff7..57861063d 100644 --- a/configure.ac +++ b/configure.ac @@ -11,6 +11,8 @@ AC_PROG_CC AC_PROG_CXX AC_PROG_RANLIB +AC_CHECK_LIB(pthread, pthread_mutex_init) + AM_CONFIG_HEADER([config.h]) AC_CONFIG_FILES([Makefile externals/Makefile src/Makefile scripts/Makefile corepkgs/Makefile corepkgs/fetchurl/Makefile diff --git a/externals/Makefile.am b/externals/Makefile.am index 543c93f6d..01ef93455 100644 --- a/externals/Makefile.am +++ b/externals/Makefile.am @@ -16,7 +16,8 @@ have-db: build-db: have-db (pfx=`pwd` && \ cd $(DB)/build_unix && \ - CC=$(CC) CXX=$(CXX) ../dist/configure --prefix=$$pfx/inst --enable-cxx --disable-shared && \ + CC=$(CC) CXX=$(CXX) ../dist/configure --prefix=$$pfx/inst \ + --enable-cxx --disable-shared && \ make && \ make install) touch build-db From ce5fd1cc12f678627163d532acd7dd4251758198 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 28 Jul 2003 16:07:01 +0000 Subject: [PATCH 0171/6440] * Do not set LD_LIBRARY_PATH; it breaks many things. E.g., SuSE's ssh dynamically links against libdb4 (?!), due to LD_LIBRARY_PATH it picks up our libdb4 instead of SuSE's libdb4, but our libdb4 uses another glibc so loading barfs. Instead, all packages should use rpaths to store library locations in executables/libraries. The disadvantage is that overriding rpaths is harder. (It is possible by invoking the dynamic linker directly, e.g., `/lib/ld-linux.so.2 --ignore-path LIST program args...' to ignore the rpath for the libraries in LIST). It would be better to use DT_RUNPATH, which is consulted by the dynamic linker *after* LD_LIBRARY_PATH but *before* ld.so.cache and the system directories. --- scripts/nix-profile.sh.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/nix-profile.sh.in b/scripts/nix-profile.sh.in index 3a64caa04..0d059e1a2 100644 --- a/scripts/nix-profile.sh.in +++ b/scripts/nix-profile.sh.in @@ -6,7 +6,7 @@ export PATH=$NIX_LINKS/bin:@prefix@/bin:$PATH - export LD_LIBRARY_PATH=$NIX_LINKS/lib:$LD_LIBRARY_PATH +# export LD_LIBRARY_PATH=$NIX_LINKS/lib:$LD_LIBRARY_PATH export LIBRARY_PATH=$NIX_LINKS/lib:$LIBRARY_PATH From 5acb45446e756c023bcb6f052331181671580a5e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 29 Jul 2003 09:45:03 +0000 Subject: [PATCH 0172/6440] * Let `nix --install' print out the id of the normal form. * Some minor refactoring. --- src/nix.cc | 6 +++++- src/normalise.cc | 51 ++++++++++++++++++++++++------------------------ src/normalise.hh | 6 +++--- src/store.cc | 3 +-- src/test.cc | 3 +-- 5 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index 9e62fa395..6dc5776e2 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -44,7 +44,11 @@ static void opInstall(Strings opFlags, Strings opArgs) for (Strings::iterator it = opArgs.begin(); it != opArgs.end(); it++) - realiseSlice(normaliseFState(argToId(*it))); + { + FSId id = normaliseFState(argToId(*it)); + realiseSlice(id); + cout << format("%1%\n") % (string) id; + } } diff --git a/src/normalise.cc b/src/normalise.cc index a814a993b..95d96351a 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -24,7 +24,7 @@ static FSId storeSuccessor(const FSId & id1, ATerm sc) typedef set FSIdSet; -Slice normaliseFState(FSId id, FSIdSet pending) +FSId normaliseFState(FSId id, FSIdSet pending) { Nest nest(lvlTalkative, format("normalising fstate %1%") % (string) id); @@ -40,7 +40,7 @@ Slice normaliseFState(FSId id, FSIdSet pending) FState fs = parseFState(termFromId(id)); /* It this is a normal form (i.e., a slice) we are done. */ - if (fs.type == FState::fsSlice) return fs.slice; + if (fs.type == FState::fsSlice) return id; /* Otherwise, it's a derivation. */ @@ -56,11 +56,12 @@ Slice normaliseFState(FSId id, FSIdSet pending) for (FSIds::iterator i = fs.derive.inputs.begin(); i != fs.derive.inputs.end(); i++) { - Slice slice = normaliseFState(*i, pending); - realiseSlice(slice, pending); - - for (SliceElems::iterator j = slice.elems.begin(); - j != slice.elems.end(); j++) + FSId nf = normaliseFState(*i, pending); + realiseSlice(nf, pending); + FState fs = parseFState(termFromId(nf)); + if (fs.type != FState::fsSlice) abort(); + for (SliceElems::iterator j = fs.slice.elems.begin(); + j != fs.slice.elems.end(); j++) inMap[j->path] = *j; } @@ -169,21 +170,24 @@ Slice normaliseFState(FSId id, FSIdSet pending) fs.type = FState::fsSlice; ATerm nf = unparseFState(fs); msg(lvlVomit, format("normal form: %1%") % printTerm(nf)); - storeSuccessor(id, nf); - - return fs.slice; + return storeSuccessor(id, nf); } -void realiseSlice(const Slice & slice, FSIdSet pending) +void realiseSlice(const FSId & id, FSIdSet pending) { - Nest nest(lvlDebug, format("realising slice")); + Nest nest(lvlDebug, + format("realising slice %1%") % (string) id); + FState fs = parseFState(termFromId(id)); + if (fs.type != FState::fsSlice) + throw Error(format("expected slice in %1%") % (string) id); + /* Perhaps all paths already contain the right id? */ bool missing = false; - for (SliceElems::const_iterator i = slice.elems.begin(); - i != slice.elems.end(); i++) + for (SliceElems::const_iterator i = fs.slice.elems.begin(); + i != fs.slice.elems.end(); i++) { SliceElem elem = *i; string id; @@ -203,8 +207,8 @@ void realiseSlice(const Slice & slice, FSIdSet pending) } /* For each element, expand its id at its path. */ - for (SliceElems::const_iterator i = slice.elems.begin(); - i != slice.elems.end(); i++) + for (SliceElems::const_iterator i = fs.slice.elems.begin(); + i != fs.slice.elems.end(); i++) { SliceElem elem = *i; debug(format("expanding %1% in `%2%'") % (string) elem.id % elem.path); @@ -217,13 +221,8 @@ Strings fstatePaths(const FSId & id, bool normalise) { Strings paths; - FState fs; - - if (normalise) { - fs.slice = normaliseFState(id); - fs.type = FState::fsSlice; - } else - fs = parseFState(termFromId(id)); + FState fs = parseFState(termFromId( + normalise ? normaliseFState(id) : id)); if (fs.type == FState::fsSlice) { /* !!! fix complexity */ @@ -249,9 +248,9 @@ Strings fstatePaths(const FSId & id, bool normalise) Strings fstateRefs(const FSId & id) { Strings paths; - Slice slice = normaliseFState(id); - for (SliceElems::const_iterator i = slice.elems.begin(); - i != slice.elems.end(); i++) + FState fs = parseFState(termFromId(normaliseFState(id))); + for (SliceElems::const_iterator i = fs.slice.elems.begin(); + i != fs.slice.elems.end(); i++) paths.push_back(i->path); return paths; } diff --git a/src/normalise.hh b/src/normalise.hh index 72ee1d089..619fef8bf 100644 --- a/src/normalise.hh +++ b/src/normalise.hh @@ -5,11 +5,11 @@ /* Normalise an fstate-expression, that is, return an equivalent - Slice. (For the meaning of `pending', see expandId()). */ -Slice normaliseFState(FSId id, FSIdSet pending = FSIdSet()); + slice. (For the meaning of `pending', see expandId()). */ +FSId normaliseFState(FSId id, FSIdSet pending = FSIdSet()); /* Realise a Slice in the file system. */ -void realiseSlice(const Slice & slice, FSIdSet pending = FSIdSet()); +void realiseSlice(const FSId & id, FSIdSet pending = FSIdSet()); /* Get the list of root (output) paths of the given fstate-expression. */ diff --git a/src/store.cc b/src/store.cc index fe7e1406f..3493ba384 100644 --- a/src/store.cc +++ b/src/store.cc @@ -219,8 +219,7 @@ string expandId(const FSId & id, const string & target, debug(format("trying substitute %1%") % (string) subId); - Slice slice = normaliseFState(subId, pending); - realiseSlice(slice, pending); + realiseSlice(normaliseFState(subId, pending), pending); return expandId(id, target, prefix, pending); } diff --git a/src/test.cc b/src/test.cc index a2431273e..cff094e2f 100644 --- a/src/test.cc +++ b/src/test.cc @@ -13,8 +13,7 @@ void realise(FSId id) { Nest nest(lvlDebug, format("TEST: realising %1%") % (string) id); - Slice slice = normaliseFState(id); - realiseSlice(slice); + realiseSlice(normaliseFState(id)); } From 79ba0431db223c1c08b46e8f3d1819e3457f21a0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 29 Jul 2003 10:43:12 +0000 Subject: [PATCH 0173/6440] * `fstateRefs' now works on derive expressions as well. TODO: make this more efficient. * A flag `-n' in 'nix --query' to normalise the argument. Default is not to normalise. --- src/fix.cc | 2 +- src/nix.cc | 16 +++++++++++++--- src/normalise.cc | 34 +++++++++++++++++++++++++--------- src/normalise.hh | 2 +- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index 10f0e4413..9b0d95912 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -213,7 +213,7 @@ static Expr evalExpr2(EvalState & state, Expr e) if (ATmatch(value, "FSId()", &s1)) { FSId id = parseHash(s1); - Strings paths = fstatePaths(id, false); + Strings paths = fstatePaths(id); if (paths.size() != 1) abort(); string path = *(paths.begin()); fs.derive.inputs.push_back(id); diff --git a/src/nix.cc b/src/nix.cc index 6dc5776e2..e9f04ff59 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -80,17 +80,24 @@ static void opAdd(Strings opFlags, Strings opArgs) } -string dotQuote(const string & s) +static string dotQuote(const string & s) { return "\"" + s + "\""; } +FSId maybeNormalise(const FSId & id, bool normalise) +{ + return normalise ? normaliseFState(id) : id; +} + + /* Perform various sorts of queries. */ static void opQuery(Strings opFlags, Strings opArgs) { enum { qList, qRefs, qGenerators, qExpansion, qGraph } query = qList; + bool normalise = false; for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); i++) @@ -99,6 +106,7 @@ static void opQuery(Strings opFlags, Strings opArgs) else if (*i == "--generators" || *i == "-g") query = qGenerators; else if (*i == "--expansion" || *i == "-e") query = qExpansion; else if (*i == "--graph") query = qGraph; + else if (*i == "--normalise" || *i == "-n") normalise = true; else throw UsageError(format("unknown flag `%1%'") % *i); switch (query) { @@ -108,7 +116,8 @@ static void opQuery(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) { - Strings paths2 = fstatePaths(argToId(*i), true); + Strings paths2 = fstatePaths( + maybeNormalise(argToId(*i), normalise)); paths.insert(paths2.begin(), paths2.end()); } for (StringSet::iterator i = paths.begin(); @@ -122,7 +131,8 @@ static void opQuery(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) { - Strings paths2 = fstateRefs(argToId(*i)); + Strings paths2 = fstateRefs( + maybeNormalise(argToId(*i), normalise)); paths.insert(paths2.begin(), paths2.end()); } for (StringSet::iterator i = paths.begin(); diff --git a/src/normalise.cc b/src/normalise.cc index 95d96351a..eefb790b6 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -217,12 +217,11 @@ void realiseSlice(const FSId & id, FSIdSet pending) } -Strings fstatePaths(const FSId & id, bool normalise) +Strings fstatePaths(const FSId & id) { Strings paths; - FState fs = parseFState(termFromId( - normalise ? normaliseFState(id) : id)); + FState fs = parseFState(termFromId(id)); if (fs.type == FState::fsSlice) { /* !!! fix complexity */ @@ -245,14 +244,31 @@ Strings fstatePaths(const FSId & id, bool normalise) } +static void fstateRefsSet(const FSId & id, StringSet & paths) +{ + FState fs = parseFState(termFromId(id)); + + if (fs.type == FState::fsSlice) { + for (SliceElems::iterator i = fs.slice.elems.begin(); + i != fs.slice.elems.end(); i++) + paths.insert(i->path); + } + + else if (fs.type == FState::fsDerive) { + for (FSIds::iterator i = fs.derive.inputs.begin(); + i != fs.derive.inputs.end(); i++) + fstateRefsSet(*i, paths); + } + + else abort(); +} + + Strings fstateRefs(const FSId & id) { - Strings paths; - FState fs = parseFState(termFromId(normaliseFState(id))); - for (SliceElems::const_iterator i = fs.slice.elems.begin(); - i != fs.slice.elems.end(); i++) - paths.push_back(i->path); - return paths; + StringSet paths; + fstateRefsSet(id, paths); + return Strings(paths.begin(), paths.end()); } diff --git a/src/normalise.hh b/src/normalise.hh index 619fef8bf..a5b45c861 100644 --- a/src/normalise.hh +++ b/src/normalise.hh @@ -13,7 +13,7 @@ void realiseSlice(const FSId & id, FSIdSet pending = FSIdSet()); /* Get the list of root (output) paths of the given fstate-expression. */ -Strings fstatePaths(const FSId & id, bool normalise); +Strings fstatePaths(const FSId & id); /* Get the list of paths referenced by the given fstate-expression. */ Strings fstateRefs(const FSId & id); From dc14a3de46ee08a28158a886b6abba2c4144f6cd Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 29 Jul 2003 10:53:27 +0000 Subject: [PATCH 0174/6440] * Nicer dot graphs. --- src/nix.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/nix.cc b/src/nix.cc index e9f04ff59..a7bcf7268 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -185,7 +185,7 @@ static void opQuery(Strings opFlags, Strings opArgs) FState fs = parseFState(termFromId(id)); - string label; + string label, shape; if (fs.type == FState::fsDerive) { for (FSIds::iterator i = fs.derive.inputs.begin(); @@ -197,6 +197,7 @@ static void opQuery(Strings opFlags, Strings opArgs) } label = "derive"; + shape = "box"; for (StringPairs::iterator i = fs.derive.env.begin(); i != fs.derive.env.end(); i++) if (i->first == "name") label = i->second; @@ -204,12 +205,17 @@ static void opQuery(Strings opFlags, Strings opArgs) else if (fs.type == FState::fsSlice) { label = baseNameOf((*fs.slice.elems.begin()).path); + shape = "ellipse"; + if (isHash(string(label, 0, Hash::hashSize * 2)) && + label[Hash::hashSize * 2] == '-') + label = string(label, Hash::hashSize * 2 + 1); } else abort(); cout << dotQuote(id) << "[label = " << dotQuote(label) + << ", shape = " << shape << "];\n"; } } From 884646593488bfacd851bec72b7ac1a4841bf458 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 29 Jul 2003 14:28:17 +0000 Subject: [PATCH 0175/6440] * Get garbage collection and cache population to work *properly*. Renamed `fstateRefs' to `fstateRequisites'. The semantics of this function is that it returns a list of all paths necessary to realise a given expression. For a derive expression, this is the union of requisites of the inputs; for a slice expression, it is the path of each element in the slice. Also included are the paths of the expressions themselves. Optionally, one can also include the requisites of successor expressions (to recycle intermediate results). * `nix-switch' now distinguishes between an expression and its normal form. Usually, only the normal form is registered as a root of the garbage collector. With the `--source-root' flag, it will also register the original expression as a root. * `nix-collect-garbage' now has a flag `--keep-successors' which causes successors not to be included in the list of garbage paths. * `nix-collect-garbage' now has a flag `--invert' which will print all paths that should *not* be garbage collected. --- scripts/nix-collect-garbage.in | 16 ++++++++++- scripts/nix-switch.in | 50 +++++++++++++++++++++++----------- src/Makefile.am | 1 + src/nix-help.txt | 2 +- src/nix.cc | 15 ++++++---- src/normalise.cc | 20 +++++++++++--- src/normalise.hh | 10 +++++-- 7 files changed, 85 insertions(+), 29 deletions(-) diff --git a/scripts/nix-collect-garbage.in b/scripts/nix-collect-garbage.in index 8f54ba20f..eaa706578 100755 --- a/scripts/nix-collect-garbage.in +++ b/scripts/nix-collect-garbage.in @@ -5,13 +5,27 @@ my $storedir = "@prefix@/store"; my %alive; -open HASHES, "nix --query --refs \$(cat $linkdir/*.hash) |" or die "in `nix -qrh'"; +my $keepsuccessors = 0; +my $invert = 0; + +foreach my $arg (@ARGV) { + if ($arg eq "--keep-successors") { $keepsuccessors = 1; } + if ($arg eq "--invert") { $invert = 1; } + else { die "unknown argument `$arg'" }; +} + +my $extraarg = ""; +if ($keepsuccessors) { $extraarg = "--include-successors"; }; +open HASHES, "nix --query --requisites $extraarg \$(cat $linkdir/*.id) |" or die "in `nix -qrh'"; while () { chomp; $alive{$_} = 1; + if ($invert) { print "$_\n"; }; } close HASHES; +exit 0 if ($invert); + opendir(DIR, $storedir) or die "cannot opendir $storedir: $!"; my @names = readdir(DIR); closedir DIR; diff --git a/scripts/nix-switch.in b/scripts/nix-switch.in index ddaca4e22..db0e96f3e 100755 --- a/scripts/nix-switch.in +++ b/scripts/nix-switch.in @@ -3,39 +3,56 @@ use strict; my $keep = 0; +my $sourceroot = 0; +my $srcid; -if (scalar @ARGV > 0 && $ARGV[0] eq "--keep") { - shift @ARGV; - $keep = 1; +foreach my $arg (@ARGV) { + if ($arg eq "--keep") { $keep = 1; } + elsif ($arg eq "--source-root") { $sourceroot = 1; } + elsif ($arg =~ /^([0-9a-z]{32})$/) { $srcid = $arg; } + else { die "unknown argument `$arg'" }; } -my $hash = $ARGV[0]; -$hash || die "no package hash specified"; - my $linkdir = "@localstatedir@/nix/links"; # Build the specified package, and all its dependencies. -system "nix --install $hash"; +my $nfid = `nix --install $srcid`; if ($?) { die "`nix --install' failed"; } +chomp $nfid; +die unless $nfid =~ /^([0-9a-z]{32})$/; -my $pkgdir = `nix --query --list $hash`; +my $pkgdir = `nix --query --list $nfid`; if ($?) { die "`nix --query --list' failed"; } chomp $pkgdir; # Figure out a generation number. +opendir(DIR, $linkdir); my $nr = 0; -while (-e "$linkdir/$nr") { $nr++; } +foreach my $n (sort(readdir(DIR))) { + next if (!($n =~ /^\d+$/)); + $nr = $n + 1 if ($n >= $nr); +} +closedir(DIR); + my $link = "$linkdir/$nr"; # Create a symlink from $link to $pkgdir. symlink($pkgdir, $link) or die "cannot create $link: $!"; -# Also store the hash of $pkgdir. This is useful for garbage +# Store the id of the normal form. This is useful for garbage # collection and the like. -my $hashfile = "$linkdir/$nr.hash"; -open HASH, "> $hashfile" or die "cannot create $hashfile"; -print HASH "$hash\n"; -close HASH; +my $idfile = "$linkdir/$nr.id"; +open ID, "> $idfile" or die "cannot create $idfile"; +print ID "$nfid\n"; +close ID; + +# Optionally store the source id. +if ($sourceroot) { + $idfile = "$linkdir/$nr-src.id"; + open ID, "> $idfile" or die "cannot create $idfile"; + print ID "$srcid\n"; + close ID; +} my $current = "$linkdir/current"; @@ -59,6 +76,7 @@ rename($tmplink, $current) or die "cannot rename $tmplink"; if (!$keep && defined $oldlink) { print "deleting old $oldlink\n"; - unlink($oldlink) == 1 || print "cannot delete $oldlink\n"; - unlink("$oldlink.hash") == 1 || print "cannot delete $oldlink.hash\n"; + unlink($oldlink) == 1 or print "cannot delete $oldlink\n"; + unlink("$oldlink.id") == 1 or print "cannot delete $oldlink.id\n"; + unlink("$oldlink-src.id"); } diff --git a/src/Makefile.am b/src/Makefile.am index 23e242919..cc51eff36 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -43,6 +43,7 @@ nix.o: nix-help.txt.hh install-data-local: $(INSTALL) -d $(localstatedir)/nix $(INSTALL) -d $(localstatedir)/nix/links + ln -sf $(localstatedir)/nix/links/current $(prefix)/current $(INSTALL) -d $(localstatedir)/log/nix $(INSTALL) -d $(prefix)/store $(bindir)/nix --init diff --git a/src/nix-help.txt b/src/nix-help.txt index ecf9b5c16..0e54d162d 100644 --- a/src/nix-help.txt +++ b/src/nix-help.txt @@ -26,7 +26,7 @@ Source selection for --install, --dump: Query flags: --list / -l: query the output paths (roots) of an fstate (default) - --refs / -r: query paths referenced by an fstate + --requisites / -r: print all paths necessary to realise expression --generators / -g: find expressions producing a subset of given ids --expansion / -e: print a path containing id --graph: print a dot graph rooted at given ids diff --git a/src/nix.cc b/src/nix.cc index a7bcf7268..68d01f2f8 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -95,18 +95,22 @@ FSId maybeNormalise(const FSId & id, bool normalise) /* Perform various sorts of queries. */ static void opQuery(Strings opFlags, Strings opArgs) { - enum { qList, qRefs, qGenerators, qExpansion, qGraph + enum { qList, qRequisites, qGenerators, qExpansion, qGraph } query = qList; bool normalise = false; + bool includeExprs = true; + bool includeSuccessors = false; for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); i++) if (*i == "--list" || *i == "-l") query = qList; - else if (*i == "--refs" || *i == "-r") query = qRefs; + else if (*i == "--requisites" || *i == "-r") query = qRequisites; else if (*i == "--generators" || *i == "-g") query = qGenerators; else if (*i == "--expansion" || *i == "-e") query = qExpansion; else if (*i == "--graph") query = qGraph; else if (*i == "--normalise" || *i == "-n") normalise = true; + else if (*i == "--exclude-exprs") includeExprs = false; + else if (*i == "--include-successors") includeSuccessors = true; else throw UsageError(format("unknown flag `%1%'") % *i); switch (query) { @@ -126,13 +130,14 @@ static void opQuery(Strings opFlags, Strings opArgs) break; } - case qRefs: { + case qRequisites: { StringSet paths; for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) { - Strings paths2 = fstateRefs( - maybeNormalise(argToId(*i), normalise)); + Strings paths2 = fstateRequisites( + maybeNormalise(argToId(*i), normalise), + includeExprs, includeSuccessors); paths.insert(paths2.begin(), paths2.end()); } for (StringSet::iterator i = paths.begin(); diff --git a/src/normalise.cc b/src/normalise.cc index eefb790b6..5ef4d82ac 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -244,7 +244,8 @@ Strings fstatePaths(const FSId & id) } -static void fstateRefsSet(const FSId & id, StringSet & paths) +static void fstateRequisitesSet(const FSId & id, + bool includeExprs, bool includeSuccessors, StringSet & paths) { FState fs = parseFState(termFromId(id)); @@ -257,17 +258,28 @@ static void fstateRefsSet(const FSId & id, StringSet & paths) else if (fs.type == FState::fsDerive) { for (FSIds::iterator i = fs.derive.inputs.begin(); i != fs.derive.inputs.end(); i++) - fstateRefsSet(*i, paths); + fstateRequisitesSet(*i, + includeExprs, includeSuccessors, paths); } else abort(); + + if (includeExprs) + paths.insert(expandId(id)); + + string idSucc; + if (includeSuccessors && + queryDB(nixDB, dbSuccessors, id, idSucc)) + fstateRequisitesSet(parseHash(idSucc), + includeExprs, includeSuccessors, paths); } -Strings fstateRefs(const FSId & id) +Strings fstateRequisites(const FSId & id, + bool includeExprs, bool includeSuccessors) { StringSet paths; - fstateRefsSet(id, paths); + fstateRequisitesSet(id, includeExprs, includeSuccessors, paths); return Strings(paths.begin(), paths.end()); } diff --git a/src/normalise.hh b/src/normalise.hh index a5b45c861..98f58783e 100644 --- a/src/normalise.hh +++ b/src/normalise.hh @@ -15,8 +15,14 @@ void realiseSlice(const FSId & id, FSIdSet pending = FSIdSet()); fstate-expression. */ Strings fstatePaths(const FSId & id); -/* Get the list of paths referenced by the given fstate-expression. */ -Strings fstateRefs(const FSId & id); +/* Get the list of paths that are required to realise the given + expression. For a derive expression, this is the union of + requisites of the inputs; for a slice expression, it is the path of + each element in the slice. If `includeExprs' is true, include the + paths of the Nix expressions themselves. If `includeSuccessors' is + true, include the requisites of successors. */ +Strings fstateRequisites(const FSId & id, + bool includeExprs, bool includeSuccessors); /* Return the list of the ids of all known fstate-expressions whose output ids are completely contained in `ids'. */ From 40f32ae00ac60885e7c0d8dcb4522895cba8e550 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 29 Jul 2003 14:42:14 +0000 Subject: [PATCH 0176/6440] * Typo: if -> elsif. --- scripts/nix-collect-garbage.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/nix-collect-garbage.in b/scripts/nix-collect-garbage.in index eaa706578..16a0b09f5 100755 --- a/scripts/nix-collect-garbage.in +++ b/scripts/nix-collect-garbage.in @@ -10,7 +10,7 @@ my $invert = 0; foreach my $arg (@ARGV) { if ($arg eq "--keep-successors") { $keepsuccessors = 1; } - if ($arg eq "--invert") { $invert = 1; } + elsif ($arg eq "--invert") { $invert = 1; } else { die "unknown argument `$arg'" }; } From a01629894db0d961622b06c9c691c7cc0fbedff0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 29 Jul 2003 15:19:03 +0000 Subject: [PATCH 0177/6440] * Use `--query --requisites' and include successors when pushing. Don't use `--query --generators' anymore. --- scripts/nix-push.in | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/nix-push.in b/scripts/nix-push.in index 4a6426fe0..10efc48be 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -13,7 +13,7 @@ foreach my $id (@ARGV) { my @paths; - open PATHS, "nix --query --refs $id 2> /dev/null |" or die "nix -qr"; + open PATHS, "nix --query --requisites --include-successors $id 2> /dev/null |" or die "nix -qr"; while () { chomp; die "bad: $_" unless /^\//; @@ -22,13 +22,13 @@ foreach my $id (@ARGV) { close PATHS; # Also add all normal forms that are contained in these paths. - open PATHS, "nix --query --generators --path @paths |" or die "nix -qg"; - while () { - chomp; - die "bad: $_" unless /^\//; - push @paths, $_; - } - close PATHS; +# open PATHS, "nix --query --generators --path @paths |" or die "nix -qg"; +# while () { +# chomp; +# die "bad: $_" unless /^\//; +# push @paths, $_; +# } +# close PATHS; # For each path, create a Fix expression that turns the path into # a Nix archive. From 1cb030736ec1e844b3bfce32def3725c8a422a1c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 29 Jul 2003 17:56:39 +0000 Subject: [PATCH 0178/6440] * Bug: Fix does not allow empty names, so don't generate them. --- scripts/nix-pull.in | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index f584b6abd..ff85ff9a6 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -30,16 +30,16 @@ while () { next if $fn =~ /\//; next unless $fn =~ /^([0-9a-z]{32})-([0-9a-z]{32})(.*)\.nar\.bz2$/; my $hash = $1; - my $id = $2; - my $outname = $3; - my $fsid; - if ($outname =~ /^-/) { - next unless $outname =~ /^-((s-([0-9a-z]{32}))?.*)$/; - $outname = $1; - $fsid = $3; - } else { - $outname = ""; - } + my $id = $2; + my $outname = $3; + my $fsid; + if ($outname =~ /^-/) { + next unless $outname =~ /^-((s-([0-9a-z]{32}))?.*)$/; + $outname = $1; + $fsid = $3; + } else { + $outname = "unnamed"; + } print "registering $id -> $url/$fn\n"; From 83075304e5639dcab3d386eb7813c73b97c67685 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 30 Jul 2003 09:49:47 +0000 Subject: [PATCH 0179/6440] * Don't make the builder executable. --- src/exec.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/exec.cc b/src/exec.cc index 016dbaedd..5d7140827 100644 --- a/src/exec.cc +++ b/src/exec.cc @@ -86,10 +86,6 @@ void runProgram(const string & program, Environment env) if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1) throw SysError("cannot dup stderr into stdout"); - /* Make the program executable. !!! hack. */ - if (chmod(program.c_str(), 0755)) - throw SysError("cannot make program executable"); - /* Execute the program. This should not return. */ execle(program.c_str(), baseNameOf(program).c_str(), 0, env2); From aaee69cfdef287406715fb3befd7debd3a5c6ce9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 30 Jul 2003 10:14:58 +0000 Subject: [PATCH 0180/6440] * INSTALL_DATA -> INSTALL_PROGRAM to ensure that the execute bit remains set. --- corepkgs/fetchurl/Makefile.am | 2 +- corepkgs/nar/Makefile.am | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/corepkgs/fetchurl/Makefile.am b/corepkgs/fetchurl/Makefile.am index 44e07280f..72741ac23 100644 --- a/corepkgs/fetchurl/Makefile.am +++ b/corepkgs/fetchurl/Makefile.am @@ -3,7 +3,7 @@ all-local: fetchurl.sh install-exec-local: $(INSTALL) -d $(datadir)/fix/fetchurl $(INSTALL_DATA) fetchurl.fix $(datadir)/fix/fetchurl - $(INSTALL_DATA) fetchurl.sh $(datadir)/fix/fetchurl + $(INSTALL_PROGRAM) fetchurl.sh $(datadir)/fix/fetchurl include ../../substitute.mk diff --git a/corepkgs/nar/Makefile.am b/corepkgs/nar/Makefile.am index 4cb59ae39..f6eb27747 100644 --- a/corepkgs/nar/Makefile.am +++ b/corepkgs/nar/Makefile.am @@ -3,9 +3,9 @@ all-local: nar.sh unnar.sh install-exec-local: $(INSTALL) -d $(datadir)/fix/nar $(INSTALL_DATA) nar.fix $(datadir)/fix/nar - $(INSTALL_DATA) nar.sh $(datadir)/fix/nar + $(INSTALL_PROGRAM) nar.sh $(datadir)/fix/nar $(INSTALL_DATA) unnar.fix $(datadir)/fix/nar - $(INSTALL_DATA) unnar.sh $(datadir)/fix/nar + $(INSTALL_PROGRAM) unnar.sh $(datadir)/fix/nar include ../../substitute.mk From 2ac02440dc065d19e5bd00b9a0c538525e12909d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 30 Jul 2003 13:35:46 +0000 Subject: [PATCH 0181/6440] * Test cases for races. --- testpkgs/slow/slow-build.sh | 14 ++++++++++++++ testpkgs/slow/slow.fix | 5 +++++ 2 files changed, 19 insertions(+) create mode 100755 testpkgs/slow/slow-build.sh create mode 100644 testpkgs/slow/slow.fix diff --git a/testpkgs/slow/slow-build.sh b/testpkgs/slow/slow-build.sh new file mode 100755 index 000000000..a1bda1024 --- /dev/null +++ b/testpkgs/slow/slow-build.sh @@ -0,0 +1,14 @@ +#! /bin/sh + +echo "builder started..." + +mkdir $out + +for i in $(seq 1 30); do + echo $i + sleep 1 +done + +echo "done" > $out/bla + +echo "builder finished" diff --git a/testpkgs/slow/slow.fix b/testpkgs/slow/slow.fix new file mode 100644 index 000000000..5d019afbd --- /dev/null +++ b/testpkgs/slow/slow.fix @@ -0,0 +1,5 @@ +Package( + [ ("name", "slow") + , ("build", Relative("slow/slow-build.sh")) + ] +) From 64c617e9840b820b1d2d3ce2dd86a95506013b56 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 30 Jul 2003 14:40:18 +0000 Subject: [PATCH 0182/6440] * Directories for the manual. --- doc/manual/Makefile | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 doc/manual/Makefile diff --git a/doc/manual/Makefile b/doc/manual/Makefile new file mode 100644 index 000000000..8e5e1f0bc --- /dev/null +++ b/doc/manual/Makefile @@ -0,0 +1,3 @@ +DOCBOOK_DTD = /nix/current/xml/dtd/docbook +DOCBOOK_XSL =/nix/current/xml/xsl/docbook/ + From 26ff1cdf89cae33918dfdef2027d135e099ed3b2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 30 Jul 2003 14:40:46 +0000 Subject: [PATCH 0183/6440] * A better test case for Nix race conditions. --- testpkgs/slow2/slow-build.sh | 14 ++++++++++++++ testpkgs/slow2/slow.fix | 5 +++++ 2 files changed, 19 insertions(+) create mode 100755 testpkgs/slow2/slow-build.sh create mode 100644 testpkgs/slow2/slow.fix diff --git a/testpkgs/slow2/slow-build.sh b/testpkgs/slow2/slow-build.sh new file mode 100755 index 000000000..99e18c0fb --- /dev/null +++ b/testpkgs/slow2/slow-build.sh @@ -0,0 +1,14 @@ +#! /bin/sh + +echo "builder started..." + +for i in $(seq 1 30); do + echo $i + sleep 1 +done + +mkdir $out + +echo "done" > $out/bla + +echo "builder finished" diff --git a/testpkgs/slow2/slow.fix b/testpkgs/slow2/slow.fix new file mode 100644 index 000000000..52666951f --- /dev/null +++ b/testpkgs/slow2/slow.fix @@ -0,0 +1,5 @@ +Package( + [ ("name", "slow") + , ("build", Relative("slow2/slow-build.sh")) + ] +) From 9f4c19276d023433ea05f6cc3637b4327bd23fbe Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 31 Jul 2003 13:13:13 +0000 Subject: [PATCH 0184/6440] * Basic makefile. --- doc/manual/Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/manual/Makefile b/doc/manual/Makefile index 8e5e1f0bc..95b472eee 100644 --- a/doc/manual/Makefile +++ b/doc/manual/Makefile @@ -1,3 +1,10 @@ DOCBOOK_DTD = /nix/current/xml/dtd/docbook DOCBOOK_XSL =/nix/current/xml/xsl/docbook/ +check: + SP_CHARSET_FIXED=YES SP_ENCODING=XML \ + nsgmls -wxml -c /usr/share/sgml/opensp/xml.soc -c $(DOCBOOK_DTD)/docbook.cat -ges book.xml + +html: + mkdir -p out + xsltproc --output out/book.html $(DOCBOOK_XSL)/html/docbook.xsl book.xml From 758bd4673a3553fcbd78c8f895d6efe839d3d538 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 31 Jul 2003 13:13:27 +0000 Subject: [PATCH 0185/6440] * Set execute bit. --- src/test-builder-1.sh | 0 src/test-builder-2.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 src/test-builder-1.sh mode change 100644 => 100755 src/test-builder-2.sh diff --git a/src/test-builder-1.sh b/src/test-builder-1.sh old mode 100644 new mode 100755 diff --git a/src/test-builder-2.sh b/src/test-builder-2.sh old mode 100644 new mode 100755 From 4a013962bdd08ee0cf285136e4eca0f2c9c76b98 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 31 Jul 2003 13:47:13 +0000 Subject: [PATCH 0186/6440] * Started using Berkeley DB environments. This is necessary for transaction support (but we don't actually use transactions yet). --- src/Makefile.am | 1 + src/db.cc | 170 +++++++++++++++++++++++++---------- src/db.hh | 71 ++++++++++++--- src/fix.cc | 2 + src/globals.cc | 19 ++-- src/globals.hh | 17 ++-- src/nix.cc | 2 + src/normalise.cc | 12 +-- src/shared.cc | 2 +- src/store.cc | 57 ++++++------ src/test.cc | 6 +- testpkgs/slow2/slow-build.sh | 4 +- 12 files changed, 256 insertions(+), 107 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index cc51eff36..847f3eb19 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -42,6 +42,7 @@ nix.o: nix-help.txt.hh install-data-local: $(INSTALL) -d $(localstatedir)/nix + $(INSTALL) -d $(localstatedir)/nix/db $(INSTALL) -d $(localstatedir)/nix/links ln -sf $(localstatedir)/nix/links/current $(prefix)/current $(INSTALL) -d $(localstatedir)/log/nix diff --git a/src/db.cc b/src/db.cc index a8741342c..4362fe405 100644 --- a/src/db.cc +++ b/src/db.cc @@ -3,66 +3,142 @@ #include -#include - -/* Wrapper classes that ensures that the database is closed upon - object destruction. */ -class Db2 : public Db +/* Wrapper class to ensure proper destruction. */ +class DestroyDb { + Db * db; public: - Db2(DbEnv *env, u_int32_t flags) : Db(env, flags) { } - ~Db2() { close(0); } + DestroyDb(Db * _db) : db(_db) { } + ~DestroyDb() { db->close(0); delete db; } }; -class DbcClose +class DestroyDbc { - Dbc * cursor; + Dbc * dbc; public: - DbcClose(Dbc * c) : cursor(c) { } - ~DbcClose() { cursor->close(); } + DestroyDbc(Dbc * _dbc) : dbc(_dbc) { } + ~DestroyDbc() { dbc->close(); /* close() frees dbc */ } }; -static auto_ptr openDB(const string & filename, const string & dbname, - bool readonly) -{ - auto_ptr db(new Db2(0, 0)); - - db->open(filename.c_str(), dbname.c_str(), - DB_HASH, readonly ? DB_RDONLY : DB_CREATE, 0666); - - return db; -} - - static void rethrow(DbException & e) { throw Error(e.what()); } -void createDB(const string & filename, const string & dbname) +Transaction::Transaction() + : txn(0) +{ +} + + +Transaction::Transaction(Database & db) +{ + db.requireEnv(); + db.env->txn_begin(0, &txn, 0); +} + + +Transaction::~Transaction() +{ + if (txn) { + txn->abort(); + txn = 0; + } +} + + +void Transaction::commit() +{ + if (!txn) throw Error("commit called on null transaction"); + txn->commit(0); + txn = 0; +} + + +void Database::requireEnv() +{ + if (!env) throw Error("database environment not open"); +} + + +Db * Database::openDB(const Transaction & txn, + const string & table, bool create) +{ + requireEnv(); + + Db * db = new Db(env, 0); + + try { + // !!! fixme when switching to BDB 4.1: use txn. + db->open(table.c_str(), 0, + DB_HASH, create ? DB_CREATE : 0, 0666); + } catch (...) { + delete db; + throw; + } + + return db; +} + + +Database::Database() + : env(0) +{ +} + + +Database::~Database() +{ + if (env) { + env->close(0); + delete env; + } +} + + +void Database::open(const string & path) { try { - openDB(filename, dbname, false); + + if (env) throw Error(format("environment already open")); + + env = new DbEnv(0); + + debug("foo" + path); + env->open(path.c_str(), + DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | + DB_CREATE, + 0666); + } catch (DbException e) { rethrow(e); } } -bool queryDB(const string & filename, const string & dbname, +void Database::createTable(const string & table) +{ + try { + Db * db = openDB(noTxn, table, true); + DestroyDb destroyDb(db); + } catch (DbException e) { rethrow(e); } +} + + +bool Database::queryString(const Transaction & txn, const string & table, const string & key, string & data) { try { - int err; - auto_ptr db = openDB(filename, dbname, true); + Db * db = openDB(txn, table, false); + DestroyDb destroyDb(db); Dbt kt((void *) key.c_str(), key.length()); Dbt dt; - err = db->get(0, &kt, &dt, 0); + int err = db->get(txn.txn, &kt, &dt, 0); if (err) return false; if (!dt.get_data()) @@ -76,12 +152,12 @@ bool queryDB(const string & filename, const string & dbname, } -bool queryListDB(const string & filename, const string & dbname, +bool Database::queryStrings(const Transaction & txn, const string & table, const string & key, Strings & data) { string d; - if (!queryDB(filename, dbname, key, d)) + if (!queryString(txn, table, key, d)) return false; string::iterator it = d.begin(); @@ -110,19 +186,21 @@ bool queryListDB(const string & filename, const string & dbname, } -void setDB(const string & filename, const string & dbname, +void Database::setString(const Transaction & txn, const string & table, const string & key, const string & data) { try { - auto_ptr db = openDB(filename, dbname, false); + Db * db = openDB(txn, table, false); + DestroyDb destroyDb(db); + Dbt kt((void *) key.c_str(), key.length()); Dbt dt((void *) data.c_str(), data.length()); - db->put(0, &kt, &dt, 0); + db->put(txn.txn, &kt, &dt, 0); } catch (DbException e) { rethrow(e); } } -void setListDB(const string & filename, const string & dbname, +void Database::setStrings(const Transaction & txn, const string & table, const string & key, const Strings & data) { string d; @@ -141,34 +219,36 @@ void setListDB(const string & filename, const string & dbname, d += s; } - setDB(filename, dbname, key, d); + setString(txn, table, key, d); } -void delDB(const string & filename, const string & dbname, +void Database::delPair(const Transaction & txn, const string & table, const string & key) { try { - auto_ptr db = openDB(filename, dbname, false); + Db * db = openDB(txn, table, false); + DestroyDb destroyDb(db); Dbt kt((void *) key.c_str(), key.length()); - db->del(0, &kt, 0); + db->del(txn.txn, &kt, 0); } catch (DbException e) { rethrow(e); } } -void enumDB(const string & filename, const string & dbname, +void Database::enumTable(const Transaction & txn, const string & table, Strings & keys) { try { - auto_ptr db = openDB(filename, dbname, true); + Db * db = openDB(txn, table, false); + DestroyDb destroyDb(db); - Dbc * cursor; - db->cursor(0, &cursor, 0); - DbcClose cursorCloser(cursor); + Dbc * dbc; + db->cursor(0, &dbc, 0); + DestroyDbc destroyDbc(dbc); Dbt kt, dt; - while (cursor->get(&kt, &dt, DB_NEXT) != DB_NOTFOUND) + while (dbc->get(&kt, &dt, DB_NEXT) != DB_NOTFOUND) keys.push_back( string((char *) kt.get_data(), kt.get_size())); diff --git a/src/db.hh b/src/db.hh index 6f13eb30c..57b6e4d8e 100644 --- a/src/db.hh +++ b/src/db.hh @@ -4,28 +4,73 @@ #include #include +#include + #include "util.hh" using namespace std; -void createDB(const string & filename, const string & dbname); -bool queryDB(const string & filename, const string & dbname, - const string & key, string & data); +class Database; -bool queryListDB(const string & filename, const string & dbname, - const string & key, Strings & data); -void setDB(const string & filename, const string & dbname, - const string & key, const string & data); +class Transaction +{ + friend class Database; -void setListDB(const string & filename, const string & dbname, - const string & key, const Strings & data); +private: + DbTxn * txn; + +public: + Transaction(); + Transaction(Database & _db); + ~Transaction(); -void delDB(const string & filename, const string & dbname, - const string & key); + void commit(); +}; + + +#define noTxn Transaction() + + +class Database +{ + friend class Transaction; + +private: + DbEnv * env; + + void requireEnv(); + + Db * openDB(const Transaction & txn, + const string & table, bool create); + +public: + Database(); + ~Database(); + + void open(const string & path); + + void createTable(const string & table); + + bool queryString(const Transaction & txn, const string & table, + const string & key, string & data); + + bool queryStrings(const Transaction & txn, const string & table, + const string & key, Strings & data); + + void setString(const Transaction & txn, const string & table, + const string & key, const string & data); + + void setStrings(const Transaction & txn, const string & table, + const string & key, const Strings & data); + + void delPair(const Transaction & txn, const string & table, + const string & key); + + void enumTable(const Transaction & txn, const string & table, + Strings & keys); +}; -void enumDB(const string & filename, const string & dbname, - Strings & keys); #endif /* !__DB_H */ diff --git a/src/fix.cc b/src/fix.cc index 9b0d95912..4235165cc 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -301,6 +301,8 @@ static Expr evalFile(EvalState & state, string relPath) void run(Strings args) { + openDB(); + EvalState state; Strings files; diff --git a/src/globals.cc b/src/globals.cc index 1edb38f74..8c3ec3828 100644 --- a/src/globals.cc +++ b/src/globals.cc @@ -2,6 +2,9 @@ #include "db.hh" +Database nixDB; + + string dbPath2Id = "path2id"; string dbId2Paths = "id2paths"; string dbSuccessors = "successors"; @@ -11,13 +14,19 @@ string dbSubstitutes = "substitutes"; string nixStore = "/UNINIT"; string nixDataDir = "/UNINIT"; string nixLogDir = "/UNINIT"; -string nixDB = "/UNINIT"; +string nixDBPath = "/UNINIT"; + + +void openDB() +{ + nixDB.open(nixDBPath); +} void initDB() { - createDB(nixDB, dbPath2Id); - createDB(nixDB, dbId2Paths); - createDB(nixDB, dbSuccessors); - createDB(nixDB, dbSubstitutes); + nixDB.createTable(dbPath2Id); + nixDB.createTable(dbId2Paths); + nixDB.createTable(dbSuccessors); + nixDB.createTable(dbSubstitutes); } diff --git a/src/globals.hh b/src/globals.hh index fbb020df7..9df827622 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -3,10 +3,15 @@ #include +#include "db.hh" + using namespace std; -/* Database names. */ +extern Database nixDB; + + +/* Database tables. */ /* dbPath2Id :: Path -> FSId @@ -60,12 +65,14 @@ extern string nixDataDir; /* !!! fix */ /* nixLogDir is the directory where we log various operations. */ extern string nixLogDir; -/* nixDB is the file name of the Berkeley DB database where we - maintain the dbXXX mappings. */ -extern string nixDB; +/* nixDBPath is the path name of our Berkeley DB environment. */ +extern string nixDBPath; -/* Initialize the databases. */ +/* Open the database environment. */ +void openDB(); + +/* Create the required database tables. */ void initDB(); diff --git a/src/nix.cc b/src/nix.cc index 68d01f2f8..42cc4a87c 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -335,6 +335,8 @@ static void opVerify(Strings opFlags, Strings opArgs) list. */ void run(Strings args) { + openDB(); + Strings opFlags, opArgs; Operation op = 0; diff --git a/src/normalise.cc b/src/normalise.cc index 5ef4d82ac..5a8cb9a0d 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -9,7 +9,7 @@ void registerSuccessor(const FSId & id1, const FSId & id2) { - setDB(nixDB, dbSuccessors, id1, id2); + nixDB.setString(noTxn, dbSuccessors, id1, id2); } @@ -31,7 +31,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* Try to substitute $id$ by any known successors in order to speed up the rewrite process. */ string idSucc; - while (queryDB(nixDB, dbSuccessors, id, idSucc)) { + while (nixDB.queryString(noTxn, dbSuccessors, id, idSucc)) { debug(format("successor %1% -> %2%") % (string) id % idSucc); id = parseHash(idSucc); } @@ -191,7 +191,7 @@ void realiseSlice(const FSId & id, FSIdSet pending) { SliceElem elem = *i; string id; - if (!queryDB(nixDB, dbPath2Id, elem.path, id)) { + if (!nixDB.queryString(noTxn, dbPath2Id, elem.path, id)) { if (pathExists(elem.path)) throw Error(format("path `%1%' obstructed") % elem.path); missing = true; @@ -269,7 +269,7 @@ static void fstateRequisitesSet(const FSId & id, string idSucc; if (includeSuccessors && - queryDB(nixDB, dbSuccessors, id, idSucc)) + nixDB.queryString(noTxn, dbSuccessors, id, idSucc)) fstateRequisitesSet(parseHash(idSucc), includeExprs, includeSuccessors, paths); } @@ -293,13 +293,13 @@ FSIds findGenerators(const FSIds & _ids) mappings, since we know that those are Nix expressions. */ Strings sucs; - enumDB(nixDB, dbSuccessors, sucs); + nixDB.enumTable(noTxn, dbSuccessors, sucs); for (Strings::iterator i = sucs.begin(); i != sucs.end(); i++) { string s; - if (!queryDB(nixDB, dbSuccessors, *i, s)) continue; + if (!nixDB.queryString(noTxn, dbSuccessors, *i, s)) continue; FSId id = parseHash(s); FState fs; diff --git a/src/shared.cc b/src/shared.cc index 75145f6db..c0f07e955 100644 --- a/src/shared.cc +++ b/src/shared.cc @@ -19,7 +19,7 @@ static void initAndRun(int argc, char * * argv) nixStore = NIX_STORE_DIR; nixDataDir = NIX_DATA_DIR; nixLogDir = NIX_LOG_DIR; - nixDB = (string) NIX_STATE_DIR + "/nixstate.db"; + nixDBPath = (string) NIX_STATE_DIR + "/db"; /* Put the arguments in a vector. */ Strings args; diff --git a/src/store.cc b/src/store.cc index 3493ba384..3dc625a7b 100644 --- a/src/store.cc +++ b/src/store.cc @@ -96,7 +96,7 @@ void registerSubstitute(const FSId & srcId, const FSId & subId) /* For now, accept only one substitute per id. */ Strings subs; subs.push_back(subId); - setListDB(nixDB, dbSubstitutes, srcId, subs); + nixDB.setStrings(noTxn, dbSubstitutes, srcId, subs); } @@ -104,10 +104,10 @@ void registerPath(const string & _path, const FSId & id) { string path(canonPath(_path)); - setDB(nixDB, dbPath2Id, path, id); + nixDB.setString(noTxn, dbPath2Id, path, id); Strings paths; - queryListDB(nixDB, dbId2Paths, id, paths); /* non-existence = ok */ + nixDB.queryStrings(noTxn, dbId2Paths, id, paths); /* non-existence = ok */ for (Strings::iterator it = paths.begin(); it != paths.end(); it++) @@ -115,7 +115,7 @@ void registerPath(const string & _path, const FSId & id) paths.push_back(path); - setListDB(nixDB, dbId2Paths, id, paths); + nixDB.setStrings(noTxn, dbId2Paths, id, paths); } @@ -124,16 +124,15 @@ void unregisterPath(const string & _path) string path(canonPath(_path)); string _id; - if (!queryDB(nixDB, dbPath2Id, path, _id)) - return; + if (!nixDB.queryString(noTxn, dbPath2Id, path, _id)) return; FSId id(parseHash(_id)); - delDB(nixDB, dbPath2Id, path); + nixDB.delPair(noTxn, dbPath2Id, path); /* begin transaction */ Strings paths, paths2; - queryListDB(nixDB, dbId2Paths, id, paths); /* non-existence = ok */ + nixDB.queryStrings(noTxn, dbId2Paths, id, paths); /* non-existence = ok */ bool changed = false; for (Strings::iterator it = paths.begin(); @@ -141,7 +140,7 @@ void unregisterPath(const string & _path) if (*it != path) paths2.push_back(*it); else changed = true; if (changed) - setListDB(nixDB, dbId2Paths, id, paths2); + nixDB.setStrings(noTxn, dbId2Paths, id, paths2); /* end transaction */ @@ -151,7 +150,7 @@ void unregisterPath(const string & _path) bool queryPathId(const string & path, FSId & id) { string s; - if (!queryDB(nixDB, dbPath2Id, absPath(path), s)) return false; + if (!nixDB.queryString(noTxn, dbPath2Id, absPath(path), s)) return false; id = parseHash(s); return true; } @@ -174,7 +173,7 @@ string expandId(const FSId & id, const string & target, if (!target.empty() && !isInPrefix(target, prefix)) abort(); - queryListDB(nixDB, dbId2Paths, id, paths); + nixDB.queryStrings(noTxn, dbId2Paths, id, paths); /* Pick one equal to `target'. */ if (!target.empty()) { @@ -212,7 +211,7 @@ string expandId(const FSId & id, const string & target, /* Try to realise the substitutes, but only if this id is not already being realised by a substitute. */ Strings subs; - queryListDB(nixDB, dbSubstitutes, id, subs); /* non-existence = ok */ + nixDB.queryStrings(noTxn, dbSubstitutes, id, subs); /* non-existence = ok */ for (Strings::iterator it = subs.begin(); it != subs.end(); it++) { FSId subId = parseHash(*it); @@ -264,7 +263,7 @@ void deleteFromStore(const string & path) void verifyStore() { Strings paths; - enumDB(nixDB, dbPath2Id, paths); + nixDB.enumTable(noTxn, dbPath2Id, paths); for (Strings::iterator i = paths.begin(); i != paths.end(); i++) @@ -278,10 +277,10 @@ void verifyStore() else { string id; - if (!queryDB(nixDB, dbPath2Id, path, id)) abort(); + if (!nixDB.queryString(noTxn, dbPath2Id, path, id)) abort(); Strings idPaths; - queryListDB(nixDB, dbId2Paths, id, idPaths); + nixDB.queryStrings(noTxn, dbId2Paths, id, idPaths); bool found = false; for (Strings::iterator j = idPaths.begin(); @@ -298,11 +297,11 @@ void verifyStore() debug(format("reverse mapping for path `%1%' missing") % path); } - if (erase) delDB(nixDB, dbPath2Id, path); + if (erase) nixDB.delPair(noTxn, dbPath2Id, path); } Strings ids; - enumDB(nixDB, dbId2Paths, ids); + nixDB.enumTable(noTxn, dbId2Paths, ids); for (Strings::iterator i = ids.begin(); i != ids.end(); i++) @@ -310,13 +309,13 @@ void verifyStore() FSId id = parseHash(*i); Strings idPaths; - queryListDB(nixDB, dbId2Paths, id, idPaths); + nixDB.queryStrings(noTxn, dbId2Paths, id, idPaths); for (Strings::iterator j = idPaths.begin(); j != idPaths.end(); ) { string id2; - if (!queryDB(nixDB, dbPath2Id, *j, id2) || + if (!nixDB.queryString(noTxn, dbPath2Id, *j, id2) || id != parseHash(id2)) { debug(format("erasing path `%1%' from mapping for id %2%") % *j % (string) id); @@ -324,12 +323,12 @@ void verifyStore() } else j++; } - setListDB(nixDB, dbId2Paths, id, idPaths); + nixDB.setStrings(noTxn, dbId2Paths, id, idPaths); } Strings subs; - enumDB(nixDB, dbSubstitutes, subs); + nixDB.enumTable(noTxn, dbSubstitutes, subs); for (Strings::iterator i = subs.begin(); i != subs.end(); i++) @@ -337,7 +336,7 @@ void verifyStore() FSId srcId = parseHash(*i); Strings subIds; - queryListDB(nixDB, dbSubstitutes, srcId, subIds); + nixDB.queryStrings(noTxn, dbSubstitutes, srcId, subIds); for (Strings::iterator j = subIds.begin(); j != subIds.end(); ) @@ -345,7 +344,7 @@ void verifyStore() FSId subId = parseHash(*j); Strings subPaths; - queryListDB(nixDB, dbId2Paths, subId, subPaths); + nixDB.queryStrings(noTxn, dbId2Paths, subId, subPaths); if (subPaths.size() == 0) { debug(format("erasing substitute %1% for %2%") % (string) subId % (string) srcId); @@ -353,11 +352,11 @@ void verifyStore() } else j++; } - setListDB(nixDB, dbSubstitutes, srcId, subIds); + nixDB.setStrings(noTxn, dbSubstitutes, srcId, subIds); } Strings sucs; - enumDB(nixDB, dbSuccessors, sucs); + nixDB.enumTable(noTxn, dbSuccessors, sucs); for (Strings::iterator i = sucs.begin(); i != sucs.end(); i++) @@ -365,17 +364,17 @@ void verifyStore() FSId id1 = parseHash(*i); string id2; - if (!queryDB(nixDB, dbSuccessors, id1, id2)) abort(); + if (!nixDB.queryString(noTxn, dbSuccessors, id1, id2)) abort(); Strings id2Paths; - queryListDB(nixDB, dbId2Paths, id2, id2Paths); + nixDB.queryStrings(noTxn, dbId2Paths, id2, id2Paths); if (id2Paths.size() == 0) { Strings id2Subs; - queryListDB(nixDB, dbSubstitutes, id2, id2Subs); + nixDB.queryStrings(noTxn, dbSubstitutes, id2, id2Subs); if (id2Subs.size() == 0) { debug(format("successor %1% for %2% missing") % id2 % (string) id1); - delDB(nixDB, dbSuccessors, (string) id1); + nixDB.delPair(noTxn, dbSuccessors, (string) id1); } } } diff --git a/src/test.cc b/src/test.cc index cff094e2f..d640e335a 100644 --- a/src/test.cc +++ b/src/test.cc @@ -51,6 +51,8 @@ struct MySource : RestoreSource void runTests() { + verbosity = (Verbosity) 100; + /* Hashing. */ string s = "0b0ffd0538622bfe20b92c4aa57254d9"; Hash h = parseHash(s); @@ -94,14 +96,16 @@ void runTests() /* Set up the test environment. */ mkdir("scratch", 0777); + mkdir("scratch/db", 0777); string testDir = absPath("scratch"); cout << testDir << endl; nixStore = testDir; nixLogDir = testDir; - nixDB = testDir + "/db"; + nixDBPath = testDir + "/db"; + openDB(); initDB(); /* Expression evaluation. */ diff --git a/testpkgs/slow2/slow-build.sh b/testpkgs/slow2/slow-build.sh index 99e18c0fb..4a9d7d482 100755 --- a/testpkgs/slow2/slow-build.sh +++ b/testpkgs/slow2/slow-build.sh @@ -2,13 +2,13 @@ echo "builder started..." -for i in $(seq 1 30); do +for i in $(seq 1 10); do echo $i sleep 1 done mkdir $out -echo "done" > $out/bla +echo "done" >> $out/bla echo "builder finished" From 177a7782aee4c4789ad5377b5993bfa0b692282e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 31 Jul 2003 14:28:49 +0000 Subject: [PATCH 0187/6440] * Use a more reasonable log file size (256 KB instead of 10 MB). * Checkpoint on exit. --- src/db.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/db.cc b/src/db.cc index 4362fe405..77ad93a86 100644 --- a/src/db.cc +++ b/src/db.cc @@ -94,6 +94,8 @@ Database::Database() Database::~Database() { if (env) { + debug(format("closing database environment")); + env->txn_checkpoint(0, 0, 0); env->close(0); delete env; } @@ -108,12 +110,14 @@ void Database::open(const string & path) env = new DbEnv(0); - debug("foo" + path); + env->set_lg_bsize(32 * 1024); /* default */ + env->set_lg_max(256 * 1024); /* must be > 4 * lg_bsize */ + env->open(path.c_str(), DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | DB_CREATE, 0666); - + } catch (DbException e) { rethrow(e); } } From 06d3d7355d1b0ec05e61d2e7fe67f8d7153c1ff9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 31 Jul 2003 16:05:35 +0000 Subject: [PATCH 0188/6440] * Enclose most operations that update the database in transactions. * Open all database tables (Db objects) at initialisation time, not every time they are used. This is necessary because tables have to outlive all transactions that refer to them. --- src/db.cc | 131 +++++++++++++++++++++++++++-------------------- src/db.hh | 25 +++++---- src/globals.cc | 16 +++--- src/globals.hh | 8 +-- src/normalise.cc | 4 +- src/store.cc | 57 ++++++++++++++------- 6 files changed, 145 insertions(+), 96 deletions(-) diff --git a/src/db.cc b/src/db.cc index 77ad93a86..61ecb203a 100644 --- a/src/db.cc +++ b/src/db.cc @@ -5,15 +5,6 @@ /* Wrapper class to ensure proper destruction. */ -class DestroyDb -{ - Db * db; -public: - DestroyDb(Db * _db) : db(_db) { } - ~DestroyDb() { db->close(0); delete db; } -}; - - class DestroyDbc { Dbc * dbc; @@ -38,24 +29,39 @@ Transaction::Transaction() Transaction::Transaction(Database & db) { db.requireEnv(); - db.env->txn_begin(0, &txn, 0); + try { + db.env->txn_begin(0, &txn, 0); + } catch (DbException e) { rethrow(e); } } Transaction::~Transaction() { - if (txn) { - txn->abort(); - txn = 0; - } + if (txn) abort(); } void Transaction::commit() { if (!txn) throw Error("commit called on null transaction"); - txn->commit(0); + 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); } } @@ -65,28 +71,18 @@ void Database::requireEnv() } -Db * Database::openDB(const Transaction & txn, - const string & table, bool create) +Db * Database::getDb(TableId table) { - requireEnv(); - - Db * db = new Db(env, 0); - - try { - // !!! fixme when switching to BDB 4.1: use txn. - db->open(table.c_str(), 0, - DB_HASH, create ? DB_CREATE : 0, 0666); - } catch (...) { - delete db; - throw; - } - - return db; + map::iterator i = tables.find(table); + if (i == tables.end()) + throw Error("unknown table id"); + return i->second; } Database::Database() : env(0) + , nextId(1) { } @@ -95,8 +91,23 @@ Database::~Database() { if (env) { debug(format("closing database environment")); - env->txn_checkpoint(0, 0, 0); - env->close(0); + + try { + + for (map::iterator i = tables.begin(); + i != tables.end(); i++) + { + debug(format("closing table %1%") % i->first); + Db * db = i->second; + db->close(0); + delete db; + } + + env->txn_checkpoint(0, 0, 0); + env->close(0); + + } catch (DbException e) { rethrow(e); } + delete env; } } @@ -112,8 +123,9 @@ void Database::open(const string & path) env->set_lg_bsize(32 * 1024); /* default */ env->set_lg_max(256 * 1024); /* must be > 4 * lg_bsize */ + env->set_lk_detect(DB_LOCK_DEFAULT); - env->open(path.c_str(), + env->open(path.c_str(), DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | DB_CREATE, 0666); @@ -122,22 +134,36 @@ void Database::open(const string & path) } -void Database::createTable(const string & table) +TableId Database::openTable(const string & tableName) { + requireEnv(); + TableId table = nextId++; + try { - Db * db = openDB(noTxn, table, true); - DestroyDb destroyDb(db); + + Db * db = new Db(env, 0); + + try { + // !!! fixme when switching to BDB 4.1: use txn. + db->open(tableName.c_str(), 0, DB_HASH, DB_CREATE, 0666); + } catch (...) { + delete db; + throw; + } + + tables[table] = db; + } catch (DbException e) { rethrow(e); } + + return table; } -bool Database::queryString(const Transaction & txn, const string & table, +bool Database::queryString(const Transaction & txn, TableId table, const string & key, string & data) { try { - - Db * db = openDB(txn, table, false); - DestroyDb destroyDb(db); + Db * db = getDb(table); Dbt kt((void *) key.c_str(), key.length()); Dbt dt; @@ -156,7 +182,7 @@ bool Database::queryString(const Transaction & txn, const string & table, } -bool Database::queryStrings(const Transaction & txn, const string & table, +bool Database::queryStrings(const Transaction & txn, TableId table, const string & key, Strings & data) { string d; @@ -190,13 +216,11 @@ bool Database::queryStrings(const Transaction & txn, const string & table, } -void Database::setString(const Transaction & txn, const string & table, +void Database::setString(const Transaction & txn, TableId table, const string & key, const string & data) { try { - Db * db = openDB(txn, table, false); - DestroyDb destroyDb(db); - + 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); @@ -204,7 +228,7 @@ void Database::setString(const Transaction & txn, const string & table, } -void Database::setStrings(const Transaction & txn, const string & table, +void Database::setStrings(const Transaction & txn, TableId table, const string & key, const Strings & data) { string d; @@ -227,28 +251,25 @@ void Database::setStrings(const Transaction & txn, const string & table, } -void Database::delPair(const Transaction & txn, const string & table, +void Database::delPair(const Transaction & txn, TableId table, const string & key) { try { - Db * db = openDB(txn, table, false); - DestroyDb destroyDb(db); + Db * db = getDb(table); Dbt kt((void *) key.c_str(), key.length()); db->del(txn.txn, &kt, 0); } catch (DbException e) { rethrow(e); } } -void Database::enumTable(const Transaction & txn, const string & table, +void Database::enumTable(const Transaction & txn, TableId table, Strings & keys) { try { - - Db * db = openDB(txn, table, false); - DestroyDb destroyDb(db); + Db * db = getDb(table); Dbc * dbc; - db->cursor(0, &dbc, 0); + db->cursor(txn.txn, &dbc, 0); DestroyDbc destroyDbc(dbc); Dbt kt, dt; diff --git a/src/db.hh b/src/db.hh index 57b6e4d8e..4bac943e5 100644 --- a/src/db.hh +++ b/src/db.hh @@ -3,6 +3,7 @@ #include #include +#include #include @@ -26,6 +27,7 @@ public: Transaction(Database & _db); ~Transaction(); + void abort(); void commit(); }; @@ -33,6 +35,9 @@ public: #define noTxn Transaction() +typedef unsigned int TableId; /* table handles */ + + class Database { friend class Transaction; @@ -40,10 +45,12 @@ class Database private: DbEnv * env; + TableId nextId; + map tables; + void requireEnv(); - Db * openDB(const Transaction & txn, - const string & table, bool create); + Db * getDb(TableId table); public: Database(); @@ -51,24 +58,24 @@ public: void open(const string & path); - void createTable(const string & table); + TableId openTable(const string & table); - bool queryString(const Transaction & txn, const string & table, + bool queryString(const Transaction & txn, TableId table, const string & key, string & data); - bool queryStrings(const Transaction & txn, const string & table, + bool queryStrings(const Transaction & txn, TableId table, const string & key, Strings & data); - void setString(const Transaction & txn, const string & table, + void setString(const Transaction & txn, TableId table, const string & key, const string & data); - void setStrings(const Transaction & txn, const string & table, + void setStrings(const Transaction & txn, TableId table, const string & key, const Strings & data); - void delPair(const Transaction & txn, const string & table, + void delPair(const Transaction & txn, TableId table, const string & key); - void enumTable(const Transaction & txn, const string & table, + void enumTable(const Transaction & txn, TableId table, Strings & keys); }; diff --git a/src/globals.cc b/src/globals.cc index 8c3ec3828..1ec0c4f9b 100644 --- a/src/globals.cc +++ b/src/globals.cc @@ -5,10 +5,10 @@ Database nixDB; -string dbPath2Id = "path2id"; -string dbId2Paths = "id2paths"; -string dbSuccessors = "successors"; -string dbSubstitutes = "substitutes"; +TableId dbPath2Id; +TableId dbId2Paths; +TableId dbSuccessors; +TableId dbSubstitutes; string nixStore = "/UNINIT"; @@ -20,13 +20,13 @@ string nixDBPath = "/UNINIT"; void openDB() { nixDB.open(nixDBPath); + dbPath2Id = nixDB.openTable("path2id"); + dbId2Paths = nixDB.openTable("id2paths"); + dbSuccessors = nixDB.openTable("successors"); + dbSubstitutes = nixDB.openTable("substitutes"); } void initDB() { - nixDB.createTable(dbPath2Id); - nixDB.createTable(dbId2Paths); - nixDB.createTable(dbSuccessors); - nixDB.createTable(dbSubstitutes); } diff --git a/src/globals.hh b/src/globals.hh index 9df827622..2c4d33920 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -17,13 +17,13 @@ extern Database nixDB; Each pair (p, id) records that path $p$ contains an expansion of $id$. */ -extern string dbPath2Id; +extern TableId dbPath2Id; /* dbId2Paths :: FSId -> [Path] A mapping from ids to lists of paths. */ -extern string dbId2Paths; +extern TableId dbId2Paths; /* dbSuccessors :: FSId -> FSId @@ -35,7 +35,7 @@ extern string dbId2Paths; Note that a term $y$ is successor of $x$ iff there exists a sequence of rewrite steps that rewrites $x$ into $y$. */ -extern string dbSuccessors; +extern TableId dbSuccessors; /* dbSubstitutes :: FSId -> [FSId] @@ -51,7 +51,7 @@ extern string dbSuccessors; this case might be an fstate expression that fetches the Nix archive. */ -extern string dbSubstitutes; +extern TableId dbSubstitutes; /* Path names. */ diff --git a/src/normalise.cc b/src/normalise.cc index 5a8cb9a0d..e8fc6fc55 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -9,7 +9,9 @@ void registerSuccessor(const FSId & id1, const FSId & id2) { - nixDB.setString(noTxn, dbSuccessors, id1, id2); + Transaction txn(nixDB); + nixDB.setString(txn, dbSuccessors, id1, id2); + txn.commit(); } diff --git a/src/store.cc b/src/store.cc index 3dc625a7b..6d7861d0b 100644 --- a/src/store.cc +++ b/src/store.cc @@ -32,6 +32,8 @@ struct CopySource : RestoreSource void copyPath(string src, string dst) { + debug(format("copying `%1%' to `%2%'") % src % dst); + /* Unfortunately C++ doesn't support coprocedures, so we have no nice way to chain CopySink and CopySource together. Instead we fork off a child to run the sink. (Fork-less platforms should @@ -96,54 +98,69 @@ void registerSubstitute(const FSId & srcId, const FSId & subId) /* For now, accept only one substitute per id. */ Strings subs; subs.push_back(subId); - nixDB.setStrings(noTxn, dbSubstitutes, srcId, subs); + + Transaction txn(nixDB); + nixDB.setStrings(txn, dbSubstitutes, srcId, subs); + txn.commit(); } void registerPath(const string & _path, const FSId & id) { string path(canonPath(_path)); + Transaction txn(nixDB); - nixDB.setString(noTxn, dbPath2Id, path, id); + debug(format("registering path `%1%' with id %2%") + % path % (string) id); + + string oldId; + if (nixDB.queryString(txn, dbPath2Id, path, oldId)) { + txn.abort(); + if (id != parseHash(oldId)) + throw Error(format("path `%1%' already contains id %2%") + % path % oldId); + return; + } + + nixDB.setString(txn, dbPath2Id, path, id); Strings paths; - nixDB.queryStrings(noTxn, dbId2Paths, id, paths); /* non-existence = ok */ - - for (Strings::iterator it = paths.begin(); - it != paths.end(); it++) - if (*it == path) return; + nixDB.queryStrings(txn, dbId2Paths, id, paths); /* non-existence = ok */ paths.push_back(path); - nixDB.setStrings(noTxn, dbId2Paths, id, paths); + nixDB.setStrings(txn, dbId2Paths, id, paths); + + txn.commit(); } void unregisterPath(const string & _path) { string path(canonPath(_path)); + Transaction txn(nixDB); + + debug(format("unregistering path `%1%'") % path); string _id; - if (!nixDB.queryString(noTxn, dbPath2Id, path, _id)) return; + if (!nixDB.queryString(txn, dbPath2Id, path, _id)) { + txn.abort(); + return; + } FSId id(parseHash(_id)); - nixDB.delPair(noTxn, dbPath2Id, path); + nixDB.delPair(txn, dbPath2Id, path); - /* begin transaction */ - Strings paths, paths2; - nixDB.queryStrings(noTxn, dbId2Paths, id, paths); /* non-existence = ok */ + nixDB.queryStrings(txn, dbId2Paths, id, paths); /* non-existence = ok */ - bool changed = false; for (Strings::iterator it = paths.begin(); it != paths.end(); it++) - if (*it != path) paths2.push_back(*it); else changed = true; + if (*it != path) paths2.push_back(*it); - if (changed) - nixDB.setStrings(noTxn, dbId2Paths, id, paths2); - - /* end transaction */ + nixDB.setStrings(txn, dbId2Paths, id, paths2); + txn.commit(); } @@ -230,6 +247,8 @@ string expandId(const FSId & id, const string & target, void addToStore(string srcPath, string & dstPath, FSId & id, bool deterministicName) { + debug(format("adding `%1%' to the store") % srcPath); + srcPath = absPath(srcPath); id = hashPath(srcPath); From 06434072e7860f2eaac9581e979801b7b3493a1e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 31 Jul 2003 19:49:11 +0000 Subject: [PATCH 0189/6440] * Put the database verifier in a transaction. --- src/store.cc | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/store.cc b/src/store.cc index 6d7861d0b..8d424bc59 100644 --- a/src/store.cc +++ b/src/store.cc @@ -281,8 +281,12 @@ void deleteFromStore(const string & path) void verifyStore() { + Transaction txn(nixDB); + + /* !!! verify that the result is consistent */ + Strings paths; - nixDB.enumTable(noTxn, dbPath2Id, paths); + nixDB.enumTable(txn, dbPath2Id, paths); for (Strings::iterator i = paths.begin(); i != paths.end(); i++) @@ -296,10 +300,10 @@ void verifyStore() else { string id; - if (!nixDB.queryString(noTxn, dbPath2Id, path, id)) abort(); + if (!nixDB.queryString(txn, dbPath2Id, path, id)) abort(); Strings idPaths; - nixDB.queryStrings(noTxn, dbId2Paths, id, idPaths); + nixDB.queryStrings(txn, dbId2Paths, id, idPaths); bool found = false; for (Strings::iterator j = idPaths.begin(); @@ -316,11 +320,11 @@ void verifyStore() debug(format("reverse mapping for path `%1%' missing") % path); } - if (erase) nixDB.delPair(noTxn, dbPath2Id, path); + if (erase) nixDB.delPair(txn, dbPath2Id, path); } Strings ids; - nixDB.enumTable(noTxn, dbId2Paths, ids); + nixDB.enumTable(txn, dbId2Paths, ids); for (Strings::iterator i = ids.begin(); i != ids.end(); i++) @@ -328,13 +332,13 @@ void verifyStore() FSId id = parseHash(*i); Strings idPaths; - nixDB.queryStrings(noTxn, dbId2Paths, id, idPaths); + nixDB.queryStrings(txn, dbId2Paths, id, idPaths); for (Strings::iterator j = idPaths.begin(); j != idPaths.end(); ) { string id2; - if (!nixDB.queryString(noTxn, dbPath2Id, *j, id2) || + if (!nixDB.queryString(txn, dbPath2Id, *j, id2) || id != parseHash(id2)) { debug(format("erasing path `%1%' from mapping for id %2%") % *j % (string) id); @@ -342,12 +346,12 @@ void verifyStore() } else j++; } - nixDB.setStrings(noTxn, dbId2Paths, id, idPaths); + nixDB.setStrings(txn, dbId2Paths, id, idPaths); } Strings subs; - nixDB.enumTable(noTxn, dbSubstitutes, subs); + nixDB.enumTable(txn, dbSubstitutes, subs); for (Strings::iterator i = subs.begin(); i != subs.end(); i++) @@ -355,7 +359,7 @@ void verifyStore() FSId srcId = parseHash(*i); Strings subIds; - nixDB.queryStrings(noTxn, dbSubstitutes, srcId, subIds); + nixDB.queryStrings(txn, dbSubstitutes, srcId, subIds); for (Strings::iterator j = subIds.begin(); j != subIds.end(); ) @@ -363,7 +367,7 @@ void verifyStore() FSId subId = parseHash(*j); Strings subPaths; - nixDB.queryStrings(noTxn, dbId2Paths, subId, subPaths); + nixDB.queryStrings(txn, dbId2Paths, subId, subPaths); if (subPaths.size() == 0) { debug(format("erasing substitute %1% for %2%") % (string) subId % (string) srcId); @@ -371,11 +375,11 @@ void verifyStore() } else j++; } - nixDB.setStrings(noTxn, dbSubstitutes, srcId, subIds); + nixDB.setStrings(txn, dbSubstitutes, srcId, subIds); } Strings sucs; - nixDB.enumTable(noTxn, dbSuccessors, sucs); + nixDB.enumTable(txn, dbSuccessors, sucs); for (Strings::iterator i = sucs.begin(); i != sucs.end(); i++) @@ -383,18 +387,20 @@ void verifyStore() FSId id1 = parseHash(*i); string id2; - if (!nixDB.queryString(noTxn, dbSuccessors, id1, id2)) abort(); + if (!nixDB.queryString(txn, dbSuccessors, id1, id2)) abort(); Strings id2Paths; - nixDB.queryStrings(noTxn, dbId2Paths, id2, id2Paths); + nixDB.queryStrings(txn, dbId2Paths, id2, id2Paths); if (id2Paths.size() == 0) { Strings id2Subs; - nixDB.queryStrings(noTxn, dbSubstitutes, id2, id2Subs); + nixDB.queryStrings(txn, dbSubstitutes, id2, id2Subs); if (id2Subs.size() == 0) { debug(format("successor %1% for %2% missing") % id2 % (string) id1); - nixDB.delPair(noTxn, dbSuccessors, (string) id1); + nixDB.delPair(txn, dbSuccessors, (string) id1); } } } + + txn.commit(); } From 9df93f30bda81ffa3cf040c146347e02d3a56666 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 1 Aug 2003 09:01:51 +0000 Subject: [PATCH 0190/6440] * Don't use substitutes in addToStore(). --- src/store.cc | 33 +++++++++++++++++++-------------- src/store.hh | 3 ++- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/store.cc b/src/store.cc index 8d424bc59..2facb4cb1 100644 --- a/src/store.cc +++ b/src/store.cc @@ -181,7 +181,7 @@ bool isInPrefix(const string & path, const string & _prefix) string expandId(const FSId & id, const string & target, - const string & prefix, FSIdSet pending) + const string & prefix, FSIdSet pending, bool ignoreSubstitutes) { Nest nest(lvlDebug, format("expanding %1%") % (string) id); @@ -221,23 +221,27 @@ string expandId(const FSId & id, const string & target, } } - if (pending.find(id) != pending.end()) - throw Error(format("id %1% already being expanded") % (string) id); - pending.insert(id); + if (!ignoreSubstitutes) { + + if (pending.find(id) != pending.end()) + throw Error(format("id %1% already being expanded") % (string) id); + pending.insert(id); - /* Try to realise the substitutes, but only if this id is not - already being realised by a substitute. */ - Strings subs; - nixDB.queryStrings(noTxn, dbSubstitutes, id, subs); /* non-existence = ok */ + /* Try to realise the substitutes, but only if this id is not + already being realised by a substitute. */ + Strings subs; + nixDB.queryStrings(noTxn, dbSubstitutes, id, subs); /* non-existence = ok */ - for (Strings::iterator it = subs.begin(); it != subs.end(); it++) { - FSId subId = parseHash(*it); + for (Strings::iterator it = subs.begin(); it != subs.end(); it++) { + FSId subId = parseHash(*it); - debug(format("trying substitute %1%") % (string) subId); + debug(format("trying substitute %1%") % (string) subId); - realiseSlice(normaliseFState(subId, pending), pending); + realiseSlice(normaliseFState(subId, pending), pending); + + return expandId(id, target, prefix, pending); + } - return expandId(id, target, prefix, pending); } throw Error(format("cannot expand id `%1%'") % (string) id); @@ -257,7 +261,8 @@ void addToStore(string srcPath, string & dstPath, FSId & id, try { /* !!! should not use the substitutes! */ - dstPath = expandId(id, deterministicName ? dstPath : "", nixStore); + dstPath = expandId(id, deterministicName ? dstPath : "", + nixStore, FSIdSet(), true); return; } catch (...) { } diff --git a/src/store.hh b/src/store.hh index b2cdc41f1..d1f1be668 100644 --- a/src/store.hh +++ b/src/store.hh @@ -35,7 +35,8 @@ bool queryPathId(const string & path, FSId & id); substitute (since when we build the substitute, we would first try to expand the id... kaboom!). */ string expandId(const FSId & id, const string & target = "", - const string & prefix = "/", FSIdSet pending = FSIdSet()); + const string & prefix = "/", FSIdSet pending = FSIdSet(), + bool ignoreSubstitutes = false); /* Copy a file to the nixStore directory and register it in dbRefs. Return the hash code of the value. */ From 545145cd582cd80b857760ec11bb5a91b6271506 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 1 Aug 2003 14:11:19 +0000 Subject: [PATCH 0191/6440] * normaliseFState() now locks all output paths prior to building, thus ensuring that simultaneous invocations of Nix don't clobber each other's builds. * Fixed a bug in `make install'. --- src/Makefile.am | 3 +- src/normalise.cc | 111 +++++++++++++++++++++++++++++++++++------------ src/pathlocks.cc | 48 ++++++++++++++++++++ src/pathlocks.hh | 18 ++++++++ src/store.cc | 1 - 5 files changed, 151 insertions(+), 30 deletions(-) create mode 100644 src/pathlocks.cc create mode 100644 src/pathlocks.hh diff --git a/src/Makefile.am b/src/Makefile.am index 847f3eb19..85bf50282 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -22,7 +22,7 @@ noinst_LIBRARIES = libnix.a libshared.a libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c \ store.cc fstate.cc normalise.cc exec.cc \ - globals.cc db.cc references.cc + globals.cc db.cc references.cc pathlocks.cc libshared_a_SOURCES = shared.cc @@ -44,6 +44,7 @@ install-data-local: $(INSTALL) -d $(localstatedir)/nix $(INSTALL) -d $(localstatedir)/nix/db $(INSTALL) -d $(localstatedir)/nix/links + rm -f $(prefix)/current ln -sf $(localstatedir)/nix/links/current $(prefix)/current $(INSTALL) -d $(localstatedir)/log/nix $(INSTALL) -d $(prefix)/store diff --git a/src/normalise.cc b/src/normalise.cc index e8fc6fc55..cc84b6541 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -4,6 +4,7 @@ #include "references.hh" #include "db.hh" #include "exec.hh" +#include "pathlocks.hh" #include "globals.hh" @@ -23,7 +24,29 @@ static FSId storeSuccessor(const FSId & id1, ATerm sc) } -typedef set FSIdSet; +static FSId useSuccessor(const FSId & id) +{ + string idSucc; + if (nixDB.queryString(noTxn, dbSuccessors, id, idSucc)) { + debug(format("successor %1% -> %2%") % (string) id % idSucc); + return parseHash(idSucc); + } else + return id; +} + + +typedef map OutPaths; +typedef map ElemMap; + + +Strings pathsFromOutPaths(const OutPaths & ps) +{ + Strings ss; + for (OutPaths::const_iterator i = ps.begin(); + i != ps.end(); i++) + ss.push_back(i->first); + return ss; +} FSId normaliseFState(FSId id, FSIdSet pending) @@ -32,19 +55,66 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* Try to substitute $id$ by any known successors in order to speed up the rewrite process. */ - string idSucc; - while (nixDB.queryString(noTxn, dbSuccessors, id, idSucc)) { - debug(format("successor %1% -> %2%") % (string) id % idSucc); - id = parseHash(idSucc); - } + id = useSuccessor(id); /* Get the fstate expression. */ FState fs = parseFState(termFromId(id)); - /* It this is a normal form (i.e., a slice) we are done. */ + /* If this is a normal form (i.e., a slice) we are done. */ if (fs.type == FState::fsSlice) return id; + if (fs.type != FState::fsDerive) abort(); - /* Otherwise, it's a derivation. */ + + /* Otherwise, it's a derive expression, and we have to build it to + determine its normal form. */ + + + /* Some variables. */ + + /* Output paths, with their ids. */ + OutPaths outPaths; + + /* Input paths, with their slice elements. */ + ElemMap inMap; + + /* Referencable paths (i.e., input and output paths). */ + Strings refPaths; + + /* The environment to be passed to the builder. */ + Environment env; + + + /* Parse the outputs. */ + for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); + i != fs.derive.outputs.end(); i++) + { + debug(format("building %1% in `%2%'") % (string) i->second % i->first); + outPaths[i->first] = i->second; + refPaths.push_back(i->first); + } + + /* Obtain locks on all output paths. The locks are automatically + released when we exit this function or Nix crashes. */ + PathLocks outputLock(pathsFromOutPaths(outPaths)); + + /* Now check again whether there is a successor. This is because + another process may have started building in parallel. After + it has finished and released the locks, we can (and should) + reuse its results. (Strictly speaking the first successor + check above can be omitted, but that would be less efficient.) + Note that since we now hold the locks on the output paths, no + other process can build this expression, so no further checks + are necessary. */ + { + FSId id2 = useSuccessor(id); + if (id2 != id) { + FState fs = parseFState(termFromId(id2)); + debug(format("skipping build of %1%, someone beat us to it") + % (string) id); + if (fs.type != FState::fsSlice) abort(); + return id2; + } + } /* Right platform? */ if (fs.derive.platform != thisSystem) @@ -52,14 +122,12 @@ FSId normaliseFState(FSId id, FSIdSet pending) % fs.derive.platform % thisSystem); /* Realise inputs (and remember all input paths). */ - typedef map ElemMap; - - ElemMap inMap; - for (FSIds::iterator i = fs.derive.inputs.begin(); i != fs.derive.inputs.end(); i++) { FSId nf = normaliseFState(*i, pending); realiseSlice(nf, pending); + /* !!! nf should be a root of the garbage collector while we + are building */ FState fs = parseFState(termFromId(nf)); if (fs.type != FState::fsSlice) abort(); for (SliceElems::iterator j = fs.slice.elems.begin(); @@ -67,31 +135,18 @@ FSId normaliseFState(FSId id, FSIdSet pending) inMap[j->path] = *j; } - Strings inPaths; for (ElemMap::iterator i = inMap.begin(); i != inMap.end(); i++) - inPaths.push_back(i->second.path); + refPaths.push_back(i->second.path); /* Build the environment. */ - Environment env; for (StringPairs::iterator i = fs.derive.env.begin(); i != fs.derive.env.end(); i++) env[i->first] = i->second; - /* Parse the outputs. */ - typedef map OutPaths; - OutPaths outPaths; - for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); - i != fs.derive.outputs.end(); i++) - { - debug(format("building %1% in `%2%'") % (string) i->second % i->first); - outPaths[i->first] = i->second; - inPaths.push_back(i->first); - } - /* We can skip running the builder if we can expand all output paths from their ids. */ bool fastBuild = true; - for (OutPaths::iterator i = outPaths.begin(); + for (OutPaths::iterator i = outPaths.begin(); i != outPaths.end(); i++) { try { @@ -132,7 +187,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) registerPath(path, i->second); fs.slice.roots.push_back(i->second); - Strings refs = filterReferences(path, inPaths); + Strings refs = filterReferences(path, refPaths); SliceElem elem; elem.path = path; diff --git a/src/pathlocks.cc b/src/pathlocks.cc new file mode 100644 index 000000000..ac53dc643 --- /dev/null +++ b/src/pathlocks.cc @@ -0,0 +1,48 @@ +#include + +#include "pathlocks.hh" + + +PathLocks::PathLocks(const Strings & _paths) +{ + /* Note that `fds' is built incrementally so that the destructor + will only release those locks that we have already acquired. */ + + /* Sort the paths. This assures that locks are always acquired in + the same order, thus preventing deadlocks. */ + Strings paths(_paths); + paths.sort(); + + /* Acquire the lock for each path. */ + for (Strings::iterator i = paths.begin(); i != paths.end(); i++) { + string path = *i; + string lockPath = path + ".lock"; + + debug(format("locking path `%1%'") % path); + + /* Open/create the lock file. */ + int fd = open(lockPath.c_str(), O_WRONLY | O_CREAT, 0666); + if (fd == -1) + throw SysError(format("opening lock file `%1%'") % lockPath); + + fds.push_back(fd); + + /* Lock it. */ + struct flock lock; + lock.l_type = F_WRLCK; /* exclusive lock */ + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; /* entire file */ + + while (fcntl(fd, F_SETLKW, &lock) == -1) + if (errno != EINTR) + throw SysError(format("acquiring lock on `%1%'") % lockPath); + } +} + + +PathLocks::~PathLocks() +{ + for (list::iterator i = fds.begin(); i != fds.end(); i++) + close(*i); +} diff --git a/src/pathlocks.hh b/src/pathlocks.hh new file mode 100644 index 000000000..ae3a37cdb --- /dev/null +++ b/src/pathlocks.hh @@ -0,0 +1,18 @@ +#ifndef __PATHLOCKS_H +#define __PATHLOCKS_H + +#include "util.hh" + + +class PathLocks +{ +private: + list fds; + +public: + PathLocks(const Strings & _paths); + ~PathLocks(); +}; + + +#endif /* !__PATHLOCKS_H */ diff --git a/src/store.cc b/src/store.cc index 2facb4cb1..9f8e76998 100644 --- a/src/store.cc +++ b/src/store.cc @@ -260,7 +260,6 @@ void addToStore(string srcPath, string & dstPath, FSId & id, dstPath = canonPath(nixStore + "/" + (string) id + "-" + baseName); try { - /* !!! should not use the substitutes! */ dstPath = expandId(id, deterministicName ? dstPath : "", nixStore, FSIdSet(), true); return; From d99d04e6442dcc39a24cebac01af117ce00a5006 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 1 Aug 2003 15:06:23 +0000 Subject: [PATCH 0192/6440] * Defensive programming against POSIX locking idiocy. * Simplified realiseSlice(). --- src/normalise.cc | 25 ------------------------- src/pathlocks.cc | 17 +++++++++++++++++ src/pathlocks.hh | 1 + 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/normalise.cc b/src/normalise.cc index cc84b6541..2eb34d4ee 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -240,35 +240,10 @@ void realiseSlice(const FSId & id, FSIdSet pending) if (fs.type != FState::fsSlice) throw Error(format("expected slice in %1%") % (string) id); - /* Perhaps all paths already contain the right id? */ - - bool missing = false; for (SliceElems::const_iterator i = fs.slice.elems.begin(); i != fs.slice.elems.end(); i++) { SliceElem elem = *i; - string id; - if (!nixDB.queryString(noTxn, dbPath2Id, elem.path, id)) { - if (pathExists(elem.path)) - throw Error(format("path `%1%' obstructed") % elem.path); - missing = true; - break; - } - if (parseHash(id) != elem.id) - throw Error(format("path `%1%' obstructed") % elem.path); - } - - if (!missing) { - debug(format("already installed")); - return; - } - - /* For each element, expand its id at its path. */ - for (SliceElems::const_iterator i = fs.slice.elems.begin(); - i != fs.slice.elems.end(); i++) - { - SliceElem elem = *i; - debug(format("expanding %1% in `%2%'") % (string) elem.id % elem.path); expandId(elem.id, elem.path, "/", pending); } } diff --git a/src/pathlocks.cc b/src/pathlocks.cc index ac53dc643..fc05a7b55 100644 --- a/src/pathlocks.cc +++ b/src/pathlocks.cc @@ -3,6 +3,14 @@ #include "pathlocks.hh" +/* This enables us to check whether are not already holding a lock on + a file ourselves. POSIX locks (fcntl) suck in this respect: if we + close a descriptor, the previous lock will be closed as well. And + there is no way to query whether we already have a lock (F_GETLK + only works on locks held by other processes). */ +static StringSet lockedPaths; /* !!! not thread-safe */ + + PathLocks::PathLocks(const Strings & _paths) { /* Note that `fds' is built incrementally so that the destructor @@ -19,6 +27,9 @@ PathLocks::PathLocks(const Strings & _paths) string lockPath = path + ".lock"; debug(format("locking path `%1%'") % path); + + if (lockedPaths.find(lockPath) != lockedPaths.end()) + throw Error(format("already holding lock on `%1%'") % lockPath); /* Open/create the lock file. */ int fd = open(lockPath.c_str(), O_WRONLY | O_CREAT, 0666); @@ -26,6 +37,7 @@ PathLocks::PathLocks(const Strings & _paths) throw SysError(format("opening lock file `%1%'") % lockPath); fds.push_back(fd); + this->paths.push_back(lockPath); /* Lock it. */ struct flock lock; @@ -37,6 +49,8 @@ PathLocks::PathLocks(const Strings & _paths) while (fcntl(fd, F_SETLKW, &lock) == -1) if (errno != EINTR) throw SysError(format("acquiring lock on `%1%'") % lockPath); + + lockedPaths.insert(lockPath); } } @@ -45,4 +59,7 @@ PathLocks::~PathLocks() { for (list::iterator i = fds.begin(); i != fds.end(); i++) close(*i); + + for (Strings::iterator i = paths.begin(); i != paths.end(); i++) + lockedPaths.erase(*i); } diff --git a/src/pathlocks.hh b/src/pathlocks.hh index ae3a37cdb..03a62e65a 100644 --- a/src/pathlocks.hh +++ b/src/pathlocks.hh @@ -8,6 +8,7 @@ class PathLocks { private: list fds; + Strings paths; public: PathLocks(const Strings & _paths); From c95b4ad2906ce4076f04e0969b7080c0589a8cea Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 1 Aug 2003 15:41:47 +0000 Subject: [PATCH 0193/6440] * In normaliseFState(), wrap registration of the output paths and the normal form in a single transaction to ensure that if we crash, either everything is registered or nothing is. This is for recoverability: unregistered paths in the store can be deleted arbitrarily, while registered paths can only be deleted by running the garbage collector. --- src/fstate.cc | 5 ++++- src/nix.cc | 6 ++++-- src/normalise.cc | 39 +++++++++++++++++++++++---------------- src/normalise.hh | 3 ++- src/store.cc | 15 ++++++++------- src/store.hh | 4 +++- 6 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/fstate.cc b/src/fstate.cc index 5da3d8358..2e2857ffc 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -44,7 +44,10 @@ FSId writeTerm(ATerm t, const string & suffix, FSId id) // debug(format("written term %1% = %2%") % (string) id % // printTerm(t)); - registerPath(path, id); + Transaction txn(nixDB); + registerPath(txn, path, id); + txn.commit(); + return id; } diff --git a/src/nix.cc b/src/nix.cc index 42cc4a87c..4beeb5da8 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -239,14 +239,16 @@ static void opSuccessor(Strings opFlags, Strings opArgs) { if (!opFlags.empty()) throw UsageError("unknown flag"); if (opArgs.size() % 2) throw UsageError("expecting even number of arguments"); - + + Transaction txn(nixDB); /* !!! this could be a big transaction */ for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ) { FSId id1 = parseHash(*i++); FSId id2 = parseHash(*i++); - registerSuccessor(id1, id2); + registerSuccessor(txn, id1, id2); } + txn.commit(); } diff --git a/src/normalise.cc b/src/normalise.cc index 2eb34d4ee..074eda296 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -8,19 +8,10 @@ #include "globals.hh" -void registerSuccessor(const FSId & id1, const FSId & id2) +void registerSuccessor(const Transaction & txn, + const FSId & id1, const FSId & id2) { - Transaction txn(nixDB); nixDB.setString(txn, dbSuccessors, id1, id2); - txn.commit(); -} - - -static FSId storeSuccessor(const FSId & id1, ATerm sc) -{ - FSId id2 = writeTerm(sc, "-s-" + (string) id1); - registerSuccessor(id1, id2); - return id2; } @@ -153,7 +144,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) expandId(i->second, i->first, "/", pending); } catch (Error & e) { debug(format("fast build failed for `%1%': %2%") - % i->first % e.what()); + % i->first % e.what()); fastBuild = false; break; } @@ -175,8 +166,8 @@ FSId normaliseFState(FSId id, FSIdSet pending) } else msg(lvlChatty, format("fast build succesful")); - /* Check whether the output paths were created, and register each - one. */ + /* Check whether the output paths were created, and grep each + output path to determine what other paths it references. */ FSIdSet used; for (OutPaths::iterator i = outPaths.begin(); i != outPaths.end(); i++) @@ -184,7 +175,6 @@ FSId normaliseFState(FSId id, FSIdSet pending) string path = i->first; if (!pathExists(path)) throw Error(format("path `%1%' does not exist") % path); - registerPath(path, i->second); fs.slice.roots.push_back(i->second); Strings refs = filterReferences(path, refPaths); @@ -224,10 +214,27 @@ FSId normaliseFState(FSId id, FSIdSet pending) } } + /* Write the normal form. This does not have to occur in the + transaction below because writing terms is idem-potent. */ fs.type = FState::fsSlice; ATerm nf = unparseFState(fs); msg(lvlVomit, format("normal form: %1%") % printTerm(nf)); - return storeSuccessor(id, nf); + FSId idNF = writeTerm(nf, "-s-" + (string) id); + + /* Register each outpat path, and register the normal form. This + is wrapped in one database transaction to ensure that if we + crash, either everything is registered or nothing is. This is + for recoverability: unregistered paths in the store can be + deleted arbitrarily, while registered paths can only be deleted + by running the garbage collector. */ + Transaction txn(nixDB); + for (OutPaths::iterator i = outPaths.begin(); + i != outPaths.end(); i++) + registerPath(txn, i->first, i->second); + registerSuccessor(txn, id, idNF); + txn.commit(); + + return idNF; } diff --git a/src/normalise.hh b/src/normalise.hh index 98f58783e..59ab32573 100644 --- a/src/normalise.hh +++ b/src/normalise.hh @@ -29,7 +29,8 @@ Strings fstateRequisites(const FSId & id, FSIds findGenerators(const FSIds & ids); /* Register a successor. */ -void registerSuccessor(const FSId & id1, const FSId & id2); +void registerSuccessor(const Transaction & txn, + const FSId & id1, const FSId & id2); #endif /* !__NORMALISE_H */ diff --git a/src/store.cc b/src/store.cc index 9f8e76998..2411a737f 100644 --- a/src/store.cc +++ b/src/store.cc @@ -105,17 +105,16 @@ void registerSubstitute(const FSId & srcId, const FSId & subId) } -void registerPath(const string & _path, const FSId & id) +void registerPath(const Transaction & txn, + const string & _path, const FSId & id) { string path(canonPath(_path)); - Transaction txn(nixDB); debug(format("registering path `%1%' with id %2%") % path % (string) id); string oldId; if (nixDB.queryString(txn, dbPath2Id, path, oldId)) { - txn.abort(); if (id != parseHash(oldId)) throw Error(format("path `%1%' already contains id %2%") % path % oldId); @@ -130,8 +129,6 @@ void registerPath(const string & _path, const FSId & id) paths.push_back(path); nixDB.setStrings(txn, dbId2Paths, id, paths); - - txn.commit(); } @@ -215,7 +212,9 @@ string expandId(const FSId & id, const string & target, return path; else { copyPath(path, target); - registerPath(target, id); + Transaction txn(nixDB); + registerPath(txn, target, id); + txn.commit(); return target; } } @@ -267,7 +266,9 @@ void addToStore(string srcPath, string & dstPath, FSId & id, } copyPath(srcPath, dstPath); - registerPath(dstPath, id); + Transaction txn(nixDB); + registerPath(txn, dstPath, id); + txn.commit(); } diff --git a/src/store.hh b/src/store.hh index d1f1be668..7f6b24569 100644 --- a/src/store.hh +++ b/src/store.hh @@ -4,6 +4,7 @@ #include #include "hash.hh" +#include "db.hh" using namespace std; @@ -20,7 +21,8 @@ void copyPath(string src, string dst); void registerSubstitute(const FSId & srcId, const FSId & subId); /* Register a path keyed on its id. */ -void registerPath(const string & path, const FSId & id); +void registerPath(const Transaction & txn, + const string & path, const FSId & id); /* Query the id of a path. */ bool queryPathId(const string & path, FSId & id); From d2e963f7a39cebcc4c937f9060763386d72ce4db Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 4 Aug 2003 07:09:36 +0000 Subject: [PATCH 0194/6440] * Path locking in addToStore() and expandPath(). --- src/normalise.cc | 2 +- src/store.cc | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/normalise.cc b/src/normalise.cc index 074eda296..3f138a53e 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -86,7 +86,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* Obtain locks on all output paths. The locks are automatically released when we exit this function or Nix crashes. */ - PathLocks outputLock(pathsFromOutPaths(outPaths)); + PathLocks outputLocks(pathsFromOutPaths(outPaths)); /* Now check again whether there is a successor. This is because another process may have started building in parallel. After diff --git a/src/store.cc b/src/store.cc index 2411a737f..8a3db12ba 100644 --- a/src/store.cc +++ b/src/store.cc @@ -7,6 +7,7 @@ #include "globals.hh" #include "db.hh" #include "archive.hh" +#include "pathlocks.hh" #include "normalise.hh" @@ -211,10 +212,19 @@ string expandId(const FSId & id, const string & target, if (target.empty()) return path; else { + /* Acquire a lock on the target path. */ + Strings lockPaths; + lockPaths.push_back(target); + PathLocks outputLock(lockPaths); + + /* Copy. */ copyPath(path, target); + + /* Register the target path. */ Transaction txn(nixDB); registerPath(txn, target, id); txn.commit(); + return target; } } @@ -265,7 +275,12 @@ void addToStore(string srcPath, string & dstPath, FSId & id, } catch (...) { } + Strings lockPaths; + lockPaths.push_back(dstPath); + PathLocks outputLock(lockPaths); + copyPath(srcPath, dstPath); + Transaction txn(nixDB); registerPath(txn, dstPath, id); txn.commit(); From d6b6b2d3a83aa2afe3cae361954d8aa640fd77da Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 5 Aug 2003 09:47:20 +0000 Subject: [PATCH 0195/6440] * Delete obstructed paths prior to building. --- src/normalise.cc | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/normalise.cc b/src/normalise.cc index 3f138a53e..2fa6f7f40 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -152,11 +152,20 @@ FSId normaliseFState(FSId id, FSIdSet pending) if (!fastBuild) { - /* Check that none of the outputs exist. */ + /* If any of the outputs already exist but are not registered, + delete them. */ for (OutPaths::iterator i = outPaths.begin(); i != outPaths.end(); i++) - if (pathExists(i->first)) - throw Error(format("path `%1%' exists") % i->first); + { + string path = i->first; + FSId id; + if (queryPathId(path, id)) + throw Error(format("obstructed build: path `%1%' exists") % path); + if (pathExists(path)) { + debug(format("removing unregistered path `%1%'") % path); + deletePath(path); + } + } /* Run the builder. */ msg(lvlChatty, format("building...")); From 17f05dba775bb95858d9ac60ab9a9abcbe88b2fc Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 5 Aug 2003 11:13:38 +0000 Subject: [PATCH 0196/6440] * Allow the top-level expression to be a list of expressions that normalise to Nix expression. --- src/fix.cc | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index 4235165cc..57cb12630 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -130,6 +130,7 @@ static Expr evalExpr2(EvalState & state, Expr e) /* Normal forms. */ if (ATmatch(e, "", &s1) || + ATmatch(e, "[]", &e1) || ATmatch(e, "Function([], )", &e1, &e2) || ATmatch(e, "FSId()", &s1)) return e; @@ -299,6 +300,23 @@ static Expr evalFile(EvalState & state, string relPath) } +static void printFSId(EvalState & state, Expr e) +{ + ATermList es; + char * s; + if (ATmatch(e, "FSId()", &s)) { + cout << format("%1%\n") % s; + } + else if (ATmatch(e, "[]", &es)) { + while (!ATisEmpty(es)) { + printFSId(state, evalExpr(state, ATgetFirst(es))); + es = ATgetNext(es); + } + } + else throw badTerm("top level does not evaluate to a (list of) Nix expression(s)", e); +} + + void run(Strings args) { openDB(); @@ -333,11 +351,7 @@ void run(Strings args) it != files.end(); it++) { Expr e = evalFile(state, *it); - char * s; - if (ATmatch(e, "FSId()", &s)) { - cout << format("%1%\n") % s; - } - else throw badTerm("top level is not a package", e); + printFSId(state, e); } } From fd30f52cfca861d109652b6ad5a533e5c108f3e9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 5 Aug 2003 11:14:24 +0000 Subject: [PATCH 0197/6440] * Made nix-pull much faster by performing all Fix instantiations at the same time. --- scripts/nix-pull.in | 46 +++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index ff85ff9a6..1fc5d863a 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -3,9 +3,15 @@ my $tmpfile = "@localstatedir@/nix/pull.tmp"; my $conffile = "@sysconfdir@/nix/prebuilts.conf"; +my @ids; my @subs; my @sucs; +my $fixfile = "/tmp/nix-pull-tmp.fix"; +open FIX, ">$fixfile"; +print FIX "["; +my $first = 1; + open CONFFILE, "<$conffile"; while () { @@ -41,7 +47,7 @@ while () { $outname = "unnamed"; } - print "registering $id -> $url/$fn\n"; + print STDERR "$id ($outname)\n"; # Construct a Fix expression that fetches and unpacks a # Nix archive from the network. @@ -55,23 +61,14 @@ while () { ", (\"id\", \"$id\")" . "])"; - my $fixfile = "/tmp/nix-pull-tmp.fix"; - open FIX, ">$fixfile"; - print FIX $fixexpr; - close FIX; + print FIX "," unless ($first); + $first = 0; + print FIX $fixexpr; - # Instantiate a Nix expression from the Fix expression. - my $nid = `fix $fixfile`; - $? and die "instantiating Nix archive expression"; - chomp $nid; - die unless $nid =~ /^([0-9a-z]{32})$/; - - push @subs, $id; - push @subs, $nid; + push @ids, $id; # Does the name encode a successor relation? if (defined $fsid) { - print "NORMAL $fsid -> $id\n"; push @sucs, $fsid; push @sucs, $id; } @@ -84,8 +81,29 @@ while () { } +print FIX "]"; +close FIX; + +# Instantiate Nix expressions from the Fix expressions we created above. +print STDERR "running fix...\n"; +open NIDS, "fix $fixfile |" or die "cannot run fix"; +my $i = 0; +while () { + chomp; + die unless /^([0-9a-z]{32})$/; + $nid = $1; + die unless ($i < scalar @ids); + $id = $ids[$i++]; + push @subs, $id; + push @subs, $nid; +} + +# Register all substitutes. +print STDERR "registering substitutes...\n"; system "nix --substitute @subs"; if ($?) { die "`nix --substitute' failed"; } +# Register all successors. +print STDERR "registering successors...\n"; system "nix --successor @sucs"; if ($?) { die "`nix --successor' failed"; } From 4ce652640b01c97d4df287cbc0ec6766a1438fd2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 5 Aug 2003 12:29:47 +0000 Subject: [PATCH 0198/6440] * Cache result of fstatePaths(). TODO: do this in fstore.cc. --- src/fix.cc | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index 57cb12630..8f72d531c 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -9,12 +9,14 @@ typedef ATerm Expr; typedef map NormalForms; +typedef map PkgPaths; typedef map PkgHashes; struct EvalState { Strings searchDirs; NormalForms normalForms; + PkgPaths pkgPaths; PkgHashes pkgHashes; /* normalised package hashes */ }; @@ -106,7 +108,20 @@ static Expr substExprMany(ATermList formals, ATermList args, Expr body) } -Hash hashPackage(EvalState & state, FState fs) +static Strings fstatePathsCached(EvalState & state, const FSId & id) +{ + PkgPaths::iterator i = state.pkgPaths.find(id); + if (i != state.pkgPaths.end()) + return i->second; + else { + Strings paths = fstatePaths(id); + state.pkgPaths[id] = paths; + return paths; + } +} + + +static Hash hashPackage(EvalState & state, FState fs) { if (fs.type == FState::fsDerive) { for (FSIds::iterator i = fs.derive.inputs.begin(); @@ -214,7 +229,7 @@ static Expr evalExpr2(EvalState & state, Expr e) if (ATmatch(value, "FSId()", &s1)) { FSId id = parseHash(s1); - Strings paths = fstatePaths(id); + Strings paths = fstatePathsCached(state, id); if (paths.size() != 1) abort(); string path = *(paths.begin()); fs.derive.inputs.push_back(id); From b9c9b461ea3a90d7344a76c072b1f9a3e9d77144 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 5 Aug 2003 12:30:06 +0000 Subject: [PATCH 0199/6440] * Made nix-push much faster. --- scripts/nix-push.in | 55 ++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/scripts/nix-push.in b/scripts/nix-push.in index 10efc48be..bb5e6da13 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -1,6 +1,9 @@ #! /usr/bin/perl -w -my @pushlist; +my $fixfile = "/tmp/nix-push-tmp.fix"; +open FIX, ">$fixfile"; +print FIX "["; +my $first = 1; foreach my $id (@ARGV) { @@ -35,6 +38,7 @@ foreach my $id (@ARGV) { foreach my $path (@paths) { next unless ($path =~ /\/([0-9a-z]{32})[^\/]*/); + print "$path\n"; my $pathid = $1; # Construct a name for the Nix archive. If the file is an @@ -51,30 +55,41 @@ foreach my $id (@ARGV) { "[ (\"path\", Slice([\"$pathid\"], [(\"$path\", \"$pathid\", [])]))" . "])"; - my $fixfile = "/tmp/nix-push-tmp.fix"; - open FIX, ">$fixfile"; + print FIX "," unless ($first); + $first = 0; print FIX $fixexpr; - close FIX; - # Instantiate a Nix expression from the Fix expression. - my $nid = `fix $fixfile`; - $? and die "instantiating Nix archive expression"; - chomp $nid; - die unless $nid =~ /^([0-9a-z]{32})$/; - - # Realise the Nix expression. - system "nix --install $nid"; - if ($?) { die "`nix --install' failed"; } - my $npath = `nix --query --list $nid 2> /dev/null`; - $? and die "`nix --query --list' failed"; - chomp $npath; - - push @pushlist, "$npath/*"; - - print "$path -> $npath\n"; } } +print FIX "]"; +close FIX; + +# Instantiate a Nix expression from the Fix expression. +my @nids; +print STDERR "running fix...\n"; +open NIDS, "fix $fixfile |" or die "cannot run fix"; +while () { + chomp; + die unless /^([0-9a-z]{32})$/; + push @nids, $1; +} + + +# Realise the Nix expression. +my @pushlist; +print STDERR "creating archives...\n"; +system "nix --install @nids > /dev/null"; +if ($?) { die "`nix --install' failed"; } + +open NIDS, "nix --query --list @nids |" or die "cannot run nix"; +while () { + chomp; + die unless (/^\//); + print "$_\n"; + push @pushlist, "$_/*"; +} + # Push the prebuilts to the server. !!! FIXME if (scalar @pushlist > 0) { system "rsync -av -e ssh @pushlist eelco\@losser.st-lab.cs.uu.nl:/home/eelco/public_html/nix-dist/"; From d34b4d4f287a62de915a4bf75caf18d236e9b7e4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 5 Aug 2003 13:05:30 +0000 Subject: [PATCH 0200/6440] * Conditionals. --- src/fix.cc | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/fix.cc b/src/fix.cc index 8f72d531c..9111cd372 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -146,6 +146,8 @@ static Expr evalExpr2(EvalState & state, Expr e) /* Normal forms. */ if (ATmatch(e, "", &s1) || ATmatch(e, "[]", &e1) || + ATmatch(e, "True") || + ATmatch(e, "False") || ATmatch(e, "Function([], )", &e1, &e2) || ATmatch(e, "FSId()", &s1)) return e; @@ -167,6 +169,37 @@ static Expr evalExpr2(EvalState & state, Expr e) substExprMany((ATermList) e3, (ATermList) e2, e4)); } + /* Conditional. */ + if (ATmatch(e, "If(, , )", &e1, &e2, &e3)) { + e1 = evalExpr(state, e1); + Expr x; + if (ATmatch(e1, "True")) x = e2; + else if (ATmatch(e1, "False")) x = e3; + else throw badTerm("expecting a boolean", e1); + return evalExpr(state, x); + } + + /* Ad-hoc function for string matching. */ + if (ATmatch(e, "HasSubstr(, )", &e1, &e2)) { + e1 = evalExpr(state, e1); + e2 = evalExpr(state, e2); + + char * s1, * s2; + if (!ATmatch(e1, "", &s1)) + throw badTerm("expecting a string", e1); + if (!ATmatch(e2, "", &s2)) + throw badTerm("expecting a string", e2); + + return + string(s1).find(string(s2)) != string::npos ? + ATmake("True") : ATmake("False"); + } + + /* Platform constant. */ + if (ATmatch(e, "Platform")) { + return ATmake("", SYSTEM); + } + /* Fix inclusion. */ if (ATmatch(e, "IncludeFix()", &s1)) { string fileName(s1); From 37483672d425bc3b7be8e1deb049fd04c80be0cf Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 6 Aug 2003 09:05:04 +0000 Subject: [PATCH 0201/6440] * App -> Call. * Allow booleans in package environment bindings (True maps to "1", False maps to ""). --- src/fix.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/fix.cc b/src/fix.cc index 9111cd372..420dd7c04 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -161,7 +161,8 @@ static Expr evalExpr2(EvalState & state, Expr e) } /* Application. */ - if (ATmatch(e, "App(, [])", &e1, &e2)) { + if (ATmatch(e, "Call(, [])", &e1, &e2) || + ATmatch(e, "App(, [])", &e1, &e2)) { e1 = evalExpr(state, e1); if (!ATmatch(e1, "Function([], )", &e3, &e4)) throw badTerm("expecting a function", e1); @@ -277,6 +278,12 @@ static Expr evalExpr2(EvalState & state, Expr e) } fs.derive.env.push_back(StringPair(key, s1)); } + else if (ATmatch(value, "True")) { + fs.derive.env.push_back(StringPair(key, "1")); + } + else if (ATmatch(value, "False")) { + fs.derive.env.push_back(StringPair(key, "")); + } else throw badTerm("invalid package argument", value); bnds = ATinsert(bnds, From 720f06e3b05502df2dc84afb7a3ad2ea5518246a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 6 Aug 2003 09:06:32 +0000 Subject: [PATCH 0202/6440] * A flag `--flat' to just compute the MD5 checksum of the contents of a regular file. I.e., `nix-hash --flat' is equivalent to the coreutils `md5sum' command (which doesn't exist on all systems). --- src/nix-hash.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/nix-hash.cc b/src/nix-hash.cc index e35d0a1fe..77c169b9a 100644 --- a/src/nix-hash.cc +++ b/src/nix-hash.cc @@ -6,9 +6,13 @@ void run(Strings args) { - for (Strings::iterator it = args.begin(); - it != args.end(); it++) - cout << format("%1%\n") % (string) hashPath(*it); + bool flat = false; + for (Strings::iterator i = args.begin(); + i != args.end(); i++) + if (*i == "--flat") flat = true; + else + cout << format("%1%\n") % (string) + (flat ? hashFile(*i) : hashPath(*i)); } From 236eb59293194071ac518c12d0cc6fe0a3f0ac5f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 6 Aug 2003 09:34:04 +0000 Subject: [PATCH 0203/6440] * Allow locks on paths to be acquired recursively (that is, if the process is already holding a lock on a path, it may acquire the lock again without blocking or failing). (This might be dangerous, not sure). Necessary for fast builds to work. --- src/pathlocks.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pathlocks.cc b/src/pathlocks.cc index fc05a7b55..78aae26bd 100644 --- a/src/pathlocks.cc +++ b/src/pathlocks.cc @@ -28,8 +28,10 @@ PathLocks::PathLocks(const Strings & _paths) debug(format("locking path `%1%'") % path); - if (lockedPaths.find(lockPath) != lockedPaths.end()) - throw Error(format("already holding lock on `%1%'") % lockPath); + if (lockedPaths.find(lockPath) != lockedPaths.end()) { + debug(format("already holding lock on `%1%'") % lockPath); + continue; + } /* Open/create the lock file. */ int fd = open(lockPath.c_str(), O_WRONLY | O_CREAT, 0666); From d551062ec4cf33b73df4e0d38671cbe6ca03abc5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 6 Aug 2003 09:35:05 +0000 Subject: [PATCH 0204/6440] * Scan for wget and use the full path in fetchurl.sh. * Use nix-hash (not md5sum) in fetchurl.sh. --- configure.ac | 2 ++ corepkgs/fetchurl/{fetchurl.sh => fetchurl.sh.in} | 4 ++-- substitute.mk | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) rename corepkgs/fetchurl/{fetchurl.sh => fetchurl.sh.in} (65%) diff --git a/configure.ac b/configure.ac index 57861063d..4dea89c95 100644 --- a/configure.ac +++ b/configure.ac @@ -11,6 +11,8 @@ AC_PROG_CC AC_PROG_CXX AC_PROG_RANLIB +AC_PATH_PROG(wget, wget) + AC_CHECK_LIB(pthread, pthread_mutex_init) AM_CONFIG_HEADER([config.h]) diff --git a/corepkgs/fetchurl/fetchurl.sh b/corepkgs/fetchurl/fetchurl.sh.in similarity index 65% rename from corepkgs/fetchurl/fetchurl.sh rename to corepkgs/fetchurl/fetchurl.sh.in index 7b6243974..dc92c7ee5 100644 --- a/corepkgs/fetchurl/fetchurl.sh +++ b/corepkgs/fetchurl/fetchurl.sh.in @@ -1,9 +1,9 @@ #! /bin/sh echo "downloading $url into $out..." -wget "$url" -O "$out" || exit 1 +@wget@ "$url" -O "$out" || exit 1 -actual=$(md5sum -b $out | cut -c1-32) +actual=$(@bindir@/nix-hash --flat $out) if ! test "$actual" == "$md5"; then echo "hash is $actual, expected $md5" exit 1 diff --git a/substitute.mk b/substitute.mk index af3549253..8527cf6fd 100644 --- a/substitute.mk +++ b/substitute.mk @@ -4,5 +4,6 @@ -e s^@bindir\@^$(bindir)^g \ -e s^@sysconfdir\@^$(sysconfdir)^g \ -e s^@localstatedir\@^$(localstatedir)^g \ + -e s^@wget\@^$(wget)^g \ < $< > $@ || rm $@ chmod +x $@ From 9ad39df2823ea11ac670dd3006ab2b8fcf73e6a8 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 6 Aug 2003 10:00:30 +0000 Subject: [PATCH 0205/6440] * `==' is not a valid operator. --- corepkgs/fetchurl/fetchurl.sh.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/corepkgs/fetchurl/fetchurl.sh.in b/corepkgs/fetchurl/fetchurl.sh.in index dc92c7ee5..7e876a25e 100644 --- a/corepkgs/fetchurl/fetchurl.sh.in +++ b/corepkgs/fetchurl/fetchurl.sh.in @@ -4,7 +4,7 @@ echo "downloading $url into $out..." @wget@ "$url" -O "$out" || exit 1 actual=$(@bindir@/nix-hash --flat $out) -if ! test "$actual" == "$md5"; then +if test "$actual" != "$md5"; then echo "hash is $actual, expected $md5" exit 1 fi From f8035d06f2031fb1bdf30eee82a1beb707bbe044 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 6 Aug 2003 14:48:29 +0000 Subject: [PATCH 0206/6440] * Allow a name to be given to a system configuration through `--name NAME'. E.g., on the losser Subversion server, I do `nix-switch --name svn $(fix ...)' to atomically upgrade the server (the SVN server uses the Apache and Subversion installations in /nix/var/nix/links/svn). --- scripts/nix-switch.in | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/nix-switch.in b/scripts/nix-switch.in index db0e96f3e..427a803b2 100755 --- a/scripts/nix-switch.in +++ b/scripts/nix-switch.in @@ -4,11 +4,15 @@ use strict; my $keep = 0; my $sourceroot = 0; +my $name = "current"; my $srcid; -foreach my $arg (@ARGV) { +my $argnr = 0; +while ($argnr < scalar @ARGV) { + my $arg = $ARGV[$argnr++]; if ($arg eq "--keep") { $keep = 1; } elsif ($arg eq "--source-root") { $sourceroot = 1; } + elsif ($arg eq "--name") { $name = $ARGV[$argnr++]; } elsif ($arg =~ /^([0-9a-z]{32})$/) { $srcid = $arg; } else { die "unknown argument `$arg'" }; } @@ -54,7 +58,7 @@ if ($sourceroot) { close ID; } -my $current = "$linkdir/current"; +my $current = "$linkdir/$name"; # Read the current generation so that we can delete it (if --keep # wasn't specified). @@ -70,7 +74,7 @@ my $oldlink = readlink($current); print "switching $current to $link\n"; -my $tmplink = "$linkdir/new_current"; +my $tmplink = "$linkdir/.new_$name"; symlink($link, $tmplink) or die "cannot create $tmplink"; rename($tmplink, $current) or die "cannot rename $tmplink"; From 74867e72f29f8850892bd30b9c5aa5272bc49d75 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 7 Aug 2003 14:17:18 +0000 Subject: [PATCH 0207/6440] * Start of manual; installation instructions. --- doc/manual/Makefile | 9 ++-- doc/manual/book.xml | 102 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 doc/manual/book.xml diff --git a/doc/manual/Makefile b/doc/manual/Makefile index 95b472eee..52f17c65a 100644 --- a/doc/manual/Makefile +++ b/doc/manual/Makefile @@ -1,10 +1,11 @@ DOCBOOK_DTD = /nix/current/xml/dtd/docbook -DOCBOOK_XSL =/nix/current/xml/xsl/docbook/ +DOCBOOK_XSL = /nix/current/xml/xsl/docbook + +all: check html check: SP_CHARSET_FIXED=YES SP_ENCODING=XML \ - nsgmls -wxml -c /usr/share/sgml/opensp/xml.soc -c $(DOCBOOK_DTD)/docbook.cat -ges book.xml + nsgmls -wxml -c /usr/share/doc/packages/sp/html-xml/xml.soc -c $(DOCBOOK_DTD)/docbook.cat -ges book.xml html: - mkdir -p out - xsltproc --output out/book.html $(DOCBOOK_XSL)/html/docbook.xsl book.xml + xsltproc --output book.html $(DOCBOOK_XSL)/html/docbook.xsl book.xml diff --git a/doc/manual/book.xml b/doc/manual/book.xml new file mode 100644 index 000000000..51fddadf1 --- /dev/null +++ b/doc/manual/book.xml @@ -0,0 +1,102 @@ + + + + + Nix: The Manual + + + + + + Introduction + + + Nix is a system for the automatic creation and distribution of data, such + as computer programs and other software artifacts. + + + + + + + + + Installation + + + Prerequisites + + + Nix uses Sleepycat's Berkeley DB and CWI's ATerm library. However, + these are fetched automatically as part of the build process. + + + + Other than that, you need a good C++ compiler. GCC 2.95 does not + appear to work; please use GCC 3.x. + + + + + Obtaining Nix + + + Nix can be obtained from its Subversion + repository. For example, the following command will check + out the latest revision into a directory called + nix: + + + +$ svn checkout http://losser.st-lab.cs.uu.nl:12080/repos/trace/nix/trunk nix + + + Likewise, specific releases can be obtained from the tags + directory of the repository. If you don't have Subversion, + you can download a compressed + tar-file of the latest revision of the repository. + + + + + + Building Nix + + + To build Nix, do the following: + + + +$ autoreconf -i +$ ./configure options... +$ make +$ make install + + + Currently, the only useful switch for configure is + to specify + where Nix is to be installed. The default installation directory is + /nix. You can change this to any location you + like. You should ensure that you have write permission to the + installation prefix. + + + + + It is advisable not to change the installation + prefix, since doing so will in all likelihood make it impossible to + use derivates built on other systems. + + + + + + + + + From 4b7b0bd12ca59f84b7adada64818086ece684447 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 7 Aug 2003 15:27:14 +0000 Subject: [PATCH 0208/6440] * Started on the introduction. --- doc/manual/book.xml | 195 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 191 insertions(+), 4 deletions(-) diff --git a/doc/manual/book.xml b/doc/manual/book.xml index 51fddadf1..3f6c4f549 100644 --- a/doc/manual/book.xml +++ b/doc/manual/book.xml @@ -12,11 +12,128 @@ Introduction - - Nix is a system for the automatic creation and distribution of data, such - as computer programs and other software artifacts. - + + The problem space + + Nix is a system for controlling the automatic creation and distribution + of data, such as computer programs and other software artifacts. This + is a very general problem, and there are many applications that fall + under this description. + + + + Build management + + + Build management tools are used to perform software + builds, that is, the construction of derived products + such as executable programs from source code. A commonly used build + tool is Make, which is a standard tool on Unix systems. These tools + have to deal with several issues: + + + + + + + + + + + + Package management + + + After software has been built, is must also be + deployed in the intended target environment, + e.g., the user's workstation. Examples include the Red Hat package + manager (RPM), Microsoft's MSI, and so on. Here also we have to deal + with several issues: + + + + The creation of packages from some formal + description of what artifacts should be distributed in the + package. + + + + + The deployment of packages, that is, the + mechanism by which we get them onto the intended target + environment. This can be as simple as copying a file, but + complexity comes from the wide range of possible installation + media (such as a network install), and the scalability of the + process (if a program must be installed on a thousand systems, + we do not want to visit each system and perform some manual + steps to install the program on that system; that is, the + complexity for the system administrator should be constant, not + linear). + + + + + + + + + + The Nix system + + + ... + + + + Existing tools in this field generally both a underlying model (such as + the derivation graph of build tools, or the versioning scheme that + determines when two packages are compatible in a package + management system) and a formalism that allows ... + + + + Following the principle of separation of mechanism and policy, the Nix + system separates the low-level aspect of file + system object management form the high-level + aspect of the ... + + + + + + + + + + + A Guided Tour + + + Bla bla + + + + + + + + Fix Language Reference + + + Bla bla + + + + + + + + Nix Syntax and Semantics + + + Bla bla + @@ -99,4 +216,74 @@ $ make install + + + + Troubleshooting + + + Database hangs + + + If Nix or Fix appear to hang immediately after they are started, Nix's + database is probably wedged, i.e., some process died + while it held a lock on the database. The solution is to ensure that + no other processes are accessing the database and then run the + following command: + + + +$ db_recover -e -h prefix/var/nix/db + + + Here, prefix should be replaced by Nix's + installation prefix. + + + + + + + Database logfile removal + + + Every time a Nix database transaction takes place, Nix writes a record + of this transaction to a log in its database + directory to ensure that the operation can be replayed in case of a + application or system crash. However, without manual intervention, + the log grows indefinitely. Hence, unused log files should be deleted + periodically. This can be accomplished using the following command: + + + +$ rm `db_archive -a -h prefix/var/nix/db` + + + + + + + + + + + Known problems + + + + + + Nix should automatically recover the Berkeley DB database. + + + + + + Nix should automatically remove Berkeley DB logfiles. + + + + + + From c602930e08a508fce76b16f6f7f1fdfaed3b91ab Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 8 Aug 2003 14:55:56 +0000 Subject: [PATCH 0209/6440] * deletePath(): some operating systems (e.g., Mac OS X) don't like it when we delete entries from a directory while we are reading it. So read the directory into memory, then delete its contents. --- src/util.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/util.cc b/src/util.cc index 3c9a31acc..1e1c799ec 100644 --- a/src/util.cc +++ b/src/util.cc @@ -108,21 +108,28 @@ bool pathExists(const string & path) void deletePath(string path) { + msg(lvlVomit, format("deleting path `%1%'") % path); + struct stat st; if (lstat(path.c_str(), &st)) throw SysError(format("getting attributes of path %1%") % path); if (S_ISDIR(st.st_mode)) { + Strings names; + DIR * dir = opendir(path.c_str()); struct dirent * dirent; while (errno = 0, dirent = readdir(dir)) { string name = dirent->d_name; if (name == "." || name == "..") continue; - deletePath(path + "/" + name); + names.push_back(name); } closedir(dir); /* !!! close on exception */ + + for (Strings::iterator i = names.begin(); i != names.end(); i++) + deletePath(path + "/" + *i); } if (remove(path.c_str()) == -1) From e405ca506efa608c3636023a98994c05f09ecf0c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 12 Aug 2003 13:54:42 +0000 Subject: [PATCH 0210/6440] * Generate man pages from the manual. --- Makefile.am | 2 +- configure.ac | 3 +- doc/Makefile.am | 1 + doc/manual/Makefile | 11 ------- doc/manual/Makefile.am | 22 ++++++++++++++ doc/manual/book.xml | 67 +++++++++++++++++++++++++++++++++++++++++- 6 files changed, 92 insertions(+), 14 deletions(-) create mode 100644 doc/Makefile.am delete mode 100644 doc/manual/Makefile create mode 100644 doc/manual/Makefile.am diff --git a/Makefile.am b/Makefile.am index 70aa1ba96..b7bb72819 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,3 @@ -SUBDIRS = externals src scripts corepkgs +SUBDIRS = externals src scripts corepkgs doc EXTRA_DIST = boost/*.hpp boost/format/*.hpp substitute.mk \ No newline at end of file diff --git a/configure.ac b/configure.ac index 4dea89c95..3dd3df9c6 100644 --- a/configure.ac +++ b/configure.ac @@ -18,5 +18,6 @@ AC_CHECK_LIB(pthread, pthread_mutex_init) AM_CONFIG_HEADER([config.h]) AC_CONFIG_FILES([Makefile externals/Makefile src/Makefile scripts/Makefile corepkgs/Makefile corepkgs/fetchurl/Makefile - corepkgs/nar/Makefile]) + corepkgs/nar/Makefile + doc/Makefile doc/manual/Makefile]) AC_OUTPUT diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 000000000..e76efafdb --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = manual diff --git a/doc/manual/Makefile b/doc/manual/Makefile deleted file mode 100644 index 52f17c65a..000000000 --- a/doc/manual/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -DOCBOOK_DTD = /nix/current/xml/dtd/docbook -DOCBOOK_XSL = /nix/current/xml/xsl/docbook - -all: check html - -check: - SP_CHARSET_FIXED=YES SP_ENCODING=XML \ - nsgmls -wxml -c /usr/share/doc/packages/sp/html-xml/xml.soc -c $(DOCBOOK_DTD)/docbook.cat -ges book.xml - -html: - xsltproc --output book.html $(DOCBOOK_XSL)/html/docbook.xsl book.xml diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am new file mode 100644 index 000000000..a4e3f24ce --- /dev/null +++ b/doc/manual/Makefile.am @@ -0,0 +1,22 @@ +DOCBOOK_DTD = /nix/current/xml/dtd/docbook +DOCBOOK_XSL = /nix/current/xml/xsl/docbook +XML = /usr/share/doc/packages/sp/html-xml/xml.soc + +%.is-valid: %.xml + SP_CHARSET_FIXED=YES SP_ENCODING=XML \ + nsgmls -wxml -c $(XML) -c $(DOCBOOK_DTD)/docbook.cat -ges $< + touch $@ + +man1_MANS = nix.1 + +man nix.1: book.is-valid + xsltproc $(DOCBOOK_XSL)/manpages/docbook.xsl book.xml + +%.html: %.xml %.is-valid + xsltproc --output book.html $(DOCBOOK_XSL)/html/docbook.xsl book.xml + +all-local: book.html + +install-data-local: book.html + $(INSTALL) -d $(datadir)/nix/manual + $(INSTALL_DATA) book.html $(datadir)/nix/manual diff --git a/doc/manual/book.xml b/doc/manual/book.xml index 3f6c4f549..f08ffc3b0 100644 --- a/doc/manual/book.xml +++ b/doc/manual/book.xml @@ -216,6 +216,71 @@ $ make install + + + + Command Reference + + + + nix + manipulate or query the Nix store + + + + + nix + --verbose + + + + + Description + + + The command nix provides access to the Nix store. + This is the (set of) path(s) where Nix expressions and the file + system objects built by them are stored. + + + + + Common Options + + + nix has many subcommands. These are listed below. + In this section the common options are listed. These options are + allowed for every subcommand (although they may not always have an + effect). + + + + + Subcommand <command>--install</command> + + + Synopsis + + nix --install + id + + + + + Description + + + nix --install realises the given Nix expressions + in the file system. + + + + + + + + + @@ -267,7 +332,7 @@ $ rm `db_archive -a -h prefix/var/nix/db` - Known problems + Bugs From 469f1eba561403639e777721cacd59e0a6cdc39d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 12 Aug 2003 15:06:49 +0000 Subject: [PATCH 0211/6440] * Documented some Nix operations. --- doc/manual/book.xml | 169 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 159 insertions(+), 10 deletions(-) diff --git a/doc/manual/book.xml b/doc/manual/book.xml index f08ffc3b0..3d54edfcb 100644 --- a/doc/manual/book.xml +++ b/doc/manual/book.xml @@ -230,7 +230,17 @@ $ make install nix - --verbose + + + + + + + + + operation + options + arguments @@ -242,27 +252,115 @@ $ make install This is the (set of) path(s) where Nix expressions and the file system objects built by them are stored. + + + nix has many subcommands called + operations. These are individually documented + below. Exactly one operation must always be provided. + + Common Options - nix has many subcommands. These are listed below. - In this section the common options are listed. These options are - allowed for every subcommand (although they may not always have an - effect). + In this section the options that are common to all Nix operations are + listed. These options are allowed for every subcommand (although + they may not always have an effect). + + + + + + + + Indicates that any identifier arguments to the operation are + paths in the store rather than identifiers. + + + + + + + + + Increases the level of verbosity of diagnostic messages printed + on standard error. For each Nix operation, the information + printed on standard output is well-defined and specified below + in the respective sections. Any diagnostic information is + printed on standard error, never on standard output. + + + + This option may be specified repeatedly. Currently, the + following verbosity levels exist: + + + + + 0 + + + Print error messages only. + + + + + 1 + + + Print informational messages. + + + + + 2 + + + Print even more informational messages. + + + + + 3 + + + Print messages that should only be useful for debugging. + + + + + 4 + + + Vomit mode: print vast amounts of debug + information. + + + + + + + + + + - Subcommand <command>--install</command> + Operation <option>--install</option> Synopsis - nix --install - id + nix + + + + + ids @@ -270,12 +368,63 @@ $ make install Description - nix --install realises the given Nix expressions - in the file system. + The operation realises the Nix + expressions identified by ids in the + file system. If these expressions are derivation expressions, they + are first normalised. That is, their target paths are are built, + unless a normal form is already known. + + + The identifiers of the normal forms of the given Nix expressions + are printed on standard output. + + + + + + Operation <option>--delete</option> + + + Synopsis + + nix + + + + + paths + + + + + Description + + + The operation unconditionally deletes + the paths paths from the Nix store. + It is an error to attempt to delete paths outside of the store. + + + + + This operation should almost never be called directly, since no + attempt is made to check whether any references exist to the + paths to be deleted. Therefore, an inconsistent system could be + the result. Deletion of paths in the store is done by the + garbage collector (which uses to delete + unreferenced paths). + + + + + + + + From b4f88d0ec364f00196127ea29e8db5033368e23a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 13 Aug 2003 09:13:52 +0000 Subject: [PATCH 0212/6440] * Split the book.xml into several xml files. --- doc/manual/Makefile.am | 9 +- doc/manual/book.xml | 477 +-------------------------------- doc/manual/bugs.xml | 26 ++ doc/manual/installation.xml | 79 ++++++ doc/manual/introduction.xml | 98 +++++++ doc/manual/nix-reference.xml | 213 +++++++++++++++ doc/manual/troubleshooting.xml | 49 ++++ 7 files changed, 481 insertions(+), 470 deletions(-) create mode 100644 doc/manual/bugs.xml create mode 100644 doc/manual/installation.xml create mode 100644 doc/manual/introduction.xml create mode 100644 doc/manual/nix-reference.xml create mode 100644 doc/manual/troubleshooting.xml diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index a4e3f24ce..a29592f1e 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -2,17 +2,20 @@ DOCBOOK_DTD = /nix/current/xml/dtd/docbook DOCBOOK_XSL = /nix/current/xml/xsl/docbook XML = /usr/share/doc/packages/sp/html-xml/xml.soc -%.is-valid: %.xml +SOURCES = book.xml introduction.xml installation.xml nix-reference.xml \ + troubleshooting.xml bugs.xml + +book.is-valid: $(SOURCES) SP_CHARSET_FIXED=YES SP_ENCODING=XML \ nsgmls -wxml -c $(XML) -c $(DOCBOOK_DTD)/docbook.cat -ges $< touch $@ man1_MANS = nix.1 -man nix.1: book.is-valid +man nix.1: $(SOURCES) book.is-valid xsltproc $(DOCBOOK_XSL)/manpages/docbook.xsl book.xml -%.html: %.xml %.is-valid +book.html: $(SOURCES) book.is-valid xsltproc --output book.html $(DOCBOOK_XSL)/html/docbook.xsl book.xml all-local: book.html diff --git a/doc/manual/book.xml b/doc/manual/book.xml index 3d54edfcb..eb0475a62 100644 --- a/doc/manual/book.xml +++ b/doc/manual/book.xml @@ -1,503 +1,46 @@ + + + + ]> Nix: The Manual - - - - - Introduction - - - The problem space - - - Nix is a system for controlling the automatic creation and distribution - of data, such as computer programs and other software artifacts. This - is a very general problem, and there are many applications that fall - under this description. - - - - Build management - - - Build management tools are used to perform software - builds, that is, the construction of derived products - such as executable programs from source code. A commonly used build - tool is Make, which is a standard tool on Unix systems. These tools - have to deal with several issues: - - - - - - - - - - - - Package management - - - After software has been built, is must also be - deployed in the intended target environment, - e.g., the user's workstation. Examples include the Red Hat package - manager (RPM), Microsoft's MSI, and so on. Here also we have to deal - with several issues: - - - - The creation of packages from some formal - description of what artifacts should be distributed in the - package. - - - - - The deployment of packages, that is, the - mechanism by which we get them onto the intended target - environment. This can be as simple as copying a file, but - complexity comes from the wide range of possible installation - media (such as a network install), and the scalability of the - process (if a program must be installed on a thousand systems, - we do not want to visit each system and perform some manual - steps to install the program on that system; that is, the - complexity for the system administrator should be constant, not - linear). - - - - - - - - - - The Nix system - - - ... - - - - Existing tools in this field generally both a underlying model (such as - the derivation graph of build tools, or the versioning scheme that - determines when two packages are compatible in a package - management system) and a formalism that allows ... - - - - Following the principle of separation of mechanism and policy, the Nix - system separates the low-level aspect of file - system object management form the high-level - aspect of the ... - - - - - - - - + &introduction; + &installation; A Guided Tour - Bla bla - - - Fix Language Reference - Bla bla - - - Nix Syntax and Semantics - Bla bla - - - - - Installation - - - Prerequisites - - - Nix uses Sleepycat's Berkeley DB and CWI's ATerm library. However, - these are fetched automatically as part of the build process. - - - - Other than that, you need a good C++ compiler. GCC 2.95 does not - appear to work; please use GCC 3.x. - - - - - Obtaining Nix - - - Nix can be obtained from its Subversion - repository. For example, the following command will check - out the latest revision into a directory called - nix: - - - -$ svn checkout http://losser.st-lab.cs.uu.nl:12080/repos/trace/nix/trunk nix - - - Likewise, specific releases can be obtained from the tags - directory of the repository. If you don't have Subversion, - you can download a compressed - tar-file of the latest revision of the repository. - - - - - - Building Nix - - - To build Nix, do the following: - - - -$ autoreconf -i -$ ./configure options... -$ make -$ make install - - - Currently, the only useful switch for configure is - to specify - where Nix is to be installed. The default installation directory is - /nix. You can change this to any location you - like. You should ensure that you have write permission to the - installation prefix. - - - - - It is advisable not to change the installation - prefix, since doing so will in all likelihood make it impossible to - use derivates built on other systems. - - - - - - - - - - Command Reference - - - - nix - manipulate or query the Nix store - - - - - nix - - - - - - - - - operation - options - arguments - - - - - Description - - - The command nix provides access to the Nix store. - This is the (set of) path(s) where Nix expressions and the file - system objects built by them are stored. - - - - nix has many subcommands called - operations. These are individually documented - below. Exactly one operation must always be provided. - - - - - - Common Options - - - In this section the options that are common to all Nix operations are - listed. These options are allowed for every subcommand (although - they may not always have an effect). - - - - - - - - - Indicates that any identifier arguments to the operation are - paths in the store rather than identifiers. - - - - - - - - - Increases the level of verbosity of diagnostic messages printed - on standard error. For each Nix operation, the information - printed on standard output is well-defined and specified below - in the respective sections. Any diagnostic information is - printed on standard error, never on standard output. - - - - This option may be specified repeatedly. Currently, the - following verbosity levels exist: - - - - - 0 - - - Print error messages only. - - - - - 1 - - - Print informational messages. - - - - - 2 - - - Print even more informational messages. - - - - - 3 - - - Print messages that should only be useful for debugging. - - - - - 4 - - - Vomit mode: print vast amounts of debug - information. - - - - - - - - - - - - - - Operation <option>--install</option> - - - Synopsis - - nix - - - - - ids - - - - - Description - - - The operation realises the Nix - expressions identified by ids in the - file system. If these expressions are derivation expressions, they - are first normalised. That is, their target paths are are built, - unless a normal form is already known. - - - - The identifiers of the normal forms of the given Nix expressions - are printed on standard output. - - - - - - - - - Operation <option>--delete</option> - - - Synopsis - - nix - - - - - paths - - - - - Description - - - The operation unconditionally deletes - the paths paths from the Nix store. - It is an error to attempt to delete paths outside of the store. - - - - - This operation should almost never be called directly, since no - attempt is made to check whether any references exist to the - paths to be deleted. Therefore, an inconsistent system could be - the result. Deletion of paths in the store is done by the - garbage collector (which uses to delete - unreferenced paths). - - - - - - - - - - + &nix-reference; - - - - - Troubleshooting - - - Database hangs - - - If Nix or Fix appear to hang immediately after they are started, Nix's - database is probably wedged, i.e., some process died - while it held a lock on the database. The solution is to ensure that - no other processes are accessing the database and then run the - following command: - - - -$ db_recover -e -h prefix/var/nix/db - - - Here, prefix should be replaced by Nix's - installation prefix. - - - - - - - Database logfile removal - - - Every time a Nix database transaction takes place, Nix writes a record - of this transaction to a log in its database - directory to ensure that the operation can be replayed in case of a - application or system crash. However, without manual intervention, - the log grows indefinitely. Hence, unused log files should be deleted - periodically. This can be accomplished using the following command: - - - -$ rm `db_archive -a -h prefix/var/nix/db` - - - - - - - - - - - Bugs - - - - - - Nix should automatically recover the Berkeley DB database. - - - - - - Nix should automatically remove Berkeley DB logfiles. - - - - - + &troubleshooting; + &bugs; diff --git a/doc/manual/bugs.xml b/doc/manual/bugs.xml new file mode 100644 index 000000000..33c6e767b --- /dev/null +++ b/doc/manual/bugs.xml @@ -0,0 +1,26 @@ + + Bugs + + + + + + Nix should automatically recover the Berkeley DB database. + + + + + + Nix should automatically remove Berkeley DB logfiles. + + + + + + + + diff --git a/doc/manual/installation.xml b/doc/manual/installation.xml new file mode 100644 index 000000000..7d8821d47 --- /dev/null +++ b/doc/manual/installation.xml @@ -0,0 +1,79 @@ + + Installation + + + Prerequisites + + + Nix uses Sleepycat's Berkeley DB and CWI's ATerm library. However, these + are fetched automatically as part of the build process. + + + + Other than that, you need a good C++ compiler. GCC 2.95 does not appear + to work; please use GCC 3.x. + + + + + Obtaining Nix + + + Nix can be obtained from its Subversion + repository. For example, the following command will check out + the latest revision into a directory called nix: + + + + $ svn checkout http://losser.st-lab.cs.uu.nl:12080/repos/trace/nix/trunk + nix + + + Likewise, specific releases can be obtained from the tags + directory of the repository. If you don't have Subversion, you + can download a compressed + tar-file of the latest revision of the repository. + + + + + + Building Nix + + + To build Nix, do the following: + + + + $ autoreconf -i $ ./configure options... $ + make $ make install + + + Currently, the only useful switch for configure is + to specify + where Nix is to be installed. The default installation directory is + /nix. You can change this to any location you like. + You should ensure that you have write permission to the installation + prefix. + + + + + It is advisable not to change the installation + prefix, since doing so will in all likelihood make it impossible to use + derivates built on other systems. + + + + + + + + diff --git a/doc/manual/introduction.xml b/doc/manual/introduction.xml new file mode 100644 index 000000000..77a5f917e --- /dev/null +++ b/doc/manual/introduction.xml @@ -0,0 +1,98 @@ + + Introduction + + + The problem space + + + Nix is a system for controlling the automatic creation and distribution + of data, such as computer programs and other software artifacts. This is + a very general problem, and there are many applications that fall under + this description. + + + + Build management + + + Build management tools are used to perform software + builds, that is, the construction of derived products such + as executable programs from source code. A commonly used build tool is + Make, which is a standard tool on Unix systems. These tools have to + deal with several issues: + + + + + + + + + + + + Package management + + + After software has been built, is must also be + deployed in the intended target environment, e.g., + the user's workstation. Examples include the Red Hat package manager + (RPM), Microsoft's MSI, and so on. Here also we have to deal with + several issues: + + + + The creation of packages from some formal + description of what artifacts should be distributed in the + package. + + + + + The deployment of packages, that is, the + mechanism by which we get them onto the intended target + environment. This can be as simple as copying a file, but + complexity comes from the wide range of possible installation + media (such as a network install), and the scalability of the + process (if a program must be installed on a thousand systems, we + do not want to visit each system and perform some manual steps to + install the program on that system; that is, the complexity for + the system administrator should be constant, not linear). + + + + + + + + + + The Nix system + + + ... + + + + Existing tools in this field generally both a underlying model (such as + the derivation graph of build tools, or the versioning scheme that + determines when two packages are compatible in a package + management system) and a formalism that allows ... + + + + Following the principle of separation of mechanism and policy, the Nix + system separates the low-level aspect of file system + object management form the high-level aspect of the + ... + + + + + + + diff --git a/doc/manual/nix-reference.xml b/doc/manual/nix-reference.xml new file mode 100644 index 000000000..39c83518c --- /dev/null +++ b/doc/manual/nix-reference.xml @@ -0,0 +1,213 @@ + + + nix + manipulate or query the Nix store + + + + + nix + + + + + + + + + operation + options + arguments + + + + + Description + + + The command nix provides access to the Nix store. This + is the (set of) path(s) where Nix expressions and the file system objects + built by them are stored. + + + + nix has many subcommands called + operations. These are individually documented + below. Exactly one operation must always be provided. + + + + + + Common Options + + + In this section the options that are common to all Nix operations are + listed. These options are allowed for every subcommand (although they + may not always have an effect). + + + + + + + + + Indicates that any identifier arguments to the operation are paths + in the store rather than identifiers. + + + + + + + + + Increases the level of verbosity of diagnostic messages printed on + standard error. For each Nix operation, the information printed on + standard output is well-defined and specified below in the + respective sections. Any diagnostic information is printed on + standard error, never on standard output. + + + + This option may be specified repeatedly. Currently, the following + verbosity levels exist: + + + + + 0 + + + Print error messages only. + + + + + 1 + + + Print informational messages. + + + + + 2 + + + Print even more informational messages. + + + + + 3 + + + Print messages that should only be useful for debugging. + + + + + 4 + + + Vomit mode: print vast amounts of debug + information. + + + + + + + + + + + + + + Operation <option>--install</option> + + + Synopsis + + nix + + + + + ids + + + + + Description + + + The operation realises the Nix expressions + identified by ids in the file system. If + these expressions are derivation expressions, they are first + normalised. That is, their target paths are are built, unless a normal + form is already known. + + + + The identifiers of the normal forms of the given Nix expressions are + printed on standard output. + + + + + + + + + Operation <option>--delete</option> + + + Synopsis + + nix + + + + + paths + + + + + Description + + + The operation unconditionally deletes the + paths paths from the Nix store. It is an + error to attempt to delete paths outside of the store. + + + + + This operation should almost never be called directly, since no + attempt is made to verify that no references exist to the paths to + be deleted. Therefore, careless deletion can result in an + inconsistent system. Deletion of paths in the store is done by the + garbage collector (which uses to delete + unreferenced paths). + + + + + + + + + + + + + diff --git a/doc/manual/troubleshooting.xml b/doc/manual/troubleshooting.xml new file mode 100644 index 000000000..018b3555f --- /dev/null +++ b/doc/manual/troubleshooting.xml @@ -0,0 +1,49 @@ + + Troubleshooting + + + Database hangs + + + If Nix or Fix appear to hang immediately after they are started, Nix's + database is probably wedged, i.e., some process died while + it held a lock on the database. The solution is to ensure that no other + processes are accessing the database and then run the following command: + + + + $ db_recover -e -h prefix/var/nix/db + + + Here, prefix should be replaced by Nix's + installation prefix. + + + + + + + Database logfile removal + + + Every time a Nix database transaction takes place, Nix writes a record of + this transaction to a log in its database directory + to ensure that the operation can be replayed in case of a application or + system crash. However, without manual intervention, the log grows + indefinitely. Hence, unused log files should be deleted periodically. + This can be accomplished using the following command: + + + + $ rm `db_archive -a -h + prefix/var/nix/db` + + + + + + From c34a153ae5614ab879ff19194ff396ffb21b7b55 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 13 Aug 2003 10:45:01 +0000 Subject: [PATCH 0213/6440] * Documented the `--query' operation. --- doc/manual/bugs.xml | 17 +++ doc/manual/nix-reference.xml | 214 +++++++++++++++++++++++++++++++++ doc/manual/troubleshooting.xml | 3 +- 3 files changed, 232 insertions(+), 2 deletions(-) diff --git a/doc/manual/bugs.xml b/doc/manual/bugs.xml index 33c6e767b..548ce1cab 100644 --- a/doc/manual/bugs.xml +++ b/doc/manual/bugs.xml @@ -15,6 +15,23 @@ + + + Unify the concepts of successors and substitutes into a general notion + of equivalent expressions. Expressions are + equivalent if they have the same target paths with the same + identifiers. However, even though they are functionally equivalent, + they may differ stronly with respect to their performance + characteristics. For example, realising a slice is more + efficient that realising the derivation from which that slice was + produced. On the other hand, distributing sources may be more + efficient (storage- or bandwidth-wise) than distributing binaries. So + we need to be able to attach weigths or priorities or performance + annotations to expressions; Nix can then choose the most efficient + expression dependent on the context. + + + diff --git a/doc/manual/nix-reference.xml b/doc/manual/nix-reference.xml index 39c83518c..75009b1d0 100644 --- a/doc/manual/nix-reference.xml +++ b/doc/manual/nix-reference.xml @@ -126,6 +126,8 @@ + + Operation <option>--install</option> @@ -162,6 +164,8 @@ + + Operation <option>--delete</option> @@ -203,6 +207,216 @@ + + + + Operation <option>--query</option> + + + Synopsis + + nix + + + + + + + + + + + + + + + + + + + + + + + args + + + + + Description + + + The operation displays various bits of + information about Nix expressions or paths in the store. The queries + are described in . At most one query + can be specified; the default query is . + + + + + + Queries + + + + + + + + Prints out the target paths of the Nix expressions indicated by + the identifiers args. In the case of + a derivation expression, these are the paths that will be + produced by the builder of the expression. In the case of a + slice expression, these are the root paths (which are generally + the paths that were produced by the builder of the derivation + expression of which the slice is a normal form). + + + + This query has one option: + + + + + + + + + Causes the target paths of the normal + forms of the expressions to be printed, rather + than the target paths of the expressions themselves. + + + + + + + + + + + + + + Prints out the requisite paths of the Nix expressions indicated + by the identifiers args. The + requisite paths of a Nix expression are the paths that need to be + present in the system to be able to realise the expression. That + is, they form the closure of the expression + in the file system (i.e., no path in the set of requisite paths + points to anything outside the set of requisite paths). + + + + The notion of requisite paths is very useful when one wants to + distribute Nix expressions. Since they form a closure, they are + the only paths one needs to distribute to another system to be + able to realise the expression on the other system. + + + + This query is generally used to implement various kinds of + distribution. A source distribution is + obtained by distributing the requisite paths of a derivation + expression. A binary distribution is + obtained by distributing the requisite paths of a slice + expression (i.e., the normal form of a derivation expression; you + can directly specify the identifier of the slice expression, or + use and specify the identifier of a + derivation expression). A cache + distribution is obtained by distributing the + requisite paths of a derivation expression and specifying the + option . This will include + not just the paths of a source and binary distribution, but also + all expressions and paths of subterms of the source. This is + useful if one wants to realise on the target system a Nix + expression that is similar but not quite the same as the one + being distributed, since any common subterms will be reused. + + + + This query has a number of options: + + + + + + + + + Causes the requisite paths of the normal + forms of the expressions to be printed, rather + than the requisite paths of the expressions themselves. + + + + + + + + + Excludes the paths of Nix expressions. This causes the + closure property to be lost, that is, the resulting set of + paths is not enough to ensure realisibility. + + + + + + + + + Also include the requisites of successors (normal forms). + Only the requisites of known + successors are included, i.e., the normal forms of + derivation expressions that have never been normalised will + not be included. + + + + Note that not just the successor of a derivation expression + will be included, but also the successors of all input + expressions of that derivation expression. I.e., all + normal forms of subterms involved in the normalisation of + the top-level term are included. + + + + + + + + + + + + + + For each identifier in args, prints + all expansions of that identifier, that is, all paths whose + current content matches the identifier. + + + + + + + + + Prints a graph of the closure of the expressions identified by + args in the format of the + dot tool of AT&T's GraphViz package. + + + + + + + + + + + diff --git a/doc/manual/troubleshooting.xml b/doc/manual/troubleshooting.xml index 018b3555f..6c40775db 100644 --- a/doc/manual/troubleshooting.xml +++ b/doc/manual/troubleshooting.xml @@ -35,8 +35,7 @@ - $ rm `db_archive -a -h - prefix/var/nix/db` + $ rm `db_archive -a -h prefix/var/nix/db` From 68022552d295e5a223b87a1a96814fd2586350ed Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 13 Aug 2003 15:17:36 +0000 Subject: [PATCH 0214/6440] * Put the pre-built manual and man pages in the tar distribution. --- doc/manual/Makefile.am | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index a29592f1e..8f382c3f3 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -23,3 +23,5 @@ all-local: book.html install-data-local: book.html $(INSTALL) -d $(datadir)/nix/manual $(INSTALL_DATA) book.html $(datadir)/nix/manual + +EXTRA_DIST = $(SOURCES) book.html nix.1 book.is-valid From 95b49f804456cf532e61478d7f604aed381173d7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 13 Aug 2003 15:17:57 +0000 Subject: [PATCH 0215/6440] * Manual updates. --- doc/manual/book.xml | 32 ++++++++---- doc/manual/installation.xml | 9 ++-- doc/manual/introduction.xml | 98 ++++++++++++++++++++++++++++++++++--- 3 files changed, 120 insertions(+), 19 deletions(-) diff --git a/doc/manual/book.xml b/doc/manual/book.xml index eb0475a62..63aaa8a07 100644 --- a/doc/manual/book.xml +++ b/doc/manual/book.xml @@ -11,27 +11,41 @@ Nix: The Manual + + + Eelco + Dolstra + + + 2003 + Eelco Dolstra + + + &introduction; &installation; A Guided Tour - Bla bla - - - - - Fix Language Reference - - Bla bla Nix Syntax and Semantics - Bla bla + + + + + Fix Language Reference + + + + + + Writing Builders + diff --git a/doc/manual/installation.xml b/doc/manual/installation.xml index 7d8821d47..bec9ebb21 100644 --- a/doc/manual/installation.xml +++ b/doc/manual/installation.xml @@ -26,8 +26,7 @@ - $ svn checkout http://losser.st-lab.cs.uu.nl:12080/repos/trace/nix/trunk - nix +$ svn checkout http://losser.st-lab.cs.uu.nl:12080/repos/trace/nix/trunk nix Likewise, specific releases can be obtained from the - $ autoreconf -i $ ./configure options... $ - make $ make install +$ autoreconf -i +$ ./configure options... +$ make +$ make install Currently, the only useful switch for configure is diff --git a/doc/manual/introduction.xml b/doc/manual/introduction.xml index 77a5f917e..974cdedd8 100644 --- a/doc/manual/introduction.xml +++ b/doc/manual/introduction.xml @@ -15,17 +15,17 @@ Build management - Build management tools are used to perform software + Build management tools are used to perform software builds, that is, the construction of derived products such as executable programs from source code. A commonly used build tool is Make, which is a standard tool on Unix systems. These tools have to deal with several issues: - + - + @@ -34,12 +34,12 @@ Package management - After software has been built, is must also be + After software has been built, is must also be deployed in the intended target environment, e.g., the user's workstation. Examples include the Red Hat package manager (RPM), Microsoft's MSI, and so on. Here also we have to deal with several issues: - + The creation of packages from some formal @@ -60,12 +60,98 @@ the system administrator should be constant, not linear). - + + + + + + What Nix can do for you + + + Here is a summary of what Nix provides: + + + + + + + Reliable dependencies. + + + + + + Support for variability. + + + + + + Transparent source/binary deployment. + + + + + + Easy configuration duplication. + + + + + + Automatic storage management. + + + + + + Atomic upgrades and rollbacks. + + + + + + Support for many simultaneous configurations. + + + + + + + Here is what Nix doesn't yet provide, but will: + + + + + + + Build management. In principle it is already + possible to do build management using Fix (by writing builders that + perform appropriate build steps), but the Fix language is not yet + powerful enough to make this pleasant. The Maak build manager + should be retargeted to produce Nix expressions, or alternatively, + extend Fix with Maak's semantics and concrete syntax (since Fix needs + a concrete syntax anyway). Another interesting idea is to write a + make implementation that uses Nix as a back-end to + support legacy + build files. + + + + + + + + + + The Nix system From 0a2de7f543ac23b8576a232c0ecf6a5f49c1884a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 14 Aug 2003 09:29:07 +0000 Subject: [PATCH 0216/6440] * Lam -> Function. Doh! --- src/fix.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fix.cc b/src/fix.cc index 420dd7c04..dd02d7538 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -53,7 +53,7 @@ static Expr substExpr(string x, Expr rep, Expr e) else return e; - if (ATmatch(e, "Lam(, )", &s, &e2)) + if (ATmatch(e, "Function(, )", &s, &e2)) if (x == s) return e; /* !!! unfair substitutions */ From 5cde23f8698e7cdde220f30504b339b7237fd5f2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 14 Aug 2003 09:49:31 +0000 Subject: [PATCH 0217/6440] * Function() takes a list of formals. --- src/fix.cc | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index dd02d7538..5405190f3 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -53,10 +53,18 @@ static Expr substExpr(string x, Expr rep, Expr e) else return e; - if (ATmatch(e, "Function(, )", &s, &e2)) - if (x == s) - return e; - /* !!! unfair substitutions */ + ATermList formals; + if (ATmatch(e, "Function([], )", &formals, &e2)) { + while (!ATisEmpty(formals)) { + if (!ATmatch(ATgetFirst(formals), "", &s)) + throw badTerm("not a list of formals", (ATerm) formals); + if (x == (string) s) + return e; + formals = ATgetNext(formals); + } + } + + /* ??? unfair substitutions? */ /* Generically substitute in subterms. */ @@ -332,6 +340,8 @@ static Expr evalExpr2(EvalState & state, Expr e) static Expr evalExpr(EvalState & state, Expr e) { + Nest nest(lvlVomit, format("evaluating expression: %1%") % printTerm(e)); + /* Consult the memo table to quickly get the normal form of previously evaluated expressions. */ NormalForms::iterator i = state.normalForms.find(e); From 2e16ff22ac6f3a4ea6684026a609856f29d86499 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 14 Aug 2003 11:27:02 +0000 Subject: [PATCH 0218/6440] * Fix man page. --- doc/manual/Makefile.am | 6 +++--- doc/manual/book.xml | 2 ++ doc/manual/fix-reference.xml | 37 ++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 doc/manual/fix-reference.xml diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 8f382c3f3..65f1f3f44 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -10,9 +10,9 @@ book.is-valid: $(SOURCES) nsgmls -wxml -c $(XML) -c $(DOCBOOK_DTD)/docbook.cat -ges $< touch $@ -man1_MANS = nix.1 +man1_MANS = nix.1 fix.1 -man nix.1: $(SOURCES) book.is-valid +man nix.1 fix.1: $(SOURCES) book.is-valid xsltproc $(DOCBOOK_XSL)/manpages/docbook.xsl book.xml book.html: $(SOURCES) book.is-valid @@ -24,4 +24,4 @@ install-data-local: book.html $(INSTALL) -d $(datadir)/nix/manual $(INSTALL_DATA) book.html $(datadir)/nix/manual -EXTRA_DIST = $(SOURCES) book.html nix.1 book.is-valid +EXTRA_DIST = $(SOURCES) book.html nix.1 fix.1 book.is-valid diff --git a/doc/manual/book.xml b/doc/manual/book.xml index 63aaa8a07..1f54d4efd 100644 --- a/doc/manual/book.xml +++ b/doc/manual/book.xml @@ -4,6 +4,7 @@ + ]> @@ -52,6 +53,7 @@ Command Reference &nix-reference; + &fix-reference; &troubleshooting; diff --git a/doc/manual/fix-reference.xml b/doc/manual/fix-reference.xml new file mode 100644 index 000000000..aac1be648 --- /dev/null +++ b/doc/manual/fix-reference.xml @@ -0,0 +1,37 @@ + + + fix + generate Nix expressions from a high-level description + + + + + fix + + + + + files + + + + + Description + + + The command fix generates Nix expressions from + expressions is Fix's own high-level language. While Nix expressions are + very primitive and not intended to be written directly, Fix expressions + are quite easy to write. + + + + + + + + From dc0ef2ca98ddf21586b68089d28df59ef3e756dd Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 14 Aug 2003 12:37:31 +0000 Subject: [PATCH 0219/6440] * Detect infinite loops using blackholing. --- src/fix.cc | 16 +++++++++++++--- testpkgs/infrec/infrec.fix | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 testpkgs/infrec/infrec.fix diff --git a/src/fix.cc b/src/fix.cc index 5405190f3..e50d77620 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -18,6 +18,13 @@ struct EvalState NormalForms normalForms; PkgPaths pkgPaths; PkgHashes pkgHashes; /* normalised package hashes */ + Expr blackHole; + + EvalState() + { + blackHole = ATmake("BlackHole()"); + if (!blackHole) throw Error("cannot build black hole"); + } }; @@ -64,8 +71,6 @@ static Expr substExpr(string x, Expr rep, Expr e) } } - /* ??? unfair substitutions? */ - /* Generically substitute in subterms. */ if (ATgetType(e) == AT_APPL) { @@ -345,9 +350,14 @@ static Expr evalExpr(EvalState & state, Expr e) /* Consult the memo table to quickly get the normal form of previously evaluated expressions. */ NormalForms::iterator i = state.normalForms.find(e); - if (i != state.normalForms.end()) return i->second; + if (i != state.normalForms.end()) { + if (i->second == state.blackHole) + throw badTerm("infinite recursion", e); + return i->second; + } /* Otherwise, evaluate and memoize. */ + state.normalForms[e] = state.blackHole; Expr nf = evalExpr2(state, e); state.normalForms[e] = nf; return nf; diff --git a/testpkgs/infrec/infrec.fix b/testpkgs/infrec/infrec.fix new file mode 100644 index 000000000..7db7c99ad --- /dev/null +++ b/testpkgs/infrec/infrec.fix @@ -0,0 +1 @@ +IncludeFix("infrec/infrec.fix") From 9ee3b7a37a658dc5d3d1831e4155629e18a866f4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 14 Aug 2003 12:37:50 +0000 Subject: [PATCH 0220/6440] * Function application test cases. --- testpkgs/fun/fun1.fix | 9 +++++++++ testpkgs/fun/fun2.fix | 9 +++++++++ testpkgs/fun/fun3.fix | 9 +++++++++ 3 files changed, 27 insertions(+) create mode 100644 testpkgs/fun/fun1.fix create mode 100644 testpkgs/fun/fun2.fix create mode 100644 testpkgs/fun/fun3.fix diff --git a/testpkgs/fun/fun1.fix b/testpkgs/fun/fun1.fix new file mode 100644 index 000000000..c5a9e370d --- /dev/null +++ b/testpkgs/fun/fun1.fix @@ -0,0 +1,9 @@ +Call( + Function(["x"], + Call( + Function(["x"], Var("x")), + [ ("x", Var("x")) ] + ) + ), + [ ("x", True) ] +) \ No newline at end of file diff --git a/testpkgs/fun/fun2.fix b/testpkgs/fun/fun2.fix new file mode 100644 index 000000000..5741fa6f3 --- /dev/null +++ b/testpkgs/fun/fun2.fix @@ -0,0 +1,9 @@ +Call( + Function(["x"], + Call( + Function(["y", "z"], Var("y")), + [ ("y", Var("x")) ] + ) + ), + [ ("x", True) ] +) \ No newline at end of file diff --git a/testpkgs/fun/fun3.fix b/testpkgs/fun/fun3.fix new file mode 100644 index 000000000..31399c0cb --- /dev/null +++ b/testpkgs/fun/fun3.fix @@ -0,0 +1,9 @@ +Call( + Function(["x"], + Call( + Function(["x"], Var("x")), + [ ("x", False) ] + ) + ), + [ ("x", True) ] +) \ No newline at end of file From a24cb1936141981c3b3d5cd30433bb1e57d7dc76 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 14 Aug 2003 18:17:02 +0000 Subject: [PATCH 0221/6440] * Use xmllint instead of nsgmls to validate the manual. --- doc/manual/Makefile.am | 5 ++--- doc/manual/book.xml | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 65f1f3f44..5bd256658 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -1,13 +1,12 @@ DOCBOOK_DTD = /nix/current/xml/dtd/docbook DOCBOOK_XSL = /nix/current/xml/xsl/docbook -XML = /usr/share/doc/packages/sp/html-xml/xml.soc SOURCES = book.xml introduction.xml installation.xml nix-reference.xml \ troubleshooting.xml bugs.xml book.is-valid: $(SOURCES) - SP_CHARSET_FIXED=YES SP_ENCODING=XML \ - nsgmls -wxml -c $(XML) -c $(DOCBOOK_DTD)/docbook.cat -ges $< + SGML_CATALOG_FILES=$(DOCBOOK_DTD)/docbook.cat \ + xmllint --catalogs --noout --valid book.xml touch $@ man1_MANS = nix.1 fix.1 diff --git a/doc/manual/book.xml b/doc/manual/book.xml index 1f54d4efd..a2035fca7 100644 --- a/doc/manual/book.xml +++ b/doc/manual/book.xml @@ -1,5 +1,7 @@ - From 161aab582bb3d794414c0275ff8216292f85ab5c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 14 Aug 2003 18:24:40 +0000 Subject: [PATCH 0222/6440] * Use a catalog when calling xsltproc. --- doc/manual/Makefile.am | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 5bd256658..63464002b 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -1,21 +1,25 @@ DOCBOOK_DTD = /nix/current/xml/dtd/docbook DOCBOOK_XSL = /nix/current/xml/xsl/docbook +ENV = SGML_CATALOG_FILES=$(DOCBOOK_DTD)/docbook.cat + +XMLLINT = $(ENV) xmllint --catalogs +XSLTPROC = $(ENV) xsltproc --catalogs + SOURCES = book.xml introduction.xml installation.xml nix-reference.xml \ troubleshooting.xml bugs.xml book.is-valid: $(SOURCES) - SGML_CATALOG_FILES=$(DOCBOOK_DTD)/docbook.cat \ - xmllint --catalogs --noout --valid book.xml + $(XMLLINT) --noout --valid book.xml touch $@ man1_MANS = nix.1 fix.1 man nix.1 fix.1: $(SOURCES) book.is-valid - xsltproc $(DOCBOOK_XSL)/manpages/docbook.xsl book.xml + $(XSLTPROC) $(DOCBOOK_XSL)/manpages/docbook.xsl book.xml book.html: $(SOURCES) book.is-valid - xsltproc --output book.html $(DOCBOOK_XSL)/html/docbook.xsl book.xml + $(XSLTPROC) --output book.html $(DOCBOOK_XSL)/html/docbook.xsl book.xml all-local: book.html From 163db7367fb45955069b46014e60224b1bc037b6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 15 Aug 2003 09:21:19 +0000 Subject: [PATCH 0223/6440] * Fix can now read expressions from stdin (by saying `fix -'). --- src/fix.cc | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/fix.cc b/src/fix.cc index e50d77620..2a1c09c2a 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -375,6 +375,16 @@ static Expr evalFile(EvalState & state, string relPath) } +static Expr evalStdin(EvalState & state) +{ + Nest nest(lvlTalkative, format("evaluating standard input")); + Expr e = ATreadFromFile(stdin); + if (!e) + throw Error(format("unable to read a term from stdin")); + return evalExpr(state, e); +} + + static void printFSId(EvalState & state, Expr e) { ATermList es; @@ -398,6 +408,7 @@ void run(Strings args) EvalState state; Strings files; + bool readStdin = false; state.searchDirs.push_back("."); state.searchDirs.push_back(nixDataDir + "/fix"); @@ -414,13 +425,18 @@ void run(Strings args) } else if (arg == "--verbose" || arg == "-v") verbosity = (Verbosity) ((int) verbosity + 1); + else if (arg == "-") + readStdin = true; else if (arg[0] == '-') throw UsageError(format("unknown flag `%1%`") % arg); else files.push_back(arg); } - if (files.empty()) throw UsageError("no files specified"); + if (readStdin) { + Expr e = evalStdin(state); + printFSId(state, e); + } for (Strings::iterator it = files.begin(); it != files.end(); it++) From 01e30360d46ce940d8b83f4ff7a71e8464c1422b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 15 Aug 2003 09:39:33 +0000 Subject: [PATCH 0224/6440] * Don't use a temporary file. --- scripts/nix-pull.in | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index 1fc5d863a..d7b0523d6 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -1,5 +1,7 @@ #! /usr/bin/perl -w +use IPC::Open2; + my $tmpfile = "@localstatedir@/nix/pull.tmp"; my $conffile = "@sysconfdir@/nix/prebuilts.conf"; @@ -7,9 +9,7 @@ my @ids; my @subs; my @sucs; -my $fixfile = "/tmp/nix-pull-tmp.fix"; -open FIX, ">$fixfile"; -print FIX "["; +my $fullexpr = "["; my $first = 1; open CONFFILE, "<$conffile"; @@ -61,9 +61,9 @@ while () { ", (\"id\", \"$id\")" . "])"; - print FIX "," unless ($first); + if (!$first) { $fullexpr .= "," }; $first = 0; - print FIX $fixexpr; + $fullexpr .= $fixexpr; # !!! O(n^2)? push @ids, $id; @@ -81,14 +81,16 @@ while () { } -print FIX "]"; -close FIX; +$fullexpr .= "]"; # Instantiate Nix expressions from the Fix expressions we created above. print STDERR "running fix...\n"; -open NIDS, "fix $fixfile |" or die "cannot run fix"; +$pid = open2(\*READ, \*WRITE, "fix -") or die "cannot run fix"; + +print WRITE $fullexpr; +close WRITE; my $i = 0; -while () { +while () { chomp; die unless /^([0-9a-z]{32})$/; $nid = $1; @@ -98,6 +100,9 @@ while () { push @subs, $nid; } +waitpid $pid, 0; +$? == 0 or die "fix failed"; + # Register all substitutes. print STDERR "registering substitutes...\n"; system "nix --substitute @subs"; From e374dbf89b0ba9a4f5835ef9ac30eda6df1dce6a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 15 Aug 2003 10:13:41 +0000 Subject: [PATCH 0225/6440] * A script `nix-prefetch-url' to fetch a URL, place it in the Nix store, and print its hash. --- corepkgs/fetchurl/fetchurl.sh.in | 11 +++++++- scripts/Makefile.am | 3 +- scripts/nix-prefetch-url.in | 48 ++++++++++++++++++++++++++++++++ scripts/nix-pull.in | 7 +++-- 4 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 scripts/nix-prefetch-url.in diff --git a/corepkgs/fetchurl/fetchurl.sh.in b/corepkgs/fetchurl/fetchurl.sh.in index 7e876a25e..88e4d81f2 100644 --- a/corepkgs/fetchurl/fetchurl.sh.in +++ b/corepkgs/fetchurl/fetchurl.sh.in @@ -1,7 +1,16 @@ #! /bin/sh +export PATH=/bin:/usr/bin + echo "downloading $url into $out..." -@wget@ "$url" -O "$out" || exit 1 + +prefetch=@prefix@/store/nix-prefetch-url-$md5 +if test -f "$prefetch"; then + echo "using prefetched $prefetch"; + mv $prefetch $out || exit 1 +else + @wget@ "$url" -O "$out" || exit 1 +fi actual=$(@bindir@/nix-hash --flat $out) if test "$actual" != "$md5"; then diff --git a/scripts/Makefile.am b/scripts/Makefile.am index d1ab6e4cd..4a1be7f8f 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -1,5 +1,5 @@ bin_SCRIPTS = nix-switch nix-collect-garbage \ - nix-pull nix-push + nix-pull nix-push nix-prefetch-url noinst_SCRIPTS = nix-profile.sh @@ -14,5 +14,6 @@ include ../substitute.mk EXTRA_DIST = nix-switch.in nix-collect-garbage.in \ nix-pull.in nix-push.in nix-profile.sh.in \ + nix-prefetch-url.in \ prebuilts.conf diff --git a/scripts/nix-prefetch-url.in b/scripts/nix-prefetch-url.in new file mode 100644 index 000000000..f5539eb98 --- /dev/null +++ b/scripts/nix-prefetch-url.in @@ -0,0 +1,48 @@ +#! /usr/bin/perl -w + +use strict; +use IPC::Open2; + +my $url = shift @ARGV; +defined $url or die; + +print "fetching $url...\n"; + +my $out = "@prefix@/store/nix-prefetch-url-$$"; + +system "@wget@ '$url' -O '$out'"; +$? == 0 or die "unable to fetch $url"; + +my $hash=`@bindir@/nix-hash --flat $out`; +$? == 0 or die "unable to hash $out"; +chomp $hash; + +print "file has hash $hash\n"; + +my $out2 = "@prefix@/store/nix-prefetch-url-$hash"; +rename $out, $out2; + +# Create a Fix expression. +my $fixexpr = + "App(IncludeFix(\"fetchurl/fetchurl.fix\"), " . + "[(\"url\", \"$url\"), (\"md5\", \"$hash\")])"; + +# Instantiate a Nix expression. +print STDERR "running fix...\n"; +my $pid = open2(\*READ, \*WRITE, "fix -") or die "cannot run fix"; + +print WRITE $fixexpr; +close WRITE; + +my $id = ; +chomp $id; + +waitpid $pid, 0; +$? == 0 or die "fix failed"; + +# Run Nix. +print STDERR "running nix...\n"; +system "nix --install $id > /dev/null"; +$? == 0 or die "`nix --install' failed"; + +unlink $out2; diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index d7b0523d6..a3d23ea16 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -1,5 +1,6 @@ #! /usr/bin/perl -w +use strict; use IPC::Open2; my $tmpfile = "@localstatedir@/nix/pull.tmp"; @@ -85,7 +86,7 @@ $fullexpr .= "]"; # Instantiate Nix expressions from the Fix expressions we created above. print STDERR "running fix...\n"; -$pid = open2(\*READ, \*WRITE, "fix -") or die "cannot run fix"; +my $pid = open2(\*READ, \*WRITE, "fix -") or die "cannot run fix"; print WRITE $fullexpr; close WRITE; @@ -93,9 +94,9 @@ my $i = 0; while () { chomp; die unless /^([0-9a-z]{32})$/; - $nid = $1; + my $nid = $1; die unless ($i < scalar @ids); - $id = $ids[$i++]; + my $id = $ids[$i++]; push @subs, $id; push @subs, $nid; } From 555347744d116b0152a04d4fdb08258276d34199 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 15 Aug 2003 12:32:37 +0000 Subject: [PATCH 0226/6440] * Derivation expressions now can specify arguments to be passed to the builder. Note that this unfortunately causes all Fix-computed hashes to change. --- src/exec.cc | 33 ++++++++++++++++++++++----------- src/exec.hh | 5 ++++- src/fstate.cc | 33 +++++++++++++++++++++++++++------ src/fstate.hh | 3 ++- src/normalise.cc | 2 +- 5 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/exec.cc b/src/exec.cc index 5d7140827..d82f5effa 100644 --- a/src/exec.cc +++ b/src/exec.cc @@ -35,7 +35,8 @@ public: /* Run a program. */ -void runProgram(const string & program, Environment env) +void runProgram(const string & program, + const Strings & args, const Environment & env) { /* Create a log file. */ string logFileName = nixLogDir + "/run.log"; @@ -68,15 +69,25 @@ void runProgram(const string & program, Environment env) if (chdir(tmpDir.c_str()) == -1) throw SysError(format("changing into to `%1%'") % tmpDir); - /* Fill in the environment. We don't bother freeing - the strings, since we'll exec or die soon - anyway. */ - const char * env2[env.size() + 1]; - int i = 0; - for (Environment::iterator it = env.begin(); - it != env.end(); it++, i++) - env2[i] = (new string(it->first + "=" + it->second))->c_str(); - env2[i] = 0; + /* Fill in the arguments. */ + const char * argArr[args.size() + 2]; + const char * * p = argArr; + string progName = baseNameOf(program); + *p++ = progName.c_str(); + for (Strings::const_iterator i = args.begin(); + i != args.end(); i++) + *p++ = i->c_str(); + *p = 0; + + /* Fill in the environment. */ + Strings envStrs; + const char * envArr[env.size() + 1]; + p = envArr; + for (Environment::const_iterator i = env.begin(); + i != env.end(); i++) + *p++ = envStrs.insert(envStrs.end(), + i->first + "=" + i->second)->c_str(); + *p = 0; /* Dup the log handle into stderr. */ if (dup2(fileno(logFile), STDERR_FILENO) == -1) @@ -87,7 +98,7 @@ void runProgram(const string & program, Environment env) throw SysError("cannot dup stderr into stdout"); /* Execute the program. This should not return. */ - execle(program.c_str(), baseNameOf(program).c_str(), 0, env2); + execve(program.c_str(), (char * *) argArr, (char * *) envArr); throw SysError(format("unable to execute %1%") % program); diff --git a/src/exec.hh b/src/exec.hh index 9dc8e0cd0..8d410e404 100644 --- a/src/exec.hh +++ b/src/exec.hh @@ -4,6 +4,8 @@ #include #include +#include "util.hh" + using namespace std; @@ -12,7 +14,8 @@ typedef map Environment; /* Run a program. */ -void runProgram(const string & program, Environment env); +void runProgram(const string & program, + const Strings & args, const Environment & env); #endif /* !__EXEC_H */ diff --git a/src/fstate.cc b/src/fstate.cc index 2e2857ffc..47a93901f 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -120,13 +120,19 @@ static bool parseSlice(ATerm t, Slice & slice) static bool parseDerive(ATerm t, Derive & derive) { - ATermList outs, ins, bnds; + ATermList outs, ins, args, bnds; char * builder; char * platform; - if (!ATmatch(t, "Derive([], [], , , [])", - &outs, &ins, &builder, &platform, &bnds)) - return false; + if (!ATmatch(t, "Derive([], [], , , [], [])", + &outs, &ins, &platform, &builder, &args, &bnds)) + { + /* !!! compatibility -> remove eventually */ + if (!ATmatch(t, "Derive([], [], , , [])", + &outs, &ins, &builder, &platform, &bnds)) + return false; + args = ATempty; + } while (!ATisEmpty(outs)) { char * s1, * s2; @@ -142,6 +148,15 @@ static bool parseDerive(ATerm t, Derive & derive) derive.builder = builder; derive.platform = platform; + while (!ATisEmpty(args)) { + char * s; + ATerm arg = ATgetFirst(args); + if (!ATmatch(arg, "", &s)) + throw badTerm("string expected", arg); + derive.args.push_back(s); + args = ATgetNext(args); + } + while (!ATisEmpty(bnds)) { char * s1, * s2; ATerm bnd = ATgetFirst(bnds); @@ -204,6 +219,11 @@ static ATerm unparseDerive(const Derive & derive) ATmake("(, )", i->first.c_str(), ((string) i->second).c_str())); + ATermList args = ATempty; + for (Strings::const_iterator i = derive.args.begin(); + i != derive.args.end(); i++) + args = ATinsert(args, ATmake("", i->c_str())); + ATermList env = ATempty; for (StringPairs::const_iterator i = derive.env.begin(); i != derive.env.end(); i++) @@ -211,11 +231,12 @@ static ATerm unparseDerive(const Derive & derive) ATmake("(, )", i->first.c_str(), i->second.c_str())); - return ATmake("Derive(, , , , )", + return ATmake("Derive(, , , , , )", ATreverse(outs), unparseIds(derive.inputs), - derive.builder.c_str(), derive.platform.c_str(), + derive.builder.c_str(), + ATreverse(args), ATreverse(env)); } diff --git a/src/fstate.hh b/src/fstate.hh index 969abe9e0..b71739aed 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -36,8 +36,9 @@ struct Derive { DeriveOutputs outputs; FSIds inputs; - string builder; string platform; + string builder; + Strings args; StringPairs env; }; diff --git a/src/normalise.cc b/src/normalise.cc index 2fa6f7f40..3d025d5f5 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -169,7 +169,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* Run the builder. */ msg(lvlChatty, format("building...")); - runProgram(fs.derive.builder, env); + runProgram(fs.derive.builder, fs.derive.args, env); msg(lvlChatty, format("build completed")); } else From 96c7b98bf0f852d7afee9251c4ce9492310e6a87 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 15 Aug 2003 13:01:45 +0000 Subject: [PATCH 0227/6440] * Argument support in Fix. Arguments can be passed through the builder using the `args' binding: ("args", ["bla", True, IncludeFix("aterm/aterm.fix")]) Note that packages can also be declared as inputs by specifying them in the argument list. --- src/fix.cc | 63 +++++++++++++++++++++++++------------ testpkgs/args/args-build.sh | 11 +++++++ testpkgs/args/args.fix | 7 +++++ 3 files changed, 61 insertions(+), 20 deletions(-) create mode 100755 testpkgs/args/args-build.sh create mode 100644 testpkgs/args/args.fix diff --git a/src/fix.cc b/src/fix.cc index 2a1c09c2a..66ca5f1c2 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -150,6 +150,30 @@ static Hash hashPackage(EvalState & state, FState fs) } +static string processBinding(EvalState & state, Expr e, FState & fs) +{ + char * s1; + + if (ATmatch(e, "FSId()", &s1)) { + FSId id = parseHash(s1); + Strings paths = fstatePathsCached(state, id); + if (paths.size() != 1) abort(); + string path = *(paths.begin()); + fs.derive.inputs.push_back(id); + return path; + } + + if (ATmatch(e, "", &s1)) + return s1; + + if (ATmatch(e, "True")) return "1"; + + if (ATmatch(e, "False")) return ""; + + throw badTerm("invalid package binding", e); +} + + static Expr evalExpr2(EvalState & state, Expr e) { char * s1; @@ -274,30 +298,29 @@ static Expr evalExpr2(EvalState & state, Expr e) string key = it->first; ATerm value = it->second; - if (ATmatch(value, "FSId()", &s1)) { - FSId id = parseHash(s1); - Strings paths = fstatePathsCached(state, id); - if (paths.size() != 1) abort(); - string path = *(paths.begin()); - fs.derive.inputs.push_back(id); - fs.derive.env.push_back(StringPair(key, path)); - if (key == "build") fs.derive.builder = path; - } - else if (ATmatch(value, "", &s1)) { - if (key == "name") name = s1; + if (key == "args") { + ATermList args; + if (!ATmatch(value, "[]", &args)) + throw badTerm("list expected", value); + + while (!ATisEmpty(args)) { + Expr arg = evalExpr(state, ATgetFirst(args)); + fs.derive.args.push_back(processBinding(state, arg, fs)); + args = ATgetNext(args); + } + } + + else { + string s = processBinding(state, value, fs); + fs.derive.env.push_back(StringPair(key, s)); + + if (key == "build") fs.derive.builder = s; + if (key == "name") name = s; if (key == "id") { - outId = parseHash(s1); + outId = parseHash(s); outIdGiven = true; } - fs.derive.env.push_back(StringPair(key, s1)); } - else if (ATmatch(value, "True")) { - fs.derive.env.push_back(StringPair(key, "1")); - } - else if (ATmatch(value, "False")) { - fs.derive.env.push_back(StringPair(key, "")); - } - else throw badTerm("invalid package argument", value); bnds = ATinsert(bnds, ATmake("(, )", key.c_str(), value)); diff --git a/testpkgs/args/args-build.sh b/testpkgs/args/args-build.sh new file mode 100755 index 000000000..1efcc17fe --- /dev/null +++ b/testpkgs/args/args-build.sh @@ -0,0 +1,11 @@ +#! /bin/sh + +IFS= + +echo "printing list of args" + +for i in $@; do + echo "arg: $i" +done + +touch $out \ No newline at end of file diff --git a/testpkgs/args/args.fix b/testpkgs/args/args.fix new file mode 100644 index 000000000..54a13ddba --- /dev/null +++ b/testpkgs/args/args.fix @@ -0,0 +1,7 @@ +Package( + [ ("name", "args") + , ("build", Relative("args/args-build.sh")) + + , ("args", ["1", "2", "3", IncludeFix("slow2/slow.fix")]) + ] +) From 08f9cfe267934dac5a7da869e9ebadf215220217 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 18 Aug 2003 08:35:16 +0000 Subject: [PATCH 0228/6440] * No longer automatically download Berkeley DB / ATerm. --- externals/Makefile.am | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/externals/Makefile.am b/externals/Makefile.am index 01ef93455..705255e5f 100644 --- a/externals/Makefile.am +++ b/externals/Makefile.am @@ -1,10 +1,13 @@ # Berkeley DB DB = db-4.0.14 -DB_URL = http://www.sleepycat.com/update/snapshot/db-4.0.14.tar.gz $(DB).tar.gz: - wget $(DB_URL) + echo "Nix requires Berkeley DB to build." + echo "Please download version 4.0.14 from" + echo " http://www.sleepycat.com/update/snapshot/db-4.0.14.tar.gz" + echo "and place it in the externals/ directory." + false $(DB): $(DB).tar.gz gunzip < $(DB).tar.gz | tar xvf - @@ -26,10 +29,13 @@ build-db: have-db # CWI ATerm ATERM = aterm-2.0 -ATERM_URL = http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz $(ATERM).tar.gz: - wget $(ATERM_URL) + echo "Nix requires the CWI ATerm library to build." + echo "Please download version 2.0 from" + echo " http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz" + echo "and place it in the externals/ directory." + false $(ATERM): $(ATERM).tar.gz gunzip < $(ATERM).tar.gz | tar xvf - From c32e01eab2363085160bfebc2d9ab506d265c7e4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 18 Aug 2003 08:52:49 +0000 Subject: [PATCH 0229/6440] * Revision 300! * Put `@' in front of echo's in the Makefile. --- externals/Makefile.am | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/externals/Makefile.am b/externals/Makefile.am index 705255e5f..6a3d3a1f7 100644 --- a/externals/Makefile.am +++ b/externals/Makefile.am @@ -3,10 +3,10 @@ DB = db-4.0.14 $(DB).tar.gz: - echo "Nix requires Berkeley DB to build." - echo "Please download version 4.0.14 from" - echo " http://www.sleepycat.com/update/snapshot/db-4.0.14.tar.gz" - echo "and place it in the externals/ directory." + @echo "Nix requires Berkeley DB to build." + @echo "Please download version 4.0.14 from" + @echo " http://www.sleepycat.com/update/snapshot/db-4.0.14.tar.gz" + @echo "and place it in the externals/ directory." false $(DB): $(DB).tar.gz @@ -31,10 +31,10 @@ build-db: have-db ATERM = aterm-2.0 $(ATERM).tar.gz: - echo "Nix requires the CWI ATerm library to build." - echo "Please download version 2.0 from" - echo " http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz" - echo "and place it in the externals/ directory." + @echo "Nix requires the CWI ATerm library to build." + @echo "Please download version 2.0 from" + @echo " http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz" + @echo "and place it in the externals/ directory." false $(ATERM): $(ATERM).tar.gz From ebbb6ce578ab383bec7a61c364d2be27c0bad22f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 18 Aug 2003 14:54:54 +0000 Subject: [PATCH 0230/6440] * Most shells initialise PATH to some default (/bin:/usr/bin:...) when PATH is not set. We don't want this, so fill it in with some dummy value. --- src/normalise.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/normalise.cc b/src/normalise.cc index 3d025d5f5..ad79d83fc 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -129,6 +129,11 @@ FSId normaliseFState(FSId id, FSIdSet pending) for (ElemMap::iterator i = inMap.begin(); i != inMap.end(); i++) refPaths.push_back(i->second.path); + /* Most shells initialise PATH to some default (/bin:/usr/bin:...) when + PATH is not set. We don't want this, so we fill it in with some dummy + value. */ + env["PATH"] = "/path-not-set"; + /* Build the environment. */ for (StringPairs::iterator i = fs.derive.env.begin(); i != fs.derive.env.end(); i++) From 31e4aa64396858e3b6ef8477397c84cbd80670fc Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 18 Aug 2003 16:32:55 +0000 Subject: [PATCH 0231/6440] * Allow lists in package bindings, e.g., ("srcs", [Relative("foo/bar.c"), Relative("foo/baz.h")]) The result is an environment variable that contains the path names of the inputs separated by spaces (so this is not safe for values containing spaces). --- src/fix.cc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/fix.cc b/src/fix.cc index 66ca5f1c2..ef3570334 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -169,6 +169,18 @@ static string processBinding(EvalState & state, Expr e, FState & fs) if (ATmatch(e, "True")) return "1"; if (ATmatch(e, "False")) return ""; + + ATermList l; + if (ATmatch(e, "[]", &l)) { + string s; + bool first = true; + while (!ATisEmpty(l)) { + if (!first) s = s + " "; else first = false; + s += processBinding(state, evalExpr(state, ATgetFirst(l)), fs); + l = ATgetNext(l); + } + return s; + } throw badTerm("invalid package binding", e); } From 2de850479101e5a378c87d1392ea03c63ce224cf Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 19 Aug 2003 09:04:47 +0000 Subject: [PATCH 0232/6440] * Delete the temporary directories of failed builds by default, and an option `--keep-failed' to override this behaviour. --- doc/manual/nix-reference.xml | 17 +++++++++++++++++ src/exec.cc | 6 +++++- src/globals.cc | 3 +++ src/globals.hh | 6 ++++++ src/nix-help.txt | 1 + src/nix.cc | 2 ++ 6 files changed, 34 insertions(+), 1 deletion(-) diff --git a/doc/manual/nix-reference.xml b/doc/manual/nix-reference.xml index 75009b1d0..d9c78ff07 100644 --- a/doc/manual/nix-reference.xml +++ b/doc/manual/nix-reference.xml @@ -15,6 +15,10 @@ + + + + operation options arguments @@ -121,6 +125,19 @@ + + + + + + Specifies that in case of a build failure, the temporary directory + (usually in /tmp) in which the build takes + place should not be deleted. The path of the build directory is + printed as an informational message. + + + + diff --git a/src/exec.cc b/src/exec.cc index d82f5effa..fdfb467cc 100644 --- a/src/exec.cc +++ b/src/exec.cc @@ -122,7 +122,11 @@ void runProgram(const string & program, throw Error("unable to wait for child"); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - delTmpDir.cancel(); + if (keepFailed) { + msg(lvlTalkative, + format("build failed; keeping build directory `%1%'") % tmpDir); + delTmpDir.cancel(); + } throw Error("unable to build package"); } } diff --git a/src/globals.cc b/src/globals.cc index 1ec0c4f9b..f21820f59 100644 --- a/src/globals.cc +++ b/src/globals.cc @@ -17,6 +17,9 @@ string nixLogDir = "/UNINIT"; string nixDBPath = "/UNINIT"; +bool keepFailed = false; + + void openDB() { nixDB.open(nixDBPath); diff --git a/src/globals.hh b/src/globals.hh index 2c4d33920..107d617bc 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -69,6 +69,12 @@ extern string nixLogDir; extern string nixDBPath; +/* Misc. global flags. */ + +/* Whether to keep temporary directories of failed builds. */ +extern bool keepFailed; + + /* Open the database environment. */ void openDB(); diff --git a/src/nix-help.txt b/src/nix-help.txt index 0e54d162d..4e1d707c8 100644 --- a/src/nix-help.txt +++ b/src/nix-help.txt @@ -34,3 +34,4 @@ Query flags: Options: --verbose / -v: verbose operation (may be repeated) + --keep-failed / -K: keep temporary directories of failed builds diff --git a/src/nix.cc b/src/nix.cc index 4beeb5da8..704442c31 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -372,6 +372,8 @@ void run(Strings args) pathArgs = true; else if (arg == "--verbose" || arg == "-v") verbosity = (Verbosity) ((int) verbosity + 1); + else if (arg == "--keep-failed" || arg == "-K") + keepFailed = true; else if (arg == "--help") printHelp(); else if (arg[0] == '-') From 1472cc482503a39d173b5dcd34686fd6c3c644d6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 19 Aug 2003 13:07:38 +0000 Subject: [PATCH 0233/6440] * Pipe /dev/null into stdin. --- src/exec.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/exec.cc b/src/exec.cc index fdfb467cc..d4ee88f8a 100644 --- a/src/exec.cc +++ b/src/exec.cc @@ -34,6 +34,9 @@ public: }; +static string pathNullDevice = "/dev/null"; + + /* Run a program. */ void runProgram(const string & program, const Strings & args, const Environment & env) @@ -97,6 +100,13 @@ void runProgram(const string & program, if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1) throw SysError("cannot dup stderr into stdout"); + /* Reroute stdin to /dev/null. */ + int fdDevNull = open(pathNullDevice.c_str(), O_RDWR); + if (fdDevNull == -1) + throw SysError(format("cannot open `%1%'") % pathNullDevice); + if (dup2(fdDevNull, STDIN_FILENO) == -1) + throw SysError("cannot dup null device into stdin"); + /* Execute the program. This should not return. */ execve(program.c_str(), (char * *) argArr, (char * *) envArr); From ed0db2e0d80ac538fbb1f9869922be4fbf7bfeab Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 20 Aug 2003 11:30:45 +0000 Subject: [PATCH 0234/6440] * Fixed a serious bug in the computation of slices. Sometimes the slices would not be properly closed under the path reference relation. --- src/normalise.cc | 81 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/src/normalise.cc b/src/normalise.cc index ad79d83fc..35f6b2d08 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -69,7 +69,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) ElemMap inMap; /* Referencable paths (i.e., input and output paths). */ - Strings refPaths; + Strings allPaths; /* The environment to be passed to the builder. */ Environment env; @@ -81,7 +81,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) { debug(format("building %1% in `%2%'") % (string) i->second % i->first); outPaths[i->first] = i->second; - refPaths.push_back(i->first); + allPaths.push_back(i->first); } /* Obtain locks on all output paths. The locks are automatically @@ -127,7 +127,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) } for (ElemMap::iterator i = inMap.begin(); i != inMap.end(); i++) - refPaths.push_back(i->second.path); + allPaths.push_back(i->second.path); /* Most shells initialise PATH to some default (/bin:/usr/bin:...) when PATH is not set. We don't want this, so we fill it in with some dummy @@ -182,7 +182,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* Check whether the output paths were created, and grep each output path to determine what other paths it references. */ - FSIdSet used; + StringSet usedPaths; for (OutPaths::iterator i = outPaths.begin(); i != outPaths.end(); i++) { @@ -191,41 +191,82 @@ FSId normaliseFState(FSId id, FSIdSet pending) throw Error(format("path `%1%' does not exist") % path); fs.slice.roots.push_back(i->second); - Strings refs = filterReferences(path, refPaths); + /* For this output path, find the references to other paths contained + in it. */ + Strings refPaths = filterReferences(path, allPaths); + /* Construct a slice element for this output path. */ SliceElem elem; elem.path = path; elem.id = i->second; - for (Strings::iterator j = refs.begin(); j != refs.end(); j++) { + /* For each path referenced by this output path, add its id to the + slice element and add the id to the `used' set (so that the + elements referenced by *its* slice are added below). */ + for (Strings::iterator j = refPaths.begin(); + j != refPaths.end(); j++) + { + string path = *j; ElemMap::iterator k; OutPaths::iterator l; - if ((k = inMap.find(*j)) != inMap.end()) { + + /* Is it an input path? */ + if ((k = inMap.find(path)) != inMap.end()) { elem.refs.push_back(k->second.id); - used.insert(k->second.id); - for (FSIds::iterator m = k->second.refs.begin(); - m != k->second.refs.end(); m++) - used.insert(*m); - } else if ((l = outPaths.find(*j)) != outPaths.end()) { + usedPaths.insert(k->second.path); + } + + /* Or an output path? */ + else if ((l = outPaths.find(path)) != outPaths.end()) elem.refs.push_back(l->second); - used.insert(l->second); - } else - throw Error(format("unknown referenced path `%1%'") % *j); + + /* Can't happen. */ + else abort(); } fs.slice.elems.push_back(elem); } + /* Close the slice. That is, for any referenced path, add the paths + referenced by it. */ + FSIdSet donePaths; + + while (!usedPaths.empty()) { + StringSet::iterator i = usedPaths.begin(); + string path = *i; + usedPaths.erase(i); + + ElemMap::iterator j = inMap.find(path); + if (j == inMap.end()) abort(); + + donePaths.insert(j->second.id); + + fs.slice.elems.push_back(j->second); + + for (FSIds::iterator k = j->second.refs.begin(); + k != j->second.refs.end(); k++) + if (donePaths.find(*k) == donePaths.end()) { + /* !!! performance */ + bool found = false; + for (ElemMap::iterator l = inMap.begin(); + l != inMap.end(); l++) + if (l->second.id == *k) { + usedPaths.insert(l->first); + found = true; + } + if (!found) abort(); + } + } + + /* For debugging, print out the referenced and unreferenced paths. */ for (ElemMap::iterator i = inMap.begin(); i != inMap.end(); i++) { - FSIdSet::iterator j = used.find(i->second.id); - if (j == used.end()) + FSIdSet::iterator j = donePaths.find(i->second.id); + if (j == donePaths.end()) debug(format("NOT referenced: `%1%'") % i->second.path); - else { + else debug(format("referenced: `%1%'") % i->second.path); - fs.slice.elems.push_back(i->second); - } } /* Write the normal form. This does not have to occur in the From 710175e6a0f737f108e802d6b0c3de0af04e500c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 20 Aug 2003 11:31:15 +0000 Subject: [PATCH 0235/6440] * Bumped the version number to 0.3. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 3dd3df9c6..3ad8ed7ff 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT(nix, 0.2pre1) +AC_INIT(nix, 0.3) AC_CONFIG_SRCDIR(src/nix.cc) AC_CONFIG_AUX_DIR(config) AM_INIT_AUTOMAKE From 624c48260f1b4eec86daa0da5f33d4cbb963a361 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 20 Aug 2003 12:39:56 +0000 Subject: [PATCH 0236/6440] * Change the abstract syntax of slices. It used to be that ids were used as keys to reference slice elements, e.g., Slice(["1ef7..."], [("/nix/store/1ef7...-foo", "1ef7", ["8c99..."]), ...]) This was wrong, since ids represent contents, not locations. Therefore we now have: Slice(["/nix/store/1ef7..."], [("/nix/store/1ef7...-foo", "1ef7", ["/nix/store/8c99-..."]), ...]) * Fix a bug in the computation of slice closures that could cause slice elements to be duplicated. --- src/fix.cc | 2 +- src/fstate.cc | 61 +++++++++++++++++++++++++++++------------------- src/fstate.hh | 4 ++-- src/normalise.cc | 53 +++++++++++++++-------------------------- 4 files changed, 59 insertions(+), 61 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index ef3570334..83e25d142 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -268,7 +268,7 @@ static Expr evalExpr2(EvalState & state, Expr e) elem.id = id; FState fs; fs.type = FState::fsSlice; - fs.slice.roots.push_back(id); + fs.slice.roots.push_back(dstPath); fs.slice.elems.push_back(elem); Hash pkgHash = hashPackage(state, fs); diff --git a/src/fstate.cc b/src/fstate.cc index 47a93901f..96826b4a2 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -52,15 +52,15 @@ FSId writeTerm(ATerm t, const string & suffix, FSId id) } -static void parseIds(ATermList ids, FSIds & out) +static void parsePaths(ATermList paths, Strings & out) { - while (!ATisEmpty(ids)) { + while (!ATisEmpty(paths)) { char * s; - ATerm id = ATgetFirst(ids); - if (!ATmatch(id, "", &s)) - throw badTerm("not an id", id); - out.push_back(parseHash(s)); - ids = ATgetNext(ids); + ATerm t = ATgetFirst(paths); + if (!ATmatch(t, "", &s)) + throw badTerm("not a path", t); + out.push_back(s); + paths = ATgetNext(paths); } } @@ -70,22 +70,24 @@ static void checkSlice(const Slice & slice) if (slice.elems.size() == 0) throw Error("empty slice"); - FSIdSet decl; + StringSet decl; for (SliceElems::const_iterator i = slice.elems.begin(); i != slice.elems.end(); i++) - decl.insert(i->id); + decl.insert(i->path); - for (FSIds::const_iterator i = slice.roots.begin(); + for (Strings::const_iterator i = slice.roots.begin(); i != slice.roots.end(); i++) if (decl.find(*i) == decl.end()) - throw Error(format("undefined id: %1%") % (string) *i); + throw Error(format("undefined root path `%1%'") % *i); for (SliceElems::const_iterator i = slice.elems.begin(); i != slice.elems.end(); i++) - for (FSIds::const_iterator j = i->refs.begin(); + for (Strings::const_iterator j = i->refs.begin(); j != i->refs.end(); j++) if (decl.find(*j) == decl.end()) - throw Error(format("undefined id: %1%") % (string) *j); + throw Error( + format("undefined path `%1%' referenced by `%2%'") + % *j % i->path); } @@ -97,7 +99,7 @@ static bool parseSlice(ATerm t, Slice & slice) if (!ATmatch(t, "Slice([], [])", &roots, &elems)) return false; - parseIds(roots, slice.roots); + parsePaths(roots, slice.roots); while (!ATisEmpty(elems)) { char * s1, * s2; @@ -108,7 +110,7 @@ static bool parseSlice(ATerm t, Slice & slice) SliceElem elem; elem.path = s1; elem.id = parseHash(s2); - parseIds(refs, elem.refs); + parsePaths(refs, elem.refs); slice.elems.push_back(elem); elems = ATgetNext(elems); } @@ -143,7 +145,14 @@ static bool parseDerive(ATerm t, Derive & derive) outs = ATgetNext(outs); } - parseIds(ins, derive.inputs); + while (!ATisEmpty(ins)) { + char * s; + ATerm t = ATgetFirst(ins); + if (!ATmatch(t, "", &s)) + throw badTerm("not an id", t); + derive.inputs.push_back(parseHash(s)); + ins = ATgetNext(ins); + } derive.builder = builder; derive.platform = platform; @@ -182,20 +191,19 @@ FState parseFState(ATerm t) } -static ATermList unparseIds(const FSIds & ids) +static ATermList unparsePaths(const Strings & paths) { ATermList l = ATempty; - for (FSIds::const_iterator i = ids.begin(); - i != ids.end(); i++) - l = ATinsert(l, - ATmake("", ((string) *i).c_str())); + for (Strings::const_iterator i = paths.begin(); + i != paths.end(); i++) + l = ATinsert(l, ATmake("", i->c_str())); return ATreverse(l); } static ATerm unparseSlice(const Slice & slice) { - ATermList roots = unparseIds(slice.roots); + ATermList roots = unparsePaths(slice.roots); ATermList elems = ATempty; for (SliceElems::const_iterator i = slice.elems.begin(); @@ -204,7 +212,7 @@ static ATerm unparseSlice(const Slice & slice) ATmake("(, , )", i->path.c_str(), ((string) i->id).c_str(), - unparseIds(i->refs))); + unparsePaths(i->refs))); return ATmake("Slice(, )", roots, elems); } @@ -219,6 +227,11 @@ static ATerm unparseDerive(const Derive & derive) ATmake("(, )", i->first.c_str(), ((string) i->second).c_str())); + ATermList ins = ATempty; + for (FSIds::const_iterator i = derive.inputs.begin(); + i != derive.inputs.end(); i++) + ins = ATinsert(ins, ATmake("", ((string) *i).c_str())); + ATermList args = ATempty; for (Strings::const_iterator i = derive.args.begin(); i != derive.args.end(); i++) @@ -233,7 +246,7 @@ static ATerm unparseDerive(const Derive & derive) return ATmake("Derive(, , , , , )", ATreverse(outs), - unparseIds(derive.inputs), + ATreverse(ins), derive.platform.c_str(), derive.builder.c_str(), ATreverse(args), diff --git a/src/fstate.hh b/src/fstate.hh index b71739aed..e4f69cb23 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -16,14 +16,14 @@ struct SliceElem { string path; FSId id; - FSIds refs; + Strings refs; }; typedef list SliceElems; struct Slice { - FSIds roots; + Strings roots; SliceElems elems; }; diff --git a/src/normalise.cc b/src/normalise.cc index 35f6b2d08..09fcc2238 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -101,7 +101,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) if (id2 != id) { FState fs = parseFState(termFromId(id2)); debug(format("skipping build of %1%, someone beat us to it") - % (string) id); + % (string) id); if (fs.type != FState::fsSlice) abort(); return id2; } @@ -110,7 +110,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* Right platform? */ if (fs.derive.platform != thisSystem) throw Error(format("a `%1%' is required, but I am a `%2%'") - % fs.derive.platform % thisSystem); + % fs.derive.platform % thisSystem); /* Realise inputs (and remember all input paths). */ for (FSIds::iterator i = fs.derive.inputs.begin(); @@ -149,7 +149,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) expandId(i->second, i->first, "/", pending); } catch (Error & e) { debug(format("fast build failed for `%1%': %2%") - % i->first % e.what()); + % i->first % e.what()); fastBuild = false; break; } @@ -189,7 +189,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) string path = i->first; if (!pathExists(path)) throw Error(format("path `%1%' does not exist") % path); - fs.slice.roots.push_back(i->second); + fs.slice.roots.push_back(path); /* For this output path, find the references to other paths contained in it. */ @@ -201,7 +201,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) elem.id = i->second; /* For each path referenced by this output path, add its id to the - slice element and add the id to the `used' set (so that the + slice element and add the id to the `usedPaths' set (so that the elements referenced by *its* slice are added below). */ for (Strings::iterator j = refPaths.begin(); j != refPaths.end(); j++) @@ -210,18 +210,12 @@ FSId normaliseFState(FSId id, FSIdSet pending) ElemMap::iterator k; OutPaths::iterator l; - /* Is it an input path? */ - if ((k = inMap.find(path)) != inMap.end()) { - elem.refs.push_back(k->second.id); - usedPaths.insert(k->second.path); - } + elem.refs.push_back(path); - /* Or an output path? */ - else if ((l = outPaths.find(path)) != outPaths.end()) - elem.refs.push_back(l->second); - - /* Can't happen. */ - else abort(); + if ((k = inMap.find(path)) != inMap.end()) + usedPaths.insert(k->second.path); + else if ((l = outPaths.find(path)) == outPaths.end()) + abort(); } fs.slice.elems.push_back(elem); @@ -229,40 +223,31 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* Close the slice. That is, for any referenced path, add the paths referenced by it. */ - FSIdSet donePaths; + StringSet donePaths; while (!usedPaths.empty()) { StringSet::iterator i = usedPaths.begin(); string path = *i; usedPaths.erase(i); + if (donePaths.find(path) != donePaths.end()) continue; + donePaths.insert(path); + ElemMap::iterator j = inMap.find(path); if (j == inMap.end()) abort(); - donePaths.insert(j->second.id); - fs.slice.elems.push_back(j->second); - for (FSIds::iterator k = j->second.refs.begin(); + for (Strings::iterator k = j->second.refs.begin(); k != j->second.refs.end(); k++) - if (donePaths.find(*k) == donePaths.end()) { - /* !!! performance */ - bool found = false; - for (ElemMap::iterator l = inMap.begin(); - l != inMap.end(); l++) - if (l->second.id == *k) { - usedPaths.insert(l->first); - found = true; - } - if (!found) abort(); - } + usedPaths.insert(*k); } /* For debugging, print out the referenced and unreferenced paths. */ for (ElemMap::iterator i = inMap.begin(); i != inMap.end(); i++) { - FSIdSet::iterator j = donePaths.find(i->second.id); + StringSet::iterator j = donePaths.find(i->second.path); if (j == donePaths.end()) debug(format("NOT referenced: `%1%'") % i->second.path); else @@ -319,11 +304,11 @@ Strings fstatePaths(const FSId & id) if (fs.type == FState::fsSlice) { /* !!! fix complexity */ - for (FSIds::const_iterator i = fs.slice.roots.begin(); + for (Strings::const_iterator i = fs.slice.roots.begin(); i != fs.slice.roots.end(); i++) for (SliceElems::const_iterator j = fs.slice.elems.begin(); j != fs.slice.elems.end(); j++) - if (*i == j->id) paths.push_back(j->path); + if (*i == j->path) paths.push_back(j->path); } else if (fs.type == FState::fsDerive) { From 956801fcc2ac75fd4041f61619451d2935fa2598 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 20 Aug 2003 14:11:40 +0000 Subject: [PATCH 0237/6440] * Use maps and sets in the FState data type. This ensures normalisation of slices and derivations w.r.t. order of paths, slice elements, etc. --- src/fix.cc | 19 ++++---- src/fstate.cc | 35 +++++++------- src/fstate.hh | 15 +++--- src/nix.cc | 4 +- src/normalise.cc | 118 ++++++++++++++++++++--------------------------- 5 files changed, 86 insertions(+), 105 deletions(-) diff --git a/src/fix.cc b/src/fix.cc index 83e25d142..38c53c6d7 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -137,14 +137,16 @@ static Strings fstatePathsCached(EvalState & state, const FSId & id) static Hash hashPackage(EvalState & state, FState fs) { if (fs.type == FState::fsDerive) { - for (FSIds::iterator i = fs.derive.inputs.begin(); + FSIdSet inputs2; + for (FSIdSet::iterator i = fs.derive.inputs.begin(); i != fs.derive.inputs.end(); i++) { PkgHashes::iterator j = state.pkgHashes.find(*i); if (j == state.pkgHashes.end()) throw Error(format("unknown package id %1%") % (string) *i); - *i = j->second; + inputs2.insert(j->second); } + fs.derive.inputs = inputs2; } return hashTerm(unparseFState(fs)); } @@ -159,7 +161,7 @@ static string processBinding(EvalState & state, Expr e, FState & fs) Strings paths = fstatePathsCached(state, id); if (paths.size() != 1) abort(); string path = *(paths.begin()); - fs.derive.inputs.push_back(id); + fs.derive.inputs.insert(id); return path; } @@ -264,12 +266,11 @@ static Expr evalExpr2(EvalState & state, Expr e) addToStore(srcPath, dstPath, id, true); SliceElem elem; - elem.path = dstPath; elem.id = id; FState fs; fs.type = FState::fsSlice; - fs.slice.roots.push_back(dstPath); - fs.slice.elems.push_back(elem); + fs.slice.roots.insert(dstPath); + fs.slice.elems[dstPath] = elem; Hash pkgHash = hashPackage(state, fs); FSId pkgId = writeTerm(unparseFState(fs), ""); @@ -324,7 +325,7 @@ static Expr evalExpr2(EvalState & state, Expr e) else { string s = processBinding(state, value, fs); - fs.derive.env.push_back(StringPair(key, s)); + fs.derive.env[key] = s; if (key == "build") fs.derive.builder = s; if (key == "name") name = s; @@ -349,8 +350,8 @@ static Expr evalExpr2(EvalState & state, Expr e) if (!outIdGiven) outId = hashPackage(state, fs); string outPath = canonPath(nixStore + "/" + ((string) outId).c_str() + "-" + name); - fs.derive.env.push_back(StringPair("out", outPath)); - fs.derive.outputs.push_back(DeriveOutput(outPath, outId)); + fs.derive.env["out"] = outPath; + fs.derive.outputs[outPath] = outId; /* Write the resulting term into the Nix store directory. */ Hash pkgHash = outIdGiven diff --git a/src/fstate.cc b/src/fstate.cc index 96826b4a2..0502a8524 100644 --- a/src/fstate.cc +++ b/src/fstate.cc @@ -52,14 +52,14 @@ FSId writeTerm(ATerm t, const string & suffix, FSId id) } -static void parsePaths(ATermList paths, Strings & out) +static void parsePaths(ATermList paths, StringSet & out) { while (!ATisEmpty(paths)) { char * s; ATerm t = ATgetFirst(paths); if (!ATmatch(t, "", &s)) throw badTerm("not a path", t); - out.push_back(s); + out.insert(s); paths = ATgetNext(paths); } } @@ -73,21 +73,21 @@ static void checkSlice(const Slice & slice) StringSet decl; for (SliceElems::const_iterator i = slice.elems.begin(); i != slice.elems.end(); i++) - decl.insert(i->path); + decl.insert(i->first); - for (Strings::const_iterator i = slice.roots.begin(); + for (StringSet::const_iterator i = slice.roots.begin(); i != slice.roots.end(); i++) if (decl.find(*i) == decl.end()) throw Error(format("undefined root path `%1%'") % *i); for (SliceElems::const_iterator i = slice.elems.begin(); i != slice.elems.end(); i++) - for (Strings::const_iterator j = i->refs.begin(); - j != i->refs.end(); j++) + for (StringSet::const_iterator j = i->second.refs.begin(); + j != i->second.refs.end(); j++) if (decl.find(*j) == decl.end()) throw Error( format("undefined path `%1%' referenced by `%2%'") - % *j % i->path); + % *j % i->first); } @@ -108,10 +108,9 @@ static bool parseSlice(ATerm t, Slice & slice) if (!ATmatch(t, "(, , [])", &s1, &s2, &refs)) throw badTerm("not a slice element", t); SliceElem elem; - elem.path = s1; elem.id = parseHash(s2); parsePaths(refs, elem.refs); - slice.elems.push_back(elem); + slice.elems[s1] = elem; elems = ATgetNext(elems); } @@ -141,7 +140,7 @@ static bool parseDerive(ATerm t, Derive & derive) ATerm t = ATgetFirst(outs); if (!ATmatch(t, "(, )", &s1, &s2)) throw badTerm("not a derive output", t); - derive.outputs.push_back(DeriveOutput(s1, parseHash(s2))); + derive.outputs[s1] = parseHash(s2); outs = ATgetNext(outs); } @@ -150,7 +149,7 @@ static bool parseDerive(ATerm t, Derive & derive) ATerm t = ATgetFirst(ins); if (!ATmatch(t, "", &s)) throw badTerm("not an id", t); - derive.inputs.push_back(parseHash(s)); + derive.inputs.insert(parseHash(s)); ins = ATgetNext(ins); } @@ -171,7 +170,7 @@ static bool parseDerive(ATerm t, Derive & derive) ATerm bnd = ATgetFirst(bnds); if (!ATmatch(bnd, "(, )", &s1, &s2)) throw badTerm("tuple of strings expected", bnd); - derive.env.push_back(StringPair(s1, s2)); + derive.env[s1] = s2; bnds = ATgetNext(bnds); } @@ -191,10 +190,10 @@ FState parseFState(ATerm t) } -static ATermList unparsePaths(const Strings & paths) +static ATermList unparsePaths(const StringSet & paths) { ATermList l = ATempty; - for (Strings::const_iterator i = paths.begin(); + for (StringSet::const_iterator i = paths.begin(); i != paths.end(); i++) l = ATinsert(l, ATmake("", i->c_str())); return ATreverse(l); @@ -210,9 +209,9 @@ static ATerm unparseSlice(const Slice & slice) i != slice.elems.end(); i++) elems = ATinsert(elems, ATmake("(, , )", - i->path.c_str(), - ((string) i->id).c_str(), - unparsePaths(i->refs))); + i->first.c_str(), + ((string) i->second.id).c_str(), + unparsePaths(i->second.refs))); return ATmake("Slice(, )", roots, elems); } @@ -228,7 +227,7 @@ static ATerm unparseDerive(const Derive & derive) i->first.c_str(), ((string) i->second).c_str())); ATermList ins = ATempty; - for (FSIds::const_iterator i = derive.inputs.begin(); + for (FSIdSet::const_iterator i = derive.inputs.begin(); i != derive.inputs.end(); i++) ins = ATinsert(ins, ATmake("", ((string) *i).c_str())); diff --git a/src/fstate.hh b/src/fstate.hh index e4f69cb23..a7935be52 100644 --- a/src/fstate.hh +++ b/src/fstate.hh @@ -14,28 +14,25 @@ typedef list FSIds; struct SliceElem { - string path; FSId id; - Strings refs; + StringSet refs; }; -typedef list SliceElems; +typedef map SliceElems; struct Slice { - Strings roots; + StringSet roots; SliceElems elems; }; -typedef pair DeriveOutput; -typedef pair StringPair; -typedef list DeriveOutputs; -typedef list StringPairs; +typedef map DeriveOutputs; +typedef map StringPairs; struct Derive { DeriveOutputs outputs; - FSIds inputs; + FSIdSet inputs; string platform; string builder; Strings args; diff --git a/src/nix.cc b/src/nix.cc index 704442c31..04195e8d4 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -193,7 +193,7 @@ static void opQuery(Strings opFlags, Strings opArgs) string label, shape; if (fs.type == FState::fsDerive) { - for (FSIds::iterator i = fs.derive.inputs.begin(); + for (FSIdSet::iterator i = fs.derive.inputs.begin(); i != fs.derive.inputs.end(); i++) { workList.push_back(*i); @@ -209,7 +209,7 @@ static void opQuery(Strings opFlags, Strings opArgs) } else if (fs.type == FState::fsSlice) { - label = baseNameOf((*fs.slice.elems.begin()).path); + label = baseNameOf((*fs.slice.elems.begin()).first); shape = "ellipse"; if (isHash(string(label, 0, Hash::hashSize * 2)) && label[Hash::hashSize * 2] == '-') diff --git a/src/normalise.cc b/src/normalise.cc index 09fcc2238..52437059a 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -26,14 +26,10 @@ static FSId useSuccessor(const FSId & id) } -typedef map OutPaths; -typedef map ElemMap; - - -Strings pathsFromOutPaths(const OutPaths & ps) +Strings pathsFromOutputs(const DeriveOutputs & ps) { Strings ss; - for (OutPaths::const_iterator i = ps.begin(); + for (DeriveOutputs::const_iterator i = ps.begin(); i != ps.end(); i++) ss.push_back(i->first); return ss; @@ -62,31 +58,31 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* Some variables. */ - /* Output paths, with their ids. */ - OutPaths outPaths; - /* Input paths, with their slice elements. */ - ElemMap inMap; + SliceElems inSlices; /* Referencable paths (i.e., input and output paths). */ - Strings allPaths; + StringSet allPaths; /* The environment to be passed to the builder. */ Environment env; + /* The result. */ + FState nfFS; + nfFS.type = FState::fsSlice; + /* Parse the outputs. */ for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); i != fs.derive.outputs.end(); i++) { debug(format("building %1% in `%2%'") % (string) i->second % i->first); - outPaths[i->first] = i->second; - allPaths.push_back(i->first); + allPaths.insert(i->first); } /* Obtain locks on all output paths. The locks are automatically released when we exit this function or Nix crashes. */ - PathLocks outputLocks(pathsFromOutPaths(outPaths)); + PathLocks outputLocks(pathsFromOutputs(fs.derive.outputs)); /* Now check again whether there is a successor. This is because another process may have started building in parallel. After @@ -113,8 +109,9 @@ FSId normaliseFState(FSId id, FSIdSet pending) % fs.derive.platform % thisSystem); /* Realise inputs (and remember all input paths). */ - for (FSIds::iterator i = fs.derive.inputs.begin(); - i != fs.derive.inputs.end(); i++) { + for (FSIdSet::iterator i = fs.derive.inputs.begin(); + i != fs.derive.inputs.end(); i++) + { FSId nf = normaliseFState(*i, pending); realiseSlice(nf, pending); /* !!! nf should be a root of the garbage collector while we @@ -123,12 +120,12 @@ FSId normaliseFState(FSId id, FSIdSet pending) if (fs.type != FState::fsSlice) abort(); for (SliceElems::iterator j = fs.slice.elems.begin(); j != fs.slice.elems.end(); j++) - inMap[j->path] = *j; + { + inSlices[j->first] = j->second; + allPaths.insert(j->first); + } } - for (ElemMap::iterator i = inMap.begin(); i != inMap.end(); i++) - allPaths.push_back(i->second.path); - /* Most shells initialise PATH to some default (/bin:/usr/bin:...) when PATH is not set. We don't want this, so we fill it in with some dummy value. */ @@ -142,8 +139,8 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* We can skip running the builder if we can expand all output paths from their ids. */ bool fastBuild = true; - for (OutPaths::iterator i = outPaths.begin(); - i != outPaths.end(); i++) + for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); + i != fs.derive.outputs.end(); i++) { try { expandId(i->second, i->first, "/", pending); @@ -159,8 +156,8 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* If any of the outputs already exist but are not registered, delete them. */ - for (OutPaths::iterator i = outPaths.begin(); - i != outPaths.end(); i++) + for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); + i != fs.derive.outputs.end(); i++) { string path = i->first; FSId id; @@ -183,21 +180,21 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* Check whether the output paths were created, and grep each output path to determine what other paths it references. */ StringSet usedPaths; - for (OutPaths::iterator i = outPaths.begin(); - i != outPaths.end(); i++) + for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); + i != fs.derive.outputs.end(); i++) { string path = i->first; if (!pathExists(path)) throw Error(format("path `%1%' does not exist") % path); - fs.slice.roots.push_back(path); + nfFS.slice.roots.insert(path); /* For this output path, find the references to other paths contained in it. */ - Strings refPaths = filterReferences(path, allPaths); + Strings refPaths = filterReferences(path, + Strings(allPaths.begin(), allPaths.end())); /* Construct a slice element for this output path. */ SliceElem elem; - elem.path = path; elem.id = i->second; /* For each path referenced by this output path, add its id to the @@ -207,18 +204,14 @@ FSId normaliseFState(FSId id, FSIdSet pending) j != refPaths.end(); j++) { string path = *j; - ElemMap::iterator k; - OutPaths::iterator l; - - elem.refs.push_back(path); - - if ((k = inMap.find(path)) != inMap.end()) - usedPaths.insert(k->second.path); - else if ((l = outPaths.find(path)) == outPaths.end()) + elem.refs.insert(path); + if (inSlices.find(path) != inSlices.end()) + usedPaths.insert(path); + else if (fs.derive.outputs.find(path) == fs.derive.outputs.end()) abort(); } - fs.slice.elems.push_back(elem); + nfFS.slice.elems[path] = elem; } /* Close the slice. That is, for any referenced path, add the paths @@ -233,31 +226,30 @@ FSId normaliseFState(FSId id, FSIdSet pending) if (donePaths.find(path) != donePaths.end()) continue; donePaths.insert(path); - ElemMap::iterator j = inMap.find(path); - if (j == inMap.end()) abort(); + SliceElems::iterator j = inSlices.find(path); + if (j == inSlices.end()) abort(); - fs.slice.elems.push_back(j->second); + nfFS.slice.elems[path] = j->second; - for (Strings::iterator k = j->second.refs.begin(); + for (StringSet::iterator k = j->second.refs.begin(); k != j->second.refs.end(); k++) usedPaths.insert(*k); } /* For debugging, print out the referenced and unreferenced paths. */ - for (ElemMap::iterator i = inMap.begin(); - i != inMap.end(); i++) + for (SliceElems::iterator i = inSlices.begin(); + i != inSlices.end(); i++) { - StringSet::iterator j = donePaths.find(i->second.path); + StringSet::iterator j = donePaths.find(i->first); if (j == donePaths.end()) - debug(format("NOT referenced: `%1%'") % i->second.path); + debug(format("NOT referenced: `%1%'") % i->first); else - debug(format("referenced: `%1%'") % i->second.path); + debug(format("referenced: `%1%'") % i->first); } /* Write the normal form. This does not have to occur in the transaction below because writing terms is idem-potent. */ - fs.type = FState::fsSlice; - ATerm nf = unparseFState(fs); + ATerm nf = unparseFState(nfFS); msg(lvlVomit, format("normal form: %1%") % printTerm(nf)); FSId idNF = writeTerm(nf, "-s-" + (string) id); @@ -268,8 +260,8 @@ FSId normaliseFState(FSId id, FSIdSet pending) deleted arbitrarily, while registered paths can only be deleted by running the garbage collector. */ Transaction txn(nixDB); - for (OutPaths::iterator i = outPaths.begin(); - i != outPaths.end(); i++) + for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); + i != fs.derive.outputs.end(); i++) registerPath(txn, i->first, i->second); registerSuccessor(txn, id, idNF); txn.commit(); @@ -289,10 +281,7 @@ void realiseSlice(const FSId & id, FSIdSet pending) for (SliceElems::const_iterator i = fs.slice.elems.begin(); i != fs.slice.elems.end(); i++) - { - SliceElem elem = *i; - expandId(elem.id, elem.path, "/", pending); - } + expandId(i->second.id, i->first, "/", pending); } @@ -303,12 +292,9 @@ Strings fstatePaths(const FSId & id) FState fs = parseFState(termFromId(id)); if (fs.type == FState::fsSlice) { - /* !!! fix complexity */ - for (Strings::const_iterator i = fs.slice.roots.begin(); + for (StringSet::const_iterator i = fs.slice.roots.begin(); i != fs.slice.roots.end(); i++) - for (SliceElems::const_iterator j = fs.slice.elems.begin(); - j != fs.slice.elems.end(); j++) - if (*i == j->path) paths.push_back(j->path); + paths.push_back(*i); } else if (fs.type == FState::fsDerive) { @@ -328,18 +314,16 @@ static void fstateRequisitesSet(const FSId & id, { FState fs = parseFState(termFromId(id)); - if (fs.type == FState::fsSlice) { + if (fs.type == FState::fsSlice) for (SliceElems::iterator i = fs.slice.elems.begin(); i != fs.slice.elems.end(); i++) - paths.insert(i->path); - } + paths.insert(i->first); - else if (fs.type == FState::fsDerive) { - for (FSIds::iterator i = fs.derive.inputs.begin(); + else if (fs.type == FState::fsDerive) + for (FSIdSet::iterator i = fs.derive.inputs.begin(); i != fs.derive.inputs.end(); i++) fstateRequisitesSet(*i, includeExprs, includeSuccessors, paths); - } else abort(); @@ -394,7 +378,7 @@ FSIds findGenerators(const FSIds & _ids) bool okay = true; for (SliceElems::const_iterator i = fs.slice.elems.begin(); i != fs.slice.elems.end(); i++) - if (ids.find(i->id) == ids.end()) { + if (ids.find(i->second.id) == ids.end()) { okay = false; break; } From 56b98c3857b89d4f81f0127c53cfce6d8e48a71f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 22 Aug 2003 11:29:20 +0000 Subject: [PATCH 0238/6440] * Some work on the introduction. --- doc/manual/introduction.xml | 127 +++++++++++++++++++++++++++++++++--- 1 file changed, 117 insertions(+), 10 deletions(-) diff --git a/doc/manual/introduction.xml b/doc/manual/introduction.xml index 974cdedd8..5eea76459 100644 --- a/doc/manual/introduction.xml +++ b/doc/manual/introduction.xml @@ -16,15 +16,72 @@ Build management tools are used to perform software - builds, that is, the construction of derived products such - as executable programs from source code. A commonly used build tool is - Make, which is a standard tool on Unix systems. These tools have to - deal with several issues: + builds, that is, the construction of derived products + (derivates)) such as executable programs from + source code. A commonly used build tool is Make, which is a standard + tool on Unix systems. These tools have to deal with several issues: + + Efficiency. Since building large systems + can take a substantial amount of time, it is desirable that build + steps that have been performed in the past are not repeated + unnecessarily, i.e., if a new build differs from a previous build + only with respect to certain sources, then only the build steps + that (directly or indirectly) depend on + those sources should be redone. + + + + Correctness is this context means that the + derivates produced by a build are always consistent with the + sources, that is, they are equal to what we would get if we were + to build the derivates from those sources. This requirement is + trivially met when we do a full, unconditional build, but is far + from trivial under the requirement of efficiency, since it is not + easy to determine which derivates are affected by a change to a + source. + + + + + + Variability is the property that a software + system can be built in a (potentially large) number of variants. + Variation exists both in time---the + evolution of different versions of an artifact---and in + space---the artifact might have + configuration options that lead to variants that differ in the + features they support (for example, a system might be built with + or without debugging information). + + + + Build managers historically have had good support for variation + in time (rebuilding the system in an intelligent way when sources + change is one of the primary reasons to use a build manager), but + not always for variation in space. For example, + make will not automatically ensure that + variant builds are properly isolated from each other (they will + in fact overwrite each other unless special precautions are + taken). + + + + + + High-level system modelling language. The + language in which one describes what and how derivates are to be + produced should have sufficient abstraction facilities to make it + easy to specify the derivation of even very large systems. Also, + the language should be modular to enable + components from possible different sources to be easily combined. + + + @@ -37,8 +94,8 @@ After software has been built, is must also be deployed in the intended target environment, e.g., the user's workstation. Examples include the Red Hat package manager - (RPM), Microsoft's MSI, and so on. Here also we have to deal with - several issues: + (RPM), Microsoft's MSI, and so on. Here also we have several issues to + contend with: @@ -70,24 +127,66 @@ - What Nix can do for you + What Nix provides - Here is a summary of what Nix provides: + Here is a summary of Nix's main features: - Reliable dependencies. + Reliable dependencies. Builds of file system + objects depend on other file system object, such as source files, + tools, and so on. We would like to ensure that a build does not + refer to any objects that have not been declared as inputs for that + build. This is important for several reasons. First, if any of the + inputs change, we need to rebuild the things that depend on them to + maintain consistency between sources and derivates. Second, when we + deploy file system objects (that is, copy them + to a different system), we want to be certain that we copy everything + that we need. + + + + Nix ensures this by building and storing file system objects in paths + that are infeasible to predict in advance. For example, the + artifacts of a package X might be stored in + /nix/store/d58a0606ed616820de291d594602665d-X, + rather than in, say, /usr/lib. The path + component d58a... is actually a cryptographic + hash of all the inputs (i.e., sources, requisites, and build flags) + used in building X, and as such is very fragile: + any change to the inputs will change the hash. Therefore it is not + sensible to hard-code such a path into the build + scripts of a package Y that uses + X (as does happen with fixed paths + such as /usr/lib). Rather, the build script of + package Y is parameterised with the actual + location of X, which is supplied by the Nix + system. - Support for variability. + Support for variability. + + + As stated above, the path name of a file system object contain a + cryptographic hash of all inputs involved in building it. A change to + any of the inputs will cause the hash to change--and by extension, + the path name. These inputs include both sources (variation in time) + and configuration options (variation in space). Therefore variants + of the same package don't clash---they can co-exist peacefully within + the same file system. So thanks to Nix's mechanism for reliably + dealing with dependencies, we obtain management of variants for free + (or, to quote Simon Peyton-Jone, it's not free, but it has already + been paid for). + + @@ -120,6 +219,14 @@ + + + Portability. Nix is quite portable. Contrary + to build systems like those in, e.g., Vesta and ClearCase [sic?], it + does not rely on operating system extensions. + + + From a88144215c263e62528108dfae1e781058344ef2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 22 Aug 2003 20:12:44 +0000 Subject: [PATCH 0239/6440] * Remove write permission from output paths after they have been built. * Point $HOME to a non-existing path when building to prevent certain tools (such as wget) from falling back on /etc/passwd to locate the home directory (which we don't want them to look at since it's not declared as an input). --- src/normalise.cc | 15 +++++++++++++-- src/util.cc | 38 +++++++++++++++++++++++++++++++++++--- src/util.hh | 5 ++++- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/normalise.cc b/src/normalise.cc index 52437059a..059b3c83a 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -131,6 +131,14 @@ FSId normaliseFState(FSId id, FSIdSet pending) value. */ env["PATH"] = "/path-not-set"; + /* Set HOME to a non-existing path to prevent certain programs from using + /etc/passwd (or NIS, or whatever) to locate the home directory (for + example, wget looks for ~/.wgetrc). I.e., these tools use /etc/passwd + if HOME is not set, but they will just assume that the settings file + they are looking for does not exist if HOME is set but points to some + non-existing path. */ + env["HOME"] = "/homeless-shelter"; + /* Build the environment. */ for (StringPairs::iterator i = fs.derive.env.begin(); i != fs.derive.env.end(); i++) @@ -178,7 +186,8 @@ FSId normaliseFState(FSId id, FSIdSet pending) msg(lvlChatty, format("fast build succesful")); /* Check whether the output paths were created, and grep each - output path to determine what other paths it references. */ + output path to determine what other paths it references. Also make all + output paths read-only. */ StringSet usedPaths; for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); i != fs.derive.outputs.end(); i++) @@ -188,10 +197,12 @@ FSId normaliseFState(FSId id, FSIdSet pending) throw Error(format("path `%1%' does not exist") % path); nfFS.slice.roots.insert(path); + makePathReadOnly(path); + /* For this output path, find the references to other paths contained in it. */ Strings refPaths = filterReferences(path, - Strings(allPaths.begin(), allPaths.end())); + Strings(allPaths.begin(), allPaths.end())); /* Construct a slice element for this output path. */ SliceElem elem; diff --git a/src/util.cc b/src/util.cc index 1e1c799ec..c7ae711bb 100644 --- a/src/util.cc +++ b/src/util.cc @@ -106,13 +106,13 @@ bool pathExists(const string & path) } -void deletePath(string path) +void deletePath(const string & path) { msg(lvlVomit, format("deleting path `%1%'") % path); struct stat st; if (lstat(path.c_str(), &st)) - throw SysError(format("getting attributes of path %1%") % path); + throw SysError(format("getting attributes of path `%1%'") % path); if (S_ISDIR(st.st_mode)) { Strings names; @@ -128,12 +128,44 @@ void deletePath(string path) closedir(dir); /* !!! close on exception */ + /* Make the directory writable. */ + if (!(st.st_mode & S_IWUSR)) { + if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1) + throw SysError(format("making `%1%' writable")); + } + for (Strings::iterator i = names.begin(); i != names.end(); i++) deletePath(path + "/" + *i); } if (remove(path.c_str()) == -1) - throw SysError(format("cannot unlink %1%") % path); + throw SysError(format("cannot unlink `%1%'") % path); +} + + +void makePathReadOnly(const string & path) +{ + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting attributes of path `%1%'") % path); + + if (st.st_mode & S_IWUSR) { + if (chmod(path.c_str(), st.st_mode & ~S_IWUSR) == -1) + throw SysError(format("making `%1%' read-only")); + } + + if (S_ISDIR(st.st_mode)) { + DIR * dir = opendir(path.c_str()); + + struct dirent * dirent; + while (errno = 0, dirent = readdir(dir)) { + string name = dirent->d_name; + if (name == "." || name == "..") continue; + makePathReadOnly(path + "/" + name); + } + + closedir(dir); /* !!! close on exception */ + } } diff --git a/src/util.hh b/src/util.hh index 2863085c1..d0e42f3b1 100644 --- a/src/util.hh +++ b/src/util.hh @@ -67,7 +67,10 @@ bool pathExists(const string & path); /* Delete a path; i.e., in the case of a directory, it is deleted recursively. Don't use this at home, kids. */ -void deletePath(string path); +void deletePath(const string & path); + +/* Make a path read-only recursively. */ +void makePathReadOnly(const string & path); /* Messages. */ From 920193beb1a7b8894d100c63adadf00ad855dd64 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 25 Aug 2003 14:36:04 +0000 Subject: [PATCH 0240/6440] * Don't continue when the call to nix fails. --- scripts/nix-collect-garbage.in | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/nix-collect-garbage.in b/scripts/nix-collect-garbage.in index 16a0b09f5..52f274367 100755 --- a/scripts/nix-collect-garbage.in +++ b/scripts/nix-collect-garbage.in @@ -1,5 +1,8 @@ #! /usr/bin/perl -w +use strict; +use IPC::Open2; + my $linkdir = "@localstatedir@/nix/links"; my $storedir = "@prefix@/store"; @@ -16,13 +19,18 @@ foreach my $arg (@ARGV) { my $extraarg = ""; if ($keepsuccessors) { $extraarg = "--include-successors"; }; -open HASHES, "nix --query --requisites $extraarg \$(cat $linkdir/*.id) |" or die "in `nix -qrh'"; -while () { +my $pid = open2(\*READ, \*WRITE, "nix --query --requisites $extraarg \$(cat $linkdir/*.id)") + or die "determining live paths"; +close WRITE; +while () { chomp; $alive{$_} = 1; if ($invert) { print "$_\n"; }; } -close HASHES; +close READ; + +waitpid $pid, 0; +$? == 0 or die "determining live paths"; exit 0 if ($invert); From 31be53cd0a50ef9e3ddf64f79222e8e8dd1d05aa Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 25 Aug 2003 14:56:11 +0000 Subject: [PATCH 0241/6440] * Fix the atrocious (exponential? factorial?) time complexity in `nix --query --requisites'. --- src/normalise.cc | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/normalise.cc b/src/normalise.cc index 059b3c83a..c7be53206 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -321,8 +321,12 @@ Strings fstatePaths(const FSId & id) static void fstateRequisitesSet(const FSId & id, - bool includeExprs, bool includeSuccessors, StringSet & paths) + bool includeExprs, bool includeSuccessors, StringSet & paths, + FSIdSet & doneSet) { + if (doneSet.find(id) != doneSet.end()) return; + doneSet.insert(id); + FState fs = parseFState(termFromId(id)); if (fs.type == FState::fsSlice) @@ -334,7 +338,7 @@ static void fstateRequisitesSet(const FSId & id, for (FSIdSet::iterator i = fs.derive.inputs.begin(); i != fs.derive.inputs.end(); i++) fstateRequisitesSet(*i, - includeExprs, includeSuccessors, paths); + includeExprs, includeSuccessors, paths, doneSet); else abort(); @@ -345,7 +349,7 @@ static void fstateRequisitesSet(const FSId & id, if (includeSuccessors && nixDB.queryString(noTxn, dbSuccessors, id, idSucc)) fstateRequisitesSet(parseHash(idSucc), - includeExprs, includeSuccessors, paths); + includeExprs, includeSuccessors, paths, doneSet); } @@ -353,7 +357,8 @@ Strings fstateRequisites(const FSId & id, bool includeExprs, bool includeSuccessors) { StringSet paths; - fstateRequisitesSet(id, includeExprs, includeSuccessors, paths); + FSIdSet doneSet; + fstateRequisitesSet(id, includeExprs, includeSuccessors, paths, doneSet); return Strings(paths.begin(), paths.end()); } From c4f1f49574b4fe55bef7952bd0fcc2bd626b0db2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 28 Aug 2003 10:10:12 +0000 Subject: [PATCH 0242/6440] * nix-push generated invalid (old-style) slices. * nar.sh needs a path. --- corepkgs/nar/nar.sh.in | 2 ++ scripts/nix-push.in | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/corepkgs/nar/nar.sh.in b/corepkgs/nar/nar.sh.in index d21668553..9f96b03ab 100644 --- a/corepkgs/nar/nar.sh.in +++ b/corepkgs/nar/nar.sh.in @@ -1,5 +1,7 @@ #! /bin/sh +export PATH=/bin:/usr/bin + echo "packing $path into $out..." mkdir $out || exit 1 tmp=$out/tmp diff --git a/scripts/nix-push.in b/scripts/nix-push.in index bb5e6da13..e51f903b8 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -52,7 +52,7 @@ foreach my $id (@ARGV) { # Construct a Fix expression that creates a Nix archive. my $fixexpr = "App(IncludeFix(\"nar/nar.fix\"), " . - "[ (\"path\", Slice([\"$pathid\"], [(\"$path\", \"$pathid\", [])]))" . + "[ (\"path\", Slice([\"$path\"], [(\"$path\", \"$pathid\", [])]))" . "])"; print FIX "," unless ($first); From b0185173147c3eb629cc6b996459cf6ce2d608fe Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 28 Aug 2003 10:51:14 +0000 Subject: [PATCH 0243/6440] * Do not try to remove write permission from symlinks, since chmod() follows symlinks. (Note that the permissions on symlinks are ignored anyway.) --- src/normalise.cc | 2 +- src/util.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/normalise.cc b/src/normalise.cc index c7be53206..39867bfe5 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -202,7 +202,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* For this output path, find the references to other paths contained in it. */ Strings refPaths = filterReferences(path, - Strings(allPaths.begin(), allPaths.end())); + Strings(allPaths.begin(), allPaths.end())); /* Construct a slice element for this output path. */ SliceElem elem; diff --git a/src/util.cc b/src/util.cc index c7ae711bb..f2cdb1d72 100644 --- a/src/util.cc +++ b/src/util.cc @@ -149,9 +149,9 @@ void makePathReadOnly(const string & path) if (lstat(path.c_str(), &st)) throw SysError(format("getting attributes of path `%1%'") % path); - if (st.st_mode & S_IWUSR) { + if (!S_ISLNK(st.st_mode) && (st.st_mode & S_IWUSR)) { if (chmod(path.c_str(), st.st_mode & ~S_IWUSR) == -1) - throw SysError(format("making `%1%' read-only")); + throw SysError(format("making `%1%' read-only") % path); } if (S_ISDIR(st.st_mode)) { From 25304af72ed5c8fd50aa1da01c5872567ebe6ba2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 28 Aug 2003 10:51:52 +0000 Subject: [PATCH 0244/6440] * Set a path. --- corepkgs/nar/unnar.sh.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/corepkgs/nar/unnar.sh.in b/corepkgs/nar/unnar.sh.in index 4b00cb981..8a4532af3 100644 --- a/corepkgs/nar/unnar.sh.in +++ b/corepkgs/nar/unnar.sh.in @@ -1,4 +1,6 @@ #! /bin/sh +export PATH=/bin:/usr/bin + echo "unpacking $nar to $out..." bunzip2 < $nar | @bindir@/nix --restore "$out" || exit 1 From 0d2bc686817306502b71f5ca2cd49cb1d501247c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 29 Aug 2003 13:12:30 +0000 Subject: [PATCH 0245/6440] * Do not show the output of the builder unless the verbosity is at least at debug level (-vvv). The output is still appended to the build log in /nix/var/log/nix. --- src/exec.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/exec.cc b/src/exec.cc index d4ee88f8a..e9ddb5ee1 100644 --- a/src/exec.cc +++ b/src/exec.cc @@ -43,8 +43,12 @@ void runProgram(const string & program, { /* Create a log file. */ string logFileName = nixLogDir + "/run.log"; + string logCommand = + verbosity >= lvlDebug + ? "tee -a " + logFileName + " >&2" + : "cat >> " + logFileName; /* !!! auto-pclose on exit */ - FILE * logFile = popen(("tee -a " + logFileName + " >&2").c_str(), "w"); /* !!! escaping */ + FILE * logFile = popen(logCommand.c_str(), "w"); /* !!! escaping */ if (!logFile) throw SysError(format("creating log file `%1%'") % logFileName); From c0bbed0959665bc51909b285654db2a3cf120502 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 3 Sep 2003 11:20:18 +0000 Subject: [PATCH 0246/6440] * Factored out dot graph generation into a separate file. --- src/Makefile.am | 2 +- src/dotgraph.cc | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ src/dotgraph.hh | 8 +++++++ src/nix.cc | 64 ++++--------------------------------------------- 4 files changed, 76 insertions(+), 61 deletions(-) create mode 100644 src/dotgraph.cc create mode 100644 src/dotgraph.hh diff --git a/src/Makefile.am b/src/Makefile.am index 85bf50282..cddcae1c6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,7 +4,7 @@ check_PROGRAMS = test AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. -I../externals/inst/include $(CXXFLAGS) AM_LDFLAGS = -L../externals/inst/lib -ldb_cxx -lATerm $(LDFLAGS) -nix_SOURCES = nix.cc +nix_SOURCES = nix.cc dotgraph.cc nix_LDADD = libshared.a libnix.a -ldb_cxx -lATerm nix_hash_SOURCES = nix-hash.cc diff --git a/src/dotgraph.cc b/src/dotgraph.cc new file mode 100644 index 000000000..9f0182e53 --- /dev/null +++ b/src/dotgraph.cc @@ -0,0 +1,63 @@ +#include "dotgraph.hh" + + +static string dotQuote(const string & s) +{ + return "\"" + s + "\""; +} + + +void printDotGraph(const FSIds & roots) +{ + FSIds workList(roots.begin(), roots.end()); + FSIdSet doneSet; + + cout << "digraph G {\n"; + + while (!workList.empty()) { + FSId id = workList.front(); + workList.pop_front(); + + if (doneSet.find(id) == doneSet.end()) { + doneSet.insert(id); + + FState fs = parseFState(termFromId(id)); + + string label, shape; + + if (fs.type == FState::fsDerive) { + for (FSIdSet::iterator i = fs.derive.inputs.begin(); + i != fs.derive.inputs.end(); i++) + { + workList.push_back(*i); + cout << dotQuote(*i) << " -> " + << dotQuote(id) << ";\n"; + } + + label = "derive"; + shape = "box"; + for (StringPairs::iterator i = fs.derive.env.begin(); + i != fs.derive.env.end(); i++) + if (i->first == "name") label = i->second; + } + + else if (fs.type == FState::fsSlice) { + label = baseNameOf((*fs.slice.elems.begin()).first); + shape = "ellipse"; + if (isHash(string(label, 0, Hash::hashSize * 2)) && + label[Hash::hashSize * 2] == '-') + label = string(label, Hash::hashSize * 2 + 1); + } + + else abort(); + + cout << dotQuote(id) << "[label = " + << dotQuote(label) + << ", shape = " << shape + << "];\n"; + } + } + + cout << "}\n"; + +} diff --git a/src/dotgraph.hh b/src/dotgraph.hh new file mode 100644 index 000000000..d88e04e09 --- /dev/null +++ b/src/dotgraph.hh @@ -0,0 +1,8 @@ +#ifndef __DOTGRAPH_H +#define __DOTGRAPH_H + +#include "fstate.hh" + +void printDotGraph(const FSIds & roots); + +#endif /* !__DOTGRAPH_H */ diff --git a/src/nix.cc b/src/nix.cc index 04195e8d4..41ed16839 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -5,6 +5,7 @@ #include "normalise.hh" #include "archive.hh" #include "shared.hh" +#include "dotgraph.hh" typedef void (* Operation) (Strings opFlags, Strings opArgs); @@ -80,12 +81,6 @@ static void opAdd(Strings opFlags, Strings opArgs) } -static string dotQuote(const string & s) -{ - return "\"" + s + "\""; -} - - FSId maybeNormalise(const FSId & id, bool normalise) { return normalise ? normaliseFState(id) : id; @@ -170,62 +165,11 @@ static void opQuery(Strings opFlags, Strings opArgs) } case qGraph: { - - FSIds workList; - + FSIds roots; for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) - workList.push_back(argToId(*i)); - - FSIdSet doneSet; - - cout << "digraph G {\n"; - - while (!workList.empty()) { - FSId id = workList.front(); - workList.pop_front(); - - if (doneSet.find(id) == doneSet.end()) { - doneSet.insert(id); - - FState fs = parseFState(termFromId(id)); - - string label, shape; - - if (fs.type == FState::fsDerive) { - for (FSIdSet::iterator i = fs.derive.inputs.begin(); - i != fs.derive.inputs.end(); i++) - { - workList.push_back(*i); - cout << dotQuote(*i) << " -> " - << dotQuote(id) << ";\n"; - } - - label = "derive"; - shape = "box"; - for (StringPairs::iterator i = fs.derive.env.begin(); - i != fs.derive.env.end(); i++) - if (i->first == "name") label = i->second; - } - - else if (fs.type == FState::fsSlice) { - label = baseNameOf((*fs.slice.elems.begin()).first); - shape = "ellipse"; - if (isHash(string(label, 0, Hash::hashSize * 2)) && - label[Hash::hashSize * 2] == '-') - label = string(label, Hash::hashSize * 2 + 1); - } - - else abort(); - - cout << dotQuote(id) << "[label = " - << dotQuote(label) - << ", shape = " << shape - << "];\n"; - } - } - - cout << "}\n"; + roots.push_back(argToId(*i)); + printDotGraph(roots); break; } From 803a924b77730f6f7e04dde0cbfda2522f06a2b1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 3 Sep 2003 14:49:58 +0000 Subject: [PATCH 0247/6440] * Make nicer dot graphs. Also show the inner structure of slices. --- src/dotgraph.cc | 99 ++++++++++++++++++++++++++++++++++++++++++------- src/nix.cc | 2 +- 2 files changed, 86 insertions(+), 15 deletions(-) diff --git a/src/dotgraph.cc b/src/dotgraph.cc index 9f0182e53..1b352e3ff 100644 --- a/src/dotgraph.cc +++ b/src/dotgraph.cc @@ -7,6 +7,84 @@ static string dotQuote(const string & s) } +static string nextColour() +{ + static int n = 0; + static string colours[] = + { "black", "red", "green", "blue" + , "magenta", "burlywood" }; + return colours[n++ % (sizeof(colours) / sizeof(string))]; +} + + +static string makeEdge(const string & src, const string & dst) +{ + format f = format("%1% -> %2% [color = %3%];\n") + % dotQuote(src) % dotQuote(dst) % dotQuote(nextColour()); + return f.str(); +} + + +static string makeNode(const string & id, const string & label, + const string & colour) +{ + format f = format("%1% [label = %2%, shape = box, " + "style = filled, fillcolor = %3%];\n") + % dotQuote(id) % dotQuote(label) % dotQuote(colour); + return f.str(); +} + + +static string symbolicName(const string & path) +{ + string p = baseNameOf(path); + if (isHash(string(p, 0, Hash::hashSize * 2)) && + p[Hash::hashSize * 2] == '-') + p = string(p, Hash::hashSize * 2 + 1); + return p; +} + + +string pathLabel(const FSId & id, const string & path) +{ + return (string) id + "-" + path; +} + + +void printSlice(const FSId & id, const FState & fs) +{ + Strings workList(fs.slice.roots.begin(), fs.slice.roots.end()); + StringSet doneSet; + + for (Strings::iterator i = workList.begin(); i != workList.end(); i++) { + cout << makeEdge(pathLabel(id, *i), id); + } + + while (!workList.empty()) { + string path = workList.front(); + workList.pop_front(); + + if (doneSet.find(path) == doneSet.end()) { + doneSet.insert(path); + + SliceElems::const_iterator elem = fs.slice.elems.find(path); + if (elem == fs.slice.elems.end()) + throw Error(format("bad slice, missing path `%1%'") % path); + + for (StringSet::const_iterator i = elem->second.refs.begin(); + i != elem->second.refs.end(); i++) + { + workList.push_back(*i); + cout << makeEdge(pathLabel(id, *i), pathLabel(id, path)); + } + + cout << makeNode(pathLabel(id, path), + symbolicName(path), "#ff0000"); + } + } +} + + void printDotGraph(const FSIds & roots) { FSIds workList(roots.begin(), roots.end()); @@ -23,41 +101,34 @@ void printDotGraph(const FSIds & roots) FState fs = parseFState(termFromId(id)); - string label, shape; + string label, colour; if (fs.type == FState::fsDerive) { for (FSIdSet::iterator i = fs.derive.inputs.begin(); i != fs.derive.inputs.end(); i++) { workList.push_back(*i); - cout << dotQuote(*i) << " -> " - << dotQuote(id) << ";\n"; + cout << makeEdge(*i, id); } label = "derive"; - shape = "box"; + colour = "#00ff00"; for (StringPairs::iterator i = fs.derive.env.begin(); i != fs.derive.env.end(); i++) if (i->first == "name") label = i->second; } else if (fs.type == FState::fsSlice) { - label = baseNameOf((*fs.slice.elems.begin()).first); - shape = "ellipse"; - if (isHash(string(label, 0, Hash::hashSize * 2)) && - label[Hash::hashSize * 2] == '-') - label = string(label, Hash::hashSize * 2 + 1); + label = ""; + colour = "#00ffff"; + printSlice(id, fs); } else abort(); - cout << dotQuote(id) << "[label = " - << dotQuote(label) - << ", shape = " << shape - << "];\n"; + cout << makeNode(id, label, colour); } } cout << "}\n"; - } diff --git a/src/nix.cc b/src/nix.cc index 41ed16839..12a01fd36 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -168,7 +168,7 @@ static void opQuery(Strings opFlags, Strings opArgs) FSIds roots; for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) - roots.push_back(argToId(*i)); + roots.push_back(maybeNormalise(argToId(*i), normalise)); printDotGraph(roots); break; } From d930a9bc5a69b33dc7fe707838ccdd72e89f8079 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 11 Sep 2003 08:31:29 +0000 Subject: [PATCH 0248/6440] * Added some missing #includes. --- src/archive.cc | 2 ++ src/pathlocks.cc | 2 ++ src/references.cc | 1 + src/util.cc | 2 ++ 4 files changed, 7 insertions(+) diff --git a/src/archive.cc b/src/archive.cc index 9170ca3ad..4a6211e00 100644 --- a/src/archive.cc +++ b/src/archive.cc @@ -1,3 +1,5 @@ +#include +#include #include #include diff --git a/src/pathlocks.cc b/src/pathlocks.cc index 78aae26bd..93f456ace 100644 --- a/src/pathlocks.cc +++ b/src/pathlocks.cc @@ -1,3 +1,5 @@ +#include + #include #include "pathlocks.hh" diff --git a/src/references.cc b/src/references.cc index 8934d5306..be432665b 100644 --- a/src/references.cc +++ b/src/references.cc @@ -1,3 +1,4 @@ +#include #include #include diff --git a/src/util.cc b/src/util.cc index f2cdb1d72..bedd031de 100644 --- a/src/util.cc +++ b/src/util.cc @@ -1,4 +1,6 @@ #include +#include +#include #include #include From 1c7d6bf5fcddae13b6226d61fe60cfccb8d2c359 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 11 Sep 2003 10:23:55 +0000 Subject: [PATCH 0249/6440] * Removed references to char_traits so that boost/format also works on GCC 2.95. --- Makefile.am | 4 +- boost/Makefile.am | 1 + boost/format.hpp | 9 +- boost/format/Makefile.am | 3 + boost/format/feed_args.hpp | 69 ++++++----- boost/format/format_class.hpp | 43 +++---- boost/format/format_fwd.hpp | 16 +-- ...mentation.hpp => format_implementation.cc} | 46 +++---- .../format/{free_funcs.hpp => free_funcs.cc} | 17 ++- boost/format/group.hpp | 44 +++---- boost/format/internals.hpp | 48 ++++---- boost/format/internals_fwd.hpp | 24 ++-- boost/format/{parsing.hpp => parsing.cc} | 113 +++++++++--------- configure.ac | 12 +- src/Makefile.am | 12 +- 15 files changed, 222 insertions(+), 239 deletions(-) create mode 100644 boost/Makefile.am create mode 100644 boost/format/Makefile.am rename boost/format/{format_implementation.hpp => format_implementation.cc} (83%) rename boost/format/{free_funcs.hpp => free_funcs.cc} (81%) rename boost/format/{parsing.hpp => parsing.cc} (79%) diff --git a/Makefile.am b/Makefile.am index b7bb72819..5996b3ee9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,3 @@ -SUBDIRS = externals src scripts corepkgs doc +SUBDIRS = externals boost src scripts corepkgs doc -EXTRA_DIST = boost/*.hpp boost/format/*.hpp substitute.mk \ No newline at end of file +EXTRA_DIST = substitute.mk diff --git a/boost/Makefile.am b/boost/Makefile.am new file mode 100644 index 000000000..fad59a1b9 --- /dev/null +++ b/boost/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = format diff --git a/boost/format.hpp b/boost/format.hpp index f5bdada05..a287048ed 100644 --- a/boost/format.hpp +++ b/boost/format.hpp @@ -25,6 +25,9 @@ #include #include +//#define BOOST_NO_STD_LOCALE +//#define BOOST_NO_LOCALE_ISIDIGIT +//#include #include @@ -54,15 +57,15 @@ namespace boost #include // **** Implementation ------------------------------------------- -#include // member functions +//#include // member functions #include // class for grouping arguments #include // argument-feeding functions -#include // format-string parsing (member-)functions +//#include // format-string parsing (member-)functions // **** Implementation of the free functions ---------------------- -#include +//#include #endif // BOOST_FORMAT_HPP diff --git a/boost/format/Makefile.am b/boost/format/Makefile.am new file mode 100644 index 000000000..43a44a216 --- /dev/null +++ b/boost/format/Makefile.am @@ -0,0 +1,3 @@ +noinst_LIBRARIES = libformat.a + +libformat_a_SOURCES = format_implementation.cc free_funcs.cc parsing.cc diff --git a/boost/format/feed_args.hpp b/boost/format/feed_args.hpp index 2e678ca3b..ba107dce6 100644 --- a/boost/format/feed_args.hpp +++ b/boost/format/feed_args.hpp @@ -31,17 +31,16 @@ namespace io { namespace detail { namespace { - template inline - void empty_buf(BOOST_IO_STD basic_ostringstream & os) { - static const std::basic_string emptyStr; + inline + void empty_buf(BOOST_IO_STD ostringstream & os) { + static const std::string emptyStr; os.str(emptyStr); } - template - void do_pad( std::basic_string & s, + void do_pad( std::string & s, std::streamsize w, - const Ch c, - std::ios_base::fmtflags f, + const char c, + std::ios::fmtflags f, bool center) // applies centered / left / right padding to the string s. // Effects : string s is padded. @@ -59,7 +58,7 @@ namespace { } else { - if(f & std::ios_base::left) { + if(f & std::ios::left) { s.append(n, c); } else { @@ -69,32 +68,32 @@ namespace { } // -do_pad(..) - template< class Ch, class Tr, class T> inline - void put_head(BOOST_IO_STD basic_ostream& , const T& ) { + template inline + void put_head(BOOST_IO_STD ostream& , const T& ) { } - template< class Ch, class Tr, class T> inline - void put_head( BOOST_IO_STD basic_ostream& os, const group1& x ) { + template inline + void put_head( BOOST_IO_STD ostream& os, const group1& x ) { os << group_head(x.a1_); // send the first N-1 items, not the last } - template< class Ch, class Tr, class T> inline - void put_last( BOOST_IO_STD basic_ostream& os, const T& x ) { + template inline + void put_last( BOOST_IO_STD ostream& os, const T& x ) { os << x ; } - template< class Ch, class Tr, class T> inline - void put_last( BOOST_IO_STD basic_ostream& os, const group1& x ) { + template inline + void put_last( BOOST_IO_STD ostream& os, const group1& x ) { os << group_last(x.a1_); // this selects the last element } #ifndef BOOST_NO_OVERLOAD_FOR_NON_CONST - template< class Ch, class Tr, class T> inline - void put_head( BOOST_IO_STD basic_ostream& , T& ) { + template inline + void put_head( BOOST_IO_STD ostream& , T& ) { } - template< class Ch, class Tr, class T> inline - void put_last( BOOST_IO_STD basic_ostream& os, T& x ) { + template inline + void put_last( BOOST_IO_STD ostream& os, T& x ) { os << x ; } #endif @@ -102,19 +101,19 @@ namespace { -template< class Ch, class Tr, class T> +template void put( T x, - const format_item& specs, - std::basic_string & res, - BOOST_IO_STD basic_ostringstream& oss_ ) + const format_item& specs, + std::string & res, + BOOST_IO_STD ostringstream& oss_ ) { // does the actual conversion of x, with given params, into a string // using the *supplied* strinstream. (the stream state is important) - typedef std::basic_string string_t; - typedef format_item format_item_t; + typedef std::string string_t; + typedef format_item format_item_t; - stream_format_state prev_state(oss_); + stream_format_state prev_state(oss_); specs.state_.apply_on(oss_); @@ -124,8 +123,8 @@ void put( T x, empty_buf( oss_); const std::streamsize w=oss_.width(); - const std::ios_base::fmtflags fl=oss_.flags(); - const bool internal = (fl & std::ios_base::internal) != 0; + const std::ios::fmtflags fl=oss_.flags(); + const bool internal = (fl & std::ios::internal) != 0; const bool two_stepped_padding = internal && ! ( specs.pad_scheme_ & format_item_t::spacepad ) && specs.truncate_ < 0 ; @@ -203,8 +202,8 @@ void put( T x, -template< class Ch, class Tr, class T> -void distribute(basic_format& self, T x) +template +void distribute(basic_format& self, T x) // call put(x, ..) on every occurence of the current argument : { if(self.cur_arg_ >= self.num_args_) @@ -217,16 +216,16 @@ void distribute(basic_format& self, T x) { if(self.items_[i].argN_ == self.cur_arg_) { - put (x, self.items_[i], self.items_[i].res_, self.oss_ ); + put (x, self.items_[i], self.items_[i].res_, self.oss_ ); } } } -template -basic_format& feed(basic_format& self, T x) +template +basic_format& feed(basic_format& self, T x) { if(self.dumped_) self.clear(); - distribute (self, x); + distribute (self, x); ++self.cur_arg_; if(self.bound_.size() != 0) { diff --git a/boost/format/format_class.hpp b/boost/format/format_class.hpp index 9126bfad3..6875623ac 100644 --- a/boost/format/format_class.hpp +++ b/boost/format/format_class.hpp @@ -30,26 +30,21 @@ namespace boost { -template class basic_format { public: - typedef Ch CharT; // those 2 are necessary for borland compatibilty, - typedef Tr Traits; // in the body of the operator% template. - - - typedef std::basic_string string_t; - typedef BOOST_IO_STD basic_ostringstream internal_stream_t; + typedef std::string string_t; + typedef BOOST_IO_STD ostringstream internal_stream_t; private: - typedef BOOST_IO_STD basic_ostream stream_t; - typedef io::detail::stream_format_state stream_format_state; - typedef io::detail::format_item format_item_t; + typedef BOOST_IO_STD ostream stream_t; + typedef io::detail::stream_format_state stream_format_state; + typedef io::detail::format_item format_item_t; public: - basic_format(const Ch* str); + basic_format(const char* str); basic_format(const string_t& s); #ifndef BOOST_NO_STD_LOCALE - basic_format(const Ch* str, const std::locale & loc); + basic_format(const char* str, const std::locale & loc); basic_format(const string_t& s, const std::locale & loc); #endif // no locale basic_format(const basic_format& x); @@ -60,13 +55,13 @@ public: // pass arguments through those operators : template basic_format& operator%(const T& x) { - return io::detail::feed(*this,x); + return io::detail::feed(*this,x); } #ifndef BOOST_NO_OVERLOAD_FOR_NON_CONST template basic_format& operator%(T& x) { - return io::detail::feed(*this,x); + return io::detail::feed(*this,x); } #endif @@ -93,21 +88,21 @@ public: // final output string_t str() const; - friend BOOST_IO_STD basic_ostream& - operator<< ( BOOST_IO_STD basic_ostream& , const basic_format& ); + friend BOOST_IO_STD ostream& + operator<< ( BOOST_IO_STD ostream& , const basic_format& ); - template friend basic_format& - io::detail::feed(basic_format&, T); + template friend basic_format& + io::detail::feed(basic_format&, T); - template friend - void io::detail::distribute(basic_format&, T); + template friend + void io::detail::distribute(basic_format&, T); - template friend - basic_format& io::detail::modify_item_body(basic_format&, int, const T&); + template friend + basic_format& io::detail::modify_item_body(basic_format&, int, const T&); - template friend - basic_format& io::detail::bind_arg_body(basic_format&, int, const T&); + template friend + basic_format& io::detail::bind_arg_body(basic_format&, int, const T&); // make the members private only if the friend templates are supported private: diff --git a/boost/format/format_fwd.hpp b/boost/format/format_fwd.hpp index bad2f7238..97c55f668 100644 --- a/boost/format/format_fwd.hpp +++ b/boost/format/format_fwd.hpp @@ -24,13 +24,9 @@ namespace boost { -template > class basic_format; +class basic_format; -typedef basic_format format; - -#if !defined(BOOST_NO_STD_WSTRING) && !defined(BOOST_NO_STD_WSTREAMBUF) -typedef basic_format wformat; -#endif +typedef basic_format format; namespace io { enum format_error_bits { bad_format_string_bit = 1, @@ -39,15 +35,13 @@ enum format_error_bits { bad_format_string_bit = 1, all_error_bits = 255, no_error_bits=0 }; // Convertion: format to string -template -std::basic_string str(const basic_format& ) ; +std::string str(const basic_format& ) ; } // namespace io -template< class Ch, class Tr> -BOOST_IO_STD basic_ostream& -operator<<( BOOST_IO_STD basic_ostream&, const basic_format&); +BOOST_IO_STD ostream& +operator<<( BOOST_IO_STD ostream&, const basic_format&); } // namespace boost diff --git a/boost/format/format_implementation.hpp b/boost/format/format_implementation.cc similarity index 83% rename from boost/format/format_implementation.hpp rename to boost/format/format_implementation.cc index 58372bb13..41cb5fc9f 100644 --- a/boost/format/format_implementation.hpp +++ b/boost/format/format_implementation.cc @@ -22,13 +22,12 @@ //#include //#include -#include +#include namespace boost { // -------- format:: ------------------------------------------- -template< class Ch, class Tr> -basic_format ::basic_format(const Ch* str) +basic_format::basic_format(const char* str) : style_(0), cur_arg_(0), num_args_(0), dumped_(false), items_(), oss_(), exceptions_(io::all_error_bits) { @@ -39,8 +38,7 @@ basic_format ::basic_format(const Ch* str) } #ifndef BOOST_NO_STD_LOCALE -template< class Ch, class Tr> -basic_format ::basic_format(const Ch* str, const std::locale & loc) +basic_format::basic_format(const char* str, const std::locale & loc) : style_(0), cur_arg_(0), num_args_(0), dumped_(false), items_(), oss_(), exceptions_(io::all_error_bits) { @@ -51,8 +49,7 @@ basic_format ::basic_format(const Ch* str, const std::locale & loc) parse( str ); } -template< class Ch, class Tr> -basic_format ::basic_format(const string_t& s, const std::locale & loc) +basic_format::basic_format(const string_t& s, const std::locale & loc) : style_(0), cur_arg_(0), num_args_(0), dumped_(false), items_(), oss_(), exceptions_(io::all_error_bits) { @@ -62,8 +59,7 @@ basic_format ::basic_format(const string_t& s, const std::locale & loc) } #endif //BOOST_NO_STD_LOCALE -template< class Ch, class Tr> -basic_format ::basic_format(const string_t& s) +basic_format::basic_format(const string_t& s) : style_(0), cur_arg_(0), num_args_(0), dumped_(false), items_(), oss_(), exceptions_(io::all_error_bits) { @@ -71,8 +67,7 @@ basic_format ::basic_format(const string_t& s) parse(s); } -template< class Ch, class Tr> -basic_format :: basic_format(const basic_format& x) +basic_format:: basic_format(const basic_format& x) : style_(x.style_), cur_arg_(x.cur_arg_), num_args_(x.num_args_), dumped_(false), items_(x.items_), prefix_(x.prefix_), bound_(x.bound_), oss_(), // <- we obviously can't copy x.oss_ @@ -81,8 +76,7 @@ basic_format :: basic_format(const basic_format& x) state0_.apply_on(oss_); } -template< class Ch, class Tr> -basic_format& basic_format ::operator= (const basic_format& x) +basic_format& basic_format::operator= (const basic_format& x) { if(this == &x) return *this; @@ -102,14 +96,12 @@ basic_format& basic_format ::operator= (const basic_format& x) } -template< class Ch, class Tr> -unsigned char basic_format ::exceptions() const +unsigned char basic_format::exceptions() const { return exceptions_; } -template< class Ch, class Tr> -unsigned char basic_format ::exceptions(unsigned char newexcept) +unsigned char basic_format::exceptions(unsigned char newexcept) { unsigned char swp = exceptions_; exceptions_ = newexcept; @@ -117,8 +109,7 @@ unsigned char basic_format ::exceptions(unsigned char newexcept) } -template< class Ch, class Tr> -basic_format& basic_format ::clear() +basic_format& basic_format ::clear() // empty the string buffers (except bound arguments, see clear_binds() ) // and make the format object ready for formatting a new set of arguments { @@ -138,8 +129,7 @@ basic_format& basic_format ::clear() return *this; } -template< class Ch, class Tr> -basic_format& basic_format ::clear_binds() +basic_format& basic_format ::clear_binds() // cancel all bindings, and clear() { bound_.resize(0); @@ -147,8 +137,7 @@ basic_format& basic_format ::clear_binds() return *this; } -template< class Ch, class Tr> -basic_format& basic_format ::clear_bind(int argN) +basic_format& basic_format::clear_bind(int argN) // cancel the binding of ONE argument, and clear() { if(argN<1 || argN > num_args_ || bound_.size()==0 || !bound_[argN-1] ) @@ -164,8 +153,7 @@ basic_format& basic_format ::clear_bind(int argN) -template< class Ch, class Tr> -std::basic_string basic_format ::str() const +std::string basic_format::str() const { dumped_=true; if(items_.size()==0) @@ -201,8 +189,8 @@ std::basic_string basic_format ::str() const namespace io { namespace detail { -template -basic_format& bind_arg_body( basic_format& self, +template +basic_format& bind_arg_body( basic_format& self, int argN, const T& val) // bind one argument to a fixed value @@ -239,8 +227,8 @@ basic_format& bind_arg_body( basic_format& self, return self; } -template -basic_format& modify_item_body( basic_format& self, +template +basic_format& modify_item_body( basic_format& self, int itemN, const T& manipulator) // applies a manipulator to the format_item describing a given directive. diff --git a/boost/format/free_funcs.hpp b/boost/format/free_funcs.cc similarity index 81% rename from boost/format/free_funcs.hpp rename to boost/format/free_funcs.cc index d552e8baa..b2ac01774 100644 --- a/boost/format/free_funcs.hpp +++ b/boost/format/free_funcs.cc @@ -19,27 +19,26 @@ #ifndef BOOST_FORMAT_FUNCS_HPP #define BOOST_FORMAT_FUNCS_HPP -#include "boost/format/format_class.hpp" +#include "boost/format.hpp" //#include "boost/throw_exception.hpp" namespace boost { namespace io { - template inline - std::basic_string str(const basic_format& f) + inline + std::string str(const basic_format& f) // adds up all pieces of strings and converted items, and return the formatted string { return f.str(); } } // - namespace io -template< class Ch, class Tr> -BOOST_IO_STD basic_ostream& -operator<<( BOOST_IO_STD basic_ostream& os, - const boost::basic_format& f) +BOOST_IO_STD ostream& +operator<<( BOOST_IO_STD ostream& os, + const boost::basic_format& f) // effect: "return os << str(f);" but we can try to do it faster { - typedef boost::basic_format format_t; + typedef boost::basic_format format_t; if(f.items_.size()==0) os << f.prefix_; else { @@ -53,7 +52,7 @@ operator<<( BOOST_IO_STD basic_ostream& os, os << f.prefix_; for(unsigned long i=0; i inline -BOOST_IO_STD basic_ostream& -operator << ( BOOST_IO_STD basic_ostream& os, +BOOST_IO_STD ostream& +operator << ( BOOST_IO_STD ostream& os, const group0& ) { return os; @@ -63,8 +63,8 @@ struct group1 template inline -BOOST_IO_STD basic_ostream& -operator << (BOOST_IO_STD basic_ostream& os, +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, const group1& x) { os << x.a1_; @@ -86,8 +86,8 @@ struct group2 template inline -BOOST_IO_STD basic_ostream& -operator << (BOOST_IO_STD basic_ostream& os, +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, const group2& x) { os << x.a1_<< x.a2_; @@ -107,8 +107,8 @@ struct group3 template inline -BOOST_IO_STD basic_ostream& -operator << (BOOST_IO_STD basic_ostream& os, +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, const group3& x) { os << x.a1_<< x.a2_<< x.a3_; @@ -129,8 +129,8 @@ struct group4 template inline -BOOST_IO_STD basic_ostream& -operator << (BOOST_IO_STD basic_ostream& os, +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, const group4& x) { os << x.a1_<< x.a2_<< x.a3_<< x.a4_; @@ -152,8 +152,8 @@ struct group5 template inline -BOOST_IO_STD basic_ostream& -operator << (BOOST_IO_STD basic_ostream& os, +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, const group5& x) { os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_; @@ -176,8 +176,8 @@ struct group6 template inline -BOOST_IO_STD basic_ostream& -operator << (BOOST_IO_STD basic_ostream& os, +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, const group6& x) { os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_; @@ -201,8 +201,8 @@ struct group7 template inline -BOOST_IO_STD basic_ostream& -operator << (BOOST_IO_STD basic_ostream& os, +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, const group7& x) { os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_; @@ -227,8 +227,8 @@ struct group8 template inline -BOOST_IO_STD basic_ostream& -operator << (BOOST_IO_STD basic_ostream& os, +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, const group8& x) { os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_<< x.a8_; @@ -254,8 +254,8 @@ struct group9 template inline -BOOST_IO_STD basic_ostream& -operator << (BOOST_IO_STD basic_ostream& os, +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, const group9& x) { os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_<< x.a8_<< x.a9_; @@ -282,8 +282,8 @@ struct group10 template inline -BOOST_IO_STD basic_ostream& -operator << (BOOST_IO_STD basic_ostream& os, +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, const group10& x) { os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_<< x.a8_<< x.a9_<< x.a10_; diff --git a/boost/format/internals.hpp b/boost/format/internals.hpp index 52448b731..d25eb4c86 100644 --- a/boost/format/internals.hpp +++ b/boost/format/internals.hpp @@ -33,22 +33,21 @@ namespace detail { // -------------- // set of params that define the format state of a stream -template struct stream_format_state { - typedef BOOST_IO_STD basic_ios basic_ios; + typedef std::ios basic_ios; std::streamsize width_; std::streamsize precision_; - Ch fill_; - std::ios_base::fmtflags flags_; + char fill_; + std::ios::fmtflags flags_; - stream_format_state() : width_(-1), precision_(-1), fill_(0), flags_(std::ios_base::dec) {} + stream_format_state() : width_(-1), precision_(-1), fill_(0), flags_(std::ios::dec) {} stream_format_state(basic_ios& os) {set_by_stream(os); } void apply_on(basic_ios & os) const; //- applies format_state to the stream template void apply_manip(T manipulator) //- modifies state by applying manipulator. - { apply_manip_body( *this, manipulator) ; } + { apply_manip_body( *this, manipulator) ; } void reset(); //- sets to default state. void set_by_stream(const basic_ios& os); //- sets to os's state. }; @@ -58,7 +57,6 @@ struct stream_format_state // -------------- // format_item : stores all parameters that can be defined by directives in the format-string -template struct format_item { enum pad_values { zeropad = 1, spacepad =2, centered=4, tabulation = 8 }; @@ -67,10 +65,10 @@ struct format_item argN_tabulation = -2, // tabulation directive. (no argument read) argN_ignored = -3 // ignored directive. (no argument read) }; - typedef BOOST_IO_STD basic_ios basic_ios; - typedef detail::stream_format_state stream_format_state; - typedef std::basic_string string_t; - typedef BOOST_IO_STD basic_ostringstream internal_stream_t; + typedef BOOST_IO_STD ios basic_ios; + typedef detail::stream_format_state stream_format_state; + typedef std::string string_t; + typedef BOOST_IO_STD ostringstream internal_stream_t; int argN_; //- argument number (starts at 0, eg : %1 => argN=0) @@ -98,8 +96,8 @@ struct format_item // ----------------------------------------------------------- // --- stream_format_state:: ------------------------------------------- -template inline -void stream_format_state ::apply_on(basic_ios & os) const +inline +void stream_format_state::apply_on(basic_ios & os) const // set the state of this stream according to our params { if(width_ != -1) @@ -111,8 +109,8 @@ void stream_format_state ::apply_on(basic_ios & os) const os.flags(flags_); } -template inline -void stream_format_state ::set_by_stream(const basic_ios& os) +inline +void stream_format_state::set_by_stream(const basic_ios& os) // set our params according to the state of this stream { flags_ = os.flags(); @@ -121,42 +119,42 @@ void stream_format_state ::set_by_stream(const basic_ios& os) fill_ = os.fill(); } -template inline -void apply_manip_body( stream_format_state& self, +template inline +void apply_manip_body( stream_format_state& self, T manipulator) // modify our params according to the manipulator { - BOOST_IO_STD basic_stringstream ss; + BOOST_IO_STD stringstream ss; self.apply_on( ss ); ss << manipulator; self.set_by_stream( ss ); } -template inline -void stream_format_state ::reset() +inline +void stream_format_state::reset() // set our params to standard's default state { width_=-1; precision_=-1; fill_=0; - flags_ = std::ios_base::dec; + flags_ = std::ios::dec; } // --- format_items:: ------------------------------------------- -template inline -void format_item ::compute_states() +inline +void format_item::compute_states() // reflect pad_scheme_ on state_ and ref_state_ // because some pad_schemes has complex consequences on several state params. { if(pad_scheme_ & zeropad) { - if(ref_state_.flags_ & std::ios_base::left) + if(ref_state_.flags_ & std::ios::left) { pad_scheme_ = pad_scheme_ & (~zeropad); // ignore zeropad in left alignment } else { ref_state_.fill_='0'; - ref_state_.flags_ |= std::ios_base::internal; + ref_state_.flags_ |= std::ios::internal; } } state_ = ref_state_; diff --git a/boost/format/internals_fwd.hpp b/boost/format/internals_fwd.hpp index f260e6dec..a8ebf7c3a 100644 --- a/boost/format/internals_fwd.hpp +++ b/boost/format/internals_fwd.hpp @@ -26,8 +26,8 @@ namespace boost { namespace io { namespace detail { - template struct stream_format_state; - template struct format_item; + struct stream_format_state; + struct format_item; } @@ -37,24 +37,24 @@ namespace detail { // but MSVC have problems with template member functions : // defined in format_implementation.hpp : - template - basic_format& modify_item_body( basic_format& self, + template + basic_format& modify_item_body( basic_format& self, int itemN, const T& manipulator); - template - basic_format& bind_arg_body( basic_format& self, + template + basic_format& bind_arg_body( basic_format& self, int argN, const T& val); - template - void apply_manip_body( stream_format_state& self, + template + void apply_manip_body( stream_format_state& self, T manipulator); // argument feeding (defined in feed_args.hpp ) : - template - void distribute(basic_format& self, T x); + template + void distribute(basic_format& self, T x); - template - basic_format& feed(basic_format& self, T x); + template + basic_format& feed(basic_format& self, T x); } // namespace detail diff --git a/boost/format/parsing.hpp b/boost/format/parsing.cc similarity index 79% rename from boost/format/parsing.hpp rename to boost/format/parsing.cc index a461314f9..1e12dea9b 100644 --- a/boost/format/parsing.hpp +++ b/boost/format/parsing.cc @@ -22,7 +22,7 @@ #define BOOST_FORMAT_PARSING_HPP -#include +#include //#include //#include @@ -31,8 +31,8 @@ namespace boost { namespace io { namespace detail { - template inline - bool wrap_isdigit(Ch c, Stream &os) + template inline + bool wrap_isdigit(char c, Stream &os) { #ifndef BOOST_NO_LOCALE_ISIDIGIT return std::isdigit(c, os.rdbuf()->getloc() ); @@ -42,10 +42,10 @@ namespace detail { #endif } //end- wrap_isdigit(..) - template inline - Res str2int(const std::basic_string& s, - typename std::basic_string::size_type start, - BOOST_IO_STD basic_ios &os, + template inline + Res str2int(const std::string& s, + std::string::size_type start, + BOOST_IO_STD ios &os, const Res = Res(0) ) // Input : char string, with starting index // a basic_ios& merely to call its widen/narrow member function in the desired locale. @@ -54,7 +54,7 @@ namespace detail { { Res n = 0; while(start - void skip_asterisk(const std::basic_string & buf, - typename std::basic_string::size_type * pos_p, - BOOST_IO_STD basic_ios &os) + void skip_asterisk(const std::string & buf, + std::string::size_type * pos_p, + BOOST_IO_STD ios &os) // skip printf's "asterisk-fields" directives in the format-string buf // Input : char string, with starting index *pos_p // a basic_ios& merely to call its widen/narrow member function in the desired locale. @@ -76,10 +75,10 @@ namespace detail { using namespace std; BOOST_ASSERT( pos_p != 0); if(*pos_p >= buf.size() ) return; - if(buf[ *pos_p]==os.widen('*')) { + if(buf[ *pos_p]=='*') { ++ (*pos_p); while (*pos_p < buf.size() && wrap_isdigit(buf[*pos_p],os)) ++(*pos_p); - if(buf[*pos_p]==os.widen('$')) ++(*pos_p); + if(buf[*pos_p]=='$') ++(*pos_p); } } @@ -95,11 +94,10 @@ namespace detail { - template - bool parse_printf_directive(const std::basic_string & buf, - typename std::basic_string::size_type * pos_p, - detail::format_item * fpar, - BOOST_IO_STD basic_ios &os, + bool parse_printf_directive(const std::string & buf, + std::string::size_type * pos_p, + detail::format_item * fpar, + BOOST_IO_STD ios &os, unsigned char exceptions) // Input : a 'printf-directive' in the format-string, starting at buf[ *pos_p ] // a basic_ios& merely to call its widen/narrow member function in the desired locale. @@ -109,14 +107,14 @@ namespace detail { // Effects : - *pos_p is incremented so that buf[*pos_p] is the first char after the directive // - *fpar is set with the parameters read in the directive { - typedef format_item format_item_t; + typedef format_item format_item_t; BOOST_ASSERT( pos_p != 0); - typename std::basic_string::size_type &i1 = *pos_p, + std::string::size_type &i1 = *pos_p, i0; fpar->argN_ = format_item_t::argN_no_posit; // if no positional-directive bool in_brackets=false; - if(buf[i1]==os.widen('|')) + if(buf[i1]=='|') { in_brackets=true; if( ++i1 >= buf.size() ) { @@ -126,7 +124,7 @@ namespace detail { } // the flag '0' would be picked as a digit for argument order, but here it's a flag : - if(buf[i1]==os.widen('0')) + if(buf[i1]=='0') goto parse_flags; // handle argument order (%2$d) or possibly width specification: %2d @@ -142,7 +140,7 @@ namespace detail { int n=str2int(buf,i0, os, int(0) ); // %N% case : this is already the end of the directive - if( buf[i1] == os.widen('%') ) + if( buf[i1] == '%' ) { fpar->argN_ = n-1; ++i1; @@ -152,7 +150,7 @@ namespace detail { else return true; } - if ( buf[i1]==os.widen('$') ) + if ( buf[i1]=='$' ) { fpar->argN_ = n-1; ++i1; @@ -171,14 +169,14 @@ namespace detail { while ( i1 ref_state_.flags_ |= std::ios_base::left; + fpar->ref_state_.flags_ |= std::ios::left; break; case '=': fpar->pad_scheme_ |= format_item_t::centered; @@ -187,7 +185,7 @@ namespace detail { fpar->pad_scheme_ |= format_item_t::spacepad; break; case '+': - fpar->ref_state_.flags_ |= std::ios_base::showpos; + fpar->ref_state_.flags_ |= std::ios::showpos; break; case '0': fpar->pad_scheme_ |= format_item_t::zeropad; @@ -195,7 +193,7 @@ namespace detail { // so just add 'zeropad' flag for now, it will be processed later. break; case '#': - fpar->ref_state_.flags_ |= std::ios_base::showpoint | std::ios_base::showbase; + fpar->ref_state_.flags_ |= std::ios::showpoint | std::ios::showbase; break; default: goto parse_width; @@ -223,7 +221,7 @@ namespace detail { return true; } // handle precision spec - if (buf[i1]==os.widen('.')) + if (buf[i1]=='.') { ++i1; skip_asterisk(buf, &i1, os); @@ -239,51 +237,51 @@ namespace detail { // handle formatting-type flags : while( i1=buf.size()) { maybe_throw_exception(exceptions); return true; } - if( in_brackets && buf[i1]==os.widen('|') ) + if( in_brackets && buf[i1]=='|' ) { ++i1; return true; } - switch (os.narrow(buf[i1], 0) ) + switch (buf[i1]) { case 'X': - fpar->ref_state_.flags_ |= std::ios_base::uppercase; + fpar->ref_state_.flags_ |= std::ios::uppercase; case 'p': // pointer => set hex. case 'x': - fpar->ref_state_.flags_ &= ~std::ios_base::basefield; - fpar->ref_state_.flags_ |= std::ios_base::hex; + fpar->ref_state_.flags_ &= ~std::ios::basefield; + fpar->ref_state_.flags_ |= std::ios::hex; break; case 'o': - fpar->ref_state_.flags_ &= ~std::ios_base::basefield; - fpar->ref_state_.flags_ |= std::ios_base::oct; + fpar->ref_state_.flags_ &= ~std::ios::basefield; + fpar->ref_state_.flags_ |= std::ios::oct; break; case 'E': - fpar->ref_state_.flags_ |= std::ios_base::uppercase; + fpar->ref_state_.flags_ |= std::ios::uppercase; case 'e': - fpar->ref_state_.flags_ &= ~std::ios_base::floatfield; - fpar->ref_state_.flags_ |= std::ios_base::scientific; + fpar->ref_state_.flags_ &= ~std::ios::floatfield; + fpar->ref_state_.flags_ |= std::ios::scientific; - fpar->ref_state_.flags_ &= ~std::ios_base::basefield; - fpar->ref_state_.flags_ |= std::ios_base::dec; + fpar->ref_state_.flags_ &= ~std::ios::basefield; + fpar->ref_state_.flags_ |= std::ios::dec; break; case 'f': - fpar->ref_state_.flags_ &= ~std::ios_base::floatfield; - fpar->ref_state_.flags_ |= std::ios_base::fixed; + fpar->ref_state_.flags_ &= ~std::ios::floatfield; + fpar->ref_state_.flags_ |= std::ios::fixed; case 'u': case 'd': case 'i': - fpar->ref_state_.flags_ &= ~std::ios_base::basefield; - fpar->ref_state_.flags_ |= std::ios_base::dec; + fpar->ref_state_.flags_ &= ~std::ios::basefield; + fpar->ref_state_.flags_ |= std::ios::dec; break; case 'T': @@ -296,20 +294,20 @@ namespace detail { fpar->argN_ = format_item_t::argN_tabulation; break; case 't': - fpar->ref_state_.fill_ = os.widen(' '); + fpar->ref_state_.fill_ = ' '; fpar->pad_scheme_ |= format_item_t::tabulation; fpar->argN_ = format_item_t::argN_tabulation; break; case 'G': - fpar->ref_state_.flags_ |= std::ios_base::uppercase; + fpar->ref_state_.flags_ |= std::ios::uppercase; break; case 'g': // 'g' conversion is default for floats. - fpar->ref_state_.flags_ &= ~std::ios_base::basefield; - fpar->ref_state_.flags_ |= std::ios_base::dec; + fpar->ref_state_.flags_ &= ~std::ios::basefield; + fpar->ref_state_.flags_ |= std::ios::dec; // CLEAR all floatield flags, so stream will CHOOSE - fpar->ref_state_.flags_ &= ~std::ios_base::floatfield; + fpar->ref_state_.flags_ &= ~std::ios::floatfield; break; case 'C': @@ -331,7 +329,7 @@ namespace detail { if( in_brackets ) { - if( i1 -void basic_format ::parse(const string_t & buf) +void basic_format::parse(const string_t & buf) // parse the format-string { using namespace std; - const Ch arg_mark = oss_.widen('%'); + const char arg_mark = '%'; bool ordered_args=true; int max_argN=-1; - typename string_t::size_type i1=0; + string_t::size_type i1=0; int num_items=0; // A: find upper_bound on num_items and allocates arrays @@ -382,7 +379,7 @@ void basic_format ::parse(const string_t & buf) // B: Now the real parsing of the format string : num_items=0; i1 = 0; - typename string_t::size_type i0 = i1; + string_t::size_type i0 = i1; bool special_things=false; int cur_it=0; while( (i1=buf.find(arg_mark,i1)) != string::npos ) diff --git a/configure.ac b/configure.ac index 3ad8ed7ff..2f7d8333f 100644 --- a/configure.ac +++ b/configure.ac @@ -16,8 +16,12 @@ AC_PATH_PROG(wget, wget) AC_CHECK_LIB(pthread, pthread_mutex_init) AM_CONFIG_HEADER([config.h]) -AC_CONFIG_FILES([Makefile externals/Makefile src/Makefile scripts/Makefile - corepkgs/Makefile corepkgs/fetchurl/Makefile - corepkgs/nar/Makefile - doc/Makefile doc/manual/Makefile]) +AC_CONFIG_FILES([Makefile + externals/Makefile + boost/Makefile boost/format/Makefile + src/Makefile + scripts/Makefile + corepkgs/Makefile corepkgs/fetchurl/Makefile corepkgs/nar/Makefile + doc/Makefile doc/manual/Makefile + ]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index cddcae1c6..a422fb8cf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,22 +1,24 @@ bin_PROGRAMS = nix nix-hash fix check_PROGRAMS = test + AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. -I../externals/inst/include $(CXXFLAGS) -AM_LDFLAGS = -L../externals/inst/lib -ldb_cxx -lATerm $(LDFLAGS) +LDADD = -L../externals/inst/lib -ldb_cxx -lATerm -L../boost/format -lformat nix_SOURCES = nix.cc dotgraph.cc -nix_LDADD = libshared.a libnix.a -ldb_cxx -lATerm +nix_LDADD = libshared.a libnix.a $(LDADD) nix_hash_SOURCES = nix-hash.cc -nix_hash_LDADD = libshared.a libnix.a -ldb_cxx -lATerm +nix_hash_LDADD = libshared.a libnix.a $(LDADD) fix_SOURCES = fix.cc -fix_LDADD = libshared.a libnix.a -ldb_cxx -lATerm +fix_LDADD = libshared.a libnix.a $(LDADD) TESTS = test test_SOURCES = test.cc -test_LDADD = libshared.a libnix.a -ldb_cxx -lATerm +test_LDADD = libshared.a libnix.a $(LDADD) + noinst_LIBRARIES = libnix.a libshared.a From 41730f57798a9acba1fa07397cb06ba6a260ea70 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 23 Sep 2003 14:26:58 +0000 Subject: [PATCH 0250/6440] * Put the SVN revision number in the version string. --- configure.ac | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 2f7d8333f..8e4a9d12b 100644 --- a/configure.ac +++ b/configure.ac @@ -1,8 +1,15 @@ -AC_INIT(nix, 0.3) +AC_INIT(nix, "0.3") AC_CONFIG_SRCDIR(src/nix.cc) AC_CONFIG_AUX_DIR(config) AM_INIT_AUTOMAKE +# Put the revision number in the version. +if REVISION=`svnversion $srcdir 2> /dev/null`; then + if test "$REVISION" != "exported"; then + VERSION="$VERSION-r$REVISION" + fi +fi + AC_PREFIX_DEFAULT(/nix) AC_CANONICAL_HOST From 9ba2397ea971aba101235413afe27518e0b7a2ba Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 24 Sep 2003 08:28:04 +0000 Subject: [PATCH 0251/6440] * Added missing files to `make dist'. --- boost/Makefile.am | 2 ++ boost/format/Makefile.am | 3 +++ 2 files changed, 5 insertions(+) diff --git a/boost/Makefile.am b/boost/Makefile.am index fad59a1b9..9ea79c660 100644 --- a/boost/Makefile.am +++ b/boost/Makefile.am @@ -1 +1,3 @@ SUBDIRS = format + +EXTRA_DIST = format.hpp diff --git a/boost/format/Makefile.am b/boost/format/Makefile.am index 43a44a216..d62d09aa1 100644 --- a/boost/format/Makefile.am +++ b/boost/format/Makefile.am @@ -1,3 +1,6 @@ noinst_LIBRARIES = libformat.a libformat_a_SOURCES = format_implementation.cc free_funcs.cc parsing.cc + +EXTRA_DIST = exceptions.hpp feed_args.hpp format_class.hpp format_fwd.hpp \ + internals.hpp internals_fwd.hpp macros_default.hpp From 9fb94f4f2f33b8fe26e4842558a13c6c62e6eded Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 24 Sep 2003 08:39:49 +0000 Subject: [PATCH 0252/6440] * Forgot a file. --- boost/format/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boost/format/Makefile.am b/boost/format/Makefile.am index d62d09aa1..773d5118a 100644 --- a/boost/format/Makefile.am +++ b/boost/format/Makefile.am @@ -3,4 +3,4 @@ noinst_LIBRARIES = libformat.a libformat_a_SOURCES = format_implementation.cc free_funcs.cc parsing.cc EXTRA_DIST = exceptions.hpp feed_args.hpp format_class.hpp format_fwd.hpp \ - internals.hpp internals_fwd.hpp macros_default.hpp + groups.hpp internals.hpp internals_fwd.hpp macros_default.hpp From 6d478597c7672efc546b6720c8404ffb5f998612 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 24 Sep 2003 08:40:40 +0000 Subject: [PATCH 0253/6440] * Argggg... --- boost/format/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boost/format/Makefile.am b/boost/format/Makefile.am index 773d5118a..5badba27c 100644 --- a/boost/format/Makefile.am +++ b/boost/format/Makefile.am @@ -3,4 +3,4 @@ noinst_LIBRARIES = libformat.a libformat_a_SOURCES = format_implementation.cc free_funcs.cc parsing.cc EXTRA_DIST = exceptions.hpp feed_args.hpp format_class.hpp format_fwd.hpp \ - groups.hpp internals.hpp internals_fwd.hpp macros_default.hpp + group.hpp internals.hpp internals_fwd.hpp macros_default.hpp From 4193d62e08964e2c26b27674e33327bf0417bab5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 2 Oct 2003 11:55:38 +0000 Subject: [PATCH 0254/6440] * Nix now respects $TMPDIR for the creation of temporary build directories. * Retry creation of a temporary directory (with a different name) in the case of EEXIST. --- src/exec.cc | 6 +----- src/util.cc | 20 ++++++++++++++++++++ src/util.hh | 3 +++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/exec.cc b/src/exec.cc index e9ddb5ee1..2e092b2e0 100644 --- a/src/exec.cc +++ b/src/exec.cc @@ -54,11 +54,7 @@ void runProgram(const string & program, /* Create a temporary directory where the build will take place. */ - static int counter = 0; - string tmpDir = (format("/tmp/nix-%1%-%2%") % getpid() % counter++).str(); - - if (mkdir(tmpDir.c_str(), 0777) == -1) - throw SysError(format("creating directory `%1%'") % tmpDir); + string tmpDir = createTempDir(); AutoDelete delTmpDir(tmpDir); diff --git a/src/util.cc b/src/util.cc index bedd031de..ed7562a29 100644 --- a/src/util.cc +++ b/src/util.cc @@ -171,6 +171,26 @@ void makePathReadOnly(const string & path) } +static string tempName() +{ + static int counter = 0; + char * s = getenv("TMPDIR"); + string tmpRoot = s ? canonPath(string(s)) : "/tmp"; + return (format("%1%/nix-%2%-%3%") % tmpRoot % getpid() % counter++).str(); +} + + +string createTempDir() +{ + while (1) { + string tmpDir = tempName(); + if (mkdir(tmpDir.c_str(), 0777) == 0) return tmpDir; + if (errno != EEXIST) + throw SysError(format("creating directory `%1%'") % tmpDir); + } +} + + Verbosity verbosity = lvlError; static int nestingLevel = 0; diff --git a/src/util.hh b/src/util.hh index d0e42f3b1..31dba7faf 100644 --- a/src/util.hh +++ b/src/util.hh @@ -72,6 +72,9 @@ void deletePath(const string & path); /* Make a path read-only recursively. */ void makePathReadOnly(const string & path); +/* Create a temporary directory. */ +string createTempDir(); + /* Messages. */ From e78f753aa830e795fcb05920f30b0bd6d04bed0e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 2 Oct 2003 12:22:19 +0000 Subject: [PATCH 0255/6440] * Include the right files in a distribution. --- corepkgs/fetchurl/Makefile.am | 2 +- corepkgs/nar/Makefile.am | 2 +- src/Makefile.am | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/corepkgs/fetchurl/Makefile.am b/corepkgs/fetchurl/Makefile.am index 72741ac23..0c8f0c939 100644 --- a/corepkgs/fetchurl/Makefile.am +++ b/corepkgs/fetchurl/Makefile.am @@ -7,4 +7,4 @@ install-exec-local: include ../../substitute.mk -EXTRA_DIST = fetchurl.fix fetchurl.sh +EXTRA_DIST = fetchurl.fix fetchurl.sh.in diff --git a/corepkgs/nar/Makefile.am b/corepkgs/nar/Makefile.am index f6eb27747..e369d29c5 100644 --- a/corepkgs/nar/Makefile.am +++ b/corepkgs/nar/Makefile.am @@ -9,4 +9,4 @@ install-exec-local: include ../../substitute.mk -EXTRA_DIST = nar.fix nar.sh unnar.fix unnar.sh +EXTRA_DIST = nar.fix nar.sh.in unnar.fix unnar.sh.in diff --git a/src/Makefile.am b/src/Makefile.am index a422fb8cf..e5aa15fd9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -52,4 +52,4 @@ install-data-local: $(INSTALL) -d $(prefix)/store $(bindir)/nix --init -EXTRA_DIST = *.hh *.h +EXTRA_DIST = *.hh *.h test-builder-*.sh From 563afb7fcc9d6aabec9b867372ea8d651fd12e89 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 2 Oct 2003 15:48:47 +0000 Subject: [PATCH 0256/6440] * Use passive FTP in wget. --- corepkgs/fetchurl/fetchurl.sh.in | 2 +- scripts/nix-prefetch-url.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/corepkgs/fetchurl/fetchurl.sh.in b/corepkgs/fetchurl/fetchurl.sh.in index 88e4d81f2..a6cc69930 100644 --- a/corepkgs/fetchurl/fetchurl.sh.in +++ b/corepkgs/fetchurl/fetchurl.sh.in @@ -9,7 +9,7 @@ if test -f "$prefetch"; then echo "using prefetched $prefetch"; mv $prefetch $out || exit 1 else - @wget@ "$url" -O "$out" || exit 1 + @wget@ --passive-ftp "$url" -O "$out" || exit 1 fi actual=$(@bindir@/nix-hash --flat $out) diff --git a/scripts/nix-prefetch-url.in b/scripts/nix-prefetch-url.in index f5539eb98..332290d40 100644 --- a/scripts/nix-prefetch-url.in +++ b/scripts/nix-prefetch-url.in @@ -10,7 +10,7 @@ print "fetching $url...\n"; my $out = "@prefix@/store/nix-prefetch-url-$$"; -system "@wget@ '$url' -O '$out'"; +system "@wget@ --passive-ftp '$url' -O '$out'"; $? == 0 or die "unable to fetch $url"; my $hash=`@bindir@/nix-hash --flat $out`; From 5d4171f7fb548e06ecd2440f57322b3c77f1074e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 7 Oct 2003 12:27:49 +0000 Subject: [PATCH 0257/6440] * Synchronise terminology with the ICSE paper (e.g., slice -> closure, fstate -> Nix expression). * Fix src/test.cc. --- scripts/nix-push.in | 4 +- src/Makefile.am | 2 +- src/dotgraph.cc | 30 +++--- src/dotgraph.hh | 2 +- src/{fstate.cc => expr.cc} | 110 +++++++++++----------- src/{fstate.hh => expr.hh} | 32 +++---- src/fix.cc | 73 +++++++-------- src/globals.hh | 7 +- src/nix-help.txt | 4 +- src/nix.cc | 13 ++- src/normalise.cc | 181 +++++++++++++++++++------------------ src/normalise.hh | 25 +++-- src/store.cc | 2 +- src/test-builder-2.sh | 2 +- src/test.cc | 35 +++---- 15 files changed, 254 insertions(+), 268 deletions(-) rename src/{fstate.cc => expr.cc} (65%) rename src/{fstate.hh => expr.hh} (64%) diff --git a/scripts/nix-push.in b/scripts/nix-push.in index e51f903b8..d9f5cf756 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -10,7 +10,7 @@ foreach my $id (@ARGV) { die unless $id =~ /^([0-9a-z]{32})$/; # Get all paths referenced by the normalisation of the given - # fstate expression. + # Nix expression. system "nix --install $id"; if ($?) { die "`nix --install' failed"; } @@ -52,7 +52,7 @@ foreach my $id (@ARGV) { # Construct a Fix expression that creates a Nix archive. my $fixexpr = "App(IncludeFix(\"nar/nar.fix\"), " . - "[ (\"path\", Slice([\"$path\"], [(\"$path\", \"$pathid\", [])]))" . + "[ (\"path\", Closure([\"$path\"], [(\"$path\", \"$pathid\", [])]))" . "])"; print FIX "," unless ($first); diff --git a/src/Makefile.am b/src/Makefile.am index e5aa15fd9..216d664e8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,7 +23,7 @@ test_LDADD = libshared.a libnix.a $(LDADD) noinst_LIBRARIES = libnix.a libshared.a libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c \ - store.cc fstate.cc normalise.cc exec.cc \ + store.cc expr.cc normalise.cc exec.cc \ globals.cc db.cc references.cc pathlocks.cc libshared_a_SOURCES = shared.cc diff --git a/src/dotgraph.cc b/src/dotgraph.cc index 1b352e3ff..f9ff51b90 100644 --- a/src/dotgraph.cc +++ b/src/dotgraph.cc @@ -51,9 +51,9 @@ string pathLabel(const FSId & id, const string & path) } -void printSlice(const FSId & id, const FState & fs) +void printClosure(const FSId & id, const NixExpr & fs) { - Strings workList(fs.slice.roots.begin(), fs.slice.roots.end()); + Strings workList(fs.closure.roots.begin(), fs.closure.roots.end()); StringSet doneSet; for (Strings::iterator i = workList.begin(); i != workList.end(); i++) { @@ -67,9 +67,9 @@ void printSlice(const FSId & id, const FState & fs) if (doneSet.find(path) == doneSet.end()) { doneSet.insert(path); - SliceElems::const_iterator elem = fs.slice.elems.find(path); - if (elem == fs.slice.elems.end()) - throw Error(format("bad slice, missing path `%1%'") % path); + ClosureElems::const_iterator elem = fs.closure.elems.find(path); + if (elem == fs.closure.elems.end()) + throw Error(format("bad closure, missing path `%1%'") % path); for (StringSet::const_iterator i = elem->second.refs.begin(); i != elem->second.refs.end(); i++) @@ -99,29 +99,29 @@ void printDotGraph(const FSIds & roots) if (doneSet.find(id) == doneSet.end()) { doneSet.insert(id); - FState fs = parseFState(termFromId(id)); + NixExpr ne = parseNixExpr(termFromId(id)); string label, colour; - if (fs.type == FState::fsDerive) { - for (FSIdSet::iterator i = fs.derive.inputs.begin(); - i != fs.derive.inputs.end(); i++) + if (ne.type == NixExpr::neDerivation) { + for (FSIdSet::iterator i = ne.derivation.inputs.begin(); + i != ne.derivation.inputs.end(); i++) { workList.push_back(*i); cout << makeEdge(*i, id); } - label = "derive"; + label = "derivation"; colour = "#00ff00"; - for (StringPairs::iterator i = fs.derive.env.begin(); - i != fs.derive.env.end(); i++) + for (StringPairs::iterator i = ne.derivation.env.begin(); + i != ne.derivation.env.end(); i++) if (i->first == "name") label = i->second; } - else if (fs.type == FState::fsSlice) { - label = ""; + else if (ne.type == NixExpr::neClosure) { + label = ""; colour = "#00ffff"; - printSlice(id, fs); + printClosure(id, ne); } else abort(); diff --git a/src/dotgraph.hh b/src/dotgraph.hh index d88e04e09..e1678da96 100644 --- a/src/dotgraph.hh +++ b/src/dotgraph.hh @@ -1,7 +1,7 @@ #ifndef __DOTGRAPH_H #define __DOTGRAPH_H -#include "fstate.hh" +#include "expr.hh" void printDotGraph(const FSIds & roots); diff --git a/src/fstate.cc b/src/expr.cc similarity index 65% rename from src/fstate.cc rename to src/expr.cc index 0502a8524..7d79d1654 100644 --- a/src/fstate.cc +++ b/src/expr.cc @@ -1,4 +1,4 @@ -#include "fstate.hh" +#include "expr.hh" #include "globals.hh" #include "store.hh" @@ -65,23 +65,23 @@ static void parsePaths(ATermList paths, StringSet & out) } -static void checkSlice(const Slice & slice) +static void checkClosure(const Closure & closure) { - if (slice.elems.size() == 0) - throw Error("empty slice"); + if (closure.elems.size() == 0) + throw Error("empty closure"); StringSet decl; - for (SliceElems::const_iterator i = slice.elems.begin(); - i != slice.elems.end(); i++) + for (ClosureElems::const_iterator i = closure.elems.begin(); + i != closure.elems.end(); i++) decl.insert(i->first); - for (StringSet::const_iterator i = slice.roots.begin(); - i != slice.roots.end(); i++) + for (StringSet::const_iterator i = closure.roots.begin(); + i != closure.roots.end(); i++) if (decl.find(*i) == decl.end()) throw Error(format("undefined root path `%1%'") % *i); - for (SliceElems::const_iterator i = slice.elems.begin(); - i != slice.elems.end(); i++) + for (ClosureElems::const_iterator i = closure.elems.begin(); + i != closure.elems.end(); i++) for (StringSet::const_iterator j = i->second.refs.begin(); j != i->second.refs.end(); j++) if (decl.find(*j) == decl.end()) @@ -91,35 +91,35 @@ static void checkSlice(const Slice & slice) } -/* Parse a slice. */ -static bool parseSlice(ATerm t, Slice & slice) +/* Parse a closure. */ +static bool parseClosure(ATerm t, Closure & closure) { ATermList roots, elems; - if (!ATmatch(t, "Slice([], [])", &roots, &elems)) + if (!ATmatch(t, "Closure([], [])", &roots, &elems)) return false; - parsePaths(roots, slice.roots); + parsePaths(roots, closure.roots); while (!ATisEmpty(elems)) { char * s1, * s2; ATermList refs; ATerm t = ATgetFirst(elems); if (!ATmatch(t, "(, , [])", &s1, &s2, &refs)) - throw badTerm("not a slice element", t); - SliceElem elem; + throw badTerm("not a closure element", t); + ClosureElem elem; elem.id = parseHash(s2); parsePaths(refs, elem.refs); - slice.elems[s1] = elem; + closure.elems[s1] = elem; elems = ATgetNext(elems); } - checkSlice(slice); + checkClosure(closure); return true; } -static bool parseDerive(ATerm t, Derive & derive) +static bool parseDerivation(ATerm t, Derivation & derivation) { ATermList outs, ins, args, bnds; char * builder; @@ -139,8 +139,8 @@ static bool parseDerive(ATerm t, Derive & derive) char * s1, * s2; ATerm t = ATgetFirst(outs); if (!ATmatch(t, "(, )", &s1, &s2)) - throw badTerm("not a derive output", t); - derive.outputs[s1] = parseHash(s2); + throw badTerm("not a derivation output", t); + derivation.outputs[s1] = parseHash(s2); outs = ATgetNext(outs); } @@ -149,19 +149,19 @@ static bool parseDerive(ATerm t, Derive & derive) ATerm t = ATgetFirst(ins); if (!ATmatch(t, "", &s)) throw badTerm("not an id", t); - derive.inputs.insert(parseHash(s)); + derivation.inputs.insert(parseHash(s)); ins = ATgetNext(ins); } - derive.builder = builder; - derive.platform = platform; + derivation.builder = builder; + derivation.platform = platform; while (!ATisEmpty(args)) { char * s; ATerm arg = ATgetFirst(args); if (!ATmatch(arg, "", &s)) throw badTerm("string expected", arg); - derive.args.push_back(s); + derivation.args.push_back(s); args = ATgetNext(args); } @@ -170,7 +170,7 @@ static bool parseDerive(ATerm t, Derive & derive) ATerm bnd = ATgetFirst(bnds); if (!ATmatch(bnd, "(, )", &s1, &s2)) throw badTerm("tuple of strings expected", bnd); - derive.env[s1] = s2; + derivation.env[s1] = s2; bnds = ATgetNext(bnds); } @@ -178,15 +178,15 @@ static bool parseDerive(ATerm t, Derive & derive) } -FState parseFState(ATerm t) +NixExpr parseNixExpr(ATerm t) { - FState fs; - if (parseSlice(t, fs.slice)) - fs.type = FState::fsSlice; - else if (parseDerive(t, fs.derive)) - fs.type = FState::fsDerive; - else throw badTerm("not an fstate-expression", t); - return fs; + NixExpr ne; + if (parseClosure(t, ne.closure)) + ne.type = NixExpr::neClosure; + else if (parseDerivation(t, ne.derivation)) + ne.type = NixExpr::neDerivation; + else throw badTerm("not a Nix expression", t); + return ne; } @@ -200,45 +200,45 @@ static ATermList unparsePaths(const StringSet & paths) } -static ATerm unparseSlice(const Slice & slice) +static ATerm unparseClosure(const Closure & closure) { - ATermList roots = unparsePaths(slice.roots); + ATermList roots = unparsePaths(closure.roots); ATermList elems = ATempty; - for (SliceElems::const_iterator i = slice.elems.begin(); - i != slice.elems.end(); i++) + for (ClosureElems::const_iterator i = closure.elems.begin(); + i != closure.elems.end(); i++) elems = ATinsert(elems, ATmake("(, , )", i->first.c_str(), ((string) i->second.id).c_str(), unparsePaths(i->second.refs))); - return ATmake("Slice(, )", roots, elems); + return ATmake("Closure(, )", roots, elems); } -static ATerm unparseDerive(const Derive & derive) +static ATerm unparseDerivation(const Derivation & derivation) { ATermList outs = ATempty; - for (DeriveOutputs::const_iterator i = derive.outputs.begin(); - i != derive.outputs.end(); i++) + for (DerivationOutputs::const_iterator i = derivation.outputs.begin(); + i != derivation.outputs.end(); i++) outs = ATinsert(outs, ATmake("(, )", i->first.c_str(), ((string) i->second).c_str())); ATermList ins = ATempty; - for (FSIdSet::const_iterator i = derive.inputs.begin(); - i != derive.inputs.end(); i++) + for (FSIdSet::const_iterator i = derivation.inputs.begin(); + i != derivation.inputs.end(); i++) ins = ATinsert(ins, ATmake("", ((string) *i).c_str())); ATermList args = ATempty; - for (Strings::const_iterator i = derive.args.begin(); - i != derive.args.end(); i++) + for (Strings::const_iterator i = derivation.args.begin(); + i != derivation.args.end(); i++) args = ATinsert(args, ATmake("", i->c_str())); ATermList env = ATempty; - for (StringPairs::const_iterator i = derive.env.begin(); - i != derive.env.end(); i++) + for (StringPairs::const_iterator i = derivation.env.begin(); + i != derivation.env.end(); i++) env = ATinsert(env, ATmake("(, )", i->first.c_str(), i->second.c_str())); @@ -246,18 +246,18 @@ static ATerm unparseDerive(const Derive & derive) return ATmake("Derive(, , , , , )", ATreverse(outs), ATreverse(ins), - derive.platform.c_str(), - derive.builder.c_str(), + derivation.platform.c_str(), + derivation.builder.c_str(), ATreverse(args), ATreverse(env)); } -ATerm unparseFState(const FState & fs) +ATerm unparseNixExpr(const NixExpr & ne) { - if (fs.type == FState::fsSlice) - return unparseSlice(fs.slice); - else if (fs.type == FState::fsDerive) - return unparseDerive(fs.derive); + if (ne.type == NixExpr::neClosure) + return unparseClosure(ne.closure); + else if (ne.type == NixExpr::neDerivation) + return unparseDerivation(ne.derivation); else abort(); } diff --git a/src/fstate.hh b/src/expr.hh similarity index 64% rename from src/fstate.hh rename to src/expr.hh index a7935be52..2a6a06a0a 100644 --- a/src/fstate.hh +++ b/src/expr.hh @@ -8,30 +8,30 @@ extern "C" { #include "store.hh" -/* Abstract syntax of fstate-expressions. */ +/* Abstract syntax of Nix expressions. */ typedef list FSIds; -struct SliceElem +struct ClosureElem { FSId id; StringSet refs; }; -typedef map SliceElems; +typedef map ClosureElems; -struct Slice +struct Closure { StringSet roots; - SliceElems elems; + ClosureElems elems; }; -typedef map DeriveOutputs; +typedef map DerivationOutputs; typedef map StringPairs; -struct Derive +struct Derivation { - DeriveOutputs outputs; + DerivationOutputs outputs; FSIdSet inputs; string platform; string builder; @@ -39,11 +39,11 @@ struct Derive StringPairs env; }; -struct FState +struct NixExpr { - enum { fsSlice, fsDerive } type; - Slice slice; - Derive derive; + enum { neClosure, neDerivation } type; + Closure closure; + Derivation derivation; }; @@ -63,11 +63,11 @@ ATerm termFromId(const FSId & id); /* Write an aterm to the Nix store directory, and return its hash. */ FSId writeTerm(ATerm t, const string & suffix, FSId id = FSId()); -/* Parse an fstate-expression. */ -FState parseFState(ATerm t); +/* Parse a Nix expression. */ +NixExpr parseNixExpr(ATerm t); -/* Parse an fstate-expression. */ -ATerm unparseFState(const FState & fs); +/* Parse a Nix expression. */ +ATerm unparseNixExpr(const NixExpr & ne); #endif /* !__FSTATE_H */ diff --git a/src/fix.cc b/src/fix.cc index 38c53c6d7..66438464c 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -121,47 +121,47 @@ static Expr substExprMany(ATermList formals, ATermList args, Expr body) } -static Strings fstatePathsCached(EvalState & state, const FSId & id) +static Strings nixExprPathsCached(EvalState & state, const FSId & id) { PkgPaths::iterator i = state.pkgPaths.find(id); if (i != state.pkgPaths.end()) return i->second; else { - Strings paths = fstatePaths(id); + Strings paths = nixExprPaths(id); state.pkgPaths[id] = paths; return paths; } } -static Hash hashPackage(EvalState & state, FState fs) +static Hash hashPackage(EvalState & state, NixExpr ne) { - if (fs.type == FState::fsDerive) { + if (ne.type == NixExpr::neDerivation) { FSIdSet inputs2; - for (FSIdSet::iterator i = fs.derive.inputs.begin(); - i != fs.derive.inputs.end(); i++) + for (FSIdSet::iterator i = ne.derivation.inputs.begin(); + i != ne.derivation.inputs.end(); i++) { PkgHashes::iterator j = state.pkgHashes.find(*i); if (j == state.pkgHashes.end()) throw Error(format("unknown package id %1%") % (string) *i); inputs2.insert(j->second); } - fs.derive.inputs = inputs2; + ne.derivation.inputs = inputs2; } - return hashTerm(unparseFState(fs)); + return hashTerm(unparseNixExpr(ne)); } -static string processBinding(EvalState & state, Expr e, FState & fs) +static string processBinding(EvalState & state, Expr e, NixExpr & ne) { char * s1; if (ATmatch(e, "FSId()", &s1)) { FSId id = parseHash(s1); - Strings paths = fstatePathsCached(state, id); + Strings paths = nixExprPathsCached(state, id); if (paths.size() != 1) abort(); string path = *(paths.begin()); - fs.derive.inputs.insert(id); + ne.derivation.inputs.insert(id); return path; } @@ -178,7 +178,7 @@ static string processBinding(EvalState & state, Expr e, FState & fs) bool first = true; while (!ATisEmpty(l)) { if (!first) s = s + " "; else first = false; - s += processBinding(state, evalExpr(state, ATgetFirst(l)), fs); + s += processBinding(state, evalExpr(state, ATgetFirst(l)), ne); l = ATgetNext(l); } return s; @@ -204,7 +204,7 @@ static Expr evalExpr2(EvalState & state, Expr e) return e; try { - Hash pkgHash = hashPackage(state, parseFState(e)); + Hash pkgHash = hashPackage(state, parseNixExpr(e)); FSId pkgId = writeTerm(e, ""); state.pkgHashes[pkgId] = pkgHash; return ATmake("FSId()", ((string) pkgId).c_str()); @@ -265,15 +265,15 @@ static Expr evalExpr2(EvalState & state, Expr e) FSId id; addToStore(srcPath, dstPath, id, true); - SliceElem elem; + ClosureElem elem; elem.id = id; - FState fs; - fs.type = FState::fsSlice; - fs.slice.roots.insert(dstPath); - fs.slice.elems[dstPath] = elem; + NixExpr ne; + ne.type = NixExpr::neClosure; + ne.closure.roots.insert(dstPath); + ne.closure.elems[dstPath] = elem; - Hash pkgHash = hashPackage(state, fs); - FSId pkgId = writeTerm(unparseFState(fs), ""); + Hash pkgHash = hashPackage(state, ne); + FSId pkgId = writeTerm(unparseNixExpr(ne), ""); state.pkgHashes[pkgId] = pkgHash; msg(lvlChatty, format("copied `%1%' -> %2%") @@ -282,7 +282,7 @@ static Expr evalExpr2(EvalState & state, Expr e) return ATmake("FSId()", ((string) pkgId).c_str()); } - /* Packages are transformed into Derive fstate expressions. */ + /* Packages are transformed into Nix derivation expressions. */ if (ATmatch(e, "Package([])", &bnds)) { /* Evaluate the bindings and put them in a map. */ @@ -296,10 +296,11 @@ static Expr evalExpr2(EvalState & state, Expr e) bnds = ATgetNext(bnds); } - /* Gather information for building the Derive expression. */ - FState fs; - fs.type = FState::fsDerive; - fs.derive.platform = SYSTEM; + /* Gather information for building the derivation + expression. */ + NixExpr ne; + ne.type = NixExpr::neDerivation; + ne.derivation.platform = SYSTEM; string name; FSId outId; bool outIdGiven = false; @@ -318,16 +319,16 @@ static Expr evalExpr2(EvalState & state, Expr e) while (!ATisEmpty(args)) { Expr arg = evalExpr(state, ATgetFirst(args)); - fs.derive.args.push_back(processBinding(state, arg, fs)); + ne.derivation.args.push_back(processBinding(state, arg, ne)); args = ATgetNext(args); } } else { - string s = processBinding(state, value, fs); - fs.derive.env[key] = s; + string s = processBinding(state, value, ne); + ne.derivation.env[key] = s; - if (key == "build") fs.derive.builder = s; + if (key == "build") ne.derivation.builder = s; if (key == "name") name = s; if (key == "id") { outId = parseHash(s); @@ -339,25 +340,25 @@ static Expr evalExpr2(EvalState & state, Expr e) ATmake("(, )", key.c_str(), value)); } - if (fs.derive.builder == "") + if (ne.derivation.builder == "") throw badTerm("no builder specified", e); if (name == "") throw badTerm("no package name specified", e); - /* Hash the fstate-expression with no outputs to produce a + /* Hash the Nix expression with no outputs to produce a unique but deterministic path name for this package. */ - if (!outIdGiven) outId = hashPackage(state, fs); + if (!outIdGiven) outId = hashPackage(state, ne); string outPath = canonPath(nixStore + "/" + ((string) outId).c_str() + "-" + name); - fs.derive.env["out"] = outPath; - fs.derive.outputs[outPath] = outId; + ne.derivation.env["out"] = outPath; + ne.derivation.outputs[outPath] = outId; /* Write the resulting term into the Nix store directory. */ Hash pkgHash = outIdGiven ? hashString((string) outId + outPath) - : hashPackage(state, fs); - FSId pkgId = writeTerm(unparseFState(fs), "-d-" + name); + : hashPackage(state, ne); + FSId pkgId = writeTerm(unparseNixExpr(ne), "-d-" + name); state.pkgHashes[pkgId] = pkgHash; msg(lvlChatty, format("instantiated `%1%' -> %2%") diff --git a/src/globals.hh b/src/globals.hh index 107d617bc..b140f136c 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -29,7 +29,7 @@ extern TableId dbId2Paths; /* dbSuccessors :: FSId -> FSId Each pair $(id_1, id_2)$ in this mapping records the fact that a - successor of an fstate expression stored in a file with identifier + successor of a Nix expression stored in a file with identifier $id_1$ is stored in a file with identifier $id_2$. Note that a term $y$ is successor of $x$ iff there exists a @@ -41,15 +41,14 @@ extern TableId dbSuccessors; /* dbSubstitutes :: FSId -> [FSId] Each pair $(id, [ids])$ tells Nix that it can realise any of the - fstate expressions referenced by the identifiers in $ids$ to + Nix expressions referenced by the identifiers in $ids$ to generate a path with identifier $id$. The main purpose of this is for distributed caching of derivates. One system can compute a derivate with hash $h$ and put it on a website (as a Nix archive), for instance, and then another system can register a substitute for that derivate. The substitute in - this case might be an fstate expression that fetches the Nix - archive. + this case might be a Nix expression that fetches the Nix archive. */ extern TableId dbSubstitutes; diff --git a/src/nix-help.txt b/src/nix-help.txt index 4e1d707c8..be8ecf0a1 100644 --- a/src/nix-help.txt +++ b/src/nix-help.txt @@ -2,7 +2,7 @@ nix [OPTIONS...] [ARGUMENTS...] Operations: - --install / -i: realise an fstate + --install / -i: realise a Nix expression --delete / -d: delete paths from the Nix store --add / -A: copy a path to the Nix store --query / -q: query information @@ -25,7 +25,7 @@ Source selection for --install, --dump: Query flags: - --list / -l: query the output paths (roots) of an fstate (default) + --list / -l: query the output paths (roots) of a Nix expression (default) --requisites / -r: print all paths necessary to realise expression --generators / -g: find expressions producing a subset of given ids --expansion / -e: print a path containing id diff --git a/src/nix.cc b/src/nix.cc index 12a01fd36..87553de2d 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -37,8 +37,7 @@ static FSId argToId(const string & arg) } -/* Realise (or install) paths from the given Nix fstate - expressions. */ +/* Realise (or install) paths from the given Nix expressions. */ static void opInstall(Strings opFlags, Strings opArgs) { if (!opFlags.empty()) throw UsageError("unknown flag"); @@ -46,8 +45,8 @@ static void opInstall(Strings opFlags, Strings opArgs) for (Strings::iterator it = opArgs.begin(); it != opArgs.end(); it++) { - FSId id = normaliseFState(argToId(*it)); - realiseSlice(id); + FSId id = normaliseNixExpr(argToId(*it)); + realiseClosure(id); cout << format("%1%\n") % (string) id; } } @@ -83,7 +82,7 @@ static void opAdd(Strings opFlags, Strings opArgs) FSId maybeNormalise(const FSId & id, bool normalise) { - return normalise ? normaliseFState(id) : id; + return normalise ? normaliseNixExpr(id) : id; } @@ -115,7 +114,7 @@ static void opQuery(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) { - Strings paths2 = fstatePaths( + Strings paths2 = nixExprPaths( maybeNormalise(argToId(*i), normalise)); paths.insert(paths2.begin(), paths2.end()); } @@ -130,7 +129,7 @@ static void opQuery(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) { - Strings paths2 = fstateRequisites( + Strings paths2 = nixExprRequisites( maybeNormalise(argToId(*i), normalise), includeExprs, includeSuccessors); paths.insert(paths2.begin(), paths2.end()); diff --git a/src/normalise.cc b/src/normalise.cc index 39867bfe5..994a07df1 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -26,40 +26,41 @@ static FSId useSuccessor(const FSId & id) } -Strings pathsFromOutputs(const DeriveOutputs & ps) +Strings pathsFromOutputs(const DerivationOutputs & ps) { Strings ss; - for (DeriveOutputs::const_iterator i = ps.begin(); + for (DerivationOutputs::const_iterator i = ps.begin(); i != ps.end(); i++) ss.push_back(i->first); return ss; } -FSId normaliseFState(FSId id, FSIdSet pending) +FSId normaliseNixExpr(FSId id, FSIdSet pending) { - Nest nest(lvlTalkative, format("normalising fstate %1%") % (string) id); + Nest nest(lvlTalkative, + format("normalising nix expression %1%") % (string) id); /* Try to substitute $id$ by any known successors in order to speed up the rewrite process. */ id = useSuccessor(id); - /* Get the fstate expression. */ - FState fs = parseFState(termFromId(id)); + /* Get the Nix expression. */ + NixExpr ne = parseNixExpr(termFromId(id)); - /* If this is a normal form (i.e., a slice) we are done. */ - if (fs.type == FState::fsSlice) return id; - if (fs.type != FState::fsDerive) abort(); + /* If this is a normal form (i.e., a closure) we are done. */ + if (ne.type == NixExpr::neClosure) return id; + if (ne.type != NixExpr::neDerivation) abort(); - /* Otherwise, it's a derive expression, and we have to build it to + /* Otherwise, it's a derivation expression, and we have to build it to determine its normal form. */ /* Some variables. */ - /* Input paths, with their slice elements. */ - SliceElems inSlices; + /* Input paths, with their closure elements. */ + ClosureElems inClosures; /* Referencable paths (i.e., input and output paths). */ StringSet allPaths; @@ -68,13 +69,13 @@ FSId normaliseFState(FSId id, FSIdSet pending) Environment env; /* The result. */ - FState nfFS; - nfFS.type = FState::fsSlice; + NixExpr nf; + nf.type = NixExpr::neClosure; /* Parse the outputs. */ - for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); - i != fs.derive.outputs.end(); i++) + for (DerivationOutputs::iterator i = ne.derivation.outputs.begin(); + i != ne.derivation.outputs.end(); i++) { debug(format("building %1% in `%2%'") % (string) i->second % i->first); allPaths.insert(i->first); @@ -82,7 +83,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* Obtain locks on all output paths. The locks are automatically released when we exit this function or Nix crashes. */ - PathLocks outputLocks(pathsFromOutputs(fs.derive.outputs)); + PathLocks outputLocks(pathsFromOutputs(ne.derivation.outputs)); /* Now check again whether there is a successor. This is because another process may have started building in parallel. After @@ -95,33 +96,33 @@ FSId normaliseFState(FSId id, FSIdSet pending) { FSId id2 = useSuccessor(id); if (id2 != id) { - FState fs = parseFState(termFromId(id2)); + NixExpr ne = parseNixExpr(termFromId(id2)); debug(format("skipping build of %1%, someone beat us to it") % (string) id); - if (fs.type != FState::fsSlice) abort(); + if (ne.type != NixExpr::neClosure) abort(); return id2; } } /* Right platform? */ - if (fs.derive.platform != thisSystem) + if (ne.derivation.platform != thisSystem) throw Error(format("a `%1%' is required, but I am a `%2%'") - % fs.derive.platform % thisSystem); + % ne.derivation.platform % thisSystem); /* Realise inputs (and remember all input paths). */ - for (FSIdSet::iterator i = fs.derive.inputs.begin(); - i != fs.derive.inputs.end(); i++) + for (FSIdSet::iterator i = ne.derivation.inputs.begin(); + i != ne.derivation.inputs.end(); i++) { - FSId nf = normaliseFState(*i, pending); - realiseSlice(nf, pending); + FSId nf = normaliseNixExpr(*i, pending); + realiseClosure(nf, pending); /* !!! nf should be a root of the garbage collector while we are building */ - FState fs = parseFState(termFromId(nf)); - if (fs.type != FState::fsSlice) abort(); - for (SliceElems::iterator j = fs.slice.elems.begin(); - j != fs.slice.elems.end(); j++) + NixExpr ne = parseNixExpr(termFromId(nf)); + if (ne.type != NixExpr::neClosure) abort(); + for (ClosureElems::iterator j = ne.closure.elems.begin(); + j != ne.closure.elems.end(); j++) { - inSlices[j->first] = j->second; + inClosures[j->first] = j->second; allPaths.insert(j->first); } } @@ -140,15 +141,15 @@ FSId normaliseFState(FSId id, FSIdSet pending) env["HOME"] = "/homeless-shelter"; /* Build the environment. */ - for (StringPairs::iterator i = fs.derive.env.begin(); - i != fs.derive.env.end(); i++) + for (StringPairs::iterator i = ne.derivation.env.begin(); + i != ne.derivation.env.end(); i++) env[i->first] = i->second; /* We can skip running the builder if we can expand all output paths from their ids. */ bool fastBuild = true; - for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); - i != fs.derive.outputs.end(); i++) + for (DerivationOutputs::iterator i = ne.derivation.outputs.begin(); + i != ne.derivation.outputs.end(); i++) { try { expandId(i->second, i->first, "/", pending); @@ -164,8 +165,8 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* If any of the outputs already exist but are not registered, delete them. */ - for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); - i != fs.derive.outputs.end(); i++) + for (DerivationOutputs::iterator i = ne.derivation.outputs.begin(); + i != ne.derivation.outputs.end(); i++) { string path = i->first; FSId id; @@ -179,7 +180,7 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* Run the builder. */ msg(lvlChatty, format("building...")); - runProgram(fs.derive.builder, fs.derive.args, env); + runProgram(ne.derivation.builder, ne.derivation.args, env); msg(lvlChatty, format("build completed")); } else @@ -189,13 +190,13 @@ FSId normaliseFState(FSId id, FSIdSet pending) output path to determine what other paths it references. Also make all output paths read-only. */ StringSet usedPaths; - for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); - i != fs.derive.outputs.end(); i++) + for (DerivationOutputs::iterator i = ne.derivation.outputs.begin(); + i != ne.derivation.outputs.end(); i++) { string path = i->first; if (!pathExists(path)) throw Error(format("path `%1%' does not exist") % path); - nfFS.slice.roots.insert(path); + nf.closure.roots.insert(path); makePathReadOnly(path); @@ -204,28 +205,28 @@ FSId normaliseFState(FSId id, FSIdSet pending) Strings refPaths = filterReferences(path, Strings(allPaths.begin(), allPaths.end())); - /* Construct a slice element for this output path. */ - SliceElem elem; + /* Construct a closure element for this output path. */ + ClosureElem elem; elem.id = i->second; /* For each path referenced by this output path, add its id to the - slice element and add the id to the `usedPaths' set (so that the - elements referenced by *its* slice are added below). */ + closure element and add the id to the `usedPaths' set (so that the + elements referenced by *its* closure are added below). */ for (Strings::iterator j = refPaths.begin(); j != refPaths.end(); j++) { string path = *j; elem.refs.insert(path); - if (inSlices.find(path) != inSlices.end()) + if (inClosures.find(path) != inClosures.end()) usedPaths.insert(path); - else if (fs.derive.outputs.find(path) == fs.derive.outputs.end()) + else if (ne.derivation.outputs.find(path) == ne.derivation.outputs.end()) abort(); } - nfFS.slice.elems[path] = elem; + nf.closure.elems[path] = elem; } - /* Close the slice. That is, for any referenced path, add the paths + /* Close the closure. That is, for any referenced path, add the paths referenced by it. */ StringSet donePaths; @@ -237,10 +238,10 @@ FSId normaliseFState(FSId id, FSIdSet pending) if (donePaths.find(path) != donePaths.end()) continue; donePaths.insert(path); - SliceElems::iterator j = inSlices.find(path); - if (j == inSlices.end()) abort(); + ClosureElems::iterator j = inClosures.find(path); + if (j == inClosures.end()) abort(); - nfFS.slice.elems[path] = j->second; + nf.closure.elems[path] = j->second; for (StringSet::iterator k = j->second.refs.begin(); k != j->second.refs.end(); k++) @@ -248,8 +249,8 @@ FSId normaliseFState(FSId id, FSIdSet pending) } /* For debugging, print out the referenced and unreferenced paths. */ - for (SliceElems::iterator i = inSlices.begin(); - i != inSlices.end(); i++) + for (ClosureElems::iterator i = inClosures.begin(); + i != inClosures.end(); i++) { StringSet::iterator j = donePaths.find(i->first); if (j == donePaths.end()) @@ -260,9 +261,9 @@ FSId normaliseFState(FSId id, FSIdSet pending) /* Write the normal form. This does not have to occur in the transaction below because writing terms is idem-potent. */ - ATerm nf = unparseFState(nfFS); - msg(lvlVomit, format("normal form: %1%") % printTerm(nf)); - FSId idNF = writeTerm(nf, "-s-" + (string) id); + ATerm nfTerm = unparseNixExpr(nf); + msg(lvlVomit, format("normal form: %1%") % printTerm(nfTerm)); + FSId idNF = writeTerm(nfTerm, "-s-" + (string) id); /* Register each outpat path, and register the normal form. This is wrapped in one database transaction to ensure that if we @@ -271,8 +272,8 @@ FSId normaliseFState(FSId id, FSIdSet pending) deleted arbitrarily, while registered paths can only be deleted by running the garbage collector. */ Transaction txn(nixDB); - for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); - i != fs.derive.outputs.end(); i++) + for (DerivationOutputs::iterator i = ne.derivation.outputs.begin(); + i != ne.derivation.outputs.end(); i++) registerPath(txn, i->first, i->second); registerSuccessor(txn, id, idNF); txn.commit(); @@ -281,36 +282,36 @@ FSId normaliseFState(FSId id, FSIdSet pending) } -void realiseSlice(const FSId & id, FSIdSet pending) +void realiseClosure(const FSId & id, FSIdSet pending) { Nest nest(lvlDebug, - format("realising slice %1%") % (string) id); + format("realising closure %1%") % (string) id); - FState fs = parseFState(termFromId(id)); - if (fs.type != FState::fsSlice) - throw Error(format("expected slice in %1%") % (string) id); + NixExpr ne = parseNixExpr(termFromId(id)); + if (ne.type != NixExpr::neClosure) + throw Error(format("expected closure in %1%") % (string) id); - for (SliceElems::const_iterator i = fs.slice.elems.begin(); - i != fs.slice.elems.end(); i++) + for (ClosureElems::const_iterator i = ne.closure.elems.begin(); + i != ne.closure.elems.end(); i++) expandId(i->second.id, i->first, "/", pending); } -Strings fstatePaths(const FSId & id) +Strings nixExprPaths(const FSId & id) { Strings paths; - FState fs = parseFState(termFromId(id)); + NixExpr ne = parseNixExpr(termFromId(id)); - if (fs.type == FState::fsSlice) { - for (StringSet::const_iterator i = fs.slice.roots.begin(); - i != fs.slice.roots.end(); i++) + if (ne.type == NixExpr::neClosure) { + for (StringSet::const_iterator i = ne.closure.roots.begin(); + i != ne.closure.roots.end(); i++) paths.push_back(*i); } - else if (fs.type == FState::fsDerive) { - for (DeriveOutputs::iterator i = fs.derive.outputs.begin(); - i != fs.derive.outputs.end(); i++) + else if (ne.type == NixExpr::neDerivation) { + for (DerivationOutputs::iterator i = ne.derivation.outputs.begin(); + i != ne.derivation.outputs.end(); i++) paths.push_back(i->first); } @@ -320,24 +321,24 @@ Strings fstatePaths(const FSId & id) } -static void fstateRequisitesSet(const FSId & id, +static void nixExprRequisitesSet(const FSId & id, bool includeExprs, bool includeSuccessors, StringSet & paths, FSIdSet & doneSet) { if (doneSet.find(id) != doneSet.end()) return; doneSet.insert(id); - FState fs = parseFState(termFromId(id)); + NixExpr ne = parseNixExpr(termFromId(id)); - if (fs.type == FState::fsSlice) - for (SliceElems::iterator i = fs.slice.elems.begin(); - i != fs.slice.elems.end(); i++) + if (ne.type == NixExpr::neClosure) + for (ClosureElems::iterator i = ne.closure.elems.begin(); + i != ne.closure.elems.end(); i++) paths.insert(i->first); - else if (fs.type == FState::fsDerive) - for (FSIdSet::iterator i = fs.derive.inputs.begin(); - i != fs.derive.inputs.end(); i++) - fstateRequisitesSet(*i, + else if (ne.type == NixExpr::neDerivation) + for (FSIdSet::iterator i = ne.derivation.inputs.begin(); + i != ne.derivation.inputs.end(); i++) + nixExprRequisitesSet(*i, includeExprs, includeSuccessors, paths, doneSet); else abort(); @@ -348,17 +349,17 @@ static void fstateRequisitesSet(const FSId & id, string idSucc; if (includeSuccessors && nixDB.queryString(noTxn, dbSuccessors, id, idSucc)) - fstateRequisitesSet(parseHash(idSucc), + nixExprRequisitesSet(parseHash(idSucc), includeExprs, includeSuccessors, paths, doneSet); } -Strings fstateRequisites(const FSId & id, +Strings nixExprRequisites(const FSId & id, bool includeExprs, bool includeSuccessors) { StringSet paths; FSIdSet doneSet; - fstateRequisitesSet(id, includeExprs, includeSuccessors, paths, doneSet); + nixExprRequisitesSet(id, includeExprs, includeSuccessors, paths, doneSet); return Strings(paths.begin(), paths.end()); } @@ -381,19 +382,19 @@ FSIds findGenerators(const FSIds & _ids) if (!nixDB.queryString(noTxn, dbSuccessors, *i, s)) continue; FSId id = parseHash(s); - FState fs; + NixExpr ne; try { /* !!! should substitutes be used? */ - fs = parseFState(termFromId(id)); + ne = parseNixExpr(termFromId(id)); } catch (...) { /* !!! only catch parse errors */ continue; } - if (fs.type != FState::fsSlice) continue; + if (ne.type != NixExpr::neClosure) continue; bool okay = true; - for (SliceElems::const_iterator i = fs.slice.elems.begin(); - i != fs.slice.elems.end(); i++) + for (ClosureElems::const_iterator i = ne.closure.elems.begin(); + i != ne.closure.elems.end(); i++) if (ids.find(i->second.id) == ids.end()) { okay = false; break; diff --git a/src/normalise.hh b/src/normalise.hh index 59ab32573..9b8274681 100644 --- a/src/normalise.hh +++ b/src/normalise.hh @@ -1,30 +1,29 @@ #ifndef __NORMALISE_H #define __NORMALISE_H -#include "fstate.hh" +#include "expr.hh" -/* Normalise an fstate-expression, that is, return an equivalent - slice. (For the meaning of `pending', see expandId()). */ -FSId normaliseFState(FSId id, FSIdSet pending = FSIdSet()); +/* Normalise a Nix expression, that is, return an equivalent + closure. (For the meaning of `pending', see expandId()). */ +FSId normaliseNixExpr(FSId id, FSIdSet pending = FSIdSet()); -/* Realise a Slice in the file system. */ -void realiseSlice(const FSId & id, FSIdSet pending = FSIdSet()); +/* Realise a Closure in the file system. */ +void realiseClosure(const FSId & id, FSIdSet pending = FSIdSet()); -/* Get the list of root (output) paths of the given - fstate-expression. */ -Strings fstatePaths(const FSId & id); +/* Get the list of root (output) paths of the given Nix expression. */ +Strings nixExprPaths(const FSId & id); /* Get the list of paths that are required to realise the given expression. For a derive expression, this is the union of - requisites of the inputs; for a slice expression, it is the path of - each element in the slice. If `includeExprs' is true, include the + requisites of the inputs; for a closure expression, it is the path of + each element in the closure. If `includeExprs' is true, include the paths of the Nix expressions themselves. If `includeSuccessors' is true, include the requisites of successors. */ -Strings fstateRequisites(const FSId & id, +Strings nixExprRequisites(const FSId & id, bool includeExprs, bool includeSuccessors); -/* Return the list of the ids of all known fstate-expressions whose +/* Return the list of the ids of all known Nix expressions whose output ids are completely contained in `ids'. */ FSIds findGenerators(const FSIds & ids); diff --git a/src/store.cc b/src/store.cc index 8a3db12ba..f05cdf3ba 100644 --- a/src/store.cc +++ b/src/store.cc @@ -246,7 +246,7 @@ string expandId(const FSId & id, const string & target, debug(format("trying substitute %1%") % (string) subId); - realiseSlice(normaliseFState(subId, pending), pending); + realiseClosure(normaliseNixExpr(subId, pending), pending); return expandId(id, target, prefix, pending); } diff --git a/src/test-builder-2.sh b/src/test-builder-2.sh index 1c4ac8494..0794fa96a 100755 --- a/src/test-builder-2.sh +++ b/src/test-builder-2.sh @@ -2,7 +2,7 @@ echo "builder 2" -mkdir $out || exit 1 +/bin/mkdir $out || exit 1 cd $out || exit 1 echo "Hallo Wereld" > bla echo $builder >> bla diff --git a/src/test.cc b/src/test.cc index d640e335a..fb1e62eb3 100644 --- a/src/test.cc +++ b/src/test.cc @@ -13,23 +13,10 @@ void realise(FSId id) { Nest nest(lvlDebug, format("TEST: realising %1%") % (string) id); - realiseSlice(normaliseFState(id)); + realiseClosure(normaliseNixExpr(id)); } -#if 0 -void realiseFail(FState fs) -{ - try { - realiseFState(fs); - abort(); - } catch (Error e) { - cout << "error (expected): " << e.what() << endl; - } -} -#endif - - struct MySink : DumpSink { virtual void operator () (const unsigned char * data, unsigned int len) @@ -115,8 +102,8 @@ void runTests() addToStore("./test-builder-1.sh", builder1fn, builder1id); ATerm fs1 = ATmake( - "Slice([], [(, , [])])", - ((string) builder1id).c_str(), + "Closure([], [(, , [])])", + builder1fn.c_str(), builder1fn.c_str(), ((string) builder1id).c_str()); FSId fs1id = writeTerm(fs1, ""); @@ -125,8 +112,8 @@ void runTests() realise(fs1id); ATerm fs2 = ATmake( - "Slice([], [(, , [])])", - ((string) builder1id).c_str(), + "Closure([], [(, , [])])", + (builder1fn + "_bla").c_str(), (builder1fn + "_bla").c_str(), ((string) builder1id).c_str()); FSId fs2id = writeTerm(fs2, ""); @@ -137,12 +124,12 @@ void runTests() string out1id = hashString("foo"); /* !!! bad */ string out1fn = nixStore + "/" + (string) out1id + "-hello.txt"; ATerm fs3 = ATmake( - "Derive([(, )], [], , , [(\"out\", )])", + "Derive([(, )], [], , , [], [(\"out\", )])", out1fn.c_str(), ((string) out1id).c_str(), ((string) fs1id).c_str(), - ((string) builder1fn).c_str(), thisSystem.c_str(), + ((string) builder1fn).c_str(), out1fn.c_str()); debug(printTerm(fs3)); FSId fs3id = writeTerm(fs3, ""); @@ -156,8 +143,8 @@ void runTests() addToStore("./test-builder-2.sh", builder4fn, builder4id); ATerm fs4 = ATmake( - "Slice([], [(, , [])])", - ((string) builder4id).c_str(), + "Closure([], [(, , [])])", + builder4fn.c_str(), builder4fn.c_str(), ((string) builder4id).c_str()); FSId fs4id = writeTerm(fs4, ""); @@ -167,12 +154,12 @@ void runTests() string out5id = hashString("bar"); /* !!! bad */ string out5fn = nixStore + "/" + (string) out5id + "-hello2"; ATerm fs5 = ATmake( - "Derive([(, )], [], , , [(\"out\", ), (\"builder\", )])", + "Derive([(, )], [], , , [], [(\"out\", ), (\"builder\", )])", out5fn.c_str(), ((string) out5id).c_str(), ((string) fs4id).c_str(), - ((string) builder4fn).c_str(), thisSystem.c_str(), + ((string) builder4fn).c_str(), out5fn.c_str(), ((string) builder4fn).c_str()); debug(printTerm(fs5)); From b9f4942bd2f8aae44db6caa5a4ebe5680880fec2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 7 Oct 2003 14:37:41 +0000 Subject: [PATCH 0258/6440] * string -> Path. --- src/util.cc | 24 ++++++++++++------------ src/util.hh | 22 ++++++++++++++-------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/util.cc b/src/util.cc index ed7562a29..c1d0fedea 100644 --- a/src/util.cc +++ b/src/util.cc @@ -25,7 +25,7 @@ SysError::SysError(const format & f) } -string absPath(string path, string dir) +Path absPath(Path path, Path dir) { if (path[0] != '/') { if (dir == "") { @@ -40,7 +40,7 @@ string absPath(string path, string dir) } -string canonPath(const string & path) +Path canonPath(const Path & path) { string s; @@ -78,16 +78,16 @@ string canonPath(const string & path) } -string dirOf(string path) +Path dirOf(const Path & path) { unsigned int pos = path.rfind('/'); if (pos == string::npos) throw Error(format("invalid file name: %1%") % path); - return string(path, 0, pos); + return Path(path, 0, pos); } -string baseNameOf(string path) +string baseNameOf(const Path & path) { unsigned int pos = path.rfind('/'); if (pos == string::npos) @@ -96,7 +96,7 @@ string baseNameOf(string path) } -bool pathExists(const string & path) +bool pathExists(const Path & path) { int res; struct stat st; @@ -108,7 +108,7 @@ bool pathExists(const string & path) } -void deletePath(const string & path) +void deletePath(const Path & path) { msg(lvlVomit, format("deleting path `%1%'") % path); @@ -145,7 +145,7 @@ void deletePath(const string & path) } -void makePathReadOnly(const string & path) +void makePathReadOnly(const Path & path) { struct stat st; if (lstat(path.c_str(), &st)) @@ -171,19 +171,19 @@ void makePathReadOnly(const string & path) } -static string tempName() +static Path tempName() { static int counter = 0; char * s = getenv("TMPDIR"); - string tmpRoot = s ? canonPath(string(s)) : "/tmp"; + Path tmpRoot = s ? canonPath(Path(s)) : "/tmp"; return (format("%1%/nix-%2%-%3%") % tmpRoot % getpid() % counter++).str(); } -string createTempDir() +Path createTempDir() { while (1) { - string tmpDir = tempName(); + Path tmpDir = tempName(); if (mkdir(tmpDir.c_str(), 0777) == 0) return tmpDir; if (errno != EEXIST) throw SysError(format("creating directory `%1%'") % tmpDir); diff --git a/src/util.hh b/src/util.hh index 31dba7faf..016289176 100644 --- a/src/util.hh +++ b/src/util.hh @@ -42,6 +42,12 @@ typedef list Strings; typedef set StringSet; +/* Paths are just strings. */ +typedef string Path; +typedef list Paths; +typedef set PathSet; + + /* The canonical system name, as returned by config.guess. */ extern string thisSystem; @@ -49,31 +55,31 @@ extern string thisSystem; /* Return an absolutized path, resolving paths relative to the specified directory, or the current directory otherwise. The path is also canonicalised. */ -string absPath(string path, string dir = ""); +Path absPath(Path path, Path dir = ""); /* Canonicalise a path (as in realpath(3)). */ -string canonPath(const string & path); +Path canonPath(const Path & path); /* Return the directory part of the given path, i.e., everything before the final `/'. */ -string dirOf(string path); +Path dirOf(const Path & path); /* Return the base name of the given path, i.e., everything following the final `/'. */ -string baseNameOf(string path); +string baseNameOf(const Path & path); /* Return true iff the given path exists. */ -bool pathExists(const string & path); +bool pathExists(const Path & path); /* Delete a path; i.e., in the case of a directory, it is deleted recursively. Don't use this at home, kids. */ -void deletePath(const string & path); +void deletePath(const Path & path); /* Make a path read-only recursively. */ -void makePathReadOnly(const string & path); +void makePathReadOnly(const Path & path); /* Create a temporary directory. */ -string createTempDir(); +Path createTempDir(); /* Messages. */ From 6baa2c442035fb06652a7fad9d51df1ce41c05f9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Oct 2003 15:06:59 +0000 Subject: [PATCH 0259/6440] * Get rid of identifiers since they are redundant now. This greatly simplifies stuff. * The format of Nix expressions and the database schema changed because of this, so it's best to delete old Nix installations. --- src/archive.cc | 18 +-- src/archive.hh | 6 +- src/db.cc | 2 + src/dotgraph.cc | 50 ++++---- src/dotgraph.hh | 2 +- src/expr.cc | 75 ++++-------- src/expr.hh | 24 ++-- src/fix.cc | 109 +++++++++--------- src/globals.cc | 6 +- src/globals.hh | 40 +++---- src/hash.cc | 14 +-- src/hash.hh | 11 +- src/nix-help.txt | 5 - src/nix.cc | 86 +++++--------- src/normalise.cc | 285 +++++++++++++++++++++++++++++----------------- src/normalise.hh | 29 +++-- src/pathlocks.cc | 12 +- src/pathlocks.hh | 4 +- src/references.hh | 2 +- src/shared.cc | 2 - src/store.cc | 208 ++++++++------------------------- src/store.hh | 41 ++----- src/test.cc | 85 ++++++-------- 23 files changed, 485 insertions(+), 631 deletions(-) diff --git a/src/archive.cc b/src/archive.cc index 4a6211e00..9039ad7db 100644 --- a/src/archive.cc +++ b/src/archive.cc @@ -49,7 +49,7 @@ static void writeString(const string & s, DumpSink & sink) static void dump(const string & path, DumpSink & sink); -static void dumpEntries(const string & path, DumpSink & sink) +static void dumpEntries(const Path & path, DumpSink & sink) { DIR * dir = opendir(path.c_str()); if (!dir) throw SysError("opening directory " + path); @@ -82,7 +82,7 @@ static void dumpEntries(const string & path, DumpSink & sink) } -static void dumpContents(const string & path, unsigned int size, +static void dumpContents(const Path & path, unsigned int size, DumpSink & sink) { writeString("contents", sink); @@ -110,7 +110,7 @@ static void dumpContents(const string & path, unsigned int size, } -static void dump(const string & path, DumpSink & sink) +static void dump(const Path & path, DumpSink & sink) { struct stat st; if (lstat(path.c_str(), &st)) @@ -150,7 +150,7 @@ static void dump(const string & path, DumpSink & sink) } -void dumpPath(const string & path, DumpSink & sink) +void dumpPath(const Path & path, DumpSink & sink) { writeString(archiveVersion1, sink); dump(path, sink); @@ -207,10 +207,10 @@ static void skipGeneric(RestoreSource & source) } -static void restore(const string & path, RestoreSource & source); +static void restore(const Path & path, RestoreSource & source); -static void restoreEntry(const string & path, RestoreSource & source) +static void restoreEntry(const Path & path, RestoreSource & source) { string s, name; @@ -235,7 +235,7 @@ static void restoreEntry(const string & path, RestoreSource & source) } -static void restoreContents(int fd, const string & path, RestoreSource & source) +static void restoreContents(int fd, const Path & path, RestoreSource & source) { unsigned int size = readInt(source); unsigned int left = size; @@ -254,7 +254,7 @@ static void restoreContents(int fd, const string & path, RestoreSource & source) } -static void restore(const string & path, RestoreSource & source) +static void restore(const Path & path, RestoreSource & source) { string s; @@ -331,7 +331,7 @@ static void restore(const string & path, RestoreSource & source) } -void restorePath(const string & path, RestoreSource & source) +void restorePath(const Path & path, RestoreSource & source) { if (readString(source) != archiveVersion1) throw badArchive("expected Nix archive"); diff --git a/src/archive.hh b/src/archive.hh index e6006e454..67e236668 100644 --- a/src/archive.hh +++ b/src/archive.hh @@ -1,6 +1,6 @@ #include -using namespace std; +#include "util.hh" /* dumpPath creates a Nix archive of the specified path. The format @@ -45,7 +45,7 @@ struct DumpSink virtual void operator () (const unsigned char * data, unsigned int len) = 0; }; -void dumpPath(const string & path, DumpSink & sink); +void dumpPath(const Path & path, DumpSink & sink); struct RestoreSource @@ -57,4 +57,4 @@ struct RestoreSource virtual void operator () (unsigned char * data, unsigned int len) = 0; }; -void restorePath(const string & path, RestoreSource & source); +void restorePath(const Path & path, RestoreSource & source); diff --git a/src/db.cc b/src/db.cc index 61ecb203a..ffb1999fe 100644 --- a/src/db.cc +++ b/src/db.cc @@ -258,6 +258,8 @@ void Database::delPair(const Transaction & txn, TableId table, 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); } } diff --git a/src/dotgraph.cc b/src/dotgraph.cc index f9ff51b90..514fda325 100644 --- a/src/dotgraph.cc +++ b/src/dotgraph.cc @@ -45,24 +45,24 @@ static string symbolicName(const string & path) } -string pathLabel(const FSId & id, const string & path) +string pathLabel(const Path & nePath, const string & elemPath) { - return (string) id + "-" + path; + return (string) nePath + "-" + elemPath; } -void printClosure(const FSId & id, const NixExpr & fs) +void printClosure(const Path & nePath, const NixExpr & fs) { - Strings workList(fs.closure.roots.begin(), fs.closure.roots.end()); - StringSet doneSet; + PathSet workList(fs.closure.roots); + PathSet doneSet; - for (Strings::iterator i = workList.begin(); i != workList.end(); i++) { - cout << makeEdge(pathLabel(id, *i), id); + for (PathSet::iterator i = workList.begin(); i != workList.end(); i++) { + cout << makeEdge(pathLabel(nePath, *i), nePath); } while (!workList.empty()) { - string path = workList.front(); - workList.pop_front(); + Path path = *(workList.begin()); + workList.erase(path); if (doneSet.find(path) == doneSet.end()) { doneSet.insert(path); @@ -74,41 +74,41 @@ void printClosure(const FSId & id, const NixExpr & fs) for (StringSet::const_iterator i = elem->second.refs.begin(); i != elem->second.refs.end(); i++) { - workList.push_back(*i); - cout << makeEdge(pathLabel(id, *i), pathLabel(id, path)); + workList.insert(*i); + cout << makeEdge(pathLabel(nePath, *i), pathLabel(nePath, path)); } - cout << makeNode(pathLabel(id, path), + cout << makeNode(pathLabel(nePath, path), symbolicName(path), "#ff0000"); } } } -void printDotGraph(const FSIds & roots) +void printDotGraph(const PathSet & roots) { - FSIds workList(roots.begin(), roots.end()); - FSIdSet doneSet; + PathSet workList(roots); + PathSet doneSet; cout << "digraph G {\n"; while (!workList.empty()) { - FSId id = workList.front(); - workList.pop_front(); + Path nePath = *(workList.begin()); + workList.erase(nePath); - if (doneSet.find(id) == doneSet.end()) { - doneSet.insert(id); + if (doneSet.find(nePath) == doneSet.end()) { + doneSet.insert(nePath); - NixExpr ne = parseNixExpr(termFromId(id)); + NixExpr ne = parseNixExpr(termFromPath(nePath)); string label, colour; if (ne.type == NixExpr::neDerivation) { - for (FSIdSet::iterator i = ne.derivation.inputs.begin(); + for (PathSet::iterator i = ne.derivation.inputs.begin(); i != ne.derivation.inputs.end(); i++) { - workList.push_back(*i); - cout << makeEdge(*i, id); + workList.insert(*i); + cout << makeEdge(*i, nePath); } label = "derivation"; @@ -121,12 +121,12 @@ void printDotGraph(const FSIds & roots) else if (ne.type == NixExpr::neClosure) { label = ""; colour = "#00ffff"; - printClosure(id, ne); + printClosure(nePath, ne); } else abort(); - cout << makeNode(id, label, colour); + cout << makeNode(nePath, label, colour); } } diff --git a/src/dotgraph.hh b/src/dotgraph.hh index e1678da96..a5c5248f1 100644 --- a/src/dotgraph.hh +++ b/src/dotgraph.hh @@ -3,6 +3,6 @@ #include "expr.hh" -void printDotGraph(const FSIds & roots); +void printDotGraph(const PathSet & roots); #endif /* !__DOTGRAPH_H */ diff --git a/src/expr.cc b/src/expr.cc index 7d79d1654..2ed3e678b 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -22,37 +22,33 @@ Hash hashTerm(ATerm t) } -ATerm termFromId(const FSId & id) +ATerm termFromPath(const Path & path) { - string path = expandId(id); ATerm t = ATreadFromNamedFile(path.c_str()); if (!t) throw Error(format("cannot read aterm from `%1%'") % path); return t; } -FSId writeTerm(ATerm t, const string & suffix, FSId id) +Path writeTerm(ATerm t, const string & suffix) { - /* By default, the id of a term is its hash. */ - if (id == FSId()) id = hashTerm(t); + /* The id of a term is its hash. */ + Hash h = hashTerm(t); - string path = canonPath(nixStore + "/" + - (string) id + suffix + ".nix"); + Path path = canonPath(nixStore + "/" + + (string) h + suffix + ".nix"); if (!ATwriteToNamedTextFile(t, path.c_str())) throw Error(format("cannot write aterm %1%") % path); -// debug(format("written term %1% = %2%") % (string) id % -// printTerm(t)); - Transaction txn(nixDB); - registerPath(txn, path, id); + registerValidPath(txn, path); txn.commit(); - return id; + return path; } -static void parsePaths(ATermList paths, StringSet & out) +static void parsePaths(ATermList paths, PathSet & out) { while (!ATisEmpty(paths)) { char * s; @@ -70,19 +66,19 @@ static void checkClosure(const Closure & closure) if (closure.elems.size() == 0) throw Error("empty closure"); - StringSet decl; + PathSet decl; for (ClosureElems::const_iterator i = closure.elems.begin(); i != closure.elems.end(); i++) decl.insert(i->first); - for (StringSet::const_iterator i = closure.roots.begin(); + for (PathSet::const_iterator i = closure.roots.begin(); i != closure.roots.end(); i++) if (decl.find(*i) == decl.end()) throw Error(format("undefined root path `%1%'") % *i); for (ClosureElems::const_iterator i = closure.elems.begin(); i != closure.elems.end(); i++) - for (StringSet::const_iterator j = i->second.refs.begin(); + for (PathSet::const_iterator j = i->second.refs.begin(); j != i->second.refs.end(); j++) if (decl.find(*j) == decl.end()) throw Error( @@ -102,13 +98,12 @@ static bool parseClosure(ATerm t, Closure & closure) parsePaths(roots, closure.roots); while (!ATisEmpty(elems)) { - char * s1, * s2; + char * s1; ATermList refs; ATerm t = ATgetFirst(elems); - if (!ATmatch(t, "(, , [])", &s1, &s2, &refs)) + if (!ATmatch(t, "(, [])", &s1, &refs)) throw badTerm("not a closure element", t); ClosureElem elem; - elem.id = parseHash(s2); parsePaths(refs, elem.refs); closure.elems[s1] = elem; elems = ATgetNext(elems); @@ -135,23 +130,8 @@ static bool parseDerivation(ATerm t, Derivation & derivation) args = ATempty; } - while (!ATisEmpty(outs)) { - char * s1, * s2; - ATerm t = ATgetFirst(outs); - if (!ATmatch(t, "(, )", &s1, &s2)) - throw badTerm("not a derivation output", t); - derivation.outputs[s1] = parseHash(s2); - outs = ATgetNext(outs); - } - - while (!ATisEmpty(ins)) { - char * s; - ATerm t = ATgetFirst(ins); - if (!ATmatch(t, "", &s)) - throw badTerm("not an id", t); - derivation.inputs.insert(parseHash(s)); - ins = ATgetNext(ins); - } + parsePaths(outs, derivation.outputs); + parsePaths(ins, derivation.inputs); derivation.builder = builder; derivation.platform = platform; @@ -190,10 +170,10 @@ NixExpr parseNixExpr(ATerm t) } -static ATermList unparsePaths(const StringSet & paths) +static ATermList unparsePaths(const PathSet & paths) { ATermList l = ATempty; - for (StringSet::const_iterator i = paths.begin(); + for (PathSet::const_iterator i = paths.begin(); i != paths.end(); i++) l = ATinsert(l, ATmake("", i->c_str())); return ATreverse(l); @@ -208,9 +188,8 @@ static ATerm unparseClosure(const Closure & closure) for (ClosureElems::const_iterator i = closure.elems.begin(); i != closure.elems.end(); i++) elems = ATinsert(elems, - ATmake("(, , )", + ATmake("(, )", i->first.c_str(), - ((string) i->second.id).c_str(), unparsePaths(i->second.refs))); return ATmake("Closure(, )", roots, elems); @@ -219,18 +198,6 @@ static ATerm unparseClosure(const Closure & closure) static ATerm unparseDerivation(const Derivation & derivation) { - ATermList outs = ATempty; - for (DerivationOutputs::const_iterator i = derivation.outputs.begin(); - i != derivation.outputs.end(); i++) - outs = ATinsert(outs, - ATmake("(, )", - i->first.c_str(), ((string) i->second).c_str())); - - ATermList ins = ATempty; - for (FSIdSet::const_iterator i = derivation.inputs.begin(); - i != derivation.inputs.end(); i++) - ins = ATinsert(ins, ATmake("", ((string) *i).c_str())); - ATermList args = ATempty; for (Strings::const_iterator i = derivation.args.begin(); i != derivation.args.end(); i++) @@ -244,8 +211,8 @@ static ATerm unparseDerivation(const Derivation & derivation) i->first.c_str(), i->second.c_str())); return ATmake("Derive(, , , , , )", - ATreverse(outs), - ATreverse(ins), + unparsePaths(derivation.outputs), + unparsePaths(derivation.inputs), derivation.platform.c_str(), derivation.builder.c_str(), ATreverse(args), diff --git a/src/expr.hh b/src/expr.hh index 2a6a06a0a..b34564322 100644 --- a/src/expr.hh +++ b/src/expr.hh @@ -10,31 +10,27 @@ extern "C" { /* Abstract syntax of Nix expressions. */ -typedef list FSIds; - struct ClosureElem { - FSId id; - StringSet refs; + PathSet refs; }; -typedef map ClosureElems; +typedef map ClosureElems; struct Closure { - StringSet roots; + PathSet roots; ClosureElems elems; }; -typedef map DerivationOutputs; typedef map StringPairs; struct Derivation { - DerivationOutputs outputs; - FSIdSet inputs; + PathSet outputs; + PathSet inputs; /* Nix expressions, not actual inputs */ string platform; - string builder; + Path builder; Strings args; StringPairs env; }; @@ -57,11 +53,11 @@ Error badTerm(const format & f, ATerm t); /* Hash an aterm. */ Hash hashTerm(ATerm t); -/* Read an aterm from disk, given its id. */ -ATerm termFromId(const FSId & id); +/* Read an aterm from disk. */ +ATerm termFromPath(const Path & path); -/* Write an aterm to the Nix store directory, and return its hash. */ -FSId writeTerm(ATerm t, const string & suffix, FSId id = FSId()); +/* Write an aterm to the Nix store directory, and return its path. */ +Path writeTerm(ATerm t, const string & suffix); /* Parse a Nix expression. */ NixExpr parseNixExpr(ATerm t); diff --git a/src/fix.cc b/src/fix.cc index 66438464c..71fd06877 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -9,12 +9,12 @@ typedef ATerm Expr; typedef map NormalForms; -typedef map PkgPaths; -typedef map PkgHashes; +typedef map PkgPaths; +typedef map PkgHashes; struct EvalState { - Strings searchDirs; + Paths searchDirs; NormalForms normalForms; PkgPaths pkgPaths; PkgHashes pkgHashes; /* normalised package hashes */ @@ -28,18 +28,18 @@ struct EvalState }; -static Expr evalFile(EvalState & state, string fileName); +static Expr evalFile(EvalState & state, const Path & path); static Expr evalExpr(EvalState & state, Expr e); -static string searchPath(const Strings & searchDirs, string relPath) +static Path searchPath(const Paths & searchDirs, const Path & relPath) { if (string(relPath, 0, 1) == "/") return relPath; - for (Strings::const_iterator i = searchDirs.begin(); + for (Paths::const_iterator i = searchDirs.begin(); i != searchDirs.end(); i++) { - string path = *i + "/" + relPath; + Path path = *i + "/" + relPath; if (pathExists(path)) return path; } @@ -121,14 +121,14 @@ static Expr substExprMany(ATermList formals, ATermList args, Expr body) } -static Strings nixExprPathsCached(EvalState & state, const FSId & id) +static PathSet nixExprRootsCached(EvalState & state, const Path & nePath) { - PkgPaths::iterator i = state.pkgPaths.find(id); + PkgPaths::iterator i = state.pkgPaths.find(nePath); if (i != state.pkgPaths.end()) return i->second; else { - Strings paths = nixExprPaths(id); - state.pkgPaths[id] = paths; + PathSet paths = nixExprRoots(nePath); + state.pkgPaths[nePath] = paths; return paths; } } @@ -137,13 +137,13 @@ static Strings nixExprPathsCached(EvalState & state, const FSId & id) static Hash hashPackage(EvalState & state, NixExpr ne) { if (ne.type == NixExpr::neDerivation) { - FSIdSet inputs2; - for (FSIdSet::iterator i = ne.derivation.inputs.begin(); + PathSet inputs2; + for (PathSet::iterator i = ne.derivation.inputs.begin(); i != ne.derivation.inputs.end(); i++) { PkgHashes::iterator j = state.pkgHashes.find(*i); if (j == state.pkgHashes.end()) - throw Error(format("unknown package id %1%") % (string) *i); + throw Error(format("don't know expression `%1%'") % (string) *i); inputs2.insert(j->second); } ne.derivation.inputs = inputs2; @@ -156,12 +156,12 @@ static string processBinding(EvalState & state, Expr e, NixExpr & ne) { char * s1; - if (ATmatch(e, "FSId()", &s1)) { - FSId id = parseHash(s1); - Strings paths = nixExprPathsCached(state, id); + if (ATmatch(e, "NixExpr()", &s1)) { + Path nePath(s1); + PathSet paths = nixExprRootsCached(state, nePath); if (paths.size() != 1) abort(); - string path = *(paths.begin()); - ne.derivation.inputs.insert(id); + Path path = *(paths.begin()); + ne.derivation.inputs.insert(nePath); return path; } @@ -200,14 +200,14 @@ static Expr evalExpr2(EvalState & state, Expr e) ATmatch(e, "True") || ATmatch(e, "False") || ATmatch(e, "Function([], )", &e1, &e2) || - ATmatch(e, "FSId()", &s1)) + ATmatch(e, "NixExpr()", &s1)) return e; try { Hash pkgHash = hashPackage(state, parseNixExpr(e)); - FSId pkgId = writeTerm(e, ""); - state.pkgHashes[pkgId] = pkgHash; - return ATmake("FSId()", ((string) pkgId).c_str()); + Path pkgPath = writeTerm(e, ""); + state.pkgHashes[pkgPath] = pkgHash; + return ATmake("NixExpr()", pkgPath.c_str()); } catch (...) { /* !!! catch parse errors only */ } @@ -254,32 +254,29 @@ static Expr evalExpr2(EvalState & state, Expr e) /* Fix inclusion. */ if (ATmatch(e, "IncludeFix()", &s1)) { - string fileName(s1); + Path fileName(s1); return evalFile(state, s1); } /* Relative files. */ if (ATmatch(e, "Relative()", &s1)) { - string srcPath = searchPath(state.searchDirs, s1); - string dstPath; - FSId id; - addToStore(srcPath, dstPath, id, true); + Path srcPath = searchPath(state.searchDirs, s1); + Path dstPath = addToStore(srcPath); ClosureElem elem; - elem.id = id; NixExpr ne; ne.type = NixExpr::neClosure; ne.closure.roots.insert(dstPath); ne.closure.elems[dstPath] = elem; Hash pkgHash = hashPackage(state, ne); - FSId pkgId = writeTerm(unparseNixExpr(ne), ""); - state.pkgHashes[pkgId] = pkgHash; + Path pkgPath = writeTerm(unparseNixExpr(ne), ""); + state.pkgHashes[pkgPath] = pkgHash; - msg(lvlChatty, format("copied `%1%' -> %2%") - % srcPath % (string) pkgId); + msg(lvlChatty, format("copied `%1%' -> closure `%2%'") + % srcPath % pkgPath); - return ATmake("FSId()", ((string) pkgId).c_str()); + return ATmake("NixExpr()", pkgPath.c_str()); } /* Packages are transformed into Nix derivation expressions. */ @@ -302,8 +299,8 @@ static Expr evalExpr2(EvalState & state, Expr e) ne.type = NixExpr::neDerivation; ne.derivation.platform = SYSTEM; string name; - FSId outId; - bool outIdGiven = false; + Hash outHash; + bool outHashGiven = false; bnds = ATempty; for (map::iterator it = bndMap.begin(); @@ -331,8 +328,8 @@ static Expr evalExpr2(EvalState & state, Expr e) if (key == "build") ne.derivation.builder = s; if (key == "name") name = s; if (key == "id") { - outId = parseHash(s); - outIdGiven = true; + outHash = parseHash(s); + outHashGiven = true; } } @@ -348,23 +345,23 @@ static Expr evalExpr2(EvalState & state, Expr e) /* Hash the Nix expression with no outputs to produce a unique but deterministic path name for this package. */ - if (!outIdGiven) outId = hashPackage(state, ne); - string outPath = - canonPath(nixStore + "/" + ((string) outId).c_str() + "-" + name); + if (!outHashGiven) outHash = hashPackage(state, ne); + Path outPath = + canonPath(nixStore + "/" + ((string) outHash).c_str() + "-" + name); ne.derivation.env["out"] = outPath; - ne.derivation.outputs[outPath] = outId; + ne.derivation.outputs.insert(outPath); /* Write the resulting term into the Nix store directory. */ - Hash pkgHash = outIdGiven - ? hashString((string) outId + outPath) + Hash pkgHash = outHashGiven + ? hashString((string) outHash + outPath) : hashPackage(state, ne); - FSId pkgId = writeTerm(unparseNixExpr(ne), "-d-" + name); - state.pkgHashes[pkgId] = pkgHash; + Path pkgPath = writeTerm(unparseNixExpr(ne), "-d-" + name); + state.pkgHashes[pkgPath] = pkgHash; - msg(lvlChatty, format("instantiated `%1%' -> %2%") - % name % (string) pkgId); + msg(lvlChatty, format("instantiated `%1%' -> `%2%'") + % name % pkgPath); - return ATmake("FSId()", ((string) pkgId).c_str()); + return ATmake("NixExpr()", pkgPath.c_str()); } /* BaseName primitive function. */ @@ -401,9 +398,9 @@ static Expr evalExpr(EvalState & state, Expr e) } -static Expr evalFile(EvalState & state, string relPath) +static Expr evalFile(EvalState & state, const Path & relPath) { - string path = searchPath(state.searchDirs, relPath); + Path path = searchPath(state.searchDirs, relPath); Nest nest(lvlTalkative, format("evaluating file `%1%'") % path); Expr e = ATreadFromNamedFile(path.c_str()); if (!e) @@ -422,16 +419,16 @@ static Expr evalStdin(EvalState & state) } -static void printFSId(EvalState & state, Expr e) +static void printNixExpr(EvalState & state, Expr e) { ATermList es; char * s; - if (ATmatch(e, "FSId()", &s)) { + if (ATmatch(e, "NixExpr()", &s)) { cout << format("%1%\n") % s; } else if (ATmatch(e, "[]", &es)) { while (!ATisEmpty(es)) { - printFSId(state, evalExpr(state, ATgetFirst(es))); + printNixExpr(state, evalExpr(state, ATgetFirst(es))); es = ATgetNext(es); } } @@ -472,14 +469,14 @@ void run(Strings args) if (readStdin) { Expr e = evalStdin(state); - printFSId(state, e); + printNixExpr(state, e); } for (Strings::iterator it = files.begin(); it != files.end(); it++) { Expr e = evalFile(state, *it); - printFSId(state, e); + printNixExpr(state, e); } } diff --git a/src/globals.cc b/src/globals.cc index f21820f59..f34f8f102 100644 --- a/src/globals.cc +++ b/src/globals.cc @@ -5,8 +5,7 @@ Database nixDB; -TableId dbPath2Id; -TableId dbId2Paths; +TableId dbValidPaths; TableId dbSuccessors; TableId dbSubstitutes; @@ -23,8 +22,7 @@ bool keepFailed = false; void openDB() { nixDB.open(nixDBPath); - dbPath2Id = nixDB.openTable("path2id"); - dbId2Paths = nixDB.openTable("id2paths"); + dbValidPaths = nixDB.openTable("validpaths"); dbSuccessors = nixDB.openTable("successors"); dbSubstitutes = nixDB.openTable("substitutes"); } diff --git a/src/globals.hh b/src/globals.hh index b140f136c..0ea2fca09 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -13,42 +13,36 @@ extern Database nixDB; /* Database tables. */ -/* dbPath2Id :: Path -> FSId - Each pair (p, id) records that path $p$ contains an expansion of - $id$. */ -extern TableId dbPath2Id; +/* dbValidPaths :: Path -> () + + The existence of a key $p$ indicates that path $p$ is valid (that + is, produced by a succesful build). */ +extern TableId dbValidPaths; -/* dbId2Paths :: FSId -> [Path] +/* dbSuccessors :: Path -> Path - A mapping from ids to lists of paths. */ -extern TableId dbId2Paths; + Each pair $(p_1, p_2)$ in this mapping records the fact that the + Nix expression stored at path $p_1$ has a successor expression + stored at path $p_2$. - -/* dbSuccessors :: FSId -> FSId - - Each pair $(id_1, id_2)$ in this mapping records the fact that a - successor of a Nix expression stored in a file with identifier - $id_1$ is stored in a file with identifier $id_2$. - - Note that a term $y$ is successor of $x$ iff there exists a + Note that a term $y$ is a successor of $x$ iff there exists a sequence of rewrite steps that rewrites $x$ into $y$. */ extern TableId dbSuccessors; -/* dbSubstitutes :: FSId -> [FSId] +/* dbSubstitutes :: Path -> [Path] - Each pair $(id, [ids])$ tells Nix that it can realise any of the - Nix expressions referenced by the identifiers in $ids$ to - generate a path with identifier $id$. + Each pair $(p, [ps])$ tells Nix that it can realise any of the + Nix expressions stored at paths $ps$ to produce a path $p$. The main purpose of this is for distributed caching of derivates. - One system can compute a derivate with hash $h$ and put it on a - website (as a Nix archive), for instance, and then another system - can register a substitute for that derivate. The substitute in - this case might be a Nix expression that fetches the Nix archive. + One system can compute a derivate and put it on a website (as a Nix + archive), for instance, and then another system can register a + substitute for that derivate. The substitute in this case might be + a Nix expression that fetches the Nix archive. */ extern TableId dbSubstitutes; diff --git a/src/hash.cc b/src/hash.cc index b59c4f214..752b26912 100644 --- a/src/hash.cc +++ b/src/hash.cc @@ -54,11 +54,11 @@ Hash parseHash(const string & s) { Hash hash; if (s.length() != Hash::hashSize * 2) - throw BadRefError("invalid hash: " + s); + throw Error(format("invalid hash `%1%'") % s); for (unsigned int i = 0; i < Hash::hashSize; i++) { string s2(s, i * 2, 2); if (!isxdigit(s2[0]) || !isxdigit(s2[1])) - throw BadRefError("invalid hash: " + s); + throw Error(format("invalid hash `%1%'") % s); istringstream str(s2); int n; str >> hex >> n; @@ -89,15 +89,15 @@ Hash hashString(const string & s) } -Hash hashFile(const string & fileName) +Hash hashFile(const Path & path) { Hash hash; - FILE * file = fopen(fileName.c_str(), "rb"); + FILE * file = fopen(path.c_str(), "rb"); if (!file) - throw SysError("file `" + fileName + "' does not exist"); + throw SysError(format("file `%1%' does not exist") % path); int err = md5_stream(file, hash.hash); fclose(file); - if (err) throw SysError("cannot hash file " + fileName); + if (err) throw SysError(format("cannot hash file `%1%'") % path); return hash; } @@ -113,7 +113,7 @@ struct HashSink : DumpSink }; -Hash hashPath(const string & path) +Hash hashPath(const Path & path) { Hash hash; HashSink sink; diff --git a/src/hash.hh b/src/hash.hh index 387939e93..0062f987c 100644 --- a/src/hash.hh +++ b/src/hash.hh @@ -30,13 +30,6 @@ struct Hash }; -class BadRefError : public Error -{ -public: - BadRefError(string _err) : Error(_err) { }; -}; - - /* Parse a hexadecimal representation of a hash code. */ Hash parseHash(const string & s); @@ -47,12 +40,12 @@ bool isHash(const string & s); Hash hashString(const string & s); /* Compute the hash of the given file. */ -Hash hashFile(const string & fileName); +Hash hashFile(const Path & path); /* Compute the hash of the given path. The hash is defined as md5(dump(path)). */ -Hash hashPath(const string & path); +Hash hashPath(const Path & path); #endif /* !__HASH_H */ diff --git a/src/nix-help.txt b/src/nix-help.txt index be8ecf0a1..a51018ea1 100644 --- a/src/nix-help.txt +++ b/src/nix-help.txt @@ -19,16 +19,11 @@ Operations: --version: output version information --help: display help -Source selection for --install, --dump: - - --path / -p: by file name !!! -> path - Query flags: --list / -l: query the output paths (roots) of a Nix expression (default) --requisites / -r: print all paths necessary to realise expression --generators / -g: find expressions producing a subset of given ids - --expansion / -e: print a path containing id --graph: print a dot graph rooted at given ids Options: diff --git a/src/nix.cc b/src/nix.cc index 87553de2d..9907f5c74 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -11,9 +11,6 @@ typedef void (* Operation) (Strings opFlags, Strings opArgs); -static bool pathArgs = false; - - static void printHelp() { cout << @@ -24,16 +21,9 @@ static void printHelp() -static FSId argToId(const string & arg) +static Path checkPath(const Path & arg) { - if (!pathArgs) - return parseHash(arg); - else { - FSId id; - if (!queryPathId(arg, id)) - throw Error(format("don't know id of `%1%'") % arg); - return id; - } + return arg; /* !!! check that arg is in the store */ } @@ -42,12 +32,12 @@ static void opInstall(Strings opFlags, Strings opArgs) { if (!opFlags.empty()) throw UsageError("unknown flag"); - for (Strings::iterator it = opArgs.begin(); - it != opArgs.end(); it++) + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); i++) { - FSId id = normaliseNixExpr(argToId(*it)); - realiseClosure(id); - cout << format("%1%\n") % (string) id; + Path nfPath = normaliseNixExpr(checkPath(*i)); + realiseClosure(nfPath); + cout << format("%1%\n") % (string) nfPath; } } @@ -59,7 +49,7 @@ static void opDelete(Strings opFlags, Strings opArgs) for (Strings::iterator it = opArgs.begin(); it != opArgs.end(); it++) - deleteFromStore(absPath(*it)); + deleteFromStore(checkPath(*it)); } @@ -69,27 +59,21 @@ static void opAdd(Strings opFlags, Strings opArgs) { if (!opFlags.empty()) throw UsageError("unknown flag"); - for (Strings::iterator it = opArgs.begin(); - it != opArgs.end(); it++) - { - string path; - FSId id; - addToStore(*it, path, id); - cout << format("%1% %2%\n") % (string) id % path; - } + for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) + cout << format("%1%\n") % addToStore(*i); } -FSId maybeNormalise(const FSId & id, bool normalise) +Path maybeNormalise(const Path & ne, bool normalise) { - return normalise ? normaliseNixExpr(id) : id; + return normalise ? normaliseNixExpr(ne) : ne; } /* Perform various sorts of queries. */ static void opQuery(Strings opFlags, Strings opArgs) { - enum { qList, qRequisites, qGenerators, qExpansion, qGraph + enum { qList, qRequisites, qGenerators, qGraph } query = qList; bool normalise = false; bool includeExprs = true; @@ -100,7 +84,6 @@ static void opQuery(Strings opFlags, Strings opArgs) if (*i == "--list" || *i == "-l") query = qList; else if (*i == "--requisites" || *i == "-r") query = qRequisites; else if (*i == "--generators" || *i == "-g") query = qGenerators; - else if (*i == "--expansion" || *i == "-e") query = qExpansion; else if (*i == "--graph") query = qGraph; else if (*i == "--normalise" || *i == "-n") normalise = true; else if (*i == "--exclude-exprs") includeExprs = false; @@ -110,12 +93,12 @@ static void opQuery(Strings opFlags, Strings opArgs) switch (query) { case qList: { - StringSet paths; + PathSet paths; for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) { - Strings paths2 = nixExprPaths( - maybeNormalise(argToId(*i), normalise)); + StringSet paths2 = nixExprRoots( + maybeNormalise(checkPath(*i), normalise)); paths.insert(paths2.begin(), paths2.end()); } for (StringSet::iterator i = paths.begin(); @@ -129,8 +112,8 @@ static void opQuery(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) { - Strings paths2 = nixExprRequisites( - maybeNormalise(argToId(*i), normalise), + StringSet paths2 = nixExprRequisites( + maybeNormalise(checkPath(*i), normalise), includeExprs, includeSuccessors); paths.insert(paths2.begin(), paths2.end()); } @@ -140,11 +123,12 @@ static void opQuery(Strings opFlags, Strings opArgs) break; } +#if 0 case qGenerators: { FSIds outIds; for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) - outIds.push_back(argToId(*i)); + outIds.push_back(checkPath(*i)); FSIds genIds = findGenerators(outIds); @@ -153,21 +137,13 @@ static void opQuery(Strings opFlags, Strings opArgs) cout << format("%s\n") % expandId(*i); break; } - - case qExpansion: { - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); i++) - /* !!! should not use substitutes; this is a query, - it should not have side-effects */ - cout << format("%s\n") % expandId(parseHash(*i)); - break; - } +#endif case qGraph: { - FSIds roots; + PathSet roots; for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) - roots.push_back(maybeNormalise(argToId(*i), normalise)); + roots.insert(maybeNormalise(checkPath(*i), normalise)); printDotGraph(roots); break; } @@ -187,9 +163,9 @@ static void opSuccessor(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ) { - FSId id1 = parseHash(*i++); - FSId id2 = parseHash(*i++); - registerSuccessor(txn, id1, id2); + Path path1 = checkPath(*i++); + Path path2 = checkPath(*i++); + registerSuccessor(txn, path1, path2); } txn.commit(); } @@ -203,8 +179,8 @@ static void opSubstitute(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ) { - FSId src = parseHash(*i++); - FSId sub = parseHash(*i++); + Path src = checkPath(*i++); + Path sub = checkPath(*i++); registerSubstitute(src, sub); } } @@ -229,9 +205,7 @@ static void opDump(Strings opFlags, Strings opArgs) if (opArgs.size() != 1) throw UsageError("only one argument allowed"); StdoutSink sink; - string arg = *opArgs.begin(); - string path = pathArgs ? arg : expandId(parseHash(arg)); - + string path = *opArgs.begin(); dumpPath(path, sink); } @@ -311,8 +285,6 @@ void run(Strings args) op = opInit; else if (arg == "--verify") op = opVerify; - else if (arg == "--path" || arg == "-p") - pathArgs = true; else if (arg == "--verbose" || arg == "-v") verbosity = (Verbosity) ((int) verbosity + 1); else if (arg == "--keep-failed" || arg == "-K") diff --git a/src/normalise.cc b/src/normalise.cc index 994a07df1..834276f1b 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -9,47 +9,131 @@ void registerSuccessor(const Transaction & txn, - const FSId & id1, const FSId & id2) + const Path & path1, const Path & path2) { - nixDB.setString(txn, dbSuccessors, id1, id2); + nixDB.setString(txn, dbSuccessors, path1, path2); } -static FSId useSuccessor(const FSId & id) +static Path useSuccessor(const Path & path) { - string idSucc; - if (nixDB.queryString(noTxn, dbSuccessors, id, idSucc)) { - debug(format("successor %1% -> %2%") % (string) id % idSucc); - return parseHash(idSucc); + string pathSucc; + if (nixDB.queryString(noTxn, dbSuccessors, path, pathSucc)) { + debug(format("successor %1% -> %2%") % (string) path % pathSucc); + return pathSucc; } else - return id; + return path; } -Strings pathsFromOutputs(const DerivationOutputs & ps) +#if 0 +/* Return a path whose contents have the given hash. If target is + not empty, ensure that such a path is realised in target (if + necessary by copying from another location). If prefix is not + empty, only return a path that is an descendent of prefix. */ + +string expandId(const FSId & id, const string & target = "", + const string & prefix = "/", FSIdSet pending = FSIdSet(), + bool ignoreSubstitutes = false) { - Strings ss; - for (DerivationOutputs::const_iterator i = ps.begin(); - i != ps.end(); i++) - ss.push_back(i->first); - return ss; + xxx } -FSId normaliseNixExpr(FSId id, FSIdSet pending) +string expandId(const FSId & id, const string & target, + const string & prefix, FSIdSet pending, bool ignoreSubstitutes) +{ + Nest nest(lvlDebug, format("expanding %1%") % (string) id); + + Strings paths; + + if (!target.empty() && !isInPrefix(target, prefix)) + abort(); + + nixDB.queryStrings(noTxn, dbId2Paths, id, paths); + + /* Pick one equal to `target'. */ + if (!target.empty()) { + + for (Strings::iterator i = paths.begin(); + i != paths.end(); i++) + { + string path = *i; + if (path == target && pathExists(path)) + return path; + } + + } + + /* Arbitrarily pick the first one that exists and isn't stale. */ + for (Strings::iterator it = paths.begin(); + it != paths.end(); it++) + { + string path = *it; + if (isInPrefix(path, prefix) && pathExists(path)) { + if (target.empty()) + return path; + else { + /* Acquire a lock on the target path. */ + Strings lockPaths; + lockPaths.push_back(target); + PathLocks outputLock(lockPaths); + + /* Copy. */ + copyPath(path, target); + + /* Register the target path. */ + Transaction txn(nixDB); + registerPath(txn, target, id); + txn.commit(); + + return target; + } + } + } + + if (!ignoreSubstitutes) { + + if (pending.find(id) != pending.end()) + throw Error(format("id %1% already being expanded") % (string) id); + pending.insert(id); + + /* Try to realise the substitutes, but only if this id is not + already being realised by a substitute. */ + Strings subs; + nixDB.queryStrings(noTxn, dbSubstitutes, id, subs); /* non-existence = ok */ + + for (Strings::iterator it = subs.begin(); it != subs.end(); it++) { + FSId subId = parseHash(*it); + + debug(format("trying substitute %1%") % (string) subId); + + realiseClosure(normaliseNixExpr(subId, pending), pending); + + return expandId(id, target, prefix, pending); + } + + } + + throw Error(format("cannot expand id `%1%'") % (string) id); +} +#endif + + +Path normaliseNixExpr(const Path & _nePath, PathSet pending) { Nest nest(lvlTalkative, - format("normalising nix expression %1%") % (string) id); + format("normalising expression in `%1%'") % (string) _nePath); - /* Try to substitute $id$ by any known successors in order to - speed up the rewrite process. */ - id = useSuccessor(id); + /* Try to substitute the expression by any known successors in + order to speed up the rewrite process. */ + Path nePath = useSuccessor(_nePath); /* Get the Nix expression. */ - NixExpr ne = parseNixExpr(termFromId(id)); + NixExpr ne = parseNixExpr(termFromPath(nePath)); /* If this is a normal form (i.e., a closure) we are done. */ - if (ne.type == NixExpr::neClosure) return id; + if (ne.type == NixExpr::neClosure) return nePath; if (ne.type != NixExpr::neDerivation) abort(); @@ -62,8 +146,8 @@ FSId normaliseNixExpr(FSId id, FSIdSet pending) /* Input paths, with their closure elements. */ ClosureElems inClosures; - /* Referencable paths (i.e., input and output paths). */ - StringSet allPaths; + /* Referenceable paths (i.e., input and output paths). */ + PathSet allPaths; /* The environment to be passed to the builder. */ Environment env; @@ -73,17 +157,17 @@ FSId normaliseNixExpr(FSId id, FSIdSet pending) nf.type = NixExpr::neClosure; - /* Parse the outputs. */ - for (DerivationOutputs::iterator i = ne.derivation.outputs.begin(); + /* The outputs are referenceable paths. */ + for (PathSet::iterator i = ne.derivation.outputs.begin(); i != ne.derivation.outputs.end(); i++) { - debug(format("building %1% in `%2%'") % (string) i->second % i->first); - allPaths.insert(i->first); + debug(format("building path `%1%'") % *i); + allPaths.insert(*i); } /* Obtain locks on all output paths. The locks are automatically released when we exit this function or Nix crashes. */ - PathLocks outputLocks(pathsFromOutputs(ne.derivation.outputs)); + PathLocks outputLocks(ne.derivation.outputs); /* Now check again whether there is a successor. This is because another process may have started building in parallel. After @@ -94,13 +178,13 @@ FSId normaliseNixExpr(FSId id, FSIdSet pending) other process can build this expression, so no further checks are necessary. */ { - FSId id2 = useSuccessor(id); - if (id2 != id) { - NixExpr ne = parseNixExpr(termFromId(id2)); - debug(format("skipping build of %1%, someone beat us to it") - % (string) id); + Path nePath2 = useSuccessor(nePath); + if (nePath != nePath2) { + NixExpr ne = parseNixExpr(termFromPath(nePath2)); + debug(format("skipping build of expression `%1%', someone beat us to it") + % (string) nePath); if (ne.type != NixExpr::neClosure) abort(); - return id2; + return nePath2; } } @@ -110,14 +194,14 @@ FSId normaliseNixExpr(FSId id, FSIdSet pending) % ne.derivation.platform % thisSystem); /* Realise inputs (and remember all input paths). */ - for (FSIdSet::iterator i = ne.derivation.inputs.begin(); + for (PathSet::iterator i = ne.derivation.inputs.begin(); i != ne.derivation.inputs.end(); i++) { - FSId nf = normaliseNixExpr(*i, pending); - realiseClosure(nf, pending); - /* !!! nf should be a root of the garbage collector while we - are building */ - NixExpr ne = parseNixExpr(termFromId(nf)); + Path nfPath = normaliseNixExpr(*i, pending); + realiseClosure(nfPath, pending); + /* !!! nfPath should be a root of the garbage collector while + we are building */ + NixExpr ne = parseNixExpr(termFromPath(nfPath)); if (ne.type != NixExpr::neClosure) abort(); for (ClosureElems::iterator j = ne.closure.elems.begin(); j != ne.closure.elems.end(); j++) @@ -147,8 +231,10 @@ FSId normaliseNixExpr(FSId id, FSIdSet pending) /* We can skip running the builder if we can expand all output paths from their ids. */ + bool fastBuild = false; +#if 0 bool fastBuild = true; - for (DerivationOutputs::iterator i = ne.derivation.outputs.begin(); + for (PathSet::iterator i = ne.derivation.outputs.begin(); i != ne.derivation.outputs.end(); i++) { try { @@ -160,17 +246,17 @@ FSId normaliseNixExpr(FSId id, FSIdSet pending) break; } } +#endif if (!fastBuild) { /* If any of the outputs already exist but are not registered, delete them. */ - for (DerivationOutputs::iterator i = ne.derivation.outputs.begin(); + for (PathSet::iterator i = ne.derivation.outputs.begin(); i != ne.derivation.outputs.end(); i++) { - string path = i->first; - FSId id; - if (queryPathId(path, id)) + Path path = *i; + if (isValidPath(path)) throw Error(format("obstructed build: path `%1%' exists") % path); if (pathExists(path)) { debug(format("removing unregistered path `%1%'") % path); @@ -189,11 +275,11 @@ FSId normaliseNixExpr(FSId id, FSIdSet pending) /* Check whether the output paths were created, and grep each output path to determine what other paths it references. Also make all output paths read-only. */ - StringSet usedPaths; - for (DerivationOutputs::iterator i = ne.derivation.outputs.begin(); + PathSet usedPaths; + for (PathSet::iterator i = ne.derivation.outputs.begin(); i != ne.derivation.outputs.end(); i++) { - string path = i->first; + Path path = *i; if (!pathExists(path)) throw Error(format("path `%1%' does not exist") % path); nf.closure.roots.insert(path); @@ -207,15 +293,14 @@ FSId normaliseNixExpr(FSId id, FSIdSet pending) /* Construct a closure element for this output path. */ ClosureElem elem; - elem.id = i->second; /* For each path referenced by this output path, add its id to the closure element and add the id to the `usedPaths' set (so that the elements referenced by *its* closure are added below). */ - for (Strings::iterator j = refPaths.begin(); + for (Paths::iterator j = refPaths.begin(); j != refPaths.end(); j++) { - string path = *j; + Path path = *j; elem.refs.insert(path); if (inClosures.find(path) != inClosures.end()) usedPaths.insert(path); @@ -228,11 +313,11 @@ FSId normaliseNixExpr(FSId id, FSIdSet pending) /* Close the closure. That is, for any referenced path, add the paths referenced by it. */ - StringSet donePaths; + PathSet donePaths; while (!usedPaths.empty()) { - StringSet::iterator i = usedPaths.begin(); - string path = *i; + PathSet::iterator i = usedPaths.begin(); + Path path = *i; usedPaths.erase(i); if (donePaths.find(path) != donePaths.end()) continue; @@ -243,7 +328,7 @@ FSId normaliseNixExpr(FSId id, FSIdSet pending) nf.closure.elems[path] = j->second; - for (StringSet::iterator k = j->second.refs.begin(); + for (PathSet::iterator k = j->second.refs.begin(); k != j->second.refs.end(); k++) usedPaths.insert(*k); } @@ -252,7 +337,7 @@ FSId normaliseNixExpr(FSId id, FSIdSet pending) for (ClosureElems::iterator i = inClosures.begin(); i != inClosures.end(); i++) { - StringSet::iterator j = donePaths.find(i->first); + PathSet::iterator j = donePaths.find(i->first); if (j == donePaths.end()) debug(format("NOT referenced: `%1%'") % i->first); else @@ -263,7 +348,7 @@ FSId normaliseNixExpr(FSId id, FSIdSet pending) transaction below because writing terms is idem-potent. */ ATerm nfTerm = unparseNixExpr(nf); msg(lvlVomit, format("normal form: %1%") % printTerm(nfTerm)); - FSId idNF = writeTerm(nfTerm, "-s-" + (string) id); + Path nfPath = writeTerm(nfTerm, "-s"); /* Register each outpat path, and register the normal form. This is wrapped in one database transaction to ensure that if we @@ -272,63 +357,58 @@ FSId normaliseNixExpr(FSId id, FSIdSet pending) deleted arbitrarily, while registered paths can only be deleted by running the garbage collector. */ Transaction txn(nixDB); - for (DerivationOutputs::iterator i = ne.derivation.outputs.begin(); + for (PathSet::iterator i = ne.derivation.outputs.begin(); i != ne.derivation.outputs.end(); i++) - registerPath(txn, i->first, i->second); - registerSuccessor(txn, id, idNF); + registerValidPath(txn, *i); + registerSuccessor(txn, nePath, nfPath); txn.commit(); - return idNF; + return nfPath; } -void realiseClosure(const FSId & id, FSIdSet pending) +void realiseClosure(const Path & nePath, PathSet pending) { - Nest nest(lvlDebug, - format("realising closure %1%") % (string) id); + Nest nest(lvlDebug, format("realising closure `%1%'") % nePath); - NixExpr ne = parseNixExpr(termFromId(id)); + NixExpr ne = parseNixExpr(termFromPath(nePath)); if (ne.type != NixExpr::neClosure) - throw Error(format("expected closure in %1%") % (string) id); + throw Error(format("expected closure in `%1%'") % nePath); for (ClosureElems::const_iterator i = ne.closure.elems.begin(); i != ne.closure.elems.end(); i++) + assert(isValidPath(i->first)); +#if 0 expandId(i->second.id, i->first, "/", pending); +#endif } -Strings nixExprPaths(const FSId & id) +PathSet nixExprRoots(const Path & nePath) { - Strings paths; + PathSet paths; - NixExpr ne = parseNixExpr(termFromId(id)); + NixExpr ne = parseNixExpr(termFromPath(nePath)); - if (ne.type == NixExpr::neClosure) { - for (StringSet::const_iterator i = ne.closure.roots.begin(); - i != ne.closure.roots.end(); i++) - paths.push_back(*i); - } - - else if (ne.type == NixExpr::neDerivation) { - for (DerivationOutputs::iterator i = ne.derivation.outputs.begin(); - i != ne.derivation.outputs.end(); i++) - paths.push_back(i->first); - } - + if (ne.type == NixExpr::neClosure) + paths.insert(ne.closure.roots.begin(), ne.closure.roots.end()); + else if (ne.type == NixExpr::neDerivation) + paths.insert(ne.derivation.outputs.begin(), + ne.derivation.outputs.end()); else abort(); return paths; } -static void nixExprRequisitesSet(const FSId & id, - bool includeExprs, bool includeSuccessors, StringSet & paths, - FSIdSet & doneSet) +static void requisitesWorker(const Path & nePath, + bool includeExprs, bool includeSuccessors, + PathSet & paths, PathSet & doneSet) { - if (doneSet.find(id) != doneSet.end()) return; - doneSet.insert(id); + if (doneSet.find(nePath) != doneSet.end()) return; + doneSet.insert(nePath); - NixExpr ne = parseNixExpr(termFromId(id)); + NixExpr ne = parseNixExpr(termFromPath(nePath)); if (ne.type == NixExpr::neClosure) for (ClosureElems::iterator i = ne.closure.elems.begin(); @@ -336,35 +416,35 @@ static void nixExprRequisitesSet(const FSId & id, paths.insert(i->first); else if (ne.type == NixExpr::neDerivation) - for (FSIdSet::iterator i = ne.derivation.inputs.begin(); + for (PathSet::iterator i = ne.derivation.inputs.begin(); i != ne.derivation.inputs.end(); i++) - nixExprRequisitesSet(*i, + requisitesWorker(*i, includeExprs, includeSuccessors, paths, doneSet); else abort(); - if (includeExprs) - paths.insert(expandId(id)); + if (includeExprs) paths.insert(nePath); - string idSucc; - if (includeSuccessors && - nixDB.queryString(noTxn, dbSuccessors, id, idSucc)) - nixExprRequisitesSet(parseHash(idSucc), - includeExprs, includeSuccessors, paths, doneSet); + string nfPath; + if (includeSuccessors && (nfPath = useSuccessor(nePath)) != nePath) + requisitesWorker(nfPath, includeExprs, includeSuccessors, + paths, doneSet); } -Strings nixExprRequisites(const FSId & id, +PathSet nixExprRequisites(const Path & nePath, bool includeExprs, bool includeSuccessors) { - StringSet paths; - FSIdSet doneSet; - nixExprRequisitesSet(id, includeExprs, includeSuccessors, paths, doneSet); - return Strings(paths.begin(), paths.end()); + PathSet paths; + PathSet doneSet; + requisitesWorker(nePath, includeExprs, includeSuccessors, + paths, doneSet); + return paths; } -FSIds findGenerators(const FSIds & _ids) +#if 0 +PathSet findGenerators(const PathSet & outputs) { FSIdSet ids(_ids.begin(), _ids.end()); FSIds generators; @@ -407,3 +487,4 @@ FSIds findGenerators(const FSIds & _ids) return generators; } +#endif diff --git a/src/normalise.hh b/src/normalise.hh index 9b8274681..4b4db4ee2 100644 --- a/src/normalise.hh +++ b/src/normalise.hh @@ -4,15 +4,22 @@ #include "expr.hh" -/* Normalise a Nix expression, that is, return an equivalent - closure. (For the meaning of `pending', see expandId()). */ -FSId normaliseNixExpr(FSId id, FSIdSet pending = FSIdSet()); +/* Normalise a Nix expression. That is, if the expression is a + derivation, a path containing an equivalent closure expression is + returned. This requires that the derivation is performed, unless a + successor is known. */ +Path normaliseNixExpr(const Path & nePath, PathSet pending = PathSet()); -/* Realise a Closure in the file system. */ -void realiseClosure(const FSId & id, FSIdSet pending = FSIdSet()); +/* Realise a closure expression in the file system. + + The pending paths are those that are already being realised. This + prevents infinite recursion for paths realised through a substitute + (since when we build the substitute, we would first try to realise + its output paths through substitutes... kaboom!). */ +void realiseClosure(const Path & nePath, PathSet pending = PathSet()); /* Get the list of root (output) paths of the given Nix expression. */ -Strings nixExprPaths(const FSId & id); +PathSet nixExprRoots(const Path & nePath); /* Get the list of paths that are required to realise the given expression. For a derive expression, this is the union of @@ -20,16 +27,16 @@ Strings nixExprPaths(const FSId & id); each element in the closure. If `includeExprs' is true, include the paths of the Nix expressions themselves. If `includeSuccessors' is true, include the requisites of successors. */ -Strings nixExprRequisites(const FSId & id, +PathSet nixExprRequisites(const Path & nePath, bool includeExprs, bool includeSuccessors); -/* Return the list of the ids of all known Nix expressions whose - output ids are completely contained in `ids'. */ -FSIds findGenerators(const FSIds & ids); +/* Return the list of the paths of all known Nix expressions whose + output paths are completely contained in the set `outputs'. */ +PathSet findGenerators(const PathSet & outputs); /* Register a successor. */ void registerSuccessor(const Transaction & txn, - const FSId & id1, const FSId & id2); + const Path & path1, const Path & path2); #endif /* !__NORMALISE_H */ diff --git a/src/pathlocks.cc b/src/pathlocks.cc index 93f456ace..ff0226c84 100644 --- a/src/pathlocks.cc +++ b/src/pathlocks.cc @@ -13,20 +13,20 @@ static StringSet lockedPaths; /* !!! not thread-safe */ -PathLocks::PathLocks(const Strings & _paths) +PathLocks::PathLocks(const PathSet & _paths) { /* Note that `fds' is built incrementally so that the destructor will only release those locks that we have already acquired. */ /* Sort the paths. This assures that locks are always acquired in the same order, thus preventing deadlocks. */ - Strings paths(_paths); + Paths paths(_paths.begin(), _paths.end()); paths.sort(); /* Acquire the lock for each path. */ - for (Strings::iterator i = paths.begin(); i != paths.end(); i++) { - string path = *i; - string lockPath = path + ".lock"; + for (Paths::iterator i = paths.begin(); i != paths.end(); i++) { + Path path = *i; + Path lockPath = path + ".lock"; debug(format("locking path `%1%'") % path); @@ -64,6 +64,6 @@ PathLocks::~PathLocks() for (list::iterator i = fds.begin(); i != fds.end(); i++) close(*i); - for (Strings::iterator i = paths.begin(); i != paths.end(); i++) + for (Paths::iterator i = paths.begin(); i != paths.end(); i++) lockedPaths.erase(*i); } diff --git a/src/pathlocks.hh b/src/pathlocks.hh index 03a62e65a..6c36b9b1d 100644 --- a/src/pathlocks.hh +++ b/src/pathlocks.hh @@ -8,10 +8,10 @@ class PathLocks { private: list fds; - Strings paths; + Paths paths; public: - PathLocks(const Strings & _paths); + PathLocks(const PathSet & _paths); ~PathLocks(); }; diff --git a/src/references.hh b/src/references.hh index b19fbf72c..d009453d6 100644 --- a/src/references.hh +++ b/src/references.hh @@ -4,7 +4,7 @@ #include "util.hh" -Strings filterReferences(const string & path, const Strings & refs); +Strings filterReferences(const Path & path, const Strings & refs); #endif /* !__VALUES_H */ diff --git a/src/shared.cc b/src/shared.cc index c0f07e955..32e891617 100644 --- a/src/shared.cc +++ b/src/shared.cc @@ -72,5 +72,3 @@ int main(int argc, char * * argv) return 0; } - - diff --git a/src/store.cc b/src/store.cc index f05cdf3ba..b2b479e0d 100644 --- a/src/store.cc +++ b/src/store.cc @@ -8,7 +8,6 @@ #include "db.hh" #include "archive.hh" #include "pathlocks.hh" -#include "normalise.hh" struct CopySink : DumpSink @@ -31,7 +30,7 @@ struct CopySource : RestoreSource }; -void copyPath(string src, string dst) +void copyPath(const Path & src, const Path & dst) { debug(format("copying `%1%' to `%2%'") % src % dst); @@ -82,7 +81,7 @@ void copyPath(string src, string dst) } -void registerSubstitute(const FSId & srcId, const FSId & subId) +void registerSubstitute(const Path & srcPath, const Path & subPath) { #if 0 Strings subs; @@ -98,202 +97,89 @@ void registerSubstitute(const FSId & srcId, const FSId & subId) /* For now, accept only one substitute per id. */ Strings subs; - subs.push_back(subId); + subs.push_back(subPath); Transaction txn(nixDB); - nixDB.setStrings(txn, dbSubstitutes, srcId, subs); + nixDB.setStrings(txn, dbSubstitutes, srcPath, subs); txn.commit(); } -void registerPath(const Transaction & txn, - const string & _path, const FSId & id) +void registerValidPath(const Transaction & txn, const Path & _path) { - string path(canonPath(_path)); - - debug(format("registering path `%1%' with id %2%") - % path % (string) id); - - string oldId; - if (nixDB.queryString(txn, dbPath2Id, path, oldId)) { - if (id != parseHash(oldId)) - throw Error(format("path `%1%' already contains id %2%") - % path % oldId); - return; - } - - nixDB.setString(txn, dbPath2Id, path, id); - - Strings paths; - nixDB.queryStrings(txn, dbId2Paths, id, paths); /* non-existence = ok */ - - paths.push_back(path); - - nixDB.setStrings(txn, dbId2Paths, id, paths); + Path path(canonPath(_path)); + debug(format("registering path `%1%'") % path); + nixDB.setString(txn, dbValidPaths, path, ""); } -void unregisterPath(const string & _path) +bool isValidPath(const Path & path) { - string path(canonPath(_path)); + string s; + return nixDB.queryString(noTxn, dbValidPaths, path, s); +} + + +void unregisterValidPath(const Path & _path) +{ + Path path(canonPath(_path)); Transaction txn(nixDB); debug(format("unregistering path `%1%'") % path); - string _id; - if (!nixDB.queryString(txn, dbPath2Id, path, _id)) { - txn.abort(); - return; - } - FSId id(parseHash(_id)); - - nixDB.delPair(txn, dbPath2Id, path); - - Strings paths, paths2; - nixDB.queryStrings(txn, dbId2Paths, id, paths); /* non-existence = ok */ - - for (Strings::iterator it = paths.begin(); - it != paths.end(); it++) - if (*it != path) paths2.push_back(*it); - - nixDB.setStrings(txn, dbId2Paths, id, paths2); + nixDB.delPair(txn, dbValidPaths, path); txn.commit(); } -bool queryPathId(const string & path, FSId & id) -{ - string s; - if (!nixDB.queryString(noTxn, dbPath2Id, absPath(path), s)) return false; - id = parseHash(s); - return true; -} - - -bool isInPrefix(const string & path, const string & _prefix) +static bool isInPrefix(const string & path, const string & _prefix) { string prefix = canonPath(_prefix + "/"); return string(path, 0, prefix.size()) == prefix; } -string expandId(const FSId & id, const string & target, - const string & prefix, FSIdSet pending, bool ignoreSubstitutes) -{ - Nest nest(lvlDebug, format("expanding %1%") % (string) id); - - Strings paths; - - if (!target.empty() && !isInPrefix(target, prefix)) - abort(); - - nixDB.queryStrings(noTxn, dbId2Paths, id, paths); - - /* Pick one equal to `target'. */ - if (!target.empty()) { - - for (Strings::iterator i = paths.begin(); - i != paths.end(); i++) - { - string path = *i; - if (path == target && pathExists(path)) - return path; - } - - } - - /* Arbitrarily pick the first one that exists and isn't stale. */ - for (Strings::iterator it = paths.begin(); - it != paths.end(); it++) - { - string path = *it; - if (isInPrefix(path, prefix) && pathExists(path)) { - if (target.empty()) - return path; - else { - /* Acquire a lock on the target path. */ - Strings lockPaths; - lockPaths.push_back(target); - PathLocks outputLock(lockPaths); - - /* Copy. */ - copyPath(path, target); - - /* Register the target path. */ - Transaction txn(nixDB); - registerPath(txn, target, id); - txn.commit(); - - return target; - } - } - } - - if (!ignoreSubstitutes) { - - if (pending.find(id) != pending.end()) - throw Error(format("id %1% already being expanded") % (string) id); - pending.insert(id); - - /* Try to realise the substitutes, but only if this id is not - already being realised by a substitute. */ - Strings subs; - nixDB.queryStrings(noTxn, dbSubstitutes, id, subs); /* non-existence = ok */ - - for (Strings::iterator it = subs.begin(); it != subs.end(); it++) { - FSId subId = parseHash(*it); - - debug(format("trying substitute %1%") % (string) subId); - - realiseClosure(normaliseNixExpr(subId, pending), pending); - - return expandId(id, target, prefix, pending); - } - - } - - throw Error(format("cannot expand id `%1%'") % (string) id); -} - - -void addToStore(string srcPath, string & dstPath, FSId & id, - bool deterministicName) +Path addToStore(const Path & _srcPath) { + Path srcPath(absPath(_srcPath)); debug(format("adding `%1%' to the store") % srcPath); - srcPath = absPath(srcPath); - id = hashPath(srcPath); + Hash h = hashPath(srcPath); string baseName = baseNameOf(srcPath); - dstPath = canonPath(nixStore + "/" + (string) id + "-" + baseName); + Path dstPath = canonPath(nixStore + "/" + (string) h + "-" + baseName); - try { - dstPath = expandId(id, deterministicName ? dstPath : "", - nixStore, FSIdSet(), true); - return; - } catch (...) { + if (!isValidPath(dstPath)) { + + /* The first check above is an optimisation to prevent + unnecessary lock acquisition. */ + + PathSet lockPaths; + lockPaths.insert(dstPath); + PathLocks outputLock(lockPaths); + + if (!isValidPath(dstPath)) { + copyPath(srcPath, dstPath); + + Transaction txn(nixDB); + registerValidPath(txn, dstPath); + txn.commit(); + } } - - Strings lockPaths; - lockPaths.push_back(dstPath); - PathLocks outputLock(lockPaths); - copyPath(srcPath, dstPath); - - Transaction txn(nixDB); - registerPath(txn, dstPath, id); - txn.commit(); + return dstPath; } -void deleteFromStore(const string & path) +void deleteFromStore(const Path & _path) { - string prefix = + "/"; - if (!isInPrefix(path, nixStore)) - throw Error(format("path %1% is not in the store") % path); + Path path(canonPath(_path)); - unregisterPath(path); + if (!isInPrefix(path, nixStore)) + throw Error(format("path `%1%' is not in the store") % path); + + unregisterValidPath(path); deletePath(path); } @@ -305,6 +191,7 @@ void verifyStore() /* !!! verify that the result is consistent */ +#if 0 Strings paths; nixDB.enumTable(txn, dbPath2Id, paths); @@ -421,6 +308,7 @@ void verifyStore() } } } +#endif txn.commit(); } diff --git a/src/store.hh b/src/store.hh index 7f6b24569..69de82478 100644 --- a/src/store.hh +++ b/src/store.hh @@ -9,44 +9,27 @@ using namespace std; -typedef Hash FSId; - -typedef set FSIdSet; - - /* Copy a path recursively. */ -void copyPath(string src, string dst); +void copyPath(const Path & src, const Path & dst); /* Register a substitute. */ -void registerSubstitute(const FSId & srcId, const FSId & subId); +void registerSubstitute(const Path & srcPath, const Path & subPath); -/* Register a path keyed on its id. */ -void registerPath(const Transaction & txn, - const string & path, const FSId & id); +/* Register the validity of a path. */ +void registerValidPath(const Transaction & txn, const Path & path); -/* Query the id of a path. */ -bool queryPathId(const string & path, FSId & id); +/* Unregister the validity of a path. */ +void unregisterValidPath(const Path & path); -/* Return a path whose contents have the given hash. If target is - not empty, ensure that such a path is realised in target (if - necessary by copying from another location). If prefix is not - empty, only return a path that is an descendent of prefix. +/* Checks whether a path is valid. */ +bool isValidPath(const Path & path); - The list of pending ids are those that already being expanded. - This prevents infinite recursion for ids realised through a - substitute (since when we build the substitute, we would first try - to expand the id... kaboom!). */ -string expandId(const FSId & id, const string & target = "", - const string & prefix = "/", FSIdSet pending = FSIdSet(), - bool ignoreSubstitutes = false); - -/* Copy a file to the nixStore directory and register it in dbRefs. - Return the hash code of the value. */ -void addToStore(string srcPath, string & dstPath, FSId & id, - bool deterministicName = false); +/* Copy the contents of a path to the store and register the validity + the resulting path. The resulting path is returned. */ +Path addToStore(const Path & srcPath); /* Delete a value from the nixStore directory. */ -void deleteFromStore(const string & path); +void deleteFromStore(const Path & path); void verifyStore(); diff --git a/src/test.cc b/src/test.cc index fb1e62eb3..457fecf24 100644 --- a/src/test.cc +++ b/src/test.cc @@ -10,10 +10,10 @@ #include "globals.hh" -void realise(FSId id) +void realise(Path nePath) { - Nest nest(lvlDebug, format("TEST: realising %1%") % (string) id); - realiseClosure(normaliseNixExpr(id)); + Nest nest(lvlDebug, format("TEST: realising `%1%'") % nePath); + realiseClosure(normaliseNixExpr(nePath)); } @@ -48,12 +48,12 @@ void runTests() try { h = parseHash("blah blah"); abort(); - } catch (BadRefError err) { }; + } catch (Error err) { }; try { h = parseHash("0b0ffd0538622bfe20b92c4aa57254d99"); abort(); - } catch (BadRefError err) { }; + } catch (Error err) { }; /* Path canonicalisation. */ cout << canonPath("/./../././//") << endl; @@ -97,76 +97,59 @@ void runTests() /* Expression evaluation. */ - FSId builder1id; - string builder1fn; - addToStore("./test-builder-1.sh", builder1fn, builder1id); + Path builder1fn; + builder1fn = addToStore("./test-builder-1.sh"); ATerm fs1 = ATmake( - "Closure([], [(, , [])])", + "Closure([], [(, [])])", builder1fn.c_str(), - builder1fn.c_str(), - ((string) builder1id).c_str()); - FSId fs1id = writeTerm(fs1, ""); + builder1fn.c_str()); + Path fs1ne = writeTerm(fs1, "-c"); - realise(fs1id); - realise(fs1id); + realise(fs1ne); + realise(fs1ne); - ATerm fs2 = ATmake( - "Closure([], [(, , [])])", - (builder1fn + "_bla").c_str(), - (builder1fn + "_bla").c_str(), - ((string) builder1id).c_str()); - FSId fs2id = writeTerm(fs2, ""); - - realise(fs2id); - realise(fs2id); - - string out1id = hashString("foo"); /* !!! bad */ - string out1fn = nixStore + "/" + (string) out1id + "-hello.txt"; + string out1h = hashString("foo"); /* !!! bad */ + Path out1fn = nixStore + "/" + (string) out1h + "-hello.txt"; ATerm fs3 = ATmake( - "Derive([(, )], [], , , [], [(\"out\", )])", + "Derive([], [], , , [], [(\"out\", )])", out1fn.c_str(), - ((string) out1id).c_str(), - ((string) fs1id).c_str(), + fs1ne.c_str(), thisSystem.c_str(), - ((string) builder1fn).c_str(), + builder1fn.c_str(), out1fn.c_str()); debug(printTerm(fs3)); - FSId fs3id = writeTerm(fs3, ""); + Path fs3ne = writeTerm(fs3, "-d"); - realise(fs3id); - realise(fs3id); + realise(fs3ne); + realise(fs3ne); - FSId builder4id; - string builder4fn; - addToStore("./test-builder-2.sh", builder4fn, builder4id); + Path builder4fn = addToStore("./test-builder-2.sh"); ATerm fs4 = ATmake( - "Closure([], [(, , [])])", + "Closure([], [(, [])])", builder4fn.c_str(), - builder4fn.c_str(), - ((string) builder4id).c_str()); - FSId fs4id = writeTerm(fs4, ""); + builder4fn.c_str()); + Path fs4ne = writeTerm(fs4, "-c"); - realise(fs4id); + realise(fs4ne); - string out5id = hashString("bar"); /* !!! bad */ - string out5fn = nixStore + "/" + (string) out5id + "-hello2"; + string out5h = hashString("bar"); /* !!! bad */ + Path out5fn = nixStore + "/" + (string) out5h + "-hello2"; ATerm fs5 = ATmake( - "Derive([(, )], [], , , [], [(\"out\", ), (\"builder\", )])", + "Derive([], [], , , [], [(\"out\", ), (\"builder\", )])", out5fn.c_str(), - ((string) out5id).c_str(), - ((string) fs4id).c_str(), + fs4ne.c_str(), thisSystem.c_str(), - ((string) builder4fn).c_str(), + builder4fn.c_str(), out5fn.c_str(), - ((string) builder4fn).c_str()); + builder4fn.c_str()); debug(printTerm(fs5)); - FSId fs5id = writeTerm(fs5, ""); + Path fs5ne = writeTerm(fs5, "-d"); - realise(fs5id); - realise(fs5id); + realise(fs5ne); + realise(fs5ne); } From 6409c215e56cbcd10177edf358f7d0702d687099 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 9 Oct 2003 15:37:20 +0000 Subject: [PATCH 0260/6440] * Fixed nix-switch. --- scripts/nix-switch.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/nix-switch.in b/scripts/nix-switch.in index 427a803b2..85fa3ac44 100755 --- a/scripts/nix-switch.in +++ b/scripts/nix-switch.in @@ -13,7 +13,7 @@ while ($argnr < scalar @ARGV) { if ($arg eq "--keep") { $keep = 1; } elsif ($arg eq "--source-root") { $sourceroot = 1; } elsif ($arg eq "--name") { $name = $ARGV[$argnr++]; } - elsif ($arg =~ /^([0-9a-z]{32})$/) { $srcid = $arg; } + elsif ($arg =~ /^\//) { $srcid = $arg; } else { die "unknown argument `$arg'" }; } @@ -23,7 +23,7 @@ my $linkdir = "@localstatedir@/nix/links"; my $nfid = `nix --install $srcid`; if ($?) { die "`nix --install' failed"; } chomp $nfid; -die unless $nfid =~ /^([0-9a-z]{32})$/; +die unless $nfid =~ /^\//; my $pkgdir = `nix --query --list $nfid`; if ($?) { die "`nix --query --list' failed"; } From 08b7319f5bca01f46916faaec0f9de420404a5ad Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 9 Oct 2003 15:38:31 +0000 Subject: [PATCH 0261/6440] * Follow successors by default (use `--no-successors' to override). --- scripts/nix-collect-garbage.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/nix-collect-garbage.in b/scripts/nix-collect-garbage.in index 52f274367..9b471d896 100755 --- a/scripts/nix-collect-garbage.in +++ b/scripts/nix-collect-garbage.in @@ -8,11 +8,11 @@ my $storedir = "@prefix@/store"; my %alive; -my $keepsuccessors = 0; +my $keepsuccessors = 1; my $invert = 0; foreach my $arg (@ARGV) { - if ($arg eq "--keep-successors") { $keepsuccessors = 1; } + if ($arg eq "--no-successors") { $keepsuccessors = 0; } elsif ($arg eq "--invert") { $invert = 1; } else { die "unknown argument `$arg'" }; } From 1eb4da156cca1b1981ab1f60bb9797ed1e93101a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 10 Oct 2003 13:22:29 +0000 Subject: [PATCH 0262/6440] * Performance improvement: don't register already registered terms, thus greatly reducing the number of db transactions. --- src/expr.cc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/expr.cc b/src/expr.cc index 2ed3e678b..521dffc9c 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -37,12 +37,15 @@ Path writeTerm(ATerm t, const string & suffix) Path path = canonPath(nixStore + "/" + (string) h + suffix + ".nix"); - if (!ATwriteToNamedTextFile(t, path.c_str())) - throw Error(format("cannot write aterm %1%") % path); - Transaction txn(nixDB); - registerValidPath(txn, path); - txn.commit(); + if (!isValidPath(path)) { + if (!ATwriteToNamedTextFile(t, path.c_str())) + throw Error(format("cannot write aterm %1%") % path); + + Transaction txn(nixDB); + registerValidPath(txn, path); + txn.commit(); + } return path; } From d3d5e77810cca11cca95bbb6f0f5e15d23f31eea Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 10 Oct 2003 14:46:28 +0000 Subject: [PATCH 0263/6440] * Reverse mappings for the successor and substitute mappings. --- src/globals.cc | 4 ++++ src/globals.hh | 14 ++++++++++++ src/normalise.cc | 7 ------ src/normalise.hh | 4 ---- src/store.cc | 56 ++++++++++++++++++++++++++++++++++-------------- src/store.hh | 10 +++++++++ 6 files changed, 68 insertions(+), 27 deletions(-) diff --git a/src/globals.cc b/src/globals.cc index f34f8f102..22d2758b6 100644 --- a/src/globals.cc +++ b/src/globals.cc @@ -7,7 +7,9 @@ Database nixDB; TableId dbValidPaths; TableId dbSuccessors; +TableId dbSuccessorsRev; TableId dbSubstitutes; +TableId dbSubstitutesRev; string nixStore = "/UNINIT"; @@ -24,7 +26,9 @@ void openDB() nixDB.open(nixDBPath); dbValidPaths = nixDB.openTable("validpaths"); dbSuccessors = nixDB.openTable("successors"); + dbSuccessorsRev = nixDB.openTable("successors-rev"); dbSubstitutes = nixDB.openTable("substitutes"); + dbSubstitutesRev = nixDB.openTable("substitutes-rev"); } diff --git a/src/globals.hh b/src/globals.hh index 0ea2fca09..910e47e01 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -33,6 +33,13 @@ extern TableId dbValidPaths; extern TableId dbSuccessors; +/* dbSuccessorsRev :: Path -> [Path] + + The reverse mapping of dbSuccessors. +*/ +extern TableId dbSuccessorsRev; + + /* dbSubstitutes :: Path -> [Path] Each pair $(p, [ps])$ tells Nix that it can realise any of the @@ -47,6 +54,13 @@ extern TableId dbSuccessors; extern TableId dbSubstitutes; +/* dbSubstitutesRev :: Path -> [Path] + + The reverse mapping of dbSubstitutes. +*/ +extern TableId dbSubstitutesRev; + + /* Path names. */ /* nixStore is the directory where we generally store atomic and diff --git a/src/normalise.cc b/src/normalise.cc index 834276f1b..0dfc9f8e4 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -8,13 +8,6 @@ #include "globals.hh" -void registerSuccessor(const Transaction & txn, - const Path & path1, const Path & path2) -{ - nixDB.setString(txn, dbSuccessors, path1, path2); -} - - static Path useSuccessor(const Path & path) { string pathSucc; diff --git a/src/normalise.hh b/src/normalise.hh index 4b4db4ee2..e8e72f5bc 100644 --- a/src/normalise.hh +++ b/src/normalise.hh @@ -34,9 +34,5 @@ PathSet nixExprRequisites(const Path & nePath, output paths are completely contained in the set `outputs'. */ PathSet findGenerators(const PathSet & outputs); -/* Register a successor. */ -void registerSuccessor(const Transaction & txn, - const Path & path1, const Path & path2); - #endif /* !__NORMALISE_H */ diff --git a/src/store.cc b/src/store.cc index b2b479e0d..de1dcca70 100644 --- a/src/store.cc +++ b/src/store.cc @@ -81,26 +81,50 @@ void copyPath(const Path & src, const Path & dst) } +void registerSuccessor(const Transaction & txn, + const Path & path1, const Path & path2) +{ + Path known; + if (nixDB.queryString(txn, dbSuccessors, path1, known) && + known != path2) + { + throw Error(format( + "the `impossible' happened: expression in path " + "`%1%' appears to have multiple successors " + "(known `%2%', new `%3%'") + % path1 % known % path2); + } + + Paths revs; + nixDB.queryStrings(txn, dbSuccessorsRev, path2, revs); + revs.push_back(path1); + + nixDB.setString(txn, dbSuccessors, path1, path2); + nixDB.setStrings(txn, dbSuccessorsRev, path2, revs); +} + + void registerSubstitute(const Path & srcPath, const Path & subPath) { -#if 0 - Strings subs; - queryListDB(nixDB, dbSubstitutes, srcId, subs); /* non-existence = ok */ - - for (Strings::iterator it = subs.begin(); it != subs.end(); it++) - if (parseHash(*it) == subId) return; - - subs.push_back(subId); - - setListDB(nixDB, dbSubstitutes, srcId, subs); -#endif - - /* For now, accept only one substitute per id. */ - Strings subs; - subs.push_back(subPath); - Transaction txn(nixDB); + + Paths subs; + nixDB.queryStrings(txn, dbSubstitutes, srcPath, subs); + + if (find(subs.begin(), subs.end(), subPath) != subs.end()) { + /* Nothing to do if the substitute is already known. */ + txn.abort(); + return; + } + subs.push_front(subPath); /* new substitutes take precedence */ + + Paths revs; + nixDB.queryStrings(txn, dbSubstitutesRev, subPath, revs); + revs.push_back(srcPath); + nixDB.setStrings(txn, dbSubstitutes, srcPath, subs); + nixDB.setStrings(txn, dbSubstitutesRev, subPath, revs); + txn.commit(); } diff --git a/src/store.hh b/src/store.hh index 69de82478..7b32a1a73 100644 --- a/src/store.hh +++ b/src/store.hh @@ -12,6 +12,16 @@ using namespace std; /* Copy a path recursively. */ void copyPath(const Path & src, const Path & dst); +/* Register a successor. This function accepts a transaction handle + so that it can be enclosed in an atomic operation with calls to + registerValidPath(). This must be atomic, since if we register a + successor for a derivation without registering the paths built in + the derivation, we have a successor with dangling pointers, and if + we do it in reverse order, we can get an obstructed build (since to + rebuild the successor, the outputs paths must not exist). */ +void registerSuccessor(const Transaction & txn, + const Path & path1, const Path & path2); + /* Register a substitute. */ void registerSubstitute(const Path & srcPath, const Path & subPath); From 0abe185688aa19c9ca87c9d22e24a54b4b359969 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 10 Oct 2003 15:14:29 +0000 Subject: [PATCH 0264/6440] * `nix --verify': check and repair reverse mapping for successors. --- src/store.cc | 118 ++++++++++++++------------------------------------- src/store.hh | 2 +- 2 files changed, 32 insertions(+), 88 deletions(-) diff --git a/src/store.cc b/src/store.cc index de1dcca70..695320713 100644 --- a/src/store.cc +++ b/src/store.cc @@ -82,25 +82,25 @@ void copyPath(const Path & src, const Path & dst) void registerSuccessor(const Transaction & txn, - const Path & path1, const Path & path2) + const Path & srcPath, const Path & sucPath) { Path known; - if (nixDB.queryString(txn, dbSuccessors, path1, known) && - known != path2) + if (nixDB.queryString(txn, dbSuccessors, srcPath, known) && + known != sucPath) { throw Error(format( "the `impossible' happened: expression in path " "`%1%' appears to have multiple successors " "(known `%2%', new `%3%'") - % path1 % known % path2); + % srcPath % known % sucPath); } Paths revs; - nixDB.queryStrings(txn, dbSuccessorsRev, path2, revs); - revs.push_back(path1); + nixDB.queryStrings(txn, dbSuccessorsRev, sucPath, revs); + revs.push_back(srcPath); - nixDB.setString(txn, dbSuccessors, path1, path2); - nixDB.setStrings(txn, dbSuccessorsRev, path2, revs); + nixDB.setString(txn, dbSuccessors, srcPath, sucPath); + nixDB.setStrings(txn, dbSuccessorsRev, sucPath, revs); } @@ -213,74 +213,22 @@ void verifyStore() { Transaction txn(nixDB); - /* !!! verify that the result is consistent */ + Paths paths; + nixDB.enumTable(txn, dbValidPaths, paths); -#if 0 - Strings paths; - nixDB.enumTable(txn, dbPath2Id, paths); - - for (Strings::iterator i = paths.begin(); + for (Paths::iterator i = paths.begin(); i != paths.end(); i++) { - bool erase = true; - string path = *i; - + Path path = *i; if (!pathExists(path)) { debug(format("path `%1%' disappeared") % path); + nixDB.delPair(txn, dbValidPaths, path); + nixDB.delPair(txn, dbSuccessorsRev, path); + nixDB.delPair(txn, dbSubstitutesRev, path); } - - else { - string id; - if (!nixDB.queryString(txn, dbPath2Id, path, id)) abort(); - - Strings idPaths; - nixDB.queryStrings(txn, dbId2Paths, id, idPaths); - - bool found = false; - for (Strings::iterator j = idPaths.begin(); - j != idPaths.end(); j++) - if (path == *j) { - found = true; - break; - } - - if (found) - erase = false; - else - /* !!! perhaps we should add path to idPaths? */ - debug(format("reverse mapping for path `%1%' missing") % path); - } - - if (erase) nixDB.delPair(txn, dbPath2Id, path); } - Strings ids; - nixDB.enumTable(txn, dbId2Paths, ids); - - for (Strings::iterator i = ids.begin(); - i != ids.end(); i++) - { - FSId id = parseHash(*i); - - Strings idPaths; - nixDB.queryStrings(txn, dbId2Paths, id, idPaths); - - for (Strings::iterator j = idPaths.begin(); - j != idPaths.end(); ) - { - string id2; - if (!nixDB.queryString(txn, dbPath2Id, *j, id2) || - id != parseHash(id2)) { - debug(format("erasing path `%1%' from mapping for id %2%") - % *j % (string) id); - j = idPaths.erase(j); - } else j++; - } - - nixDB.setStrings(txn, dbId2Paths, id, idPaths); - } - - +#if 0 Strings subs; nixDB.enumTable(txn, dbSubstitutes, subs); @@ -308,31 +256,27 @@ void verifyStore() nixDB.setStrings(txn, dbSubstitutes, srcId, subIds); } +#endif - Strings sucs; + Paths sucs; nixDB.enumTable(txn, dbSuccessors, sucs); - for (Strings::iterator i = sucs.begin(); - i != sucs.end(); i++) - { - FSId id1 = parseHash(*i); + for (Paths::iterator i = sucs.begin(); i != sucs.end(); i++) { + Path srcPath = *i; - string id2; - if (!nixDB.queryString(txn, dbSuccessors, id1, id2)) abort(); - - Strings id2Paths; - nixDB.queryStrings(txn, dbId2Paths, id2, id2Paths); - if (id2Paths.size() == 0) { - Strings id2Subs; - nixDB.queryStrings(txn, dbSubstitutes, id2, id2Subs); - if (id2Subs.size() == 0) { - debug(format("successor %1% for %2% missing") - % id2 % (string) id1); - nixDB.delPair(txn, dbSuccessors, (string) id1); - } + Path sucPath; + if (!nixDB.queryString(txn, dbSuccessors, srcPath, sucPath)) abort(); + + Paths revs; + nixDB.queryStrings(txn, dbSuccessorsRev, sucPath, revs); + + if (find(revs.begin(), revs.end(), srcPath) == revs.end()) { + debug(format("reverse successor mapping from `%1%' to `%2%' missing") + % srcPath % sucPath); + revs.push_back(srcPath); + nixDB.setStrings(txn, dbSuccessorsRev, sucPath, revs); } } -#endif txn.commit(); } diff --git a/src/store.hh b/src/store.hh index 7b32a1a73..abf874543 100644 --- a/src/store.hh +++ b/src/store.hh @@ -20,7 +20,7 @@ void copyPath(const Path & src, const Path & dst); we do it in reverse order, we can get an obstructed build (since to rebuild the successor, the outputs paths must not exist). */ void registerSuccessor(const Transaction & txn, - const Path & path1, const Path & path2); + const Path & srcPath, const Path & sucPath); /* Register a substitute. */ void registerSubstitute(const Path & srcPath, const Path & subPath); From 1d61e473c88568fae7ef5edebc77acd53e4126f9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 10 Oct 2003 15:25:21 +0000 Subject: [PATCH 0265/6440] * New query `nix --query --predecessors' to print the predecessors of a Nix expression. --- src/globals.hh | 3 ++- src/nix-help.txt | 1 + src/nix.cc | 15 ++++++++++++++- src/store.cc | 8 ++++++++ src/store.hh | 4 ++++ 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/globals.hh b/src/globals.hh index 910e47e01..816cb4766 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -35,7 +35,8 @@ extern TableId dbSuccessors; /* dbSuccessorsRev :: Path -> [Path] - The reverse mapping of dbSuccessors. + The reverse mapping of dbSuccessors (i.e., it stores the + predecessors of a Nix expression). */ extern TableId dbSuccessorsRev; diff --git a/src/nix-help.txt b/src/nix-help.txt index a51018ea1..ceff114ae 100644 --- a/src/nix-help.txt +++ b/src/nix-help.txt @@ -24,6 +24,7 @@ Query flags: --list / -l: query the output paths (roots) of a Nix expression (default) --requisites / -r: print all paths necessary to realise expression --generators / -g: find expressions producing a subset of given ids + --predecessors: print predecessors of a Nix expression --graph: print a dot graph rooted at given ids Options: diff --git a/src/nix.cc b/src/nix.cc index 9907f5c74..9bbbf4ae8 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -73,7 +73,7 @@ Path maybeNormalise(const Path & ne, bool normalise) /* Perform various sorts of queries. */ static void opQuery(Strings opFlags, Strings opArgs) { - enum { qList, qRequisites, qGenerators, qGraph + enum { qList, qRequisites, qGenerators, qPredecessors, qGraph } query = qList; bool normalise = false; bool includeExprs = true; @@ -84,6 +84,7 @@ static void opQuery(Strings opFlags, Strings opArgs) if (*i == "--list" || *i == "-l") query = qList; else if (*i == "--requisites" || *i == "-r") query = qRequisites; else if (*i == "--generators" || *i == "-g") query = qGenerators; + else if (*i == "--predecessors") query = qPredecessors; else if (*i == "--graph") query = qGraph; else if (*i == "--normalise" || *i == "-n") normalise = true; else if (*i == "--exclude-exprs") includeExprs = false; @@ -139,6 +140,18 @@ static void opQuery(Strings opFlags, Strings opArgs) } #endif + case qPredecessors: { + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); i++) + { + Paths preds = queryPredecessors(checkPath(*i)); + for (Paths::iterator j = preds.begin(); + j != preds.end(); j++) + cout << format("%s\n") % *j; + } + break; + } + case qGraph: { PathSet roots; for (Strings::iterator i = opArgs.begin(); diff --git a/src/store.cc b/src/store.cc index 695320713..3e755a0d1 100644 --- a/src/store.cc +++ b/src/store.cc @@ -104,6 +104,14 @@ void registerSuccessor(const Transaction & txn, } +Paths queryPredecessors(const Path & sucPath) +{ + Paths revs; + nixDB.queryStrings(noTxn, dbSuccessorsRev, sucPath, revs); + return revs; +} + + void registerSubstitute(const Path & srcPath, const Path & subPath) { Transaction txn(nixDB); diff --git a/src/store.hh b/src/store.hh index abf874543..7851b1e3d 100644 --- a/src/store.hh +++ b/src/store.hh @@ -22,6 +22,10 @@ void copyPath(const Path & src, const Path & dst); void registerSuccessor(const Transaction & txn, const Path & srcPath, const Path & sucPath); +/* Return the predecessors of the Nix expression stored at the given + path. */ +Paths queryPredecessors(const Path & sucPath); + /* Register a substitute. */ void registerSubstitute(const Path & srcPath, const Path & subPath); From c190f051ac34b2df51402bf593150de97f491d86 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 14 Oct 2003 15:33:00 +0000 Subject: [PATCH 0266/6440] * Automatically recover the database in case of a crash. --- src/db.cc | 187 ++++++++++++++++++++++++++++++++++++++++------- src/db.hh | 4 + src/fix.cc | 4 +- src/nix.cc | 4 +- src/pathlocks.cc | 41 ++++++++--- src/pathlocks.hh | 5 ++ 6 files changed, 204 insertions(+), 41 deletions(-) diff --git a/src/db.cc b/src/db.cc index ffb1999fe..f9d618b3b 100644 --- a/src/db.cc +++ b/src/db.cc @@ -1,8 +1,13 @@ -#include "db.hh" -#include "util.hh" +#include +#include +#include #include +#include "db.hh" +#include "util.hh" +#include "pathlocks.hh" + /* Wrapper class to ensure proper destruction. */ class DestroyDbc @@ -89,51 +94,179 @@ Database::Database() Database::~Database() { - if (env) { - debug(format("closing database environment")); + close(); +} - try { - for (map::iterator i = tables.begin(); - i != tables.end(); i++) - { - debug(format("closing table %1%") % i->first); - Db * db = i->second; - db->close(0); - delete db; - } - - env->txn_checkpoint(0, 0, 0); - env->close(0); - - } catch (DbException e) { rethrow(e); } - - delete env; +int getAccessorCount(int fd) +{ + if (lseek(fd, 0, SEEK_SET) == -1) + throw SysError("seeking accessor count"); + char buf[128]; + int len; + if ((len = read(fd, buf, sizeof(buf) - 1)) == -1) + throw SysError("reading accessor count"); + buf[len] = 0; + int count; + if (sscanf(buf, "%d", &count) != 1) { + debug(format("accessor count is invalid: `%1%'") % buf); + return -1; } + return count; +} + + +void setAccessorCount(int fd, int n) +{ + if (lseek(fd, 0, SEEK_SET) == -1) + throw SysError("seeking accessor count"); + string s = (format("%1%") % n).str(); + const char * s2 = s.c_str(); + if (write(fd, s2, strlen(s2)) != (ssize_t) strlen(s2) || + ftruncate(fd, strlen(s2)) != 0) + throw SysError("writing accessor count"); +} + + +void openEnv(DbEnv * env, const string & path, u_int32_t flags) +{ + env->open(path.c_str(), + DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | + DB_CREATE | flags, + 0666); } void Database::open(const string & path) { - try { - - if (env) throw Error(format("environment already open")); + if (env) throw Error(format("environment already open")); + try { + + debug(format("opening database environment")); + + + /* Create the database environment object. */ env = new DbEnv(0); env->set_lg_bsize(32 * 1024); /* default */ env->set_lg_max(256 * 1024); /* must be > 4 * lg_bsize */ env->set_lk_detect(DB_LOCK_DEFAULT); - env->open(path.c_str(), - DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | - DB_CREATE, - 0666); + + /* The following code provides automatic recovery of the + database environment. Recovery is necessary when a process + dies while it has the database open. To detect this, + processes atomically increment a counter when the open the + database, and decrement it when they close it. If we see + that counter is > 0 but no processes are accessing the + database---determined by attempting to obtain a write lock + on a lock file on which all accessors have a read lock---we + must run recovery. Note that this also ensures that we + only run recovery when there are no other accessors (which + could cause database corruption). */ + + /* !!! close fdAccessors / fdLock on exception */ + + /* Open the accessor count file. */ + string accessorsPath = path + "/accessor_count"; + fdAccessors = ::open(accessorsPath.c_str(), O_RDWR | O_CREAT, 0666); + if (fdAccessors == -1) + throw SysError(format("opening file `%1%'") % accessorsPath); + + /* Open the lock file. */ + string lockPath = path + "/access_lock"; + fdLock = ::open(lockPath.c_str(), O_RDWR | O_CREAT, 0666); + if (fdLock == -1) + throw SysError(format("opening lock file `%1%'") % lockPath); + + /* Try to acquire a write lock. */ + debug(format("attempting write lock on `%1%'") % lockPath); + if (lockFile(fdLock, ltWrite, false)) { /* don't wait */ + + debug(format("write lock granted")); + + /* We have a write lock, which means that there are no + other readers or writers. */ + + int n = getAccessorCount(fdAccessors); + setAccessorCount(fdAccessors, 1); + + if (n != 0) { + msg(lvlTalkative, format("accessor count is %1%, running recovery") % n); + + /* Open the environment after running recovery. */ + openEnv(env, path, DB_RECOVER); + } + + else + /* Open the environment normally. */ + openEnv(env, path, 0); + + /* Downgrade to a read lock. */ + debug(format("downgrading to read lock on `%1%'") % lockPath); + lockFile(fdLock, ltRead, true); + + } else { + /* There are other accessors. */ + debug(format("write lock refused")); + + /* Acquire a read lock. */ + debug(format("acquiring read lock on `%1%'") % lockPath); + lockFile(fdLock, ltRead, true); /* wait indefinitely */ + + /* Increment the accessor count. */ + lockFile(fdAccessors, ltWrite, true); + int n = getAccessorCount(fdAccessors) + 1; + setAccessorCount(fdAccessors, n); + debug(format("incremented accessor count to %1%") % n); + lockFile(fdAccessors, ltNone, true); + + /* Open the environment normally. */ + openEnv(env, path, 0); + } } catch (DbException e) { rethrow(e); } } +void Database::close() +{ + if (!env) return; + + /* Close the database environment. */ + debug(format("closing database environment")); + + try { + + for (map::iterator i = tables.begin(); + i != tables.end(); i++) + { + debug(format("closing table %1%") % i->first); + Db * db = i->second; + db->close(0); + delete db; + } + + env->txn_checkpoint(0, 0, 0); + env->close(0); + + } catch (DbException e) { rethrow(e); } + + delete env; + + /* Decrement the accessor count. */ + lockFile(fdAccessors, ltWrite, true); + int n = getAccessorCount(fdAccessors) - 1; + setAccessorCount(fdAccessors, n); + debug(format("decremented accessor count to %1%") % n); + lockFile(fdAccessors, ltNone, true); + + ::close(fdAccessors); + ::close(fdLock); +} + + TableId Database::openTable(const string & tableName) { requireEnv(); diff --git a/src/db.hh b/src/db.hh index 4bac943e5..e3dc7ce7a 100644 --- a/src/db.hh +++ b/src/db.hh @@ -45,6 +45,9 @@ class Database private: DbEnv * env; + int fdLock; + int fdAccessors; + TableId nextId; map tables; @@ -57,6 +60,7 @@ public: ~Database(); void open(const string & path); + void close(); TableId openTable(const string & table); diff --git a/src/fix.cc b/src/fix.cc index 71fd06877..cbf759b31 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -438,8 +438,6 @@ static void printNixExpr(EvalState & state, Expr e) void run(Strings args) { - openDB(); - EvalState state; Strings files; bool readStdin = false; @@ -467,6 +465,8 @@ void run(Strings args) files.push_back(arg); } + openDB(); + if (readStdin) { Expr e = evalStdin(state); printNixExpr(state, e); diff --git a/src/nix.cc b/src/nix.cc index 9bbbf4ae8..1012780af 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -267,8 +267,6 @@ static void opVerify(Strings opFlags, Strings opArgs) list. */ void run(Strings args) { - openDB(); - Strings opFlags, opArgs; Operation op = 0; @@ -315,6 +313,8 @@ void run(Strings args) if (!op) throw UsageError("no operation specified"); + openDB(); + op(opFlags, opArgs); } diff --git a/src/pathlocks.cc b/src/pathlocks.cc index ff0226c84..3ecbbbcba 100644 --- a/src/pathlocks.cc +++ b/src/pathlocks.cc @@ -1,10 +1,39 @@ #include +#include +#include #include #include "pathlocks.hh" +bool lockFile(int fd, LockType lockType, bool wait) +{ + struct flock lock; + if (lockType == ltRead) lock.l_type = F_RDLCK; + else if (lockType == ltWrite) lock.l_type = F_WRLCK; + else if (lockType == ltNone) lock.l_type = F_UNLCK; + else abort(); + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; /* entire file */ + + if (wait) { + while (fcntl(fd, F_SETLKW, &lock) != 0) + if (errno != EINTR) + throw SysError(format("acquiring/releasing lock")); + } else { + while (fcntl(fd, F_SETLK, &lock) != 0) { + if (errno == EACCES || errno == EAGAIN) return false; + if (errno != EINTR) + throw SysError(format("acquiring/releasing lock")); + } + } + + return true; +} + + /* This enables us to check whether are not already holding a lock on a file ourselves. POSIX locks (fcntl) suck in this respect: if we close a descriptor, the previous lock will be closed as well. And @@ -43,16 +72,8 @@ PathLocks::PathLocks(const PathSet & _paths) fds.push_back(fd); this->paths.push_back(lockPath); - /* Lock it. */ - struct flock lock; - lock.l_type = F_WRLCK; /* exclusive lock */ - lock.l_whence = SEEK_SET; - lock.l_start = 0; - lock.l_len = 0; /* entire file */ - - while (fcntl(fd, F_SETLKW, &lock) == -1) - if (errno != EINTR) - throw SysError(format("acquiring lock on `%1%'") % lockPath); + /* Acquire an exclusive lock. */ + lockFile(fd, ltWrite, true); lockedPaths.insert(lockPath); } diff --git a/src/pathlocks.hh b/src/pathlocks.hh index 6c36b9b1d..ce61386d6 100644 --- a/src/pathlocks.hh +++ b/src/pathlocks.hh @@ -4,6 +4,11 @@ #include "util.hh" +typedef enum LockType { ltRead, ltWrite, ltNone }; + +bool lockFile(int fd, LockType lockType, bool wait); + + class PathLocks { private: From 5fc71276430e8e6a4588fa54da692f81d5ada585 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 15 Oct 2003 10:34:50 +0000 Subject: [PATCH 0267/6440] * Keep sources (derivation expression) by default, `--no-source' to override. --- scripts/nix-switch.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/nix-switch.in b/scripts/nix-switch.in index 85fa3ac44..9fcb598e3 100755 --- a/scripts/nix-switch.in +++ b/scripts/nix-switch.in @@ -3,7 +3,7 @@ use strict; my $keep = 0; -my $sourceroot = 0; +my $sourceroot = 1; my $name = "current"; my $srcid; @@ -11,7 +11,7 @@ my $argnr = 0; while ($argnr < scalar @ARGV) { my $arg = $ARGV[$argnr++]; if ($arg eq "--keep") { $keep = 1; } - elsif ($arg eq "--source-root") { $sourceroot = 1; } + elsif ($arg eq "--no-source") { $sourceroot = 0; } elsif ($arg eq "--name") { $name = $ARGV[$argnr++]; } elsif ($arg =~ /^\//) { $srcid = $arg; } else { die "unknown argument `$arg'" }; From ebff82222c7b946e70e539389c0027529b6c7ad0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 15 Oct 2003 12:42:39 +0000 Subject: [PATCH 0268/6440] * Refactoring: move all database manipulation into store.cc. * Removed `--query --generators'. --- src/db.cc | 8 ++++ src/db.hh | 2 + src/expr.cc | 11 ++--- src/globals.cc | 29 ------------- src/globals.hh | 64 ----------------------------- src/nix-help.txt | 1 - src/nix.cc | 22 ++-------- src/normalise.cc | 53 ++---------------------- src/store.cc | 105 +++++++++++++++++++++++++++++++++++++++++++++++ src/store.hh | 18 ++++++++ 10 files changed, 143 insertions(+), 170 deletions(-) diff --git a/src/db.cc b/src/db.cc index f9d618b3b..75f97a1e4 100644 --- a/src/db.cc +++ b/src/db.cc @@ -70,6 +70,14 @@ void Transaction::abort() } +void Transaction::moveTo(Transaction & t) +{ + if (t.txn) throw Error("target txn already exists"); + t.txn = txn; + txn = 0; +} + + void Database::requireEnv() { if (!env) throw Error("database environment not open"); diff --git a/src/db.hh b/src/db.hh index e3dc7ce7a..1c681b9b5 100644 --- a/src/db.hh +++ b/src/db.hh @@ -29,6 +29,8 @@ public: void abort(); void commit(); + + void moveTo(Transaction & t); }; diff --git a/src/expr.cc b/src/expr.cc index 521dffc9c..cfc4af1f3 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -39,14 +39,11 @@ Path writeTerm(ATerm t, const string & suffix) (string) h + suffix + ".nix"); if (!isValidPath(path)) { - if (!ATwriteToNamedTextFile(t, path.c_str())) - throw Error(format("cannot write aterm %1%") % path); - - Transaction txn(nixDB); - registerValidPath(txn, path); - txn.commit(); + char * s = ATwriteToString(t); + if (!s) throw Error(format("cannot write aterm to `%1%'") % path); + addTextToStore(path, string(s)); } - + return path; } diff --git a/src/globals.cc b/src/globals.cc index 22d2758b6..a292b49ae 100644 --- a/src/globals.cc +++ b/src/globals.cc @@ -1,37 +1,8 @@ #include "globals.hh" -#include "db.hh" - - -Database nixDB; - - -TableId dbValidPaths; -TableId dbSuccessors; -TableId dbSuccessorsRev; -TableId dbSubstitutes; -TableId dbSubstitutesRev; - string nixStore = "/UNINIT"; string nixDataDir = "/UNINIT"; string nixLogDir = "/UNINIT"; string nixDBPath = "/UNINIT"; - bool keepFailed = false; - - -void openDB() -{ - nixDB.open(nixDBPath); - dbValidPaths = nixDB.openTable("validpaths"); - dbSuccessors = nixDB.openTable("successors"); - dbSuccessorsRev = nixDB.openTable("successors-rev"); - dbSubstitutes = nixDB.openTable("substitutes"); - dbSubstitutesRev = nixDB.openTable("substitutes-rev"); -} - - -void initDB() -{ -} diff --git a/src/globals.hh b/src/globals.hh index 816cb4766..1b4d0bde3 100644 --- a/src/globals.hh +++ b/src/globals.hh @@ -3,65 +3,8 @@ #include -#include "db.hh" - using namespace std; - -extern Database nixDB; - - -/* Database tables. */ - - -/* dbValidPaths :: Path -> () - - The existence of a key $p$ indicates that path $p$ is valid (that - is, produced by a succesful build). */ -extern TableId dbValidPaths; - - -/* dbSuccessors :: Path -> Path - - Each pair $(p_1, p_2)$ in this mapping records the fact that the - Nix expression stored at path $p_1$ has a successor expression - stored at path $p_2$. - - Note that a term $y$ is a successor of $x$ iff there exists a - sequence of rewrite steps that rewrites $x$ into $y$. -*/ -extern TableId dbSuccessors; - - -/* dbSuccessorsRev :: Path -> [Path] - - The reverse mapping of dbSuccessors (i.e., it stores the - predecessors of a Nix expression). -*/ -extern TableId dbSuccessorsRev; - - -/* dbSubstitutes :: Path -> [Path] - - Each pair $(p, [ps])$ tells Nix that it can realise any of the - Nix expressions stored at paths $ps$ to produce a path $p$. - - The main purpose of this is for distributed caching of derivates. - One system can compute a derivate and put it on a website (as a Nix - archive), for instance, and then another system can register a - substitute for that derivate. The substitute in this case might be - a Nix expression that fetches the Nix archive. -*/ -extern TableId dbSubstitutes; - - -/* dbSubstitutesRev :: Path -> [Path] - - The reverse mapping of dbSubstitutes. -*/ -extern TableId dbSubstitutesRev; - - /* Path names. */ /* nixStore is the directory where we generally store atomic and @@ -83,11 +26,4 @@ extern string nixDBPath; extern bool keepFailed; -/* Open the database environment. */ -void openDB(); - -/* Create the required database tables. */ -void initDB(); - - #endif /* !__GLOBALS_H */ diff --git a/src/nix-help.txt b/src/nix-help.txt index ceff114ae..bf2afd061 100644 --- a/src/nix-help.txt +++ b/src/nix-help.txt @@ -23,7 +23,6 @@ Query flags: --list / -l: query the output paths (roots) of a Nix expression (default) --requisites / -r: print all paths necessary to realise expression - --generators / -g: find expressions producing a subset of given ids --predecessors: print predecessors of a Nix expression --graph: print a dot graph rooted at given ids diff --git a/src/nix.cc b/src/nix.cc index 1012780af..a4d2898f1 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -73,7 +73,7 @@ Path maybeNormalise(const Path & ne, bool normalise) /* Perform various sorts of queries. */ static void opQuery(Strings opFlags, Strings opArgs) { - enum { qList, qRequisites, qGenerators, qPredecessors, qGraph + enum { qList, qRequisites, qPredecessors, qGraph } query = qList; bool normalise = false; bool includeExprs = true; @@ -83,7 +83,6 @@ static void opQuery(Strings opFlags, Strings opArgs) i != opFlags.end(); i++) if (*i == "--list" || *i == "-l") query = qList; else if (*i == "--requisites" || *i == "-r") query = qRequisites; - else if (*i == "--generators" || *i == "-g") query = qGenerators; else if (*i == "--predecessors") query = qPredecessors; else if (*i == "--graph") query = qGraph; else if (*i == "--normalise" || *i == "-n") normalise = true; @@ -124,22 +123,6 @@ static void opQuery(Strings opFlags, Strings opArgs) break; } -#if 0 - case qGenerators: { - FSIds outIds; - for (Strings::iterator i = opArgs.begin(); - i != opArgs.end(); i++) - outIds.push_back(checkPath(*i)); - - FSIds genIds = findGenerators(outIds); - - for (FSIds::iterator i = genIds.begin(); - i != genIds.end(); i++) - cout << format("%s\n") % expandId(*i); - break; - } -#endif - case qPredecessors: { for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) @@ -172,7 +155,8 @@ static void opSuccessor(Strings opFlags, Strings opArgs) if (!opFlags.empty()) throw UsageError("unknown flag"); if (opArgs.size() % 2) throw UsageError("expecting even number of arguments"); - Transaction txn(nixDB); /* !!! this could be a big transaction */ + Transaction txn; + createStoreTransaction(txn); for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ) { diff --git a/src/normalise.cc b/src/normalise.cc index 0dfc9f8e4..160130d96 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -2,7 +2,6 @@ #include "normalise.hh" #include "references.hh" -#include "db.hh" #include "exec.hh" #include "pathlocks.hh" #include "globals.hh" @@ -11,7 +10,7 @@ static Path useSuccessor(const Path & path) { string pathSucc; - if (nixDB.queryString(noTxn, dbSuccessors, path, pathSucc)) { + if (querySuccessor(path, pathSucc)) { debug(format("successor %1% -> %2%") % (string) path % pathSucc); return pathSucc; } else @@ -349,7 +348,8 @@ Path normaliseNixExpr(const Path & _nePath, PathSet pending) for recoverability: unregistered paths in the store can be deleted arbitrarily, while registered paths can only be deleted by running the garbage collector. */ - Transaction txn(nixDB); + Transaction txn; + createStoreTransaction(txn); for (PathSet::iterator i = ne.derivation.outputs.begin(); i != ne.derivation.outputs.end(); i++) registerValidPath(txn, *i); @@ -434,50 +434,3 @@ PathSet nixExprRequisites(const Path & nePath, paths, doneSet); return paths; } - - -#if 0 -PathSet findGenerators(const PathSet & outputs) -{ - FSIdSet ids(_ids.begin(), _ids.end()); - FSIds generators; - - /* !!! hack; for performance, we just look at the rhs of successor - mappings, since we know that those are Nix expressions. */ - - Strings sucs; - nixDB.enumTable(noTxn, dbSuccessors, sucs); - - for (Strings::iterator i = sucs.begin(); - i != sucs.end(); i++) - { - string s; - if (!nixDB.queryString(noTxn, dbSuccessors, *i, s)) continue; - FSId id = parseHash(s); - - NixExpr ne; - try { - /* !!! should substitutes be used? */ - ne = parseNixExpr(termFromId(id)); - } catch (...) { /* !!! only catch parse errors */ - continue; - } - - if (ne.type != NixExpr::neClosure) continue; - - bool okay = true; - for (ClosureElems::const_iterator i = ne.closure.elems.begin(); - i != ne.closure.elems.end(); i++) - if (ids.find(i->second.id) == ids.end()) { - okay = false; - break; - } - - if (!okay) continue; - - generators.push_back(id); - } - - return generators; -} -#endif diff --git a/src/store.cc b/src/store.cc index 3e755a0d1..7f10c6377 100644 --- a/src/store.cc +++ b/src/store.cc @@ -1,7 +1,10 @@ #include #include +#include #include +#include +#include #include "store.hh" #include "globals.hh" @@ -10,6 +13,81 @@ #include "pathlocks.hh" +/* Nix database. */ +static Database nixDB; + + +/* Database tables. */ + +/* dbValidPaths :: Path -> () + + The existence of a key $p$ indicates that path $p$ is valid (that + is, produced by a succesful build). */ +static TableId dbValidPaths; + +/* dbSuccessors :: Path -> Path + + Each pair $(p_1, p_2)$ in this mapping records the fact that the + Nix expression stored at path $p_1$ has a successor expression + stored at path $p_2$. + + Note that a term $y$ is a successor of $x$ iff there exists a + sequence of rewrite steps that rewrites $x$ into $y$. +*/ +static TableId dbSuccessors; + +/* dbSuccessorsRev :: Path -> [Path] + + The reverse mapping of dbSuccessors (i.e., it stores the + predecessors of a Nix expression). +*/ +static TableId dbSuccessorsRev; + +/* dbSubstitutes :: Path -> [Path] + + Each pair $(p, [ps])$ tells Nix that it can realise any of the + Nix expressions stored at paths $ps$ to produce a path $p$. + + The main purpose of this is for distributed caching of derivates. + One system can compute a derivate and put it on a website (as a Nix + archive), for instance, and then another system can register a + substitute for that derivate. The substitute in this case might be + a Nix expression that fetches the Nix archive. +*/ +static TableId dbSubstitutes; + +/* dbSubstitutesRev :: Path -> [Path] + + The reverse mapping of dbSubstitutes. +*/ +static TableId dbSubstitutesRev; + + +void openDB() +{ + nixDB.open(nixDBPath); + dbValidPaths = nixDB.openTable("validpaths"); + dbSuccessors = nixDB.openTable("successors"); + dbSuccessorsRev = nixDB.openTable("successors-rev"); + dbSubstitutes = nixDB.openTable("substitutes"); + dbSubstitutesRev = nixDB.openTable("substitutes-rev"); +} + + +void initDB() +{ +} + + +void createStoreTransaction(Transaction & txn) +{ + Transaction txn2(nixDB); + txn2.moveTo(txn); +} + + +/* Path copying. */ + struct CopySink : DumpSink { int fd; @@ -104,6 +182,12 @@ void registerSuccessor(const Transaction & txn, } +bool querySuccessor(const Path & srcPath, Path & sucPath) +{ + return nixDB.queryString(noTxn, dbSuccessors, srcPath, sucPath); +} + + Paths queryPredecessors(const Path & sucPath) { Paths revs; @@ -204,6 +288,27 @@ Path addToStore(const Path & _srcPath) } +void addTextToStore(const Path & dstPath, const string & s) +{ + if (!isValidPath(dstPath)) { + + /* !!! locking? -> parallel writes are probably idempotent */ + + int fd = open(dstPath.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd == -1) throw SysError(format("creating store file `%1%'") % dstPath); + + if (write(fd, s.c_str(), s.size()) != (ssize_t) s.size()) + throw SysError(format("writing store file `%1%'") % dstPath); + + close(fd); /* !!! close on exception */ + + Transaction txn(nixDB); + registerValidPath(txn, dstPath); + txn.commit(); + } +} + + void deleteFromStore(const Path & _path) { Path path(canonPath(_path)); diff --git a/src/store.hh b/src/store.hh index 7851b1e3d..3d7575c3e 100644 --- a/src/store.hh +++ b/src/store.hh @@ -9,6 +9,15 @@ using namespace std; +/* Open the database environment. */ +void openDB(); + +/* Create the required database tables. */ +void initDB(); + +/* Get a transaction object. */ +void createStoreTransaction(Transaction & txn); + /* Copy a path recursively. */ void copyPath(const Path & src, const Path & dst); @@ -22,6 +31,10 @@ void copyPath(const Path & src, const Path & dst); void registerSuccessor(const Transaction & txn, const Path & srcPath, const Path & sucPath); +/* Return the predecessors of the Nix expression stored at the given + path. */ +bool querySuccessor(const Path & srcPath, Path & sucPath); + /* Return the predecessors of the Nix expression stored at the given path. */ Paths queryPredecessors(const Path & sucPath); @@ -42,6 +55,11 @@ bool isValidPath(const Path & path); the resulting path. The resulting path is returned. */ Path addToStore(const Path & srcPath); +/* Like addToStore, but the path of the output is given, and the + contents written to the output path is a regular file containing + the given string. */ +void addTextToStore(const Path & dstPath, const string & s); + /* Delete a value from the nixStore directory. */ void deleteFromStore(const Path & path); From 181aa3dc4198d2e2cfa89d3ebb53a96fa567e12f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 16 Oct 2003 08:06:19 +0000 Subject: [PATCH 0269/6440] * Don't sort the result of `--query --list'. --- src/nix.cc | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/nix.cc b/src/nix.cc index a4d2898f1..187568999 100644 --- a/src/nix.cc +++ b/src/nix.cc @@ -93,17 +93,15 @@ static void opQuery(Strings opFlags, Strings opArgs) switch (query) { case qList: { - PathSet paths; for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) { - StringSet paths2 = nixExprRoots( + StringSet paths = nixExprRoots( maybeNormalise(checkPath(*i), normalise)); - paths.insert(paths2.begin(), paths2.end()); + for (StringSet::iterator j = paths.begin(); + j != paths.end(); j++) + cout << format("%s\n") % *j; } - for (StringSet::iterator i = paths.begin(); - i != paths.end(); i++) - cout << format("%s\n") % *i; break; } From f7c7aad1351a0ed58f458e485968af498d542b5b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 16 Oct 2003 08:52:44 +0000 Subject: [PATCH 0270/6440] * Upgraded to Berkeley DB 4.1.25 and do not synchronously flush the log on commit. This means that there is a small change that some transactions may be rolled back in case of a system crash, but this should not be a problem (it merely might cause some expression realisations to be rolled back), and it vastly improves performance. * Upgraded to ATerm 2.0.5 (which also includes Armijn's 64-bit patches). --- externals/Makefile.am | 12 ++++++------ src/db.cc | 9 +++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/externals/Makefile.am b/externals/Makefile.am index 6a3d3a1f7..46b1a9fdb 100644 --- a/externals/Makefile.am +++ b/externals/Makefile.am @@ -1,11 +1,11 @@ # Berkeley DB -DB = db-4.0.14 +DB = db-4.1.25 $(DB).tar.gz: @echo "Nix requires Berkeley DB to build." - @echo "Please download version 4.0.14 from" - @echo " http://www.sleepycat.com/update/snapshot/db-4.0.14.tar.gz" + @echo "Please download version 4.1.25 from" + @echo " http://www.sleepycat.com/update/snapshot/db-4.1.25.tar.gz" @echo "and place it in the externals/ directory." false @@ -28,12 +28,12 @@ build-db: have-db # CWI ATerm -ATERM = aterm-2.0 +ATERM = aterm-2.0.5 $(ATERM).tar.gz: @echo "Nix requires the CWI ATerm library to build." - @echo "Please download version 2.0 from" - @echo " http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.tar.gz" + @echo "Please download version 2.0.5 from" + @echo " http://www.cwi.nl/projects/MetaEnv/aterm/aterm-2.0.5.tar.gz" @echo "and place it in the externals/ directory." false diff --git a/src/db.cc b/src/db.cc index 75f97a1e4..2f53ca3b5 100644 --- a/src/db.cc +++ b/src/db.cc @@ -160,6 +160,7 @@ void Database::open(const string & path) env->set_lg_bsize(32 * 1024); /* default */ env->set_lg_max(256 * 1024); /* must be > 4 * lg_bsize */ env->set_lk_detect(DB_LOCK_DEFAULT); + env->set_flags(DB_TXN_WRITE_NOSYNC, 1); /* The following code provides automatic recovery of the @@ -252,11 +253,11 @@ void Database::close() { debug(format("closing table %1%") % i->first); Db * db = i->second; - db->close(0); + db->close(DB_NOSYNC); delete db; } - env->txn_checkpoint(0, 0, 0); +// env->txn_checkpoint(0, 0, 0); env->close(0); } catch (DbException e) { rethrow(e); } @@ -285,8 +286,8 @@ TableId Database::openTable(const string & tableName) Db * db = new Db(env, 0); try { - // !!! fixme when switching to BDB 4.1: use txn. - db->open(tableName.c_str(), 0, DB_HASH, DB_CREATE, 0666); + db->open(0, tableName.c_str(), 0, + DB_HASH, DB_CREATE | DB_AUTO_COMMIT, 0666); } catch (...) { delete db; throw; From c78bf115248f62fa355e7a9b2b9532b37e693085 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 16 Oct 2003 11:55:37 +0000 Subject: [PATCH 0271/6440] * Enable buffering of stderr in C++. --- src/shared.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shared.cc b/src/shared.cc index 32e891617..dcda0b50a 100644 --- a/src/shared.cc +++ b/src/shared.cc @@ -53,6 +53,10 @@ int main(int argc, char * * argv) ATerm bottomOfStack; ATinit(argc, argv, &bottomOfStack); + /* Turn on buffering for cerr. */ + char buf[1024]; + cerr.rdbuf()->pubsetbuf(buf, sizeof(buf)); + try { initAndRun(argc, argv); } catch (UsageError & e) { From ab5e8767fafb2d62213e3f1558ead2882bc65c05 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 16 Oct 2003 13:13:39 +0000 Subject: [PATCH 0272/6440] * Get nix-push to work again. * Fixed svn:ignore on externals/. --- corepkgs/nar/nar.sh.in | 8 +-- scripts/nix-push.in | 110 +++++++++++++++++++++++++++++------------ 2 files changed, 82 insertions(+), 36 deletions(-) diff --git a/corepkgs/nar/nar.sh.in b/corepkgs/nar/nar.sh.in index 9f96b03ab..c92ef8e25 100644 --- a/corepkgs/nar/nar.sh.in +++ b/corepkgs/nar/nar.sh.in @@ -4,9 +4,9 @@ export PATH=/bin:/usr/bin echo "packing $path into $out..." mkdir $out || exit 1 -tmp=$out/tmp -@bindir@/nix --dump --path "$path" | bzip2 > $out/tmp || exit 1 +dst=$out/`basename $path`.nar.bz2 +@bindir@/nix --dump "$path" | bzip2 > $dst || exit 1 -md5=$(md5sum -b $tmp | cut -c1-32) +md5=$(md5sum -b $dst | cut -c1-32) if test $? != 0; then exit 1; fi -mv $out/tmp $out/$md5-`basename $path`.nar.bz2 || exit 1 +echo $md5 > $out/md5 || exit 1 diff --git a/scripts/nix-push.in b/scripts/nix-push.in index d9f5cf756..c0aba5322 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -1,21 +1,32 @@ #! /usr/bin/perl -w -my $fixfile = "/tmp/nix-push-tmp.fix"; +use strict; +use POSIX qw(tmpnam); + +my $tmpdir; +do { $tmpdir = tmpnam(); } +until mkdir $tmpdir, 0777; + +my $fixfile = "$tmpdir/create-nars.fix"; +my $manifest = "$tmpdir/MANIFEST"; + +END { unlink $manifest; unlink $fixfile; rmdir $tmpdir; } + open FIX, ">$fixfile"; print FIX "["; my $first = 1; +my @paths; + foreach my $id (@ARGV) { - die unless $id =~ /^([0-9a-z]{32})$/; + die unless $id =~ /^\//; # Get all paths referenced by the normalisation of the given # Nix expression. - system "nix --install $id"; + system "nix --install $id > /dev/null"; if ($?) { die "`nix --install' failed"; } - my @paths; - open PATHS, "nix --query --requisites --include-successors $id 2> /dev/null |" or die "nix -qr"; while () { chomp; @@ -24,35 +35,17 @@ foreach my $id (@ARGV) { } close PATHS; - # Also add all normal forms that are contained in these paths. -# open PATHS, "nix --query --generators --path @paths |" or die "nix -qg"; -# while () { -# chomp; -# die "bad: $_" unless /^\//; -# push @paths, $_; -# } -# close PATHS; - # For each path, create a Fix expression that turns the path into # a Nix archive. foreach my $path (@paths) { - next unless ($path =~ /\/([0-9a-z]{32})[^\/]*/); + die unless ($path =~ /\/[0-9a-z]{32}.*$/); print "$path\n"; - my $pathid = $1; - - # Construct a name for the Nix archive. If the file is an - # fstate successor, encode this into the name. - my $name = $pathid; - if ($path =~ /-s-([0-9a-z]{32}).nix$/) { - $name = "$name-s-$1"; - } - $name = $name . ".nar.bz2"; # Construct a Fix expression that creates a Nix archive. my $fixexpr = - "App(IncludeFix(\"nar/nar.fix\"), " . - "[ (\"path\", Closure([\"$path\"], [(\"$path\", \"$pathid\", [])]))" . + "Call(IncludeFix(\"nar/nar.fix\"), " . + "[ (\"path\", Closure([\"$path\"], [(\"$path\", [])]))" . "])"; print FIX "," unless ($first); @@ -65,32 +58,85 @@ foreach my $id (@ARGV) { print FIX "]"; close FIX; + # Instantiate a Nix expression from the Fix expression. my @nids; print STDERR "running fix...\n"; open NIDS, "fix $fixfile |" or die "cannot run fix"; while () { chomp; - die unless /^([0-9a-z]{32})$/; - push @nids, $1; + die unless /^\//; + push @nids, $_; } +close NIDS; # Realise the Nix expression. -my @pushlist; print STDERR "creating archives...\n"; -system "nix --install @nids > /dev/null"; +system "nix --install -v @nids > /dev/null"; if ($?) { die "`nix --install' failed"; } +my @narpaths; open NIDS, "nix --query --list @nids |" or die "cannot run nix"; while () { chomp; die unless (/^\//); - print "$_\n"; - push @pushlist, "$_/*"; + push @narpaths, "$_"; +} +close NIDS; + + +# Create the manifest. +print STDERR "creating manifest...\n"; + +open MANIFEST, ">$manifest"; + +my @pushlist; +push @pushlist, $manifest; + +for (my $n = 0; $n < scalar @paths; $n++) { + my $storepath = $paths[$n]; + my $nardir = $narpaths[$n]; + + $storepath =~ /\/([^\/]*)$/; + my $basename = $1; + defined $basename or die; + + my $narname = "$basename.nar.bz2"; + + my $narfile = "$nardir/$narname"; + (-f $narfile) or die "narfile for $storepath not found"; + push @pushlist, $narfile; + + open MD5, "$nardir/md5" or die "cannot open hash"; + my $hash = ; + chomp $hash; + $hash =~ /^[0-9a-z]{32}$/ or die "invalid hash"; + close MD5; + + print MANIFEST "{\n"; + print MANIFEST " StorePath: $storepath\n"; + print MANIFEST " NarPath: $basename\n"; + print MANIFEST " MD5: $hash\n"; + + if ($storepath =~ /\.nix$/) { + open PREDS, "nix --query --predecessors $storepath |" or die "cannot run nix"; + while () { + chomp; + die unless (/^\//); + print MANIFEST " SuccOf: $_\n"; + } + close PREDS; + } + + print MANIFEST "}\n"; } +close MANIFEST; + + # Push the prebuilts to the server. !!! FIXME +print STDERR "pushing to server...\n"; if (scalar @pushlist > 0) { system "rsync -av -e ssh @pushlist eelco\@losser.st-lab.cs.uu.nl:/home/eelco/public_html/nix-dist/"; } From 0791282b2f42313c94dd9bc85b24428e585cd099 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 16 Oct 2003 16:29:57 +0000 Subject: [PATCH 0273/6440] * Substitutes and nix-pull now work again. * Fixed a segfault caused by the buffering of stderr. * Fix now allows the specification of the full output path. This should be used with great care, since it by-passes the normal hash generation. * Incremented the version number to 0.4 (prerelease). --- configure.ac | 2 +- corepkgs/nar/unnar.fix | 6 +- scripts/nix-pull.in | 141 +++++++++++++++++++++++++---------------- src/dotgraph.cc | 5 +- src/expr.cc | 8 --- src/expr.hh | 3 - src/fix.cc | 12 ++-- src/normalise.cc | 53 +++++++++++++--- src/normalise.hh | 8 +++ src/shared.cc | 3 +- src/store.cc | 14 +++- src/store.hh | 3 + 12 files changed, 169 insertions(+), 89 deletions(-) diff --git a/configure.ac b/configure.ac index 8e4a9d12b..8c878f2e3 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT(nix, "0.3") +AC_INIT(nix, "0.4") AC_CONFIG_SRCDIR(src/nix.cc) AC_CONFIG_AUX_DIR(config) AM_INIT_AUTOMAKE diff --git a/corepkgs/nar/unnar.fix b/corepkgs/nar/unnar.fix index 315be5873..cd5079e50 100644 --- a/corepkgs/nar/unnar.fix +++ b/corepkgs/nar/unnar.fix @@ -1,9 +1,9 @@ -Function(["nar", "name"], +Function(["nar", "outPath"], Package( - [ ("name", Var("name")) + [ ("name", "unnar") + , ("outPath", Var("outPath")) , ("build", Relative("nar/unnar.sh")) , ("nar", Var("nar")) - , ("id", Var("id")) ] ) ) \ No newline at end of file diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index a3d23ea16..8cd276801 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -2,11 +2,18 @@ use strict; use IPC::Open2; +use POSIX qw(tmpnam); -my $tmpfile = "@localstatedir@/nix/pull.tmp"; +my $tmpdir; +do { $tmpdir = tmpnam(); } +until mkdir $tmpdir, 0777; + +my $manifest = "$tmpdir/manifest"; my $conffile = "@sysconfdir@/nix/prebuilts.conf"; -my @ids; +#END { unlink $manifest; rmdir $tmpdir; } + +my @srcpaths; my @subs; my @sucs; @@ -20,70 +27,89 @@ while () { chomp; if (/^\s*(\S+)\s*(\#.*)?$/) { my $url = $1; + $url =~ s/\/$//; print "obtaining list of Nix archives at $url...\n"; - system "wget '$url' -O '$tmpfile' 2> /dev/null"; # !!! escape + system "wget '$url'/MANIFEST -O '$manifest' 2> /dev/null"; # !!! escape if ($?) { die "`wget' failed"; } - open INDEX, "<$tmpfile"; + open MANIFEST, "<$manifest"; - while () { - # Get all links to prebuilts, that is, file names of the - # form foo-HASH-HASH.tar.bz2. - next unless (/HREF=\"([^\"]*)\"/); - my $fn = $1; - next if $fn =~ /\.\./; - next if $fn =~ /\//; - next unless $fn =~ /^([0-9a-z]{32})-([0-9a-z]{32})(.*)\.nar\.bz2$/; - my $hash = $1; - my $id = $2; - my $outname = $3; - my $fsid; - if ($outname =~ /^-/) { - next unless $outname =~ /^-((s-([0-9a-z]{32}))?.*)$/; - $outname = $1; - $fsid = $3; - } else { - $outname = "unnamed"; - } + my $inside = 0; - print STDERR "$id ($outname)\n"; + my $storepath; + my $narname; + my $hash; + my @preds; - # Construct a Fix expression that fetches and unpacks a - # Nix archive from the network. - my $fetch = - "App(IncludeFix(\"fetchurl/fetchurl.fix\"), " . - "[(\"url\", \"$url/$fn\"), (\"md5\", \"$hash\")])"; - my $fixexpr = - "App(IncludeFix(\"nar/unnar.fix\"), " . - "[ (\"nar\", $fetch)" . - ", (\"name\", \"$outname\")" . - ", (\"id\", \"$id\")" . - "])"; - - if (!$first) { $fullexpr .= "," }; - $first = 0; - $fullexpr .= $fixexpr; # !!! O(n^2)? + while () { + chomp; + s/\#.*$//g; + next if (/^$/); - push @ids, $id; + if (!$inside) { + if (/^\{$/) { + $inside = 1; + undef $storepath; + undef $narname; + undef $hash; + @preds = (); + } + else { die "bad line: $_"; } + } else { + if (/^\}$/) { + $inside = 0; + my $fullurl = "$url/$narname"; + print "$storepath\n"; - # Does the name encode a successor relation? - if (defined $fsid) { - push @sucs, $fsid; - push @sucs, $id; - } + # Construct a Fix expression that fetches and unpacks a + # Nix archive from the network. + my $fetch = + "App(IncludeFix(\"fetchurl/fetchurl.fix\"), " . + "[(\"url\", \"$fullurl\"), (\"md5\", \"$hash\")])"; + my $fixexpr = + "App(IncludeFix(\"nar/unnar.fix\"), " . + "[ (\"nar\", $fetch)" . + ", (\"outPath\", \"$storepath\")" . + "])"; + + if (!$first) { $fullexpr .= "," }; + $first = 0; + $fullexpr .= $fixexpr; # !!! O(n^2)? + + push @srcpaths, $storepath; + + foreach my $p (@preds) { + push @sucs, $p; + push @sucs, $storepath; + } + + } + elsif (/^\s*StorePath:\s*(\/\S+)\s*$/) { + $storepath = $1; + } + elsif (/^\s*NarName:\s*(\S+)\s*$/) { + $narname = $1; + } + elsif (/^\s*MD5:\s*(\S+)\s*$/) { + $hash = $1; + } + elsif (/^\s*SuccOf:\s*(\/\S+)\s*$/) { + push @preds, $1; + } + else { die "bad line: $_"; } + } } - close INDEX; - - unlink $tmpfile; + close MANIFEST; } } $fullexpr .= "]"; + # Instantiate Nix expressions from the Fix expressions we created above. print STDERR "running fix...\n"; my $pid = open2(\*READ, \*WRITE, "fix -") or die "cannot run fix"; @@ -93,23 +119,28 @@ close WRITE; my $i = 0; while () { chomp; - die unless /^([0-9a-z]{32})$/; - my $nid = $1; - die unless ($i < scalar @ids); - my $id = $ids[$i++]; - push @subs, $id; - push @subs, $nid; + die unless /^\//; + my $subpath = $_; + die unless ($i < scalar @srcpaths); + my $srcpath = $srcpaths[$i++]; + push @subs, $srcpath; + push @subs, $subpath; + print "$srcpath $subpath\n"; } waitpid $pid, 0; $? == 0 or die "fix failed"; + # Register all substitutes. print STDERR "registering substitutes...\n"; +print "@subs\n"; system "nix --substitute @subs"; if ($?) { die "`nix --substitute' failed"; } + # Register all successors. print STDERR "registering successors...\n"; -system "nix --successor @sucs"; +print "@sucs\n"; +system "nix --successor -vvvv @sucs"; if ($?) { die "`nix --successor' failed"; } diff --git a/src/dotgraph.cc b/src/dotgraph.cc index 514fda325..36daf7f99 100644 --- a/src/dotgraph.cc +++ b/src/dotgraph.cc @@ -1,4 +1,5 @@ #include "dotgraph.hh" +#include "normalise.hh" static string dotQuote(const string & s) @@ -98,8 +99,8 @@ void printDotGraph(const PathSet & roots) if (doneSet.find(nePath) == doneSet.end()) { doneSet.insert(nePath); - - NixExpr ne = parseNixExpr(termFromPath(nePath)); + + NixExpr ne = exprFromPath(nePath); string label, colour; diff --git a/src/expr.cc b/src/expr.cc index cfc4af1f3..cead80342 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -22,14 +22,6 @@ Hash hashTerm(ATerm t) } -ATerm termFromPath(const Path & path) -{ - ATerm t = ATreadFromNamedFile(path.c_str()); - if (!t) throw Error(format("cannot read aterm from `%1%'") % path); - return t; -} - - Path writeTerm(ATerm t, const string & suffix) { /* The id of a term is its hash. */ diff --git a/src/expr.hh b/src/expr.hh index b34564322..7d0420935 100644 --- a/src/expr.hh +++ b/src/expr.hh @@ -53,9 +53,6 @@ Error badTerm(const format & f, ATerm t); /* Hash an aterm. */ Hash hashTerm(ATerm t); -/* Read an aterm from disk. */ -ATerm termFromPath(const Path & path); - /* Write an aterm to the Nix store directory, and return its path. */ Path writeTerm(ATerm t, const string & suffix); diff --git a/src/fix.cc b/src/fix.cc index cbf759b31..c1f9c1ad6 100644 --- a/src/fix.cc +++ b/src/fix.cc @@ -299,6 +299,7 @@ static Expr evalExpr2(EvalState & state, Expr e) ne.type = NixExpr::neDerivation; ne.derivation.platform = SYSTEM; string name; + Path outPath; Hash outHash; bool outHashGiven = false; bnds = ATempty; @@ -327,6 +328,7 @@ static Expr evalExpr2(EvalState & state, Expr e) if (key == "build") ne.derivation.builder = s; if (key == "name") name = s; + if (key == "outPath") outPath = s; if (key == "id") { outHash = parseHash(s); outHashGiven = true; @@ -343,11 +345,13 @@ static Expr evalExpr2(EvalState & state, Expr e) if (name == "") throw badTerm("no package name specified", e); - /* Hash the Nix expression with no outputs to produce a - unique but deterministic path name for this package. */ + /* Determine the output path. */ if (!outHashGiven) outHash = hashPackage(state, ne); - Path outPath = - canonPath(nixStore + "/" + ((string) outHash).c_str() + "-" + name); + if (outPath == "") + /* Hash the Nix expression with no outputs to produce a + unique but deterministic path name for this package. */ + outPath = + canonPath(nixStore + "/" + ((string) outHash).c_str() + "-" + name); ne.derivation.env["out"] = outPath; ne.derivation.outputs.insert(outPath); diff --git a/src/normalise.cc b/src/normalise.cc index 160130d96..be71081ff 100644 --- a/src/normalise.cc +++ b/src/normalise.cc @@ -122,7 +122,7 @@ Path normaliseNixExpr(const Path & _nePath, PathSet pending) Path nePath = useSuccessor(_nePath); /* Get the Nix expression. */ - NixExpr ne = parseNixExpr(termFromPath(nePath)); + NixExpr ne = exprFromPath(nePath, pending); /* If this is a normal form (i.e., a closure) we are done. */ if (ne.type == NixExpr::neClosure) return nePath; @@ -172,7 +172,7 @@ Path normaliseNixExpr(const Path & _nePath, PathSet pending) { Path nePath2 = useSuccessor(nePath); if (nePath != nePath2) { - NixExpr ne = parseNixExpr(termFromPath(nePath2)); + NixExpr ne = exprFromPath(nePath2, pending); debug(format("skipping build of expression `%1%', someone beat us to it") % (string) nePath); if (ne.type != NixExpr::neClosure) abort(); @@ -193,7 +193,7 @@ Path normaliseNixExpr(const Path & _nePath, PathSet pending) realiseClosure(nfPath, pending); /* !!! nfPath should be a root of the garbage collector while we are building */ - NixExpr ne = parseNixExpr(termFromPath(nfPath)); + NixExpr ne = exprFromPath(nfPath, pending); if (ne.type != NixExpr::neClosure) abort(); for (ClosureElems::iterator j = ne.closure.elems.begin(); j != ne.closure.elems.end(); j++) @@ -364,16 +364,49 @@ void realiseClosure(const Path & nePath, PathSet pending) { Nest nest(lvlDebug, format("realising closure `%1%'") % nePath); - NixExpr ne = parseNixExpr(termFromPath(nePath)); + NixExpr ne = exprFromPath(nePath, pending); if (ne.type != NixExpr::neClosure) throw Error(format("expected closure in `%1%'") % nePath); for (ClosureElems::const_iterator i = ne.closure.elems.begin(); i != ne.closure.elems.end(); i++) - assert(isValidPath(i->first)); -#if 0 - expandId(i->second.id, i->first, "/", pending); -#endif + ensurePath(i->first, pending); +} + + +void ensurePath(const Path & path, PathSet pending) +{ + /* If the path is already valid, we're done. */ + if (isValidPath(path)) return; + + /* Otherwise, try the substitutes. */ + Paths subPaths = querySubstitutes(path); + + for (Paths::iterator i = subPaths.begin(); + i != subPaths.end(); i++) + { + try { + normaliseNixExpr(*i, pending); + if (isValidPath(path)) return; + throw Error(format("substitute failed to produce expected output path")); + } catch (Error & e) { + msg(lvlTalkative, + format("building of substitute `%1%' for `%2%' failed: %3%") + % *i % path % e.what()); + } + } + + throw Error(format("path `%1%' is required, " + "but there are no (successful) substitutes") % path); +} + + +NixExpr exprFromPath(const Path & path, PathSet pending) +{ + ensurePath(path, pending); + ATerm t = ATreadFromNamedFile(path.c_str()); + if (!t) throw Error(format("cannot read aterm from `%1%'") % path); + return parseNixExpr(t); } @@ -381,7 +414,7 @@ PathSet nixExprRoots(const Path & nePath) { PathSet paths; - NixExpr ne = parseNixExpr(termFromPath(nePath)); + NixExpr ne = exprFromPath(nePath); if (ne.type == NixExpr::neClosure) paths.insert(ne.closure.roots.begin(), ne.closure.roots.end()); @@ -401,7 +434,7 @@ static void requisitesWorker(const Path & nePath, if (doneSet.find(nePath) != doneSet.end()) return; doneSet.insert(nePath); - NixExpr ne = parseNixExpr(termFromPath(nePath)); + NixExpr ne = exprFromPath(nePath); if (ne.type == NixExpr::neClosure) for (ClosureElems::iterator i = ne.closure.elems.begin(); diff --git a/src/normalise.hh b/src/normalise.hh index e8e72f5bc..bbe846404 100644 --- a/src/normalise.hh +++ b/src/normalise.hh @@ -18,6 +18,14 @@ Path normaliseNixExpr(const Path & nePath, PathSet pending = PathSet()); its output paths through substitutes... kaboom!). */ void realiseClosure(const Path & nePath, PathSet pending = PathSet()); +/* Ensure that a path exists, possibly by instantiating it by + realising a substitute. */ +void ensurePath(const Path & path, PathSet pending = PathSet()); + +/* Read a Nix expression, after ensuring its existence through + ensurePath(). */ +NixExpr exprFromPath(const Path & path, PathSet pending = PathSet()); + /* Get the list of root (output) paths of the given Nix expression. */ PathSet nixExprRoots(const Path & nePath); diff --git a/src/shared.cc b/src/shared.cc index dcda0b50a..80463308a 100644 --- a/src/shared.cc +++ b/src/shared.cc @@ -47,6 +47,8 @@ static void initAndRun(int argc, char * * argv) } +static char buf[1024]; + int main(int argc, char * * argv) { /* ATerm setup. */ @@ -54,7 +56,6 @@ int main(int argc, char * * argv) ATinit(argc, argv, &bottomOfStack); /* Turn on buffering for cerr. */ - char buf[1024]; cerr.rdbuf()->pubsetbuf(buf, sizeof(buf)); try { diff --git a/src/store.cc b/src/store.cc index 7f10c6377..2d223313b 100644 --- a/src/store.cc +++ b/src/store.cc @@ -175,7 +175,8 @@ void registerSuccessor(const Transaction & txn, Paths revs; nixDB.queryStrings(txn, dbSuccessorsRev, sucPath, revs); - revs.push_back(srcPath); + if (find(revs.begin(), revs.end(), srcPath) == revs.end()) + revs.push_back(srcPath); nixDB.setString(txn, dbSuccessors, srcPath, sucPath); nixDB.setStrings(txn, dbSuccessorsRev, sucPath, revs); @@ -212,7 +213,8 @@ void registerSubstitute(const Path & srcPath, const Path & subPath) Paths revs; nixDB.queryStrings(txn, dbSubstitutesRev, subPath, revs); - revs.push_back(srcPath); + if (find(revs.begin(), revs.end(), srcPath) == revs.end()) + revs.push_back(srcPath); nixDB.setStrings(txn, dbSubstitutes, srcPath, subs); nixDB.setStrings(txn, dbSubstitutesRev, subPath, revs); @@ -221,6 +223,14 @@ void registerSubstitute(const Path & srcPath, const Path & subPath) } +Paths querySubstitutes(const Path & srcPath) +{ + Paths subPaths; + nixDB.queryStrings(noTxn, dbSubstitutes, srcPath, subPaths); + return subPaths; +} + + void registerValidPath(const Transaction & txn, const Path & _path) { Path path(canonPath(_path)); diff --git a/src/store.hh b/src/store.hh index 3d7575c3e..dab3d603f 100644 --- a/src/store.hh +++ b/src/store.hh @@ -42,6 +42,9 @@ Paths queryPredecessors(const Path & sucPath); /* Register a substitute. */ void registerSubstitute(const Path & srcPath, const Path & subPath); +/* Return the substitutes expression for the given path. */ +Paths querySubstitutes(const Path & srcPath); + /* Register the validity of a path. */ void registerValidPath(const Transaction & txn, const Path & path); From a0a7a4e0875c2cfdd2895bb1b4a16c998cde576e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 16 Oct 2003 19:24:04 +0000 Subject: [PATCH 0274/6440] * Remove some debug output. --- scripts/nix-pull.in | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index 8cd276801..ffdcf8982 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -125,7 +125,6 @@ while () { my $srcpath = $srcpaths[$i++]; push @subs, $srcpath; push @subs, $subpath; - print "$srcpath $subpath\n"; } waitpid $pid, 0; @@ -134,13 +133,11 @@ $? == 0 or die "fix failed"; # Register all substitutes. print STDERR "registering substitutes...\n"; -print "@subs\n"; system "nix --substitute @subs"; if ($?) { die "`nix --substitute' failed"; } # Register all successors. print STDERR "registering successors...\n"; -print "@sucs\n"; -system "nix --successor -vvvv @sucs"; +system "nix --successor @sucs"; if ($?) { die "`nix --successor' failed"; } From 0eab306466fdb186c692521dd1f2b949e56c54da Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 20 Oct 2003 09:08:44 +0000 Subject: [PATCH 0275/6440] * NarPath -> NarName. --- scripts/nix-push.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/nix-push.in b/scripts/nix-push.in index c0aba5322..2e8158a6a 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -116,7 +116,7 @@ for (my $n = 0; $n < scalar @paths; $n++) { print MANIFEST "{\n"; print MANIFEST " StorePath: $storepath\n"; - print MANIFEST " NarPath: $basename\n"; + print MANIFEST " NarName: $narname\n"; print MANIFEST " MD5: $hash\n"; if ($storepath =~ /\.nix$/) { From 53e376d836133a660223198c7bb8308fb912375e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 20 Oct 2003 09:20:11 +0000 Subject: [PATCH 0276/6440] * Refactored the source tree. --- {boost => src/boost}/Makefile.am | 0 {boost => src/boost}/format.hpp | 0 {boost => src/boost}/format/Makefile.am | 0 {boost => src/boost}/format/exceptions.hpp | 0 {boost => src/boost}/format/feed_args.hpp | 0 {boost => src/boost}/format/format_class.hpp | 0 {boost => src/boost}/format/format_fwd.hpp | 0 {boost => src/boost}/format/format_implementation.cc | 0 {boost => src/boost}/format/free_funcs.cc | 0 {boost => src/boost}/format/group.hpp | 0 {boost => src/boost}/format/internals.hpp | 0 {boost => src/boost}/format/internals_fwd.hpp | 0 {boost => src/boost}/format/macros_default.hpp | 0 {boost => src/boost}/format/parsing.cc | 0 src/{ => fix}/fix.cc | 0 src/{ => libmain}/shared.cc | 0 src/{ => libmain}/shared.hh | 0 src/{ => libnix}/archive.cc | 0 src/{ => libnix}/archive.hh | 0 src/{ => libnix}/db.cc | 0 src/{ => libnix}/db.hh | 0 src/{ => libnix}/exec.cc | 0 src/{ => libnix}/exec.hh | 0 src/{ => libnix}/expr.cc | 0 src/{ => libnix}/expr.hh | 0 src/{ => libnix}/globals.cc | 0 src/{ => libnix}/globals.hh | 0 src/{ => libnix}/hash.cc | 0 src/{ => libnix}/hash.hh | 0 src/{ => libnix}/md5.c | 0 src/{ => libnix}/md5.h | 0 src/{ => libnix}/normalise.cc | 0 src/{ => libnix}/normalise.hh | 0 src/{ => libnix}/pathlocks.cc | 0 src/{ => libnix}/pathlocks.hh | 0 src/{ => libnix}/references.cc | 0 src/{ => libnix}/references.hh | 0 src/{ => libnix}/store.cc | 0 src/{ => libnix}/store.hh | 0 src/{ => libnix}/test-builder-1.sh | 0 src/{ => libnix}/test-builder-2.sh | 0 src/{ => libnix}/test.cc | 0 src/{ => libnix}/util.cc | 0 src/{ => libnix}/util.hh | 0 src/{ => nix-hash}/nix-hash.cc | 0 src/{ => nix}/dotgraph.cc | 0 src/{ => nix}/dotgraph.hh | 0 src/{ => nix}/nix-help.txt | 0 src/{ => nix}/nix.cc | 0 src/test-expr-1.nix | 1 - 50 files changed, 1 deletion(-) rename {boost => src/boost}/Makefile.am (100%) rename {boost => src/boost}/format.hpp (100%) rename {boost => src/boost}/format/Makefile.am (100%) rename {boost => src/boost}/format/exceptions.hpp (100%) rename {boost => src/boost}/format/feed_args.hpp (100%) rename {boost => src/boost}/format/format_class.hpp (100%) rename {boost => src/boost}/format/format_fwd.hpp (100%) rename {boost => src/boost}/format/format_implementation.cc (100%) rename {boost => src/boost}/format/free_funcs.cc (100%) rename {boost => src/boost}/format/group.hpp (100%) rename {boost => src/boost}/format/internals.hpp (100%) rename {boost => src/boost}/format/internals_fwd.hpp (100%) rename {boost => src/boost}/format/macros_default.hpp (100%) rename {boost => src/boost}/format/parsing.cc (100%) rename src/{ => fix}/fix.cc (100%) rename src/{ => libmain}/shared.cc (100%) rename src/{ => libmain}/shared.hh (100%) rename src/{ => libnix}/archive.cc (100%) rename src/{ => libnix}/archive.hh (100%) rename src/{ => libnix}/db.cc (100%) rename src/{ => libnix}/db.hh (100%) rename src/{ => libnix}/exec.cc (100%) rename src/{ => libnix}/exec.hh (100%) rename src/{ => libnix}/expr.cc (100%) rename src/{ => libnix}/expr.hh (100%) rename src/{ => libnix}/globals.cc (100%) rename src/{ => libnix}/globals.hh (100%) rename src/{ => libnix}/hash.cc (100%) rename src/{ => libnix}/hash.hh (100%) rename src/{ => libnix}/md5.c (100%) rename src/{ => libnix}/md5.h (100%) rename src/{ => libnix}/normalise.cc (100%) rename src/{ => libnix}/normalise.hh (100%) rename src/{ => libnix}/pathlocks.cc (100%) rename src/{ => libnix}/pathlocks.hh (100%) rename src/{ => libnix}/references.cc (100%) rename src/{ => libnix}/references.hh (100%) rename src/{ => libnix}/store.cc (100%) rename src/{ => libnix}/store.hh (100%) rename src/{ => libnix}/test-builder-1.sh (100%) rename src/{ => libnix}/test-builder-2.sh (100%) rename src/{ => libnix}/test.cc (100%) rename src/{ => libnix}/util.cc (100%) rename src/{ => libnix}/util.hh (100%) rename src/{ => nix-hash}/nix-hash.cc (100%) rename src/{ => nix}/dotgraph.cc (100%) rename src/{ => nix}/dotgraph.hh (100%) rename src/{ => nix}/nix-help.txt (100%) rename src/{ => nix}/nix.cc (100%) delete mode 100644 src/test-expr-1.nix diff --git a/boost/Makefile.am b/src/boost/Makefile.am similarity index 100% rename from boost/Makefile.am rename to src/boost/Makefile.am diff --git a/boost/format.hpp b/src/boost/format.hpp similarity index 100% rename from boost/format.hpp rename to src/boost/format.hpp diff --git a/boost/format/Makefile.am b/src/boost/format/Makefile.am similarity index 100% rename from boost/format/Makefile.am rename to src/boost/format/Makefile.am diff --git a/boost/format/exceptions.hpp b/src/boost/format/exceptions.hpp similarity index 100% rename from boost/format/exceptions.hpp rename to src/boost/format/exceptions.hpp diff --git a/boost/format/feed_args.hpp b/src/boost/format/feed_args.hpp similarity index 100% rename from boost/format/feed_args.hpp rename to src/boost/format/feed_args.hpp diff --git a/boost/format/format_class.hpp b/src/boost/format/format_class.hpp similarity index 100% rename from boost/format/format_class.hpp rename to src/boost/format/format_class.hpp diff --git a/boost/format/format_fwd.hpp b/src/boost/format/format_fwd.hpp similarity index 100% rename from boost/format/format_fwd.hpp rename to src/boost/format/format_fwd.hpp diff --git a/boost/format/format_implementation.cc b/src/boost/format/format_implementation.cc similarity index 100% rename from boost/format/format_implementation.cc rename to src/boost/format/format_implementation.cc diff --git a/boost/format/free_funcs.cc b/src/boost/format/free_funcs.cc similarity index 100% rename from boost/format/free_funcs.cc rename to src/boost/format/free_funcs.cc diff --git a/boost/format/group.hpp b/src/boost/format/group.hpp similarity index 100% rename from boost/format/group.hpp rename to src/boost/format/group.hpp diff --git a/boost/format/internals.hpp b/src/boost/format/internals.hpp similarity index 100% rename from boost/format/internals.hpp rename to src/boost/format/internals.hpp diff --git a/boost/format/internals_fwd.hpp b/src/boost/format/internals_fwd.hpp similarity index 100% rename from boost/format/internals_fwd.hpp rename to src/boost/format/internals_fwd.hpp diff --git a/boost/format/macros_default.hpp b/src/boost/format/macros_default.hpp similarity index 100% rename from boost/format/macros_default.hpp rename to src/boost/format/macros_default.hpp diff --git a/boost/format/parsing.cc b/src/boost/format/parsing.cc similarity index 100% rename from boost/format/parsing.cc rename to src/boost/format/parsing.cc diff --git a/src/fix.cc b/src/fix/fix.cc similarity index 100% rename from src/fix.cc rename to src/fix/fix.cc diff --git a/src/shared.cc b/src/libmain/shared.cc similarity index 100% rename from src/shared.cc rename to src/libmain/shared.cc diff --git a/src/shared.hh b/src/libmain/shared.hh similarity index 100% rename from src/shared.hh rename to src/libmain/shared.hh diff --git a/src/archive.cc b/src/libnix/archive.cc similarity index 100% rename from src/archive.cc rename to src/libnix/archive.cc diff --git a/src/archive.hh b/src/libnix/archive.hh similarity index 100% rename from src/archive.hh rename to src/libnix/archive.hh diff --git a/src/db.cc b/src/libnix/db.cc similarity index 100% rename from src/db.cc rename to src/libnix/db.cc diff --git a/src/db.hh b/src/libnix/db.hh similarity index 100% rename from src/db.hh rename to src/libnix/db.hh diff --git a/src/exec.cc b/src/libnix/exec.cc similarity index 100% rename from src/exec.cc rename to src/libnix/exec.cc diff --git a/src/exec.hh b/src/libnix/exec.hh similarity index 100% rename from src/exec.hh rename to src/libnix/exec.hh diff --git a/src/expr.cc b/src/libnix/expr.cc similarity index 100% rename from src/expr.cc rename to src/libnix/expr.cc diff --git a/src/expr.hh b/src/libnix/expr.hh similarity index 100% rename from src/expr.hh rename to src/libnix/expr.hh diff --git a/src/globals.cc b/src/libnix/globals.cc similarity index 100% rename from src/globals.cc rename to src/libnix/globals.cc diff --git a/src/globals.hh b/src/libnix/globals.hh similarity index 100% rename from src/globals.hh rename to src/libnix/globals.hh diff --git a/src/hash.cc b/src/libnix/hash.cc similarity index 100% rename from src/hash.cc rename to src/libnix/hash.cc diff --git a/src/hash.hh b/src/libnix/hash.hh similarity index 100% rename from src/hash.hh rename to src/libnix/hash.hh diff --git a/src/md5.c b/src/libnix/md5.c similarity index 100% rename from src/md5.c rename to src/libnix/md5.c diff --git a/src/md5.h b/src/libnix/md5.h similarity index 100% rename from src/md5.h rename to src/libnix/md5.h diff --git a/src/normalise.cc b/src/libnix/normalise.cc similarity index 100% rename from src/normalise.cc rename to src/libnix/normalise.cc diff --git a/src/normalise.hh b/src/libnix/normalise.hh similarity index 100% rename from src/normalise.hh rename to src/libnix/normalise.hh diff --git a/src/pathlocks.cc b/src/libnix/pathlocks.cc similarity index 100% rename from src/pathlocks.cc rename to src/libnix/pathlocks.cc diff --git a/src/pathlocks.hh b/src/libnix/pathlocks.hh similarity index 100% rename from src/pathlocks.hh rename to src/libnix/pathlocks.hh diff --git a/src/references.cc b/src/libnix/references.cc similarity index 100% rename from src/references.cc rename to src/libnix/references.cc diff --git a/src/references.hh b/src/libnix/references.hh similarity index 100% rename from src/references.hh rename to src/libnix/references.hh diff --git a/src/store.cc b/src/libnix/store.cc similarity index 100% rename from src/store.cc rename to src/libnix/store.cc diff --git a/src/store.hh b/src/libnix/store.hh similarity index 100% rename from src/store.hh rename to src/libnix/store.hh diff --git a/src/test-builder-1.sh b/src/libnix/test-builder-1.sh similarity index 100% rename from src/test-builder-1.sh rename to src/libnix/test-builder-1.sh diff --git a/src/test-builder-2.sh b/src/libnix/test-builder-2.sh similarity index 100% rename from src/test-builder-2.sh rename to src/libnix/test-builder-2.sh diff --git a/src/test.cc b/src/libnix/test.cc similarity index 100% rename from src/test.cc rename to src/libnix/test.cc diff --git a/src/util.cc b/src/libnix/util.cc similarity index 100% rename from src/util.cc rename to src/libnix/util.cc diff --git a/src/util.hh b/src/libnix/util.hh similarity index 100% rename from src/util.hh rename to src/libnix/util.hh diff --git a/src/nix-hash.cc b/src/nix-hash/nix-hash.cc similarity index 100% rename from src/nix-hash.cc rename to src/nix-hash/nix-hash.cc diff --git a/src/dotgraph.cc b/src/nix/dotgraph.cc similarity index 100% rename from src/dotgraph.cc rename to src/nix/dotgraph.cc diff --git a/src/dotgraph.hh b/src/nix/dotgraph.hh similarity index 100% rename from src/dotgraph.hh rename to src/nix/dotgraph.hh diff --git a/src/nix-help.txt b/src/nix/nix-help.txt similarity index 100% rename from src/nix-help.txt rename to src/nix/nix-help.txt diff --git a/src/nix.cc b/src/nix/nix.cc similarity index 100% rename from src/nix.cc rename to src/nix/nix.cc diff --git a/src/test-expr-1.nix b/src/test-expr-1.nix deleted file mode 100644 index a94ee7382..000000000 --- a/src/test-expr-1.nix +++ /dev/null @@ -1 +0,0 @@ -Str("Hello World") From c62433751d5fce8c25a802989c50fee993f39c2d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 20 Oct 2003 10:05:01 +0000 Subject: [PATCH 0277/6440] * Finished refactoring the tree. --- Makefile.am | 2 +- configure.ac | 19 ++++++++---- src/Makefile.am | 56 +----------------------------------- src/boost/format/Makefile.am | 2 ++ src/fix/Makefile.am | 8 ++++++ src/fix/fix.cc | 8 +++--- src/libmain/Makefile.am | 12 ++++++++ src/libnix/Makefile.am | 9 ++++++ src/nix-hash/Makefile.am | 8 ++++++ src/nix/Makefile.am | 27 +++++++++++++++++ 10 files changed, 86 insertions(+), 65 deletions(-) create mode 100644 src/fix/Makefile.am create mode 100644 src/libmain/Makefile.am create mode 100644 src/libnix/Makefile.am create mode 100644 src/nix-hash/Makefile.am create mode 100644 src/nix/Makefile.am diff --git a/Makefile.am b/Makefile.am index 5996b3ee9..f069e454a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,3 @@ -SUBDIRS = externals boost src scripts corepkgs doc +SUBDIRS = externals src scripts corepkgs doc EXTRA_DIST = substitute.mk diff --git a/configure.ac b/configure.ac index 8c878f2e3..06054cf00 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_INIT(nix, "0.4") -AC_CONFIG_SRCDIR(src/nix.cc) +AC_CONFIG_SRCDIR(README) AC_CONFIG_AUX_DIR(config) AM_INIT_AUTOMAKE @@ -23,12 +23,21 @@ AC_PATH_PROG(wget, wget) AC_CHECK_LIB(pthread, pthread_mutex_init) AM_CONFIG_HEADER([config.h]) -AC_CONFIG_FILES([Makefile +AC_CONFIG_FILES([Makefile externals/Makefile - boost/Makefile boost/format/Makefile src/Makefile + src/boost/Makefile + src/boost/format/Makefile + src/libnix/Makefile + src/libmain/Makefile + src/nix/Makefile + src/nix-hash/Makefile + src/fix/Makefile scripts/Makefile - corepkgs/Makefile corepkgs/fetchurl/Makefile corepkgs/nar/Makefile - doc/Makefile doc/manual/Makefile + corepkgs/Makefile + corepkgs/fetchurl/Makefile + corepkgs/nar/Makefile + doc/Makefile + doc/manual/Makefile ]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index 216d664e8..200ea45fb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,55 +1 @@ -bin_PROGRAMS = nix nix-hash fix -check_PROGRAMS = test - - -AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. -I../externals/inst/include $(CXXFLAGS) -LDADD = -L../externals/inst/lib -ldb_cxx -lATerm -L../boost/format -lformat - -nix_SOURCES = nix.cc dotgraph.cc -nix_LDADD = libshared.a libnix.a $(LDADD) - -nix_hash_SOURCES = nix-hash.cc -nix_hash_LDADD = libshared.a libnix.a $(LDADD) - -fix_SOURCES = fix.cc -fix_LDADD = libshared.a libnix.a $(LDADD) - -TESTS = test - -test_SOURCES = test.cc -test_LDADD = libshared.a libnix.a $(LDADD) - - -noinst_LIBRARIES = libnix.a libshared.a - -libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c \ - store.cc expr.cc normalise.cc exec.cc \ - globals.cc db.cc references.cc pathlocks.cc - -libshared_a_SOURCES = shared.cc - -libshared_a_CXXFLAGS = \ - -DNIX_STORE_DIR=\"$(prefix)/store\" \ - -DNIX_DATA_DIR=\"$(datadir)\" \ - -DNIX_STATE_DIR=\"$(localstatedir)/nix\" \ - -DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \ - $(AM_CXXFLAGS) - -nix.o: nix-help.txt.hh - -%.hh: % - echo -n '"' > $@ - sed 's|\(.*\)|\1\\n\\|' < $< >> $@ - echo '"' >> $@ - -install-data-local: - $(INSTALL) -d $(localstatedir)/nix - $(INSTALL) -d $(localstatedir)/nix/db - $(INSTALL) -d $(localstatedir)/nix/links - rm -f $(prefix)/current - ln -sf $(localstatedir)/nix/links/current $(prefix)/current - $(INSTALL) -d $(localstatedir)/log/nix - $(INSTALL) -d $(prefix)/store - $(bindir)/nix --init - -EXTRA_DIST = *.hh *.h test-builder-*.sh +SUBDIRS = boost libnix libmain nix nix-hash fix diff --git a/src/boost/format/Makefile.am b/src/boost/format/Makefile.am index 5badba27c..bae92c198 100644 --- a/src/boost/format/Makefile.am +++ b/src/boost/format/Makefile.am @@ -2,5 +2,7 @@ noinst_LIBRARIES = libformat.a libformat_a_SOURCES = format_implementation.cc free_funcs.cc parsing.cc +AM_CXXFLAGS = -Wall -I../.. + EXTRA_DIST = exceptions.hpp feed_args.hpp format_class.hpp format_fwd.hpp \ group.hpp internals.hpp internals_fwd.hpp macros_default.hpp diff --git a/src/fix/Makefile.am b/src/fix/Makefile.am new file mode 100644 index 000000000..4786db54c --- /dev/null +++ b/src/fix/Makefile.am @@ -0,0 +1,8 @@ +bin_PROGRAMS = fix + +fix_SOURCES = fix.cc +fix_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../boost/format/libformat.a \ + -L../../externals/inst/lib -ldb_cxx -lATerm + +AM_CXXFLAGS = \ + -I.. -I../../externals/inst/include -I../libnix -I../libmain diff --git a/src/fix/fix.cc b/src/fix/fix.cc index c1f9c1ad6..9a8ff1513 100644 --- a/src/fix/fix.cc +++ b/src/fix/fix.cc @@ -249,7 +249,7 @@ static Expr evalExpr2(EvalState & state, Expr e) /* Platform constant. */ if (ATmatch(e, "Platform")) { - return ATmake("", SYSTEM); + return ATmake("", thisSystem.c_str()); } /* Fix inclusion. */ @@ -284,7 +284,7 @@ static Expr evalExpr2(EvalState & state, Expr e) /* Evaluate the bindings and put them in a map. */ map bndMap; - bndMap["platform"] = ATmake("", SYSTEM); + bndMap["platform"] = ATmake("", thisSystem.c_str()); while (!ATisEmpty(bnds)) { ATerm bnd = ATgetFirst(bnds); if (!ATmatch(bnd, "(, )", &s1, &e1)) @@ -297,7 +297,7 @@ static Expr evalExpr2(EvalState & state, Expr e) expression. */ NixExpr ne; ne.type = NixExpr::neDerivation; - ne.derivation.platform = SYSTEM; + ne.derivation.platform = thisSystem; string name; Path outPath; Hash outHash; @@ -320,7 +320,7 @@ static Expr evalExpr2(EvalState & state, Expr e) ne.derivation.args.push_back(processBinding(state, arg, ne)); args = ATgetNext(args); } - } + } else { string s = processBinding(state, value, ne); diff --git a/src/libmain/Makefile.am b/src/libmain/Makefile.am new file mode 100644 index 000000000..aebb1286e --- /dev/null +++ b/src/libmain/Makefile.am @@ -0,0 +1,12 @@ +noinst_LIBRARIES = libmain.a + +libmain_a_SOURCES = shared.cc + +AM_CXXFLAGS = \ + -DNIX_STORE_DIR=\"$(prefix)/store\" \ + -DNIX_DATA_DIR=\"$(datadir)\" \ + -DNIX_STATE_DIR=\"$(localstatedir)/nix\" \ + -DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \ + -I.. -I../../externals/inst/include -I../libnix + +EXTRA_DIST = *.hh diff --git a/src/libnix/Makefile.am b/src/libnix/Makefile.am new file mode 100644 index 000000000..b890ba8c0 --- /dev/null +++ b/src/libnix/Makefile.am @@ -0,0 +1,9 @@ +noinst_LIBRARIES = libnix.a + +libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c \ + store.cc expr.cc normalise.cc exec.cc \ + globals.cc db.cc references.cc pathlocks.cc + +AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. -I../../externals/inst/include + +EXTRA_DIST = *.hh *.h test-builder-*.sh diff --git a/src/nix-hash/Makefile.am b/src/nix-hash/Makefile.am new file mode 100644 index 000000000..7fb428a29 --- /dev/null +++ b/src/nix-hash/Makefile.am @@ -0,0 +1,8 @@ +bin_PROGRAMS = nix-hash + +nix_hash_SOURCES = nix-hash.cc +nix_hash_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../boost/format/libformat.a \ + -L../../externals/inst/lib -ldb_cxx -lATerm + +AM_CXXFLAGS = \ + -I.. -I../../externals/inst/include -I../libnix -I../libmain diff --git a/src/nix/Makefile.am b/src/nix/Makefile.am new file mode 100644 index 000000000..37415976b --- /dev/null +++ b/src/nix/Makefile.am @@ -0,0 +1,27 @@ +bin_PROGRAMS = nix + +nix_SOURCES = nix.cc dotgraph.cc +nix_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../boost/format/libformat.a \ + -L../../externals/inst/lib -ldb_cxx -lATerm + +nix.o: nix-help.txt.hh + +%.hh: % + echo -n '"' > $@ + sed 's|\(.*\)|\1\\n\\|' < $< >> $@ + echo '"' >> $@ + +AM_CXXFLAGS = \ + -I.. -I../../externals/inst/include -I../libnix -I../libmain + +install-data-local: + $(INSTALL) -d $(localstatedir)/nix + $(INSTALL) -d $(localstatedir)/nix/db + $(INSTALL) -d $(localstatedir)/nix/links + rm -f $(prefix)/current + ln -sf $(localstatedir)/nix/links/current $(prefix)/current + $(INSTALL) -d $(localstatedir)/log/nix + $(INSTALL) -d $(prefix)/store + $(bindir)/nix --init + +EXTRA_DIST = *.hh From 4a8948b7a60e751dd809f279f1baa434ea09a4d3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 22 Oct 2003 10:48:22 +0000 Subject: [PATCH 0278/6440] * Some wrapper classes to ensure that file descriptors / directory handles are closed when they go out of scope. --- src/libnix/archive.cc | 12 ++---- src/libnix/exec.cc | 25 ----------- src/libnix/references.cc | 8 +--- src/libnix/store.cc | 4 +- src/libnix/util.cc | 93 ++++++++++++++++++++++++++++++++++------ src/libnix/util.hh | 37 ++++++++++++++++ 6 files changed, 123 insertions(+), 56 deletions(-) diff --git a/src/libnix/archive.cc b/src/libnix/archive.cc index 9039ad7db..ed57df4c9 100644 --- a/src/libnix/archive.cc +++ b/src/libnix/archive.cc @@ -51,7 +51,7 @@ static void dump(const string & path, DumpSink & sink); static void dumpEntries(const Path & path, DumpSink & sink) { - DIR * dir = opendir(path.c_str()); + AutoCloseDir dir = opendir(path.c_str()); if (!dir) throw SysError("opening directory " + path); vector names; @@ -77,8 +77,6 @@ static void dumpEntries(const Path & path, DumpSink & sink) dump(path + "/" + *it, sink); writeString(")", sink); } - - closedir(dir); /* !!! close on exception */ } @@ -88,7 +86,7 @@ static void dumpContents(const Path & path, unsigned int size, writeString("contents", sink); writeInt(size, sink); - int fd = open(path.c_str(), O_RDONLY); + AutoCloseFD fd = open(path.c_str(), O_RDONLY); if (fd == -1) throw SysError(format("opening file `%1%'") % path); unsigned char buf[65536]; @@ -105,8 +103,6 @@ static void dumpContents(const Path & path, unsigned int size, throw SysError("file changed while reading it: " + path); writePadding(size, sink); - - close(fd); /* !!! close on exception */ } @@ -262,7 +258,7 @@ static void restore(const Path & path, RestoreSource & source) if (s != "(") throw badArchive("expected open tag"); enum { tpUnknown, tpRegular, tpDirectory, tpSymlink } type = tpUnknown; - int fd = -1; /* !!! close on exception */ + AutoCloseFD fd; while (1) { s = readString(source); @@ -326,8 +322,6 @@ static void restore(const Path & path, RestoreSource & source) } } - - if (fd != -1) close(fd); } diff --git a/src/libnix/exec.cc b/src/libnix/exec.cc index 2e092b2e0..a51b605d8 100644 --- a/src/libnix/exec.cc +++ b/src/libnix/exec.cc @@ -11,29 +11,6 @@ #include "globals.hh" -class AutoDelete -{ - string path; - bool del; -public: - - AutoDelete(const string & p) : path(p) - { - del = true; - } - - ~AutoDelete() - { - if (del) deletePath(path); - } - - void cancel() - { - del = false; - } -}; - - static string pathNullDevice = "/dev/null"; @@ -140,5 +117,3 @@ void runProgram(const string & program, throw Error("unable to build package"); } } - - diff --git a/src/libnix/references.cc b/src/libnix/references.cc index be432665b..ab743f76d 100644 --- a/src/libnix/references.cc +++ b/src/libnix/references.cc @@ -36,7 +36,7 @@ void checkPath(const string & path, throw SysError(format("getting attributes of path `%1%'") % path); if (S_ISDIR(st.st_mode)) { - DIR * dir = opendir(path.c_str()); + AutoCloseDir dir = opendir(path.c_str()); struct dirent * dirent; while (errno = 0, dirent = readdir(dir)) { @@ -45,15 +45,13 @@ void checkPath(const string & path, search(name, ids, seen); checkPath(path + "/" + name, ids, seen); } - - closedir(dir); /* !!! close on exception */ } else if (S_ISREG(st.st_mode)) { debug(format("checking `%1%'") % path); - int fd = open(path.c_str(), O_RDONLY); + AutoCloseFD fd = open(path.c_str(), O_RDONLY); if (fd == -1) throw SysError(format("opening file `%1%'") % path); unsigned char * buf = new unsigned char[st.st_size]; @@ -63,8 +61,6 @@ void checkPath(const string & path, search(string((char *) buf, st.st_size), ids, seen); delete buf; /* !!! autodelete */ - - close(fd); /* !!! close on exception */ } else if (S_ISLNK(st.st_mode)) { diff --git a/src/libnix/store.cc b/src/libnix/store.cc index 2d223313b..c40d9efbe 100644 --- a/src/libnix/store.cc +++ b/src/libnix/store.cc @@ -304,14 +304,12 @@ void addTextToStore(const Path & dstPath, const string & s) /* !!! locking? -> parallel writes are probably idempotent */ - int fd = open(dstPath.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666); + AutoCloseFD fd = open(dstPath.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666); if (fd == -1) throw SysError(format("creating store file `%1%'") % dstPath); if (write(fd, s.c_str(), s.size()) != (ssize_t) s.size()) throw SysError(format("writing store file `%1%'") % dstPath); - close(fd); /* !!! close on exception */ - Transaction txn(nixDB); registerValidPath(txn, dstPath); txn.commit(); diff --git a/src/libnix/util.cc b/src/libnix/util.cc index c1d0fedea..016ee991a 100644 --- a/src/libnix/util.cc +++ b/src/libnix/util.cc @@ -118,17 +118,17 @@ void deletePath(const Path & path) if (S_ISDIR(st.st_mode)) { Strings names; + + { + AutoCloseDir dir = opendir(path.c_str()); - DIR * dir = opendir(path.c_str()); - - struct dirent * dirent; - while (errno = 0, dirent = readdir(dir)) { - string name = dirent->d_name; - if (name == "." || name == "..") continue; - names.push_back(name); - } - - closedir(dir); /* !!! close on exception */ + struct dirent * dirent; + while (errno = 0, dirent = readdir(dir)) { + string name = dirent->d_name; + if (name == "." || name == "..") continue; + names.push_back(name); + } + } /* scoped to ensure that dir is closed at this point */ /* Make the directory writable. */ if (!(st.st_mode & S_IWUSR)) { @@ -157,7 +157,7 @@ void makePathReadOnly(const Path & path) } if (S_ISDIR(st.st_mode)) { - DIR * dir = opendir(path.c_str()); + AutoCloseDir dir = opendir(path.c_str()); struct dirent * dirent; while (errno = 0, dirent = readdir(dir)) { @@ -165,8 +165,6 @@ void makePathReadOnly(const Path & path) if (name == "." || name == "..") continue; makePathReadOnly(path + "/" + name); } - - closedir(dir); /* !!! close on exception */ } } @@ -251,3 +249,72 @@ void writeFull(int fd, const unsigned char * buf, size_t count) buf += res; } } + + +AutoDelete::AutoDelete(const string & p) : path(p) +{ + del = true; +} + +AutoDelete::~AutoDelete() +{ + if (del) deletePath(path); +} + +void AutoDelete::cancel() +{ + del = false; +} + + +AutoCloseFD::AutoCloseFD() +{ + fd = -1; +} + +AutoCloseFD::AutoCloseFD(int fd) +{ + this->fd = fd; +} + +AutoCloseFD::~AutoCloseFD() +{ + if (fd != -1) close(fd); +} + +void AutoCloseFD::operator =(int fd) +{ + this->fd = fd; +} + +AutoCloseFD::operator int() +{ + return fd; +} + + +AutoCloseDir::AutoCloseDir() +{ + dir = 0; +} + +AutoCloseDir::AutoCloseDir(DIR * dir) +{ + this->dir = dir; +} + +AutoCloseDir::~AutoCloseDir() +{ + if (dir) closedir(dir); +} + +void AutoCloseDir::operator =(DIR * dir) +{ + this->dir = dir; +} + +AutoCloseDir::operator DIR *() +{ + return dir; +} + diff --git a/src/libnix/util.hh b/src/libnix/util.hh index 016289176..02a9b7fcb 100644 --- a/src/libnix/util.hh +++ b/src/libnix/util.hh @@ -6,6 +6,8 @@ #include #include +#include +#include #include #include @@ -113,4 +115,39 @@ void readFull(int fd, unsigned char * buf, size_t count); void writeFull(int fd, const unsigned char * buf, size_t count); +/* Automatic cleanup of resources. */ + +class AutoDelete +{ + string path; + bool del; +public: + AutoDelete(const string & p); + ~AutoDelete(); + void cancel(); +}; + +class AutoCloseFD +{ + int fd; +public: + AutoCloseFD(); + AutoCloseFD(int fd); + ~AutoCloseFD(); + void operator =(int fd); + operator int(); +}; + +class AutoCloseDir +{ + DIR * dir; +public: + AutoCloseDir(); + AutoCloseDir(DIR * dir); + ~AutoCloseDir(); + void operator =(DIR * dir); + operator DIR *(); +}; + + #endif /* !__UTIL_H */ From 143427f90b9b54bd957cd50a2110157ddfedeeaf Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 22 Oct 2003 10:53:46 +0000 Subject: [PATCH 0279/6440] * Dead code removal. --- src/libnix/normalise.cc | 94 ----------------------------------------- 1 file changed, 94 deletions(-) diff --git a/src/libnix/normalise.cc b/src/libnix/normalise.cc index be71081ff..196fcad2e 100644 --- a/src/libnix/normalise.cc +++ b/src/libnix/normalise.cc @@ -18,100 +18,6 @@ static Path useSuccessor(const Path & path) } -#if 0 -/* Return a path whose contents have the given hash. If target is - not empty, ensure that such a path is realised in target (if - necessary by copying from another location). If prefix is not - empty, only return a path that is an descendent of prefix. */ - -string expandId(const FSId & id, const string & target = "", - const string & prefix = "/", FSIdSet pending = FSIdSet(), - bool ignoreSubstitutes = false) -{ - xxx -} - - -string expandId(const FSId & id, const string & target, - const string & prefix, FSIdSet pending, bool ignoreSubstitutes) -{ - Nest nest(lvlDebug, format("expanding %1%") % (string) id); - - Strings paths; - - if (!target.empty() && !isInPrefix(target, prefix)) - abort(); - - nixDB.queryStrings(noTxn, dbId2Paths, id, paths); - - /* Pick one equal to `target'. */ - if (!target.empty()) { - - for (Strings::iterator i = paths.begin(); - i != paths.end(); i++) - { - string path = *i; - if (path == target && pathExists(path)) - return path; - } - - } - - /* Arbitrarily pick the first one that exists and isn't stale. */ - for (Strings::iterator it = paths.begin(); - it != paths.end(); it++) - { - string path = *it; - if (isInPrefix(path, prefix) && pathExists(path)) { - if (target.empty()) - return path; - else { - /* Acquire a lock on the target path. */ - Strings lockPaths; - lockPaths.push_back(target); - PathLocks outputLock(lockPaths); - - /* Copy. */ - copyPath(path, target); - - /* Register the target path. */ - Transaction txn(nixDB); - registerPath(txn, target, id); - txn.commit(); - - return target; - } - } - } - - if (!ignoreSubstitutes) { - - if (pending.find(id) != pending.end()) - throw Error(format("id %1% already being expanded") % (string) id); - pending.insert(id); - - /* Try to realise the substitutes, but only if this id is not - already being realised by a substitute. */ - Strings subs; - nixDB.queryStrings(noTxn, dbSubstitutes, id, subs); /* non-existence = ok */ - - for (Strings::iterator it = subs.begin(); it != subs.end(); it++) { - FSId subId = parseHash(*it); - - debug(format("trying substitute %1%") % (string) subId); - - realiseClosure(normaliseNixExpr(subId, pending), pending); - - return expandId(id, target, prefix, pending); - } - - } - - throw Error(format("cannot expand id `%1%'") % (string) id); -} -#endif - - Path normaliseNixExpr(const Path & _nePath, PathSet pending) { Nest nest(lvlTalkative, From 9d95aafe8ccf9d037dc97bb875bc62b919d8b123 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 22 Oct 2003 11:04:57 +0000 Subject: [PATCH 0280/6440] * Ad hoc per-package logging. When Nix performs a derivation, it now writes stdout/stderr of the builder to ${prefix}/var/log/nix/x, where x is the file name of the derivation expression, e.g., /nix/var/log/nix/54256391624be04fcb426048ae3ea0a4-d-pan-0.14.2.nix Note that consecutive builds of the same expression overwrite, rather than append to, existing log files. --- src/libnix/exec.cc | 8 ++++---- src/libnix/exec.hh | 3 ++- src/libnix/normalise.cc | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libnix/exec.cc b/src/libnix/exec.cc index a51b605d8..4934712f9 100644 --- a/src/libnix/exec.cc +++ b/src/libnix/exec.cc @@ -16,14 +16,14 @@ static string pathNullDevice = "/dev/null"; /* Run a program. */ void runProgram(const string & program, - const Strings & args, const Environment & env) + const Strings & args, const Environment & env, + const string & logFileName) { /* Create a log file. */ - string logFileName = nixLogDir + "/run.log"; string logCommand = verbosity >= lvlDebug - ? "tee -a " + logFileName + " >&2" - : "cat >> " + logFileName; + ? "tee " + logFileName + " >&2" + : "cat > " + logFileName; /* !!! auto-pclose on exit */ FILE * logFile = popen(logCommand.c_str(), "w"); /* !!! escaping */ if (!logFile) diff --git a/src/libnix/exec.hh b/src/libnix/exec.hh index 8d410e404..fc5bd6ac8 100644 --- a/src/libnix/exec.hh +++ b/src/libnix/exec.hh @@ -15,7 +15,8 @@ typedef map Environment; /* Run a program. */ void runProgram(const string & program, - const Strings & args, const Environment & env); + const Strings & args, const Environment & env, + const string & logFileName); #endif /* !__EXEC_H */ diff --git a/src/libnix/normalise.cc b/src/libnix/normalise.cc index 196fcad2e..0ce38d68a 100644 --- a/src/libnix/normalise.cc +++ b/src/libnix/normalise.cc @@ -164,7 +164,8 @@ Path normaliseNixExpr(const Path & _nePath, PathSet pending) /* Run the builder. */ msg(lvlChatty, format("building...")); - runProgram(ne.derivation.builder, ne.derivation.args, env); + runProgram(ne.derivation.builder, ne.derivation.args, env, + nixLogDir + "/" + baseNameOf(nePath)); msg(lvlChatty, format("build completed")); } else From c4e7d324b826d1197a90bf7f4f4b593e406e1087 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 22 Oct 2003 13:29:40 +0000 Subject: [PATCH 0281/6440] * Use writeFull(). --- src/libnix/store.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libnix/store.cc b/src/libnix/store.cc index c40d9efbe..e32f403b6 100644 --- a/src/libnix/store.cc +++ b/src/libnix/store.cc @@ -307,8 +307,7 @@ void addTextToStore(const Path & dstPath, const string & s) AutoCloseFD fd = open(dstPath.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666); if (fd == -1) throw SysError(format("creating store file `%1%'") % dstPath); - if (write(fd, s.c_str(), s.size()) != (ssize_t) s.size()) - throw SysError(format("writing store file `%1%'") % dstPath); + writeFull(fd, (unsigned char *) s.c_str(), s.size()); Transaction txn(nixDB); registerValidPath(txn, dstPath); From 92eea8fc4e7a2e4d6d0dda604ecd22c60367b76e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 23 Oct 2003 10:51:55 +0000 Subject: [PATCH 0282/6440] * Fix a race condition in addTextToStore(). --- src/libnix/store.cc | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/libnix/store.cc b/src/libnix/store.cc index e32f403b6..c83316cf6 100644 --- a/src/libnix/store.cc +++ b/src/libnix/store.cc @@ -302,16 +302,21 @@ void addTextToStore(const Path & dstPath, const string & s) { if (!isValidPath(dstPath)) { - /* !!! locking? -> parallel writes are probably idempotent */ + PathSet lockPaths; + lockPaths.insert(dstPath); + PathLocks outputLock(lockPaths); - AutoCloseFD fd = open(dstPath.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666); - if (fd == -1) throw SysError(format("creating store file `%1%'") % dstPath); + if (!isValidPath(dstPath)) { - writeFull(fd, (unsigned char *) s.c_str(), s.size()); + AutoCloseFD fd = open(dstPath.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd == -1) throw SysError(format("creating store file `%1%'") % dstPath); - Transaction txn(nixDB); - registerValidPath(txn, dstPath); - txn.commit(); + writeFull(fd, (unsigned char *) s.c_str(), s.size()); + + Transaction txn(nixDB); + registerValidPath(txn, dstPath); + txn.commit(); + } } } From 7102455cba5ceb13e7f3558716ee0a49fff1c58f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 27 Oct 2003 18:43:09 +0000 Subject: [PATCH 0283/6440] * Don't cache the manifest. --- scripts/nix-pull.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index ffdcf8982..40e7d62f3 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -31,7 +31,7 @@ while () { print "obtaining list of Nix archives at $url...\n"; - system "wget '$url'/MANIFEST -O '$manifest' 2> /dev/null"; # !!! escape + system "wget --cache=off '$url'/MANIFEST -O '$manifest' 2> /dev/null"; # !!! escape if ($?) { die "`wget' failed"; } open MANIFEST, "<$manifest"; From f31661a3b593a15fe061bb398f1814d9c37902a6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 29 Oct 2003 15:04:50 +0000 Subject: [PATCH 0284/6440] * Add sdf2-bundle to externals. --- externals/Makefile.am | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/externals/Makefile.am b/externals/Makefile.am index 46b1a9fdb..c644c0569 100644 --- a/externals/Makefile.am +++ b/externals/Makefile.am @@ -53,6 +53,33 @@ build-aterm: have-aterm touch build-aterm -all: build-db build-aterm +# SDF bundle -EXTRA_DIST = $(DB).tar.gz $(ATERM).tar.gz +SDF2 = sdf2-bundle-1.6 + +$(SDF2).tar.gz: + @echo "Nix requires the SDF2 bundle to build." + @echo "Please download version 1.6 from" + @echo " ftp://ftp.stratego-language.org/pub/stratego/sdf2/sdf2-bundle-1.6.tar.gzP" + @echo "and place it in the externals/ directory." + false + +$(SDF2): $(SDF2).tar.gz + gunzip < $(SDF2).tar.gz | tar xvf - + +have-sdf2: + $(MAKE) $(SDF2) + touch have-sdf2 + +build-sdf2: have-sdf2 + (pfx=`pwd` && \ + cd $(SDF2) && \ + ./configure --prefix=$$pfx/inst && \ + make && \ + make install) + touch build-sdf2 + + +all: build-db build-aterm build-sdf2 + +EXTRA_DIST = $(DB).tar.gz $(ATERM).tar.gz $(SDF2).tar.gz From 4d728f6a36c83ff684426788df775b385fae9e88 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 29 Oct 2003 15:05:18 +0000 Subject: [PATCH 0285/6440] * Forked new version of Fix. --- configure.ac | 1 + src/Makefile.am | 2 +- src/fix-ng/Makefile.am | 8 + src/fix-ng/fix.cc | 488 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 498 insertions(+), 1 deletion(-) create mode 100644 src/fix-ng/Makefile.am create mode 100644 src/fix-ng/fix.cc diff --git a/configure.ac b/configure.ac index 06054cf00..54db20317 100644 --- a/configure.ac +++ b/configure.ac @@ -33,6 +33,7 @@ AC_CONFIG_FILES([Makefile src/nix/Makefile src/nix-hash/Makefile src/fix/Makefile + src/fix-ng/Makefile scripts/Makefile corepkgs/Makefile corepkgs/fetchurl/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 200ea45fb..57af4dbc8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1 +1 @@ -SUBDIRS = boost libnix libmain nix nix-hash fix +SUBDIRS = boost libnix libmain nix nix-hash fix fix-ng diff --git a/src/fix-ng/Makefile.am b/src/fix-ng/Makefile.am new file mode 100644 index 000000000..64e8b2ed3 --- /dev/null +++ b/src/fix-ng/Makefile.am @@ -0,0 +1,8 @@ +bin_PROGRAMS = fix-ng + +fix_ng_SOURCES = fix.cc +fix_ng_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../boost/format/libformat.a \ + -L../../externals/inst/lib -ldb_cxx -lATerm + +AM_CXXFLAGS = \ + -I.. -I../../externals/inst/include -I../libnix -I../libmain diff --git a/src/fix-ng/fix.cc b/src/fix-ng/fix.cc new file mode 100644 index 000000000..9a8ff1513 --- /dev/null +++ b/src/fix-ng/fix.cc @@ -0,0 +1,488 @@ +#include +#include + +#include "globals.hh" +#include "normalise.hh" +#include "shared.hh" + + +typedef ATerm Expr; + +typedef map NormalForms; +typedef map PkgPaths; +typedef map PkgHashes; + +struct EvalState +{ + Paths searchDirs; + NormalForms normalForms; + PkgPaths pkgPaths; + PkgHashes pkgHashes; /* normalised package hashes */ + Expr blackHole; + + EvalState() + { + blackHole = ATmake("BlackHole()"); + if (!blackHole) throw Error("cannot build black hole"); + } +}; + + +static Expr evalFile(EvalState & state, const Path & path); +static Expr evalExpr(EvalState & state, Expr e); + + +static Path searchPath(const Paths & searchDirs, const Path & relPath) +{ + if (string(relPath, 0, 1) == "/") return relPath; + + for (Paths::const_iterator i = searchDirs.begin(); + i != searchDirs.end(); i++) + { + Path path = *i + "/" + relPath; + if (pathExists(path)) return path; + } + + throw Error( + format("path `%1%' not found in any of the search directories") + % relPath); +} + + +static Expr substExpr(string x, Expr rep, Expr e) +{ + char * s; + Expr e2; + + if (ATmatch(e, "Var()", &s)) + if (x == s) + return rep; + else + return e; + + ATermList formals; + if (ATmatch(e, "Function([], )", &formals, &e2)) { + while (!ATisEmpty(formals)) { + if (!ATmatch(ATgetFirst(formals), "", &s)) + throw badTerm("not a list of formals", (ATerm) formals); + if (x == (string) s) + return e; + formals = ATgetNext(formals); + } + } + + /* Generically substitute in subterms. */ + + if (ATgetType(e) == AT_APPL) { + AFun fun = ATgetAFun(e); + int arity = ATgetArity(fun); + ATermList args = ATempty; + + for (int i = arity - 1; i >= 0; i--) + args = ATinsert(args, substExpr(x, rep, ATgetArgument(e, i))); + + return (ATerm) ATmakeApplList(fun, args); + } + + if (ATgetType(e) == AT_LIST) { + ATermList in = (ATermList) e; + ATermList out = ATempty; + + while (!ATisEmpty(in)) { + out = ATinsert(out, substExpr(x, rep, ATgetFirst(in))); + in = ATgetNext(in); + } + + return (ATerm) ATreverse(out); + } + + throw badTerm("do not know how to substitute", e); +} + + +static Expr substExprMany(ATermList formals, ATermList args, Expr body) +{ + char * s; + Expr e; + + /* !!! check args against formals */ + + while (!ATisEmpty(args)) { + ATerm tup = ATgetFirst(args); + if (!ATmatch(tup, "(, )", &s, &e)) + throw badTerm("expected an argument tuple", tup); + + body = substExpr(s, e, body); + + args = ATgetNext(args); + } + + return body; +} + + +static PathSet nixExprRootsCached(EvalState & state, const Path & nePath) +{ + PkgPaths::iterator i = state.pkgPaths.find(nePath); + if (i != state.pkgPaths.end()) + return i->second; + else { + PathSet paths = nixExprRoots(nePath); + state.pkgPaths[nePath] = paths; + return paths; + } +} + + +static Hash hashPackage(EvalState & state, NixExpr ne) +{ + if (ne.type == NixExpr::neDerivation) { + PathSet inputs2; + for (PathSet::iterator i = ne.derivation.inputs.begin(); + i != ne.derivation.inputs.end(); i++) + { + PkgHashes::iterator j = state.pkgHashes.find(*i); + if (j == state.pkgHashes.end()) + throw Error(format("don't know expression `%1%'") % (string) *i); + inputs2.insert(j->second); + } + ne.derivation.inputs = inputs2; + } + return hashTerm(unparseNixExpr(ne)); +} + + +static string processBinding(EvalState & state, Expr e, NixExpr & ne) +{ + char * s1; + + if (ATmatch(e, "NixExpr()", &s1)) { + Path nePath(s1); + PathSet paths = nixExprRootsCached(state, nePath); + if (paths.size() != 1) abort(); + Path path = *(paths.begin()); + ne.derivation.inputs.insert(nePath); + return path; + } + + if (ATmatch(e, "", &s1)) + return s1; + + if (ATmatch(e, "True")) return "1"; + + if (ATmatch(e, "False")) return ""; + + ATermList l; + if (ATmatch(e, "[]", &l)) { + string s; + bool first = true; + while (!ATisEmpty(l)) { + if (!first) s = s + " "; else first = false; + s += processBinding(state, evalExpr(state, ATgetFirst(l)), ne); + l = ATgetNext(l); + } + return s; + } + + throw badTerm("invalid package binding", e); +} + + +static Expr evalExpr2(EvalState & state, Expr e) +{ + char * s1; + Expr e1, e2, e3, e4; + ATermList bnds; + + /* Normal forms. */ + if (ATmatch(e, "", &s1) || + ATmatch(e, "[]", &e1) || + ATmatch(e, "True") || + ATmatch(e, "False") || + ATmatch(e, "Function([], )", &e1, &e2) || + ATmatch(e, "NixExpr()", &s1)) + return e; + + try { + Hash pkgHash = hashPackage(state, parseNixExpr(e)); + Path pkgPath = writeTerm(e, ""); + state.pkgHashes[pkgPath] = pkgHash; + return ATmake("NixExpr()", pkgPath.c_str()); + } catch (...) { /* !!! catch parse errors only */ + } + + /* Application. */ + if (ATmatch(e, "Call(, [])", &e1, &e2) || + ATmatch(e, "App(, [])", &e1, &e2)) { + e1 = evalExpr(state, e1); + if (!ATmatch(e1, "Function([], )", &e3, &e4)) + throw badTerm("expecting a function", e1); + return evalExpr(state, + substExprMany((ATermList) e3, (ATermList) e2, e4)); + } + + /* Conditional. */ + if (ATmatch(e, "If(, , )", &e1, &e2, &e3)) { + e1 = evalExpr(state, e1); + Expr x; + if (ATmatch(e1, "True")) x = e2; + else if (ATmatch(e1, "False")) x = e3; + else throw badTerm("expecting a boolean", e1); + return evalExpr(state, x); + } + + /* Ad-hoc function for string matching. */ + if (ATmatch(e, "HasSubstr(, )", &e1, &e2)) { + e1 = evalExpr(state, e1); + e2 = evalExpr(state, e2); + + char * s1, * s2; + if (!ATmatch(e1, "", &s1)) + throw badTerm("expecting a string", e1); + if (!ATmatch(e2, "", &s2)) + throw badTerm("expecting a string", e2); + + return + string(s1).find(string(s2)) != string::npos ? + ATmake("True") : ATmake("False"); + } + + /* Platform constant. */ + if (ATmatch(e, "Platform")) { + return ATmake("", thisSystem.c_str()); + } + + /* Fix inclusion. */ + if (ATmatch(e, "IncludeFix()", &s1)) { + Path fileName(s1); + return evalFile(state, s1); + } + + /* Relative files. */ + if (ATmatch(e, "Relative()", &s1)) { + Path srcPath = searchPath(state.searchDirs, s1); + Path dstPath = addToStore(srcPath); + + ClosureElem elem; + NixExpr ne; + ne.type = NixExpr::neClosure; + ne.closure.roots.insert(dstPath); + ne.closure.elems[dstPath] = elem; + + Hash pkgHash = hashPackage(state, ne); + Path pkgPath = writeTerm(unparseNixExpr(ne), ""); + state.pkgHashes[pkgPath] = pkgHash; + + msg(lvlChatty, format("copied `%1%' -> closure `%2%'") + % srcPath % pkgPath); + + return ATmake("NixExpr()", pkgPath.c_str()); + } + + /* Packages are transformed into Nix derivation expressions. */ + if (ATmatch(e, "Package([])", &bnds)) { + + /* Evaluate the bindings and put them in a map. */ + map bndMap; + bndMap["platform"] = ATmake("", thisSystem.c_str()); + while (!ATisEmpty(bnds)) { + ATerm bnd = ATgetFirst(bnds); + if (!ATmatch(bnd, "(, )", &s1, &e1)) + throw badTerm("binding expected", bnd); + bndMap[s1] = evalExpr(state, e1); + bnds = ATgetNext(bnds); + } + + /* Gather information for building the derivation + expression. */ + NixExpr ne; + ne.type = NixExpr::neDerivation; + ne.derivation.platform = thisSystem; + string name; + Path outPath; + Hash outHash; + bool outHashGiven = false; + bnds = ATempty; + + for (map::iterator it = bndMap.begin(); + it != bndMap.end(); it++) + { + string key = it->first; + ATerm value = it->second; + + if (key == "args") { + ATermList args; + if (!ATmatch(value, "[]", &args)) + throw badTerm("list expected", value); + + while (!ATisEmpty(args)) { + Expr arg = evalExpr(state, ATgetFirst(args)); + ne.derivation.args.push_back(processBinding(state, arg, ne)); + args = ATgetNext(args); + } + } + + else { + string s = processBinding(state, value, ne); + ne.derivation.env[key] = s; + + if (key == "build") ne.derivation.builder = s; + if (key == "name") name = s; + if (key == "outPath") outPath = s; + if (key == "id") { + outHash = parseHash(s); + outHashGiven = true; + } + } + + bnds = ATinsert(bnds, + ATmake("(, )", key.c_str(), value)); + } + + if (ne.derivation.builder == "") + throw badTerm("no builder specified", e); + + if (name == "") + throw badTerm("no package name specified", e); + + /* Determine the output path. */ + if (!outHashGiven) outHash = hashPackage(state, ne); + if (outPath == "") + /* Hash the Nix expression with no outputs to produce a + unique but deterministic path name for this package. */ + outPath = + canonPath(nixStore + "/" + ((string) outHash).c_str() + "-" + name); + ne.derivation.env["out"] = outPath; + ne.derivation.outputs.insert(outPath); + + /* Write the resulting term into the Nix store directory. */ + Hash pkgHash = outHashGiven + ? hashString((string) outHash + outPath) + : hashPackage(state, ne); + Path pkgPath = writeTerm(unparseNixExpr(ne), "-d-" + name); + state.pkgHashes[pkgPath] = pkgHash; + + msg(lvlChatty, format("instantiated `%1%' -> `%2%'") + % name % pkgPath); + + return ATmake("NixExpr()", pkgPath.c_str()); + } + + /* BaseName primitive function. */ + if (ATmatch(e, "BaseName()", &e1)) { + e1 = evalExpr(state, e1); + if (!ATmatch(e1, "", &s1)) + throw badTerm("string expected", e1); + return ATmake("", baseNameOf(s1).c_str()); + } + + /* Barf. */ + throw badTerm("invalid expression", e); +} + + +static Expr evalExpr(EvalState & state, Expr e) +{ + Nest nest(lvlVomit, format("evaluating expression: %1%") % printTerm(e)); + + /* Consult the memo table to quickly get the normal form of + previously evaluated expressions. */ + NormalForms::iterator i = state.normalForms.find(e); + if (i != state.normalForms.end()) { + if (i->second == state.blackHole) + throw badTerm("infinite recursion", e); + return i->second; + } + + /* Otherwise, evaluate and memoize. */ + state.normalForms[e] = state.blackHole; + Expr nf = evalExpr2(state, e); + state.normalForms[e] = nf; + return nf; +} + + +static Expr evalFile(EvalState & state, const Path & relPath) +{ + Path path = searchPath(state.searchDirs, relPath); + Nest nest(lvlTalkative, format("evaluating file `%1%'") % path); + Expr e = ATreadFromNamedFile(path.c_str()); + if (!e) + throw Error(format("unable to read a term from `%1%'") % path); + return evalExpr(state, e); +} + + +static Expr evalStdin(EvalState & state) +{ + Nest nest(lvlTalkative, format("evaluating standard input")); + Expr e = ATreadFromFile(stdin); + if (!e) + throw Error(format("unable to read a term from stdin")); + return evalExpr(state, e); +} + + +static void printNixExpr(EvalState & state, Expr e) +{ + ATermList es; + char * s; + if (ATmatch(e, "NixExpr()", &s)) { + cout << format("%1%\n") % s; + } + else if (ATmatch(e, "[]", &es)) { + while (!ATisEmpty(es)) { + printNixExpr(state, evalExpr(state, ATgetFirst(es))); + es = ATgetNext(es); + } + } + else throw badTerm("top level does not evaluate to a (list of) Nix expression(s)", e); +} + + +void run(Strings args) +{ + EvalState state; + Strings files; + bool readStdin = false; + + state.searchDirs.push_back("."); + state.searchDirs.push_back(nixDataDir + "/fix"); + + for (Strings::iterator it = args.begin(); + it != args.end(); ) + { + string arg = *it++; + + if (arg == "--includedir" || arg == "-I") { + if (it == args.end()) + throw UsageError(format("argument required in `%1%'") % arg); + state.searchDirs.push_back(*it++); + } + else if (arg == "--verbose" || arg == "-v") + verbosity = (Verbosity) ((int) verbosity + 1); + else if (arg == "-") + readStdin = true; + else if (arg[0] == '-') + throw UsageError(format("unknown flag `%1%`") % arg); + else + files.push_back(arg); + } + + openDB(); + + if (readStdin) { + Expr e = evalStdin(state); + printNixExpr(state, e); + } + + for (Strings::iterator it = files.begin(); + it != files.end(); it++) + { + Expr e = evalFile(state, *it); + printNixExpr(state, e); + } +} + + +string programId = "fix"; From b95a3dc45bcbbe8a0985bab82146ed00afcf0239 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 29 Oct 2003 16:05:03 +0000 Subject: [PATCH 0286/6440] * Basic grammar and parser for the Fix language. We use libsglr and friends to do the parsing. The parse table is embedded in the Fix executable using bin2c, which converts an arbitrary file into a C character array. --- src/fix-ng/Makefile.am | 21 +++++- src/fix-ng/bin2c.c | 23 ++++++ src/fix-ng/fix.cc | 7 +- src/fix-ng/fix.sdf | 163 +++++++++++++++++++++++++++++++++++++++++ src/fix-ng/parser.cc | 76 +++++++++++++++++++ src/fix-ng/parser.hh | 15 ++++ 6 files changed, 298 insertions(+), 7 deletions(-) create mode 100644 src/fix-ng/bin2c.c create mode 100644 src/fix-ng/fix.sdf create mode 100644 src/fix-ng/parser.cc create mode 100644 src/fix-ng/parser.hh diff --git a/src/fix-ng/Makefile.am b/src/fix-ng/Makefile.am index 64e8b2ed3..3672c3dc9 100644 --- a/src/fix-ng/Makefile.am +++ b/src/fix-ng/Makefile.am @@ -1,8 +1,25 @@ bin_PROGRAMS = fix-ng -fix_ng_SOURCES = fix.cc +fix_ng_SOURCES = fix.cc parser.cc fix_ng_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../boost/format/libformat.a \ - -L../../externals/inst/lib -ldb_cxx -lATerm + -L../../externals/inst/lib -ldb_cxx -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm AM_CXXFLAGS = \ -I.. -I../../externals/inst/include -I../libnix -I../libmain + + +# Parse table generation. + +parser.o: parse-table.h + +parse-table.h: fix.tbl bin2c + ./bin2c fixParseTable < $< > $@ || (rm $@ && exit 1) + +noinst_PROGRAMS = bin2c + +bin2c_SOURCES = bin2c.c + +%.tbl: %.sdf + ../../externals/inst/bin/sdf2table -i $< -o $@ + +CLEANFILES = parse-table.h fix.tbl diff --git a/src/fix-ng/bin2c.c b/src/fix-ng/bin2c.c new file mode 100644 index 000000000..18bf81d69 --- /dev/null +++ b/src/fix-ng/bin2c.c @@ -0,0 +1,23 @@ +#include +#include +#include + +void print(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + if (vprintf(format, ap) < 0) abort(); + va_end(ap); +} + +int main(int argc, char * * argv) +{ + int c; + if (argc != 2) abort(); + print("static unsigned char %s[] = {", argv[1]); + while ((c = getchar()) != EOF) { + print("0x%02x, ", (unsigned char) c); + } + print("};\n"); + return 0; +} diff --git a/src/fix-ng/fix.cc b/src/fix-ng/fix.cc index 9a8ff1513..e13413bb4 100644 --- a/src/fix-ng/fix.cc +++ b/src/fix-ng/fix.cc @@ -1,13 +1,12 @@ #include #include +#include "parser.hh" #include "globals.hh" #include "normalise.hh" #include "shared.hh" -typedef ATerm Expr; - typedef map NormalForms; typedef map PkgPaths; typedef map PkgHashes; @@ -406,9 +405,7 @@ static Expr evalFile(EvalState & state, const Path & relPath) { Path path = searchPath(state.searchDirs, relPath); Nest nest(lvlTalkative, format("evaluating file `%1%'") % path); - Expr e = ATreadFromNamedFile(path.c_str()); - if (!e) - throw Error(format("unable to read a term from `%1%'") % path); + Expr e = parseExprFromFile(path); return evalExpr(state, e); } diff --git a/src/fix-ng/fix.sdf b/src/fix-ng/fix.sdf new file mode 100644 index 000000000..72f3e694d --- /dev/null +++ b/src/fix-ng/fix.sdf @@ -0,0 +1,163 @@ +definition + +module Main +imports Fix + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Top level syntax. + +module Fix +imports Fix-Exprs Fix-Layout + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Expressions. + +module Fix-Exprs +imports Fix-Lexicals URI +exports + sorts Expr Bind + context-free syntax + + Id + -> Expr {cons("Var")} + + Int + -> Expr {cons("Int")} + + Str + -> Expr {cons("Str")} + + Uri + -> Expr {cons("Uri")} + + Path + -> Expr {cons("Path")} + + "(" Expr ")" + -> Expr {bracket} + + Expr Expr + -> Expr {cons("Call"), left} + + "{" {Id ","}* "}" ":" Expr + -> Expr {cons("Function"), right} + + "{" {Bind ","}+ "}" + -> Expr {cons("Attrs")} + + Id "=" Expr + -> Bind {cons("Bind")} + + "[" {Expr ","}* "]" + -> Expr {cons("List")} + + context-free priorities + + Expr Expr -> Expr + > "{" {Id ","}* "}" ":" Expr -> Expr + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Lexical syntax. + +module Fix-Lexicals +exports + sorts Id Path + lexical syntax + [a-zA-Z\_][a-zA-Z0-9\_\']* -> Id + [0-9]+ -> Int + "\"" ~[\n\"]* "\"" -> Str + PathComp ("/" PathComp)+ -> Path + [a-zA-Z0-9\.\_\-]+ -> PathComp + lexical restrictions + Id -/- [a-zA-Z0-9\_\'] + Int -/- [0-9] + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% URIs (RFC 2396, appendix A). + +module URI +exports + sorts Uri + lexical syntax + Uscheme ":" (Uhierpath | Uopaquepath) -> Uri + + (Unetpath | Uabspath) ("?" Uquery)? -> Uhierpath + Uuricnoslash Uuric* -> Uopaquepath + + Uunreserved | Uescaped | [\;\?\:\@\&\=\+\$\,] -> Uuricnoslash + + "//" Uauthority Uabspath? -> Unetpath + "/" Upathsegments -> Uabspath + Urelsegment Uabspath? -> Urelpath + + (Uunreserved | Uescaped | [\;\@\&\=\+\$\,])+ -> Urelsegment + + Ualpha (Ualpha | Udigit | [\+\-\.])* -> Uscheme + + Userver | Uregname -> Uauthority + + (Uunreserved | Uescaped | [\$\,\;\:\@\&\=\+])+ -> Uregname + + ((Uuserinfo "@") Uhostport) -> Userver + (Uunreserved | Uescaped | [\;\:\&\=\+\$\,])* -> Uuserinfo + + Uhost (":" Uport)? -> Uhostport + Uhostname | UIPv4address -> Uhost + (Udomainlabel ".")+ Utoplabel "."? -> Uhostname + Ualphanum | Ualphanum (Ualphanum | "-")* Ualphanum -> Udomainlabel + Ualpha | Ualpha (Ualphanum | "-")* Ualphanum -> Utoplabel + Udigit+ "." Udigit+ "." Udigit+ "." Udigit+ -> UIPv4address + Udigit* -> Uport + + Uabspath | Uopaquepart -> Upath + Usegment ("/" Usegment)* -> Upathsegments + Upchar* (";" Uparam)* -> Usegment + Upchar* -> Uparam + Uunreserved | Uescaped | [\:\@\&\=\+\$\,] -> Upchar + + Uuric* -> Uquery + + Uuric* -> Ufragment + + Ureserved | Uunreserved | Uescaped -> Uuric + [\;\/\?\:\@\&\=\+\$\,] -> Ureserved + Ualphanum | Umark -> Uunreserved + [\-\_\.\!\~\*\'\(\)] -> Umark + + "%" Uhex Uhex -> Uescaped + Udigit | [A-Fa-f] -> Uhex + + Ualpha | Udigit -> Ualphanum + Ulowalpha | Uupalpha -> Ualpha + + [a-z] -> Ulowalpha + [A-Z] -> Uupalpha + [0-9] -> Udigit + + lexical restrictions + Uri -/- [a-zA-Z0-9\-\_\.\!\~\*\'\(\)] + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Layout. + +module Fix-Layout +exports + lexical syntax + [\ \t\n] -> LAYOUT + HashComment -> LAYOUT + Comment -> LAYOUT + "#" ~[\n]* [\n] -> HashComment + "//" ~[\n]* [\n] -> HashComment + "/*" ( ~[\*] | Asterisk )* "*/" -> Comment + [\*] -> Asterisk + lexical restrictions + Asterisk -/- [\/] + context-free restrictions + LAYOUT? -/- [\ \t\n] | [\#] + syntax + HashComment -> diff --git a/src/fix-ng/parser.cc b/src/fix-ng/parser.cc new file mode 100644 index 000000000..b2f0ed05d --- /dev/null +++ b/src/fix-ng/parser.cc @@ -0,0 +1,76 @@ +extern "C" { +#include +#include +} + +#include "parser.hh" +#include "shared.hh" +#include "expr.hh" +#include "parse-table.h" + + +Expr parseExprFromFile(const Path & path) +{ + /* Perhaps this is already an imploded parse tree? */ + Expr e = ATreadFromNamedFile(path.c_str()); + if (e) return e; + + /* Initialise the SDF libraries. */ + static bool initialised = false; + static ATerm parseTable = 0; + static language lang = 0; + + if (!initialised) { + PT_initMEPTApi(); + PT_initAsFix2Api(); + SGinitParser(ATfalse); + + ATprotect(&parseTable); + parseTable = ATreadFromBinaryString( + (char *) fixParseTable, sizeof fixParseTable); + if (!parseTable) + throw Error(format("cannot construct parse table term")); + + ATprotect(&lang); + lang = ATmake("Fix"); + if (!SGopenLanguageFromTerm( + (char *) programId.c_str(), lang, parseTable)) + throw Error(format("cannot open language")); + + SG_STARTSYMBOL_ON(); + SG_OUTPUT_ON(); + SG_ASFIX2ME_ON(); + SG_AMBIGUITY_ERROR_ON(); + + initialised = true; + } + + ATerm result = SGparseFile((char *) programId.c_str(), lang, + "Expr", (char *) path.c_str()); + if (!result) + throw SysError(format("parse failed in `%1%'") % path); + if (SGisParseError(result)) + throw Error(format("parse error in `%1%': %2%") + % path % printTerm(result)); + + PT_ParseTree tree = PT_makeParseTreeFromTerm(result); + if (!tree) + throw Error(format("cannot create parse tree")); + + ATerm imploded = PT_implodeParseTree(tree, + ATtrue, + ATtrue, + ATtrue, + ATtrue, + ATtrue, + ATtrue, + ATfalse, + ATtrue, + ATtrue, + ATtrue, + ATfalse); + if (!imploded) + throw Error(format("cannot implode parse tree")); + + return imploded; +} diff --git a/src/fix-ng/parser.hh b/src/fix-ng/parser.hh new file mode 100644 index 000000000..80e266f2d --- /dev/null +++ b/src/fix-ng/parser.hh @@ -0,0 +1,15 @@ +#ifndef __PARSER_H +#define __PARSER_H + +#include +#include + +#include "util.hh" + + +typedef ATerm Expr; + +Expr parseExprFromFile(const Path & path); + + +#endif /* !__PARSER_H */ From 933b3f677d0338b264d4e758f8932bb7f2454c46 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 30 Oct 2003 16:10:20 +0000 Subject: [PATCH 0287/6440] * Attribute selection operator. --- src/fix-ng/fix.sdf | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/fix-ng/fix.sdf b/src/fix-ng/fix.sdf index 72f3e694d..e94e69674 100644 --- a/src/fix-ng/fix.sdf +++ b/src/fix-ng/fix.sdf @@ -53,9 +53,13 @@ exports "[" {Expr ","}* "]" -> Expr {cons("List")} + Expr "." Id + -> Expr {cons("Select")} + context-free priorities - Expr Expr -> Expr + Expr "." Id -> Expr + > Expr Expr -> Expr > "{" {Id ","}* "}" ":" Expr -> Expr From 442b09ea33dcf516a6f5244ed2c362682e1347b0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 30 Oct 2003 16:10:56 +0000 Subject: [PATCH 0288/6440] * Don't use a search path. --- src/fix-ng/fix.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/fix-ng/fix.cc b/src/fix-ng/fix.cc index e13413bb4..05e27c506 100644 --- a/src/fix-ng/fix.cc +++ b/src/fix-ng/fix.cc @@ -31,6 +31,7 @@ static Expr evalFile(EvalState & state, const Path & path); static Expr evalExpr(EvalState & state, Expr e); +#if 0 static Path searchPath(const Paths & searchDirs, const Path & relPath) { if (string(relPath, 0, 1) == "/") return relPath; @@ -46,6 +47,7 @@ static Path searchPath(const Paths & searchDirs, const Path & relPath) format("path `%1%' not found in any of the search directories") % relPath); } +#endif static Expr substExpr(string x, Expr rep, Expr e) @@ -259,7 +261,7 @@ static Expr evalExpr2(EvalState & state, Expr e) /* Relative files. */ if (ATmatch(e, "Relative()", &s1)) { - Path srcPath = searchPath(state.searchDirs, s1); + Path srcPath = s1; Path dstPath = addToStore(srcPath); ClosureElem elem; @@ -401,9 +403,8 @@ static Expr evalExpr(EvalState & state, Expr e) } -static Expr evalFile(EvalState & state, const Path & relPath) +static Expr evalFile(EvalState & state, const Path & path) { - Path path = searchPath(state.searchDirs, relPath); Nest nest(lvlTalkative, format("evaluating file `%1%'") % path); Expr e = parseExprFromFile(path); return evalExpr(state, e); @@ -476,7 +477,7 @@ void run(Strings args) for (Strings::iterator it = files.begin(); it != files.end(); it++) { - Expr e = evalFile(state, *it); + Expr e = evalFile(state, absPath(*it)); printNixExpr(state, e); } } From e537844f4ebc53df13f52722fb16bbeb1f4cbd18 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 30 Oct 2003 16:11:24 +0000 Subject: [PATCH 0289/6440] * Bottomup rewrite function. --- src/fix-ng/fix-expr.cc | 33 +++++++++++++++++++++++++++++++++ src/fix-ng/fix-expr.hh | 27 +++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/fix-ng/fix-expr.cc create mode 100644 src/fix-ng/fix-expr.hh diff --git a/src/fix-ng/fix-expr.cc b/src/fix-ng/fix-expr.cc new file mode 100644 index 000000000..00795da4c --- /dev/null +++ b/src/fix-ng/fix-expr.cc @@ -0,0 +1,33 @@ +#include "fix-expr.hh" +#include "expr.hh" + + +ATerm bottomupRewrite(TermFun & f, ATerm e) +{ + e = f(e); + + if (ATgetType(e) == AT_APPL) { + AFun fun = ATgetAFun(e); + int arity = ATgetArity(fun); + ATermList args = ATempty; + + for (int i = arity - 1; i >= 0; i--) + args = ATinsert(args, bottomupRewrite(f, ATgetArgument(e, i))); + + return (ATerm) ATmakeApplList(fun, args); + } + + if (ATgetType(e) == AT_LIST) { + ATermList in = (ATermList) e; + ATermList out = ATempty; + + while (!ATisEmpty(in)) { + out = ATinsert(out, bottomupRewrite(f, ATgetFirst(in))); + in = ATgetNext(in); + } + + return (ATerm) ATreverse(out); + } + + throw badTerm("cannot rewrite", e); +} diff --git a/src/fix-ng/fix-expr.hh b/src/fix-ng/fix-expr.hh new file mode 100644 index 000000000..5c50e9170 --- /dev/null +++ b/src/fix-ng/fix-expr.hh @@ -0,0 +1,27 @@ +#ifndef __FIXEXPR_H +#define __FIXEXPR_H + +#include + +#include "util.hh" + + +/* Fix 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; + + +/* 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 +{ + virtual ATerm operator () (ATerm e) = 0; +}; + +ATerm bottomupRewrite(TermFun & f, ATerm e); + + +#endif /* !__FIXEXPR_H */ From 9f8f39aa3cdb54532a85e41f14985fc6a530fb36 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 30 Oct 2003 16:18:40 +0000 Subject: [PATCH 0290/6440] * Clean up the imploded parse tree. Quotes around strings are removed, paths are absolutised relative to the path containing the expression we just parsed, and integer literals are converted to actual integers. --- src/fix-ng/Makefile.am | 2 +- src/fix-ng/fix-expr.cc | 2 +- src/fix-ng/parser.cc | 55 ++++++++++++++++++++++++++++++++++++++++-- src/fix-ng/parser.hh | 9 ++----- 4 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/fix-ng/Makefile.am b/src/fix-ng/Makefile.am index 3672c3dc9..88f1f4fe9 100644 --- a/src/fix-ng/Makefile.am +++ b/src/fix-ng/Makefile.am @@ -1,6 +1,6 @@ bin_PROGRAMS = fix-ng -fix_ng_SOURCES = fix.cc parser.cc +fix_ng_SOURCES = fix-expr.cc parser.cc fix.cc fix_ng_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../boost/format/libformat.a \ -L../../externals/inst/lib -ldb_cxx -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm diff --git a/src/fix-ng/fix-expr.cc b/src/fix-ng/fix-expr.cc index 00795da4c..8d47817ff 100644 --- a/src/fix-ng/fix-expr.cc +++ b/src/fix-ng/fix-expr.cc @@ -29,5 +29,5 @@ ATerm bottomupRewrite(TermFun & f, ATerm e) return (ATerm) ATreverse(out); } - throw badTerm("cannot rewrite", e); + return e; } diff --git a/src/fix-ng/parser.cc b/src/fix-ng/parser.cc index b2f0ed05d..e0812817a 100644 --- a/src/fix-ng/parser.cc +++ b/src/fix-ng/parser.cc @@ -1,3 +1,9 @@ +#include + +#include +#include +#include + extern "C" { #include #include @@ -5,15 +11,58 @@ extern "C" { #include "parser.hh" #include "shared.hh" +#include "fix-expr.hh" #include "expr.hh" #include "parse-table.h" -Expr parseExprFromFile(const Path & path) +struct Cleanup : TermFun { + string basePath; + + virtual ATerm operator () (ATerm e) + { + char * s; + + if (ATmatch(e, "Str()", &s)) { + string s2(s); + return ATmake("Str()", + string(s2, 1, s2.size() - 2).c_str()); + } + + if (ATmatch(e, "Path()", &s)) { + string path(s); + if (path[0] != '/') + path = basePath + "/" + path; + return ATmake("Str()", canonPath(path).c_str()); + } + + if (ATmatch(e, "Int()", &s)) { + istringstream s2(s); + int n; + s2 >> n; + return ATmake("Int()", n); + } + + return e; + } +}; + + +Expr parseExprFromFile(Path path) +{ +#if 0 /* Perhaps this is already an imploded parse tree? */ Expr e = ATreadFromNamedFile(path.c_str()); if (e) return e; +#endif + + /* If `path' refers to a directory, append `/default.fix'. */ + struct stat st; + if (stat(path.c_str(), &st)) + throw SysError(format("getting status of `%1%'") % path); + if (S_ISDIR(st.st_mode)) + path = canonPath(path + "/default.fix"); /* Initialise the SDF libraries. */ static bool initialised = false; @@ -72,5 +121,7 @@ Expr parseExprFromFile(const Path & path) if (!imploded) throw Error(format("cannot implode parse tree")); - return imploded; + Cleanup cleanup; + cleanup.basePath = dirOf(path); + return bottomupRewrite(cleanup, imploded); } diff --git a/src/fix-ng/parser.hh b/src/fix-ng/parser.hh index 80e266f2d..c56a339a3 100644 --- a/src/fix-ng/parser.hh +++ b/src/fix-ng/parser.hh @@ -1,15 +1,10 @@ #ifndef __PARSER_H #define __PARSER_H -#include -#include - -#include "util.hh" +#include "fix-expr.hh" -typedef ATerm Expr; - -Expr parseExprFromFile(const Path & path); +Expr parseExprFromFile(Path path); #endif /* !__PARSER_H */ From 403cb9327f5c298cb6a85a87241962df4a90857b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 30 Oct 2003 16:48:26 +0000 Subject: [PATCH 0291/6440] * Factor out evaluation into a separate file. --- src/fix-ng/Makefile.am | 2 +- src/fix-ng/eval.cc | 45 +++++++++++++++++++++++++++++ src/fix-ng/eval.hh | 31 ++++++++++++++++++++ src/fix-ng/fix.cc | 65 +++++++----------------------------------- 4 files changed, 87 insertions(+), 56 deletions(-) create mode 100644 src/fix-ng/eval.cc create mode 100644 src/fix-ng/eval.hh diff --git a/src/fix-ng/Makefile.am b/src/fix-ng/Makefile.am index 88f1f4fe9..b0e90450e 100644 --- a/src/fix-ng/Makefile.am +++ b/src/fix-ng/Makefile.am @@ -1,6 +1,6 @@ bin_PROGRAMS = fix-ng -fix_ng_SOURCES = fix-expr.cc parser.cc fix.cc +fix_ng_SOURCES = fix-expr.cc parser.cc eval.cc fix.cc fix_ng_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../boost/format/libformat.a \ -L../../externals/inst/lib -ldb_cxx -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm diff --git a/src/fix-ng/eval.cc b/src/fix-ng/eval.cc new file mode 100644 index 000000000..c0f4680a4 --- /dev/null +++ b/src/fix-ng/eval.cc @@ -0,0 +1,45 @@ +#include "eval.hh" +#include "expr.hh" +#include "parser.hh" + + +EvalState::EvalState() +{ + blackHole = ATmake("BlackHole()"); + if (!blackHole) throw Error("cannot build black hole"); +} + + +Expr evalExpr2(EvalState & state, Expr e) +{ + return e; +} + + +Expr evalExpr(EvalState & state, Expr e) +{ + Nest nest(lvlVomit, format("evaluating expression: %1%") % printTerm(e)); + + /* Consult the memo table to quickly get the normal form of + previously evaluated expressions. */ + NormalForms::iterator i = state.normalForms.find(e); + if (i != state.normalForms.end()) { + if (i->second == state.blackHole) + throw badTerm("infinite recursion", e); + return i->second; + } + + /* Otherwise, evaluate and memoize. */ + state.normalForms[e] = state.blackHole; + Expr nf = evalExpr2(state, e); + state.normalForms[e] = nf; + return nf; +} + + +Expr evalFile(EvalState & state, const Path & path) +{ + Nest nest(lvlTalkative, format("evaluating file `%1%'") % path); + Expr e = parseExprFromFile(path); + return evalExpr(state, e); +} diff --git a/src/fix-ng/eval.hh b/src/fix-ng/eval.hh new file mode 100644 index 000000000..5fcb648a7 --- /dev/null +++ b/src/fix-ng/eval.hh @@ -0,0 +1,31 @@ +#ifndef __EVAL_H +#define __EVAL_H + +#include + +#include "fix-expr.hh" + + +typedef map NormalForms; +//typedef map PkgPaths; +//typedef map PkgHashes; + +struct EvalState +{ + NormalForms normalForms; + // PkgPaths pkgPaths; + // PkgHashes pkgHashes; /* normalised package hashes */ + Expr blackHole; + + EvalState(); +}; + + +/* 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); + + +#endif /* !__EVAL_H */ diff --git a/src/fix-ng/fix.cc b/src/fix-ng/fix.cc index 05e27c506..fb98dc697 100644 --- a/src/fix-ng/fix.cc +++ b/src/fix-ng/fix.cc @@ -1,36 +1,14 @@ #include #include -#include "parser.hh" #include "globals.hh" #include "normalise.hh" #include "shared.hh" +#include "expr.hh" +#include "eval.hh" -typedef map NormalForms; -typedef map PkgPaths; -typedef map PkgHashes; - -struct EvalState -{ - Paths searchDirs; - NormalForms normalForms; - PkgPaths pkgPaths; - PkgHashes pkgHashes; /* normalised package hashes */ - Expr blackHole; - - EvalState() - { - blackHole = ATmake("BlackHole()"); - if (!blackHole) throw Error("cannot build black hole"); - } -}; - - -static Expr evalFile(EvalState & state, const Path & path); -static Expr evalExpr(EvalState & state, Expr e); - - +#if 0 #if 0 static Path searchPath(const Paths & searchDirs, const Path & relPath) { @@ -380,35 +358,7 @@ static Expr evalExpr2(EvalState & state, Expr e) /* Barf. */ throw badTerm("invalid expression", e); } - - -static Expr evalExpr(EvalState & state, Expr e) -{ - Nest nest(lvlVomit, format("evaluating expression: %1%") % printTerm(e)); - - /* Consult the memo table to quickly get the normal form of - previously evaluated expressions. */ - NormalForms::iterator i = state.normalForms.find(e); - if (i != state.normalForms.end()) { - if (i->second == state.blackHole) - throw badTerm("infinite recursion", e); - return i->second; - } - - /* Otherwise, evaluate and memoize. */ - state.normalForms[e] = state.blackHole; - Expr nf = evalExpr2(state, e); - state.normalForms[e] = nf; - return nf; -} - - -static Expr evalFile(EvalState & state, const Path & path) -{ - Nest nest(lvlTalkative, format("evaluating file `%1%'") % path); - Expr e = parseExprFromFile(path); - return evalExpr(state, e); -} +#endif static Expr evalStdin(EvalState & state) @@ -444,20 +394,25 @@ void run(Strings args) Strings files; bool readStdin = false; +#if 0 state.searchDirs.push_back("."); state.searchDirs.push_back(nixDataDir + "/fix"); +#endif for (Strings::iterator it = args.begin(); it != args.end(); ) { string arg = *it++; +#if 0 if (arg == "--includedir" || arg == "-I") { if (it == args.end()) throw UsageError(format("argument required in `%1%'") % arg); state.searchDirs.push_back(*it++); } - else if (arg == "--verbose" || arg == "-v") + else +#endif + if (arg == "--verbose" || arg == "-v") verbosity = (Verbosity) ((int) verbosity + 1); else if (arg == "-") readStdin = true; From 7db08cc9244c374903180c8e816ecf81f6ff1d79 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 31 Oct 2003 11:22:56 +0000 Subject: [PATCH 0292/6440] * Use SGparseString() instead of SGparseFile() because the latter is buggy. It fails to clear an internal variable (SG_textIndex) between invocations, so it can be called only once during a program execution. --- src/fix-ng/parser.cc | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/fix-ng/parser.cc b/src/fix-ng/parser.cc index e0812817a..d146ad88c 100644 --- a/src/fix-ng/parser.cc +++ b/src/fix-ng/parser.cc @@ -2,6 +2,7 @@ #include #include +#include #include extern "C" { @@ -16,6 +17,11 @@ extern "C" { #include "parse-table.h" +/* Cleanup cleans up an imploded parse tree into an actual abstract + syntax tree that we can evaluate. It removes quotes around + strings, converts integer literals into actual integers, and + absolutises paths relative to the directory containing the input + file. */ struct Cleanup : TermFun { string basePath; @@ -94,14 +100,28 @@ Expr parseExprFromFile(Path path) initialised = true; } - ATerm result = SGparseFile((char *) programId.c_str(), lang, - "Expr", (char *) path.c_str()); + /* Read the input file. We can't use SGparseFile() because it's + broken, so we read the input ourselves and call + SGparseString(). */ + AutoCloseFD fd = open(path.c_str(), O_RDONLY); + if (fd == -1) throw SysError(format("opening `%1%'") % path); + + if (fstat(fd, &st) == -1) + throw SysError(format("statting `%1%'") % path); + + char text[st.st_size + 1]; + readFull(fd, (unsigned char *) text, st.st_size); + text[st.st_size] = 0; + + /* Parse it. */ + ATerm result = SGparseString(lang, "Expr", text); if (!result) throw SysError(format("parse failed in `%1%'") % path); if (SGisParseError(result)) throw Error(format("parse error in `%1%': %2%") % path % printTerm(result)); + /* Implode it. */ PT_ParseTree tree = PT_makeParseTreeFromTerm(result); if (!tree) throw Error(format("cannot create parse tree")); @@ -121,6 +141,7 @@ Expr parseExprFromFile(Path path) if (!imploded) throw Error(format("cannot implode parse tree")); + /* Finally, clean it up. */ Cleanup cleanup; cleanup.basePath = dirOf(path); return bottomupRewrite(cleanup, imploded); From f1c1a3c97f1dc81b2d9b19f58589b4b8a5ed196e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 31 Oct 2003 12:21:01 +0000 Subject: [PATCH 0293/6440] * Allow empty attribute (argument) sets. --- src/fix-ng/fix.sdf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fix-ng/fix.sdf b/src/fix-ng/fix.sdf index e94e69674..2074d9829 100644 --- a/src/fix-ng/fix.sdf +++ b/src/fix-ng/fix.sdf @@ -44,7 +44,7 @@ exports "{" {Id ","}* "}" ":" Expr -> Expr {cons("Function"), right} - "{" {Bind ","}+ "}" + "{" {Bind ","}* "}" -> Expr {cons("Attrs")} Id "=" Expr From 9210d4d530b68b5f19ac7062f129c88ccdc03e04 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 31 Oct 2003 17:09:31 +0000 Subject: [PATCH 0294/6440] * Working evaluator. * Mutually recursive attribute sets. * Print evaluator efficiency statistics. --- src/fix-ng/Makefile.am | 2 +- src/fix-ng/eval.cc | 162 ++++++++++++++++++++- src/fix-ng/eval.hh | 19 ++- src/fix-ng/fix-expr.cc | 93 ++++++++++++ src/fix-ng/fix-expr.hh | 20 ++- src/fix-ng/fix.cc | 319 +++-------------------------------------- src/fix-ng/fix.sdf | 4 + src/fix-ng/parser.cc | 2 +- src/fix-ng/primops.cc | 206 ++++++++++++++++++++++++++ src/fix-ng/primops.hh | 22 +++ 10 files changed, 540 insertions(+), 309 deletions(-) create mode 100644 src/fix-ng/primops.cc create mode 100644 src/fix-ng/primops.hh diff --git a/src/fix-ng/Makefile.am b/src/fix-ng/Makefile.am index b0e90450e..359e39c46 100644 --- a/src/fix-ng/Makefile.am +++ b/src/fix-ng/Makefile.am @@ -1,6 +1,6 @@ bin_PROGRAMS = fix-ng -fix_ng_SOURCES = fix-expr.cc parser.cc eval.cc fix.cc +fix_ng_SOURCES = fix-expr.cc parser.cc eval.cc primops.cc fix.cc fix_ng_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../boost/format/libformat.a \ -L../../externals/inst/lib -ldb_cxx -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm diff --git a/src/fix-ng/eval.cc b/src/fix-ng/eval.cc index c0f4680a4..785467741 100644 --- a/src/fix-ng/eval.cc +++ b/src/fix-ng/eval.cc @@ -1,18 +1,167 @@ #include "eval.hh" #include "expr.hh" #include "parser.hh" +#include "primops.hh" EvalState::EvalState() { blackHole = ATmake("BlackHole()"); if (!blackHole) throw Error("cannot build black hole"); + nrEvaluated = nrCached = 0; +} + + +Expr getAttr(EvalState & state, Expr e, const string & name) +{ +} + + +/* Substitute an argument set into the body of a function. */ +static Expr substArgs(Expr body, ATermList formals, Expr arg) +{ + Subs subs; + Expr undefined = ATmake("Undefined"); + + /* Get the formal arguments. */ + while (!ATisEmpty(formals)) { + char * s; + if (!ATmatch(ATgetFirst(formals), "", &s)) + abort(); /* can't happen */ + subs[s] = undefined; + formals = ATgetNext(formals); + } + + /* Get the actual arguments, and check that they match with the + formals. */ + Attrs args; + queryAllAttrs(arg, args); + for (Attrs::iterator i = args.begin(); i != args.end(); i++) { + if (subs.find(i->first) == subs.end()) + throw badTerm(format("argument `%1%' not declared") % i->first, arg); + subs[i->first] = i->second; + } + + /* Check that all arguments are defined. */ + for (Subs::iterator i = subs.begin(); i != subs.end(); i++) + if (i->second == undefined) + throw badTerm(format("formal argument `%1%' missing") % i->first, arg); + + return substitute(subs, body); +} + + +/* Transform a mutually recursive set into a non-recursive set. Each + attribute is transformed into an expression that has all references + to attributes substituted with selection expressions on the + original set. E.g., e = `rec {x = f x y, y = x}' becomes `{x = f + (e.x) (e.y), y = e.x}'. */ +ATerm expandRec(ATerm e, ATermList bnds) +{ + /* Create the substitution list. */ + Subs subs; + ATermList bs = bnds; + while (!ATisEmpty(bs)) { + char * s; + Expr e2; + if (!ATmatch(ATgetFirst(bs), "Bind(, )", &s, &e2)) + abort(); /* can't happen */ + subs[s] = ATmake("Select(, )", e, s); + bs = ATgetNext(bs); + } + + /* Create the non-recursive set. */ + Attrs as; + bs = bnds; + while (!ATisEmpty(bs)) { + char * s; + Expr e2; + if (!ATmatch(ATgetFirst(bs), "Bind(, )", &s, &e2)) + abort(); /* can't happen */ + as[s] = substitute(subs, e2); + bs = ATgetNext(bs); + } + + return makeAttrs(as); +} + + +string evalString(EvalState & state, Expr e) +{ + e = evalExpr(state, e); + char * s; + if (!ATmatch(e, "Str()", &s)) + throw badTerm("string expected", e); + return s; +} + + +Path evalPath(EvalState & state, Expr e) +{ + e = evalExpr(state, e); + char * s; + if (!ATmatch(e, "Path()", &s)) + throw badTerm("path expected", e); + return s; } Expr evalExpr2(EvalState & state, Expr e) { - return e; + Expr e1, e2, e3, e4; + char * s1; + + /* Normal forms. */ + if (ATmatch(e, "Str()", &s1) || + ATmatch(e, "Path()", &s1) || + ATmatch(e, "Uri()", &s1) || + ATmatch(e, "Function([], )", &e1, &e2) || + ATmatch(e, "Attrs([])", &e1) || + ATmatch(e, "List([])", &e1)) + return e; + + /* Any encountered variables must be undeclared or primops. */ + if (ATmatch(e, "Var()", &s1)) { + return e; + } + + /* Function application. */ + if (ATmatch(e, "Call(, )", &e1, &e2)) { + + /* Evaluate the left-hand side. */ + e1 = evalExpr(state, e1); + + /* Is it a primop or a function? */ + if (ATmatch(e1, "Var()", &s1)) { + string primop(s1); + if (primop == "import") return primImport(state, e2); + if (primop == "derivation") return primDerivation(state, e2); + else throw badTerm("undefined variable/primop", e1); + } + + else if (ATmatch(e1, "Function([], )", &e3, &e4)) { + return evalExpr(state, + substArgs(e4, (ATermList) e3, evalExpr(state, e2))); + } + + else throw badTerm("expecting a function or primop", e1); + } + + /* Attribute selection. */ + if (ATmatch(e, "Select(, )", &e1, &s1)) { + string name(s1); + Expr a = queryAttr(evalExpr(state, e1), name); + if (!a) throw badTerm(format("missing attribute `%1%'") % name, e); + return evalExpr(state, a); + } + + /* Mutually recursive sets. */ + ATermList bnds; + if (ATmatch(e, "Rec([])", &bnds)) + return expandRec(e, (ATermList) bnds); + + /* Barf. */ + throw badTerm("invalid expression", e); } @@ -20,12 +169,15 @@ Expr evalExpr(EvalState & state, Expr e) { Nest nest(lvlVomit, format("evaluating expression: %1%") % printTerm(e)); + state.nrEvaluated++; + /* Consult the memo table to quickly get the normal form of previously evaluated expressions. */ NormalForms::iterator i = state.normalForms.find(e); if (i != state.normalForms.end()) { if (i->second == state.blackHole) throw badTerm("infinite recursion", e); + state.nrCached++; return i->second; } @@ -43,3 +195,11 @@ Expr evalFile(EvalState & state, const Path & path) Expr e = parseExprFromFile(path); return evalExpr(state, e); } + + +void printEvalStats(EvalState & state) +{ + debug(format("evaluated %1% expressions, %2% cache hits, %3%%% efficiency") + % state.nrEvaluated % state.nrCached + % ((float) state.nrCached / (float) state.nrEvaluated * 100)); +} diff --git a/src/fix-ng/eval.hh b/src/fix-ng/eval.hh index 5fcb648a7..364f28471 100644 --- a/src/fix-ng/eval.hh +++ b/src/fix-ng/eval.hh @@ -4,19 +4,23 @@ #include #include "fix-expr.hh" +#include "expr.hh" typedef map NormalForms; -//typedef map PkgPaths; -//typedef map PkgHashes; +typedef map DrvPaths; +typedef map DrvHashes; struct EvalState { NormalForms normalForms; - // PkgPaths pkgPaths; - // PkgHashes pkgHashes; /* normalised package hashes */ + DrvPaths drvPaths; + DrvHashes drvHashes; /* normalised derivation hashes */ Expr blackHole; + unsigned int nrEvaluated; + unsigned int nrCached; + EvalState(); }; @@ -27,5 +31,12 @@ Expr evalExpr(EvalState & state, Expr e); /* Evaluate an expression read from the given file to normal form. */ Expr evalFile(EvalState & state, const Path & path); +/* Specific results. */ +string evalString(EvalState & state, Expr e); +Path evalPath(EvalState & state, Expr e); + +/* Print statistics. */ +void printEvalStats(EvalState & state); + #endif /* !__EVAL_H */ diff --git a/src/fix-ng/fix-expr.cc b/src/fix-ng/fix-expr.cc index 8d47817ff..ff0b7d43d 100644 --- a/src/fix-ng/fix-expr.cc +++ b/src/fix-ng/fix-expr.cc @@ -31,3 +31,96 @@ ATerm bottomupRewrite(TermFun & f, ATerm e) return e; } + + +void queryAllAttrs(Expr e, Attrs & attrs) +{ + ATermList bnds; + if (!ATmatch(e, "Attrs([])", &bnds)) + throw badTerm("expected attribute set", e); + + while (!ATisEmpty(bnds)) { + char * s; + Expr e; + if (!ATmatch(ATgetFirst(bnds), "Bind(, )", &s, &e)) + abort(); /* can't happen */ + attrs[s] = e; + bnds = ATgetNext(bnds); + } +} + + +Expr queryAttr(Expr e, const string & name) +{ + Attrs attrs; + queryAllAttrs(e, attrs); + Attrs::iterator i = attrs.find(name); + return i == attrs.end() ? 0 : i->second; +} + + +Expr makeAttrs(const Attrs & attrs) +{ + ATermList bnds = ATempty; + for (Attrs::const_iterator i = attrs.begin(); i != attrs.end(); i++) + bnds = ATinsert(bnds, + ATmake("Bind(, )", i->first.c_str(), i->second)); + return ATmake("Attrs()", ATreverse(bnds)); +} + + +ATerm substitute(Subs & subs, ATerm e) +{ + char * s; + + if (ATmatch(e, "Var()", &s)) { + Subs::iterator i = subs.find(s); + if (i == subs.end()) + return e; + else + return i->second; + } + + /* In case of a function, filter out all variables bound by this + function. */ + ATermList formals; + ATerm body; + if (ATmatch(e, "Function([], )", &formals, &body)) { + Subs subs2(subs); + ATermList fs = formals; + while (!ATisEmpty(fs)) { + if (!ATmatch(ATgetFirst(fs), "", &s)) abort(); + subs2.erase(s); + fs = ATgetNext(fs); + } + return ATmake("Function(, )", formals, + substitute(subs2, body)); + } + + /* !!! Rec(...) */ + + if (ATgetType(e) == AT_APPL) { + AFun fun = ATgetAFun(e); + int arity = ATgetArity(fun); + ATermList args = ATempty; + + for (int i = arity - 1; i >= 0; i--) + args = ATinsert(args, substitute(subs, ATgetArgument(e, i))); + + return (ATerm) ATmakeApplList(fun, args); + } + + if (ATgetType(e) == AT_LIST) { + ATermList in = (ATermList) e; + ATermList out = ATempty; + + while (!ATisEmpty(in)) { + out = ATinsert(out, substitute(subs, ATgetFirst(in))); + in = ATgetNext(in); + } + + return (ATerm) ATreverse(out); + } + + return e; +} diff --git a/src/fix-ng/fix-expr.hh b/src/fix-ng/fix-expr.hh index 5c50e9170..700f7beca 100644 --- a/src/fix-ng/fix-expr.hh +++ b/src/fix-ng/fix-expr.hh @@ -1,6 +1,8 @@ #ifndef __FIXEXPR_H #define __FIXEXPR_H +#include + #include #include "util.hh" @@ -15,13 +17,27 @@ typedef ATerm Expr; /* 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 { virtual ATerm operator () (ATerm e) = 0; }; - ATerm bottomupRewrite(TermFun & f, ATerm e); +/* Query all attributes in an attribute set expression. The + expression must be in normal form. */ +typedef map Attrs; +void queryAllAttrs(Expr e, Attrs & attrs); + +/* Query a specific attribute from an attribute set expression. The + expression must be in normal form. */ +Expr queryAttr(Expr e, const string & name); + +/* Create an attribute set expression from an Attrs value. */ +Expr makeAttrs(const Attrs & attrs); + +/* Perform a set of substitutions on an expression. */ +typedef map Subs; +ATerm substitute(Subs & subs, ATerm e); + #endif /* !__FIXEXPR_H */ diff --git a/src/fix-ng/fix.cc b/src/fix-ng/fix.cc index fb98dc697..d791461bd 100644 --- a/src/fix-ng/fix.cc +++ b/src/fix-ng/fix.cc @@ -8,7 +8,6 @@ #include "eval.hh" -#if 0 #if 0 static Path searchPath(const Paths & searchDirs, const Path & relPath) { @@ -28,178 +27,9 @@ static Path searchPath(const Paths & searchDirs, const Path & relPath) #endif -static Expr substExpr(string x, Expr rep, Expr e) -{ - char * s; - Expr e2; - - if (ATmatch(e, "Var()", &s)) - if (x == s) - return rep; - else - return e; - - ATermList formals; - if (ATmatch(e, "Function([], )", &formals, &e2)) { - while (!ATisEmpty(formals)) { - if (!ATmatch(ATgetFirst(formals), "", &s)) - throw badTerm("not a list of formals", (ATerm) formals); - if (x == (string) s) - return e; - formals = ATgetNext(formals); - } - } - - /* Generically substitute in subterms. */ - - if (ATgetType(e) == AT_APPL) { - AFun fun = ATgetAFun(e); - int arity = ATgetArity(fun); - ATermList args = ATempty; - - for (int i = arity - 1; i >= 0; i--) - args = ATinsert(args, substExpr(x, rep, ATgetArgument(e, i))); - - return (ATerm) ATmakeApplList(fun, args); - } - - if (ATgetType(e) == AT_LIST) { - ATermList in = (ATermList) e; - ATermList out = ATempty; - - while (!ATisEmpty(in)) { - out = ATinsert(out, substExpr(x, rep, ATgetFirst(in))); - in = ATgetNext(in); - } - - return (ATerm) ATreverse(out); - } - - throw badTerm("do not know how to substitute", e); -} - - -static Expr substExprMany(ATermList formals, ATermList args, Expr body) -{ - char * s; - Expr e; - - /* !!! check args against formals */ - - while (!ATisEmpty(args)) { - ATerm tup = ATgetFirst(args); - if (!ATmatch(tup, "(, )", &s, &e)) - throw badTerm("expected an argument tuple", tup); - - body = substExpr(s, e, body); - - args = ATgetNext(args); - } - - return body; -} - - -static PathSet nixExprRootsCached(EvalState & state, const Path & nePath) -{ - PkgPaths::iterator i = state.pkgPaths.find(nePath); - if (i != state.pkgPaths.end()) - return i->second; - else { - PathSet paths = nixExprRoots(nePath); - state.pkgPaths[nePath] = paths; - return paths; - } -} - - -static Hash hashPackage(EvalState & state, NixExpr ne) -{ - if (ne.type == NixExpr::neDerivation) { - PathSet inputs2; - for (PathSet::iterator i = ne.derivation.inputs.begin(); - i != ne.derivation.inputs.end(); i++) - { - PkgHashes::iterator j = state.pkgHashes.find(*i); - if (j == state.pkgHashes.end()) - throw Error(format("don't know expression `%1%'") % (string) *i); - inputs2.insert(j->second); - } - ne.derivation.inputs = inputs2; - } - return hashTerm(unparseNixExpr(ne)); -} - - -static string processBinding(EvalState & state, Expr e, NixExpr & ne) -{ - char * s1; - - if (ATmatch(e, "NixExpr()", &s1)) { - Path nePath(s1); - PathSet paths = nixExprRootsCached(state, nePath); - if (paths.size() != 1) abort(); - Path path = *(paths.begin()); - ne.derivation.inputs.insert(nePath); - return path; - } - - if (ATmatch(e, "", &s1)) - return s1; - - if (ATmatch(e, "True")) return "1"; - - if (ATmatch(e, "False")) return ""; - - ATermList l; - if (ATmatch(e, "[]", &l)) { - string s; - bool first = true; - while (!ATisEmpty(l)) { - if (!first) s = s + " "; else first = false; - s += processBinding(state, evalExpr(state, ATgetFirst(l)), ne); - l = ATgetNext(l); - } - return s; - } - - throw badTerm("invalid package binding", e); -} - - +#if 0 static Expr evalExpr2(EvalState & state, Expr e) { - char * s1; - Expr e1, e2, e3, e4; - ATermList bnds; - - /* Normal forms. */ - if (ATmatch(e, "", &s1) || - ATmatch(e, "[]", &e1) || - ATmatch(e, "True") || - ATmatch(e, "False") || - ATmatch(e, "Function([], )", &e1, &e2) || - ATmatch(e, "NixExpr()", &s1)) - return e; - - try { - Hash pkgHash = hashPackage(state, parseNixExpr(e)); - Path pkgPath = writeTerm(e, ""); - state.pkgHashes[pkgPath] = pkgHash; - return ATmake("NixExpr()", pkgPath.c_str()); - } catch (...) { /* !!! catch parse errors only */ - } - - /* Application. */ - if (ATmatch(e, "Call(, [])", &e1, &e2) || - ATmatch(e, "App(, [])", &e1, &e2)) { - e1 = evalExpr(state, e1); - if (!ATmatch(e1, "Function([], )", &e3, &e4)) - throw badTerm("expecting a function", e1); - return evalExpr(state, - substExprMany((ATermList) e3, (ATermList) e2, e4)); - } - /* Conditional. */ if (ATmatch(e, "If(, , )", &e1, &e2, &e3)) { e1 = evalExpr(state, e1); @@ -226,127 +56,6 @@ static Expr evalExpr2(EvalState & state, Expr e) ATmake("True") : ATmake("False"); } - /* Platform constant. */ - if (ATmatch(e, "Platform")) { - return ATmake("", thisSystem.c_str()); - } - - /* Fix inclusion. */ - if (ATmatch(e, "IncludeFix()", &s1)) { - Path fileName(s1); - return evalFile(state, s1); - } - - /* Relative files. */ - if (ATmatch(e, "Relative()", &s1)) { - Path srcPath = s1; - Path dstPath = addToStore(srcPath); - - ClosureElem elem; - NixExpr ne; - ne.type = NixExpr::neClosure; - ne.closure.roots.insert(dstPath); - ne.closure.elems[dstPath] = elem; - - Hash pkgHash = hashPackage(state, ne); - Path pkgPath = writeTerm(unparseNixExpr(ne), ""); - state.pkgHashes[pkgPath] = pkgHash; - - msg(lvlChatty, format("copied `%1%' -> closure `%2%'") - % srcPath % pkgPath); - - return ATmake("NixExpr()", pkgPath.c_str()); - } - - /* Packages are transformed into Nix derivation expressions. */ - if (ATmatch(e, "Package([])", &bnds)) { - - /* Evaluate the bindings and put them in a map. */ - map bndMap; - bndMap["platform"] = ATmake("", thisSystem.c_str()); - while (!ATisEmpty(bnds)) { - ATerm bnd = ATgetFirst(bnds); - if (!ATmatch(bnd, "(, )", &s1, &e1)) - throw badTerm("binding expected", bnd); - bndMap[s1] = evalExpr(state, e1); - bnds = ATgetNext(bnds); - } - - /* Gather information for building the derivation - expression. */ - NixExpr ne; - ne.type = NixExpr::neDerivation; - ne.derivation.platform = thisSystem; - string name; - Path outPath; - Hash outHash; - bool outHashGiven = false; - bnds = ATempty; - - for (map::iterator it = bndMap.begin(); - it != bndMap.end(); it++) - { - string key = it->first; - ATerm value = it->second; - - if (key == "args") { - ATermList args; - if (!ATmatch(value, "[]", &args)) - throw badTerm("list expected", value); - - while (!ATisEmpty(args)) { - Expr arg = evalExpr(state, ATgetFirst(args)); - ne.derivation.args.push_back(processBinding(state, arg, ne)); - args = ATgetNext(args); - } - } - - else { - string s = processBinding(state, value, ne); - ne.derivation.env[key] = s; - - if (key == "build") ne.derivation.builder = s; - if (key == "name") name = s; - if (key == "outPath") outPath = s; - if (key == "id") { - outHash = parseHash(s); - outHashGiven = true; - } - } - - bnds = ATinsert(bnds, - ATmake("(, )", key.c_str(), value)); - } - - if (ne.derivation.builder == "") - throw badTerm("no builder specified", e); - - if (name == "") - throw badTerm("no package name specified", e); - - /* Determine the output path. */ - if (!outHashGiven) outHash = hashPackage(state, ne); - if (outPath == "") - /* Hash the Nix expression with no outputs to produce a - unique but deterministic path name for this package. */ - outPath = - canonPath(nixStore + "/" + ((string) outHash).c_str() + "-" + name); - ne.derivation.env["out"] = outPath; - ne.derivation.outputs.insert(outPath); - - /* Write the resulting term into the Nix store directory. */ - Hash pkgHash = outHashGiven - ? hashString((string) outHash + outPath) - : hashPackage(state, ne); - Path pkgPath = writeTerm(unparseNixExpr(ne), "-d-" + name); - state.pkgHashes[pkgPath] = pkgHash; - - msg(lvlChatty, format("instantiated `%1%' -> `%2%'") - % name % pkgPath); - - return ATmake("NixExpr()", pkgPath.c_str()); - } - /* BaseName primitive function. */ if (ATmatch(e, "BaseName()", &e1)) { e1 = evalExpr(state, e1); @@ -355,8 +64,6 @@ static Expr evalExpr2(EvalState & state, Expr e) return ATmake("", baseNameOf(s1).c_str()); } - /* Barf. */ - throw badTerm("invalid expression", e); } #endif @@ -374,17 +81,27 @@ static Expr evalStdin(EvalState & state) static void printNixExpr(EvalState & state, Expr e) { ATermList es; - char * s; - if (ATmatch(e, "NixExpr()", &s)) { - cout << format("%1%\n") % s; - } - else if (ATmatch(e, "[]", &es)) { + + if (ATmatch(e, "Attrs([])", &es)) { + Expr a = queryAttr(e, "type"); + if (a && evalString(state, a) == "derivation") { + a = queryAttr(e, "drvPath"); + if (a) { + cout << format("%1%\n") % evalPath(state, a); + return; + } + } + } + + if (ATmatch(e, "[]", &es)) { while (!ATisEmpty(es)) { printNixExpr(state, evalExpr(state, ATgetFirst(es))); es = ATgetNext(es); } + return; } - else throw badTerm("top level does not evaluate to a (list of) Nix expression(s)", e); + + throw badTerm("top level does not evaluate to one or more Nix expressions", e); } @@ -435,6 +152,8 @@ void run(Strings args) Expr e = evalFile(state, absPath(*it)); printNixExpr(state, e); } + + printEvalStats(state); } diff --git a/src/fix-ng/fix.sdf b/src/fix-ng/fix.sdf index 2074d9829..cae5d2748 100644 --- a/src/fix-ng/fix.sdf +++ b/src/fix-ng/fix.sdf @@ -44,6 +44,9 @@ exports "{" {Id ","}* "}" ":" Expr -> Expr {cons("Function"), right} + "rec" "{" {Bind ","}* "}" + -> Expr {cons("Rec")} + "{" {Bind ","}* "}" -> Expr {cons("Attrs")} @@ -71,6 +74,7 @@ exports sorts Id Path lexical syntax [a-zA-Z\_][a-zA-Z0-9\_\']* -> Id + "rec" -> Id {reject} [0-9]+ -> Int "\"" ~[\n\"]* "\"" -> Str PathComp ("/" PathComp)+ -> Path diff --git a/src/fix-ng/parser.cc b/src/fix-ng/parser.cc index d146ad88c..d310397c2 100644 --- a/src/fix-ng/parser.cc +++ b/src/fix-ng/parser.cc @@ -40,7 +40,7 @@ struct Cleanup : TermFun string path(s); if (path[0] != '/') path = basePath + "/" + path; - return ATmake("Str()", canonPath(path).c_str()); + return ATmake("Path()", canonPath(path).c_str()); } if (ATmatch(e, "Int()", &s)) { diff --git a/src/fix-ng/primops.cc b/src/fix-ng/primops.cc new file mode 100644 index 000000000..f86f9eb38 --- /dev/null +++ b/src/fix-ng/primops.cc @@ -0,0 +1,206 @@ +#include "primops.hh" +#include "normalise.hh" +#include "globals.hh" + + +Expr primImport(EvalState & state, Expr arg) +{ + char * path; + if (!ATmatch(arg, "Path()", &path)) + throw badTerm("path expected", arg); + return evalFile(state, path); +} + + +static PathSet nixExprRootsCached(EvalState & state, const Path & nePath) +{ + DrvPaths::iterator i = state.drvPaths.find(nePath); + if (i != state.drvPaths.end()) + return i->second; + else { + PathSet paths = nixExprRoots(nePath); + state.drvPaths[nePath] = paths; + return paths; + } +} + + +static Hash hashDerivation(EvalState & state, NixExpr ne) +{ + if (ne.type == NixExpr::neDerivation) { + PathSet inputs2; + for (PathSet::iterator i = ne.derivation.inputs.begin(); + i != ne.derivation.inputs.end(); i++) + { + DrvHashes::iterator j = state.drvHashes.find(*i); + if (j == state.drvHashes.end()) + throw Error(format("don't know expression `%1%'") % (string) *i); + inputs2.insert(j->second); + } + ne.derivation.inputs = inputs2; + } + return hashTerm(unparseNixExpr(ne)); +} + + +static Path copyAtom(EvalState & state, const Path & srcPath) +{ + /* !!! should be cached */ + Path dstPath(addToStore(srcPath)); + + ClosureElem elem; + NixExpr ne; + ne.type = NixExpr::neClosure; + ne.closure.roots.insert(dstPath); + ne.closure.elems[dstPath] = elem; + + Hash drvHash = hashDerivation(state, ne); + Path drvPath = writeTerm(unparseNixExpr(ne), ""); + state.drvHashes[drvPath] = drvHash; + + msg(lvlChatty, format("copied `%1%' -> closure `%2%'") + % srcPath % drvPath); + return drvPath; +} + + +static string addInput(EvalState & state, + Path & nePath, NixExpr & ne) +{ + PathSet paths = nixExprRootsCached(state, nePath); + if (paths.size() != 1) abort(); + Path path = *(paths.begin()); + ne.derivation.inputs.insert(nePath); + return path; +} + + +static string processBinding(EvalState & state, Expr e, NixExpr & ne) +{ + e = evalExpr(state, e); + + char * s; + ATermList es; + + if (ATmatch(e, "Str()", &s)) return s; + if (ATmatch(e, "Uri()", &s)) return s; + if (ATmatch(e, "True")) return "1"; + if (ATmatch(e, "False")) return ""; + + if (ATmatch(e, "Attrs([])", &es)) { + Expr a = queryAttr(e, "type"); + if (a && evalString(state, a) == "derivation") { + a = queryAttr(e, "drvPath"); + if (a) { + Path drvPath = evalPath(state, a); + return addInput(state, drvPath, ne); + } + } + } + + if (ATmatch(e, "Path()", &s)) { + Path drvPath = copyAtom(state, s); + return addInput(state, drvPath, ne); + } + + if (ATmatch(e, "List([])", &es)) { + string s; + bool first = true; + while (!ATisEmpty(es)) { + Nest nest(lvlVomit, format("processing list element")); + if (!first) s = s + " "; else first = false; + s += processBinding(state, evalExpr(state, ATgetFirst(es)), ne); + es = ATgetNext(es); + } + return s; + } + + throw badTerm("invalid derivation binding", e); +} + + +Expr primDerivation(EvalState & state, Expr args) +{ + Nest nest(lvlVomit, "evaluating derivation"); + + Attrs attrs; + args = evalExpr(state, args); + queryAllAttrs(args, attrs); + + /* Build the derivation expression by processing the attributes. */ + NixExpr ne; + ne.type = NixExpr::neDerivation; + + string drvName; + Path outPath; + Hash outHash; + bool outHashGiven = false; + + for (Attrs::iterator i = attrs.begin(); i != attrs.end(); i++) { + string key = i->first; + Expr value = i->second; + Nest nest(lvlVomit, format("processing attribute `%1%'") % key); + + /* The `args' attribute is special: it supplies the + command-line arguments to the builder. */ + if (key == "args") { + ATermList args; + if (!ATmatch(value, "[]", &args)) + throw badTerm("list expected", value); + while (!ATisEmpty(args)) { + Expr arg = evalExpr(state, ATgetFirst(args)); + ne.derivation.args.push_back(processBinding(state, arg, ne)); + args = ATgetNext(args); + } + } + + /* All other attributes are passed to the builder through the + environment. */ + else { + string s = processBinding(state, value, ne); + ne.derivation.env[key] = s; + if (key == "builder") ne.derivation.builder = s; + else if (key == "system") ne.derivation.platform = s; + else if (key == "name") drvName = s; + else if (key == "outPath") outPath = s; + else if (key == "id") { + outHash = parseHash(s); + outHashGiven = true; + } + } + } + + /* Do we have all required attributes? */ + if (ne.derivation.builder == "") + throw badTerm("required attribute `builder' missing", args); + if (ne.derivation.platform == "") + throw badTerm("required attribute `system' missing", args); + if (drvName == "") + throw badTerm("required attribute `name' missing", args); + + /* Determine the output path. */ + if (!outHashGiven) outHash = hashDerivation(state, ne); + if (outPath == "") + /* Hash the Nix expression with no outputs to produce a + unique but deterministic path name for this derivation. */ + outPath = canonPath(nixStore + "/" + + ((string) outHash).c_str() + "-" + drvName); + ne.derivation.env["out"] = outPath; + ne.derivation.outputs.insert(outPath); + + /* Write the resulting term into the Nix store directory. */ + Hash drvHash = outHashGiven + ? hashString((string) outHash + outPath) + : hashDerivation(state, ne); + Path drvPath = writeTerm(unparseNixExpr(ne), "-d-" + drvName); + state.drvHashes[drvPath] = drvHash; + + msg(lvlChatty, format("instantiated `%1%' -> `%2%'") + % drvName % drvPath); + + attrs["outPath"] = ATmake("Path()", outPath.c_str()); + attrs["drvPath"] = ATmake("Path()", drvPath.c_str()); + attrs["type"] = ATmake("Str(\"derivation\")"); + + return makeAttrs(attrs); +} diff --git a/src/fix-ng/primops.hh b/src/fix-ng/primops.hh new file mode 100644 index 000000000..41b572c68 --- /dev/null +++ b/src/fix-ng/primops.hh @@ -0,0 +1,22 @@ +#ifndef __PRIMOPS_H +#define __PRIMOPS_H + +#include "eval.hh" + + +/* Load and evaluate an expression from path specified by the + argument. */ +Expr primImport(EvalState & state, Expr arg); + + +/* Construct (as a unobservable) side effect) a Nix derivation + expression that performs the derivation described by the argument + set. Returns the original set extended with the following + attributes: `outPath' containing the primary output path of the + derivation; `drvPath' containing the path of the Nix expression; + and `type' set to `derivation' to indicate that this is a + derivation. */ +Expr primDerivation(EvalState & state, Expr args); + + +#endif /* !__PRIMOPS_H */ From 449411e5113084da323d265f1b1313d9a5ca64aa Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 31 Oct 2003 19:20:03 +0000 Subject: [PATCH 0295/6440] * Typo fix. --- externals/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/externals/Makefile.am b/externals/Makefile.am index c644c0569..117973fa3 100644 --- a/externals/Makefile.am +++ b/externals/Makefile.am @@ -60,7 +60,7 @@ SDF2 = sdf2-bundle-1.6 $(SDF2).tar.gz: @echo "Nix requires the SDF2 bundle to build." @echo "Please download version 1.6 from" - @echo " ftp://ftp.stratego-language.org/pub/stratego/sdf2/sdf2-bundle-1.6.tar.gzP" + @echo " ftp://ftp.stratego-language.org/pub/stratego/sdf2/sdf2-bundle-1.6.tar.gz" @echo "and place it in the externals/ directory." false From a2a9bacd8296ed1ddb6105b649a062fe65c27759 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 1 Nov 2003 19:10:19 +0000 Subject: [PATCH 0296/6440] * Filter the substitution list when descending into a recursive attribute set. --- src/fix-ng/fix-expr.cc | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/fix-ng/fix-expr.cc b/src/fix-ng/fix-expr.cc index ff0b7d43d..96cb13b72 100644 --- a/src/fix-ng/fix-expr.cc +++ b/src/fix-ng/fix-expr.cc @@ -97,7 +97,20 @@ ATerm substitute(Subs & subs, ATerm e) substitute(subs2, body)); } - /* !!! Rec(...) */ + /* Idem for a mutually recursive attribute set. */ + ATermList bindings; + if (ATmatch(e, "Rec([])", &bindings)) { + Subs subs2(subs); + ATermList bnds = bindings; + while (!ATisEmpty(bnds)) { + Expr e; + if (!ATmatch(ATgetFirst(bnds), "Bind(, )", &s, &e)) + abort(); /* can't happen */ + subs2.erase(s); + bnds = ATgetNext(bnds); + } + return ATmake("Rec()", substitute(subs2, (ATerm) bindings)); + } if (ATgetType(e) == AT_APPL) { AFun fun = ATgetAFun(e); From 1b4184ccbb01634792897d3412c489b989674567 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 1 Nov 2003 19:10:41 +0000 Subject: [PATCH 0297/6440] * Let syntax. --- src/fix-ng/eval.cc | 5 +++++ src/fix-ng/fix.sdf | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/src/fix-ng/eval.cc b/src/fix-ng/eval.cc index 785467741..46bb1f941 100644 --- a/src/fix-ng/eval.cc +++ b/src/fix-ng/eval.cc @@ -160,6 +160,11 @@ Expr evalExpr2(EvalState & state, Expr e) if (ATmatch(e, "Rec([])", &bnds)) return expandRec(e, (ATermList) bnds); + /* Let expressions `let {..., body = ...}' are just desugared + into `(rec {..., body = ...}).body'. */ + if (ATmatch(e, "LetRec()", &e1)) + return evalExpr(state, ATmake("Select(Rec(), \"body\")", e1)); + /* Barf. */ throw badTerm("invalid expression", e); } diff --git a/src/fix-ng/fix.sdf b/src/fix-ng/fix.sdf index cae5d2748..251a63f0c 100644 --- a/src/fix-ng/fix.sdf +++ b/src/fix-ng/fix.sdf @@ -47,6 +47,9 @@ exports "rec" "{" {Bind ","}* "}" -> Expr {cons("Rec")} + "let" "{" {Bind ","}* "}" + -> Expr {cons("LetRec")} + "{" {Bind ","}* "}" -> Expr {cons("Attrs")} @@ -59,6 +62,12 @@ exports Expr "." Id -> Expr {cons("Select")} + "if" Expr "then" Expr "else" Expr + -> Expr {cons("If")} + + Expr "==" Expr + -> Expr {cons("OpEq")} + context-free priorities Expr "." Id -> Expr From 16104446712acf7e381039199eaf39dfa0fcea35 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 1 Nov 2003 19:15:08 +0000 Subject: [PATCH 0298/6440] * Conditions, string equality. --- src/fix-ng/eval.cc | 24 ++++++++++++++++++++++++ src/fix-ng/fix.cc | 6 ------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/fix-ng/eval.cc b/src/fix-ng/eval.cc index 46bb1f941..c58a06dff 100644 --- a/src/fix-ng/eval.cc +++ b/src/fix-ng/eval.cc @@ -106,6 +106,15 @@ Path evalPath(EvalState & state, Expr e) } +bool evalBool(EvalState & state, Expr e) +{ + e = evalExpr(state, e); + if (ATmatch(e, "True")) return true; + else if (ATmatch(e, "False")) return false; + else throw badTerm("expecting a boolean", e); +} + + Expr evalExpr2(EvalState & state, Expr e) { Expr e1, e2, e3, e4; @@ -165,6 +174,21 @@ Expr evalExpr2(EvalState & state, Expr e) if (ATmatch(e, "LetRec()", &e1)) return evalExpr(state, ATmake("Select(Rec(), \"body\")", e1)); + /* Conditionals. */ + if (ATmatch(e, "If(, , )", &e1, &e2, &e3)) { + if (evalBool(state, e1)) + return evalExpr(state, e2); + else + return evalExpr(state, e3); + } + + /* Equality. Just strings for now. */ + if (ATmatch(e, "OpEq(, )", &e1, &e2)) { + string s1 = evalString(state, e1); + string s2 = evalString(state, e2); + return s1 == s2 ? ATmake("True") : ATmake("False"); + } + /* Barf. */ throw badTerm("invalid expression", e); } diff --git a/src/fix-ng/fix.cc b/src/fix-ng/fix.cc index d791461bd..1c37a0b7b 100644 --- a/src/fix-ng/fix.cc +++ b/src/fix-ng/fix.cc @@ -32,12 +32,6 @@ static Expr evalExpr2(EvalState & state, Expr e) { /* Conditional. */ if (ATmatch(e, "If(, , )", &e1, &e2, &e3)) { - e1 = evalExpr(state, e1); - Expr x; - if (ATmatch(e1, "True")) x = e2; - else if (ATmatch(e1, "False")) x = e3; - else throw badTerm("expecting a boolean", e1); - return evalExpr(state, x); } /* Ad-hoc function for string matching. */ From 7de1b2a6980f71cfbf36f7250e247f6eafd763d9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 1 Nov 2003 21:11:52 +0000 Subject: [PATCH 0299/6440] * Print the exit code of the builder. --- src/libnix/db.cc | 1 - src/libnix/exec.cc | 12 ++++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/libnix/db.cc b/src/libnix/db.cc index 2f53ca3b5..c498fab74 100644 --- a/src/libnix/db.cc +++ b/src/libnix/db.cc @@ -251,7 +251,6 @@ void Database::close() for (map::iterator i = tables.begin(); i != tables.end(); i++) { - debug(format("closing table %1%") % i->first); Db * db = i->second; db->close(DB_NOSYNC); delete db; diff --git a/src/libnix/exec.cc b/src/libnix/exec.cc index 4934712f9..00d9e6a0a 100644 --- a/src/libnix/exec.cc +++ b/src/libnix/exec.cc @@ -111,9 +111,17 @@ void runProgram(const string & program, if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { if (keepFailed) { msg(lvlTalkative, - format("build failed; keeping build directory `%1%'") % tmpDir); + format("program `%1%' failed; keeping build directory `%2%'") + % program % tmpDir); delTmpDir.cancel(); } - throw Error("unable to build package"); + if (WIFEXITED(status)) + throw Error(format("program `%1%' failed with exit code %2%") + % program % WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + throw Error(format("program `%1%' failed due to signal %2%") + % program % WTERMSIG(status)); + else + throw Error(format("program `%1%' died abnormally") % program); } } From c8268ca9917061466a3448028ea524d9842e1ac9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 1 Nov 2003 23:29:02 +0000 Subject: [PATCH 0300/6440] * Fast builds. --- src/libnix/normalise.cc | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/libnix/normalise.cc b/src/libnix/normalise.cc index 0ce38d68a..d3978ac2c 100644 --- a/src/libnix/normalise.cc +++ b/src/libnix/normalise.cc @@ -127,24 +127,17 @@ Path normaliseNixExpr(const Path & _nePath, PathSet pending) i != ne.derivation.env.end(); i++) env[i->first] = i->second; - /* We can skip running the builder if we can expand all output - paths from their ids. */ - bool fastBuild = false; -#if 0 + /* We can skip running the builder if all output paths are already + valid. */ bool fastBuild = true; for (PathSet::iterator i = ne.derivation.outputs.begin(); i != ne.derivation.outputs.end(); i++) { - try { - expandId(i->second, i->first, "/", pending); - } catch (Error & e) { - debug(format("fast build failed for `%1%': %2%") - % i->first % e.what()); + if (!isValidPath(*i)) { fastBuild = false; break; } } -#endif if (!fastBuild) { From adf9a45469f55258446d383333aa2ca79cfb0536 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 2 Nov 2003 16:31:35 +0000 Subject: [PATCH 0301/6440] * Primops: baseNameOf, toString. --- src/fix-ng/eval.cc | 2 ++ src/fix-ng/fix.cc | 4 ---- src/fix-ng/primops.cc | 19 +++++++++++++++++++ src/fix-ng/primops.hh | 9 +++++++++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/fix-ng/eval.cc b/src/fix-ng/eval.cc index c58a06dff..38a1d81fc 100644 --- a/src/fix-ng/eval.cc +++ b/src/fix-ng/eval.cc @@ -145,6 +145,8 @@ Expr evalExpr2(EvalState & state, Expr e) string primop(s1); if (primop == "import") return primImport(state, e2); if (primop == "derivation") return primDerivation(state, e2); + if (primop == "toString") return primToString(state, e2); + if (primop == "baseNameOf") return primBaseNameOf(state, e2); else throw badTerm("undefined variable/primop", e1); } diff --git a/src/fix-ng/fix.cc b/src/fix-ng/fix.cc index 1c37a0b7b..c24ca4d9c 100644 --- a/src/fix-ng/fix.cc +++ b/src/fix-ng/fix.cc @@ -30,10 +30,6 @@ static Path searchPath(const Paths & searchDirs, const Path & relPath) #if 0 static Expr evalExpr2(EvalState & state, Expr e) { - /* Conditional. */ - if (ATmatch(e, "If(, , )", &e1, &e2, &e3)) { - } - /* Ad-hoc function for string matching. */ if (ATmatch(e, "HasSubstr(, )", &e1, &e2)) { e1 = evalExpr(state, e1); diff --git a/src/fix-ng/primops.cc b/src/fix-ng/primops.cc index f86f9eb38..7d060124b 100644 --- a/src/fix-ng/primops.cc +++ b/src/fix-ng/primops.cc @@ -204,3 +204,22 @@ Expr primDerivation(EvalState & state, Expr args) return makeAttrs(attrs); } + + +Expr primBaseNameOf(EvalState & state, Expr arg) +{ + string s = evalString(state, arg); + return ATmake("Str()", baseNameOf(s).c_str()); +} + + +Expr primToString(EvalState & state, Expr arg) +{ + arg = evalExpr(state, arg); + char * s; + if (ATmatch(arg, "Str()", &s) || + ATmatch(arg, "Path()", &s) || + ATmatch(arg, "Uri()", &s)) + return ATmake("Str()", s); + else throw badTerm("cannot coerce to string", arg); +} diff --git a/src/fix-ng/primops.hh b/src/fix-ng/primops.hh index 41b572c68..e48883b0b 100644 --- a/src/fix-ng/primops.hh +++ b/src/fix-ng/primops.hh @@ -19,4 +19,13 @@ Expr primImport(EvalState & state, Expr arg); Expr primDerivation(EvalState & state, Expr args); +/* Return the base name of the given string, i.e., everything + following the last slash. */ +Expr primBaseNameOf(EvalState & state, Expr arg); + + +/* Convert the argument (which can be a path or a uri) to a string. */ +Expr primToString(EvalState & state, Expr arg); + + #endif /* !__PRIMOPS_H */ From 40986312bb00f0101a6634db42080daee03f887b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 2 Nov 2003 17:36:15 +0000 Subject: [PATCH 0302/6440] * Boolean constants. --- src/fix-ng/eval.cc | 7 ++++--- src/fix-ng/fix.sdf | 12 ++++++++++++ src/fix-ng/parser.cc | 6 ++++++ src/fix-ng/primops.cc | 4 ++-- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/fix-ng/eval.cc b/src/fix-ng/eval.cc index 38a1d81fc..726bc5dae 100644 --- a/src/fix-ng/eval.cc +++ b/src/fix-ng/eval.cc @@ -109,8 +109,8 @@ Path evalPath(EvalState & state, Expr e) bool evalBool(EvalState & state, Expr e) { e = evalExpr(state, e); - if (ATmatch(e, "True")) return true; - else if (ATmatch(e, "False")) return false; + if (ATmatch(e, "Bool(True)")) return true; + else if (ATmatch(e, "Bool(False)")) return false; else throw badTerm("expecting a boolean", e); } @@ -124,6 +124,7 @@ Expr evalExpr2(EvalState & state, Expr e) if (ATmatch(e, "Str()", &s1) || ATmatch(e, "Path()", &s1) || ATmatch(e, "Uri()", &s1) || + ATmatch(e, "Bool()", &e1) || ATmatch(e, "Function([], )", &e1, &e2) || ATmatch(e, "Attrs([])", &e1) || ATmatch(e, "List([])", &e1)) @@ -188,7 +189,7 @@ Expr evalExpr2(EvalState & state, Expr e) if (ATmatch(e, "OpEq(, )", &e1, &e2)) { string s1 = evalString(state, e1); string s2 = evalString(state, e2); - return s1 == s2 ? ATmake("True") : ATmake("False"); + return s1 == s2 ? ATmake("Bool(True)") : ATmake("Bool(False)"); } /* Barf. */ diff --git a/src/fix-ng/fix.sdf b/src/fix-ng/fix.sdf index 251a63f0c..8effe6d21 100644 --- a/src/fix-ng/fix.sdf +++ b/src/fix-ng/fix.sdf @@ -68,6 +68,9 @@ exports Expr "==" Expr -> Expr {cons("OpEq")} + Bool + -> Expr {cons("Bool")} + context-free priorities Expr "." Id -> Expr @@ -84,10 +87,19 @@ exports lexical syntax [a-zA-Z\_][a-zA-Z0-9\_\']* -> Id "rec" -> Id {reject} + "true" -> Id {reject} + "false" -> Id {reject} + [0-9]+ -> Int + "\"" ~[\n\"]* "\"" -> Str + PathComp ("/" PathComp)+ -> Path [a-zA-Z0-9\.\_\-]+ -> PathComp + + "true" -> Bool + "false" -> Bool + lexical restrictions Id -/- [a-zA-Z0-9\_\'] Int -/- [0-9] diff --git a/src/fix-ng/parser.cc b/src/fix-ng/parser.cc index d310397c2..7dff0363d 100644 --- a/src/fix-ng/parser.cc +++ b/src/fix-ng/parser.cc @@ -50,6 +50,12 @@ struct Cleanup : TermFun return ATmake("Int()", n); } + if (ATmatch(e, "Bool(\"true\")", &s)) + return ATmake("Bool(True)"); + + if (ATmatch(e, "Bool(\"false\")", &s)) + return ATmake("Bool(False)"); + return e; } }; diff --git a/src/fix-ng/primops.cc b/src/fix-ng/primops.cc index 7d060124b..f887d265f 100644 --- a/src/fix-ng/primops.cc +++ b/src/fix-ng/primops.cc @@ -84,8 +84,8 @@ static string processBinding(EvalState & state, Expr e, NixExpr & ne) if (ATmatch(e, "Str()", &s)) return s; if (ATmatch(e, "Uri()", &s)) return s; - if (ATmatch(e, "True")) return "1"; - if (ATmatch(e, "False")) return ""; + if (ATmatch(e, "Bool(True)")) return "1"; + if (ATmatch(e, "Bool(False)")) return ""; if (ATmatch(e, "Attrs([])", &es)) { Expr a = queryAttr(e, "type"); From ad0976f8d5f2afbca4e2fe6cbb3d2c2e53760222 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 3 Nov 2003 10:21:30 +0000 Subject: [PATCH 0303/6440] * Grammar changes. Attributes in attribute sets are now delimited with semicolons instead of comma's. Final semicolon in the set is optional. --- src/fix-ng/fix.sdf | 57 ++++++++++++++++---------------------------- src/fix-ng/parser.cc | 3 +++ 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/src/fix-ng/fix.sdf b/src/fix-ng/fix.sdf index 8effe6d21..e09480314 100644 --- a/src/fix-ng/fix.sdf +++ b/src/fix-ng/fix.sdf @@ -20,60 +20,45 @@ exports sorts Expr Bind context-free syntax - Id - -> Expr {cons("Var")} + Id -> Expr {cons("Var")} - Int - -> Expr {cons("Int")} + Int -> Expr {cons("Int")} - Str - -> Expr {cons("Str")} + Str -> Expr {cons("Str")} - Uri - -> Expr {cons("Uri")} + Uri -> Expr {cons("Uri")} - Path - -> Expr {cons("Path")} + Path -> Expr {cons("Path")} - "(" Expr ")" - -> Expr {bracket} + "(" Expr ")" -> Expr {bracket} - Expr Expr - -> Expr {cons("Call"), left} + Expr Expr -> Expr {cons("Call"), left} - "{" {Id ","}* "}" ":" Expr - -> Expr {cons("Function"), right} + "{" {Id ","}* "}" ":" Expr -> Expr {cons("Function"), right} - "rec" "{" {Bind ","}* "}" - -> Expr {cons("Rec")} + "rec" "{" Binds "}" -> Expr {cons("Rec")} + "let" "{" Binds "}" -> Expr {cons("LetRec")} + "{" Binds "}" -> Expr {cons("Attrs")} - "let" "{" {Bind ","}* "}" - -> Expr {cons("LetRec")} + Id "=" Expr -> Bind {cons("Bind")} + {Bind ";"}* -> Binds + Bind ";" -> BindSemi + BindSemi* -> Binds - "{" {Bind ","}* "}" - -> Expr {cons("Attrs")} + "[" Expr* "]" -> Expr {cons("List")} - Id "=" Expr - -> Bind {cons("Bind")} + Expr "." Id -> Expr {cons("Select")} - "[" {Expr ","}* "]" - -> Expr {cons("List")} + "if" Expr "then" Expr "else" Expr -> Expr {cons("If")} - Expr "." Id - -> Expr {cons("Select")} + Expr "==" Expr -> Expr {cons("OpEq")} - "if" Expr "then" Expr "else" Expr - -> Expr {cons("If")} - - Expr "==" Expr - -> Expr {cons("OpEq")} - - Bool - -> Expr {cons("Bool")} + Bool -> Expr {cons("Bool")} context-free priorities Expr "." Id -> Expr + > > Expr Expr -> Expr > "{" {Id ","}* "}" ":" Expr -> Expr diff --git a/src/fix-ng/parser.cc b/src/fix-ng/parser.cc index 7dff0363d..e159262ca 100644 --- a/src/fix-ng/parser.cc +++ b/src/fix-ng/parser.cc @@ -147,6 +147,9 @@ Expr parseExprFromFile(Path path) if (!imploded) throw Error(format("cannot implode parse tree")); + debug(format("imploded parse tree of `%1%': %2%") + % path % printTerm(imploded)); + /* Finally, clean it up. */ Cleanup cleanup; cleanup.basePath = dirOf(path); From e2655aa332a33b56d9168928511a598fc9b0c1e6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 3 Nov 2003 11:59:35 +0000 Subject: [PATCH 0304/6440] * Shorter list syntax ([a b c] instead of [a, b, c]). --- src/fix-ng/fix-expr.cc | 10 ++++------ src/fix-ng/fix.sdf | 6 ++++-- src/fix-ng/parser.cc | 7 +++++++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/fix-ng/fix-expr.cc b/src/fix-ng/fix-expr.cc index 96cb13b72..6333595c6 100644 --- a/src/fix-ng/fix-expr.cc +++ b/src/fix-ng/fix-expr.cc @@ -4,8 +4,6 @@ ATerm bottomupRewrite(TermFun & f, ATerm e) { - e = f(e); - if (ATgetType(e) == AT_APPL) { AFun fun = ATgetAFun(e); int arity = ATgetArity(fun); @@ -14,10 +12,10 @@ ATerm bottomupRewrite(TermFun & f, ATerm e) for (int i = arity - 1; i >= 0; i--) args = ATinsert(args, bottomupRewrite(f, ATgetArgument(e, i))); - return (ATerm) ATmakeApplList(fun, args); + e = (ATerm) ATmakeApplList(fun, args); } - if (ATgetType(e) == AT_LIST) { + else if (ATgetType(e) == AT_LIST) { ATermList in = (ATermList) e; ATermList out = ATempty; @@ -26,10 +24,10 @@ ATerm bottomupRewrite(TermFun & f, ATerm e) in = ATgetNext(in); } - return (ATerm) ATreverse(out); + e = (ATerm) ATreverse(out); } - return e; + return f(e); } diff --git a/src/fix-ng/fix.sdf b/src/fix-ng/fix.sdf index e09480314..8e9f0fa72 100644 --- a/src/fix-ng/fix.sdf +++ b/src/fix-ng/fix.sdf @@ -45,7 +45,9 @@ exports Bind ";" -> BindSemi BindSemi* -> Binds - "[" Expr* "]" -> Expr {cons("List")} + "[" ExprList "]" -> Expr {cons("List")} + "" -> ExprList {cons("ExprNil")} + Expr ExprList -> ExprList {cons("ExprCons")} Expr "." Id -> Expr {cons("Select")} @@ -58,7 +60,7 @@ exports context-free priorities Expr "." Id -> Expr - > + > Expr ExprList -> ExprList > Expr Expr -> Expr > "{" {Id ","}* "}" ":" Expr -> Expr diff --git a/src/fix-ng/parser.cc b/src/fix-ng/parser.cc index e159262ca..43678ec97 100644 --- a/src/fix-ng/parser.cc +++ b/src/fix-ng/parser.cc @@ -56,6 +56,13 @@ struct Cleanup : TermFun if (ATmatch(e, "Bool(\"false\")", &s)) return ATmake("Bool(False)"); + if (ATmatch(e, "ExprNil")) + return (ATerm) ATempty; + + ATerm e1, e2; + if (ATmatch(e, "ExprCons(, [])", &e1, &e2)) + return (ATerm) ATinsert((ATermList) e2, e1); + return e; } }; From ff3132427839888933c3779844bf35ca9e189cb9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 3 Nov 2003 18:21:53 +0000 Subject: [PATCH 0305/6440] * Ignore options passed to the aterm library. --- src/libmain/shared.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 80463308a..39439f8e1 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -26,12 +26,15 @@ static void initAndRun(int argc, char * * argv) while (argc--) args.push_back(*argv++); args.erase(args.begin()); - /* Expand compound dash options (i.e., `-qlf' -> `-q -l -f'). */ + /* Expand compound dash options (i.e., `-qlf' -> `-q -l -f'), and + ignore options for the ATerm library. */ for (Strings::iterator it = args.begin(); it != args.end(); ) { string arg = *it; - if (arg.length() > 2 && arg[0] == '-' && arg[1] != '-') { + if (string(arg, 0, 4) == "-at-") + it = args.erase(it); + else if (arg.length() > 2 && arg[0] == '-' && arg[1] != '-') { for (unsigned int i = 1; i < arg.length(); i++) if (isalpha(arg[i])) args.insert(it, (string) "-" + arg[i]); From 0690c1c9c01dd5889dbfccf2da6cb99f5c4e151b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 3 Nov 2003 20:30:40 +0000 Subject: [PATCH 0306/6440] * Work around problems with the ATerm library. The ATerm library doesn't search the heap for pointers to ATerms when garbage collecting. As a result, C++ containers such as `map' will cause pointer to be hidden from the garbage collector, causing crashes. Instead, we now use ATermTables. --- src/fix-ng/eval.cc | 56 +++++++++++-------- src/fix-ng/eval.hh | 3 +- src/fix-ng/fix-expr.cc | 122 ++++++++++++++++++++++++++++++++++------- src/fix-ng/fix-expr.hh | 40 ++++++++++++-- src/fix-ng/primops.cc | 16 +++--- 5 files changed, 182 insertions(+), 55 deletions(-) diff --git a/src/fix-ng/eval.cc b/src/fix-ng/eval.cc index 726bc5dae..90b7ff29f 100644 --- a/src/fix-ng/eval.cc +++ b/src/fix-ng/eval.cc @@ -5,6 +5,7 @@ EvalState::EvalState() + : normalForms(32768, 75) { blackHole = ATmake("BlackHole()"); if (!blackHole) throw Error("cannot build black hole"); @@ -20,32 +21,43 @@ Expr getAttr(EvalState & state, Expr e, const string & name) /* Substitute an argument set into the body of a function. */ static Expr substArgs(Expr body, ATermList formals, Expr arg) { - Subs subs; + ATermMap subs; Expr undefined = ATmake("Undefined"); /* Get the formal arguments. */ while (!ATisEmpty(formals)) { + ATerm t = ATgetFirst(formals); char * s; - if (!ATmatch(ATgetFirst(formals), "", &s)) + if (!ATmatch(t, "", &s)) abort(); /* can't happen */ - subs[s] = undefined; + subs.set(t, undefined); formals = ATgetNext(formals); } /* Get the actual arguments, and check that they match with the formals. */ - Attrs args; + ATermMap args; queryAllAttrs(arg, args); - for (Attrs::iterator i = args.begin(); i != args.end(); i++) { - if (subs.find(i->first) == subs.end()) - throw badTerm(format("argument `%1%' not declared") % i->first, arg); - subs[i->first] = i->second; + for (ATermList keys = args.keys(); !ATisEmpty(keys); + keys = ATgetNext(keys)) + { + Expr key = ATgetFirst(keys); + Expr cur = subs.get(key); + if (!cur) + throw badTerm(format("argument `%1%' not declared") + % aterm2String(key), arg); + subs.set(key, args.get(key)); } /* Check that all arguments are defined. */ - for (Subs::iterator i = subs.begin(); i != subs.end(); i++) - if (i->second == undefined) - throw badTerm(format("formal argument `%1%' missing") % i->first, arg); + for (ATermList keys = subs.keys(); !ATisEmpty(keys); + keys = ATgetNext(keys)) + { + Expr key = ATgetFirst(keys); + if (subs.get(key) == undefined) + throw badTerm(format("formal argument `%1%' missing") + % aterm2String(key), arg); + } return substitute(subs, body); } @@ -59,26 +71,26 @@ static Expr substArgs(Expr body, ATermList formals, Expr arg) ATerm expandRec(ATerm e, ATermList bnds) { /* Create the substitution list. */ - Subs subs; + ATermMap subs; ATermList bs = bnds; while (!ATisEmpty(bs)) { char * s; Expr e2; if (!ATmatch(ATgetFirst(bs), "Bind(, )", &s, &e2)) abort(); /* can't happen */ - subs[s] = ATmake("Select(, )", e, s); + subs.set(s, ATmake("Select(, )", e, s)); bs = ATgetNext(bs); } /* Create the non-recursive set. */ - Attrs as; + ATermMap as; bs = bnds; while (!ATisEmpty(bs)) { char * s; Expr e2; if (!ATmatch(ATgetFirst(bs), "Bind(, )", &s, &e2)) abort(); /* can't happen */ - as[s] = substitute(subs, e2); + as.set(s, substitute(subs, e2)); bs = ATgetNext(bs); } @@ -205,18 +217,18 @@ Expr evalExpr(EvalState & state, Expr e) /* Consult the memo table to quickly get the normal form of previously evaluated expressions. */ - NormalForms::iterator i = state.normalForms.find(e); - if (i != state.normalForms.end()) { - if (i->second == state.blackHole) + Expr nf = state.normalForms.get(e); + if (nf) { + if (nf == state.blackHole) throw badTerm("infinite recursion", e); state.nrCached++; - return i->second; + return nf; } /* Otherwise, evaluate and memoize. */ - state.normalForms[e] = state.blackHole; - Expr nf = evalExpr2(state, e); - state.normalForms[e] = nf; + state.normalForms.set(e, state.blackHole); + nf = evalExpr2(state, e); + state.normalForms.set(e, nf); return nf; } diff --git a/src/fix-ng/eval.hh b/src/fix-ng/eval.hh index 364f28471..9be3ae2da 100644 --- a/src/fix-ng/eval.hh +++ b/src/fix-ng/eval.hh @@ -7,13 +7,12 @@ #include "expr.hh" -typedef map NormalForms; typedef map DrvPaths; typedef map DrvHashes; struct EvalState { - NormalForms normalForms; + ATermMap normalForms; DrvPaths drvPaths; DrvHashes drvHashes; /* normalised derivation hashes */ Expr blackHole; diff --git a/src/fix-ng/fix-expr.cc b/src/fix-ng/fix-expr.cc index 6333595c6..814e186b4 100644 --- a/src/fix-ng/fix-expr.cc +++ b/src/fix-ng/fix-expr.cc @@ -2,6 +2,91 @@ #include "expr.hh" +ATermMap::ATermMap(unsigned int initialSize, unsigned int maxLoadPct) +{ + table = ATtableCreate(initialSize, maxLoadPct); + if (!table) throw Error("cannot create ATerm table"); +} + + +ATermMap::ATermMap(const ATermMap & map) + : table(0) +{ + ATermList keys = map.keys(); + + /* !!! adjust allocation for load pct */ + table = ATtableCreate(ATgetLength(keys), map.maxLoadPct); + if (!table) throw Error("cannot create ATerm table"); + + for (; !ATisEmpty(keys); keys = ATgetNext(keys)) { + ATerm key = ATgetFirst(keys); + set(key, map.get(key)); + } +} + + +ATermMap::~ATermMap() +{ + if (table) ATtableDestroy(table); +} + + +void ATermMap::set(ATerm key, ATerm value) +{ + return ATtablePut(table, key, value); +} + + +void ATermMap::set(const string & key, ATerm value) +{ + set(string2ATerm(key), value); +} + + +ATerm ATermMap::get(ATerm key) const +{ + return ATtableGet(table, key); +} + + +ATerm ATermMap::get(const string & key) const +{ + return get(string2ATerm(key)); +} + + +void ATermMap::remove(ATerm key) +{ + ATtableRemove(table, key); +} + + +void ATermMap::remove(const string & key) +{ + remove(string2ATerm(key)); +} + + +ATermList ATermMap::keys() const +{ + ATermList keys = ATtableKeys(table); + if (!keys) throw Error("cannot query aterm map keys"); + return keys; +} + + +ATerm string2ATerm(const string & s) +{ + return (ATerm) ATmakeAppl0(ATmakeAFun((char *) s.c_str(), 0, ATtrue)); +} + + +string aterm2String(ATerm t) +{ + return ATgetName(ATgetAFun(t)); +} + + ATerm bottomupRewrite(TermFun & f, ATerm e) { if (ATgetType(e) == AT_APPL) { @@ -31,7 +116,7 @@ ATerm bottomupRewrite(TermFun & f, ATerm e) } -void queryAllAttrs(Expr e, Attrs & attrs) +void queryAllAttrs(Expr e, ATermMap & attrs) { ATermList bnds; if (!ATmatch(e, "Attrs([])", &bnds)) @@ -42,7 +127,7 @@ void queryAllAttrs(Expr e, Attrs & attrs) Expr e; if (!ATmatch(ATgetFirst(bnds), "Bind(, )", &s, &e)) abort(); /* can't happen */ - attrs[s] = e; + attrs.set(s, e); bnds = ATgetNext(bnds); } } @@ -50,33 +135,32 @@ void queryAllAttrs(Expr e, Attrs & attrs) Expr queryAttr(Expr e, const string & name) { - Attrs attrs; + ATermMap attrs; queryAllAttrs(e, attrs); - Attrs::iterator i = attrs.find(name); - return i == attrs.end() ? 0 : i->second; + return attrs.get(name); } -Expr makeAttrs(const Attrs & attrs) +Expr makeAttrs(const ATermMap & attrs) { - ATermList bnds = ATempty; - for (Attrs::const_iterator i = attrs.begin(); i != attrs.end(); i++) + ATermList bnds = ATempty, keys = attrs.keys(); + while (!ATisEmpty(keys)) { + Expr key = ATgetFirst(keys); bnds = ATinsert(bnds, - ATmake("Bind(, )", i->first.c_str(), i->second)); + ATmake("Bind(, )", key, attrs.get(key))); + keys = ATgetNext(keys); + } return ATmake("Attrs()", ATreverse(bnds)); } -ATerm substitute(Subs & subs, ATerm e) +Expr substitute(const ATermMap & subs, Expr e) { char * s; if (ATmatch(e, "Var()", &s)) { - Subs::iterator i = subs.find(s); - if (i == subs.end()) - return e; - else - return i->second; + Expr sub = subs.get(s); + return sub ? sub : e; } /* In case of a function, filter out all variables bound by this @@ -84,11 +168,11 @@ ATerm substitute(Subs & subs, ATerm e) ATermList formals; ATerm body; if (ATmatch(e, "Function([], )", &formals, &body)) { - Subs subs2(subs); + ATermMap subs2(subs); ATermList fs = formals; while (!ATisEmpty(fs)) { if (!ATmatch(ATgetFirst(fs), "", &s)) abort(); - subs2.erase(s); + subs2.remove(s); fs = ATgetNext(fs); } return ATmake("Function(, )", formals, @@ -98,13 +182,13 @@ ATerm substitute(Subs & subs, ATerm e) /* Idem for a mutually recursive attribute set. */ ATermList bindings; if (ATmatch(e, "Rec([])", &bindings)) { - Subs subs2(subs); + ATermMap subs2(subs); ATermList bnds = bindings; while (!ATisEmpty(bnds)) { Expr e; if (!ATmatch(ATgetFirst(bnds), "Bind(, )", &s, &e)) abort(); /* can't happen */ - subs2.erase(s); + subs2.remove(s); bnds = ATgetNext(bnds); } return ATmake("Rec()", substitute(subs2, (ATerm) bindings)); diff --git a/src/fix-ng/fix-expr.hh b/src/fix-ng/fix-expr.hh index 700f7beca..93a010abe 100644 --- a/src/fix-ng/fix-expr.hh +++ b/src/fix-ng/fix-expr.hh @@ -14,6 +14,38 @@ typedef ATerm Expr; +/* Mappings from ATerms to ATerms. This is just a wrapper around + ATerm tables. */ +class ATermMap +{ +private: + unsigned int maxLoadPct; + ATermTable table; + +public: + ATermMap(unsigned int initialSize = 16, unsigned int maxLoadPct = 75); + ATermMap(const ATermMap & map); + ~ATermMap(); + + void set(ATerm key, ATerm value); + void set(const string & key, ATerm value); + + ATerm get(ATerm key) const; + ATerm get(const string & key) const; + + void remove(ATerm key); + void remove(const string & key); + + ATermList keys() const; +}; + + +/* Convert a string to an ATerm (i.e., a quoted nullary function + applicaton). */ +ATerm string2ATerm(const string & s); +string aterm2String(ATerm t); + + /* Generic bottomup traversal over ATerms. The traversal first recursively descends into subterms, and then applies the given term function to the resulting term. */ @@ -25,19 +57,17 @@ ATerm bottomupRewrite(TermFun & f, ATerm e); /* Query all attributes in an attribute set expression. The expression must be in normal form. */ -typedef map Attrs; -void queryAllAttrs(Expr e, Attrs & attrs); +void queryAllAttrs(Expr e, ATermMap & attrs); /* Query a specific attribute from an attribute set expression. The expression must be in normal form. */ Expr queryAttr(Expr e, const string & name); /* Create an attribute set expression from an Attrs value. */ -Expr makeAttrs(const Attrs & attrs); +Expr makeAttrs(const ATermMap & attrs); /* Perform a set of substitutions on an expression. */ -typedef map Subs; -ATerm substitute(Subs & subs, ATerm e); +Expr substitute(const ATermMap & subs, Expr e); #endif /* !__FIXEXPR_H */ diff --git a/src/fix-ng/primops.cc b/src/fix-ng/primops.cc index f887d265f..ef0fd354e 100644 --- a/src/fix-ng/primops.cc +++ b/src/fix-ng/primops.cc @@ -123,7 +123,7 @@ Expr primDerivation(EvalState & state, Expr args) { Nest nest(lvlVomit, "evaluating derivation"); - Attrs attrs; + ATermMap attrs; args = evalExpr(state, args); queryAllAttrs(args, attrs); @@ -136,9 +136,11 @@ Expr primDerivation(EvalState & state, Expr args) Hash outHash; bool outHashGiven = false; - for (Attrs::iterator i = attrs.begin(); i != attrs.end(); i++) { - string key = i->first; - Expr value = i->second; + for (ATermList keys = attrs.keys(); !ATisEmpty(keys); + keys = ATgetNext(keys)) + { + string key = aterm2String(ATgetFirst(keys)); + Expr value = attrs.get(key); Nest nest(lvlVomit, format("processing attribute `%1%'") % key); /* The `args' attribute is special: it supplies the @@ -198,9 +200,9 @@ Expr primDerivation(EvalState & state, Expr args) msg(lvlChatty, format("instantiated `%1%' -> `%2%'") % drvName % drvPath); - attrs["outPath"] = ATmake("Path()", outPath.c_str()); - attrs["drvPath"] = ATmake("Path()", drvPath.c_str()); - attrs["type"] = ATmake("Str(\"derivation\")"); + attrs.set("outPath", ATmake("Path()", outPath.c_str())); + attrs.set("drvPath", ATmake("Path()", drvPath.c_str())); + attrs.set("type", ATmake("Str(\"derivation\")")); return makeAttrs(attrs); } From 80bb477cc4ea5226ae760726730b3e09d21559de Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 5 Nov 2003 15:34:12 +0000 Subject: [PATCH 0307/6440] * Default function arguments. --- src/fix-ng/eval.cc | 13 ++++++++----- src/fix-ng/fix-expr.cc | 5 ++++- src/fix-ng/fix.sdf | 8 +++++--- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/fix-ng/eval.cc b/src/fix-ng/eval.cc index 90b7ff29f..770802f32 100644 --- a/src/fix-ng/eval.cc +++ b/src/fix-ng/eval.cc @@ -27,10 +27,13 @@ static Expr substArgs(Expr body, ATermList formals, Expr arg) /* Get the formal arguments. */ while (!ATisEmpty(formals)) { ATerm t = ATgetFirst(formals); - char * s; - if (!ATmatch(t, "", &s)) - abort(); /* can't happen */ - subs.set(t, undefined); + Expr name, def; + debug(printTerm(t)); + if (ATmatch(t, "NoDefFormal()", &name)) + subs.set(name, undefined); + else if (ATmatch(t, "DefFormal(, )", &name, &def)) + subs.set(name, def); + else abort(); /* can't happen */ formals = ATgetNext(formals); } @@ -44,7 +47,7 @@ static Expr substArgs(Expr body, ATermList formals, Expr arg) Expr key = ATgetFirst(keys); Expr cur = subs.get(key); if (!cur) - throw badTerm(format("argument `%1%' not declared") + throw badTerm(format("function has no formal argument `%1%'") % aterm2String(key), arg); subs.set(key, args.get(key)); } diff --git a/src/fix-ng/fix-expr.cc b/src/fix-ng/fix-expr.cc index 814e186b4..6e73b2934 100644 --- a/src/fix-ng/fix-expr.cc +++ b/src/fix-ng/fix-expr.cc @@ -171,7 +171,10 @@ Expr substitute(const ATermMap & subs, Expr e) ATermMap subs2(subs); ATermList fs = formals; while (!ATisEmpty(fs)) { - if (!ATmatch(ATgetFirst(fs), "", &s)) abort(); + Expr def; + if (!ATmatch(ATgetFirst(fs), "NoDefFormal()", &s) && + !ATmatch(ATgetFirst(fs), "DefFormal(, )", &s)) + abort(); subs2.remove(s); fs = ATgetNext(fs); } diff --git a/src/fix-ng/fix.sdf b/src/fix-ng/fix.sdf index 8e9f0fa72..9dc04d937 100644 --- a/src/fix-ng/fix.sdf +++ b/src/fix-ng/fix.sdf @@ -17,7 +17,7 @@ imports Fix-Exprs Fix-Layout module Fix-Exprs imports Fix-Lexicals URI exports - sorts Expr Bind + sorts Expr Bind Formal context-free syntax Id -> Expr {cons("Var")} @@ -34,7 +34,9 @@ exports Expr Expr -> Expr {cons("Call"), left} - "{" {Id ","}* "}" ":" Expr -> Expr {cons("Function"), right} + "{" {Formal ","}* "}" ":" Expr -> Expr {cons("Function"), right} + Id -> Formal {cons("NoDefFormal")} + Id "?" Expr -> Formal {cons("DefFormal")} "rec" "{" Binds "}" -> Expr {cons("Rec")} "let" "{" Binds "}" -> Expr {cons("LetRec")} @@ -62,7 +64,7 @@ exports Expr "." Id -> Expr > Expr ExprList -> ExprList > Expr Expr -> Expr - > "{" {Id ","}* "}" ":" Expr -> Expr + > "{" {Formal ","}* "}" ":" Expr -> Expr %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% From e17e95a82892b31c8063f2ace1b21c79e82e6f6d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 5 Nov 2003 16:20:57 +0000 Subject: [PATCH 0308/6440] * Print a shared textual ATerm if the term if very large. Due to substitutions, Fix terms are very large when printed as trees (in memory, they are quite compact due to sharing). --- src/libnix/expr.cc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libnix/expr.cc b/src/libnix/expr.cc index cead80342..9bbe80ab4 100644 --- a/src/libnix/expr.cc +++ b/src/libnix/expr.cc @@ -6,13 +6,21 @@ string printTerm(ATerm t) { char * s = ATwriteToString(t); + if (!s) throw Error("cannot print term"); return s; } Error badTerm(const format & f, ATerm t) { - return Error(format("%1%, in `%2%'") % f.str() % printTerm(t)); + char * s = ATwriteToString(t); + if (!s) throw Error("cannot print term"); + if (strlen(s) > 1000) { + int len; + s = ATwriteToSharedString(t, &len); + if (!s) throw Error("cannot print term"); + } + return Error(format("%1%, in `%2%'") % f.str() % (string) s); } From fa18f1f184ba89b3dfe592e77a276da42d326f42 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 5 Nov 2003 16:27:40 +0000 Subject: [PATCH 0309/6440] * Assertions. * Logical operators (!, &&, ||, ->). --- src/fix-ng/eval.cc | 43 +++++++++++++++++++++++++++++++----------- src/fix-ng/fix-expr.cc | 6 ++++++ src/fix-ng/fix-expr.hh | 4 +++- src/fix-ng/fix.sdf | 18 +++++++++++++++++- src/fix-ng/primops.cc | 13 +++++++++++++ src/fix-ng/primops.hh | 9 ++++++--- 6 files changed, 77 insertions(+), 16 deletions(-) diff --git a/src/fix-ng/eval.cc b/src/fix-ng/eval.cc index 770802f32..1f131fb7b 100644 --- a/src/fix-ng/eval.cc +++ b/src/fix-ng/eval.cc @@ -13,11 +13,6 @@ EvalState::EvalState() } -Expr getAttr(EvalState & state, Expr e, const string & name) -{ -} - - /* Substitute an argument set into the body of a function. */ static Expr substArgs(Expr body, ATermList formals, Expr arg) { @@ -142,7 +137,8 @@ Expr evalExpr2(EvalState & state, Expr e) ATmatch(e, "Bool()", &e1) || ATmatch(e, "Function([], )", &e1, &e2) || ATmatch(e, "Attrs([])", &e1) || - ATmatch(e, "List([])", &e1)) + ATmatch(e, "List([])", &e1) || + ATmatch(e, "Null", &e1)) return e; /* Any encountered variables must be undeclared or primops. */ @@ -163,6 +159,8 @@ Expr evalExpr2(EvalState & state, Expr e) if (primop == "derivation") return primDerivation(state, e2); if (primop == "toString") return primToString(state, e2); if (primop == "baseNameOf") return primBaseNameOf(state, e2); + if (primop == "null") return primNull(state, e2); + if (primop == "isNull") return primIsNull(state, e2); else throw badTerm("undefined variable/primop", e1); } @@ -200,13 +198,36 @@ Expr evalExpr2(EvalState & state, Expr e) return evalExpr(state, e3); } - /* Equality. Just strings for now. */ - if (ATmatch(e, "OpEq(, )", &e1, &e2)) { - string s1 = evalString(state, e1); - string s2 = evalString(state, e2); - return s1 == s2 ? ATmake("Bool(True)") : ATmake("Bool(False)"); + /* Assertions. */ + if (ATmatch(e, "Assert(, )", &e1, &e2)) { + if (!evalBool(state, e1)) throw badTerm("guard failed", e); + return evalExpr(state, e2); } + /* Generic equality. */ + if (ATmatch(e, "OpEq(, )", &e1, &e2)) + return makeBool(evalExpr(state, e1) == evalExpr(state, e2)); + + /* Generic inequality. */ + if (ATmatch(e, "OpNEq(, )", &e1, &e2)) + return makeBool(evalExpr(state, e1) != evalExpr(state, e2)); + + /* Negation. */ + if (ATmatch(e, "OpNot()", &e1)) + return makeBool(!evalBool(state, e1)); + + /* Implication. */ + if (ATmatch(e, "OpImpl(, )", &e1, &e2)) + return makeBool(!evalBool(state, e1) || evalBool(state, e2)); + + /* Conjunction (logical AND). */ + if (ATmatch(e, "OpAnd(, )", &e1, &e2)) + return makeBool(evalBool(state, e1) && evalBool(state, e2)); + + /* Disjunction (logical OR). */ + if (ATmatch(e, "OpOr(, )", &e1, &e2)) + return makeBool(evalBool(state, e1) || evalBool(state, e2)); + /* Barf. */ throw badTerm("invalid expression", e); } diff --git a/src/fix-ng/fix-expr.cc b/src/fix-ng/fix-expr.cc index 6e73b2934..1ce4a55e4 100644 --- a/src/fix-ng/fix-expr.cc +++ b/src/fix-ng/fix-expr.cc @@ -222,3 +222,9 @@ Expr substitute(const ATermMap & subs, Expr e) return e; } + + +Expr makeBool(bool b) +{ + return b ? ATmake("Bool(True)") : ATmake("Bool(False)"); +} diff --git a/src/fix-ng/fix-expr.hh b/src/fix-ng/fix-expr.hh index 93a010abe..6c1e51d9c 100644 --- a/src/fix-ng/fix-expr.hh +++ b/src/fix-ng/fix-expr.hh @@ -45,7 +45,6 @@ public: ATerm string2ATerm(const string & s); string aterm2String(ATerm t); - /* Generic bottomup traversal over ATerms. The traversal first recursively descends into subterms, and then applies the given term function to the resulting term. */ @@ -69,5 +68,8 @@ Expr makeAttrs(const ATermMap & attrs); /* Perform a set of substitutions on an expression. */ Expr substitute(const ATermMap & subs, Expr e); +/* Create an expression representing a boolean. */ +Expr makeBool(bool b); + #endif /* !__FIXEXPR_H */ diff --git a/src/fix-ng/fix.sdf b/src/fix-ng/fix.sdf index 9dc04d937..0cc486e14 100644 --- a/src/fix-ng/fix.sdf +++ b/src/fix-ng/fix.sdf @@ -38,6 +38,8 @@ exports Id -> Formal {cons("NoDefFormal")} Id "?" Expr -> Formal {cons("DefFormal")} + "assert" Expr ";" Expr -> Expr {cons("Assert"), right} + "rec" "{" Binds "}" -> Expr {cons("Rec")} "let" "{" Binds "}" -> Expr {cons("LetRec")} "{" Binds "}" -> Expr {cons("Attrs")} @@ -55,7 +57,13 @@ exports "if" Expr "then" Expr "else" Expr -> Expr {cons("If")} - Expr "==" Expr -> Expr {cons("OpEq")} + Expr "==" Expr -> Expr {cons("OpEq"), non-assoc} + Expr "!=" Expr -> Expr {cons("OpNEq"), non-assoc} + + "!" Expr -> Expr {cons("OpNot")} + Expr "&&" Expr -> Expr {cons("OpAnd"), right} + Expr "||" Expr -> Expr {cons("OpOr"), right} + Expr "->" Expr -> Expr {cons("OpImpl"), right} Bool -> Expr {cons("Bool")} @@ -64,6 +72,13 @@ exports Expr "." Id -> Expr > Expr ExprList -> ExprList > Expr Expr -> Expr + > "!" Expr -> Expr + > Expr "==" Expr -> Expr + > Expr "!=" Expr -> Expr + > Expr "&&" Expr -> Expr + > Expr "||" Expr -> Expr + > Expr "->" Expr -> Expr + > "assert" Expr ";" Expr -> Expr > "{" {Formal ","}* "}" ":" Expr -> Expr @@ -78,6 +93,7 @@ exports "rec" -> Id {reject} "true" -> Id {reject} "false" -> Id {reject} + "assert" -> Id {reject} [0-9]+ -> Int diff --git a/src/fix-ng/primops.cc b/src/fix-ng/primops.cc index ef0fd354e..f5a278f66 100644 --- a/src/fix-ng/primops.cc +++ b/src/fix-ng/primops.cc @@ -225,3 +225,16 @@ Expr primToString(EvalState & state, Expr arg) return ATmake("Str()", s); else throw badTerm("cannot coerce to string", arg); } + + +Expr primNull(EvalState & state, Expr arg) +{ + return ATmake("Null"); +} + + +Expr primIsNull(EvalState & state, Expr arg) +{ + arg = evalExpr(state, arg); + return makeBool(ATmatch(arg, "Null")); +} diff --git a/src/fix-ng/primops.hh b/src/fix-ng/primops.hh index e48883b0b..775ec5568 100644 --- a/src/fix-ng/primops.hh +++ b/src/fix-ng/primops.hh @@ -8,7 +8,6 @@ argument. */ Expr primImport(EvalState & state, Expr arg); - /* Construct (as a unobservable) side effect) a Nix derivation expression that performs the derivation described by the argument set. Returns the original set extended with the following @@ -18,14 +17,18 @@ Expr primImport(EvalState & state, Expr arg); derivation. */ Expr primDerivation(EvalState & state, Expr args); - /* Return the base name of the given string, i.e., everything following the last slash. */ Expr primBaseNameOf(EvalState & state, Expr arg); - /* Convert the argument (which can be a path or a uri) to a string. */ Expr primToString(EvalState & state, Expr arg); +/* Return the null value. */ +Expr primNull(EvalState & state, Expr arg); + +/* Determine whether the argument is the null value. */ +Expr primIsNull(EvalState & state, Expr arg); + #endif /* !__PRIMOPS_H */ From 569e7940f878f27b0fb9d3c8e8abfc29f3379103 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 6 Nov 2003 14:41:29 +0000 Subject: [PATCH 0310/6440] * Allow `+' in path names. --- src/fix-ng/fix.sdf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fix-ng/fix.sdf b/src/fix-ng/fix.sdf index 0cc486e14..30f930686 100644 --- a/src/fix-ng/fix.sdf +++ b/src/fix-ng/fix.sdf @@ -100,7 +100,7 @@ exports "\"" ~[\n\"]* "\"" -> Str PathComp ("/" PathComp)+ -> Path - [a-zA-Z0-9\.\_\-]+ -> PathComp + [a-zA-Z0-9\.\_\-\+]+ -> PathComp "true" -> Bool "false" -> Bool From cfaea07444a0011aa7d91ce1bcc8f105b8f283fa Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 6 Nov 2003 14:41:49 +0000 Subject: [PATCH 0311/6440] * `null' is a nullary primop. --- src/fix-ng/eval.cc | 5 ++--- src/fix-ng/primops.cc | 2 +- src/fix-ng/primops.hh | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/fix-ng/eval.cc b/src/fix-ng/eval.cc index 1f131fb7b..8e10caa84 100644 --- a/src/fix-ng/eval.cc +++ b/src/fix-ng/eval.cc @@ -137,12 +137,12 @@ Expr evalExpr2(EvalState & state, Expr e) ATmatch(e, "Bool()", &e1) || ATmatch(e, "Function([], )", &e1, &e2) || ATmatch(e, "Attrs([])", &e1) || - ATmatch(e, "List([])", &e1) || - ATmatch(e, "Null", &e1)) + ATmatch(e, "List([])", &e1)) return e; /* Any encountered variables must be undeclared or primops. */ if (ATmatch(e, "Var()", &s1)) { + if ((string) s1 == "null") return primNull(state); return e; } @@ -159,7 +159,6 @@ Expr evalExpr2(EvalState & state, Expr e) if (primop == "derivation") return primDerivation(state, e2); if (primop == "toString") return primToString(state, e2); if (primop == "baseNameOf") return primBaseNameOf(state, e2); - if (primop == "null") return primNull(state, e2); if (primop == "isNull") return primIsNull(state, e2); else throw badTerm("undefined variable/primop", e1); } diff --git a/src/fix-ng/primops.cc b/src/fix-ng/primops.cc index f5a278f66..df5747edd 100644 --- a/src/fix-ng/primops.cc +++ b/src/fix-ng/primops.cc @@ -227,7 +227,7 @@ Expr primToString(EvalState & state, Expr arg) } -Expr primNull(EvalState & state, Expr arg) +Expr primNull(EvalState & state) { return ATmake("Null"); } diff --git a/src/fix-ng/primops.hh b/src/fix-ng/primops.hh index 775ec5568..76d587afd 100644 --- a/src/fix-ng/primops.hh +++ b/src/fix-ng/primops.hh @@ -25,7 +25,7 @@ Expr primBaseNameOf(EvalState & state, Expr arg); Expr primToString(EvalState & state, Expr arg); /* Return the null value. */ -Expr primNull(EvalState & state, Expr arg); +Expr primNull(EvalState & state); /* Determine whether the argument is the null value. */ Expr primIsNull(EvalState & state, Expr arg); From 90e26d392c7ac4c2a69163f881f73916e6fba3c1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 6 Nov 2003 15:24:31 +0000 Subject: [PATCH 0312/6440] * Allow null in derivation bindings. --- src/fix-ng/primops.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/fix-ng/primops.cc b/src/fix-ng/primops.cc index df5747edd..fb923ed46 100644 --- a/src/fix-ng/primops.cc +++ b/src/fix-ng/primops.cc @@ -114,6 +114,8 @@ static string processBinding(EvalState & state, Expr e, NixExpr & ne) } return s; } + + if (ATmatch(e, "Null")) return ""; throw badTerm("invalid derivation binding", e); } From d2e3a132fe6796b2ac038ccb20e7aa32afc1a85f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 9 Nov 2003 10:31:56 +0000 Subject: [PATCH 0313/6440] * Pass CFLAGS to the subpackages. --- externals/Makefile.am | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/externals/Makefile.am b/externals/Makefile.am index 117973fa3..61122f4ab 100644 --- a/externals/Makefile.am +++ b/externals/Makefile.am @@ -19,7 +19,8 @@ have-db: build-db: have-db (pfx=`pwd` && \ cd $(DB)/build_unix && \ - CC=$(CC) CXX=$(CXX) ../dist/configure --prefix=$$pfx/inst \ + CC="$(CC)" CXX="$(CXX)" CFLAGS="$(CFLAGS)" CXXFLAGS="$(CXXFLAGS)" \ + ../dist/configure --prefix=$$pfx/inst \ --enable-cxx --disable-shared && \ make && \ make install) @@ -47,7 +48,8 @@ have-aterm: build-aterm: have-aterm (pfx=`pwd` && \ cd $(ATERM) && \ - ./configure --prefix=$$pfx/inst && \ + CC="$(CC)" ./configure --prefix=$$pfx/inst \ + --with-cflags="-DNDEBUG -DXGC_VERBOSE -DXHASHPEM -DWITH_STATS $(CFLAGS)" && \ make && \ make install) touch build-aterm @@ -74,7 +76,7 @@ have-sdf2: build-sdf2: have-sdf2 (pfx=`pwd` && \ cd $(SDF2) && \ - ./configure --prefix=$$pfx/inst && \ + CC="$(CC) -pg" ./configure --prefix=$$pfx/inst --with-cflags="$(CFLAGS)" && \ make && \ make install) touch build-sdf2 @@ -83,3 +85,7 @@ build-sdf2: have-sdf2 all: build-db build-aterm build-sdf2 EXTRA_DIST = $(DB).tar.gz $(ATERM).tar.gz $(SDF2).tar.gz + +ext-clean: + $(RM) -f have-db build-db have-aterm build-aterm have-sdf2 build-sdf2 + $(RM) -rf $(DB) $(ATERM) $(SDF2) From 15801c88fad38253b19ac2ea77e7597deab5fd6b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 9 Nov 2003 10:35:45 +0000 Subject: [PATCH 0314/6440] * Turned the msg() and debug() functions into macros, since they turned out to be a huge performance bottleneck (the text to printed would always be evaluated, even when it was above the verbosity level). This reduces fix-ng execution time by over 50%. gprof(1) is very useful. :-) --- src/fix-ng/eval.cc | 6 +++--- src/fix-ng/fix.cc | 2 +- src/fix-ng/primops.cc | 10 +++++----- src/fix/fix.cc | 11 ++++++----- src/libmain/shared.cc | 6 +++--- src/libnix/db.cc | 3 ++- src/libnix/exec.cc | 2 +- src/libnix/normalise.cc | 14 +++++++------- src/libnix/util.cc | 30 ++++++++++++++---------------- src/libnix/util.hh | 21 ++++++++++++++++++--- 10 files changed, 60 insertions(+), 45 deletions(-) diff --git a/src/fix-ng/eval.cc b/src/fix-ng/eval.cc index 8e10caa84..2b45b4798 100644 --- a/src/fix-ng/eval.cc +++ b/src/fix-ng/eval.cc @@ -23,7 +23,6 @@ static Expr substArgs(Expr body, ATermList formals, Expr arg) while (!ATisEmpty(formals)) { ATerm t = ATgetFirst(formals); Expr name, def; - debug(printTerm(t)); if (ATmatch(t, "NoDefFormal()", &name)) subs.set(name, undefined); else if (ATmatch(t, "DefFormal(, )", &name, &def)) @@ -234,7 +233,8 @@ Expr evalExpr2(EvalState & state, Expr e) Expr evalExpr(EvalState & state, Expr e) { - Nest nest(lvlVomit, format("evaluating expression: %1%") % printTerm(e)); + startNest(nest, lvlVomit, + format("evaluating expression: %1%") % printTerm(e)); state.nrEvaluated++; @@ -258,7 +258,7 @@ Expr evalExpr(EvalState & state, Expr e) Expr evalFile(EvalState & state, const Path & path) { - Nest nest(lvlTalkative, format("evaluating file `%1%'") % path); + startNest(nest, lvlTalkative, format("evaluating file `%1%'") % path); Expr e = parseExprFromFile(path); return evalExpr(state, e); } diff --git a/src/fix-ng/fix.cc b/src/fix-ng/fix.cc index c24ca4d9c..dc2790a60 100644 --- a/src/fix-ng/fix.cc +++ b/src/fix-ng/fix.cc @@ -60,7 +60,7 @@ static Expr evalExpr2(EvalState & state, Expr e) static Expr evalStdin(EvalState & state) { - Nest nest(lvlTalkative, format("evaluating standard input")); + startNest(nest, lvlTalkative, format("evaluating standard input")); Expr e = ATreadFromFile(stdin); if (!e) throw Error(format("unable to read a term from stdin")); diff --git a/src/fix-ng/primops.cc b/src/fix-ng/primops.cc index fb923ed46..07281e89b 100644 --- a/src/fix-ng/primops.cc +++ b/src/fix-ng/primops.cc @@ -58,7 +58,7 @@ static Path copyAtom(EvalState & state, const Path & srcPath) Path drvPath = writeTerm(unparseNixExpr(ne), ""); state.drvHashes[drvPath] = drvHash; - msg(lvlChatty, format("copied `%1%' -> closure `%2%'") + printMsg(lvlChatty, format("copied `%1%' -> closure `%2%'") % srcPath % drvPath); return drvPath; } @@ -107,7 +107,7 @@ static string processBinding(EvalState & state, Expr e, NixExpr & ne) string s; bool first = true; while (!ATisEmpty(es)) { - Nest nest(lvlVomit, format("processing list element")); + startNest(nest, lvlVomit, format("processing list element")); if (!first) s = s + " "; else first = false; s += processBinding(state, evalExpr(state, ATgetFirst(es)), ne); es = ATgetNext(es); @@ -123,7 +123,7 @@ static string processBinding(EvalState & state, Expr e, NixExpr & ne) Expr primDerivation(EvalState & state, Expr args) { - Nest nest(lvlVomit, "evaluating derivation"); + startNest(nest, lvlVomit, "evaluating derivation"); ATermMap attrs; args = evalExpr(state, args); @@ -143,7 +143,7 @@ Expr primDerivation(EvalState & state, Expr args) { string key = aterm2String(ATgetFirst(keys)); Expr value = attrs.get(key); - Nest nest(lvlVomit, format("processing attribute `%1%'") % key); + startNest(nest, lvlVomit, format("processing attribute `%1%'") % key); /* The `args' attribute is special: it supplies the command-line arguments to the builder. */ @@ -199,7 +199,7 @@ Expr primDerivation(EvalState & state, Expr args) Path drvPath = writeTerm(unparseNixExpr(ne), "-d-" + drvName); state.drvHashes[drvPath] = drvHash; - msg(lvlChatty, format("instantiated `%1%' -> `%2%'") + printMsg(lvlChatty, format("instantiated `%1%' -> `%2%'") % drvName % drvPath); attrs.set("outPath", ATmake("Path()", outPath.c_str())); diff --git a/src/fix/fix.cc b/src/fix/fix.cc index 9a8ff1513..d75e26b00 100644 --- a/src/fix/fix.cc +++ b/src/fix/fix.cc @@ -273,7 +273,7 @@ static Expr evalExpr2(EvalState & state, Expr e) Path pkgPath = writeTerm(unparseNixExpr(ne), ""); state.pkgHashes[pkgPath] = pkgHash; - msg(lvlChatty, format("copied `%1%' -> closure `%2%'") + printMsg(lvlChatty, format("copied `%1%' -> closure `%2%'") % srcPath % pkgPath); return ATmake("NixExpr()", pkgPath.c_str()); @@ -362,7 +362,7 @@ static Expr evalExpr2(EvalState & state, Expr e) Path pkgPath = writeTerm(unparseNixExpr(ne), "-d-" + name); state.pkgHashes[pkgPath] = pkgHash; - msg(lvlChatty, format("instantiated `%1%' -> `%2%'") + printMsg(lvlChatty, format("instantiated `%1%' -> `%2%'") % name % pkgPath); return ATmake("NixExpr()", pkgPath.c_str()); @@ -383,7 +383,8 @@ static Expr evalExpr2(EvalState & state, Expr e) static Expr evalExpr(EvalState & state, Expr e) { - Nest nest(lvlVomit, format("evaluating expression: %1%") % printTerm(e)); + startNest(nest, lvlVomit, + format("evaluating expression: %1%") % printTerm(e)); /* Consult the memo table to quickly get the normal form of previously evaluated expressions. */ @@ -405,7 +406,7 @@ static Expr evalExpr(EvalState & state, Expr e) static Expr evalFile(EvalState & state, const Path & relPath) { Path path = searchPath(state.searchDirs, relPath); - Nest nest(lvlTalkative, format("evaluating file `%1%'") % path); + startNest(nest, lvlTalkative, format("evaluating file `%1%'") % path); Expr e = ATreadFromNamedFile(path.c_str()); if (!e) throw Error(format("unable to read a term from `%1%'") % path); @@ -415,7 +416,7 @@ static Expr evalFile(EvalState & state, const Path & relPath) static Expr evalStdin(EvalState & state) { - Nest nest(lvlTalkative, format("evaluating standard input")); + startNest(nest, lvlTalkative, format("evaluating standard input")); Expr e = ATreadFromFile(stdin); if (!e) throw Error(format("unable to read a term from stdin")); diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 39439f8e1..b06f5eb8b 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -64,17 +64,17 @@ int main(int argc, char * * argv) try { initAndRun(argc, argv); } catch (UsageError & e) { - msg(lvlError, + printMsg(lvlError, format( "error: %1%\n" "Try `%2% --help' for more information.") % e.what() % programId); return 1; } catch (Error & e) { - msg(lvlError, format("error: %1%") % e.msg()); + printMsg(lvlError, format("error: %1%") % e.msg()); return 1; } catch (exception & e) { - msg(lvlError, format("error: %1%") % e.what()); + printMsg(lvlError, format("error: %1%") % e.what()); return 1; } diff --git a/src/libnix/db.cc b/src/libnix/db.cc index c498fab74..63ec2724f 100644 --- a/src/libnix/db.cc +++ b/src/libnix/db.cc @@ -202,7 +202,8 @@ void Database::open(const string & path) setAccessorCount(fdAccessors, 1); if (n != 0) { - msg(lvlTalkative, format("accessor count is %1%, running recovery") % n); + printMsg(lvlTalkative, + format("accessor count is %1%, running recovery") % n); /* Open the environment after running recovery. */ openEnv(env, path, DB_RECOVER); diff --git a/src/libnix/exec.cc b/src/libnix/exec.cc index 00d9e6a0a..47a385f14 100644 --- a/src/libnix/exec.cc +++ b/src/libnix/exec.cc @@ -110,7 +110,7 @@ void runProgram(const string & program, if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { if (keepFailed) { - msg(lvlTalkative, + printMsg(lvlTalkative, format("program `%1%' failed; keeping build directory `%2%'") % program % tmpDir); delTmpDir.cancel(); diff --git a/src/libnix/normalise.cc b/src/libnix/normalise.cc index d3978ac2c..49f86cc6f 100644 --- a/src/libnix/normalise.cc +++ b/src/libnix/normalise.cc @@ -20,7 +20,7 @@ static Path useSuccessor(const Path & path) Path normaliseNixExpr(const Path & _nePath, PathSet pending) { - Nest nest(lvlTalkative, + startNest(nest, lvlTalkative, format("normalising expression in `%1%'") % (string) _nePath); /* Try to substitute the expression by any known successors in @@ -156,13 +156,13 @@ Path normaliseNixExpr(const Path & _nePath, PathSet pending) } /* Run the builder. */ - msg(lvlChatty, format("building...")); + printMsg(lvlChatty, format("building...")); runProgram(ne.derivation.builder, ne.derivation.args, env, nixLogDir + "/" + baseNameOf(nePath)); - msg(lvlChatty, format("build completed")); + printMsg(lvlChatty, format("build completed")); } else - msg(lvlChatty, format("fast build succesful")); + printMsg(lvlChatty, format("fast build succesful")); /* Check whether the output paths were created, and grep each output path to determine what other paths it references. Also make all @@ -239,7 +239,7 @@ Path normaliseNixExpr(const Path & _nePath, PathSet pending) /* Write the normal form. This does not have to occur in the transaction below because writing terms is idem-potent. */ ATerm nfTerm = unparseNixExpr(nf); - msg(lvlVomit, format("normal form: %1%") % printTerm(nfTerm)); + printMsg(lvlVomit, format("normal form: %1%") % printTerm(nfTerm)); Path nfPath = writeTerm(nfTerm, "-s"); /* Register each outpat path, and register the normal form. This @@ -262,7 +262,7 @@ Path normaliseNixExpr(const Path & _nePath, PathSet pending) void realiseClosure(const Path & nePath, PathSet pending) { - Nest nest(lvlDebug, format("realising closure `%1%'") % nePath); + startNest(nest, lvlDebug, format("realising closure `%1%'") % nePath); NixExpr ne = exprFromPath(nePath, pending); if (ne.type != NixExpr::neClosure) @@ -290,7 +290,7 @@ void ensurePath(const Path & path, PathSet pending) if (isValidPath(path)) return; throw Error(format("substitute failed to produce expected output path")); } catch (Error & e) { - msg(lvlTalkative, + printMsg(lvlTalkative, format("building of substitute `%1%' for `%2%' failed: %3%") % *i % path % e.what()); } diff --git a/src/libnix/util.cc b/src/libnix/util.cc index 016ee991a..299a37f6c 100644 --- a/src/libnix/util.cc +++ b/src/libnix/util.cc @@ -110,7 +110,7 @@ bool pathExists(const Path & path) void deletePath(const Path & path) { - msg(lvlVomit, format("deleting path `%1%'") % path); + printMsg(lvlVomit, format("deleting path `%1%'") % path); struct stat st; if (lstat(path.c_str(), &st)) @@ -194,15 +194,9 @@ Verbosity verbosity = lvlError; static int nestingLevel = 0; -Nest::Nest(Verbosity level, const format & f) +Nest::Nest() { - if (level > verbosity) - nest = false; - else { - msg(level, f); - nest = true; - nestingLevel++; - } + nest = false; } @@ -212,7 +206,17 @@ Nest::~Nest() } -void msg(Verbosity level, const format & f) +void Nest::open(Verbosity level, const format & f) +{ + if (level <= verbosity) { + printMsg_(level, f); + nest = true; + nestingLevel++; + } +} + + +void printMsg_(Verbosity level, const format & f) { if (level > verbosity) return; string spaces; @@ -222,12 +226,6 @@ void msg(Verbosity level, const format & f) } -void debug(const format & f) -{ - msg(lvlDebug, f); -} - - void readFull(int fd, unsigned char * buf, size_t count) { while (count) { diff --git a/src/libnix/util.hh b/src/libnix/util.hh index 02a9b7fcb..e6b600eff 100644 --- a/src/libnix/util.hh +++ b/src/libnix/util.hh @@ -101,12 +101,27 @@ class Nest private: bool nest; public: - Nest(Verbosity level, const format & f); + Nest(); ~Nest(); + void open(Verbosity level, const format & f); }; -void msg(Verbosity level, const format & f); -void debug(const format & f); /* short-hand for msg(lvlDebug, ...) */ +void printMsg_(Verbosity level, const format & f); + +#define startNest(varName, level, f) \ + Nest varName; \ + if (level <= verbosity) { \ + varName.open(level, (f)); \ + } + +#define printMsg(level, f) \ + do { \ + if (level <= verbosity) { \ + printMsg_(level, (f)); \ + } \ + } while (0) + +#define debug(f) printMsg(lvlDebug, f) /* Wrappers arount read()/write() that read/write exactly the From 06ae269c7c5cdda32072f3f00cf644e540ba12cd Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 10 Nov 2003 11:00:38 +0000 Subject: [PATCH 0315/6440] * Do not filter when parsing. This is much faster. * Add some rejections and lexical restrictions to the grammar to make this work. --- src/fix-ng/fix.sdf | 6 ++++++ src/fix-ng/parser.cc | 1 + 2 files changed, 7 insertions(+) diff --git a/src/fix-ng/fix.sdf b/src/fix-ng/fix.sdf index 30f930686..54f5d5266 100644 --- a/src/fix-ng/fix.sdf +++ b/src/fix-ng/fix.sdf @@ -91,6 +91,10 @@ exports lexical syntax [a-zA-Z\_][a-zA-Z0-9\_\']* -> Id "rec" -> Id {reject} + "let" -> Id {reject} + "if" -> Id {reject} + "then" -> Id {reject} + "else" -> Id {reject} "true" -> Id {reject} "false" -> Id {reject} "assert" -> Id {reject} @@ -108,6 +112,7 @@ exports lexical restrictions Id -/- [a-zA-Z0-9\_\'] Int -/- [0-9] + Path -/- [a-zA-Z0-9\.\_\-\+\/] %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -126,6 +131,7 @@ exports "//" Uauthority Uabspath? -> Unetpath "/" Upathsegments -> Uabspath + "//" Uuric* -> Uabspath {reject} Urelsegment Uabspath? -> Urelpath (Uunreserved | Uescaped | [\;\@\&\=\+\$\,])+ -> Urelsegment diff --git a/src/fix-ng/parser.cc b/src/fix-ng/parser.cc index 43678ec97..93afe0627 100644 --- a/src/fix-ng/parser.cc +++ b/src/fix-ng/parser.cc @@ -109,6 +109,7 @@ Expr parseExprFromFile(Path path) SG_OUTPUT_ON(); SG_ASFIX2ME_ON(); SG_AMBIGUITY_ERROR_ON(); + SG_FILTER_OFF(); initialised = true; } From 3e5a019a070cbaac7d1248e208c66da9fdb23313 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 16 Nov 2003 17:46:31 +0000 Subject: [PATCH 0316/6440] * Some utility functions for working with ATerms. --- src/fix-ng/eval.cc | 108 +++++++++++++++++++++------------------ src/fix-ng/fix-expr.cc | 22 ++++---- src/fix-ng/fix.cc | 36 ++----------- src/fix-ng/parser.cc | 36 ++++++------- src/fix-ng/primops.cc | 43 +++++++++------- src/fix/fix.cc | 2 +- src/libnix/Makefile.am | 9 +++- src/libnix/aterm.cc | 93 +++++++++++++++++++++++++++++++++ src/libnix/aterm.hh | 55 ++++++++++++++++++++ src/libnix/expr.cc | 50 +++++++----------- src/libnix/expr.hh | 8 +-- src/libnix/normalise.cc | 2 +- src/libnix/references.hh | 6 +-- src/libnix/test-aterm.cc | 66 ++++++++++++++++++++++++ 14 files changed, 362 insertions(+), 174 deletions(-) create mode 100644 src/libnix/aterm.cc create mode 100644 src/libnix/aterm.hh create mode 100644 src/libnix/test-aterm.cc diff --git a/src/fix-ng/eval.cc b/src/fix-ng/eval.cc index 2b45b4798..55cb5fbcf 100644 --- a/src/fix-ng/eval.cc +++ b/src/fix-ng/eval.cc @@ -16,6 +16,7 @@ EvalState::EvalState() /* Substitute an argument set into the body of a function. */ static Expr substArgs(Expr body, ATermList formals, Expr arg) { + ATMatcher m; ATermMap subs; Expr undefined = ATmake("Undefined"); @@ -23,9 +24,9 @@ static Expr substArgs(Expr body, ATermList formals, Expr arg) while (!ATisEmpty(formals)) { ATerm t = ATgetFirst(formals); Expr name, def; - if (ATmatch(t, "NoDefFormal()", &name)) + if (atMatch(m, t) >> "NoDefFormal" >> name) subs.set(name, undefined); - else if (ATmatch(t, "DefFormal(, )", &name, &def)) + else if (atMatch(m, t) >> "DefFormal" >> name >> def) subs.set(name, def); else abort(); /* can't happen */ formals = ATgetNext(formals); @@ -67,15 +68,17 @@ static Expr substArgs(Expr body, ATermList formals, Expr arg) (e.x) (e.y), y = e.x}'. */ ATerm expandRec(ATerm e, ATermList bnds) { + ATMatcher m; + /* Create the substitution list. */ ATermMap subs; ATermList bs = bnds; while (!ATisEmpty(bs)) { - char * s; + string s; Expr e2; - if (!ATmatch(ATgetFirst(bs), "Bind(, )", &s, &e2)) + if (!(atMatch(m, ATgetFirst(bs)) >> "Bind" >> s >> e2)) abort(); /* can't happen */ - subs.set(s, ATmake("Select(, )", e, s)); + subs.set(s, ATmake("Select(, )", e, s.c_str())); bs = ATgetNext(bs); } @@ -83,9 +86,9 @@ ATerm expandRec(ATerm e, ATermList bnds) ATermMap as; bs = bnds; while (!ATisEmpty(bs)) { - char * s; + string s; Expr e2; - if (!ATmatch(ATgetFirst(bs), "Bind(, )", &s, &e2)) + if (!(atMatch(m, ATgetFirst(bs)) >> "Bind" >> s >> e2)) abort(); /* can't happen */ as.set(s, substitute(subs, e2)); bs = ATgetNext(bs); @@ -98,8 +101,9 @@ ATerm expandRec(ATerm e, ATermList bnds) string evalString(EvalState & state, Expr e) { e = evalExpr(state, e); - char * s; - if (!ATmatch(e, "Str()", &s)) + ATMatcher m; + string s; + if (!(atMatch(m, e) >> "Str" >> s)) throw badTerm("string expected", e); return s; } @@ -108,8 +112,9 @@ string evalString(EvalState & state, Expr e) Path evalPath(EvalState & state, Expr e) { e = evalExpr(state, e); - char * s; - if (!ATmatch(e, "Path()", &s)) + ATMatcher m; + string s; + if (!(atMatch(m, e) >> "Path" >> s)) throw badTerm("path expected", e); return s; } @@ -118,78 +123,79 @@ Path evalPath(EvalState & state, Expr e) bool evalBool(EvalState & state, Expr e) { e = evalExpr(state, e); - if (ATmatch(e, "Bool(True)")) return true; - else if (ATmatch(e, "Bool(False)")) return false; + ATMatcher m; + if (atMatch(m, e) >> "Bool" >> "True") return true; + else if (atMatch(m, e) >> "Bool" >> "False") return false; else throw badTerm("expecting a boolean", e); } Expr evalExpr2(EvalState & state, Expr e) { + ATMatcher m; Expr e1, e2, e3, e4; - char * s1; + string s1; /* Normal forms. */ - if (ATmatch(e, "Str()", &s1) || - ATmatch(e, "Path()", &s1) || - ATmatch(e, "Uri()", &s1) || - ATmatch(e, "Bool()", &e1) || - ATmatch(e, "Function([], )", &e1, &e2) || - ATmatch(e, "Attrs([])", &e1) || - ATmatch(e, "List([])", &e1)) + if (atMatch(m, e) >> "Str" || + atMatch(m, e) >> "Path" || + atMatch(m, e) >> "Uri" || + atMatch(m, e) >> "Bool" || + atMatch(m, e) >> "Function" || + atMatch(m, e) >> "Attrs" || + atMatch(m, e) >> "List") return e; /* Any encountered variables must be undeclared or primops. */ - if (ATmatch(e, "Var()", &s1)) { - if ((string) s1 == "null") return primNull(state); + if (atMatch(m, e) >> "Var" >> s1) { + if (s1 == "null") return primNull(state); return e; } /* Function application. */ - if (ATmatch(e, "Call(, )", &e1, &e2)) { + if (atMatch(m, e) >> "Call" >> e1 >> e2) { + + ATermList formals; /* Evaluate the left-hand side. */ e1 = evalExpr(state, e1); /* Is it a primop or a function? */ - if (ATmatch(e1, "Var()", &s1)) { - string primop(s1); - if (primop == "import") return primImport(state, e2); - if (primop == "derivation") return primDerivation(state, e2); - if (primop == "toString") return primToString(state, e2); - if (primop == "baseNameOf") return primBaseNameOf(state, e2); - if (primop == "isNull") return primIsNull(state, e2); + if (atMatch(m, e1) >> "Var" >> s1) { + if (s1 == "import") return primImport(state, e2); + if (s1 == "derivation") return primDerivation(state, e2); + if (s1 == "toString") return primToString(state, e2); + if (s1 == "baseNameOf") return primBaseNameOf(state, e2); + if (s1 == "isNull") return primIsNull(state, e2); else throw badTerm("undefined variable/primop", e1); } - else if (ATmatch(e1, "Function([], )", &e3, &e4)) { + else if (atMatch(m, e1) >> "Function" >> formals >> e4) return evalExpr(state, - substArgs(e4, (ATermList) e3, evalExpr(state, e2))); - } + substArgs(e4, formals, evalExpr(state, e2))); else throw badTerm("expecting a function or primop", e1); } /* Attribute selection. */ - if (ATmatch(e, "Select(, )", &e1, &s1)) { - string name(s1); - Expr a = queryAttr(evalExpr(state, e1), name); - if (!a) throw badTerm(format("missing attribute `%1%'") % name, e); + if (atMatch(m, e) >> "Select" >> e1 >> s1) { + Expr a = queryAttr(evalExpr(state, e1), s1); + if (!a) throw badTerm(format("missing attribute `%1%'") % s1, e); return evalExpr(state, a); } /* Mutually recursive sets. */ ATermList bnds; - if (ATmatch(e, "Rec([])", &bnds)) - return expandRec(e, (ATermList) bnds); + if (atMatch(m, e) >> "Rec" >> bnds) + return expandRec(e, bnds); /* Let expressions `let {..., body = ...}' are just desugared into `(rec {..., body = ...}).body'. */ - if (ATmatch(e, "LetRec()", &e1)) - return evalExpr(state, ATmake("Select(Rec(), \"body\")", e1)); + if (atMatch(m, e) >> "LetRec" >> bnds) + return evalExpr(state, ATmake("Select(Rec(), \"body\")", bnds)); /* Conditionals. */ - if (ATmatch(e, "If(, , )", &e1, &e2, &e3)) { + if (atMatch(m, e) >> "If" >> e1 >> e2 >> e3) { if (evalBool(state, e1)) return evalExpr(state, e2); else @@ -197,33 +203,33 @@ Expr evalExpr2(EvalState & state, Expr e) } /* Assertions. */ - if (ATmatch(e, "Assert(, )", &e1, &e2)) { + if (atMatch(m, e) >> "Assert" >> e1 >> e2) { if (!evalBool(state, e1)) throw badTerm("guard failed", e); return evalExpr(state, e2); } /* Generic equality. */ - if (ATmatch(e, "OpEq(, )", &e1, &e2)) + if (atMatch(m, e) >> "OpEq" >> e1 >> e2) return makeBool(evalExpr(state, e1) == evalExpr(state, e2)); /* Generic inequality. */ - if (ATmatch(e, "OpNEq(, )", &e1, &e2)) + if (atMatch(m, e) >> "OpNEq" >> e1 >> e2) return makeBool(evalExpr(state, e1) != evalExpr(state, e2)); /* Negation. */ - if (ATmatch(e, "OpNot()", &e1)) + if (atMatch(m, e) >> "OpNot" >> e1) return makeBool(!evalBool(state, e1)); /* Implication. */ - if (ATmatch(e, "OpImpl(, )", &e1, &e2)) + if (atMatch(m, e) >> "OpImpl" >> e1 >> e2) return makeBool(!evalBool(state, e1) || evalBool(state, e2)); /* Conjunction (logical AND). */ - if (ATmatch(e, "OpAnd(, )", &e1, &e2)) + if (atMatch(m, e) >> "OpAnd" >> e1 >> e2) return makeBool(evalBool(state, e1) && evalBool(state, e2)); /* Disjunction (logical OR). */ - if (ATmatch(e, "OpOr(, )", &e1, &e2)) + if (atMatch(m, e) >> "OpOr" >> e1 >> e2) return makeBool(evalBool(state, e1) || evalBool(state, e2)); /* Barf. */ @@ -234,7 +240,7 @@ Expr evalExpr2(EvalState & state, Expr e) Expr evalExpr(EvalState & state, Expr e) { startNest(nest, lvlVomit, - format("evaluating expression: %1%") % printTerm(e)); + format("evaluating expression: %1%") % e); state.nrEvaluated++; diff --git a/src/fix-ng/fix-expr.cc b/src/fix-ng/fix-expr.cc index 1ce4a55e4..a3d24ba0e 100644 --- a/src/fix-ng/fix-expr.cc +++ b/src/fix-ng/fix-expr.cc @@ -118,14 +118,15 @@ ATerm bottomupRewrite(TermFun & f, ATerm e) void queryAllAttrs(Expr e, ATermMap & attrs) { + ATMatcher m; ATermList bnds; - if (!ATmatch(e, "Attrs([])", &bnds)) + if (!(atMatch(m, e) >> "Attrs" >> bnds)) throw badTerm("expected attribute set", e); while (!ATisEmpty(bnds)) { - char * s; + string s; Expr e; - if (!ATmatch(ATgetFirst(bnds), "Bind(, )", &s, &e)) + if (!(atMatch(m, ATgetFirst(bnds)) >> "Bind" >> s >> e)) abort(); /* can't happen */ attrs.set(s, e); bnds = ATgetNext(bnds); @@ -156,9 +157,10 @@ Expr makeAttrs(const ATermMap & attrs) Expr substitute(const ATermMap & subs, Expr e) { - char * s; + ATMatcher m; + string s; - if (ATmatch(e, "Var()", &s)) { + if (atMatch(m, e) >> "Var" >> s) { Expr sub = subs.get(s); return sub ? sub : e; } @@ -167,13 +169,13 @@ Expr substitute(const ATermMap & subs, Expr e) function. */ ATermList formals; ATerm body; - if (ATmatch(e, "Function([], )", &formals, &body)) { + if (atMatch(m, e) >> "Function" >> formals >> body) { ATermMap subs2(subs); ATermList fs = formals; while (!ATisEmpty(fs)) { Expr def; - if (!ATmatch(ATgetFirst(fs), "NoDefFormal()", &s) && - !ATmatch(ATgetFirst(fs), "DefFormal(, )", &s)) + if (!(atMatch(m, ATgetFirst(fs)) >> "NoDefFormal" >> s) && + !(atMatch(m, ATgetFirst(fs)) >> "DefFormal" >> s >> def)) abort(); subs2.remove(s); fs = ATgetNext(fs); @@ -184,12 +186,12 @@ Expr substitute(const ATermMap & subs, Expr e) /* Idem for a mutually recursive attribute set. */ ATermList bindings; - if (ATmatch(e, "Rec([])", &bindings)) { + if (atMatch(m, e) >> "Rec" >> bindings) { ATermMap subs2(subs); ATermList bnds = bindings; while (!ATisEmpty(bnds)) { Expr e; - if (!ATmatch(ATgetFirst(bnds), "Bind(, )", &s, &e)) + if (!(atMatch(m, ATgetFirst(bnds)) >> "Bind" >> s >> e)) abort(); /* can't happen */ subs2.remove(s); bnds = ATgetNext(bnds); diff --git a/src/fix-ng/fix.cc b/src/fix-ng/fix.cc index dc2790a60..49f19669a 100644 --- a/src/fix-ng/fix.cc +++ b/src/fix-ng/fix.cc @@ -27,37 +27,6 @@ static Path searchPath(const Paths & searchDirs, const Path & relPath) #endif -#if 0 -static Expr evalExpr2(EvalState & state, Expr e) -{ - /* Ad-hoc function for string matching. */ - if (ATmatch(e, "HasSubstr(, )", &e1, &e2)) { - e1 = evalExpr(state, e1); - e2 = evalExpr(state, e2); - - char * s1, * s2; - if (!ATmatch(e1, "", &s1)) - throw badTerm("expecting a string", e1); - if (!ATmatch(e2, "", &s2)) - throw badTerm("expecting a string", e2); - - return - string(s1).find(string(s2)) != string::npos ? - ATmake("True") : ATmake("False"); - } - - /* BaseName primitive function. */ - if (ATmatch(e, "BaseName()", &e1)) { - e1 = evalExpr(state, e1); - if (!ATmatch(e1, "", &s1)) - throw badTerm("string expected", e1); - return ATmake("", baseNameOf(s1).c_str()); - } - -} -#endif - - static Expr evalStdin(EvalState & state) { startNest(nest, lvlTalkative, format("evaluating standard input")); @@ -70,9 +39,10 @@ static Expr evalStdin(EvalState & state) static void printNixExpr(EvalState & state, Expr e) { + ATMatcher m; ATermList es; - if (ATmatch(e, "Attrs([])", &es)) { + if (atMatch(m, e) >> "Attrs" >> es) { Expr a = queryAttr(e, "type"); if (a && evalString(state, a) == "derivation") { a = queryAttr(e, "drvPath"); @@ -83,7 +53,7 @@ static void printNixExpr(EvalState & state, Expr e) } } - if (ATmatch(e, "[]", &es)) { + if (ATgetType(e) == AT_LIST) { while (!ATisEmpty(es)) { printNixExpr(state, evalExpr(state, ATgetFirst(es))); es = ATgetNext(es); diff --git a/src/fix-ng/parser.cc b/src/fix-ng/parser.cc index 93afe0627..710ea6a86 100644 --- a/src/fix-ng/parser.cc +++ b/src/fix-ng/parser.cc @@ -28,40 +28,40 @@ struct Cleanup : TermFun virtual ATerm operator () (ATerm e) { - char * s; + ATMatcher m; + string s; - if (ATmatch(e, "Str()", &s)) { - string s2(s); + if (atMatch(m, e) >> "Str" >> s) { return ATmake("Str()", - string(s2, 1, s2.size() - 2).c_str()); + string(s, 1, s.size() - 2).c_str()); } - if (ATmatch(e, "Path()", &s)) { - string path(s); - if (path[0] != '/') - path = basePath + "/" + path; - return ATmake("Path()", canonPath(path).c_str()); + if (atMatch(m, e) >> "Path" >> s) { + if (s[0] != '/') + s = basePath + "/" + s; + return ATmake("Path()", canonPath(s).c_str()); } - if (ATmatch(e, "Int()", &s)) { + if (atMatch(m, e) >> "Int" >> s) { istringstream s2(s); int n; s2 >> n; return ATmake("Int()", n); } - if (ATmatch(e, "Bool(\"true\")", &s)) + if (atMatch(m, e) >> "Bool" >> "true") return ATmake("Bool(True)"); - if (ATmatch(e, "Bool(\"false\")", &s)) + if (atMatch(m, e) >> "Bool" >> "false") return ATmake("Bool(False)"); - if (ATmatch(e, "ExprNil")) + if (atMatch(m, e) >> "ExprNil") return (ATerm) ATempty; - ATerm e1, e2; - if (ATmatch(e, "ExprCons(, [])", &e1, &e2)) - return (ATerm) ATinsert((ATermList) e2, e1); + ATerm e1; + ATermList e2; + if (atMatch(m, e) >> "ExprCons" >> e1 >> e2) + return (ATerm) ATinsert(e2, e1); return e; } @@ -133,7 +133,7 @@ Expr parseExprFromFile(Path path) throw SysError(format("parse failed in `%1%'") % path); if (SGisParseError(result)) throw Error(format("parse error in `%1%': %2%") - % path % printTerm(result)); + % path % result); /* Implode it. */ PT_ParseTree tree = PT_makeParseTreeFromTerm(result); @@ -156,7 +156,7 @@ Expr parseExprFromFile(Path path) throw Error(format("cannot implode parse tree")); debug(format("imploded parse tree of `%1%': %2%") - % path % printTerm(imploded)); + % path % imploded); /* Finally, clean it up. */ Cleanup cleanup; diff --git a/src/fix-ng/primops.cc b/src/fix-ng/primops.cc index 07281e89b..a68352579 100644 --- a/src/fix-ng/primops.cc +++ b/src/fix-ng/primops.cc @@ -5,8 +5,9 @@ Expr primImport(EvalState & state, Expr arg) { - char * path; - if (!ATmatch(arg, "Path()", &path)) + ATMatcher m; + string path; + if (!(atMatch(m, arg) >> "Path" >> path)) throw badTerm("path expected", arg); return evalFile(state, path); } @@ -79,15 +80,16 @@ static string processBinding(EvalState & state, Expr e, NixExpr & ne) { e = evalExpr(state, e); - char * s; + ATMatcher m; + string s; ATermList es; - if (ATmatch(e, "Str()", &s)) return s; - if (ATmatch(e, "Uri()", &s)) return s; - if (ATmatch(e, "Bool(True)")) return "1"; - if (ATmatch(e, "Bool(False)")) return ""; + if (atMatch(m, e) >> "Str" >> s) return s; + if (atMatch(m, e) >> "Uri" >> s) return s; + if (atMatch(m, e) >> "Bool" >> "True") return "1"; + if (atMatch(m, e) >> "Bool" >> "False") return ""; - if (ATmatch(e, "Attrs([])", &es)) { + if (atMatch(m, e) >> "Attrs" >> es) { Expr a = queryAttr(e, "type"); if (a && evalString(state, a) == "derivation") { a = queryAttr(e, "drvPath"); @@ -98,12 +100,12 @@ static string processBinding(EvalState & state, Expr e, NixExpr & ne) } } - if (ATmatch(e, "Path()", &s)) { + if (atMatch(m, e) >> "Path" >> s) { Path drvPath = copyAtom(state, s); return addInput(state, drvPath, ne); } - if (ATmatch(e, "List([])", &es)) { + if (atMatch(m, e) >> "List" >> es) { string s; bool first = true; while (!ATisEmpty(es)) { @@ -115,7 +117,7 @@ static string processBinding(EvalState & state, Expr e, NixExpr & ne) return s; } - if (ATmatch(e, "Null")) return ""; + if (atMatch(m, e) >> "Null") return ""; throw badTerm("invalid derivation binding", e); } @@ -148,14 +150,17 @@ Expr primDerivation(EvalState & state, Expr args) /* The `args' attribute is special: it supplies the command-line arguments to the builder. */ if (key == "args") { + throw Error("args not implemented"); +#if 0 ATermList args; - if (!ATmatch(value, "[]", &args)) + if (!(ATmatch(value, "[]", &args)) throw badTerm("list expected", value); while (!ATisEmpty(args)) { Expr arg = evalExpr(state, ATgetFirst(args)); ne.derivation.args.push_back(processBinding(state, arg, ne)); args = ATgetNext(args); } +#endif } /* All other attributes are passed to the builder through the @@ -220,11 +225,12 @@ Expr primBaseNameOf(EvalState & state, Expr arg) Expr primToString(EvalState & state, Expr arg) { arg = evalExpr(state, arg); - char * s; - if (ATmatch(arg, "Str()", &s) || - ATmatch(arg, "Path()", &s) || - ATmatch(arg, "Uri()", &s)) - return ATmake("Str()", s); + ATMatcher m; + string s; + if (atMatch(m, arg) >> "Str" >> s || + atMatch(m, arg) >> "Path" >> s || + atMatch(m, arg) >> "Uri" >> s) + return ATmake("Str()", s.c_str()); else throw badTerm("cannot coerce to string", arg); } @@ -238,5 +244,6 @@ Expr primNull(EvalState & state) Expr primIsNull(EvalState & state, Expr arg) { arg = evalExpr(state, arg); - return makeBool(ATmatch(arg, "Null")); + ATMatcher m; + return makeBool(atMatch(m, arg) >> "Null"); } diff --git a/src/fix/fix.cc b/src/fix/fix.cc index d75e26b00..8b8441050 100644 --- a/src/fix/fix.cc +++ b/src/fix/fix.cc @@ -384,7 +384,7 @@ static Expr evalExpr2(EvalState & state, Expr e) static Expr evalExpr(EvalState & state, Expr e) { startNest(nest, lvlVomit, - format("evaluating expression: %1%") % printTerm(e)); + format("evaluating expression: %1%") % e); /* Consult the memo table to quickly get the normal form of previously evaluated expressions. */ diff --git a/src/libnix/Makefile.am b/src/libnix/Makefile.am index b890ba8c0..7671b1613 100644 --- a/src/libnix/Makefile.am +++ b/src/libnix/Makefile.am @@ -2,8 +2,15 @@ noinst_LIBRARIES = libnix.a libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c \ store.cc expr.cc normalise.cc exec.cc \ - globals.cc db.cc references.cc pathlocks.cc + globals.cc db.cc references.cc pathlocks.cc aterm.cc AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. -I../../externals/inst/include EXTRA_DIST = *.hh *.h test-builder-*.sh + +check_PROGRAMS = test-aterm + +test_aterm_SOURCES = test-aterm.cc +test_aterm_LDADD = libnix.a $(LDADD) ../boost/format/libformat.a \ + -L../../externals/inst/lib -ldb_cxx -lATerm + diff --git a/src/libnix/aterm.cc b/src/libnix/aterm.cc new file mode 100644 index 000000000..de7c35952 --- /dev/null +++ b/src/libnix/aterm.cc @@ -0,0 +1,93 @@ +#include "aterm.hh" + + +string atPrint(ATerm t) +{ + if (!t) throw Error("attempt to print null aterm"); + char * s = ATwriteToString(t); + if (!s) throw Error("cannot print term"); + return s; +} + + +ostream & operator << (ostream & stream, ATerm e) +{ + return stream << atPrint(e); +} + + +ATMatcher & atMatch(ATMatcher & pos, ATerm t) +{ + pos.t = t; + pos.pos = ATMatcher::funPos; + return pos; +} + + +static inline bool failed(const ATMatcher & pos) +{ + return pos.pos == ATMatcher::failPos; +} + + +static inline ATMatcher & fail(ATMatcher & pos) +{ + pos.pos = ATMatcher::failPos; + return pos; +} + + +ATMatcher & operator >> (ATMatcher & pos, ATerm & out) +{ + out = 0; + if (failed(pos)) return pos; + if (pos.pos == ATMatcher::funPos || + ATgetType(pos.t) != AT_APPL || + pos.pos >= (int) ATgetArity(ATgetAFun(pos.t))) + return fail(pos); + out = ATgetArgument(pos.t, pos.pos); + pos.pos++; + return pos; +} + + +ATMatcher & operator >> (ATMatcher & pos, string & out) +{ + out = ""; + if (pos.pos == ATMatcher::funPos) { + if (ATgetType(pos.t) != AT_APPL) return fail(pos); + out = ATgetName(ATgetAFun(pos.t)); + pos.pos = 0; + } else { + ATerm t; + pos = pos >> t; + if (failed(pos)) return pos; + if (ATgetType(t) != AT_APPL || + ATgetArity(ATgetAFun(t)) != 0) + return fail(pos); + out = ATgetName(ATgetAFun(t)); + } + return pos; +} + + +ATMatcher & operator >> (ATMatcher & pos, const string & s) +{ + string s2; + pos = pos >> s2; + if (failed(pos)) return pos; + if (s != s2) return fail(pos); + return pos; +} + + +ATMatcher & operator >> (ATMatcher & pos, ATermList & out) +{ + out = 0; + ATerm t; + pos = pos >> t; + if (failed(pos)) return pos; + if (ATgetType(t) != AT_LIST) return fail(pos); + out = (ATermList) t; + return pos; +} diff --git a/src/libnix/aterm.hh b/src/libnix/aterm.hh new file mode 100644 index 000000000..1e4ee80ee --- /dev/null +++ b/src/libnix/aterm.hh @@ -0,0 +1,55 @@ +#ifndef __ATERM_H +#define __ATERM_H + +extern "C" { +#include +} + +#include "util.hh" + + +/* Print an ATerm. */ +string atPrint(ATerm t); + +/* Write an ATerm to an output stream. */ +ostream & operator << (ostream & stream, ATerm e); + +/* Type-safe matching. */ + +struct ATMatcher +{ + ATerm t; + int pos; + const static int failPos = -2; + const static int funPos = -1; + + ATMatcher() : t(0), pos(failPos) + { + } + + operator bool() const + { + return pos != failPos; + } +}; + +/* Initiate matching of a term. */ +ATMatcher & atMatch(ATMatcher & pos, ATerm t); + +/* Get the next argument of an application. */ +ATMatcher & operator >> (ATMatcher & pos, ATerm & out); + +/* Get the name of the function symbol of an applicatin, or the next + argument of an application as a string. */ +ATMatcher & operator >> (ATMatcher & pos, string & out); + +/* Like the previous, but check that the string is equal to the given + string. */ +ATMatcher & operator >> (ATMatcher & pos, const string & s); + +/* Get the next argument of an application, and verify that it is a + list. */ +ATMatcher & operator >> (ATMatcher & pos, ATermList & out); + + +#endif /* !__ATERM_H */ diff --git a/src/libnix/expr.cc b/src/libnix/expr.cc index 9bbe80ab4..67fa69f72 100644 --- a/src/libnix/expr.cc +++ b/src/libnix/expr.cc @@ -3,14 +3,6 @@ #include "store.hh" -string printTerm(ATerm t) -{ - char * s = ATwriteToString(t); - if (!s) throw Error("cannot print term"); - return s; -} - - Error badTerm(const format & f, ATerm t) { char * s = ATwriteToString(t); @@ -26,7 +18,7 @@ Error badTerm(const format & f, ATerm t) Hash hashTerm(ATerm t) { - return hashString(printTerm(t)); + return hashString(atPrint(t)); } @@ -50,10 +42,11 @@ Path writeTerm(ATerm t, const string & suffix) static void parsePaths(ATermList paths, PathSet & out) { + ATMatcher m; while (!ATisEmpty(paths)) { - char * s; + string s; ATerm t = ATgetFirst(paths); - if (!ATmatch(t, "", &s)) + if (!(atMatch(m, t) >> s)) throw badTerm("not a path", t); out.insert(s); paths = ATgetNext(paths); @@ -91,21 +84,22 @@ static void checkClosure(const Closure & closure) static bool parseClosure(ATerm t, Closure & closure) { ATermList roots, elems; - - if (!ATmatch(t, "Closure([], [])", &roots, &elems)) + ATMatcher m; + + if (!(atMatch(m, t) >> "Closure" >> roots >> elems)) return false; parsePaths(roots, closure.roots); while (!ATisEmpty(elems)) { - char * s1; + string path; ATermList refs; ATerm t = ATgetFirst(elems); - if (!ATmatch(t, "(, [])", &s1, &refs)) + if (!(atMatch(m, t) >> "" >> path >> refs)) throw badTerm("not a closure element", t); ClosureElem elem; parsePaths(refs, elem.refs); - closure.elems[s1] = elem; + closure.elems[path] = elem; elems = ATgetNext(elems); } @@ -116,19 +110,13 @@ static bool parseClosure(ATerm t, Closure & closure) static bool parseDerivation(ATerm t, Derivation & derivation) { + ATMatcher m; ATermList outs, ins, args, bnds; - char * builder; - char * platform; + string builder, platform; - if (!ATmatch(t, "Derive([], [], , , [], [])", - &outs, &ins, &platform, &builder, &args, &bnds)) - { - /* !!! compatibility -> remove eventually */ - if (!ATmatch(t, "Derive([], [], , , [])", - &outs, &ins, &builder, &platform, &bnds)) - return false; - args = ATempty; - } + if (!(atMatch(m, t) >> "Derive" >> outs >> ins >> platform + >> builder >> args >> bnds)) + return false; parsePaths(outs, derivation.outputs); parsePaths(ins, derivation.inputs); @@ -137,18 +125,18 @@ static bool parseDerivation(ATerm t, Derivation & derivation) derivation.platform = platform; while (!ATisEmpty(args)) { - char * s; + string s; ATerm arg = ATgetFirst(args); - if (!ATmatch(arg, "", &s)) + if (!(atMatch(m, arg) >> s)) throw badTerm("string expected", arg); derivation.args.push_back(s); args = ATgetNext(args); } while (!ATisEmpty(bnds)) { - char * s1, * s2; + string s1, s2; ATerm bnd = ATgetFirst(bnds); - if (!ATmatch(bnd, "(, )", &s1, &s2)) + if (!(atMatch(m, bnd) >> "" >> s1 >> s2)) throw badTerm("tuple of strings expected", bnd); derivation.env[s1] = s2; bnds = ATgetNext(bnds); diff --git a/src/libnix/expr.hh b/src/libnix/expr.hh index 7d0420935..f5abf9af0 100644 --- a/src/libnix/expr.hh +++ b/src/libnix/expr.hh @@ -1,10 +1,7 @@ #ifndef __FSTATE_H #define __FSTATE_H -extern "C" { -#include -} - +#include "aterm.hh" #include "store.hh" @@ -43,9 +40,6 @@ struct NixExpr }; -/* Return a canonical textual representation of an expression. */ -string printTerm(ATerm t); - /* Throw an exception with an error message containing the given aterm. */ Error badTerm(const format & f, ATerm t); diff --git a/src/libnix/normalise.cc b/src/libnix/normalise.cc index 49f86cc6f..cb2bf4f5b 100644 --- a/src/libnix/normalise.cc +++ b/src/libnix/normalise.cc @@ -239,7 +239,7 @@ Path normaliseNixExpr(const Path & _nePath, PathSet pending) /* Write the normal form. This does not have to occur in the transaction below because writing terms is idem-potent. */ ATerm nfTerm = unparseNixExpr(nf); - printMsg(lvlVomit, format("normal form: %1%") % printTerm(nfTerm)); + printMsg(lvlVomit, format("normal form: %1%") % atPrint(nfTerm)); Path nfPath = writeTerm(nfTerm, "-s"); /* Register each outpat path, and register the normal form. This diff --git a/src/libnix/references.hh b/src/libnix/references.hh index d009453d6..ada23a883 100644 --- a/src/libnix/references.hh +++ b/src/libnix/references.hh @@ -1,5 +1,5 @@ -#ifndef __VALUES_H -#define __VALUES_H +#ifndef __REFERENCES_H +#define __REFERENCES_H #include "util.hh" @@ -7,4 +7,4 @@ Strings filterReferences(const Path & path, const Strings & refs); -#endif /* !__VALUES_H */ +#endif /* !__REFERENCES_H */ diff --git a/src/libnix/test-aterm.cc b/src/libnix/test-aterm.cc new file mode 100644 index 000000000..325639ca4 --- /dev/null +++ b/src/libnix/test-aterm.cc @@ -0,0 +1,66 @@ +#include "aterm.hh" +#include + + +void runTests() +{ + verbosity = lvlDebug; + + ATMatcher pos; + + ATerm t = ATmake("Call(Foo, Bar, \"xyz\")"); + + debug(format("term: %1%") % t); + + string fun, arg3; + ATerm lhs, rhs; + + if (!(atMatch(pos, t) >> "Call" >> lhs >> rhs >> arg3)) + throw Error("should succeed"); + if (arg3 != "xyz") throw Error("bad 1"); + + if (!(atMatch(pos, t) >> fun >> lhs >> rhs >> arg3)) + throw Error("should succeed"); + if (fun != "Call") throw Error("bad 2"); + if (arg3 != "xyz") throw Error("bad 3"); + + if (!(atMatch(pos, t) >> fun >> lhs >> rhs >> "xyz")) + throw Error("should succeed"); + + if (atMatch(pos, t) >> fun >> lhs >> rhs >> "abc") + throw Error("should fail"); + + if (atMatch(pos, t) >> "Call" >> lhs >> rhs >> "abc") + throw Error("should fail"); + + t = ATmake("X([A, B, C], \"abc\")"); + + ATerm t1, t2, t3; + if (atMatch(pos, t) >> "X" >> t1 >> t2 >> t3) + throw Error("should fail"); + if (!(atMatch(pos, t) >> "X" >> t1 >> t2)) + throw Error("should succeed"); + ATermList ts; + if (!(atMatch(pos, t) >> "X" >> ts >> t2)) + throw Error("should succeed"); + if (ATgetLength(ts) != 3) + throw Error("bad"); + if (atMatch(pos, t) >> "X" >> t1 >> ts) + throw Error("should fail"); +} + + +int main(int argc, char * * argv) +{ + ATerm bottomOfStack; + ATinit(argc, argv, &bottomOfStack); + + try { + runTests(); + } catch (Error & e) { + printMsg(lvlError, format("error: %1%") % e.msg()); + return 1; + } + + return 0; +} From 45610ae675f6f8d6ecbd48c495cb7012b143d531 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 16 Nov 2003 18:31:29 +0000 Subject: [PATCH 0317/6440] * An forward non-random access input iterator class for ATermLists. --- src/fix-ng/eval.cc | 36 +++++++++++-------------------- src/fix-ng/fix-expr.cc | 49 ++++++++++++++---------------------------- src/fix-ng/fix.cc | 6 ++---- src/fix-ng/primops.cc | 11 ++++------ src/libnix/aterm.hh | 22 +++++++++++++++++++ src/libnix/expr.cc | 32 +++++++++++---------------- 6 files changed, 68 insertions(+), 88 deletions(-) diff --git a/src/fix-ng/eval.cc b/src/fix-ng/eval.cc index 55cb5fbcf..57cca4c4a 100644 --- a/src/fix-ng/eval.cc +++ b/src/fix-ng/eval.cc @@ -21,25 +21,21 @@ static Expr substArgs(Expr body, ATermList formals, Expr arg) Expr undefined = ATmake("Undefined"); /* Get the formal arguments. */ - while (!ATisEmpty(formals)) { - ATerm t = ATgetFirst(formals); + for (ATermIterator i(formals); i; ++i) { Expr name, def; - if (atMatch(m, t) >> "NoDefFormal" >> name) + if (atMatch(m, *i) >> "NoDefFormal" >> name) subs.set(name, undefined); - else if (atMatch(m, t) >> "DefFormal" >> name >> def) + else if (atMatch(m, *i) >> "DefFormal" >> name >> def) subs.set(name, def); else abort(); /* can't happen */ - formals = ATgetNext(formals); } /* Get the actual arguments, and check that they match with the formals. */ ATermMap args; queryAllAttrs(arg, args); - for (ATermList keys = args.keys(); !ATisEmpty(keys); - keys = ATgetNext(keys)) - { - Expr key = ATgetFirst(keys); + for (ATermIterator i(args.keys()); i; ++i) { + Expr key = *i; Expr cur = subs.get(key); if (!cur) throw badTerm(format("function has no formal argument `%1%'") @@ -48,14 +44,10 @@ static Expr substArgs(Expr body, ATermList formals, Expr arg) } /* Check that all arguments are defined. */ - for (ATermList keys = subs.keys(); !ATisEmpty(keys); - keys = ATgetNext(keys)) - { - Expr key = ATgetFirst(keys); - if (subs.get(key) == undefined) + for (ATermIterator i(subs.keys()); i; ++i) + if (subs.get(*i) == undefined) throw badTerm(format("formal argument `%1%' missing") - % aterm2String(key), arg); - } + % aterm2String(*i), arg); return substitute(subs, body); } @@ -72,26 +64,22 @@ ATerm expandRec(ATerm e, ATermList bnds) /* Create the substitution list. */ ATermMap subs; - ATermList bs = bnds; - while (!ATisEmpty(bs)) { + for (ATermIterator i(bnds); i; ++i) { string s; Expr e2; - if (!(atMatch(m, ATgetFirst(bs)) >> "Bind" >> s >> e2)) + if (!(atMatch(m, *i) >> "Bind" >> s >> e2)) abort(); /* can't happen */ subs.set(s, ATmake("Select(, )", e, s.c_str())); - bs = ATgetNext(bs); } /* Create the non-recursive set. */ ATermMap as; - bs = bnds; - while (!ATisEmpty(bs)) { + for (ATermIterator i(bnds); i; ++i) { string s; Expr e2; - if (!(atMatch(m, ATgetFirst(bs)) >> "Bind" >> s >> e2)) + if (!(atMatch(m, *i) >> "Bind" >> s >> e2)) abort(); /* can't happen */ as.set(s, substitute(subs, e2)); - bs = ATgetNext(bs); } return makeAttrs(as); diff --git a/src/fix-ng/fix-expr.cc b/src/fix-ng/fix-expr.cc index a3d24ba0e..e9c5a3ba6 100644 --- a/src/fix-ng/fix-expr.cc +++ b/src/fix-ng/fix-expr.cc @@ -18,10 +18,8 @@ ATermMap::ATermMap(const ATermMap & map) table = ATtableCreate(ATgetLength(keys), map.maxLoadPct); if (!table) throw Error("cannot create ATerm table"); - for (; !ATisEmpty(keys); keys = ATgetNext(keys)) { - ATerm key = ATgetFirst(keys); - set(key, map.get(key)); - } + for (ATermIterator i(keys); i; ++i) + set(*i, map.get(*i)); } @@ -104,10 +102,8 @@ ATerm bottomupRewrite(TermFun & f, ATerm e) ATermList in = (ATermList) e; ATermList out = ATempty; - while (!ATisEmpty(in)) { - out = ATinsert(out, bottomupRewrite(f, ATgetFirst(in))); - in = ATgetNext(in); - } + for (ATermIterator i(in); i; ++i) + out = ATinsert(out, bottomupRewrite(f, *i)); e = (ATerm) ATreverse(out); } @@ -123,13 +119,12 @@ void queryAllAttrs(Expr e, ATermMap & attrs) if (!(atMatch(m, e) >> "Attrs" >> bnds)) throw badTerm("expected attribute set", e); - while (!ATisEmpty(bnds)) { + for (ATermIterator i(bnds); i; ++i) { string s; Expr e; - if (!(atMatch(m, ATgetFirst(bnds)) >> "Bind" >> s >> e)) + if (!(atMatch(m, *i) >> "Bind" >> s >> e)) abort(); /* can't happen */ attrs.set(s, e); - bnds = ATgetNext(bnds); } } @@ -144,13 +139,10 @@ Expr queryAttr(Expr e, const string & name) Expr makeAttrs(const ATermMap & attrs) { - ATermList bnds = ATempty, keys = attrs.keys(); - while (!ATisEmpty(keys)) { - Expr key = ATgetFirst(keys); + ATermList bnds = ATempty; + for (ATermIterator i(attrs.keys()); i; ++i) bnds = ATinsert(bnds, - ATmake("Bind(, )", key, attrs.get(key))); - keys = ATgetNext(keys); - } + ATmake("Bind(, )", *i, attrs.get(*i))); return ATmake("Attrs()", ATreverse(bnds)); } @@ -171,14 +163,12 @@ Expr substitute(const ATermMap & subs, Expr e) ATerm body; if (atMatch(m, e) >> "Function" >> formals >> body) { ATermMap subs2(subs); - ATermList fs = formals; - while (!ATisEmpty(fs)) { + for (ATermIterator i(formals); i; ++i) { Expr def; - if (!(atMatch(m, ATgetFirst(fs)) >> "NoDefFormal" >> s) && - !(atMatch(m, ATgetFirst(fs)) >> "DefFormal" >> s >> def)) + if (!(atMatch(m, *i) >> "NoDefFormal" >> s) && + !(atMatch(m, *i) >> "DefFormal" >> s >> def)) abort(); subs2.remove(s); - fs = ATgetNext(fs); } return ATmake("Function(, )", formals, substitute(subs2, body)); @@ -188,13 +178,11 @@ Expr substitute(const ATermMap & subs, Expr e) ATermList bindings; if (atMatch(m, e) >> "Rec" >> bindings) { ATermMap subs2(subs); - ATermList bnds = bindings; - while (!ATisEmpty(bnds)) { + for (ATermIterator i(bindings); i; ++i) { Expr e; - if (!(atMatch(m, ATgetFirst(bnds)) >> "Bind" >> s >> e)) + if (!(atMatch(m, *i) >> "Bind" >> s >> e)) abort(); /* can't happen */ subs2.remove(s); - bnds = ATgetNext(bnds); } return ATmake("Rec()", substitute(subs2, (ATerm) bindings)); } @@ -211,14 +199,9 @@ Expr substitute(const ATermMap & subs, Expr e) } if (ATgetType(e) == AT_LIST) { - ATermList in = (ATermList) e; ATermList out = ATempty; - - while (!ATisEmpty(in)) { - out = ATinsert(out, substitute(subs, ATgetFirst(in))); - in = ATgetNext(in); - } - + for (ATermIterator i((ATermList) e); i; ++i) + out = ATinsert(out, substitute(subs, *i)); return (ATerm) ATreverse(out); } diff --git a/src/fix-ng/fix.cc b/src/fix-ng/fix.cc index 49f19669a..3b7dae38e 100644 --- a/src/fix-ng/fix.cc +++ b/src/fix-ng/fix.cc @@ -54,10 +54,8 @@ static void printNixExpr(EvalState & state, Expr e) } if (ATgetType(e) == AT_LIST) { - while (!ATisEmpty(es)) { - printNixExpr(state, evalExpr(state, ATgetFirst(es))); - es = ATgetNext(es); - } + for (ATermIterator i((ATermList) e); i; ++i) + printNixExpr(state, evalExpr(state, *i)); return; } diff --git a/src/fix-ng/primops.cc b/src/fix-ng/primops.cc index a68352579..86b364c30 100644 --- a/src/fix-ng/primops.cc +++ b/src/fix-ng/primops.cc @@ -108,11 +108,10 @@ static string processBinding(EvalState & state, Expr e, NixExpr & ne) if (atMatch(m, e) >> "List" >> es) { string s; bool first = true; - while (!ATisEmpty(es)) { + for (ATermIterator i(es); i; ++i) { startNest(nest, lvlVomit, format("processing list element")); if (!first) s = s + " "; else first = false; - s += processBinding(state, evalExpr(state, ATgetFirst(es)), ne); - es = ATgetNext(es); + s += processBinding(state, evalExpr(state, *i), ne); } return s; } @@ -140,10 +139,8 @@ Expr primDerivation(EvalState & state, Expr args) Hash outHash; bool outHashGiven = false; - for (ATermList keys = attrs.keys(); !ATisEmpty(keys); - keys = ATgetNext(keys)) - { - string key = aterm2String(ATgetFirst(keys)); + for (ATermIterator i(attrs.keys()); i; ++i) { + string key = aterm2String(*i); Expr value = attrs.get(key); startNest(nest, lvlVomit, format("processing attribute `%1%'") % key); diff --git a/src/libnix/aterm.hh b/src/libnix/aterm.hh index 1e4ee80ee..16d8d6bb6 100644 --- a/src/libnix/aterm.hh +++ b/src/libnix/aterm.hh @@ -14,6 +14,28 @@ string atPrint(ATerm t); /* Write an ATerm to an output stream. */ ostream & operator << (ostream & stream, ATerm e); +class ATermIterator +{ + ATermList t; + +public: + ATermIterator(ATermList _t) : t(_t) { } + ATermIterator & operator ++ () + { + t = ATgetNext(t); + return *this; + } + ATerm operator * () + { + return ATgetFirst(t); + } + operator bool () + { + return t != ATempty; + } +}; + + /* Type-safe matching. */ struct ATMatcher diff --git a/src/libnix/expr.cc b/src/libnix/expr.cc index 67fa69f72..7bb1f5306 100644 --- a/src/libnix/expr.cc +++ b/src/libnix/expr.cc @@ -43,13 +43,11 @@ Path writeTerm(ATerm t, const string & suffix) static void parsePaths(ATermList paths, PathSet & out) { ATMatcher m; - while (!ATisEmpty(paths)) { + for (ATermIterator i(paths); i; ++i) { string s; - ATerm t = ATgetFirst(paths); - if (!(atMatch(m, t) >> s)) - throw badTerm("not a path", t); + if (!(atMatch(m, *i) >> s)) + throw badTerm("not a path", *i); out.insert(s); - paths = ATgetNext(paths); } } @@ -91,16 +89,14 @@ static bool parseClosure(ATerm t, Closure & closure) parsePaths(roots, closure.roots); - while (!ATisEmpty(elems)) { + for (ATermIterator i(elems); i; ++i) { string path; ATermList refs; - ATerm t = ATgetFirst(elems); - if (!(atMatch(m, t) >> "" >> path >> refs)) - throw badTerm("not a closure element", t); + if (!(atMatch(m, *i) >> "" >> path >> refs)) + throw badTerm("not a closure element", *i); ClosureElem elem; parsePaths(refs, elem.refs); closure.elems[path] = elem; - elems = ATgetNext(elems); } checkClosure(closure); @@ -124,22 +120,18 @@ static bool parseDerivation(ATerm t, Derivation & derivation) derivation.builder = builder; derivation.platform = platform; - while (!ATisEmpty(args)) { + for (ATermIterator i(args); i; ++i) { string s; - ATerm arg = ATgetFirst(args); - if (!(atMatch(m, arg) >> s)) - throw badTerm("string expected", arg); + if (!(atMatch(m, *i) >> s)) + throw badTerm("string expected", *i); derivation.args.push_back(s); - args = ATgetNext(args); } - while (!ATisEmpty(bnds)) { + for (ATermIterator i(bnds); i; ++i) { string s1, s2; - ATerm bnd = ATgetFirst(bnds); - if (!(atMatch(m, bnd) >> "" >> s1 >> s2)) - throw badTerm("tuple of strings expected", bnd); + if (!(atMatch(m, *i) >> "" >> s1 >> s2)) + throw badTerm("tuple of strings expected", *i); derivation.env[s1] = s2; - bnds = ATgetNext(bnds); } return true; From 8798fae30450a88c339c8f23d7e0c75f5df2ef1c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Nov 2003 10:47:59 +0000 Subject: [PATCH 0318/6440] * Source tree refactoring. --- configure.ac | 2 +- src/Makefile.am | 2 +- src/fix-ng/Makefile.am | 7 +- src/fix/Makefile.am | 8 - src/fix/fix.cc | 489 -------------------------- src/libmain/Makefile.am | 2 +- src/libnix/Makefile.am | 7 +- src/libutil/Makefile.am | 14 + src/{libnix => libutil}/archive.cc | 0 src/{libnix => libutil}/archive.hh | 0 src/{libnix => libutil}/aterm.cc | 0 src/{libnix => libutil}/aterm.hh | 0 src/{libnix => libutil}/hash.cc | 0 src/{libnix => libutil}/hash.hh | 0 src/{libnix => libutil}/md5.c | 0 src/{libnix => libutil}/md5.h | 0 src/{libnix => libutil}/test-aterm.cc | 0 src/{libnix => libutil}/util.cc | 0 src/{libnix => libutil}/util.hh | 0 src/nix-hash/Makefile.am | 6 +- src/nix/Makefile.am | 6 +- 21 files changed, 31 insertions(+), 512 deletions(-) delete mode 100644 src/fix/Makefile.am delete mode 100644 src/fix/fix.cc create mode 100644 src/libutil/Makefile.am rename src/{libnix => libutil}/archive.cc (100%) rename src/{libnix => libutil}/archive.hh (100%) rename src/{libnix => libutil}/aterm.cc (100%) rename src/{libnix => libutil}/aterm.hh (100%) rename src/{libnix => libutil}/hash.cc (100%) rename src/{libnix => libutil}/hash.hh (100%) rename src/{libnix => libutil}/md5.c (100%) rename src/{libnix => libutil}/md5.h (100%) rename src/{libnix => libutil}/test-aterm.cc (100%) rename src/{libnix => libutil}/util.cc (100%) rename src/{libnix => libutil}/util.hh (100%) diff --git a/configure.ac b/configure.ac index 54db20317..cc7db29c8 100644 --- a/configure.ac +++ b/configure.ac @@ -28,11 +28,11 @@ AC_CONFIG_FILES([Makefile src/Makefile src/boost/Makefile src/boost/format/Makefile + src/libutil/Makefile src/libnix/Makefile src/libmain/Makefile src/nix/Makefile src/nix-hash/Makefile - src/fix/Makefile src/fix-ng/Makefile scripts/Makefile corepkgs/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 57af4dbc8..e62c29d7c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1 +1 @@ -SUBDIRS = boost libnix libmain nix nix-hash fix fix-ng +SUBDIRS = boost libutil libnix libmain nix nix-hash fix-ng diff --git a/src/fix-ng/Makefile.am b/src/fix-ng/Makefile.am index 359e39c46..cf8549329 100644 --- a/src/fix-ng/Makefile.am +++ b/src/fix-ng/Makefile.am @@ -1,11 +1,12 @@ bin_PROGRAMS = fix-ng fix_ng_SOURCES = fix-expr.cc parser.cc eval.cc primops.cc fix.cc -fix_ng_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../boost/format/libformat.a \ - -L../../externals/inst/lib -ldb_cxx -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm +fix_ng_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../libutil/libutil.a \ + ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ + -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm AM_CXXFLAGS = \ - -I.. -I../../externals/inst/include -I../libnix -I../libmain + -I.. -I../../externals/inst/include -I../libutil -I../libnix -I../libmain # Parse table generation. diff --git a/src/fix/Makefile.am b/src/fix/Makefile.am deleted file mode 100644 index 4786db54c..000000000 --- a/src/fix/Makefile.am +++ /dev/null @@ -1,8 +0,0 @@ -bin_PROGRAMS = fix - -fix_SOURCES = fix.cc -fix_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../boost/format/libformat.a \ - -L../../externals/inst/lib -ldb_cxx -lATerm - -AM_CXXFLAGS = \ - -I.. -I../../externals/inst/include -I../libnix -I../libmain diff --git a/src/fix/fix.cc b/src/fix/fix.cc deleted file mode 100644 index 8b8441050..000000000 --- a/src/fix/fix.cc +++ /dev/null @@ -1,489 +0,0 @@ -#include -#include - -#include "globals.hh" -#include "normalise.hh" -#include "shared.hh" - - -typedef ATerm Expr; - -typedef map NormalForms; -typedef map PkgPaths; -typedef map PkgHashes; - -struct EvalState -{ - Paths searchDirs; - NormalForms normalForms; - PkgPaths pkgPaths; - PkgHashes pkgHashes; /* normalised package hashes */ - Expr blackHole; - - EvalState() - { - blackHole = ATmake("BlackHole()"); - if (!blackHole) throw Error("cannot build black hole"); - } -}; - - -static Expr evalFile(EvalState & state, const Path & path); -static Expr evalExpr(EvalState & state, Expr e); - - -static Path searchPath(const Paths & searchDirs, const Path & relPath) -{ - if (string(relPath, 0, 1) == "/") return relPath; - - for (Paths::const_iterator i = searchDirs.begin(); - i != searchDirs.end(); i++) - { - Path path = *i + "/" + relPath; - if (pathExists(path)) return path; - } - - throw Error( - format("path `%1%' not found in any of the search directories") - % relPath); -} - - -static Expr substExpr(string x, Expr rep, Expr e) -{ - char * s; - Expr e2; - - if (ATmatch(e, "Var()", &s)) - if (x == s) - return rep; - else - return e; - - ATermList formals; - if (ATmatch(e, "Function([], )", &formals, &e2)) { - while (!ATisEmpty(formals)) { - if (!ATmatch(ATgetFirst(formals), "", &s)) - throw badTerm("not a list of formals", (ATerm) formals); - if (x == (string) s) - return e; - formals = ATgetNext(formals); - } - } - - /* Generically substitute in subterms. */ - - if (ATgetType(e) == AT_APPL) { - AFun fun = ATgetAFun(e); - int arity = ATgetArity(fun); - ATermList args = ATempty; - - for (int i = arity - 1; i >= 0; i--) - args = ATinsert(args, substExpr(x, rep, ATgetArgument(e, i))); - - return (ATerm) ATmakeApplList(fun, args); - } - - if (ATgetType(e) == AT_LIST) { - ATermList in = (ATermList) e; - ATermList out = ATempty; - - while (!ATisEmpty(in)) { - out = ATinsert(out, substExpr(x, rep, ATgetFirst(in))); - in = ATgetNext(in); - } - - return (ATerm) ATreverse(out); - } - - throw badTerm("do not know how to substitute", e); -} - - -static Expr substExprMany(ATermList formals, ATermList args, Expr body) -{ - char * s; - Expr e; - - /* !!! check args against formals */ - - while (!ATisEmpty(args)) { - ATerm tup = ATgetFirst(args); - if (!ATmatch(tup, "(, )", &s, &e)) - throw badTerm("expected an argument tuple", tup); - - body = substExpr(s, e, body); - - args = ATgetNext(args); - } - - return body; -} - - -static PathSet nixExprRootsCached(EvalState & state, const Path & nePath) -{ - PkgPaths::iterator i = state.pkgPaths.find(nePath); - if (i != state.pkgPaths.end()) - return i->second; - else { - PathSet paths = nixExprRoots(nePath); - state.pkgPaths[nePath] = paths; - return paths; - } -} - - -static Hash hashPackage(EvalState & state, NixExpr ne) -{ - if (ne.type == NixExpr::neDerivation) { - PathSet inputs2; - for (PathSet::iterator i = ne.derivation.inputs.begin(); - i != ne.derivation.inputs.end(); i++) - { - PkgHashes::iterator j = state.pkgHashes.find(*i); - if (j == state.pkgHashes.end()) - throw Error(format("don't know expression `%1%'") % (string) *i); - inputs2.insert(j->second); - } - ne.derivation.inputs = inputs2; - } - return hashTerm(unparseNixExpr(ne)); -} - - -static string processBinding(EvalState & state, Expr e, NixExpr & ne) -{ - char * s1; - - if (ATmatch(e, "NixExpr()", &s1)) { - Path nePath(s1); - PathSet paths = nixExprRootsCached(state, nePath); - if (paths.size() != 1) abort(); - Path path = *(paths.begin()); - ne.derivation.inputs.insert(nePath); - return path; - } - - if (ATmatch(e, "", &s1)) - return s1; - - if (ATmatch(e, "True")) return "1"; - - if (ATmatch(e, "False")) return ""; - - ATermList l; - if (ATmatch(e, "[]", &l)) { - string s; - bool first = true; - while (!ATisEmpty(l)) { - if (!first) s = s + " "; else first = false; - s += processBinding(state, evalExpr(state, ATgetFirst(l)), ne); - l = ATgetNext(l); - } - return s; - } - - throw badTerm("invalid package binding", e); -} - - -static Expr evalExpr2(EvalState & state, Expr e) -{ - char * s1; - Expr e1, e2, e3, e4; - ATermList bnds; - - /* Normal forms. */ - if (ATmatch(e, "", &s1) || - ATmatch(e, "[]", &e1) || - ATmatch(e, "True") || - ATmatch(e, "False") || - ATmatch(e, "Function([], )", &e1, &e2) || - ATmatch(e, "NixExpr()", &s1)) - return e; - - try { - Hash pkgHash = hashPackage(state, parseNixExpr(e)); - Path pkgPath = writeTerm(e, ""); - state.pkgHashes[pkgPath] = pkgHash; - return ATmake("NixExpr()", pkgPath.c_str()); - } catch (...) { /* !!! catch parse errors only */ - } - - /* Application. */ - if (ATmatch(e, "Call(, [])", &e1, &e2) || - ATmatch(e, "App(, [])", &e1, &e2)) { - e1 = evalExpr(state, e1); - if (!ATmatch(e1, "Function([], )", &e3, &e4)) - throw badTerm("expecting a function", e1); - return evalExpr(state, - substExprMany((ATermList) e3, (ATermList) e2, e4)); - } - - /* Conditional. */ - if (ATmatch(e, "If(, , )", &e1, &e2, &e3)) { - e1 = evalExpr(state, e1); - Expr x; - if (ATmatch(e1, "True")) x = e2; - else if (ATmatch(e1, "False")) x = e3; - else throw badTerm("expecting a boolean", e1); - return evalExpr(state, x); - } - - /* Ad-hoc function for string matching. */ - if (ATmatch(e, "HasSubstr(, )", &e1, &e2)) { - e1 = evalExpr(state, e1); - e2 = evalExpr(state, e2); - - char * s1, * s2; - if (!ATmatch(e1, "", &s1)) - throw badTerm("expecting a string", e1); - if (!ATmatch(e2, "", &s2)) - throw badTerm("expecting a string", e2); - - return - string(s1).find(string(s2)) != string::npos ? - ATmake("True") : ATmake("False"); - } - - /* Platform constant. */ - if (ATmatch(e, "Platform")) { - return ATmake("", thisSystem.c_str()); - } - - /* Fix inclusion. */ - if (ATmatch(e, "IncludeFix()", &s1)) { - Path fileName(s1); - return evalFile(state, s1); - } - - /* Relative files. */ - if (ATmatch(e, "Relative()", &s1)) { - Path srcPath = searchPath(state.searchDirs, s1); - Path dstPath = addToStore(srcPath); - - ClosureElem elem; - NixExpr ne; - ne.type = NixExpr::neClosure; - ne.closure.roots.insert(dstPath); - ne.closure.elems[dstPath] = elem; - - Hash pkgHash = hashPackage(state, ne); - Path pkgPath = writeTerm(unparseNixExpr(ne), ""); - state.pkgHashes[pkgPath] = pkgHash; - - printMsg(lvlChatty, format("copied `%1%' -> closure `%2%'") - % srcPath % pkgPath); - - return ATmake("NixExpr()", pkgPath.c_str()); - } - - /* Packages are transformed into Nix derivation expressions. */ - if (ATmatch(e, "Package([])", &bnds)) { - - /* Evaluate the bindings and put them in a map. */ - map bndMap; - bndMap["platform"] = ATmake("", thisSystem.c_str()); - while (!ATisEmpty(bnds)) { - ATerm bnd = ATgetFirst(bnds); - if (!ATmatch(bnd, "(, )", &s1, &e1)) - throw badTerm("binding expected", bnd); - bndMap[s1] = evalExpr(state, e1); - bnds = ATgetNext(bnds); - } - - /* Gather information for building the derivation - expression. */ - NixExpr ne; - ne.type = NixExpr::neDerivation; - ne.derivation.platform = thisSystem; - string name; - Path outPath; - Hash outHash; - bool outHashGiven = false; - bnds = ATempty; - - for (map::iterator it = bndMap.begin(); - it != bndMap.end(); it++) - { - string key = it->first; - ATerm value = it->second; - - if (key == "args") { - ATermList args; - if (!ATmatch(value, "[]", &args)) - throw badTerm("list expected", value); - - while (!ATisEmpty(args)) { - Expr arg = evalExpr(state, ATgetFirst(args)); - ne.derivation.args.push_back(processBinding(state, arg, ne)); - args = ATgetNext(args); - } - } - - else { - string s = processBinding(state, value, ne); - ne.derivation.env[key] = s; - - if (key == "build") ne.derivation.builder = s; - if (key == "name") name = s; - if (key == "outPath") outPath = s; - if (key == "id") { - outHash = parseHash(s); - outHashGiven = true; - } - } - - bnds = ATinsert(bnds, - ATmake("(, )", key.c_str(), value)); - } - - if (ne.derivation.builder == "") - throw badTerm("no builder specified", e); - - if (name == "") - throw badTerm("no package name specified", e); - - /* Determine the output path. */ - if (!outHashGiven) outHash = hashPackage(state, ne); - if (outPath == "") - /* Hash the Nix expression with no outputs to produce a - unique but deterministic path name for this package. */ - outPath = - canonPath(nixStore + "/" + ((string) outHash).c_str() + "-" + name); - ne.derivation.env["out"] = outPath; - ne.derivation.outputs.insert(outPath); - - /* Write the resulting term into the Nix store directory. */ - Hash pkgHash = outHashGiven - ? hashString((string) outHash + outPath) - : hashPackage(state, ne); - Path pkgPath = writeTerm(unparseNixExpr(ne), "-d-" + name); - state.pkgHashes[pkgPath] = pkgHash; - - printMsg(lvlChatty, format("instantiated `%1%' -> `%2%'") - % name % pkgPath); - - return ATmake("NixExpr()", pkgPath.c_str()); - } - - /* BaseName primitive function. */ - if (ATmatch(e, "BaseName()", &e1)) { - e1 = evalExpr(state, e1); - if (!ATmatch(e1, "", &s1)) - throw badTerm("string expected", e1); - return ATmake("", baseNameOf(s1).c_str()); - } - - /* Barf. */ - throw badTerm("invalid expression", e); -} - - -static Expr evalExpr(EvalState & state, Expr e) -{ - startNest(nest, lvlVomit, - format("evaluating expression: %1%") % e); - - /* Consult the memo table to quickly get the normal form of - previously evaluated expressions. */ - NormalForms::iterator i = state.normalForms.find(e); - if (i != state.normalForms.end()) { - if (i->second == state.blackHole) - throw badTerm("infinite recursion", e); - return i->second; - } - - /* Otherwise, evaluate and memoize. */ - state.normalForms[e] = state.blackHole; - Expr nf = evalExpr2(state, e); - state.normalForms[e] = nf; - return nf; -} - - -static Expr evalFile(EvalState & state, const Path & relPath) -{ - Path path = searchPath(state.searchDirs, relPath); - startNest(nest, lvlTalkative, format("evaluating file `%1%'") % path); - Expr e = ATreadFromNamedFile(path.c_str()); - if (!e) - throw Error(format("unable to read a term from `%1%'") % path); - return evalExpr(state, e); -} - - -static Expr evalStdin(EvalState & state) -{ - startNest(nest, lvlTalkative, format("evaluating standard input")); - Expr e = ATreadFromFile(stdin); - if (!e) - throw Error(format("unable to read a term from stdin")); - return evalExpr(state, e); -} - - -static void printNixExpr(EvalState & state, Expr e) -{ - ATermList es; - char * s; - if (ATmatch(e, "NixExpr()", &s)) { - cout << format("%1%\n") % s; - } - else if (ATmatch(e, "[]", &es)) { - while (!ATisEmpty(es)) { - printNixExpr(state, evalExpr(state, ATgetFirst(es))); - es = ATgetNext(es); - } - } - else throw badTerm("top level does not evaluate to a (list of) Nix expression(s)", e); -} - - -void run(Strings args) -{ - EvalState state; - Strings files; - bool readStdin = false; - - state.searchDirs.push_back("."); - state.searchDirs.push_back(nixDataDir + "/fix"); - - for (Strings::iterator it = args.begin(); - it != args.end(); ) - { - string arg = *it++; - - if (arg == "--includedir" || arg == "-I") { - if (it == args.end()) - throw UsageError(format("argument required in `%1%'") % arg); - state.searchDirs.push_back(*it++); - } - else if (arg == "--verbose" || arg == "-v") - verbosity = (Verbosity) ((int) verbosity + 1); - else if (arg == "-") - readStdin = true; - else if (arg[0] == '-') - throw UsageError(format("unknown flag `%1%`") % arg); - else - files.push_back(arg); - } - - openDB(); - - if (readStdin) { - Expr e = evalStdin(state); - printNixExpr(state, e); - } - - for (Strings::iterator it = files.begin(); - it != files.end(); it++) - { - Expr e = evalFile(state, *it); - printNixExpr(state, e); - } -} - - -string programId = "fix"; diff --git a/src/libmain/Makefile.am b/src/libmain/Makefile.am index aebb1286e..edd668545 100644 --- a/src/libmain/Makefile.am +++ b/src/libmain/Makefile.am @@ -7,6 +7,6 @@ AM_CXXFLAGS = \ -DNIX_DATA_DIR=\"$(datadir)\" \ -DNIX_STATE_DIR=\"$(localstatedir)/nix\" \ -DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \ - -I.. -I../../externals/inst/include -I../libnix + -I.. -I../../externals/inst/include -I../libutil -I../libnix EXTRA_DIST = *.hh diff --git a/src/libnix/Makefile.am b/src/libnix/Makefile.am index 7671b1613..055ef1af7 100644 --- a/src/libnix/Makefile.am +++ b/src/libnix/Makefile.am @@ -1,10 +1,11 @@ noinst_LIBRARIES = libnix.a -libnix_a_SOURCES = util.cc hash.cc archive.cc md5.c \ +libnix_a_SOURCES = \ store.cc expr.cc normalise.cc exec.cc \ - globals.cc db.cc references.cc pathlocks.cc aterm.cc + globals.cc db.cc references.cc pathlocks.cc -AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. -I../../externals/inst/include +AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall \ + -I.. -I../../externals/inst/include -I../libutil EXTRA_DIST = *.hh *.h test-builder-*.sh diff --git a/src/libutil/Makefile.am b/src/libutil/Makefile.am new file mode 100644 index 000000000..46f0a048f --- /dev/null +++ b/src/libutil/Makefile.am @@ -0,0 +1,14 @@ +noinst_LIBRARIES = libutil.a + +libutil_a_SOURCES = util.cc hash.cc archive.cc md5.c aterm.cc + +AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. -I../../externals/inst/include + +EXTRA_DIST = *.hh *.h + +check_PROGRAMS = test-aterm + +test_aterm_SOURCES = test-aterm.cc +test_aterm_LDADD = libnix.a $(LDADD) ../boost/format/libformat.a \ + -L../../externals/inst/lib -ldb_cxx -lATerm + diff --git a/src/libnix/archive.cc b/src/libutil/archive.cc similarity index 100% rename from src/libnix/archive.cc rename to src/libutil/archive.cc diff --git a/src/libnix/archive.hh b/src/libutil/archive.hh similarity index 100% rename from src/libnix/archive.hh rename to src/libutil/archive.hh diff --git a/src/libnix/aterm.cc b/src/libutil/aterm.cc similarity index 100% rename from src/libnix/aterm.cc rename to src/libutil/aterm.cc diff --git a/src/libnix/aterm.hh b/src/libutil/aterm.hh similarity index 100% rename from src/libnix/aterm.hh rename to src/libutil/aterm.hh diff --git a/src/libnix/hash.cc b/src/libutil/hash.cc similarity index 100% rename from src/libnix/hash.cc rename to src/libutil/hash.cc diff --git a/src/libnix/hash.hh b/src/libutil/hash.hh similarity index 100% rename from src/libnix/hash.hh rename to src/libutil/hash.hh diff --git a/src/libnix/md5.c b/src/libutil/md5.c similarity index 100% rename from src/libnix/md5.c rename to src/libutil/md5.c diff --git a/src/libnix/md5.h b/src/libutil/md5.h similarity index 100% rename from src/libnix/md5.h rename to src/libutil/md5.h diff --git a/src/libnix/test-aterm.cc b/src/libutil/test-aterm.cc similarity index 100% rename from src/libnix/test-aterm.cc rename to src/libutil/test-aterm.cc diff --git a/src/libnix/util.cc b/src/libutil/util.cc similarity index 100% rename from src/libnix/util.cc rename to src/libutil/util.cc diff --git a/src/libnix/util.hh b/src/libutil/util.hh similarity index 100% rename from src/libnix/util.hh rename to src/libutil/util.hh diff --git a/src/nix-hash/Makefile.am b/src/nix-hash/Makefile.am index 7fb428a29..7377a62e7 100644 --- a/src/nix-hash/Makefile.am +++ b/src/nix-hash/Makefile.am @@ -1,8 +1,8 @@ bin_PROGRAMS = nix-hash nix_hash_SOURCES = nix-hash.cc -nix_hash_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../boost/format/libformat.a \ - -L../../externals/inst/lib -ldb_cxx -lATerm +nix_hash_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../libutil/libutil.a \ + ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx -lATerm AM_CXXFLAGS = \ - -I.. -I../../externals/inst/include -I../libnix -I../libmain + -I.. -I../../externals/inst/include -I../libutil -I../libnix -I../libmain diff --git a/src/nix/Makefile.am b/src/nix/Makefile.am index 37415976b..c4368a37c 100644 --- a/src/nix/Makefile.am +++ b/src/nix/Makefile.am @@ -1,8 +1,8 @@ bin_PROGRAMS = nix nix_SOURCES = nix.cc dotgraph.cc -nix_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../boost/format/libformat.a \ - -L../../externals/inst/lib -ldb_cxx -lATerm +nix_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../libutil/libutil.a \ + ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx -lATerm nix.o: nix-help.txt.hh @@ -12,7 +12,7 @@ nix.o: nix-help.txt.hh echo '"' >> $@ AM_CXXFLAGS = \ - -I.. -I../../externals/inst/include -I../libnix -I../libmain + -I.. -I../../externals/inst/include -I../libutil -I../libnix -I../libmain install-data-local: $(INSTALL) -d $(localstatedir)/nix From 9f0f020929c9e093405cc6193d2f227cab763912 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Nov 2003 10:55:27 +0000 Subject: [PATCH 0319/6440] * libnix -> libstore. --- configure.ac | 2 +- src/Makefile.am | 2 +- src/fix-ng/Makefile.am | 4 ++-- src/libmain/Makefile.am | 2 +- src/{libnix => libstore}/Makefile.am | 6 +++--- src/{libnix => libstore}/db.cc | 0 src/{libnix => libstore}/db.hh | 0 src/{libnix => libstore}/exec.cc | 0 src/{libnix => libstore}/exec.hh | 0 src/{libnix => libstore}/expr.cc | 0 src/{libnix => libstore}/expr.hh | 0 src/{libnix => libstore}/globals.cc | 0 src/{libnix => libstore}/globals.hh | 0 src/{libnix => libstore}/normalise.cc | 0 src/{libnix => libstore}/normalise.hh | 0 src/{libnix => libstore}/pathlocks.cc | 0 src/{libnix => libstore}/pathlocks.hh | 0 src/{libnix => libstore}/references.cc | 0 src/{libnix => libstore}/references.hh | 0 src/{libnix => libstore}/store.cc | 0 src/{libnix => libstore}/store.hh | 0 src/{libnix => libstore}/test-builder-1.sh | 0 src/{libnix => libstore}/test-builder-2.sh | 0 src/{libnix => libstore}/test.cc | 0 src/nix-hash/Makefile.am | 4 ++-- src/nix/Makefile.am | 4 ++-- 26 files changed, 12 insertions(+), 12 deletions(-) rename src/{libnix => libstore}/Makefile.am (73%) rename src/{libnix => libstore}/db.cc (100%) rename src/{libnix => libstore}/db.hh (100%) rename src/{libnix => libstore}/exec.cc (100%) rename src/{libnix => libstore}/exec.hh (100%) rename src/{libnix => libstore}/expr.cc (100%) rename src/{libnix => libstore}/expr.hh (100%) rename src/{libnix => libstore}/globals.cc (100%) rename src/{libnix => libstore}/globals.hh (100%) rename src/{libnix => libstore}/normalise.cc (100%) rename src/{libnix => libstore}/normalise.hh (100%) rename src/{libnix => libstore}/pathlocks.cc (100%) rename src/{libnix => libstore}/pathlocks.hh (100%) rename src/{libnix => libstore}/references.cc (100%) rename src/{libnix => libstore}/references.hh (100%) rename src/{libnix => libstore}/store.cc (100%) rename src/{libnix => libstore}/store.hh (100%) rename src/{libnix => libstore}/test-builder-1.sh (100%) rename src/{libnix => libstore}/test-builder-2.sh (100%) rename src/{libnix => libstore}/test.cc (100%) diff --git a/configure.ac b/configure.ac index cc7db29c8..9d4b41b6b 100644 --- a/configure.ac +++ b/configure.ac @@ -29,7 +29,7 @@ AC_CONFIG_FILES([Makefile src/boost/Makefile src/boost/format/Makefile src/libutil/Makefile - src/libnix/Makefile + src/libstore/Makefile src/libmain/Makefile src/nix/Makefile src/nix-hash/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index e62c29d7c..ed989fed2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1 +1 @@ -SUBDIRS = boost libutil libnix libmain nix nix-hash fix-ng +SUBDIRS = boost libutil libstore libmain nix nix-hash fix-ng diff --git a/src/fix-ng/Makefile.am b/src/fix-ng/Makefile.am index cf8549329..df3d17308 100644 --- a/src/fix-ng/Makefile.am +++ b/src/fix-ng/Makefile.am @@ -1,12 +1,12 @@ bin_PROGRAMS = fix-ng fix_ng_SOURCES = fix-expr.cc parser.cc eval.cc primops.cc fix.cc -fix_ng_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../libutil/libutil.a \ +fix_ng_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm AM_CXXFLAGS = \ - -I.. -I../../externals/inst/include -I../libutil -I../libnix -I../libmain + -I.. -I../../externals/inst/include -I../libutil -I../libstore -I../libmain # Parse table generation. diff --git a/src/libmain/Makefile.am b/src/libmain/Makefile.am index edd668545..5d3144ccc 100644 --- a/src/libmain/Makefile.am +++ b/src/libmain/Makefile.am @@ -7,6 +7,6 @@ AM_CXXFLAGS = \ -DNIX_DATA_DIR=\"$(datadir)\" \ -DNIX_STATE_DIR=\"$(localstatedir)/nix\" \ -DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \ - -I.. -I../../externals/inst/include -I../libutil -I../libnix + -I.. -I../../externals/inst/include -I../libutil -I../libstore EXTRA_DIST = *.hh diff --git a/src/libnix/Makefile.am b/src/libstore/Makefile.am similarity index 73% rename from src/libnix/Makefile.am rename to src/libstore/Makefile.am index 055ef1af7..472b1b079 100644 --- a/src/libnix/Makefile.am +++ b/src/libstore/Makefile.am @@ -1,6 +1,6 @@ -noinst_LIBRARIES = libnix.a +noinst_LIBRARIES = libstore.a -libnix_a_SOURCES = \ +libstore_a_SOURCES = \ store.cc expr.cc normalise.cc exec.cc \ globals.cc db.cc references.cc pathlocks.cc @@ -12,6 +12,6 @@ EXTRA_DIST = *.hh *.h test-builder-*.sh check_PROGRAMS = test-aterm test_aterm_SOURCES = test-aterm.cc -test_aterm_LDADD = libnix.a $(LDADD) ../boost/format/libformat.a \ +test_aterm_LDADD = libstore.a $(LDADD) ../boost/format/libformat.a \ -L../../externals/inst/lib -ldb_cxx -lATerm diff --git a/src/libnix/db.cc b/src/libstore/db.cc similarity index 100% rename from src/libnix/db.cc rename to src/libstore/db.cc diff --git a/src/libnix/db.hh b/src/libstore/db.hh similarity index 100% rename from src/libnix/db.hh rename to src/libstore/db.hh diff --git a/src/libnix/exec.cc b/src/libstore/exec.cc similarity index 100% rename from src/libnix/exec.cc rename to src/libstore/exec.cc diff --git a/src/libnix/exec.hh b/src/libstore/exec.hh similarity index 100% rename from src/libnix/exec.hh rename to src/libstore/exec.hh diff --git a/src/libnix/expr.cc b/src/libstore/expr.cc similarity index 100% rename from src/libnix/expr.cc rename to src/libstore/expr.cc diff --git a/src/libnix/expr.hh b/src/libstore/expr.hh similarity index 100% rename from src/libnix/expr.hh rename to src/libstore/expr.hh diff --git a/src/libnix/globals.cc b/src/libstore/globals.cc similarity index 100% rename from src/libnix/globals.cc rename to src/libstore/globals.cc diff --git a/src/libnix/globals.hh b/src/libstore/globals.hh similarity index 100% rename from src/libnix/globals.hh rename to src/libstore/globals.hh diff --git a/src/libnix/normalise.cc b/src/libstore/normalise.cc similarity index 100% rename from src/libnix/normalise.cc rename to src/libstore/normalise.cc diff --git a/src/libnix/normalise.hh b/src/libstore/normalise.hh similarity index 100% rename from src/libnix/normalise.hh rename to src/libstore/normalise.hh diff --git a/src/libnix/pathlocks.cc b/src/libstore/pathlocks.cc similarity index 100% rename from src/libnix/pathlocks.cc rename to src/libstore/pathlocks.cc diff --git a/src/libnix/pathlocks.hh b/src/libstore/pathlocks.hh similarity index 100% rename from src/libnix/pathlocks.hh rename to src/libstore/pathlocks.hh diff --git a/src/libnix/references.cc b/src/libstore/references.cc similarity index 100% rename from src/libnix/references.cc rename to src/libstore/references.cc diff --git a/src/libnix/references.hh b/src/libstore/references.hh similarity index 100% rename from src/libnix/references.hh rename to src/libstore/references.hh diff --git a/src/libnix/store.cc b/src/libstore/store.cc similarity index 100% rename from src/libnix/store.cc rename to src/libstore/store.cc diff --git a/src/libnix/store.hh b/src/libstore/store.hh similarity index 100% rename from src/libnix/store.hh rename to src/libstore/store.hh diff --git a/src/libnix/test-builder-1.sh b/src/libstore/test-builder-1.sh similarity index 100% rename from src/libnix/test-builder-1.sh rename to src/libstore/test-builder-1.sh diff --git a/src/libnix/test-builder-2.sh b/src/libstore/test-builder-2.sh similarity index 100% rename from src/libnix/test-builder-2.sh rename to src/libstore/test-builder-2.sh diff --git a/src/libnix/test.cc b/src/libstore/test.cc similarity index 100% rename from src/libnix/test.cc rename to src/libstore/test.cc diff --git a/src/nix-hash/Makefile.am b/src/nix-hash/Makefile.am index 7377a62e7..8609cb216 100644 --- a/src/nix-hash/Makefile.am +++ b/src/nix-hash/Makefile.am @@ -1,8 +1,8 @@ bin_PROGRAMS = nix-hash nix_hash_SOURCES = nix-hash.cc -nix_hash_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../libutil/libutil.a \ +nix_hash_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx -lATerm AM_CXXFLAGS = \ - -I.. -I../../externals/inst/include -I../libutil -I../libnix -I../libmain + -I.. -I../../externals/inst/include -I../libutil -I../libstore -I../libmain diff --git a/src/nix/Makefile.am b/src/nix/Makefile.am index c4368a37c..8a52be364 100644 --- a/src/nix/Makefile.am +++ b/src/nix/Makefile.am @@ -1,7 +1,7 @@ bin_PROGRAMS = nix nix_SOURCES = nix.cc dotgraph.cc -nix_LDADD = ../libmain/libmain.a ../libnix/libnix.a ../libutil/libutil.a \ +nix_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx -lATerm nix.o: nix-help.txt.hh @@ -12,7 +12,7 @@ nix.o: nix-help.txt.hh echo '"' >> $@ AM_CXXFLAGS = \ - -I.. -I../../externals/inst/include -I../libutil -I../libnix -I../libmain + -I.. -I../../externals/inst/include -I../libutil -I../libstore -I../libmain install-data-local: $(INSTALL) -d $(localstatedir)/nix From ce92d1bf1434562f5b80320c503768c4d06f1f8d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Nov 2003 11:22:29 +0000 Subject: [PATCH 0320/6440] * "Nix expression" -> "store expression". * More refactoring. --- src/fix-ng/Makefile.am | 2 +- src/fix-ng/eval.cc | 1 - src/fix-ng/eval.hh | 5 ++- src/fix-ng/fix.cc | 1 - src/fix-ng/{fix-expr.cc => fixexpr.cc} | 4 +- src/fix-ng/{fix-expr.hh => fixexpr.hh} | 0 src/fix-ng/parser.cc | 4 +- src/fix-ng/parser.hh | 2 +- src/fix-ng/primops.cc | 28 ++++++------- src/libstore/Makefile.am | 2 +- src/libstore/normalise.cc | 54 +++++++++++++------------- src/libstore/normalise.hh | 32 ++++++++------- src/libstore/{expr.cc => storeexpr.cc} | 31 +++++---------- src/libstore/{expr.hh => storeexpr.hh} | 24 +++++------- src/libutil/aterm.cc | 13 +++++++ src/libutil/aterm.hh | 5 +++ src/nix/dotgraph.cc | 8 ++-- src/nix/dotgraph.hh | 2 +- src/nix/nix-help.txt | 4 +- src/nix/nix.cc | 18 ++++----- 20 files changed, 121 insertions(+), 119 deletions(-) rename src/fix-ng/{fix-expr.cc => fixexpr.cc} (99%) rename src/fix-ng/{fix-expr.hh => fixexpr.hh} (100%) rename src/libstore/{expr.cc => storeexpr.cc} (87%) rename src/libstore/{expr.hh => storeexpr.hh} (59%) diff --git a/src/fix-ng/Makefile.am b/src/fix-ng/Makefile.am index df3d17308..d30a5b2ea 100644 --- a/src/fix-ng/Makefile.am +++ b/src/fix-ng/Makefile.am @@ -1,6 +1,6 @@ bin_PROGRAMS = fix-ng -fix_ng_SOURCES = fix-expr.cc parser.cc eval.cc primops.cc fix.cc +fix_ng_SOURCES = fixexpr.cc parser.cc eval.cc primops.cc fix.cc fix_ng_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm diff --git a/src/fix-ng/eval.cc b/src/fix-ng/eval.cc index 57cca4c4a..b110c3a4a 100644 --- a/src/fix-ng/eval.cc +++ b/src/fix-ng/eval.cc @@ -1,5 +1,4 @@ #include "eval.hh" -#include "expr.hh" #include "parser.hh" #include "primops.hh" diff --git a/src/fix-ng/eval.hh b/src/fix-ng/eval.hh index 9be3ae2da..061c840a7 100644 --- a/src/fix-ng/eval.hh +++ b/src/fix-ng/eval.hh @@ -3,8 +3,9 @@ #include -#include "fix-expr.hh" -#include "expr.hh" +#include "aterm.hh" +#include "hash.hh" +#include "fixexpr.hh" typedef map DrvPaths; diff --git a/src/fix-ng/fix.cc b/src/fix-ng/fix.cc index 3b7dae38e..e407aaf44 100644 --- a/src/fix-ng/fix.cc +++ b/src/fix-ng/fix.cc @@ -4,7 +4,6 @@ #include "globals.hh" #include "normalise.hh" #include "shared.hh" -#include "expr.hh" #include "eval.hh" diff --git a/src/fix-ng/fix-expr.cc b/src/fix-ng/fixexpr.cc similarity index 99% rename from src/fix-ng/fix-expr.cc rename to src/fix-ng/fixexpr.cc index e9c5a3ba6..721fa8afa 100644 --- a/src/fix-ng/fix-expr.cc +++ b/src/fix-ng/fixexpr.cc @@ -1,5 +1,5 @@ -#include "fix-expr.hh" -#include "expr.hh" +#include "fixexpr.hh" +#include "storeexpr.hh" ATermMap::ATermMap(unsigned int initialSize, unsigned int maxLoadPct) diff --git a/src/fix-ng/fix-expr.hh b/src/fix-ng/fixexpr.hh similarity index 100% rename from src/fix-ng/fix-expr.hh rename to src/fix-ng/fixexpr.hh diff --git a/src/fix-ng/parser.cc b/src/fix-ng/parser.cc index 710ea6a86..eaa41b396 100644 --- a/src/fix-ng/parser.cc +++ b/src/fix-ng/parser.cc @@ -10,10 +10,10 @@ extern "C" { #include } +#include "aterm.hh" #include "parser.hh" #include "shared.hh" -#include "fix-expr.hh" -#include "expr.hh" +#include "fixexpr.hh" #include "parse-table.h" diff --git a/src/fix-ng/parser.hh b/src/fix-ng/parser.hh index c56a339a3..e44987dd0 100644 --- a/src/fix-ng/parser.hh +++ b/src/fix-ng/parser.hh @@ -1,7 +1,7 @@ #ifndef __PARSER_H #define __PARSER_H -#include "fix-expr.hh" +#include "fixexpr.hh" Expr parseExprFromFile(Path path); diff --git a/src/fix-ng/primops.cc b/src/fix-ng/primops.cc index 86b364c30..097933115 100644 --- a/src/fix-ng/primops.cc +++ b/src/fix-ng/primops.cc @@ -13,22 +13,22 @@ Expr primImport(EvalState & state, Expr arg) } -static PathSet nixExprRootsCached(EvalState & state, const Path & nePath) +static PathSet storeExprRootsCached(EvalState & state, const Path & nePath) { DrvPaths::iterator i = state.drvPaths.find(nePath); if (i != state.drvPaths.end()) return i->second; else { - PathSet paths = nixExprRoots(nePath); + PathSet paths = storeExprRoots(nePath); state.drvPaths[nePath] = paths; return paths; } } -static Hash hashDerivation(EvalState & state, NixExpr ne) +static Hash hashDerivation(EvalState & state, StoreExpr ne) { - if (ne.type == NixExpr::neDerivation) { + if (ne.type == StoreExpr::neDerivation) { PathSet inputs2; for (PathSet::iterator i = ne.derivation.inputs.begin(); i != ne.derivation.inputs.end(); i++) @@ -40,7 +40,7 @@ static Hash hashDerivation(EvalState & state, NixExpr ne) } ne.derivation.inputs = inputs2; } - return hashTerm(unparseNixExpr(ne)); + return hashTerm(unparseStoreExpr(ne)); } @@ -50,13 +50,13 @@ static Path copyAtom(EvalState & state, const Path & srcPath) Path dstPath(addToStore(srcPath)); ClosureElem elem; - NixExpr ne; - ne.type = NixExpr::neClosure; + StoreExpr ne; + ne.type = StoreExpr::neClosure; ne.closure.roots.insert(dstPath); ne.closure.elems[dstPath] = elem; Hash drvHash = hashDerivation(state, ne); - Path drvPath = writeTerm(unparseNixExpr(ne), ""); + Path drvPath = writeTerm(unparseStoreExpr(ne), ""); state.drvHashes[drvPath] = drvHash; printMsg(lvlChatty, format("copied `%1%' -> closure `%2%'") @@ -66,9 +66,9 @@ static Path copyAtom(EvalState & state, const Path & srcPath) static string addInput(EvalState & state, - Path & nePath, NixExpr & ne) + Path & nePath, StoreExpr & ne) { - PathSet paths = nixExprRootsCached(state, nePath); + PathSet paths = storeExprRootsCached(state, nePath); if (paths.size() != 1) abort(); Path path = *(paths.begin()); ne.derivation.inputs.insert(nePath); @@ -76,7 +76,7 @@ static string addInput(EvalState & state, } -static string processBinding(EvalState & state, Expr e, NixExpr & ne) +static string processBinding(EvalState & state, Expr e, StoreExpr & ne) { e = evalExpr(state, e); @@ -131,8 +131,8 @@ Expr primDerivation(EvalState & state, Expr args) queryAllAttrs(args, attrs); /* Build the derivation expression by processing the attributes. */ - NixExpr ne; - ne.type = NixExpr::neDerivation; + StoreExpr ne; + ne.type = StoreExpr::neDerivation; string drvName; Path outPath; @@ -198,7 +198,7 @@ Expr primDerivation(EvalState & state, Expr args) Hash drvHash = outHashGiven ? hashString((string) outHash + outPath) : hashDerivation(state, ne); - Path drvPath = writeTerm(unparseNixExpr(ne), "-d-" + drvName); + Path drvPath = writeTerm(unparseStoreExpr(ne), "-d-" + drvName); state.drvHashes[drvPath] = drvHash; printMsg(lvlChatty, format("instantiated `%1%' -> `%2%'") diff --git a/src/libstore/Makefile.am b/src/libstore/Makefile.am index 472b1b079..b365f57c6 100644 --- a/src/libstore/Makefile.am +++ b/src/libstore/Makefile.am @@ -1,7 +1,7 @@ noinst_LIBRARIES = libstore.a libstore_a_SOURCES = \ - store.cc expr.cc normalise.cc exec.cc \ + store.cc storeexpr.cc normalise.cc exec.cc \ globals.cc db.cc references.cc pathlocks.cc AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall \ diff --git a/src/libstore/normalise.cc b/src/libstore/normalise.cc index cb2bf4f5b..c531e6784 100644 --- a/src/libstore/normalise.cc +++ b/src/libstore/normalise.cc @@ -18,21 +18,21 @@ static Path useSuccessor(const Path & path) } -Path normaliseNixExpr(const Path & _nePath, PathSet pending) +Path normaliseStoreExpr(const Path & _nePath, PathSet pending) { startNest(nest, lvlTalkative, - format("normalising expression in `%1%'") % (string) _nePath); + format("normalising store expression in `%1%'") % (string) _nePath); /* Try to substitute the expression by any known successors in order to speed up the rewrite process. */ Path nePath = useSuccessor(_nePath); - /* Get the Nix expression. */ - NixExpr ne = exprFromPath(nePath, pending); + /* Get the store expression. */ + StoreExpr ne = storeExprFromPath(nePath, pending); /* If this is a normal form (i.e., a closure) we are done. */ - if (ne.type == NixExpr::neClosure) return nePath; - if (ne.type != NixExpr::neDerivation) abort(); + if (ne.type == StoreExpr::neClosure) return nePath; + if (ne.type != StoreExpr::neDerivation) abort(); /* Otherwise, it's a derivation expression, and we have to build it to @@ -51,8 +51,8 @@ Path normaliseNixExpr(const Path & _nePath, PathSet pending) Environment env; /* The result. */ - NixExpr nf; - nf.type = NixExpr::neClosure; + StoreExpr nf; + nf.type = StoreExpr::neClosure; /* The outputs are referenceable paths. */ @@ -78,10 +78,10 @@ Path normaliseNixExpr(const Path & _nePath, PathSet pending) { Path nePath2 = useSuccessor(nePath); if (nePath != nePath2) { - NixExpr ne = exprFromPath(nePath2, pending); + StoreExpr ne = storeExprFromPath(nePath2, pending); debug(format("skipping build of expression `%1%', someone beat us to it") % (string) nePath); - if (ne.type != NixExpr::neClosure) abort(); + if (ne.type != StoreExpr::neClosure) abort(); return nePath2; } } @@ -95,12 +95,12 @@ Path normaliseNixExpr(const Path & _nePath, PathSet pending) for (PathSet::iterator i = ne.derivation.inputs.begin(); i != ne.derivation.inputs.end(); i++) { - Path nfPath = normaliseNixExpr(*i, pending); + Path nfPath = normaliseStoreExpr(*i, pending); realiseClosure(nfPath, pending); /* !!! nfPath should be a root of the garbage collector while we are building */ - NixExpr ne = exprFromPath(nfPath, pending); - if (ne.type != NixExpr::neClosure) abort(); + StoreExpr ne = storeExprFromPath(nfPath, pending); + if (ne.type != StoreExpr::neClosure) abort(); for (ClosureElems::iterator j = ne.closure.elems.begin(); j != ne.closure.elems.end(); j++) { @@ -238,7 +238,7 @@ Path normaliseNixExpr(const Path & _nePath, PathSet pending) /* Write the normal form. This does not have to occur in the transaction below because writing terms is idem-potent. */ - ATerm nfTerm = unparseNixExpr(nf); + ATerm nfTerm = unparseStoreExpr(nf); printMsg(lvlVomit, format("normal form: %1%") % atPrint(nfTerm)); Path nfPath = writeTerm(nfTerm, "-s"); @@ -264,8 +264,8 @@ void realiseClosure(const Path & nePath, PathSet pending) { startNest(nest, lvlDebug, format("realising closure `%1%'") % nePath); - NixExpr ne = exprFromPath(nePath, pending); - if (ne.type != NixExpr::neClosure) + StoreExpr ne = storeExprFromPath(nePath, pending); + if (ne.type != StoreExpr::neClosure) throw Error(format("expected closure in `%1%'") % nePath); for (ClosureElems::const_iterator i = ne.closure.elems.begin(); @@ -286,7 +286,7 @@ void ensurePath(const Path & path, PathSet pending) i != subPaths.end(); i++) { try { - normaliseNixExpr(*i, pending); + normaliseStoreExpr(*i, pending); if (isValidPath(path)) return; throw Error(format("substitute failed to produce expected output path")); } catch (Error & e) { @@ -301,24 +301,24 @@ void ensurePath(const Path & path, PathSet pending) } -NixExpr exprFromPath(const Path & path, PathSet pending) +StoreExpr storeExprFromPath(const Path & path, PathSet pending) { ensurePath(path, pending); ATerm t = ATreadFromNamedFile(path.c_str()); if (!t) throw Error(format("cannot read aterm from `%1%'") % path); - return parseNixExpr(t); + return parseStoreExpr(t); } -PathSet nixExprRoots(const Path & nePath) +PathSet storeExprRoots(const Path & nePath) { PathSet paths; - NixExpr ne = exprFromPath(nePath); + StoreExpr ne = storeExprFromPath(nePath); - if (ne.type == NixExpr::neClosure) + if (ne.type == StoreExpr::neClosure) paths.insert(ne.closure.roots.begin(), ne.closure.roots.end()); - else if (ne.type == NixExpr::neDerivation) + else if (ne.type == StoreExpr::neDerivation) paths.insert(ne.derivation.outputs.begin(), ne.derivation.outputs.end()); else abort(); @@ -334,14 +334,14 @@ static void requisitesWorker(const Path & nePath, if (doneSet.find(nePath) != doneSet.end()) return; doneSet.insert(nePath); - NixExpr ne = exprFromPath(nePath); + StoreExpr ne = storeExprFromPath(nePath); - if (ne.type == NixExpr::neClosure) + if (ne.type == StoreExpr::neClosure) for (ClosureElems::iterator i = ne.closure.elems.begin(); i != ne.closure.elems.end(); i++) paths.insert(i->first); - else if (ne.type == NixExpr::neDerivation) + else if (ne.type == StoreExpr::neDerivation) for (PathSet::iterator i = ne.derivation.inputs.begin(); i != ne.derivation.inputs.end(); i++) requisitesWorker(*i, @@ -358,7 +358,7 @@ static void requisitesWorker(const Path & nePath, } -PathSet nixExprRequisites(const Path & nePath, +PathSet storeExprRequisites(const Path & nePath, bool includeExprs, bool includeSuccessors) { PathSet paths; diff --git a/src/libstore/normalise.hh b/src/libstore/normalise.hh index bbe846404..1003858cb 100644 --- a/src/libstore/normalise.hh +++ b/src/libstore/normalise.hh @@ -1,16 +1,16 @@ #ifndef __NORMALISE_H #define __NORMALISE_H -#include "expr.hh" +#include "storeexpr.hh" -/* Normalise a Nix expression. That is, if the expression is a +/* Normalise a store expression. That is, if the expression is a derivation, a path containing an equivalent closure expression is returned. This requires that the derivation is performed, unless a successor is known. */ -Path normaliseNixExpr(const Path & nePath, PathSet pending = PathSet()); +Path normaliseStoreExpr(const Path & nePath, PathSet pending = PathSet()); -/* Realise a closure expression in the file system. +/* Realise a closure store expression in the file system. The pending paths are those that are already being realised. This prevents infinite recursion for paths realised through a substitute @@ -22,23 +22,25 @@ void realiseClosure(const Path & nePath, PathSet pending = PathSet()); realising a substitute. */ void ensurePath(const Path & path, PathSet pending = PathSet()); -/* Read a Nix expression, after ensuring its existence through +/* Read a store expression, after ensuring its existence through ensurePath(). */ -NixExpr exprFromPath(const Path & path, PathSet pending = PathSet()); +StoreExpr storeExprFromPath(const Path & path, PathSet pending = PathSet()); -/* Get the list of root (output) paths of the given Nix expression. */ -PathSet nixExprRoots(const Path & nePath); +/* Get the list of root (output) paths of the given store + expression. */ +PathSet storeExprRoots(const Path & nePath); -/* Get the list of paths that are required to realise the given +/* Get the list of paths that are required to realise the given store expression. For a derive expression, this is the union of - requisites of the inputs; for a closure expression, it is the path of - each element in the closure. If `includeExprs' is true, include the - paths of the Nix expressions themselves. If `includeSuccessors' is - true, include the requisites of successors. */ -PathSet nixExprRequisites(const Path & nePath, + requisites of the inputs; for a closure expression, it is the path + of each element in the closure. If `includeExprs' is true, include + the paths of the store expressions themselves. If + `includeSuccessors' is true, include the requisites of + successors. */ +PathSet storeExprRequisites(const Path & nePath, bool includeExprs, bool includeSuccessors); -/* Return the list of the paths of all known Nix expressions whose +/* Return the list of the paths of all known store expressions whose output paths are completely contained in the set `outputs'. */ PathSet findGenerators(const PathSet & outputs); diff --git a/src/libstore/expr.cc b/src/libstore/storeexpr.cc similarity index 87% rename from src/libstore/expr.cc rename to src/libstore/storeexpr.cc index 7bb1f5306..e11cd5bfe 100644 --- a/src/libstore/expr.cc +++ b/src/libstore/storeexpr.cc @@ -1,21 +1,8 @@ -#include "expr.hh" +#include "storeexpr.hh" #include "globals.hh" #include "store.hh" -Error badTerm(const format & f, ATerm t) -{ - char * s = ATwriteToString(t); - if (!s) throw Error("cannot print term"); - if (strlen(s) > 1000) { - int len; - s = ATwriteToSharedString(t, &len); - if (!s) throw Error("cannot print term"); - } - return Error(format("%1%, in `%2%'") % f.str() % (string) s); -} - - Hash hashTerm(ATerm t) { return hashString(atPrint(t)); @@ -138,14 +125,14 @@ static bool parseDerivation(ATerm t, Derivation & derivation) } -NixExpr parseNixExpr(ATerm t) +StoreExpr parseStoreExpr(ATerm t) { - NixExpr ne; + StoreExpr ne; if (parseClosure(t, ne.closure)) - ne.type = NixExpr::neClosure; + ne.type = StoreExpr::neClosure; else if (parseDerivation(t, ne.derivation)) - ne.type = NixExpr::neDerivation; - else throw badTerm("not a Nix expression", t); + ne.type = StoreExpr::neDerivation; + else throw badTerm("not a store expression", t); return ne; } @@ -200,11 +187,11 @@ static ATerm unparseDerivation(const Derivation & derivation) } -ATerm unparseNixExpr(const NixExpr & ne) +ATerm unparseStoreExpr(const StoreExpr & ne) { - if (ne.type == NixExpr::neClosure) + if (ne.type == StoreExpr::neClosure) return unparseClosure(ne.closure); - else if (ne.type == NixExpr::neDerivation) + else if (ne.type == StoreExpr::neDerivation) return unparseDerivation(ne.derivation); else abort(); } diff --git a/src/libstore/expr.hh b/src/libstore/storeexpr.hh similarity index 59% rename from src/libstore/expr.hh rename to src/libstore/storeexpr.hh index f5abf9af0..07676c3cc 100644 --- a/src/libstore/expr.hh +++ b/src/libstore/storeexpr.hh @@ -1,11 +1,11 @@ -#ifndef __FSTATE_H -#define __FSTATE_H +#ifndef __STOREEXPR_H +#define __STOREEXPR_H #include "aterm.hh" #include "store.hh" -/* Abstract syntax of Nix expressions. */ +/* Abstract syntax of store expressions. */ struct ClosureElem { @@ -25,14 +25,14 @@ typedef map StringPairs; struct Derivation { PathSet outputs; - PathSet inputs; /* Nix expressions, not actual inputs */ + PathSet inputs; /* Store expressions, not actual inputs */ string platform; Path builder; Strings args; StringPairs env; }; -struct NixExpr +struct StoreExpr { enum { neClosure, neDerivation } type; Closure closure; @@ -40,21 +40,17 @@ struct NixExpr }; -/* Throw an exception with an error message containing the given - aterm. */ -Error badTerm(const format & f, ATerm t); - /* Hash an aterm. */ Hash hashTerm(ATerm t); /* Write an aterm to the Nix store directory, and return its path. */ Path writeTerm(ATerm t, const string & suffix); -/* Parse a Nix expression. */ -NixExpr parseNixExpr(ATerm t); +/* Parse a store expression. */ +StoreExpr parseStoreExpr(ATerm t); -/* Parse a Nix expression. */ -ATerm unparseNixExpr(const NixExpr & ne); +/* Parse a store expression. */ +ATerm unparseStoreExpr(const StoreExpr & ne); -#endif /* !__FSTATE_H */ +#endif /* !__STOREEXPR_H */ diff --git a/src/libutil/aterm.cc b/src/libutil/aterm.cc index de7c35952..dc6abf9e7 100644 --- a/src/libutil/aterm.cc +++ b/src/libutil/aterm.cc @@ -91,3 +91,16 @@ ATMatcher & operator >> (ATMatcher & pos, ATermList & out) out = (ATermList) t; return pos; } + + +Error badTerm(const format & f, ATerm t) +{ + char * s = ATwriteToString(t); + if (!s) throw Error("cannot print term"); + if (strlen(s) > 1000) { + int len; + s = ATwriteToSharedString(t, &len); + if (!s) throw Error("cannot print term"); + } + return Error(format("%1%, in `%2%'") % f.str() % (string) s); +} diff --git a/src/libutil/aterm.hh b/src/libutil/aterm.hh index 16d8d6bb6..d38d8e3f4 100644 --- a/src/libutil/aterm.hh +++ b/src/libutil/aterm.hh @@ -74,4 +74,9 @@ ATMatcher & operator >> (ATMatcher & pos, const string & s); ATMatcher & operator >> (ATMatcher & pos, ATermList & out); +/* Throw an exception with an error message containing the given + aterm. */ +Error badTerm(const format & f, ATerm t); + + #endif /* !__ATERM_H */ diff --git a/src/nix/dotgraph.cc b/src/nix/dotgraph.cc index 36daf7f99..c670bf19e 100644 --- a/src/nix/dotgraph.cc +++ b/src/nix/dotgraph.cc @@ -52,7 +52,7 @@ string pathLabel(const Path & nePath, const string & elemPath) } -void printClosure(const Path & nePath, const NixExpr & fs) +void printClosure(const Path & nePath, const StoreExpr & fs) { PathSet workList(fs.closure.roots); PathSet doneSet; @@ -100,11 +100,11 @@ void printDotGraph(const PathSet & roots) if (doneSet.find(nePath) == doneSet.end()) { doneSet.insert(nePath); - NixExpr ne = exprFromPath(nePath); + StoreExpr ne = storeExprFromPath(nePath); string label, colour; - if (ne.type == NixExpr::neDerivation) { + if (ne.type == StoreExpr::neDerivation) { for (PathSet::iterator i = ne.derivation.inputs.begin(); i != ne.derivation.inputs.end(); i++) { @@ -119,7 +119,7 @@ void printDotGraph(const PathSet & roots) if (i->first == "name") label = i->second; } - else if (ne.type == NixExpr::neClosure) { + else if (ne.type == StoreExpr::neClosure) { label = ""; colour = "#00ffff"; printClosure(nePath, ne); diff --git a/src/nix/dotgraph.hh b/src/nix/dotgraph.hh index a5c5248f1..ef389b30d 100644 --- a/src/nix/dotgraph.hh +++ b/src/nix/dotgraph.hh @@ -1,7 +1,7 @@ #ifndef __DOTGRAPH_H #define __DOTGRAPH_H -#include "expr.hh" +#include "storeexpr.hh" void printDotGraph(const PathSet & roots); diff --git a/src/nix/nix-help.txt b/src/nix/nix-help.txt index bf2afd061..a2d9958ff 100644 --- a/src/nix/nix-help.txt +++ b/src/nix/nix-help.txt @@ -2,7 +2,7 @@ nix [OPTIONS...] [ARGUMENTS...] Operations: - --install / -i: realise a Nix expression + --realise / -r: realise a Nix expression --delete / -d: delete paths from the Nix store --add / -A: copy a path to the Nix store --query / -q: query information @@ -22,7 +22,7 @@ Operations: Query flags: --list / -l: query the output paths (roots) of a Nix expression (default) - --requisites / -r: print all paths necessary to realise expression + --requisites / -R: print all paths necessary to realise expression --predecessors: print predecessors of a Nix expression --graph: print a dot graph rooted at given ids diff --git a/src/nix/nix.cc b/src/nix/nix.cc index 187568999..d1766de39 100644 --- a/src/nix/nix.cc +++ b/src/nix/nix.cc @@ -27,15 +27,15 @@ static Path checkPath(const Path & arg) } -/* Realise (or install) paths from the given Nix expressions. */ -static void opInstall(Strings opFlags, Strings opArgs) +/* Realise paths from the given store expressions. */ +static void opRealise(Strings opFlags, Strings opArgs) { if (!opFlags.empty()) throw UsageError("unknown flag"); for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) { - Path nfPath = normaliseNixExpr(checkPath(*i)); + Path nfPath = normaliseStoreExpr(checkPath(*i)); realiseClosure(nfPath); cout << format("%1%\n") % (string) nfPath; } @@ -66,7 +66,7 @@ static void opAdd(Strings opFlags, Strings opArgs) Path maybeNormalise(const Path & ne, bool normalise) { - return normalise ? normaliseNixExpr(ne) : ne; + return normalise ? normaliseStoreExpr(ne) : ne; } @@ -82,7 +82,7 @@ static void opQuery(Strings opFlags, Strings opArgs) for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); i++) if (*i == "--list" || *i == "-l") query = qList; - else if (*i == "--requisites" || *i == "-r") query = qRequisites; + else if (*i == "--requisites" || *i == "-R") query = qRequisites; else if (*i == "--predecessors") query = qPredecessors; else if (*i == "--graph") query = qGraph; else if (*i == "--normalise" || *i == "-n") normalise = true; @@ -96,7 +96,7 @@ static void opQuery(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) { - StringSet paths = nixExprRoots( + StringSet paths = storeExprRoots( maybeNormalise(checkPath(*i), normalise)); for (StringSet::iterator j = paths.begin(); j != paths.end(); j++) @@ -110,7 +110,7 @@ static void opQuery(Strings opFlags, Strings opArgs) for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) { - StringSet paths2 = nixExprRequisites( + StringSet paths2 = storeExprRequisites( maybeNormalise(checkPath(*i), normalise), includeExprs, includeSuccessors); paths.insert(paths2.begin(), paths2.end()); @@ -258,8 +258,8 @@ void run(Strings args) Operation oldOp = op; - if (arg == "--install" || arg == "-i") - op = opInstall; + if (arg == "--realise" || arg == "-r") + op = opRealise; else if (arg == "--delete" || arg == "-d") op = opDelete; else if (arg == "--add" || arg == "-A") From b1117ef29d35822647bda32f8cd3887f4f6eaede Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Nov 2003 11:38:25 +0000 Subject: [PATCH 0321/6440] * nix -> nix-store, fix -> nix-instantiate. --- configure.ac | 4 +- src/Makefile.am | 2 +- src/{fix-ng => nix-instantiate}/Makefile.am | 6 +- src/{fix-ng => nix-instantiate}/bin2c.c | 0 src/{fix-ng => nix-instantiate}/eval.cc | 0 src/{fix-ng => nix-instantiate}/eval.hh | 0 src/nix-instantiate/fix-expr.cc | 215 ++++++++++++++++++ .../fix-expr.hh} | 0 src/{fix-ng => nix-instantiate}/fix.cc | 0 src/{fix-ng => nix-instantiate}/fix.sdf | 0 src/{fix-ng => nix-instantiate}/fixexpr.cc | 0 src/nix-instantiate/fixexpr.hh | 75 ++++++ src/{fix-ng => nix-instantiate}/parser.cc | 0 src/{fix-ng => nix-instantiate}/parser.hh | 0 src/{fix-ng => nix-instantiate}/primops.cc | 0 src/{fix-ng => nix-instantiate}/primops.hh | 0 src/{nix => nix-store}/Makefile.am | 8 +- src/{nix => nix-store}/dotgraph.cc | 0 src/{nix => nix-store}/dotgraph.hh | 0 src/{nix => nix-store}/nix-help.txt | 4 +- src/{nix => nix-store}/nix.cc | 0 21 files changed, 303 insertions(+), 11 deletions(-) rename src/{fix-ng => nix-instantiate}/Makefile.am (71%) rename src/{fix-ng => nix-instantiate}/bin2c.c (100%) rename src/{fix-ng => nix-instantiate}/eval.cc (100%) rename src/{fix-ng => nix-instantiate}/eval.hh (100%) create mode 100644 src/nix-instantiate/fix-expr.cc rename src/{fix-ng/fixexpr.hh => nix-instantiate/fix-expr.hh} (100%) rename src/{fix-ng => nix-instantiate}/fix.cc (100%) rename src/{fix-ng => nix-instantiate}/fix.sdf (100%) rename src/{fix-ng => nix-instantiate}/fixexpr.cc (100%) create mode 100644 src/nix-instantiate/fixexpr.hh rename src/{fix-ng => nix-instantiate}/parser.cc (100%) rename src/{fix-ng => nix-instantiate}/parser.hh (100%) rename src/{fix-ng => nix-instantiate}/primops.cc (100%) rename src/{fix-ng => nix-instantiate}/primops.hh (100%) rename src/{nix => nix-store}/Makefile.am (76%) rename src/{nix => nix-store}/dotgraph.cc (100%) rename src/{nix => nix-store}/dotgraph.hh (100%) rename src/{nix => nix-store}/nix-help.txt (90%) rename src/{nix => nix-store}/nix.cc (100%) diff --git a/configure.ac b/configure.ac index 9d4b41b6b..a0ab10dc4 100644 --- a/configure.ac +++ b/configure.ac @@ -31,9 +31,9 @@ AC_CONFIG_FILES([Makefile src/libutil/Makefile src/libstore/Makefile src/libmain/Makefile - src/nix/Makefile + src/nix-store/Makefile src/nix-hash/Makefile - src/fix-ng/Makefile + src/nix-instantiate/Makefile scripts/Makefile corepkgs/Makefile corepkgs/fetchurl/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index ed989fed2..e35b82a88 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1 +1 @@ -SUBDIRS = boost libutil libstore libmain nix nix-hash fix-ng +SUBDIRS = boost libutil libstore libmain nix-store nix-hash nix-instantiate diff --git a/src/fix-ng/Makefile.am b/src/nix-instantiate/Makefile.am similarity index 71% rename from src/fix-ng/Makefile.am rename to src/nix-instantiate/Makefile.am index d30a5b2ea..cdaec1390 100644 --- a/src/fix-ng/Makefile.am +++ b/src/nix-instantiate/Makefile.am @@ -1,7 +1,7 @@ -bin_PROGRAMS = fix-ng +bin_PROGRAMS = nix-instantiate -fix_ng_SOURCES = fixexpr.cc parser.cc eval.cc primops.cc fix.cc -fix_ng_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ +nix_instantiate_SOURCES = fixexpr.cc parser.cc eval.cc primops.cc fix.cc +nix_instantiate_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm diff --git a/src/fix-ng/bin2c.c b/src/nix-instantiate/bin2c.c similarity index 100% rename from src/fix-ng/bin2c.c rename to src/nix-instantiate/bin2c.c diff --git a/src/fix-ng/eval.cc b/src/nix-instantiate/eval.cc similarity index 100% rename from src/fix-ng/eval.cc rename to src/nix-instantiate/eval.cc diff --git a/src/fix-ng/eval.hh b/src/nix-instantiate/eval.hh similarity index 100% rename from src/fix-ng/eval.hh rename to src/nix-instantiate/eval.hh diff --git a/src/nix-instantiate/fix-expr.cc b/src/nix-instantiate/fix-expr.cc new file mode 100644 index 000000000..e9c5a3ba6 --- /dev/null +++ b/src/nix-instantiate/fix-expr.cc @@ -0,0 +1,215 @@ +#include "fix-expr.hh" +#include "expr.hh" + + +ATermMap::ATermMap(unsigned int initialSize, unsigned int maxLoadPct) +{ + table = ATtableCreate(initialSize, maxLoadPct); + if (!table) throw Error("cannot create ATerm table"); +} + + +ATermMap::ATermMap(const ATermMap & map) + : table(0) +{ + ATermList keys = map.keys(); + + /* !!! adjust allocation for load pct */ + table = ATtableCreate(ATgetLength(keys), map.maxLoadPct); + if (!table) throw Error("cannot create ATerm table"); + + for (ATermIterator i(keys); i; ++i) + set(*i, map.get(*i)); +} + + +ATermMap::~ATermMap() +{ + if (table) ATtableDestroy(table); +} + + +void ATermMap::set(ATerm key, ATerm value) +{ + return ATtablePut(table, key, value); +} + + +void ATermMap::set(const string & key, ATerm value) +{ + set(string2ATerm(key), value); +} + + +ATerm ATermMap::get(ATerm key) const +{ + return ATtableGet(table, key); +} + + +ATerm ATermMap::get(const string & key) const +{ + return get(string2ATerm(key)); +} + + +void ATermMap::remove(ATerm key) +{ + ATtableRemove(table, key); +} + + +void ATermMap::remove(const string & key) +{ + remove(string2ATerm(key)); +} + + +ATermList ATermMap::keys() const +{ + ATermList keys = ATtableKeys(table); + if (!keys) throw Error("cannot query aterm map keys"); + return keys; +} + + +ATerm string2ATerm(const string & s) +{ + return (ATerm) ATmakeAppl0(ATmakeAFun((char *) s.c_str(), 0, ATtrue)); +} + + +string aterm2String(ATerm t) +{ + return ATgetName(ATgetAFun(t)); +} + + +ATerm bottomupRewrite(TermFun & f, ATerm e) +{ + if (ATgetType(e) == AT_APPL) { + AFun fun = ATgetAFun(e); + int arity = ATgetArity(fun); + ATermList args = ATempty; + + for (int i = arity - 1; i >= 0; i--) + args = ATinsert(args, bottomupRewrite(f, ATgetArgument(e, i))); + + e = (ATerm) ATmakeApplList(fun, args); + } + + else if (ATgetType(e) == AT_LIST) { + ATermList in = (ATermList) e; + ATermList out = ATempty; + + for (ATermIterator i(in); i; ++i) + out = ATinsert(out, bottomupRewrite(f, *i)); + + e = (ATerm) ATreverse(out); + } + + return f(e); +} + + +void queryAllAttrs(Expr e, ATermMap & attrs) +{ + ATMatcher m; + ATermList bnds; + if (!(atMatch(m, e) >> "Attrs" >> bnds)) + throw badTerm("expected attribute set", e); + + for (ATermIterator i(bnds); i; ++i) { + string s; + Expr e; + if (!(atMatch(m, *i) >> "Bind" >> s >> e)) + abort(); /* can't happen */ + attrs.set(s, e); + } +} + + +Expr queryAttr(Expr e, const string & name) +{ + ATermMap attrs; + queryAllAttrs(e, attrs); + return attrs.get(name); +} + + +Expr makeAttrs(const ATermMap & attrs) +{ + ATermList bnds = ATempty; + for (ATermIterator i(attrs.keys()); i; ++i) + bnds = ATinsert(bnds, + ATmake("Bind(, )", *i, attrs.get(*i))); + return ATmake("Attrs()", ATreverse(bnds)); +} + + +Expr substitute(const ATermMap & subs, Expr e) +{ + ATMatcher m; + string s; + + if (atMatch(m, e) >> "Var" >> s) { + Expr sub = subs.get(s); + return sub ? sub : e; + } + + /* In case of a function, filter out all variables bound by this + function. */ + ATermList formals; + ATerm body; + if (atMatch(m, e) >> "Function" >> formals >> body) { + ATermMap subs2(subs); + for (ATermIterator i(formals); i; ++i) { + Expr def; + if (!(atMatch(m, *i) >> "NoDefFormal" >> s) && + !(atMatch(m, *i) >> "DefFormal" >> s >> def)) + abort(); + subs2.remove(s); + } + return ATmake("Function(, )", formals, + substitute(subs2, body)); + } + + /* Idem for a mutually recursive attribute set. */ + ATermList bindings; + if (atMatch(m, e) >> "Rec" >> bindings) { + ATermMap subs2(subs); + for (ATermIterator i(bindings); i; ++i) { + Expr e; + if (!(atMatch(m, *i) >> "Bind" >> s >> e)) + abort(); /* can't happen */ + subs2.remove(s); + } + return ATmake("Rec()", substitute(subs2, (ATerm) bindings)); + } + + if (ATgetType(e) == AT_APPL) { + AFun fun = ATgetAFun(e); + int arity = ATgetArity(fun); + ATermList args = ATempty; + + for (int i = arity - 1; i >= 0; i--) + args = ATinsert(args, substitute(subs, ATgetArgument(e, i))); + + return (ATerm) ATmakeApplList(fun, args); + } + + if (ATgetType(e) == AT_LIST) { + ATermList out = ATempty; + for (ATermIterator i((ATermList) e); i; ++i) + out = ATinsert(out, substitute(subs, *i)); + return (ATerm) ATreverse(out); + } + + return e; +} + + +Expr makeBool(bool b) +{ + return b ? ATmake("Bool(True)") : ATmake("Bool(False)"); +} diff --git a/src/fix-ng/fixexpr.hh b/src/nix-instantiate/fix-expr.hh similarity index 100% rename from src/fix-ng/fixexpr.hh rename to src/nix-instantiate/fix-expr.hh diff --git a/src/fix-ng/fix.cc b/src/nix-instantiate/fix.cc similarity index 100% rename from src/fix-ng/fix.cc rename to src/nix-instantiate/fix.cc diff --git a/src/fix-ng/fix.sdf b/src/nix-instantiate/fix.sdf similarity index 100% rename from src/fix-ng/fix.sdf rename to src/nix-instantiate/fix.sdf diff --git a/src/fix-ng/fixexpr.cc b/src/nix-instantiate/fixexpr.cc similarity index 100% rename from src/fix-ng/fixexpr.cc rename to src/nix-instantiate/fixexpr.cc diff --git a/src/nix-instantiate/fixexpr.hh b/src/nix-instantiate/fixexpr.hh new file mode 100644 index 000000000..6c1e51d9c --- /dev/null +++ b/src/nix-instantiate/fixexpr.hh @@ -0,0 +1,75 @@ +#ifndef __FIXEXPR_H +#define __FIXEXPR_H + +#include + +#include + +#include "util.hh" + + +/* Fix 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; + + +/* Mappings from ATerms to ATerms. This is just a wrapper around + ATerm tables. */ +class ATermMap +{ +private: + unsigned int maxLoadPct; + ATermTable table; + +public: + ATermMap(unsigned int initialSize = 16, unsigned int maxLoadPct = 75); + ATermMap(const ATermMap & map); + ~ATermMap(); + + void set(ATerm key, ATerm value); + void set(const string & key, ATerm value); + + ATerm get(ATerm key) const; + ATerm get(const string & key) const; + + void remove(ATerm key); + void remove(const string & key); + + ATermList keys() const; +}; + + +/* Convert a string to an ATerm (i.e., a quoted nullary function + applicaton). */ +ATerm string2ATerm(const string & s); +string aterm2String(ATerm t); + +/* 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 +{ + virtual ATerm operator () (ATerm e) = 0; +}; +ATerm bottomupRewrite(TermFun & f, ATerm e); + +/* Query all attributes in an attribute set expression. The + expression must be in normal form. */ +void queryAllAttrs(Expr e, ATermMap & attrs); + +/* Query a specific attribute from an attribute set expression. The + expression must be in normal form. */ +Expr queryAttr(Expr e, const string & name); + +/* 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 ATermMap & subs, Expr e); + +/* Create an expression representing a boolean. */ +Expr makeBool(bool b); + + +#endif /* !__FIXEXPR_H */ diff --git a/src/fix-ng/parser.cc b/src/nix-instantiate/parser.cc similarity index 100% rename from src/fix-ng/parser.cc rename to src/nix-instantiate/parser.cc diff --git a/src/fix-ng/parser.hh b/src/nix-instantiate/parser.hh similarity index 100% rename from src/fix-ng/parser.hh rename to src/nix-instantiate/parser.hh diff --git a/src/fix-ng/primops.cc b/src/nix-instantiate/primops.cc similarity index 100% rename from src/fix-ng/primops.cc rename to src/nix-instantiate/primops.cc diff --git a/src/fix-ng/primops.hh b/src/nix-instantiate/primops.hh similarity index 100% rename from src/fix-ng/primops.hh rename to src/nix-instantiate/primops.hh diff --git a/src/nix/Makefile.am b/src/nix-store/Makefile.am similarity index 76% rename from src/nix/Makefile.am rename to src/nix-store/Makefile.am index 8a52be364..a39d1e2ad 100644 --- a/src/nix/Makefile.am +++ b/src/nix-store/Makefile.am @@ -1,7 +1,7 @@ -bin_PROGRAMS = nix +bin_PROGRAMS = nix-store -nix_SOURCES = nix.cc dotgraph.cc -nix_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ +nix_store_SOURCES = nix.cc dotgraph.cc +nix_store_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx -lATerm nix.o: nix-help.txt.hh @@ -22,6 +22,6 @@ install-data-local: ln -sf $(localstatedir)/nix/links/current $(prefix)/current $(INSTALL) -d $(localstatedir)/log/nix $(INSTALL) -d $(prefix)/store - $(bindir)/nix --init + $(bindir)/nix-store --init EXTRA_DIST = *.hh diff --git a/src/nix/dotgraph.cc b/src/nix-store/dotgraph.cc similarity index 100% rename from src/nix/dotgraph.cc rename to src/nix-store/dotgraph.cc diff --git a/src/nix/dotgraph.hh b/src/nix-store/dotgraph.hh similarity index 100% rename from src/nix/dotgraph.hh rename to src/nix-store/dotgraph.hh diff --git a/src/nix/nix-help.txt b/src/nix-store/nix-help.txt similarity index 90% rename from src/nix/nix-help.txt rename to src/nix-store/nix-help.txt index a2d9958ff..d7f977025 100644 --- a/src/nix/nix-help.txt +++ b/src/nix-store/nix-help.txt @@ -1,4 +1,6 @@ -nix [OPTIONS...] [ARGUMENTS...] +nix-store [OPTIONS...] [ARGUMENTS...] + +`nix-store' is a tool to manipulate the Nix store. Operations: diff --git a/src/nix/nix.cc b/src/nix-store/nix.cc similarity index 100% rename from src/nix/nix.cc rename to src/nix-store/nix.cc From dfc9c64ead7f24d51ed1a232e4b3ecafa8384f2e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Nov 2003 12:06:07 +0000 Subject: [PATCH 0322/6440] * "Fix expression" -> "Nix expression". * More refactoring. --- src/Makefile.am | 2 +- src/bin2c/Makefile.am | 3 +++ src/{nix-instantiate => bin2c}/bin2c.c | 0 src/nix-instantiate/Makefile.am | 12 ++++-------- src/nix-instantiate/eval.hh | 2 +- src/nix-instantiate/{fix.cc => main.cc} | 4 ++-- src/nix-instantiate/{fix.sdf => nix.sdf} | 0 src/nix-instantiate/{fixexpr.cc => nixexpr.cc} | 2 +- src/nix-instantiate/{fixexpr.hh => nixexpr.hh} | 8 ++++---- src/nix-instantiate/parser.cc | 9 ++++----- src/nix-instantiate/parser.hh | 2 +- src/nix-store/Makefile.am | 8 +++----- src/nix-store/{nix-help.txt => help.txt} | 0 src/nix-store/{nix.cc => main.cc} | 7 +++---- 14 files changed, 27 insertions(+), 32 deletions(-) create mode 100644 src/bin2c/Makefile.am rename src/{nix-instantiate => bin2c}/bin2c.c (100%) rename src/nix-instantiate/{fix.cc => main.cc} (96%) rename src/nix-instantiate/{fix.sdf => nix.sdf} (100%) rename src/nix-instantiate/{fixexpr.cc => nixexpr.cc} (99%) rename src/nix-instantiate/{fixexpr.hh => nixexpr.hh} (93%) rename src/nix-store/{nix-help.txt => help.txt} (100%) rename src/nix-store/{nix.cc => main.cc} (98%) diff --git a/src/Makefile.am b/src/Makefile.am index e35b82a88..1f2aafcdb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1 +1 @@ -SUBDIRS = boost libutil libstore libmain nix-store nix-hash nix-instantiate +SUBDIRS = bin2c boost libutil libstore libmain nix-store nix-hash nix-instantiate diff --git a/src/bin2c/Makefile.am b/src/bin2c/Makefile.am new file mode 100644 index 000000000..bdd58808a --- /dev/null +++ b/src/bin2c/Makefile.am @@ -0,0 +1,3 @@ +noinst_PROGRAMS = bin2c + +bin2c_SOURCES = bin2c.c diff --git a/src/nix-instantiate/bin2c.c b/src/bin2c/bin2c.c similarity index 100% rename from src/nix-instantiate/bin2c.c rename to src/bin2c/bin2c.c diff --git a/src/nix-instantiate/Makefile.am b/src/nix-instantiate/Makefile.am index cdaec1390..3a09add0c 100644 --- a/src/nix-instantiate/Makefile.am +++ b/src/nix-instantiate/Makefile.am @@ -1,6 +1,6 @@ bin_PROGRAMS = nix-instantiate -nix_instantiate_SOURCES = fixexpr.cc parser.cc eval.cc primops.cc fix.cc +nix_instantiate_SOURCES = nixexpr.cc parser.cc eval.cc primops.cc main.cc nix_instantiate_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm @@ -13,14 +13,10 @@ AM_CXXFLAGS = \ parser.o: parse-table.h -parse-table.h: fix.tbl bin2c - ./bin2c fixParseTable < $< > $@ || (rm $@ && exit 1) - -noinst_PROGRAMS = bin2c - -bin2c_SOURCES = bin2c.c +parse-table.h: nix.tbl + ../bin2c/bin2c nixParseTable < $< > $@ || (rm $@ && exit 1) %.tbl: %.sdf ../../externals/inst/bin/sdf2table -i $< -o $@ -CLEANFILES = parse-table.h fix.tbl +CLEANFILES = parse-table.h nix.tbl diff --git a/src/nix-instantiate/eval.hh b/src/nix-instantiate/eval.hh index 061c840a7..0bc052676 100644 --- a/src/nix-instantiate/eval.hh +++ b/src/nix-instantiate/eval.hh @@ -5,7 +5,7 @@ #include "aterm.hh" #include "hash.hh" -#include "fixexpr.hh" +#include "nixexpr.hh" typedef map DrvPaths; diff --git a/src/nix-instantiate/fix.cc b/src/nix-instantiate/main.cc similarity index 96% rename from src/nix-instantiate/fix.cc rename to src/nix-instantiate/main.cc index e407aaf44..aa6883ff8 100644 --- a/src/nix-instantiate/fix.cc +++ b/src/nix-instantiate/main.cc @@ -70,7 +70,7 @@ void run(Strings args) #if 0 state.searchDirs.push_back("."); - state.searchDirs.push_back(nixDataDir + "/fix"); + state.searchDirs.push_back(nixDataDir + "/nix"); #endif for (Strings::iterator it = args.begin(); @@ -114,4 +114,4 @@ void run(Strings args) } -string programId = "fix"; +string programId = "nix-instantiate"; diff --git a/src/nix-instantiate/fix.sdf b/src/nix-instantiate/nix.sdf similarity index 100% rename from src/nix-instantiate/fix.sdf rename to src/nix-instantiate/nix.sdf diff --git a/src/nix-instantiate/fixexpr.cc b/src/nix-instantiate/nixexpr.cc similarity index 99% rename from src/nix-instantiate/fixexpr.cc rename to src/nix-instantiate/nixexpr.cc index 721fa8afa..816b39dc1 100644 --- a/src/nix-instantiate/fixexpr.cc +++ b/src/nix-instantiate/nixexpr.cc @@ -1,4 +1,4 @@ -#include "fixexpr.hh" +#include "nixexpr.hh" #include "storeexpr.hh" diff --git a/src/nix-instantiate/fixexpr.hh b/src/nix-instantiate/nixexpr.hh similarity index 93% rename from src/nix-instantiate/fixexpr.hh rename to src/nix-instantiate/nixexpr.hh index 6c1e51d9c..011c2900e 100644 --- a/src/nix-instantiate/fixexpr.hh +++ b/src/nix-instantiate/nixexpr.hh @@ -1,5 +1,5 @@ -#ifndef __FIXEXPR_H -#define __FIXEXPR_H +#ifndef __NIXEXPR_H +#define __NIXEXPR_H #include @@ -8,7 +8,7 @@ #include "util.hh" -/* Fix expressions are represented as ATerms. The maximal sharing +/* 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; @@ -72,4 +72,4 @@ Expr substitute(const ATermMap & subs, Expr e); Expr makeBool(bool b); -#endif /* !__FIXEXPR_H */ +#endif /* !__NIXEXPR_H */ diff --git a/src/nix-instantiate/parser.cc b/src/nix-instantiate/parser.cc index eaa41b396..f950a51f6 100644 --- a/src/nix-instantiate/parser.cc +++ b/src/nix-instantiate/parser.cc @@ -13,7 +13,6 @@ extern "C" { #include "aterm.hh" #include "parser.hh" #include "shared.hh" -#include "fixexpr.hh" #include "parse-table.h" @@ -76,12 +75,12 @@ Expr parseExprFromFile(Path path) if (e) return e; #endif - /* If `path' refers to a directory, append `/default.fix'. */ + /* If `path' refers to a directory, append `/default.nix'. */ struct stat st; if (stat(path.c_str(), &st)) throw SysError(format("getting status of `%1%'") % path); if (S_ISDIR(st.st_mode)) - path = canonPath(path + "/default.fix"); + path = canonPath(path + "/default.nix"); /* Initialise the SDF libraries. */ static bool initialised = false; @@ -95,12 +94,12 @@ Expr parseExprFromFile(Path path) ATprotect(&parseTable); parseTable = ATreadFromBinaryString( - (char *) fixParseTable, sizeof fixParseTable); + (char *) nixParseTable, sizeof nixParseTable); if (!parseTable) throw Error(format("cannot construct parse table term")); ATprotect(&lang); - lang = ATmake("Fix"); + lang = ATmake("Nix"); if (!SGopenLanguageFromTerm( (char *) programId.c_str(), lang, parseTable)) throw Error(format("cannot open language")); diff --git a/src/nix-instantiate/parser.hh b/src/nix-instantiate/parser.hh index e44987dd0..5983ec562 100644 --- a/src/nix-instantiate/parser.hh +++ b/src/nix-instantiate/parser.hh @@ -1,7 +1,7 @@ #ifndef __PARSER_H #define __PARSER_H -#include "fixexpr.hh" +#include "nixexpr.hh" Expr parseExprFromFile(Path path); diff --git a/src/nix-store/Makefile.am b/src/nix-store/Makefile.am index a39d1e2ad..516d78efc 100644 --- a/src/nix-store/Makefile.am +++ b/src/nix-store/Makefile.am @@ -1,15 +1,13 @@ bin_PROGRAMS = nix-store -nix_store_SOURCES = nix.cc dotgraph.cc +nix_store_SOURCES = main.cc dotgraph.cc nix_store_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx -lATerm -nix.o: nix-help.txt.hh +main.o: help.txt.hh %.hh: % - echo -n '"' > $@ - sed 's|\(.*\)|\1\\n\\|' < $< >> $@ - echo '"' >> $@ + ../bin2c/bin2c helpText < $< > $@ || (rm $@ && exit 1) AM_CXXFLAGS = \ -I.. -I../../externals/inst/include -I../libutil -I../libstore -I../libmain diff --git a/src/nix-store/nix-help.txt b/src/nix-store/help.txt similarity index 100% rename from src/nix-store/nix-help.txt rename to src/nix-store/help.txt diff --git a/src/nix-store/nix.cc b/src/nix-store/main.cc similarity index 98% rename from src/nix-store/nix.cc rename to src/nix-store/main.cc index d1766de39..0d87db9df 100644 --- a/src/nix-store/nix.cc +++ b/src/nix-store/main.cc @@ -6,6 +6,7 @@ #include "archive.hh" #include "shared.hh" #include "dotgraph.hh" +#include "help.txt.hh" typedef void (* Operation) (Strings opFlags, Strings opArgs); @@ -13,9 +14,7 @@ typedef void (* Operation) (Strings opFlags, Strings opArgs); static void printHelp() { - cout << -#include "nix-help.txt.hh" - ; + cout << string((char *) helpText, sizeof helpText); exit(0); } @@ -301,4 +300,4 @@ void run(Strings args) } -string programId = "nix"; +string programId = "nix-store"; From 38946e1378d50cf2921c509635e2119216fc9b0c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Nov 2003 12:07:39 +0000 Subject: [PATCH 0323/6440] * Forgot this one. --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index a0ab10dc4..1fa6e1d33 100644 --- a/configure.ac +++ b/configure.ac @@ -26,6 +26,7 @@ AM_CONFIG_HEADER([config.h]) AC_CONFIG_FILES([Makefile externals/Makefile src/Makefile + src/bin2c/Makefile src/boost/Makefile src/boost/format/Makefile src/libutil/Makefile From 2be8b5917a8040fac72e7970e94bbb436e8c35d6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Nov 2003 10:04:03 +0000 Subject: [PATCH 0324/6440] * Use `sdftable -s' to get warnings about the grammar. * Several bug fixes in the grammar. * Allow one-line comments (#... and //...) to end in EOF. --- src/nix-instantiate/Makefile.am | 2 +- src/nix-instantiate/nix.sdf | 30 ++++++++++++++++++------------ src/nix-instantiate/parser.cc | 2 +- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/nix-instantiate/Makefile.am b/src/nix-instantiate/Makefile.am index 3a09add0c..6fe798501 100644 --- a/src/nix-instantiate/Makefile.am +++ b/src/nix-instantiate/Makefile.am @@ -17,6 +17,6 @@ parse-table.h: nix.tbl ../bin2c/bin2c nixParseTable < $< > $@ || (rm $@ && exit 1) %.tbl: %.sdf - ../../externals/inst/bin/sdf2table -i $< -o $@ + ../../externals/inst/bin/sdf2table -s -i $< -o $@ CLEANFILES = parse-table.h nix.tbl diff --git a/src/nix-instantiate/nix.sdf b/src/nix-instantiate/nix.sdf index 54f5d5266..615bdb974 100644 --- a/src/nix-instantiate/nix.sdf +++ b/src/nix-instantiate/nix.sdf @@ -17,7 +17,7 @@ imports Fix-Exprs Fix-Layout module Fix-Exprs imports Fix-Lexicals URI exports - sorts Expr Bind Formal + sorts Expr Formal Bind Binds BindSemi ExprList context-free syntax Id -> Expr {cons("Var")} @@ -34,11 +34,11 @@ exports Expr Expr -> Expr {cons("Call"), left} - "{" {Formal ","}* "}" ":" Expr -> Expr {cons("Function"), right} + "{" {Formal ","}* "}" ":" Expr -> Expr {cons("Function")} Id -> Formal {cons("NoDefFormal")} Id "?" Expr -> Formal {cons("DefFormal")} - "assert" Expr ";" Expr -> Expr {cons("Assert"), right} + "assert" Expr ";" Expr -> Expr {cons("Assert")} "rec" "{" Binds "}" -> Expr {cons("Rec")} "let" "{" Binds "}" -> Expr {cons("LetRec")} @@ -87,7 +87,7 @@ exports module Fix-Lexicals exports - sorts Id Path + sorts Id Int Str Path PathComp Bool lexical syntax [a-zA-Z\_][a-zA-Z0-9\_\']* -> Id "rec" -> Id {reject} @@ -120,12 +120,17 @@ exports module URI exports - sorts Uri + sorts Uri Uhierpart Uopaquepart Uuricnoslash Unetpath Uabspath + Urelpath Urelsegment Uscheme Uauthority Uregname Userver + Uuserinfo Uhostport Uhost Uhostname Udomainlabel Utoplabel + UIPv4address Uport Upath Upathsegments Usegment Uparam + Upchar Uquery Ufragment Uuric Ureserved Uunreserved Umark + Uescaped Uhex Ualphanum Ualpha Ulowalpha Uupalpha Udigit lexical syntax - Uscheme ":" (Uhierpath | Uopaquepath) -> Uri + Uscheme ":" (Uhierpart | Uopaquepart) -> Uri - (Unetpath | Uabspath) ("?" Uquery)? -> Uhierpath - Uuricnoslash Uuric* -> Uopaquepath + (Unetpath | Uabspath) ("?" Uquery)? -> Uhierpart + Uuricnoslash Uuric* -> Uopaquepart Uunreserved | Uescaped | [\;\?\:\@\&\=\+\$\,] -> Uuricnoslash @@ -187,17 +192,18 @@ exports module Fix-Layout exports + sorts HashComment Asterisk Comment EOF lexical syntax [\ \t\n] -> LAYOUT HashComment -> LAYOUT Comment -> LAYOUT - "#" ~[\n]* [\n] -> HashComment - "//" ~[\n]* [\n] -> HashComment + "#" ~[\n]* ([\n] | EOF) -> HashComment + "//" ~[\n]* ([\n] | EOF) -> HashComment "/*" ( ~[\*] | Asterisk )* "*/" -> Comment [\*] -> Asterisk + "" -> EOF lexical restrictions Asterisk -/- [\/] + EOF -/- ~[] context-free restrictions LAYOUT? -/- [\ \t\n] | [\#] - syntax - HashComment -> diff --git a/src/nix-instantiate/parser.cc b/src/nix-instantiate/parser.cc index f950a51f6..b2c74af33 100644 --- a/src/nix-instantiate/parser.cc +++ b/src/nix-instantiate/parser.cc @@ -154,7 +154,7 @@ Expr parseExprFromFile(Path path) if (!imploded) throw Error(format("cannot implode parse tree")); - debug(format("imploded parse tree of `%1%': %2%") + printMsg(lvlVomit, format("imploded parse tree of `%1%': %2%") % path % imploded); /* Finally, clean it up. */ From ac68840e79ce74f05ee8b31bb1d528c98b9c7f76 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Nov 2003 11:35:41 +0000 Subject: [PATCH 0325/6440] * Refactoring: put the Nix expression evaluator in its own library so that it can be used by multiple programs. --- configure.ac | 2 +- src/Makefile.am | 3 ++- src/{nix-instantiate => libexpr}/Makefile.am | 0 src/{nix-instantiate => libexpr}/eval.cc | 0 src/{nix-instantiate => libexpr}/eval.hh | 0 src/{nix-instantiate => libexpr}/fix-expr.cc | 0 src/{nix-instantiate => libexpr}/fix-expr.hh | 0 src/{nix-instantiate => libexpr}/main.cc | 0 src/{nix-instantiate => libexpr}/nix.sdf | 0 src/{nix-instantiate => libexpr}/nixexpr.cc | 0 src/{nix-instantiate => libexpr}/nixexpr.hh | 0 src/{nix-instantiate => libexpr}/parser.cc | 0 src/{nix-instantiate => libexpr}/parser.hh | 0 src/{nix-instantiate => libexpr}/primops.cc | 0 src/{nix-instantiate => libexpr}/primops.hh | 0 15 files changed, 3 insertions(+), 2 deletions(-) rename src/{nix-instantiate => libexpr}/Makefile.am (100%) rename src/{nix-instantiate => libexpr}/eval.cc (100%) rename src/{nix-instantiate => libexpr}/eval.hh (100%) rename src/{nix-instantiate => libexpr}/fix-expr.cc (100%) rename src/{nix-instantiate => libexpr}/fix-expr.hh (100%) rename src/{nix-instantiate => libexpr}/main.cc (100%) rename src/{nix-instantiate => libexpr}/nix.sdf (100%) rename src/{nix-instantiate => libexpr}/nixexpr.cc (100%) rename src/{nix-instantiate => libexpr}/nixexpr.hh (100%) rename src/{nix-instantiate => libexpr}/parser.cc (100%) rename src/{nix-instantiate => libexpr}/parser.hh (100%) rename src/{nix-instantiate => libexpr}/primops.cc (100%) rename src/{nix-instantiate => libexpr}/primops.hh (100%) diff --git a/configure.ac b/configure.ac index 1fa6e1d33..09e292e1b 100644 --- a/configure.ac +++ b/configure.ac @@ -34,7 +34,7 @@ AC_CONFIG_FILES([Makefile src/libmain/Makefile src/nix-store/Makefile src/nix-hash/Makefile - src/nix-instantiate/Makefile + src/libexpr/Makefile scripts/Makefile corepkgs/Makefile corepkgs/fetchurl/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 1f2aafcdb..f06bb1f1d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1 +1,2 @@ -SUBDIRS = bin2c boost libutil libstore libmain nix-store nix-hash nix-instantiate +SUBDIRS = bin2c boost libutil libstore libmain nix-store nix-hash \ + libexpr #nix-instantiate diff --git a/src/nix-instantiate/Makefile.am b/src/libexpr/Makefile.am similarity index 100% rename from src/nix-instantiate/Makefile.am rename to src/libexpr/Makefile.am diff --git a/src/nix-instantiate/eval.cc b/src/libexpr/eval.cc similarity index 100% rename from src/nix-instantiate/eval.cc rename to src/libexpr/eval.cc diff --git a/src/nix-instantiate/eval.hh b/src/libexpr/eval.hh similarity index 100% rename from src/nix-instantiate/eval.hh rename to src/libexpr/eval.hh diff --git a/src/nix-instantiate/fix-expr.cc b/src/libexpr/fix-expr.cc similarity index 100% rename from src/nix-instantiate/fix-expr.cc rename to src/libexpr/fix-expr.cc diff --git a/src/nix-instantiate/fix-expr.hh b/src/libexpr/fix-expr.hh similarity index 100% rename from src/nix-instantiate/fix-expr.hh rename to src/libexpr/fix-expr.hh diff --git a/src/nix-instantiate/main.cc b/src/libexpr/main.cc similarity index 100% rename from src/nix-instantiate/main.cc rename to src/libexpr/main.cc diff --git a/src/nix-instantiate/nix.sdf b/src/libexpr/nix.sdf similarity index 100% rename from src/nix-instantiate/nix.sdf rename to src/libexpr/nix.sdf diff --git a/src/nix-instantiate/nixexpr.cc b/src/libexpr/nixexpr.cc similarity index 100% rename from src/nix-instantiate/nixexpr.cc rename to src/libexpr/nixexpr.cc diff --git a/src/nix-instantiate/nixexpr.hh b/src/libexpr/nixexpr.hh similarity index 100% rename from src/nix-instantiate/nixexpr.hh rename to src/libexpr/nixexpr.hh diff --git a/src/nix-instantiate/parser.cc b/src/libexpr/parser.cc similarity index 100% rename from src/nix-instantiate/parser.cc rename to src/libexpr/parser.cc diff --git a/src/nix-instantiate/parser.hh b/src/libexpr/parser.hh similarity index 100% rename from src/nix-instantiate/parser.hh rename to src/libexpr/parser.hh diff --git a/src/nix-instantiate/primops.cc b/src/libexpr/primops.cc similarity index 100% rename from src/nix-instantiate/primops.cc rename to src/libexpr/primops.cc diff --git a/src/nix-instantiate/primops.hh b/src/libexpr/primops.hh similarity index 100% rename from src/nix-instantiate/primops.hh rename to src/libexpr/primops.hh From fd7ac09f1073179d9ac439c3e9fb12a1bf00a7d5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Nov 2003 12:03:01 +0000 Subject: [PATCH 0326/6440] * Refactoring (step 2). --- configure.ac | 1 + src/Makefile.am | 2 +- src/libexpr/Makefile.am | 9 +++------ src/libexpr/parser.cc | 4 +--- src/nix-instantiate/Makefile.am | 11 +++++++++++ src/{libexpr => nix-instantiate}/main.cc | 0 6 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 src/nix-instantiate/Makefile.am rename src/{libexpr => nix-instantiate}/main.cc (100%) diff --git a/configure.ac b/configure.ac index 09e292e1b..54a251b23 100644 --- a/configure.ac +++ b/configure.ac @@ -35,6 +35,7 @@ AC_CONFIG_FILES([Makefile src/nix-store/Makefile src/nix-hash/Makefile src/libexpr/Makefile + src/nix-instantiate/Makefile scripts/Makefile corepkgs/Makefile corepkgs/fetchurl/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index f06bb1f1d..fe8cbf1e3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,2 +1,2 @@ SUBDIRS = bin2c boost libutil libstore libmain nix-store nix-hash \ - libexpr #nix-instantiate + libexpr nix-instantiate diff --git a/src/libexpr/Makefile.am b/src/libexpr/Makefile.am index 6fe798501..71c1f89f1 100644 --- a/src/libexpr/Makefile.am +++ b/src/libexpr/Makefile.am @@ -1,12 +1,9 @@ -bin_PROGRAMS = nix-instantiate +noinst_LIBRARIES = libexpr.a -nix_instantiate_SOURCES = nixexpr.cc parser.cc eval.cc primops.cc main.cc -nix_instantiate_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ - ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ - -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm +libexpr_a_SOURCES = nixexpr.cc parser.cc eval.cc primops.cc AM_CXXFLAGS = \ - -I.. -I../../externals/inst/include -I../libutil -I../libstore -I../libmain + -I.. -I../../externals/inst/include -I../libutil -I../libstore # Parse table generation. diff --git a/src/libexpr/parser.cc b/src/libexpr/parser.cc index b2c74af33..22d76c263 100644 --- a/src/libexpr/parser.cc +++ b/src/libexpr/parser.cc @@ -12,7 +12,6 @@ extern "C" { #include "aterm.hh" #include "parser.hh" -#include "shared.hh" #include "parse-table.h" @@ -100,8 +99,7 @@ Expr parseExprFromFile(Path path) ATprotect(&lang); lang = ATmake("Nix"); - if (!SGopenLanguageFromTerm( - (char *) programId.c_str(), lang, parseTable)) + if (!SGopenLanguageFromTerm("nix-parse", lang, parseTable)) throw Error(format("cannot open language")); SG_STARTSYMBOL_ON(); diff --git a/src/nix-instantiate/Makefile.am b/src/nix-instantiate/Makefile.am new file mode 100644 index 000000000..91843f663 --- /dev/null +++ b/src/nix-instantiate/Makefile.am @@ -0,0 +1,11 @@ +bin_PROGRAMS = nix-instantiate + +nix_instantiate_SOURCES = main.cc +nix_instantiate_LDADD = ../libmain/libmain.a ../libexpr/libexpr.a \ + ../libstore/libstore.a ../libutil/libutil.a \ + ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ + -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm + +AM_CXXFLAGS = \ + -I.. -I../../externals/inst/include -I../libutil -I../libstore \ + -I../libexpr -I../libmain diff --git a/src/libexpr/main.cc b/src/nix-instantiate/main.cc similarity index 100% rename from src/libexpr/main.cc rename to src/nix-instantiate/main.cc From 9898746ef3732979bf30e9048021b6232ddf15ac Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Nov 2003 17:27:16 +0000 Subject: [PATCH 0327/6440] * nix-env: a tool to manage user environments. * Replace all directory reading code by a generic readDirectory() function. --- configure.ac | 1 + corepkgs/buildenv/builder.pl | 72 +++++++++ corepkgs/buildenv/default.nix | 9 ++ src/Makefile.am | 2 +- src/libexpr/parser.cc | 2 + src/libmain/shared.cc | 1 + src/libstore/db.cc | 4 +- src/libstore/globals.cc | 1 + src/libstore/globals.hh | 3 + src/libstore/references.cc | 12 +- src/libutil/archive.cc | 21 +-- src/libutil/util.cc | 45 +++--- src/libutil/util.hh | 4 + src/nix-env/Makefile.am | 11 ++ src/nix-env/main.cc | 270 ++++++++++++++++++++++++++++++++++ src/nix-store/main.cc | 5 +- 16 files changed, 412 insertions(+), 51 deletions(-) create mode 100755 corepkgs/buildenv/builder.pl create mode 100644 corepkgs/buildenv/default.nix create mode 100644 src/nix-env/Makefile.am create mode 100644 src/nix-env/main.cc diff --git a/configure.ac b/configure.ac index 54a251b23..bc12b9a10 100644 --- a/configure.ac +++ b/configure.ac @@ -36,6 +36,7 @@ AC_CONFIG_FILES([Makefile src/nix-hash/Makefile src/libexpr/Makefile src/nix-instantiate/Makefile + src/nix-env/Makefile scripts/Makefile corepkgs/Makefile corepkgs/fetchurl/Makefile diff --git a/corepkgs/buildenv/builder.pl b/corepkgs/buildenv/builder.pl new file mode 100755 index 000000000..144ad3374 --- /dev/null +++ b/corepkgs/buildenv/builder.pl @@ -0,0 +1,72 @@ +#! /usr/bin/perl -w + +use strict; +use Cwd; + +my $selfdir = $ENV{"out"}; +mkdir "$selfdir", 0755 || die "error creating $selfdir"; + +# For each activated package, create symlinks. + +sub createLinks { + my $srcdir = shift; + my $dstdir = shift; + + my @srcfiles = glob("$srcdir/*"); + + foreach my $srcfile (@srcfiles) { + my $basename = $srcfile; + $basename =~ s/^.*\///g; # strip directory + my $dstfile = "$dstdir/$basename"; + if ($srcfile =~ /\/envpkgs$/) { + } elsif (-d $srcfile) { + # !!! hack for resolving name clashes + if (!-e $dstfile) { + mkdir $dstfile, 0755 || + die "error creating directory $dstfile"; + } + -d $dstfile or die "$dstfile is not a directory"; + createLinks($srcfile, $dstfile); + } elsif (-l $dstfile) { + my $target = readlink($dstfile); + die "collission between $srcfile and $target"; + } else { +# print "linking $dstfile to $srcfile\n"; + symlink($srcfile, $dstfile) || + die "error creating link $dstfile"; + } + } +} + +my %done; + +sub addPkg { + my $pkgdir = shift; + + return if (defined $done{$pkgdir}); + $done{$pkgdir} = 1; + +# print "merging $pkgdir\n"; + + createLinks("$pkgdir", "$selfdir"); + +# if (-f "$pkgdir/envpkgs") { +# my $envpkgs = `cat $pkgdir/envpkgs`; +# chomp $envpkgs; +# my @envpkgs = split / +/, $envpkgs; +# foreach my $envpkg (@envpkgs) { +# addPkg($envpkg); +# } +# } +} + +my @args = split ' ', $ENV{"derivations"}; + +while (scalar @args > 0) { + my $drvpath = shift @args; + print "adding $drvpath\n"; + addPkg($drvpath); +} + +symlink($ENV{"manifest"}, "$selfdir/manifest") or die "cannot create manifest"; + diff --git a/corepkgs/buildenv/default.nix b/corepkgs/buildenv/default.nix new file mode 100644 index 000000000..9bc704d8d --- /dev/null +++ b/corepkgs/buildenv/default.nix @@ -0,0 +1,9 @@ +{system, derivations, manifest}: + +derivation { + name = "user-environment"; + system = system; + builder = ./builder.pl; + derivations = derivations; + manifest = manifest; +} diff --git a/src/Makefile.am b/src/Makefile.am index fe8cbf1e3..0f1273426 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,2 +1,2 @@ SUBDIRS = bin2c boost libutil libstore libmain nix-store nix-hash \ - libexpr nix-instantiate + libexpr nix-instantiate nix-env diff --git a/src/libexpr/parser.cc b/src/libexpr/parser.cc index 22d76c263..e3c863a55 100644 --- a/src/libexpr/parser.cc +++ b/src/libexpr/parser.cc @@ -68,6 +68,8 @@ struct Cleanup : TermFun Expr parseExprFromFile(Path path) { + assert(path[0] == '/'); + #if 0 /* Perhaps this is already an imploded parse tree? */ Expr e = ATreadFromNamedFile(path.c_str()); diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index b06f5eb8b..632794db3 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -19,6 +19,7 @@ static void initAndRun(int argc, char * * argv) nixStore = NIX_STORE_DIR; nixDataDir = NIX_DATA_DIR; nixLogDir = NIX_LOG_DIR; + nixStateDir = (string) NIX_STATE_DIR; nixDBPath = (string) NIX_STATE_DIR + "/db"; /* Put the arguments in a vector. */ diff --git a/src/libstore/db.cc b/src/libstore/db.cc index 63ec2724f..a97111a3a 100644 --- a/src/libstore/db.cc +++ b/src/libstore/db.cc @@ -166,7 +166,7 @@ void Database::open(const string & path) /* The following code provides automatic recovery of the database environment. Recovery is necessary when a process dies while it has the database open. To detect this, - processes atomically increment a counter when the open the + processes atomically increment a counter when they open the database, and decrement it when they close it. If we see that counter is > 0 but no processes are accessing the database---determined by attempting to obtain a write lock @@ -199,7 +199,7 @@ void Database::open(const string & path) other readers or writers. */ int n = getAccessorCount(fdAccessors); - setAccessorCount(fdAccessors, 1); + setAccessorCount(fdAccessors, 1); if (n != 0) { printMsg(lvlTalkative, diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index a292b49ae..e5d76ff48 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -3,6 +3,7 @@ string nixStore = "/UNINIT"; string nixDataDir = "/UNINIT"; string nixLogDir = "/UNINIT"; +string nixStateDir = "/UNINIT"; string nixDBPath = "/UNINIT"; bool keepFailed = false; diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 1b4d0bde3..3da294cc8 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -16,6 +16,9 @@ extern string nixDataDir; /* !!! fix */ /* nixLogDir is the directory where we log various operations. */ extern string nixLogDir; +/* nixStateDir is the directory where state is stored. */ +extern string nixStateDir; + /* nixDBPath is the path name of our Berkeley DB environment. */ extern string nixDBPath; diff --git a/src/libstore/references.cc b/src/libstore/references.cc index ab743f76d..2bea44131 100644 --- a/src/libstore/references.cc +++ b/src/libstore/references.cc @@ -36,14 +36,10 @@ void checkPath(const string & path, throw SysError(format("getting attributes of path `%1%'") % path); if (S_ISDIR(st.st_mode)) { - AutoCloseDir dir = opendir(path.c_str()); - - struct dirent * dirent; - while (errno = 0, dirent = readdir(dir)) { - string name = dirent->d_name; - if (name == "." || name == "..") continue; - search(name, ids, seen); - checkPath(path + "/" + name, ids, seen); + Strings names = readDirectory(path); + for (Strings::iterator i = names.begin(); i != names.end(); i++) { + search(*i, ids, seen); + checkPath(path + "/" + *i, ids, seen); } } diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc index ed57df4c9..f605e8b61 100644 --- a/src/libutil/archive.cc +++ b/src/libutil/archive.cc @@ -51,23 +51,12 @@ static void dump(const string & path, DumpSink & sink); static void dumpEntries(const Path & path, DumpSink & sink) { - AutoCloseDir dir = opendir(path.c_str()); - if (!dir) throw SysError("opening directory " + path); + Strings names = readDirectory(path); + vector names2(names.begin(), names.end()); + sort(names2.begin(), names2.end()); - vector names; - - struct dirent * dirent; - while (errno = 0, dirent = readdir(dir)) { - string name = dirent->d_name; - if (name == "." || name == "..") continue; - names.push_back(name); - } - if (errno) throw SysError("reading directory " + path); - - sort(names.begin(), names.end()); - - for (vector::iterator it = names.begin(); - it != names.end(); it++) + for (vector::iterator it = names2.begin(); + it != names2.end(); it++) { writeString("entry", sink); writeString("(", sink); diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 299a37f6c..6a032a3f1 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -108,6 +108,25 @@ bool pathExists(const Path & path) } +Strings readDirectory(const Path & path) +{ + Strings names; + + AutoCloseDir dir = opendir(path.c_str()); + if (!dir) throw SysError(format("opening directory `%1%'") % path); + + struct dirent * dirent; + while (errno = 0, dirent = readdir(dir)) { /* sic */ + string name = dirent->d_name; + if (name == "." || name == "..") continue; + names.push_back(name); + } + if (errno) throw SysError(format("reading directory `%1%'") % path); + + return names; +} + + void deletePath(const Path & path) { printMsg(lvlVomit, format("deleting path `%1%'") % path); @@ -117,18 +136,7 @@ void deletePath(const Path & path) throw SysError(format("getting attributes of path `%1%'") % path); if (S_ISDIR(st.st_mode)) { - Strings names; - - { - AutoCloseDir dir = opendir(path.c_str()); - - struct dirent * dirent; - while (errno = 0, dirent = readdir(dir)) { - string name = dirent->d_name; - if (name == "." || name == "..") continue; - names.push_back(name); - } - } /* scoped to ensure that dir is closed at this point */ + Strings names = readDirectory(path); /* Make the directory writable. */ if (!(st.st_mode & S_IWUSR)) { @@ -136,7 +144,7 @@ void deletePath(const Path & path) throw SysError(format("making `%1%' writable")); } - for (Strings::iterator i = names.begin(); i != names.end(); i++) + for (Strings::iterator i = names.begin(); i != names.end(); ++i) deletePath(path + "/" + *i); } @@ -157,14 +165,9 @@ void makePathReadOnly(const Path & path) } if (S_ISDIR(st.st_mode)) { - AutoCloseDir dir = opendir(path.c_str()); - - struct dirent * dirent; - while (errno = 0, dirent = readdir(dir)) { - string name = dirent->d_name; - if (name == "." || name == "..") continue; - makePathReadOnly(path + "/" + name); - } + Strings names = readDirectory(path); + for (Strings::iterator i = names.begin(); i != names.end(); ++i) + makePathReadOnly(path + "/" + *i); } } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index e6b600eff..d0e7b3ada 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -73,6 +73,10 @@ string baseNameOf(const Path & path); /* Return true iff the given path exists. */ bool pathExists(const Path & path); +/* Read the contents of a directory. The entries `.' and `..' are + removed. */ +Strings readDirectory(const Path & path); + /* Delete a path; i.e., in the case of a directory, it is deleted recursively. Don't use this at home, kids. */ void deletePath(const Path & path); diff --git a/src/nix-env/Makefile.am b/src/nix-env/Makefile.am new file mode 100644 index 000000000..add54581b --- /dev/null +++ b/src/nix-env/Makefile.am @@ -0,0 +1,11 @@ +bin_PROGRAMS = nix-env + +nix_env_SOURCES = main.cc +nix_env_LDADD = ../libmain/libmain.a ../libexpr/libexpr.a \ + ../libstore/libstore.a ../libutil/libutil.a \ + ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ + -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm + +AM_CXXFLAGS = \ + -I.. -I../../externals/inst/include -I../libutil -I../libstore \ + -I../libexpr -I../libmain diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc new file mode 100644 index 000000000..4296ce509 --- /dev/null +++ b/src/nix-env/main.cc @@ -0,0 +1,270 @@ +#include "globals.hh" +#include "normalise.hh" +#include "shared.hh" +#include "parser.hh" +#include "eval.hh" + + +typedef void (* Operation) (EvalState & state, + Strings opFlags, Strings opArgs); + + +struct DrvInfo +{ + string name; + Path drvPath; + Path outPath; +}; + +typedef map DrvInfos; + + +bool parseDerivation(EvalState & state, Expr e, DrvInfo & drv) +{ + ATMatcher m; + + e = evalExpr(state, e); + if (!(atMatch(m, e) >> "Attrs")) return false; + Expr a = queryAttr(e, "type"); + if (!a || evalString(state, a) != "derivation") return false; + + a = queryAttr(e, "name"); + if (!a) throw badTerm("derivation name missing", e); + drv.name = evalString(state, a); + + a = queryAttr(e, "drvPath"); + if (!a) throw badTerm("derivation path missing", e); + drv.drvPath = evalPath(state, a); + + a = queryAttr(e, "outPath"); + if (!a) throw badTerm("output path missing", e); + drv.outPath = evalPath(state, a); + + return true; +} + + +bool parseDerivations(EvalState & state, Expr e, DrvInfos & drvs) +{ + e = evalExpr(state, e); + + ATermMap drvMap; + queryAllAttrs(e, drvMap); + + for (ATermIterator i(drvMap.keys()); i; ++i) { + DrvInfo drv; + debug(format("evaluating attribute `%1%'") % *i); + if (parseDerivation(state, drvMap.get(*i), drv)) + drvs[drv.name] = drv; + } + + return true; +} + + +void loadDerivations(EvalState & state, Path nePath, DrvInfos & drvs) +{ + Expr e = parseExprFromFile(absPath(nePath)); + if (!parseDerivations(state, e, drvs)) + throw badTerm("expected set of derivations", e); +} + + +static Path getLinksDir() +{ + return canonPath(nixStateDir + "/links"); +} + + +Path createLink(Path outPath, Path drvPath) +{ + Path linksDir = getLinksDir(); + + unsigned int num = 0; + + Strings names = readDirectory(linksDir); + for (Strings::iterator i = names.begin(); i != names.end(); ++i) { + istringstream s(*i); + unsigned int n; + if (s >> n && s.eof() && n > num) num = n + 1; + } + + Path linkPath = (format("%1%/%2%") % linksDir % num).str(); + + if (symlink(outPath.c_str(), linkPath.c_str()) != 0) + throw SysError(format("creating symlink `%1%'") % linkPath); + + return linkPath; +} + + +void installDerivations(EvalState & state, + Path nePath, Strings drvNames) +{ + debug(format("installing derivations from `%1%'") % nePath); + + /* Fetch all derivations from the input file. */ + DrvInfos availDrvs; + loadDerivations(state, nePath, availDrvs); + + /* Filter out the ones we're not interested in. */ + DrvInfos selectedDrvs; + for (Strings::iterator i = drvNames.begin(); + i != drvNames.end(); ++i) + { + DrvInfos::iterator j = availDrvs.find(*i); + if (j == availDrvs.end()) + throw Error(format("unknown derivation `%1%'") % *i); + else + selectedDrvs[j->first] = j->second; + } + + /* Get the environment builder expression. */ + Expr envBuilder = parseExprFromFile("/home/eelco/nix/corepkgs/buildenv"); /* !!! */ + + /* Construct the whole top level derivation. */ + ATermList inputs = ATempty; + for (DrvInfos::iterator i = selectedDrvs.begin(); + i != selectedDrvs.end(); ++i) + { + ATerm t = ATmake( + "Attrs([" + "Bind(\"type\", Str(\"derivation\")), " + "Bind(\"name\", Str()), " + "Bind(\"drvPath\", Path()), " + "Bind(\"outPath\", Path())" + "])", + i->second.name.c_str(), + i->second.drvPath.c_str(), + i->second.outPath.c_str()); + inputs = ATinsert(inputs, t); + } + + ATerm inputs2 = ATmake("List()", ATreverse(inputs)); + + /* Also write a copy of the list of inputs to the store; we need + it for future modifications of the environment. */ + Path inputsFile = writeTerm(inputs2, "-env-inputs"); + + Expr topLevel = ATmake( + "Call(, Attrs([" + "Bind(\"system\", Str()), " + "Bind(\"derivations\", ), " // !!! redundant + "Bind(\"manifest\", Path())" + "]))", + envBuilder, thisSystem.c_str(), inputs2, inputsFile.c_str()); + + /* Instantiate it. */ + debug(format("evaluating builder expression `%1%'") % topLevel); + DrvInfo topLevelDrv; + if (!parseDerivation(state, topLevel, topLevelDrv)) + abort(); + + /* Realise the resulting store expression. */ + debug(format("realising user environment")); + Path nfPath = normaliseStoreExpr(topLevelDrv.drvPath); + realiseClosure(nfPath); + + /* Switch the current user environment to the output path. */ + debug(format("switching to new user environment")); + Path linkPath = createLink(topLevelDrv.outPath, topLevelDrv.drvPath); +// switchLink(current"), link); +} + + +static void opInstall(EvalState & state, + Strings opFlags, Strings opArgs) +{ + if (opArgs.size() < 1) throw UsageError("Nix expression expected"); + + Path nePath = opArgs.front(); + opArgs.pop_front(); + + installDerivations(state, nePath, + Strings(opArgs.begin(), opArgs.end())); +} + + +static void opQuery(EvalState & state, + Strings opFlags, Strings opArgs) +{ + enum { qName } query = qName; + enum { sInstalled, sAvailable } source = sInstalled; + + for (Strings::iterator i = opFlags.begin(); + i != opFlags.end(); ++i) + if (*i == "--name") query = qName; + else if (*i == "--installed") source = sInstalled; + else if (*i == "--available" || *i == "-f") source = sAvailable; + else throw UsageError(format("unknown flag `%1%'") % *i); + + /* Obtain derivation information from the specified source. */ + DrvInfos drvs; + + switch (source) { + + case sInstalled: + break; + + case sAvailable: { + Path nePath = opArgs.front(); + opArgs.pop_front(); + loadDerivations(state, nePath, drvs); + break; + } + + default: abort(); + } + + /* Perform the specified query on the derivations. */ + switch (query) { + + case qName: { + if (opArgs.size() != 0) throw UsageError("no arguments expected"); + for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) + cout << format("%1%\n") % i->second.name; + break; + } + + default: abort(); + } +} + + +void run(Strings args) +{ + EvalState state; + Strings opFlags, opArgs; + Operation op = 0; + + for (Strings::iterator i = args.begin(); i != args.end(); ++i) { + string arg = *i; + + Operation oldOp = op; + + if (arg == "--install" || arg == "-i") + op = opInstall; + if (arg == "--query" || arg == "-q") + op = opQuery; + else if (arg == "--verbose" || arg == "-v") + verbosity = (Verbosity) ((int) verbosity + 1); + else if (arg[0] == '-') + opFlags.push_back(arg); + else + opArgs.push_back(arg); + + if (oldOp && oldOp != op) + throw UsageError("only one operation may be specified"); + } + + if (!op) throw UsageError("no operation specified"); + + openDB(); + + op(state, opFlags, opArgs); + + printEvalStats(state); +} + + +string programId = "nix-env"; diff --git a/src/nix-store/main.cc b/src/nix-store/main.cc index 0d87db9df..c73de5289 100644 --- a/src/nix-store/main.cc +++ b/src/nix-store/main.cc @@ -251,9 +251,8 @@ void run(Strings args) Strings opFlags, opArgs; Operation op = 0; - for (Strings::iterator it = args.begin(); it != args.end(); ) - { - string arg = *it++; + for (Strings::iterator i = args.begin(); i != args.end(); ++i) { + string arg = *i; Operation oldOp = op; From e0b5a492f537cacee7eadc47f44817908a0ab05a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Nov 2003 21:32:03 +0000 Subject: [PATCH 0328/6440] * Installation: add the previously installed packages. Switch to the new configuration. * Status queries. --- src/nix-env/main.cc | 130 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 111 insertions(+), 19 deletions(-) diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 4296ce509..26b0ed3f5 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -16,7 +16,7 @@ struct DrvInfo Path outPath; }; -typedef map DrvInfos; +typedef map DrvInfos; bool parseDerivation(EvalState & state, Expr e, DrvInfo & drv) @@ -46,16 +46,30 @@ bool parseDerivation(EvalState & state, Expr e, DrvInfo & drv) bool parseDerivations(EvalState & state, Expr e, DrvInfos & drvs) { + ATMatcher m; + ATermList es; + e = evalExpr(state, e); - ATermMap drvMap; - queryAllAttrs(e, drvMap); + if (atMatch(m, e) >> "Attrs") { + ATermMap drvMap; + queryAllAttrs(e, drvMap); - for (ATermIterator i(drvMap.keys()); i; ++i) { - DrvInfo drv; - debug(format("evaluating attribute `%1%'") % *i); - if (parseDerivation(state, drvMap.get(*i), drv)) - drvs[drv.name] = drv; + for (ATermIterator i(drvMap.keys()); i; ++i) { + DrvInfo drv; + debug(format("evaluating attribute `%1%'") % *i); + if (parseDerivation(state, drvMap.get(*i), drv)) + drvs[drv.drvPath] = drv; + } + } + + else if (atMatch(m, e) >> "List" >> es) { + for (ATermIterator i(es); i; ++i) { + DrvInfo drv; + debug(format("evaluating list element") % *i); + if (parseDerivation(state, *i, drv)) + drvs[drv.drvPath] = drv; + } } return true; @@ -76,6 +90,26 @@ static Path getLinksDir() } +static Path getCurrentPath() +{ + return getLinksDir() + "/current"; +} + + +void queryInstalled(EvalState & state, DrvInfos & drvs) +{ + Path path = getCurrentPath() + "/manifest"; + + if (!pathExists(path)) return; /* not an error, assume nothing installed */ + + Expr e = ATreadFromNamedFile(path.c_str()); + if (!e) throw Error(format("cannot read Nix expression from `%1%'") % path); + + if (!parseDerivations(state, e, drvs)) + throw badTerm(format("expected set of derivations in `%1%'") % path, e); +} + + Path createLink(Path outPath, Path drvPath) { Path linksDir = getLinksDir(); @@ -86,18 +120,40 @@ Path createLink(Path outPath, Path drvPath) for (Strings::iterator i = names.begin(); i != names.end(); ++i) { istringstream s(*i); unsigned int n; - if (s >> n && s.eof() && n > num) num = n + 1; + if (s >> n && s.eof() && n >= num) num = n + 1; } - Path linkPath = (format("%1%/%2%") % linksDir % num).str(); + Path linkPath; - if (symlink(outPath.c_str(), linkPath.c_str()) != 0) - throw SysError(format("creating symlink `%1%'") % linkPath); + while (1) { + linkPath = (format("%1%/%2%") % linksDir % num).str(); + if (symlink(outPath.c_str(), linkPath.c_str()) == 0) break; + if (errno != EEXIST) + throw SysError(format("creating symlink `%1%'") % linkPath); + /* Somebody beat us to it, retry with a higher number. */ + num++; + } return linkPath; } +void switchLink(Path link, Path target) +{ + Path tmp = canonPath(dirOf(link) + "/.new_" + baseNameOf(link)); + if (symlink(target.c_str(), tmp.c_str()) != 0) + throw SysError(format("creating symlink `%1%'") % tmp); + /* The rename() system call is supposed to be essentially atomic + on Unix. That is, if we have links `current -> X' and + `new_current -> Y', and we rename new_current to current, a + process accessing current will see X or Y, but never a + file-not-found or other error condition. This is sufficient to + atomically switch user environments. */ + if (rename(tmp.c_str(), link.c_str()) != 0) + throw SysError(format("renaming `%1%' to `%2%'") % tmp % link); +} + + void installDerivations(EvalState & state, Path nePath, Strings drvNames) { @@ -107,18 +163,30 @@ void installDerivations(EvalState & state, DrvInfos availDrvs; loadDerivations(state, nePath, availDrvs); + typedef map NameMap; + NameMap nameMap; + + for (DrvInfos::iterator i = availDrvs.begin(); + i != availDrvs.end(); ++i) + nameMap[i->second.name] = i->first; + /* Filter out the ones we're not interested in. */ DrvInfos selectedDrvs; for (Strings::iterator i = drvNames.begin(); i != drvNames.end(); ++i) { - DrvInfos::iterator j = availDrvs.find(*i); - if (j == availDrvs.end()) + NameMap::iterator j = nameMap.find(*i); + if (j == nameMap.end()) throw Error(format("unknown derivation `%1%'") % *i); else - selectedDrvs[j->first] = j->second; + selectedDrvs[j->second] = availDrvs[j->second]; } + /* Add in the already installed derivations. */ + DrvInfos installedDrvs; + queryInstalled(state, installedDrvs); + selectedDrvs.insert(installedDrvs.begin(), installedDrvs.end()); + /* Get the environment builder expression. */ Expr envBuilder = parseExprFromFile("/home/eelco/nix/corepkgs/buildenv"); /* !!! */ @@ -168,14 +236,14 @@ void installDerivations(EvalState & state, /* Switch the current user environment to the output path. */ debug(format("switching to new user environment")); Path linkPath = createLink(topLevelDrv.outPath, topLevelDrv.drvPath); -// switchLink(current"), link); + switchLink(getLinksDir() + "/current", linkPath); } static void opInstall(EvalState & state, Strings opFlags, Strings opArgs) { - if (opArgs.size() < 1) throw UsageError("Nix expression expected"); + if (opArgs.size() < 1) throw UsageError("Nix file expected"); Path nePath = opArgs.front(); opArgs.pop_front(); @@ -188,12 +256,14 @@ static void opInstall(EvalState & state, static void opQuery(EvalState & state, Strings opFlags, Strings opArgs) { - enum { qName } query = qName; + enum { qName, qDrvPath, qStatus } query = qName; enum { sInstalled, sAvailable } source = sInstalled; for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ++i) if (*i == "--name") query = qName; + else if (*i == "--expr" || *i == "-e") query = qDrvPath; + else if (*i == "--status" || *i == "-s") query = qStatus; else if (*i == "--installed") source = sInstalled; else if (*i == "--available" || *i == "-f") source = sAvailable; else throw UsageError(format("unknown flag `%1%'") % *i); @@ -204,9 +274,11 @@ static void opQuery(EvalState & state, switch (source) { case sInstalled: + queryInstalled(state, drvs); break; case sAvailable: { + if (opArgs.size() < 1) throw UsageError("Nix file expected"); Path nePath = opArgs.front(); opArgs.pop_front(); loadDerivations(state, nePath, drvs); @@ -216,16 +288,36 @@ static void opQuery(EvalState & state, default: abort(); } + if (opArgs.size() != 0) throw UsageError("no arguments expected"); + /* Perform the specified query on the derivations. */ switch (query) { case qName: { - if (opArgs.size() != 0) throw UsageError("no arguments expected"); for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) cout << format("%1%\n") % i->second.name; break; } + case qDrvPath: { + for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) + cout << format("%1%\n") % i->second.drvPath; + break; + } + + case qStatus: { + DrvInfos installed; + queryInstalled(state, installed); + + for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) { + cout << format("%1%%2% %3%\n") + % (installed.find(i->first) != installed.end() ? 'I' : '-') + % (isValidPath(i->second.outPath) ? 'P' : '-') + % i->second.name; + } + break; + } + default: abort(); } } From 2e9042bd1e7e3a322f072f0bf98510698afa626a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 20 Nov 2003 13:48:48 +0000 Subject: [PATCH 0329/6440] * Uninstall command (doesn't work yet). --- src/nix-env/main.cc | 117 ++++++++++++++++++++++++++++++-------------- 1 file changed, 81 insertions(+), 36 deletions(-) diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 26b0ed3f5..c391fc13c 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -66,7 +66,7 @@ bool parseDerivations(EvalState & state, Expr e, DrvInfos & drvs) else if (atMatch(m, e) >> "List" >> es) { for (ATermIterator i(es); i; ++i) { DrvInfo drv; - debug(format("evaluating list element") % *i); + debug(format("evaluating list element")); if (parseDerivation(state, *i, drv)) drvs[drv.drvPath] = drv; } @@ -154,46 +154,15 @@ void switchLink(Path link, Path target) } -void installDerivations(EvalState & state, - Path nePath, Strings drvNames) +void createUserEnv(EvalState & state, const DrvInfos & drvs) { - debug(format("installing derivations from `%1%'") % nePath); - - /* Fetch all derivations from the input file. */ - DrvInfos availDrvs; - loadDerivations(state, nePath, availDrvs); - - typedef map NameMap; - NameMap nameMap; - - for (DrvInfos::iterator i = availDrvs.begin(); - i != availDrvs.end(); ++i) - nameMap[i->second.name] = i->first; - - /* Filter out the ones we're not interested in. */ - DrvInfos selectedDrvs; - for (Strings::iterator i = drvNames.begin(); - i != drvNames.end(); ++i) - { - NameMap::iterator j = nameMap.find(*i); - if (j == nameMap.end()) - throw Error(format("unknown derivation `%1%'") % *i); - else - selectedDrvs[j->second] = availDrvs[j->second]; - } - - /* Add in the already installed derivations. */ - DrvInfos installedDrvs; - queryInstalled(state, installedDrvs); - selectedDrvs.insert(installedDrvs.begin(), installedDrvs.end()); - /* Get the environment builder expression. */ Expr envBuilder = parseExprFromFile("/home/eelco/nix/corepkgs/buildenv"); /* !!! */ /* Construct the whole top level derivation. */ ATermList inputs = ATempty; - for (DrvInfos::iterator i = selectedDrvs.begin(); - i != selectedDrvs.end(); ++i) + for (DrvInfos::const_iterator i = drvs.begin(); + i != drvs.end(); ++i) { ATerm t = ATmake( "Attrs([" @@ -240,6 +209,50 @@ void installDerivations(EvalState & state, } +typedef map NameMap; + + +NameMap mapByNames(DrvInfos & drvs) +{ + NameMap nameMap; + for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) + nameMap[i->second.name] = i->first; + return nameMap; +} + + +void installDerivations(EvalState & state, + Path nePath, Strings drvNames) +{ + debug(format("installing derivations from `%1%'") % nePath); + + /* Fetch all derivations from the input file. */ + DrvInfos availDrvs; + loadDerivations(state, nePath, availDrvs); + + NameMap nameMap = mapByNames(availDrvs); + + /* Filter out the ones we're not interested in. */ + DrvInfos selectedDrvs; + for (Strings::iterator i = drvNames.begin(); + i != drvNames.end(); ++i) + { + NameMap::iterator j = nameMap.find(*i); + if (j == nameMap.end()) + throw Error(format("unknown derivation `%1%'") % *i); + else + selectedDrvs[j->second] = availDrvs[j->second]; + } + + /* Add in the already installed derivations. */ + DrvInfos installedDrvs; + queryInstalled(state, installedDrvs); + selectedDrvs.insert(installedDrvs.begin(), installedDrvs.end()); + + createUserEnv(state, selectedDrvs); +} + + static void opInstall(EvalState & state, Strings opFlags, Strings opArgs) { @@ -253,6 +266,36 @@ static void opInstall(EvalState & state, } +void uninstallDerivations(EvalState & state, Strings drvNames) +{ + DrvInfos installedDrvs; + queryInstalled(state, installedDrvs); + + NameMap nameMap = mapByNames(installedDrvs); + + for (Strings::iterator i = drvNames.begin(); + i != drvNames.end(); ++i) + { + NameMap::iterator j = nameMap.find(*i); + if (j == nameMap.end()) + throw Error(format("unknown derivation `%1%'") % *i); + else + installedDrvs.erase(j->first); + } + + createUserEnv(state, installedDrvs); +#if 0 +#endif +} + + +static void opUninstall(EvalState & state, + Strings opFlags, Strings opArgs) +{ + uninstallDerivations(state, opArgs); +} + + static void opQuery(EvalState & state, Strings opFlags, Strings opArgs) { @@ -336,7 +379,9 @@ void run(Strings args) if (arg == "--install" || arg == "-i") op = opInstall; - if (arg == "--query" || arg == "-q") + else if (arg == "--uninstall" || arg == "-u") + op = opUninstall; + else if (arg == "--query" || arg == "-q") op = opQuery; else if (arg == "--verbose" || arg == "-v") verbosity = (Verbosity) ((int) verbosity + 1); From 06208d1d8677eaea1fb56dd09832f43154bbab5d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 21 Nov 2003 14:23:18 +0000 Subject: [PATCH 0330/6440] * Uninstallation. --- src/libexpr/primops.cc | 16 +++++++++++----- src/nix-env/main.cc | 11 ++++++++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 097933115..481966af9 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -93,10 +93,16 @@ static string processBinding(EvalState & state, Expr e, StoreExpr & ne) Expr a = queryAttr(e, "type"); if (a && evalString(state, a) == "derivation") { a = queryAttr(e, "drvPath"); - if (a) { - Path drvPath = evalPath(state, a); - return addInput(state, drvPath, ne); - } + if (!a) throw badTerm("derivation name missing", e); + Path drvPath = evalPath(state, a); + + a = queryAttr(e, "drvHash"); + if (!a) throw badTerm("derivation hash missing", e); + Hash drvHash = parseHash(evalString(state, a)); + + state.drvHashes[drvPath] = drvHash; + + return addInput(state, drvPath, ne); } } @@ -199,13 +205,13 @@ Expr primDerivation(EvalState & state, Expr args) ? hashString((string) outHash + outPath) : hashDerivation(state, ne); Path drvPath = writeTerm(unparseStoreExpr(ne), "-d-" + drvName); - state.drvHashes[drvPath] = drvHash; printMsg(lvlChatty, format("instantiated `%1%' -> `%2%'") % drvName % drvPath); attrs.set("outPath", ATmake("Path()", outPath.c_str())); attrs.set("drvPath", ATmake("Path()", drvPath.c_str())); + attrs.set("drvHash", ATmake("Str()", ((string) drvHash).c_str())); attrs.set("type", ATmake("Str(\"derivation\")")); return makeAttrs(attrs); diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index c391fc13c..73166964f 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -14,6 +14,7 @@ struct DrvInfo string name; Path drvPath; Path outPath; + Hash drvHash; }; typedef map DrvInfos; @@ -36,6 +37,10 @@ bool parseDerivation(EvalState & state, Expr e, DrvInfo & drv) if (!a) throw badTerm("derivation path missing", e); drv.drvPath = evalPath(state, a); + a = queryAttr(e, "drvHash"); + if (!a) throw badTerm("derivation hash missing", e); + drv.drvHash = parseHash(evalString(state, a)); + a = queryAttr(e, "outPath"); if (!a) throw badTerm("output path missing", e); drv.outPath = evalPath(state, a); @@ -169,10 +174,12 @@ void createUserEnv(EvalState & state, const DrvInfos & drvs) "Bind(\"type\", Str(\"derivation\")), " "Bind(\"name\", Str()), " "Bind(\"drvPath\", Path()), " + "Bind(\"drvHash\", Str()), " "Bind(\"outPath\", Path())" "])", i->second.name.c_str(), i->second.drvPath.c_str(), + ((string) i->second.drvHash).c_str(), i->second.outPath.c_str()); inputs = ATinsert(inputs, t); } @@ -280,12 +287,10 @@ void uninstallDerivations(EvalState & state, Strings drvNames) if (j == nameMap.end()) throw Error(format("unknown derivation `%1%'") % *i); else - installedDrvs.erase(j->first); + installedDrvs.erase(j->second); } createUserEnv(state, installedDrvs); -#if 0 -#endif } From 7a02d954186d6ba1ea41d9917d63f9fab84736b3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 21 Nov 2003 16:05:19 +0000 Subject: [PATCH 0331/6440] * Remove lock files after building. --- src/libstore/normalise.cc | 5 +++++ src/libstore/pathlocks.cc | 14 +++++++++++++- src/libstore/pathlocks.hh | 2 ++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/libstore/normalise.cc b/src/libstore/normalise.cc index c531e6784..db85c3b5b 100644 --- a/src/libstore/normalise.cc +++ b/src/libstore/normalise.cc @@ -256,6 +256,11 @@ Path normaliseStoreExpr(const Path & _nePath, PathSet pending) registerSuccessor(txn, nePath, nfPath); txn.commit(); + /* It is now safe to delete the lock files, since all future + lockers will see the successor; they will not create new lock + files with the same names as the old (unlinked) lock files. */ + outputLocks.setDeletion(true); + return nfPath; } diff --git a/src/libstore/pathlocks.cc b/src/libstore/pathlocks.cc index 3ecbbbcba..c057edce1 100644 --- a/src/libstore/pathlocks.cc +++ b/src/libstore/pathlocks.cc @@ -43,6 +43,7 @@ static StringSet lockedPaths; /* !!! not thread-safe */ PathLocks::PathLocks(const PathSet & _paths) + : deletePaths(false) { /* Note that `fds' is built incrementally so that the destructor will only release those locks that we have already acquired. */ @@ -85,6 +86,17 @@ PathLocks::~PathLocks() for (list::iterator i = fds.begin(); i != fds.end(); i++) close(*i); - for (Paths::iterator i = paths.begin(); i != paths.end(); i++) + for (Paths::iterator i = paths.begin(); i != paths.end(); i++) { + if (deletePaths) + /* This is not safe in general! */ + if (unlink(i->c_str()) != 0) + throw SysError(format("removing lock file `%1%'") % *i); lockedPaths.erase(*i); + } +} + + +void PathLocks::setDeletion(bool deletePaths) +{ + this->deletePaths = deletePaths; } diff --git a/src/libstore/pathlocks.hh b/src/libstore/pathlocks.hh index ce61386d6..606ae91c0 100644 --- a/src/libstore/pathlocks.hh +++ b/src/libstore/pathlocks.hh @@ -14,10 +14,12 @@ class PathLocks private: list fds; Paths paths; + bool deletePaths; public: PathLocks(const PathSet & _paths); ~PathLocks(); + void setDeletion(bool deletePaths); }; From 40d9eb14dfb842c51e9f86818b43ae7711e1a5d6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 22 Nov 2003 15:58:34 +0000 Subject: [PATCH 0332/6440] * Fix the garbage collector. --- scripts/nix-collect-garbage.in | 21 ++++++++++++++++++--- src/libstore/store.cc | 9 +-------- src/libutil/util.cc | 11 +++++++++++ src/libutil/util.hh | 5 +++++ src/nix-env/main.cc | 4 +++- 5 files changed, 38 insertions(+), 12 deletions(-) diff --git a/scripts/nix-collect-garbage.in b/scripts/nix-collect-garbage.in index 9b471d896..d0552fd2f 100755 --- a/scripts/nix-collect-garbage.in +++ b/scripts/nix-collect-garbage.in @@ -17,9 +17,24 @@ foreach my $arg (@ARGV) { else { die "unknown argument `$arg'" }; } +opendir(DIR, $linkdir) or die "cannot open directory $linkdir: $!"; +my @links = readdir DIR or die "cannot read directory $linkdir: $!"; +closedir DIR; + +my @roots; +foreach my $link (@links) { + $link = $linkdir . "/" . $link; + next if (!($link =~ /.id$/)); + open ROOT, "<$link" or die "cannot open $link: $!"; + my $root = ; + chomp $root; + close ROOT; + push @roots, $root; +} + my $extraarg = ""; if ($keepsuccessors) { $extraarg = "--include-successors"; }; -my $pid = open2(\*READ, \*WRITE, "nix --query --requisites $extraarg \$(cat $linkdir/*.id)") +my $pid = open2(\*READ, \*WRITE, "nix-store --query --requisites $extraarg @roots") or die "determining live paths"; close WRITE; while () { @@ -34,8 +49,8 @@ $? == 0 or die "determining live paths"; exit 0 if ($invert); -opendir(DIR, $storedir) or die "cannot opendir $storedir: $!"; -my @names = readdir(DIR); +opendir(DIR, $storedir) or die "cannot open directory $storedir: $!"; +my @names = readdir DIR; closedir DIR; foreach my $name (@names) { diff --git a/src/libstore/store.cc b/src/libstore/store.cc index c83316cf6..caaa293a6 100644 --- a/src/libstore/store.cc +++ b/src/libstore/store.cc @@ -1,9 +1,6 @@ #include -#include -#include #include -#include #include #include "store.hh" @@ -307,11 +304,7 @@ void addTextToStore(const Path & dstPath, const string & s) PathLocks outputLock(lockPaths); if (!isValidPath(dstPath)) { - - AutoCloseFD fd = open(dstPath.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666); - if (fd == -1) throw SysError(format("creating store file `%1%'") % dstPath); - - writeFull(fd, (unsigned char *) s.c_str(), s.size()); + writeStringToFile(dstPath, s); Transaction txn(nixDB); registerValidPath(txn, dstPath); diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 6a032a3f1..60b86b162 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -6,6 +6,7 @@ #include #include #include +#include #include "util.hh" @@ -192,6 +193,16 @@ Path createTempDir() } +void writeStringToFile(const Path & path, const string & s) +{ + AutoCloseFD fd = open(path.c_str(), + O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd == -1) + throw SysError(format("creating file `%1%'") % path); + writeFull(fd, (unsigned char *) s.c_str(), s.size()); +} + + Verbosity verbosity = lvlError; static int nestingLevel = 0; diff --git a/src/libutil/util.hh b/src/libutil/util.hh index d0e7b3ada..cca93cdc7 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -87,6 +87,11 @@ void makePathReadOnly(const Path & path); /* Create a temporary directory. */ Path createTempDir(); +/* Create a file and write the given text to it. The file is written + in binary mode (i.e., no end-of-line conversions). The path should + not already exist. */ +void writeStringToFile(const Path & path, const string & s); + /* Messages. */ diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 73166964f..a8377582c 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -139,6 +139,8 @@ Path createLink(Path outPath, Path drvPath) num++; } + writeStringToFile(linkPath + "-src.id", drvPath); + return linkPath; } @@ -290,7 +292,7 @@ void uninstallDerivations(EvalState & state, Strings drvNames) installedDrvs.erase(j->second); } - createUserEnv(state, installedDrvs); + createUserEnv(state, installedDrvs); } From ab0bc4999a49efbc8e1c25989662a96e32fa0cc5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 22 Nov 2003 18:45:56 +0000 Subject: [PATCH 0333/6440] * Maintain integrity of the substitute and successor mappings when deleting a path in the store. * Allow absolute paths in Nix expressions. * Get nix-prefetch-url to work again. * Various other fixes. --- corepkgs/fetchurl/Makefile.am | 11 ++- .../{fetchurl.sh.in => builder.sh.in} | 0 corepkgs/fetchurl/default.nix | 8 ++ corepkgs/fetchurl/fetchurl.fix | 10 -- scripts/Makefile.am | 2 +- scripts/nix-prefetch-url.in | 28 +++--- scripts/nix-switch.in | 86 ----------------- src/libexpr/nix.sdf | 3 +- src/libexpr/parser.cc | 81 +++++++++------- src/libexpr/parser.hh | 6 ++ src/libstore/normalise.cc | 1 + src/libstore/store.cc | 93 +++++++++++-------- src/libstore/store.hh | 3 - src/nix-instantiate/main.cc | 7 +- substitute.mk | 12 ++- 15 files changed, 152 insertions(+), 199 deletions(-) rename corepkgs/fetchurl/{fetchurl.sh.in => builder.sh.in} (100%) create mode 100644 corepkgs/fetchurl/default.nix delete mode 100644 corepkgs/fetchurl/fetchurl.fix delete mode 100755 scripts/nix-switch.in diff --git a/corepkgs/fetchurl/Makefile.am b/corepkgs/fetchurl/Makefile.am index 0c8f0c939..270bf0142 100644 --- a/corepkgs/fetchurl/Makefile.am +++ b/corepkgs/fetchurl/Makefile.am @@ -1,10 +1,11 @@ -all-local: fetchurl.sh +all-local: builder.sh install-exec-local: - $(INSTALL) -d $(datadir)/fix/fetchurl - $(INSTALL_DATA) fetchurl.fix $(datadir)/fix/fetchurl - $(INSTALL_PROGRAM) fetchurl.sh $(datadir)/fix/fetchurl + $(INSTALL) -d $(datadir)/nix/corepkgs + $(INSTALL) -d $(datadir)/nix/corepkgs/fetchurl + $(INSTALL_DATA) default.nix $(datadir)/nix/corepkgs/fetchurl + $(INSTALL_PROGRAM) builder.sh $(datadir)/nix/corepkgs/fetchurl include ../../substitute.mk -EXTRA_DIST = fetchurl.fix fetchurl.sh.in +EXTRA_DIST = default.nix builder.sh.in diff --git a/corepkgs/fetchurl/fetchurl.sh.in b/corepkgs/fetchurl/builder.sh.in similarity index 100% rename from corepkgs/fetchurl/fetchurl.sh.in rename to corepkgs/fetchurl/builder.sh.in diff --git a/corepkgs/fetchurl/default.nix b/corepkgs/fetchurl/default.nix new file mode 100644 index 000000000..663bba4a3 --- /dev/null +++ b/corepkgs/fetchurl/default.nix @@ -0,0 +1,8 @@ +{system, url, md5}: derivation { + name = baseNameOf (toString url); + system = system; + builder = ./builder.sh; + url = url; + md5 = md5; + id = md5; +} diff --git a/corepkgs/fetchurl/fetchurl.fix b/corepkgs/fetchurl/fetchurl.fix deleted file mode 100644 index 0221b612c..000000000 --- a/corepkgs/fetchurl/fetchurl.fix +++ /dev/null @@ -1,10 +0,0 @@ -Function(["url", "md5"], - Package( - [ ("build", Relative("fetchurl/fetchurl.sh")) - , ("url", Var("url")) - , ("md5", Var("md5")) - , ("name", BaseName(Var("url"))) - , ("id", Var("md5")) - ] - ) -) diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 4a1be7f8f..35bb926af 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -1,4 +1,4 @@ -bin_SCRIPTS = nix-switch nix-collect-garbage \ +bin_SCRIPTS = nix-collect-garbage \ nix-pull nix-push nix-prefetch-url noinst_SCRIPTS = nix-profile.sh diff --git a/scripts/nix-prefetch-url.in b/scripts/nix-prefetch-url.in index 332290d40..0873f5a8d 100644 --- a/scripts/nix-prefetch-url.in +++ b/scripts/nix-prefetch-url.in @@ -22,27 +22,29 @@ print "file has hash $hash\n"; my $out2 = "@prefix@/store/nix-prefetch-url-$hash"; rename $out, $out2; -# Create a Fix expression. -my $fixexpr = - "App(IncludeFix(\"fetchurl/fetchurl.fix\"), " . - "[(\"url\", \"$url\"), (\"md5\", \"$hash\")])"; +# Create a Nix expression. +my $nixexpr = + "(import @datadir@/nix/corepkgs/fetchurl) " . + "{url = $url; md5 = \"$hash\"; system = \"@host@\"}"; + +print "expr: $nixexpr\n"; # Instantiate a Nix expression. -print STDERR "running fix...\n"; -my $pid = open2(\*READ, \*WRITE, "fix -") or die "cannot run fix"; +print STDERR "instantiating Nix expression...\n"; +my $pid = open2(\*READ, \*WRITE, "nix-instantiate -") or die "cannot run nix-instantiate"; -print WRITE $fixexpr; +print WRITE $nixexpr; close WRITE; -my $id = ; -chomp $id; +my $drvpath = ; +chomp $drvpath; waitpid $pid, 0; -$? == 0 or die "fix failed"; +$? == 0 or die "nix-instantiate failed"; # Run Nix. -print STDERR "running nix...\n"; -system "nix --install $id > /dev/null"; -$? == 0 or die "`nix --install' failed"; +print STDERR "realising store expression $drvpath...\n"; +system "nix-store --realise $drvpath > /dev/null"; +$? == 0 or die "realisation failed"; unlink $out2; diff --git a/scripts/nix-switch.in b/scripts/nix-switch.in deleted file mode 100755 index 9fcb598e3..000000000 --- a/scripts/nix-switch.in +++ /dev/null @@ -1,86 +0,0 @@ -#! /usr/bin/perl -w - -use strict; - -my $keep = 0; -my $sourceroot = 1; -my $name = "current"; -my $srcid; - -my $argnr = 0; -while ($argnr < scalar @ARGV) { - my $arg = $ARGV[$argnr++]; - if ($arg eq "--keep") { $keep = 1; } - elsif ($arg eq "--no-source") { $sourceroot = 0; } - elsif ($arg eq "--name") { $name = $ARGV[$argnr++]; } - elsif ($arg =~ /^\//) { $srcid = $arg; } - else { die "unknown argument `$arg'" }; -} - -my $linkdir = "@localstatedir@/nix/links"; - -# Build the specified package, and all its dependencies. -my $nfid = `nix --install $srcid`; -if ($?) { die "`nix --install' failed"; } -chomp $nfid; -die unless $nfid =~ /^\//; - -my $pkgdir = `nix --query --list $nfid`; -if ($?) { die "`nix --query --list' failed"; } -chomp $pkgdir; - -# Figure out a generation number. -opendir(DIR, $linkdir); -my $nr = 0; -foreach my $n (sort(readdir(DIR))) { - next if (!($n =~ /^\d+$/)); - $nr = $n + 1 if ($n >= $nr); -} -closedir(DIR); - -my $link = "$linkdir/$nr"; - -# Create a symlink from $link to $pkgdir. -symlink($pkgdir, $link) or die "cannot create $link: $!"; - -# Store the id of the normal form. This is useful for garbage -# collection and the like. -my $idfile = "$linkdir/$nr.id"; -open ID, "> $idfile" or die "cannot create $idfile"; -print ID "$nfid\n"; -close ID; - -# Optionally store the source id. -if ($sourceroot) { - $idfile = "$linkdir/$nr-src.id"; - open ID, "> $idfile" or die "cannot create $idfile"; - print ID "$srcid\n"; - close ID; -} - -my $current = "$linkdir/$name"; - -# Read the current generation so that we can delete it (if --keep -# wasn't specified). -my $oldlink = readlink($current); - -# Make $link the current generation by pointing $linkdir/current to -# it. The rename() system call is supposed to be essentially atomic -# on Unix. That is, if we have links `current -> X' and `new_current -# -> Y', and we rename new_current to current, a process accessing -# current will see X or Y, but never a file-not-found or other error -# condition. This is sufficient to atomically switch the current link -# tree. - -print "switching $current to $link\n"; - -my $tmplink = "$linkdir/.new_$name"; -symlink($link, $tmplink) or die "cannot create $tmplink"; -rename($tmplink, $current) or die "cannot rename $tmplink"; - -if (!$keep && defined $oldlink) { - print "deleting old $oldlink\n"; - unlink($oldlink) == 1 or print "cannot delete $oldlink\n"; - unlink("$oldlink.id") == 1 or print "cannot delete $oldlink.id\n"; - unlink("$oldlink-src.id"); -} diff --git a/src/libexpr/nix.sdf b/src/libexpr/nix.sdf index 615bdb974..b6bb23ebd 100644 --- a/src/libexpr/nix.sdf +++ b/src/libexpr/nix.sdf @@ -104,6 +104,7 @@ exports "\"" ~[\n\"]* "\"" -> Str PathComp ("/" PathComp)+ -> Path + ("/" PathComp)+ -> Path [a-zA-Z0-9\.\_\-\+]+ -> PathComp "true" -> Bool @@ -184,7 +185,7 @@ exports [0-9] -> Udigit lexical restrictions - Uri -/- [a-zA-Z0-9\-\_\.\!\~\*\'\(\)] + Uri -/- [a-zA-Z0-9\-\_\.\!\~\*\'\(\)\/] %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/src/libexpr/parser.cc b/src/libexpr/parser.cc index e3c863a55..aecfa4348 100644 --- a/src/libexpr/parser.cc +++ b/src/libexpr/parser.cc @@ -66,23 +66,9 @@ struct Cleanup : TermFun }; -Expr parseExprFromFile(Path path) +static Expr parse(const char * text, const string & location, + const Path & basePath) { - 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' refers to a directory, append `/default.nix'. */ - struct stat st; - 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"); - /* Initialise the SDF libraries. */ static bool initialised = false; static ATerm parseTable = 0; @@ -113,26 +99,13 @@ Expr parseExprFromFile(Path path) initialised = true; } - /* Read the input file. We can't use SGparseFile() because it's - broken, so we read the input ourselves and call - SGparseString(). */ - AutoCloseFD fd = open(path.c_str(), O_RDONLY); - if (fd == -1) throw SysError(format("opening `%1%'") % path); - - if (fstat(fd, &st) == -1) - throw SysError(format("statting `%1%'") % path); - - char text[st.st_size + 1]; - readFull(fd, (unsigned char *) text, st.st_size); - text[st.st_size] = 0; - /* Parse it. */ - ATerm result = SGparseString(lang, "Expr", text); + ATerm result = SGparseString(lang, "Expr", (char *) text); if (!result) - throw SysError(format("parse failed in `%1%'") % path); + throw SysError(format("parse failed in `%1%'") % location); if (SGisParseError(result)) throw Error(format("parse error in `%1%': %2%") - % path % result); + % location % result); /* Implode it. */ PT_ParseTree tree = PT_makeParseTreeFromTerm(result); @@ -155,10 +128,50 @@ Expr parseExprFromFile(Path path) throw Error(format("cannot implode parse tree")); printMsg(lvlVomit, format("imploded parse tree of `%1%': %2%") - % path % imploded); + % location % imploded); /* Finally, clean it up. */ Cleanup cleanup; - cleanup.basePath = dirOf(path); + cleanup.basePath = basePath; return bottomupRewrite(cleanup, imploded); } + + +Expr parseExprFromFile(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' refers to a directory, append `/default.nix'. */ + struct stat st; + 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"); + + /* Read the input file. We can't use SGparseFile() because it's + broken, so we read the input ourselves and call + SGparseString(). */ + AutoCloseFD fd = open(path.c_str(), O_RDONLY); + if (fd == -1) throw SysError(format("opening `%1%'") % path); + + if (fstat(fd, &st) == -1) + throw SysError(format("statting `%1%'") % path); + + char text[st.st_size + 1]; + readFull(fd, (unsigned char *) text, st.st_size); + text[st.st_size] = 0; + + return parse(text, path, dirOf(path)); +} + + +Expr parseExprFromString(const string & s, const Path & basePath) +{ + return parse(s.c_str(), "(string)", basePath); +} diff --git a/src/libexpr/parser.hh b/src/libexpr/parser.hh index 5983ec562..461dae08c 100644 --- a/src/libexpr/parser.hh +++ b/src/libexpr/parser.hh @@ -4,7 +4,13 @@ #include "nixexpr.hh" +/* Parse a Nix expression from the specified file. If `path' refers + to a directory, the "/default.nix" is appended. */ Expr parseExprFromFile(Path path); +/* Parse a Nix expression from the specified string. */ +Expr parseExprFromString(const string & s, + const Path & basePath); + #endif /* !__PARSER_H */ diff --git a/src/libstore/normalise.cc b/src/libstore/normalise.cc index db85c3b5b..7ef45e292 100644 --- a/src/libstore/normalise.cc +++ b/src/libstore/normalise.cc @@ -82,6 +82,7 @@ Path normaliseStoreExpr(const Path & _nePath, PathSet pending) debug(format("skipping build of expression `%1%', someone beat us to it") % (string) nePath); if (ne.type != StoreExpr::neClosure) abort(); + outputLocks.setDeletion(true); return nePath2; } } diff --git a/src/libstore/store.cc b/src/libstore/store.cc index caaa293a6..4c6b8bbec 100644 --- a/src/libstore/store.cc +++ b/src/libstore/store.cc @@ -243,16 +243,33 @@ bool isValidPath(const Path & path) } -void unregisterValidPath(const Path & _path) +static void invalidatePath(const Path & path, Transaction & txn) { - Path path(canonPath(_path)); - Transaction txn(nixDB); - debug(format("unregistering path `%1%'") % path); nixDB.delPair(txn, dbValidPaths, path); - txn.commit(); + /* Remove any successor mappings to this path (but not *from* + it). */ + Paths revs; + nixDB.queryStrings(txn, dbSuccessorsRev, path, revs); + for (Paths::iterator i = revs.begin(); i != revs.end(); ++i) + nixDB.delPair(txn, dbSuccessors, *i); + nixDB.delPair(txn, dbSuccessorsRev, path); + + /* Remove any substitute mappings to this path. */ + revs.clear(); + nixDB.queryStrings(txn, dbSubstitutesRev, path, revs); + for (Paths::iterator i = revs.begin(); i != revs.end(); ++i) { + Paths subs; + nixDB.queryStrings(txn, dbSubstitutes, *i, subs); + remove(subs.begin(), subs.end(), path); + if (subs.size() > 0) + nixDB.setStrings(txn, dbSubstitutes, *i, subs); + else + nixDB.delPair(txn, dbSubstitutes, *i); + } + nixDB.delPair(txn, dbSubstitutesRev, path); } @@ -289,6 +306,8 @@ Path addToStore(const Path & _srcPath) registerValidPath(txn, dstPath); txn.commit(); } + + outputLock.setDeletion(true); } return dstPath; @@ -310,6 +329,8 @@ void addTextToStore(const Path & dstPath, const string & s) registerValidPath(txn, dstPath); txn.commit(); } + + outputLock.setDeletion(true); } } @@ -321,7 +342,9 @@ void deleteFromStore(const Path & _path) if (!isInPrefix(path, nixStore)) throw Error(format("path `%1%' is not in the store") % path); - unregisterValidPath(path); + Transaction txn(nixDB); + invalidatePath(path, txn); + txn.commit(); deletePath(path); } @@ -332,50 +355,43 @@ void verifyStore() Transaction txn(nixDB); Paths paths; + PathSet validPaths; nixDB.enumTable(txn, dbValidPaths, paths); - for (Paths::iterator i = paths.begin(); - i != paths.end(); i++) + for (Paths::iterator i = paths.begin(); i != paths.end(); ++i) { Path path = *i; if (!pathExists(path)) { debug(format("path `%1%' disappeared") % path); - nixDB.delPair(txn, dbValidPaths, path); - nixDB.delPair(txn, dbSuccessorsRev, path); - nixDB.delPair(txn, dbSubstitutesRev, path); - } + invalidatePath(path, txn); + } else + validPaths.insert(path); } -#if 0 - Strings subs; - nixDB.enumTable(txn, dbSubstitutes, subs); - - for (Strings::iterator i = subs.begin(); - i != subs.end(); i++) - { - FSId srcId = parseHash(*i); - - Strings subIds; - nixDB.queryStrings(txn, dbSubstitutes, srcId, subIds); - - for (Strings::iterator j = subIds.begin(); - j != subIds.end(); ) + Paths sucs; + nixDB.enumTable(txn, dbSuccessors, sucs); + for (Paths::iterator i = sucs.begin(); i != sucs.end(); ++i) { + /* Note that *i itself does not have to be valid, just its + successor. */ + Path sucPath; + if (nixDB.queryString(txn, dbSuccessors, *i, sucPath) && + validPaths.find(sucPath) == validPaths.end()) { - FSId subId = parseHash(*j); - - Strings subPaths; - nixDB.queryStrings(txn, dbId2Paths, subId, subPaths); - if (subPaths.size() == 0) { - debug(format("erasing substitute %1% for %2%") - % (string) subId % (string) srcId); - j = subIds.erase(j); - } else j++; + debug(format("found successor mapping to non-existent path `%1%'") % sucPath); + nixDB.delPair(txn, dbSuccessors, *i); } - - nixDB.setStrings(txn, dbSubstitutes, srcId, subIds); } -#endif + Paths rsucs; + nixDB.enumTable(txn, dbSuccessorsRev, rsucs); + for (Paths::iterator i = rsucs.begin(); i != rsucs.end(); ++i) { + if (validPaths.find(*i) == validPaths.end()) { + debug(format("found reverse successor mapping for non-existent path `%1%'") % *i); + nixDB.delPair(txn, dbSuccessorsRev, *i); + } + } + +#if 0 Paths sucs; nixDB.enumTable(txn, dbSuccessors, sucs); @@ -395,6 +411,7 @@ void verifyStore() nixDB.setStrings(txn, dbSuccessorsRev, sucPath, revs); } } +#endif txn.commit(); } diff --git a/src/libstore/store.hh b/src/libstore/store.hh index dab3d603f..143cad8db 100644 --- a/src/libstore/store.hh +++ b/src/libstore/store.hh @@ -48,9 +48,6 @@ Paths querySubstitutes(const Path & srcPath); /* Register the validity of a path. */ void registerValidPath(const Transaction & txn, const Path & path); -/* Unregister the validity of a path. */ -void unregisterValidPath(const Path & path); - /* Checks whether a path is valid. */ bool isValidPath(const Path & path); diff --git a/src/nix-instantiate/main.cc b/src/nix-instantiate/main.cc index aa6883ff8..f63789fc4 100644 --- a/src/nix-instantiate/main.cc +++ b/src/nix-instantiate/main.cc @@ -5,6 +5,7 @@ #include "normalise.hh" #include "shared.hh" #include "eval.hh" +#include "parser.hh" #if 0 @@ -29,9 +30,9 @@ static Path searchPath(const Paths & searchDirs, const Path & relPath) static Expr evalStdin(EvalState & state) { startNest(nest, lvlTalkative, format("evaluating standard input")); - Expr e = ATreadFromFile(stdin); - if (!e) - throw Error(format("unable to read a term from stdin")); + string s, s2; + while (getline(cin, s2)) s += s2 + "\n"; + Expr e = parseExprFromString(s, absPath(".")); return evalExpr(state, e); } diff --git a/substitute.mk b/substitute.mk index 8527cf6fd..c575dc0ff 100644 --- a/substitute.mk +++ b/substitute.mk @@ -1,9 +1,11 @@ %: %.in Makefile sed \ - -e s^@prefix\@^$(prefix)^g \ - -e s^@bindir\@^$(bindir)^g \ - -e s^@sysconfdir\@^$(sysconfdir)^g \ - -e s^@localstatedir\@^$(localstatedir)^g \ - -e s^@wget\@^$(wget)^g \ + -e "s^@prefix\@^$(prefix)^g" \ + -e "s^@bindir\@^$(bindir)^g" \ + -e "s^@sysconfdir\@^$(sysconfdir)^g" \ + -e "s^@localstatedir\@^$(localstatedir)^g" \ + -e "s^@datadir\@^$(datadir)^g" \ + -e "s^@host\@^$(host)^g" \ + -e "s^@wget\@^$(wget)^g" \ < $< > $@ || rm $@ chmod +x $@ From 9486dda1152d18b502fc31ff1d6aed4eba6f2fe3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 22 Nov 2003 20:39:51 +0000 Subject: [PATCH 0334/6440] * Fix nix-push. --- corepkgs/nar/Makefile.am | 13 +++---- corepkgs/nar/nar.fix | 8 ----- corepkgs/nar/nar.nix | 6 ++++ corepkgs/nar/nar.sh.in | 2 +- corepkgs/nar/unnar.fix | 9 ----- corepkgs/nar/unnar.nix | 7 ++++ corepkgs/nar/unnar.sh.in | 2 +- scripts/nix-push.in | 68 +++++++++++++++++-------------------- src/nix-instantiate/main.cc | 4 +-- 9 files changed, 56 insertions(+), 63 deletions(-) delete mode 100644 corepkgs/nar/nar.fix create mode 100644 corepkgs/nar/nar.nix delete mode 100644 corepkgs/nar/unnar.fix create mode 100644 corepkgs/nar/unnar.nix diff --git a/corepkgs/nar/Makefile.am b/corepkgs/nar/Makefile.am index e369d29c5..3e0aab869 100644 --- a/corepkgs/nar/Makefile.am +++ b/corepkgs/nar/Makefile.am @@ -1,12 +1,13 @@ all-local: nar.sh unnar.sh install-exec-local: - $(INSTALL) -d $(datadir)/fix/nar - $(INSTALL_DATA) nar.fix $(datadir)/fix/nar - $(INSTALL_PROGRAM) nar.sh $(datadir)/fix/nar - $(INSTALL_DATA) unnar.fix $(datadir)/fix/nar - $(INSTALL_PROGRAM) unnar.sh $(datadir)/fix/nar + $(INSTALL) -d $(datadir)/nix/corepkgs + $(INSTALL) -d $(datadir)/nix/corepkgs/nar + $(INSTALL_DATA) nar.nix $(datadir)/nix/corepkgs/nar + $(INSTALL_PROGRAM) nar.sh $(datadir)/nix/corepkgs/nar + $(INSTALL_DATA) unnar.nix $(datadir)/nix/corepkgs/nar + $(INSTALL_PROGRAM) unnar.sh $(datadir)/nix/corepkgs/nar include ../../substitute.mk -EXTRA_DIST = nar.fix nar.sh.in unnar.fix unnar.sh.in +EXTRA_DIST = nar.nix nar.sh.in unnar.nix unnar.sh.in diff --git a/corepkgs/nar/nar.fix b/corepkgs/nar/nar.fix deleted file mode 100644 index 429e7b549..000000000 --- a/corepkgs/nar/nar.fix +++ /dev/null @@ -1,8 +0,0 @@ -Function(["path"], - Package( - [ ("name", "nar") - , ("build", Relative("nar/nar.sh")) - , ("path", Var("path")) - ] - ) -) \ No newline at end of file diff --git a/corepkgs/nar/nar.nix b/corepkgs/nar/nar.nix new file mode 100644 index 000000000..f288e0ed4 --- /dev/null +++ b/corepkgs/nar/nar.nix @@ -0,0 +1,6 @@ +{system, path}: derivation { + name = "nar"; + builder = ./nar.sh; + system = system; + path = path; +} diff --git a/corepkgs/nar/nar.sh.in b/corepkgs/nar/nar.sh.in index c92ef8e25..8d3fdb51b 100644 --- a/corepkgs/nar/nar.sh.in +++ b/corepkgs/nar/nar.sh.in @@ -5,7 +5,7 @@ export PATH=/bin:/usr/bin echo "packing $path into $out..." mkdir $out || exit 1 dst=$out/`basename $path`.nar.bz2 -@bindir@/nix --dump "$path" | bzip2 > $dst || exit 1 +@bindir@/nix-store --dump "$path" | bzip2 > $dst || exit 1 md5=$(md5sum -b $dst | cut -c1-32) if test $? != 0; then exit 1; fi diff --git a/corepkgs/nar/unnar.fix b/corepkgs/nar/unnar.fix deleted file mode 100644 index cd5079e50..000000000 --- a/corepkgs/nar/unnar.fix +++ /dev/null @@ -1,9 +0,0 @@ -Function(["nar", "outPath"], - Package( - [ ("name", "unnar") - , ("outPath", Var("outPath")) - , ("build", Relative("nar/unnar.sh")) - , ("nar", Var("nar")) - ] - ) -) \ No newline at end of file diff --git a/corepkgs/nar/unnar.nix b/corepkgs/nar/unnar.nix new file mode 100644 index 000000000..a18e499b2 --- /dev/null +++ b/corepkgs/nar/unnar.nix @@ -0,0 +1,7 @@ +{system, narFile, outPath}: derivation { + name = "unnar"; + builder = ./unnar.sh; + system = system; + narFile = narFile; + outPath = outPath; +} diff --git a/corepkgs/nar/unnar.sh.in b/corepkgs/nar/unnar.sh.in index 8a4532af3..308135649 100644 --- a/corepkgs/nar/unnar.sh.in +++ b/corepkgs/nar/unnar.sh.in @@ -3,4 +3,4 @@ export PATH=/bin:/usr/bin echo "unpacking $nar to $out..." -bunzip2 < $nar | @bindir@/nix --restore "$out" || exit 1 +bunzip2 < $nar | @bindir@/nix-store --restore "$out" || exit 1 diff --git a/scripts/nix-push.in b/scripts/nix-push.in index 2e8158a6a..c1624d835 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -7,14 +7,13 @@ my $tmpdir; do { $tmpdir = tmpnam(); } until mkdir $tmpdir, 0777; -my $fixfile = "$tmpdir/create-nars.fix"; +my $nixfile = "$tmpdir/create-nars.nix"; my $manifest = "$tmpdir/MANIFEST"; END { unlink $manifest; unlink $fixfile; rmdir $tmpdir; } -open FIX, ">$fixfile"; -print FIX "["; -my $first = 1; +open NIX, ">$nixfile"; +print NIX "["; my @paths; @@ -24,10 +23,10 @@ foreach my $id (@ARGV) { # Get all paths referenced by the normalisation of the given # Nix expression. - system "nix --install $id > /dev/null"; - if ($?) { die "`nix --install' failed"; } + system "nix-store --realise $id > /dev/null"; + die if ($?); - open PATHS, "nix --query --requisites --include-successors $id 2> /dev/null |" or die "nix -qr"; + open PATHS, "nix-store --query --requisites --include-successors $id 2> /dev/null |" or die; while () { chomp; die "bad: $_" unless /^\//; @@ -35,34 +34,31 @@ foreach my $id (@ARGV) { } close PATHS; - # For each path, create a Fix expression that turns the path into + # For each path, create a Nix expression that turns the path into # a Nix archive. foreach my $path (@paths) { - die unless ($path =~ /\/[0-9a-z]{32}.*$/); - print "$path\n"; - - # Construct a Fix expression that creates a Nix archive. - my $fixexpr = - "Call(IncludeFix(\"nar/nar.fix\"), " . - "[ (\"path\", Closure([\"$path\"], [(\"$path\", [])]))" . - "])"; - - print FIX "," unless ($first); - $first = 0; - print FIX $fixexpr; + die unless ($path =~ /\/[0-9a-z]{32}.*$/); + print "$path\n"; + # Construct a Nix expression that creates a Nix archive. + my $nixexpr = + "((import @datadir@/nix/corepkgs/nar/nar.nix) " . + # !!! $path should be represented as a closure + "{path = \"$path\"; system = \"@host@\"}) "; + + print NIX $nixexpr; } } -print FIX "]"; -close FIX; +print NIX "]"; +close NIX; -# Instantiate a Nix expression from the Fix expression. +# Instantiate a store expression from the Nix expression. my @nids; -print STDERR "running fix...\n"; -open NIDS, "fix $fixfile |" or die "cannot run fix"; +print STDERR "instantiating Nix expression...\n"; +open NIDS, "nix-instantiate $nixfile |" or die "cannot run nix-instantiate"; while () { chomp; die unless /^\//; @@ -71,13 +67,13 @@ while () { close NIDS; -# Realise the Nix expression. +# Realise the store expression. print STDERR "creating archives...\n"; -system "nix --install -v @nids > /dev/null"; -if ($?) { die "`nix --install' failed"; } +system "nix-store --realise -v @nids > /dev/null"; +if ($?) { die "`nix --realise' failed"; } my @narpaths; -open NIDS, "nix --query --list @nids |" or die "cannot run nix"; +open NIDS, "nix-store --query --list @nids |" or die "cannot run nix"; while () { chomp; die unless (/^\//); @@ -120,13 +116,13 @@ for (my $n = 0; $n < scalar @paths; $n++) { print MANIFEST " MD5: $hash\n"; if ($storepath =~ /\.nix$/) { - open PREDS, "nix --query --predecessors $storepath |" or die "cannot run nix"; - while () { - chomp; - die unless (/^\//); - print MANIFEST " SuccOf: $_\n"; - } - close PREDS; + open PREDS, "nix-store --query --predecessors $storepath |" or die "cannot run nix"; + while () { + chomp; + die unless (/^\//); + print MANIFEST " SuccOf: $_\n"; + } + close PREDS; } print MANIFEST "}\n"; diff --git a/src/nix-instantiate/main.cc b/src/nix-instantiate/main.cc index f63789fc4..50a4991a5 100644 --- a/src/nix-instantiate/main.cc +++ b/src/nix-instantiate/main.cc @@ -53,8 +53,8 @@ static void printNixExpr(EvalState & state, Expr e) } } - if (ATgetType(e) == AT_LIST) { - for (ATermIterator i((ATermList) e); i; ++i) + if (atMatch(m, e) >> "List" >> es) { + for (ATermIterator i(es); i; ++i) printNixExpr(state, evalExpr(state, *i)); return; } From af7e6fe22e8db606eb92c044140c00e6d8fe61cc Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 22 Nov 2003 21:12:36 +0000 Subject: [PATCH 0335/6440] * Don't use a hard-coded path. --- configure.ac | 1 + corepkgs/Makefile.am | 2 +- corepkgs/buildenv/Makefile.am | 9 +++++++++ src/nix-env/main.cc | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 corepkgs/buildenv/Makefile.am diff --git a/configure.ac b/configure.ac index bc12b9a10..8c3aecb9c 100644 --- a/configure.ac +++ b/configure.ac @@ -41,6 +41,7 @@ AC_CONFIG_FILES([Makefile corepkgs/Makefile corepkgs/fetchurl/Makefile corepkgs/nar/Makefile + corepkgs/buildenv/Makefile doc/Makefile doc/manual/Makefile ]) diff --git a/corepkgs/Makefile.am b/corepkgs/Makefile.am index e5b892bfc..2fb23f1c0 100644 --- a/corepkgs/Makefile.am +++ b/corepkgs/Makefile.am @@ -1 +1 @@ -SUBDIRS = fetchurl nar +SUBDIRS = fetchurl nar buildenv diff --git a/corepkgs/buildenv/Makefile.am b/corepkgs/buildenv/Makefile.am new file mode 100644 index 000000000..74c39199f --- /dev/null +++ b/corepkgs/buildenv/Makefile.am @@ -0,0 +1,9 @@ +install-exec-local: + $(INSTALL) -d $(datadir)/nix/corepkgs + $(INSTALL) -d $(datadir)/nix/corepkgs/buildenv + $(INSTALL_DATA) default.nix $(datadir)/nix/corepkgs/buildenv + $(INSTALL_PROGRAM) builder.pl $(datadir)/nix/corepkgs/buildenv + +include ../../substitute.mk + +EXTRA_DIST = default.nix builder.pl diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index a8377582c..b3c38616e 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -164,7 +164,7 @@ void switchLink(Path link, Path target) void createUserEnv(EvalState & state, const DrvInfos & drvs) { /* Get the environment builder expression. */ - Expr envBuilder = parseExprFromFile("/home/eelco/nix/corepkgs/buildenv"); /* !!! */ + Expr envBuilder = parseExprFromFile(nixDataDir + "/nix/corepkgs/buildenv"); /* !!! */ /* Construct the whole top level derivation. */ ATermList inputs = ATempty; From 60e86b124f09763b1f0f55fc4c635a255bd028d2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 24 Nov 2003 08:20:49 +0000 Subject: [PATCH 0336/6440] * Get rid of tab characters. --- scripts/nix-pull.in | 114 ++++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index 40e7d62f3..4cb6409d7 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -27,7 +27,7 @@ while () { chomp; if (/^\s*(\S+)\s*(\#.*)?$/) { my $url = $1; - $url =~ s/\/$//; + $url =~ s/\/$//; print "obtaining list of Nix archives at $url...\n"; @@ -36,70 +36,70 @@ while () { open MANIFEST, "<$manifest"; - my $inside = 0; + my $inside = 0; - my $storepath; - my $narname; - my $hash; - my @preds; + my $storepath; + my $narname; + my $hash; + my @preds; while () { - chomp; - s/\#.*$//g; - next if (/^$/); + chomp; + s/\#.*$//g; + next if (/^$/); - if (!$inside) { - if (/^\{$/) { - $inside = 1; - undef $storepath; - undef $narname; - undef $hash; - @preds = (); - } - else { die "bad line: $_"; } - } else { - if (/^\}$/) { - $inside = 0; - my $fullurl = "$url/$narname"; - print "$storepath\n"; + if (!$inside) { + if (/^\{$/) { + $inside = 1; + undef $storepath; + undef $narname; + undef $hash; + @preds = (); + } + else { die "bad line: $_"; } + } else { + if (/^\}$/) { + $inside = 0; + my $fullurl = "$url/$narname"; + print "$storepath\n"; - # Construct a Fix expression that fetches and unpacks a - # Nix archive from the network. - my $fetch = - "App(IncludeFix(\"fetchurl/fetchurl.fix\"), " . - "[(\"url\", \"$fullurl\"), (\"md5\", \"$hash\")])"; - my $fixexpr = - "App(IncludeFix(\"nar/unnar.fix\"), " . - "[ (\"nar\", $fetch)" . - ", (\"outPath\", \"$storepath\")" . - "])"; - - if (!$first) { $fullexpr .= "," }; - $first = 0; - $fullexpr .= $fixexpr; # !!! O(n^2)? + # Construct a Fix expression that fetches and unpacks a + # Nix archive from the network. + my $fetch = + "App(IncludeFix(\"fetchurl/fetchurl.fix\"), " . + "[(\"url\", \"$fullurl\"), (\"md5\", \"$hash\")])"; + my $fixexpr = + "App(IncludeFix(\"nar/unnar.fix\"), " . + "[ (\"nar\", $fetch)" . + ", (\"outPath\", \"$storepath\")" . + "])"; + + if (!$first) { $fullexpr .= "," }; + $first = 0; + $fullexpr .= $fixexpr; # !!! O(n^2)? - push @srcpaths, $storepath; + push @srcpaths, $storepath; - foreach my $p (@preds) { - push @sucs, $p; - push @sucs, $storepath; - } + foreach my $p (@preds) { + push @sucs, $p; + push @sucs, $storepath; + } - } - elsif (/^\s*StorePath:\s*(\/\S+)\s*$/) { - $storepath = $1; - } - elsif (/^\s*NarName:\s*(\S+)\s*$/) { - $narname = $1; - } - elsif (/^\s*MD5:\s*(\S+)\s*$/) { - $hash = $1; - } - elsif (/^\s*SuccOf:\s*(\/\S+)\s*$/) { - push @preds, $1; - } - else { die "bad line: $_"; } - } + } + elsif (/^\s*StorePath:\s*(\/\S+)\s*$/) { + $storepath = $1; + } + elsif (/^\s*NarName:\s*(\S+)\s*$/) { + $narname = $1; + } + elsif (/^\s*MD5:\s*(\S+)\s*$/) { + $hash = $1; + } + elsif (/^\s*SuccOf:\s*(\/\S+)\s*$/) { + push @preds, $1; + } + else { die "bad line: $_"; } + } } close MANIFEST; From c9cb1fa21f4454a214f4cd62aef2ccc35a8f89af Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 24 Nov 2003 09:24:52 +0000 Subject: [PATCH 0337/6440] * Bug fix in path invalidation. * More consistency checks. --- src/libstore/store.cc | 64 +++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/src/libstore/store.cc b/src/libstore/store.cc index 4c6b8bbec..2e066a3d4 100644 --- a/src/libstore/store.cc +++ b/src/libstore/store.cc @@ -243,6 +243,16 @@ bool isValidPath(const Path & path) } +static void setOrClearStrings(Transaction & txn, + TableId table, const string & key, const Strings & value) +{ + if (value.size() > 0) + nixDB.setStrings(txn, table, key, value); + else + nixDB.delPair(txn, table, key); +} + + static void invalidatePath(const Path & path, Transaction & txn) { debug(format("unregistering path `%1%'") % path); @@ -263,11 +273,10 @@ static void invalidatePath(const Path & path, Transaction & txn) for (Paths::iterator i = revs.begin(); i != revs.end(); ++i) { Paths subs; nixDB.queryStrings(txn, dbSubstitutes, *i, subs); - remove(subs.begin(), subs.end(), path); - if (subs.size() > 0) - nixDB.setStrings(txn, dbSubstitutes, *i, subs); - else - nixDB.delPair(txn, dbSubstitutes, *i); + if (find(subs.begin(), subs.end(), path) == subs.end()) + throw Error("integrity error in substitutes mapping"); + subs.remove(path); + setOrClearStrings(txn, dbSubstitutes, *i, subs); } nixDB.delPair(txn, dbSubstitutesRev, path); } @@ -368,6 +377,8 @@ void verifyStore() validPaths.insert(path); } + /* Check that the values of the successor mappings are valid + paths. */ Paths sucs; nixDB.enumTable(txn, dbSuccessors, sucs); for (Paths::iterator i = sucs.begin(); i != sucs.end(); ++i) { @@ -382,6 +393,8 @@ void verifyStore() } } + /* Check that the keys of the reverse successor mappings are valid + paths. */ Paths rsucs; nixDB.enumTable(txn, dbSuccessorsRev, rsucs); for (Paths::iterator i = rsucs.begin(); i != rsucs.end(); ++i) { @@ -391,27 +404,32 @@ void verifyStore() } } -#if 0 - Paths sucs; - nixDB.enumTable(txn, dbSuccessors, sucs); + /* Check that the values of the substitute mappings are valid + paths. */ + Paths subs; + nixDB.enumTable(txn, dbSubstitutes, subs); + for (Paths::iterator i = subs.begin(); i != subs.end(); ++i) { + Paths subPaths, subPaths2; + nixDB.queryStrings(txn, dbSubstitutes, *i, subPaths); + for (Paths::iterator j = subPaths.begin(); j != subPaths.end(); ++j) + if (validPaths.find(*j) == validPaths.end()) + debug(format("found substitute mapping to non-existent path `%1%'") % *j); + else + subPaths2.push_back(*j); + if (subPaths.size() != subPaths2.size()) + setOrClearStrings(txn, dbSubstitutes, *i, subPaths2); + } - for (Paths::iterator i = sucs.begin(); i != sucs.end(); i++) { - Path srcPath = *i; - - Path sucPath; - if (!nixDB.queryString(txn, dbSuccessors, srcPath, sucPath)) abort(); - - Paths revs; - nixDB.queryStrings(txn, dbSuccessorsRev, sucPath, revs); - - if (find(revs.begin(), revs.end(), srcPath) == revs.end()) { - debug(format("reverse successor mapping from `%1%' to `%2%' missing") - % srcPath % sucPath); - revs.push_back(srcPath); - nixDB.setStrings(txn, dbSuccessorsRev, sucPath, revs); + /* Check that the keys of the reverse substitute mappings are + valid paths. */ + Paths rsubs; + nixDB.enumTable(txn, dbSubstitutesRev, rsubs); + for (Paths::iterator i = rsubs.begin(); i != rsubs.end(); ++i) { + if (validPaths.find(*i) == validPaths.end()) { + debug(format("found reverse substitute mapping for non-existent path `%1%'") % *i); + nixDB.delPair(txn, dbSubstitutesRev, *i); } } -#endif txn.commit(); } From 496934a99ce509ac94a99a938d7d79d1b38461ef Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 24 Nov 2003 09:25:08 +0000 Subject: [PATCH 0338/6440] * Fix nix-pull. --- scripts/nix-pull.in | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index 4cb6409d7..ad21b6f8a 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -18,7 +18,6 @@ my @subs; my @sucs; my $fullexpr = "["; -my $first = 1; open CONFFILE, "<$conffile"; @@ -63,20 +62,15 @@ while () { my $fullurl = "$url/$narname"; print "$storepath\n"; - # Construct a Fix expression that fetches and unpacks a + # Construct a Nix expression that fetches and unpacks a # Nix archive from the network. my $fetch = - "App(IncludeFix(\"fetchurl/fetchurl.fix\"), " . - "[(\"url\", \"$fullurl\"), (\"md5\", \"$hash\")])"; - my $fixexpr = - "App(IncludeFix(\"nar/unnar.fix\"), " . - "[ (\"nar\", $fetch)" . - ", (\"outPath\", \"$storepath\")" . - "])"; - - if (!$first) { $fullexpr .= "," }; - $first = 0; - $fullexpr .= $fixexpr; # !!! O(n^2)? + "(import @datadir@/nix/corepkgs/fetchurl) " . + "{url = $fullurl; md5 = \"$hash\"; system = \"@host@\"}"; + my $nixexpr = + "((import @datadir@/nix/corepkgs/nar/unnar.nix) " . + "{narFile = ($fetch); outPath = \"$storepath\"; system = \"@host@\"}) "; + $fullexpr .= $nixexpr; # !!! O(n^2)? push @srcpaths, $storepath; @@ -110,9 +104,9 @@ while () { $fullexpr .= "]"; -# Instantiate Nix expressions from the Fix expressions we created above. -print STDERR "running fix...\n"; -my $pid = open2(\*READ, \*WRITE, "fix -") or die "cannot run fix"; +# Instantiate store expressions from the Nix expressions we created above. +print STDERR "instantiating Nix expression...\n"; +my $pid = open2(\*READ, \*WRITE, "nix-instantiate -") or die "cannot run nix-instantiate"; print WRITE $fullexpr; close WRITE; @@ -128,16 +122,16 @@ while () { } waitpid $pid, 0; -$? == 0 or die "fix failed"; +$? == 0 or die "nix-instantiate failed"; # Register all substitutes. print STDERR "registering substitutes...\n"; -system "nix --substitute @subs"; -if ($?) { die "`nix --substitute' failed"; } +system "nix-store --substitute @subs"; +if ($?) { die "`nix-store --substitute' failed"; } # Register all successors. print STDERR "registering successors...\n"; -system "nix --successor @sucs"; -if ($?) { die "`nix --successor' failed"; } +system "nix-store --successor @sucs"; +if ($?) { die "`nix-store --successor' failed"; } From b8572678930568efcf0b44523e6a2a65afef7c43 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 24 Nov 2003 11:01:19 +0000 Subject: [PATCH 0339/6440] * Allow the top-level expression to be a derivation. * Hack: `nix-env -i *' installs all available derivations. --- src/nix-env/main.cc | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index b3c38616e..3c72a7950 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -53,15 +53,17 @@ bool parseDerivations(EvalState & state, Expr e, DrvInfos & drvs) { ATMatcher m; ATermList es; + DrvInfo drv; e = evalExpr(state, e); - - if (atMatch(m, e) >> "Attrs") { + + if (parseDerivation(state, e, drv)) + drvs[drv.drvPath] = drv; + + else if (atMatch(m, e) >> "Attrs") { ATermMap drvMap; queryAllAttrs(e, drvMap); - for (ATermIterator i(drvMap.keys()); i; ++i) { - DrvInfo drv; debug(format("evaluating attribute `%1%'") % *i); if (parseDerivation(state, drvMap.get(*i), drv)) drvs[drv.drvPath] = drv; @@ -70,7 +72,6 @@ bool parseDerivations(EvalState & state, Expr e, DrvInfos & drvs) else if (atMatch(m, e) >> "List" >> es) { for (ATermIterator i(es); i; ++i) { - DrvInfo drv; debug(format("evaluating list element")); if (parseDerivation(state, *i, drv)) drvs[drv.drvPath] = drv; @@ -243,14 +244,18 @@ void installDerivations(EvalState & state, /* Filter out the ones we're not interested in. */ DrvInfos selectedDrvs; - for (Strings::iterator i = drvNames.begin(); - i != drvNames.end(); ++i) - { - NameMap::iterator j = nameMap.find(*i); - if (j == nameMap.end()) - throw Error(format("unknown derivation `%1%'") % *i); - else - selectedDrvs[j->second] = availDrvs[j->second]; + if (drvNames.size() > 0 && drvNames.front() == "*") { /* !!! hack */ + selectedDrvs = availDrvs; + } else { + for (Strings::iterator i = drvNames.begin(); + i != drvNames.end(); ++i) + { + NameMap::iterator j = nameMap.find(*i); + if (j == nameMap.end()) + throw Error(format("unknown derivation `%1%'") % *i); + else + selectedDrvs[j->second] = availDrvs[j->second]; + } } /* Add in the already installed derivations. */ From e7ea52d3b336e6336c801eb8f868c0b8dd464910 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 24 Nov 2003 11:11:40 +0000 Subject: [PATCH 0340/6440] * One-click installation :-) The script nix-install-package takes a `Nix package file' (which contains one or more derivations, along with URLs of Nix caches), unpacks it, pulls the caches, and installs the derivations in the user's environment. For best results, associate the command `xterm -e /nix/bin/nix-install-package' with the MIME type `application/x-nix-package' and visit http://losser.st-lab.cs.uu.nl/~eelco/test/. --- scripts/Makefile.am | 7 +- scripts/nix-install-package.in | 37 +++++++++ scripts/nix-pull.in | 140 ++++++++++++++++++--------------- 3 files changed, 117 insertions(+), 67 deletions(-) create mode 100644 scripts/nix-install-package.in diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 35bb926af..94df32b24 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -1,5 +1,6 @@ bin_SCRIPTS = nix-collect-garbage \ - nix-pull nix-push nix-prefetch-url + nix-pull nix-push nix-prefetch-url \ + nix-install-package noinst_SCRIPTS = nix-profile.sh @@ -12,8 +13,8 @@ install-exec-local: include ../substitute.mk -EXTRA_DIST = nix-switch.in nix-collect-garbage.in \ +EXTRA_DIST = nix-collect-garbage.in \ nix-pull.in nix-push.in nix-profile.sh.in \ - nix-prefetch-url.in \ + nix-prefetch-url.in nix-install-package.in \ prebuilts.conf diff --git a/scripts/nix-install-package.in b/scripts/nix-install-package.in new file mode 100644 index 000000000..4988606c3 --- /dev/null +++ b/scripts/nix-install-package.in @@ -0,0 +1,37 @@ +#! /usr/bin/perl -w + +use strict; +use POSIX qw(tmpnam); + +my $pkgfile = $ARGV[0]; +die unless defined $pkgfile; + +my $tmpdir; +do { $tmpdir = tmpnam(); } +until mkdir $tmpdir, 0777; + +# !!! remove tmpdir on exit + +print "unpacking $pkgfile in $tmpdir...\n"; +system "bunzip2 < $pkgfile | (cd $tmpdir && tar xf -)"; +die if $?; + +print "this package contains the following derivations:\n"; +system "nix-env -qsf $tmpdir/default.nix"; +die if $?; + +print "do you wish to install them (y/n)? "; +my $reply = ; +chomp $reply; +exit if (!($reply eq "y")); + +print "pulling caches...\n"; +system "nix-pull `cat $tmpdir/caches`"; +die if $?; + +print "installing package...\n"; +system "nix-env -i $tmpdir/default.nix '*'"; +die if $?; + +print "installing succeeded! (enter to continue)\n"; +; diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index ad21b6f8a..1453a46ac 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -19,86 +19,98 @@ my @sucs; my $fullexpr = "["; -open CONFFILE, "<$conffile"; -while () { +sub processURL { + my $url = shift; + $url =~ s/\/$//; + print "obtaining list of Nix archives at $url...\n"; - chomp; - if (/^\s*(\S+)\s*(\#.*)?$/) { - my $url = $1; - $url =~ s/\/$//; - - print "obtaining list of Nix archives at $url...\n"; - - system "wget --cache=off '$url'/MANIFEST -O '$manifest' 2> /dev/null"; # !!! escape - if ($?) { die "`wget' failed"; } + system "wget --cache=off '$url'/MANIFEST -O '$manifest' 2> /dev/null"; # !!! escape + if ($?) { die "`wget' failed"; } - open MANIFEST, "<$manifest"; + open MANIFEST, "<$manifest"; - my $inside = 0; + my $inside = 0; - my $storepath; - my $narname; - my $hash; - my @preds; + my $storepath; + my $narname; + my $hash; + my @preds; - while () { - chomp; - s/\#.*$//g; - next if (/^$/); + while () { + chomp; + s/\#.*$//g; + next if (/^$/); - if (!$inside) { - if (/^\{$/) { - $inside = 1; - undef $storepath; - undef $narname; - undef $hash; - @preds = (); + if (!$inside) { + if (/^\{$/) { + $inside = 1; + undef $storepath; + undef $narname; + undef $hash; + @preds = (); } - else { die "bad line: $_"; } - } else { - if (/^\}$/) { - $inside = 0; - my $fullurl = "$url/$narname"; - print "$storepath\n"; + else { die "bad line: $_"; } + } else { + if (/^\}$/) { + $inside = 0; + my $fullurl = "$url/$narname"; +# print "$storepath\n"; - # Construct a Nix expression that fetches and unpacks a - # Nix archive from the network. - my $fetch = - "(import @datadir@/nix/corepkgs/fetchurl) " . - "{url = $fullurl; md5 = \"$hash\"; system = \"@host@\"}"; - my $nixexpr = - "((import @datadir@/nix/corepkgs/nar/unnar.nix) " . - "{narFile = ($fetch); outPath = \"$storepath\"; system = \"@host@\"}) "; - $fullexpr .= $nixexpr; # !!! O(n^2)? + # Construct a Nix expression that fetches and unpacks a + # Nix archive from the network. + my $fetch = + "(import @datadir@/nix/corepkgs/fetchurl) " . + "{url = $fullurl; md5 = \"$hash\"; system = \"@host@\"}"; + my $nixexpr = + "((import @datadir@/nix/corepkgs/nar/unnar.nix) " . + "{narFile = ($fetch); outPath = \"$storepath\"; system = \"@host@\"}) "; + $fullexpr .= $nixexpr; # !!! O(n^2)? - push @srcpaths, $storepath; + push @srcpaths, $storepath; - foreach my $p (@preds) { - push @sucs, $p; - push @sucs, $storepath; - } + foreach my $p (@preds) { + push @sucs, $p; + push @sucs, $storepath; + } - } - elsif (/^\s*StorePath:\s*(\/\S+)\s*$/) { - $storepath = $1; - } - elsif (/^\s*NarName:\s*(\S+)\s*$/) { - $narname = $1; - } - elsif (/^\s*MD5:\s*(\S+)\s*$/) { - $hash = $1; - } - elsif (/^\s*SuccOf:\s*(\/\S+)\s*$/) { - push @preds, $1; - } - else { die "bad line: $_"; } } + elsif (/^\s*StorePath:\s*(\/\S+)\s*$/) { + $storepath = $1; + } + elsif (/^\s*NarName:\s*(\S+)\s*$/) { + $narname = $1; + } + elsif (/^\s*MD5:\s*(\S+)\s*$/) { + $hash = $1; + } + elsif (/^\s*SuccOf:\s*(\/\S+)\s*$/) { + push @preds, $1; + } + else { die "bad line: $_"; } } - - close MANIFEST; } + close MANIFEST; +} + + +# Obtain URLs either from the command line or from a configuration file. +if (scalar @ARGV > 0) { + while (@ARGV) { + my $url = shift @ARGV; + processURL $url; + } +} else { + open CONFFILE, "<$conffile"; + while () { + chomp; + if (/^\s*(\S+)\s*(\#.*)?$/) { + my $url = $1; + processURL $url; + } + } + close CONFFILE; } $fullexpr .= "]"; From 604c45e960f27be9e26e44dbc85fa0f00a097670 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 24 Nov 2003 12:10:16 +0000 Subject: [PATCH 0341/6440] * Autoconf sucks. --- substitute.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substitute.mk b/substitute.mk index c575dc0ff..1f09f73b9 100644 --- a/substitute.mk +++ b/substitute.mk @@ -5,7 +5,7 @@ -e "s^@sysconfdir\@^$(sysconfdir)^g" \ -e "s^@localstatedir\@^$(localstatedir)^g" \ -e "s^@datadir\@^$(datadir)^g" \ - -e "s^@host\@^$(host)^g" \ + -e "s^@host\@^$(host_triplet)^g" \ -e "s^@wget\@^$(wget)^g" \ < $< > $@ || rm $@ chmod +x $@ From d1d87badf6d07c9d319c555593be5c6d0bd08bb4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 24 Nov 2003 16:38:46 +0000 Subject: [PATCH 0342/6440] * Bug fix. Hmm, I thought I'd fixed this before :-| --- corepkgs/nar/unnar.sh.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/corepkgs/nar/unnar.sh.in b/corepkgs/nar/unnar.sh.in index 308135649..b0b6f9d46 100644 --- a/corepkgs/nar/unnar.sh.in +++ b/corepkgs/nar/unnar.sh.in @@ -2,5 +2,5 @@ export PATH=/bin:/usr/bin -echo "unpacking $nar to $out..." -bunzip2 < $nar | @bindir@/nix-store --restore "$out" || exit 1 +echo "unpacking $narFile to $out..." +bunzip2 < $narFile | @bindir@/nix-store --restore "$out" || exit 1 From 6e8c19714af00b8340eea6eecf1c38fc6b09f6de Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 25 Nov 2003 12:05:48 +0000 Subject: [PATCH 0343/6440] * Allow integer bindings in derivations. --- src/libexpr/eval.cc | 1 + src/libexpr/primops.cc | 7 +++++++ src/libutil/aterm.cc | 12 ++++++++++++ src/libutil/aterm.hh | 6 +++++- 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index b110c3a4a..f6634e892 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -127,6 +127,7 @@ Expr evalExpr2(EvalState & state, Expr e) if (atMatch(m, e) >> "Str" || atMatch(m, e) >> "Path" || atMatch(m, e) >> "Uri" || + atMatch(m, e) >> "Int" || atMatch(m, e) >> "Bool" || atMatch(m, e) >> "Function" || atMatch(m, e) >> "Attrs" || diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 481966af9..da6927d0f 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -89,6 +89,13 @@ static string processBinding(EvalState & state, Expr e, StoreExpr & ne) if (atMatch(m, e) >> "Bool" >> "True") return "1"; if (atMatch(m, e) >> "Bool" >> "False") return ""; + int n; + if (atMatch(m, e) >> "Int" >> n) { + ostringstream st; + st << n; + return st.str(); + } + if (atMatch(m, e) >> "Attrs" >> es) { Expr a = queryAttr(e, "type"); if (a && evalString(state, a) == "derivation") { diff --git a/src/libutil/aterm.cc b/src/libutil/aterm.cc index dc6abf9e7..fb734b3a0 100644 --- a/src/libutil/aterm.cc +++ b/src/libutil/aterm.cc @@ -81,6 +81,18 @@ ATMatcher & operator >> (ATMatcher & pos, const string & s) } +ATMatcher & operator >> (ATMatcher & pos, int & n) +{ + n = 0; + ATerm t; + pos = pos >> t; + if (failed(pos)) return pos; + if (ATgetType(t) != AT_INT) return fail(pos); + n = ATgetInt((ATermInt) t); + return pos; +} + + ATMatcher & operator >> (ATMatcher & pos, ATermList & out) { out = 0; diff --git a/src/libutil/aterm.hh b/src/libutil/aterm.hh index d38d8e3f4..577b784be 100644 --- a/src/libutil/aterm.hh +++ b/src/libutil/aterm.hh @@ -61,7 +61,7 @@ ATMatcher & atMatch(ATMatcher & pos, ATerm t); /* Get the next argument of an application. */ ATMatcher & operator >> (ATMatcher & pos, ATerm & out); -/* Get the name of the function symbol of an applicatin, or the next +/* Get the name of the function symbol of an application, or the next argument of an application as a string. */ ATMatcher & operator >> (ATMatcher & pos, string & out); @@ -69,6 +69,10 @@ ATMatcher & operator >> (ATMatcher & pos, string & out); string. */ ATMatcher & operator >> (ATMatcher & pos, const string & s); +/* Get the next argument of an application, and verify that it is a + integer. */ +ATMatcher & operator >> (ATMatcher & pos, int & n); + /* Get the next argument of an application, and verify that it is a list. */ ATMatcher & operator >> (ATMatcher & pos, ATermList & out); From c3ee8c9166e32031a61c397594be4aef127862d4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 25 Nov 2003 12:35:52 +0000 Subject: [PATCH 0344/6440] * `make dist' fix. --- src/libstore/Makefile.am | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/libstore/Makefile.am b/src/libstore/Makefile.am index b365f57c6..ad31567b3 100644 --- a/src/libstore/Makefile.am +++ b/src/libstore/Makefile.am @@ -7,11 +7,4 @@ libstore_a_SOURCES = \ AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall \ -I.. -I../../externals/inst/include -I../libutil -EXTRA_DIST = *.hh *.h test-builder-*.sh - -check_PROGRAMS = test-aterm - -test_aterm_SOURCES = test-aterm.cc -test_aterm_LDADD = libstore.a $(LDADD) ../boost/format/libformat.a \ - -L../../externals/inst/lib -ldb_cxx -lATerm - +EXTRA_DIST = *.hh *.h test-builder-*.sh test.cc From 66c115ef5faabb1bcd47ac0d375aeee39b300c43 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 25 Nov 2003 13:01:21 +0000 Subject: [PATCH 0345/6440] * More `make dist' fixes. --- src/boost/format/Makefile.am | 8 ++++---- src/libexpr/Makefile.am | 3 ++- src/libmain/Makefile.am | 4 +--- src/libstore/Makefile.am | 8 ++++---- src/libutil/Makefile.am | 5 ++--- src/nix-store/Makefile.am | 4 +--- 6 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/boost/format/Makefile.am b/src/boost/format/Makefile.am index bae92c198..d28a2b31d 100644 --- a/src/boost/format/Makefile.am +++ b/src/boost/format/Makefile.am @@ -1,8 +1,8 @@ noinst_LIBRARIES = libformat.a -libformat_a_SOURCES = format_implementation.cc free_funcs.cc parsing.cc +libformat_a_SOURCES = format_implementation.cc free_funcs.cc \ + parsing.cc exceptions.hpp feed_args.hpp format_class.hpp \ + format_fwd.hpp group.hpp internals.hpp internals_fwd.hpp \ + macros_default.hpp AM_CXXFLAGS = -Wall -I../.. - -EXTRA_DIST = exceptions.hpp feed_args.hpp format_class.hpp format_fwd.hpp \ - group.hpp internals.hpp internals_fwd.hpp macros_default.hpp diff --git a/src/libexpr/Makefile.am b/src/libexpr/Makefile.am index 71c1f89f1..a11dbbda6 100644 --- a/src/libexpr/Makefile.am +++ b/src/libexpr/Makefile.am @@ -1,6 +1,7 @@ noinst_LIBRARIES = libexpr.a -libexpr_a_SOURCES = nixexpr.cc parser.cc eval.cc primops.cc +libexpr_a_SOURCES = nixexpr.cc nixexpr.hh parser.cc parser.hh \ + eval.cc eval.hh primops.cc primops.hh nix.sdf AM_CXXFLAGS = \ -I.. -I../../externals/inst/include -I../libutil -I../libstore diff --git a/src/libmain/Makefile.am b/src/libmain/Makefile.am index 5d3144ccc..a4969ff36 100644 --- a/src/libmain/Makefile.am +++ b/src/libmain/Makefile.am @@ -1,6 +1,6 @@ noinst_LIBRARIES = libmain.a -libmain_a_SOURCES = shared.cc +libmain_a_SOURCES = shared.cc shared.hh AM_CXXFLAGS = \ -DNIX_STORE_DIR=\"$(prefix)/store\" \ @@ -8,5 +8,3 @@ AM_CXXFLAGS = \ -DNIX_STATE_DIR=\"$(localstatedir)/nix\" \ -DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \ -I.. -I../../externals/inst/include -I../libutil -I../libstore - -EXTRA_DIST = *.hh diff --git a/src/libstore/Makefile.am b/src/libstore/Makefile.am index ad31567b3..6605a76c2 100644 --- a/src/libstore/Makefile.am +++ b/src/libstore/Makefile.am @@ -1,10 +1,10 @@ noinst_LIBRARIES = libstore.a libstore_a_SOURCES = \ - store.cc storeexpr.cc normalise.cc exec.cc \ - globals.cc db.cc references.cc pathlocks.cc + store.cc store.hh storeexpr.cc storeexpr.hh \ + normalise.cc normalise.hh exec.cc exec.hh \ + globals.cc globals.hh db.cc db.hh \ + references.cc references.hh pathlocks.cc pathlocks.hh AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall \ -I.. -I../../externals/inst/include -I../libutil - -EXTRA_DIST = *.hh *.h test-builder-*.sh test.cc diff --git a/src/libutil/Makefile.am b/src/libutil/Makefile.am index 46f0a048f..362a6e55c 100644 --- a/src/libutil/Makefile.am +++ b/src/libutil/Makefile.am @@ -1,11 +1,10 @@ noinst_LIBRARIES = libutil.a -libutil_a_SOURCES = util.cc hash.cc archive.cc md5.c aterm.cc +libutil_a_SOURCES = util.cc util.hh hash.cc hash.hh \ + archive.cc archive.hh md5.c md5.h aterm.cc aterm.hh AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I.. -I../../externals/inst/include -EXTRA_DIST = *.hh *.h - check_PROGRAMS = test-aterm test_aterm_SOURCES = test-aterm.cc diff --git a/src/nix-store/Makefile.am b/src/nix-store/Makefile.am index 516d78efc..be35c5f27 100644 --- a/src/nix-store/Makefile.am +++ b/src/nix-store/Makefile.am @@ -1,6 +1,6 @@ bin_PROGRAMS = nix-store -nix_store_SOURCES = main.cc dotgraph.cc +nix_store_SOURCES = main.cc dotgraph.cc dotgraph.hh help.txt.hh nix_store_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx -lATerm @@ -21,5 +21,3 @@ install-data-local: $(INSTALL) -d $(localstatedir)/log/nix $(INSTALL) -d $(prefix)/store $(bindir)/nix-store --init - -EXTRA_DIST = *.hh From ba73f94b3bba9c19726443556b0124fe63dccee6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 25 Nov 2003 13:03:48 +0000 Subject: [PATCH 0346/6440] * Another fix. --- src/nix-store/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix-store/Makefile.am b/src/nix-store/Makefile.am index be35c5f27..3738c53ca 100644 --- a/src/nix-store/Makefile.am +++ b/src/nix-store/Makefile.am @@ -1,6 +1,6 @@ bin_PROGRAMS = nix-store -nix_store_SOURCES = main.cc dotgraph.cc dotgraph.hh help.txt.hh +nix_store_SOURCES = main.cc dotgraph.cc dotgraph.hh help.txt nix_store_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx -lATerm From 12e805cfb032e403e4dd6f410b7e3f24bbc89a98 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 25 Nov 2003 13:06:12 +0000 Subject: [PATCH 0347/6440] * Don't hardcode the path to the DocBook DTD/stylesheets. --- doc/manual/Makefile.am | 3 --- 1 file changed, 3 deletions(-) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 63464002b..262c7bcaa 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -1,6 +1,3 @@ -DOCBOOK_DTD = /nix/current/xml/dtd/docbook -DOCBOOK_XSL = /nix/current/xml/xsl/docbook - ENV = SGML_CATALOG_FILES=$(DOCBOOK_DTD)/docbook.cat XMLLINT = $(ENV) xmllint --catalogs From 6d5877ea122f1d8aede4f9b9d2ac247d1b896093 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 25 Nov 2003 13:43:48 +0000 Subject: [PATCH 0348/6440] * Use --nonet flag to prevent fetching of DTD. --- doc/manual/Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 262c7bcaa..ac991ddbf 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -1,7 +1,7 @@ ENV = SGML_CATALOG_FILES=$(DOCBOOK_DTD)/docbook.cat -XMLLINT = $(ENV) xmllint --catalogs -XSLTPROC = $(ENV) xsltproc --catalogs +XMLLINT = $(ENV) xmllint --catalogs --nonet +XSLTPROC = $(ENV) xsltproc --catalogs --nonet SOURCES = book.xml introduction.xml installation.xml nix-reference.xml \ troubleshooting.xml bugs.xml From 4da9316c8fa576cad77bf398785765e165f6865c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 25 Nov 2003 16:49:23 +0000 Subject: [PATCH 0349/6440] * Use svn-revision to construct package version. --- configure.ac | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index 8c3aecb9c..243ef392f 100644 --- a/configure.ac +++ b/configure.ac @@ -1,13 +1,13 @@ -AC_INIT(nix, "0.4") +AC_INIT(nix, "0.5") AC_CONFIG_SRCDIR(README) AC_CONFIG_AUX_DIR(config) AM_INIT_AUTOMAKE # Put the revision number in the version. -if REVISION=`svnversion $srcdir 2> /dev/null`; then - if test "$REVISION" != "exported"; then - VERSION="$VERSION-r$REVISION" - fi +if REVISION=`test -d $srcdir/.svn && svnversion $srcdir 2> /dev/null`; then + VERSION="$VERSION-r$REVISION" +elif REVISION=`cat $srcdir/svn-revision 2> /dev/null`; then + VERSION="$VERSION-r$REVISION" fi AC_PREFIX_DEFAULT(/nix) From c38ba181ede6b46f28d5a70bf0243bcd003f943b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 26 Nov 2003 10:41:21 +0000 Subject: [PATCH 0350/6440] * Configure flags to specify the location of the DocBook DTD / stylesheets. --- configure.ac | 17 +++++++++++++++++ doc/manual/Makefile.am | 10 +++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index 243ef392f..1da1ab854 100644 --- a/configure.ac +++ b/configure.ac @@ -19,6 +19,23 @@ AC_PROG_CXX AC_PROG_RANLIB AC_PATH_PROG(wget, wget) +AC_PATH_PROG(xmllint, xmllint) +AC_PATH_PROG(xsltproc, xsltproc) + +AC_ARG_WITH(docbook-catalog, AC_HELP_STRING([--with-docbook-catalog=PATH], + [path of the DocBook XML DTD]), + docbookcatalog=$withval, docbookcatalog=/docbook-dtd-missing) +AC_SUBST(docbookcatalog) + +AC_ARG_WITH(docbook-xsl, AC_HELP_STRING([--with-docbook-xsl=PATH], + [path of the DocBook XSL stylesheets]), + docbookxsl=$withval, docbookxsl=/docbook-xsl-missing) +AC_SUBST(docbookxsl) + +AC_ARG_WITH(xml-flags, AC_HELP_STRING([--xml-flags=FLAGS], + [extra flags to be passed to xmllint and xsltproc]), + xmlflags=$withval, xmlflags=) +AC_SUBST(xmlflags) AC_CHECK_LIB(pthread, pthread_mutex_init) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index ac991ddbf..87d4e87d7 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -1,7 +1,7 @@ -ENV = SGML_CATALOG_FILES=$(DOCBOOK_DTD)/docbook.cat +ENV = SGML_CATALOG_FILES=$(docbookcatalog)/docbook.cat -XMLLINT = $(ENV) xmllint --catalogs --nonet -XSLTPROC = $(ENV) xsltproc --catalogs --nonet +XMLLINT = $(ENV) $(xmllint) $(xmlflags) --catalogs +XSLTPROC = $(ENV) $(xsltproc) $(xmlflags) --catalogs SOURCES = book.xml introduction.xml installation.xml nix-reference.xml \ troubleshooting.xml bugs.xml @@ -13,10 +13,10 @@ book.is-valid: $(SOURCES) man1_MANS = nix.1 fix.1 man nix.1 fix.1: $(SOURCES) book.is-valid - $(XSLTPROC) $(DOCBOOK_XSL)/manpages/docbook.xsl book.xml + $(XSLTPROC) $(docbookxsl)/manpages/docbook.xsl book.xml book.html: $(SOURCES) book.is-valid - $(XSLTPROC) --output book.html $(DOCBOOK_XSL)/html/docbook.xsl book.xml + $(XSLTPROC) --output book.html $(docbookxsl)/html/docbook.xsl book.xml all-local: book.html From 80f8c38384605f99ac43bdd3ae637e72996ca2da Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 26 Nov 2003 10:41:59 +0000 Subject: [PATCH 0351/6440] * Typo fix. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 1da1ab854..fe5fa136e 100644 --- a/configure.ac +++ b/configure.ac @@ -32,7 +32,7 @@ AC_ARG_WITH(docbook-xsl, AC_HELP_STRING([--with-docbook-xsl=PATH], docbookxsl=$withval, docbookxsl=/docbook-xsl-missing) AC_SUBST(docbookxsl) -AC_ARG_WITH(xml-flags, AC_HELP_STRING([--xml-flags=FLAGS], +AC_ARG_WITH(xml-flags, AC_HELP_STRING([--with-xml-flags=FLAGS], [extra flags to be passed to xmllint and xsltproc]), xmlflags=$withval, xmlflags=) AC_SUBST(xmlflags) From bd0ce1a4be6612cf53d9d31f1cbe1b25085ccf75 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 26 Nov 2003 10:47:54 +0000 Subject: [PATCH 0352/6440] * Minor fix. --- doc/manual/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 87d4e87d7..8b3060ac7 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -1,4 +1,4 @@ -ENV = SGML_CATALOG_FILES=$(docbookcatalog)/docbook.cat +ENV = SGML_CATALOG_FILES=$(docbookcatalog) XMLLINT = $(ENV) $(xmllint) $(xmlflags) --catalogs XSLTPROC = $(ENV) $(xsltproc) $(xmlflags) --catalogs From 2a4bac5459f42764b39ac70f906f5dd3330a3ac5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 26 Nov 2003 11:24:13 +0000 Subject: [PATCH 0353/6440] * Refactoring. * Convert tabs to spaces. --- doc/manual/Makefile.am | 11 +- doc/manual/book.xml | 32 +- doc/manual/installation.xml | 18 +- doc/manual/introduction.xml | 338 ++++++------- ...ence.xml => nix-instantiate-reference.xml} | 6 +- doc/manual/nix-reference.xml | 444 ------------------ doc/manual/nix-store-reference.xml | 444 ++++++++++++++++++ 7 files changed, 636 insertions(+), 657 deletions(-) rename doc/manual/{fix-reference.xml => nix-instantiate-reference.xml} (86%) delete mode 100644 doc/manual/nix-reference.xml create mode 100644 doc/manual/nix-store-reference.xml diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 8b3060ac7..8e2fff208 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -3,16 +3,17 @@ ENV = SGML_CATALOG_FILES=$(docbookcatalog) XMLLINT = $(ENV) $(xmllint) $(xmlflags) --catalogs XSLTPROC = $(ENV) $(xsltproc) $(xmlflags) --catalogs -SOURCES = book.xml introduction.xml installation.xml nix-reference.xml \ +SOURCES = book.xml introduction.xml installation.xml \ + nix-store-reference.xml \ troubleshooting.xml bugs.xml book.is-valid: $(SOURCES) $(XMLLINT) --noout --valid book.xml touch $@ -man1_MANS = nix.1 fix.1 +man1_MANS = nix-store.1 nix-instantiate.1 -man nix.1 fix.1: $(SOURCES) book.is-valid +man $(MANS): $(SOURCES) book.is-valid $(XSLTPROC) $(docbookxsl)/manpages/docbook.xsl book.xml book.html: $(SOURCES) book.is-valid @@ -24,4 +25,6 @@ install-data-local: book.html $(INSTALL) -d $(datadir)/nix/manual $(INSTALL_DATA) book.html $(datadir)/nix/manual -EXTRA_DIST = $(SOURCES) book.html nix.1 fix.1 book.is-valid +EXTRA_DIST = $(SOURCES) book.html book.is-valid $(MANS) + +DISTCLEANFILES = book.html book.is-valid $(MANS) diff --git a/doc/manual/book.xml b/doc/manual/book.xml index a2035fca7..1dc69d004 100644 --- a/doc/manual/book.xml +++ b/doc/manual/book.xml @@ -5,8 +5,8 @@ [ - - + + ]> @@ -28,34 +28,10 @@ &introduction; &installation; - - A Guided Tour - - - - - - Nix Syntax and Semantics - - - - - - Fix Language Reference - - - - - - Writing Builders - - - - Command Reference - &nix-reference; - &fix-reference; + &nix-store-reference; + &nix-instantiate-reference; &troubleshooting; diff --git a/doc/manual/installation.xml b/doc/manual/installation.xml index bec9ebb21..f9bd0a742 100644 --- a/doc/manual/installation.xml +++ b/doc/manual/installation.xml @@ -20,8 +20,8 @@ Nix can be obtained from its Subversion - repository. For example, the following command will check out + url='http://losser.st-lab.cs.uu.nl:12080/repos/trace/nix/trunk'>Subversion + repository. For example, the following command will check out the latest revision into a directory called nix: @@ -30,11 +30,11 @@ $ svn checkout http://losser.st-lab.cs.uu.nl:12080/repos/trace/nix/trunk nix Likewise, specific releases can be obtained from the tags - directory of the repository. If you don't have Subversion, you + url='http://losser.st-lab.cs.uu.nl:12080/repos/trace/nix/tags'>tags + directory of the repository. If you don't have Subversion, you can download a compressed - tar-file of the latest revision of the repository. + url='http://losser.st-lab.cs.uu.nl:12080/dist/trace/'>compressed + tar-file of the latest revision of the repository. @@ -63,9 +63,9 @@ $ make install - It is advisable not to change the installation - prefix, since doing so will in all likelihood make it impossible to use - derivates built on other systems. + It is advisable not to change the installation + prefix, since doing so will in all likelihood make it impossible to use + derivates built on other systems. diff --git a/doc/manual/introduction.xml b/doc/manual/introduction.xml index 5eea76459..feabeef9c 100644 --- a/doc/manual/introduction.xml +++ b/doc/manual/introduction.xml @@ -15,74 +15,74 @@ Build management - Build management tools are used to perform software - builds, that is, the construction of derived products - (derivates)) such as executable programs from - source code. A commonly used build tool is Make, which is a standard - tool on Unix systems. These tools have to deal with several issues: - + Build management tools are used to perform software + builds, that is, the construction of derived products + (derivates)) such as executable programs from + source code. A commonly used build tool is Make, which is a standard + tool on Unix systems. These tools have to deal with several issues: + - - - Efficiency. Since building large systems - can take a substantial amount of time, it is desirable that build - steps that have been performed in the past are not repeated - unnecessarily, i.e., if a new build differs from a previous build - only with respect to certain sources, then only the build steps - that (directly or indirectly) depend on - those sources should be redone. - - + + + Efficiency. Since building large systems + can take a substantial amount of time, it is desirable that build + steps that have been performed in the past are not repeated + unnecessarily, i.e., if a new build differs from a previous build + only with respect to certain sources, then only the build steps + that (directly or indirectly) depend on + those sources should be redone. + + - - - Correctness is this context means that the - derivates produced by a build are always consistent with the - sources, that is, they are equal to what we would get if we were - to build the derivates from those sources. This requirement is - trivially met when we do a full, unconditional build, but is far - from trivial under the requirement of efficiency, since it is not - easy to determine which derivates are affected by a change to a - source. - - + + + Correctness is this context means that the + derivates produced by a build are always consistent with the + sources, that is, they are equal to what we would get if we were + to build the derivates from those sources. This requirement is + trivially met when we do a full, unconditional build, but is far + from trivial under the requirement of efficiency, since it is not + easy to determine which derivates are affected by a change to a + source. + + - - - Variability is the property that a software - system can be built in a (potentially large) number of variants. - Variation exists both in time---the - evolution of different versions of an artifact---and in - space---the artifact might have - configuration options that lead to variants that differ in the - features they support (for example, a system might be built with - or without debugging information). - + + + Variability is the property that a software + system can be built in a (potentially large) number of variants. + Variation exists both in time---the + evolution of different versions of an artifact---and in + space---the artifact might have + configuration options that lead to variants that differ in the + features they support (for example, a system might be built with + or without debugging information). + - - Build managers historically have had good support for variation - in time (rebuilding the system in an intelligent way when sources - change is one of the primary reasons to use a build manager), but - not always for variation in space. For example, - make will not automatically ensure that - variant builds are properly isolated from each other (they will - in fact overwrite each other unless special precautions are - taken). - - + + Build managers historically have had good support for variation + in time (rebuilding the system in an intelligent way when sources + change is one of the primary reasons to use a build manager), but + not always for variation in space. For example, + make will not automatically ensure that + variant builds are properly isolated from each other (they will + in fact overwrite each other unless special precautions are + taken). + + - - - High-level system modelling language. The - language in which one describes what and how derivates are to be - produced should have sufficient abstraction facilities to make it - easy to specify the derivation of even very large systems. Also, - the language should be modular to enable - components from possible different sources to be easily combined. - - + + + High-level system modelling language. The + language in which one describes what and how derivates are to be + produced should have sufficient abstraction facilities to make it + easy to specify the derivation of even very large systems. Also, + the language should be modular to enable + components from possible different sources to be easily combined. + + - + @@ -91,33 +91,33 @@ Package management - After software has been built, is must also be - deployed in the intended target environment, e.g., - the user's workstation. Examples include the Red Hat package manager - (RPM), Microsoft's MSI, and so on. Here also we have several issues to - contend with: - - - - The creation of packages from some formal - description of what artifacts should be distributed in the - package. - - - - - The deployment of packages, that is, the - mechanism by which we get them onto the intended target - environment. This can be as simple as copying a file, but - complexity comes from the wide range of possible installation - media (such as a network install), and the scalability of the - process (if a program must be installed on a thousand systems, we - do not want to visit each system and perform some manual steps to - install the program on that system; that is, the complexity for - the system administrator should be constant, not linear). - - - + After software has been built, is must also be + deployed in the intended target environment, e.g., + the user's workstation. Examples include the Red Hat package manager + (RPM), Microsoft's MSI, and so on. Here also we have several issues to + contend with: + + + + The creation of packages from some formal + description of what artifacts should be distributed in the + package. + + + + + The deployment of packages, that is, the + mechanism by which we get them onto the intended target + environment. This can be as simple as copying a file, but + complexity comes from the wide range of possible installation + media (such as a network install), and the scalability of the + process (if a program must be installed on a thousand systems, we + do not want to visit each system and perform some manual steps to + install the program on that system; that is, the complexity for + the system administrator should be constant, not linear). + + + @@ -136,95 +136,95 @@ - - Reliable dependencies. Builds of file system - objects depend on other file system object, such as source files, - tools, and so on. We would like to ensure that a build does not - refer to any objects that have not been declared as inputs for that - build. This is important for several reasons. First, if any of the - inputs change, we need to rebuild the things that depend on them to - maintain consistency between sources and derivates. Second, when we - deploy file system objects (that is, copy them - to a different system), we want to be certain that we copy everything - that we need. - + + Reliable dependencies. Builds of file system + objects depend on other file system object, such as source files, + tools, and so on. We would like to ensure that a build does not + refer to any objects that have not been declared as inputs for that + build. This is important for several reasons. First, if any of the + inputs change, we need to rebuild the things that depend on them to + maintain consistency between sources and derivates. Second, when we + deploy file system objects (that is, copy them + to a different system), we want to be certain that we copy everything + that we need. + - - Nix ensures this by building and storing file system objects in paths - that are infeasible to predict in advance. For example, the - artifacts of a package X might be stored in - /nix/store/d58a0606ed616820de291d594602665d-X, - rather than in, say, /usr/lib. The path - component d58a... is actually a cryptographic - hash of all the inputs (i.e., sources, requisites, and build flags) - used in building X, and as such is very fragile: - any change to the inputs will change the hash. Therefore it is not - sensible to hard-code such a path into the build - scripts of a package Y that uses - X (as does happen with fixed paths - such as /usr/lib). Rather, the build script of - package Y is parameterised with the actual - location of X, which is supplied by the Nix - system. - + + Nix ensures this by building and storing file system objects in paths + that are infeasible to predict in advance. For example, the + artifacts of a package X might be stored in + /nix/store/d58a0606ed616820de291d594602665d-X, + rather than in, say, /usr/lib. The path + component d58a... is actually a cryptographic + hash of all the inputs (i.e., sources, requisites, and build flags) + used in building X, and as such is very fragile: + any change to the inputs will change the hash. Therefore it is not + sensible to hard-code such a path into the build + scripts of a package Y that uses + X (as does happen with fixed paths + such as /usr/lib). Rather, the build script of + package Y is parameterised with the actual + location of X, which is supplied by the Nix + system. + - - Support for variability. - - - - As stated above, the path name of a file system object contain a - cryptographic hash of all inputs involved in building it. A change to - any of the inputs will cause the hash to change--and by extension, - the path name. These inputs include both sources (variation in time) - and configuration options (variation in space). Therefore variants - of the same package don't clash---they can co-exist peacefully within - the same file system. So thanks to Nix's mechanism for reliably - dealing with dependencies, we obtain management of variants for free - (or, to quote Simon Peyton-Jone, it's not free, but it has already - been paid for). - + + Support for variability. + + + + As stated above, the path name of a file system object contain a + cryptographic hash of all inputs involved in building it. A change to + any of the inputs will cause the hash to change--and by extension, + the path name. These inputs include both sources (variation in time) + and configuration options (variation in space). Therefore variants + of the same package don't clash---they can co-exist peacefully within + the same file system. So thanks to Nix's mechanism for reliably + dealing with dependencies, we obtain management of variants for free + (or, to quote Simon Peyton-Jone, it's not free, but it has already + been paid for). + - - Transparent source/binary deployment. - + + Transparent source/binary deployment. + - - Easy configuration duplication. - + + Easy configuration duplication. + - - Automatic storage management. - + + Automatic storage management. + - - Atomic upgrades and rollbacks. - + + Atomic upgrades and rollbacks. + - - Support for many simultaneous configurations. - + + Support for many simultaneous configurations. + - - Portability. Nix is quite portable. Contrary - to build systems like those in, e.g., Vesta and ClearCase [sic?], it - does not rely on operating system extensions. - + + Portability. Nix is quite portable. Contrary + to build systems like those in, e.g., Vesta and ClearCase [sic?], it + does not rely on operating system extensions. + @@ -236,20 +236,20 @@ - - Build management. In principle it is already - possible to do build management using Fix (by writing builders that - perform appropriate build steps), but the Fix language is not yet - powerful enough to make this pleasant. The Maak build manager - should be retargeted to produce Nix expressions, or alternatively, - extend Fix with Maak's semantics and concrete syntax (since Fix needs - a concrete syntax anyway). Another interesting idea is to write a - make implementation that uses Nix as a back-end to - support legacy - build files. - + + Build management. In principle it is already + possible to do build management using Fix (by writing builders that + perform appropriate build steps), but the Fix language is not yet + powerful enough to make this pleasant. The Maak build manager + should be retargeted to produce Nix expressions, or alternatively, + extend Fix with Maak's semantics and concrete syntax (since Fix needs + a concrete syntax anyway). Another interesting idea is to write a + make implementation that uses Nix as a back-end to + support legacy + build files. + diff --git a/doc/manual/fix-reference.xml b/doc/manual/nix-instantiate-reference.xml similarity index 86% rename from doc/manual/fix-reference.xml rename to doc/manual/nix-instantiate-reference.xml index aac1be648..2e2749e43 100644 --- a/doc/manual/fix-reference.xml +++ b/doc/manual/nix-instantiate-reference.xml @@ -1,6 +1,6 @@ - fix + nix-instantiate generate Nix expressions from a high-level description @@ -8,8 +8,8 @@ fix - - + + files diff --git a/doc/manual/nix-reference.xml b/doc/manual/nix-reference.xml deleted file mode 100644 index d9c78ff07..000000000 --- a/doc/manual/nix-reference.xml +++ /dev/null @@ -1,444 +0,0 @@ - - - nix - manipulate or query the Nix store - - - - - nix - - - - - - - - - - - - - operation - options - arguments - - - - - Description - - - The command nix provides access to the Nix store. This - is the (set of) path(s) where Nix expressions and the file system objects - built by them are stored. - - - - nix has many subcommands called - operations. These are individually documented - below. Exactly one operation must always be provided. - - - - - - Common Options - - - In this section the options that are common to all Nix operations are - listed. These options are allowed for every subcommand (although they - may not always have an effect). - - - - - - - - - Indicates that any identifier arguments to the operation are paths - in the store rather than identifiers. - - - - - - - - - Increases the level of verbosity of diagnostic messages printed on - standard error. For each Nix operation, the information printed on - standard output is well-defined and specified below in the - respective sections. Any diagnostic information is printed on - standard error, never on standard output. - - - - This option may be specified repeatedly. Currently, the following - verbosity levels exist: - - - - - 0 - - - Print error messages only. - - - - - 1 - - - Print informational messages. - - - - - 2 - - - Print even more informational messages. - - - - - 3 - - - Print messages that should only be useful for debugging. - - - - - 4 - - - Vomit mode: print vast amounts of debug - information. - - - - - - - - - - - - - Specifies that in case of a build failure, the temporary directory - (usually in /tmp) in which the build takes - place should not be deleted. The path of the build directory is - printed as an informational message. - - - - - - - - - - - - - Operation <option>--install</option> - - - Synopsis - - nix - - - - - ids - - - - - Description - - - The operation realises the Nix expressions - identified by ids in the file system. If - these expressions are derivation expressions, they are first - normalised. That is, their target paths are are built, unless a normal - form is already known. - - - - The identifiers of the normal forms of the given Nix expressions are - printed on standard output. - - - - - - - - - - - Operation <option>--delete</option> - - - Synopsis - - nix - - - - - paths - - - - - Description - - - The operation unconditionally deletes the - paths paths from the Nix store. It is an - error to attempt to delete paths outside of the store. - - - - - This operation should almost never be called directly, since no - attempt is made to verify that no references exist to the paths to - be deleted. Therefore, careless deletion can result in an - inconsistent system. Deletion of paths in the store is done by the - garbage collector (which uses to delete - unreferenced paths). - - - - - - - - - - - - - Operation <option>--query</option> - - - Synopsis - - nix - - - - - - - - - - - - - - - - - - - - - - - args - - - - - Description - - - The operation displays various bits of - information about Nix expressions or paths in the store. The queries - are described in . At most one query - can be specified; the default query is . - - - - - - Queries - - - - - - - - Prints out the target paths of the Nix expressions indicated by - the identifiers args. In the case of - a derivation expression, these are the paths that will be - produced by the builder of the expression. In the case of a - slice expression, these are the root paths (which are generally - the paths that were produced by the builder of the derivation - expression of which the slice is a normal form). - - - - This query has one option: - - - - - - - - - Causes the target paths of the normal - forms of the expressions to be printed, rather - than the target paths of the expressions themselves. - - - - - - - - - - - - - - Prints out the requisite paths of the Nix expressions indicated - by the identifiers args. The - requisite paths of a Nix expression are the paths that need to be - present in the system to be able to realise the expression. That - is, they form the closure of the expression - in the file system (i.e., no path in the set of requisite paths - points to anything outside the set of requisite paths). - - - - The notion of requisite paths is very useful when one wants to - distribute Nix expressions. Since they form a closure, they are - the only paths one needs to distribute to another system to be - able to realise the expression on the other system. - - - - This query is generally used to implement various kinds of - distribution. A source distribution is - obtained by distributing the requisite paths of a derivation - expression. A binary distribution is - obtained by distributing the requisite paths of a slice - expression (i.e., the normal form of a derivation expression; you - can directly specify the identifier of the slice expression, or - use and specify the identifier of a - derivation expression). A cache - distribution is obtained by distributing the - requisite paths of a derivation expression and specifying the - option . This will include - not just the paths of a source and binary distribution, but also - all expressions and paths of subterms of the source. This is - useful if one wants to realise on the target system a Nix - expression that is similar but not quite the same as the one - being distributed, since any common subterms will be reused. - - - - This query has a number of options: - - - - - - - - - Causes the requisite paths of the normal - forms of the expressions to be printed, rather - than the requisite paths of the expressions themselves. - - - - - - - - - Excludes the paths of Nix expressions. This causes the - closure property to be lost, that is, the resulting set of - paths is not enough to ensure realisibility. - - - - - - - - - Also include the requisites of successors (normal forms). - Only the requisites of known - successors are included, i.e., the normal forms of - derivation expressions that have never been normalised will - not be included. - - - - Note that not just the successor of a derivation expression - will be included, but also the successors of all input - expressions of that derivation expression. I.e., all - normal forms of subterms involved in the normalisation of - the top-level term are included. - - - - - - - - - - - - - - For each identifier in args, prints - all expansions of that identifier, that is, all paths whose - current content matches the identifier. - - - - - - - - - Prints a graph of the closure of the expressions identified by - args in the format of the - dot tool of AT&T's GraphViz package. - - - - - - - - - - - - - - - diff --git a/doc/manual/nix-store-reference.xml b/doc/manual/nix-store-reference.xml new file mode 100644 index 000000000..686fe4c15 --- /dev/null +++ b/doc/manual/nix-store-reference.xml @@ -0,0 +1,444 @@ + + + nix-store + manipulate or query the Nix store + + + + + nix-store + + + + + + + + + + + + + operation + options + arguments + + + + + Description + + + The command nix provides access to the Nix store. This + is the (set of) path(s) where Nix expressions and the file system objects + built by them are stored. + + + + nix has many subcommands called + operations. These are individually documented + below. Exactly one operation must always be provided. + + + + + + Common Options + + + In this section the options that are common to all Nix operations are + listed. These options are allowed for every subcommand (although they + may not always have an effect). + + + + + + + + + Indicates that any identifier arguments to the operation are paths + in the store rather than identifiers. + + + + + + + + + Increases the level of verbosity of diagnostic messages printed on + standard error. For each Nix operation, the information printed on + standard output is well-defined and specified below in the + respective sections. Any diagnostic information is printed on + standard error, never on standard output. + + + + This option may be specified repeatedly. Currently, the following + verbosity levels exist: + + + + + 0 + + + Print error messages only. + + + + + 1 + + + Print informational messages. + + + + + 2 + + + Print even more informational messages. + + + + + 3 + + + Print messages that should only be useful for debugging. + + + + + 4 + + + Vomit mode: print vast amounts of debug + information. + + + + + + + + + + + + + Specifies that in case of a build failure, the temporary directory + (usually in /tmp) in which the build takes + place should not be deleted. The path of the build directory is + printed as an informational message. + + + + + + + + + + + + + Operation <option>--install</option> + + + Synopsis + + nix + + + + + ids + + + + + Description + + + The operation realises the Nix expressions + identified by ids in the file system. If + these expressions are derivation expressions, they are first + normalised. That is, their target paths are are built, unless a normal + form is already known. + + + + The identifiers of the normal forms of the given Nix expressions are + printed on standard output. + + + + + + + + + + + Operation <option>--delete</option> + + + Synopsis + + nix + + + + + paths + + + + + Description + + + The operation unconditionally deletes the + paths paths from the Nix store. It is an + error to attempt to delete paths outside of the store. + + + + + This operation should almost never be called directly, since no + attempt is made to verify that no references exist to the paths to + be deleted. Therefore, careless deletion can result in an + inconsistent system. Deletion of paths in the store is done by the + garbage collector (which uses to delete + unreferenced paths). + + + + + + + + + + + + + Operation <option>--query</option> + + + Synopsis + + nix + + + + + + + + + + + + + + + + + + + + + + + args + + + + + Description + + + The operation displays various bits of + information about Nix expressions or paths in the store. The queries + are described in . At most one query + can be specified; the default query is . + + + + + + Queries + + + + + + + + Prints out the target paths of the Nix expressions indicated by + the identifiers args. In the case of + a derivation expression, these are the paths that will be + produced by the builder of the expression. In the case of a + slice expression, these are the root paths (which are generally + the paths that were produced by the builder of the derivation + expression of which the slice is a normal form). + + + + This query has one option: + + + + + + + + + Causes the target paths of the normal + forms of the expressions to be printed, rather + than the target paths of the expressions themselves. + + + + + + + + + + + + + + Prints out the requisite paths of the Nix expressions indicated + by the identifiers args. The + requisite paths of a Nix expression are the paths that need to be + present in the system to be able to realise the expression. That + is, they form the closure of the expression + in the file system (i.e., no path in the set of requisite paths + points to anything outside the set of requisite paths). + + + + The notion of requisite paths is very useful when one wants to + distribute Nix expressions. Since they form a closure, they are + the only paths one needs to distribute to another system to be + able to realise the expression on the other system. + + + + This query is generally used to implement various kinds of + distribution. A source distribution is + obtained by distributing the requisite paths of a derivation + expression. A binary distribution is + obtained by distributing the requisite paths of a slice + expression (i.e., the normal form of a derivation expression; you + can directly specify the identifier of the slice expression, or + use and specify the identifier of a + derivation expression). A cache + distribution is obtained by distributing the + requisite paths of a derivation expression and specifying the + option . This will include + not just the paths of a source and binary distribution, but also + all expressions and paths of subterms of the source. This is + useful if one wants to realise on the target system a Nix + expression that is similar but not quite the same as the one + being distributed, since any common subterms will be reused. + + + + This query has a number of options: + + + + + + + + + Causes the requisite paths of the normal + forms of the expressions to be printed, rather + than the requisite paths of the expressions themselves. + + + + + + + + + Excludes the paths of Nix expressions. This causes the + closure property to be lost, that is, the resulting set of + paths is not enough to ensure realisibility. + + + + + + + + + Also include the requisites of successors (normal forms). + Only the requisites of known + successors are included, i.e., the normal forms of + derivation expressions that have never been normalised will + not be included. + + + + Note that not just the successor of a derivation expression + will be included, but also the successors of all input + expressions of that derivation expression. I.e., all + normal forms of subterms involved in the normalisation of + the top-level term are included. + + + + + + + + + + + + + + For each identifier in args, prints + all expansions of that identifier, that is, all paths whose + current content matches the identifier. + + + + + + + + + Prints a graph of the closure of the expressions identified by + args in the format of the + dot tool of AT&T's GraphViz package. + + + + + + + + + + + + + + + From f6a30ab264506ca966180666dff45310d176659d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 26 Nov 2003 12:30:16 +0000 Subject: [PATCH 0354/6440] * Updates. --- doc/manual/book.xml | 2 +- doc/manual/bugs.xml | 39 ++-- doc/manual/installation.xml | 100 +++++++--- doc/manual/introduction.xml | 343 +++++++-------------------------- doc/manual/troubleshooting.xml | 21 -- 5 files changed, 169 insertions(+), 336 deletions(-) diff --git a/doc/manual/book.xml b/doc/manual/book.xml index 1dc69d004..710246ca1 100644 --- a/doc/manual/book.xml +++ b/doc/manual/book.xml @@ -12,7 +12,7 @@ ]> - Nix: The Manual + Nix: A System for Software Deployment diff --git a/doc/manual/bugs.xml b/doc/manual/bugs.xml index 548ce1cab..fcb69c364 100644 --- a/doc/manual/bugs.xml +++ b/doc/manual/bugs.xml @@ -1,34 +1,43 @@ - Bugs + Bugs / To-Do - Nix should automatically recover the Berkeley DB database. + Nix should automatically remove Berkeley DB logfiles. - Nix should automatically remove Berkeley DB logfiles. + Unify the concepts of successors and substitutes into a general notion + of equivalent expressions. Expressions are + equivalent if they have the same target paths with the same + identifiers. However, even though they are functionally equivalent, + they may differ stronly with respect to their performance + characteristics. For example, realising a slice is more + efficient that realising the derivation from which that slice was + produced. On the other hand, distributing sources may be more + efficient (storage- or bandwidth-wise) than distributing binaries. So + we need to be able to attach weigths or priorities or performance + annotations to expressions; Nix can then choose the most efficient + expression dependent on the context. - Unify the concepts of successors and substitutes into a general notion - of equivalent expressions. Expressions are - equivalent if they have the same target paths with the same - identifiers. However, even though they are functionally equivalent, - they may differ stronly with respect to their performance - characteristics. For example, realising a slice is more - efficient that realising the derivation from which that slice was - produced. On the other hand, distributing sources may be more - efficient (storage- or bandwidth-wise) than distributing binaries. So - we need to be able to attach weigths or priorities or performance - annotations to expressions; Nix can then choose the most efficient - expression dependent on the context. + Build management. In principle it is already + possible to do build management using Nix (by writing builders that + perform appropriate build steps), but the Nix expression language is + not yet powerful enough to make this pleasant (?). The language should + be extended with features from the Maak build manager. + Another interesting idea is to write a make + implementation that uses Nix as a back-end to support legacy + build files. diff --git a/doc/manual/installation.xml b/doc/manual/installation.xml index f9bd0a742..3872a7fb8 100644 --- a/doc/manual/installation.xml +++ b/doc/manual/installation.xml @@ -1,74 +1,114 @@ Installation - - Prerequisites - - - Nix uses Sleepycat's Berkeley DB and CWI's ATerm library. However, these - are fetched automatically as part of the build process. - - - - Other than that, you need a good C++ compiler. GCC 2.95 does not appear - to work; please use GCC 3.x. - - - Obtaining Nix - Nix can be obtained from its Subversion + The easiest way to obtain Nix is to download a source + distribution. + + + + Alternatively, the most recent sources of Nix can be obtained from its + Subversion repository. For example, the following command will check out the latest revision into a directory called nix: -$ svn checkout http://losser.st-lab.cs.uu.nl:12080/repos/trace/nix/trunk nix +$ svn checkout https://svn.cs.uu.nl:12443/repos/trace/nix/trunk nix Likewise, specific releases can be obtained from the tags + url='https://svn.cs.uu.nl:12443/repos/trace/nix/tags'>tags directory of the repository. If you don't have Subversion, you - can download a compressed - tar-file of the latest revision of the repository. + can also download an automatically generated compressed + tar-file of the head revision of the trunk. + + Prerequisites + + + A fairly recent version of GCC/G++ is required. Version 2.95 and higher + should work. + + + + To rebuild this manual and the man-pages you need the + xmllint and xsltproc, which are + part of the libxml2 and libxslt + packages, respectively. You also need the DocBook XSL + stylesheets and optionally the + DocBook XML 4.2 DTD. Note that these are only required if you + modify the manual sources or when you are building from the Subversion + repository. + + + + Nix uses Sleepycat's Berkeley DB, CWI's ATerm library, and SDF parser + 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 externals/ directory. See + externals/Makefile.am for the precise URLs of these + packages. + + + Building Nix - To build Nix, do the following: + After unpacking or checking out the Nix sources, issue the following + commands: -$ autoreconf -i $ ./configure options... $ make $ make install - Currently, the only useful switch for configure is - to specify - where Nix is to be installed. The default installation directory is + When building from the Subversion repository, these should be preceded by + the command: + + + +$ autoreconf -i + + + The installation path can be specified by passing the + to + configure. The default installation directory is /nix. You can change this to any location you like. - You should ensure that you have write permission to the installation - prefix. + You must have write permission to the prefix + path. It is advisable not to change the installation - prefix, since doing so will in all likelihood make it impossible to use - derivates built on other systems. + prefix from its default, since doing so will in all likelihood make it + impossible to use derivations built on other systems. + + If you want to rebuilt the documentation, pass the full path to the + DocBook XML catalog file (docbook.cat) and to the + DocBook XSL stylesheets using the + + and + options. + + diff --git a/doc/manual/introduction.xml b/doc/manual/introduction.xml index feabeef9c..48e29c0d8 100644 --- a/doc/manual/introduction.xml +++ b/doc/manual/introduction.xml @@ -1,289 +1,94 @@ Introduction - - The problem space + + Nix is a system for software deployment. It supports the + creation and distribution of software packages, as well as the installation + and subsequent management of these on target machines (i.e., it is also a + package manager). + - - Nix is a system for controlling the automatic creation and distribution - of data, such as computer programs and other software artifacts. This is - a very general problem, and there are many applications that fall under - this description. - + + Nix solves some large problems that exist in most current deployment and + package management systems. Dependency determination + is a big one: the correct installation of a software component requires + that all dependencies of that component (i.e., other components used by it) + are also installed. Most systems have no way to verify that the specified + dependencies of a component are actually sufficient. + - - Build management + + Another big problem is the lack of support for concurrent availability of + multiple variants of a component. It must be possible + to have several versions of a component installed at the same time, or + several instances of the same version built with different parameters. + Unfortunately, components are in general not properly isolated from each + other. For instance, upgrading a component that is a dependency for some + other component might break the latter. + - - Build management tools are used to perform software - builds, that is, the construction of derived products - (derivates)) such as executable programs from - source code. A commonly used build tool is Make, which is a standard - tool on Unix systems. These tools have to deal with several issues: - + + Nix solves these problems by building and storing packages in paths that + are infeasible to predict in advance. For example, the artifacts of a + package X might be stored in + /nix/store/d58a0606ed616820de291d594602665d-X, rather + than in, say, /usr/lib. The path component + d58a... is actually a cryptographic hash of all the + inputs (i.e., sources, requisites, and build flags) used in building + X, and as such is very fragile: any change to the inputs + will change the hash. Therefore it is not sensible to + hard-code such a path into the build scripts of a + package Y that uses X (as does happen + with fixed paths such as /usr/lib). + Rather, the build script of package Y is parameterised + with the actual location of X, which is supplied by the + Nix system. + - - - Efficiency. Since building large systems - can take a substantial amount of time, it is desirable that build - steps that have been performed in the past are not repeated - unnecessarily, i.e., if a new build differs from a previous build - only with respect to certain sources, then only the build steps - that (directly or indirectly) depend on - those sources should be redone. - - + + As stated above, the path name of a file system object contain a + cryptographic hash of all inputs involved in building it. A change to any + of the inputs will cause the hash to change--and by extension, the path + name. These inputs include both sources (variation in time) and + configuration options (variation in space). Therefore variants of the same + package don't clash---they can co-exist peacefully within the same file + system. + - - - Correctness is this context means that the - derivates produced by a build are always consistent with the - sources, that is, they are equal to what we would get if we were - to build the derivates from those sources. This requirement is - trivially met when we do a full, unconditional build, but is far - from trivial under the requirement of efficiency, since it is not - easy to determine which derivates are affected by a change to a - source. - - + + Other features: + - - - Variability is the property that a software - system can be built in a (potentially large) number of variants. - Variation exists both in time---the - evolution of different versions of an artifact---and in - space---the artifact might have - configuration options that lead to variants that differ in the - features they support (for example, a system might be built with - or without debugging information). - + + Transparent source/binary deployment. + - - Build managers historically have had good support for variation - in time (rebuilding the system in an intelligent way when sources - change is one of the primary reasons to use a build manager), but - not always for variation in space. For example, - make will not automatically ensure that - variant builds are properly isolated from each other (they will - in fact overwrite each other unless special precautions are - taken). - - + + Unambiguous identification of configuration. + - - - High-level system modelling language. The - language in which one describes what and how derivates are to be - produced should have sufficient abstraction facilities to make it - easy to specify the derivation of even very large systems. Also, - the language should be modular to enable - components from possible different sources to be easily combined. - - + + Automatic storage management. + - - + + Atomic upgrades and rollbacks. + - + + Support for many simultaneous configurations. + - - Package management - - - After software has been built, is must also be - deployed in the intended target environment, e.g., - the user's workstation. Examples include the Red Hat package manager - (RPM), Microsoft's MSI, and so on. Here also we have several issues to - contend with: - - - - The creation of packages from some formal - description of what artifacts should be distributed in the - package. - - - - - The deployment of packages, that is, the - mechanism by which we get them onto the intended target - environment. This can be as simple as copying a file, but - complexity comes from the wide range of possible installation - media (such as a network install), and the scalability of the - process (if a program must be installed on a thousand systems, we - do not want to visit each system and perform some manual steps to - install the program on that system; that is, the complexity for - the system administrator should be constant, not linear). - - - - - - - - - - - - - What Nix provides - - - Here is a summary of Nix's main features: - - - - - - - Reliable dependencies. Builds of file system - objects depend on other file system object, such as source files, - tools, and so on. We would like to ensure that a build does not - refer to any objects that have not been declared as inputs for that - build. This is important for several reasons. First, if any of the - inputs change, we need to rebuild the things that depend on them to - maintain consistency between sources and derivates. Second, when we - deploy file system objects (that is, copy them - to a different system), we want to be certain that we copy everything - that we need. - - - - Nix ensures this by building and storing file system objects in paths - that are infeasible to predict in advance. For example, the - artifacts of a package X might be stored in - /nix/store/d58a0606ed616820de291d594602665d-X, - rather than in, say, /usr/lib. The path - component d58a... is actually a cryptographic - hash of all the inputs (i.e., sources, requisites, and build flags) - used in building X, and as such is very fragile: - any change to the inputs will change the hash. Therefore it is not - sensible to hard-code such a path into the build - scripts of a package Y that uses - X (as does happen with fixed paths - such as /usr/lib). Rather, the build script of - package Y is parameterised with the actual - location of X, which is supplied by the Nix - system. - - - - - - Support for variability. - - - - As stated above, the path name of a file system object contain a - cryptographic hash of all inputs involved in building it. A change to - any of the inputs will cause the hash to change--and by extension, - the path name. These inputs include both sources (variation in time) - and configuration options (variation in space). Therefore variants - of the same package don't clash---they can co-exist peacefully within - the same file system. So thanks to Nix's mechanism for reliably - dealing with dependencies, we obtain management of variants for free - (or, to quote Simon Peyton-Jone, it's not free, but it has already - been paid for). - - - - - - - Transparent source/binary deployment. - - - - - - Easy configuration duplication. - - - - - - Automatic storage management. - - - - - - Atomic upgrades and rollbacks. - - - - - - Support for many simultaneous configurations. - - - - - - Portability. Nix is quite portable. Contrary - to build systems like those in, e.g., Vesta and ClearCase [sic?], it - does not rely on operating system extensions. - - - - - - - Here is what Nix doesn't yet provide, but will: - - - - - - - Build management. In principle it is already - possible to do build management using Fix (by writing builders that - perform appropriate build steps), but the Fix language is not yet - powerful enough to make this pleasant. The Maak build manager - should be retargeted to produce Nix expressions, or alternatively, - extend Fix with Maak's semantics and concrete syntax (since Fix needs - a concrete syntax anyway). Another interesting idea is to write a - make implementation that uses Nix as a back-end to - support legacy - build files. - - - - - - - - - - - - The Nix system - - - ... - - - - Existing tools in this field generally both a underlying model (such as - the derivation graph of build tools, or the versioning scheme that - determines when two packages are compatible in a package - management system) and a formalism that allows ... - - - - Following the principle of separation of mechanism and policy, the Nix - system separates the low-level aspect of file system - object management form the high-level aspect of the - ... - - - + + Portability. Nix is quite portable. Contrary to + build systems like those in, e.g., Vesta and ClearCase, it does not rely on + operating system extensions. + + + From 7b0e29b4dc42946b64fc3d616caa33ae442d94c6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 26 Nov 2003 16:09:27 +0000 Subject: [PATCH 0356/6440] * Overview of nix-env. Recommended reading. :-) --- doc/manual/overview.xml | 224 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 220 insertions(+), 4 deletions(-) diff --git a/doc/manual/overview.xml b/doc/manual/overview.xml index 1c2c283f0..b26d28604 100644 --- a/doc/manual/overview.xml +++ b/doc/manual/overview.xml @@ -9,18 +9,234 @@ Basic package management - Let's start from the perspective of an end-user. Common operations at + Let's start from the perspective of an end user. Common operations at this level are to install and remove packages, ask what packages are - installed or available for installation, and so on. + installed or available for installation, and so on. These are operations + on the user environment: the set of packages that a + user sees. In a command line Unix environment, this means + the set of programs that are available through the + PATH environment variable. (In other environments it + might mean the set of programs available on the desktop, through the + start menu, and so on.) + + The terms installation and uninstallation + are used in this context to denote the act of adding or removing packages + from the user environment. In Nix, these operations are dissociated from + the physical copying or deleting of files. Installation requires that + the files constituting the package are present, but they may be present + beforehand. Likewise, uninstallation does not actually delete any files; + this is done automatically by running a garbage collector. + + + + User environments are manipulated through the nix-env + command. The query operation can be used to see what packages are + currently installed. + + + +$ nix-env -q +MozillaFirebird-0.7 +sylpheed-0.9.7 +pan-0.14.2 + + + ( is actually short for .) The package names are symbolic: they don't have + any particular significance to Nix (as they shouldn't, since they are not + unique—there can be many derivations with the same name). Note that + these packages have many dependencies (e.g., Mozilla uses the + gtk+ package) but these have not been installed in the + user environment, though they are present on the system. Generally, + there is no need to install such packages; only packages containing + programs should be installed. + + To install packages, a Nix expression is required that tells Nix how to build that package. There is a standard - distribution of Nix expressions for many common packages. + url='https://svn.cs.uu.nl:12443/dist/trace/trace-nixpkgs-trunk.tar.bz2'>set + of standard of Nix expressions for many common packages. + Assuming that you have downloaded and unpacked these, you can view the + set of available packages: + +$ nix-env -qf pkgs/system/i686-suse-linux.nix +gettext-0.12.1 +sylpheed-0.9.7 +aterm-2.0 +gtk+-1.2.10 +apache-httpd-2.0.48 +pan-0.14.2 +... + + + The Nix expression in the file i686-suse-linux.nix + yields the set of packages for a SuSE Linux system running on x86 + hardware. For other platforms, copy and modify this file for your + platform as appropriate. [TODO: improve this] + + + + It is also possible to see the status of available + packages, i.e., whether they are installed into the user environment + and/or present in the system: + + + +$ nix-env -qf pkgs/system/i686-suse-linux.nix +-P gettext-0.12.1 +IP sylpheed-0.9.7 +-- aterm-2.0 +-P gtk+-1.2.10 + + + This reveals that the sylpheed package is already + installed, or more precisely, that exactly the same instantiation of + sylpheed is installed. This guarantees that the + available package is exactly the same as the installed package with + regard to sources, dependencies, build flags, and so on. Similarly, we + see that the gettext and gtk+ + packages are present but not installed in the user environment, while the + aterm package is not installed or present at all (so, + if we were to install it, it would have to be built or downloaded first). + + + + The install operation is used install available packages from a Nix + environment. To install the pan package (a + newsreader), you would do: + + + +$ nix-env -i pkgs/system/i686-suse-linux.nix pan-0.14.2 + + + Since installation may take a long time, depending on whether any + packages need to be built or downloaded, it's a good idea to make + nix-env run verbosely by using the + () option. This option may be repeated to + increase the level of verbosity. A good value is 3 + (). + + + + In fact, if you run this command verbosely you will observe that Nix + starts to build many packages, including large and fundamental ones such + as glibc and gcc. I.e., you are + performing a source installation. This is generally undesirable, since + installation from sources may require large amounts of disk and CPU + resources. Therefore a binary installation is generally + preferable. + + + + Rather than provide different mechanisms to create and perform + the installation of binary packages, Nix supports binary deployment + transparently through a generic mechanism of + substitute expressions. If an request is made to + build some Nix expression, Nix will first try to build any substitutes + for that expression. These substitutes presumably perform an identical + build operation with respect to the result, but require less resources. + For instance, a substitute that downloads a pre-built package from the + network requires less CPU and disk resources, and possibly less time. + + + + Nix's use of cryptographic hashes makes this entirely safe. It is not + possible, for instance, to accidentally substitute a build of some + package for a Solaris or Windows system for a build on a SuSE/x86 system. + + + + While the substitute mechanism is a generic mechanism, Nix provides two + standard tools called nix-push and + nix-push that maintain and use a shared cache of + prebuilt derivations on some network site (reachable through HTTP). If + you attempt to install some package that someone else has previously + built and pushed into the cache, and you have done a + pull to register substitutes that download these prebuilt + packages, then the installation will automatically use these. + + + + For example, to pull from our cache of + prebuilt packages (at the time of writing, for SuSE Linux/x86), use the + following command: + + + +$ nix-pull http://losser.st-lab.cs.uu.nl/~eelco/nix-dist/ +obtaining list of Nix archives at http://losser.st-lab.cs.uu.nl/~eelco/nix-dist... +... + + + If nix-pull is run without any arguments, it will pull + from the URLs specified in the file + prefix/etc/nix/prebuilts.conf. + + + + Assuming that the pan installation produced no errors, + it can be used immediately, that is, it now appears in a directory in the + PATH environment variable. Specifically, + PATH includes the entry + prefix/var/nix/links/current/bin, + where + prefix/var/nix/links/current + is just a symlink to the current user environment: + + + +$ ls -l /nix/var/nix/links/ +... +lrwxrwxrwx 1 eelco ... 15 -> /nix/store/1871...12b0-user-environment +lrwxrwxrwx 1 eelco ... 16 -> /nix/store/59ba...df6b-user-environment +lrwxrwxrwx 1 eelco ... current -> /nix/var/nix/links/16 + + + That is, current in this example is a link to + 16, which is the current user environment. Before + the installation, it pointed to 15. Note that this + means that you can atomically roll-back to the previous user environment + by pointing the symlink current at + 15 again. This also shows that operations such as + installation are atomic in the Nix system: any arbitrarily complex + set of installation, uninstallation, or upgrade actions eventually boil + down to the single operation of pointing a symlink somewhere else (which + can be implemented atomically in Unix). + + + + What's in a user environment? It's just a set of symlinks to the files + that constitute the installed packages. For instance: + + + +$ ls -l /nix/var/nix/links/16/bin +lrwxrwxrwx 1 eelco ... MozillaFirebird -> /nix/store/35f8...4ae6-MozillaFirebird-0.7/bin/MozillaFirebird +lrwxrwxrwx 1 eelco ... svn -> /nix/store/3829...fb5d-subversion-0.32.1/bin/svn +... + + + Note that, e.g., svn = + /nix/var/nix/links/current/bin/svn = + /nix/var/nix/links/16/bin/svn = + /nix/store/59ba...df6b-user-environment/bin/svn = + /nix/store/3829...fb5d-subversion-0.32.1/bin/svn. + + + + Naturally, packages can also be uninstalled: + + + +$ nix-env -u pan-0.14.2 + From dc05f29cf6e1c5ee557441951116ee3fb35e0e00 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 27 Nov 2003 14:58:32 +0000 Subject: [PATCH 0357/6440] * Manual updates. --- doc/manual/Makefile.am | 4 +- doc/manual/installation.xml | 2 +- doc/manual/overview.xml | 177 ++++++++++++++++++++++++++++++++++-- 3 files changed, 175 insertions(+), 8 deletions(-) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 51ed7c05f..53a913ce6 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -1,7 +1,9 @@ ENV = SGML_CATALOG_FILES=$(docbookcatalog) XMLLINT = $(ENV) $(xmllint) $(xmlflags) --catalogs -XSLTPROC = $(ENV) $(xsltproc) $(xmlflags) --catalogs +XSLTPROC = $(ENV) $(xsltproc) $(xmlflags) --catalogs \ + --param section.autolabel 1 \ + --param section.label.includes.component.label 1 SOURCES = book.xml introduction.xml installation.xml \ overview.xml \ diff --git a/doc/manual/installation.xml b/doc/manual/installation.xml index 7d11fa43e..a9a30b09d 100644 --- a/doc/manual/installation.xml +++ b/doc/manual/installation.xml @@ -117,7 +117,7 @@ $ autoreconf -i To use Nix, some environment variables should be set. In particular, - PATH should contain the directories + PATH should contain the directories prefix/bin and prefix/var/nix/links/current/bin. The first directory contains the Nix tools themselves, while the second diff --git a/doc/manual/overview.xml b/doc/manual/overview.xml index b26d28604..64368fe2d 100644 --- a/doc/manual/overview.xml +++ b/doc/manual/overview.xml @@ -14,10 +14,9 @@ installed or available for installation, and so on. These are operations on the user environment: the set of packages that a user sees. In a command line Unix environment, this means - the set of programs that are available through the - PATH environment variable. (In other environments it - might mean the set of programs available on the desktop, through the - start menu, and so on.) + the set of programs that are available through the PATH + environment variable. (In other environments it might mean the set of + programs available on the desktop, through the start menu, and so on.) @@ -183,8 +182,8 @@ obtaining list of Nix archives at http://losser.st-lab.cs.uu.nl/~eelco/nix-dist. Assuming that the pan installation produced no errors, it can be used immediately, that is, it now appears in a directory in the - PATH environment variable. Specifically, - PATH includes the entry + PATH environment variable. Specifically, + PATH includes the entry prefix/var/nix/links/current/bin, where prefix/var/nix/links/current @@ -239,6 +238,172 @@ $ nix-env -u pan-0.14.2 + + + Writing Nix expressions + + + A simple Nix expression + + + This section shows how to write simple Nix expressions—the things + that describe how to build a package. + + + + Nix expression for GNU Hello + +{stdenv, fetchurl, perl}: + +derivation { + name = "hello-2.1.1"; + system = stdenv.system; + builder = ./builder.sh; + src = fetchurl { + url = ftp://ftp.nluug.nl/pub/gnu/hello/hello-2.1.1.tar.gz; + md5 = "70c9ccf9fac07f762c24f2df2290784d"; + }; + stdenv = stdenv; + perl = perl; +} + + + + + A simple Nix expression is shown in . It + describes how to the build the GNU Hello + package. This package has several dependencies. First, it + requires a number of other packages, such as a C compiler, standard + Unix shell tools, and Perl. Rather than have this Nix expression refer + to and use specific versions of these packages, it should be generic; + that is, it should be a function that takes the + required packages as inputs and yield a build of the GNU Hello package + as a result. This Nix expression defines a function with three + arguments , namely: + + stdenv, which should be a + standard environment package. The standard + environment is a set of tools and other components that would be + expected in a fairly minimal Unix-like environment: a C compiler + and linker, Unix shell tools, and so on. + + fetchurl, which should be a + function that given parameters url and + md5, will fetch a file from the specified + location and check that this file has the given MD5 hash code. + The hash is required because build operations must be + pure: given the same inputs they should + always yield the same output. Since network resources can change + at any time, we must in some way guarantee what the result will + be. + + perl, which should be a Perl + interpreter. + + + + + + The remainder of the file is the body of the function, which happens to + be a derivation , which is the built-in function + derivation applied to a set of attributes that + encode all the necessary information for building the GNU Hello + package. + + + + Build script (<filename>builder.sh</filename>) for GNU + Hello + +#! /bin/sh + +buildinputs="$perl" +. $stdenv/setup || exit 1 + +tar xvfz $src || exit 1 +cd hello-* || exit 1 +./configure --prefix=$out || exit 1 +make || exit 1 +make install || exit 1 + + + + + + + A more complex Nix expression + + + Nix expression for Subversion + +{ localServer ? false +, httpServer ? false +, sslSupport ? false +, swigBindings ? false +, stdenv, fetchurl +, openssl ? null, httpd ? null, db4 ? null, expat, swig ? null +}: + +assert !isNull expat; +assert localServer -> !isNull db4; +assert httpServer -> !isNull httpd && httpd.expat == expat; +assert sslSupport -> !isNull openssl && (httpServer -> httpd.openssl == openssl); +assert swigBindings -> !isNull swig; + +derivation { + name = "subversion-0.32.1"; + system = stdenv.system; + + builder = ./builder.sh; + src = fetchurl { + url = http://svn.collab.net/tarballs/subversion-0.32.1.tar.gz; + md5 = "b06717a8ef50db4b5c4d380af00bd901"; + }; + + localServer = localServer; + httpServer = httpServer; + sslSupport = sslSupport; + swigBindings = swigBindings; + + stdenv = stdenv; + openssl = if sslSupport then openssl else null; + httpd = if httpServer then httpd else null; + expat = expat; + db4 = if localServer then db4 else null; + swig = if swigBindings then swig else null; +} + + + + + This example shows several features. Default parameters can be used to simplify call sites: if an + argument that has a default is omitted, its default value is used. + + + + You can use assertions to test whether arguments + satisfy certain constraints. The simple assertion tests whether the + expat argument is not a null value. The more + complex assertion says that if + Subversion is built with Apache support, then httpd + (the Apache package) must not be null and it must have been built using + the same instance of the expat library as was passed + to the Subversion expression. This is since the Subversion code is + dynamically linked against the Apache code and they both use Expat, + they must be linked against the same instance—otherwise a + conflict might occur. + + + + + + + + diff --git a/doc/manual/schemas.xml b/doc/manual/schemas.xml new file mode 100644 index 000000000..076f3a141 --- /dev/null +++ b/doc/manual/schemas.xml @@ -0,0 +1,4 @@ + + + + From a81b621202844f58ae7202e592324f1232107810 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 21 Dec 2003 22:34:41 +0000 Subject: [PATCH 0375/6440] * `-u' -> `-e'. * `--link' / `-l' flag to specify the switch symlink to use (by default, /nix/var/nix/links/current). --- src/nix-env/help.txt | 3 +- src/nix-env/main.cc | 94 ++++++++++++++++++++++++++------------------ 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/src/nix-env/help.txt b/src/nix-env/help.txt index 69c474294..cebffc99d 100644 --- a/src/nix-env/help.txt +++ b/src/nix-env/help.txt @@ -5,7 +5,7 @@ nix-env [OPTIONS...] [ARGUMENTS...] Operations: --install / -i FILE: add a derivation to the user environment - --uninstall / -u: remove a derivation to the user environment + --uninstall / -e: remove a derivation to the user environment --query / -q: perform a query on an environment or Nix expression The previous operations take a list of derivation names. The special @@ -27,5 +27,6 @@ Query sources: Options: + --link / -l LINK: use symlink LINK instead of (...)/current --verbose / -v: verbose operation (may be repeated) --keep-failed / -K: keep temporary directories of failed builds diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index a09021669..9190660cb 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -6,7 +6,14 @@ #include "help.txt.hh" -typedef void (* Operation) (EvalState & state, +struct Globals +{ + Path linkPath; + EvalState state; +}; + + +typedef void (* Operation) (Globals & globals, Strings opFlags, Strings opArgs); @@ -103,15 +110,10 @@ static Path getLinksDir() } -static Path getCurrentPath() +void queryInstalled(EvalState & state, DrvInfos & drvs, + const Path & userEnv) { - return getLinksDir() + "/current"; -} - - -void queryInstalled(EvalState & state, DrvInfos & drvs) -{ - Path path = getCurrentPath() + "/manifest"; + Path path = userEnv + "/manifest"; if (!pathExists(path)) return; /* not an error, assume nothing installed */ @@ -123,7 +125,7 @@ void queryInstalled(EvalState & state, DrvInfos & drvs) } -Path createLink(Path outPath, Path drvPath) +Path createGeneration(Path outPath, Path drvPath) { Path linksDir = getLinksDir(); @@ -136,20 +138,20 @@ Path createLink(Path outPath, Path drvPath) if (s >> n && s.eof() && n >= num) num = n + 1; } - Path linkPath; + Path generation; while (1) { - linkPath = (format("%1%/%2%") % linksDir % num).str(); - if (symlink(outPath.c_str(), linkPath.c_str()) == 0) break; + generation = (format("%1%/%2%") % linksDir % num).str(); + if (symlink(outPath.c_str(), generation.c_str()) == 0) break; if (errno != EEXIST) - throw SysError(format("creating symlink `%1%'") % linkPath); + throw SysError(format("creating symlink `%1%'") % generation); /* Somebody beat us to it, retry with a higher number. */ num++; } - writeStringToFile(linkPath + "-src.id", drvPath); + writeStringToFile(generation + "-src.id", drvPath); - return linkPath; + return generation; } @@ -169,7 +171,8 @@ void switchLink(Path link, Path target) } -void createUserEnv(EvalState & state, const DrvInfos & drvs) +void createUserEnv(EvalState & state, const DrvInfos & drvs, + const Path & linkPath) { /* Get the environment builder expression. */ Expr envBuilder = parseExprFromFile(nixDataDir + "/nix/corepkgs/buildenv"); /* !!! */ @@ -221,8 +224,9 @@ void createUserEnv(EvalState & state, const DrvInfos & drvs) /* Switch the current user environment to the output path. */ debug(format("switching to new user environment")); - Path linkPath = createLink(topLevelDrv.outPath, topLevelDrv.drvPath); - switchLink(getLinksDir() + "/current", linkPath); + Path generation = createGeneration( + topLevelDrv.outPath, topLevelDrv.drvPath); + switchLink(linkPath, generation); } @@ -239,7 +243,7 @@ NameMap mapByNames(DrvInfos & drvs) void installDerivations(EvalState & state, - Path nePath, Strings drvNames) + Path nePath, Strings drvNames, const Path & linkPath) { debug(format("installing derivations from `%1%'") % nePath); @@ -267,30 +271,33 @@ void installDerivations(EvalState & state, /* Add in the already installed derivations. */ DrvInfos installedDrvs; - queryInstalled(state, installedDrvs); + queryInstalled(state, installedDrvs, linkPath); selectedDrvs.insert(installedDrvs.begin(), installedDrvs.end()); - createUserEnv(state, selectedDrvs); + createUserEnv(state, selectedDrvs, linkPath); } -static void opInstall(EvalState & state, +static void opInstall(Globals & globals, Strings opFlags, Strings opArgs) { + if (opFlags.size() > 0) + throw UsageError(format("unknown flags `%1%'") % opFlags.front()); if (opArgs.size() < 1) throw UsageError("Nix file expected"); Path nePath = opArgs.front(); opArgs.pop_front(); - installDerivations(state, nePath, - Strings(opArgs.begin(), opArgs.end())); + installDerivations(globals.state, nePath, + Strings(opArgs.begin(), opArgs.end()), globals.linkPath); } -void uninstallDerivations(EvalState & state, Strings drvNames) +void uninstallDerivations(EvalState & state, Strings drvNames, + Path & linkPath) { DrvInfos installedDrvs; - queryInstalled(state, installedDrvs); + queryInstalled(state, installedDrvs, linkPath); NameMap nameMap = mapByNames(installedDrvs); @@ -304,18 +311,21 @@ void uninstallDerivations(EvalState & state, Strings drvNames) installedDrvs.erase(j->second); } - createUserEnv(state, installedDrvs); + createUserEnv(state, installedDrvs, linkPath); } -static void opUninstall(EvalState & state, +static void opUninstall(Globals & globals, Strings opFlags, Strings opArgs) { - uninstallDerivations(state, opArgs); + if (opFlags.size() > 0) + throw UsageError(format("unknown flags `%1%'") % opFlags.front()); + + uninstallDerivations(globals.state, opArgs, globals.linkPath); } -static void opQuery(EvalState & state, +static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) { enum { qName, qDrvPath, qStatus } query = qName; @@ -336,14 +346,14 @@ static void opQuery(EvalState & state, switch (source) { case sInstalled: - queryInstalled(state, drvs); + queryInstalled(globals.state, drvs, globals.linkPath); break; case sAvailable: { if (opArgs.size() < 1) throw UsageError("Nix file expected"); Path nePath = opArgs.front(); opArgs.pop_front(); - loadDerivations(state, nePath, drvs); + loadDerivations(globals.state, nePath, drvs); break; } @@ -369,7 +379,7 @@ static void opQuery(EvalState & state, case qStatus: { DrvInfos installed; - queryInstalled(state, installed); + queryInstalled(globals.state, installed, globals.linkPath); for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) { cout << format("%1%%2% %3%\n") @@ -387,9 +397,11 @@ static void opQuery(EvalState & state, void run(Strings args) { - EvalState state; Strings opFlags, opArgs; Operation op = 0; + + Globals globals; + globals.linkPath = getLinksDir() + "/current"; for (Strings::iterator i = args.begin(); i != args.end(); ++i) { string arg = *i; @@ -398,10 +410,16 @@ void run(Strings args) if (arg == "--install" || arg == "-i") op = opInstall; - else if (arg == "--uninstall" || arg == "-u") + else if (arg == "--uninstall" || arg == "-e") op = opUninstall; else if (arg == "--query" || arg == "-q") op = opQuery; + else if (arg == "--link" || arg == "-l") { + ++i; + if (i == args.end()) throw UsageError( + format("`%1%' requires an argument") % arg); + globals.linkPath = absPath(*i); + } else if (arg[0] == '-') opFlags.push_back(arg); else @@ -415,9 +433,9 @@ void run(Strings args) openDB(); - op(state, opFlags, opArgs); + op(globals, opFlags, opArgs); - printEvalStats(state); + printEvalStats(globals.state); } From f3c978384698a0b9b0f5dee41f98e6208f269347 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 21 Dec 2003 23:58:56 +0000 Subject: [PATCH 0376/6440] * Version numbers can be omitted in install/uninstall. E.g., nix-env -i foo.nix subversion The version number part of a derivation name is defined as everything following the first dash not followed by a letter. --- src/nix-env/help.txt | 5 +- src/nix-env/main.cc | 143 ++++++++++++++++++++++++++++++++----------- 2 files changed, 110 insertions(+), 38 deletions(-) diff --git a/src/nix-env/help.txt b/src/nix-env/help.txt index cebffc99d..d940bd209 100644 --- a/src/nix-env/help.txt +++ b/src/nix-env/help.txt @@ -4,8 +4,9 @@ nix-env [OPTIONS...] [ARGUMENTS...] Operations: - --install / -i FILE: add a derivation to the user environment - --uninstall / -e: remove a derivation to the user environment + --install / -i FILE: add derivations to the user environment + --uninstall / -e: remove derivations from the user environment + --upgrade / -u FILE: upgrade derivation in the user environment --query / -q: perform a query on an environment or Nix expression The previous operations take a list of derivation names. The special diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 9190660cb..fc37f01be 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -230,20 +230,71 @@ void createUserEnv(EvalState & state, const DrvInfos & drvs, } -typedef map NameMap; - - -NameMap mapByNames(DrvInfos & drvs) +class DrvName { - NameMap nameMap; - for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) - nameMap[i->second.name] = i->first; - return nameMap; + string fullName; + string name; + string version; + unsigned int hits; + +public: + + /* Parse a derivation name. The `name' part of a derivation name + is everything up to but not including the first dash *not* + followed by a letter. The `version' part is the rest + (excluding the separating dash). E.g., `apache-httpd-2.0.48' + is parsed to (`apache-httpd', '2.0.48'). */ + DrvName(const string & s) : hits(0) + { + name = fullName = s; + for (unsigned int i = 0; i < s.size(); ++i) { + if (s[i] == '-' && i + 1 < s.size() && !isalpha(s[i + 1])) { + name = string(s, 0, i); + version = string(s, i + 1); + break; + } + } + } + + bool matches(DrvName & n) + { + if (name != "*" && name != n.name) return false; + if (version != "" && version != n.version) return false; + return true; + } + + void hit() + { + hits++; + } + + unsigned int getHits() + { + return hits; + } + + string getFullName() + { + return fullName; + } +}; + + +typedef list DrvNames; + + +static DrvNames drvNamesFromArgs(const Strings & opArgs) +{ + DrvNames result; + for (Strings::const_iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + result.push_back(DrvName(*i)); + return result; } void installDerivations(EvalState & state, - Path nePath, Strings drvNames, const Path & linkPath) + Path nePath, DrvNames & selectors, const Path & linkPath) { debug(format("installing derivations from `%1%'") % nePath); @@ -251,24 +302,29 @@ void installDerivations(EvalState & state, DrvInfos availDrvs; loadDerivations(state, nePath, availDrvs); - NameMap nameMap = mapByNames(availDrvs); - /* Filter out the ones we're not interested in. */ DrvInfos selectedDrvs; - if (drvNames.size() > 0 && drvNames.front() == "*") { /* !!! hack */ - selectedDrvs = availDrvs; - } else { - for (Strings::iterator i = drvNames.begin(); - i != drvNames.end(); ++i) + for (DrvInfos::iterator i = availDrvs.begin(); + i != availDrvs.end(); ++i) + { + DrvName drvName(i->second.name); + for (DrvNames::iterator j = selectors.begin(); + j != selectors.end(); ++j) { - NameMap::iterator j = nameMap.find(*i); - if (j == nameMap.end()) - throw Error(format("unknown derivation `%1%'") % *i); - else - selectedDrvs[j->second] = availDrvs[j->second]; + if (j->matches(drvName)) { + j->hit(); + selectedDrvs.insert(*i); + } } } + /* Check that all selectors have been used. */ + for (DrvNames::iterator i = selectors.begin(); + i != selectors.end(); ++i) + if (i->getHits() == 0) + throw Error(format("selector `%1%' matches no derivations") + % i->getFullName()); + /* Add in the already installed derivations. */ DrvInfos installedDrvs; queryInstalled(state, installedDrvs, linkPath); @@ -286,29 +342,40 @@ static void opInstall(Globals & globals, if (opArgs.size() < 1) throw UsageError("Nix file expected"); Path nePath = opArgs.front(); - opArgs.pop_front(); - - installDerivations(globals.state, nePath, - Strings(opArgs.begin(), opArgs.end()), globals.linkPath); + DrvNames drvNames = drvNamesFromArgs( + Strings(++opArgs.begin(), opArgs.end())); + + installDerivations(globals.state, nePath, drvNames, globals.linkPath); } -void uninstallDerivations(EvalState & state, Strings drvNames, +static void opUpgrade(Globals & globals, + Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError(format("unknown flags `%1%'") % opFlags.front()); + if (opArgs.size() < 1) throw UsageError("Nix file expected"); + + Path nePath = opArgs.front(); + opArgs.pop_front(); + +} + + +void uninstallDerivations(EvalState & state, DrvNames & selectors, Path & linkPath) { DrvInfos installedDrvs; queryInstalled(state, installedDrvs, linkPath); - NameMap nameMap = mapByNames(installedDrvs); - - for (Strings::iterator i = drvNames.begin(); - i != drvNames.end(); ++i) + for (DrvInfos::iterator i = installedDrvs.begin(); + i != installedDrvs.end(); ++i) { - NameMap::iterator j = nameMap.find(*i); - if (j == nameMap.end()) - throw Error(format("unknown derivation `%1%'") % *i); - else - installedDrvs.erase(j->second); + DrvName drvName(i->second.name); + for (DrvNames::iterator j = selectors.begin(); + j != selectors.end(); ++j) + if (j->matches(drvName)) + installedDrvs.erase(i); } createUserEnv(state, installedDrvs, linkPath); @@ -321,7 +388,9 @@ static void opUninstall(Globals & globals, if (opFlags.size() > 0) throw UsageError(format("unknown flags `%1%'") % opFlags.front()); - uninstallDerivations(globals.state, opArgs, globals.linkPath); + DrvNames drvNames = drvNamesFromArgs(opArgs); + + uninstallDerivations(globals.state, drvNames, globals.linkPath); } @@ -412,6 +481,8 @@ void run(Strings args) op = opInstall; else if (arg == "--uninstall" || arg == "-e") op = opUninstall; + else if (arg == "--upgrade" || arg == "-u") + op = opUpgrade; else if (arg == "--query" || arg == "-q") op = opQuery; else if (arg == "--link" || arg == "-l") { From cf0287c09e8b5816c65dd265c4ef167865d70172 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 22 Dec 2003 16:04:00 +0000 Subject: [PATCH 0377/6440] * Upgrade operation in `nix-env'. For instance, you can say nix-env -u foo.nix strategoxt to replace the installed `strategoxt' derivation with the one from `foo.nix', if the latter has a higher version number. This is a no-op if `strategoxt' is not installed. Wildcards are also accepted, so nix-env -u foo.nix '*' will replace any installed derivation with newer versions from `foo.nix', if available. The notion of "version number" is somewhat ad hoc, but should be useful in most cases, as evidenced by the following unit tests for the version comparator: TEST("1.0", "2.3", -1); TEST("2.1", "2.3", -1); TEST("2.3", "2.3", 0); TEST("2.5", "2.3", 1); TEST("3.1", "2.3", 1); TEST("2.3.1", "2.3", 1); TEST("2.3.1", "2.3a", 1); TEST("2.3pre1", "2.3", -1); TEST("2.3pre3", "2.3pre12", -1); TEST("2.3a", "2.3c", -1); TEST("2.3pre1", "2.3c", -1); TEST("2.3pre1", "2.3q", -1); (-1 = less, 0 = equal, 1 = greater) * A new verbosity level `lvlInfo', between `lvlError' and `lvlTalkative'. This is the default for `nix-env', so without any `-v' flags users should get useful output, e.g., $ nix-env -u foo.nix strategoxt upgrading `strategoxt-0.9.2' to `strategoxt-0.9.3' --- src/libutil/util.hh | 3 +- src/nix-env/main.cc | 192 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 168 insertions(+), 27 deletions(-) diff --git a/src/libutil/util.hh b/src/libutil/util.hh index cca93cdc7..4126381d9 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -96,7 +96,8 @@ void writeStringToFile(const Path & path, const string & s); /* Messages. */ typedef enum { - lvlError, + lvlError, + lvlInfo, lvlTalkative, lvlChatty, lvlDebug, diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index fc37f01be..06678fc51 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -230,15 +230,13 @@ void createUserEnv(EvalState & state, const DrvInfos & drvs, } -class DrvName +struct DrvName { string fullName; string name; string version; unsigned int hits; -public: - /* Parse a derivation name. The `name' part of a derivation name is everything up to but not including the first dash *not* followed by a letter. The `version' part is the rest @@ -248,6 +246,7 @@ public: { name = fullName = s; for (unsigned int i = 0; i < s.size(); ++i) { + /* !!! isalpha/isdigit are affected by the locale. */ if (s[i] == '-' && i + 1 < s.size() && !isalpha(s[i + 1])) { name = string(s, 0, i); version = string(s, i + 1); @@ -262,24 +261,92 @@ public: if (version != "" && version != n.version) return false; return true; } - - void hit() - { - hits++; - } - - unsigned int getHits() - { - return hits; - } - - string getFullName() - { - return fullName; - } }; +string nextComponent(string::const_iterator & p, + const string::const_iterator end) +{ + /* Skip any dots and dashes (component separators). */ + while (p != end && (*p == '.' || *p == '-')) ++p; + + if (p == end) return ""; + + /* If the first character is a digit, consume the longest sequence + of digits. Otherwise, consume the longest sequence of + non-digit, non-separator characters. */ + string s; + if (isdigit(*p)) + while (p != end && isdigit(*p)) s += *p++; + else + while (p != end && (!isdigit(*p) && *p != '.' && *p != '-')) + s += *p++; + + return s; +} + + +#include + +bool parseInt(const string & s, int & n) +{ + istringstream st(s); + st >> n; + return !st.fail(); +} + + +static bool componentsLT(const string & c1, const string & c2) +{ + int n1, n2; + bool c1Num = parseInt(c1, n1), c2Num = parseInt(c2, n2); + + if (c1Num && c2Num) return n1 < n2; + else if (c1 == "" && c2Num) return true; + else if (c1 == "pre" && c2 != "pre") return true; + else if (c2 == "pre") return false; + /* Assume that `2.3a' < `2.3.1'. */ + else if (c2Num) return true; + else if (c1Num) return false; + else return c1 < c2; +} + + +static int compareVersions(const string & v1, const string & v2) +{ + string::const_iterator p1 = v1.begin(); + string::const_iterator p2 = v2.begin(); + + while (p1 != v1.end() || p2 != v2.end()) { + string c1 = nextComponent(p1, v1.end()); + string c2 = nextComponent(p2, v2.end()); + if (componentsLT(c1, c2)) return -1; + else if (componentsLT(c2, c1)) return 1; + } + + return 0; +} + + +static void testCompareVersions() +{ +#define TEST(v1, v2, n) assert( \ + compareVersions(v1, v2) == n && compareVersions(v2, v1) == -n) + TEST("1.0", "2.3", -1); + TEST("2.1", "2.3", -1); + TEST("2.3", "2.3", 0); + TEST("2.5", "2.3", 1); + TEST("3.1", "2.3", 1); + TEST("2.3.1", "2.3", 1); + TEST("2.3.1", "2.3a", 1); + TEST("2.3pre1", "2.3", -1); + TEST("2.3pre3", "2.3pre12", -1); + TEST("2.3a", "2.3c", -1); + TEST("2.3pre1", "2.3c", -1); + TEST("2.3pre1", "2.3q", -1); +} + + typedef list DrvNames; @@ -293,7 +360,7 @@ static DrvNames drvNamesFromArgs(const Strings & opArgs) } -void installDerivations(EvalState & state, +static void installDerivations(EvalState & state, Path nePath, DrvNames & selectors, const Path & linkPath) { debug(format("installing derivations from `%1%'") % nePath); @@ -312,7 +379,9 @@ void installDerivations(EvalState & state, j != selectors.end(); ++j) { if (j->matches(drvName)) { - j->hit(); + printMsg(lvlInfo, + format("installing `%1%'") % i->second.name); + j->hits++; selectedDrvs.insert(*i); } } @@ -321,9 +390,9 @@ void installDerivations(EvalState & state, /* Check that all selectors have been used. */ for (DrvNames::iterator i = selectors.begin(); i != selectors.end(); ++i) - if (i->getHits() == 0) + if (i->hits == 0) throw Error(format("selector `%1%' matches no derivations") - % i->getFullName()); + % i->fullName); /* Add in the already installed derivations. */ DrvInfos installedDrvs; @@ -349,6 +418,69 @@ static void opInstall(Globals & globals, } +static void upgradeDerivations(EvalState & state, + Path nePath, DrvNames & selectors, const Path & linkPath) +{ + debug(format("upgrading derivations from `%1%'") % nePath); + + /* Upgrade works as follows: we take all currently installed + derivations, and for any derivation matching any selector, look + for a derivation in the input Nix expression that has the same + name and a higher version number. */ + + /* Load the currently installed derivations. */ + DrvInfos installedDrvs; + queryInstalled(state, installedDrvs, linkPath); + + /* Fetch all derivations from the input file. */ + DrvInfos availDrvs; + loadDerivations(state, nePath, availDrvs); + + /* Go through all installed derivations. */ + for (DrvInfos::iterator i = installedDrvs.begin(); + i != installedDrvs.end(); ++i) + { + DrvName drvName(i->second.name); + + /* Do we want to upgrade this derivation? */ + bool upgrade = false; + for (DrvNames::iterator j = selectors.begin(); + j != selectors.end(); ++j) + { + if (j->matches(drvName)) { + j->hits++; + upgrade = true; + break; + } + } + if (!upgrade) continue; + + /* If yes, find the derivation in the input Nix expression + with the same name and the highest version number. */ + DrvInfos::iterator bestDrv = i; + DrvName bestName = drvName; + for (DrvInfos::iterator j = availDrvs.begin(); + j != availDrvs.end(); ++j) + { + DrvName newName(j->second.name); + if (newName.name == bestName.name && + compareVersions(newName.version, bestName.version) > 0) + bestDrv = j; + } + + if (bestDrv != i) { + printMsg(lvlInfo, + format("upgrading `%1%' to `%2%'") + % i->second.name % bestDrv->second.name); + installedDrvs.erase(i); + installedDrvs.insert(*bestDrv); + } + } + + createUserEnv(state, installedDrvs, linkPath); +} + + static void opUpgrade(Globals & globals, Strings opFlags, Strings opArgs) { @@ -357,12 +489,14 @@ static void opUpgrade(Globals & globals, if (opArgs.size() < 1) throw UsageError("Nix file expected"); Path nePath = opArgs.front(); - opArgs.pop_front(); - + DrvNames drvNames = drvNamesFromArgs( + Strings(++opArgs.begin(), opArgs.end())); + + upgradeDerivations(globals.state, nePath, drvNames, globals.linkPath); } -void uninstallDerivations(EvalState & state, DrvNames & selectors, +static void uninstallDerivations(EvalState & state, DrvNames & selectors, Path & linkPath) { DrvInfos installedDrvs; @@ -374,8 +508,11 @@ void uninstallDerivations(EvalState & state, DrvNames & selectors, DrvName drvName(i->second.name); for (DrvNames::iterator j = selectors.begin(); j != selectors.end(); ++j) - if (j->matches(drvName)) + if (j->matches(drvName)) { + printMsg(lvlInfo, + format("uninstalling `%1%'") % i->second.name); installedDrvs.erase(i); + } } createUserEnv(state, installedDrvs, linkPath); @@ -466,6 +603,9 @@ static void opQuery(Globals & globals, void run(Strings args) { + /* Use a higher default verbosity (lvlInfo). */ + verbosity = (Verbosity) ((int) verbosity + 1); + Strings opFlags, opArgs; Operation op = 0; From 833f2fc92da8d31c62eb35dae8b3861829a1383a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 22 Dec 2003 16:40:46 +0000 Subject: [PATCH 0378/6440] * GCC 2.95 compatibility. --- configure.ac | 16 ++++++++++++++++ src/boost/format.hpp | 9 ++++++--- src/libmain/shared.cc | 3 +++ src/libstore/exec.cc | 1 + src/libstore/store.cc | 1 + src/nix-env/main.cc | 2 ++ 6 files changed, 29 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index 3e0bca31e..85244ebab 100644 --- a/configure.ac +++ b/configure.ac @@ -26,6 +26,22 @@ AC_PROG_CC AC_PROG_CXX AC_PROG_RANLIB +# Check for pubsetbuf. +AC_MSG_CHECKING([for pubsetbuf]) +AC_LANG_PUSH(C++) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include +using namespace std; +static char buf[1024];]], + [[cerr.rdbuf()->pubsetbuf(buf, sizeof(buf));]])], + [AC_MSG_RESULT(yes) AC_DEFINE(HAVE_PUBSETBUF, 1, [whether pubsetbuf is available])], + AC_MSG_RESULT(no)) +AC_LANG_POP(C++) + +# Check for +AC_LANG_PUSH(C++) +AC_CHECK_HEADERS([locale]) +AC_LANG_POP(C++) + AC_PATH_PROG(wget, wget) AC_PATH_PROG(xmllint, xmllint) AC_PATH_PROG(xsltproc, xsltproc) diff --git a/src/boost/format.hpp b/src/boost/format.hpp index a287048ed..1476cb796 100644 --- a/src/boost/format.hpp +++ b/src/boost/format.hpp @@ -24,10 +24,13 @@ #include #include +#if HAVE_LOCALE #include -//#define BOOST_NO_STD_LOCALE -//#define BOOST_NO_LOCALE_ISIDIGIT -//#include +#else +#define BOOST_NO_STD_LOCALE +#define BOOST_NO_LOCALE_ISIDIGIT +#include +#endif #include diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index b99a74bb4..92349488e 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -1,3 +1,4 @@ + #include #include @@ -71,7 +72,9 @@ int main(int argc, char * * argv) ATinit(argc, argv, &bottomOfStack); /* Turn on buffering for cerr. */ +#if HAVE_PUBSETBUF cerr.rdbuf()->pubsetbuf(buf, sizeof(buf)); +#endif try { initAndRun(argc, argv); diff --git a/src/libstore/exec.cc b/src/libstore/exec.cc index 47a385f14..2adf03841 100644 --- a/src/libstore/exec.cc +++ b/src/libstore/exec.cc @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/src/libstore/store.cc b/src/libstore/store.cc index acdb4708e..20625b9fe 100644 --- a/src/libstore/store.cc +++ b/src/libstore/store.cc @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 06678fc51..6150b2fb6 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -1,3 +1,5 @@ +#include + #include "globals.hh" #include "normalise.hh" #include "shared.hh" From 392b7e0f8ecd50f98017021253386714f0771752 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 23 Dec 2003 22:13:36 +0000 Subject: [PATCH 0379/6440] * Fixed a bug in the upgrade operation. --- src/nix-env/main.cc | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 6150b2fb6..7bced5be0 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -439,6 +439,7 @@ static void upgradeDerivations(EvalState & state, loadDerivations(state, nePath, availDrvs); /* Go through all installed derivations. */ + DrvInfos newDrvs; for (DrvInfos::iterator i = installedDrvs.begin(); i != installedDrvs.end(); ++i) { @@ -455,31 +456,32 @@ static void upgradeDerivations(EvalState & state, break; } } - if (!upgrade) continue; /* If yes, find the derivation in the input Nix expression with the same name and the highest version number. */ DrvInfos::iterator bestDrv = i; DrvName bestName = drvName; - for (DrvInfos::iterator j = availDrvs.begin(); - j != availDrvs.end(); ++j) - { - DrvName newName(j->second.name); - if (newName.name == bestName.name && - compareVersions(newName.version, bestName.version) > 0) - bestDrv = j; + if (upgrade) { + for (DrvInfos::iterator j = availDrvs.begin(); + j != availDrvs.end(); ++j) + { + DrvName newName(j->second.name); + if (newName.name == bestName.name && + compareVersions(newName.version, bestName.version) > 0) + bestDrv = j; + } } if (bestDrv != i) { printMsg(lvlInfo, format("upgrading `%1%' to `%2%'") % i->second.name % bestDrv->second.name); - installedDrvs.erase(i); - installedDrvs.insert(*bestDrv); } + + newDrvs.insert(*bestDrv); } - createUserEnv(state, installedDrvs, linkPath); + createUserEnv(state, newDrvs, linkPath); } From 68f2fadb788f8d401fad6fd1db1cfac283e5e337 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 23 Dec 2003 22:15:12 +0000 Subject: [PATCH 0380/6440] * nix-pull requires libexecdir to be substituted. --- substitute.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/substitute.mk b/substitute.mk index 6557e496d..cc21eba40 100644 --- a/substitute.mk +++ b/substitute.mk @@ -5,6 +5,7 @@ -e "s^@sysconfdir\@^$(sysconfdir)^g" \ -e "s^@localstatedir\@^$(localstatedir)^g" \ -e "s^@datadir\@^$(datadir)^g" \ + -e "s^@libexecdir\@^$(libexecdir)^g" \ -e "s^@system\@^$(system)^g" \ -e "s^@wget\@^$(wget)^g" \ < $< > $@ || rm $@ From 94175e978a87a79f3362879898dc1cf7d08d7791 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 30 Dec 2003 20:09:00 +0000 Subject: [PATCH 0381/6440] * RPM spec file. * Respect DESTDIR variable. --- Makefile.am | 5 ++++- corepkgs/buildenv/Makefile.am | 8 +++---- corepkgs/fetchurl/Makefile.am | 8 +++---- corepkgs/nar/Makefile.am | 12 +++++----- doc/manual/Makefile.am | 6 ++--- nix.spec | 41 +++++++++++++++++++++++++++++++++++ scripts/Makefile.am | 12 +++++----- src/nix-store/Makefile.am | 16 +++++++------- 8 files changed, 76 insertions(+), 32 deletions(-) create mode 100644 nix.spec diff --git a/Makefile.am b/Makefile.am index f069e454a..f9fc8695f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,6 @@ SUBDIRS = externals src scripts corepkgs doc -EXTRA_DIST = substitute.mk +EXTRA_DIST = substitute.mk nix.spec + +rpm: dist + rpm -ta $(distdir).tar.gz diff --git a/corepkgs/buildenv/Makefile.am b/corepkgs/buildenv/Makefile.am index 74c39199f..f6a14600f 100644 --- a/corepkgs/buildenv/Makefile.am +++ b/corepkgs/buildenv/Makefile.am @@ -1,8 +1,8 @@ install-exec-local: - $(INSTALL) -d $(datadir)/nix/corepkgs - $(INSTALL) -d $(datadir)/nix/corepkgs/buildenv - $(INSTALL_DATA) default.nix $(datadir)/nix/corepkgs/buildenv - $(INSTALL_PROGRAM) builder.pl $(datadir)/nix/corepkgs/buildenv + $(INSTALL) -d $(DESTDIR)$(datadir)/nix/corepkgs + $(INSTALL) -d $(DESTDIR)$(datadir)/nix/corepkgs/buildenv + $(INSTALL_DATA) default.nix $(DESTDIR)$(datadir)/nix/corepkgs/buildenv + $(INSTALL_PROGRAM) builder.pl $(DESTDIR)$(datadir)/nix/corepkgs/buildenv include ../../substitute.mk diff --git a/corepkgs/fetchurl/Makefile.am b/corepkgs/fetchurl/Makefile.am index 270bf0142..3cb63e0ce 100644 --- a/corepkgs/fetchurl/Makefile.am +++ b/corepkgs/fetchurl/Makefile.am @@ -1,10 +1,10 @@ all-local: builder.sh install-exec-local: - $(INSTALL) -d $(datadir)/nix/corepkgs - $(INSTALL) -d $(datadir)/nix/corepkgs/fetchurl - $(INSTALL_DATA) default.nix $(datadir)/nix/corepkgs/fetchurl - $(INSTALL_PROGRAM) builder.sh $(datadir)/nix/corepkgs/fetchurl + $(INSTALL) -d $(DESTDIR)$(datadir)/nix/corepkgs + $(INSTALL) -d $(DESTDIR)$(datadir)/nix/corepkgs/fetchurl + $(INSTALL_DATA) default.nix $(DESTDIR)$(datadir)/nix/corepkgs/fetchurl + $(INSTALL_PROGRAM) builder.sh $(DESTDIR)$(datadir)/nix/corepkgs/fetchurl include ../../substitute.mk diff --git a/corepkgs/nar/Makefile.am b/corepkgs/nar/Makefile.am index 3e0aab869..8fb879ae1 100644 --- a/corepkgs/nar/Makefile.am +++ b/corepkgs/nar/Makefile.am @@ -1,12 +1,12 @@ all-local: nar.sh unnar.sh install-exec-local: - $(INSTALL) -d $(datadir)/nix/corepkgs - $(INSTALL) -d $(datadir)/nix/corepkgs/nar - $(INSTALL_DATA) nar.nix $(datadir)/nix/corepkgs/nar - $(INSTALL_PROGRAM) nar.sh $(datadir)/nix/corepkgs/nar - $(INSTALL_DATA) unnar.nix $(datadir)/nix/corepkgs/nar - $(INSTALL_PROGRAM) unnar.sh $(datadir)/nix/corepkgs/nar + $(INSTALL) -d $(DESTDIR)$(datadir)/nix/corepkgs + $(INSTALL) -d $(DESTDIR)$(datadir)/nix/corepkgs/nar + $(INSTALL_DATA) nar.nix $(DESTDIR)$(datadir)/nix/corepkgs/nar + $(INSTALL_PROGRAM) nar.sh $(DESTDIR)$(datadir)/nix/corepkgs/nar + $(INSTALL_DATA) unnar.nix $(DESTDIR)$(datadir)/nix/corepkgs/nar + $(INSTALL_PROGRAM) unnar.sh $(DESTDIR)$(datadir)/nix/corepkgs/nar include ../../substitute.mk diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 46c79ae3b..749eb07c6 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -26,9 +26,9 @@ book.html: $(SOURCES) book.is-valid all-local: book.html install-data-local: book.html - $(INSTALL) -d $(datadir)/nix/manual - $(INSTALL_DATA) book.html $(datadir)/nix/manual - $(INSTALL_DATA) style.css $(datadir)/nix/manual + $(INSTALL) -d $(DESTDIR)$(datadir)/nix/manual + $(INSTALL_DATA) book.html $(DESTDIR)$(datadir)/nix/manual + $(INSTALL_DATA) style.css $(DESTDIR)$(datadir)/nix/manual EXTRA_DIST = $(SOURCES) book.html book.is-valid $(MANS) diff --git a/nix.spec b/nix.spec new file mode 100644 index 000000000..9d459034b --- /dev/null +++ b/nix.spec @@ -0,0 +1,41 @@ +Summary: The Nix software deployment system +Name: nix +Version: 0.5 +Release: 1 +License: GPL +Group: WeetNiet +URL: http://www.cs.uu.nl/groups/ST/twiki/bin/view/Trace/NixDeploymentSystem +Source0: %{name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot +%define _prefix /nix +Prefix: %{_prefix} + +%description + +Nix is a software deployment system. + +%prep +%setup -q + +%build +./configure --prefix=%{_prefix} +make + +%install +rm -rf $RPM_BUILD_ROOT +make DESTDIR=$RPM_BUILD_ROOT install + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root) +%{_prefix}/bin +%{_prefix}/libexec +%{_prefix}/var +%{_prefix}/share +%{_prefix}/man +%config +%{_prefix}/etc +#%doc +#%{_prefix}/share/nix/manual diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 1662e99c1..65d9a4fd8 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -7,13 +7,13 @@ noinst_SCRIPTS = nix-profile.sh nix-pull nix-push: readmanifest.pm install-exec-local: readmanifest.pm - $(INSTALL) -d $(sysconfdir)/profile.d - $(INSTALL_PROGRAM) nix-profile.sh $(sysconfdir)/profile.d/nix.sh - $(INSTALL) -d $(libexecdir)/nix - $(INSTALL_DATA) readmanifest.pm $(libexecdir)/nix - $(INSTALL) -d $(sysconfdir)/nix + $(INSTALL) -d $(DESTDIR)$(sysconfdir)/profile.d + $(INSTALL_PROGRAM) nix-profile.sh $(DESTDIR)$(sysconfdir)/profile.d/nix.sh + $(INSTALL) -d $(DESTDIR)$(libexecdir)/nix + $(INSTALL_DATA) readmanifest.pm $(DESTDIR)$(libexecdir)/nix + $(INSTALL) -d $(DESTDIR)$(sysconfdir)/nix # !!! don't overwrite local modifications - $(INSTALL_DATA) prebuilts.conf $(sysconfdir)/nix/prebuilts.conf + $(INSTALL_DATA) prebuilts.conf $(DESTDIR)$(sysconfdir)/nix/prebuilts.conf include ../substitute.mk diff --git a/src/nix-store/Makefile.am b/src/nix-store/Makefile.am index 80e598742..74cf814a7 100644 --- a/src/nix-store/Makefile.am +++ b/src/nix-store/Makefile.am @@ -13,11 +13,11 @@ AM_CXXFLAGS = \ -I.. -I../../externals/inst/include -I../libutil -I../libstore -I../libmain install-data-local: - $(INSTALL) -d $(localstatedir)/nix - $(INSTALL) -d $(localstatedir)/nix/db - $(INSTALL) -d $(localstatedir)/nix/links - rm -f $(prefix)/current - ln -sf $(localstatedir)/nix/links/current $(prefix)/current - $(INSTALL) -d $(localstatedir)/log/nix - $(INSTALL) -d $(prefix)/store - $(bindir)/nix-store --init + $(INSTALL) -d $(DESTDIR)$(localstatedir)/nix + $(INSTALL) -d $(DESTDIR)$(localstatedir)/nix/db + $(INSTALL) -d $(DESTDIR)$(localstatedir)/nix/links + rm -f $(DESTDIR)$(prefix)/current + ln -sf $(localstatedir)/nix/links/current $(DESTDIR)$(prefix)/current + $(INSTALL) -d $(DESTDIR)$(localstatedir)/log/nix + $(INSTALL) -d $(DESTDIR)$(prefix)/store +# $(bindir)/nix-store --init From 0e09cc12c08ad2db0b8620ca537bd81ca45c05df Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 2 Jan 2004 14:58:25 +0000 Subject: [PATCH 0382/6440] * Add $prefix/store to the RPM. * Allow extra flags to be passed to RPM. --- Makefile.am | 2 +- nix.spec | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index f9fc8695f..99ec7f741 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,4 +3,4 @@ SUBDIRS = externals src scripts corepkgs doc EXTRA_DIST = substitute.mk nix.spec rpm: dist - rpm -ta $(distdir).tar.gz + rpm $(EXTRA_RPM_FLAGS) -ta $(distdir).tar.gz diff --git a/nix.spec b/nix.spec index 9d459034b..74fc18ef2 100644 --- a/nix.spec +++ b/nix.spec @@ -35,6 +35,7 @@ rm -rf $RPM_BUILD_ROOT %{_prefix}/var %{_prefix}/share %{_prefix}/man +%{_prefix}/store %config %{_prefix}/etc #%doc From 9ff365709541b8f50fddcf667ded07a5b9f774de Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 2 Jan 2004 16:04:53 +0000 Subject: [PATCH 0383/6440] * Generate RPM spec file. --- Makefile.am | 8 ++++++-- configure.ac | 4 ++-- nix.spec => nix.spec.in | 4 ++-- substitute.mk | 3 ++- 4 files changed, 12 insertions(+), 7 deletions(-) rename nix.spec => nix.spec.in (92%) diff --git a/Makefile.am b/Makefile.am index 99ec7f741..2b44a56a6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,10 @@ SUBDIRS = externals src scripts corepkgs doc -EXTRA_DIST = substitute.mk nix.spec +EXTRA_DIST = substitute.mk nix.spec nix.spec.in -rpm: dist +include ./substitute.mk + +nix.spec: nix.spec.in + +rpm: nix.spec dist rpm $(EXTRA_RPM_FLAGS) -ta $(distdir).tar.gz diff --git a/configure.ac b/configure.ac index 85244ebab..ecd97b1e7 100644 --- a/configure.ac +++ b/configure.ac @@ -5,9 +5,9 @@ AM_INIT_AUTOMAKE # Put the revision number in the version. if REVISION=`test -d $srcdir/.svn && svnversion $srcdir 2> /dev/null`; then - VERSION="$VERSION-r$REVISION" + VERSION="${VERSION}-pre${REVISION}" elif REVISION=`cat $srcdir/svn-revision 2> /dev/null`; then - VERSION="$VERSION-r$REVISION" + VERSION="${VERSION}-pre${REVISION}" fi AC_PREFIX_DEFAULT(/nix) diff --git a/nix.spec b/nix.spec.in similarity index 92% rename from nix.spec rename to nix.spec.in index 74fc18ef2..388c40751 100644 --- a/nix.spec +++ b/nix.spec.in @@ -1,11 +1,11 @@ Summary: The Nix software deployment system Name: nix -Version: 0.5 +Version: @version@ Release: 1 License: GPL Group: WeetNiet URL: http://www.cs.uu.nl/groups/ST/twiki/bin/view/Trace/NixDeploymentSystem -Source0: %{name}-%{version}.tar.gz +Source0: %{name}-@version@.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot %define _prefix /nix Prefix: %{_prefix} diff --git a/substitute.mk b/substitute.mk index cc21eba40..bbdb9b617 100644 --- a/substitute.mk +++ b/substitute.mk @@ -8,5 +8,6 @@ -e "s^@libexecdir\@^$(libexecdir)^g" \ -e "s^@system\@^$(system)^g" \ -e "s^@wget\@^$(wget)^g" \ + -e "s^@version\@^$(VERSION)^g" \ < $< > $@ || rm $@ - chmod +x $@ + if test -x $<; then chmod +x $@; fi From 0e68af0ce380b09c14ff36084499c0d8a6590b25 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 2 Jan 2004 16:09:59 +0000 Subject: [PATCH 0384/6440] * RPM sucks. --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index ecd97b1e7..e04b9684b 100644 --- a/configure.ac +++ b/configure.ac @@ -5,9 +5,9 @@ AM_INIT_AUTOMAKE # Put the revision number in the version. if REVISION=`test -d $srcdir/.svn && svnversion $srcdir 2> /dev/null`; then - VERSION="${VERSION}-pre${REVISION}" + VERSION="${VERSION}pre${REVISION}" elif REVISION=`cat $srcdir/svn-revision 2> /dev/null`; then - VERSION="${VERSION}-pre${REVISION}" + VERSION="${VERSION}pre${REVISION}" fi AC_PREFIX_DEFAULT(/nix) From f83c5e3e5f3e6b33c095d6559a4b3cd5922e88ce Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 5 Jan 2004 11:18:59 +0000 Subject: [PATCH 0385/6440] * Implemented Eelco V.'s `-p' command to switch profiles. It switches the symlink ~/.nix-userenv to the given argument (which defaults to .../links/current). /etc/profile.d/nix-profile creates this symlink if it doesn't exist yet. Example use: $ nix-env -l my_profile -i foo.nix subversion quake $ nix-env -p my_profile I don't like the term "profile". Let's deprecate it :-) --- scripts/nix-profile.sh.in | 27 +++++++++------------------ src/nix-env/help.txt | 2 ++ src/nix-env/main.cc | 22 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/scripts/nix-profile.sh.in b/scripts/nix-profile.sh.in index 0d059e1a2..064a6a347 100644 --- a/scripts/nix-profile.sh.in +++ b/scripts/nix-profile.sh.in @@ -1,20 +1,11 @@ -#if test -z "$NIX_SET"; then +if test -n "$HOME"; then + NIX_LINK="$HOME/.nix-userenv" -# export NIX_SET=1 + if ! test -a "$NIX_LINK"; then + echo "creating $NIX_LINK" + _NIX_DEF_LINK=@localstatedir@/nix/links/current + ln -s "$_NIX_DEF_LINK" "$NIX_LINK" + fi - NIX_LINKS=@localstatedir@/nix/links/current - - export PATH=$NIX_LINKS/bin:@prefix@/bin:$PATH - -# export LD_LIBRARY_PATH=$NIX_LINKS/lib:$LD_LIBRARY_PATH - - export LIBRARY_PATH=$NIX_LINKS/lib:$LIBRARY_PATH - - export C_INCLUDE_PATH=$NIX_LINKS/include:$C_INCLUDE_PATH - export CPLUS_INCLUDE_PATH=$NIX_LINKS/include:$CPLUS_INCLUDE_PATH - - export PKG_CONFIG_PATH=$NIX_LINKS/lib/pkgconfig:$PKG_CONFIG_PATH - -# export MANPATH=$NIX_LINKS/man:$MANPATH - -#fi + export PATH=$NIX_LINK/bin:@prefix@/bin:$PATH +fi diff --git a/src/nix-env/help.txt b/src/nix-env/help.txt index d940bd209..3f15e6a8e 100644 --- a/src/nix-env/help.txt +++ b/src/nix-env/help.txt @@ -12,6 +12,8 @@ Operations: The previous operations take a list of derivation names. The special name `*' may be used to indicate all derivations. + --profile / -p [FILE]: switch to specified user environment + --version: output version information --help: display help diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 7bced5be0..64ae6d412 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -605,6 +605,26 @@ static void opQuery(Globals & globals, } +static void opSwitchProfile(Globals & globals, + Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError(format("unknown flags `%1%'") % opFlags.front()); + if (opArgs.size() > 1) + throw UsageError(format("--profile takes at most one argument")); + + string linkPath = + opArgs.size() == 0 ? globals.linkPath : opArgs.front(); + + string homeDir(getenv("HOME")); + if (homeDir == "") throw Error("HOME environment variable not set"); + + string linkPathFinal = homeDir + "/.nix-userenv"; + + switchLink(linkPathFinal, linkPath); +} + + void run(Strings args) { /* Use a higher default verbosity (lvlInfo). */ @@ -635,6 +655,8 @@ void run(Strings args) format("`%1%' requires an argument") % arg); globals.linkPath = absPath(*i); } + else if (arg == "--profile" || arg == "-p") + op = opSwitchProfile; else if (arg[0] == '-') opFlags.push_back(arg); else From 4a373a3e9ac07a2d4c43d495c0a44883106ecfde Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 5 Jan 2004 16:26:43 +0000 Subject: [PATCH 0386/6440] * Implemented Eelco V.'s `nix-env -I' command to specify the default path of the Nix expression to be used with the import, upgrade, and query commands. For instance, $ nix-env -I ~/nixpkgs/pkgs/system/i686-linux.nix $ nix-env --query --available [aka -qa] sylpheed-0.9.7 bison-1.875 pango-1.2.5 subversion-0.35.1 ... $ nix-env -i sylpheed $ nix-env -u subversion There can be only one default at a time. * If the path to a Nix expression is a symlink, follow the symlink prior to resolving relative path references in the expression. --- src/libexpr/parser.cc | 18 +++++----- src/libstore/references.cc | 8 ++--- src/libutil/archive.cc | 5 +-- src/libutil/util.cc | 14 ++++++++ src/libutil/util.hh | 4 +++ src/nix-env/help.txt | 10 +++--- src/nix-env/main.cc | 71 +++++++++++++++++++++++++++----------- 7 files changed, 87 insertions(+), 43 deletions(-) diff --git a/src/libexpr/parser.cc b/src/libexpr/parser.cc index aecfa4348..b9e79e13d 100644 --- a/src/libexpr/parser.cc +++ b/src/libexpr/parser.cc @@ -29,16 +29,12 @@ struct Cleanup : TermFun ATMatcher m; string s; - if (atMatch(m, e) >> "Str" >> s) { + if (atMatch(m, e) >> "Str" >> s) return ATmake("Str()", string(s, 1, s.size() - 2).c_str()); - } - if (atMatch(m, e) >> "Path" >> s) { - if (s[0] != '/') - s = basePath + "/" + s; - return ATmake("Path()", canonPath(s).c_str()); - } + if (atMatch(m, e) >> "Path" >> s) + return ATmake("Path()", absPath(s, basePath).c_str()); if (atMatch(m, e) >> "Int" >> s) { istringstream s2(s); @@ -147,8 +143,14 @@ Expr parseExprFromFile(Path path) if (e) return e; #endif - /* If `path' refers to a directory, append `/default.nix'. */ + /* If `path' is a symlink, follow it. This is so that relative + path references work. */ struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting status of `%1%'") % path); + if (S_ISLNK(st.st_mode)) path = absPath(readLink(path), dirOf(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)) diff --git a/src/libstore/references.cc b/src/libstore/references.cc index 2bea44131..2daf4d4f4 100644 --- a/src/libstore/references.cc +++ b/src/libstore/references.cc @@ -59,12 +59,8 @@ void checkPath(const string & path, delete buf; /* !!! autodelete */ } - else if (S_ISLNK(st.st_mode)) { - char buf[st.st_size]; - if (readlink(path.c_str(), buf, st.st_size) != st.st_size) - throw SysError(format("reading symbolic link `%1%'") % path); - search(string(buf, st.st_size), ids, seen); - } + else if (S_ISLNK(st.st_mode)) + search(readLink(path), ids, seen); else throw Error(format("unknown file type: %1%") % path); } diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc index f605e8b61..90a039164 100644 --- a/src/libutil/archive.cc +++ b/src/libutil/archive.cc @@ -122,11 +122,8 @@ static void dump(const Path & path, DumpSink & sink) else if (S_ISLNK(st.st_mode)) { writeString("type", sink); writeString("symlink", sink); - char buf[st.st_size]; - if (readlink(path.c_str(), buf, st.st_size) != st.st_size) - throw SysError("reading symbolic link " + path); writeString("target", sink); - writeString(string(buf, st.st_size), sink); + writeString(readLink(path), sink); } else throw Error("unknown file type: " + path); diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 60b86b162..28e276a32 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -109,6 +109,20 @@ bool pathExists(const Path & path) } +Path readLink(const Path & path) +{ + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting status of `%1%'") % path); + if (!S_ISLNK(st.st_mode)) + throw Error(format("`%1%' is not a symlink") % path); + char buf[st.st_size]; + if (readlink(path.c_str(), buf, st.st_size) != st.st_size) + throw SysError(format("reading symbolic link `%1%'") % path); + return string(buf, st.st_size); +} + + Strings readDirectory(const Path & path) { Strings names; diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 4126381d9..5d27ac1bd 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -73,6 +73,10 @@ string baseNameOf(const Path & path); /* Return true iff the given path exists. */ bool pathExists(const Path & path); +/* Read the contents (target) of a symbolic link. The result is not + in any way canonicalised. */ +Path readLink(const Path & path); + /* Read the contents of a directory. The entries `.' and `..' are removed. */ Strings readDirectory(const Path & path); diff --git a/src/nix-env/help.txt b/src/nix-env/help.txt index 3f15e6a8e..823f5213a 100644 --- a/src/nix-env/help.txt +++ b/src/nix-env/help.txt @@ -4,15 +4,16 @@ nix-env [OPTIONS...] [ARGUMENTS...] Operations: - --install / -i FILE: add derivations to the user environment + --install / -i: add derivations to the user environment + --upgrade / -u: upgrade derivation in the user environment --uninstall / -e: remove derivations from the user environment - --upgrade / -u FILE: upgrade derivation in the user environment --query / -q: perform a query on an environment or Nix expression The previous operations take a list of derivation names. The special name `*' may be used to indicate all derivations. - --profile / -p [FILE]: switch to specified user environment + --profile / -p [FILE]: switch to specified user environment + --import / -I FILE: set default Nix expression --version: output version information --help: display help @@ -26,10 +27,11 @@ Query types: Query sources: --installed: use installed derivations (default) - --available / -f FILE: use derivations available in expression FILE + --available / -a: use derivations available in Nix expression Options: --link / -l LINK: use symlink LINK instead of (...)/current + --file / -f FILE: use Nix expression FILE for installation, etc. --verbose / -v: verbose operation (may be repeated) --keep-failed / -K: keep temporary directories of failed builds diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 64ae6d412..f0877b058 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -11,6 +11,7 @@ struct Globals { Path linkPath; + Path nixExprPath; EvalState state; }; @@ -106,12 +107,26 @@ void loadDerivations(EvalState & state, Path nePath, DrvInfos & drvs) } +static Path getHomeDir() +{ + Path homeDir(getenv("HOME")); + if (homeDir == "") throw Error("HOME environment variable not set"); + return homeDir; +} + + static Path getLinksDir() { return canonPath(nixStateDir + "/links"); } +static Path getDefNixExprPath() +{ + return getHomeDir() + "/.nix-defexpr"; +} + + void queryInstalled(EvalState & state, DrvInfos & drvs, const Path & userEnv) { @@ -410,13 +425,11 @@ static void opInstall(Globals & globals, { if (opFlags.size() > 0) throw UsageError(format("unknown flags `%1%'") % opFlags.front()); - if (opArgs.size() < 1) throw UsageError("Nix file expected"); - Path nePath = opArgs.front(); - DrvNames drvNames = drvNamesFromArgs( - Strings(++opArgs.begin(), opArgs.end())); + DrvNames drvNames = drvNamesFromArgs(opArgs); - installDerivations(globals.state, nePath, drvNames, globals.linkPath); + installDerivations(globals.state, globals.nixExprPath, + drvNames, globals.linkPath); } @@ -492,11 +505,10 @@ static void opUpgrade(Globals & globals, throw UsageError(format("unknown flags `%1%'") % opFlags.front()); if (opArgs.size() < 1) throw UsageError("Nix file expected"); - Path nePath = opArgs.front(); - DrvNames drvNames = drvNamesFromArgs( - Strings(++opArgs.begin(), opArgs.end())); + DrvNames drvNames = drvNamesFromArgs(opArgs); - upgradeDerivations(globals.state, nePath, drvNames, globals.linkPath); + upgradeDerivations(globals.state, globals.nixExprPath, + drvNames, globals.linkPath); } @@ -547,7 +559,7 @@ static void opQuery(Globals & globals, else if (*i == "--expr" || *i == "-e") query = qDrvPath; else if (*i == "--status" || *i == "-s") query = qStatus; else if (*i == "--installed") source = sInstalled; - else if (*i == "--available" || *i == "-f") source = sAvailable; + else if (*i == "--available" || *i == "-a") source = sAvailable; else throw UsageError(format("unknown flag `%1%'") % *i); /* Obtain derivation information from the specified source. */ @@ -560,10 +572,7 @@ static void opQuery(Globals & globals, break; case sAvailable: { - if (opArgs.size() < 1) throw UsageError("Nix file expected"); - Path nePath = opArgs.front(); - opArgs.pop_front(); - loadDerivations(globals.state, nePath, drvs); + loadDerivations(globals.state, globals.nixExprPath, drvs); break; } @@ -611,20 +620,31 @@ static void opSwitchProfile(Globals & globals, if (opFlags.size() > 0) throw UsageError(format("unknown flags `%1%'") % opFlags.front()); if (opArgs.size() > 1) - throw UsageError(format("--profile takes at most one argument")); + throw UsageError(format("`--profile' takes at most one argument")); - string linkPath = + Path linkPath = opArgs.size() == 0 ? globals.linkPath : opArgs.front(); + Path linkPathFinal = getHomeDir() + "/.nix-userenv"; - string homeDir(getenv("HOME")); - if (homeDir == "") throw Error("HOME environment variable not set"); - - string linkPathFinal = homeDir + "/.nix-userenv"; - switchLink(linkPathFinal, linkPath); } +static void opDefaultExpr(Globals & globals, + Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError(format("unknown flags `%1%'") % opFlags.front()); + if (opArgs.size() != 1) + throw UsageError(format("`--import' takes exactly one argument")); + + Path defNixExpr = opArgs.front(); + Path defNixExprLink = getDefNixExprPath(); + + switchLink(defNixExprLink, defNixExpr); +} + + void run(Strings args) { /* Use a higher default verbosity (lvlInfo). */ @@ -635,6 +655,7 @@ void run(Strings args) Globals globals; globals.linkPath = getLinksDir() + "/current"; + globals.nixExprPath = getDefNixExprPath(); for (Strings::iterator i = args.begin(); i != args.end(); ++i) { string arg = *i; @@ -649,12 +670,20 @@ void run(Strings args) op = opUpgrade; else if (arg == "--query" || arg == "-q") op = opQuery; + else if (arg == "--import" || arg == "-I") /* !!! bad name */ + op = opDefaultExpr; else if (arg == "--link" || arg == "-l") { ++i; if (i == args.end()) throw UsageError( format("`%1%' requires an argument") % arg); globals.linkPath = absPath(*i); } + else if (arg == "--file" || arg == "-f") { + ++i; + if (i == args.end()) throw UsageError( + format("`%1%' requires an argument") % arg); + globals.nixExprPath = absPath(*i); + } else if (arg == "--profile" || arg == "-p") op = opSwitchProfile; else if (arg[0] == '-') From 2f0b93904bf88e9d2bf9d623b35991dd6dd0c521 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 6 Jan 2004 16:35:07 +0000 Subject: [PATCH 0387/6440] * Install images. --- doc/manual/Makefile.am | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 749eb07c6..74475b3ff 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -20,7 +20,7 @@ man1_MANS = nix-store.1 nix-instantiate.1 man $(MANS): $(SOURCES) book.is-valid $(XSLTPROC) $(docbookxsl)/manpages/docbook.xsl book.xml -book.html: $(SOURCES) book.is-valid +book.html: $(SOURCES) book.is-valid images $(XSLTPROC) --output book.html $(docbookxsl)/html/docbook.xsl book.xml all-local: book.html @@ -29,6 +29,14 @@ install-data-local: book.html $(INSTALL) -d $(DESTDIR)$(datadir)/nix/manual $(INSTALL_DATA) book.html $(DESTDIR)$(datadir)/nix/manual $(INSTALL_DATA) style.css $(DESTDIR)$(datadir)/nix/manual + cp -r images $(DESTDIR)$(datadir)/nix/manual/images + +images: + mkdir images + cp $(docbookxsl)/images/*.png images + mkdir images/callouts + cp $(docbookxsl)/images/callouts/*.png images/callouts + chmod +w -R images EXTRA_DIST = $(SOURCES) book.html book.is-valid $(MANS) From 1ff986d51a4fc7f77a80f5c23351f9d2cbb26550 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 6 Jan 2004 16:38:32 +0000 Subject: [PATCH 0388/6440] * book -> manual --- doc/manual/Makefile.am | 24 ++++++++++++------------ doc/manual/{book.xml => manual.xml} | 0 2 files changed, 12 insertions(+), 12 deletions(-) rename doc/manual/{book.xml => manual.xml} (100%) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 74475b3ff..eaa0300fc 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -6,28 +6,28 @@ XSLTPROC = $(ENV) $(xsltproc) $(xmlflags) --catalogs \ --param section.label.includes.component.label 1 \ --param html.stylesheet \'style.css\' -SOURCES = book.xml introduction.xml installation.xml overview.xml \ +SOURCES = manual.xml introduction.xml installation.xml overview.xml \ common-options.xml nix-store.xml nix-instantiate.xml \ troubleshooting.xml bugs.xml \ style.css -book.is-valid: $(SOURCES) - $(XMLLINT) --noout --valid book.xml +manual.is-valid: $(SOURCES) + $(XMLLINT) --noout --valid manual.xml touch $@ man1_MANS = nix-store.1 nix-instantiate.1 -man $(MANS): $(SOURCES) book.is-valid - $(XSLTPROC) $(docbookxsl)/manpages/docbook.xsl book.xml +man $(MANS): $(SOURCES) manual.is-valid + $(XSLTPROC) $(docbookxsl)/manpages/docbook.xsl manual.xml -book.html: $(SOURCES) book.is-valid images - $(XSLTPROC) --output book.html $(docbookxsl)/html/docbook.xsl book.xml +manual.html: $(SOURCES) manual.is-valid images + $(XSLTPROC) --output manual.html $(docbookxsl)/html/docbook.xsl manual.xml -all-local: book.html +all-local: manual.html -install-data-local: book.html +install-data-local: manual.html $(INSTALL) -d $(DESTDIR)$(datadir)/nix/manual - $(INSTALL_DATA) book.html $(DESTDIR)$(datadir)/nix/manual + $(INSTALL_DATA) manual.html $(DESTDIR)$(datadir)/nix/manual $(INSTALL_DATA) style.css $(DESTDIR)$(datadir)/nix/manual cp -r images $(DESTDIR)$(datadir)/nix/manual/images @@ -38,6 +38,6 @@ images: cp $(docbookxsl)/images/callouts/*.png images/callouts chmod +w -R images -EXTRA_DIST = $(SOURCES) book.html book.is-valid $(MANS) +EXTRA_DIST = $(SOURCES) manual.html manual.is-valid $(MANS) -DISTCLEANFILES = book.html book.is-valid $(MANS) +DISTCLEANFILES = manual.html manual.is-valid $(MANS) diff --git a/doc/manual/book.xml b/doc/manual/manual.xml similarity index 100% rename from doc/manual/book.xml rename to doc/manual/manual.xml From abe8c8c2aa24646955b78cd2c826483d8d8a1149 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 7 Jan 2004 10:59:38 +0000 Subject: [PATCH 0389/6440] * Include images/ in distribution. --- doc/manual/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index eaa0300fc..595773f1c 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -9,7 +9,7 @@ XSLTPROC = $(ENV) $(xsltproc) $(xmlflags) --catalogs \ SOURCES = manual.xml introduction.xml installation.xml overview.xml \ common-options.xml nix-store.xml nix-instantiate.xml \ troubleshooting.xml bugs.xml \ - style.css + style.css images manual.is-valid: $(SOURCES) $(XMLLINT) --noout --valid manual.xml From 7959354379416bd8513cb00e636c0310e42eaa01 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 7 Jan 2004 15:53:14 +0000 Subject: [PATCH 0390/6440] * Upgraded to Berkeley DB 4.2.52. The main advantage of 4.2 is that it automatically removes log files when they are no longer needed. *** IMPORTANT *** If you have an existing Nix installation, you must checkpoint the Nix database to prevent recent transactions from being undone. Do the following: - optional: make a backup of $prefix/var/nix/db. - run `db_checkpoint' from Berkeley DB 4.1: $ db_checkpoint -h $prefix/var/nix/db -1 - optional (?): run `db_recover' from Berkeley DB 4.1: $ db_recover -h $prefix/var/nix/db - remove $prefix/var/nix/db/log* and $prefix/var/nix/db/__db* --- externals/Makefile.am | 11 ++++++----- src/libstore/db.cc | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/externals/Makefile.am b/externals/Makefile.am index 61122f4ab..c74cb227e 100644 --- a/externals/Makefile.am +++ b/externals/Makefile.am @@ -1,11 +1,11 @@ # Berkeley DB -DB = db-4.1.25 +DB = db-4.2.52 $(DB).tar.gz: @echo "Nix requires Berkeley DB to build." - @echo "Please download version 4.1.25 from" - @echo " http://www.sleepycat.com/update/snapshot/db-4.1.25.tar.gz" + @echo "Please download version 4.2.52 from" + @echo " http://www.sleepycat.com/update/snapshot/db-4.2.52.tar.gz" @echo "and place it in the externals/ directory." false @@ -20,8 +20,9 @@ build-db: have-db (pfx=`pwd` && \ cd $(DB)/build_unix && \ CC="$(CC)" CXX="$(CXX)" CFLAGS="$(CFLAGS)" CXXFLAGS="$(CXXFLAGS)" \ - ../dist/configure --prefix=$$pfx/inst \ - --enable-cxx --disable-shared && \ + ../dist/configure --prefix=$$pfx/inst \ + --enable-cxx --disable-shared --disable-cryptography \ + --disable-replication --disable-verify && \ make && \ make install) touch build-db diff --git a/src/libstore/db.cc b/src/libstore/db.cc index a97111a3a..72823be8b 100644 --- a/src/libstore/db.cc +++ b/src/libstore/db.cc @@ -160,7 +160,7 @@ void Database::open(const string & path) env->set_lg_bsize(32 * 1024); /* default */ env->set_lg_max(256 * 1024); /* must be > 4 * lg_bsize */ env->set_lk_detect(DB_LOCK_DEFAULT); - env->set_flags(DB_TXN_WRITE_NOSYNC, 1); + env->set_flags(DB_TXN_WRITE_NOSYNC | DB_LOG_AUTOREMOVE, 1); /* The following code provides automatic recovery of the From 5346536b626a047d53f9c44d6562cfaeffe27f14 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 8 Jan 2004 10:45:23 +0000 Subject: [PATCH 0391/6440] * Include version number in manual. --- doc/manual/Makefile.am | 11 ++++++++--- doc/manual/introduction.xml | 2 +- doc/manual/manual.xml | 3 +++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 595773f1c..8f4c84790 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -11,10 +11,13 @@ SOURCES = manual.xml introduction.xml installation.xml overview.xml \ troubleshooting.xml bugs.xml \ style.css images -manual.is-valid: $(SOURCES) +manual.is-valid: $(SOURCES) version.xml $(XMLLINT) --noout --valid manual.xml touch $@ +version.xml: + echo -n $(VERSION) > version.xml + man1_MANS = nix-store.1 nix-instantiate.1 man $(MANS): $(SOURCES) manual.is-valid @@ -38,6 +41,8 @@ images: cp $(docbookxsl)/images/callouts/*.png images/callouts chmod +w -R images -EXTRA_DIST = $(SOURCES) manual.html manual.is-valid $(MANS) +KEEP = manual.html manual.is-valid version.xml $(MANS) -DISTCLEANFILES = manual.html manual.is-valid $(MANS) +EXTRA_DIST = $(SOURCES) $(KEEP) + +DISTCLEANFILES = $(KEEP) diff --git a/doc/manual/introduction.xml b/doc/manual/introduction.xml index 7759cd65a..b41cad0a8 100644 --- a/doc/manual/introduction.xml +++ b/doc/manual/introduction.xml @@ -2,7 +2,7 @@ Introduction - The number of Nix installations in the world has grown to 4, + The number of Nix installations in the world has grown to 5, with more expected. diff --git a/doc/manual/manual.xml b/doc/manual/manual.xml index e8896b073..9f88dd409 100644 --- a/doc/manual/manual.xml +++ b/doc/manual/manual.xml @@ -11,11 +11,14 @@ + ]> Nix: A System for Software Deployment + Draft (Version &version;) + Eelco From b5942155314ea4b479fabde6ce236866f5ef4b97 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 8 Jan 2004 15:01:37 +0000 Subject: [PATCH 0392/6440] * Manual updates. --- doc/manual/Makefile.am | 4 +- doc/manual/bugs.xml | 30 +-- doc/manual/common-options.xml | 13 - doc/manual/manual.xml | 3 +- doc/manual/nix-store.xml | 458 ++++++++++++++++++--------------- doc/manual/opt-verbose.xml | 73 ++++++ doc/manual/troubleshooting.xml | 19 +- 7 files changed, 346 insertions(+), 254 deletions(-) delete mode 100644 doc/manual/common-options.xml create mode 100644 doc/manual/opt-verbose.xml diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 8f4c84790..a0fa19406 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -7,8 +7,8 @@ XSLTPROC = $(ENV) $(xsltproc) $(xmlflags) --catalogs \ --param html.stylesheet \'style.css\' SOURCES = manual.xml introduction.xml installation.xml overview.xml \ - common-options.xml nix-store.xml nix-instantiate.xml \ - troubleshooting.xml bugs.xml \ + nix-store.xml nix-instantiate.xml \ + troubleshooting.xml bugs.xml opt-verbose.xml \ style.css images manual.is-valid: $(SOURCES) version.xml diff --git a/doc/manual/bugs.xml b/doc/manual/bugs.xml index fcb69c364..33bd96a02 100644 --- a/doc/manual/bugs.xml +++ b/doc/manual/bugs.xml @@ -5,24 +5,20 @@ - Nix should automatically remove Berkeley DB logfiles. - - - - - - Unify the concepts of successors and substitutes into a general notion - of equivalent expressions. Expressions are - equivalent if they have the same target paths with the same - identifiers. However, even though they are functionally equivalent, - they may differ stronly with respect to their performance - characteristics. For example, realising a slice is more - efficient that realising the derivation from which that slice was + Unify the concepts of successors and substitutes into a + general notion of equivalent expressions. + Expressions are equivalent if they have the same target paths + with the same identifiers. However, even though they are + functionally equivalent, they may differ stronly with respect + to their performance characteristics. + For example, realising a closure expression is more efficient + that realising the derivation expression from which it was produced. On the other hand, distributing sources may be more - efficient (storage- or bandwidth-wise) than distributing binaries. So - we need to be able to attach weigths or priorities or performance - annotations to expressions; Nix can then choose the most efficient - expression dependent on the context. + efficient (storage- or bandwidth-wise) than distributing + binaries. So we need to be able to attach weigths or + priorities or performance annotations to expressions; Nix can + then choose the most efficient expression dependent on the + context. diff --git a/doc/manual/common-options.xml b/doc/manual/common-options.xml deleted file mode 100644 index d04042993..000000000 --- a/doc/manual/common-options.xml +++ /dev/null @@ -1,13 +0,0 @@ - - Common options - - - - - - - diff --git a/doc/manual/manual.xml b/doc/manual/manual.xml index 9f88dd409..1df0d1fc9 100644 --- a/doc/manual/manual.xml +++ b/doc/manual/manual.xml @@ -6,7 +6,7 @@ - + @@ -36,7 +36,6 @@ Command Reference - &common-options; nix-store &nix-store; diff --git a/doc/manual/nix-store.xml b/doc/manual/nix-store.xml index 686fe4c15..36abf7af3 100644 --- a/doc/manual/nix-store.xml +++ b/doc/manual/nix-store.xml @@ -7,10 +7,6 @@ nix-store - - - - @@ -25,106 +21,36 @@ - + Description - The command nix provides access to the Nix store. This - is the (set of) path(s) where Nix expressions and the file system objects - built by them are stored. + The command nix-store performs primitive + operations on the Nix store. You generally do not need to run + this command manually. - nix has many subcommands called - operations. These are individually documented - below. Exactly one operation must always be provided. + nix-store takes exactly one + operation flag which indicated the + subcommand to be performed. These are individually + documented below. - + - - Common Options + + Common options - In this section the options that are common to all Nix operations are - listed. These options are allowed for every subcommand (although they - may not always have an effect). + This section lists the options that are common to all Nix + operations. These options are allowed for every subcommand, + though they may not always have an effect. - - - - - Indicates that any identifier arguments to the operation are paths - in the store rather than identifiers. - - - - - - - - - Increases the level of verbosity of diagnostic messages printed on - standard error. For each Nix operation, the information printed on - standard output is well-defined and specified below in the - respective sections. Any diagnostic information is printed on - standard error, never on standard output. - - - - This option may be specified repeatedly. Currently, the following - verbosity levels exist: - - - - - 0 - - - Print error messages only. - - - - - 1 - - - Print informational messages. - - - - - 2 - - - Print even more informational messages. - - - - - 3 - - - Print messages that should only be useful for debugging. - - - - - 4 - - - Vomit mode: print vast amounts of debug - information. - - - - - - - + &opt-verbose; @@ -140,65 +66,103 @@ - + + + Environment variables + + + The following environment variables affect the behaviour of + nix-store. + + + + + + TMPDIR=path + + + Use the directory path to store + temporary files. In particular, this includes temporary + build directories; these can take up substantial amounts + of disk space. The default is /tmp. + + + + + + + + + + - - Operation <option>--install</option> + + Operation <option>--realise</option> - + Synopsis - nix + nix-store - - + + - ids + paths - + - + Description - The operation realises the Nix expressions - identified by ids in the file system. If - these expressions are derivation expressions, they are first - normalised. That is, their target paths are are built, unless a normal - form is already known. + The operation realises in the file + system the store expressions stored in + paths. If these expressions are + derivation expressions, they are first + normalised into a closure expression. + This may happen in two ways. First, the corresponding closure + expression (the successor) may already + known (either because the build has already been performed, or + because a successor was explicitly registered through the + operation). Otherwise, the build + action described by the derivation is performed, and a closure + expression is computed by scanning the result of the build for + references to other paths in the store. - The identifiers of the normal forms of the given Nix expressions are - printed on standard output. + The paths of the closure expression corresponding to each + expression in paths is printed on + standard output. - + - + + - + Operation <option>--delete</option> - + Synopsis - nix + nix-store paths - + - + Description @@ -215,24 +179,24 @@ inconsistent system. Deletion of paths in the store is done by the garbage collector (which uses to delete unreferenced paths). - - + - + + - + Operation <option>--query</option> - + Synopsis - nix + nix-store @@ -244,34 +208,28 @@ - - - - - - - - - + + + args - + - + Description The operation displays various bits of - information about Nix expressions or paths in the store. The queries + information about store expressions or store paths. The queries are described in . At most one query - can be specified; the default query is . + can be specified. The default query is . - + - + Queries @@ -280,34 +238,15 @@ - Prints out the target paths of the Nix expressions indicated by - the identifiers args. In the case of - a derivation expression, these are the paths that will be - produced by the builder of the expression. In the case of a - slice expression, these are the root paths (which are generally - the paths that were produced by the builder of the derivation - expression of which the slice is a normal form). + Prints out the output paths of the + store expressions indicated by the identifiers + args. In the case of a + derivation expression, these are the paths that will be + produced when the derivation is realised. In the case + of a closure expression, these are the paths that were + produced the derivation expression of which the closure + expression is a successor. - - - This query has one option: - - - - - - - - - Causes the target paths of the normal - forms of the expressions to be printed, rather - than the target paths of the expressions themselves. - - - - - - @@ -315,40 +254,42 @@ - Prints out the requisite paths of the Nix expressions indicated - by the identifiers args. The - requisite paths of a Nix expression are the paths that need to be - present in the system to be able to realise the expression. That - is, they form the closure of the expression - in the file system (i.e., no path in the set of requisite paths - points to anything outside the set of requisite paths). + Prints out the requisite paths of the store expressions + indicated by the identifiers + args. The requisite paths of + a Nix expression are the paths that need to be present + in the system to be able to realise the expression. + That is, they form the closure of + the expression in the file system (i.e., no path in the + set of requisite paths points to anything outside the + set of requisite paths). - The notion of requisite paths is very useful when one wants to - distribute Nix expressions. Since they form a closure, they are - the only paths one needs to distribute to another system to be - able to realise the expression on the other system. + The notion of requisite paths is very useful when one + wants to distribute store expressions. Since they form a + closure, they are the only paths one needs to distribute + to another system to be able to realise the expression + on the other system. - This query is generally used to implement various kinds of - distribution. A source distribution is - obtained by distributing the requisite paths of a derivation - expression. A binary distribution is - obtained by distributing the requisite paths of a slice - expression (i.e., the normal form of a derivation expression; you - can directly specify the identifier of the slice expression, or - use and specify the identifier of a - derivation expression). A cache - distribution is obtained by distributing the - requisite paths of a derivation expression and specifying the - option . This will include - not just the paths of a source and binary distribution, but also - all expressions and paths of subterms of the source. This is - useful if one wants to realise on the target system a Nix - expression that is similar but not quite the same as the one - being distributed, since any common subterms will be reused. + This query is generally used to implement various kinds + of deployment. A source deployment + is obtained by distributing the requisite paths of a + derivation expression. A binary + deployment is obtained by distributing the + requisite paths of a closure expression. A + cache deployment is obtained by + distributing the requisite paths of a derivation + expression and specifying the option + . This will + include not just the paths of a source and binary + deployment, but also all expressions and paths of + subterms of the source. This is useful if one wants to + realise on the target system a Nix expression that is + similar but not quite the same as the one being + distributed, since any common subterms will be reused. @@ -361,9 +302,10 @@ - Causes the requisite paths of the normal - forms of the expressions to be printed, rather - than the requisite paths of the expressions themselves. + Causes the requisite paths of the + successor of the given store + expressions to be printed, rather than the + requisite paths of the expressions themselves. @@ -372,9 +314,10 @@ - Excludes the paths of Nix expressions. This causes the - closure property to be lost, that is, the resulting set of - paths is not enough to ensure realisibility. + Excludes the paths of store expressions. This + causes the closure property to be lost, that is, + the resulting set of paths is not enough to ensure + realisibility. @@ -406,12 +349,16 @@ - + - For each identifier in args, prints - all expansions of that identifier, that is, all paths whose - current content matches the identifier. + For each store expression stored at paths + args, prints its + predecessors. A derivation + expression p is a predecessor of a + store expression q iff + q is a successor of + p. @@ -420,18 +367,121 @@ - Prints a graph of the closure of the expressions identified by - args in the format of the - dot tool of AT&T's GraphViz package. + Prints a graph of the closure of the store expressions + identified by args in the + format of the dot tool of AT&T's + GraphViz package. - + + + + + + + + + + Operation <option>--successor</option> + + + Synopsis + + nix-store + + + + srcpath sucpath + + + + + Description + + + The operation registers that the + closure expression in sucpath is a + successor of the derivation expression in + srcpath. This is used to implement + binary deployment. + + + + + + + + + + + + Operation <option>--substitute</option> + + + Synopsis + + nix-store + + + + srcpath subpath + + + + + Description + + + The operation registers that the + store path srcpath can be built by + realising the derivation expression in + subpath. This is used to implement + binary deployment. + + + + + + + + + + + + Operation <option>--verify</option> + + + Synopsis + + nix-store + + + + + + + + Description + + + The operation verifies the internal + consistency of the Nix database, and the consistency between + the Nix database and the Nix store. Any inconsistencies + encountered are automatically repaired. Inconsistencies are + generally the result of the Nix store or database being + modified by non-Nix tools, or of bugs in Nix itself. + + + + + - diff --git a/doc/manual/opt-verbose.xml b/doc/manual/opt-verbose.xml new file mode 100644 index 000000000..5a1007a6a --- /dev/null +++ b/doc/manual/opt-verbose.xml @@ -0,0 +1,73 @@ + + + + + Increases the level of verbosity of diagnostic messages printed + on standard error. For each Nix operation, the information + printed on standard output is well-defined; any diagnostic + information is printed on standard error, never on standard + output. + + + + This option may be specified repeatedly. Currently, the + following verbosity levels exist: + + + + + 0 + + + Errors only: only print messages explaining + why the Nix invocation failed. + + + + + 1 + + + Informational: print + useful messages about what Nix is + doing. + + + + + 2 + + + Talkative: print more informational messages. + + + + + 3 + + + Chatty: print even more informational messages. + + + + + 4 + + + Debug: print debug information: + + + + + 5 + + + Vomit: print vast amounts of debug + information. + + + + + + + diff --git a/doc/manual/troubleshooting.xml b/doc/manual/troubleshooting.xml index 1e35c6079..529943f91 100644 --- a/doc/manual/troubleshooting.xml +++ b/doc/manual/troubleshooting.xml @@ -1,22 +1,9 @@ Troubleshooting - - Database logfile removal - - - Every time a Nix database transaction takes place, Nix writes a record of - this transaction to a log in its database directory - to ensure that the operation can be replayed in case of a application or - system crash. However, without manual intervention, the log grows - indefinitely. Hence, unused log files should be deleted periodically. - This can be accomplished using the following command: - - - - $ rm `db_archive -a -h prefix/var/nix/db` - - + + (Nothing.) + From 30b31a8f6190dad40ca972b445420e10f47a9afc Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 8 Jan 2004 16:56:40 +0000 Subject: [PATCH 0393/6440] * Start of nix-env reference. * Some CSS tweaks. --- doc/manual/Makefile.am | 4 +- doc/manual/manual.xml | 5 ++ doc/manual/nix-env.xml | 166 +++++++++++++++++++++++++++++++++++++++ doc/manual/nix-store.xml | 24 +++--- doc/manual/style.css | 32 ++++++-- 5 files changed, 215 insertions(+), 16 deletions(-) create mode 100644 doc/manual/nix-env.xml diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index a0fa19406..f0344ef64 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -7,7 +7,7 @@ XSLTPROC = $(ENV) $(xsltproc) $(xmlflags) --catalogs \ --param html.stylesheet \'style.css\' SOURCES = manual.xml introduction.xml installation.xml overview.xml \ - nix-store.xml nix-instantiate.xml \ + nix-env.xml nix-store.xml nix-instantiate.xml \ troubleshooting.xml bugs.xml opt-verbose.xml \ style.css images @@ -18,7 +18,7 @@ manual.is-valid: $(SOURCES) version.xml version.xml: echo -n $(VERSION) > version.xml -man1_MANS = nix-store.1 nix-instantiate.1 +man1_MANS = nix-env.1 nix-store.1 nix-instantiate.1 man $(MANS): $(SOURCES) manual.is-valid $(XSLTPROC) $(docbookxsl)/manpages/docbook.xsl manual.xml diff --git a/doc/manual/manual.xml b/doc/manual/manual.xml index 1df0d1fc9..e95b0fc91 100644 --- a/doc/manual/manual.xml +++ b/doc/manual/manual.xml @@ -7,6 +7,7 @@ + @@ -36,6 +37,10 @@ Command Reference + + nix-env + &nix-env; + nix-store &nix-store; diff --git a/doc/manual/nix-env.xml b/doc/manual/nix-env.xml new file mode 100644 index 000000000..d8b50bf3c --- /dev/null +++ b/doc/manual/nix-env.xml @@ -0,0 +1,166 @@ + + + nix-env + manipulate or query Nix user environments + + + + + nix-env + + + + + + + + + + + + + + path + + operation + options + arguments + + + + + Description + + + The command nix-env is used to manipulate Nix + user environments. User environments are sets of software + components available to a user at some point in time. In other + words, they are a synthesised view of the programs available in + the Nix store. There may be many user environments: different + users can have different environments, and individual users can + switch between different environments. + + + + + + nix-env takes exactly one + operation flag which indicates the + subcommand to be performed. These are documented below. + + + + + + + + + + Common options + + + This section lists the options that are common to all + operations. These options are allowed for every subcommand, + though they may not always have an effect. + + + + + &opt-verbose; + + + + + + Specifies the Nix expression used by the + , , + and operations to + obtain derivations. The default is + ~/.nix-defexpr. + + + + + + + + + + + + + + Files + + + + + ~/.nix-defexpr + + + The default Nix expression used by the + , , + and operations to + obtain derivations. It is generally a symbolic link to + some other location set using the + operation. The + option may be used to override + this default. + + + + + + ~/.nix-userenv + + + A symbolic link to the user's current user environment. + By default, it points to + prefix/var/nix/links/current. + The PATH environment variable should + include ~/.nix-userenv for the use + environments to be visible to the user. + + + + + + + + + + + + + + Operation <option>--install</option> + + + Synopsis + + nix-env + + + + + + + + Description + + + + + + + + + + diff --git a/doc/manual/nix-store.xml b/doc/manual/nix-store.xml index 36abf7af3..cbd38cd23 100644 --- a/doc/manual/nix-store.xml +++ b/doc/manual/nix-store.xml @@ -11,7 +11,7 @@ - + @@ -32,18 +32,21 @@ nix-store takes exactly one - operation flag which indicated the - subcommand to be performed. These are individually - documented below. + operation flag which indicates the + subcommand to be performed. These are documented below. + + + + Common options - This section lists the options that are common to all Nix + This section lists the options that are common to all operations. These options are allowed for every subcommand, though they may not always have an effect. @@ -68,6 +71,9 @@ + + + Environment variables @@ -221,10 +227,10 @@ Description - The operation displays various bits of - information about store expressions or store paths. The queries - are described in . At most one query - can be specified. The default query is . + The operation displays various bits + of information about store expressions or store paths. The + queries are described below. At most one query can be + specified. The default query is . diff --git a/doc/manual/style.css b/doc/manual/style.css index a7d8809f0..3ff9edbd4 100644 --- a/doc/manual/style.css +++ b/doc/manual/style.css @@ -20,19 +20,41 @@ h1,h2,h3 text-align: left; } -h1 +h1 /* title */ { - font-size: 185%; + font-size: 200%; } -h2 +h2 /* chapters, appendices, subtitle */ +{ + font-size: 180%; +} + +/* Extra space between chapters, appendices. */ +div.chapter > div.titlepage h2, div.appendix > div.titlepage h2 +{ + margin-top: 1.5em; +/* border-top: solid #005aa0; */ +} + +div.sect1 h2 /* sections */ { font-size: 150%; } -h3 +div.refnamediv h2, div.refsynopsisdiv h2, div.refsection h2 /* refentry parts */ { - font-size: 120%; + font-size: 125%; +} + +div.refsection h3 +{ + font-size: 110%; +} + +h3 /* subsections */ +{ + font-size: 125%; } From 46a71c857c617b5acbf3d1fdb8fb7e676a4881a2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 9 Jan 2004 14:18:28 +0000 Subject: [PATCH 0394/6440] * Option `--force-realise' in `nix-store --query'. --- doc/manual/nix-env.xml | 29 ++++----- doc/manual/nix-store.xml | 124 +++++++++++++++++++++---------------- doc/manual/opt-verbose.xml | 2 +- src/nix-store/main.cc | 17 +++-- 4 files changed, 98 insertions(+), 74 deletions(-) diff --git a/doc/manual/nix-env.xml b/doc/manual/nix-env.xml index d8b50bf3c..702faa621 100644 --- a/doc/manual/nix-env.xml +++ b/doc/manual/nix-env.xml @@ -7,18 +7,14 @@ nix-env - - - - - - - - + + + + - - - + + + path @@ -76,7 +72,7 @@ &opt-verbose; - + / Specifies the Nix expression used by the @@ -125,8 +121,8 @@ By default, it points to prefix/var/nix/links/current. The PATH environment variable should - include ~/.nix-userenv for the use - environments to be visible to the user. + include ~/.nix-userenv for the user + environment to be visible to the user. @@ -146,8 +142,9 @@ Synopsis nix-env - - + + + diff --git a/doc/manual/nix-store.xml b/doc/manual/nix-store.xml index cbd38cd23..479c1c3b0 100644 --- a/doc/manual/nix-store.xml +++ b/doc/manual/nix-store.xml @@ -7,14 +7,10 @@ nix-store - - - - - - - - + + + + operation options arguments @@ -56,7 +52,7 @@ &opt-verbose; - + / Specifies that in case of a build failure, the temporary directory @@ -112,9 +108,9 @@ Synopsis nix-store - - - + + + paths @@ -160,9 +156,9 @@ Synopsis nix-store - - - + + + paths @@ -203,22 +199,22 @@ Synopsis nix-store - - - + + + - - - - - - - - - - - + + + + + + + + + + + args @@ -235,13 +231,55 @@ + + + Common query options + + + + + / + + + For those queries that take a Nix store expression, this + option causes those expressions to be normalised first. + + + + + + / + + + For those queries that take a Nix store expression, this + option causes those expressions to be realised first. + This is just a short-cut for the common idiom + + +nix-store --realise /nix/store/bla.store +x=`nix-store --query --normalise /nix/store/bla.store +(do something with the path $x + + which using this flag can be written as + + +x=`nix-store --query --normalise --force-realise /nix/store/bla.store +(do something with the path $x + + + + + + + + Queries - + / Prints out the output paths of the @@ -257,7 +295,7 @@ - + / Prints out the requisite paths of the store expressions @@ -304,18 +342,6 @@ - - - - - Causes the requisite paths of the - successor of the given store - expressions to be printed, rather than the - requisite paths of the expressions themselves. - - - - @@ -398,9 +424,7 @@ Synopsis nix-store - - - + srcpath sucpath @@ -432,9 +456,7 @@ Synopsis nix-store - - - + srcpath subpath @@ -466,9 +488,7 @@ Synopsis nix-store - - - + diff --git a/doc/manual/opt-verbose.xml b/doc/manual/opt-verbose.xml index 5a1007a6a..3868acf30 100644 --- a/doc/manual/opt-verbose.xml +++ b/doc/manual/opt-verbose.xml @@ -1,5 +1,5 @@ - + / Increases the level of verbosity of diagnostic messages printed diff --git a/src/nix-store/main.cc b/src/nix-store/main.cc index 48752c2bf..078618a5d 100644 --- a/src/nix-store/main.cc +++ b/src/nix-store/main.cc @@ -61,9 +61,14 @@ static void opAdd(Strings opFlags, Strings opArgs) } -Path maybeNormalise(const Path & ne, bool normalise) +Path maybeNormalise(const Path & ne, bool normalise, bool realise) { - return normalise ? normaliseStoreExpr(ne) : ne; + if (realise) { + Path ne2 = normaliseStoreExpr(ne); + realiseClosure(ne2); + return normalise ? ne2 : ne; + } else + return normalise ? normaliseStoreExpr(ne) : ne; } @@ -73,6 +78,7 @@ static void opQuery(Strings opFlags, Strings opArgs) enum { qList, qRequisites, qPredecessors, qGraph } query = qList; bool normalise = false; + bool realise = false; bool includeExprs = true; bool includeSuccessors = false; @@ -83,6 +89,7 @@ static void opQuery(Strings opFlags, Strings opArgs) else if (*i == "--predecessors") query = qPredecessors; else if (*i == "--graph") query = qGraph; else if (*i == "--normalise" || *i == "-n") normalise = true; + else if (*i == "--force-realise" || *i == "-f") realise = true; else if (*i == "--exclude-exprs") includeExprs = false; else if (*i == "--include-successors") includeSuccessors = true; else throw UsageError(format("unknown flag `%1%'") % *i); @@ -94,7 +101,7 @@ static void opQuery(Strings opFlags, Strings opArgs) i != opArgs.end(); i++) { StringSet paths = storeExprRoots( - maybeNormalise(checkPath(*i), normalise)); + maybeNormalise(checkPath(*i), normalise, realise)); for (StringSet::iterator j = paths.begin(); j != paths.end(); j++) cout << format("%s\n") % *j; @@ -108,7 +115,7 @@ static void opQuery(Strings opFlags, Strings opArgs) i != opArgs.end(); i++) { StringSet paths2 = storeExprRequisites( - maybeNormalise(checkPath(*i), normalise), + maybeNormalise(checkPath(*i), normalise, realise), includeExprs, includeSuccessors); paths.insert(paths2.begin(), paths2.end()); } @@ -134,7 +141,7 @@ static void opQuery(Strings opFlags, Strings opArgs) PathSet roots; for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); i++) - roots.insert(maybeNormalise(checkPath(*i), normalise)); + roots.insert(maybeNormalise(checkPath(*i), normalise, realise)); printDotGraph(roots); break; } From 4c4fe7a114968a9c7accd86499f029314530f44c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Jan 2004 10:44:48 +0000 Subject: [PATCH 0395/6440] * Changed the extension for store expressions from ".nix" to ".store" (following the Usenix paper). --- src/libstore/storeexpr.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/storeexpr.cc b/src/libstore/storeexpr.cc index e11cd5bfe..45b5af055 100644 --- a/src/libstore/storeexpr.cc +++ b/src/libstore/storeexpr.cc @@ -15,7 +15,7 @@ Path writeTerm(ATerm t, const string & suffix) Hash h = hashTerm(t); Path path = canonPath(nixStore + "/" + - (string) h + suffix + ".nix"); + (string) h + suffix + ".store"); if (!isValidPath(path)) { char * s = ATwriteToString(t); From 23fbc72f5d65725dd1804efe695aaa84580a8637 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 13 Jan 2004 11:53:12 +0000 Subject: [PATCH 0396/6440] * Print error messages, not debug messages. --- src/libstore/store.cc | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libstore/store.cc b/src/libstore/store.cc index 20625b9fe..c1d95ab8c 100644 --- a/src/libstore/store.cc +++ b/src/libstore/store.cc @@ -400,7 +400,7 @@ void verifyStore() for (Paths::iterator i = paths.begin(); i != paths.end(); ++i) { Path path = *i; if (!pathExists(path)) { - debug(format("path `%1%' disappeared") % path); + printMsg(lvlError, format("path `%1%' disappeared") % path); invalidatePath(path, txn); } else validPaths.insert(path); @@ -424,7 +424,8 @@ void verifyStore() nixDB.queryStrings(txn, dbSubstitutes, *i, subPaths); for (Paths::iterator j = subPaths.begin(); j != subPaths.end(); ++j) if (validPaths.find(*j) == validPaths.end()) - debug(format("found substitute mapping to non-existent path `%1%'") % *j); + printMsg(lvlError, + format("found substitute mapping to non-existent path `%1%'") % *j); else subPaths2.push_back(*j); if (subPaths.size() != subPaths2.size()) @@ -439,7 +440,8 @@ void verifyStore() nixDB.enumTable(txn, dbSubstitutesRev, rsubs); for (Paths::iterator i = rsubs.begin(); i != rsubs.end(); ++i) { if (validPaths.find(*i) == validPaths.end()) { - debug(format("found reverse substitute mapping for non-existent path `%1%'") % *i); + printMsg(lvlError, + format("found reverse substitute mapping for non-existent path `%1%'") % *i); nixDB.delPair(txn, dbSubstitutesRev, *i); } } @@ -455,7 +457,8 @@ void verifyStore() if (nixDB.queryString(txn, dbSuccessors, *i, sucPath) && usablePaths.find(sucPath) == usablePaths.end()) { - debug(format("found successor mapping to non-existent path `%1%'") % sucPath); + printMsg(lvlError, + format("found successor mapping to non-existent path `%1%'") % sucPath); nixDB.delPair(txn, dbSuccessors, *i); } } @@ -466,7 +469,8 @@ void verifyStore() nixDB.enumTable(txn, dbSuccessorsRev, rsucs); for (Paths::iterator i = rsucs.begin(); i != rsucs.end(); ++i) { if (usablePaths.find(*i) == usablePaths.end()) { - debug(format("found reverse successor mapping for non-existent path `%1%'") % *i); + printMsg(lvlError, + format("found reverse successor mapping for non-existent path `%1%'") % *i); nixDB.delPair(txn, dbSuccessorsRev, *i); } } From 698e880c9f03b3aaade13897de3498a5915ea8fd Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 13 Jan 2004 12:36:43 +0000 Subject: [PATCH 0397/6440] * Tricky: make sure that the accessor count is not reset to 0 if recovery fails. --- src/libstore/db.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libstore/db.cc b/src/libstore/db.cc index 72823be8b..057371ef5 100644 --- a/src/libstore/db.cc +++ b/src/libstore/db.cc @@ -155,6 +155,7 @@ void Database::open(const string & path) /* Create the database environment object. */ + DbEnv * env = 0; /* !!! close on error */ env = new DbEnv(0); env->set_lg_bsize(32 * 1024); /* default */ @@ -199,7 +200,6 @@ void Database::open(const string & path) other readers or writers. */ int n = getAccessorCount(fdAccessors); - setAccessorCount(fdAccessors, 1); if (n != 0) { printMsg(lvlTalkative, @@ -213,6 +213,8 @@ void Database::open(const string & path) /* Open the environment normally. */ openEnv(env, path, 0); + setAccessorCount(fdAccessors, 1); + /* Downgrade to a read lock. */ debug(format("downgrading to read lock on `%1%'") % lockPath); lockFile(fdLock, ltRead, true); @@ -236,6 +238,8 @@ void Database::open(const string & path) openEnv(env, path, 0); } + this->env = env; + } catch (DbException e) { rethrow(e); } } From 3495d153b317a0853abd7114f29b44517f7c3813 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 13 Jan 2004 13:37:25 +0000 Subject: [PATCH 0398/6440] * Periodically checkpoint the log. --- src/libstore/db.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libstore/db.cc b/src/libstore/db.cc index 057371ef5..c89d6b197 100644 --- a/src/libstore/db.cc +++ b/src/libstore/db.cc @@ -261,7 +261,9 @@ void Database::close() delete db; } -// env->txn_checkpoint(0, 0, 0); + /* Do a checkpoint every 128 kilobytes, or every 5 minutes. */ + env->txn_checkpoint(128, 5, 0); + env->close(0); } catch (DbException e) { rethrow(e); } From ff9af107d3aa1362af906972c490773eeaaad4b5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 13 Jan 2004 16:35:43 +0000 Subject: [PATCH 0399/6440] * Option `-B' to always show the output of builders, regardless of verbosity level. --- doc/manual/nix-env.xml | 2 ++ doc/manual/nix-store.xml | 2 ++ doc/manual/opt-verbose.xml | 14 ++++++++++++++ src/libmain/shared.cc | 2 ++ src/libstore/exec.cc | 2 +- src/libstore/globals.cc | 2 ++ src/libstore/globals.hh | 4 ++++ 7 files changed, 27 insertions(+), 1 deletion(-) diff --git a/doc/manual/nix-env.xml b/doc/manual/nix-env.xml index 702faa621..c73716381 100644 --- a/doc/manual/nix-env.xml +++ b/doc/manual/nix-env.xml @@ -9,6 +9,8 @@ nix-env + + diff --git a/doc/manual/nix-store.xml b/doc/manual/nix-store.xml index 479c1c3b0..8c6bcfe37 100644 --- a/doc/manual/nix-store.xml +++ b/doc/manual/nix-store.xml @@ -9,6 +9,8 @@ nix-store + + operation diff --git a/doc/manual/opt-verbose.xml b/doc/manual/opt-verbose.xml index 3868acf30..53fe07ae7 100644 --- a/doc/manual/opt-verbose.xml +++ b/doc/manual/opt-verbose.xml @@ -71,3 +71,17 @@ + + + / + + + Causes the output written by build actions to standard output + and standard error to be echoed to standard error, regardless of + verbosity level. By default, it is only echoed at a verbosity + level of at least 4 (Debug), and is suppressed at + lower levels. Note that it is always written to a log file in + prefix/nix/var/log/nix. + + + diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 92349488e..24bedb3fb 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -51,6 +51,8 @@ static void initAndRun(int argc, char * * argv) string arg = *i; if (arg == "--verbose" || arg == "-v") verbosity = (Verbosity) ((int) verbosity + 1); + else if (arg == "--build-output" || arg == "-B") + buildVerbosity = lvlError; /* lowest */ else if (arg == "--help") { printHelp(); return; diff --git a/src/libstore/exec.cc b/src/libstore/exec.cc index 2adf03841..b25423b44 100644 --- a/src/libstore/exec.cc +++ b/src/libstore/exec.cc @@ -22,7 +22,7 @@ void runProgram(const string & program, { /* Create a log file. */ string logCommand = - verbosity >= lvlDebug + verbosity >= buildVerbosity ? "tee " + logFileName + " >&2" : "cat > " + logFileName; /* !!! auto-pclose on exit */ diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index e5d76ff48..b3c658c29 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -7,3 +7,5 @@ string nixStateDir = "/UNINIT"; string nixDBPath = "/UNINIT"; bool keepFailed = false; + +Verbosity buildVerbosity = lvlDebug; diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 3da294cc8..5d5e9efcf 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -2,6 +2,7 @@ #define __GLOBALS_H #include +#include "util.hh" using namespace std; @@ -28,5 +29,8 @@ extern string nixDBPath; /* Whether to keep temporary directories of failed builds. */ extern bool keepFailed; +/* Verbosity level for build output. */ +extern Verbosity buildVerbosity; + #endif /* !__GLOBALS_H */ From 16f9b133ec8c1fc6226d486e5170dd3a43aa35a7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 14 Jan 2004 11:13:08 +0000 Subject: [PATCH 0400/6440] * Improved `nix-push': it now uses HTTP PUT (instead of rsync) to copy files. Target location is no longer hard-coded; it accepts a number of URLs on the command line. * `nix-install-package': compatibility fixes. --- scripts/nix-install-package.in | 16 ++--- scripts/nix-push.in | 121 +++++++++++++++++++++------------ 2 files changed, 86 insertions(+), 51 deletions(-) diff --git a/scripts/nix-install-package.in b/scripts/nix-install-package.in index 4988606c3..c71a6ca5f 100644 --- a/scripts/nix-install-package.in +++ b/scripts/nix-install-package.in @@ -12,26 +12,26 @@ until mkdir $tmpdir, 0777; # !!! remove tmpdir on exit -print "unpacking $pkgfile in $tmpdir...\n"; +print "Unpacking $pkgfile in $tmpdir...\n"; system "bunzip2 < $pkgfile | (cd $tmpdir && tar xf -)"; die if $?; -print "this package contains the following derivations:\n"; -system "nix-env -qsf $tmpdir/default.nix"; +print "This package contains the following derivations:\n"; +system "nix-env -qasf $tmpdir/default.nix"; die if $?; -print "do you wish to install them (y/n)? "; +print "Do you wish to install these (Y/N)? "; my $reply = ; chomp $reply; exit if (!($reply eq "y")); -print "pulling caches...\n"; +print "Pulling caches...\n"; system "nix-pull `cat $tmpdir/caches`"; die if $?; -print "installing package...\n"; -system "nix-env -i $tmpdir/default.nix '*'"; +print "Installing package...\n"; +system "nix-env -if $tmpdir/default.nix '*'"; die if $?; -print "installing succeeded! (enter to continue)\n"; +print "Installation succeeded! Press Enter to continue.\n"; ; diff --git a/scripts/nix-push.in b/scripts/nix-push.in index 275f5e99b..20883e011 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -12,40 +12,52 @@ my $manifest = "$tmpdir/MANIFEST"; END { unlink $manifest; unlink $nixfile; rmdir $tmpdir; } -my @paths; +my $curl = "curl --fail --silent"; -foreach my $id (@ARGV) { - die unless $id =~ /^\//; + +# Parse the command line. +my $archives_put_url = shift @ARGV; +my $archives_get_url = shift @ARGV; +my $manifest_put_url = shift @ARGV; + + +# From the given store expressions, determine the requisite store +# paths. +my %storepaths; + +foreach my $storeexpr (@ARGV) { + die unless $storeexpr =~ /^\//; # Get all paths referenced by the normalisation of the given # Nix expression. - system "nix-store --realise $id > /dev/null"; + system "nix-store --realise $storeexpr > /dev/null"; die if ($?); - open PATHS, "nix-store --query --requisites --include-successors $id 2> /dev/null |" or die; + open PATHS, "nix-store --query --requisites --include-successors $storeexpr 2> /dev/null |" or die; while () { chomp; die "bad: $_" unless /^\//; - push @paths, $_; + $storepaths{$_} = ""; } close PATHS; } +my @storepaths = keys %storepaths; + + # For each path, create a Nix expression that turns the path into # a Nix archive. open NIX, ">$nixfile"; print NIX "["; -foreach my $path (@paths) { - - die unless ($path =~ /\/[0-9a-z]{32}.*$/); - print "$path\n"; +foreach my $storepath (@storepaths) { + die unless ($storepath =~ /\/[0-9a-z]{32}.*$/); # Construct a Nix expression that creates a Nix archive. my $nixexpr = "((import @datadir@/nix/corepkgs/nar/nar.nix) " . - # !!! $path should be represented as a closure - "{path = \"$path\"; system = \"@system@\";}) "; + # !!! $storepath should be represented as a closure + "{path = \"$storepath\"; system = \"@system@\";}) "; print NIX $nixexpr; } @@ -54,44 +66,51 @@ print NIX "]"; close NIX; -# Instantiate a store expression from the Nix expression. -my @nids; -print STDERR "instantiating Nix expression...\n"; -open NIDS, "nix-instantiate $nixfile |" or die "cannot run nix-instantiate"; -while () { +# Instantiate store expressions from the Nix expression. +my @storeexprs; +print STDERR "instantiating store expressions...\n"; +open STOREEXPRS, "nix-instantiate $nixfile |" or die "cannot run nix-instantiate"; +while () { chomp; die unless /^\//; - push @nids, $_; - print "$_\n"; + push @storeexprs, $_; } -close NIDS; +close STOREEXPRS; -# Realise the store expression. +# Realise the store expressions. print STDERR "creating archives...\n"; -system "nix-store --realise -v @nids > /dev/null"; -if ($?) { die "`nix-store --realise' failed"; } my @narpaths; -open NIDS, "nix-store --query --list @nids |" or die "cannot run nix"; -while () { - chomp; - die unless (/^\//); - push @narpaths, "$_"; -} -close NIDS; - +my @tmp = @storeexprs; +while (scalar @tmp > 0) { + my $n = scalar @tmp; + if ($n > 256) { $n = 256 }; + my @tmp2 = @tmp[0..$n - 1]; + @tmp = @tmp[$n..scalar @tmp - 1]; + + system "nix-store --realise -B @tmp2 > /dev/null"; + if ($?) { die "`nix-store --realise' failed"; } + + open NARPATHS, "nix-store --query --list @tmp2 |" or die "cannot run nix"; + while () { + chomp; + die unless (/^\//); + push @narpaths, "$_"; + } + close NARPATHS; +} + + # Create the manifest. print STDERR "creating manifest...\n"; open MANIFEST, ">$manifest"; -my @pushlist; -push @pushlist, $manifest; - -for (my $n = 0; $n < scalar @paths; $n++) { - my $storepath = $paths[$n]; +my @nararchives; +for (my $n = 0; $n < scalar @storepaths; $n++) { + my $storepath = $storepaths[$n]; my $nardir = $narpaths[$n]; $storepath =~ /\/([^\/]*)$/; @@ -102,7 +121,7 @@ for (my $n = 0; $n < scalar @paths; $n++) { my $narfile = "$nardir/$narname"; (-f $narfile) or die "narfile for $storepath not found"; - push @pushlist, $narfile; + push @nararchives, $narfile; open MD5, "$nardir/md5" or die "cannot open hash"; my $hash = ; @@ -112,10 +131,10 @@ for (my $n = 0; $n < scalar @paths; $n++) { print MANIFEST "{\n"; print MANIFEST " StorePath: $storepath\n"; - print MANIFEST " NarName: $narname\n"; + print MANIFEST " NarURL: $archives_get_url/$narname\n"; print MANIFEST " MD5: $hash\n"; - if ($storepath =~ /\.nix$/) { + if ($storepath =~ /\.store$/) { open PREDS, "nix-store --query --predecessors $storepath |" or die "cannot run nix"; while () { chomp; @@ -131,8 +150,24 @@ for (my $n = 0; $n < scalar @paths; $n++) { close MANIFEST; -# Push the prebuilts to the server. !!! FIXME -print STDERR "pushing to server...\n"; -if (scalar @pushlist > 0) { - system "rsync -av -e ssh @pushlist eelco\@losser.st-lab.cs.uu.nl:/home/eelco/public_html/nix-dist/"; +# Upload the archives. +print STDERR "uploading archives...\n"; +foreach my $nararchive (@nararchives) { + + $nararchive =~ /\/([^\/]*)$/; + my $basename = $1; + + if (system("$curl --head $archives_get_url/$basename > /dev/null") != 0) { + print STDERR " $nararchive\n"; + system("$curl --show-error --upload-file " . + "'$nararchive' '$archives_put_url/$basename' > /dev/null") == 0 or + die "curl failed on $nararchive: $?"; + } } + + +# Upload the manifest. +print STDERR "uploading manifest...\n"; +system("$curl --show-error --upload-file " . + "'$manifest' '$manifest_put_url/' > /dev/null") == 0 or + die "curl failed on $manifest: $?"; From 9a404e45c9e8257e2d353d569e189a4b003159b5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 14 Jan 2004 14:20:33 +0000 Subject: [PATCH 0401/6440] * Synchronous `nix-pull' with `nix-push'. * Use curl instead of wget. --- scripts/nix-pull.in | 2 +- scripts/nix-push.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index f0d5d95f2..2b2d4e857 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -64,7 +64,7 @@ $fullexpr .= "]"; # Instantiate store expressions from the Nix expressions we created above. -print STDERR "instantiating Nix expression...\n"; +print STDERR "instantiating store expressions...\n"; my $pid = open2(\*READ, \*WRITE, "nix-instantiate -") or die "cannot run nix-instantiate"; print WRITE $fullexpr; diff --git a/scripts/nix-push.in b/scripts/nix-push.in index 20883e011..1e1d905ed 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -159,7 +159,7 @@ foreach my $nararchive (@nararchives) { if (system("$curl --head $archives_get_url/$basename > /dev/null") != 0) { print STDERR " $nararchive\n"; - system("$curl --show-error --upload-file " . + system("$curl --show-error --upload-file " . "'$nararchive' '$archives_put_url/$basename' > /dev/null") == 0 or die "curl failed on $nararchive: $?"; } From 55e11bc0d3e3c440503ee71d21c3ef5e34431cbb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 15 Jan 2004 14:43:00 +0000 Subject: [PATCH 0402/6440] * In `nix-env --query --status', determine the `I' bit by looking at the output path of a derivation, not the path of its store expression. This ensures that changes that affect the path of the store expression but not the output path, do not affect the `installed' state of a derivation. --- src/nix-env/main.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index f0877b058..5c27a89fc 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -600,9 +600,15 @@ static void opQuery(Globals & globals, DrvInfos installed; queryInstalled(globals.state, installed, globals.linkPath); + PathSet installedPaths; /* output paths of installed drvs */ + for (DrvInfos::iterator i = installed.begin(); + i != installed.end(); ++i) + installedPaths.insert(i->second.outPath); + for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) { cout << format("%1%%2% %3%\n") - % (installed.find(i->first) != installed.end() ? 'I' : '-') + % (installedPaths.find(i->second.outPath) + != installedPaths.end() ? 'I' : '-') % (isValidPath(i->second.outPath) ? 'P' : '-') % i->second.name; } From 08719c6c97e25fb362eeb7463d8b764ecefc53cb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 15 Jan 2004 20:13:54 +0000 Subject: [PATCH 0403/6440] * Obsolete. --- src/libexpr/fix-expr.cc | 215 ---------------------------------------- src/libexpr/fix-expr.hh | 75 -------------- 2 files changed, 290 deletions(-) delete mode 100644 src/libexpr/fix-expr.cc delete mode 100644 src/libexpr/fix-expr.hh diff --git a/src/libexpr/fix-expr.cc b/src/libexpr/fix-expr.cc deleted file mode 100644 index e9c5a3ba6..000000000 --- a/src/libexpr/fix-expr.cc +++ /dev/null @@ -1,215 +0,0 @@ -#include "fix-expr.hh" -#include "expr.hh" - - -ATermMap::ATermMap(unsigned int initialSize, unsigned int maxLoadPct) -{ - table = ATtableCreate(initialSize, maxLoadPct); - if (!table) throw Error("cannot create ATerm table"); -} - - -ATermMap::ATermMap(const ATermMap & map) - : table(0) -{ - ATermList keys = map.keys(); - - /* !!! adjust allocation for load pct */ - table = ATtableCreate(ATgetLength(keys), map.maxLoadPct); - if (!table) throw Error("cannot create ATerm table"); - - for (ATermIterator i(keys); i; ++i) - set(*i, map.get(*i)); -} - - -ATermMap::~ATermMap() -{ - if (table) ATtableDestroy(table); -} - - -void ATermMap::set(ATerm key, ATerm value) -{ - return ATtablePut(table, key, value); -} - - -void ATermMap::set(const string & key, ATerm value) -{ - set(string2ATerm(key), value); -} - - -ATerm ATermMap::get(ATerm key) const -{ - return ATtableGet(table, key); -} - - -ATerm ATermMap::get(const string & key) const -{ - return get(string2ATerm(key)); -} - - -void ATermMap::remove(ATerm key) -{ - ATtableRemove(table, key); -} - - -void ATermMap::remove(const string & key) -{ - remove(string2ATerm(key)); -} - - -ATermList ATermMap::keys() const -{ - ATermList keys = ATtableKeys(table); - if (!keys) throw Error("cannot query aterm map keys"); - return keys; -} - - -ATerm string2ATerm(const string & s) -{ - return (ATerm) ATmakeAppl0(ATmakeAFun((char *) s.c_str(), 0, ATtrue)); -} - - -string aterm2String(ATerm t) -{ - return ATgetName(ATgetAFun(t)); -} - - -ATerm bottomupRewrite(TermFun & f, ATerm e) -{ - if (ATgetType(e) == AT_APPL) { - AFun fun = ATgetAFun(e); - int arity = ATgetArity(fun); - ATermList args = ATempty; - - for (int i = arity - 1; i >= 0; i--) - args = ATinsert(args, bottomupRewrite(f, ATgetArgument(e, i))); - - e = (ATerm) ATmakeApplList(fun, args); - } - - else if (ATgetType(e) == AT_LIST) { - ATermList in = (ATermList) e; - ATermList out = ATempty; - - for (ATermIterator i(in); i; ++i) - out = ATinsert(out, bottomupRewrite(f, *i)); - - e = (ATerm) ATreverse(out); - } - - return f(e); -} - - -void queryAllAttrs(Expr e, ATermMap & attrs) -{ - ATMatcher m; - ATermList bnds; - if (!(atMatch(m, e) >> "Attrs" >> bnds)) - throw badTerm("expected attribute set", e); - - for (ATermIterator i(bnds); i; ++i) { - string s; - Expr e; - if (!(atMatch(m, *i) >> "Bind" >> s >> e)) - abort(); /* can't happen */ - attrs.set(s, e); - } -} - - -Expr queryAttr(Expr e, const string & name) -{ - ATermMap attrs; - queryAllAttrs(e, attrs); - return attrs.get(name); -} - - -Expr makeAttrs(const ATermMap & attrs) -{ - ATermList bnds = ATempty; - for (ATermIterator i(attrs.keys()); i; ++i) - bnds = ATinsert(bnds, - ATmake("Bind(, )", *i, attrs.get(*i))); - return ATmake("Attrs()", ATreverse(bnds)); -} - - -Expr substitute(const ATermMap & subs, Expr e) -{ - ATMatcher m; - string s; - - if (atMatch(m, e) >> "Var" >> s) { - Expr sub = subs.get(s); - return sub ? sub : e; - } - - /* In case of a function, filter out all variables bound by this - function. */ - ATermList formals; - ATerm body; - if (atMatch(m, e) >> "Function" >> formals >> body) { - ATermMap subs2(subs); - for (ATermIterator i(formals); i; ++i) { - Expr def; - if (!(atMatch(m, *i) >> "NoDefFormal" >> s) && - !(atMatch(m, *i) >> "DefFormal" >> s >> def)) - abort(); - subs2.remove(s); - } - return ATmake("Function(, )", formals, - substitute(subs2, body)); - } - - /* Idem for a mutually recursive attribute set. */ - ATermList bindings; - if (atMatch(m, e) >> "Rec" >> bindings) { - ATermMap subs2(subs); - for (ATermIterator i(bindings); i; ++i) { - Expr e; - if (!(atMatch(m, *i) >> "Bind" >> s >> e)) - abort(); /* can't happen */ - subs2.remove(s); - } - return ATmake("Rec()", substitute(subs2, (ATerm) bindings)); - } - - if (ATgetType(e) == AT_APPL) { - AFun fun = ATgetAFun(e); - int arity = ATgetArity(fun); - ATermList args = ATempty; - - for (int i = arity - 1; i >= 0; i--) - args = ATinsert(args, substitute(subs, ATgetArgument(e, i))); - - return (ATerm) ATmakeApplList(fun, args); - } - - if (ATgetType(e) == AT_LIST) { - ATermList out = ATempty; - for (ATermIterator i((ATermList) e); i; ++i) - out = ATinsert(out, substitute(subs, *i)); - return (ATerm) ATreverse(out); - } - - return e; -} - - -Expr makeBool(bool b) -{ - return b ? ATmake("Bool(True)") : ATmake("Bool(False)"); -} diff --git a/src/libexpr/fix-expr.hh b/src/libexpr/fix-expr.hh deleted file mode 100644 index 6c1e51d9c..000000000 --- a/src/libexpr/fix-expr.hh +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef __FIXEXPR_H -#define __FIXEXPR_H - -#include - -#include - -#include "util.hh" - - -/* Fix 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; - - -/* Mappings from ATerms to ATerms. This is just a wrapper around - ATerm tables. */ -class ATermMap -{ -private: - unsigned int maxLoadPct; - ATermTable table; - -public: - ATermMap(unsigned int initialSize = 16, unsigned int maxLoadPct = 75); - ATermMap(const ATermMap & map); - ~ATermMap(); - - void set(ATerm key, ATerm value); - void set(const string & key, ATerm value); - - ATerm get(ATerm key) const; - ATerm get(const string & key) const; - - void remove(ATerm key); - void remove(const string & key); - - ATermList keys() const; -}; - - -/* Convert a string to an ATerm (i.e., a quoted nullary function - applicaton). */ -ATerm string2ATerm(const string & s); -string aterm2String(ATerm t); - -/* 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 -{ - virtual ATerm operator () (ATerm e) = 0; -}; -ATerm bottomupRewrite(TermFun & f, ATerm e); - -/* Query all attributes in an attribute set expression. The - expression must be in normal form. */ -void queryAllAttrs(Expr e, ATermMap & attrs); - -/* Query a specific attribute from an attribute set expression. The - expression must be in normal form. */ -Expr queryAttr(Expr e, const string & name); - -/* 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 ATermMap & subs, Expr e); - -/* Create an expression representing a boolean. */ -Expr makeBool(bool b); - - -#endif /* !__FIXEXPR_H */ From 447089a5f699f085661287dec4b3d88219f67068 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 15 Jan 2004 20:23:55 +0000 Subject: [PATCH 0404/6440] * Catch SIGINT to terminate cleanly when the user tries to interrupt Nix. This is to prevent Berkeley DB from becoming wedged. Unfortunately it is not possible to throw C++ exceptions from a signal handler. In fact, you can't do much of anything except change variables of type `volatile sig_atomic_t'. So we set an interrupt flag in the signal handler and check it at various strategic locations in the code (by calling checkInterrupt()). Since this is unlikely to cover all cases (e.g., (semi-)infinite loops), sometimes SIGTERM may now be required to kill Nix. --- src/libexpr/eval.cc | 2 ++ src/libexpr/nixexpr.cc | 4 ++++ src/libexpr/parser.cc | 2 ++ src/libmain/shared.cc | 15 +++++++++++++++ src/libstore/db.cc | 9 ++++++++- src/libstore/exec.cc | 4 +++- src/libstore/normalise.cc | 6 ++++++ src/libstore/pathlocks.cc | 7 ++++++- src/libstore/references.cc | 3 +++ src/libstore/store.cc | 2 +- src/libutil/archive.cc | 6 ++++++ src/libutil/util.cc | 17 +++++++++++++++++ src/libutil/util.hh | 13 +++++++++++++ 13 files changed, 86 insertions(+), 4 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index f6634e892..0470deee9 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -227,6 +227,8 @@ Expr evalExpr2(EvalState & state, Expr e) Expr evalExpr(EvalState & state, Expr e) { + checkInterrupt(); + startNest(nest, lvlVomit, format("evaluating expression: %1%") % e); diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 816b39dc1..dd0f5d58a 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -87,6 +87,8 @@ string aterm2String(ATerm t) ATerm bottomupRewrite(TermFun & f, ATerm e) { + checkInterrupt(); + if (ATgetType(e) == AT_APPL) { AFun fun = ATgetAFun(e); int arity = ATgetArity(fun); @@ -149,6 +151,8 @@ Expr makeAttrs(const ATermMap & attrs) Expr substitute(const ATermMap & subs, Expr e) { + checkInterrupt(); + ATMatcher m; string s; diff --git a/src/libexpr/parser.cc b/src/libexpr/parser.cc index b9e79e13d..83b656342 100644 --- a/src/libexpr/parser.cc +++ b/src/libexpr/parser.cc @@ -26,6 +26,8 @@ struct Cleanup : TermFun virtual ATerm operator () (ATerm e) { + checkInterrupt(); + ATMatcher m; string s; diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 24bedb3fb..17d4dda67 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -12,6 +12,12 @@ extern "C" { #include "config.h" +void sigintHandler(int signo) +{ + _isInterrupted = 1; +} + + /* Initialize and reorder arguments, then call the actual argument processor. */ static void initAndRun(int argc, char * * argv) @@ -23,6 +29,15 @@ static void initAndRun(int argc, char * * argv) nixStateDir = (string) NIX_STATE_DIR; nixDBPath = (string) NIX_STATE_DIR + "/db"; + /* Catch SIGINT. */ + struct sigaction act, oact; + act.sa_handler = sigintHandler; + sigfillset(&act.sa_mask); + act.sa_flags = 0; + if (sigaction(SIGINT, &act, &oact)) + throw SysError("installing handler for SIGINT"); + printMsg(lvlError, "SIG HANDLER INSTALLED"); + /* Put the arguments in a vector. */ Strings args, remaining; while (argc--) args.push_back(*argv++); diff --git a/src/libstore/db.cc b/src/libstore/db.cc index c89d6b197..d2a002638 100644 --- a/src/libstore/db.cc +++ b/src/libstore/db.cc @@ -80,6 +80,7 @@ void Transaction::moveTo(Transaction & t) void Database::requireEnv() { + checkInterrupt(); if (!env) throw Error("database environment not open"); } @@ -310,6 +311,8 @@ TableId Database::openTable(const string & tableName) bool Database::queryString(const Transaction & txn, TableId table, const string & key, string & data) { + checkInterrupt(); + try { Db * db = getDb(table); @@ -367,6 +370,7 @@ bool Database::queryStrings(const Transaction & txn, TableId table, 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()); @@ -402,6 +406,7 @@ void Database::setStrings(const Transaction & txn, TableId table, 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()); @@ -423,9 +428,11 @@ void Database::enumTable(const Transaction & txn, TableId table, DestroyDbc destroyDbc(dbc); Dbt kt, dt; - while (dbc->get(&kt, &dt, DB_NEXT) != DB_NOTFOUND) + while (dbc->get(&kt, &dt, DB_NEXT) != DB_NOTFOUND) { + checkInterrupt(); keys.push_back( string((char *) kt.get_data(), kt.get_size())); + } } catch (DbException e) { rethrow(e); } } diff --git a/src/libstore/exec.cc b/src/libstore/exec.cc index b25423b44..01577143d 100644 --- a/src/libstore/exec.cc +++ b/src/libstore/exec.cc @@ -108,7 +108,9 @@ void runProgram(const string & program, int status; if (waitpid(pid, &status, 0) != pid) throw Error("unable to wait for child"); - + + checkInterrupt(); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { if (keepFailed) { printMsg(lvlTalkative, diff --git a/src/libstore/normalise.cc b/src/libstore/normalise.cc index 7ef45e292..51f90207e 100644 --- a/src/libstore/normalise.cc +++ b/src/libstore/normalise.cc @@ -96,6 +96,7 @@ Path normaliseStoreExpr(const Path & _nePath, PathSet pending) for (PathSet::iterator i = ne.derivation.inputs.begin(); i != ne.derivation.inputs.end(); i++) { + checkInterrupt(); Path nfPath = normaliseStoreExpr(*i, pending); realiseClosure(nfPath, pending); /* !!! nfPath should be a root of the garbage collector while @@ -193,6 +194,7 @@ Path normaliseStoreExpr(const Path & _nePath, PathSet pending) for (Paths::iterator j = refPaths.begin(); j != refPaths.end(); j++) { + checkInterrupt(); Path path = *j; elem.refs.insert(path); if (inClosures.find(path) != inClosures.end()) @@ -209,6 +211,7 @@ Path normaliseStoreExpr(const Path & _nePath, PathSet pending) PathSet donePaths; while (!usedPaths.empty()) { + checkInterrupt(); PathSet::iterator i = usedPaths.begin(); Path path = *i; usedPaths.erase(i); @@ -291,6 +294,7 @@ void ensurePath(const Path & path, PathSet pending) for (Paths::iterator i = subPaths.begin(); i != subPaths.end(); i++) { + checkInterrupt(); try { normaliseStoreExpr(*i, pending); if (isValidPath(path)) return; @@ -337,6 +341,8 @@ static void requisitesWorker(const Path & nePath, bool includeExprs, bool includeSuccessors, PathSet & paths, PathSet & doneSet) { + checkInterrupt(); + if (doneSet.find(nePath) != doneSet.end()) return; doneSet.insert(nePath); diff --git a/src/libstore/pathlocks.cc b/src/libstore/pathlocks.cc index 321e965bb..d4f980c64 100644 --- a/src/libstore/pathlocks.cc +++ b/src/libstore/pathlocks.cc @@ -19,11 +19,14 @@ bool lockFile(int fd, LockType lockType, bool wait) lock.l_len = 0; /* entire file */ if (wait) { - while (fcntl(fd, F_SETLKW, &lock) != 0) + while (fcntl(fd, F_SETLKW, &lock) != 0) { + checkInterrupt(); if (errno != EINTR) throw SysError(format("acquiring/releasing lock")); + } } else { while (fcntl(fd, F_SETLK, &lock) != 0) { + checkInterrupt(); if (errno == EACCES || errno == EAGAIN) return false; if (errno != EINTR) throw SysError(format("acquiring/releasing lock")); @@ -55,6 +58,7 @@ PathLocks::PathLocks(const PathSet & _paths) /* Acquire the lock for each path. */ for (Paths::iterator i = paths.begin(); i != paths.end(); i++) { + checkInterrupt(); Path path = *i; Path lockPath = path + ".lock"; @@ -87,6 +91,7 @@ PathLocks::~PathLocks() close(*i); for (Paths::iterator i = paths.begin(); i != paths.end(); i++) { + checkInterrupt(); if (deletePaths) { /* This is not safe in general! */ unlink(i->c_str()); diff --git a/src/libstore/references.cc b/src/libstore/references.cc index 2daf4d4f4..9b20b980a 100644 --- a/src/libstore/references.cc +++ b/src/libstore/references.cc @@ -17,6 +17,7 @@ static void search(const string & s, for (Strings::iterator i = ids.begin(); i != ids.end(); ) { + checkInterrupt(); if (s.find(*i) == string::npos) i++; else { @@ -31,6 +32,8 @@ static void search(const string & s, void checkPath(const string & path, Strings & ids, Strings & seen) { + checkInterrupt(); + struct stat st; if (lstat(path.c_str(), &st)) throw SysError(format("getting attributes of path `%1%'") % path); diff --git a/src/libstore/store.cc b/src/libstore/store.cc index c1d95ab8c..4cd77796e 100644 --- a/src/libstore/store.cc +++ b/src/libstore/store.cc @@ -133,7 +133,7 @@ void copyPath(const Path & src, const Path & dst) source.fd = fds[0]; restorePath(dst, source); _exit(0); - } catch (exception & e) { + } catch (exception & e) { cerr << "error: " << e.what() << endl; } _exit(1); diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc index 90a039164..2b8fb2f10 100644 --- a/src/libutil/archive.cc +++ b/src/libutil/archive.cc @@ -83,6 +83,7 @@ static void dumpContents(const Path & path, unsigned int size, unsigned int total = 0; ssize_t n; while ((n = read(fd, buf, sizeof(buf)))) { + checkInterrupt(); if (n == -1) throw SysError("reading file " + path); total += n; sink(buf, n); @@ -200,6 +201,8 @@ static void restoreEntry(const Path & path, RestoreSource & source) if (s != "(") throw badArchive("expected open tag"); while (1) { + checkInterrupt(); + s = readString(source); if (s == ")") { @@ -224,6 +227,7 @@ static void restoreContents(int fd, const Path & path, RestoreSource & source) unsigned char buf[65536]; while (left) { + checkInterrupt(); unsigned int n = sizeof(buf); if (n > left) n = left; source(buf, n); @@ -247,6 +251,8 @@ static void restore(const Path & path, RestoreSource & source) AutoCloseFD fd; while (1) { + checkInterrupt(); + s = readString(source); if (s == ")") { diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 28e276a32..5c8b7279c 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -132,6 +132,7 @@ Strings readDirectory(const Path & path) struct dirent * dirent; while (errno = 0, dirent = readdir(dir)) { /* sic */ + checkInterrupt(); string name = dirent->d_name; if (name == "." || name == "..") continue; names.push_back(name); @@ -144,6 +145,8 @@ Strings readDirectory(const Path & path) void deletePath(const Path & path) { + checkInterrupt(); + printMsg(lvlVomit, format("deleting path `%1%'") % path); struct stat st; @@ -170,6 +173,8 @@ void deletePath(const Path & path) void makePathReadOnly(const Path & path) { + checkInterrupt(); + struct stat st; if (lstat(path.c_str(), &st)) throw SysError(format("getting attributes of path `%1%'") % path); @@ -199,6 +204,7 @@ static Path tempName() Path createTempDir() { while (1) { + checkInterrupt(); Path tmpDir = tempName(); if (mkdir(tmpDir.c_str(), 0777) == 0) return tmpDir; if (errno != EEXIST) @@ -246,6 +252,7 @@ void Nest::open(Verbosity level, const format & f) void printMsg_(Verbosity level, const format & f) { + checkInterrupt(); if (level > verbosity) return; string spaces; for (int i = 0; i < nestingLevel; i++) @@ -257,6 +264,7 @@ void printMsg_(Verbosity level, const format & f) void readFull(int fd, unsigned char * buf, size_t count) { while (count) { + checkInterrupt(); ssize_t res = read(fd, (char *) buf, count); if (res == -1) throw SysError("reading from file"); if (res == 0) throw Error("unexpected end-of-file"); @@ -269,6 +277,7 @@ void readFull(int fd, unsigned char * buf, size_t count) void writeFull(int fd, const unsigned char * buf, size_t count) { while (count) { + checkInterrupt(); ssize_t res = write(fd, (char *) buf, count); if (res == -1) throw SysError("writing to file"); count -= res; @@ -344,3 +353,11 @@ AutoCloseDir::operator DIR *() return dir; } + +volatile sig_atomic_t _isInterrupted = 0; + +void _interrupted() +{ + _isInterrupted = 0; + throw Error("interrupted by the user"); +} diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 5d27ac1bd..34fff003b 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -179,4 +180,16 @@ public: }; +/* User interruption. */ + +extern volatile sig_atomic_t _isInterrupted; + +void _interrupted(); + +void inline checkInterrupt() +{ + if (_isInterrupted) _interrupted(); +} + + #endif /* !__UTIL_H */ From 291030b900ffe5b9934729a3dce9b73c4effe715 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 15 Jan 2004 20:58:44 +0000 Subject: [PATCH 0405/6440] * Remove debug message. --- src/libmain/shared.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 17d4dda67..e91ef2667 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -36,7 +36,6 @@ static void initAndRun(int argc, char * * argv) act.sa_flags = 0; if (sigaction(SIGINT, &act, &oact)) throw SysError("installing handler for SIGINT"); - printMsg(lvlError, "SIG HANDLER INSTALLED"); /* Put the arguments in a vector. */ Strings args, remaining; From b1c5f3c10dbcbbb9469ba7147bb26acf6a9aa733 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 16 Jan 2004 14:54:39 +0000 Subject: [PATCH 0406/6440] * Doh! Edited `readmanifest.pm' instead of `readmanifest.pm.in'. --- scripts/prebuilts.conf | 4 ++-- scripts/readmanifest.pm.in | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/scripts/prebuilts.conf b/scripts/prebuilts.conf index c7bc89c61..394436538 100644 --- a/scripts/prebuilts.conf +++ b/scripts/prebuilts.conf @@ -1,2 +1,2 @@ -# A list of URLs from where we obtain Nix archives. -http://losser.st-lab.cs.uu.nl/~eelco/nix-dist/ +# A list of URLs from where `nix-pull' obtain Nix archives if +# no URL is specified on the command line. diff --git a/scripts/readmanifest.pm.in b/scripts/readmanifest.pm.in index e8819d8b8..2c6223807 100644 --- a/scripts/readmanifest.pm.in +++ b/scripts/readmanifest.pm.in @@ -10,15 +10,16 @@ sub processURL { $url =~ s/\/$//; print "obtaining list of Nix archives at $url...\n"; - system "wget --cache=off '$url'/MANIFEST -O '$manifest' 2> /dev/null"; # !!! escape - if ($?) { die "`wget' failed"; } + system("curl --fail --silent --show-error " . + "'$url/MANIFEST' > '$manifest' 2> /dev/null") == 0 + or die "curl failed: $?"; open MANIFEST, "<$manifest"; my $inside = 0; my $storepath; - my $narname; + my $narurl; my $hash; my @preds; @@ -31,7 +32,7 @@ sub processURL { if (/^\{$/) { $inside = 1; undef $storepath; - undef $narname; + undef $narurl; undef $hash; @preds = (); } @@ -39,10 +40,9 @@ sub processURL { } else { if (/^\}$/) { $inside = 0; - my $fullurl = "$url/$narname"; - $$storepaths2urls{$storepath} = $fullurl; - $$urls2hashes{$fullurl} = $hash; + $$storepaths2urls{$storepath} = $narurl; + $$urls2hashes{$narurl} = $hash; foreach my $p (@preds) { $$successors{$p} = $storepath; @@ -52,8 +52,8 @@ sub processURL { elsif (/^\s*StorePath:\s*(\/\S+)\s*$/) { $storepath = $1; } - elsif (/^\s*NarName:\s*(\S+)\s*$/) { - $narname = $1; + elsif (/^\s*NarURL:\s*(\S+)\s*$/) { + $narurl = $1; } elsif (/^\s*MD5:\s*(\S+)\s*$/) { $hash = $1; From f899e8ce4df3e700ae5b47279c16edc0efb4aeed Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 16 Jan 2004 15:17:36 +0000 Subject: [PATCH 0407/6440] * Test whether the symlink, not its target, exists. --- scripts/nix-profile.sh.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/nix-profile.sh.in b/scripts/nix-profile.sh.in index 064a6a347..55cff3e62 100644 --- a/scripts/nix-profile.sh.in +++ b/scripts/nix-profile.sh.in @@ -1,7 +1,7 @@ if test -n "$HOME"; then NIX_LINK="$HOME/.nix-userenv" - if ! test -a "$NIX_LINK"; then + if ! test -L "$NIX_LINK"; then echo "creating $NIX_LINK" _NIX_DEF_LINK=@localstatedir@/nix/links/current ln -s "$_NIX_DEF_LINK" "$NIX_LINK" From 3a4a4aaa8820e3483b64b3e2d25ee523c0a0cb44 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 19 Jan 2004 08:49:25 +0000 Subject: [PATCH 0408/6440] * Strip binaries in RPMs. --- nix.spec.in | 1 + 1 file changed, 1 insertion(+) diff --git a/nix.spec.in b/nix.spec.in index 388c40751..c1a13be65 100644 --- a/nix.spec.in +++ b/nix.spec.in @@ -24,6 +24,7 @@ make %install rm -rf $RPM_BUILD_ROOT make DESTDIR=$RPM_BUILD_ROOT install +strip $RPM_BUILD_ROOT/%{_prefix}/bin/* %clean rm -rf $RPM_BUILD_ROOT From 699989b2168bed80cc1383e5bf3c8326d55a3cc1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 19 Jan 2004 09:01:28 +0000 Subject: [PATCH 0409/6440] * Ignore exit code from strip. --- nix.spec.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix.spec.in b/nix.spec.in index c1a13be65..6fdd93e9f 100644 --- a/nix.spec.in +++ b/nix.spec.in @@ -24,7 +24,7 @@ make %install rm -rf $RPM_BUILD_ROOT make DESTDIR=$RPM_BUILD_ROOT install -strip $RPM_BUILD_ROOT/%{_prefix}/bin/* +strip $RPM_BUILD_ROOT/%{_prefix}/bin/* || true %clean rm -rf $RPM_BUILD_ROOT From 8baf50f10821e6def3006af8ff8b8739c66b028a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 20 Jan 2004 11:49:32 +0000 Subject: [PATCH 0410/6440] * Manual updates. * Updated the README. Now it just refers to the manual. --- README | 39 ++-------------- doc/manual/installation.xml | 91 +++++++++++++++++++++++++++++-------- doc/manual/introduction.xml | 8 ---- doc/manual/overview.xml | 60 +++++++++++++++++++----- 4 files changed, 124 insertions(+), 74 deletions(-) diff --git a/README b/README index d3d9d603e..344cfddb0 100644 --- a/README +++ b/README @@ -1,36 +1,5 @@ -Overview -======== + *** Nix *** -Nix is a package manager, deployment system, and component glue -mechanism. - - -Prerequisites -============= - -* Berkeley DB 4.0.14 -* CWI ATerm 2.0 - - -Installation -============ - -* When building from the Subversion repository, first do: - - autoreconf -i - -* To build, do: - - ./configure - make - make install - - Note that this will install to /nix, which is the default prefix. - You can specify another prefix, but this is not recommended if you - want to use prebuilt packages from other sources. - - -Usage -===== - -TODO \ No newline at end of file +For installation and usage instructions, please read the manual, which +can be found in docs/manual/manual.html, and additionally at the Nix +website at . diff --git a/doc/manual/installation.xml b/doc/manual/installation.xml index a9a30b09d..ebc4f168a 100644 --- a/doc/manual/installation.xml +++ b/doc/manual/installation.xml @@ -6,8 +6,9 @@ The easiest way to obtain Nix is to download a source - distribution. + url='http://www.cs.uu.nl/groups/ST/Trace/Nix'>source + distribution. RPMs for SuSE and Red Hat are also + available. These distributions are generated automatically. @@ -35,8 +36,13 @@ $ svn checkout https://svn.cs.uu.nl:12443/repos/trace/nix/trunk nix Prerequisites - A fairly recent version of GCC/G++ is required. Version 2.95 and higher - should work. + The following prerequisites only apply when you build from + source. Binary releases (e.g., RPMs) have no prerequisites. + + + + A fairly recent version of GCC/G++ is required. Version 2.95 + and higher should work. @@ -63,7 +69,7 @@ $ svn checkout https://svn.cs.uu.nl:12443/repos/trace/nix/trunk nix - Building Nix + Building Nix from source After unpacking or checking out the Nix sources, issue the following @@ -112,20 +118,71 @@ $ autoreconf -i + + Installing from RPMs + + + RPM packages of Nix can be downloaded from . 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 glibc 2.3 or + later. + + + + Once downloaded, the RPMs can be installed or upgraded using + rpm -U. For example, + + + +rpm -U nix-0.5pre664-1.i386.rpm + + + The RPMs install into the directory /nix. + Nix can be uninstalled using rpm -e nix. + After this it will be necessary to manually remove the Nix store + and other auxiliary data: + + + +rm -rf /nix/store +rm -rf /nix/var + + + + + Permissions + + + All Nix operations must be performed under the user ID that owns + the Nix store and database + (prefix/store + and + prefix/var/nix/db, + respectively). When installed from the RPM packages, these + directories are owned by root. + + + + Using Nix - To use Nix, some environment variables should be set. In particular, - PATH should contain the directories + To use Nix, some environment variables should be set. In + particular, PATH should contain the directories prefix/bin and - prefix/var/nix/links/current/bin. - The first directory contains the Nix tools themselves, while the second - contains to the current user environment (an - automatically generated package consisting of symlinks to installed - packages). The simplest way to set the required environment variables is - to include the file - prefix/etc/profile.d/nix.sh + ~/.nix-userenv/bin. The first directory + contains the Nix tools themselves, while + ~/.nix-userenv is a symbolic link to the + current user environment (an automatically + generated package consisting of symlinks to installed packages). + The simplest way to set the required environment variables is to + include the file + prefix/etc/profile.d/nix.sh in your ~/.bashrc (or similar), like this: @@ -135,9 +192,3 @@ $ autoreconf -i - - diff --git a/doc/manual/introduction.xml b/doc/manual/introduction.xml index b41cad0a8..02a438336 100644 --- a/doc/manual/introduction.xml +++ b/doc/manual/introduction.xml @@ -91,11 +91,3 @@ - - - - diff --git a/doc/manual/overview.xml b/doc/manual/overview.xml index 85f164587..9925be994 100644 --- a/doc/manual/overview.xml +++ b/doc/manual/overview.xml @@ -5,6 +5,10 @@ This chapter provides a guided tour of Nix. + + + + Basic package management @@ -63,7 +67,7 @@ pan-0.14.2 -$ nix-env -qf pkgs/system/i686-linux.nix +$ nix-env -qaf pkgs/system/i686-linux.nix gettext-0.12.1 sylpheed-0.9.7 aterm-2.0 @@ -86,7 +90,7 @@ pan-0.14.2 -$ nix-env -qsf pkgs/system/i686-linux.nix +$ nix-env -qasf pkgs/system/i686-linux.nix -P gettext-0.12.1 IP sylpheed-0.9.7 -- aterm-2.0 @@ -111,7 +115,7 @@ IP sylpheed-0.9.7 -$ nix-env -i pkgs/system/i686-linux.nix pan-0.14.2 +$ nix-env -if pkgs/system/i686-linux.nix pan Since installation may take a long time, depending on whether any @@ -234,11 +238,52 @@ lrwxrwxrwx 1 eelco ... svn -> /nix/store/3829...fb5d-subversion-0.32.1/bin/svn -$ nix-env -u pan-0.14.2 +$ nix-env -e pan + + + This means that the package is removed from the user + environment. It is not yet removed from + the system. When a package is uninstalled from a user + environment, it may still be used by other packages, or may + still be present in other user environments. Deleting it under + such conditions would break those other packages or user + environments. To prevent this, packages are only + physically deleted by running the Nix garbage + collector, which searches for all packages in the Nix store that + are no longer reachable from outside the store. + Thus, uninstalling a package is always safe: it cannot break + other packages. + + + + Upgrading packages is easy. Given a Nix expression that + contains newer versions of installed packages (that is, packages + with the same package name, but a higher version number), + nix-env -u will replace the installed package + in the user environment with the newer package. For example, + + +$ nix-env -uf pkgs/system/i686-linux.nix pan + + looks for a newer version of Pan, and installs it if found. + Also useful is the ability to upgrade all + packages: + + +$ nix-env -uf pkgs/system/i686-linux.nix '*' + + The asterisk matches all installed packagesNo, + we don't support arbitrary regular + expressions. Note that * + must be quoted to prevent shell globbing. + + + + Writing Nix expressions @@ -400,11 +445,4 @@ derivation { - - - From 3778586b2aaf2b5c905866d91d6f67e617ceb203 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 20 Jan 2004 15:37:55 +0000 Subject: [PATCH 0411/6440] * Nix Quick Start guide. --- doc/manual/Makefile.am | 2 +- doc/manual/installation.xml | 2 +- doc/manual/manual.xml | 4 +- doc/manual/overview.xml | 2 +- doc/manual/quick-start.xml | 136 ++++++++++++++++++++++++++++++++++++ 5 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 doc/manual/quick-start.xml diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index f0344ef64..b1f5c3f46 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -9,7 +9,7 @@ XSLTPROC = $(ENV) $(xsltproc) $(xmlflags) --catalogs \ SOURCES = manual.xml introduction.xml installation.xml overview.xml \ nix-env.xml nix-store.xml nix-instantiate.xml \ troubleshooting.xml bugs.xml opt-verbose.xml \ - style.css images + quick-start.xml style.css images manual.is-valid: $(SOURCES) version.xml $(XMLLINT) --noout --valid manual.xml diff --git a/doc/manual/installation.xml b/doc/manual/installation.xml index ebc4f168a..39f6654ef 100644 --- a/doc/manual/installation.xml +++ b/doc/manual/installation.xml @@ -1,4 +1,4 @@ - + Installation diff --git a/doc/manual/manual.xml b/doc/manual/manual.xml index e95b0fc91..d47e57123 100644 --- a/doc/manual/manual.xml +++ b/doc/manual/manual.xml @@ -4,6 +4,7 @@ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [ + @@ -26,12 +27,13 @@ Dolstra - 2003 + 2004 Eelco Dolstra &introduction; + &quick-start; &installation; &overview; diff --git a/doc/manual/overview.xml b/doc/manual/overview.xml index 9925be994..191f7a6fe 100644 --- a/doc/manual/overview.xml +++ b/doc/manual/overview.xml @@ -1,4 +1,4 @@ - + Overview diff --git a/doc/manual/quick-start.xml b/doc/manual/quick-start.xml new file mode 100644 index 000000000..62dc64939 --- /dev/null +++ b/doc/manual/quick-start.xml @@ -0,0 +1,136 @@ + + Quick Start + + + This chapter is for impatient people who don't like reading + documentation. For more in-depth information you are kindly + referred to and . + + + + + + + Download a source tarball or RPM from . + Build source distributions using the regular sequence: + + +$ tar xvfj nix-version.tar.bz2 +$ ./configure +$ make +$ make install (as root) + + This will install Nix in /nix. + + + + + + Get some Nix expressions for pre-built packages by downloading + the latest nixpkgs distribution (from the + same location), and unpack them. + + +$ wget http://.../nix/nixpkgs-version/nixpkgs-version.tar.bz2 +$ tar xfj nixpkgs-version.tar.bz2 + + + + + + + Pull the Nix cache. This ensures that when you install + packages they are downloaded in pre-built form from the + network, rather than built from source. + + +$ nix-pull http://.../nix/nixpkgs-version/ + + + + + Note that currently we only pre-build for Linux on x86 + platforms. + + + + + + + See what's available: + + +$ nix-env -qaf nixpkgs-version +MozillaFirebird-0.7 +hello-2.1.1 +docbook-xml-4.2 +libxslt-1.1.0 +... + + + + + + + Install some packages: + + +$ nix-env -iBf nixpkgs-version hello MozillaFirebird ... + + + + + + + Test that they work: + + +$ which hello +/home/eelco/.nix-userenv/bin/hello +$ hello +Hello, world! +$ MozillaFirebird +(read Slashdot or something) + + + + + + + Uninstall a package: + + +$ nix-env -e hello + + + + + + + If a new release of nixpkgs comes along, + you can upgrade all installed packages to the latest versions + by downloading and unpacking the new release and doing: + + +$ nix-env -uBf nixpkgs-version '*' + + + + + + + You should periodically run the Nix garbage collector to get + rid of unused packages, since uninstalls or upgrades don't + actual delete them: + + +$ nix-collect-garbage | xargs nix-store --delete + + + + + + + \ No newline at end of file From 4db7ef3fcc7c392dc03fc02a22886a8c9bcbcacb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 20 Jan 2004 17:18:41 +0000 Subject: [PATCH 0412/6440] * Fixed URL. --- doc/manual/quick-start.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/quick-start.xml b/doc/manual/quick-start.xml index 62dc64939..79323c523 100644 --- a/doc/manual/quick-start.xml +++ b/doc/manual/quick-start.xml @@ -13,7 +13,7 @@ Download a source tarball or RPM from . + url='http://www.cs.uu.nl/groups/ST/Trace/Nix'/>. Build source distributions using the regular sequence: From 47f19b6293357a8bdc3a2290813765170f241b58 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 20 Jan 2004 20:36:58 +0000 Subject: [PATCH 0413/6440] * Absolutise the specified path in `--import' and `--profile'. --- src/nix-env/main.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 5c27a89fc..eaa11fe25 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -629,7 +629,7 @@ static void opSwitchProfile(Globals & globals, throw UsageError(format("`--profile' takes at most one argument")); Path linkPath = - opArgs.size() == 0 ? globals.linkPath : opArgs.front(); + absPath(opArgs.size() == 0 ? globals.linkPath : opArgs.front()); Path linkPathFinal = getHomeDir() + "/.nix-userenv"; switchLink(linkPathFinal, linkPath); @@ -644,7 +644,7 @@ static void opDefaultExpr(Globals & globals, if (opArgs.size() != 1) throw UsageError(format("`--import' takes exactly one argument")); - Path defNixExpr = opArgs.front(); + Path defNixExpr = absPath(opArgs.front()); Path defNixExprLink = getDefNixExprPath(); switchLink(defNixExprLink, defNixExpr); From 1109ea068097d4c5e3a4dfdeececf4590c52329a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 21 Jan 2004 14:49:32 +0000 Subject: [PATCH 0414/6440] * Fixed a subtle uninitialised variable bug in ATermMaps copied from ATermMaps. Found thanks to Valgrind! --- src/libexpr/nixexpr.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index dd0f5d58a..7de3e823c 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -4,6 +4,7 @@ ATermMap::ATermMap(unsigned int initialSize, unsigned int maxLoadPct) { + this->maxLoadPct = maxLoadPct; table = ATtableCreate(initialSize, maxLoadPct); if (!table) throw Error("cannot create ATerm table"); } @@ -15,7 +16,8 @@ ATermMap::ATermMap(const ATermMap & map) ATermList keys = map.keys(); /* !!! adjust allocation for load pct */ - table = ATtableCreate(ATgetLength(keys), map.maxLoadPct); + maxLoadPct = map.maxLoadPct; + table = ATtableCreate(ATgetLength(keys), maxLoadPct); if (!table) throw Error("cannot create ATerm table"); for (ATermIterator i(keys); i; ++i) From 840551ebdb6ca09ab733081dd0e92daee73ba900 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 21 Jan 2004 16:41:17 +0000 Subject: [PATCH 0415/6440] * Extra bit `S' in `--query --status' output: show whether there are any substitutes for the derivation. --- src/nix-env/main.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index eaa11fe25..a2e9b119a 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -606,10 +606,12 @@ static void opQuery(Globals & globals, installedPaths.insert(i->second.outPath); for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) { - cout << format("%1%%2% %3%\n") + Paths subs = querySubstitutes(i->second.drvPath); + cout << format("%1%%2%%3% %4%\n") % (installedPaths.find(i->second.outPath) != installedPaths.end() ? 'I' : '-') % (isValidPath(i->second.outPath) ? 'P' : '-') + % (subs.size() > 0 ? 'S' : '-') % i->second.name; } break; From 4f72b408a5b0cf4401362960000763322eeb1846 Mon Sep 17 00:00:00 2001 From: Martin Bravenboer Date: Thu, 22 Jan 2004 08:47:59 +0000 Subject: [PATCH 0416/6440] Typos and url losser -> catamaran --- doc/manual/overview.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/manual/overview.xml b/doc/manual/overview.xml index 191f7a6fe..5525f4bc0 100644 --- a/doc/manual/overview.xml +++ b/doc/manual/overview.xml @@ -156,7 +156,7 @@ $ nix-env -if pkgs/system/i686-linux.nix pan While the substitute mechanism is a generic mechanism, Nix provides two - standard tools called nix-push and + standard tools called nix-pull and nix-push that maintain and use a shared cache of prebuilt derivations on some network site (reachable through HTTP). If you attempt to install some package that someone else has previously @@ -173,8 +173,9 @@ $ nix-env -if pkgs/system/i686-linux.nix pan -$ nix-pull http://losser.st-lab.cs.uu.nl/~eelco/nix-dist/ -obtaining list of Nix archives at http://losser.st-lab.cs.uu.nl/~eelco/nix-dist... +$ nix-pull +http://catamaran.labs.cs.uu.nl/~eelco/nix/nixpkgs-version/ +obtaining list of Nix archives at http://catamaran.labs.cs.uu.nl/~eelco/nix/nixpkgs-version... ... From 3c4bc7276a4599867c46e872858550499a94c641 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 22 Jan 2004 09:17:55 +0000 Subject: [PATCH 0417/6440] * Added a note about adding /nix/etc/profile.d/nix.sh to the profile. --- doc/manual/quick-start.xml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/manual/quick-start.xml b/doc/manual/quick-start.xml index 79323c523..f4deb0bbb 100644 --- a/doc/manual/quick-start.xml +++ b/doc/manual/quick-start.xml @@ -22,7 +22,10 @@ $ ./configure $ make $ make install (as root) - This will install Nix in /nix. + This will install Nix in /nix. You + should also add /nix/etc/profile.d/nix.sh + to your ~/.bashrc (or some other login + file). @@ -36,6 +39,8 @@ $ make install (as root) $ wget http://.../nix/nixpkgs-version/nixpkgs-version.tar.bz2 $ tar xfj nixpkgs-version.tar.bz2 + This will unpack the distribution into a directory + nixpkgs-version/. @@ -62,7 +67,7 @@ $ nix-pull http://.../nix/nixpkgs-versio See what's available: -$ nix-env -qaf nixpkgs-version +$ nix-env -qaf nixpkgs-version/ MozillaFirebird-0.7 hello-2.1.1 docbook-xml-4.2 @@ -77,7 +82,7 @@ libxslt-1.1.0 Install some packages: -$ nix-env -iBf nixpkgs-version hello MozillaFirebird ... +$ nix-env -iBf nixpkgs-version/ hello MozillaFirebird ... From cdb50886f40e879d7b9abcfdd3ff4ac9d66f6242 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 22 Jan 2004 09:35:35 +0000 Subject: [PATCH 0418/6440] * Typos. --- doc/manual/nix-store.xml | 4 ++-- doc/manual/quick-start.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual/nix-store.xml b/doc/manual/nix-store.xml index 8c6bcfe37..7758d04ea 100644 --- a/doc/manual/nix-store.xml +++ b/doc/manual/nix-store.xml @@ -259,13 +259,13 @@ nix-store --realise /nix/store/bla.store -x=`nix-store --query --normalise /nix/store/bla.store +x=`nix-store --query --normalise /nix/store/bla.store` (do something with the path $x which using this flag can be written as -x=`nix-store --query --normalise --force-realise /nix/store/bla.store +x=`nix-store --query --normalise --force-realise /nix/store/bla.store` (do something with the path $x diff --git a/doc/manual/quick-start.xml b/doc/manual/quick-start.xml index f4deb0bbb..ec49dfb27 100644 --- a/doc/manual/quick-start.xml +++ b/doc/manual/quick-start.xml @@ -119,7 +119,7 @@ $ nix-env -e hello by downloading and unpacking the new release and doing: -$ nix-env -uBf nixpkgs-version '*' +$ nix-env -uBf nixpkgs-version/ '*' From 3648d1c732379ef5d0f74cc3c2e5b876a7f2c9a2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 22 Jan 2004 13:04:57 +0000 Subject: [PATCH 0419/6440] * Explicitly compute the release name. --- Makefile.am | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile.am b/Makefile.am index 2b44a56a6..cf6a13ad6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,3 +8,6 @@ nix.spec: nix.spec.in rpm: nix.spec dist rpm $(EXTRA_RPM_FLAGS) -ta $(distdir).tar.gz + +relname: + echo -n $(distdir) > relname From abd1878b26200ba3fa75592637aa87e04f52100d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 29 Jan 2004 14:24:53 +0000 Subject: [PATCH 0420/6440] * Optimised the SDF grammar. --- src/libexpr/nix.sdf | 105 ++++++------------------------------------ src/libexpr/parser.cc | 4 +- 2 files changed, 15 insertions(+), 94 deletions(-) diff --git a/src/libexpr/nix.sdf b/src/libexpr/nix.sdf index b6bb23ebd..36fbef39c 100644 --- a/src/libexpr/nix.sdf +++ b/src/libexpr/nix.sdf @@ -15,9 +15,9 @@ imports Fix-Exprs Fix-Layout %% Expressions. module Fix-Exprs -imports Fix-Lexicals URI +imports Fix-Lexicals exports - sorts Expr Formal Bind Binds BindSemi ExprList + sorts Expr Formal Bind Binds ExprList context-free syntax Id -> Expr {cons("Var")} @@ -44,10 +44,8 @@ exports "let" "{" Binds "}" -> Expr {cons("LetRec")} "{" Binds "}" -> Expr {cons("Attrs")} - Id "=" Expr -> Bind {cons("Bind")} - {Bind ";"}* -> Binds - Bind ";" -> BindSemi - BindSemi* -> Binds + Bind* -> Binds + Id "=" Expr ";" -> Bind {cons("Bind")} "[" ExprList "]" -> Expr {cons("List")} "" -> ExprList {cons("ExprNil")} @@ -65,8 +63,6 @@ exports Expr "||" Expr -> Expr {cons("OpOr"), right} Expr "->" Expr -> Expr {cons("OpImpl"), right} - Bool -> Expr {cons("Bool")} - context-free priorities Expr "." Id -> Expr @@ -87,7 +83,7 @@ exports module Fix-Lexicals exports - sorts Id Int Str Path PathComp Bool + sorts Id Int Str Path PathComp Uri lexical syntax [a-zA-Z\_][a-zA-Z0-9\_\']* -> Id "rec" -> Id {reject} @@ -95,97 +91,24 @@ exports "if" -> Id {reject} "then" -> Id {reject} "else" -> Id {reject} - "true" -> Id {reject} - "false" -> Id {reject} "assert" -> Id {reject} [0-9]+ -> Int "\"" ~[\n\"]* "\"" -> Str - PathComp ("/" PathComp)+ -> Path + "." ("/" PathComp)+ -> Path + ".." ("/" PathComp)+ -> Path ("/" PathComp)+ -> Path [a-zA-Z0-9\.\_\-\+]+ -> PathComp - "true" -> Bool - "false" -> Bool + [a-zA-Z] [a-zA-Z0-9\+\-\.]* ":" [a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\']* -> Uri lexical restrictions Id -/- [a-zA-Z0-9\_\'] Int -/- [0-9] Path -/- [a-zA-Z0-9\.\_\-\+\/] - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% URIs (RFC 2396, appendix A). - -module URI -exports - sorts Uri Uhierpart Uopaquepart Uuricnoslash Unetpath Uabspath - Urelpath Urelsegment Uscheme Uauthority Uregname Userver - Uuserinfo Uhostport Uhost Uhostname Udomainlabel Utoplabel - UIPv4address Uport Upath Upathsegments Usegment Uparam - Upchar Uquery Ufragment Uuric Ureserved Uunreserved Umark - Uescaped Uhex Ualphanum Ualpha Ulowalpha Uupalpha Udigit - lexical syntax - Uscheme ":" (Uhierpart | Uopaquepart) -> Uri - - (Unetpath | Uabspath) ("?" Uquery)? -> Uhierpart - Uuricnoslash Uuric* -> Uopaquepart - - Uunreserved | Uescaped | [\;\?\:\@\&\=\+\$\,] -> Uuricnoslash - - "//" Uauthority Uabspath? -> Unetpath - "/" Upathsegments -> Uabspath - "//" Uuric* -> Uabspath {reject} - Urelsegment Uabspath? -> Urelpath - - (Uunreserved | Uescaped | [\;\@\&\=\+\$\,])+ -> Urelsegment - - Ualpha (Ualpha | Udigit | [\+\-\.])* -> Uscheme - - Userver | Uregname -> Uauthority - - (Uunreserved | Uescaped | [\$\,\;\:\@\&\=\+])+ -> Uregname - - ((Uuserinfo "@") Uhostport) -> Userver - (Uunreserved | Uescaped | [\;\:\&\=\+\$\,])* -> Uuserinfo - - Uhost (":" Uport)? -> Uhostport - Uhostname | UIPv4address -> Uhost - (Udomainlabel ".")+ Utoplabel "."? -> Uhostname - Ualphanum | Ualphanum (Ualphanum | "-")* Ualphanum -> Udomainlabel - Ualpha | Ualpha (Ualphanum | "-")* Ualphanum -> Utoplabel - Udigit+ "." Udigit+ "." Udigit+ "." Udigit+ -> UIPv4address - Udigit* -> Uport - - Uabspath | Uopaquepart -> Upath - Usegment ("/" Usegment)* -> Upathsegments - Upchar* (";" Uparam)* -> Usegment - Upchar* -> Uparam - Uunreserved | Uescaped | [\:\@\&\=\+\$\,] -> Upchar - - Uuric* -> Uquery - - Uuric* -> Ufragment - - Ureserved | Uunreserved | Uescaped -> Uuric - [\;\/\?\:\@\&\=\+\$\,] -> Ureserved - Ualphanum | Umark -> Uunreserved - [\-\_\.\!\~\*\'\(\)] -> Umark - - "%" Uhex Uhex -> Uescaped - Udigit | [A-Fa-f] -> Uhex - - Ualpha | Udigit -> Ualphanum - Ulowalpha | Uupalpha -> Ualpha - - [a-z] -> Ulowalpha - [A-Z] -> Uupalpha - [0-9] -> Udigit - - lexical restrictions - Uri -/- [a-zA-Z0-9\-\_\.\!\~\*\'\(\)\/] + Uri -/- [a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\'] %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -193,18 +116,16 @@ exports module Fix-Layout exports - sorts HashComment Asterisk Comment EOF + sorts HashComment Asterisk Comment lexical syntax [\ \t\n] -> LAYOUT HashComment -> LAYOUT Comment -> LAYOUT - "#" ~[\n]* ([\n] | EOF) -> HashComment - "//" ~[\n]* ([\n] | EOF) -> HashComment + "#" ~[\n]* -> HashComment "/*" ( ~[\*] | Asterisk )* "*/" -> Comment [\*] -> Asterisk - "" -> EOF lexical restrictions Asterisk -/- [\/] - EOF -/- ~[] + HashComment -/- ~[\n] context-free restrictions - LAYOUT? -/- [\ \t\n] | [\#] + LAYOUT? -/- [\ \t\n] diff --git a/src/libexpr/parser.cc b/src/libexpr/parser.cc index 83b656342..1e0ef0c45 100644 --- a/src/libexpr/parser.cc +++ b/src/libexpr/parser.cc @@ -45,10 +45,10 @@ struct Cleanup : TermFun return ATmake("Int()", n); } - if (atMatch(m, e) >> "Bool" >> "true") + if (atMatch(m, e) >> "Var" >> "true") return ATmake("Bool(True)"); - if (atMatch(m, e) >> "Bool" >> "false") + if (atMatch(m, e) >> "Var" >> "false") return ATmake("Bool(False)"); if (atMatch(m, e) >> "ExprNil") From c5baaafae69394082817ede9e6eb3910c4601a72 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 30 Jan 2004 15:21:42 +0000 Subject: [PATCH 0421/6440] * Replaced the SDF parser by a substantially faster Bison/Flex parser (roughly 80x faster). The absolutely latest version of Bison (1.875c) is required for reentrant GLR support, as well as a recent version of Flex (say, 2.5.31). Note that most Unix distributions ship with the prehistoric Flex 2.5.4, which doesn't support reentrancy. --- src/libexpr/Makefile.am | 18 ++--- src/libexpr/eval.cc | 2 + src/libexpr/lexer.l | 78 ++++++++++++++++++++ src/libexpr/nix.sdf | 131 --------------------------------- src/libexpr/parser.cc | 156 +++++++++++----------------------------- src/libexpr/parser.y | 128 +++++++++++++++++++++++++++++++++ 6 files changed, 261 insertions(+), 252 deletions(-) create mode 100644 src/libexpr/lexer.l delete mode 100644 src/libexpr/nix.sdf create mode 100644 src/libexpr/parser.y diff --git a/src/libexpr/Makefile.am b/src/libexpr/Makefile.am index a11dbbda6..66a3008ed 100644 --- a/src/libexpr/Makefile.am +++ b/src/libexpr/Makefile.am @@ -1,20 +1,22 @@ noinst_LIBRARIES = libexpr.a libexpr_a_SOURCES = nixexpr.cc nixexpr.hh parser.cc parser.hh \ - eval.cc eval.hh primops.cc primops.hh nix.sdf + eval.cc eval.hh primops.cc primops.hh \ + lexer-tab.c lexer-tab.h parser-tab.c parser-tab.h AM_CXXFLAGS = \ -I.. -I../../externals/inst/include -I../libutil -I../libstore +AM_CFLAGS = \ + -I../../externals/inst/include -# Parse table generation. +# Parser generation. -parser.o: parse-table.h +parser-tab.c parser-tab.h: parser.y + ../grammartest/inst/bin/bison -v -o parser-tab.c parser.y -d -parse-table.h: nix.tbl - ../bin2c/bin2c nixParseTable < $< > $@ || (rm $@ && exit 1) +lexer-tab.c lexer-tab.h: lexer.l + flex --outfile lexer-tab.c --header-file=lexer-tab.h lexer.l -%.tbl: %.sdf - ../../externals/inst/bin/sdf2table -s -i $< -o $@ -CLEANFILES = parse-table.h nix.tbl +CLEANFILES = diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 0470deee9..77cab55d0 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -137,6 +137,8 @@ Expr evalExpr2(EvalState & state, Expr e) /* Any encountered variables must be undeclared or primops. */ if (atMatch(m, e) >> "Var" >> s1) { if (s1 == "null") return primNull(state); + if (s1 == "true") return ATmake("Bool(True)"); + if (s1 == "false") return ATmake("Bool(False)"); return e; } diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l new file mode 100644 index 000000000..705b31b41 --- /dev/null +++ b/src/libexpr/lexer.l @@ -0,0 +1,78 @@ +%option reentrant bison-bridge bison-locations +%option noyywrap +%option never-interactive + + +%{ +#include +#include +#include "parser-tab.h" + +static void initLoc(YYLTYPE * loc) +{ + loc->first_line = 1; + loc->first_column = 1; +} + +static void adjustLoc(YYLTYPE * loc, const char * s, size_t len) +{ + while (len--) { + switch (*s++) { + case '\n': + ++loc->first_line; + loc->first_column = 1; + break; + default: + ++loc->first_column; + } + } +} + +#define YY_USER_INIT initLoc(yylloc) +#define YY_USER_ACTION adjustLoc(yylloc, yytext, yyleng); + +%} + + +ID [a-zA-Z\_][a-zA-Z0-9\_\']* +INT [0-9]+ +STR \"[^\n\"]*\" +PATH [a-zA-Z0-9\.\_\-\+]*(\/[a-zA-Z0-9\.\_\-\+]+)+ +URI [a-zA-Z][a-zA-Z0-9\+\-\.]*\:[a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\']* + + +%% + + +if { return IF; } +then { return THEN; } +else { return ELSE; } +assert { return ASSERT; } +let { return LET; } +rec { return REC; } + +\=\= { return EQ; } +\!\= { return NEQ; } +\&\& { return AND; } +\|\| { return OR; } +\-\> { return IMPL; } + +{ID} { yylval->t = ATmake("", yytext); return ID; /* !!! alloc */ } +{INT} { return INT; } +{STR} { int len = strlen(yytext); + yytext[len - 1] = 0; + yylval->t = ATmake("", yytext + 1); + yytext[len - 1] = '\"'; + return STR; /* !!! alloc */ + } +{PATH} { yylval->t = ATmake("", yytext); return PATH; /* !!! alloc */ } +{URI} { yylval->t = ATmake("", yytext); return URI; /* !!! alloc */ } + +[ \t\n]+ /* eat up whitespace */ +\#[^\n]* /* single-line comments */ +\/\*(.|\n)*\*\/ /* long comments */ + +. return yytext[0]; + + +%% diff --git a/src/libexpr/nix.sdf b/src/libexpr/nix.sdf deleted file mode 100644 index 36fbef39c..000000000 --- a/src/libexpr/nix.sdf +++ /dev/null @@ -1,131 +0,0 @@ -definition - -module Main -imports Fix - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Top level syntax. - -module Fix -imports Fix-Exprs Fix-Layout - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Expressions. - -module Fix-Exprs -imports Fix-Lexicals -exports - sorts Expr Formal Bind Binds ExprList - context-free syntax - - Id -> Expr {cons("Var")} - - Int -> Expr {cons("Int")} - - Str -> Expr {cons("Str")} - - Uri -> Expr {cons("Uri")} - - Path -> Expr {cons("Path")} - - "(" Expr ")" -> Expr {bracket} - - Expr Expr -> Expr {cons("Call"), left} - - "{" {Formal ","}* "}" ":" Expr -> Expr {cons("Function")} - Id -> Formal {cons("NoDefFormal")} - Id "?" Expr -> Formal {cons("DefFormal")} - - "assert" Expr ";" Expr -> Expr {cons("Assert")} - - "rec" "{" Binds "}" -> Expr {cons("Rec")} - "let" "{" Binds "}" -> Expr {cons("LetRec")} - "{" Binds "}" -> Expr {cons("Attrs")} - - Bind* -> Binds - Id "=" Expr ";" -> Bind {cons("Bind")} - - "[" ExprList "]" -> Expr {cons("List")} - "" -> ExprList {cons("ExprNil")} - Expr ExprList -> ExprList {cons("ExprCons")} - - Expr "." Id -> Expr {cons("Select")} - - "if" Expr "then" Expr "else" Expr -> Expr {cons("If")} - - Expr "==" Expr -> Expr {cons("OpEq"), non-assoc} - Expr "!=" Expr -> Expr {cons("OpNEq"), non-assoc} - - "!" Expr -> Expr {cons("OpNot")} - Expr "&&" Expr -> Expr {cons("OpAnd"), right} - Expr "||" Expr -> Expr {cons("OpOr"), right} - Expr "->" Expr -> Expr {cons("OpImpl"), right} - - context-free priorities - - Expr "." Id -> Expr - > Expr ExprList -> ExprList - > Expr Expr -> Expr - > "!" Expr -> Expr - > Expr "==" Expr -> Expr - > Expr "!=" Expr -> Expr - > Expr "&&" Expr -> Expr - > Expr "||" Expr -> Expr - > Expr "->" Expr -> Expr - > "assert" Expr ";" Expr -> Expr - > "{" {Formal ","}* "}" ":" Expr -> Expr - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Lexical syntax. - -module Fix-Lexicals -exports - sorts Id Int Str Path PathComp Uri - lexical syntax - [a-zA-Z\_][a-zA-Z0-9\_\']* -> Id - "rec" -> Id {reject} - "let" -> Id {reject} - "if" -> Id {reject} - "then" -> Id {reject} - "else" -> Id {reject} - "assert" -> Id {reject} - - [0-9]+ -> Int - - "\"" ~[\n\"]* "\"" -> Str - - "." ("/" PathComp)+ -> Path - ".." ("/" PathComp)+ -> Path - ("/" PathComp)+ -> Path - [a-zA-Z0-9\.\_\-\+]+ -> PathComp - - [a-zA-Z] [a-zA-Z0-9\+\-\.]* ":" [a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\']* -> Uri - - lexical restrictions - Id -/- [a-zA-Z0-9\_\'] - Int -/- [0-9] - Path -/- [a-zA-Z0-9\.\_\-\+\/] - Uri -/- [a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\'] - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Layout. - -module Fix-Layout -exports - sorts HashComment Asterisk Comment - lexical syntax - [\ \t\n] -> LAYOUT - HashComment -> LAYOUT - Comment -> LAYOUT - "#" ~[\n]* -> HashComment - "/*" ( ~[\*] | Asterisk )* "*/" -> Comment - [\*] -> Asterisk - lexical restrictions - Asterisk -/- [\/] - HashComment -/- ~[\n] - context-free restrictions - LAYOUT? -/- [\ \t\n] diff --git a/src/libexpr/parser.cc b/src/libexpr/parser.cc index 1e0ef0c45..167c34bd8 100644 --- a/src/libexpr/parser.cc +++ b/src/libexpr/parser.cc @@ -5,133 +5,63 @@ #include #include -extern "C" { -#include -#include -} - #include "aterm.hh" #include "parser.hh" -#include "parse-table.h" -/* Cleanup cleans up an imploded parse tree into an actual abstract - syntax tree that we can evaluate. It removes quotes around - strings, converts integer literals into actual integers, and - absolutises paths relative to the directory containing the input - file. */ -struct Cleanup : TermFun +struct ParseData { + Expr result; string basePath; - - virtual ATerm operator () (ATerm e) - { - checkInterrupt(); - - ATMatcher m; - string s; - - if (atMatch(m, e) >> "Str" >> s) - return ATmake("Str()", - string(s, 1, s.size() - 2).c_str()); - - if (atMatch(m, e) >> "Path" >> s) - return ATmake("Path()", absPath(s, basePath).c_str()); - - if (atMatch(m, e) >> "Int" >> s) { - istringstream s2(s); - int n; - s2 >> n; - return ATmake("Int()", n); - } - - if (atMatch(m, e) >> "Var" >> "true") - return ATmake("Bool(True)"); - - if (atMatch(m, e) >> "Var" >> "false") - return ATmake("Bool(False)"); - - if (atMatch(m, e) >> "ExprNil") - return (ATerm) ATempty; - - ATerm e1; - ATermList e2; - if (atMatch(m, e) >> "ExprCons" >> e1 >> e2) - return (ATerm) ATinsert(e2, e1); - - return e; - } + string location; + string error; }; +extern "C" { + +#include "parser-tab.h" +#include "lexer-tab.h" + + /* Callbacks for getting from C to C++. Due to a (small) bug in the + GLR code of Bison we cannot currently compile the parser as C++ + code. */ + + void setParseResult(ParseData * data, ATerm t) + { + data->result = t; + } + + ATerm absParsedPath(ParseData * data, ATerm t) + { + return string2ATerm(absPath(aterm2String(t), data->basePath).c_str()); + } + + void parseError(ParseData * data, char * error, int line, int column) + { + data->error = (format("%1%, at line %2%, column %3%, of %4%") + % error % line % column % data->location).str(); + } + + int yyparse(yyscan_t scanner, ParseData * data); +} + static Expr parse(const char * text, const string & location, const Path & basePath) { - /* Initialise the SDF libraries. */ - static bool initialised = false; - static ATerm parseTable = 0; - static language lang = 0; + yyscan_t scanner; + ParseData data; + data.basePath = basePath; + data.location = location; - if (!initialised) { - PT_initMEPTApi(); - PT_initAsFix2Api(); - SGinitParser(ATfalse); - - ATprotect(&parseTable); - parseTable = ATreadFromBinaryString( - (char *) nixParseTable, sizeof nixParseTable); - if (!parseTable) - throw Error(format("cannot construct parse table term")); - - ATprotect(&lang); - lang = ATmake("Nix"); - if (!SGopenLanguageFromTerm("nix-parse", lang, parseTable)) - throw Error(format("cannot open language")); - - SG_STARTSYMBOL_ON(); - SG_OUTPUT_ON(); - SG_ASFIX2ME_ON(); - SG_AMBIGUITY_ERROR_ON(); - SG_FILTER_OFF(); - - initialised = true; - } - - /* Parse it. */ - ATerm result = SGparseString(lang, "Expr", (char *) text); - if (!result) - throw SysError(format("parse failed in `%1%'") % location); - if (SGisParseError(result)) - throw Error(format("parse error in `%1%': %2%") - % location % result); - - /* Implode it. */ - PT_ParseTree tree = PT_makeParseTreeFromTerm(result); - if (!tree) - throw Error(format("cannot create parse tree")); + yylex_init(&scanner); + yy_scan_string(text, scanner); + int res = yyparse(scanner, &data); + yylex_destroy(scanner); - ATerm imploded = PT_implodeParseTree(tree, - ATtrue, - ATtrue, - ATtrue, - ATtrue, - ATtrue, - ATtrue, - ATfalse, - ATtrue, - ATtrue, - ATtrue, - ATfalse); - if (!imploded) - throw Error(format("cannot implode parse tree")); + if (res) throw Error(data.error); - printMsg(lvlVomit, format("imploded parse tree of `%1%': %2%") - % location % imploded); - - /* Finally, clean it up. */ - Cleanup cleanup; - cleanup.basePath = basePath; - return bottomupRewrite(cleanup, imploded); + return data.result; } @@ -171,7 +101,7 @@ Expr parseExprFromFile(Path path) readFull(fd, (unsigned char *) text, st.st_size); text[st.st_size] = 0; - return parse(text, path, dirOf(path)); + return parse(text, "`" + path + "'", dirOf(path)); } diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y new file mode 100644 index 000000000..45e6a98e8 --- /dev/null +++ b/src/libexpr/parser.y @@ -0,0 +1,128 @@ +%glr-parser +%pure-parser +%locations +%error-verbose +%parse-param { yyscan_t scanner } +%parse-param { void * data } +%lex-param { yyscan_t scanner } + +%{ +#include +#include +#include +#include + +#include "parser-tab.h" +#include "lexer-tab.h" + +void setParseResult(void * data, ATerm t); +void parseError(void * data, char * error, int line, int column); +ATerm absParsedPath(void * data, ATerm t); + +void yyerror(YYLTYPE * loc, yyscan_t scanner, void * data, char * s) +{ + parseError(data, s, loc->first_line, loc->first_column); +} + +%} + +%union { + ATerm t; + ATermList ts; +} + +%type start expr expr_function expr_assert expr_op +%type expr_app expr_select expr_simple bind formal +%type binds expr_list formals +%token ID INT STR PATH URI +%token IF THEN ELSE ASSERT LET REC EQ NEQ AND OR IMPL + +%nonassoc IMPL +%left OR +%left AND +%nonassoc EQ NEQ +%left NEG + +%% + +start: expr { setParseResult(data, $1); }; + +expr: expr_function; + +expr_function + : '{' formals '}' ':' expr_function + { $$ = ATmake("Function(, )", $2, $5); } + | expr_assert + ; + +expr_assert + : ASSERT expr ';' expr_assert + { $$ = ATmake("Assert(, )", $2, $4); } + | expr_op + ; + +expr_op + : '!' expr_op %prec NEG { $$ = ATmake("OpNot()", $2); } + | expr_op EQ expr_op { $$ = ATmake("OpEq(, )", $1, $3); } + | expr_op NEQ expr_op { $$ = ATmake("OpNEq(, )", $1, $3); } + | expr_op AND expr_op { $$ = ATmake("OpAnd(, )", $1, $3); } + | expr_op OR expr_op { $$ = ATmake("OpOr(, )", $1, $3); } + | expr_op IMPL expr_op { $$ = ATmake("OpImpl(, )", $1, $3); } + | expr_app + ; + +expr_app + : expr_app expr_select + { $$ = ATmake("Call(, )", $1, $2); } + | expr_select { $$ = $1; } + ; + +expr_select + : expr_select '.' ID + { $$ = ATmake("Select(, )", $1, $3); } + | expr_simple { $$ = $1; } + ; + +expr_simple + : ID { $$ = ATmake("Var()", $1); } + | STR { $$ = ATmake("Str()", $1); } + | PATH { $$ = ATmake("Path()", absParsedPath(data, $1)); } + | URI { $$ = ATmake("Uri()", $1); } + | '(' expr ')' { $$ = $2; } + | LET '{' binds '}' { $$ = ATmake("LetRec()", $3); } + | REC '{' binds '}' { $$ = ATmake("Rec()", $3); } + | '{' binds '}' { $$ = ATmake("Attrs()", $2); } + | '[' expr_list ']' { $$ = ATmake("List()", $2); } + | IF expr THEN expr ELSE expr + { $$ = ATmake("If(, , )", $2, $4, $6); } + ; + +binds + : binds bind { $$ = ATinsert($1, $2); } + | { $$ = ATempty; } + ; + +bind + : ID '=' expr ';' + { $$ = ATmake("Bind(, )", $1, $3); } + ; + +expr_list + : 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 { $$ = ATinsert($3, $1); } /* idem - right recursive */ + | formal { $$ = ATinsert(ATempty, $1); } + ; + +formal + : ID { $$ = ATmake("NoDefFormal()", $1); } + | ID '?' expr { $$ = ATmake("DefFormal(, )", $1, $3); } + ; + +%% From c6257185139bf5f298b19177867f3afa8e5472b7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 30 Jan 2004 16:32:14 +0000 Subject: [PATCH 0422/6440] * Detect flex and bison; updated the manual. --- configure.ac | 2 ++ doc/manual/installation.xml | 49 +++++++++++++++++++++++++------------ doc/manual/overview.xml | 4 +-- externals/Makefile.am | 35 +++----------------------- src/libexpr/Makefile.am | 6 +++-- 5 files changed, 45 insertions(+), 51 deletions(-) diff --git a/configure.ac b/configure.ac index e04b9684b..c6cb782d8 100644 --- a/configure.ac +++ b/configure.ac @@ -45,6 +45,8 @@ AC_LANG_POP(C++) AC_PATH_PROG(wget, wget) AC_PATH_PROG(xmllint, xmllint) AC_PATH_PROG(xsltproc, xsltproc) +AC_PATH_PROG(flex, flex, false) +AC_PATH_PROG(bison, bison, false) AC_ARG_WITH(docbook-catalog, AC_HELP_STRING([--with-docbook-catalog=PATH], [path of the DocBook XML DTD]), diff --git a/doc/manual/installation.xml b/doc/manual/installation.xml index 39f6654ef..1f45404e9 100644 --- a/doc/manual/installation.xml +++ b/doc/manual/installation.xml @@ -46,25 +46,42 @@ $ svn checkout https://svn.cs.uu.nl:12443/repos/trace/nix/trunk nix - To rebuild this manual and the man-pages you need the - xmllint and xsltproc, which are - part of the libxml2 and libxslt - packages, respectively. You also need the DocBook XSL - stylesheets and optionally the - DocBook XML 4.2 DTD. Note that these are only required if you - modify the manual sources or when you are building from the Subversion - repository. + To build this manual and the man-pages you need the + xmllint and xsltproc + programs, which are part of the libxml2 and + libxslt packages, respectively. You also + need the DocBook XSL + stylesheets and optionally the + DocBook XML 4.2 DTD. Note that these are only required + if you modify the manual sources or when you are building from + the Subversion repository. - Nix uses Sleepycat's Berkeley DB, CWI's ATerm library, and SDF parser - 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 externals/ directory. See - externals/Makefile.am for the precise URLs of these - packages. + To build the parser, very recent versions + of Bison and Flex are required. (This is because Nix needs GLR + support in Bison and reentrancy support in Flex.) For Bison, + you need version 1.875c or higher (1.875 does + not work), which can be obtained from the + GNU FTP + server. For Flex, you need version 2.5.31, which is + available on SourceForge. Slightly + older versions may also work, but ancient versions like the + 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. + + + + Nix uses Sleepycat's Berkeley DB and CWI's ATerm 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 externals/ directory. + See externals/Makefile.am for the precise + URLs of these packages. diff --git a/doc/manual/overview.xml b/doc/manual/overview.xml index 5525f4bc0..a5bcccd34 100644 --- a/doc/manual/overview.xml +++ b/doc/manual/overview.xml @@ -174,8 +174,8 @@ $ nix-env -if pkgs/system/i686-linux.nix pan $ nix-pull -http://catamaran.labs.cs.uu.nl/~eelco/nix/nixpkgs-version/ -obtaining list of Nix archives at http://catamaran.labs.cs.uu.nl/~eelco/nix/nixpkgs-version... +http://catamaran.labs.cs.uu.nl/dist/nix/nixpkgs-version/ +obtaining list of Nix archives at http://catamaran.labs.cs.uu.nl/dist/nix/nixpkgs-version... ... diff --git a/externals/Makefile.am b/externals/Makefile.am index c74cb227e..5f48a697d 100644 --- a/externals/Makefile.am +++ b/externals/Makefile.am @@ -56,37 +56,10 @@ build-aterm: have-aterm touch build-aterm -# SDF bundle +all: build-db build-aterm -SDF2 = sdf2-bundle-1.6 - -$(SDF2).tar.gz: - @echo "Nix requires the SDF2 bundle to build." - @echo "Please download version 1.6 from" - @echo " ftp://ftp.stratego-language.org/pub/stratego/sdf2/sdf2-bundle-1.6.tar.gz" - @echo "and place it in the externals/ directory." - false - -$(SDF2): $(SDF2).tar.gz - gunzip < $(SDF2).tar.gz | tar xvf - - -have-sdf2: - $(MAKE) $(SDF2) - touch have-sdf2 - -build-sdf2: have-sdf2 - (pfx=`pwd` && \ - cd $(SDF2) && \ - CC="$(CC) -pg" ./configure --prefix=$$pfx/inst --with-cflags="$(CFLAGS)" && \ - make && \ - make install) - touch build-sdf2 - - -all: build-db build-aterm build-sdf2 - -EXTRA_DIST = $(DB).tar.gz $(ATERM).tar.gz $(SDF2).tar.gz +EXTRA_DIST = $(DB).tar.gz $(ATERM).tar.gz ext-clean: - $(RM) -f have-db build-db have-aterm build-aterm have-sdf2 build-sdf2 - $(RM) -rf $(DB) $(ATERM) $(SDF2) + $(RM) -f have-db build-db have-aterm build-aterm + $(RM) -rf $(DB) $(ATERM) diff --git a/src/libexpr/Makefile.am b/src/libexpr/Makefile.am index 66a3008ed..7a361771e 100644 --- a/src/libexpr/Makefile.am +++ b/src/libexpr/Makefile.am @@ -12,11 +12,13 @@ AM_CFLAGS = \ # Parser generation. +parser.o: parser-tab.h lexer-tab.h + parser-tab.c parser-tab.h: parser.y - ../grammartest/inst/bin/bison -v -o parser-tab.c parser.y -d + $(bison) -v -o parser-tab.c parser.y -d lexer-tab.c lexer-tab.h: lexer.l - flex --outfile lexer-tab.c --header-file=lexer-tab.h lexer.l + $(flex) --outfile lexer-tab.c --header-file=lexer-tab.h lexer.l CLEANFILES = From 619f20775dae99ad5cd04ff6e7f7cde693d912f0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 30 Jan 2004 17:06:03 +0000 Subject: [PATCH 0423/6440] * Parser numbers again. * Include missing files in distributions. --- src/libexpr/Makefile.am | 2 +- src/libexpr/lexer.l | 5 ++++- src/libexpr/parser.y | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libexpr/Makefile.am b/src/libexpr/Makefile.am index 7a361771e..a16cd0785 100644 --- a/src/libexpr/Makefile.am +++ b/src/libexpr/Makefile.am @@ -2,7 +2,7 @@ noinst_LIBRARIES = libexpr.a libexpr_a_SOURCES = nixexpr.cc nixexpr.hh parser.cc parser.hh \ eval.cc eval.hh primops.cc primops.hh \ - lexer-tab.c lexer-tab.h parser-tab.c parser-tab.h + lexer.l lexer-tab.c lexer-tab.h parser.y parser-tab.c parser-tab.h AM_CXXFLAGS = \ -I.. -I../../externals/inst/include -I../libutil -I../libstore diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l index 705b31b41..3b6e0bb65 100644 --- a/src/libexpr/lexer.l +++ b/src/libexpr/lexer.l @@ -58,7 +58,10 @@ rec { return REC; } \-\> { return IMPL; } {ID} { yylval->t = ATmake("", yytext); return ID; /* !!! alloc */ } -{INT} { return INT; } +{INT} { int n = atoi(yytext); /* !!! overflow */ + yylval->t = ATmake("", n); + return INT; + } {STR} { int len = strlen(yytext); yytext[len - 1] = 0; yylval->t = ATmake("", yytext + 1); diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 45e6a98e8..dc03117bb 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -85,6 +85,7 @@ expr_select expr_simple : ID { $$ = ATmake("Var()", $1); } + | INT { $$ = ATmake("Int()", $1); } | STR { $$ = ATmake("Str()", $1); } | PATH { $$ = ATmake("Path()", absParsedPath(data, $1)); } | URI { $$ = ATmake("Uri()", $1); } From 47c003cb5999344aa2e4cb9f912551e33a94cd41 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 30 Jan 2004 17:14:08 +0000 Subject: [PATCH 0424/6440] * Doh! --- src/libexpr/Makefile.am | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libexpr/Makefile.am b/src/libexpr/Makefile.am index a16cd0785..02269bcb8 100644 --- a/src/libexpr/Makefile.am +++ b/src/libexpr/Makefile.am @@ -2,7 +2,9 @@ noinst_LIBRARIES = libexpr.a libexpr_a_SOURCES = nixexpr.cc nixexpr.hh parser.cc parser.hh \ eval.cc eval.hh primops.cc primops.hh \ - lexer.l lexer-tab.c lexer-tab.h parser.y parser-tab.c parser-tab.h + lexer-tab.c lexer-tab.h parser-tab.c parser-tab.h + +EXTRA_DIST = lexer.l parser.y AM_CXXFLAGS = \ -I.. -I../../externals/inst/include -I../libutil -I../libstore From d9f30fe7c74ae8518a575c0d15ee00aa46a2229a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 2 Feb 2004 10:51:54 +0000 Subject: [PATCH 0425/6440] * Sort `nix-env -q' output by derivation name. * `--version' flag for all commands. * Manual updates. --- doc/manual/Makefile.am | 2 +- doc/manual/manual.xml | 3 +- doc/manual/nix-env.xml | 9 ++--- doc/manual/nix-instantiate.xml | 22 +++--------- doc/manual/nix-store.xml | 21 ++--------- doc/manual/opt-common-syn.xml | 8 +++++ .../{opt-verbose.xml => opt-common.xml} | 35 +++++++++++++++++++ src/libmain/Makefile.am | 1 + src/libmain/shared.cc | 3 ++ src/nix-env/help.txt | 2 +- src/nix-env/main.cc | 33 +++++++++++------ 11 files changed, 83 insertions(+), 56 deletions(-) create mode 100644 doc/manual/opt-common-syn.xml rename doc/manual/{opt-verbose.xml => opt-common.xml} (76%) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index b1f5c3f46..8bf06cf2b 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -8,7 +8,7 @@ XSLTPROC = $(ENV) $(xsltproc) $(xmlflags) --catalogs \ SOURCES = manual.xml introduction.xml installation.xml overview.xml \ nix-env.xml nix-store.xml nix-instantiate.xml \ - troubleshooting.xml bugs.xml opt-verbose.xml \ + troubleshooting.xml bugs.xml opt-common.xml opt-common-syn.xml \ quick-start.xml style.css images manual.is-valid: $(SOURCES) version.xml diff --git a/doc/manual/manual.xml b/doc/manual/manual.xml index d47e57123..686ab612f 100644 --- a/doc/manual/manual.xml +++ b/doc/manual/manual.xml @@ -7,7 +7,8 @@ - + + diff --git a/doc/manual/nix-env.xml b/doc/manual/nix-env.xml index c73716381..a943fd976 100644 --- a/doc/manual/nix-env.xml +++ b/doc/manual/nix-env.xml @@ -7,12 +7,7 @@ nix-env - - - - - - + &opt-common-syn; @@ -71,7 +66,7 @@ - &opt-verbose; + &opt-common; / diff --git a/doc/manual/nix-instantiate.xml b/doc/manual/nix-instantiate.xml index 2e2749e43..ee073a17b 100644 --- a/doc/manual/nix-instantiate.xml +++ b/doc/manual/nix-instantiate.xml @@ -1,16 +1,13 @@ nix-instantiate - generate Nix expressions from a high-level description + instantiate store expressions from Nix expressions - fix - - - - + nix-instantiate + &opt-common-syn; files @@ -19,19 +16,10 @@ Description - The command fix generates Nix expressions from - expressions is Fix's own high-level language. While Nix expressions are - very primitive and not intended to be written directly, Fix expressions - are quite easy to write. + The command nix-instantiate generates + (low-level) store expressions from (high-level) Nix expressions. - - - diff --git a/doc/manual/nix-store.xml b/doc/manual/nix-store.xml index 7758d04ea..a3fcad063 100644 --- a/doc/manual/nix-store.xml +++ b/doc/manual/nix-store.xml @@ -7,12 +7,7 @@ nix-store - - - - - - + &opt-common-syn; operation options arguments @@ -51,19 +46,7 @@ - &opt-verbose; - - - / - - - Specifies that in case of a build failure, the temporary directory - (usually in /tmp) in which the build takes - place should not be deleted. The path of the build directory is - printed as an informational message. - - - + &opt-common; diff --git a/doc/manual/opt-common-syn.xml b/doc/manual/opt-common-syn.xml new file mode 100644 index 000000000..3a3e4ce87 --- /dev/null +++ b/doc/manual/opt-common-syn.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/doc/manual/opt-verbose.xml b/doc/manual/opt-common.xml similarity index 76% rename from doc/manual/opt-verbose.xml rename to doc/manual/opt-common.xml index 53fe07ae7..6c8db2a72 100644 --- a/doc/manual/opt-verbose.xml +++ b/doc/manual/opt-common.xml @@ -1,3 +1,23 @@ + + + + + Prints out a summary of the command syntax and exits. + + + + + + + + + + Prints out the Nix version number on standard output and exits. + + + + + / @@ -72,6 +92,7 @@ + / @@ -85,3 +106,17 @@ + + + + / + + + Specifies that in case of a build failure, the temporary + directory (usually in /tmp) in which the + build takes place should not be deleted. The path of the build + directory is printed as an informational message. + + + + diff --git a/src/libmain/Makefile.am b/src/libmain/Makefile.am index a4969ff36..6d70b0406 100644 --- a/src/libmain/Makefile.am +++ b/src/libmain/Makefile.am @@ -7,4 +7,5 @@ AM_CXXFLAGS = \ -DNIX_DATA_DIR=\"$(datadir)\" \ -DNIX_STATE_DIR=\"$(localstatedir)/nix\" \ -DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \ + -DNIX_VERSION=\"$(VERSION)\" \ -I.. -I../../externals/inst/include -I../libutil -I../libstore diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index e91ef2667..32f4f8124 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -70,6 +70,9 @@ static void initAndRun(int argc, char * * argv) else if (arg == "--help") { printHelp(); return; + } else if (arg == "--version") { + cout << format("%1% (Nix) %2%") % programId % NIX_VERSION << endl; + return; } else if (arg == "--keep-failed" || arg == "-K") keepFailed = true; else remaining.push_back(arg); diff --git a/src/nix-env/help.txt b/src/nix-env/help.txt index 823f5213a..f1419cdf7 100644 --- a/src/nix-env/help.txt +++ b/src/nix-env/help.txt @@ -21,7 +21,7 @@ name `*' may be used to indicate all derivations. Query types: --name: print derivation names (default) - --expr / -e: print derivation store expression + --expr: print derivation store expression --status / -s: print installed/present status Query sources: diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index a2e9b119a..0fd4ec63a 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -29,6 +29,7 @@ struct DrvInfo }; typedef map DrvInfos; +typedef vector DrvInfoList; void printHelp() @@ -547,6 +548,12 @@ static void opUninstall(Globals & globals, } +static bool cmpDrvByName(const DrvInfo & a, const DrvInfo & b) +{ + return a.name < b.name; +} + + static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) { @@ -556,7 +563,7 @@ static void opQuery(Globals & globals, for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ++i) if (*i == "--name") query = qName; - else if (*i == "--expr" || *i == "-e") query = qDrvPath; + else if (*i == "--expr") query = qDrvPath; else if (*i == "--status" || *i == "-s") query = qStatus; else if (*i == "--installed") source = sInstalled; else if (*i == "--available" || *i == "-a") source = sAvailable; @@ -580,19 +587,25 @@ static void opQuery(Globals & globals, } if (opArgs.size() != 0) throw UsageError("no arguments expected"); + + /* Sort them by name. */ + DrvInfoList drvs2; + for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) + drvs2.push_back(i->second); + sort(drvs2.begin(), drvs2.end(), cmpDrvByName); /* Perform the specified query on the derivations. */ switch (query) { case qName: { - for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) - cout << format("%1%\n") % i->second.name; + for (DrvInfoList::iterator i = drvs2.begin(); i != drvs2.end(); ++i) + cout << format("%1%\n") % i->name; break; } case qDrvPath: { - for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) - cout << format("%1%\n") % i->second.drvPath; + for (DrvInfoList::iterator i = drvs2.begin(); i != drvs2.end(); ++i) + cout << format("%1%\n") % i->drvPath; break; } @@ -605,14 +618,14 @@ static void opQuery(Globals & globals, i != installed.end(); ++i) installedPaths.insert(i->second.outPath); - for (DrvInfos::iterator i = drvs.begin(); i != drvs.end(); ++i) { - Paths subs = querySubstitutes(i->second.drvPath); + for (DrvInfoList::iterator i = drvs2.begin(); i != drvs2.end(); ++i) { + Paths subs = querySubstitutes(i->drvPath); cout << format("%1%%2%%3% %4%\n") - % (installedPaths.find(i->second.outPath) + % (installedPaths.find(i->outPath) != installedPaths.end() ? 'I' : '-') - % (isValidPath(i->second.outPath) ? 'P' : '-') + % (isValidPath(i->outPath) ? 'P' : '-') % (subs.size() > 0 ? 'S' : '-') - % i->second.name; + % i->name; } break; } From 1c9c0a5a46822be60c999f0196567c9e17cf5fa3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 2 Feb 2004 21:39:33 +0000 Subject: [PATCH 0426/6440] * Added syntactic sugar to the construction of attribute sets to `inherit' variables from the surrounding lexical scope. E.g., {stdenv, libfoo}: derivation { builder = ./bla; inherit stdenv libfoo; xyzzy = 1; } is equivalent to {stdenv, libfoo}: derivation { builder = ./bla; stdenv = stdenv; libfoo = libfoo; xyzzy = 1; } Note that for mutually recursive attribute set definitions (`rec {...}'), this also works, that is, `rec {inherit x;}' is equivalent to `let {fresh = x; body = rec {x = fresh;};}', *not* `rec {x = x}'. --- src/libexpr/eval.cc | 30 ++++++++++++---------- src/libexpr/lexer.l | 1 + src/libexpr/nixexpr.cc | 10 +++++--- src/libexpr/parser.cc | 56 ++++++++++++++++++++++++++++-------------- src/libexpr/parser.y | 20 +++++++++++---- src/libexpr/primops.cc | 2 +- 6 files changed, 78 insertions(+), 41 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 77cab55d0..820e934a6 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -55,15 +55,15 @@ static Expr substArgs(Expr body, ATermList formals, Expr arg) /* Transform a mutually recursive set into a non-recursive set. Each attribute is transformed into an expression that has all references to attributes substituted with selection expressions on the - original set. E.g., e = `rec {x = f x y, y = x}' becomes `{x = f - (e.x) (e.y), y = e.x}'. */ -ATerm expandRec(ATerm e, ATermList bnds) + original set. E.g., e = `rec {x = f x y; y = x;}' becomes `{x = f + (e.x) (e.y); y = e.x;}'. */ +ATerm expandRec(ATerm e, ATermList rbnds, ATermList nrbnds) { ATMatcher m; /* Create the substitution list. */ ATermMap subs; - for (ATermIterator i(bnds); i; ++i) { + for (ATermIterator i(rbnds); i; ++i) { string s; Expr e2; if (!(atMatch(m, *i) >> "Bind" >> s >> e2)) @@ -73,7 +73,7 @@ ATerm expandRec(ATerm e, ATermList bnds) /* Create the non-recursive set. */ ATermMap as; - for (ATermIterator i(bnds); i; ++i) { + for (ATermIterator i(rbnds); i; ++i) { string s; Expr e2; if (!(atMatch(m, *i) >> "Bind" >> s >> e2)) @@ -81,6 +81,15 @@ ATerm expandRec(ATerm e, ATermList bnds) as.set(s, substitute(subs, e2)); } + /* Copy the non-recursive bindings. !!! inefficient */ + for (ATermIterator i(nrbnds); i; ++i) { + string s; + Expr e2; + if (!(atMatch(m, *i) >> "Bind" >> s >> e2)) + abort(); /* can't happen */ + as.set(s, e2); + } + return makeAttrs(as); } @@ -175,14 +184,9 @@ Expr evalExpr2(EvalState & state, Expr e) } /* Mutually recursive sets. */ - ATermList bnds; - if (atMatch(m, e) >> "Rec" >> bnds) - return expandRec(e, bnds); - - /* Let expressions `let {..., body = ...}' are just desugared - into `(rec {..., body = ...}).body'. */ - if (atMatch(m, e) >> "LetRec" >> bnds) - return evalExpr(state, ATmake("Select(Rec(), \"body\")", bnds)); + ATermList rbnds, nrbnds; + if (atMatch(m, e) >> "Rec" >> rbnds >> nrbnds) + return expandRec(e, rbnds, nrbnds); /* Conditionals. */ if (atMatch(m, e) >> "If" >> e1 >> e2 >> e3) { diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l index 3b6e0bb65..853362cd0 100644 --- a/src/libexpr/lexer.l +++ b/src/libexpr/lexer.l @@ -50,6 +50,7 @@ else { return ELSE; } assert { return ASSERT; } let { return LET; } rec { return REC; } +inherit { return INHERIT; } \=\= { return EQ; } \!\= { return NEQ; } diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 7de3e823c..b0f506e65 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -181,16 +181,18 @@ Expr substitute(const ATermMap & subs, Expr e) } /* Idem for a mutually recursive attribute set. */ - ATermList bindings; - if (atMatch(m, e) >> "Rec" >> bindings) { + ATermList rbnds, nrbnds; + if (atMatch(m, e) >> "Rec" >> rbnds >> nrbnds) { ATermMap subs2(subs); - for (ATermIterator i(bindings); i; ++i) { + for (ATermIterator i(rbnds); i; ++i) { Expr e; if (!(atMatch(m, *i) >> "Bind" >> s >> e)) abort(); /* can't happen */ subs2.remove(s); } - return ATmake("Rec()", substitute(subs2, (ATerm) bindings)); + return ATmake("Rec(, )", + substitute(subs2, (ATerm) rbnds), + substitute(subs, (ATerm) nrbnds)); } if (ATgetType(e) == AT_APPL) { diff --git a/src/libexpr/parser.cc b/src/libexpr/parser.cc index 167c34bd8..2574a55bd 100644 --- a/src/libexpr/parser.cc +++ b/src/libexpr/parser.cc @@ -17,32 +17,52 @@ struct ParseData string error; }; + extern "C" { #include "parser-tab.h" #include "lexer-tab.h" - /* Callbacks for getting from C to C++. Due to a (small) bug in the - GLR code of Bison we cannot currently compile the parser as C++ - code. */ - - void setParseResult(ParseData * data, ATerm t) - { - data->result = t; - } +/* Callbacks for getting from C to C++. Due to a (small) bug in the + GLR code of Bison we cannot currently compile the parser as C++ + code. */ - ATerm absParsedPath(ParseData * data, ATerm t) - { - return string2ATerm(absPath(aterm2String(t), data->basePath).c_str()); - } +void setParseResult(ParseData * data, ATerm t) +{ + data->result = t; +} + +ATerm absParsedPath(ParseData * data, ATerm t) +{ + return string2ATerm(absPath(aterm2String(t), data->basePath).c_str()); +} - void parseError(ParseData * data, char * error, int line, int column) - { - data->error = (format("%1%, at line %2%, column %3%, of %4%") - % error % line % column % data->location).str(); - } +void parseError(ParseData * data, char * error, int line, int column) +{ + data->error = (format("%1%, at line %2%, column %3%, of %4%") + % error % line % column % data->location).str(); +} - int yyparse(yyscan_t scanner, ParseData * data); +ATerm fixAttrs(int recursive, ATermList as) +{ + ATMatcher m; + ATermList bs = ATempty, cs = ATempty; + ATermList * is = recursive ? &cs : &bs; + for (ATermIterator i(as); i; ++i) { + ATermList names; + if (atMatch(m, *i) >> "Inherit" >> names) + for (ATermIterator j(names); j; ++j) + *is = ATinsert(*is, + ATmake("Bind(, Var())", *j, *j)); + else bs = ATinsert(bs, *i); + } + if (recursive) + return ATmake("Rec(, )", bs, cs); + else + return ATmake("Attrs()", bs); +} + +int yyparse(yyscan_t scanner, ParseData * data); } diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index dc03117bb..d97106fca 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -18,6 +18,7 @@ void setParseResult(void * data, ATerm t); void parseError(void * data, char * error, int line, int column); ATerm absParsedPath(void * data, ATerm t); +ATerm fixAttrs(int recursive, ATermList as); void yyerror(YYLTYPE * loc, yyscan_t scanner, void * data, char * s) { @@ -33,9 +34,9 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, void * data, char * s) %type start expr expr_function expr_assert expr_op %type expr_app expr_select expr_simple bind formal -%type binds expr_list formals +%type binds ids expr_list formals %token ID INT STR PATH URI -%token IF THEN ELSE ASSERT LET REC EQ NEQ AND OR IMPL +%token IF THEN ELSE ASSERT LET REC INHERIT EQ NEQ AND OR IMPL %nonassoc IMPL %left OR @@ -90,9 +91,14 @@ expr_simple | PATH { $$ = ATmake("Path()", absParsedPath(data, $1)); } | URI { $$ = ATmake("Uri()", $1); } | '(' expr ')' { $$ = $2; } - | LET '{' binds '}' { $$ = ATmake("LetRec()", $3); } - | REC '{' binds '}' { $$ = ATmake("Rec()", $3); } - | '{' binds '}' { $$ = ATmake("Attrs()", $2); } + /* Let expressions `let {..., body = ...}' are just desugared + into `(rec {..., body = ...}).body'. */ + | LET '{' binds '}' + { $$ = ATmake("Select(, \"body\")", fixAttrs(1, $3)); } + | REC '{' binds '}' + { $$ = fixAttrs(1, $3); } + | '{' binds '}' + { $$ = fixAttrs(0, $2); } | '[' expr_list ']' { $$ = ATmake("List()", $2); } | IF expr THEN expr ELSE expr { $$ = ATmake("If(, , )", $2, $4, $6); } @@ -106,8 +112,12 @@ binds bind : ID '=' expr ';' { $$ = ATmake("Bind(, )", $1, $3); } + | INHERIT ids ';' + { $$ = ATmake("Inherit()", $2); } ; +ids: ids ID { $$ = ATinsert($1, $2); } | { $$ = ATempty; }; + expr_list : expr_select expr_list { $$ = ATinsert($2, $1); } /* yes, this is right-recursive, but it doesn't matter since diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index da6927d0f..d1c398a34 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -96,7 +96,7 @@ static string processBinding(EvalState & state, Expr e, StoreExpr & ne) return st.str(); } - if (atMatch(m, e) >> "Attrs" >> es) { + if (atMatch(m, e) >> "Attrs") { Expr a = queryAttr(e, "type"); if (a && evalString(state, a) == "derivation") { a = queryAttr(e, "drvPath"); From c4f7ae4aa5fc7071cfa853ec5d75aaf00e7a97fc Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 3 Feb 2004 14:45:34 +0000 Subject: [PATCH 0427/6440] * Verify that all variables in a Nix expression are defined. --- src/libexpr/eval.cc | 18 +++++----- src/libexpr/nixexpr.cc | 75 +++++++++++++++++++++++++++++++++++------- src/libexpr/nixexpr.hh | 4 +++ src/libexpr/parser.cc | 16 +++++++++ 4 files changed, 92 insertions(+), 21 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 820e934a6..bbb1393f8 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -64,30 +64,30 @@ ATerm expandRec(ATerm e, ATermList rbnds, ATermList nrbnds) /* Create the substitution list. */ ATermMap subs; for (ATermIterator i(rbnds); i; ++i) { - string s; + ATerm name; Expr e2; - if (!(atMatch(m, *i) >> "Bind" >> s >> e2)) + if (!(atMatch(m, *i) >> "Bind" >> name >> e2)) abort(); /* can't happen */ - subs.set(s, ATmake("Select(, )", e, s.c_str())); + subs.set(name, ATmake("Select(, )", e, name)); } /* Create the non-recursive set. */ ATermMap as; for (ATermIterator i(rbnds); i; ++i) { - string s; + ATerm name; Expr e2; - if (!(atMatch(m, *i) >> "Bind" >> s >> e2)) + if (!(atMatch(m, *i) >> "Bind" >> name >> e2)) abort(); /* can't happen */ - as.set(s, substitute(subs, e2)); + as.set(name, substitute(subs, e2)); } /* Copy the non-recursive bindings. !!! inefficient */ for (ATermIterator i(nrbnds); i; ++i) { - string s; + ATerm name; Expr e2; - if (!(atMatch(m, *i) >> "Bind" >> s >> e2)) + if (!(atMatch(m, *i) >> "Bind" >> name >> e2)) abort(); /* can't happen */ - as.set(s, e2); + as.set(name, e2); } return makeAttrs(as); diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index b0f506e65..92027a70e 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -156,10 +156,10 @@ Expr substitute(const ATermMap & subs, Expr e) checkInterrupt(); ATMatcher m; - string s; + ATerm name; - if (atMatch(m, e) >> "Var" >> s) { - Expr sub = subs.get(s); + if (atMatch(m, e) >> "Var" >> name) { + Expr sub = subs.get(name); return sub ? sub : e; } @@ -170,11 +170,10 @@ Expr substitute(const ATermMap & subs, Expr e) if (atMatch(m, e) >> "Function" >> formals >> body) { ATermMap subs2(subs); for (ATermIterator i(formals); i; ++i) { - Expr def; - if (!(atMatch(m, *i) >> "NoDefFormal" >> s) && - !(atMatch(m, *i) >> "DefFormal" >> s >> def)) + if (!(atMatch(m, *i) >> "NoDefFormal" >> name) && + !(atMatch(m, *i) >> "DefFormal" >> name)) abort(); - subs2.remove(s); + subs2.remove(name); } return ATmake("Function(, )", formals, substitute(subs2, body)); @@ -184,12 +183,11 @@ Expr substitute(const ATermMap & subs, Expr e) ATermList rbnds, nrbnds; if (atMatch(m, e) >> "Rec" >> rbnds >> nrbnds) { ATermMap subs2(subs); - for (ATermIterator i(rbnds); i; ++i) { - Expr e; - if (!(atMatch(m, *i) >> "Bind" >> s >> e)) + for (ATermIterator i(rbnds); i; ++i) + if (atMatch(m, *i) >> "Bind" >> name) + subs2.remove(name); + else abort(); /* can't happen */ - subs2.remove(s); - } return ATmake("Rec(, )", substitute(subs2, (ATerm) rbnds), substitute(subs, (ATerm) nrbnds)); @@ -217,6 +215,59 @@ Expr substitute(const ATermMap & subs, Expr e) } +void checkVarDefs(const ATermMap & defs, Expr e) +{ + ATMatcher m; + ATerm name; + ATermList formals; + ATerm body; + ATermList rbnds, nrbnds; + + if (atMatch(m, e) >> "Var" >> name) { + if (!defs.get(name)) + throw Error(format("undefined variable `%1%'") + % aterm2String(name)); + return; + } + + else if (atMatch(m, e) >> "Function" >> formals >> body) { + ATermMap defs2(defs); + for (ATermIterator i(formals); i; ++i) { + Expr deflt; + if (!(atMatch(m, *i) >> "NoDefFormal" >> name)) + if (atMatch(m, *i) >> "DefFormal" >> name >> deflt) + checkVarDefs(defs, deflt); + else + abort(); + defs2.set(name, (ATerm) ATempty); + } + return checkVarDefs(defs2, body); + } + + else if (atMatch(m, e) >> "Rec" >> rbnds >> nrbnds) { + checkVarDefs(defs + , (ATerm) nrbnds); + ATermMap defs2(defs); + for (ATermIterator i(rbnds); i; ++i) { + if (!(atMatch(m, *i) >> "Bind" >> name)) + abort(); /* can't happen */ + defs2.set(name, (ATerm) ATempty); + } + checkVarDefs(defs2, (ATerm) rbnds); + } + + else if (ATgetType(e) == AT_APPL) { + int arity = ATgetArity(ATgetAFun(e)); + for (int i = 0; i < arity; ++i) + checkVarDefs(defs, ATgetArgument(e, i)); + } + + else if (ATgetType(e) == AT_LIST) + for (ATermIterator i((ATermList) e); i; ++i) + checkVarDefs(defs, *i); +} + + Expr makeBool(bool b) { return b ? ATmake("Bool(True)") : ATmake("Bool(False)"); diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 011c2900e..26c29a2f3 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -68,6 +68,10 @@ Expr makeAttrs(const ATermMap & attrs); /* Perform a set of substitutions on an expression. */ Expr substitute(const ATermMap & 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); + /* Create an expression representing a boolean. */ Expr makeBool(bool b); diff --git a/src/libexpr/parser.cc b/src/libexpr/parser.cc index 2574a55bd..0a550fb35 100644 --- a/src/libexpr/parser.cc +++ b/src/libexpr/parser.cc @@ -81,6 +81,22 @@ static Expr parse(const char * text, const string & location, if (res) throw Error(data.error); + ATermMap primOps; + primOps.set("import", (ATerm) ATempty); + primOps.set("derivation", (ATerm) ATempty); + primOps.set("true", (ATerm) ATempty); + primOps.set("false", (ATerm) ATempty); + primOps.set("null", (ATerm) ATempty); + primOps.set("isNull", (ATerm) ATempty); + primOps.set("toString", (ATerm) ATempty); + primOps.set("baseNameOf", (ATerm) ATempty); + + try { + checkVarDefs(primOps, data.result); + } catch (Error & e) { + throw Error(format("%1%, in %2%") % e.msg() % location); + } + return data.result; } From 9b44480612dd30a7292ec94a88e4018b8f18e3f0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 4 Feb 2004 16:03:29 +0000 Subject: [PATCH 0428/6440] * Use a map to lookup primops. * Various performance improvements in the evaluator. * Do not link against unused (and missing!) libraries (-lsglr, etc.). --- src/libexpr/eval.cc | 68 +++++++++++++++++++++++++-------- src/libexpr/eval.hh | 11 ++++++ src/libexpr/nixexpr.cc | 23 ++++++++++- src/libexpr/nixexpr.hh | 9 ++++- src/libexpr/parser.cc | 24 ++++-------- src/libexpr/parser.hh | 6 +-- src/libexpr/primops.cc | 12 ++++++ src/libexpr/primops.hh | 6 ++- src/nix-env/Makefile.am | 2 +- src/nix-env/main.cc | 5 ++- src/nix-instantiate/Makefile.am | 2 +- src/nix-instantiate/main.cc | 34 +---------------- 12 files changed, 127 insertions(+), 75 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index bbb1393f8..802b83aa1 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -3,12 +3,52 @@ #include "primops.hh" +static void addPrimOp(ATermMap & map, const string & name, void * f) +{ + map.set(name, (ATerm) ATmakeBlob(0, f)); +} + + +static void * lookupPrimOp(ATermMap & map, ATerm name) +{ + ATermBlob b = (ATermBlob) map.get(name); + if (!b) return 0; + return ATgetBlobData(b); +} + + EvalState::EvalState() - : normalForms(32768, 75) + : normalForms(32768, 50) { blackHole = ATmake("BlackHole()"); if (!blackHole) throw Error("cannot build black hole"); + nrEvaluated = nrCached = 0; + + addPrimOp0("true", primTrue); + addPrimOp0("false", primFalse); + addPrimOp0("null", primNull); + + addPrimOp1("import", primImport); + addPrimOp1("derivation", primDerivation); + addPrimOp1("baseNameOf", primBaseNameOf); + addPrimOp1("toString", primToString); + addPrimOp1("isNull", primIsNull); + + primOpsAll.add(primOps0); + primOpsAll.add(primOps1); +} + + +void EvalState::addPrimOp0(const string & name, PrimOp0 primOp) +{ + addPrimOp(primOps0, name, (void *) primOp); +} + + +void EvalState::addPrimOp1(const string & name, PrimOp1 primOp) +{ + addPrimOp(primOps1, name, (void *) primOp); } @@ -130,7 +170,7 @@ Expr evalExpr2(EvalState & state, Expr e) { ATMatcher m; Expr e1, e2, e3, e4; - string s1; + ATerm name; /* Normal forms. */ if (atMatch(m, e) >> "Str" || @@ -144,11 +184,12 @@ Expr evalExpr2(EvalState & state, Expr e) return e; /* Any encountered variables must be undeclared or primops. */ - if (atMatch(m, e) >> "Var" >> s1) { - if (s1 == "null") return primNull(state); - if (s1 == "true") return ATmake("Bool(True)"); - if (s1 == "false") return ATmake("Bool(False)"); - return e; + if (atMatch(m, e) >> "Var" >> name) { + PrimOp0 primOp = (PrimOp0) lookupPrimOp(state.primOps0, name); + if (primOp) + return primOp(state); + else + return e; } /* Function application. */ @@ -160,13 +201,9 @@ Expr evalExpr2(EvalState & state, Expr e) e1 = evalExpr(state, e1); /* Is it a primop or a function? */ - if (atMatch(m, e1) >> "Var" >> s1) { - if (s1 == "import") return primImport(state, e2); - if (s1 == "derivation") return primDerivation(state, e2); - if (s1 == "toString") return primToString(state, e2); - if (s1 == "baseNameOf") return primBaseNameOf(state, e2); - if (s1 == "isNull") return primIsNull(state, e2); - else throw badTerm("undefined variable/primop", e1); + if (atMatch(m, e1) >> "Var" >> name) { + PrimOp1 primOp = (PrimOp1) lookupPrimOp(state.primOps1, name); + if (primOp) return primOp(state, e2); else abort(); } else if (atMatch(m, e1) >> "Function" >> formals >> e4) @@ -177,6 +214,7 @@ Expr evalExpr2(EvalState & state, Expr e) } /* Attribute selection. */ + string s1; if (atMatch(m, e) >> "Select" >> e1 >> s1) { Expr a = queryAttr(evalExpr(state, e1), s1); if (!a) throw badTerm(format("missing attribute `%1%'") % s1, e); @@ -261,7 +299,7 @@ Expr evalExpr(EvalState & state, Expr e) Expr evalFile(EvalState & state, const Path & path) { startNest(nest, lvlTalkative, format("evaluating file `%1%'") % path); - Expr e = parseExprFromFile(path); + Expr e = parseExprFromFile(state, path); return evalExpr(state, e); } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 0bc052676..34ac467f1 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -11,9 +11,17 @@ typedef map DrvPaths; typedef map DrvHashes; +struct EvalState; +typedef Expr (* PrimOp0) (EvalState &); +typedef Expr (* PrimOp1) (EvalState &, Expr arg); + + struct EvalState { ATermMap normalForms; + ATermMap primOps0; /* nullary primops */ + ATermMap primOps1; /* unary primops */ + ATermMap primOpsAll; DrvPaths drvPaths; DrvHashes drvHashes; /* normalised derivation hashes */ Expr blackHole; @@ -22,6 +30,9 @@ struct EvalState unsigned int nrCached; EvalState(); + + void addPrimOp0(const string & name, PrimOp0 primOp); + void addPrimOp1(const string & name, PrimOp1 primOp); }; diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 92027a70e..7739e99a9 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -20,8 +20,7 @@ ATermMap::ATermMap(const ATermMap & map) table = ATtableCreate(ATgetLength(keys), maxLoadPct); if (!table) throw Error("cannot create ATerm table"); - for (ATermIterator i(keys); i; ++i) - set(*i, map.get(*i)); + add(map, keys); } @@ -75,6 +74,26 @@ ATermList ATermMap::keys() const } +void ATermMap::add(const ATermMap & map) +{ + ATermList keys = map.keys(); + add(map, keys); +} + + +void ATermMap::add(const ATermMap & map, ATermList & keys) +{ + for (ATermIterator i(keys); i; ++i) + set(*i, map.get(*i)); +} + + +void ATermMap::reset() +{ + ATtableReset(table); +} + + ATerm string2ATerm(const string & s) { return (ATerm) ATmakeAppl0(ATmakeAFun((char *) s.c_str(), 0, ATtrue)); diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 26c29a2f3..924f8b912 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -23,7 +23,7 @@ private: ATermTable table; public: - ATermMap(unsigned int initialSize = 16, unsigned int maxLoadPct = 75); + ATermMap(unsigned int initialSize = 64, unsigned int maxLoadPct = 75); ATermMap(const ATermMap & map); ~ATermMap(); @@ -37,6 +37,13 @@ public: void remove(const string & key); ATermList keys() const; + + void add(const ATermMap & map); + + void reset(); + +private: + void add(const ATermMap & map, ATermList & keys); }; diff --git a/src/libexpr/parser.cc b/src/libexpr/parser.cc index 0a550fb35..68b367340 100644 --- a/src/libexpr/parser.cc +++ b/src/libexpr/parser.cc @@ -66,7 +66,8 @@ int yyparse(yyscan_t scanner, ParseData * data); } -static Expr parse(const char * text, const string & location, +static Expr parse(EvalState & state, + const char * text, const string & location, const Path & basePath) { yyscan_t scanner; @@ -81,18 +82,8 @@ static Expr parse(const char * text, const string & location, if (res) throw Error(data.error); - ATermMap primOps; - primOps.set("import", (ATerm) ATempty); - primOps.set("derivation", (ATerm) ATempty); - primOps.set("true", (ATerm) ATempty); - primOps.set("false", (ATerm) ATempty); - primOps.set("null", (ATerm) ATempty); - primOps.set("isNull", (ATerm) ATempty); - primOps.set("toString", (ATerm) ATempty); - primOps.set("baseNameOf", (ATerm) ATempty); - try { - checkVarDefs(primOps, data.result); + checkVarDefs(state.primOpsAll, data.result); } catch (Error & e) { throw Error(format("%1%, in %2%") % e.msg() % location); } @@ -101,7 +92,7 @@ static Expr parse(const char * text, const string & location, } -Expr parseExprFromFile(Path path) +Expr parseExprFromFile(EvalState & state, Path path) { assert(path[0] == '/'); @@ -137,11 +128,12 @@ Expr parseExprFromFile(Path path) readFull(fd, (unsigned char *) text, st.st_size); text[st.st_size] = 0; - return parse(text, "`" + path + "'", dirOf(path)); + return parse(state, text, "`" + path + "'", dirOf(path)); } -Expr parseExprFromString(const string & s, const Path & basePath) +Expr parseExprFromString(EvalState & state, + const string & s, const Path & basePath) { - return parse(s.c_str(), "(string)", basePath); + return parse(state, s.c_str(), "(string)", basePath); } diff --git a/src/libexpr/parser.hh b/src/libexpr/parser.hh index 461dae08c..2af5385f6 100644 --- a/src/libexpr/parser.hh +++ b/src/libexpr/parser.hh @@ -1,15 +1,15 @@ #ifndef __PARSER_H #define __PARSER_H -#include "nixexpr.hh" +#include "eval.hh" /* Parse a Nix expression from the specified file. If `path' refers to a directory, the "/default.nix" is appended. */ -Expr parseExprFromFile(Path path); +Expr parseExprFromFile(EvalState & state, Path path); /* Parse a Nix expression from the specified string. */ -Expr parseExprFromString(const string & s, +Expr parseExprFromString(EvalState & state, const string & s, const Path & basePath); diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index d1c398a34..92683fcac 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -245,6 +245,18 @@ Expr primToString(EvalState & state, Expr arg) } +Expr primTrue(EvalState & state) +{ + return ATmake("Bool(True)"); +} + + +Expr primFalse(EvalState & state) +{ + return ATmake("Bool(False)"); +} + + Expr primNull(EvalState & state) { return ATmake("Null"); diff --git a/src/libexpr/primops.hh b/src/libexpr/primops.hh index 76d587afd..6b9722dac 100644 --- a/src/libexpr/primops.hh +++ b/src/libexpr/primops.hh @@ -8,7 +8,7 @@ argument. */ Expr primImport(EvalState & state, Expr arg); -/* Construct (as a unobservable) side effect) a Nix derivation +/* Construct (as a unobservable side effect) a Nix derivation expression that performs the derivation described by the argument set. Returns the original set extended with the following attributes: `outPath' containing the primary output path of the @@ -24,6 +24,10 @@ Expr primBaseNameOf(EvalState & state, Expr arg); /* Convert the argument (which can be a path or a uri) to a string. */ Expr primToString(EvalState & state, Expr arg); +/* Boolean constructors. */ +Expr primTrue(EvalState & state); +Expr primFalse(EvalState & state); + /* Return the null value. */ Expr primNull(EvalState & state); diff --git a/src/nix-env/Makefile.am b/src/nix-env/Makefile.am index 53d8d9b02..bfe7369c6 100644 --- a/src/nix-env/Makefile.am +++ b/src/nix-env/Makefile.am @@ -4,7 +4,7 @@ nix_env_SOURCES = main.cc help.txt nix_env_LDADD = ../libmain/libmain.a ../libexpr/libexpr.a \ ../libstore/libstore.a ../libutil/libutil.a \ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ - -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm + -lATerm main.o: help.txt.hh diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 0fd4ec63a..3810e9144 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -102,7 +102,7 @@ bool parseDerivations(EvalState & state, Expr e, DrvInfos & drvs) void loadDerivations(EvalState & state, Path nePath, DrvInfos & drvs) { - Expr e = parseExprFromFile(absPath(nePath)); + Expr e = parseExprFromFile(state, absPath(nePath)); if (!parseDerivations(state, e, drvs)) throw badTerm("expected set of derivations", e); } @@ -193,7 +193,8 @@ void createUserEnv(EvalState & state, const DrvInfos & drvs, const Path & linkPath) { /* Get the environment builder expression. */ - Expr envBuilder = parseExprFromFile(nixDataDir + "/nix/corepkgs/buildenv"); /* !!! */ + Expr envBuilder = parseExprFromFile(state, + nixDataDir + "/nix/corepkgs/buildenv"); /* !!! */ /* Construct the whole top level derivation. */ ATermList inputs = ATempty; diff --git a/src/nix-instantiate/Makefile.am b/src/nix-instantiate/Makefile.am index d04fdb376..0726d1296 100644 --- a/src/nix-instantiate/Makefile.am +++ b/src/nix-instantiate/Makefile.am @@ -4,7 +4,7 @@ nix_instantiate_SOURCES = main.cc help.txt nix_instantiate_LDADD = ../libmain/libmain.a ../libexpr/libexpr.a \ ../libstore/libstore.a ../libutil/libutil.a \ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ - -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm + -lATerm main.o: help.txt.hh diff --git a/src/nix-instantiate/main.cc b/src/nix-instantiate/main.cc index 305c3b551..cc3444837 100644 --- a/src/nix-instantiate/main.cc +++ b/src/nix-instantiate/main.cc @@ -15,31 +15,12 @@ void printHelp() } -#if 0 -static Path searchPath(const Paths & searchDirs, const Path & relPath) -{ - if (string(relPath, 0, 1) == "/") return relPath; - - for (Paths::const_iterator i = searchDirs.begin(); - i != searchDirs.end(); i++) - { - Path path = *i + "/" + relPath; - if (pathExists(path)) return path; - } - - throw Error( - format("path `%1%' not found in any of the search directories") - % relPath); -} -#endif - - static Expr evalStdin(EvalState & state) { startNest(nest, lvlTalkative, format("evaluating standard input")); string s, s2; while (getline(cin, s2)) s += s2 + "\n"; - Expr e = parseExprFromString(s, absPath(".")); + Expr e = parseExprFromString(state, s, absPath(".")); return evalExpr(state, e); } @@ -76,24 +57,11 @@ void run(Strings args) Strings files; bool readStdin = false; -#if 0 - state.searchDirs.push_back("."); - state.searchDirs.push_back(nixDataDir + "/nix"); -#endif - for (Strings::iterator it = args.begin(); it != args.end(); ) { string arg = *it++; -#if 0 - if (arg == "--includedir" || arg == "-I") { - if (it == args.end()) - throw UsageError(format("argument required in `%1%'") % arg); - state.searchDirs.push_back(*it++); - } - else -#endif if (arg == "-") readStdin = true; else if (arg[0] == '-') From 6d46e647ba16e19100dcd0abda9ca5a81ccf764f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 4 Feb 2004 16:20:51 +0000 Subject: [PATCH 0429/6440] * Fixed the old envpkgs filename. --- corepkgs/buildenv/builder.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/corepkgs/buildenv/builder.pl b/corepkgs/buildenv/builder.pl index 144ad3374..3bbb178c8 100755 --- a/corepkgs/buildenv/builder.pl +++ b/corepkgs/buildenv/builder.pl @@ -18,7 +18,7 @@ sub createLinks { my $basename = $srcfile; $basename =~ s/^.*\///g; # strip directory my $dstfile = "$dstdir/$basename"; - if ($srcfile =~ /\/envpkgs$/) { + if ($srcfile =~ /\/propagated-build-inputs$/) { } elsif (-d $srcfile) { # !!! hack for resolving name clashes if (!-e $dstfile) { From 9d25466b34a5f7c1c8b1c273976cf59c33961a6c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 4 Feb 2004 16:49:51 +0000 Subject: [PATCH 0430/6440] * An attribute set update operator (//). E.g., {x=1; y=2; z=3;} // {y=4;} => {x=1; y=4; z=3;} --- src/libexpr/eval.cc | 16 ++++++++++++++++ src/libexpr/lexer.l | 1 + src/libexpr/parser.y | 2 ++ 3 files changed, 19 insertions(+) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 802b83aa1..eaa4b4ea4 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -134,6 +134,18 @@ ATerm expandRec(ATerm e, ATermList rbnds, ATermList nrbnds) } +static Expr updateAttrs(Expr e1, Expr e2) +{ + /* Note: e1 and e2 should be in normal form. */ + + ATermMap attrs; + queryAllAttrs(e1, attrs); + queryAllAttrs(e2, attrs); + + return makeAttrs(attrs); +} + + string evalString(EvalState & state, Expr e) { e = evalExpr(state, e); @@ -264,6 +276,10 @@ Expr evalExpr2(EvalState & state, Expr e) if (atMatch(m, e) >> "OpOr" >> e1 >> e2) return makeBool(evalBool(state, e1) || evalBool(state, e2)); + /* Attribut set update (//). */ + if (atMatch(m, e) >> "OpUpdate" >> e1 >> e2) + return updateAttrs(evalExpr(state, e1), evalExpr(state, e2)); + /* Barf. */ throw badTerm("invalid expression", e); } diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l index 853362cd0..ce1c4673d 100644 --- a/src/libexpr/lexer.l +++ b/src/libexpr/lexer.l @@ -57,6 +57,7 @@ inherit { return INHERIT; } \&\& { return AND; } \|\| { return OR; } \-\> { return IMPL; } +\/\/ { return UPDATE; } {ID} { yylval->t = ATmake("", yytext); return ID; /* !!! alloc */ } {INT} { int n = atoi(yytext); /* !!! overflow */ diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index d97106fca..6c0fdbda2 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -42,6 +42,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, void * data, char * s) %left OR %left AND %nonassoc EQ NEQ +%right UPDATE %left NEG %% @@ -69,6 +70,7 @@ expr_op | expr_op AND expr_op { $$ = ATmake("OpAnd(, )", $1, $3); } | expr_op OR expr_op { $$ = ATmake("OpOr(, )", $1, $3); } | expr_op IMPL expr_op { $$ = ATmake("OpImpl(, )", $1, $3); } + | expr_op UPDATE expr_op { $$ = ATmake("OpUpdate(, )", $1, $3); } | expr_app ; From d445da7a7b3cbb4822bcad3904a36f0d914917d3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 4 Feb 2004 17:23:26 +0000 Subject: [PATCH 0431/6440] * Extended the `inherit' syntax to optionally select attributes from other attribute sets, rather than the current scope. E.g., {inherit (pkgs) gcc binutils;} is equivalent to {gcc = pkgs.gcc; binutils = pkgs.binutils;} I am not so happy about the syntax. --- src/libexpr/parser.cc | 15 ++++++++++----- src/libexpr/parser.y | 11 ++++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/libexpr/parser.cc b/src/libexpr/parser.cc index 68b367340..c300a0d07 100644 --- a/src/libexpr/parser.cc +++ b/src/libexpr/parser.cc @@ -50,11 +50,16 @@ ATerm fixAttrs(int recursive, ATermList as) ATermList * is = recursive ? &cs : &bs; for (ATermIterator i(as); i; ++i) { ATermList names; - if (atMatch(m, *i) >> "Inherit" >> names) - for (ATermIterator j(names); j; ++j) - *is = ATinsert(*is, - ATmake("Bind(, Var())", *j, *j)); - else bs = ATinsert(bs, *i); + Expr src; + if (atMatch(m, *i) >> "Inherit" >> src >> names) { + bool fromScope = atMatch(m, src) >> "Scope"; + for (ATermIterator j(names); j; ++j) { + Expr rhs = fromScope + ? ATmake("Var()", *j) + : ATmake("Select(, )", src, *j); + *is = ATinsert(*is, ATmake("Bind(, )", *j, rhs)); + } + } else bs = ATinsert(bs, *i); } if (recursive) return ATmake("Rec(, )", bs, cs); diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 6c0fdbda2..257c0cd38 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -33,7 +33,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, void * data, char * s) } %type start expr expr_function expr_assert expr_op -%type expr_app expr_select expr_simple bind formal +%type expr_app expr_select expr_simple bind inheritsrc formal %type binds ids expr_list formals %token ID INT STR PATH URI %token IF THEN ELSE ASSERT LET REC INHERIT EQ NEQ AND OR IMPL @@ -114,8 +114,13 @@ binds bind : ID '=' expr ';' { $$ = ATmake("Bind(, )", $1, $3); } - | INHERIT ids ';' - { $$ = ATmake("Inherit()", $2); } + | INHERIT inheritsrc ids ';' + { $$ = ATmake("Inherit(, )", $2, $3); } + ; + +inheritsrc + : '(' expr ')' { $$ = $2; } + | { $$ = ATmake("Scope"); } ; ids: ids ID { $$ = ATinsert($1, $2); } | { $$ = ATempty; }; From 66e94d3275e9a0a549c28b7d0ad5f3f897e2fbf0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 6 Feb 2004 10:30:20 +0000 Subject: [PATCH 0432/6440] * Improvements to profiles. Generations are now per-profile, e.g., default -> default-94-link default-82-link -> /nix/store/cc4480... default-83-link -> /nix/store/caeec8... ... default-94-link -> /nix/store/2896ca... experimental -> experimental-2-link experimental-1-link -> /nix/store/cc4480... experimental-2-link -> /nix/store/a3148f... * `--profile' / `-p' -> `--switch-profile' / `-S' * `--link' / `-l' -> `--profile' / `-p' * The default profile is stored in $prefix/var/nix/profiles. $prefix/var/nix/links is gone. Profiles can be stored anywhere. * The current profile is now referenced from ~/.nix-profile, not ~/.nix-userenv. * The roots to the garbage collector now have extension `.gcroot', not `.id'. --- doc/manual/installation.xml | 4 +- doc/manual/nix-env.xml | 10 ++-- doc/manual/overview.xml | 40 ++++++++-------- doc/manual/quick-start.xml | 2 +- scripts/nix-collect-garbage.in | 4 +- scripts/nix-profile.sh.in | 4 +- src/nix-env/Makefile.am | 3 ++ src/nix-env/help.txt | 4 +- src/nix-env/main.cc | 88 ++++++++++++++++++---------------- src/nix-store/Makefile.am | 3 -- 10 files changed, 84 insertions(+), 78 deletions(-) diff --git a/doc/manual/installation.xml b/doc/manual/installation.xml index 1f45404e9..d35b3de5d 100644 --- a/doc/manual/installation.xml +++ b/doc/manual/installation.xml @@ -192,9 +192,9 @@ rm -rf /nix/var To use Nix, some environment variables should be set. In particular, PATH should contain the directories prefix/bin and - ~/.nix-userenv/bin. The first directory + ~/.nix-profile/bin. The first directory contains the Nix tools themselves, while - ~/.nix-userenv is a symbolic link to the + ~/.nix-profile is a symbolic link to the current user environment (an automatically generated package consisting of symlinks to installed packages). The simplest way to set the required environment variables is to diff --git a/doc/manual/nix-env.xml b/doc/manual/nix-env.xml index a943fd976..bdf35bd32 100644 --- a/doc/manual/nix-env.xml +++ b/doc/manual/nix-env.xml @@ -111,14 +111,14 @@ - ~/.nix-userenv + ~/.nix-profile - A symbolic link to the user's current user environment. - By default, it points to - prefix/var/nix/links/current. + A symbolic link to the user's current profile. The + default profile is + prefix/var/nix/profiles/default. The PATH environment variable should - include ~/.nix-userenv for the user + include ~/.nix-profile/bin for the user environment to be visible to the user. diff --git a/doc/manual/overview.xml b/doc/manual/overview.xml index a5bcccd34..e42c811c0 100644 --- a/doc/manual/overview.xml +++ b/doc/manual/overview.xml @@ -189,30 +189,32 @@ obtaining list of Nix archives at http://catamaran.labs.cs.uu.nl/dist/nix/nixpkg it can be used immediately, that is, it now appears in a directory in the PATH environment variable. Specifically, PATH includes the entry - prefix/var/nix/links/current/bin, + prefix/var/nix/profiles/default/bin, where - prefix/var/nix/links/current + prefix/var/nix/profiles/default is just a symlink to the current user environment: -$ ls -l /nix/var/nix/links/ +$ ls -l /nix/var/nix/profiles/ ... -lrwxrwxrwx 1 eelco ... 15 -> /nix/store/1871...12b0-user-environment -lrwxrwxrwx 1 eelco ... 16 -> /nix/store/59ba...df6b-user-environment -lrwxrwxrwx 1 eelco ... current -> /nix/var/nix/links/16 +lrwxrwxrwx 1 eelco ... default-15-link -> /nix/store/1871...12b0-user-environment +lrwxrwxrwx 1 eelco ... default-16-link -> /nix/store/59ba...df6b-user-environment +lrwxrwxrwx 1 eelco ... default -> default-16-link - That is, current in this example is a link to - 16, which is the current user environment. Before - the installation, it pointed to 15. Note that this - means that you can atomically roll-back to the previous user environment - by pointing the symlink current at - 15 again. This also shows that operations such as - installation are atomic in the Nix system: any arbitrarily complex - set of installation, uninstallation, or upgrade actions eventually boil - down to the single operation of pointing a symlink somewhere else (which - can be implemented atomically in Unix). + That is, default in this example is a link + to default-16-link, which is the current + user environment. Before the installation, it pointed to + default-15-link. Note that this means that + you can atomically roll-back to the previous user environment by + pointing the symlink default at + default-15-link again. This also shows + that operations such as installation are atomic in the Nix + system: any arbitrarily complex set of installation, + uninstallation, or upgrade actions eventually boil down to the + single operation of pointing a symlink somewhere else (which can + be implemented atomically in Unix). @@ -221,15 +223,15 @@ lrwxrwxrwx 1 eelco ... current -> /nix/var/nix/links/16 -$ ls -l /nix/var/nix/links/16/bin +$ ls -l /nix/var/nix/profiles/default-16-link/bin lrwxrwxrwx 1 eelco ... MozillaFirebird -> /nix/store/35f8...4ae6-MozillaFirebird-0.7/bin/MozillaFirebird lrwxrwxrwx 1 eelco ... svn -> /nix/store/3829...fb5d-subversion-0.32.1/bin/svn ... Note that, e.g., svn = - /nix/var/nix/links/current/bin/svn = - /nix/var/nix/links/16/bin/svn = + /nix/var/nix/profiles/default/bin/svn = + /nix/var/nix/profiles/default-16-link/bin/svn = /nix/store/59ba...df6b-user-environment/bin/svn = /nix/store/3829...fb5d-subversion-0.32.1/bin/svn. diff --git a/doc/manual/quick-start.xml b/doc/manual/quick-start.xml index ec49dfb27..b16fe7de8 100644 --- a/doc/manual/quick-start.xml +++ b/doc/manual/quick-start.xml @@ -93,7 +93,7 @@ $ nix-env -iBf nixpkgs-version/ hello MozillaFirebird $ which hello -/home/eelco/.nix-userenv/bin/hello +/home/eelco/.nix-profile/bin/hello $ hello Hello, world! $ MozillaFirebird diff --git a/scripts/nix-collect-garbage.in b/scripts/nix-collect-garbage.in index d0552fd2f..1effc14d6 100755 --- a/scripts/nix-collect-garbage.in +++ b/scripts/nix-collect-garbage.in @@ -3,7 +3,7 @@ use strict; use IPC::Open2; -my $linkdir = "@localstatedir@/nix/links"; +my $linkdir = "@localstatedir@/nix/profiles"; my $storedir = "@prefix@/store"; my %alive; @@ -24,7 +24,7 @@ closedir DIR; my @roots; foreach my $link (@links) { $link = $linkdir . "/" . $link; - next if (!($link =~ /.id$/)); + next if (!($link =~ /.gcroot$/)); open ROOT, "<$link" or die "cannot open $link: $!"; my $root = ; chomp $root; diff --git a/scripts/nix-profile.sh.in b/scripts/nix-profile.sh.in index 55cff3e62..7f19f8014 100644 --- a/scripts/nix-profile.sh.in +++ b/scripts/nix-profile.sh.in @@ -1,9 +1,9 @@ if test -n "$HOME"; then - NIX_LINK="$HOME/.nix-userenv" + NIX_LINK="$HOME/.nix-profile" if ! test -L "$NIX_LINK"; then echo "creating $NIX_LINK" - _NIX_DEF_LINK=@localstatedir@/nix/links/current + _NIX_DEF_LINK=@localstatedir@/nix/profiles/default ln -s "$_NIX_DEF_LINK" "$NIX_LINK" fi diff --git a/src/nix-env/Makefile.am b/src/nix-env/Makefile.am index bfe7369c6..5353a7a6b 100644 --- a/src/nix-env/Makefile.am +++ b/src/nix-env/Makefile.am @@ -14,3 +14,6 @@ main.o: help.txt.hh AM_CXXFLAGS = \ -I.. -I../../externals/inst/include -I../libutil -I../libstore \ -I../libexpr -I../libmain + +install-data-local: + $(INSTALL) -d $(DESTDIR)$(localstatedir)/nix/profiles diff --git a/src/nix-env/help.txt b/src/nix-env/help.txt index f1419cdf7..3832f1655 100644 --- a/src/nix-env/help.txt +++ b/src/nix-env/help.txt @@ -12,7 +12,7 @@ Operations: The previous operations take a list of derivation names. The special name `*' may be used to indicate all derivations. - --profile / -p [FILE]: switch to specified user environment + --switch-profile / -S [FILE]: switch to specified profile --import / -I FILE: set default Nix expression --version: output version information @@ -31,7 +31,7 @@ Query sources: Options: - --link / -l LINK: use symlink LINK instead of (...)/current + --profile / -p LINK: use specified profile instead of target of ~/.nix-profile --file / -f FILE: use Nix expression FILE for installation, etc. --verbose / -v: verbose operation (may be repeated) --keep-failed / -K: keep temporary directories of failed builds diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 3810e9144..20b7da655 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -10,7 +10,7 @@ struct Globals { - Path linkPath; + Path profile; Path nixExprPath; EvalState state; }; @@ -116,12 +116,6 @@ static Path getHomeDir() } -static Path getLinksDir() -{ - return canonPath(nixStateDir + "/links"); -} - - static Path getDefNixExprPath() { return getHomeDir() + "/.nix-defexpr"; @@ -143,23 +137,30 @@ void queryInstalled(EvalState & state, DrvInfos & drvs, } -Path createGeneration(Path outPath, Path drvPath) +Path createGeneration(Path profile, Path outPath, Path drvPath) { - Path linksDir = getLinksDir(); - + Path profileDir = dirOf(profile); + string profileName = baseNameOf(profile); + unsigned int num = 0; - Strings names = readDirectory(linksDir); + Strings names = readDirectory(profileDir); for (Strings::iterator i = names.begin(); i != names.end(); ++i) { - istringstream s(*i); - unsigned int n; - if (s >> n && s.eof() && n >= num) num = n + 1; + if (string(*i, 0, profileName.size() + 1) != profileName + "-") continue; + string s = string(*i, profileName.size() + 1); + int p = s.find("-link"); + if (p == string::npos) continue; + istringstream str(string(s, 0, p)); + unsigned int n; + if (str >> n && str.eof() && n >= num) num = n + 1; } - Path generation; + Path generation, gcrootSrc; while (1) { - generation = (format("%1%/%2%") % linksDir % num).str(); + Path prefix = (format("%1%-%2%") % profile % num).str(); + generation = prefix + "-link"; + gcrootSrc = prefix + "-src.gcroot"; if (symlink(outPath.c_str(), generation.c_str()) == 0) break; if (errno != EEXIST) throw SysError(format("creating symlink `%1%'") % generation); @@ -167,7 +168,7 @@ Path createGeneration(Path outPath, Path drvPath) num++; } - writeStringToFile(generation + "-src.id", drvPath); + writeStringToFile(gcrootSrc, drvPath); return generation; } @@ -175,6 +176,9 @@ Path createGeneration(Path outPath, Path drvPath) void switchLink(Path link, Path target) { + /* Hacky. */ + if (dirOf(target) == dirOf(link)) target = baseNameOf(target); + Path tmp = canonPath(dirOf(link) + "/.new_" + baseNameOf(link)); if (symlink(target.c_str(), tmp.c_str()) != 0) throw SysError(format("creating symlink `%1%'") % tmp); @@ -190,7 +194,7 @@ void switchLink(Path link, Path target) void createUserEnv(EvalState & state, const DrvInfos & drvs, - const Path & linkPath) + const Path & profile) { /* Get the environment builder expression. */ Expr envBuilder = parseExprFromFile(state, @@ -243,9 +247,9 @@ void createUserEnv(EvalState & state, const DrvInfos & drvs, /* Switch the current user environment to the output path. */ debug(format("switching to new user environment")); - Path generation = createGeneration( + Path generation = createGeneration(profile, topLevelDrv.outPath, topLevelDrv.drvPath); - switchLink(linkPath, generation); + switchLink(profile, generation); } @@ -380,7 +384,7 @@ static DrvNames drvNamesFromArgs(const Strings & opArgs) static void installDerivations(EvalState & state, - Path nePath, DrvNames & selectors, const Path & linkPath) + Path nePath, DrvNames & selectors, const Path & profile) { debug(format("installing derivations from `%1%'") % nePath); @@ -415,10 +419,10 @@ static void installDerivations(EvalState & state, /* Add in the already installed derivations. */ DrvInfos installedDrvs; - queryInstalled(state, installedDrvs, linkPath); + queryInstalled(state, installedDrvs, profile); selectedDrvs.insert(installedDrvs.begin(), installedDrvs.end()); - createUserEnv(state, selectedDrvs, linkPath); + createUserEnv(state, selectedDrvs, profile); } @@ -431,12 +435,12 @@ static void opInstall(Globals & globals, DrvNames drvNames = drvNamesFromArgs(opArgs); installDerivations(globals.state, globals.nixExprPath, - drvNames, globals.linkPath); + drvNames, globals.profile); } static void upgradeDerivations(EvalState & state, - Path nePath, DrvNames & selectors, const Path & linkPath) + Path nePath, DrvNames & selectors, const Path & profile) { debug(format("upgrading derivations from `%1%'") % nePath); @@ -447,7 +451,7 @@ static void upgradeDerivations(EvalState & state, /* Load the currently installed derivations. */ DrvInfos installedDrvs; - queryInstalled(state, installedDrvs, linkPath); + queryInstalled(state, installedDrvs, profile); /* Fetch all derivations from the input file. */ DrvInfos availDrvs; @@ -496,7 +500,7 @@ static void upgradeDerivations(EvalState & state, newDrvs.insert(*bestDrv); } - createUserEnv(state, newDrvs, linkPath); + createUserEnv(state, newDrvs, profile); } @@ -510,15 +514,15 @@ static void opUpgrade(Globals & globals, DrvNames drvNames = drvNamesFromArgs(opArgs); upgradeDerivations(globals.state, globals.nixExprPath, - drvNames, globals.linkPath); + drvNames, globals.profile); } static void uninstallDerivations(EvalState & state, DrvNames & selectors, - Path & linkPath) + Path & profile) { DrvInfos installedDrvs; - queryInstalled(state, installedDrvs, linkPath); + queryInstalled(state, installedDrvs, profile); for (DrvInfos::iterator i = installedDrvs.begin(); i != installedDrvs.end(); ++i) @@ -533,7 +537,7 @@ static void uninstallDerivations(EvalState & state, DrvNames & selectors, } } - createUserEnv(state, installedDrvs, linkPath); + createUserEnv(state, installedDrvs, profile); } @@ -545,7 +549,7 @@ static void opUninstall(Globals & globals, DrvNames drvNames = drvNamesFromArgs(opArgs); - uninstallDerivations(globals.state, drvNames, globals.linkPath); + uninstallDerivations(globals.state, drvNames, globals.profile); } @@ -576,7 +580,7 @@ static void opQuery(Globals & globals, switch (source) { case sInstalled: - queryInstalled(globals.state, drvs, globals.linkPath); + queryInstalled(globals.state, drvs, globals.profile); break; case sAvailable: { @@ -612,7 +616,7 @@ static void opQuery(Globals & globals, case qStatus: { DrvInfos installed; - queryInstalled(globals.state, installed, globals.linkPath); + queryInstalled(globals.state, installed, globals.profile); PathSet installedPaths; /* output paths of installed drvs */ for (DrvInfos::iterator i = installed.begin(); @@ -644,11 +648,11 @@ static void opSwitchProfile(Globals & globals, if (opArgs.size() > 1) throw UsageError(format("`--profile' takes at most one argument")); - Path linkPath = - absPath(opArgs.size() == 0 ? globals.linkPath : opArgs.front()); - Path linkPathFinal = getHomeDir() + "/.nix-userenv"; + Path profile = + absPath(opArgs.size() == 0 ? globals.profile : opArgs.front()); + Path profileLink = getHomeDir() + "/.nix-userenv"; - switchLink(linkPathFinal, linkPath); + switchLink(profileLink, profile); } @@ -676,7 +680,7 @@ void run(Strings args) Operation op = 0; Globals globals; - globals.linkPath = getLinksDir() + "/current"; + globals.profile = canonPath(nixStateDir + "/profiles/default"); globals.nixExprPath = getDefNixExprPath(); for (Strings::iterator i = args.begin(); i != args.end(); ++i) { @@ -694,11 +698,11 @@ void run(Strings args) op = opQuery; else if (arg == "--import" || arg == "-I") /* !!! bad name */ op = opDefaultExpr; - else if (arg == "--link" || arg == "-l") { + else if (arg == "--profile" || arg == "-p") { ++i; if (i == args.end()) throw UsageError( format("`%1%' requires an argument") % arg); - globals.linkPath = absPath(*i); + globals.profile = absPath(*i); } else if (arg == "--file" || arg == "-f") { ++i; @@ -706,7 +710,7 @@ void run(Strings args) format("`%1%' requires an argument") % arg); globals.nixExprPath = absPath(*i); } - else if (arg == "--profile" || arg == "-p") + else if (arg == "--switch-profile" || arg == "-S") op = opSwitchProfile; else if (arg[0] == '-') opFlags.push_back(arg); diff --git a/src/nix-store/Makefile.am b/src/nix-store/Makefile.am index 74cf814a7..3a152e3e4 100644 --- a/src/nix-store/Makefile.am +++ b/src/nix-store/Makefile.am @@ -15,9 +15,6 @@ AM_CXXFLAGS = \ install-data-local: $(INSTALL) -d $(DESTDIR)$(localstatedir)/nix $(INSTALL) -d $(DESTDIR)$(localstatedir)/nix/db - $(INSTALL) -d $(DESTDIR)$(localstatedir)/nix/links - rm -f $(DESTDIR)$(prefix)/current - ln -sf $(localstatedir)/nix/links/current $(DESTDIR)$(prefix)/current $(INSTALL) -d $(DESTDIR)$(localstatedir)/log/nix $(INSTALL) -d $(DESTDIR)$(prefix)/store # $(bindir)/nix-store --init From 49bafe1faf4eedf0f059740be4f99c700ee93fe7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 6 Feb 2004 10:59:06 +0000 Subject: [PATCH 0433/6440] * Use the profile pointed to by ~/.nix-profile if no --profile argument is specified. --- src/libutil/util.cc | 2 +- src/nix-env/main.cc | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 5c8b7279c..3a6914cba 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -101,7 +101,7 @@ bool pathExists(const Path & path) { int res; struct stat st; - res = stat(path.c_str(), &st); + res = lstat(path.c_str(), &st); if (!res) return true; if (errno != ENOENT) throw SysError(format("getting status of %1%") % path); diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 20b7da655..00b6c7f8d 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -645,12 +645,11 @@ static void opSwitchProfile(Globals & globals, { if (opFlags.size() > 0) throw UsageError(format("unknown flags `%1%'") % opFlags.front()); - if (opArgs.size() > 1) - throw UsageError(format("`--profile' takes at most one argument")); + if (opArgs.size() != 1) + throw UsageError(format("`--profile' takes exactly one argument")); - Path profile = - absPath(opArgs.size() == 0 ? globals.profile : opArgs.front()); - Path profileLink = getHomeDir() + "/.nix-userenv"; + Path profile = opArgs.front(); + Path profileLink = getHomeDir() + "/.nix-profile"; switchLink(profileLink, profile); } @@ -680,7 +679,6 @@ void run(Strings args) Operation op = 0; Globals globals; - globals.profile = canonPath(nixStateDir + "/profiles/default"); globals.nixExprPath = getDefNixExprPath(); for (Strings::iterator i = args.begin(); i != args.end(); ++i) { @@ -723,6 +721,13 @@ void run(Strings args) if (!op) throw UsageError("no operation specified"); + if (globals.profile == "") { + Path profileLink = getHomeDir() + "/.nix-profile"; + globals.profile = pathExists(profileLink) + ? absPath(readLink(profileLink), dirOf(profileLink)) + : canonPath(nixStateDir + "/profiles/default"); + } + openDB(); op(globals, opFlags, opArgs); From 7abf9911d997b241f1e6b58805140fbfe1f2771d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 6 Feb 2004 14:49:41 +0000 Subject: [PATCH 0434/6440] * Refactoring. --- src/nix-env/Makefile.am | 2 +- src/nix-env/main.cc | 131 +--------------------------------------- src/nix-env/names.cc | 121 +++++++++++++++++++++++++++++++++++++ src/nix-env/names.hh | 29 +++++++++ 4 files changed, 152 insertions(+), 131 deletions(-) create mode 100644 src/nix-env/names.cc create mode 100644 src/nix-env/names.hh diff --git a/src/nix-env/Makefile.am b/src/nix-env/Makefile.am index 5353a7a6b..bb418ae06 100644 --- a/src/nix-env/Makefile.am +++ b/src/nix-env/Makefile.am @@ -1,6 +1,6 @@ bin_PROGRAMS = nix-env -nix_env_SOURCES = main.cc help.txt +nix_env_SOURCES = main.cc names.cc names.hh help.txt nix_env_LDADD = ../libmain/libmain.a ../libexpr/libexpr.a \ ../libstore/libstore.a ../libutil/libutil.a \ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 00b6c7f8d..051236681 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -1,5 +1,6 @@ #include +#include "names.hh" #include "globals.hh" #include "normalise.hh" #include "shared.hh" @@ -253,136 +254,6 @@ void createUserEnv(EvalState & state, const DrvInfos & drvs, } -struct DrvName -{ - string fullName; - string name; - string version; - unsigned int hits; - - /* Parse a derivation name. The `name' part of a derivation name - is everything up to but not including the first dash *not* - followed by a letter. The `version' part is the rest - (excluding the separating dash). E.g., `apache-httpd-2.0.48' - is parsed to (`apache-httpd', '2.0.48'). */ - DrvName(const string & s) : hits(0) - { - name = fullName = s; - for (unsigned int i = 0; i < s.size(); ++i) { - /* !!! isalpha/isdigit are affected by the locale. */ - if (s[i] == '-' && i + 1 < s.size() && !isalpha(s[i + 1])) { - name = string(s, 0, i); - version = string(s, i + 1); - break; - } - } - } - - bool matches(DrvName & n) - { - if (name != "*" && name != n.name) return false; - if (version != "" && version != n.version) return false; - return true; - } -}; - - -string nextComponent(string::const_iterator & p, - const string::const_iterator end) -{ - /* Skip any dots and dashes (component separators). */ - while (p != end && (*p == '.' || *p == '-')) ++p; - - if (p == end) return ""; - - /* If the first character is a digit, consume the longest sequence - of digits. Otherwise, consume the longest sequence of - non-digit, non-separator characters. */ - string s; - if (isdigit(*p)) - while (p != end && isdigit(*p)) s += *p++; - else - while (p != end && (!isdigit(*p) && *p != '.' && *p != '-')) - s += *p++; - - return s; -} - - -#include - -bool parseInt(const string & s, int & n) -{ - istringstream st(s); - st >> n; - return !st.fail(); -} - - -static bool componentsLT(const string & c1, const string & c2) -{ - int n1, n2; - bool c1Num = parseInt(c1, n1), c2Num = parseInt(c2, n2); - - if (c1Num && c2Num) return n1 < n2; - else if (c1 == "" && c2Num) return true; - else if (c1 == "pre" && c2 != "pre") return true; - else if (c2 == "pre") return false; - /* Assume that `2.3a' < `2.3.1'. */ - else if (c2Num) return true; - else if (c1Num) return false; - else return c1 < c2; -} - - -static int compareVersions(const string & v1, const string & v2) -{ - string::const_iterator p1 = v1.begin(); - string::const_iterator p2 = v2.begin(); - - while (p1 != v1.end() || p2 != v2.end()) { - string c1 = nextComponent(p1, v1.end()); - string c2 = nextComponent(p2, v2.end()); - if (componentsLT(c1, c2)) return -1; - else if (componentsLT(c2, c1)) return 1; - } - - return 0; -} - - -static void testCompareVersions() -{ -#define TEST(v1, v2, n) assert( \ - compareVersions(v1, v2) == n && compareVersions(v2, v1) == -n) - TEST("1.0", "2.3", -1); - TEST("2.1", "2.3", -1); - TEST("2.3", "2.3", 0); - TEST("2.5", "2.3", 1); - TEST("3.1", "2.3", 1); - TEST("2.3.1", "2.3", 1); - TEST("2.3.1", "2.3a", 1); - TEST("2.3pre1", "2.3", -1); - TEST("2.3pre3", "2.3pre12", -1); - TEST("2.3a", "2.3c", -1); - TEST("2.3pre1", "2.3c", -1); - TEST("2.3pre1", "2.3q", -1); -} - - -typedef list DrvNames; - - -static DrvNames drvNamesFromArgs(const Strings & opArgs) -{ - DrvNames result; - for (Strings::const_iterator i = opArgs.begin(); - i != opArgs.end(); ++i) - result.push_back(DrvName(*i)); - return result; -} - - static void installDerivations(EvalState & state, Path nePath, DrvNames & selectors, const Path & profile) { diff --git a/src/nix-env/names.cc b/src/nix-env/names.cc new file mode 100644 index 000000000..252b07b91 --- /dev/null +++ b/src/nix-env/names.cc @@ -0,0 +1,121 @@ +#include "names.hh" + + +/* Parse a derivation name. The `name' part of a derivation name is + everything up to but not including the first dash *not* followed by + a letter. The `version' part is the rest (excluding the separating + dash). E.g., `apache-httpd-2.0.48' is parsed to (`apache-httpd', + '2.0.48'). */ +DrvName::DrvName(const string & s) : hits(0) +{ + name = fullName = s; + for (unsigned int i = 0; i < s.size(); ++i) { + /* !!! isalpha/isdigit are affected by the locale. */ + if (s[i] == '-' && i + 1 < s.size() && !isalpha(s[i + 1])) { + name = string(s, 0, i); + version = string(s, i + 1); + break; + } + } +} + + +bool DrvName::matches(DrvName & n) +{ + if (name != "*" && name != n.name) return false; + if (version != "" && version != n.version) return false; + return true; +} + + +static string nextComponent(string::const_iterator & p, + const string::const_iterator end) +{ + /* Skip any dots and dashes (component separators). */ + while (p != end && (*p == '.' || *p == '-')) ++p; + + if (p == end) return ""; + + /* If the first character is a digit, consume the longest sequence + of digits. Otherwise, consume the longest sequence of + non-digit, non-separator characters. */ + string s; + if (isdigit(*p)) + while (p != end && isdigit(*p)) s += *p++; + else + while (p != end && (!isdigit(*p) && *p != '.' && *p != '-')) + s += *p++; + + return s; +} + + +#include + +static bool parseInt(const string & s, int & n) +{ + istringstream st(s); + st >> n; + return !st.fail(); +} + + +static bool componentsLT(const string & c1, const string & c2) +{ + int n1, n2; + bool c1Num = parseInt(c1, n1), c2Num = parseInt(c2, n2); + + if (c1Num && c2Num) return n1 < n2; + else if (c1 == "" && c2Num) return true; + else if (c1 == "pre" && c2 != "pre") return true; + else if (c2 == "pre") return false; + /* Assume that `2.3a' < `2.3.1'. */ + else if (c2Num) return true; + else if (c1Num) return false; + else return c1 < c2; +} + + +int compareVersions(const string & v1, const string & v2) +{ + string::const_iterator p1 = v1.begin(); + string::const_iterator p2 = v2.begin(); + + while (p1 != v1.end() || p2 != v2.end()) { + string c1 = nextComponent(p1, v1.end()); + string c2 = nextComponent(p2, v2.end()); + if (componentsLT(c1, c2)) return -1; + else if (componentsLT(c2, c1)) return 1; + } + + return 0; +} + + +static void testCompareVersions() +{ +#define TEST(v1, v2, n) assert( \ + compareVersions(v1, v2) == n && compareVersions(v2, v1) == -n) + TEST("1.0", "2.3", -1); + TEST("2.1", "2.3", -1); + TEST("2.3", "2.3", 0); + TEST("2.5", "2.3", 1); + TEST("3.1", "2.3", 1); + TEST("2.3.1", "2.3", 1); + TEST("2.3.1", "2.3a", 1); + TEST("2.3pre1", "2.3", -1); + TEST("2.3pre3", "2.3pre12", -1); + TEST("2.3a", "2.3c", -1); + TEST("2.3pre1", "2.3c", -1); + TEST("2.3pre1", "2.3q", -1); +} + + +DrvNames drvNamesFromArgs(const Strings & opArgs) +{ + DrvNames result; + for (Strings::const_iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + result.push_back(DrvName(*i)); + return result; +} diff --git a/src/nix-env/names.hh b/src/nix-env/names.hh new file mode 100644 index 000000000..0fc9b57d0 --- /dev/null +++ b/src/nix-env/names.hh @@ -0,0 +1,29 @@ +#ifndef __NAMES_H +#define __NAMES_H + +#include +#include + +#include "util.hh" + + +struct DrvName +{ + string fullName; + string name; + string version; + unsigned int hits; + + DrvName(const string & s); + bool matches(DrvName & n); +}; + + +typedef list DrvNames; + + +int compareVersions(const string & v1, const string & v2); +DrvNames drvNamesFromArgs(const Strings & opArgs); + + +#endif /* !__NAMES_H */ From 7c0fa4474f0010f8266b85e891ca6049595ecb32 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 6 Feb 2004 14:57:10 +0000 Subject: [PATCH 0435/6440] * More refactoring. --- src/nix-env/Makefile.am | 3 ++- src/nix-env/main.cc | 57 +---------------------------------------- src/nix-env/profiles.cc | 57 +++++++++++++++++++++++++++++++++++++++++ src/nix-env/profiles.hh | 14 ++++++++++ 4 files changed, 74 insertions(+), 57 deletions(-) create mode 100644 src/nix-env/profiles.cc create mode 100644 src/nix-env/profiles.hh diff --git a/src/nix-env/Makefile.am b/src/nix-env/Makefile.am index bb418ae06..e4b03943c 100644 --- a/src/nix-env/Makefile.am +++ b/src/nix-env/Makefile.am @@ -1,6 +1,7 @@ bin_PROGRAMS = nix-env -nix_env_SOURCES = main.cc names.cc names.hh help.txt +nix_env_SOURCES = main.cc names.cc names.hh \ + profiles.cc profiles.hh help.txt nix_env_LDADD = ../libmain/libmain.a ../libexpr/libexpr.a \ ../libstore/libstore.a ../libutil/libutil.a \ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 051236681..87190b620 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -1,5 +1,6 @@ #include +#include "profiles.hh" #include "names.hh" #include "globals.hh" #include "normalise.hh" @@ -138,62 +139,6 @@ void queryInstalled(EvalState & state, DrvInfos & drvs, } -Path createGeneration(Path profile, Path outPath, Path drvPath) -{ - Path profileDir = dirOf(profile); - string profileName = baseNameOf(profile); - - unsigned int num = 0; - - Strings names = readDirectory(profileDir); - for (Strings::iterator i = names.begin(); i != names.end(); ++i) { - if (string(*i, 0, profileName.size() + 1) != profileName + "-") continue; - string s = string(*i, profileName.size() + 1); - int p = s.find("-link"); - if (p == string::npos) continue; - istringstream str(string(s, 0, p)); - unsigned int n; - if (str >> n && str.eof() && n >= num) num = n + 1; - } - - Path generation, gcrootSrc; - - while (1) { - Path prefix = (format("%1%-%2%") % profile % num).str(); - generation = prefix + "-link"; - gcrootSrc = prefix + "-src.gcroot"; - if (symlink(outPath.c_str(), generation.c_str()) == 0) break; - if (errno != EEXIST) - throw SysError(format("creating symlink `%1%'") % generation); - /* Somebody beat us to it, retry with a higher number. */ - num++; - } - - writeStringToFile(gcrootSrc, drvPath); - - return generation; -} - - -void switchLink(Path link, Path target) -{ - /* Hacky. */ - if (dirOf(target) == dirOf(link)) target = baseNameOf(target); - - Path tmp = canonPath(dirOf(link) + "/.new_" + baseNameOf(link)); - if (symlink(target.c_str(), tmp.c_str()) != 0) - throw SysError(format("creating symlink `%1%'") % tmp); - /* The rename() system call is supposed to be essentially atomic - on Unix. That is, if we have links `current -> X' and - `new_current -> Y', and we rename new_current to current, a - process accessing current will see X or Y, but never a - file-not-found or other error condition. This is sufficient to - atomically switch user environments. */ - if (rename(tmp.c_str(), link.c_str()) != 0) - throw SysError(format("renaming `%1%' to `%2%'") % tmp % link); -} - - void createUserEnv(EvalState & state, const DrvInfos & drvs, const Path & profile) { diff --git a/src/nix-env/profiles.cc b/src/nix-env/profiles.cc new file mode 100644 index 000000000..5b9c00406 --- /dev/null +++ b/src/nix-env/profiles.cc @@ -0,0 +1,57 @@ +#include "profiles.hh" + + +Path createGeneration(Path profile, Path outPath, Path drvPath) +{ + Path profileDir = dirOf(profile); + string profileName = baseNameOf(profile); + + unsigned int num = 0; + + Strings names = readDirectory(profileDir); + for (Strings::iterator i = names.begin(); i != names.end(); ++i) { + if (string(*i, 0, profileName.size() + 1) != profileName + "-") continue; + string s = string(*i, profileName.size() + 1); + int p = s.find("-link"); + if (p == string::npos) continue; + istringstream str(string(s, 0, p)); + unsigned int n; + if (str >> n && str.eof() && n >= num) num = n + 1; + } + + Path generation, gcrootSrc; + + while (1) { + Path prefix = (format("%1%-%2%") % profile % num).str(); + generation = prefix + "-link"; + gcrootSrc = prefix + "-src.gcroot"; + if (symlink(outPath.c_str(), generation.c_str()) == 0) break; + if (errno != EEXIST) + throw SysError(format("creating symlink `%1%'") % generation); + /* Somebody beat us to it, retry with a higher number. */ + num++; + } + + writeStringToFile(gcrootSrc, drvPath); + + return generation; +} + + +void switchLink(Path link, Path target) +{ + /* Hacky. */ + if (dirOf(target) == dirOf(link)) target = baseNameOf(target); + + Path tmp = canonPath(dirOf(link) + "/.new_" + baseNameOf(link)); + if (symlink(target.c_str(), tmp.c_str()) != 0) + throw SysError(format("creating symlink `%1%'") % tmp); + /* The rename() system call is supposed to be essentially atomic + on Unix. That is, if we have links `current -> X' and + `new_current -> Y', and we rename new_current to current, a + process accessing current will see X or Y, but never a + file-not-found or other error condition. This is sufficient to + atomically switch user environments. */ + if (rename(tmp.c_str(), link.c_str()) != 0) + throw SysError(format("renaming `%1%' to `%2%'") % tmp % link); +} diff --git a/src/nix-env/profiles.hh b/src/nix-env/profiles.hh new file mode 100644 index 000000000..3d3c86e50 --- /dev/null +++ b/src/nix-env/profiles.hh @@ -0,0 +1,14 @@ +#ifndef __PROFILES_H +#define __PROFILES_H + +#include + +#include "util.hh" + + +Path createGeneration(Path profile, Path outPath, Path drvPath); + +void switchLink(Path link, Path target); + + +#endif /* !__PROFILES_H */ From 73ab2ed4fd1c3cd974851be4f13e7a276ab16acf Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 6 Feb 2004 16:03:27 +0000 Subject: [PATCH 0436/6440] * A command `--list-generations' to show all generations for a profile. --- src/nix-env/main.cc | 34 +++++++++++++++++++++++++++++----- src/nix-env/profiles.cc | 41 +++++++++++++++++++++++++++++++++++++---- src/nix-env/profiles.hh | 14 ++++++++++++++ 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 87190b620..6aa342c1d 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -1,5 +1,3 @@ -#include - #include "profiles.hh" #include "names.hh" #include "globals.hh" @@ -9,6 +7,9 @@ #include "eval.hh" #include "help.txt.hh" +#include +#include + struct Globals { @@ -460,9 +461,9 @@ static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs) { if (opFlags.size() > 0) - throw UsageError(format("unknown flags `%1%'") % opFlags.front()); + throw UsageError(format("unknown flag `%1%'") % opFlags.front()); if (opArgs.size() != 1) - throw UsageError(format("`--profile' takes exactly one argument")); + throw UsageError(format("exactly one argument expected")); Path profile = opArgs.front(); Path profileLink = getHomeDir() + "/.nix-profile"; @@ -471,13 +472,34 @@ static void opSwitchProfile(Globals & globals, } +static void opListGenerations(Globals & globals, + Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError(format("unknown flag `%1%'") % opFlags.front()); + if (opArgs.size() != 0) + throw UsageError(format("no arguments expected")); + + Generations gens = findGenerations(globals.profile); + + for (Generations::iterator i = gens.begin(); i != gens.end(); ++i) { + tm t; + if (!localtime_r(&i->creationTime, &t)) throw Error("cannot convert time"); + cout << format("%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02|\n") + % i->number + % (t.tm_year + 1900) % (t.tm_mon + 1) % t.tm_mday + % t.tm_hour % t.tm_min % t.tm_sec; + } +} + + static void opDefaultExpr(Globals & globals, Strings opFlags, Strings opArgs) { if (opFlags.size() > 0) throw UsageError(format("unknown flags `%1%'") % opFlags.front()); if (opArgs.size() != 1) - throw UsageError(format("`--import' takes exactly one argument")); + throw UsageError(format("exactly one argument expected")); Path defNixExpr = absPath(opArgs.front()); Path defNixExprLink = getDefNixExprPath(); @@ -526,6 +548,8 @@ void run(Strings args) } else if (arg == "--switch-profile" || arg == "-S") op = opSwitchProfile; + else if (arg == "--list-generations") + op = opListGenerations; else if (arg[0] == '-') opFlags.push_back(arg); else diff --git a/src/nix-env/profiles.cc b/src/nix-env/profiles.cc index 5b9c00406..22da6336b 100644 --- a/src/nix-env/profiles.cc +++ b/src/nix-env/profiles.cc @@ -1,13 +1,23 @@ #include "profiles.hh" +#include +#include +#include -Path createGeneration(Path profile, Path outPath, Path drvPath) + +static bool cmpGensByNumber(const Generation & a, const Generation & b) { + return a.number < b.number; +} + + +Generations findGenerations(Path profile) +{ + Generations gens; + Path profileDir = dirOf(profile); string profileName = baseNameOf(profile); - unsigned int num = 0; - Strings names = readDirectory(profileDir); for (Strings::iterator i = names.begin(); i != names.end(); ++i) { if (string(*i, 0, profileName.size() + 1) != profileName + "-") continue; @@ -16,9 +26,32 @@ Path createGeneration(Path profile, Path outPath, Path drvPath) if (p == string::npos) continue; istringstream str(string(s, 0, p)); unsigned int n; - if (str >> n && str.eof() && n >= num) num = n + 1; + if (str >> n && str.eof()) { + Generation gen; + gen.path = profileDir + "/" + *i; + gen.number = n; + struct stat st; + if (lstat(gen.path.c_str(), &st) != 0) + throw SysError(format("statting `%1%'") % gen.path); + gen.creationTime = st.st_mtime; + gens.push_back(gen); + } } + gens.sort(cmpGensByNumber); + + return gens; +} + + +Path createGeneration(Path profile, Path outPath, Path drvPath) +{ + /* The new generation number should be higher than old the + previous ones. */ + Generations gens = findGenerations(profile); + unsigned int num = gens.size() > 0 ? gens.front().number : 0; + + /* Create the new generation. */ Path generation, gcrootSrc; while (1) { diff --git a/src/nix-env/profiles.hh b/src/nix-env/profiles.hh index 3d3c86e50..f9480ed3f 100644 --- a/src/nix-env/profiles.hh +++ b/src/nix-env/profiles.hh @@ -6,6 +6,20 @@ #include "util.hh" +struct Generation +{ + int number; + Path path; + time_t creationTime; +}; + +typedef list Generations; + + +/* Returns the list of currently present generations for the specified + profile, sorted by generation number. */ +Generations findGenerations(Path profile); + Path createGeneration(Path profile, Path outPath, Path drvPath); void switchLink(Path link, Path target); From b8675aee5470c5387e4bfe4906e4ab1e94b610b2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 6 Feb 2004 16:16:55 +0000 Subject: [PATCH 0437/6440] * In `--list-generations', show what the current generation is. --- src/nix-env/main.cc | 8 +++++--- src/nix-env/profiles.cc | 33 ++++++++++++++++++++++++--------- src/nix-env/profiles.hh | 2 +- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 6aa342c1d..5ef61bb30 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -480,15 +480,17 @@ static void opListGenerations(Globals & globals, if (opArgs.size() != 0) throw UsageError(format("no arguments expected")); - Generations gens = findGenerations(globals.profile); + int curGen; + Generations gens = findGenerations(globals.profile, curGen); for (Generations::iterator i = gens.begin(); i != gens.end(); ++i) { tm t; if (!localtime_r(&i->creationTime, &t)) throw Error("cannot convert time"); - cout << format("%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02|\n") + cout << format("%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02| %||\n") % i->number % (t.tm_year + 1900) % (t.tm_mon + 1) % t.tm_mday - % t.tm_hour % t.tm_min % t.tm_sec; + % t.tm_hour % t.tm_min % t.tm_sec + % (i->number == curGen ? "(current)" : ""); } } diff --git a/src/nix-env/profiles.cc b/src/nix-env/profiles.cc index 22da6336b..a1e0c94a9 100644 --- a/src/nix-env/profiles.cc +++ b/src/nix-env/profiles.cc @@ -11,7 +11,22 @@ static bool cmpGensByNumber(const Generation & a, const Generation & b) } -Generations findGenerations(Path profile) +/* Parse a generation name of the format + `--link'. */ +static int parseName(const string & profileName, const string & name) +{ + if (string(name, 0, profileName.size() + 1) != profileName + "-") return -1; + string s = string(name, profileName.size() + 1); + int p = s.find("-link"); + if (p == string::npos) return -1; + istringstream str(string(s, 0, p)); + unsigned int n; + if (str >> n && str.eof()) return n; else return -1; +} + + + +Generations findGenerations(Path profile, int & curGen) { Generations gens; @@ -20,13 +35,8 @@ Generations findGenerations(Path profile) Strings names = readDirectory(profileDir); for (Strings::iterator i = names.begin(); i != names.end(); ++i) { - if (string(*i, 0, profileName.size() + 1) != profileName + "-") continue; - string s = string(*i, profileName.size() + 1); - int p = s.find("-link"); - if (p == string::npos) continue; - istringstream str(string(s, 0, p)); - unsigned int n; - if (str >> n && str.eof()) { + int n; + if ((n = parseName(profileName, *i)) != -1) { Generation gen; gen.path = profileDir + "/" + *i; gen.number = n; @@ -40,6 +50,10 @@ Generations findGenerations(Path profile) gens.sort(cmpGensByNumber); + curGen = pathExists(profile) + ? parseName(profileName, readLink(profile)) + : -1; + return gens; } @@ -48,7 +62,8 @@ Path createGeneration(Path profile, Path outPath, Path drvPath) { /* The new generation number should be higher than old the previous ones. */ - Generations gens = findGenerations(profile); + int dummy; + Generations gens = findGenerations(profile, dummy); unsigned int num = gens.size() > 0 ? gens.front().number : 0; /* Create the new generation. */ diff --git a/src/nix-env/profiles.hh b/src/nix-env/profiles.hh index f9480ed3f..2422c9f74 100644 --- a/src/nix-env/profiles.hh +++ b/src/nix-env/profiles.hh @@ -18,7 +18,7 @@ typedef list Generations; /* Returns the list of currently present generations for the specified profile, sorted by generation number. */ -Generations findGenerations(Path profile); +Generations findGenerations(Path profile, int & curGen); Path createGeneration(Path profile, Path outPath, Path drvPath); From 06a75a7e0c1813d90c205e654da43a32812ce5f4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 8 Feb 2004 14:07:43 +0000 Subject: [PATCH 0438/6440] * A command `--switch-generation' to switch to a specific generation of the current profile, e.g., $ nix-env --list-generations ... 39 2004-02-02 17:53:53 40 2004-02-02 17:55:18 41 2004-02-02 17:55:41 42 2004-02-02 17:55:50 (current) $ nix-env --switch-generation 39 $ ls -l /nix/var/nix/profiles/default ... default -> default-39-link * Also a command `--rollback' which is just a convenience operation to rollback to the oldest generation younger than the current one. Note that generations properly form a tree. E.g., if after switching to generation 39, we perform an installation action, a generation 43 is created which is a descendant of 39, not 42. So a rollback from 43 ought to go back to 39. This is not currently implemented; generations form a linear sequence. --- src/nix-env/help.txt | 4 +++ src/nix-env/main.cc | 59 +++++++++++++++++++++++++++++++++++++++++ src/nix-env/profiles.hh | 8 ++++++ 3 files changed, 71 insertions(+) diff --git a/src/nix-env/help.txt b/src/nix-env/help.txt index 3832f1655..9a772fc83 100644 --- a/src/nix-env/help.txt +++ b/src/nix-env/help.txt @@ -13,6 +13,10 @@ The previous operations take a list of derivation names. The special name `*' may be used to indicate all derivations. --switch-profile / -S [FILE]: switch to specified profile + --switch-generation / -G NUMBER: switch to specified generation of profile + --rollback: switch to the previous generation + --list-generations: list available generations of a profile + --import / -I FILE: set default Nix expression --version: output version information diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 5ef61bb30..69b3fdd9c 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -472,6 +472,61 @@ static void opSwitchProfile(Globals & globals, } +static const int prevGen = -2; + + +static void switchGeneration(Globals & globals, int dstGen) +{ + int curGen; + Generations gens = findGenerations(globals.profile, curGen); + + Generation dst; + for (Generations::iterator i = gens.begin(); i != gens.end(); ++i) + if ((dstGen == prevGen && i->number < curGen) || + (dstGen >= 0 && i->number == dstGen)) + dst = *i; + + if (!dst) + if (dstGen == prevGen) + throw Error(format("no generation older than the current (%1%) exists") + % curGen); + else + throw Error(format("generation %1% does not exist") % dstGen); + + switchLink(globals.profile, dst.path); +} + + +static void opSwitchGeneration(Globals & globals, + Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError(format("unknown flag `%1%'") % opFlags.front()); + if (opArgs.size() != 1) + throw UsageError(format("exactly one argument expected")); + + istringstream str(opArgs.front()); + int dstGen; + str >> dstGen; + if (!str || !str.eof()) + throw UsageError(format("expected a generation number")); + + switchGeneration(globals, dstGen); +} + + +static void opRollback(Globals & globals, + Strings opFlags, Strings opArgs) +{ + if (opFlags.size() > 0) + throw UsageError(format("unknown flag `%1%'") % opFlags.front()); + if (opArgs.size() != 0) + throw UsageError(format("no arguments expected")); + + switchGeneration(globals, prevGen); +} + + static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs) { @@ -550,6 +605,10 @@ void run(Strings args) } else if (arg == "--switch-profile" || arg == "-S") op = opSwitchProfile; + else if (arg == "--switch-generation" || arg == "-G") + op = opSwitchGeneration; + else if (arg == "--rollback") + op = opRollback; else if (arg == "--list-generations") op = opListGenerations; else if (arg[0] == '-') diff --git a/src/nix-env/profiles.hh b/src/nix-env/profiles.hh index 2422c9f74..2ce468dfa 100644 --- a/src/nix-env/profiles.hh +++ b/src/nix-env/profiles.hh @@ -11,6 +11,14 @@ struct Generation int number; Path path; time_t creationTime; + Generation() + { + number = -1; + } + operator bool() const + { + return number != -1; + } }; typedef list Generations; From 618aa69b015bd3ee1f6e19f3025e31fae0241826 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 9 Feb 2004 11:59:39 +0000 Subject: [PATCH 0439/6440] * In `--upgrade': added flags `--lt', `--leq', `--always' to specify whether we want to upgrade if the current version is less than the available version (default), when it is less or equal, or always. * Added a flag `--dry-run' to show what would happen in `--install', `--uninstall', and `--upgrade', without actually performing the operation. --- src/nix-env/help.txt | 10 +++++ src/nix-env/main.cc | 88 ++++++++++++++++++++++++++++++++------------ src/nix-env/names.cc | 6 +++ src/nix-env/names.hh | 1 + 4 files changed, 81 insertions(+), 24 deletions(-) diff --git a/src/nix-env/help.txt b/src/nix-env/help.txt index 9a772fc83..f363d3f94 100644 --- a/src/nix-env/help.txt +++ b/src/nix-env/help.txt @@ -22,6 +22,16 @@ name `*' may be used to indicate all derivations. --version: output version information --help: display help +Install / upgrade / uninstall flags: + + --dry-run: show what would be done, but don't do it + +Upgrade flags: + + --lt: upgrade unless the current version is older (default) + --leq: upgrade unless the current version is older or current + --always: upgrade regardless of current version + Query types: --name: print derivation names (default) diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 69b3fdd9c..57cde9fd0 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -16,6 +16,7 @@ struct Globals Path profile; Path nixExprPath; EvalState state; + bool dryRun; }; @@ -201,7 +202,8 @@ void createUserEnv(EvalState & state, const DrvInfos & drvs, static void installDerivations(EvalState & state, - Path nePath, DrvNames & selectors, const Path & profile) + Path nePath, DrvNames & selectors, const Path & profile, + bool dryRun) { debug(format("installing derivations from `%1%'") % nePath); @@ -239,6 +241,8 @@ static void installDerivations(EvalState & state, queryInstalled(state, installedDrvs, profile); selectedDrvs.insert(installedDrvs.begin(), installedDrvs.end()); + if (dryRun) return; + createUserEnv(state, selectedDrvs, profile); } @@ -252,12 +256,16 @@ static void opInstall(Globals & globals, DrvNames drvNames = drvNamesFromArgs(opArgs); installDerivations(globals.state, globals.nixExprPath, - drvNames, globals.profile); + drvNames, globals.profile, globals.dryRun); } +typedef enum { utLt, utLeq, utAlways } UpgradeType; + + static void upgradeDerivations(EvalState & state, - Path nePath, DrvNames & selectors, const Path & profile) + Path nePath, DrvNames & selectors, const Path & profile, + UpgradeType upgradeType, bool dryRun) { debug(format("upgrading derivations from `%1%'") % nePath); @@ -293,30 +301,50 @@ static void upgradeDerivations(EvalState & state, } } + if (!upgrade) { + newDrvs.insert(*i); + continue; + } + /* If yes, find the derivation in the input Nix expression - with the same name and the highest version number. */ - DrvInfos::iterator bestDrv = i; - DrvName bestName = drvName; - if (upgrade) { - for (DrvInfos::iterator j = availDrvs.begin(); - j != availDrvs.end(); ++j) - { - DrvName newName(j->second.name); - if (newName.name == bestName.name && - compareVersions(newName.version, bestName.version) > 0) - bestDrv = j; + with the same name and satisfying the version constraints + specified by upgradeType. If there are multiple matches, + take the one with highest version. */ + DrvInfos::iterator bestDrv = availDrvs.end(); + DrvName bestName; + for (DrvInfos::iterator j = availDrvs.begin(); + j != availDrvs.end(); ++j) + { + DrvName newName(j->second.name); + if (newName.name == drvName.name) { + int d = compareVersions(drvName.version, newName.version); + if (upgradeType == utLt && d < 0 || + upgradeType == utLeq && d <= 0 || + upgradeType == utAlways) + { + if (bestDrv == availDrvs.end() || + compareVersions( + bestName.version, newName.version) < 0) + { + bestDrv = j; + bestName = newName; + } + } } } - if (bestDrv != i) { + if (bestDrv != availDrvs.end() && + i->second.drvPath != bestDrv->second.drvPath) + { printMsg(lvlInfo, format("upgrading `%1%' to `%2%'") % i->second.name % bestDrv->second.name); - } - - newDrvs.insert(*bestDrv); + newDrvs.insert(*bestDrv); + } else newDrvs.insert(*i); } + if (dryRun) return; + createUserEnv(state, newDrvs, profile); } @@ -324,19 +352,23 @@ static void upgradeDerivations(EvalState & state, static void opUpgrade(Globals & globals, Strings opFlags, Strings opArgs) { - if (opFlags.size() > 0) - throw UsageError(format("unknown flags `%1%'") % opFlags.front()); - if (opArgs.size() < 1) throw UsageError("Nix file expected"); + UpgradeType upgradeType = utLt; + for (Strings::iterator i = opFlags.begin(); + i != opFlags.end(); ++i) + if (*i == "--lt") upgradeType = utLt; + else if (*i == "--leq") upgradeType = utLeq; + else if (*i == "--always") upgradeType = utAlways; + else throw UsageError(format("unknown flag `%1%'") % *i); DrvNames drvNames = drvNamesFromArgs(opArgs); upgradeDerivations(globals.state, globals.nixExprPath, - drvNames, globals.profile); + drvNames, globals.profile, upgradeType, globals.dryRun); } static void uninstallDerivations(EvalState & state, DrvNames & selectors, - Path & profile) + Path & profile, bool dryRun) { DrvInfos installedDrvs; queryInstalled(state, installedDrvs, profile); @@ -354,6 +386,8 @@ static void uninstallDerivations(EvalState & state, DrvNames & selectors, } } + if (dryRun) return; + createUserEnv(state, installedDrvs, profile); } @@ -366,7 +400,8 @@ static void opUninstall(Globals & globals, DrvNames drvNames = drvNamesFromArgs(opArgs); - uninstallDerivations(globals.state, drvNames, globals.profile); + uninstallDerivations(globals.state, drvNames, + globals.profile, globals.dryRun); } @@ -575,6 +610,7 @@ void run(Strings args) Globals globals; globals.nixExprPath = getDefNixExprPath(); + globals.dryRun = false; for (Strings::iterator i = args.begin(); i != args.end(); ++i) { string arg = *i; @@ -611,6 +647,10 @@ void run(Strings args) op = opRollback; else if (arg == "--list-generations") op = opListGenerations; + else if (arg == "--dry-run") { + printMsg(lvlInfo, "(dry run; not doing anything)"); + globals.dryRun = true; + } else if (arg[0] == '-') opFlags.push_back(arg); else diff --git a/src/nix-env/names.cc b/src/nix-env/names.cc index 252b07b91..c6054d6c1 100644 --- a/src/nix-env/names.cc +++ b/src/nix-env/names.cc @@ -1,6 +1,12 @@ #include "names.hh" +DrvName::DrvName() +{ + name = ""; +} + + /* Parse a derivation name. The `name' part of a derivation name is everything up to but not including the first dash *not* followed by a letter. The `version' part is the rest (excluding the separating diff --git a/src/nix-env/names.hh b/src/nix-env/names.hh index 0fc9b57d0..aeb923546 100644 --- a/src/nix-env/names.hh +++ b/src/nix-env/names.hh @@ -14,6 +14,7 @@ struct DrvName string version; unsigned int hits; + DrvName(); DrvName(const string & s); bool matches(DrvName & n); }; From 0616b7feea26786c298052b0779614b2888b482a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 10 Feb 2004 11:51:16 +0000 Subject: [PATCH 0440/6440] * Documented the most important nix-env flags. --- doc/manual/bugs.xml | 6 + doc/manual/nix-env.xml | 447 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 448 insertions(+), 5 deletions(-) diff --git a/doc/manual/bugs.xml b/doc/manual/bugs.xml index 33bd96a02..77579776b 100644 --- a/doc/manual/bugs.xml +++ b/doc/manual/bugs.xml @@ -3,6 +3,12 @@ + + + The man-pages generated from the DocBook documentation are ugly. + + + Unify the concepts of successors and substitutes into a diff --git a/doc/manual/nix-env.xml b/doc/manual/nix-env.xml index bdf35bd32..874f27081 100644 --- a/doc/manual/nix-env.xml +++ b/doc/manual/nix-env.xml @@ -15,6 +15,14 @@ path + + + + + + path + + operation options arguments @@ -72,7 +80,8 @@ / - Specifies the Nix expression used by the + Specifies the Nix expression (designated below as the + active Nix expression) used by the , , and operations to obtain derivations. The default is @@ -81,6 +90,36 @@ + + / + + + Specifies the profile to be used by those operations that + operate on a profile (designated below as the + active profile). A profile is + sequence of user environments called + generations, one of which is the + current generation. The default + profile is the target of the symbolic link + ~/.nix-profile (see below). + + + + + + + + + For the , + and + operations, this flag will + cause nix-env to print what + would be done if this flag had not + been specified, without actually doing it. + + + + @@ -114,12 +153,12 @@ ~/.nix-profile - A symbolic link to the user's current profile. The - default profile is + A symbolic link to the user's current profile. By + default, this symlink points to prefix/var/nix/profiles/default. The PATH environment variable should - include ~/.nix-profile/bin for the user - environment to be visible to the user. + include ~/.nix-profile/bin for the + user environment to be visible to the user. @@ -143,6 +182,7 @@ + drvnames @@ -150,6 +190,189 @@ Description + The install operation creates a new user environment, based on + the current generation of the active profile, to which the + derivations designated by drvnames + in the active Nix expression are added. + + + + + + Examples + + +$ nix-env --install gcc-3.3.2 (install specific version) +$ nix-env --install gcc (just pick any version) +$ nix-env -f ~/foo.nix -i '*' (install everything in foo.nix) + + + + + + + + + + + Operation <option>--upgrade</option> + + + Synopsis + + nix-env + + + + + + + + + + drvnames + + + + + Description + + + The upgrade operation creates a new user environment, based on + the current generation of the active profile, in which all + derivations designated by drvnames + for which there are newer versions in the active Nix + expression are replaced by those newer versions. Matching + derivations for which there are no newer versions are left + untouched; this is not an error. It is also not an error if + an element of drvnames matches no + installed derivations. + + + + If multiple derivations in the active Nix expression match an + installed derivation, the one with the highest version is + selected. + + + + + + Flags + + + + + + + + Only upgrade a derivation to newer versions. This is + the default. + + + + + + + + + In addition to upgrading to newer versions, also + upgrade to derivations that have the same + version. Version are not a unique identification of a + derivation, so there may be many derivations that have + the same version. This flag may be useful to force + synchronisation between the installed and + available derivations. + + + + + + + + + In addition to upgrading to newer versions, also + upgrade to derivations that have the same + or a lower version. I.e., derivations may actually be + downgraded depending on what is available in the active + Nix expression. + + + + + + + + + + Examples + + +$ nix-env --upgrade gcc +upgrading `gcc-3.3.1' to `gcc-3.4' + +$ nix-env --upgrade pan +(no upgrades available, so nothing happens) + +$ nix-env -u '*' (try to upgrade everything) +upgrading `hello-2.1.2' to `hello-2.1.3' +upgrading `mozilla-1.2' to `mozilla-1.4' + + + + + Versions + + + The upgrade operation determines whether a derivation + y is an upgrade of a derivation + x by looking at their respective + name attributes. The names (e.g., + gcc-3.3.1 are split into two parts: the + package name (gcc), and the version + (3.3.1). The version part starts after the + first dash not following by a letter. x is + considered an upgrade of y if their package + names match, and the version of y is higher + that that of x. + + + + The versions are compared by splitting them into contiguous + components of numbers and letters. E.g., + 3.3.1pre5 is split into [3, 3, 1, + "pre", 5]. These lists are then compared + lexicographically (from left to right). Corresponding + components a and b are + compared as follows. If they are both numbers, integer + comparison is used. If a is an empty + string and b is a number, + a is considered less than + b. The special string component + pre (for pre-release) + is considered to be less than other components. String + components are considered less than number components. + Otherwise, they are compared lexicographically (i.e., using + case-sensitive string comparison). + + + + This is illustrated by the following examples: + + +1.0 < 2.3 +2.1 < 2.3 +2.3 = 2.3 +2.5 > 2.3 +3.1 > 2.3 +2.3.1 > 2.3 +2.3.1 > 2.3a +2.3pre1 < 2.3 +2.3pre3 < 2.3pre12 +2.3a < 2.3c +2.3pre1 < 2.3c +2.3pre1 < 2.3q + @@ -157,4 +380,218 @@ + + + + + Operation <option>--uninstall</option> + + + Synopsis + + nix-env + + + + + drvnames + + + + + Description + + + The uninstall operation creates a new user environment, based + on the current generation of the active profile, from which the + derivations designated by drvnames + are removed. + + + + + + Examples + + +$ nix-env --uninstall gcc +$ nix-env -e '*' (remove everything) + + + + + + + + + + + Operation <option>--query</option> + + + Synopsis + + nix-env + + + + + + + + + + + + + + + + + + + + Description + + + The query operation displays information about either the + derivations that are installed in the current generation of + the active profile (), or the + derivations that are available for installation in the active + Nix expression (). + + + + The derivations are sorted by their name + attributes. + + + + + + Source selection + + + The following flags specify the set of derivations on which + the query operates. + + + + + + + + + The query operates on the derivations that are installed + in the current generation of the active profile. This + is the default + + + + + + / + + + The query operates on the derivations that are available + in the active Nix expression. + + + + + + + + + + Queries + + + The following flags specify what information to display about + the selected derivations. Only one type of query may be + specified. + + + + + + + + + Prints the name attribute of each + derivation. This is the default. + + + + + + + + + Prints the store expression in the Nix store that + described the derivation. + + + + + + / + + + Prints the status of each + derivation, followed by its name + attribute. The status consists of three characters. + The first is I or + -, indicating whether the derivation + is currently installed in the current generation of the + active profile. This is by definition the case for + , but not for + . The second is + P or -, indicating + whether the derivation is present on the system. This + indicates whether installation of an available + derivation will require the derivation to be built. The + third is S or -, + indicating whether a substitute is available for the + derivation. + + + + + + + + + + Examples + + +$ nix-env -q (show installed derivations) +MozillaFirebird-0.7 +bison-1.875c +docbook-xml-4.2 +... + +$ nix-env -qa (show available derivations) +GConf-2.4.0.1 +MPlayer-1.0pre3 +MozillaFirebird-0.7 +ORBit2-2.8.3 +... + +$ nix-env -qas (show status of available derivations) +-P- GConf-2.4.0.1 (not installed but present) +--S MPlayer-1.0pre3 (not present, but there is a substitute for fast installation) +--S MozillaFirebird-0.7 (i.e., this is not the installed Firebird, even though the version is the same!) +IP- bison-1.875c (installed and by definition present) +... + +$ nix-env -f ./foo.nix -qa (show available derivations in the Nix expression foo.nix) +foo-1.2.3 + + + + + + From 6551b36790d47477087fc3a7f7bb779f28e42d8e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 10 Feb 2004 13:42:58 +0000 Subject: [PATCH 0441/6440] * Print what generation we are switching to; honour --dry-run flag. --- src/nix-env/main.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 57cde9fd0..673d1b2be 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -528,6 +528,11 @@ static void switchGeneration(Globals & globals, int dstGen) else throw Error(format("generation %1% does not exist") % dstGen); + printMsg(lvlInfo, format("switching from generation %1% to %2%") + % curGen % dst.number); + + if (globals.dryRun) return; + switchLink(globals.profile, dst.path); } From 92e832348db13637875c4f529ed0aa83d3d34493 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 10 Feb 2004 16:14:47 +0000 Subject: [PATCH 0442/6440] * Lots of manual stuff. Reference pages for most Nix commands. * nix-pull now requires the full url to the manifest, i.e., `/MANIFEST/' is no longer automatically appended. * nix-prefetch-url works again. --- doc/manual/Makefile.am | 8 +- doc/manual/bugs.xml | 73 +++++++++- doc/manual/manual.xml | 20 +++ doc/manual/nix-collect-garbage.xml | 75 ++++++++++ doc/manual/nix-env.xml | 226 ++++++++++++++++++++++++++++- doc/manual/nix-instantiate.xml | 46 +++++- doc/manual/nix-prefetch-url.xml | 54 +++++++ doc/manual/nix-pull.xml | 43 ++++++ doc/manual/nix-push.xml | 138 ++++++++++++++++++ doc/manual/overview.xml | 5 +- doc/manual/quick-start.xml | 2 +- doc/manual/style.css | 4 +- scripts/nix-prefetch-url.in | 2 +- scripts/nix-push.in | 2 +- scripts/readmanifest.pm.in | 2 +- src/nix-hash/help.txt | 2 +- 16 files changed, 678 insertions(+), 24 deletions(-) create mode 100644 doc/manual/nix-collect-garbage.xml create mode 100644 doc/manual/nix-prefetch-url.xml create mode 100644 doc/manual/nix-pull.xml create mode 100644 doc/manual/nix-push.xml diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 8bf06cf2b..94077acc0 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -6,8 +6,12 @@ XSLTPROC = $(ENV) $(xsltproc) $(xmlflags) --catalogs \ --param section.label.includes.component.label 1 \ --param html.stylesheet \'style.css\' +man1_MANS = nix-env.1 nix-store.1 nix-instantiate.1 \ + nix-collect-garbage.1 nix-push.1 nix-pull.1 \ + nix-prefetch-url.1 + SOURCES = manual.xml introduction.xml installation.xml overview.xml \ - nix-env.xml nix-store.xml nix-instantiate.xml \ + $(man1_MANS:.1=.xml) \ troubleshooting.xml bugs.xml opt-common.xml opt-common-syn.xml \ quick-start.xml style.css images @@ -18,8 +22,6 @@ manual.is-valid: $(SOURCES) version.xml version.xml: echo -n $(VERSION) > version.xml -man1_MANS = nix-env.1 nix-store.1 nix-instantiate.1 - man $(MANS): $(SOURCES) manual.is-valid $(XSLTPROC) $(docbookxsl)/manpages/docbook.xsl manual.xml diff --git a/doc/manual/bugs.xml b/doc/manual/bugs.xml index 77579776b..eb479945a 100644 --- a/doc/manual/bugs.xml +++ b/doc/manual/bugs.xml @@ -9,6 +9,16 @@ + + + Generations properly form a tree. E.g., if after switching to + generation 39, we perform an installation action, a generation + 43 is created which is a descendant of 39, not 42. So a + rollback from 43 ought to go back to 39. This is not + currently implemented; generations form a linear sequence. + + + Unify the concepts of successors and substitutes into a @@ -43,12 +53,63 @@ + + + The current garbage collector is a hack. It should be + integrated into nix-store. It should + delete derivations in an order determined by topologically + sorting derivations under the points-to relation. This + ensures that no store paths ever exist that point to + non-existant store paths. + + + + + + There are race conditions between the garbage collector and + other Nix tools. For instance, when we run + nix-env to build and install a derivation + and run the garbage collector at the same time, the garbage + collector may kick in exactly between the build and + installation steps, i.e., before the newly built derivation + has become reachable from a root of the garbage collector. + + + + One solution would be for these programs to properly register + temporary roots for the collector. Another would be to use + stop-the-world garbage collection: if any tool is running, the + garbage collector blocks, and vice versa. These solutions do + not solve the situation where multiple tools are involved, + e.g., + + +$ nix-store -r $(nix-instantiate foo.nix) + + since even if nix-instantiate where to + register a temporary root, it would be released by the time + nix-store is started. A solution would be + to write the intermediate value to a file that is used as a + root to the collector, e.g., + + +$ nix-instantiate foo.nix > /nix/var/nix/roots/bla +$ nix-store -r $(cat /nix/var/nix/roots/bla) + + + + + + + For security, nix-push manifests should be + digitally signed, and nix-pull should + verify the signatures. The actual NAR archives in the cache + do not need to be signed, since the manifest contains + cryptographic hashes of these files (and + fetchurl.nix checks them). + + + - - diff --git a/doc/manual/manual.xml b/doc/manual/manual.xml index 686ab612f..8188a1ff9 100644 --- a/doc/manual/manual.xml +++ b/doc/manual/manual.xml @@ -12,6 +12,10 @@ + + + + @@ -52,6 +56,22 @@ nix-instantiate &nix-instantiate; + + nix-collect-garbage + &nix-collect-garbage; + + + nix-push + &nix-push; + + + nix-pull + &nix-pull; + + + nix-prefetch-url + &nix-prefetch-url; + &troubleshooting; diff --git a/doc/manual/nix-collect-garbage.xml b/doc/manual/nix-collect-garbage.xml new file mode 100644 index 000000000..8ff741e22 --- /dev/null +++ b/doc/manual/nix-collect-garbage.xml @@ -0,0 +1,75 @@ + + + nix-collect-garbage + determine the set of unreachable store paths + + + + + nix-collect-garbage + + + + + + + Description + + + The command nix-collect-garbage determines + the paths in the Nix store that are garbage, that is, not + reachable from outside of the store. These paths can be safely + deleted without affecting the integrity of the system. + + + + + + Options + + + + + + + + Causes the set of reachable paths to + be printed, rather than the unreachable paths. These are + the paths that may not be deleted. + + + + + + + + + Causes nix-collect-garbage not to + follow successor relations. By default, if a derivation + store expression is reachable, its successor (i.e., a + closure store expression) is also considered to be + reachable. This option is always safe, but garbage + collecting successors may cause undesirable rebuilds later + on. + + + + + + + + + + Examples + + + To delete all unreachable paths, do the following: + + +$ nix-collect-garbage | xargs nix-store --delete + + + + + + diff --git a/doc/manual/nix-env.xml b/doc/manual/nix-env.xml index 874f27081..70069fa1a 100644 --- a/doc/manual/nix-env.xml +++ b/doc/manual/nix-env.xml @@ -111,8 +111,9 @@ For the , - and - operations, this flag will + , , + and + operations, this flag will cause nix-env to print what would be done if this flag had not been specified, without actually doing it. @@ -594,4 +595,225 @@ foo-1.2.3 + + + + + Operation <option>--switch-profile</option> + + + Synopsis + + nix-env + + + + + path + + + + + Description + + + This operation makes path the + current profile for the user. That is, the symlink + ~/.nix-profile is made to point to + path. + + + + + + Examples + + +$ nix-env -S ~/my-profile + + + + + + + + + + + Operation <option>--list-generations</option> + + + Synopsis + + nix-env + + + + + + Description + + + This operation print a list of all the currently existing + generations for the active profile. These may be switched to + using the operation. It + also prints the creation date of the generation, and indicates + the current generation. + + + + + + Examples + + +$ nix-env --list-generations + 95 2004-02-06 11:48:24 + 96 2004-02-06 11:49:01 + 97 2004-02-06 16:22:45 + 98 2004-02-06 16:24:33 (current) + + + + + + + + + + + Operation <option>--switch-generation</option> + + + Synopsis + + nix-env + + + + + generation + + + + + Description + + + This operation makes generation number + generation the current generation + of the active profile. That is, if the + profile is the + path to the active profile, then the symlink + profile is + made to point to + profile-generation-link, + which is in turn a symlink to the actual user environment in + the Nix store. + + + + Switching will fail if the specified generation does not + exist. + + + + + + Examples + + +$ nix-env -G 42 +switching from generation 50 to 42 + + + + + + + + + + + Operation <option>--rollback</option> + + + Synopsis + + nix-env + + + + + + Description + + + This operation switches to the previous + generation of the active profile, that is, the highest + numbered generation lower than the current generation, if it + exists. It is just a convenience wrapper around + and + . + + + + + + Examples + + +$ nix-env --rollback +switching from generation 92 to 91 + +$ nix-env --rolback +error: no generation older than the current (91) exists + + + + + + + + + + + Operation <option>--import</option> + + + Synopsis + + nix-env + + + + + path + + + + + Description + + + This operation makes path the + default active Nix expression for the user. That is, the + symlink ~/.nix-userenv is made to point + to path. + + + + + + Examples + + +$ nix-env -I ~/nixpkgs-0.5/ + + + + + + + diff --git a/doc/manual/nix-instantiate.xml b/doc/manual/nix-instantiate.xml index ee073a17b..69630cb55 100644 --- a/doc/manual/nix-instantiate.xml +++ b/doc/manual/nix-instantiate.xml @@ -8,18 +8,58 @@ nix-instantiate &opt-common-syn; - files + files - + Description The command nix-instantiate generates (low-level) store expressions from (high-level) Nix expressions. + It loads and evaluates the Nix expressions in each of + files. Each top-level expression + should evaluate to a derivation, a list of derivations, or a set + of derivations. The paths of the resulting store expressions + are printed on standard output. - + + This command is generally used for testing Nix expression before + they are used with nix-env. + + + + + Options + + + + &opt-common; + + + + + + + Examples + + +$ nix-instantiate gcc.nix (instantiate) +/nix/store/468abdcb93aa22bb721142615b97698b-d-gcc-3.3.2.store + +$ nix-store -r $(nix-instantiate gcc.nix) (build) + +$ nix-store -r $(nix-instantiate gcc.nix) (print output path) +/nix/store/9afa718cddfdfe94b5b9303d0430ceb1-gcc-3.3.2 + +$ ls -l /nix/store/9afa718cddfdfe94b5b9303d0430ceb1-gcc-3.3.2 +dr-xr-xr-x 2 eelco users 360 2003-12-01 16:12 bin +dr-xr-xr-x 3 eelco users 72 2003-12-01 16:12 include +... + + + diff --git a/doc/manual/nix-prefetch-url.xml b/doc/manual/nix-prefetch-url.xml new file mode 100644 index 000000000..a6b3711e3 --- /dev/null +++ b/doc/manual/nix-prefetch-url.xml @@ -0,0 +1,54 @@ + + + nix-prefetch-url + copy a file from a URL into the store and print its MD5 hash + + + + + nix-prefetch-url + url + + + + + Description + + + The command nix-prefetch-url downloads the + file referenced by the URL url, + prints its MD5 cryptographic hash code, and copies it into the + Nix store. The file name in the store is + hash-basename, + where basename is everything + following the final slash in url. + + + + This command is just a convenience to Nix expression writers. + Often a Nix expressions fetch some source distribution from the + network using the fetchurl expression + contained in nixpkgs. However, + fetchurl requires an MD5 hash. If you don't + know the hash, you would have to download the file first, and + then fetchurl would download it again when + you build your Nix expression. Since + fetchurl uses the same name for the + downloaded file as nix-prefetch-url, the + redundant download can be avoided. + + + + + + Examples + + +$ nix-prefetch-url ftp://ftp.nluug.nl/pub/gnu/make/make-3.80.tar.bz2 +... +file has hash 0bbd1df101bc0294d440471e50feca71 +... + + + + diff --git a/doc/manual/nix-pull.xml b/doc/manual/nix-pull.xml new file mode 100644 index 000000000..2e0723c10 --- /dev/null +++ b/doc/manual/nix-pull.xml @@ -0,0 +1,43 @@ + + + nix-pull + pull substitutes from a network cache + + + + + nix-pull + url + + + + + Description + + + The command nix-pull obtains a list of + pre-built store paths from the URL + url, and for each of these store + paths, registers a substitute derivation that downloads and + unpacks it into the Nix store. This is used to speed up + installations: if you attempt to install something that has + already been built and stored into the network cache, Nix can + transparently re-use the pre-built store paths. + + + + The file at url must be compatible + with the files created by nix-push. + + + + + + Examples + + +$ nix-pull http://catamaran.labs.cs.uu.nl/dist/nix/nixpkgs-0.5pre753/MANIFEST + + + + diff --git a/doc/manual/nix-push.xml b/doc/manual/nix-push.xml new file mode 100644 index 000000000..be704d746 --- /dev/null +++ b/doc/manual/nix-push.xml @@ -0,0 +1,138 @@ + + + nix-push + push store paths onto a network cache + + + + + nix-push + archives-put-url + archives-get-url + manifest-put-url + paths + + + + + Description + + + The command nix-push builds a set of store + expressions (if necessary), and then packages and uploads all + store paths in the resulting closures to a server. A network + cache thus populated can subsequently be used to speed up + software deployment on other machines using the + nix-pull command. + + + + nix-push performs the following actions. + + + + + + The store expressions stored in + paths are realised (using + nix-store --realise). + + + + + + All paths in the closure of the store expressions stored + in paths are determined (using + nix-store --query --requisites + --include-successors). It should be noted that + since the flag is + used, if you specify a derivation store expression, you + get a combined source/binary distribution. If you only + want a binary distribution, you should specify the closure + store expression that result from realising these (see + below). + + + + + + All store paths determined in the previous step are + packaged and compressed into a bzipped + NAR archive (extension .nar.bz2). + + + + + + A manifest is created that contains + information on the store paths, their eventual URLs in the + cache, and cryptographic hashes of the contents of the NAR + archives. + + + + + + Each store path is uploaded to the remote directory + specified by archives-put-url. + HTTP PUT requests are used to do this. However, before a + file x is uploaded to + archives-put-url/x, + nix-push first determines whether this + upload is unnecessary by issuing a HTTP HEAD request on + archives-get-url/x. + This allows a cache to be shared between many partially + overlapping nix-push invocations. + (We use two URLs because the upload URL typically + refers to a CGI script, while the download URL just refers + to a file system directory on the server.) + + + + + + The manifest is uploaded using an HTTP PUT request to + manifest-put-url. The + corresponding URL to download the manifest can then be + used by nix-pull. + + + + + + + + + + Examples + + + To upload files there typically is some CGI script on the server + side. This script should be be protected with a password. The + following example uploads the store paths resulting from + building the Nix expressions in foo.nix, + passing appropriate authentication information: + + +$ nix-push \ + http://foo@bar:server.domain/cgi-bin/upload.pl/cache \ + http://server.domain/cache \ + http://foo@bar:server.domain/cgi-bin/upload.pl/MANIFEST \ + $(nix-instantiate foo.nix) + + This will push both sources and binaries (and any build-time + dependencies used in the build, such as compilers). + + + + If we just want to push binaries, not sources and build-time + dependencies, we can do: + + +$ nix-push urls $(nix-instantiate $(nix-store -r foo.nix)) + + + + + + diff --git a/doc/manual/overview.xml b/doc/manual/overview.xml index e42c811c0..d3a7e443b 100644 --- a/doc/manual/overview.xml +++ b/doc/manual/overview.xml @@ -173,9 +173,8 @@ $ nix-env -if pkgs/system/i686-linux.nix pan -$ nix-pull -http://catamaran.labs.cs.uu.nl/dist/nix/nixpkgs-version/ -obtaining list of Nix archives at http://catamaran.labs.cs.uu.nl/dist/nix/nixpkgs-version... +$ nix-pull http://catamaran.labs.cs.uu.nl/dist/nix/nixpkgs-version/MANIFEST +obtaining list of Nix archives at http://catamaran.labs.cs.uu.nl/dist/nix/nixpkgs-version/MANIFEST... ... diff --git a/doc/manual/quick-start.xml b/doc/manual/quick-start.xml index b16fe7de8..294fbcba2 100644 --- a/doc/manual/quick-start.xml +++ b/doc/manual/quick-start.xml @@ -51,7 +51,7 @@ $ tar xfj nixpkgs-version.tar.bz2 network, rather than built from source. -$ nix-pull http://.../nix/nixpkgs-version/ +$ nix-pull http://.../nix/nixpkgs-version/MANIFEST diff --git a/doc/manual/style.css b/doc/manual/style.css index 3ff9edbd4..5b8534533 100644 --- a/doc/manual/style.css +++ b/doc/manual/style.css @@ -1,5 +1,5 @@ -/* Copied from http://bakefile.sourceforge.net/ and covered by the GNU - GPL. */ +/* Copied from http://bakefile.sourceforge.net/, which appears + licensed under the GNU GPL. */ /*************************************************************************** diff --git a/scripts/nix-prefetch-url.in b/scripts/nix-prefetch-url.in index 050955682..71ba3caab 100644 --- a/scripts/nix-prefetch-url.in +++ b/scripts/nix-prefetch-url.in @@ -25,7 +25,7 @@ rename $out, $out2; # Create a Nix expression. my $nixexpr = "(import @datadir@/nix/corepkgs/fetchurl) " . - "{url = $url; md5 = \"$hash\"; system = \"@system@\"}"; + "{url = $url; md5 = \"$hash\"; system = \"@system@\";}"; print "expr: $nixexpr\n"; diff --git a/scripts/nix-push.in b/scripts/nix-push.in index 1e1d905ed..b5899e458 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -169,5 +169,5 @@ foreach my $nararchive (@nararchives) { # Upload the manifest. print STDERR "uploading manifest...\n"; system("$curl --show-error --upload-file " . - "'$manifest' '$manifest_put_url/' > /dev/null") == 0 or + "'$manifest' '$manifest_put_url' > /dev/null") == 0 or die "curl failed on $manifest: $?"; diff --git a/scripts/readmanifest.pm.in b/scripts/readmanifest.pm.in index 2c6223807..08227a5d7 100644 --- a/scripts/readmanifest.pm.in +++ b/scripts/readmanifest.pm.in @@ -11,7 +11,7 @@ sub processURL { print "obtaining list of Nix archives at $url...\n"; system("curl --fail --silent --show-error " . - "'$url/MANIFEST' > '$manifest' 2> /dev/null") == 0 + "'$url' > '$manifest' 2> /dev/null") == 0 or die "curl failed: $?"; open MANIFEST, "<$manifest"; diff --git a/src/nix-hash/help.txt b/src/nix-hash/help.txt index 84ba152c5..a38c2ab9e 100644 --- a/src/nix-hash/help.txt +++ b/src/nix-hash/help.txt @@ -1,6 +1,6 @@ nix-hash [OPTIONS...] [FILES...] -`nix-hash computes and prints cryptographic hashes for the specified +`nix-hash' computes and prints cryptographic hashes for the specified files. --flat: compute hash of regular file contents, not metadata From 00fe1a506f045e612b0564ab0b5aff3917e26bd3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 11 Feb 2004 10:25:31 +0000 Subject: [PATCH 0443/6440] * When creating a new generation, also make the normal form of the derivation (i.e., the closure store expression) a root of the garbage collector. This ensures that running `nix-collect-garbage --no-successors' is safe. --- src/nix-env/main.cc | 2 +- src/nix-env/profiles.cc | 11 +++++++---- src/nix-env/profiles.hh | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/nix-env/main.cc b/src/nix-env/main.cc index 673d1b2be..07a49a122 100644 --- a/src/nix-env/main.cc +++ b/src/nix-env/main.cc @@ -196,7 +196,7 @@ void createUserEnv(EvalState & state, const DrvInfos & drvs, /* Switch the current user environment to the output path. */ debug(format("switching to new user environment")); Path generation = createGeneration(profile, - topLevelDrv.outPath, topLevelDrv.drvPath); + topLevelDrv.outPath, topLevelDrv.drvPath, nfPath); switchLink(profile, generation); } diff --git a/src/nix-env/profiles.cc b/src/nix-env/profiles.cc index a1e0c94a9..d47c528b2 100644 --- a/src/nix-env/profiles.cc +++ b/src/nix-env/profiles.cc @@ -58,7 +58,8 @@ Generations findGenerations(Path profile, int & curGen) } -Path createGeneration(Path profile, Path outPath, Path drvPath) +Path createGeneration(Path profile, Path outPath, + Path drvPath, Path clrPath) { /* The new generation number should be higher than old the previous ones. */ @@ -67,12 +68,13 @@ Path createGeneration(Path profile, Path outPath, Path drvPath) unsigned int num = gens.size() > 0 ? gens.front().number : 0; /* Create the new generation. */ - Path generation, gcrootSrc; + Path generation, gcrootDrv, gcrootClr; while (1) { Path prefix = (format("%1%-%2%") % profile % num).str(); generation = prefix + "-link"; - gcrootSrc = prefix + "-src.gcroot"; + gcrootDrv = prefix + "-drv.gcroot"; + gcrootClr = prefix + "-clr.gcroot"; if (symlink(outPath.c_str(), generation.c_str()) == 0) break; if (errno != EEXIST) throw SysError(format("creating symlink `%1%'") % generation); @@ -80,7 +82,8 @@ Path createGeneration(Path profile, Path outPath, Path drvPath) num++; } - writeStringToFile(gcrootSrc, drvPath); + writeStringToFile(gcrootDrv, drvPath); + writeStringToFile(gcrootClr, clrPath); return generation; } diff --git a/src/nix-env/profiles.hh b/src/nix-env/profiles.hh index 2ce468dfa..423134412 100644 --- a/src/nix-env/profiles.hh +++ b/src/nix-env/profiles.hh @@ -28,7 +28,8 @@ typedef list Generations; profile, sorted by generation number. */ Generations findGenerations(Path profile, int & curGen); -Path createGeneration(Path profile, Path outPath, Path drvPath); +Path createGeneration(Path profile, Path outPath, + Path drvPath, Path clrPath); void switchLink(Path link, Path target); From 1ad9d1124727dd48beaec36b006bba856350905d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 13 Feb 2004 10:43:31 +0000 Subject: [PATCH 0444/6440] * Only include predecessors that are themselves being pushed. Otherwise the substitute mechanism can break in subtle ways. --- scripts/nix-push.in | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/nix-push.in b/scripts/nix-push.in index b5899e458..356fe1952 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -139,7 +139,12 @@ for (my $n = 0; $n < scalar @storepaths; $n++) { while () { chomp; die unless (/^\//); - print MANIFEST " SuccOf: $_\n"; + my $pred = $_; + # Only include predecessors that are themselves being + # pushed. + if (defined $storepaths{$pred}) { + print MANIFEST " SuccOf: $pred\n"; + } } close PREDS; } From 6f5a5ea5ea7fa80bc709c4a2b14ea4395ebe7469 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 13 Feb 2004 10:45:09 +0000 Subject: [PATCH 0445/6440] * Regression fix: realise substitutes and detect cycles. --- src/libstore/normalise.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libstore/normalise.cc b/src/libstore/normalise.cc index 51f90207e..5c13f04ec 100644 --- a/src/libstore/normalise.cc +++ b/src/libstore/normalise.cc @@ -287,6 +287,12 @@ void ensurePath(const Path & path, PathSet pending) { /* If the path is already valid, we're done. */ if (isValidPath(path)) return; + + if (pending.find(path) != pending.end()) + throw Error(format( + "path `%1%' already being realised (possible substitute cycle?)") + % path); + pending.insert(path); /* Otherwise, try the substitutes. */ Paths subPaths = querySubstitutes(path); @@ -296,7 +302,8 @@ void ensurePath(const Path & path, PathSet pending) { checkInterrupt(); try { - normaliseStoreExpr(*i, pending); + Path nf = normaliseStoreExpr(*i, pending); + realiseClosure(nf, pending); if (isValidPath(path)) return; throw Error(format("substitute failed to produce expected output path")); } catch (Error & e) { From 76c0e85929dc747288a8fe66a7bb77673cf2aa7e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 14 Feb 2004 21:44:18 +0000 Subject: [PATCH 0446/6440] * The environment variable NIX_ROOT can now be set to execute Nix in a chroot() environment. * A operation `--validpath' to register path validity. Useful for bootstrapping in a pure Nix environment. * Safety checks: ensure that files involved in store operations are in the store. --- src/libmain/shared.cc | 7 +++++++ src/libstore/store.cc | 38 +++++++++++++++++++++++++++++--------- src/nix-store/help.txt | 6 ++++-- src/nix-store/main.cc | 31 ++++++++++++++++++++++++++++++- 4 files changed, 70 insertions(+), 12 deletions(-) diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 32f4f8124..ec639052b 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -22,6 +22,13 @@ void sigintHandler(int signo) processor. */ static void initAndRun(int argc, char * * argv) { + char * root = getenv("NIX_ROOT"); + + if (root) { + if (chroot(root) != 0) + throw SysError(format("changing root to `%1%'") % root); + } + /* Setup Nix paths. */ nixStore = NIX_STORE_DIR; nixDataDir = NIX_DATA_DIR; diff --git a/src/libstore/store.cc b/src/libstore/store.cc index 4cd77796e..d85b0608f 100644 --- a/src/libstore/store.cc +++ b/src/libstore/store.cc @@ -157,6 +157,22 @@ void copyPath(const Path & src, const Path & dst) } +static bool isInStore(const Path & path) +{ + return path[0] == '/' + && Path(path, 0, nixStore.size()) == nixStore + && path.size() > nixStore.size() + 1 + && path[nixStore.size()] == '/'; +} + + +static void assertStorePath(const Path & path) +{ + if (!isInStore(path)) + throw Error(format("path `%1%' is not in the Nix store") % path); +} + + static bool isValidPathTxn(const Path & path, const Transaction & txn) { string s; @@ -182,6 +198,9 @@ static bool isUsablePathTxn(const Path & path, const Transaction & txn) void registerSuccessor(const Transaction & txn, const Path & srcPath, const Path & sucPath) { + assertStorePath(srcPath); + assertStorePath(sucPath); + if (!isUsablePathTxn(sucPath, txn)) throw Error( format("path `%1%' cannot be a successor, since it is not usable") % sucPath); @@ -223,6 +242,9 @@ Paths queryPredecessors(const Path & sucPath) void registerSubstitute(const Path & srcPath, const Path & subPath) { + assertStorePath(srcPath); + assertStorePath(subPath); + if (!isValidPathTxn(subPath, noTxn)) throw Error( format("path `%1%' cannot be a substitute, since it is not valid") % subPath); @@ -262,6 +284,7 @@ Paths querySubstitutes(const Path & srcPath) void registerValidPath(const Transaction & txn, const Path & _path) { Path path(canonPath(_path)); + assertStorePath(path); debug(format("registering path `%1%'") % path); nixDB.setString(txn, dbValidPaths, path, ""); } @@ -312,13 +335,6 @@ static void invalidatePath(const Path & path, Transaction & txn) } -static bool isInPrefix(const string & path, const string & _prefix) -{ - string prefix = canonPath(_prefix + "/"); - return string(path, 0, prefix.size()) == prefix; -} - - Path addToStore(const Path & _srcPath) { Path srcPath(absPath(_srcPath)); @@ -355,6 +371,8 @@ Path addToStore(const Path & _srcPath) void addTextToStore(const Path & dstPath, const string & s) { + assertStorePath(dstPath); + if (!isValidPath(dstPath)) { PathSet lockPaths; @@ -378,8 +396,7 @@ void deleteFromStore(const Path & _path) { Path path(canonPath(_path)); - if (!isInPrefix(path, nixStore)) - throw Error(format("path `%1%' is not in the store") % path); + assertStorePath(path); Transaction txn(nixDB); invalidatePath(path, txn); @@ -402,6 +419,9 @@ void verifyStore() if (!pathExists(path)) { printMsg(lvlError, format("path `%1%' disappeared") % path); invalidatePath(path, txn); + } else if (!isInStore(path)) { + printMsg(lvlError, format("path `%1%' is not in the Nix store") % path); + invalidatePath(path, txn); } else validPaths.insert(path); } diff --git a/src/nix-store/help.txt b/src/nix-store/help.txt index d7f977025..a3dcdebb6 100644 --- a/src/nix-store/help.txt +++ b/src/nix-store/help.txt @@ -9,8 +9,10 @@ Operations: --add / -A: copy a path to the Nix store --query / -q: query information - --successor: register a successor expression - --substitute: register a substitute expression + --successor: register a successor expression (dangerous!) + --substitute: register a substitute expression (dangerous!) + --validpath: register path validity (dangerous!) + --isvalid: check path validity --dump: dump a path as a Nix archive --restore: restore a path from a Nix archive diff --git a/src/nix-store/main.cc b/src/nix-store/main.cc index 078618a5d..4be8e8f44 100644 --- a/src/nix-store/main.cc +++ b/src/nix-store/main.cc @@ -185,6 +185,30 @@ static void opSubstitute(Strings opFlags, Strings opArgs) } +static void opValidPath(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + + Transaction txn; + createStoreTransaction(txn); + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + registerValidPath(txn, *i); + txn.commit(); +} + + +static void opIsValid(Strings opFlags, Strings opArgs) +{ + if (!opFlags.empty()) throw UsageError("unknown flag"); + + for (Strings::iterator i = opArgs.begin(); + i != opArgs.end(); ++i) + if (!isValidPath(*i)) + throw Error(format("path `%1%' is not valid") % *i); +} + + /* A sink that writes dump output to stdout. */ struct StdoutSink : DumpSink { @@ -273,6 +297,10 @@ void run(Strings args) op = opSuccessor; else if (arg == "--substitute") op = opSubstitute; + else if (arg == "--validpath") + op = opValidPath; + else if (arg == "--isvalid") + op = opIsValid; else if (arg == "--dump") op = opDump; else if (arg == "--restore") @@ -292,7 +320,8 @@ void run(Strings args) if (!op) throw UsageError("no operation specified"); - openDB(); + if (op != opDump && op != opRestore) /* !!! hack */ + openDB(); op(opFlags, opArgs); } From fbc48a469c80201f0d159a9b9f48a22ce5f36984 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 16 Feb 2004 09:18:35 +0000 Subject: [PATCH 0447/6440] * Inherited attributes in recursive attribute sets are in scope of the non-inherited attributes. --- src/libexpr/eval.cc | 13 +++++++------ src/libexpr/nixexpr.cc | 15 +++++++++++---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index eaa4b4ea4..335f44baa 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -100,22 +100,25 @@ static Expr substArgs(Expr body, ATermList formals, Expr arg) ATerm expandRec(ATerm e, ATermList rbnds, ATermList nrbnds) { ATMatcher m; + ATerm name; + Expr e2; /* Create the substitution list. */ ATermMap subs; for (ATermIterator i(rbnds); i; ++i) { - ATerm name; - Expr e2; if (!(atMatch(m, *i) >> "Bind" >> name >> e2)) abort(); /* can't happen */ subs.set(name, ATmake("Select(, )", e, name)); } + for (ATermIterator i(nrbnds); i; ++i) { + if (!(atMatch(m, *i) >> "Bind" >> name >> e2)) + abort(); /* can't happen */ + subs.set(name, e2); + } /* Create the non-recursive set. */ ATermMap as; for (ATermIterator i(rbnds); i; ++i) { - ATerm name; - Expr e2; if (!(atMatch(m, *i) >> "Bind" >> name >> e2)) abort(); /* can't happen */ as.set(name, substitute(subs, e2)); @@ -123,8 +126,6 @@ ATerm expandRec(ATerm e, ATermList rbnds, ATermList nrbnds) /* Copy the non-recursive bindings. !!! inefficient */ for (ATermIterator i(nrbnds); i; ++i) { - ATerm name; - Expr e2; if (!(atMatch(m, *i) >> "Bind" >> name >> e2)) abort(); /* can't happen */ as.set(name, e2); diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 7739e99a9..8fe5d379a 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -205,8 +205,11 @@ Expr substitute(const ATermMap & subs, Expr e) for (ATermIterator i(rbnds); i; ++i) if (atMatch(m, *i) >> "Bind" >> name) subs2.remove(name); - else - abort(); /* can't happen */ + else abort(); /* can't happen */ + for (ATermIterator i(nrbnds); i; ++i) + if (atMatch(m, *i) >> "Bind" >> name) + subs2.remove(name); + else abort(); /* can't happen */ return ATmake("Rec(, )", substitute(subs2, (ATerm) rbnds), substitute(subs, (ATerm) nrbnds)); @@ -264,14 +267,18 @@ void checkVarDefs(const ATermMap & defs, Expr e) } else if (atMatch(m, e) >> "Rec" >> rbnds >> nrbnds) { - checkVarDefs(defs - , (ATerm) nrbnds); + checkVarDefs(defs, (ATerm) nrbnds); ATermMap defs2(defs); for (ATermIterator i(rbnds); i; ++i) { if (!(atMatch(m, *i) >> "Bind" >> name)) abort(); /* can't happen */ defs2.set(name, (ATerm) ATempty); } + for (ATermIterator i(nrbnds); i; ++i) { + if (!(atMatch(m, *i) >> "Bind" >> name)) + abort(); /* can't happen */ + defs2.set(name, (ATerm) ATempty); + } checkVarDefs(defs2, (ATerm) rbnds); } From f34de121401bb43c6cfab892b2b254e42652ba90 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 16 Feb 2004 15:23:19 +0000 Subject: [PATCH 0448/6440] * Allow the location of the store to be specified (--with-store-dir). * Do not create stuff in localstatedir when doing `make install' (since we may not have write access). In general, installation of constant code/data should be separate from the initialisation of mutable state. --- configure.ac | 5 +++++ nix.spec.in | 1 + src/libmain/Makefile.am | 2 +- src/nix-store/Makefile.am | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index c6cb782d8..b8d51916a 100644 --- a/configure.ac +++ b/configure.ac @@ -63,6 +63,11 @@ AC_ARG_WITH(xml-flags, AC_HELP_STRING([--with-xml-flags=FLAGS], xmlflags=$withval, xmlflags=) AC_SUBST(xmlflags) +AC_ARG_WITH(store-dir, AC_HELP_STRING([--with-store-dir=PATH], + [path of the Nix store]), + storedir=$withval, storedir='${prefix}/store') +AC_SUBST(storedir) + AC_CHECK_LIB(pthread, pthread_mutex_init) AM_CONFIG_HEADER([config.h]) diff --git a/nix.spec.in b/nix.spec.in index 6fdd93e9f..e2537960e 100644 --- a/nix.spec.in +++ b/nix.spec.in @@ -24,6 +24,7 @@ make %install rm -rf $RPM_BUILD_ROOT make DESTDIR=$RPM_BUILD_ROOT install +(cd src/nix-store && make DESTDIR=$RPM_BUILD_ROOT init-state-local) strip $RPM_BUILD_ROOT/%{_prefix}/bin/* || true %clean diff --git a/src/libmain/Makefile.am b/src/libmain/Makefile.am index 6d70b0406..72126f113 100644 --- a/src/libmain/Makefile.am +++ b/src/libmain/Makefile.am @@ -3,7 +3,7 @@ noinst_LIBRARIES = libmain.a libmain_a_SOURCES = shared.cc shared.hh AM_CXXFLAGS = \ - -DNIX_STORE_DIR=\"$(prefix)/store\" \ + -DNIX_STORE_DIR=\"$(storedir)\" \ -DNIX_DATA_DIR=\"$(datadir)\" \ -DNIX_STATE_DIR=\"$(localstatedir)/nix\" \ -DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \ diff --git a/src/nix-store/Makefile.am b/src/nix-store/Makefile.am index 3a152e3e4..76d3a6c6b 100644 --- a/src/nix-store/Makefile.am +++ b/src/nix-store/Makefile.am @@ -12,7 +12,7 @@ main.o: help.txt.hh AM_CXXFLAGS = \ -I.. -I../../externals/inst/include -I../libutil -I../libstore -I../libmain -install-data-local: +init-state-local: $(INSTALL) -d $(DESTDIR)$(localstatedir)/nix $(INSTALL) -d $(DESTDIR)$(localstatedir)/nix/db $(INSTALL) -d $(DESTDIR)$(localstatedir)/log/nix From 0dfdafdf6de4f741ff60637843f0e7900384cd9f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 16 Feb 2004 16:37:16 +0000 Subject: [PATCH 0449/6440] * Allow linking against an external Berkeley DB / ATerm library. --- Makefile.am | 1 - configure.ac | 28 ++++++++++++++++++++++++++++ externals/Makefile.am | 8 ++++++-- src/libexpr/Makefile.am | 4 ++-- src/libmain/Makefile.am | 2 +- src/libstore/Makefile.am | 2 +- src/libutil/Makefile.am | 4 ++-- src/nix-env/Makefile.am | 5 ++--- src/nix-hash/Makefile.am | 2 +- src/nix-instantiate/Makefile.am | 5 ++--- src/nix-store/Makefile.am | 4 ++-- 11 files changed, 47 insertions(+), 18 deletions(-) diff --git a/Makefile.am b/Makefile.am index cf6a13ad6..cb5040135 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,4 @@ SUBDIRS = externals src scripts corepkgs doc - EXTRA_DIST = substitute.mk nix.spec nix.spec.in include ./substitute.mk diff --git a/configure.ac b/configure.ac index b8d51916a..689c2617e 100644 --- a/configure.ac +++ b/configure.ac @@ -68,6 +68,34 @@ AC_ARG_WITH(store-dir, AC_HELP_STRING([--with-store-dir=PATH], storedir=$withval, storedir='${prefix}/store') AC_SUBST(storedir) +AC_ARG_WITH(bdb, AC_HELP_STRING([--with-bdb=PATH], + [prefix of Berkeley DB]), + 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 -Wl,-rpath,$bdb/lib -ldb_cxx" + bdb_include="-I$bdb/include" +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' +else + aterm_lib="-L$aterm/lib -Wl,-rpath,$aterm/lib -lATerm" + aterm_include="-I$aterm/include" +fi +AC_SUBST(aterm_lib) +AC_SUBST(aterm_include) + AC_CHECK_LIB(pthread, pthread_mutex_init) AM_CONFIG_HEADER([config.h]) diff --git a/externals/Makefile.am b/externals/Makefile.am index 5f48a697d..4819b95fe 100644 --- a/externals/Makefile.am +++ b/externals/Makefile.am @@ -16,16 +16,20 @@ 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 \ + ../dist/configure --prefix=$$pfx/inst-bdb \ --enable-cxx --disable-shared --disable-cryptography \ --disable-replication --disable-verify && \ make && \ make install) touch build-db +endif # CWI ATerm @@ -49,7 +53,7 @@ have-aterm: build-aterm: have-aterm (pfx=`pwd` && \ cd $(ATERM) && \ - CC="$(CC)" ./configure --prefix=$$pfx/inst \ + CC="$(CC)" ./configure --prefix=$$pfx/inst-aterm \ --with-cflags="-DNDEBUG -DXGC_VERBOSE -DXHASHPEM -DWITH_STATS $(CFLAGS)" && \ make && \ make install) diff --git a/src/libexpr/Makefile.am b/src/libexpr/Makefile.am index 02269bcb8..f3ffb6a03 100644 --- a/src/libexpr/Makefile.am +++ b/src/libexpr/Makefile.am @@ -7,9 +7,9 @@ libexpr_a_SOURCES = nixexpr.cc nixexpr.hh parser.cc parser.hh \ EXTRA_DIST = lexer.l parser.y AM_CXXFLAGS = \ - -I.. -I../../externals/inst/include -I../libutil -I../libstore + -I.. ${bdb_include} ${aterm_include} -I../libutil -I../libstore AM_CFLAGS = \ - -I../../externals/inst/include + ${aterm_include} # Parser generation. diff --git a/src/libmain/Makefile.am b/src/libmain/Makefile.am index 72126f113..3c28441ca 100644 --- a/src/libmain/Makefile.am +++ b/src/libmain/Makefile.am @@ -8,4 +8,4 @@ AM_CXXFLAGS = \ -DNIX_STATE_DIR=\"$(localstatedir)/nix\" \ -DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \ -DNIX_VERSION=\"$(VERSION)\" \ - -I.. -I../../externals/inst/include -I../libutil -I../libstore + -I.. ${aterm_include} -I../libutil -I../libstore diff --git a/src/libstore/Makefile.am b/src/libstore/Makefile.am index 3e2777f6a..0a7b148fe 100644 --- a/src/libstore/Makefile.am +++ b/src/libstore/Makefile.am @@ -7,4 +7,4 @@ libstore_a_SOURCES = \ references.cc references.hh pathlocks.cc pathlocks.hh AM_CXXFLAGS = -Wall \ - -I.. -I../../externals/inst/include -I../libutil + -I.. ${bdb_include} ${aterm_include} -I../libutil diff --git a/src/libutil/Makefile.am b/src/libutil/Makefile.am index 92c4472f8..d5d4fcfad 100644 --- a/src/libutil/Makefile.am +++ b/src/libutil/Makefile.am @@ -3,11 +3,11 @@ noinst_LIBRARIES = libutil.a libutil_a_SOURCES = util.cc util.hh hash.cc hash.hh \ archive.cc archive.hh md5.c md5.h aterm.cc aterm.hh -AM_CXXFLAGS = -DSYSTEM=\"@system@\" -Wall -I.. -I../../externals/inst/include +AM_CXXFLAGS = -DSYSTEM=\"@system@\" -Wall -I.. ${aterm_include} check_PROGRAMS = test-aterm test_aterm_SOURCES = test-aterm.cc test_aterm_LDADD = ./libutil.a ../boost/format/libformat.a \ - -L../../externals/inst/lib -ldb_cxx -lATerm + ${aterm_lib} diff --git a/src/nix-env/Makefile.am b/src/nix-env/Makefile.am index e4b03943c..3943a9d9a 100644 --- a/src/nix-env/Makefile.am +++ b/src/nix-env/Makefile.am @@ -4,8 +4,7 @@ nix_env_SOURCES = main.cc names.cc names.hh \ profiles.cc profiles.hh help.txt nix_env_LDADD = ../libmain/libmain.a ../libexpr/libexpr.a \ ../libstore/libstore.a ../libutil/libutil.a \ - ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ - -lATerm + ../boost/format/libformat.a ${bdb_lib} ${aterm_lib} main.o: help.txt.hh @@ -13,7 +12,7 @@ main.o: help.txt.hh ../bin2c/bin2c helpText < $< > $@ || (rm $@ && exit 1) AM_CXXFLAGS = \ - -I.. -I../../externals/inst/include -I../libutil -I../libstore \ + -I.. ${bdb_include} ${aterm_include} -I../libutil -I../libstore \ -I../libexpr -I../libmain install-data-local: diff --git a/src/nix-hash/Makefile.am b/src/nix-hash/Makefile.am index 00bf6afd6..7906285fe 100644 --- a/src/nix-hash/Makefile.am +++ b/src/nix-hash/Makefile.am @@ -2,7 +2,7 @@ bin_PROGRAMS = nix-hash nix_hash_SOURCES = nix-hash.cc help.txt nix_hash_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ - ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx -lATerm + ../boost/format/libformat.a ${bdb_lib} ${aterm_lib} nix-hash.o: help.txt.hh diff --git a/src/nix-instantiate/Makefile.am b/src/nix-instantiate/Makefile.am index 0726d1296..60ee09b94 100644 --- a/src/nix-instantiate/Makefile.am +++ b/src/nix-instantiate/Makefile.am @@ -3,8 +3,7 @@ bin_PROGRAMS = nix-instantiate nix_instantiate_SOURCES = main.cc help.txt nix_instantiate_LDADD = ../libmain/libmain.a ../libexpr/libexpr.a \ ../libstore/libstore.a ../libutil/libutil.a \ - ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \ - -lATerm + ../boost/format/libformat.a ${bdb_lib} ${aterm_lib} main.o: help.txt.hh @@ -12,5 +11,5 @@ main.o: help.txt.hh ../bin2c/bin2c helpText < $< > $@ || (rm $@ && exit 1) AM_CXXFLAGS = \ - -I.. -I../../externals/inst/include -I../libutil -I../libstore \ + -I.. ${bdb_include} ${aterm_include} -I../libutil -I../libstore \ -I../libexpr -I../libmain diff --git a/src/nix-store/Makefile.am b/src/nix-store/Makefile.am index 76d3a6c6b..588d645eb 100644 --- a/src/nix-store/Makefile.am +++ b/src/nix-store/Makefile.am @@ -2,7 +2,7 @@ bin_PROGRAMS = nix-store nix_store_SOURCES = main.cc dotgraph.cc dotgraph.hh help.txt nix_store_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \ - ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx -lATerm + ../boost/format/libformat.a ${bdb_lib} ${aterm_lib} main.o: help.txt.hh @@ -10,7 +10,7 @@ main.o: help.txt.hh ../bin2c/bin2c helpText < $< > $@ || (rm $@ && exit 1) AM_CXXFLAGS = \ - -I.. -I../../externals/inst/include -I../libutil -I../libstore -I../libmain + -I.. ${bdb_include} $(aterm_include) -I../libutil -I../libstore -I../libmain init-state-local: $(INSTALL) -d $(DESTDIR)$(localstatedir)/nix From 86b7efbdbe3c2da0f788df3ee7839cf3b88f7120 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 16 Feb 2004 16:48:06 +0000 Subject: [PATCH 0450/6440] * Don't build ATerm library if we don't need to. --- externals/Makefile.am | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/externals/Makefile.am b/externals/Makefile.am index 4819b95fe..6da79ab8a 100644 --- a/externals/Makefile.am +++ b/externals/Makefile.am @@ -50,6 +50,9 @@ have-aterm: $(MAKE) $(ATERM) touch have-aterm +if HAVE_ATERM +build-aterm: +else build-aterm: have-aterm (pfx=`pwd` && \ cd $(ATERM) && \ @@ -58,6 +61,7 @@ build-aterm: have-aterm make && \ make install) touch build-aterm +endif all: build-db build-aterm From dbf547645d26baee030d7db0535e0c0be72c13cc Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 19 Feb 2004 13:11:12 +0000 Subject: [PATCH 0451/6440] * Resolve an ambiguity between ifs and attribute selection, e.g., `if b then x else y.z'. --- src/libexpr/parser.y | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 257c0cd38..bfd539a3f 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -32,7 +32,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, void * data, char * s) ATermList ts; } -%type start expr expr_function expr_assert expr_op +%type start expr expr_function expr_assert expr_if expr_op %type expr_app expr_select expr_simple bind inheritsrc formal %type binds ids expr_list formals %token ID INT STR PATH URI @@ -60,6 +60,12 @@ expr_function expr_assert : ASSERT expr ';' expr_assert { $$ = ATmake("Assert(, )", $2, $4); } + | expr_if + ; + +expr_if + : IF expr THEN expr ELSE expr + { $$ = ATmake("If(, , )", $2, $4, $6); } | expr_op ; @@ -102,8 +108,6 @@ expr_simple | '{' binds '}' { $$ = fixAttrs(0, $2); } | '[' expr_list ']' { $$ = ATmake("List()", $2); } - | IF expr THEN expr ELSE expr - { $$ = ATmake("If(, , )", $2, $4, $6); } ; binds From 7f0ed370da62b867d90ba5346f4b9f217fbbe10f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 20 Feb 2004 11:32:30 +0000 Subject: [PATCH 0452/6440] * Use $(storedir) instead of $(prefix)/store. --- scripts/nix-collect-garbage.in | 2 +- scripts/nix-prefetch-url.in | 4 ++-- substitute.mk | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/nix-collect-garbage.in b/scripts/nix-collect-garbage.in index 1effc14d6..a3ee7bd5e 100755 --- a/scripts/nix-collect-garbage.in +++ b/scripts/nix-collect-garbage.in @@ -4,7 +4,7 @@ use strict; use IPC::Open2; my $linkdir = "@localstatedir@/nix/profiles"; -my $storedir = "@prefix@/store"; +my $storedir = "@storedir@"; my %alive; diff --git a/scripts/nix-prefetch-url.in b/scripts/nix-prefetch-url.in index 71ba3caab..6cc3b7a26 100644 --- a/scripts/nix-prefetch-url.in +++ b/scripts/nix-prefetch-url.in @@ -8,7 +8,7 @@ defined $url or die; print "fetching $url...\n"; -my $out = "@prefix@/store/nix-prefetch-url-$$"; +my $out = "@storedir@/nix-prefetch-url-$$"; system "@wget@ --passive-ftp '$url' -O '$out'"; $? == 0 or die "unable to fetch $url"; @@ -19,7 +19,7 @@ chomp $hash; print "file has hash $hash\n"; -my $out2 = "@prefix@/store/nix-prefetch-url-$hash"; +my $out2 = "@storedir@/nix-prefetch-url-$hash"; rename $out, $out2; # Create a Nix expression. diff --git a/substitute.mk b/substitute.mk index bbdb9b617..6882393bc 100644 --- a/substitute.mk +++ b/substitute.mk @@ -6,6 +6,7 @@ -e "s^@localstatedir\@^$(localstatedir)^g" \ -e "s^@datadir\@^$(datadir)^g" \ -e "s^@libexecdir\@^$(libexecdir)^g" \ + -e "s^@storedir\@^$(storedir)^g" \ -e "s^@system\@^$(system)^g" \ -e "s^@wget\@^$(wget)^g" \ -e "s^@version\@^$(VERSION)^g" \ From a5619f1dffbf3600dd16b51e84ae3c999edc439c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 12 Mar 2004 10:45:08 +0000 Subject: [PATCH 0453/6440] * Set the NIX_STORE and NIX_BUILD_TOP environment variables in builders to point to the store and the temporary build directory, respectively. Useful for purity checking. * Also set TEMPDIR, TMPDIR, TEMP, and TEMP to NIX_BUILD_TOP to make sure that tools in the builder store temporary files in the right location. --- src/libstore/exec.cc | 14 ++++++++++++-- src/libstore/exec.hh | 2 +- src/libstore/normalise.cc | 6 ++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/libstore/exec.cc b/src/libstore/exec.cc index 01577143d..31a2bae81 100644 --- a/src/libstore/exec.cc +++ b/src/libstore/exec.cc @@ -17,7 +17,7 @@ static string pathNullDevice = "/dev/null"; /* Run a program. */ void runProgram(const string & program, - const Strings & args, const Environment & env, + const Strings & args, Environment env, const string & logFileName) { /* Create a log file. */ @@ -32,10 +32,20 @@ void runProgram(const string & program, /* Create a temporary directory where the build will take place. */ - string tmpDir = createTempDir(); + Path tmpDir = createTempDir(); AutoDelete delTmpDir(tmpDir); + /* For convenience, set an environment pointing to the top build + directory. */ + env["NIX_BUILD_TOP"] = tmpDir; + + /* Also set TMPDIR and variants to point to this directory. */ + env["TMPDIR"] = tmpDir; + env["TEMPDIR"] = tmpDir; + env["TMP"] = tmpDir; + env["TEMP"] = tmpDir; + /* Fork a child to build the package. */ pid_t pid; switch (pid = fork()) { diff --git a/src/libstore/exec.hh b/src/libstore/exec.hh index fc5bd6ac8..892815c5c 100644 --- a/src/libstore/exec.hh +++ b/src/libstore/exec.hh @@ -15,7 +15,7 @@ typedef map Environment; /* Run a program. */ void runProgram(const string & program, - const Strings & args, const Environment & env, + const Strings & args, Environment env, const string & logFileName); diff --git a/src/libstore/normalise.cc b/src/libstore/normalise.cc index 5c13f04ec..e287914a1 100644 --- a/src/libstore/normalise.cc +++ b/src/libstore/normalise.cc @@ -124,6 +124,12 @@ Path normaliseStoreExpr(const Path & _nePath, PathSet pending) non-existing path. */ env["HOME"] = "/homeless-shelter"; + /* Tell the builder where the Nix store is. Usually they + shouldn't care, but this is useful for purity checking (e.g., + the compiler or linker might only want to accept paths to files + in the store or in the build directory). */ + env["NIX_STORE"] = nixStore; + /* Build the environment. */ for (StringPairs::iterator i = ne.derivation.env.begin(); i != ne.derivation.env.end(); i++) From beda10f5a2a69ac32ad91c8a80477fde19be6a83 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 15 Mar 2004 15:23:53 +0000 Subject: [PATCH 0454/6440] * Make perl a dependency of Nix. --- configure.ac | 5 +++++ corepkgs/buildenv/Makefile.am | 4 +++- corepkgs/buildenv/{builder.pl => builder.pl.in} | 2 +- scripts/nix-collect-garbage.in | 2 +- scripts/nix-install-package.in | 2 +- scripts/nix-prefetch-url.in | 2 +- scripts/nix-pull.in | 2 +- scripts/nix-push.in | 2 +- substitute.mk | 1 + 9 files changed, 15 insertions(+), 7 deletions(-) rename corepkgs/buildenv/{builder.pl => builder.pl.in} (98%) diff --git a/configure.ac b/configure.ac index 689c2617e..093812e7a 100644 --- a/configure.ac +++ b/configure.ac @@ -47,6 +47,11 @@ AC_PATH_PROG(xmllint, xmllint) AC_PATH_PROG(xsltproc, xsltproc) AC_PATH_PROG(flex, flex, false) AC_PATH_PROG(bison, bison, false) +AC_PATH_PROG(perl, perl) +if test -z "$perl"; then + echo "Perl is required for Nix." + exit 1 +fi AC_ARG_WITH(docbook-catalog, AC_HELP_STRING([--with-docbook-catalog=PATH], [path of the DocBook XML DTD]), diff --git a/corepkgs/buildenv/Makefile.am b/corepkgs/buildenv/Makefile.am index f6a14600f..7a5df476d 100644 --- a/corepkgs/buildenv/Makefile.am +++ b/corepkgs/buildenv/Makefile.am @@ -1,3 +1,5 @@ +all-local: builder.pl + install-exec-local: $(INSTALL) -d $(DESTDIR)$(datadir)/nix/corepkgs $(INSTALL) -d $(DESTDIR)$(datadir)/nix/corepkgs/buildenv @@ -6,4 +8,4 @@ install-exec-local: include ../../substitute.mk -EXTRA_DIST = default.nix builder.pl +EXTRA_DIST = default.nix builder.pl.in diff --git a/corepkgs/buildenv/builder.pl b/corepkgs/buildenv/builder.pl.in similarity index 98% rename from corepkgs/buildenv/builder.pl rename to corepkgs/buildenv/builder.pl.in index 3bbb178c8..d9ff73e17 100755 --- a/corepkgs/buildenv/builder.pl +++ b/corepkgs/buildenv/builder.pl.in @@ -1,4 +1,4 @@ -#! /usr/bin/perl -w +#! @perl@ -w use strict; use Cwd; diff --git a/scripts/nix-collect-garbage.in b/scripts/nix-collect-garbage.in index a3ee7bd5e..c701ad482 100755 --- a/scripts/nix-collect-garbage.in +++ b/scripts/nix-collect-garbage.in @@ -1,4 +1,4 @@ -#! /usr/bin/perl -w +#! @perl@ -w use strict; use IPC::Open2; diff --git a/scripts/nix-install-package.in b/scripts/nix-install-package.in index c71a6ca5f..73afead7d 100644 --- a/scripts/nix-install-package.in +++ b/scripts/nix-install-package.in @@ -1,4 +1,4 @@ -#! /usr/bin/perl -w +#! @perl@ -w use strict; use POSIX qw(tmpnam); diff --git a/scripts/nix-prefetch-url.in b/scripts/nix-prefetch-url.in index 6cc3b7a26..d921e922b 100644 --- a/scripts/nix-prefetch-url.in +++ b/scripts/nix-prefetch-url.in @@ -1,4 +1,4 @@ -#! /usr/bin/perl -w +#! @perl@ -w use strict; use IPC::Open2; diff --git a/scripts/nix-pull.in b/scripts/nix-pull.in index 2b2d4e857..acb4c0732 100644 --- a/scripts/nix-pull.in +++ b/scripts/nix-pull.in @@ -1,4 +1,4 @@ -#! /usr/bin/perl -w -I@libexecdir@/nix +#! @perl@ -w -I@libexecdir@/nix use strict; use IPC::Open2; diff --git a/scripts/nix-push.in b/scripts/nix-push.in index 356fe1952..167d787b7 100644 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -1,4 +1,4 @@ -#! /usr/bin/perl -w +#! @perl@ -w use strict; use POSIX qw(tmpnam); diff --git a/substitute.mk b/substitute.mk index 6882393bc..73a93963f 100644 --- a/substitute.mk +++ b/substitute.mk @@ -9,6 +9,7 @@ -e "s^@storedir\@^$(storedir)^g" \ -e "s^@system\@^$(system)^g" \ -e "s^@wget\@^$(wget)^g" \ + -e "s^@perl\@^$(perl)^g" \ -e "s^@version\@^$(VERSION)^g" \ < $< > $@ || rm $@ if test -x $<; then chmod +x $@; fi From 9d2669d218d03d64c69a702a96fc87ee1fd3a9d0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 15 Mar 2004 21:51:14 +0000 Subject: [PATCH 0455/6440] * Added a utility that can be used to produce nice HTML pages from Nix build logs. The program `log2xml' converts a Nix build log (read from standard input) into XML file that can then be converted to XHTML by the `log2html.xsl' stylesheet. The CSS stylesheet `logfile.css' is necessary to make it look good. This is primarily useful if the log file has a *tree structure*, i.e., that sub-tasks such as the various phases of a build (unpack, configure, make, etc.) or recursive invocations of Make are represented as such. While a log file is in principle an unstructured plain text file, builders can communicate this tree structure to `log2xml' by using escape sequences: - "\e[p" starts a new nesting level; the first line following the escape code is the header; - "\e[q" ends the current nesting level. The generic builder in nixpkgs (not yet committed) uses this. It shouldn't be to hard to patch GNU Make to speak this protocol. Further improvements to the generated HTML pages are to allow collapsing/expanding of subtrees, and to abbreviate store paths (but to show the full path by hovering the mouse over it). --- configure.ac | 1 + src/Makefile.am | 2 +- src/log2xml/Makefile.am | 9 ++++ src/log2xml/log2html.xsl | 61 +++++++++++++++++++++++ src/log2xml/log2xml.cc | 102 +++++++++++++++++++++++++++++++++++++++ src/log2xml/logfile.css | 66 +++++++++++++++++++++++++ 6 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 src/log2xml/Makefile.am create mode 100644 src/log2xml/log2html.xsl create mode 100644 src/log2xml/log2xml.cc create mode 100644 src/log2xml/logfile.css diff --git a/configure.ac b/configure.ac index 093812e7a..f07dd6dd7 100644 --- a/configure.ac +++ b/configure.ac @@ -118,6 +118,7 @@ AC_CONFIG_FILES([Makefile src/libexpr/Makefile src/nix-instantiate/Makefile src/nix-env/Makefile + src/log2xml/Makefile scripts/Makefile corepkgs/Makefile corepkgs/fetchurl/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 0f1273426..29bd535f6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,2 +1,2 @@ SUBDIRS = bin2c boost libutil libstore libmain nix-store nix-hash \ - libexpr nix-instantiate nix-env + libexpr nix-instantiate nix-env log2xml diff --git a/src/log2xml/Makefile.am b/src/log2xml/Makefile.am new file mode 100644 index 000000000..b439f5e10 --- /dev/null +++ b/src/log2xml/Makefile.am @@ -0,0 +1,9 @@ +bin_PROGRAMS = log2xml + +log2xml_SOURCES = log2xml.cc + +%.xml: %.log log2xml + ./log2xml < $< > $@ + +%.html: %.xml log2html.xsl + xsltproc log2html.xsl $< > $@ \ No newline at end of file diff --git a/src/log2xml/log2html.xsl b/src/log2xml/log2html.xsl new file mode 100644 index 000000000..96691a6ae --- /dev/null +++ b/src/log2xml/log2html.xsl @@ -0,0 +1,61 @@ + + + + + + + + + Log File + + + + + + + + +

+ + + + + + + + + \ No newline at end of file diff --git a/src/log2xml/log2xml.cc b/src/log2xml/log2xml.cc new file mode 100644 index 000000000..711fc82b8 --- /dev/null +++ b/src/log2xml/log2xml.cc @@ -0,0 +1,102 @@ +#include +#include +#include + +using namespace std; + + +struct Decoder +{ + enum { stTop, stEscape, stCSI } state; + string line; + bool inHeader; + int level; + + Decoder() + { + state = stTop; + line = ""; + inHeader = false; + level = 0; + } + + void pushChar(char c); + + void finishLine(); +}; + + +void Decoder::pushChar(char c) +{ + switch (state) { + + case stTop: + if (c == '\e') { + state = stEscape; + } else if (c == '\n') { + finishLine(); + } else if (c == '<') + line += "<"; + else if (c == '&') + line += "&"; + else + line += c; + break; + + case stEscape: + if (c == '[') + state = stCSI; + else + state = stTop; /* !!! wrong */ + break; + + case stCSI: + if (c >= 0x40 && c != 0x7e) { + state = stTop; + switch (c) { + case 'p': + if (line.size()) finishLine(); + level++; + inHeader = true; + cout << "" << endl; + break; + case 'q': + if (line.size()) finishLine(); + if (level > 0) { + level--; + cout << "" << endl; + } else + cerr << "not enough nesting levels" << endl; + break; + } + } + break; + + } +} + + +void Decoder::finishLine() +{ + string tag = inHeader ? "head" : "line"; + cout << "<" << tag << ">"; + cout << line; + cout << "" << endl; + line = ""; + inHeader = false; +} + + +int main(int argc, char * * argv) +{ + Decoder dec; + int c; + + cout << "" << endl; + + while ((c = getchar()) != EOF) { + dec.pushChar(c); + } + + cout << "" << endl; +} diff --git a/src/log2xml/logfile.css b/src/log2xml/logfile.css new file mode 100644 index 000000000..e240eb381 --- /dev/null +++ b/src/log2xml/logfile.css @@ -0,0 +1,66 @@ +body +{ + font-family: sans-serif; + background: white; +} + + +blockquote.body +{ + padding: 6px 0px; + margin: 0px 1px; +} + + +table.x, tr.x +{ + border-collapse: separate; + border-spacing: 0pt; + margin: 0em 0em 0em 0em; + padding: 0em 0em 0em 0em; +} + + +tr.x > td.dummy +{ + vertical-align: top; + margin: 0em 0em 0em 0em; + padding: 0.5em 0em 0em 0em; + border-left: 3px solid #6185a0; +} + + +tr.x > td.dummy > div.dummy +{ + width: 1.5em; + height: 3px; + margin: 0em 0em 0em 0em; + border-top: 3px solid #6185a0; +} + + +table.y, tr.y +{ + border-collapse: separate; + border-spacing: 0pt; + margin: 0em 0em 0em 0em; + padding: 0em 0em 0em 0em; +} + + +tr.y > td.dummy +{ + vertical-align: top; + margin: 0em 0em 0em 0em; + padding: 0em 0em 0em 0em; +} + + +tr.y > td.dummy > div.dummy +{ + width: 1.5em; + height: 6px; + margin: 0em 0em 0em 0em; + border-left: 3px solid #6185a0; + border-bottom: 3px solid #6185a0; +} From b5539e7a30da963af3e5967e2af2524a5e99efbb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 16 Mar 2004 12:47:09 +0000 Subject: [PATCH 0456/6440] * Store paths are now abbreviated in the generated HTML file. Hovering over the abbreviated path will reveal the full path. This probably only works in Mozilla. --- src/log2xml/log2html.xsl | 17 ++++++++++------ src/log2xml/log2xml.cc | 43 +++++++++++++++++++++++++++++++++------- src/log2xml/logfile.css | 42 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 13 deletions(-) diff --git a/src/log2xml/log2html.xsl b/src/log2xml/log2html.xsl index 96691a6ae..be4af66ce 100644 --- a/src/log2xml/log2html.xsl +++ b/src/log2xml/log2html.xsl @@ -17,9 +17,7 @@
- - - +
@@ -52,10 +50,17 @@
- - - + + + + + + + + (...) + + \ No newline at end of file diff --git a/src/log2xml/log2xml.cc b/src/log2xml/log2xml.cc index 711fc82b8..f3e976fd9 100644 --- a/src/log2xml/log2xml.cc +++ b/src/log2xml/log2xml.cc @@ -35,12 +35,7 @@ void Decoder::pushChar(char c) state = stEscape; } else if (c == '\n') { finishLine(); - } else if (c == '<') - line += "<"; - else if (c == '&') - line += "&"; - else - line += c; + } else line += c; break; case stEscape: @@ -78,9 +73,43 @@ void Decoder::pushChar(char c) void Decoder::finishLine() { + string storeDir = "/nix/store/"; + int sz = storeDir.size(); string tag = inHeader ? "head" : "line"; cout << "<" << tag << ">"; - cout << line; + + for (int i = 0; i < line.size(); i++) { + + if (line[i] == '<') cout << "<"; + else if (line[i] == '&') cout << "&"; + else if (i + sz + 33 < line.size() && + string(line, i, sz) == storeDir && + line[i + sz + 32] == '-') + { + int j = i + sz + 32; + /* skip name */ + while (!strchr("/\n\r\t ()[]:;?<>", line[j])) j++; + int k = j; + while (!strchr("\n\r\t ()[]:;?<>", line[k])) k++; + // !!! escaping + cout << "" + << "" + << string(line, i, sz) + << "" + << "" + << string(line, i + sz, 32) + << "" + << "" + << string(line, i + sz + 32, j - (i + sz + 32)) + << "" + << "" + << string(line, j, k - j) + << "" + << ""; + i = k - 1; + } else cout << line[i]; + } + cout << "" << endl; line = ""; inHeader = false; diff --git a/src/log2xml/logfile.css b/src/log2xml/logfile.css index e240eb381..342cf2583 100644 --- a/src/log2xml/logfile.css +++ b/src/log2xml/logfile.css @@ -64,3 +64,45 @@ tr.y > td.dummy > div.dummy border-left: 3px solid #6185a0; border-bottom: 3px solid #6185a0; } + + +em.storeref +{ + color: #500000; +} + + +em.storeref:hover +{ + background-color: #eeeeee; +} + + +*.popup { + display: none; +/* background: url('http://losser.st-lab.cs.uu.nl/~mbravenb/menuback.png') repeat; */ + background: #ffffcd; + border: solid #555555 1px; + position: absolute; + top: 1.5em; + left: 0.5em; + margin: 0; + padding: 0; + z-index: 100; +} + + +em.storeref { + position: static; +} + + +span.z { + position: absolute; + width: 100%; +} + + +em.storeref:hover > span.z > *.popup { + display: block; +} From 8330c8202aa77ab65ce6344a45c5ecce287fd988 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 17 Mar 2004 16:52:48 +0000 Subject: [PATCH 0457/6440] * A simpler way of implementing the store reference popups, thanks to Martin and CSS guru Martijn Vermaat. --- src/log2xml/log2html.xsl | 8 ++++---- src/log2xml/logfile.css | 23 ++++++++++------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/log2xml/log2html.xsl b/src/log2xml/log2html.xsl index be4af66ce..05429efef 100644 --- a/src/log2xml/log2html.xsl +++ b/src/log2xml/log2html.xsl @@ -27,7 +27,7 @@
- + @@ -39,7 +39,7 @@
- + @@ -58,8 +58,8 @@ - - (...) + + /... diff --git a/src/log2xml/logfile.css b/src/log2xml/logfile.css index 342cf2583..01af4b08d 100644 --- a/src/log2xml/logfile.css +++ b/src/log2xml/logfile.css @@ -5,6 +5,12 @@ body } +td.line +{ + width: 100%; +} + + blockquote.body { padding: 6px 0px; @@ -69,6 +75,8 @@ tr.y > td.dummy > div.dummy em.storeref { color: #500000; + position: relative; + width: 100%; } @@ -92,17 +100,6 @@ em.storeref:hover } -em.storeref { - position: static; -} - - -span.z { - position: absolute; - width: 100%; -} - - -em.storeref:hover > span.z > *.popup { - display: block; +em.storeref:hover span.popup { + display: inline; } From 8ce3dd488711d0eca43c64ccc04903eeba135836 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 17 Mar 2004 16:55:53 +0000 Subject: [PATCH 0458/6440] * Display the popup directly over the abbreviation. --- src/log2xml/logfile.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/log2xml/logfile.css b/src/log2xml/logfile.css index 01af4b08d..7b2cdd066 100644 --- a/src/log2xml/logfile.css +++ b/src/log2xml/logfile.css @@ -92,8 +92,8 @@ em.storeref:hover background: #ffffcd; border: solid #555555 1px; position: absolute; - top: 1.5em; - left: 0.5em; + top: 0em; + left: 0em; margin: 0; padding: 0; z-index: 100; From a784fd5792a5447ad2b7dac63bea2a0b2fc379c3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 18 Mar 2004 13:04:05 +0000 Subject: [PATCH 0459/6440] * Don't use tables. Konqueror likes this much better. --- src/log2xml/log2html.xsl | 32 +++++++----------- src/log2xml/logfile.css | 72 ++++++++++++---------------------------- 2 files changed, 33 insertions(+), 71 deletions(-) diff --git a/src/log2xml/log2html.xsl b/src/log2xml/log2html.xsl index 05429efef..4a9a6f452 100644 --- a/src/log2xml/log2html.xsl +++ b/src/log2xml/log2html.xsl @@ -22,28 +22,20 @@
- - - - - -
-
-
- -
+
+ + + + +
- - - - - -
-
-
- -
+
+ + + + +
diff --git a/src/log2xml/logfile.css b/src/log2xml/logfile.css index 7b2cdd066..6ae1b08e2 100644 --- a/src/log2xml/logfile.css +++ b/src/log2xml/logfile.css @@ -1,74 +1,44 @@ -body -{ +body { font-family: sans-serif; background: white; } -td.line -{ - width: 100%; -} - - -blockquote.body -{ +blockquote.body { padding: 6px 0px; - margin: 0px 1px; + margin: 0px 0px; } -table.x, tr.x -{ - border-collapse: separate; - border-spacing: 0pt; - margin: 0em 0em 0em 0em; - padding: 0em 0em 0em 0em; +div.line, div.lastline { + position: relative; } - -tr.x > td.dummy -{ - vertical-align: top; - margin: 0em 0em 0em 0em; - padding: 0.5em 0em 0em 0em; - border-left: 3px solid #6185a0; +div.line { + border-left: 0.1em solid #6185a0; } - -tr.x > td.dummy > div.dummy -{ - width: 1.5em; - height: 3px; - margin: 0em 0em 0em 0em; - border-top: 3px solid #6185a0; +span.lineconn { + position: absolute; + height: 0.5em; + width: 1em; + border-bottom: 0.1em solid #6185a0; } - -table.y, tr.y -{ - border-collapse: separate; - border-spacing: 0pt; - margin: 0em 0em 0em 0em; - padding: 0em 0em 0em 0em; +div.lastline > span.lineconn { + border-left: 0.1em solid #6185a0; } - -tr.y > td.dummy -{ - vertical-align: top; - margin: 0em 0em 0em 0em; - padding: 0em 0em 0em 0em; +span.linebody { + position: relative; } +div.line > span.linebody { + left: 1.1em; +} -tr.y > td.dummy > div.dummy -{ - width: 1.5em; - height: 6px; - margin: 0em 0em 0em 0em; - border-left: 3px solid #6185a0; - border-bottom: 3px solid #6185a0; +div.lastline > span.linebody { + left: 1.2em; } From c2fc2c13c981e28ff673221da47cc93a7ed9291f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 18 Mar 2004 14:58:16 +0000 Subject: [PATCH 0460/6440] * Use unordered lists, which is more sensible semantically for representing tree structures. --- src/log2xml/log2html.xsl | 49 ++++++++++++++++++---------------------- src/log2xml/logfile.css | 44 +++++++++++++++++------------------- 2 files changed, 43 insertions(+), 50 deletions(-) diff --git a/src/log2xml/log2html.xsl b/src/log2xml/log2html.xsl index 4a9a6f452..cb56a3cb4 100644 --- a/src/log2xml/log2html.xsl +++ b/src/log2xml/log2html.xsl @@ -1,7 +1,9 @@ - + + + @@ -9,37 +11,30 @@ Log File - +
    + +
  • + +
  • +
    +
-
-
- -
-
- - -
- - - - -
-
- -
- - - - -
-
-
-
-
+ +
    + + linelastline +
  • + + + + +
  • +
    +
diff --git a/src/log2xml/logfile.css b/src/log2xml/logfile.css index 6ae1b08e2..9b3b56da3 100644 --- a/src/log2xml/logfile.css +++ b/src/log2xml/logfile.css @@ -4,43 +4,44 @@ body { } -blockquote.body { - padding: 6px 0px; - margin: 0px 0px; +ul.nesting, ul.toplevel { + padding: 0; + margin: 0; } +ul.toplevel { + list-style-type: none; +} -div.line, div.lastline { +ul.nesting li.line, ul.nesting li.lastline { position: relative; + list-style-type: none; } -div.line { +ul.nesting li.line { + padding-left: 1.1em; +} + +ul.nesting li.lastline { + padding-left: 1.2em; +} + +li.line { border-left: 0.1em solid #6185a0; } -span.lineconn { +li.line > span.lineconn, li.lastline > span.lineconn { position: absolute; - height: 0.5em; + height: 0.65em; + left: 0em; width: 1em; border-bottom: 0.1em solid #6185a0; } -div.lastline > span.lineconn { +li.lastline > span.lineconn { border-left: 0.1em solid #6185a0; } -span.linebody { - position: relative; -} - -div.line > span.linebody { - left: 1.1em; -} - -div.lastline > span.linebody { - left: 1.2em; -} - em.storeref { @@ -49,13 +50,11 @@ em.storeref width: 100%; } - em.storeref:hover { background-color: #eeeeee; } - *.popup { display: none; /* background: url('http://losser.st-lab.cs.uu.nl/~mbravenb/menuback.png') repeat; */ @@ -69,7 +68,6 @@ em.storeref:hover z-index: 100; } - em.storeref:hover span.popup { display: inline; } From 84c617966b8a78b7385aff04f1ac9b3bb7391898 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 18 Mar 2004 18:26:22 +0000 Subject: [PATCH 0461/6440] * Collapsable trees. --- src/log2xml/log2html.xsl | 2 ++ src/log2xml/logfile.css | 18 +++++++++++---- src/log2xml/treebits.js | 50 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 src/log2xml/treebits.js diff --git a/src/log2xml/log2html.xsl b/src/log2xml/log2html.xsl index cb56a3cb4..667eb5998 100644 --- a/src/log2xml/log2html.xsl +++ b/src/log2xml/log2html.xsl @@ -7,6 +7,7 @@ +