diff --git a/src/Makefile.am b/src/Makefile.am index a3cd46ca27..7d12719ac4 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 4f4d419f5e..354ecb6cc7 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 553c7c40b2..b04588e7b3 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 0000000000..cb42aca6b7 --- /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 639bd5ccf5..3851ef8676 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 fe65b977ea..5db04036c4 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 79ef48671f..b96fa30ba8 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. */