From a7efabf301f23364ac2335c80c5e1e712bc43204 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 10 Nov 2016 00:26:54 +0300 Subject: Add cat, false and true builtins --- build2/buildfile | 3 + build2/test/script/builtin | 21 +-- build2/test/script/builtin.cxx | 293 +++++++++++++++++++++++++++++++++++++---- build2/test/script/parser | 6 + build2/test/script/parser.cxx | 19 +-- build2/test/script/runner.cxx | 21 ++- build2/types | 3 + build2/utility | 16 ++- 8 files changed, 334 insertions(+), 48 deletions(-) (limited to 'build2') diff --git a/build2/buildfile b/build2/buildfile index 86b67a9..cd04ce7 100644 --- a/build2/buildfile +++ b/build2/buildfile @@ -88,6 +88,9 @@ test/script/{hxx cxx}{ token } \ # obj{b context}: cxx.poptions += -DBUILD2_HOST_TRIPLET=\"$cxx.target\" +if ($cxx.target.class != "windows") + cxx.libs += -lpthread + # Generated options parser. # {hxx ixx cxx}{b-options}: cli{b} diff --git a/build2/test/script/builtin b/build2/test/script/builtin index e3c16b3..bd5fe50 100644 --- a/build2/test/script/builtin +++ b/build2/test/script/builtin @@ -5,11 +5,11 @@ #ifndef BUILD2_TEST_SCRIPT_BUILTIN #define BUILD2_TEST_SCRIPT_BUILTIN +#include + #include #include -#include - namespace build2 { namespace test @@ -18,21 +18,26 @@ namespace build2 { class scope; + // Start builtin command. Throw system_error on failure. + // // Note that unlike argc/argv, our args don't include the program name. // - using builtin = int (*) (scope&, - const strings& args, - auto_fd in, auto_fd out, auto_fd err); + // Also note that the future object being returned doesn't block in dtor + // until the builtin command terminates. + // + using builtin = future (scope&, + const strings& args, + auto_fd in, auto_fd out, auto_fd err); - class builtin_map: public std::map + class builtin_map: public std::map { public: - using base = std::map; + using base = std::map; using base::base; // Return NULL if not a builtin. // - builtin + builtin* find (const string& n) const { auto i (base::find (n)); diff --git a/build2/test/script/builtin.cxx b/build2/test/script/builtin.cxx index 9241447..2dcfde6 100644 --- a/build2/test/script/builtin.cxx +++ b/build2/test/script/builtin.cxx @@ -10,12 +10,22 @@ # include #endif +#include + #include // use default operator<< implementation -#include // fdopen_mode +#include // fdopen_mode, fdstream_mode #include // mkdir_status #include +// Strictly speaking a builtin which reads/writes from/to standard streams +// must be asynchronous so that the caller can communicate with it through +// pipes without being blocked on I/O operations. However, as an optimization, +// we allow builtins that only print diagnostics to STDERR to be synchronous +// assuming that their output will always fit the pipe buffer. Synchronous +// builtins must not read from STDIN and write to STDOUT. Later we may relax +// this rule to allow a "short" output for such builtins. +// using namespace std; using namespace butl; @@ -25,6 +35,18 @@ namespace build2 { namespace script { + using builtin_impl = uint8_t (scope&, + const strings& args, + auto_fd in, auto_fd out, auto_fd err); + static future + to_future (uint8_t status) + { + promise p; + future f (p.get_future ()); + p.set_value (status); + return f; + } + // Operation failed, diagnostics has already been issued. // struct failed {}; @@ -48,13 +70,141 @@ namespace build2 return p; } + // Builtin commands functions. + // + + // cat ... + // + // Read files in sequence and write their contents to STDOUT in the same + // sequence. Read from STDIN if no argumements provided or '-' is + // specified as a file path. STDIN, STDOUT and file streams are set to + // binary mode prior to I/O operations. + // + // Note that POSIX doesn't specify if after I/O operation failure the + // command should proceed with the rest of the arguments. The current + // implementation exits immediatelly in such a case. + // + // @@ Shouldn't we check that we don't print a nonempty regular file to + // itself, as that would merely exhaust the output device? POSIX + // allows (but not requires) such a check and some implementations do + // this. That would require to fstat() file descriptors and complicate + // the code a bit. Was able to reproduce on a big file (should be + // bigger than the stream buffer size) with the test + // 'cat file >>>&file'. + // + // Note: must be executed asynchronously. + // + static uint8_t + cat (scope& sp, + const strings& args, + auto_fd in, auto_fd out, auto_fd err) noexcept + try + { + uint8_t r (1); + ofdstream cerr (move (err)); + + try + { + ifdstream cin (move (in), fdstream_mode::binary); + ofdstream cout (move (out), fdstream_mode::binary); + + // Copy input stream to STDOUT. + // + auto copy = [&cout] (istream& is) + { + if (is.peek () != ifdstream::traits_type::eof ()) + cout << is.rdbuf (); + + is.clear (istream::eofbit); // Sets eofbit. + }; + + // Path of a file being printed to STDOUT. An empty path represents + // STDIN. Used in diagnostics. + // + path p; + + try + { + // Print STDIN. + // + if (args.empty ()) + copy (cin); + + // Print files. + // + for (auto i (args.begin ()); i != args.end (); ++i) + { + if (*i == "-") + { + if (!cin.eof ()) + { + p.clear (); + copy (cin); + } + + continue; + } + + p = parse_path (*i, sp.wd_path); + + ifdstream is (p, ifdstream::binary); + copy (is); + is.close (); + } + } + catch (const io_error& e) + { + cerr << "cat: unable to print "; + + if (p.empty ()) + cerr << "stdin"; + else + cerr << "'" << p << "'"; + + cerr << ": " << e.what () << endl; + throw failed (); + } + + cin.close (); + cout.close (); + r = 0; + } + catch (const invalid_path& e) + { + cerr << "cat: invalid path '" << e.path << "'" << endl; + } + // Can be thrown while closing cin, cout or writing to cerr (that's + // why need to check its state before writing). + // + catch (const io_error& e) + { + if (cerr.good ()) + cerr << "cat: " << e.what () << endl; + } + catch (const failed&) + { + // Diagnostics has already been issued. + } + + cerr.close (); + return r; + } + catch (const std::exception&) + { + return 1; + } + // echo ... // - static int - echo (scope&, const strings& args, auto_fd in, auto_fd out, auto_fd err) + // Note: must be executed asynchronously. + // + static uint8_t + echo (scope&, + const strings& args, + auto_fd in, auto_fd out, auto_fd err) noexcept try { - int r (1); + uint8_t r (1); ofdstream cerr (move (err)); try @@ -83,6 +233,26 @@ namespace build2 return 1; } + // false + // + // Return 1. Failure to close the file descriptors is silently ignored. + // + static future + false_ (scope&, const strings&, auto_fd, auto_fd, auto_fd) + { + return to_future (1); + } + + // true + // + // Return 0. Failure to close the file descriptors is silently ignored. + // + static future + true_ (scope&, const strings&, auto_fd, auto_fd, auto_fd) + { + return to_future (0); + } + // Create a directory if not exist and its parent directories if // necessary. Throw system_error on failure. Register created // directories for cleanup. The directory path must be absolute. @@ -106,20 +276,19 @@ namespace build2 // Create any missing intermediate pathname components. Each argument // that names an existing directory must be ignored without error. // - static int + // Note that POSIX doesn't specify if after a directory creation failure + // the command should proceed with the rest of the arguments. The current + // implementation exits immediatelly in such a case. + // + // Note: can be executed synchronously. + // + static uint8_t mkdir (scope& sp, const strings& args, - auto_fd in, auto_fd out, auto_fd err) + auto_fd in, auto_fd out, auto_fd err) noexcept try { - // @@ Should we set a proper verbosity so paths get printed as - // relative? Can be inconvenient for end-user when build2 runs from - // a testscript. - // - // No, don't think so. If this were an external program, there - // won't be such functionality. - // - int r (1); + uint8_t r (1); ofdstream cerr (move (err)); try @@ -170,7 +339,6 @@ namespace build2 { cerr << "mkdir: unable to create directory '" << p << "': " << e.what () << endl; - throw failed (); } } @@ -181,6 +349,14 @@ namespace build2 { cerr << "mkdir: invalid path '" << e.path << "'" << endl; } + // Can be thrown while closing in, out or writing to cerr (that's why + // need to check its state before writing). + // + catch (const io_error& e) + { + if (cerr.good ()) + cerr << "mkdir: " << e.what () << endl; + } catch (const failed&) { // Diagnostics has already been issued. @@ -194,7 +370,7 @@ namespace build2 return 1; } - // touch ... + // touch ... // // Change file access and modification times to the current time. Create // a file if doesn't exist. Fail if a file system entry other than file @@ -203,13 +379,19 @@ namespace build2 // Note that POSIX doesn't specify the behavior for touching an entry // other than file. // - static int + // Also note that POSIX doesn't specify if after a file touch failure the + // command should proceed with the rest of the arguments. The current + // implementation exits immediatelly in such a case. + // + // Note: can be executed synchronously. + // + static uint8_t touch (scope& sp, const strings& args, - auto_fd in, auto_fd out, auto_fd err) + auto_fd in, auto_fd out, auto_fd err) noexcept try { - int r (1); + uint8_t r (1); ofdstream cerr (move (err)); try @@ -283,6 +465,14 @@ namespace build2 { cerr << "touch: invalid path '" << e.path << "'" << endl; } + // Can be thrown while closing in, out or writing to cerr (that's why + // need to check its state before writing). + // + catch (const io_error& e) + { + if (cerr.good ()) + cerr << "touch: " << e.what () << endl; + } catch (const failed&) { // Diagnostics has already been issued. @@ -296,11 +486,70 @@ namespace build2 return 1; } + static void + thread_thunk (builtin_impl* fn, + scope& sp, + const strings& args, + auto_fd in, auto_fd out, auto_fd err, + promise p) + { + // The use of set_value_at_thread_exit() would be more appropriate but + // the function is not supported by old versions of g++ (e.g., not in + // 4.9). There could also be overhead associated with it. + // + p.set_value (fn (sp, args, move (in), move (out), move (err))); + } + + // Run builtin implementation asynchronously. + // + static future + async_impl (builtin_impl fn, + scope& sp, + const strings& args, + auto_fd in, auto_fd out, auto_fd err) + { + promise p; + future f (p.get_future ()); + + thread t (thread_thunk, + fn, + ref (sp), + cref (args), + move (in), move (out), move (err), + move (p)); + + t.detach (); + return f; + } + + template + static future + async_impl (scope& sp, + const strings& args, + auto_fd in, auto_fd out, auto_fd err) + { + return async_impl (fn, sp, args, move (in), move (out), move (err)); + } + + // Run builtin implementation synchronously. + // + template + static future + sync_impl (scope& sp, + const strings& args, + auto_fd in, auto_fd out, auto_fd err) + { + return to_future (fn (sp, args, move (in), move (out), move (err))); + } + const builtin_map builtins { - {"echo", &echo}, - {"mkdir", &mkdir}, - {"touch", &touch} + {"cat", &async_impl<&cat>}, + {"echo", &async_impl<&echo>}, + {"false", &false_}, + {"mkdir", &sync_impl<&mkdir>}, + {"touch", &sync_impl<&touch>}, + {"true", &true_} }; } } diff --git a/build2/test/script/parser b/build2/test/script/parser index a81ddf0..fdfbe11 100644 --- a/build2/test/script/parser +++ b/build2/test/script/parser @@ -158,6 +158,12 @@ namespace build2 const string& insert_id (string, location); + // Set lexer pointers for both the current and the base classes. + // + protected: + void + set_lexer (lexer* l); + protected: using base_parser = build2::parser; diff --git a/build2/test/script/parser.cxx b/build2/test/script/parser.cxx index 10103f3..438e1f2 100644 --- a/build2/test/script/parser.cxx +++ b/build2/test/script/parser.cxx @@ -60,8 +60,7 @@ namespace build2 pre_parse_ = true; lexer l (is, *path_, lexer_mode::script_line); - lexer_ = &l; - base_parser::lexer_ = &l; + set_lexer (&l); id_prefix_.clear (); @@ -948,8 +947,7 @@ namespace build2 path_ = &p; lexer* ol (lexer_); - lexer_ = &l; - base_parser::lexer_ = &l; + set_lexer (&l); string oip (id_prefix_); id_prefix_ += to_string (dl.line); @@ -963,8 +961,7 @@ namespace build2 fail (t) << "stray " << t; id_prefix_ = oip; - base_parser::lexer_ = ol; - lexer_ = ol; + set_lexer (ol); path_ = op; } catch (const io_error& e) @@ -2219,8 +2216,7 @@ namespace build2 pre_parse_ = false; - lexer_ = nullptr; - base_parser::lexer_ = nullptr; + set_lexer (nullptr); script_ = &s; runner_ = &r; @@ -2694,6 +2690,13 @@ namespace build2 return p.first->first; } + + void parser:: + set_lexer (lexer* l) + { + lexer_ = l; + base_parser::lexer_ = l; + } } } } diff --git a/build2/test/script/runner.cxx b/build2/test/script/runner.cxx index 1d6920a..aa35612 100644 --- a/build2/test/script/runner.cxx +++ b/build2/test/script/runner.cxx @@ -406,7 +406,8 @@ namespace build2 ifd.reset (fdnull ()); // @@ Eventually will be throwing. if (ifd.get () == -1) // @@ TMP - throw io_error ("", error_code (errno, system_category ())); + throw io_error ( + error_code (errno, system_category ()).message ()); in = -2; } @@ -512,7 +513,8 @@ namespace build2 fd.reset (fdnull ()); // @@ Eventully will be throwing. if (fd.get () == -1) // @@ TMP - throw io_error ("", error_code (errno, system_category ())); + throw io_error ( + error_code (errno, system_category ()).message ()); } catch (const io_error& e) { @@ -590,13 +592,24 @@ namespace build2 } optional status; - builtin b (builtins.find (c.program.string ())); + builtin* b (builtins.find (c.program.string ())); if (b != nullptr) { // Execute the builtin. // - status = (*b) (sp, c.arguments, move (ifd), move (ofd), move (efd)); + try + { + future f ( + (*b) (sp, c.arguments, move (ifd), move (ofd), move (efd))); + + status = f.get (); + } + catch (const system_error& e) + { + fail (ll) << "unable to execute " << c.program << " builtin: " + << e.what (); + } } else { diff --git a/build2/types b/build2/types index 9b3dbc7..783aff0 100644 --- a/build2/types +++ b/build2/types @@ -8,6 +8,7 @@ #include #include #include +#include #include // unique_ptr, shared_ptr #include // pair, move() #include // size_t, nullptr_t @@ -63,6 +64,8 @@ namespace build2 using std::istream; using std::ostream; + using std::future; + // Exceptions. While is included, there is no using for // std::exception -- use qualified. // diff --git a/build2/utility b/build2/utility index e08ae69..deefc43 100644 --- a/build2/utility +++ b/build2/utility @@ -5,12 +5,13 @@ #ifndef BUILD2_UTILITY #define BUILD2_UTILITY -#include // make_tuple() -#include // make_shared() -#include // to_string() -#include // move(), forward(), declval(), make_pair() -#include // assert() -#include // make_move_iterator() +#include // make_tuple() +#include // make_shared() +#include // to_string() +#include // move(), forward(), declval(), make_pair() +#include // assert() +#include // make_move_iterator() +#include // ref(), cref() #include // combine_hash(), reverse_iterate(), casecmp(), // lcase() @@ -27,6 +28,9 @@ namespace build2 using std::forward; using std::declval; + using std::ref; + using std::cref; + using std::make_pair; using std::make_tuple; using std::make_shared; -- cgit v1.1