From cf6b3e34b59ad120111e0c1ead779bbb3a70c38d Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 12 Mar 2015 15:43:17 +0200 Subject: Implement clean operation --- build/rule.cxx | 247 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 190 insertions(+), 57 deletions(-) (limited to 'build/rule.cxx') 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 #include #include -#include +#include +#include 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 (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 (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; } } -- cgit v1.1