From 2d4b7eb982d2f7140d8093d9b1f0c3498d84f936 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 30 May 2023 09:36:35 +0200 Subject: Add support for fsdir{} dynamic prerequisites in the dyndep lines format This can be used to handle situations where the dynamic targets are placed into subdirectories. --- libbuild2/adhoc-rule-buildscript.cxx | 93 ++++++++++++++++++++++++++++-------- libbuild2/build/script/builtin.cli | 4 ++ libbuild2/build/script/parser.cxx | 59 ++++++++++++++++++++++- 3 files changed, 135 insertions(+), 21 deletions(-) (limited to 'libbuild2') diff --git a/libbuild2/adhoc-rule-buildscript.cxx b/libbuild2/adhoc-rule-buildscript.cxx index 4277573..29b11ee 100644 --- a/libbuild2/adhoc-rule-buildscript.cxx +++ b/libbuild2/adhoc-rule-buildscript.cxx @@ -551,14 +551,16 @@ namespace build2 } } - // Read the list of dynamic targets from depdb, if exists (used in a few - // depdb-dyndep --dyn-target handling places below). + // Read the list of dynamic targets and, optionally, fsdir{} prerequisites + // from depdb, if exists (used in a few depdb-dyndep --dyn-target handling + // places below). // - auto read_dyn_targets = [] (path ddp) -> dynamic_targets + auto read_dyn_targets = [] (path ddp, bool fsdir) + -> pair { depdb dd (move (ddp), true /* read_only */); - dynamic_targets r; + pair r; while (dd.reading ()) // Breakout loop. { string* l; @@ -571,8 +573,7 @@ namespace build2 break; // We can omit this for as long as we don't break our blank line - // anchors semantics (e.g., by adding another blank somewhere, say - // after the custom depdb builtins). + // anchors semantics. // #if 0 if (*l != rule_id_) @@ -581,14 +582,33 @@ namespace build2 #endif // Note that we cannot read out expected lines since there can be - // custom depdb builtins. We also need to skip the prerequisites - // list. So we read until the blank line that always terminates the - // prerequisites list. + // custom depdb builtins. So we use the blank lines as anchors to + // skip to the parts we need. // + // Skip until the first blank that separated custom depdb entries from + // the prerequisites list. { - bool r; - while ((r = read ()) && !l->empty ()) ; - if (!r) + bool g; + while ((g = read ()) && !l->empty ()) ; + if (!g) + break; + } + + // Next read the prerequisites, detecting fsdir{} entries if asked. + // + { + bool g; + while ((g = read ()) && !l->empty ()) + { + if (fsdir) + { + path p (*l); + if (p.to_directory ()) + r.second.push_back (path_cast (move (p))); + } + } + + if (!g) break; } @@ -608,7 +628,8 @@ namespace build2 p + 1 == l->size ()) // Empty path. break; - r.emplace_back (string (*l, 0, p), path (*l, p + 1, string::npos)); + r.first.emplace_back (string (*l, 0, p), + path (*l, p + 1, string::npos)); } break; @@ -661,7 +682,10 @@ namespace build2 }; } - for (dynamic_target& dt: read_dyn_targets (target_path () + ".d")) + pair p ( + read_dyn_targets (target_path () + ".d", true)); + + for (dynamic_target& dt: p.first) { path& f (dt.path); @@ -687,6 +711,20 @@ namespace build2 dyndep::inject_adhoc_group_member (a, bs, t, move (f), *tt); } } + + // Enter fsdir{} prerequisites. + // + // See the add lambda in exec_depdb_dyndep() for background. + // + for (dir_path& d: p.second) + { + const fsdir& dt (search (t, + move (d), + dir_path (), + string (), nullptr, nullptr)); + match_sync (a, dt); + pts.push_back (prerequisite_target (&dt, true /* adhoc */)); + } } return g == nullptr ? perform_clean_file : perform_clean_group; @@ -838,7 +876,7 @@ namespace build2 // will need to update. Oh, well, being dynamic ain't free. // if (script.depdb_dyndep_dyn_target) - old_dyn_targets = read_dyn_targets (tp + ".d"); + old_dyn_targets = read_dyn_targets (tp + ".d", false).first; } depdb dd (tp + ".d"); @@ -940,6 +978,14 @@ namespace build2 { build::script::parser p (ctx); p.execute_depdb_preamble (a, bs, t, env, script, run, dd); + + // Write a blank line after the custom depdb entries and before + // prerequisites, which we use as an anchor (see read_dyn_targets + // above). We only do it for the new --dyn-target mode in order not to + // invalidate the existing depdb instances. + // + if (script.depdb_dyndep_dyn_target) + dd.expect (""); } // Determine if we need to do an update based on the above checks. @@ -1480,6 +1526,11 @@ namespace build2 { f = path (l); + // fsdir{} prerequisites only make sense with dynamic targets. + // + if (f.to_directory ()) + throw invalid_path (""); + if (f.relative ()) { if (!byp.cwd) @@ -1551,8 +1602,6 @@ namespace build2 // const group* g (t.is_a ()); - const file& ft ((g == nullptr ? t : *g->members.front ()).as ()); - // Note that even if we've updated all our prerequisites in apply(), we // still need to execute them here to keep the dependency counts straight. // @@ -1581,7 +1630,8 @@ namespace build2 if (!ctx.dry_run || verb != 0) { if (g == nullptr) - execute_update_file (*md.bs, a, ft, env, run, md.deferred_failure); + execute_update_file ( + *md.bs, a, t.as (), env, run, md.deferred_failure); else execute_update_group (*md.bs, a, *g, env, run, md.deferred_failure); } @@ -1591,10 +1641,15 @@ namespace build2 timestamp now (system_clock::now ()); if (!ctx.dry_run) + { + // Note: in case of deferred failure we may not have any members. + // + const file& ft ((g == nullptr ? t : *g->members.front ()).as ()); depdb::check_mtime (start, md.dd, ft.path (), now); + } (g == nullptr - ? static_cast (ft) + ? static_cast (t) : static_cast (*g)).mtime (now); return target_state::changed; diff --git a/libbuild2/build/script/builtin.cli b/libbuild2/build/script/builtin.cli index 6d6369b..5aea034 100644 --- a/libbuild2/build/script/builtin.cli +++ b/libbuild2/build/script/builtin.cli @@ -56,6 +56,10 @@ namespace build2 // lines. In the non-byproduct mode a prerequisite line that starts // with a leading space is considered a non-existent prerequisite. // Currently only relative non-existent prerequisites are supported. + // Finally, in this mode, if the prerequisite is syntactically a + // directory (that is, it ends with a trailing directory separator), + // then it is added as fsdir{}. This can be used to handle situations + // where the dynamic targets are placed into subdirectories. // // Note on naming: whenever we (may) have two options, one for target // and the other for prerequisite, we omit "prerequisite" as that's diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx index 9965799..7e2feb9 100644 --- a/libbuild2/build/script/parser.cxx +++ b/libbuild2/build/script/parser.cxx @@ -2434,6 +2434,61 @@ namespace build2 bool cache (skip == nullptr); + // Handle fsdir{} prerequisite separately. + // + // Note: inspired by inject_fsdir(). + // + if (fp.to_directory ()) + { + if (!cache) + { + // Note: already absolute since cannot be non-existent. + // + fp.normalize (); + } + + const fsdir* dt (&search (t, + path_cast (fp), + dir_path (), + string (), nullptr, nullptr)); + + // Subset of code for file below. + // + if (!cache) + { + for (size_t i (0); i != pts_n; ++i) + { + const prerequisite_target& p (pts[i]); + + if (const target* pt = + (p.target != nullptr ? p.target : + p.adhoc () ? reinterpret_cast (p.data) : + nullptr)) + { + if (dt == pt) + return false; + } + } + + if (*skip != 0) + { + --(*skip); + return false; + } + } + + match_sync (a, *dt); + pts.push_back ( + prerequisite_target ( + nullptr, true /* adhoc */, reinterpret_cast (dt))); + + if (!cache) + dd.expect (fp.representation ()); + + skip_count++; + return false; + } + // We can only defer the failure if we will be running the recipe // body. // @@ -3013,7 +3068,8 @@ namespace build2 { f = path (l.c_str () + n, l.size () - n); - if (f.empty ()) + if (f.empty () || + (n && f.to_directory ())) // Non-existent fsdir{}. throw invalid_path (""); if (f.relative ()) @@ -3036,7 +3092,6 @@ namespace build2 // throw invalid_path (""); } - } catch (const invalid_path&) { -- cgit v1.1