From 985e8f5f28da87be779b80942577f088321024af Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 5 Dec 2014 13:26:29 +0200 Subject: Add support for starting processes, getting file timestamps g++-4.9 -std=c++11 -I.. -o bd bd.cxx process.cxx timestamp.cxx --- build/bd.cxx | 95 +++++++++++++-- build/process | 67 +++++++++++ build/process.cxx | 326 ++++++++++++++++++++++++++++++++++++++++++++++++++++ build/target | 9 +- build/timestamp | 52 +++++++++ build/timestamp.cxx | 154 +++++++++++++++++++++++++ 6 files changed, 690 insertions(+), 13 deletions(-) create mode 100644 build/process create mode 100644 build/process.cxx create mode 100644 build/timestamp create mode 100644 build/timestamp.cxx diff --git a/build/bd.cxx b/build/bd.cxx index 918434a..03d0aa4 100644 --- a/build/bd.cxx +++ b/build/bd.cxx @@ -1,10 +1,17 @@ -// file : build/bd.cxx +// file : build/bd.cxx -*- C++ -*- // copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC // license : MIT; see accompanying LICENSE file +#include // tzset() + #include +#include // exit +#include #include +#include +#include +#include #include using namespace std; @@ -14,33 +21,91 @@ namespace build bool update (target& t) { - const targets& ps (t.prerequisites ()); + auto tts (path_timestamp (t.name ())); + cout << t.name () << ": " << tts << endl; - for (target& p: ps) + bool u (tts == timestamp_nonexistent); + for (target& p: t.prerequisites ()) + { if (!update (p)) return false; - //@@ TODO: check for existance, compare timestamps. + if (!u) + { + auto tps (path_timestamp (p.name ())); + + if (tts <= tps) // Note: not just less. + { + cout << t.name () << " vs " << p.name () << ": " << (tps - tts) + << " ahead" << endl; + u = true; + } + } + } - auto r (t.rule ()); - return r != 0 ? r (t, t.prerequisites ()) : true; + if (!u) // Nothing to do. + return true; + + try + { + auto r (t.rule ()); + return r != 0 ? r (t) : true; + } + catch (const process_error& e) + { + // Take care of failed children. In a multi-threaded program that + // fork()'ed but did not exec(), it is unwise to try to do any kind + // of cleanup (like unwinding the stack and running destructors). + // + assert (e.child ()); + exit (1); + } } } using namespace build; bool -cxx_compile_rule (target& t, const targets& p) +cxx_compile_rule (target& t) { - //@@ TODO: actually execute + const targets& ps (t.prerequisites ()); + + //@@ TODO: assuming .cxx is first. + // + const target& p0 (ps[0]); + const char* args[] { + "g++-4.9", + "-std=c++11", + "-I..", + "-c", + "-o", t.name ().c_str (), + p0.name ().c_str (), + nullptr}; cerr << "c++ " << t.name () << endl; - return true; + + try + { + process pr (args); + return pr.wait (); + } + catch (const process_error& e) + { + cerr << "error: unable to execute '" << args[0] << "': " << + e.what () << endl; + + if (e.child ()) + throw; // Let our caller terminate us quickly without causing a scene. + + return false; + } } bool -cxx_link_rule (target& t, const targets& p) +cxx_link_rule (target& t) { + const targets& ps (t.prerequisites ()); + cerr << "ld " << t.name () << endl; return true; } @@ -48,6 +113,10 @@ cxx_link_rule (target& t, const targets& p) int main (int argc, char* argv[]) { + // Initialize time conversion data that is used by localtime_r(). + // + tzset (); + exe bd ("bd"); obj bd_o ("bd.o"); bd.prerequisite (bd_o); @@ -59,5 +128,9 @@ main (int argc, char* argv[]) bd_o.prerequisite (target); bd_o.rule (&cxx_compile_rule); - update (bd); + if (!update (bd)) + { + cerr << "unable to update '" << bd.name () << "'" << endl; + return 1; + } } diff --git a/build/process b/build/process new file mode 100644 index 0000000..0b45846 --- /dev/null +++ b/build/process @@ -0,0 +1,67 @@ +// file : build/process -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_PROCESS +#define BUILD_PROCESS + +#ifndef _WIN32 +# include // pid_t +#endif + +#include +#include + +namespace build +{ + + struct process_error: std::system_error + { + process_error (int e, bool child) + : system_error (e, std::system_category ()), child_ (child) {} + + bool + child () const {return child_;} + + private: + bool child_; + }; + + struct process + { + // Start another process using the specified command line. Connect the + // newly created process' stdin to out_fd. Also if connect_* are true, + // connect the created process' stdout and stderr to in_*fd. Throw + // process_error if anything goes wrong. + // + // Note that some of the exceptions (e.g., if exec() failed) can be + // thrown in the child version of us. + // + process (char const* args[], + bool connect_stdin = false, + bool connect_stderr = false, + bool connect_stdout = false); + + // Wait for the process to terminate. Return true if the process + // terminated normally and with the zero exit status. Throw + // process_error if anything goes wrong. + // + bool + wait (); + + ~process () {assert (id == 0);} + +#ifndef _WIN32 + typedef pid_t id_type; +#else + typedef void* id_type; // Win32 HANDLE. +#endif + + id_type id; + int out_fd; // Write to this fd to send to the new process' stdin. + int in_efd; // Read from this fd to receive from the new process' stderr. + int in_ofd; // Read from this fd to receive from the new process' stdout. + }; +} + +#endif // BUILD_PROCESS diff --git a/build/process.cxx b/build/process.cxx new file mode 100644 index 0000000..09b8d36 --- /dev/null +++ b/build/process.cxx @@ -0,0 +1,326 @@ +// file : build/process.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include + +#ifndef _WIN32 +# include // execvp, fork, dup2, pipe, {STDIN,STDERR}_FILENO +# include // waitpid +#else +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include // CreatePipe, CreateProcess +# include // _open_osfhandle +# include // _O_TEXT +#endif + +using namespace std; + +namespace build +{ +#ifndef _WIN32 + + process:: + process (char const* args[], bool in, bool err, bool out) + { + int out_fd[2]; + int in_efd[2]; + int in_ofd[2]; + + if ((in && pipe (out_fd) == -1) || + (err && pipe (in_efd) == -1) || + (out && pipe (in_ofd) == -1)) + throw process_error (errno, false); + + id = fork (); + + if (id == -1) + throw process_error (errno, false); + + if (id == 0) + { + // Child. If requested, close the write end of the pipe and duplicate + // the read end to stdin. Then close the original read end descriptor. + // + if (in) + { + if (close (out_fd[1]) == -1 || + dup2 (out_fd[0], STDIN_FILENO) == -1 || + close (out_fd[0]) == -1) + throw process_error (errno, true); + } + + // Do the same for the stderr if requested. + // + if (err) + { + if (close (in_efd[0]) == -1 || + dup2 (in_efd[1], STDERR_FILENO) == -1 || + close (in_efd[1]) == -1) + throw process_error (errno, true); + } + + // Do the same for the stdout if requested. + // + if (out) + { + if (close (in_ofd[0]) == -1 || + dup2 (in_ofd[1], STDOUT_FILENO) == -1 || + close (in_ofd[1]) == -1) + throw process_error (errno, true); + } + + if (execvp (args[0], const_cast (&args[0])) == -1) + throw process_error (errno, true); + } + else + { + // Parent. Close the other ends of the pipes. + // + if ((in && close (out_fd[0])) == -1 || + (err && close (in_efd[1]) == -1) || + (out && close (in_ofd[1]) == -1)) + throw process_error (errno, false); + } + + this->out_fd = in ? out_fd[1] : 0; + this->in_efd = err ? in_efd[0] : 0; + this->in_ofd = out ? in_ofd[0] : 0; + } + + bool process:: + wait () + { + int status; + int r (waitpid (id, &status, 0)); + id = 0; // We have tried. + + if (r == -1) + throw process_error (errno, false); + + return WIFEXITED (status) && WEXITSTATUS (status) == 0; + } + +#else // _WIN32 + + static void + print_error (char const* name) + { + LPTSTR msg; + DWORD e (GetLastError()); + + if (!FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + 0, + e, + MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &msg, + 0, + 0)) + { + cerr << name << ": error: unknown error code " << e << endl; + return; + } + + cerr << name << ": error: " << msg << endl; + LocalFree (msg); + } + + static process_info + start_process (char const* args[], char const* name, bool err, bool out) + { + HANDLE out_h[2]; + HANDLE in_eh[2]; + HANDLE in_oh[2]; + SECURITY_ATTRIBUTES sa; + + sa.nLength = sizeof (SECURITY_ATTRIBUTES); + sa.bInheritHandle = true; + sa.lpSecurityDescriptor = 0; + + if (!CreatePipe (&out_h[0], &out_h[1], &sa, 0) || + !SetHandleInformation (out_h[1], HANDLE_FLAG_INHERIT, 0)) + { + print_error (name); + throw process_failure (); + } + + if (err) + { + if (!CreatePipe (&in_eh[0], &in_eh[1], &sa, 0) || + !SetHandleInformation (in_eh[0], HANDLE_FLAG_INHERIT, 0)) + { + print_error (name); + throw process_failure (); + } + } + + if (out) + { + if (!CreatePipe (&in_oh[0], &in_oh[1], &sa, 0) || + !SetHandleInformation (in_oh[0], HANDLE_FLAG_INHERIT, 0)) + { + print_error (name); + throw process_failure (); + } + } + + // Create the process. + // + path file (args[0]); + + // Do PATH search. + // + if (file.directory ().empty ()) + file = path_search (file); + + if (file.empty ()) + { + cerr << args[0] << ": error: file not found" << endl; + throw process_failure (); + } + + // Serialize the arguments to string. + // + string cmd_line; + + for (char const** p (args); *p != 0; ++p) + { + if (p != args) + cmd_line += ' '; + + // On Windows we need to protect values with spaces using quotes. + // Since there could be actual quotes in the value, we need to + // escape them. + // + string a (*p); + bool quote (a.find (' ') != string::npos); + + if (quote) + cmd_line += '"'; + + for (size_t i (0); i < a.size (); ++i) + { + if (a[i] == '"') + cmd_line += "\\\""; + else + cmd_line += a[i]; + } + + if (quote) + cmd_line += '"'; + } + + // Prepare other info. + // + STARTUPINFO si; + PROCESS_INFORMATION pi; + + memset (&si, 0, sizeof (STARTUPINFO)); + memset (&pi, 0, sizeof (PROCESS_INFORMATION)); + + si.cb = sizeof(STARTUPINFO); + + if (err) + si.hStdError = in_eh[1]; + else + si.hStdError = GetStdHandle (STD_ERROR_HANDLE); + + if (out) + si.hStdOutput = in_oh[1]; + else + si.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE); + + si.hStdInput = out_h[0]; + si.dwFlags |= STARTF_USESTDHANDLES; + + if (!CreateProcess ( + file.string ().c_str (), + const_cast (cmd_line.c_str ()), + 0, // Process security attributes. + 0, // Primary thread security attributes. + true, // Inherit handles. + 0, // Creation flags. + 0, // Use our environment. + 0, // Use our current directory. + &si, + &pi)) + { + print_error (name); + throw process_failure (); + } + + CloseHandle (pi.hThread); + CloseHandle (out_h[0]); + + if (err) + CloseHandle (in_eh[1]); + + if (out) + CloseHandle (in_oh[1]); + + process_info r; + r.id = pi.hProcess; + r.out_fd = _open_osfhandle ((intptr_t) (out_h[1]), 0); + + if (r.out_fd == -1) + { + cerr << name << ": error: unable to obtain C file handle" << endl; + throw process_failure (); + } + + if (err) + { + // Pass _O_TEXT to get newline translation. + // + r.in_efd = _open_osfhandle ((intptr_t) (in_eh[0]), _O_TEXT); + + if (r.in_efd == -1) + { + cerr << name << ": error: unable to obtain C file handle" << endl; + throw process_failure (); + } + } + else + r.in_efd = 0; + + if (out) + { + // Pass _O_TEXT to get newline translation. + // + r.in_ofd = _open_osfhandle ((intptr_t) (in_oh[0]), _O_TEXT); + + if (r.in_ofd == -1) + { + cerr << name << ": error: unable to obtain C file handle" << endl; + throw process_failure (); + } + } + else + r.in_ofd = 0; + + return r; + } + + static bool + wait_process (process_info pi, char const* name) + { + DWORD status; + + if (WaitForSingleObject (pi.id, INFINITE) != WAIT_OBJECT_0 || + !GetExitCodeProcess (pi.id, &status)) + { + print_error (name); + throw process_failure (); + } + + CloseHandle (pi.id); + return status == 0; + } + +#endif // _WIN32 +} diff --git a/build/target b/build/target index e6f05a5..4319ccb 100644 --- a/build/target +++ b/build/target @@ -2,9 +2,12 @@ // copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC // license : MIT; see accompanying LICENSE file +#ifndef BUILD_TARGET +#define BUILD_TARGET + #include #include -#include +#include // std::reference_wrapper namespace build { @@ -29,7 +32,7 @@ namespace build prerequisite (target& t) {prerequisites_.push_back (t);} public: - typedef bool (*rule_type) (target&, const targets&); + typedef bool (*rule_type) (target&); rule_type rule () const {return rule_;} @@ -67,3 +70,5 @@ namespace build using target::target; }; } + +#endif // BUILD_TARGET diff --git a/build/timestamp b/build/timestamp new file mode 100644 index 0000000..6ed2f2c --- /dev/null +++ b/build/timestamp @@ -0,0 +1,52 @@ +// file : build/timestamp -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_TIMESTAMP +#define BUILD_TIMESTAMP + +#include +#include +#include + +namespace build +{ + // On all three main platforms that we target (GNU/Linux, Windows (both + // VC++ and GCC/MinGW64), and MacOS X) with recent C++ runtimes, + // system_clock has nanoseconds resolution and counts from the UNIX + // epoch. The latter is important since struct stat also returns times + // based on UNIX epoch. + // + // The underlying type for nanoseconds duration is signed integer type + // of at least 64 bits (currently int64_t). Because it is signed, we + // will overflow in year 2262 but by then the underlying type will + // most likely have changed to something larger than 64-bit. + // + // So to support other platforms that could possibly use a different + // system_clock resolutions (e.g., microseconds), we actually not going + // to assume anywhere (except perhaps timestamp.cxx) that we are dealing + // with nanoseconds or the 64-bit underlying type. + // + using std::chrono::system_clock; + + using timestamp = system_clock::time_point; + using duration = system_clock::duration; + + const timestamp timestamp_unknown {duration {-1}}; + const timestamp timestamp_nonexistent {duration {0}}; + + std::ostream& + operator<< (std::ostream&, timestamp); + + std::ostream& + operator<< (std::ostream&, duration); + + // Returns timestamp_nonexistent if the entry at the specified path + // does not exist. All other errors are reported by throwing + // std::system_error. + // + timestamp + path_timestamp (const std::string&); +}; + +#endif // BUILD_TIMESTAMP diff --git a/build/timestamp.cxx b/build/timestamp.cxx new file mode 100644 index 0000000..db8efb2 --- /dev/null +++ b/build/timestamp.cxx @@ -0,0 +1,154 @@ +// file : build/timestamp.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include + +#include // stat +#include // stat +#include // stat + +#include // localtime, gmtime, strftime + +#include +#include + +using namespace std; + +namespace build +{ + // Figuring out whether we have the nanoseconds in some form. + // + template + constexpr auto nsec (const S* s) -> decltype(s->st_mtim.tv_nsec) + { + return s->st_mtim.tv_nsec; // POSIX (GNU/Linux, Solaris). + } + + template + constexpr auto nsec (const S* s) -> decltype(s->st_mtimespec.tv_nsec) + { + return s->st_mtimespec.tv_nsec; // MacOS X. + } + + template + constexpr auto nsec (const S* s) -> decltype(s->st_mtime_n) + { + return s->st_mtime_n; // AIX 5.2 and later. + } + + template + constexpr int nsec (...) {return 0;} + + timestamp + path_timestamp (const std::string& p) + { + struct stat s; + if (stat (p.c_str (), &s) != 0) + { + if (errno == ENOENT || errno == ENOTDIR) + return timestamp_nonexistent; + else + throw system_error (errno, system_category ()); + } + + return system_clock::from_time_t (s.st_mtime) + + chrono::duration_cast ( + chrono::nanoseconds (nsec (&s))); + } + + ostream& + operator<< (ostream& os, timestamp ts) + { + // @@ replace with put_time() + // + + time_t t (system_clock::to_time_t (ts)); + + if (t == 0) + return os << ""; + + std::tm tm; + if (localtime_r (&t, &tm) == nullptr) + throw system_error (errno, system_category ()); + + // If year is greater than 9999, we will overflow. + // + char buf[20]; // YYYY-MM-DD HH:MM:SS\0 + if (strftime (buf, sizeof (buf), "%Y-%m-%d %H:%M:%S", &tm) == 0) + return os << ""; + + os << buf; + + using namespace chrono; + + timestamp sec (system_clock::from_time_t (t)); + nanoseconds ns (duration_cast (ts - sec)); + + if (ns != nanoseconds::zero ()) + { + os << '.'; + os.width (9); + os.fill ('0'); + os << ns.count (); + } + + return os; + } + + ostream& + operator<< (ostream& os, duration d) + { + // @@ replace with put_time() + // + + timestamp ts; // Epoch. + ts += d; + + time_t t (system_clock::to_time_t (ts)); + + const char* fmt (nullptr); + if (t >= 365 * 12 * 24 * 60 * 60) + fmt = "%Y-%m-%d %H:%M:%S"; + else if (t >= 12 * 24 * 60* 60) + fmt = "%m-%d %H:%M:%S"; + else if (t >= 24 * 60* 60) + fmt = "%d %H:%M:%S"; + else if (t >= 60 * 60) + fmt = "%H:%M:%S"; + else if (t >= 60) + fmt = "%M:%S"; + else if (t >= 1) + fmt = "%S"; + + if (fmt != nullptr) + { + std::tm tm; + if (gmtime_r (&t, &tm) == nullptr) + throw system_error (errno, system_category ()); + + char buf[20]; // YYYY-MM-DD HH:MM:SS\0 + if (strftime (buf, sizeof (buf), fmt, &tm) == 0) + return os << ""; + + os << buf; + } + + using namespace chrono; + + timestamp sec (system_clock::from_time_t (t)); + nanoseconds ns (duration_cast (ts - sec)); + + if (ns != nanoseconds::zero ()) + { + os << '.'; + os.width (9); + os.fill ('0'); + os << ns.count (); + } + else if (fmt == 0) + os << '0'; + + return os; + } +} -- cgit v1.1