// file : libbuild2/rule.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file #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. // bool 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. // switch (a) { case perform_clean_id: return true; default: { // While normally we shouldn't do any of this in match(), no other // rule should ever be ambiguous with the fallback one and path/mtime // access is atomic. In other words, we know what we are doing but // don't do this in normal rules. // First check the timestamp. This takes care of the special "trust // me, this file exists" situations (used, for example, for installed // stuff where we know it's there, just not exactly where). // mtime_target& mt (t.as ()); timestamp ts (mt.mtime ()); if (ts != timestamp_unknown) return ts != timestamp_nonexistent; // Otherwise, if this is not a path_target, then we don't match. // path_target* pt (mt.is_a ()); if (pt == nullptr) return false; const path* p (&pt->path ()); // Assign the path. // if (p->empty ()) { // Since we cannot come up with an extension, ask the target's // derivation function to treat this as a prerequisite (just like in // search_existing_file()). // if (pt->derive_extension (true) == nullptr) { l4 ([&]{trace << "no default extension for target " << *pt;}); return false; } p = &pt->derive_path (); } ts = mtime (*p); pt->mtime (ts); if (ts != timestamp_nonexistent) return true; l4 ([&]{trace << "no existing file for target " << *pt;}); return false; } } } recipe file_rule:: apply (action a, target& t) const { /* @@ outer return noop_recipe; */ // 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 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_group_prerequisites ()) // Group as in match_prerequisites(). return noop_recipe; // Match all the prerequisites. // match_prerequisites (a, t); // Note that we used to provide perform_update() which checked that this // target is not older than any of its prerequisites. However, later we // realized this is probably wrong: consider a script with a testscript as // a prerequisite; chances are the testscript will be newer than the // script and there is nothing wrong with that. // return default_recipe; } const file_rule file_rule::instance; // alias_rule // bool alias_rule:: match (action, target&, const string&) const { return true; } recipe alias_rule:: apply (action a, target& t) const { // Inject dependency on our directory (note: not parent) so that it is // automatically created on update and removed on clean. // inject_fsdir (a, t, false); match_prerequisites (a, t); return default_recipe; } const alias_rule alias_rule::instance; // fsdir_rule // bool fsdir_rule:: match (action, target&, const string&) const { return true; } recipe fsdir_rule:: apply (action a, target& t) const { // Inject dependency on the parent directory. Note that it must be first // (see perform_update_direct()). // inject_fsdir (a, t); 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; } } static bool fsdir_mkdir (const target& t, const dir_path& d) { // Even with the exists() check below this can still be racy so only print // things if we actually did create it (similar to build2::mkdir()). // auto print = [&t, &d] () { if (verb >= 2) text << "mkdir " << d; else if (verb && t.ctx.current_diag_noise) text << "mkdir " << t; }; // Note: ignoring the dry_run flag. // mkdir_status ms; try { ms = try_mkdir (d); } catch (const system_error& e) { print (); fail << "unable to create directory " << d << ": " << e << endf; } if (ms == mkdir_status::success) { print (); return true; } return false; } target_state fsdir_rule:: perform_update (action a, const target& t) { target_state ts (target_state::unchanged); // First update prerequisites (e.g. create parent directories) then create // this directory. // // @@ outer: should we assume for simplicity its only prereqs are fsdir{}? // if (!t.prerequisite_targets[a].empty ()) ts = straight_execute_prerequisites (a, t); // The same code as in perform_update_direct() below. // 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 // butl::try_mkdir() for details. // // @@ Also skip prerequisites? Can't we return noop in apply? // if (!exists (d) && fsdir_mkdir (t, d)) ts |= target_state::changed; return ts; } void fsdir_rule:: perform_update_direct (action a, const target& t) { // First create the parent directory. If present, it is always first. // const target* p (t.prerequisite_targets[a].empty () ? nullptr : t.prerequisite_targets[a][0]); if (p != nullptr && p->is_a ()) perform_update_direct (a, *p); // The same code as in perform_update() above. // const dir_path& d (t.dir); if (!exists (d)) fsdir_mkdir (t, d); } target_state fsdir_rule:: perform_clean (action a, const 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, t.ctx.current_diag_noise ? 1 : 2) ? target_state::changed : target_state::unchanged); if (!t.prerequisite_targets[a].empty ()) ts |= reverse_execute_prerequisites (a, t); return ts; } const fsdir_rule fsdir_rule::instance; // noop_rule // bool noop_rule:: match (action, target&, const string&) const { return true; } recipe noop_rule:: apply (action, target&) const { return noop_recipe; } const noop_rule noop_rule::instance; // adhoc_rule // static inline const adhoc_recipe* find_recipe (action a, target& t) { auto i (find_if (t.adhoc_recipes.begin (), t.adhoc_recipes.end (), [a] (const adhoc_recipe& r) {return r.action == a;})); return i != t.adhoc_recipes.end () ? &*i : nullptr; } bool adhoc_rule:: match (action a, target& t, const string&) const { // TODO: // // @@ If action is Y-for-X, how would we distinguish between X and // Y-for-X? See match_rule() for the hairy details. We could start with // supporting just the inner case. Or we could try to just match an // inner rule by default? I think need a clear use-case to see what's // the correct semantics. if (find_recipe (a, t)) return true; // If this is clean for a file target and we have a recipe for update, // then we will supply the standard clean. // if (a == perform_clean_id && t.is_a () && find_recipe (action (perform_id, update_id), t)) return true; return false; } recipe adhoc_rule:: apply (action a, target& t) const { // Derive file names for the target and its ad hoc group members, if any. // for (target* m (&t); m != nullptr; m = m->adhoc_member) { if (auto* p = m->is_a ()) p->derive_path (); } // Inject dependency on the output directory. // // We do it always instead of only if one of the targets is path-based in // case the recipe creates temporary files or some such. // inject_fsdir (a, t); // Match prerequisites. // match_prerequisite_members (a, t); // For update inject dependency on the tool target. // // @@ We could see that it's a target and do it but not sure if we should // bother. We dropped this idea of implicit targets in tests. Maybe we // should verify path assigned, like we do there? I think we will have // to. // // if (a == perform_update_id) // inject (a, t, tgt); if (const adhoc_recipe* r = find_recipe (a, t)) { // @@ Perhaps we should have different implementations for file-based // targets (depdb, timestamp, etc) and non. switch (a) { case perform_update_id: return [r] (action a, const target& t) { return perform_update (a, t, *r); }; default: return [r] (action a, const target& t) { // @@ TODO text << t << ' ' << a << ' ' << r; return target_state::unchanged; }; } } else { // Otherwise this should be the standard clean. // return &perform_clean_depdb; } } target_state adhoc_rule:: perform_update (action, const target&, const adhoc_recipe&) { tracer trace ("adhoc_rule::perform_update"); #if 0 // The rule has been matched which means the members should be resolved // and paths assigned. We use the header file as our "target path" for // timestamp, depdb, etc. // const cli_cxx& t (xt.as ()); const path& tp (t.h->path ()); // Update prerequisites and determine if any relevant ones render us // out-of-date. Note that currently we treat all the prerequisites as // potentially affecting the result (think prologues/epilogues, CLI // compiler target itself, etc). // timestamp mt (t.load_mtime (tp)); auto pr (execute_prerequisites (a, t, mt)); bool update (!pr.first); target_state ts (update ? target_state::changed : *pr.first); const cli& s (pr.second); // We use depdb to track changes to the .cli file name, options, // compiler, etc. // depdb dd (tp + ".d"); { // First should come the rule name/version. // if (dd.expect ("cli.compile 1") != nullptr) l4 ([&]{trace << "rule mismatch forcing update of " << t;}); // Then the compiler checksum. // if (dd.expect (csum) != nullptr) l4 ([&]{trace << "compiler mismatch forcing update of " << t;}); // Then the options checksum. // sha256 cs; append_options (cs, t, "cli.options"); if (dd.expect (cs.string ()) != nullptr) l4 ([&]{trace << "options mismatch forcing update of " << t;}); // Finally the .cli input file. // if (dd.expect (s.path ()) != nullptr) l4 ([&]{trace << "input file mismatch forcing update of " << t;}); } // Update if depdb mismatch. // if (dd.writing () || dd.mtime > mt) update = true; dd.close (); // If nothing changed, then we are done. // if (!update) return ts; // Translate paths to relative (to working directory). This results in // easier to read diagnostics. // path relo (relative (t.dir)); path rels (relative (s.path ())); const process_path& pp (ctgt.process_path ()); cstrings args {pp.recall_string ()}; // See if we need to pass --output-{prefix,suffix} // string prefix, suffix; match_stem (t.name, s.name, &prefix, &suffix); if (!prefix.empty ()) { args.push_back ("--output-prefix"); args.push_back (prefix.c_str ()); } if (!suffix.empty ()) { args.push_back ("--output-suffix"); args.push_back (suffix.c_str ()); } // See if we need to pass any --?xx-suffix options. // append_extension (args, *t.h, "--hxx-suffix", "hxx"); append_extension (args, *t.c, "--cxx-suffix", "cxx"); if (t.i != nullptr) append_extension (args, *t.i, "--ixx-suffix", "ixx"); append_options (args, t, "cli.options"); if (!relo.empty ()) { args.push_back ("-o"); args.push_back (relo.string ().c_str ()); } args.push_back (rels.string ().c_str ()); args.push_back (nullptr); if (verb >= 2) print_process (args); else if (verb) text << "cli " << s; if (!t.ctx.dry_run) { run (pp, args); dd.check_mtime (tp); } t.mtime (system_clock::now ()); #endif return target_state::changed; } const adhoc_rule adhoc_rule::instance; const rule_match adhoc_rule::match_instance {"adhoc", adhoc_rule::instance}; }