diff options
Diffstat (limited to 'build/file.cxx')
-rw-r--r-- | build/file.cxx | 980 |
1 files changed, 0 insertions, 980 deletions
diff --git a/build/file.cxx b/build/file.cxx deleted file mode 100644 index b4b1b77..0000000 --- a/build/file.cxx +++ /dev/null @@ -1,980 +0,0 @@ -// file : build/file.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/file> - -#include <fstream> -#include <utility> // move() -#include <system_error> - -#include <butl/filesystem> - -#include <build/scope> -#include <build/context> -#include <build/parser> -#include <build/prerequisite> -#include <build/diagnostics> - -#include <build/token> -#include <build/lexer> - -using namespace std; -using namespace butl; - -namespace build -{ - const dir_path build_dir ("build"); - const dir_path bootstrap_dir ("build/bootstrap"); - - const path root_file ("build/root.build"); - const path bootstrap_file ("build/bootstrap.build"); - const path src_root_file ("build/bootstrap/src-root.build"); - - bool - is_src_root (const dir_path& d) - { - // @@ Can we have root without bootstrap? I don't think so. - // - return file_exists (d / bootstrap_file) || file_exists (d / root_file); - } - - bool - is_out_root (const dir_path& d) - { - return file_exists (d / src_root_file); - } - - dir_path - find_src_root (const dir_path& b) - { - for (dir_path d (b); !d.root () && d != home; d = d.directory ()) - { - if (is_src_root (d)) - return d; - } - - return dir_path (); - } - - dir_path - find_out_root (const dir_path& b, bool* src) - { - for (dir_path d (b); !d.root () && d != home; d = d.directory ()) - { - bool s (false); - if ((s = is_src_root (d)) || is_out_root (d)) // Order is important! - { - if (src != nullptr) - *src = s; - - return d; - } - } - - return dir_path (); - } - - static void - source (const path& bf, scope& root, scope& base, bool boot) - { - tracer trace ("source"); - - try - { - ifstream ifs (bf.string ()); - if (!ifs.is_open ()) - fail << "unable to open " << bf; - - ifs.exceptions (ifstream::failbit | ifstream::badbit); - - level5 ([&]{trace << "sourcing " << bf;}); - - parser p (boot); - p.parse_buildfile (ifs, bf, root, base); - } - catch (const ifstream::failure&) - { - fail << "unable to read buildfile " << bf; - } - } - - void - source (const path& bf, scope& root, scope& base) - { - return source (bf, root, base, false); - } - - void - source_once (const path& bf, scope& root, scope& base, scope& once) - { - tracer trace ("source_once"); - - if (!once.buildfiles.insert (bf).second) - { - level5 ([&]{trace << "skipping already sourced " << bf;}); - return; - } - - source (bf, root, base); - } - - scope& - create_root (const dir_path& out_root, const dir_path& src_root) - { - auto i (scopes.insert (out_root, nullptr, true, true)); - scope& rs (*i->second); - - // Set out_path. src_path is set in setup_root() below. - // - if (rs.out_path_ != &i->first) - { - assert (rs.out_path_ == nullptr); - rs.out_path_ = &i->first; - } - - // 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. - // - if (rs.meta_operations.empty ()) - { - rs.meta_operations.insert (perform_id, perform); - - rs.operations.insert (default_id, default_); - rs.operations.insert (update_id, update); - rs.operations.insert (clean_id, clean); - } - - // If this is already a root scope, verify that things are - // consistent. - // - { - value& v (rs.assign ("out_root")); - - if (!v) - v = out_root; - else - { - const dir_path& p (as<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 ("src_root")); - - if (!v) - v = src_root; - else - { - const dir_path& p (as<dir_path> (v)); - - if (p != src_root) - fail << "new src_root " << src_root << " does not match " - << "existing " << p; - } - } - - return rs; - } - - void - setup_root (scope& s) - { - value& v (s.assign ("src_root")); - assert (v); - - // Register and set src_path. - // - if (s.src_path_ == nullptr) - s.src_path_ = &scopes.insert (as<dir_path> (v), &s, false, true)->first; - } - - scope& - setup_base (scope_map::iterator i, - const dir_path& out_base, - const dir_path& src_base) - { - scope& s (*i->second); - - // Set src/out_path. The key (i->first) can be either out_base - // or src_base. - // - if (s.out_path_ == nullptr) - { - s.out_path_ = - i->first == out_base - ? &i->first - : &scopes.insert (out_base, &s, true, false)->first; - } - - if (s.src_path_ == nullptr) - { - s.src_path_ = - i->first == src_base - ? &i->first - : &scopes.insert (src_base, &s, false, false)->first; - } - - // Set src/out_base variables. - // - { - value& v (s.assign ("out_base")); - - if (!v) - v = out_base; - else - assert (as<dir_path> (v) == out_base); - } - - { - value& v (s.assign ("src_base")); - - if (!v) - v = src_base; - else - assert (as<dir_path> (v) == src_base); - } - - return s; - } - - void - bootstrap_out (scope& root) - { - path bf (root.out_path () / path ("build/bootstrap/src-root.build")); - - if (!file_exists (bf)) - return; - - //@@ TODO: if bootstrap files can source other bootstrap files - // (the 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). - // - source_once (bf, root, root); - } - - // 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. - // - static value - extract_variable (const path& bf, const char* var) - { - try - { - ifstream ifs (bf.string ()); - if (!ifs.is_open ()) - fail << "unable to open " << bf; - - ifs.exceptions (ifstream::failbit | ifstream::badbit); - - path rbf (diag_relative (bf)); - - lexer lex (ifs, rbf.string ()); - token t (lex.next ()); - token_type tt; - - if (t.type != token_type::name || t.value != var || - ((tt = lex.next ().type) != token_type::equal && - tt != token_type::equal_plus && - tt != token_type::plus_equal)) - { - error << "variable '" << var << "' expected as first line in " << rbf; - throw failed (); // Suppress "used uninitialized" warning. - } - - parser p; - temp_scope tmp (*global_scope); - p.parse_variable (lex, tmp, t.value, tt); - - auto l (tmp.vars[var]); - assert (l.defined ()); - value& v (*l); - return move (v); // Steal the value, the scope is going away. - } - catch (const ifstream::failure&) - { - fail << "unable to read buildfile " << bf; - } - - return value (); // Never reaches. - } - - // Extract the project name from bootstrap.build. - // - static string - find_project_name (const dir_path& out_root, - const dir_path& fallback_src_root, - bool* src_hint = nullptr) - { - tracer trace ("find_project_name"); - - // 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. - // - const dir_path* src_root; - value src_root_v; // Need it to live until the end. - - if (src_hint != nullptr ? *src_hint : is_src_root (out_root)) - src_root = &out_root; - else - { - path f (out_root / src_root_file); - - if (!fallback_src_root.empty () && !file_exists (f)) - src_root = &fallback_src_root; - else - { - src_root_v = extract_variable (f, "src_root"); - src_root = &as<dir_path> (src_root_v); - level5 ([&]{trace << "extracted src_root " << *src_root << " for " - << out_root;}); - } - } - - string name; - { - value v (extract_variable (*src_root / bootstrap_file, "project")); - name = move (as<string> (v)); - } - - level5 ([&]{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. - // Otherwise, scan the subdirectory recursively. - // - static void - find_subprojects (subprojects& sps, - const dir_path& d, - const dir_path& root, - bool out) - { - tracer trace ("find_subprojects"); - - for (const dir_entry& de: dir_iterator (d)) - { - // 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. - // - try - { - if (de.type () != entry_type::directory) - continue; - } - catch (const system_error& e) - { - continue; - } - - dir_path sd (d / path_cast<dir_path> (de.path ())); - - bool src (false); - if (!((out && is_out_root (sd)) || (src = is_src_root (sd)))) - { - find_subprojects (sps, sd, root, out); - continue; - } - - // Calculate relative subdirectory for this subproject. - // - dir_path dir (sd.leaf (root)); - level5 ([&]{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. - // - 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 - // 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 trailing '/' to it. - // - if (name.empty ()) - name = dir.posix_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; - - level6 ([&]{trace << "skipping duplicate";}); - } - } - } - - bool - bootstrap_src (scope& root) - { - tracer trace ("bootstrap_src"); - - bool r (false); - - const dir_path& out_root (root.out_path ()); - const dir_path& src_root (root.src_path ()); - - path bf (src_root / path ("build/bootstrap.build")); - - if (file_exists (bf)) - { - // 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 (bf).second) - source (bf, root, root, true); - else - level5 ([&]{trace << "skipping already sourced " << bf;}); - - 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. - // - { - auto rp (root.vars.assign ("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 (as<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. - // - level5 ([&]{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. - // - const dir_path& ad (find_out_root (out_root.directory ())); - - if (!ad.empty ()) - { - dir_path rd (ad.relative (out_root)); - level5 ([&]{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 directory[=project] 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. - // - { - const variable& var (var_pool.find ("subprojects")); - auto rp (root.vars.assign(var)); // 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 (dir_exists (out_root)) - find_subprojects (sps, out_root, out_root, true); - - if (out_root != 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 - { - // Pre-scan the value and convert it to the "canonical" form, - // that is, a list of simple=dir pairs. - // - for (auto i (v.data_.begin ()); i != v.data_.end (); ++i) - { - bool p (i->pair != '\0'); - - if (p) - { - // Project name. - // - if (!assign<string> (*i) || as<string> (*i).empty ()) - fail << "expected project name instead of '" << *i << "' in " - << "the subprojects variable"; - - ++i; // Got to have the second half of the pair. - } - - if (!assign<dir_path> (*i)) - fail << "expected directory instead of '" << *i << "' in the " - << "subprojects variable"; - - auto& d (as<dir_path> (*i)); - - // Figure out the project name if the user didn't specify one. - // - if (!p) - { - // Pass fallback src_root since this is a subproject that - // was specified by the user so it is most likely in our - // src. - // - string n (find_project_name (out_root / d, src_root / d)); - - // See find_subprojects() for details on unnamed projects. - // - if (n.empty ()) - n = d.posix_string () + '/'; - - i = v.data_.emplace (i, move (n)); - - i->pair = '='; - ++i; - } - } - - // Make it of the map type. - // - assign<subprojects> (v, var); - } - } - } - - return r; - } - - void - create_bootstrap_outer (scope& root) - { - auto l (root.vars["amalgamation"]); - - if (!l) - return; - - const dir_path& d (as<dir_path> (*l)); - dir_path out_root (root.out_path () / d); - out_root.normalize (); - - // 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 (out_root, dir_path ())); - bootstrap_out (rs); // #3 happens here, if at all. - - value& v (rs.assign ("src_root")); - - if (!v) - { - if (is_src_root (out_root)) // #2 - v = out_root; - else // #1 - { - dir_path src_root (root.src_path () / d); - src_root.normalize (); - v = move (src_root); - } - } - - setup_root (rs); - - bootstrap_src (rs); - create_bootstrap_outer (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) - { - if (auto l = root.vars["subprojects"]) - { - for (const name& n: *l) - { - if (n.pair != '\0') - continue; // Skip project names. - - dir_path out_root (root.out_path () / n.dir); - - if (!out_base.sub (out_root)) - continue; - - // The same logic to src_root as in create_bootstrap_outer(). - // - scope& rs (create_root (out_root, dir_path ())); - bootstrap_out (rs); - - value& v (rs.assign ("src_root")); - - if (!v) - v = is_src_root (out_root) - ? out_root - : (root.src_path () / n.dir); - - setup_root (rs); - - bootstrap_src (rs); - - // 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. - // - return create_bootstrap_inner (rs, out_base); - } - } - - return root; - } - - void - load_root_pre (scope& root) - { - tracer trace ("root_pre"); - - // First load outer roots, if any. - // - if (scope* rs = root.parent_scope ()->root_scope ()) - load_root_pre (*rs); - - // Finish off loading bootstrapped modules. - // - for (auto& p: root.modules) - { - const string& n (p.first); - module_state& s (p.second); - - if (s.boot) - { - load_module (false, n, root, root, s.loc); - assert (!s.boot); - } - } - - // Load root.build. - // - path bf (root.src_path () / path ("build/root.build")); - - if (file_exists (bf)) - source_once (bf, root, root); - } - - names - import (scope& ibase, name target, const location& loc) - { - tracer trace ("import"); - - // 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_pool.find (""); - return names {move (target)}; - } - - // Otherwise, get the project name and convert the target to - // unqualified. - // - const string& project (*target.proj); - target.proj = nullptr; - - scope& iroot (*ibase.root_scope ()); - - // Figure out this project's out_root. - // - dir_path out_root; - dir_path fallback_src_root; // We have seen this already, haven't we..? - - // First 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 ()) - { - // First check the amalgamation itself. - // - if (r != &iroot && as<string> (*r->vars["project"]) == project) - { - out_root = r->out_path (); - fallback_src_root = r->src_path (); - break; - } - - if (auto l = r->vars["subprojects"]) - { - const auto& m (as<subprojects> (*l)); - auto i (m.find (project)); - - if (i != m.end ()) - { - const dir_path& d ((*i).second); - out_root = r->out_path () / d; - fallback_src_root = r->src_path () / d; - break; - } - } - - if (!r->vars["amalgamation"]) - break; - } - - // Then try the config.import.* mechanism. - // - if (out_root.empty ()) - { - const variable& var ( - var_pool.find ("config.import." + project, dir_path_type)); - - if (auto l = iroot[var]) - { - out_root = as<dir_path> (*l); - - if (l.belongs (*global_scope)) // A value from command line. - { - // Process the path by making it absolute and normalized. - // - if (out_root.relative ()) - out_root = work / out_root; - - out_root.normalize (); - - // Set on our root scope (part of our configuration). - // - iroot.assign (var) = out_root; - - // Also update the command-line value. This is necessary to avoid - // a warning issued by the config module about global/root scope - // value mismatch. Not very clean. - // - dir_path& d (as<dir_path> (const_cast<value&> (*l))); - if (d != out_root) - d = out_root; - } - } - else - { - // If we can't find the project, convert it back into qualified - // target and return to let someone else (e.g., a rule) to take - // a stab at it. - // - target.proj = &project; - level5 ([&]{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. - // - dir_path src_root (is_src_root (out_root) ? out_root : dir_path ()); - scope& root (create_root (out_root, src_root)); - - bootstrap_out (root); - - // Check that the bootstrap process set src_root. - // - if (auto l = root.vars["src_root"]) - { - const dir_path& p (as<dir_path> (*l)); - - if (!src_root.empty () && p != src_root) - fail (loc) << "bootstrapped src_root " << p << " does not match " - << "discovered " << src_root; - } - // Otherwise, use fallback if available. - // - else if (!fallback_src_root.empty ()) - { - value& v (root.assign ("src_root")); - v = move (fallback_src_root); - } - else - fail (loc) << "unable to determine src_root for imported " << project << - info << "consider configuring " << out_root; - - setup_root (root); - - bootstrap_src (root); - - // Bootstrap outer roots if any. Loading will be done by - // load_root_pre() below. - // - create_bootstrap_outer (root); - - // Load the imported root scope. - // - load_root_pre (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 ("out_root") = move (out_root); - ts.assign ("src_root") = move (src_root); - - // Also pass the target being imported. - // - { - value& v (ts.assign ("target")); - - if (!target.empty ()) // Otherwise leave NULL. - v = move (target); - } - - // 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 () / path ("build/export.build")); - - try - { - ifstream ifs (es.string ()); - if (!ifs.is_open ()) - fail (loc) << "unable to open " << es; - - ifs.exceptions (ifstream::failbit | ifstream::badbit); - - level5 ([&]{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; - return p.parse_export_stub (ifs, es, iroot, ts); - } - catch (const ifstream::failure&) - { - fail (loc) << "unable to read buildfile " << es; - } - - return names (); // Never reached. - } - - target& - import (const prerequisite_key& pk) - { - assert (pk.proj != nullptr); - const string& p (*pk.proj); - - // @@ 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... - // - if (!p.empty ()) - fail << "unable to import target " << pk << - info << "consider explicitly specifying its project out_root via the " - << "config.import." << p << " command line variable"; - else - fail << "unable to import target " << pk << - info << "consider adding its installation location" << - info << "or explicitly specifying its project name"; - - throw failed (); // No return. - } -} |