aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2018-07-16 15:21:26 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2018-07-16 15:54:06 +0200
commit4f63afc1177021d6345502892dbd028f5d6db5eb (patch)
tree9f3919d7d6798a82deab6fd9ebfb1d1802b2030f
parent55e858010b9ba53c27475d9ce6f864a84d28fa81 (diff)
Implement in module
Given test.in containing something along these lines: foo = $foo$ Now we can do: using in file{test}: in{test.in} file{test}: foo = FOO The alternative variable substitution symbol can be specified with the in.symbol variable and lax (instead of the default strict) mode with in.substitution. For example: file{test}: in.symbol = '@' file{test}: in.substitution = lax
-rw-r--r--build2/b.cxx13
-rw-r--r--build2/bin/init.cxx2
-rw-r--r--build2/bin/target.cxx4
-rw-r--r--build2/cc/target.cxx4
-rw-r--r--build2/cli/target.cxx4
-rw-r--r--build2/context.cxx1
-rw-r--r--build2/cxx/target.cxx4
-rw-r--r--build2/in/init.cxx110
-rw-r--r--build2/in/init.hxx37
-rw-r--r--build2/in/rule.cxx427
-rw-r--r--build2/in/rule.hxx50
-rw-r--r--build2/in/target.cxx59
-rw-r--r--build2/in/target.hxx46
-rw-r--r--build2/target.cxx44
-rw-r--r--build2/target.hxx27
-rw-r--r--build2/version/init.cxx34
-rw-r--r--build2/version/rule.cxx6
-rw-r--r--tests/in/buildfile5
-rw-r--r--tests/in/testscript96
19 files changed, 861 insertions, 112 deletions
diff --git a/build2/b.cxx b/build2/b.cxx
index cd738f2..977a76e 100644
--- a/build2/b.cxx
+++ b/build2/b.cxx
@@ -45,15 +45,19 @@ using namespace butl;
using namespace std;
#include <build2/config/init.hxx>
+#include <build2/version/init.hxx>
+#include <build2/test/init.hxx>
#include <build2/dist/init.hxx>
+#include <build2/install/init.hxx>
+
+#include <build2/in/init.hxx>
+
#include <build2/bin/init.hxx>
#include <build2/c/init.hxx>
#include <build2/cc/init.hxx>
#include <build2/cxx/init.hxx>
+
#include <build2/cli/init.hxx>
-#include <build2/test/init.hxx>
-#include <build2/install/init.hxx>
-#include <build2/version/init.hxx>
namespace build2
{
@@ -385,6 +389,9 @@ main (int argc, char* argv[])
bm["install"] = mf {&install::boot, &install::init};
bm["version"] = mf {&version::boot, &version::init};
+ bm["in.base"] = mf {nullptr, &in::base_init};
+ bm["in"] = mf {nullptr, &in::init};
+
bm["bin.vars"] = mf {nullptr, &bin::vars_init};
bm["bin.config"] = mf {nullptr, &bin::config_init};
bm["bin"] = mf {nullptr, &bin::init};
diff --git a/build2/bin/init.cxx b/build2/bin/init.cxx
index 13e3289..ea4a0f1 100644
--- a/build2/bin/init.cxx
+++ b/build2/bin/init.cxx
@@ -46,7 +46,7 @@ namespace build2
bool,
const variable_map&)
{
- tracer trace ("cc::core_vars_init");
+ tracer trace ("bin::vars_init");
l5 ([&]{trace << "for " << r.out_path ();});
assert (first);
diff --git a/build2/bin/target.cxx b/build2/bin/target.cxx
index a33875c..0560b8e 100644
--- a/build2/bin/target.cxx
+++ b/build2/bin/target.cxx
@@ -2,10 +2,10 @@
// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
// license : MIT; see accompanying LICENSE file
-#include <build2/context.hxx>
-
#include <build2/bin/target.hxx>
+#include <build2/context.hxx>
+
using namespace std;
namespace build2
diff --git a/build2/cc/target.cxx b/build2/cc/target.cxx
index 8f0d554..7039728 100644
--- a/build2/cc/target.cxx
+++ b/build2/cc/target.cxx
@@ -2,10 +2,10 @@
// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
// license : MIT; see accompanying LICENSE file
-#include <build2/context.hxx>
-
#include <build2/cc/target.hxx>
+#include <build2/context.hxx>
+
using namespace std;
namespace build2
diff --git a/build2/cli/target.cxx b/build2/cli/target.cxx
index 1e3ffae..1a2ea41 100644
--- a/build2/cli/target.cxx
+++ b/build2/cli/target.cxx
@@ -2,10 +2,10 @@
// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
// license : MIT; see accompanying LICENSE file
-#include <build2/context.hxx>
-
#include <build2/cli/target.hxx>
+#include <build2/context.hxx>
+
using namespace std;
using namespace butl;
diff --git a/build2/context.cxx b/build2/context.cxx
index 5a94772..069bc94 100644
--- a/build2/context.cxx
+++ b/build2/context.cxx
@@ -523,7 +523,6 @@ namespace build2
t.insert<alias> ();
t.insert<dir> ();
t.insert<fsdir> ();
- t.insert<in> ();
t.insert<exe> ();
t.insert<doc> ();
t.insert<man> ();
diff --git a/build2/cxx/target.cxx b/build2/cxx/target.cxx
index 20c93b3..3431bc4 100644
--- a/build2/cxx/target.cxx
+++ b/build2/cxx/target.cxx
@@ -2,10 +2,10 @@
// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
// license : MIT; see accompanying LICENSE file
-#include <build2/context.hxx>
-
#include <build2/cxx/target.hxx>
+#include <build2/context.hxx>
+
using namespace std;
namespace build2
diff --git a/build2/in/init.cxx b/build2/in/init.cxx
new file mode 100644
index 0000000..337722a
--- /dev/null
+++ b/build2/in/init.cxx
@@ -0,0 +1,110 @@
+// file : build2/in/init.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/in/init.hxx>
+
+#include <build2/scope.hxx>
+#include <build2/context.hxx>
+#include <build2/variable.hxx>
+#include <build2/diagnostics.hxx>
+
+#include <build2/in/rule.hxx>
+#include <build2/in/target.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ namespace in
+ {
+ static const rule rule_;
+
+ bool
+ base_init (scope& rs,
+ scope&,
+ const location&,
+ unique_ptr<module_base>&,
+ bool first,
+ bool,
+ const variable_map&)
+ {
+ tracer trace ("in::base_init");
+ l5 ([&]{trace << "for " << rs.out_path ();});
+
+ assert (first);
+
+ // Enter variables.
+ //
+ {
+ auto& vp (var_pool.rw (rs));
+
+ // Alternative variable substitution symbol with '$' being the
+ // default.
+ //
+ vp.insert<string> ("in.symbol");
+
+ // Substitution mode. Valid values are 'strict' (default) and 'lax'.
+ // In the strict mode every substitution symbol is expected to start a
+ // substitution with the double symbol (e.g., $$) serving as an escape
+ // sequence.
+ //
+ // In the lax mode a pair of substitution symbols is only treated as a
+ // substitution if what's between them looks like a build2 variable
+ // name (i.e., doesn't contain spaces, etc). Everything else,
+ // including unterminated substitution symbols, is copied as is. Note
+ // also that in this mode the double symbol is not treated as an
+ // escape sequence.
+ //
+ // The lax mode is mostly useful when trying to reuse existing .in
+ // files, for example, from autoconf. Note, however, that the lax mode
+ // is still stricter than the autoconf's semantics which also leaves
+ // unknown substitutions as is.
+ //
+ vp.insert<string> ("in.substitution");
+ }
+
+ // Register target types.
+ //
+ rs.target_types.insert<in> ();
+
+ return true;
+ }
+
+ bool
+ init (scope& rs,
+ scope& bs,
+ const location& loc,
+ unique_ptr<module_base>&,
+ bool,
+ bool,
+ const variable_map&)
+ {
+ tracer trace ("in::init");
+ l5 ([&]{trace << "for " << bs.out_path ();});
+
+ // Load in.base.
+ //
+ if (!cast_false<bool> (rs["in.base.loaded"]))
+ load_module (rs, rs, "in.base", loc);
+
+ // Register rules.
+ //
+ {
+ auto& r (rs.rules);
+
+ // There are rules that are "derived" from this generic in rule in
+ // order to provide extended preprocessing functionality (see the
+ // version module for an example). To make sure they are tried first
+ // we register for path_target, not file, but in rule::match() we only
+ // match if the target is a file. A bit of a hack.
+ //
+ r.insert<path_target> (perform_update_id, "in", rule_);
+ r.insert<path_target> (perform_clean_id, "in", rule_);
+ r.insert<path_target> (configure_update_id, "in", rule_);
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/build2/in/init.hxx b/build2/in/init.hxx
new file mode 100644
index 0000000..82879fb
--- /dev/null
+++ b/build2/in/init.hxx
@@ -0,0 +1,37 @@
+// file : build2/in/init.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_IN_INIT_HXX
+#define BUILD2_IN_INIT_HXX
+
+#include <build2/types.hxx>
+#include <build2/utility.hxx>
+
+#include <build2/module.hxx>
+
+namespace build2
+{
+ namespace in
+ {
+ bool
+ base_init (scope&,
+ scope&,
+ const location&,
+ unique_ptr<module_base>&,
+ bool,
+ bool,
+ const variable_map&);
+
+ bool
+ init (scope&,
+ scope&,
+ const location&,
+ unique_ptr<module_base>&,
+ bool,
+ bool,
+ const variable_map&);
+ }
+}
+
+#endif // BUILD2_IN_INIT_HXX
diff --git a/build2/in/rule.cxx b/build2/in/rule.cxx
new file mode 100644
index 0000000..5988566
--- /dev/null
+++ b/build2/in/rule.cxx
@@ -0,0 +1,427 @@
+// file : build2/in/rule.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/in/rule.hxx>
+
+#include <cstdlib> // strtoull()
+
+#include <build2/depdb.hxx>
+#include <build2/scope.hxx>
+#include <build2/target.hxx>
+#include <build2/function.hxx>
+#include <build2/algorithm.hxx>
+#include <build2/filesystem.hxx>
+#include <build2/diagnostics.hxx>
+
+#include <build2/in/target.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace in
+ {
+ bool rule::
+ match (action a, target& xt, const string&) const
+ {
+ tracer trace ("in::rule::match");
+
+ if (!xt.is_a<file> ()) // See module init() for details.
+ return false;
+
+ file& t (static_cast<file&> (xt));
+
+ bool fi (false); // Found in.
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ if (include (a, t, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
+ fi = fi || p.is_a<in> ();
+ }
+
+ if (!fi)
+ l4 ([&]{trace << "no in file prerequisite for target " << t;});
+
+ return fi;
+ }
+
+ recipe rule::
+ apply (action a, target& xt) const
+ {
+ file& t (static_cast<file&> (xt));
+
+ // Derive the file name.
+ //
+ t.derive_path ();
+
+ // Inject dependency on the output directory.
+ //
+ inject_fsdir (a, t);
+
+ // Match prerequisite members.
+ //
+ match_prerequisite_members (a, t);
+
+ switch (a)
+ {
+ case perform_update_id: return [this] (action a, const target& t)
+ {
+ return perform_update (a, t);
+ };
+ case perform_clean_id: return &perform_clean_depdb; // Standard clean.
+ default: return noop_recipe; // Configure update.
+ }
+ }
+
+ string rule::
+ lookup (const location& l, const target& t, const string& n) const
+ {
+ if (auto x = t[n])
+ {
+ value v (*x);
+
+ // For typed values call string() for conversion.
+ //
+ try
+ {
+ return convert<string> (
+ v.type == nullptr
+ ? move (v)
+ : functions.call (&t.base_scope (),
+ "string",
+ vector_view<value> (&v, 1),
+ l));
+ }
+ catch (const invalid_argument& e)
+ {
+ fail (l) << e <<
+ info << "while substituting '" << n << "'" << endf;
+ }
+ }
+ else
+ fail (l) << "undefined variable '" << n << "'" << endf;
+ }
+
+ optional<string> rule::
+ substitute (const location& l,
+ const target& t,
+ const string& n,
+ bool strict) const
+ {
+ // In the lax mode scan the fragment to make sure it is a variable name
+ // (that is, it can be expanded in a buildfile as just $<name>; see
+ // lexer's variable mode for details).
+ //
+ if (!strict)
+ {
+ for (size_t i (0), e (n.size ()); i != e; )
+ {
+ bool f (i == 0); // First.
+ char c (n[i++]);
+ bool l (i == e); // Last.
+
+ if (c == '_' || (f ? alpha (c) : alnum (c)))
+ continue;
+
+ if (c == '.' && !l)
+ continue;
+
+ return nullopt; // Ignore this substitution.
+ }
+ }
+
+ return lookup (l, t, n);
+ }
+
+ target_state rule::
+ perform_update (action a, const target& xt) const
+ {
+ tracer trace ("in::rule::perform_update");
+
+ const file& t (xt.as<const file&> ());
+ const path& tp (t.path ());
+
+ // Substitution symbol.
+ //
+ char sym ('$');
+ if (const string* s = cast_null<string> (t["in.symbol"]))
+ {
+ if (s->size () == 1)
+ sym = s->front ();
+ else
+ fail << "invalid substitution symbol '" << *s << "'";
+ }
+
+ // Substitution mode.
+ //
+ bool strict (true);
+ if (const string* s = cast_null<string> (t["in.substitution"]))
+ {
+ if (*s == "lax")
+ strict = false;
+ else if (*s != "strict")
+ fail << "invalid substitution mode '" << *s << "'";
+ }
+
+ // Determine if anything needs to be updated.
+ //
+ timestamp mt (t.load_mtime ());
+ auto pr (execute_prerequisites<in> (a, t, mt));
+
+ bool update (!pr.first);
+ target_state ts (update ? target_state::changed : *pr.first);
+
+ const in& i (pr.second);
+ const path& ip (i.path ());
+
+ // We use depdb to track changes to the .in file name, symbol/mode, and
+ // variable values that have been substituted.
+ //
+ depdb dd (tp + ".d");
+
+ // First should come the rule name/version.
+ //
+ if (dd.expect ("in 1") != nullptr)
+ l4 ([&]{trace << "rule mismatch forcing update of " << t;});
+
+ // Then the substitution symbol.
+ //
+ if (dd.expect (string (1, sym)) != nullptr)
+ l4 ([&]{trace << "substitution symbol mismatch forcing update of"
+ << t;});
+
+ // Then the substitution mode.
+ //
+ if (dd.expect (strict ? "strict" : "lax") != nullptr)
+ l4 ([&]{trace << "substitution mode mismatch forcing update of"
+ << t;});
+
+ // Then the .in file.
+ //
+ if (dd.expect (i.path ()) != nullptr)
+ l4 ([&]{trace << "in file mismatch forcing update of " << t;});
+
+ // Update if any mismatch or depdb is newer that the output.
+ //
+ if (dd.writing () || dd.mtime () > mt)
+ update = true;
+
+ // Substituted variable values.
+ //
+ // The plan is to save each substituted variable name and the hash of
+ // its value one entry per line. Plus the line location of its expansion
+ // for diagnostics.
+ //
+ // If update is true (i.e., the .in file has changes), then we simply
+ // overwrite the whole list.
+ //
+ // If update is false, then we need to read each name/hash, query and
+ // hash its current value, and compare. If hashes differ, then we need
+ // to start overwriting from this variable (the prefix of variables
+ // couldn't have changed since the .in file hasn't changed).
+ //
+ // Note that if the .in file substitutes the same variable multiple
+ // times, then we will end up with multiple entries for such a variable.
+ // For now we assume this is ok since this is probably not very common
+ // and it makes the overall logic simpler.
+ //
+ size_t dd_skip (0); // Number of "good" variable lines.
+
+ if (update)
+ {
+ // If we are still reading, mark the next line for overwriting.
+ //
+ if (dd.reading ())
+ {
+ dd.read (); // Read the first variable line, if any.
+ dd.write (); // Mark it for overwriting.
+ }
+ }
+ else
+ {
+ while (dd.more ())
+ {
+ if (string* s = dd.read ())
+ {
+ // The line format is:
+ //
+ // <ln> <name> <hash>
+ //
+ char* e (nullptr);
+ uint64_t ln (strtoull (s->c_str (), &e, 10));
+
+ size_t p1 (*e == ' ' ? e - s->c_str () : string::npos);
+ size_t p2 (s->rfind (' '));
+
+ if (p1 != string::npos && p2 != string::npos && p2 - p1 > 1)
+ {
+ string n (*s, p1 + 1, p2 - p1 - 1);
+ string v (lookup (location (&ip, ln), t, n));
+
+ if (s->compare (p2 + 1, string::npos, sha256 (v).string ()) == 0)
+ {
+ dd_skip++;
+ continue;
+ }
+ else
+ l4 ([&]{trace << n << " variable value mismatch forcing "
+ << "update of " << t;});
+ // Fall through.
+ }
+
+ dd.write (); // Mark this line for overwriting.
+
+ // Fall through.
+ }
+
+ break;
+ }
+ }
+
+ if (dd.writing ()) // Recheck.
+ update = true;
+
+ // If nothing changed, then we are done.
+ //
+ if (!update)
+ {
+ dd.close ();
+ return ts;
+ }
+
+ if (verb >= 2)
+ text << "in " << ip << " >" << tp;
+ else if (verb)
+ text << "in " << ip;
+
+ // Read and process the file, one line at a time.
+ //
+ const char* what;
+ const path* whom;
+ try
+ {
+ what = "open"; whom = &ip;
+ ifdstream ifs (ip, fdopen_mode::in, ifdstream::badbit);
+
+ what = "open"; whom = &tp;
+ ofdstream ofs (tp);
+ auto_rmfile arm (tp);
+
+ string s; // Reuse the buffer.
+ for (size_t ln (1);; ++ln)
+ {
+ what = "read"; whom = &ip;
+ if (!getline (ifs, s))
+ break; // Could not read anything, not even newline.
+
+ // Not tracking column for now (see also depdb above).
+ //
+ const location l (&ip, ln);
+
+ // Scan the line looking for substiutions in the $<name>$ form. In
+ // the strict mode treat $$ as an escape sequence.
+ //
+ for (size_t b (0), n, d; b != (n = s.size ()); b += d)
+ {
+ d = 1;
+
+ if (s[b] != sym)
+ continue;
+
+ // Note that in the lax mode these should still be substitutions:
+ //
+ // @project@@
+ // @@project@
+
+ // Find the other end.
+ //
+ size_t e (b + 1);
+ for (; e != (n = s.size ()); ++e)
+ {
+ if (s[e] == sym)
+ {
+ if (strict && e + 1 != n && s[e + 1] == sym) // Escape.
+ s.erase (e, 1); // Keep one, erase the other.
+ else
+ break;
+ }
+ }
+
+ if (e == n)
+ {
+ if (strict)
+ fail (l) << "unterminated '" << sym << "'" << endf;
+
+ break;
+ }
+
+ if (e - b == 1) // Escape (or just double symbol in the lax mode).
+ {
+ if (strict)
+ s.erase (b, 1); // Keep one, erase the other.
+
+ continue;
+ }
+
+ // We have a (potential, in the lax mode) substition with b
+ // pointing to the opening symbol and e -- to the closing.
+ //
+ string name (s, b + 1, e - b -1);
+ if (optional<string> val = substitute (l, t, name, strict))
+ {
+ // Save in depdb.
+ //
+ if (dd_skip == 0)
+ {
+ // The line format is:
+ //
+ // <ln> <name> <hash>
+ //
+ string s (to_string (ln));
+ s += ' ';
+ s += name;
+ s += ' ';
+ s += sha256 (*val).string ();
+ dd.write (s);
+ }
+ else
+ --dd_skip;
+
+ // Patch the result in and adjust the delta.
+ //
+ s.replace (b, e - b + 1, *val);
+ d = val->size ();
+ }
+ else
+ d = e - b + 1; // Ignore this substitution.
+ }
+
+ what = "write"; whom = &tp;
+ ofs << s << endl;
+ }
+
+ // Close depdb before closing the output file so its mtime is not
+ // newer than of the output.
+ //
+ dd.close ();
+
+ what = "close"; whom = &tp;
+ ofs.close ();
+ arm.cancel ();
+
+ what = "close"; whom = &ip;
+ ifs.close ();
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to " << what << ' ' << *whom << ": " << e;
+ }
+
+ t.mtime (system_clock::now ());
+ return target_state::changed;
+ }
+ }
+}
diff --git a/build2/in/rule.hxx b/build2/in/rule.hxx
new file mode 100644
index 0000000..e588fd9f
--- /dev/null
+++ b/build2/in/rule.hxx
@@ -0,0 +1,50 @@
+// file : build2/in/rule.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_IN_RULE_HXX
+#define BUILD2_IN_RULE_HXX
+
+#include <build2/types.hxx>
+#include <build2/utility.hxx>
+
+#include <build2/rule.hxx>
+
+namespace build2
+{
+ namespace in
+ {
+ // Preprocess an .in file.
+ //
+ class rule: public build2::rule
+ {
+ public:
+ rule () {}
+
+ virtual bool
+ match (action, target&, const string&) const override;
+
+ virtual recipe
+ apply (action, target&) const override;
+
+ // Perform variable lookup.
+ //
+ virtual string
+ lookup (const location&, const target&, const string& name) const;
+
+ // Perform variable substitution. Return nullopt if it should be
+ // ignored.
+ //
+ virtual optional<string>
+ substitute (const location&,
+ const target&,
+ const string& name,
+ bool strict) const;
+
+ target_state
+ perform_update (action, const target&) const;
+ };
+ }
+}
+
+#endif // BUILD2_IN_RULE_HXX
diff --git a/build2/in/target.cxx b/build2/in/target.cxx
new file mode 100644
index 0000000..ecf975f
--- /dev/null
+++ b/build2/in/target.cxx
@@ -0,0 +1,59 @@
+// file : build2/in/target.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/in/target.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ namespace in
+ {
+ // in
+ //
+ static const target*
+ in_search (const target& xt, const prerequisite_key& cpk)
+ {
+ // If we have no extension then derive it from our target. Then delegate
+ // to file_search().
+ //
+ prerequisite_key pk (cpk);
+ optional<string>& e (pk.tk.ext);
+
+ if (!e)
+ {
+ if (const file* t = xt.is_a<file> ())
+ {
+ const string& te (t->derive_extension ());
+ e = te + (te.empty () ? "" : ".") + "in";
+ }
+ else
+ fail << "prerequisite " << pk << " for a non-file target " << xt;
+ }
+
+ return file_search (xt, pk);
+ }
+
+ static bool
+ in_pattern (const target_type&, const scope&, string&, bool)
+ {
+ fail << "pattern in in{} prerequisite" << endf;
+ }
+
+ extern const char in_ext_def[] = ""; // No extension by default.
+
+ const target_type in::static_type
+ {
+ "in",
+ &file::static_type,
+ &target_factory<in>,
+ &target_extension_fix<in_ext_def>,
+ nullptr, /* default_extension */ // Taken care of by search.
+ &in_pattern,
+ &target_print_1_ext_verb, // Same as file.
+ &in_search,
+ false
+ };
+ }
+}
diff --git a/build2/in/target.hxx b/build2/in/target.hxx
new file mode 100644
index 0000000..90def2d
--- /dev/null
+++ b/build2/in/target.hxx
@@ -0,0 +1,46 @@
+// file : build2/in/target.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_IN_TARGET_HXX
+#define BUILD2_IN_TARGET_HXX
+
+#include <build2/types.hxx>
+#include <build2/utility.hxx>
+
+#include <build2/target.hxx>
+
+namespace build2
+{
+ namespace in
+ {
+ // This is the venerable .in ("input") file that needs some kind of
+ // preprocessing.
+ //
+ // One interesting aspect of this target type is that the prerequisite
+ // search is target-dependent. Consider:
+ //
+ // hxx{version}: in{version.hxx} // version.hxx.in -> version.hxx
+ //
+ // Having to specify the header extension explicitly is inelegant. Instead
+ // what we really want to write is this:
+ //
+ // hxx{version}: in{version}
+ //
+ // But how do we know that in{version} means version.hxx.in? That's where
+ // the target-dependent search comes in: we take into account the target
+ // we are a prerequisite of.
+ //
+ class in: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+ }
+}
+
+#endif // BUILD2_IN_TARGET_HXX
diff --git a/build2/target.cxx b/build2/target.cxx
index 973eed8..f2e3462 100644
--- a/build2/target.cxx
+++ b/build2/target.cxx
@@ -942,50 +942,6 @@ namespace build2
false
};
- // in
- //
- static const target*
- in_search (const target& xt, const prerequisite_key& cpk)
- {
- // If we have no extension then derive it from our target. Then delegate
- // to file_search().
- //
- prerequisite_key pk (cpk);
- optional<string>& e (pk.tk.ext);
-
- if (!e)
- {
- if (const file* t = xt.is_a<file> ())
- {
- const string& te (t->derive_extension ());
- e = te + (te.empty () ? "" : ".") + "in";
- }
- else
- fail << "prerequisite " << pk << " for a non-file target " << xt;
- }
-
- return file_search (xt, pk);
- }
-
- static bool
- in_pattern (const target_type&, const scope&, string&, bool)
- {
- fail << "pattern in in{} prerequisite" << endf;
- }
-
- const target_type in::static_type
- {
- "in",
- &file::static_type,
- &target_factory<in>,
- &target_extension_fix<file_ext_def>, // No extension by default.
- nullptr, /* default_extension */ // Should be taken care if by search.
- &in_pattern,
- &target_print_1_ext_verb, // Same as file.
- &in_search,
- false
- };
-
const target_type doc::static_type
{
"doc",
diff --git a/build2/target.hxx b/build2/target.hxx
index 515a082..aad5331 100644
--- a/build2/target.hxx
+++ b/build2/target.hxx
@@ -1607,33 +1607,6 @@ namespace build2
virtual const target_type& dynamic_type () const {return static_type;}
};
- // This is the venerable .in ("input") file that needs some kind of
- // preprocessing.
- //
- // One interesting aspect of this target type is that the prerequisite
- // search is target-dependent. Consider:
- //
- // hxx{version}: in{version.hxx} // version.hxx.in -> version.hxx
- //
- // Having to specify the header extension explicitly is inelegant. Instead
- // what we really want to write is this:
- //
- // hxx{version}: in{version}
- //
- // But how do we know that in{version} means version.hxx.in? That's where
- // the target-dependent search comes in: we take into account the target
- // we are a prerequisite of.
- //
- class in: public file
- {
- public:
- using file::file;
-
- public:
- static const target_type static_type;
- virtual const target_type& dynamic_type () const {return static_type;}
- };
-
// Common documentation file targets.
//
class doc: public file
diff --git a/build2/version/init.cxx b/build2/version/init.cxx
index dbbb7d9..d2df772 100644
--- a/build2/version/init.cxx
+++ b/build2/version/init.cxx
@@ -254,6 +254,11 @@ namespace build2
if (!first)
fail (l) << "multiple version module initializations";
+ // Load in.base (in.* varibales, in{} target type).
+ //
+ if (!cast_false<bool> (rs["in.base.loaded"]))
+ load_module (rs, rs, "in.base", l);
+
module& m (static_cast<module&> (*mod));
const standard_version& v (m.version);
@@ -293,33 +298,8 @@ namespace build2
// Enter variables.
//
{
- auto& vp (var_pool.rw (rs));
-
- // @@ Note: these should be moved to the 'in' module once we have it.
- //
-
- // Alternative variable substitution symbol.
- //
- m.in_symbol = &vp.insert<string> ("in.symbol");
-
- // Substitution mode. Valid values are 'strict' (default) and 'lax'.
- // In the strict mode every substitution symbol is expected to start a
- // substitution with the double symbol (e.g., $$) serving as an
- // escape sequence.
- //
- // In the lax mode a pair of substitution symbols is only treated as a
- // substitution if what's between them looks like a build2 variable
- // name (i.e., doesn't contain spaces, etc). Everything else,
- // including unterminated substitution symbols is copied as is. Note
- // also that in this mode the double symbol is not treated as an
- // escape sequence.
- //
- // The lax mode is mostly useful when trying to reuse existing .in
- // files, for example from autoconf. Note, however, that the lax mode
- // is still stricter than the autoconf's semantics which also leaves
- // unknown substitutions as is.
- //
- m.in_substitution = &vp.insert<string> ("in.substitution");
+ m.in_symbol = var_pool.find ("in.symbol"); // in.base
+ m.in_substitution = var_pool.find ("in.substitution"); // in.base
}
// Register rules.
diff --git a/build2/version/rule.cxx b/build2/version/rule.cxx
index 91b8c75..0250a15 100644
--- a/build2/version/rule.cxx
+++ b/build2/version/rule.cxx
@@ -13,6 +13,8 @@
#include <build2/filesystem.hxx>
#include <build2/diagnostics.hxx>
+#include <build2/in/target.hxx>
+
#include <build2/version/module.hxx>
#include <build2/version/utility.hxx>
@@ -23,6 +25,8 @@ namespace build2
{
namespace version
{
+ using in::in;
+
// Return true if this prerequisite is a project's manifest file. To be
// sure we would need to search it into target but that we can't do in
// match().
@@ -403,7 +407,7 @@ namespace build2
};
if (verb >= 2)
- text << "ver -o " << tp << ' ' << ip;
+ text << "ver " << ip << " >" << tp;
else if (verb)
text << "ver " << ip;
diff --git a/tests/in/buildfile b/tests/in/buildfile
new file mode 100644
index 0000000..54d3b90
--- /dev/null
+++ b/tests/in/buildfile
@@ -0,0 +1,5 @@
+# file : tests/in/buildfile
+# copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+./: testscript $b
diff --git a/tests/in/testscript b/tests/in/testscript
new file mode 100644
index 0000000..4cf4ebf
--- /dev/null
+++ b/tests/in/testscript
@@ -0,0 +1,96 @@
+# file : tests/in/testscript
+# copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+crosstest = false
+test.arguments =
+
+.include ../common.test
+
++cat <<EOI >=build/root.build
+using in
+EOI
+
+: basic
+:
+cat <<EOI >=test.in;
+ foo = $foo$
+ EOI
+cat <<EOI >=buildfile;
+ file{test}: in{test}
+ file{test}: foo = FOO
+ EOI
+$* <<<buildfile;
+cat test >>EOO;
+ foo = FOO
+ EOO
+$* clean <<<buildfile
+
+: lax
+:
+cat <<EOI >=test.in;
+ $10
+ $foo bar$ baz
+ EOI
+$* <<EOI &test &test.d;
+ file{test}: in{test}
+ file{test}: in.substitution = lax
+ EOI
+cat test >>EOO
+ $10
+ $foo bar$ baz
+ EOO
+
+: rebuild
+:
+cat <'$foo$ $bar$' >=test.in;
+$* <<EOI &test &test.d;
+ foo = foo
+ bar = bar
+ file{test}: in{test}
+ EOI
+cat test >'foo bar';
+$* <<EOI;
+ foo = FOO
+ bar = bar
+ file{test}: in{test}
+ EOI
+cat test >'FOO bar';
+$* <<EOI;
+ foo = FOO
+ bar = BAR
+ file{test}: in{test}
+ EOI
+cat test >'FOO BAR';
+cat <'$fox$ $baz$' >=test.in;
+$* <<EOI;
+ fox = fox
+ baz = baz
+ file{test}: in{test}
+ EOI
+cat test >'fox baz';
+mv test.in tst.in;
+$* <<EOI;
+ fox = FOX
+ baz = BAZ
+ file{test}: in{tst.in}
+ EOI
+cat test >'FOX BAZ'
+
+: rebuild-diag
+:
+cat <<EOI >=test.in;
+ foo = $foo$
+ bar = $bar$
+ EOI
+$* <<EOI &test &test.d;
+ foo = foo
+ bar = bar
+ file{test}: in{test}
+ EOI
+$* <<EOI 2>>EOE != 0
+ foo = foo
+ file{test}: in{test}
+ EOI
+ test.in:2: error: undefined variable 'bar'
+ EOE