diff options
25 files changed, 542 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. @@ -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> @@ -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>}; } diff --git a/tests/build/semantics/include/buildfile b/tests/build/semantics/include/buildfile new file mode 120000 index 0000000..361dcf4 --- /dev/null +++ b/tests/build/semantics/include/buildfile @@ -0,0 +1 @@ +includer
\ No newline at end of file diff --git a/tests/build/semantics/include/includee1 b/tests/build/semantics/include/includee1 new file mode 100644 index 0000000..70ed3fc --- /dev/null +++ b/tests/build/semantics/include/includee1 @@ -0,0 +1 @@ +print includee1
\ No newline at end of file diff --git a/tests/build/semantics/include/includee2 b/tests/build/semantics/include/includee2 new file mode 100644 index 0000000..762825b --- /dev/null +++ b/tests/build/semantics/include/includee2 @@ -0,0 +1 @@ +print includee2 diff --git a/tests/build/semantics/include/includee4 b/tests/build/semantics/include/includee4 new file mode 100644 index 0000000..4964d81 --- /dev/null +++ b/tests/build/semantics/include/includee4 @@ -0,0 +1 @@ +print includee4 diff --git a/tests/build/semantics/include/includer b/tests/build/semantics/include/includer new file mode 100644 index 0000000..c129e33 --- /dev/null +++ b/tests/build/semantics/include/includer @@ -0,0 +1,9 @@ +include +include includee1 +include includee1 ../include/includee2 # includee1 is skipped +include nested/includee3 +nested/: +{ + include includee5 + include ../includee2 # skipped +} diff --git a/tests/build/semantics/include/nested/includee3 b/tests/build/semantics/include/nested/includee3 new file mode 100644 index 0000000..7196e82 --- /dev/null +++ b/tests/build/semantics/include/nested/includee3 @@ -0,0 +1,2 @@ +print nested/includee3 +include ../includee4 diff --git a/tests/build/semantics/include/nested/includee5 b/tests/build/semantics/include/nested/includee5 new file mode 100644 index 0000000..b4c9c74 --- /dev/null +++ b/tests/build/semantics/include/nested/includee5 @@ -0,0 +1 @@ +print nested/includee5 diff --git a/tests/build/semantics/include/test.std b/tests/build/semantics/include/test.std new file mode 100644 index 0000000..559584c --- /dev/null +++ b/tests/build/semantics/include/test.std @@ -0,0 +1,5 @@ +includee1 +includee2 +nested/includee3 +includee4 +nested/includee5 diff --git a/tests/build/semantics/source/buildfile b/tests/build/semantics/source/buildfile new file mode 120000 index 0000000..b6d1987 --- /dev/null +++ b/tests/build/semantics/source/buildfile @@ -0,0 +1 @@ +sourcer
\ No newline at end of file diff --git a/tests/build/semantics/source/nested/sourcee3 b/tests/build/semantics/source/nested/sourcee3 new file mode 100644 index 0000000..cdf51fb --- /dev/null +++ b/tests/build/semantics/source/nested/sourcee3 @@ -0,0 +1 @@ +print nested/sourcee3
\ No newline at end of file diff --git a/tests/build/semantics/source/sourcee1 b/tests/build/semantics/source/sourcee1 new file mode 100644 index 0000000..683cd1f --- /dev/null +++ b/tests/build/semantics/source/sourcee1 @@ -0,0 +1 @@ +print sourcee1
\ No newline at end of file diff --git a/tests/build/semantics/source/sourcee2 b/tests/build/semantics/source/sourcee2 new file mode 100644 index 0000000..c989838 --- /dev/null +++ b/tests/build/semantics/source/sourcee2 @@ -0,0 +1 @@ +print sourcee2 diff --git a/tests/build/semantics/source/sourcer b/tests/build/semantics/source/sourcer new file mode 100644 index 0000000..e8b9558 --- /dev/null +++ b/tests/build/semantics/source/sourcer @@ -0,0 +1,8 @@ +source +source sourcee1 +source sourcee1 ../source/sourcee2 +nested/: +{ + source sourcee3 + source ../sourcee1 +} diff --git a/tests/build/semantics/source/test.std b/tests/build/semantics/source/test.std new file mode 100644 index 0000000..84d94b7 --- /dev/null +++ b/tests/build/semantics/source/test.std @@ -0,0 +1,5 @@ +sourcee1 +sourcee1 +sourcee2 +nested/sourcee3 +sourcee1 |