From 7cc8afb67fecf61994ad6c345559617f374e9d20 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 6 Sep 2021 08:28:53 +0200 Subject: Add argument grouping support for dependencies in bdep-{sync,init} --- bdep/sync.cxx | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 224 insertions(+), 34 deletions(-) (limited to 'bdep/sync.cxx') diff --git a/bdep/sync.cxx b/bdep/sync.cxx index b3ef500..5dc99a0 100644 --- a/bdep/sync.cxx +++ b/bdep/sync.cxx @@ -791,6 +791,8 @@ namespace bdep // configurations (as opposed to paths). Also upgrade can only be specified // with origin_prj. // + // Note that pkg_args and dep_pkgs may contain groups. + // static void cmd_sync (const common_options& co, const dir_path& origin_prj, @@ -853,6 +855,9 @@ namespace bdep // Note that this may add more (implicit) configurations to origin_prj's // entry. // + // @@ We may end up openning the database (in load_implicit()) for each + // project multiple times. + // for (const linked_config& cfg: linked_cfgs) load_implicit (co, cfg.path, prjs, origin_prj, origin_tr); @@ -867,8 +872,10 @@ namespace bdep { auto& pkgs (cfg->packages); - for (const string& n: dep_pkgs) + for (cli::vector_group_scanner s (dep_pkgs); s.more (); ) { + const char* n (s.next ()); + if (find_if (pkgs.begin (), pkgs.end (), [&n] (const package_state& ps) { @@ -876,6 +883,8 @@ namespace bdep }) != pkgs.end ()) fail << "initialized package " << n << " specified as dependency" << info << "package initialized in project " << prj.path; + + s.skip_group (); } } } @@ -959,23 +968,30 @@ namespace bdep { if (origin_only) { - for (const string& a: pkg_args) + for (cli::vector_group_scanner s (pkg_args); s.more (); ) { - if (a.find ('=') == string::npos) + if (strchr (s.next (), '=') == nullptr) { origin_only = false; break; } + + s.skip_group (); } } - for (const string& a: pkg_args) + for (cli::vector_group_scanner s (pkg_args); s.more (); ) { - size_t p (a.find ('=')); - if (p == string::npos) + const char* a (s.next ()); + const char* p (strchr (a, '=')); + + if (p == nullptr) + { + s.skip_group (); continue; + } - if (a.front () != '!') + if (*a != '!') { if (!dep_pkgs.empty ()) dep_vars = true; @@ -989,13 +1005,16 @@ namespace bdep } else fail << "non-global configuration variable " << - string (a, 0, p) << " without packages or dependencies"; + string (a, 0, p - a) << " without packages or dependencies"; if (dep_vars || origin_vars) continue; } args.push_back (a); + + // Note: we let diagnostics for unhandled groups cover groups for + // configuration variables. } if (!args.empty ()) @@ -1074,9 +1093,17 @@ namespace bdep // if (vars) { - for (const string& a: pkg_args) - if (a.find ('=') != string::npos && a.front () != '!') - args.push_back (a); + for (cli::vector_group_scanner s (pkg_args); s.more (); ) + { + const char* a (s.next ()); + if (strchr (a, '=') != nullptr) + { + if (*a != '!') + args.push_back (a); + } + else + s.skip_group (); + } } if (g) @@ -1121,8 +1148,13 @@ namespace bdep // if (upgrade) { - for (const string& n: dep_pkgs) + for (cli::vector_group_scanner s (dep_pkgs); s.more (); ) { + const char* n (s.next ()); + + // Note that we are using the leading group for our options since + // we pass the user's group as trailing below. + // bool g (multi_cfg || dep_vars); if (g) args.push_back ("{"); @@ -1152,9 +1184,17 @@ namespace bdep // if (dep_vars) { - for (const string& a: pkg_args) - if (a.find ('=') != string::npos && a.front () != '!') - args.push_back (a); + for (cli::vector_group_scanner s (pkg_args); s.more (); ) + { + const char* a (s.next ()); + if (strchr (a, '=') != nullptr) + { + if (*a != '!') + args.push_back (a); + } + else + s.skip_group (); + } } if (g) @@ -1162,44 +1202,192 @@ namespace bdep // Make sure it is treated as a dependency. // - args.push_back ('?' + n); + args.push_back (string ("?") + n); + + // Note that bpkg expects options first and configuration variables + // last. Which mean that if we have dep_vars above and an option + // below, then things will blow up. Though it's unclear what option + // someone may want to pass here. + // + cli::scanner& gs (s.group ()); + if (gs.more ()) + { + args.push_back ("+{"); + for (; gs.more (); args.push_back (gs.next ())) ; + args.push_back ("}"); + } } } // Finally, add packages (?) from pkg_args, if any. // // Similar to the dep_pkgs case above, we restrict this to the origin - // configurations. + // configurations unless configuration(s) were explicitly specified by the + // user. // - for (const string& a: pkg_args) + for (cli::vector_group_scanner s (pkg_args); s.more (); ) { - if (a.find ('=') != string::npos) + const char* ca (s.next ()); + + if (strchr (ca, '=') != nullptr) continue; - if (multi_cfg) + string a (ca); // Not guaranteed to be valid after group processing. + + cli::scanner& gs (s.group ()); + + if (gs.more () || multi_cfg) { args.push_back ("{"); - // Note that here (unlike the dep_pkgs case above), we have to make - // sure the configuration is actually involved. - // - for (const sync_config& ocfg: origin_cfgs) + cmd_sync_pkg_options po; + try { - if (find_if (cfgs.begin (), cfgs.end (), - [&ocfg] (const config& cfg) - { - return ocfg.path () == cfg.path.get (); - }) == cfgs.end ()) - continue; + while (gs.more ()) + { + const char* a (gs.peek ()); + + // Handle @ & -@. + // + if (*a == '@' || (*a == '-' && a[1] == '@')) + { + string n (a + (*a == '@' ? 1 : 2)); + + if (n.empty ()) + fail << "missing configuration name in '" << a << "'"; + + po.config_name ().emplace_back (move (n), gs.position ()); + po.config_name_specified (true); + + gs.next (); + continue; + } + + if (!po.parse (gs)) + break; + } + } + catch (const cli::exception& e) + { + fail << e << " grouped for package " << a; + } - args.push_back ("--config-uuid=" + - linked_cfgs.find (ocfg.path ())->uuid.string ()); + if (po.config_specified () || + po.config_id_specified () || + po.config_name_specified ()) + { + auto append = [&linked_cfgs, &args, &a] (const dir_path& d) + { + if (const linked_config* cfg = linked_cfgs.find (d)) + { + args.push_back ("--config-uuid=" + cfg->uuid.string ()); + } + else + fail << "configuration " << d << " is not part of linked " + << "configuration cluster being synchronized" << + info << "specified for package " << a; + }; + + // We will be a bit lax and allow specifying with --config any + // configuration in the cluster, not necessarily associated with the + // origin project (which we may not have). + // + for (dir_path d: po.config ()) + append (normalize (d, "configuration")); + + if (const char* o = (po.config_id_specified () ? "--config-id" : + po.config_name_specified () ? "--config-name|-n" : + nullptr)) + { + if (!origin) + fail << o << "specified without project" << + info << "specified for package " << a; + + // Origin project is first. + // + const dir_path& pd (prjs.front ().path); + + auto lookup = [&append, &po, &pd] (database& db) + { + // Similar code to find_configurations(). + // + using query = bdep::query; + + for (uint64_t id: po.config_id ()) + { + if (auto cfg = db.find (id)) + append (cfg->path); + else + fail << "no configuration id " << id << " in project " << pd; + } + + for (const string& n: po.config_name ()) + { + if (auto cfg = db.query_one (query::name == n)) + append (cfg->path); + else + fail << "no configuration name '" << n << "' in project " + << pd; + } + }; + + // Reuse the transaction, if any (similar to load_implicit()). + // + if (origin_tr != nullptr) + { + lookup (origin_tr->database ()); + } + else + { + // Save and restore the current transaction, if any. + // + transaction* ct (nullptr); + if (transaction::has_current ()) + { + ct = &transaction::current (); + transaction::reset_current (); + } + + auto tg (make_guard ([ct] () + { + if (ct != nullptr) + transaction::current (*ct); + })); + + database db (open (pd, trace)); + transaction t (db.begin ()); + lookup (db); + t.commit (); + } + } + } + else + { + // Note that here (unlike the dep_pkgs case above), we have to make + // sure the configuration is actually involved. + // + for (const sync_config& ocfg: origin_cfgs) + { + if (find_if (cfgs.begin (), cfgs.end (), + [&ocfg] (const config& cfg) + { + return ocfg.path () == cfg.path.get (); + }) == cfgs.end ()) + continue; + + args.push_back ("--config-uuid=" + + linked_cfgs.find (ocfg.path ())->uuid.string ()); + } } + // Add the rest of group arguments (e.g., configuration variables). + // + for (; gs.more (); args.push_back (gs.next ())) ; + args.push_back ("}+"); } - args.push_back (a); + args.push_back (move (a)); } // We do a separate fetch instead of letting pkg-build do it. This way we @@ -1751,8 +1939,8 @@ namespace bdep cmd_sync (const common_options& co, const dir_path& prj, const shared_ptr& c, - const strings& pkg_args, bool implicit, + const strings& pkg_args, bool fetch, bool yes, bool name_cfg, @@ -1842,6 +2030,8 @@ namespace bdep // starts with '?' (dependency flag) or contains '=' (config variable), // then we assume it is pkg-args. // + // Note: scan_argument() passes through groups. + // strings pkg_args; strings dep_pkgs; while (args.more ()) -- cgit v1.1