diff options
Diffstat (limited to 'libbuild2/diagnostics.hxx')
-rw-r--r-- | libbuild2/diagnostics.hxx | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/libbuild2/diagnostics.hxx b/libbuild2/diagnostics.hxx new file mode 100644 index 0000000..9ad18ff --- /dev/null +++ b/libbuild2/diagnostics.hxx @@ -0,0 +1,436 @@ +// file : libbuild2/diagnostics.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_DIAGNOSTICS_HXX +#define LIBBUILD2_DIAGNOSTICS_HXX + +#include <libbutl/diagnostics.mxx> + +#include <libbuild2/types.hxx> +#include <libbuild2/utility.hxx> + +#include <libbuild2/export.hxx> + +namespace build2 +{ + using butl::diag_record; + + // Throw this exception to terminate the build. The handler should + // assume that the diagnostics has already been issued. + // + class failed: public std::exception {}; + + // Print process commmand line. If the number of elements is specified + // (or the second version is used), then it will print the piped multi- + // process command line, if present. In this case, the expected format + // is as follows: + // + // name1 arg arg ... nullptr + // name2 arg arg ... nullptr + // ... + // nameN arg arg ... nullptr nullptr + // + LIBBUILD2_SYMEXPORT void + print_process (diag_record&, const char* const* args, size_t n = 0); + + LIBBUILD2_SYMEXPORT void + print_process (const char* const* args, size_t n = 0); + + inline void + print_process (diag_record& dr, const cstrings& args, size_t n = 0) + { + print_process (dr, args.data (), n != 0 ? n : args.size ()); + } + + inline void + print_process (const cstrings& args, size_t n = 0) + { + print_process (args.data (), n != 0 ? n : args.size ()); + } + + // Program verbosity level (-v/--verbose). + // + // 0 - disabled + // 1 - high-level information messages + // 2 - essential underlying commands that are being executed + // 3 - all underlying commands that are being executed + // 4 - information helpful to the user (e.g., why a rule did not match) + // 5 - information helpful to the developer + // 6 - even more detailed information + // + // While uint8 is more than enough, use uint16 for the ease of printing. + // + + // Forward-declarated in utility.hxx. + // + // extern uint16_t verb; + // const uint16_t verb_never = 7; + + template <typename F> inline void l1 (const F& f) {if (verb >= 1) f ();} + template <typename F> inline void l2 (const F& f) {if (verb >= 2) f ();} + template <typename F> inline void l3 (const F& f) {if (verb >= 3) f ();} + template <typename F> inline void l4 (const F& f) {if (verb >= 4) f ();} + template <typename F> inline void l5 (const F& f) {if (verb >= 5) f ();} + template <typename F> inline void l6 (const F& f) {if (verb >= 6) f ();} + + // Stream verbosity level. Determined by the diagnostic type (e.g., trace + // always has maximum verbosity) as well as the program verbosity. It is + // used to decide whether to print relative/absolute paths and default + // target extensions. + // + // Currently we have the following program to stream verbosity mapping: + // + // fail/error/warn/info <2:{0,0} 2:{0,1} >2:{1,2} + // trace *:{1,2} + // + // A stream that hasn't been (yet) assigned any verbosity explicitly (e.g., + // ostringstream) defaults to maximum. + // + struct stream_verbosity + { + union + { + struct + { + // 0 - print relative. + // 1 - print absolute. + // + uint16_t path: 1; + + // 0 - don't print. + // 1 - print if specified. + // 2 - print as 'foo.?' if unspecified and 'foo.' if specified as + // "no extension" (empty). + // + uint16_t extension: 2; + }; + uint16_t value_; + }; + + constexpr + stream_verbosity (uint16_t p, uint16_t e): path (p), extension (e) {} + + explicit + stream_verbosity (uint16_t v = 0): value_ (v) {} + }; + + constexpr stream_verbosity stream_verb_max = {1, 2}; + + // Default program to stream verbosity mapping, as outlined above. + // + inline stream_verbosity + stream_verb_map () + { + return + verb < 2 ? stream_verbosity (0, 0) : + verb > 2 ? stream_verbosity (1, 2) : + /* */ stream_verbosity (0, 1); + } + + LIBBUILD2_SYMEXPORT extern const int stream_verb_index; + + inline stream_verbosity + stream_verb (ostream& os) + { + long v (os.iword (stream_verb_index)); + return v == 0 + ? stream_verb_max + : stream_verbosity (static_cast<uint16_t> (v - 1)); + } + + inline void + stream_verb (ostream& os, stream_verbosity v) + { + os.iword (stream_verb_index) = static_cast<long> (v.value_) + 1; + } + + // Progress reporting. + // + using butl::diag_progress; + using butl::diag_progress_lock; + + // Return true if progress is to be shown. The max_verb argument is the + // maximum verbosity level that this type of progress should be shown by + // default. + // + inline bool + show_progress (uint16_t max_verb) + { + return diag_progress_option + ? *diag_progress_option + : stderr_term && verb >= 1 && verb <= max_verb; + } + + // Diagnostic facility, base infrastructure. + // + using butl::diag_stream_lock; + using butl::diag_stream; + using butl::diag_epilogue; + + // Diagnostics stack. Each frame is "applied" to the fail/error/warn/info + // diag record. + // + // Unfortunately most of our use-cases don't fit into the 2-pointer small + // object optimization of std::function. So we have to complicate things + // a bit here. + // + struct LIBBUILD2_SYMEXPORT diag_frame + { + explicit + diag_frame (void (*f) (const diag_frame&, const diag_record&)) + : func_ (f) + { + if (func_ != nullptr) + prev_ = stack (this); + } + + diag_frame (diag_frame&& x) + : func_ (x.func_) + { + if (func_ != nullptr) + { + prev_ = x.prev_; + stack (this); + + x.func_ = nullptr; + } + } + + diag_frame& operator= (diag_frame&&) = delete; + + diag_frame (const diag_frame&) = delete; + diag_frame& operator= (const diag_frame&) = delete; + + ~diag_frame () + { + if (func_ != nullptr ) + stack (prev_); + } + + static void + apply (const diag_record& r) + { + for (const diag_frame* f (stack ()); f != nullptr; f = f->prev_) + f->func_ (*f, r); + } + + // Tip of the stack. + // + static const diag_frame* + stack () noexcept; + + // Set the new and return the previous tip of the stack. + // + static const diag_frame* + stack (const diag_frame*) noexcept; + + struct stack_guard + { + explicit stack_guard (const diag_frame* s): s_ (stack (s)) {} + ~stack_guard () {stack (s_);} + const diag_frame* s_; + }; + + private: + void (*func_) (const diag_frame&, const diag_record&); + const diag_frame* prev_; + }; + + template <typename F> + struct diag_frame_impl: diag_frame + { + explicit + diag_frame_impl (F f): diag_frame (&thunk), func_ (move (f)) {} + + private: + static void + thunk (const diag_frame& f, const diag_record& r) + { + static_cast<const diag_frame_impl&> (f).func_ (r); + } + + const F func_; + }; + + template <typename F> + inline diag_frame_impl<F> + make_diag_frame (F f) + { + return diag_frame_impl<F> (move (f)); + } + + // Diagnostic facility, project specifics. + // + struct LIBBUILD2_SYMEXPORT simple_prologue_base + { + explicit + simple_prologue_base (const char* type, + const char* mod, + const char* name, + stream_verbosity sverb) + : type_ (type), mod_ (mod), name_ (name), sverb_ (sverb) {} + + void + operator() (const diag_record& r) const; + + private: + const char* type_; + const char* mod_; + const char* name_; + const stream_verbosity sverb_; + }; + + struct LIBBUILD2_SYMEXPORT location_prologue_base + { + location_prologue_base (const char* type, + const char* mod, + const char* name, + const location& l, + stream_verbosity sverb) + : type_ (type), mod_ (mod), name_ (name), + loc_ (l), + sverb_ (sverb) {} + + location_prologue_base (const char* type, + const char* mod, + const char* name, + path&& f, + stream_verbosity sverb) + : type_ (type), mod_ (mod), name_ (name), + file_ (move (f)), loc_ (&file_), + sverb_ (sverb) {} + + void + operator() (const diag_record& r) const; + + private: + const char* type_; + const char* mod_; + const char* name_; + const path file_; + const location loc_; + const stream_verbosity sverb_; + }; + + struct basic_mark_base + { + using simple_prologue = butl::diag_prologue<simple_prologue_base>; + using location_prologue = butl::diag_prologue<location_prologue_base>; + + explicit + basic_mark_base (const char* type, + const void* data = nullptr, + diag_epilogue* epilogue = &diag_frame::apply, + stream_verbosity (*sverb) () = &stream_verb_map, + const char* mod = nullptr, + const char* name = nullptr) + : sverb_ (sverb), + type_ (type), mod_ (mod), name_ (name), data_ (data), + epilogue_ (epilogue) {} + + simple_prologue + operator() () const + { + return simple_prologue (epilogue_, type_, mod_, name_, sverb_ ()); + } + + location_prologue + operator() (const location& l) const + { + return location_prologue (epilogue_, type_, mod_, name_, l, sverb_ ()); + } + + // fail (relative (src)) << ... + // + location_prologue + operator() (path&& f) const + { + return location_prologue ( + epilogue_, type_, mod_, name_, move (f), sverb_ ()); + } + + template <typename L> + location_prologue + operator() (const L& l) const + { + return location_prologue ( + epilogue_, type_, mod_, name_, get_location (l, data_), sverb_ ()); + } + + protected: + stream_verbosity (*sverb_) (); + const char* type_; + const char* mod_; + const char* name_; + const void* data_; + diag_epilogue* const epilogue_; + }; + using basic_mark = butl::diag_mark<basic_mark_base>; + + LIBBUILD2_SYMEXPORT extern const basic_mark error; + LIBBUILD2_SYMEXPORT extern const basic_mark warn; + LIBBUILD2_SYMEXPORT extern const basic_mark info; + LIBBUILD2_SYMEXPORT extern const basic_mark text; + + // trace + // + struct trace_mark_base: basic_mark_base + { + explicit + trace_mark_base (const char* name, const void* data = nullptr) + : trace_mark_base (nullptr, name, data) {} + + trace_mark_base (const char* mod, + const char* name, + const void* data = nullptr) + : basic_mark_base ("trace", + data, + nullptr, // No diag stack. + []() {return stream_verb_max;}, + mod, + name) {} + }; + using trace_mark = butl::diag_mark<trace_mark_base>; + using tracer = trace_mark; + + // fail + // + struct fail_mark_base: basic_mark_base + { + explicit + fail_mark_base (const char* type, + const void* data = nullptr) + : basic_mark_base (type, + data, + [](const diag_record& r) + { + diag_frame::apply (r); + r.flush (); + throw failed (); + }, + &stream_verb_map, + nullptr, + nullptr) {} + }; + using fail_mark = butl::diag_mark<fail_mark_base>; + + struct fail_end_base + { + [[noreturn]] void + operator() (const diag_record& r) const + { + // If we just throw then the record's destructor will see an active + // exception and will not flush the record. + // + r.flush (); + throw failed (); + } + }; + using fail_end = butl::diag_noreturn_end<fail_end_base>; + + LIBBUILD2_SYMEXPORT extern const fail_mark fail; + LIBBUILD2_SYMEXPORT extern const fail_end endf; +} + +#endif // LIBBUILD2_DIAGNOSTICS_HXX |