From 57b10c06925d0bdf6ffb38488ee908f085109e95 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 4 Jul 2019 19:12:15 +0300 Subject: Move config, dist, test, and install modules into library --- libbuild2/install/functions.cxx | 33 ++ libbuild2/install/init.cxx | 309 ++++++++++ libbuild2/install/init.hxx | 36 ++ libbuild2/install/operation.cxx | 84 +++ libbuild2/install/operation.hxx | 23 + libbuild2/install/rule.cxx | 1223 +++++++++++++++++++++++++++++++++++++++ libbuild2/install/rule.hxx | 197 +++++++ libbuild2/install/utility.hxx | 78 +++ 8 files changed, 1983 insertions(+) create mode 100644 libbuild2/install/functions.cxx create mode 100644 libbuild2/install/init.cxx create mode 100644 libbuild2/install/init.hxx create mode 100644 libbuild2/install/operation.cxx create mode 100644 libbuild2/install/operation.hxx create mode 100644 libbuild2/install/rule.cxx create mode 100644 libbuild2/install/rule.hxx create mode 100644 libbuild2/install/utility.hxx (limited to 'libbuild2/install') diff --git a/libbuild2/install/functions.cxx b/libbuild2/install/functions.cxx new file mode 100644 index 0000000..f067918 --- /dev/null +++ b/libbuild2/install/functions.cxx @@ -0,0 +1,33 @@ +// file : libbuild2/install/functions.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include +#include + +#include + +using namespace std; + +namespace build2 +{ + namespace install + { + void + functions () + { + function_family f ("install"); + + // Resolve potentially relative install.* value to an absolute directory + // based on (other) install.* values visible from the calling scope. + // + f[".resolve"] = [] (const scope* s, dir_path d) + { + if (s == nullptr) + fail << "install.resolve() called out of scope" << endf; + + return resolve_dir (*s, move (d)); + }; + } + } +} diff --git a/libbuild2/install/init.cxx b/libbuild2/install/init.cxx new file mode 100644 index 0000000..fb3d9ea --- /dev/null +++ b/libbuild2/install/init.cxx @@ -0,0 +1,309 @@ +// file : libbuild2/install/init.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace install + { + // Set install..* values based on config.install..* ones + // or the defaults. If none of config.install.* values were specified, + // then we do omitted/delayed configuration. Note that we still need + // to set all the install.* values to defaults, as if we had the + // default configuration. + // + // If override is true, then override values that came from outer + // configurations. We have to do this for paths that contain the + // package name. + // + // For global values we only set config.install.* variables. Non-global + // values with NULL defaults are omitted. + // + template + static void + set_var (bool spec, + scope& r, + const char* name, + const char* var, + const CT* dv, + bool override = false) + { + string vn; + lookup l; + + bool global (*name == '\0'); + + if (spec) + { + // Note: overridable. + // + vn = "config.install"; + if (!global) + { + vn += '.'; + vn += name; + } + vn += var; + const variable& vr (var_pool.rw (r).insert (move (vn), true)); + + l = dv != nullptr + ? config::required (r, vr, *dv, override).first + : (global + ? config::optional (r, vr) + : config::omitted (r, vr).first); + } + + if (global) + return; + + // Note: not overridable. + // + vn = "install."; + vn += name; + vn += var; + const variable& vr (var_pool.rw (r).insert (move (vn))); + + value& v (r.assign (vr)); + + if (spec) + { + if (l) + v = cast (l); // Strip CT to T. + } + else + { + if (dv != nullptr) + v = *dv; + } + } + + template + static void + set_dir (bool s, // specified + scope& r, // root scope + const char* n, // var name + const T& p, // path + bool o = false, // override + const string& fm = string (), // file mode + const string& dm = string (), // dir mode + const build2::path& c = build2::path ()) // command + { + using build2::path; + + bool global (*n == '\0'); + + if (!global) + set_var (s, r, n, "", p.empty () ? nullptr : &p, o); + + set_var (s, r, n, ".cmd", c.empty () ? nullptr : &c); + set_var (s, r, n, ".options", (strings*) (nullptr)); + set_var (s, r, n, ".mode", fm.empty () ? nullptr : &fm); + set_var (s, r, n, ".dir_mode", dm.empty () ? nullptr : &dm); + set_var (s, r, n, ".sudo", (string*) (nullptr)); + + // This one doesn't have config.* value (only set in a buildfile). + // + if (!global) + var_pool.rw (r).insert (string ("install.") + n + ".subdirs"); + } + + void + functions (); // functions.cxx + + bool + boot (scope& rs, const location&, unique_ptr&) + { + tracer trace ("install::boot"); + l5 ([&]{trace << "for " << rs;}); + + // Register install function family if this is the first instance of the + // install modules. + // + if (!function_family::defined ("install")) + functions (); + + // Register our operations. + // + rs.insert_operation (install_id, op_install); + rs.insert_operation (uninstall_id, op_uninstall); + rs.insert_operation (update_for_install_id, op_update_for_install); + + return false; + } + + static const path cmd ("install"); + + static const dir_path dir_root ("root"); + + static const dir_path dir_sbin (dir_path ("exec_root") /= "sbin"); + static const dir_path dir_bin (dir_path ("exec_root") /= "bin"); + static const dir_path dir_lib (dir_path ("exec_root") /= "lib"); + static const dir_path dir_libexec (dir_path ("exec_root") /= "libexec"); + static const dir_path dir_pkgconfig (dir_path ("lib") /= "pkgconfig"); + + static const dir_path dir_data (dir_path ("data_root") /= "share"); + static const dir_path dir_include (dir_path ("data_root") /= "include"); + + static const dir_path dir_doc (dir_path (dir_data) /= "doc"); + static const dir_path dir_man (dir_path (dir_data) /= "man"); + static const dir_path dir_man1 (dir_path ("man") /= "man1"); + + static const group_rule group_rule_ (true /* see_through_only */); + + bool + init (scope& rs, + scope& bs, + const location& l, + unique_ptr&, + bool first, + bool, + const variable_map& config_hints) + { + tracer trace ("install::init"); + + if (!first) + { + warn (l) << "multiple install module initializations"; + return true; + } + + const dir_path& out_root (rs.out_path ()); + l5 ([&]{trace << "for " << out_root;}); + + assert (config_hints.empty ()); // We don't known any hints. + + // Enter module variables. + // + auto& vp (var_pool.rw (rs)); + + // Note that the set_dir() calls below enter some more. + // + { + // Note: not overridable. + // + // The install variable is a path, not dir_path, since it can be used + // to both specify the target directory (to install with the same file + // name) or target file (to install with a different name). And the + // way we distinguish between the two is via the presence/absence of + // the trailing directory separator. + // + vp.insert ("install", variable_visibility::target); + vp.insert ("install.mode", variable_visibility::project); + vp.insert ("install.subdirs", variable_visibility::project); + } + + // Register our rules. + // + { + auto& r (bs.rules); + + const auto& ar (alias_rule::instance); + const auto& dr (fsdir_rule::instance); + const auto& fr (file_rule::instance); + const auto& gr (group_rule_); + + r.insert (perform_install_id, "install.alias", ar); + r.insert (perform_uninstall_id, "uninstall.alias", ar); + + r.insert (perform_install_id, "install.fsdir", dr); + r.insert (perform_uninstall_id, "install.fsdir", dr); + + r.insert (perform_install_id, "install.file", fr); + r.insert (perform_uninstall_id, "uninstall.file", fr); + + r.insert (perform_install_id, "install.file", gr); + r.insert (perform_uninstall_id, "uninstall.file", gr); + } + + // Configuration. + // + // Note that we don't use any defaults for root -- the location + // must be explicitly specified or the installer will complain + // if and when we try to install. + // + { + using build2::path; + + bool s (config::specified (rs, "install")); + + // Adjust module priority so that the (numerous) config.install.* + // values are saved at the end of config.build. + // + if (s) + config::save_module (rs, "install", INT32_MAX); + + const string& n (project (rs).string ()); + + // Global config.install.* values. + // + set_dir (s, rs, "", abs_dir_path (), false, "644", "755", cmd); + + set_dir (s, rs, "root", abs_dir_path ()); + + set_dir (s, rs, "data_root", dir_root); + set_dir (s, rs, "exec_root", dir_root, false, "755"); + + set_dir (s, rs, "sbin", dir_sbin); + set_dir (s, rs, "bin", dir_bin); + set_dir (s, rs, "lib", dir_lib); + set_dir (s, rs, "libexec", dir_path (dir_libexec) /= n, true); + set_dir (s, rs, "pkgconfig", dir_pkgconfig, false, "644"); + + set_dir (s, rs, "data", dir_path (dir_data) /= n, true); + set_dir (s, rs, "include", dir_include); + + set_dir (s, rs, "doc", dir_path (dir_doc) /= n, true); + set_dir (s, rs, "man", dir_man); + set_dir (s, rs, "man1", dir_man1); + + // Support for chroot'ed install (aka DESTDIR). + // + { + auto& var (vp.insert ( "install.chroot")); + auto& cvar (vp.insert ("config.install.chroot", true)); + + value& v (rs.assign (var)); + + if (s) + { + if (lookup l = config::optional (rs, cvar)) + v = cast (l); // Strip abs_dir_path. + } + } + } + + // Configure "installability" for built-in target types. + // + install_path (bs, dir_path ("bin")); // Install into install.bin. + install_path (bs, dir_path ("doc")); // Install into install.doc. + install_path (bs, dir_path ("man")); // Install into install.man. + install_path (bs, dir_path ("man1")); // Install into install.man1. + + return true; + } + + module_functions + build2_install_load () + { + return module_functions {&boot, &init}; + } + } +} diff --git a/libbuild2/install/init.hxx b/libbuild2/install/init.hxx new file mode 100644 index 0000000..fa0a1e1 --- /dev/null +++ b/libbuild2/install/init.hxx @@ -0,0 +1,36 @@ +// file : libbuild2/install/init.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_INSTALL_INIT_HXX +#define LIBBUILD2_INSTALL_INIT_HXX + +#include +#include + +#include + +#include + +namespace build2 +{ + namespace install + { + bool + boot (scope&, const location&, unique_ptr&); + + bool + init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + extern "C" LIBBUILD2_SYMEXPORT module_functions + build2_install_load (); + } +} + +#endif // LIBBUILD2_INSTALL_INIT_HXX diff --git a/libbuild2/install/operation.cxx b/libbuild2/install/operation.cxx new file mode 100644 index 0000000..1135ad6 --- /dev/null +++ b/libbuild2/install/operation.cxx @@ -0,0 +1,84 @@ +// file : libbuild2/install/operation.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace install + { + static operation_id + install_pre (const values& params, meta_operation_id mo, const location& l) + { + 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; + } + + // 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. + + const operation_info op_install { + install_id, + 0, + "install", + "install", + "installing", + "installed", + "has nothing to install", // We cannot "be installed". + execution_mode::first, + 0, + &install_pre, + nullptr + }; + + // Note that we run update as a pre-operation, just like install. Which + // may seem bizarre at first. We do it to obtain the exact same dependency + // graph as install so that we uninstall exactly the same set of files as + // install would install. Note that just matching the rules without + // executing them may not be enough: for example, a presence of an ad hoc + // group member may only be discovered after executing the rule (e.g., VC + // link.exe only creates a DLL's import library if there are any exported + // symbols). + // + const operation_info op_uninstall { + uninstall_id, + 0, + "uninstall", + "uninstall", + "uninstalling", + "uninstalled", + "is not installed", + execution_mode::last, + 0, + &install_pre, + nullptr + }; + + // Also the explicit update-for-install operation alias. + // + const operation_info op_update_for_install { + update_id, // Note: not update_for_install_id. + install_id, + op_update.name, + op_update.name_do, + op_update.name_doing, + op_update.name_did, + op_update.name_done, + op_update.mode, + op_update.concurrency, + op_update.pre, + op_update.post + }; + } +} diff --git a/libbuild2/install/operation.hxx b/libbuild2/install/operation.hxx new file mode 100644 index 0000000..40cf25d --- /dev/null +++ b/libbuild2/install/operation.hxx @@ -0,0 +1,23 @@ +// file : libbuild2/install/operation.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_INSTALL_OPERATION_HXX +#define LIBBUILD2_INSTALL_OPERATION_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace install + { + extern const operation_info op_install; + extern const operation_info op_uninstall; + extern const operation_info op_update_for_install; + } +} + +#endif // LIBBUILD2_INSTALL_OPERATION_HXX diff --git a/libbuild2/install/rule.cxx b/libbuild2/install/rule.cxx new file mode 100644 index 0000000..0b34832 --- /dev/null +++ b/libbuild2/install/rule.cxx @@ -0,0 +1,1223 @@ +// file : libbuild2/install/rule.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include +#include // resolve_dir() declaration + +#include // dir_exists(), file_exists() + +#include +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace install + { + // Lookup the install or install.* variable. Return NULL if not found or + // if the value is the special 'false' name (which means do not install; + // so the result can be used as bool). T is either scope or target. + // + template + static const P* + lookup_install (T& t, const string& var) + { + auto l (t[var]); + + if (!l) + return nullptr; + + const P& r (cast

