blob: 6d548b5a8b29e62d0690b6acc72674f1587d0edc [file] [log] [blame]
Philipp Schrader0e19c602018-03-07 21:07:22 -08001#!/usr/bin/env python3
2
3import sys
4import os
Brian Silverman4b90dd02022-01-08 18:16:22 -08005import os.path
Philipp Schrader0e19c602018-03-07 21:07:22 -08006import re
7import subprocess
8import tempfile
Brian Silverman4b90dd02022-01-08 18:16:22 -08009import urllib.request
Philipp Schrader0e19c602018-03-07 21:07:22 -080010import argparse
11import hashlib
12
Ravago Jones5127ccc2022-07-31 16:32:45 -070013
Brian Silverman4b90dd02022-01-08 18:16:22 -080014def initialize_apt(apt_dir, apt_args, args):
Ravago Jones5127ccc2022-07-31 16:32:45 -070015 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 Silverman4b90dd02022-01-08 18:16:22 -080026deb http://deb.debian.org/debian/ {release} main contrib non-free
27deb-src http://deb.debian.org/debian/ {release} main contrib non-free
28
29deb https://security.debian.org/debian-security {release}-security main contrib non-free
30deb-src https://security.debian.org/debian-security {release}-security main contrib non-free
31
32deb http://deb.debian.org/debian/ {release}-updates main contrib non-free
33deb-src http://deb.debian.org/debian/ {release}-updates main contrib non-free
34
35deb http://deb.debian.org/debian {release}-backports main contrib non-free
36deb-src http://deb.debian.org/debian {release}-backports main contrib non-free
37""".format(release=args.release))
Ravago Jones5127ccc2022-07-31 16:32:45 -070038 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 Silverman4b90dd02022-01-08 18:16:22 -080045
46def get_deps(apt_args, package):
Ravago Jones5127ccc2022-07-31 16:32:45 -070047 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 Schrader0e19c602018-03-07 21:07:22 -080054
Brian Silverman4b90dd02022-01-08 18:16:22 -080055def get_all_deps(apt_args, packages):
Ravago Jones5127ccc2022-07-31 16:32:45 -070056 deps = set()
57 for package in packages or ():
58 deps.update(get_deps(apt_args, package))
59 return deps
60
Philipp Schrader0e19c602018-03-07 21:07:22 -080061
Brian Silverman6470f442018-08-05 12:08:16 -070062def map_virtual_packages(packages):
Ravago Jones5127ccc2022-07-31 16:32:45 -070063 '''Maps known virtual packages to the preferred concrete packages which
Brian Silverman6470f442018-08-05 12:08:16 -070064 provide them.'''
Ravago Jones5127ccc2022-07-31 16:32:45 -070065 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 Silverman6470f442018-08-05 12:08:16 -0700107
Brian Silverman4b90dd02022-01-08 18:16:22 -0800108def download_deps(apt_args, packages, excludes, force_includes):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700109 deps = get_all_deps(apt_args, packages)
110 exclude_deps = get_all_deps(apt_args, excludes)
111 deps -= exclude_deps
112 force_include_deps = get_all_deps(apt_args, force_includes)
113 deps |= force_include_deps
114 env = dict(os.environ)
115 del env['LD_LIBRARY_PATH']
116 subprocess.check_call([b"apt-get"] + [a.encode('utf-8')
117 for a in apt_args] + [b"download"] +
118 list(map_virtual_packages(deps)),
119 env=env)
120
Philipp Schrader0e19c602018-03-07 21:07:22 -0800121
122def fixup_files():
Ravago Jones5127ccc2022-07-31 16:32:45 -0700123 # Gotta remove those pesky epoch numbers in the file names. Bazel doesn't
124 # like them.
125 regex = re.compile(".%3a")
126 contents = os.listdir(os.getcwd())
127 for deb in contents:
128 new_name = regex.sub("", deb)
129 if new_name != deb:
130 os.rename(deb, new_name)
131
Philipp Schrader0e19c602018-03-07 21:07:22 -0800132
133def sha256_checksum(filename, block_size=65536):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700134 sha256 = hashlib.sha256()
135 with open(filename, 'rb') as f:
136 for block in iter(lambda: f.read(block_size), b''):
137 sha256.update(block)
138 return sha256.hexdigest()
139
Philipp Schrader0e19c602018-03-07 21:07:22 -0800140
141def print_file_list():
Ravago Jones5127ccc2022-07-31 16:32:45 -0700142 contents = os.listdir(os.getcwd())
143 contents.sort()
144 print("_files = {")
145 for deb in contents:
146 print(' "%s": "%s",' % (deb, sha256_checksum(deb)))
147 print("}")
148
Philipp Schrader0e19c602018-03-07 21:07:22 -0800149
Philipp Schraderaedfc5c2018-03-10 19:32:30 -0800150_ALWAYS_EXCLUDE = [
Tyler Chatow60671d32020-02-26 19:49:30 -0800151 "dbus-session-bus",
Philipp Schraderaedfc5c2018-03-10 19:32:30 -0800152 "debconf",
153 "debconf-2.0",
Tyler Chatow60671d32020-02-26 19:49:30 -0800154 "default-dbus-session-bus",
Philipp Schraderaedfc5c2018-03-10 19:32:30 -0800155 "dpkg",
156 "install-info",
157 "libc-dev",
158 "libc6",
159 "libc6-dev",
160]
161
Ravago Jones5127ccc2022-07-31 16:32:45 -0700162
Philipp Schrader0e19c602018-03-07 21:07:22 -0800163def main(argv):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700164 parser = argparse.ArgumentParser()
165 parser.add_argument("--exclude",
166 "-e",
167 type=str,
168 action="append",
169 help="A package to exclude from the list")
170 parser.add_argument(
171 "--force-include",
172 type=str,
173 action="append",
174 help=
175 "Force include this and its dependencies. Even if listed in excludes.")
176 parser.add_argument("--arch",
177 type=str,
178 default="amd64",
179 help="Architecture to download files for.")
180 parser.add_argument(
181 "--apt-dir",
182 type=str,
183 help=" ".join([
184 "File to generate and store apt files in.",
185 "Helpful for saving time when downloading multiple groups of packages.",
186 "Some flags will be ignored in favor of the values used to create this folder, so be careful.",
187 ]))
188 parser.add_argument("--release",
189 type=str,
190 default="bullseye",
191 help="Debian release to use.")
192 parser.add_argument(
193 "--apt-key",
194 type=str,
195 action="append",
196 default=[
197 "https://ftp-master.debian.org/keys/archive-key-11.asc",
198 "https://ftp-master.debian.org/keys/archive-key-11-security.asc",
199 ],
200 help="URL of an additional apt archive key to trust.")
201 parser.add_argument("package", nargs="+", help="The packages to download.")
202 args = parser.parse_args(argv[1:])
203 if args.apt_dir:
204 apt_dir = args.apt_dir
205 else:
206 apt_dir = tempfile.mkdtemp()
207 apt_args = ["-o", "Dir=" + apt_dir, "-o", "APT::Architecture=" + args.arch]
208 if not args.apt_dir:
209 print("Creating apt files in %s" % apt_dir)
210 initialize_apt(apt_dir, apt_args, args)
211 folder = tempfile.mkdtemp()
212 os.chdir(folder)
213 excludes = args.exclude or []
214 # Exclude common packages that don't make sense to include in everything all
215 # the time.
216 excludes += _ALWAYS_EXCLUDE
217 download_deps(apt_args, args.package, excludes, args.force_include)
218 fixup_files()
219 print_file_list()
220 print("Your packages are all in %s" % folder)
221
Philipp Schrader0e19c602018-03-07 21:07:22 -0800222
223if __name__ == "__main__":
Ravago Jones5127ccc2022-07-31 16:32:45 -0700224 sys.exit(main(sys.argv))