aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2014-12-05 13:26:29 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2014-12-05 13:26:29 +0200
commit985e8f5f28da87be779b80942577f088321024af (patch)
tree811473dc2c89d755e46cfa28711c3ce8569a1556
parent0ba8af59dbc3e7419a9ef24c6d4d466d6d64862c (diff)
Add support for starting processes, getting file timestamps
g++-4.9 -std=c++11 -I.. -o bd bd.cxx process.cxx timestamp.cxx
-rw-r--r--build/bd.cxx95
-rw-r--r--build/process67
-rw-r--r--build/process.cxx326
-rw-r--r--build/target9
-rw-r--r--build/timestamp52
-rw-r--r--build/timestamp.cxx154
6 files changed, 690 insertions, 13 deletions
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 <time.h> // tzset()
+
#include <vector>
+#include <cstdlib> // exit
+#include <cassert>
#include <iostream>
+#include <system_error>
+#include <build/process>
+#include <build/timestamp>
#include <build/target>
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 <sys/types.h> // pid_t
+#endif
+
+#include <cassert>
+#include <system_error>
+
+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 <build/process>
+
+#ifndef _WIN32
+# include <unistd.h> // execvp, fork, dup2, pipe, {STDIN,STDERR}_FILENO
+# include <sys/wait.h> // waitpid
+#else
+# ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# endif
+# include <windows.h> // CreatePipe, CreateProcess
+# include <io.h> // _open_osfhandle
+# include <fcntl.h> // _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<char**> (&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<char*> (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 <string>
#include <vector>
-#include <functional>
+#include <functional> // 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 <chrono>
+#include <string>
+#include <iosfwd>
+
+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 <build/timestamp>
+
+#include <unistd.h> // stat
+#include <sys/types.h> // stat
+#include <sys/stat.h> // stat
+
+#include <time.h> // localtime, gmtime, strftime
+
+#include <ostream>
+#include <system_error>
+
+using namespace std;
+
+namespace build
+{
+ // Figuring out whether we have the nanoseconds in some form.
+ //
+ template <typename S>
+ constexpr auto nsec (const S* s) -> decltype(s->st_mtim.tv_nsec)
+ {
+ return s->st_mtim.tv_nsec; // POSIX (GNU/Linux, Solaris).
+ }
+
+ template <typename S>
+ constexpr auto nsec (const S* s) -> decltype(s->st_mtimespec.tv_nsec)
+ {
+ return s->st_mtimespec.tv_nsec; // MacOS X.
+ }
+
+ template <typename S>
+ constexpr auto nsec (const S* s) -> decltype(s->st_mtime_n)
+ {
+ return s->st_mtime_n; // AIX 5.2 and later.
+ }
+
+ template <typename S>
+ 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<duration> (
+ chrono::nanoseconds (nsec<struct stat> (&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 << "<nonexistent>";
+
+ 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 << "<beyond year 9999>";
+
+ os << buf;
+
+ using namespace chrono;
+
+ timestamp sec (system_clock::from_time_t (t));
+ nanoseconds ns (duration_cast<nanoseconds> (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 << "<beyond 9999 years>";
+
+ os << buf;
+ }
+
+ using namespace chrono;
+
+ timestamp sec (system_clock::from_time_t (t));
+ nanoseconds ns (duration_cast<nanoseconds> (ts - sec));
+
+ if (ns != nanoseconds::zero ())
+ {
+ os << '.';
+ os.width (9);
+ os.fill ('0');
+ os << ns.count ();
+ }
+ else if (fmt == 0)
+ os << '0';
+
+ return os;
+ }
+}