aboutsummaryrefslogtreecommitdiff
path: root/bpkg/system-package-manager-fedora.hxx
blob: 3e68b98bb6d573a3debc78b7c5561b5ec76984e2 (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
// file      : bpkg/system-package-manager-fedora.hxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#ifndef BPKG_SYSTEM_PACKAGE_MANAGER_FEDORA_HXX
#define BPKG_SYSTEM_PACKAGE_MANAGER_FEDORA_HXX

#include <map>

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

#include <bpkg/system-package-manager.hxx>

namespace bpkg
{
  // The system package manager implementation for Fedora and alike (Red Hat
  // Enterprise Linux, CentOS, etc) using the DNF frontend.
  //
  // NOTE: THE BELOW DESCRIPTION IS ALSO REPRODUCED IN THE BPKG MANUAL.
  //
  // For background, a library in Fedora is normally split up into several
  // packages: the shared library package (e.g., libfoo), the development
  // files package (e.g., libfoo-devel), the static library package (e.g.,
  // libfoo-static; may also be placed into the -devel package), the
  // documentation files package (e.g., libfoo-doc), the debug symbols and
  // source files packages (e.g., libfoo-debuginfo and libfoo-debugsource),
  // and the common or architecture-independent files (e.g., libfoo-common).
  // All the packages except -devel are optional and there is quite a bit of
  // variability here. In particular, the lib prefix in libfoo is not a
  // requirement (unlike in Debian) and is normally present only if upstream
  // name has it (see some examples below).
  //
  // For mixed packages which include both applications and libraries, the
  // shared library package normally has the -libs suffix (e.g., foo-libs).
  // Such packages may have separate -debuginfo packages for applications and
  // libraries (e.g. openssl-debuginfo and openssl-libs-debuginfo).
  //
  // A package name may also include an upstream version based suffix if
  // multiple versions of the package can be installed simultaneously (e.g.,
  // libfoo1.1 libfoo1.1-devel, libfoo2 libfoo2-devel).
  //
  // Terminology-wise, the term "base package" (sometime also "main package")
  // normally refers to either the application or shared library package (as
  // decided by the package maintainer in the spec file) with the suffixed
  // packages (-devel, -doc, etc) called "subpackages".
  //
  // Here are a few examples:
  //
  // libpq libpq-devel
  //
  // zlib zlib-devel zlib-static
  //
  // catch-devel
  //
  // eigen3-devel eigen3-doc
  //
  // xerces-c xerces-c-devel xerces-c-doc
  //
  // libsigc++20 libsigc++20-devel libsigc++20-doc
  // libsigc++30 libsigc++30-devel libsigc++30-doc
  //
  // icu libicu libicu-devel libicu-doc
  //
  // openssl openssl-libs openssl-devel openssl-static
  // openssl1.1 openssl1.1-devel
  //
  // curl libcurl libcurl-devel
  //
  // sqlite sqlite-libs sqlite-devel sqlite-doc
  //
  // community-mysql community-mysql-libs community-mysql-devel
  // community-mysql-common community-mysql-server
  //
  // ncurses ncurses-libs ncurses-c++-libs ncurses-devel ncurses-static
  //
  // keyutils keyutils-libs keyutils-libs-devel
  //
  // Note that while we support arbitrary -debug* sub-package names for
  // consumption, we only generate <main-package>-debug*.
  //
  // Based on that, it seems our best bet when trying to automatically map our
  // library package name to Fedora package names is to go for the -devel
  // package first and figure out the shared library package from that based
  // on the fact that the -devel package should have the == dependency on the
  // shared library package with the same version and its name should normally
  // start with the -devel package's stem and be potentially followed with the
  // -libs suffix. Failed to find the -devel package, we may re-try but now
  // using the project name instead of the package name (see, for example,
  // openssl, sqlite).
  //
  // For application packages there is normally no -devel packages but
  // -debug*, -doc, and -common are plausible.
  //
  // The format of the fedora-name (or alike) manifest value is a comma-
  // separated list of one or more package groups:
  //
  // <package-group> [, <package-group>...]
  //
  // Where each <package-group> is the space-separated list of one or more
  // package names:
  //
  // <package-name> [ <package-name>...]
  //
  // All the packages in the group should belong to the same "logical
  // package", such as -devel, -doc, -common packages. They normally have the
  // same version.
  //
  // The first group is called the main group and the first package in the
  // group is called the main package. Note that all the groups are consumed
  // (installed) but only the main group is produced (packaged).
  //
  // (Note that above we use the term "logical package" instead of "base
  // package" since the main package may not be the base package, for example
  // being the -libs subpackage.)
  //
  // We allow/recommend specifying the -devel package instead of the main
  // package for libraries (the bpkg package name starts with lib), seeing
  // that we are capable of detecting the main package automatically. If the
  // library name happens to end with -devel (which poses an ambiguity), then
  // the -devel package should be specified explicitly as the second package
  // to disambiguate this situation (if a non-library name happened to start
  // with lib and end with -devel, well, you are out of luck, I guess).
  //
  // Note also that for now we treat all the packages from the non-main groups
  // as extras but in the future we may decide to sort them out like the main
  // group (see parse_name_value() for details).
  //
  // The Fedora package version has the [<epoch>:]<version>-<release> form
  // where the parts correspond to the Epoch (optional upstream versioning
  // scheme), Version (upstream version), and Release (Fedora's package
  // revision) RPM tags (see the Fedora Package Versioning Guidelines and RPM
  // tags documentation for details). If no explicit mapping to the bpkg
  // version is specified with the fedora-to-downstream-version (or alike)
  // manifest values or none match, then we fallback to using the <version>
  // part as the bpkg version. If explicit mapping is specified, then we match
  // it against the [<epoch>:]<version> parts ignoring <release>.
  //
  struct system_package_status_fedora: system_package_status
  {
    string main;
    string devel;
    string static_;
    string doc;
    string debuginfo;
    string debugsource;
    string common;
    strings extras;

    string fallback; // Fallback for main/devel package based on project name.

    // The `dnf list` output.
    //
    struct package_info
    {
      string name;
      string installed_version; // Empty if none.
      string candidate_version; // Empty if none and no installed_version.

      // The architecture of the installed/candidate package version. Can only
      // be the host architecture or noarch (so it could have been bool but
      // it's more convenient to have the actual name).
      //
      // Note that in Fedora the same package version can be available for
      // multiple architectures or be architecture-independent. For example:
      //
      // dbus-libs-1:1.12.22-1.fc35.i686
      // dbus-libs-1:1.12.22-1.fc35.x86_64
      // dbus-common-1:1.12.22-1.fc35.noarch
      // code-insiders-1.75.0-1675123170.el7.armv7hl
      // code-insiders-1.75.0-1675123170.el7.aarch64
      // code-insiders-1.75.0-1675123170.el7.x86_64
      //
      // Thus, for a package query we normally need to qualify the package
      // with the architecture suffix or filter the query result, normally
      // skipping packages for architectures other than the host architecture.
      //
      string installed_arch;
      string candidate_arch;

      explicit
      package_info (string n): name (move (n)) {}
    };

    vector<package_info> package_infos;
    size_t package_infos_main = 0; // Size of the main group.

    explicit
    system_package_status_fedora (string m, string d = {}, string f = {})
        : main (move (m)), devel (move (d)), fallback (move (f))
    {
      assert (!main.empty () || !devel.empty ());
    }

    system_package_status_fedora () = default;
  };

  class system_package_manager_fedora: public system_package_manager
  {
  public:
    virtual optional<const system_package_status*>
    status (const package_name&, const available_packages*) override;

    virtual void
    install (const vector<package_name>&) override;

    virtual binary_files
    generate (const packages&,
              const packages&,
              const strings&,
              const dir_path&,
              const package_manifest&,
              const string&,
              const small_vector<language, 1>&,
              optional<bool>,
              bool) override;

  public:
    // Expect os_release::name_id to be "fedora" or os_release::like_ids to
    // contain "fedora".
    //
    system_package_manager_fedora (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)
        : system_package_manager (move (osr),
                                  h,
                                  a.empty () ? arch_from_target (h) : move (a),
                                  progress,
                                  fetch_timeout,
                                  install,
                                  fetch,
                                  yes,
                                  move (sudo)) {}

    // Note: options can only be NULL when testing functions that don't need
    // them.
    //
    system_package_manager_fedora (bpkg::os_release&& osr,
                                   const target_triplet& h,
                                   string a,
                                   optional<bool> progress,
                                   const pkg_bindist_options* ops)
        : system_package_manager (move (osr),
                                  h,
                                  a.empty () ? arch_from_target (h) : move (a),
                                  progress),
          ops_ (ops) {}

    // Implementation details exposed for testing (see definitions for
    // documentation).
    //
  public:
    using package_status = system_package_status_fedora;
    using package_info = package_status::package_info;

    void
    dnf_list (vector<package_info>&, size_t = 0);

    vector<pair<string, string>>
    dnf_repoquery_requires (const string&, const string&, const string&, bool);

    void
    dnf_makecache ();

    void
    dnf_install (const strings&);

    void
    dnf_mark_install (const strings&);

    pair<cstrings, const process_path&>
    dnf_common (const char*,
                optional<size_t> fetch_timeout,
                strings& args_storage);

    static package_status
    parse_name_value (const string&, const string&, bool, bool, bool);

    static string
    main_from_devel (const string&,
                     const string&,
                     const vector<pair<string, string>>&);

    static string
    arch_from_target (const target_triplet&);

    package_status
    map_package (const package_name&,
                 const version&,
                 const available_packages&) const;

    static strings
    rpm_eval (const cstrings& opts, const cstrings& expressions);

    // If simulate is not NULL, then instead of executing the actual dnf
    // commands simulate their execution: (1) for `dnf list` and `dnf
    // repoquery --requires` by printing their command lines and reading the
    // results from files specified in the below dnf_* maps and (2) for `dnf
    // makecache`, `dnf install`, and `dnf mark install` by printing their
    // command lines and failing if requested.
    //
    // In the (1) case if the corresponding map entry does not exist or the
    // path is empty, then act as if the specified package/version is
    // unknown. If the path is special "-" then read from stdin. For `dnf
    // list` and `dnf repoquery --requires` different post-fetch and (for the
    // former) post-install results can be specified (if the result is not
    // found in one of the later maps, the previous map is used as a
    // fallback). Note that the keys in the dnf_list_* maps are the package
    // sets and the corresponding result file is expected to contain (or not)
    // the results for all of them. See dnf_list() and
    // dnf_repoquery_requires() implementations for details on the expected
    // results.
    //
    struct simulation
    {
      std::map<strings, path> dnf_list_;
      std::map<strings, path> dnf_list_fetched_;
      std::map<strings, path> dnf_list_installed_;

      struct package
      {
        string name;
        string version;
        string arch;
        bool installed;

        bool
        operator< (const package& p) const
        {
          if (int r = name.compare (p.name))
            return r < 0;

          if (int r = version.compare (p.version))
            return r < 0;

          if (int r = arch.compare (p.arch))
            return r < 0;

          return installed < p.installed;
        }
      };

      std::map<package, path> dnf_repoquery_requires_;
      std::map<package, path> dnf_repoquery_requires_fetched_;

      bool dnf_makecache_fail_ = false;
      bool dnf_install_fail_ = false;
      bool dnf_mark_install_fail_ = false;
    };

    const simulation* simulate_ = nullptr;

  private:
    optional<system_package_status_fedora>
    status (const package_name&, const available_packages&);

  private:
    bool fetched_ = false;   // True if already fetched metadata.
    bool installed_ = false; // True if already installed.

    std::map<package_name, optional<system_package_status_fedora>> status_cache_;

    const pkg_bindist_options* ops_ = nullptr; // Only for production.
  };
}

#endif // BPKG_SYSTEM_PACKAGE_MANAGER_FEDORA_HXX