Philipp Schrader | 0e19c60 | 2018-03-07 21:07:22 -0800 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | |
James Kuszmaul | ae9c578 | 2022-04-07 11:28:42 -0700 | [diff] [blame^] | 3 | from collections import namedtuple |
Philipp Schrader | 0e19c60 | 2018-03-07 21:07:22 -0800 | [diff] [blame] | 4 | import sys |
| 5 | import os |
Brian Silverman | 4b90dd0 | 2022-01-08 18:16:22 -0800 | [diff] [blame] | 6 | import os.path |
Philipp Schrader | 0e19c60 | 2018-03-07 21:07:22 -0800 | [diff] [blame] | 7 | import re |
| 8 | import subprocess |
| 9 | import tempfile |
Brian Silverman | 4b90dd0 | 2022-01-08 18:16:22 -0800 | [diff] [blame] | 10 | import urllib.request |
Philipp Schrader | 0e19c60 | 2018-03-07 21:07:22 -0800 | [diff] [blame] | 11 | import argparse |
| 12 | import hashlib |
| 13 | |
James Kuszmaul | ae9c578 | 2022-04-07 11:28:42 -0700 | [diff] [blame^] | 14 | DistroInfo = namedtuple('DistroInfo', |
| 15 | ['sources_list', 'url_keys', 'public_keys']) |
| 16 | |
| 17 | _DISTROS = { |
| 18 | "ubuntu": |
| 19 | DistroInfo( |
| 20 | """deb http://us.archive.ubuntu.com/ubuntu/ {release} main restricted |
| 21 | deb-src http://us.archive.ubuntu.com/ubuntu/ {release} main restricted |
| 22 | |
| 23 | deb http://security.ubuntu.com/ubuntu {release}-security main restricted |
| 24 | deb-src http://security.ubuntu.com/ubuntu {release}-security main restricted |
| 25 | |
| 26 | deb http://us.archive.ubuntu.com/ubuntu/ {release} universe |
| 27 | deb-src http://us.archive.ubuntu.com/ubuntu/ {release} universe |
| 28 | deb http://us.archive.ubuntu.com/ubuntu/ {release}-updates universe |
| 29 | deb-src http://us.archive.ubuntu.com/ubuntu/ {release}-updates universe |
| 30 | |
| 31 | deb http://us.archive.ubuntu.com/ubuntu/ {release}-updates main restricted |
| 32 | deb-src http://us.archive.ubuntu.com/ubuntu/ {release}-updates main restricted |
| 33 | |
| 34 | deb http://us.archive.ubuntu.com/ubuntu {release}-backports main restricted |
| 35 | deb-src http://us.archive.ubuntu.com/ubuntu {release}-backports main restricted""", |
| 36 | [], ['3B4FE6ACC0B21F32', '871920D1991BC93C']), |
| 37 | "debian": |
| 38 | DistroInfo( |
| 39 | """deb http://deb.debian.org/debian/ {release} main contrib non-free |
| 40 | deb-src http://deb.debian.org/debian/ {release} main contrib non-free |
| 41 | |
| 42 | deb https://security.debian.org/debian-security {release}-security main contrib non-free |
| 43 | deb-src https://security.debian.org/debian-security {release}-security main contrib non-free |
| 44 | |
| 45 | deb http://deb.debian.org/debian/ {release}-updates main contrib non-free |
| 46 | deb-src http://deb.debian.org/debian/ {release}-updates main contrib non-free |
| 47 | |
| 48 | deb http://deb.debian.org/debian {release}-backports main contrib non-free |
| 49 | deb-src http://deb.debian.org/debian {release}-backports main contrib non-free""", |
| 50 | [ |
| 51 | "https://ftp-master.debian.org/keys/archive-key-11.asc", |
| 52 | "https://ftp-master.debian.org/keys/archive-key-11-security.asc" |
| 53 | ], []), |
| 54 | } |
| 55 | |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 56 | |
Brian Silverman | 4b90dd0 | 2022-01-08 18:16:22 -0800 | [diff] [blame] | 57 | def initialize_apt(apt_dir, apt_args, args): |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 58 | os.mkdir(os.path.join(apt_dir, 'etc')) |
| 59 | os.mkdir(os.path.join(apt_dir, 'etc', 'apt')) |
| 60 | os.mkdir(os.path.join(apt_dir, 'etc', 'apt', 'trusted.gpg.d')) |
| 61 | os.mkdir(os.path.join(apt_dir, 'etc', 'apt', 'preferences.d')) |
| 62 | os.mkdir(os.path.join(apt_dir, 'var')) |
| 63 | os.mkdir(os.path.join(apt_dir, 'var', 'lib')) |
| 64 | os.mkdir(os.path.join(apt_dir, 'var', 'lib', 'dpkg')) |
| 65 | with open(os.path.join(apt_dir, 'var', 'lib', 'dpkg', 'status'), 'w'): |
| 66 | pass |
James Kuszmaul | ae9c578 | 2022-04-07 11:28:42 -0700 | [diff] [blame^] | 67 | distro = _DISTROS[args.distro] |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 68 | with open(os.path.join(apt_dir, 'etc', 'apt', 'sources.list'), 'w') as f: |
James Kuszmaul | ae9c578 | 2022-04-07 11:28:42 -0700 | [diff] [blame^] | 69 | f.write(distro.sources_list.format(release=args.release)) |
| 70 | for key in args.apt_key + distro.url_keys: |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 71 | basename = os.path.basename(key) |
| 72 | urllib.request.urlretrieve( |
| 73 | key, os.path.join(apt_dir, 'etc', 'apt', 'trusted.gpg.d', |
| 74 | basename)) |
James Kuszmaul | ae9c578 | 2022-04-07 11:28:42 -0700 | [diff] [blame^] | 75 | for key in distro.public_keys: |
| 76 | subprocess.check_call([ |
| 77 | "apt-key", "--keyring", |
| 78 | os.path.join(apt_dir, 'etc', 'apt', 'trusted.gpg'), "adv", |
| 79 | "--keyserver", "keyserver.ubuntu.com", "--recv-key", key |
| 80 | ]) |
| 81 | |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 82 | subprocess.check_call(["apt-get"] + apt_args + ["update"]) |
| 83 | |
Brian Silverman | 4b90dd0 | 2022-01-08 18:16:22 -0800 | [diff] [blame] | 84 | |
| 85 | def get_deps(apt_args, package): |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 86 | env = dict(os.environ) |
James Kuszmaul | ae9c578 | 2022-04-07 11:28:42 -0700 | [diff] [blame^] | 87 | if 'LD_LIBRARY_PATH' in env: |
| 88 | del env['LD_LIBRARY_PATH'] |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 89 | out = subprocess.check_output(["apt-rdepends"] + apt_args + [package], |
| 90 | env=env) |
| 91 | deps = out.splitlines() |
| 92 | return set([dep for dep in deps if not dep.startswith(b" ")]) |
| 93 | |
Philipp Schrader | 0e19c60 | 2018-03-07 21:07:22 -0800 | [diff] [blame] | 94 | |
Brian Silverman | 4b90dd0 | 2022-01-08 18:16:22 -0800 | [diff] [blame] | 95 | def get_all_deps(apt_args, packages): |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 96 | deps = set() |
| 97 | for package in packages or (): |
| 98 | deps.update(get_deps(apt_args, package)) |
| 99 | return deps |
| 100 | |
Philipp Schrader | 0e19c60 | 2018-03-07 21:07:22 -0800 | [diff] [blame] | 101 | |
Brian Silverman | 6470f44 | 2018-08-05 12:08:16 -0700 | [diff] [blame] | 102 | def map_virtual_packages(packages): |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 103 | '''Maps known virtual packages to the preferred concrete packages which |
Brian Silverman | 6470f44 | 2018-08-05 12:08:16 -0700 | [diff] [blame] | 104 | provide them.''' |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 105 | for package in packages: |
| 106 | if package == b'python-numpy-abi9': |
| 107 | yield b'python-numpy' |
| 108 | continue |
| 109 | if package == b'python3-numpy-abi9': |
| 110 | yield b'python3-numpy' |
| 111 | continue |
| 112 | if package == b'libjack-0.125': |
| 113 | yield b'libjack-jackd2-0' |
| 114 | continue |
| 115 | if package == b'fonts-freefont': |
| 116 | yield b'fonts-freefont-ttf' |
| 117 | continue |
| 118 | if package == b'gsettings-backend': |
| 119 | yield b'dconf-gsettings-backend' |
| 120 | continue |
| 121 | if package == b'gdal-abi-2-4-0': |
| 122 | yield b'libgdal20' |
| 123 | continue |
| 124 | if package == b'libglu1': |
| 125 | yield b'libglu1-mesa' |
| 126 | continue |
| 127 | if package == b'liblapack.so.3': |
| 128 | yield b'liblapack3' |
| 129 | continue |
| 130 | if package == b'libopencl1': |
| 131 | yield b'ocl-icd-libopencl1' |
| 132 | continue |
| 133 | if package == b'libgcc1': |
| 134 | yield b'libgcc-s1' |
| 135 | continue |
| 136 | if package == b'libopencl-1.2-1': |
| 137 | yield b'ocl-icd-libopencl1' |
| 138 | continue |
| 139 | if package == b'libblas.so.3': |
| 140 | yield b'libblas3' |
| 141 | continue |
| 142 | if package == b'debconf-2.0': |
| 143 | yield b'debconf' |
| 144 | continue |
| 145 | yield package |
| 146 | |
Brian Silverman | 6470f44 | 2018-08-05 12:08:16 -0700 | [diff] [blame] | 147 | |
Philipp Schrader | fd5489f | 2022-09-17 17:31:09 -0700 | [diff] [blame] | 148 | def download_deps(apt_args, packages, excludes, force_includes, |
| 149 | force_excludes): |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 150 | deps = get_all_deps(apt_args, packages) |
| 151 | exclude_deps = get_all_deps(apt_args, excludes) |
Austin Schuh | b7775c0 | 2023-06-02 18:12:02 -0700 | [diff] [blame] | 152 | exclude_deps -= set({b'libssl1.1'}) |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 153 | deps -= exclude_deps |
| 154 | force_include_deps = get_all_deps(apt_args, force_includes) |
| 155 | deps |= force_include_deps |
Philipp Schrader | fd5489f | 2022-09-17 17:31:09 -0700 | [diff] [blame] | 156 | force_exclude_deps = get_all_deps(apt_args, force_excludes) |
| 157 | deps -= force_exclude_deps |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 158 | env = dict(os.environ) |
James Kuszmaul | ae9c578 | 2022-04-07 11:28:42 -0700 | [diff] [blame^] | 159 | if 'LD_LIBRARY_PATH' in env: |
| 160 | del env['LD_LIBRARY_PATH'] |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 161 | subprocess.check_call([b"apt-get"] + [a.encode('utf-8') |
| 162 | for a in apt_args] + [b"download"] + |
| 163 | list(map_virtual_packages(deps)), |
| 164 | env=env) |
| 165 | |
Philipp Schrader | 0e19c60 | 2018-03-07 21:07:22 -0800 | [diff] [blame] | 166 | |
| 167 | def fixup_files(): |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 168 | # Gotta remove those pesky epoch numbers in the file names. Bazel doesn't |
| 169 | # like them. |
| 170 | regex = re.compile(".%3a") |
| 171 | contents = os.listdir(os.getcwd()) |
| 172 | for deb in contents: |
| 173 | new_name = regex.sub("", deb) |
| 174 | if new_name != deb: |
| 175 | os.rename(deb, new_name) |
| 176 | |
Philipp Schrader | 0e19c60 | 2018-03-07 21:07:22 -0800 | [diff] [blame] | 177 | |
| 178 | def sha256_checksum(filename, block_size=65536): |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 179 | sha256 = hashlib.sha256() |
| 180 | with open(filename, 'rb') as f: |
| 181 | for block in iter(lambda: f.read(block_size), b''): |
| 182 | sha256.update(block) |
| 183 | return sha256.hexdigest() |
| 184 | |
Philipp Schrader | 0e19c60 | 2018-03-07 21:07:22 -0800 | [diff] [blame] | 185 | |
| 186 | def print_file_list(): |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 187 | contents = os.listdir(os.getcwd()) |
| 188 | contents.sort() |
| 189 | print("_files = {") |
| 190 | for deb in contents: |
Stephan Pleines | 69a52cb | 2023-12-14 19:13:51 -0800 | [diff] [blame] | 191 | print(' "%s": "%s",' % (deb, sha256_checksum(deb))) |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 192 | print("}") |
| 193 | |
Philipp Schrader | 0e19c60 | 2018-03-07 21:07:22 -0800 | [diff] [blame] | 194 | |
Philipp Schrader | aedfc5c | 2018-03-10 19:32:30 -0800 | [diff] [blame] | 195 | _ALWAYS_EXCLUDE = [ |
Tyler Chatow | 60671d3 | 2020-02-26 19:49:30 -0800 | [diff] [blame] | 196 | "dbus-session-bus", |
Philipp Schrader | aedfc5c | 2018-03-10 19:32:30 -0800 | [diff] [blame] | 197 | "debconf", |
| 198 | "debconf-2.0", |
Tyler Chatow | 60671d3 | 2020-02-26 19:49:30 -0800 | [diff] [blame] | 199 | "default-dbus-session-bus", |
Philipp Schrader | aedfc5c | 2018-03-10 19:32:30 -0800 | [diff] [blame] | 200 | "dpkg", |
| 201 | "install-info", |
| 202 | "libc-dev", |
| 203 | "libc6", |
| 204 | "libc6-dev", |
| 205 | ] |
| 206 | |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 207 | |
Philipp Schrader | 0e19c60 | 2018-03-07 21:07:22 -0800 | [diff] [blame] | 208 | def main(argv): |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 209 | parser = argparse.ArgumentParser() |
| 210 | parser.add_argument("--exclude", |
| 211 | "-e", |
| 212 | type=str, |
| 213 | action="append", |
| 214 | help="A package to exclude from the list") |
| 215 | parser.add_argument( |
| 216 | "--force-include", |
| 217 | type=str, |
| 218 | action="append", |
| 219 | help= |
| 220 | "Force include this and its dependencies. Even if listed in excludes.") |
Philipp Schrader | fd5489f | 2022-09-17 17:31:09 -0700 | [diff] [blame] | 221 | parser.add_argument( |
| 222 | "--force-exclude", |
| 223 | type=str, |
| 224 | action="append", |
| 225 | help= |
| 226 | "Force exclude this and its dependencies. Even if listed via --force-include." |
| 227 | ) |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 228 | parser.add_argument("--arch", |
| 229 | type=str, |
| 230 | default="amd64", |
| 231 | help="Architecture to download files for.") |
James Kuszmaul | ae9c578 | 2022-04-07 11:28:42 -0700 | [diff] [blame^] | 232 | parser.add_argument("--distro", |
| 233 | type=str, |
| 234 | default="debian", |
| 235 | choices=_DISTROS.keys(), |
| 236 | help="Architecture to download files for.") |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 237 | parser.add_argument( |
| 238 | "--apt-dir", |
| 239 | type=str, |
| 240 | help=" ".join([ |
| 241 | "File to generate and store apt files in.", |
| 242 | "Helpful for saving time when downloading multiple groups of packages.", |
| 243 | "Some flags will be ignored in favor of the values used to create this folder, so be careful.", |
| 244 | ])) |
| 245 | parser.add_argument("--release", |
| 246 | type=str, |
James Kuszmaul | ae9c578 | 2022-04-07 11:28:42 -0700 | [diff] [blame^] | 247 | default="bookworm", |
| 248 | help="Debian/Ubuntu release to use.") |
| 249 | parser.add_argument("--apt-key", |
| 250 | type=str, |
| 251 | action="append", |
| 252 | default=[], |
| 253 | help="URL of an additional apt archive key to trust.") |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 254 | parser.add_argument("package", nargs="+", help="The packages to download.") |
| 255 | args = parser.parse_args(argv[1:]) |
| 256 | if args.apt_dir: |
| 257 | apt_dir = args.apt_dir |
| 258 | else: |
| 259 | apt_dir = tempfile.mkdtemp() |
| 260 | apt_args = ["-o", "Dir=" + apt_dir, "-o", "APT::Architecture=" + args.arch] |
| 261 | if not args.apt_dir: |
| 262 | print("Creating apt files in %s" % apt_dir) |
| 263 | initialize_apt(apt_dir, apt_args, args) |
| 264 | folder = tempfile.mkdtemp() |
| 265 | os.chdir(folder) |
| 266 | excludes = args.exclude or [] |
| 267 | # Exclude common packages that don't make sense to include in everything all |
| 268 | # the time. |
| 269 | excludes += _ALWAYS_EXCLUDE |
Philipp Schrader | fd5489f | 2022-09-17 17:31:09 -0700 | [diff] [blame] | 270 | download_deps(apt_args, args.package, excludes, args.force_include, |
| 271 | args.force_exclude) |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 272 | fixup_files() |
| 273 | print_file_list() |
| 274 | print("Your packages are all in %s" % folder) |
| 275 | |
Philipp Schrader | 0e19c60 | 2018-03-07 21:07:22 -0800 | [diff] [blame] | 276 | |
| 277 | if __name__ == "__main__": |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 278 | sys.exit(main(sys.argv)) |