From 0d2b24cdd103f21861ad42fd6d98e5d1cb252646 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Apr 2003 12:00:51 +0000 Subject: [PATCH] * `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