aboutsummaryrefslogtreecommitdiff
path: root/build2/action.hxx
blob: eeb73fdafa6ed01a3aa70190689565d0023f8738 (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
// file      : build2/action.hxx -*- C++ -*-
// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
// license   : MIT; see accompanying LICENSE file

#ifndef BUILD2_ACTION_HXX
#define BUILD2_ACTION_HXX

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

namespace build2
{
  // While we are using uint8_t for the meta/operation ids, we assume
  // that each is limited to 4 bits (max 128 entries) so that we can
  // store the combined action id in uint8_t as well. This makes our
  // life easier when it comes to defining switch labels for action
  // ids (no need to mess with endian-ness).
  //
  // Note that 0 is not a valid meta/operation/action id.
  //
  using meta_operation_id = uint8_t;
  using operation_id = uint8_t;
  using action_id = uint8_t;

  // Meta-operations and operations are not the end of the story. We also have
  // operation nesting (currently only one level deep) which is used to
  // implement pre/post operations (currently, but may be useful for other
  // things). Here is the idea: the test operation needs to make sure that the
  // targets that it needs to test are up-to-date. So it runs update as its
  // pre-operation. It is almost like an ordinary update except that it has
  // test as its outer operation (the meta-operations are always the same).
  // This way a rule can recognize that this is "update for test" and do
  // something differently. For example, if an executable is not a test, then
  // there is no use updating it. At the same time, most rules will ignore the
  // fact that this is a nested update and for them it is "update as usual".
  //
  // This inner/outer operation support is implemented by maintaining two
  // independent "target states" (see target::state; initially we tried to do
  // it via rule/recipe override but that didn't end up well, to put it
  // mildly). While the outer operation normally "directs" the inner, inner
  // rules can still be matched/executed directly, without outer's involvement
  // (e.g., because of other inner rules). A typical implementation of an
  // outer rule either returns noop or delegates to the inner rule. In
  // particular, it should not replace or override the inner's logic.
  //
  // While most of the relevant target state is duplicated, certain things are
  // shared among the inner/outer rules, such as the target data pad and the
  // group state. In particular, it is assumed the group state is always
  // determined by the inner rule (see resolve_members()).
  //
  // Normally, an outer rule will be responsible for any additional, outer
  // operation-specific work. Sometimes, however, the inner rule needs to
  // customize its behavior. In this case the outer and inner rules must
  // communicate this explicitly (normally via the target's data pad) and
  // there is a number of restrictions to this approach. See
  // cc::{link,install}_rule for details.
  //
  struct action
  {
    action (): inner_id (0), outer_id (0) {} // Invalid action.

    // If this is not a nested operation, then outer should be 0.
    //
    action (meta_operation_id m, operation_id inner, operation_id outer = 0)
        : inner_id ((m << 4) | inner),
          outer_id (outer == 0 ? 0 : (m << 4) | outer) {}

    meta_operation_id
    meta_operation () const {return inner_id >> 4;}

    operation_id
    operation () const {return inner_id & 0xF;}

    operation_id
    outer_operation () const {return outer_id & 0xF;}

    bool inner () const {return outer_id == 0;}
    bool outer () const {return outer_id != 0;}

    action
    inner_action () const
    {
      return action (meta_operation (), operation ());
    }

    // Implicit conversion operator to action_id for the switch() statement,
    // etc. Most places only care about the inner operation.
    //
    operator action_id () const {return inner_id;}

    action_id inner_id;
    action_id outer_id;
  };

  inline bool
  operator== (action x, action y)
  {
    return x.inner_id == y.inner_id && x.outer_id == y.outer_id;
  }

  inline bool
  operator!= (action x, action y) {return !(x == y);}

  bool operator>  (action, action) = delete;
  bool operator<  (action, action) = delete;
  bool operator>= (action, action) = delete;
  bool operator<= (action, action) = delete;

  ostream&
  operator<< (ostream&, action); // operation.cxx

  // Inner/outer operation state container.
  //
  template <typename T>
  struct action_state
  {
    T data[2]; // [0] -- inner, [1] -- outer.

    T&       operator[] (action a)       {return data[a.inner () ? 0 : 1];}
    const T& operator[] (action a) const {return data[a.inner () ? 0 : 1];}
  };

  // Id constants for build-in and pre-defined meta/operations.
  //
  const meta_operation_id noop_id      = 1; // nomop?
  const meta_operation_id perform_id   = 2;
  const meta_operation_id configure_id = 3;
  const meta_operation_id disfigure_id = 4;
  const meta_operation_id create_id    = 5;
  const meta_operation_id dist_id      = 6;
  const meta_operation_id info_id      = 7;

  // The default operation is a special marker that can be used to indicate
  // that no operation was explicitly specified by the user. If adding
  // something here remember to update the man page.
  //
  const operation_id default_id            = 1; // Shall be first.
  const operation_id update_id             = 2; // Shall be second.
  const operation_id clean_id              = 3;

  const operation_id test_id               = 4;
  const operation_id update_for_test_id    = 5; // update(for test) alias.

  const operation_id install_id            = 6;
  const operation_id uninstall_id          = 7;
  const operation_id update_for_install_id = 8; // update(for install) alias.

  const action_id perform_update_id     = (perform_id << 4) | update_id;
  const action_id perform_clean_id      = (perform_id << 4) | clean_id;
  const action_id perform_test_id       = (perform_id << 4) | test_id;
  const action_id perform_install_id    = (perform_id << 4) | install_id;
  const action_id perform_uninstall_id  = (perform_id << 4) | uninstall_id;

  const action_id configure_update_id   = (configure_id << 4) | update_id;

  // Recipe execution mode.
  //
  // When a target is a prerequisite of another target, its recipe can be
  // executed before the dependent's recipe (the normal case) or after.
  // We will call these "front" and "back" execution modes, respectively
  // (think "the prerequisite is 'front-running' the dependent").
  //
  // There could also be several dependent targets and the prerequisite's
  // recipe can be execute as part of the first dependent (the normal
  // case) or last (or for all/some of them; see the recipe execution
  // protocol in <target>). We will call these "first" and "last"
  // execution modes, respectively.
  //
  // Now you may be having a hard time imagining where a mode other than
  // the normal one (first/front) could be useful. An the answer is,
  // compensating or inverse operations such as clean, uninstall, etc.
  // If we use the last/back mode for, say, clean, then we will remove
  // targets in the order inverse to the way they were updated. While
  // this sounds like an elegant idea, are there any practical benefits
  // of doing it this way? As it turns out there is (at least) one: when
  // we are removing a directory (see fsdir{}), we want to do it after
  // all the targets that depend on it (such as files, sub-directories)
  // were removed. If we do it before, then the directory won't be empty
  // yet.
  //
  // It appears that this execution mode is dictated by the essence of
  // the operation. Constructive operations (those that "do") seem to
  // naturally use the first/front mode. That is, we need to "do" the
  // prerequisite first before we can "do" the dependent. While the
  // destructive ones (those that "undo") seem to need last/back. That
  // is, we need to "undo" all the dependents before we can "undo" the
  // prerequisite (say, we need to remove all the files before we can
  // remove their directory).
  //
  // If you noticed the parallel with the way C++ construction and
  // destruction works for base/derived object then you earned a gold
  // star!
  //
  // Note that the front/back mode is realized in the dependen's recipe
  // (which is another indication that it is a property of the operation).
  //
  enum class execution_mode {first, last};
}

#endif // BUILD2_ACTION_HXX