extra-platforms 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- extra_platforms/__init__.py +29 -0
- extra_platforms/platforms.py +972 -0
- extra_platforms-1.0.0.dist-info/METADATA +86 -0
- extra_platforms-1.0.0.dist-info/RECORD +7 -0
- extra_platforms-1.0.0.dist-info/WHEEL +5 -0
- extra_platforms-1.0.0.dist-info/entry_points.txt +2 -0
- extra_platforms-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
|
|
2
|
+
#
|
|
3
|
+
# This program is Free Software; you can redistribute it and/or
|
|
4
|
+
# modify it under the terms of the GNU General Public License
|
|
5
|
+
# as published by the Free Software Foundation; either version 2
|
|
6
|
+
# of the License, or (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program; if not, write to the Free Software
|
|
15
|
+
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
16
|
+
"""Expose package-wide elements."""
|
|
17
|
+
|
|
18
|
+
__version__ = "1.0.0"
|
|
19
|
+
"""Examples of valid version strings according :pep:`440#version-scheme`:
|
|
20
|
+
|
|
21
|
+
.. code-block:: python
|
|
22
|
+
|
|
23
|
+
__version__ = "1.2.3.dev1" # Development release 1
|
|
24
|
+
__version__ = "1.2.3a1" # Alpha Release 1
|
|
25
|
+
__version__ = "1.2.3b1" # Beta Release 1
|
|
26
|
+
__version__ = "1.2.3rc1" # RC Release 1
|
|
27
|
+
__version__ = "1.2.3" # Final Release
|
|
28
|
+
__version__ = "1.2.3.post1" # Post Release 1
|
|
29
|
+
"""
|
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
|
|
2
|
+
#
|
|
3
|
+
# This program is Free Software; you can redistribute it and/or
|
|
4
|
+
# modify it under the terms of the GNU General Public License
|
|
5
|
+
# as published by the Free Software Foundation; either version 2
|
|
6
|
+
# of the License, or (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program; if not, write to the Free Software
|
|
15
|
+
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
16
|
+
"""Helpers and utilities to identify platforms.
|
|
17
|
+
|
|
18
|
+
Everything here can be aggressively cached and frozen, as it's only compute
|
|
19
|
+
platform-dependent values.
|
|
20
|
+
|
|
21
|
+
.. note::
|
|
22
|
+
|
|
23
|
+
Default icons are inspired from Starship project:
|
|
24
|
+
- https://starship.rs/config/#os
|
|
25
|
+
- https://github.com/davidkna/starship/blob/e9faf17/.github/config-schema.json#L1221-L1269
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
.. note::
|
|
29
|
+
|
|
30
|
+
Heuristics for unrecognized platforms can be found in `Rust's sysinfo crate
|
|
31
|
+
<https://github.com/stanislav-tkach/os_info/tree/master/os_info/src>`_.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import platform
|
|
37
|
+
import sys
|
|
38
|
+
import warnings
|
|
39
|
+
from dataclasses import dataclass, field
|
|
40
|
+
from itertools import combinations
|
|
41
|
+
from os import environ
|
|
42
|
+
from typing import Any, Iterable, Iterator
|
|
43
|
+
|
|
44
|
+
import distro
|
|
45
|
+
from boltons.iterutils import remap
|
|
46
|
+
|
|
47
|
+
from . import cache
|
|
48
|
+
|
|
49
|
+
""" Below is the collection of heuristics used to identify each platform.
|
|
50
|
+
|
|
51
|
+
All these heuristics can be hard-cached as the underlying system is not suppose to
|
|
52
|
+
change between code execution.
|
|
53
|
+
|
|
54
|
+
We mostly rely on ``sys.platform`` first as it seems to be the lowest-level primitive
|
|
55
|
+
available to identify systems.
|
|
56
|
+
|
|
57
|
+
We choose to have separate function to detect each platform so we can easely check
|
|
58
|
+
consistency. It helps ensure there is no heuristics conflicting and matching multiple
|
|
59
|
+
systems at the same time.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@cache
|
|
64
|
+
def is_aix() -> bool:
|
|
65
|
+
"""Return `True` only if current platform is AIX."""
|
|
66
|
+
return sys.platform.startswith("aix") or distro.id() == "aix"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@cache
|
|
70
|
+
def is_altlinux() -> bool:
|
|
71
|
+
"""Return `True` only if current platform is ALT Linux."""
|
|
72
|
+
return distro.id() == "altlinux"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@cache
|
|
76
|
+
def is_amzn() -> bool:
|
|
77
|
+
"""Return `True` only if current platform is Amazon Linux."""
|
|
78
|
+
return distro.id() == "amzn"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@cache
|
|
82
|
+
def is_android() -> bool:
|
|
83
|
+
"""Return `True` only if current platform is Android.
|
|
84
|
+
|
|
85
|
+
Source: https://github.com/kivy/kivy/blob/master/kivy/utils.py#L429
|
|
86
|
+
"""
|
|
87
|
+
return "ANDROID_ROOT" in environ or "P4A_BOOTSTRAP" in environ
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@cache
|
|
91
|
+
def is_arch() -> bool:
|
|
92
|
+
"""Return `True` only if current platform is Arch Linux."""
|
|
93
|
+
return distro.id() == "arch"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@cache
|
|
97
|
+
def is_buildroot() -> bool:
|
|
98
|
+
"""Return `True` only if current platform is Buildroot."""
|
|
99
|
+
return distro.id() == "buildroot"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@cache
|
|
103
|
+
def is_centos() -> bool:
|
|
104
|
+
"""Return `True` only if current platform is CentOS."""
|
|
105
|
+
return distro.id() == "centos"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@cache
|
|
109
|
+
def is_cloudlinux() -> bool:
|
|
110
|
+
"""Return `True` only if current platform is CloudLinux OS."""
|
|
111
|
+
return distro.id() == "cloudlinux"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@cache
|
|
115
|
+
def is_cygwin() -> bool:
|
|
116
|
+
"""Return `True` only if current platform is Cygwin."""
|
|
117
|
+
return sys.platform.startswith("cygwin")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@cache
|
|
121
|
+
def is_debian() -> bool:
|
|
122
|
+
"""Return `True` only if current platform is Debian."""
|
|
123
|
+
return distro.id() == "debian"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@cache
|
|
127
|
+
def is_exherbo() -> bool:
|
|
128
|
+
"""Return `True` only if current platform is Exherbo Linux."""
|
|
129
|
+
return distro.id() == "exherbo"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@cache
|
|
133
|
+
def is_fedora() -> bool:
|
|
134
|
+
"""Return `True` only if current platform is Fedora."""
|
|
135
|
+
return distro.id() == "fedora"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@cache
|
|
139
|
+
def is_freebsd() -> bool:
|
|
140
|
+
"""Return `True` only if current platform is FreeBSD."""
|
|
141
|
+
return sys.platform.startswith("freebsd") or distro.id() == "freebsd"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@cache
|
|
145
|
+
def is_gentoo() -> bool:
|
|
146
|
+
"""Return `True` only if current platform is GenToo Linux."""
|
|
147
|
+
return distro.id() == "gentoo"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@cache
|
|
151
|
+
def is_guix() -> bool:
|
|
152
|
+
"""Return `True` only if current platform is Guix System."""
|
|
153
|
+
return distro.id() == "guix"
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@cache
|
|
157
|
+
def is_hurd() -> bool:
|
|
158
|
+
"""Return `True` only if current platform is GNU/Hurd."""
|
|
159
|
+
return sys.platform.startswith("GNU")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@cache
|
|
163
|
+
def is_ibm_powerkvm() -> bool:
|
|
164
|
+
"""Return `True` only if current platform is IBM PowerKVM."""
|
|
165
|
+
return distro.id() == "ibm_powerkvm"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@cache
|
|
169
|
+
def is_kvmibm() -> bool:
|
|
170
|
+
"""Return `True` only if current platform is KVM for IBM z Systems."""
|
|
171
|
+
return distro.id() == "kvmibm"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@cache
|
|
175
|
+
def is_linux() -> bool:
|
|
176
|
+
""" """
|
|
177
|
+
warnings.warn(
|
|
178
|
+
"is_linux() is a covenient method that has been deprecated by the recent "
|
|
179
|
+
"introduction of fine-grained distribution identification",
|
|
180
|
+
DeprecationWarning,
|
|
181
|
+
)
|
|
182
|
+
return CURRENT_OS_ID in ALL_LINUX.platform_ids
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@cache
|
|
186
|
+
def is_linuxmint() -> bool:
|
|
187
|
+
"""Return `True` only if current platform is Linux Mint."""
|
|
188
|
+
return distro.id() == "linuxmint"
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@cache
|
|
192
|
+
def is_macos() -> bool:
|
|
193
|
+
"""Return `True` only if current platform is macOS."""
|
|
194
|
+
return platform.platform(terse=True).startswith(("macOS", "Darwin"))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@cache
|
|
198
|
+
def is_mageia() -> bool:
|
|
199
|
+
"""Return `True` only if current platform is Mageia."""
|
|
200
|
+
return distro.id() == "mageia"
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@cache
|
|
204
|
+
def is_mandriva() -> bool:
|
|
205
|
+
"""Return `True` only if current platform is Mandriva Linux."""
|
|
206
|
+
return distro.id() == "mandriva"
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@cache
|
|
210
|
+
def is_midnightbsd() -> bool:
|
|
211
|
+
"""Return `True` only if current platform is MidnightBSD."""
|
|
212
|
+
return sys.platform.startswith("midnightbsd") or distro.id() == "midnightbsd"
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@cache
|
|
216
|
+
def is_netbsd() -> bool:
|
|
217
|
+
"""Return `True` only if current platform is NetBSD."""
|
|
218
|
+
return sys.platform.startswith("netbsd") or distro.id() == "netbsd"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@cache
|
|
222
|
+
def is_openbsd() -> bool:
|
|
223
|
+
"""Return `True` only if current platform is OpenBSD."""
|
|
224
|
+
return sys.platform.startswith("openbsd") or distro.id() == "openbsd"
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@cache
|
|
228
|
+
def is_opensuse() -> bool:
|
|
229
|
+
"""Return `True` only if current platform is openSUSE."""
|
|
230
|
+
return distro.id() == "opensuse"
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@cache
|
|
234
|
+
def is_oracle() -> bool:
|
|
235
|
+
"""Return `True` only if current platform is Oracle Linux (and Oracle Enterprise Linux)."""
|
|
236
|
+
return distro.id() == "oracle"
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@cache
|
|
240
|
+
def is_parallels() -> bool:
|
|
241
|
+
"""Return `True` only if current platform is Parallels."""
|
|
242
|
+
return distro.id() == "parallels"
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@cache
|
|
246
|
+
def is_pidora() -> bool:
|
|
247
|
+
"""Return `True` only if current platform is Pidora."""
|
|
248
|
+
return distro.id() == "pidora"
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@cache
|
|
252
|
+
def is_raspbian() -> bool:
|
|
253
|
+
"""Return `True` only if current platform is Raspbian."""
|
|
254
|
+
return distro.id() == "raspbian"
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@cache
|
|
258
|
+
def is_rhel() -> bool:
|
|
259
|
+
"""Return `True` only if current platform is RedHat Enterprise Linux."""
|
|
260
|
+
return distro.id() == "rhel"
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@cache
|
|
264
|
+
def is_rocky() -> bool:
|
|
265
|
+
"""Return `True` only if current platform is Rocky Linux."""
|
|
266
|
+
return distro.id() == "rocky"
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@cache
|
|
270
|
+
def is_scientific() -> bool:
|
|
271
|
+
"""Return `True` only if current platform is Scientific Linux."""
|
|
272
|
+
return distro.id() == "scientific"
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@cache
|
|
276
|
+
def is_slackware() -> bool:
|
|
277
|
+
"""Return `True` only if current platform is Slackware."""
|
|
278
|
+
return distro.id() == "slackware"
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@cache
|
|
282
|
+
def is_sles() -> bool:
|
|
283
|
+
"""Return `True` only if current platform is SUSE Linux Enterprise Server."""
|
|
284
|
+
return distro.id() == "sles"
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@cache
|
|
288
|
+
def is_solaris() -> bool:
|
|
289
|
+
"""Return `True` only if current platform is Solaris."""
|
|
290
|
+
return platform.platform(aliased=True, terse=True).startswith("Solaris")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@cache
|
|
294
|
+
def is_sunos() -> bool:
|
|
295
|
+
"""Return `True` only if current platform is SunOS."""
|
|
296
|
+
return platform.platform(aliased=True, terse=True).startswith("SunOS")
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@cache
|
|
300
|
+
def is_ubuntu() -> bool:
|
|
301
|
+
"""Return `True` only if current platform is Ubuntu."""
|
|
302
|
+
return distro.id() == "ubuntu"
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@cache
|
|
306
|
+
def is_unknown_linux() -> bool:
|
|
307
|
+
"""Return `True` only if current platform is an unknown Linux.
|
|
308
|
+
|
|
309
|
+
Excludes WSL1 and WSL2 from this check to
|
|
310
|
+
`avoid false positives <https://github.com/kdeldycke/meta-package-manager/issues/944>`_.
|
|
311
|
+
"""
|
|
312
|
+
return sys.platform.startswith("linux") and not (
|
|
313
|
+
is_ubuntu() or is_wsl1() or is_wsl2()
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@cache
|
|
318
|
+
def is_windows() -> bool:
|
|
319
|
+
"""Return `True` only if current platform is Windows."""
|
|
320
|
+
return sys.platform.startswith("win32")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@cache
|
|
324
|
+
def is_wsl1() -> bool:
|
|
325
|
+
"""Return `True` only if current platform is Windows Subsystem for Linux v1.
|
|
326
|
+
|
|
327
|
+
.. caution::
|
|
328
|
+
The only difference between WSL1 and WSL2 is `the case of the kernel release
|
|
329
|
+
version <https://github.com/andweeb/presence.nvim/pull/64#issue-1174430662>`_:
|
|
330
|
+
|
|
331
|
+
- WSL 1:
|
|
332
|
+
|
|
333
|
+
.. code-block:: shell-session
|
|
334
|
+
|
|
335
|
+
$ uname -r
|
|
336
|
+
4.4.0-22572-Microsoft
|
|
337
|
+
|
|
338
|
+
- WSL 2:
|
|
339
|
+
|
|
340
|
+
.. code-block:: shell-session
|
|
341
|
+
|
|
342
|
+
$ uname -r
|
|
343
|
+
5.10.102.1-microsoft-standard-WSL2
|
|
344
|
+
"""
|
|
345
|
+
return "Microsoft" in platform.release()
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@cache
|
|
349
|
+
def is_wsl2() -> bool:
|
|
350
|
+
"""Return `True` only if current platform is Windows Subsystem for Linux v2."""
|
|
351
|
+
return "microsoft" in platform.release()
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@cache
|
|
355
|
+
def is_xenserver() -> bool:
|
|
356
|
+
"""Return `True` only if current platform is XenServer."""
|
|
357
|
+
return distro.id() == "xenserver"
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def recursive_update(
|
|
361
|
+
a: dict[str, Any], b: dict[str, Any], strict: bool = False
|
|
362
|
+
) -> dict[str, Any]:
|
|
363
|
+
"""Like standard ``dict.update()``, but recursive so sub-dict gets updated.
|
|
364
|
+
|
|
365
|
+
Ignore elements present in ``b`` but not in ``a``. Unless ``strict`` is set to
|
|
366
|
+
`True`, in which case a `ValueError` exception will be raised.
|
|
367
|
+
"""
|
|
368
|
+
for k, v in b.items():
|
|
369
|
+
if isinstance(v, dict) and isinstance(a.get(k), dict):
|
|
370
|
+
a[k] = recursive_update(a[k], v, strict=strict)
|
|
371
|
+
# Ignore elements unregistered in the template structure.
|
|
372
|
+
elif k in a:
|
|
373
|
+
a[k] = b[k]
|
|
374
|
+
elif strict:
|
|
375
|
+
msg = f"Parameter {k!r} found in second dict but not in first."
|
|
376
|
+
raise ValueError(msg)
|
|
377
|
+
return a
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def remove_blanks(
|
|
381
|
+
tree: dict,
|
|
382
|
+
remove_none: bool = True,
|
|
383
|
+
remove_dicts: bool = True,
|
|
384
|
+
remove_str: bool = True,
|
|
385
|
+
) -> dict:
|
|
386
|
+
"""Returns a copy of a dict without items whose values blanks.
|
|
387
|
+
|
|
388
|
+
Are considered blanks:
|
|
389
|
+
- `None` values
|
|
390
|
+
- empty strings
|
|
391
|
+
- empty `dict`
|
|
392
|
+
|
|
393
|
+
The removal of each of these class can be skipped by setting ``remove_*``
|
|
394
|
+
parameters.
|
|
395
|
+
|
|
396
|
+
Dictionarries are inspected recursively and their own blank values are removed.
|
|
397
|
+
"""
|
|
398
|
+
|
|
399
|
+
def visit(path, key, value) -> bool:
|
|
400
|
+
"""Ignore some class of blank values depending on configuration."""
|
|
401
|
+
if remove_none and value is None:
|
|
402
|
+
return False
|
|
403
|
+
if remove_dicts and isinstance(value, dict) and not len(value):
|
|
404
|
+
return False
|
|
405
|
+
if remove_str and isinstance(value, str) and not len(value):
|
|
406
|
+
return False
|
|
407
|
+
return True
|
|
408
|
+
|
|
409
|
+
return remap(tree, visit=visit)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@dataclass(frozen=True)
|
|
413
|
+
class Platform:
|
|
414
|
+
"""A platform can identify multiple distributions or OSes with the same
|
|
415
|
+
characteristics.
|
|
416
|
+
|
|
417
|
+
It has a unique ID, a human-readable name, and boolean to flag current platform.
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
id: str
|
|
421
|
+
"""Unique ID of the platform."""
|
|
422
|
+
|
|
423
|
+
name: str
|
|
424
|
+
"""User-friendly name of the platform."""
|
|
425
|
+
|
|
426
|
+
icon: str = field(repr=False, default="❓")
|
|
427
|
+
"""Icon of the platform."""
|
|
428
|
+
|
|
429
|
+
current: bool = field(init=False)
|
|
430
|
+
"""`True` if current environment runs on this platform."""
|
|
431
|
+
|
|
432
|
+
def __post_init__(self):
|
|
433
|
+
"""Set the ``current`` attribute to identifying the current platform."""
|
|
434
|
+
check_func_id = f"is_{self.id}"
|
|
435
|
+
assert check_func_id in globals()
|
|
436
|
+
object.__setattr__(self, "current", globals()[check_func_id]())
|
|
437
|
+
object.__setattr__(self, "__doc__", f"Identify {self.name}.")
|
|
438
|
+
|
|
439
|
+
def info(self) -> dict[str, str | bool | None | dict[str, str | None]]:
|
|
440
|
+
"""Returns all platform attributes we can gather."""
|
|
441
|
+
info = {
|
|
442
|
+
"id": self.id,
|
|
443
|
+
"name": self.name,
|
|
444
|
+
"icon": self.icon,
|
|
445
|
+
"current": self.current,
|
|
446
|
+
# Extra fields from distro.info().
|
|
447
|
+
"distro_id": None,
|
|
448
|
+
"version": None,
|
|
449
|
+
"version_parts": {"major": None, "minor": None, "build_number": None},
|
|
450
|
+
"like": None,
|
|
451
|
+
"codename": None,
|
|
452
|
+
}
|
|
453
|
+
# Get extra info from distro.
|
|
454
|
+
if self.current:
|
|
455
|
+
distro_info = distro.info()
|
|
456
|
+
# Rename distro ID to avoid conflict with our own ID.
|
|
457
|
+
distro_info["distro_id"] = distro_info.pop("id")
|
|
458
|
+
info = recursive_update(info, remove_blanks(distro_info))
|
|
459
|
+
return info
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
AIX = Platform("aix", "IBM AIX", "➿")
|
|
463
|
+
ALTLINUX = Platform("altlinux", "ALT Linux")
|
|
464
|
+
AMZN = Platform("amzn", "Amazon Linux", "🙂")
|
|
465
|
+
ANDROID = Platform("android", "Android", "🤖")
|
|
466
|
+
ARCH = Platform("arch", "Arch Linux", "🎗️")
|
|
467
|
+
BUILDROOT = Platform("buildroot", "Buildroot")
|
|
468
|
+
CENTOS = Platform("centos", "CentOS", "💠")
|
|
469
|
+
CLOUDLINUX = Platform("cloudlinux", "CloudLinux OS")
|
|
470
|
+
CYGWIN = Platform("cygwin", "Cygwin", "Ͼ")
|
|
471
|
+
DEBIAN = Platform("debian", "Debian", "🌀")
|
|
472
|
+
EXHERBO = Platform("exherbo", "Exherbo Linux")
|
|
473
|
+
FEDORA = Platform("fedora", "Fedora", "🎩")
|
|
474
|
+
FREEBSD = Platform("freebsd", "FreeBSD", "😈")
|
|
475
|
+
GENTOO = Platform("gentoo", "Gentoo Linux", "🗜️")
|
|
476
|
+
GUIX = Platform("guix", "Guix System")
|
|
477
|
+
HURD = Platform("hurd", "GNU/Hurd", "🐃")
|
|
478
|
+
IBM_POWERKVM = Platform("ibm_powerkvm", "IBM PowerKVM")
|
|
479
|
+
KVMIBM = Platform("kvmibm", "KVM for IBM z Systems")
|
|
480
|
+
LINUXMINT = Platform("linuxmint", "Linux Mint", "🌿")
|
|
481
|
+
MACOS = Platform("macos", "macOS", "🍎")
|
|
482
|
+
MAGEIA = Platform("mageia", "Mageia")
|
|
483
|
+
MANDRIVA = Platform("mandriva", "Mandriva Linux")
|
|
484
|
+
MIDNIGHTBSD = Platform("midnightbsd", "MidnightBSD", "🌘")
|
|
485
|
+
NETBSD = Platform("netbsd", "NetBSD", "🚩")
|
|
486
|
+
OPENBSD = Platform("openbsd", "OpenBSD", "🐡")
|
|
487
|
+
OPENSUSE = Platform("opensuse", "openSUSE", "🦎")
|
|
488
|
+
ORACLE = Platform("oracle", "Oracle Linux", "🦴")
|
|
489
|
+
PARALLELS = Platform("parallels", "Parallels")
|
|
490
|
+
PIDORA = Platform("pidora", "Pidora")
|
|
491
|
+
RASPBIAN = Platform("raspbian", "Raspbian", "🍓")
|
|
492
|
+
RHEL = Platform("rhel", "RedHat Enterprise Linux", "🎩")
|
|
493
|
+
ROCKY = Platform("rocky", "Rocky Linux", "💠")
|
|
494
|
+
SCIENTIFIC = Platform("scientific", "Scientific Linux")
|
|
495
|
+
SLACKWARE = Platform("slackware", "Slackware")
|
|
496
|
+
SLES = Platform("sles", "SUSE Linux Enterprise Server", "🦎")
|
|
497
|
+
SOLARIS = Platform("solaris", "Solaris", "🌞")
|
|
498
|
+
SUNOS = Platform("sunos", "SunOS", "☀️")
|
|
499
|
+
UBUNTU = Platform("ubuntu", "Ubuntu", "🎯")
|
|
500
|
+
UNKNOWN_LINUX = Platform("unknown_linux", "Unknown Linux", "🐧")
|
|
501
|
+
WINDOWS = Platform("windows", "Windows", "🪟")
|
|
502
|
+
WSL1 = Platform("wsl1", "Windows Subsystem for Linux v1", "⊞")
|
|
503
|
+
WSL2 = Platform("wsl2", "Windows Subsystem for Linux v2", "⊞")
|
|
504
|
+
XENSERVER = Platform("xenserver", "XenServer")
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
@dataclass(frozen=True)
|
|
508
|
+
class Group:
|
|
509
|
+
"""A ``Group`` identify a collection of ``Platform``.
|
|
510
|
+
|
|
511
|
+
Used to group platforms of the same family.
|
|
512
|
+
"""
|
|
513
|
+
|
|
514
|
+
id: str
|
|
515
|
+
"""Unique ID of the group."""
|
|
516
|
+
|
|
517
|
+
name: str
|
|
518
|
+
"""User-friendly description of a group."""
|
|
519
|
+
|
|
520
|
+
icon: str = field(repr=False, default="❓")
|
|
521
|
+
"""Icon of the group."""
|
|
522
|
+
|
|
523
|
+
platforms: tuple[Platform, ...] = field(repr=False, default_factory=tuple)
|
|
524
|
+
"""Sorted list of platforms that belong to this group."""
|
|
525
|
+
|
|
526
|
+
platform_ids: frozenset[str] = field(default_factory=frozenset)
|
|
527
|
+
"""Set of platform IDs that belong to this group.
|
|
528
|
+
|
|
529
|
+
Used to test platform overlaps between groups.
|
|
530
|
+
"""
|
|
531
|
+
|
|
532
|
+
def __post_init__(self):
|
|
533
|
+
"""Keep the platforms sorted by IDs."""
|
|
534
|
+
object.__setattr__(
|
|
535
|
+
self,
|
|
536
|
+
"platforms",
|
|
537
|
+
tuple(sorted(self.platforms, key=lambda p: p.id)),
|
|
538
|
+
)
|
|
539
|
+
object.__setattr__(
|
|
540
|
+
self,
|
|
541
|
+
"platform_ids",
|
|
542
|
+
frozenset({p.id for p in self.platforms}),
|
|
543
|
+
)
|
|
544
|
+
# Double-check there is no duplicate platforms.
|
|
545
|
+
assert len(self.platforms) == len(self.platform_ids)
|
|
546
|
+
|
|
547
|
+
def __iter__(self) -> Iterator[Platform]:
|
|
548
|
+
"""Iterate over the platforms of the group."""
|
|
549
|
+
yield from self.platforms
|
|
550
|
+
|
|
551
|
+
def __len__(self) -> int:
|
|
552
|
+
"""Return the number of platforms in the group."""
|
|
553
|
+
return len(self.platforms)
|
|
554
|
+
|
|
555
|
+
@staticmethod
|
|
556
|
+
def _extract_platform_ids(other: Group | Iterable[Platform]) -> frozenset[str]:
|
|
557
|
+
"""Extract the platform IDs from ``other``."""
|
|
558
|
+
if isinstance(other, Group):
|
|
559
|
+
return other.platform_ids
|
|
560
|
+
return frozenset(p.id for p in other)
|
|
561
|
+
|
|
562
|
+
def isdisjoint(self, other: Group | Iterable[Platform]) -> bool:
|
|
563
|
+
"""Return `True` if the group has no platforms in common with ``other``."""
|
|
564
|
+
return self.platform_ids.isdisjoint(self._extract_platform_ids(other))
|
|
565
|
+
|
|
566
|
+
def fullyintersects(self, other: Group | Iterable[Platform]) -> bool:
|
|
567
|
+
"""Return `True` if the group has all platforms in common with ``other``.
|
|
568
|
+
|
|
569
|
+
We cannot just compare ``Groups`` with the ``==`` equality operator as the
|
|
570
|
+
latter takes all attributes into account, as per ``dataclass`` default behavior.
|
|
571
|
+
"""
|
|
572
|
+
return self.platform_ids == self._extract_platform_ids(other)
|
|
573
|
+
|
|
574
|
+
def issubset(self, other: Group | Iterable[Platform]) -> bool:
|
|
575
|
+
return self.platform_ids.issubset(self._extract_platform_ids(other))
|
|
576
|
+
|
|
577
|
+
def issuperset(self, other: Group | Iterable[Platform]) -> bool:
|
|
578
|
+
return self.platform_ids.issuperset(self._extract_platform_ids(other))
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
ALL_PLATFORMS: Group = Group(
|
|
582
|
+
"all_platforms",
|
|
583
|
+
"Any platforms",
|
|
584
|
+
"🖥️",
|
|
585
|
+
(
|
|
586
|
+
AIX,
|
|
587
|
+
ALTLINUX,
|
|
588
|
+
AMZN,
|
|
589
|
+
ANDROID,
|
|
590
|
+
ARCH,
|
|
591
|
+
BUILDROOT,
|
|
592
|
+
CENTOS,
|
|
593
|
+
CLOUDLINUX,
|
|
594
|
+
CYGWIN,
|
|
595
|
+
DEBIAN,
|
|
596
|
+
EXHERBO,
|
|
597
|
+
FEDORA,
|
|
598
|
+
FREEBSD,
|
|
599
|
+
GENTOO,
|
|
600
|
+
GUIX,
|
|
601
|
+
HURD,
|
|
602
|
+
IBM_POWERKVM,
|
|
603
|
+
KVMIBM,
|
|
604
|
+
LINUXMINT,
|
|
605
|
+
MACOS,
|
|
606
|
+
MAGEIA,
|
|
607
|
+
MANDRIVA,
|
|
608
|
+
MIDNIGHTBSD,
|
|
609
|
+
NETBSD,
|
|
610
|
+
OPENBSD,
|
|
611
|
+
OPENSUSE,
|
|
612
|
+
ORACLE,
|
|
613
|
+
PARALLELS,
|
|
614
|
+
PIDORA,
|
|
615
|
+
RASPBIAN,
|
|
616
|
+
RHEL,
|
|
617
|
+
ROCKY,
|
|
618
|
+
SCIENTIFIC,
|
|
619
|
+
SLACKWARE,
|
|
620
|
+
SLES,
|
|
621
|
+
SOLARIS,
|
|
622
|
+
SUNOS,
|
|
623
|
+
UBUNTU,
|
|
624
|
+
UNKNOWN_LINUX,
|
|
625
|
+
WINDOWS,
|
|
626
|
+
WSL1,
|
|
627
|
+
WSL2,
|
|
628
|
+
XENSERVER,
|
|
629
|
+
),
|
|
630
|
+
)
|
|
631
|
+
"""All recognized platforms."""
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
ALL_WINDOWS = Group("all_windows", "Any Windows", "🪟", (WINDOWS,))
|
|
635
|
+
"""All Windows operating systems."""
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
UNIX = Group(
|
|
639
|
+
"unix",
|
|
640
|
+
"Any Unix",
|
|
641
|
+
"⨷",
|
|
642
|
+
tuple(p for p in ALL_PLATFORMS.platforms if p not in ALL_WINDOWS),
|
|
643
|
+
)
|
|
644
|
+
"""All Unix-like operating systems and compatibility layers."""
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
UNIX_WITHOUT_MACOS = Group(
|
|
648
|
+
"unix_without_macos",
|
|
649
|
+
"Any Unix but macOS",
|
|
650
|
+
"⨂",
|
|
651
|
+
tuple(p for p in UNIX if p is not MACOS),
|
|
652
|
+
)
|
|
653
|
+
"""All Unix platforms, without macOS.
|
|
654
|
+
|
|
655
|
+
This is useful to avoid macOS-specific workarounds on Unix platforms.
|
|
656
|
+
"""
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
BSD = Group(
|
|
660
|
+
"bsd", "Any BSD", "🅱️", (FREEBSD, MACOS, MIDNIGHTBSD, NETBSD, OPENBSD, SUNOS)
|
|
661
|
+
)
|
|
662
|
+
"""All BSD platforms.
|
|
663
|
+
|
|
664
|
+
.. note::
|
|
665
|
+
Are considered of this family (`according Wikipedia
|
|
666
|
+
<https://en.wikipedia.org/wiki/Template:Unix>`_):
|
|
667
|
+
|
|
668
|
+
- `386BSD` (`FreeBSD`, `NetBSD`, `OpenBSD`, `DragonFly BSD`)
|
|
669
|
+
- `NeXTSTEP`
|
|
670
|
+
- `Darwin` (`macOS`, `iOS`, `audioOS`, `iPadOS`, `tvOS`, `watchOS`, `bridgeOS`)
|
|
671
|
+
- `SunOS`
|
|
672
|
+
- `Ultrix`
|
|
673
|
+
"""
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
BSD_WITHOUT_MACOS = Group(
|
|
677
|
+
"bsd_without_macos",
|
|
678
|
+
"Any BSD but macOS",
|
|
679
|
+
"🅱️",
|
|
680
|
+
tuple(p for p in BSD if p is not MACOS),
|
|
681
|
+
)
|
|
682
|
+
"""All BSD platforms, without macOS.
|
|
683
|
+
|
|
684
|
+
This is useful to avoid macOS-specific workarounds on BSD platforms.
|
|
685
|
+
"""
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
ALL_LINUX = Group(
|
|
689
|
+
"all_linux",
|
|
690
|
+
"Any Linux",
|
|
691
|
+
"🐧",
|
|
692
|
+
(
|
|
693
|
+
ALTLINUX,
|
|
694
|
+
AMZN,
|
|
695
|
+
ANDROID,
|
|
696
|
+
ARCH,
|
|
697
|
+
BUILDROOT,
|
|
698
|
+
CENTOS,
|
|
699
|
+
CLOUDLINUX,
|
|
700
|
+
DEBIAN,
|
|
701
|
+
EXHERBO,
|
|
702
|
+
FEDORA,
|
|
703
|
+
GENTOO,
|
|
704
|
+
GUIX,
|
|
705
|
+
IBM_POWERKVM,
|
|
706
|
+
KVMIBM,
|
|
707
|
+
LINUXMINT,
|
|
708
|
+
MAGEIA,
|
|
709
|
+
MANDRIVA,
|
|
710
|
+
OPENSUSE,
|
|
711
|
+
ORACLE,
|
|
712
|
+
PARALLELS,
|
|
713
|
+
PIDORA,
|
|
714
|
+
RASPBIAN,
|
|
715
|
+
RHEL,
|
|
716
|
+
ROCKY,
|
|
717
|
+
SCIENTIFIC,
|
|
718
|
+
SLACKWARE,
|
|
719
|
+
SLES,
|
|
720
|
+
UBUNTU,
|
|
721
|
+
UNKNOWN_LINUX,
|
|
722
|
+
XENSERVER,
|
|
723
|
+
),
|
|
724
|
+
)
|
|
725
|
+
"""All Unix platforms based on a Linux kernel.
|
|
726
|
+
|
|
727
|
+
.. note::
|
|
728
|
+
Are considered of this family (`according Wikipedia
|
|
729
|
+
<https://en.wikipedia.org/wiki/Template:Unix>`_):
|
|
730
|
+
|
|
731
|
+
- `Android`
|
|
732
|
+
- `ChromeOS`
|
|
733
|
+
- any other distribution
|
|
734
|
+
"""
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
LINUX_LAYERS = Group(
|
|
738
|
+
"linux_layers", "Any Linux compatibility layers", "≚", (WSL1, WSL2)
|
|
739
|
+
)
|
|
740
|
+
"""Interfaces that allows Linux binaries to run on a different host system.
|
|
741
|
+
|
|
742
|
+
.. note::
|
|
743
|
+
Are considered of this family (`according Wikipedia
|
|
744
|
+
<https://en.wikipedia.org/wiki/Template:Unix>`_):
|
|
745
|
+
|
|
746
|
+
- `Windows Subsystem for Linux`
|
|
747
|
+
"""
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
SYSTEM_V = Group(
|
|
751
|
+
"system_v", "Any Unix derived from AT&T System Five", "Ⅴ", (AIX, SOLARIS)
|
|
752
|
+
)
|
|
753
|
+
"""All Unix platforms derived from AT&T System Five.
|
|
754
|
+
|
|
755
|
+
.. note::
|
|
756
|
+
Are considered of this family (`according Wikipedia
|
|
757
|
+
<https://en.wikipedia.org/wiki/Template:Unix>`_):
|
|
758
|
+
|
|
759
|
+
- `A/UX`
|
|
760
|
+
- `AIX`
|
|
761
|
+
- `HP-UX`
|
|
762
|
+
- `IRIX`
|
|
763
|
+
- `OpenServer`
|
|
764
|
+
- `Solaris`
|
|
765
|
+
- `OpenSolaris`
|
|
766
|
+
- `Illumos`
|
|
767
|
+
- `Tru64`
|
|
768
|
+
- `UNIX`
|
|
769
|
+
- `UnixWare`
|
|
770
|
+
"""
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
UNIX_LAYERS = Group(
|
|
774
|
+
"unix_layers",
|
|
775
|
+
"Any Unix compatibility layers",
|
|
776
|
+
"≛",
|
|
777
|
+
(CYGWIN,),
|
|
778
|
+
)
|
|
779
|
+
"""Interfaces that allows Unix binaries to run on a different host system.
|
|
780
|
+
|
|
781
|
+
.. note::
|
|
782
|
+
Are considered of this family (`according Wikipedia
|
|
783
|
+
<https://en.wikipedia.org/wiki/Template:Unix>`_):
|
|
784
|
+
|
|
785
|
+
- `Cygwin`
|
|
786
|
+
- `Darling`
|
|
787
|
+
- `Eunice`
|
|
788
|
+
- `GNV`
|
|
789
|
+
- `Interix`
|
|
790
|
+
- `MachTen`
|
|
791
|
+
- `Microsoft POSIX subsystem`
|
|
792
|
+
- `MKS Toolkit`
|
|
793
|
+
- `PASE`
|
|
794
|
+
- `P.I.P.S.`
|
|
795
|
+
- `PWS/VSE-AF`
|
|
796
|
+
- `UNIX System Services`
|
|
797
|
+
- `UserLAnd Technologies`
|
|
798
|
+
- `Windows Services for UNIX`
|
|
799
|
+
"""
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
OTHER_UNIX = Group(
|
|
803
|
+
"other_unix",
|
|
804
|
+
"Any other Unix",
|
|
805
|
+
"⊎",
|
|
806
|
+
tuple(
|
|
807
|
+
p
|
|
808
|
+
for p in UNIX
|
|
809
|
+
if p
|
|
810
|
+
not in (
|
|
811
|
+
BSD.platforms
|
|
812
|
+
+ ALL_LINUX.platforms
|
|
813
|
+
+ LINUX_LAYERS.platforms
|
|
814
|
+
+ SYSTEM_V.platforms
|
|
815
|
+
+ UNIX_LAYERS.platforms
|
|
816
|
+
)
|
|
817
|
+
),
|
|
818
|
+
)
|
|
819
|
+
"""All other Unix platforms.
|
|
820
|
+
|
|
821
|
+
.. note::
|
|
822
|
+
Are considered of this family (`according Wikipedia
|
|
823
|
+
<https://en.wikipedia.org/wiki/Template:Unix>`_):
|
|
824
|
+
|
|
825
|
+
- `Coherent`
|
|
826
|
+
- `GNU/Hurd`
|
|
827
|
+
- `HarmonyOS`
|
|
828
|
+
- `LiteOS`
|
|
829
|
+
- `LynxOS`
|
|
830
|
+
- `Minix`
|
|
831
|
+
- `MOS`
|
|
832
|
+
- `OSF/1`
|
|
833
|
+
- `QNX`
|
|
834
|
+
- `BlackBerry 10`
|
|
835
|
+
- `Research Unix`
|
|
836
|
+
- `SerenityOS`
|
|
837
|
+
"""
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
NON_OVERLAPPING_GROUPS: frozenset[Group] = frozenset(
|
|
841
|
+
(
|
|
842
|
+
ALL_WINDOWS,
|
|
843
|
+
BSD,
|
|
844
|
+
ALL_LINUX,
|
|
845
|
+
LINUX_LAYERS,
|
|
846
|
+
SYSTEM_V,
|
|
847
|
+
UNIX_LAYERS,
|
|
848
|
+
OTHER_UNIX,
|
|
849
|
+
),
|
|
850
|
+
)
|
|
851
|
+
"""Non-overlapping groups."""
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
EXTRA_GROUPS: frozenset[Group] = frozenset(
|
|
855
|
+
(
|
|
856
|
+
ALL_PLATFORMS,
|
|
857
|
+
UNIX,
|
|
858
|
+
UNIX_WITHOUT_MACOS,
|
|
859
|
+
BSD_WITHOUT_MACOS,
|
|
860
|
+
),
|
|
861
|
+
)
|
|
862
|
+
"""Overlapping groups, defined for convenience."""
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
ALL_GROUPS: frozenset[Group] = frozenset(NON_OVERLAPPING_GROUPS | EXTRA_GROUPS)
|
|
866
|
+
"""All groups."""
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
ALL_OS_LABELS: frozenset[str] = frozenset(p.name for p in ALL_PLATFORMS.platforms)
|
|
870
|
+
"""Sets of all recognized labels."""
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
def reduce(items: Iterable[Group | Platform]) -> set[Group | Platform]:
|
|
874
|
+
"""Reduce a collection of ``Group`` and ``Platform`` to a minimal set.
|
|
875
|
+
|
|
876
|
+
Returns a deduplicated set of ``Group`` and ``Platform`` that covers the same exact
|
|
877
|
+
platforms as the original input, but group as much platforms as possible, to reduce
|
|
878
|
+
the number of items.
|
|
879
|
+
|
|
880
|
+
.. hint::
|
|
881
|
+
Maybe this could be solved with some `Euler diagram
|
|
882
|
+
<https://en.wikipedia.org/wiki/Euler_diagram>`_ algorithms, like those
|
|
883
|
+
implemented in `eule <https://github.com/trouchet/eule>`_.
|
|
884
|
+
|
|
885
|
+
This is being discussed upstream at `trouchet/eule#120
|
|
886
|
+
<https://github.com/trouchet/eule/issues/120>`_.
|
|
887
|
+
"""
|
|
888
|
+
# Collect all platforms.
|
|
889
|
+
platforms: set[Platform] = set()
|
|
890
|
+
for item in items:
|
|
891
|
+
if isinstance(item, Group):
|
|
892
|
+
platforms.update(item.platforms)
|
|
893
|
+
else:
|
|
894
|
+
platforms.add(item)
|
|
895
|
+
|
|
896
|
+
# List any group matching the platforms.
|
|
897
|
+
valid_groups: set[Group] = set()
|
|
898
|
+
for group in ALL_GROUPS:
|
|
899
|
+
if group.issubset(platforms):
|
|
900
|
+
valid_groups.add(group)
|
|
901
|
+
|
|
902
|
+
# Test all combination of groups to find the smallest set of groups + platforms.
|
|
903
|
+
min_items: int = 0
|
|
904
|
+
results: list[set[Group | Platform]] = []
|
|
905
|
+
# Serialize group sets for deterministic lookups. Sort them by platform count.
|
|
906
|
+
groups = tuple(sorted(valid_groups, key=len, reverse=True))
|
|
907
|
+
for subset_size in range(1, len(groups) + 1):
|
|
908
|
+
# If we already have a solution that involves less items than the current
|
|
909
|
+
# subset of groups we're going to evaluates, there is no point in continuing.
|
|
910
|
+
if min_items and subset_size > min_items:
|
|
911
|
+
break
|
|
912
|
+
|
|
913
|
+
for group_subset in combinations(groups, subset_size):
|
|
914
|
+
# If any group overlaps another, there is no point in exploring this subset.
|
|
915
|
+
if not all(g[0].isdisjoint(g[1]) for g in combinations(group_subset, 2)):
|
|
916
|
+
continue
|
|
917
|
+
|
|
918
|
+
# Remove all platforms covered by the groups.
|
|
919
|
+
ungrouped_platforms = platforms.copy()
|
|
920
|
+
for group in group_subset:
|
|
921
|
+
ungrouped_platforms.difference_update(group.platforms)
|
|
922
|
+
|
|
923
|
+
# Merge the groups and the remaining platforms.
|
|
924
|
+
reduction = ungrouped_platforms.union(group_subset)
|
|
925
|
+
reduction_size = len(reduction)
|
|
926
|
+
|
|
927
|
+
# Reset the results if we have a new solution that is better than the
|
|
928
|
+
# previous ones.
|
|
929
|
+
if not results or reduction_size < min_items:
|
|
930
|
+
results = [reduction]
|
|
931
|
+
min_items = reduction_size
|
|
932
|
+
# If the solution is as good as the previous one, add it to the results.
|
|
933
|
+
elif reduction_size == min_items:
|
|
934
|
+
results.append(reduction)
|
|
935
|
+
|
|
936
|
+
if len(results) > 1:
|
|
937
|
+
msg = f"Multiple solutions found: {results}"
|
|
938
|
+
raise RuntimeError(msg)
|
|
939
|
+
|
|
940
|
+
# If no reduced solution was found, return the original platforms.
|
|
941
|
+
if not results:
|
|
942
|
+
return platforms # type: ignore[return-value]
|
|
943
|
+
|
|
944
|
+
return results.pop()
|
|
945
|
+
|
|
946
|
+
|
|
947
|
+
@cache
|
|
948
|
+
def current_os() -> Platform:
|
|
949
|
+
"""Return the current platform."""
|
|
950
|
+
matching = []
|
|
951
|
+
for p in ALL_PLATFORMS.platforms:
|
|
952
|
+
if p.current:
|
|
953
|
+
matching.append(p)
|
|
954
|
+
|
|
955
|
+
if len(matching) > 1:
|
|
956
|
+
msg = f"Multiple platforms match current OS: {matching}"
|
|
957
|
+
raise RuntimeError(msg)
|
|
958
|
+
|
|
959
|
+
if not matching:
|
|
960
|
+
msg = (
|
|
961
|
+
f"Unrecognized {sys.platform} / "
|
|
962
|
+
f"{platform.platform(aliased=True, terse=True)} platform."
|
|
963
|
+
)
|
|
964
|
+
raise SystemError(msg)
|
|
965
|
+
|
|
966
|
+
assert len(matching) == 1
|
|
967
|
+
return matching.pop()
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
CURRENT_OS_ID: str = current_os().id
|
|
971
|
+
CURRENT_OS_LABEL: str = current_os().name
|
|
972
|
+
"""Constants about the current platform."""
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: extra-platforms
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Detect platforms and group them by family
|
|
5
|
+
Author-email: Kevin Deldycke <kevin@deldycke.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/kdeldycke/extra-platforms
|
|
7
|
+
Project-URL: Documentation, https://kdeldycke.github.io/extra-platforms
|
|
8
|
+
Project-URL: Repository, https://github.com/kdeldycke/extra-platforms
|
|
9
|
+
Project-URL: Funding, https://github.com/sponsors/kdeldycke
|
|
10
|
+
Project-URL: Issues, https://github.com/kdeldycke/extra-platforms/issues
|
|
11
|
+
Project-URL: Changelog, https://kdeldycke.github.io/extra-platforms/changelog.html
|
|
12
|
+
Keywords: multiplatform,pytest,python
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Framework :: Pytest
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)
|
|
18
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
19
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
20
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
21
|
+
Classifier: Programming Language :: Python :: 3
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
27
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
28
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
|
+
Classifier: Topic :: Utilities
|
|
30
|
+
Classifier: Typing :: Typed
|
|
31
|
+
Requires-Python: >=3.8
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
Requires-Dist: boltons ~=24.0.0
|
|
34
|
+
Requires-Dist: distro ~=1.9.0
|
|
35
|
+
Provides-Extra: docs
|
|
36
|
+
Requires-Dist: furo ~=2024.8.6 ; extra == 'docs'
|
|
37
|
+
Requires-Dist: myst-parser ~=3.0.0 ; extra == 'docs'
|
|
38
|
+
Requires-Dist: sphinx >=7 ; extra == 'docs'
|
|
39
|
+
Requires-Dist: sphinx-autodoc-typehints >=2 ; extra == 'docs'
|
|
40
|
+
Requires-Dist: sphinx-copybutton ~=0.5.2 ; extra == 'docs'
|
|
41
|
+
Requires-Dist: sphinx-design >=0.5 ; extra == 'docs'
|
|
42
|
+
Requires-Dist: sphinx-issues ~=4.1.0 ; extra == 'docs'
|
|
43
|
+
Requires-Dist: sphinxcontrib-mermaid ~=0.9.2 ; extra == 'docs'
|
|
44
|
+
Requires-Dist: sphinxext-opengraph ~=0.9.0 ; extra == 'docs'
|
|
45
|
+
Requires-Dist: tomli ~=2.0.1 ; (python_version < "3.11") and extra == 'docs'
|
|
46
|
+
Provides-Extra: pytest
|
|
47
|
+
Requires-Dist: pytest >=8 ; extra == 'pytest'
|
|
48
|
+
Provides-Extra: test
|
|
49
|
+
Requires-Dist: coverage[toml] ~=7.6.0 ; extra == 'test'
|
|
50
|
+
Requires-Dist: pytest ~=8.3.1 ; extra == 'test'
|
|
51
|
+
Requires-Dist: pytest-cov ~=5.0.0 ; extra == 'test'
|
|
52
|
+
Requires-Dist: pytest-github-actions-annotate-failures ~=0.2.0 ; extra == 'test'
|
|
53
|
+
Requires-Dist: pytest-randomly ~=3.15.0 ; extra == 'test'
|
|
54
|
+
|
|
55
|
+
# Extra Platforms
|
|
56
|
+
|
|
57
|
+
[](https://pypi.python.org/pypi/extra-platforms)
|
|
58
|
+
[](https://pypi.python.org/pypi/extra-platforms)
|
|
59
|
+
[](https://pepy.tech/project/extra_platforms)
|
|
60
|
+
[](https://github.com/kdeldycke/extra-platforms/actions/workflows/tests.yaml?query=branch%3Amain)
|
|
61
|
+
[](https://app.codecov.io/gh/kdeldycke/extra-platforms)
|
|
62
|
+
[](https://github.com/kdeldycke/extra-platforms/actions/workflows/docs.yaml?query=branch%3Amain)
|
|
63
|
+
[](https://doi.org/10.5281/zenodo.7116050)
|
|
64
|
+
|
|
65
|
+
## What is Extra Platforms?
|
|
66
|
+
|
|
67
|
+
> [!NOTE]
|
|
68
|
+
> TODO
|
|
69
|
+
|
|
70
|
+
## Example
|
|
71
|
+
|
|
72
|
+
> [!NOTE]
|
|
73
|
+
> TODO
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## Used in
|
|
77
|
+
|
|
78
|
+
Check these projects to get real-life examples of `extra-platforms` usage:
|
|
79
|
+
|
|
80
|
+
-  [Click Extra](https://github.com/kdeldycke/click-extra#readme) - Drop-in replacement for Click to make user-friendly and colorful CLI.
|
|
81
|
+
|
|
82
|
+
Feel free to send a PR to add your project in this list if you are relying on Click Extra in any way.
|
|
83
|
+
|
|
84
|
+
## Development
|
|
85
|
+
|
|
86
|
+
[Development guidelines](https://github.com/kdeldycke/click-extra?tab=readme-ov-file#development) are the same as [parent project Click Extra](https://github.com/kdeldycke/click-extra), from which `extra-platforms` originated.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
extra_platforms/__init__.py,sha256=IpA6CQJ4VXHabn7qf4bA2pkO1jyJB-Wc0dxwYMGvrjY,1219
|
|
2
|
+
extra_platforms/platforms.py,sha256=78DcCNoTKh0i05xZBZxw8Dif3_nzKbGu8_ge3CyGGMc,26955
|
|
3
|
+
extra_platforms-1.0.0.dist-info/METADATA,sha256=clyQ5zdSMpN3kwiE_oMktQw8FKXSDFrFw66hx_SBajQ,4460
|
|
4
|
+
extra_platforms-1.0.0.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
|
|
5
|
+
extra_platforms-1.0.0.dist-info/entry_points.txt,sha256=isBLN3Ql7i0xvHQnB1S36QVWepytJD7GvesLxzhPjCg,52
|
|
6
|
+
extra_platforms-1.0.0.dist-info/top_level.txt,sha256=9182Fz_BFq0UF5vXtcQFkH1lQOeMK29bs3SH3e6tjJA,16
|
|
7
|
+
extra_platforms-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
extra_platforms
|