Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions image/compose.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ def _disk_impl(ctx: AnalysisContext) -> list[Provider]:
mkfs_options = ctx.attrs.mkfs_options,
seed = ctx.attrs.disk_seed,
split = True,
strip_paths = ctx.attrs.strip_paths,
strip_pkgdb = ctx.attrs.strip_pkgdb,
verity_key = resolve_signing_key(ctx.attrs.verity_key),
)
Expand Down Expand Up @@ -508,6 +509,7 @@ def _disk_impl(ctx: AnalysisContext) -> list[Provider]:
mkfs_options = ctx.attrs.mkfs_options,
seed = ctx.attrs.disk_seed,
split = True,
strip_paths = ctx.attrs.strip_paths,
strip_pkgdb = ctx.attrs.strip_pkgdb,
verity_key = resolve_signing_key(ctx.attrs.verity_key),
virtual_size = ctx.attrs.virtual_size,
Expand Down
16 changes: 12 additions & 4 deletions image/ddi.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ def declare_ddi(
seed: str | None = None,
verity_key: SigningKeyInfo | None = None,
strip_pkgdb: bool = False,
strip_paths: list[str] = [],
mkfs_options: dict[str, list[str]] = {},
basename: str = "image",
identifier: str | None = None,
Expand All @@ -294,10 +295,11 @@ def declare_ddi(

With strip_pkgdb, the partitions omit the package database wherever the image's package system
keeps it, for a system nothing ever resolves packages in. The image's `[pkgdb]` and `[sbom]`
subtargets still capture it from the tree itself. `extension` makes the driver package the tree
as an extension (see extension.bzl). `tmpfiles` replaces the tmpfiles lines of `image`. The
partitions leave out the contents of the directories in `exclude`. `virtual_size` goes into the
provider, see DdiInfo.
subtargets still capture it from the tree itself. `strip_paths` drops further files by glob, for
what the partitions do not need but a sibling target reading the same layer still does.
`extension` makes the driver package the tree as an extension (see extension.bzl). `tmpfiles`
replaces the tmpfiles lines of `image`. The partitions leave out the contents of the directories
in `exclude`. `virtual_size` goes into the provider, see DdiInfo.
"""
_check_definitions(
definitions,
Expand Down Expand Up @@ -400,6 +402,7 @@ def declare_ddi(
"root_hash_out": None,
"seed": seed,
"signing": signing_key_spec(verity_key),
"strip_paths": strip_paths,
}

if tmpfiles != None:
Expand Down Expand Up @@ -481,6 +484,11 @@ DDI_ATTRS = {
default = {},
doc = "filesystem -> the options mkfs is given when formatting a partition of that type",
),
"strip_paths": attrs.list(
attrs.string(),
default = [],
doc = "globs, relative to the image root, to leave out of the partitions; the image still carries them",
),
"strip_pkgdb": attrs.bool(
default = False,
doc = "leave the package database out of the partitions; the image still carries it",
Expand Down
11 changes: 11 additions & 0 deletions image/ddi.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ class Spec(finalize.ImageSpec):
root_hash_out: str | None
# Image paths holding the package database, stripped from the partitions.
pkgdb_paths: list[str]
# Globs, relative to the image root, of further files to strip from the partitions.
strip_paths: list[str]
# Image directories whose contents the partitions leave out. The directories stay, empty.
exclude: list[str]
# Where to list what the composed disk carries, its partitions taken together.
Expand Down Expand Up @@ -318,6 +320,14 @@ def empty_directories(tree: Path, directories: list[str]) -> None:
util.remove_path(child)


def strip_files(tree: Path, patterns: list[str]) -> None:
"""Remove everything below `tree` that one of the globs in `patterns` matches."""
for pattern in patterns:
# Sorted so a failure reports the same path every run, whatever order the tree is read in.
for path in sorted(tree.glob(pattern.lstrip("/"))):
util.remove_path(path)


def _write_definitions(
directory: Path,
written: list[WrittenPartition],
Expand Down Expand Up @@ -479,6 +489,7 @@ def main(argv: list[str] | None = None) -> None:
# subtargets capture from the tree, so partitions nothing resolves packages in can drop it.
for relative in spec["pkgdb_paths"]:
util.remove_path(tree / relative, with_parents=True)
strip_files(tree, spec["strip_paths"])
empty_directories(tree, spec["exclude"])

if extension:
Expand Down
39 changes: 39 additions & 0 deletions image/ddi_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,5 +185,44 @@ def test_removes_the_contents_and_keeps_the_directory(self) -> None:
self.assertEqual((tree / "usr/os-release").read_text(), "ID=test")


class TestStripFiles(unittest.TestCase):
def _tree(self, scratch_dir: str) -> Path:
tree = Path(scratch_dir)
for version in ("6.17.0", "6.18.0"):
modules = tree / "usr/lib/modules" / version
(modules / "kernel/fs").mkdir(parents=True)
(modules / "vmlinuz").write_text("kernel")
(modules / "modules.dep").write_text("deps")
(modules / "kernel/fs/xfs.ko").write_text("module")
return tree

def test_removes_every_match_and_nothing_else(self) -> None:
with tempfile.TemporaryDirectory(prefix="ddi_test.") as scratch_dir:
tree = self._tree(scratch_dir)
ddi.strip_files(tree, ["usr/lib/modules/*/vmlinuz"])
self.assertEqual(sorted(tree.glob("usr/lib/modules/*/vmlinuz")), [])
self.assertEqual(len(sorted(tree.glob("usr/lib/modules/*/modules.dep"))), 2)

def test_takes_a_directory_with_its_contents(self) -> None:
with tempfile.TemporaryDirectory(prefix="ddi_test.") as scratch_dir:
tree = self._tree(scratch_dir)
ddi.strip_files(tree, ["usr/lib/modules/*/kernel"])
self.assertEqual(sorted(tree.glob("usr/lib/modules/*/kernel")), [])
self.assertTrue((tree / "usr/lib/modules/6.17.0").is_dir())

def test_reads_a_leading_slash_as_the_image_root(self) -> None:
with tempfile.TemporaryDirectory(prefix="ddi_test.") as scratch_dir:
tree = self._tree(scratch_dir)
ddi.strip_files(tree, ["/usr/lib/modules/*/vmlinuz"])
self.assertEqual(sorted(tree.glob("usr/lib/modules/*/vmlinuz")), [])

def test_ignores_a_pattern_that_matches_nothing(self) -> None:
# A glob holds for every image the rule builds, so one without the file is not an error.
with tempfile.TemporaryDirectory(prefix="ddi_test.") as scratch_dir:
tree = self._tree(scratch_dir)
ddi.strip_files(tree, ["usr/lib/modules/*/.vmlinuz.hmac"])
self.assertEqual(len(sorted(tree.glob("usr/lib/modules/*/vmlinuz"))), 2)


if __name__ == "__main__":
unittest.main()
Loading