aboutsummaryrefslogtreecommitdiff
path: root/bpkg/package-skeleton.hxx
blob: 947522ee9ac6af5826e17c02cfc634c845eba7f9 (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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
// file      : bpkg/package-skeleton.hxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#ifndef BPKG_PACKAGE_SKELETON_HXX
#define BPKG_PACKAGE_SKELETON_HXX

#include <libbuild2/forward.hxx>

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

#include <bpkg/package.hxx>
#include <bpkg/package-configuration.hxx>
#include <bpkg/common-options.hxx>

namespace bpkg
{
  // A build system skeleton of a package used to evaluate buildfile clauses
  // during dependency resolution (enable, reflect, require or prefer/accept).
  //
  class package_skeleton
  {
  public:
    // If the package is system, then its available package should be NULL if
    // it doesn't match the system package version "close enough" to be usable
    // as the source of its configuration information (types, defaults). If it
    // is NULL, then the skeleton can only be used to print and collect the
    // configuration information.
    //
    // If the package is being reconfigured (rather than up/downgraded), then
    // the existing package source and output root directories (src_root and
    // out_root) need to be specified (as absolute and normalized). Otherwise,
    // if the package is external, then the existing package source root
    // directory needs to be specified (as absolute and normalized). In this
    // case, if output root is specified (as absolute and normalized; normally
    // <config-dir>/<package-name>), then it's used as is. Otherwise, an empty
    // skeleton directory is used as output root.
    //
    // If the package is neither being reconfigured nor is external, then none
    // of the root directories should be specified.
    //
    // If the package is configured as source and the user and/or dependent
    // configuration is requested to be loaded from config.build, then the
    // existing package old source and output root directories (old_src_root
    // and old_out_root) need to be specified (as absolute and normalized). If
    // specified, they are used instead of package source and output root
    // directories to load the current user and/or dependent configuration.
    // The idea here is that during package upgrade/downgrade, we want to load
    // the old configuration from the old version's src/out but then continue
    // evaluating clauses using the new version's src/out.
    //
    // The disfigure argument should indicate whether the package is being
    // reconfigured from scratch (--disfigure).
    //
    // The config_vars argument contains configuration variables specified by
    // the user in this bpkg execution. Optional config_srcs is used to
    // extract (from config.build or equivalent) configuration variables
    // specified by the user in previous bpkg executions. It should be NULL if
    // this is the first build of the package. The extracted variables are
    // merged with config_vars and the combined result is returned by
    // collect_config() below.
    //
    // @@ TODO: speaking of the "config.build or equivalent" part, the
    //    equivalent is likely to be extracted configuration (probably saved
    //    to file in tmp somewhere) that we will load with config.config.load.
    //    It doesn't seem like a good idea to pass it as part of config_vars
    //    (because sometimes we may need to omit it) so most likely it will be
    //    passed as a separate arguments (likely a file path).
    //
    // Note that the options, database, and config_srcs are expected to
    // outlive this object.
    //
    // Note also that this creates an "unloaded" skeleton and is therefore
    // relatively cheap.
    //
    package_skeleton (const common_options& co,
                      package_key,
                      bool system,
                      shared_ptr<const available_package>,
                      strings config_vars,
                      bool disfigure,
                      const vector<config_variable>* config_srcs,
                      optional<dir_path> src_root,
                      optional<dir_path> out_root,
                      optional<dir_path> old_src_root,
                      optional<dir_path> old_out_root,
                      uint16_t load_config_flags);

    package_key package;
    bool system;
    shared_ptr<const available_package> available;

    // Load package (old) configuration flags.
    //
    uint16_t load_config_flags;

    static const uint16_t load_config_user      = 0x1;
    static const uint16_t load_config_dependent = 0x2;

    // The following functions should be called in the following sequence
    // (* -- zero or more, ? -- zero or one):
    //
    // * reload_defaults() | verify_sensible()
    // ? dependent_config()
    // * evaluate_*()
    // * empty() | print_config()
    // * config_checksum()
    //   collect_config()
    //
    // Note that the load_old_config() function can be called at eny point
    // before collect_config() (and is called implicitly by most other
    // functions).
    //
    // Note that a copy of the skeleton is expected to continue with the
    // sequence rather than starting from scratch, unless reset() is called.
    //
  public:
    // Reload the default values and type information for configuration
    // variables using the values with the buildfile origin as a "tentative"
    // dependent configuration.
    //
    void
    reload_defaults (package_configuration&);

    // Load overrides for a system package without skeleton info. Note that
    // this is done in an ad hoc manner and only to support evaluate_require()
    // semantics (see the implementation for details).
    //
    void
    load_overrides (package_configuration&);

    // Verify the specified "tentative" dependent configuration is sensible,
    // that is, acceptable to the dependency itself. If it is not, then the
    // second half of the result contains the diagnostics.
    //
    pair<bool, string>
    verify_sensible (const package_configuration&);

    // Incorporate the "final" dependent configuration into subsequent
    // evaluations. Dependent configuration variables are expected not to
    // clash with user.
    //
    void
    dependent_config (const package_configuration&);

    // For the following evaluate_*() functions assume that the clause belongs
    // to the dependency alternative specified as a pair of indexes (depends
    // value index and alternative index).

    // Evaluate the enable clause.
    //
    bool
    evaluate_enable (const string&, pair<size_t, size_t>);

    // Evaluate the reflect clause.
    //
    void
    evaluate_reflect (const string&, pair<size_t, size_t>);

    // Evaluate the prefer/accept or require clauses on the specified
    // dependency configurations (serves as both input and output).
    //
    // Return true is acceptable and false otherwise. If acceptable, the
    // passed configuration is updated with new values, if any.
    //
    using dependency_configurations =
      small_vector<reference_wrapper<package_configuration>, 1>;

    bool
    evaluate_prefer_accept (const dependency_configurations&,
                            const string&, const string&, pair<size_t, size_t>,
                            bool has_alternative);

    bool
    evaluate_require (const dependency_configurations&,
                      const string&, pair<size_t, size_t>,
                      bool has_alternative);

    // Reset the skeleton to the start of the call sequence.
    //
    // Note that this function cannot be called after collect_config().
    //
    void
    reset ();

    // Return true if there are no accumulated *project* configuration
    // variables that will be printed by print_config().
    //
    bool
    empty_print ();

    // Print the accumulated *project* configuration variables as command line
    // overrides one per line with the specified indentation.
    //
    void
    print_config (ostream&, const char* indent);

    // Load the package's old configuration, unless it is already loaded.
    //
    void
    load_old_config ();

    // Return the accumulated configuration variables (first) and project
    // configuration variable sources (second). Note that the arrays are not
    // necessarily parallel (config_vars may contain non-project variables).
    //
    // Note that the dependent and reflect variables are merged with
    // config_vars/config_srcs and should be used instead rather than in
    // addition to config_vars.
    //
    // Note also that this should be the final call on this object.
    //
    pair<strings, vector<config_variable>>
    collect_config () &&;

    // Return the checksum of the project configuration variables that will be
    // returned by the collect_config() function call.
    //
    string
    config_checksum ();

    // Implementation details.
    //
  public:
    // We have to define these because context is forward-declared. Also, copy
    // constructor has some special logic.
    //
    ~package_skeleton ();
    package_skeleton (package_skeleton&&) noexcept;
    package_skeleton& operator= (package_skeleton&&) noexcept;

    package_skeleton (const package_skeleton&);
    package_skeleton& operator= (const package_skeleton&) = delete;

  private:
    // Load old user and/or dependent configuration variables from
    // config.build (or equivalent) and merge them into config_vars_ and
    // config_var_srcs_. Also verify new user configuration already in
    // config_vars_ makes sense.
    //
    // This should be done before any attempt to load the configuration with
    // config.config.disfigure and, if this did not happen, inside
    // collect_config() (since the package will be reconfigured with
    // config.config.disfigure).
    //
    void
    load_old_config_impl ();

    // (Re)load the build system state.
    //
    // Call this function before evaluating every clause.
    //
    // If dependency configurations are specified, then typify the variables
    // and set their values. If defaults is false, then only typify the
    // variables and set overrides without setting the default/buildfile
    // values. Note that buildfile values have value::extra set to 2. While
    // at it, also remove from dependency_var_prefixes_ and add to
    // dependency_var_prefixes variable prefixes (config.<project>) for
    // the passed dependencies.
    //
    build2::scope&
    load (const dependency_configurations& = {},
          strings* dependency_var_prefixes = nullptr,
          bool defaults = true);

    // Merge command line variable overrides into one list (normally to be
    // passed to bootstrap()).
    //
    // If cache is true, then assume the result can be reused on subsequent
    // calls.
    //
    const strings&
    merge_cmd_vars (const strings& dependent_vars,
                    const strings& dependency_vars = {},
                    bool cache = false);

    // Implementation details (public for bootstrap()).
    //
  public:
    // NOTE: remember to update move/copy constructors!
    //
    const common_options* co_;
    database* db_;

    string var_prefix_; // config.<project>

    strings config_vars_;

    // Configuration sources for variables in config_vars_ (parallel). Can
    // only contain config_source::{user,dependent} entries (see
    // load_old_config_impl() for details).
    //
    vector<config_source> config_var_srcs_;

    bool disfigure_;
    const vector<config_variable>* config_srcs_; // NULL if nothing to do or
                                                 // already done.

    dir_path src_root_; // Must be absolute and normalized.
    dir_path out_root_; // If empty, the same as src_root_.

    // True if the existing source root directory has been specified.
    //
    // Note that if that's the case, we can use the manifest file this
    // directory contains for diagnostics.
    //
    bool src_root_specified_ = false;

    // If specified, are used instead of {src,out}_root_ for loading of the
    // project configuration variables.
    //
    dir_path old_src_root_;
    dir_path old_out_root_;

    bool created_ = false;
    bool verified_ = false;
    bool loaded_old_config_;
    bool develop_ = true;  // Package has config.*.develop.

    unique_ptr<build2::context> ctx_;
    build2::scope* rs_ = nullptr;

    // Storage for merged build2_cmd_vars and config_vars_ and extra overrides
    // (like config.config.disfigure). If cache is true, then the existing
    // content can be reused.
    //
    strings cmd_vars_;
    bool cmd_vars_cache_ = false;

    strings             dependent_vars_; // Dependent variable overrides.
    vector<package_key> dependent_orgs_; // Dependent originators (parallel).

    // Reflect variable value storage. Used for both real reflect and
    // dependency reflect.
    //
    struct reflect_variable_value
    {
      string                          name;
      build2::config::variable_origin origin;
      optional<string>                type;
      optional<build2::names>         value;
    };

    class reflect_variable_values: public vector<reflect_variable_value>
    {
    public:
      const reflect_variable_value*
      find (const string& name)
      {
        auto i (find_if (begin (), end (),
                         [&name] (const reflect_variable_value& v)
                         {
                           return v.name == name;
                         }));
        return i != end () ? &*i : nullptr;
      }
    };

    reflect_variable_values reflect_; // Reflect variables.

    // Dependency configuration variables set by the prefer/require clauses
    // and that should be reflected in subsequent clauses.
    //
    // The same prefer/require clause could be re-evaluated multiple times in
    // which case the previous dependency reflect values from this clause (but
    // not from any previous clauses) should be dropped. This is achieved by
    // keeping track of the depends_index for the most recently evaluated
    // prefer/require clause along with the position of the first element that
    // was added by this clause. Note also that this logic does the right
    // thing if we move to a different dependency alternative withing the same
    // depends value.
    //
    reflect_variable_values dependency_reflect_;
    size_t                  dependency_reflect_index_ = 0;
    size_t                  dependency_reflect_pending_ = 0;

    // List of variable prefixes (config.<project>) of all known dependencies.
    //
    // This information is used to detect and diagnose references to undefined
    // dependency configuration variables (for example, those that were not
    // set and therefore not reflected). The pending index is used to ignore
    // the entries added by the last evaluate_prefer_accept() in the following
    // reflect clause (see prefer_accept_ below for details).
    //
    strings dependency_var_prefixes_;
    size_t  dependency_var_prefixes_pending_ = 0;

    // Position of the last successfully evaluated prefer/accept clauses.
    //
    // This information is used to make all (as opposed to only those set by
    // the prefer clause) dependency configuration variables available to the
    // reflect clause but only at the same position. This allows for some more
    // advanced configuration techniques, such as, using a feature if enabled
    // by someone else but not having any preferences ourselves.
    //
    optional<pair<size_t, size_t>> prefer_accept_;
  };
}

#endif // BPKG_PACKAGE_SKELETON_HXX