def inputs(self, transitive: bool = False) -> Set[str]: """List the files tracked as inputs to the image. These files are used to compute the fingerprint for the image. See `ResolvedImage.fingerprint` for details. Returns: inputs: A list of input files, relative to the root of the repository. """ paths = set( git.expand_globs(self.image.rd.root, f"{self.image.path}/**")) if not paths: # While we could find an `mzbuild.yml` file for this service, expland_globs didn't # return any files that matched this service. At the very least, the `mzbuild.yml` # file itself should have been returned. We have a bug if paths is empty. raise AssertionError( f"{self.image.name} mzbuild exists but its files are unknown to git" ) if self.image.pre_image is not None: paths |= self.image.pre_image.inputs() if transitive: for dep in self.dependencies.values(): paths |= dep.inputs(transitive) return paths
def inputs(self) -> Set[str]: """Compute the files that can impact the compilation of this crate. Note that the returned list may have false positives (i.e., include files that do not in fact impact the compilation of this crate), but it is not believed to have false negatives. Returns: inputs: A list of input files, relative to the root of the Cargo workspace. """ # NOTE(benesch): it would be nice to have fine-grained tracking of only # exactly the files that go into a Rust crate, but doing this properly # requires parsing Rust code, and we don't want to force a dependency on # a Rust toolchain for users running demos. Instead, we assume that all† # files in a crate's directory are inputs to that crate. # # † As a development convenience, we omit mzcompose and mzcompose.yml # files within a crate. This is technically incorrect if someone writes # `include!("mzcompose.yml")`, but that seems like a crazy thing to do. return git.expand_globs( self.root, f"{self.path}/**", f":(exclude){self.path}/mzcompose", f":(exclude){self.path}/mzcompose.yml", )
def fingerprint(self) -> Fingerprint: """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 all non-gitignored files in its mzbuild context to be inputs. If it has a pre-image action, that action may add additional inputs via `PreImage.inputs`. """ self_hash = hashlib.sha1() for rel_path in sorted( set(git.expand_globs(self.image.rd.root, *self.inputs()))): abs_path = self.image.rd.root / rel_path file_hash = hashlib.sha1() raw_file_mode = os.lstat(abs_path).st_mode # Compute a simplified file mode using the same rules as Git. # https://github.com/git/git/blob/3bab5d562/Documentation/git-fast-import.txt#L610-L616 if stat.S_ISLNK(raw_file_mode): file_mode = 0o120000 elif raw_file_mode & stat.S_IXUSR: file_mode = 0o100755 else: file_mode = 0o100644 with open(abs_path, "rb") as f: file_hash.update(f.read()) self_hash.update(file_mode.to_bytes(2, byteorder="big")) self_hash.update(rel_path.encode()) self_hash.update(file_hash.digest()) self_hash.update(b"\0") for pre_image in self.image.pre_images: self_hash.update(pre_image.extra().encode()) self_hash.update(b"\0") self_hash.update(f"arch={self.image.rd.arch}".encode()) self_hash.update(f"coverage={self.image.rd.coverage}".encode()) full_hash = hashlib.sha1() full_hash.update(self_hash.digest()) for dep in sorted(self.dependencies.values(), key=lambda d: d.name): full_hash.update(dep.name.encode()) full_hash.update(dep.fingerprint()) full_hash.update(b"\0") return Fingerprint(full_hash.digest())
def inputs(self, transitive: bool = False) -> Set[str]: """List the files tracked as inputs to the image. These files are used to compute the fingerprint for the image. See `ResolvedImage.fingerprint` for details. Returns: inputs: A list of input files, relative to the root of the repository. """ paths = set(git.expand_globs(self.image.rd.root, f"{self.image.path}/**")) if self.image.pre_image is not None: paths |= self.image.pre_image.inputs() if transitive: for dep in self.dependencies.values(): paths |= dep.inputs(transitive) return paths
def inputs(self) -> Set[str]: return set( git.expand_globs(self.rd.root, f"{self.source}/{self.matching}"))