aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libbutl/default-options.cxx74
-rw-r--r--libbutl/default-options.mxx25
-rw-r--r--libbutl/default-options.txx184
-rw-r--r--tests/b-info/driver.cxx7
-rw-r--r--tests/default-options/driver.cxx38
-rw-r--r--tests/default-options/testscript158
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
+ }
+ }
}