From 2485425dfcd85344dd0293c0b446c9bb0e28bf17 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 1 Mar 2023 16:03:31 +0200 Subject: Add support for installation manifest --- build2/b.cxx | 64 ++++++++- libbuild2/cc/install-rule.cxx | 6 +- libbuild2/config/operation.cxx | 6 + libbuild2/context.cxx | 3 + libbuild2/context.hxx | 16 +++ libbuild2/dist/operation.cxx | 62 +++++++-- libbuild2/install/init.cxx | 85 ++++++++++++ libbuild2/install/operation.cxx | 300 +++++++++++++++++++++++++++++++++++++++- libbuild2/install/operation.hxx | 58 ++++++++ libbuild2/install/rule.cxx | 41 ++++-- libbuild2/install/rule.hxx | 20 ++- libbuild2/module.cxx | 3 + libbuild2/operation.cxx | 6 + libbuild2/operation.hxx | 32 ++++- libbuild2/test/operation.cxx | 14 +- 15 files changed, 664 insertions(+), 52 deletions(-) diff --git a/build2/b.cxx b/build2/b.cxx index 1d6f231..3b5f43a 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -1118,23 +1118,38 @@ main (int argc, char* argv[]) if (oif->outer_id != 0) outer_oif = lookup (oif->outer_id); + if (!oparams.empty ()) + { + // Operation parameters belong to outer operation, if any. + // + auto* i (outer_oif != nullptr ? outer_oif : oif); + + if (i->operation_pre == nullptr) + fail (l) << "unexpected parameters for operation " << i->name; + } + // Handle pre/post operations. // if (auto po = oif->pre_operation) { - if ((orig_pre_oid = po (ctx, oparams, mid, l)) != 0) + if ((orig_pre_oid = po ( + ctx, + outer_oif == nullptr ? oparams : values {}, + mid, + l)) != 0) { assert (orig_pre_oid != default_id); pre_oif = lookup (orig_pre_oid); pre_oid = pre_oif->id; // De-alias. } } - else if (!oparams.empty ()) - fail (l) << "unexpected parameters for operation " << oif->name; if (auto po = oif->post_operation) { - if ((orig_post_oid = po (ctx, oparams, mid)) != 0) + if ((orig_post_oid = po ( + ctx, + outer_oif == nullptr ? oparams : values {}, + mid)) != 0) { assert (orig_post_oid != default_id); post_oif = lookup (orig_post_oid); @@ -1321,6 +1336,12 @@ main (int argc, char* argv[]) ctx.current_operation (*pre_oif, oif); + if (oif->operation_pre != nullptr) + oif->operation_pre (ctx, oparams, false /* inner */, l); + + if (pre_oif->operation_pre != nullptr) + pre_oif->operation_pre (ctx, {}, true /* inner */, l); + action a (mid, pre_oid, oid); { @@ -1339,6 +1360,12 @@ main (int argc, char* argv[]) mif->execute (mparams, a, tgs, diag, true /* progress */); } + if (pre_oif->operation_post != nullptr) + pre_oif->operation_post (ctx, {}, true /* inner */); + + if (oif->operation_post != nullptr) + oif->operation_post (ctx, oparams, false /* inner */); + if (mif->operation_post != nullptr) mif->operation_post (ctx, mparams, pre_oid); @@ -1350,6 +1377,15 @@ main (int argc, char* argv[]) ctx.current_operation (*oif, outer_oif); + if (outer_oif != nullptr && outer_oif->operation_pre != nullptr) + outer_oif->operation_pre (ctx, oparams, false /* inner */, l); + + if (oif->operation_pre != nullptr) + oif->operation_pre (ctx, + outer_oif == nullptr ? oparams : values {}, + true /* inner */, + l); + action a (mid, oid, oif->outer_id); { @@ -1368,6 +1404,14 @@ main (int argc, char* argv[]) mif->execute (mparams, a, tgs, diag, true /* progress */); } + if (oif->operation_post != nullptr) + oif->operation_post (ctx, + outer_oif == nullptr ? oparams : values {}, + true /* inner */); + + if (outer_oif != nullptr && outer_oif->operation_post != nullptr) + outer_oif->operation_post (ctx, oparams, false /* inner */); + if (post_oid != 0) { tgs.reset (); @@ -1380,6 +1424,12 @@ main (int argc, char* argv[]) ctx.current_operation (*post_oif, oif); + if (oif->operation_pre != nullptr) + oif->operation_pre (ctx, oparams, false /* inner */, l); + + if (post_oif->operation_pre != nullptr) + post_oif->operation_pre (ctx, {}, true /* inner */, l); + action a (mid, post_oid, oid); { @@ -1398,6 +1448,12 @@ main (int argc, char* argv[]) mif->execute (mparams, a, tgs, diag, true /* progress */); } + if (post_oif->operation_post != nullptr) + post_oif->operation_post (ctx, {}, true /* inner */); + + if (oif->operation_post != nullptr) + oif->operation_post (ctx, oparams, false /* inner */); + if (mif->operation_post != nullptr) mif->operation_post (ctx, mparams, post_oid); diff --git a/libbuild2/cc/install-rule.cxx b/libbuild2/cc/install-rule.cxx index 640612c..b867466 100644 --- a/libbuild2/cc/install-rule.cxx +++ b/libbuild2/cc/install-rule.cxx @@ -238,9 +238,9 @@ namespace build2 const scope& rs (t.root_scope ()); auto& lp (t.data (perform_install_id).libs_paths); - auto ln = [&rs, &id] (const path& f, const path& l) + auto ln = [&t, &rs, &id] (const path& f, const path& l) { - install_l (rs, id, f.leaf (), l.leaf (), 2 /* verbosity */); + install_l (rs, id, l.leaf (), t, f.leaf (), 2 /* verbosity */); return true; }; @@ -274,7 +274,7 @@ namespace build2 auto rm = [&rs, &id] (const path& f, const path& l) { - return uninstall_l (rs, id, f.leaf (), l.leaf (), 2 /* verbosity */); + return uninstall_l (rs, id, l.leaf (), f.leaf (), 2 /* verbosity */); }; const path& lk (lp.link); diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx index 9079bbf..ea5540b 100644 --- a/libbuild2/config/operation.cxx +++ b/libbuild2/config/operation.cxx @@ -976,8 +976,14 @@ namespace build2 ctx.current_operation (*oif); + if (oif->operation_pre != nullptr) + oif->operation_pre (ctx, {}, true /* inner */, location ()); + phase_lock pl (ctx, run_phase::match); match_sync (action (configure_id, id), t); + + if (oif->operation_post != nullptr) + oif->operation_post (ctx, {}, true /* inner */); } } diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx index eabd279..b7429e0 100644 --- a/libbuild2/context.cxx +++ b/libbuild2/context.cxx @@ -749,6 +749,7 @@ namespace build2 } current_mif = &mif; + current_mdata = current_data_ptr (nullptr, null_current_data_deleter); current_on = 0; // Reset. } @@ -762,6 +763,8 @@ namespace build2 current_oname = oif.name; current_inner_oif = &inner_oif; current_outer_oif = outer_oif; + current_inner_odata = current_data_ptr (nullptr, null_current_data_deleter); + current_outer_odata = current_data_ptr (nullptr, null_current_data_deleter); current_on++; current_mode = inner_oif.mode; current_diag_noise = diag_noise; diff --git a/libbuild2/context.hxx b/libbuild2/context.hxx index bb8f31f..c040927 100644 --- a/libbuild2/context.hxx +++ b/libbuild2/context.hxx @@ -339,6 +339,22 @@ namespace build2 (current_mname.empty () && current_oname == mo)); }; + // Meta/operation-specific context-global auxiliary data storage. + // + // Note: cleared by current_[meta_]operation() below. Normally set by + // meta/operation-specific callbacks from [mate_]operation_info. + // + // Note also: watch out for MT-safety in the data itself. + // + static void + null_current_data_deleter (void* p) { assert (p == nullptr); } + + using current_data_ptr = unique_ptr; + + current_data_ptr current_mdata = {nullptr, null_current_data_deleter}; + current_data_ptr current_inner_odata = {nullptr, null_current_data_deleter}; + current_data_ptr current_outer_odata = {nullptr, null_current_data_deleter}; + // Current operation number (1-based) in the meta-operation batch. // size_t current_on; diff --git a/libbuild2/dist/operation.cxx b/libbuild2/dist/operation.cxx index 0a75afe..d68b573 100644 --- a/libbuild2/dist/operation.cxx +++ b/libbuild2/dist/operation.cxx @@ -273,7 +273,6 @@ namespace build2 // Note that we are not calling operation_pre/post() callbacks here // since the meta operation is dist and we know what we are doing. // - values params; path_name pn (""); const location loc (pn); // Dummy location. action_targets ts {tgt}; @@ -326,39 +325,72 @@ namespace build2 // if (auto pp = oif->pre_operation) { - if (operation_id pid = pp (ctx, params, dist_id, loc)) + if (operation_id pid = pp (ctx, {}, dist_id, loc)) { const operation_info* poif (ops[pid]); ctx.current_operation (*poif, oif, false /* diag_noise */); + + if (oif->operation_pre != nullptr) + oif->operation_pre (ctx, {}, false /* inner */, loc); + + if (poif->operation_pre != nullptr) + poif->operation_pre (ctx, {}, true /* inner */, loc); + action a (dist_id, poif->id, oif->id); mod.postponed.list.clear (); - perform_match (params, a, ts, + perform_match ({}, a, ts, 1 /* diag (failures only) */, false /* progress */); process_postponed (); + + if (poif->operation_post != nullptr) + poif->operation_post (ctx, {}, true /* inner */); + + if (oif->operation_post != nullptr) + oif->operation_post (ctx, {}, false /* inner */); } } ctx.current_operation (*oif, nullptr, false /* diag_noise */); + + if (oif->operation_pre != nullptr) + oif->operation_pre (ctx, {}, true /* inner */, loc); + action a (dist_id, oif->id); mod.postponed.list.clear (); - perform_match (params, a, ts, + perform_match ({}, a, ts, 1 /* diag (failures only) */, false /* progress */); process_postponed (); + if (oif->operation_post != nullptr) + oif->operation_post (ctx, {}, true /* inner */); + if (auto po = oif->post_operation) { - if (operation_id pid = po (ctx, params, dist_id)) + if (operation_id pid = po (ctx, {}, dist_id)) { const operation_info* poif (ops[pid]); ctx.current_operation (*poif, oif, false /* diag_noise */); + + if (oif->operation_pre != nullptr) + oif->operation_pre (ctx, {}, false /* inner */, loc); + + if (poif->operation_pre != nullptr) + poif->operation_pre (ctx, {}, true /* inner */, loc); + action a (dist_id, poif->id, oif->id); mod.postponed.list.clear (); - perform_match (params, a, ts, + perform_match ({}, a, ts, 1 /* diag (failures only) */, false /* progress */); process_postponed (); + + if (poif->operation_post != nullptr) + poif->operation_post (ctx, {}, true /* inner */); + + if (oif->operation_post != nullptr) + oif->operation_post (ctx, {}, false /* inner */); } } } @@ -471,7 +503,7 @@ namespace build2 // { if (mo_perform.meta_operation_pre != nullptr) - mo_perform.meta_operation_pre (ctx, params, loc); + mo_perform.meta_operation_pre (ctx, {}, loc); // This is a hack since according to the rules we need to completely // reset the state. We could have done that (i.e., saved target @@ -487,25 +519,31 @@ namespace build2 ctx.current_on = on + 1; if (mo_perform.operation_pre != nullptr) - mo_perform.operation_pre (ctx, params, update_id); + mo_perform.operation_pre (ctx, {}, update_id); ctx.current_operation (op_update, nullptr, false /* diag_noise */); + if (op_update.operation_pre != nullptr) + op_update.operation_pre (ctx, {}, true /* inner */, loc); + action a (perform_update_id); - mo_perform.match (params, a, files, + mo_perform.match ({}, a, files, 1 /* diag (failures only) */, prog /* progress */); - mo_perform.execute (params, a, files, + mo_perform.execute ({}, a, files, 1 /* diag (failures only) */, prog /* progress */); + if (op_update.operation_post != nullptr) + op_update.operation_post (ctx, {}, true /* inner */); + if (mo_perform.operation_post != nullptr) - mo_perform.operation_post (ctx, params, update_id); + mo_perform.operation_post (ctx, {}, update_id); if (mo_perform.meta_operation_post != nullptr) - mo_perform.meta_operation_post (ctx, params); + mo_perform.meta_operation_post (ctx, {}); } } else diff --git a/libbuild2/install/init.cxx b/libbuild2/install/init.cxx index d4b7f86..0f8b1be 100644 --- a/libbuild2/install/init.cxx +++ b/libbuild2/install/init.cxx @@ -459,6 +459,91 @@ namespace build2 config::unsave_variable (rs, v); } + // config.install.manifest + // + // Installation manifest. Valid values are a file path or `-` to dump + // the manifest to stdout. + // + // If specified during the install operation, then write the + // information about all the filesystem entries being installed into + // the manifest. If specified during uninstall, then remove the + // filesystem entries according to the manifest as opposed to the + // current build state. In particular, this functionality can be used + // to avoid surprising (and potentially lengthy) updates during + // uninstall that may happen because of changes to system-installed + // dependencies (for example, the compiler or standard library). + // + // @@ TODO: manifest uninstall is still TODO. + // + // Note: there is a single manifest per operation and thus this + // variable can only be specified as a global override. (While it + // could be handy to save this varible in config.build in some + // situations, supporting this will complicate the global override + // case). + // + // Note also that the manifest is produced even in the dry-run mode. + // However, in this case no directory creation is tracked. + // + // The format of the installation manifest is "JSON lines", that is, + // each line is a JSON text (this makes it possible to reverse the + // order of lines without loading the entire file into memory). For + // example (indented lines indicate line continuations): + // + // {"type":"directory","path":"/tmp/install","mode":"755"} + // {"type":"target","name":"/tmp/libhello/libs{hello}", + // "entries":[ + // {"type":"file","path":"/tmp/install/lib/libhello-1.0.so","mode":"755"}, + // {"type":"symlink","path":"/tmp/install/lib/libhello.so","target":"libhello-1.0.so"}]} + // + // Each line is a serialization of one of the following non-abstract + // C++ structs: + // + // struct entry // abstract + // { + // enum {directory, file, symlink, target} type; + // }; + // + // struct filesystem_entry: entry // abstract + // { + // path path; + // }; + // + // struct directory_entry: filesystem_entry + // { + // string mode; + // }; + // + // struct file_entry: filesystem_entry + // { + // string mode; + // }; + // + // struct symlink_entry: filesystem_entry + // { + // path target; + // }; + // + // struct target_entry: entry + // { + // string name; + // vector entries; + // }; + // + { + auto& v (vp.insert ("config.install.manifest")); + + // If specified, verify it is a global override. + // + if (lookup l = rs[v]) + { + if (!l.belongs (rs.global_scope ())) + fail << "config.install.manifest must be a global override" << + info << "specify !config.install.manifest=..."; + } + + config::unsave_variable (rs, v); + } + // Support for private install (aka poor man's Flatpack). // const dir_path* p; diff --git a/libbuild2/install/operation.cxx b/libbuild2/install/operation.cxx index 52e8c94..32da60f 100644 --- a/libbuild2/install/operation.cxx +++ b/libbuild2/install/operation.cxx @@ -3,6 +3,11 @@ #include +#include + +#include +#include +#include #include using namespace std; @@ -12,25 +17,300 @@ namespace build2 { namespace install { +#ifndef BUILD2_BOOTSTRAP + install_context_data:: + install_context_data (const path* mf) + : manifest_file (mf), + manifest_os (mf != nullptr + ? open_file_or_stdout (manifest_file, manifest_ofs) + : manifest_ofs), + manifest_autorm (manifest_ofs.is_open () ? *mf : path ()), + manifest_json (manifest_os, 0 /* indentation */) + { + } + + // Serialize current target and, if tgt is not NULL, start the new target. + // + // Note that we always serialize directories as top-level entries. And + // theoretically we can end up "splitting" a target with a directory + // creation. For example, if some files that belong to the target are + // installed into subdirectories that have not yet been created. So we + // have to cache the information for the current target in memory and only + // flush it once we see the next target (or the end). + // + // You may be wondering why not just serialize directories as target + // entries. While we could do that, it's not quite correct conceptually, + // since this would be the first of potentially many targets that caused + // the directory's creation. To put it another way, while files and + // symlinks belong to tragets, directories do not. + // + static void + manifest_flush_target (install_context_data& d, const target* tgt) + { + if (d.manifest_target != nullptr) + { + assert (!d.manifest_target_entries.empty ()); + + // Target name format is the same as in the structured result output. + // + ostringstream os; + stream_verb (os, stream_verbosity (1, 0)); + os << *d.manifest_target; + + try + { + auto& s (d.manifest_json); + + s.begin_object (); + s.member ("type", "target"); + s.member ("name", os.str ()); + s.member_name ("entries"); + s.begin_array (); + + for (const auto& e: d.manifest_target_entries) + { + s.begin_object (); + + if (e.target.empty ()) + { + s.member ("type", "file"); + s.member ("path", e.path); + s.member ("mode", e.mode); + } + else + { + s.member ("type", "symlink"); + s.member ("path", e.path); + s.member ("target", e.target); + } + + s.end_object (); + } + + s.end_array (); // entries member + s.end_object (); // target object + } + catch (const json::invalid_json_output& e) + { + fail << "invalid " << d.manifest_file << " json output: " << e; + } + catch (const io_error& e) + { + fail << "unable to write to " << d.manifest_file << ": " << e; + } + + d.manifest_target_entries.clear (); + } + + d.manifest_target = tgt; + } + + void install_context_data:: + manifest_install_d (context& ctx, + const target& tgt, + const dir_path& dir, + const string& mode) + { + auto& d ( + *static_cast (ctx.current_inner_odata.get ())); + + if (d.manifest_file.path != nullptr) + { + try + { + auto& s (d.manifest_json); + + // If we moved to the next target, flush the current one. + // + if (d.manifest_target != &tgt) + manifest_flush_target (d, nullptr); + + s.begin_object (); + s.member ("type", "directory"); + s.member ("path", dir.string ()); + s.member ("mode", mode); + s.end_object (); + } + catch (const json::invalid_json_output& e) + { + fail << "invalid " << d.manifest_file << " json output: " << e; + } + catch (const io_error& e) + { + fail << "unable to write to " << d.manifest_file << ": " << e; + } + } + } + + void install_context_data:: + manifest_install_f (context& ctx, + const target& tgt, + const dir_path& dir, + const path& name, + const string& mode) + { + auto& d ( + *static_cast (ctx.current_inner_odata.get ())); + + if (d.manifest_file.path != nullptr) + { + if (d.manifest_target != &tgt) + manifest_flush_target (d, &tgt); + + d.manifest_target_entries.push_back ( + manifest_target_entry {(dir / name).string (), mode, ""}); + } + } + + void install_context_data:: + manifest_install_l (context& ctx, + const target& tgt, + const path& link_target, + const dir_path& dir, + const path& link) + { + auto& d ( + *static_cast (ctx.current_inner_odata.get ())); + + if (d.manifest_file.path != nullptr) + { + if (d.manifest_target != &tgt) + manifest_flush_target (d, &tgt); + + d.manifest_target_entries.push_back ( + manifest_target_entry { + (dir / link).string (), "", link_target.string ()}); + } + } + + static void + manifest_close (context& ctx) + { + auto& d ( + *static_cast (ctx.current_inner_odata.get ())); + + if (d.manifest_file.path != nullptr) + { + try + { + manifest_flush_target (d, nullptr); + + d.manifest_os << '\n'; // Final newline. + + if (d.manifest_ofs.is_open ()) + { + d.manifest_ofs.close (); + d.manifest_autorm.cancel (); + } + } + catch (const json::invalid_json_output& e) + { + fail << "invalid " << d.manifest_file << " json output: " << e; + } + catch (const io_error& e) + { + fail << "unable to write to " << d.manifest_file << ": " << e; + } + } + } +#else + install_context_data:: + install_context_data (const path*) + { + } + + void install_context_data:: + manifest_install_d (context&, + const target&, + const dir_path&, + const string&) + { + } + + void install_context_data:: + manifest_install_f (context&, + const target&, + const dir_path&, + const path&, + const string&) + { + } + + void install_context_data:: + manifest_install_l (context&, + const target&, + const path&, + const dir_path&, + const path&) + { + } + + static void + manifest_close (context&) + { + } +#endif + static operation_id - install_pre (context&, - const values& params, + pre_install (context&, + const values&, meta_operation_id mo, - const location& l) + const location&) { - if (!params.empty ()) - fail (l) << "unexpected parameters for operation install"; + // Run update as a pre-operation, unless we are disfiguring. + // + return mo != disfigure_id ? update_id : 0; + } + static operation_id + pre_uninstall (context&, + const values&, + meta_operation_id mo, + const location&) + { // Run update as a pre-operation, unless we are disfiguring. // return mo != disfigure_id ? update_id : 0; } + static void + install_pre (context& ctx, + const values& params, + bool inner, + const location& l) + { + if (!params.empty ()) + fail (l) << "unexpected parameters for operation install"; + + if (inner) + { + // See if we need to write the installation manifest. + // + // Note: go straight for the public variable pool. + // + const variable& var (*ctx.var_pool.find ("config.install.manifest")); + const path* mf (cast_null (ctx.global_scope[var])); + + ctx.current_inner_odata = context::current_data_ptr ( + new install_context_data (mf), + [] (void* p) {delete static_cast (p);}); + } + } + + static void + install_post (context& ctx, const values&, bool inner) + { + if (inner) + manifest_close (ctx); + } + // Note that we run both install and uninstall serially. The reason for // this is all the fuzzy things we are trying to do like removing empty // outer directories if they are empty. If we do this in parallel, then // those things get racy. Also, since all we do here is creating/removing // files, there is not going to be much speedup from doing it in parallel. + // There is also now the installation manifest, which relies on us + // installing all the filesystem entries of a target serially. const operation_info op_install { install_id, @@ -42,8 +322,10 @@ namespace build2 "has nothing to install", // We cannot "be installed". execution_mode::first, 0 /* concurrency */, // Run serially. - &install_pre, + &pre_install, nullptr, + &install_pre, + &install_post, nullptr, nullptr }; @@ -67,7 +349,9 @@ namespace build2 "is not installed", execution_mode::last, 0 /* concurrency */, // Run serially - &install_pre, + &pre_uninstall, + nullptr, + nullptr, nullptr, nullptr, nullptr @@ -87,6 +371,8 @@ namespace build2 op_update.concurrency, op_update.pre_operation, op_update.post_operation, + op_update.operation_pre, + op_update.operation_post, op_update.adhoc_match, op_update.adhoc_apply }; diff --git a/libbuild2/install/operation.hxx b/libbuild2/install/operation.hxx index c1f5416..71bdcba 100644 --- a/libbuild2/install/operation.hxx +++ b/libbuild2/install/operation.hxx @@ -7,7 +7,12 @@ #include #include +#ifndef BUILD2_BOOTSTRAP +# include +#endif + #include +#include // auto_rmfile namespace build2 { @@ -16,6 +21,59 @@ namespace build2 extern const operation_info op_install; extern const operation_info op_uninstall; extern const operation_info op_update_for_install; + + // Set as context::current_inner_odata during the install inner operation. + // + struct install_context_data + { +#ifndef BUILD2_BOOTSTRAP + path_name manifest_file; + ofdstream manifest_ofs; + ostream& manifest_os; + auto_rmfile manifest_autorm; + butl::json::stream_serializer manifest_json; + const target* manifest_target = nullptr; // Target being installed. + struct manifest_target_entry + { + string path; + string mode; + string target; + }; + vector manifest_target_entries; +#endif + + explicit + install_context_data (const path* manifest); + + // The following manifest_install_[dfl]() functions correspond to (and + // are called from) file_rule::install_[dfl](). + + // install -d -m + // + static void + manifest_install_d (context&, + const target&, + const dir_path& dir, + const string& mode); + + // install -m / + // + static void + manifest_install_f (context&, + const target& file, + const dir_path& dir, + const path& name, + const string& mode); + + // install -l / + // + static void + manifest_install_l (context&, + const target&, + const path& link_target, + const dir_path& dir, + const path& link); + }; } } diff --git a/libbuild2/install/rule.cxx b/libbuild2/install/rule.cxx index 5ff4703..a3fa5ee 100644 --- a/libbuild2/install/rule.cxx +++ b/libbuild2/install/rule.cxx @@ -13,6 +13,8 @@ #include #include +#include + using namespace std; using namespace butl; @@ -775,6 +777,7 @@ namespace build2 install_d (const scope& rs, const install_dir& base, const dir_path& d, + const file& t, uint16_t verbosity) { context& ctx (rs.ctx); @@ -789,6 +792,9 @@ namespace build2 // with uninstall since the directories won't be empty (because we don't // actually uninstall any files). // + // Note that this also means we won't have the directory entries in the + // manifest created with dry-run. Probably not a big deal. + // if (ctx.dry_run) return; @@ -816,7 +822,7 @@ namespace build2 dir_path pd (d.directory ()); if (pd != base.dir) - install_d (rs, base, pd, verbosity); + install_d (rs, base, pd, t, verbosity); } cstrings args; @@ -853,6 +859,8 @@ namespace build2 run (ctx, pp, args, verb >= verbosity ? 1 : verb_never /* finish_verbosity */); + + install_context_data::manifest_install_d (ctx, t, d, *base.dir_mode); } void file_rule:: @@ -915,13 +923,21 @@ namespace build2 run (ctx, pp, args, verb >= verbosity ? 1 : verb_never /* finish_verbosity */); + + install_context_data::manifest_install_f ( + ctx, + t, + base.dir, + name.empty () ? f.leaf () : name, + *base.mode); } void file_rule:: install_l (const scope& rs, const install_dir& base, - const path& target, const path& link, + const file& target, + const path& link_target, uint16_t verbosity) { context& ctx (rs.ctx); @@ -942,7 +958,7 @@ namespace build2 base.sudo != nullptr ? base.sudo->c_str () : nullptr, "ln", "-sf", - target.string ().c_str (), + link_target.string ().c_str (), rell.string ().c_str (), nullptr}; @@ -960,7 +976,7 @@ namespace build2 // a link. FreeBSD install(1) has the -l flag with the appropriate // semantics. For consistency, we also pass -d above. // - print_diag ("install -l", target, chd / link); + print_diag ("install -l", link_target, chd / link); } } @@ -979,15 +995,15 @@ namespace build2 if (verb >= verbosity) { if (verb >= 2) - text << "ln -sf " << target.string () << ' ' << rell.string (); + text << "ln -sf " << link_target.string () << ' ' << rell.string (); else if (verb) - print_diag ("install -l", target, chd / link); + print_diag ("install -l", link_target, chd / link); } if (!ctx.dry_run) try { - mkanylink (target, rell, true /* copy */); + mkanylink (link_target, rell, true /* copy */); } catch (const pair& e) { @@ -999,6 +1015,13 @@ namespace build2 fail << "unable to make " << w << ' ' << rell << ": " << e.second; } #endif + + install_context_data::manifest_install_l ( + ctx, + target, + link_target, + base.dir, + link); } target_state file_rule:: @@ -1047,7 +1070,7 @@ namespace build2 // sudo, etc). // for (auto i (ids.begin ()), j (i); i != ids.end (); j = i++) - install_d (rs, *j, i->dir, verbosity); // install -d + install_d (rs, *j, i->dir, t, verbosity); // install -d install_dir& id (ids.back ()); @@ -1336,8 +1359,8 @@ namespace build2 bool file_rule:: uninstall_l (const scope& rs, const install_dir& base, - const path& /*target*/, const path& link, + const path& /*link_target*/, uint16_t verbosity) { dir_path chd (chroot_path (rs, base.dir)); diff --git a/libbuild2/install/rule.hxx b/libbuild2/install/rule.hxx index 98d2d0d..eb8addf 100644 --- a/libbuild2/install/rule.hxx +++ b/libbuild2/install/rule.hxx @@ -188,10 +188,16 @@ namespace build2 // // install -d // + // Note: is expected to be absolute. + // + // Note that the target argument only specifies which target caused + // this directory to be created. + // static void install_d (const scope& rs, const install_dir& base, const dir_path& dir, + const file& target, uint16_t verbosity = 1); // Install a file: @@ -209,13 +215,21 @@ namespace build2 // Install (make) a symlink: // - // ln -s / + // install -l / + // + // Which is essentially: + // + // ln -s / + // + // Note that the target argument only specifies which target this + // symlink "belongs" to. // static void install_l (const scope& rs, const install_dir& base, - const path& target, const path& link, + const file& target, + const path& link_target, uint16_t verbosity = 1); // Uninstall (remove) a file or symlink: @@ -241,8 +255,8 @@ namespace build2 static bool uninstall_l (const scope& rs, const install_dir& base, - const path& target, const path& link, + const path& link_target, uint16_t verbosity = 1); diff --git a/libbuild2/module.cxx b/libbuild2/module.cxx index 62145ca..234b469 100644 --- a/libbuild2/module.cxx +++ b/libbuild2/module.cxx @@ -128,6 +128,9 @@ namespace build2 { // New update operation. // + assert (op_update.operation_pre == nullptr && + op_update.operation_post == nullptr); + ctx.module_context->current_operation (op_update); // Un-tune the scheduler. diff --git a/libbuild2/operation.cxx b/libbuild2/operation.cxx index 0e782d1..2e5886c 100644 --- a/libbuild2/operation.cxx +++ b/libbuild2/operation.cxx @@ -1200,6 +1200,8 @@ namespace build2 nullptr, nullptr, nullptr, + nullptr, + nullptr, nullptr }; @@ -1226,6 +1228,8 @@ namespace build2 nullptr, nullptr, nullptr, + nullptr, + nullptr, nullptr }; @@ -1242,6 +1246,8 @@ namespace build2 nullptr, nullptr, nullptr, + nullptr, + nullptr, nullptr }; } diff --git a/libbuild2/operation.hxx b/libbuild2/operation.hxx index 9d84e26..e8ff38a 100644 --- a/libbuild2/operation.hxx +++ b/libbuild2/operation.hxx @@ -121,6 +121,8 @@ namespace build2 // End of operation and meta-operation batches. // + // Note: not called in case any of the earlier callbacks failed. + // void (*operation_post) (context&, const values&, operation_id); void (*meta_operation_post) (context&, const values&); @@ -223,14 +225,22 @@ namespace build2 // const size_t concurrency; - // The first argument in all the callbacks is the operation parameters. + // The values argument in the callbacks is the operation parameters. If + // the operation expects parameters, then it should have a non-NULL + // operation_pre() callback. Failed that, any parameters will be diagnosed + // as unexpected. // - // If the operation expects parameters, then it should have a non-NULL - // pre(). Failed that, any parameters will be diagnosed as unexpected. + // Note also that if the specified operation has outer (for example, + // update-for-install), then parameters belong to outer (for example, + // install; this is done in order to be consistent with the case when + // update is performed as a pre-operation of install). - // If the returned operation_id's are not 0, then they are injected - // as pre/post operations for this operation. Can be NULL if unused. - // The returned operation_id shall not be default_id. + // Pre/post operations for this operation. Note that these callbacks are + // called before this operation becomes current. + // + // If the returned by pre/post_*() operation_id's are not 0, then they are + // injected as pre/post operations for this operation. Can be NULL if + // unused. The returned operation_id shall not be default_id. // operation_id (*pre_operation) ( context&, const values&, meta_operation_id, const location&); @@ -238,6 +248,16 @@ namespace build2 operation_id (*post_operation) ( context&, const values&, meta_operation_id); + // Called immediately after/before this operation becomes/ceases to be + // current operation for the specified context. Can be used to + // initialize/finalize operation-specific data (context::current_*_odata). + // Can be NULL if unused. + // + void (*operation_pre) ( + context&, const values&, bool inner, const location&); + void (*operation_post) ( + context&, const values&, bool inner); + // Operation-specific ad hoc rule callbacks. Essentially, if not NULL, // then every ad hoc rule match and apply call for this operation is // proxied through these functions. diff --git a/libbuild2/test/operation.cxx b/libbuild2/test/operation.cxx index 841abb5..2535adb 100644 --- a/libbuild2/test/operation.cxx +++ b/libbuild2/test/operation.cxx @@ -17,14 +17,8 @@ namespace build2 namespace test { static operation_id - test_pre (context&, - const values& params, - meta_operation_id mo, - const location& l) + pre_test (context&, const values&, meta_operation_id mo, const location&) { - if (!params.empty ()) - fail (l) << "unexpected parameters for operation test"; - // Run update as a pre-operation, unless we are disfiguring. // return mo != disfigure_id ? update_id : 0; @@ -70,7 +64,9 @@ namespace build2 "has nothing to test", // We cannot "be tested". execution_mode::first, 1 /* concurrency */, - &test_pre, + &pre_test, + nullptr, + nullptr, nullptr, nullptr, &adhoc_apply @@ -90,6 +86,8 @@ namespace build2 op_update.concurrency, op_update.pre_operation, op_update.post_operation, + op_update.operation_pre, + op_update.operation_post, op_update.adhoc_match, op_update.adhoc_apply }; -- cgit v1.1