I'd like to migrate away from --define
flags and to build settings per: https://docs.bazel.build/versions/5.0.0/skylark/config.html
Here's the rule to which I'd like to pass command line values.
_impl
were public, it seems like I might be able to wrap it with my own implementation that passes it the flags.I'm somewhat new to Bazel and still figuring out the right way to conceptualize this stuff. Any guidance is appreciated!
backend/BUILD.bazel:
load("@io_bazel_rules_docker//container:container.bzl", "container_image", "container_push")
# container_image :run_server definition
container_push(
name = "push_server",
format = "Docker",
image = ":run_server",
registry = "gcr.io",
repository = "$(PROJECT_ID)/chat/server",
tag = "$(CONTAINER_TAG)",
)
Then I run:
bazel run \
--platforms=@io_bazel_rules_go//go/toolchain:linux_amd64 \
--define PROJECT_ID=$(gcloud config get-value project) \
--define CONTAINER_TAG=some_feature_branch \
-- //backend:push_server
A few variations of:
load("//backend:rules.bzl", "gcr_container_push")
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
load("@io_bazel_rules_docker//container:container.bzl", "container_image")
string_flag(
name = "container_tag",
build_setting_default = "latest",
visibility = ["//visibility:public"],
)
string_flag(
name = "project_id",
build_setting_default = "",
visibility = ["//visibility:public"],
)
# container_image :run_server definition
gcr_container_push(
name = "push_server",
image = ":run_server",
path = "chat/server",
)
backend/rules.bzl:
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load("@bazel_skylib//lib:paths.bzl", "paths")
load("@io_bazel_rules_docker//container:container.bzl", "container_push")
def _gcr_container_push_impl(ctx):
project_id = ctx.attr._project_id[BuildSettingInfo].value
if len(project_id) == 0:
fail("Please provide a GCP project ID via --//backend:project_id=<PROJECT ID>.")
container_push(
name = ctx.label.name,
format = "Docker",
image = ctx.attr.image,
registry = "gcr.io",
repository = paths.join(project_id, ctx.attr.path),
tag = ctx.attr._container_tag[BuildSettingInfo].value,
)
_gcr_container_push_attrs = {
"image": attr.label(
allow_single_file = [".tar"],
mandatory = True,
doc = "The label of the image to push.",
),
"path": attr.string(
mandatory = True,
doc = "The name of the image within the repository. Ex. gcr.io/project_id/<PATH>:tag.",
),
"_container_tag": attr.label(default = Label("//backend:container_tag")),
"_project_id": attr.label(default = Label("//backend:project_id")),
}
gcr_container_push = rule(
implementation = _gcr_container_push_impl,
attrs = _gcr_container_push_attrs,
executable = True,
)
Then I run:
bazel run \
--platforms=@io_bazel_rules_go//go/toolchain:linux_amd64 \
--//backend:project_id=ggx-prototype \
-- //backend:push_server
Which returns:
Error in container_push_: 'container_push_' can only be called during the loading phase
rules_docker
has attrs like repository_file
and tag_file
for exactly this kind of thing. You can generate those files however you want, including a custom rule that uses your user-defined flags. I'd do it like this:
def gcr_container_push(name, image, path, **kwargs):
if 'tag' in kwargs or 'repository' in kwargs:
fail('Not allowed to set these')
_gcr_container_repository(
name = name + '_repository',
visibility = ['//visibility:private'],
path = path,
)
_gcr_container_tag(
name = name + '_tag',
visibility = ['//visibility:private'],
path = path,
)
container_push(
name = name,
format = 'Docker',
image = image,
registry = 'gcr.io',
repository = '',
repository_file = ':%s_repository' % name,
tag_file = ':%s_tag' % name,
**kwargs
)
def _gcr_container_repository_impl(ctx):
project_id = ctx.attr._project_id[BuildSettingInfo].value
if len(project_id) == 0:
fail("Please provide a GCP project ID via --//backend:project_id=<PROJECT ID>.")
output = ctx.actions.declare_file(ctx.label.name + '_file')
ctx.actions.write(output, paths.join(project_id, ctx.attr.path))
return [DefaultInfo(files = depset([output]))]
_gcr_container_repository = rule(
impl = _gcr_container_repository_impl,
attrs = {
"path": attr.string(mandatory = True),
"_project_id": attr.label(default = Label("//backend:project_id")),
},
)
def _gcr_container_tag_impl(ctx):
output = ctx.actions.declare_file(ctx.label.name + '_file')
ctx.actions.write(output, ctx.attr._container_tag[BuildSettingInfo].value)
return [DefaultInfo(files = depset([output]))]
_gcr_container_tag = rule(
impl = _gcr_container_tag_impl,
attrs = {
"path": attr.string(mandatory = True),
"_container_tag": attr.label(default = Label("//backend:container_tag")),
},
)
Your attempt is mixing a rule and a macro. Rules have attrs and an _impl vs macros can use other rules. My approach uses custom rules to generate the files, and a macro to tie those rules to container_push
.
The general answer to your question is that this requires modifying the rule to perform new kinds of substitutions based on a user-defined flag. I can see some kind of --@rules_docker//flags:docker_info=MY_PROJECT=foo
configured with allow_multiple = True
that would get substituted, but it definitely requires rule modifications. Wrapping the _impl is going to be tricky, because you have to reach in and change some of the actions.