blob: e6dba6dbd7381dbe4d2749810bf15c605e0a6d27 [file] [log] [blame]
Austin Schuh51014832023-10-20 17:44:45 -07001#!/usr/bin/python3
2
Austin Schuhbffbe8b2023-11-22 21:32:05 -08003from __future__ import annotations
4import apt_pkg
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07005import sys
Austin Schuh51014832023-10-20 17:44:45 -07006import collections
Austin Schuhbffbe8b2023-11-22 21:32:05 -08007import contextlib
Austin Schuh86d980e2023-10-20 22:44:47 -07008import datetime
Austin Schuhbffbe8b2023-11-22 21:32:05 -08009import functools
10import jinja2
Austin Schuh51014832023-10-20 17:44:45 -070011import os
Austin Schuhbffbe8b2023-11-22 21:32:05 -080012import pathlib
Jim Ostrowskif9396de2024-01-22 15:13:21 -080013import platform
Austin Schuhbffbe8b2023-11-22 21:32:05 -080014import re
15import shlex
Austin Schuh86d980e2023-10-20 22:44:47 -070016import shutil
Austin Schuhbffbe8b2023-11-22 21:32:05 -080017import subprocess
Austin Schuh51014832023-10-20 17:44:45 -070018
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -070019# Name of debian image to be created/modified
Austin Schuh51014832023-10-20 17:44:45 -070020IMAGE = "arm64_bookworm_debian_yocto.img"
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -070021
22# Path to yocto build for the orin (using meta-frc971)
Austin Schuh51014832023-10-20 17:44:45 -070023YOCTO = "/home/austin/local/jetpack/robot-yocto/build"
24
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -070025REQUIRED_DEPS = ["debootstrap", "u-boot-tools", "xfsprogs"]
Austin Schuh51014832023-10-20 17:44:45 -070026
Austin Schuhbffbe8b2023-11-22 21:32:05 -080027apt_pkg.init_system()
28
Austin Schuh51014832023-10-20 17:44:45 -070029
30@contextlib.contextmanager
31def scoped_loopback(image):
32 """Mounts an image as a loop back device."""
33 result = subprocess.run(["sudo", "losetup", "--show", "-f", image],
34 check=True,
35 stdout=subprocess.PIPE)
36 device = result.stdout.decode('utf-8').strip()
Austin Schuhf5dbe2c2024-04-06 16:10:24 -070037 print("Mounted", image, "to", repr(device), file=sys.stderr)
Austin Schuh51014832023-10-20 17:44:45 -070038 try:
39 yield device
40 finally:
41 subprocess.run(["sudo", "losetup", "-d", device], check=True)
42
43
44@contextlib.contextmanager
45def scoped_mount(image):
46 """Mounts an image as a partition."""
47 partition = f"{image}.partition"
48 try:
49 os.mkdir(partition)
50 except FileExistsError:
51 pass
52
53 result = subprocess.run(["sudo", "mount", "-o", "loop", image, partition],
54 check=True)
55
56 try:
57 yield partition
58 finally:
59 subprocess.run(
60 ["sudo", "rm", f"{partition}/usr/bin/qemu-aarch64-static"])
61 subprocess.run(["sudo", "umount", partition], check=True)
62
63
Jim Ostrowskif9396de2024-01-22 15:13:21 -080064def check_buildifier():
65 """Checks if buildifier is in the path"""
66 result = subprocess.run(["which", "buildifier"], stdout=subprocess.PIPE)
67 if result.stdout.decode('utf-8') == "":
68 return False
69 else:
70 return True
71
72
Austin Schuh51014832023-10-20 17:44:45 -070073def check_required_deps(deps):
74 """Checks if the provided list of dependencies is installed."""
75 missing_deps = []
76 for dep in deps:
77 result = subprocess.run(["dpkg-query", "-W", "-f='${Status}'", dep],
78 check=True,
79 stdout=subprocess.PIPE)
80
81 if "install ok installed" not in result.stdout.decode('utf-8'):
82 missing_deps.append(dep)
83
84 if len(missing_deps) > 0:
Austin Schuhf5dbe2c2024-04-06 16:10:24 -070085 print("Missing dependencies, please install:", file=sys.stderr)
86 print("sudo apt-get install", " ".join(missing_deps), file=sys.stderr)
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -070087 exit()
Austin Schuh51014832023-10-20 17:44:45 -070088
89
90def make_image(image):
91 """Makes an image and creates an xfs filesystem on it."""
Austin Schuhf5dbe2c2024-04-06 16:10:24 -070092 print("--> Creating NEW image ", f"{image}", file=sys.stderr)
Austin Schuh51014832023-10-20 17:44:45 -070093 result = subprocess.run([
94 "dd", "if=/dev/zero", f"of={image}", "bs=1", "count=0",
95 "seek=8589934592"
96 ],
97 check=True)
98
99 with scoped_loopback(image) as loopback:
100 subprocess.run([
101 "sudo", "mkfs.xfs", "-d", "su=128k", "-d", "sw=1", "-L", "rootfs",
102 loopback
103 ],
104 check=True)
105
106
107def target_unescaped(cmd):
108 """Runs a command as root with bash -c cmd, ie without escaping."""
109 subprocess.run([
110 "sudo", "chroot", "--userspec=0:0", f"{PARTITION}",
111 "qemu-aarch64-static", "/bin/bash", "-c", cmd
112 ],
113 check=True)
114
115
116def target(cmd):
117 """Runs a command as root with escaping."""
118 target_unescaped(shlex.join([shlex.quote(c) for c in cmd]))
119
120
121def pi_target_unescaped(cmd):
122 """Runs a command as pi with bash -c cmd, ie without escaping."""
123 subprocess.run([
124 "sudo", "chroot", "--userspec=pi:pi", "--groups=pi", f"{PARTITION}",
125 "qemu-aarch64-static", "/bin/bash", "-c", cmd
126 ],
127 check=True)
128
129
130def pi_target(cmd):
131 """Runs a command as pi with escaping."""
132 pi_target_unescaped(shlex.join([shlex.quote(c) for c in cmd]))
133
134
135def copyfile(owner, permissions, file):
136 """Copies a file from contents/{file} with the provided owner and permissions."""
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700137 print("copyfile", owner, permissions, file, file=sys.stderr)
Austin Schuh51014832023-10-20 17:44:45 -0700138 subprocess.run(["sudo", "cp", f"contents/{file}", f"{PARTITION}/{file}"],
139 check=True)
140 subprocess.run(["sudo", "chmod", permissions, f"{PARTITION}/{file}"],
141 check=True)
142 target(["chown", owner, f"/{file}"])
143
144
145def target_mkdir(owner_group, permissions, folder):
146 """Creates a directory recursively with the provided permissions and ownership."""
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700147 print("target_mkdir", owner_group, permissions, folder, file=sys.stderr)
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -0700148 owner, group = owner_group.split(':')
Austin Schuh51014832023-10-20 17:44:45 -0700149 target(
150 ["install", "-d", "-m", permissions, "-o", owner, "-g", group, folder])
151
152
153def list_packages():
154 """Lists all installed packages.
155
156 Returns:
157 A dictionary with keys as packages, and values as versions.
158 """
159 result = subprocess.run([
160 "sudo", "chroot", "--userspec=0:0", f"{PARTITION}",
161 "qemu-aarch64-static", "/bin/bash", "-c",
162 "dpkg-query -W -f='${Package} ${Version}\n'"
163 ],
164 check=True,
165 stdout=subprocess.PIPE)
166
167 device = result.stdout.decode('utf-8').strip()
168
169 r = {}
170 for line in result.stdout.decode('utf-8').strip().split('\n'):
171 package, version = line.split(' ')
172 r[package] = version
173
174 return r
175
176
177def list_yocto_packages():
178 """Lists all packages in the Yocto folder.
179
180 Returns:
181 list of Package classes.
182 """
183 Package = collections.namedtuple(
184 'Package', ['path', 'name', 'version', 'architecture'])
185 result = []
186 pathlist = pathlib.Path(f"{YOCTO}/tmp/deploy/deb").glob('**/*.deb')
187 for path in pathlist:
188 # Strip off the path, .deb, and split on _ to parse the package info.
189 s = os.path.basename(str(path))[:-4].split('_')
190 result.append(Package(str(path), s[0], s[1], s[2]))
191
192 return result
193
194
195def install_packages(new_packages, existing_packages):
196 """Installs the provided yocto packages, if they are new."""
197 # To install the yocto packages, first copy them into a folder in /tmp, then install them, then clean the folder up.
198 target(["mkdir", "-p", "/tmp/yocto_packages"])
199 try:
200 to_install = []
201 for package in new_packages:
202 if package.name in existing_packages and existing_packages[
203 package.name] == package.version:
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700204 print('Skipping existing package: ',
205 package.name,
206 file=sys.stderr)
Austin Schuh51014832023-10-20 17:44:45 -0700207 continue
208
209 subprocess.run([
210 "sudo", "cp", package.path,
211 f"{PARTITION}/tmp/yocto_packages/{os.path.basename(package.path)}"
212 ],
213 check=True)
214 to_install.append(package)
215
216 if len(to_install) > 0:
217 target(["dpkg", "-i"] + [
218 f"/tmp/yocto_packages/{os.path.basename(package.path)}"
219 for package in to_install
220 ])
221
222 finally:
223 target(["rm", "-rf", "/tmp/yocto_packages"])
224
225
226def install_virtual_packages(virtual_packages):
227 """Builds and installs the provided virtual packages."""
228 try:
229 target(["mkdir", "-p", "/tmp/yocto_packages"])
230 for virtual_package in virtual_packages:
231 subprocess.run(
232 ["dpkg-deb", "--build", f"virtual_packages/{virtual_package}"],
233 check=True)
234 subprocess.run([
235 "sudo", "cp", f"virtual_packages/{virtual_package}.deb",
236 f"{PARTITION}/tmp/yocto_packages/{virtual_package}.deb"
237 ],
238 check=True)
239
240 target(["dpkg", "-i"] + [
241 f"/tmp/yocto_packages/{package}.deb"
242 for package in virtual_packages
243 ])
244
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800245 for virtual_package in virtual_packages:
246 subprocess.run(["rm", f"virtual_packages/{virtual_package}.deb"],
247 check=True)
248
Austin Schuh51014832023-10-20 17:44:45 -0700249 finally:
250 target(["rm", "-rf", "/tmp/yocto_packages"])
251
252
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800253class NameVersion:
254 """ Class representing a package name and optionally a version constraint. """
255
256 def __init__(self, nameversion: str):
257 # We are processing package names here like:
258 # python3:any
259 # python3-markdown (= 3.4.1-2)
260 if '(' in nameversion:
261 s = nameversion.split(' (')
262 self.name = s[0].strip()
263
264 v = s[1][:-1].split(' ')
265
266 self.operator = v[0]
267 self.version = v[1]
268 else:
269 self.name = nameversion.strip()
270 self.operator = None
271 self.version = None
272
273 # Rip off :amd64 or :aarch64 from the name if it is here.
274 if ':' in self.name:
275 self.name = self.name.split(':')[0]
276
277 def matches(self, other: NameVersion) -> bool:
278 """If self meets the requirements defined by other."""
279 if other.name != self.name:
280 return False
281
282 if other.operator is None:
283 return True
284
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700285 # libz1 is special and doesn't have a version... Don't stress it for now until we learn why.
286 if self.operator is None and self.name == 'libz1':
287 return True
288
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800289 vc = apt_pkg.version_compare(self.version, other.version)
290 if vc < 0:
291 return other.operator in ('<=', '<<')
292 elif vc == 0:
293 return other.operator in ('=', '>=', '<=')
294 elif vc > 0:
295 return other.operator in ('>=', '>>')
296
297 def __repr__(self) -> str:
298 if self.operator is not None:
299 return f"NameVersion({self.name} ({self.operator} {self.version}))"
300 else:
301 return f"NameVersion({self.name})"
302
303
304class Package:
305
306 def __init__(self, name: str, provides: str, version: str, depends: str,
307 files: list[str]):
308 self.name = NameVersion(f"{name} (= {version})")
309
310 self.provides = [self.name]
311
312 if provides:
313 for package_and_version in provides.split(","):
314 self.provides.append(NameVersion(package_and_version))
315
316 self.depends = []
317 if depends:
318 for package_and_version in depends.split(", "):
319 if ' | ' in package_and_version:
320 oneof = []
321 for oneof_package_and_version in package_and_version.split(
322 ' | '):
323 oneof.append(NameVersion(oneof_package_and_version))
324 self.depends.append(oneof)
325 else:
326 self.depends.append([NameVersion(package_and_version)])
327
328 self.files = files
329
330 def update_filetypes(self, directories: set[str], symlinks: dict[str,
331 str]):
332 if hasattr(self, 'directories') or hasattr(self, 'symlinks'):
333 return
334
335 self.directories = []
336 self.symlinks = dict()
337 files = []
338 for f in self.files:
339 if f in directories:
340 self.directories.append(f)
341 elif f in symlinks:
342 self.symlinks[f] = symlinks[f]
343 else:
344 files.append(f)
345
346 self.files = files
347
348 def matches(self, other: NameVersion) -> bool:
349 """If self meets the requirements defined by other."""
350 return any(p.matches(other) for p in self.provides)
351
352 def resolved_depends(self, packages: dict[Package]) -> list[Package]:
353 result = set()
354
355 # The dependencies are lists of lists of dependencies. At least one
356 # element from each inner list needs to match for it to be valid. Most
357 # of the dependencies are going to be a single element list.
358 for p_or_list in self.depends:
359 resolved_set = set()
360 for oneof_package in p_or_list:
361 if oneof_package.name not in packages:
362 continue
363
364 resolved_oneof_package = packages[oneof_package.name]
365 if resolved_oneof_package.matches(oneof_package):
366 resolved_set.add(resolved_oneof_package)
367
368 if len(resolved_set) == 0:
369 raise RuntimeError(
370 f"Failed to find dependencies for {p_or_list}: {repr(self)}"
371 )
372
373 result.update(resolved_set)
374
375 return sorted(list(result), key=lambda x: x.name.name)
376
377 def headers(self) -> list[str]:
378 return [h for h in self.files if h.startswith('/usr/include')]
379
380 def objects(self) -> list[str]:
381 result = []
382 for file in self.files:
383 if not file.startswith('/usr'):
384 continue
385
386 # Gotta love GDB extensions ...libc.so....py. Ignore them.
387 if file.endswith('.py'):
388 continue
389
390 # We want to find things like libfoo.so.1.2.3.4.5. The .so needs to be last.
391 opath = file
392 found_so = False
393 while True:
394 opath, ext = os.path.splitext(opath)
395 if ext == '':
396 break
397 elif ext == '.so':
398 found_so = True
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800399
400 if found_so:
401 result.append(file)
402
403 return sorted(result)
404
405 def __repr__(self) -> str:
406 return f"{{ {repr(self.provides[0])}, \"provides\": {repr(self.provides[1:])}, \"depends\": {repr(self.depends)} }}"
407
408
409class PkgConfig:
410
411 def __init__(self, contents, package):
412 # The pkgconfig file format lets you specify variables and the expand
413 # them into the various fields. These are in the form
414 # asdf=15234
415 self.variables = dict()
416
417 self.package = package
418 self.libs = []
419 self.cflags = []
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700420 self.requires = []
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800421 for line in contents.split('\n'):
422 line = line.strip()
423 # Parse everything so we learn if a new field shows up we don't
424 # know how to parse.
425 if line == '':
426 pass
427 elif line[0] == '#':
428 pass
429 elif line.startswith('Name:'):
430 self.name = self.expand(line.removeprefix('Name:').strip())
431 elif line.startswith('Description:'):
432 self.description = self.expand(
433 line.removeprefix('Description:').strip())
434 elif line.startswith('Version:'):
435 self.version = self.expand(
436 line.removeprefix('Version:').strip())
437 elif line.startswith('Libs:'):
438 self.libs = self.expand(
439 line.removeprefix('Libs:').strip()).split()
440 elif line.startswith('Cflags:'):
441 self.cflags = self.expand(
442 line.removeprefix('Cflags:').strip()).split()
443 elif line.startswith('URL:'):
444 pass
445 elif line.startswith('Cflags.private:'):
446 pass
447 elif line.startswith('Requires:'):
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700448 # Parse a Requires line of the form:
449 # Requires: glib-2.0 >= 2.56.0, gobject-2.0
450 self.requires += [
451 f.split()[0] for f in self.expand(
452 line.removeprefix('Requires:').strip()).split(',') if f
453 ]
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800454 elif line.startswith('Requires.private:'):
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700455 # Parse a Requires.private line of the form:
456 # Requires.private: gmodule-2.0
457 self.requires += [
458 f.split()[0] for f in self.expand(
459 line.removeprefix('Requires.private:').strip()).split(
460 ',') if f
461 ]
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800462 elif line.startswith('Libs.private:'):
463 pass
464 elif line.startswith('Conflicts:'):
465 pass
466 elif re.match('^[-a-zA-Z_0-9]* *=.*$', line):
467 split_line = re.split(' *= *', line)
468 self.variables[split_line[0]] = self.expand(split_line[1])
469 else:
470 raise ValueError('Unknown line in pkgconfig file')
471
472 if self.name is None:
473 raise RuntimeError("Failed to find Name.")
474
475 def expand(self, line: str) -> str:
476 """ Expands a string with variable expansions in it like bash (${foo}). """
477 for var in self.variables:
478 line = line.replace('${' + var + '}', self.variables[var])
479 return line
480
481
482class Filesystem:
483
484 def __init__(self, partition):
485 self.partition = partition
486 # TODO(austin): I really want to be able to run this on an amd64
487 # filesystem too, which won't work with qemu-aarch64-static. Pull it
488 # into a library.
489 result = subprocess.run([
490 "sudo", "chroot", "--userspec=0:0", f"{self.partition}",
491 "qemu-aarch64-static", "/bin/bash", "-c",
492 "dpkg-query -W -f='Version: ${Version}\nPackage: ${Package}\nProvides: ${Provides}\nDepends: ${Depends}\n${db-fsys:Files}--\n'"
493 ],
494 check=True,
495 stdout=subprocess.PIPE)
496
497 # Mapping from all package names (str) to their corresponding Package
498 # objects for that package.
499 self.packages = dict()
500
501 package_in_progress = {'files': []}
502 files = set()
503 for line in result.stdout.decode('utf-8').strip().split('\n'):
504 if line == '--':
505 # We found the end of line deliminator, save the package and
506 # clear everything out.
507 new_package = Package(package_in_progress['Package'],
508 package_in_progress['Provides'],
509 package_in_progress['Version'],
510 package_in_progress['Depends'],
511 package_in_progress['files'])
512
513 for provides in new_package.provides:
514 self.packages[provides.name] = new_package
515
516 # Wipe everything so we detect if any fields are missing.
517 package_in_progress = {'files': []}
518 elif line.startswith("Version: "):
519 package_in_progress['Version'] = line.removeprefix("Version: ")
520 elif line.startswith("Package: "):
521 package_in_progress['Package'] = line.removeprefix("Package: ")
522 elif line.startswith("Provides: "):
523 package_in_progress['Provides'] = line.removeprefix(
524 "Provides: ")
525 elif line.startswith("Depends: "):
526 package_in_progress['Depends'] = line.removeprefix("Depends: ")
527 else:
528 assert (line.startswith(' '))
529 f = line.removeprefix(' ')
530 package_in_progress['files'].append(f)
531 files.add(f)
532
533 self.directories = set()
534 self.symlinks = dict()
535
536 for root, walked_dirs, walked_files in os.walk(self.partition):
537 for entry in walked_files + walked_dirs:
538 full_target = os.path.join(root, entry)
539 if pathlib.Path(full_target).is_symlink():
540 target = full_target.removeprefix(self.partition)
541 self.symlinks[target] = os.readlink(full_target)
542
543 for file in files:
544 full_target = f"{self.partition}/{file}"
545 try:
546 if pathlib.Path(full_target).is_symlink():
547 self.symlinks[file] = os.readlink(full_target)
548
549 if pathlib.Path(full_target).is_dir():
550 self.directories.add(file)
551 except PermissionError:
552 # Assume it is a file...
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700553 print("Failed to read", file, file=sys.stderr)
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800554 pass
555
556 # Directories are all the things before the last /
557 for parent in pathlib.Path(file).parents:
558 self.directories.add(parent)
559
560 # Now, populate self.files with a mapping from each file to the owning
561 # package so we can do file ownership lookups.
562 visited = set()
563 self.files = dict()
564 for package in self.packages.values():
565 if package in visited:
566 continue
567 visited.add(package)
568
569 for f in package.files:
570 if f in self.directories:
571 continue
572
573 if f in self.files:
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700574 print("Duplicate file",
575 repr(f),
576 ' current',
577 package,
578 ' already',
579 self.files[f],
580 file=sys.stderr)
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800581 if not f.startswith('/usr/share'):
582 assert (f not in self.files)
583 self.files[f] = package
584
585 # For each package, update the file list to track dependencies and symlinks correctly.
586 for p in self.packages.values():
587 p.update_filetypes(self.directories, self.symlinks)
588
589 # Print out all the libraries and where they live as known to ldconfig
590 result = subprocess.run(
Jim Ostrowskif9396de2024-01-22 15:13:21 -0800591 [
592 '/usr/sbin/ldconfig', '-C',
593 f'{self.partition}/etc/ld.so.cache', '-p'
594 ],
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800595 check=True,
596 stdout=subprocess.PIPE,
597 )
598
599 self.ldconfig_cache = dict()
600 for line in result.stdout.decode('utf-8').split('\n'):
601 if line.startswith('\t'):
602 split_line = re.split(' \\(libc6,(AArch64|x86-64)\\) => ',
603 line.strip())
604 self.ldconfig_cache[split_line[0]] = split_line[2]
605
606 self.pkgcfg = dict()
607 for pkgconfig in [
608 '/usr/local/lib/aarch64-linux-gnu/pkgconfig',
609 '/usr/local/lib/pkgconfig',
610 '/usr/local/share/pkgconfig',
611 '/usr/lib/aarch64-linux-gnu/pkgconfig',
612 '/usr/lib/pkgconfig',
613 '/usr/share/pkgconfig',
614 ]:
615 candidate_folder = f"{self.partition}/{pkgconfig}"
616 if not os.path.exists(candidate_folder):
617 continue
618
619 for f in os.listdir(candidate_folder):
620 full_filename = f"{candidate_folder}/{f}"
621 if pathlib.Path(full_filename).is_dir():
622 continue
623 if not f.endswith('.pc'):
624 continue
625
626 package_name = f.removesuffix('.pc')
627
628 with open(f"{candidate_folder}/{f}", "r") as file:
629 self.pkgcfg[package_name] = PkgConfig(
630 file.read(), self.files[f'{pkgconfig}/{f}'])
631
632 def resolve_symlink(self, path: str) -> str:
633 """ Implements symlink resolution using self.symlinks. """
634 # Only need to support absolute links since we don't have a concept of cwd.
635
636 # Implements the symlink algorithm in
637 # https://android.googlesource.com/platform/bionic.git/+/android-4.0.1_r1/libc/bionic/realpath.c
638 assert (path[0] == '/')
639
640 left = path.split('/')[1:]
641
642 if len(path) == 0:
643 return path
644
645 resolved = ['']
646
647 while len(left) > 0:
648 if left[0] == '.':
649 left = left[1:]
650 elif left[0] == '..':
651 assert (len(resolved) >= 1)
652 resolved = resolved[:-1]
653 left = left[1:]
654 else:
655 resolved.append(left[0])
656 merged = '/'.join(resolved)
657 if merged in self.symlinks:
658 symlink = self.symlinks[merged]
659 # Absolute symlink, blow away the previously accumulated path
660 if symlink[0] == '/':
661 resolved = ['']
662 left = symlink[1:].split('/') + left[1:]
663 else:
664 # Relative symlink, replace the symlink name in the path with the newly found target.
665 resolved = resolved[:-1]
666 left = symlink.split('/') + left[1:]
667 else:
668 left = left[1:]
669
670 return '/'.join(resolved)
671
672 def exists(self, path: str) -> bool:
673 if path in self.files or path in self.symlinks or path in self.directories:
674 return True
675 return False
676
677 def resolve_object(self,
678 obj: str,
679 requesting_obj: str | None = None) -> str:
680 if obj in self.ldconfig_cache:
681 return self.resolve_symlink(self.ldconfig_cache[obj])
682 elif requesting_obj is not None:
683 to_search = os.path.join(os.path.split(requesting_obj)[0], obj)
684 if self.exists(to_search):
685 return self.resolve_symlink(to_search)
686
687 raise FileNotFoundError(obj)
688
689 @functools.cache
690 def object_dependencies(self, obj: str) -> str:
691 result = subprocess.run(
692 ['objdump', '-p', f'{self.partition}/{obj}'],
693 check=True,
694 stdout=subprocess.PIPE,
695 )
696
697 # Part of the example output. We only want NEEDED from the dynamic section.
698 #
699 # RELRO off 0x0000000000128af0 vaddr 0x0000000000128af0 paddr 0x0000000000128af0 align 2**0
700 # filesz 0x0000000000003510 memsz 0x0000000000003510 flags r--
701 #
702 # Dynamic Section:
703 # NEEDED libtinfo.so.6
704 # NEEDED libc.so.6
705 # INIT 0x000000000002f000
706 # FINI 0x00000000000efb94
707
708 deps = []
709 for line in result.stdout.decode('utf-8').split('\n'):
710 if 'NEEDED' in line:
711 deps.append(line.strip().split()[1])
712
713 return deps
714
715
716def generate_build_file(partition):
717 filesystem = Filesystem(partition)
718
719 packages_to_eval = [
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700720 filesystem.packages['libglib2.0-dev'],
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800721 filesystem.packages['libopencv-dev'],
722 filesystem.packages['libc6-dev'],
723 filesystem.packages['libstdc++-12-dev'],
724 filesystem.packages['libnpp-11-8-dev'],
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700725 filesystem.packages['gstreamer1.0-dev'],
726 filesystem.packages['orc-dev'],
727 filesystem.packages['libgstrtp-1.0-0'],
728 filesystem.packages['gstreamer1.0-plugins-bad-dev'],
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800729 ]
730
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700731 # Now, we want to figure out what the dependencies of each of the packages are.
732 # Generate the dependency tree starting from an initial list of packages.
733 # Then, figure out how to link the .so's in.
734
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800735 # Recursively walk the tree using dijkstra's algorithm to generate targets
736 # for each set of headers.
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700737 print('Walking tree for', [p.name.name for p in packages_to_eval],
738 file=sys.stderr)
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800739
740 rules = []
741 objs_to_eval = []
742
743 # Set of packages already generated in case our graph hits a package
744 # multiple times.
745 packages_visited_set = set()
746 while packages_to_eval:
747 next_package = packages_to_eval.pop()
748 if next_package in packages_visited_set:
749 continue
750 packages_visited_set.add(next_package)
751
752 hdrs = next_package.headers()
753 objects = next_package.objects()
754
755 deps = []
756 for p in next_package.resolved_depends(filesystem.packages):
757 if p not in packages_visited_set:
758 packages_to_eval.append(p)
759
760 # These two form a circular dependency...
761 # Don't add them since libc6 has no headers in it.
762 if next_package.name.name == 'libgcc-s1' and p.name.name == 'libc6':
763 continue
764
765 deps.append(p.name.name)
766
767 if objects:
768 objs_to_eval += objects
769
770 hdrs.sort()
771 deps.sort()
772 hdrs = [f' "{h[1:]}",\n' for h in hdrs]
773 hdrs_files = ''.join(hdrs)
774 deps_joined = ''.join([f' ":{d}-headers",\n' for d in deps])
775
776 filegroup_srcs = ''.join(
777 [f' "{f[1:]}",\n' for f in next_package.files] +
778 [f' ":{d}-filegroup",\n' for d in deps])
779
780 rules.append(
781 f'filegroup(\n name = "{next_package.name.name}-filegroup",\n srcs = [\n{filegroup_srcs} ],\n)'
782 )
783 rules.append(
784 f'cc_library(\n name = "{next_package.name.name}-headers",\n hdrs = [\n{hdrs_files} ],\n visibility = ["//visibility:public"],\n deps = [\n{deps_joined} ],\n)'
785 )
786
787 skip_set = set()
788 # These two are linker scripts. Since they are soooo deep in the
789 # hierarchy, let's not stress parsing them correctly.
790 skip_set.add('/usr/lib/aarch64-linux-gnu/libc.so')
791 skip_set.add('/usr/lib/gcc/aarch64-linux-gnu/12/libgcc_s.so')
792
793 obj_set = set()
794 obj_set.update(skip_set)
795
796 while objs_to_eval:
797 obj = objs_to_eval.pop()
798 if obj in obj_set:
799 continue
800 obj_set.add(obj)
801
802 deps = filesystem.object_dependencies(obj)
803 resolved_deps = []
804 for d in deps:
805 resolved_obj = filesystem.resolve_object(d, requesting_obj=obj)
806 resolved_deps.append(resolved_obj)
807 if resolved_obj not in obj_set:
808 objs_to_eval.append(resolved_obj)
809
810 resolved_deps.sort()
811 rule_name = obj[1:].replace('/', '_')
812 rule_deps = ''.join([
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700813 ' ":{}-lib",\n'.format(d[1:].replace('/', '_'))
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800814 for d in resolved_deps if d not in skip_set
815 ])
816 rules.append(
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700817 f'cc_library(\n name = "{rule_name}-lib",\n srcs = ["{obj[1:]}"],\n deps = [\n{rule_deps} ],\n)'
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800818 )
819
820 standard_includes = set()
821 standard_includes.add('/usr/include')
822 standard_includes.add('/usr/include/aarch64-linux-gnu')
823 standard_includes.add('/usr/include/x86-64-linux-gnu')
824 for pkg in filesystem.pkgcfg:
825 try:
826 contents = filesystem.pkgcfg[pkg]
827 resolved_libraries = [
828 filesystem.resolve_object('lib' + f.removeprefix('-l') + '.so')
829 for f in contents.libs if f.startswith('-l')
830 ]
831
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800832 includes = []
833 for flag in contents.cflags:
834 if flag.startswith('-I/') and flag.removeprefix(
835 '-I') not in standard_includes:
836 includes.append(flag.removeprefix('-I/'))
837
838 rule_deps = ''.join(
839 sorted([
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700840 ' ":' + l[1:].replace('/', '_') + '-lib",\n'
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800841 for l in resolved_libraries
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700842 ] + [f' ":{contents.package.name.name}-headers",\n'] +
843 [f' ":{dep}",\n' for dep in contents.requires]))
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800844 includes.sort()
845 if len(includes) > 0:
846 includes_string = ' includes = ["' + '", "'.join(
847 includes) + '"],\n'
848 else:
849 includes_string = ''
850 rules.append(
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700851 f'# pkgconf -> {pkg}\ncc_library(\n name = "{pkg}",\n{includes_string} visibility = ["//visibility:public"],\n deps = [\n{rule_deps} ],\n)'
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800852 )
853 # Look up which package this is from to include the headers
854 # Depend on all the libraries
855 # Parse -I -> includes
856 except FileNotFoundError:
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700857 print('Failed to instantiate package', repr(pkg), file=sys.stderr)
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800858 pass
859
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800860 with open("orin_debian_rootfs.BUILD.template", "r") as file:
861 template = jinja2.Template(file.read())
862
863 substitutions = {
864 "SYSROOT_SRCS": """glob(
865 include = [
866 "include/**",
867 "lib/**",
868 "lib64/**",
869 "usr/include/**",
870 "usr/local/**",
871 "usr/lib/**",
872 "usr/lib64/**",
873 ],
874 exclude = [
875 "usr/share/**",
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800876 ],
877 )""",
878 "RULES": '\n\n'.join(rules),
879 }
880
881 with open("../../compilers/orin_debian_rootfs.BUILD", "w") as file:
882 file.write(template.render(substitutions))
883
Austin Schuh8de7def2024-01-01 12:47:02 -0800884 subprocess.run(['buildifier', "../../compilers/orin_debian_rootfs.BUILD"])
885
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800886
887def do_package(partition):
888 tarball = datetime.date.today().strftime(
889 f"{os.getcwd()}/%Y-%m-%d-bookworm-arm64-nvidia-rootfs.tar")
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700890 print(tarball, file=sys.stderr)
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800891
892 subprocess.run([
893 "sudo",
894 "tar",
895 "--sort=name",
896 "--mtime=0",
897 "--owner=0",
898 "--group=0",
899 "--numeric-owner",
900 "--exclude=./usr/share/ca-certificates",
901 "--exclude=./home",
902 "--exclude=./root",
903 "--exclude=./usr/src",
904 "--exclude=./usr/lib/mesa-diverted",
905 "--exclude=./usr/bin/X11",
906 "--exclude=./usr/lib/systemd/system/system-systemd*cryptsetup.slice",
907 "--exclude=./dev",
908 "--exclude=./usr/local/cuda-11.8/bin/fatbinary",
909 "--exclude=./usr/local/cuda-11.8/bin/ptxas",
910 "--exclude=./usr/local/cuda-11.8/include/thrust",
911 "--exclude=./usr/local/cuda-11.8/include/nv",
912 "--exclude=./usr/local/cuda-11.8/include/cuda",
913 "--exclude=./usr/local/cuda-11.8/include/cub",
Austin Schuh1fc0d482023-12-24 14:40:34 -0800914 "--exclude=./usr/include/cub",
915 "--exclude=./usr/include/nv",
916 "--exclude=./usr/include/thrust",
917 "--exclude=./usr/include/cuda",
Austin Schuhbffbe8b2023-11-22 21:32:05 -0800918 "--exclude=./usr/share",
919 "-cf",
920 tarball,
921 ".",
922 ],
923 cwd=partition,
924 check=True)
925
926 # Pack ptxas and fatbinary into the spots that clang expect them to make compiling easy.
927 nvidia_cuda_toolkit_path = 'nvidia-cuda-toolkit'
928 if not os.path.exists(nvidia_cuda_toolkit_path):
929 os.mkdir(nvidia_cuda_toolkit_path)
930
931 subprocess.run(['apt-get', 'download', 'nvidia-cuda-toolkit'],
932 cwd=nvidia_cuda_toolkit_path,
933 check=True)
934
935 subprocess.run(
936 ['dpkg', '-x',
937 os.listdir(nvidia_cuda_toolkit_path)[0], '.'],
938 cwd=nvidia_cuda_toolkit_path,
939 check=True)
940
941 subprocess.run([
942 "sudo", "tar", "--sort=name", "--mtime=0", "--owner=0", "--group=0",
943 "--numeric-owner",
944 '--transform=s|usr/bin/ptxas|usr/local/cuda-11.8/bin/ptxas|',
945 '--transform=s|usr/bin/fatbinary|usr/local/cuda-11.8/bin/aarch64-unknown-linux-gnu-fatbinary|',
946 "--append", "-f", tarball, "usr/bin/fatbinary", "usr/bin/ptxas"
947 ],
948 cwd=nvidia_cuda_toolkit_path,
949 check=True)
950
951 subprocess.run(["sha256sum", tarball], check=True)
952
953
Jim Ostrowskif9396de2024-01-22 15:13:21 -0800954def mount_and_bash():
955 """Helper function to just mount and open a bash interface
956 To run from the CLI, call
957 python3 -c "from build_rootfs import *; mount_and_bash()"
958 """
959 with scoped_mount(IMAGE) as partition:
960 subprocess.run([
961 "sudo", "cp", "/usr/bin/qemu-aarch64-static",
962 f"{partition}/usr/bin/"
963 ],
964 check=True)
965
966 global PARTITION
967 PARTITION = partition
968 target(["/bin/bash"])
969
970
Austin Schuh51014832023-10-20 17:44:45 -0700971def main():
972 check_required_deps(REQUIRED_DEPS)
973
Jim Ostrowskif9396de2024-01-22 15:13:21 -0800974 if not os.path.exists(YOCTO):
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700975 print("ERROR: Must have YOCTO directory properly specified to run",
976 file=sys.stderr)
977 print("See https://github.com/frc971/meta-frc971/tree/main for info",
978 file=sys.stderr)
Jim Ostrowskif9396de2024-01-22 15:13:21 -0800979 exit()
980
981 if not check_buildifier():
982 print(
Austin Schuhf5dbe2c2024-04-06 16:10:24 -0700983 "ERROR: Need to have buildifier in the path. Please resolve this.",
984 file=sys.stderr)
Jim Ostrowskif9396de2024-01-22 15:13:21 -0800985 exit()
986
Austin Schuh51014832023-10-20 17:44:45 -0700987 new_image = not os.path.exists(IMAGE)
988 if new_image:
989 make_image(IMAGE)
990
991 with scoped_mount(IMAGE) as partition:
992 if new_image:
993 subprocess.run([
994 "sudo", "debootstrap", "--arch=arm64", "--no-check-gpg",
995 "--foreign", "bookworm", partition,
996 "http://deb.debian.org/debian/"
997 ],
998 check=True)
999
1000 subprocess.run([
1001 "sudo", "cp", "/usr/bin/qemu-aarch64-static",
1002 f"{partition}/usr/bin/"
1003 ],
1004 check=True)
1005
1006 global PARTITION
1007 PARTITION = partition
1008
1009 if new_image:
1010 target(["/debootstrap/debootstrap", "--second-stage"])
1011
Jim Ostrowskif9396de2024-01-22 15:13:21 -08001012 # Do this unescaped; otherwise, we get quotes in the password
1013 target_unescaped(
1014 "useradd -m -p \"\$y\$j9T\$85lzhdky63CTj.two7Zj20\$pVY53UR0VebErMlm8peyrEjmxeiRw/rfXfx..9.xet1\" -s /bin/bash pi"
1015 )
Austin Schuh51014832023-10-20 17:44:45 -07001016 target(["addgroup", "debug"])
1017 target(["addgroup", "crypto"])
1018 target(["addgroup", "trusty"])
1019
1020 if not os.path.exists(
1021 f"{partition}/etc/apt/sources.list.d/bullseye-backports.list"):
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -07001022 copyfile("root:root", "644",
Austin Schuh51014832023-10-20 17:44:45 -07001023 "etc/apt/sources.list.d/bullseye-backports.list")
1024 target(["apt-get", "update"])
1025
Jim Ostrowskif9396de2024-01-22 15:13:21 -08001026 # This is useful in recovering from a partial build, and shouldn't break
1027 # anything otherwise
1028 target(["apt", "--fix-broken", "install"])
1029
Austin Schuh51014832023-10-20 17:44:45 -07001030 target([
1031 "apt-get", "-y", "install", "gnupg", "wget", "systemd",
1032 "systemd-resolved", "locales"
1033 ])
1034
1035 target(["localedef", "-i", "en_US", "-f", "UTF-8", "en_US.UTF-8"])
1036
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -07001037 target_mkdir("root:root", "755", "run/systemd")
1038 target_mkdir("systemd-resolve:systemd-resolve", "755",
Austin Schuh51014832023-10-20 17:44:45 -07001039 "run/systemd/resolve")
Jim Ostrowskif9396de2024-01-22 15:13:21 -08001040
1041 # Need to use the machine's local resolv.conf when running this
1042 # We put a generic file in place at the end when we're done
1043 subprocess.run([
1044 "sudo", "cp", f"/etc/resolv.conf",
1045 f"{PARTITION}/run/systemd/resolve/stub-resolv.conf"
1046 ],
1047 check=True)
1048 subprocess.run([
1049 "sudo", "chmod", "644",
1050 f"{PARTITION}/run/systemd/resolve/stub-resolv.conf"
1051 ],
1052 check=True)
1053 target([
1054 "chown", "systemd-resolve:systemd-resolve",
1055 f"/run/systemd/resolve/stub-resolv.conf"
1056 ])
Austin Schuh51014832023-10-20 17:44:45 -07001057 target(["systemctl", "enable", "systemd-resolved"])
1058
1059 target([
1060 "apt-get", "-y", "install", "bpfcc-tools", "sudo",
Austin Schuhcf1dda52024-01-01 20:19:21 -08001061 "openssh-server", "python3", "bash-completion", "git",
Austin Schuh51014832023-10-20 17:44:45 -07001062 "cpufrequtils", "pmount", "rsync", "vim-nox", "chrony",
Austin Schuhcf1dda52024-01-01 20:19:21 -08001063 "libnice10", "pmount", "libnice-dev", "feh", "usbutils", "locales",
1064 "trace-cmd", "clinfo", "jq", "strace", "sysstat", "lm-sensors",
1065 "can-utils", "xfsprogs", "bridge-utils", "net-tools", "apt-file",
1066 "parted", "xxd", "file", "pkexec", "libxkbfile1", "gdb", "autossh",
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001067 "smartmontools", "nvme-cli", "libgtk-3.0", "eog", "psmisc",
1068 "libsrtp2-1"
Austin Schuh51014832023-10-20 17:44:45 -07001069 ])
1070 target(["apt-get", "clean"])
1071
1072 target(["usermod", "-a", "-G", "sudo", "pi"])
1073 target(["usermod", "-a", "-G", "video", "pi"])
1074 target(["usermod", "-a", "-G", "systemd-journal", "pi"])
1075 target(["usermod", "-a", "-G", "dialout", "pi"])
1076
1077 virtual_packages = [
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001078 'glib-2.0-dev',
Austin Schuhcf1dda52024-01-01 20:19:21 -08001079 'libglib-2.0-0',
1080 'libglvnd',
1081 'libgtk-3-0',
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001082 'libxcb-dev',
Austin Schuhcf1dda52024-01-01 20:19:21 -08001083 'libxcb-glx',
Austin Schuhcf1dda52024-01-01 20:19:21 -08001084 'libz1',
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001085 'wayland',
1086 'wayland-dev',
Austin Schuh51014832023-10-20 17:44:45 -07001087 ]
1088
1089 install_virtual_packages(virtual_packages)
1090
Austin Schuhcf1dda52024-01-01 20:19:21 -08001091 yocto_packages = list_yocto_packages()
1092 packages = list_packages()
1093
1094 # Install the kernel and modules after all the normal packages are in.
1095 yocto_packages_to_install = [
1096 package for package in yocto_packages
1097 if (package.name.startswith('kernel-module-') or package.name.
1098 startswith('kernel-5.10') or package.name == 'kernel-modules')
1099 ]
1100
1101 packages_to_remove = []
1102
1103 # Remove kernel-module-* packages + kernel- package.
1104 for key in packages:
1105 if key.startswith('kernel-module') or key.startswith(
1106 'kernel-5.10'):
1107 already_installed = False
1108 for index, yocto_package in enumerate(
1109 yocto_packages_to_install):
1110 if key == yocto_package.name and packages[
1111 key] == yocto_package.version:
1112 already_installed = True
1113 del yocto_packages_to_install[index]
1114 break
1115 if not already_installed:
1116 packages_to_remove.append(key)
1117
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001118 print("Removing", packages_to_remove, file=sys.stderr)
Austin Schuhcf1dda52024-01-01 20:19:21 -08001119 if len(packages_to_remove) > 0:
1120 target(['dpkg', '--purge'] + packages_to_remove)
1121 print("Installing",
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001122 [package.name for package in yocto_packages_to_install],
1123 file=sys.stderr)
Austin Schuhcf1dda52024-01-01 20:19:21 -08001124
1125 install_packages(yocto_packages_to_install, packages)
1126
Austin Schuh51014832023-10-20 17:44:45 -07001127 yocto_package_names = [
Austin Schuh86d980e2023-10-20 22:44:47 -07001128 'tegra-argus-daemon',
1129 'tegra-firmware',
1130 'tegra-firmware-tegra234',
1131 'tegra-firmware-vic',
1132 'tegra-firmware-xusb',
1133 'tegra-libraries-argus-daemon-base',
1134 'tegra-libraries-camera',
1135 'tegra-libraries-core',
1136 'tegra-libraries-cuda',
1137 'tegra-libraries-eglcore',
1138 'tegra-libraries-glescore',
1139 'tegra-libraries-glxcore',
1140 'tegra-libraries-multimedia',
Austin Schuh51014832023-10-20 17:44:45 -07001141 'tegra-libraries-multimedia-utils',
Austin Schuh86d980e2023-10-20 22:44:47 -07001142 'tegra-libraries-multimedia-v4l',
1143 'tegra-libraries-nvsci',
1144 'tegra-libraries-vulkan',
1145 'tegra-nvphs',
1146 'tegra-nvphs-base',
1147 'libnvidia-egl-wayland1',
1148 'tegra-mmapi',
1149 'tegra-mmapi-dev',
1150 'cuda-cudart-11-8',
1151 'cuda-cudart-11-8-dev',
1152 'cuda-cudart-11-8-stubs',
1153 'libcurand-11-8',
1154 'libcurand-11-8-dev',
1155 'libcurand-11-8-stubs',
Austin Schuh86d980e2023-10-20 22:44:47 -07001156 'tegra-cmake-overrides',
1157 'cuda-target-environment',
1158 'libnpp-11-8',
1159 'libnpp-11-8-stubs',
1160 'libnpp-11-8-dev',
1161 'cuda-cccl-11-8',
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001162 'cuda-nvcc-11-8', # This one isn't in our yocto packages, but we need it
Austin Schuh86d980e2023-10-20 22:44:47 -07001163 'cuda-nvcc-headers-11-8',
Austin Schuhbffbe8b2023-11-22 21:32:05 -08001164 'nsight-systems-cli',
1165 'nsight-systems-cli-qdstrmimporter',
Austin Schuh6243c762023-12-24 14:42:45 -08001166 'tegra-tools-jetson-clocks',
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001167 # Added to support gstreamer1.0-dev, etc, for image_streamer
1168 'libgstfft-1.0-0',
1169 'libgstgl-1.0-0',
1170 'libgstrtsp-1.0-0',
1171 'libgstsdp-1.0-0',
1172 'libgstadaptivedemux-1.0-0',
1173 'libgstbadaudio-1.0-0',
1174 'libgstbasecamerabinsrc-1.0-0',
1175 'libgstcodecs-1.0-0',
1176 'libgstinsertbin-1.0-0',
1177 'libgstisoff-1.0-0',
1178 'libgstmpegts-1.0-0',
1179 'libgstphotography-1.0-0',
1180 'libgstplay-1.0-0',
1181 'libgstplayer-1.0-0',
1182 'libgstsctp-1.0-0',
1183 'libgsttranscoder-1.0-0',
1184 'libgsturidownloader-1.0-0',
1185 'libgstvulkan-1.0-0',
1186 'libgstwayland-1.0-0',
1187 'libgstwebrtc-1.0-0',
Austin Schuhcf1dda52024-01-01 20:19:21 -08001188 'gstreamer1.0',
1189 'gstreamer1.0-plugins-base',
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001190 'gstreamer1.0-plugins-bad',
1191 'gstreamer1.0-dev',
1192 'gstreamer1.0-plugins-base-dev',
1193 'gstreamer1.0-plugins-bad-dev',
Austin Schuhcf1dda52024-01-01 20:19:21 -08001194 'libgstallocators-1.0-0',
Austin Schuhcf1dda52024-01-01 20:19:21 -08001195 'libgstvideo-1.0-0',
1196 'libnvdsbufferpool1.0.0',
1197 'gstreamer1.0-plugins-nvarguscamerasrc',
1198 'cuda-cudart',
1199 'gstreamer1.0-plugins-nvvidconv',
1200 'gstreamer1.0-plugins-tegra',
1201 'gstreamer1.0-plugins-nvcompositor',
1202 'gstreamer1.0-plugins-nvdrmvideosink',
1203 'gstreamer1.0-plugins-nveglgles',
1204 'gstreamer1.0-plugins-nvjpeg',
1205 'gstreamer1.0-plugins-nvtee',
1206 'gstreamer1.0-plugins-nvv4l2camerasrc',
1207 'gstreamer1.0-plugins-nvvideo4linux2',
1208 'gstreamer1.0-plugins-nvvideosinks',
1209 'gstreamer1.0-plugins-tegra-binaryonly',
1210 'libgstnvcustomhelper',
1211 'gstreamer1.0-plugins-bad-videoparsersbad',
1212 'libgstcodecparsers-1.0-0',
1213 'libgstriff-1.0-0',
1214 'liborc-0.4-0',
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001215 'liborc-test-0.4-0',
1216 'orc-dev',
1217 'orc',
Austin Schuhcf1dda52024-01-01 20:19:21 -08001218 'libgstaudio-1.0-0',
1219 'libgsttag-1.0-0',
1220 'gstreamer1.0-plugins-good-rtp',
1221 'libgstrtp-1.0-0',
1222 'gstreamer1.0-plugins-good-udp',
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001223 'gstreamer1.0-plugins-bad-srtp',
1224 'gstreamer1.0-plugins-base-app',
1225 'gstreamer1.0-plugins-base-videoconvert',
1226 'gstreamer1.0-plugins-bad-dtls',
1227 'gstreamer1.0-plugins-good-video4linux2',
1228 'libcrypto3',
1229 'libssl3',
1230 'gstreamer1.0-plugins-ugly-x264',
1231 'gstreamer1.0-plugins-bad-webrtc',
1232 'libgstwebrtc-1.0-0',
1233 'libx264-163',
1234 'libnice',
1235 'gstreamer1.0-plugins-good-rtpmanager',
1236 'gstreamer1.0-plugins-ugly-asf',
Austin Schuhcf1dda52024-01-01 20:19:21 -08001237 # Yocto's doesn't work with gstreamer, and we don't actually care
1238 # hugely. opencv seems to work.
1239 'libv4l',
1240 'libv4l-dev',
1241 'libgstpbutils-1.0-0',
1242 'libgstnvdsseimeta1.0.0',
1243 'media-ctl',
1244 'libgstapp-1.0-0',
Austin Schuhf78eb3e2024-03-31 16:08:50 -07001245 'v4l-utils',
Austin Schuh51014832023-10-20 17:44:45 -07001246 ]
Austin Schuh51014832023-10-20 17:44:45 -07001247
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001248 for desired_package in yocto_package_names:
1249 found_package = False
1250 for yocto_package in yocto_packages:
1251 if desired_package == yocto_package.name:
1252 found_package = True
1253 if not found_package:
1254 print("Couldn't find package",
1255 desired_package,
1256 " for installation",
1257 file=sys.stderr)
1258
Austin Schuh51014832023-10-20 17:44:45 -07001259 install_packages([
1260 package for package in yocto_packages
1261 if package.name in yocto_package_names
1262 ], packages)
1263
Austin Schuhcf1dda52024-01-01 20:19:21 -08001264 install_virtual_packages([
1265 'libgstreamer1.0-0',
1266 "libgstreamer-plugins-base1.0-0",
1267 ])
Austin Schuh51014832023-10-20 17:44:45 -07001268
Austin Schuhcf1dda52024-01-01 20:19:21 -08001269 target([
1270 "apt-mark",
1271 "hold",
1272 "gstreamer1.0-plugins-base",
1273 "libgstreamer1.0-0",
1274 "liborc-0.4-0",
1275 ])
Austin Schuh51014832023-10-20 17:44:45 -07001276
Austin Schuhcf1dda52024-01-01 20:19:21 -08001277 # Opencv depends on gstreamer, but we want our gstreamer... So install
1278 # ours first, install the adapter packages, then install theirs.
1279 target([
1280 "apt-get",
1281 "-y",
1282 "install",
1283 "libopencv-calib3d406",
1284 "libopencv-contrib406",
1285 "libopencv-core406",
1286 "libopencv-features2d406",
1287 "libopencv-flann406",
1288 "libopencv-highgui406",
1289 "libopencv-imgcodecs406",
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001290 "gstreamer1.0-nice",
Austin Schuhcf1dda52024-01-01 20:19:21 -08001291 "libopencv-imgproc406",
1292 "libopencv-ml406",
1293 "libopencv-objdetect406",
1294 "libopencv-photo406",
1295 "libopencv-shape406",
1296 "libopencv-stitching406",
1297 "libopencv-superres406",
1298 "libopencv-video406",
1299 "libopencv-videoio406",
1300 "libopencv-videostab406",
1301 "libopencv-viz406",
1302 "libopencv-dev",
1303 ])
Austin Schuh51014832023-10-20 17:44:45 -07001304
1305 target(["systemctl", "enable", "nvargus-daemon.service"])
1306
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -07001307 copyfile("root:root", "644", "etc/sysctl.d/sctp.conf")
1308 copyfile("root:root", "644", "etc/systemd/logind.conf")
1309 copyfile("root:root", "555",
Austin Schuh51014832023-10-20 17:44:45 -07001310 "etc/bash_completion.d/aos_dump_autocomplete")
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -07001311 copyfile("root:root", "644", "etc/security/limits.d/rt.conf")
1312 copyfile("root:root", "644", "etc/systemd/system/usb-mount@.service")
1313 copyfile("root:root", "644", "etc/chrony/chrony.conf")
1314 target_mkdir("root:root", "700", "root/bin")
1315 target_mkdir("pi:pi", "755", "home/pi/.ssh")
1316 copyfile("pi:pi", "600", "home/pi/.ssh/authorized_keys")
1317 target_mkdir("root:root", "700", "root/bin")
1318 copyfile("root:root", "644", "etc/systemd/system/grow-rootfs.service")
Jim Ostrowskiafcc0982024-01-15 15:28:53 -08001319 copyfile("root:root", "644", "etc/systemd/system/frc971.service")
1320 copyfile("root:root", "644", "etc/systemd/system/frc971chrt.service")
Austin Schuh6243c762023-12-24 14:42:45 -08001321 copyfile("root:root", "644",
1322 "etc/systemd/system/jetson-clocks.service")
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -07001323 copyfile("root:root", "500", "root/bin/change_hostname.sh")
Jim Ostrowskiafcc0982024-01-15 15:28:53 -08001324 copyfile("root:root", "500", "root/bin/chrt.sh")
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -07001325 copyfile("root:root", "700", "root/trace.sh")
1326 copyfile("root:root", "440", "etc/sudoers")
1327 copyfile("root:root", "644", "etc/fstab")
Austin Schuha6bfa5c2024-02-19 22:12:43 -08001328 copyfile("root:root", "644", "etc/modprobe.d/audio.conf")
1329 copyfile("root:root", "644", "etc/modprobe.d/can.conf")
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -07001330 copyfile("root:root", "644",
Austin Schuh51014832023-10-20 17:44:45 -07001331 "var/nvidia/nvcam/settings/camera_overrides.isp")
Jim Ostrowskiafcc0982024-01-15 15:28:53 -08001332 copyfile("root:root", "644", "/etc/ld.so.conf.d/yocto.conf")
Austin Schuh51014832023-10-20 17:44:45 -07001333
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -07001334 target_mkdir("root:root", "755", "etc/systemd/network")
1335 copyfile("root:root", "644", "etc/systemd/network/eth0.network")
Austin Schuh8de7def2024-01-01 12:47:02 -08001336 copyfile("root:root", "644", "etc/systemd/network/80-cana.network")
1337 copyfile("root:root", "644", "etc/systemd/network/80-canb.network")
1338 copyfile("root:root", "644", "etc/systemd/network/80-canc.network")
Austin Schuhbffbe8b2023-11-22 21:32:05 -08001339 copyfile("root:root", "644", "etc/udev/rules.d/nvidia.rules")
Austin Schuh8de7def2024-01-01 12:47:02 -08001340 copyfile("root:root", "644", "etc/udev/rules.d/can.rules")
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001341 copyfile("root:root", "644", "etc/udev/rules.d/camera.rules")
Austin Schuh5db29f22024-03-16 17:00:31 -07001342 copyfile("root:root", "644",
1343 "lib/systemd/system/nvargus-daemon.service")
Jim Ostrowskiafcc0982024-01-15 15:28:53 -08001344 target(["/root/bin/change_hostname.sh", "orin-971-1"])
Austin Schuh51014832023-10-20 17:44:45 -07001345
1346 target(["systemctl", "enable", "systemd-networkd"])
1347 target(["systemctl", "enable", "grow-rootfs"])
Austin Schuh6243c762023-12-24 14:42:45 -08001348 target(["systemctl", "enable", "jetson-clocks"])
Jim Ostrowskiafcc0982024-01-15 15:28:53 -08001349 target(["systemctl", "enable", "frc971"])
1350 target(["systemctl", "enable", "frc971chrt"])
Austin Schuhbffbe8b2023-11-22 21:32:05 -08001351
Jim Ostrowskid25f5bc2024-02-03 20:03:46 -08001352 # Set up HW clock to use /dev/rtc0 and install hwclock service
1353 target(["ln", "-sf", "/dev/rtc0", "/dev/rtc"])
Austin Schuhf5dbe2c2024-04-06 16:10:24 -07001354 target(["rm", "/usr/lib/gstreamer-1.0/libgstnice.so"])
1355 target([
1356 "ln", "-s",
1357 "/usr/lib/aarch64-linux-gnu/gstreamer-1.0/libgstnice.so",
1358 "/usr/lib/gstreamer-1.0/"
1359 ])
Jim Ostrowskid25f5bc2024-02-03 20:03:46 -08001360 target_unescaped(
1361 "sed -i s/ATTR{hctosys}==\\\"1\\\"/ATTR{hctosys}==\\\"0\\\"/ /lib/udev/rules.d/50-udev-default.rules"
1362 )
1363 copyfile("root:root", "644", "etc/systemd/system/hwclock.service")
1364 target(["systemctl", "enable", "hwclock"])
1365
Austin Schuh51014832023-10-20 17:44:45 -07001366 target(["apt-file", "update"])
1367
1368 target(["ldconfig"])
1369
1370 if not os.path.exists(f"{partition}/home/pi/.dotfiles"):
1371 pi_target_unescaped(
1372 "cd /home/pi/ && git clone --separate-git-dir=/home/pi/.dotfiles https://github.com/AustinSchuh/.dotfiles.git tmpdotfiles && rsync --recursive --verbose --exclude .git tmpdotfiles/ /home/pi/ && rm -r tmpdotfiles && git --git-dir=/home/pi/.dotfiles/ --work-tree=/home/pi/ config --local status.showUntrackedFiles no"
1373 )
1374 pi_target(["vim", "-c", "\":qa!\""])
1375
1376 target_unescaped(
1377 "cd /root/ && git clone --separate-git-dir=/root/.dotfiles https://github.com/AustinSchuh/.dotfiles.git tmpdotfiles && rsync --recursive --verbose --exclude .git tmpdotfiles/ /root/ && rm -r tmpdotfiles && git --git-dir=/root/.dotfiles/ --work-tree=/root/ config --local status.showUntrackedFiles no"
1378 )
1379 target(["vim", "-c", "\":qa!\""])
1380
Jim Ostrowskif9396de2024-01-22 15:13:21 -08001381 # Do this after all the network needs are finished, since it won't
1382 # allow us to find URL's from the build server (frc971)
1383 target(["systemctl", "disable", "systemd-resolved"])
1384 copyfile("systemd-resolve:systemd-resolve", "644",
1385 "run/systemd/resolve/stub-resolv.conf")
1386 target(["systemctl", "enable", "systemd-resolved"])
1387
Austin Schuhbffbe8b2023-11-22 21:32:05 -08001388 generate_build_file(partition)
Austin Schuh86d980e2023-10-20 22:44:47 -07001389
Austin Schuhbffbe8b2023-11-22 21:32:05 -08001390 do_package(partition)
Austin Schuh86d980e2023-10-20 22:44:47 -07001391
Austin Schuh51014832023-10-20 17:44:45 -07001392
1393if __name__ == '__main__':
1394 main()