(l)); + return r.simple () && r.string () == "false" ? nullptr : &r; + } + + // alias_rule + // + const alias_rule alias_rule::instance; + + bool alias_rule:: + match (action, target&, const string&) const + { + // We always match. + // + // Note that we are called both as the outer part during the update-for- + // un/install pre-operation and as the inner part during the un/install + // operation itself. + // + return true; + } + + const target* alias_rule:: + filter (action a, const target& t, prerequisite_iterator& i) const + { + assert (i->member == nullptr); + return filter (a, t, i->prerequisite); + } + + const target* alias_rule:: + filter (action, const target& t, const prerequisite& p) const + { + const target& pt (search (t, p)); + return pt.in (t.weak_scope ()) ? &pt : nullptr; + } + + recipe alias_rule:: + apply (action a, target& t) const + { + tracer trace ("install::alias_rule::apply"); + + // Pass-through to our installable prerequisites. + // + // @@ Shouldn't we do match in parallel (here and below)? + // + auto& pts (t.prerequisite_targets[a]); + + auto pms (group_prerequisite_members (a, t, members_mode::never)); + for (auto i (pms.begin ()), e (pms.end ()); i != e; ++i) + { + const prerequisite& p (i->prerequisite); + + // Ignore excluded. + // + include_type pi (include (a, t, p)); + + if (!pi) + continue; + + // Ignore unresolved targets that are imported from other projects. + // We are definitely not installing those. + // + if (p.proj) + continue; + + // Let a customized rule have its say. + // + // Note: we assume that if the filter enters the group, then it + // iterates over all its members. + // + const target* pt (filter (a, t, i)); + if (pt == nullptr) + { + l5 ([&]{trace << "ignoring " << p << " (filtered out)";}); + continue; + } + + // Check if this prerequisite is explicitly "not installable", that + // is, there is the 'install' variable and its value is false. + // + // At first, this might seem redundand since we could have let the + // file_rule below take care of it. The nuance is this: this + // prerequsite can be in a different subproject that hasn't loaded the + // install module (and therefore has no file_rule registered). The + // typical example would be the 'tests' subproject. + // + // Note: not the same as lookup_install() above. + // + auto l ((*pt)["install"]); + if (l && cast (l).string () == "false") + { + l5 ([&]{trace << "ignoring " << *pt << " (not installable)";}); + continue; + } + + // If this is not a file-based target (e.g., a target group such as + // libu{}) then ignore it if there is no rule to install. + // + if (pt->is_a ()) + build2::match (a, *pt); + else if (!try_match (a, *pt).first) + { + l5 ([&]{trace << "ignoring " << *pt << " (no rule)";}); + pt = nullptr; + } + + if (pt != nullptr) + pts.push_back (prerequisite_target (pt, pi)); + } + + return default_recipe; + } + + // fsdir_rule + // + const fsdir_rule fsdir_rule::instance; + + bool fsdir_rule:: + match (action, target&, const string&) const + { + // We always match. + // + // Note that we are called both as the outer part during the update-for- + // un/install pre-operation and as the inner part during the un/install + // operation itself. + // + return true; + } + + recipe fsdir_rule:: + apply (action a, target& t) const + { + // If this is outer part of the update-for-un/install, delegate to the + // default fsdir rule. Otherwise, this is a noop (we don't install + // fsdir{}). + // + // For now we also assume we don't need to do anything for prerequisites + // (the only sensible prerequisite of fsdir{} is another fsdir{}). + // + if (a.operation () == update_id) + { + match_inner (a, t); + return &execute_inner; + } + else + return noop_recipe; + } + + // group_rule + // + const group_rule group_rule::instance (false /* see_through_only */); + + bool group_rule:: + match (action a, target& t, const string& h) const + { + return (!see_through || t.type ().see_through) && + alias_rule::match (a, t, h); + } + + const target* group_rule:: + filter (action, const target&, const target& m) const + { + return &m; + } + + recipe group_rule:: + apply (action a, target& t) const + { + tracer trace ("install::group_rule::apply"); + + // Resolve group members. + // + // Remember that we are called twice: first during update for install + // (pre-operation) and then during install. During the former, we rely + // on the normall update rule to resolve the group members. During the + // latter, there will be no rule to do this but the group will already + // have been resolved by the pre-operation. + // + // If the rule could not resolve the group, then we ignore it. + // + group_view gv (a.outer () + ? resolve_members (a, t) + : t.group_members (a)); + + if (gv.members != nullptr) + { + auto& pts (t.prerequisite_targets[a]); + + for (size_t i (0); i != gv.count; ++i) + { + const target* m (gv.members[i]); + + if (m == nullptr) + continue; + + // Let a customized rule have its say. + // + const target* mt (filter (a, t, *m)); + if (mt == nullptr) + { + l5 ([&]{trace << "ignoring " << *m << " (filtered out)";}); + continue; + } + + // See if we were explicitly instructed not to touch this target + // (the same semantics as in the prerequisites match). + // + // Note: not the same as lookup_install() above. + // + auto l ((*mt)["install"]); + if (l && cast (l).string () == "false") + { + l5 ([&]{trace << "ignoring " << *mt << " (not installable)";}); + continue; + } + + build2::match (a, *mt); + pts.push_back (mt); // Never ad hoc. + } + } + + // Delegate to the base rule. + // + return alias_rule::apply (a, t); + } + + + // file_rule + // + const file_rule file_rule::instance; + + bool file_rule:: + match (action, target&, const string&) const + { + // We always match, even if this target is not installable (so that we + // can ignore it; see apply()). + // + return true; + } + + const target* file_rule:: + filter (action a, const target& t, prerequisite_iterator& i) const + { + assert (i->member == nullptr); + return filter (a, t, i->prerequisite); + } + + const target* file_rule:: + filter (action, const target& t, const prerequisite& p) const + { + const target& pt (search (t, p)); + return pt.in (t.root_scope ()) ? &pt : nullptr; + } + + recipe file_rule:: + apply (action a, target& t) const + { + tracer trace ("install::file_rule::apply"); + + // Note that we are called both as the outer part during the update-for- + // un/install pre-operation and as the inner part during the un/install + // operation itself. + // + // In both cases we first determine if the target is installable and + // return noop if it's not. Otherwise, in the first case (update-for- + // un/install) we delegate to the normal update and in the second + // (un/install) -- perform the test. + // + if (!lookup_install (t, "install")) + return noop_recipe; + + // In both cases, the next step is to search, match, and collect all the + // installable prerequisites. + // + // But first, in case of the update pre-operation, match the inner rule + // (actual update). We used to do this after matching the prerequisites + // but the inner rule may provide some rule-specific information (like + // the target extension for exe{}) that may be required during the + // prerequisite search (like the base name for in{}). + // + optional unchanged; + if (a.operation () == update_id) + unchanged = match_inner (a, t, unmatch::unchanged); + + auto& pts (t.prerequisite_targets[a]); + + auto pms (group_prerequisite_members (a, t, members_mode::never)); + for (auto i (pms.begin ()), e (pms.end ()); i != e; ++i) + { + const prerequisite& p (i->prerequisite); + + // Ignore excluded. + // + include_type pi (include (a, t, p)); + + if (!pi) + continue; + + // Ignore unresolved targets that are imported from other projects. + // We are definitely not installing those. + // + if (p.proj) + continue; + + // Let a customized rule have its say. + // + // Note: we assume that if the filter enters the group, then it + // iterates over all its members. + // + const target* pt (filter (a, t, i)); + if (pt == nullptr) + { + l5 ([&]{trace << "ignoring " << p << " (filtered out)";}); + continue; + } + + // See if we were explicitly instructed not to touch this target (the + // same semantics as in alias_rule). + // + // Note: not the same as lookup_install() above. + // + auto l ((*pt)["install"]); + if (l && cast (l).string () == "false") + { + l5 ([&]{trace << "ignoring " << *pt << " (not installable)";}); + continue; + } + + if (pt->is_a ()) + { + // If the matched rule returned noop_recipe, then the target state + // is set to unchanged as an optimization. Use this knowledge to + // optimize things on our side as well since this will help a lot + // when updating static installable content (headers, documentation, + // etc). + // + if (build2::match (a, *pt, unmatch::unchanged)) + pt = nullptr; + } + else if (!try_match (a, *pt).first) + { + l5 ([&]{trace << "ignoring " << *pt << " (no rule)";}); + pt = nullptr; + } + + if (pt != nullptr) + pts.push_back (prerequisite_target (pt, pi)); + } + + if (a.operation () == update_id) + { + return *unchanged + ? (pts.empty () ? noop_recipe : default_recipe) + : &perform_update; + } + else + { + return [this] (action a, const target& t) + { + return a.operation () == install_id + ? perform_install (a, t) + : perform_uninstall (a, t); + }; + } + } + + target_state file_rule:: + perform_update (action a, const target& t) + { + // First execute the inner recipe then prerequisites. + // + target_state ts (execute_inner (a, t)); + + if (t.prerequisite_targets[a].size () != 0) + ts |= straight_execute_prerequisites (a, t); + + return ts; + } + + bool file_rule:: + install_extra (const file&, const install_dir&) const + { + return false; + } + + bool file_rule:: + uninstall_extra (const file&, const install_dir&) const + { + return false; + } + + auto_rmfile file_rule:: + install_pre (const file& t, const install_dir&) const + { + return auto_rmfile (t.path (), false /* active */); + } + + bool file_rule:: + install_post (const file& t, const install_dir& id, auto_rmfile&&) const + { + return install_extra (t, id); + } + + struct install_dir + { + dir_path dir; + + // If not NULL, then point to the corresponding install.* value. + // + const string* sudo = nullptr; + const path* cmd = nullptr; + const strings* options = nullptr; + const string* mode = nullptr; + const string* dir_mode = nullptr; + + explicit + install_dir (dir_path d = dir_path ()): dir (move (d)) {} + + install_dir (dir_path d, const install_dir& b) + : dir (move (d)), + sudo (b.sudo), + cmd (b.cmd), + options (b.options), + mode (b.mode), + dir_mode (b.dir_mode) {} + }; + + using install_dirs = vector; + + // Calculate a subdirectory based on l's location (*.subdirs) and if not + // empty add it to install_dirs. Return the new last element. + // + static install_dir& + resolve_subdir (install_dirs& rs, + const target& t, + const scope& s, + const lookup& l) + { + // Find the scope from which this value came and use as a base + // to calculate the subdirectory. + // + for (const scope* p (&s); p != nullptr; p = p->parent_scope ()) + { + if (l.belongs (*p, true)) // Include target type/pattern-specific. + { + // The target can be in out or src. + // + const dir_path& d (t.out_dir ().leaf (p->out_path ())); + + // Add it as another leading directory rather than modifying + // the last one directly; somehow, it feels right. + // + if (!d.empty ()) + rs.emplace_back (rs.back ().dir / d, rs.back ()); + break; + } + } + + return rs.back (); + } + + // Resolve installation directory name to absolute directory path. Return + // all the super-directories leading up to the destination (last). + // + // If target is not NULL, then also handle the subdirs logic. + // + static install_dirs + resolve (const scope& s, + const target* t, + dir_path d, + bool fail_unknown = true, + const string* var = nullptr) + { + install_dirs rs; + + if (d.absolute ()) + rs.emplace_back (move (d.normalize ())); + else + { + // If it is relative, then the first component is treated as the + // installation directory name, e.g., bin, sbin, lib, etc. Look it + // up and recurse. + // + if (d.empty ()) + fail << "empty installation directory name"; + + const string& sn (*d.begin ()); + const string var ("install." + sn); + if (const dir_path* dn = lookup_install (s, var)) + { + if (dn->empty ()) + fail << "empty installation directory for name " << sn << + info << "did you specified empty config." << var << "?"; + + rs = resolve (s, t, *dn, fail_unknown, &var); + + if (rs.empty ()) + { + assert (!fail_unknown); + return rs; // Empty. + } + + d = rs.back ().dir / dir_path (++d.begin (), d.end ()); + rs.emplace_back (move (d.normalize ()), rs.back ()); + } + else + { + if (fail_unknown) + fail << "unknown installation directory name '" << sn << "'" << + info << "did you forget to specify config." << var << "?"; + + return rs; // Empty. + } + } + + install_dir* r (&rs.back ()); + + // Override components in install_dir if we have our own. + // + if (var != nullptr) + { + if (auto l = s[*var + ".sudo"]) r->sudo = &cast (l); + if (auto l = s[*var + ".cmd"]) r->cmd = &cast (l); + if (auto l = s[*var + ".mode"]) r->mode = &cast (l); + if (auto l = s[*var + ".dir_mode"]) r->dir_mode = &cast (l); + if (auto l = s[*var + ".options"]) r->options = &cast (l); + + if (t != nullptr) + { + if (auto l = s[*var + ".subdirs"]) + { + if (cast (l)) + r = &resolve_subdir (rs, *t, s, l); + } + } + } + + // Set globals for unspecified components. + // + if (r->sudo == nullptr) + r->sudo = cast_null (s["config.install.sudo"]); + + if (r->cmd == nullptr) + r->cmd = &cast (s["config.install.cmd"]); + + if (r->options == nullptr) + r->options = cast_null (s["config.install.options"]); + + if (r->mode == nullptr) + r->mode = &cast (s["config.install.mode"]); + + if (r->dir_mode == nullptr) + r->dir_mode = &cast (s["config.install.dir_mode"]); + + return rs; + } + + static inline install_dirs + resolve (const target& t, dir_path d, bool fail_unknown = true) + { + return resolve (t.base_scope (), &t, d, fail_unknown); + } + + dir_path + resolve_dir (const target& t, dir_path d, bool fail_unknown) + { + install_dirs r (resolve (t, move (d), fail_unknown)); + return r.empty () ? dir_path () : move (r.back ().dir); + } + + dir_path + resolve_dir (const scope& s, dir_path d, bool fail_unknown) + { + install_dirs r (resolve (s, nullptr, move (d), fail_unknown)); + return r.empty () ? dir_path () : move (r.back ().dir); + } + + path + resolve_file (const file& f) + { + // Note: similar logic to perform_install(). + // + const path* p (lookup_install (f, "install")); + + if (p == nullptr) // Not installable. + return path (); + + bool n (!p->to_directory ()); + dir_path d (n ? p->directory () : path_cast (*p)); + + install_dirs ids (resolve (f, d)); + + if (!n) + { + if (auto l = f["install.subdirs"]) + { + if (cast (l)) + resolve_subdir (ids, f, f.base_scope (), l); + } + } + + return ids.back ().dir / (n ? p->leaf () : f.path ().leaf ()); + } + + // On Windows we use MSYS2 install.exe and MSYS2 by default ignores + // filesystem permissions (noacl mount option). And this means, for + // example, that .exe that we install won't be runnable by Windows (MSYS2 + // itself will still run them since it recognizes the file extension). + // + // NOTE: this is no longer the case and we now use noacl (and acl causes + // other problems; see baseutils fstab for details). + // + // The way we work around this (at least in our distribution of the MSYS2 + // tools) is by changing the mount option for cygdrives (/c, /d, etc) to + // acl. But that's not all: we also have to install via a path that "hits" + // one of those mount points, c:\foo won't work, we have to use /c/foo. + // So this function translates an absolute Windows path to its MSYS + // representation. + // + // Note that we return the result as a string, not dir_path since path + // starting with / are illegal on Windows. Also note that the result + // doesn't have the trailing slash. + // + static string + msys_path (const dir_path& d) + { + assert (d.absolute ()); + string s (d.representation ()); + + // First replace ':' with the drive letter (so the path is no longer + // absolute) but postpone setting the first character to / until we are + // a string. + // + s[1] = lcase (s[0]); + s = dir_path (move (s)).posix_string (); + s[0] = '/'; + + return s; + } + + // Given an abolute path return its chroot'ed version, if any, accoring to + // install.chroot. + // + template + static inline P + chroot_path (const scope& rs, const P& p) + { + if (const dir_path* d = cast_null (rs["install.chroot"])) + { + dir_path r (p.root_directory ()); + assert (!r.empty ()); // Must be absolute. + + return *d / p.leaf (r); + } + + return p; + } + + // install -d

