Salesforce Spring Rules for Bazel

repository·main·Indexed 42 days ago

https://github.com/salesforce/rules_spring

Bazel rules for building and packaging Spring Boot applications into executable JAR files. Provides the `springboot` rule for production deployment, support for Spring Boot 3 launcher classes, and `deps_filter` for managing and excluding Java dependencies.

Tokens
17.3K
Snippets
50
Records
72
Agent score
53%

What's inside rules_spring

  1. Overview of Salesforce Spring Rules for Bazel

    main
    The rules_spring repository provides a Bazel extension to build and package Spring Boot applications as executable JAR files. This is the recommended method for deploying Spring Boot applications in production environments where the Bazel build system is not available.
  2. Use neverlink for compilation-only libraries

    main

    In Bazel, setting neverlink = True on a java_library specifies that the library should be used for compilation purposes but should not be included in the runtime classpath.

    This is useful when the library is provided by the runtime environment (e.g., tools.jar in a standard JDK or IDE APIs for plugins).

    Important Considerations:

    • Inlining: Setting neverlink = True does not prevent the Java compiler from inlining material (like static final constants of String or primitive types) into dependent compilation targets.
    • Compatibility: The preferred use case is when the runtime library is identical to the compilation library. If the runtime library differs from the compilation library, you must ensure they only differ in ways that the Java Language Specification (JLS) forbids compilers from inlining.
    java_library(
        name = "lib3_neverlink",
        neverlink = True,
        ...
    )
  3. How dependencyset handles deps vs runtime_deps

    main

    The dependencyset macro uses the name parameter to decide how to apply dependency filtering:

    1. Compile-time (deps): If the name is set to "deps" (or similar logic targeting compile-time), it applies exclusions to the compile classpath.
    2. Runtime (runtime_deps): If the name contains the string "runtime_deps", the macro treats the items as runtime dependencies and applies the appropriate filtering for the runtime classpath.

    This automation ensures that centralized security exclusions are applied consistently to both compile-time and runtime environments.

  4. Configure `deps_filter_disable_transitives` with different exclusion patterns

    main

    The behavior of the rule depends on which dependencies are included in the deps_to_exclude_transitives list:

    1. Exclude all transitives for all listed deps: If deps_to_exclude_transitives contains all items in deps, only the direct dependencies are included; their transitives are removed.
    2. Partial exclusion: If deps_to_exclude_transitives only contains a subset of deps, only those specific dependencies will have their transitives excluded. Other dependencies in deps will retain their transitive dependencies.
    3. No exclusion: If deps_to_exclude_transitives is omitted, all dependencies in deps and runtime_deps will include their transitive dependencies as normal.
    ### Example 1: Exclude all transitives for both deps
    deps_filter_disable_transitives(
        name = "filtered_deps_disable_transitives_case_A",
        deps = [
            "@maven//:org_springframework_spring_jdbc",
            "@maven//:org_springframework_spring_web",
        ],
        deps_to_exclude_transitives = [
            "@maven//:org_springframework_spring_jdbc",
            "@maven//:org_springframework_spring_web",
        ],
    )
    
    ### Example 2: Exclude transitives for only one dep
    deps_filter_disable_transitives(
        name = "filtered_deps_disable_transitives_case_A",
        deps = [
            "@maven//:org_springframework_spring_jdbc",
            "@maven//:org_springframework_spring_web",
        ],
        deps_to_exclude_transitives = [
            "@maven//:org_springframework_spring_web",
        ],
    )
    
    ### Example 3: No exclusions (standard behavior)
    deps_filter_disable_transitives(
        name = "filtered_deps_disable_transitives_case_A",
        deps = [
            "@maven//:org_springframework_spring_jdbc",
            "@maven//:org_springframework_spring_web",
        ],
    )
  5. Suppress duplicate classes via Classpath Ordering

    main

    If you cannot remove a dependency, you can influence which version of a duplicate class 'wins' by controlling the classpath order. In a Spring Boot executable jar, the order of entries determines which class is loaded first by the JVM.

    Method 1: BUILD file dependency order

    Spring Boot writes dependencies into the jar based on Bazel's depset order, which is influenced by the order in your deps list. Earlier entries in the deps list are generally loaded first.

    Note: If you encounter errors with very large dependency sets (command line length limits), you can disable this ordering behavior by setting deps_use_starlark_order = False in the springboot rule.

    To verify the order in your jar, use: jar -tvf {springboot_jar_name}.

    Method 2: Spring Boot Classpath Index

    You can provide a deps_index_file to the springboot rule to instruct the Spring Boot loader to load specific jars in a specific order.

    ⚠️ Caveat: This only works if you explode the executable jar and invoke the JarLauncher manually:

    jar -xf helloworld.jar
    java org.springframework.boot.loader.JarLauncher
    springboot(
        name = "helloworld",
        boot_app_class = "com.sample.SampleMain",
        java_library = ":helloworld_lib",
    
        # if you have conflicting classes in dependency jar files, you can define the order in which the jars are loaded
        deps_index_file = "helloworld_classpath.idx",
    )
  6. Use the `deps_filter` rule to manage Java dependencies

    main

    The deps_filter rule allows you to filter Java dependencies in Bazel projects. It is used to remove specific JARs or dependencies from the compile-time and runtime dependency graphs. This is particularly useful for:

    • Excluding dependencies with known vulnerabilities.
    • Resolving classpath conflicts (e.g., duplicate classes) by excluding unfavored versions.
    • Managing complex classpaths in Spring Boot applications.

    To use it, you define a deps_filter target and then reference that target in the deps or runtime_deps attribute of your java_library or other Java targets instead of the original dependency list.

    load("@rules_spring//springboot/deps_filter_rules_legacy:deps_filter.bzl", "deps_filter")
    
    deps_filter(
        name = "filtered_deps",
        deps = ["@maven//:some_dep"],
        deps_exclude_labels = ["@maven//:unwanted_dep"],
    )
    
    java_library(
        name = "my_library",
        deps = [":filtered_deps"],
    )
  7. How transitive dependency exclusion works in deps_filter

    main

    The exclude_transitives attribute controls how dependencies are removed:

    • exclude_transitives=False (Default): Only the direct dependencies specified in deps_exclude_labels are excluded. Their transitive dependencies are preserved in the graph.
    • exclude_transitives=True: Both the direct dependencies specified in deps_exclude_labels AND their transitive dependencies are removed.

    Important Limitation: exclude_transitives only applies to dependencies matched via deps_exclude_labels. It does not apply to dependencies matched via deps_exclude_paths.

  8. Understand the Jakarta migration path for Spring Boot 3

    main

    Migrating to Spring Boot 3 requires moving from javax to jakarta namespaces. This migration involves two distinct changes:

    1. GAV Migration: Changing the GroupId, ArtifactId, and Version (e.g., from javax.servlet:javax.servlet-api to jakarta.servlet:jakarta.servlet-api). This is a configuration-only change.
    2. Java Package Migration: Changing the actual code imports from javax.* to jakarta.*. This requires code-level changes.

    To ease this transition, you can use Transitional Jars. These are jars that use a jakarta GAV but still contain javax Java packaging. This allows you to perform the GAV migration first as a build metadata change before committing to the code-level package changes.

  9. Understand Bazel Run internals and scripts

    main

    When you run a springboot target, Bazel executes a 'wrapper script' that coordinates the launch. You can find these scripts in bazel-bin/ under your target's path. For a target //examples/demoapp:demoapp, the components are:

    • Wrapper script (bazel-bin/examples/demoapp/demoapp): A shell script (without a .sh extension) that coordinates the launch. It is generated from the *_bazelrun_script_template in springboot.bzl.
    • Env script (bazel-bin/examples/demoapp/demoapp_bazelrun_env.sh): Contains environment variables, including those from bazelrun_data and bazelrun_env_flag_list.
    • Launcher script (bazel-bin/examples/demoapp/demoapp.runfiles/_main/springboot/default_bazelrun_script.sh): The script that actually executes the java -jar command. This is the script you replace if using bazelrun_script.
    # Direct invocation of the wrapper script for troubleshooting
    ./bazel-bin/examples/demoapp/demoapp
  10. Note on Bazel Query limitations with deps_filter

    main
    The bazel query "deps(target)" command does not accurately reflect the filtered dependencies when using deps_filter. This is due to how Bazel processes dependency queries. To verify the actual dependencies used in a build, rely on the build output or inspection of the resulting JavaInfo provider rather than bazel query.
  11. Load Spring Rules into your Bazel workspace

    main

    To use the springboot rule, you must first load rules_spring into your Bazel workspace. You can do this using either the modern Bzlmod system or the legacy WORKSPACE method.

    Note: Do not use git_repository to point to the main branch, as breaking changes may be introduced at any time. Always use official releases.

    ### Bzlmod
    ```starlark
    bazel_dep(name = "rules_spring", version = "2.6.3")

    WORKSPACE (legacy)

    http_archive(
        name = "rules_spring",
        sha256 = "2d0805b4096db89b8e407ed0c243ce81c3d20f346e4c259885041d5eabc59436",
        urls = [
            "https://github.com/salesforce/rules_spring/releases/download/2.6.3/rules-spring-2.6.3.zip",
        ],
    )
  12. Run a build with full stamping

    main

    By default, the build uses limited stamping to optimize for Bazel remote build caching. This prevents Git data (branch, commit ID, build time) from being included in the stamp, allowing artifacts to be shared across machines.

    When building artifacts for deployment to shared environments (e.g., test or production), you should enable full stamping to include Git coordinates for troubleshooting.

    You can enable full stamping using one of two methods:

    1. Marker File: Create a file named full_stamp.txt in your workspace root (the directory containing your WORKSPACE file).
    2. Environment Variable: Set the IS_RELEASE_BUILD environment variable to true.
    # Method 1: Using a marker file
    $ touch full_stamp.txt
    $ bazel build //...
    
    # Method 2: Using an environment variable
    $ export IS_RELEASE_BUILD=true
    $ bazel build //...