From aacff79e854d6d4eb22540339bc88c3efab353a2 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 5 Nov 2015 17:41:16 +0200 Subject: Implement package dependency resolution --- brep/package | 124 +++++++---- brep/package-details.cxx | 24 +- brep/package-search.cxx | 17 +- brep/package-version-details.cxx | 35 ++- brep/package.cxx | 10 +- brep/page.cxx | 27 ++- loader/loader.cxx | 266 ++++++++++++++++++++-- tests/loader/driver.cxx | 366 ++++++++++++++++--------------- tests/loader/external/1/staging/packages | 17 ++ tests/loader/external/1/testing/packages | 6 +- tests/loader/internal/1/math/packages | 16 ++ tests/loader/internal/1/stable/packages | 15 -- web/module | 5 +- 13 files changed, 644 insertions(+), 284 deletions(-) diff --git a/brep/package b/brep/package index 719a906..ef73542 100644 --- a/brep/package +++ b/brep/package @@ -10,6 +10,7 @@ #include #include #include // shared_ptr +#include #include // size_t #include // move() #include // uint16 @@ -75,13 +76,11 @@ namespace brep class package; using strings = std::vector; - - template - using optional = butl::optional; + using butl::optional; // path // - using path = butl::path; + using butl::path; #pragma db map type(path) as(std::string) \ to((?).string ()) from(brep::path (?)) @@ -93,14 +92,14 @@ namespace brep to((?) ? (?)->string () : brep::optional_string ()) \ from((?) ? brep::path (*(?)) : brep::optional_path ()) - using dir_path = butl::dir_path; + using butl::dir_path; #pragma db map type(dir_path) as(std::string) \ to((?).string ()) from(brep::dir_path (?)) // timestamp // - using timestamp = butl::timestamp; + using butl::timestamp; #pragma db map type(timestamp) as(std::uint64_t) \ to(std::chrono::system_clock::to_time_t (?)) \ @@ -108,7 +107,7 @@ namespace brep // version // - using version = bpkg::version; + using bpkg::version; #pragma db value struct canonical_version @@ -116,6 +115,18 @@ namespace brep std::uint16_t epoch; std::string canonical_upstream; std::uint16_t revision; + + bool + empty () const noexcept + { + // No sense to test epoch and revision for 0 as a valid canonical_version + // object can not have them different from 0 if canonical_upstream is + // empty. The predicate semantics is equal to the one of the + // bpkg::version class. + // + assert (!canonical_upstream.empty () || (epoch == 0 && revision == 0)); + return canonical_upstream.empty (); + } }; #pragma db value transient @@ -140,14 +151,14 @@ namespace brep // priority // - using priority = bpkg::priority; + using bpkg::priority; #pragma db value(priority) definition #pragma db member(priority::value) column("") // url // - using url = bpkg::url; + using bpkg::url; #pragma db value(url) definition #pragma db member(url::value) virtual(std::string) before access(this) \ @@ -155,7 +166,7 @@ namespace brep // email // - using email = bpkg::email; + using bpkg::email; #pragma db value(email) definition #pragma db member(email::value) virtual(std::string) before access(this) \ @@ -163,17 +174,33 @@ namespace brep // licenses // - using licenses = bpkg::licenses; + using bpkg::licenses; using license_alternatives = std::vector; #pragma db value(licenses) definition // dependencies // - using comparison = bpkg::comparison; - using dependency_constraint = bpkg::dependency_constraint; + using bpkg::comparison; + using bpkg::dependency_constraint; #pragma db value(dependency_constraint) definition + #pragma db member(dependency_constraint::operation) column("") + #pragma db member(dependency_constraint::version) column("") + + #pragma db value + struct package_id + { + std::string name; + canonical_version version; + + package_id () = default; + package_id (std::string n, const brep::version& v) + : name (std::move (n)), + version {v.epoch, v.canonical_upstream, v.revision} + { + } + }; // Notes: // @@ -215,24 +242,51 @@ namespace brep // * No need to complicate persisted object model with repository // relations otherwise required just for dependency resolution. // - using dependency = bpkg::dependency; - using dependency_alternatives = bpkg::dependency_alternatives; - using dependencies = std::vector; - #pragma db value(dependency) definition - #pragma db member(dependency::constraint) column("") - #pragma db value(dependency_alternatives) definition + #pragma db value + struct dependency + { + using package_type = brep::package; + + odb::lazy_shared_ptr package; + optional constraint; + + // Prerequisite package name. + // + std::string + name () const; + + // Database mapping. + // + #pragma db member(package) column("") not_null + }; + + #pragma db value + class dependency_alternatives: public std::vector + { + public: + bool conditional; + std::string comment; + + dependency_alternatives () = default; + + explicit + dependency_alternatives (bool d, std::string c) + : conditional (d), comment (std::move (c)) {} + }; + + using dependencies = std::vector; // requirements // - using requirement_alternatives = bpkg::requirement_alternatives; + using bpkg::requirement_alternatives; using requirements = std::vector; #pragma db value(requirement_alternatives) definition // repository_location // - using repository_location = bpkg::repository_location; + using bpkg::repository_location; #pragma db map type(repository_location) as(std::string) \ to((?).string ()) from(brep::repository_location (?)) @@ -270,6 +324,8 @@ namespace brep timestamp repositories_timestamp; bool internal; + std::vector> complements; + std::vector> prerequisites; // Database mapping. // @@ -279,25 +335,17 @@ namespace brep set(this.location = std::move (?); \ assert (this.name == this.location.canonical_name ())) + #pragma db member(complements) id_column("repository") \ + value_column("complement") value_not_null + + #pragma db member(prerequisites) id_column("repository") \ + value_column("prerequisite") value_not_null + private: friend class odb::access; repository () = default; }; - #pragma db value - struct package_id - { - std::string name; - canonical_version version; - - package_id () = default; - package_id (std::string n, const brep::version& v) - : name (std::move (n)), - version {v.epoch, v.canonical_upstream, v.revision} - { - } - }; - // The 'to' expression calls the PostgreSQL to_tsvector(weighted_text) // function overload (package-extra.sql). Since we are only interested // in "write-only" members of this type, make the 'from' expression @@ -382,7 +430,7 @@ namespace brep // optional location; - std::vector> external_repositories; + std::vector> other_repositories; // Database mapping. // @@ -446,9 +494,9 @@ namespace brep set(odb::nested_set (this.requirements, move (?))) \ id_column("") key_column("") value_column("id") - // external_repositories + // other_repositories // - #pragma db member(external_repositories) \ + #pragma db member(other_repositories) \ id_column("") value_column("repository") value_not_null // search_index diff --git a/brep/package-details.cxx b/brep/package-details.cxx index f814ef8..0424d5b 100644 --- a/brep/package-details.cxx +++ b/brep/package-details.cxx @@ -10,6 +10,7 @@ #include +#include #include #include @@ -119,6 +120,7 @@ namespace brep << A(HREF=url (!f, sq, pg)) << (f ? "[brief]" : "[full]") << ~A << ~DIV; + session sn; transaction t (db_->begin ()); shared_ptr p; @@ -172,13 +174,12 @@ namespace brep "OFFSET" + to_string (pg * rp) + "LIMIT" + to_string (rp))); - s << FORM_SEARCH (sq.c_str ()) - << DIV_COUNTER (pc, "Version", "Versions") - - // Enclose the subsequent tables to be able to use nth-child CSS selector. - // - << DIV; + s << FORM_SEARCH (sq) + << DIV_COUNTER (pc, "Version", "Versions"); + // Enclose the subsequent tables to be able to use nth-child CSS selector. + // + s << DIV; for (const auto& pr: r) { shared_ptr p (db_->load (pr.id)); @@ -187,7 +188,7 @@ namespace brep << TBODY << TR_VERSION (name, p->version.string ()) - // @@ Shouldn't we skip low priority row ? + // @@ Shouldn't we skip low priority row ? Don't think so, why? // << TR_PRIORITY (p->priority); @@ -200,24 +201,27 @@ namespace brep assert (p->internal_repository != nullptr); // @@ Shouldn't we make package location to be a link to the proper - // place of the About page, describing corresponding repository ? + // place of the About page, describing corresponding repository? + // Yes, I think that's sounds reasonable, once we have about. // // @@ In most cases package location will be the same for all versions // of the same package. Shouldn't we put package location to the // package summary part and display it here only if it differes // from the one in the summary ? // + // Hm, I am not so sure about this. Consider: stable/testing/unstable. + // s << TR_LOCATION (p->internal_repository.object_id ()) << TR_DEPENDS (p->dependencies) << TR_REQUIRES (p->requirements) << ~TBODY << ~TABLE; } + s << ~DIV; t.commit (); - s << ~DIV - << DIV_PAGER (pg, pc, rp, options_->pages_in_pager (), url (f, sq)) + s << DIV_PAGER (pg, pc, rp, options_->pages_in_pager (), url (f, sq)) << ~DIV << ~BODY << ~HTML; diff --git a/brep/package-search.cxx b/brep/package-search.cxx index 64e43f1..47f53e9 100644 --- a/brep/package-search.cxx +++ b/brep/package-search.cxx @@ -10,6 +10,7 @@ #include +#include #include #include @@ -92,6 +93,7 @@ namespace brep << DIV_HEADER () << DIV(ID="content"); + session sn; transaction t (db_->begin ()); auto pc ( @@ -105,13 +107,12 @@ namespace brep "OFFSET" + to_string (pg * rp) + "LIMIT" + to_string (rp))); - s << FORM_SEARCH (sq.c_str ()) - << DIV_COUNTER (pc, "Package", "Packages") - - // Enclose the subsequent tables to be able to use nth-child CSS selector. - // - << DIV; + s << FORM_SEARCH (sq) + << DIV_COUNTER (pc, "Package", "Packages"); + // Enclose the subsequent tables to be able to use nth-child CSS selector. + // + s << DIV; for (const auto& pr: r) { shared_ptr p (db_->load (pr.id)); @@ -127,13 +128,13 @@ namespace brep << ~TBODY << ~TABLE; } + s << ~DIV; t.commit (); string url (qp.empty () ? "/" : ("/?" + qp)); - s << ~DIV - << DIV_PAGER (pg, pc, rp, options_->pages_in_pager (), url) + s << DIV_PAGER (pg, pc, rp, options_->pages_in_pager (), url) << ~DIV << ~BODY << ~HTML; diff --git a/brep/package-version-details.cxx b/brep/package-version-details.cxx index 71559b9..a4fa95a 100644 --- a/brep/package-version-details.cxx +++ b/brep/package-version-details.cxx @@ -11,6 +11,7 @@ #include +#include #include #include @@ -63,7 +64,7 @@ namespace brep } assert (i != rq.path ().rend ()); - const string& n (*i); + const string& n (*i); // Package name. params::package_version_details pr; @@ -120,6 +121,7 @@ namespace brep bool not_found (false); shared_ptr p; + session sn; transaction t (db_->begin ()); try @@ -153,8 +155,6 @@ namespace brep const string du (p->internal_repository.load ()->location.string () + "/" + p->location->string ()); - t.commit (); - s << TABLE(CLASS="proplist", ID="version") << TBODY @@ -209,7 +209,32 @@ namespace brep if (&d != &da[0]) s << " | "; - s << d; // @@ Should it be a link ? + shared_ptr p (d.package.load ()); + string en (mime_url_encode (p->id.name)); + + if (p->internal_repository != nullptr) + s << A << HREF << "/go/" << en << ~HREF << p->id.name << ~A; + else + // @@ Refer to package repository URL when supported in repository + // manifest. + // + s << p->id.name; + + if (d.constraint) + { + s << ' '; + + if (p->internal_repository != nullptr) + s << A + << HREF << "/go/" << en << "/" << p->version.string () << ~HREF + << *d.constraint + << ~A; + else + // @@ Refer to package repository URL when supported in + // repository manifest. + // + s << *d.constraint; + } } s << ~SPAN @@ -222,6 +247,8 @@ namespace brep << ~TABLE; } + t.commit (); + const auto& rt (p->requirements); if (!rt.empty ()) diff --git a/brep/package.cxx b/brep/package.cxx index 9b7d24a..3c65ed8 100644 --- a/brep/package.cxx +++ b/brep/package.cxx @@ -16,6 +16,14 @@ using namespace odb::core; namespace brep { + // dependency + // + string dependency:: + name () const + { + return package.object_id ().name; + } + // package // package:: @@ -63,7 +71,7 @@ namespace brep version (move (vr)) { assert (!rp->internal); - external_repositories.emplace_back (move (rp)); + other_repositories.emplace_back (move (rp)); } weighted_text package:: diff --git a/brep/page.cxx b/brep/page.cxx index a23ce17..deee055 100644 --- a/brep/page.cxx +++ b/brep/page.cxx @@ -6,6 +6,7 @@ #include #include +#include // shared_ptr #include #include // move() #include // min() @@ -16,6 +17,7 @@ #include #include +#include using namespace std; using namespace xml; @@ -51,7 +53,7 @@ namespace brep void FORM_SEARCH:: operator() (serializer& s) const { - // The 'action' attribute is optional in HTML5. While the standard don't + // The 'action' attribute is optional in HTML5. While the standard doesn't // specify browser behavior explicitly for the case the attribute is // ommited, the only reasonable behavior is to default it to the current // document URL. @@ -260,26 +262,35 @@ namespace brep // set ds; for (const auto& da: d) - ds.emplace (da.name); + ds.emplace (da.name ()); bool m (ds.size () > 1); if (m) s << "("; - bool first (true); + bool f (true); // First dependency alternative. for (const auto& da: d) { - if (ds.find (da.name) != ds.end ()) + string n (da.name ()); + if (ds.find (n) != ds.end ()) { - ds.erase (da.name); + ds.erase (n); - if (first) - first = false; + if (f) + f = false; else s << " | "; - s << da.name; // @@ Make it a link. + shared_ptr p (da.package.load ()); + + if (p->internal_repository != nullptr) + s << A << HREF << "/go/" << mime_url_encode (n) << ~HREF << n << ~A; + else + // @@ Refer to package repository URL when supported in repository + // manifest. + // + s << n; } } diff --git a/loader/loader.cxx b/loader/loader.cxx index ecc6032..74844f5 100644 --- a/loader/loader.cxx +++ b/loader/loader.cxx @@ -12,7 +12,9 @@ #include #include #include // runtime_error, invalid_argument +#include // find(), find_if() +#include #include #include @@ -319,6 +321,22 @@ load_packages (const shared_ptr& rp, database& db) } } + dependencies ds; + for (auto& pda: pm.dependencies) + { + ds.emplace_back (pda.conditional, move (pda.comment)); + + for (auto& pd: pda) + // Proper version will be assigned during dependency resolution + // procedure. Here we rely on the fact the foreign key constraint + // check is deferred until the current transaction commit. + // + ds.back ().push_back ({ + lazy_shared_ptr ( + db, package_id (move (pd.name), version ())), + move (pd.constraint)}); + } + p = make_shared ( move (pm.name), move (pm.version), @@ -332,7 +350,7 @@ load_packages (const shared_ptr& rp, database& db) move (pm.package_url), move (pm.email), move (pm.package_email), - move (pm.dependencies), + move (ds), move (pm.requirements), move (pm.location), rp); @@ -351,21 +369,13 @@ load_packages (const shared_ptr& rp, database& db) // for this purpose. // - if (rp->internal) - { - // Just skip the duplicate. - // + // As soon as internal repositories get loaded first, the internal + // package can duplicate an internal package only. + // + assert (!rp->internal || p->internal_repository != nullptr); - // As soon as internal repositories get loaded first, the internal - // package can duplicate an internal package only. - // - assert (p->internal_repository != nullptr); - } - else - { - p->external_repositories.push_back (rp); - db.update (p); - } + p->other_repositories.push_back (rp); + db.update (p); } } @@ -374,7 +384,8 @@ load_packages (const shared_ptr& rp, database& db) // Load the prerequsite repositories and their complements state from the // 'repositories' file. Update the repository persistent state to save -// repositories_timestamp member. Should be called once per internal repository. +// repositories_timestamp, prerequsites, and complements members. +// Should be called once per persisted internal repository. // static void load_prerequisites (const shared_ptr& rp, database& db) @@ -389,13 +400,16 @@ load_prerequisites (const shared_ptr& rp, database& db) // assert (!rp->local_path.empty ()); + // Repository is already persisted by the load_packages() function call. + // + assert (db.find (rp->name) != nullptr); + repository_manifests rpm; { ifstream ifs; path p (rp->local_path / path ("repositories")); rp->repositories_timestamp = manifest_stream (p, ifs); - db.update (rp); manifest_parser mp (ifs, p.string ()); rpm = repository_manifests (mp); @@ -408,6 +422,8 @@ load_prerequisites (const shared_ptr& rp, database& db) rm.effective_role () == repository_role::prerequisite)) continue; // Ignore entry for this repository. + assert (rm.effective_role () != repository_role::base); + repository_location rl; auto bad_location ( @@ -438,7 +454,18 @@ load_prerequisites (const shared_ptr& rp, database& db) bad_location (); } - shared_ptr pr (db.find (rl.canonical_name ())); + const auto& cn (rl.canonical_name ()); + + // Add repository to prerequisites or complements member of the dependent + // repository. + // + auto& rs (rm.effective_role () == repository_role::prerequisite + ? rp->prerequisites + : rp->complements); + + rs.emplace_back (db, cn); + + shared_ptr pr (db.find (cn)); if (pr != nullptr) // The prerequisite repository is already loaded. @@ -473,6 +500,189 @@ load_prerequisites (const shared_ptr& rp, database& db) load_packages (pr, db); load_prerequisites (pr, db); } + + db.update (rp); +} + +static ostream& +operator<< (ostream& o, + const brep::dependency& d) // Ambiguity with bpkg::dependency. +{ + o << d.name (); + + if (d.constraint) + o << ' ' << *d.constraint; + + return o; +} + +// Check if the package is available from the specified repository, +// its prerequisite repositories, or one of their complements, +// recursively. +// +static bool +find (const lazy_shared_ptr& r, + const package& p, + bool prereq = true) +{ + assert (r != nullptr); + + const auto& o (p.other_repositories); + if (r == p.internal_repository || find (o.begin (), o.end (), r) != o.end ()) + return true; + + auto rp (r.load ()); + for (const auto& cr: rp->complements) + { + if (find (lazy_shared_ptr (cr), p, false)) + return true; + } + + if (prereq) + { + for (auto pr: rp->prerequisites) + { + if (find (lazy_shared_ptr (pr), p, false)) + return true; + } + } + + return false; +} + +// Resolve package dependencies. Ensure that the best matching dependency +// belongs to the package repositories, their immediate prerequisite +// repositories, or their complements, recursively. Should be called once per +// internal package. +// +static void +resolve_dependencies (package& p, database& db) +{ + // Resolve dependencies for internal packages only. + // + // @@ add package::internal() predicate? Lots of place where you do + // (p.internal_repository != nullptr). + // + assert (p.internal_repository != nullptr); + + if (p.dependencies.empty ()) + return; + + for (auto& da: p.dependencies) + { + for (auto& d: da) + { + // Dependency should not be resolved yet. + // + assert (d.package.object_id ().version.empty ()); + + using query = query; + query q (query::id.name == d.name ()); + + if (d.constraint) + { + auto c (*d.constraint); + switch (c.operation) + { + case comparison::eq: q = q && query::id.version == c.version; break; + case comparison::lt: q = q && query::id.version < c.version; break; + case comparison::gt: q = q && query::id.version > c.version; break; + case comparison::le: q = q && query::id.version <= c.version; break; + case comparison::ge: q = q && query::id.version >= c.version; break; + } + } + + auto r ( + db.query (q + order_by_version_desc (query::id.version))); + + for (const auto& pp: r) + { + if (find (p.internal_repository, pp)) + { + d.package.reset (db, pp.id); + break; + } + } + + if (d.package.object_id ().version.empty ()) + { + ostringstream o; + o << "can't resolve dependency " << d << " of the package " + << p.id.name << " " << p.version.string () + << " (" << p.internal_repository.load ()->name << ")"; + + // Practically it is enough to resolve at least one dependency + // alternative to build a package. Meanwhile here we consider an error + // specifying in the manifest file an alternative which can't be + // resolved. + // + throw runtime_error (o.str ()); + } + } + } + + db.update (p); // Update the package state. +} + +using package_ids = vector; + +// Ensure the package dependency chain do not contain the package id. Throw +// runtime_error otherwise. Continue the chain with the package id and call +// itself recursively for each prerequisite of the package. Should be called +// once per internal package. +// +// @@ This should probably be eventually moved to bpkg. +// +static void +detect_dependency_cycle (const package_id& id, package_ids& chain, database& db) +{ + // Package of one version depending on the same package of another version + // is something obscure. So the comparison is made up to a package name. + // + auto pr ([&id](const package_id& i) -> bool {return i.name == id.name;}); + auto i (find_if (chain.begin (), chain.end (), pr)); + + if (i != chain.end ()) + { + ostringstream o; + o << "package dependency cycle: "; + + auto prn ( + [&o, &db](const package_id& id) + { + shared_ptr p (db.load (id)); + + assert (p->internal_repository != nullptr || + !p->other_repositories.empty ()); + + shared_ptr r ( + p->internal_repository != nullptr + ? p->internal_repository.load () + : p->other_repositories[0].load ()); + + o << id.name << " " << p->version.string () << " (" << r->name << ")"; + }); + + for (; i != chain.end (); ++i) + { + prn (*i); + o << " -> "; + } + + prn (id); + throw runtime_error (o.str ()); + } + + chain.push_back (id); + + shared_ptr p (db.load (id)); + for (const auto& da: p->dependencies) + { + for (const auto& d: da) + detect_dependency_cycle (d.package.object_id (), chain, db); + } + + chain.pop_back (); } int @@ -581,6 +791,26 @@ main (int argc, char* argv[]) load_prerequisites (r, db); } + + session s; + using query = query; + + // Resolve internal packages dependencies. + // + { + auto r (db.query (query::internal_repository.is_not_null ())); + for (auto& p: r) + resolve_dependencies (p, db); + } + + // Ensure there is no package dependency cycles. + // + { + package_ids chain; + auto r (db.query (query::internal_repository.is_not_null ())); + for (const auto& p: r) + detect_dependency_cycle (p.id, chain, db); + } } t.commit (); diff --git a/tests/loader/driver.cxx b/tests/loader/driver.cxx index 74c6966..2222fde 100644 --- a/tests/loader/driver.cxx +++ b/tests/loader/driver.cxx @@ -30,7 +30,7 @@ using namespace brep; static inline bool operator== (const dependency& a, const dependency& b) { - return a.name == b.name && !a.constraint == !b.constraint && + return a.name () == b.name () && !a.constraint == !b.constraint && (!a.constraint || (a.constraint->operation == b.constraint->operation && a.constraint->version == b.constraint->version)); } @@ -45,9 +45,22 @@ check_location (shared_ptr& p) path (p->id.name + "-" + p->version.string () + ".tar.gz"); } +static bool +check_external (const package& p) +{ + return p.summary.empty () && p.tags.empty () && !p.description && + p.url.empty () && !p.package_url && p.email.empty () && !p.package_email && + p.internal_repository == nullptr && p.other_repositories.size () > 0 && + p.priority == priority () && p.changes.empty () && + p.license_alternatives.empty () && p.dependencies.empty () && + p.requirements.empty (); +} + int main (int argc, char* argv[]) { + using brep::optional; // Ambiguity with butl::optional. + if (argc != 7) { cerr << "usage: " << argv[0] @@ -90,7 +103,7 @@ main (int argc, char* argv[]) transaction t (db.begin ()); assert (db.query ().size () == 5); - assert (db.query ().size () == 12); + assert (db.query ().size () == 14); shared_ptr sr (db.load ("cppget.org/stable")); shared_ptr mr (db.load ("cppget.org/math")); @@ -129,48 +142,10 @@ main (int argc, char* argv[]) db.load (package_id ("libfoo", version ("1.2.4")))); assert (check_location (fpv4)); - shared_ptr xpv ( - db.load (package_id ("libstudxml", version ("1.0.0-1")))); - assert (check_location (xpv)); - - // Verify libstudxml package version. - // - assert (xpv->summary == "Modern C++ XML API"); - assert (xpv->tags == strings ({"c++", "xml", "parser", "serializer", - "pull", "streaming", "modern"})); - assert (!xpv->description); - assert (xpv->url == "http://www.codesynthesis.com/projects/libstudxml/"); - assert (!xpv->package_url); - assert (xpv->email == - email ("studxml-users@codesynthesis.com", - "Public mailing list, posts by non-members " - "are allowed but moderated.")); - assert (xpv->package_email && - *xpv->package_email == email ("boris@codesynthesis.com", - "Direct email to the author.")); - - assert (xpv->internal_repository.load () == sr); - assert (xpv->external_repositories.empty ()); - assert (xpv->priority == priority::low); - assert (xpv->changes.empty ()); - - assert (xpv->license_alternatives.size () == 1); - assert (xpv->license_alternatives[0].size () == 1); - assert (xpv->license_alternatives[0][0] == "MIT"); - - assert (xpv->dependencies.size () == 2); - assert (xpv->dependencies[0].size () == 1); - assert (xpv->dependencies[0][0] == - (dependency { - "libexpat", - brep::optional ( - dependency_constraint{ - comparison::ge, version ("2.0.0")})})); - - assert (xpv->dependencies[1].size () == 1); - assert (xpv->dependencies[1][0] == (dependency {"libgenx", nullopt})); - - assert (xpv->requirements.empty ()); + assert (sr->complements.empty ()); + assert (sr->prerequisites.size () == 2); + assert (sr->prerequisites[0].load () == cr); + assert (sr->prerequisites[1].load () == mr); // Verify libfoo package versions. // @@ -185,8 +160,10 @@ main (int argc, char* argv[]) assert (!fpv1->package_email); assert (fpv1->internal_repository.load () == sr); - assert (fpv1->external_repositories.size () == 1); - assert (fpv1->external_repositories[0].load () == cr); + assert (fpv1->other_repositories.size () == 2); + assert (fpv1->other_repositories[0].load () == mr); + assert (fpv1->other_repositories[1].load () == cr); + assert (fpv1->priority == priority::low); assert (fpv1->changes.empty ()); @@ -208,7 +185,7 @@ main (int argc, char* argv[]) assert (!fpv2->package_email); assert (fpv2->internal_repository.load () == sr); - assert (fpv2->external_repositories.empty ()); + assert (fpv2->other_repositories.empty ()); assert (fpv2->priority == priority::low); assert (fpv2->changes.empty ()); @@ -220,17 +197,24 @@ main (int argc, char* argv[]) assert (fpv2->dependencies[0].size () == 1); assert (fpv2->dependencies[1].size () == 1); + auto dep ( + [&db](const char* n, + const optional& c) -> dependency + { + return {lazy_shared_ptr (db, package_id (n, version ())), c}; + }); + assert (fpv2->dependencies[0][0] == - (dependency { - "libbar", - brep::optional ( - dependency_constraint{comparison::le, version ("2.4.0")})})); + dep ( + "libbar", + optional ( + dependency_constraint{comparison::le, version ("2.4.0")}))); assert (fpv2->dependencies[1][0] == - (dependency { - "libexp", - brep::optional ( - dependency_constraint{comparison::eq, version ("1+1.2")})})); + dep ( + "libexp", + brep::optional ( + dependency_constraint{comparison::eq, version ("1+1.2")}))); assert (fpv2->requirements.empty ()); @@ -245,7 +229,7 @@ main (int argc, char* argv[]) assert (!fpv3->package_email); assert (fpv3->internal_repository.load () == sr); - assert (fpv3->external_repositories.empty ()); + assert (fpv3->other_repositories.empty ()); assert (fpv3->priority == priority::low); assert (fpv3->changes.empty ()); @@ -257,10 +241,10 @@ main (int argc, char* argv[]) assert (fpv3->dependencies.size () == 1); assert (fpv3->dependencies[0].size () == 1); assert (fpv3->dependencies[0][0] == - (dependency { - "libmisc", - brep::optional ( - dependency_constraint{comparison::ge, version ("2.0.0")})})); + dep ( + "libmisc", + brep::optional ( + dependency_constraint{comparison::ge, version ("2.0.0")}))); // libfoo-1.2.4 // @@ -273,7 +257,7 @@ main (int argc, char* argv[]) assert (!fpv4->package_email); assert (fpv4->internal_repository.load () == sr); - assert (fpv4->external_repositories.empty ()); + assert (fpv4->other_repositories.empty ()); assert (fpv4->priority == priority::low); assert (fpv4->changes == "some changes 1\nsome changes 2"); @@ -286,10 +270,10 @@ main (int argc, char* argv[]) assert (fpv4->dependencies.size () == 1); assert (fpv4->dependencies[0].size () == 1); assert (fpv4->dependencies[0][0] == - (dependency { - "libmisc", - brep::optional ( - dependency_constraint{comparison::ge, version ("2.0.0")})})); + dep ( + "libmisc", + brep::optional ( + dependency_constraint{comparison::ge, version ("2.0.0")}))); // Verify 'math' repository. // @@ -315,6 +299,52 @@ main (int argc, char* argv[]) db.load (package_id ("libfoo", version ("1.2.4-1")))); assert (check_location (fpv5)); + shared_ptr xpv ( + db.load (package_id ("libstudxml", version ("1.0.0-1")))); + assert (check_location (xpv)); + + assert (mr->complements.empty ()); + assert (mr->prerequisites.size () == 1); + assert (mr->prerequisites[0].load () == cr); + + // Verify libstudxml package version. + // + assert (xpv->summary == "Modern C++ XML API"); + assert (xpv->tags == strings ({"c++", "xml", "parser", "serializer", + "pull", "streaming", "modern"})); + assert (!xpv->description); + assert (xpv->url == "http://www.codesynthesis.com/projects/libstudxml/"); + assert (!xpv->package_url); + assert (xpv->email == + email ("studxml-users@codesynthesis.com", + "Public mailing list, posts by non-members " + "are allowed but moderated.")); + assert (xpv->package_email && + *xpv->package_email == email ("boris@codesynthesis.com", + "Direct email to the author.")); + + assert (xpv->internal_repository.load () == mr); + assert (xpv->other_repositories.empty ()); + assert (xpv->priority == priority::low); + assert (xpv->changes.empty ()); + + assert (xpv->license_alternatives.size () == 1); + assert (xpv->license_alternatives[0].size () == 1); + assert (xpv->license_alternatives[0][0] == "MIT"); + + assert (xpv->dependencies.size () == 2); + assert (xpv->dependencies[0].size () == 1); + assert (xpv->dependencies[0][0] == + dep ( + "libexpat", + optional ( + dependency_constraint{comparison::ge, version ("2.0.0")}))); + + assert (xpv->dependencies[1].size () == 1); + assert (xpv->dependencies[1][0] == dep ("libgenx", nullopt)); + + assert (xpv->requirements.empty ()); + // Verify libfoo package versions. // // libfoo-1.2.4-1 @@ -335,8 +365,8 @@ main (int argc, char* argv[]) *fpv5->package_email == "pack@example.com"); assert (fpv5->internal_repository.load () == mr); - assert (fpv5->external_repositories.size () == 1); - assert (fpv5->external_repositories[0].load () == cr); + assert (fpv5->other_repositories.size () == 1); + assert (fpv5->other_repositories[0].load () == cr); assert (fpv5->priority == priority::high); assert (fpv5->priority.comment == @@ -362,28 +392,36 @@ main (int argc, char* argv[]) assert (fpv5->license_alternatives[1].size () == 1); assert (fpv5->license_alternatives[1][0] == "BSD"); - assert (fpv5->dependencies.size () == 2); + assert (fpv5->dependencies.size () == 3); assert (fpv5->dependencies[0].size () == 2); assert (fpv5->dependencies[0].comment == "Crashes with 1.1.0-2.3.0."); assert (fpv5->dependencies[0][0] == - (dependency { - "libmisc", - brep::optional ( - dependency_constraint{comparison::lt, version ("1.1")})})); + dep ( + "libmisc", + brep::optional ( + dependency_constraint{comparison::lt, version ("1.1")}))); assert (fpv5->dependencies[0][1] == - (dependency { - "libmisc", - brep::optional ( - dependency_constraint{comparison::gt, version ("2.3.0")})})); + dep ( + "libmisc", + brep::optional ( + dependency_constraint{comparison::gt, version ("2.3.0")}))); + + assert (fpv5->dependencies[1].size () == 1); + assert (fpv5->dependencies[1].comment.empty ()); - assert (fpv5->dependencies[1].size () == 2); - assert (fpv5->dependencies[1].comment == "The newer the better."); + assert (fpv5->dependencies[1][0] == + dep ("libexp", + brep::optional ( + dependency_constraint{comparison::ge, version ("1.0")}))); - assert (fpv5->dependencies[1][0] == (dependency {"libstudxml", nullopt})); - assert (fpv5->dependencies[1][1] == (dependency {"libexpat", nullopt})); + assert (fpv5->dependencies[2].size () == 2); + assert (fpv5->dependencies[2].comment == "The newer the better."); + + assert (fpv5->dependencies[2][0] == dep ("libstudxml", nullopt)); + assert (fpv5->dependencies[2][1] == dep ("libexpat", nullopt)); requirements& fpvr5 (fpv5->requirements); assert (fpvr5.size () == 4); @@ -419,7 +457,7 @@ main (int argc, char* argv[]) assert (!epv->package_email); assert (epv->internal_repository.load () == mr); - assert (epv->external_repositories.empty ()); + assert (epv->other_repositories.empty ()); assert (epv->priority == priority (priority::low)); assert (epv->changes.empty ()); @@ -429,7 +467,7 @@ main (int argc, char* argv[]) assert (epv->dependencies.size () == 1); assert (epv->dependencies[0].size () == 1); - assert (epv->dependencies[0][0] == (dependency {"libmisc", nullopt})); + assert (epv->dependencies[0][0] == dep ("libmisc", nullopt)); assert (epv->requirements.empty ()); @@ -461,72 +499,31 @@ main (int argc, char* argv[]) db.load (package_id ("libfoo", version ("1.2.4-2")))); assert (check_location (fpv6)); + assert (cr->prerequisites.empty ()); + assert (cr->complements.size () == 1); + assert (cr->complements[0].load () == tr); + // Verify libbar package version. // // libbar-2.3.5 // - assert (bpv->summary.empty ()); - assert (bpv->tags.empty ()); - assert (!bpv->description); - assert (bpv->url.empty ()); - assert (!bpv->package_url); - assert (bpv->email.empty ()); - assert (!bpv->package_email); - - assert (bpv->internal_repository == nullptr); - assert (bpv->external_repositories.size () == 1); - assert (bpv->external_repositories[0].load () == cr); - - assert (bpv->priority == priority ()); - assert (bpv->changes.empty ()); - - assert (bpv->license_alternatives.empty ()); - assert (bpv->dependencies.empty ()); - assert (bpv->requirements.empty ()); + assert (check_external (*bpv)); + assert (bpv->other_repositories.size () == 1); + assert (bpv->other_repositories[0].load () == cr); // Verify libfoo package versions. // // libfoo-0.1 // - assert (fpv0->summary.empty ()); - assert (fpv0->tags.empty ()); - assert (!fpv0->description); - assert (fpv0->url.empty ()); - assert (!fpv0->package_url); - assert (fpv0->email.empty ()); - assert (!fpv0->package_email); - - assert (fpv0->internal_repository == nullptr); - assert (fpv0->external_repositories.size () == 1); - assert (fpv0->external_repositories[0].load () == cr); - assert (fpv0->priority == priority::low); - assert (fpv0->changes.empty ()); - - assert (fpv0->license_alternatives.empty ()); - - assert (fpv0->dependencies.empty ()); - assert (fpv0->requirements.empty ()); + assert (check_external (*fpv0)); + assert (fpv0->other_repositories.size () == 1); + assert (fpv0->other_repositories[0].load () == cr); // libfoo-1.2.4-2 // - assert (fpv6->summary.empty ()); - assert (fpv6->tags.empty ()); - assert (!fpv6->description); - assert (fpv6->url.empty ()); - assert (!fpv6->package_url); - assert (fpv6->email.empty ()); - assert (!fpv6->package_email); - - assert (fpv6->internal_repository == nullptr); - assert (fpv6->external_repositories.size () == 1); - assert (fpv6->external_repositories[0].load () == cr); - assert (fpv6->priority == priority::low); - assert (fpv6->changes.empty ()); - - assert (fpv6->license_alternatives.empty ()); - - assert (fpv6->dependencies.empty ()); - assert (fpv6->requirements.empty ()); + assert (check_external (*fpv6)); + assert (fpv6->other_repositories.size () == 1); + assert (fpv6->other_repositories[0].load () == cr); // Verify 'testing' repository. // @@ -544,59 +541,76 @@ main (int argc, char* argv[]) file_mtime (dir_path (tr->local_path) / path ("repositories"))); assert (!tr->internal); - shared_ptr mpv ( - db.load (package_id ("libmisc", version ("1.1")))); - assert (check_location (mpv)); + shared_ptr mpv0 ( + db.load (package_id ("libmisc", version ("2.4.0")))); + assert (check_location (mpv0)); - shared_ptr tpv ( - db.load (package_id ("libexpat", version ("5.1")))); - assert (check_location (tpv)); + assert (tr->prerequisites.empty ()); + assert (tr->complements.size () == 1); + assert (tr->complements[0].load () == gr); // Verify libmisc package version. // - // libmisc-1.1 + // libmisc-2.4.0 + // + assert (check_external (*mpv0)); + assert (mpv0->other_repositories.size () == 1); + assert (mpv0->other_repositories[0].load () == tr); + + // Verify 'staging' repository. // - assert (mpv->summary.empty ()); - assert (mpv->tags.empty ()); - assert (!mpv->description); - assert (mpv->url.empty ()); - assert (!mpv->package_url); - assert (mpv->email.empty ()); - assert (!mpv->package_email); + assert (gr->location.canonical_name () == "cppget.org/staging"); + assert (gr->location.string () == + "http://pkg.cppget.org/external/1/staging"); + assert (gr->display_name.empty ()); - assert (mpv->internal_repository == nullptr); - assert (mpv->external_repositories.size () == 1); - assert (mpv->external_repositories[0].load () == tr); + dir_path grp (cp.directory () / dir_path ("external/1/staging")); + assert (gr->local_path == grp.normalize ()); - assert (mpv->priority == priority ()); - assert (mpv->changes.empty ()); + assert (gr->packages_timestamp == + file_mtime (dir_path (gr->local_path) / path ("packages"))); + assert (gr->repositories_timestamp == + file_mtime (dir_path (gr->local_path) / path ("repositories"))); + assert (!gr->internal); - assert (mpv->license_alternatives.empty ()); - assert (mpv->dependencies.empty ()); - assert (mpv->requirements.empty ()); + shared_ptr tpv ( + db.load (package_id ("libexpat", version ("5.1")))); + assert (check_location (tpv)); + + shared_ptr gpv ( + db.load (package_id ("libgenx", version ("1.0")))); + assert (check_location (gpv)); + + shared_ptr mpv1 ( + db.load (package_id ("libmisc", version ("1.0")))); + assert (check_location (mpv1)); + + assert (gr->prerequisites.empty ()); + assert (gr->complements.empty ()); // Verify libexpat package version. // // libexpat-5.1 // - assert (tpv->summary.empty ()); - assert (tpv->tags.empty ()); - assert (!tpv->description); - assert (tpv->url.empty ()); - assert (!tpv->package_url); - assert (tpv->email.empty ()); - assert (!tpv->package_email); - - assert (tpv->internal_repository == nullptr); - assert (tpv->external_repositories.size () == 1); - assert (tpv->external_repositories[0].load () == gr); - - assert (tpv->priority == priority ()); - assert (tpv->changes.empty ()); - - assert (tpv->license_alternatives.empty ()); - assert (tpv->dependencies.empty ()); - assert (tpv->requirements.empty ()); + assert (check_external (*tpv)); + assert (tpv->other_repositories.size () == 1); + assert (tpv->other_repositories[0].load () == gr); + + // Verify libgenx package version. + // + // libgenx-1.0 + // + assert (check_external (*gpv)); + assert (gpv->other_repositories.size () == 1); + assert (gpv->other_repositories[0].load () == gr); + + // Verify libmisc package version. + // + // libmisc-1.0 + // + assert (check_external (*mpv1)); + assert (mpv1->other_repositories.size () == 1); + assert (mpv1->other_repositories[0].load () == gr); // Change package summary, update the object persistent state, rerun // loader and ensure the model were not rebuilt. diff --git a/tests/loader/external/1/staging/packages b/tests/loader/external/1/staging/packages index 86c20c1..e7b22b0 100644 --- a/tests/loader/external/1/staging/packages +++ b/tests/loader/external/1/staging/packages @@ -6,3 +6,20 @@ license: MIT url: http://www.example.com/expat/ email: expat-users@example.com location: libexpat-5.1.tar.gz +: +name: libgenx +version: 1.0 +summary: The Genx Library +license: MIT +url: http://www.example.com/genx/ +email: genx-users@example.com +location: libgenx-1.0.tar.gz +: +name: libmisc +version: 1.0 +summary: The Misc Library +license: MIT +url: http://www.example.com/misc/ +email: misc-users@example.com +depends: libexpat >= 5.0 +location: libmisc-1.0.tar.gz diff --git a/tests/loader/external/1/testing/packages b/tests/loader/external/1/testing/packages index ac5ab95..bdebece 100644 --- a/tests/loader/external/1/testing/packages +++ b/tests/loader/external/1/testing/packages @@ -1,9 +1,9 @@ : 1 name: libmisc -version: 1.1 -summary: The Expat Library +version: 2.4.0 +summary: The Misc Library license: MIT url: http://www.example.com/misc/ email: misc-users@example.com depends: libexpat >= 5.0 -location: libmisc-1.1.tar.gz +location: libmisc-2.4.0.tar.gz diff --git a/tests/loader/internal/1/math/packages b/tests/loader/internal/1/math/packages index d55a9e3..4d34c13 100644 --- a/tests/loader/internal/1/math/packages +++ b/tests/loader/internal/1/math/packages @@ -1,4 +1,19 @@ : 1 +name: libstudxml +version: 1.0.0-1 +summary: Modern C++ XML API +license: MIT +tags: c++, xml, parser, serializer, pull, streaming, modern +description-file: README +changes-file: NEWS +url: http://www.codesynthesis.com/projects/libstudxml/ +email: studxml-users@codesynthesis.com; Public mailing list, posts by\ + non-members are allowed but moderated. +package-email: boris@codesynthesis.com; Direct email to the author. +depends: libexpat >= 2.0.0 +depends: libgenx +location: libstudxml-1.0.0-1.tar.gz +: name: libexp version: 1+1.2 summary: The exponent @@ -31,6 +46,7 @@ email: foo-users@example.com; Public mailing list. Read FAQ before posting. package-url: http://www.example.com/foo/pack; Package details. package-email: pack@example.com; Current packager. depends: libmisc < 1.1 | libmisc > 2.3.0; Crashes with 1.1.0-2.3.0. +depends: libexp >= 1.0 depends: ? libstudxml | libexpat; The newer the better. requires: linux | windows | macosx; Symbian support is coming. requires: c++11 diff --git a/tests/loader/internal/1/stable/packages b/tests/loader/internal/1/stable/packages index 8d1e2fd..756a562 100644 --- a/tests/loader/internal/1/stable/packages +++ b/tests/loader/internal/1/stable/packages @@ -9,21 +9,6 @@ email: foo-users@example.com depends: libmisc >= 2.0.0 location: libfoo-1.2.3-4.tar.gz : -name: libstudxml -version: 1.0.0-1 -summary: Modern C++ XML API -license: MIT -tags: c++, xml, parser, serializer, pull, streaming, modern -description-file: README -changes-file: NEWS -url: http://www.codesynthesis.com/projects/libstudxml/ -email: studxml-users@codesynthesis.com; Public mailing list, posts by\ - non-members are allowed but moderated. -package-email: boris@codesynthesis.com; Direct email to the author. -depends: libexpat >= 2.0.0 -depends: libgenx -location: libstudxml-1.0.0-1.tar.gz -: name: libfoo version: 1.2.2 summary: The Foo library diff --git a/web/module b/web/module index 25c4bf2..1774884 100644 --- a/web/module +++ b/web/module @@ -58,8 +58,7 @@ namespace web sequence_error (std::string d): std::runtime_error (std::move (d)) {} }; - template - using optional = butl::optional; + using butl::optional; struct name_value { @@ -74,7 +73,7 @@ namespace web }; using name_values = std::vector; - using path = butl::path; + using butl::path; class request { -- cgit v1.1