From 2319d01e262b705716e19474865f555f3ca81a7d Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Mon, 1 May 2017 13:50:13 +0300 Subject: Add hxx extension for headers and lib prefix for library dirs --- bpkg/package.hxx | 917 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 917 insertions(+) create mode 100644 bpkg/package.hxx (limited to 'bpkg/package.hxx') diff --git a/bpkg/package.hxx b/bpkg/package.hxx new file mode 100644 index 0000000..bd45e9c --- /dev/null +++ b/bpkg/package.hxx @@ -0,0 +1,917 @@ +// file : bpkg/package.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BPKG_PACKAGE_HXX +#define BPKG_PACKAGE_HXX + +#include +#include +#include +#include +#include // static_assert + +#include +#include + +#include + +#include +#include + +#pragma db model version(3, 3, closed) + +namespace bpkg +{ + // Compare two lazy pointers via the pointed-to object ids. + // + struct compare_lazy_ptr + { + template + bool + operator() (const P& x, const P& y) const + { + return x.object_id () < y.object_id (); + } + }; + + // path + // + using optional_string = optional; + using optional_path = optional; + using optional_dir_path = optional; + + #pragma db map type(path) as(string) \ + to((?).string ()) from(bpkg::path (?)) + + #pragma db map type(optional_path) as(bpkg::optional_string) \ + to((?) ? (?)->string () : bpkg::optional_string ()) \ + from((?) ? bpkg::path (*(?)) : bpkg::optional_path ()) + + #pragma db map type(dir_path) as(string) \ + to((?).string ()) from(bpkg::dir_path (?)) + + #pragma db map type(optional_dir_path) as(bpkg::optional_string) \ + to((?) ? (?)->string () : bpkg::optional_string ()) \ + from((?) ? bpkg::dir_path (*(?)) : bpkg::optional_dir_path ()) + + // timestamp + // + using butl::timestamp; + using butl::timestamp_unknown; + + // Ensure that timestamp can be represented in nonoseconds without loss of + // accuracy, so the following ODB mapping is adequate. + // + static_assert ( + std::ratio_greater_equal::value, + "The following timestamp ODB mapping is invalid"); + + // As pointed out in butl/timestamp we will overflow in year 2262, but by + // that time some larger basic type will be available for mapping. + // + #pragma db map type(timestamp) as(uint64_t) \ + to(std::chrono::duration_cast ( \ + (?).time_since_epoch ()).count ()) \ + from(butl::timestamp ( \ + std::chrono::duration_cast ( \ + std::chrono::nanoseconds (?)))) + + // An image type that is used to map version to the database since + // there is no way to modify individual components directly. We have + // to define it before including since some value + // types that are defined there use version as their data members. + // + #pragma db value + struct _version + { + uint16_t epoch; + string canonical_upstream; + string canonical_release; + uint16_t revision; + string upstream; + optional release; + }; +} + +#include + +#include + +// Prevent assert() macro expansion in get/set expressions. This should +// appear after all #include directives since the assert() macro is +// redefined in each inclusion. +// +#ifdef ODB_COMPILER_HXX +# undef assert +# define assert assert +void assert (int); +#endif + +namespace bpkg +{ + // version + // + // Sometimes we need to split the version into two parts: the part + // that goes into the object id (epoch, canonical upstream, canonical + // release, revision) and the original upstream and release. This is what + // the canonical_version and upstream_version value types are for. Note that + // upstream_version derives from version and uses it as storage. The idea + // here is this: when we split the version, we often still want to have the + // "whole" version object readily accessible and that's exactly what this + // strange contraption is for. See available_package for an example + // on how everything fits together. + // + // + #pragma db value + struct canonical_version + { + uint16_t epoch; + string canonical_upstream; + string canonical_release; + uint16_t revision; + + // By default SQLite3 uses BINARY collation for TEXT columns. So while this + // means we don't need to do anything special to make "absent" (~) and + // specified canonical releases compare properly, better make it explicit + // in case the Unicode Collation Algorithm (UCA, where '~' < 'a') becomes + // the default. + // + #pragma db member(canonical_release) options("COLLATE BINARY") + }; + + #pragma db value transient + struct upstream_version: version + { + #pragma db member(upstream_) virtual(string) \ + get(this.upstream) \ + set(this = bpkg::version (0, std::move (?), std::string (), 0)) + + #pragma db member(release_) virtual(optional_string) \ + get(this.release) \ + set(this = bpkg::version ( \ + 0, std::move (this.upstream), std::move (?), 0)) + + upstream_version () = default; + upstream_version (version v): version (move (v)) {} + upstream_version& + operator= (version v) {version& b (*this); b = v; return *this;} + + void + init (const canonical_version& cv, const upstream_version& uv) + { + *this = version (cv.epoch, uv.upstream, uv.release, cv.revision); + assert (cv.canonical_upstream == canonical_upstream && + cv.canonical_release == canonical_release); + } + }; + + #pragma db map type(version) as(_version) \ + to(bpkg::_version{(?).epoch, \ + (?).canonical_upstream, \ + (?).canonical_release, \ + (?).revision, \ + (?).upstream, \ + (?).release}) \ + from(bpkg::version ((?).epoch, \ + std::move ((?).upstream), \ + std::move ((?).release), \ + (?).revision)) + + using optional_version = optional; + using _optional_version = optional<_version>; + + #pragma db map type(optional_version) as(_optional_version) \ + to((?) \ + ? bpkg::_version{(?)->epoch, \ + (?)->canonical_upstream, \ + (?)->canonical_release, \ + (?)->revision, \ + (?)->upstream, \ + (?)->release} \ + : bpkg::_optional_version ()) \ + from((?) \ + ? bpkg::version ((?)->epoch, \ + std::move ((?)->upstream), \ + std::move ((?)->release), \ + (?)->revision) \ + : bpkg::optional_version ()) + + // repository_location + // + #pragma db map type(repository_location) as(string) \ + to((?).string ()) from(bpkg::repository_location (?)) + + + // repository + // + #pragma db object pointer(shared_ptr) session + class repository + { + public: + // We use a weak pointer for prerequisite repositories because we + // could have cycles. No cycles in complements, thought. + // + using complements_type = + std::set, compare_lazy_ptr>; + using prerequisites_type = + std::set, compare_lazy_ptr>; + + string name; // Object id (canonical name). + repository_location location; + complements_type complements; + prerequisites_type prerequisites; + + // Used to detect recursive fetching. Will probably be replaced + // by the 'repositories' file timestamp or hashsum later. + // + #pragma db transient + bool fetched = false; + + public: + explicit + repository (repository_location l): location (move (l)) + { + name = location.canonical_name (); + } + + // Database mapping. + // + #pragma db member(name) id + + #pragma db member(location) \ + 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 view object(repository) query(repository::name != "" && (?)) + struct repository_count + { + #pragma db column("count(*)") + size_t result; + + operator size_t () const {return result;} + }; + + + // package_location + // + #pragma db value + struct package_location + { + using repository_type = bpkg::repository; + + lazy_shared_ptr repository; + path location; // Relative to the repository. + }; + + // dependencies + // + #pragma db value(dependency_constraint) definition + #pragma db value(dependency) definition + #pragma db member(dependency::constraint) column("") + #pragma db value(dependency_alternatives) definition + + using dependencies = vector; + + // Wildcard version. Satisfies any dependency constraint and is represented + // as 0+0 (which is also the "stub version"; since a real version is always + // greater than the stub version, we reuse it to signify a special case). + // + extern const version wildcard_version; + + // available_package + // + #pragma db value + struct available_package_id + { + string name; + canonical_version version; + + available_package_id () = default; + available_package_id (string, const bpkg::version&); + }; + + bool + operator< (const available_package_id&, const available_package_id&); + + #pragma db object pointer(shared_ptr) session + class available_package + { + public: + using version_type = bpkg::version; + + available_package_id id; + upstream_version version; + + // List of repositories to which this package version belongs (yes, + // in our world, it can be in multiple, unrelated repositories). + // + // Note that if the repository is the special root repository (its + // location is empty), then this is a transient (or "fake") object + // for an existing package archive or package directory. In this + // case the location is the path to the archive/directory and to + // determine which one it is, use file/dir_exists(). While on the + // topic of fake available_package objects, when one is created for + // a selected package (see make_available()), this list is left empty + // with the thinking being that since the package is already in at + // least fetched state, we shouldn't be needing its location. + // + vector locations; //@@ Map? + + // Package manifest data. + // + using dependencies_type = bpkg::dependencies; + + dependencies_type dependencies; + + // Present for non-transient objects only. + // + optional sha256sum; + + private: + #pragma db transient + mutable optional system_version_; + + public: + available_package (package_manifest&& m) + : id (move (m.name), m.version), + version (move (m.version)), + dependencies (move (m.dependencies)), + sha256sum (move (m.sha256sum)) {} + + // Create a stub available package with a fixed system version. This + // constructor is only used to create transient/fake available packages + // based on the system selected packages. + // + available_package (string n, version_type sysv) + : id (move (n), wildcard_version), + version (wildcard_version), + system_version_ (sysv) {} + + bool + stub () const {return version.compare (wildcard_version, true) == 0;} + + // Return package system version if one has been discovered. Note that + // we do not implicitly assume a wildcard version. + // + const version_type* + system_version () const + { + if (!system_version_) + { + if (const system_package* sp = system_repository.find (id.name)) + { + // Only cache if it is authoritative. + // + if (sp->authoritative) + system_version_ = sp->version; + else + return &sp->version; + } + } + + return system_version_ ? &*system_version_ : nullptr; + } + + // As above but also return an indication if the version information is + // authoritative. + // + pair + system_version_authoritative () const + { + const system_package* sp (system_repository.find (id.name)); + + if (!system_version_) + { + if (sp != nullptr) + { + // Only cache if it is authoritative. + // + if (sp->authoritative) + system_version_ = sp->version; + else + return make_pair (&sp->version, false); + } + } + + return make_pair (system_version_ ? &*system_version_ : nullptr, + sp != nullptr ? sp->authoritative : false); + } + + // Database mapping. + // + #pragma db member(id) id column("") + #pragma db member(version) set(this.version.init (this.id.version, (?))) + #pragma db member(locations) id_column("") value_column("") \ + unordered value_not_null + + // dependencies + // + using _dependency_key = odb::nested_key; + using _dependency_alternatives_type = + std::map<_dependency_key, dependency>; + + #pragma db value(_dependency_key) + #pragma db member(_dependency_key::outer) column("dependency_index") + #pragma db member(_dependency_key::inner) column("index") + + #pragma db member(dependencies) id_column("") value_column("") + #pragma db member(dependency_alternatives) \ + virtual(_dependency_alternatives_type) \ + after(dependencies) \ + get(odb::nested_get (this.dependencies)) \ + set(odb::nested_set (this.dependencies, std::move (?))) \ + id_column("") key_column("") value_column("dep_") + + private: + friend class odb::access; + available_package () = default; + }; + + #pragma db view object(available_package) + struct available_package_count + { + #pragma db column("count(*)") + size_t result; + + operator size_t () const {return result;} + }; + + // Only return packages that are in the specified repository or its + // complements, recursively. While you could maybe come up with a + // (barely comprehensible) view/query to achieve this, doing it on + // the "client side" is definitely more straightforward. + // + vector> + filter (const shared_ptr&, odb::result&&); + + pair, shared_ptr> + filter_one (const shared_ptr&, odb::result&&); + + // package_state + // + enum class package_state + { + transient, // No longer or not yet in the database. + broken, + fetched, + unpacked, + configured + }; + + string + to_string (package_state); + + package_state + to_package_state (const string&); // May throw invalid_argument. + + inline ostream& + operator<< (ostream& os, package_state s) {return os << to_string (s);} + + #pragma db map type(package_state) as(string) \ + to(to_string (?)) \ + from(bpkg::to_package_state (?)) + + // package_substate + // + enum class package_substate + { + none, + system // System package; valid states: configured. + }; + + string + to_string (package_substate); + + package_substate + to_package_substate (const string&); // May throw invalid_argument. + + inline ostream& + operator<< (ostream& os, package_substate s) {return os << to_string (s);} + + #pragma db map type(package_substate) as(string) \ + to(to_string (?)) \ + from(bpkg::to_package_substate (?)) + + // package + // + #pragma db object pointer(shared_ptr) session + class selected_package + { + public: + using version_type = bpkg::version; + + string name; // Object id. + version_type version; + package_state state; + package_substate substate; + + // The hold flags indicate whether this package and/or version + // should be retained in the configuration. A held package will + // not be automatically removed. A held version will not be + // automatically upgraded. Note also that the two flags are + // orthogonal: we may want to keep a specific version of the + // package as long as it has dependents. + // + bool hold_package; + bool hold_version; + + // Repository from which this package came. Note that it is not + // a pointer to the repository object because it could be wiped + // out (e.g., as a result of rep-fetch). We call such packages + // "orphans". While we can get a list of orphan's prerequisites + // (by loading its manifest), we wouldn't know which repository + // to use as a base to resolve them. As a result, an orphan that + // is not already configured (and thus has all its prerequisites + // resolved) is not very useful and can only be purged. + // + repository_location repository; + + // Path to the archive of this package, if any. If not absolute, + // then it is relative to the configuration directory. The purge + // flag indicates whether the archive should be removed when the + // packaged is purged. If the archive is not present, it should + // be false. + // + optional archive; + bool purge_archive; + + // Path to the source directory of this package, if any. If not + // absolute, then it is relative to the configuration directory. + // The purge flag indicates whether the directory should be + // removed when the packaged is purged. If the source directory + // is not present, it should be false. + // + optional src_root; + bool purge_src; + + // Path to the output directory of this package, if any. It is + // always relative to the configuration directory and currently + // is always -. It is only set once the package + // is configured and its main purse is to keep track of what + // needs to be cleaned by the user before a broken package can + // be purged. Note that it could be the same as out_root. + // + optional out_root; + + // A map of "effective" prerequisites (i.e., pointers to other selected + // packages) to optional dependency constraint. Note that because it is a + // single constraint, we don't support multiple dependencies on the same + // package (e.g., two ranges of versions). See pkg_configure(). + // + using prerequisites_type = std::map, + optional, + compare_lazy_ptr>; + prerequisites_type prerequisites; + + bool + system () const + { + // The system substate is only valid for the configured state. + // + assert (substate != package_substate::system || + state == package_state::configured); + + return substate == package_substate::system; + } + + // Represent the wildcard version with the "*" string. Represent naturally + // all other versions. + // + string + version_string () const; + + // 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; + selected_package () = default; + }; + + // Print the package name, version and the 'sys:' prefix for the system + // substate. The wildcard version is represented with the "*" string. + // + ostream& + operator<< (ostream&, const selected_package&); + + // certificate + // + // Information extracted from a repository X.509 certificate. The actual + // certificate is stored on disk as .bpkg/certs/.pem (we have + // to store it as a file because that's the only way to pass it to openssl). + // + // If a repository is not authenticated (has no certificate/signature, + // called unauth from now on), then we ask for the user's confirmation and + // create a dummy certificate in order not to ask for the same confirmation + // (for this repository) on next fetch. The problem is, there could be + // multiple sections in such a repository and it would be annoying to + // confirm all of them. So what we are going to do is create a dummy + // certificate not for this specific repository location but for a + // repository location only up to the version, so the name member will + // contain the name prefix rather than the full name (just like a normal + // certificate would). The fingerprint member for such a dummy certificate + // contains the SHA256 checksum of this name. Members other then name and + // fingerprint are meaningless for the dummy certificate. + // + #pragma db object pointer(shared_ptr) session + class certificate + { + public: + string fingerprint; // Object id (note: SHA256 fingerprint). + + string name; // CN component of Subject. + string organization; // O component of Subject. + string email; // email: in Subject Alternative Name. + + timestamp start_date; // notBefore (UTC) + timestamp end_date; // notAfter (UTC) + + bool + dummy () const {return start_date == timestamp_unknown;} + + bool + expired () const + { + assert (!dummy ()); + return timestamp::clock::now () > end_date; + } + + public: + certificate (string f, + string n, + string o, + string e, + timestamp sd, + timestamp ed) + : fingerprint (move (f)), + name (move (n)), + organization (move (o)), + email (move (e)), + start_date (move (sd)), + end_date (move (ed)) + { + } + + // Create dummy certificate. + // + certificate (string f, string n) + : fingerprint (move (f)), + name (move (n)), + start_date (timestamp_unknown), + end_date (timestamp_unknown) + { + } + + // Database mapping. + // + #pragma db member(fingerprint) id + + private: + friend class odb::access; + certificate () = default; + }; + + // Note: prints all the certificate information on one line so mostly + // useful for tracing. + // + ostream& + operator<< (ostream&, const certificate&); + + // Return a list of packages that depend on this package along with + // their constraints. + // + /* + #pragma db view object(selected_package) \ + container(selected_package::prerequisites = pp inner: pp.key) + struct package_dependent + { + #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(selected_package) \ + table("selected_package_prerequisites" = "pp" inner: \ + "pp.prerequisite = " + selected_package::name) + struct package_dependent + { + #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, + // canonical_release, and revision data members. The idea is that this + // works for both query members of types version and canonical_version + // as well as for comparing canonical_version to version. + // + template + inline auto + compare_version_eq (const T1& x, const T2& y, bool revision) + -> decltype (x.epoch == y.epoch) + { + // Since we don't quite know what T1 and T2 are (and where the resulting + // expression will run), let's not push our luck with something like + // (!revision || x.revision == y.revision). + // + auto r (x.epoch == y.epoch && + x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release); + + return revision + ? r && x.revision == y.revision + : r; + } + + /* + Currently unused (and probably should stay that way). + + template + inline auto + operator== (const T1& x, const T2& y) -> decltype (x.epoch == y.epoch) + { + return compare_version_eq (x, y, true); + } + */ + + template + inline auto + compare_version_ne (const T1& x, const T2& y, bool revision) + -> decltype (x.epoch == y.epoch) + { + auto r (x.epoch != y.epoch || + x.canonical_upstream != y.canonical_upstream || + x.canonical_release != y.canonical_release); + + return revision + ? r || x.revision != y.revision + : r; + } + + template + inline auto + operator!= (const T1& x, const T2& y) -> decltype (x.epoch != y.epoch) + { + return compare_version_ne (x, y, true); + } + + template + inline auto + compare_version_lt (const T1& x, const T2& y, bool revision) + -> decltype (x.epoch == y.epoch) + { + auto r ( + x.epoch < y.epoch || + (x.epoch == y.epoch && x.canonical_upstream < y.canonical_upstream) || + (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && + x.canonical_release < y.canonical_release)); + + return revision + ? r || + (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release && x.revision < y.revision) + : r; + } + + template + inline auto + operator< (const T1& x, const T2& y) -> decltype (x.epoch < y.epoch) + { + return compare_version_lt (x, y, true); + } + + template + inline auto + compare_version_le (const T1& x, const T2& y, bool revision) + -> decltype (x.epoch == y.epoch) + { + auto r ( + x.epoch < y.epoch || + (x.epoch == y.epoch && x.canonical_upstream < y.canonical_upstream)); + + return revision + ? r || + (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && + x.canonical_release < y.canonical_release) || + (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release && x.revision <= y.revision) + : r || + (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && + x.canonical_release <= y.canonical_release); + } + + /* + Currently unused (and probably should stay that way). + + template + inline auto + operator<= (const T1& x, const T2& y) -> decltype (x.epoch <= y.epoch) + { + return compare_version_le (x, y, true); + } + */ + + template + inline auto + compare_version_gt (const T1& x, const T2& y, bool revision) + -> decltype (x.epoch == y.epoch) + { + auto r ( + x.epoch > y.epoch || + (x.epoch == y.epoch && x.canonical_upstream > y.canonical_upstream) || + (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && + x.canonical_release > y.canonical_release)); + + return revision + ? r || + (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release && x.revision > y.revision) + : r; + } + + template + inline auto + operator> (const T1& x, const T2& y) -> decltype (x.epoch > y.epoch) + { + return compare_version_gt (x, y, true); + } + + template + inline auto + compare_version_ge (const T1& x, const T2& y, bool revision) + -> decltype (x.epoch == y.epoch) + { + auto r ( + x.epoch > y.epoch || + (x.epoch == y.epoch && x.canonical_upstream > y.canonical_upstream)); + + return revision + ? r || + (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && + x.canonical_release > y.canonical_release) || + (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && + x.canonical_release == y.canonical_release && x.revision >= y.revision) + : r || + (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && + x.canonical_release >= y.canonical_release); + } + + template + inline auto + operator>= (const T1& x, const T2& y) -> decltype (x.epoch >= y.epoch) + { + return compare_version_ge (x, y, true); + } + + template + inline auto + order_by_version_desc (const T& x) -> //decltype ("ORDER BY" + x.epoch) + decltype (x.epoch == 0) + { + return "ORDER BY" + + x.epoch + "DESC," + + x.canonical_upstream + "DESC," + + x.canonical_release + "DESC," + + x.revision + "DESC"; + } + + template + inline auto + order_by_revision_desc (const T& x) -> //decltype ("ORDER BY" + x.epoch) + decltype (x.revision == 0) + { + return "ORDER BY" + x.revision + "DESC"; + } +} + +#include + +#endif // BPKG_PACKAGE_HXX -- cgit v1.1