diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2018-04-28 16:41:02 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2018-04-28 16:41:02 +0200 |
commit | b2d5f82512d0118a0668ce02f1a0730c3dcd50b8 (patch) | |
tree | b470b17e276a67164212065f164823aa30b5165f | |
parent | 77a9dc17b487123dc1aaf5c41b539d9abfe32dee (diff) |
Implement auto-synchronization via build system hook
-rw-r--r-- | bdep/bdep.cxx | 3 | ||||
-rw-r--r-- | bdep/config.cli | 22 | ||||
-rw-r--r-- | bdep/config.cxx | 73 | ||||
-rw-r--r-- | bdep/config.hxx | 18 | ||||
-rw-r--r-- | bdep/init.cxx | 24 | ||||
-rw-r--r-- | bdep/init.hxx | 5 | ||||
-rw-r--r-- | bdep/new.cxx | 13 | ||||
-rw-r--r-- | bdep/project.cli | 11 | ||||
-rw-r--r-- | bdep/project.hxx | 1 | ||||
-rw-r--r-- | bdep/project.xml | 1 | ||||
-rw-r--r-- | bdep/sync.cli | 23 | ||||
-rw-r--r-- | bdep/sync.cxx | 178 | ||||
-rw-r--r-- | bdep/sync.hxx | 3 | ||||
-rw-r--r-- | bdep/utility.cxx | 1 | ||||
-rw-r--r-- | bdep/utility.hxx | 8 |
15 files changed, 289 insertions, 95 deletions
diff --git a/bdep/bdep.cxx b/bdep/bdep.cxx index 2541325..82a4259 100644 --- a/bdep/bdep.cxx +++ b/bdep/bdep.cxx @@ -121,7 +121,8 @@ try { using namespace cli; - exec_dir = path (argv[0]).directory (); + argv0 = argv[0]; + exec_dir = path (argv0).directory (); // This is a little hack to make our baseutils for Windows work when called // with absolute path. In a nutshell, MSYS2's exec*p() doesn't search in the diff --git a/bdep/config.cli b/bdep/config.cli index c9b2ce8..2c9e19f 100644 --- a/bdep/config.cli +++ b/bdep/config.cli @@ -39,7 +39,8 @@ namespace bdep \b{bdep config rename} [<options>] [<prj-spec>] <cfg-spec> <cfg-name>\n \b{bdep config set} \ \ \ [<options>] [<prj-spec>] <cfg-spec>\n \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [\b{--}[\b{no-}]\b{default}]\n - \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [\b{--}[\b{no-}]\b{forward}]} + \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [\b{--}[\b{no-}]\b{forward}]\n + \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [\b{--}[\b{no-}]\b{auto-sync}]} \c{<cfg-spec> = \b{@}<cfg-name> | \b{--config}|\b{-c} <cfg-dir>\n <prj-spec> = \b{--directory}|\b{-d} <prj-dir>\n @@ -89,7 +90,14 @@ namespace bdep initialized in a forwarded build configuration, its source directory is configured to forward to this configuration (see \l{b(1)} for details on forwarded configurations). To forward to a non-default - configuration use the \cb{--forward} option.| + configuration use the \cb{--forward} option. + + Unless the \cb{--no-auto-sync} option is specified, an added or + created build configuration will be automatically synchronized on + every build system invocation. Note that this flag affects the entire + build configuration and if multiple projects share the same + configuration, then they must have a consistent auto-synchronization + setting.| \li|\cb{remove} @@ -106,10 +114,12 @@ namespace bdep \li|\cb{set} The \cb{set} subcommand sets various properties of the specified - build configuration. These include the default - (\c{\b{--}[\b{no-}]\b{default}}) and forward - (\c{\b{--}[\b{no-}]\b{forward}}) flags. Note that changing the - forward flag requires an explicit \cb{sync} command to take effect.||" + build configuration. These include the + default (\c{\b{--}[\b{no-}]\b{default}}), + forward (\c{\b{--}[\b{no-}]\b{forward}}), and + auto-synchronization (\c{\b{--}[\b{no-}]\b{auto-sync}}) flags. + Note that changing any of these flags requires an explicit + \cb{sync} command to take effect.||" bool add; bool create; diff --git a/bdep/config.cxx b/bdep/config.cxx index 0dcea48..aeb1a5f 100644 --- a/bdep/config.cxx +++ b/bdep/config.cxx @@ -13,14 +13,13 @@ using namespace std; namespace bdep { shared_ptr<configuration> - cmd_config_add (const dir_path& prj, - database& db, - dir_path path, - optional<string> name, - optional<bool> def, - optional<bool> fwd, - optional<uint64_t> id, - const char* what) + cmd_config_add (const configuration_add_options& ao, + const dir_path& prj, + database& db, + dir_path path, + optional<string> name, + optional<uint64_t> id, + const char* what) { if (!exists (path)) fail << "configuration directory " << path << " does not exist"; @@ -29,11 +28,15 @@ namespace bdep using count = configuration_count; + optional<bool> def, fwd; { using query = bdep::query<configuration>; // By default the first added configuration is the default. // + if (ao.default_ () || ao.no_default ()) + def = ao.default_ () && !ao.no_default (); + if (!def) def = (db.query_value<count> () == 0); else if (*def) @@ -46,6 +49,9 @@ namespace bdep // By default the default configuration is forwarded unless another // is already forwarded. // + if (ao.forward () || ao.no_forward ()) + fwd = ao.forward () && !ao.no_forward (); + if (!fwd) fwd = *def && db.query_one<configuration> (query::forward) == nullptr; else if (*fwd) @@ -73,6 +79,7 @@ namespace bdep move (rel_path), *def, *fwd, + !ao.no_auto_sync (), {} /* packages */}); try @@ -107,12 +114,13 @@ namespace bdep if (verb) { diag_record dr (text); - /* */ dr << what << " configuration "; - if (r->name) dr << '@' << *r->name << ' '; - /* */ dr << r->path << " (" << *r->id; - if (r->default_) dr << ", default"; - if (r->forward) dr << ", forwarded"; - /* */ dr << ')'; + /* */ dr << what << " configuration "; + if (r->name) dr << '@' << *r->name << ' '; + /* */ dr << r->path << " (" << *r->id; + if (r->default_) dr << ", default"; + if (r->forward) dr << ", forwarded"; + if (r->auto_sync) dr << ", auto-synchronized"; + /* */ dr << ')'; } return r; @@ -125,15 +133,14 @@ namespace bdep } shared_ptr<configuration> - cmd_config_create (const common_options& co, - const dir_path& prj, - database& db, - dir_path path, - cli::scanner& cfg_args, - optional<string> name, - optional<bool> def, - optional<bool> fwd, - optional<uint64_t> id) + cmd_config_create (const common_options& co, + const configuration_add_options& ao, + const dir_path& prj, + database& db, + dir_path path, + cli::scanner& cfg_args, + optional<string> name, + optional<uint64_t> id) { // Call bpkg to create the configuration. // @@ -145,12 +152,11 @@ namespace bdep run_bpkg (co, "create", "-d", path, args); } - return cmd_config_add (prj, + return cmd_config_add (ao, + prj, db, move (path), move (name), - def, - fwd, id, "created"); } @@ -192,10 +198,17 @@ namespace bdep if (o.forward () && o.no_forward ()) fail << "both --forward and --no-forward specified"; - return (o.default_ () ? "--default" : - o.forward () ? "--forward" : - o.no_default () ? "--no-default" : - o.no_forward () ? "--no-forward" : nullptr); + // --[no-]auto-sync + // + if (o.auto_sync () && o.no_auto_sync ()) + fail << "both --auto-sync and --no-auto-sync specified"; + + return (o.default_ () ? "--default" : + o.no_default () ? "--no-default" : + o.forward () ? "--forward" : + o.no_forward () ? "--no-forward" : + o.auto_sync () ? "--auto-sync" : + o.no_auto_sync () ? "--no-auto-sync" : nullptr); } int diff --git a/bdep/config.hxx b/bdep/config.hxx index b521bac..6bc732e 100644 --- a/bdep/config.hxx +++ b/bdep/config.hxx @@ -14,25 +14,23 @@ namespace bdep { shared_ptr<configuration> - cmd_config_add (const dir_path& prj, + cmd_config_add (const configuration_add_options&, + const dir_path& prj, database&, dir_path path, optional<string> name, - optional<bool> default_ = nullopt, - optional<bool> forward = nullopt, optional<uint64_t> id = nullopt, const char* what = "added"); shared_ptr<configuration> cmd_config_create (const common_options&, - const dir_path& prj, + const configuration_add_options&, + const dir_path& prj, database&, - dir_path path, - cli::scanner& args, - optional<string> name, - optional<bool> default_ = nullopt, - optional<bool> forward = nullopt, - optional<uint64_t> id = nullopt); + dir_path path, + cli::scanner& args, + optional<string> name, + optional<uint64_t> id = nullopt); int cmd_config (const cmd_config_options&, cli::scanner& args); diff --git a/bdep/init.cxx b/bdep/init.cxx index 1875c42..303c475 100644 --- a/bdep/init.cxx +++ b/bdep/init.cxx @@ -18,14 +18,13 @@ namespace bdep { shared_ptr<configuration> cmd_init_config (const configuration_name_options& o, + const configuration_add_options& ao, const dir_path& prj, database& db, const dir_path& cfg, cli::scanner& args, bool ca, - bool cc, - optional<bool> cd, - optional<bool> cf) + bool cc) { const char* m (!ca ? "--config-create" : !cc ? "--config-add" : nullptr); @@ -52,8 +51,8 @@ namespace bdep } return ca - ? cmd_config_add ( prj, db, cfg, move (nm), cd, cf, move (id)) - : cmd_config_create (o, prj, db, cfg, args, move (nm), cd, cf, move (id)); + ? cmd_config_add ( ao, prj, db, cfg, move (nm), move (id)) + : cmd_config_create (o, ao, prj, db, cfg, args, move (nm), move (id)); } void @@ -117,7 +116,7 @@ namespace bdep db.update (c); t.commit (); - cmd_sync (o, prj, c); + cmd_sync (o, prj, c, false /* implicit */); } } @@ -181,25 +180,16 @@ namespace bdep configurations cfgs; if (ca || cc) { - optional<bool> cd; - if (o.default_ () || o.no_default ()) - cd = o.default_ () && !o.no_default (); - - optional<bool> cf; - if (o.forward () || o.no_forward ()) - cf = o.forward () && !o.no_forward (); - cfgs.push_back ( cmd_init_config ( o, + o, prj, db, ca ? o.config_add () : o.config_create (), args, ca, - cc, - cd, - cf)); + cc)); // Fall through. } diff --git a/bdep/init.hxx b/bdep/init.hxx index 6e52737..1ac46c1 100644 --- a/bdep/init.hxx +++ b/bdep/init.hxx @@ -17,14 +17,13 @@ namespace bdep // shared_ptr<configuration> cmd_init_config (const configuration_name_options&, + const configuration_add_options&, const dir_path& prj, database&, const dir_path& cfg, cli::scanner& cfg_args, bool config_add_specified, - bool config_create_specified, - optional<bool> config_default, - optional<bool> config_forward); + bool config_create_specified); // Initialize each package in each configuration skipping those that are // already initialized. Then synchronize each configuration. diff --git a/bdep/new.cxx b/bdep/new.cxx index a6074b8..85acad4 100644 --- a/bdep/new.cxx +++ b/bdep/new.cxx @@ -617,25 +617,16 @@ namespace bdep if (ca || cc) { - optional<bool> cd; - if (o.default_ () || o.no_default ()) - cd = o.default_ () && !o.no_default (); - - optional<bool> cf; - if (o.forward () || o.no_forward ()) - cf = o.forward () && !o.no_forward (); - configurations cfgs { cmd_init_config ( o, + o, prj, db, ca ? o.config_add () : o.config_create (), args, ca, - cc, - cd, - cf)}; + cc)}; package_locations pkgs {{n, dir_path ()}}; // project == package diff --git a/bdep/project.cli b/bdep/project.cli index 137c77d..2fe37e4 100644 --- a/bdep/project.cli +++ b/bdep/project.cli @@ -31,6 +31,17 @@ namespace bdep { "Don't make the added or created configuration forwarded." } + + bool --auto-sync + { + "Make the added or created configuration automatically synchronized." + } + + bool --no-auto-sync + { + "Don't make the added or created configuration automatically + synchronized." + } }; // Common options for commands that accept --config-id and @<cfg-name>. diff --git a/bdep/project.hxx b/bdep/project.hxx index 2e8d366..bbfd41d 100644 --- a/bdep/project.hxx +++ b/bdep/project.hxx @@ -79,6 +79,7 @@ namespace bdep bool default_; bool forward; + bool auto_sync; // We made it a vector instead of set/map since we are unlikely to have // more than a handful of packages. We may, however, want to use a change- diff --git a/bdep/project.xml b/bdep/project.xml index 8d1bf26..b9da262 100644 --- a/bdep/project.xml +++ b/bdep/project.xml @@ -7,6 +7,7 @@ <column name="relative_path" type="TEXT" null="true"/> <column name="default" type="INTEGER" null="true"/> <column name="forward" type="INTEGER" null="true"/> + <column name="auto_sync" type="INTEGER" null="true"/> <primary-key auto="true"> <column name="id"/> </primary-key> diff --git a/bdep/sync.cli b/bdep/sync.cli index 4fd5cb8..61d15cb 100644 --- a/bdep/sync.cli +++ b/bdep/sync.cli @@ -198,5 +198,28 @@ namespace bdep { "Don't prompt for confirmation when up/down-grading dependencies." } + + bool --implicit + { + "Perform implicit synchronization. This mode is normally used by other + tools (for example, a build system hook) to ensure packages and + configurations are synchronized. To improve performance, especially for + the \"everything is already synchronized\" case, \cb{sync} executed in + this mode assumes that no configuration flags (see \l{bdep-config(1)}) + have changed since the last explicit synchronization. + + To avoid recursive re-synchronization, the \cb{sync} command maintains + the \cb{BDEP_SYNCED_CONFIGS} environment variable. It contains a + space-separated, \cb{\"}-quoted list of configuration paths that have + been or are being synchronized by the current \cb{bdep} invocation + chain. The \cb{sync} command in the implicit mode also examines this + variable and silently ignores synchronization requests that have been + or are already being performed." + } + + // The build system hook protocol version. Internal, undocumented, and + // implies --implicit. + // + uint16_t --hook = 0; }; } diff --git a/bdep/sync.cxx b/bdep/sync.cxx index 6c29a50..b95aa54 100644 --- a/bdep/sync.cxx +++ b/bdep/sync.cxx @@ -4,6 +4,8 @@ #include <bdep/sync.hxx> +#include <stdlib.h> // getenv() setenv()/_putenv() + #include <bdep/database.hxx> #include <bdep/diagnostics.hxx> @@ -22,6 +24,7 @@ namespace bdep cmd_sync (const common_options& co, const dir_path& prj, const shared_ptr<configuration>& c, + bool implicit, bool fetch, bool yes, optional<bool> upgrade, // true - upgrade, false - patch @@ -44,6 +47,10 @@ namespace bdep // We also use the repository name rather than the location as a sanity // check (the repository must have been added as part of init). // + // @@ For implicit fetch this will print diagnostics/progress before + // "synchronizing <cfg-dir>:". Maybe rep-fetch also needs something + // like --plan but for progress? Plus there might be no sync at all. + // if (fetch) run_bpkg (co, "fetch", @@ -110,36 +117,42 @@ namespace bdep } } + // For implicit sync (normally performed on one configuration at a time) + // add the configuration directory to the plan header. + // + // We use the configuration directory rather than the name because this + // could be a multi-project configuration and in the implicit mode there + // will normally be no "originating project" (unlike with explicit sync). + // + string plan (implicit + ? "synchronizing " + c->path.representation () + ':' + : "synchronizing:"); + run_bpkg (co, "build", "-d", c->path, "--no-fetch", "--configure-only", "--keep-out", - "--plan", "synchronizing:", + "--plan", plan, (yes ? "--yes" : nullptr), args); - // Handle configuration forwarding. // // We do it here (instead of, say init) because a change in a package may // introduce new subprojects. Though it would be nice to only do this if - // the package was upgraded (probably by comparing before/after versions - // @@ TODO: changed flag below). + // the package was upgraded. + // + // @@ TODO: changed flag below: Probably by comparing before/after + // versions. Or, even simpler, if pkg-build signalled that + // nothing has changed (the --exit-result idea) -- then we + // can skip the whole thing. // // Also, the current thinking is that config set --[no-]forward is best // implemented by just changing the flag on the configuration and then // requiring an explicit sync to configure/disfigure forwards. // - // @@ TODO: could optimize out version query/comparison if pkg-build - // signalled that nothing has changed (the --exit-result idea). Would - // be nice to optimize the whole thing. Maybe --force flag (for things - // like config set --no-forward)? Or maybe we should require explicit - // sync for certain changes (and pass --implicit or some such in hook - // -- after all, configuring forward of a project being bootstrapped - // might get tricky). - // package_locations pls (load_packages (prj)); for (const package_state& p: c->packages) @@ -177,7 +190,7 @@ namespace bdep if (changed || !e) o = "configure:"; } - else + else if (!implicit) // Requires explicit sync. { //@@ This is broken: we will disfigure forwards to other configs. // Looks like we will need to test that the forward is to this @@ -195,18 +208,71 @@ namespace bdep run_b (co, o, arg); } } + + // Add/remove auto-synchronization build system hook. + // + if (!implicit) + { + path f (c->path / "build" / "bootstrap" / "pre-bdep-sync.build"); + bool e (exists (f)); + + if (c->auto_sync) + { + if (!e) + { + mk (f.directory ()); + + try + { + ofdstream os (f); + + // Should we analyze BDEP_SYNCED_CONFIGS ourselves or should we + // let bdep-sync to it for us? Doing it here instead of spawning a + // process (which will loading the database, etc) will be faster. + // But, on the other hand, this is only an issue for commands like + // update and test that do their own implicit sync. + // + // cfgs = $getenv(BDEP_SYNCED_CONFIGS) + // if! $null($cfgs) + // cfgs = [dir_paths] $regex.split($cfgs, ' *"([^"]*)" *', '\1') + // + os << "# Created automatically by bdep." << endl + << "#" << endl + << "if ($build.meta_operation != 'info' && \\" << endl + << " $build.meta_operation != 'configure' && \\" << endl + << " $build.meta_operation != 'disfigure')" << endl + << " run " << argv0 << " sync --hook=1 " << + "--config \"$out_root\" " << + "-d '" << prj.string () << "'" << endl; + + os.close (); + } + catch (const io_error& e) + { + fail << "unable to write " << f << ": " << e; + } + } + } + else + { + if (e) + rm (f); + } + } } void cmd_sync (const common_options& co, const dir_path& prj, const shared_ptr<configuration>& c, + bool implicit, bool fetch, bool yes) { cmd_sync (co, prj, c, + implicit, fetch, yes, nullopt /* upgrade */, @@ -216,7 +282,7 @@ namespace bdep } int - cmd_sync (const cmd_sync_options& o, cli::scanner& args) + cmd_sync (cmd_sync_options&& o, cli::scanner& args) { tracer trace ("sync"); @@ -242,6 +308,29 @@ namespace bdep strings dep_pkgs; for (; args.more (); dep_pkgs.push_back (args.next ())) ; + // --hook + // + if (o.hook_specified ()) + { + if (o.hook () != 1) + fail << "unsupported build system hook protocol" << + info << "project requires re-initialization"; + + o.implicit (true); // Implies --implicit. + } + + // --implicit + // + if (o.implicit ()) + { + if (const char* n = (o.upgrade () ? "--upgrade|-u" : + o.patch () ? "--patch|-p" : nullptr)) + fail << "--implicit specified with " << n; + + if (!dep_pkgs.empty ()) + fail << "--implicit specified with dependency package"; + } + // We could be running from a package directory (or the user specified one // with -d) that has not been init'ed in this configuration. Unless we are // upgrading specific dependencies, we want to diagnose that since such a @@ -306,6 +395,7 @@ namespace bdep cmd_sync (o, prj, c, + false /* implicit */, !fetch, o.recursive () || o.immediate () ? o.yes () : true, !o.patch (), // Upgrade by default unless patch requested. @@ -322,6 +412,7 @@ namespace bdep cmd_sync (o, prj, c, + false /* implicit */, !fetch, o.yes (), o.upgrade (), @@ -331,9 +422,64 @@ namespace bdep } else { - // The first form: sync of project packages. + // The first form: sync of project packages (potentially implicit). + // + + // Update the BDEP_SYNCED_CONFIGS environment variable. // - cmd_sync (o, prj, c, !fetch, true /* yes */); + // Note that it covers both depth and breadth (i.e., we don't restore + // the previous value before returning). The idea here is for commands + // like update or test would perform an implicit sync which will then + // be "noticed" by the build system hook. This should be both faster + // (no need to spawn multiple bdep processes) and simpler (no need to + // worry about who has the database open, etc). + // + { + const char n[] = "BDEP_SYNCED_CONFIGS"; + + string v; + const string& p (c->path.string ()); + + if (const char* e = getenv (n)) + { + v = e; + + // Check if this configuration is already (being) synchronized. + // + for (size_t b (0), e (0); + (e = v.find ('"', e)) != string::npos; // Skip leading ' '. + ++e) // Skip trailing '"'. + { + size_t n (next_word (v, b, e, '"')); + + // Both paths are normilized so we can just compare them as + // strings. + // + if (path::traits::compare (v.c_str () + b, n, + p.c_str (), p.size ()) == 0) + { + if (o.implicit ()) + return 0; // Ignore. + else + fail << "explicit re-synchronization of " << c->path; + } + } + + v += ' '; + } + + v += '"'; + v += p; + v += '"'; + +#ifndef _WIN32 + setenv (n, v.c_str (), 1 /* overwrite */); +#else + _putenv ((string (n) + '=' + v).c_str ()); +#endif + } + + cmd_sync (o, prj, c, o.implicit (), !fetch, true /* yes */); } } diff --git a/bdep/sync.hxx b/bdep/sync.hxx index b397c10..8edd4b4 100644 --- a/bdep/sync.hxx +++ b/bdep/sync.hxx @@ -20,11 +20,12 @@ namespace bdep cmd_sync (const common_options&, const dir_path& prj, const shared_ptr<configuration>&, + bool implicit, bool fetch = true, bool yes = true); int - cmd_sync (const cmd_sync_options&, cli::scanner& args); + cmd_sync (cmd_sync_options&&, cli::scanner& args); } #endif // BDEP_SYNC_HXX diff --git a/bdep/utility.cxx b/bdep/utility.cxx index a902273..e048a5a 100644 --- a/bdep/utility.cxx +++ b/bdep/utility.cxx @@ -26,6 +26,7 @@ namespace bdep const path repositories_file ("repositories.manifest"); const path configurations_file ("configurations.manifest"); + const char* argv0; dir_path exec_dir; bool diff --git a/bdep/utility.hxx b/bdep/utility.hxx index 31cd0cb..09e0e45 100644 --- a/bdep/utility.hxx +++ b/bdep/utility.hxx @@ -40,6 +40,10 @@ namespace bdep using butl::ucase; using butl::lcase; using butl::casecmp; + + using butl::trim; + using butl::next_word; + using butl::reverse_iterate; using butl::exception_guard; @@ -66,6 +70,10 @@ namespace bdep extern const path repositories_file; // repositories.manifest extern const path configurations_file; // configurations.manifest + // Process path (argv[0]). + // + extern const char* argv0; + // Directory extracted from argv[0] (i.e., this process' recall directory) // or empty if there is none. Can be used as a search fallback. // |