From d77ca8720df495017139a24a59c502f53c07df9f Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 22 Apr 2021 21:57:13 +0300 Subject: Add support for associated configurations --- bpkg/cfg-add.cxx | 324 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 bpkg/cfg-add.cxx (limited to 'bpkg/cfg-add.cxx') diff --git a/bpkg/cfg-add.cxx b/bpkg/cfg-add.cxx new file mode 100644 index 0000000..6528423 --- /dev/null +++ b/bpkg/cfg-add.cxx @@ -0,0 +1,324 @@ +// file : bpkg/cfg-add.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include + +using namespace std; + +namespace bpkg +{ + shared_ptr + cfg_add (database& db, + const dir_path& ad, + bool rel, + optional name, + bool sys_rep) + { + tracer trace ("cfg_add"); + + bool name_specified (name); + const dir_path& cd (db.config); // Note: absolute and normalized. + + // Load the self-association object from the database of the configuration + // being associated to obtain its name, type, and uuid. + // + database& adb (db.attach (ad, sys_rep)); + + string type; + uuid uid; + { + shared_ptr cf (adb.load (0)); + + type = move (cf->type); + uid = cf->uuid; + + if (!name) + name = move (cf->name); + } + + if (db.uuid == uid) + fail << "associating configuration " << ad << " with itself" << + info << "uuid: " << uid; + + if (name && name == db.name) + fail << "associating configuration " << ad << " using current " + << "configuration name '" << *name << "'" << + info << "consider specifying alternative name with --name"; + + // Verify that the name and path of the configuration being associated do + // not clash with already associated configurations. Fail if + // configurations with this uuid is already associated unless the + // association is implicit, in which case make it explicit and update its + // name and path. + // + // Note that when we make an implicit association explicit, we start + // treating it as an implicit and explicit simultaneously. So, for + // example, for cfg1 the association cfg2 is explicit and the association + // cfg3 is both explicit and implicit: + // + // cfg2 <- cfg1 <-> cfg3 + // + // Similar, if we associate cfg1 with cfg2, the explicit association cfg2 + // in cfg1 also becomes both explicit and implicit, not being amended + // directly. + // + shared_ptr acf; + + using query = query; + + for (shared_ptr ac: + pointer_result (db.query (query::id != 0))) + { + if (uid == ac->uuid) + { + if (ac->expl) + fail << "configuration with uuid " << uid << " is already " + << "associated as " << ac->path; + + // Verify the existing implicit association integrity and cache it to + // update later, when the name/path clash check is complete. + // + db.verify_association (*ac, adb); + + acf = move (ac); + continue; + } + + if (ad == ac->effective_path (cd)) + fail << "configuration with path " << ad << " is already associated"; + + // If the name clashes, then fail if it was specified by the user and + // issue a warning and associate the configuration as unnamed otherwise. + // + if (name && name == ac->name) + { + diag_record dr (name_specified ? error : warn); + dr << "configuration with name " << *name << " is already " + << "associated as " << ac->path; + + if (name_specified) + { + dr << info << "consider specifying alternative name with --name" + << endf; + } + else + { + dr << ", associating as unnamed"; + name = nullopt; + } + } + } + + // If requested, rebase the first path relative to the second or return it + // as is otherwise. Fail if the rebase is not possible (e.g., paths are on + // different drives on Windows). + // + auto rebase = [rel] (const dir_path& x, const dir_path& y) -> dir_path + { + try + { + return rel ? x.relative (y) : x; + } + catch (const invalid_path&) + { + fail << "unable to rebase " << x << " relative to " << y << + info << "specify absolute configuration directory path to save it " + << "as absolute" << endf; + } + }; + + // If the implicit association already exists, then make it explicit and + // update its name and path. Otherwise, create a new association. + // + // Note that in the former case the current configuration must already be + // explicitly associated with the configuration being associated. We + // verify that and the association integrity. + // + if (acf != nullptr) + { + // Verify the reverse association integrity. + // + shared_ptr cf ( + adb.query_one (query::uuid == db.uuid.string ())); + + // Note: both sides of the association cannot be implicit. + // + if (cf == nullptr || !cf->expl) + fail << "configuration " << ad << " is already implicitly " + << "associated but current configuration " << cd << " is not " + << "explicitly associated with it"; + + adb.verify_association (*cf, db); + + // Finally, turn the implicit association into explicit. + // + // Note: reuse id. + // + acf->expl = true; + acf->name = move (name); + acf->path = rebase (ad, cd); // Note: can't clash (see above). + + db.update (acf); + } + else + { + // If the directory path of the configuration being associated is + // relative or the --relative option is specified, then rebase it + // relative to the current configuration directory path. + // + acf = make_shared (uid, + move (name), + move (type), + rebase (ad, cd), + true /* explicit */); + + db.persist (acf); + + // Now implicitly associate ourselves with the just associated + // configuration. Note that we associate ourselves as unnamed. + // + shared_ptr ccf (db.load (0)); + + // What if we find the current configuration to already be implicitly + // associated? The potential scenario could be, that the current + // configuration was recreated from scratch, previously being implicitly + // associated with the configuration we currently associate. It feels + // like in this case we would rather overwrite the existing dead + // implicit association than just fail. Let's also warn for good + // measure. + // + shared_ptr cf; + + for (shared_ptr ac: + pointer_result (adb.query (query::id != 0))) + { + if (cd == ac->make_effective_path (ad)) + { + if (ac->expl) + fail << "current configuration " << cd << " is already " + << "associated with " << ad; + + warn << "current configuration " << cd << " is already " + << "implicitly associated with " << ad; + + cf = move (ac); + continue; + } + + if (ccf->uuid == ac->uuid) + fail << "current configuration " << ccf->uuid + << " is already associated with " << ad; + } + + // It feels natural to persist explicitly and implicitly associated + // configuration paths both either relative or absolute. + // + if (cf != nullptr) + { + // The dead implicit association case. + // + // Note: reuse id. + // + cf->uuid = ccf->uuid; + cf->type = move (ccf->type); + cf->path = rebase (cd, ad); + + adb.update (cf); + } + else + { + ccf = make_shared (ccf->uuid, + nullopt /* name */, + move (ccf->type), + rebase (cd, ad), + false /* explicit */); + + adb.persist (ccf); + } + } + + // If explicit associations of the current database are pre-attached, then + // also pre-attach explicit associations of the newly associated database. + // + associated_configs& acs (db.explicit_associations ()); + + if (!acs.empty ()) + { + acs.push_back (associated_config {*acf->id, acf->name, adb}); + adb.attach_explicit (sys_rep); + } + + // If the implicit associations of the added database are already + // attached, then also attach the current database, unless it is already + // there (see above for the dead association case). + // + associated_databases& ads (adb.implicit_associations (false /* attach */)); + + if (!ads.empty () && find (ads.begin (), ads.end (), db) == ads.end ()) + ads.push_back (db); + + return acf; + } + + int + cfg_add (const cfg_add_options& o, cli::scanner& args) + try + { + tracer trace ("cfg_add"); + + dir_path c (o.directory ()); + l4 ([&]{trace << "configuration: " << c;}); + + if (o.name_specified ()) + validate_configuration_name (o.name (), "--name option value"); + + if (!args.more ()) + fail << "configuration directory argument expected" << + info << "run 'bpkg help cfg-add' for more information"; + + dir_path ad (args.next ()); + if (ad.empty ()) + throw invalid_path (ad.string ()); + + l4 ([&]{trace << "add configuration: " << ad;}); + + bool rel (ad.relative () || o.relative ()); + normalize (ad, "specified associated configuration"); + + database db (c, trace, false /* pre_attach */, false /* sys_rep */, &ad); + transaction t (db); + + shared_ptr ac ( + cfg_add (db, + ad, + rel, + o.name_specified () ? o.name () : optional ())); + + t.commit (); + + if (verb && !o.no_result ()) + { + diag_record dr (text); + + dr << "associated configuration " << ad << + info << "uuid: " << ac->uuid << + info << "type: " << ac->type; + + if (ac->name) + dr << info << "name: " << *ac->name; + + dr << info << "id: " << *ac->id; + } + + return 0; + } + catch (const invalid_path& e) + { + fail << "invalid path: '" << e.path << "'" << endf; + } +} -- cgit v1.1