pkgman: Add command resolve-build-dependencies

This is a service command for haikuporter. It resolves the dependencies
for a package to be built and lists them, so that haikuporter can
prepare the build environment accordingly.

The implementation isn't quite finished yet. Currently the packages are
printed only with name and version, while it would probably be more
helpful to print the path of the package file. Also, the package itself
(respectively a dummy package) is printed as well.
This commit is contained in:
Ingo Weinhold 2013-04-03 02:32:50 +00:00
parent 1a4d020daf
commit 1acf2be80d
4 changed files with 423 additions and 2 deletions

View File

@ -1,10 +1,11 @@
SubDir HAIKU_TOP src bin pkgman ;
UsePrivateHeaders support ;
UsePrivateHeaders shared support ;
BinCommand pkgman :
command_add_repo.cpp
command_drop_repo.cpp
command_resolve_build_dependencies.cpp
command_list_repos.cpp
command_refresh.cpp
DecisionProvider.cpp

View File

@ -0,0 +1,413 @@
/*
* Copyright 2013, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Ingo Weinhold <ingo_weinhold@gmx.de>
*/
#include <dirent.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <Entry.h>
#include <File.h>
#include <Path.h>
#include <package/PackageInfo.h>
#include <package/solver/Solver.h>
#include <package/solver/SolverPackage.h>
#include <package/solver/SolverPackageSpecifier.h>
#include <package/solver/SolverPackageSpecifierList.h>
#include <package/solver/SolverProblem.h>
#include <package/solver/SolverRepository.h>
#include <package/solver/SolverResult.h>
#include <AutoDeleter.h>
//#include "DecisionProvider.h"
//#include "JobStateListener.h"
#include "pkgman.h"
using namespace BPackageKit;
// TODO: internationalization!
struct PackageInfoErrorListener : public BPackageInfo::ParseErrorListener {
public:
PackageInfoErrorListener(const BString& errorContext)
:
fErrorContext(errorContext)
{
}
virtual void OnError(const BString& message, int line, int column)
{
fprintf(stderr, "%s: Parse error in line %d:%d: %s\n",
fErrorContext.String(), line, column, message.String());
}
private:
BString fErrorContext;
};
struct RepositoryBuilder {
RepositoryBuilder(BSolverRepository& repository, const BString& name,
const BString& errorName = BString())
:
fRepository(repository),
fErrorName(errorName.IsEmpty() ? name : errorName)
{
status_t error = fRepository.SetTo(name);
if (error != B_OK)
DIE(error, "failed to init %s repository", fErrorName.String());
}
RepositoryBuilder& AddPackage(const BPackageInfo& info,
const char* packageErrorName = NULL)
{
status_t error = fRepository.AddPackage(info);
if (error != B_OK) {
DIE(error, "failed to add %s to %s repository",
packageErrorName != NULL
? packageErrorName
: (BString("package ") << info.Name()).String(),
fErrorName.String());
}
return *this;
}
RepositoryBuilder& AddPackages(BPackageInstallationLocation location,
const char* locationErrorName)
{
status_t error = fRepository.AddPackages(location);
if (error != B_OK) {
DIE(error, "failed to add %s packages to %s repository",
locationErrorName, fErrorName.String());
}
return *this;
}
RepositoryBuilder& AddPackagesDirectory(const char* path)
{
// open directory
DIR* dir = opendir(path);
if (dir == NULL)
DIE(errno, "failed to open package directory \"%s\"", path);
CObjectDeleter<DIR, int> dirCloser(dir, &closedir);
// iterate through directory entries
while (dirent* entry = readdir(dir)) {
// skip "." and ".."
const char* name = entry->d_name;
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
continue;
// stat() the entry and skip any non-file
BPath entryPath;
status_t error = entryPath.SetTo(path, name);
if (error != B_OK)
DIE(errno, "failed to construct path");
struct stat st;
if (lstat(entryPath.Path(), &st) != 0)
DIE(errno, "failed to stat() %s", entryPath.Path());
if (!S_ISREG(st.st_mode))
continue;
// read a package info from the (HPKG or package info) file
BPackageInfo packageInfo;
size_t nameLength = strlen(name);
if (nameLength > 5 && strcmp(name + nameLength - 5, ".hpkg") == 0) {
// a package file
error = packageInfo.ReadFromPackageFile(entryPath.Path());
} else {
// a package info file (supposedly)
PackageInfoErrorListener errorListener("reading package info");
error = packageInfo.ReadFromConfigFile(BEntry(entryPath.Path()),
&errorListener);
}
if (error != B_OK) {
DIE(errno, "failed to read package info from \"%s\"",
entryPath.Path());
}
AddPackage(packageInfo, entryPath.Path());
}
return *this;
}
RepositoryBuilder& AddToSolver(BSolver* solver, bool isInstalled = false)
{
fRepository.SetInstalled(isInstalled);
status_t error = solver->AddRepository(&fRepository);
if (error != B_OK) {
DIE(error, "failed to add %s repository to solver",
fErrorName.String());
}
return *this;
}
private:
BSolverRepository& fRepository;
BString fErrorName;
};
static const char* kCommandUsage =
"Usage: %s resolve-build-dependencies <package> <prerequisites>\n"
" <build-repository> [ <prerequisite-repository> ... ]\n"
"Resolves and lists all packages needed for building a package. Fails, if\n"
"not all dependencies could be resolved.\n"
"\n"
"<package>\n"
" The package info for the package to be built, requiring all build\n"
" requisites.\n"
"<prerequisites>\n"
" A text file listing the package's build prerequisites, i.e. the\n"
" requisites that must be installed from the host environment.\n"
"<build-repository>\n"
" Path to a directory containing package infos for all the packages that\n"
" can be built. This repository is used to resolve the build requisites\n"
" the package to build.\n"
"<prerequisite-repository>\n"
" Path to a directory containing packages from which the package's\n"
" prerequisites shall be resolved. Multiple directories can be\n"
" specified. If none is given, the system's installed packages are used.\n"
"\n"
;
static const char* kPrerequisitesPackageInfoTemplate =
"name _build_prerequisites_\n"
"version 1.0.0-1\n"
"architecture %s\n"
"summary none\n"
"description none\n"
"packager none\n"
"vendor \"%s\"\n"
"licenses {\n"
" MIT\n"
"}\n"
"copyrights {\n"
" none\n"
"}\n"
"provides {\n"
" _build_prerequisites_ = 1.0.0\n"
"}\n"
"requires {\n %s }\n";
static void
print_command_usage_and_exit(bool error)
{
fprintf(error ? stderr : stdout, kCommandUsage, kProgramName);
exit(error ? 1 : 0);
}
static void
resolve_packages(BSolver* solver, const BPackageInfo& packageInfo,
bool isPrerequisitePhase, BSolverResult& _result)
{
const char* requisitesString = isPrerequisitePhase
? "prerequisites" :"requisites";
// add a repository with the build package resolvable
BSolverRepository dummyRepository;
RepositoryBuilder(dummyRepository, "dummy", "build package")
.AddPackage(packageInfo, "package to install")
.AddToSolver(solver);
// resolve
BSolverPackageSpecifierList packagesToInstall;
if (!packagesToInstall.AppendSpecifier(
BSolverPackageSpecifier(&dummyRepository,
BPackageResolvableExpression(packageInfo.Name())))) {
DIE(B_NO_MEMORY, "failed to add package to install");
}
status_t error = solver->Install(packagesToInstall);
if (error != B_OK)
DIE(error, "failed to resolve %s", requisitesString);
if (solver->HasProblems()) {
fprintf(stderr, "Encountered problems resolving %s:\n",
requisitesString);
int32 problemCount = solver->CountProblems();
for (int32 i = 0; i < problemCount; i++) {
printf(" %" B_PRId32 ": %s\n", i + 1,
solver->ProblemAt(i)->ToString().String());
}
exit(1);
}
BSolverResult& result = _result;
error = solver->GetResult(result);
if (error != B_OK)
DIE(error, "failed to resolve %s", requisitesString);
// print packages
printf("[%s]\n", requisitesString);
for (int32 i = 0; const BSolverResultElement* element = result.ElementAt(i);
i++) {
// TODO: Print the path to the package/package info!
printf("%s-%s\n",
element->Package()->Info().Name().String(),
element->Package()->Info().Version().ToString().String());
// TODO: Filter out the given package!
}
}
int
command_resolve_build_dependencies(int argc, const char* const* argv)
{
while (true) {
static struct option sLongOptions[] = {
{ "help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
opterr = 0; // don't print errors
int c = getopt_long(argc, (char**)argv, "hu", sLongOptions, NULL);
if (c == -1)
break;
switch (c) {
case 'h':
print_command_usage_and_exit(false);
break;
default:
print_command_usage_and_exit(true);
break;
}
}
// The remaining arguments are the build package info, the prerequisites file,
// the build requisites directory, and zero or more prerequisites
// directories.
if (argc < optind + 3)
print_command_usage_and_exit(true);
const char* buildPackagePath = argv[optind++];
const char* prerequisitesPath = argv[optind++];
const char* requisitesDirectoryPath = argv[optind++];
int prerequisitesDirectoryPathCount = argc - optind;
const char* const* prerequisitesDirectoryPaths
= prerequisitesDirectoryPathCount > 0 ? argv + optind : NULL;
if (prerequisitesDirectoryPaths != NULL) {
DIE(B_NOT_SUPPORTED,
"sorry, prerequisite directory repositories not supported yet");
// TODO: Support prerequisite directory repositories!
}
// read build package info
BEntry buildPackageEntry(buildPackagePath);
BPackageInfo buildPackageInfo;
PackageInfoErrorListener buildPackageInfoErrorListener(
"Error: Failed to parse package info");
status_t error = buildPackageInfo.ReadFromConfigFile(buildPackageEntry,
&buildPackageInfoErrorListener);
if (error != B_OK)
DIE(error, "failed to read build package info file");
// read the prerequisites file into a string
BString prerequisitesString;
BFile prerequisitesFile;
error = prerequisitesFile.SetTo(prerequisitesPath, B_READ_ONLY);
if (error != B_OK)
DIE(error, "failed to open prerequisites file");
off_t fileSize;
error = prerequisitesFile.GetSize(&fileSize);
if (error != B_OK)
DIE(error, "failed to get prerequisites file size");
if (char* buffer = prerequisitesString.LockBuffer(fileSize)) {
ssize_t bytesRead = prerequisitesFile.Read(buffer, fileSize);
if (bytesRead != fileSize) {
DIE(bytesRead < 0 ? (status_t)bytesRead :B_ERROR,
"failed to read prerequisites");
}
} else
DIE(B_NO_MEMORY, "failed to read prerequisites");
prerequisitesString.UnlockBuffer();
prerequisitesFile.Unset();
// create a package info that contains the build prerequisites
BPackageInfo prerequisitesPackageInfo;
PackageInfoErrorListener buildPrerequisitesPackageInfoErrorListener(
"Error: Failed to parse prerequisites package info");
error = prerequisitesPackageInfo.ReadFromConfigString(
BString().SetToFormat(kPrerequisitesPackageInfoTemplate,
BPackageInfo::kArchitectureNames[buildPackageInfo.Architecture()],
buildPackageInfo.Vendor().String(),
prerequisitesString.String()),
&buildPrerequisitesPackageInfoErrorListener);
if (error != B_OK)
DIE(error, "failed to create prerequisites package info");
// resolve the prerequisites
// create the solver
BSolver* solver;
error = BSolver::Create(solver);
if (error != B_OK)
DIE(error, "failed to create solver");
// add prerequisite repository
BSolverRepository prerequisiteRepository;
RepositoryBuilder(prerequisiteRepository, "prerequisites")
.AddPackages(B_PACKAGE_INSTALLATION_LOCATION_SYSTEM, "system")
.AddPackages(B_PACKAGE_INSTALLATION_LOCATION_COMMON, "common")
.AddToSolver(solver);
BSolverResult result;
resolve_packages(solver, prerequisitesPackageInfo, true, result);
// resolve the requisites
// reset the solver
error = solver->Init();
if (error != B_OK)
DIE(error, "failed to re-init solver");
// add an installation repository with the resolved prerequisites
BSolverRepository installationRepository;
RepositoryBuilder installationRepositoryBuilder(installationRepository,
"installation");
for (int32 i = 0; const BSolverResultElement* element = result.ElementAt(i);
i++) {
installationRepositoryBuilder.AddPackage(element->Package()->Info());
}
installationRepositoryBuilder.AddToSolver(solver, true);
// add requisite repository
BSolverRepository requisiteRepository;
RepositoryBuilder(requisiteRepository, "requisites")
.AddPackagesDirectory(requisitesDirectoryPath)
.AddToSolver(solver);
resolve_packages(solver, buildPackageInfo, false, result);
// TODO: We need to make sure that the package isn't part of a cyclic
// dependency.
return 0;
}

View File

@ -33,6 +33,10 @@ static const char* kUsage =
" refresh [<repo-name> ...]\n"
" Refreshes all or just the given repositories.\n"
"\n"
" resolve-build-dependencies <package> <prerequisites>\n"
" <build-repository> [ <prerequisite-repository> ... ]\n"
" Resolves all packages needed for building a package.\n"
"\n"
"Common Options:\n"
" -h, --help - Print this usage info.\n"
;
@ -56,6 +60,9 @@ main(int argc, const char* const* argv)
if (strncmp(command, "add-r", 5) == 0)
return command_add_repo(argc - 1, argv + 1);
if (strcmp(command, "list-build-dependency-packages") == 0)
return command_resolve_build_dependencies(argc - 1, argv + 1);
if (strncmp(command, "drop-r", 6) == 0)
return command_drop_repo(argc - 1, argv + 1);

View File

@ -34,8 +34,8 @@ do { \
void print_usage_and_exit(bool error);
int command_add_repo(int argc, const char* const* argv);
int command_resolve_build_dependencies(int argc, const char* const* argv);
int command_drop_repo(int argc, const char* const* argv);
int command_list_repos(int argc, const char* const* argv);
int command_refresh(int argc, const char* const* argv);