From 27c616e238b6891cd0fe4081d614e6b5c1f977bb Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 5 Oct 2021 18:21:10 +0300 Subject: Add repositories manifest header --- libbpkg/manifest.cxx | 172 +++++++++++++++++++++++++++++++++++++++++----- libbpkg/manifest.hxx | 17 +++++ tests/manifest/driver.cxx | 10 +++ tests/manifest/testscript | 143 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 325 insertions(+), 17 deletions(-) diff --git a/libbpkg/manifest.cxx b/libbpkg/manifest.cxx index f1b50a3..5638d1b 100644 --- a/libbpkg/manifest.cxx +++ b/libbpkg/manifest.cxx @@ -4080,7 +4080,8 @@ namespace bpkg parse_repository_manifest (parser& p, name_value nv, repository_type base_type, - bool iu) + bool iu, + bool verify_version = true) { auto bad_name ([&p, &nv](const string& d) { throw parsing (p.name (), nv.name_line, nv.name_column, d);}); @@ -4090,11 +4091,16 @@ namespace bpkg // Make sure this is the start and we support the version. // - if (!nv.name.empty ()) - bad_name ("start of repository manifest expected"); + if (verify_version) + { + if (!nv.name.empty ()) + bad_name ("start of repository manifest expected"); - if (nv.value != "1") - bad_value ("unsupported format version"); + if (nv.value != "1") + bad_value ("unsupported format version"); + + nv = p.next (); + } repository_manifest r; @@ -4105,7 +4111,7 @@ namespace bpkg optional type; optional location; - for (nv = p.next (); !nv.empty (); nv = p.next ()) + for (; !nv.empty (); nv = p.next ()) { string& n (nv.name); string& v (nv.value); @@ -4450,13 +4456,126 @@ namespace bpkg parse_repository_manifests (parser& p, repository_type base_type, bool iu, + optional& header, vector& ms) { + // Return nullopt on eos. Otherwise, parse and verify the + // manifest-starting format version value and return the subsequent + // manifest value, that can potentially be empty (for an empty manifest). + // + // Also save the manifest-starting position (start_nv) for the + // diagnostics. + // + name_value start_nv; + auto next_manifest = [&p, &start_nv] () -> optional + { + start_nv = p.next (); + + if (start_nv.empty ()) + return nullopt; + + // Make sure this is the start and we support the version. + // + if (!start_nv.name.empty ()) + throw parsing (p.name (), start_nv.name_line, start_nv.name_column, + "start of repository manifest expected"); + + if (start_nv.value != "1") + throw parsing (p.name (), start_nv.value_line, start_nv.value_column, + "unsupported format version"); + + return p.next (); + }; + + optional nv (next_manifest ()); + + if (!nv) + throw parsing (p.name (), start_nv.name_line, start_nv.name_column, + "start of repository manifest expected"); + + auto bad_name ([&p, &nv](const string& d) { + throw parsing (p.name (), nv->name_line, nv->name_column, d);}); + + auto bad_value ([&p, &nv](const string& d) { + throw parsing (p.name (), nv->value_line, nv->value_column, d);}); + + // First check if this a header manifest, if any manifest is present. + // + // Note that if this is none of the known header values, then we assume + // this is a repository manifest (rather than a header that starts with an + // unknown value; so use one of the original names to make sure it's + // recognized as such, for example `compression:none`). + // + if (nv->name == "min-bpkg-version" || + nv->name == "compression") + { + header = repositories_manifest_header (); + + // First verify the version, if any. + // + if (nv->name == "min-bpkg-version") + try + { + const string& v (nv->value); + standard_version mbv (v, standard_version::allow_earliest); + + if (mbv > standard_version (LIBBPKG_VERSION_STR)) + bad_value ( + "incompatible repositories manifest: minimum bpkg version is " + v); + + header->min_bpkg_version = move (mbv); + + nv = p.next (); + } + catch (const invalid_argument& e) + { + bad_value (string ("invalid minimum bpkg version: ") + e.what ()); + } + + // Parse the remaining header values, failing if min-bpkg-version is + // encountered (should be first). + // + for (; !nv->empty (); nv = p.next ()) + { + const string& n (nv->name); + string& v (nv->value); + + if (n == "min-bpkg-version") + { + bad_name ("minimum bpkg version must be first in repositories " + "manifest header"); + } + else if (n == "compression") + { + header->compression = move (v); + } + else if (!iu) + bad_name ("unknown name '" + n + "' in repositories manifest header"); + } + + nv = next_manifest (); + } + + // Parse the manifest list. + // + // Note that if nv is present, then it contains the manifest's first + // value, which can potentially be empty (for an empty manifest, which is + // recognized as a base manifest). + // + // Also note that if the header is present but is not followed by + // repository manifests (there is no ':' line after the header values), + // then the empty manifest list is returned (no base manifest is + // automatically added). + // bool base (false); - for (name_value nv (p.next ()); !nv.empty (); nv = p.next ()) + while (nv) { - ms.push_back (parse_repository_manifest (p, nv, base_type, iu)); + ms.push_back (parse_repository_manifest (p, + *nv, + base_type, + iu, + false /* verify_version */)); // Make sure that there is a single base repository manifest in the // list. @@ -4464,19 +4583,38 @@ namespace bpkg if (ms.back ().effective_role () == repository_role::base) { if (base) - throw parsing (p.name (), nv.name_line, nv.name_column, + throw parsing (p.name (), start_nv.name_line, start_nv.name_column, "base repository manifest redefinition"); base = true; } + + nv = next_manifest (); } } // Serialize the repository manifest list. // static void - serialize_repository_manifests (serializer& s, - const vector& ms) + serialize_repository_manifests ( + serializer& s, + const optional& header, + const vector& ms) { + if (header) + { + s.next ("", "1"); // Start of manifest. + + const repositories_manifest_header& h (*header); + + if (h.min_bpkg_version) + s.next ("min-bpkg-version", h.min_bpkg_version->string ()); + + if (h.compression) + s.next ("compression", *h.compression); + + s.next ("", ""); // End of manifest. + } + for (const repository_manifest& r: ms) r.serialize (s); @@ -4488,13 +4626,13 @@ namespace bpkg pkg_repository_manifests:: pkg_repository_manifests (parser& p, bool iu) { - parse_repository_manifests (p, repository_type::pkg, iu, *this); + parse_repository_manifests (p, repository_type::pkg, iu, header, *this); } void pkg_repository_manifests:: serialize (serializer& s) const { - serialize_repository_manifests (s, *this); + serialize_repository_manifests (s, header, *this); } // dir_repository_manifests @@ -4502,13 +4640,13 @@ namespace bpkg dir_repository_manifests:: dir_repository_manifests (parser& p, bool iu) { - parse_repository_manifests (p, repository_type::dir, iu, *this); + parse_repository_manifests (p, repository_type::dir, iu, header, *this); } void dir_repository_manifests:: serialize (serializer& s) const { - serialize_repository_manifests (s, *this); + serialize_repository_manifests (s, header, *this); } // git_repository_manifests @@ -4516,13 +4654,13 @@ namespace bpkg git_repository_manifests:: git_repository_manifests (parser& p, bool iu) { - parse_repository_manifests (p, repository_type::git, iu, *this); + parse_repository_manifests (p, repository_type::git, iu, header, *this); } void git_repository_manifests:: serialize (serializer& s) const { - serialize_repository_manifests (s, *this); + serialize_repository_manifests (s, header, *this); } // signature_manifest diff --git a/libbpkg/manifest.hxx b/libbpkg/manifest.hxx index ca54cdc..77043b0 100644 --- a/libbpkg/manifest.hxx +++ b/libbpkg/manifest.hxx @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -1524,6 +1525,13 @@ namespace bpkg butl::manifest_name_value start, bool ignore_unknown = false); + struct repositories_manifest_header + { + public: + butl::optional min_bpkg_version; + butl::optional compression; + }; + class LIBBPKG_EXPORT pkg_repository_manifests: public std::vector { @@ -1532,6 +1540,9 @@ namespace bpkg using base_type::base_type; + butl::optional header; + + public: pkg_repository_manifests () = default; pkg_repository_manifests (butl::manifest_parser&, bool ignore_unknown = false); @@ -1548,6 +1559,9 @@ namespace bpkg using base_type::base_type; + butl::optional header; + + public: dir_repository_manifests () = default; dir_repository_manifests (butl::manifest_parser&, bool ignore_unknown = false); @@ -1564,6 +1578,9 @@ namespace bpkg using base_type::base_type; + butl::optional header; + + public: git_repository_manifests () = default; git_repository_manifests (butl::manifest_parser&, bool ignore_unknown = false); diff --git a/tests/manifest/driver.cxx b/tests/manifest/driver.cxx index 44b1a79..273dce5 100644 --- a/tests/manifest/driver.cxx +++ b/tests/manifest/driver.cxx @@ -23,6 +23,7 @@ using namespace bpkg; // argv[0] (-pp|-dp|-gp|-pr|-dr|-gr|-s) // argv[0] -p -c -i // argv[0] -ec +// argv[0] -v // // In the first form read and parse manifest list from stdin and serialize it // to stdout. The following options specify the manifest type. @@ -34,6 +35,7 @@ using namespace bpkg; // -dr parse dir repository manifest list // -gr parse git repository manifest list // -s parse signature manifest +// -v print the libbpkg version // // In the second form read and parse the package manifest from stdin and // serialize it to stdout. @@ -47,6 +49,8 @@ using namespace bpkg; // roundtrip them to stdout together with their effective constraints, // calculated using version passed as an argument. // +// In the forth form print the libbpkg version to stdout and exit. +// int main (int argc, char* argv[]) { @@ -55,6 +59,12 @@ main (int argc, char* argv[]) cout.exceptions (ios_base::failbit | ios_base::badbit); + if (mode == "-v") + { + cout << standard_version (LIBBPKG_VERSION_STR) << endl; + return 0; + } + manifest_parser p (cin, "stdin"); manifest_serializer s (cout, "stdout"); diff --git a/tests/manifest/testscript b/tests/manifest/testscript index 25e0ae3..c35f618 100644 --- a/tests/manifest/testscript +++ b/tests/manifest/testscript @@ -935,6 +935,149 @@ : repository-list : { + : header + : + { + +$* -v | set v + + test.options += -pr + + : version + : + { + $* <<"EOF" >>"EOF" + : 1 + min-bpkg-version: $v + : + location: http://pkg.example.org/1/math + type: pkg + role: prerequisite + : + url: http://cppget.org + email: repoman@cppget.org; General mailing list. + EOF + } + + : invalid-version + : + { + $* <'stdin:2:19: error: invalid minimum bpkg version: invalid major version' != 0 + : 1 + min-bpkg-version: foo + EOI + } + + : too-new + : + { + $* <'stdin:2:19: error: incompatible repositories manifest: minimum bpkg version is 1000.0.0' != 0 + : 1 + min-bpkg-version: 1000.0.0 + EOI + } + + : non-version + : + { + $* <>EOF + : 1 + compression: none + : + location: http://pkg.example.org/1/math + type: pkg + role: prerequisite + : + url: http://cppget.org + email: repoman@cppget.org; General mailing list. + EOF + } + + : version-non-first + : + { + $* <<"EOI" 2>"stdin:3:1: error: minimum bpkg version must be first in repositories manifest header" != 0 + : 1 + compression: none + min-bpkg-version: $v + EOI + } + + : unknown-header-value + : + { + $* <"stdin:3:1: error: unknown name 'unknown' in repositories manifest header" != 0 + : 1 + compression: none + unknown: foo + EOI + } + + : unknown-manifest-value + : + { + $* <"stdin:2:1: error: unknown name 'unknown' in repository manifest" != 0 + : 1 + unknown: foo + EOI + } + + : empty-repository-manifest + : + { + $* <>EOF + : 1 + compression: none + : + EOF + } + + : base-redefinition + : + { + $* <'stdin:4:1: error: base repository manifest redefinition' != 0 + : 1 + compression: none + : + : + EOI + } + + : empty-base + : + { + $* <<"EOF" >>"EOF" + : 1 + compression: none + : + location: http://pkg.example.org/1/math + type: pkg + role: prerequisite + : + EOF + } + + : no-repository-manifest + : + { + $* <>EOF + : 1 + compression: none + EOF + } + + : only-empty-base + : + { + $* <>EOF + : 1 + EOF + } + + : empty-manifest-list + : + $* <'' 2>'stdin:2:1: error: start of repository manifest expected' != 0 + } + : pkg : { -- cgit v1.1