aboutsummaryrefslogtreecommitdiff
path: root/build2/depdb.hxx
blob: 906cbe52e72ee467eb7f72935856586126020f61 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// file      : build2/depdb.hxx -*- C++ -*-
// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
// license   : MIT; see accompanying LICENSE file

#ifndef BUILD2_DEPDB_HXX
#define BUILD2_DEPDB_HXX

#include <fstream>
#include <cstring> // strlen()

#include <build2/types.hxx>
#include <build2/utility.hxx>

namespace build2
{
  // Auxiliary dependency database (those .d files). Uses io_error and
  // system_error exceptions to signal errors except for openning (see
  // below).
  //
  // 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.
  //
  class depdb
  {
  public:
    // 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.
    //
    // If the database cannot be opened, issue diagnostics and throw failed.
    // This commonly happens when the user tries to stash the target in a
    // non-existent subdirectory but forgets to add the corresponding fsdir{}
    // prerequisite. Handling this as io_error in every rule that uses depdb
    // would be burdensome thus we issue the diagnostics here.
    //
    depdb (const path&);

    // Return the modification time of the database. This value only makes
    // sense while reading (in the write mode it will be timestamp_unknown).
    //
    timestamp
    mtime () const {return mtime_;}

    // 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).
    //
    void
    touch () {touch_ = true;}

    // 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.
    //
    void
    close ();

    // 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 available. 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;}

    bool
    touched () const {return touch_;}

    // 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& 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& v)
    {
      string* l (read ());
      if (l == nullptr || path::traits::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;
    }

  private:
    void
    change (bool flush = true);

    string*
    read_ ();

  private:
    timestamp mtime_;
    std::fstream fs_;

    std::fstream::pos_type pos_; // Start of the last returned line.
    string line_;

    enum class state {read, read_eof, write} state_;
    bool touch_;
  };
}

#endif // BUILD2_DEPDB_HXX