1/*
2 * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <errno.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
12#include <list>
13#include <map>
14
15#include <package/PackageInfo.h>
16#include <package/PackageResolvable.h>
17#include <package/PackageResolvableExpression.h>
18#include <package/RepositoryCache.h>
19
20
21using namespace BPackageKit;
22
23
24typedef std::list<BPackageResolvable*> ProvidesList;
25
26
27static const char* sProgramName = "update_package_requires";
28
29
30#define DIE(result, msg...)											\
31do {																\
32	fprintf(stderr, "*** " msg);									\
33	fprintf(stderr, " : %s\n", strerror(result));					\
34	exit(5);														\
35} while(0)
36
37
38static void
39print_usage_and_exit(bool error)
40{
41	fprintf(error ? stderr : stdout,
42		"Usage: %s <package info> <repository>\n"
43		"Updates the versions in the \"requires\" list of the given package\n"
44		"info file using the available package information from the given\n"
45		"repository cache file <repository>.\n",
46		sProgramName);
47	exit(error ? 1 : 0);
48}
49
50
51static void
52update_requires_expression(BPackageResolvableExpression& expression,
53	const ProvidesList& providesList)
54{
55	// find the best-matching provides
56	BPackageResolvable* bestProvides = NULL;
57	for (ProvidesList::const_iterator it = providesList.begin();
58		it != providesList.end(); ++it) {
59		BPackageResolvable* provides = *it;
60		if (!expression.Matches(*provides))
61			continue;
62
63		if (bestProvides == NULL || bestProvides->Version().InitCheck() != B_OK
64			|| (provides->Version().InitCheck() == B_OK
65				&& provides->Version().Compare(bestProvides->Version()) > 0)) {
66			bestProvides = provides;
67		}
68	}
69
70	if (bestProvides == NULL || bestProvides->Version().InitCheck() != B_OK)
71		return;
72
73	// Update the expression. Enforce the minimum found version, if the requires
74	// has no version requirement or also a minimum. Otherwise enforce the exact
75	// version found.
76	BPackageResolvableOperator newOperator = B_PACKAGE_RESOLVABLE_OP_EQUAL;
77	switch (expression.Operator()) {
78		case B_PACKAGE_RESOLVABLE_OP_LESS:
79		case B_PACKAGE_RESOLVABLE_OP_LESS_EQUAL:
80		case B_PACKAGE_RESOLVABLE_OP_EQUAL:
81		case B_PACKAGE_RESOLVABLE_OP_NOT_EQUAL:
82			break;
83		case B_PACKAGE_RESOLVABLE_OP_GREATER_EQUAL:
84		case B_PACKAGE_RESOLVABLE_OP_GREATER:
85		case B_PACKAGE_RESOLVABLE_OP_ENUM_COUNT:
86			newOperator = B_PACKAGE_RESOLVABLE_OP_GREATER_EQUAL;
87			break;
88	}
89
90	expression.SetTo(expression.Name(), newOperator, bestProvides->Version());
91}
92
93
94int
95main(int argc, const char* const* argv)
96{
97	if (argc != 3)
98		print_usage_and_exit(true);
99
100	if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)
101		print_usage_and_exit(false);
102
103	const char* const packageInfoPath = argv[1];
104	const char* const repositoryCachePath = argv[2];
105
106	// read the repository cache
107	BRepositoryCache repositoryCache;
108	status_t error = repositoryCache.SetTo(repositoryCachePath);
109	if (error != B_OK) {
110		DIE(error, "failed to read repository cache file \"%s\"",
111			repositoryCachePath);
112	}
113
114	// create a map for all provides (name -> resolvable list)
115	typedef std::map<BString, ProvidesList> ProvidesMap;
116
117	ProvidesMap providesMap;
118
119	for (BRepositoryCache::Iterator it = repositoryCache.GetIterator();
120		const BPackageInfo* info = it.Next();) {
121		const BObjectList<BPackageResolvable>& provides = info->ProvidesList();
122		int32 count = provides.CountItems();
123		for (int32 i = 0; i < count; i++) {
124			BPackageResolvable* resolvable = provides.ItemAt(i);
125			ProvidesList& providesList = providesMap[resolvable->Name()];
126			providesList.push_back(resolvable);
127		}
128	}
129
130	// load the package info
131	BPackageInfo packageInfo;
132	error = packageInfo.ReadFromConfigFile(packageInfoPath);
133	if (error != B_OK)
134		DIE(error, "failed to read package info file \"%s\"", packageInfoPath);
135
136	// clone the package info's requires list
137	typedef std::list<BPackageResolvableExpression> RequiresList;
138	RequiresList requiresList;
139	int32 requiresCount = packageInfo.RequiresList().CountItems();
140	for (int32 i = 0; i < requiresCount; i++)
141		requiresList.push_back(*packageInfo.RequiresList().ItemAt(i));
142
143	// rebuild the requires list with updated versions
144	packageInfo.ClearRequiresList();
145	for (RequiresList::iterator it = requiresList.begin();
146		it != requiresList.end(); ++it) {
147		BPackageResolvableExpression expression = *it;
148		ProvidesMap::iterator foundIt = providesMap.find(expression.Name());
149		if (foundIt != providesMap.end())
150			update_requires_expression(expression, foundIt->second);
151
152		error = packageInfo.AddRequires(expression);
153		if (error != B_OK)
154			DIE(error, "failed to add requires item to package info");
155	}
156
157	// write updated package info
158	BString configString;
159	error = packageInfo.GetConfigString(configString);
160	if (error != B_OK)
161		DIE(error, "failed to get updated package info string");
162
163	FILE* file = fopen(packageInfoPath, "w");
164	if (file == NULL) {
165		DIE(errno, "failed to open package info file \"%s\" for writing",
166			packageInfoPath);
167	}
168
169	if (fwrite(configString.String(), configString.Length(), 1, file) != 1) {
170		DIE(errno, "failed to write updated package info file \"%s\"",
171			packageInfoPath);
172	}
173
174	fclose(file);
175
176	return 0;
177}
178