Skip to content

Commit

Permalink
Merge pull request #3 from chrismgrayftsinc/bzlmod
Browse files Browse the repository at this point in the history
Add bzlmod support
  • Loading branch information
chrismgrayftsinc authored Aug 13, 2024
2 parents c5e7bb8 + b87d4cd commit fca50b9
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 81 deletions.
1 change: 0 additions & 1 deletion BUILD
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@

4 changes: 4 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module(name = "rules_spring_image", repo_name = "rules_spring_image", version = "1.0.0")

bazel_dep(name = "rules_oci", version = "1.7.5")
bazel_dep(name = "aspect_bazel_lib", version = "2.5.1")
254 changes: 174 additions & 80 deletions spring_image.bzl
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@io_bazel_rules_docker//container:container.bzl", "container_image", "container_layer", _container = "container")

def rules_spring_image_deps():
maybe(
http_archive,
name = "com_github_rules_spring",
sha256 = "4afceddd222bfd596f09591fd41f0800e57dd2d49e3fa0bda67f1b43149e8f3e",
url = "https://github.com/salesforce/rules_spring/releases/download/2.1.3/rules-spring-2.1.3.zip",
)
maybe(
http_archive,
name = "com_github_io_bazel_rules_docker",
sha256 = "59d5b42ac315e7eadffa944e86e90c2990110a1c8075f1cd145f487e999d22b3",
strip_prefix = "rules_docker-0.17.0",
urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.17.0/rules_docker-v0.17.0.tar.gz"],
)
load("@aspect_bazel_lib//lib:tar.bzl", "tar")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_image_rule")

def _depaggregator_rule_impl(ctx):
# magical incantation for getting upstream transitive closure of java deps
Expand Down Expand Up @@ -53,8 +37,8 @@ def _dependencies_copier_rule_impl(ctx):
if path.find("spring-boot-loader") >= 0 or path.find("spring_boot_loader") >= 0:
continue
else:
if path.find("external") >= 0 and path.find("maven2", path.find("external")) >= 0:
libdestdir = path[path.find("maven2") + len("maven2"):]
if path.find("external") >= 0 and path.find("maven~maven", path.find("external")) >= 0:
libdestdir = path[path.find("maven~maven") + len("maven~maven"):]
elif path.find("external") >= 0 and path.find("public", path.find("external")) >= 0:
libdestdir = path[path.find("public") + len("public"):]
else:
Expand Down Expand Up @@ -86,10 +70,19 @@ _dependencies_copier_rule = rule(
def tar_jar(ctx, file, path, out):
java_runtime = ctx.attr._jdk[java_common.JavaRuntimeInfo]
jar_path = "%s/bin/jar" % java_runtime.java_home

file_list_path = "loader_paths"
file_list = ctx.actions.declare_file(file_list_path)

ctx.actions.run_shell(
inputs = ctx.files._jdk + [file],
outputs = [file_list],
command = "{jar} tf {path} | grep -v '/$' | sort | uniq > {file_list_path}".format(jar = jar_path, path = path, file_list_path = file_list.path),
)
ctx.actions.run_shell(
inputs = ctx.files._jdk + [file, file_list],
outputs = [out],
command = "%s xf %s && %s tf %s | tar cf %s -T -" % (jar_path, path, jar_path, path, out.path),
command = "{jar} xf {path} && cat {file_list_path} | tar cf {out} -T -".format(jar = jar_path, path = path, out = out.path, file_list_path = file_list.path),
)

def _loader_copier_rule_impl(ctx):
Expand Down Expand Up @@ -132,12 +125,20 @@ def tar_jars(ctx, files, out):
command = "touch {file}; for i in {all_paths}; do {jar} xf $i && if [ -s META-INF/spring.components ]; then cat META-INF/spring.components >> {file}; fi; done".format(file = spring_components_file.path, jar = jar_path, all_paths = " ".join(paths)),
)

all_paths_file_path = "application_paths"
all_paths_file = ctx.actions.declare_file(all_paths_file_path)
ctx.actions.run_shell(
inputs = ctx.files._jdk + files,
outputs = [all_paths_file],
command = "for i in {all_paths}; do {jar} tf $i | grep -v '/$' | grep -v spring.components | grep -v MANIFEST.MF >> {out}.tmp; done; sort {out}.tmp | uniq > {out}".format(out = all_paths_file.path, all_paths = " ".join(paths), jar = jar_path),
)

