aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/depdb.hxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/depdb.hxx')
-rw-r--r--libbuild2/depdb.hxx288
1 files changed, 288 insertions, 0 deletions
diff --git a/libbuild2/depdb.hxx b/libbuild2/depdb.hxx
new file mode 100644
index 0000000..8a1cd1f
--- /dev/null
+++ b/libbuild2/depdb.hxx
@@ -0,0 +1,288 @@
+// file : libbuild2/depdb.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_DEPDB_HXX
+#define LIBBUILD2_DEPDB_HXX
+
+#include <cstring> // strlen()
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ // Auxiliary dependency database (those .d files). Prints the diagnostics
+ // and fails on system and IO errors.
+ //
+ // This is a strange beast: a line-oriented, streaming database that can, at
+ // some point, be switched from reading to (over)writing. The idea is to
+ // store auxiliary/ad-hoc dependency information in the "invalidation"
+ // order. That is, if an earlier line is out of date, then all the
+ // subsequent ones are out of date as well.
+ //
+ // As an example, consider a dependency database for foo.o which is built
+ // from foo.cxx by the cxx.compile rule. The first line could be the rule
+ // name itself (perhaps with the version). If a different rule is now
+ // building foo.o, then any dep info that was saved by cxx.compile is
+ // probably useless. Next we can have the command line options that were
+ // used to build foo.o. Then could come the source file name followed by the
+ // extracted header dependencies. If the compile options or the source file
+ // name have changed, then the header dependencies are likely to have
+ // changed as well.
+ //
+ // As an example, here is what our foo.o.d could look like (the first line
+ // is the database format version and the last '\0' character is the end
+ // marker):
+ //
+ // 1
+ // cxx.compile 1
+ // g++-4.8 -I/tmp/foo -O3
+ // /tmp/foo/foo.cxx
+ // /tmp/foo/foo.hxx
+ // /usr/include/string.h
+ // /usr/include/stdlib.h
+ // /tmp/foo/bar.hxx
+ // ^@
+ //
+ // A race is possible between updating the database and the target. For
+ // example, we may detect a line mismatch that renders the target out-of-
+ // date (say, compile options in the above example). We update the database
+ // but before getting a chance to update the target, we get interrupted. On
+ // a subsequent re-run, because the database has been updated, we will miss
+ // the "target requires update" condition.
+ //
+ // If we assume that an update of the database also means an update of the
+ // target, then this "interrupted update" situation can be easily detected
+ // by comparing the database and target modification timestamps. This is
+ // also used to handle the dry-run mode where we essentially do the
+ // interruption ourselves.
+ //
+ struct LIBBUILD2_SYMEXPORT depdb_base
+ {
+ explicit
+ depdb_base (const path&, timestamp);
+
+ ~depdb_base ();
+
+ enum class state {read, read_eof, write} state_;
+
+ union
+ {
+ ifdstream is_; // read, read_eof
+ ofdstream os_; // write
+ };
+
+ butl::fdbuf* buf_; // Current buffer (for tellg()/tellp()).
+ };
+
+ class LIBBUILD2_SYMEXPORT depdb: private depdb_base
+ {
+ public:
+ using path_type = build2::path;
+
+ // The modification time of the database only makes sense while reading
+ // (in the write mode it will be set to timestamp_unknown).
+ //
+ // If touch is set to true, update the database modification time in
+ // close() even if otherwise no modifications are necessary (i.e., the
+ // database is in the read mode and is at eof).
+ //
+ path_type path;
+ timestamp mtime;
+ bool touch;
+
+ // Open the database for reading. Note that if the file does not exist,
+ // has wrong format version, or is corrupt, then the database will be
+ // immediately switched to writing.
+ //
+ // The failure commonly happens when the user tries to stash the target in
+ // a non-existent subdirectory but forgets to add the corresponding fsdir{}
+ // prerequisite. That's why the issued diagnostics may provide the
+ // corresponding hint.
+ //
+ explicit
+ depdb (path_type);
+
+ // Close the database. If this function is not called, then the database
+ // may be left in the old/currupt state. Note that in the read mode this
+ // function will "chop off" lines that haven't been read.
+ //
+ // Make sure to also call check_mtime() after updating the target to
+ // perform the target/database modification times sanity checks.
+ //
+ void
+ close ();
+
+ // Flush any unwritten data to disk. This is primarily useful when reusing
+ // a (partially written) database as an input to external programs (e.g.,
+ // as a module map).
+ //
+ void
+ flush ();
+
+ // Perform target/database modification times sanity check.
+ //
+ // Note that it would also be good to compare the target timestamp against
+ // the newest prerequisite. However, obtaining this information would cost
+ // extra (see execute_prerequisites()). So maybe later, if we get a case
+ // where this is a problem (in a sense, the database is a buffer between
+ // prerequisites and the target).
+ //
+ void
+ check_mtime (const path_type& target, timestamp end = timestamp_unknown);
+
+ static void
+ check_mtime (timestamp start,
+ const path_type& db,
+ const path_type& target,
+ timestamp end);
+
+ // Return true if mtime checks are enabled.
+ //
+ static bool
+ mtime_check ();
+
+ // Read the next line. If the result is not NULL, then it is a pointer to
+ // the next line in the database (which you are free to move from). If you
+ // then call write(), this line will be overwritten.
+ //
+ // If the result is NULL, then it means no next line is unavailable. This
+ // can be due to several reasons:
+ //
+ // - eof reached (you can detect this by calling more() before read())
+ // - database is already in the write mode
+ // - the next line (and the rest of the database are corrupt)
+ //
+ string*
+ read () {return state_ == state::write ? nullptr : read_ ();}
+
+ // Return true if the database is in the read mode and there is at least
+ // one more line available. Note that there is no guarantee that the line
+ // is not corrupt. In other words, read() can still return NULL, it just
+ // won't be because of eof.
+ //
+ bool
+ more () const {return state_ == state::read;}
+
+ bool
+ reading () const {return state_ != state::write;}
+
+ bool
+ writing () const {return state_ == state::write;}
+
+ // Skip to the end of the database and return true if it is valid.
+ // Otherwise, return false, in which case the database must be
+ // overwritten. Note that this function expects the database to be in the
+ // read state.
+ //
+ bool
+ skip ();
+
+ // Write the next line. If nl is false then don't write the newline yet.
+ // Note that this switches the database into the write mode and no further
+ // reading will be possible.
+ //
+ void
+ write (const string& l, bool nl = true) {write (l.c_str (), l.size (), nl);}
+
+ void
+ write (const path_type& p, bool nl = true) {write (p.string (), nl);}
+
+ void
+ write (const char* s, bool nl = true) {write (s, std::strlen (s), nl);}
+
+ void
+ write (const char*, size_t, bool nl = true);
+
+ void
+ write (char, bool nl = true);
+
+ // Mark the previously read line as to be overwritte.
+ //
+ void
+ write () {if (state_ != state::write) change ();}
+
+ // Read the next line and compare it to the expected value. If it matches,
+ // return NULL. Otherwise, overwrite it and return the old value (which
+ // could also be NULL). This strange-sounding result semantics is used to
+ // detect the "there is a value but it does not match" case for tracing:
+ //
+ // if (string* o = d.expect (...))
+ // l4 ([&]{trace << "X mismatch forcing update of " << t;});
+ //
+ string*
+ expect (const string& v)
+ {
+ string* l (read ());
+ if (l == nullptr || *l != v)
+ {
+ write (v);
+ return l;
+ }
+
+ return nullptr;
+ }
+
+ string*
+ expect (const path_type& v)
+ {
+ string* l (read ());
+ if (l == nullptr ||
+ path_type::traits_type::compare (*l, v.string ()) != 0)
+ {
+ write (v);
+ return l;
+ }
+
+ return nullptr;
+ }
+
+ string*
+ expect (const char* v)
+ {
+ string* l (read ());
+ if (l == nullptr || *l != v)
+ {
+ write (v);
+ return l;
+ }
+
+ return nullptr;
+ }
+
+ // Could be supported if required.
+ //
+ depdb (depdb&&) = delete;
+ depdb (const depdb&) = delete;
+
+ depdb& operator= (depdb&&) = delete;
+ depdb& operator= (const depdb&) = delete;
+
+ private:
+ depdb (path_type&&, timestamp);
+
+ void
+ change (bool truncate = true);
+
+ string*
+ read_ ();
+
+ void
+ check_mtime_ (const path_type&, timestamp);
+
+ static void
+ check_mtime_ (timestamp, const path_type&, const path_type&, timestamp);
+
+ private:
+ uint64_t pos_; // Start of the last returned line.
+ string line_; // Current line.
+ timestamp start_; // Sequence start (mtime check).
+ };
+}
+
+#include <libbuild2/depdb.ixx>
+
+#endif // LIBBUILD2_DEPDB_HXX