From 40632ed4dc0a51149ab034cebed6227db21c9dab Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sun, 13 May 2018 12:51:21 +0200 Subject: Implement deinit command --- bdep/bdep.cli | 12 ++++ bdep/bdep.cxx | 2 + bdep/buildfile | 2 + bdep/config.cli | 2 + bdep/deinit.cli | 45 ++++++++++++ bdep/deinit.cxx | 210 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bdep/deinit.hxx | 19 +++++ bdep/init.cxx | 18 +++-- bdep/status.cxx | 3 +- bdep/sync.cxx | 120 ++++++++++++++++++-------------- bdep/sync.hxx | 11 +++ doc/cli.sh | 2 +- 12 files changed, 386 insertions(+), 60 deletions(-) create mode 100644 bdep/deinit.cli create mode 100644 bdep/deinit.cxx create mode 100644 bdep/deinit.hxx diff --git a/bdep/bdep.cli b/bdep/bdep.cli index 0d30561..595e9d9 100644 --- a/bdep/bdep.cli +++ b/bdep/bdep.cli @@ -247,6 +247,13 @@ namespace bdep <...> \ + To deinitialize a project in one or more build configurations we + can use the \l{bdep-deinit(1)} command. For example: + + \ + $ bdep deinit -a + \ + | \li|\b{Add, Remove, or Change Dependencies}\n @@ -426,6 +433,11 @@ namespace bdep "\l{bdep-status(1)} \- print status of project and/or its dependencies" } + bool deinit + { + "\l{bdep-deinit(1)} \- deinitialize project in build configurations" + } + bool config { "\l{bdep-config(1)} \- manage project's build configurations" diff --git a/bdep/bdep.cxx b/bdep/bdep.cxx index b382776..c9d3234 100644 --- a/bdep/bdep.cxx +++ b/bdep/bdep.cxx @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -264,6 +265,7 @@ try COMMAND_IMPL (sync, sync, "sync"); COMMAND_IMPL (fetch, fetch, "fetch"); COMMAND_IMPL (status, status, "status"); + COMMAND_IMPL (deinit, deinit, "deinit"); COMMAND_IMPL (config, config, "config"); COMMAND_IMPL (test, test, "test"); COMMAND_IMPL (update, update, "update"); diff --git a/bdep/buildfile b/bdep/buildfile index 54ba5e4..efb747f 100644 --- a/bdep/buildfile +++ b/bdep/buildfile @@ -27,6 +27,7 @@ init-options \ sync-options \ fetch-options \ status-options \ +deinit-options \ config-options \ test-options \ update-options \ @@ -69,6 +70,7 @@ if $cli.configured cli.cxx{sync-options}: cli{sync} cli.cxx{fetch-options}: cli{fetch} cli.cxx{status-options}: cli{status} + cli.cxx{deinit-options}: cli{deinit} cli.cxx{config-options}: cli{config} cli.cxx{test-options}: cli{test} cli.cxx{update-options}: cli{update} diff --git a/bdep/config.cli b/bdep/config.cli index 1633edb..0a5dd74 100644 --- a/bdep/config.cli +++ b/bdep/config.cli @@ -23,6 +23,8 @@ namespace bdep // // @@ Should we be able to remove config with init'ed packages? Who // is going to drop them in configs? Or require them to be de-init'ed? + // A: yes, only empty configs should be removed, if not empty, suggest + // to deinitialize with bdep-deinit. // // @@ Should set be able to alter forward flag? This will require both // disfiguring forward on old and configuring on new. Perhaps best diff --git a/bdep/deinit.cli b/bdep/deinit.cli new file mode 100644 index 0000000..bfcec51 --- /dev/null +++ b/bdep/deinit.cli @@ -0,0 +1,45 @@ +// file : bdep/deinit.cli +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +include ; + +"\section=1" +"\name=bdep-deinit" +"\summary=deinitialize project in build configurations" + +namespace bdep +{ + { + " + + + ", + + "\h|SYNOPSIS| + + \c{\b{bdep deinit} [] [] []} + + \c{ = (\b{@} | \b{--config}|\b{-c} )... | \b{--all}|\b{-a}\n + = (\b{--directory}|\b{-d} )... | \n + = \b{--directory}|\b{-d} } + + \h|DESCRIPTION| + + The \cb{deinit} command deinitializes the specified project packages + (), or, if the project itself is specified (), all + its previously initialized packages, in one or more build configurations + (). + + If no project directory is specified, then the current working directory + is assumed. If no configuration is specified, then the default + configuration is assumed. See \l{bdep-projects-configs(1)} for details on + specifying projects and configurations. + " + } + + class cmd_deinit_options: project_options + { + "\h|DEINIT OPTIONS|" + }; +} diff --git a/bdep/deinit.cxx b/bdep/deinit.cxx new file mode 100644 index 0000000..4a8c463 --- /dev/null +++ b/bdep/deinit.cxx @@ -0,0 +1,210 @@ +// file : bdep/deinit.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include + +#include // configuration_projects(), hook_file + +using namespace std; + +namespace bdep +{ + static void + cmd_deinit (const cmd_deinit_options& o, + const dir_path& prj, + const shared_ptr& c, + const strings& pkgs) + { + const dir_path& cfg (c->path); + + // Remove auto-synchronization build system hook. + // + // We have to check if there are any other projects that share this + // configuration. Note that we don't load the other project's database, + // check if the configuration is also auto-synchronized (we assume things + // are consistent) or that it has initialized packages (we assume it would + // have been removed from the configuration's repositories if that were + // the case). + // + if (c->auto_sync && + c->packages.empty () && + configuration_projects (o, cfg, prj).empty ()) + { + path f (cfg / hook_file); + if (exists (f)) + rm (f); + } + + // Disfigure configuration forwarding. + // + if (c->forward) + { + package_locations pls (load_packages (prj)); + + for (const string& n: pkgs) + { + dir_path out (dir_path (cfg) /= n); + dir_path src (prj); + { + auto i (find_if (pls.begin (), + pls.end (), + [&n] (const package_location& pl) + { + return pl.name == n; + })); + + if (i == pls.end ()) + fail << "package " << n << " is not listed in " << prj; + + src /= i->path; + } + + run_b (o, + "disfigure:", + src.representation () + '@' + out.representation () + + ",forward"); + } + } + + // Note that --keep-dependent is important: if we drop dependent packages + // that are managed by bdep, then its view of what has been initialized + // in the configuration will become invalid. + // + run_bpkg (2, + o, + "drop", + "-d", cfg, + "--keep-dependent", + "--plan", "synchronizing:", + "--yes", + pkgs); + } + + int + cmd_deinit (const cmd_deinit_options& o, cli::scanner&) + { + tracer trace ("deinit"); + + // The same ignore/load story as in sync. + // + project_packages pp ( + find_project_packages (o, + false /* ignore_packages */, + false /* load_packages */)); + + const dir_path& prj (pp.project); + + if (verb) + text << "deinitializing in project " << prj; + + database db (open (prj, trace)); + + transaction t (db.begin ()); + configurations cfgs (find_configurations (prj, t, o)); + t.commit (); + + // If specified, verify packages are present in each configuration. + // + if (!pp.packages.empty ()) + verify_project_packages (pp, cfgs); + + // If no packages were explicitly specified, then we deinitalize all that + // have been initialized in each configuration. + // + strings pkgs; + + bool all (pp.packages.empty ()); + if (!all) + { + for (const package_location& p: pp.packages) + pkgs.push_back (p.name); + } + + // Deinitialize in each configuration skipping empty ones. + // + // We do each configuration in a separate transaction so that our state + // reflects the bpkg configuration as closely as possible. + // + bool first (true); + for (const shared_ptr& c: cfgs) + { + if (c->packages.empty ()) + { + if (verb) + info << "skipping empty configuration " << *c; + + continue; + } + + // If we are printing multiple configurations, separate them with a + // blank line and print the configuration name/directory. + // + if (verb && cfgs.size () > 1) + { + text << (first ? "" : "\n") + << "in configuration " << *c << ':'; + + first = false; + } + + transaction t (db.begin ()); + + // Collect packages to drop and remove them from the configuration. + // + if (all) + { + pkgs.clear (); + + for (const package_state& p: c->packages) + pkgs.push_back (p.name); + } + + c->packages.erase ( + remove_if (c->packages.begin (), + c->packages.end (), + [&pkgs] (const package_state& p) + { + return find_if (pkgs.begin (), + pkgs.end (), + [&p] (const string& n) + { + return p.name == n; + }) != pkgs.end (); + }), + c->packages.end ()); + + // If we are deinitializing multiple packages, print their names. + // + if (verb && pkgs.size () > 1) + { + for (const string& n: pkgs) + text << "deinitializing package " << n; + } + + // The same story as in init with regard to the state update order. + // + cmd_deinit (o, prj, c, pkgs); + + db.update (c); + t.commit (); + + // Remove our repository from the configuration if we have no more + // packages that are initialized in it. + // + if (c->packages.empty ()) + run_bpkg (3, + o, + "remove", + "-d", c->path, + "dir:" + prj.string ()); + } + + return 0; + } +} diff --git a/bdep/deinit.hxx b/bdep/deinit.hxx new file mode 100644 index 0000000..9a2099d --- /dev/null +++ b/bdep/deinit.hxx @@ -0,0 +1,19 @@ +// file : bdep/deinit.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BDEP_DEINIT_HXX +#define BDEP_DEINIT_HXX + +#include +#include + +#include + +namespace bdep +{ + int + cmd_deinit (const cmd_deinit_options&, cli::scanner& args); +} + +#endif // BDEP_DEINIT_HXX diff --git a/bdep/init.cxx b/bdep/init.cxx index 1c885eb..9fe4334 100644 --- a/bdep/init.cxx +++ b/bdep/init.cxx @@ -80,8 +80,6 @@ namespace bdep first = false; } - transaction t (db.begin ()); - // Add project repository to the configuration. Note that we don't fetch // it since sync is going to do it anyway. // @@ -92,6 +90,8 @@ namespace bdep "--type", "dir", prj); + transaction t (db.begin ()); + for (const package_location& p: pkgs) { if (find_if (c->packages.begin (), @@ -115,10 +115,18 @@ namespace bdep c->packages.push_back (package_state {p.name}); } + // Should we sync then commit the database or commit and then sync? + // Either way we can end up with an incosistent state. Note, however, + // that the state in the build configuration can in most cases be + // corrected with a retry (e.g., "upgrade" the package to the fixed + // version, etc) while if we think (from the database state) that the + // package has already been initialized, then there will be no way to + // retry anything. + // + cmd_sync (o, prj, c, pkg_args, false /* implicit */); + db.update (c); t.commit (); - - cmd_sync (o, prj, c, pkg_args, false /* implicit */); } } @@ -148,7 +156,7 @@ namespace bdep const dir_path& prj (pp.project); if (verb) - text << "initializing project " << prj; + text << "initializing in project " << prj; // Create .bdep/. // diff --git a/bdep/status.cxx b/bdep/status.cxx index 3c8dfe3..b460cd1 100644 --- a/bdep/status.cxx +++ b/bdep/status.cxx @@ -5,7 +5,6 @@ #include #include -#include #include #include @@ -92,7 +91,7 @@ namespace bdep strings dep_pkgs; for (; args.more (); dep_pkgs.push_back (args.next ())) ; - // For the project status the same story as in sync. + // The same ignore/load story as in sync. // project_packages pp ( find_project_packages (o, diff --git a/bdep/sync.cxx b/bdep/sync.cxx index 484a1ad..19afdce 100644 --- a/bdep/sync.cxx +++ b/bdep/sync.cxx @@ -18,29 +18,15 @@ using namespace std; namespace bdep { - // Project to be synchronized. - // - struct project - { - dir_path path; - shared_ptr config; - - bool implicit; - bool fetch; - }; - - using projects = small_vector; + const path hook_file ( + dir_path ("build") / "bootstrap" / "pre-bdep-sync.build"); - // Append the list of additional (to origin, if not empty) projects that are - // using this configuration. - // - static void - load_implicit (const common_options& co, - const dir_path& cfg, - const dir_path& origin_prj, - projects& r) + dir_paths + configuration_projects (const common_options& co, + const dir_path& cfg, + const dir_path& prj) { - tracer trace ("load_implicit"); + dir_paths r; // Use bpkg-rep-list to discover the list of project directories. // @@ -81,7 +67,7 @@ namespace bdep d.normalize (); // For good measure. - if (d == origin_prj) + if (d == prj) continue; // Next see if it looks like a bdep-managed project. @@ -89,29 +75,7 @@ namespace bdep if (!exists (d / bdep_file)) continue; - shared_ptr c; - { - using query = bdep::query; - - database db (open (d, trace)); - - transaction t (db.begin ()); - c = db.query_one (query::path == cfg.string ()); - t.commit (); - } - - // If the project is a repository of this configuration but the bdep - // database has no knowledge of this configuration, then assume it is - // not managed by bdep (i.e., the user added the project manually or - // some such). - // - if (c == nullptr) - continue; - - r.push_back (project {move (d), - move (c), - true /* implicit */, - true /* fetch */}); + r.push_back (move (d)); } is.close (); // Detect errors. @@ -125,6 +89,60 @@ namespace bdep } finish_bpkg (co, pr, io); + + return r; + } + + // Project to be synchronized. + // + struct project + { + dir_path path; + shared_ptr config; + + bool implicit; + bool fetch; + }; + + using projects = small_vector; + + // Append the list of additional (to origin, if not empty) projects that are + // using this configuration. + // + static void + load_implicit (const common_options& co, + const dir_path& cfg, + const dir_path& origin_prj, + projects& r) + { + tracer trace ("load_implicit"); + + for (dir_path& d: configuration_projects (co, cfg, origin_prj)) + { + shared_ptr c; + { + using query = bdep::query; + + database db (open (d, trace)); + + transaction t (db.begin ()); + c = db.query_one (query::path == cfg.string ()); + t.commit (); + } + + // If the project is a repository of this configuration but the bdep + // database has no knowledge of this configuration, then assume it is + // not managed by bdep (i.e., the user added the project manually or + // some such). + // + if (c == nullptr) + continue; + + r.push_back (project {move (d), + move (c), + true /* implicit */, + true /* fetch */}); + } } // Sync with optional upgrade. @@ -252,9 +270,6 @@ namespace bdep // "synchronizing :". Maybe rep-fetch also needs something // like --plan but for progress? Plus there might be no sync at all. // - // @@ TODO: remember to rep-remove in deinit if there are no more - // init'ed packages in this configuration. - // if (!reps.empty ()) run_bpkg (3, co, "fetch", "-d", cfg, "--shallow", reps); @@ -353,9 +368,10 @@ namespace bdep if (o != nullptr) { dir_path out (dir_path (cfg) /= pkg.name); - string arg (src.representation () + '@' + out.representation () + - ",forward"); - run_b (co, o, arg); + run_b (co, + o, + src.representation () + '@' + out.representation () + + ",forward"); } } } @@ -364,7 +380,7 @@ namespace bdep // if (origin_config != nullptr && !implicit) { - path f (cfg / "build" / "bootstrap" / "pre-bdep-sync.build"); + path f (cfg / hook_file); bool e (exists (f)); if (origin_config->auto_sync) diff --git a/bdep/sync.hxx b/bdep/sync.hxx index 1aebfe3..6ca3937 100644 --- a/bdep/sync.hxx +++ b/bdep/sync.hxx @@ -33,6 +33,17 @@ namespace bdep int cmd_sync (cmd_sync_options&&, cli::group_scanner& args); + + + // Return the list of additional (to prj, if not empty) projects that are + // using this configuration. + // + dir_paths + configuration_projects (const common_options& co, + const dir_path& cfg, + const dir_path& prj = dir_path ()); + + extern const path hook_file; // build/bootstrap/pre-bdep-sync.build } #endif // BDEP_SYNC_HXX diff --git a/doc/cli.sh b/doc/cli.sh index e75346d..071e7fd 100755 --- a/doc/cli.sh +++ b/doc/cli.sh @@ -60,7 +60,7 @@ o="--suppress-undocumented --output-prefix bdep- --class-doc bdep::common_option compile "common" $o --output-suffix "-options" --class-doc bdep::common_options=long compile "bdep" $o --output-prefix "" --class-doc bdep::commands=short --class-doc bdep::topics=short -pages="new help init sync fetch status config test update clean projects-configs" +pages="new help init sync fetch status deinit config test update clean projects-configs" for p in $pages; do compile $p $o -- cgit v1.1