aboutsummaryrefslogtreecommitdiff
path: root/build
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-02-19 16:10:03 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-02-19 16:10:03 +0200
commit8bd89cfca333e58f6990d7d168649dfc79878f31 (patch)
tree730bb9eec4fc5ddd086ed8465c4d7c0030db0d6b /build
parentb0524a0b18eec9d5e5c3f6ce30b6cecdd02a6306 (diff)
Add support for sourcing/including buildfiles, print, dir{} alias
Diffstat (limited to 'build')
-rw-r--r--build/b.cxx10
-rw-r--r--build/context9
-rw-r--r--build/context.cxx15
-rw-r--r--build/diagnostics23
-rw-r--r--build/parser38
-rw-r--r--build/parser.cxx434
-rw-r--r--build/path13
-rw-r--r--build/rule17
-rw-r--r--build/rule.cxx36
-rw-r--r--build/target14
-rw-r--r--build/target.cxx16
11 files changed, 504 insertions, 121 deletions
diff --git a/build/b.cxx b/build/b.cxx
index 3d19131..c9322a0 100644
--- a/build/b.cxx
+++ b/build/b.cxx
@@ -154,6 +154,7 @@ main (int argc, char* argv[])
// Register target types.
//
target_types.insert (file::static_type);
+ target_types.insert (dir::static_type);
target_types.insert (exe::static_type);
target_types.insert (obj::static_type);
@@ -225,7 +226,7 @@ main (int argc, char* argv[])
//
path bf ("buildfile");
- ifstream ifs (bf.string ().c_str ());
+ ifstream ifs (bf.string ());
if (!ifs.is_open ())
fail << "unable to open " << bf;
@@ -251,8 +252,11 @@ main (int argc, char* argv[])
cxx::compile cxx_compile;
rules[typeid (obj)].emplace ("cxx.gnu.compile", cxx_compile);
- default_path_rule path_exists;
- rules[typeid (path_target)].emplace ("", path_exists);
+ dir_rule dir_r;
+ rules[typeid (dir)].emplace ("", dir_r);
+
+ path_rule path_r;
+ rules[typeid (path_target)].emplace ("", path_r);
// Build.
//
diff --git a/build/context b/build/context
index a369ade..05f0094 100644
--- a/build/context
+++ b/build/context
@@ -21,6 +21,15 @@ namespace build
extern path src_base;
extern path out_base;
+ // Return the src/out directory corresponding to the given out/src. The
+ // passed directory should be a sub-directory of out/src_root.
+ //
+ path
+ src_out (const path&);
+
+ path
+ out_src (const path&);
+
// If possible, translate an absolute, normalized path into relative to
// the work directory.
//
diff --git a/build/context.cxx b/build/context.cxx
index 2eb76c0..4d1d1e4 100644
--- a/build/context.cxx
+++ b/build/context.cxx
@@ -5,6 +5,7 @@
#include <build/context>
#include <ostream>
+#include <cassert>
using namespace std;
@@ -20,6 +21,20 @@ namespace build
path out_base;
path
+ src_out (const path& o)
+ {
+ assert (o.sub (out_root));
+ return src_root / o.leaf (out_root);
+ }
+
+ path
+ out_src (const path& s)
+ {
+ assert (s.sub (src_root));
+ return out_root / s.leaf (src_root);
+ }
+
+ path
translate (const path& p)
{
if (p.sub (work))
diff --git a/build/diagnostics b/build/diagnostics
index f85d118..7e56fa3 100644
--- a/build/diagnostics
+++ b/build/diagnostics
@@ -238,15 +238,17 @@ namespace build
private:
const char* type_;
const char* name_;
- const location& loc_;
+ const location loc_;
};
typedef diag_prologue<location_prologue_base> location_prologue;
struct basic_mark_base
{
explicit
- basic_mark_base (const char* type, const char* name = nullptr)
- : type_ (type), name_ (name) {}
+ basic_mark_base (const char* type,
+ const char* name = nullptr,
+ const void* data = nullptr)
+ : type_ (type), name_ (name), data_ (data) {}
simple_prologue
operator() () const
@@ -264,12 +266,13 @@ namespace build
location_prologue
operator() (const L& l) const
{
- return location_prologue (type_, name_, get_location (l));
+ return location_prologue (type_, name_, get_location (l, data_));
}
private:
const char* type_;
const char* name_;
+ const void* data_;
};
typedef diag_mark<basic_mark_base> basic_mark;
@@ -281,7 +284,8 @@ namespace build
struct trace_mark_base: basic_mark_base
{
explicit
- trace_mark_base (const char* name): basic_mark_base ("trace", name) {}
+ trace_mark_base (const char* name, const void* data = nullptr)
+ : basic_mark_base ("trace", name, data) {}
};
typedef diag_mark<trace_mark_base> trace_mark;
@@ -290,6 +294,9 @@ namespace build
template <typename E>
struct fail_mark_base
{
+ explicit
+ fail_mark_base (const void* data = nullptr): data_ (data) {}
+
simple_prologue
operator() () const
{
@@ -306,11 +313,15 @@ namespace build
location_prologue
operator() (const L& l) const
{
- return location_prologue (&epilogue, "error", nullptr, get_location (l));
+ return location_prologue (
+ &epilogue, "error", nullptr, get_location (l, data_));
}
static void
epilogue (const diag_record&) {throw E ();}
+
+ private:
+ const void* data_;
};
template <typename E>
diff --git a/build/parser b/build/parser
index 7515908..b62e8e7 100644
--- a/build/parser
+++ b/build/parser
@@ -8,22 +8,23 @@
#include <string>
#include <vector>
#include <iosfwd>
-#include <utility> // std::move
+#include <utility> // std::move
+#include <unordered_set>
#include <build/path>
+#include <build/token>
#include <build/diagnostics>
namespace build
{
class scope;
-
- class token;
- enum class token_type;
class lexer;
class parser
{
public:
+ parser (): fail (&path_) {}
+
// Issues diagnostics and throws failed in case of an error.
//
void
@@ -47,6 +48,15 @@ namespace build
void
parse_clause (token&, token_type&);
+ void
+ parse_print (token&, token_type&);
+
+ void
+ parse_source (token&, token_type&);
+
+ void
+ parse_include (token&, token_type&);
+
names
parse_names (token& t, token_type& tt)
{
@@ -65,23 +75,23 @@ namespace build
token_type
next (token&, token_type&);
+ token_type
+ peek ();
+
// Diagnostics.
//
private:
- struct fail_mark_base: build::fail_mark_base<failed>
- {
- location_prologue
- operator() (const token&) const;
-
- const path* path_;
- };
- typedef diag_mark<fail_mark_base> fail_mark;
+ const fail_mark<failed> fail;
private:
- fail_mark fail;
-
+ const std::string* path_; // Path processed by diagnostic_string().
lexer* lexer_;
scope* scope_;
+
+ token peek_ {token_type::eos, 0, 0};
+ bool peeked_ {false};
+
+ std::unordered_set<path> include_;
};
}
diff --git a/build/parser.cxx b/build/parser.cxx
index 696e2a3..1cb0684 100644
--- a/build/parser.cxx
+++ b/build/parser.cxx
@@ -5,6 +5,9 @@
#include <build/parser>
#include <memory> // unique_ptr
+#include <fstream>
+#include <utility> // move()
+#include <iostream>
#include <build/token>
#include <build/lexer>
@@ -13,6 +16,7 @@
#include <build/target>
#include <build/prerequisite>
#include <build/diagnostics>
+#include <build/context>
using namespace std;
@@ -23,14 +27,37 @@ namespace build
ostream&
operator<< (ostream&, const token&);
+ static location
+ get_location (const token&, const void*);
+
typedef token_type type;
+ // Given a target or prerequisite name, figure out its type, taking
+ // into account extensions, trailing '/', or anything else that might
+ // be relevant.
+ //
+ static const char*
+ find_target_type (const string& n, const string* e)
+ {
+ // Empty name or a name ending with a directory separator
+ // signifies a directory.
+ //
+ if (n.empty () || path::traits::is_separator (n.back ()))
+ return "dir";
+
+ //@@ TODO: derive type from extension.
+ //
+ return "file";
+ }
+
void parser::
parse (istream& is, const path& p, scope& s)
{
+ string ds (diagnostic_string (p));
+ path_ = &ds;
+
lexer l (is, p.string ());
lexer_ = &l;
- fail.path_ = &p;
scope_ = &s;
token t (type::eos, 0, 0);
@@ -46,51 +73,141 @@ namespace build
void parser::
parse_clause (token& t, token_type& tt)
{
- tracer trace ("parser::parse_clause");
+ tracer trace ("parser::parse_clause", &path_);
while (tt != type::eos)
{
// We always start with one or more names.
//
- if (tt != type::name && tt != type::lcbrace)
+ if (tt != type::name && tt != type::lcbrace && tt != type::colon)
break; // Something else. Let our caller handle that.
- names tns (parse_names (t, tt));
+ // See if this is one of the keywords.
+ //
+ if (tt == type::name)
+ {
+ const string& n (t.name ());
+
+ if (n == "print")
+ {
+ // @@ Is this the only place where it is valid? Probably also
+ // in var namespace.
+ //
+ next (t, tt);
+ parse_print (t, tt);
+ continue;
+ }
+ else if (n == "source")
+ {
+ next (t, tt);
+ parse_source (t, tt);
+ continue;
+ }
+ else if (n == "include")
+ {
+ next (t, tt);
+ parse_include (t, tt);
+ continue;
+ }
+ }
+
+ // ': foo' is equvalent to '{}: foo' and to 'dir{}: foo'.
+ //
+ names tns (tt != type::colon
+ ? parse_names (t, tt)
+ : names({name_type ("", path (), "")}));
if (tt == type::colon)
{
next (t, tt);
- // Dependency declaration.
- //
- if (tt == type::name || tt == type::lcbrace)
+ if (tt == type::newline)
{
- names pns (parse_names (t, tt));
-
- // Prepare the prerequisite list.
+ // See if this is a directory/target scope.
//
- target::prerequisites_type ps;
- ps.reserve (pns.size ());
-
- for (auto& pn: pns)
+ if (peek () == type::lcbrace)
{
- // Resolve prerequisite type.
+ next (t, tt);
+
+ // Should be on its own line.
//
- //@@ TODO: derive type from extension, factor to common function
+ if (next (t, tt) != type::newline)
+ fail (t) << "expected newline after {";
+
+ // See if this is a directory or target scope. Different
+ // things can appear inside depending on which one it is.
//
- const char* tt (pn.type.empty () ? "file" : pn.type.c_str ());
+ bool dir (false);
+ for (const auto& n: tns)
+ {
+ if (n.type.empty () && n.name.back () == '/')
+ {
+ if (tns.size () != 1)
+ {
+ // @@ TODO: point to name.
+ //
+ fail (t) << "multiple names in directory scope";
+ }
- auto i (target_types.find (tt));
+ dir = true;
+ }
+ }
- if (i == target_types.end ())
+ next (t, tt);
+
+ if (dir)
{
- //@@ TODO name (or better yet, type) location
+ scope& prev (*scope_);
+ path p (tns[0].name);
- fail (t) << "unknown prerequisite type " << tt;
+ if (p.relative ())
+ p = prev.path () / p;
+
+ p.normalize ();
+ scope_ = &scopes[p];
+
+ // A directory scope can contain anything that a top level can.
+ //
+ parse_clause (t, tt);
+
+ scope_ = &prev;
+ }
+ else
+ {
+ // @@ TODO: target scope.
}
- const target_type& ti (i->second);
+ if (tt != type::rcbrace)
+ fail (t) << "expected '}' instead of " << t;
+
+ // Should be on its own line.
+ //
+ if (next (t, tt) == type::newline)
+ next (t, tt);
+ else if (tt != type::eos)
+ fail (t) << "expected newline after }";
+ continue;
+ }
+
+ // If this is not a scope, then it is a target without any
+ // prerequisites.
+ //
+ }
+
+ // Dependency declaration.
+ //
+ if (tt == type::name || tt == type::lcbrace || tt == type::newline)
+ {
+ names pns (tt != type::newline ? parse_names (t, tt) : names ());
+
+ // Prepare the prerequisite list.
+ //
+ target::prerequisites_type ps;
+ ps.reserve (pns.size ());
+
+ for (auto& pn: pns)
+ {
// We need to split the path into its directory part (if any)
// the name part, and the extension (if any). We cannot assume
// the name part is a valid filesystem name so we will have
@@ -111,6 +228,16 @@ namespace build
n.assign (pn.name, i + 1, string::npos);
}
+ // Handle '.' and '..'.
+ //
+ if (n == ".")
+ n.clear ();
+ else if (n == "..")
+ {
+ d /= path (n);
+ n.clear ();
+ }
+
d.normalize ();
// Extract extension.
@@ -124,6 +251,23 @@ namespace build
}
}
+ // Resolve prerequisite type.
+ //
+ const char* tt (pn.type.empty ()
+ ? find_target_type (n, e)
+ : pn.type.c_str ());
+
+ auto i (target_types.find (tt));
+
+ if (i == target_types.end ())
+ {
+ //@@ TODO name (or better yet, type) location
+
+ fail (t) << "unknown prerequisite type " << tt;
+ }
+
+ const target_type& ti (i->second);
+
// Find or insert.
//
prerequisite& p (
@@ -152,6 +296,16 @@ namespace build
n.assign (tn.name, i + 1, string::npos);
}
+ // Handle '.' and '..'.
+ //
+ if (n == ".")
+ n.clear ();
+ else if (n == "..")
+ {
+ d /= path (n);
+ n.clear ();
+ }
+
if (d.empty ())
d = scope_->path (); // Already normalized.
else
@@ -175,9 +329,9 @@ namespace build
// Resolve target type.
//
- //@@ TODO: derive type from extension
- //
- const char* tt (tn.type.empty () ? "file" : tn.type.c_str ());
+ const char* tt (tn.type.empty ()
+ ? find_target_type (n, e)
+ : tn.type.c_str ());
auto i (target_types.find (tt));
@@ -210,82 +364,155 @@ namespace build
continue;
}
- if (tt == type::newline)
- {
- // See if we have a directory/target scope.
- //
- if (next (t, tt) == type::lcbrace)
- {
- // Should be on its own line.
- //
- if (next (t, tt) != type::newline)
- fail (t) << "expected newline after {";
+ if (tt == type::eos)
+ continue;
- // See if this is a directory or target scope. Different
- // things can appear inside depending on which one it is.
- //
- bool dir (false);
- for (const auto& n: tns)
- {
- if (n.type.empty () && n.name.back () == '/')
- {
- if (tns.size () != 1)
- {
- // @@ TODO: point to name.
- //
- fail (t) << "multiple names in directory scope";
- }
+ fail (t) << "expected newline instead of " << t;
+ }
- dir = true;
- }
- }
+ fail (t) << "unexpected " << t;
+ }
+ }
- next (t, tt);
+ void parser::
+ parse_source (token& t, token_type& tt)
+ {
+ tracer trace ("parser::parse_source", &path_);
- if (dir)
- {
- scope& prev (*scope_);
- path p (tns[0].name);
+ // The rest should be a list of paths to buildfiles.
+ //
+ for (; tt != type::newline && tt != type::eos; next (t, tt))
+ {
+ if (tt != type::name)
+ fail (t) << "expected buildfile to source instead of " << t;
- if (p.relative ())
- p = prev.path () / p;
+ path p (t.name ());
- p.normalize ();
- scope_ = &scopes[p];
+ // If the path is relative then use the src directory corresponding
+ // to the current directory scope.
+ //
+ if (p.relative ())
+ p = src_out (scope_->path ()) / p;
- // A directory scope can contain anything that a top level can.
- //
- parse_clause (t, tt);
+ ifstream ifs (p.string ());
- scope_ = &prev;
- }
- else
- {
- // @@ TODO: target scope.
- }
+ if (!ifs.is_open ())
+ fail (t) << "unable to open " << p;
- if (tt != type::rcbrace)
- fail (t) << "expected '}' instead of " << t;
+ ifs.exceptions (ifstream::failbit | ifstream::badbit);
- // Should be on its own line.
- //
- if (next (t, tt) == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline after }";
- }
+ level4 ([&]{trace (t) << "entering " << p;});
- continue;
- }
+ string ds (diagnostic_string (p));
+ const string* op (path_);
+ path_ = &ds;
- if (tt == type::eos)
- continue;
+ lexer l (ifs, p.string ());
+ lexer* ol (lexer_);
+ lexer_ = &l;
+
+ next (t, tt);
+ parse_clause (t, tt);
+
+ if (tt != type::eos)
+ fail (t) << "unexpected " << t;
+
+ level4 ([&]{trace (t) << "leaving " << p;});
+
+ lexer_ = ol;
+ path_ = op;
+ }
+
+ if (tt != type::eos)
+ next (t, tt); // Swallow newline.
+ }
+
+ void parser::
+ parse_include (token& t, token_type& tt)
+ {
+ tracer trace ("parser::parse_include", &path_);
+
+ // The rest should be a list of paths to buildfiles.
+ //
+ for (; tt != type::newline && tt != type::eos; next (t, tt))
+ {
+ if (tt != type::name)
+ fail (t) << "expected buildfile to include instead of " << t;
+
+ path p (t.name ());
+ bool in_out (false);
+
+ if (p.absolute ())
+ {
+ p.normalize ();
+
+ // Make sure the path is in this project. Include is only meant
+ // to be used for intra-project inclusion.
+ //
+ if (!p.sub (src_root) && !(in_out = p.sub (out_root)))
+ fail (t) << "out of project include " << p;
+ }
+ else
+ {
+ // Use the src directory corresponding to the current directory scope.
+ //
+ p = src_out (scope_->path ()) / p;
+ p.normalize ();
+ }
- fail (t) << "expected newline insetad of " << t;
+ if (!include_.insert (p).second)
+ {
+ level4 ([&]{trace (t) << "skipping already included " << p;});
+ continue;
}
- fail (t) << "unexpected " << t;
+ ifstream ifs (p.string ());
+
+ if (!ifs.is_open ())
+ fail (t) << "unable to open " << p;
+
+ ifs.exceptions (ifstream::failbit | ifstream::badbit);
+
+ level4 ([&]{trace (t) << "entering " << p;});
+
+ string ds (diagnostic_string (p));
+ const string* op (path_);
+ path_ = &ds;
+
+ lexer l (ifs, p.string ());
+ lexer* ol (lexer_);
+ lexer_ = &l;
+
+ scope* os (scope_);
+ scope_ = &scopes[(in_out ? p : out_src (p)).directory ()];
+
+ next (t, tt);
+ parse_clause (t, tt);
+
+ if (tt != type::eos)
+ fail (t) << "unexpected " << t;
+
+ level4 ([&]{trace (t) << "leaving " << p;});
+
+ scope_ = os;
+ lexer_ = ol;
+ path_ = op;
}
+
+ if (tt != type::eos)
+ next (t, tt); // Swallow newline.
+ }
+
+ void parser::
+ parse_print (token& t, token_type& tt)
+ {
+ for (; tt != type::newline && tt != type::eos; next (t, tt))
+ cout << t;
+
+ cout << endl;
+
+ if (tt != type::eos)
+ next (t, tt); // Swallow newline.
}
void parser::
@@ -372,23 +599,48 @@ namespace build
if (!first)
break;
- fail (t) << "expected name instead of " << t;
+ if (tt == type::rcbrace) // Empty name, e.g., dir{}.
+ {
+ ns.emplace_back ((tp != nullptr ? *tp : string ()),
+ (dp != nullptr ? *dp : path ()),
+ "");
+ break;
+ }
+ else
+ fail (t) << "expected name instead of " << t;
}
}
token_type parser::
next (token& t, token_type& tt)
{
- t = lexer_->next ();
+ if (!peeked_)
+ t = lexer_->next ();
+ else
+ {
+ t = move (peek_);
+ peeked_ = false;
+ }
+
tt = t.type ();
return tt;
}
- location_prologue parser::fail_mark_base::
- operator() (const token& t) const
+ token_type parser::
+ peek ()
+ {
+ if (!peeked_)
+ peek_ = lexer_->next ();
+
+ return peek_.type ();
+ }
+
+ static location
+ get_location (const token& t, const void* data)
{
- return build::fail_mark_base<failed>::operator() (
- location (path_->string ().c_str (), t.line (), t.column ()));
+ assert (data != nullptr);
+ const string& p (**static_cast<const string* const*> (data));
+ return location (p.c_str (), t.line (), t.column ());
}
// Output the token type and value in a format suitable for diagnostics.
diff --git a/build/path b/build/path
index ee7937b..8eb560b 100644
--- a/build/path
+++ b/build/path
@@ -385,6 +385,19 @@ namespace build
*/
}
+namespace std
+{
+ template <typename C>
+ struct hash<build::basic_path<C>>: hash<basic_string<C>>
+ {
+ size_t
+ operator() (const build::basic_path<C>& p) const
+ {
+ return hash<basic_string<C>>::operator() (p.string ());
+ }
+ };
+}
+
#include <build/path.ixx>
#include <build/path.txx>
diff --git a/build/rule b/build/rule
index 325204f..378ed9b 100644
--- a/build/rule
+++ b/build/rule
@@ -31,7 +31,22 @@ namespace build
extern rule_map rules;
- class default_path_rule: public rule
+ // Fallback rule that check that the path exists.
+ //
+ class path_rule: public rule
+ {
+ public:
+ virtual void*
+ match (target&, const std::string& hint) const;
+
+ virtual recipe
+ select (target&, void*) const;
+
+ static target_state
+ update (target&);
+ };
+
+ class dir_rule: public rule
{
public:
virtual void*
diff --git a/build/rule.cxx b/build/rule.cxx
index d40eebf..e145ea8 100644
--- a/build/rule.cxx
+++ b/build/rule.cxx
@@ -14,9 +14,9 @@ namespace build
{
rule_map rules;
- // default_path_rule
+ // path_rule
//
- void* default_path_rule::
+ void* path_rule::
match (target& t, const string&) const
{
// @@ TODO:
@@ -51,13 +51,13 @@ namespace build
return pt.mtime () != timestamp_nonexistent ? &t : nullptr;
}
- recipe default_path_rule::
+ recipe path_rule::
select (target&, void*) const
{
return &update;
}
- target_state default_path_rule::
+ target_state path_rule::
update (target& t)
{
// Make sure the target is not older than any of its prerequisites.
@@ -99,4 +99,32 @@ namespace build
return target_state::uptodate;
}
+
+ // dir_rule
+ //
+ void* dir_rule::
+ match (target& t, const string&) const
+ {
+ return &t;
+ }
+
+ recipe dir_rule::
+ select (target&, void*) const
+ {
+ return &update;
+ }
+
+ target_state dir_rule::
+ update (target& t)
+ {
+ for (const prerequisite& p: t.prerequisites)
+ {
+ auto ts (p.target->state ());
+
+ if (ts != target_state::uptodate)
+ return ts; // updated or failed
+ }
+
+ return target_state::uptodate;
+ }
}
diff --git a/build/target b/build/target
index f57e8cc..b84931c 100644
--- a/build/target
+++ b/build/target
@@ -209,6 +209,20 @@ namespace build
virtual const target_type& type () const {return static_type;}
static const target_type static_type;
};
+
+ // Directory alias/action target. Note that it is not mtime-based.
+ // Rather it is meant to represent a group of targets. For actual
+ // filesystem directory (creation), see fsdir.
+ //
+ class dir: public target
+ {
+ public:
+ using target::target;
+
+ public:
+ virtual const target_type& type () const {return static_type;}
+ static const target_type static_type;
+ };
}
#endif // BUILD_TARGET
diff --git a/build/target.cxx b/build/target.cxx
index 3852bc4..31a7f59 100644
--- a/build/target.cxx
+++ b/build/target.cxx
@@ -11,6 +11,11 @@ using namespace std;
namespace build
{
+ // target_type
+ //
+ const target_type*
+ resolve_target_type (const std::string name);
+
// target
//
ostream&
@@ -23,7 +28,12 @@ namespace build
string s (diagnostic_string (t.directory));
if (!s.empty ())
- os << s << path::traits::directory_separator;
+ {
+ os << s;
+
+ if (!t.name.empty ())
+ os << path::traits::directory_separator;
+ }
}
os << t.name;
@@ -79,7 +89,6 @@ namespace build
}
target_set targets;
-
target* default_target = nullptr;
target_type_map target_types;
@@ -103,4 +112,7 @@ namespace build
const target_type file::static_type {
typeid (file), "file", &path_target::static_type, &target_factory<file>};
+
+ const target_type dir::static_type {
+ typeid (dir), "dir", &target::static_type, &target_factory<dir>};
}