From fd2c0dc9707714d82580dc61854efc06335e6091 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 6 Oct 2015 08:43:01 +0200 Subject: Track prerequisite packages, handle in pkg-{con,dis}figure --- bpkg/buildfile | 44 ++++++++++----------- bpkg/package | 43 +++++++++++++++++++++ bpkg/package.xml | 24 ++++++++++++ bpkg/pkg-configure.cxx | 57 ++++++++++++++++++++++++++++ bpkg/pkg-disfigure.cxx | 31 ++++++++++++++- bpkg/pkg-fetch.cxx | 3 +- bpkg/pkg-unpack.cxx | 3 +- bpkg/satisfaction | 39 +++++++++++++++++++ bpkg/satisfaction.cxx | 101 +++++++++++++++++++++++++++++++++++++++++++++++++ bpkg/test.sh | 67 ++++++++++++++++++++++++++++++++ 10 files changed, 387 insertions(+), 25 deletions(-) create mode 100644 bpkg/satisfaction create mode 100644 bpkg/satisfaction.cxx (limited to 'bpkg') diff --git a/bpkg/buildfile b/bpkg/buildfile index e9f00f2..12bb391 100644 --- a/bpkg/buildfile +++ b/bpkg/buildfile @@ -9,28 +9,28 @@ import libs += libbutl%lib{butl} import libs += libodb%lib{odb} import libs += libodb-sqlite%lib{odb-sqlite} -exe{bpkg}: cxx{fetch package package-odb manifest-utility database \ - diagnostics utility} \ - cli.cxx{common-options} cxx{types-parsers} \ - cxx{bpkg} cli.cxx{bpkg-options} \ - cxx{help} cli.cxx{help-options} \ - cli.cxx{configuration-options} \ - cxx{build} cli.cxx{build-options} \ - cxx{pkg-command} \ - cxx{pkg-verify} cli.cxx{pkg-verify-options} \ - cxx{pkg-status} cli.cxx{pkg-status-options} \ - cxx{pkg-fetch} cli.cxx{pkg-fetch-options} \ - cxx{pkg-unpack} cli.cxx{pkg-unpack-options} \ - cxx{pkg-purge} cli.cxx{pkg-purge-options} \ - cxx{pkg-configure} cli.cxx{pkg-configure-options} \ - cxx{pkg-disfigure} cli.cxx{pkg-disfigure-options} \ - cli.cxx{pkg-update-options} \ - cli.cxx{pkg-clean-options} \ - cxx{cfg-create} cli.cxx{cfg-create-options} \ - cxx{rep-add} cli.cxx{rep-add-options} \ - cxx{rep-fetch} cli.cxx{rep-fetch-options} \ - cxx{rep-info} cli.cxx{rep-info-options} \ - cxx{rep-create} cli.cxx{rep-create-options} \ +exe{bpkg}: cxx{satisfaction fetch package package-odb manifest-utility \ + database diagnostics utility} \ + cli.cxx{common-options} cxx{types-parsers} \ + cxx{bpkg} cli.cxx{bpkg-options} \ + cxx{help} cli.cxx{help-options} \ + cli.cxx{configuration-options} \ + cxx{build} cli.cxx{build-options} \ + cxx{pkg-command} \ + cxx{pkg-verify} cli.cxx{pkg-verify-options} \ + cxx{pkg-status} cli.cxx{pkg-status-options} \ + cxx{pkg-fetch} cli.cxx{pkg-fetch-options} \ + cxx{pkg-unpack} cli.cxx{pkg-unpack-options} \ + cxx{pkg-purge} cli.cxx{pkg-purge-options} \ + cxx{pkg-configure} cli.cxx{pkg-configure-options} \ + cxx{pkg-disfigure} cli.cxx{pkg-disfigure-options} \ + cli.cxx{pkg-update-options} \ + cli.cxx{pkg-clean-options} \ + cxx{cfg-create} cli.cxx{cfg-create-options} \ + cxx{rep-add} cli.cxx{rep-add-options} \ + cxx{rep-fetch} cli.cxx{rep-fetch-options} \ + cxx{rep-info} cli.cxx{rep-info-options} \ + cxx{rep-create} cli.cxx{rep-create-options} \ $libs # Option length must be the same to get commands/topics/options aligned. diff --git a/bpkg/package b/bpkg/package index 419acdc..04cb821 100644 --- a/bpkg/package +++ b/bpkg/package @@ -387,15 +387,58 @@ namespace bpkg // optional out_root; + // A map of "effective" prerequisites (i.e., pointers to other + // selected packages) to optional dependency constraint. + // + using prerequisites_type = std::map, + optional, + compare_lazy_ptr>; + prerequisites_type prerequisites; + // Database mapping. // #pragma db member(name) id + #pragma db member(prerequisites) id_column("package") \ + key_column("prerequisite") value_column("") key_not_null + private: friend class odb::access; package () = default; }; + // Return a list of packages that depend on this package along with + // their constraints. + // + /* + #pragma db view object(package) \ + container(package::prerequisites = pp inner: pp.key) + struct package_dependents + { + #pragma db column(pp.id) + string name; + + #pragma db column(pp.value) + optional constraint; + }; + */ + + // @@ Using raw container table since ODB doesn't support containers + // in views yet. + // + #pragma db view object(package) \ + table("package_prerequisites" = "pp" inner: \ + "pp.prerequisite = " + package::name) + struct package_dependents + { + #pragma db column("pp.package") + string name; + + #pragma db column("pp.") + optional constraint; + }; + + // Version comparison operators. // // They allow comparing objects that have epoch, canonical_upstream, diff --git a/bpkg/package.xml b/bpkg/package.xml index 89ec0af..9935e06 100644 --- a/bpkg/package.xml +++ b/bpkg/package.xml @@ -169,5 +169,29 @@ + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/bpkg/pkg-configure.cxx b/bpkg/pkg-configure.cxx index 169772c..208fad4 100644 --- a/bpkg/pkg-configure.cxx +++ b/bpkg/pkg-configure.cxx @@ -10,7 +10,9 @@ #include #include #include +#include +#include #include using namespace std; @@ -49,6 +51,7 @@ namespace bpkg database db (open (c, trace)); transaction t (db.begin ()); + session s; shared_ptr p (db.find (n)); @@ -73,6 +76,60 @@ namespace bpkg level4 ([&]{trace << "src_root: " << src_root << ", " << "out_root: " << out_root;}); + // Verify all our prerequisites are configured and populate the + // prerequisites list. + // + { + assert (p->prerequisites.empty ()); + + package_manifest m (pkg_verify (src_root)); + + for (const dependency_alternatives& da: m.dependencies) + { + assert (!da.conditional); //@@ TODO + + bool satisfied (false); + for (const dependency& d: da) + { + if (shared_ptr dp = db.find (d.name)) + { + if (dp->state != state::configured) + continue; + + if (!satisfies (dp->version, d.constraint)) + continue; + + auto r (p->prerequisites.emplace (dp, d.constraint)); + + // If we already have a dependency on this package, pick the + // stricter of the two constraints. + // + if (!r.second) + { + auto& c (r.first->second); + + bool s1 (satisfies (c, d.constraint)); + bool s2 (satisfies (d.constraint, c)); + + if (!s1 && !s2) + fail << "incompatible constraints " + << "(" << d.name << " " << *c << ") and " + << "(" << d.name << " " << *d.constraint << ")"; + + if (s2 && !s1) + c = d.constraint; + } + + satisfied = true; + break; + } + } + + if (!satisfied) + fail << "no configured package satisfies dependency on " << da; + } + } + // Form the buildspec. // string bspec; diff --git a/bpkg/pkg-disfigure.cxx b/bpkg/pkg-disfigure.cxx index 6c357f4..a3d0336 100644 --- a/bpkg/pkg-disfigure.cxx +++ b/bpkg/pkg-disfigure.cxx @@ -21,11 +21,40 @@ namespace bpkg transaction& t, const shared_ptr& p) { + assert (p->state == state::configured || p->state == state::broken); + tracer trace ("pkg_disfigure"); database& db (t.database ()); tracer_guard tg (db, trace); + // Check that we have no dependents. + // + if (p->state == state::configured) + { + using query = query; + + auto r (db.query (query::name == p->name)); + + if (!r.empty ()) + { + diag_record dr; + dr << fail << "package " << p->name << " still has dependencies:"; + + for (const package_dependents& pd: r) + { + dr << info << "package " << pd.name; + + if (pd.constraint) + dr << " on " << p->name << " " << *pd.constraint; + } + } + } + + // Since we are no longer configured, clear the prerequisites list. + // + p->prerequisites.clear (); + // Calculate package's src_root and out_root. // assert (p->src_root); // Must be set since unpacked. @@ -43,7 +72,7 @@ namespace bpkg // string bspec; - if (p->state != state::broken) + if (p->state == state::configured) { bspec = "clean(" + out_root.string () + "/) " "disfigure(" + out_root.string () + "/)"; diff --git a/bpkg/pkg-fetch.cxx b/bpkg/pkg-fetch.cxx index b1e5253..4a2a9ea 100644 --- a/bpkg/pkg-fetch.cxx +++ b/bpkg/pkg-fetch.cxx @@ -145,7 +145,8 @@ namespace bpkg purge, nullopt, // No source directory yet. false, - nullopt // No output directory yet. + nullopt, // No output directory yet. + {} // No prerequisites captured yet. }); db.persist (p); diff --git a/bpkg/pkg-unpack.cxx b/bpkg/pkg-unpack.cxx index b6decca..32d2af3 100644 --- a/bpkg/pkg-unpack.cxx +++ b/bpkg/pkg-unpack.cxx @@ -69,7 +69,8 @@ namespace bpkg false, // Don't purge archive. move (ad), purge, - nullopt // No output directory yet. + nullopt, // No output directory yet. + {} // No prerequisites captured yet. }); db.persist (p); diff --git a/bpkg/satisfaction b/bpkg/satisfaction new file mode 100644 index 0000000..2200688 --- /dev/null +++ b/bpkg/satisfaction @@ -0,0 +1,39 @@ +// file : bpkg/satisfaction -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BPKG_SATISFACTION +#define BPKG_SATISFACTION + +#include +#include +#include + +namespace bpkg +{ + // Return true if version satisfies the constraint. + // + bool + satisfies (const version&, const dependency_constraint&); + + inline bool + satisfies (const version& v, const optional& c) + { + return !c || satisfies (v, *c); + } + + // Return true if any version that satisfies l also satisfies r, or, in + // other words, l is stricter than or equal to r. + // + bool + satisfies (const dependency_constraint& l, const dependency_constraint& r); + + inline bool + satisfies (const optional& l, + const optional& r) + { + return l ? (!r || satisfies (*l, *r)) : !r; + } +} + +#endif // BPKG_SATISFACTION diff --git a/bpkg/satisfaction.cxx b/bpkg/satisfaction.cxx new file mode 100644 index 0000000..0631b83 --- /dev/null +++ b/bpkg/satisfaction.cxx @@ -0,0 +1,101 @@ +// file : bpkg/satisfaction.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include + +using namespace std; +using namespace butl; + +namespace bpkg +{ + bool + satisfies (const version& v, const dependency_constraint& c) + { + using op = comparison; + + // Note that the constraint's version is always rhs (libfoo >= 1.2.3). + // + switch (c.operation) + { + case op::eq: return v == c.version; + case op::lt: return v < c.version; + case op::gt: return v > c.version; + case op::le: return v <= c.version; + case op::ge: return v >= c.version; + } + + assert (false); + return false; + } + + bool + satisfies (const dependency_constraint& l, const dependency_constraint& r) + { + using op = comparison; + + op lo (l.operation); + op ro (r.operation); + + const version& lv (l.version); + const version& rv (r.version); + + switch (lo) + { + case op::eq: // == + { + return ro == op::eq && lv == rv; + } + case op::lt: // < + { + switch (ro) + { + case op::eq: return rv < lv; + case op::lt: return rv <= lv; + case op::le: return rv < lv; + case op::gt: + case op::ge: return false; + } + } + case op::le: // <= + { + switch (ro) + { + case op::eq: return rv <= lv; + case op::lt: return rv < lv; + case op::le: return rv <= lv; + case op::gt: + case op::ge: return false; + } + } + case op::gt: // > + { + switch (ro) + { + case op::eq: return lv > rv; + case op::lt: + case op::le: return false; + case op::gt: return lv >= rv; + case op::ge: return lv > rv; + } + } + case op::ge: // >= + { + switch (ro) + { + case op::eq: return lv >= rv; + case op::lt: + case op::le: return false; + case op::gt: return lv > rv; + case op::ge: return lv >= rv; + } + } + } + + assert (false); + return false; + } +} diff --git a/bpkg/test.sh b/bpkg/test.sh index 5449883..4b2db6f 100755 --- a/bpkg/test.sh +++ b/bpkg/test.sh @@ -409,6 +409,73 @@ rm -r $out test pkg-purge -f $pkg stat unknown +# dependency management +# +test rep-create ../tests/repository/1/depend/stable +test cfg-create --wipe +test rep-add ../tests/repository/1/depend/stable +test rep-fetch + +test pkg-fetch libbar 1.0.0 +test pkg-unpack libbar +fail pkg-configure libbar # no libfoo +stat libbar 1.0.0 "unpacked" +test pkg-fetch libfoo 1.0.0 +test pkg-unpack libfoo +fail pkg-configure libbar # libfoo not configured +test pkg-configure libfoo +test pkg-configure libbar +fail pkg-disfigure libfoo # libbar still depends on libfoo +test pkg-disfigure libbar +test pkg-disfigure libfoo +test pkg-purge libbar +test pkg-purge libfoo + +test pkg-fetch libfoo 1.0.0 +test pkg-unpack libfoo +test pkg-configure libfoo +test pkg-fetch libbar 1.1.0 +test pkg-unpack libbar +fail pkg-configure libbar # libfoo >= 1.1.0 +test pkg-disfigure libfoo +test pkg-purge libfoo +test pkg-fetch libfoo 1.1.0 +test pkg-unpack libfoo +test pkg-configure libfoo +test pkg-configure libbar +test pkg-disfigure libbar +test pkg-disfigure libfoo +test pkg-purge libfoo +test pkg-purge libbar + +test pkg-fetch libfoo 1.1.0 +test pkg-unpack libfoo +test pkg-configure libfoo +test pkg-fetch libbar 1.2.0 +test pkg-unpack libbar +fail pkg-configure libbar # libfoo >= 1.2.0 +test pkg-disfigure libfoo +test pkg-purge libfoo +test pkg-fetch libfoo 1.2.0 +test pkg-unpack libfoo +test pkg-configure libfoo +test pkg-configure libbar +fail pkg-disfigure libfoo # "package libbar on libfoo >= 1.2.0" +test pkg-disfigure libbar +test pkg-disfigure libfoo +test pkg-purge libfoo +test pkg-purge libbar + +test pkg-fetch libfoo 1.1.0 +test pkg-unpack libfoo +test pkg-configure libfoo +test pkg-fetch libbar 1.3.0 +test pkg-unpack libbar +fail pkg-configure libbar # incompatible constraints +test pkg-disfigure libfoo +test pkg-purge libfoo +test pkg-purge libbar + ## ## pkg-status (also tested in pkg-{fetch,unpack,configure,disfigure,purge} ## -- cgit v1.1