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/algorithm | 3 +- build/bin/module.cxx | 56 ++++++-- build/bin/rule.cxx | 2 + build/buildfile | 4 +- build/config/utility | 58 ++++---- build/config/utility.cxx | 30 ++++ build/config/utility.txx | 24 ++++ build/context.cxx | 3 + build/cxx/compile | 2 + build/cxx/compile.cxx | 12 +- build/cxx/install | 29 ++++ build/cxx/install.cxx | 48 +++++++ build/cxx/link | 2 + build/cxx/link.cxx | 2 + build/cxx/module.cxx | 86 ++++++----- build/install/module.cxx | 134 +++++++++++------ build/install/rule | 35 +++++ build/install/rule.cxx | 365 +++++++++++++++++++++++++++++++++++++++++++++++ build/install/utility | 36 +++++ build/operation | 2 + build/parser.cxx | 10 +- build/rule | 27 ++-- build/rule.cxx | 14 +- build/target | 68 +++++++++ build/target.cxx | 122 +++++++++++----- build/test/rule.cxx | 16 ++- build/variable | 25 +++- 27 files changed, 1017 insertions(+), 198 deletions(-) create mode 100644 build/cxx/install create mode 100644 build/cxx/install.cxx create mode 100644 build/install/rule create mode 100644 build/install/rule.cxx create mode 100644 build/install/utility (limited to 'build') diff --git a/build/algorithm b/build/algorithm index 3b09d9c..1171f9d 100644 --- a/build/algorithm +++ b/build/algorithm @@ -147,8 +147,7 @@ namespace build // on each non-ignored (non-NULL) prerequisite target in a loop. If this // target is a member of a group, then it first does this to the group's // prerequisites. Returns target_state::changed if any of them were - // changed and target_state::unchanged otherwise. It treats targets - // with postponed execution the same as ignored. Note that this + // changed and target_state::unchanged otherwise. Note that this // function can be used as a recipe. // target_state diff --git a/build/bin/module.cxx b/build/bin/module.cxx index 5e7888b..7dd5f00 100644 --- a/build/bin/module.cxx +++ b/build/bin/module.cxx @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -29,19 +30,19 @@ namespace build static const list_value libso_lib ("shared"); extern "C" void - bin_init (scope& root, - scope& base, + bin_init (scope& r, + scope& b, const location&, std::unique_ptr&, bool) { tracer trace ("bin::init"); - level4 ([&]{trace << "for " << base.path ();}); + level4 ([&]{trace << "for " << b.path ();}); // Register target types. // { - auto& tts (base.target_types); + auto& tts (b.target_types); tts.insert (); tts.insert (); tts.insert (); @@ -54,7 +55,7 @@ namespace build // Register rules. // { - auto& rs (base.rules); + auto& rs (b.rules); rs.insert (default_id, "bin.obj", obj_); rs.insert (update_id, "bin.obj", obj_); @@ -63,6 +64,8 @@ namespace build rs.insert (default_id, "bin.lib", lib_); rs.insert (update_id, "bin.lib", lib_); rs.insert (clean_id, "bin.lib", lib_); + + rs.insert (install_id, "bin.lib", lib_); } // Configure. @@ -82,34 +85,61 @@ namespace build // config.bin.lib // { - auto v (base.assign ("bin.lib")); + auto v (b.assign ("bin.lib")); if (!v) - v = required (root, "config.bin.lib", "shared").first; + v = required (r, "config.bin.lib", "both").first; } // config.bin.exe.lib // { - auto v (base.assign ("bin.exe.lib")); + auto v (b.assign ("bin.exe.lib")); if (!v) - v = required (root, "config.bin.exe.lib", exe_lib).first; + v = required (r, "config.bin.exe.lib", exe_lib).first; } // config.bin.liba.lib // { - auto v (base.assign ("bin.liba.lib")); + auto v (b.assign ("bin.liba.lib")); if (!v) - v = required (root, "config.bin.liba.lib", liba_lib).first; + v = required (r, "config.bin.liba.lib", liba_lib).first; } // config.bin.libso.lib // { - auto v (base.assign ("bin.libso.lib")); + auto v (b.assign ("bin.libso.lib")); if (!v) - v = required (root, "config.bin.libso.lib", libso_lib).first; + v = required (r, "config.bin.libso.lib", libso_lib).first; } + + // Configure "installability" of our target types. + // + install::path (b, "bin"); // Install into install.bin. + + // Should shared libraries have executable bit? That depends on + // who you ask. In Debian, for example, it should not unless, it + // really is executable (i.e., has main()). On the other hand, on + // some systems, this may be required in order for the dynamic + // linker to be able to load the library. So, by default, we will + // keep it executable, especially seeing that this is also the + // behavior of autotools. At the same time, it is easy to override + // this, for example: + // + // config.install.lib.mode=644 + // + // And a library that wants to override any such overrides (e.g., + // because it does have main()) can do: + // + // libso{foo}: install.mode=755 + // + // Everyone is happy then? + // + install::path (b, "lib"); // Install into install.lib. + + install::path (b, "lib"); // Install into install.lib. + install::mode (b, "644"); } } } diff --git a/build/bin/rule.cxx b/build/bin/rule.cxx index 0a7f2ee..eefa069 100644 --- a/build/bin/rule.cxx +++ b/build/bin/rule.cxx @@ -81,6 +81,8 @@ namespace build // the exported options machinery work for the library chains. // See cxx.export.*-related code in cxx/rule.cxx for details. // + // @@ Messes up dependents count. + // for (prerequisite& p: group_prerequisites (t)) { if (p.is_a () || p.is_a () || p.is_a ()) diff --git a/build/buildfile b/build/buildfile index ceda4eb..7522dc2 100644 --- a/build/buildfile +++ b/build/buildfile @@ -6,10 +6,10 @@ import libs += libbutl%lib{butl} config = config/{operation module utility} bin = bin/{target rule module} -cxx = cxx/{target compile link module utility} +cxx = cxx/{target compile link install module utility} cli = cli/{target rule module} test = test/{operation rule module} -install = install/{operation module} +install = install/{operation rule module} exe{b}: cxx{b algorithm name operation spec scope variable target \ prerequisite rule file module context search diagnostics token \ diff --git a/build/config/utility b/build/config/utility index 713ab01..ef3ceed 100644 --- a/build/config/utility +++ b/build/config/utility @@ -27,6 +27,13 @@ namespace build std::pair required (scope& root, const char* name, const T& default_value); + template + inline std::pair + required (scope& root, const std::string& name, const T& default_value) + { + return required (root, name.c_str (), default_value); + } + std::pair required (scope& root, const char* name, const char* default_value); @@ -48,45 +55,38 @@ namespace build return optional (root, name.c_str ()); } + // Check whether there are any variables specified from the + // config namespace. The idea is that we can check if there + // are any, say, config.install.* values. If there are none, + // then we can assume this functionality is not (yet) used + // and omit writing a whole bunch of NULL config.install.* + // values to config.build. We call it omitted/delayed + // configuration. + // + bool + specified (scope& root, const std::string& ns); + + // @@ Why are these here? + // + // Add all the values from a variable to the C-string list. T is // either target or scope. // template void - append_options (cstrings& args, T& s, const char* var) - { - if (auto val = s[var]) - { - for (const name& n: val.template as ()) - { - if (n.simple ()) - args.push_back (n.value.c_str ()); - else if (n.directory ()) - args.push_back (n.dir.string ().c_str ()); - else - fail << "expected option instead of " << n << - info << "in variable " << var; - } - } - } + append_options (cstrings& args, T& s, const char* var); + + // As above but from the list value directly. Variable name is for + // diagnostics. + // + void + append_options (cstrings& args, const list_value&, const char* var); // Check if a specified option is present. T is either target or scope. // template bool - find_option (const char* option, T& s, const char* var) - { - if (auto val = s[var]) - { - for (const name& n: val.template as ()) - { - if (n.simple () && n.value == option) - return true; - } - } - - return false; - } + find_option (const char* option, T& s, const char* var); } } diff --git a/build/config/utility.cxx b/build/config/utility.cxx index 212d030..e2afc80 100644 --- a/build/config/utility.cxx +++ b/build/config/utility.cxx @@ -37,5 +37,35 @@ namespace build return pair (v.as (), true); } + + bool + specified (scope& r, const string& ns) + { + // Search all outer scopes for any value in this namespace. + // + for (scope* s (&r); s != nullptr; s = s->parent_scope ()) + { + auto p (s->vars.find_namespace (ns)); + if (p.first != p.second) + return true; + } + + return false; + } + + void + append_options (cstrings& args, const list_value& lv, const char* var) + { + for (const name& n: lv) + { + if (n.simple ()) + args.push_back (n.value.c_str ()); + else if (n.directory ()) + args.push_back (n.dir.string ().c_str ()); + else + fail << "expected option instead of " << n << + info << "in variable " << var; + } + } } } diff --git a/build/config/utility.txx b/build/config/utility.txx index bee8a0b..cffdecf 100644 --- a/build/config/utility.txx +++ b/build/config/utility.txx @@ -57,5 +57,29 @@ namespace build return r; } + + template + void + append_options (cstrings& args, T& s, const char* var) + { + if (auto val = s[var]) + append_options (args, val.template as (), var); + } + + template + bool + find_option (const char* option, T& s, const char* var) + { + if (auto val = s[var]) + { + for (const name& n: val.template as ()) + { + if (n.simple () && n.value == option) + return true; + } + } + + return false; + } } } diff --git a/build/context.cxx b/build/context.cxx index f0e853d..5a9452b 100644 --- a/build/context.cxx +++ b/build/context.cxx @@ -65,6 +65,9 @@ namespace build tts.insert (); tts.insert (); tts.insert (); + tts.insert (); + tts.insert (); + tts.insert (); } // Register builtin rules. diff --git a/build/cxx/compile b/build/cxx/compile index 1cc91ab..cb44829 100644 --- a/build/cxx/compile +++ b/build/cxx/compile @@ -23,6 +23,8 @@ namespace build static target_state perform_update (action, target&); + + static compile instance; }; } } diff --git a/build/cxx/compile.cxx b/build/cxx/compile.cxx index ae1ebe2..d89aaac 100644 --- a/build/cxx/compile.cxx +++ b/build/cxx/compile.cxx @@ -649,12 +649,14 @@ namespace build // There would normally be a lot of headers for every source // file (think all the system headers) and this can get // expensive. At the same time, most of these headers are - // existing files that we will never be updating (again, + // existing files that we will never be updated (again, // system headers, for example) and the rule that will match - // them is fallback file_rule. So we are going to do a little - // fast-path optimization by detecting this common case. + // them is fallback file_rule. That rule has an optimization + // in that it returns noop_recipe (which causes the target + // state to be automatically set to unchanged) if the file + // is known to be up to date. // - if (!file_rule::uptodate (a, pt)) + if (pt.state () != target_state::unchanged) { // We only want to restart if our call to execute() actually // caused an update. In particular, the target could already @@ -787,5 +789,7 @@ namespace build throw failed (); } } + + compile compile::instance; } } diff --git a/build/cxx/install b/build/cxx/install new file mode 100644 index 0000000..3a6f463 --- /dev/null +++ b/build/cxx/install @@ -0,0 +1,29 @@ +// file : build/cxx/install -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_CXX_INSTALL +#define BUILD_CXX_INSTALL + +#include +#include + +namespace build +{ + namespace cxx + { + class install: public build::install::rule + { + public: + virtual bool + filter (action, target&, prerequisite_member) const; + + virtual match_result + match (action, target&, const std::string&) const; + + static install instance; + }; + } +} + +#endif // BUILD_CXX_INSTALL diff --git a/build/cxx/install.cxx b/build/cxx/install.cxx new file mode 100644 index 0000000..b623349 --- /dev/null +++ b/build/cxx/install.cxx @@ -0,0 +1,48 @@ +// file : build/cxx/install.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include + +using namespace std; + +namespace build +{ + namespace cxx + { + using namespace bin; + + bool install:: + filter (action, target& t, prerequisite_member p) const + { + // Don't install executable's prerequisite headers. + // + if (t.is_a () && + (p.is_a () || p.is_a () || p.is_a () || p.is_a ())) + return false; + + return true; + } + + match_result install:: + match (action a, target& t, const std::string& hint) const + { + // @@ How do we split the hint between the two? + // + + // We only want to handle installation if we are also the + // ones building this target. So first run link's match(). + // + match_result r (link::instance.match (a, t, hint)); + + return r ? install::rule::match (a, t, "") : r; + } + + install install::instance; + } +} diff --git a/build/cxx/link b/build/cxx/link index 75223fc..5d3d29e 100644 --- a/build/cxx/link +++ b/build/cxx/link @@ -28,6 +28,8 @@ namespace build static target_state perform_update (action, target&); + static link instance; + private: friend class compile; diff --git a/build/cxx/link.cxx b/build/cxx/link.cxx index 145a085..5c3a515 100644 --- a/build/cxx/link.cxx +++ b/build/cxx/link.cxx @@ -835,5 +835,7 @@ namespace build throw failed (); } } + + link link::instance; } } diff --git a/build/cxx/module.cxx b/build/cxx/module.cxx index 882d4b0..1e6ed8a 100644 --- a/build/cxx/module.cxx +++ b/build/cxx/module.cxx @@ -11,12 +11,14 @@ #include #include +#include #include #include #include #include +#include using namespace std; using namespace butl; @@ -25,29 +27,26 @@ namespace build { namespace cxx { - compile compile_; - link link_; - extern "C" void - cxx_init (scope& root, - scope& base, + cxx_init (scope& r, + scope& b, const location& l, std::unique_ptr&, bool first) { tracer trace ("cxx::init"); - level4 ([&]{trace << "for " << base.path ();}); + level4 ([&]{trace << "for " << b.path ();}); // Initialize the bin module. Only do this if it hasn't already // been loaded so that we don't overwrite user's bin.* settings. // - if (base.find_target_type ("obj") == nullptr) - load_module ("bin", root, base, l); + if (b.find_target_type ("obj") == nullptr) + load_module ("bin", r, b, l); // Register target types. // { - auto& tts (base.target_types); + auto& tts (b.target_types); tts.insert (); tts.insert (); @@ -63,27 +62,31 @@ namespace build { using namespace bin; - auto& rs (base.rules); + auto& rs (b.rules); + + rs.insert (default_id, "cxx.compile", compile::instance); + rs.insert (update_id, "cxx.compile", compile::instance); + rs.insert (clean_id, "cxx.compile", compile::instance); - rs.insert (default_id, "cxx.compile", compile_); - rs.insert (update_id, "cxx.compile", compile_); - rs.insert (clean_id, "cxx.compile", compile_); + rs.insert (default_id, "cxx.compile", compile::instance); + rs.insert (update_id, "cxx.compile", compile::instance); + rs.insert (clean_id, "cxx.compile", compile::instance); - rs.insert (default_id, "cxx.compile", compile_); - rs.insert (update_id, "cxx.compile", compile_); - rs.insert (clean_id, "cxx.compile", compile_); + rs.insert (default_id, "cxx.link", link::instance); + rs.insert (update_id, "cxx.link", link::instance); + rs.insert (clean_id, "cxx.link", link::instance); - rs.insert (default_id, "cxx.link", link_); - rs.insert (update_id, "cxx.link", link_); - rs.insert (clean_id, "cxx.link", link_); + rs.insert (default_id, "cxx.link", link::instance); + rs.insert (update_id, "cxx.link", link::instance); + rs.insert (clean_id, "cxx.link", link::instance); - rs.insert (default_id, "cxx.link", link_); - rs.insert (update_id, "cxx.link", link_); - rs.insert (clean_id, "cxx.link", link_); + rs.insert (default_id, "cxx.link", link::instance); + rs.insert (update_id, "cxx.link", link::instance); + rs.insert (clean_id, "cxx.link", link::instance); - rs.insert (default_id, "cxx.link", link_); - rs.insert (update_id, "cxx.link", link_); - rs.insert (clean_id, "cxx.link", link_); + rs.insert (install_id, "cxx.install", install::instance); + rs.insert (install_id, "cxx.install", install::instance); + rs.insert (install_id, "cxx.install", install::instance); } // Configure. @@ -93,13 +96,13 @@ namespace build // if (first) { - auto r (config::required (root, "config.cxx", "g++")); + auto p (config::required (r, "config.cxx", "g++")); // If we actually set a new value, test it by trying to execute. // - if (r.second) + if (p.second) { - const string& cxx (r.first); + const string& cxx (p.first); const char* args[] = {cxx.c_str (), "-dumpversion", nullptr}; if (verb) @@ -152,17 +155,28 @@ namespace build // using cxx // cxx.coptions += # Note: '+='. // - if (auto* v = config::optional (root, "config.cxx.poptions")) - base.assign ("cxx.poptions") += *v; + if (auto* v = config::optional (r, "config.cxx.poptions")) + b.assign ("cxx.poptions") += *v; - if (auto* v = config::optional (root, "config.cxx.coptions")) - base.assign ("cxx.coptions") += *v; + if (auto* v = config::optional (r, "config.cxx.coptions")) + b.assign ("cxx.coptions") += *v; - if (auto* v = config::optional (root, "config.cxx.loptions")) - base.assign ("cxx.loptions") += *v; + if (auto* v = config::optional (r, "config.cxx.loptions")) + b.assign ("cxx.loptions") += *v; - if (auto* v = config::optional (root, "config.cxx.libs")) - base.assign ("cxx.libs") += *v; + if (auto* v = config::optional (r, "config.cxx.libs")) + b.assign ("cxx.libs") += *v; + + // Configure "installability" of our target types. + // + { + using build::install::path; + + path (b, "include"); // Install into install.include. + path (b, "include"); + path (b, "include"); + path (b, "include"); + } } } } 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 diff --git a/build/operation b/build/operation index 55f901d..ad7b911 100644 --- a/build/operation +++ b/build/operation @@ -112,9 +112,11 @@ namespace build const operation_id default_id = 1; const operation_id update_id = 2; const operation_id clean_id = 3; + const operation_id install_id = 4; const action_id perform_update_id = (perform_id << 4) | update_id; const action_id perform_clean_id = (perform_id << 4) | clean_id; + const action_id perform_install_id = (perform_id << 4) | install_id; // Recipe execution mode. // diff --git a/build/parser.cxx b/build/parser.cxx index 29ee02b..682a720 100644 --- a/build/parser.cxx +++ b/build/parser.cxx @@ -701,9 +701,9 @@ namespace build // The rest is a value. Parse it as names to get variable expansion. // build::import() will check the names, if required. // - export_value_ = (tt != type::newline && tt != type::eos - ? names (t, tt) - : names_type ()); + export_value_ = list_value (tt != type::newline && tt != type::eos + ? names (t, tt) + : names_type ()); if (tt == type::newline) next (t, tt); @@ -788,7 +788,7 @@ namespace build auto v (target_ != nullptr ? target_->assign (var) : scope_->assign (var)); - v = move (vns); + v = list_value (move (vns)); } else { @@ -806,7 +806,7 @@ namespace build make_move_iterator (vns.end ())); } else - v = move (vns); // Same as assignment. + v = list_value (move (vns)); // Same as assignment. } } diff --git a/build/rule b/build/rule index a144a9b..5339b40 100644 --- a/build/rule +++ b/build/rule @@ -23,20 +23,22 @@ namespace build // Can contain neither (both are NULL), one of, or both. If both // are NULL, then it is a "no match" indicator. // - // Note that if the "payload" is stored in value instead of + // Note that if the "payload" is stored in *value instead of // prerequisite, then target must not be NULL. // union { prerequisite_type* prerequisite; - bool value; + + bool bvalue; + void* pvalue; + const void* cpvalue; }; target_type* target; action recipe_action = action (); // Used as recipe's action if set. - match_result (target_type& t, bool v): value (v), target (&t) {} match_result (std::nullptr_t v = nullptr): prerequisite (v), target (v) {} match_result (prerequisite_type& p): prerequisite (&p), target (nullptr) {} match_result (prerequisite_type* p): prerequisite (p), target (nullptr) {} @@ -45,6 +47,11 @@ namespace build match_result (const prerequisite_member& pm) : prerequisite (&pm.prerequisite.get ()), target (pm.target) {} + match_result (target_type& t, bool v): bvalue (v), target (&t) {} + match_result (target_type& t, void* v): pvalue (v), target (&t) {} + match_result (target_type& t, const void* v): cpvalue (v), target (&t) {} + match_result (target_type& t, std::nullptr_t v): pvalue (v), target (&t) {} + explicit operator bool () const { @@ -77,20 +84,6 @@ namespace build static target_state perform_update (action, target&); - // Sometimes it is useful, normally as an optimization, to check - // if the file target is up to date. In particular, if the rule - // that matched a target is this fallback rule and the target - // has no prerequisites, then it means it is up to date. - // - static bool - uptodate (action a, target& t) - { - recipe_function* const* r (t.recipe (a).target ()); - return r != nullptr && *r == &perform_update && - t.prerequisites.empty () && - (t.group == nullptr || t.group->prerequisites.empty ()); - } - static file_rule instance; }; diff --git a/build/rule.cxx b/build/rule.cxx index 82ce993..9f17a2c 100644 --- a/build/rule.cxx +++ b/build/rule.cxx @@ -88,13 +88,19 @@ namespace build if (a.operation () == clean_id) return noop_recipe; + // If we have no prerequisites, then this means this file + // is up to date. Return noop_recipe which will also cause + // the target's state to be set to unchanged. This is an + // important optimization on which quite a few places that + // deal with predominantly static content rely. + // + if (!t.has_prerequisites ()) + return noop_recipe; + // Search and match all the prerequisites. // search_and_match_prerequisites (a, t); - - return a == perform_update_id - ? &perform_update - : t.has_prerequisites () ? default_recipe : noop_recipe; + return a == perform_update_id ? &perform_update : default_recipe; } target_state file_rule:: diff --git a/build/target b/build/target index f777268..dbfe9a8 100644 --- a/build/target +++ b/build/target @@ -952,6 +952,74 @@ namespace build static const target_type static_type; }; + // Common documentation file targets. + // + // @@ Maybe these should be in the built-in doc module? + // + class doc: public file + { + public: + using file::file; + + public: + virtual const target_type& type () const {return static_type;} + static const target_type static_type; + }; + + // The problem with man pages is this: different platforms have + // different sets of sections. What seems to be the "sane" set + // is 1-9 (Linux and BSDs). SysV (e.g., Solaris) instead maps + // 8 to 1M (system administration). The section determines two + // things: the directory where the page is installed (e.g., + // /usr/share/man/man1) as well as the extension of the file + // (e.g., test.1). Note also that there could be sub-sections, + // e.g., 1p (for POSIX). Such a page would still go into man1 + // but will have the .1p extension (at least that's what happens + // on Linux). The challenge is to somehow handle this in a + // portable manner. So here is the plan: + // + // First of all, we have the man{} target type which can be used + // for a custom man page. That is, you can have any extension and + // install it anywhere you please: + // + // man{foo.X}: install = man/manX + // + // Then we have man1..9{} target types which model the "sane" + // section set and that would be automatically installed into + // correct locations on other platforms. In other words, the + // idea is that you should be able to have the foo.8 file, + // write man8{foo} and have it installed as man1m/foo.1m on + // some SysV host. + // + // Re-mapping the installation directory is easy: to help with + // that we have assigned install.man1..9 directory names. The + // messy part is to change the extension. It seems the only + // way to do that would be to have special logic for man pages + // in the generic install rule. @@ This is still a TODO. + // + // Note that handling subsections with man1..9{} is easy, we + // simply specify the extension explicitly, e.g., man{foo.1p}. + // + class man: public doc + { + public: + using doc::doc; + + public: + virtual const target_type& type () const {return static_type;} + static const target_type static_type; + }; + + class man1: public man + { + public: + using man::man; + + public: + virtual const target_type& type () const {return static_type;} + static const target_type static_type; + }; + // Common implementation of the target factory, extension, and // search functions. // diff --git a/build/target.cxx b/build/target.cxx index cdcc6b0..c1c5ef4 100644 --- a/build/target.cxx +++ b/build/target.cxx @@ -76,59 +76,68 @@ namespace build value_proxy target:: operator[] (const variable& var) const { - auto i (vars.find (var)); + auto find = [&var] (const variable_map& vars) + { + auto i (vars.find (var)); + return i != vars.end () ? &const_cast (i->second) : nullptr; + }; - if (i != vars.end ()) - // @@ Same issue as in variable_map: need ro_value_proxy. - return value_proxy (&const_cast (i->second), &vars); + if (auto p = find (vars)) + return value_proxy (p, &vars); if (group != nullptr) - return (*group)[var]; + { + if (auto p = find (group->vars)) + return value_proxy (p, &group->vars); + } // Cannot simply delegate to scope's operator[] since we also // need to check target type/pattern-specific variables. // - const target_type& btt (type ()); - for (const scope* s (&base_scope ()); s != nullptr; s = s->parent_scope ()) { if (!s->target_vars.empty ()) { - // Search across target type hierarchy. - // - for (auto tt (&btt); tt != nullptr; tt = tt->base) + auto find_specific = [&find, s] (const target& t) -> value_proxy { - auto i (s->target_vars.find (*tt)); - - if (i == s->target_vars.end ()) - continue; + // Search across target type hierarchy. + // + for (auto tt (&t.type ()); tt != nullptr; tt = tt->base) + { + auto i (s->target_vars.find (*tt)); - //@@ TODO: match pattern. For now, we only handle '*'. + if (i == s->target_vars.end ()) + continue; - auto j (i->second.find ("*")); + //@@ TODO: match pattern. For now, we only handle '*'. - if (j == i->second.end ()) - continue; + auto j (i->second.find ("*")); - auto k (j->second.find (var)); + if (j == i->second.end ()) + continue; - if (k != j->second.end ()) - { - // Note that we use the scope's vars, not from target_vars. - // We use it to identify whether the variable belongs to a - // specific scope/target. - // - return value_proxy (&const_cast (k->second), &s->vars); + if (auto p = find (j->second)) + return value_proxy (p, &j->second); } + + return value_proxy (); + }; + + if (auto p = find_specific (*this)) + return p; + + if (group != nullptr) + { + if (auto p = find_specific (*group)) + return p; } } - auto i (s->vars.find (var)); - if (i != s->vars.end ()) - return value_proxy (&const_cast (i->second), &s->vars); + if (auto p = find (s->vars)) + return value_proxy (p, &s->vars); } - return value_proxy (nullptr, nullptr); + return value_proxy (); } value_proxy target:: @@ -387,6 +396,7 @@ namespace build false }; + template static target* file_factory (dir_path d, string n, const string* e) { @@ -394,9 +404,9 @@ namespace build // wasn't specified, set it to empty rather than unspecified. // In other words, we always treat file{foo} as file{foo.}. // - return new file (move (d), - move (n), - (e != nullptr ? e : &extension_pool.find (""))); + return new T (move (d), + move (n), + (e != nullptr ? e : &extension_pool.find (""))); } constexpr const char file_ext[] = ""; @@ -405,7 +415,7 @@ namespace build typeid (file), "file", &path_target::static_type, - &file_factory, + &file_factory, &target_extension_fix, &search_file, false @@ -443,4 +453,48 @@ namespace build &search_target, false }; + + constexpr const char doc_ext[] = ""; + const target_type doc::static_type + { + typeid (doc), + "doc", + &file::static_type, + &file_factory, + &target_extension_fix, + &search_file, + false + }; + + static target* + man_factory (dir_path d, string n, const string* e) + { + if (e == nullptr) + fail << "man target '" << n << "' must include extension (man section)"; + + return new man (move (d), move (n), e); + } + + const target_type man::static_type + { + typeid (man), + "man", + &doc::static_type, + &man_factory, + nullptr, // Should be specified explicitly. + &search_file, + false + }; + + constexpr const char man1_ext[] = "1"; + const target_type man1::static_type + { + typeid (man1), + "man1", + &man::static_type, + &file_factory, + &target_extension_fix, + &search_file, + false + }; } diff --git a/build/test/rule.cxx b/build/test/rule.cxx index da17d61..6e82d8d 100644 --- a/build/test/rule.cxx +++ b/build/test/rule.cxx @@ -5,7 +5,6 @@ #include #include -#include #include #include @@ -31,6 +30,9 @@ namespace build bool r (false); value_proxy v; + // @@ This logic doesn't take into account target type/pattern- + // specific variables. + // for (auto p (t.vars.find_namespace ("test")); p.first != p.second; ++p.first) @@ -62,7 +64,7 @@ namespace build if (!r) { - // See if the is a scope variable. + // See if there is a scope variable. // if (!v) v.rebind (t.base_scope ()[string("test.") + t.type ().name]); @@ -103,7 +105,7 @@ namespace build { tracer trace ("test::rule::apply"); - if (!mr.value) // Not a test. + if (!mr.bvalue) // Not a test. return noop_recipe; // In case of test, we don't do anything for other meta-operations. @@ -116,7 +118,7 @@ namespace build // 1. This target is a test. // 2. The action is either // a. (perform, test, 0) or - // b. (*, update, 0) + // b. (*, update, install) // // In both cases, the next step is to see if we have test.{input, // output,roundtrip}. @@ -207,7 +209,7 @@ namespace build { build::match (a, *it); - if (file_rule::uptodate (a, *it)) + if (it->state () == target_state::unchanged) it = nullptr; } @@ -215,7 +217,7 @@ namespace build { build::match (a, *ot); - if (file_rule::uptodate (a, *ot)) + if (ot->state () == target_state::unchanged) ot = nullptr; } else @@ -229,7 +231,7 @@ namespace build recipe d (match_delegate (a, t).first); // If we have no input/output that needs updating, then simply - // redirect to it + // redirect to it. // if (it == nullptr && ot == nullptr) return d; diff --git a/build/variable b/build/variable index 998a853..ed8d65c 100644 --- a/build/variable +++ b/build/variable @@ -73,10 +73,11 @@ namespace build using names::names; list_value () = default; - list_value (names d): names (std::move (d)) {} - list_value (name n) {emplace_back (std::move (n));} - list_value (dir_path d) {emplace_back (std::move (d));} - list_value (std::string d) {emplace_back (std::move (d));} + explicit list_value (names d): names (std::move (d)) {} + explicit list_value (name n) {emplace_back (std::move (n));} + explicit list_value (dir_path d) {emplace_back (std::move (d));} + explicit list_value (std::string s) {emplace_back (std::move (s));} + explicit list_value (const char* s) {emplace_back (s);} virtual value_ptr clone () const {return value_ptr (new list_value (*this));} @@ -165,6 +166,7 @@ namespace build operator+= (std::string) const; // Append simple name to list_value. // Return true if this value belongs to the specified scope or target. + // Note that it can also be a target type/pattern-specific value. // template bool @@ -218,6 +220,15 @@ namespace build } template <> + inline const name& value_proxy:: + as () const + { + const auto& lv (as ()); + assert (lv.size () == 1); + return lv.front (); + } + + template <> std::string& value_proxy:: as () const; @@ -348,6 +359,12 @@ namespace build using variable_pattern_map = std::map; using variable_type_map = std::map, variable_pattern_map>; + + //@@ In quite a few places we assume that we can store a reference + // to the returned value (e.g., install::lookup_install()). If + // we "instantiate" the value on the fly, then we will need to + // consider its lifetime. + } #include -- cgit v1.1