aboutsummaryrefslogtreecommitdiff
path: root/build/rule.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'build/rule.cxx')
-rw-r--r--build/rule.cxx247
1 files changed, 190 insertions, 57 deletions
diff --git a/build/rule.cxx b/build/rule.cxx
index f193760..2359445 100644
--- a/build/rule.cxx
+++ b/build/rule.cxx
@@ -10,7 +10,8 @@
#include <build/algorithm>
#include <build/diagnostics>
#include <build/timestamp>
-#include <build/mkdir>
+#include <build/filesystem>
+#include <build/context>
using namespace std;
@@ -21,52 +22,94 @@ namespace build
// path_rule
//
+ // Note that this rule is special. It is the last, fallback rule. If
+ // it doesn't match, then no other rule can possibly match and we have
+ // an error. It also cannot be ambigious with any other rule. As a
+ // result the below implementation bends or ignores quite a few rules
+ // that normal implementations should follow. So you probably shouldn't
+ // use it as a guide to implement your own, normal, rules.
+ //
void* path_rule::
match (action a, target& t, const string&) const
{
- // @@ TODO:
- //
- // - need to try all the target-type-specific extensions, just
- // like search_existing_file().
+ // While strictly speaking we should check for the file's existence
+ // 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.
//
- path_target& pt (dynamic_cast<path_target&> (t));
-
- if (pt.path ().empty ())
+ switch (a)
{
- path p (t.dir / path (pt.name));
+ case perform_update_id:
+ {
+ // @@ TODO:
+ //
+ // - need to try all the target-type-specific extensions, just
+ // like search_existing_file().
+ //
+ path_target& pt (dynamic_cast<path_target&> (t));
- // @@ TMP: target name as an extension.
- //
- const string& e (pt.ext != nullptr ? *pt.ext : pt.type ().name);
+ if (pt.path ().empty ())
+ {
+ path p (t.dir / path (pt.name));
+
+ // @@ TMP: target name as an extension.
+ //
+ const string& e (pt.ext != nullptr ? *pt.ext : pt.type ().name);
+
+ if (!e.empty ())
+ {
+ p += '.';
+ p += e;
+ }
- if (!e.empty ())
+ // While strictly speaking we shouldn't do this in match(),
+ // no other rule should ever be ambiguous with the fallback
+ // one.
+ //
+ pt.path (move (p));
+ }
+
+ return pt.mtime () != timestamp_nonexistent ? &t : nullptr;
+ }
+ default:
{
- p += '.';
- p += e;
+ return &t;
}
-
- // While strictly speaking we shouldn't do this in match(),
- // no other rule should ever be ambiguous with this fallback
- // one.
- //
- pt.path (move (p));
}
-
- return pt.mtime () != timestamp_nonexistent ? &t : nullptr;
}
recipe path_rule::
apply (action a, target& t, void*) const
{
+ // Update triggers the update of this target's prerequisites
+ // so it would seem natural that we should also trigger their
+ // cleanup. However, this possibility is rather theoretical
+ // since such an update would render this target out of date
+ // which in turn would lead to an error. So until we see a
+ // real use-case for this functionality, we simply ignore
+ // the clean operation.
+ //
+ if (a.operation () == clean_id)
+ return noop_recipe;
+
// Search and match all the prerequisites.
//
search_and_match (a, t);
- return &update;
+ return a == perform_update_id
+ ? &perform_update
+ : t.prerequisites.empty () ? noop_recipe : default_recipe;
}
target_state path_rule::
- update (action a, target& t)
+ perform_update (action a, target& t)
{
// Make sure the target is not older than any of its prerequisites.
//
@@ -75,7 +118,7 @@ namespace build
for (const prerequisite& p: t.prerequisites)
{
target& pt (*p.target);
- target_state ts (update (a, pt));
+ target_state ts (execute (a, pt));
// If this is an mtime-based target, then compare timestamps.
//
@@ -113,17 +156,17 @@ namespace build
recipe dir_rule::
apply (action a, target& t, void*) const
{
- search_and_match (a, t);
- return &update;
- }
-
- target_state dir_rule::
- update (action a, target& t)
- {
- // Return changed if any of our prerequsites were updated and
- // unchanged otherwise.
+ // When cleaning, ignore prerequisites that are not in the same
+ // or a subdirectory of ours.
//
- return execute_prerequisites (a, t);
+ switch (a.operation ())
+ {
+ case update_id: search_and_match (a, t); break;
+ case clean_id: search_and_match (a, t, t.dir); break;
+ default: assert (false);
+ }
+
+ return default_recipe;
}
// fsdir_rule
@@ -137,47 +180,137 @@ namespace build
recipe fsdir_rule::
apply (action a, target& t, void*) const
{
- // Let's not allow any prerequisites for this target since it
- // doesn't make much sense. The sole purpose of this target type
- // is to create a directory.
- //
- if (!t.prerequisites.empty ())
- fail << "no prerequisites allowed for target " << t;
+ switch (a.operation ())
+ {
+ case update_id:
+ {
+ search_and_match (a, t);
+ break;
+ }
+ 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;
+ }
+ default:
+ assert (false);
+ }
- return &update;
+ switch (a)
+ {
+ case perform_update_id: return &perform_update;
+ case perform_clean_id: return &perform_clean;
+ default: return noop_recipe;
+ }
}
target_state fsdir_rule::
- update (action a, target& t)
+ perform_update (action a, target& t)
{
- path d (t.dir / path (t.name));
+ target_state ts (target_state::unchanged);
- // Add the extension back if it was specified.
+ // First update prerequisites (e.g. create parent directories)
+ // then create this directory.
//
- if (t.ext != nullptr)
+ if (!t.prerequisites.empty ())
+ ts = execute_prerequisites (a, t);
+
+ const path& d (t.dir); // Everything is in t.dir.
+
+ if (path_mtime (d) == timestamp_nonexistent)
{
- d += '.';
- d += *t.ext;
+ if (verb >= 1)
+ text << "mkdir " << d.string ();
+ else
+ text << "mkdir " << t;
+
+ try
+ {
+ mkdir (d);
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to create directory " << d.string () << ": "
+ << e.what ();
+ }
+
+ ts = target_state::changed;
}
- if (path_mtime (d) != timestamp_nonexistent)
+ return ts;
+ }
+
+ target_state fsdir_rule::
+ perform_clean (action a, target& t)
+ {
+ // Wait until the last dependent to get an empty directory.
+ //
+ if (t.dependents != 0)
+ {
+ t.state = target_state::unknown;
return target_state::unchanged;
+ }
- if (verb >= 1)
- text << "mkdir " << d.string ();
- else
- text << "mkdir " << t; //@@ Probably only show if [show]?
+ // 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
{
- mkdir (d);
+ rs = !w ? try_rmdir (d) : rmdir_status::not_empty;
}
catch (const system_error& e)
{
- fail << "unable to create directory " << d.string () << ": "
+ if (verb >= 1)
+ text << "rmdir " << d.string ();
+ else
+ text << "rmdir " << t;
+
+ fail << "unable to delete directory " << d.string () << ": "
<< e.what ();
}
- return target_state::changed;
+ 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;
+ }
+
+ target_state ts (target_state::unchanged);
+
+ if (!t.prerequisites.empty ())
+ ts = execute_prerequisites (a, t);
+
+ return rs == rmdir_status::success ? target_state::changed : ts;
}
}