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