aboutsummaryrefslogtreecommitdiff
path: root/bpkg/system-package-manager.hxx
blob: 35b74394a92378d21453cd5add9c02e2d087f3ce (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
// 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 <libbpkg/manifest.hxx>     // version
#include <libbpkg/package-name.hxx>

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

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

namespace bpkg
{
  // The system/distribution package manager interface. Used by both pkg-build
  // (to query and install system packages) and by pkg-bindist (to build
  // 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) package name and version for
    // diagnostics.
    //
    // Note that this status may represent multiple system packages (for
    // example, libssl3 and libssl3-dev) and here we have the main package
    // name (for example, libssl3).
    //
    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;
  };

  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 empty 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.
    //
    virtual optional<const system_package_status*>
    pkg_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 necessary 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
    pkg_install (const vector<package_name>&) = 0;

  public:
    virtual
    ~system_package_manager ();

    // 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.
    //
    system_package_manager (os_release&& osr,
                            bool install,
                            bool fetch,
                            optional<bool> progress,
                            bool yes,
                            string sudo)
        : os_release_ (osr),
          progress_ (progress),
          install_ (install),
          fetch_ (fetch),
          yes_ (yes),
          sudo_ (sudo != "false" ? move (sudo) : string ()) {}

  protected:
    // 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 of less than the specified version_id
    // (include the value with the absent <version>). In a sense, absent
    // <version> can be treated as 0 semver-like versions.
    //
    // 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 the like_id's
    // version and try that).
    //
    // @@ TODO: allow multiple -name values per same distribution and handle
    //    here? E.g., libcurl4-openssl-dev libcurl4-gnutls-dev. But they will
    //    have the same available version, how will we deal with that? How
    //    will we pick one? Perhaps this should all be handled by the system
    //    package manager (conceptually, this is configuration negotiation).
    //
    static strings
    system_package_names (const available_packages&,
                          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, like translate
    // version_id to the like_id's version and try that).
    //
    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);
  protected:
    os_release os_release_;
    optional<bool> progress_; // --[no]-progress (see also stderr_term)

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

  // Create a package manager instance corresponding to the specified host
  // target and optional manager name. If name is empty, return NULL if there
  // is no support for this platform.
  //
  // @@ TODO: need to assign names. Ideas:
  //
  //    dpkg-apt, rpm-dnf
  //    deb, rpm
  //    debian, fedora (i.e., follow  /etc/os-release ID_LIKE lead)
  //
  unique_ptr<system_package_manager>
  make_system_package_manager (const common_options&,
                               const target_triplet&,
                               bool install,
                               bool fetch,
                               bool yes,
                               const string& sudo,
                               const string& name);
}

#endif // BPKG_SYSTEM_PACKAGE_MANAGER_HXX