aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/algorithm3
-rw-r--r--build/algorithm.cxx197
-rw-r--r--build/b.cxx8
-rw-r--r--build/bin/module.cxx12
-rw-r--r--build/buildfile5
-rw-r--r--build/cli/module.cxx20
-rw-r--r--build/cli/rule.cxx2
-rw-r--r--build/config/operation.cxx34
-rw-r--r--build/config/utility19
-rw-r--r--build/config/utility.cxx33
-rw-r--r--build/context6
-rw-r--r--build/context.cxx40
-rw-r--r--build/cxx/compile.cxx2
-rw-r--r--build/cxx/link.cxx2
-rw-r--r--build/cxx/module.cxx31
-rw-r--r--build/dist/module21
-rw-r--r--build/dist/module.cxx130
-rw-r--r--build/dist/operation18
-rw-r--r--build/dist/operation.cxx423
-rw-r--r--build/dist/rule29
-rw-r--r--build/dist/rule.cxx49
-rw-r--r--build/install/module.cxx16
-rw-r--r--build/install/rule.cxx5
-rw-r--r--build/lexer.cxx5
-rw-r--r--build/operation15
-rw-r--r--build/operation.cxx11
-rw-r--r--build/parser5
-rw-r--r--build/parser.cxx31
-rw-r--r--build/rule-map70
-rw-r--r--build/rule.cxx14
-rw-r--r--build/target10
-rw-r--r--build/target.cxx20
-rw-r--r--build/test/module.cxx12
-rw-r--r--build/test/rule.cxx24
-rw-r--r--doc/language.txt5
-rw-r--r--tests/.gitignore2
-rw-r--r--tests/cli/simple/build/bootstrap.build1
-rw-r--r--tests/dist/simple/README0
-rwxr-xr-xtests/dist/simple/bootstrap0
-rw-r--r--tests/dist/simple/build/bootstrap.build9
-rw-r--r--tests/dist/simple/build/export.build0
-rw-r--r--tests/dist/simple/buildfile7
-rw-r--r--tests/dist/simple/driver.cxx10
-rw-r--r--tests/dist/simple/test.out1
44 files changed, 1127 insertions, 230 deletions
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 <build/config/module>
+#include <build/dist/module>
#include <build/bin/module>
#include <build/cxx/module>
#include <build/cli/module>
@@ -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<obj> (default_id, "bin.obj", obj_);
- rs.insert<obj> (update_id, "bin.obj", obj_);
- rs.insert<obj> (clean_id, "bin.obj", obj_);
+ rs.insert<obj> (perform_id, update_id, "bin.obj", obj_);
+ rs.insert<obj> (perform_id, clean_id, "bin.obj", obj_);
- rs.insert<lib> (default_id, "bin.lib", lib_);
- rs.insert<lib> (update_id, "bin.lib", lib_);
- rs.insert<lib> (clean_id, "bin.lib", lib_);
+ rs.insert<lib> (perform_id, update_id, "bin.lib", lib_);
+ rs.insert<lib> (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<lib> (install_id, "bin.lib", lib_);
+ rs.insert<lib> (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<cli_cxx> (default_id, "cli.compile", compile_);
- rs.insert<cli_cxx> (update_id, "cli.compile", compile_);
- rs.insert<cli_cxx> (clean_id, "cli.compile", compile_);
+ rs.insert<cli_cxx> (perform_id, update_id, "cli", compile_);
+ rs.insert<cli_cxx> (perform_id, clean_id, "cli", compile_);
- rs.insert<cxx::hxx> (default_id, "cli.compile", compile_);
- rs.insert<cxx::hxx> (update_id, "cli.compile", compile_);
- rs.insert<cxx::hxx> (clean_id, "cli.compile", compile_);
+ rs.insert<cxx::hxx> (perform_id, update_id, "cli", compile_);
+ rs.insert<cxx::hxx> (perform_id, clean_id, "cli", compile_);
- rs.insert<cxx::cxx> (default_id, "cli.compile", compile_);
- rs.insert<cxx::cxx> (update_id, "cli.compile", compile_);
- rs.insert<cxx::cxx> (clean_id, "cli.compile", compile_);
+ rs.insert<cxx::cxx> (perform_id, update_id, "cli", compile_);
+ rs.insert<cxx::cxx> (perform_id, clean_id, "cli", compile_);
- rs.insert<cxx::ixx> (default_id, "cli.compile", compile_);
- rs.insert<cxx::ixx> (update_id, "cli.compile", compile_);
- rs.insert<cxx::ixx> (clean_id, "cli.compile", compile_);
+ rs.insert<cxx::ixx> (perform_id, update_id, "cli", compile_);
+ rs.insert<cxx::ixx> (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<target*> (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<scope*> (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 <build/config/utility>
+#include <build/context>
+
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<value&> (*l));
+
+ if (v && !v.empty ())
+ {
+ dir_path& d (as<dir_path> (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<butl::rmdir_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<butl::rmdir_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<alias> (default_id, "alias", alias_rule::instance);
- rs.insert<alias> (update_id, "alias", alias_rule::instance);
- rs.insert<alias> (clean_id, "alias", alias_rule::instance);
+ rs.insert<alias> (perform_id, 0, "alias", alias_rule::instance);
- rs.insert<fsdir> (default_id, "fsdir", fsdir_rule::instance);
- rs.insert<fsdir> (update_id, "fsdir", fsdir_rule::instance);
- rs.insert<fsdir> (clean_id, "fsdir", fsdir_rule::instance);
+ rs.insert<fsdir> (perform_id, update_id, "fsdir", fsdir_rule::instance);
+ rs.insert<fsdir> (perform_id, clean_id, "fsdir", fsdir_rule::instance);
- rs.insert<file> (default_id, "file", file_rule::instance);
- rs.insert<file> (update_id, "file", file_rule::instance);
- rs.insert<file> (clean_id, "file", file_rule::instance);
+ rs.insert<file> (perform_id, update_id, "file", file_rule::instance);
+ rs.insert<file> (perform_id, clean_id, "file", file_rule::instance);
}
}
@@ -162,6 +159,31 @@ namespace build
return ms;
}
+ fs_status<butl::rmdir_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<obja> (default_id, "cxx.compile", compile::instance);
- rs.insert<obja> (update_id, "cxx.compile", compile::instance);
- rs.insert<obja> (clean_id, "cxx.compile", compile::instance);
+ rs.insert<obja> (perform_id, update_id, "cxx", compile::instance);
+ rs.insert<obja> (perform_id, clean_id, "cxx", compile::instance);
- rs.insert<objso> (default_id, "cxx.compile", compile::instance);
- rs.insert<objso> (update_id, "cxx.compile", compile::instance);
- rs.insert<objso> (clean_id, "cxx.compile", compile::instance);
+ rs.insert<objso> (perform_id, update_id, "cxx", compile::instance);
+ rs.insert<objso> (perform_id, clean_id, "cxx", compile::instance);
- rs.insert<exe> (default_id, "cxx.link", link::instance);
- rs.insert<exe> (update_id, "cxx.link", link::instance);
- rs.insert<exe> (clean_id, "cxx.link", link::instance);
+ rs.insert<exe> (perform_id, update_id, "cxx", link::instance);
+ rs.insert<exe> (perform_id, clean_id, "cxx", link::instance);
- rs.insert<liba> (default_id, "cxx.link", link::instance);
- rs.insert<liba> (update_id, "cxx.link", link::instance);
- rs.insert<liba> (clean_id, "cxx.link", link::instance);
+ rs.insert<liba> (perform_id, update_id, "cxx", link::instance);
+ rs.insert<liba> (perform_id, clean_id, "cxx", link::instance);
- rs.insert<libso> (default_id, "cxx.link", link::instance);
- rs.insert<libso> (update_id, "cxx.link", link::instance);
- rs.insert<libso> (clean_id, "cxx.link", link::instance);
+ rs.insert<libso> (perform_id, update_id, "cxx", link::instance);
+ rs.insert<libso> (perform_id, clean_id, "cxx", link::instance);
//@@ Should we check if install module was loaded (see bin)?
//
- rs.insert<exe> (install_id, "cxx.install", install::instance);
- rs.insert<liba> (install_id, "cxx.install", install::instance);
- rs.insert<libso> (install_id, "cxx.install", install::instance);
+ rs.insert<exe> (perform_id, install_id, "cxx", install::instance);
+ rs.insert<liba> (perform_id, install_id, "cxx", install::instance);
+ rs.insert<libso> (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 <build/types>
+#include <build/module>
+
+namespace build
+{
+ namespace dist
+ {
+ extern "C" void
+ dist_init (
+ scope&, scope&, const location&, std::unique_ptr<module>&, 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 <build/dist/module>
+
+#include <build/scope>
+#include <build/file>
+#include <build/diagnostics>
+
+#include <build/config/utility>
+
+#include <build/dist/rule>
+#include <build/dist/operation>
+
+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<module>&,
+ 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<target>(dist_id, test_id)
+ // taking precedence.
+ //
+ r.rules.insert<target> (dist_id, 0, "dist", rule_);
+ r.rules.insert<alias> (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 <build/operation>
+
+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 <build/dist/operation>
+
+#include <cassert>
+
+#include <butl/process>
+#include <butl/filesystem>
+
+#include <build/file>
+#include <build/dump>
+#include <build/scope>
+#include <build/target>
+#include <build/context>
+#include <build/algorithm>
+#include <build/diagnostics>
+
+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 <dir>
+ //
+ static void
+ install (const string& cmd, const dir_path&);
+
+ // install <file> <dir>
+ //
+ static void
+ install (const string& cmd, file&, const dir_path&);
+
+ // cd <root> && tar|zip ... <pkg>.<ext> <pkg>
+ //
+ 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<target*> (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<dir_path> (*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<string> (*l));
+ const string& dist_cmd (as<string> (*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<target*> (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<buildfile> (
+ 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<file> ());
+
+ 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<file*> (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<strings> (*l))
+ archive (dist_root, dist_package, e);
+ }
+ }
+
+ // install -d <dir>
+ //
+ 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 <file> <dir>
+ //
+ 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 <build/rule>
+#include <build/types>
+#include <build/target>
+#include <build/operation>
+
+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 <build/dist/rule>
+
+#include <build/scope>
+#include <build/target>
+#include <build/algorithm>
+#include <build/diagnostics>
+
+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<alias> (install_id, "alias", alias_rule::instance);
-
- // Register our file installer rule.
- //
- rs.insert<file> (install_id, "install", rule_);
- }
+ b.rules.insert<file> (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 <build/operation>
+#include <vector>
#include <ostream>
#include <cassert>
#include <functional> // 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<reference_wrapper<target>> psp;
auto body (
- [a, &psp, &trace] (void* v)
+ [a, quiet, &psp, &trace] (void* v)
{
target& t (*static_cast<target*> (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<buildfile> (
+ 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 <map>
#include <vector>
#include <string>
+#include <memory> // unique_ptr
#include <typeindex>
-#include <functional> // reference_wrapper
+#include <functional> // reference_wrapper
#include <butl/prefix-map>
@@ -25,21 +26,76 @@ namespace build
butl::prefix_map<std::string, // Rule hint.
std::reference_wrapper<rule>, '.'>>;
- // 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<target_type_rule_map>
+ class operation_rule_map
{
public:
template <typename T>
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<target_type_rule_map> 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 <typename T>
+ void
+ insert (meta_operation_id mid, operation_id oid, const char* hint, rule& r)
+ {
+ if (mid_ == mid)
+ map_.insert<T> (oid, hint, r);
+ else
+ {
+ if (next_ == nullptr)
+ next_.reset (new rule_map (mid));
+
+ next_->insert<T> (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<rule_map> 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>,
+ &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<alias> (test_id, "alias", alias_rule::instance);
+ rs.insert<target> (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<target> (test_id, "test", rule_);
+ rs.insert<target> (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
--- /dev/null
+++ b/tests/dist/simple/README
diff --git a/tests/dist/simple/bootstrap b/tests/dist/simple/bootstrap
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/tests/dist/simple/bootstrap
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
--- /dev/null
+++ b/tests/dist/simple/build/export.build
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 <iostream>
+
+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)