#include #include #include #include #include extern "C" { #include } #include "util.hh" 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()); if (WEXITSTATUS(res) != 0) 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; } /* 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); 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"); } 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] = outHash; return outHash; } /* 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; }