From fd689eb883655dcb29e505b041cd02fac01f0bac Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 27 Aug 2015 15:11:40 +0200 Subject: Dist module/meta-operation initial implementation --- build/algorithm | 3 + build/algorithm.cxx | 197 ++++++++------- build/b.cxx | 8 +- build/bin/module.cxx | 12 +- build/buildfile | 5 +- build/cli/module.cxx | 20 +- build/cli/rule.cxx | 2 +- build/config/operation.cxx | 34 ++- build/config/utility | 19 +- build/config/utility.cxx | 33 +++ build/context | 6 + build/context.cxx | 40 ++- build/cxx/compile.cxx | 2 +- build/cxx/link.cxx | 2 +- build/cxx/module.cxx | 31 +-- build/dist/module | 21 ++ build/dist/module.cxx | 130 ++++++++++ build/dist/operation | 18 ++ build/dist/operation.cxx | 423 ++++++++++++++++++++++++++++++++ build/dist/rule | 29 +++ build/dist/rule.cxx | 49 ++++ build/install/module.cxx | 16 +- build/install/rule.cxx | 5 - build/lexer.cxx | 5 +- build/operation | 15 +- build/operation.cxx | 11 +- build/parser | 5 + build/parser.cxx | 31 +++ build/rule-map | 70 +++++- build/rule.cxx | 14 +- build/target | 10 + build/target.cxx | 20 ++ build/test/module.cxx | 12 +- build/test/rule.cxx | 24 +- doc/language.txt | 5 + tests/.gitignore | 2 +- tests/cli/simple/build/bootstrap.build | 1 + tests/dist/simple/README | 0 tests/dist/simple/bootstrap | 0 tests/dist/simple/build/bootstrap.build | 9 + tests/dist/simple/build/export.build | 0 tests/dist/simple/buildfile | 7 + tests/dist/simple/driver.cxx | 10 + tests/dist/simple/test.out | 1 + 44 files changed, 1127 insertions(+), 230 deletions(-) create mode 100644 build/dist/module create mode 100644 build/dist/module.cxx create mode 100644 build/dist/operation create mode 100644 build/dist/operation.cxx create mode 100644 build/dist/rule create mode 100644 build/dist/rule.cxx create mode 100644 doc/language.txt create mode 100644 tests/dist/simple/README create mode 100755 tests/dist/simple/bootstrap create mode 100644 tests/dist/simple/build/bootstrap.build create mode 100644 tests/dist/simple/build/export.build create mode 100644 tests/dist/simple/buildfile create mode 100644 tests/dist/simple/driver.cxx create mode 100644 tests/dist/simple/test.out diff --git a/build/algorithm b/build/algorithm index af461dc..375cebb 100644 --- a/build/algorithm +++ b/build/algorithm @@ -73,6 +73,9 @@ namespace build void match (action, target&); + // Note that calling this function only makes sense if the + // target itself doesn't have its own dependents. + // void unmatch (action, target&); diff --git a/build/algorithm.cxx b/build/algorithm.cxx index e09d125..f462bb8 100644 --- a/build/algorithm.cxx +++ b/build/algorithm.cxx @@ -83,7 +83,6 @@ namespace build // action ra (a.meta_operation (), io, o != oo ? 0 : oo); - size_t oi (o - 1); // Operation index in rule_map. scope& bs (t.base_scope ()); for (auto tt (&t.type ()); tt != nullptr; tt = tt->base) @@ -94,129 +93,139 @@ namespace build s != nullptr; s = s->root () ? global_scope : s->parent_scope ()) { - const rule_map& om (s->rules); + const operation_rule_map* om (s->rules[a.meta_operation ()]); - if (om.size () <= oi) - continue; // No entry for this operation id. + if (om == nullptr) + continue; // No entry for this meta-operation id. - const target_type_rule_map& ttm (om[oi]); + // First try the map for the actual operation. If that + // doesn't yeld anything, try the wildcard map. + // + for (size_t oi (o), oip (o); oip != 0; oip = oi, oi = 0) + { + const target_type_rule_map* ttm ((*om)[oi]); - if (ttm.empty ()) - continue; // Empty map for this operation id. + if (ttm == nullptr) + continue; // No entry for this operation id. - auto i (ttm.find (tt->id)); + if (ttm->empty ()) + continue; // Empty map for this operation id. - if (i == ttm.end () || i->second.empty ()) - continue; // No rules registered for this target type. + auto i (ttm->find (tt->id)); - const auto& rules (i->second); // Hint map. + if (i == ttm->end () || i->second.empty ()) + continue; // No rules registered for this target type. - // @@ TODO - // - // Different rules can be used for different operations (update - // vs test is a good example). So, at some point, we will probably - // have to support a list of hints or even an operation-hint map - // (e.g., 'hint=cxx test=foo' if cxx supports the test operation - // but we want the foo rule instead). This is also the place where - // the '{build clean}=cxx' construct (which we currently do not - // support) can come handy. - // - // Also, ignore the hint (that is most likely ment for a different - // operation) if this is a unique match. - // - string hint; - auto rs (rules.size () == 1 - ? make_pair (rules.begin (), rules.end ()) - : rules.find_prefix (hint)); + const auto& rules (i->second); // Hint map. - for (auto i (rs.first); i != rs.second; ++i) - { - const string& n (i->first); - const rule& ru (i->second); - - match_result m; - { - auto g ( - make_exception_guard ( - [](action a, target& t, const string& n) - { - info << "while matching rule " << n << " to " - << diag_do (a, t); - }, - ra, t, n)); - - if (!(m = ru.match (ra, t, hint))) - continue; - - if (!m.recipe_action.valid ()) - m.recipe_action = ra; // Default, if not set. - } - - // Do the ambiguity test. + // @@ TODO // - bool ambig (false); - - diag_record dr; + // Different rules can be used for different operations (update + // vs test is a good example). So, at some point, we will probably + // have to support a list of hints or even an operation-hint map + // (e.g., 'hint=cxx test=foo' if cxx supports the test operation + // but we want the foo rule instead). This is also the place where + // the '{build clean}=cxx' construct (which we currently do not + // support) can come handy. + // + // Also, ignore the hint (that is most likely ment for a different + // operation) if this is a unique match. + // + string hint; + auto rs (rules.size () == 1 + ? make_pair (rules.begin (), rules.end ()) + : rules.find_prefix (hint)); - for (++i; i != rs.second; ++i) + for (auto i (rs.first); i != rs.second; ++i) { - const string& n1 (i->first); - const rule& ru1 (i->second); + const string& n (i->first); + const rule& ru (i->second); + match_result m; { auto g ( make_exception_guard ( - [](action a, target& t, const string& n1) + [](action a, target& t, const string& n) { - info << "while matching rule " << n1 << " to " + info << "while matching rule " << n << " to " << diag_do (a, t); }, - ra, t, n1)); + ra, t, n)); - if (!ru1.match (ra, t, hint)) + if (!(m = ru.match (ra, t, hint))) continue; - } - if (!ambig) - { - dr << fail << "multiple rules matching " << diag_doing (ra, t) - << info << "rule " << n << " matches"; - ambig = true; + if (!m.recipe_action.valid ()) + m.recipe_action = ra; // Default, if not set. } - dr << info << "rule " << n1 << " also matches"; - } + // Do the ambiguity test. + // + bool ambig (false); - if (!ambig) - { - ra = m.recipe_action; // Use custom, if set. + diag_record dr; - if (apply) + for (++i; i != rs.second; ++i) { - auto g ( - make_exception_guard ( - [](action a, target& t, const string& n) - { - info << "while applying rule " << n << " to " - << diag_do (a, t); - }, - ra, t, n)); - - // @@ We could also allow the rule to change the recipe - // action in apply(). Could be useful with delegates. - // - t.recipe (ra, ru.apply (ra, t, m)); + const string& n1 (i->first); + const rule& ru1 (i->second); + + { + auto g ( + make_exception_guard ( + [](action a, target& t, const string& n1) + { + info << "while matching rule " << n1 << " to " + << diag_do (a, t); + }, + ra, t, n1)); + + if (!ru1.match (ra, t, hint)) + continue; + } + + if (!ambig) + { + dr << fail << "multiple rules matching " + << diag_doing (ra, t) + << info << "rule " << n << " matches"; + ambig = true; + } + + dr << info << "rule " << n1 << " also matches"; } - else + + if (!ambig) { - r.first = &ru; - r.second = move (m); + ra = m.recipe_action; // Use custom, if set. + + if (apply) + { + auto g ( + make_exception_guard ( + [](action a, target& t, const string& n) + { + info << "while applying rule " << n << " to " + << diag_do (a, t); + }, + ra, t, n)); + + // @@ We could also allow the rule to change the recipe + // action in apply(). Could be useful with delegates. + // + t.recipe (ra, ru.apply (ra, t, m)); + } + else + { + r.first = &ru; + r.second = move (m); + } + + return r; } - - return r; + else + dr << info << "use rule hint to disambiguate this match"; } - else - dr << info << "use rule hint to disambiguate this match"; } } } diff --git a/build/b.cxx b/build/b.cxx index 412fb12..38a944c 100644 --- a/build/b.cxx +++ b/build/b.cxx @@ -42,6 +42,7 @@ using namespace std; #include +#include #include #include #include @@ -94,6 +95,7 @@ main (int argc, char* argv[]) // Register builtin modules. // builtin_modules["config"] = &config::config_init; + builtin_modules["dist"] = &dist::dist_init; builtin_modules["bin"] = &bin::bin_init; builtin_modules["cxx"] = &cxx::cxx_init; builtin_modules["cli"] = &cli::cli_init; @@ -771,7 +773,7 @@ main (int argc, char* argv[]) action a (mid, pre_oid, oid); mif->match (a, tgs); - mif->execute (a, tgs); + mif->execute (a, tgs, true); // Run quiet. if (mif->operation_post != nullptr) mif->operation_post (pre_oid); @@ -788,7 +790,7 @@ main (int argc, char* argv[]) action a (mid, oid, 0); mif->match (a, tgs); - mif->execute (a, tgs); + mif->execute (a, tgs, false); if (post_oid != 0) { @@ -806,7 +808,7 @@ main (int argc, char* argv[]) action a (mid, post_oid, oid); mif->match (a, tgs); - mif->execute (a, tgs); + mif->execute (a, tgs, true); // Run quiet. if (mif->operation_post != nullptr) mif->operation_post (post_oid); diff --git a/build/bin/module.cxx b/build/bin/module.cxx index 4c5cca5..63f602c 100644 --- a/build/bin/module.cxx +++ b/build/bin/module.cxx @@ -57,13 +57,11 @@ namespace build { auto& rs (b.rules); - rs.insert (default_id, "bin.obj", obj_); - rs.insert (update_id, "bin.obj", obj_); - rs.insert (clean_id, "bin.obj", obj_); + rs.insert (perform_id, update_id, "bin.obj", obj_); + rs.insert (perform_id, clean_id, "bin.obj", obj_); - rs.insert (default_id, "bin.lib", lib_); - rs.insert (update_id, "bin.lib", lib_); - rs.insert (clean_id, "bin.lib", lib_); + rs.insert (perform_id, update_id, "bin.lib", lib_); + rs.insert (perform_id, clean_id, "bin.lib", lib_); //@@ Should we check if the install module was loaded // (by checking if install operation is registered @@ -72,7 +70,7 @@ namespace build // should enforce loading of all operation-defining // modules before all others? // - rs.insert (install_id, "bin.lib", lib_); + rs.insert (perform_id, install_id, "bin.lib", lib_); } // Enter module variables. diff --git a/build/buildfile b/build/buildfile index ce917f6..0da8d8e 100644 --- a/build/buildfile +++ b/build/buildfile @@ -5,6 +5,7 @@ import libs += libbutl%lib{butl} config = config/{operation module utility} +dist = dist/{operation rule module} bin = bin/{target rule module} cxx = cxx/{target compile link install module utility} cli = cli/{target rule module} @@ -13,8 +14,8 @@ install1 = install/{operation rule module} exe{b}: cxx{b algorithm name operation spec scope variable target \ prerequisite rule file module context search diagnostics token \ - lexer parser path-io utility dump options $config $bin $cxx $cli \ - $test1 $install1} $libs + lexer parser path-io utility dump options $config $dist $bin $cxx \ + $cli $test1 $install1} $libs #@@ TODO # diff --git a/build/cli/module.cxx b/build/cli/module.cxx index 7702589..efc9ff2 100644 --- a/build/cli/module.cxx +++ b/build/cli/module.cxx @@ -61,21 +61,17 @@ namespace build { auto& rs (base.rules); - rs.insert (default_id, "cli.compile", compile_); - rs.insert (update_id, "cli.compile", compile_); - rs.insert (clean_id, "cli.compile", compile_); + rs.insert (perform_id, update_id, "cli", compile_); + rs.insert (perform_id, clean_id, "cli", compile_); - rs.insert (default_id, "cli.compile", compile_); - rs.insert (update_id, "cli.compile", compile_); - rs.insert (clean_id, "cli.compile", compile_); + rs.insert (perform_id, update_id, "cli", compile_); + rs.insert (perform_id, clean_id, "cli", compile_); - rs.insert (default_id, "cli.compile", compile_); - rs.insert (update_id, "cli.compile", compile_); - rs.insert (clean_id, "cli.compile", compile_); + rs.insert (perform_id, update_id, "cli", compile_); + rs.insert (perform_id, clean_id, "cli", compile_); - rs.insert (default_id, "cli.compile", compile_); - rs.insert (update_id, "cli.compile", compile_); - rs.insert (clean_id, "cli.compile", compile_); + rs.insert (perform_id, update_id, "cli", compile_); + rs.insert (perform_id, clean_id, "cli", compile_); } // Enter module variables. diff --git a/build/cli/rule.cxx b/build/cli/rule.cxx index a892d7c..b3c57f1 100644 --- a/build/cli/rule.cxx +++ b/build/cli/rule.cxx @@ -172,7 +172,7 @@ namespace build { case perform_update_id: return &perform_update; case perform_clean_id: return &perform_clean; - default: return default_recipe; // Forward to prerequisites. + default: assert (false); return default_recipe; } } else diff --git a/build/config/operation.cxx b/build/config/operation.cxx index 913ae29..80b92dc 100644 --- a/build/config/operation.cxx +++ b/build/config/operation.cxx @@ -187,17 +187,26 @@ namespace build } static void - configure_execute (action a, const action_targets& ts) + configure_search (scope& root, + const target_key&, + const location&, + action_targets& ts) { - for (void* v: ts) - { - target& t (*static_cast (v)); - scope* rs (t.base_scope ().root_scope ()); + tracer trace ("configure_search"); + level5 ([&]{trace << "collecting " << root.path ();}); + ts.push_back (&root); + } - if (rs == nullptr) - fail << "out of project target " << t; + static void + configure_match (action, action_targets&) {} - configure_project (a, *rs); + static void + configure_execute (action a, const action_targets& ts, bool) + { + for (void* v: ts) + { + scope& root (*static_cast (v)); + configure_project (a, root); } } @@ -209,8 +218,8 @@ namespace build nullptr, // meta-operation pre &configure_operation_pre, &load, // normal load - &search, // normal search - &match, // normal match + &configure_search, + &configure_match, &configure_execute, nullptr, // operation post nullptr // meta-operation post @@ -355,7 +364,7 @@ namespace build } static void - disfigure_execute (action a, const action_targets& ts) + disfigure_execute (action a, const action_targets& ts, bool quiet) { tracer trace ("disfigure_execute"); @@ -372,7 +381,8 @@ namespace build targets.insert ( dir::static_type, root.path (), "", nullptr, trace).first); - info << diag_done (a, t); + if (!quiet) + info << diag_done (a, t); } } } diff --git a/build/config/utility b/build/config/utility index 82f71fe..406c271 100644 --- a/build/config/utility +++ b/build/config/utility @@ -49,12 +49,25 @@ namespace build // Return the value, which can be NULL. // const value& - optional (scope& root, const variable& var); + optional (scope& root, const variable&); inline const value& - optional (scope& root, const std::string& name) + optional (scope& root, const std::string& var) { - return optional (root, variable_pool.find (name)); + return optional (root, variable_pool.find (var)); + } + + // As above but assumes the value is dir_path and makes it + // absolute if the value specified on the command line is + // relative. + // + const value& + optional_absolute (scope& root, const variable&); + + inline const value& + optional_absolute (scope& root, const std::string& var) + { + return optional_absolute (root, variable_pool.find (var)); } // Check whether there are any variables specified from the diff --git a/build/config/utility.cxx b/build/config/utility.cxx index ce723fe..c9d7c66 100644 --- a/build/config/utility.cxx +++ b/build/config/utility.cxx @@ -4,6 +4,8 @@ #include +#include + using namespace std; namespace build @@ -20,6 +22,37 @@ namespace build : root.assign (var); // NULL } + const value& + optional_absolute (scope& root, const variable& var) + { + auto l (root[var]); + + if (!l.defined ()) + return root.assign (var); // NULL + + if (!l.belongs (*global_scope)) // Value from (some) root scope. + return *l; + + // Make the command-line value absolute. This is necessary to avoid + // a warning issued by the config module about global/root scope + // value mismatch. + // + value& v (const_cast (*l)); + + if (v && !v.empty ()) + { + dir_path& d (as (v)); + + if (d.relative ()) + { + d = work / d; + d.normalize (); + } + } + + return root.assign (var) = v; + } + bool specified (scope& r, const string& ns) { diff --git a/build/context b/build/context index b8d178b..920ad1f 100644 --- a/build/context +++ b/build/context @@ -93,6 +93,12 @@ namespace build inline fs_status rmdir (const dir_path& d) {return rmdir (d, d);} + // Note that this function returns not_empty if we try to remove + // a working directory. + // + fs_status + rmdir_r (const dir_path&); + // Return the src/out directory corresponding to the given out/src. The // passed directory should be a sub-directory of out/src_root. // diff --git a/build/context.cxx b/build/context.cxx index 3e3d0c3..aaeb426 100644 --- a/build/context.cxx +++ b/build/context.cxx @@ -47,6 +47,7 @@ namespace build meta_operation_table.insert ("perform"); meta_operation_table.insert ("configure"); meta_operation_table.insert ("disfigure"); + meta_operation_table.insert ("dist"); operation_table.clear (); operation_table.insert ("default"); @@ -100,17 +101,13 @@ namespace build { rule_map& rs (global_scope->rules); - rs.insert (default_id, "alias", alias_rule::instance); - rs.insert (update_id, "alias", alias_rule::instance); - rs.insert (clean_id, "alias", alias_rule::instance); + rs.insert (perform_id, 0, "alias", alias_rule::instance); - rs.insert (default_id, "fsdir", fsdir_rule::instance); - rs.insert (update_id, "fsdir", fsdir_rule::instance); - rs.insert (clean_id, "fsdir", fsdir_rule::instance); + rs.insert (perform_id, update_id, "fsdir", fsdir_rule::instance); + rs.insert (perform_id, clean_id, "fsdir", fsdir_rule::instance); - rs.insert (default_id, "file", file_rule::instance); - rs.insert (update_id, "file", file_rule::instance); - rs.insert (clean_id, "file", file_rule::instance); + rs.insert (perform_id, update_id, "file", file_rule::instance); + rs.insert (perform_id, clean_id, "file", file_rule::instance); } } @@ -162,6 +159,31 @@ namespace build return ms; } + fs_status + rmdir_r (const dir_path& d) + { + using namespace butl; + + if (work.sub (d)) // Don't try to remove working directory. + return rmdir_status::not_empty; + + if (!dir_exists (d)) + return rmdir_status::not_exist; + + text << "rmdir -r " << d; + + try + { + butl::rmdir_r (d); + } + catch (const std::system_error& e) + { + fail << "unable to remove directory " << d << ": " << e.what (); + } + + return rmdir_status::success; + } + dir_path src_out (const dir_path& out, scope& s) { diff --git a/build/cxx/compile.cxx b/build/cxx/compile.cxx index ce921d2..bb42c30 100644 --- a/build/cxx/compile.cxx +++ b/build/cxx/compile.cxx @@ -151,7 +151,7 @@ namespace build { case perform_update_id: return &perform_update; case perform_clean_id: return &perform_clean; - default: return default_recipe; // Forward to prerequisites. + default: assert (false); return default_recipe; } } diff --git a/build/cxx/link.cxx b/build/cxx/link.cxx index 4023105..9602e75 100644 --- a/build/cxx/link.cxx +++ b/build/cxx/link.cxx @@ -722,7 +722,7 @@ namespace build { case perform_update_id: return &perform_update; case perform_clean_id: return &perform_clean; - default: return default_recipe; // Forward to prerequisites. + default: assert (false); return default_recipe; } } diff --git a/build/cxx/module.cxx b/build/cxx/module.cxx index 7f200cd..c2469d9 100644 --- a/build/cxx/module.cxx +++ b/build/cxx/module.cxx @@ -64,31 +64,26 @@ namespace build auto& rs (b.rules); - rs.insert (default_id, "cxx.compile", compile::instance); - rs.insert (update_id, "cxx.compile", compile::instance); - rs.insert (clean_id, "cxx.compile", compile::instance); + rs.insert (perform_id, update_id, "cxx", compile::instance); + rs.insert (perform_id, clean_id, "cxx", compile::instance); - rs.insert (default_id, "cxx.compile", compile::instance); - rs.insert (update_id, "cxx.compile", compile::instance); - rs.insert (clean_id, "cxx.compile", compile::instance); + rs.insert (perform_id, update_id, "cxx", compile::instance); + rs.insert (perform_id, clean_id, "cxx", compile::instance); - rs.insert (default_id, "cxx.link", link::instance); - rs.insert (update_id, "cxx.link", link::instance); - rs.insert (clean_id, "cxx.link", link::instance); + rs.insert (perform_id, update_id, "cxx", link::instance); + rs.insert (perform_id, clean_id, "cxx", link::instance); - rs.insert (default_id, "cxx.link", link::instance); - rs.insert (update_id, "cxx.link", link::instance); - rs.insert (clean_id, "cxx.link", link::instance); + rs.insert (perform_id, update_id, "cxx", link::instance); + rs.insert (perform_id, clean_id, "cxx", link::instance); - rs.insert (default_id, "cxx.link", link::instance); - rs.insert (update_id, "cxx.link", link::instance); - rs.insert (clean_id, "cxx.link", link::instance); + rs.insert (perform_id, update_id, "cxx", link::instance); + rs.insert (perform_id, clean_id, "cxx", link::instance); //@@ Should we check if install module was loaded (see bin)? // - rs.insert (install_id, "cxx.install", install::instance); - rs.insert (install_id, "cxx.install", install::instance); - rs.insert (install_id, "cxx.install", install::instance); + rs.insert (perform_id, install_id, "cxx", install::instance); + rs.insert (perform_id, install_id, "cxx", install::instance); + rs.insert (perform_id, install_id, "cxx", install::instance); } // Enter module variables. diff --git a/build/dist/module b/build/dist/module new file mode 100644 index 0000000..9fad86f --- /dev/null +++ b/build/dist/module @@ -0,0 +1,21 @@ +// file : build/dist/module -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_DIST_MODULE +#define BUILD_DIST_MODULE + +#include +#include + +namespace build +{ + namespace dist + { + extern "C" void + dist_init ( + scope&, scope&, const location&, std::unique_ptr&, bool); + } +} + +#endif // BUILD_DIST_MODULE diff --git a/build/dist/module.cxx b/build/dist/module.cxx new file mode 100644 index 0000000..7b9b340 --- /dev/null +++ b/build/dist/module.cxx @@ -0,0 +1,130 @@ +// file : build/dist/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include + +#include + +#include +#include + +using namespace std; +using namespace butl; + +namespace build +{ + namespace dist + { + static rule rule_; + + extern "C" void + dist_init (scope& r, + scope& b, + const location& l, + std::unique_ptr&, + bool first) + { + tracer trace ("dist::init"); + + if (&r != &b) + fail (l) << "dist module must be initialized in bootstrap.build"; + + if (!first) + { + warn (l) << "multiple dist module initializations"; + return; + } + + const dir_path& out_root (r.path ()); + level4 ([&]{trace << "for " << out_root;}); + + // Register meta-operation. + // + r.meta_operations.insert (dist_id, dist); + + // Register our wildcard rule. Do it explicitly for the alias + // to prevent something like insert(dist_id, test_id) + // taking precedence. + // + r.rules.insert (dist_id, 0, "dist", rule_); + r.rules.insert (dist_id, 0, "dist", rule_); + + // Enter module variables. + // + if (first) + { + variable_pool.find ("dist.package", string_type); + + variable_pool.find ("dist.root", dir_path_type); + variable_pool.find ("config.dist.root", dir_path_type); + + //@@ VAR type + // + variable_pool.find ("dist.cmd", string_type); + variable_pool.find ("config.dist.cmd", string_type); + + variable_pool.find ("dist.archives", strings_type); + variable_pool.find ("config.dist.archives", strings_type); + } + + // Configuration. + // + // Note that we don't use any defaults for root -- the location + // must be explicitly specified or we will complain if and when + // we try to dist. + // + using namespace config; + + bool s (specified (r, "config.dist")); + + // dist.root + // + { + value& v (r.assign ("dist.root")); + + if (s) + { + const value& cv (optional_absolute (r, "config.dist.root")); + + if (cv && !cv.empty ()) + v = cv; + } + } + + // dist.cmd + // + { + value& v (r.assign ("dist.cmd")); + + if (s) + { + const value& cv (required (r, "config.dist.cmd", "install").first); + + if (cv && !cv.empty ()) + v = cv; + } + else + v = "install"; + } + + // dist.archives + // + { + value& v (r.assign ("dist.archives")); + + if (s) + { + const value& cv (optional (r, "config.dist.archives")); + + if (cv && !cv.empty ()) + v = cv; + } + } + } + } +} diff --git a/build/dist/operation b/build/dist/operation new file mode 100644 index 0000000..c7aa014 --- /dev/null +++ b/build/dist/operation @@ -0,0 +1,18 @@ +// file : build/dist/operation -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_DIST_OPERATION +#define BUILD_DIST_OPERATION + +#include + +namespace build +{ + namespace dist + { + extern meta_operation_info dist; + } +} + +#endif // BUILD_DIST_OPERATION diff --git a/build/dist/operation.cxx b/build/dist/operation.cxx new file mode 100644 index 0000000..3c6ec98 --- /dev/null +++ b/build/dist/operation.cxx @@ -0,0 +1,423 @@ +// file : build/dist/operation.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build +{ + namespace dist + { + static void + dist_meta_operation_pre () + { + // Reset the dependency state so that we don't end up with stray + // files from previous batches. + // + // @@ This is called too late, after we have bootstrapped the + // project. + // + //reset (); + } + + static operation_id + dist_operation_pre (operation_id o) + { + if (o != default_id) + fail << "explicit operation specified for dist meta-operation"; + + return o; + } + + static void + dist_match (action, action_targets&) + { + // Don't match anything -- see execute (). + } + + // install -d + // + static void + install (const string& cmd, const dir_path&); + + // install + // + static void + install (const string& cmd, file&, const dir_path&); + + // cd && tar|zip ... . + // + static void + archive (const dir_path& root, const string& pkg, const string& ext); + + static void + dist_execute (action, const action_targets& ts, bool) + { + tracer trace ("dist_execute"); + + // For now we assume all the targets are from the same project. + // + target& t (*static_cast (ts[0])); + scope* rs (t.base_scope ().root_scope ()); + + if (rs == nullptr) + fail << "out of project target " << t; + + const dir_path& out_root (rs->path ()); + const dir_path& src_root (rs->src_path ()); + + if (out_root == src_root) + fail << "in-tree distribution of target " << t << + info << "distribution requires out-of-tree build"; + + // Make sure we have the necessary configuration before + // we get down to business. + // + auto l (rs->vars["dist.root"]); + + if (!l || l->empty ()) + fail << "unknown root distribution directory" << + info << "did you forget to specify config.dist.root?"; + + const dir_path& dist_root (as (*l)); + + if (!dir_exists (dist_root)) + fail << "root distribution directory " << dist_root + << " does not exist"; + + l = rs->vars["dist.package"]; + + if (!l || l->empty ()) + fail << "unknown distribution package name" << + info << "did you forget to set dist.package?"; + + const string& dist_package (as (*l)); + const string& dist_cmd (as (*rs->vars["dist.cmd"])); + + // Get the list of operations supported by this project. Skip + // default_id. + // + for (operations::size_type id (default_id + 1); + id < rs->operations.size (); + ++id) + { + const operation_info* oi (rs->operations[id]); + if (oi == nullptr) + continue; + + // Note that we are not calling operation_pre/post() callbacks + // here since the meta operation is dist and we know what we + // are doing. + // + current_inner_oif = oi; + current_outer_oif = nullptr; + current_mode = oi->mode; + dependency_count = 0; + + action a (dist_id, id); + + if (verb >= 5) + dump (a); + + for (void* v: ts) + { + target& t (*static_cast (v)); + + if (rs != t.base_scope ().root_scope ()) + fail << "out of project target " << t; + + level4 ([&]{trace << diag_doing (a, t);}); + + match (a, t); + } + + if (verb >= 5) + dump (a); + } + + // Add buildfiles that are not normally loaded as part of the + // project, for example, the export stub. They will still be + // ignored on the next step if the user explicitly marked them + // nodist. + // + auto add_adhoc = [&src_root, &trace] (const char* f) + { + path p (src_root / path (f)); + if (file_exists (p)) + { + const char* e (p.extension ()); + targets.insert ( + p.directory (), + p.leaf ().base ().string (), + &extension_pool.find (e == nullptr ? "" : e), // Specified. + trace); + } + }; + + add_adhoc ("build/export.build"); + + // Collect the files. We want to take the snapshot of targets + // since updating some of them may result in more targets being + // entered. + // + action_targets files; + + for (const auto& pt: targets) + { + file* ft (pt->is_a ()); + + if (ft == nullptr) // Not a file. + continue; + + if (ft->dir.sub (src_root)) + { + // Include unless explicitly excluded. + // + files.push_back (ft); + continue; + } + + if (ft->dir.sub (out_root)) + { + // Exclude unless explicitly included. + // + //files.push_back (*ft); + continue; + } + } + + // Make sure what we need to distribute is up to date. + // + { + if (perform.meta_operation_pre != nullptr) + perform.meta_operation_pre (); + + current_mif = &perform; + + if (perform.operation_pre != nullptr) + perform.operation_pre (update_id); + + current_inner_oif = &update; + current_outer_oif = nullptr; + current_mode = update.mode; + dependency_count = 0; + + action a (perform_id, update_id); + + perform.match (a, files); + perform.execute (a, files, true); // Run quiet. + + if (perform.operation_post != nullptr) + perform.operation_post (update_id); + + if (perform.meta_operation_post != nullptr) + perform.meta_operation_post (); + } + + dir_path td (dist_root / dir_path (dist_package)); + + // Clean up the target directory. + // + // @@ Not for incremental dist? + // + if (build::rmdir_r (td) == rmdir_status::not_empty) + fail << "unable to clean target directory " << td; + + install (dist_cmd, td); + + // Copy over all the files. + // + for (void* v: files) + { + file& t (*static_cast (v)); + + // Figure out where this file is inside the target directory. + // + dir_path d (td); + d /= t.dir.sub (src_root) + ? t.dir.leaf (src_root) + : t.dir.leaf (out_root); + + if (!dir_exists (d)) + install (dist_cmd, d); + + install (dist_cmd, t, d); + } + + // Archive if requested. + // + if (auto l = rs->vars["dist.archives"]) + { + for (const string& e: as (*l)) + archive (dist_root, dist_package, e); + } + } + + // install -d + // + static void + install (const string& cmd, const dir_path& d) + { + path reld (relative (d)); + + cstrings args {cmd.c_str (), "-d"}; + + args.push_back ("-m"); + args.push_back ("755"); + args.push_back (reld.string ().c_str ()); + args.push_back (nullptr); + + if (verb) + print_process (args); + else + text << "dist -d " << d; + + try + { + process pr (args.data ()); + + if (!pr.wait ()) + throw failed (); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + } + + // install + // + static void + install (const string& cmd, file& t, const dir_path& d) + { + path reld (relative (d)); + path relf (relative (t.path ())); + + cstrings args {cmd.c_str ()}; + + // Preserve timestamps. This could becomes important if, for + // example, we have pre-generated sources. Note that the + // install-sh script doesn't support this option, while both + // Linux and BSD install's do. + // + args.push_back ("-p"); + + // Assume the file is executable if the owner has execute + // permission, in which case we make it executable for + // everyone. + // + args.push_back ("-m"); + args.push_back ( + (path_permissions (t.path ()) & permissions::xu) == permissions::xu + ? "755" + : "644"); + + args.push_back (relf.string ().c_str ()); + args.push_back (reld.string ().c_str ()); + args.push_back (nullptr); + + if (verb) + print_process (args); + else + text << "dist " << t; + + try + { + process pr (args.data ()); + + if (!pr.wait ()) + throw failed (); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + } + + static void + archive (const dir_path& root, const string& pkg, const string& e) + { + string a (pkg + '.' + e); + + // Delete old archive for good measure. + // + path ap (root / path (a)); + if (file_exists (ap)) + rmfile (ap); + + // Use zip for .zip archives. Everything else goes to tar in the + // auto-compress mode (-a). + // + cstrings args; + if (e == "zip") + args = {"zip", "-rq", a.c_str (), pkg.c_str (), nullptr}; + else + args = {"tar", "-a", "-cf", a.c_str (), pkg.c_str (), nullptr}; + + if (verb) + print_process (args); + else + text << args[0] << " " << ap; + + try + { + // Change child's working directory to dist_root. + // + process pr (root.string ().c_str (), args.data ()); + + if (!pr.wait ()) + throw failed (); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + } + + meta_operation_info dist { + "dist", + "distribute", + "distributing", + "has nothing to distribute", // We cannot "be distributed". + &dist_meta_operation_pre, + &dist_operation_pre, + &load, // normal load + &search, // normal search + &dist_match, + &dist_execute, + nullptr, // operation post + nullptr // meta-operation post + }; + } +} diff --git a/build/dist/rule b/build/dist/rule new file mode 100644 index 0000000..ffb2227 --- /dev/null +++ b/build/dist/rule @@ -0,0 +1,29 @@ +// file : build/dist/rule -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_DIST_RULE +#define BUILD_DIST_RULE + +#include +#include +#include +#include + +namespace build +{ + namespace dist + { + class rule: public build::rule + { + public: + virtual match_result + match (action, target&, const std::string&) const; + + virtual recipe + apply (action, target&, const match_result&) const; + }; + } +} + +#endif // BUILD_DIST_RULE diff --git a/build/dist/rule.cxx b/build/dist/rule.cxx new file mode 100644 index 0000000..bda2ef7 --- /dev/null +++ b/build/dist/rule.cxx @@ -0,0 +1,49 @@ +// file : build/dist/rule.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include + +using namespace std; + +namespace build +{ + namespace dist + { + match_result rule:: + match (action, target& t, const std::string&) const + { + return t; // We always match. + } + + recipe rule:: + apply (action a, target& t, const match_result&) const + { + const dir_path& out_root (t.root_scope ().path ()); + + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + // Skip prerequisites imported from other projects. + // + if (p.proj () != nullptr) + continue; + + // @@ This is where we will handle dist/nodist. + + target& pt (p.search ()); + + // Don't match targets that are outside our project. + // + if (pt.dir.sub (out_root)) + build::match (a, pt); + } + + return noop_recipe; // We will never be executed. + } + } +} diff --git a/build/install/module.cxx b/build/install/module.cxx index 610154a..5bf2876 100644 --- a/build/install/module.cxx +++ b/build/install/module.cxx @@ -63,7 +63,7 @@ namespace build if (spec) { - if (cv != nullptr && *cv && !cv->empty ()) + if (*cv && !cv->empty ()) v = *cv; } else @@ -116,19 +116,9 @@ namespace build // r.operations.insert (install_id, install); - // Register rules. + // Register our file installer rule. // - { - auto& rs (b.rules); - - // Register the standard alias rule for the install operation. - // - rs.insert (install_id, "alias", alias_rule::instance); - - // Register our file installer rule. - // - rs.insert (install_id, "install", rule_); - } + b.rules.insert (perform_id, install_id, "install", rule_); // Enter module variables. // diff --git a/build/install/rule.cxx b/build/install/rule.cxx index 8f67001..b1627be 100644 --- a/build/install/rule.cxx +++ b/build/install/rule.cxx @@ -61,11 +61,6 @@ namespace build if (!mr.bvalue) // Not installable. return noop_recipe; - // In case of install, we don't do anything for other meta-operations. - // - if (a.operation () == install_id && a.meta_operation () != perform_id) - return noop_recipe; - // Ok, if we are here, then this means: // // 1. This target is installable. diff --git a/build/lexer.cxx b/build/lexer.cxx index 9dce949..fb4d9ea 100644 --- a/build/lexer.cxx +++ b/build/lexer.cxx @@ -48,10 +48,6 @@ namespace build } case '$': { - // The following name is lexed in the variable mode. - // - next_mode_ = lexer_mode::variable; - return token (token_type::dollar, sep, ln, cn); } case '(': @@ -147,6 +143,7 @@ namespace build switch (c) { case '/': + case '-': { done = true; break; diff --git a/build/operation b/build/operation index 286e003..8aeede6 100644 --- a/build/operation +++ b/build/operation @@ -109,10 +109,12 @@ namespace build const meta_operation_id configure_id = 2; const meta_operation_id disfigure_id = 3; + const meta_operation_id dist_id = 4; + // The default operation is a special marker that can be used to // indicate that no operation was explicitly specified by the user. // - const operation_id default_id = 1; + const operation_id default_id = 1; // Shall be first. const operation_id update_id = 2; const operation_id clean_id = 3; @@ -210,7 +212,7 @@ namespace build void (*match) (action, action_targets&); - void (*execute) (action, const action_targets&); + void (*execute) (action, const action_targets&, bool quiet); void (*operation_post) (operation_id); // End of operation batch. void (*meta_operation_post) (); // End of meta-operation batch. @@ -245,10 +247,10 @@ namespace build // Execute the action on the list of targets. This is the default // implementation that does just that while issuing appropriate - // diagnostics. + // diagnostics (unless quiet). // void - execute (action, const action_targets&); + execute (action, const action_targets&, bool quiet); extern meta_operation_info perform; @@ -342,6 +344,11 @@ namespace build bool empty () const {return v_.empty ();} + // Note that this is more of a "max index" rather than size. + // + size_type + size () const {return v_.size ();} + private: base_type v_; }; diff --git a/build/operation.cxx b/build/operation.cxx index a92c912..55a926a 100644 --- a/build/operation.cxx +++ b/build/operation.cxx @@ -4,6 +4,7 @@ #include +#include #include #include #include // reference_wrapper @@ -109,7 +110,7 @@ namespace build } void - execute (action a, const action_targets& ts) + execute (action a, const action_targets& ts, bool quiet) { tracer trace ("execute"); @@ -119,7 +120,7 @@ namespace build vector> psp; auto body ( - [a, &psp, &trace] (void* v) + [a, quiet, &psp, &trace] (void* v) { target& t (*static_cast (v)); @@ -129,9 +130,7 @@ namespace build { case target_state::unchanged: { - // Be quiet in pre/post operations. - // - if (a.outer_operation () == 0) + if (!quiet) info << diag_done (a, t); break; } @@ -165,7 +164,7 @@ namespace build { case target_state::unchanged: { - if (a.outer_operation () == 0) + if (!quiet) info << diag_done (a, t); break; } diff --git a/build/parser b/build/parser index c6d275b..3374ac0 100644 --- a/build/parser +++ b/build/parser @@ -108,6 +108,11 @@ namespace build void process_default_target (token&); + // Enter buildfile as a target. + // + void + enter_buildfile (const path&); + // Lexer. // private: diff --git a/build/parser.cxx b/build/parser.cxx index 03c20d0..9c50d17 100644 --- a/build/parser.cxx +++ b/build/parser.cxx @@ -36,6 +36,8 @@ namespace build void parser:: parse_buildfile (istream& is, const path& p, scope& root, scope& base) { + enter_buildfile (p); + string rw (diag_relative (p)); // Relative to work. path_ = &rw; @@ -446,6 +448,8 @@ namespace build level4 ([&]{trace (t) << "entering " << p;}); + enter_buildfile (p); + string rw (diag_relative (p)); // Relative to work. const string* op (path_); path_ = &rw; @@ -585,6 +589,8 @@ namespace build level4 ([&]{trace (t) << "entering " << p;}); + enter_buildfile (p); + string rw (diag_relative (p)); // Relative to work. const string* op (path_); path_ = &rw; @@ -1015,11 +1021,23 @@ namespace build // if (tt == type::dollar) { + // Switch to the variable name mode. We want to use this + // mode for $foo but not for $(foo). Since we don't know + // whether the next token is a paren or a name, we turn + // it on and turn it off if what we get next is a paren + // so that the following name is scanned in the normal + // mode. + // + lexer_->mode (lexer_mode::variable); + next (t, tt); bool paren (tt == type::lparen); if (paren) + { + lexer_->mode (lexer_mode::normal); next (t, tt); + } if (tt != type::name) fail (t) << "variable name expected instead of " << t; @@ -1499,6 +1517,19 @@ namespace build ct.prerequisites.emplace_back (p); } + void parser:: + enter_buildfile (const path& p) + { + tracer trace ("parser::enter_buildfile", &path_); + + const char* e (p.extension ()); + targets.insert ( + p.directory (), + p.leaf ().base ().string (), + &extension_pool.find (e == nullptr ? "" : e), // Always specified. + trace); + } + token_type parser:: next (token& t, token_type& tt) { diff --git a/build/rule-map b/build/rule-map index 883800b..0ef0036 100644 --- a/build/rule-map +++ b/build/rule-map @@ -8,8 +8,9 @@ #include #include #include +#include // unique_ptr #include -#include // reference_wrapper +#include // reference_wrapper #include @@ -25,21 +26,76 @@ namespace build butl::prefix_map, '.'>>; - // This is an "indexed map" with (operation_id - 1) being the - // index. + // This is an "indexed map" with operation_id being the index. Entry + // with id 0 is a wildcard. // - class rule_map: public std::vector + class operation_rule_map { public: template void insert (operation_id oid, const char* hint, rule& r) { - if (oid > size ()) - resize (oid < 3 ? 3 : oid); // 3 is the number of builtin operations. + // 3 is the number of builtin operations. + // + if (oid >= map_.size ()) + map_.resize ((oid < 3 ? 3 : oid) + 1); - (*this)[oid - 1][typeid (T)].emplace (hint, r); + map_[oid][typeid (T)].emplace (hint, r); } + + // Return NULL if not found. + // + const target_type_rule_map* + operator[] (operation_id oid) const + { + return map_.size () > oid ? &map_[oid] : nullptr; + } + + private: + std::vector map_; + }; + + // This is another indexed map but this time meta_operation_id is the + // index. The implementation is different, however: here we use a linked + // list with the first, statically-allocated node corresponding to the + // perform meta-operation. The idea is to try and get away with a dynamic + // allocation for the common cases since most rules will be registered + // for perform, at least on non-root scopes. + // + class rule_map + { + public: + template + void + insert (meta_operation_id mid, operation_id oid, const char* hint, rule& r) + { + if (mid_ == mid) + map_.insert (oid, hint, r); + else + { + if (next_ == nullptr) + next_.reset (new rule_map (mid)); + + next_->insert (mid, oid, hint, r); + } + } + + // Return NULL if not found. + // + const operation_rule_map* + operator[] (meta_operation_id mid) const + { + return mid == mid_ ? &map_ : next_ == nullptr ? nullptr : (*next_)[mid]; + } + + explicit + rule_map (meta_operation_id mid = perform_id): mid_ (mid) {} + + private: + meta_operation_id mid_; + operation_rule_map map_; + std::unique_ptr next_; }; } diff --git a/build/rule.cxx b/build/rule.cxx index 7bf030d..b9a989b 100644 --- a/build/rule.cxx +++ b/build/rule.cxx @@ -38,13 +38,9 @@ namespace build // for every action (because that's the condition for us matching), // for some actions this is clearly a waste. Say, perform_clean: we // are not doing anything for this action so not checking if the file - // exists seems harmless. What about, say, configure_update? Again, - // whether we match or not, there is nothing to be done for this - // action. And, who knows, maybe the file doesn't exist during - // configure_update but will magically appear during perform_update. - // So the overall guideline seems to be this: if we don't do anything - // for the action (other than performing it on the prerequisites), - // then we match. + // exists seems harmless. So the overall guideline seems to be this: + // if we don't do anything for the action (other than performing it + // on the prerequisites), then we match. // switch (a) { @@ -181,7 +177,7 @@ namespace build { case perform_update_id: return &perform_update; case perform_clean_id: return &perform_clean; - default: return default_recipe; // Forward to prerequisites. + default: assert (false); return default_recipe; } } @@ -196,7 +192,7 @@ namespace build if (!t.prerequisite_targets.empty ()) ts = execute_prerequisites (a, t); - const path& d (t.dir); // Everything is in t.dir. + const dir_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 diff --git a/build/target b/build/target index 2c05ea1..f2b093e 100644 --- a/build/target +++ b/build/target @@ -925,6 +925,16 @@ namespace build static const target_type static_type; }; + class buildfile: public file + { + public: + using file::file; + + public: + virtual const target_type& type () const {return static_type;} + static const target_type static_type; + }; + // Common documentation file targets. // // @@ Maybe these should be in the built-in doc module? diff --git a/build/target.cxx b/build/target.cxx index 6315d04..669ff7a 100644 --- a/build/target.cxx +++ b/build/target.cxx @@ -493,6 +493,26 @@ namespace build false }; + static const std::string& + buildfile_target_extension (const target_key& tk, scope&) + { + // If the name is special 'buildfile', then there is no extension, + // otherwise it is .build. + // + return extension_pool.find (*tk.name == "buildfile" ? "" : "build"); + } + + const target_type buildfile::static_type + { + typeid (buildfile), + "buildfile", + &file::static_type, + &file_factory, + &buildfile_target_extension, + &search_file, + false + }; + constexpr const char doc_ext[] = ""; const target_type doc::static_type { diff --git a/build/test/module.cxx b/build/test/module.cxx index 9495275..8c0df38 100644 --- a/build/test/module.cxx +++ b/build/test/module.cxx @@ -51,18 +51,20 @@ namespace build { auto& rs (r.rules); - // Register the standard alias rule for the test operation. + // Register our test running rule. // - rs.insert (test_id, "alias", alias_rule::instance); + rs.insert (perform_id, test_id, "test", rule_); - // Register our test running rule. + // Register our rule for the dist meta-operation. We need + // to do this because we have "ad-hoc prerequisites", test + // input/output files, that need to be entered into the + // target list. // - rs.insert (test_id, "test", rule_); + rs.insert (dist_id, test_id, "test", rule_); } // Enter module variables. // - if (first) { variable_pool.find ("test", bool_type); variable_pool.find ("test.input", name_type); diff --git a/build/test/rule.cxx b/build/test/rule.cxx index eecd613..25baaa7 100644 --- a/build/test/rule.cxx +++ b/build/test/rule.cxx @@ -113,11 +113,6 @@ namespace build if (!mr.bvalue) // Not a test. return noop_recipe; - // In case of test, we don't do anything for other meta-operations. - // - if (a.operation () == test_id && a.meta_operation () != perform_id) - return noop_recipe; - // Ok, if we are here, then this means: // // 1. This target is a test. @@ -218,18 +213,21 @@ namespace build } } - if (ot != nullptr && in == on) + if (ot != nullptr) { - build::match (a, *ot); - - if (ot->state () == target_state::unchanged) + if (in != on) { - unmatch (a, *ot); - ot = nullptr; + build::match (a, *ot); + + if (ot->state () == target_state::unchanged) + { + unmatch (a, *ot); + ot = nullptr; + } } + else + ot = it; } - else - ot = it; // Find the "real" update rule, that is, the rule that would diff --git a/doc/language.txt b/doc/language.txt new file mode 100644 index 0000000..97815f6 --- /dev/null +++ b/doc/language.txt @@ -0,0 +1,5 @@ +- Normally variable names should be like C-identifiers plus '.'. In + particular, in simple expansion (i.e., $package-$version) we assume + characters [/-] end the name. But this can be overrident with paren- + expansion (i.e., $(package-version)). In assignment we always use + full name until space, '=', or '=+'. diff --git a/tests/.gitignore b/tests/.gitignore index c8ad1f0..c7bd86c 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -3,4 +3,4 @@ config.build # Temporary out-of-tree build directories. # -*-build +*-out diff --git a/tests/cli/simple/build/bootstrap.build b/tests/cli/simple/build/bootstrap.build index e2fd93f..2c116c9 100644 --- a/tests/cli/simple/build/bootstrap.build +++ b/tests/cli/simple/build/bootstrap.build @@ -1,2 +1,3 @@ project = cli-simple +amalgamation = # Disabled. using config diff --git a/tests/dist/simple/README b/tests/dist/simple/README new file mode 100644 index 0000000..e69de29 diff --git a/tests/dist/simple/bootstrap b/tests/dist/simple/bootstrap new file mode 100755 index 0000000..e69de29 diff --git a/tests/dist/simple/build/bootstrap.build b/tests/dist/simple/build/bootstrap.build new file mode 100644 index 0000000..0cd9015 --- /dev/null +++ b/tests/dist/simple/build/bootstrap.build @@ -0,0 +1,9 @@ +project = dist-simple +version = 1.0.0 +amalgamation = # Disabled. +using config +using dist +using test +using install + +dist.package = $project-$version diff --git a/tests/dist/simple/build/export.build b/tests/dist/simple/build/export.build new file mode 100644 index 0000000..e69de29 diff --git a/tests/dist/simple/buildfile b/tests/dist/simple/buildfile new file mode 100644 index 0000000..27f143d --- /dev/null +++ b/tests/dist/simple/buildfile @@ -0,0 +1,7 @@ +using cxx + +hxx.ext = hxx +cxx.ext = cxx + +exe{driver}: cxx{driver} doc{README} file{bootstrap} +exe{driver}: test.output = test.out diff --git a/tests/dist/simple/driver.cxx b/tests/dist/simple/driver.cxx new file mode 100644 index 0000000..293d7ff --- /dev/null +++ b/tests/dist/simple/driver.cxx @@ -0,0 +1,10 @@ +#include + +using namespace std; + +int +main () +{ + cerr << "test is running (stderr)" << endl; + cout << "test is running (stdout)" << endl; +} diff --git a/tests/dist/simple/test.out b/tests/dist/simple/test.out new file mode 100644 index 0000000..5d63fab --- /dev/null +++ b/tests/dist/simple/test.out @@ -0,0 +1 @@ +test is running (stdout) -- cgit v1.1