2017-04-18 13:26:38 +00:00
|
|
|
|
#include <nix/config.h>
|
|
|
|
|
|
2016-08-11 12:41:26 +00:00
|
|
|
|
#include <chrono>
|
|
|
|
|
#include <regex>
|
|
|
|
|
|
|
|
|
|
#include "shared.hh"
|
|
|
|
|
#include "globals.hh"
|
|
|
|
|
#include "eval.hh"
|
|
|
|
|
#include "store-api.hh"
|
|
|
|
|
#include "get-drvs.hh"
|
|
|
|
|
#include "thread-pool.hh"
|
|
|
|
|
#include "sqlite.hh"
|
2017-04-18 14:41:11 +00:00
|
|
|
|
#include "binary-cache-store.hh"
|
2016-11-10 14:49:54 +00:00
|
|
|
|
|
2017-07-06 12:53:52 +00:00
|
|
|
|
#include "file-cache.hh"
|
2016-08-11 12:41:26 +00:00
|
|
|
|
|
|
|
|
|
using namespace nix;
|
|
|
|
|
|
|
|
|
|
static const char * programsSchema = R"sql(
|
|
|
|
|
|
|
|
|
|
create table if not exists Programs (
|
|
|
|
|
name text not null,
|
|
|
|
|
system text not null,
|
|
|
|
|
package text not null,
|
|
|
|
|
primary key (name, system, package)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
)sql";
|
|
|
|
|
|
|
|
|
|
void mainWrapped(int argc, char * * argv)
|
|
|
|
|
{
|
|
|
|
|
initNix();
|
|
|
|
|
initGC();
|
|
|
|
|
|
2017-07-06 12:53:52 +00:00
|
|
|
|
if (argc != 6) throw Error("usage: generate-programs-index CACHE-DB PROGRAMS-DB BINARY-CACHE-URI STORE-PATHS NIXPKGS-PATH");
|
2016-08-11 12:41:26 +00:00
|
|
|
|
|
|
|
|
|
Path cacheDbPath = argv[1];
|
|
|
|
|
Path programsDbPath = argv[2];
|
|
|
|
|
Path storePathsFile = argv[4];
|
|
|
|
|
Path nixpkgsPath = argv[5];
|
|
|
|
|
|
|
|
|
|
settings.readOnlyMode = true;
|
2016-08-22 15:26:27 +00:00
|
|
|
|
settings.showTrace = true;
|
2016-08-11 12:41:26 +00:00
|
|
|
|
|
|
|
|
|
auto localStore = openStore();
|
2016-11-10 14:49:54 +00:00
|
|
|
|
std::string binaryCacheUri = argv[3];
|
|
|
|
|
if (hasSuffix(binaryCacheUri, "/")) binaryCacheUri.pop_back();
|
2017-04-18 14:41:11 +00:00
|
|
|
|
auto binaryCache = openStore(binaryCacheUri).cast<BinaryCacheStore>();
|
2016-08-11 12:41:26 +00:00
|
|
|
|
|
|
|
|
|
/* Get the allowed store paths to be included in the database. */
|
2019-12-30 20:51:21 +00:00
|
|
|
|
auto allowedPaths = binaryCache->parseStorePathSet(tokenizeString<PathSet>(readFile(storePathsFile, true)));
|
2016-08-11 12:41:26 +00:00
|
|
|
|
|
2019-12-30 20:51:21 +00:00
|
|
|
|
StorePathSet allowedPathsClosure;
|
2016-11-10 16:30:12 +00:00
|
|
|
|
binaryCache->computeFSClosure(allowedPaths, allowedPathsClosure);
|
2016-08-11 12:41:26 +00:00
|
|
|
|
|
2019-12-30 20:51:21 +00:00
|
|
|
|
printMsg(lvlInfo, "%d top-level paths, %d paths in closure",
|
|
|
|
|
allowedPaths.size(), allowedPathsClosure.size());
|
2016-08-11 12:41:26 +00:00
|
|
|
|
|
2017-07-06 12:53:52 +00:00
|
|
|
|
FileCache fileCache(cacheDbPath);
|
2016-08-11 12:41:26 +00:00
|
|
|
|
|
|
|
|
|
/* Initialise the programs database. */
|
|
|
|
|
struct ProgramsState
|
|
|
|
|
{
|
|
|
|
|
SQLite db;
|
|
|
|
|
SQLiteStmt insertProgram;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Sync<ProgramsState> programsState_;
|
|
|
|
|
|
2017-04-18 14:41:11 +00:00
|
|
|
|
unlink(programsDbPath.c_str());
|
|
|
|
|
|
2016-08-11 12:41:26 +00:00
|
|
|
|
{
|
|
|
|
|
auto programsState(programsState_.lock());
|
|
|
|
|
|
|
|
|
|
programsState->db = SQLite(programsDbPath);
|
|
|
|
|
programsState->db.exec("pragma synchronous = off");
|
|
|
|
|
programsState->db.exec("pragma main.journal_mode = truncate");
|
|
|
|
|
programsState->db.exec(programsSchema);
|
|
|
|
|
|
|
|
|
|
programsState->insertProgram.create(programsState->db,
|
|
|
|
|
"insert or replace into Programs(name, system, package) values (?, ?, ?)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EvalState state({}, localStore);
|
|
|
|
|
|
|
|
|
|
Value vRoot;
|
2017-04-18 14:41:11 +00:00
|
|
|
|
state.eval(state.parseExprFromFile(resolveExprPath(absPath(nixpkgsPath))), vRoot);
|
2016-08-11 12:41:26 +00:00
|
|
|
|
|
|
|
|
|
/* Get all derivations. */
|
|
|
|
|
DrvInfos packages;
|
|
|
|
|
|
|
|
|
|
for (auto system : std::set<std::string>{"x86_64-linux", "i686-linux"}) {
|
|
|
|
|
auto args = state.allocBindings(2);
|
|
|
|
|
Value * vConfig = state.allocValue();
|
|
|
|
|
state.mkAttrs(*vConfig, 0);
|
|
|
|
|
args->push_back(Attr(state.symbols.create("config"), vConfig));
|
|
|
|
|
Value * vSystem = state.allocValue();
|
|
|
|
|
mkString(*vSystem, system);
|
|
|
|
|
args->push_back(Attr(state.symbols.create("system"), vSystem));
|
|
|
|
|
args->sort();
|
|
|
|
|
getDerivations(state, vRoot, "", *args, packages, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* For each store path, figure out the package with the shortest
|
|
|
|
|
attribute name. E.g. "nix" is preferred over "nixStable". */
|
2019-12-30 20:51:21 +00:00
|
|
|
|
std::map<StorePath, DrvInfo *> packagesByPath;
|
2016-08-11 12:41:26 +00:00
|
|
|
|
|
|
|
|
|
for (auto & package : packages)
|
|
|
|
|
try {
|
|
|
|
|
auto outputs = package.queryOutputs(true);
|
|
|
|
|
|
|
|
|
|
for (auto & output : outputs) {
|
2019-12-30 20:51:21 +00:00
|
|
|
|
auto storePath = binaryCache->parseStorePath(output.second);
|
|
|
|
|
if (!allowedPathsClosure.count(storePath)) continue;
|
|
|
|
|
auto i = packagesByPath.find(storePath);
|
2016-08-11 12:41:26 +00:00
|
|
|
|
if (i != packagesByPath.end() &&
|
|
|
|
|
(i->second->attrPath.size() < package.attrPath.size() ||
|
2019-12-30 20:51:21 +00:00
|
|
|
|
(i->second->attrPath.size() == package.attrPath.size() && i->second->attrPath < package.attrPath)))
|
2016-08-11 12:41:26 +00:00
|
|
|
|
continue;
|
2019-12-30 20:51:21 +00:00
|
|
|
|
packagesByPath.emplace(std::move(storePath), &package);
|
2016-08-11 12:41:26 +00:00
|
|
|
|
}
|
|
|
|
|
} catch (AssertionError & e) {
|
2016-08-22 15:26:27 +00:00
|
|
|
|
} catch (Error & e) {
|
2019-12-30 20:51:21 +00:00
|
|
|
|
e.addPrefix(fmt("in package ‘%s’: ", package.attrPath));
|
2016-08-22 15:26:27 +00:00
|
|
|
|
throw;
|
2016-08-11 12:41:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Note: we don't index hidden files. */
|
|
|
|
|
std::regex isProgram("bin/([^.][^/]*)");
|
|
|
|
|
|
|
|
|
|
/* Process each store path. */
|
|
|
|
|
auto doPath = [&](const Path & storePath, DrvInfo * package) {
|
|
|
|
|
try {
|
2017-07-06 12:53:52 +00:00
|
|
|
|
auto files = fileCache.getFiles(binaryCache, storePath);
|
2016-08-11 12:41:26 +00:00
|
|
|
|
if (files.empty()) return;
|
|
|
|
|
|
|
|
|
|
std::set<std::string> programs;
|
|
|
|
|
|
2017-07-07 14:45:45 +00:00
|
|
|
|
for (auto file : files) {
|
|
|
|
|
|
2016-08-11 12:41:26 +00:00
|
|
|
|
std::smatch match;
|
2017-07-07 14:45:45 +00:00
|
|
|
|
if (!std::regex_match(file.first, match, isProgram)) continue;
|
|
|
|
|
|
|
|
|
|
auto curPath = file.first;
|
|
|
|
|
auto stat = file.second;
|
|
|
|
|
|
|
|
|
|
while (stat.type == FSAccessor::Type::tSymlink) {
|
|
|
|
|
|
|
|
|
|
auto target = canonPath(
|
|
|
|
|
hasPrefix(stat.target, "/")
|
|
|
|
|
? stat.target
|
|
|
|
|
: dirOf(storePath + "/" + curPath) + "/" + stat.target);
|
|
|
|
|
// FIXME: resolve symlinks in components of stat.target.
|
|
|
|
|
|
|
|
|
|
if (!hasPrefix(target, "/nix/store/")) break;
|
|
|
|
|
|
|
|
|
|
/* Assume that symlinks to other store paths point
|
|
|
|
|
to executables. But check symlinks within the
|
|
|
|
|
same store path. */
|
|
|
|
|
if (target.compare(0, storePath.size(), storePath) != 0) {
|
|
|
|
|
stat.type = FSAccessor::Type::tRegular;
|
|
|
|
|
stat.isExecutable = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string sub(target, storePath.size() + 1);
|
|
|
|
|
|
|
|
|
|
auto file2 = files.find(sub);
|
|
|
|
|
if (file2 == files.end()) {
|
|
|
|
|
printError("symlink ‘%s’ has non-existent target ‘%s’",
|
|
|
|
|
storePath + "/" + file.first, stat.target);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
curPath = sub;
|
|
|
|
|
stat = file2->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (stat.type == FSAccessor::Type::tDirectory
|
|
|
|
|
|| stat.type == FSAccessor::Type::tSymlink
|
|
|
|
|
|| (stat.type == FSAccessor::Type::tRegular && !stat.isExecutable))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
programs.insert(match[1]);
|
2016-08-11 12:41:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (programs.empty()) return;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
auto programsState(programsState_.lock());
|
|
|
|
|
SQLiteTxn txn(programsState->db);
|
|
|
|
|
for (auto & program : programs)
|
2017-07-21 10:15:49 +00:00
|
|
|
|
programsState->insertProgram.use()(program)(package->querySystem())(package->attrPath).exec();
|
2016-08-11 12:41:26 +00:00
|
|
|
|
txn.commit();
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-10 16:40:56 +00:00
|
|
|
|
} catch (BadJSON & e) {
|
|
|
|
|
printError("error: in %s (%s): %s", package->attrPath, storePath, e.what());
|
2016-08-11 12:41:26 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Enqueue work items for each package. */
|
2016-11-10 14:49:54 +00:00
|
|
|
|
ThreadPool threadPool(16);
|
2016-08-11 12:41:26 +00:00
|
|
|
|
|
|
|
|
|
for (auto & i : packagesByPath)
|
2019-12-30 20:51:21 +00:00
|
|
|
|
threadPool.enqueue(std::bind(doPath, binaryCache->printStorePath(i.first), i.second));
|
2016-08-11 12:41:26 +00:00
|
|
|
|
|
|
|
|
|
threadPool.process();
|
2016-11-10 17:27:04 +00:00
|
|
|
|
|
|
|
|
|
/* Vacuum programs.sqlite to make it smaller. */
|
|
|
|
|
{
|
|
|
|
|
auto programsState(programsState_.lock());
|
|
|
|
|
programsState->db.exec("vacuum");
|
|
|
|
|
}
|
2016-08-11 12:41:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main(int argc, char * * argv)
|
|
|
|
|
{
|
|
|
|
|
return handleExceptions(argv[0], [&]() {
|
|
|
|
|
mainWrapped(argc, argv);
|
|
|
|
|
});
|
|
|
|
|
}
|