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

#ifndef BPKG_SYSTEM_PACKAGE_MANAGER_DEBIAN_HXX
#define BPKG_SYSTEM_PACKAGE_MANAGER_DEBIAN_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 Debian and alike (Ubuntu,
  // etc) using the apt frontend (specifically, apt-get and apt-cache) for
  // consumption and the dpkg-buildpackage/debhelper/dh tooling for
  // production.
  //
  // NOTE: THE BELOW DESCRIPTION IS ALSO REPRODUCED IN THE BPKG MANUAL.
  //
  // For background, a library in Debian is normally split up into several
  // packages: the shared library package (e.g., libfoo1 where 1 is the ABI
  // version), the development files package (e.g., libfoo-dev), the
  // documentation files package (e.g., libfoo-doc), the debug symbols package
  // (e.g., libfoo1-dbg), and the (usually) architecture-independent files
  // (e.g., libfoo1-common). All the packages except -dev are optional and
  // there is quite a bit of variability here. Here are a few examples:
  //
  // libsqlite3-0 libsqlite3-dev
  //
  // libssl1.1 libssl-dev libssl-doc
  // libssl3 libssl-dev libssl-doc
  //
  // libcurl4 libcurl4-openssl-dev libcurl4-doc
  // libcurl3-gnutls libcurl4-gnutls-dev libcurl4-doc  (yes, 3 and 4)
  //
  // Note that while most library package names in Debian start with lib (per
  // the policy), there are exceptions (e.g., zlib1g zlib1g-dev). The
  // header-only library package names may or may not start with lib and end
  // with -dev (e.g., libeigen3-dev, rapidjson-dev, catch2).
  //
  // Also note that manual -dbg packages are obsolete in favor of automatic
  // -dbgsym packages from Debian 9. So while we support -dbg for consumption,
  // we only generate -dbgsym.
  //
  // Based on that, it seems our best bet when trying to automatically map our
  // library package name to Debian package names is to go for the -dev
  // package first and figure out the shared library package from that based
  // on the fact that the -dev package should have the == dependency on the
  // shared library package with the same version and its name should normally
  // start with the -dev package's stem.
  //
  // For executable packages there is normally no -dev packages but -dbg,
  // -doc, and -common are plausible.
  //
  // The format of the debian-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 be "package components" (for the
  // lack of a better term) of the same "logical package", such as -dev, -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).
  //
  // We allow/recommend specifying the -dev 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 -dev (which poses an ambiguity), then
  // the -dev 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 -dev, 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 Debian package version has the [<epoch>:]<upstream>[-<revision>] form
  // (see deb-version(5) for details). If no explicit mapping to the bpkg
  // version is specified with the debian-to-downstream-version (or alike)
  // manifest values or none match, then we fallback to using the <upstream>
  // part as the bpkg version. If explicit mapping is specified, then we match
  // it against the [<epoch>:]<upstream> parts ignoring <revision>.
  //
  struct system_package_status_debian: system_package_status
  {
    string main;
    string dev;
    string doc;
    string dbg;
    string common;
    strings extras;

    // The `apt-cache policy` output.
    //
    struct package_policy
    {
      string name;
      string installed_version; // Empty if none.
      string candidate_version; // Empty if none and no installed_version.

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

    vector<package_policy> package_policies;
    size_t package_policies_main = 0; // Size of the main group.

    explicit
    system_package_status_debian (string m, string d = {})
        : main (move (m)), dev (move (d))
    {
      assert (!main.empty () || !dev.empty ());
    }

    system_package_status_debian () = default;
  };

  class system_package_manager_debian: 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 "debian" or os_release::like_ids to
    // contain "debian".
    //
    // @@ TODO: we currently don't handle non-host arch in consumption.
    //
    system_package_manager_debian (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_debian (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_debian;
    using package_policy = package_status::package_policy;

    void
    apt_cache_policy (vector<package_policy>&, size_t = 0);

    string
    apt_cache_show (const string&, const string&);

    void
    apt_get_update ();

    void
    apt_get_install (const strings&);

    pair<cstrings, const process_path&>
    apt_get_common (const char*, strings& args_storage);

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

    static string
    main_from_dev (const string&, const string&, const string&);

    static string
    arch_from_target (const target_triplet&);

    package_status
    map_package (const package_name&,
                 const version&,
                 const available_packages&,
                 const optional<string>&) const;

    // If simulate is not NULL, then instead of executing the actual apt-cache
    // and apt-get commands simulate their execution: (1) for apt-cache by
    // printing their command lines and reading the results from files
    // specified in the below apt_cache_* maps and (2) for apt-get 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 apt-cache
    // different post-fetch and (for policy) 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
    // apt_cache_policy_* maps are the package sets and the corresponding
    // result file is expected to contain (or not) the results for all of
    // them. See apt_cache_policy() and apt_cache_show() implementations for
    // details on the expected results.
    //
    struct simulation
    {
      std::map<strings, path> apt_cache_policy_;
      std::map<strings, path> apt_cache_policy_fetched_;
      std::map<strings, path> apt_cache_policy_installed_;

      std::map<pair<string, string>, path> apt_cache_show_;
      std::map<pair<string, string>, path> apt_cache_show_fetched_;

      bool apt_get_update_fail_ = false;
      bool apt_get_install_fail_ = false;
    };

    const simulation* simulate_ = nullptr;

  private:
    optional<system_package_status_debian>
    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_debian>> status_cache_;

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

#endif // BPKG_SYSTEM_PACKAGE_MANAGER_DEBIAN_HXX