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