aboutsummaryrefslogtreecommitdiff
path: root/bpkg/package-configuration.cxx
blob: 3ddf68efab6021ec4916de122898f26687859a78 (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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// file      : bpkg/package-configuration.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <bpkg/package-configuration.hxx>

#include <bpkg/package-skeleton.hxx>

namespace bpkg
{
  using build2::config::variable_origin;

  bool
  up_negotiate_configuration (
    package_configurations& cfgs,
    package_skeleton& dept,
    pair<size_t, size_t> pos,
    const small_vector<reference_wrapper<package_skeleton>, 1>& depcs)
  {
    pos.first--; pos.second--; // Convert to 0-base.

    const dependency_alternative& da (
      dept.available.get ().dependencies[pos.first][pos.second]);

    assert (da.require || da.prefer);

    // Step 1: save a snapshot of the old configuration while unsetting values
    // that have this dependent as the originator and reloading the defaults.
    //
    // While at it, also collect the configurations to pass to dependent's
    // evaluate_*() calls.
    //
    // Our assumptions regarding require:
    //
    // - Can only set bool configuration variables and only to true.
    //
    // - Should not have any conditions on the state of other configuration
    //   variables, including their origin (but can have other conditions,
    //   for example on the target platform).
    //
    // This means that we don't need to set the default values, but will need
    // the type information as well as overrides. So what we will do is only
    // call reload_defaults() for the first time to load types/override. Note
    // that this assumes the set of configuration variables cannot change
    // based on the values of other configuration variables (we have a note
    // in the manual instructing the user not to do this).
    //
    dependent_config_variable_values old_cfgs;
    package_skeleton::dependency_configurations depc_cfgs;
    depc_cfgs.reserve (depcs.size ());

    for (package_skeleton& depc: depcs)
    {
      package_configuration& cfg (cfgs[depc.key]);

      for (config_variable_value& v: cfg)
      {
        if (v.origin == variable_origin::buildfile)
        {
          if (*v.dependent == dept.key)
          {
            old_cfgs.push_back (
              dependent_config_variable_value {
                v.name, move (v.value), move (*v.dependent)});

            // Note that we will not reload it to default in case of require.
            //
            v.origin = variable_origin::undefined;
            v.value = nullopt;
            v.dependent = nullopt;
          }
          else
            old_cfgs.push_back (
              dependent_config_variable_value {v.name, v.value, *v.dependent});
        }
      }

      if (da.prefer || cfg.empty ())
        depc.reload_defaults (cfg);

      depc_cfgs.push_back (cfg);
    }

    // Step 2: execute the prefer/accept or requires clauses.
    //
    if (!(da.require
          ? dept.evaluate_require (depc_cfgs, *da.require, pos.first)
          : dept.evaluate_prefer_accept (depc_cfgs,
                                         *da.prefer, *da.accept, pos.first)))
    {
      fail << "unable to negotiate acceptable configuration"; // @@ TODO
    }

    // Check if anything changed by comparing to entries in old_cfgs.
    //
    // While at it, also detect if we have any changes where one dependent
    // overrides a value set by another dependent (see below).
    //
    bool cycle (false);
    {
      optional<size_t> n (0); // Number of unchanged.

      for (package_skeleton& depc: depcs)
      {
        package_configuration& cfg (cfgs[depc.key]);

        for (config_variable_value& v: cfg)
        {
          if (v.origin == variable_origin::buildfile)
          {
            auto i (find_if (old_cfgs.begin (), old_cfgs.end (),
                             [&v] (const dependent_config_variable_value& o)
                             {
                               return v.name == o.name;
                             }));

            if (i != old_cfgs.end ())
            {
              if (i->value == v.value)
              {
                // If the value hasn't change, so shouldn't the originating
                // dependent.
                //
                assert (i->dependent == *v.dependent);

                if (n)
                  ++*n;

                continue;
              }
              else
              {
                assert (i->dependent != *v.dependent);
                cycle = true;
              }
            }

            n = nullopt;

            if (cycle)
              break;
          }
        }

        if (!n && cycle)
          break;
      }

      // If we haven't seen any changed and we've seen the same number, then
      // nothing has changed.
      //
      if (n && *n == old_cfgs.size ())
        return false;
    }

    // Besides the dependent returning false from its accept clause, there is
    // another manifestation of the inability to negotiate an acceptable
    // configuration: two dependents keep changing the same configuration to
    // mutually unacceptable values. To detect this, we need to look for
    // negotiation cycles.
    //
    // Specifically, given a linear change history in the form:
    //
    //   O->N ... O->N ... O->N
    //
    // We need to look for a possibility of turning it into a cycle:
    //
    //   O->N ... O->N
    //    \   ...   /
    //
    // Where O->N is a change that involves one dependent overriding a value
    // set by another dependent and `...` are identical history segments.
    //
    if (!cycle)
      return true;

    // Populate new_cfgs.
    //
    dependent_config_variable_values new_cfgs;
    for (package_skeleton& depc: depcs)
    {
      package_configuration& cfg (cfgs[depc.key]);

      for (config_variable_value& v: cfg)
      {
        if (v.origin == variable_origin::buildfile)
        {
          new_cfgs.push_back (
            dependent_config_variable_value {v.name, v.value, *v.dependent});
        }
      }
    }

    // Sort both.
    //
    {
      auto cmp = [] (const dependent_config_variable_value& x,
                     const dependent_config_variable_value& y)
      {
        return x.name < y.name;
      };

      sort (old_cfgs.begin (), old_cfgs.end (), cmp);
      sort (new_cfgs.begin (), new_cfgs.end (), cmp);
    }

    // Look backwards for identical O->N changes and see if we can come
    // up with two identical segments between them.
    //
    cycle = false;

    auto& change_history (cfgs.change_history_);

    for (size_t n (change_history.size ()), i (n); i != 0; i -= 2)
    {
      if (change_history[i - 2] == old_cfgs &&
          change_history[i - 1] == new_cfgs)
      {
        size_t d (n - i); // Segment length.

        // See if there is an identical segment before this that also starts
        // with O->N.
        //
        if (i < 2 + d + 2)
          break; // Not long enough to possibly find anything.

        size_t j (i - 2 - d); // Earlier O->N.

        if (change_history[j - 2] == old_cfgs &&
            change_history[j - 1] == new_cfgs)
        {
          if (equal (change_history.begin () + j,
                     change_history.begin () + j + d,
                     change_history.begin () + i))
          {
            cycle = true;
            break;
          }
        }

        // Otherwise, keep looking for a potentially longer segment.
      }
    }

    if (cycle)
    {
      // @@ TODO
      //
      // Here we can analyze the O->N change history and determine the other
      // problematic dependent(s). Do we actually know for sure they are all
      // problematic? Well, they repeatedly changed the values so I guess so.
      //
      fail << "unable to negotiate acceptable configuration (cycle)";
    }

    change_history.push_back (move (old_cfgs));
    change_history.push_back (move (new_cfgs));

    return true;
  }
}