aboutsummaryrefslogtreecommitdiff
path: root/build
diff options
context:
space:
mode:
Diffstat (limited to 'build')
-rw-r--r--build/bd.cxx167
-rw-r--r--build/cxx/rule40
-rw-r--r--build/cxx/rule.cxx260
-rw-r--r--build/cxx/target52
-rw-r--r--build/cxx/target.cxx20
-rw-r--r--build/native31
-rw-r--r--build/native.cxx15
-rw-r--r--build/rule39
-rw-r--r--build/rule.cxx72
-rw-r--r--build/target110
-rw-r--r--build/target.cxx38
-rw-r--r--build/timestamp4
-rw-r--r--build/timestamp.cxx37
13 files changed, 792 insertions, 93 deletions
diff --git a/build/bd.cxx b/build/bd.cxx
index 03d0aa4..e48de21 100644
--- a/build/bd.cxx
+++ b/build/bd.cxx
@@ -8,48 +8,82 @@
#include <cstdlib> // exit
#include <cassert>
#include <iostream>
+#include <typeinfo>
#include <system_error>
-#include <build/process>
-#include <build/timestamp>
#include <build/target>
+#include <build/rule>
+#include <build/process>
using namespace std;
namespace build
{
bool
- update (target& t)
+ match (target& t)
{
- auto tts (path_timestamp (t.name ()));
- cout << t.name () << ": " << tts << endl;
+ if (!t.recipe ())
+ {
+ for (auto ti (&t.type_id ());
+ ti != nullptr && !t.recipe ();
+ ti = ti->base)
+ {
+ for (auto rs (rules.equal_range (ti->id));
+ rs.first != rs.second;
+ ++rs.first)
+ {
+ const rule& ru (rs.first->second);
+
+ if (recipe re = ru.match (t))
+ {
+ t.recipe (re);
+ break;
+ }
+ }
+ }
+
+ if (!t.recipe ())
+ {
+ cerr << "error: no rule to build target " << t << endl;
+ return false;
+ }
+ }
- bool u (tts == timestamp_nonexistent);
for (target& p: t.prerequisites ())
{
- if (!update (p))
+ if (!match (p))
+ {
+ cerr << "info: required by " << t << endl;
return false;
+ }
+ }
+
+ return true;
+ }
+
+ target_state
+ update (target& t)
+ {
+ assert (t.state () == target_state::unknown);
- if (!u)
+ target_state ts;
+
+ for (target& p: t.prerequisites ())
+ {
+ if (p.state () == target_state::unknown)
{
- auto tps (path_timestamp (p.name ()));
+ p.state ((ts = update (p)));
- if (tts <= tps) // Note: not just less.
- {
- cout << t.name () << " vs " << p.name () << ": " << (tps - tts)
- << " ahead" << endl;
- u = true;
- }
+ if (ts == target_state::failed)
+ return ts;
}
}
- if (!u) // Nothing to do.
- return true;
-
try
{
- auto r (t.rule ());
- return r != 0 ? r (t) : true;
+ t.state ((ts = t.recipe () (t)));
+ assert (ts != target_state::unknown);
+ return ts;
}
catch (const process_error& e)
{
@@ -63,52 +97,13 @@ namespace build
}
}
-using namespace build;
+#include <build/native>
-bool
-cxx_compile_rule (target& t)
-{
- 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;
-
- 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.
+#include <build/cxx/target>
+#include <build/cxx/rule>
- return false;
- }
-}
-
-bool
-cxx_link_rule (target& t)
-{
- const targets& ps (t.prerequisites ());
- cerr << "ld " << t.name () << endl;
- return true;
-}
+using namespace build;
int
main (int argc, char* argv[])
@@ -117,20 +112,52 @@ main (int argc, char* argv[])
//
tzset ();
+ cxx::link cxx_link;
+ rules.emplace (typeid (exe), cxx_link);
+
+ cxx::compile cxx_compile;
+ rules.emplace (typeid (obj), cxx_compile);
+
+ default_path_rule path_exists;
+ rules.emplace (typeid (path_target), path_exists);
+
+ //
+ //
+ using namespace build::cxx;
+
exe bd ("bd");
- obj bd_o ("bd.o");
+ obj bd_o ("bd");
bd.prerequisite (bd_o);
- bd.rule (&cxx_link_rule);
- cxx bd_cxx ("bd.cxx");
+ cxx::cxx bd_cxx ("bd");
+ bd_cxx.path (path ("bd.cxx"));
+
hxx target ("target");
+ target.path (path ("target"));
+
bd_o.prerequisite (bd_cxx);
bd_o.prerequisite (target);
- bd_o.rule (&cxx_compile_rule);
- if (!update (bd))
+ //
+ //
+ if (!match (bd))
+ return 1; // Diagnostics has already been issued.
+
+ switch (update (bd))
{
- cerr << "unable to update '" << bd.name () << "'" << endl;
- return 1;
+ case target_state::uptodate:
+ {
+ cerr << "info: target " << bd << " is up to date" << endl;
+ break;
+ }
+ case target_state::updated:
+ break;
+ case target_state::failed:
+ {
+ cerr << "error: failed to update target " << bd << endl;
+ return 1;
+ }
+ case target_state::unknown:
+ assert (false);
}
}
diff --git a/build/cxx/rule b/build/cxx/rule
new file mode 100644
index 0000000..d4412e4
--- /dev/null
+++ b/build/cxx/rule
@@ -0,0 +1,40 @@
+// file : build/cxx/rule -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD_CXX_RULE
+#define BUILD_CXX_RULE
+
+#include <build/rule>
+
+namespace build
+{
+ namespace cxx
+ {
+ // @@ Can't we do match(obj&) and then registration code extracts
+ // that. And no virtuals.
+ //
+
+ class compile: public rule
+ {
+ public:
+ virtual recipe
+ match (target&) const;
+
+ static target_state
+ update (target&);
+ };
+
+ class link: public rule
+ {
+ public:
+ virtual recipe
+ match (target&) const;
+
+ static target_state
+ update (target&);
+ };
+ }
+}
+
+#endif // BUILD_CXX_RULE
diff --git a/build/cxx/rule.cxx b/build/cxx/rule.cxx
new file mode 100644
index 0000000..9f8f1ae
--- /dev/null
+++ b/build/cxx/rule.cxx
@@ -0,0 +1,260 @@
+// file : build/cxx/rule.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#include <build/cxx/rule>
+
+#include <vector>
+#include <iostream>
+
+#include <build/native>
+#include <build/process>
+#include <build/timestamp>
+
+#include <build/cxx/target>
+
+using namespace std;
+
+namespace build
+{
+ namespace cxx
+ {
+ // compile
+ //
+ recipe compile::
+ match (target& t) const
+ {
+ // @@ TODO:
+ //
+ // - check prerequisites: single source file
+ // - check prerequisites: the rest are headers (issue warning at v=1?)
+ // - if path already assigned, verify extension
+ //
+ // @@ Q:
+ //
+ // - if there is no .cxx, are we going to check if the one derived
+ // from target exist or can be built? If we do that, then it
+ // probably makes sense to try other rules first (two passes).
+ //
+ // - Wouldn't it make sense to cache source file? Careful: unloading
+ // of dependency info.
+ //
+
+ // See if we have a source file.
+ //
+ const cxx* s (nullptr);
+ for (const target& p: t.prerequisites ())
+ {
+ if ((s = dynamic_cast<const cxx*> (&p)) != nullptr)
+ break;
+ }
+
+ if (s == nullptr)
+ return recipe ();
+
+ // Derive object file name from target name.
+ //
+ obj& o (dynamic_cast<obj&> (t));
+
+ if (o.path ().empty ())
+ o.path (path (o.name () + ".o"));
+
+ return recipe (&update);
+ }
+
+ target_state compile::
+ update (target& t)
+ {
+ obj& o (dynamic_cast<obj&> (t));
+ timestamp mt (o.mtime ());
+
+ bool u (mt == timestamp_nonexistent);
+ const cxx* s (nullptr);
+
+ for (const target& p: t.prerequisites ())
+ {
+ // Assume all our prerequisites are mtime-based (checked in
+ // match()).
+ //
+ if (!u)
+ {
+ const auto& mtp (dynamic_cast<const mtime_target&> (p));
+ timestamp mp (mtp.mtime ());
+
+ // What do we do if timestamps are equal? This can happen, for
+ // example, on filesystems that don't have subsecond resolution.
+ // There is not much we can do here except detect the case where
+ // the prerequisite was updated in this run which means the
+ // target must be out of date.
+ //
+ if (mt < mp || mt == mp && mtp.state () == target_state::updated)
+ u = true;
+ }
+
+ if (s == nullptr)
+ s = dynamic_cast<const cxx*> (&p);
+
+ if (u && s != nullptr)
+ break;
+ }
+
+ if (!u)
+ return target_state::uptodate;
+
+ const char* args[] = {
+ "g++-4.9",
+ "-std=c++11",
+ "-I..",
+ "-c",
+ "-o", o.path ().string ().c_str (),
+ s->path ().string ().c_str (),
+ nullptr};
+
+ cerr << "c++ " << *s << endl;
+
+ try
+ {
+ process pr (args);
+
+ if (!pr.wait ())
+ return target_state::failed;
+
+ // Should we go to the filesystem and get the new mtime? We
+ // know the file has been modified, so instead just use the
+ // current clock time. It has the advantage of having the
+ // subseconds precision.
+ //
+ o.mtime (system_clock::now ());
+ return target_state::updated;
+ }
+ catch (const process_error& e)
+ {
+ cerr << "error: unable to execute '" << args[0] << "': " <<
+ e.what () << endl;
+
+ if (e.child ())
+ throw; // Let caller terminate us quickly without causing a scene.
+
+ return target_state::failed;
+ }
+ }
+
+ // link
+ //
+ recipe link::
+ match (target& t) const
+ {
+ // @@ TODO:
+ //
+ // - check prerequisites: object files, libraries
+ // - if path already assigned, verify extension
+ //
+ // @@ Q:
+ //
+ // - if there is no .o, are we going to check if the one derived
+ // from target exist or can be built? If we do that, then it
+ // probably makes sense to try other rules first (two passes).
+ // What if there is a library. Probably ok if .a, not is .so.
+ //
+
+ // See if we have at least one object file.
+ //
+ const obj* o (nullptr);
+ for (const target& p: t.prerequisites ())
+ {
+ if ((o = dynamic_cast<const obj*> (&p)) != nullptr)
+ break;
+ }
+
+ if (o == nullptr)
+ return recipe ();
+
+ // Derive executable file name from target name.
+ //
+ exe& e (dynamic_cast<exe&> (t));
+
+ if (e.path ().empty ())
+ e.path (path (e.name ()));
+
+ return recipe (&update);
+ }
+
+ target_state link::
+ update (target& t)
+ {
+ // @@ Q:
+ //
+ // - what are we doing with libraries?
+ //
+
+ exe& e (dynamic_cast<exe&> (t));
+ timestamp mt (e.mtime ());
+
+ bool u (mt == timestamp_nonexistent);
+
+ for (const target& p: t.prerequisites ())
+ {
+ // Assume all our prerequisites are mtime-based (checked in
+ // match()).
+ //
+ const auto& mtp (dynamic_cast<const mtime_target&> (p));
+ timestamp mp (mtp.mtime ());
+
+ // What do we do if timestamps are equal? This can happen, for
+ // example, on filesystems that don't have subsecond resolution.
+ // There is not much we can do here except detect the case where
+ // the prerequisite was updated in this run which means the
+ // target must be out of date.
+ //
+ if (mt < mp || mt == mp && mtp.state () == target_state::updated)
+ {
+ u = true;
+ break;
+ }
+ }
+
+ if (!u)
+ return target_state::uptodate;
+
+ vector<const char*> args {"g++-4.9", "-std=c++11", "-o"};
+
+ args.push_back (e.path ().string ().c_str ());
+
+ for (const target& p: t.prerequisites ())
+ {
+ const obj& o (dynamic_cast<const obj&> (p));
+ args.push_back (o.path ().string ().c_str ());
+ }
+
+ args.push_back (nullptr);
+
+ cerr << "ld " << e << endl;
+
+ try
+ {
+ process pr (args.data ());
+
+ if (!pr.wait ())
+ return target_state::failed;
+
+ // Should we go to the filesystem and get the new mtime? We
+ // know the file has been modified, so instead just use the
+ // current clock time. It has the advantage of having the
+ // subseconds precision.
+ //
+ e.mtime (system_clock::now ());
+ return target_state::updated;
+ }
+ catch (const process_error& e)
+ {
+ cerr << "error: unable to execute '" << args[0] << "': " <<
+ e.what () << endl;
+
+ if (e.child ())
+ throw; // Let caller terminate us quickly without causing a scene.
+
+ return target_state::failed;
+ }
+ }
+ }
+}
diff --git a/build/cxx/target b/build/cxx/target
new file mode 100644
index 0000000..fe17c36
--- /dev/null
+++ b/build/cxx/target
@@ -0,0 +1,52 @@
+// file : build/cxx/target -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD_CXX_TARGET
+#define BUILD_CXX_TARGET
+
+#include <build/target>
+
+namespace build
+{
+ namespace cxx
+ {
+ class hxx: public file
+ {
+ public:
+ using file::file;
+
+ public: virtual const type_info& type_id () const {return ti_;}
+ protected: static const type_info ti_;
+ };
+
+ class ixx: public file
+ {
+ public:
+ using file::file;
+
+ public: virtual const type_info& type_id () const {return ti_;}
+ protected: static const type_info ti_;
+ };
+
+ class txx: public file
+ {
+ public:
+ using file::file;
+
+ public: virtual const type_info& type_id () const {return ti_;}
+ protected: static const type_info ti_;
+ };
+
+ class cxx: public file
+ {
+ public:
+ using file::file;
+
+ public: virtual const type_info& type_id () const {return ti_;}
+ protected: static const type_info ti_;
+ };
+ }
+}
+
+#endif // BUILD_CXX_TARGET
diff --git a/build/cxx/target.cxx b/build/cxx/target.cxx
new file mode 100644
index 0000000..7171e30
--- /dev/null
+++ b/build/cxx/target.cxx
@@ -0,0 +1,20 @@
+// file : build/cxx/target.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#include <build/cxx/target>
+
+using namespace std;
+
+namespace build
+{
+ namespace cxx
+ {
+ using type_info = target::type_info;
+
+ const type_info hxx::ti_ {typeid (hxx), "hxx", &file::ti_};
+ const type_info ixx::ti_ {typeid (ixx), "ixx", &file::ti_};
+ const type_info txx::ti_ {typeid (txx), "txx", &file::ti_};
+ const type_info cxx::ti_ {typeid (cxx), "cxx", &file::ti_};
+ }
+}
diff --git a/build/native b/build/native
new file mode 100644
index 0000000..29ad653
--- /dev/null
+++ b/build/native
@@ -0,0 +1,31 @@
+// file : build/native -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD_NATIVE
+#define BUILD_NATIVE
+
+#include <build/target>
+
+namespace build
+{
+ class exe: public file
+ {
+ public:
+ using file::file;
+
+ public: virtual const type_info& type_id () const {return ti_;}
+ protected: static const type_info ti_;
+ };
+
+ class obj: public file
+ {
+ public:
+ using file::file;
+
+ public: virtual const type_info& type_id () const {return ti_;}
+ protected: static const type_info ti_;
+ };
+}
+
+#endif // BUILD_NATIVE
diff --git a/build/native.cxx b/build/native.cxx
new file mode 100644
index 0000000..6130578
--- /dev/null
+++ b/build/native.cxx
@@ -0,0 +1,15 @@
+// file : build/native.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#include <build/native>
+
+using namespace std;
+
+namespace build
+{
+ using type_info = target::type_info;
+
+ const type_info exe::ti_ {typeid (exe), "exe", &file::ti_};
+ const type_info obj::ti_ {typeid (obj), "obj", &file::ti_};
+}
diff --git a/build/rule b/build/rule
new file mode 100644
index 0000000..6bb58fe
--- /dev/null
+++ b/build/rule
@@ -0,0 +1,39 @@
+// file : build/rule -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD_RULE
+#define BUILD_RULE
+
+#include <typeindex>
+#include <functional> // reference_wrapper
+#include <unordered_map>
+
+#include <build/target>
+
+namespace build
+{
+ class rule
+ {
+ public:
+ virtual recipe
+ match (target&) const = 0;
+ };
+
+ typedef std::unordered_multimap<std::type_index,
+ std::reference_wrapper<rule>> rule_map;
+
+ extern rule_map rules;
+
+ class default_path_rule: public rule
+ {
+ public:
+ virtual recipe
+ match (target&) const;
+
+ static target_state
+ update (target&);
+ };
+}
+
+#endif // BUILD_RULE
diff --git a/build/rule.cxx b/build/rule.cxx
new file mode 100644
index 0000000..fe4c4b2
--- /dev/null
+++ b/build/rule.cxx
@@ -0,0 +1,72 @@
+// file : build/rule.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#include <build/rule>
+
+#include <iostream>
+
+using namespace std;
+
+namespace build
+{
+ rule_map rules;
+
+ // default_path_rule
+ //
+ recipe default_path_rule::
+ match (target& t) const
+ {
+ // @@ TODO:
+ //
+ // - need to assign path somehow. Get (potentially several)
+ // extensions from target type? Maybe target type should
+ // generate a list of potential paths that we can try here.
+ //
+
+ path_target& pt (dynamic_cast<path_target&> (t));
+
+ return pt.mtime () != timestamp_nonexistent ? &update : nullptr;
+ }
+
+ target_state default_path_rule::
+ update (target& t)
+ {
+ // Make sure the target is not older than any of its prerequisites.
+ //
+ path_target& pt (dynamic_cast<path_target&> (t));
+ timestamp mt (pt.mtime ());
+
+ for (const target& p: t.prerequisites ())
+ {
+ // If this is an mtime-based target, then simply compare timestamps.
+ //
+ if (auto mtp = dynamic_cast<const mtime_target*> (&p))
+ {
+ if (mt < mtp->mtime ())
+ {
+ cerr << "error: no rule to update target " << t << endl
+ << "info: prerequisite " << p << " is ahead of " << t <<
+ " by " << (mtp->mtime () - mt) << endl;
+
+ return target_state::failed;
+ }
+ }
+ else
+ {
+ // Otherwise we assume the prerequisite is newer if it was updated.
+ //
+ if (p.state () == target_state::updated)
+ {
+ cerr << "error: no rule to update target " << t << endl
+ << "info: prerequisite " << p << " is ahead of " << t <<
+ " because it was updated" << endl;
+
+ return target_state::failed;
+ }
+ }
+ }
+
+ return target_state::uptodate;
+ }
+}
diff --git a/build/target b/build/target
index 4319ccb..3ef3192 100644
--- a/build/target
+++ b/build/target
@@ -7,11 +7,21 @@
#include <string>
#include <vector>
-#include <functional> // std::reference_wrapper
+#include <functional> // function, reference_wrapper
+#include <typeindex>
+#include <iosfwd>
+#include <cassert>
+
+#include <build/path>
+#include <build/timestamp>
namespace build
{
class target;
+
+ enum class target_state {unknown, uptodate, updated, failed};
+ typedef std::function<target_state (target&)> recipe;
+
typedef std::vector<std::reference_wrapper<target>> targets;
class target
@@ -32,42 +42,112 @@ namespace build
prerequisite (target& t) {prerequisites_.push_back (t);}
public:
- typedef bool (*rule_type) (target&);
+ typedef build::recipe recipe_type;
- rule_type
- rule () const {return rule_;}
+ const recipe_type&
+ recipe () const {return recipe_;}
void
- rule (rule_type r) {rule_ = r;}
+ recipe (recipe_type r) {assert (!recipe_); recipe_ = r;}
+
+ public:
+ target_state
+ state () const {return state_;}
+
+ void
+ state (target_state s) {state_ = s;}
private:
target (const target&) = delete;
target& operator= (const target&) = delete;
+ public:
+ struct type_info
+ {
+ std::type_index id;
+ const char* name;
+ const type_info* base;
+ };
+
+ virtual const type_info&
+ type_id () const = 0;
+
+ protected:
+ static const type_info ti_;
+
private:
std::string name_;
targets prerequisites_;
- rule_type rule_ {0};
+ recipe_type recipe_;
+ target_state state_ {target_state::unknown};
};
- class exe: public target
- {
- using target::target;
- };
+ std::ostream&
+ operator<< (std::ostream&, const target&);
- class obj: public target
+ // Modification time-based target.
+ //
+ class mtime_target: public target
{
+ public:
using target::target;
+
+ timestamp
+ mtime () const
+ {
+ if (mtime_ == timestamp_unknown)
+ mtime_ = load_mtime ();
+
+ return mtime_;
+ }
+
+ void
+ mtime (timestamp mt) {mtime_ = mt;}
+
+ protected:
+ virtual timestamp
+ load_mtime () const = 0;
+
+ protected: static const type_info ti_;
+
+ private:
+ mutable timestamp mtime_ {timestamp_unknown};
};
- class hxx: public target
+ // Filesystem path-bases target.
+ //
+ class path_target: public mtime_target
{
- using target::target;
+ public:
+ using mtime_target::mtime_target;
+
+ typedef build::path path_type;
+
+ const path_type&
+ path () const {return path_;}
+
+ void
+ path (path_type p) {assert (path_.empty ()); path_ = p;}
+
+ protected:
+ virtual timestamp
+ load_mtime () const;
+
+ protected: static const type_info ti_;
+
+ private:
+ path_type path_;
};
- class cxx: public target
+ // File target.
+ //
+ class file: public path_target
{
- using target::target;
+ public:
+ using path_target::path_target;
+
+ public: virtual const type_info& type_id () const {return ti_;}
+ protected: static const type_info ti_;
};
}
diff --git a/build/target.cxx b/build/target.cxx
new file mode 100644
index 0000000..e3e3d4d
--- /dev/null
+++ b/build/target.cxx
@@ -0,0 +1,38 @@
+// file : build/target.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#include <build/target>
+
+#include <ostream>
+
+using namespace std;
+
+namespace build
+{
+ // target
+ //
+ ostream&
+ operator<< (ostream& os, const target& t)
+ {
+ return os << t.type_id ().name << '{' << t.name () << '}';
+ }
+
+ // path_target
+ //
+ timestamp path_target::
+ load_mtime () const
+ {
+ assert (!path_.empty ());
+ return path_mtime (path_);
+ }
+
+ using type_info = target::type_info;
+
+ const type_info target::ti_ {typeid (target), "target", nullptr};
+ const type_info mtime_target::ti_ {
+ typeid (mtime_target), "mtime_target", &target::ti_};
+ const type_info path_target::ti_ {
+ typeid (path_target), "path_target", &mtime_target::ti_};
+ const type_info file::ti_ {typeid (file), "file", &path_target::ti_};
+}
diff --git a/build/timestamp b/build/timestamp
index 6ed2f2c..2386125 100644
--- a/build/timestamp
+++ b/build/timestamp
@@ -9,6 +9,8 @@
#include <string>
#include <iosfwd>
+#include <build/path>
+
namespace build
{
// On all three main platforms that we target (GNU/Linux, Windows (both
@@ -46,7 +48,7 @@ namespace build
// std::system_error.
//
timestamp
- path_timestamp (const std::string&);
+ path_mtime (const path&);
};
#endif // BUILD_TIMESTAMP
diff --git a/build/timestamp.cxx b/build/timestamp.cxx
index db8efb2..0ad3f3f 100644
--- a/build/timestamp.cxx
+++ b/build/timestamp.cxx
@@ -41,10 +41,10 @@ namespace build
constexpr int nsec (...) {return 0;}
timestamp
- path_timestamp (const std::string& p)
+ path_mtime (const path& p)
{
struct stat s;
- if (stat (p.c_str (), &s) != 0)
+ if (stat (p.string ().c_str (), &s) != 0)
{
if (errno == ENOENT || errno == ENOTDIR)
return timestamp_nonexistent;
@@ -108,18 +108,37 @@ namespace build
time_t t (system_clock::to_time_t (ts));
const char* fmt (nullptr);
+ const char* unt ("nanoseconds");
if (t >= 365 * 12 * 24 * 60 * 60)
+ {
fmt = "%Y-%m-%d %H:%M:%S";
+ unt = "years";
+ }
else if (t >= 12 * 24 * 60* 60)
+ {
fmt = "%m-%d %H:%M:%S";
+ unt = "months";
+ }
else if (t >= 24 * 60* 60)
+ {
fmt = "%d %H:%M:%S";
+ unt = "days";
+ }
else if (t >= 60 * 60)
+ {
fmt = "%H:%M:%S";
+ unt = "hours";
+ }
else if (t >= 60)
+ {
fmt = "%M:%S";
+ unt = "minutes";
+ }
else if (t >= 1)
+ {
fmt = "%S";
+ unt = "seconds";
+ }
if (fmt != nullptr)
{
@@ -129,7 +148,7 @@ namespace build
char buf[20]; // YYYY-MM-DD HH:MM:SS\0
if (strftime (buf, sizeof (buf), fmt, &tm) == 0)
- return os << "<beyond 9999 years>";
+ return os << "<beyond 9999>";
os << buf;
}
@@ -141,10 +160,14 @@ namespace build
if (ns != nanoseconds::zero ())
{
- os << '.';
- os.width (9);
- os.fill ('0');
- os << ns.count ();
+ if (fmt != nullptr)
+ {
+ os << '.';
+ os.width (9);
+ os.fill ('0');
+ }
+
+ os << ns.count () << ' ' << unt;
}
else if (fmt == 0)
os << '0';