aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/file.cxx
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2019-06-24 12:01:19 +0200
committerKaren Arutyunov <karen@codesynthesis.com>2019-07-01 18:13:55 +0300
commit977d07a3ae47ef204665d1eda2d642e5064724f3 (patch)
tree525a3d6421f61ce789b690191d3c30fc09be3517 /libbuild2/file.cxx
parent7161b24963dd9da4d218f92c736b77c35c328a2d (diff)
Split build system into library and driver
Diffstat (limited to 'libbuild2/file.cxx')
-rw-r--r--libbuild2/file.cxx1660
1 files changed, 1660 insertions, 0 deletions
diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx
new file mode 100644
index 0000000..5966168
--- /dev/null
+++ b/libbuild2/file.cxx
@@ -0,0 +1,1660 @@
+// file : libbuild2/file.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/file.hxx>
+
+#include <iostream> // cin
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/context.hxx>
+#include <libbuild2/filesystem.hxx> // exists()
+#include <libbuild2/prerequisite.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/token.hxx>
+#include <libbuild2/lexer.hxx>
+#include <libbuild2/parser.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ // Standard and alternative build file/directory naming schemes.
+ //
+ const dir_path std_build_dir ("build");
+ const dir_path std_root_dir (dir_path (std_build_dir) /= "root");
+ const dir_path std_bootstrap_dir (dir_path (std_build_dir) /= "bootstrap");
+
+ const path std_root_file (std_build_dir / "root.build");
+ const path std_bootstrap_file (std_build_dir / "bootstrap.build");
+ const path std_src_root_file (std_bootstrap_dir / "src-root.build");
+ const path std_out_root_file (std_bootstrap_dir / "out-root.build");
+ const path std_export_file (std_build_dir / "export.build");
+
+ const string std_build_ext ("build");
+ const path std_buildfile_file ("buildfile");
+ const path std_buildignore_file (".buildignore");
+
+ //
+
+ const dir_path alt_build_dir ("build2");
+ const dir_path alt_root_dir (dir_path (alt_build_dir) /= "root");
+ const dir_path alt_bootstrap_dir (dir_path (alt_build_dir) /= "bootstrap");
+
+ const path alt_root_file (alt_build_dir / "root.build2");
+ const path alt_bootstrap_file (alt_build_dir / "bootstrap.build2");
+ const path alt_src_root_file (alt_bootstrap_dir / "src-root.build2");
+ const path alt_out_root_file (alt_bootstrap_dir / "out-root.build2");
+ const path alt_export_file (alt_build_dir / "export.build2");
+
+ const string alt_build_ext ("build2");
+ const path alt_buildfile_file ("build2file");
+ const path alt_buildignore_file (".build2ignore");
+
+ ostream&
+ operator<< (ostream& os, const subprojects& sps)
+ {
+ for (auto b (sps.begin ()), i (b); os && i != sps.end (); ++i)
+ {
+ // See find_subprojects() for details.
+ //
+ const project_name& n (
+ path::traits_type::is_separator (i->first.string ().back ())
+ ? empty_project_name
+ : i->first);
+
+ os << (i != b ? " " : "") << n << '@' << i->second;
+ }
+
+ return os;
+ }
+
+ // Check if the standard/alternative file/directory exists, returning empty
+ // path if it does not.
+ //
+ template <typename T>
+ static T
+ exists (const dir_path& d, const T& s, const T& a, optional<bool>& altn)
+ {
+ T p;
+ bool e;
+
+ if (altn)
+ {
+ p = d / (*altn ? a : s);
+ e = exists (p);
+ }
+ else
+ {
+ // Check the alternative name first since it is more specific.
+ //
+ p = d / a;
+
+ if ((e = exists (p)))
+ altn = true;
+ else
+ {
+ p = d / s;
+
+ if ((e = exists (p)))
+ altn = false;
+ }
+ }
+
+ return e ? p : T ();
+ }
+
+ bool
+ is_src_root (const dir_path& d, optional<bool>& altn)
+ {
+ // We can't have root without bootstrap.build.
+ //
+ return !exists (d, std_bootstrap_file, alt_bootstrap_file, altn).empty ();
+ }
+
+ bool
+ is_out_root (const dir_path& d, optional<bool>& altn)
+ {
+ return !exists (d, std_src_root_file, alt_src_root_file, altn).empty ();
+ }
+
+ dir_path
+ find_src_root (const dir_path& b, optional<bool>& altn)
+ {
+ for (dir_path d (b); !d.root () && d != home; d = d.directory ())
+ {
+ if (is_src_root (d, altn))
+ return d;
+ }
+
+ return dir_path ();
+ }
+
+ pair<dir_path, bool>
+ find_out_root (const dir_path& b, optional<bool>& altn)
+ {
+ for (dir_path d (b); !d.root () && d != home; d = d.directory ())
+ {
+ bool s;
+ if ((s = is_src_root (d, altn)) || is_out_root (d, altn))
+ return make_pair (move (d), s);
+ }
+
+ return make_pair (dir_path (), false);
+ }
+
+ dir_path old_src_root;
+ dir_path new_src_root;
+
+ // Remap the src_root variable value if it is inside old_src_root.
+ //
+ static inline void
+ remap_src_root (value& v)
+ {
+ if (!old_src_root.empty ())
+ {
+ dir_path& d (cast<dir_path> (v));
+
+ if (d.sub (old_src_root))
+ d = new_src_root / d.leaf (old_src_root);
+ }
+ }
+
+ static void
+ source (scope& root, scope& base, const path& bf, bool boot)
+ {
+ tracer trace ("source");
+
+ try
+ {
+ bool sin (bf.string () == "-");
+
+ ifdstream ifs;
+
+ if (!sin)
+ ifs.open (bf);
+ else
+ cin.exceptions (ifdstream::failbit | ifdstream::badbit);
+
+ istream& is (sin ? cin : ifs);
+
+ l5 ([&]{trace << "sourcing " << bf;});
+
+ parser p (boot);
+ p.parse_buildfile (is, bf, root, base);
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to read buildfile " << bf << ": " << e;
+ }
+ }
+
+ void
+ source (scope& root, scope& base, const path& bf)
+ {
+ source (root, base, bf, false);
+ }
+
+ bool
+ source_once (scope& root, scope& base, const path& bf, scope& once)
+ {
+ tracer trace ("source_once");
+
+ if (!once.buildfiles.insert (bf).second)
+ {
+ l5 ([&]{trace << "skipping already sourced " << bf;});
+ return false;
+ }
+
+ source (root, base, bf);
+ return true;
+ }
+
+ // Source (once) pre-*.build (pre is true) or post-*.build (otherwise) hooks
+ // from the specified directory (build/{bootstrap,root}/ of out_root) which
+ // must exist.
+ //
+ static void
+ source_hooks (scope& root, const dir_path& d, bool pre)
+ {
+ // While we could have used the wildcard pattern matching functionality,
+ // our needs are pretty basic and performance is quite important, so let's
+ // handle this ourselves.
+ //
+ try
+ {
+ for (const dir_entry& de: dir_iterator (d, false /* ignore_dangling */))
+ {
+ // If this is a link, then type() will try to stat() it. And if the
+ // link is dangling or points to something inaccessible, it will fail.
+ // So let's first check that the name matches and only then check the
+ // type.
+ //
+ const path& n (de.path ());
+
+ if (n.string ().compare (0,
+ pre ? 4 : 5,
+ pre ? "pre-" : "post-") != 0 ||
+ n.extension () != root.root_extra->build_ext)
+ continue;
+
+ path f (d / n);
+
+ try
+ {
+ if (de.type () != entry_type::regular)
+ continue;
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to read buildfile " << f << ": " << e;
+ }
+
+ source_once (root, root, f);
+ }
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to iterate over " << d << ": " << e;
+ }
+ }
+
+ scope_map::iterator
+ create_root (scope& l, const dir_path& out_root, const dir_path& src_root)
+ {
+ auto i (scopes.rw (l).insert (out_root, true /* root */));
+ scope& rs (i->second);
+
+ // Set out_path. Note that src_path is set in setup_root() below.
+ //
+ if (rs.out_path_ != &i->first)
+ {
+ assert (rs.out_path_ == nullptr);
+ rs.out_path_ = &i->first;
+ }
+
+ // If this is already a root scope, verify that things are consistent.
+ //
+ {
+ value& v (rs.assign (var_out_root));
+
+ if (!v)
+ v = out_root;
+ else
+ {
+ const dir_path& p (cast<dir_path> (v));
+
+ if (p != out_root)
+ fail << "new out_root " << out_root << " does not match "
+ << "existing " << p;
+ }
+ }
+
+ if (!src_root.empty ())
+ {
+ value& v (rs.assign (var_src_root));
+
+ if (!v)
+ v = src_root;
+ else
+ {
+ const dir_path& p (cast<dir_path> (v));
+
+ if (p != src_root)
+ fail << "new src_root " << src_root << " does not match "
+ << "existing " << p;
+ }
+ }
+
+ return i;
+ }
+
+ void
+ setup_root (scope& s, bool forwarded)
+ {
+ // The caller must have made sure src_root is set on this scope.
+ //
+ value& v (s.assign (var_src_root));
+ assert (v);
+ const dir_path& d (cast<dir_path> (v));
+
+ if (s.src_path_ == nullptr)
+ s.src_path_ = &d;
+ else
+ assert (s.src_path_ == &d);
+
+ s.assign (var_forwarded) = forwarded;
+ }
+
+ scope&
+ setup_base (scope_map::iterator i,
+ const dir_path& out_base,
+ const dir_path& src_base)
+ {
+ scope& s (i->second);
+
+ // Set src/out_base variables.
+ //
+ value& ov (s.assign (var_out_base));
+
+ if (!ov)
+ ov = out_base;
+ else
+ assert (cast<dir_path> (ov) == out_base);
+
+ value& sv (s.assign (var_src_base));
+
+ if (!sv)
+ sv = src_base;
+ else
+ assert (cast<dir_path> (sv) == src_base);
+
+ // Set src/out_path. The key (i->first) is out_base.
+ //
+ if (s.out_path_ == nullptr)
+ s.out_path_ = &i->first;
+ else
+ assert (*s.out_path_ == out_base);
+
+ if (s.src_path_ == nullptr)
+ s.src_path_ = &cast<dir_path> (sv);
+ else
+ assert (*s.src_path_ == src_base);
+
+ return s;
+ }
+
+ pair<scope&, scope*>
+ switch_scope (scope& root, const dir_path& p)
+ {
+ // First, enter the scope into the map and see if it is in any project. If
+ // it is not, then there is nothing else to do.
+ //
+ auto i (scopes.rw (root).insert (p));
+ scope& base (i->second);
+ scope* rs (base.root_scope ());
+
+ if (rs != nullptr)
+ {
+ // Path p can be src_base or out_base. Figure out which one it is.
+ //
+ dir_path out_base (p.sub (rs->out_path ()) ? p : src_out (p, *rs));
+
+ // Create and bootstrap root scope(s) of subproject(s) that this scope
+ // may belong to. If any were created, load them. Note that we need to
+ // do this before figuring out src_base since we may switch the root
+ // project (and src_root with it).
+ //
+ {
+ scope* nrs (&create_bootstrap_inner (*rs, out_base));
+
+ if (rs != nrs)
+ rs = nrs;
+ }
+
+ // Switch to the new root scope.
+ //
+ if (rs != &root)
+ load_root (*rs); // Load new root(s) recursively.
+
+ // Now we can figure out src_base and finish setting the scope.
+ //
+ dir_path src_base (src_out (out_base, *rs));
+ setup_base (i, move (out_base), move (src_base));
+ }
+
+ return pair<scope&, scope*> (base, rs);
+ }
+
+ dir_path
+ bootstrap_fwd (const dir_path& src_root, optional<bool>& altn)
+ {
+ path f (exists (src_root, std_out_root_file, alt_out_root_file, altn));
+
+ if (f.empty ())
+ return src_root;
+
+ // We cannot just source the buildfile since there is no scope to do
+ // this on yet.
+ //
+ auto p (extract_variable (f, *var_out_root));
+
+ if (!p.second)
+ fail << "variable out_root expected as first line in " << f;
+
+ try
+ {
+ return convert<dir_path> (move (p.first));
+ }
+ catch (const invalid_argument& e)
+ {
+ fail << "invalid out_root value in " << f << ": " << e << endf;
+ }
+ }
+
+ static void
+ setup_root_extra (scope& root, optional<bool>& altn)
+ {
+ assert (altn && root.root_extra == nullptr);
+ bool a (*altn);
+
+ root.root_extra = unique_ptr<scope::root_data> (
+ new scope::root_data {
+ a,
+ a ? alt_build_ext : std_build_ext,
+ a ? alt_build_dir : std_build_dir,
+ a ? alt_buildfile_file : std_buildfile_file,
+ a ? alt_buildignore_file : std_buildignore_file,
+ a ? alt_root_dir : std_root_dir,
+ a ? alt_bootstrap_dir : std_bootstrap_dir,
+ a ? alt_bootstrap_file : std_bootstrap_file,
+ a ? alt_root_file : std_root_file,
+ a ? alt_export_file : std_export_file,
+ a ? alt_src_root_file : std_src_root_file,
+ a ? alt_out_root_file : std_out_root_file,
+ {}, /* meta_operations */
+ {}, /* operations */
+ {}, /* modules */
+ {} /* override_cache */});
+
+ // Enter built-in meta-operation and operation names. Loading of
+ // modules (via the src bootstrap; see below) can result in
+ // additional meta/operations being added.
+ //
+ root.insert_meta_operation (noop_id, mo_noop);
+ root.insert_meta_operation (perform_id, mo_perform);
+ root.insert_meta_operation (info_id, mo_info);
+
+ root.insert_operation (default_id, op_default);
+ root.insert_operation (update_id, op_update);
+ root.insert_operation (clean_id, op_clean);
+ }
+
+ void
+ bootstrap_out (scope& root, optional<bool>& altn)
+ {
+ const dir_path& out_root (root.out_path ());
+
+ path f (exists (out_root, std_src_root_file, alt_src_root_file, altn));
+
+ if (f.empty ())
+ return;
+
+ if (root.root_extra == nullptr)
+ setup_root_extra (root, altn);
+
+ //@@ TODO: if bootstrap files can source other bootstrap files (for
+ // example, as a way to express dependecies), then we need a way to
+ // prevent multiple sourcing. We handle it here but we still need
+ // something like source_once (once [scope] source) in buildfiles.
+ //
+ source_once (root, root, f);
+ }
+
+ pair<value, bool>
+ extract_variable (const path& bf, const variable& var)
+ {
+ try
+ {
+ ifdstream ifs (bf);
+
+ lexer lex (ifs, bf);
+ token t (lex.next ());
+ token_type tt;
+
+ if (t.type != token_type::word || t.value != var.name ||
+ ((tt = lex.next ().type) != token_type::assign &&
+ tt != token_type::prepend &&
+ tt != token_type::append))
+ {
+ return make_pair (value (), false);
+ }
+
+ parser p;
+ temp_scope tmp (global_scope->rw ());
+ p.parse_variable (lex, tmp, var, tt);
+
+ value* v (tmp.vars.find_to_modify (var).first);
+ assert (v != nullptr);
+
+ // Steal the value, the scope is going away.
+ //
+ return make_pair (move (*v), true);
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to read buildfile " << bf << ": " << e << endf;
+ }
+ }
+
+ // Extract the project name from bootstrap.build.
+ //
+ static project_name
+ find_project_name (const dir_path& out_root,
+ const dir_path& fallback_src_root,
+ optional<bool> out_src, // True if out_root is src_root.
+ optional<bool>& altn)
+ {
+ tracer trace ("find_project_name");
+
+ // First check if the root scope for this project has already been setup
+ // in which case we will have src_root and maybe even the name.
+ //
+ const dir_path* src_root (nullptr);
+ const scope& s (scopes.find (out_root));
+
+ if (s.root_scope () == &s && s.out_path () == out_root)
+ {
+ if (s.root_extra != nullptr)
+ {
+ if (!altn)
+ altn = s.root_extra->altn;
+ else
+ assert (*altn == s.root_extra->altn);
+ }
+
+ if (lookup l = s.vars[var_project])
+ return cast<project_name> (l);
+
+ src_root = s.src_path_;
+ }
+
+ // Load the project name. If this subdirectory is the subproject's
+ // src_root, then we can get directly to that. Otherwise, we first have to
+ // discover its src_root.
+ //
+ value src_root_v; // Need it to live until the end.
+
+ if (src_root == nullptr)
+ {
+ if (out_src ? *out_src : is_src_root (out_root, altn))
+ src_root = &out_root;
+ else
+ {
+ path f (exists (out_root, std_src_root_file, alt_src_root_file, altn));
+
+ if (f.empty ())
+ {
+ // Note: the same diagnostics as in main().
+ //
+ if (fallback_src_root.empty ())
+ fail << "no bootstrapped src_root for " << out_root <<
+ info << "consider reconfiguring this out_root";
+
+ src_root = &fallback_src_root;
+ }
+ else
+ {
+ auto p (extract_variable (f, *var_src_root));
+
+ if (!p.second)
+ fail << "variable src_root expected as first line in " << f;
+
+ src_root_v = move (p.first);
+ remap_src_root (src_root_v); // Remap if inside old_src_root.
+ src_root = &cast<dir_path> (src_root_v);
+
+ l5 ([&]{trace << "extracted src_root " << *src_root
+ << " for " << out_root;});
+ }
+ }
+ }
+
+ project_name name;
+ {
+ path f (exists (*src_root, std_bootstrap_file, alt_bootstrap_file, altn));
+
+ if (f.empty ())
+ fail << "no build/bootstrap.build in " << *src_root;
+
+ auto p (extract_variable (f, *var_project));
+
+ if (!p.second)
+ fail << "variable " << var_project->name << " expected "
+ << "as a first line in " << f;
+
+ name = cast<project_name> (move (p.first));
+ }
+
+ l5 ([&]{trace << "extracted project name '" << name << "' for "
+ << *src_root;});
+ return name;
+ }
+
+ // Scan the specified directory for any subprojects. If a subdirectory
+ // is a subproject, then enter it into the map, handling the duplicates.
+ //
+ static void
+ find_subprojects (subprojects& sps,
+ const dir_path& d,
+ const dir_path& root,
+ bool out)
+ {
+ tracer trace ("find_subprojects");
+
+ try
+ {
+ for (const dir_entry& de: dir_iterator (d, true /* ignore_dangling */))
+ {
+ if (de.type () != entry_type::directory)
+ continue;
+
+ dir_path sd (d / path_cast<dir_path> (de.path ()));
+
+ bool src (false);
+ optional<bool> altn;
+
+ if (!((out && is_out_root (sd, altn)) ||
+ (src = is_src_root (sd, altn))))
+ {
+ // We used to scan for subproject recursively but this is probably
+ // too loose (think of some tests laying around). In the future we
+ // should probably allow specifying something like extra/* or
+ // extra/** in subprojects.
+ //
+ //find_subprojects (sps, sd, root, out);
+ //
+ continue;
+ }
+
+ // Calculate relative subdirectory for this subproject.
+ //
+ dir_path dir (sd.leaf (root));
+ l5 ([&]{trace << "subproject " << sd << " as " << dir;});
+
+ // 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.
+ //
+ project_name name (find_project_name (sd, dir_path (), src, altn));
+
+ // If the name is empty, then is is an unnamed project. While the
+ // 'project' variable stays empty, here we come up with a surrogate
+ // name for a key. The idea is that such a key should never conflict
+ // with a real project name. We ensure this by using the project's
+ // sub-directory and appending a trailing directory separator to it.
+ //
+ if (name.empty ())
+ name = project_name (dir.posix_string () + '/',
+ project_name::raw_string);
+
+ // @@ Can't use move() because we may need the values in diagnostics
+ // below. Looks like C++17 try_emplace() is what we need.
+ //
+ auto rp (sps.emplace (name, dir));
+
+ // Handle duplicates.
+ //
+ if (!rp.second)
+ {
+ const dir_path& dir1 (rp.first->second);
+
+ if (dir != dir1)
+ fail << "inconsistent subproject directories for " << name <<
+ info << "first alternative: " << dir1 <<
+ info << "second alternative: " << dir;
+
+ l6 ([&]{trace << "skipping duplicate";});
+ }
+ }
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to iterate over " << d << ": " << e;
+ }
+ }
+
+ bool
+ bootstrap_src (scope& root, optional<bool>& altn)
+ {
+ tracer trace ("bootstrap_src");
+
+ bool r (false);
+
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ {
+ path f (exists (src_root, std_bootstrap_file, alt_bootstrap_file, altn));
+
+ if (root.root_extra == nullptr)
+ {
+ // If nothing so far has indicated the naming, assume standard.
+ //
+ if (!altn)
+ altn = false;
+
+ setup_root_extra (root, altn);
+ }
+
+ if (!f.empty ())
+ {
+ // We assume that bootstrap out cannot load this file explicitly. It
+ // feels wrong to allow this since that makes the whole bootstrap
+ // process hard to reason about. But we may try to bootstrap the same
+ // root scope multiple time.
+ //
+ if (root.buildfiles.insert (f).second)
+ source (root, root, f, true);
+ else
+ l5 ([&]{trace << "skipping already sourced " << f;});
+
+ r = true;
+ }
+ }
+
+ // See if we are a part of an amalgamation. There are two key players: the
+ // outer root scope which may already be present (i.e., we were loaded as
+ // part of an amalgamation) and the amalgamation variable that may or may
+ // not be set by the user (in bootstrap.build) or by an earlier call to
+ // this function for the same scope. When set by the user, the empty
+ // special value means that the project shall not be amalgamated (and
+ // which we convert to NULL below). When calculated, the NULL value
+ // indicates that we are not amalgamated.
+ //
+ // Note: the amalgamation variable value is always a relative directory.
+ //
+ {
+ auto rp (root.vars.insert (*var_amalgamation)); // Set NULL by default.
+ value& v (rp.first);
+
+ if (v && v.empty ()) // Convert empty to NULL.
+ v = nullptr;
+
+ if (scope* aroot = root.parent_scope ()->root_scope ())
+ {
+ const dir_path& ad (aroot->out_path ());
+ dir_path rd (ad.relative (out_root));
+
+ // If we already have the amalgamation variable set, verify
+ // that aroot matches its value.
+ //
+ if (!rp.second)
+ {
+ if (!v)
+ {
+ fail << out_root << " cannot be amalgamated" <<
+ info << "amalgamated by " << ad;
+ }
+ else
+ {
+ const dir_path& vd (cast<dir_path> (v));
+
+ if (vd != rd)
+ {
+ fail << "inconsistent amalgamation of " << out_root <<
+ info << "specified: " << vd <<
+ info << "actual: " << rd << " by " << ad;
+ }
+ }
+ }
+ else
+ {
+ // Otherwise, use the outer root as our amalgamation.
+ //
+ l5 ([&]{trace << out_root << " amalgamated as " << rd;});
+ v = move (rd);
+ }
+ }
+ else if (rp.second)
+ {
+ // If there is no outer root and the amalgamation variable
+ // hasn't been set, then we need to check if any of the
+ // outer directories is a project's out_root. If so, then
+ // that's our amalgamation.
+ //
+ optional<bool> altn;
+ const dir_path& ad (find_out_root (out_root.directory (), altn).first);
+
+ if (!ad.empty ())
+ {
+ dir_path rd (ad.relative (out_root));
+ l5 ([&]{trace << out_root << " amalgamated as " << rd;});
+ v = move (rd);
+ }
+ }
+ }
+
+ // See if we have any subprojects. In a sense, this is the other
+ // side/direction of the amalgamation logic above. Here, the subprojects
+ // variable may or may not be set by the user (in bootstrap.build) or by
+ // an earlier call to this function for the same scope. When set by the
+ // user, the empty special value means that there are no subproject and
+ // none should be searched for (and which we convert to NULL below).
+ // Otherwise, it is a list of [project@]directory pairs. The directory
+ // must be relative to our out_root. If the project name is not specified,
+ // then we have to figure it out. When subprojects are calculated, the
+ // NULL value indicates that we found no subprojects.
+ //
+ {
+ auto rp (root.vars.insert (*var_subprojects)); // Set NULL by default.
+ value& v (rp.first);
+
+ if (rp.second)
+ {
+ // No subprojects set so we need to figure out if there are any.
+ //
+ // First we are going to scan our out_root and find all the
+ // pre-configured subprojects. Then, if out_root != src_root,
+ // we are going to do the same for src_root. Here, however,
+ // we need to watch out for duplicates.
+ //
+ subprojects sps;
+
+ if (exists (out_root))
+ {
+ l5 ([&]{trace << "looking for subprojects in " << out_root;});
+ find_subprojects (sps, out_root, out_root, true);
+ }
+
+ if (out_root != src_root)
+ {
+ l5 ([&]{trace << "looking for subprojects in " << src_root;});
+ find_subprojects (sps, src_root, src_root, false);
+ }
+
+ if (!sps.empty ()) // Keep it NULL if no subprojects.
+ v = move (sps);
+ }
+ else if (v)
+ {
+ // Convert empty to NULL.
+ //
+ if (v.empty ())
+ v = nullptr;
+ else
+ {
+ // Scan the (untyped) value and convert it to the "canonical" form,
+ // that is, a list of name@dir pairs.
+ //
+ subprojects sps;
+ names& ns (cast<names> (v));
+
+ for (auto i (ns.begin ()); i != ns.end (); ++i)
+ {
+ // Project name.
+ //
+ project_name n;
+ if (i->pair)
+ {
+ if (i->pair != '@')
+ fail << "unexpected pair style in variable subprojects";
+
+ try
+ {
+ n = convert<project_name> (move (*i));
+
+ if (n.empty ())
+ fail << "empty project name in variable subprojects";
+ }
+ catch (const invalid_argument&)
+ {
+ fail << "expected project name instead of '" << *i << "' in "
+ << "variable subprojects";
+ }
+
+ ++i; // Got to have the second half of the pair.
+ }
+
+ // Directory.
+ //
+ dir_path d;
+ try
+ {
+ d = convert<dir_path> (move (*i));
+
+ if (d.empty ())
+ fail << "empty directory in variable subprojects";
+ }
+ catch (const invalid_argument&)
+ {
+ fail << "expected directory instead of '" << *i << "' in "
+ << "variable subprojects";
+ }
+
+ // Figure out the project name if the user didn't specify one.
+ //
+ if (n.empty ())
+ {
+ optional<bool> altn;
+
+ // Pass fallback src_root since this is a subproject that was
+ // specified by the user so it is most likely in our src.
+ //
+ n = find_project_name (out_root / d,
+ src_root / d,
+ nullopt /* out_src */,
+ altn);
+
+ // See find_subprojects() for details on unnamed projects.
+ //
+ if (n.empty ())
+ n = project_name (d.posix_string () + '/',
+ project_name::raw_string);
+ }
+
+ sps.emplace (move (n), move (d));
+ }
+
+ // Change the value to the typed map.
+ //
+ v = move (sps);
+ }
+ }
+ }
+
+ return r;
+ }
+
+ void
+ bootstrap_pre (scope& root, optional<bool>& altn)
+ {
+ const dir_path& out_root (root.out_path ());
+
+ // This test is a bit loose in a sense that there can be a stray
+ // build/bootstrap/ directory that will make us mis-treat a project as
+ // following the standard naming scheme (the other way, while also
+ // possible, is a lot less likely). If this does becomes a problem, we can
+ // always tighten the test by also looking for a hook file with the
+ // correct extension.
+ //
+ dir_path d (exists (out_root, std_bootstrap_dir, alt_bootstrap_dir, altn));
+
+ if (!d.empty ())
+ {
+ if (root.root_extra == nullptr)
+ setup_root_extra (root, altn);
+
+ source_hooks (root, d, true /* pre */);
+ }
+ }
+
+ void
+ bootstrap_post (scope& root)
+ {
+ const dir_path& out_root (root.out_path ());
+
+ dir_path d (out_root / root.root_extra->bootstrap_dir);
+
+ if (exists (d))
+ source_hooks (root, d, false /* pre */);
+ }
+
+ bool
+ bootstrapped (scope& root)
+ {
+ // Use the subprojects variable set by bootstrap_src() as an indicator.
+ // It should either be NULL or typed (so we assume that the user will
+ // never set it to NULL).
+ //
+ auto l (root.vars[var_subprojects]);
+ 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 orig) should be forwarded.
+ //
+ static inline bool
+ forwarded (const scope& orig,
+ const dir_path& out_root,
+ const dir_path& src_root,
+ optional<bool>& altn)
+ {
+ // 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> (orig.vars[var_forwarded]) &&
+ bootstrap_fwd (src_root, altn) == out_root);
+ }
+
+ void
+ create_bootstrap_outer (scope& root)
+ {
+ auto l (root.vars[var_amalgamation]);
+
+ if (!l)
+ return;
+
+ const dir_path& d (cast<dir_path> (l));
+ dir_path out_root (root.out_path () / d);
+ out_root.normalize (); // No need to actualize (d is a bunch of ..)
+
+ // src_root is a bit more complicated. Here we have three cases:
+ //
+ // 1. Amalgamation's src_root is "parallel" to the sub-project's.
+ // 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.
+ //
+ scope& rs (create_root (root, out_root, dir_path ())->second);
+
+ bool bstrapped (bootstrapped (rs));
+
+ optional<bool> altn;
+ if (!bstrapped)
+ {
+ bootstrap_out (rs, altn); // #3 happens here (or it can be #1).
+
+ value& v (rs.assign (var_src_root));
+
+ if (!v)
+ {
+ if (is_src_root (out_root, altn)) // #2
+ v = out_root;
+ else // #1
+ {
+ dir_path src_root (root.src_path () / d);
+ src_root.normalize (); // No need to actualize (as above).
+ v = move (src_root);
+ }
+ }
+ else
+ remap_src_root (v); // Remap if inside old_src_root.
+
+ setup_root (rs, forwarded (root, out_root, v.as<dir_path> (), altn));
+ bootstrap_pre (rs, altn);
+ bootstrap_src (rs, altn);
+ // bootstrap_post() delayed until after create_bootstrap_outer().
+ }
+ else
+ {
+ altn = rs.root_extra->altn;
+
+ if (forwarded (root, rs.out_path (), rs.src_path (), altn))
+ rs.assign (var_forwarded) = true; // Only upgrade (see main()).
+ }
+
+ create_bootstrap_outer (rs);
+
+ if (!bstrapped)
+ bootstrap_post (rs);
+
+ // Check if we are strongly amalgamated by this outer root scope.
+ //
+ if (root.src_path ().sub (rs.src_path ()))
+ root.strong_ = rs.strong_scope (); // Itself or some outer scope.
+ }
+
+ scope&
+ create_bootstrap_inner (scope& root, const dir_path& out_base)
+ {
+ scope* r (&root);
+
+ if (auto l = root.vars[var_subprojects])
+ {
+ for (const auto& p: cast<subprojects> (l))
+ {
+ dir_path out_root (root.out_path () / p.second);
+
+ if (!out_base.empty () && !out_base.sub (out_root))
+ continue;
+
+ // The same logic to src_root as in create_bootstrap_outer().
+ //
+ scope& rs (create_root (root, out_root, dir_path ())->second);
+
+ optional<bool> altn;
+ if (!bootstrapped (rs))
+ {
+ bootstrap_out (rs, altn);
+
+ value& v (rs.assign (var_src_root));
+
+ if (!v)
+ {
+ v = is_src_root (out_root, altn)
+ ? out_root
+ : (root.src_path () / p.second);
+ }
+ else
+ remap_src_root (v); // Remap if inside old_src_root.
+
+ setup_root (rs, forwarded (root, out_root, v.as<dir_path> (), altn));
+ bootstrap_pre (rs, altn);
+ bootstrap_src (rs, altn);
+ bootstrap_post (rs);
+ }
+ else
+ {
+ altn = rs.root_extra->altn;
+ if (forwarded (root, rs.out_path (), rs.src_path (), altn))
+ rs.assign (var_forwarded) = true; // Only upgrade (see main()).
+ }
+
+ // Check if we strongly amalgamated this inner root scope.
+ //
+ if (rs.src_path ().sub (root.src_path ()))
+ rs.strong_ = root.strong_scope (); // Itself or some outer scope.
+
+ // See if there are more inner roots.
+ //
+ r = &create_bootstrap_inner (rs, out_base);
+
+ if (!out_base.empty ())
+ break; // We have found our subproject.
+ }
+ }
+
+ return *r;
+ }
+
+ void
+ load_root (scope& root)
+ {
+ tracer trace ("load_root");
+
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ // As an optimization, check if we have already loaded root.build. If
+ // that's the case, then we have already been called for this project.
+ //
+ path f (src_root / root.root_extra->root_file);
+
+ if (root.buildfiles.find (f) != root.buildfiles.end ())
+ return;
+
+ // First load outer roots, if any.
+ //
+ if (scope* rs = root.parent_scope ()->root_scope ())
+ load_root (*rs);
+
+ // Finish off loading bootstrapped modules.
+ //
+ for (auto& p: root.root_extra->modules)
+ {
+ module_state& s (p.second);
+
+ if (s.boot && s.first)
+ load_module (root, root, p.first, s.loc);
+ }
+
+ for (auto& p: root.root_extra->modules)
+ {
+ module_state& s (p.second);
+
+ if (s.boot && !s.first)
+ load_module (root, root, p.first, s.loc);
+ }
+
+ // Load hooks and root.build.
+ //
+ // We can load the pre hooks before finishing off loading the bootstrapped
+ // modules (which, in case of config would load config.build) or after and
+ // one can come up with a plausible use-case for either approach. Note,
+ // however, that one can probably achieve adequate pre-modules behavior
+ // with a post-bootstrap hook.
+ //
+ dir_path hd (out_root / root.root_extra->root_dir);
+ bool he (exists (hd));
+
+ if (he) source_hooks (root, hd, true /* pre */);
+ if (exists (f)) source_once (root, root, f);
+ if (he) source_hooks (root, hd, false /* pre */);
+ }
+
+ 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))
+ {
+ optional<bool> altn;
+ bootstrap_out (rs, altn);
+ setup_root (rs, forwarded);
+ bootstrap_pre (rs, altn);
+ bootstrap_src (rs, altn);
+ bootstrap_post (rs);
+ }
+ else
+ {
+ if (forwarded)
+ rs.assign (var_forwarded) = true; // Only upgrade (see main()).
+ }
+
+ if (load)
+ {
+ load_root (rs);
+ setup_base (i, out_root, src_root); // Setup as base.
+ }
+
+ return rs;
+ }
+
+ names
+ import (scope& ibase, name target, const location& loc)
+ {
+ tracer trace ("import");
+
+ l5 ([&]{trace << target << " from " << ibase;});
+
+ // If there is no project specified for this target, then our run will be
+ // short and sweet: we simply return it as empty-project-qualified and
+ // let someone else (e.g., a rule) take a stab at it.
+ //
+ if (target.unqualified ())
+ {
+ target.proj = project_name ();
+ return names {move (target)};
+ }
+
+ // Otherwise, get the project name and convert the target to unqualified.
+ //
+ project_name proj (move (*target.proj));
+ target.proj = nullopt;
+
+ scope& iroot (*ibase.root_scope ());
+
+ // Figure out this project's out_root.
+ //
+ dir_path out_root;
+
+ // First try the config.import.* mechanism. The idea is that if the user
+ // explicitly told us the project's location, then we should prefer that
+ // over anything that we may discover. In particular, we will prefer it
+ // over any bundled subprojects.
+ //
+ auto& vp (var_pool.rw (iroot));
+
+ for (;;) // Break-out loop.
+ {
+ string n ("config.import." + proj.variable ());
+
+ // config.import.<proj>
+ //
+ {
+ // Note: pattern-typed in context.cxx:reset() as an overridable
+ // variable of type abs_dir_path (path auto-completion).
+ //
+ const variable& var (vp.insert (n));
+
+ if (auto l = iroot[var])
+ {
+ out_root = cast<dir_path> (l); // Normalized and actualized.
+
+ // Mark as part of config.
+ //
+ if (config_save_variable != nullptr)
+ config_save_variable (iroot, var, 0 /* flags */);
+
+ // Empty config.import.* value means don't look in subprojects or
+ // amalgamations and go straight to the rule-specific import (e.g.,
+ // to use system-installed).
+ //
+ if (out_root.empty ())
+ {
+ target.proj = move (proj);
+ l5 ([&]{trace << "skipping " << target;});
+ return names {move (target)};
+ }
+
+ break;
+ }
+ }
+
+ // config.import.<proj>.<name>.<type>
+ // config.import.<proj>.<name>
+ //
+ // For example: config.import.build2.b.exe=/opt/build2/bin/b
+ //
+ if (!target.value.empty ())
+ {
+ auto lookup = [&iroot, &vp, &loc] (string name) -> path
+ {
+ // Note: pattern-typed in context.cxx:reset() as an overridable
+ // variable of type path.
+ //
+ const variable& var (vp.insert (move (name)));
+
+ path r;
+ if (auto l = iroot[var])
+ {
+ r = cast<path> (l);
+
+ if (r.empty ())
+ fail (loc) << "empty path in " << var.name;
+
+ if (config_save_variable != nullptr)
+ config_save_variable (iroot, var, 0 /* flags */);
+ }
+
+ return r;
+ };
+
+ // First try .<name>.<type>, then just .<name>.
+ //
+ path p;
+ if (target.typed ())
+ p = lookup (n + '.' + target.value + '.' + target.type);
+
+ if (p.empty ())
+ p = lookup (n + '.' + target.value);
+
+ if (!p.empty ())
+ {
+ // If the path is relative, then keep it project-qualified assuming
+ // import phase 2 knows what to do with it. Think:
+ //
+ // config.import.build2.b=b-boot
+ //
+ if (p.relative ())
+ target.proj = move (proj);
+
+ target.dir = p.directory ();
+ target.value = p.leaf ().string ();
+
+ return names {move (target)};
+ }
+ }
+
+ // Otherwise search subprojects, starting with our root and then trying
+ // outer roots for as long as we are inside an amalgamation.
+ //
+ for (scope* r (&iroot);; r = r->parent_scope ()->root_scope ())
+ {
+ l5 ([&]{trace << "looking in " << *r;});
+
+ // First check the amalgamation itself.
+ //
+ if (r != &iroot && cast<project_name> (r->vars[var_project]) == proj)
+ {
+ out_root = r->out_path ();
+ break;
+ }
+
+ if (auto l = r->vars[var_subprojects])
+ {
+ const auto& m (cast<subprojects> (l));
+ auto i (m.find (proj));
+
+ if (i != m.end ())
+ {
+ const dir_path& d ((*i).second);
+ out_root = r->out_path () / d;
+ break;
+ }
+ }
+
+ if (!r->vars[var_amalgamation])
+ break;
+ }
+
+ break;
+ }
+
+ // If we couldn't find the project, convert it back into qualified target
+ // and return to let someone else (e.g., a rule) take a stab at it.
+ //
+ if (out_root.empty ())
+ {
+ target.proj = move (proj);
+ l5 ([&]{trace << "postponing " << target;});
+ return names {move (target)};
+ }
+
+ // Bootstrap the imported root scope. This is pretty similar to what we do
+ // in main() except that here we don't try to guess src_root.
+ //
+ // The user can also specify the out_root of the amalgamation that contains
+ // our project. For now we only consider top-level sub-projects.
+ //
+ 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);
+ optional<bool> altn;
+ if (is_src_root (out_root, altn))
+ {
+ src_root = move (out_root);
+ out_root = bootstrap_fwd (src_root, altn);
+ fwd = (src_root != out_root);
+ }
+
+ for (const scope* proot (nullptr); ; proot = root)
+ {
+ bool top (proot == nullptr);
+
+ root = &create_root (iroot, out_root, src_root)->second;
+
+ bool bstrapped (bootstrapped (*root));
+
+ if (!bstrapped)
+ {
+ bootstrap_out (*root, altn);
+
+ // Check that the bootstrap process set src_root.
+ //
+ auto l (root->vars[*var_src_root]);
+ if (l)
+ {
+ // Note that unlike main() here we fail hard. The idea is that if
+ // the project we are importing is misconfigured, then it should be
+ // fixed first.
+ //
+ const dir_path& p (cast<dir_path> (l));
+
+ if (!src_root.empty () && p != src_root)
+ fail (loc) << "configured src_root " << p << " does not match "
+ << "discovered " << src_root;
+ }
+ else
+ fail (loc) << "unable to determine src_root for imported " << proj <<
+ info << "consider configuring " << out_root;
+
+ setup_root (*root,
+ (top
+ ? fwd
+ : forwarded (*proot, out_root, l->as<dir_path> (), altn)));
+
+ bootstrap_pre (*root, altn);
+ bootstrap_src (*root, altn);
+ if (!top)
+ bootstrap_post (*root);
+ }
+ else
+ {
+ altn = root->root_extra->altn;
+
+ if (src_root.empty ())
+ src_root = root->src_path ();
+
+ if (top ? fwd : forwarded (*proot, out_root, src_root, altn))
+ root->assign (var_forwarded) = true; // Only upgrade (see main()).
+ }
+
+ if (top)
+ {
+ create_bootstrap_outer (*root);
+
+ if (!bstrapped)
+ bootstrap_post (*root);
+ }
+
+ // Now we know this project's name as well as all its subprojects.
+ //
+ if (cast<project_name> (root->vars[var_project]) == proj)
+ break;
+
+ if (auto l = root->vars[var_subprojects])
+ {
+ const auto& m (cast<subprojects> (l));
+ auto i (m.find (proj));
+
+ if (i != m.end ())
+ {
+ const dir_path& d ((*i).second);
+ altn = nullopt;
+ out_root = root->out_path () / d;
+ src_root = is_src_root (out_root, altn) ? out_root : dir_path ();
+ continue;
+ }
+ }
+
+ fail (loc) << out_root << " is not out_root for " << proj;
+ }
+
+ // Load the imported root scope.
+ //
+ load_root (*root);
+
+ // Create a temporary scope so that the export stub does not mess
+ // up any of our variables.
+ //
+ temp_scope ts (ibase);
+
+ // "Pass" the imported project's roots to the stub.
+ //
+ ts.assign (var_out_root) = move (out_root);
+ ts.assign (var_src_root) = move (src_root);
+
+ // Also pass the target being imported in the import.target variable.
+ //
+ {
+ value& v (ts.assign (var_import_target));
+
+ if (!target.empty ()) // Otherwise leave NULL.
+ v = target; // Can't move (need for diagnostics below).
+ }
+
+ // Load the export stub. Note that it is loaded in the context
+ // of the importing project, not the imported one. The export
+ // stub will normally switch to the imported root scope at some
+ // point.
+ //
+ path es (root->src_path () / root->root_extra->export_file);
+
+ try
+ {
+ ifdstream ifs (es);
+
+ l5 ([&]{trace << "importing " << es;});
+
+ // @@ Should we verify these are all unqualified names? Or maybe
+ // there is a use-case for the export stub to return a qualified
+ // name?
+ //
+ parser p;
+ names v (p.parse_export_stub (ifs, es, iroot, ts));
+
+ // If there were no export directive executed in an export stub, assume
+ // the target is not exported.
+ //
+ if (v.empty () && !target.empty ())
+ fail (loc) << "target " << target << " is not exported by project "
+ << proj;
+
+ return v;
+ }
+ catch (const io_error& e)
+ {
+ fail (loc) << "unable to read buildfile " << es << ": " << e;
+ }
+
+ return names (); // Never reached.
+ }
+
+ const target*
+ import (const prerequisite_key& pk, bool existing)
+ {
+ tracer trace ("import");
+
+ assert (pk.proj);
+ const project_name& proj (*pk.proj);
+
+ // Target type-specific search.
+ //
+ const target_key& tk (pk.tk);
+ const target_type& tt (*tk.type);
+
+ // Try to find the executable in PATH (or CWD if relative).
+ //
+ if (tt.is_a<exe> ())
+ {
+ path n (*tk.dir);
+ n /= *tk.name;
+ if (tk.ext)
+ {
+ n += '.';
+ n += *tk.ext;
+ }
+
+ // Only search in PATH (or CWD).
+ //
+ process_path pp (process::try_path_search (n, true, dir_path (), true));
+
+ if (!pp.empty ())
+ {
+ path& p (pp.effect);
+ assert (!p.empty ()); // We searched for a simple name.
+
+ const exe* t (
+ !existing
+ ? &targets.insert<exe> (tt,
+ p.directory (),
+ dir_path (), // No out (out of project).
+ p.leaf ().base ().string (),
+ p.extension (), // Always specified.
+ trace)
+ : targets.find<exe> (tt,
+ p.directory (),
+ dir_path (),
+ p.leaf ().base ().string (),
+ p.extension (),
+ trace));
+
+ if (t != nullptr)
+ {
+ if (!existing)
+ t->path (move (p));
+ else
+ assert (t->path () == p);
+
+ return t;
+ }
+ }
+ }
+
+ if (existing)
+ return nullptr;
+
+ // @@ We no longer have location. This is especially bad for the
+ // empty case, i.e., where do I need to specify the project
+ // name)? Looks like the only way to do this is to keep location
+ // in name and then in prerequisite. Perhaps one day...
+ //
+ diag_record dr;
+ dr << fail << "unable to import target " << pk;
+
+ if (proj.empty ())
+ dr << info << "consider adding its installation location" <<
+ info << "or explicitly specify its project name";
+ else
+ dr << info << "use config.import." << proj.variable ()
+ << " command line variable to specify its project out_root";
+
+ dr << endf;
+ }
+}