+ // + static void + install_d (const scope& rs, + const install_dir& base, + const dir_path& d, + bool verbose = true) + { + // Here is the problem: if this is a dry-run, then we will keep showing + // the same directory creation commands over and over again (because we + // don't actually create them). There are two alternative ways to solve + // this: actually create the directories or simply don't show anything. + // While we use the former approach during update (see mkdir() in + // filesystem), here it feels like we really shouldn't be touching the + // destination filesystem. Plus, not showing anything will be symmetric + // with uninstall since the directories won't be empty (because we don't + // actually uninstall any files). + // + if (dry_run) + return; + + dir_path chd (chroot_path (rs, d)); + + try + { + if (dir_exists (chd)) // May throw (e.g., EACCES). + return; + } + catch (const system_error& e) + { + fail << "invalid installation directory " << chd << ": " << e; + } + + // While install -d will create all the intermediate components between + // base and dir, we do it explicitly, one at a time. This way the output + // is symmetrical to uninstall() below. + // + // Note that if the chroot directory does not exist, then install -d + // will create it and we don't bother removing it. + // + if (d != base.dir) + { + dir_path pd (d.directory ()); + + if (pd != base.dir) + install_d (rs, base, pd, verbose); + } + + cstrings args; + + string reld ( + cast ((*global_scope)["build.host.class"]) == "windows" + ? msys_path (chd) + : relative (chd).string ()); + + if (base.sudo != nullptr) + args.push_back (base.sudo->c_str ()); + + args.push_back (base.cmd->string ().c_str ()); + args.push_back ("-d"); + + if (base.options != nullptr) + append_options (args, *base.options); + + args.push_back ("-m"); + args.push_back (base.dir_mode->c_str ()); + args.push_back (reld.c_str ()); + args.push_back (nullptr); + + process_path pp (run_search (args[0])); + + if (verb >= 2) + print_process (args); + else if (verb && verbose) + text << "install " << chd; + + run (pp, args); + } + + // install / + // install + // + static void + install_f (const scope& rs, + const install_dir& base, + const path& name, + const file& t, + const path& f, + bool verbose) + { + path relf (relative (f)); + + dir_path chd (chroot_path (rs, base.dir)); + + string reld ( + cast ((*global_scope)["build.host.class"]) == "windows" + ? msys_path (chd) + : relative (chd).string ()); + + if (!name.empty ()) + { + reld += path::traits_type::directory_separator; + reld += name.string (); + } + + cstrings args; + + if (base.sudo != nullptr) + args.push_back (base.sudo->c_str ()); + + args.push_back (base.cmd->string ().c_str ()); + + if (base.options != nullptr) + append_options (args, *base.options); + + args.push_back ("-m"); + args.push_back (base.mode->c_str ()); + args.push_back (relf.string ().c_str ()); + args.push_back (reld.c_str ()); + args.push_back (nullptr); + + process_path pp (run_search (args[0])); + + if (verb >= 2) + print_process (args); + else if (verb && verbose) + text << "install " << t; + + if (!dry_run) + run (pp, args); + } + + void file_rule:: + install_l (const scope& rs, + const install_dir& base, + const path& target, + const path& link, + bool verbose) + { + path rell (relative (chroot_path (rs, base.dir))); + rell /= link; + + // We can create a symlink directly without calling ln. This, however, + // won't work if we have sudo. Also, we would have to deal with existing + // destinations (ln's -f takes care of that). So we are just going to + // always use ln. + // + const char* args_a[] = { + base.sudo != nullptr ? base.sudo->c_str () : nullptr, + "ln", + "-sf", + target.string ().c_str (), + rell.string ().c_str (), + nullptr}; + + const char** args (&args_a[base.sudo == nullptr ? 1 : 0]); + + process_path pp (run_search (args[0])); + + if (verb >= 2) + print_process (args); + else if (verb && verbose) + text << "install " << rell << " -> " << target; + + if (!dry_run) + run (pp, args); + } + + target_state file_rule:: + perform_install (action a, const target& xt) const + { + const file& t (xt.as ()); + const path& tp (t.path ()); + + // Path should have been assigned by update unless it is unreal. + // + assert (!tp.empty () || t.mtime () == timestamp_unreal); + + const scope& rs (t.root_scope ()); + + auto install_target = [&rs, this] (const file& t, + const path& p, + bool verbose) + { + // Note: similar logic to resolve_file(). + // + bool n (!p.to_directory ()); + dir_path d (n ? p.directory () : path_cast (p)); + + // Resolve target directory. + // + install_dirs ids (resolve (t, d)); + + // Handle install.subdirs if one was specified. Unless the target path + // includes the file name in which case we assume it's a "final" path. + // + if (!n) + { + if (auto l = t["install.subdirs"]) + { + if (cast (l)) + resolve_subdir (ids, t, t.base_scope (), l); + } + } + + // Create leading directories. Note that we are using the leading + // directory (if there is one) for the creation information (mode, + // sudo, etc). + // + for (auto i (ids.begin ()), j (i); i != ids.end (); j = i++) + install_d (rs, *j, i->dir, verbose); // install -d + + install_dir& id (ids.back ()); + + // Override mode if one was specified. + // + if (auto l = t["install.mode"]) + id.mode = &cast (l); + + // Install the target. + // + auto_rmfile f (install_pre (t, id)); + + // If install_pre() returned a different file name, make sure we + // install it as the original. + // + const path& tp (t.path ()); + const path& fp (f.path); + + install_f ( + rs, + id, + n ? p.leaf () : fp.leaf () != tp.leaf () ? tp.leaf () : path (), + t, + f.path, + verbose); + + install_post (t, id, move (f)); + }; + + // First handle installable prerequisites. + // + target_state r (straight_execute_prerequisites (a, t)); + + // Then installable ad hoc group members, if any. + // + for (const target* m (t.member); m != nullptr; m = m->member) + { + if (const path* p = lookup_install (*m, "install")) + { + install_target (m->as (), *p, tp.empty () /* verbose */); + r |= target_state::changed; + } + } + + // Finally install the target itself (since we got here we know the + // install variable is there). + // + if (!tp.empty ()) + { + install_target (t, cast (t["install"]), true /* verbose */); + r |= target_state::changed; + } + + return r; + } + + // uninstall -d + // + // We try to remove all the directories between base and dir but not base + // itself unless base == dir. Return false if nothing has been removed + // (i.e., the directories do not exist or are not empty). + // + static bool + uninstall_d (const scope& rs, + const install_dir& base, + const dir_path& d, + bool verbose) + { + // See install_d() for the rationale. + // + if (dry_run) + return false; + + dir_path chd (chroot_path (rs, d)); + + // Figure out if we should try to remove this directory. Note that if + // it doesn't exist, then we may still need to remove outer ones. + // + bool r (false); + try + { + if ((r = dir_exists (chd))) // May throw (e.g., EACCES). + { + if (!dir_empty (chd)) // May also throw. + return false; // Won't be able to remove any outer directories. + } + } + catch (const system_error& e) + { + fail << "invalid installation directory " << chd << ": " << e; + } + + if (r) + { + dir_path reld (relative (chd)); + + // Normally when we need to remove a file or directory we do it + // directly without calling rm/rmdir. This however, won't work if we + // have sudo. So we are going to do it both ways. + // + // While there is no sudo on Windows, deleting things that are being + // used can get complicated. So we will always use rm/rmdir there. + // +#ifndef _WIN32 + if (base.sudo == nullptr) + { + if (verb >= 2) + text << "rmdir " << reld; + else if (verb && verbose) + text << "uninstall " << reld; + + try + { + try_rmdir (chd); + } + catch (const system_error& e) + { + fail << "unable to remove directory " << chd << ": " << e; + } + } + else +#endif + { + const char* args_a[] = { + base.sudo != nullptr ? base.sudo->c_str () : nullptr, + "rmdir", + reld.string ().c_str (), + nullptr}; + + const char** args (&args_a[base.sudo == nullptr ? 1 : 0]); + + process_path pp (run_search (args[0])); + + if (verb >= 2) + print_process (args); + else if (verb && verbose) + text << "uninstall " << reld; + + run (pp, args); + } + } + + // If we have more empty directories between base and dir, then try + // to clean them up as well. + // + if (d != base.dir) + { + dir_path pd (d.directory ()); + + if (pd != base.dir) + r = uninstall_d (rs, base, pd, verbose) || r; + } + + return r; + } + + bool file_rule:: + uninstall_f (const scope& rs, + const install_dir& base, + const file* t, + const path& name, + bool verbose) + { + assert (t != nullptr || !name.empty ()); + path f (chroot_path (rs, base.dir) / + (name.empty () ? t->path ().leaf () : name)); + + try + { + // Note: don't follow symlinks so if the target is a dangling symlinks + // we will proceed to removing it. + // + if (!file_exists (f, false)) // May throw (e.g., EACCES). + return false; + } + catch (const system_error& e) + { + fail << "invalid installation path " << f << ": " << e; + } + + path relf (relative (f)); + + if (verb == 1 && verbose) + { + if (t != nullptr) + text << "uninstall " << *t; + else + text << "uninstall " << relf; + } + + // The same story as with uninstall -d. + // +#ifndef _WIN32 + if (base.sudo == nullptr) + { + if (verb >= 2) + text << "rm " << relf; + + if (!dry_run) + { + try + { + try_rmfile (f); + } + catch (const system_error& e) + { + fail << "unable to remove file " << f << ": " << e; + } + } + } + else +#endif + { + const char* args_a[] = { + base.sudo != nullptr ? base.sudo->c_str () : nullptr, + "rm", + "-f", + relf.string ().c_str (), + nullptr}; + + const char** args (&args_a[base.sudo == nullptr ? 1 : 0]); + + process_path pp (run_search (args[0])); + + if (verb >= 2) + print_process (args); + + if (!dry_run) + run (pp, args); + } + + return true; + } + + target_state file_rule:: + perform_uninstall (action a, const target& xt) const + { + const file& t (xt.as ()); + const path& tp (t.path ()); + + // Path should have been assigned by update unless it is unreal. + // + assert (!tp.empty () || t.mtime () == timestamp_unreal); + + const scope& rs (t.root_scope ()); + + auto uninstall_target = [&rs, this] (const file& t, + const path& p, + bool verbose) -> target_state + { + bool n (!p.to_directory ()); + dir_path d (n ? p.directory () : path_cast (p)); + + // Resolve target directory. + // + install_dirs ids (resolve (t, d)); + + // Handle install.subdirs if one was specified. + // + if (!n) + { + if (auto l = t["install.subdirs"]) + { + if (cast (l)) + resolve_subdir (ids, t, t.base_scope (), l); + } + } + + // Remove extras and the target itself. + // + const install_dir& id (ids.back ()); + + target_state r (uninstall_extra (t, id) + ? target_state::changed + : target_state::unchanged); + + if (uninstall_f (rs, id, &t, n ? p.leaf () : path (), verbose)) + r |= target_state::changed; + + // Clean up empty leading directories (in reverse). + // + // Note that we are using the leading directory (if there is one) + // for the clean up information (sudo, etc). + // + for (auto i (ids.rbegin ()), j (i), e (ids.rend ()); i != e; j = ++i) + { + if (install::uninstall_d (rs, ++j != e ? *j : *i, i->dir, verbose)) + r |= target_state::changed; + } + + return r; + }; + + // Reverse order of installation: first the target itself (since we got + // here we know the install variable is there). + // + target_state r (target_state::unchanged); + + if (!tp.empty ()) + r |= uninstall_target (t, cast (t["install"]), true); + + // Then installable ad hoc group members, if any. To be anally precise + // we would have to do it in reverse, but that's not easy (it's a + // single-linked list). + // + for (const target* m (t.member); m != nullptr; m = m->member) + { + if (const path* p = lookup_install (*m, "install")) + r |= uninstall_target (m->as (), + *p, + tp.empty () || r != target_state::changed); + } + + // Finally handle installable prerequisites. + // + r |= reverse_execute_prerequisites (a, t); + + return r; + } + } +} diff --git a/libbuild2/install/rule.hxx b/libbuild2/install/rule.hxx new file mode 100644 index 0000000..ff99c6e --- /dev/null +++ b/libbuild2/install/rule.hxx @@ -0,0 +1,197 @@ +// file : libbuild2/install/rule.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_INSTALL_RULE_HXX +#define LIBBUILD2_INSTALL_RULE_HXX + +#include +#include + +#include +#include +#include +#include + +#include + +namespace build2 +{ + namespace install + { + class LIBBUILD2_SYMEXPORT alias_rule: public rule + { + public: + virtual bool + match (action, target&, const string&) const override; + + // Return NULL if this prerequisite should be ignored and pointer to its + // target otherwise. The default implementation accepts all prerequsites + // from the target's (weak) amalgamation. + // + // The prerequisite is passed as an iterator allowing the filter to + // "see" inside groups. + // + using prerequisite_iterator = + prerequisite_members_range::iterator; + + virtual const target* + filter (action, const target&, prerequisite_iterator&) const; + + virtual const target* + filter (action, const target&, const prerequisite&) const; + + virtual recipe + apply (action, target&) const override; + + alias_rule () {} + static const alias_rule instance; + }; + + class fsdir_rule: public rule + { + public: + virtual bool + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + + fsdir_rule () {} + static const fsdir_rule instance; + }; + + // In addition to the alias rule's semantics, this rule sees through to + // the group's members. + // + // The default group_rule::instance matches any target for which it was + // registered. It is to be used for non-see-through groups that should + // exhibit the see-through behavior for install (see lib{} in the bin + // module for an example). + // + // We also register (for all targets) another instance of this rule that + // only matches see-through groups. + // + class LIBBUILD2_SYMEXPORT group_rule: public alias_rule + { + public: + virtual bool + match (action, target&, const string&) const override; + + // Return NULL if this group member should be ignored and pointer to its + // target otherwise. The default implementation accepts all members. + // + virtual const target* + filter (action, const target&, const target& group_member) const; + + using alias_rule::filter; // "Unhide" to make Clang happy. + + virtual recipe + apply (action, target&) const override; + + group_rule (bool see_through_only): see_through (see_through_only) {} + static const group_rule instance; + + bool see_through; + }; + + struct install_dir; + + class LIBBUILD2_SYMEXPORT file_rule: public rule + { + public: + virtual bool + match (action, target&, const string&) const override; + + // Return NULL if this prerequisite should be ignored and pointer to its + // target otherwise. The default implementation ignores prerequsites + // that are outside of this target's project. + // + // @@ I wonder why we do weak amalgamation for alias but project for + // file? And then override this for prerequisite libraries/modules + // in cc::install_rule and bash::install_rule... + // + // The prerequisite is passed as an iterator allowing the filter to + // "see" inside groups. + // + using prerequisite_iterator = + prerequisite_members_range::iterator; + + virtual const target* + filter (action, const target&, prerequisite_iterator&) const; + + virtual const target* + filter (action, const target&, const prerequisite&) const; + + virtual recipe + apply (action, target&) const override; + + static target_state + perform_update (action, const target&); + + // Extra un/installation hooks. Return true if anything was actually + // un/installed. + // + using install_dir = install::install_dir; // For derived rules. + + virtual bool + install_extra (const file&, const install_dir&) const; + + virtual bool + uninstall_extra (const file&, const install_dir&) const; + + // Lower-level pre/post installation hooks that can be used to override + // the source file path being installed (for example, to implement + // post-processing, etc). + // + // Note that one cannot generally perform post-processing in-place + // because of permissions. + // + virtual auto_rmfile + install_pre (const file&, const install_dir&) const; + + virtual bool + install_post (const file&, const install_dir&, auto_rmfile&&) const; + + // Installation/uninstallation "commands". + // + // If verbose is false, then only print the command at verbosity level 2 + // or higher. Note that these functions respect the dry_run flag. + + // Install a symlink: base/link -> target. + // + static void + install_l (const scope& rs, + const install_dir& base, + const path& target, + const path& link, + bool verbose); + + // Uninstall a file or symlink: + // + // uninstall / rm /.leaf (); name empty + // uninstall rm /; target can be NULL + // + // Return false if nothing has been removed (i.e., the file does not + // exist). + // + static bool + uninstall_f (const scope& rs, + const install_dir& base, + const file* target, + const path& name, + bool verbose); + + target_state + perform_install (action, const target&) const; + + target_state + perform_uninstall (action, const target&) const; + + static const file_rule instance; + file_rule () {} + }; + } +} + +#endif // LIBBUILD2_INSTALL_RULE_HXX diff --git a/libbuild2/install/utility.hxx b/libbuild2/install/utility.hxx new file mode 100644 index 0000000..13fcceb --- /dev/null +++ b/libbuild2/install/utility.hxx @@ -0,0 +1,78 @@ +// file : libbuild2/install/utility.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_INSTALL_UTILITY_HXX +#define LIBBUILD2_INSTALL_UTILITY_HXX + +#include +#include + +#include +#include + +#include + +namespace build2 +{ + namespace install + { + // Set install path, mode for a target type. + // + inline void + install_path (scope& s, const target_type& tt, dir_path d) + { + auto r ( + s.target_vars[tt]["*"].insert ( + var_pool.rw (s).insert ("install"))); + + if (r.second) // Already set by the user? + r.first.get () = path_cast (move (d)); + } + + template + inline void + install_path (scope& s, dir_path d) + { + return install_path (s, T::static_type, move (d)); + } + + inline void + install_mode (scope& s, const target_type& tt, string m) + { + auto r ( + s.target_vars[tt]["*"].insert ( + var_pool.rw (s).insert ("install.mode"))); + + if (r.second) // Already set by the user? + r.first.get () = move (m); + } + + template + inline void + install_mode (scope& s, string m) + { + return install_mode (s, T::static_type, move (m)); + } + + // Resolve relative installation directory path (e.g., include/libfoo) to + // its absolute directory path (e.g., /usr/include/libfoo). If the + // resolution encountered an unknown directory, issue diagnostics and fail + // unless fail_unknown is false, in which case return empty directory. + // + // Note: implemented in rule.cxx. + // + LIBBUILD2_SYMEXPORT dir_path + resolve_dir (const target&, dir_path, bool fail_unknown = true); + + LIBBUILD2_SYMEXPORT dir_path + resolve_dir (const scope&, dir_path, bool fail_unknown = true); + + // Resolve file installation path returning empty path if not installable. + // + LIBBUILD2_SYMEXPORT path + resolve_file (const file&); // rule.cxx + } +} + +#endif // LIBBUILD2_INSTALL_UTILITY_HXX -- cgit v1.1