aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-03-12 15:43:17 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-03-12 15:43:17 +0200
commitcf6b3e34b59ad120111e0c1ead779bbb3a70c38d (patch)
tree424e9def98c65d9080e72a69064334c6716fb82b
parent5925c11a1fe8b2e02b790dd40b031ae005d5b68f (diff)
Implement clean operation
-rw-r--r--build/algorithm20
-rw-r--r--build/algorithm.cxx99
-rw-r--r--build/algorithm.ixx27
-rw-r--r--build/algorithm.txx5
-rw-r--r--build/b.cxx22
-rw-r--r--build/buildfile2
-rw-r--r--build/cxx/rule16
-rw-r--r--build/cxx/rule.cxx84
-rw-r--r--build/filesystem40
-rw-r--r--build/filesystem.cxx56
-rw-r--r--build/mkdir22
-rw-r--r--build/mkdir.cxx21
-rw-r--r--build/path3
-rw-r--r--build/rule13
-rw-r--r--build/rule.cxx247
-rw-r--r--build/target80
-rw-r--r--build/target.cxx17
17 files changed, 584 insertions, 190 deletions
diff --git a/build/algorithm b/build/algorithm
index 8ce35f8..e4b319a 100644
--- a/build/algorithm
+++ b/build/algorithm
@@ -31,6 +31,12 @@ namespace build
void
search_and_match (action, target&);
+ // As above but ignores (does not match) prerequsites that are not
+ // in the same or a subdirectory of dir.
+ //
+ void
+ search_and_match (action, target&, const path& dir);
+
// Execute the action on target, assuming a rule has been matched
// and the recipe for this action has been set.
//
@@ -38,8 +44,10 @@ namespace build
execute (action, target&);
// The default prerequisite execute implementation. It calls execute()
- // for each prerequisite in a loop. Returns target_state::changed
- // if any of them were changed and target_state::unchanged otherwise.
+ // on each non-ignored (NULL target) prerequisite in a loop. Returns
+ // target_state::changed if any of them were changed and
+ // target_state::unchanged otherwise. Note that this function can be
+ // used as a recipe.
//
target_state
execute_prerequisites (action, target&);
@@ -53,11 +61,17 @@ namespace build
// Another version of the above that does two extra things for the
// caller: it determines whether the action needs to be executed on
// the target based on the passed timestamp and, if so, finds a
- // prerequisite of the specified type (e.g., source file).
+ // prerequisite of the specified type (e.g., a source file).
//
template <typename T>
T*
execute_prerequisites (action, target&, const timestamp&);
+
+ // Standard perform(clean) action implementation for the file target
+ // or derived.
+ //
+ target_state
+ perform_clean_file (action, target&);
}
#include <build/algorithm.ixx>
diff --git a/build/algorithm.cxx b/build/algorithm.cxx
index 46387b3..cccda49 100644
--- a/build/algorithm.cxx
+++ b/build/algorithm.cxx
@@ -7,6 +7,7 @@
#include <memory> // unique_ptr
#include <utility> // move
#include <cassert>
+#include <system_error>
#include <build/path>
#include <build/scope>
@@ -15,6 +16,7 @@
#include <build/rule>
#include <build/search>
#include <build/utility>
+#include <build/filesystem>
#include <build/diagnostics>
using namespace std;
@@ -22,7 +24,7 @@ using namespace std;
namespace build
{
target&
- search (prerequisite& p)
+ search_impl (prerequisite& p)
{
assert (p.target == nullptr);
@@ -149,24 +151,33 @@ namespace build
search_and_match (action a, target& t)
{
for (prerequisite& p: t.prerequisites)
+ match (a, search (p));
+ }
+
+ void
+ search_and_match (action a, target& t, const path& d)
+ {
+ for (prerequisite& p: t.prerequisites)
{
- if (p.target == nullptr)
- search (p);
+ target& pt (search (p));
- match (a, *p.target);
+ if (pt.dir.sub (d))
+ match (a, pt);
+ else
+ p.target = nullptr; // Ignore.
}
}
target_state
- execute (action a, target& t)
+ execute_impl (action a, target& t)
{
// Implementation with some multi-threading ideas in mind.
//
- switch (target_state ts = t.state ())
+ switch (target_state ts = t.state)
{
case target_state::unknown:
{
- t.state (target_state::failed); // So the rule can just throw.
+ t.state = target_state::failed; // So the rule can just throw.
auto g (
make_exception_guard (
@@ -175,12 +186,17 @@ namespace build
ts = t.recipe (a) (a, t);
assert (ts != target_state::unknown && ts != target_state::failed);
- t.state (ts);
+
+ // The recipe may have set the target's state manually.
+ //
+ if (t.state == target_state::failed)
+ t.state = ts;
+
return ts;
}
case target_state::unchanged:
case target_state::changed:
- return ts;
+ assert (false); // Should have been handled by inline execute().
case target_state::failed:
throw failed ();
}
@@ -193,9 +209,12 @@ namespace build
for (const prerequisite& p: t.prerequisites)
{
- assert (p.target != nullptr);
+ if (p.target == nullptr) // Skip ignored.
+ continue;
+
+ target& pt (*p.target);
- if (execute (a, *p.target) != target_state::unchanged)
+ if (execute (a, pt) != target_state::unchanged)
ts = target_state::changed;
}
@@ -209,9 +228,10 @@ namespace build
for (const prerequisite& p: t.prerequisites)
{
- assert (p.target != nullptr);
- target& pt (*p.target);
+ if (p.target == nullptr) // Skip ignored.
+ continue;
+ target& pt (*p.target);
target_state ts (execute (a, pt));
if (!e)
@@ -243,4 +263,57 @@ namespace build
return e;
}
+
+ target_state
+ perform_clean_file (action a, target& t)
+ {
+ // The reverse order of update: first delete the file, then clean
+ // 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;
+ }
+
+ // Update timestamp in case there are operations after us that
+ // could use the information.
+ //
+ ft.mtime (timestamp_nonexistent);
+
+ // Clean prerequisites.
+ //
+ target_state ts (target_state::unchanged);
+
+ if (!t.prerequisites.empty ())
+ ts = execute_prerequisites (a, t);
+
+ return rs == rmfile_status::success ? target_state::changed : ts;
+ }
}
diff --git a/build/algorithm.ixx b/build/algorithm.ixx
index e33566b..340789e 100644
--- a/build/algorithm.ixx
+++ b/build/algorithm.ixx
@@ -4,6 +4,15 @@
namespace build
{
+ target&
+ search_impl (prerequisite&);
+
+ inline target&
+ search (prerequisite& p)
+ {
+ return p.target != nullptr ? *p.target : search_impl (p);
+ }
+
void
match_impl (action, target&);
@@ -12,5 +21,23 @@ namespace build
{
if (!t.recipe (a))
match_impl (a, t);
+
+ t.dependents++;
+ }
+
+ target_state
+ execute_impl (action, target&);
+
+ inline target_state
+ execute (action a, target& t)
+ {
+ t.dependents--;
+
+ switch (t.state)
+ {
+ case target_state::unchanged:
+ case target_state::changed: return t.state;
+ default: return execute_impl (a, t);
+ }
}
}
diff --git a/build/algorithm.txx b/build/algorithm.txx
index 4c9a673..1e22be1 100644
--- a/build/algorithm.txx
+++ b/build/algorithm.txx
@@ -18,9 +18,10 @@ namespace build
for (const prerequisite& p: t.prerequisites)
{
- assert (p.target != nullptr);
- target& pt (*p.target);
+ if (p.target == nullptr) // Skip ignored.
+ continue;
+ target& pt (*p.target);
target_state ts (execute (a, pt));
if (!e)
diff --git a/build/b.cxx b/build/b.cxx
index 499ac10..0764622 100644
--- a/build/b.cxx
+++ b/build/b.cxx
@@ -515,11 +515,8 @@ main (int argc, char* argv[])
target& t (**i);
- if (!t.recipe (act))
- {
- level4 ([&]{trace << "matching target " << t;});
- match (act, t);
- }
+ level4 ([&]{trace << "matching target " << t;});
+ match (act, t);
tgs.push_back (t);
}
@@ -530,18 +527,9 @@ main (int argc, char* argv[])
//
for (target& t: tgs)
{
- // The target might have already been updated indirectly. We
- // still want to inform the user about its status since they
- // requested its update explicitly.
- //
- target_state s (t.state ());
- if (s == target_state::unknown)
- {
- level4 ([&]{trace << "updating target " << t;});
- s = execute (act, t);
- }
+ level4 ([&]{trace << "updating target " << t;});
- switch (s)
+ switch (execute (act, t))
{
case target_state::unchanged:
{
@@ -552,7 +540,7 @@ main (int argc, char* argv[])
break;
case target_state::failed:
//@@ This could probably happen in a parallel build.
- case target_state::unknown:
+ default:
assert (false);
}
}
diff --git a/build/buildfile b/build/buildfile
index 8f11823..4a3b6b3 100644
--- a/build/buildfile
+++ b/build/buildfile
@@ -1,3 +1,3 @@
exe{b1}: cxx{b algorithm parser lexer name operation spec scope variable \
target prerequisite rule native context search diagnostics cxx/target \
- cxx/rule process timestamp path utility mkdir dump}
+ cxx/rule process timestamp path utility filesystem dump}
diff --git a/build/cxx/rule b/build/cxx/rule
index 1dfb8b5..d2378d2 100644
--- a/build/cxx/rule
+++ b/build/cxx/rule
@@ -17,36 +17,36 @@ namespace build
namespace cxx
{
// @@ Can't we do match(obj&) and then registration code extracts
- // that. And no virtuals.
+ // that. And no virtuals?
//
class compile: public rule
{
public:
virtual void*
- match (action a, target&, const std::string& hint) const;
+ match (action, target&, const std::string& hint) const;
virtual recipe
- apply (action a, target&, void*) const;
+ apply (action, target&, void*) const;
static target_state
- update (action a, target&);
+ perform_update (action, target&);
private:
void
- inject_prerequisites (action a, obj&, const cxx&, scope&) const;
+ inject_prerequisites (action, obj&, const cxx&, scope&) const;
};
class link: public rule
{
public:
virtual void*
- match (action a, target&, const std::string& hint) const;
+ match (action, target&, const std::string& hint) const;
virtual recipe
- apply (action a, target&, void*) const;
+ apply (action, target&, void*) const;
static target_state
- update (action a, target&);
+ perform_update (action, target&);
};
}
}
diff --git a/build/cxx/rule.cxx b/build/cxx/rule.cxx
index 4c93a1a..649067e 100644
--- a/build/cxx/rule.cxx
+++ b/build/cxx/rule.cxx
@@ -69,17 +69,34 @@ namespace build
// Search and match all the existing prerequisites. The injection
// code (below) takes care of the ones it is adding.
//
- search_and_match (a, t);
+ // When cleaning, ignore prerequisites that are not in the same
+ // or a subdirectory of ours.
+ //
+ 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);
+ }
- // Inject additional prerequisites.
+ // Inject additional prerequisites. For now we only do it for
+ // update.
//
- auto& sp (*static_cast<prerequisite*> (v));
- auto& st (dynamic_cast<cxx&> (*sp.target));
+ if (a.operation () == update_id)
+ {
+ auto& sp (*static_cast<prerequisite*> (v));
+ auto& st (dynamic_cast<cxx&> (*sp.target));
- if (st.mtime () != timestamp_nonexistent)
- inject_prerequisites (a, o, st, sp.scope);
+ if (st.mtime () != timestamp_nonexistent)
+ inject_prerequisites (a, o, st, sp.scope);
+ }
- return &update;
+ switch (a)
+ {
+ case perform_update_id: return &perform_update;
+ case perform_clean_id: return &perform_clean_file;
+ default: return noop_recipe;
+ }
}
// Return the next make prerequisite starting from the specified
@@ -215,9 +232,7 @@ namespace build
// Resolve to target.
//
- path_target& t (
- dynamic_cast<path_target&> (
- p.target != nullptr ? *p.target : search (p)));
+ path_target& t (dynamic_cast<path_target&> (search (p)));
// Assign path.
//
@@ -251,7 +266,7 @@ namespace build
}
target_state compile::
- update (action a, target& t)
+ perform_update (action a, target& t)
{
obj& o (dynamic_cast<obj&> (t));
cxx* s (execute_prerequisites<cxx> (a, o, o.mtime ()));
@@ -359,7 +374,7 @@ namespace build
}
// We will only chain a C source if there is also a C++ source or we
- // we explicitly told to.
+ // were explicitly told to.
//
if (seen_c && !seen_cxx && hint < "cxx")
{
@@ -397,10 +412,15 @@ namespace build
if (p.type.id != typeid (c) && p.type.id != typeid (cxx))
{
- if (p.target == nullptr)
- search (p);
+ // The same logic as in search_and_match().
+ //
+ target& pt (search (p));
+
+ if (a.operation () == clean_id && !pt.dir.sub (e.dir))
+ p.target = nullptr; // Ignore.
+ else
+ build::match (a, pt);
- build::match (a, *p.target);
continue;
}
@@ -449,6 +469,26 @@ namespace build
//
target& ot (search (op));
+ // If we are cleaning, check that this target is in the same or
+ // a subdirectory of ours.
+ //
+ // If it is not, then we are effectively leaving the prerequisites
+ // half-rewritten (we only rewrite those that we should clean).
+ // What will happen if, say, after clean we have update? Well,
+ // update will come and finish the rewrite process (it will even
+ // reuse op that we have created but then ignored). So all is good.
+ //
+ if (a.operation () == clean_id && !ot.dir.sub (e.dir))
+ {
+ // If we shouldn't clean obj{}, then it is fair to assume
+ // we shouldn't clean cxx{} either (generated source will
+ // be in the same directory as obj{} and if not, well, go
+ // and find yourself another build system).
+ //
+ p.target = nullptr; // Skip.
+ continue;
+ }
+
// If this target already exists, then it needs to be "compatible"
// with what we are doing here.
//
@@ -461,7 +501,6 @@ namespace build
// work out, then we fail, in which case searching and matching
// speculatively doesn't really hurt.
//
- //
prerequisite* cp1 (nullptr);
for (prerequisite& p: ot.prerequisites)
{
@@ -488,9 +527,7 @@ namespace build
if (cp1 != nullptr)
{
build::match (a, ot); // Now cp1 should be resolved.
-
- if (cp.target == nullptr)
- search (cp); // Our own prerequisite, so this is ok.
+ search (cp); // Our own prerequisite, so this is ok.
if (cp.target != cp1->target)
fail << "synthesized target for prerequisite " << cp
@@ -510,11 +547,16 @@ namespace build
pr = op;
}
- return &update;
+ switch (a)
+ {
+ case perform_update_id: return &perform_update;
+ case perform_clean_id: return &perform_clean_file;
+ default: return noop_recipe;
+ }
}
target_state link::
- update (action a, target& t)
+ perform_update (action a, target& t)
{
// @@ Q:
//
diff --git a/build/filesystem b/build/filesystem
new file mode 100644
index 0000000..8fa76c2
--- /dev/null
+++ b/build/filesystem
@@ -0,0 +1,40 @@
+// file : build/filesystem -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD_FILESYSTEM
+#define BUILD_FILESYSTEM
+
+#include <sys/types.h> // mode_t
+
+#include <build/path>
+
+namespace build
+{
+ // 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.
+ //
+ void
+ 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
+ // reported by throwing std::system_error.
+ //
+ enum class rmdir_status {success, not_exist, not_empty};
+
+ rmdir_status
+ try_rmdir (const path&);
+
+ // Try to remove the file (or symbolic link) returning not_exist if
+ // it does not exist. All other errors are reported by throwing
+ // std::system_error.
+ //
+ enum class rmfile_status {success, not_exist};
+
+ rmfile_status
+ try_rmfile (const path&);
+}
+
+#endif // BUILD_FILESYSTEM
diff --git a/build/filesystem.cxx b/build/filesystem.cxx
new file mode 100644
index 0000000..75d0283
--- /dev/null
+++ b/build/filesystem.cxx
@@ -0,0 +1,56 @@
+// file : build/filesystem.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#include <build/filesystem>
+
+#include <unistd.h> // rmdir(), unlink()
+#include <sys/stat.h> // mkdir()
+
+#include <system_error>
+
+using namespace std;
+
+namespace build
+{
+ void
+ mkdir (const path& p, mode_t m)
+ {
+ if (::mkdir (p.string ().c_str (), m) != 0)
+ throw system_error (errno, system_category ());
+ }
+
+ rmdir_status
+ try_rmdir (const path& p)
+ {
+ rmdir_status r (rmdir_status::success);
+
+ if (::rmdir (p.string ().c_str ()) != 0)
+ {
+ if (errno == ENOENT)
+ r = rmdir_status::not_exist;
+ else if (errno == ENOTEMPTY || errno == EEXIST)
+ r = rmdir_status::not_empty;
+ else
+ throw system_error (errno, system_category ());
+ }
+
+ return r;
+ }
+
+ rmfile_status
+ try_rmfile (const path& p)
+ {
+ rmfile_status r (rmfile_status::success);
+
+ if (::unlink (p.string ().c_str ()) != 0)
+ {
+ if (errno == ENOENT || errno == ENOTDIR)
+ r = rmfile_status::not_exist;
+ else
+ throw system_error (errno, system_category ());
+ }
+
+ return r;
+ }
+}
diff --git a/build/mkdir b/build/mkdir
deleted file mode 100644
index 62e824b..0000000
--- a/build/mkdir
+++ /dev/null
@@ -1,22 +0,0 @@
-// file : build/mkdir -*- C++ -*-
-// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
-// license : MIT; see accompanying LICENSE file
-
-#ifndef BUILD_MKDIR
-#define BUILD_MKDIR
-
-#include <sys/types.h> // mode_t
-
-#include <build/path>
-
-namespace build
-{
- // 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.
- //
- void
- mkdir (const path&, mode_t = 0777);
-}
-
-#endif // BUILD_MKDIR
diff --git a/build/mkdir.cxx b/build/mkdir.cxx
deleted file mode 100644
index 1fbab03..0000000
--- a/build/mkdir.cxx
+++ /dev/null
@@ -1,21 +0,0 @@
-// file : build/mkdir.cxx -*- C++ -*-
-// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
-// license : MIT; see accompanying LICENSE file
-
-#include <build/mkdir>
-
-#include <sys/stat.h> // mkdir()
-
-#include <system_error>
-
-using namespace std;
-
-namespace build
-{
- void
- mkdir (const path& p, mode_t m)
- {
- if (::mkdir (p.string ().c_str (), m) != 0)
- throw system_error (errno, system_category ());
- }
-}
diff --git a/build/path b/build/path
index 550af80..9644b6a 100644
--- a/build/path
+++ b/build/path
@@ -255,7 +255,8 @@ 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.
+ // normalized. Note that this function returns true if the paths
+ // are equal.
//
bool
sub (const basic_path& p) const
diff --git a/build/rule b/build/rule
index 2e11191..7501f7d 100644
--- a/build/rule
+++ b/build/rule
@@ -35,7 +35,8 @@ namespace build
extern operation_rule_map rules;
extern const target_rule_map* current_rules; // Rules for current operation.
- // Fallback rule that check that the path exists.
+ // Fallback rule that on update verifies that the path exists and is
+ // not older than any of its prerequisites.
//
class path_rule: public rule
{
@@ -47,7 +48,7 @@ namespace build
apply (action, target&, void*) const;
static target_state
- update (action, target&);
+ perform_update (action, target&);
};
class dir_rule: public rule
@@ -58,9 +59,6 @@ namespace build
virtual recipe
apply (action, target&, void*) const;
-
- static target_state
- update (action, target&);
};
class fsdir_rule: public rule
@@ -73,7 +71,10 @@ namespace build
apply (action, target&, void*) const;
static target_state
- update (action, target&);
+ perform_update (action, target&);
+
+ static target_state
+ perform_clean (action, target&);
};
}
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;
}
}
diff --git a/build/target b/build/target
index a49c57f..f9aa50a 100644
--- a/build/target
+++ b/build/target
@@ -9,6 +9,7 @@
#include <string>
#include <vector>
#include <memory> // unique_ptr
+#include <cstddef> // size_t
#include <functional> // function, reference_wrapper
#include <typeindex>
#include <ostream>
@@ -27,12 +28,43 @@ namespace build
{
class target;
+ // Target state.
+ //
enum class target_state {unknown, unchanged, changed, failed};
- // Note: should throw rather than returning target_state::failed.
+ // Recipe.
+ //
+ // The returned target state should be either changed or unchanged.
+ // If there is an error, then the recipe should throw rather than
+ // returning failed.
+ //
+ // The recipe execution protocol is as follows: before executing
+ // the recipe, the caller sets the target's state to failed. If
+ // the recipe returns normally and the target's state is still
+ // failed, then the caller sets it to the returned value. This
+ // means that the recipe can set the target's state manually to
+ // some other value. For example, setting it to unknown will
+ // result in the recipe to be executed again if this target is
+ // a prerequisite of another target. Note that in this case the
+ // returned by the recipe value is still used (by the caller) as
+ // the resulting target state for this execution of the recipe.
+ //
+ using recipe_function = target_state (action, target&);
+ using recipe = std::function<recipe_function>;
+
+ // Commonly-used recipes. The default recipe executes the action
+ // on all the prerequisites in a loop, skipping ignored (see the
+ // execute_prerequisites() in <algorithm> for details).
//
- typedef std::function<target_state (action, target&)> recipe;
+ extern const recipe empty_recipe;
+ extern const recipe noop_recipe;
+ extern const recipe default_recipe;
+ target_state
+ noop_recipe_function (action, target&);
+
+ // Target type.
+ //
struct target_type
{
std::type_index id;
@@ -69,29 +101,47 @@ namespace build
prerequisites_type prerequisites;
public:
+ target_state state;
+
+ // Number of direct targets that depend on this target in the current
+ // action. It is incremented during the match phase and then decremented
+ // during execution, before running the recipe. As a result, the recipe
+ // can detect the last chance (i.e., last dependent) to execute the
+ // command ("front-running" vs "back-running" recipes).
+ //
+ // Note that setting a new recipe (which happens when we match the rule
+ // and which in turn is triggered by the first dependent) clears this
+ // counter. However, if the previous action was the same as the current,
+ // then the existing recipe is reused. In this case, however, the counter
+ // should have been decremented to 0 naturally, as part of the previous
+ // action execution.
+ //
+ std::size_t dependents;
+
+ public:
typedef build::recipe recipe_type;
const recipe_type&
- recipe (action_id a) const {return action_ == a ? recipe_ : empty_recipe_;}
+ recipe (action_id a) const {return action_ == a ? recipe_ : empty_recipe;}
void
recipe (action_id a, recipe_type r)
{
assert (action_ != a || !recipe_);
action_ = a;
- recipe_ = r;
+ recipe_ = std::move (r);
- // Also reset the target state.
+ // Also reset the target state. If this is a noop recipe, then
+ // mark the target unchanged so that we don't waste time executing
+ // the recipe.
//
- state_ = target_state::unknown;
- }
+ recipe_function** f (recipe_.target<recipe_function*> ());
+ state = (f == nullptr || *f != &noop_recipe_function)
+ ? target_state::unknown
+ : target_state::unchanged;
- public:
- target_state
- state () const {return state_;}
-
- void
- state (target_state s) {state_ = s;}
+ dependents = 0;
+ }
private:
target (const target&) = delete;
@@ -102,10 +152,8 @@ namespace build
static const target_type static_type;
private:
- action_id action_ {0}; // Action id of this target recipe.
+ action_id action_ {0}; // Action id of this recipe.
recipe_type recipe_;
- static const recipe_type empty_recipe_;
- target_state state_ {target_state::unknown};
};
std::ostream&
diff --git a/build/target.cxx b/build/target.cxx
index 5cf131f..15d57ca 100644
--- a/build/target.cxx
+++ b/build/target.cxx
@@ -7,16 +7,29 @@
#include <build/scope>
#include <build/search>
#include <build/context>
+#include <build/algorithm> // execute_prerequisites()
#include <build/diagnostics>
using namespace std;
namespace build
{
- // target
+ // recipe
//
- const recipe target::empty_recipe_;
+ const recipe empty_recipe;
+ const recipe noop_recipe (&noop_recipe_function);
+ const recipe default_recipe (
+ static_cast<recipe_function*> (&execute_prerequisites));
+
+ target_state
+ noop_recipe_function (action, target&)
+ {
+ assert (false); // We shouldn't be called, see target::recipe().
+ return target_state::unchanged;
+ }
+ // target
+ //
ostream&
operator<< (ostream& os, const target& t)
{