aboutsummaryrefslogtreecommitdiff
path: root/bpkg/system-package-manager.hxx
blob: 7f5af7dd6de56f8b181d4a4c3410b92d99fca454 (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
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
// file      : bpkg/system-package-manager.hxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#ifndef BPKG_SYSTEM_PACKAGE_MANAGER_HXX
#define BPKG_SYSTEM_PACKAGE_MANAGER_HXX

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

#include <libbutl/path-map.hxx>
#include <libbutl/host-os-release.hxx>

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

namespace bpkg
{
  using os_release = butl::os_release;

  // The system/distribution package manager interface. Used by both pkg-build
  // (to query and install system packages) and by pkg-bindist (to generate
  // them).
  //
  // Note that currently the result of a query is a single available version.
  // While some package managers may support having multiple available
  // versions and may even allow installing multiple versions in parallel,
  // supporting this on our side will complicate things quite a bit. While we
  // can probably plug multiple available versions into our constraint
  // satisfaction machinery, the rabbit hole goes deeper than that since, for
  // example, different bpkg packages can be mapped to the same system
  // package, as is the case for libcrypto/libssl which are both mapped to
  // libssl on Debian. This means we will need to somehow coordinate (and
  // likely backtrack) version selection between unrelated bpkg packages
  // because only one underlying system version can be selected. (One
  // simplified way to handle this would be to detect that different versions
  // we selected and fail asking the user to resolve this manually.)
  //
  // Additionally, parallel installation is unlikely to be suppored for the
  // packages we are interested in due to the underlying limitations.
  // Specifically, the packages that we are primarily interested in are
  // libraries with headers and executables (tools). While most package
  // managers (e.g., Debian, Fedora) are able to install multiple libraries in
  // parallel, they normally can only install a single set of headers, static
  // libraries, pkg-config files, etc., (e.g., -dev/-devel package) at a time
  // due to them being installed into the same location (e.g., /usr/include).
  // The same holds for executables, which are installed into the same
  // location (e.g., /usr/bin).
  //
  // It is possible that a certain library has made arrangements for
  // multiple of its versions to co-exist. For example, hypothetically, our
  // libssl package could be mapped to both libssl1.1 libssl1.1-dev and
  // libssl3 libssl3-dev which could be installed at the same time (note
  // that it is not the case in reality; there is only libssl-dev). However,
  // in this case, we should probably also have two packages with separate
  // names (e.g., libssl and libssl3) that can also co-exist. An example of
  // this would be libQt5Core and libQt6Core. (Note that strictly speaking
  // there could be different degrees of co-existence: for the system
  // package manager it is sufficient for different versions not to clobber
  // each other's files while for us we may also need the ability to use
  // different versions in the base build).
  //
  // Note also that the above reasoning is quite C/C++-centric and it's
  // possible that multiple versions of libraries (or equivalent) for other
  // languages (e.g., Rust) can always co-exist. Plus, even in the case of
  // C/C++ libraries, there is still the plausible case of picking one of
  // the multiple available version.
  //
  // On the other hand, the ultimate goal of system package managers, at least
  // traditional ones like Debian and Fedora, is to end up with a single,
  // usually the latest available, version of the package that is used by
  // everyone. In fact, if one looks at a stable distributions of Debian and
  // Fedora, they normally provide only a single version of each package. This
  // decision will also likely simplify the implementation. For example, on
  // Debian, it's straightforward to get the installed and candidate versions
  // (e.g., from apt-cache policy). But getting all the possible versions that
  // can be installed without having to specify the release explicitly is a
  // lot less straightforward (see the apt-cache command documentation in The
  // Debian Administrator's Handbook for background).
  //
  // So for now we keep it simple and pick a single available version but can
  // probably revise this decision later.
  //
  struct system_package_status
  {
    // Downstream (as in, bpkg package) version.
    //
    bpkg::version version;

    // System (as in, distribution) package name and version for diagnostics.
    //
    // Note that this status may represent multiple system packages (for
    // example, libfoo and libfoo-dev) and here we have only the
    // main/representative package name (for example, libfoo).
    //
    string system_name;
    string system_version;

    // The system package can be either "available already installed",
    // "available partially installed" (for example, libfoo but not
    // libfoo-dev is installed) or "available not yet installed".
    //
    enum status_type {installed, partially_installed, not_installed};

    status_type status = not_installed;
  };

  // As mentioned above the system package manager API has two parts:
  // consumption (status() and install()) and production (generate()) and a
  // particular implementation may only implement one, the other, or both. If
  // a particular part is not implemented, then the correponding make_*()
  // function below should never return an instance of such a system package
  // manager.
  //
  class system_package_manager
  {
  public:
    // Query the system package status.
    //
    // This function has two modes: cache-only (available_packages is NULL)
    // and full (available_packages is not NULL). In the cache-only mode this
    // function returns the status of this package if it has already been
    // queried and nullopt otherwise. This allows the caller to only collect
    // all the available packages (for the name/version mapping information)
    // if really necessary.
    //
    // The returned status can be NULL, which indicates that no such package
    // is available from the system package manager. Note that NULL is also
    // returned if no fully installed package is available from the system and
    // package installation is not enabled (see the constructor below).
    //
    // Note also that the implementation is expected to issue appropriate
    // progress and diagnostics if fetching package metadata (again see the
    // constructor below).
    //
    virtual optional<const system_package_status*>
    status (const package_name&, const available_packages*) = 0;

    // Install the specified subset of the previously-queried packages.
    // Should only be called if installation is enabled (see the constructor
    // below).
    //
    // Note that this function should be called only once after the final set
    // of the required system packages has been determined. And the specified
    // subset should contain all the selected packages, including the already
    // fully installed. This allows the implementation to merge and de-
    // duplicate the system package set to be installed (since some bpkg
    // packages may be mapped to the same system package), perform post-
    // installation verifications (such as making sure the versions of already
    // installed packages have not changed due to upgrades), change properties
    // of already installed packages (e.g., mark them as manually installed in
    // Debian), etc.
    //
    // Note also that the implementation is expected to issue appropriate
    // progress and diagnostics.
    //
    virtual void
    install (const vector<package_name>&) = 0;

    // Generate a binary distribution package. See the pkg-bindist(1) man page
    // for background and the pkg_bindist() function implementation for
    // details. The recursive_full argument corresponds to the --recursive
    // auto (present false) and full (present true) modes.
    //
    // The available packages are loaded for all the packages in pkgs and
    // deps. For non-system packages (so for all in pkgs) there is always a
    // single available package that corresponds to the selected package. The
    // out_root is only set for packages in pkgs. Note also that all the
    // packages in pkgs and deps are guaranteed to belong to the same build
    // configuration (as opposed to being spread over multiple linked
    // configurations). Its absolute path is bassed in cfg_dir.
    //
    // The passed package manifest corresponds to the first package in pkgs
    // (normally used as a source of additional package metadata such as
    // summary, emails, urls, etc).
    //
    // The passed package type corresponds to the first package in pkgs while
    // the languages -- to all the packages in pkgs plus, in the recursive
    // mode, to all the non-system dependencies. In other words, the languages
    // list contains every language that is used by anything that ends up in
    // the package.
    //
    // Return the list of paths to binary packages and any other associated
    // files (build metadata, etc) that could be useful for their consumption.
    // Each returned file has a distribution-specific type that classifies it.
    // If the result is empty, assume the prepare-only mode (or similar) with
    // appropriate result diagnostics having been already issued.
    //
    // Note that this function may be called multiple times in the
    // --recursive=separate mode. In this case the first argument indicates
    // whether this is the first call (can be used, for example, to adjust the
    // --wipe-output semantics).
    //
    struct package
    {
      shared_ptr<selected_package> selected;
      available_packages           available;
      dir_path                     out_root; // Absolute and normalized.
    };

    using packages = vector<package>;

    struct binary_file
    {
      string     type;
      bpkg::path path;
      string     system_name; // Empty if not applicable.
    };

    struct binary_files: public vector<binary_file>
    {
      string system_version; // Empty if not applicable.
    };

    virtual binary_files
    generate (const packages& pkgs,
              const packages& deps,
              const strings& vars,
              const dir_path& cfg_dir,
              const package_manifest&,
              const string& type,
              const small_vector<language, 1>&,
              optional<bool> recursive_full,
              bool first) = 0;

  public:
    bpkg::os_release os_release;
    target_triplet   host;
    string           arch; // Architecture in system package manager spelling.

    // Consumption constructor.
    //
    // If install is true, then enable package installation.
    //
    // If fetch is false, then do not re-fetch the system package repository
    // metadata (that is, available packages/versions) before querying for the
    // available version of the not yet installed or partially installed
    // packages.
    //
    // If fetch timeout (in seconds) is specified, then use it for all the
    // underlying network operations.
    //
    system_package_manager (bpkg::os_release&& osr,
                            const target_triplet& h,
                            string a,
                            optional<bool> progress,
                            optional<size_t> fetch_timeout,
                            bool install,
                            bool fetch,
                            bool yes,
                            string sudo)
        : os_release (move (osr)),
          host (h),
          arch (move (a)),
          progress_ (progress),
          fetch_timeout_ (fetch_timeout),
          install_ (install),
          fetch_ (fetch),
          yes_ (yes),
          sudo_ (sudo != "false" ? move (sudo) : string ()) {}

    // Production constructor.
    //
    system_package_manager (bpkg::os_release&& osr,
                            const target_triplet& h,
                            string a,
                            optional<bool> progress)
        : os_release (move (osr)),
          host (h),
          arch (move (a)),
          progress_ (progress),
          install_ (false),
          fetch_ (false),
          yes_ (false) {}

    virtual
    ~system_package_manager ();

    // Implementation details.
    //
  public:
    // Given the available packages (as returned by find_available_all())
    // return the list of system package names as mapped by the
    // <distribution>-name values.
    //
    // The name_id, version_id, and like_ids are the values from os_release
    // (refer there for background). If version_id is empty, then it's treated
    // as "0".
    //
    // First consider <distribution>-name values corresponding to name_id.
    // Assume <distribution> has the <name>[_<version>] form, where <version>
    // is a semver-like version (e.g, 10, 10.15, or 10.15.1) and return all
    // the values that are equal or less than the specified version_id
    // (include the value with the absent <version>). In a sense, absent
    // <version> is treated as a 0 semver-like version.
    //
    // If no value is found then repeat the above process for every like_ids
    // entry (from left to right) instead of name_id with version_id equal 0.
    //
    // If still no value is found, then return empty list (in which case the
    // caller may choose to fallback to the downstream package name or do
    // something more elaborate, like translate version_id to one of the
    // like_id's version and try that).
    //
    // Note that multiple -name values per same distribution can be returned
    // as, for example, for the following distribution values:
    //
    // debian_10-name: libcurl4 libcurl4-doc libcurl4-openssl-dev
    // debian_10-name: libcurl3-gnutls libcurl4-gnutls-dev    (yes, 3 and 4)
    //
    // The <distribution> value in the <name>_0 form is the special "non-
    // native" name mapping. If the native argument is false, then such a
    // mapping is preferred over any other mapping. If it is true, then such a
    // mapping is ignored. The purpose of this special value is to allow
    // specifying different package names for production compared to
    // consumption. Note, however, that such a deviation may make it
    // impossible to use native and non-native binary packages
    // interchangeably, for example, to satisfy dependencies.
    //
    // Note also that the values are returned in the "override order", that is
    // from the newest package version to oldest and then from the highest
    // distribution version to lowest.
    //
    static strings
    system_package_names (const available_packages&,
                          const string& name_id,
                          const string& version_id,
                          const vector<string>& like_ids,
                          bool native);

    // Given the available package and the repository fragment it belongs to,
    // return the system package version as mapped by one of the
    // <distribution>-version values.
    //
    // The rest of the arguments as well as the overalls semantics is the same
    // as in system_package_names() above. That is, first consider
    // <distribution>-version values corresponding to name_id. If none match,
    // then repeat the above process for every like_ids entry with version_id
    // equal 0. If still no match, then return nullopt (in which case the
    // caller may choose to fallback to the upstream/bpkg package version or
    // do something more elaborate).
    //
    // Note that lazy_shared_ptr<repository_fragment> is used only for
    // diagnostics and conveys the database the available package object
    // belongs to.
    //
    static optional<string>
    system_package_version (const shared_ptr<available_package>&,
                            const lazy_shared_ptr<repository_fragment>&,
                            const string& name_id,
                            const string& version_id,
                            const vector<string>& like_ids);

    // Given the system package version and available packages (as returned by
    // find_available_all()) return the downstream package version as mapped
    // by one of the <distribution>-to-downstream-version values.
    //
    // The rest of the arguments as well as the overalls semantics is the same
    // as in system_package_names() above. That is, first consider
    // <distribution>-to-downstream-version values corresponding to
    // name_id. If none match, then repeat the above process for every
    // like_ids entry with version_id equal 0. If still no match, then return
    // nullopt (in which case the caller may choose to fallback to the system
    // package version or do something more elaborate).
    //
    static optional<version>
    downstream_package_version (const string& system_version,
                                const available_packages&,
                                const string& name_id,
                                const string& version_id,
                                const vector<string>& like_ids);

    // Return the map of filesystem entries (files and symlinks) that would be
    // installed for the specified packages with the specified configuration
    // variables.
    //
    // In essence, this function runs:
    //
    // b --dry-run --quiet <vars> !config.install.scope=<scope>
    //   !config.install.manifest=- install: <pkgs>
    //
    // And converts the printed installation manifest into the path map.
    //
    // Note that this function prints an appropriate progress indicator since
    // even in the dry-run mode it may take some time (see the --dry-run
    // option documentation for details).
    //
    struct installed_entry
    {
      string mode;                                     // Empty if symlink.
      const pair<const path, installed_entry>* target; // Target if symlink.
    };

    class installed_entry_map: public butl::path_map<installed_entry>
    {
    public:
      // Return true if there are filesystem entries in the specified
      // directory or its subdirectories.
      //
      bool
      contains_sub (const dir_path& d)
      {
        auto p (find_sub (d));
        return p.first != p.second;
      }
    };

    installed_entry_map
    installed_entries (const common_options&,
                       const packages& pkgs,
                       const strings& vars,
                       const string& scope);

  protected:
    optional<bool>   progress_;      // --[no]-progress (see also stderr_term)
    optional<size_t> fetch_timeout_; // --fetch-timeout

    // The --sys-* option values.
    //
    bool install_;
    bool fetch_;
    bool yes_;
    string sudo_;
  };

  // Create a package manager instance corresponding to the specified host
  // target triplet as well as optional distribution package manager name and
  // architecture. If name is empty, return NULL if there is no support for
  // this platform. If architecture is empty, then derive it automatically
  // from the host target triplet. Currently recognized names:
  //
  //   debian  -- Debian and alike (Ubuntu, etc) using the APT frontend.
  //   fedora  -- Fedora and alike (RHEL, Centos, etc) using the DNF frontend.
  //   archive -- Installation archive, any platform, production only.
  //
  // Note: the name can be used to select an alternative package manager
  // implementation on platforms that support multiple.
  //
  unique_ptr<system_package_manager>
  make_consumption_system_package_manager (const common_options&,
                                           const target_triplet&,
                                           const string& name,
                                           const string& arch,
                                           bool install,
                                           bool fetch,
                                           bool yes,
                                           const string& sudo);

  // Create for production. The second half of the result is the effective
  // distribution name.
  //
  // Note that the reference to options is expected to outlive the returned
  // instance.
  //
  class pkg_bindist_options;

  pair<unique_ptr<system_package_manager>, string>
  make_production_system_package_manager (const pkg_bindist_options&,
                                          const target_triplet&,
                                          const string& name,
                                          const string& arch);
}

#endif // BPKG_SYSTEM_PACKAGE_MANAGER_HXX