|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# pylint: disable=invalid-name |
| 3 | + |
| 4 | +from argparse import ArgumentParser |
| 5 | +import re |
| 6 | +import shlex |
| 7 | +import shutil |
| 8 | +import subprocess |
| 9 | + |
| 10 | + |
| 11 | +def print_cmd(args, command): |
| 12 | + if not args.quiet: |
| 13 | + print(f"$ {' '.join([shlex.quote(str(elem)) for elem in command])}", |
| 14 | + flush=True) |
| 15 | + |
| 16 | + |
| 17 | +def chronic(*args, **kwargs): |
| 18 | + try: |
| 19 | + return subprocess.run(*args, |
| 20 | + **kwargs, |
| 21 | + capture_output=True, |
| 22 | + check=True, |
| 23 | + text=True) |
| 24 | + except subprocess.CalledProcessError as err: |
| 25 | + print(err.stdout) |
| 26 | + print(err.stderr) |
| 27 | + raise err |
| 28 | + |
| 29 | + |
| 30 | +PARSER = ArgumentParser( |
| 31 | + description= |
| 32 | + 'Get clang version from various Linux distributions through docker/podman)' |
| 33 | +) |
| 34 | +PARSER.add_argument('-m', |
| 35 | + '--markdown', |
| 36 | + action='store_true', |
| 37 | + help='Output results as a Markdown table') |
| 38 | +PARSER.add_argument('-q', |
| 39 | + '--quiet', |
| 40 | + action='store_true', |
| 41 | + help='Do not print commands being run') |
| 42 | +ARGS = PARSER.parse_args() |
| 43 | + |
| 44 | +if ARGS.markdown: |
| 45 | + # pylint: disable-next=import-error |
| 46 | + from py_markdown_table.markdown_table import markdown_table |
| 47 | + |
| 48 | +for MANAGER in (MANAGERS := ['podman', 'docker']): |
| 49 | + if shutil.which(MANAGER): |
| 50 | + break |
| 51 | +else: |
| 52 | + raise RuntimeError( |
| 53 | + f"Neither {' nor '.join(MANAGERS)} could be found on your system!") |
| 54 | + |
| 55 | +# This list should only include versions that are actively being supported. |
| 56 | +# |
| 57 | +# Tags such as "latest", "stable", or "rolling" are preferred so that the list |
| 58 | +# does not have to be constantly updated. Old but supported releases like |
| 59 | +# Fedora or OpenSUSE are the exception. |
| 60 | +IMAGES = [ |
| 61 | + # Arch Linux is rolling release, it only has one supported version at a time. |
| 62 | + ('archlinux', 'latest'), |
| 63 | + # Debian: |
| 64 | + # * https://www.debian.org/releases/ |
| 65 | + # * https://wiki.debian.org/LTS |
| 66 | + # * https://hub.docker.com/_/debian |
| 67 | + *[('debian', f"{ver}-slim") for ver in |
| 68 | + ['oldoldstable', 'oldstable', 'stable', 'testing', 'unstable']], |
| 69 | + # Fedora: |
| 70 | + # * https://fedoraproject.org/wiki/Releases |
| 71 | + # * https://fedoraproject.org/wiki/End_of_life |
| 72 | + # * https://hub.docker.com/_/fedora |
| 73 | + *[('fedora', ver) for ver in ['38', 'latest', 'rawhide']], |
| 74 | + # OpenSUSE: |
| 75 | + # * https://en.opensuse.org/openSUSE:Roadmap |
| 76 | + # * https://en.opensuse.org/Lifetime |
| 77 | + # * https://hub.docker.com/r/opensuse/leap |
| 78 | + ('opensuse/leap', 'latest'), |
| 79 | + ('opensuse/tumbleweed', 'latest'), |
| 80 | + # Ubuntu: |
| 81 | + # * https://wiki.ubuntu.com/Releases |
| 82 | + # * https://hub.docker.com/_/ubuntu |
| 83 | + *[('ubuntu', ver) for ver in ['focal', 'latest', 'rolling', 'devel']], |
| 84 | +] |
| 85 | + |
| 86 | +RESULTS = {} |
| 87 | +for ITEM in IMAGES: |
| 88 | + DISTRO = ITEM[0] |
| 89 | + IMAGE = ':'.join(ITEM) |
| 90 | + FULL_IMAGE = f"docker.io/{IMAGE}" |
| 91 | + |
| 92 | + # Make sure image is up to date |
| 93 | + PULL_CMD = [MANAGER, 'pull', FULL_IMAGE] |
| 94 | + print_cmd(ARGS, PULL_CMD) |
| 95 | + chronic(PULL_CMD) |
| 96 | + |
| 97 | + # Build command to update the distribution, install clang, and get its |
| 98 | + # version. |
| 99 | + ENV_VARS = {} |
| 100 | + CMDS = [] |
| 101 | + if DISTRO == 'archlinux': |
| 102 | + CMDS.append('pacman -Syyu --noconfirm') |
| 103 | + CMDS.append('pacman -S --noconfirm clang') |
| 104 | + elif DISTRO in ('debian', 'ubuntu'): |
| 105 | + ENV_VARS['DEBIAN_FRONTEND'] = 'noninteractive' |
| 106 | + |
| 107 | + CMDS.append('apt-get update') |
| 108 | + CMDS.append('apt-get upgrade -y') |
| 109 | + CMDS.append('apt-get install --no-install-recommends -y clang') |
| 110 | + elif DISTRO == 'fedora': |
| 111 | + CMDS.append('dnf update -y') |
| 112 | + CMDS.append('dnf install -y clang') |
| 113 | + elif 'opensuse' in DISTRO: |
| 114 | + CMDS.append('zypper -n up') |
| 115 | + CMDS.append('zypper -n in clang') |
| 116 | + else: |
| 117 | + raise RuntimeError(f"Don't know how to install clang on {DISTRO}?") |
| 118 | + CMDS.append('clang --version') |
| 119 | + |
| 120 | + # Run container manager with commands generated above. |
| 121 | + RUN_CMD = [ |
| 122 | + MANAGER, |
| 123 | + 'run', |
| 124 | + *[f"--env={key}={value}" for key, value in ENV_VARS.items()], |
| 125 | + '--rm', |
| 126 | + FULL_IMAGE, |
| 127 | + 'sh', |
| 128 | + '-c', |
| 129 | + ' && '.join(CMDS), |
| 130 | + ] |
| 131 | + print_cmd(ARGS, RUN_CMD) |
| 132 | + RESULT = chronic(RUN_CMD) |
| 133 | + |
| 134 | + # Locate clang version in output and add it to results |
| 135 | + if not (match := re.search( |
| 136 | + r'^[A-Za-z ]*?clang version [0-9]+\.[0-9]+\.[0-9]+.*$', |
| 137 | + RESULT.stdout, |
| 138 | + flags=re.M)): |
| 139 | + raise RuntimeError('Could not find clang version in output?') |
| 140 | + RESULTS[IMAGE] = match[0] |
| 141 | + |
| 142 | +print() |
| 143 | + |
| 144 | +# Pretty print results |
| 145 | +if ARGS.markdown: |
| 146 | + MD_DATA = [{ |
| 147 | + "Container image": f"`{key}`", |
| 148 | + "Compiler version": f"`{value}`", |
| 149 | + } for key, value in RESULTS.items()] |
| 150 | + print( |
| 151 | + markdown_table(MD_DATA).set_params(padding_weight='right', |
| 152 | + quote=False, |
| 153 | + row_sep='markdown').get_markdown()) |
| 154 | +else: |
| 155 | + WIDTH = len(max(RESULTS.keys(), key=len)) |
| 156 | + for IMAGE, VERSION in RESULTS.items(): |
| 157 | + print(f"{IMAGE:{WIDTH}} {VERSION}") |
0 commit comments