From 9fb791e9fad6c63fc1dac49f4d05ae63b8a3db9b Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 5 Jan 2016 11:55:15 +0200 Subject: Rename build directory/namespace to build2 --- build2/rule.cxx | 249 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 build2/rule.cxx (limited to 'build2/rule.cxx') diff --git a/build2/rule.cxx b/build2/rule.cxx new file mode 100644 index 0000000..34fb3b4 --- /dev/null +++ b/build2/rule.cxx @@ -0,0 +1,249 @@ +// file : build2/rule.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // move() +#include + +#include + +#include +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + // file_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. + // + match_result file_rule:: + match (action a, target& t, const string&) const + { + tracer trace ("file_rule::match"); + + // 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. 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) + { + case perform_update_id: + { + path_target& pt (dynamic_cast (t)); + + // Assign the path. While normally we shouldn't do this in match(), + // no other rule should ever be ambiguous with the fallback one. + // + if (pt.path ().empty ()) + pt.derive_path (); + + // We cannot just call pt.mtime() since we haven't matched yet. + // + timestamp ts (file_mtime (pt.path ())); + pt.mtime (ts); + + if (ts != timestamp_nonexistent) + return t; + + level4 ([&]{trace << "no existing file for target " << t;}); + return nullptr; + } + default: + return t; + } + } + + recipe file_rule:: + apply (action a, target& t, const match_result&) 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; + + // If we have no prerequisites, then this means this file + // is up to date. Return noop_recipe which will also cause + // the target's state to be set to unchanged. This is an + // important optimization on which quite a few places that + // deal with predominantly static content rely. + // + if (!t.has_prerequisites ()) + return noop_recipe; + + // Search and match all the prerequisites. + // + search_and_match_prerequisites (a, t); + return a == perform_update_id ? &perform_update : default_recipe; + } + + target_state file_rule:: + perform_update (action a, target& t) + { + // Make sure the target is not older than any of its prerequisites. + // + timestamp mt (dynamic_cast (t).mtime ()); + + for (target* pt: t.prerequisite_targets) + { + target_state ts (execute (a, *pt)); + + // If this is an mtime-based target, then compare timestamps. + // + if (auto mpt = dynamic_cast (pt)) + { + timestamp mp (mpt->mtime ()); + + if (mt < mp) + fail << "no recipe to " << diag_do (a, t) << + info << "prerequisite " << *pt << " is ahead of " << t + << " by " << (mp - mt); + } + else + { + // Otherwise we assume the prerequisite is newer if it was changed. + // + if (ts == target_state::changed) + fail << "no recipe to " << diag_do (a, t) << + info << "prerequisite " << *pt << " is ahead of " << t + << " because it was updated"; + } + } + + return target_state::unchanged; + } + + file_rule file_rule::instance; + + // alias_rule + // + match_result alias_rule:: + match (action, target& t, const string&) const + { + return t; + } + + recipe alias_rule:: + apply (action a, target& t, const match_result&) const + { + search_and_match_prerequisites (a, t); + return default_recipe; + } + + alias_rule alias_rule::instance; + + // fsdir_rule + // + match_result fsdir_rule:: + match (action, target& t, const string&) const + { + return t; + } + + recipe fsdir_rule:: + apply (action a, target& t, const match_result&) const + { + // Inject dependency on the parent directory. Note that we + // don't do it for clean since we shouldn't be removing it. + // + if (a.operation () != clean_id) + inject_parent_fsdir (a, t); + + search_and_match_prerequisites (a, t); + + switch (a) + { + case perform_update_id: return &perform_update; + case perform_clean_id: return &perform_clean; + default: assert (false); return default_recipe; + } + } + + target_state fsdir_rule:: + perform_update (action a, target& t) + { + target_state ts (target_state::unchanged); + + // First update prerequisites (e.g. create parent directories) + // then create this directory. + // + if (!t.prerequisite_targets.empty ()) + ts = execute_prerequisites (a, t); + + 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 + // going to get better performance by first checking if it indeed + // exists. See try_mkdir() for details. + // + if (!dir_exists (d)) + { + if (verb >= 2) + text << "mkdir " << d; + else if (verb) + text << "mkdir " << t; + + try + { + try_mkdir (d); + } + catch (const system_error& e) + { + fail << "unable to create directory " << d << ": " << e.what (); + } + + ts |= target_state::changed; + } + + return ts; + } + + target_state fsdir_rule:: + perform_clean (action a, target& t) + { + // The reverse order of update: first delete this directory, + // then clean prerequisites (e.g., delete parent directories). + // + // Don't fail if we couldn't remove the directory because it + // is not empty (or is current working directory). In this + // case rmdir() will issue a warning when appropriate. + // + target_state ts (rmdir (t.dir, t) + ? target_state::changed + : target_state::unchanged); + + if (!t.prerequisite_targets.empty ()) + ts |= reverse_execute_prerequisites (a, t); + + return ts; + } + + fsdir_rule fsdir_rule::instance; + + // fallback_rule + // + fallback_rule fallback_rule::instance; +} -- cgit v1.1