about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--configure.ac1
-rw-r--r--src/Makefile.am8
-rw-r--r--src/fix.cc295
-rwxr-xr-xsrc/nix-instantiate.in121
-rw-r--r--src/nix.cc86
-rw-r--r--src/util.hh82
-rw-r--r--test/fixdescriptors/aterm-2.0.fix10
-rwxr-xr-xtest/register19
8 files changed, 405 insertions, 217 deletions
diff --git a/configure.ac b/configure.ac
index 22e320a7c015..0a0d491a0e50 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 e1db3c4ebe86..2113b9620a8c 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 000000000000..cb1990928469
--- /dev/null
+++ b/src/fix.cc
@@ -0,0 +1,295 @@
+#include <iostream>
+#include <map>
+
+extern "C" {
+#include <aterm2.h>
+}
+
+#include "util.hh"
+
+
+static string nixDescriptorDir;
+static string nixSourcesDir;
+
+
+typedef map<string, string> 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, "<str>", &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, "<str>", &s)) {
+        checkHash(s);
+        return s;
+    } else if (ATmatch(e, "Url(<term>)", &t)) {
+        string url = evaluateStr(t);
+        string filename = fetchURL(url);
+        registerFile(filename);
+        return hashFile(filename);
+    } else if (ATmatch(e, "Local(<term>)", &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, "<str>", &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(<term>)", &t))
+        return ATmake("Str(<str>)", evaluateStr(t).c_str());
+    else if (ATmatch(e, "Bool(<term>)", &t))
+        return ATmake("Bool(<term>)", evaluateBool(t));
+    else if (ATmatch(e, "File(<term>)", &t))
+        return ATmake("File(<str>)", evaluateFile(t, dir).c_str());
+    else if (ATmatch(e, "Pkg(<term>)", &t))
+        return ATmake("Pkg(<term>)", evaluatePkg(t, done));
+    else throw Error("invalid expression type");
+}
+
+
+typedef map<string, ATerm> 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(<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(<term>)", &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(<str>, <term>), <list>]", 
+               &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(<str>)", id.c_str());
+
+    /* Add a system name. */
+    bindingsMap["system"] = ATmake("Str(<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(<str>, <term>)", it->first.c_str(), it->second));
+    ATerm outTerm = ATmake("Descr(<term>)", 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 7551e9d24da3..000000000000
--- 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 eb0706774cd8..9e42917a49fe 100644
--- a/src/nix.cc
+++ b/src/nix.cc
@@ -1,15 +1,11 @@
 #include <iostream>
-#include <fstream>
 #include <memory>
-#include <string>
-#include <sstream>
 #include <list>
 #include <vector>
 #include <set>
 #include <map>
 #include <cstdio>
 
-#include <unistd.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
@@ -20,10 +16,6 @@ extern "C" {
 #include <aterm1.h>
 }
 
-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<string, string> Params;
 
 
@@ -212,7 +153,14 @@ void readPkgDescr(const string & hash,
             fileImports[name] = arg;
         } else if (ATmatch(value, "Str(<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 8d82c80c14b4..fb405b0f1200 100644
--- a/src/util.hh
+++ b/src/util.hh
@@ -1,7 +1,15 @@
 #ifndef __UTIL_H
 #define __UTIL_H
 
+#include <string>
 #include <vector>
+#include <sstream>
+
+#include <unistd.h>
+
+extern "C" {
+#include "md5.h"
+}
 
 using namespace std;
 
@@ -31,4 +39,78 @@ public:
 typedef vector<string> 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 000000000000..0362314d6c3e
--- /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 57fe5b6c0d01..000000000000
--- 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