Skip to content

Commit

Permalink
getting 'bazel-springboot-rule' working
Browse files Browse the repository at this point in the history
  • Loading branch information
thundergolfer-two committed Jan 4, 2020
1 parent 8c5af99 commit dc7d2c6
Show file tree
Hide file tree
Showing 12 changed files with 1,001 additions and 7 deletions.
1 change: 1 addition & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ maven_install(
"junit:junit:4.12",
"org.hamcrest:hamcrest-library:1.3",
"org.springframework.boot:spring-boot-autoconfigure:2.1.3.RELEASE",
"org.springframework.boot:spring-boot-loader:2.1.3.RELEASE",
"org.springframework.boot:spring-boot-test-autoconfigure:2.1.3.RELEASE",
"org.springframework.boot:spring-boot-test:2.1.3.RELEASE",
"org.springframework.boot:spring-boot:2.1.3.RELEASE",
Expand Down
34 changes: 27 additions & 7 deletions store-api/src/main/java/com/book/store/api/BUILD
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
load("//tools/springboot:springboot.bzl", "springboot")

#springboot_deps = [
# "//tools/springboot/import_bundles:springboot_required_deps",
# "//tools/springboot/import_bundles:springboot_jetty_starter_deps",
# "//tools/springboot/import_bundles:springboot_web_starter_deps",
#]

DEPS = [
"@maven//:org_springframework_boot_spring_boot",
"@maven//:org_springframework_boot_spring_boot_loader",
"@maven//:org_springframework_boot_spring_boot_autoconfigure",
"@maven//:org_springframework_boot_spring_boot_starter_web",
"@maven//:org_springframework_spring_context",
"@maven//:org_springframework_spring_web",
]

java_library(
name = "lib",
srcs = glob(["*.java"]),
visibility = ["//src/test:__subpackages__"],
deps = [
"@maven//:org_springframework_boot_spring_boot",
"@maven//:org_springframework_boot_spring_boot_autoconfigure",
"@maven//:org_springframework_boot_spring_boot_starter_web",
"@maven//:org_springframework_spring_context",
"@maven//:org_springframework_spring_web",
],
deps = DEPS,
)

java_binary(
Expand All @@ -17,4 +28,13 @@ java_binary(
runtime_deps = [
":lib",
],
)

# use the springboot rule to build the app as a Spring Boot executable jar
springboot(
name = "deployable",
boot_app_class = "com.book.store.api.Application",
java_library = ":lib",
# deps = springboot_deps,
deps = DEPS,
)
13 changes: 13 additions & 0 deletions tools/springboot/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#
# Copyright (c) 2017-9, 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 or https://opensource.org/licenses/BSD-3-Clause
#

# Spring Boot Packager
# (implemented by Salesforce)

# See the README.md file for detailed usage instructions.

exports_files(["springboot.bzl", "springboot_pkg.sh", "verify_conflict.py", "write_gitinfo_properties.sh", "write_manifest.sh", "whitelist.txt"])
114 changes: 114 additions & 0 deletions tools/springboot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
## SpringBoot Rule

This implements a Bazel rule for packaging a Spring Boot application as an executable jar file.
The output of this rule is a jar file that can be copied to production environments and run.

## How to Use:

### Create your BUILD file

This is a *BUILD* file code snippet of how to invoke the rule:

```
# load our Spring Boot rule
load("//tools/springboot:springboot.bzl", "springboot",)
# create our deps list for Spring Boot, we have some convenience targets for this
springboot_deps = [
"//tools/springboot/import_bundles:springboot_required_deps",
"//tools/springboot/import_bundles:springboot_jetty_starter_deps",
"//tools/springboot/import_bundles:springboot_web_starter_deps",
]
# this Java library contains your service code
java_library(
name = 'helloworld_lib',
srcs = glob(["src/main/java/**/*.java"]),
resources = glob(["src/main/resources/**"]),
deps = springboot_deps,
)
# use the springboot rule to build the app as a Spring Boot executable jar
springboot(
name = "helloworld",
boot_app_class = "com.sample.SampleMain",
java_library = ":helloworld_lib",
deps = springboot_deps,
)
```

The *springboot* rule properties are as follows:

- name: name of your application; the convention is to use the same name as the enclosing folder (i.e. the Bazel package name)
- boot_app_class: the classname (java package+type) of the @SpringBootApplication class in your app
- java_library: the library containing your service code
- deps: list of jar file dependencies to add (these get packages as *BOOT-INF/lib* inside the executable jar)

### Convenience Import Bundles

The [//tools/springboot/import_bundles](import_bundles) package contains some useful bundles of imports.
There are bundles for the Spring Boot framework, as well as bundles for the various starters.

Since Spring Boot apps all need similar groups of dependencies, prefer to create/curate those import bundles if a
dependency is coming as a transitive for a Spring Boot class.

### Manage External Dependencies in your WORKSPACE

This repository has an example [WORKSPACE](../../external_deps.bzl) file that lists necessary and some optional Spring Boot dependencies.
These will come from a Nexus/Artifactory repository, or Maven Central.
Because the version of each dependency needs to be explicitly defined, it is left for you to review and add to your *WORKSPACE* file.

## Build and Run

After installing the rule into your workspace at *tools/springboot*, you are ready to build.
Add the rule invocation to your Spring Boot application *BUILD* file as shown above.
You will then need to follow an iterative process, adding external dependencies to your *BUILD* and *WORKSPACE* files until it builds and runs.

The build will run and create an executable jar file in the *bazel-bin* directory.
Find it in the output directories, and then run it:

```
bazel run //samples/helloworld
```

In production environments, you will likely not have Bazel installed nor the Bazel workspace files.
This is the primary use case for the executable jar file.
Run the jar file locally using *java* like so:

```
java -jar bazel-bin/samples/helloworld/helloworld.jar
```

The executable jar file is ready to be copied to your production environment.

## In Depth

To understand how this rule works, start by reading the [springboot.bzl file](springboot.bzl).

### Build Stamping of the Spring Boot jar

Spring Boot has a nice feature that can display Git coordinates for your built service in the
[/actuator/info](https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-endpoints) webadmin endpoint.
If you are interested in this feature, it is supported by this *springboot* rule.
However, to avoid breaking Bazel remote caching, we generally have this feature disabled for most builds.
See the [//tools/buildstamp](../buildstamp) package for more details.

### Duplicate Classes Detection

We find that Spring Boot jars aggregate a great number of dependency jars, many from outside our Bazel
build (external Maven-built jars).
We have had cases where multiple jars brought in the same class, but at different versions.
These are difficult to diagnose.

There is a feature on the *springboot* rule that will fail the build if duplicate classes are detected:

```
# use the springboot rule to build the app as a Spring Boot executable jar
springboot(
name = "helloworld",
boot_app_class = "com.sample.SampleMain",
java_library = ":helloworld_lib",
deps = springboot_deps,
fail_on_duplicate_classes = True,
)
```
97 changes: 97 additions & 0 deletions tools/springboot/import_bundles/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#
# Copyright (c) 2017-9, 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 or https://opensource.org/licenses/BSD-3-Clause
#

# List of standard imports bundles that tend to be used together in Spring Boot.
# Please create/curate new bundles and contribute back to the community. Thanks!


java_import(
name = 'springboot_required_deps',
jars = [],
exports = [
"@javax_servlet_javax_servlet_api//jar",
"@org_springframework_spring_aop//jar",
"@org_springframework_spring_beans//jar",
"@org_springframework_spring_context//jar",
"@org_springframework_spring_core//jar",
"@org_springframework_spring_expression//jar",
"@org_springframework_spring_web//jar",
"@org_springframework_boot_spring_boot_actuator//jar",
"@org_springframework_boot_spring_boot_actuator_autoconfigure//jar",
"@org_springframework_boot_spring_boot//jar",
"@org_springframework_boot_spring_boot_autoconfigure//jar",
"@org_springframework_boot_spring_boot_loader//jar",
"@org_springframework_boot_spring_boot_starter//jar",
"@org_springframework_boot_spring_boot_starter_logging//jar",
"@javax_annotation_javax_annotation_api//jar",
],
visibility = ["//visibility:public"],
)

java_import(
name = 'springboot_jetty_starter_deps',
jars = [],
exports = [
"@org_springframework_boot_spring_boot_starter_jetty//jar",
"@org_eclipse_jetty_jetty_http//jar",
"@org_eclipse_jetty_jetty_io//jar",
"@org_eclipse_jetty_jetty_security//jar",
"@org_eclipse_jetty_jetty_server//jar",
"@org_eclipse_jetty_jetty_servlet//jar",
"@org_eclipse_jetty_jetty_util//jar",
"@org_eclipse_jetty_jetty_webapp//jar",
],
visibility = ["//visibility:public"],
)

java_import(
name = 'springboot_web_starter_deps',
jars = [],
exports = [
":springboot_logging_deps",
":springboot_jackson_deps",
"@org_springframework_boot_spring_boot_starter_web//jar",
"@org_springframework_spring_web//jar",
"@org_springframework_spring_webmvc//jar",
"@org_eclipse_jetty_jetty_server//jar",
"@org_eclipse_jetty_jetty_servlet//jar",
"@org_eclipse_jetty_jetty_util//jar",
"@org_eclipse_jetty_jetty_webapp//jar",
],
visibility = ["//visibility:public"],
)

java_import(
name = 'springboot_logging_deps',
jars = [],
exports = [
"@ch_qos_logback_logback_classic//jar",
"@ch_qos_logback_logback_core//jar",
"@commons_logging_commons_logging//jar",
"@org_slf4j_slf4j_api//jar",
"@org_apache_logging_log4j_log4j_to_slf4j//jar",
"@org_apache_logging_log4j_log4j_api//jar",
"@org_slf4j_jul_to_slf4j//jar",
"@org_jboss_logging_jboss_logging//jar",
"@com_fasterxml_classmate//jar",
],
visibility = ["//visibility:public"],
)

java_import(
name = 'springboot_jackson_deps',
jars = [],
exports = [
"@com_fasterxml_jackson_core_jackson_databind//jar",
"@com_fasterxml_jackson_core_jackson_annotations//jar",
"@com_fasterxml_jackson_core_jackson_core//jar",
"@com_fasterxml_jackson_datatype_jackson_datatype_jdk8//jar",
"@com_fasterxml_jackson_datatype_jackson_datatype_jsr310//jar",
"@com_fasterxml_jackson_module_jackson_module_parameter_names//jar",
],
visibility = ["//visibility:public"],
)
37 changes: 37 additions & 0 deletions tools/springboot/import_bundles/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
## Import Bundles for Spring Boot Starters

This is a scheme for providing convenience bundles of dependencies for each
open source Spring Boot starter.
The intent of this package is to provide rule users with a single dep target for each
Spring Boot starter you include, and one dep target for the base Spring Boot framework.

In the following example, our dep list for the application includes the Spring Boot framework
and two starters:

```
springboot_deps = [
"//tools/springboot/import_bundles:springboot_required_deps",
"//tools/springboot/import_bundles:springboot_jetty_starter_deps",
"//tools/springboot/import_bundles:springboot_web_starter_deps",
]
```

### Open Source Starters

There are about 50 open source starters supported by the Spring Boot team, and others in the wild
supported by other teams.

- [Official Starters](https://github.com/spring-projects/spring-boot/tree/master/spring-boot-project/spring-boot-starters)

At this time, only a few starters are populated in the package.
It is an open work item to improve that situation.

### Improving this Feature

For reasons explained in the issue linked below, internally at Salesforce this is not a complex problem.
We have some constraints that eliminate much of the complexity.
Solving this correctly in the public open source repository will take some effort.

Review the details in this issue:

- [Implement a stronger dependency bundling system for Spring Boot starters](https://github.com/salesforce/bazel-springboot-rule/issues/4)

0 comments on commit dc7d2c6

Please sign in to comment.