tools/defs.bzl (283 lines of code) (raw):
"""Utility macros for buildpacks."""
load("@rules_pkg//pkg:mappings.bzl", "pkg_mklink")
load("@rules_pkg//pkg:tar.bzl", "pkg_tar")
def buildpack(name, executables, prefix, version, api = "0.9", srcs = None, extension = "tgz", strip_prefix = ".", visibility = None):
"""Macro to create a single buildpack as a tgz or tar archive.
The result is a tar or tgz archive with a buildpack descriptor
(`buildpack.toml`) and interface scripts (bin/detect, bin/build).
As this is a macro, the actual target name for the buildpack is `name.extension`.
The builder.toml spec allows either tar or tgz archives.
Args:
name: the base name of the tar archive
srcs: list of other files to include
prefix: the language name or group used as a namespace in the buildpack ID
version: the version of the buildpack
api: the buildpacks API version
executables: list of labels of buildpack binaries
strip_prefix: by default preserves the paths of srcs
extension: tgz by default
visibility: the visibility
"""
if len(executables) != 1:
fail("You must provide exactly one buildpack executable")
pkg_mklink(
name = "_link_build" + name,
target = "main",
link_name = "bin/build",
)
pkg_mklink(
name = "_link_detect" + name,
target = "main",
link_name = "bin/detect",
)
_buildpack_descriptor(
name = name + ".descriptor",
api = api,
version = version,
prefix = prefix,
bp_name = name,
output = "buildpack.toml",
)
if not srcs:
srcs = []
pkg_tar(
name = name,
extension = extension,
srcs = [
name + ".descriptor",
"_link_build" + name,
"_link_detect" + name,
] + srcs,
files = {
executables[0]: "/bin/main",
},
strip_prefix = strip_prefix,
visibility = visibility,
)
def _buildpack_descriptor_impl(ctx):
ctx.actions.expand_template(
output = ctx.outputs.output,
substitutions = {
"${API}": ctx.attr.api,
"${VERSION}": ctx.attr.version,
"${ID}": "google.{prefix}.{name}".format(
prefix = ctx.attr.prefix,
name = ctx.attr.bp_name.replace("_", "-"),
),
"${NAME}": "{prefix} - {name}".format(
prefix = _pretty_prefix(ctx.attr.prefix),
name = ctx.attr.bp_name.replace("_", " ").title(),
),
},
template = ctx.file._template,
)
_buildpack_descriptor = rule(
implementation = _buildpack_descriptor_impl,
attrs = {
"api": attr.string(mandatory = True),
"version": attr.string(mandatory = True),
"bp_name": attr.string(mandatory = True),
"prefix": attr.string(mandatory = True),
"output": attr.output(mandatory = True),
"_template": attr.label(
default = ":buildpack.toml.template",
allow_single_file = True,
),
},
)
def _pretty_prefix(prefix):
"""Helper function to convert a buildpack prefix into a human readable name.
Args:
prefix: the namespace used in the buildpack id (eg dotnet, nodejs).
"""
if prefix == "dotnet":
return ".NET"
if prefix == "php":
return "PHP"
if prefix == "nodejs":
return "Node.js"
if prefix == "cpp":
return "C++"
return prefix.title()
def builder(
name,
image,
descriptor = "builder.toml",
buildpacks = None,
groups = None,
visibility = None,
builder_template = None,
stack = None):
"""Macro to create a set of targets for a builder with specified buildpacks.
`name` and `name.tar`:
Creates tar archive with a builder descriptor (`builder.toml`) and its
associated buildpacks. The buildpacks should either have unique names
or be assigned to different groups. The grouped buildpacks are placed
in directories named by the key. Both `buildpacks` and `groups` may
be used simultaneously.
`name.image` and `name.sha`:
Creates a builder image based on the source from `name.tar` using pack
and outputs the image SHA into the `name.sha` file. The builder will
be named `image`.
Args:
name: the base name of the tar archive
image: the name of the builder image
descriptor: path to the `builder.toml`
buildpacks: list of labels to buildpacks (tar or tgz archives)
groups: dict(name -> list of labels to buildpacks);
the buildpacks are grouped under a single-level directory named <key>
visibility: the visibility
builder_template: if builder.toml needs to be generated for input stack
stack: Either of google.gae.22 or google.gae.18 representing ubuntu-22 or ubuntu-18 stacks
"""
srcs = []
# Determine the builder descriptor source.
# If a builder template and stack are provided, generate a custom descriptor.
# Otherwise, use the default descriptor.
if builder_template and stack:
srcs.append(_generate_builder_descriptor(name, descriptor, builder_template, stack))
else:
srcs.append(descriptor)
srcs += buildpacks if buildpacks else []
deps = _package_buildpack_groups(name, groups) if groups else []
# `name` and `name.tar` rules.
pkg_tar(
name = name,
extension = "tar",
srcs = srcs,
deps = deps,
visibility = visibility,
)
# `name.image` and `name.sha` rules.
native.genrule(
name = name + ".image",
srcs = [name + ".tar"],
outs = [name + ".sha"],
local = 1,
tools = [
"//tools/checktools:main",
"//tools:create_builder",
],
cmd = """$(execpath {check_script}) && $(execpath {create_script}) {image} $(execpath {tar}) "{descriptor}" $@""".format(
image = image,
tar = name + ".tar",
descriptor = descriptor,
check_script = "//tools/checktools:main",
create_script = "//tools:create_builder",
),
)
def _generate_builder_descriptor(name, descriptor, builder_template, stack):
"""Generates a builder descriptor from a template for a specific stack."""
gae_stack = "google-gae-22" if stack == "google.gae.22" else "google-gae-18"
image_prefix = "gcr.io/gae-runtimes/buildpacks/stacks/{}/".format(gae_stack)
build_image = image_prefix + "build"
run_image = image_prefix + "run"
_builder_descriptor(
name = name + ".descriptor",
stack_id = stack,
stack_build_image = build_image,
stack_run_image = run_image,
template = builder_template,
output = name + "/" + descriptor,
)
return name + ".descriptor"
def _package_buildpack_groups(name, groups):
"""Packages buildpacks into groups."""
deps = []
for (k, v) in groups.items():
pkg_tar(name = name + "_" + k, srcs = v, package_dir = k)
deps.append(name + "_" + k)
return deps
def _builder_descriptor_impl(ctx):
ctx.actions.expand_template(
output = ctx.outputs.output,
substitutions = {
"${STACK_ID}": ctx.attr.stack_id,
"${STACK_BUILD_IMAGE}": ctx.attr.stack_build_image,
"${STACK_RUN_IMAGE}": ctx.attr.stack_run_image,
},
template = ctx.file.template,
)
_builder_descriptor = rule(
implementation = _builder_descriptor_impl,
attrs = {
"stack_id": attr.string(mandatory = True),
"stack_build_image": attr.string(mandatory = True),
"stack_run_image": attr.string(mandatory = True),
"template": attr.label(
default = ":buildpack.toml.template",
allow_single_file = True,
),
"output": attr.output(mandatory = True),
},
)
def buildpackage(name, buildpacks, descriptor = "buildpack.toml", visibility = None):
"""Macro to create a set of targets for a meta-buildpack containing the specified buildpacks.
The result is a meta-builpack packages as a ".cnb" file created via the `pack buildpack package`
command.
`name` and `name.cnb`:
Creates meta-buildpack persisted to disk as a ".cnb" file.
`name.package`:
Creates a package TOML file describing the contents of the meta-buildpack.
`name.tar` and `name.tar.tar`:
Creates source tarball for the meta-buildpacks that includes the specified buildpacks, the
package TOML, and the buildpack TOML.
Args:
name: the name of the buildpackage to create.
descriptor: path to the `buildpack.toml`
buildpacks: list of labels to buildpacks (tar or tgz archives)
visibility: the visibility
"""
files = {
descriptor: descriptor,
"package.toml": "package.toml",
}
manifest = '''[buildpack]
uri = "./"'''
for b in buildpacks:
# add the buildpack to the tarball fileset at the namespaced filepath
files[b] = _buildpack_filepath(b)
# add the buildpack to manifest at the namespaced filepath
manifest += '''
[[dependencies]]
uri = "./{tarname}"'''.format(tarname = _buildpack_filepath(b))
native.genrule(
name = name + ".package",
outs = ["package.toml"],
cmd = "echo '{manifest}' > $@".format(manifest = manifest),
)
pkg_tar(
name = name + ".tar",
extension = "tar",
files = files,
visibility = visibility,
)
# `name.image` and `name.sha` rules.
native.genrule(
name = name,
srcs = [name + ".tar"],
outs = [name + ".cnb"],
local = 1,
tools = [
"//tools/checktools:main",
"//tools:create_buildpackage",
],
cmd = """$(execpath {check_script}) && $(execpath {create_script}) $(execpath {tar}) $@""".format(
tar = name + ".tar",
check_script = "//tools/checktools:main",
create_script = "//tools:create_buildpackage",
),
)
def _buildpack_filepath(symbol):
"""Helper function to convert a symbol pointing to a buildpack into a filepath.
This prevents collisions by re-using the directory structure inside of the /cmd directory.
Args:
symbol: a build symbol of a buildpack to get a relative filepath for.
"""
paths = symbol.split("/cmd/")
if len(paths) != 2:
fail("Buildpack symbol was not in cmd/: " + symbol)
parts = paths[1].split(":")
if len(parts) != 2:
fail("Buildpack symbol was invalid: " + symbol)
return parts[0] + ".tgz"