Skip to content

Commit 15c0ade

Browse files
erock2112SkCQ
authored andcommitted
[bazel] Replace extract_files_from_skia_wasm_container
The existing implementation uses rules_docker, which we need to move awa from. It was fairly straightforward to write a script to extract files from the image tarball(s). Note: this drops the existing functionality where we avoid pulling the specified image (and pull "@empty_container" instead) when the resulting files are not going to be used. As I understand it, this should not be a problem because when the flag is set we don't depend on the output files anyway, so Bazel shouldn't attempt to build the target. Bug: b/458681039 Change-Id: Ib02727086e96d712f8e480009ec5e4e0ffaaf422 Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/1115076 Reviewed-by: Kaylee Lubick <[email protected]> Commit-Queue: Eric Boren <[email protected]>
1 parent 8116398 commit 15c0ade

File tree

8 files changed

+177
-126
lines changed

8 files changed

+177
-126
lines changed

bazel/BUILD.bazel

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ get_fixup_owners_layers_test_suite(
44
name = "get_fixup_owners_layers_test",
55
)
66

7-
exports_files(["strip-package-lock.py"])
7+
exports_files([
8+
"oci-extract.py",
9+
"strip-package-lock.py",
10+
])

bazel/oci-extract.bzl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""
2+
Provides the oci_extract rule, used for extracting files from an oci_image.
3+
"""
4+
5+
def oci_extract(name, image, paths, **kwargs):
6+
"""Extract files from an OCI image.
7+
8+
Args:
9+
name: Name of the target.
10+
image: Label of the image to use.
11+
paths: Dictionary whose keys are absolute paths within the image and
12+
values are extraction destination paths.
13+
**kwargs: Additional arguments to pass to the genrule.
14+
"""
15+
python = "@python_3_11//:py3_runtime"
16+
script = "//bazel:oci-extract.py"
17+
cmd = """
18+
PYTHON=""
19+
for path in $(locations {python}); do
20+
if [[ $$path = *"/bin/python3" ]]; then
21+
PYTHON="$$path"
22+
fi
23+
done
24+
25+
$$PYTHON $(execpath {script}) --input=$(execpath {image}) --dest=$(RULEDIR) {path_flags}
26+
""".format(
27+
python = python,
28+
script = script,
29+
image = image,
30+
path_flags = " ".join(["--path=%s:%s" % (input, output) for input, output in paths.items()]),
31+
)
32+
native.genrule(
33+
name = name,
34+
srcs = [image],
35+
outs = paths.values(),
36+
cmd = cmd,
37+
tools = [
38+
python,
39+
script,
40+
],
41+
**kwargs
42+
)

bazel/oci-extract.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""
2+
Utility script used by the oci_extract rule in //bazel:oci-extract.bzl to
3+
extract files from an oci_image.
4+
"""
5+
6+
7+
import argparse
8+
import json
9+
import os
10+
import tarfile
11+
12+
13+
# Unfortunately, TarFile.extract()'s path parameter doesn't do what we'd expect
14+
# it to do, which is to extract place the extracted file at the exact specified
15+
# path. Instead, it treats the path as a directory into which the archive path
16+
# of the file is nested, eg. extracting "/usr/bin/myexec" with
17+
# path="/some/dir/myexec" produces "/some/dir/myexec/usr/bin/myexec". To work
18+
# around this we use an extraction filter which replaces the destination path.
19+
def make_extraction_filter(dst_path):
20+
def filter(tarinfo, path):
21+
# Call the default extraction filter first. This helps make
22+
# extraction more secure.
23+
tarinfo = tarfile.data_filter(tarinfo, path)
24+
tarinfo.path = dst_path
25+
return tarinfo
26+
return filter
27+
28+
29+
def extract_members(input_path, members, dest_dir):
30+
def path_for_blob(digest):
31+
return os.path.join(*[input_path, "blobs"] + digest.split(":"))
32+
33+
with open(os.path.join(input_path, "index.json")) as f:
34+
index = json.load(f)
35+
# We iterate in reverse, because later layers override the earlier ones and
36+
# we want the "final" version of the file.
37+
for manifest_spec in reversed(index["manifests"]):
38+
with open(path_for_blob(manifest_spec["digest"])) as f:
39+
manifest = json.load(f)
40+
for layer in reversed(manifest["layers"]):
41+
layer_tar = tarfile.open(path_for_blob(layer["digest"]))
42+
for src_path in list(members.keys()):
43+
try:
44+
member = layer_tar.getmember(src_path)
45+
except KeyError:
46+
continue
47+
dst_path = os.path.join(dest_dir, members[src_path])
48+
layer_tar.extract(member, filter=make_extraction_filter(dst_path))
49+
del members[src_path]
50+
51+
if len(members) > 0:
52+
raise Exception("Failed to find and extract files from %s: %s" % (input_path, ", ".join(members.keys())))
53+
54+
55+
# TODO(borenet): Remove once we've switched to oci_image.
56+
def extract_members_legacy(input_path, members, dest_dir):
57+
with tarfile.open(input_path) as input:
58+
manifest_tar = input.extractfile("manifest.json")
59+
manifest_list = json.load(manifest_tar)
60+
61+
# We iterate in reverse, because later layers override the earlier ones and
62+
# we want the "final" version of the file.
63+
for manifest in reversed(manifest_list):
64+
for layer in reversed(manifest["Layers"]):
65+
layer_tar = tarfile.open(
66+
fileobj=input.extractfile(layer), mode="r")
67+
for src_path in list(members.keys()):
68+
try:
69+
member = layer_tar.getmember(src_path)
70+
except KeyError:
71+
continue
72+
dst_path = os.path.join(dest_dir, members[src_path])
73+
layer_tar.extract(member, filter=make_extraction_filter(dst_path))
74+
del members[src_path]
75+
76+
if len(members) > 0:
77+
raise Exception("Failed to find and extract files from %s: %s" % (input_path, ", ".join(members.keys())))
78+
79+
80+
81+
def main():
82+
parser = argparse.ArgumentParser()
83+
parser.add_argument("--input")
84+
parser.add_argument("--path", action="append")
85+
parser.add_argument("--dest", default=".")
86+
args = parser.parse_args()
87+
88+
members = {}
89+
for arg in args.path:
90+
split = arg.split(":")
91+
if len(split) != 2:
92+
raise ValueError("Invalid path: %s" % arg)
93+
src = split[0].removeprefix("/") # There's no leading "/" in the tarball.
94+
dst = split[1]
95+
members[src] = dst
96+
97+
extract_members_legacy(args.input, members, args.dest)
98+
99+
100+
if __name__ == "__main__":
101+
main()

