1/*
2 * Copyright 2013, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Ingo Weinhold <ingo_weinhold@gmx.de>
7 */
8
9
10#include <errno.h>
11#include <getopt.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <sys/stat.h>
15
16#include <package/manager/RepositoryBuilder.h>
17#include <package/solver/SolverPackageSpecifier.h>
18#include <package/solver/SolverPackageSpecifierList.h>
19#include <package/solver/SolverProblem.h>
20#include <package/solver/SolverResult.h>
21
22#include <AutoDeleter.h>
23
24#include "Command.h"
25#include "pkgman.h"
26
27
28// TODO: internationalization!
29
30
31using namespace BPackageKit;
32using BManager::BPrivate::BPackagePathMap;
33using BManager::BPrivate::BRepositoryBuilder;
34
35
36static const char* const kShortUsage =
37	"  %command% <package> ... <repository> [ <priority> ] ...\n"
38	"    Resolves all packages the given packages depend on.\n";
39
40static const char* const kLongUsage =
41	"Usage: %program% %command% <package> ... <repository> [ <priority> ] ...\n"
42	"Resolves and lists all packages the given packages depend on. Fails, if\n"
43	"not all dependencies could be resolved.\n"
44	"\n"
45	"Options:\n"
46	"  --debug <level>\n"
47	"    Print debug output. <level> should be between 0 (no debug output,\n"
48	"    the default) and 10 (most debug output).\n"
49	"\n"
50	"Arguments:\n"
51	"  <package>\n"
52	"    The HPKG or package info file of the package for which the\n"
53	"    dependencies shall be resolved. Multiple files can be specified.\n"
54	"  <repository>\n"
55	"    Path to a directory containing packages from which the package's\n"
56	"    dependencies shall be resolved. Multiple directories can be\n"
57	"    specified.\n"
58	"  <priority>\n"
59	"    Can follow a <repository> to specify the priority of that\n"
60	"    repository. The default priority is 0.\n"
61	"\n";
62
63
64DEFINE_COMMAND(ResolveDependenciesCommand, "resolve-dependencies", kShortUsage,
65	kLongUsage, COMMAND_CATEGORY_OTHER)
66
67
68static void
69check_problems(BSolver* solver, const char* errorContext)
70{
71	if (solver->HasProblems()) {
72		fprintf(stderr,
73			"Encountered problems %s:\n", errorContext);
74
75		int32 problemCount = solver->CountProblems();
76		for (int32 i = 0; i < problemCount; i++) {
77			printf("  %" B_PRId32 ": %s\n", i + 1,
78				solver->ProblemAt(i)->ToString().String());
79		}
80		exit(1);
81	}
82}
83
84
85static void
86verify_result(const BSolverResult& result,
87	const BPackagePathMap& specifiedPackagePaths)
88{
89	// create the solver
90	BSolver* solver;
91	status_t error = BSolver::Create(solver);
92	if (error != B_OK)
93		DIE(error, "failed to create solver");
94
95	// Add an installation repository and add all of the result packages save
96	// the specified packages.
97	BSolverRepository installation;
98	BRepositoryBuilder installationBuilder(installation, "installation");
99
100	for (int32 i = 0; const BSolverResultElement* element = result.ElementAt(i);
101			i++) {
102		BSolverPackage* package = element->Package();
103		if (specifiedPackagePaths.find(package) == specifiedPackagePaths.end())
104			installationBuilder.AddPackage(package->Info());
105	}
106	installationBuilder.AddToSolver(solver, true);
107
108	// resolve
109	error = solver->VerifyInstallation();
110	if (error != B_OK)
111		DIE(error, "failed to verify computed package dependencies");
112
113	check_problems(solver, "verifying computed package dependencies");
114}
115
116
117int
118ResolveDependenciesCommand::Execute(int argc, const char* const* argv)
119{
120	while (true) {
121		static struct option sLongOptions[] = {
122			{ "debug", required_argument, 0, OPTION_DEBUG },
123			{ "help", no_argument, 0, 'h' },
124			{ 0, 0, 0, 0 }
125		};
126
127		opterr = 0; // don't print errors
128		int c = getopt_long(argc, (char**)argv, "h", sLongOptions, NULL);
129		if (c == -1)
130			break;
131
132		if (fCommonOptions.HandleOption(c))
133			continue;
134
135		switch (c) {
136			case 'h':
137				PrintUsageAndExit(false);
138				break;
139
140			default:
141				PrintUsageAndExit(true);
142				break;
143		}
144	}
145
146	// The remaining arguments are the package (info) files and the repository
147	// directories (at least one), optionally with priorities.
148	if (argc < optind + 2)
149		PrintUsageAndExit(true);
150
151	// Determine where the package list ends and the repository list starts.
152	const char* const* specifiedPackages = argv + optind;
153	for (; optind < argc; optind++) {
154		const char* path = argv[optind];
155		struct stat st;
156		if (stat(path, &st) != 0)
157			DIE(errno, "failed to stat() \"%s\"", path);
158
159		if (S_ISDIR(st.st_mode))
160			break;
161	}
162
163	const char* const* repositoryDirectories = argv + optind;
164	int repositoryDirectoryCount = argc - optind;
165	int specifiedPackageCount = repositoryDirectories - specifiedPackages;
166
167	// create the solver
168	BSolver* solver;
169	status_t error = BSolver::Create(solver);
170	if (error != B_OK)
171		DIE(error, "failed to create solver");
172
173	solver->SetDebugLevel(fCommonOptions.DebugLevel());
174
175	// add repositories
176	BPackagePathMap packagePaths;
177	BObjectList<BSolverRepository> repositories(10, true);
178	int32 repositoryIndex = 0;
179	for (int i = 0; i < repositoryDirectoryCount; i++, repositoryIndex++) {
180		const char* directoryPath = repositoryDirectories[i];
181
182		BSolverRepository* repository = new(std::nothrow) BSolverRepository;
183		if (repository == NULL || !repositories.AddItem(repository))
184			DIE(B_NO_MEMORY, "failed to create repository");
185
186
187		if (i + 1 < repositoryDirectoryCount) {
188			char* end;
189			long priority = strtol(repositoryDirectories[i + 1], &end, 0);
190			if (*end == '\0') {
191				repository->SetPriority((uint8)priority);
192				i++;
193			}
194		}
195
196		BRepositoryBuilder(*repository,
197				BString("repository") << repositoryIndex)
198			.SetPackagePathMap(&packagePaths)
199			.AddPackagesDirectory(directoryPath)
200			.AddToSolver(solver);
201	}
202
203	// add a repository with only the specified packages
204	BPackagePathMap specifiedPackagePaths;
205	BSolverRepository dummyRepository;
206	{
207		BRepositoryBuilder builder(dummyRepository, "dummy",
208				"specified packages");
209		builder.SetPackagePathMap(&specifiedPackagePaths);
210
211		for (int i = 0; i < specifiedPackageCount; i++)
212			builder.AddPackage(specifiedPackages[i]);
213
214		builder.AddToSolver(solver);
215	}
216
217	// resolve
218	BSolverPackageSpecifierList packagesToInstall;
219	for (BPackagePathMap::const_iterator it = specifiedPackagePaths.begin();
220		it != specifiedPackagePaths.end(); ++it) {
221		if (!packagesToInstall.AppendSpecifier(it->first))
222			DIE(B_NO_MEMORY, "failed to add specified package");
223	}
224
225	error = solver->Install(packagesToInstall);
226	if (error != B_OK)
227		DIE(error, "failed to resolve package dependencies");
228
229	check_problems(solver, "resolving package dependencies");
230
231	BSolverResult result;
232	error = solver->GetResult(result);
233	if (error != B_OK)
234		DIE(error, "failed to resolve package dependencies");
235
236	// Verify that the resolved packages don't depend on the specified package.
237	verify_result(result, specifiedPackagePaths);
238
239	// print packages
240	for (int32 i = 0; const BSolverResultElement* element = result.ElementAt(i);
241			i++) {
242		// skip the specified package
243		BSolverPackage* package = element->Package();
244		if (specifiedPackagePaths.find(package) != specifiedPackagePaths.end())
245			continue;
246
247		// resolve and print the path
248		BPackagePathMap::const_iterator it = packagePaths.find(package);
249		if (it == packagePaths.end()) {
250			DIE(B_ERROR, "ugh, no package %p (%s-%s) not in package path map",
251				package,  package->Info().Name().String(),
252				package->Info().Version().ToString().String());
253		}
254
255		printf("%s\n", it->second.String());
256	}
257
258	return 0;
259}
260