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