aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2018-04-23 14:25:58 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2018-04-26 09:38:09 +0200
commitf98262e37f608330fcfce799dcacc6fbacac8f8a (patch)
tree080ef26d80ee6419ef3a2f27434d75136f502cb2
parentc414abe13450e2b4e204f6368ba83c8916de1ebd (diff)
Implement forwarded configurations and backlinking
-rw-r--r--build2/algorithm.cxx334
-rw-r--r--build2/algorithm.hxx31
-rw-r--r--build2/algorithm.ixx14
-rw-r--r--build2/b.cli122
-rw-r--r--build2/b.cxx79
-rw-r--r--build2/cc/compile-rule.cxx5
-rw-r--r--build2/cc/link-rule.cxx42
-rw-r--r--build2/cc/pkgconfig.cxx2
-rw-r--r--build2/cc/windows-rpath.cxx9
-rw-r--r--build2/config/init.cxx2
-rw-r--r--build2/config/operation.cxx360
-rw-r--r--build2/context.cxx48
-rw-r--r--build2/context.hxx2
-rw-r--r--build2/file.cxx164
-rw-r--r--build2/file.hxx30
-rw-r--r--build2/filesystem.hxx12
-rw-r--r--build2/scope.hxx6
-rw-r--r--build2/spec.hxx1
-rw-r--r--build2/target.hxx14
-rw-r--r--build2/test/rule.cxx32
-rw-r--r--build2/variable.hxx15
21 files changed, 1047 insertions, 277 deletions
diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx
index fc7ef88..a3f4d0a 100644
--- a/build2/algorithm.cxx
+++ b/build2/algorithm.cxx
@@ -205,21 +205,19 @@ namespace build2
}
target_lock
- add_adhoc_member (action a, target& t, const target_type& tt, const char* s)
+ add_adhoc_member (action a,
+ target& t,
+ const target_type& tt,
+ const dir_path& dir,
+ const dir_path& out,
+ const string& n)
{
- string n (t.name);
- if (s != nullptr)
- {
- n += '.';
- n += s;
- }
-
const_ptr<target>* mp (&t.member);
for (; *mp != nullptr && !(*mp)->is_a (tt); mp = &(*mp)->member) ;
const target& m (*mp != nullptr // Might already be there.
? **mp
- : search (t, tt, t.dir, t.out, n));
+ : search (t, tt, dir, out, n));
target_lock l (lock (a, m));
assert (l.target != nullptr); // Someone messing with ad hoc members?
@@ -227,7 +225,7 @@ namespace build2
if (*mp == nullptr)
*mp = l.target;
else
- assert ((*mp)->name == n); // Basic sanity check.
+ assert ((*mp)->dir == dir && (*mp)->name == n); // Basic sanity check.
return l;
};
@@ -896,6 +894,286 @@ namespace build2
return ts;
}
+ void
+ update_backlink (const file& f, const path& l, bool changed)
+ {
+ const path& p (f.path ());
+ dir_path d (l.directory ());
+
+ // At low verbosity levels we print the command if the target changed or
+ // the link does not exist (we also treat errors as "not exist" and let
+ // the link update code below handle it).
+ //
+ // Note that in the changed case we print it even if the link is not
+ // actually updated to signal to the user that the updated out target is
+ // now available in src.
+ //
+ if (verb <= 2)
+ {
+ if (changed || !butl::entry_exists (l,
+ false /* follow_symlinks */,
+ true /* ignore_errors */))
+ {
+ if (verb >= 2)
+ text << "ln -s " << p << ' ' << l;
+ else
+ text << "ln " << f << " -> " << d;
+ }
+ }
+
+ // What if there is no such subdirectory in src (some like to stash their
+ // executables in bin/ or some such). The easiest is probably just to
+ // create it even though we won't be cleaning it up.
+ //
+ if (!exists (d))
+ mkdir_p (d, 2 /* verbosity */);
+
+ update_backlink (p, l);
+ }
+
+ void
+ update_backlink (const path& p, const path& l, bool changed)
+ {
+ // As above but with a slightly different diagnostics.
+
+ dir_path d (l.directory ());
+
+ if (verb <= 2)
+ {
+ if (changed || !butl::entry_exists (l,
+ false /* follow_symlinks */,
+ true /* ignore_errors */))
+ {
+ if (verb >= 2)
+ text << "ln -s " << p.string () << ' ' << l.string ();
+ else
+ text << "ln " << p.string () << " -> " << d;
+ }
+ }
+
+ if (!exists (d))
+ mkdir_p (d, 2 /* verbosity */);
+
+ update_backlink (p, l);
+ }
+
+ void
+ update_backlink (const path& p, const path& l)
+ {
+ bool d (l.to_directory ());
+
+ bool sym (true);
+ auto print = [&sym, &p, &l] ()
+ {
+ if (verb >= 3)
+ text << (sym ? "ln -sf" : "ln -f")
+ << ' ' << p.string ()
+ << ' ' << l.string (); // 'ln foo/ bar/' means different thing.
+ };
+
+ try
+ {
+ try_rmfile (l); // Normally will be there.
+
+ // Skip (ad hoc) targets that don't exist.
+ //
+ if (!(d ? dir_exists (p) : file_exists (p)))
+ return;
+
+ try
+ {
+ mksymlink (p, l, d);
+ }
+ catch (const system_error& e)
+ {
+ // If symlinks not supported, try a hardlink.
+ //
+ // Note that we are not guaranteed that the system_error exception is
+ // of the generic category.
+ //
+ int c (e.code ().value ());
+ if (!(e.code ().category () == generic_category () &&
+ (c == ENOSYS || // Not implemented.
+ c == EPERM))) // Not supported by the filesystem(s).
+ throw;
+
+ sym = false;
+ mkhardlink (p, l, d);
+ }
+ }
+ catch (const system_error& e)
+ {
+ print ();
+ fail << "unable to create " << (sym ? "symlink " : " hardlink ") << l
+ << ": " << e;
+ }
+
+ print ();
+ }
+
+ void
+ clean_backlink (const path& l, uint16_t verbosity)
+ {
+ // Assuming this works for directories (which can only be symlinked). See
+ // also try_rmfile() calls in ~backlink() and update_backlink().
+ //
+ rmfile (l, verbosity);
+ }
+
+ // If target/link path are syntactically to a directory, then the backlink
+ // is assumed to be to a directory, otherwise -- to a file.
+ //
+ struct backlink: auto_rm<path>
+ {
+ using path_type = build2::path;
+
+ reference_wrapper<const path_type> target;
+
+ backlink (const path_type& t, path_type&& l)
+ : auto_rm<path_type> (move (l)), target (t)
+ {
+ assert (t.to_directory () == path.to_directory ());
+ }
+
+ ~backlink ()
+ {
+ if (active)
+ {
+ // Assuming this works for both file and directory (sym)links.
+ //
+ try_rmfile (path, true /* ignore_errors */);
+ active = false;
+ }
+ }
+
+ backlink (backlink&&) = default;
+ backlink& operator= (backlink&&) = default;
+ };
+
+ // Normally (i.e., on sane platforms that don't have things like PDBs, etc)
+ // there will be just one backlink so optimize for that.
+ //
+ using backlinks = small_vector<backlink, 1>;
+
+ static bool
+ backlink_test (action a, target& t)
+ {
+ // Note: the order of these checks is from the least to most expensive.
+
+ // Only for plain update/clean.
+ //
+ if (a.outer () || (a != perform_update_id && a != perform_clean_id))
+ return false;
+
+ // Only file-based targets in the out tree can be backlinked.
+ //
+ if (!t.out.empty () || !t.is_a<file> ())
+ return false;
+
+ // An in-src configuration cannot be forwarded.
+ //
+ const scope& bs (t.base_scope ());
+ if (bs.src_path () == bs.out_path ())
+ return false;
+
+ // Only for forwarded configurations.
+ //
+ if (!cast_false<bool> (bs.root_scope ()->vars[var_forwarded]))
+ return false;
+
+ lookup l (t[var_backlink]);
+
+ // If not found, check for some defaults in the global scope (this does
+ // not happen automatically since target type/pattern-specific lookup
+ // stops at the project boundary).
+ //
+ if (!l.defined ())
+ l = global_scope->find (*var_backlink, t.key ());
+
+ return cast_false<bool> (l);
+ }
+
+ static backlinks
+ backlink_collect (target& t)
+ {
+ const scope& s (t.base_scope ());
+
+ backlinks bls;
+ auto add = [&bls, &s] (const path& p)
+ {
+ bls.emplace_back (p, s.src_path () / p.leaf (s.out_path ()));
+ };
+
+ // First the target itself.
+ //
+ add (t.as<file> ().path ());
+
+ // Then ad hoc group file/fsdir members, if any.
+ //
+ for (const target* m (t.member); m != nullptr; m = m->member)
+ {
+ if (const file* f = m->is_a<file> ())
+ {
+ const path& p (f->path ());
+
+ if (!p.empty ()) // The "trust me, it's somewhere" case.
+ add (p);
+ }
+ else if (const fsdir* d = m->is_a<fsdir> ())
+ add (d->dir);
+ }
+
+ return bls;
+ }
+
+ static inline backlinks
+ backlink_update_pre (target& t)
+ {
+ return backlink_collect (t);
+ }
+
+ static void
+ backlink_update_post (target& t, target_state ts, backlinks& bls)
+ {
+ if (ts == target_state::failed)
+ return; // Let auto rm clean things up.
+
+ // Make backlinks.
+ //
+ for (auto b (bls.begin ()), i (b); i != bls.end (); ++i)
+ {
+ const backlink& bl (*i);
+
+ if (i == b)
+ update_backlink (t.as<file> (), bl.path, ts == target_state::changed);
+ else
+ update_backlink (bl.target, bl.path);
+ }
+
+ // Cancel removal.
+ //
+ for (backlink& bl: bls)
+ bl.cancel ();
+ }
+
+ static void
+ backlink_clean_pre (target& t)
+ {
+ backlinks bls (backlink_collect (t));
+
+ for (auto b (bls.begin ()), i (b); i != bls.end (); ++i)
+ {
+ backlink& bl (*i);
+ const path& l (bl.path);
+
+ bl.cancel ();
+
+ // Printing anything at level 1 will probably just add more noise.
+ //
+ clean_backlink (l, i == b ? 2 : 3 /* verbosity */);
+ }
+ }
+
static target_state
execute_impl (action a, target& t)
{
@@ -904,7 +1182,41 @@ namespace build2
assert (s.task_count.load (memory_order_consume) == target::count_busy ()
&& s.state == target_state::unknown);
- target_state ts (execute_recipe (a, t, s.recipe));
+ target_state ts;
+ try
+ {
+ // Handle target backlinking to forwarded configurations.
+ //
+ // Note that this function will never be called if the recipe is noop
+ // which is ok since such targets are probably not interesting for
+ // backlinking.
+ //
+ backlinks bls;
+ bool bl (backlink_test (a, t));
+
+ if (bl)
+ {
+ if (a == perform_update_id)
+ bls = backlink_update_pre (t);
+ else
+ backlink_clean_pre (t);
+ }
+
+ ts = execute_recipe (a, t, s.recipe);
+
+ if (bl)
+ {
+ if (a == perform_update_id)
+ backlink_update_post (t, ts, bls);
+ }
+ }
+ catch (const failed&)
+ {
+ // If we could not backlink the target, then the best way to signal the
+ // failure seems to be to mark the target as failed.
+ //
+ ts = s.state = target_state::failed;
+ }
// Decrement the target count (see set_recipe() for details).
//
diff --git a/build2/algorithm.hxx b/build2/algorithm.hxx
index 62c312f..e736626 100644
--- a/build2/algorithm.hxx
+++ b/build2/algorithm.hxx
@@ -181,13 +181,23 @@ namespace build2
lock (action, const target&);
// Add an ad hoc member to the end of the chain assuming that an already
- // existing member of this target type is the same.
+ // existing member of this target type is the same. Return the locked member
+ // target.
//
+ target_lock
+ add_adhoc_member (action,
+ target&,
+ const target_type&,
+ const dir_path& dir,
+ const dir_path& out,
+ const string& name);
+
// If the suffix is specified, it is added (as an extension) to the member's
- // target name. Return the locked member target.
+ // target name.
//
target_lock
- add_adhoc_member (action, target&,
+ add_adhoc_member (action,
+ target&,
const target_type&,
const char* suffix = nullptr);
@@ -632,6 +642,21 @@ namespace build2
{
return clean_extra (a, f, {extra});
}
+
+ // Update/clean a backlink issuing appropriate diagnostics at appropriate
+ // levels depending on first/changed.
+ //
+ void
+ update_backlink (const file&, const path& link, bool changed);
+
+ void
+ update_backlink (const path& target, const path& link, bool changed);
+
+ void
+ update_backlink (const path& target, const path& link);
+
+ void
+ clean_backlink (const path& link, uint16_t verbosity);
}
#include <build2/algorithm.ixx>
diff --git a/build2/algorithm.ixx b/build2/algorithm.ixx
index 7572548..12dc2da 100644
--- a/build2/algorithm.ixx
+++ b/build2/algorithm.ixx
@@ -240,6 +240,20 @@ namespace build2
return r;
}
+ inline target_lock
+ add_adhoc_member (action a, target& t, const target_type& tt, const char* s)
+ {
+ string n (t.name);
+
+ if (s != nullptr)
+ {
+ n += '.';
+ n += s;
+ }
+
+ return add_adhoc_member (a, t, tt, t.dir, t.out, n);
+ }
+
const rule_match*
match_impl (action, target&, const rule* skip, bool try_match = false);
diff --git a/build2/b.cli b/build2/b.cli
index 8d2b48e..d054d05 100644
--- a/build2/b.cli
+++ b/build2/b.cli
@@ -52,17 +52,17 @@ namespace build2
meta-operations may take additional <parameters>. For example:
\
- b # perform(update(./))
- b foo/ # perform(update(foo/))
- b foo/ bar/ # perform(update(foo/ bar/))
- b update # perform(update(./))
- b 'clean(../)' # perform(clean(../))
- b perform # perform(update(./))
- b configure # configure(?(./))
- b 'configure(../)' # configure(?(../))
- b clean update # perform(clean(./) update(./))
- b configure update # configure(?(./)) perform(update(./))
- b 'create(conf/, cxx)' # create(?(conf/), cxx)
+ $ b # perform(update(./))
+ $ b foo/ # perform(update(foo/))
+ $ b foo/ bar/ # perform(update(foo/ bar/))
+ $ b update # perform(update(./))
+ $ b 'clean(../)' # perform(clean(../))
+ $ b perform # perform(update(./))
+ $ b configure # configure(?(./))
+ $ b 'configure(../)' # configure(?(../))
+ $ b clean update # perform(clean(./) update(./))
+ $ b configure update # configure(?(./)) perform(update(./))
+ $ b 'create(conf/, cxx)' # create(?(conf/), cxx)
\
Notice the question mark used to show the (imaginary) default operation
@@ -74,17 +74,17 @@ namespace build2
Compare:
\
- b 'clean(foo/ bar/)' 'update(foo/ bar/)'
- b '{clean update}(foo/ bar/)'
+ $ b 'clean(foo/ bar/)' 'update(foo/ bar/)'
+ $ b '{clean update}(foo/ bar/)'
\
Some more useful buildspec examples:
\
- b '{clean update}(...)' # rebuild
- b '{clean update clean}(...)' # make sure builds
- b '{clean test clean}(...)' # make sure passes tests
- b '{clean disfigure}(...)' # similar to distclean
+ $ b '{clean update}(...)' # rebuild
+ $ b '{clean update clean}(...)' # make sure builds
+ $ b '{clean test clean}(...)' # make sure passes tests
+ $ b '{clean disfigure}(...)' # similar to distclean
\
In POSIX shells parenthesis are special characters and must be quoted
@@ -94,10 +94,10 @@ namespace build2
meta-operation, for example:
\
- b clean: foo/ bar/ # clean(foo/ bar/)
- b configure: src/@out/ # configure(src/@out/)
- b create: conf/, cxx # create(conf/, cxx)
- b configure: config.cxx=g++ src/ # configure(src/) config.cxx=g++
+ $ b clean: foo/ bar/ # clean(foo/ bar/)
+ $ b configure: src/@out/ # configure(src/@out/)
+ $ b create: conf/, cxx # create(conf/, cxx)
+ $ b configure: config.cxx=g++ src/ # configure(src/) config.cxx=g++
\
To activate the shortcut syntax the first buildspec argument must start
@@ -114,9 +114,9 @@ namespace build2
For example, assuming \cb{foo/} is the source directory of a project:
\
- b foo/ # out_base=src_base=foo/
- b foo-out/ # out_base=foo-out/ src_base=foo/
- b foo-out/exe{foo} # out_base=foo-out/ src_base=foo/
+ $ b foo/ # out_base=src_base=foo/
+ $ b foo-out/ # out_base=foo-out/ src_base=foo/
+ $ b foo-out/exe{foo} # out_base=foo-out/ src_base=foo/
\
An exception to this requirement is a directory target in which case,
@@ -143,23 +143,39 @@ namespace build2
Continuing with the previous example:
\
- b foo/@foo-out/exe{foo} # out_base=foo-out/ src_base=foo/
+ $ b foo/@foo-out/exe{foo} # out_base=foo-out/ src_base=foo/
\
Normally, you would need to specify \cb{src_base} explicitly only once,
during configuration. For example, a typical usage would be:
\
- b 'configure(foo/@foo-out/)' # src_base is saved
- b foo-out/ # no need to specify src_base
- b 'clean(foo-out/exe{foo})' # no need to specify src_base
+ $ b configure: foo/@foo-out/ # src_base is saved
+ $ b foo-out/ # no need to specify src_base
+ $ b clean: foo-out/exe{foo} # no need to specify src_base
+ \
+
+ Besides in and out of source builds, \cb{build2} also supports
+ configuring a project's source directory as \i{forwarded} to an out of
+ source build. With such a forwarded configuration in place, if we run the
+ build system driver from the source directory, it will automatically
+ build in the output directory and \i{backlink} (using symlinks or another
+ suitable mechanism) certain \"interesting\" targets (executables,
+ documentation, etc) to the source directory for easy access. Continuing
+ with the previous example:
+
+ \
+ $ b configure: foo/@foo-out/,forward # foo/ forwarded to foo-out/
+ $ cd foo/
+ $ b # build in foo-out/
+ $ ./foo # symlink to foo-out/foo
\
The ability to specify \cb{build2} variables as part of the command line
is normally used to pass configuration values, for example:
\
- b config.cxx=clang++ config.cxx.coptions=-O3
+ $ b config.cxx=clang++ config.cxx.coptions=-O3
\
Similar to buildspec, POSIX shells often inhibit path auto-completion on
@@ -168,7 +184,7 @@ namespace build2
arguments, for example:
\
- b config.import.libhello = ../libhello/
+ $ b config.import.libhello = ../libhello/
\
The build system has the following built-in and pre-defined
@@ -187,9 +203,18 @@ namespace build2
\cb{config} module. For example:
\
- b config.cxx=clang++ config.cxx.coptions=-O3 \
- config.install.root=/usr/local config.install.root.sudo=sudo \
- configure
+ $ b configure \
+ config.cxx=clang++ \
+ config.cxx.coptions=-O3 \
+ config.install.root=/usr/local \
+ config.install.root.sudo=sudo
+ \
+
+ Use the \cb{forward} parameter to instead configure a source
+ directory as forwarded to an out of source build. For example:
+
+ \
+ $ b configure: src/@out/,forward
\
|
@@ -198,7 +223,16 @@ namespace build2
Disfigure all operations supported by a project and remove the
project's \cb{build/config.build} file. Implemented by the
- \cb{config} module.|
+ \cb{config} module.
+
+ Use the \cb{forward} parameter to instead disfigure forwarding of a
+ source directory to an out of source build. For example:
+
+ \
+ $ b disfigure: src/,forward
+ \
+
+ |
\li|\cb{create}
@@ -225,9 +259,9 @@ namespace build2
own directory, for example:
\
- b 'configure(libhello/@libhello-clang/)' config.cxx=clang++
- b 'configure(hello/@hello-clang/)' config.cxx=clang++ \
- config.import.libhello=libhello-clang/
+ $ b configure: libhello/@libhello-clang/ config.cxx=clang++
+ $ b configure: hello/@hello-clang/ config.cxx=clang++ \
+ config.import.libhello=libhello-clang/
\
The two drawbacks, as mentioned above, are the need to explicitly
@@ -238,9 +272,9 @@ namespace build2
drawbacks using the configuration project:
\
- b 'create(clang/, cxx)' config.cxx=clang++ # Creates clang/.
- b 'configure(libhello/@clang/libhello/)'
- b 'configure(hello/@clang/hello/)'
+ $ b create: clang/,cxx config.cxx=clang++ # Creates clang/.
+ $ b configure: libhello/@clang/libhello/
+ $ b configure: hello/@clang/hello/
\
The targets passed to the \cb{create} meta-operation must be
@@ -286,7 +320,7 @@ namespace build2
identified by its root directory target. For example:
\
- b info: libfoo/ libbar/
+ $ b info: libfoo/ libbar/
\
||
@@ -343,9 +377,9 @@ namespace build2
For example:
\
- b config.install.root=c:\projects\install
- b \"config.install.root='c:\Program Files (x86)\test\'\"
- b 'config.cxx.poptions=-DFOO_STR=\"foo\"'
+ $ b config.install.root=c:\projects\install
+ $ b \"config.install.root='c:\Program Files (x86)\test\'\"
+ $ b 'config.cxx.poptions=-DFOO_STR=\"foo\"'
\
"
}
diff --git a/build2/b.cxx b/build2/b.cxx
index e78eebd..e427237 100644
--- a/build2/b.cxx
+++ b/build2/b.cxx
@@ -665,11 +665,11 @@ main (int argc, char* argv[])
{
name& tn (ts.name);
- // First figure out the out_base of this target. The logic
- // is as follows: if a directory was specified in any form,
- // then that's the out_base. Otherwise, we check if the name
- // value has a directory prefix. This has a good balance of
- // control and the expected result in most cases.
+ // First figure out the out_base of this target. The logic is as
+ // follows: if a directory was specified in any form, then that's
+ // the out_base. Otherwise, we check if the name value has a
+ // directory prefix. This has a good balance of control and the
+ // expected result in most cases.
//
dir_path out_base (tn.dir);
if (out_base.empty ())
@@ -711,7 +711,10 @@ main (int argc, char* argv[])
dir_path src_root;
dir_path out_root;
- dir_path& src_base (ts.src_base); // Update it in buildspec.
+ // Update these in buildspec.
+ //
+ bool& forwarded (ts.forwarded);
+ dir_path& src_base (ts.src_base);
if (!src_base.empty ())
{
@@ -770,24 +773,40 @@ main (int argc, char* argv[])
// If no src_base was explicitly specified, search for out_root.
//
auto p (find_out_root (out_base));
- out_root = move (p.first);
- // If not found (i.e., we have no idea where the roots are), then
- // this can only mean a simple project. Which in turn means there
- // should be a buildfile in out_base.
- //
- if (out_root.empty ())
+ if (p.second) // Also src_root.
{
- if (find_buildfile (out_base).empty ())
+ src_root = move (p.first);
+
+ // Handle a forwarded configuration. Note that if we've changed
+ // out_root then we also have to remap out_base.
+ //
+ out_root = bootstrap_fwd (src_root);
+ if (src_root != out_root)
{
- fail << "no buildfile in " << out_base <<
- info << "consider explicitly specifying its src_base";
+ out_base = out_root / out_base.leaf (src_root);
+ forwarded = true;
}
+ }
+ else
+ {
+ out_root = move (p.first);
- src_root = src_base = out_root = out_base;
+ // If not found (i.e., we have no idea where the roots are),
+ // then this can only mean a simple project. Which in turn means
+ // there should be a buildfile in out_base.
+ //
+ if (out_root.empty ())
+ {
+ if (find_buildfile (out_base).empty ())
+ {
+ fail << "no buildfile in " << out_base <<
+ info << "consider explicitly specifying its src_base";
+ }
+
+ src_root = src_base = out_root = out_base;
+ }
}
- else if (p.second)
- src_root = out_root;
}
// Now we know out_root and, if it was explicitly specified or the
@@ -825,7 +844,7 @@ main (int argc, char* argv[])
else if (src_root != p)
{
fail << "bootstrapped src_root " << p << " does not match "
- << "specified " << src_root;
+ << (forwarded ? "forwarded " : "specified ") << src_root;
}
}
else
@@ -851,6 +870,13 @@ main (int argc, char* argv[])
else if (src_root.empty ())
src_root = rs.src_path ();
+ // Note that we only "upgrade" the forwarded value since the same
+ // project root can be arrived at via multiple paths (think command
+ // line and import).
+ //
+ if (forwarded)
+ rs.assign (var_forwarded) = true;
+
// At this stage we should have both roots and out_base figured
// out. If src_base is still undetermined, calculate it.
//
@@ -1087,13 +1113,13 @@ main (int argc, char* argv[])
if (verb >= 5)
{
trace << "bootstrapped " << tn << ':';
- trace << " out_base: " << out_base;
- trace << " src_base: " << src_base;
- trace << " out_root: " << out_root;
- trace << " src_root: " << src_root;
-
+ trace << " out_base: " << out_base;
+ trace << " src_base: " << src_base;
+ trace << " out_root: " << out_root;
+ trace << " src_root: " << src_root;
+ trace << " forwarded: " << (forwarded ? "true" : "false");
if (auto l = rs.vars[var_amalgamation])
- trace << " amalgamat: " << cast<dir_path> (l);
+ trace << " amalgamation: " << cast<dir_path> (l);
}
path bf (find_buildfile (src_base));
@@ -1220,6 +1246,9 @@ main (int argc, char* argv[])
d.normalize (true); // Actualize since came from command line.
+ if (ts.forwarded)
+ d = rs.out_path () / d.leaf (rs.src_path ()); // Remap.
+
// Figure out if this target is in the src tree.
//
dir_path out (ts.out_base != ts.src_base && d.sub (ts.src_base)
diff --git a/build2/cc/compile-rule.cxx b/build2/cc/compile-rule.cxx
index d6fea9f..ebcf7b8 100644
--- a/build2/cc/compile-rule.cxx
+++ b/build2/cc/compile-rule.cxx
@@ -3853,7 +3853,10 @@ namespace build2
2); /* verbosity */
}
- ps = &load_project (as->rw () /* lock */, pd, pd);
+ ps = &load_project (as->rw () /* lock */,
+ pd,
+ pd,
+ false /* forwarded */);
}
}
diff --git a/build2/cc/link-rule.cxx b/build2/cc/link-rule.cxx
index 4379362..4d98e5f 100644
--- a/build2/cc/link-rule.cxx
+++ b/build2/cc/link-rule.cxx
@@ -477,6 +477,34 @@ namespace build2
match_recipe (pc, group_recipe); // Set recipe and unlock.
}
+
+ // Add the Windows rpath emulating assembly directory as fsdir{}.
+ //
+ // Currently this is used in the backlinking logic and in the future
+ // could also be used for clean (though there we may want to clean
+ // old assemblies).
+ //
+ if (ot == otype::e && tclass == "windows")
+ {
+ // Note that here we cannot determine whether we will actually
+ // need one (for_install, library timestamps are not available at
+ // this point to call windows_rpath_timestamp()). So we may add
+ // the ad hoc target but actually not produce the assembly. So
+ // whomever relies on this must check if the directory actually
+ // exists (windows_rpath_assembly() does take care to clean it up
+ // if not used).
+ //
+ target_lock dir (
+ add_adhoc_member (
+ a,
+ t,
+ fsdir::static_type,
+ path_cast<dir_path> (t.path () + ".dlls"),
+ t.out,
+ string ()));
+
+ match_recipe (dir, group_recipe); // Set recipe and unlock.
+ }
}
}
@@ -1258,7 +1286,7 @@ namespace build2
// If targeting Windows, take care of the manifest.
//
path manifest; // Manifest itself (msvc) or compiled object file.
- timestamp rpath_timestamp (timestamp_nonexistent); // DLLs timestamp.
+ timestamp rpath_timestamp = timestamp_nonexistent; // DLLs timestamp.
if (lt.executable () && tclass == "windows")
{
@@ -1269,10 +1297,7 @@ namespace build2
if (!for_install)
rpath_timestamp = windows_rpath_timestamp (t, bs, a, li);
- pair<path, bool> p (
- windows_manifest (t,
- rpath_timestamp != timestamp_nonexistent));
-
+ auto p (windows_manifest (t, rpath_timestamp != timestamp_nonexistent));
path& mf (p.first);
bool mf_cf (p.second); // Changed flag (timestamp resolution).
@@ -2032,10 +2057,9 @@ namespace build2
if (tclass == "windows")
{
- // For Windows generate rpath-emulating assembly (unless updating for
- // install).
+ // For Windows generate (or clean up) rpath-emulating assembly.
//
- if (lt.executable () && !for_install)
+ if (lt.executable ())
windows_rpath_assembly (t, bs, a, li,
cast<string> (rs[x_target_cpu]),
rpath_timestamp,
@@ -2052,7 +2076,7 @@ namespace build2
try
{
- if (file_exists (l, false)) // The -f part.
+ if (file_exists (l, false /* follow_symlinks */)) // The -f part.
try_rmfile (l);
mksymlink (f, l);
diff --git a/build2/cc/pkgconfig.cxx b/build2/cc/pkgconfig.cxx
index 9d52db9..378be49 100644
--- a/build2/cc/pkgconfig.cxx
+++ b/build2/cc/pkgconfig.cxx
@@ -1005,7 +1005,7 @@ namespace build2
}
{
- mt.vars.assign (*x_symexport) = (se == "true");
+ mt.vars.assign (x_symexport) = (se == "true");
}
tl.second.unlock ();
diff --git a/build2/cc/windows-rpath.cxx b/build2/cc/windows-rpath.cxx
index 8854542..594e3e0 100644
--- a/build2/cc/windows-rpath.cxx
+++ b/build2/cc/windows-rpath.cxx
@@ -215,8 +215,7 @@ namespace build2
const char*
windows_manifest_arch (const string& tcpu); // windows-manifest.cxx
- // The ts argument should be the the DLLs timestamp returned by
- // *_timestamp().
+ // The ts argument should be the DLLs timestamp returned by *_timestamp().
//
// The scratch argument should be true if the DLL set has changed and we
// need to regenerate everything from scratch. Otherwise, we try to avoid
@@ -248,10 +247,10 @@ namespace build2
// signalling that there aren't any DLLs but the assembly manifest
// file exists. This, however, can only happen if we somehow managed
// to transition from the "have DLLs" state to "no DLLs" without going
- // through the "from scratch" update. And this shouldn't happen
- // (famous last words before a core dump).
+ // through the "from scratch" update. Actually this can happen when
+ // switching to update-for-install.
//
- if (ts <= file_mtime (am))
+ if (ts != timestamp_nonexistent && ts <= file_mtime (am))
return;
}
diff --git a/build2/config/init.cxx b/build2/config/init.cxx
index 8bbc07e..c88f252 100644
--- a/build2/config/init.cxx
+++ b/build2/config/init.cxx
@@ -103,7 +103,7 @@ namespace build2
{
// Assume missing version is 0.
//
- auto p (extract_variable (rs, f, c_v));
+ auto p (extract_variable (f, c_v));
uint64_t v (p.second ? cast<uint64_t> (p.first) : 0);
if (v != module::version)
diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx
index 1e1ec94..1bd5d4d 100644
--- a/build2/config/operation.cxx
+++ b/build2/config/operation.cxx
@@ -53,6 +53,32 @@ namespace build2
}
}
+ static void
+ save_out_root (const dir_path& out_root, const dir_path& src_root)
+ {
+ path f (src_root / out_root_file);
+
+ if (verb)
+ text << (verb >= 2 ? "cat >" : "save ") << f;
+
+ try
+ {
+ ofdstream ofs (f);
+
+ ofs << "# Created automatically by the config module." << endl
+ << "#" << endl
+ << "out_root = ";
+ to_stream (ofs, name (out_root), true, '@'); // Quote.
+ ofs << endl;
+
+ ofs.close ();
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write " << f << ": " << e;
+ }
+ }
+
using project_set = set<const scope*>; // Use pointers to get comparison.
static void
@@ -338,6 +364,41 @@ namespace build2
}
}
+ static void
+ configure_forward (const scope& root, project_set& projects)
+ {
+ tracer trace ("configure_forward");
+
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ if (!projects.insert (&root).second)
+ {
+ l5 ([&]{trace << "skipping already configured " << src_root;});
+ return;
+ }
+
+ mkdir (src_root / bootstrap_dir, 2); // Make sure exists.
+ save_out_root (out_root, src_root);
+
+ // Configure subprojects. Since we don't load buildfiles if configuring
+ // a forward, we do it for all known subprojects.
+ //
+ if (auto l = root.vars[var_subprojects])
+ {
+ for (auto p: cast<subprojects> (l))
+ {
+ dir_path out_nroot (out_root / p.second);
+ const scope& nroot (scopes.find (out_nroot));
+ assert (nroot.out_path () == out_nroot);
+
+ configure_forward (nroot, projects);
+ }
+ }
+ }
+
+ operation_id (*pre) (const values&, meta_operation_id, const location&);
+
static operation_id
configure_operation_pre (const values&, operation_id o)
{
@@ -347,6 +408,84 @@ namespace build2
return o;
}
+ // The (vague) idea is that in the future we may turn this into to some
+ // sort of key-value sequence (similar to the config initializer idea),
+ // for example:
+ //
+ // configure(out/@src/, forward foo bar@123)
+ //
+ // Though using commas instead spaces and '=' instead of '@' would have
+ // been nicer.
+ //
+ static bool
+ forward (const values& params,
+ const char* mo = nullptr,
+ const location& l = location ())
+ {
+ if (params.size () == 1)
+ {
+ const names& ns (cast<names> (params[0]));
+
+ if (ns.size () == 1 && ns[0].simple () && ns[0].value == "forward")
+ return true;
+ else if (!ns.empty ())
+ fail (l) << "unexpected parameter '" << ns << "' for "
+ << "meta-operation " << mo;
+ }
+ else if (!params.empty ())
+ fail (l) << "unexpected parameters for meta-operation " << mo;
+
+ return false;
+ }
+
+ static void
+ configure_pre (const values& params, const location& l)
+ {
+ forward (params, "configure", l); // Validate.
+ }
+
+ static void
+ configure_load (const values& params,
+ scope& root,
+ const path& buildfile,
+ const dir_path& out_base,
+ const dir_path& src_base,
+ const location& l)
+ {
+ if (forward (params))
+ {
+ // We don't need to load the buildfiles in order to configure
+ // forwarding but in order to configure subprojects we have to
+ // bootstrap them (similar to disfigure).
+ //
+ create_bootstrap_inner (root);
+
+ if (root.out_path () == root.src_path ())
+ fail (l) << "forwarding to source directory " << root.src_path ();
+ }
+ else
+ load (params, root, buildfile, out_base, src_base, l); // Normal load.
+ }
+
+ static void
+ configure_search (const values& params,
+ const scope& root,
+ const scope& base,
+ const target_key& tk,
+ const location& l,
+ action_targets& ts)
+ {
+ if (forward (params))
+ {
+ // For forwarding we only collect the projects (again, similar to
+ // disfigure).
+ //
+ ts.push_back (&root);
+ }
+ else
+ search (params, root, base, tk, l, ts); // Normal search.
+ }
+
static void
configure_match (const values&, action, action_targets&, uint16_t, bool)
{
@@ -354,24 +493,40 @@ namespace build2
}
static void
- configure_execute (const values&, action a, action_targets& ts,
- uint16_t, bool)
+ configure_execute (const values& params,
+ action a,
+ action_targets& ts,
+ uint16_t,
+ bool)
{
- // Match rules to configure every operation supported by each
- // project. Note that we are not calling operation_pre/post()
- // callbacks here since the meta operation is configure and we
- // know what we are doing.
- //
+ bool fwd (forward (params));
+
project_set projects;
- // Note that we cannot do this in parallel. We cannot parallelize the
- // outer loop because we should match for a single action at a time.
- // And we cannot swap the loops because the list of operations is
- // target-specific. However, inside match(), things can proceed in
- // parallel.
- //
for (const action_target& at: ts)
{
+ if (fwd)
+ {
+ // Forward configuration.
+ //
+ const scope& root (*static_cast<const scope*> (at.target));
+ configure_forward (root, projects);
+ continue;
+ }
+
+ // Normal configuration.
+ //
+ // Match rules to configure every operation supported by each project.
+ // Note that we are not calling operation_pre/post() callbacks here
+ // since the meta operation is configure and we know what we are
+ // doing.
+ //
+ // Note that we cannot do this in parallel. We cannot parallelize the
+ // outer loop because we should match for a single action at a time.
+ // And we cannot swap the loops because the list of operations is
+ // target-specific. However, inside match(), things can proceed in
+ // parallel.
+ //
const target& t (at.as_target ());
const scope* rs (t.base_scope ().root_scope ());
@@ -407,10 +562,10 @@ namespace build2
"configuring",
"configured",
"is configured",
- nullptr, // meta-operation pre
+ &configure_pre, // meta-operation pre
&configure_operation_pre,
- &load, // normal load
- &search, // normal search
+ &configure_load, // normal load unless configuring forward
+ &configure_search, // normal search unless configuring forward
&configure_match,
&configure_execute,
nullptr, // operation post
@@ -420,51 +575,6 @@ namespace build2
// disfigure
//
- static operation_id
- disfigure_operation_pre (const values&, operation_id o)
- {
- // Don't translate default to update. In our case unspecified
- // means disfigure everything.
- //
- return o;
- }
-
- static void
- disfigure_load (const values&,
- scope& root,
- const path& bf,
- const dir_path&,
- const dir_path&,
- const location&)
- {
- tracer trace ("disfigure_load");
- l6 ([&]{trace << "skipping " << bf;});
-
- // Since we don't load buildfiles during disfigure but still want to
- // disfigure all the subprojects (see disfigure_project() below), we
- // bootstrap all the known subprojects.
- //
- create_bootstrap_inner (root);
- }
-
- static void
- disfigure_search (const values&,
- const scope& root,
- const scope&,
- const target_key&,
- const location&,
- action_targets& ts)
- {
- tracer trace ("disfigure_search");
- l6 ([&]{trace << "collecting " << root.out_path ();});
- ts.push_back (&root);
- }
-
- static void
- disfigure_match (const values&, action, action_targets&, uint16_t, bool)
- {
- }
-
static bool
disfigure_project (action a, const scope& root, project_set& projects)
{
@@ -476,10 +586,10 @@ namespace build2
if (!projects.insert (&root).second)
{
l5 ([&]{trace << "skipping already disfigured " << out_root;});
- return true;
+ return false;
}
- bool m (false); // Keep track of whether we actually did anything.
+ bool r (false); // Keep track of whether we actually did anything.
// Disfigure subprojects. Since we don't load buildfiles during
// disfigure, we do it for all known subprojects.
@@ -493,7 +603,7 @@ namespace build2
const scope& nroot (scopes.find (out_nroot));
assert (nroot.out_path () == out_nroot); // See disfigure_load().
- m = disfigure_project (a, nroot, projects) || m;
+ r = disfigure_project (a, nroot, projects) || r;
// We use mkdir_p() to create the out_root of a subproject
// which means there could be empty parent directories left
@@ -510,7 +620,7 @@ namespace build2
if (s == rmdir_status::not_empty)
break; // No use trying do remove parent ones.
- m = (s == rmdir_status::success) || m;
+ r = (s == rmdir_status::success) || r;
}
}
}
@@ -523,16 +633,16 @@ namespace build2
{
l5 ([&]{trace << "completely disfiguring " << out_root;});
- m = rmfile (out_root / config_file) || m;
+ r = rmfile (out_root / config_file) || r;
if (out_root != src_root)
{
- m = rmfile (out_root / src_root_file, 2) || m;
+ r = rmfile (out_root / src_root_file, 2) || r;
// Clean up the directories.
//
- m = rmdir (out_root / bootstrap_dir, 2) || m;
- m = rmdir (out_root / build_dir, 2) || m;
+ r = rmdir (out_root / bootstrap_dir, 2) || r;
+ r = rmdir (out_root / build_dir, 2) || r;
switch (rmdir (out_root))
{
@@ -550,7 +660,7 @@ namespace build2
break;
}
case rmdir_status::success:
- m = true;
+ r = true;
default:
break;
}
@@ -560,15 +670,105 @@ namespace build2
{
}
- return m;
+ return r;
+ }
+
+ static bool
+ disfigure_forward (const scope& root, project_set& projects)
+ {
+ // Pretty similar logic to disfigure_project().
+ //
+ tracer trace ("disfigure_forward");
+
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ if (!projects.insert (&root).second)
+ {
+ l5 ([&]{trace << "skipping already disfigured " << src_root;});
+ return false;
+ }
+
+ bool r (false);
+
+ if (auto l = root.vars[var_subprojects])
+ {
+ for (auto p: cast<subprojects> (l))
+ {
+ dir_path out_nroot (out_root / p.second);
+ const scope& nroot (scopes.find (out_nroot));
+ assert (nroot.out_path () == out_nroot);
+
+ r = disfigure_forward (nroot, projects) || r;
+ }
+ }
+
+ // Remove the out-root.build file and try to remove the bootstrap/
+ // directory if it is empty.
+ //
+ r = rmfile (src_root / out_root_file) || r;
+ r = rmdir (src_root / bootstrap_dir, 2) || r;
+
+ return r;
+ }
+
+ static void
+ disfigure_pre (const values& params, const location& l)
+ {
+ forward (params, "disfigure", l); // Validate.
+ }
+
+ static operation_id
+ disfigure_operation_pre (const values&, operation_id o)
+ {
+ // Don't translate default to update. In our case unspecified
+ // means disfigure everything.
+ //
+ return o;
+ }
+
+ static void
+ disfigure_load (const values&,
+ scope& root,
+ const path&,
+ const dir_path&,
+ const dir_path&,
+ const location&)
+ {
+ // Since we don't load buildfiles during disfigure but still want to
+ // disfigure all the subprojects (see disfigure_project() below), we
+ // bootstrap all the known subprojects.
+ //
+ create_bootstrap_inner (root);
+ }
+
+ static void
+ disfigure_search (const values&,
+ const scope& root,
+ const scope&,
+ const target_key&,
+ const location&,
+ action_targets& ts)
+ {
+ ts.push_back (&root);
}
static void
- disfigure_execute (const values&, action a, action_targets& ts,
- uint16_t diag, bool)
+ disfigure_match (const values&, action, action_targets&, uint16_t, bool)
+ {
+ }
+
+ static void
+ disfigure_execute (const values& params,
+ action a,
+ action_targets& ts,
+ uint16_t diag,
+ bool)
{
tracer trace ("disfigure_execute");
+ bool fwd (forward (params));
+
project_set projects;
// Note: doing everything in the load phase (disfigure_project () does
@@ -578,14 +778,16 @@ namespace build2
{
const scope& root (*static_cast<const scope*> (at.target));
- if (!disfigure_project (a, root, projects))
+ if (!(fwd
+ ? disfigure_forward ( root, projects)
+ : disfigure_project (a, root, projects)))
{
- // Create a dir{$out_root/} target to signify the project's
- // root in diagnostics. Not very clean but seems harmless.
+ // Create a dir{$out_root/} target to signify the project's root in
+ // diagnostics. Not very clean but seems harmless.
//
target& t (
targets.insert (dir::static_type,
- root.out_path (),
+ fwd ? root.src_path () : root.out_path (),
dir_path (), // Out tree.
"",
nullopt,
@@ -605,7 +807,7 @@ namespace build2
"disfiguring",
"disfigured",
"is disfigured",
- nullptr, // meta-operation pre
+ disfigure_pre, // meta-operation pre
&disfigure_operation_pre,
&disfigure_load,
&disfigure_search,
@@ -629,7 +831,7 @@ namespace build2
// way main() does it.
//
scope& gs (*scope::global_);
- scope& rs (load_project (gs, d, d, false /* load */));
+ scope& rs (load_project (gs, d, d, false /* fwd */, false /* load */));
module& m (*rs.modules.lookup<module> (module::name));
// Save all the global config.import.* variables.
diff --git a/build2/context.cxx b/build2/context.cxx
index b669ae6..2ebe163 100644
--- a/build2/context.cxx
+++ b/build2/context.cxx
@@ -326,6 +326,7 @@ namespace build2
const variable* var_out_root;
const variable* var_src_base;
const variable* var_out_base;
+ const variable* var_forwarded;
const variable* var_project;
const variable* var_amalgamation;
@@ -338,6 +339,7 @@ namespace build2
const variable* var_import_target;
const variable* var_clean;
+ const variable* var_backlink;
const char var_extension[10] = "extension";
@@ -417,10 +419,6 @@ namespace build2
// may reference these things.
//
- // Target extension.
- //
- vp.insert<string> (var_extension, variable_visibility::target);
-
gs.assign<dir_path> ("build.work") = work;
gs.assign<dir_path> ("build.home") = home;
@@ -673,28 +671,38 @@ namespace build2
vp.insert_pattern<bool> (
"**.configured", false, variable_visibility::project);
- var_src_root = &vp.insert<dir_path> ("src_root");
- var_out_root = &vp.insert<dir_path> ("out_root");
- var_src_base = &vp.insert<dir_path> ("src_base");
- var_out_base = &vp.insert<dir_path> ("out_base");
-
- // Note that subprojects is not typed since the value requires
- // pre-processing (see file.cxx).
- //
{
- auto pv (variable_visibility::project);
+ auto v_p (variable_visibility::project);
+ auto v_t (variable_visibility::target);
- var_project = &vp.insert<string> ("project", pv);
- var_amalgamation = &vp.insert<dir_path> ("amalgamation", pv);
- var_subprojects = &vp.insert ("subprojects", pv);
- var_version = &vp.insert<string> ("version", pv);
+ var_src_root = &vp.insert<dir_path> ("src_root");
+ var_out_root = &vp.insert<dir_path> ("out_root");
+ var_src_base = &vp.insert<dir_path> ("src_base");
+ var_out_base = &vp.insert<dir_path> ("out_base");
- var_project_url = &vp.insert<string> ("project.url", pv);
- var_project_summary = &vp.insert<string> ("project.summary", pv);
+ var_forwarded = &vp.insert<bool> ("forwarded", v_p);
+
+ // Note that subprojects is not typed since the value requires
+ // pre-processing (see file.cxx).
+ //
+ var_project = &vp.insert<string> ("project", v_p);
+ var_amalgamation = &vp.insert<dir_path> ("amalgamation", v_p);
+ var_subprojects = &vp.insert ("subprojects", v_p);
+ var_version = &vp.insert<string> ("version", v_p);
+
+ var_project_url = &vp.insert<string> ("project.url", v_p);
+ var_project_summary = &vp.insert<string> ("project.summary", v_p);
var_import_target = &vp.insert<name> ("import.target");
- var_clean = &vp.insert<bool> ("clean", variable_visibility::target);
+ var_clean = &vp.insert<bool> ("clean", v_t);
+ var_backlink = &vp.insert<bool> ("backlink", v_t);
+ vp.insert<string> (var_extension, v_t);
+
+ // Backlink executables and (generated) documentation by default.
+ //
+ gs.target_vars[exe::static_type]["*"].assign (var_backlink) = true;
+ gs.target_vars[doc::static_type]["*"].assign (var_backlink) = true;
}
// Register builtin rules.
diff --git a/build2/context.hxx b/build2/context.hxx
index 846dee2..378c6c0 100644
--- a/build2/context.hxx
+++ b/build2/context.hxx
@@ -264,6 +264,7 @@ namespace build2
extern const variable* var_out_root;
extern const variable* var_src_base;
extern const variable* var_out_base;
+ extern const variable* var_forwarded;
extern const variable* var_project;
extern const variable* var_amalgamation;
@@ -276,6 +277,7 @@ namespace build2
extern const variable* var_import_target; // import.target
extern const variable* var_clean; // [bool] target visibility
+ extern const variable* var_backlink; // [bool] target visibility
extern const char var_extension[10]; // "extension"
diff --git a/build2/file.cxx b/build2/file.cxx
index b0d202c..ae24b77 100644
--- a/build2/file.cxx
+++ b/build2/file.cxx
@@ -30,6 +30,7 @@ namespace build2
const path root_file (build_dir / "root.build");
const path bootstrap_file (build_dir / "bootstrap.build");
const path src_root_file (bootstrap_dir / "src-root.build");
+ const path out_root_file (bootstrap_dir / "out-root.build");
const path export_file (build_dir / "export.build");
// While strictly speaking it belongs in, say, config/module.cxx, the static
@@ -177,8 +178,7 @@ namespace build2
rs.operations.insert (clean_id, op_clean);
}
- // If this is already a root scope, verify that things are
- // consistent.
+ // If this is already a root scope, verify that things are consistent.
//
{
value& v (rs.assign (var_out_root));
@@ -309,28 +309,30 @@ namespace build2
return pair<scope&, scope*> (base, rs);
}
- scope&
- load_project (scope& lock,
- const dir_path& out_root, const dir_path& src_root,
- bool load)
+ dir_path
+ bootstrap_fwd (const dir_path& src_root)
{
- auto i (create_root (lock, out_root, src_root));
- scope& rs (i->second);
+ // We cannot just source the buildfile since there is no scope to do
+ // this on yet.
+ //
+ path bf (src_root / out_root_file);
- if (!bootstrapped (rs))
+ if (!exists (bf))
+ return src_root;
+
+ auto p (extract_variable (bf, *var_out_root));
+
+ if (!p.second)
+ fail << "variable out_root expected as first line in " << bf;
+
+ try
{
- bootstrap_out (rs);
- setup_root (rs);
- bootstrap_src (rs);
+ return convert<dir_path> (move (p.first));
}
-
- if (load)
+ catch (const invalid_argument& e)
{
- load_root_pre (rs);
- setup_base (i, out_root, src_root); // Setup as base.
+ fail << "invalid out_root value in " << bf << ": " << e << endf;
}
-
- return rs;
}
void
@@ -349,12 +351,8 @@ namespace build2
source_once (root, root, bf);
}
- // Extract the specified variable value from a buildfile. It is expected to
- // be the first non-comment line and not to rely on any variable expansion
- // other than those from the global scope or any variable overrides.
- //
pair<value, bool>
- extract_variable (scope& s, const path& bf, const variable& var)
+ extract_variable (const path& bf, const variable& var)
{
try
{
@@ -373,7 +371,7 @@ namespace build2
}
parser p;
- temp_scope tmp (s.global ());
+ temp_scope tmp (global_scope->rw ());
p.parse_variable (lex, tmp, var, tt);
value* v (tmp.vars.find_to_modify (var).first);
@@ -387,15 +385,12 @@ namespace build2
{
fail << "unable to read buildfile " << bf << ": " << e << endf;
}
-
- // Never reached.
}
// Extract the project name from bootstrap.build.
//
static string
- find_project_name (scope& s,
- const dir_path& out_root,
+ find_project_name (const dir_path& out_root,
const dir_path& fallback_src_root,
bool* src_hint = nullptr)
{
@@ -418,10 +413,10 @@ namespace build2
src_root = &fallback_src_root;
else
{
- auto p (extract_variable (s, f, *var_src_root));
+ auto p (extract_variable (f, *var_src_root));
if (!p.second)
- fail << "variable 'src_root' expected as first line in " << f;
+ fail << "variable src_root expected as first line in " << f;
src_root_v = move (p.first);
src_root = &cast<dir_path> (src_root_v);
@@ -433,10 +428,10 @@ namespace build2
string name;
{
path f (*src_root / bootstrap_file);
- auto p (extract_variable (s, f, *var_project));
+ auto p (extract_variable (f, *var_project));
if (!p.second)
- fail << "variable '" << var_project->name << "' expected "
+ fail << "variable " << var_project->name << " expected "
<< "as a first line in " << f;
name = cast<string> (move (p.first));
@@ -451,8 +446,7 @@ namespace build2
// is a subproject, then enter it into the map, handling the duplicates.
//
static void
- find_subprojects (scope& s,
- subprojects& sps,
+ find_subprojects (subprojects& sps,
const dir_path& d,
const dir_path& root,
bool out)
@@ -498,7 +492,7 @@ namespace build2
// Load its name. Note that here we don't use fallback src_root
// since this function is used to scan both out_root and src_root.
//
- string name (find_project_name (s, sd, dir_path (), &src));
+ string name (find_project_name (sd, dir_path (), &src));
// If the name is empty, then is is an unnamed project. While the
// 'project' variable stays empty, here we come up with a surrogate
@@ -657,13 +651,13 @@ namespace build2
if (exists (out_root))
{
l5 ([&]{trace << "looking for subprojects in " << out_root;});
- find_subprojects (root, sps, out_root, out_root, true);
+ find_subprojects (sps, out_root, out_root, true);
}
if (out_root != src_root)
{
l5 ([&]{trace << "looking for subprojects in " << src_root;});
- find_subprojects (root, sps, src_root, src_root, false);
+ find_subprojects (sps, src_root, src_root, false);
}
if (!sps.empty ()) // Keep it NULL if no subprojects.
@@ -733,7 +727,7 @@ namespace build2
// was specified by the user so it is most likely in our
// src.
//
- n = find_project_name (root, out_root / d, src_root / d);
+ n = find_project_name (out_root / d, src_root / d);
// See find_subprojects() for details on unnamed projects.
//
@@ -765,6 +759,27 @@ namespace build2
return l.defined () && (l->null || l->type != nullptr);
}
+ // Return true if the inner/outer project (identified by out/src_root) of
+ // the 'origin' project (identified by root) should be forwarded.
+ //
+ static inline bool
+ forwarded (const scope& root,
+ const dir_path& out_root,
+ const dir_path& src_root)
+ {
+ // The conditions are:
+ //
+ // 1. Origin is itself forwarded.
+ //
+ // 2. Inner/outer src_root != out_root.
+ //
+ // 3. Inner/outer out-root.build exists in src_root and refers out_root.
+ //
+ return (out_root != src_root &&
+ cast_false<bool> (root.vars[var_forwarded]) &&
+ bootstrap_fwd (src_root) == out_root);
+ }
+
void
create_bootstrap_outer (scope& root)
{
@@ -783,16 +798,15 @@ namespace build2
// 2. Amalgamation's src_root is the same as its out_root.
// 3. Some other pre-configured (via src-root.build) src_root.
//
- // So we need to try all these cases in some sensible order.
- // #3 should probably be tried first since that src_root was
- // explicitly configured by the user. After that, #2 followed
- // by #1 seems reasonable.
+ // So we need to try all these cases in some sensible order. #3 should
+ // probably be tried first since that src_root was explicitly configured
+ // by the user. After that, #2 followed by #1 seems reasonable.
//
scope& rs (create_root (root, out_root, dir_path ())->second);
if (!bootstrapped (rs))
{
- bootstrap_out (rs); // #3 happens here, if at all.
+ bootstrap_out (rs); // #3 happens here (or it can be #1).
value& v (rs.assign (var_src_root));
@@ -812,6 +826,9 @@ namespace build2
bootstrap_src (rs);
}
+ if (forwarded (root, rs.out_path (), rs.src_path ()))
+ rs.assign (var_forwarded) = true;
+
create_bootstrap_outer (rs);
// Check if we are strongly amalgamated by this outer root scope.
@@ -856,6 +873,9 @@ namespace build2
if (rs.src_path ().sub (root.src_path ()))
rs.strong_ = root.strong_scope (); // Itself or some outer scope.
+ if (forwarded (root, rs.out_path (), rs.src_path ()))
+ rs.assign (var_forwarded) = true;
+
// See if there are more inner roots.
//
return create_bootstrap_inner (rs, out_base);
@@ -901,6 +921,37 @@ namespace build2
source_once (root, root, bf);
}
+ scope&
+ load_project (scope& lock,
+ const dir_path& out_root,
+ const dir_path& src_root,
+ bool forwarded,
+ bool load)
+ {
+ assert (!forwarded || out_root != src_root);
+
+ auto i (create_root (lock, out_root, src_root));
+ scope& rs (i->second);
+
+ if (!bootstrapped (rs))
+ {
+ bootstrap_out (rs);
+ setup_root (rs);
+ bootstrap_src (rs);
+ }
+
+ if (forwarded)
+ rs.assign (var_forwarded) = true;
+
+ if (load)
+ {
+ load_root_pre (rs);
+ setup_base (i, out_root, src_root); // Setup as base.
+ }
+
+ return rs;
+ }
+
names
import (scope& ibase, name target, const location& loc)
{
@@ -1073,12 +1124,23 @@ namespace build2
// The user can also specify the out_root of the amalgamation that contains
// our project. For now we only consider top-level sub-projects.
//
- dir_path src_root;
scope* root;
+ dir_path src_root;
+
+ // See if this is a forwarded configuration. For top-level project we want
+ // to use the same logic as in main() while for inner subprojects -- as in
+ // create_bootstrap_inner().
+ //
+ bool fwd (false);
+ if (is_src_root (out_root))
+ {
+ src_root = move (out_root);
+ out_root = bootstrap_fwd (src_root);
+ fwd = (src_root != out_root);
+ }
- for (;;)
+ for (const scope* proot (nullptr); ; proot = root)
{
- src_root = is_src_root (out_root) ? out_root : dir_path ();
root = &create_root (iroot, out_root, src_root)->second;
if (!bootstrapped (*root))
@@ -1105,6 +1167,11 @@ namespace build2
else if (src_root.empty ())
src_root = root->src_path ();
+ if (proot == nullptr
+ ? fwd
+ : forwarded (*proot, root->out_path (), root->src_path ()))
+ root->assign (var_forwarded) = true;
+
// Now we know this project's name as well as all its subprojects.
//
if (cast<string> (root->vars[var_project]) == proj)
@@ -1119,6 +1186,7 @@ namespace build2
{
const dir_path& d ((*i).second);
out_root = root->out_path () / d;
+ src_root = is_src_root (out_root) ? out_root : dir_path ();
continue;
}
}
@@ -1126,8 +1194,8 @@ namespace build2
fail (loc) << out_root << " is not out_root for " << proj;
}
- // Bootstrap outer roots if any. Loading will be done by
- // load_root_pre() below.
+ // Bootstrap outer roots if any. Loading will be done by load_root_pre()
+ // below.
//
create_bootstrap_outer (*root);
diff --git a/build2/file.hxx b/build2/file.hxx
index a720d50..74f2f64 100644
--- a/build2/file.hxx
+++ b/build2/file.hxx
@@ -30,6 +30,7 @@ namespace build2
extern const path root_file; // build/root.build
extern const path bootstrap_file; // build/bootstrap.build
extern const path src_root_file; // build/bootstrap/src-root.build
+ extern const path out_root_file; // build/bootstrap/out-root.build
extern const path export_file; // build/export.build
extern const path config_file; // build/config.build
@@ -71,14 +72,14 @@ namespace build2
bool
source_once (scope& root, scope& base, const path&, scope& once);
- // Create project's root scope. Only set the src_root variable if the
- // passed src_root value is not empty. The scope argument is only used
- // as proof of lock.
+ // Create project's root scope. Only set the src_root variable if the passed
+ // src_root value is not empty. The scope argument is only used as proof of
+ // lock.
//
scope_map::iterator
create_root (scope&, const dir_path& out_root, const dir_path& src_root);
- // Setup root scope. Note that it assume the src_root variable
+ // Setup root scope. Note that it assumes the src_root variable
// has already been set.
//
void
@@ -111,9 +112,17 @@ namespace build2
//
scope&
load_project (scope&,
- const dir_path& out_root, const dir_path& src_root,
+ const dir_path& out_root,
+ const dir_path& src_root,
+ bool forwarded,
bool load = true);
+ // Bootstrap the project's forward. Return the forwarded-to out_root or
+ // src_root if there is no forward.
+ //
+ dir_path
+ bootstrap_fwd (const dir_path& src_root);
+
// Bootstrap the project's root scope, the out part.
//
void
@@ -135,8 +144,8 @@ namespace build2
bool
bootstrapped (scope& root);
- // Create and bootstrap outer root scopes, if any. Loading is
- // done by load_root_pre() below.
+ // Create and bootstrap outer root scopes, if any. Loading is done by
+ // load_root_pre().
//
void
create_bootstrap_outer (scope& root);
@@ -144,7 +153,7 @@ namespace build2
// Create and bootstrap inner root scopes between root and base, if any. If
// out_base is empty, then bootstrap all the way in. Return the innermost
// created root scope or root if none were created. Note: loading is done by
- // load_root_pre() below.
+ // load_root_pre().
//
scope&
create_bootstrap_inner (scope& root, const dir_path& out_base = dir_path ());
@@ -159,11 +168,10 @@ namespace build2
// Extract the specified variable value from a buildfile. It is expected to
// be the first non-comment line and not to rely on any variable expansion
// other than those from the global scope or any variable overrides. Return
- // an indication of whether the variable was found. The scope is only used
- // as proof of lock (though we don't modify anything).
+ // an indication of whether the variable was found.
//
pair<value, bool>
- extract_variable (scope&, const path&, const variable&);
+ extract_variable (const path&, const variable&);
// Import has two phases: the first is triggered by the import
// directive in the buildfile. It will try to find and load the
diff --git a/build2/filesystem.hxx b/build2/filesystem.hxx
index fe4473c..4c118a1 100644
--- a/build2/filesystem.hxx
+++ b/build2/filesystem.hxx
@@ -70,6 +70,12 @@ namespace build2
return rmfile (f, f, static_cast<uint16_t> (verbosity));
}
+ inline fs_status<rmfile_status>
+ rmfile (const path& f, uint16_t verbosity) // Overload (verb_never).
+ {
+ return rmfile (f, f, verbosity);
+ }
+
// Similar to rmfile() but for directories (note: not -r).
//
using rmdir_status = butl::rmdir_status;
@@ -84,6 +90,12 @@ namespace build2
return rmdir (d, d, static_cast<uint16_t> (verbosity));
}
+ inline fs_status<rmdir_status>
+ rmdir (const dir_path& d, uint16_t verbosity) // Overload (verb_never).
+ {
+ return rmdir (d, d, verbosity);
+ }
+
// Remove the directory recursively and print the standard diagnostics
// starting from the specified verbosity level. Note that this function
// returns not_empty if we try to remove a working directory. If the dir
diff --git a/build2/scope.hxx b/build2/scope.hxx
index 5d0fcd8..0bfd76e 100644
--- a/build2/scope.hxx
+++ b/build2/scope.hxx
@@ -146,11 +146,7 @@ namespace build2
assign (const variable& var) {return vars.assign (var);}
value&
- assign (const variable* var) // For cached variables.
- {
- assert (var != nullptr);
- return vars.assign (*var);
- }
+ assign (const variable* var) {return vars.assign (var);} // For cached.
value&
assign (string name)
diff --git a/build2/spec.hxx b/build2/spec.hxx
index 2af74db..8b62150 100644
--- a/build2/spec.hxx
+++ b/build2/spec.hxx
@@ -31,6 +31,7 @@ namespace build2
scope* root_scope = nullptr;
dir_path out_base;
path buildfile; // Empty if implied.
+ bool forwarded = false;
};
struct opspec: vector<targetspec>
diff --git a/build2/target.hxx b/build2/target.hxx
index 5ba3905..2f32064 100644
--- a/build2/target.hxx
+++ b/build2/target.hxx
@@ -400,6 +400,9 @@ namespace build2
value&
assign (const variable& var) {return vars.assign (var);}
+ value&
+ assign (const variable* var) {return vars.assign (var);} // For cached.
+
// Return a value suitable for appending. See scope for details.
//
value&
@@ -1556,12 +1559,11 @@ namespace build2
search_implied (const scope&, const K&, tracer&);
};
- // While a filesystem directory is mtime-based, the semantics is
- // not very useful in our case. In particular, if another target
- // depends on fsdir{}, then all that's desired is the creation of
- // the directory if it doesn't already exist. In particular, we
- // don't want to update the target just because some unrelated
- // entry was created in that directory.
+ // While a filesystem directory is mtime-based, the semantics is not very
+ // useful in our case. In particular, if another target depends on fsdir{},
+ // then all that's desired is the creation of the directory if it doesn't
+ // already exist. In particular, we don't want to update the target just
+ // because some unrelated entry was created in that directory.
//
class fsdir: public target
{
diff --git a/build2/test/rule.cxx b/build2/test/rule.cxx
index 4225d24..47c8004 100644
--- a/build2/test/rule.cxx
+++ b/build2/test/rule.cxx
@@ -427,6 +427,18 @@ namespace build2
else
wd /= "test-" + t.name;
+ // Are we backlinking the test working directory to src? (See
+ // backlink_*() in algorithm.cxx for details.)
+ //
+ const scope& bs (t.base_scope ());
+
+ dir_path bl;
+ if (cast_false<bool> (bs.root_scope ()->vars[var_forwarded]))
+ {
+ bl = bs.src_path () / wd.leaf (bs.out_path ());
+ clean_backlink (bl, verb_never);
+ }
+
// If this is a (potentially) multi-testscript test, then create (and
// later cleanup) the root directory. If this is just 'testscript', then
// the root directory is used directly as test's working directory and
@@ -517,7 +529,7 @@ namespace build2
// going, bail out.
//
if (r == scope_state::failed && !keep_going)
- throw failed ();
+ break;
}
}
}
@@ -526,19 +538,23 @@ namespace build2
// Re-examine.
//
+ bool bad (false);
for (scope_state r: result)
{
switch (r)
{
- case scope_state::passed: break;
- case scope_state::failed: throw failed ();
+ case scope_state::passed: break;
+ case scope_state::failed: bad = true; break;
case scope_state::unknown: assert (false);
}
+
+ if (bad)
+ break;
}
// Cleanup.
//
- if (!one && !mk && after == output_after::clean)
+ if (!bad && !one && !mk && after == output_after::clean)
{
if (!empty (wd))
fail << "working directory " << wd << " is not empty at the "
@@ -547,6 +563,14 @@ namespace build2
rmdir (wd, 2);
}
+ // Backlink if the working directory exists.
+ //
+ if (!bl.empty () && exists (wd))
+ update_backlink (wd, bl, true /* changed */);
+
+ if (bad)
+ throw failed ();
+
return target_state::changed;
}
diff --git a/build2/variable.hxx b/build2/variable.hxx
index 299ec71..d61bb1c 100644
--- a/build2/variable.hxx
+++ b/build2/variable.hxx
@@ -96,13 +96,13 @@ namespace build2
enum class variable_visibility
{
+ // Note that the search for target type/pattern-specific terminates at
+ // the project boundary.
+ //
target, // Target and target type/pattern-specific.
scope, // This scope (no outer scopes).
project, // This project (no outer projects).
normal // All outer scopes.
-
- // Note that the search for target type/pattern-specific terminates at
- // the project boundary.
};
// variable
@@ -307,7 +307,7 @@ namespace build2
// from lookup expects the value to also be defined.
//
// Note that a cast to names expects the value to be untyped while a cast
- // to vector<names> -- typed.
+ // to vector<name> -- typed.
//
// Why are these non-members? The cast is easier on the eyes and is also
// consistent with the cast operators. The other two are for symmetry.
@@ -1236,6 +1236,13 @@ namespace build2
value&
assign (const variable& var) {return insert (var).first;}
+ value&
+ assign (const variable* var) // For cached variables.
+ {
+ assert (var != nullptr);
+ return assign (*var);
+ }
+
// Note that the variable is expected to have already been registered.
//
value&