From b2bd3dc5f992b1898061e6836ea2b8b04ec243f1 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 6 Mar 2018 23:37:15 +0300 Subject: Add support for dir repository --- libbpkg/manifest.cxx | 308 +++++++++++++++++++++++++---------- libbpkg/manifest.hxx | 100 ++++++++++-- tests/manifest/driver.cxx | 14 +- tests/manifest/testscript | 31 ++++ tests/repository-location/driver.cxx | 14 ++ 5 files changed, 370 insertions(+), 97 deletions(-) diff --git a/libbpkg/manifest.cxx b/libbpkg/manifest.cxx index a030be2..d6a7a14 100644 --- a/libbpkg/manifest.cxx +++ b/libbpkg/manifest.cxx @@ -1327,10 +1327,11 @@ namespace bpkg return pkg_package_manifest (p, nv, true, iu); } - // git_package_manifest + // Parse the directory manifest that may contain the only (and required) + // location name that refers to the package directory. // - package_manifest - git_package_manifest (parser& p, name_value nv, bool iu) + static package_manifest + parse_directory_manifest (parser& p, name_value nv, bool iu) { auto bad_name ([&p, &nv](const string& d) { throw parsing (p.name (), nv.name_line, nv.name_column, d);}); @@ -1391,10 +1392,10 @@ namespace bpkg return r; } - package_manifest - git_package_manifest (parser& p, bool iu) + static package_manifest + parse_directory_manifest (parser& p, bool iu) { - package_manifest r (git_package_manifest (p, p.next (), iu)); + package_manifest r (parse_directory_manifest (p, p.next (), iu)); // Make sure this is the end. // @@ -1406,8 +1407,10 @@ namespace bpkg return r; } - void - git_package_manifest (serializer& s, const package_manifest& m) + // Serialize the directory manifest (see above). + // + static void + serialize_directory_manifest (serializer& s, const package_manifest& m) { s.next ("", "1"); // Start of manifest. @@ -1422,6 +1425,46 @@ namespace bpkg s.next ("", ""); // End of manifest. } + // dir_package_manifest + // + package_manifest + dir_package_manifest (parser& p, name_value nv, bool iu) + { + return parse_directory_manifest (p, nv, iu); + } + + package_manifest + dir_package_manifest (parser& p, bool iu) + { + return parse_directory_manifest (p, iu); + } + + void + dir_package_manifest (serializer& s, const package_manifest& m) + { + serialize_directory_manifest (s, m); + } + + // git_package_manifest + // + package_manifest + git_package_manifest (parser& p, name_value nv, bool iu) + { + return parse_directory_manifest (p, nv, iu); + } + + package_manifest + git_package_manifest (parser& p, bool iu) + { + return parse_directory_manifest (p, iu); + } + + void + git_package_manifest (serializer& s, const package_manifest& m) + { + serialize_directory_manifest (s, m); + } + // pkg_package_manifests // pkg_package_manifests:: @@ -1515,6 +1558,28 @@ namespace bpkg s.next ("", ""); // End of stream. } + // dir_package_manifests + // + dir_package_manifests:: + dir_package_manifests (parser& p, bool iu) + { + // Parse package manifests. + // + for (name_value nv (p.next ()); !nv.empty (); nv = p.next ()) + push_back (dir_package_manifest (p, nv, iu)); + } + + void dir_package_manifests:: + serialize (serializer& s) const + { + // Serialize package manifests. + // + for (const package_manifest& p: *this) + dir_package_manifest (s, p); + + s.next ("", ""); // End of stream. + } + // git_package_manifests // git_package_manifests:: @@ -1762,6 +1827,7 @@ namespace bpkg switch (t) { case repository_type::pkg: return "pkg"; + case repository_type::dir: return "dir"; case repository_type::git: return "git"; } @@ -1773,6 +1839,7 @@ namespace bpkg to_repository_type (const string& t) { if (t == "pkg") return repository_type::pkg; + else if (t == "dir") return repository_type::dir; else if (t == "git") return repository_type::git; else throw invalid_argument ("invalid repository type '" + t + "'"); } @@ -1837,6 +1904,14 @@ namespace bpkg break; } + case repository_type::dir: + { + // Can't be here as repository location for the dir type can only be + // local. + // + assert (false); + break; + } } if (h && h->empty ()) @@ -1939,6 +2014,20 @@ namespace bpkg if (url_.scheme == repository_protocol::git) throw invalid_argument ("unsupported scheme for pkg repository"); + if (url_.fragment) + throw invalid_argument ("unexpected fragment for pkg repository"); + + break; + } + case repository_type::dir: + { + if (url_.scheme != repository_protocol::file) + throw invalid_argument ( + "unsupported scheme for dir repository"); + + if (url_.fragment) + throw invalid_argument ("unexpected fragment for dir repository"); + break; } case repository_type::git: @@ -1963,6 +2052,7 @@ namespace bpkg switch (t) { case repository_type::pkg: + case repository_type::dir: case repository_type::git: { if (!up.to_directory ()) @@ -2066,27 +2156,48 @@ namespace bpkg return; } - // Canonical name / part for the pkg repository, and the - // full path with the .git extension stripped for the git repository. + // Canonical name part that is produced from the repository location path + // part. The algorithm depends on the repository type. // path sp; - if (type_ == repository_type::pkg) + switch (type_) { - sp = strip_path (up, - remote () - ? strip_mode::component - : strip_mode::path); + case repository_type::pkg: + { + // Produce the pkg repository canonical name / part (see + // the Repository Chaining documentation for more details). + // + sp = strip_path (up, + remote () + ? strip_mode::component + : strip_mode::path); + + // If for an absolute path location the stripping result is empty + // (which also means part is empty as well) then fallback to + // stripping just the version component. + // + if (absolute () && sp.empty ()) + sp = strip_path (up, strip_mode::version); - // If for an absolute path location the stripping result is empty (which - // also means part is empty as well) then fallback to stripping - // just the version component. - // - if (absolute () && sp.empty ()) - sp = strip_path (up, strip_mode::version); + break; + } + case repository_type::dir: + { + // For dir repository we use the absolute (normalized) path. + // + sp = up; + break; + } + case repository_type::git: + { + // For git repository we use the absolute (normalized) path, stripping + // the .git extension if present. + // + sp = strip_path (up, strip_mode::extension); + break; + } } - else - sp = strip_path (up, strip_mode::extension); string cp (sp.relative () ? sp.posix_string () : sp.string ()); @@ -2401,9 +2512,20 @@ namespace bpkg // location or guess it otherwise. // if (!type) - type = u.scheme == repository_protocol::file && u.path->relative () - ? base_type - : guess_type (u, false); // Can't throw. + { + if (u.scheme == repository_protocol::file && u.path->relative ()) + { + type = base_type; + + // Strip the URL fragment if the base repository type is dir (see + // the Repository Manifest documentation for the gory details). + // + if (base_type == repository_type::dir) + u.fragment = nullopt; + } + else + type = guess_type (u, false); // Can't throw. + } // Call prerequisite repository location constructor, do not amend // relative path. @@ -2445,6 +2567,22 @@ namespace bpkg return r; } + static repository_manifest + parse_repository_manifest (parser& p, repository_type base_type, bool iu) + { + repository_manifest r ( + parse_repository_manifest (p, p.next (), base_type, iu)); + + // Make sure this is the end. + // + name_value nv (p.next ()); + if (!nv.empty ()) + throw parsing (p.name (), nv.name_line, nv.name_column, + "single repository manifest expected"); + + return r; + } + void repository_manifest:: serialize (serializer& s) const { @@ -2519,16 +2657,7 @@ namespace bpkg repository_manifest pkg_repository_manifest (parser& p, bool iu) { - repository_manifest r (pkg_repository_manifest (p, p.next (), iu)); - - // Make sure this is the end. - // - name_value nv (p.next ()); - if (!nv.empty ()) - throw parsing (p.name (), nv.name_line, nv.name_column, - "single repository manifest expected"); - - return r; + return parse_repository_manifest (p, repository_type::pkg, iu); } repository_manifest @@ -2537,21 +2666,26 @@ namespace bpkg return parse_repository_manifest (p, nv, repository_type::pkg, iu); } - // git_repository_manifest + // dir_repository_manifest // repository_manifest - git_repository_manifest (parser& p, bool iu) + dir_repository_manifest (parser& p, bool iu) { - repository_manifest r (git_repository_manifest (p, p.next (), iu)); + return parse_repository_manifest (p, repository_type::dir, iu); + } - // Make sure this is the end. - // - name_value nv (p.next ()); - if (!nv.empty ()) - throw parsing (p.name (), nv.name_line, nv.name_column, - "single repository manifest expected"); + repository_manifest + dir_repository_manifest (parser& p, name_value nv, bool iu) + { + return parse_repository_manifest (p, nv, repository_type::dir, iu); + } - return r; + // git_repository_manifest + // + repository_manifest + git_repository_manifest (parser& p, bool iu) + { + return parse_repository_manifest (p, repository_type::git, iu); } repository_manifest @@ -2560,80 +2694,90 @@ namespace bpkg return parse_repository_manifest (p, nv, repository_type::git, iu); } - // pkg_repository_manifests + // Parse the repository manifest list. // - pkg_repository_manifests:: - pkg_repository_manifests (parser& p, bool iu) + static void + parse_repository_manifests (parser& p, + repository_type base_type, + bool iu, + vector& ms) { name_value nv (p.next ()); while (!nv.empty ()) { - push_back (pkg_repository_manifest (p, nv, iu)); + ms.push_back (parse_repository_manifest (p, nv, base_type, iu)); nv = p.next (); // Make sure there is location in all except the last entry. // - if (back ().location.empty () && !nv.empty ()) + if (ms.back ().location.empty () && !nv.empty ()) throw parsing (p.name (), nv.name_line, nv.name_column, "repository location expected"); } - if (empty () || !back ().location.empty ()) + if (ms.empty () || !ms.back ().location.empty ()) throw parsing (p.name (), nv.name_line, nv.name_column, "base repository manifest expected"); } - void pkg_repository_manifests:: - serialize (serializer& s) const + // Serialize the repository manifest list. + // + static void + serialize_repository_manifests (serializer& s, + const vector& ms) { - if (empty () || !back ().location.empty ()) + if (ms.empty () || !ms.back ().location.empty ()) throw serialization (s.name (), "base repository manifest expected"); // @@ Should we check that there is location in all except the last // entry? // - for (const repository_manifest& r: *this) + for (const repository_manifest& r: ms) r.serialize (s); s.next ("", ""); // End of stream. } - // git_repository_manifests + // pkg_repository_manifests // - git_repository_manifests:: - git_repository_manifests (parser& p, bool iu) + pkg_repository_manifests:: + pkg_repository_manifests (parser& p, bool iu) { - name_value nv (p.next ()); - while (!nv.empty ()) - { - push_back (git_repository_manifest (p, nv, iu)); - nv = p.next (); + parse_repository_manifests (p, repository_type::pkg, iu, *this); + } - // Make sure there is location in all except the last entry. - // - if (back ().location.empty () && !nv.empty ()) - throw parsing (p.name (), nv.name_line, nv.name_column, - "repository location expected"); - } + void pkg_repository_manifests:: + serialize (serializer& s) const + { + serialize_repository_manifests (s, *this); + } - if (empty () || !back ().location.empty ()) - throw parsing (p.name (), nv.name_line, nv.name_column, - "base repository manifest expected"); + // dir_repository_manifests + // + dir_repository_manifests:: + dir_repository_manifests (parser& p, bool iu) + { + parse_repository_manifests (p, repository_type::dir, iu, *this); } - void git_repository_manifests:: + void dir_repository_manifests:: serialize (serializer& s) const { - if (empty () || !back ().location.empty ()) - throw serialization (s.name (), "base repository manifest expected"); + serialize_repository_manifests (s, *this); + } - // @@ Should we check that there is location in all except the last - // entry? - // - for (const repository_manifest& r: *this) - r.serialize (s); + // git_repository_manifests + // + git_repository_manifests:: + git_repository_manifests (parser& p, bool iu) + { + parse_repository_manifests (p, repository_type::git, iu, *this); + } - s.next ("", ""); // End of stream. + void git_repository_manifests:: + serialize (serializer& s) const + { + serialize_repository_manifests (s, *this); } // signature_manifest diff --git a/libbpkg/manifest.hxx b/libbpkg/manifest.hxx index ada6cd3..0e2ee9d 100644 --- a/libbpkg/manifest.hxx +++ b/libbpkg/manifest.hxx @@ -395,6 +395,9 @@ namespace bpkg pkg_package_manifest (butl::manifest_parser&, bool ignore_unknown = false); LIBBPKG_EXPORT package_manifest + dir_package_manifest (butl::manifest_parser&, bool ignore_unknown = false); + + LIBBPKG_EXPORT package_manifest git_package_manifest (butl::manifest_parser&, bool ignore_unknown = false); // Create an element of the package list manifest. @@ -405,6 +408,11 @@ namespace bpkg bool ignore_unknown = false); LIBBPKG_EXPORT package_manifest + dir_package_manifest (butl::manifest_parser&, + butl::manifest_name_value start, + bool ignore_unknown = false); + + LIBBPKG_EXPORT package_manifest git_package_manifest (butl::manifest_parser&, butl::manifest_name_value start, bool ignore_unknown = false); @@ -418,10 +426,13 @@ namespace bpkg m.serialize (s); } - // Normally there is no need to serialize git package manifest, unless for - // testing. + // Normally there is no need to serialize dir and git package manifests, + // unless for testing. // LIBBPKG_EXPORT void + dir_package_manifest (butl::manifest_serializer&, const package_manifest&); + + LIBBPKG_EXPORT void git_package_manifest (butl::manifest_serializer&, const package_manifest&); class LIBBPKG_EXPORT pkg_package_manifests: @@ -445,6 +456,26 @@ namespace bpkg serialize (butl::manifest_serializer&) const; }; + class LIBBPKG_EXPORT dir_package_manifests: + public std::vector + { + public: + using base_type = std::vector; + + using base_type::base_type; + + public: + dir_package_manifests () = default; + dir_package_manifests (butl::manifest_parser&, + bool ignore_unknown = false); + + // Normally there is no need to serialize dir package manifests, unless for + // testing. + // + void + serialize (butl::manifest_serializer&) const; + }; + class LIBBPKG_EXPORT git_package_manifests: public std::vector { @@ -524,7 +555,7 @@ namespace bpkg // Repository type. // - enum class repository_type {pkg, git}; + enum class repository_type {pkg, dir, git}; LIBBPKG_EXPORT std::string to_string (repository_type); @@ -538,6 +569,15 @@ namespace bpkg return os << to_string (t); } + // Repository basis. + // + enum class repository_basis + { + archive, + directory, + version_control + }; + // Guess the repository type for the URL: // // 1. If scheme is git then git. @@ -656,6 +696,20 @@ namespace bpkg return type_; } + 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; + } + // URL of an empty location is empty. // const repository_url& @@ -717,20 +771,19 @@ namespace bpkg bool archive_based () const { - switch (type ()) - { - case repository_type::pkg: return true; - case repository_type::git: return false; - } + return basis () == repository_basis::archive; + } - assert (false); // Can't be here. - return false; + bool + directory_based () const + { + return basis () == repository_basis::directory; } bool version_control_based () const { - return !archive_based (); + return basis () == repository_basis::version_control; } // String representation of an empty location is the empty string. @@ -835,6 +888,10 @@ namespace bpkg bool ignore_unknown = false); LIBBPKG_EXPORT repository_manifest + dir_repository_manifest (butl::manifest_parser&, + bool ignore_unknown = false); + + LIBBPKG_EXPORT repository_manifest git_repository_manifest (butl::manifest_parser&, bool ignore_unknown = false); @@ -846,6 +903,11 @@ namespace bpkg bool ignore_unknown = false); LIBBPKG_EXPORT repository_manifest + dir_repository_manifest (butl::manifest_parser&, + butl::manifest_name_value start, + bool ignore_unknown = false); + + LIBBPKG_EXPORT repository_manifest git_repository_manifest (butl::manifest_parser&, butl::manifest_name_value start, bool ignore_unknown = false); @@ -866,6 +928,22 @@ namespace bpkg serialize (butl::manifest_serializer&) const; }; + class LIBBPKG_EXPORT dir_repository_manifests: + public std::vector + { + public: + using base_type = std::vector; + + using base_type::base_type; + + dir_repository_manifests () = default; + dir_repository_manifests (butl::manifest_parser&, + bool ignore_unknown = false); + + void + serialize (butl::manifest_serializer&) const; + }; + class LIBBPKG_EXPORT git_repository_manifests: public std::vector { diff --git a/tests/manifest/driver.cxx b/tests/manifest/driver.cxx index caca79d..5e028bf 100644 --- a/tests/manifest/driver.cxx +++ b/tests/manifest/driver.cxx @@ -16,16 +16,18 @@ using namespace std; using namespace butl; using namespace bpkg; -// Usage: argv[0] (-p|-r|-s) +// Usage: argv[0] (-pp|-dp|-gp|-pr|-dr|-gr|-s) // // Read and parse manifest from STDIN and serialize it to STDOUT. The // following options specify the manifest type. // // -pp parse pkg package manifest list +// -dp parse dir package manifest list // -gp parse git package manifest list // -pr parse pkg repository manifest list +// -dr parse dir repository manifest list // -gr parse git repository manifest list -// -s parse signature manifest +// -s parse signature manifest // int main (int argc, char* argv[]) @@ -41,10 +43,14 @@ main (int argc, char* argv[]) if (opt == "-pp") pkg_package_manifests (p).serialize (s); - else if (opt == "-pr") - pkg_repository_manifests (p).serialize (s); + else if (opt == "-dp") + dir_package_manifests (p).serialize (s); else if (opt == "-gp") git_package_manifests (p).serialize (s); + else if (opt == "-pr") + pkg_repository_manifests (p).serialize (s); + else if (opt == "-dr") + dir_repository_manifests (p).serialize (s); else if (opt == "-gr") git_repository_manifests (p).serialize (s); else if (opt == "-s") diff --git a/tests/manifest/testscript b/tests/manifest/testscript index 68ceced..b72f492 100644 --- a/tests/manifest/testscript +++ b/tests/manifest/testscript @@ -260,6 +260,37 @@ url: http://cppget.org EOO } + + : dir + : + { + : manifest + : + : Roundtrip the dir repository manifest list. + : + $* -dr <>EOF + : 1 + location: ../stable + type: dir + role: complement + : + EOF + + : prerequisite-with-fragment + : + $* -dr <>EOO + : 1 + location: ../stable.git#stable + role: complement + : + EOI + : 1 + location: ../stable.git + type: dir + role: complement + : + EOO + } } : signature diff --git a/tests/repository-location/driver.cxx b/tests/repository-location/driver.cxx index 89a32f8..014b810 100644 --- a/tests/repository-location/driver.cxx +++ b/tests/repository-location/driver.cxx @@ -201,6 +201,10 @@ namespace bpkg assert (bad_loc ("file:/abc", repository_type::git)); #endif + // Can't be remote. + // + assert (bad_loc ("http://example.com/dir", repository_type::dir)); + // Invalid web interface URL. // assert (bad_url (".a/..", loc ("http://stable.cppget.org/1/misc"))); @@ -321,6 +325,11 @@ namespace bpkg assert (l.string () == "file:/#master"); assert (l.canonical_name () == "git:/#master"); } + { + repository_location l (loc ("/home/user/repo", repository_type::dir)); + assert (l.string () == "/home/user/repo"); + assert (l.canonical_name () == "dir:/home/user/repo"); + } #else { repository_location l (loc ("c:\\1\\aa\\bb", loc ())); @@ -388,6 +397,11 @@ namespace bpkg assert (l.string () == "file:/c:#master"); assert (l.canonical_name () == "git:c:#master"); } + { + repository_location l (loc ("c:\\user\\repo", repository_type::dir)); + assert (l.string () == "c:\\user\\repo"); + assert (l.canonical_name () == "dir:c:\\user\\repo"); + } #endif { repository_location l (loc ("../c/../c/./1/aa/../bb", loc ())); -- cgit v1.1