From cd75e06a87aa74aa6968113107afa53d401d20bc Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 25 Mar 2015 14:48:36 +0200 Subject: Configure/disfigure src_root saving/removing support; fsdir{} injection We can now build out-of-tree. --- build/algorithm | 7 ++ build/algorithm.cxx | 69 +++++++++-------- build/b.cxx | 189 +++++++++++++++++++++++++++++++-------------- build/bootstrap.build | 3 - build/config/module.cxx | 10 ++- build/config/operation.cxx | 166 ++++++++++++++++++++++++++++++++++++--- build/context | 39 ++++++++++ build/context.cxx | 57 ++++++++++++++ build/context.txx | 100 ++++++++++++++++++++++++ build/cxx/rule.cxx | 27 +++++-- build/dump | 3 + build/dump.cxx | 23 ++++++ build/filesystem | 20 +++-- build/filesystem.cxx | 20 ++++- build/operation | 3 +- build/operation.cxx | 7 +- build/parser.cxx | 9 ++- build/path | 15 ++-- build/path.ixx | 37 +++++++++ build/path.txx | 12 +-- build/root.build | 4 +- build/rule.cxx | 99 ++++++++---------------- build/scope | 15 ++-- build/target | 3 + 24 files changed, 720 insertions(+), 217 deletions(-) create mode 100644 build/context.txx diff --git a/build/algorithm b/build/algorithm index a272a4c..c056d57 100644 --- a/build/algorithm +++ b/build/algorithm @@ -37,6 +37,13 @@ namespace build void search_and_match (action, target&, const path& dir); + // Inject dependency on the parent directory fsdir{}, unless it is + // the project's out_root (or is outside of any project; think, for + // example, install directories). + // + void + inject_parent_fsdir (action, target&); + // Execute the action on target, assuming a rule has been matched // and the recipe for this action has been set. // diff --git a/build/algorithm.cxx b/build/algorithm.cxx index 109f456..91ece49 100644 --- a/build/algorithm.cxx +++ b/build/algorithm.cxx @@ -7,7 +7,6 @@ #include // unique_ptr #include // move #include -#include #include #include @@ -16,7 +15,6 @@ #include #include #include -#include #include using namespace std; @@ -168,6 +166,41 @@ namespace build } } + void + inject_parent_fsdir (action a, target& t) + { + tracer trace ("inject_parent_fsdir"); + + scope& s (scopes.find (t.dir)); + + if (auto v = s["out_root"]) // Could be outside any project. + { + const path& out_root (v.as ()); + + // If t is a directory (name is empty), say foo/bar/, then + // t is bar and its parent directory is foo/. + // + const path& d (t.name.empty () ? t.dir.directory () : t.dir); + + if (d.sub (out_root) && d != out_root) + { + level5 ([&]{trace << "injecting prerequisite for " << t;}); + + prerequisite& pp ( + s.prerequisites.insert ( + fsdir::static_type, + d, + string (), + nullptr, + s, + trace).first); + + t.prerequisites.push_back (pp); + match (a, search (pp)); + } + } + } + target_state execute_impl (action a, target& t) { @@ -292,36 +325,8 @@ namespace build // prerequisites. // file& ft (dynamic_cast (t)); - const path& f (ft.path ()); - rmfile_status rs; - - // We don't want to print the command if we couldn't delete the - // file because it does not exist (just like we don't print the - // update command if the file is up to date). This makes the - // below code a bit ugly. - // - try - { - rs = try_rmfile (f); - } - catch (const system_error& e) - { - if (verb >= 1) - text << "rm " << f.string (); - else - text << "rm " << t; - - fail << "unable to delete file " << f.string () << ": " << e.what (); - } - - if (rs == rmfile_status::success) - { - if (verb >= 1) - text << "rm " << f.string (); - else - text << "rm " << t; - } + bool r (rmfile (ft.path (), ft) == rmfile_status::success); // Update timestamp in case there are operations after us that // could use the information. @@ -335,6 +340,6 @@ namespace build if (!t.prerequisites.empty ()) ts = reverse_execute_prerequisites (a, t); - return rs == rmfile_status::success ? target_state::changed : ts; + return r ? target_state::changed : ts; } } diff --git a/build/b.cxx b/build/b.cxx index 96f6106..3a37b68 100644 --- a/build/b.cxx +++ b/build/b.cxx @@ -38,28 +38,6 @@ 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) { @@ -94,14 +72,15 @@ namespace build // tree build. // path - find_out_root (const path& b) + find_out_root (const path& b, bool& src) { for (path d (b); !d.root () && d != home; d = d.directory ()) { - if (is_out_root (d) || is_src_root (d)) + if ((src = is_src_root (d)) || is_out_root (d)) return d; } + src = false; return path (); } @@ -121,7 +100,9 @@ namespace build source_once (bf, root, root); } - static void + // Return true if we loaded anything. + // + static bool bootstrap_src (scope& root) { tracer trace ("bootstrap_src"); @@ -129,7 +110,7 @@ namespace build path bf (root.src_path () / path ("build/bootstrap.build")); if (!file_exists (bf)) - return; + return false; // We assume that bootstrap out cannot load this file explicitly. It // feels wrong to allow this since that makes the whole bootstrap @@ -137,6 +118,7 @@ namespace build // same root scope multiple time. // source_once (bf, root, root); + return true; } } @@ -162,7 +144,7 @@ main (int argc, char* argv[]) // Trace verbosity. // - verb = 5; + verb = 4; // Register modules. // @@ -188,22 +170,27 @@ main (int argc, char* argv[]) // Register rules. // cxx::link cxx_link; + rules[default_id][typeid (exe)].emplace ("cxx.gnu.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[default_id][typeid (obj)].emplace ("cxx.gnu.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[default_id][typeid (dir)].emplace ("dir", 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[default_id][typeid (fsdir)].emplace ("fsdir", 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[default_id][typeid (path_target)].emplace ("path", path_r); rules[update_id][typeid (path_target)].emplace ("path", path_r); rules[clean_id][typeid (path_target)].emplace ("path", path_r); @@ -232,18 +219,9 @@ main (int argc, char* argv[]) 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. + // Initialize the dependency state. // -#ifdef _WIN32 - root_scope = &scopes[path ()]; -#else - root_scope = &scopes[path ("/")]; -#endif - - root_scope->variables["work"] = work; - root_scope->variables["home"] = home; + reset (); // Parse the buildspec. // @@ -291,6 +269,8 @@ main (int argc, char* argv[]) meta_operation_id mid (0); // Not yet translated. const meta_operation_info* mif (nullptr); + bool lifted (false); // See below. + for (opspec& os: ms) { const location l ("", 1, 0); //@@ TODO @@ -310,6 +290,21 @@ main (int argc, char* argv[]) action_targets tgs; tgs.reserve (os.size ()); + // If the previous operation was lifted to meta-operation, + // end the meta-operation batch. + // + if (lifted) + { + if (mif->meta_operation_post != nullptr) + mif->meta_operation_post (); + + level4 ([&]{trace << "end meta-operation batch " << mif->name + << ", id " << static_cast (mid);}); + + mid = 0; + lifted = false; + } + for (targetspec& ts: os) { name& tn (ts.name); @@ -378,27 +373,56 @@ main (int argc, char* argv[]) { // If no src_base was explicitly specified, search for out_root. // - out_root = find_out_root (out_base); + bool src; + out_root = find_out_root (out_base, src); // 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. + // simple project or a fresh out-of-tree build. To test for + // the latter, try to find src_root starting from work. If + // we can't, then assume it is the former case. // if (out_root.empty ()) { - src_root = src_base = out_root = out_base; + src_root = find_src_root (work); + + if (!src_root.empty ()) + { + src_base = work; + + if (src_root != src_base) + { + try + { + out_root = out_base.directory (src_base.leaf (src_root)); + } + catch (const invalid_path&) + { + fail << "out_base directory suffix does not match src_base" + << info << "src_base is " << src_base.string () + << info << "src_root is " << src_root.string () + << info << "out_base is " << out_base.string () + << info << "consider explicitly specifying src_base " + << "for " << tn; + } + } + else + out_root = out_base; + } + else + src_root = src_base = out_root = out_base; + guessing = true; } + else if (src) + src_root = out_root; } - // 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. + // Now we know out_root and, if it was explicitly specified + // or the same as out_root, 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. // scope& rs (scopes[out_root]); @@ -418,9 +442,17 @@ main (int argc, char* argv[]) } rs.variables["out_root"] = out_root; + + // If we know src_root, add that variable as well. This could + // be of use to the bootstrap file (other than src-root.build, + // which, BTW, doesn't need to exist if src_root == out_root). + // + if (!src_root.empty ()) + rs.variables["src_root"] = src_root; + bootstrap_out (rs); - // See if the bootstrap process set src_root. + // See if the bootstrap process set/changed src_root. // { auto v (rs.variables["src_root"]); @@ -453,8 +485,6 @@ main (int argc, char* argv[]) { // 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)); @@ -477,7 +507,7 @@ main (int argc, char* argv[]) // Now that we have src_root, load the src_root bootstrap file, // if there is one. // - bootstrap_src (rs); + bool bootstrapped (bootstrap_src (rs)); // The src bootstrap should have loaded all the modules that // may add new meta/operations. So at this stage they should @@ -510,13 +540,47 @@ main (int argc, char* argv[]) if (!mn.empty ()) fail (l) << "nested meta-operation " << mn << '(' << on << ')'; + + if (!lifted) // If this is the first target. + { + // End the previous meta-operation batch if there was one + // and start a new one. + // + if (mid != 0) + { + assert (oid == 0); + + if (mif->meta_operation_post != nullptr) + mif->meta_operation_post (); + + level4 ([&]{trace << "end meta-operation batch " + << mif->name << ", id " + << static_cast (mid);}); + + mid = 0; + } + + lifted = true; // Flag to also end it; see above. + } } else { o = rs.operations.find (on); if (o == 0) - fail (l) << "unknown operation " << on; + { + diag_record dr; + dr << fail (l) << "unknown operation " << on; + + // If we guessed src_root and didn't load anything during + // bootstrap, then this is probably a meta-operation that + // would have been added by the module if src_root was + // correct. + // + if (guessing && !bootstrapped) + dr << info << "consider explicitly specifying src_base " + << "for " << tn; + } } } @@ -525,7 +589,16 @@ main (int argc, char* argv[]) m = rs.meta_operations.find (mn); if (m == 0) - fail (l) << "unknown meta-operation " << mn; + { + diag_record dr; + dr << fail (l) << "unknown meta-operation " << mn; + + // Same idea as for the operation case above. + // + if (guessing && !bootstrapped) + dr << info << "consider explicitly specifying src_base " + << "for " << tn; + } } // The default meta-operation is perform. The default @@ -649,7 +722,7 @@ main (int argc, char* argv[]) d.normalize (); - mif->match (act, target_key {ti, &d, &tn.value, &e}, l, tgs); + mif->match (act, rs, target_key {ti, &d, &tn.value, &e}, l, tgs); } } @@ -662,8 +735,6 @@ main (int argc, char* argv[]) level4 ([&]{trace << "end operation batch " << oif->name << ", id " << static_cast (oid);}); - - //@@ operation batch_post } if (mif->meta_operation_post != nullptr) @@ -677,9 +748,11 @@ main (int argc, char* argv[]) { return 1; // Diagnostics has already been issued. } + /* catch (const std::exception& e) { error << e.what (); return 1; } + */ } diff --git a/build/bootstrap.build b/build/bootstrap.build index 492b6c0..020b73a 100644 --- a/build/bootstrap.build +++ b/build/bootstrap.build @@ -1,5 +1,2 @@ -print bootstrap.build - project_name = build2 - using config diff --git a/build/config/module.cxx b/build/config/module.cxx index dca6ede..41c2526 100644 --- a/build/config/module.cxx +++ b/build/config/module.cxx @@ -28,15 +28,17 @@ namespace build void init (scope& root, scope& base, const location& l) { - tracer trace ("config::init"); - //@@ TODO: avoid multiple inits (generally, for modules). // - level4 ([&]{trace << "for " << root.path () << '/';}); + + tracer trace ("config::init"); if (&root != &base) fail (l) << "config module must be initialized in project root scope"; + const path& out_root (root.path ()); + level4 ([&]{trace << "for " << out_root << '/';}); + // Register meta-operations. // if (root.meta_operations.insert (configure) != configure_id || @@ -45,7 +47,7 @@ namespace build // Register the build/config.build sourcing trigger. // - root.triggers[path ("build/config.build")] = &trigger; + root.triggers[out_root / path ("build/config.build")] = &trigger; } } } diff --git a/build/config/operation.cxx b/build/config/operation.cxx index 1411ce4..df4682e 100644 --- a/build/config/operation.cxx +++ b/build/config/operation.cxx @@ -4,6 +4,11 @@ #include +#include + +#include +#include +#include #include using namespace std; @@ -12,14 +17,114 @@ namespace build { namespace config { - meta_operation_info configure {"configure"}; + static const path build_dir ("build"); + static const path bootstrap_dir ("build/bootstrap"); + + static const path config_file ("build/config.build"); + static const path src_root_file ("build/bootstrap/src-root.build"); + + // configure + // + static operation_id + configure_operation_pre (operation_id o) + { + // Don't translate default to update. In our case unspecified + // means configure everything. + // + return o; + } + + static void + save_src_root (const path& out_root, const path& src_root) + { + path f (out_root / src_root_file); + + if (verb >= 1) + text << "config::save_src_root " << f.string (); + else + text << "save " << f; + + try + { + ofstream ofs (f.string ()); + if (!ofs.is_open ()) + fail << "unable to open " << f; + + ofs.exceptions (ofstream::failbit | ofstream::badbit); + + //@@ TODO: quote path + // + ofs << "# Created automatically by the config module." << endl + << "#" << endl + << "src_root = " << src_root.string () << '/' << endl; + } + catch (const ios_base::failure&) + { + fail << "failed to write to " << f; + } + } + + static void + configure_execute (action a, const action_targets& ts) + { + tracer trace ("configure_execute"); + + for (void* v: ts) + { + target& t (*static_cast (v)); + scope& s (scopes.find (t.dir)); + + const path& out_root (s["out_root"].as ()); + const path& src_root (s["src_root"].as ()); + + // Make sure the directories exist. + // + if (out_root != src_root) + { + mkdir (out_root); + mkdir (out_root / build_dir); + } + + mkdir (out_root / bootstrap_dir); + + // We distinguish between a complete configure and operation- + // specific. + // + if (a.operation () == default_id) + { + level4 ([&]{trace << "completely configuring " << out_root;}); + + // Save src-root.build unless out_root is the same as src. + // + if (out_root != src_root) + save_src_root (out_root, src_root); + } + else + { + } + } + } + + meta_operation_info configure { + "configure", + nullptr, // meta-operation pre + &configure_operation_pre, + &load, // normal load + &match, // normal match + &configure_execute, + nullptr, // operation post + nullptr // meta-operation post + }; // disfigure // static operation_id disfigure_operation_pre (operation_id o) { - return o; // Don't translate default to update. + // Don't translate default to update. In our case unspecified + // means disfigure everything. + // + return o; } static void @@ -30,32 +135,75 @@ namespace build const location&) { tracer trace ("disfigure_load"); - level4 ([&]{trace << "skipping " << bf;}); + level5 ([&]{trace << "skipping " << bf;}); } static void disfigure_match (action a, + scope& root, const target_key& tk, const location& l, action_targets& ts) { tracer trace ("disfigure_match"); - //level4 ([&]{trace << "matching " << t;}); - //ts.push_back (&t); + level5 ([&]{trace << "collecting " << root.path ();}); + ts.push_back (&root); } static void disfigure_execute (action a, const action_targets& ts) { - tracer trace ("execute"); - + tracer trace ("disfigure_execute"); for (void* v: ts) { - //level4 ([&]{trace << "disfiguring target " << t;}); + scope& root (*static_cast (v)); + const path& out_root (root.path ()); + const path& src_root (root.src_path ()); + + // We distinguish between a complete disfigure and operation- + // specific. + // + if (a.operation () == default_id) + { + level4 ([&]{trace << "completely disfiguring " << out_root;}); + + rmfile (out_root / config_file); + rmfile (out_root / src_root_file); + + // Clean up the directories. + // + rmdir (out_root / bootstrap_dir); + + if (out_root != src_root) + { + rmdir (out_root / build_dir); + + if (rmdir (out_root) == rmdir_status::not_empty) + warn << "directory " << out_root.string () << " is " + << (out_root == work + ? "current working directory" + : "not empty") << ", not removing"; + } + } + else + { + } } } + static void + disfigure_meta_operation_post () + { + tracer trace ("disfigure_meta_operation_post"); + + // Reset the dependency state since anything that could have been + // loaded earlier using a previous configuration is now invalid. + // + level5 ([&]{trace << "resetting dependency state";}); + reset (); + } + meta_operation_info disfigure { "disfigure", nullptr, // meta-operation pre @@ -64,7 +212,7 @@ namespace build &disfigure_match, &disfigure_execute, nullptr, // operation post - nullptr // meta-operation post + &disfigure_meta_operation_post }; } } diff --git a/build/context b/build/context index 2e3209a..ac6e29c 100644 --- a/build/context +++ b/build/context @@ -11,10 +11,12 @@ #include #include #include +#include namespace build { class scope; + class file; extern path work; extern path home; @@ -24,6 +26,41 @@ namespace build extern execution_mode current_mode; extern const target_rule_map* current_rules; + // Reset the dependency state. In particular, this removes all the + // targets, scopes, and variable names. + // + void + reset (); + + // Create the directory and print the standard diagnostics. Note that + // this implementation is not suitable if it is expected that the + // directory will exist in the majority of case and performance is + // important. See the fsdir{} rule for details. + // + mkdir_status + mkdir (const path&); + + // Remove the file and print the standard diagnostics. The second + // argument is only used in diagnostics, to print the target name. + // Passing the path for target will result in the relative path + // being printed. + // + template + rmfile_status + rmfile (const path&, const T& target); + + inline rmfile_status + rmfile (const path& f) {return rmfile (f, f);} + + // Similar to rmfile() but for directories. + // + template + rmdir_status + rmdir (const path&, const T& target); + + inline rmdir_status + rmdir (const path& d) {return rmdir (d, d);} + // Return the src/out directory corresponding to the given out/src. The // passed directory should be a sub-directory of out/src_root. // @@ -46,4 +83,6 @@ namespace build relative_work (const path&); } +#include + #endif // BUILD_CONTEXT diff --git a/build/context.cxx b/build/context.cxx index e9434f7..92f3db9 100644 --- a/build/context.cxx +++ b/build/context.cxx @@ -6,8 +6,10 @@ #include #include +#include #include +#include using namespace std; @@ -19,6 +21,61 @@ namespace build execution_mode current_mode; const target_rule_map* current_rules; + void + reset () + { + targets.clear (); + scopes.clear (); + variable_pool.clear (); + + // 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; + } + + mkdir_status + mkdir (const path& d) + { + // We don't want to print the command if the directory already + // exists. This makes the below code a bit ugly. + // + mkdir_status ms; + + try + { + ms = try_mkdir (d); + } + catch (const system_error& e) + { + if (verb >= 1) + text << "mkdir " << d.string (); + else + text << "mkdir " << d; + + fail << "unable to create directory " << d.string () << ": " + << e.what (); + } + + if (ms == mkdir_status::success) + { + if (verb >= 1) + text << "mkdir " << d.string (); + else + text << "mkdir " << d; + } + + return ms; + } + path src_out (const path& out, scope& s) { diff --git a/build/context.txx b/build/context.txx new file mode 100644 index 0000000..cae1ce8 --- /dev/null +++ b/build/context.txx @@ -0,0 +1,100 @@ +// file : build/context.txx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include + +#include + +namespace build +{ + template + rmfile_status + rmfile (const path& f, const T& t) + { + // We don't want to print the command if we couldn't remove the + // file because it does not exist (just like we don't print the + // update command if the file is up to date). This makes the + // below code a bit ugly. + // + rmfile_status rs; + + try + { + rs = try_rmfile (f); + } + catch (const std::system_error& e) + { + if (verb >= 1) + text << "rm " << f.string (); + else + text << "rm " << t; + + fail << "unable to remove file " << f.string () << ": " << e.what (); + } + + if (rs == rmfile_status::success) + { + if (verb >= 1) + text << "rm " << f.string (); + else + text << "rm " << t; + } + + return rs; + } + + template + rmdir_status + rmdir (const path& d, const T& t) + { + bool w (d == work); // Don't try to remove working directory. + rmdir_status rs; + + // We don't want to print the command if we couldn't remove the + // directory because it does not exist (just like we don't print + // mkdir if it already exists) or if it is not empty. This makes + // the below code a bit ugly. + // + try + { + rs = !w ? try_rmdir (d) : rmdir_status::not_empty; + } + catch (const std::system_error& e) + { + if (verb >= 1) + text << "rmdir " << d.string (); + else + text << "rmdir " << t; + + fail << "unable to remove directory " << d.string () << ": " + << e.what (); + } + + switch (rs) + { + case rmdir_status::success: + { + if (verb >= 1) + text << "rmdir " << d.string (); + else + text << "rmdir " << t; + + break; + } + case rmdir_status::not_empty: + { + if (verb >= 1) + text << "directory " << d.string () << " is " + << (w ? "current working directory" : "not empty") + << ", not removing"; + + break; + } + case rmdir_status::not_exist: + break; + } + + return rs; + } +} diff --git a/build/cxx/rule.cxx b/build/cxx/rule.cxx index 649067e..f53dd3b 100644 --- a/build/cxx/rule.cxx +++ b/build/cxx/rule.cxx @@ -74,15 +74,20 @@ namespace build // switch (a.operation ()) { + case default_id: case update_id: search_and_match (a, t); break; case clean_id: search_and_match (a, t, t.dir); break; default: assert (false); } + // Inject dependency on the output directory. + // + inject_parent_fsdir (a, t); + // Inject additional prerequisites. For now we only do it for - // update. + // update and default. // - if (a.operation () == update_id) + if (a.operation () == update_id || a.operation () == default_id) { auto& sp (*static_cast (v)); auto& st (dynamic_cast (*sp.target)); @@ -95,7 +100,7 @@ namespace build { case perform_update_id: return &perform_update; case perform_clean_id: return &perform_clean_file; - default: return noop_recipe; + default: return default_recipe; // Forward to prerequisites. } } @@ -366,7 +371,7 @@ namespace build if (!seen_obj) seen_obj = true; } - else + else if (p.type.id != typeid (fsdir)) { level3 ([&]{trace << "unexpected prerequisite type " << p.type;}); return nullptr; @@ -547,11 +552,15 @@ namespace build pr = op; } + // Inject dependency on the output directory. + // + inject_parent_fsdir (a, t); + switch (a) { case perform_update_id: return &perform_update; case perform_clean_id: return &perform_clean_file; - default: return noop_recipe; + default: return default_recipe; // Forward to prerequisites. } } @@ -580,9 +589,11 @@ namespace build for (const prerequisite& p: t.prerequisites) { - const obj& o (dynamic_cast (*p.target)); - ro.push_back (relative_work (o.path ())); - args.push_back (ro.back ().string ().c_str ()); + if (const obj* o = dynamic_cast (p.target)) + { + ro.push_back (relative_work (o->path ())); + args.push_back (ro.back ().string ().c_str ()); + } } args.push_back (nullptr); diff --git a/build/dump b/build/dump index 0de7569..e3cc5d2 100644 --- a/build/dump +++ b/build/dump @@ -8,6 +8,9 @@ namespace build { void + dump (); + + void dump_scopes (); } diff --git a/build/dump.cxx b/build/dump.cxx index 4294e92..98b0ff2 100644 --- a/build/dump.cxx +++ b/build/dump.cxx @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -16,6 +17,28 @@ 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; + } + static void dump_scope (scope& p, scope_map::iterator& i, string& ind) { diff --git a/build/filesystem b/build/filesystem index c96e669..8617dd4 100644 --- a/build/filesystem +++ b/build/filesystem @@ -23,13 +23,21 @@ namespace build bool file_exists (const path&); - - // Note that you should probably use the default mode 0777 and let - // the umask mechanism adjust it to the user's preferences. Errors - // are reported by throwing std::system_error. + // Try to create a directory unless it already exists. If you expect + // the directory to exist and performance is important, then you + // should first call dir_exists() above since that's what this + // implementation will do to make sure the path is actually a + // directory. + // + // You should also probably use the default mode 0777 and let the + // umask mechanism adjust it to the user's preferences. // - void - mkdir (const path&, mode_t = 0777); + // Errors are reported by throwing std::system_error. + // + enum class mkdir_status {success, already_exists}; + + mkdir_status + try_mkdir (const path&, mode_t = 0777); // Try to remove the directory returning not_exist if it does not // exist and not_empty if it is not empty. All other errors are diff --git a/build/filesystem.cxx b/build/filesystem.cxx index ee17fba..5494749 100644 --- a/build/filesystem.cxx +++ b/build/filesystem.cxx @@ -44,11 +44,25 @@ namespace build return S_ISREG (s.st_mode); } - void - mkdir (const path& p, mode_t m) + mkdir_status + try_mkdir (const path& p, mode_t m) { + mkdir_status r (mkdir_status::success); + if (::mkdir (p.string ().c_str (), m) != 0) - throw system_error (errno, system_category ()); + { + int e (errno); + + // EEXIST means the path already exists but not necessarily as + // a directory. + // + if (e == EEXIST && dir_exists (p)) + return mkdir_status::already_exists; + else + throw system_error (e, system_category ()); + } + + return r; } rmdir_status diff --git a/build/operation b/build/operation index c759d59..ac93999 100644 --- a/build/operation +++ b/build/operation @@ -135,6 +135,7 @@ namespace build const location&); void (*match) (action, + scope& root, const target_key&, const location&, action_targets&); @@ -167,7 +168,7 @@ namespace build // that does just that and adds a pointer to the target to the list. // void - match (action, const target_key&, const location&, action_targets&); + match (action, scope&, const target_key&, const location&, action_targets&); // Execute the action on the list of targets. This is the default // implementation that does just that while issuing appropriate diff --git a/build/operation.cxx b/build/operation.cxx index bbd474f..7fab63e 100644 --- a/build/operation.cxx +++ b/build/operation.cxx @@ -60,6 +60,7 @@ namespace build void match (action a, + scope&, const target_key& tk, const location& l, action_targets& ts) @@ -72,12 +73,14 @@ namespace build target& t (**i); - //@@ dump + if (verb >= 5) + dump (); level4 ([&]{trace << "matching " << t;}); match (a, t); - //@@ dump + if (verb >= 5) + dump (); ts.push_back (&t); } diff --git a/build/parser.cxx b/build/parser.cxx index c87a04a..809841f 100644 --- a/build/parser.cxx +++ b/build/parser.cxx @@ -277,7 +277,11 @@ namespace build targets.insert ( *ti, move (tn.dir), move (tn.value), e, trace).first); - t.prerequisites = ps; //@@ OPT: move if last target. + //@@ OPT: move if last/single target (common cases). + // + t.prerequisites.insert (t.prerequisites.end (), + ps.begin (), + ps.end ()); if (default_target_ == nullptr) default_target_ = &t; @@ -408,9 +412,8 @@ namespace build // See if there is a trigger for this path. // - if (src_root_ != nullptr && p.sub (*src_root_)) { - auto i (root_->triggers.find (p.leaf (*src_root_))); + auto i (root_->triggers.find (p)); if (i != root_->triggers.end () && !i->second (*root_, p)) { diff --git a/build/path b/build/path index 699882e..1a62287 100644 --- a/build/path +++ b/build/path @@ -257,13 +257,18 @@ namespace build // Return true if *this is a sub-path of the specified path (i.e., // the specified path is a prefix). Expects both paths to be // normalized. Note that this function returns true if the paths - // are equal. + // are equal. Empty path is considered a prefix of any path. // bool - sub (const basic_path& p) const - { - return path_.compare (0, p.path_.size (), p.path_) == 0; - } + sub (const basic_path&) const; + + // Return true if *this is a super-path of the specified path (i.e., + // the specified path is a suffix). Expects both paths to be + // normalized. Note that this function returns true if the paths + // are equal. Empty path is considered a suffix of any path. + // + bool + sup (const basic_path&) const; public: // Return the path without the directory part. diff --git a/build/path.ixx b/build/path.ixx index ef1232b..95608e9 100644 --- a/build/path.ixx +++ b/build/path.ixx @@ -48,6 +48,43 @@ namespace build } template + inline bool basic_path:: + sub (const basic_path& p) const + { + size_type n (p.path_.size ()); + + if (n == 0) + return true; + + size_type m (path_.size ()); + + // The second condition guards against the /foo-bar vs /foo case. + // + return m >= n && path_.compare (0, n, p.path_) == 0 && + (traits::is_separator (p.path_.back ()) || // p ends with a separator + m == n || // *this == p + traits::is_separator (path_[n])); // next char is a separator + } + + template + inline bool basic_path:: + sup (const basic_path& p) const + { + size_type n (p.path_.size ()); + + if (n == 0) + return true; + + size_type m (path_.size ()); + + // The second condition guards against the /foo-bar vs bar case. + // + return m >= n && path_.compare (m - n, n, p.path_) == 0 && + (m == n || // *this == p + traits::is_separator (path_[m - n - 1])); // prev char is a separator + } + + template inline basic_path& basic_path:: complete () { diff --git a/build/path.txx b/build/path.txx index 5fbabbd..a8bf859 100644 --- a/build/path.txx +++ b/build/path.txx @@ -84,11 +84,11 @@ namespace build if (n == 0) return *this; - size_type m (path_.size ()); - - if (m < n || path_.compare (0, n, d.path_) != 0) + if (!sub (d)) throw invalid_basic_path (path_); + size_type m (path_.size ()); + if (n != m #ifndef _WIN32 && !d.root () @@ -108,11 +108,11 @@ namespace build if (n == 0) return *this; - size_type m (path_.size ()); - - if (m < n || path_.compare (m - n, n, l.path_) != 0) + if (!sup (l)) throw invalid_basic_path (path_); + size_type m (path_.size ()); + if (n != m) n++; // Skip the directory separator. diff --git a/build/root.build b/build/root.build index 8412356..5199200 100644 --- a/build/root.build +++ b/build/root.build @@ -1,3 +1 @@ -print root.build - -source build/config.build +source $out_root/build/config.build diff --git a/build/rule.cxx b/build/rule.cxx index aa0300a..fb5af3f 100644 --- a/build/rule.cxx +++ b/build/rule.cxx @@ -7,6 +7,7 @@ #include // move() #include +#include #include #include #include @@ -78,9 +79,7 @@ namespace build return pt.mtime () != timestamp_nonexistent ? &t : nullptr; } default: - { - return &t; - } + return &t; } } @@ -156,13 +155,15 @@ namespace build apply (action a, target& t, void*) const { // When cleaning, ignore prerequisites that are not in the same - // or a subdirectory of ours. + // or a subdirectory of ours. For default, we don't do anything + // other than letting our prerequisites do their thing. // switch (a.operation ()) { + case default_id: case update_id: search_and_match (a, t); break; - case clean_id: search_and_match (a, t, t.dir); break; - default: assert (false); + case clean_id: search_and_match (a, t, t.dir); break; + default: assert (false); } return default_recipe; @@ -181,29 +182,34 @@ namespace build { switch (a.operation ()) { + // For default, we don't do anything other than letting our + // prerequisites do their thing. + // + case default_id: case update_id: - { - search_and_match (a, t); - break; - } + search_and_match (a, t); + break; + // For clean, ignore prerequisites that are not in the same or a + // subdirectory of ours (if t.dir is foo/bar/, then "we" are bar + // and our directory is foo/). Just meditate on it a bit and you + // will see the light. + // case clean_id: - { - // Ignore prerequisites that are not in the same or a subdirectory - // of ours (if t.dir is foo/bar/, then "we" are bar and our directory - // is foo/). Just meditate on it a bit and you will see the light. - // - search_and_match (a, t, t.dir.root () ? t.dir : t.dir.directory ()); - break; - } + search_and_match (a, t, t.dir.root () ? t.dir : t.dir.directory ()); + break; default: assert (false); } + // Inject dependency on the parent directory. + // + inject_parent_fsdir (a, t); + switch (a) { case perform_update_id: return &perform_update; case perform_clean_id: return &perform_clean; - default: return noop_recipe; + default: return default_recipe; // Forward to prerequisites. } } @@ -220,6 +226,11 @@ namespace build const path& d (t.dir); // Everything is in t.dir. + // Generally, it is probably correct to assume that in the majority + // of cases the directory will already exist. If so, then we are + // going to get better performance by first checking if it indeed + // exists. See try_mkdir() for details. + // if (!dir_exists (d)) { if (verb >= 1) @@ -229,7 +240,7 @@ namespace build try { - mkdir (d); + try_mkdir (d); } catch (const system_error& e) { @@ -249,53 +260,7 @@ namespace build // The reverse order of update: first delete this directory, // then clean prerequisites (e.g., delete parent directories). // - const path& d (t.dir); // Everything is in t.dir. - bool w (d == work); // Don't try to delete working directory. - - rmdir_status rs; - - // We don't want to print the command if we couldn't delete the - // directory because it does not exist (just like we don't print - // mkdir if it already exists) or if it is not empty. This makes - // the below code a bit ugly. - // - try - { - rs = !w ? try_rmdir (d) : rmdir_status::not_empty; - } - catch (const system_error& e) - { - if (verb >= 1) - text << "rmdir " << d.string (); - else - text << "rmdir " << t; - - fail << "unable to delete directory " << d.string () << ": " - << e.what (); - } - - switch (rs) - { - case rmdir_status::success: - { - if (verb >= 1) - text << "rmdir " << d.string (); - else - text << "rmdir " << t; - - break; - } - case rmdir_status::not_empty: - { - if (verb >= 1) - text << "directory " << d.string () << " is " - << (w ? "cwd" : "not empty") << ", not removing"; - - break; - } - case rmdir_status::not_exist: - break; - } + rmdir_status rs (rmdir (t.dir, t)); target_state ts (target_state::unchanged); diff --git a/build/scope b/build/scope index 26a4e03..8471a2a 100644 --- a/build/scope +++ b/build/scope @@ -31,7 +31,9 @@ namespace build scope* parent () const {return parent_;} - // Variable lookup. + // Variable lookup. Note that this find, not find or insert like + // in the variable_map, because we also search in outer scopes. + // For the latter use the variables map directly. // public: value_proxy @@ -72,13 +74,12 @@ namespace build std::unordered_set buildfiles; // A map of buildfiles to trigger functions that are executed when - // such files are sourced. The path is is assumed to be relative to - // the src directory corresponding to this scope. + // such files are sourced. The path must be absolute and normalized. // - // The passed path is the actual, absolute buildfile path. If the - // returned value is true, then the file is sourced. If false -- - // the file is ignored. Note that currently triggers can only be - // registered on the project root scope. + // The passed path is the buildfile. If the returned value is true, + // then the file is sourced. If false -- the file is ignored. Note + // that currently triggers can only be registered on the project's + // root scope. // using trigger_type = std::function; std::unordered_map triggers; diff --git a/build/target b/build/target index 050696c..f8bb2c1 100644 --- a/build/target +++ b/build/target @@ -227,6 +227,9 @@ namespace build const std::string* ext, tracer&); + void + clear () {map_.clear ();} + private: map map_; }; -- cgit v1.1