aboutsummaryrefslogtreecommitdiff
path: root/build2/install
diff options
context:
space:
mode:
Diffstat (limited to 'build2/install')
-rw-r--r--build2/install/module26
-rw-r--r--build2/install/module.cxx188
-rw-r--r--build2/install/operation18
-rw-r--r--build2/install/operation.cxx32
-rw-r--r--build2/install/rule49
-rw-r--r--build2/install/rule.cxx410
-rw-r--r--build2/install/utility40
7 files changed, 763 insertions, 0 deletions
diff --git a/build2/install/module b/build2/install/module
new file mode 100644
index 0000000..91b5ecb
--- /dev/null
+++ b/build2/install/module
@@ -0,0 +1,26 @@
+// file : build2/install/module -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_INSTALL_MODULE
+#define BUILD2_INSTALL_MODULE
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/module>
+
+namespace build2
+{
+ namespace install
+ {
+ extern "C" void
+ install_boot (scope&, const location&, unique_ptr<module>&);
+
+ extern "C" bool
+ install_init (
+ scope&, scope&, const location&, unique_ptr<module>&, bool, bool);
+ }
+}
+
+#endif // BUILD2_INSTALL_MODULE
diff --git a/build2/install/module.cxx b/build2/install/module.cxx
new file mode 100644
index 0000000..c6ad717
--- /dev/null
+++ b/build2/install/module.cxx
@@ -0,0 +1,188 @@
+// file : build2/install/module.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/install/module>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/rule>
+#include <build2/operation>
+#include <build2/diagnostics>
+
+#include <build2/config/utility>
+
+#include <build2/install/rule>
+#include <build2/install/utility>
+#include <build2/install/operation>
+
+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.
+ //
+ template <typename T>
+ static void
+ set_var (bool spec,
+ scope& r,
+ const char* name,
+ const char* var,
+ const T* dv,
+ bool override = false)
+ {
+ string vn;
+ const value* cv (nullptr);
+
+ if (spec)
+ {
+ vn = "config.install.";
+ vn += name;
+ vn += var;
+ const variable& vr (
+ var_pool.find (move (vn), &value_traits<T>::value_type));
+
+ cv = dv != nullptr
+ ? &config::required (r, vr, *dv, override).first.get ()
+ : &config::optional (r, vr);
+ }
+
+ vn = "install.";
+ vn += name;
+ vn += var;
+ const variable& vr (
+ var_pool.find (move (vn), &value_traits<T>::value_type));
+
+ value& v (r.assign (vr));
+
+ if (spec)
+ {
+ if (*cv && !cv->empty ())
+ v = *cv;
+ }
+ else
+ {
+ if (dv != nullptr)
+ v = *dv;
+ }
+ }
+
+ static void
+ set_dir (bool s, // specified
+ scope& r, // root scope
+ const char* n, // var name
+ const string& ps, // path (as string)
+ const string& fm = string (), // file mode
+ const string& dm = string (), // dir mode
+ const string& c = string (), // command
+ bool o = false) // override
+ {
+ dir_path p (ps);
+ set_var (s, r, n, "", p.empty () ? nullptr : &p, o);
+ set_var (s, r, n, ".mode", fm.empty () ? nullptr : &fm);
+ set_var (s, r, n, ".dir_mode", dm.empty () ? nullptr : &dm);
+ set_var<string> (s, r, n, ".sudo", nullptr);
+ set_var (s, r, n, ".cmd", c.empty () ? nullptr : &c);
+ set_var<strings> (s, r, n, ".options", nullptr);
+ }
+
+ static alias_rule alias_;
+ static file_rule file_;
+
+ extern "C" void
+ install_boot (scope& r, const location&, unique_ptr<module>&)
+ {
+ tracer trace ("install::boot");
+
+ level5 ([&]{trace << "for " << r.out_path ();});
+
+ // Register the install operation.
+ //
+ r.operations.insert (install_id, install);
+ }
+
+ extern "C" bool
+ install_init (scope& r,
+ scope& b,
+ const location& l,
+ unique_ptr<module>&,
+ bool first,
+ bool)
+ {
+ tracer trace ("install::init");
+
+ if (!first)
+ {
+ warn (l) << "multiple install module initializations";
+ return true;
+ }
+
+ const dir_path& out_root (r.out_path ());
+ level5 ([&]{trace << "for " << out_root;});
+
+ // Enter module variables.
+ //
+ // Note that the set_dir() calls below enter some more.
+ //
+ if (first)
+ {
+ auto& v (var_pool);
+
+ v.find ("install", dir_path_type);
+ }
+
+ // Register our alias and file installer rule.
+ //
+ b.rules.insert<alias> (perform_install_id, "install.alias", alias_);
+ b.rules.insert<file> (perform_install_id, "install.file", file_);
+
+ // 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.
+ //
+ if (first)
+ {
+ bool s (config::specified (r, "config.install"));
+ const string& n (as<string> (*r["project"]));
+
+ set_dir (s, r, "root", "", "", "755", "install");
+ set_dir (s, r, "data_root", "root", "644");
+ set_dir (s, r, "exec_root", "root", "755");
+
+ set_dir (s, r, "sbin", "exec_root/sbin");
+ set_dir (s, r, "bin", "exec_root/bin");
+ set_dir (s, r, "lib", "exec_root/lib");
+ set_dir (s, r, "libexec", "exec_root/libexec/" + n, "", "", "", true);
+
+ set_dir (s, r, "data", "data_root/share/" + n, "", "", "", true);
+ set_dir (s, r, "include", "data_root/include");
+
+ set_dir (s, r, "doc", "data_root/share/doc/" + n, "", "", "", true);
+ set_dir (s, r, "man", "data_root/share/man");
+
+ set_dir (s, r, "man1", "man/man1");
+ }
+
+ // Configure "installability" for built-in target types.
+ //
+ path<doc> (b, dir_path ("doc")); // Install into install.doc.
+ path<man> (b, dir_path ("man")); // Install into install.man.
+ path<man1> (b, dir_path ("man1")); // Install into install.man1.
+
+ return true;
+ }
+ }
+}
diff --git a/build2/install/operation b/build2/install/operation
new file mode 100644
index 0000000..20630d5
--- /dev/null
+++ b/build2/install/operation
@@ -0,0 +1,18 @@
+// file : build2/install/operation -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_INSTALL_OPERATION
+#define BUILD2_INSTALL_OPERATION
+
+#include <build2/operation>
+
+namespace build2
+{
+ namespace install
+ {
+ extern operation_info install;
+ }
+}
+
+#endif // BUILD2_INSTALL_OPERATION
diff --git a/build2/install/operation.cxx b/build2/install/operation.cxx
new file mode 100644
index 0000000..eb7cdb1
--- /dev/null
+++ b/build2/install/operation.cxx
@@ -0,0 +1,32 @@
+// file : build2/install/operation.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/install/operation>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace install
+ {
+ static operation_id
+ install_pre (meta_operation_id mo)
+ {
+ // Run update as a pre-operation, unless we are disfiguring.
+ //
+ return mo != disfigure_id ? update_id : 0;
+ }
+
+ operation_info install {
+ "install",
+ "install",
+ "installing",
+ "has nothing to install", // We cannot "be installed".
+ execution_mode::first,
+ &install_pre,
+ nullptr
+ };
+ }
+}
diff --git a/build2/install/rule b/build2/install/rule
new file mode 100644
index 0000000..aeb7f13
--- /dev/null
+++ b/build2/install/rule
@@ -0,0 +1,49 @@
+// file : build2/install/rule -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_INSTALL_RULE
+#define BUILD2_INSTALL_RULE
+
+#include <build2/rule>
+#include <build2/types>
+#include <build2/target>
+#include <build2/operation>
+
+namespace build2
+{
+ namespace install
+ {
+ class alias_rule: public rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string&) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+ };
+
+ class file_rule: public rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string&) const;
+
+ // 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.
+ //
+ virtual target*
+ filter (action, target&, prerequisite_member) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+
+ static target_state
+ perform_install (action, target&);
+ };
+ }
+}
+
+#endif // BUILD2_INSTALL_RULE
diff --git a/build2/install/rule.cxx b/build2/install/rule.cxx
new file mode 100644
index 0000000..16feca8
--- /dev/null
+++ b/build2/install/rule.cxx
@@ -0,0 +1,410 @@
+// file : build2/install/rule.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/install/rule>
+
+#include <butl/process>
+#include <butl/filesystem>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+
+#include <build2/config/utility>
+
+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). T is either scope or target.
+ //
+ template <typename T>
+ static const dir_path*
+ lookup (T& t, const string& var)
+ {
+ auto l (t[var]);
+
+ if (!l)
+ return nullptr;
+
+ const dir_path& r (as<dir_path> (*l));
+ return r.simple () && r.string () == "false" ? nullptr : &r;
+ }
+
+ // alias_rule
+ //
+ match_result alias_rule::
+ match (action, target& t, const std::string&) const
+ {
+ return t;
+ }
+
+ recipe alias_rule::
+ apply (action a, target& t, const match_result&) const
+ {
+ tracer trace ("install::alias_rule::apply");
+
+ for (prerequisite p: group_prerequisites (t))
+ {
+ target& pt (search (p));
+
+ // 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.
+ //
+ auto l (pt["install"]);
+
+ if (l && as<dir_path> (*l).string () == "false")
+ {
+ level5 ([&]{trace << "ignoring " << pt;});
+ continue;
+ }
+
+ build2::match (a, pt);
+ t.prerequisite_targets.push_back (&pt);
+ }
+
+ return default_recipe;
+ }
+
+ // file_rule
+ //
+
+ match_result file_rule::
+ match (action a, target& t, const std::string&) const
+ {
+ // First determine if this target should be installed (called
+ // "installable" for short).
+ //
+ if (lookup (t, "install") == nullptr)
+ // If this is the update pre-operation, signal that we don't match so
+ // that some other rule can take care of it.
+ //
+ return a.operation () == update_id ? nullptr : match_result (t, false);
+
+ match_result mr (t, true);
+
+ // If this is the update pre-operation, change the recipe action
+ // to (update, 0) (i.e., "unconditional update").
+ //
+ if (a.operation () == update_id)
+ mr.recipe_action = action (a.meta_operation (), update_id);
+
+ return mr;
+ }
+
+ target* file_rule::
+ filter (action, target& t, prerequisite_member p) const
+ {
+ target& pt (p.search ());
+ return pt.in (t.root_scope ()) ? &pt : nullptr;
+ }
+
+ recipe file_rule::
+ apply (action a, target& t, const match_result& mr) const
+ {
+ if (!mr.bvalue) // Not installable.
+ return noop_recipe;
+
+ // Ok, if we are here, then this means:
+ //
+ // 1. This target is installable.
+ // 2. The action is either
+ // a. (perform, install, 0) or
+ // b. (*, update, install)
+ //
+ // In both cases, the next step is to search, match, and collect
+ // all the installable prerequisites.
+ //
+ // @@ Perhaps if [noinstall] will be handled by the
+ // group_prerequisite_members machinery, then we can just
+ // run standard search_and_match()? Will need an indicator
+ // that it was forced (e.g., [install]) for filter() below.
+ //
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ // Ignore unresolved targets that are imported from other projects.
+ // We are definitely not installing those.
+ //
+ if (p.proj () != nullptr)
+ continue;
+
+ // Let a customized rule have its say.
+ //
+ target* pt (filter (a, t, p));
+ if (pt == nullptr)
+ continue;
+
+ // See if the user instructed us not to install it.
+ //
+ auto l ((*pt)["install"]);
+ if (l && as<dir_path> (*l).string () == "false")
+ continue;
+
+ build2::match (a, *pt);
+
+ // If the matched rule returned noop_recipe, then the target
+ // state will be set to unchanged as an optimization. Use this
+ // knowledge to optimize things on our side as well since this
+ // will help a lot in case of any static installable content
+ // (headers, documentation, etc).
+ //
+ if (pt->state () != target_state::unchanged)
+ t.prerequisite_targets.push_back (pt);
+ else
+ unmatch (a, *pt); // No intent to execute.
+ }
+
+ // This is where we diverge depending on the operation. In the
+ // update pre-operation, we need to make sure that this target
+ // as well as all its installable prerequisites are up to date.
+ //
+ if (a.operation () == update_id)
+ {
+ // Save the prerequisite targets that we found since the
+ // call to match_delegate() below will wipe them out.
+ //
+ target::prerequisite_targets_type p;
+
+ if (!t.prerequisite_targets.empty ())
+ p.swap (t.prerequisite_targets);
+
+ // Find the "real" update rule, that is, the rule that would
+ // have been found if we signalled that we do not match from
+ // match() above.
+ //
+ recipe d (match_delegate (a, t).first);
+
+ // If we have no installable prerequisites, then simply redirect
+ // to it.
+ //
+ if (p.empty ())
+ return d;
+
+ // Ok, the worst case scenario: we need to cause update of
+ // prerequisite targets and also delegate to the real update.
+ //
+ return [pt = move (p), dr = move (d)]
+ (action a, target& t) mutable -> target_state
+ {
+ // Do the target update first.
+ //
+ target_state r (execute_delegate (dr, a, t));
+
+ // Swap our prerequisite targets back in and execute.
+ //
+ t.prerequisite_targets.swap (pt);
+ r |= execute_prerequisites (a, t);
+ pt.swap (t.prerequisite_targets); // In case we get re-executed.
+
+ return r;
+ };
+ }
+ else
+ return &perform_install;
+ }
+
+ struct install_dir
+ {
+ dir_path dir;
+ string sudo;
+ string cmd; //@@ VAR type
+ const_strings_value options {nullptr};
+ string mode;
+ string dir_mode;
+ };
+
+ // install -d <dir>
+ //
+ static void
+ install (const install_dir& base, const dir_path& d)
+ {
+ path reld (relative (d));
+
+ cstrings args;
+
+ if (!base.sudo.empty ())
+ args.push_back (base.sudo.c_str ());
+
+ args.push_back (base.cmd.c_str ());
+ args.push_back ("-d");
+
+ if (base.options.d != nullptr) //@@ VAR
+ config::append_options (args, base.options);
+
+ args.push_back ("-m");
+ args.push_back (base.dir_mode.c_str ());
+ args.push_back (reld.string ().c_str ());
+ args.push_back (nullptr);
+
+ if (verb >= 2)
+ print_process (args);
+ else if (verb)
+ text << "install " << d;
+
+ try
+ {
+ process pr (args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+
+ // install <file> <dir>
+ //
+ static void
+ install (const install_dir& base, file& t)
+ {
+ path reld (relative (base.dir));
+ path relf (relative (t.path ()));
+
+ cstrings args;
+
+ if (!base.sudo.empty ())
+ args.push_back (base.sudo.c_str ());
+
+ args.push_back (base.cmd.c_str ());
+
+ if (base.options.d != nullptr) //@@ VAR
+ config::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.string ().c_str ());
+ args.push_back (nullptr);
+
+ if (verb >= 2)
+ print_process (args);
+ else if (verb)
+ text << "install " << t;
+
+ try
+ {
+ process pr (args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+
+ // Resolve installation directory name to absolute directory path,
+ // creating leading directories as necessary.
+ //
+ static install_dir
+ resolve (scope& s, dir_path d, const string* var = nullptr)
+ {
+ install_dir r;
+
+ if (d.absolute ())
+ {
+ d.normalize ();
+
+ // Make sure it already exists (this will normally be
+ // install.root with everything else defined in term of it).
+ //
+ if (!dir_exists (d))
+ fail << "installation directory " << d << " does not exist";
+ }
+ 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.
+ //
+ const string& sn (*d.begin ());
+ const string var ("install." + sn);
+ if (const dir_path* dn = lookup (s, var))
+ {
+ r = resolve (s, *dn, &var);
+ d = r.dir / dir_path (++d.begin (), d.end ());
+ d.normalize ();
+
+ if (!dir_exists (d))
+ install (r, d); // install -d
+ }
+ else
+ fail << "unknown installation directory name " << sn <<
+ info << "did you forget to specify config." << var << "?";
+ }
+
+ r.dir = move (d);
+
+ // Override components in install_dir if we have our own.
+ //
+ if (var != nullptr)
+ {
+ if (auto l = s[*var + ".sudo"]) r.sudo = as<string> (*l);
+ if (auto l = s[*var + ".cmd"]) r.cmd = as<string> (*l);
+ if (auto l = s[*var + ".mode"]) r.mode = as<string> (*l);
+ if (auto l = s[*var + ".dir_mode"]) r.dir_mode = as<string> (*l);
+ if (auto l = s[*var + ".options"]) r.options = as<strings> (*l);
+ }
+
+ // Set defaults for unspecified components.
+ //
+ if (r.cmd.empty ()) r.cmd = "install";
+ if (r.mode.empty ()) r.mode = "644";
+ if (r.dir_mode.empty ()) r.dir_mode = "755";
+
+ return r;
+ }
+
+ target_state file_rule::
+ perform_install (action a, target& t)
+ {
+ file& ft (static_cast<file&> (t));
+ assert (!ft.path ().empty ()); // Should have been assigned by update.
+
+ // First handle installable prerequisites.
+ //
+ target_state r (execute_prerequisites (a, t));
+
+ // Resolve and, if necessary, create target directory.
+ //
+ install_dir d (
+ resolve (t.base_scope (),
+ as<dir_path> (*t["install"]))); // We know it's there.
+
+ // Override mode if one was specified.
+ //
+ if (auto l = t["install.mode"])
+ d.mode = as<string> (*l);
+
+ install (d, ft);
+ return (r |= target_state::changed);
+ }
+ }
+}
diff --git a/build2/install/utility b/build2/install/utility
new file mode 100644
index 0000000..3cfc3e2
--- /dev/null
+++ b/build2/install/utility
@@ -0,0 +1,40 @@
+// file : build2/install/utility -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_INSTALL_UTILITY
+#define BUILD2_INSTALL_UTILITY
+
+#include <string>
+#include <utility>
+
+#include <build2/scope>
+#include <build2/types>
+
+namespace build2
+{
+ namespace install
+ {
+ // Set install path, mode for a target type.
+ //
+ template <typename T>
+ inline void
+ path (scope& s, dir_path d)
+ {
+ auto r (s.target_vars[T::static_type]["*"].assign ("install"));
+ if (r.second) // Already set by the user?
+ r.first.get () = std::move (d);
+ }
+
+ template <typename T>
+ inline void
+ mode (scope& s, std::string m)
+ {
+ auto r (s.target_vars[T::static_type]["*"].assign ("install.mode"));
+ if (r.second) // Already set by the user?
+ r.first.get () = std::move (m);
+ }
+ }
+}
+
+#endif // BUILD2_INSTALL_UTILITY