From bbd0f3bb21442a2833916110cbe8e9a07e9f4c1f Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 31 Jul 2015 12:52:20 +0200 Subject: Essential install module functionality --- build/install/module.cxx | 134 +++++++++++------ build/install/rule | 35 +++++ build/install/rule.cxx | 365 +++++++++++++++++++++++++++++++++++++++++++++++ build/install/utility | 36 +++++ 4 files changed, 529 insertions(+), 41 deletions(-) create mode 100644 build/install/rule create mode 100644 build/install/rule.cxx create mode 100644 build/install/utility (limited to 'build/install') diff --git a/build/install/module.cxx b/build/install/module.cxx index dc88ec2..1dd655e 100644 --- a/build/install/module.cxx +++ b/build/install/module.cxx @@ -7,10 +7,13 @@ #include #include #include +#include #include #include +#include +#include #include using namespace std; @@ -20,48 +23,74 @@ namespace build { namespace install { - // Set install. values based on config.install. values - // or the defaults. + // 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. // static void - set_dir (scope& r, const char* name, const char* path, const char* mode) + set_dir (bool spec, + scope& r, + const char* name, + const char* path, + const char* mode = nullptr, + const char* dir_mode = nullptr, + const char* cmd = nullptr, + const char* options = nullptr) { - string vn ("config.install."); - vn += name; - - const list_value* pv (config::optional (r, vn)); - - vn += ".mode"; - const list_value* mv (config::optional (r, vn)); - - vn = "install."; - vn += name; - auto p (r.assign (vn)); - - if (pv != nullptr && !pv->empty ()) - p = *pv; - else if (path != nullptr) - p = path; - - vn += ".mode"; - auto m (r.assign (vn)); - - if (mv != nullptr && !mv->empty ()) - p = *mv; - else if (mode != nullptr) - p = mode; + auto set = [spec, &r, name] (const char* var, const char* dv) + { + string vn; + const list_value* lv (nullptr); + + if (spec) + { + vn = "config.install."; + vn += name; + vn += var; + + lv = dv != nullptr + ? &config::required (r, vn, list_value (dv)).first + : config::optional (r, vn); + } + + vn = "install."; + vn += name; + vn += var; + auto v (r.assign (vn)); + + if (spec) + { + if (lv != nullptr && !lv->empty ()) + v = *lv; + } + else + { + if (dv != nullptr) + v = dv; + } + }; + + set ("", path); + set (".mode", mode); + set (".dir_mode", dir_mode); + set (".cmd", cmd); + set (".options", options); } + static rule rule_; + extern "C" void - install_init (scope& root, - scope& base, + install_init (scope& r, + scope& b, const location& l, unique_ptr&, bool first) { tracer trace ("install::init"); - if (&root != &base) + if (&r != &b) fail (l) << "install module must be initialized in bootstrap.build"; if (!first) @@ -70,36 +99,59 @@ namespace build return; } - const dir_path& out_root (root.path ()); + const dir_path& out_root (r.path ()); level4 ([&]{trace << "for " << out_root;}); // Register the install operation. // - operation_id install_id (root.operations.insert (install)); + assert (r.operations.insert (install) == install_id); { - auto& rs (base.rules); + auto& rs (b.rules); // Register the standard alias rule for the install operation. // rs.insert (install_id, "alias", alias_rule::instance); + + // Register our file installer rule. + // + rs.insert (install_id, "install", rule_); } // Configuration. // - // Note that we don't use any defaults -- the location must - // be explicitly specified or the installer will complain if - // and when we try to install. + // 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) { - set_dir (root, "root", nullptr, nullptr); - set_dir (root, "data_root", "root", "644"); - set_dir (root, "exec_root", "root", "755"); + bool s (config::specified (r, "config.install")); + const string& n (r["project"].as ()); + + set_dir (s, r, "root", nullptr, nullptr, "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).c_str ()); + + set_dir (s, r, "data", ("data_root/share/" + n).c_str ()); + set_dir (s, r, "include", "data_root/include"); - set_dir (root, "bin", "exec_root/bin", nullptr); - set_dir (root, "sbin", "exec_root/sbin", nullptr); + set_dir (s, r, "doc", ("data_root/share/doc/" + n).c_str ()); + set_dir (s, r, "man", "data_root/share/man"); + + set_dir (s, r, "man1", "man/man1"); } + + // Configure "installability" for built-in target types. + // + path (b, "doc"); // Install into install.doc. + path (b, "man"); // Install into install.man. + path (b, "man1"); // Install into install.man1. } } } diff --git a/build/install/rule b/build/install/rule new file mode 100644 index 0000000..bed0836 --- /dev/null +++ b/build/install/rule @@ -0,0 +1,35 @@ +// file : build/install/rule -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_INSTALL_RULE +#define BUILD_INSTALL_RULE + +#include +#include +#include +#include + +namespace build +{ + namespace install + { + class rule: public build::rule + { + public: + virtual bool + filter (action, target&, prerequisite_member) const {return true;} + + virtual match_result + match (action, target&, const std::string&) const; + + virtual recipe + apply (action, target&, const match_result&) const; + + static target_state + perform_install (action, target&); + }; + } +} + +#endif // BUILD_INSTALL_RULE diff --git a/build/install/rule.cxx b/build/install/rule.cxx new file mode 100644 index 0000000..5642388 --- /dev/null +++ b/build/install/rule.cxx @@ -0,0 +1,365 @@ +// file : build/install/rule.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include + +#include +#include +#include +#include + +#include + +using namespace std; +using namespace butl; + +namespace build +{ + namespace install + { + // Lookup the install or install.* variable and check that + // the value makes sense. 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 + static const name* + lookup (T& t, const char* var) + { + auto v (t[var]); + + const name* r (nullptr); + + if (!v) + return r; + + const list_value& lv (v.template as ()); + + if (lv.empty ()) + return r; + + if (lv.size () == 1) + { + const name& n (lv.front ()); + + if (n.simple () && n.value == "false") + return r; + + if (!n.empty () && (n.simple () || n.directory ())) + return &n; + } + + fail << "expected directory instead of '" << lv << "' in " + << "the " << var << " variable"; + + return r; + } + + template + static inline const name* + lookup (T& t, const string& var) {return lookup (t, var.c_str ());} + + match_result rule:: + match (action a, target& t, const std::string&) const + { + // First determine if this target should be installed (called + // "installable" for short). + // + match_result mr (t, lookup (t, "install") != nullptr); + + // If this is the update pre-operation, change the recipe action + // to (update, 0) (i.e., "unconditional update"). + // + if (mr.bvalue && a.operation () == update_id) + mr.recipe_action = action (a.meta_operation (), update_id); + + return mr; + } + + recipe rule:: + apply (action a, target& t, const match_result& mr) const + { + if (!mr.bvalue) // Not installable. + return noop_recipe; + + // In case of install, we don't do anything for other meta-operations. + // + if (a.operation () == install_id && a.meta_operation () != perform_id) + 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)) + { + // @@ This is where we will handle [noinstall]. + // + + // Let a customized rule have its say. + // + // @@ This will be skipped if forced with [install]. + // + if (!filter (a, t, p)) + continue; + + target& pt (p.search ()); + build::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). + // + // @@ This messes up the dependents count logic. + // + if (pt.state () != target_state::unchanged) + t.prerequisite_targets.push_back (&pt); + } + + // 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 prerequsites, 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 cmd; + const list_value* options {nullptr}; + string mode; + string dir_mode; + }; + + // install -d + // + static void + install (const install_dir& base, const dir_path& d) + { + path reld (relative (d)); + + cstrings args {base.cmd.c_str (), "-d"}; + + if (base.options != nullptr) + config::append_options (args, *base.options, "install.*.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) + print_process (args); + else + 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 + // + static void + install (const install_dir& base, file& t) + { + path reld (relative (base.dir)); + path relf (relative (t.path ())); + + cstrings args {base.cmd.c_str ()}; + + if (base.options != nullptr) + config::append_options (args, *base.options, "install.*.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) + print_process (args); + else + 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, const name& n, const string* var = nullptr) + { + install_dir r; + dir_path d (n.simple () ? dir_path (n.value) : n.dir); + + 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& dn (*d.begin ()); + const string var ("install." + dn); + if (const name* n = lookup (s, var)) + { + r = resolve (s, *n, &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 " << dn << + 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 v = s[*var + ".cmd"]) r.cmd = v.as (); + if (auto v = s[*var + ".mode"]) r.mode = v.as (); + if (auto v = s[*var + ".dir_mode"]) + r.dir_mode = v.as (); + if (auto v = s[*var + ".options"]) + r.options = &v.as (); + } + + // 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 rule:: + perform_install (action a, target& t) + { + file& ft (static_cast (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 (), + t["install"].as ())); // We know it's there. + + // Override mode if one was specified. + // + if (auto v = t["install.mode"]) + d.mode = v.as (); + + install (d, ft); + return (r |= target_state::changed); + } + } +} diff --git a/build/install/utility b/build/install/utility new file mode 100644 index 0000000..5c703fc --- /dev/null +++ b/build/install/utility @@ -0,0 +1,36 @@ +// file : build/install/utility -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_INSTALL_UTILITY +#define BUILD_INSTALL_UTILITY + +#include + +namespace build +{ + namespace install + { + // Set install path, mode for a target type. + // + template + inline void + path (scope& s, const char* v) + { + auto p (s.target_vars[T::static_type]["*"].assign ("install")); + if (p.second) // Already set by the user? + p.first = v; + } + + template + inline void + mode (scope& s, const char* v) + { + auto m (s.target_vars[T::static_type]["*"].assign ("install.mode")); + if (m.second) // Already set by the user? + m.first = v; + } + } +} + +#endif // BUILD_INSTALL_UTILITY -- cgit v1.1