diff options
-rw-r--r-- | libbutl/default-options.cxx | 74 | ||||
-rw-r--r-- | libbutl/default-options.mxx | 25 | ||||
-rw-r--r-- | libbutl/default-options.txx | 184 | ||||
-rw-r--r-- | tests/b-info/driver.cxx | 7 | ||||
-rw-r--r-- | tests/default-options/driver.cxx | 38 | ||||
-rw-r--r-- | tests/default-options/testscript | 158 |
6 files changed, 382 insertions, 104 deletions
diff --git a/libbutl/default-options.cxx b/libbutl/default-options.cxx new file mode 100644 index 0000000..69b9c42 --- /dev/null +++ b/libbutl/default-options.cxx @@ -0,0 +1,74 @@ +// file : libbutl/default-options.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules_ts +#include <libbutl/default-options.mxx> +#endif + +#include <cassert> + +#ifndef __cpp_lib_modules_ts +#include <vector> +#endif + +// Other includes. + +#ifdef __cpp_modules_ts +module butl.default_options; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules_ts +import std.core; +#endif +import butl.path; +import butl.optional; +import butl.small_vector; +#endif + +#endif + +using namespace std; + +namespace butl +{ + optional<dir_path> + default_options_start (const optional<dir_path>& home, + const vector<dir_path>& dirs) + { + if (home) + assert (home->absolute () && home->normalized ()); + + if (dirs.empty ()) + return nullopt; + + // Use the first directory as a start. + // + auto i (dirs.begin ()); + dir_path d (*i); + + // Try to find a common prefix for each subsequent directory. + // + for (++i; i != dirs.end (); ++i) + { + bool p (false); + + for (; + !(d.root () || (home && d == *home)); + d = d.directory ()) + { + if (i->sub (d)) + { + p = true; + break; + } + } + + if (!p) + return nullopt; + } + + return d; + } +} diff --git a/libbutl/default-options.mxx b/libbutl/default-options.mxx index 62c7f92..403df47 100644 --- a/libbutl/default-options.mxx +++ b/libbutl/default-options.mxx @@ -7,7 +7,10 @@ #endif #ifndef __cpp_lib_modules_ts +#include <vector> + #include <utility> // move(), forward(), make_pair() +#include <algorithm> // reverse() #include <system_error> #endif @@ -42,7 +45,7 @@ LIBBUTL_MODEXPORT namespace butl struct default_options_files { small_vector<path, 2> files; - optional<dir_path> start_dir; + optional<dir_path> start; }; template <typename O> @@ -62,7 +65,11 @@ LIBBUTL_MODEXPORT namespace butl // Pass each default options file path to the specified function prior to // load (can be used for tracing, etc). The function signature is: // - // void (const path&, bool remote) + // void (const path&, bool remote, bool overwrite) + // + // Note that the function may be called for the same file twice if it was + // later discovered that it is in fact remote. In the second call the + // overwrite flag will be true. // // Throw `pair<path, system_error>` on the underlying OS error with the // first half referring the filesystem entry the error relates to and pass @@ -78,13 +85,16 @@ LIBBUTL_MODEXPORT namespace butl // .build2/local/ subdirectories of each directory. For sys_dir they are // looked for in the directory itself (e.g., /etc/build2/). // - // Note that all the directories should be absolute and normalized. + // Note that the search is stopped at the directory containing a file with + // --no-default-options. + // + // Also note that all the directories should be absolute and normalized. // // The presence of the .git filesystem entry causes the options files in // this directory and any of its subdirectories to be considered remote // (note that in the current implementation this is the case even for files // from the .build2/local/ subdirectory since the mere location is not a - // sufficient ground to definititevly conclude that the file is not remote; + // sufficient ground to definitively conclude that the file is not remote; // to be sure we would need to query the VCS or some such). // template <typename O, typename S, typename U, typename F> @@ -118,6 +128,13 @@ LIBBUTL_MODEXPORT namespace butl template <typename O, typename F> O merge_default_options (const default_options<O>&, const O&, F&&); + + // Find a common start (parent) directory stopping at home or root + // (excluding). + // + LIBBUTL_SYMEXPORT optional<dir_path> + default_options_start (const optional<dir_path>& home_dir, + const std::vector<dir_path>&); } #include <libbutl/default-options.ixx> diff --git a/libbutl/default-options.txx b/libbutl/default-options.txx index 996ee33..276fa63 100644 --- a/libbutl/default-options.txx +++ b/libbutl/default-options.txx @@ -16,8 +16,9 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. } // Search for and parse the options files in the specified directory and - // its local/ subdirectory, if exists, and append the options to the - // resulting list. + // its local/ subdirectory, if exists, in the reverse order and append the + // options to the resulting list. Return false if --no-default-options is + // encountered. // // Note that we check for the local/ subdirectory even if we don't think it // belongs to the remote directory; the user may move things around or it @@ -29,18 +30,20 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. // remote (see load_default_options() for details). // template <typename O, typename S, typename U, typename F> - void + bool load_default_options_files (const dir_path& d, bool remote, const small_vector<path, 2>& fs, F&& fn, - default_options<O>& r) + default_options<O>& def_ops) { - auto load = [&fs, &fn, &r] (const dir_path& d, bool remote) + bool r (true); + + auto load = [&fs, &fn, &def_ops, &r] (const dir_path& d, bool remote) { using namespace std; - for (const path& f: fs) + for (const path& f: reverse_iterate (fs)) { path p (d / f); @@ -48,7 +51,7 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. { if (file_exists (p)) // Follows symlinks. { - fn (p, remote); + fn (p, remote, false /* overwrite */); S s (p.string ()); @@ -61,9 +64,12 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. O o; o.parse (s, U::fail, U::fail); - r.push_back (default_options_entry<O> {move (p), - move (o), - remote}); + if (o.no_default_options ()) + r = false; + + def_ops.push_back (default_options_entry<O> {move (p), + move (o), + remote}); } } catch (std::system_error& e) @@ -73,55 +79,18 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. } }; - load (d, remote); - dir_path ld (d / dir_path ("local")); if (options_dir_exists (ld)) load (ld, remote); - } - - // Search for and parse the options files in the specified and outer - // directories until root/home directory (excluding) and append the options - // to the resulting list. Return true if the directory is "remote" (i.e., - // belongs to a VCS repository). - // - template <typename O, typename S, typename U, typename F> - bool - load_default_options_files (const dir_path& start_dir, - const optional<dir_path>& home_dir, - const small_vector<path, 2>& fs, - F&& fn, - default_options<O>& r) - { - if (start_dir.root () || (home_dir && start_dir == *home_dir)) - return false; - bool remote (load_default_options_files<O, S, U> (start_dir.directory (), - home_dir, - fs, - std::forward<F> (fn), - r)); - if (!remote) - try - { - remote = git_repository (start_dir); - } - catch (std::system_error& e) - { - throw std::make_pair (start_dir / ".git", std::move (e)); - } - - dir_path d (start_dir / dir_path (".build2")); - - if (options_dir_exists (d)) - load_default_options_files<O, S, U> (d, - remote, - fs, - std::forward<F> (fn), - r); + // Don't load options from .build2/ if --no-default-options is encountered + // in .build2/local/. + // + if (r) + load (d, remote); - return remote; + return r; } template <typename O, typename S, typename U, typename F> @@ -133,42 +102,109 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. { default_options<O> r; - if (sys_dir) + // Search for and parse the options files in the specified and outer + // directories until root/home directory and in the system directory, + // stopping if --no-default-options is encountered and reversing the + // resulting options entry list in the end. + // + bool load (true); + + if (ofs.start) { - assert (sys_dir->absolute () && sys_dir->normalized ()); + assert (ofs.start->absolute () && ofs.start->normalized ()); - if (options_dir_exists (*sys_dir)) - load_default_options_files<O, S, U> (*sys_dir, - false /* remote */, - ofs.files, - std::forward<F> (fn), - r); + for (dir_path d (*ofs.start); + !(d.root () || (home_dir && d == *home_dir)); + d = d.directory ()) + { + bool remote; + + try + { + remote = git_repository (d); + } + catch (std::system_error& e) + { + throw std::make_pair (d / ".git", std::move (e)); + } + + // If the directory is remote, then mark all the previously collected + // local entries (that belong to its subdirectories) as remote too. + // + // @@ Note that currently the local/ subdirectory of a remote + // directory is considered remote (see above for details). When + // changing that, skip entries from directories with the `local` + // name. + // + if (remote) + { + // We could optimize this, iterating in the reverse order until the + // fist remote entry. However, let's preserve the function calls + // order for entries being overwritten. + // + for (default_options_entry<O>& e: r) + { + if (!e.remote) + { + e.remote = true; + + fn (e.file, true /* remote */, true /* overwrite */); + } + } + } + + // If --no-default-options is encountered, then stop the files search + // but continue the directory traversal until the remote directory is + // encountered and, if that's the case, mark the already collected + // local entries as remote. + // + if (load) + { + dir_path od (d / dir_path (".build2")); + + if (options_dir_exists (od)) + load = load_default_options_files<O, S, U> (od, + remote, + ofs.files, + std::forward<F> (fn), + r); + } + + if (!load && remote) + break; + } } if (home_dir) { assert (home_dir->absolute () && home_dir->normalized ()); - dir_path d (*home_dir / dir_path (".build2")); + if (load) + { + dir_path d (*home_dir / dir_path (".build2")); + + if (options_dir_exists (d)) + load = load_default_options_files<O, S, U> (d, + false /* remote */, + ofs.files, + std::forward<F> (fn), + r); + } + } - if (options_dir_exists (d)) - load_default_options_files<O, S, U> (d, + if (sys_dir) + { + assert (sys_dir->absolute () && sys_dir->normalized ()); + + if (load && options_dir_exists (*sys_dir)) + load_default_options_files<O, S, U> (*sys_dir, false /* remote */, ofs.files, std::forward<F> (fn), r); } - if (ofs.start_dir) - { - assert (ofs.start_dir->absolute () && ofs.start_dir->normalized ()); - - load_default_options_files<O, S, U> (*ofs.start_dir, - home_dir, - ofs.files, - std::forward<F> (fn), - r); - } + std::reverse (r.begin (), r.end ()); return r; } diff --git a/tests/b-info/driver.cxx b/tests/b-info/driver.cxx index e22a018..fbd4763 100644 --- a/tests/b-info/driver.cxx +++ b/tests/b-info/driver.cxx @@ -63,7 +63,12 @@ try cout.exceptions (ios::failbit | ios::badbit); - b_project_info pi (b_info (project, 1 /* verb */, {}, b)); + b_project_info pi (b_info (project, + 1 /* verb */, + {} /* cmd_callback */, + b, + {} /* search_fallback */, + {"--no-default-options"})); cout << "project: " << pi.project << endl << "version: " << pi.version << endl diff --git a/tests/default-options/driver.cxx b/tests/default-options/driver.cxx index 535df31..106f70f 100644 --- a/tests/default-options/driver.cxx +++ b/tests/default-options/driver.cxx @@ -45,7 +45,9 @@ using namespace butl; // Default options file name. Can be specified multiple times. // // -d -// Directory to start the default options files search from. +// Directory to start the default options files search from. Can be +// specified multiple times, in which case a common start (parent) +// directory is deduced. // // -s // System directory. @@ -59,6 +61,9 @@ using namespace butl; // // <file>,<space-separated-options>,<remote> // +// -t +// Trace the default options files search to STDERR. +// int main (int argc, const char* argv[]) { @@ -94,6 +99,9 @@ main (int argc, const char* argv[]) bool r (false); while (optional<string> o = s.next ()) { + if (*o == "--no-default-options") + no_default_options_ = true; + push_back (move (*o)); r = true; } @@ -105,6 +113,15 @@ main (int argc, const char* argv[]) { insert (end (), o.begin (), o.end ()); } + + bool + no_default_options () const noexcept + { + return no_default_options_; + } + + private: + bool no_default_options_ = false; }; // Parse and validate the arguments. @@ -112,8 +129,10 @@ main (int argc, const char* argv[]) default_options_files fs; optional<dir_path> sys_dir; optional<dir_path> home_dir; + vector<dir_path> dirs; options cmd_ops; bool print_entries (false); + bool trace (false); for (int i (1); i != argc; ++i) { @@ -127,7 +146,7 @@ main (int argc, const char* argv[]) else if (op == "-d") { assert (++i != argc); - fs.start_dir = dir_path (argv[i]); + dirs.emplace_back (argv[i]); } else if (op == "-s") { @@ -143,10 +162,18 @@ main (int argc, const char* argv[]) { print_entries = true; } + else if (op == "-t") + { + trace = true; + } else cmd_ops.push_back (argv[i]); } + // Deduce a common start directory. + // + fs.start = default_options_start (home_dir, dirs); + // Load and print the default options. // default_options<options> def_ops ( @@ -154,7 +181,12 @@ main (int argc, const char* argv[]) sys_dir, home_dir, fs, - [] (const path&, bool) {})); + [trace] (const path& f, bool remote, bool overwrite) + { + if (trace) + cerr << (overwrite ? "overwriting " : "loading ") + << (remote ? "remote " : "local ") << f << endl; + })); if (print_entries) { diff --git a/tests/default-options/testscript b/tests/default-options/testscript index c0c3816..89164d2 100644 --- a/tests/default-options/testscript +++ b/tests/default-options/testscript @@ -9,43 +9,46 @@ exit end -sys_dir = $canonicalize([dir_path] $~/build2) -+mkdir -p $sys_dir/local +: basic +: +{ + sys_dir = $canonicalize([dir_path] $~/build2) + +mkdir -p $sys_dir/local -+echo 'sys-foo' >=$sys_dir/foo -+echo 'sys-bar' >=$sys_dir/bar -+echo 'sys-local-foo' >=$sys_dir/local/foo -+echo 'sys-local-bar' >=$sys_dir/local/bar + +echo 'sys-foo' >=$sys_dir/foo + +echo 'sys-bar' >=$sys_dir/bar + +echo 'sys-local-foo' >=$sys_dir/local/foo + +echo 'sys-local-bar' >=$sys_dir/local/bar -home_dir = $canonicalize([dir_path] $~/home) -+mkdir -p $home_dir/.build2/local/ + home_dir = $canonicalize([dir_path] $~/home) + +mkdir -p $home_dir/.build2/local/ -+echo 'home-foo' >=$home_dir/.build2/foo -+echo 'home-bar' >=$home_dir/.build2/bar -+echo 'home-local-foo' >=$home_dir/.build2/local/foo -+echo 'home-local-bar' >=$home_dir/.build2/local/bar + +echo 'home-foo' >=$home_dir/.build2/foo + +echo 'home-bar' >=$home_dir/.build2/bar + +echo 'home-local-foo' >=$home_dir/.build2/local/foo + +echo 'home-local-bar' >=$home_dir/.build2/local/bar -: in-home -: -{ - d = $home_dir/work/.build2 - +mkdir -p $d/local/ + work_dir = $home_dir/work + +mkdir -p $work_dir/.build2/local/ + + d = $work_dir/.build2 +echo 'work-foo' >=$d/foo +echo 'work-bar' >=$d/bar +echo 'work-local-foo' >=$d/local/foo +echo 'work-local-bar' >=$d/local/bar - d = $home_dir/work/project/.build2 + d = $work_dir/project/.build2 +mkdir -p $d/local/ - +touch $home_dir/work/project/.git + + +touch $work_dir/project/.git +echo 'project-foo' >=$d/foo +echo 'project-bar' >=$d/bar +echo 'project-local-foo' >=$d/local/foo +echo 'project-local-bar' >=$d/local/bar - d = $home_dir/work/project/package/.build2 + d = $work_dir/project/package/.build2 +mkdir -p $d/local/ +echo 'package-foo' >=$d/foo @@ -53,11 +56,13 @@ home_dir = $canonicalize([dir_path] $~/home) +echo 'package-local-foo' >=$d/local/foo +echo 'package-local-bar' >=$d/local/bar - start_dir = $canonicalize([dir_path] $home_dir/work/project/package) + +echo '--no-default-options' >=$d/local/baz + + start_dir = $canonicalize([dir_path] $work_dir/project/package) : entries : - $* -e -f foo -f bar -d $start_dir -s $sys_dir -h $home_dir cmd-foo cmd-bar >>/~%EOO%d + $* -e -t -f foo -f bar -d $start_dir -s $sys_dir -h $home_dir >>/~%EOO%d 2>>/~%EOE%d %\.+/build2/foo,sys-foo,false% %\.+/build2/bar,sys-bar,false% %\.+/build2/local/foo,sys-local-foo,false% @@ -79,6 +84,31 @@ home_dir = $canonicalize([dir_path] $~/home) %\.+/home/work/project/package/.build2/local/foo,package-local-foo,true% %\.+/home/work/project/package/.build2/local/bar,package-local-bar,true% EOO + %loading local \.+/home/work/project/package/.build2/local/bar% + %loading local \.+/home/work/project/package/.build2/local/foo% + %loading local \.+/home/work/project/package/.build2/bar% + %loading local \.+/home/work/project/package/.build2/foo% + %overwriting remote \.+/home/work/project/package/.build2/local/bar% + %overwriting remote \.+/home/work/project/package/.build2/local/foo% + %overwriting remote \.+/home/work/project/package/.build2/bar% + %overwriting remote \.+/home/work/project/package/.build2/foo% + %loading remote \.+/home/work/project/.build2/local/bar% + %loading remote \.+/home/work/project/.build2/local/foo% + %loading remote \.+/home/work/project/.build2/bar% + %loading remote \.+/home/work/project/.build2/foo% + %loading local \.+/home/work/.build2/local/bar% + %loading local \.+/home/work/.build2/local/foo% + %loading local \.+/home/work/.build2/bar% + %loading local \.+/home/work/.build2/foo% + %loading local \.+/home/.build2/local/bar% + %loading local \.+/home/.build2/local/foo% + %loading local \.+/home/.build2/bar% + %loading local \.+/home/.build2/foo% + %loading local \.+/build2/local/bar% + %loading local \.+/build2/local/foo% + %loading local \.+/build2/bar% + %loading local \.+/build2/foo% + EOE : merged : @@ -106,4 +136,88 @@ home_dir = $canonicalize([dir_path] $~/home) cmd-foo cmd-bar EOO + + : no-default-options + : + $* -e -t -f foo -f baz -f bar -d $start_dir -s $sys_dir -h $home_dir >>/~%EOO%d 2>>/~%EOE%d + %\.+/home/work/project/package/.build2/local/foo,package-local-foo,true% + %\.+/home/work/project/package/.build2/local/baz,--no-default-options,true% + %\.+/home/work/project/package/.build2/local/bar,package-local-bar,true% + EOO + %loading local \.+/home/work/project/package/.build2/local/bar% + %loading local \.+/home/work/project/package/.build2/local/baz% + %loading local \.+/home/work/project/package/.build2/local/foo% + %overwriting remote \.+/home/work/project/package/.build2/local/bar% + %overwriting remote \.+/home/work/project/package/.build2/local/baz% + %overwriting remote \.+/home/work/project/package/.build2/local/foo% + EOE +} + +: common-start +: +{ + home_dir = $canonicalize([dir_path] $~/home) + + work_dir = $home_dir/work + +mkdir -p $work_dir/.build2 + + cfg1 = $canonicalize([dir_path] $work_dir/cfg1) + cfg2 = $canonicalize([dir_path] $work_dir/cfg2) + cfg3 = $canonicalize([dir_path] $cfg2/cfg3) + + +mkdir -p $work_dir/.build2 $cfg1/.build2 $cfg2/.build2 $cfg3/.build2 + + +echo 'work' >=$work_dir/.build2/cfg + +echo 'cfg1' >=$cfg1/.build2/cfg + +echo 'cfg2' >=$cfg2/.build2/cfg + +echo 'cfg3' >=$cfg3/.build2/cfg + + : exists + : + { + : single + : + $* -f cfg -d $cfg3 -h $home_dir >>EOO + work + cfg2 + cfg3 + EOO + + : same + : + $* -f cfg -d $cfg1 -d $cfg1 -h $home_dir >>EOO + work + cfg1 + EOO + + : adjacent + : + $* -f cfg -d $cfg1 -d $cfg2 -h $home_dir >>EOO + work + EOO + + : nested + : + $* -f cfg -d $cfg2 -d $cfg3 -h $home_dir >>EOO + work + cfg2 + EOO + } + + : not-exists + : + { + : home-reached + : + $* -f cfg -d $cfg1 -d $cfg2 -h $work_dir >>EOO + work + EOO + + : root-reached + : + if ($cxx.target.class != 'windows') + { + $* -f cfg -d $cfg1 -d /non-existent-directory/cfg2 + } + } } |