source: appstream-generator/src/asgen/backends/debian/debutils.d @ 4841

Last change on this file since 4841 was 4841, checked in by Juanma, 2 years ago

Initial release

File size: 7.8 KB
Line 
1/*
2 * Copyright (C) 2016 Matthias Klumpp <matthias@tenstral.net>
3 * Copyright (C) The APT development team.
4 * Copyright (C) 2016 Canonical Ltd
5 *   Author(s): Iain Lane <iain@orangesquash.org.uk>
6 *
7 * Licensed under the GNU Lesser General Public License Version 3
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation, either version 3 of the license, or
12 * (at your option) any later version.
13 *
14 * This software is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this software.  If not, see <http://www.gnu.org/licenses/>.
21 */
22
23module asgen.backends.debian.debutils;
24
25import std.string;
26static import std.file;
27
28import asgen.logging;
29import asgen.utils : downloadFile, isRemote;
30
31/**
32 * If prefix is remote, download the first of (prefix + suffix).{xz,bz2,gz},
33 * otherwise check if any of (prefix + suffix).{xz,bz2,gz} exists.
34 *
35 * Returns: Path to the file, which is guaranteed to exist.
36 *
37 * Params:
38 *      prefix = First part of the address, i.e.
39 *               "http://ftp.debian.org/debian/" or "/srv/mirrors/debian/"
40 *      destPrefix = If the file is remote, the directory to save it under,
41 *                   which is created if necessary.
42 *      suffix = the rest of the address, so that (prefix +
43 *               suffix).format({xz,bz2,gz}) is a full path or URL, i.e.
44 *               "dists/unstable/main/binary-i386/Packages.%s". The suffix must
45 *               contain exactly one "%s"; this function is only suitable for
46 *               finding `.xz`, `.bz2` and `.gz` files.
47 */
48immutable (string) downloadIfNecessary (const string prefix,
49                                        const string destPrefix,
50                                        const string suffix)
51{
52    import std.net.curl;
53    import std.path;
54
55    immutable exts = ["xz", "bz2", "gz"];
56    foreach (ref ext; exts) {
57        immutable fileName = format (buildPath (prefix, suffix), ext);
58        immutable destFileName = format (buildPath (destPrefix, suffix), ext);
59
60        if (fileName.isRemote) {
61            try {
62                /* This should use download(), but that doesn't throw errors */
63                downloadFile (fileName, destFileName);
64
65                return destFileName;
66            } catch (CurlException ex) {
67                logDebug ("Could not download: %s", ex.msg);
68            }
69        } else {
70            if (std.file.exists (fileName))
71                return fileName;
72        }
73    }
74
75    /* all extensions failed, so we failed */
76    throw new Exception (format ("Could not obtain any file matching %s",
77                         buildPath (prefix, suffix)));
78}
79
80/**
81 * This compares a fragment of the version. This is a slightly adapted
82 * version of what dpkg uses in dpkg/lib/dpkg/version.c.
83 * In particular, the a | b = NULL check is removed as we check this in the
84 * caller, we use an explicit end for a | b strings and we check ~ explicit.
85 */
86private int order (char c) pure
87{
88    import std.ascii;
89
90    if (c.isDigit)
91        return 0;
92    else if (c.isAlpha)
93        return c;
94    else if (c == '~')
95        return -1;
96    else if (c)
97        return c + 256;
98    else
99        return 0;
100}
101
102/**
103 * Iterate over the whole string
104 * What this does is to split the whole string into groups of
105 * numeric and non numeric portions. For instance:
106 *    a67bhgs89
107 * Has 4 portions 'a', '67', 'bhgs', '89'. A more normal:
108 *    2.7.2-linux-1
109 * Has '2', '.', '7', '.' ,'-linux-','1'
110 */
111private int cmpFragment (const(immutable(char)*) a, const(immutable(char)*) aEnd,
112                         const(immutable(char)*) b, const(immutable(char)*) bEnd) @trusted pure
113{
114    import std.ascii;
115
116    immutable(char) *lhs = a;
117    immutable(char) *rhs = b;
118
119    while (lhs != aEnd && rhs != bEnd) {
120        int first_diff = 0;
121
122        while (lhs != aEnd && rhs != bEnd && (!(*lhs).isDigit || !(*rhs).isDigit)) {
123            int vc = order (*lhs);
124            int rc = order (*rhs);
125
126            if (vc != rc)
127                return vc - rc;
128            ++lhs; ++rhs;
129        }
130
131        while (*lhs == '0')
132            ++lhs;
133        while (*rhs == '0')
134            ++rhs;
135        while ((*lhs).isDigit && (*rhs).isDigit) {
136            if (!first_diff)
137                first_diff = *lhs - *rhs;
138            ++lhs;
139            ++rhs;
140        }
141
142        if ((*lhs).isDigit)
143            return 1;
144        if ((*rhs).isDigit)
145            return -1;
146        if (first_diff)
147            return first_diff;
148    }
149
150    // The strings must be equal
151    if (lhs == aEnd && rhs == bEnd)
152        return 0;
153
154    // lhs is shorter
155    if (lhs == aEnd) {
156        if (*rhs == '~')
157            return 1;
158        return -1;
159    }
160
161    // rhs is shorter
162    if (rhs == bEnd) {
163        if (*lhs == '~')
164            return -1;
165        return 1;
166    }
167
168    // Shouldn't happen
169    return 1;
170}
171
172// import from string.h, needs glibc
173private extern(C) void *memrchr (const void *s, int c, size_t n) @system pure;
174
175/**
176 * Compare two Debian-style version numbers.
177 */
178int compareVersions (const string a, const string b) @trusted pure
179{
180    import core.stdc.string;
181
182    immutable(char) *ac = a.toStringz;
183    immutable(char) *bc = b.toStringz;
184
185    immutable(char*) aEnd = ac + a.length;
186    immutable(char*) bEnd = bc + b.length;
187
188    // Strip off the epoch and compare it
189    auto lhs = cast(immutable(char)*) memchr (ac, ':', aEnd - ac);
190    auto rhs = cast(immutable(char)*) memchr (bc, ':', bEnd - bc);
191
192    if (lhs is null)
193        lhs = ac;
194    if (rhs is null)
195        rhs = bc;
196
197    // Special case: a zero epoch is the same as no epoch,
198    // so remove it.
199    if (lhs != ac) {
200        for (; *ac == '0'; ++ac) {}
201        if (ac == lhs) {
202            ++ac;
203            ++lhs;
204        }
205    }
206    if (rhs != bc) {
207        for (; *bc == '0'; ++bc) {}
208        if (bc == rhs) {
209            ++bc;
210            ++rhs;
211        }
212    }
213
214    // Compare the epoch
215    auto res = cmpFragment (ac, lhs, bc, rhs);
216    if (res != 0)
217        return res;
218
219    // Skip the :
220    if (lhs != ac)
221        lhs++;
222    if (rhs != bc)
223        rhs++;
224
225    // Find the last -
226    auto dlhs = cast(immutable(char)*) memrchr (lhs, '-', aEnd - lhs);
227    auto drhs = cast(immutable(char)*) memrchr (rhs, '-', bEnd - rhs);
228    if (dlhs is null)
229        dlhs = aEnd;
230    if (drhs is null)
231        drhs = bEnd;
232
233    // Compare the main version
234    res = cmpFragment (lhs, dlhs, rhs, drhs);
235    if (res != 0)
236        return res;
237
238    // Skip the -
239    if (dlhs != lhs)
240        dlhs++;
241    if (drhs != rhs)
242        drhs++;
243
244    // no debian revision need to be treated like -0
245    if (*(dlhs - 1) == '-' && *(drhs - 1) == '-') {
246        return cmpFragment (dlhs, aEnd, drhs, bEnd);
247    } else if (*(dlhs - 1) == '-') {
248        immutable(char)* zeroZ = "0";
249        return cmpFragment (dlhs, aEnd, zeroZ, zeroZ + 1);
250    } else if (*(drhs - 1) == '-') {
251        immutable(char)* zeroZ = "0";
252        return cmpFragment (zeroZ, zeroZ + 1, drhs, bEnd);
253    } else {
254        return 0;
255    }
256}
257
258unittest
259{
260    import std.stdio : writeln;
261    writeln ("TEST: ", "DebianUtils");
262
263    assert (compareVersions ("6", "8") < 0);
264    assert (compareVersions ("0.6.12b-d", "0.6.12a") > 0);
265    assert (compareVersions ("7.4", "7.4") == 0);
266    assert (compareVersions ("ab.d", "ab.f") < 0);
267
268    assert (compareVersions ("0.6.16", "0.6.14") > 0);
269
270    assert (compareVersions ("3.0.rc2", "3.0.0") > 0);
271    assert (compareVersions ("3.0.0~rc2", "3.0.0") < 0);
272
273    assert (compareVersions ("4:5.6-2", "8.0-6") > 0);
274    assert (compareVersions ("1:1.0-4", "3:0.8-2") < 0);
275}
Note: See TracBrowser for help on using the repository browser.