aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/install
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2019-07-04 19:12:15 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2019-07-05 14:24:43 +0300
commit57b10c06925d0bdf6ffb38488ee908f085109e95 (patch)
treef2103684d319650c3302aef9d7a70dd64ff2a347 /libbuild2/install
parent30b4eda196e090aa820d312e6a9435a4ae84c303 (diff)
Move config, dist, test, and install modules into library
Diffstat (limited to 'libbuild2/install')
-rw-r--r--libbuild2/install/functions.cxx33
-rw-r--r--libbuild2/install/init.cxx309
-rw-r--r--libbuild2/install/init.hxx36
-rw-r--r--libbuild2/install/operation.cxx84
-rw-r--r--libbuild2/install/operation.hxx23
-rw-r--r--libbuild2/install/rule.cxx1223
-rw-r--r--libbuild2/install/rule.hxx197
-rw-r--r--libbuild2/install/utility.hxx78
8 files changed, 1983 insertions, 0 deletions
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 <libbuild2/function.hxx>
+#include <libbuild2/variable.hxx>
+
+#include <libbuild2/install/utility.hxx>
+
+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 <libbuild2/install/init.hxx>
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/rule.hxx>
+#include <libbuild2/function.hxx>
+#include <libbuild2/operation.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/config/utility.hxx>
+
+#include <libbuild2/install/rule.hxx>
+#include <libbuild2/install/utility.hxx>
+#include <libbuild2/install/operation.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace install
+ {
+ // Set install.<name>.* values based on config.install.<name>.* 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 <typename T, typename CT>
+ 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<CT> (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<T> (move (vn)));
+
+ value& v (r.assign (vr));
+
+ if (spec)
+ {
+ if (l)
+ v = cast<T> (l); // Strip CT to T.
+ }
+ else
+ {
+ if (dv != nullptr)
+ v = *dv;
+ }
+ }
+
+ template <typename T>
+ 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<dir_path> (s, r, n, "", p.empty () ? nullptr : &p, o);
+
+ set_var<path> (s, r, n, ".cmd", c.empty () ? nullptr : &c);
+ set_var<strings> (s, r, n, ".options", (strings*) (nullptr));
+ set_var<string> (s, r, n, ".mode", fm.empty () ? nullptr : &fm);
+ set_var<string> (s, r, n, ".dir_mode", dm.empty () ? nullptr : &dm);
+ set_var<string> (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<bool> (string ("install.") + n + ".subdirs");
+ }
+
+ void
+ functions (); // functions.cxx
+
+ bool
+ boot (scope& rs, const location&, unique_ptr<module_base>&)
+ {
+ 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<module_base>&,
+ 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<path> ("install", variable_visibility::target);
+ vp.insert<string> ("install.mode", variable_visibility::project);
+ vp.insert<bool> ("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<alias> (perform_install_id, "install.alias", ar);
+ r.insert<alias> (perform_uninstall_id, "uninstall.alias", ar);
+
+ r.insert<fsdir> (perform_install_id, "install.fsdir", dr);
+ r.insert<fsdir> (perform_uninstall_id, "install.fsdir", dr);
+
+ r.insert<file> (perform_install_id, "install.file", fr);
+ r.insert<file> (perform_uninstall_id, "uninstall.file", fr);
+
+ r.insert<target> (perform_install_id, "install.file", gr);
+ r.insert<target> (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<dir_path> ( "install.chroot"));
+ auto& cvar (vp.insert<abs_dir_path> ("config.install.chroot", true));
+
+ value& v (rs.assign (var));
+
+ if (s)
+ {
+ if (lookup l = config::optional (rs, cvar))
+ v = cast<dir_path> (l); // Strip abs_dir_path.
+ }
+ }
+ }
+
+ // Configure "installability" for built-in target types.
+ //
+ install_path<exe> (bs, dir_path ("bin")); // Install into install.bin.
+ install_path<doc> (bs, dir_path ("doc")); // Install into install.doc.
+ install_path<man> (bs, dir_path ("man")); // Install into install.man.
+ install_path<man1> (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 <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/module.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ namespace install
+ {
+ bool
+ boot (scope&, const location&, unique_ptr<module_base>&);
+
+ bool
+ init (scope&,
+ scope&,
+ const location&,
+ unique_ptr<module_base>&,
+ 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 <libbuild2/install/operation.hxx>
+
+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 <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/operation.hxx>
+
+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 <libbuild2/install/rule.hxx>
+#include <libbuild2/install/utility.hxx> // resolve_dir() declaration
+
+#include <libbutl/filesystem.mxx> // dir_exists(), file_exists()
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/algorithm.hxx>
+#include <libbuild2/filesystem.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+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 <typename P, typename T>
+ static const P*
+ lookup_install (T& t, const string& var)
+ {
+ auto l (t[var]);
+
+ if (!l)
+ return nullptr;
+
+ const P& r (cast<P> (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<path> (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<file> ())
+ 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<path> (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<path> (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<bool> 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<path> (l).string () == "false")
+ {
+ l5 ([&]{trace << "ignoring " << *pt << " (not installable)";});
+ continue;
+ }
+
+ if (pt->is_a<file> ())
+ {
+ // 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<install_dir>;
+
+ // 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<dir_path> (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<string> (l);
+ if (auto l = s[*var + ".cmd"]) r->cmd = &cast<path> (l);
+ if (auto l = s[*var + ".mode"]) r->mode = &cast<string> (l);
+ if (auto l = s[*var + ".dir_mode"]) r->dir_mode = &cast<string> (l);
+ if (auto l = s[*var + ".options"]) r->options = &cast<strings> (l);
+
+ if (t != nullptr)
+ {
+ if (auto l = s[*var + ".subdirs"])
+ {
+ if (cast<bool> (l))
+ r = &resolve_subdir (rs, *t, s, l);
+ }
+ }
+ }
+
+ // Set globals for unspecified components.
+ //
+ if (r->sudo == nullptr)
+ r->sudo = cast_null<string> (s["config.install.sudo"]);
+
+ if (r->cmd == nullptr)
+ r->cmd = &cast<path> (s["config.install.cmd"]);
+
+ if (r->options == nullptr)
+ r->options = cast_null<strings> (s["config.install.options"]);
+
+ if (r->mode == nullptr)
+ r->mode = &cast<string> (s["config.install.mode"]);
+
+ if (r->dir_mode == nullptr)
+ r->dir_mode = &cast<string> (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<path> (f, "install"));
+
+ if (p == nullptr) // Not installable.
+ return path ();
+
+ bool n (!p->to_directory ());
+ dir_path d (n ? p->directory () : path_cast<dir_path> (*p));
+
+ install_dirs ids (resolve (f, d));
+
+ if (!n)
+ {
+ if (auto l = f["install.subdirs"])
+ {
+ if (cast<bool> (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 <typename P>
+ static inline P
+ chroot_path (const scope& rs, const P& p)
+ {
+ if (const dir_path* d = cast_null<dir_path> (rs["install.chroot"]))
+ {
+ dir_path r (p.root_directory ());
+ assert (!r.empty ()); // Must be absolute.
+
+ return *d / p.leaf (r);
+ }
+
+ return p;
+ }
+
+ // install -d <dir>
+ //
+ 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<string> ((*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 <file> <dir>/
+ // install <file> <file>
+ //
+ 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<string> ((*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<file> ());
+ 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<dir_path> (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<bool> (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<string> (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<path> (*m, "install"))
+ {
+ install_target (m->as<file> (), *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<path> (t["install"]), true /* verbose */);
+ r |= target_state::changed;
+ }
+
+ return r;
+ }
+
+ // uninstall -d <dir>
+ //
+ // 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<file> ());
+ 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<dir_path> (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<bool> (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<path> (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<path> (*m, "install"))
+ r |= uninstall_target (m->as<file> (),
+ *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 <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/rule.hxx>
+#include <libbuild2/action.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/filesystem.hxx>
+
+#include <libbuild2/export.hxx>
+
+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<group_prerequisites>::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<group_prerequisites>::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 <target> <base>/ rm <base>/<target>.leaf (); name empty
+ // uninstall <target> <name> rm <base>/<name>; 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 <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
+
+#include <libbuild2/export.hxx>
+
+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<path> (move (d));
+ }
+
+ template <typename T>
+ 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 <typename T>
+ 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