From f1c981a22365411794806ed0744b857ef0804e35 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 24 Jun 2022 10:29:09 +0200 Subject: Allow ad hoc rules not to list targets that are updated during match For example, this allows a Qt moc rule not to list generated headers from libQtCore since they are pre-generated by the library. --- libbuild2/adhoc-rule-buildscript.cxx | 35 ++++++++++++++---------- libbuild2/adhoc-rule-buildscript.hxx | 4 +++ libbuild2/algorithm.hxx | 5 +++- libbuild2/build/script/parser.cxx | 9 +++++- libbuild2/cc/common.cxx | 4 ++- libbuild2/cc/common.hxx | 5 ++++ libbuild2/cc/link-rule.cxx | 18 +++++++----- libbuild2/dyndep.cxx | 53 ++++++++++++++++++++++++++++++++---- libbuild2/dyndep.hxx | 7 +++-- libbuild2/target.hxx | 33 ++++++++++++++++++---- 10 files changed, 136 insertions(+), 37 deletions(-) diff --git a/libbuild2/adhoc-rule-buildscript.cxx b/libbuild2/adhoc-rule-buildscript.cxx index 03e810d..9a38a31 100644 --- a/libbuild2/adhoc-rule-buildscript.cxx +++ b/libbuild2/adhoc-rule-buildscript.cxx @@ -350,8 +350,8 @@ namespace build2 include_type pi ( include (a, xt, p, a.operation () == update_id ? &l : nullptr)); - // Use bit 2 of prerequisite_target::include to signal update during - // match and bit 3 -- unmatch. + // Use prerequisite_target::include to signal update during match or + // unmatch. // uintptr_t mask (0); if (l) @@ -361,12 +361,12 @@ namespace build2 if (v == "match") { if (a == perform_update_id) - mask = 2; + mask = prerequisite_target::include_udm; } else if (v == "unmatch") { if (a == perform_update_id) - mask = 4; + mask = include_unmatch; } else if (v != "false" && v != "true" && v != "execute") { @@ -422,7 +422,9 @@ namespace build2 // Handle update=unmatch. // - unmatch um ((pt.include & 4) != 0 ? unmatch::safe : unmatch::none); + unmatch um ((pt.include & include_unmatch) != 0 + ? unmatch::safe + : unmatch::none); pair mr (match_complete (a, *pt.target, um)); @@ -444,7 +446,7 @@ namespace build2 if (mr.first) pt.target = nullptr; else - pt.include |= 1; + pt.include |= prerequisite_target::include_adhoc; } } } @@ -464,7 +466,7 @@ namespace build2 // prerequisite_target::data. // if (a == perform_update_id) - update_during_match_prerequisites (trace, a, xt, 2 /* mask */); + update_during_match_prerequisites (trace, a, xt); // See if this is not update or not on a file-based target. // @@ -531,6 +533,10 @@ namespace build2 // them to the auxiliary data member in prerequisite_target (see // execute_update_prerequisites() for details). // + // @@ This actually messes up with updated_during_match() check. Could + // we not redo this so that we always keep p.target intact? Can't + // we just omit p.adhoc() targets from $ (p.data) : nullptr)) { - if ((p.include & 4) != 0) // Skip update=unmatch. + if ((p.include & include_unmatch) != 0) // Skip update=unmatch. continue; hash_prerequisite_target (prq_cs, exe_cs, env_cs, *pt, storage); @@ -665,7 +671,8 @@ namespace build2 // update on encountering any non-existent files in depbd, we may // actually incorrectly "validate" some number of depdb entires while // having an out-of-date main source file. We could probably avoid the - // update if we are already updating. + // update if we are already updating (or not: there is pre-generation + // to consider; see inject_existing_file() for details). // { build::script::parser p (ctx); @@ -701,7 +708,7 @@ namespace build2 size_t& skip_count (mdb->skip_count); auto add = [&trace, what, - a, &bs, &t, + a, &bs, &t, pts_n = mdb->pts_n, &byp, &map_ext, &skip_count, mt] (path fp) -> optional { @@ -716,7 +723,7 @@ namespace build2 // if (optional u = dyndep::inject_existing_file ( trace, what, - a, t, + a, t, pts_n, *ft, mt, false /* fail */, false /* adhoc */, @@ -961,7 +968,7 @@ namespace build2 // @@ Currently we will issue an imprecise diagnostics if this is // a static prerequisite that was not updated (see above). // - dyndep::verify_existing_file (trace, what, a, t, *ft); + dyndep::verify_existing_file (trace, what, a, t, pts_n, *ft); } dd.write (fp); @@ -1156,7 +1163,7 @@ namespace build2 p.adhoc () ? reinterpret_cast (p.data) : nullptr)) { - if ((p.include & 4) != 0) // Skip update=unmatch. + if ((p.include & include_unmatch) != 0) // Skip update=unmatch. continue; hash_prerequisite_target (prq_cs, exe_cs, env_cs, *pt, storage); @@ -1396,7 +1403,7 @@ namespace build2 // Compare our timestamp to this prerequisite's skipping // update=unmatch. // - if (!e && (p.include & 4) == 0) + if (!e && (p.include & include_unmatch) == 0) { // If this is an mtime-based target, then compare timestamps. // diff --git a/libbuild2/adhoc-rule-buildscript.hxx b/libbuild2/adhoc-rule-buildscript.hxx index e7b18e2..2334cdd 100644 --- a/libbuild2/adhoc-rule-buildscript.hxx +++ b/libbuild2/adhoc-rule-buildscript.hxx @@ -82,6 +82,10 @@ namespace build2 public: using script_type = build::script::script; + // The prerequisite_target::include bit that indicates update=unmatch. + // + static const uintptr_t include_unmatch = 0x100; + script_type script; string checksum; // Script text hash. const target_type* ttype; // First target/pattern type. diff --git a/libbuild2/algorithm.hxx b/libbuild2/algorithm.hxx index db3e93d..300016f 100644 --- a/libbuild2/algorithm.hxx +++ b/libbuild2/algorithm.hxx @@ -637,7 +637,10 @@ namespace build2 // for temporary storage). But it resets data to 0 once done. // LIBBUILD2_SYMEXPORT bool - update_during_match_prerequisites (tracer&, action, target&, uintptr_t mask); + update_during_match_prerequisites ( + tracer&, + action, target&, + uintptr_t mask = prerequisite_target::include_udm); // The default prerequisite execute implementation. Call execute_async() on // each non-ignored (non-NULL) prerequisite target in a loop and then wait diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx index 9f04102..795dc72 100644 --- a/libbuild2/build/script/parser.cxx +++ b/libbuild2/build/script/parser.cxx @@ -15,6 +15,8 @@ #include #include +#include + #include #include @@ -1632,7 +1634,7 @@ namespace build2 // So there is a nuanced interaction between update=match and // --update-*. // - if ((p.include & 4) != 0) + if ((p.include & adhoc_buildscript_rule::include_unmatch) != 0) { l6 ([&]{trace << "skipping unmatched " << *pt;}); continue; @@ -1705,6 +1707,11 @@ namespace build2 update = dyndep::update ( trace, a, *pt, update ? timestamp_unknown : mt) || update; + // While implicit, it is for a static prerequisite, so marking it + // feels correct. + // + p.include |= prerequisite_target::include_udm; + // Mark as updated (see execute_update_prerequisites() for // details. // diff --git a/libbuild2/cc/common.cxx b/libbuild2/cc/common.cxx index 976127f..a320626 100644 --- a/libbuild2/cc/common.cxx +++ b/libbuild2/cc/common.cxx @@ -397,7 +397,9 @@ namespace build2 { // See link_rule for details. // - const target* g ((pt.include & 4) != 0 ? f->group : nullptr); + const target* g ((pt.include & include_group) != 0 + ? f->group + : nullptr); if (sysd == nullptr) find_sysd (); if (!li) find_linfo (); diff --git a/libbuild2/cc/common.hxx b/libbuild2/cc/common.hxx index a5a4859..2aaa0d0 100644 --- a/libbuild2/cc/common.hxx +++ b/libbuild2/cc/common.hxx @@ -305,6 +305,11 @@ namespace build2 using library_cache = small_vector; + // The prerequisite_target::include bit that indicates a library + // member has been picked from the group. + // + static const uintptr_t include_group = 0x100; + void process_libraries ( action, diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx index 0081fe2..d0a1f4a 100644 --- a/libbuild2/cc/link-rule.cxx +++ b/libbuild2/cc/link-rule.cxx @@ -1003,7 +1003,7 @@ namespace build2 if (*um) { pto.target = &p.search (t); // mark 0 - pto.include |= 2; + pto.include |= prerequisite_target::include_udm; update_match = true; } } @@ -1144,7 +1144,7 @@ namespace build2 if (const libx* l = pt->is_a ()) { pt = link_member (*l, a, li); - pto.include |= 4; + pto.include |= include_group; } } else @@ -1231,7 +1231,7 @@ namespace build2 << "not supported by this rule"; m = 0; - pto.include |= 2; + pto.include |= prerequisite_target::include_udm; update_match = true; } } @@ -1245,10 +1245,14 @@ namespace build2 // of the libraries (for example, if generation requires some of the // metadata; think poptions needed by Qt moc). // - match_members (a, t, pts, start, {2 /* mask */, 0 /* value */}); + { + auto mask (prerequisite_target::include_udm); - if (update_match) - match_members (a, t, pts, start, {2, 2}); + match_members (a, t, pts, start, {mask, 0}); + + if (update_match) + match_members (a, t, pts, start, {mask, mask}); + } // Check if we have any binful utility libraries. // @@ -1383,7 +1387,7 @@ namespace build2 // prerequisite_target::data. // if (update_match) - update_during_match_prerequisites (trace, a, t, 2 /* mask */); + update_during_match_prerequisites (trace, a, t); // Now that we know for sure whether we are binless, derive file name(s) // and add ad hoc group members. Note that for binless we still need the diff --git a/libbuild2/dyndep.cxx b/libbuild2/dyndep.cxx index 61fa8cb..bc307f6 100644 --- a/libbuild2/dyndep.cxx +++ b/libbuild2/dyndep.cxx @@ -56,9 +56,43 @@ namespace build2 return r; } + // Check if the specified prerequisite is updated during match by any other + // prerequisites of the specified target, recursively. + // + static bool + updated_during_match (action a, const target& t, size_t pts_n, + const target& pt) + { + const auto& pts (t.prerequisite_targets[a]); + + for (size_t i (0); i != pts_n; ++i) + { + const prerequisite_target& p (pts[i]); + + // @@ This currently doesn't cover adhoc targets if matched with + // buildscript (it stores them in p.data). Probably need to redo + // things there (see adhoc_buildscript_rule::apply()). + // + if (p.target != nullptr) + { + if (p.target == &pt && + (p.include & prerequisite_target::include_udm) != 0) + return true; + + if (size_t n = p.target->prerequisite_targets[a].size ()) + { + if (updated_during_match (a, *p.target, n, pt)) + return true; + } + } + } + + return false; + } + optional dyndep_rule:: inject_existing_file (tracer& trace, const char* what, - action a, target& t, + action a, target& t, size_t pts_n, const file& pt, timestamp mt, bool f, @@ -81,8 +115,11 @@ namespace build2 recipe_function* const* rf (pt[a].recipe.target ()); if (rf == nullptr || *rf != &noop_action) { - fail << what << ' ' << pt << " has non-noop recipe" << - info << "consider listing it as static prerequisite of " << t; + if (!updated_during_match (a, t, pts_n, pt)) + { + fail << what << ' ' << pt << " has non-noop recipe" << + info << "consider listing it as static prerequisite of " << t; + } } bool r (update (trace, a, pt, mt)); @@ -96,7 +133,7 @@ namespace build2 void dyndep_rule:: verify_existing_file (tracer&, const char* what, - action a, const target& t, + action a, const target& t, size_t pts_n, const file& pt) { diag_record dr; @@ -106,11 +143,17 @@ namespace build2 recipe_function* const* rf (pt[a].recipe.target ()); if (rf == nullptr || *rf != &noop_action) { - dr << fail << what << ' ' << pt << " has non-noop recipe"; + if (!updated_during_match (a, t, pts_n, pt)) + { + dr << fail << what << ' ' << pt << " has non-noop recipe"; + } } } else if (pt.decl == target_decl::real) { + // Note that this target could not possibly be updated during match + // since it's not matched. + // dr << fail << what << ' ' << pt << " is explicitly declared as " << "target and may have non-noop recipe"; } diff --git a/libbuild2/dyndep.hxx b/libbuild2/dyndep.hxx index 6632eb6..0789e78 100644 --- a/libbuild2/dyndep.hxx +++ b/libbuild2/dyndep.hxx @@ -65,9 +65,12 @@ namespace build2 // byproduct of recipe execution (and thus must have all the generated // prerequisites specified statically). // + // Note that this function expects all the static prerequisites of the + // target to already be matched and their number passed in pts_n. + // static optional inject_existing_file (tracer&, const char* what, - action, target&, + action, target&, size_t pts_n, const file& prerequiste, timestamp, bool fail, @@ -82,7 +85,7 @@ namespace build2 // static void verify_existing_file (tracer&, const char* what, - action, const target&, + action, const target&, size_t pts_n, const file& prerequiste); // Reverse-lookup target type(s) from file name/extension. diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx index 411025c..1a7abfc 100644 --- a/libbuild2/target.hxx +++ b/libbuild2/target.hxx @@ -75,27 +75,48 @@ namespace build2 // // The include member normally just indicates (in the first bit) whether // this prerequisite is ad hoc. But it can also carry additional information - // (for example, from operation-specific override) in other bits. + // (for example, from operation-specific override) in other bits (see below + // for details). // struct prerequisite_target { using target_type = build2::target; prerequisite_target (const target_type* t, bool a = false, uintptr_t d = 0) - : target (t), include (a ? 1 : 0), data (d) {} + : target (t), include (a ? include_adhoc : 0), data (d) {} prerequisite_target (const target_type* t, include_type a, uintptr_t d = 0) : prerequisite_target (t, a == include_type::adhoc, d) {} + const target_type* target; + operator const target_type*& () {return target;} operator const target_type* () const {return target;} const target_type* operator-> () const {return target;} - bool adhoc () const {return (include & 1) != 0;} + // The first 8 bits are reserved with the first two having the following + // semantics: + // + // adhoc + // + // This prerequisite is ad hoc. + // + // udm + // + // This prerequisite is updated during match. Note that only static + // prerequisites that are updated during match should have this bit set + // (see dyndep_rule::*_existing_file() for details). + // + static const uintptr_t include_adhoc = 0x01; + static const uintptr_t include_udm = 0x02; - const target_type* target; - uintptr_t include; // First bit is 1 if include=adhoc. - uintptr_t data; + uintptr_t include; + + bool adhoc () const {return (include & include_adhoc) != 0;} + + // Auxiliary data. + // + uintptr_t data; }; using prerequisite_targets = vector; -- cgit v1.1