]> cygwin.com Git - cygwin-apps/setup.git/blame - libsolv.cc
Use solver to check for problems and produce a list of package transactions
[cygwin-apps/setup.git] / libsolv.cc
CommitLineData
1c159e0a
JT
1/*
2 * Copyright (c) 2017 Jon Turney
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * A copy of the GNU General Public License can be found at
10 * http://www.gnu.org/
11 *
12 */
13
14#include "libsolv.h"
15
16#include "solv/solver.h"
17#include "solv/solverdebug.h"
18#include "solv/evr.h"
19
20#include "LogSingleton.h"
1d553f34 21#include <iomanip>
1c159e0a
JT
22
23// ---------------------------------------------------------------------------
24// Utility functions for mapping between Operators and Relation Ids
25// ---------------------------------------------------------------------------
26
27static Id
28Operator2RelId(PackageSpecification::_operators op)
29{
30 switch (op)
31 {
32 case PackageSpecification::Equals:
33 return REL_EQ;
34 case PackageSpecification::LessThan:
35 return REL_LT;
36 case PackageSpecification::MoreThan:
37 return REL_GT;
38 case PackageSpecification::LessThanEquals:
39 return REL_LT | REL_EQ;
40 case PackageSpecification::MoreThanEquals:
41 return REL_GT | REL_EQ;
42 }
43
44 return 0;
45}
46
47static PackageSpecification::_operators
48RelId2Operator(Id id)
49{
50 switch (id)
51 {
52 case REL_EQ:
53 return PackageSpecification::Equals;
54 case REL_LT:
55 return PackageSpecification::LessThan;
56 case REL_GT:
57 return PackageSpecification::MoreThan;
58 case REL_LT | REL_EQ:
59 return PackageSpecification::LessThanEquals;
60 case REL_GT | REL_EQ:
61 return PackageSpecification::MoreThanEquals;
62 }
63
64 return PackageSpecification::Equals;
65}
66
67// ---------------------------------------------------------------------------
68// implements class SolvableVersion
69//
70// a wrapper around a libsolv Solvable
71// ---------------------------------------------------------------------------
72
73const std::string
74SolvableVersion::Name () const
75{
76 Solvable *solvable = pool_id2solvable(pool, id);
77 return std::string(pool_id2str(pool, solvable->name));
78}
79
80const std::string
81SolvableVersion::Canonical_version() const
82{
83 Solvable *solvable = pool_id2solvable(pool, id);
84 return std::string(pool_id2str(pool, solvable->evr));
85}
86
87package_type_t
88SolvableVersion::Type () const
89{
90 Solvable *solvable = pool_id2solvable(pool, id);
91 if (solvable->arch == ARCH_SRC)
92 return package_source;
93 else
94 return package_binary;
95}
96
97const PackageDepends
98SolvableVersion::depends() const
99{
100 Solvable *solvable = pool_id2solvable(pool, id);
101
102 Queue q;
103 queue_init(&q);
104
105 if (repo_lookup_idarray(solvable->repo, id, SOLVABLE_REQUIRES, &q))
106 {
107 // convert
108 PackageDepends dep;
109
110 for (int i = 0; i < q.count; i++)
111 {
112#ifdef DEBUG
113 Log (LOG_PLAIN) << "dep " << std::hex << q.elements[i] << ": " << pool_dep2str(pool, q.elements[i]) << endLog;
114#endif
115
116 const char *name = pool_id2str(pool, q.elements[i]);
117 PackageSpecification *spec = new PackageSpecification (name);
118
119 if (ISRELDEP(id))
120 {
121 Reldep *rd = GETRELDEP(pool, id);
122 spec->setOperator(RelId2Operator(rd->flags));
123 spec->setVersion(pool_id2str(pool, rd->evr));
124 }
125
126 dep.push_back (spec);
127 }
128
129 queue_empty(&q);
130
131 return dep;
132 }
133
134 // otherwise, return an empty depends list
135 static PackageDepends empty_package;
136 return empty_package;
137}
138
139const std::string
140SolvableVersion::SDesc () const
141{
142 Solvable *solvable = pool_id2solvable(pool, id);
143 const char *sdesc = repo_lookup_str(solvable->repo, id, SOLVABLE_SUMMARY);
144 return sdesc;
145}
146
147SolvableVersion
148SolvableVersion::sourcePackage () const
149{
150 if (!id)
151 return SolvableVersion();
152
153 // extract source package id
154 Solvable *solvable = pool_id2solvable(pool, id);
155 Id spkg_attr = pool_str2id(pool, "solvable:sourceid", 1);
156 Id spkg_id = repo_lookup_id(solvable->repo, id, spkg_attr);
157
158 // has no such attribute
159 if (!spkg_id)
160 return SolvableVersion();
161
162 return SolvableVersion(spkg_id, pool);
163}
164
165packagesource *
166SolvableVersion::source() const
167{
168 if (!id) {
169 static packagesource empty_source = packagesource();
170 return &empty_source;
171 }
172
173 Solvable *solvable = pool_id2solvable(pool, id);
174 Id psrc_attr = pool_str2id(pool, "solvable:packagesource", 1);
175 return (packagesource *)repo_lookup_num(solvable->repo, id, psrc_attr, 0);
176}
177
178bool
179SolvableVersion::accessible () const
180{
181 // XXX: accessible if archive is locally available, or we know a mirror
182 //
183 // (This seems utterly pointless. Packages which aren't locally available are
184 // removed from the package list. Packages we don't know a mirror for don't
185 // appear in the packagelist.)
186 return TRUE;
187}
188
189package_stability_t
190SolvableVersion::Stability () const
191{
192 Solvable *solvable = pool_id2solvable(pool, id);
193 Id stability_attr = pool_str2id(pool, "solvable:stability", 1);
194 return (package_stability_t)repo_lookup_num(solvable->repo, id, stability_attr, TRUST_UNKNOWN);
195}
196
197bool
198SolvableVersion::operator <(SolvableVersion const &rhs) const
199{
200 return (compareVersions(*this, rhs) < 0);
201}
202
203bool
204SolvableVersion::operator ==(SolvableVersion const &rhs) const
205{
206 return (compareVersions(*this, rhs) == 0);
207}
208
209bool
210SolvableVersion::operator !=(SolvableVersion const &rhs) const
211{
212 return (compareVersions(*this, rhs) != 0);
213}
214
215int
216SolvableVersion::compareVersions(const SolvableVersion &a,
217 const SolvableVersion &b)
218{
219 if (a.id == b.id)
220 return 0;
221
222 // if a and b are different, at least one of them has a pool
223 Pool *pool = a.pool ? a.pool : b.pool;
224
225 Solvable *sa = a.id ? pool_id2solvable(a.pool, a.id) : NULL;
226 Solvable *sb = b.id ? pool_id2solvable(b.pool, b.id) : NULL;
227
228 // empty versions compare as if their version is the empty string
229 Id evra = sa ? sa->evr : pool_str2id(pool, "", 1);
230 Id evrb = sb ? sb->evr : pool_str2id(pool, "", 1);
231
232 return pool_evrcmp(pool, evra, evrb, EVRCMP_COMPARE);
233}
234
235// ---------------------------------------------------------------------------
236// implements class SolverPool
237//
238// a simplified wrapper for libsolv
239// ---------------------------------------------------------------------------
240
241static
242void debug_callback(Pool *pool, void *data, int type, const char *str)
243{
244 if (type & (SOLV_FATAL|SOLV_ERROR))
245 LogPlainPrintf("libsolv: %s", str);
246 else
247 LogBabblePrintf("libsolv: %s", str);
248}
249
250SolverPool::SolverPool()
251{
252 /* create a pool */
253 pool = pool_create();
254
255 pool_setdebugcallback(pool, debug_callback, NULL);
256
257 int level = 1;
258#if DEBUG
259 level = 3;
260#endif
261 pool_setdebuglevel(pool, level);
262
263 /* create the repo to hold installed packages */
264 SolvRepo *installed = getRepo("_installed");
265 pool_set_installed(pool, installed->repo);
266}
267
268SolvRepo *
269SolverPool::getRepo(const std::string &name, bool test)
270{
271 RepoList::iterator i = repos.find(name);
272 if (i != repos.end())
273 return i->second;
274
275 /* create repo if not found */
276 SolvRepo *r = new(SolvRepo);
277 r->repo = repo_create(pool, name.c_str());
278
279 /* create attribute store, with no local pool */
280 r->data = repo_add_repodata(r->repo, 0);
281
282 /* remember if this is a test stability repo */
283 r->test = test;
284
285 repos[name] = r;
286
287 return r;
288}
289
290/*
291 Helper function to convert a PackageDepends list to libsolv dependencies.
292*/
293Id
294SolverPool::makedeps(Repo *repo, PackageDepends *requires)
295{
296 Id deps = 0;
297
298 for (PackageDepends::iterator i = requires->begin();
299 i != requires->end();
300 i++)
301 {
302 Id name = pool_str2id(pool, (*i)->packageName().c_str(), 1);
303
304 if ((*i)->version().size() == 0)
305 {
306 // no relation, so dependency is just on package name
307 deps = repo_addid_dep(repo, deps, name, 0);
308 }
309 else
310 {
311 // otherwise, dependency is on package name with a version condition
312 Id evr = pool_str2id(pool, (*i)->version().c_str(), 1);
313 int rel = pool_rel2id(pool, name, evr, Operator2RelId((*i)->op()), 1);
314
315 deps = repo_addid_dep(repo, deps, rel, 0);
316 }
317 }
318
319 return deps;
320}
321
322SolvableVersion
323SolverPool::addPackage(const std::string& pkgname, const addPackageData &pkgdata)
324{
325 std::string repoName = pkgdata.reponame;
326 bool test = false;
327
328 /* It's simplest to place test packages into a separate repo, and then
329 arrange for that repo to be disabled, if we don't want to consider
330 those packages */
331
332 if (pkgdata.stability == TRUST_TEST)
333 {
334 repoName = pkgdata.reponame + "_test_";
335 test = true;
336 }
337
338 SolvRepo *r = getRepo(repoName, test);
339 Repo *repo = r->repo;
340
341 /* create a solvable */
342 Id s = repo_add_solvable(repo);
343 Solvable *solvable = pool_id2solvable(pool, s);
344
345 /* initialize solvable for this packageo/version/etc. */
346 solvable->name = pool_str2id(pool, pkgname.c_str(), 1);
347 solvable->arch = (pkgdata.type == package_binary) ? ARCH_ANY : ARCH_SRC;
348 solvable->evr = pool_str2id(repo->pool, pkgdata.version.c_str(), 1);
349 solvable->vendor = pool_str2id(repo->pool, pkgdata.vendor.c_str(), 1);
350 solvable->provides = repo_addid_dep(repo, solvable->provides, pool_rel2id(pool, solvable->name, solvable->evr, REL_EQ, 1), 0);
351 if (pkgdata.requires)
352 solvable->requires = makedeps(repo, pkgdata.requires);
353
354 /* a solvable can also store arbitrary attributes not needed for dependency
355 resolution, if we need them */
356
357 Repodata *data = r->data;
358 Id handle = s;
359#if DEBUG
360 Log (LOG_PLAIN) << "solvable " << s << " name " << pkgname << endLog;
361#endif
362
363 /* store short description attribute */
364 repodata_set_str(data, handle, SOLVABLE_SUMMARY, pkgdata.sdesc.c_str());
365 /* store long description attribute */
366 repodata_set_str(data, handle, SOLVABLE_DESCRIPTION, pkgdata.ldesc.c_str());
367
368 /* store source-package attribute */
369 const std::string sname = pkgdata.spkg.packageName();
370 if (!sname.empty())
371 repodata_set_id(data, handle, SOLVABLE_SOURCENAME, pool_str2id(pool, sname.c_str(), 1));
372 else
373 repodata_set_void(data, handle, SOLVABLE_SOURCENAME);
374 /* solvable:sourceevr may also be available from spkg but assumed to be same
375 as evr for the moment */
376
377 /* store source-package id */
378 /* XXX: this assumes we create install package after source package and so can
379 know that id */
380 Id spkg_attr = pool_str2id(pool, "solvable:sourceid", 1);
381 repodata_set_id(data, handle, spkg_attr, pkgdata.spkg_id.id);
382
383 /* we could store packagesource information as attributes ...
384
385 e.g.
386 size SOLVABLE_DOWNLOADSIZE
387 pathname SOLVABLE_MEDIAFILE
388 site SOLVABLE_MEDIABASE
389 checksum SOLVABLE_CHECKSUM
390
391 ... but for the moment, we just store a pointer to a packagesource object
392 */
393 Id psrc_attr = pool_str2id(pool, "solvable:packagesource", 1);
394 packagesource *psrc = new packagesource(pkgdata.archive);
395 repodata_set_num(data, handle, psrc_attr, (intptr_t)psrc);
396
397 /* store stability level attribute */
398 Id stability_attr = pool_str2id(pool, "solvable:stability", 1);
399 repodata_set_num(data, handle, stability_attr, pkgdata.stability);
400
401#if 0
402 repodata_internalize(data);
403
404 /* debug: verify the attributes we've just set get retrieved correctly */
405 SolvableVersion sv = SolvableVersion(s, pool);
406 const std::string check_sdesc = sv.SDesc();
407 if (pkgdata.sdesc.compare(check_sdesc) != 0) {
408 Log (LOG_PLAIN) << pkgname << " has sdesc mismatch: '" << pkgdata.sdesc << "' and '"
409 << check_sdesc << "'" << endLog;
410 }
411 if (!sname.empty()) {
412 SolvableVersion check_spkg = sv.sourcePackage();
413 Solvable *check_spkg_solvable = pool_id2solvable(pool, check_spkg.id);
414 std::string check_sname = pool_id2str(pool, check_spkg_solvable->name);
415 if (sname.compare(check_sname) != 0) {
416 Log (LOG_PLAIN) << pkgname << " has spkg mismatch: '" << pkgdata.spkg.packageName()
417 << "' and '" << check_sname << "'" << endLog;
418 }
419 }
420 packagesource *check_archive = sv.source();
421 if (check_archive != psrc)
422 Log (LOG_PLAIN) << pkgname << " has archive mismatch: " << psrc
423 << " and " << check_archive << endLog;
424 package_stability_t check_stability = sv.Stability();
425 if (check_stability != pkgdata.stability) {
426 Log (LOG_PLAIN) << pkgname << " has stability mismatch: " << pkgdata.stability
427 << " and " << check_stability << endLog;
428 }
429#endif
430
431 return SolvableVersion(s, pool);
432}
433
434void
435SolverPool::internalize()
436{
437 /* Make attribute data available to queries */
438 for (RepoList::iterator i = repos.begin();
439 i != repos.end();
440 i++)
441 {
442 repodata_internalize(i->second->data);
443 }
444}
1d553f34
JT
445
446void
447SolverPool::use_test_packages(bool use_test_packages)
448{
449 // Only enable repos containing test packages if wanted
450 for (RepoList::iterator i = repos.begin();
451 i != repos.end();
452 i++)
453 {
454 if (i->second->test)
455 {
456 i->second->repo->disabled = !use_test_packages;
457 }
458 }
459}
460
461// ---------------------------------------------------------------------------
462// implements class SolverSolution
463//
464// A wrapper around the libsolv solver
465// ---------------------------------------------------------------------------
466
467SolverSolution::~SolverSolution()
468{
469 if (solv)
470 {
471 solver_free(solv);
472 solv = NULL;
473 }
474}
475
476static
477std::ostream &operator<<(std::ostream &stream,
478 SolverTransaction::transType type)
479{
480 switch (type)
481 {
482 case SolverTransaction::transInstall:
483 stream << "install";
484 break;
485 case SolverTransaction::transErase:
486 stream << "erase";
487 break;
488 default:
489 stream << "unknown";
490 }
491 return stream;
492}
493
494bool
495SolverSolution::update(SolverTasks &tasks, bool update, bool use_test_packages, bool include_source)
496{
497 Log (LOG_PLAIN) << "solving: " << tasks.tasks.size() << " tasks," <<
498 " update: " << (update ? "yes" : "no") << "," <<
499 " use test packages: " << (use_test_packages ? "yes" : "no") << "," <<
500 " include_source: " << (include_source ? "yes" : "no") << endLog;
501
502 pool.use_test_packages(use_test_packages);
503
504 Queue job;
505 queue_init(&job);
506 // solver accepts a queue containing pairs of (cmd, id) tasks
507 // cmd is job and selection flags ORed together
508 for (SolverTasks::taskList::const_iterator i = tasks.tasks.begin();
509 i != tasks.tasks.end();
510 i++)
511 {
512 const SolvableVersion &sv = (*i).first;
513
514 switch ((*i).second)
515 {
516 case SolverTasks::taskInstall:
517 queue_push2(&job, SOLVER_INSTALL | SOLVER_SOLVABLE, sv.id);
518 break;
519 case SolverTasks::taskUninstall:
520 queue_push2(&job, SOLVER_ERASE | SOLVER_SOLVABLE, sv.id);
521 break;
522 case SolverTasks::taskReinstall:
523 // we don't know how to ask solver for this, so we just add the erase
524 // and install later
525 break;
526 default:
527 Log (LOG_PLAIN) << "unknown task " << (*i).second << endLog;
528 }
529 }
530
531 if (update)
532 queue_push2(&job, SOLVER_UPDATE | SOLVER_SOLVABLE_ALL, 0);
533
534 if (!solv)
535 solv = solver_create(pool.pool);
536
537 solver_set_flag(solv, SOLVER_FLAG_ALLOW_VENDORCHANGE, 1);
538 solver_set_flag(solv, SOLVER_FLAG_ALLOW_DOWNGRADE, 0);
539 solver_solve(solv, &job);
540 queue_free(&job);
541
542 int pcnt = solver_problem_count(solv);
543 solver_printdecisions(solv);
544
545 // get transactions for solution
546 Transaction *t = solver_create_transaction(solv);
547 transaction_order(t, 0);
548 transaction_print(t);
549
550 // massage into SolverTransactions form
551 trans.clear();
552
553 for (int i = 0; i < t->steps.count; i++)
554 {
555 Id id = t->steps.elements[i];
556 SolverTransaction::transType tt = type(t, i);
557 trans.push_back(SolverTransaction(SolvableVersion(id, pool.pool), tt));
558 }
559
560 // add install and remove tasks for anything marked as reinstall
561 for (SolverTasks::taskList::const_iterator i = tasks.tasks.begin();
562 i != tasks.tasks.end();
563 i++)
564 {
565 const SolvableVersion &sv = (*i).first;
566
567 if (((*i).second) == SolverTasks::taskReinstall)
568 {
569 trans.push_back(SolverTransaction(SolvableVersion(sv.id, pool.pool),
570 SolverTransaction::transErase));
571 trans.push_back(SolverTransaction(SolvableVersion(sv.id, pool.pool),
572 SolverTransaction::transInstall));
573 }
574 }
575
576 // if include_source mode is on, also install source for everything we are
577 // installing
578 if (include_source)
579 {
580 // (this uses indicies into the vector, as iterators might become
581 // invalidated by doing push_back)
582 size_t n = trans.size();
583 for (size_t i = 0; i < n; i++)
584 {
585 if (trans[i].type == SolverTransaction::transInstall)
586 {
587 SolvableVersion src_version = trans[i].version.sourcePackage();
588 if (src_version)
589 trans.push_back(SolverTransaction(src_version,
590 SolverTransaction::transInstall));
591 }
592 }
593 }
594
595 if (trans.size())
596 {
597 Log (LOG_PLAIN) << "Augmented Transaction List:" << endLog;
598 for (SolverTransactionList::iterator i = trans.begin ();
599 i != trans.end ();
600 ++i)
601 {
602 Log (LOG_PLAIN) << std::setw(4) << std::distance(trans.begin(), i)
603 << std::setw(8) << i->type
604 << std::setw(48) << i->version.Name()
605 << std::setw(20) << i->version.Canonical_version() << endLog;
606 }
607 }
608
609 transaction_free(t);
610
611 return (pcnt == 0);
612}
613
614const SolverTransactionList &
615SolverSolution::transactions() const
616{
617 return trans;
618}
619
620// Construct a string reporting the problems and solutions
621std::string
622SolverSolution::report() const
623{
624 std::string r = "";
625 int pcnt = solver_problem_count(solv);
626 for (Id problem = 1; problem <= pcnt; problem++)
627 {
628 r += "Problem " + std::to_string(problem) + "/" + std::to_string(pcnt);
629 r += "\n";
630
631 Id probr = solver_findproblemrule(solv, problem);
632 Id dep, source, target;
633 SolverRuleinfo type = solver_ruleinfo(solv, probr, &source, &target, &dep);
634 r += solver_problemruleinfo2str(solv, type, source, target, dep);
635 r += "\n";
636
637 int scnt = solver_solution_count(solv, problem);
638 for (Id solution = 1; solution <= scnt; solution++)
639 {
640 r += "Solution " + std::to_string(solution) + "/" + std::to_string(scnt);
641 r += "\n";
642
643 Id p, rp, element;
644 element = 0;
645 while ((element = solver_next_solutionelement(solv, problem, solution, element, &p, &rp)) != 0)
646 {
647 r += " - ";
648 r += solver_solutionelement2str(solv, p, rp);
649 r += "\n";
650 }
651 }
652 }
653 return r;
654}
655
656// helper function to map transaction type
657SolverTransaction::transType
658SolverSolution::type(Transaction *trans, int pos)
659{
660 Id tt = transaction_type(trans, trans->steps.elements[pos],
661 SOLVER_TRANSACTION_SHOW_ACTIVE);
662
663 // if active side of transaction is nothing, ask again for passive side of
664 // transaction
665 if (tt == SOLVER_TRANSACTION_IGNORE)
666 tt = transaction_type(trans, trans->steps.elements[pos], 0);
667
668 switch(tt)
669 {
670 case SOLVER_TRANSACTION_INSTALL:
671 case SOLVER_TRANSACTION_REINSTALL:
672 case SOLVER_TRANSACTION_UPGRADE:
673 case SOLVER_TRANSACTION_DOWNGRADE:
674 return SolverTransaction::transInstall;
675 case SOLVER_TRANSACTION_ERASE:
676 case SOLVER_TRANSACTION_REINSTALLED:
677 case SOLVER_TRANSACTION_UPGRADED:
678 case SOLVER_TRANSACTION_DOWNGRADED:
679 return SolverTransaction::transErase;
680 default:
681 Log (LOG_PLAIN) << "unknown transaction type " << std::hex << tt << endLog;
682 case SOLVER_TRANSACTION_IGNORE:
683 return SolverTransaction::transIgnore;
684 }
685};
This page took 0.082837 seconds and 5 git commands to generate.