aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-07-31 12:52:20 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-07-31 12:52:20 +0200
commitbbd0f3bb21442a2833916110cbe8e9a07e9f4c1f (patch)
treed25de6f2bcfa4b6cabe1fd55a1b8f508005de4c1
parent729b56300c441a0d63c7d2013eb5a881211d352b (diff)
Essential install module functionality
-rw-r--r--build/algorithm3
-rw-r--r--build/bin/module.cxx56
-rw-r--r--build/bin/rule.cxx2
-rw-r--r--build/buildfile4
-rw-r--r--build/config/utility58
-rw-r--r--build/config/utility.cxx30
-rw-r--r--build/config/utility.txx24
-rw-r--r--build/context.cxx3
-rw-r--r--build/cxx/compile2
-rw-r--r--build/cxx/compile.cxx12
-rw-r--r--build/cxx/install29
-rw-r--r--build/cxx/install.cxx48
-rw-r--r--build/cxx/link2
-rw-r--r--build/cxx/link.cxx2
-rw-r--r--build/cxx/module.cxx86
-rw-r--r--build/install/module.cxx134
-rw-r--r--build/install/rule35
-rw-r--r--build/install/rule.cxx365
-rw-r--r--build/install/utility36
-rw-r--r--build/operation2
-rw-r--r--build/parser.cxx10
-rw-r--r--build/rule27
-rw-r--r--build/rule.cxx14
-rw-r--r--build/target68
-rw-r--r--build/target.cxx122
-rw-r--r--build/test/rule.cxx16
-rw-r--r--build/variable25
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/install/lib/libtest/build/bootstrap.build5
-rw-r--r--tests/install/lib/libtest/build/export.build6
-rw-r--r--tests/install/lib/libtest/build/root.build11
-rw-r--r--tests/install/lib/libtest/buildfile3
-rw-r--r--tests/install/lib/libtest/doc/buildfile1
-rw-r--r--tests/install/lib/libtest/doc/test.15
-rw-r--r--tests/install/lib/libtest/doc/test.txt1
-rw-r--r--tests/install/lib/libtest/test/buildfile10
-rw-r--r--tests/install/lib/libtest/test/driver.cxx4
-rw-r--r--tests/install/lib/libtest/test/driver.hxx0
-rw-r--r--tests/install/lib/libtest/test/utility.cxx6
-rw-r--r--tests/install/lib/libtest/test/utility.hxx5
-rw-r--r--tests/install/lib/libtest/tests/buildfile2
-rw-r--r--tests/install/lib/libtest/tests/driver.cxx3
-rw-r--r--tests/install/simple/buildfile3
-rw-r--r--tests/install/simple/driver.cxx3
-rw-r--r--tests/install/simple/utility.hxx2
45 files changed, 1086 insertions, 200 deletions
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 <build/diagnostics>
#include <build/config/utility>
+#include <build/install/utility>
#include <build/bin/rule>
#include <build/bin/target>
@@ -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<module>&,
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<obja> ();
tts.insert<objso> ();
tts.insert<obj> ();
@@ -54,7 +55,7 @@ namespace build
// Register rules.
//
{
- auto& rs (base.rules);
+ auto& rs (b.rules);
rs.insert<obj> (default_id, "bin.obj", obj_);
rs.insert<obj> (update_id, "bin.obj", obj_);
@@ -63,6 +64,8 @@ namespace build
rs.insert<lib> (default_id, "bin.lib", lib_);
rs.insert<lib> (update_id, "bin.lib", lib_);
rs.insert<lib> (clean_id, "bin.lib", lib_);
+
+ rs.insert<lib> (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<exe> (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<libso> (b, "lib"); // Install into install.lib.
+
+ install::path<liba> (b, "lib"); // Install into install.lib.
+ install::mode<liba> (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<lib> () || p.is_a<liba> () || p.is_a<libso> ())
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<const T&, bool>
required (scope& root, const char* name, const T& default_value);
+ template <typename T>
+ inline std::pair<const T&, bool>
+ required (scope& root, const std::string& name, const T& default_value)
+ {
+ return required<T> (root, name.c_str (), default_value);
+ }
+
std::pair<const std::string&, bool>
required (scope& root, const char* name, const char* default_value);
@@ -48,45 +55,38 @@ namespace build
return optional<T> (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 <typename T>
void
- append_options (cstrings& args, T& s, const char* var)
- {
- if (auto val = s[var])
- {
- for (const name& n: val.template as<const list_value&> ())
- {
- 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 <typename T>
bool
- find_option (const char* option, T& s, const char* var)
- {
- if (auto val = s[var])
- {
- for (const name& n: val.template as<const list_value&> ())
- {
- 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<const string&, bool> (v.as<const string&> (), 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 <typename T>
+ void
+ append_options (cstrings& args, T& s, const char* var)
+ {
+ if (auto val = s[var])
+ append_options (args, val.template as<const list_value&> (), var);
+ }
+
+ template <typename T>
+ bool
+ find_option (const char* option, T& s, const char* var)
+ {
+ if (auto val = s[var])
+ {
+ for (const name& n: val.template as<const list_value&> ())
+ {
+ 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<alias> ();
tts.insert<dir> ();
tts.insert<fsdir> ();
+ tts.insert<doc> ();
+ tts.insert<man> ();
+ tts.insert<man1> ();
}
// 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 <build/types>
+#include <build/install/rule>
+
+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 <build/cxx/install>
+
+#include <build/bin/target>
+
+#include <build/cxx/target>
+#include <build/cxx/link>
+
+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<exe> () &&
+ (p.is_a<hxx> () || p.is_a<ixx> () || p.is_a<txx> () || p.is_a<h> ()))
+ 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 <build/diagnostics>
#include <build/config/utility>
+#include <build/install/utility>
#include <build/bin/target>
#include <build/cxx/target>
#include <build/cxx/compile>
#include <build/cxx/link>
+#include <build/cxx/install>
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<module>&,
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<h> ();
tts.insert<c> ();
@@ -63,27 +62,31 @@ namespace build
{
using namespace bin;
- auto& rs (base.rules);
+ auto& rs (b.rules);
+
+ rs.insert<obja> (default_id, "cxx.compile", compile::instance);
+ rs.insert<obja> (update_id, "cxx.compile", compile::instance);
+ rs.insert<obja> (clean_id, "cxx.compile", compile::instance);
- rs.insert<obja> (default_id, "cxx.compile", compile_);
- rs.insert<obja> (update_id, "cxx.compile", compile_);
- rs.insert<obja> (clean_id, "cxx.compile", compile_);
+ rs.insert<objso> (default_id, "cxx.compile", compile::instance);
+ rs.insert<objso> (update_id, "cxx.compile", compile::instance);
+ rs.insert<objso> (clean_id, "cxx.compile", compile::instance);
- rs.insert<objso> (default_id, "cxx.compile", compile_);
- rs.insert<objso> (update_id, "cxx.compile", compile_);
- rs.insert<objso> (clean_id, "cxx.compile", compile_);
+ rs.insert<exe> (default_id, "cxx.link", link::instance);
+ rs.insert<exe> (update_id, "cxx.link", link::instance);
+ rs.insert<exe> (clean_id, "cxx.link", link::instance);
- rs.insert<exe> (default_id, "cxx.link", link_);
- rs.insert<exe> (update_id, "cxx.link", link_);
- rs.insert<exe> (clean_id, "cxx.link", link_);
+ rs.insert<liba> (default_id, "cxx.link", link::instance);
+ rs.insert<liba> (update_id, "cxx.link", link::instance);
+ rs.insert<liba> (clean_id, "cxx.link", link::instance);
- rs.insert<liba> (default_id, "cxx.link", link_);
- rs.insert<liba> (update_id, "cxx.link", link_);
- rs.insert<liba> (clean_id, "cxx.link", link_);
+ rs.insert<libso> (default_id, "cxx.link", link::instance);
+ rs.insert<libso> (update_id, "cxx.link", link::instance);
+ rs.insert<libso> (clean_id, "cxx.link", link::instance);
- rs.insert<libso> (default_id, "cxx.link", link_);
- rs.insert<libso> (update_id, "cxx.link", link_);
- rs.insert<libso> (clean_id, "cxx.link", link_);
+ rs.insert<exe> (install_id, "cxx.install", install::instance);
+ rs.insert<liba> (install_id, "cxx.install", install::instance);
+ rs.insert<libso> (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 += <overriding options> # Note: '+='.
//
- if (auto* v = config::optional<list_value> (root, "config.cxx.poptions"))
- base.assign ("cxx.poptions") += *v;
+ if (auto* v = config::optional<list_value> (r, "config.cxx.poptions"))
+ b.assign ("cxx.poptions") += *v;
- if (auto* v = config::optional<list_value> (root, "config.cxx.coptions"))
- base.assign ("cxx.coptions") += *v;
+ if (auto* v = config::optional<list_value> (r, "config.cxx.coptions"))
+ b.assign ("cxx.coptions") += *v;
- if (auto* v = config::optional<list_value> (root, "config.cxx.loptions"))
- base.assign ("cxx.loptions") += *v;
+ if (auto* v = config::optional<list_value> (r, "config.cxx.loptions"))
+ b.assign ("cxx.loptions") += *v;
- if (auto* v = config::optional<list_value> (root, "config.cxx.libs"))
- base.assign ("cxx.libs") += *v;
+ if (auto* v = config::optional<list_value> (r, "config.cxx.libs"))
+ b.assign ("cxx.libs") += *v;
+
+ // Configure "installability" of our target types.
+ //
+ {
+ using build::install::path;
+
+ path<hxx> (b, "include"); // Install into install.include.
+ path<ixx> (b, "include");
+ path<txx> (b, "include");
+ path<h> (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 <build/scope>
#include <build/target>
#include <build/rule>
+#include <build/operation>
#include <build/diagnostics>
#include <build/config/utility>
+#include <build/install/rule>
+#include <build/install/utility>
#include <build/install/operation>
using namespace std;
@@ -20,48 +23,74 @@ namespace build
{
namespace install
{
- // Set install.<name> values based on config.install.<name> values
- // or the defaults.
+ // 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.
//
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<list_value> (r, vn));
-
- vn += ".mode";
- const list_value* mv (config::optional<list_value> (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<list_value> (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<build::module>&,
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<alias> (install_id, "alias", alias_rule::instance);
+
+ // Register our file installer rule.
+ //
+ rs.insert<file> (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<const string&> ());
+
+ 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<doc> (b, "doc"); // Install into install.doc.
+ path<man> (b, "man"); // Install into install.man.
+ path<man> (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 <build/rule>
+#include <build/types>
+#include <build/target>
+#include <build/operation>
+
+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 <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);
+ }
+ }
+}
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 <build/scope>
+
+namespace build
+{
+ namespace install
+ {
+ // Set install path, mode for a target type.
+ //
+ template <typename T>
+ 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 <typename T>
+ 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<recipe_function*> ());
- 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<value_ptr&> (i->second) : nullptr;
+ };
- if (i != vars.end ())
- // @@ Same issue as in variable_map: need ro_value_proxy.
- return value_proxy (&const_cast<value_ptr&> (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<value_ptr&> (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<value_ptr&> (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 <typename T>
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<file>,
&target_extension_fix<file_ext>,
&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<doc>,
+ &target_extension_fix<doc_ext>,
+ &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<man1>,
+ &target_extension_fix<man1_ext>,
+ &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 <build/test/rule>
#include <butl/process>
-#include <butl/fdstream>
#include <build/scope>
#include <build/target>
@@ -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 <typename T>
bool
@@ -218,6 +220,15 @@ namespace build
}
template <>
+ inline const name& value_proxy::
+ as<const name&> () const
+ {
+ const auto& lv (as<const list_value&> ());
+ assert (lv.size () == 1);
+ return lv.front ();
+ }
+
+ template <>
std::string& value_proxy::
as<std::string&> () const;
@@ -348,6 +359,12 @@ namespace build
using variable_pattern_map = std::map<std::string, variable_map>;
using variable_type_map = std::map<std::reference_wrapper<const target_type>,
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 <build/variable.ixx>
diff --git a/tests/.gitignore b/tests/.gitignore
index d22ca88..c8ad1f0 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -1,4 +1,5 @@
driver
+config.build
# Temporary out-of-tree build directories.
#
diff --git a/tests/install/lib/libtest/build/bootstrap.build b/tests/install/lib/libtest/build/bootstrap.build
new file mode 100644
index 0000000..0e83554
--- /dev/null
+++ b/tests/install/lib/libtest/build/bootstrap.build
@@ -0,0 +1,5 @@
+project = install-lib-libtest
+amalgamation = # Disabled.
+using config
+using install
+using test
diff --git a/tests/install/lib/libtest/build/export.build b/tests/install/lib/libtest/build/export.build
new file mode 100644
index 0000000..e8b12b3
--- /dev/null
+++ b/tests/install/lib/libtest/build/export.build
@@ -0,0 +1,6 @@
+$out_root/:
+{
+ include test/
+}
+
+export $out_root/test/lib{test}
diff --git a/tests/install/lib/libtest/build/root.build b/tests/install/lib/libtest/build/root.build
new file mode 100644
index 0000000..59c1e37
--- /dev/null
+++ b/tests/install/lib/libtest/build/root.build
@@ -0,0 +1,11 @@
+using cxx
+
+hxx.ext = hxx
+ixx.ext = ixx
+cxx.ext = cxx
+
+tests/:
+{
+ test.exe = true
+ install = false
+}
diff --git a/tests/install/lib/libtest/buildfile b/tests/install/lib/libtest/buildfile
new file mode 100644
index 0000000..021f51e
--- /dev/null
+++ b/tests/install/lib/libtest/buildfile
@@ -0,0 +1,3 @@
+d = doc/ test/ tests/
+.: $d
+include $d
diff --git a/tests/install/lib/libtest/doc/buildfile b/tests/install/lib/libtest/doc/buildfile
new file mode 100644
index 0000000..117395b
--- /dev/null
+++ b/tests/install/lib/libtest/doc/buildfile
@@ -0,0 +1 @@
+.: doc{test.txt} man1{test}
diff --git a/tests/install/lib/libtest/doc/test.1 b/tests/install/lib/libtest/doc/test.1
new file mode 100644
index 0000000..0a32f2b
--- /dev/null
+++ b/tests/install/lib/libtest/doc/test.1
@@ -0,0 +1,5 @@
+.TH TEST 1
+.SH NAME
+test \- this is a test
+.SH SYNOPSIS
+.B test-driver
diff --git a/tests/install/lib/libtest/doc/test.txt b/tests/install/lib/libtest/doc/test.txt
new file mode 100644
index 0000000..484ba93
--- /dev/null
+++ b/tests/install/lib/libtest/doc/test.txt
@@ -0,0 +1 @@
+This is a test.
diff --git a/tests/install/lib/libtest/test/buildfile b/tests/install/lib/libtest/test/buildfile
new file mode 100644
index 0000000..5e275c6
--- /dev/null
+++ b/tests/install/lib/libtest/test/buildfile
@@ -0,0 +1,10 @@
+cxx.poptions += -I$src_root
+
+install.include = $install.include/test
+
+lib{test}: cxx{utility} hxx{utility}
+lib{test}: cxx.export.poptions = -I$src_root
+
+exe{driver}: cxx{driver} hxx{driver} lib{test}
+
+.: lib{test} exe{driver}
diff --git a/tests/install/lib/libtest/test/driver.cxx b/tests/install/lib/libtest/test/driver.cxx
new file mode 100644
index 0000000..fbb6643
--- /dev/null
+++ b/tests/install/lib/libtest/test/driver.cxx
@@ -0,0 +1,4 @@
+#include <test/utility.hxx>
+#include "driver.hxx"
+
+int main () {return test::f ();}
diff --git a/tests/install/lib/libtest/test/driver.hxx b/tests/install/lib/libtest/test/driver.hxx
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/install/lib/libtest/test/driver.hxx
diff --git a/tests/install/lib/libtest/test/utility.cxx b/tests/install/lib/libtest/test/utility.cxx
new file mode 100644
index 0000000..2016b45
--- /dev/null
+++ b/tests/install/lib/libtest/test/utility.cxx
@@ -0,0 +1,6 @@
+#include <test/utility.hxx>
+
+namespace test
+{
+ int f () {return 0;}
+}
diff --git a/tests/install/lib/libtest/test/utility.hxx b/tests/install/lib/libtest/test/utility.hxx
new file mode 100644
index 0000000..1a9dd72
--- /dev/null
+++ b/tests/install/lib/libtest/test/utility.hxx
@@ -0,0 +1,5 @@
+namespace test
+{
+ int f ();
+}
+
diff --git a/tests/install/lib/libtest/tests/buildfile b/tests/install/lib/libtest/tests/buildfile
new file mode 100644
index 0000000..72d549a
--- /dev/null
+++ b/tests/install/lib/libtest/tests/buildfile
@@ -0,0 +1,2 @@
+exe{driver}: cxx{driver} ../test/lib{test}
+include ../test/
diff --git a/tests/install/lib/libtest/tests/driver.cxx b/tests/install/lib/libtest/tests/driver.cxx
new file mode 100644
index 0000000..9a12f9d
--- /dev/null
+++ b/tests/install/lib/libtest/tests/driver.cxx
@@ -0,0 +1,3 @@
+#include <test/utility.hxx>
+
+int main () {return test::f ();}
diff --git a/tests/install/simple/buildfile b/tests/install/simple/buildfile
index 986f391..cc11491 100644
--- a/tests/install/simple/buildfile
+++ b/tests/install/simple/buildfile
@@ -3,4 +3,5 @@ using cxx
hxx.ext = hxx
cxx.ext = cxx
-exe{driver}: cxx{driver}
+exe{driver}: cxx{driver} hxx{utility}
+exe{driver}: install = sbin
diff --git a/tests/install/simple/driver.cxx b/tests/install/simple/driver.cxx
index a444f13..1b28c94 100644
--- a/tests/install/simple/driver.cxx
+++ b/tests/install/simple/driver.cxx
@@ -1,2 +1,3 @@
-int main () {}
+#include "utility.hxx"
+int main () {return result;}
diff --git a/tests/install/simple/utility.hxx b/tests/install/simple/utility.hxx
new file mode 100644
index 0000000..1b8e18e
--- /dev/null
+++ b/tests/install/simple/utility.hxx
@@ -0,0 +1,2 @@
+const int result = 0;
+