From f98262e37f608330fcfce799dcacc6fbacac8f8a Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 23 Apr 2018 14:25:58 +0200 Subject: Implement forwarded configurations and backlinking --- build2/algorithm.cxx | 334 ++++++++++++++++++++++++++++++++++++++-- build2/algorithm.hxx | 31 +++- build2/algorithm.ixx | 14 ++ build2/b.cli | 122 +++++++++------ build2/b.cxx | 79 +++++++--- build2/cc/compile-rule.cxx | 5 +- build2/cc/link-rule.cxx | 42 ++++-- build2/cc/pkgconfig.cxx | 2 +- build2/cc/windows-rpath.cxx | 9 +- build2/config/init.cxx | 2 +- build2/config/operation.cxx | 360 ++++++++++++++++++++++++++++++++++---------- build2/context.cxx | 48 +++--- build2/context.hxx | 2 + build2/file.cxx | 164 ++++++++++++++------ build2/file.hxx | 30 ++-- build2/filesystem.hxx | 12 ++ build2/scope.hxx | 6 +- build2/spec.hxx | 1 + build2/target.hxx | 14 +- build2/test/rule.cxx | 32 +++- build2/variable.hxx | 15 +- 21 files changed, 1047 insertions(+), 277 deletions(-) (limited to 'build2') 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* 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 + { + using path_type = build2::path; + + reference_wrapper target; + + backlink (const path_type& t, path_type&& l) + : auto_rm (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; + + 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 ()) + 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 (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 (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 ().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 ()) + { + 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 ()) + 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 (), 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 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 . 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 (l); + trace << " amalgamation: " << cast (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 (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 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 (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 (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; // 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 (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 (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 (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 (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 (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::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 (var_extension, variable_visibility::target); - gs.assign ("build.work") = work; gs.assign ("build.home") = home; @@ -673,28 +671,38 @@ namespace build2 vp.insert_pattern ( "**.configured", false, variable_visibility::project); - var_src_root = &vp.insert ("src_root"); - var_out_root = &vp.insert ("out_root"); - var_src_base = &vp.insert ("src_base"); - var_out_base = &vp.insert ("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 ("project", pv); - var_amalgamation = &vp.insert ("amalgamation", pv); - var_subprojects = &vp.insert ("subprojects", pv); - var_version = &vp.insert ("version", pv); + var_src_root = &vp.insert ("src_root"); + var_out_root = &vp.insert ("out_root"); + var_src_base = &vp.insert ("src_base"); + var_out_base = &vp.insert ("out_base"); - var_project_url = &vp.insert ("project.url", pv); - var_project_summary = &vp.insert ("project.summary", pv); + var_forwarded = &vp.insert ("forwarded", v_p); + + // Note that subprojects is not typed since the value requires + // pre-processing (see file.cxx). + // + var_project = &vp.insert ("project", v_p); + var_amalgamation = &vp.insert ("amalgamation", v_p); + var_subprojects = &vp.insert ("subprojects", v_p); + var_version = &vp.insert ("version", v_p); + + var_project_url = &vp.insert ("project.url", v_p); + var_project_summary = &vp.insert ("project.summary", v_p); var_import_target = &vp.insert ("import.target"); - var_clean = &vp.insert ("clean", variable_visibility::target); + var_clean = &vp.insert ("clean", v_t); + var_backlink = &vp.insert ("backlink", v_t); + vp.insert (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 (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 (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 - 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 (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 (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 (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 (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 - 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 (verbosity)); } + inline fs_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 (verbosity)); } + inline fs_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 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 (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 -- typed. + // to vector -- 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& -- cgit v1.1