ctx.actions.run_shell(
inputs = ctx.files._jdk + files + [spring_components_file],
inputs = ctx.files._jdk + files + [spring_components_file, all_paths_file],
outputs = [out],
# Create an empty tarball, then extract all the jars and append the contents into it.
# TODO: get rid of the hardcoded bazel-out path in the first transform.
command = 'tar cf {out} -T /dev/null && if [ -s {scf} ]; then tar rhf {out} --transform "s,bazel-out/.*/bin/,BOOT-INF/classes/META-INF/," {scf}; fi && for i in {all_paths}; do {jar} xf $i && {jar} tf $i | tar rf {out} --transform "s,^,BOOT-INF/classes/," -T -; done'.format(out = out.path, all_paths = " ".join(paths), jar = jar_path, scf = spring_components_file.path),
command = 'tar cf {out} -T /dev/null && if [ -s {scf} ]; then tar rhf {out} --transform "s,bazel-out/.*/bin/,BOOT-INF/classes/META-INF/," {scf}; fi && for i in {all_paths}; do {jar} xf $i; done && cat {all_paths_file} | tar rf {out} --transform "s,^,BOOT-INF/classes/," -T -'.format(out = out.path, all_paths = " ".join(paths), jar = jar_path, scf = spring_components_file.path, all_paths_file = all_paths_file.path),
)

def _application_copier_rule_impl(ctx):
Expand All @@ -149,7 +150,7 @@ def _application_copier_rule_impl(ctx):
path = file.path
if path.find("spring-boot-loader") >= 0 or path.find("spring_boot_loader") >= 0:
continue
elif path.find("external") >= 0 and path.find("maven2", path.find("external")) >= 0:
elif path.find("external") >= 0 and path.find("maven~maven", path.find("external")) >= 0:
continue
elif path.find("external") >= 0 and path.find("public", path.find("external")) >= 0:
continue
Expand Down Expand Up @@ -190,7 +191,7 @@ def _gen_layers_idx_rule_impl(ctx):
if path.find("spring-boot-loader") >= 0 or path.find("spring_boot_loader") >= 0:
loader_found = True
continue
elif path.find("external") >= 0 and path.find("maven2", path.find("external")) >= 0:
elif path.find("external") >= 0 and path.find("maven~maven", path.find("external")) >= 0:
maven_dependencies_found = True
continue
elif path.find("external") >= 0 and path.find("public", path.find("external")) >= 0:
Expand Down Expand Up @@ -231,43 +232,136 @@ _gen_layers_idx_rule = rule(
},
)

