diff options
Diffstat (limited to 'build/install/rule.cxx')
-rw-r--r-- | build/install/rule.cxx | 365 |
1 files changed, 365 insertions, 0 deletions
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 <build/install/rule> + +#include <butl/process> +#include <butl/filesystem> + +#include <build/scope> +#include <build/target> +#include <build/algorithm> +#include <build/diagnostics> + +#include <build/config/utility> + +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 <typename T> + 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<const list_value&> ()); + + 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 <typename T> + 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 <dir> + // + 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 <file> <dir> + // + 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<const string&> (); + if (auto v = s[*var + ".mode"]) r.mode = v.as<const string&> (); + if (auto v = s[*var + ".dir_mode"]) + r.dir_mode = v.as<const string&> (); + if (auto v = s[*var + ".options"]) + r.options = &v.as<const list_value&> (); + } + + // 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<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 (), + t["install"].as<const name&> ())); // We know it's there. + + // Override mode if one was specified. + // + if (auto v = t["install.mode"]) + d.mode = v.as<const string&> (); + + install (d, ft); + return (r |= target_state::changed); + } + } +} |