debugger-app/wasm_libs/BUILD.bazel

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
load("@aspect_rules_js//js:defs.bzl", "js_library")
2-
load("//infra-sk:index.bzl", "bool_flag", "extract_files_from_skia_wasm_container")
2+
load("//bazel:oci-extract.bzl", "oci_extract")
3+
load("//infra-sk:index.bzl", "bool_flag")
34

45
bool_flag(
56
default = True,
@@ -9,16 +10,18 @@ bool_flag(
910
# This target makes the wasm binaries and js files available for tests. This is not the same
1011
# as ../build because we want to be able to control which binaries get used when we deploy a
1112
# container, but we are less stringent when running tests on the infra CI.
12-
extract_files_from_skia_wasm_container(
13+
oci_extract(
1314
name = "fetch_debugger_wasm",
1415
testonly = True,
15-
container = "@pinned_debugger",
16-
container_files = {
16+
# TODO(borenet): Remove //image when we move to oci_image.
17+
image = "@pinned_debugger//image",
18+
paths = {
1719
"/usr/local/share/debugger-app/index.d.ts": "from_container/canvaskit.d.ts",
1820
"/usr/local/share/debugger-app/canvaskit.js": "from_container/canvaskit.js",
1921
"/usr/local/share/debugger-app/canvaskit.wasm": "from_container/canvaskit.wasm",
2022
},
21-
enabled_flag = ":use_debugger_from_container",
23+
tags = ["manual"], # Exclude it from wildcard queries, e.g. "bazel build //...".
24+
visibility = ["//jsfiddle:__subpackages__"],
2225
)
2326

2427
filegroup(

infra-sk/index.bzl

Lines changed: 0 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ load("@aspect_rules_js//js:defs.bzl", "js_binary")
44

55
# https://github.com/bazelbuild/bazel-skylib/blob/main/rules/common_settings.bzl
66
load("@bazel_skylib//rules:common_settings.bzl", skylib_bool_flag = "bool_flag")
7-
load("@io_bazel_rules_docker//container:flatten.bzl", "container_flatten")
87
load("@npm//:mocha/package_json.bzl", _mocha_bin = "bin")
98
load("//bazel/test_on_env:test_on_env.bzl", "test_on_env")
109
load("//infra-sk/esbuild:esbuild.bzl", "esbuild_dev_bundle", "esbuild_node_bundle", "esbuild_prod_bundle")
@@ -623,109 +622,6 @@ def sk_page(
623622
visibility = ["//visibility:public"],
624623
)
625624

626-
def extract_files_from_skia_wasm_container(name, container, container_files, enabled_flag, **kwargs):
627-
"""Extracts files from the Skia WASM container image (gcr.io/skia-public/skia-wasm-release).
628-
629-
This macro takes as inputs a list of paths inside the Docker container (container_files
630-
argument), and a list of the same length with the destination paths for each of the files to
631-
extract (outs argument), relative to the directory where the macro is instantiated.
632-
633-
This image will be pulled if the enabled_flag is true, so users who need to set that flag to
634-
true (e.g. infra folks pushing a clean image) should be granted permissions and have Docker
635-
authentication set up. Users who are working with a local build should set the flag to false
636-
and not need Docker authentication.
637-
638-
Args:
639-
name: Name of the target.
640-
container: The name of the docker container (defined in //WORKSPACE) which contains the files.
641-
container_files: Dictionary of absolute paths inside the Docker container to extract mapped to
642-
the path they should be extracted to.
643-
enabled_flag: Label. If set, should be the name of a bool_flag. If the bool_flag
644-
is True, the real skia_wasm_container image will be pulled and the images loaded as per
645-
usual. If the bool_flag is false, the container will not be loaded, but any outs will be
646-
created as empty files to make dependent rules happy. It is up to the caller to use that
647-
same flag to properly ignore those empty files if the flag is false (e.g. via a select).
648-
**kwargs: Any flags that should be forwarded to the generated rule
649-
"""
650-
651-
# Generates a .tar file with the contents of the image's filesystem (and a .json metadata file
652-
# which we ignore).
653-
#
654-
# See the rule implementation here:
655-
# https://github.com/bazelbuild/rules_docker/blob/02ad0a48fac9afb644908a634e8b2139c5e84670/container/flatten.bzl#L48
656-
#
657-
# Notes:
658-
# - It is unclear whether container_flatten is part of the public API because it is not
659-
# documented. But the fact that container_flatten is re-exported along with other, well
660-
# documented rules in rules_docker suggests that it might indeed be part of the public API
661-
# (see [1] and [2]).
662-
# - If they ever make breaking changes to container_flatten, then it is probably best to fork
663-
# it. The rule itself is relatively small; it is just a wrapper around a Go program that does
664-
# all the heavy lifting.
665-
# - This rule was chosen because most other rules in the rules_docker repository produce .tar
666-
# files with layered outputs, which means we would have to do the flattening ourselves.
667-
#
668-
# [1] https://github.com/bazelbuild/rules_docker/blob/6c29619903b6bc533ad91967f41f2a3448758e6f/container/container.bzl#L28
669-
# [2] https://github.com/bazelbuild/rules_docker/blob/e290d0975ab19f9811933d2c93be275b6daf7427/container/BUILD#L158
670-
671-
# We expect enabled_flag to be a bool_flag, which means we should have two labels defined
672-
# based on the passed in flag name, one with a _true suffix and one with a _false suffix. If
673-
# the flag is set to false, we pull from a public image that has no files in it.
674-
container_flatten(
675-
name = name + "_skia_wasm_container_filesystem",
676-
image = select({
677-
enabled_flag + "_true": container + "//image",
678-
enabled_flag + "_false": "@empty_container//image",
679-
}),
680-
)
681-
682-
# Name of the .tar file produced by the container_flatten target.
683-
skia_wasm_filesystem_tar = name + "_skia_wasm_container_filesystem.tar"
684-
685-
# Shell command that returns the directory of the BUILD file where this macro was instantiated.
686-
#
687-
# This works because:
688-
# - The $< variable[1] is expanded by the below genrule to the path of its only input file.
689-
# - The only input file to the genrule is the .tar file produced by the above container_flatten
690-
# target (see the genrule's srcs attribute).
691-
# - Said .tar file is produced in the directory of the BUILD file where this macro was
692-
# instantiated.
693-
#
694-
# [1] https://bazel.build/reference/be/make-variables
695-
output_dir = "$$(dirname $<)"
696-
697-
# Directory where we will untar the .tar file produced by the container_flatten target.
698-
skia_wasm_filesystem_dir = output_dir + "/" + skia_wasm_filesystem_tar + "_untarred"
699-
700-
# Untar the .tar file produced by the container_flatten rule.
701-
cmd = ("mkdir -p " + skia_wasm_filesystem_dir +
702-
" && tar xf $< -C " + skia_wasm_filesystem_dir)
703-
704-
outs = []
705-
706-
# Copy each requested file from the container filesystem to its desired destination.
707-
for src in container_files:
708-
dst = container_files[src]
709-
copy = " && (cp %s/%s %s/%s" % (skia_wasm_filesystem_dir, src, output_dir, dst)
710-
711-
# If the enabled_flag is False, we will be loading from an empty image and thus the
712-
# files will not exist. As such, we need to create them or Bazel will fail because the
713-
# expected generated files will not be created. It is up to the dependent rules to properly
714-
# ignore these files. We cannot easily have the genrule change its behavior depending on
715-
# how the flag is set, so we always touch the file as a backup. We write stderr to
716-
# /dev/null to squash any errors about files not existing.
717-
copy += " 2>/dev/null || touch %s/%s) " % (output_dir, dst)
718-
cmd += copy
719-
outs.append(dst)
720-
721-
native.genrule(
722-
name = name,
723-
srcs = [skia_wasm_filesystem_tar],
724-
outs = outs,
725-
cmd = cmd,
726-
**kwargs
727-
)
728-
729625
def bool_flag(flag_name, default = True, name = ""): # buildifier: disable=unused-variable
730626
"""Create a boolean flag and corresponding config_settings.
731627

jsfiddle/wasm_libs/BUILD.bazel

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
load("//infra-sk:index.bzl", "bool_flag", "extract_files_from_skia_wasm_container")
1+
load("//bazel:oci-extract.bzl", "oci_extract")
2+
load("//infra-sk:index.bzl", "bool_flag")
23

34
bool_flag(
45
default = True,
@@ -8,16 +9,17 @@ bool_flag(
89
# This target makes the wasm binaries and js files available for tests. This is not the same
910
# as ../build because we want to be able to control which binaries get used when we deploy a
1011
# container, but we are less stringent when running tests on the infra CI.
11-
extract_files_from_skia_wasm_container(
12+
oci_extract(
1213
name = "fetch_canvaskit_wasm",
1314
testonly = True,
14-
container = "@pinned_jsfiddle",
15-
container_files = {
15+
# TODO(borenet): Remove //image when we move to oci_image.
16+
image = "@pinned_jsfiddle//image",
17+
paths = {
1618
"/usr/local/share/jsfiddle/dist/index.d.ts": "from_container/canvaskit.d.ts",
1719
"/usr/local/share/jsfiddle/dist/canvaskit.js": "from_container/canvaskit.js",
1820
"/usr/local/share/jsfiddle/dist/canvaskit.wasm": "from_container/canvaskit.wasm",
1921
},
20-
enabled_flag = ":use_libraries_from_container",
22+
tags = ["manual"], # Exclude it from wildcard queries, e.g. "bazel build //...".
2123
visibility = ["//jsfiddle:__subpackages__"],
2224
)
2325

shaders/wasm_libs/BUILD.bazel

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
load("@aspect_rules_js//js:defs.bzl", "js_library")
2-
load("//infra-sk:index.bzl", "bool_flag", "extract_files_from_skia_wasm_container")
2+
load("//bazel:oci-extract.bzl", "oci_extract")
3+
load("//infra-sk:index.bzl", "bool_flag")
34

45
bool_flag(
56
default = True,
@@ -9,16 +10,17 @@ bool_flag(
910
# This target makes the canvaskit js and wasm binaries available for tests. This is not the same
1011
# as ../build because we want to be able to control which binaries get used when we deploy a
1112
# container, but we are less stringent when running tests on the infra CI.
12-
extract_files_from_skia_wasm_container(
13+
oci_extract(
1314
name = "fetch_canvaskit_wasm",
1415
testonly = True,
15-
container = "@pinned_jsfiddle",
16-
container_files = {
16+
# TODO(borenet): Remove //image when we move to oci_image.
17+
image = "@pinned_jsfiddle//image",
18+
paths = {
1719
"/usr/local/share/jsfiddle/dist/index.d.ts": "from_container/canvaskit.d.ts",
1820
"/usr/local/share/jsfiddle/dist/canvaskit.js": "from_container/canvaskit.js",
1921
"/usr/local/share/jsfiddle/dist/canvaskit.wasm": "from_container/canvaskit.wasm",
2022
},
21-
enabled_flag = ":use_canvaskit_from_container",
23+
tags = ["manual"], # Exclude it from wildcard queries, e.g. "bazel build //...".
2224
visibility = ["//shaders:__subpackages__"],
2325
)
2426

skottie/wasm_libs/BUILD.bazel

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
load("//infra-sk:index.bzl", "bool_flag", "extract_files_from_skia_wasm_container")
1+
load("//bazel:oci-extract.bzl", "oci_extract")
2+
load("//infra-sk:index.bzl", "bool_flag")
23

34
bool_flag(
45
default = True,
@@ -8,17 +9,18 @@ bool_flag(
89
# This target makes the canvaskit js and wasm binaries available for tests. This is not the same
910
# as ../build because we want to be able to control which binaries get used when we deploy a
1011
# container, but we are less stringent when running tests on the infra CI.
11-
extract_files_from_skia_wasm_container(
12+
oci_extract(
1213
name = "fetch_canvaskit_wasm",
1314
testonly = True,
14-
container = "@pinned_jsfiddle",
15-
container_files = {
15+
# TODO(borenet): Remove //image when we move to oci_image.
16+
image = "@pinned_jsfiddle//image",
17+
paths = {
1618
"/usr/local/share/jsfiddle/dist/index.d.ts": "from_container/canvaskit.d.ts",
1719
"/usr/local/share/jsfiddle/dist/canvaskit.js": "from_container/canvaskit.js",
1820
"/usr/local/share/jsfiddle/dist/canvaskit.wasm": "from_container/canvaskit.wasm",
1921
},
20-
enabled_flag = ":use_canvaskit_from_container",
21-
visibility = ["//skottie:__subpackages__"],
22+
tags = ["manual"], # Exclude it from wildcard queries, e.g. "bazel build //...".
23+
visibility = ["//jsfiddle:__subpackages__"],
2224
)
2325

2426
filegroup(

0 commit comments

Comments
 (0)