From 40b5936691fe2448dea0080e2319cc340bc7c65c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 27 Jun 2003 14:56:12 +0000 Subject: [PATCH] * 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);