diff --git a/antlir/antlir2/genrule_in_image/genrule_in_image.bzl b/antlir/antlir2/genrule_in_image/genrule_in_image.bzl index 576fbb6988..94c84743c1 100644 --- a/antlir/antlir2/genrule_in_image/genrule_in_image.bzl +++ b/antlir/antlir2/genrule_in_image/genrule_in_image.bzl @@ -10,6 +10,7 @@ load("//antlir/antlir2/bzl:selects.bzl", "selects") load("//antlir/antlir2/bzl:types.bzl", "LayerInfo") load("//antlir/antlir2/bzl/image:cfg.bzl", "attrs_selected_by_cfg", "cfg_attrs", "layer_cfg") load("//antlir/antlir2/bzl/image:layer.bzl", "layer_rule") +load("//antlir/antlir2/bzl/image:mounts.bzl", "all_mounts", "container_mount_args") load("//antlir/antlir2/os:package.bzl", "get_default_os_for_package", "should_all_images_in_package_use_default_os") def _impl(ctx: AnalysisContext) -> list[Provider] | Promise: @@ -53,6 +54,13 @@ def _impl(ctx: AnalysisContext) -> list[Provider] | Promise: cmd_args(ctx.attrs._working_format, format = "--working-format={}"), cmd_args(out.as_output(), format = "--out={}"), "--dir" if out_is_dir else cmd_args(), + cmd_args([ + container_mount_args(mount) + for mount in all_mounts( + features = layer[LayerInfo].features, + parent_layer = layer[LayerInfo].parent[LayerInfo] if layer[LayerInfo].parent else None, + ) + ]), "--", ctx.attrs.bash, ), diff --git a/antlir/antlir2/genrule_in_image/src/main.rs b/antlir/antlir2/genrule_in_image/src/main.rs index 3a56fec0f6..f0b02f2958 100644 --- a/antlir/antlir2/genrule_in_image/src/main.rs +++ b/antlir/antlir2/genrule_in_image/src/main.rs @@ -5,12 +5,14 @@ * LICENSE file in the root directory of this source tree. */ +use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; use antlir2_isolate::unshare; use antlir2_isolate::IsolationContext; use antlir2_overlayfs::OverlayFs; +use anyhow::anyhow; use anyhow::ensure; use anyhow::Context; use anyhow::Result; @@ -28,6 +30,9 @@ struct Args { #[clap(value_enum, long)] /// On-disk format of the layer storage working_format: WorkingFormat, + /// `--bind-mount-ro src dst` creates an RO bind-mount of src to dst in the subvol + #[clap(long, num_args = 2)] + bind_mount_ro: Vec, #[clap(flatten)] out: Out, #[clap(last(true))] @@ -98,6 +103,16 @@ fn main() -> Result<()> { .tmpfs(Path::new("/tmp")) .devtmpfs(Path::new("/dev")); + builder.inputs( + args.bind_mount_ro + .chunks(2) + .map(|pair| match pair { + [src, dst] => Ok((dst.clone(), src.clone())), + _ => Err(anyhow!("Unrecognized mount arg: {:?}", pair)), + }) + .collect::>>()?, + ); + if args.out.dir { std::fs::create_dir_all(&args.out.out)?; builder diff --git a/antlir/antlir2/genrule_in_image/tests/BUCK b/antlir/antlir2/genrule_in_image/tests/BUCK index a3ae188a0a..c546020c27 100644 --- a/antlir/antlir2/genrule_in_image/tests/BUCK +++ b/antlir/antlir2/genrule_in_image/tests/BUCK @@ -132,6 +132,42 @@ genrule_in_image( layer = ":with-par", ) +image.layer( + name = "mount-src", + features = [ + feature.install_text( + dst = "/file-in-mount-1", + text = "file 1 in mounted layer", + ), + feature.install_text( + dst = "/file-in-mount-2", + text = "file 2 in mounted layer", + ), + ], +) + +image.layer( + name = "layer-with-mount", + features = [ + feature.ensure_dirs_exist(dirs = "/mnt/layer"), + feature.layer_mount( + mountpoint = "/mnt/layer", + source = ":mount-src", + ), + ], + parent_layer = ":layer", +) + +genrule_in_image( + name = "with-mount", + out = "f", + bash = """ + ls /mnt/layer > $OUT + cat /mnt/layer/file-in-mount-1 >> $OUT + """, + layer = ":layer-with-mount", +) + python_unittest( name = "test", srcs = ["test.py"], @@ -144,5 +180,6 @@ python_unittest( "NAMED_DIR": "$(location :named-dir)", "NAMED_OUTS": "$(location :named-outs)", "SINGLE_FILE": "$(location :single-file)", + "WITH_MOUNT": "$(location :with-mount)", }, ) diff --git a/antlir/antlir2/genrule_in_image/tests/test.py b/antlir/antlir2/genrule_in_image/tests/test.py index 124ce6b6ea..b99e8b0fec 100644 --- a/antlir/antlir2/genrule_in_image/tests/test.py +++ b/antlir/antlir2/genrule_in_image/tests/test.py @@ -52,3 +52,10 @@ def test_installed_par(self) -> None: f = Path(os.getenv("INSTALLED_PAR")) self.assertTrue(f.exists()) self.assertEqual(f.read_text(), "From par\n") + + def test_with_mount(self) -> None: + f = Path(os.getenv("WITH_MOUNT")) + self.assertTrue(f.exists()) + self.assertEqual( + f.read_text(), "file-in-mount-1\nfile-in-mount-2\nfile 1 in mounted layer" + )