diff --git a/BUILD b/BUILD index 8b13789..e69de29 100644 --- a/BUILD +++ b/BUILD @@ -1 +0,0 @@ - diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..9fd5001 --- /dev/null +++ b/MODULE.bazel @@ -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") diff --git a/spring_image.bzl b/spring_image.bzl index 12755bf..d33e6f7 100644 --- a/spring_image.bzl +++ b/spring_image.bzl @@ -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 @@ -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: @@ -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): @@ -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): @@ -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 @@ -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: @@ -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): @@ -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 @@ -309,10 +400,9 @@ 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( @@ -320,11 +410,6 @@ def spring_image( 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], @@ -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", )