Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: py_image_layers #349

Closed
wants to merge 2 commits into from
Closed
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
26 changes: 24 additions & 2 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,24 @@ load("//py:toolchains.bzl", "rules_py_toolchains")

rules_py_toolchains()

load("@bazel_features//:deps.bzl", "bazel_features_deps")

bazel_features_deps()

# Load the Python toolchain for rules_docker
register_toolchains("//:container_py_toolchain")

load("@rules_oci//oci:repositories.bzl", "oci_register_toolchains")

oci_register_toolchains(name = "oci")

load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")

python_register_toolchains(
name = "python_toolchain_3_8",
python_version = "3.8.12",
# Setting `set_python_version_constraint` will set special constraints on the registered toolchain.
# This means that this toolchain registration will only be selected for `py_binary` / `py_test` targets
# This means that this toolchain registration will only be selected for `py_binary` / `py_test` targets
# that have the `python_version = "3.8.12"` attribute set. Targets that have no `python_attribute` will use
# the default toolchain resolved which can be seen below.
set_python_version_constraint = True,
Expand All @@ -45,10 +53,12 @@ py_repositories()

############################################
# Aspect bazel-lib
load("@aspect_bazel_lib//lib:repositories.bzl", "register_coreutils_toolchains")
load("@aspect_bazel_lib//lib:repositories.bzl", "register_coreutils_toolchains", "register_tar_toolchains")

register_coreutils_toolchains()

register_tar_toolchains()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users will also need this toolchain? Is the expectation that they register too, or should this ruleset be doing that in the setup


############################################
## CC toolchain using llvm
load("@toolchains_llvm//toolchain:deps.bzl", "bazel_toolchain_dependencies")
Expand Down Expand Up @@ -146,6 +156,18 @@ load(

_py_image_repos()

load("@rules_oci//oci:pull.bzl", "oci_pull")

oci_pull(
name = "ubuntu",
digest = "sha256:67211c14fa74f070d27cc59d69a7fa9aeff8e28ea118ef3babc295a0428a6d21",
image = "ubuntu",
platforms = [
"linux/arm64/v8",
"linux/amd64",
],
)

############################################
# rules_rust dependencies for building tools
load("@rules_rust//rust:repositories.bzl", "rules_rust_dependencies", "rust_register_toolchains", "rust_repository_set")
Expand Down
33 changes: 33 additions & 0 deletions docs/rules.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 44 additions & 2 deletions examples/py_binary/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,9 +1,51 @@
load("//py:defs.bzl", "py_binary")
load("@aspect_bazel_lib//lib:transitions.bzl", "platform_transition_filegroup")
load("//py:defs.bzl", "py_binary", "py_image_layers")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_tarball")

py_binary(
name = "py_binary",
name = "say_hello",
srcs = ["say.py"],
deps = [
"@pypi_cowsay//:pkg",
],
)

oci_image(
name = "say_image",
base = "@ubuntu",
entrypoint = ["/examples/py_binary/say_hello"],
tars = py_image_layers("say_image_layers", "say_hello"),
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this instead produce a filegroup, so it can be used like a regular rule, rather than unexpectedly returning a list?


platform(
name = "aarch64_linux",
constraint_values = [
"@platforms//os:linux",
"@platforms//cpu:aarch64",
],
)

platform(
name = "x86_64_linux",
constraint_values = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
)

platform_transition_filegroup(
name = "platform_image",
srcs = [":say_image"],
target_platform = select({
"@platforms//cpu:arm64": ":aarch64_linux",
"@platforms//cpu:x86_64": ":x86_64_linux",
}),
)

# $ bazel run //examples/py_binary:load
# $ docker run --rm gcr.io/oci_python_hello_world:latest
oci_tarball(
name = "load",
image = ":platform_image",
repo_tags = ["bazel/say:latest"],
)
15 changes: 15 additions & 0 deletions internal_deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ def rules_py_internal_deps():
],
)

http_archive(
name = "bazel_features",
sha256 = "5d7e4eb0bb17aee392143cd667b67d9044c270a9345776a5e5a3cccbc44aa4b3",
strip_prefix = "bazel_features-1.13.0",
url = "https://github.com/bazel-contrib/bazel_features/releases/download/v1.13.0/bazel_features-v1.13.0.tar.gz",
)

# Override bazel_skylib distribution to fetch sources instead
# so that the gazelle extension is included
# see https://github.com/bazelbuild/bazel-skylib/issues/250
Expand Down Expand Up @@ -118,3 +125,11 @@ def rules_py_internal_deps():
sha256 = "0523026398aea9c8b5f7a4a6d5c0829c285b4fbd960c17b5967a369342e21e01",
downloaded_file_path = "sqlparse-0.4.0-py3-none-any.whl",
)

