mirror of
https://github.com/NixOS/nix
synced 2025-07-05 16:31:47 +02:00
* For efficiency: md5 integrated into nix.
* Command `nix ensure' which is like `nix getpkg' except that if the has refers to a run action it will just ensure that the imports are there. * Command `nix closure' to print out the closure of the set of descriptors under the import relation, starting at a set of roots. This can be used for garbage collection (e.g., given a list of `activated' packages, we can delete all packages not reachable from those). * Command `nix graph' to print out a Dot graph of the dependency graph. * `nix-addroot' adds a root for the (unimplemented) garbage collector.
This commit is contained in:
parent
eeab86e0ac
commit
73c53935d0
5 changed files with 810 additions and 66 deletions
260
src/nix.cc
260
src/nix.cc
|
@ -4,6 +4,8 @@
|
|||
#include <string>
|
||||
#include <sstream>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <cstdio>
|
||||
|
||||
|
@ -14,6 +16,10 @@
|
|||
|
||||
#include <db4/db_cxx.h>
|
||||
|
||||
extern "C" {
|
||||
#include "md5.h"
|
||||
}
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
|
@ -146,8 +152,20 @@ 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 checkRef(const string & s)
|
||||
void checkHash(const string & s)
|
||||
{
|
||||
string err = "invalid reference: " + s;
|
||||
if (s.length() != 32)
|
||||
|
@ -162,31 +180,36 @@ void checkRef(const string & s)
|
|||
|
||||
|
||||
/* Compute the MD5 hash of a file. */
|
||||
string makeRef(string filename)
|
||||
string hashFile(string filename)
|
||||
{
|
||||
char hash[33];
|
||||
|
||||
FILE * pipe = popen(("md5sum " + filename + " 2> /dev/null").c_str(), "r");
|
||||
if (!pipe) throw BadRefError("cannot execute md5sum");
|
||||
|
||||
if (fread(hash, 32, 1, pipe) != 1)
|
||||
throw BadRefError("cannot read hash from md5sum of " + filename);
|
||||
hash[32] = 0;
|
||||
|
||||
pclose(pipe);
|
||||
|
||||
checkRef(hash);
|
||||
return hash;
|
||||
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 pair<string, string> Param;
|
||||
typedef list<Param> Params;
|
||||
typedef map<string, string> Params;
|
||||
|
||||
|
||||
void readPkgDescr(const string & pkgfile,
|
||||
void readPkgDescr(const string & hash,
|
||||
Params & pkgImports, Params & fileImports, Params & arguments)
|
||||
{
|
||||
string pkgfile;
|
||||
|
||||
if (!queryDB(dbRefs, hash, pkgfile))
|
||||
throw Error("unknown package " + hash);
|
||||
|
||||
// cerr << "reading information about " + hash + " from " + pkgfile + "\n";
|
||||
|
||||
/* Verify that the file hasn't changed. !!! race */
|
||||
if (hashFile(pkgfile) != hash)
|
||||
throw Error("file " + pkgfile + " is stale");
|
||||
|
||||
ifstream file;
|
||||
file.exceptions(ios::badbit);
|
||||
file.open(pkgfile.c_str());
|
||||
|
@ -206,13 +229,13 @@ void readPkgDescr(const string & pkgfile,
|
|||
str >> name >> op >> ref;
|
||||
|
||||
if (op == "<-") {
|
||||
checkRef(ref);
|
||||
pkgImports.push_back(Param(name, ref));
|
||||
checkHash(ref);
|
||||
pkgImports[name] = ref;
|
||||
} else if (op == "=") {
|
||||
checkRef(ref);
|
||||
fileImports.push_back(Param(name, ref));
|
||||
checkHash(ref);
|
||||
fileImports[name] = ref;
|
||||
} else if (op == ":")
|
||||
arguments.push_back(Param(name, ref));
|
||||
arguments[name] = ref;
|
||||
else throw Error("invalid operator " + op);
|
||||
}
|
||||
}
|
||||
|
@ -226,20 +249,9 @@ typedef map<string, string> Environment;
|
|||
|
||||
void fetchDeps(string hash, Environment & env)
|
||||
{
|
||||
string pkgfile;
|
||||
|
||||
if (!queryDB(dbRefs, hash, pkgfile))
|
||||
throw Error("unknown package " + hash);
|
||||
|
||||
cerr << "reading information about " + hash + " from " + pkgfile + "\n";
|
||||
|
||||
/* Verify that the file hasn't changed. !!! race */
|
||||
if (makeRef(pkgfile) != hash)
|
||||
throw Error("file " + pkgfile + " is stale");
|
||||
|
||||
/* Read the package description file. */
|
||||
Params pkgImports, fileImports, arguments;
|
||||
readPkgDescr(pkgfile, pkgImports, fileImports, arguments);
|
||||
readPkgDescr(hash, pkgImports, fileImports, arguments);
|
||||
|
||||
/* Recursively fetch all the dependencies, filling in the
|
||||
environment as we go along. */
|
||||
|
@ -264,7 +276,7 @@ void fetchDeps(string hash, Environment & env)
|
|||
if (!queryDB(dbRefs, it->second, file))
|
||||
throw Error("unknown file " + it->second);
|
||||
|
||||
if (makeRef(file) != it->second)
|
||||
if (hashFile(file) != it->second)
|
||||
throw Error("file " + file + " is stale");
|
||||
|
||||
env[it->first] = file;
|
||||
|
@ -374,7 +386,7 @@ void installPkg(string hash)
|
|||
string getPkg(string hash)
|
||||
{
|
||||
string path;
|
||||
checkRef(hash);
|
||||
checkHash(hash);
|
||||
while (!queryDB(dbInstPkgs, hash, path))
|
||||
installPkg(hash);
|
||||
return path;
|
||||
|
@ -434,6 +446,20 @@ void runPkg(string hash)
|
|||
}
|
||||
|
||||
|
||||
void ensurePkg(string hash)
|
||||
{
|
||||
Params pkgImports, fileImports, arguments;
|
||||
readPkgDescr(hash, pkgImports, fileImports, arguments);
|
||||
|
||||
if (fileImports.find("build") != fileImports.end())
|
||||
getPkg(hash);
|
||||
else if (fileImports.find("run") != fileImports.end()) {
|
||||
Environment env;
|
||||
fetchDeps(hash, env);
|
||||
} else throw Error("invalid descriptor");
|
||||
}
|
||||
|
||||
|
||||
string absPath(string filename)
|
||||
{
|
||||
if (filename[0] != '/') {
|
||||
|
@ -450,14 +476,14 @@ string absPath(string filename)
|
|||
void registerFile(string filename)
|
||||
{
|
||||
filename = absPath(filename);
|
||||
setDB(dbRefs, makeRef(filename), filename);
|
||||
setDB(dbRefs, hashFile(filename), filename);
|
||||
}
|
||||
|
||||
|
||||
/* This is primarily used for bootstrapping. */
|
||||
void registerInstalledPkg(string hash, string path)
|
||||
{
|
||||
checkRef(hash);
|
||||
checkHash(hash);
|
||||
if (path == "")
|
||||
delDB(dbInstPkgs, hash);
|
||||
else
|
||||
|
@ -483,8 +509,10 @@ void verifyDB()
|
|||
it != fileRefs.end(); it++)
|
||||
{
|
||||
try {
|
||||
if (makeRef(it->second) != it->first)
|
||||
if (hashFile(it->second) != it->first) {
|
||||
cerr << "file " << it->second << " has changed\n";
|
||||
delDB(dbRefs, it->first);
|
||||
}
|
||||
} catch (BadRefError e) { /* !!! better error check */
|
||||
cerr << "file " << it->second << " has disappeared\n";
|
||||
delDB(dbRefs, it->first);
|
||||
|
@ -519,48 +547,136 @@ void listInstalledPkgs()
|
|||
|
||||
for (DBPairs::iterator it = instPkgs.begin();
|
||||
it != instPkgs.end(); it++)
|
||||
cout << it->first << endl;
|
||||
}
|
||||
|
||||
|
||||
void printInfo(vector<string> hashes)
|
||||
{
|
||||
for (vector<string>::iterator it = hashes.begin();
|
||||
it != hashes.end(); it++)
|
||||
{
|
||||
string descr;
|
||||
if (!queryDB(dbRefs, it->first, descr))
|
||||
descr = "descriptor missing";
|
||||
cout << it->first << " " << descr << endl;
|
||||
try {
|
||||
Params pkgImports, fileImports, arguments;
|
||||
readPkgDescr(*it, pkgImports, fileImports, arguments);
|
||||
cout << *it << " " << getFromEnv(arguments, "id") << endl;
|
||||
} catch (Error & e) {
|
||||
cout << *it << " (descriptor missing)\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void run(int argc, char * * argv)
|
||||
void computeClosure(const vector<string> & rootHashes,
|
||||
set<string> & result)
|
||||
{
|
||||
list<string> workList(rootHashes.begin(), rootHashes.end());
|
||||
set<string> doneSet;
|
||||
|
||||
while (!workList.empty()) {
|
||||
string hash = workList.front();
|
||||
workList.pop_front();
|
||||
|
||||
if (doneSet.find(hash) == doneSet.end()) {
|
||||
doneSet.insert(hash);
|
||||
|
||||
Params pkgImports, fileImports, arguments;
|
||||
readPkgDescr(hash, pkgImports, fileImports, arguments);
|
||||
|
||||
for (Params::iterator it = pkgImports.begin();
|
||||
it != pkgImports.end(); it++)
|
||||
workList.push_back(it->second);
|
||||
}
|
||||
}
|
||||
|
||||
result = doneSet;
|
||||
}
|
||||
|
||||
|
||||
void printClosure(const vector<string> & rootHashes)
|
||||
{
|
||||
set<string> allHashes;
|
||||
computeClosure(rootHashes, allHashes);
|
||||
for (set<string>::iterator it = allHashes.begin();
|
||||
it != allHashes.end(); it++)
|
||||
cout << *it << endl;
|
||||
}
|
||||
|
||||
|
||||
string dotQuote(const string & s)
|
||||
{
|
||||
return "\"" + s + "\"";
|
||||
}
|
||||
|
||||
|
||||
void printGraph(vector<string> rootHashes)
|
||||
{
|
||||
set<string> allHashes;
|
||||
computeClosure(rootHashes, allHashes);
|
||||
|
||||
cout << "digraph G {\n";
|
||||
|
||||
for (set<string>::iterator it = allHashes.begin();
|
||||
it != allHashes.end(); it++)
|
||||
{
|
||||
Params pkgImports, fileImports, arguments;
|
||||
readPkgDescr(*it, pkgImports, fileImports, arguments);
|
||||
|
||||
cout << dotQuote(*it) << "[label = \""
|
||||
<< getFromEnv(arguments, "id")
|
||||
<< "\"];\n";
|
||||
|
||||
for (Params::iterator it2 = pkgImports.begin();
|
||||
it2 != pkgImports.end(); it2++)
|
||||
cout << dotQuote(it2->second) << " -> "
|
||||
<< dotQuote(*it) << ";\n";
|
||||
}
|
||||
|
||||
cout << "}\n";
|
||||
}
|
||||
|
||||
|
||||
void run(vector<string> args)
|
||||
{
|
||||
UsageError argcError("wrong number of arguments");
|
||||
string cmd;
|
||||
|
||||
if (argc < 1)
|
||||
throw UsageError("no command specified");
|
||||
|
||||
cmd = argv[0];
|
||||
argc--, argv++;
|
||||
if (args.size() < 1) throw UsageError("no command specified");
|
||||
|
||||
cmd = args[0];
|
||||
args.erase(args.begin()); // O(n)
|
||||
|
||||
if (cmd == "init") {
|
||||
if (argc != 0) throw argcError;
|
||||
if (args.size() != 0) throw argcError;
|
||||
initDB();
|
||||
} else if (cmd == "verify") {
|
||||
if (argc != 0) throw argcError;
|
||||
if (args.size() != 0) throw argcError;
|
||||
verifyDB();
|
||||
} else if (cmd == "getpkg") {
|
||||
if (argc != 1) throw argcError;
|
||||
string path = getPkg(argv[0]);
|
||||
if (args.size() != 1) throw argcError;
|
||||
string path = getPkg(args[0]);
|
||||
cout << path << endl;
|
||||
} else if (cmd == "run") {
|
||||
if (argc != 1) throw argcError;
|
||||
runPkg(argv[0]);
|
||||
if (args.size() != 1) throw argcError;
|
||||
runPkg(args[0]);
|
||||
} else if (cmd == "ensure") {
|
||||
if (args.size() != 1) throw argcError;
|
||||
ensurePkg(args[0]);
|
||||
} else if (cmd == "regfile") {
|
||||
if (argc != 1) throw argcError;
|
||||
registerFile(argv[0]);
|
||||
if (args.size() != 1) throw argcError;
|
||||
registerFile(args[0]);
|
||||
} else if (cmd == "reginst") {
|
||||
if (argc != 2) throw argcError;
|
||||
registerInstalledPkg(argv[0], argv[1]);
|
||||
if (args.size() != 2) throw argcError;
|
||||
registerInstalledPkg(args[0], args[1]);
|
||||
} else if (cmd == "listinst") {
|
||||
if (argc != 0) throw argcError;
|
||||
if (args.size() != 0) throw argcError;
|
||||
listInstalledPkgs();
|
||||
} else if (cmd == "info") {
|
||||
printInfo(args);
|
||||
} else if (cmd == "closure") {
|
||||
printClosure(args);
|
||||
} else if (cmd == "graph") {
|
||||
printGraph(args);
|
||||
} else
|
||||
throw UsageError("unknown command: " + string(cmd));
|
||||
}
|
||||
|
@ -594,6 +710,20 @@ Subcommands:
|
|||
|
||||
run HASH
|
||||
Run the descriptor referenced by HASH.
|
||||
|
||||
ensure HASH
|
||||
Like getpkg, but if HASH refers to a run descriptor, fetch only
|
||||
the dependencies.
|
||||
|
||||
info HASH...
|
||||
Print information about the specified descriptors.
|
||||
|
||||
closure HASH...
|
||||
Determine the closure of the set of descriptors under the import
|
||||
relation, starting at the given roots.
|
||||
|
||||
graph HASH...
|
||||
Like closure, but print a dot graph specification.
|
||||
";
|
||||
}
|
||||
|
||||
|
@ -630,11 +760,13 @@ void main2(int argc, char * * argv)
|
|||
}
|
||||
}
|
||||
|
||||
argc -= optind, argv += optind;
|
||||
run(argc, argv);
|
||||
vector<string> args;
|
||||
argc--, argv++;
|
||||
while (argc--) args.push_back(*argv++);
|
||||
run(args);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int main(int argc, char * * argv)
|
||||
{
|
||||
prog = argv[0];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue