Fingerprint the inputs to the image. Compute the fingerprint of the image. Changing the contents of any of the files or adding or removing files to the image will change the fingerprint, as will modifying the inputs to any of its dependencies. The image considers al
(self)
| 1281 | |
| 1282 | @cache |
| 1283 | def fingerprint(self) -> Fingerprint: |
| 1284 | """Fingerprint the inputs to the image. |
| 1285 | |
| 1286 | Compute the fingerprint of the image. Changing the contents of any of |
| 1287 | the files or adding or removing files to the image will change the |
| 1288 | fingerprint, as will modifying the inputs to any of its dependencies. |
| 1289 | |
| 1290 | The image considers all non-gitignored files in its mzbuild context to |
| 1291 | be inputs. If it has a pre-image action, that action may add additional |
| 1292 | inputs via `PreImage.inputs`. |
| 1293 | """ |
| 1294 | self_hash = hashlib.sha1() |
| 1295 | # When inputs come from precomputed sources (crate and image context |
| 1296 | # batching + resolved CargoPreImage paths), they are already individual |
| 1297 | # file paths from git. Skip the expensive expand_globs subprocess calls. |
| 1298 | inputs = self.inputs() |
| 1299 | if self.image._context_files_cache is not None: |
| 1300 | resolved_inputs = sorted(inputs) |
| 1301 | else: |
| 1302 | resolved_inputs = sorted(set(git.expand_globs(self.image.rd.root, *inputs))) |
| 1303 | for rel_path in resolved_inputs: |
| 1304 | abs_path = self.image.rd.root / rel_path |
| 1305 | file_hash = hashlib.sha1() |
| 1306 | raw_file_mode = os.lstat(abs_path).st_mode |
| 1307 | # Compute a simplified file mode using the same rules as Git. |
| 1308 | # https://github.com/git/git/blob/3bab5d562/Documentation/git-fast-import.txt#L610-L616 |
| 1309 | if stat.S_ISLNK(raw_file_mode): |
| 1310 | file_mode = 0o120000 |
| 1311 | elif raw_file_mode & stat.S_IXUSR: |
| 1312 | file_mode = 0o100755 |
| 1313 | else: |
| 1314 | file_mode = 0o100644 |
| 1315 | with open(abs_path, "rb") as f: |
| 1316 | file_hash.update(f.read()) |
| 1317 | self_hash.update(file_mode.to_bytes(2, byteorder="big")) |
| 1318 | self_hash.update(rel_path.encode()) |
| 1319 | self_hash.update(file_hash.digest()) |
| 1320 | self_hash.update(b"\0") |
| 1321 | |
| 1322 | for pre_image in self.image.pre_images: |
| 1323 | self_hash.update(pre_image.extra().encode()) |
| 1324 | self_hash.update(b"\0") |
| 1325 | |
| 1326 | self_hash.update(f"profile={self.image.rd.profile}".encode()) |
| 1327 | self_hash.update(f"arch={self.image.rd.arch}".encode()) |
| 1328 | self_hash.update(f"coverage={self.image.rd.coverage}".encode()) |
| 1329 | self_hash.update(f"sanitizer={self.image.rd.sanitizer}".encode()) |
| 1330 | # This exists to make sure all hashes from before we had a GHCR mirror are invalidated, so that we rebuild when an image doesn't exist on GHCR yet |
| 1331 | self_hash.update(b"mirror=ghcr") |
| 1332 | |
| 1333 | full_hash = hashlib.sha1() |
| 1334 | full_hash.update(self_hash.digest()) |
| 1335 | for dep in sorted(self.dependencies.values(), key=lambda d: d.name): |
| 1336 | full_hash.update(dep.name.encode()) |
| 1337 | full_hash.update(dep.fingerprint()) |
| 1338 | full_hash.update(b"\0") |
| 1339 | |
| 1340 | return Fingerprint(full_hash.digest()) |