From 9fb791e9fad6c63fc1dac49f4d05ae63b8a3db9b Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 5 Jan 2016 11:55:15 +0200 Subject: Rename build directory/namespace to build2 --- build2/install/module | 26 +++ build2/install/module.cxx | 188 ++++++++++++++++++++ build2/install/operation | 18 ++ build2/install/operation.cxx | 32 ++++ build2/install/rule | 49 ++++++ build2/install/rule.cxx | 410 +++++++++++++++++++++++++++++++++++++++++++ build2/install/utility | 40 +++++ 7 files changed, 763 insertions(+) create mode 100644 build2/install/module create mode 100644 build2/install/module.cxx create mode 100644 build2/install/operation create mode 100644 build2/install/operation.cxx create mode 100644 build2/install/rule create mode 100644 build2/install/rule.cxx create mode 100644 build2/install/utility (limited to 'build2/install') 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 +#include + +#include + +namespace build2 +{ + namespace install + { + extern "C" void + install_boot (scope&, const location&, unique_ptr&); + + extern "C" bool + install_init ( + scope&, scope&, const location&, unique_ptr&, 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 + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace install + { + // Set install..* values based on config.install..* ones + // or the defaults. If none of config.install.* values were specified, + // then we do omitted/delayed configuration. Note that we still need + // to set all the install.* values to defaults, as if we had the + // default configuration. + // + // If override is true, then override values that came from outer + // configurations. We have to do this for paths that contain the + // package name. + // + template + 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::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::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 (s, r, n, ".sudo", nullptr); + set_var (s, r, n, ".cmd", c.empty () ? nullptr : &c); + set_var (s, r, n, ".options", nullptr); + } + + static alias_rule alias_; + static file_rule file_; + + extern "C" void + install_boot (scope& r, const location&, unique_ptr&) + { + 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&, + 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 (perform_install_id, "install.alias", alias_); + b.rules.insert (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 (*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 (b, dir_path ("doc")); // Install into install.doc. + path (b, dir_path ("man")); // Install into install.man. + path (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 + +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 + +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 +#include +#include +#include + +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 + +#include +#include + +#include +#include +#include +#include + +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace install + { + // Lookup the install or install.* variable. Return NULL if + // not found or if the value is the special 'false' name (which + // means do not install). T is either scope or target. + // + template + static const dir_path* + lookup (T& t, const string& var) + { + auto l (t[var]); + + if (!l) + return nullptr; + + const dir_path& r (as (*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 (*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 (*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 + // + 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 + // + 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 (*l); + if (auto l = s[*var + ".cmd"]) r.cmd = as (*l); + if (auto l = s[*var + ".mode"]) r.mode = as (*l); + if (auto l = s[*var + ".dir_mode"]) r.dir_mode = as (*l); + if (auto l = s[*var + ".options"]) r.options = as (*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 (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 (*t["install"]))); // We know it's there. + + // Override mode if one was specified. + // + if (auto l = t["install.mode"]) + d.mode = as (*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 +#include + +#include +#include + +namespace build2 +{ + namespace install + { + // Set install path, mode for a target type. + // + template + 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 + 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 -- cgit v1.1