// file : build/b.cxx -*- C++ -*- // copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC // license : MIT; see accompanying LICENSE file #include // tzset() #include // strerror() #include // getenv() #include // getuid() #include // uid_t #include // struct passwd, getpwuid() #include #include #include #include #include // make_move_iterator() #include //@@ TMP, for dump() #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; namespace build { void dump () { cout << endl; for (const auto& pt: targets) { target& t (*pt); cout << t << ':'; for (const auto& p: t.prerequisites) { cout << ' ' << p; } cout << endl; } cout << endl; } inline bool is_src_root (const path& d) { return file_exists (d / path ("build/bootstrap.build")) || file_exists (d / path ("build/root.build")); } inline bool is_out_root (const path& d) { return file_exists (d / path ("build/bootstrap/src-root.build")); } // Given an src_base directory, look for the project's src_root // based on the presence of known special files. Return empty // path if not found. // path find_src_root (const path& b) { for (path d (b); !d.root () && d != home; d = d.directory ()) { if (is_src_root (d)) return d; } return path (); } // The same but for out. Note that we also check whether a // directory happens to be src_root, in case this is an in- // tree build. // path find_out_root (const path& b) { for (path d (b); !d.root () && d != home; d = d.directory ()) { if (is_out_root (d) || is_src_root (d)) return d; } return path (); } void load (const path& buildfile, scope& root, scope& base) { ifstream ifs (buildfile.string ()); if (!ifs.is_open ()) fail << "unable to open " << buildfile; ifs.exceptions (ifstream::failbit | ifstream::badbit); parser p; try { p.parse_buildfile (ifs, buildfile, root, base); } catch (const std::ios_base::failure&) { fail << "failed to read from " << buildfile; } } void bootstrap_out (scope& root) { tracer trace ("bootstrap_out"); path bf (root.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. // level4 ([&]{trace << "loading " << bf;}); load (bf, root, root); } void bootstrap_src (scope& root, const path& src_root) { tracer trace ("bootstrap_src"); path bf (src_root / path ("build/bootstrap.build")); if (!file_exists (bf)) return; level4 ([&]{trace << "loading " << bf;}); load (bf, root, root); } void root_pre (scope& root, const path& src_root) { tracer trace ("root_pre"); path bf (src_root / path ("build/root.build")); if (!file_exists (bf)) return; level4 ([&]{trace << "loading " << bf;}); load (bf, root, root); } } #include #include #include #include using namespace build; int main (int argc, char* argv[]) { try { tracer trace ("main"); // Initialize time conversion data that is used by localtime_r(). // tzset (); // Trace verbosity. // verb = 5; // Register modules. // modules["config"] = &config::load; // Register target types. // target_types.insert (file::static_type); target_types.insert (dir::static_type); target_types.insert (fsdir::static_type); target_types.insert (exe::static_type); target_types.insert (obj::static_type); target_types.insert (cxx::h::static_type); target_types.insert (cxx::c::static_type); target_types.insert (cxx::cxx::static_type); target_types.insert (cxx::hxx::static_type); target_types.insert (cxx::ixx::static_type); target_types.insert (cxx::txx::static_type); // Register rules. // cxx::link cxx_link; rules[update_id][typeid (exe)].emplace ("cxx.gnu.link", cxx_link); rules[clean_id][typeid (exe)].emplace ("cxx.gnu.link", cxx_link); cxx::compile cxx_compile; rules[update_id][typeid (obj)].emplace ("cxx.gnu.compile", cxx_compile); rules[clean_id][typeid (obj)].emplace ("cxx.gnu.compile", cxx_compile); dir_rule dir_r; rules[update_id][typeid (dir)].emplace ("dir", dir_r); rules[clean_id][typeid (dir)].emplace ("dir", dir_r); fsdir_rule fsdir_r; rules[update_id][typeid (fsdir)].emplace ("fsdir", fsdir_r); rules[clean_id][typeid (fsdir)].emplace ("fsdir", fsdir_r); path_rule path_r; rules[update_id][typeid (path_target)].emplace ("path", path_r); rules[clean_id][typeid (path_target)].emplace ("path", path_r); // Figure out work and home directories. // work = path::current (); if (const char* h = getenv ("HOME")) home = path (h); else { struct passwd* pw (getpwuid (getuid ())); if (pw == nullptr) { const char* msg (strerror (errno)); fail << "unable to determine home directory: " << msg; } home = path (pw->pw_dir); } if (verb >= 4) { trace << "work dir: " << work.string (); trace << "home dir: " << home.string (); } // Create root scope. For Win32 we use the empty path since there // is no such "real" root path. On POSIX, however, this is a real // path. See the comment in for details. // #ifdef _WIN32 root_scope = &scopes[path ()]; #else root_scope = &scopes[path ("/")]; #endif root_scope->variables["work"] = work; root_scope->variables["home"] = home; // Parse the buildspec. // buildspec bspec; { // Merge all the individual buildspec arguments into a single // string. Instead, we could also parse them individually ( // and merge the result). The benefit of doing it this way // is potentially better diagnostics (i.e., we could have // used , to give the idea about // which argument is invalid). // string s; for (int i (1); i != argc;) { s += argv[i]; if (++i != argc) s += ' '; } istringstream is (s); is.exceptions (ifstream::failbit | ifstream::badbit); parser p; try { bspec = p.parse_buildspec (is, ""); } catch (const std::ios_base::failure&) { fail << "failed to parse buildspec string"; } } level4 ([&]{trace << "buildspec: " << bspec;}); // Load all the buildfiles. // if (bspec.empty ()) bspec.push_back (metaopspec ()); // Default meta-operation. for (metaopspec& ms: bspec) { if (ms.empty ()) ms.push_back (opspec ()); // Default operation. meta_operation_id mid (0); // Not yet translated. const meta_operation_info* mif (nullptr); for (opspec& os: ms) { const location l ("", 1, 0); //@@ TODO if (os.empty ()) // Default target: dir{}. // os.push_back (targetspec (name ("dir", path (), string ()))); operation_id oid (0); // Not yet translated. const operation_info* oif (nullptr); action act (0, 0); // Not yet initialized. // We do meta-operation and operation batches sequentially (no // parallelism). But multiple targets in an operation batch // can be done in parallel. // vector> tgs; tgs.reserve (os.size ()); for (targetspec& ts: os) { 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. // path out_base (tn.dir); if (out_base.empty ()) { // See if there is a directory part in value. We cannot // assume it is a valid filesystem name so we will have // to do the splitting manually. // path::size_type i (path::traits::rfind_separator (tn.value)); if (i != string::npos) out_base = path (tn.value, i != 0 ? i : 1); // Special case: "/". } if (out_base.relative ()) out_base = work / out_base; out_base.normalize (); // The order in which we determine the roots depends on whether // src_base was specified explicitly. There will also be a few // cases where we are guessing things that can turn out wrong. // Keep track of that so that we can issue more extensive // diagnostics for such cases. // bool guessing (false); path src_root; path out_root; path& src_base (ts.src_base); // Update it in buildspec. if (!src_base.empty ()) { if (src_base.relative ()) src_base = work / src_base; src_base.normalize (); // If the src_base was explicitly specified, search for src_root. // src_root = find_src_root (src_base); // If not found, assume this is a simple project with src_root // being the same as src_base. // if (src_root.empty ()) { src_root = src_base; out_root = out_base; } else // Calculate out_root based on src_root/src_base. // out_root = out_base.directory (src_base.leaf (src_root)); } else { // If no src_base was explicitly specified, search for out_root. // out_root = find_out_root (out_base); // If not found (i.e., we have no idea where the roots are), // then this can mean two things: an in-tree build of a // simple project or a fresh out-of-tree build. Assume this // is the former and set out_root to out_base. If we are // wrong (most likely) and this is the latter, then things // will go badly when we try to load the buildfile. // if (out_root.empty ()) { src_root = src_base = out_root = out_base; guessing = true; } } // Now we know out_root and, if it was explicitly specified, // src_root. The next step is to create the root scope and // load the out_root bootstrap files, if any. Note that we // might already have done this as a result of one of the // preceding target processing. // auto rsp (scopes.insert (out_root)); scope& rs (rsp.first); if (rsp.second) { // Enter built-in meta-operation and operation names. Note that // the order of registration should match the id constants; see // for details. Loading of modules (via the src_root // bootstrap; see below) can result in additional names being // added. // rs.meta_operations.insert (perform); rs.operations.insert (default_); rs.operations.insert (update); rs.operations.insert (clean); rs.variables["out_root"] = out_root; bootstrap_out (rs); } // See if the bootstrap process set src_root. // { auto v (rs.variables["src_root"]); if (v) { // If we also have src_root specified by the user, make // sure they match. // const path& p (v.as ()); if (src_root.empty ()) src_root = p; else if (src_root != p) fail << "bootstrapped src_root " << p << " does not match " << "specified " << src_root; } else { // Bootstrap didn't produce src_root. // if (src_root.empty ()) { // If it also wasn't explicitly specified, see if it is // the same as out_root. // if (is_src_root (out_root)) src_root = out_root; else { // If not, then assume we are running from src_base // and calculate src_root based on out_root/out_base. // Note that this is different from the above case // were we couldn't determine either root. // src_base = work; src_root = src_base.directory (out_base.leaf (out_root)); guessing = true; } } v = src_root; } } // At this stage we should have both roots and out_base figured // out. If src_base is still undetermined, calculate it. // if (src_base.empty ()) src_base = src_root / out_base.leaf (out_root); // Now that we have src_root, load the src_root bootstrap file, // if there is one. Again, we might have already done that. // if (rsp.second) bootstrap_src (rs, src_root); // The src bootstrap should have loaded all the modules that // may add new meta/operations. So at this stage they should // all be known. We store the combined action id in uint8_t; // see for details. // assert (rs.operations.size () <= 128); assert (rs.meta_operations.size () <= 128); // Since we now know all the names of meta-operations and // operations, "lift" names that we assumed (from buildspec // syntax) were operations but are actually meta-operations. // Also convert empty names (which means they weren't explicitly // specified) to the defaults and verify that all the names are // known. // { const auto& mn (ms.name); const auto& on (os.name); meta_operation_id m (0); operation_id o (0); if (!on.empty ()) { m = rs.meta_operations.find (on); if (m != 0) { if (!mn.empty ()) fail (l) << "nested meta-operation " << mn << '(' << on << ')'; } else { o = rs.operations.find (on); if (o == 0) fail (l) << "unknown operation " << on; } } if (!mn.empty ()) { m = rs.meta_operations.find (mn); if (m == 0) fail (l) << "unknown meta-operation " << mn; } // The default meta-operation is perform. The default // operation is assigned by the meta-operation below. // if (m == 0) m = perform_id; // If this is the first target in the meta-operation batch, // then set the batch meta-operation id. // if (mid == 0) { //@@ meta-operation batch_pre // mid = m; mif = &rs.meta_operations[m].get (); level4 ([&]{trace << "start meta-operation batch " << mif->name << ", id " << static_cast (mid);}); } // // Otherwise, check that all the targets in a meta-operation // batch have the same meta-operation implementation. // else { if (mid > rs.meta_operations.size () || // Not a valid index. mif != &rs.meta_operations[mid].get ()) // Not the same impl. fail (l) << "different meta-operation implementations " << "in a meta-operation batch"; } // If this is the first target in the operation batch, then set // the batch operation id. // if (oid == 0) { //@@ operation batch_pre; translate operation (pass // default_id for 0). if (o == 0) o = update_id; // @@ TMP; if no batch_pre oid = o; oif = &rs.operations[o].get (); act = action (mid, oid); current_mode = oif->mode; current_rules = &rules[o]; level4 ([&]{trace << "start operation batch " << oif->name << ", id " << static_cast (oid);}); } // // Similar to meta-operations, check that all the targets in // an operation batch have the same operation implementation. // else { if (oid > rs.operations.size () || // Not a valid index. oif != &rs.operations[oid].get ()) // Not the same impl. fail (l) << "different operation implementations " << "in an operation batch"; } } //@@ target pre_load; may request skipping loading if (verb >= 4) { trace << "target " << tn << ':'; trace << " out_base: " << out_base.string (); trace << " src_base: " << src_base.string (); trace << " out_root: " << out_root.string (); trace << " src_root: " << src_root.string (); } // Load project's root[-pre].build. // if (rsp.second) root_pre (rs, src_root); // Create the base scope. Note that its existence doesn't // mean it was already processed as a base scope; it can // be the same as root. // scope& bs (scopes[out_base]); bs.variables["out_base"] = out_base; bs.variables["src_base"] = src_base; // Parse the buildfile. // path bf (src_base / path ("buildfile")); // Check if this buildfile has already been loaded. // if (rs.buildfiles.insert (bf).second) { level4 ([&]{trace << "loading " << bf;}); ifstream ifs (bf.string ()); if (!ifs.is_open ()) { diag_record dr; dr << fail << "unable to open " << bf; if (guessing) dr << info << "consider explicitly specifying src_base " << "for " << tn; } ifs.exceptions (ifstream::failbit | ifstream::badbit); parser p; try { p.parse_buildfile (ifs, bf, rs, bs); } catch (const std::ios_base::failure&) { fail << "failed to read from " << bf; } } else level4 ([&]{trace << "skipping already loaded " << bf;}); //@@ target post_load // Next resolve and match the target. We don't want to start // building before we know how to for all the targets in this // operation batch. // { const string* e; const target_type* ti (target_types.find (tn, e)); if (ti == nullptr) fail (l) << "unknown target type " << tn.type; // If the directory is relative, assume it is relative to work // (must be consistent with how we derived out_base above). // path& d (tn.dir); if (d.relative ()) d = work / d; d.normalize (); target_set::key tk {ti, &d, &tn.value, &e}; auto i (targets.find (tk, trace)); if (i == targets.end ()) fail (l) << "unknown target " << tk; target& t (**i); //@@ dump level4 ([&]{trace << "matching target " << t;}); match (act, t); //@@ dump tgs.push_back (t); } } // Now build collecting postponed targets (to be re-examined). // vector> psp; for (target& t: tgs) { level4 ([&]{trace << "executing target " << t;}); switch (execute (act, t)) { case target_state::postponed: { info << "target " << t << " is postponed"; psp.push_back (t); break; } case target_state::unchanged: { info << "target " << t << " is unchanged"; break; } case target_state::changed: break; case target_state::failed: //@@ This could probably happen in a parallel build. default: assert (false); } } // Re-examine postponed targets. // for (target& t: psp) { switch (t.state) { case target_state::postponed: { info << "unable to execute target " << t << " at this time"; break; } case target_state::unchanged: { info << "target " << t << " is unchanged"; break; } case target_state::unknown: // Assume something was done to it. case target_state::changed: break; case target_state::failed: //@@ This could probably happen in a parallel build. default: assert (false); } } level4 ([&]{trace << "end operation batch " << oif->name << ", id " << static_cast (oid);}); //@@ operation batch_post } level4 ([&]{trace << "end meta-operation batch " << mif->name << ", id " << static_cast (mid);}); //@@ meta-operation batch_post } } catch (const failed&) { return 1; // Diagnostics has already been issued. } catch (const std::exception& e) { error << e.what (); return 1; } }