blob: 1d780d027cf36c0ca5b176d28b3e7472b0b99a14 [file] [log] [blame]
Philipp Schrader0e19c602018-03-07 21:07:22 -08001#!/usr/bin/env python3
2
James Kuszmaulae9c5782022-04-07 11:28:42 -07003from collections import namedtuple
Philipp Schrader0e19c602018-03-07 21:07:22 -08004import sys
5import os
Brian Silverman4b90dd02022-01-08 18:16:22 -08006import os.path
Philipp Schrader0e19c602018-03-07 21:07:22 -08007import re
8import subprocess
9import tempfile
Brian Silverman4b90dd02022-01-08 18:16:22 -080010import urllib.request
Philipp Schrader0e19c602018-03-07 21:07:22 -080011import argparse
12import hashlib
13
James Kuszmaulae9c5782022-04-07 11:28:42 -070014DistroInfo = 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
21deb-src http://us.archive.ubuntu.com/ubuntu/ {release} main restricted
22
23deb http://security.ubuntu.com/ubuntu {release}-security main restricted
24deb-src http://security.ubuntu.com/ubuntu {release}-security main restricted
25
26deb http://us.archive.ubuntu.com/ubuntu/ {release} universe
27deb-src http://us.archive.ubuntu.com/ubuntu/ {release} universe
28deb http://us.archive.ubuntu.com/ubuntu/ {release}-updates universe
29deb-src http://us.archive.ubuntu.com/ubuntu/ {release}-updates universe
30
31deb http://us.archive.ubuntu.com/ubuntu/ {release}-updates main restricted
32deb-src http://us.archive.ubuntu.com/ubuntu/ {release}-updates main restricted
33
34deb http://us.archive.ubuntu.com/ubuntu {release}-backports main restricted
35deb-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
40deb-src http://deb.debian.org/debian/ {release} main contrib non-free
41
42deb https://security.debian.org/debian-security {release}-security main contrib non-free
43deb-src https://security.debian.org/debian-security {release}-security main contrib non-free
44
45deb http://deb.debian.org/debian/ {release}-updates main contrib non-free
46deb-src http://deb.debian.org/debian/ {release}-updates main contrib non-free
47
48deb http://deb.debian.org/debian {release}-backports main contrib non-free
49deb-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 Jones5127ccc2022-07-31 16:32:45 -070056
Brian Silverman4b90dd02022-01-08 18:16:22 -080057def initialize_apt(apt_dir, apt_args, args):
Ravago Jones5127ccc2022-07-31 16:32:45 -070058 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 Kuszmaulae9c5782022-04-07 11:28:42 -070067 distro = _DISTROS[args.distro]
Ravago Jones5127ccc2022-07-31 16:32:45 -070068 with open(os.path.join(apt_dir, 'etc', 'apt', 'sources.list'), 'w') as f:
James Kuszmaulae9c5782022-04-07 11:28:42 -070069 f.write(distro.sources_list.format(release=args.release))
70 for key in args.apt_key + distro.url_keys:
Ravago Jones5127ccc2022-07-31 16:32:45 -070071 basename = os.path.basename(key)
72 urllib.request.urlretrieve(
73 key, os.path.join(apt_dir, 'etc', 'apt', 'trusted.gpg.d',
74 basename))
James Kuszmaulae9c5782022-04-07 11:28:42 -070075 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 Jones5127ccc2022-07-31 16:32:45 -070082 subprocess.check_call(["apt-get"] + apt_args + ["update"])
83
Brian Silverman4b90dd02022-01-08 18:16:22 -080084
85def get_deps(apt_args, package):
Ravago Jones5127ccc2022-07-31 16:32:45 -070086 env = dict(os.environ)
James Kuszmaulae9c5782022-04-07 11:28:42 -070087 if 'LD_LIBRARY_PATH' in env:
88 del env['LD_LIBRARY_PATH']
Ravago Jones5127ccc2022-07-31 16:32:45 -070089 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 Schrader0e19c602018-03-07 21:07:22 -080094
Brian Silverman4b90dd02022-01-08 18:16:22 -080095def get_all_deps(apt_args, packages):
Ravago Jones5127ccc2022-07-31 16:32:45 -070096 deps = set()
97 for package in packages or ():
98 deps.update(get_deps(apt_args, package))
99 return deps
100
Philipp Schrader0e19c602018-03-07 21:07:22 -0800101
Brian Silverman6470f442018-08-05 12:08:16 -0700102def map_virtual_packages(packages):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700103 '''Maps known virtual packages to the preferred concrete packages which
Brian Silverman6470f442018-08-05 12:08:16 -0700104 provide them.'''
Ravago Jones5127ccc2022-07-31 16:32:45 -0700105 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 Silverman6470f442018-08-05 12:08:16 -0700147
Philipp Schraderfd5489f2022-09-17 17:31:09 -0700148def download_deps(apt_args, packages, excludes, force_includes,
149 force_excludes):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700150 deps = get_all_deps(apt_args, packages)
151 exclude_deps = get_all_deps(apt_args, excludes)
Austin Schuhb7775c02023-06-02 18:12:02 -0700152 exclude_deps -= set({b'libssl1.1'})
Ravago Jones5127ccc2022-07-31 16:32:45 -0700153 deps -= exclude_deps
154 force_include_deps = get_all_deps(apt_args, force_includes)
155 deps |= force_include_deps
Philipp Schraderfd5489f2022-09-17 17:31:09 -0700156 force_exclude_deps = get_all_deps(apt_args, force_excludes)
157 deps -= force_exclude_deps
Ravago Jones5127ccc2022-07-31 16:32:45 -0700158 env = dict(os.environ)
James Kuszmaulae9c5782022-04-07 11:28:42 -0700159 if 'LD_LIBRARY_PATH' in env:
160 del env['LD_LIBRARY_PATH']
Ravago Jones5127ccc2022-07-31 16:32:45 -0700161 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 Schrader0e19c602018-03-07 21:07:22 -0800166
167def fixup_files():
Ravago Jones5127ccc2022-07-31 16:32:45 -0700168 # 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 Schrader0e19c602018-03-07 21:07:22 -0800177
178def sha256_checksum(filename, block_size=65536):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700179 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 Schrader0e19c602018-03-07 21:07:22 -0800185
186def print_file_list():
Ravago Jones5127ccc2022-07-31 16:32:45 -0700187 contents = os.listdir(os.getcwd())
188 contents.sort()
189 print("_files = {")
190 for deb in contents:
Stephan Pleines69a52cb2023-12-14 19:13:51 -0800191 print(' "%s": "%s",' % (deb, sha256_checksum(deb)))
Ravago Jones5127ccc2022-07-31 16:32:45 -0700192 print("}")
193
Philipp Schrader0e19c602018-03-07 21:07:22 -0800194
Philipp Schraderaedfc5c2018-03-10 19:32:30 -0800195_ALWAYS_EXCLUDE = [
Tyler Chatow60671d32020-02-26 19:49:30 -0800196 "dbus-session-bus",
Philipp Schraderaedfc5c2018-03-10 19:32:30 -0800197 "debconf",
198 "debconf-2.0",
Tyler Chatow60671d32020-02-26 19:49:30 -0800199 "default-dbus-session-bus",
Philipp Schraderaedfc5c2018-03-10 19:32:30 -0800200 "dpkg",
201 "install-info",
202 "libc-dev",
203 "libc6",
204 "libc6-dev",
205]
206
Ravago Jones5127ccc2022-07-31 16:32:45 -0700207
Philipp Schrader0e19c602018-03-07 21:07:22 -0800208def main(argv):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700209 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 Schraderfd5489f2022-09-17 17:31:09 -0700221 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 Jones5127ccc2022-07-31 16:32:45 -0700228 parser.add_argument("--arch",
229 type=str,
230 default="amd64",
231 help="Architecture to download files for.")
James Kuszmaulae9c5782022-04-07 11:28:42 -0700232 parser.add_argument("--distro",
233 type=str,
234 default="debian",
235 choices=_DISTROS.keys(),
236 help="Architecture to download files for.")
Ravago Jones5127ccc2022-07-31 16:32:45 -0700237 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 Kuszmaulae9c5782022-04-07 11:28:42 -0700247 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 Jones5127ccc2022-07-31 16:32:45 -0700254 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 Schraderfd5489f2022-09-17 17:31:09 -0700270 download_deps(apt_args, args.package, excludes, args.force_include,
271 args.force_exclude)
Ravago Jones5127ccc2022-07-31 16:32:45 -0700272 fixup_files()
273 print_file_list()
274 print("Your packages are all in %s" % folder)
275
Philipp Schrader0e19c602018-03-07 21:07:22 -0800276
277if __name__ == "__main__":
Ravago Jones5127ccc2022-07-31 16:32:45 -0700278 sys.exit(main(sys.argv))