From 1374560d558f4cab72f66b3d851e2a052f59998d Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 1 Mar 2018 18:33:16 +0300 Subject: Update pkg-build --- bpkg/pkg-build.cxx | 198 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 173 insertions(+), 25 deletions(-) (limited to 'bpkg/pkg-build.cxx') diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx index fc6ff26..439aee7 100644 --- a/bpkg/pkg-build.cxx +++ b/bpkg/pkg-build.cxx @@ -9,7 +9,7 @@ #include #include // strlen() #include // cout -#include // find() +#include // find(), find_if() #include #include @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -54,7 +55,8 @@ namespace bpkg find_available (database& db, const string& name, const shared_ptr& r, - const optional& c) + const optional& c, + bool prereq = true) { using query = query; @@ -124,7 +126,7 @@ namespace bpkg // Filter the result based on the repository to which each version // belongs. // - return filter_one (r, db.query (q)); + return filter_one (r, db.query (q), prereq); } // Create a transient (or fake, if you prefer) available_package object @@ -1071,35 +1073,188 @@ namespace bpkg fail << "package name argument expected" << info << "run 'bpkg help pkg-build' for more information"; + // Check if the argument has the @ form. Return the + // delimiter position if that's the case. Otherwise return string::npos. + // + // We consider '@' to be such a delimiter if it comes before ':' (e.g., a + // URL which could contain its own '@'). + // + auto location = [] (const string& arg) -> size_t + { + size_t p (arg.find_first_of ("@:")); + return p != string::npos && arg[p] == '@' ? p : string::npos; + }; + + // Collect repository locations from @ arguments, + // suppressing duplicates. + // + // Note that the last repository location overrides the previous ones with + // the same canonical name. + // + strings args; + vector locations; + + while (a.more ()) + { + string arg (a.next ()); + size_t p (location (arg)); + + if (p != string::npos) + { + repository_location l ( + parse_location (string (arg, p + 1), nullopt /* type */)); + + auto pr = [&l] (const repository_location& i) -> bool + { + return i.canonical_name () == l.canonical_name (); + }; + + auto i (find_if (locations.begin (), locations.end (), pr)); + + if (i != locations.end ()) + *i = move (l); + else + locations.push_back (move (l)); + } + + args.push_back (move (arg)); + } + + database db (open (c, trace)); // Also populates the system repository. + + // Note that the session spans all our transactions. The idea here is + // that selected_package objects in the build_packages list below will + // be cached in this session. When subsequent transactions modify any + // of these objects, they will modify the cached instance, which means + // our list will always "see" their updated state. + // + // Also note that rep_fetch() must be called in session. + // + session s; + + if (!locations.empty ()) + rep_fetch (o, c, db, locations); + + // Expand @ arguments. + // + strings eargs; + { + transaction t (db.begin ()); + + for (string& arg: args) + { + size_t p (location (arg)); + + if (p == string::npos) + { + eargs.push_back (move (arg)); + continue; + } + + repository_location l ( + parse_location (string (arg, p + 1), nullopt /* type */)); + + shared_ptr r (db.load (l.canonical_name ())); + + // If no packages are specified explicitly (the argument starts with + // '@') then we select latest versions of all the packages from this + // repository. Otherwise, we search for the specified packages and + // versions (if specified) or latest versions (if unspecified) in the + // repository and its complements (recursively), failing if any of + // them are not found. + // + if (p == 0) // No packages are specified explicitly. + { + // Collect the latest package version. + // + map pvs; + + using query = query; + + for (const auto& rp: db.query ( + (query::repository::name == r->name) + + order_by_version_desc (query::package::id.version))) + { + const shared_ptr& p (rp); + pvs.insert (make_pair (p->id.name, p->version)); + } + + // Populate the argument list with the latest package versions. + // + for (const auto& pv: pvs) + eargs.push_back (pv.first + '/' + pv.second.string ()); + } + else // Packages with optional versions in the coma-separated list. + { + string ps (arg, 0, p); + for (size_t b (0); b != string::npos;) + { + // Extract the package. + // + p = ps.find (',', b); + + string s (ps, b, p != string::npos ? p - b : p); + string n (parse_package_name (s)); + version v (parse_package_version (s)); + + optional c ( + !v.empty () ? optional (v) : nullopt); + + // Check if the package is present in the repository and its + // complements, recursively. + // + shared_ptr ap ( + find_available (db, n, r, c, false /* prereq */).first); + + if (ap == nullptr) + { + diag_record dr (fail); + dr << "package " << s << " is not found in " << r->name; + + if (!r->complements.empty ()) + dr << " nor its complements"; + } + + // Add the package/version to the argument list. + // + eargs.push_back (ap->id.name + '/' + ap->version.string ()); + + b = p != string::npos ? p + 1 : p; + } + } + } + + t.commit (); + } + map package_arg; // Check if the package is a duplicate. Return true if it is but harmless. // - auto check_dup = [&package_arg] (const string& n, const char* arg) -> bool + auto check_dup = [&package_arg] (const string& n, const string& a) -> bool { - auto r (package_arg.emplace (n, arg)); + auto r (package_arg.emplace (n, a)); - if (!r.second && r.first->second != arg) + if (!r.second && r.first->second != a) fail << "duplicate package " << n << info << "first mentioned as " << r.first->second << - info << "second mentioned as " << arg; + info << "second mentioned as " << a; return !r.second; }; // Pre-scan the arguments and sort them out into optional and mandatory. // - strings args; - while (a.more ()) + strings pargs; + for (const string& arg: eargs) { - const char* arg (a.next ()); - const char* s (arg); + const char* s (arg.c_str ()); bool opt (s[0] == '?'); if (opt) ++s; else - args.push_back (s); + pargs.emplace_back (s); if (parse_package_scheme (s) == package_scheme::sys) { @@ -1113,7 +1268,10 @@ namespace bpkg v = wildcard_version; const system_package* sp (system_repository.find (n)); - if (sp == nullptr) // Will deal with all the duplicates later. + + // Will deal with all the duplicates later. + // + if (sp == nullptr || !sp->authoritative) system_repository.insert (n, v, true); } else if (opt) @@ -1121,22 +1279,12 @@ namespace bpkg info << "package is ignored"; } - if (args.empty ()) + if (pargs.empty ()) { warn << "nothing to build"; return 0; } - database db (open (c, trace)); - - // Note that the session spans all our transactions. The idea here is - // that selected_package objects in the build_packages list below will - // be cached in this session. When subsequent transactions modify any - // of these objects, they will modify the cached instance, which means - // our list will always "see" their updated state. - // - session s; - // Assemble the list of packages we will need to build. // build_packages pkgs; @@ -1156,7 +1304,7 @@ namespace bpkg // diagnostics if the name/version guess doesn't pan out. // bool diag (false); - for (auto i (args.cbegin ()); i != args.cend (); ) + for (auto i (pargs.cbegin ()); i != pargs.cend (); ) { const char* package (i->c_str ()); -- cgit v1.1