diff --git a/src/bin/pkgman/Jamfile b/src/bin/pkgman/Jamfile index f8f93155e8..b752c4a6b6 100644 --- a/src/bin/pkgman/Jamfile +++ b/src/bin/pkgman/Jamfile @@ -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 diff --git a/src/bin/pkgman/command_resolve_build_dependencies.cpp b/src/bin/pkgman/command_resolve_build_dependencies.cpp new file mode 100644 index 0000000000..2ebbb59494 --- /dev/null +++ b/src/bin/pkgman/command_resolve_build_dependencies.cpp @@ -0,0 +1,413 @@ +/* + * Copyright 2013, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Ingo Weinhold + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +//#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 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 \n" + " [ ... ]\n" + "Resolves and lists all packages needed for building a package. Fails, if\n" + "not all dependencies could be resolved.\n" + "\n" + "\n" + " The package info for the package to be built, requiring all build\n" + " requisites.\n" + "\n" + " A text file listing the package's build prerequisites, i.e. the\n" + " requisites that must be installed from the host environment.\n" + "\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" + "\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; +} diff --git a/src/bin/pkgman/pkgman.cpp b/src/bin/pkgman/pkgman.cpp index dc2f9c8912..bfa50d11a1 100644 --- a/src/bin/pkgman/pkgman.cpp +++ b/src/bin/pkgman/pkgman.cpp @@ -33,6 +33,10 @@ static const char* kUsage = " refresh [ ...]\n" " Refreshes all or just the given repositories.\n" "\n" + " resolve-build-dependencies \n" + " [ ... ]\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); diff --git a/src/bin/pkgman/pkgman.h b/src/bin/pkgman/pkgman.h index c5641d2586..088cd23271 100644 --- a/src/bin/pkgman/pkgman.h +++ b/src/bin/pkgman/pkgman.h @@ -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);