def _spring_image_rule_impl(ctx):
return _container.image.implementation(
ctx = ctx,
name = ctx.attr.name,
cmd = ctx.attr.cmd,
layers = [ctx.attr.dependencies_layer, ctx.attr.loader_layer, ctx.attr.application_layer] + ctx.attr.extra_layers,
def _gen_manifest_rule_impl(ctx):
java_runtime = ctx.attr._jdk[java_common.JavaRuntimeInfo]
java_home = java_runtime.java_home
out = ctx.actions.declare_file(ctx.attr.out)
mnemonic = "WriteManifest"

write_manifest_string = """
#!/bin/bash
#
# Copyright (c) 2017-2021, salesforce.com, inc.
# All rights reserved.
# Licensed under the BSD 3-Clause license.
# For full license text, see LICENSE.txt file in the repo root at https://github.com/salesforce/rules_spring or https://opensource.org/licenses/BSD-3-Clause
#
set -e
mainclass={{mainclass}}
springbootlauncherclass={{springbootlauncherclass}}
manifestfile={{manifestfile}}
javabase={{javabase}}
found_spring_jar=0
# Looking for the springboot jar injected by springboot.bzl and extracting the version
for var in "$@"
do
# determine the version of spring boot
# this little area of the rule has had problems in the past; reconsider whether doing
# this is worth it; and certainly carefully review prior issues here before making changes
# Issues: #130, #119, #111
$javabase/bin/jar xf $var META-INF/MANIFEST.MF || continue
spring_version=$(grep 'Implementation-Version' META-INF/MANIFEST.MF | cut -d : -f2 | tr -d '[:space:]')
rm -rf META-INF
# we do want to validate that the deps include spring boot, and this is a
# convenient place to do it, but it is a little misplaced as we are
# generating the manifest in this script
found_spring_jar=1
break
done
if test $found_spring_jar -ne 1 ; then
echo "ERROR: //springboot/write_manifest.sh could not find the spring-boot jar"
exit 1
fi
#get the java -version details
# todo this isn't the best value to use. it is the version that will be used by the jar tool
# to package the boot jar but not for compiling the code (java_toolchain)
java_string=$($javabase/bin/java -version 2>&1)
#get the first line of the version details and get the version
java_version=$(echo "$java_string" | head -n1 | cut -d ' ' -f 3 | rev | cut -c2- | rev | cut -c2- )
mkdir -p $(dirname $manifestfile)
echo "Manifest-Version: 1.0" > $manifestfile
echo "Created-By: Bazel" >> $manifestfile
echo "Built-By: Bazel" >> $manifestfile
echo "Main-Class: $springbootlauncherclass" >> $manifestfile
echo "Spring-Boot-Classes: BOOT-INF/classes/" >> $manifestfile
echo "Spring-Boot-Lib: BOOT-INF/lib/" >> $manifestfile
echo "Spring-Boot-Version: $spring_version" >> $manifestfile
echo "Build-Jdk: $java_version" >> $manifestfile
echo "Start-Class: $mainclass" >> $manifestfile
"""

write_manifest_sh = ctx.actions.declare_file("write_manifest.sh")
write_manifest_tpl_sh = ctx.actions.declare_file("write_manifest.tpl.sh")
ctx.actions.write(
output = write_manifest_tpl_sh,
content = write_manifest_string,
)

ctx.actions.expand_template(
output = write_manifest_sh,
template = write_manifest_tpl_sh,
is_executable = True,
substitutions = {
"{{mainclass}}": ctx.attr.boot_app_class,
"{{springbootlauncherclass}}": "org.springframework.boot.loader.JarLauncher",
"{{manifestfile}}": out.path,
"{{javabase}}": java_home,
},
)

ctx.actions.run(
executable = write_manifest_sh,
outputs = [out],
inputs = [
dep
for src in ctx.attr.srcs
for dep in src.files.to_list()
],
arguments = [
dep.path
for src in ctx.attr.srcs
for dep in src.files.to_list()
if dep.path.find("spring-boot") >= 0 or dep.path.find("spring_boot") >= 0
],
tools = [java_runtime.files],
)
return [
DefaultInfo(
files = depset([out]),
runfiles = ctx.runfiles(files = [out]),
),
]

_spring_image_rule = rule(
implementation = _spring_image_rule_impl,
executable = True,
outputs = _container.image.outputs,
attrs = dict(
_container.image.attrs,
base = attr.label(),
ports = attr.string_list(),
app_compile_rule = attr.label(),
extra_layers = attr.label_list(),
java_library = attr.string(),
boot_app_class = attr.string(),
deps = attr.label_list(),
deps_exclude = attr.label_list(),
application_layer = attr.label(),
loader_layer = attr.label(),
dependencies_layer = attr.label(),
),
toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"],
_gen_manifest_rule = rule(
implementation = _gen_manifest_rule_impl,
attrs = {
"srcs": attr.label_list(),
"out": attr.string(),
"_jdk": attr.label(
default = Label("@bazel_tools//tools/jdk:current_java_runtime"),
providers = [java_common.JavaRuntimeInfo],
),
"boot_app_class": attr.string(),
},
)

def spring_image(
name,
java_library,
boot_app_class,
cmd,
base,
ports,
extra_layers = None,
cmd = None,
entrypoint = None,
extra_layers = [],
deps = None,
deps_exclude = None,
tags = None):
Expand All @@ -293,14 +387,11 @@ def spring_image(
)

genmanifest_out = "META-INF/MANIFEST.MF"
native.genrule(
_gen_manifest_rule(
name = genmanifest_rule,
srcs = [":" + dep_aggregator_rule],
cmd = "$(location @com_github_rules_spring//springboot:write_manifest.sh) " + boot_app_class + " $@ $(JAVABASE) $(SRCS)",
tools = ["@com_github_rules_spring//springboot:write_manifest.sh"],
outs = [genmanifest_out],
tags = tags,
toolchains = ["@bazel_tools//tools/jdk:current_host_java_runtime"],
out = genmanifest_out,
boot_app_class = boot_app_class,
)

# Create layers and write layers.idx
Expand All @@ -309,22 +400,16 @@ def spring_image(
deps = [":" + dep_aggregator_rule],
)

container_layer(
tar(
name = "dependencies",
data_path = "BOOT-INF/lib/",
files = [":" + gen_dependencies_rule],
srcs = [":" + gen_dependencies_rule],
)

_loader_copier_rule(
name = gen_loader_rule,
deps = [":" + dep_aggregator_rule],
)

container_layer(
name = "spring-boot-loader",
tars = [":" + gen_loader_rule],
)

_application_copier_rule(
name = gen_application_rule,
deps = [":" + dep_aggregator_rule],
Expand All @@ -335,21 +420,30 @@ def spring_image(
deps = [":" + dep_aggregator_rule],
)

container_layer(
name = "application",
tars = [":" + gen_application_rule],
data_path = ".",
files = [":" + genmanifest_rule, ":" + gen_layers_idx_rule],
tar(
name = "layers_index",
srcs = [
":" + gen_layers_idx_rule,
],
)
_spring_image_rule(
name = name,
app_compile_rule = java_library,
boot_app_class = boot_app_class,

tar(
name = "manifest",
srcs = [
":" + genmanifest_rule,
],
)

oci_image(
name = name + "-image",
cmd = cmd,
entrypoint = entrypoint,
tars = [
":dependencies",
":layers_index",
":" + gen_loader_rule,
":manifest",
":" + gen_application_rule,
] + extra_layers,
base = base,
ports = ports,
extra_layers = extra_layers,
application_layer = ":application",
loader_layer = ":spring-boot-loader",
dependencies_layer = ":dependencies",
)

0 comments on commit fca50b9

Please sign in to comment.