* Started implementing the new evaluation model.
* Lots of refactorings. * Unit tests.
This commit is contained in:
parent
b9f09b3268
commit
822794001c
15 changed files with 742 additions and 202 deletions
|
@ -9,16 +9,15 @@ nix_LDADD = -ldb_cxx-4 -lATerm
|
|||
fix_SOURCES = fix.cc util.cc hash.cc md5.c
|
||||
fix_LDADD = -lATerm
|
||||
|
||||
test_SOURCES = test.cc util.cc hash.cc md5.c
|
||||
test_SOURCES = test.cc util.cc hash.cc md5.c eval.cc values.cc globals.cc db.cc
|
||||
test_LDADD = -ldb_cxx-4 -lATerm
|
||||
|
||||
install-data-local:
|
||||
$(INSTALL) -d $(localstatedir)/nix
|
||||
$(INSTALL) -d $(localstatedir)/nix/descriptors
|
||||
$(INSTALL) -d $(localstatedir)/nix/sources
|
||||
$(INSTALL) -d $(localstatedir)/nix/links
|
||||
$(INSTALL) -d $(localstatedir)/nix/prebuilts
|
||||
$(INSTALL) -d $(localstatedir)/nix/prebuilts/imports
|
||||
$(INSTALL) -d $(localstatedir)/nix/prebuilts/exports
|
||||
# $(INSTALL) -d $(localstatedir)/nix/prebuilts
|
||||
# $(INSTALL) -d $(localstatedir)/nix/prebuilts/imports
|
||||
# $(INSTALL) -d $(localstatedir)/nix/prebuilts/exports
|
||||
$(INSTALL) -d $(localstatedir)/log/nix
|
||||
$(INSTALL) -d $(prefix)/pkg
|
||||
$(INSTALL) -d $(prefix)/values
|
||||
$(bindir)/nix init
|
||||
|
|
297
src/eval.cc
Normal file
297
src/eval.cc
Normal file
|
@ -0,0 +1,297 @@
|
|||
#include <map>
|
||||
#include <iostream>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "eval.hh"
|
||||
#include "globals.hh"
|
||||
#include "values.hh"
|
||||
#include "db.hh"
|
||||
|
||||
|
||||
/* A Unix environment is a mapping from strings to strings. */
|
||||
typedef map<string, string> Environment;
|
||||
|
||||
|
||||
/* Return true iff the given path exists. */
|
||||
bool pathExists(string path)
|
||||
{
|
||||
int res;
|
||||
struct stat st;
|
||||
res = stat(path.c_str(), &st);
|
||||
if (!res) return true;
|
||||
if (errno != ENOENT)
|
||||
throw SysError("getting status of " + path);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* Compute a derived value by running a program. */
|
||||
static Hash computeDerived(Hash sourceHash, string targetName,
|
||||
string platform, Hash prog, 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";
|
||||
/* !!! auto-pclose on exit */
|
||||
FILE * logFile = popen(("tee " + logFileName + " >&2").c_str(), "w"); /* !!! escaping */
|
||||
if (!logFile)
|
||||
throw SysError("unable to create log file " + logFileName);
|
||||
|
||||
try {
|
||||
|
||||
/* Fork a child to build the package. */
|
||||
pid_t pid;
|
||||
switch (pid = fork()) {
|
||||
|
||||
case -1:
|
||||
throw SysError("unable to fork");
|
||||
|
||||
case 0:
|
||||
|
||||
try { /* child */
|
||||
|
||||
#if 0
|
||||
/* 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);
|
||||
}
|
||||
#endif
|
||||
|
||||
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 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(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);
|
||||
|
||||
/* 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);
|
||||
|
||||
} catch (exception &) {
|
||||
// system(("rm -rf " + targetPath).c_str());
|
||||
throw;
|
||||
}
|
||||
|
||||
/* Hash the result. */
|
||||
Hash targetHash = hashFile(targetPath);
|
||||
|
||||
/* Register targetHash -> targetPath. !!! this should be in
|
||||
values.cc. */
|
||||
setDB(nixDB, dbNFs, sourceHash, 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;
|
||||
}
|
||||
|
||||
|
||||
/* Throw an exception if the given platform string is not supported by
|
||||
the platform we are executing on. */
|
||||
static void checkPlatform(string platform)
|
||||
{
|
||||
if (platform != thisSystem)
|
||||
throw Error("a `" + platform +
|
||||
"' is required, but I am a `" + thisSystem + "'");
|
||||
}
|
||||
|
||||
|
||||
/* Throw an exception with an error message containing the given
|
||||
aterm. */
|
||||
static Error badTerm(const string & msg, Expr e)
|
||||
{
|
||||
char * s = ATwriteToString(e);
|
||||
return Error(msg + ", in `" + s + "'");
|
||||
}
|
||||
|
||||
|
||||
/* Hash an expression. Hopefully the representation used by
|
||||
ATwriteToString() won't change, otherwise all hashes will
|
||||
change. */
|
||||
static Hash hashExpr(Expr e)
|
||||
{
|
||||
char * s = ATwriteToString(e);
|
||||
debug(s);
|
||||
return hashString(s);
|
||||
}
|
||||
|
||||
|
||||
/* Evaluate an expression; the result must be a string. */
|
||||
static string evalString(Expr e)
|
||||
{
|
||||
e = evalValue(e).e;
|
||||
char * s;
|
||||
if (ATmatch(e, "Str(<str>)", &s)) return s;
|
||||
else throw badTerm("string value expected", e);
|
||||
}
|
||||
|
||||
|
||||
/* Evaluate an expression; the result must be a external
|
||||
non-expression reference. */
|
||||
static Hash evalExternal(Expr e)
|
||||
{
|
||||
EvalResult r = evalValue(e);
|
||||
char * s;
|
||||
if (ATmatch(r.e, "External(<str>)", &s)) return r.h;
|
||||
else throw badTerm("external non-expression value expected", r.e);
|
||||
}
|
||||
|
||||
|
||||
/* Evaluate an expression. */
|
||||
EvalResult evalValue(Expr e)
|
||||
{
|
||||
EvalResult r;
|
||||
char * s;
|
||||
Expr eBuildPlatform, eProg;
|
||||
ATermList args;
|
||||
|
||||
/* Normal forms. */
|
||||
if (ATmatch(e, "Str(<str>)", &s) ||
|
||||
ATmatch(e, "Bool(True)") ||
|
||||
ATmatch(e, "Bool(False)"))
|
||||
{
|
||||
r.e = e;
|
||||
}
|
||||
|
||||
/* External expressions. */
|
||||
|
||||
/* External non-expressions. */
|
||||
else if (ATmatch(e, "External(<str>)", &s)) {
|
||||
r.e = e;
|
||||
r.h = parseHash(s);
|
||||
}
|
||||
|
||||
/* Execution primitive. */
|
||||
|
||||
else if (ATmatch(e, "Exec(<term>, <term>, [<list>])",
|
||||
&eBuildPlatform, &eProg, &args))
|
||||
{
|
||||
string buildPlatform = evalString(eBuildPlatform);
|
||||
|
||||
checkPlatform(buildPlatform);
|
||||
|
||||
Hash prog = evalExternal(eProg);
|
||||
|
||||
Environment env;
|
||||
while (!ATisEmpty(args)) {
|
||||
debug("arg");
|
||||
Expr arg = ATgetFirst(args);
|
||||
throw badTerm("foo", arg);
|
||||
args = ATgetNext(args);
|
||||
}
|
||||
|
||||
Hash sourceHash = hashExpr(
|
||||
ATmake("Exec(Str(<str>), External(<str>), [])",
|
||||
buildPlatform.c_str(), ((string) prog).c_str()));
|
||||
|
||||
/* Do we know a normal form for sourceHash? */
|
||||
Hash targetHash;
|
||||
string targetHashS;
|
||||
if (queryDB(nixDB, dbNFs, sourceHash, targetHashS)) {
|
||||
/* Yes. */
|
||||
targetHash = parseHash(targetHashS);
|
||||
debug("already built: " + (string) sourceHash
|
||||
+ " -> " + (string) targetHash);
|
||||
} else {
|
||||
/* No, so we compute one. */
|
||||
targetHash = computeDerived(sourceHash,
|
||||
(string) sourceHash + "-nf", buildPlatform, prog, env);
|
||||
}
|
||||
|
||||
r.e = ATmake("External(<str>)", ((string) targetHash).c_str());
|
||||
r.h = targetHash;
|
||||
}
|
||||
|
||||
/* Barf. */
|
||||
else throw badTerm("invalid expression", e);
|
||||
|
||||
return r;
|
||||
}
|
86
src/eval.hh
Normal file
86
src/eval.hh
Normal file
|
@ -0,0 +1,86 @@
|
|||
#ifndef __EVAL_H
|
||||
#define __EVAL_H
|
||||
|
||||
extern "C" {
|
||||
#include <aterm2.h>
|
||||
}
|
||||
|
||||
#include "hash.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
/* Abstract syntax of Nix values:
|
||||
|
||||
e := Hash(h) -- reference to expression value
|
||||
| External(h) -- reference to non-expression value
|
||||
| Str(s) -- string constant
|
||||
| Bool(b) -- boolean constant
|
||||
| App(e, e) -- application
|
||||
| Lam(x, e) -- lambda abstraction
|
||||
| Exec(platform, e, [(s, e)])
|
||||
-- primitive; execute e with args e* on platform
|
||||
;
|
||||
|
||||
Semantics
|
||||
|
||||
Each rules given as eval(e) => (e', h'), i.e., expression e has a
|
||||
normal form e' with hash code h'. evalE = fst . eval. evalH = snd
|
||||
. eval.
|
||||
|
||||
eval(Hash(h)) => eval(loadExpr(h))
|
||||
|
||||
eval(External(h)) => (External(h), h)
|
||||
|
||||
eval(Str(s)@e) => (e, 0) # idem for Bool
|
||||
|
||||
eval(App(e1, e2)) => eval(App(e1', e2))
|
||||
where e1' = evalE(e1)
|
||||
|
||||
eval(App(Lam(var, body), arg)@in) =>
|
||||
eval(subst(var, arg, body))@out
|
||||
[AND write out to storage, and dbNFs[hash(in)] = hash(out) ???]
|
||||
|
||||
eval(Exec(platform, prog, args)@e) =>
|
||||
(External(h), h)
|
||||
where
|
||||
hIn = hashExpr(e)
|
||||
|
||||
fn = ... form name involving hIn ...
|
||||
|
||||
h =
|
||||
if exec(evalE(platform) => Str(...)
|
||||
, getFile(evalH(prog))
|
||||
, map(makeArg . eval, args)
|
||||
) then
|
||||
hashExternal(fn)
|
||||
else
|
||||
undef
|
||||
|
||||
makeArg((argn, (External(h), h))) => (argn, getFile(h))
|
||||
makeArg((argn, (Str(s), _))) => (argn, s)
|
||||
makeArg((argn, (Bool(True), _))) => (argn, "1")
|
||||
makeArg((argn, (Bool(False), _))) => (argn, undef)
|
||||
|
||||
getFile :: Hash -> FileName
|
||||
loadExpr :: Hash -> FileName
|
||||
hashExpr :: Expr -> Hash
|
||||
hashExternal :: FileName -> Hash
|
||||
exec :: Platform -> FileName -> [(String, String)] -> Status
|
||||
*/
|
||||
|
||||
typedef ATerm Expr;
|
||||
|
||||
|
||||
struct EvalResult
|
||||
{
|
||||
Expr e;
|
||||
Hash h;
|
||||
};
|
||||
|
||||
|
||||
/* Evaluate an expression. */
|
||||
EvalResult evalValue(Expr e);
|
||||
|
||||
|
||||
#endif /* !__EVAL_H */
|
19
src/globals.cc
Normal file
19
src/globals.cc
Normal file
|
@ -0,0 +1,19 @@
|
|||
#include "globals.hh"
|
||||
#include "db.hh"
|
||||
|
||||
|
||||
string dbRefs = "refs";
|
||||
string dbNFs = "nfs";
|
||||
string dbNetSources = "netsources";
|
||||
|
||||
string nixValues = "/UNINIT";
|
||||
string nixLogDir = "/UNINIT";
|
||||
string nixDB = "/UNINIT";
|
||||
|
||||
|
||||
void initDB()
|
||||
{
|
||||
createDB(nixDB, dbRefs);
|
||||
createDB(nixDB, dbNFs);
|
||||
createDB(nixDB, dbNetSources);
|
||||
}
|
60
src/globals.hh
Normal file
60
src/globals.hh
Normal file
|
@ -0,0 +1,60 @@
|
|||
#ifndef __GLOBALS_H
|
||||
#define __GLOBALS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
/* Database names. */
|
||||
|
||||
/* dbRefs :: Hash -> FileName
|
||||
|
||||
Maintains a mapping from hashes to filenames within the NixValues
|
||||
directory. This mapping is for performance only; it can be
|
||||
reconstructed unambiguously. The reason is that names in this
|
||||
directory are not printed hashes but also might carry some
|
||||
descriptive element (e.g., "aterm-2.0-ae749a..."). Without this
|
||||
mapping, looking up a value would take O(n) time because we would
|
||||
need to read the entire directory. */
|
||||
extern string dbRefs;
|
||||
|
||||
/* dbNFs :: Hash -> Hash
|
||||
|
||||
Each pair (h1, h2) in this mapping records the fact that the value
|
||||
referenced by h2 is a normal form obtained by evaluating the value
|
||||
referenced by value h1.
|
||||
*/
|
||||
extern string dbNFs;
|
||||
|
||||
/* dbNetSources :: Hash -> URL
|
||||
|
||||
Each pair (hash, url) in this mapping states that the value
|
||||
identified by hash can be obtained by fetching the value pointed
|
||||
to by url.
|
||||
|
||||
TODO: this should be Hash -> [URL]
|
||||
|
||||
TODO: factor this out into a separate tool? */
|
||||
extern string dbNetSources;
|
||||
|
||||
|
||||
/* Path names. */
|
||||
|
||||
/* nixValues is the directory where all Nix values (both files and
|
||||
directories, and both normal and non-normal forms) live. */
|
||||
extern string nixValues;
|
||||
|
||||
/* nixLogDir is the directory where we log evaluations. */
|
||||
extern string nixLogDir;
|
||||
|
||||
/* nixDB is the file name of the Berkeley DB database where we
|
||||
maintain the dbXXX mappings. */
|
||||
extern string nixDB;
|
||||
|
||||
|
||||
/* Initialize the databases. */
|
||||
void initDB();
|
||||
|
||||
|
||||
#endif /* !__GLOBALS_H */
|
15
src/hash.cc
15
src/hash.cc
|
@ -46,6 +46,8 @@ Hash::operator string() const
|
|||
Hash parseHash(const string & s)
|
||||
{
|
||||
Hash hash;
|
||||
if (s.length() != Hash::hashSize * 2)
|
||||
throw BadRefError("invalid hash: " + s);
|
||||
for (unsigned int i = 0; i < Hash::hashSize; i++) {
|
||||
string s2(s, i * 2, 2);
|
||||
if (!isxdigit(s2[0]) || !isxdigit(s2[1]))
|
||||
|
@ -73,15 +75,24 @@ bool isHash(const string & s)
|
|||
}
|
||||
|
||||
|
||||
/* Compute the MD5 hash of a file. */
|
||||
Hash hashString(const string & s)
|
||||
{
|
||||
Hash hash;
|
||||
md5_buffer(s.c_str(), s.length(), hash.hash);
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
/* Compute the MD5 hash of a file. */
|
||||
Hash hashFile(const string & fileName)
|
||||
{
|
||||
Hash hash;
|
||||
FILE * file = fopen(fileName.c_str(), "rb");
|
||||
if (!file)
|
||||
throw Error("file `" + fileName + "' does not exist");
|
||||
throw SysError("file `" + fileName + "' does not exist");
|
||||
int err = md5_stream(file, hash.hash);
|
||||
fclose(file);
|
||||
if (err) throw Error("cannot hash file");
|
||||
if (err) throw SysError("cannot hash file " + fileName);
|
||||
return hash;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ public:
|
|||
|
||||
Hash parseHash(const string & s);
|
||||
bool isHash(const string & s);
|
||||
Hash hashString(const string & s);
|
||||
Hash hashFile(const string & fileName);
|
||||
|
||||
#endif /* !__HASH_H */
|
||||
|
|
156
src/nix.cc
156
src/nix.cc
|
@ -11,155 +11,15 @@
|
|||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
extern "C" {
|
||||
#include <aterm1.h>
|
||||
}
|
||||
|
||||
#include "util.hh"
|
||||
#include "hash.hh"
|
||||
#include "db.hh"
|
||||
#include "nix.hh"
|
||||
#include "eval.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
/* Database names. */
|
||||
|
||||
/* dbRefs :: Hash -> FileName
|
||||
|
||||
Maintains a mapping from hashes to filenames within the NixValues
|
||||
directory. This mapping is for performance only; it can be
|
||||
reconstructed unambiguously from the nixValues directory. The
|
||||
reason is that names in this directory are not printed hashes but
|
||||
also might carry some descriptive element (e.g.,
|
||||
"aterm-2.0-ae749a..."). Without this mapping, looking up a value
|
||||
would take O(n) time because we would need to read the entire
|
||||
directory. */
|
||||
static string dbRefs = "refs";
|
||||
|
||||
/* dbNFs :: Hash -> Hash
|
||||
|
||||
Each pair (h1, h2) in this mapping records the fact that h2 is a
|
||||
normal form obtained by evaluating the value h1.
|
||||
|
||||
We would really like to have h2 be the hash of the object
|
||||
referenced by h2. However, that gives a cyclic dependency: to
|
||||
compute the hash (and thus the file name) of the object, we need to
|
||||
compute the object, but to do that, we need the file name of the
|
||||
object.
|
||||
|
||||
So for now we abandon the requirement that
|
||||
|
||||
hashFile(dbRefs[h]) == h.
|
||||
|
||||
I.e., this property does not hold for computed normal forms.
|
||||
Rather, we use h2 = hash(h1). This allows dbNFs to be
|
||||
reconstructed. Perhaps using a pseudo random number would be
|
||||
better to prevent the system from being subverted in some way.
|
||||
*/
|
||||
static string dbNFs = "nfs";
|
||||
|
||||
/* dbNetSources :: Hash -> URL
|
||||
|
||||
Each pair (hash, url) in this mapping states that the object
|
||||
identified by hash can be obtained by fetching the object pointed
|
||||
to by url.
|
||||
|
||||
TODO: this should be Hash -> [URL]
|
||||
|
||||
TODO: factor this out into a separate tool? */
|
||||
static string dbNetSources = "netsources";
|
||||
|
||||
|
||||
/* Path names. */
|
||||
|
||||
/* nixValues is the directory where all Nix values (both files and
|
||||
directories, and both normal and non-normal forms) live. */
|
||||
static string nixValues;
|
||||
|
||||
/* nixLogDir is the directory where we log evaluations. */
|
||||
static string nixLogDir;
|
||||
|
||||
/* nixDB is the file name of the Berkeley DB database where we
|
||||
maintain the dbXXX mappings. */
|
||||
static string nixDB;
|
||||
|
||||
|
||||
/* Abstract syntax of Nix values:
|
||||
|
||||
e := Hash(h) -- external reference
|
||||
| Str(s) -- string constant
|
||||
| Bool(b) -- boolean constant
|
||||
| Name(e) -- "&" operator; pointer (file name) formation
|
||||
| App(e, e) -- application
|
||||
| Lam(x, e) -- lambda abstraction
|
||||
| Exec(platform, e, e*)
|
||||
-- primitive; execute e with args e* on platform
|
||||
;
|
||||
*/
|
||||
|
||||
|
||||
/* 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)) {
|
||||
cerr << "fetching " << url << endl;
|
||||
/* !!! 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;
|
||||
}
|
||||
|
||||
|
||||
/* Obtain an object with the given hash. If a file with that hash is
|
||||
known to exist in the local file system (as indicated by the dbRefs
|
||||
database), we use that. Otherwise, we attempt to fetch it from the
|
||||
network (using dbNetSources). We verify that the file has the
|
||||
right hash. */
|
||||
string getFile(Hash hash)
|
||||
{
|
||||
bool checkedNet = false;
|
||||
|
||||
while (1) {
|
||||
|
||||
string fn, url;
|
||||
|
||||
if (queryDB(nixDB, dbRefs, hash, fn)) {
|
||||
|
||||
/* Verify that the file hasn't changed. !!! race */
|
||||
if (hashFile(fn) != hash)
|
||||
throw Error("file " + fn + " is stale");
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
if (checkedNet)
|
||||
throw Error("consistency problem: file fetched from " + url +
|
||||
" should have hash " + (string) hash + ", but it doesn't");
|
||||
|
||||
if (!queryDB(nixDB, dbNetSources, hash, url))
|
||||
throw Error("a file with hash " + (string) hash + " is requested, "
|
||||
"but it is not known to exist locally or on the network");
|
||||
|
||||
checkedNet = true;
|
||||
|
||||
fn = fetchURL(url);
|
||||
|
||||
setDB(nixDB, dbRefs, hash, fn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef map<string, string> Params;
|
||||
|
||||
|
||||
void readPkgDescr(Hash hash,
|
||||
Params & pkgImports, Params & fileImports, Params & arguments)
|
||||
{
|
||||
|
@ -204,9 +64,6 @@ void readPkgDescr(Hash hash,
|
|||
string getPkg(Hash hash);
|
||||
|
||||
|
||||
typedef map<string, string> Environment;
|
||||
|
||||
|
||||
void fetchDeps(Hash hash, Environment & env)
|
||||
{
|
||||
/* Read the package description file. */
|
||||
|
@ -538,15 +395,6 @@ void registerInstalledPkg(Hash hash, string path)
|
|||
}
|
||||
|
||||
|
||||
void initDB()
|
||||
{
|
||||
createDB(nixDB, dbRefs);
|
||||
createDB(nixDB, dbInstPkgs);
|
||||
createDB(nixDB, dbPrebuilts);
|
||||
createDB(nixDB, dbNetSources);
|
||||
}
|
||||
|
||||
|
||||
void verifyDB()
|
||||
{
|
||||
/* Check that all file references are still valid. */
|
||||
|
|
3
src/test-builder-1.sh
Normal file
3
src/test-builder-1.sh
Normal file
|
@ -0,0 +1,3 @@
|
|||
#! /bin/sh
|
||||
|
||||
echo "Hello World" > $out
|
5
src/test-builder-2.sh
Normal file
5
src/test-builder-2.sh
Normal file
|
@ -0,0 +1,5 @@
|
|||
#! /bin/sh
|
||||
|
||||
mkdir $out || exit 1
|
||||
cd $out || exit 1
|
||||
echo "Hello World" > bla
|
82
src/test.cc
82
src/test.cc
|
@ -1,16 +1,82 @@
|
|||
#include <iostream>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "hash.hh"
|
||||
#include "util.hh"
|
||||
#include "eval.hh"
|
||||
#include "values.hh"
|
||||
#include "globals.hh"
|
||||
|
||||
|
||||
void evalTest(Expr e)
|
||||
{
|
||||
EvalResult r = evalValue(e);
|
||||
|
||||
char * s = ATwriteToString(r.e);
|
||||
cout << (string) r.h << ": " << s << endl;
|
||||
}
|
||||
|
||||
|
||||
void runTests()
|
||||
{
|
||||
/* Hashing. */
|
||||
string s = "0b0ffd0538622bfe20b92c4aa57254d9";
|
||||
Hash h = parseHash(s);
|
||||
if ((string) h != s) abort();
|
||||
|
||||
try {
|
||||
h = parseHash("blah blah");
|
||||
abort();
|
||||
} catch (BadRefError err) { };
|
||||
|
||||
try {
|
||||
h = parseHash("0b0ffd0538622bfe20b92c4aa57254d99");
|
||||
abort();
|
||||
} catch (BadRefError err) { };
|
||||
|
||||
|
||||
/* Set up the test environment. */
|
||||
|
||||
mkdir("scratch", 0777);
|
||||
|
||||
string testDir = absPath("scratch");
|
||||
cout << testDir << endl;
|
||||
|
||||
nixValues = testDir;
|
||||
nixLogDir = testDir;
|
||||
nixDB = testDir + "/db";
|
||||
|
||||
initDB();
|
||||
|
||||
/* Expression evaluation. */
|
||||
|
||||
evalTest(ATmake("Str(\"Hello World\")"));
|
||||
evalTest(ATmake("Bool(True)"));
|
||||
evalTest(ATmake("Bool(False)"));
|
||||
|
||||
Hash builder1 = addValue("./test-builder-1.sh");
|
||||
|
||||
evalTest(ATmake("Exec(Str(<str>), External(<str>), [])",
|
||||
thisSystem.c_str(), ((string) builder1).c_str()));
|
||||
|
||||
Hash builder2 = addValue("./test-builder-2.sh");
|
||||
|
||||
evalTest(ATmake("Exec(Str(<str>), External(<str>), [])",
|
||||
thisSystem.c_str(), ((string) builder2).c_str()));
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char * * argv)
|
||||
{
|
||||
Hash h = hashFile("/etc/passwd");
|
||||
|
||||
cout << (string) h << endl;
|
||||
ATerm bottomOfStack;
|
||||
ATinit(argc, argv, &bottomOfStack);
|
||||
|
||||
h = parseHash("0b0ffd0538622bfe20b92c4aa57254d9");
|
||||
|
||||
cout << (string) h << endl;
|
||||
|
||||
return 0;
|
||||
try {
|
||||
runTests();
|
||||
} catch (exception & e) {
|
||||
cerr << "error: " << e.what() << endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
54
src/util.cc
54
src/util.cc
|
@ -1,47 +1,55 @@
|
|||
#include <iostream>
|
||||
|
||||
#include "util.hh"
|
||||
|
||||
|
||||
string thisSystem = SYSTEM;
|
||||
string nixHomeDir = "/nix";
|
||||
string nixHomeDirEnvVar = "NIX";
|
||||
|
||||
|
||||
|
||||
string absPath(string filename, string dir)
|
||||
SysError::SysError(string msg)
|
||||
{
|
||||
if (filename[0] != '/') {
|
||||
char * sysMsg = strerror(errno);
|
||||
err = msg + ": " + sysMsg;
|
||||
}
|
||||
|
||||
|
||||
string absPath(string path, string dir)
|
||||
{
|
||||
if (path[0] != '/') {
|
||||
if (dir == "") {
|
||||
char buf[PATH_MAX];
|
||||
if (!getcwd(buf, sizeof(buf)))
|
||||
throw Error("cannot get cwd");
|
||||
throw SysError("cannot get cwd");
|
||||
dir = buf;
|
||||
}
|
||||
filename = dir + "/" + filename;
|
||||
path = dir + "/" + path;
|
||||
/* !!! canonicalise */
|
||||
char resolved[PATH_MAX];
|
||||
if (!realpath(filename.c_str(), resolved))
|
||||
throw Error("cannot canonicalise path " + filename);
|
||||
filename = resolved;
|
||||
if (!realpath(path.c_str(), resolved))
|
||||
throw SysError("cannot canonicalise path " + path);
|
||||
path = resolved;
|
||||
}
|
||||
return filename;
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
/* Return the directory part of the given path, i.e., everything
|
||||
before the final `/'. */
|
||||
string dirOf(string s)
|
||||
string dirOf(string path)
|
||||
{
|
||||
unsigned int pos = s.rfind('/');
|
||||
if (pos == string::npos) throw Error("invalid file name");
|
||||
return string(s, 0, pos);
|
||||
unsigned int pos = path.rfind('/');
|
||||
if (pos == string::npos) throw Error("invalid file name: " + path);
|
||||
return string(path, 0, pos);
|
||||
}
|
||||
|
||||
|
||||
/* Return the base name of the given path, i.e., everything following
|
||||
the final `/'. */
|
||||
string baseNameOf(string s)
|
||||
string baseNameOf(string path)
|
||||
{
|
||||
unsigned int pos = s.rfind('/');
|
||||
if (pos == string::npos) throw Error("invalid file name");
|
||||
return string(s, pos + 1);
|
||||
unsigned int pos = path.rfind('/');
|
||||
if (pos == string::npos) throw Error("invalid file name: " + path);
|
||||
return string(path, pos + 1);
|
||||
}
|
||||
|
||||
|
||||
void debug(string s)
|
||||
{
|
||||
cerr << "debug: " << s << endl;
|
||||
}
|
||||
|
|
29
src/util.hh
29
src/util.hh
|
@ -12,13 +12,21 @@ using namespace std;
|
|||
|
||||
class Error : public exception
|
||||
{
|
||||
protected:
|
||||
string err;
|
||||
public:
|
||||
Error() { }
|
||||
Error(string _err) { err = _err; }
|
||||
~Error() throw () { };
|
||||
~Error() throw () { }
|
||||
const char * what() const throw () { return err.c_str(); }
|
||||
};
|
||||
|
||||
class SysError : public Error
|
||||
{
|
||||
public:
|
||||
SysError(string msg);
|
||||
};
|
||||
|
||||
class UsageError : public Error
|
||||
{
|
||||
public:
|
||||
|
@ -33,15 +41,20 @@ typedef vector<string> Strings;
|
|||
extern string thisSystem;
|
||||
|
||||
|
||||
/* The prefix of the Nix installation, and the environment variable
|
||||
that can be used to override the default. */
|
||||
extern string nixHomeDir;
|
||||
extern string nixHomeDirEnvVar;
|
||||
/* Return an absolutized path, resolving paths relative to the
|
||||
specified directory, or the current directory otherwise. */
|
||||
string absPath(string path, string dir = "");
|
||||
|
||||
/* Return the directory part of the given path, i.e., everything
|
||||
before the final `/'. */
|
||||
string dirOf(string path);
|
||||
|
||||
/* Return the base name of the given path, i.e., everything following
|
||||
the final `/'. */
|
||||
string baseNameOf(string path);
|
||||
|
||||
|
||||
string absPath(string filename, string dir = "");
|
||||
string dirOf(string s);
|
||||
string baseNameOf(string s);
|
||||
void debug(string s);
|
||||
|
||||
|
||||
#endif /* !__UTIL_H */
|
||||
|
|
100
src/values.cc
Normal file
100
src/values.cc
Normal file
|
@ -0,0 +1,100 @@
|
|||
#include "values.hh"
|
||||
#include "globals.hh"
|
||||
#include "db.hh"
|
||||
|
||||
|
||||
static void copyFile(string src, string dst)
|
||||
{
|
||||
int res = system(("cat " + src + " > " + dst).c_str()); /* !!! escape */
|
||||
if (WEXITSTATUS(res) != 0)
|
||||
throw Error("cannot copy " + src + " to " + dst);
|
||||
}
|
||||
|
||||
|
||||
static string absValuePath(string s)
|
||||
{
|
||||
return nixValues + "/" + s;
|
||||
}
|
||||
|
||||
|
||||
Hash addValue(string path)
|
||||
{
|
||||
Hash hash = hashFile(path);
|
||||
|
||||
string name;
|
||||
if (queryDB(nixDB, dbRefs, hash, name)) {
|
||||
debug((string) hash + " already known");
|
||||
return hash;
|
||||
}
|
||||
|
||||
string baseName = baseNameOf(path);
|
||||
|
||||
string targetName = (string) hash + "-" + baseName;
|
||||
|
||||
copyFile(path, absValuePath(targetName));
|
||||
|
||||
setDB(nixDB, dbRefs, hash, targetName);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
/* 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)) {
|
||||
cerr << "fetching " << url << endl;
|
||||
/* !!! 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
string queryValuePath(Hash hash)
|
||||
{
|
||||
bool checkedNet = false;
|
||||
|
||||
while (1) {
|
||||
|
||||
string name, url;
|
||||
|
||||
if (queryDB(nixDB, dbRefs, hash, name)) {
|
||||
string fn = absValuePath(name);
|
||||
|
||||
/* Verify that the file hasn't changed. !!! race */
|
||||
if (hashFile(fn) != hash)
|
||||
throw Error("file " + fn + " is stale");
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
throw Error("a file with hash " + (string) hash + " is requested, "
|
||||
"but it is not known to exist locally or on the network");
|
||||
#if 0
|
||||
if (checkedNet)
|
||||
throw Error("consistency problem: file fetched from " + url +
|
||||
" should have hash " + (string) hash + ", but it doesn't");
|
||||
|
||||
if (!queryDB(nixDB, dbNetSources, hash, url))
|
||||
throw Error("a file with hash " + (string) hash + " is requested, "
|
||||
"but it is not known to exist locally or on the network");
|
||||
|
||||
checkedNet = true;
|
||||
|
||||
fn = fetchURL(url);
|
||||
|
||||
setDB(nixDB, dbRefs, hash, fn);
|
||||
#endif
|
||||
}
|
||||
}
|
24
src/values.hh
Normal file
24
src/values.hh
Normal file
|
@ -0,0 +1,24 @@
|
|||
#ifndef __VALUES_H
|
||||
#define __VALUES_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "hash.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
/* Copy a value to the nixValues directory and register it in dbRefs.
|
||||
Return the hash code of the value. */
|
||||
Hash addValue(string pathName);
|
||||
|
||||
|
||||
/* Obtain the path of a value with the given hash. If a file with
|
||||
that hash is known to exist in the local file system (as indicated
|
||||
by the dbRefs database), we use that. Otherwise, we attempt to
|
||||
fetch it from the network (using dbNetSources). We verify that the
|
||||
file has the right hash. */
|
||||
string queryValuePath(Hash hash);
|
||||
|
||||
|
||||
#endif /* !__VALUES_H */
|
Loading…
Reference in a new issue