blob: 82f01ced9b9f79b0f4013c7cad0a8f0d97bf7d4c [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
Philipp Schraderfd5489f2022-09-17 17:31:09 -0700108def download_deps(apt_args, packages, excludes, force_includes,
109 force_excludes):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700110 deps = get_all_deps(apt_args, packages)
111 exclude_deps = get_all_deps(apt_args, excludes)
Austin Schuhb7775c02023-06-02 18:12:02 -0700112 exclude_deps -= set({b'libssl1.1'})
Ravago Jones5127ccc2022-07-31 16:32:45 -0700113 deps -= exclude_deps
114 force_include_deps = get_all_deps(apt_args, force_includes)
115 deps |= force_include_deps
Philipp Schraderfd5489f2022-09-17 17:31:09 -0700116 force_exclude_deps = get_all_deps(apt_args, force_excludes)
117 deps -= force_exclude_deps
Ravago Jones5127ccc2022-07-31 16:32:45 -0700118 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 Schrader0e19c602018-03-07 21:07:22 -0800125
126def fixup_files():
Ravago Jones5127ccc2022-07-31 16:32:45 -0700127 # 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 Schrader0e19c602018-03-07 21:07:22 -0800136
137def sha256_checksum(filename, block_size=65536):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700138 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 Schrader0e19c602018-03-07 21:07:22 -0800144
145def print_file_list():
Ravago Jones5127ccc2022-07-31 16:32:45 -0700146 contents = os.listdir(os.getcwd())
147 contents.sort()
148 print("_files = {")
149 for deb in contents:
Stephan Pleines69a52cb2023-12-14 19:13:51 -0800150 print(' "%s": "%s",' % (deb, sha256_checksum(deb)))
Ravago Jones5127ccc2022-07-31 16:32:45 -0700151 print("}")
152
Philipp Schrader0e19c602018-03-07 21:07:22 -0800153
Philipp Schraderaedfc5c2018-03-10 19:32:30 -0800154_ALWAYS_EXCLUDE = [
Tyler Chatow60671d32020-02-26 19:49:30 -0800155 "dbus-session-bus",
Philipp Schraderaedfc5c2018-03-10 19:32:30 -0800156 "debconf",
157 "debconf-2.0",
Tyler Chatow60671d32020-02-26 19:49:30 -0800158 "default-dbus-session-bus",
Philipp Schraderaedfc5c2018-03-10 19:32:30 -0800159 "dpkg",
160 "install-info",
161 "libc-dev",
162 "libc6",
163 "libc6-dev",
164]
165
Ravago Jones5127ccc2022-07-31 16:32:45 -0700166
Philipp Schrader0e19c602018-03-07 21:07:22 -0800167def main(argv):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700168 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 Schraderfd5489f2022-09-17 17:31:09 -0700180 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 Jones5127ccc2022-07-31 16:32:45 -0700187 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 Schraderfd5489f2022-09-17 17:31:09 -0700228 download_deps(apt_args, args.package, excludes, args.force_include,
229 args.force_exclude)
Ravago Jones5127ccc2022-07-31 16:32:45 -0700230 fixup_files()
231 print_file_list()
232 print("Your packages are all in %s" % folder)
233
Philipp Schrader0e19c602018-03-07 21:07:22 -0800234
235if __name__ == "__main__":
Ravago Jones5127ccc2022-07-31 16:32:45 -0700236 sys.exit(main(sys.argv))