# for testing py_image_layers
http_archive(
name = "rules_oci",
sha256 = "768cd23d5fea0235858eecfc8bfaae77a11fe9db9ebb1ac03d31c4b19eb9bc11",
strip_prefix = "rules_oci-2.0.0-alpha5",
url = "https://github.com/bazel-contrib/rules_oci/releases/download/v2.0.0-alpha5/rules_oci-v2.0.0-alpha5.tar.gz",
)
3 changes: 2 additions & 1 deletion py/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
# For stardoc to reference the files
exports_files(["defs.bzl"])

# For Bazel 6.x compatibility, since
# For Bazel 6.x compatibility, since
# PyRuntimeInfo shipped only with Bazel 7
# Users can set, e.g. --@aspect_rules_py//py:interpreter_version=3.9.18
string_flag(
Expand All @@ -31,6 +31,7 @@ bzl_library(
deps = [
"//py/private:py_binary",
"//py/private:py_executable",
"//py/private:py_image_layers",
"//py/private:py_library",
"//py/private:py_pytest_main",
"//py/private:py_unpacked_wheel",
Expand Down
3 changes: 2 additions & 1 deletion py/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ load("//py/private:py_pytest_main.bzl", _py_pytest_main = "py_pytest_main")
load("//py/private:py_unpacked_wheel.bzl", _py_unpacked_wheel = "py_unpacked_wheel")
load("//py/private:virtual.bzl", _resolutions = "resolutions")
load("//py/private:py_venv.bzl", _py_venv = "py_venv")
load("//py/private:py_image_layers.bzl", _py_image_layers = "py_image_layers")

py_pytest_main = _py_pytest_main

Expand All @@ -16,7 +17,7 @@ py_binary_rule = _py_binary
py_test_rule = _py_test
py_library_rule = _py_library
py_unpacked_wheel_rule = _py_unpacked_wheel

py_image_layers = _py_image_layers
resolutions = _resolutions

def _py_binary_or_test(name, rule, srcs, main, imports, deps = [], resolutions = {}, **kwargs):
Expand Down
7 changes: 7 additions & 0 deletions py/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,10 @@ bzl_library(
srcs = ["virtual.bzl"],
visibility = ["//py:__subpackages__"],
)

bzl_library(
name = "py_image_layers",
srcs = ["py_image_layers.bzl"],
visibility = ["//py:__subpackages__"],
deps = ["@aspect_bazel_lib//lib:tar"],
)
77 changes: 77 additions & 0 deletions py/private/py_image_layers.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"Helper function to make three separate layers for python applications"

load("@aspect_bazel_lib//lib:tar.bzl", "mtree_spec", "tar")

# match *only* external repositories that have the string "python"
# e.g. this will match
# `/hello_world/hello_world_bin.runfiles/rules_python~0.21.0~python~python3_9_aarch64-unknown-linux-gnu/bin/python3`
# but not match
# `/hello_world/hello_world_bin.runfiles/_main/python_app`
PY_INTERPRETER_REGEX = "\\.runfiles/.*python.*-.*"

# match *only* external pip like repositories that contain the string "site-packages"
SITE_PACKAGES_REGEX = "\\.runfiles/.*/site-packages/.*"

def py_image_layers(name, binary, interpreter_regex = PY_INTERPRETER_REGEX, site_packages_regex = SITE_PACKAGES_REGEX):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass kwargs and propagate the well known tags.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interpreter_regex and site_packages_regex are unused, the constants are used in the macro below, did you mean to use them?

"""Create three layers for a py_binary target: interpreter, third-party packages, and application code.

This allows a container image to have smaller uploads, since the application layer usually changes more
than the other two.

> [!NOTE]
> The middle layer may duplicate other py_image_layers which have a disjoint set of dependencies.
> Follow https://github.com/aspect-build/rules_py/issues/244

Args:
name: prefix for generated targets, to ensure they are unique within the package
binary: a py_binary target
interpreter_regex: a regular expression for use by `grep` which extracts the interpreter and related files from the binary runfiles tree
site_packages_regex: a regular expression for use by `grep` which extracts installed packages from the binary runfiles tree

Returns:
a list of labels for the layers, which are tar files
"""

# Produce layers in this order, as the app changes most often
layers = ["interpreter", "packages", "app"]

# Produce the manifest for a tar file of our py_binary, but don't tar it up yet, so we can split
# into fine-grained layers for better docker performance.
mtree_spec(
name = name + ".mf",
srcs = [binary],
)

native.genrule(
name = name + ".interpreter_tar_manifest",
srcs = [name + ".mf"],
outs = [name + ".interpreter_tar_manifest.spec"],
cmd = "grep '{}' $< >$@".format(PY_INTERPRETER_REGEX),
)

native.genrule(
name = name + ".packages_tar_manifest",
srcs = [name + ".mf"],
outs = [name + ".packages_tar_manifest.spec"],
cmd = "grep '{}' $< >$@".format(SITE_PACKAGES_REGEX),
)

# Any lines that didn't match one of the two grep above
native.genrule(
name = name + ".app_tar_manifest",
srcs = [name + ".mf"],
outs = [name + ".app_tar_manifest.spec"],
cmd = "grep -v '{}' $< | grep -v '{}' >$@".format(SITE_PACKAGES_REGEX, PY_INTERPRETER_REGEX),
)

result = []
for layer in layers:
layer_target = "{}.{}_layer".format(name, layer)
result.append(layer_target)
tar(
name = layer_target,
srcs = [binary],
mtree = "{}.{}_tar_manifest".format(name, layer),
)

return result
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

native.filegroup(
    name = name,
    srcs = results,
    ... tags, visibility, etc.
)

9 changes: 4 additions & 5 deletions py/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
def http_archive(name, **kwargs):
maybe(_http_archive, name = name, **kwargs)


# WARNING: any changes in this function may be BREAKING CHANGES for users
# because we'll fetch a dependency which may be different from one that
# they were previously fetching later in their WORKSPACE setup, and now
Expand All @@ -32,14 +31,14 @@ def rules_py_dependencies():

http_archive(
name = "aspect_bazel_lib",
sha256 = "5371d3143307e5222e3c33a575042f93647b4e0a7d6d837f87b6b751102d27ca",
strip_prefix = "bazel-lib-1.40.3",
url = "https://github.com/aspect-build/bazel-lib/archive/refs/tags/v1.40.3.tar.gz",
sha256 = "6d758a8f646ecee7a3e294fbe4386daafbe0e5966723009c290d493f227c390b",
strip_prefix = "bazel-lib-2.7.7",
url = "https://github.com/aspect-build/bazel-lib/releases/download/v2.7.7/bazel-lib-v2.7.7.tar.gz",
)

http_archive(
name = "rules_python",
sha256 = "c68bdc4fbec25de5b5493b8819cfc877c4ea299c0dcb15c244c5a00208cde311",
strip_prefix = "rules_python-0.31.0",
url = "https://github.com/bazelbuild/rules_python/releases/download/0.31.0/rules_python-0.31.0.tar.gz",
)
)
Loading