diff options
Diffstat (limited to 'libbpkg/manifest.hxx')
-rw-r--r-- | libbpkg/manifest.hxx | 1311 |
1 files changed, 1013 insertions, 298 deletions
diff --git a/libbpkg/manifest.hxx b/libbpkg/manifest.hxx index d25e98c..feb3b96 100644 --- a/libbpkg/manifest.hxx +++ b/libbpkg/manifest.hxx @@ -1,5 +1,4 @@ // file : libbpkg/manifest.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #ifndef LIBBPKG_MANIFEST_HXX @@ -9,16 +8,16 @@ #include <string> #include <vector> #include <cassert> -#include <cstdint> // uint16_t +#include <cstdint> // uint*_t #include <ostream> -#include <utility> // move() -#include <stdexcept> // logic_error +#include <utility> // move(), pair #include <functional> -#include <libbutl/url.mxx> -#include <libbutl/path.mxx> -#include <libbutl/optional.mxx> -#include <libbutl/small-vector.mxx> +#include <libbutl/url.hxx> +#include <libbutl/path.hxx> +#include <libbutl/optional.hxx> +#include <libbutl/small-vector.hxx> +#include <libbutl/standard-version.hxx> #include <libbutl/manifest-forward.hxx> #include <libbpkg/package-name.hxx> @@ -68,13 +67,20 @@ namespace bpkg // std::invalid_argument if the passed string is not a valid version // representation. // + enum flags + { + none = 0, + fold_zero_revision = 0x01, + allow_iteration = 0x02 + }; + explicit - version (const std::string& v, bool fold_zero_revision = true) - : version (v.c_str (), fold_zero_revision) {} + version (const std::string& v, flags fl = fold_zero_revision) + : version (v.c_str (), fl) {} explicit - version (const char* v, bool fold_zero_revision = true) - : version (data_type (v, data_type::parse::full, fold_zero_revision)) + version (const char* v, flags fl = fold_zero_revision) + : version (data_type (v, data_type::parse::full, fl)) { } @@ -95,7 +101,7 @@ namespace bpkg version (version&&) = default; version (const version&) = default; - version& operator= (version&&); + version& operator= (version&&) noexcept; version& operator= (const version&); // If the revision is ignored, then the iteration (that semantically @@ -104,23 +110,12 @@ namespace bpkg std::string string (bool ignore_revision = false, bool ignore_iteration = false) const; - bool - operator< (const version& v) const noexcept {return compare (v) < 0;} - - bool - operator> (const version& v) const noexcept {return compare (v) > 0;} - - bool - operator== (const version& v) const noexcept {return compare (v) == 0;} - - bool - operator<= (const version& v) const noexcept {return compare (v) <= 0;} - - bool - operator>= (const version& v) const noexcept {return compare (v) >= 0;} - - bool - operator!= (const version& v) const noexcept {return compare (v) != 0;} + bool operator< (const version& v) const noexcept; + bool operator> (const version& v) const noexcept; + bool operator== (const version& v) const noexcept; + bool operator<= (const version& v) const noexcept; + bool operator>= (const version& v) const noexcept; + bool operator!= (const version& v) const noexcept; // If the revision is ignored, then the iteration is also ignored, // regardless of the argument (see above for details). @@ -128,28 +123,7 @@ namespace bpkg int compare (const version& v, bool ignore_revision = false, - bool ignore_iteration = false) const noexcept - { - if (epoch != v.epoch) - return epoch < v.epoch ? -1 : 1; - - if (int c = canonical_upstream.compare (v.canonical_upstream)) - return c; - - if (int c = canonical_release.compare (v.canonical_release)) - return c; - - if (!ignore_revision) - { - if (revision != v.revision) - return revision < v.revision ? -1 : 1; - - if (!ignore_iteration && iteration != v.iteration) - return iteration < v.iteration ? -1 : 1; - } - - return 0; - } + bool ignore_iteration = false) const noexcept; bool empty () const noexcept @@ -169,7 +143,7 @@ namespace bpkg { enum class parse {full, upstream, release}; - data_type (const char*, parse, bool fold_zero_revision); + data_type (const char*, parse, flags); // Note that there is no iteration component as it can't be present in // the string representation passed to the ctor. @@ -178,6 +152,7 @@ namespace bpkg std::string upstream; butl::optional<std::string> release; butl::optional<std::uint16_t> revision; + std::uint32_t iteration; std::string canonical_upstream; std::string canonical_release; }; @@ -188,7 +163,7 @@ namespace bpkg upstream (std::move (d.upstream)), release (std::move (d.release)), revision (d.revision), - iteration (0), + iteration (d.iteration), canonical_upstream (std::move (d.canonical_upstream)), canonical_release (std::move (d.canonical_release)) {} }; @@ -199,6 +174,11 @@ namespace bpkg return os << (v.empty () ? "<empty-version>" : v.string ()); } + version::flags operator& (version::flags, version::flags); + version::flags operator| (version::flags, version::flags); + version::flags operator&= (version::flags&, version::flags); + version::flags operator|= (version::flags&, version::flags); + // priority // class priority @@ -215,11 +195,17 @@ namespace bpkg operator value_type () const {return value;} }; - // description - // description-file - // change - // change-file + // language // + struct language + { + std::string name; + bool impl; // True if implementation-only. + + language (): impl (false) {} + language (std::string n, bool i): name (std::move (n)), impl (i) {} + }; + class LIBBPKG_EXPORT text_file { public: @@ -245,14 +231,80 @@ namespace bpkg text_file (path_type p, std::string c) : file (true), path (std::move (p)), comment (std::move (c)) {} - text_file (text_file&&); + text_file (text_file&&) noexcept; text_file (const text_file&); - text_file& operator= (text_file&&); + text_file& operator= (text_file&&) noexcept; text_file& operator= (const text_file&); ~text_file (); }; + enum class text_type + { + plain, + common_mark, + github_mark + }; + + LIBBPKG_EXPORT std::string + to_string (text_type); + + // Throw std::invalid_argument if the argument is not a well-formed text + // type. Otherwise, return nullopt for an unknown text variant. + // + LIBBPKG_EXPORT butl::optional<text_type> + to_text_type (const std::string&); + + inline std::ostream& + operator<< (std::ostream& os, text_type t) + { + return os << to_string (t); + } + + // description + // description-file + // description-type + // package-description + // package-description-file + // package-description-type + // change + // change-file + // change-type + // + class LIBBPKG_EXPORT typed_text_file: public text_file + { + public: + butl::optional<std::string> type; + + // File text constructor. + // + explicit + typed_text_file (std::string s = "", + butl::optional<std::string> t = butl::nullopt) + : text_file (std::move (s)), type (std::move (t)) {} + + // File reference constructor. + // + typed_text_file (path_type p, + std::string c, + butl::optional<std::string> t = butl::nullopt) + : text_file (std::move (p), std::move (c)), type (std::move (t)) {} + + // Return the type value if present, text_type::github_mark if it refers + // to a file with the .md or .markdown extension and text_type::plain if + // it refers to a file with the .txt extension or no extension or the text + // does not come from a file. Depending on the ignore_unknown value either + // throw std::invalid_argument or return nullopt if the type value or the + // file extension is unknown. + // + // Note: also throws std::invalid_argument if the type is not well-formed. + // This, however, may not happen for an object created by the package + // manifest parser since it has already verified that. + // + butl::optional<text_type> + effective_type (bool ignore_unknown = false) const; + }; + // license // class licenses: public butl::small_vector<std::string, 1> @@ -275,19 +327,21 @@ namespace bpkg // - is not local (the scheme is not `file`) // - authority is present and is not empty // - // See libbutl/url.mxx for details. + // See libbutl/url.hxx for details. + // + // NOTE: this class must not be DLL-exported wholesale (non-exported base). // - class url: public butl::url + class manifest_url: public butl::url { public: std::string comment; // Throw invalid_argument on parsing or constraints checking error. // - explicit - url (const std::string& u, std::string c = ""); + explicit LIBBPKG_EXPORT + manifest_url (const std::string& u, std::string c = ""); - url () = default; + manifest_url () = default; }; // email @@ -377,68 +431,366 @@ namespace bpkg } inline bool - operator== (const version_constraint& x, const version_constraint& y) - { - return x.min_version == y.min_version && x.max_version == y.max_version && - x.min_open == y.min_open && x.max_open == y.max_open; - } + operator== (const version_constraint&, const version_constraint&); inline bool - operator!= (const version_constraint& x, const version_constraint& y) - { - return !(x == y); - } + operator!= (const version_constraint&, const version_constraint&); struct LIBBPKG_EXPORT dependency { package_name name; butl::optional<version_constraint> constraint; + dependency () = default; + dependency (package_name n, butl::optional<version_constraint> c) + : name (std::move (n)), constraint (std::move (c)) {} + + // Parse the dependency string representation in the + // `<name> [<version-constraint>]` form. Throw std::invalid_argument if + // the value is invalid. + // + explicit + dependency (std::string); + std::string string () const; }; + std::ostream& + operator<< (std::ostream&, const dependency&); + + // depends + // + // The dependency alternative can be represented in one of the following + // forms. + // + // Single-line form: + // + // <dependencies> ['?' <enable-condition>] [<reflect-config>] + // + // <dependencies> = <dependency> | + // ({ <dependency> [ <dependency>]* } [<version-constraint>]) + // + // <enable-condition> - buildfile evaluation context + // <reflect-config> - dependent package configuration variable assignment + // + // If the version constraint is specified after the dependency group, it + // only applies to dependencies without a version constraint. + // + // Multi-line forms: + // + // <dependencies> + // { + // enable <enable-condition> + // + // prefer + // { + // <prefer-config> + // } + // + // accept <accept-condition> + // + // reflect + // { + // <reflect-config> + // } + // } + // | + // <dependencies> + // { + // enable <enable-condition> + // + // require + // { + // <require-config> + // } + // + // reflect + // { + // <reflect-config> + // } + // } + // + // <prefer-config> - buildfile fragment containing dependency packages + // configuration variables assignments + // + // <accept-condition> - buildfile evaluation context + // + // <require-config> - buildfile fragment containing dependency packages + // configuration variables assignments + // + // <reflect-config> - buildfile fragment containing dependent package + // configuration variables assignments + // + // In the multi-line form the block may contain comments besides the + // clauses. The '#' character starts a single-line comment which spans + // until the end of the line. Unless it is followed with '\' followed by + // the newline in which case this is a multi-line comment which spans + // until the closing '#\' is encountered. + // + // The dependency alternative is only considered by bpkg if the enable + // condition evaluates to true. If the enable clause is not specified, then + // it is always considered. + // + // The prefer clause specifies the preferred dependency package + // configuration that may potentially differ from the resulting + // configuration after the preferred/required configurations from all the + // selected dependency alternatives of all the dependent packages are + // "negotiated" by bpkg. The accept clause is used to verify that the + // resulting configuration is still acceptable for the dependent + // package. The accept clause must always be specified if the prefer clause + // is specified. + // + // The require clause specifies the only acceptable dependency packages + // configuration. It is a shortcut for specifying the prefer/accept clauses, + // where the accept condition verifies all the variable values assigned in + // the prefer clause. The require clause and the prefer/accept clause pair + // are optional and are mutually exclusive. + // + // The reflect clause specifies the dependent package configuration that + // should be used if the alternative is selected. + // + // All clauses are optional but at least one of them must be specified. + // + class dependency_alternative: public butl::small_vector<dependency, 1> + { + public: + butl::optional<std::string> enable; + butl::optional<std::string> reflect; + butl::optional<std::string> prefer; + butl::optional<std::string> accept; + butl::optional<std::string> require; + + dependency_alternative () = default; + dependency_alternative (butl::optional<std::string> e, + butl::optional<std::string> r, + butl::optional<std::string> p, + butl::optional<std::string> a, + butl::optional<std::string> q) + : enable (std::move (e)), + reflect (std::move (r)), + prefer (std::move (p)), + accept (std::move (a)), + require (std::move (q)) {} + + // Can be used to copy a dependency alternative object, while omitting + // some clauses which are no longer needed. + // + dependency_alternative (butl::optional<std::string> e, + butl::optional<std::string> r, + butl::optional<std::string> p, + butl::optional<std::string> a, + butl::optional<std::string> q, + butl::small_vector<dependency, 1> ds) + : small_vector<dependency, 1> (move (ds)), + enable (std::move (e)), + reflect (std::move (r)), + prefer (std::move (p)), + accept (std::move (a)), + require (std::move (q)) {} + + // Return the single-line representation if possible (the prefer and + // require clauses are absent and the reflect clause either absent or + // contains no newlines). + // + LIBBPKG_EXPORT std::string + string () const; + + // Return true if the string() function would return the single-line + // representation. + // + bool + single_line () const + { + return !prefer && + !require && + (!reflect || reflect->find ('\n') == std::string::npos); + } + }; + inline std::ostream& - operator<< (std::ostream& os, const dependency& d) + operator<< (std::ostream& os, const dependency_alternative& da) { - return os << d.string (); + return os << da.string (); } - // depends - // - class dependency_alternatives: public butl::small_vector<dependency, 1> + class dependency_alternatives: + public butl::small_vector<dependency_alternative, 1> { public: - bool conditional; bool buildtime; std::string comment; dependency_alternatives () = default; - dependency_alternatives (bool d, bool b, std::string c) - : conditional (d), buildtime (b), comment (std::move (c)) {} + dependency_alternatives (bool b, std::string c) + : buildtime (b), comment (std::move (c)) {} + + // Parse the dependency alternatives string representation in the form: + // + // [*] <alternative> [ '|' <alternative>]* [; <comment>] + // + // Where <alternative> can be single or multi-line (see above). Note also + // that leading `*` and trailing comment can be on separate lines. Throw + // manifest_parsing if the value is invalid. + // + // Use the dependent package name to verify that the reflect clauses in + // the dependency alternative representations refer to the dependent + // package configuration variable. + // + // Optionally, specify the stream name to use when creating the + // manifest_parsing exception. The start line and column arguments can be + // used to align the exception information with a containing stream. This + // is useful when the alternatives representation is a part of some larger + // text (manifest, etc). + // + // Note that semicolons inside alternatives must be escaped with the + // backslash (not to be treated as the start of a comment). Backslashes at + // the end of buildfile fragment lines need to also be escaped, if + // dependency alternatives representation comes from the manifest file + // (since trailing backslashes in manifest lines has special semantics). + // + explicit LIBBPKG_EXPORT + dependency_alternatives (const std::string&, + const package_name& dependent, + const std::string& name = std::string (), + std::uint64_t line = 1, + std::uint64_t column = 1); + + LIBBPKG_EXPORT std::string + string () const; + + // Return true if there is a conditional alternative in the list. + // + bool + conditional () const; }; - LIBBPKG_EXPORT std::ostream& - operator<< (std::ostream&, const dependency_alternatives&); + inline std::ostream& + operator<< (std::ostream& os, const dependency_alternatives& das) + { + return os << das.string (); + } // requires // - class requirement_alternatives: public butl::small_vector<std::string, 1> + // The requirement alternative string representation is similar to that of + // the dependency alternative with the following differences: + // + // - The requirement id (with or without version) can mean anything (but + // must still be a valid package name). + // + // - Only the enable and reflect clauses are permitted (reflect is allowed + // for potential future support of recognized requirement alternatives, + // for example, C++ standard). + // + // - The simplified representation syntax, where the comment carries the + // main information and thus is mandatory, is also supported (see + // requirement_alternatives for details). For example: + // + // requires: ; X11 libs. + // requires: ? ($windows) ; Only 64-bit. + // requires: ? ; Only 64-bit if on Windows. + // requires: x86_64 ? ; Only if on Windows. + // + class requirement_alternative: public butl::small_vector<std::string, 1> + { + public: + butl::optional<std::string> enable; + butl::optional<std::string> reflect; + + requirement_alternative () = default; + requirement_alternative (butl::optional<std::string> e, + butl::optional<std::string> r) + : enable (std::move (e)), reflect (std::move (r)) {} + + // Return the single-line representation if possible (the reflect clause + // either absent or contains no newlines). + // + LIBBPKG_EXPORT std::string + string () const; + + // Return true if the string() function would return the single-line + // representation. + // + bool + single_line () const + { + return !reflect || reflect->find ('\n') == std::string::npos; + } + + // Return true if this is a single requirement with an empty id or an + // empty enable condition. + // + bool + simple () const + { + return size () == 1 && (back ().empty () || (enable && enable->empty ())); + } + }; + + class requirement_alternatives: + public butl::small_vector<requirement_alternative, 1> { public: - bool conditional; bool buildtime; std::string comment; requirement_alternatives () = default; - requirement_alternatives (bool d, bool b, std::string c) - : conditional (d), buildtime (b), comment (std::move (c)) {} + requirement_alternatives (bool b, std::string c) + : buildtime (b), comment (std::move (c)) {} + + // Parse the requirement alternatives string representation in the + // following forms: + // + // [*] <alternative> [ '|' <alternative>]* [; <comment>] + // [*] [<requirement-id>] [? [<enable-condition>]] ; <comment> + // + // Parsing the second form ends up with a single alternative with a single + // potentially empty requirement id, potentially with an enable condition + // with potentially empty value (see examples above). + // + // Throw manifest_parsing if the value is invalid. + // + // Optionally, specify the stream name to use when creating the + // manifest_parsing exception. The start line and column arguments can be + // used to align the exception information with a containing stream. This + // is useful when the alternatives representation is a part of some larger + // text (manifest, etc). + // + explicit LIBBPKG_EXPORT + requirement_alternatives (const std::string&, + const package_name& dependent, + const std::string& name = std::string (), + std::uint64_t line = 1, + std::uint64_t column = 1); + + LIBBPKG_EXPORT std::string + string () const; + + // Return true if there is a conditional alternative in the list. + // + bool + conditional () const; + + // Return true if this is a single simple requirement alternative. + // + bool + simple () const + { + return size () == 1 && back ().simple (); + } }; + inline std::ostream& + operator<< (std::ostream& os, const requirement_alternatives& ra) + { + return os << ra.string (); + } + class build_constraint { public: - // If true, then the package should not be built for matching + // If true, then the package should not be built for matching target // configurations by automated build bots. // bool exclusion; @@ -473,48 +825,33 @@ namespace bpkg // enum class package_manifest_flags: std::uint16_t { - none = 0x00, - - forbid_file = 0x01, // Forbid *-file manifest values. - forbid_location = 0x02, - forbid_sha256sum = 0x04, - forbid_fragment = 0x08, - forbid_incomplete_dependencies = 0x10, - - require_location = 0x20, - require_sha256sum = 0x40, - require_description_type = 0x80 + none = 0x000, + + forbid_file = 0x001, // Forbid *-file manifest values. + forbid_location = 0x002, + forbid_sha256sum = 0x004, + forbid_fragment = 0x008, + forbid_incomplete_values = 0x010, // depends, <distribution>-version, etc. + + require_location = 0x020, + require_sha256sum = 0x040, + require_text_type = 0x080, // description-type, changes-type, etc. + require_bootstrap_build = 0x100 }; - inline package_manifest_flags - operator&= (package_manifest_flags& x, package_manifest_flags y) - { - return x = static_cast<package_manifest_flags> ( - static_cast<std::uint16_t> (x) & - static_cast<std::uint16_t> (y)); - } + package_manifest_flags + operator& (package_manifest_flags, package_manifest_flags); - inline package_manifest_flags - operator|= (package_manifest_flags& x, package_manifest_flags y) - { - return x = static_cast<package_manifest_flags> ( - static_cast<std::uint16_t> (x) | - static_cast<std::uint16_t> (y)); - } + package_manifest_flags + operator| (package_manifest_flags, package_manifest_flags); - inline package_manifest_flags - operator& (package_manifest_flags x, package_manifest_flags y) - { - return x &= y; - } + package_manifest_flags + operator&= (package_manifest_flags&, package_manifest_flags); - inline package_manifest_flags - operator| (package_manifest_flags x, package_manifest_flags y) - { - return x |= y; - } + package_manifest_flags + operator|= (package_manifest_flags&, package_manifest_flags); - // Build configuration class term. + // Target build configuration class term. // class LIBBPKG_EXPORT build_class_term { @@ -543,9 +880,9 @@ namespace bpkg build_class_term () : operation ('\0'), inverted (false), simple (true), name () {} - build_class_term (build_class_term&&); + build_class_term (build_class_term&&) noexcept; build_class_term (const build_class_term&); - build_class_term& operator= (build_class_term&&); + build_class_term& operator= (build_class_term&&) noexcept; build_class_term& operator= (const build_class_term&); ~build_class_term (); @@ -564,8 +901,8 @@ namespace bpkg // using build_class_inheritance_map = std::map<std::string, std::string>; - // Build configuration class expression. Includes comment and optional - // underlying set. + // Target build configuration class expression. Includes comment and + // optional underlying set. // class LIBBPKG_EXPORT build_class_expr { @@ -614,10 +951,10 @@ namespace bpkg std::string string () const; - // Match a build configuration that belongs to the specified list of - // classes (and recursively to their bases) against the expression. Either - // return or update the result (the latter allows to sequentially matching - // against a list of expressions). + // Match a target build configuration that belongs to the specified list + // of classes (and recursively to their bases) against the expression. + // Either return or update the result (the latter allows to sequentially + // matching against a list of expressions). // // Notes: // @@ -625,7 +962,8 @@ namespace bpkg // inheritance cycles, etc.). // // - The underlying class set doesn't affect the match in any way (it - // should have been used to pre-filter the set of build configurations). + // should have been used to pre-filter the set of target build + // configurations). // void match (const strings&, @@ -633,12 +971,7 @@ namespace bpkg bool& result) const; bool - match (const strings& cs, const build_class_inheritance_map& bs) const - { - bool r (false); - match (cs, bs, r); - return r; - } + match (const strings&, const build_class_inheritance_map&) const; }; inline std::ostream& @@ -647,58 +980,312 @@ namespace bpkg return os << bce.string (); } - enum class text_type + // Build auxiliary configuration name-matching wildcard. Includes optional + // environment name (specified as a suffix in the [*-]build-auxiliary[-*] + // value name) and comment. + // + class LIBBPKG_EXPORT build_auxiliary { - plain, - common_mark, - github_mark + public: + std::string environment_name; + + // Filesystem wildcard pattern for the build auxiliary configuration name. + // + std::string config; + + std::string comment; + + build_auxiliary () = default; + build_auxiliary (std::string en, + std::string cf, + std::string cm) + : environment_name (std::move (en)), + config (std::move (cf)), + comment (std::move (cm)) {} + + // Parse a package manifest value name in the [*-]build-auxiliary[-*] form + // into the pair of the build package configuration name (first) and the + // build auxiliary environment name (second), with an unspecified name + // represented as an empty string. Return nullopt if the value name + // doesn't match this form. + // + static butl::optional<std::pair<std::string, std::string>> + parse_value_name (const std::string&); + }; + + // Package build configuration. Includes comment and optional overrides for + // target build configuration class expressions/constraints, auxiliaries, + // custom bot public keys, and notification emails. + // + // Note that in the package manifest the build bot keys list contains the + // public keys data (std::string type). However, for other use cases it may + // be convenient to store some other key representations (public key object + // pointers represented as key fingerprints, etc; see brep for such a use + // case). + // + template <typename K> + class build_package_config_template + { + public: + using email_type = bpkg::email; + using key_type = K; + + std::string name; + + // Whitespace separated list of potentially double/single-quoted package + // configuration arguments for bpkg-pkg-build command executed by + // automated build bots. + // + std::string arguments; + + std::string comment; + + butl::small_vector<build_class_expr, 1> builds; + std::vector<build_constraint> constraints; + + // Note that all entries in this list must have distinct environment names + // (with empty name being one of the possibilities). + // + std::vector<build_auxiliary> auxiliaries; + + std::vector<key_type> bot_keys; + + butl::optional<email_type> email; + butl::optional<email_type> warning_email; + butl::optional<email_type> error_email; + + build_package_config_template () = default; + + build_package_config_template (std::string n, + std::string a, + std::string c, + butl::small_vector<build_class_expr, 1> bs, + std::vector<build_constraint> cs, + std::vector<build_auxiliary> as, + std::vector<key_type> bks, + butl::optional<email_type> e, + butl::optional<email_type> we, + butl::optional<email_type> ee) + : name (move (n)), + arguments (move (a)), + comment (move (c)), + builds (move (bs)), + constraints (move (cs)), + auxiliaries (move (as)), + bot_keys (move (bks)), + email (move (e)), + warning_email (move (we)), + error_email (move (ee)) {} + + // Built incrementally. + // + explicit + build_package_config_template (std::string n): name (move (n)) {} + + // Return the configuration's build class expressions/constraints if they + // override the specified common expressions/constraints and return the + // latter otherwise (see package_manifest::override() for the override + // semantics details). + // + const butl::small_vector<build_class_expr, 1>& + effective_builds (const butl::small_vector<build_class_expr, 1>& common) + const noexcept + { + return !builds.empty () ? builds : common; + } + + const std::vector<build_constraint>& + effective_constraints (const std::vector<build_constraint>& common) const + noexcept + { + return !builds.empty () || !constraints.empty () ? constraints : common; + } + + // Return the configuration's auxiliaries, if specified, and the common + // ones otherwise. + // + const std::vector<build_auxiliary>& + effective_auxiliaries (const std::vector<build_auxiliary>& common) const + noexcept + { + return !auxiliaries.empty () ? auxiliaries : common; + } + + // Return the configuration's custom bot public keys, if specified, and + // the common ones otherwise. + // + const std::vector<key_type>& + effective_bot_keys (const std::vector<key_type>& common) const noexcept + { + return !bot_keys.empty () ? bot_keys : common; + } + + // Return the configuration's build notification emails if they override + // the specified common build notification emails and return the latter + // otherwise (see package_manifest::override() for the override semantics + // details). + // + const butl::optional<email_type>& + effective_email (const butl::optional<email_type>& common) const noexcept + { + return email || warning_email || error_email ? email : common; + } + + const butl::optional<email_type>& + effective_warning_email (const butl::optional<email_type>& common) const + noexcept + { + return email || warning_email || error_email ? warning_email : common; + } + + const butl::optional<email_type>& + effective_error_email (const butl::optional<email_type>& common) const + noexcept + { + return email || warning_email || error_email ? error_email : common; + } + }; + + using build_package_config = build_package_config_template<std::string>; + + enum class test_dependency_type + { + tests, + examples, + benchmarks }; LIBBPKG_EXPORT std::string - to_string (text_type); + to_string (test_dependency_type); - // Throw std::invalid_argument if the argument is not a well-formed text - // type. Otherwise, return nullopt for an unknown text variant. + // May throw std::invalid_argument. // - LIBBPKG_EXPORT butl::optional<text_type> - to_text_type (const std::string&); // May throw std::invalid_argument. + LIBBPKG_EXPORT test_dependency_type + to_test_dependency_type (const std::string&); inline std::ostream& - operator<< (std::ostream& os, text_type t) + operator<< (std::ostream& os, test_dependency_type t) { return os << to_string (t); } + struct LIBBPKG_EXPORT test_dependency: dependency + { + test_dependency_type type; + bool buildtime; + butl::optional<std::string> enable; + butl::optional<std::string> reflect; + + test_dependency () = default; + test_dependency (package_name n, + test_dependency_type t, + bool b, + butl::optional<version_constraint> c, + butl::optional<std::string> e, + butl::optional<std::string> r) + : dependency {std::move (n), std::move (c)}, + type (t), + buildtime (b), + enable (std::move (e)), + reflect (std::move (r)) {} + + // Parse the test dependency string representation in the + // `[*] <name> [<version-constraint>] ['?' <enable-condition>] [<reflect-config>]` + // form. Throw std::invalid_argument if the value is invalid. + // + // Verify that the reflect clause, if present, refers to the test + // dependency package configuration variable. Note that such variable + // value normally signals the dependent package being tested. + // + test_dependency (std::string, test_dependency_type); + + std::string + string () const; + }; + + // Package's buildfile path and content. + // + struct buildfile + { + // The path is relative to the package's build/ subdirectory with the + // extension stripped. + // + // For example, for the build/config/common.build file the path will be + // config/common. + // + // Note that the actual file path depends on the project's buildfile + // naming scheme and for the config/common example above the actual path + // can also be build2/config/common.build2. + // + butl::path path; + std::string content; + + buildfile () = default; + buildfile (butl::path p, std::string c) + : path (std::move (p)), + content (std::move (c)) {} + }; + + // Binary distribution package information. + // + // The name is prefixed with the <distribution> id, typically name/version + // pair in the <name>[_<version>] form. For example: + // + // debian-name + // debian_10-name + // ubuntu_20.04-name + // + // Currently recognized names: + // + // <distribution>-name + // <distribution>-version + // <distribution>-to-downstream-version + // + // Note that the value format/semantics can be distribution-specific. + // + struct distribution_name_value + { + std::string name; + std::string value; + + distribution_name_value () = default; + distribution_name_value (std::string n, std::string v) + : name (std::move (n)), + value (std::move (v)) {} + + // Return the name's <distribution> component if the name has the + // specified suffix, which is assumed to be valid (-name, etc). + // + butl::optional<std::string> + distribution (const std::string& suffix) const; + }; + class LIBBPKG_EXPORT package_manifest { public: using version_type = bpkg::version; using priority_type = bpkg::priority; - using url_type = bpkg::url; using email_type = bpkg::email; package_name name; version_type version; butl::optional<std::string> upstream_version; + butl::optional<std::string> type; // <name>[, ...] + butl::small_vector<language, 1> languages; // <name>[=impl][, ...] butl::optional<package_name> project; butl::optional<priority_type> priority; std::string summary; - - // @@ Replace with small_vector<licenses, 1>. Note that currently it is - // unsupported by the odb::nested_*() functions that are - // std::vector-specific. - // - std::vector<licenses> license_alternatives; + butl::small_vector<licenses, 1> license_alternatives; butl::small_vector<std::string, 5> topics; butl::small_vector<std::string, 5> keywords; - butl::optional<text_file> description; - butl::optional<std::string> description_type; - butl::small_vector<text_file, 1> changes; - butl::optional<url_type> url; - butl::optional<url_type> doc_url; - butl::optional<url_type> src_url; - butl::optional<url_type> package_url; + butl::optional<typed_text_file> description; + butl::optional<typed_text_file> package_description; + butl::small_vector<typed_text_file, 1> changes; + butl::optional<manifest_url> url; + butl::optional<manifest_url> doc_url; + butl::optional<manifest_url> src_url; + butl::optional<manifest_url> package_url; butl::optional<email_type> email; butl::optional<email_type> package_email; butl::optional<email_type> build_email; @@ -706,12 +1293,42 @@ namespace bpkg butl::optional<email_type> build_error_email; std::vector<dependency_alternatives> dependencies; std::vector<requirement_alternatives> requirements; - butl::small_vector<dependency, 1> tests; - butl::small_vector<dependency, 1> examples; - butl::small_vector<dependency, 1> benchmarks; + butl::small_vector<test_dependency, 1> tests; + // Common build classes, constraints, auxiliaries, and custom bot public + // keys that apply to all configurations unless overridden. + // + // Note that all entries in build_auxiliaries must have distinct + // environment names (with empty name being one of the possibilities). + // butl::small_vector<build_class_expr, 1> builds; std::vector<build_constraint> build_constraints; + std::vector<build_auxiliary> build_auxiliaries; + strings build_bot_keys; + + // Note that the parsing constructor adds the implied (empty) default + // configuration at the beginning of the list. Also note that serialize() + // writes no values for such a configuration. + // + butl::small_vector<build_package_config, 1> build_configs; // 1 for default. + + // If true, then this package use the alternative buildfile naming scheme + // (build2/, .build2). In the manifest serialization this is encoded as + // either *-build or *-build2 value names. + // + butl::optional<bool> alt_naming; + + butl::optional<std::string> bootstrap_build; + butl::optional<std::string> root_build; + + // Additional buildfiles which are potentially included by root.build. + // + std::vector<buildfile> buildfiles; // Buildfiles content. + std::vector<butl::path> buildfile_paths; + + // The binary distributions package information. + // + std::vector<distribution_name_value> distribution_values; // The following values are only valid in the manifest list (and only for // certain repository types). @@ -720,19 +1337,45 @@ namespace bpkg butl::optional<std::string> sha256sum; butl::optional<std::string> fragment; - const package_name& - effective_project () const noexcept {return project ? *project : name;} + // Extract the name from optional type, returning either `exe`, `lib`, or + // `other`. + // + // Specifically, if type is present but the name is not recognized, then + // return `other`. If type is absent and the package name starts with the + // `lib` prefix, then return `lib`. Otherwise, return `exe`. + // + std::string + effective_type () const; + + static std::string + effective_type (const butl::optional<std::string>&, const package_name&); - // Return the description type value if present, text_type::github_mark if - // the description refers to a file with the .md or .markdown extension - // and text_type::plain if it refers to a file with the .txt extension or - // no extension or the description does not come from a file. Depending on - // the ignore_unknown value either throw std::invalid_argument or return - // nullopt if the description value or the file extension is unknown. - // Throw std::logic_error if the description value is nullopt. + // Extract sub-options from optional type. // - butl::optional<text_type> - effective_description_type (bool ignore_unknown = false) const; + strings + effective_type_sub_options () const; + + static strings + effective_type_sub_options (const butl::optional<std::string>&); + + // Translate the potentially empty list of languages to a non-empty one. + // + // Specifically, if the list of languages is not empty, then return it as + // is. Otherwise, if the package name has an extension (as in, say, + // libbutl.bash), then return it as the language. Otherwise, return `cc` + // (unspecified c-common language). + // + butl::small_vector<language, 1> + effective_languages () const; + + static butl::small_vector<language, 1> + effective_languages (const butl::small_vector<language, 1>&, + const package_name&); + + // Return effective project name. + // + const package_name& + effective_project () const noexcept {return project ? *project : name;} public: package_manifest () = default; @@ -744,7 +1387,7 @@ namespace bpkg // package_manifest (butl::manifest_parser&, bool ignore_unknown = false, - bool complete_dependencies = true, + bool complete_values = true, package_manifest_flags = package_manifest_flags::forbid_location | package_manifest_flags::forbid_sha256sum | @@ -756,7 +1399,7 @@ namespace bpkg // release, etc). // // In particular, the translation function may "patch" the version with - // the snapshot information (see <libbutl/standard-version.mxx> for + // the snapshot information (see <libbutl/standard-version.hxx> for // details). This translation is normally required for manifests of // packages that are accessed as directories (as opposed to package // archives that should have their version already patched). @@ -766,7 +1409,32 @@ namespace bpkg package_manifest (butl::manifest_parser&, const std::function<translate_function>&, bool ignore_unknown = false, - bool complete_depends = true, + bool complete_values = true, + package_manifest_flags = + package_manifest_flags::forbid_location | + package_manifest_flags::forbid_sha256sum | + package_manifest_flags::forbid_fragment); + + // As above but construct the package manifest from the pre-parsed + // manifest values list. + // + // Note that the list is expected not to contain the format version nor + // the end-of-manifest/stream pairs. + // + package_manifest (const std::string& name, + std::vector<butl::manifest_name_value>&&, + bool ignore_unknown = false, + bool complete_values = true, + package_manifest_flags = + package_manifest_flags::forbid_location | + package_manifest_flags::forbid_sha256sum | + package_manifest_flags::forbid_fragment); + + package_manifest (const std::string& name, + std::vector<butl::manifest_name_value>&&, + const std::function<translate_function>&, + bool ignore_unknown = false, + bool complete_values = true, package_manifest_flags = package_manifest_flags::forbid_location | package_manifest_flags::forbid_sha256sum | @@ -777,23 +1445,72 @@ namespace bpkg package_manifest (butl::manifest_parser&, butl::manifest_name_value start, bool ignore_unknown, - bool complete_depends, + bool complete_values, package_manifest_flags); // Override manifest values with the specified. Throw manifest_parsing if // any value is invalid, cannot be overridden, or its name is not // recognized. // - // The specified values override the whole groups they belong to, - // resetting all the group values prior to being applied. Currently, only - // the following value groups can be overridden: {build-*email} and - // {builds, build-{include,exclude}}. + // The specified values other than [*-]build-auxiliary[-*] override the + // whole groups they belong to, resetting all the group values prior to + // being applied. The [*-]build-auxiliary[-*] values only override the + // matching values, which are expected to already be present in the + // manifest. Currently, only the following value groups/values can be + // overridden: + // + // {build-*email} + // {builds, build-{include,exclude}} + // {build-bot} + // {*-builds, *-build-{include,exclude}} + // {*-build-bot} + // {*-build-config} + // {*-build-*email} + // + // [*-]build-auxiliary[-*] + // + // Throw manifest_parsing if the configuration specified by the build + // package configuration-specific build constraint, email, auxiliary, or + // custom bot public key value override doesn't exists. In contrast, for + // the build config override add a new configuration if it doesn't exist + // and update the arguments of the existing configuration otherwise. In + // the former case, all the potential build constraint, email, auxiliary, + // and bot key overrides for such a newly added configuration must follow + // the respective *-build-config override. + // + // Note that the build constraints group values (both common and build + // config-specific) are overridden hierarchically so that the + // [*-]build-{include,exclude} overrides don't affect the respective + // [*-]builds values. + // + // Also note that the common and build config-specific build constraints + // group value overrides are mutually exclusive. If the common build + // constraints are overridden, then all the build config-specific + // constraints are removed. Otherwise, if some build config-specific + // constraints are overridden, then for the remaining configs the build + // constraints are reset to `builds: none`. + // + // Similar to the build constraints groups, the common and build + // config-specific custom bot key value overrides are mutually + // exclusive. If the common custom bot keys are overridden, then all the + // build config-specific custom bot keys are removed. Otherwise, if some + // build config-specific custom bot keys are overridden, then for the + // remaining configs the custom bot keys are left unchanged. + // + // Similar to the above, the common and build config-specific build emails + // group value overrides are mutually exclusive. If the common build + // emails are overridden, then all the build config-specific emails are + // reset to nullopt. Otherwise, if some build config-specific emails are + // overridden, then for the remaining configs the email is reset to the + // empty value and the warning and error emails are reset to nullopt + // (which effectively disables email notifications for such + // configurations). // // If a non-empty source name is specified, then the specified values are // assumed to also include the line/column information and the possibly - // thrown manifest_parsing exception will contain the invalid value + // thrown manifest_parsing exception will contain the invalid value's // location information. Otherwise, the exception description will refer - // to the invalid value name instead. + // to the invalid value instead. // void override (const std::vector<butl::manifest_name_value>&, @@ -801,12 +1518,30 @@ namespace bpkg // Validate the overrides without applying them to any manifest. // + // Specifically, validate that the override values can be parsed according + // to their name semantics and that the value sequence makes sense (no + // mutually exclusive values, etc). Note, however, that the subsequent + // applying of the successfully validated overrides to a specific package + // manifest may still fail (no build config exists for specified *-builds, + // etc). + // static void validate_overrides (const std::vector<butl::manifest_name_value>&, const std::string& source_name); + // If the minimum libbpkg version is specified, then also apply the + // required backward compatibility workarounds to the serialized manifest + // so that clients of all libbpkg versions greater or equal to the + // specified version can parse it, ignoring unknown values. + // + // Note that clients of the latest major libbpkg version can fully + // recognize the produced manifest and thus can parse it without ignoring + // unknown values. + // void - serialize (butl::manifest_serializer&) const; + serialize ( + butl::manifest_serializer&, + const butl::optional<butl::standard_version>& = butl::nullopt) const; // Serialize only package manifest header values. // @@ -815,17 +1550,20 @@ namespace bpkg // Load the *-file manifest values using the specified load function that // returns the file contents passing through any exception it may throw. - // Set the potentially absent description type value to the effective - // description type. If the effective type is nullopt then assign a - // synthetic unknown type. + // If nullopt is returned, then the respective *-file value is left + // unexpanded. Set the potentially absent project description, package + // description, and changes type values to their effective types. If an + // effective type is nullopt then assign a synthetic unknown type if the + // ignore_unknown argument is true and throw manifest_parsing otherwise. // // Note that if the returned file contents is empty, load_files() makes // sure that this is allowed by the value's semantics throwing // manifest_parsing otherwise. However, the load function may want to // recognize such cases itself in order to issue more precise diagnostics. // - using load_function = std::string (const std::string& name, - const butl::path& value); + using load_function = + butl::optional<std::string> (const std::string& name, + const butl::path& value); void load_files (const std::function<load_function>&, @@ -834,13 +1572,10 @@ namespace bpkg // Create individual package manifest. // - inline package_manifest + package_manifest pkg_package_manifest (butl::manifest_parser& p, bool ignore_unknown = false, - bool complete_depends = true) - { - return package_manifest (p, ignore_unknown, complete_depends); - } + bool complete_values = true); LIBBPKG_EXPORT package_manifest dir_package_manifest (butl::manifest_parser&, bool ignore_unknown = false); @@ -868,10 +1603,12 @@ namespace bpkg // Serialize. // inline void - pkg_package_manifest (butl::manifest_serializer& s, - const package_manifest& m) + pkg_package_manifest ( + butl::manifest_serializer& s, + const package_manifest& m, + const butl::optional<butl::standard_version>& min_ver = butl::nullopt) { - m.serialize (s); + m.serialize (s, min_ver); } // Normally there is no need to serialize dir and git package manifests, @@ -900,8 +1637,14 @@ namespace bpkg pkg_package_manifests (butl::manifest_parser&, bool ignore_unknown = false); + // If the minimum libbpkg version is specified, then also apply the + // required backward compatibility workarounds to the serialized package + // manifests list (see package_manifest::serialize() for details). + // void - serialize (butl::manifest_serializer&) const; + serialize ( + butl::manifest_serializer&, + const butl::optional<butl::standard_version>& = butl::nullopt) const; }; class LIBBPKG_EXPORT dir_package_manifests: @@ -992,8 +1735,8 @@ namespace bpkg // non-empty object with non-empty path never contains the trailing slash // (except for the root path on POSIX system). // - // - For the remote URL object the host name is in the lower case (IPv4/6 are - // not supported) and the path is relative. + // - For the remote URL object the host component is normalized (see + // butl::basic_url_host for details) and the path is relative. // // - For the local URL object the path can be relative or absolute. Query // can not be present. Represent the object using the file:// notation if @@ -1138,9 +1881,8 @@ namespace bpkg repository_type, const repository_location& base); - repository_location (const repository_location& l, - const repository_location& base) - : repository_location (l.url (), l.type (), base) {} + repository_location (const repository_location&, + const repository_location& base); // Note that relative locations have no canonical name. Canonical name of // an empty location is the empty name. @@ -1158,59 +1900,22 @@ namespace bpkg empty () const noexcept {return url_.empty ();} bool - local () const - { - if (empty ()) - throw std::logic_error ("empty location"); - - return url_.scheme == repository_protocol::file; - } + local () const; bool - remote () const - { - return !local (); - } + remote () const; bool - absolute () const - { - if (empty ()) - throw std::logic_error ("empty location"); - - // Note that in remote locations path is always relative. - // - return url_.path->absolute (); - } + absolute () const; bool - relative () const - { - return local () && url_.path->relative (); - } + relative () const; repository_type - type () const - { - if (empty ()) - throw std::logic_error ("empty location"); - - return type_; - } + type () const; repository_basis - basis () const - { - switch (type ()) - { - case repository_type::pkg: return repository_basis::archive; - case repository_type::dir: return repository_basis::directory; - case repository_type::git: return repository_basis::version_control; - } - - assert (false); // Can't be here. - return repository_basis::archive; - } + basis () const; // Note that the URL of an empty location is empty. // @@ -1224,69 +1929,30 @@ namespace bpkg // "directories" it always contains the trailing slash. // const butl::path& - path () const - { - if (empty ()) - throw std::logic_error ("empty location"); - - return *url_.path; - } + path () const; const std::string& - host () const - { - if (local ()) - throw std::logic_error ("local location"); - - return url_.authority->host; - } + host () const; // Value 0 indicated that no port was specified explicitly. // std::uint16_t - port () const - { - if (local ()) - throw std::logic_error ("local location"); - - return url_.authority->port; - } + port () const; repository_protocol - proto () const - { - if (empty ()) - throw std::logic_error ("empty location"); - - return url_.scheme; - } + proto () const; const butl::optional<std::string>& - fragment () const - { - if (relative ()) - throw std::logic_error ("relative filesystem path"); - - return url_.fragment; - } + fragment () const; bool - archive_based () const - { - return basis () == repository_basis::archive; - } + archive_based () const; bool - directory_based () const - { - return basis () == repository_basis::directory; - } + directory_based () const; bool - version_control_based () const - { - return basis () == repository_basis::version_control; - } + version_control_based () const; // Return an untyped URL if the correct type can be guessed just from // the URL. Otherwise, return the typed URL. @@ -1447,6 +2113,13 @@ namespace bpkg butl::manifest_name_value start, bool ignore_unknown = false); + struct repositories_manifest_header + { + public: + butl::optional<butl::standard_version> min_bpkg_version; + butl::optional<std::string> compression; + }; + class LIBBPKG_EXPORT pkg_repository_manifests: public std::vector<repository_manifest> { @@ -1455,6 +2128,9 @@ namespace bpkg using base_type::base_type; + butl::optional<repositories_manifest_header> header; + + public: pkg_repository_manifests () = default; pkg_repository_manifests (butl::manifest_parser&, bool ignore_unknown = false); @@ -1471,6 +2147,9 @@ namespace bpkg using base_type::base_type; + butl::optional<repositories_manifest_header> header; + + public: dir_repository_manifests () = default; dir_repository_manifests (butl::manifest_parser&, bool ignore_unknown = false); @@ -1487,6 +2166,9 @@ namespace bpkg using base_type::base_type; + butl::optional<repositories_manifest_header> header; + + public: git_repository_manifests () = default; git_repository_manifests (butl::manifest_parser&, bool ignore_unknown = false); @@ -1534,6 +2216,39 @@ namespace bpkg butl::manifest_name_value start, bool ignore_unknown); }; + + // Extract the package name component from <name>[/<version>] or + // <name><version-constraint>. Throw invalid_argument on parsing error. + // + // Note: the version and version constraint are not verified. + // + LIBBPKG_EXPORT package_name + extract_package_name (const char*, bool allow_version = true); + + inline package_name + extract_package_name (const std::string& s, bool allow_version = true) + { + return extract_package_name (s.c_str (), allow_version); + } + + // Extract the package version component from <name>[/<version>]. Return + // empty version if none is specified. Throw invalid_argument on parsing + // error and for the earliest and stub versions. + // + // Note: the package name is not verified. + // + LIBBPKG_EXPORT version + extract_package_version (const char*, + version::flags fl = version::fold_zero_revision); + + inline version + extract_package_version (const std::string& s, + version::flags fl = version::fold_zero_revision) + { + return extract_package_version (s.c_str (), fl); + } } +#include <libbpkg/manifest.ixx> + #endif // LIBBPKG_MANIFEST_HXX |