From ab4421747146aa7995f0cfb1a639c9121c82c915 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 8 Jan 2015 13:27:15 +0200 Subject: Implement tracing support Also use to-relative path translation in diagnostics. --- build/algorithm.cxx | 9 ++- build/b.cxx | 27 +++++-- build/buildfile | 6 +- build/context | 21 +++++ build/context.cxx | 36 +++++++++ build/cxx/rule.cxx | 56 ++++++++++--- build/diagnostics | 14 ++++ build/diagnostics.cxx | 20 +++++ build/parser.cxx | 1 + build/path | 19 +++++ build/path.txx | 30 ++++++- build/pre.build | 0 build/prerequisite.cxx | 33 +++++++- build/target.cxx | 16 +++- build/trace | 71 ++++++++++++++++ build/trace.cxx | 12 +++ tests/build/lexer/buildfile | 8 ++ tests/build/path/driver.cxx | 193 ++++++++++++++++++++++++++++++++++++++++++++ 18 files changed, 544 insertions(+), 28 deletions(-) create mode 100644 build/diagnostics.cxx create mode 100644 build/pre.build create mode 100644 build/trace create mode 100644 build/trace.cxx create mode 100644 tests/build/lexer/buildfile create mode 100644 tests/build/path/driver.cxx diff --git a/build/algorithm.cxx b/build/algorithm.cxx index fac0bf6..419bfaf 100644 --- a/build/algorithm.cxx +++ b/build/algorithm.cxx @@ -23,6 +23,8 @@ namespace build target* search (prerequisite& p) { + tracer tr ("search"); + assert (p.target == nullptr); //@@ TODO for now we just default to the directory scope. @@ -36,7 +38,7 @@ namespace build d.normalize (); } - //@@ TODO would be nice to first check if this target is + //@@ TODO: would be nice to first check if this target is // already in the set before allocating a new instance. // Find or insert. @@ -45,8 +47,9 @@ namespace build targets.emplace ( unique_ptr (p.type.factory (p.name, move (d))))); - //if (r.second) - // cout << "new target for prerequsite " << p << " " << d << endl; + trace (4, [&]{ + tr << (r.second ? "new" : "existing") << " target " << **r.first + << " for prerequsite " << p;}); return (p.target = r.first->get ()); } diff --git a/build/b.cxx b/build/b.cxx index a5123f1..ad68e21 100644 --- a/build/b.cxx +++ b/build/b.cxx @@ -110,6 +110,8 @@ namespace build void dump () { + cout << endl; + for (const auto& pt: targets) { target& t (*pt); @@ -123,6 +125,8 @@ namespace build cout << endl; } + + cout << endl; } } @@ -138,10 +142,16 @@ using namespace build; int main (int argc, char* argv[]) { + tracer tr ("main"); + // Initialize time conversion data that is used by localtime_r(). // tzset (); + // Trace verbosity. + // + verb = 5; + // Register target types. // target_types.insert (file::static_type); @@ -200,12 +210,15 @@ main (int argc, char* argv[]) else out_root = out_base.directory (src_base.leaf (src_root)); - cerr << "work dir: " << work << endl; - cerr << "home dir: " << home << endl; - cerr << "out_base: " << out_base << endl; - cerr << "src_base: " << src_base << endl; - cerr << "out_root: " << out_root << endl; - cerr << "src_root: " << src_root << endl; + if (verb >= 4) + { + tr << "work dir: " << work.string (); + tr << "home dir: " << home.string (); + tr << "out_base: " << out_base.string (); + tr << "src_base: " << src_base.string (); + tr << "out_root: " << out_root.string (); + tr << "src_root: " << src_root.string (); + } // Parse buildfile. // @@ -267,7 +280,7 @@ main (int argc, char* argv[]) if (!match_recursive (d)) return 1; // Diagnostics has already been issued. - //dump (); + dump (); switch (update (d)) { diff --git a/build/buildfile b/build/buildfile index 88ea4d6..3afe19b 100644 --- a/build/buildfile +++ b/build/buildfile @@ -1,16 +1,18 @@ -exe{b1}: obj{b algorithm scope parser lexer target prerequisite rule \ - native context cxx/target cxx/rule process timestamp path} +exe{b1}: obj{b algorithm scope parser lexer trace target prerequisite rule \ + native context diagnostics cxx/target cxx/rule process timestamp path} obj{b}: cxx{b} obj{algorithm}: cxx{algorithm} obj{scope}: cxx{scope} obj{parser}: cxx{parser} obj{lexer}: cxx{lexer} +obj{trace}: cxx{trace} obj{target}: cxx{target} obj{prerequisite}: cxx{prerequisite} obj{rule}: cxx{rule} obj{native}: cxx{native} obj{context}: cxx{context} +obj{diagnostics}: cxx{diagnostics} obj{cxx/target}: cxx{cxx/target} obj{cxx/rule}: cxx{cxx/rule} obj{process}: cxx{process} diff --git a/build/context b/build/context index 4c86b14..a369ade 100644 --- a/build/context +++ b/build/context @@ -5,6 +5,9 @@ #ifndef BUILD_CONTEXT #define BUILD_CONTEXT +#include +#include + #include namespace build @@ -17,6 +20,24 @@ namespace build extern path src_base; extern path out_base; + + // If possible, translate an absolute, normalized path into relative to + // the work directory. + // + path + translate (const path&); + + // In addition to calling translate() above, this function also uses + // shorter notations such as ~/. + // + std::string + diagnostic_string (const path&); + + inline std::ostream& + operator<< (std::ostream& os, const path& p) + { + return os << diagnostic_string (p); + } } #endif // BUILD_CONTEXT diff --git a/build/context.cxx b/build/context.cxx index ce80324..fc4ec1c 100644 --- a/build/context.cxx +++ b/build/context.cxx @@ -2,6 +2,8 @@ // copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC // license : MIT; see accompanying LICENSE file +#include + #include using namespace std; @@ -16,4 +18,38 @@ namespace build path src_base; path out_base; + + path + translate (const path& p) + { + if (p.sub (work)) + return p.leaf (work); + + // If work is a sub-path of {src,out}_root and this path is also a + // sub-bath of it, then use '..' to form a relative path. + // + if (work.sub (src_root) && p.sub (src_root) || + work.sub (out_root) && p.sub (out_root)) // @@ cache + return p.relative (work); + + return p; + } + + std::string + diagnostic_string (const path& p) + { + if (p.absolute ()) + { + path rp (translate (p)); + +#ifndef _WIN32 + if (rp.absolute () && rp.sub (home)) + return "~/" + rp.leaf (home).string (); +#endif + + return rp.string (); + } + + return p.string (); + } } diff --git a/build/cxx/rule.cxx b/build/cxx/rule.cxx index 3cab2d4..1d2a2c4 100644 --- a/build/cxx/rule.cxx +++ b/build/cxx/rule.cxx @@ -17,6 +17,7 @@ #include #include #include +#include using namespace std; @@ -29,6 +30,8 @@ namespace build recipe compile:: match (target& t) const { + tracer tr ("cxx::compile::match"); + // @@ TODO: // // - check prerequisites: single source file @@ -59,7 +62,7 @@ namespace build if (sp == nullptr) { - cout << "no source file" << endl; + trace (3, [&]{tr << "no c++ source file for target " << t;}); return recipe (); } @@ -137,16 +140,27 @@ namespace build void compile:: inject_prerequisites (obj& o, const cxx& s, scope& ds) const { + tracer tr ("cxx::compile::inject_prerequisites"); + + // We are using absolute source file path in order to get + // absolute paths in the result. + // const char* args[] = { "g++-4.9", "-std=c++14", - "-I..", - "-MM", //@@ TMP -M + "-I", src_root.string ().c_str (), + "-MM", //@@ -M "-MG", // Treat missing headers as generated. "-MQ", "*", // Quoted target (older version can't handle empty name). s.path ().string ().c_str (), nullptr}; + if (verb >= 2) + print_process (args); + + if (verb >= 5) + tr << "target: " << o; + try { process pr (args, false, false, true); @@ -186,6 +200,9 @@ namespace build path file (next (l, pos)); file.normalize (); + if (verb >= 5) + tr << "prerequisite path: " << file.string (); + // If there is no extension (e.g., standard C++ headers), // then assume it is a header. Otherwise, let the standard // mechanism derive the type from the extension. @@ -282,17 +299,26 @@ namespace build if (!u) return target_state::uptodate; + // Translate paths to relative (to working directory) ones. This + // results in easier to read diagnostics. + // + path ro (translate (o.path ())); + path rs (translate (s->path ())); + const char* args[] = { "g++-4.9", "-std=c++14", "-g", - "-I..", + "-I", src_root.string ().c_str (), "-c", - "-o", o.path ().string ().c_str (), - s->path ().string ().c_str (), + "-o", ro.string ().c_str (), + rs.string ().c_str (), nullptr}; - cerr << "c++ " << *s << endl; + if (verb >= 1) + print_process (args); + else + cerr << "c++ " << *s << endl; try { @@ -407,19 +433,29 @@ namespace build if (!u) return target_state::uptodate; + // Translate paths to relative (to working directory) ones. This + // results in easier to read diagnostics. + // + path re (translate (e.path ())); + vector ro; + vector args {"g++-4.9", "-std=c++14", "-g", "-o"}; - args.push_back (e.path ().string ().c_str ()); + args.push_back (re.string ().c_str ()); for (const prerequisite& p: t.prerequisites) { const obj& o (dynamic_cast (*p.target)); - args.push_back (o.path ().string ().c_str ()); + ro.push_back (translate (o.path ())); + args.push_back (ro.back ().string ().c_str ()); } args.push_back (nullptr); - cerr << "ld " << e << endl; + if (verb >= 1) + print_process (args); + else + cerr << "ld " << e << endl; try { diff --git a/build/diagnostics b/build/diagnostics index c8fb169..98f481f 100644 --- a/build/diagnostics +++ b/build/diagnostics @@ -6,9 +6,12 @@ #define BUILD_DIAGNOSTICS #include +#include #include #include +#include + namespace build { // Throw this exception to terminate the build. The handler should @@ -16,6 +19,17 @@ namespace build // class error: public std::exception {}; + // Print process commmand line. + // + void + print_process (const char* const* args); + + inline void + print_process (const std::vector& args) + { + print_process (args.data ()); + } + // Call a function if there is an exception. // template diff --git a/build/diagnostics.cxx b/build/diagnostics.cxx new file mode 100644 index 0000000..f213707 --- /dev/null +++ b/build/diagnostics.cxx @@ -0,0 +1,20 @@ +// file : build/diagnostics.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include + +#include + +using namespace std; + +namespace build +{ + void + print_process (const char* const* args) + { + for (const char* const* p (args); *p != nullptr; p++) + cerr << (p != args ? " " : "") << *p; + cerr << endl; + } +} diff --git a/build/parser.cxx b/build/parser.cxx index e300201..289e571 100644 --- a/build/parser.cxx +++ b/build/parser.cxx @@ -243,6 +243,7 @@ namespace build if (p.relative ()) p = prev.path () / p; + p.normalize (); scope_ = &scopes[p]; // A directory scope can contain anything that a top level can. diff --git a/build/path b/build/path index ccc1b85..92832a1 100644 --- a/build/path +++ b/build/path @@ -218,6 +218,16 @@ namespace build bool root () const; + // Return true if *this is a sub-path of the specified path (i.e., + // the specified path is a prefix). Expects both paths to be + // normalized. + // + bool + sub (const basic_path& p) const + { + return path_.compare (0, p.path_.size (), p.path_) == 0; + } + public: // Return the path without the directory part. // @@ -249,6 +259,13 @@ namespace build basic_path base () const; + // Return a path relative to the specified path that is equivalent + // to *this. Throws invalid_path if a relative path cannot be derived + // (e.g., paths are on different drives on Windows). + // + basic_path + relative (basic_path) const; + public: // Normalize the path. This includes collapsing the '.' and '..' // directories if possible, collapsing multiple directory @@ -334,12 +351,14 @@ namespace build string_type path_; }; + /* template inline std::basic_ostream& operator<< (std::basic_ostream& os, basic_path const& p) { return os << p.string (); } + */ } #include diff --git a/build/path.txx b/build/path.txx index 6a01547..3e952d8 100644 --- a/build/path.txx +++ b/build/path.txx @@ -117,8 +117,12 @@ namespace build if (m < n || path_.compare (0, n, d.path_) != 0) throw invalid_basic_path (path_); - if (n != m) - n++; // Skip the directory separator. + if (n != m +#ifndef _WIN32 + && !d.root () +#endif + ) + n++; // Skip the directory separator (unless it is POSIX root). return basic_path (path_.c_str () + n, m - n); } @@ -144,6 +148,28 @@ namespace build } template + basic_path basic_path:: + relative (basic_path d) const + { + basic_path r; + + for (;; d = d.directory ()) + { + if (sub (d)) + break; + + r /= path (".."); + + // Roots of the paths do not match. + // + if (d.root ()) + throw invalid_basic_path (path_); + } + + return r / leaf (d); + } + + template basic_path& basic_path:: normalize () { diff --git a/build/pre.build b/build/pre.build new file mode 100644 index 0000000..e69de29 diff --git a/build/prerequisite.cxx b/build/prerequisite.cxx index 8153a6c..c43827d 100644 --- a/build/prerequisite.cxx +++ b/build/prerequisite.cxx @@ -6,7 +6,9 @@ #include +#include #include // target_type +#include using namespace std; @@ -15,9 +17,36 @@ namespace build ostream& operator<< (ostream& os, const prerequisite& p) { - // @@ TODO: need to come up with a relative (to current) path. + if (p.target != nullptr) + os << *p.target; + else + { + os << p.type.name << '{'; - return os << p.type.name << '{' << p.name << '}'; + // Print scope unless the directory is absolute. + // + if (!p.directory.absolute ()) + { + string s (diagnostic_string (p.scope.path ())); + + if (!s.empty ()) + os << s << path::traits::directory_separator << ": "; + } + + // Print directory. + // + if (!p.directory.empty ()) + { + string s (diagnostic_string (p.directory)); + + if (!s.empty ()) + os << s << path::traits::directory_separator; + } + + os << p.name << '}'; + } + + return os; } bool diff --git a/build/target.cxx b/build/target.cxx index 8314ba4..4dffe7f 100644 --- a/build/target.cxx +++ b/build/target.cxx @@ -6,6 +6,8 @@ #include +#include + using namespace std; namespace build @@ -15,9 +17,19 @@ namespace build ostream& operator<< (ostream& os, const target& t) { - // @@ TODO: need to come up with a relative (to current) path. + os << t.type ().name << '{'; + + if (!t.directory.empty ()) + { + string s (diagnostic_string (t.directory)); + + if (!s.empty ()) + os << s << path::traits::directory_separator; + } + + os << t.name << '}'; - return os << t.type ().name << '{' << t.name << '}'; + return os; } target_set targets; diff --git a/build/trace b/build/trace new file mode 100644 index 0000000..f1c0567 --- /dev/null +++ b/build/trace @@ -0,0 +1,71 @@ +// file : build/trace -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_TRACE +#define BUILD_TRACE + +#include +#include + +namespace build +{ + // 1 - command lines to update explicit targets (e.g., .o) + // 2 - command lines to update implicit targets (e.g., .d) + // 3 - things didn't work out (e.g., rule did not match) + // 4 - additional information + // 5 - more additional information + // + extern std::uint8_t verb; + + struct tracer + { + explicit + tracer (const char* name): name_ (name) {} + + struct record + { + ~record () {if (!empty_) std::cerr << std::endl;} + + template + std::ostream& + operator<< (const T& x) const + { + return std::cerr << x; + } + + // Movable-only type. + // + explicit record (bool e = true): empty_ (e) {} + record (record&& r) {empty_ = r.empty_; r.empty_ = true;} + record& operator= (record&& r) {empty_ = r.empty_; r.empty_ = true;} + + record (const record&) = delete; + record& operator= (const record&) = delete; + + private: + mutable bool empty_; + }; + + template + record + operator<< (const T& x) const + { + std::cerr << "trace: " << name_ << ": " << x; + return record (false); + } + + private: + const char* name_; + }; + + template + inline void + trace (std::uint8_t level, const F& f) + { + if (verb >= level) + f (); + } +} + +#endif // BUILD_TRACE diff --git a/build/trace.cxx b/build/trace.cxx new file mode 100644 index 0000000..a7e6566 --- /dev/null +++ b/build/trace.cxx @@ -0,0 +1,12 @@ +// file : build/trace.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include + +using namespace std; + +namespace build +{ + uint8_t verb; +} diff --git a/tests/build/lexer/buildfile b/tests/build/lexer/buildfile new file mode 100644 index 0000000..06e1f75 --- /dev/null +++ b/tests/build/lexer/buildfile @@ -0,0 +1,8 @@ +exe{driver}: obj{driver ../../../build/lexer} + +obj{driver}: cxx{driver} + +../../../build/: +{ + obj{lexer}: cxx{lexer} +} diff --git a/tests/build/path/driver.cxx b/tests/build/path/driver.cxx new file mode 100644 index 0000000..739146b --- /dev/null +++ b/tests/build/path/driver.cxx @@ -0,0 +1,193 @@ +// file : tests/build/path/driver.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include +#include + +#include + +using namespace std; +using namespace build; + +int +main () +{ + assert (path ("/").string () == "/"); + assert (path ("//").string () == "/"); + assert (path ("/tmp/foo/").string () == "/tmp/foo"); +#ifdef _WIN32 + assert (path ("\\\\").string () == "\\"); + assert (path ("/\\").string () == "/"); + assert (path ("C:").string () == "C:"); + assert (path ("C:\\").string () == "C:"); + assert (path ("C:\\tmp\\foo\\").string () == "C:\\tmp\\foo"); +#endif + + // abslote/relative/root + // +#ifndef _WIN32 + assert (path ("/").root ()); + assert (path ("//").root ()); + assert (path ("/").absolute ()); + assert (path ("/foo/bar").absolute ()); + assert (path ("bar/baz").relative ()); +#else + assert (path ("C:").root ()); + assert (path ("C:\\").root ()); + assert (path ("C:\\").absolute ()); + assert (path ("C:\\foo\\bar").absolute ()); + assert (path ("bar\\baz").relative ()); +#endif + + + // leaf + // +#ifndef _WIN32 + assert (path ("/").leaf ().string () == ""); + assert (path ("/tmp").leaf ().string () == "tmp"); + assert (path ("//tmp").leaf ().string () == "tmp"); +#else + assert (path ("C:").leaf ().string () == "C:"); + assert (path ("C:\\tmp").leaf ().string () == "tmp"); + assert (path ("C:\\\\tmp").leaf ().string () == "tmp"); +#endif + + // directory + // +#ifndef _WIN32 + assert (path ("/").directory ().string () == ""); + assert (path ("/tmp").directory ().string () == "/"); + assert (path ("//tmp").directory ().string () == "/"); +#else + assert (path ("C:").directory ().string () == ""); + assert (path ("C:\\tmp").directory ().string () == "C:"); + assert (path ("C:\\\\tmp").directory ().string () == "C:"); +#endif + + // base + // + assert (path ("/").base ().string () == "/"); + assert (path ("/foo.txt").base ().string () == "/foo"); + assert (path (".txt").base ().string () == ".txt"); + assert (path ("/.txt").base ().string () == "/.txt"); + assert (path ("foo.txt.orig").base ().string () == "foo.txt"); +#ifdef _WIN32 + assert (path ("C:").base ().string () == "C:"); + assert (path ("C:\\foo.txt").base ().string () == "C:\\foo"); +#endif + + // operator/ + // +#ifndef _WIN32 + assert ((path ("/") / path ("tmp")).string () == "/tmp"); + assert ((path ("foo") / path ("bar")).string () == "foo/bar"); +#else + assert ((path ("\\") / path ("tmp")).string () == "\\tmp"); + assert ((path ("C:\\") / path ("tmp")).string () == "C:\\tmp"); + assert ((path ("foo") / path ("bar")).string () == "foo\\bar"); +#endif + + // normalize + // +#ifndef _WIN32 + assert (path ("../foo").normalize ().string () == "../foo"); + assert (path ("..///foo").normalize ().string () == "../foo"); + assert (path ("../../foo").normalize ().string () == "../../foo"); + assert (path (".././foo").normalize ().string () == "../foo"); + assert (path (".").normalize ().string () == ""); + assert (path ("./..").normalize ().string () == ".."); + assert (path ("../.").normalize ().string () == ".."); + assert (path ("foo/./..").normalize ().string () == ""); + assert (path ("/foo/./..").normalize ().string () == "/"); + assert (path ("./foo").normalize ().string () == "foo"); +#else + assert (path ("../foo").normalize ().string () == "..\\foo"); + assert (path ("..///foo").normalize ().string () == "..\\foo"); + assert (path ("..\\../foo").normalize ().string () == "..\\..\\foo"); + assert (path (".././foo").normalize ().string () == "..\\foo"); + assert (path (".").normalize ().string () == ""); + assert (path ("./..").normalize ().string () == ".."); + assert (path ("../.").normalize ().string () == ".."); + assert (path ("foo/./..").normalize ().string () == ""); + assert (path ("C:/foo/./..").normalize ().string () == "C:"); + assert (path ("./foo").normalize ().string () == "foo"); + + assert (path ("C:").normalize ().string () == "C:"); + assert (path ("C:\\Foo12//Bar").normalize ().string () == "C:\\Foo12\\Bar"); +#endif + + // comparison + // + assert (path ("./foo") == path("./foo")); + assert (path ("./boo") < path("./foo")); +#ifdef _WIN32 + assert (path (".\\foo") == path("./FoO")); + assert (path (".\\boo") < path(".\\Foo")); +#endif + + // posix_string + // + assert (path ("foo/bar/../baz").posix_string () == "foo/bar/../baz"); +#ifdef _WIN32 + assert (path ("foo\\bar\\..\\baz").posix_string () == "foo/bar/../baz"); + try + { + path ("c:\\foo\\bar\\..\\baz").posix_string (); + assert (false); + } + catch (const invalid_path&) {} +#endif + + // sub + // + assert (path ("foo").sub (path ("foo"))); + assert (path ("foo/bar").sub (path ("foo/bar"))); + assert (path ("foo/bar").sub (path ("foo"))); + assert (!path ("foo/bar").sub (path ("bar"))); + assert (path ("/foo/bar").sub (path ("/foo"))); + assert (path ("/foo/bar/baz").sub (path ("/foo/bar"))); + assert (!path ("/foo/bar/baz").sub (path ("/foo/baz"))); +#ifdef _WIN32 + assert (path ("c:").sub (path ("c:"))); + assert (!path ("c:").sub (path ("d:"))); + assert (path ("c:\\foo").sub (path ("c:"))); +#else + assert (path ("/foo/bar/baz").sub (path ("/"))); +#endif + + // relative + // + assert (path ("foo").relative (path ("foo")) == path ()); + assert (path ("foo/bar").relative (path ("foo/bar")) == path ()); + assert (path ("foo/bar/baz").relative (path ("foo/bar")) == path ("baz")); + assert (path ("foo/bar/baz").relative (path ("foo/bar/buz")). + posix_string () == "../baz"); + assert (path ("foo/bar/baz").relative (path ("foo/biz/baz")). + posix_string () == "../../bar/baz"); + assert (path ("foo/bar/baz").relative (path ("fox/bar/baz")). + posix_string () == "../../../foo/bar/baz"); +#ifdef _WIN32 + assert (path ("c:\\foo\\bar").relative (path ("c:\\fox\\bar")) == + path ("..\\..\\foo\\bar")); + try + { + path ("c:\\foo\\bar").relative (path ("d:\\fox\\bar")); + assert (false); + } + catch (const invalid_path&) {} +#else + assert (path ("/foo/bar/baz").relative (path ("/")) == + path ("foo/bar/baz")); +#endif + + /* + path p ("../foo"); + p.complete (); + + cerr << path::current () << endl; + cerr << p << endl; + p.normalize (); + cerr << p << endl; + */ +} -- cgit v1.1