diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 41c79ea9..6ff8d263 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -1,70 +1,102 @@ --- build_targets: &build_targets - "//..." - - "@remote_java_tools//java_tools/..." - # TODO: Look into broken targets in //toolchains + # can't build some analysis tests on older Bazel versions + - "-//test/java/..." + # build java_tools from source + - "@remote_java_tools//:ijar_cc_binary" + - "@remote_java_tools//:one_version_cc_bin" + - "@remote_java_tools//:proguard" + - "@remote_java_tools//:singlejar_cc_bin" + # can't build @remote_java_tools_X repos for other platforms - "-//toolchains/..." - - "-//test/repo/..." + - "-//java/docs/..." -build_targets_bzlmod: &build_targets_bzlmod +build_targets_integration: &build_targets_integration - "//..." - - "@remote_java_tools//java_tools/..." - - "-//toolchains/..." - # TODO(pcloudy): pkg_tar doesn't work with Bzlmod due to https://github.com/bazelbuild/bazel/issues/14259 - # Enable once the issue is fixed. - - "-//distro/..." - - "-//test/repo/..." + - "//:bin_deploy.jar" test_targets: &test_targets - "//test/..." +test_target_integration: &test_target_integration + - "//:MyTest" + +flags_workspace_integration: &flags_workspace_integration + - "--noenable_bzlmod" + - "--enable_workspace" + buildifier: latest +matrix: + all_platforms: ["rockylinux8_arm64", "ubuntu2004", "macos", "macos_arm64", "windows"] + bazel: ["7.x", "8.x", "9.x", "last_green"] + modern_bazel: ["9.x", "last_green", "rolling"] # Fully supported Bazel versions + tasks: - ubuntu2004: +# Bazel 9+ + build_and_test: + name: "Bazel {modern_bazel}" + bazel: ${{ modern_bazel }} + platform: ${{ all_platforms }} build_targets: *build_targets test_targets: *test_targets - ubuntu2004_integration: - name: "Integration Tests" - platform: ubuntu2004 - working_directory: "test/repo" - build_targets: - - "//..." - - "//:bin_deploy.jar" - macos: - build_targets: *build_targets - windows: - build_targets: *build_targets - ubuntu2004_head: - name: "Bazel@HEAD" - bazel: last_green - platform: ubuntu2004 +# Bazel 8.x + build_and_test_bazel8: + name: "Bazel 8.x" + bazel: "8.x" + platform: ${{ all_platforms }} build_targets: *build_targets - macos_head: - name: "Bazel@HEAD" - bazel: last_green - platform: macos - build_targets: *build_targets - windows_head: - name: "Bazel@HEAD" - bazel: last_green - platform: windows + test_targets: *test_targets + test_flags: + - "--test_tag_filters=-min_bazel_9" +# Bazel 7.x + build_and_test_bazel7: + name: "Bazel 7.x" + bazel: "7.x" + platform: ${{ all_platforms }} build_targets: *build_targets - ubuntu2004_bzlmod: - name: "bzlmod" - platform: ubuntu2004 - build_flags: - - "--config=bzlmod" - build_targets: *build_targets_bzlmod - macos_bzlmod: - name: "bzlmod" - platform: macos - build_flags: - - "--config=bzlmod" - build_targets: *build_targets_bzlmod - windows_bzlmod: - name: "bzlmod" - platform: windows - build_flags: - - "--config=bzlmod" - build_targets: *build_targets_bzlmod \ No newline at end of file + test_targets: *test_targets + test_flags: + - "--test_tag_filters=-min_bazel_8,-min_bazel_9" + +# Integration tests + integration_build_and_test: + name: "Integration w/ Bazel {bazel}" + bazel: ${{ bazel }} + platform: ${{ all_platforms }} + working_directory: "test/repo" + shell_commands: + - sh setup.sh + batch_commands: + - setup.bat + build_targets: *build_targets_integration + test_targets: *test_target_integration + integration_build_and_test_workspace: + name: "Integration (WORKSPACE) w/ Bazel {bazel}" + bazel: ${{ bazel }} + platform: ${{ all_platforms }} + working_directory: "test/repo" + shell_commands: + - sh setup.sh + batch_commands: + - setup.bat + build_targets: *build_targets_integration + build_flags: *flags_workspace_integration + test_targets: *test_target_integration + test_flags: *flags_workspace_integration + # internal tests for configs and release process + internal_tests: + name: "Internal tests" + platform: "ubuntu2004" + shell_commands: + - "git init" + - "git config user.name 'fake-user-for-testing'" + - "git commit --allow-empty -m 'Fake init commit'" + - "git tag -a 'fake-tag-for-testing' -m 'ignore'" + - "git commit --allow-empty -m 'Fake commit message for testing'" + - "git tag -a 'fake-tag-for-testing2' -m 'ignore'" + test_targets: + - "//test:check_remote_jdk_configs_test" + - "//test:check_remote_java_tools_configs_test" + - "//test:check_release_notes_test" diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 00000000..457eb2a0 --- /dev/null +++ b/.bazelignore @@ -0,0 +1 @@ +test/repo diff --git a/.bazelrc b/.bazelrc index 5d84eb8f..12d684fc 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,3 +1,22 @@ -build:bzlmod --experimental_enable_bzlmod +common --incompatible_disallow_empty_glob -common --incompatible_disallow_empty_glob \ No newline at end of file +# Use hermetic JDKs for testing and ensure compatibliity with Java 8. +common --java_language_version=8 +common --java_runtime_version=remotejdk_8 +common --tool_java_language_version=8 +common --tool_java_runtime_version=remotejdk_8 + +# Hide Java 8 deprecation warnings. +common --javacopt=-Xlint:-options + +# Enable modern C++ features, for compiling java_tools from source +build --cxxopt=-std=c++17 +build --host_cxxopt=-std=c++17 + +# Some tests relies on dynamic libs that are not interface libs +# the latter are always enabled on Windows: https://github.com/bazelbuild/bazel/blob/1f5414408467171581b6142e93f67fe730d722cf/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java#L2430 +# we can't use a transition because toolchain deps exceed the default --analysis_testing_deps_limit. +test --nointerface_shared_objects + +# For testing add_exports/add_opens module flags behavior. +test --incompatible_java_info_merge_runtime_module_flags diff --git a/.bazelversion b/.bazelversion deleted file mode 100644 index 66ce77b7..00000000 --- a/.bazelversion +++ /dev/null @@ -1 +0,0 @@ -7.0.0 diff --git a/.bcr/presubmit.yml b/.bcr/presubmit.yml index a9943343..26dad0fe 100644 --- a/.bcr/presubmit.yml +++ b/.bcr/presubmit.yml @@ -1,15 +1,16 @@ matrix: platform: - - centos7 + - rockylinux8 - debian10 - macos - ubuntu2004 - windows bazel: - 7.x + - 8.x tasks: verify_build_targets: - name: "Verify build targets" + name: "Verify build targets w/ Bazel {bazel}" platform: ${{ platform }} bazel: ${{ bazel }} build_targets: diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 00000000..3652a521 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,32 @@ +name: Publish to BCR +on: + # Run the publish workflow after a successful release + # Will be triggered from the release.yaml workflow + workflow_call: + inputs: + tag_name: + required: true + type: string + # Permit release engineers to retry manually from the GitHub UI + workflow_dispatch: + inputs: + tag_name: + description: git tag being released + required: true + type: string +jobs: + publish: + uses: bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml@v1.1.0 + with: + tag_name: ${{ inputs.tag_name }} + # GitHub repository which is a fork of the upstream where the Pull Request will be opened. + registry_fork: bazel-io/bazel-central-registry + draft: false + attest: true + permissions: + contents: write + id-token: write + attestations: write + secrets: + # Necessary to push to the BCR fork, and to open a pull request against a registry + publish_token: ${{ secrets.BCR_PUBLISH_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..4bfb5090 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,28 @@ +# Automatically perform a release whenever a new "release-like" tag is pushed to the repo. +name: Release + +on: + push: + tags: + # Detect tags that look like a release. + - "*.*.*" +permissions: + id-token: write + attestations: write + contents: write +jobs: + release: + uses: bazel-contrib/.github/.github/workflows/release_ruleset.yaml@v7.3.0 + with: + tag_name: ${{ github.ref_name }} + prerelease: false + draft: false + generate_release_notes: true + release_files: bazel-bin/distro/rules_java-*.tar.gz + bazel_test_command: "bazel test //test/..." + publish: + needs: release + uses: ./.github/workflows/publish.yaml + with: + tag_name: ${{ github.ref_name }} + secrets: inherit diff --git a/.github/workflows/release_prep.sh b/.github/workflows/release_prep.sh new file mode 100755 index 00000000..3173376a --- /dev/null +++ b/.github/workflows/release_prep.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -o errexit -o nounset -o pipefail + +RELEASE_VERSION=${1} + +# update MODULE.bazel with the version number +sed -i "3s/version = \"0.0.0\"/version = \"${RELEASE_VERSION}\"/" MODULE.bazel + +# create release artifacts +bazel build //distro:relnotes //distro:rules_java-${RELEASE_VERSION}.tar.gz + +# revert change to MODULE.bazel +git checkout -- MODULE.bazel + +# print the release notes for release.yaml +cat bazel-bin/distro/relnotes.txt diff --git a/.gitignore b/.gitignore index ef436251..86e15abe 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ # Ignore jekyll build output. /production /.sass-cache +# Ignore MODULE.bazel.lock as this is a library project. +MODULE.bazel.lock diff --git a/BUILD b/BUILD index c138b7fa..2fbbbd62 100644 --- a/BUILD +++ b/BUILD @@ -1,3 +1,8 @@ +load("@rules_license//rules:license.bzl", "license") +load("//java:java_test.bzl", "java_test") + +package(default_applicable_licenses = [":license"]) + licenses(["notice"]) exports_files([ @@ -18,3 +23,22 @@ filegroup( ], visibility = ["//visibility:public"], ) + +license( + name = "license", + package_name = "rules_java", +) + +# For exercising root-package behavior in tests +java_test( + name = "invalid_test_at_repo_root", + srcs = ["SomeTest.java"], + tags = [ + "manual", + "nobuilder", + "notap", + ], + visibility = [ + "//test/java/bazel/rules:__pkg__", + ], +) diff --git a/CODEOWNERS b/CODEOWNERS index 1f7a7ed6..9d182e13 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -14,4 +14,4 @@ # Component owners # ---------------- -* @comius @bazelbuild/java-team +* @hvadehra @bazelbuild/java-team diff --git a/MODULE.bazel b/MODULE.bazel index d9aaa8ef..38405ca6 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,19 +1,30 @@ module( name = "rules_java", - version = "7.7.2", - # Requires @bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type. + version = "0.0.0", bazel_compatibility = [">=7.0.0"], compatibility_level = 1, ) -bazel_dep(name = "platforms", version = "0.0.4") -bazel_dep(name = "rules_cc", version = "0.0.2") -bazel_dep(name = "bazel_features", version = "1.11.0") +bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_cc", version = "0.2.17") +bazel_dep(name = "bazel_features", version = "1.30.0") bazel_dep(name = "bazel_skylib", version = "1.6.1") +bazel_dep(name = "protobuf", version = "32.1", repo_name = "com_google_protobuf") +bazel_dep(name = "zlib", version = "1.3.1.bcr.5") # Required by @remote_java_tools, which is loaded via module extension. -bazel_dep(name = "rules_proto", version = "4.0.0") bazel_dep(name = "rules_license", version = "0.0.3") +bazel_dep(name = "abseil-cpp", version = "20250814.1", repo_name = "com_google_absl") +bazel_dep(name = "re2", version = "2025-11-05.bcr.1") + +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = [ + "//third_party:protobuf_load-cc-rules.patch", + ], + version = "32.1", +) register_toolchains("//toolchains:all") @@ -22,6 +33,7 @@ toolchains = use_extension("//java:extensions.bzl", "toolchains") # Declare remote java tools repos use_repo(toolchains, "remote_java_tools") use_repo(toolchains, "remote_java_tools_linux") +use_repo(toolchains, "remote_java_tools_linux_aarch64") use_repo(toolchains, "remote_java_tools_windows") use_repo(toolchains, "remote_java_tools_darwin_x86_64") use_repo(toolchains, "remote_java_tools_darwin_arm64") @@ -72,6 +84,18 @@ JDKS = { "linux", "linux_aarch64", "linux_ppc64le", + "linux_riscv64", + "linux_s390x", + "macos", + "macos_aarch64", + "win", + "win_arm64", + ], + "25": [ + "linux", + "linux_aarch64", + "linux_ppc64le", + "linux_riscv64", "linux_s390x", "macos", "macos_aarch64", @@ -89,11 +113,21 @@ REMOTE_JDK_REPOS = [(("remote_jdk" if version == "8" else "remotejdk") + version [register_toolchains("@" + name + "_toolchain_config_repo//:all") for name in REMOTE_JDK_REPOS] +# Compatibility layer +compat = use_extension("//java:rules_java_deps.bzl", "compatibility_proxy") +use_repo(compat, "compatibility_proxy") + # Dev dependencies bazel_dep(name = "rules_pkg", version = "0.9.1", dev_dependency = True) - -# Override rules_python version to deal with #161 and https://github.com/bazelbuild/bazel/issues/20458 -single_version_override( - module_name = "rules_python", - version = "0.24.0", +bazel_dep(name = "stardoc", version = "0.8.0", dev_dependency = True) +bazel_dep(name = "rules_shell", version = "0.2.0", dev_dependency = True) +bazel_dep(name = "rules_testing", dev_dependency = True) +archive_override( + module_name = "rules_testing", + integrity = "sha256-WYc72jM8jNK3m45vjWlmN6NB+95f6HAHnTkRaSaNisE=", + strip_prefix = "rules_testing-04a1219ee516ccdf3ffadfe3b28e5c634c9b90e1", + urls = ["https://github.com/bazelbuild/rules_testing/archive/04a1219ee516ccdf3ffadfe3b28e5c634c9b90e1.tar.gz"], ) + +test_repositories = use_extension("//test:repositories.bzl", "test_repositories_ext", dev_dependency = True) +use_repo(test_repositories, "guava", "other_repo", "truth") diff --git a/README.md b/README.md index 8c946de5..c9d62dc2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,26 @@ # rules_java -* Postsubmit [![Build status](https://badge.buildkite.com/d4f950ef5f481b8ca066624ba06c238fa1446d84a057ddbf89.svg?branch=master)](https://buildkite.com/bazel/rules-java-java) -* Postsubmit + Current Bazel Incompatible Flags [![Build status](https://badge.buildkite.com/ef265d270238c02aff65106a0b861abb9265efacdf4af399c3.svg?branch=master)](https://buildkite.com/bazel/rules-java-plus-bazelisk-migrate) +[![Build status](https://badge.buildkite.com/d4f950ef5f481b8ca066624ba06c238fa1446d84a057ddbf89.svg?branch=master)](https://buildkite.com/bazel/rules-java-java) Java Rules for Bazel https://bazel.build. + +**Documentation** + +For a quickstart tutorial, see https://bazel.build/start/java + +The fastest way to try this in an empty project is to click the green "Use this template" button on https://github.com/bazel-starters/java. + +For slightly more advanced usage, like setting up toolchains +or writing your own java-like rules, +see https://bazel.build/docs/bazel-and-java + + +***Core Java rules*** + +Add a load like: +```build +load("@rules_java//java:java_library.bzl", "java_library") +``` +to your `BUILD` / `BUILD.bazel` / `*.bzl` files + +For detailed docs on the core rules, see https://bazel.build/reference/be/java diff --git a/WORKSPACE b/WORKSPACE index 6e4b3c02..475e9ef8 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -4,10 +4,10 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( name = "bazel_skylib", - sha256 = "9f38886a40548c6e96c106b752f242130ee11aaa068a56ba7e56f4511f33e4f2", + sha256 = "bc283cdfcd526a52c3201279cda4bc298652efa898b10b4db0837dc51652756f", urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.6.1/bazel-skylib-1.6.1.tar.gz", - "https://github.com/bazelbuild/bazel-skylib/releases/download/1.6.1/bazel-skylib-1.6.1.tar.gz", + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz", + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz", ], ) @@ -27,3 +27,50 @@ http_archive( load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies") rules_pkg_dependencies() + +http_archive( + name = "stardoc", + sha256 = "62bd2e60216b7a6fec3ac79341aa201e0956477e7c8f6ccc286f279ad1d96432", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/stardoc/releases/download/0.6.2/stardoc-0.6.2.tar.gz", + "https://github.com/bazelbuild/stardoc/releases/download/0.6.2/stardoc-0.6.2.tar.gz", + ], +) + +http_archive( + name = "bazel_features", + sha256 = "a660027f5a87f13224ab54b8dc6e191693c554f2692fcca46e8e29ee7dabc43b", + strip_prefix = "bazel_features-1.30.0", + url = "https://github.com/bazel-contrib/bazel_features/releases/download/v1.30.0/bazel_features-v1.30.0.tar.gz", +) + +load("@bazel_features//:deps.bzl", "bazel_features_deps") + +bazel_features_deps() + +load("//java:rules_java_deps.bzl", "rules_java_dependencies") + +rules_java_dependencies() + +load("@com_google_protobuf//bazel/private:proto_bazel_features.bzl", "proto_bazel_features") # buildifier: disable=bzl-visibility + +proto_bazel_features(name = "proto_bazel_features") + +load("//java:repositories.bzl", "rules_java_toolchains") + +rules_java_toolchains() + +load("@stardoc//:setup.bzl", "stardoc_repositories") + +stardoc_repositories() + +http_archive( + name = "rules_testing", + sha256 = "d3ede92e37990aa9fe2b5a92ec736bec76f0317063c4bbc99bea4c4948437a5f", + strip_prefix = "rules_testing-db007bfee840cebcb6f955b80973ba866de38947", + urls = ["https://github.com/bazelbuild/rules_testing/archive/db007bfee840cebcb6f955b80973ba866de38947.tar.gz"], +) + +load("//test:repositories.bzl", "test_repositories") + +test_repositories() diff --git a/distro/BUILD.bazel b/distro/BUILD.bazel index 0fa843fc..0aca7dc3 100644 --- a/distro/BUILD.bazel +++ b/distro/BUILD.bazel @@ -1,7 +1,10 @@ load("@rules_pkg//pkg:tar.bzl", "pkg_tar") -load("@rules_pkg//pkg/releasing:defs.bzl", "print_rel_notes") +load(":relnotes.bzl", "print_rel_notes") -package(default_visibility = ["//visibility:private"]) +package( + default_applicable_licenses = ["@rules_java//:license"], + default_visibility = ["//visibility:private"], +) # Build the artifact to put on the github release page. pkg_tar( @@ -18,10 +21,6 @@ pkg_tar( print_rel_notes( name = "relnotes", - outs = ["relnotes.txt"], - deps_method = "rules_java_dependencies", - repo = "rules_java", - setup_file = "java:repositories.bzl", - toolchains_method = "rules_java_toolchains", + archive = ":rules_java-%s" % module_version(), version = module_version(), ) diff --git a/distro/relnotes.bzl b/distro/relnotes.bzl new file mode 100644 index 00000000..e47c3c95 --- /dev/null +++ b/distro/relnotes.bzl @@ -0,0 +1,73 @@ +"""Release notes generator""" + +def print_rel_notes(*, name, version, archive): + native.genrule( + name = name, + outs = [name + ".txt"], + cmd = """ + [ "$$(/usr/bin/git rev-parse --is-shallow-repository)" == "true" ] && /usr/bin/git fetch --unshallow + last_rel=$$(git describe --tags --abbrev=0 HEAD~1) + changelog=$$(/usr/bin/git log tags/$$last_rel..HEAD --format=oneline --invert-grep --grep 'ignore-relnotes' --) + sha=$$(/usr/bin/sha256sum $(SRCS) | cut -d ' ' -f1) + cat > $@ < /dev/stderr - curl --silent -o $$TMP_FILE -L "$$primary_url" > /dev/stderr + curl --retry 5 --write-out '%{{onerror}}%{{url}}\n' --show-error --silent --fail -o $$TMP_FILE -L "$$primary_url" > /dev/stderr sha256=`sha256sum $$TMP_FILE | cut -d' ' -f1` echo "struct(" echo " name = \\"$$name\\"," @@ -53,3 +57,22 @@ done <<< '{configs}' >> $@ ], visibility = ["//visibility:private"], ) + +bzl_library( + name = "http_jar_bzl", + srcs = ["http_jar.bzl"], + visibility = ["@compatibility_proxy//:__pkg__"], + deps = ["@bazel_tools//tools:bzl_srcs"], +) + +filegroup( + name = "for_bazel_tests", + testonly = 1, + visibility = ["//java:__pkg__"], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//java:__pkg__"], +) diff --git a/java/bazel/common/BUILD.bazel b/java/bazel/common/BUILD.bazel new file mode 100644 index 00000000..e5654e71 --- /dev/null +++ b/java/bazel/common/BUILD.bazel @@ -0,0 +1,15 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +package(default_applicable_licenses = ["@rules_java//:license"]) + +bzl_library( + name = "common", + srcs = glob(["*.bzl"]), + visibility = ["//java:__pkg__"], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//java:__pkg__"], +) diff --git a/java/bazel/common/empty.bzl b/java/bazel/common/empty.bzl new file mode 100644 index 00000000..c40e750a --- /dev/null +++ b/java/bazel/common/empty.bzl @@ -0,0 +1 @@ +"""Placeholder for glob""" diff --git a/java/bazel/http_jar.bzl b/java/bazel/http_jar.bzl new file mode 100644 index 00000000..6ba729c4 --- /dev/null +++ b/java/bazel/http_jar.bzl @@ -0,0 +1,178 @@ +"""The http_jar repo rule, for downloading jars over HTTP.""" + +load("@bazel_tools//tools/build_defs/repo:cache.bzl", "CANONICAL_ID_DOC", "DEFAULT_CANONICAL_ID_ENV", "get_default_canonical_id") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "get_auth", "update_attrs") + +_URL_DOC = """A URL to the jar that will be made available to Bazel. + +This must be a file, http or https URL. Redirections are followed. +Authentication is not supported. + +More flexibility can be achieved by the urls parameter that allows +to specify alternative URLs to fetch from.""" + +_URLS_DOC = """A list of URLs to the jar that will be made available to Bazel. + +Each entry must be a file, http or https URL. Redirections are followed. +Authentication is not supported. + +URLs are tried in order until one succeeds, so you should list local mirrors first. +If all downloads fail, the rule will fail.""" + +_AUTH_PATTERN_DOC = """An optional dict mapping host names to custom authorization patterns. + +If a URL's host name is present in this dict the value will be used as a pattern when +generating the authorization header for the http request. This enables the use of custom +authorization schemes used in a lot of common cloud storage providers. + +The pattern currently supports 2 tokens: <login> and +<password>, which are replaced with their equivalent value +in the netrc file for the same host name. After formatting, the result is set +as the value for the Authorization field of the HTTP request. + +Example attribute and netrc for a http download to an oauth2 enabled API using a bearer token: + +
+auth_patterns = {
+    "storage.cloudprovider.com": "Bearer <password>"
+}
+
+ +netrc: + +
+machine storage.cloudprovider.com
+        password RANDOM-TOKEN
+
+ +The final HTTP request would have the following header: + +
+Authorization: Bearer RANDOM-TOKEN
+
+""" + +def _get_source_urls(ctx): + """Returns source urls provided via the url, urls attributes. + + Also checks that at least one url is provided.""" + if not ctx.attr.url and not ctx.attr.urls: + fail("At least one of url and urls must be provided") + + source_urls = [] + if ctx.attr.urls: + source_urls = ctx.attr.urls + if ctx.attr.url: + source_urls = [ctx.attr.url] + source_urls + return source_urls + +def _update_integrity_attr(ctx, attrs, download_info): + # We don't need to override the integrity attribute if sha256 is already specified. + integrity_override = {} if ctx.attr.sha256 else {"integrity": download_info.integrity} + return update_attrs(ctx.attr, attrs.keys(), integrity_override) + +_HTTP_JAR_BUILD = """\ +load("{java_import_bzl}", "java_import") + +java_import( + name = 'jar', + jars = ["{file_name}"], + visibility = ['//visibility:public'], +) + +filegroup( + name = 'file', + srcs = ["{file_name}"], + visibility = ['//visibility:public'], +) + +""" + +def _http_jar_impl(ctx): + """Implementation of the http_jar rule.""" + source_urls = _get_source_urls(ctx) + downloaded_file_name = ctx.attr.downloaded_file_name + download_info = ctx.download( + source_urls, + "jar/" + downloaded_file_name, + ctx.attr.sha256, + canonical_id = ctx.attr.canonical_id or get_default_canonical_id(ctx, source_urls), + auth = get_auth(ctx, source_urls), + integrity = ctx.attr.integrity, + ) + ctx.file("jar/BUILD", _HTTP_JAR_BUILD.format( + java_import_bzl = str(Label("//java:java_import.bzl")), + file_name = downloaded_file_name, + )) + + return _update_integrity_attr(ctx, _http_jar_attrs, download_info) + +_http_jar_attrs = { + "sha256": attr.string( + doc = """The expected SHA-256 of the jar downloaded. + +This must match the SHA-256 of the jar downloaded. _It is a security risk +to omit the SHA-256 as remote files can change._ At best omitting this +field will make your build non-hermetic. It is optional to make development +easier but either this attribute or `integrity` should be set before shipping.""", + ), + "integrity": attr.string( + doc = """Expected checksum in Subresource Integrity format of the jar downloaded. + +This must match the checksum of the file downloaded. _It is a security risk +to omit the checksum as remote files can change._ At best omitting this +field will make your build non-hermetic. It is optional to make development +easier but either this attribute or `sha256` should be set before shipping.""", + ), + "canonical_id": attr.string( + doc = CANONICAL_ID_DOC, + ), + "url": attr.string(doc = _URL_DOC + "\n\nThe URL must end in `.jar`."), + "urls": attr.string_list(doc = _URLS_DOC + "\n\nAll URLs must end in `.jar`."), + "netrc": attr.string( + doc = "Location of the .netrc file to use for authentication", + ), + "auth_patterns": attr.string_dict( + doc = _AUTH_PATTERN_DOC, + ), + "downloaded_file_name": attr.string( + default = "downloaded.jar", + doc = "Filename assigned to the jar downloaded", + ), +} + +http_jar = repository_rule( + implementation = _http_jar_impl, + attrs = _http_jar_attrs, + environ = [DEFAULT_CANONICAL_ID_ENV], + doc = + """Downloads a jar from a URL and makes it available as java_import + +Downloaded files must have a .jar extension. + +Examples: + Suppose the current repository contains the source code for a chat program, rooted at the + directory `~/chat-app`. It needs to depend on an SSL library which is available from + `http://example.com/openssl-0.2.jar`. + + Targets in the `~/chat-app` repository can depend on this target if the following lines are + added to `~/chat-app/MODULE.bazel`: + + ```python + http_jar = use_repo_rule("@rules_java//java:http_jar.bzl", "http_jar") + + http_jar( + name = "my_ssl", + url = "http://example.com/openssl-0.2.jar", + sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + ) + ``` + + Targets would specify `@my_ssl//jar` as a dependency to depend on this jar. + + You may also reference files on the current system (localhost) by using "file:///path/to/file" + if you are on Unix-based systems. If you're on Windows, use "file:///c:/path/to/file". In both + examples, note the three slashes (`/`) -- the first two slashes belong to `file://` and the third + one belongs to the absolute path to the file. +""", +) diff --git a/java/bazel/repositories_util.bzl b/java/bazel/repositories_util.bzl index d895fc28..40101d37 100644 --- a/java/bazel/repositories_util.bzl +++ b/java/bazel/repositories_util.bzl @@ -18,7 +18,7 @@ visibility(["//test"]) _RELEASE_CONFIGS = { "8": { "zulu": { - "release": "8.78.0.19-ca-jdk8.0.412", + "release": "8.90.0.19-ca-jdk8.0.472", "platforms": { "linux": ["aarch64", "x86_64"], "macos": ["aarch64", "x86_64"], @@ -34,7 +34,7 @@ _RELEASE_CONFIGS = { }, "11": { "zulu": { - "release": "11.72.19-ca-jdk11.0.23", + "release": "11.84.17-ca-jdk11.0.29", "platforms": { "linux": ["aarch64", "x86_64"], "macos": ["aarch64", "x86_64"], @@ -42,13 +42,13 @@ _RELEASE_CONFIGS = { }, }, "adoptium": { - "release": "11.0.15+10", + "release": "11.0.28+6", "platforms": { - "linux": ["ppc", "s390x"], + "linux": ["ppc64le", "s390x"], }, }, "microsoft": { - "release": "11.0.13.8.1", + "release": "11.0.28", "platforms": { "windows": ["arm64"], }, @@ -56,7 +56,7 @@ _RELEASE_CONFIGS = { }, "17": { "zulu": { - "release": "17.50.19-ca-jdk17.0.11", + "release": "17.62.17-ca-jdk17.0.17", "platforms": { "linux": ["aarch64", "x86_64"], "macos": ["aarch64", "x86_64"], @@ -64,39 +64,48 @@ _RELEASE_CONFIGS = { }, }, "adoptium": { - "release": "17.0.8.1+1", + "release": "17.0.16+8", "platforms": { - "linux": ["ppc", "s390x"], + "linux": ["ppc64le", "s390x"], }, }, }, "21": { "zulu": { - "release": "21.34.19-ca-jdk21.0.3", + "release": "21.46.19-ca-jdk21.0.9", "platforms": { "linux": ["aarch64", "x86_64"], "macos": ["aarch64", "x86_64"], - "windows": ["x86_64"], + "windows": ["arm64", "x86_64"], }, }, "adoptium": { - "release": "21.0.2+13", + "release": "21.0.8+9", "platforms": { - "linux": ["ppc", "s390x"], + "linux": ["ppc64le", "riscv64", "s390x"], }, }, - "microsoft": { - "release": "21.0.0", + }, + "25": { + "zulu": { + "release": "25.32.17-ca-jdk25.0.2", "platforms": { - "windows": ["arm64"], + "linux": ["aarch64", "x86_64"], + "macos": ["aarch64", "x86_64"], + "windows": ["arm64", "x86_64"], + }, + }, + "adoptium": { + "release": "25.0.2+10", + "platforms": { + "linux": ["ppc64le", "riscv64", "s390x"], }, }, }, } _STRIP_PREFIX_OVERRIDES = { - "remotejdk11_win_arm64": "jdk-11.0.13+8", - "remotejdk21_win_arm64": "jdk-21+35", + "remotejdk11_win_arm64": "jdk-11.0.28+6", } def _name_for_remote_jdk(version, os, cpu): @@ -104,8 +113,6 @@ def _name_for_remote_jdk(version, os, cpu): os_part = "win" if (os == "windows" and version != "8") else os if cpu == "x86_64": suffix = "" - elif cpu == "ppc": - suffix = "_ppc64le" else: suffix = "_" + cpu return prefix + version + "_" + os_part + suffix @@ -133,8 +140,6 @@ def _zulu_remote_jdk_repo(os, cpu, release): def _adoptium_linux_remote_jdk_repo(version, cpu, release): os = "linux" arch = cpu - if cpu == "ppc": - arch = "ppc64le" archive_name = "OpenJDK" + version + "U-jdk_" + arch + "_" + os + "_hotspot_" + release.replace("+", "_") + ".tar.gz" primary_url = "github.com/adoptium/temurin" + version + "-binaries/releases/download/jdk-" + release + "/" + archive_name urls = [ diff --git a/java/bazel/rules/BUILD.bazel b/java/bazel/rules/BUILD.bazel index 74e1fa5a..108a3f9c 100644 --- a/java/bazel/rules/BUILD.bazel +++ b/java/bazel/rules/BUILD.bazel @@ -13,6 +13,8 @@ # limitations under the License. load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +package(default_applicable_licenses = ["@rules_java//:license"]) + filegroup( name = "srcs", srcs = glob(["**"]), @@ -21,8 +23,31 @@ filegroup( bzl_library( name = "rules", - srcs = glob(["*.bzl"]), - visibility = ["//java:__pkg__"], + srcs = glob( + ["*.bzl"], + exclude = ["bazel_java_single_jar.bzl"], + ), + visibility = ["//visibility:public"], # for Bazel docgen + deps = [ + "//java/common:semantics_bzl", + "//java/common/rules:core_rules", + "//java/common/rules/impl", + "//java/private:internals", + "@bazel_skylib//lib:paths", + "@rules_cc//cc:find_cc_toolchain_bzl", + "@rules_cc//cc/common", + ], +) + +bzl_library( + name = "java_single_jar_bzl", + srcs = ["bazel_java_single_jar.bzl"], + visibility = [ + "//java:__pkg__", + ], + deps = [ + "//java/common/rules:java_single_jar_bzl", + ], ) filegroup( @@ -30,7 +55,14 @@ filegroup( testonly = 1, srcs = [ "BUILD.bazel", + ":java_stub_template.txt", ":rules", ], visibility = ["//java:__pkg__"], ) + +# TODO: remove this in the next major version (rules_java 9.x / Bazel 9.x) +exports_files( + ["java_stub_template.txt"], + visibility = ["@bazel_tools//tools/java:__pkg__"], +) diff --git a/java/bazel/rules/bazel_java_binary.bzl b/java/bazel/rules/bazel_java_binary.bzl new file mode 100644 index 00000000..4ee9c881 --- /dev/null +++ b/java/bazel/rules/bazel_java_binary.bzl @@ -0,0 +1,476 @@ +# Copyright 2022 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Bazel java_binary rule""" + +load("@bazel_features//:features.bzl", "bazel_features") +load("@bazel_skylib//lib:paths.bzl", "paths") +load("@rules_cc//cc:find_cc_toolchain.bzl", "use_cc_toolchain") +load("//java/common:java_semantics.bzl", "semantics") +load( + "//java/common/rules:android_lint.bzl", + "android_lint_subrule", +) +load("//java/common/rules:java_binary.bzl", "BASIC_JAVA_BINARY_ATTRIBUTES") +load("//java/common/rules:rule_util.bzl", "merge_attrs") +load("//java/common/rules/impl:java_binary_deploy_jar.bzl", "create_deploy_archives") +load("//java/common/rules/impl:java_binary_impl.bzl", "basic_java_binary", "binary_provider_helper") +load("//java/common/rules/impl:java_helper.bzl", "helper") +load("//java/private:java_info.bzl", "JavaInfo") + +def _bazel_java_binary_impl(ctx): + return bazel_base_binary_impl(ctx, is_test_rule_class = False) + binary_provider_helper.executable_providers(ctx) + +def bazel_base_binary_impl(ctx, is_test_rule_class): + """Common implementation for binaries and tests + + Args: + ctx: (RuleContext) + is_test_rule_class: (bool) + + Returns: + [Provider] + """ + deps = _collect_all_targets_as_deps(ctx, classpath_type = "compile_only") + runtime_deps = _collect_all_targets_as_deps(ctx) + + main_class = _check_and_get_main_class(ctx) + coverage_main_class = main_class + coverage_config = helper.get_coverage_config(ctx, _get_coverage_runner(ctx)) + if coverage_config: + main_class = coverage_config.main_class + + launcher_info = _get_launcher_info(ctx) + + executable = _get_executable(ctx) + + feature_config = helper.get_feature_config(ctx) + if feature_config: + strip_as_default = helper.should_strip_as_default(ctx, feature_config) + else: + # No C++ toolchain available. + strip_as_default = False + + providers, default_info, jvm_flags = basic_java_binary( + ctx, + deps, + runtime_deps, + ctx.files.resources, + main_class, + coverage_main_class, + coverage_config, + launcher_info, + executable, + strip_as_default, + is_test_rule_class = is_test_rule_class, + ) + + if ctx.attr.use_testrunner: + _java_runtime_version = semantics.find_java_runtime_toolchain(ctx).version + if _java_runtime_version >= 17 and _java_runtime_version < 24: + jvm_flags.append("-Djava.security.manager=allow") + test_class = ctx.attr.test_class if hasattr(ctx.attr, "test_class") else "" + if test_class == "": + test_class = helper.primary_class(ctx) + if test_class == None: + fail("cannot determine test class. You might want to rename the " + + "rule or add a 'test_class' attribute.") + jvm_flags.extend([ + "-ea", + "-Dbazel.test_suite=" + helper.shell_escape(test_class), + ]) + + java_attrs = providers["InternalDeployJarInfo"].java_attrs + + if executable: + _create_stub(ctx, java_attrs, launcher_info.launcher, executable, jvm_flags, main_class, coverage_main_class) + + runfiles = default_info.runfiles + + if executable: + runtime_toolchain = semantics.find_java_runtime_toolchain(ctx) + runfiles = runfiles.merge(ctx.runfiles(transitive_files = runtime_toolchain.files)) + + test_support = helper.get_test_support(ctx) + if test_support: + runfiles = runfiles.merge(test_support[DefaultInfo].default_runfiles) + + providers["DefaultInfo"] = DefaultInfo( + files = default_info.files, + runfiles = runfiles, + executable = default_info.executable, + ) + + info = providers.pop("InternalDeployJarInfo") + create_deploy_archives( + ctx, + info.java_attrs, + launcher_info, + main_class, + coverage_main_class, + info.strip_as_default, + add_exports = info.add_exports, + add_opens = info.add_opens, + ) + + return providers.values() + +def _get_coverage_runner(ctx): + if ctx.configuration.coverage_enabled and ctx.attr.create_executable: + toolchain = semantics.find_java_toolchain(ctx) + runner = toolchain.jacocorunner + if not runner: + fail("jacocorunner not set in java_toolchain: %s" % toolchain.label) + runner_jar = runner.executable + + # wrap the jar in JavaInfo so we can add it to deps for java_common.compile() + return JavaInfo(output_jar = runner_jar, compile_jar = runner_jar) + + return None + +def _collect_all_targets_as_deps(ctx, classpath_type = "all"): + deps = helper.collect_all_targets_as_deps(ctx, classpath_type = classpath_type) + + if classpath_type == "compile_only" and ctx.fragments.java.enforce_explicit_java_test_deps(): + return deps + + test_support = helper.get_test_support(ctx) + if test_support: + deps.append(test_support) + return deps + +def _check_and_get_main_class(ctx): + create_executable = ctx.attr.create_executable + main_class = _get_main_class(ctx) + + if not create_executable and main_class: + fail("main class must not be specified when executable is not created") + if create_executable and not main_class: + if not ctx.attr.srcs: + fail("need at least one of 'main_class' or Java source files") + main_class = helper.primary_class(ctx) + if main_class == None: + fail("main_class was not provided and cannot be inferred: " + + "source path doesn't include a known root (java, javatests, src, testsrc)") + + return _get_main_class(ctx) + +def _get_main_class(ctx): + if not ctx.attr.create_executable: + return None + + main_class = _get_main_class_from_rule(ctx) + + if main_class == "": + main_class = helper.primary_class(ctx) + return main_class + +def _get_main_class_from_rule(ctx): + main_class = ctx.attr.main_class + if main_class: + return main_class + if ctx.attr.use_testrunner: + return "com.google.testing.junit.runner.BazelTestRunner" + return main_class + +def _get_launcher_info(ctx): + launcher = helper.launcher_artifact_for_target(ctx) + return struct( + launcher = launcher, + unstripped_launcher = launcher, + runfiles = [], + runtime_jars = [], + jvm_flags = [], + classpath_resources = [], + ) + +def _get_executable(ctx): + if not ctx.attr.create_executable: + return None + executable_name = ctx.label.name + if helper.is_target_platform_windows(ctx): + executable_name = executable_name + ".exe" + + return ctx.actions.declare_file(executable_name) + +_LAUNCHER_MAKER_TOOLCHAIN_TYPE = "@bazel_tools//tools/launcher:launcher_maker_toolchain_type" +_LAUNCHER_MAKER_TOOLCHAIN = config_common.toolchain_type(_LAUNCHER_MAKER_TOOLCHAIN_TYPE, mandatory = True) + +def _find_launcher_maker(ctx): + if bazel_features.rules._has_launcher_maker_toolchain: + return ctx.toolchains[_LAUNCHER_MAKER_TOOLCHAIN_TYPE].binary + return ctx.executable._windows_launcher_maker + +def _create_stub(ctx, java_attrs, launcher, executable, jvm_flags, main_class, coverage_main_class): + java_runtime_toolchain = semantics.find_java_runtime_toolchain(ctx) + java_executable = helper.get_java_executable(ctx, java_runtime_toolchain, launcher) + workspace_name = ctx.workspace_name + workspace_prefix = workspace_name + ("/" if workspace_name else "") + runfiles_enabled = helper.runfiles_enabled(ctx) + coverage_enabled = ctx.configuration.coverage_enabled + + test_support = helper.get_test_support(ctx) + test_support_jars = test_support[JavaInfo].transitive_runtime_jars if test_support else depset() + classpath = depset( + transitive = [ + java_attrs.runtime_classpath, + test_support_jars if ctx.fragments.java.enforce_explicit_java_test_deps() else depset(), + ], + ) + + if helper.is_target_platform_windows(ctx): + jvm_flags_for_launcher = [] + for flag in jvm_flags: + jvm_flags_for_launcher.extend(ctx.tokenize(flag)) + return _create_windows_exe_launcher(ctx, java_executable, classpath, main_class, jvm_flags_for_launcher, runfiles_enabled, coverage_enabled, executable, coverage_main_class) + + if runfiles_enabled: + prefix = "" if helper.is_absolute_target_platform_path(ctx, java_executable) else "${JAVA_RUNFILES}/" + java_bin = "JAVABIN=${JAVABIN:-" + prefix + java_executable + "}" + else: + java_bin = "JAVABIN=${JAVABIN:-$(rlocation " + java_executable + ")}" + + td = ctx.actions.template_dict() + td.add_joined( + "%classpath%", + classpath, + map_each = lambda file: _format_classpath_entry(runfiles_enabled, workspace_prefix, file), + # At the moment, this condition is always false since the dedicated + # Windows launcher is used above when targeting Windows. + join_with = ";" if helper.is_target_platform_windows(ctx) else ":", + format_joined = "\"%s\"", + allow_closure = True, + ) + + ctx.actions.expand_template( + template = ctx.file._stub_template, + output = executable, + substitutions = { + "%runfiles_manifest_only%": "" if runfiles_enabled else "1", + "%workspace_prefix%": workspace_prefix, + "%javabin%": java_bin, + "%needs_runfiles%": "0" if helper.is_absolute_target_platform_path(ctx, java_runtime_toolchain.java_executable_exec_path) else "1", + "%set_jacoco_metadata%": "", + "%set_jacoco_main_class%": "export JACOCO_MAIN_CLASS=" + coverage_main_class if coverage_enabled else "", + "%set_jacoco_java_runfiles_root%": "export JACOCO_JAVA_RUNFILES_ROOT=${JAVA_RUNFILES}/" + workspace_prefix if coverage_enabled else "", + "%java_start_class%": helper.shell_escape(main_class), + "%jvm_flags%": " ".join(jvm_flags), + }, + computed_substitutions = td, + is_executable = True, + ) + return executable + +def _format_classpath_entry(runfiles_enabled, workspace_prefix, file): + if runfiles_enabled: + return "${RUNPATH}" + file.short_path + + return "$(rlocation " + paths.normalize(workspace_prefix + file.short_path) + ")" + +def _create_windows_exe_launcher(ctx, java_executable, classpath, main_class, jvm_flags_for_launcher, runfiles_enabled, coverage_enabled, executable, coverage_main_class): + launch_info = ctx.actions.args().use_param_file("%s", use_always = True).set_param_file_format("multiline") + launch_info.add("binary_type=Java") + launch_info.add(ctx.workspace_name, format = "workspace_name=%s") + launch_info.add("1" if runfiles_enabled else "0", format = "symlink_runfiles_enabled=%s") + launch_info.add(java_executable, format = "java_bin_path=%s") + launch_info.add(main_class, format = "java_start_class=%s") + if coverage_enabled: + launch_info.add(coverage_main_class, format = "jacoco_main_class=%s") + launch_info.add_joined(classpath, map_each = _short_path, join_with = ";", format_joined = "classpath=%s", omit_if_empty = False) + launch_info.add_joined(jvm_flags_for_launcher, join_with = "\t", format_joined = "jvm_flags=%s", omit_if_empty = False) + launch_info.add(semantics.find_java_runtime_toolchain(ctx).java_home_runfiles_path, format = "jar_bin_path=%s/bin/jar.exe") + + # TODO(b/295221112): Change to use the "launcher" attribute (only windows use a fixed _launcher attribute) + launcher_artifact = ctx.executable._launcher + ctx.actions.run( + executable = _find_launcher_maker(ctx), + inputs = [launcher_artifact], + outputs = [executable], + arguments = [launcher_artifact.path, launch_info, executable.path], + use_default_shell_env = True, + toolchain = _LAUNCHER_MAKER_TOOLCHAIN_TYPE if bazel_features.rules._has_launcher_maker_toolchain else None, + mnemonic = "JavaLauncherMaker", + ) + return executable + +def _short_path(file): + return file.short_path + +def _compute_test_support(use_testrunner): + return Label(semantics.JAVA_TEST_RUNNER_LABEL) if use_testrunner else None + +def make_binary_rule(implementation, *, doc, attrs, executable = False, test = False, initializer = None): + return rule( + implementation = implementation, + initializer = initializer, + doc = doc, + attrs = attrs, + executable = executable, + test = test, + fragments = ["cpp", "java"], + provides = [JavaInfo], + toolchains = [semantics.JAVA_TOOLCHAIN] + use_cc_toolchain(mandatory = False) + ( + [semantics.JAVA_RUNTIME_TOOLCHAIN] if executable or test else [] + ) + ( + [_LAUNCHER_MAKER_TOOLCHAIN] if bazel_features.rules._has_launcher_maker_toolchain else [] + ), + # TODO(hvd): replace with filegroups? + outputs = { + "classjar": "%{name}.jar", + "sourcejar": "%{name}-src.jar", + "deploysrcjar": "%{name}_deploy-src.jar", + "deployjar": "%{name}_deploy.jar", + "unstrippeddeployjar": "%{name}_deploy.jar.unstripped", + }, + exec_groups = { + "cpp_link": exec_group(toolchains = use_cc_toolchain(mandatory = False)), + }, + subrules = [android_lint_subrule], + ) + +BASE_BINARY_ATTRS = merge_attrs( + BASIC_JAVA_BINARY_ATTRIBUTES, + { + "resource_strip_prefix": attr.string( + doc = """ +The path prefix to strip from Java resources. +

+If specified, this path prefix is stripped from every file in the resources +attribute. It is an error for a resource file not to be under this directory. If not +specified (the default), the path of resource file is determined according to the same +logic as the Java package of source files. For example, a source file at +stuff/java/foo/bar/a.txt will be located at foo/bar/a.txt. +

+ """, + ), + "_test_support": attr.label(default = _compute_test_support), + "_launcher": attr.label( + cfg = "target", + executable = True, + default = "@bazel_tools//tools/launcher:launcher", + ), + }, + { + "_windows_launcher_maker": attr.label( + default = "@bazel_tools//tools/launcher:launcher_maker", + cfg = "exec", + executable = True, + ), + } if not bazel_features.rules._has_launcher_maker_toolchain else {}, +) + +def make_java_binary(executable): + return make_binary_rule( + _bazel_java_binary_impl, + doc = """ +

+ Builds a Java archive ("jar file"), plus a wrapper shell script with the same name as the rule. + The wrapper shell script uses a classpath that includes, among other things, a jar file for each + library on which the binary depends. When running the wrapper shell script, any nonempty + JAVABIN environment variable will take precedence over the version specified via + Bazel's --java_runtime_version flag. +

+

+ The wrapper script accepts several unique flags. Refer to + java_stub_template.txt + for a list of configurable flags and environment variables accepted by the wrapper. +

+ +

Implicit output targets

+ + +

+It is good practice to use the name of the source file that is the main entry point of the +application (minus the extension). For example, if your entry point is called +Main.java, then your name could be Main. +

+ +

+ A deps attribute is not allowed in a java_binary rule without + srcs; such a rule requires a + main_class provided by + runtime_deps. +

+ +

The following code snippet illustrates a common mistake:

+ +
+
+java_binary(
+    name = "DontDoThis",
+    srcs = [
+        ...,
+        "GeneratedJavaFile.java",  # a generated .java file
+    ],
+    deps = [":generating_rule",],  # rule that generates that file
+)
+
+
+ +

Do this instead:

+ +
+
+java_binary(
+    name = "DoThisInstead",
+    srcs = [
+        ...,
+        ":generating_rule",
+    ],
+)
+
+
+ """, + attrs = merge_attrs( + BASE_BINARY_ATTRS, + ({} if executable else { + "args": attr.string_list(), + "output_licenses": attr.string_list(), + }), + ), + executable = executable, + ) + +java_binary = make_java_binary(executable = True) diff --git a/java/bazel/rules/bazel_java_binary_nonexec.bzl b/java/bazel/rules/bazel_java_binary_nonexec.bzl new file mode 100644 index 00000000..a59030ea --- /dev/null +++ b/java/bazel/rules/bazel_java_binary_nonexec.bzl @@ -0,0 +1,25 @@ +# Copyright 2022 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Defines a java_binary rule class that is non-executable. + +There are two physical rule classes for java_binary and we want both of them +to have a name string of "java_binary" because various tooling expects that. +""" + +load(":bazel_java_binary.bzl", "make_java_binary") + +visibility("private") + +java_binary = make_java_binary(executable = False) diff --git a/java/bazel/rules/bazel_java_binary_wrapper.bzl b/java/bazel/rules/bazel_java_binary_wrapper.bzl new file mode 100644 index 00000000..c44f1511 --- /dev/null +++ b/java/bazel/rules/bazel_java_binary_wrapper.bzl @@ -0,0 +1,43 @@ +# Copyright 2022 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Macro encapsulating the java_binary implementation + +This is needed since the `executable` nature of the target must be computed from +the supplied value of the `create_executable` attribute. +""" + +load("//java/common:java_semantics.bzl", "semantics") +load( + "//java/common/rules:java_binary_wrapper.bzl", + "register_java_binary_rules", + "register_legacy_java_binary_rules", +) +load(":bazel_java_binary.bzl", java_bin_exec = "java_binary") +load(":bazel_java_binary_nonexec.bzl", java_bin_nonexec = "java_binary") + +# copybara: default visibility + +def java_binary(**kwargs): + if semantics.INCOMPATIBLE_DISABLE_NON_EXECUTABLE_JAVA_BINARY: + register_java_binary_rules( + java_bin_exec, + **kwargs + ) + else: + register_legacy_java_binary_rules( + java_bin_exec, + java_bin_nonexec, + **kwargs + ) diff --git a/java/bazel/rules/bazel_java_import.bzl b/java/bazel/rules/bazel_java_import.bzl new file mode 100644 index 00000000..855420fe --- /dev/null +++ b/java/bazel/rules/bazel_java_import.bzl @@ -0,0 +1,66 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Definition of java_import rule. +""" + +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules:java_import.bzl", "JAVA_IMPORT_ATTRS") +load("//java/common/rules/impl:bazel_java_import_impl.bzl", "bazel_java_import_rule") +load("//java/private:java_info.bzl", "JavaInfo") + +def _proxy(ctx): + return bazel_java_import_rule( + ctx, + ctx.attr.jars, + ctx.file.srcjar, + ctx.attr.deps, + ctx.attr.runtime_deps, + ctx.attr.exports, + ctx.attr.neverlink, + ctx.files.proguard_specs, + ctx.attr.add_exports, + ctx.attr.add_opens, + ).values() + +java_import = rule( + _proxy, + doc = """ +

+ This rule allows the use of precompiled .jar files as + libraries for java_library and + java_binary rules. +

+ +

Examples

+ +
+
+    java_import(
+        name = "maven_model",
+        jars = [
+            "maven_model/maven-aether-provider-3.2.3.jar",
+            "maven_model/maven-model-3.2.3.jar",
+            "maven_model/maven-model-builder-3.2.3.jar",
+        ],
+    )
+
+
+ """, + attrs = JAVA_IMPORT_ATTRS, + provides = [JavaInfo], + fragments = ["java", "cpp"], + toolchains = [semantics.JAVA_TOOLCHAIN], +) diff --git a/java/bazel/rules/bazel_java_library.bzl b/java/bazel/rules/bazel_java_library.bzl new file mode 100644 index 00000000..2c923946 --- /dev/null +++ b/java/bazel/rules/bazel_java_library.bzl @@ -0,0 +1,65 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Definition of java_library rule. +""" + +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules:android_lint.bzl", "android_lint_subrule") +load("//java/common/rules:java_library.bzl", "JAVA_LIBRARY_ATTRS") +load("//java/common/rules/impl:bazel_java_library_impl.bzl", "bazel_java_library_rule") +load("//java/private:java_info.bzl", "JavaInfo") + +def _proxy(ctx): + return bazel_java_library_rule( + ctx, + ctx.files.srcs, + ctx.attr.deps, + ctx.attr.runtime_deps, + ctx.attr.plugins, + ctx.attr.exports, + ctx.attr.exported_plugins, + ctx.files.resources, + ctx.attr.javacopts, + ctx.attr.neverlink, + ctx.files.proguard_specs, + ctx.attr.add_exports, + ctx.attr.add_opens, + ctx.attr.bootclasspath, + ctx.attr.javabuilder_jvm_flags, + ).values() + +java_library = rule( + _proxy, + doc = """ +

This rule compiles and links sources into a .jar file.

+ +

Implicit outputs

+ + """, + attrs = JAVA_LIBRARY_ATTRS, + provides = [JavaInfo], + outputs = { + "classjar": "lib%{name}.jar", + "sourcejar": "lib%{name}-src.jar", + }, + fragments = ["java", "cpp"], + toolchains = [semantics.JAVA_TOOLCHAIN], + subrules = [android_lint_subrule], +) diff --git a/java/bazel/rules/bazel_java_plugin.bzl b/java/bazel/rules/bazel_java_plugin.bzl new file mode 100644 index 00000000..cbd7eeca --- /dev/null +++ b/java/bazel/rules/bazel_java_plugin.bzl @@ -0,0 +1,154 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Definition of java_plugin rule. +""" + +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules:android_lint.bzl", "android_lint_subrule") +load("//java/common/rules:java_library.bzl", "JAVA_LIBRARY_IMPLICIT_ATTRS") +load("//java/common/rules:java_plugin.bzl", "JAVA_PLUGIN_ATTRS") +load("//java/common/rules:rule_util.bzl", "merge_attrs") +load("//java/common/rules/impl:basic_java_library_impl.bzl", "basic_java_library", "construct_defaultinfo") +load("//java/private:java_info.bzl", "JavaPluginInfo") + +def bazel_java_plugin_rule( + ctx, + srcs = [], + data = [], + generates_api = False, + processor_class = "", + deps = [], + plugins = [], + resources = [], + javacopts = [], + neverlink = False, + proguard_specs = [], + add_exports = [], + add_opens = []): + """Implements java_plugin rule. + + Use this call when you need to produce a fully fledged java_plugin from + another rule's implementation. + + Args: + ctx: (RuleContext) Used to register the actions. + srcs: (list[File]) The list of source files that are processed to create the target. + data: (list[File]) The list of files needed by this plugin at runtime. + generates_api: (bool) This attribute marks annotation processors that generate API code. + processor_class: (str) The processor class is the fully qualified type of + the class that the Java compiler should use as entry point to the annotation processor. + deps: (list[Target]) The list of other libraries to be linked in to the target. + plugins: (list[Target]) Java compiler plugins to run at compile-time. + resources: (list[File]) A list of data files to include in a Java jar. + javacopts: (list[str]) Extra compiler options for this library. + neverlink: (bool) Whether this library should only be used for compilation and not at runtime. + proguard_specs: (list[File]) Files to be used as Proguard specification. + add_exports: (list[str]) Allow this library to access the given /. + add_opens: (list[str]) Allow this library to reflectively access the given /. + Returns: + (list[provider]) A list containing DefaultInfo, JavaInfo, + InstrumentedFilesInfo, OutputGroupsInfo, ProguardSpecProvider providers. + """ + target, base_info = basic_java_library( + ctx, + srcs, + deps, + [], # runtime_deps + plugins, + [], # exports + [], # exported_plugins + resources, + [], # resource_jars + [], # classpath_resources + javacopts, + neverlink, + proguard_specs = proguard_specs, + add_exports = add_exports, + add_opens = add_opens, + ) + java_info = target.pop("JavaInfo") + + # Replace JavaInfo with JavaPluginInfo + target["JavaPluginInfo"] = JavaPluginInfo( + runtime_deps = [java_info], + processor_classes = [processor_class] if processor_class else [], # ignore empty string (default) + data = data, + generates_api = generates_api, + ) + target["DefaultInfo"] = construct_defaultinfo( + ctx, + base_info.files_to_build, + base_info.runfiles, + neverlink, + ) + target["OutputGroupInfo"] = OutputGroupInfo(**base_info.output_groups) + + return target + +def _proxy(ctx): + return bazel_java_plugin_rule( + ctx, + ctx.files.srcs, + ctx.files.data, + ctx.attr.generates_api, + ctx.attr.processor_class, + ctx.attr.deps, + ctx.attr.plugins, + ctx.files.resources, + ctx.attr.javacopts, + ctx.attr.neverlink, + ctx.files.proguard_specs, + ctx.attr.add_exports, + ctx.attr.add_opens, + ).values() + +_JAVA_PLUGIN_IMPLICIT_ATTRS = JAVA_LIBRARY_IMPLICIT_ATTRS + +java_plugin = rule( + _proxy, + doc = """ +

+ java_plugin defines plugins for the Java compiler run by Bazel. The + only supported kind of plugins are annotation processors. A java_library or + java_binary rule can run plugins by depending on them via the plugins + attribute. A java_library can also automatically export plugins to libraries that + directly depend on it using + exported_plugins. +

+ +

Implicit output targets

+
    +
  • libname.jar: A Java archive.
  • +
+ +

Arguments are a subset of (and with identical semantics to) those of +java_library(), +except for the addition of the processor_class and +generates_api arguments.

+ """, + attrs = merge_attrs( + JAVA_PLUGIN_ATTRS, + _JAVA_PLUGIN_IMPLICIT_ATTRS, + ), + provides = [JavaPluginInfo], + outputs = { + "classjar": "lib%{name}.jar", + "sourcejar": "lib%{name}-src.jar", + }, + fragments = ["java", "cpp"], + toolchains = [semantics.JAVA_TOOLCHAIN], + subrules = [android_lint_subrule], +) diff --git a/java/bazel/rules/bazel_java_single_jar.bzl b/java/bazel/rules/bazel_java_single_jar.bzl new file mode 100644 index 00000000..922fb638 --- /dev/null +++ b/java/bazel/rules/bazel_java_single_jar.bzl @@ -0,0 +1,21 @@ +# Copyright 2026 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Bazel java_single_jar rule""" + +load("//java/common/rules:java_single_jar.bzl", _bazel_java_single_jar = "bazel_java_single_jar") + +def java_single_jar(*, name, **kwargs): + if "output" not in kwargs: + kwargs["output"] = name + ".jar" + _bazel_java_single_jar(name = name, **kwargs) diff --git a/java/bazel/rules/bazel_java_test.bzl b/java/bazel/rules/bazel_java_test.bzl new file mode 100644 index 00000000..061c058e --- /dev/null +++ b/java/bazel/rules/bazel_java_test.bzl @@ -0,0 +1,148 @@ +# Copyright 2022 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Bazel java_test rule""" + +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules:java_binary.bzl", "BASE_TEST_ATTRIBUTES") +load("//java/common/rules:rule_util.bzl", "merge_attrs") +load("//java/common/rules/impl:java_binary_impl.bzl", "binary_provider_helper") +load(":bazel_java_binary.bzl", "BASE_BINARY_ATTRS", "bazel_base_binary_impl", "make_binary_rule") + +def _bazel_java_test_impl(ctx): + return bazel_base_binary_impl(ctx, is_test_rule_class = True) + binary_provider_helper.test_providers(ctx) + +def _java_test_initializer(**kwargs): + if "stamp" in kwargs and type(kwargs["stamp"]) == type(True): + kwargs["stamp"] = 1 if kwargs["stamp"] else 0 + if "use_launcher" in kwargs and not kwargs["use_launcher"]: + kwargs["launcher"] = None + else: + # If launcher is not set or None, set it to config flag + if "launcher" not in kwargs or not kwargs["launcher"]: + kwargs["launcher"] = semantics.LAUNCHER_FLAG_LABEL + return kwargs + +java_test = make_binary_rule( + _bazel_java_test_impl, + doc = """ +

+A java_test() rule compiles a Java test. A test is a binary wrapper around your +test code. The test runner's main method is invoked instead of the main class being compiled. +

+ +

Implicit output targets

+
    +
  • name.jar: A Java archive.
  • +
  • name_deploy.jar: A Java archive suitable + for deployment. (Only built if explicitly requested.) See the description of the + name_deploy.jar output from + java_binary for more details.
  • +
+ +

+See the section on java_binary() arguments. This rule also +supports all attributes common +to all test rules (*_test). +

+ +

Examples

+ +
+
+
+java_library(
+    name = "tests",
+    srcs = glob(["*.java"]),
+    deps = [
+        "//java/com/foo/base:testResources",
+        "//java/com/foo/testing/util",
+    ],
+)
+
+java_test(
+    name = "AllTests",
+    size = "small",
+    runtime_deps = [
+        ":tests",
+        "//util/mysql",
+    ],
+)
+
+
+ """, + attrs = merge_attrs( + BASE_TEST_ATTRIBUTES, + BASE_BINARY_ATTRS, + { + "_lcov_merger": attr.label( + cfg = config.exec(exec_group = "test"), + default = configuration_field( + fragment = "coverage", + name = "output_generator", + ), + ), + "_collect_cc_coverage": attr.label( + cfg = config.exec(exec_group = "test"), + allow_single_file = True, + default = "@bazel_tools//tools/test:collect_cc_coverage", + ), + }, + override_attrs = { + "use_testrunner": attr.bool( + default = True, + doc = semantics.DOCS.for_attribute("use_testrunner") + """ +
+You can use this to override the default +behavior, which is to use test runner for +java_test rules, +and not use it for java_binary rules. It is unlikely +you will want to do this. One use is for AllTest +rules that are invoked by another rule (to set up a database +before running the tests, for example). The AllTest +rule must be declared as a java_binary, but should +still use the test runner as its main entry point. + +The name of a test runner class can be overridden with main_class attribute. + """, + ), + "stamp": attr.int( + default = 0, + values = [-1, 0, 1], + doc = """ +Whether to encode build information into the binary. Possible values: +
    +
  • + stamp = 1: Always stamp the build information into the binary, even in + --nostamp builds. This + setting should be avoided, since it potentially kills remote caching for the + binary and any downstream actions that depend on it. +
  • +
  • + stamp = 0: Always replace build information by constant values. This + gives good build result caching. +
  • +
  • + stamp = -1: Embedding of build information is controlled by the + --[no]stamp flag. +
  • +
+

Stamped binaries are not rebuilt unless their dependencies change.

+ """, + ), + }, + remove_attrs = ["deploy_env"], + ), + test = True, + initializer = _java_test_initializer, +) diff --git a/java/bazel/rules/java_stub_template.txt b/java/bazel/rules/java_stub_template.txt new file mode 100644 index 00000000..115b46e6 --- /dev/null +++ b/java/bazel/rules/java_stub_template.txt @@ -0,0 +1,410 @@ +#!/usr/bin/env bash +# Copyright 2014 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This script was generated from java_stub_template.txt. Please +# don't edit it directly. +# +# If present, these flags should either be at the beginning of the command +# line, or they should be wrapped in a --wrapper_script_flag=FLAG argument. +# +# --debug Launch the JVM in remote debugging mode listening +# --debug= to the specified port or the port set in the +# DEFAULT_JVM_DEBUG_PORT environment variable (e.g. +# 'export DEFAULT_JVM_DEBUG_PORT=8000') or else the +# default port of 5005. The JVM starts suspended +# unless the DEFAULT_JVM_DEBUG_SUSPEND environment +# variable is set to 'n'. +# --main_advice= Run an alternate main class with the usual main +# program and arguments appended as arguments. +# --main_advice_classpath= +# Prepend additional class path entries. +# --jvm_flag= Pass to the "java" command itself. +# may contain spaces. Can be used multiple times. +# --jvm_flags= Pass space-separated flags to the "java" command +# itself. Can be used multiple times. +# --singlejar Start the program from the packed-up deployment +# jar rather than from the classpath. +# --print_javabin Print the location of java executable binary and exit. +# --classpath_limit= +# Specify the maximum classpath length. If the classpath +# is shorter, this script passes it to Java as a command +# line flag, otherwise it creates a classpath jar. +# +# The remainder of the command line is passed to the program. + +set -o posix + +# Make it easy to insert 'set -x' or similar commands when debugging problems with this script. +eval "$JAVA_STUB_DEBUG" + +# Prevent problems where the caller has exported CLASSPATH, causing our +# computed value to be copied into the environment and double-counted +# against the argv limit. +unset CLASSPATH + +JVM_FLAGS_CMDLINE=() + +# Processes an argument for the wrapper. Returns 0 if the given argument +# was recognized as an argument for this wrapper, and 1 if it was not. +function process_wrapper_argument() { + case "$1" in + --debug) JVM_DEBUG_PORT="${DEFAULT_JVM_DEBUG_PORT:-5005}" ;; + --debug=*) JVM_DEBUG_PORT="${1#--debug=}" ;; + --main_advice=*) MAIN_ADVICE="${1#--main_advice=}" ;; + --main_advice_classpath=*) MAIN_ADVICE_CLASSPATH="${1#--main_advice_classpath=}" ;; + --jvm_flag=*) JVM_FLAGS_CMDLINE+=( "${1#--jvm_flag=}" ) ;; + --jvm_flags=*) JVM_FLAGS_CMDLINE+=( ${1#--jvm_flags=} ) ;; + --singlejar) SINGLEJAR=1 ;; + --print_javabin) PRINT_JAVABIN=1 ;; + --classpath_limit=*) + CLASSPATH_LIMIT="${1#--classpath_limit=}" + echo "$CLASSPATH_LIMIT" | grep -q '^[0-9]\+$' || \ + die "ERROR: $self failed, --classpath_limit is not a number" + ;; + *) + return 1 ;; + esac + return 0 +} + +die() { + printf "%s: $1\n" "$0" "${@:2}" >&2 + exit 1 +} + +# Windows +function is_windows() { + [[ "${OSTYPE}" =~ msys* ]] || [[ "${OSTYPE}" =~ cygwin* ]] +} + +# macOS +function is_macos() { + [[ "${OSTYPE}" =~ darwin* ]] +} + +function available_utf8_locale() { + # Both C.UTF-8 and en_US.UTF-8 do not cause any language-specific effects + # when set as LC_CTYPE, but neither is certain to exist on all systems. + # + # https://github.com/bazelbuild/bazel/pull/17670: Note that the use of "env" + # is important in these calls. Without "env", bash itself seems to pick up + # the LC_CTYPE change as soon as the variable is defined and may emit a + # warning when the locale files are not present. By using "env", bash never + # sees the change and the 2>/dev/null redirection does the right thing. + if [[ "$(env LC_CTYPE=C.UTF-8 locale charmap 2>/dev/null)" == "UTF-8" ]]; then + echo "C.UTF-8" + elif [[ "$(env LC_CTYPE=en_US.UTF-8 locale charmap 2>/dev/null)" == "UTF-8" ]]; then + echo "en_US.UTF-8" + fi +} + +# Parse arguments sequentially until the first unrecognized arg is encountered. +# Scan the remaining args for --wrapper_script_flag=X options and process them. +ARGS=() +for ARG in "$@"; do + if [[ "$ARG" == --wrapper_script_flag=* ]]; then + process_wrapper_argument "${ARG#--wrapper_script_flag=}" \ + || die "invalid wrapper argument '%s'" "$ARG" + elif [[ "${#ARGS}" -gt 0 ]] || ! process_wrapper_argument "$ARG"; then + ARGS+=( "$ARG" ) + fi +done + +# Find our runfiles tree. We need this to construct the classpath +# (unless --singlejar was passed). +# +# Call this program X. X was generated by a java_binary or java_test rule. +# X may be invoked in many ways: +# 1a) directly by a user, with $0 in the output tree +# 1b) via 'bazel run' (similar to case 1a) +# 2) directly by a user, with $0 in X's runfiles tree +# 3) by another program Y which has a data dependency on X, with $0 in Y's runfiles tree +# 4) via 'bazel test' +# 5) by a genrule cmd, with $0 in the output tree +# 6) case 3 in the context of a genrule +# +# For case 1, $0 will be a regular file, and the runfiles tree will be +# at $0.runfiles. +# For case 2, $0 will be a symlink to the file seen in case 1. +# For case 3, we use Y's runfiles tree, which will be a superset of X's. +# For case 4, $JAVA_RUNFILES and $TEST_SRCDIR should already be set. +# Case 5 is handled like case 1. +# Case 6 is handled like case 3. + +# If we are running on Windows, convert the windows style path +# to unix style for detecting runfiles path. +if is_windows; then + self=$(cygpath --unix "$0") +else + self="$0" +fi + +if [[ "$self" != /* ]]; then + self="$PWD/$self" +fi + +if [[ "$SINGLEJAR" != 1 || "%needs_runfiles%" == 1 ]]; then + if [[ -z "$JAVA_RUNFILES" ]]; then + while true; do + if [[ -e "$self.runfiles" ]]; then + JAVA_RUNFILES="$self.runfiles" + break + fi + if [[ $self == *.runfiles/* ]]; then + JAVA_RUNFILES="${self%.runfiles/*}.runfiles" + break + fi + if [[ ! -L "$self" ]]; then + break + fi + readlink="$(readlink "$self")" + if [[ "$readlink" = /* ]]; then + self="$readlink" + else + # resolve relative symlink + self="${self%/*}/$readlink" + fi + done + if [[ -n "$JAVA_RUNFILES" ]]; then + export TEST_SRCDIR=${TEST_SRCDIR:-$JAVA_RUNFILES} + elif [[ -f "${self}_deploy.jar" && "%needs_runfiles%" == 0 ]]; then + SINGLEJAR=1; + else + die 'Cannot locate runfiles directory. (Set $JAVA_RUNFILES to inhibit searching.)' + fi + fi +fi + +# If we are running on Windows, we need a windows style runfiles path for constructing CLASSPATH +if is_windows; then + JAVA_RUNFILES=$(cygpath --windows "$JAVA_RUNFILES") +fi + +export JAVA_RUNFILES +export RUNFILES_MANIFEST_FILE="${JAVA_RUNFILES}/MANIFEST" +export RUNFILES_MANIFEST_ONLY=%runfiles_manifest_only% + +if [ -z "$RUNFILES_MANIFEST_ONLY" ]; then + function rlocation() { + if [[ "$1" = /* ]]; then + echo $1 + else + echo "$(dirname $RUNFILES_MANIFEST_FILE)/$1" + fi + } +else + if ! is_macos; then + # Read file into my_array + oifs=$IFS + IFS=$'\n' + my_array=( $(sed -e 's/\r//g' "$RUNFILES_MANIFEST_FILE") ) + IFS=$oifs + + # Process each runfile line into a [key,value] entry in runfiles_array + # declare -A is not supported on macOS because an old version of bash is used. + declare -A runfiles_array + for line in "${my_array[@]}" + do + line_split=($line) + runfiles_array[${line_split[0]}]=${line_split[@]:1} + done + fi + + function rlocation() { + if [[ "$1" = /* ]]; then + echo $1 + else + if is_macos; then + # Print the rest of line after the first space + # First, set the first column to empty and print rest of the line + # Second, use a trick of awk to remove leading and trailing spaces. + echo $(grep "^$1 " $RUNFILES_MANIFEST_FILE | awk '{ $1=""; print }' | awk '{ $1=$1; print }') + else + echo ${runfiles_array[$1]} + fi + fi + } +fi + +# Set JAVABIN to the path to the JVM launcher. +%javabin% + +if [[ "$PRINT_JAVABIN" == 1 || "%java_start_class%" == "--print_javabin" ]]; then + echo -n "$JAVABIN" + exit 0 +fi + +if [[ "$SINGLEJAR" == 1 ]]; then + CLASSPATH="${self}_deploy.jar" + # Check for the deploy jar now. If it doesn't exist, we can print a + # more helpful error message than the JVM. + [[ -r "$CLASSPATH" ]] \ + || die "Option --singlejar was passed, but %s does not exist.\n (You may need to build it explicitly.)" "$CLASSPATH" +else + # Create the shortest classpath we can, by making it relative if possible. + RUNPATH="${JAVA_RUNFILES}/%workspace_prefix%" + RUNPATH="${RUNPATH#$PWD/}" + CLASSPATH=%classpath% +fi + +# Export the locations which will be used to find the location of the classes from the classpath file. +export SELF_LOCATION="$self" +export CLASSLOADER_PREFIX_PATH="${RUNPATH}" + +# If using Jacoco in offline instrumentation mode, the CLASSPATH contains instrumented files. +# We need to make the metadata jar with uninstrumented classes available for generating +# the lcov-compatible coverage report, and we don't want it on the classpath. +%set_jacoco_metadata% +%set_jacoco_main_class% +%set_jacoco_java_runfiles_root% +# export JACOCO_IS_JAR_WRAPPED for compatibility with older versions of +# JacocoCoverageRunner that check for this and not CLASSPATH_JAR +# TODO(cmita): Remove when this is no longer required +export JACOCO_IS_JAR_WRAPPED=0 +export CLASSPATH_JAR="" + +if [[ -n "$JVM_DEBUG_PORT" ]]; then + JVM_DEBUG_SUSPEND=${DEFAULT_JVM_DEBUG_SUSPEND:-"y"} + JVM_DEBUG_FLAGS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=${JVM_DEBUG_SUSPEND},address=${JVM_DEBUG_PORT}" +fi + +if [[ -n "$MAIN_ADVICE_CLASSPATH" ]]; then + CLASSPATH="${MAIN_ADVICE_CLASSPATH}:${CLASSPATH}" +fi + +# Check if TEST_TMPDIR is available to use for scratch. +if [[ -n "$TEST_TMPDIR" && -d "$TEST_TMPDIR" ]]; then + JVM_FLAGS+=" -Djava.io.tmpdir=$TEST_TMPDIR" +fi + +ARGS=( + ${JVM_DEBUG_FLAGS} + ${JVM_FLAGS} + %jvm_flags% + "${JVM_FLAGS_CMDLINE[@]}" + ${MAIN_ADVICE} + %java_start_class% + "${ARGS[@]}") + + +# Creates a JAR containing the classpath and put the result to stdout +function create_classpath_jar() { + # Build class path. + MANIFEST_CLASSPATH=() + if is_windows; then + CLASSPATH_SEPARATOR=";" + else + CLASSPATH_SEPARATOR=":" + fi + + OLDIFS="$IFS" + IFS="${CLASSPATH_SEPARATOR}" # Use a custom separator for the loop. + current_dir=$(pwd) + for path in ${CLASSPATH}; do + # Loop through the characters of the path and convert characters that are + # not alphanumeric nor -_.~/ to their 2-digit hexadecimal representation + if [[ ! $path =~ ^[-_.~/a-zA-Z0-9]*$ ]]; then + local i c buff + local converted_path="" + + for ((i=0; i<${#path}; i++)); do + c=${path:$i:1} + case ${c} in + [-_.~/a-zA-Z0-9] ) buff=${c} ;; + * ) printf -v buff '%%%02x' "'$c'" + esac + converted_path+="${buff}" + done + path=${converted_path} + fi + + if is_windows; then + path="file:/${path}" # e.g. "file:/C:/temp/foo.jar" + else + # If not absolute, qualify the path + case "${path}" in + /*) ;; # Already an absolute path + *) path="${current_dir}/${path}";; # Now qualified + esac + path="file:${path}" # e.g. "file:/usr/local/foo.jar" + fi + + MANIFEST_CLASSPATH+=("${path}") + done + IFS="$OLDIFS" + + # Create manifest file + MANIFEST_FILE="$(mktemp -t XXXXXXXX.jar_manifest)" + ( + echo "Manifest-Version: 1.0" + + CLASSPATH_LINE="Class-Path: ${MANIFEST_CLASSPATH[*]}" + CLASSPATH_MANIFEST_LINES=$(sed -E $'s/(.{71})/\\1\\\n /g' <<< "${CLASSPATH_LINE}") + + echo "$CLASSPATH_MANIFEST_LINES" + echo "Created-By: Bazel" + ) >$MANIFEST_FILE + + # Create classpath JAR file + MANIFEST_JAR_FILE="$(mktemp -t XXXXXXXX-classpath.jar)" + if is_windows; then + MANIFEST_JAR_FILE="$(cygpath --windows "$MANIFEST_JAR_FILE")" + MANIFEST_FILE="$(cygpath --windows "$MANIFEST_FILE")" + fi + if is_windows; then + JARBIN="${JARBIN:=${JAVABIN%/java.exe}/jar.exe}" + else + JARBIN="${JARBIN:=${JAVABIN%/java}/jar}" + fi + $JARBIN cvfm "$MANIFEST_JAR_FILE" "$MANIFEST_FILE" >/dev/null || \ + die "ERROR: $self failed because $JARBIN failed" + rm -f "$MANIFEST_FILE" + + echo "$MANIFEST_JAR_FILE" +} + +# If the user didn't specify a --classpath_limit, use the default value. +if [ -z "$CLASSPATH_LIMIT" ]; then + # Windows per-arg limit MAX_ARG_STRLEN == 8k + # Linux per-arg limit MAX_ARG_STRLEN == 128k + is_windows && CLASSPATH_LIMIT=7000 || CLASSPATH_LIMIT=120000 +fi + +# On non-macOS Unix, without any locale variable set, the JVM would use +# using ASCII rather than UTF-8 as the encoding for file system paths. +if ! is_macos; then + if [ -z ${LC_CTYPE+x} ] && [ -z ${LC_ALL+x} ] && [ -z ${LANG+x} ]; then + UTF8_LOCALE=$(available_utf8_locale) + if [[ -n "$UTF8_LOCALE" ]]; then + export LC_CTYPE="$UTF8_LOCALE" + fi + fi +fi + +if (("${#CLASSPATH}" > ${CLASSPATH_LIMIT})); then + # TODO(cmtia): Remove JACOCO_IS_JAR_WRAPPED when JacocoCoverageRunner will + # never need it anymore. + export JACOCO_IS_JAR_WRAPPED=1 + CLASSPATH_MANIFEST_JAR=$(create_classpath_jar) + export CLASSPATH_JAR="$(basename $CLASSPATH_MANIFEST_JAR)" + "$JAVABIN" -classpath "$CLASSPATH_MANIFEST_JAR" "${ARGS[@]}" + exit_code=$? + rm -f "$CLASSPATH_MANIFEST_JAR" + exit $exit_code +else + export JACOCO_IS_JAR_WRAPPED=0 + export CLASSPATH_JAR="" + exec "$JAVABIN" -classpath $CLASSPATH "${ARGS[@]}" +fi diff --git a/java/common/BUILD b/java/common/BUILD index e9d0165b..7887b1f0 100644 --- a/java/common/BUILD +++ b/java/common/BUILD @@ -1,20 +1,51 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") -package(default_visibility = ["//visibility:public"]) +package( + default_applicable_licenses = ["@rules_java//:license"], + default_visibility = ["//visibility:public"], +) licenses(["notice"]) filegroup( name = "srcs", - srcs = glob(["**"]), + srcs = glob(["**"]) + [ + "//java/common/rules:srcs", + ], visibility = ["//java:__pkg__"], ) bzl_library( name = "common", - srcs = glob(["*.bzl"]), + srcs = glob( + ["*.bzl"], + exclude = [ + "java_semantics.bzl", + "proguard_spec_info.bzl", + ], + ), + visibility = ["//visibility:public"], + deps = [ + ":proguard_spec_info_bzl", + ":semantics_bzl", + "@compatibility_proxy//:proxy_bzl", + ], +) + +bzl_library( + name = "semantics_bzl", + srcs = ["java_semantics.bzl"], + visibility = ["//visibility:public"], + deps = [ + "@bazel_features//:features", + "@rules_cc//cc/common:cc_helper_bzl", + ], +) + +bzl_library( + name = "proguard_spec_info_bzl", + srcs = ["proguard_spec_info.bzl"], visibility = ["//visibility:public"], - deps = ["//java/private"], ) filegroup( @@ -23,6 +54,7 @@ filegroup( srcs = [ "BUILD", ":common", + "//java/common/rules:for_bazel_tests", ], visibility = ["//java:__pkg__"], ) diff --git a/java/common/java_common.bzl b/java/common/java_common.bzl index 201beba7..ed55bdb7 100644 --- a/java/common/java_common.bzl +++ b/java/common/java_common.bzl @@ -13,6 +13,6 @@ # limitations under the License. """java_common module""" -load("//java/private:native.bzl", "native_java_common") +load("@compatibility_proxy//:proxy.bzl", _java_common = "java_common") -java_common = native_java_common +java_common = _java_common diff --git a/java/common/java_info.bzl b/java/common/java_info.bzl index e22fb3d8..2748f6d6 100644 --- a/java/common/java_info.bzl +++ b/java/common/java_info.bzl @@ -13,6 +13,6 @@ # limitations under the License. """JavaInfo provider""" -load("//java/private:native.bzl", "NativeJavaInfo") +load("@compatibility_proxy//:proxy.bzl", _JavaInfo = "JavaInfo") -JavaInfo = NativeJavaInfo +JavaInfo = _JavaInfo diff --git a/java/common/java_plugin_info.bzl b/java/common/java_plugin_info.bzl index 36d84f94..b43dfd54 100644 --- a/java/common/java_plugin_info.bzl +++ b/java/common/java_plugin_info.bzl @@ -13,6 +13,6 @@ # limitations under the License. """JavaPluginInfo provider""" -load("//java/private:native.bzl", "NativeJavaPluginInfo") +load("@compatibility_proxy//:proxy.bzl", _JavaPluginInfo = "JavaPluginInfo") -JavaPluginInfo = NativeJavaPluginInfo +JavaPluginInfo = _JavaPluginInfo diff --git a/java/common/java_semantics.bzl b/java/common/java_semantics.bzl new file mode 100644 index 00000000..919553e9 --- /dev/null +++ b/java/common/java_semantics.bzl @@ -0,0 +1,131 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Bazel Java Semantics""" + +load("@bazel_features//private:util.bzl", _bazel_version_ge = "ge") +load("@rules_cc//cc/common:cc_common.bzl", "cc_common") +load("@rules_cc//cc/common:cc_helper.bzl", "cc_helper") + +# copybara: default visibility + +def _find_java_toolchain(ctx): + return ctx.toolchains["@bazel_tools//tools/jdk:toolchain_type"].java + +def _find_java_runtime_toolchain(ctx): + return ctx.toolchains["@bazel_tools//tools/jdk:runtime_toolchain_type"].java_runtime + +def _update_args_for_import_deps(*_args): + pass + +def _get_default_resource_path(path, segment_extractor): + # Look for src/.../resources to match Maven repository structure. + segments = path.split("/") + for idx in range(0, len(segments) - 2): + if segments[idx] == "src" and segments[idx + 2] == "resources": + return "/".join(segments[idx + 3:]) + java_segments = segment_extractor(path) + return "/".join(java_segments) if java_segments != None else path + +def _compatible_javac_options(*_args): + return depset() + +def _check_java_info_opens_exports(): + pass + +def _minimize_cc_info(cc_info): + return cc_info + +def _merge_cc_infos(*args, **kwargs): + # TODO: b/483025864 - use https://github.com/bazelbuild/rules_cc/commit/011d6d9e7fae71d43df2d4d83c577f2cef2aa52e + return cc_common.merge_cc_infos(*args, **kwargs) + +_DOCS = struct( + ATTRS = { + "resources": """ +

+If resources are specified, they will be bundled in the jar along with the usual +.class files produced by compilation. The location of the resources inside +of the jar file is determined by the project structure. Bazel first looks for Maven's +standard directory layout, +(a "src" directory followed by a "resources" directory grandchild). If that is not +found, Bazel then looks for the topmost directory named "java" or "javatests" (so, for +example, if a resource is at <workspace root>/x/java/y/java/z, the +path of the resource will be y/java/z. This heuristic cannot be overridden, +however, the resource_strip_prefix attribute can be used to specify a +specific alternative directory for resource files. + """, + "use_testrunner": """ +Use the test runner (by default +com.google.testing.junit.runner.BazelTestRunner) class as the +main entry point for a Java program, and provide the test class +to the test runner as a value of bazel.test_suite +system property. + """, + }, +) + +def _tokenize_javacopts(opts): + result = [] + for opt_str in opts: + cc_helper.tokenize(result, opt_str) + return result + +semantics = struct( + JAVA_TOOLCHAIN_LABEL = "@bazel_tools//tools/jdk:current_java_toolchain", + JAVA_TOOLCHAIN_TYPE = "@bazel_tools//tools/jdk:toolchain_type", + JAVA_TOOLCHAIN = config_common.toolchain_type("@bazel_tools//tools/jdk:toolchain_type", mandatory = True), + find_java_toolchain = _find_java_toolchain, + JAVA_RUNTIME_TOOLCHAIN_TYPE = "@bazel_tools//tools/jdk:runtime_toolchain_type", + JAVA_RUNTIME_TOOLCHAIN = config_common.toolchain_type("@bazel_tools//tools/jdk:runtime_toolchain_type", mandatory = True), + find_java_runtime_toolchain = _find_java_runtime_toolchain, + JAVA_PLUGINS_FLAG_ALIAS_LABEL = "@bazel_tools//tools/jdk:java_plugins_flag_alias", + EXTRA_SRCS_TYPES = [], + ALLOWED_RULES_IN_DEPS = [ + "cc_binary", # NB: linkshared=1 + "cc_library", + "genrule", + "genproto", # TODO(bazel-team): we should filter using providers instead (starlark rule). + "java_import", + "java_library", + "java_proto_library", + "java_lite_proto_library", + "proto_library", + "sh_binary", + "sh_library", + ], + ALLOWED_RULES_IN_DEPS_WITH_WARNING = [], + LINT_PROGRESS_MESSAGE = "Running Android Lint for: %{label}", + JAVA_STUB_TEMPLATE_LABEL = "@rules_java//java/bazel/rules:java_stub_template.txt", # copybara-use-repo-external-label + BUILD_INFO_TRANSLATOR_LABEL = "@bazel_tools//tools/build_defs/build_info:java_build_info", + JAVA_TEST_RUNNER_LABEL = "@bazel_tools//tools/jdk:TestRunner", + IS_BAZEL = True, + get_default_resource_path = _get_default_resource_path, + compatible_javac_options = _compatible_javac_options, + LAUNCHER_FLAG_LABEL = Label("@bazel_tools//tools/jdk:launcher_flag_alias"), + PROGUARD_ALLOWLISTER_LABEL = "@bazel_tools//tools/jdk:proguard_whitelister", + TOOLS_TEST_DEFAULT_TEST_TOOLCHAIN_TYPE = "@bazel_tools//tools/test:default_test_toolchain_type", + TOOLS_TEST_EMPTY_TOOLCHAIN = "@bazel_tools//tools/test:empty_toolchain", + check_java_info_opens_exports = _check_java_info_opens_exports, + DOCS = struct( + for_attribute = lambda name: _DOCS.ATTRS.get(name, ""), + ), + minimize_cc_info = _minimize_cc_info, + merge_cc_infos = _merge_cc_infos, + tokenize_javacopts = _tokenize_javacopts, + PLATFORMS_ROOT = "@platforms//", + INCOMPATIBLE_DISABLE_NON_EXECUTABLE_JAVA_BINARY = False, # Flip when java_single_jar is feature complete + update_args_for_import_deps = _update_args_for_import_deps, + expand_javacopts_make_variables = True, + java_toolchain_supports_one_version = _bazel_version_ge("8.0.0"), # can be dropped once we no longer support Bazel 7 +) diff --git a/java/common/proguard_spec_info.bzl b/java/common/proguard_spec_info.bzl index a57a0ed7..bf006e80 100644 --- a/java/common/proguard_spec_info.bzl +++ b/java/common/proguard_spec_info.bzl @@ -13,4 +13,14 @@ # limitations under the License. """ProguardSpecInfo provider""" -ProguardSpecInfo = ProguardSpecProvider +def _proguard_spec_info_init(specs): + # The constructor supports positional parameter, i.e. ProguardSpecInfo([file]) + return {"specs": specs} + +ProguardSpecInfo, _ = provider( + doc = "Information about proguard specs for Android binaries.", + fields = { + "specs": "A list of proguard specs files", + }, + init = _proguard_spec_info_init, +) diff --git a/java/common/rules/BUILD b/java/common/rules/BUILD new file mode 100644 index 00000000..4c24d073 --- /dev/null +++ b/java/common/rules/BUILD @@ -0,0 +1,101 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +package( + default_applicable_licenses = ["@rules_java//:license"], + default_visibility = ["//visibility:public"], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]) + [ + "//java/common/rules/impl:srcs", + ], + visibility = ["//java/common:__pkg__"], +) + +bzl_library( + name = "android_lint_bzl", + srcs = ["android_lint.bzl"], + visibility = ["//visibility:private"], +) + +bzl_library( + name = "rule_util_bzl", + srcs = ["rule_util.bzl"], + visibility = ["//visibility:private"], +) + +bzl_library( + name = "java_helper_bzl", + srcs = ["java_helper.bzl"], + visibility = ["//java:__subpackages__"], +) + +bzl_library( + name = "java_single_jar_bzl", + srcs = ["java_single_jar.bzl"], + visibility = ["//java:__subpackages__"], + deps = [ + "//java/common", + "//java/common:semantics_bzl", + "//java/common/rules/impl:java_helper_bzl", + ], +) + +bzl_library( + name = "core_rules", + srcs = [ + "basic_java_library.bzl", + "java_binary.bzl", + "java_binary_wrapper.bzl", + "java_import.bzl", + "java_library.bzl", + "java_plugin.bzl", + ], + visibility = [ + "//java:__subpackages__", + ], + deps = [ + ":android_lint_bzl", + ":rule_util_bzl", + "//java/private:internals", + "@bazel_skylib//lib:paths", + "@rules_cc//cc/common", + ], +) + +bzl_library( + name = "toolchain_rules", + srcs = [ + "java_package_configuration.bzl", + "java_runtime.bzl", + "java_toolchain.bzl", + ], + visibility = [ + "//java:__subpackages__", + "@compatibility_proxy//:__pkg__", + ], + deps = [ + ":java_helper_bzl", + "//java/common:semantics_bzl", + "//java/private:boot_class_path_info_bzl", + "//java/private:java_info_bzl", + "//java/private:native_bzl", + "@bazel_skylib//lib:paths", + "@rules_cc//cc/common", + ], +) + +filegroup( + name = "for_bazel_tests", + testonly = 1, + srcs = [ + "BUILD", + ":core_rules", + ":java_single_jar_bzl", + ":toolchain_rules", + "//java/common/rules/impl:for_bazel_tests", + "@rules_cc//cc/private/rules_impl:srcs", + ], + visibility = ["//java/common:__pkg__"], +) diff --git a/java/common/rules/android_lint.bzl b/java/common/rules/android_lint.bzl new file mode 100644 index 00000000..7152990c --- /dev/null +++ b/java/common/rules/android_lint.bzl @@ -0,0 +1,161 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Creates the android lint action for java rules""" + +load("//java/common:java_semantics.bzl", "semantics") + +# copybara: default visibility + +def _tokenize_opts(opts_depset): + opts = reversed(opts_depset.to_list()) + return semantics.tokenize_javacopts(opts) + +def _flags_from_disable_lint_checks(args, disable_lint_checks): + invalid_ids = [check for check in disable_lint_checks if not _check_id_is_valid(check)] + if invalid_ids: + fail("Found not allowed character in disabled check ID(s) %s" % str(invalid_ids)) + args.add_joined("--disable", disable_lint_checks, join_with = ",") + +# Returns whether the passed Android Lint check ID is valid +def _check_id_is_valid(check): + # We want to prevent whitespace so that extra flags can't be 'injected' into Android Lint + return check.replace(".", "").replace("_", "").isalnum() + +def _android_lint_action(ctx, source_files, source_jars, compilation_info, is_library, disable_lint_checks = []): + """ + Creates an action that runs Android lint against Java source files. + + You need to add `ANDROID_LINT_IMPLICIT_ATTRS` to any rule or aspect using this call. + + To lint generated source jars (java_info.java_outputs.gen_source_jar) + add them to the `source_jar` parameter. + + `compilation_info` parameter should supply the classpath and Javac options + that were used during Java compilation. + + The Android lint tool is obtained from Java toolchain. + + Args: + ctx: (RuleContext) Used to register the action. + source_files: (list[File]) A list of .java source files + source_jars: (list[File]) A list of .jar or .srcjar files containing + source files. It should also include generated source jars. + compilation_info: (struct) Information about compilation. + is_library: (bool) Whether the target is a library. + disable_lint_checks: (list[str]) A list of AndroidLint checks to be skipped. + + Returns: + (None|File) The Android lint output file or None if no source files were + present. + """ + + # assuming that linting is enabled for all java rules i.e. + # --experimental_limit_android_lint_to_android_constrained_java=false + + # --experimental_run_android_lint_on_java_rules= is checked in basic_java_library.bzl + + if not (source_files or source_jars): + return None + + toolchain = semantics.find_java_toolchain(ctx) + java_runtime = toolchain.java_runtime + linter = toolchain._android_linter + if not linter: + # TODO(hvd): enable after enabling in tests + # fail("android linter not set in java_toolchain") + return None + + args = ctx.actions.args() + + executable = linter.tool.executable + transitive_inputs = [] + if executable.extension != "jar": + tools = [linter.tool] + transitive_inputs.append(linter.data) + args_list = [args] + else: + jvm_args = ctx.actions.args() + jvm_args.add_all(toolchain.jvm_opt) + jvm_args.add_all(linter.jvm_opts) + jvm_args.add("-jar", executable) + executable = java_runtime.java_executable_exec_path + tools = [java_runtime.files, linter.tool.executable] + transitive_inputs.append(linter.data) + args_list = [jvm_args, args] + + classpath = compilation_info.compilation_classpath + + # TODO(hvd): get from toolchain if we need this - probably android only + bootclasspath_aux = [] + if bootclasspath_aux: + classpath = depset(transitive = [classpath, bootclasspath_aux]) + transitive_inputs.append(classpath) + + bootclasspath = toolchain.bootclasspath + transitive_inputs.append(bootclasspath) + + transitive_inputs.append(compilation_info.plugins.processor_jars) + transitive_inputs.append(compilation_info.plugins.processor_data) + args.add_all("--sources", source_files) + args.add_all("--source_jars", source_jars) + args.add_all("--bootclasspath", bootclasspath) + args.add_all("--classpath", classpath) + args.add_all("--lint_rules", compilation_info.plugins.processor_jars) + args.add("--target_label", ctx.label) + args.add("--library", str(is_library).lower()) + + javac_opts = compilation_info.javac_options + if javac_opts: + # wrap in a list so that map_each passes the depset to _tokenize_opts + args.add_all("--javacopts", [javac_opts], map_each = _tokenize_opts) + args.add("--") + + args.add("--lintopts") + args.add_all(linter.lint_opts) + _flags_from_disable_lint_checks(args, disable_lint_checks) + + for package_config in linter.package_config: + if package_config.matches(package_config.package_specs, ctx.label): + # wrap in a list so that map_each passes the depset to _tokenize_opts + package_opts = [package_config.javac_opts] + args.add_all(package_opts, map_each = _tokenize_opts) + transitive_inputs.append(package_config.data) + + android_lint_out = ctx.actions.declare_file("%s_android_lint_output.xml" % ctx.label.name) + args.add("--xml", android_lint_out) + + args.set_param_file_format(format = "multiline") + args.use_param_file(param_file_arg = "@%s", use_always = True) + ctx.actions.run( + mnemonic = "AndroidLint", + progress_message = semantics.LINT_PROGRESS_MESSAGE, + executable = executable, + inputs = depset( + # TODO(b/213551463) benchmark using a transitive depset instead + source_files + source_jars, + transitive = transitive_inputs, + ), + outputs = [android_lint_out], + tools = tools, + arguments = args_list, + execution_requirements = {"supports-workers": "1"}, + use_default_shell_env = True, + ) + return android_lint_out + +android_lint_subrule = subrule( + implementation = _android_lint_action, + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], +) diff --git a/java/common/rules/basic_java_library.bzl b/java/common/rules/basic_java_library.bzl new file mode 100644 index 00000000..74b43762 --- /dev/null +++ b/java/common/rules/basic_java_library.bzl @@ -0,0 +1,39 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Common code for reuse across java_* rules +""" + +load("//java/common:java_semantics.bzl", "semantics") +load("//java/private:java_common.bzl", "java_common") +load("//java/private:java_info.bzl", "JavaPluginInfo") +load(":rule_util.bzl", "merge_attrs") + +# copybara: default multiline visibility + +BASIC_JAVA_LIBRARY_IMPLICIT_ATTRS = merge_attrs( + { + "_java_plugins": attr.label( + default = semantics.JAVA_PLUGINS_FLAG_ALIAS_LABEL, + providers = [JavaPluginInfo], + ), + # TODO(b/245144242): Used by IDE integration, remove when toolchains are used + "_java_toolchain": attr.label( + default = semantics.JAVA_TOOLCHAIN_LABEL, + providers = [java_common.JavaToolchainInfo], + ), + "_use_auto_exec_groups": attr.bool(default = True), + }, +) diff --git a/java/common/rules/impl/BUILD b/java/common/rules/impl/BUILD new file mode 100644 index 00000000..70adeccd --- /dev/null +++ b/java/common/rules/impl/BUILD @@ -0,0 +1,50 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +package( + default_applicable_licenses = ["@rules_java//:license"], + default_visibility = ["//visibility:public"], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//java/common/rules:__pkg__"], +) + +bzl_library( + name = "impl", + srcs = glob( + ["*.bzl"], + exclude = ["java_helper.bzl"], + ), + visibility = ["//java:__subpackages__"], + deps = [ + ":java_helper_bzl", + "//java/common:proguard_spec_info_bzl", + "@com_google_protobuf//bazel/common:proto_info_bzl", + "@rules_cc//cc/common:cc_helper_bzl", + ], +) + +bzl_library( + name = "java_helper_bzl", + srcs = ["java_helper.bzl"], + visibility = ["//java:__subpackages__"], + deps = [ + "//java/common:semantics_bzl", + "//java/common/rules:java_helper_bzl", + "@bazel_skylib//lib:paths", + "@rules_cc//cc:find_cc_toolchain_bzl", + "@rules_cc//cc/common", + ], +) + +filegroup( + name = "for_bazel_tests", + testonly = 1, + srcs = [ + "BUILD", + ":impl", + ], + visibility = ["//java/common/rules:__pkg__"], +) diff --git a/java/common/rules/impl/basic_java_library_impl.bzl b/java/common/rules/impl/basic_java_library_impl.bzl new file mode 100644 index 00000000..e2e27442 --- /dev/null +++ b/java/common/rules/impl/basic_java_library_impl.bzl @@ -0,0 +1,274 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Common code for reuse across java_* rules +""" + +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") +load("//java/common/rules:android_lint.bzl", "android_lint_subrule") +load("//java/private:boot_class_path_info.bzl", "BootClassPathInfo") +load("//java/private:java_common_internal.bzl", "target_kind") +load("//java/private:java_info.bzl", "JavaInfo", "JavaPluginInfo") +load(":compile_action.bzl", "compile_action") +load(":proguard_validation.bzl", "validate_proguard_specs") + +# copybara: default multiline visibility + +def _filter_srcs(srcs, ext): + return [f for f in srcs if f.extension == ext] + +def _filter_provider(provider, *attrs): + return [dep[provider] for attr in attrs for dep in attr if provider in dep] + +# TODO(b/11285003): disallow jar files in deps, require java_import instead +def _filter_javainfo_and_legacy_jars(attr): + dep_list = [] + + # Native code collected data into a NestedSet, using add for legacy jars and + # addTransitive for JavaInfo. This resulted in legacy jars being first in the list. + for dep in attr: + kind = target_kind(dep) + if not JavaInfo in dep or kind == "java_binary" or kind == "java_test": + for file in dep[DefaultInfo].files.to_list(): + if file.extension == "jar": + # Native doesn't construct JavaInfo + java_info = JavaInfo(output_jar = file, compile_jar = file) + dep_list.append(java_info) + + for dep in attr: + if JavaInfo in dep: + dep_list.append(dep[JavaInfo]) + return dep_list + +def basic_java_library( + ctx, + srcs, + deps = [], + runtime_deps = [], + plugins = [], + exports = [], + exported_plugins = [], + resources = [], + resource_jars = [], + classpath_resources = [], + javacopts = [], + neverlink = False, + enable_compile_jar_action = True, + coverage_config = None, + proguard_specs = None, + add_exports = [], + add_opens = [], + bootclasspath = None, + javabuilder_jvm_flags = None, + is_library = True, + disable_lint_checks = []): + """ + Creates actions that compile and lint Java sources, sets up coverage and returns JavaInfo, InstrumentedFilesInfo and output groups. + + The call creates actions and providers needed and shared by `java_library`, + `java_plugin`,`java_binary`, and `java_test` rules and it is primarily + intended to be used in those rules. + + Before compilation coverage.runner is added to the dependencies and if + present plugins are extended with the value of `--plugin` flag. + + Args: + ctx: (RuleContext) Used to register the actions. + srcs: (list[File]) The list of source files that are processed to create the target. + deps: (list[Target]) The list of other libraries to be linked in to the target. + runtime_deps: (list[Target]) Libraries to make available to the final binary or test at runtime only. + plugins: (list[Target]) Java compiler plugins to run at compile-time. + exports: (list[Target]) Exported libraries. + exported_plugins: (list[Target]) The list of `java_plugin`s (e.g. annotation + processors) to export to libraries that directly depend on this library. + resources: (list[File]) A list of data files to include in a Java jar. + resource_jars: (list[File]) A list of jar files to unpack and include in a + Java jar. + classpath_resources: (list[File]) + javacopts: (list[str]) + neverlink: (bool) Whether this library should only be used for compilation and not at runtime. + enable_compile_jar_action: (bool) Enables header compilation or ijar creation. + coverage_config: (struct{runner:JavaInfo, support_files:list[File]|depset[File], env:dict[str,str]}) + Coverage configuration. `runner` is added to dependencies during + compilation, `support_files` and `env` is returned in InstrumentedFilesInfo. + proguard_specs: (list[File]) Files to be used as Proguard specification. + Proguard validation is done only when the parameter is set. + add_exports: (list[str]) Allow this library to access the given /. + add_opens: (list[str]) Allow this library to reflectively access the given /. + bootclasspath: (Target) The JDK APIs to compile this library against. + javabuilder_jvm_flags: (list[str]) Additional JVM flags to pass to JavaBuilder. + is_library: (bool) Whether the target is a library. Primarily for static analysis purposes. + disable_lint_checks: (list[str]) A list of AndroidLint checks to be skipped. + Returns: + (dict[str, Provider], + {files_to_build: list[File], + runfiles: list[File], + output_groups: dict[str,list[File]]}) + """ + source_files = _filter_srcs(srcs, "java") + source_jars = _filter_srcs(srcs, "srcjar") + + plugins_javaplugininfo = _collect_plugins(plugins) + plugins_javaplugininfo.append(ctx.attr._java_plugins[JavaPluginInfo]) + + properties = _filter_srcs(srcs, "properties") + if properties: + resources = list(resources) + resources.extend(properties) + + java_info, compilation_info = compile_action( + ctx, + output_class_jar = ctx.outputs.classjar, + output_source_jar = ctx.outputs.sourcejar, + source_files = source_files, + source_jars = source_jars, + deps = collect_deps(deps) + ([coverage_config.runner] if coverage_config and coverage_config.runner else []), + runtime_deps = collect_deps(runtime_deps), + plugins = plugins_javaplugininfo, + exports = collect_deps(exports), + exported_plugins = _collect_plugins(exported_plugins), + resources = resources, + resource_jars = resource_jars, + classpath_resources = classpath_resources, + native_libraries = _collect_native_libraries(deps, runtime_deps, exports), + javacopts = javacopts, + neverlink = neverlink, + strict_deps = ctx.fragments.java.strict_java_deps, + enable_compile_jar_action = enable_compile_jar_action, + add_exports = add_exports, + add_opens = add_opens, + bootclasspath = bootclasspath[BootClassPathInfo] if bootclasspath else None, + javabuilder_jvm_flags = javabuilder_jvm_flags, + ) + target = {"JavaInfo": java_info} + + output_groups = dict( + compilation_outputs = compilation_info.files_to_build, + _source_jars = java_info.transitive_source_jars, + _direct_source_jars = java_info.source_jars, + ) + + validation_outputs = [] + + # Validation actions don't run in the exec config, so no need to create them. + if ctx.fragments.java.run_android_lint and not ctx.configuration.is_tool_configuration(): + generated_source_jars = [ + output.generated_source_jar + for output in java_info.java_outputs + if output.generated_source_jar != None + ] + lint_output = android_lint_subrule( + source_files, + source_jars + generated_source_jars, + compilation_info, + is_library, + disable_lint_checks, + ) + if lint_output: + validation_outputs.append(depset([lint_output])) + + if neverlink: + validation_outputs.append(java_info.compilation_info.runtime_classpath) + + if validation_outputs: + output_groups["_validation"] = depset(transitive = validation_outputs) + + target["InstrumentedFilesInfo"] = coverage_common.instrumented_files_info( + ctx, + source_attributes = ["srcs"], + dependency_attributes = ["deps", "data", "resources", "resource_jars", "exports", "runtime_deps", "jars"], + coverage_support_files = coverage_config.support_files if coverage_config else depset(), + coverage_environment = coverage_config.env if coverage_config else {}, + ) + + if proguard_specs != None: + target["ProguardSpecProvider"] = validate_proguard_specs( + ctx, + proguard_specs, + [deps, runtime_deps, exports], + ) + output_groups["_hidden_top_level_INTERNAL_"] = target["ProguardSpecProvider"].specs + + return target, struct( + files_to_build = compilation_info.files_to_build, + runfiles = compilation_info.runfiles, + output_groups = output_groups, + ) + +def _collect_plugins(plugins): + """Collects plugins from an attribute. + + Use this call to collect plugins from `plugins` or `exported_plugins` attribute. + + The call simply extracts JavaPluginInfo provider. + + Args: + plugins: (list[Target]) Attribute to collect plugins from. + Returns: + (list[JavaPluginInfo]) The plugins. + """ + return _filter_provider(JavaPluginInfo, plugins) + +def collect_deps(deps): + """Collects dependencies from an attribute. + + Use this call to collect plugins from `deps`, `runtime_deps`, or `exports` attribute. + + The call extracts JavaInfo and additionaly also "legacy jars". "legacy jars" + are wrapped into a JavaInfo. + + Args: + deps: (list[Target]) Attribute to collect dependencies from. + Returns: + (list[JavaInfo]) The dependencies. + """ + return _filter_javainfo_and_legacy_jars(deps) + +def _collect_native_libraries(*attrs): + """Collects native libraries from a list of attributes. + + Use this call to collect native libraries from `deps`, `runtime_deps`, or `exports` attributes. + + The call simply extracts CcInfo provider. + Args: + *attrs: (*list[Target]) Attribute to collect native libraries from. + Returns: + (list[CcInfo]) The native library dependencies. + """ + return _filter_provider(CcInfo, *attrs) + +def construct_defaultinfo(ctx, files_to_build, files, neverlink, *extra_attrs): + """Constructs DefaultInfo for Java library like rule. + + Args: + ctx: (RuleContext) Used to construct the runfiles. + files_to_build: (list[File]) List of the files built by the rule. + files: (list[File]) List of the files include in runfiles. + neverlink: (bool) When true empty runfiles are constructed. + *extra_attrs: (list[Target]) Extra attributes to merge runfiles from. + + Returns: + (DefaultInfo) DefaultInfo provider. + """ + if neverlink: + runfiles = None + else: + runfiles = ctx.runfiles(files = files, collect_default = True) + runfiles = runfiles.merge_all([dep[DefaultInfo].default_runfiles for attr in extra_attrs for dep in attr]) + default_info = DefaultInfo( + files = depset(files_to_build), + runfiles = runfiles, + ) + return default_info diff --git a/java/common/rules/impl/bazel_java_import_impl.bzl b/java/common/rules/impl/bazel_java_import_impl.bzl new file mode 100644 index 00000000..c402fc44 --- /dev/null +++ b/java/common/rules/impl/bazel_java_import_impl.bzl @@ -0,0 +1,185 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Definition of java_import rule. +""" + +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules/impl:basic_java_library_impl.bzl", "construct_defaultinfo") +load("//java/common/rules/impl:import_deps_check.bzl", "import_deps_check") +load("//java/private:java_common.bzl", "java_common") +load("//java/private:java_common_internal.bzl", _run_ijar_private_for_builtins = "run_ijar") +load("//java/private:java_info.bzl", "JavaInfo") +load(":proguard_validation.bzl", "validate_proguard_specs") + +# copybara: default visibility + +def _filter_provider(provider, *attrs): + return [dep[provider] for attr in attrs for dep in attr if provider in dep] + +def _collect_jars(ctx, jars): + jars_dict = {} + for info in jars: + if JavaInfo in info: + fail("'jars' attribute cannot contain labels of Java targets") + for jar in info[DefaultInfo].files.to_list(): + jar_path = jar.dirname + jar.basename + if jars_dict.get(jar_path) != None: + fail("in jars attribute of java_import rule //" + ctx.label.package + ":" + ctx.attr.name + ": " + jar.basename + " is a duplicate") + jars_dict[jar_path] = jar + return [jar_tuple[1] for jar_tuple in jars_dict.items()] if len(jars_dict.items()) > 0 else [] + +def _process_with_ijars_if_needed(jars, ctx): + file_dict = {} + use_ijars = ctx.fragments.java.use_ijars() + for jar in jars: + interface_jar = jar + if use_ijars: + ijar_basename = jar.short_path.removeprefix("../").removesuffix("." + jar.extension) + "-ijar.jar" + interface_jar_directory = "_ijar/" + ctx.label.name + "/" + ijar_basename + + interface_jar = ctx.actions.declare_file(interface_jar_directory) + _run_ijar_private_for_builtins( + ctx.actions, + target_label = ctx.label, + jar = jar, + output = interface_jar, + java_toolchain = semantics.find_java_toolchain(ctx), + ) + file_dict[jar] = interface_jar + + return file_dict + +def _check_export_error(ctx, exports, permit_exports): + disallow_java_import_exports = ctx.fragments.java.disallow_java_import_exports() + + if len(exports) != 0 and (disallow_java_import_exports or not permit_exports): + fail("java_import.exports is no longer supported; use java_import.deps instead") + +def _check_empty_jars_error(ctx, jars): + if len(jars) == 0: + fail("empty java_import.jars is not supported " + ctx.label.package) + +def bazel_java_import_rule( + ctx, + jars = [], + srcjar = None, + deps = [], + runtime_deps = [], + exports = [], + neverlink = False, + proguard_specs = [], + add_exports = [], + add_opens = [], + permit_exports = True, + skip_incomplete_deps_check = True): + """Implements java_import. + + This rule allows the use of precompiled .jar files as libraries in other Java rules. + + Args: + ctx: (RuleContext) Used to register the actions. + jars: (list[Artifact]) List of output jars. + srcjar: (Artifact) The jar containing the sources. + deps: (list[Target]) The list of dependent libraries. + runtime_deps: (list[Target]) Runtime dependencies to attach to the rule. + exports: (list[Target]) The list of exported libraries. + neverlink: (bool) Whether this rule should only be used for compilation and not at runtime. + proguard_specs: (list[File]) Files to be used as Proguard specification. + add_exports: (list[str]) Allow this library to access the given /. + add_opens: (list[str]) Allow this library to reflectively access the given /. + permit_exports: (bool) Allow using exports + skip_incomplete_deps_check: (bool) If this target is allowed to have incomplete deps + + Returns: + (list[provider]) A list containing DefaultInfo, JavaInfo, + OutputGroupsInfo, ProguardSpecProvider providers. + """ + + _check_empty_jars_error(ctx, jars) + _check_export_error(ctx, exports, permit_exports) + + collected_jars = _collect_jars(ctx, jars) + all_deps = _filter_provider(JavaInfo, deps, exports) + + jdeps_artifact = None + merged_java_info = java_common.merge(all_deps) + if not skip_incomplete_deps_check and "incomplete-deps" not in ctx.attr.tags: + jdeps_artifact = import_deps_check( + ctx, + collected_jars, + merged_java_info.compile_jars, + merged_java_info.transitive_compile_time_jars, + "java_import", + ) + + compilation_to_runtime_jar_map = _process_with_ijars_if_needed(collected_jars, ctx) + runtime_deps_list = [runtime_dep[JavaInfo] for runtime_dep in runtime_deps if JavaInfo in runtime_dep] + cc_info_list = [dep[CcInfo] for dep in deps if CcInfo in dep] + java_infos = [] + for jar in collected_jars: + java_infos.append(JavaInfo( + output_jar = jar, + compile_jar = compilation_to_runtime_jar_map[jar], + deps = all_deps, + runtime_deps = runtime_deps_list, + neverlink = neverlink, + source_jar = srcjar, + exports = [export[JavaInfo] for export in exports if JavaInfo in export], # Watchout, maybe you need to add them there manually. + native_libraries = cc_info_list, + add_exports = add_exports, + add_opens = add_opens, + )) + java_info = java_common.merge(java_infos) + + target = {"JavaInfo": java_info} + + target["ProguardSpecProvider"] = validate_proguard_specs( + ctx, + proguard_specs, + [deps, runtime_deps, exports], + ) + + # TODO(kotlaja): Revise if collected_runtimes can be added into construct_defaultinfo directly. + collected_runtimes = [] + for runtime_dep in ctx.attr.runtime_deps: + collected_runtimes.extend(runtime_dep[DefaultInfo].files.to_list()) + + target["DefaultInfo"] = construct_defaultinfo( + ctx, + collected_jars, + collected_jars + collected_runtimes, + neverlink, + exports, + ) + + output_group_src_jars = depset() if srcjar == None else depset([srcjar]) + + validation_group = [] + if jdeps_artifact != None: + validation_group.append(jdeps_artifact) + if srcjar != None: + validation_group.append(srcjar) + + target["OutputGroupInfo"] = OutputGroupInfo( + **{ + "_source_jars": output_group_src_jars, + "_direct_source_jars": output_group_src_jars, + "_validation": depset(validation_group), + "_hidden_top_level_INTERNAL_": target["ProguardSpecProvider"].specs, + } + ) + return target diff --git a/java/common/rules/impl/bazel_java_library_impl.bzl b/java/common/rules/impl/bazel_java_library_impl.bzl new file mode 100644 index 00000000..3ee1deb7 --- /dev/null +++ b/java/common/rules/impl/bazel_java_library_impl.bzl @@ -0,0 +1,101 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Definition of java_library rule. +""" + +load("//java/common/rules/impl:basic_java_library_impl.bzl", "basic_java_library", "construct_defaultinfo") + +# copybara: default visibility + +def bazel_java_library_rule( + ctx, + srcs = [], + deps = [], + runtime_deps = [], + plugins = [], + exports = [], + exported_plugins = [], + resources = [], + javacopts = [], + neverlink = False, + proguard_specs = [], + add_exports = [], + add_opens = [], + bootclasspath = None, + javabuilder_jvm_flags = None, + disable_lint_checks = []): + """Implements java_library. + + Use this call when you need to produce a fully fledged java_library from + another rule's implementation. + + Args: + ctx: (RuleContext) Used to register the actions. + srcs: (list[File]) The list of source files that are processed to create the target. + deps: (list[Target]) The list of other libraries to be linked in to the target. + runtime_deps: (list[Target]) Libraries to make available to the final binary or test at runtime only. + plugins: (list[Target]) Java compiler plugins to run at compile-time. + exports: (list[Target]) Exported libraries. + exported_plugins: (list[Target]) The list of `java_plugin`s (e.g. annotation + processors) to export to libraries that directly depend on this library. + resources: (list[File]) A list of data files to include in a Java jar. + javacopts: (list[str]) Extra compiler options for this library. + neverlink: (bool) Whether this library should only be used for compilation and not at runtime. + proguard_specs: (list[File]) Files to be used as Proguard specification. + add_exports: (list[str]) Allow this library to access the given /. + add_opens: (list[str]) Allow this library to reflectively access the given /. + bootclasspath: (Target) The JDK APIs to compile this library against. + javabuilder_jvm_flags: (list[str]) Additional JVM flags to pass to JavaBuilder. + disable_lint_checks: (list[str]) A list of AndroidLint checks to be skipped. + Returns: + (dict[str, provider]) A list containing DefaultInfo, JavaInfo, + InstrumentedFilesInfo, OutputGroupsInfo, ProguardSpecProvider providers. + """ + if not srcs and deps: + fail("deps not allowed without srcs; move to runtime_deps?") + + target, base_info = basic_java_library( + ctx, + srcs = srcs, + deps = deps, + runtime_deps = runtime_deps, + plugins = plugins, + exports = exports, + exported_plugins = exported_plugins, + resources = resources, + resource_jars = [], + classpath_resources = [], + javacopts = javacopts, + neverlink = neverlink, + proguard_specs = proguard_specs, + add_exports = add_exports, + add_opens = add_opens, + bootclasspath = bootclasspath, + javabuilder_jvm_flags = javabuilder_jvm_flags, + disable_lint_checks = disable_lint_checks, + ) + + target["DefaultInfo"] = construct_defaultinfo( + ctx, + base_info.files_to_build, + base_info.runfiles, + neverlink, + exports, + runtime_deps, + ) + target["OutputGroupInfo"] = OutputGroupInfo(**base_info.output_groups) + + return target diff --git a/java/common/rules/impl/compile_action.bzl b/java/common/rules/impl/compile_action.bzl new file mode 100644 index 00000000..f553a837 --- /dev/null +++ b/java/common/rules/impl/compile_action.bzl @@ -0,0 +1,177 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Java compile action +""" + +load("//java/common:java_semantics.bzl", "semantics") +load("//java/private:java_common_internal.bzl", _compile_private_for_builtins = "compile") + +visibility("private") + +def _filter_strict_deps(mode): + return "error" if mode in ["strict", "default"] else mode + +def _collect_plugins(deps, plugins): + transitive_processor_jars = [] + transitive_processor_data = [] + for plugin in plugins: + transitive_processor_jars.append(plugin.plugins.processor_jars) + transitive_processor_data.append(plugin.plugins.processor_data) + for dep in deps: + transitive_processor_jars.append(dep.plugins.processor_jars) + transitive_processor_data.append(dep.plugins.processor_data) + return struct( + processor_jars = depset(transitive = transitive_processor_jars), + processor_data = depset(transitive = transitive_processor_data), + ) + +def compile_action( + ctx, + output_class_jar, + output_source_jar, + source_files = [], + source_jars = [], + deps = [], + runtime_deps = [], + plugins = [], + exports = [], + exported_plugins = [], + resources = [], + resource_jars = [], + classpath_resources = [], + native_libraries = [], + javacopts = [], + neverlink = False, + strict_deps = "ERROR", + enable_compile_jar_action = True, + add_exports = [], + add_opens = [], + bootclasspath = None, + javabuilder_jvm_flags = None): + """ + Creates actions that compile Java sources, produce source jar, and produce header jar and returns JavaInfo. + + Use this call when you need the most basic and consistent Java compilation. + + Most parameters correspond to attributes on a java_library (srcs, deps, + plugins, resources ...) except they are more strict, for example: + + - Where java_library's srcs attribute allows mixing of .java, .srcjar, and + .properties files the arguments accepted by this call should be strictly + separated into source_files, source_jars, and resources parameter. + - deps parameter accepts only JavaInfo providers and plugins parameter only + JavaPluginInfo + + The call creates following actions and files: + - compiling Java sources to a class jar (output_class_jar parameter) + - a source jar (output_source_jar parameter) + - optionally a jar containing plugin generated classes when plugins are present + - optionally a jar containing plugin generated sources + - jdeps file containing dependencies used during compilation + - other files used to speed up incremental builds: + - a header jar - a jar containing only method signatures without implementation + - compile jdeps - dependencies used during header compilation + + The returned JavaInfo provider may be used as a "fully-qualified" dependency + to a java_library. + + Args: + ctx: (RuleContext) Used to register the actions. + output_class_jar: (File) Output class .jar file. The file needs to be declared. + output_source_jar: (File) Output source .jar file. The file needs to be declared. + source_files: (list[File]) A list of .java source files to compile. + At least one of source_files or source_jars parameter must be specified. + source_jars: (list[File]) A list of .jar or .srcjar files containing + source files to compile. + At least one of source_files or source_jars parameter must be specified. + deps: (list[JavaInfo]) A list of dependencies. + runtime_deps: (list[JavaInfo]) A list of runtime dependencies. + plugins: (list[JavaPluginInfo]) A list of plugins. + exports: (list[JavaInfo]) A list of exports. + exported_plugins: (list[JavaInfo]) A list of exported plugins. + resources: (list[File]) A list of resources. + resource_jars: (list[File]) A list of jars to unpack. + classpath_resources: (list[File]) A list of classpath resources. + native_libraries: (list[CcInfo]) C++ native library dependencies that are + needed for this library. + javacopts: (list[str]) A list of the desired javac options. The options + may contain `$(location ..)` templates that will be expanded. + neverlink: (bool) Whether or not this library should be used only for + compilation and not at runtime. + strict_deps: (str) A string that specifies how to handle strict deps. + Possible values: 'OFF', 'ERROR', 'WARN' and 'DEFAULT'. For more details + see https://bazel.build/docs/user-manual#strict-java-deps. + By default 'ERROR'. + enable_compile_jar_action: (bool) Enables header compilation or ijar + creation. If set to False, it forces use of the full class jar in the + compilation classpaths of any dependants. Doing so is intended for use + by non-library targets such as binaries that do not have dependants. + add_exports: (list[str]) Allow this library to access the given /. + add_opens: (list[str]) Allow this library to reflectively access the given /. + bootclasspath: (BootClassPathInfo) The set of JDK APIs to compile this library against. + javabuilder_jvm_flags: (list[str]) Additional JVM flags to pass to JavaBuilder. + + Returns: + ((JavaInfo, {files_to_build: list[File], + runfiles: list[File], + compilation_classpath: list[File], + plugins: {processor_jars, + processor_data: depset[File]}})) + A tuple with JavaInfo provider and additional compilation info. + + Files_to_build may include an empty .jar file when there are no sources + or resources present, whereas runfiles in this case are empty. + """ + expanded_javacopts = javacopts + expanded_javacopts = [ctx.expand_location(opt) for opt in expanded_javacopts] + if semantics.expand_javacopts_make_variables: + expanded_javacopts = [ctx.expand_make_variables("javacopts", opt, {}) for opt in expanded_javacopts] + java_info = _compile_private_for_builtins( + ctx, + output = output_class_jar, + java_toolchain = semantics.find_java_toolchain(ctx), + source_files = source_files, + source_jars = source_jars, + resources = resources, + resource_jars = resource_jars, + classpath_resources = classpath_resources, + plugins = plugins, + deps = deps, + native_libraries = native_libraries, + runtime_deps = runtime_deps, + exports = exports, + exported_plugins = exported_plugins, + javac_opts = expanded_javacopts, + neverlink = neverlink, + output_source_jar = output_source_jar, + strict_deps = _filter_strict_deps(strict_deps), + enable_compile_jar_action = enable_compile_jar_action, + add_exports = add_exports, + add_opens = add_opens, + bootclasspath = bootclasspath, + javabuilder_jvm_flags = javabuilder_jvm_flags, + ) + + compilation_info = struct( + files_to_build = [output_class_jar], + runfiles = [output_class_jar] if source_files or source_jars or resources else [], + # TODO(ilist): collect compile_jars from JavaInfo in deps & exports + compilation_classpath = java_info.compilation_info.compilation_classpath, + javac_options = java_info.compilation_info.javac_options, + plugins = _collect_plugins(deps, plugins), + ) + + return java_info, compilation_info diff --git a/java/common/rules/impl/import_deps_check.bzl b/java/common/rules/impl/import_deps_check.bzl new file mode 100644 index 00000000..69dec8b6 --- /dev/null +++ b/java/common/rules/impl/import_deps_check.bzl @@ -0,0 +1,83 @@ +# Copyright 2022 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Creates the import deps checker for java rules""" + +load("//java/common:java_semantics.bzl", "semantics") + +visibility(["//java/common/rules/..."]) + +def import_deps_check( + ctx, + jars_to_check, + declared_deps, + transitive_deps, + rule_class): + """ + Creates actions that checks import deps for java rules. + + Args: + ctx: (RuleContext) Used to register the actions. + jars_to_check: (list[File]) A list of jars files to check. + declared_deps: (list[File]) A list of direct dependencies. + transitive_deps: (list[File]) A list of transitive dependencies. + rule_class: (String) Rule class. + + Returns: + (File) Output file of the created action. + """ + java_toolchain = semantics.find_java_toolchain(ctx) + deps_checker = java_toolchain._deps_checker + if deps_checker == None: + return None + + jdeps_output = ctx.actions.declare_file("_%s/%s/jdeps.proto" % (rule_class, ctx.label.name)) + + args = ctx.actions.args() + args.add("-jar", deps_checker) + args.add_all(jars_to_check, before_each = "--input") + args.add_all(declared_deps, before_each = "--directdep") + args.add_all( + depset(order = "preorder", transitive = [declared_deps, transitive_deps]), + before_each = "--classpath_entry", + ) + args.add_all(java_toolchain.bootclasspath, before_each = "--bootclasspath_entry") + args.add("--checking_mode=error") + args.add("--jdeps_output", jdeps_output) + args.add("--rule_label", ctx.label) + + semantics.update_args_for_import_deps(ctx, args) + inputs = depset( + jars_to_check, + transitive = [ + declared_deps, + transitive_deps, + java_toolchain.bootclasspath, + ], + ) + tools = [deps_checker, java_toolchain.java_runtime.files] + + ctx.actions.run( + mnemonic = "ImportDepsChecker", + progress_message = "Checking the completeness of the deps for %s" % jars_to_check, + executable = java_toolchain.java_runtime.java_executable_exec_path, + arguments = [args], + inputs = inputs, + outputs = [jdeps_output], + tools = tools, + toolchain = semantics.JAVA_TOOLCHAIN_TYPE, + use_default_shell_env = True, + ) + + return jdeps_output diff --git a/java/common/rules/impl/java_binary_deploy_jar.bzl b/java/common/rules/impl/java_binary_deploy_jar.bzl new file mode 100644 index 00000000..d6add141 --- /dev/null +++ b/java/common/rules/impl/java_binary_deploy_jar.bzl @@ -0,0 +1,232 @@ +# Copyright 2022 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Auxiliary rule to create the deploy archives for java_binary""" + +load("//java/common:java_semantics.bzl", "semantics") +load(":java_helper.bzl", "helper") + +# copybara: default visibility + +def create_deploy_archives( + ctx, + java_attrs, + launcher_info, + main_class, + coverage_main_class, + strip_as_default, + hermetic = False, + add_exports = depset(), + add_opens = depset(), + one_version_level = "OFF", + one_version_allowlist = None, + extra_args = [], + extra_manifest_lines = []): + """ Registers actions for _deploy.jar and _deploy.jar.unstripped + + Args: + ctx: (RuleContext) The rule context + java_attrs: (Struct) Struct of (classpath_resources, runtime_jars, runtime_classpath_for_archive, resources) + launcher_info: (Struct) Struct of (runtime_jars, launcher, unstripped_launcher) + main_class: (String) FQN of the entry point for execution + coverage_main_class: (String) FQN of the entry point for coverage collection + strip_as_default: (bool) Whether to create unstripped deploy jar + hermetic: (bool) + add_exports: (depset) + add_opens: (depset) + one_version_level: (String) Optional one version check level, default OFF + one_version_allowlist: (File) Optional allowlist for one version check + extra_args: (list[Args]) Optional arguments for the deploy jar action + extra_manifest_lines: (list[String]) Optional lines added to the jar manifest + """ + classpath_resources = java_attrs.classpath_resources + + runtime_classpath = depset( + direct = launcher_info.runtime_jars, + transitive = [ + java_attrs.runtime_jars, + java_attrs.runtime_classpath_for_archive, + ], + order = "preorder", + ) + multi_release = ctx.fragments.java.multi_release_deploy_jars + build_info_files = helper.get_build_info(ctx, ctx.attr.stamp) + build_target = str(ctx.label) + manifest_lines = ctx.attr.deploy_manifest_lines + extra_manifest_lines + create_deploy_archive( + ctx, + launcher_info.launcher, + main_class, + coverage_main_class, + java_attrs.resources, + classpath_resources, + runtime_classpath, + manifest_lines, + build_info_files, + build_target, + output = ctx.outputs.deployjar, + one_version_level = one_version_level, + one_version_allowlist = one_version_allowlist, + multi_release = multi_release, + hermetic = hermetic, + add_exports = add_exports, + add_opens = add_opens, + extra_args = extra_args, + ) + + if strip_as_default: + create_deploy_archive( + ctx, + launcher_info.unstripped_launcher, + main_class, + coverage_main_class, + java_attrs.resources, + classpath_resources, + runtime_classpath, + manifest_lines, + build_info_files, + build_target, + output = ctx.outputs.unstrippeddeployjar, + multi_release = multi_release, + hermetic = hermetic, + add_exports = add_exports, + add_opens = add_opens, + extra_args = extra_args, + ) + else: + ctx.actions.write(ctx.outputs.unstrippeddeployjar, "") + +def create_deploy_archive( + ctx, + launcher, + main_class, + coverage_main_class, + resources, + classpath_resources, + runtime_classpath, + manifest_lines, + build_info_files, + build_target, + output, + one_version_level = "OFF", + one_version_allowlist = None, + multi_release = False, + hermetic = False, + add_exports = [], + add_opens = [], + extra_args = []): + """ Creates a deploy jar + + Requires a Java runtime toolchain if and only if hermetic is True. + + Args: + ctx: (RuleContext) The rule context + launcher: (File) the launcher artifact + main_class: (String) FQN of the entry point for execution + coverage_main_class: (String) FQN of the entry point for coverage collection + resources: (Depset) resource inputs + classpath_resources: (Depset) classpath resource inputs + runtime_classpath: (Depset) source files to add to the jar + build_target: (String) Name of the build target for stamping + manifest_lines: (list[String]) Optional lines added to the jar manifest + build_info_files: (list[File]) build info files for stamping + build_target: (String) the owner build target label name string + output: (File) the output jar artifact + one_version_level: (String) Optional one version check level, default OFF + one_version_allowlist: (File) Optional allowlist for one version check + multi_release: (bool) + hermetic: (bool) + add_exports: (depset) + add_opens: (depset) + extra_args: (list[Args]) Optional arguments for the deploy jar action + """ + input_files = [] + input_files.extend(build_info_files) + + transitive_input_files = [ + resources, + classpath_resources, + runtime_classpath, + ] + + single_jar = semantics.find_java_toolchain(ctx).single_jar + + manifest_lines = list(manifest_lines) + if ctx.configuration.coverage_enabled: + manifest_lines.append("Coverage-Main-Class: %s" % coverage_main_class) + + args = ctx.actions.args() + args.set_param_file_format("shell").use_param_file("@%s", use_always = True) + + args.add("--output", output) + args.add("--build_target", build_target) + args.add("--normalize") + args.add("--compression") + args.add("--nocompress_suffixes", "protobuf.meta") + if main_class: + args.add("--main_class", main_class) + args.add_all("--deploy_manifest_lines", manifest_lines) + args.add_all(build_info_files, before_each = "--build_info_file") + if launcher: + input_files.append(launcher) + args.add("--java_launcher", launcher) + args.add_all("--classpath_resources", classpath_resources) + args.add_all( + "--sources", + runtime_classpath, + map_each = helper.jar_and_target_arg_mapper, + ) + + if one_version_level != "OFF" and one_version_allowlist: + input_files.append(one_version_allowlist) + args.add("--enforce_one_version") + args.add("--one_version_allowlist", one_version_allowlist) + if one_version_level == "WARNING": + args.add("--succeed_on_found_violations") + + if multi_release: + args.add("--multi_release") + + if hermetic: + runtime = ctx.toolchains["@//tools/jdk/hermetic:hermetic_runtime_toolchain_type"].java_runtime + if runtime.lib_modules != None: + java_home = runtime.java_home + lib_modules = runtime.lib_modules + hermetic_files = runtime.hermetic_files + default_cds = runtime.default_cds + args.add("--hermetic_java_home", java_home) + args.add("--jdk_lib_modules", lib_modules) + args.add_all("--resources", hermetic_files) + input_files.append(lib_modules) + transitive_input_files.append(hermetic_files) + if default_cds: + input_files.append(default_cds) + args.add("--cds_archive", default_cds) + + args.add_all("--add_exports", add_exports) + args.add_all("--add_opens", add_opens) + + inputs = depset(input_files, transitive = transitive_input_files) + + ctx.actions.run( + mnemonic = "JavaDeployJar", + progress_message = "Building deploy jar %s" % output.short_path, + executable = single_jar, + inputs = inputs, + tools = [single_jar], + outputs = [output], + arguments = [args] + extra_args, + use_default_shell_env = True, + toolchain = semantics.JAVA_TOOLCHAIN_TYPE, + ) diff --git a/java/common/rules/impl/java_binary_impl.bzl b/java/common/rules/impl/java_binary_impl.bzl new file mode 100644 index 00000000..8b9b68de --- /dev/null +++ b/java/common/rules/impl/java_binary_impl.bzl @@ -0,0 +1,503 @@ +# Copyright 2022 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" Implementation of java_binary for bazel """ + +load("@com_google_protobuf//bazel/common:proto_info.bzl", "ProtoInfo") +load("@rules_cc//cc/common:cc_common.bzl", "cc_common") +load("@rules_cc//cc/common:cc_helper.bzl", "cc_helper") +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules/impl:basic_java_library_impl.bzl", "basic_java_library", "collect_deps") +load("//java/private:java_common.bzl", "java_common") +load( + "//java/private:java_common_internal.bzl", + "collect_native_deps_dirs", + "get_runtime_classpath_for_archive", +) +load("//java/private:java_info.bzl", "JavaCompilationInfo", "JavaInfo", "to_java_binary_info") +load(":java_binary_deploy_jar.bzl", "create_deploy_archive") +load(":java_helper.bzl", "helper") + +# copybara: default visibility + +InternalDeployJarInfo = provider( + "Provider for passing info to deploy jar rule", + fields = [ + "java_attrs", + "strip_as_default", + "add_exports", + "add_opens", + ], +) + +def basic_java_binary( + ctx, + deps, + runtime_deps, + resources, + main_class, + coverage_main_class, + coverage_config, + launcher_info, + executable, + strip_as_default, + extra_java_info = None, + is_test_rule_class = False): + """Creates actions for compiling and linting java sources, coverage support, and sources jar (_deploy-src.jar). + + Args: + ctx: (RuleContext) The rule context + deps: (list[Target]) The list of other targets to be compiled with + runtime_deps: (list[Target]) The list of other targets to be linked in + resources: (list[File]) The list of data files to be included in the class jar + main_class: (String) FQN of the java main class + coverage_main_class: (String) FQN of the actual main class if coverage is enabled + coverage_config: (Struct|None) If coverage is enabled, a struct with fields (runner, manifest, env, support_files), None otherwise + launcher_info: (Struct) Structure with fields (launcher, unstripped_launcher, runfiles, runtime_jars, jvm_flags, classpath_resources) + executable: (File) The executable output of the rule + strip_as_default: (bool) Whether this target outputs a stripped launcher and deploy jar + extra_java_info: (JavaInfo) additional outputs to merge + is_test_rule_class: (bool) Whether this rule is a test rule + + Returns: + Tuple( + dict[str, Provider], // providers + Struct( // default info + files_to_build: depset(File), + runfiles: Runfiles, + executable: File + ), + list[String] // jvm flags + ) + + """ + if not ctx.attr.create_executable and (ctx.attr.launcher and cc_common.launcher_provider in ctx.attr.launcher): + fail("launcher specified but create_executable is false") + if not ctx.attr.use_launcher and (ctx.attr.launcher and ctx.attr.launcher.label != semantics.LAUNCHER_FLAG_LABEL): + fail("launcher specified but use_launcher is false") + + if not ctx.attr.srcs and ctx.attr.deps: + fail("deps not allowed without srcs; move to runtime_deps?") + + module_flags = [dep[JavaInfo].module_flags_info for dep in runtime_deps if JavaInfo in dep] + add_exports = depset(ctx.attr.add_exports, transitive = [m.add_exports for m in module_flags]) + add_opens = depset(ctx.attr.add_opens, transitive = [m.add_opens for m in module_flags]) + + classpath_resources = [] + classpath_resources.extend(launcher_info.classpath_resources) + if hasattr(ctx.files, "classpath_resources"): + classpath_resources.extend(ctx.files.classpath_resources) + + toolchain = semantics.find_java_toolchain(ctx) + timezone_data = [toolchain._timezone_data] if toolchain._timezone_data else [] + target, common_info = basic_java_library( + ctx, + srcs = ctx.files.srcs, + deps = deps, + runtime_deps = runtime_deps, + plugins = ctx.attr.plugins, + resources = resources, + resource_jars = timezone_data, + classpath_resources = classpath_resources, + javacopts = ctx.attr.javacopts, + neverlink = ctx.attr.neverlink, + enable_compile_jar_action = False, + coverage_config = coverage_config, + add_exports = ctx.attr.add_exports, + add_opens = ctx.attr.add_opens, + bootclasspath = ctx.attr.bootclasspath, + is_library = False, + ) + java_info = target["JavaInfo"] + compilation_info = java_info.compilation_info + runtime_classpath = depset( + order = "preorder", + transitive = [ + java_info.transitive_runtime_jars + for java_info in ( + collect_deps(ctx.attr.runtime_deps + deps) + + ([coverage_config.runner] if coverage_config and coverage_config.runner else []) + ) + ], + ) + if extra_java_info: + runtime_classpath = depset(order = "preorder", transitive = [ + extra_java_info.transitive_runtime_jars, + runtime_classpath, + ]) + java_info = java_common.merge([java_info, extra_java_info]) + compilation_info = JavaCompilationInfo( + compilation_classpath = compilation_info.compilation_classpath, + runtime_classpath = runtime_classpath, + boot_classpath = compilation_info.boot_classpath, + javac_options = compilation_info.javac_options, + ) + + java_attrs = _collect_attrs(ctx, runtime_classpath, classpath_resources) + + jvm_flags = [] + + jvm_flags.extend(launcher_info.jvm_flags) + + native_libs_depsets = [] + for dep in runtime_deps: + if JavaInfo in dep: + native_libs_depsets.append(dep[JavaInfo].transitive_native_libraries) + if CcInfo in dep: + if hasattr(dep[CcInfo], "_legacy_transitive_native_libraries"): + native_libs_depsets.append(dep[CcInfo]._legacy_transitive_native_libraries) + else: + native_libs_depsets.append(dep[CcInfo].transitive_native_libraries()) + native_libs_dirs = collect_native_deps_dirs(depset(transitive = native_libs_depsets)) + if native_libs_dirs: + prefix = "${JAVA_RUNFILES}/" + ctx.workspace_name + "/" + jvm_flags.append("-Djava.library.path=%s" % ( + ":".join([prefix + d for d in native_libs_dirs]) + )) + + jvm_flags.extend(ctx.fragments.java.default_jvm_opts) + jvm_flags.extend([ctx.expand_make_variables( + "jvm_flags", + ctx.expand_location(flag, ctx.attr.data, short_paths = True), + {}, + ) for flag in ctx.attr.jvm_flags]) + + # TODO(cushon): make string formatting lazier once extend_template support is added + # https://github.com/bazelbuild/proposals#:~:text=2022%2D04%2D25,Starlark + jvm_flags.extend(["--add-exports=%s=ALL-UNNAMED" % x for x in add_exports.to_list()]) + jvm_flags.extend(["--add-opens=%s=ALL-UNNAMED" % x for x in add_opens.to_list()]) + + files_to_build = [] + + if executable: + files_to_build.append(executable) + + output_groups = common_info.output_groups + + if coverage_config: + _generate_coverage_manifest(ctx, coverage_config.manifest, java_attrs.runtime_classpath) + files_to_build.append(coverage_config.manifest) + + if extra_java_info: + files_to_build.extend(extra_java_info.runtime_output_jars) + output_groups["_direct_source_jars"] = ( + output_groups["_direct_source_jars"] + extra_java_info.source_jars + ) + output_groups["_source_jars"] = depset( + direct = extra_java_info.source_jars, + transitive = [output_groups["_source_jars"]], + ) + + if (ctx.fragments.java.one_version_enforcement_on_java_tests or not is_test_rule_class): + one_version_output = _create_one_version_check(ctx, java_attrs.runtime_classpath, is_test_rule_class) + else: + one_version_output = None + + validation_outputs = [one_version_output] if one_version_output else [] + + _create_deploy_sources_jar(ctx, output_groups["_source_jars"]) + + files = depset(files_to_build + common_info.files_to_build) + + transitive_runfiles_artifacts = depset(transitive = [ + files, + java_attrs.runtime_classpath, + depset(transitive = launcher_info.runfiles), + ]) + + runfiles = ctx.runfiles( + transitive_files = transitive_runfiles_artifacts, + collect_default = True, + ) + + if launcher_info.launcher: + default_launcher = helper.filter_launcher_for_target(ctx) + default_launcher_artifact = helper.launcher_artifact_for_target(ctx) + default_launcher_runfiles = default_launcher[DefaultInfo].default_runfiles + if default_launcher_artifact == launcher_info.launcher: + runfiles = runfiles.merge(default_launcher_runfiles) + else: + # N.B. The "default launcher" referred to here is the launcher target specified through + # an attribute or flag. We wish to retain the runfiles of the default launcher, *except* + # for the original cc_binary artifact, because we've swapped it out with our custom + # launcher. Hence, instead of calling builder.addTarget(), or adding an odd method + # to Runfiles.Builder, we "unravel" the call and manually add things to the builder. + # Because the NestedSet representing each target's launcher runfiles is re-built here, + # we may see increased memory consumption for representing the target's runfiles. + runfiles = runfiles.merge( + ctx.runfiles( + files = [launcher_info.launcher], + transitive_files = depset([ + file + for file in default_launcher_runfiles.files.to_list() + if file != default_launcher_artifact + ]), + symlinks = default_launcher_runfiles.symlinks, + root_symlinks = default_launcher_runfiles.root_symlinks, + ), + ) + + runfiles = runfiles.merge_all([ + dep[DefaultInfo].default_runfiles + for dep in ctx.attr.runtime_deps + if DefaultInfo in dep + ]) + + if validation_outputs: + output_groups["_validation"] = depset( + validation_outputs, + transitive = [output_groups.get("_validation", depset([]))], + ) + + _filter_validation_output_group(ctx, output_groups) + + java_binary_info = to_java_binary_info(java_info, compilation_info) + + internal_deploy_jar_info = InternalDeployJarInfo( + java_attrs = java_attrs, + strip_as_default = strip_as_default, + add_exports = add_exports, + add_opens = add_opens, + ) + + # "temporary" workaround for https://github.com/bazelbuild/intellij/issues/5845 + extra_files = [] + if is_test_rule_class and ctx.fragments.java.auto_create_java_test_deploy_jars(): + extra_files.append(_auto_create_deploy_jar(ctx, internal_deploy_jar_info, launcher_info, main_class, coverage_main_class)) + + default_info = struct( + files = depset(extra_files, transitive = [files]), + runfiles = runfiles, + executable = executable, + ) + + return { + "OutputGroupInfo": OutputGroupInfo(**output_groups), + "JavaInfo": java_binary_info, + "InstrumentedFilesInfo": target["InstrumentedFilesInfo"], + "JavaRuntimeClasspathInfo": java_common.JavaRuntimeClasspathInfo(runtime_classpath = java_info.transitive_runtime_jars), + "InternalDeployJarInfo": internal_deploy_jar_info, + }, default_info, jvm_flags + +def _collect_attrs(ctx, runtime_classpath, classpath_resources): + deploy_env_jars = depset(transitive = [ + dep[java_common.JavaRuntimeClasspathInfo].runtime_classpath + for dep in ctx.attr.deploy_env + ]) if hasattr(ctx.attr, "deploy_env") else depset() + + runtime_classpath_for_archive = get_runtime_classpath_for_archive(runtime_classpath, deploy_env_jars) + runtime_jars = [ctx.outputs.classjar] + + resources = [p for p in ctx.files.srcs if p.extension == "properties"] + transitive_resources = [] + for r in ctx.attr.resources: + transitive_resources.append( + r[ProtoInfo].transitive_sources if ProtoInfo in r else r.files, + ) + + resource_names = dict() + for r in classpath_resources: + if r.basename in resource_names: + fail("entries must have different file names (duplicate: %s)" % r.basename) + resource_names[r.basename] = None + + return struct( + runtime_jars = depset(runtime_jars), + runtime_classpath_for_archive = runtime_classpath_for_archive, + classpath_resources = depset(classpath_resources), + runtime_classpath = depset(order = "preorder", direct = runtime_jars, transitive = [runtime_classpath]), + resources = depset(resources, transitive = transitive_resources), + ) + +def _generate_coverage_manifest(ctx, output, runtime_classpath): + ctx.actions.write( + output = output, + content = "\n".join([file.short_path for file in runtime_classpath.to_list()]), + ) + +def _create_one_version_check(ctx, inputs, is_test_rule_class): + one_version_level = ctx.fragments.java.one_version_enforcement_level + if one_version_level == "OFF": + return None + tool = helper.check_and_get_one_version_attribute(ctx, "_one_version_tool") + + if is_test_rule_class: + toolchain = semantics.find_java_toolchain(ctx) + allowlist = toolchain._one_version_allowlist_for_tests + else: + allowlist = helper.check_and_get_one_version_attribute(ctx, "_one_version_allowlist") + + if not tool: # On Mac oneversion tool is not available + return None + + output = ctx.actions.declare_file("%s-one-version.txt" % ctx.label.name) + + args = ctx.actions.args() + args.set_param_file_format("shell").use_param_file("@%s", use_always = True) + + one_version_inputs = [] + args.add("--output", output) + if allowlist: + args.add("--allowlist", allowlist) + one_version_inputs.append(allowlist) + if one_version_level == "WARNING": + args.add("--succeed_on_found_violations") + args.add_all( + "--inputs", + inputs, + map_each = helper.jar_and_target_arg_mapper, + ) + + ctx.actions.run( + mnemonic = "JavaOneVersion", + progress_message = "Checking for one-version violations in %{label}", + executable = tool, + toolchain = semantics.JAVA_TOOLCHAIN_TYPE, + inputs = depset(one_version_inputs, transitive = [inputs]), + tools = [tool], + outputs = [output], + arguments = [args], + use_default_shell_env = True, + ) + + return output + +def _create_deploy_sources_jar(ctx, sources): + helper.create_single_jar( + ctx.actions, + toolchain = semantics.find_java_toolchain(ctx), + output = ctx.outputs.deploysrcjar, + sources = sources, + ) + +def _filter_validation_output_group(ctx, output_group): + to_exclude = depset(transitive = [ + dep[OutputGroupInfo]._validation + for dep in ctx.attr.deploy_env + if OutputGroupInfo in dep and hasattr(dep[OutputGroupInfo], "_validation") + ]) if hasattr(ctx.attr, "deploy_env") else depset() + if to_exclude: + transitive_validations = depset(transitive = [ + _get_validations_from_attr(ctx, attr_name) + for attr_name in dir(ctx.attr) + # we also exclude implicit, cfg=host/exec and tool attributes + if not attr_name.startswith("_") and + attr_name not in [ + "deploy_env", + "applicable_licenses", + "package_metadata", + "plugins", + "translations", + # special ignored attributes + # LINT.IfChange(validation_ignored_attrs) + "compatible_with", + "restricted_to", + "exec_compatible_with", + "exec_group_compatible_with", + "target_compatible_with", + # LINT.ThenChange(//jtcg/devtools/build/lib/rules/java/AbstractJavaBinaryConfiguredTargetTest.java:validation_ignored_attrs) + ] + ]) + if not ctx.attr.create_executable: + excluded_set = {x: None for x in to_exclude.to_list()} + transitive_validations = [ + x + for x in transitive_validations.to_list() + if x not in excluded_set + ] + output_group["_validation_transitive"] = transitive_validations + +def _get_validations_from_attr(ctx, attr_name): + attr = getattr(ctx.attr, attr_name) + if type(attr) == "list": + return depset(transitive = [_get_validations_from_target(t) for t in attr]) + else: + return _get_validations_from_target(attr) + +def _get_validations_from_target(target): + if ( + type(target) == "Target" and + OutputGroupInfo in target and + hasattr(target[OutputGroupInfo], "_validation") + ): + return target[OutputGroupInfo]._validation + else: + return depset() + +# TODO: bazelbuild/intellij/issues/5845 - remove this once no longer required +# this need not be completely identical to the regular deploy jar since we only +# care about packaging the classpath +def _auto_create_deploy_jar(ctx, info, launcher_info, main_class, coverage_main_class): + output = ctx.actions.declare_file(ctx.label.name + "_auto_deploy.jar") + java_attrs = info.java_attrs + runtime_classpath = depset( + direct = launcher_info.runtime_jars, + transitive = [ + java_attrs.runtime_jars, + java_attrs.runtime_classpath_for_archive, + ], + order = "preorder", + ) + create_deploy_archive( + ctx, + launcher = launcher_info.launcher, + main_class = main_class, + coverage_main_class = coverage_main_class, + resources = java_attrs.resources, + classpath_resources = java_attrs.classpath_resources, + runtime_classpath = runtime_classpath, + manifest_lines = [], + build_info_files = [], + build_target = str(ctx.label), + output = output, + one_version_level = ctx.fragments.java.one_version_enforcement_level, + one_version_allowlist = helper.check_and_get_one_version_attribute(ctx, "_one_version_allowlist"), + multi_release = ctx.fragments.java.multi_release_deploy_jars, + hermetic = hasattr(ctx.attr, "hermetic") and ctx.attr.hermetic, + add_exports = info.add_exports, + add_opens = info.add_opens, + ) + return output + +def _test_providers(ctx): + test_providers = [] + if helper.has_target_constraints(ctx, ctx.attr._apple_constraints): + test_providers.append(testing.ExecutionInfo({"requires-darwin": ""})) + + test_env = {} + test_env.update(cc_helper.get_expanded_env(ctx, {})) + + coverage_config = helper.get_coverage_config( + ctx, + runner = None, # we only need the environment + ) + if coverage_config: + test_env.update(coverage_config.env) + test_providers.append(testing.TestEnvironment( + environment = test_env, + inherited_environment = ctx.attr.env_inherit, + )) + + return test_providers + +def _executable_providers(ctx): + if ctx.attr.create_executable: + return [RunEnvironmentInfo(cc_helper.get_expanded_env(ctx, {}))] + return [] + +binary_provider_helper = struct( + executable_providers = _executable_providers, + test_providers = _test_providers, +) diff --git a/java/common/rules/impl/java_helper.bzl b/java/common/rules/impl/java_helper.bzl new file mode 100644 index 00000000..638878ba --- /dev/null +++ b/java/common/rules/impl/java_helper.bzl @@ -0,0 +1,280 @@ +# Copyright 2022 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Common util functions for java_* rules implementations""" + +load("@bazel_skylib//lib:paths.bzl", "paths") +load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain") +load("@rules_cc//cc/common:cc_common.bzl", "cc_common") +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules:java_helper.bzl", _loading_phase_helper = "helper") + +# copybara: rules_java visibility + +def _collect_all_targets_as_deps(ctx, classpath_type = "all"): + deps = [] + if not classpath_type == "compile_only": + if hasattr(ctx.attr, "runtime_deps"): + deps.extend(ctx.attr.runtime_deps) + if hasattr(ctx.attr, "exports"): + deps.extend(ctx.attr.exports) + + deps.extend(ctx.attr.deps or []) + + launcher = _filter_launcher_for_target(ctx) + if launcher: + deps.append(launcher) + + return deps + +def _filter_launcher_for_target(ctx): + # create_executable=0 disables the launcher + if hasattr(ctx.attr, "create_executable") and not ctx.attr.create_executable: + return None + + # use_launcher=False disables the launcher + if hasattr(ctx.attr, "use_launcher") and not ctx.attr.use_launcher: + return None + + # BUILD rule "launcher" attribute + if ctx.attr.launcher and cc_common.launcher_provider in ctx.attr.launcher: + return ctx.attr.launcher + + return None + +def _launcher_artifact_for_target(ctx): + launcher = _filter_launcher_for_target(ctx) + if not launcher: + return None + files = launcher[DefaultInfo].files.to_list() + if len(files) != 1: + fail("%s expected a single artifact in %s" % (ctx.label, launcher)) + return files[0] + +def _check_and_get_main_class(ctx): + create_executable = ctx.attr.create_executable + use_testrunner = ctx.attr.use_testrunner + main_class = ctx.attr.main_class + + if not create_executable and use_testrunner: + fail("cannot have use_testrunner without creating an executable") + if not create_executable and main_class: + fail("main class must not be specified when executable is not created") + if create_executable and not use_testrunner: + if not main_class: + if not ctx.attr.srcs: + fail("need at least one of 'main_class', 'use_testrunner' or Java source files") + main_class = _primary_class(ctx) + if main_class == None: + fail("main_class was not provided and cannot be inferred: " + + "source path doesn't include a known root (java, javatests, src, testsrc)") + if not create_executable: + return None + if not main_class: + if use_testrunner: + main_class = "com.google.testing.junit.runner.GoogleTestRunner" + else: + main_class = _primary_class(ctx) + return main_class + +def _primary_class(ctx): + if ctx.attr.srcs: + main = ctx.label.name + ".java" + for src in ctx.files.srcs: + if src.basename == main: + return _full_classname(_strip_extension(src)) + return _full_classname(helper.get_relative(ctx.label.package, ctx.label.name)) + +def _strip_extension(file): + return file.dirname + "/" + ( + file.basename[:-(1 + len(file.extension))] if file.extension else file.basename + ) + +# TODO(b/465048589): once out of builtins, create a canonical implementation and remove duplicates in depot +def _full_classname(path): + java_segments = _loading_phase_helper.java_segments(path) + return ".".join(java_segments) if java_segments != None else None + +def _concat(*lists): + result = [] + for list in lists: + result.extend(list) + return result + +def _get_shared_native_deps_path( + linker_inputs, + link_opts, + linkstamps, + build_info_artifacts, + features, + is_test_target_partially_disabled_thin_lto): + """ + Returns the path of the shared native library. + + The name must be generated based on the rule-specific inputs to the link actions. At this point + this includes order-sensitive list of linker inputs and options collected from the transitive + closure and linkstamp-related artifacts that are compiled during linking. All those inputs can + be affected by modifying target attributes (srcs/deps/stamp/etc). However, target build + configuration can be ignored since it will either change output directory (in case of different + configuration instances) or will not affect anything (if two targets use same configuration). + Final goal is for all native libraries that use identical linker command to use same output + name. + +

TODO(bazel-team): (2010) Currently process of identifying parameters that can affect native + library name is manual and should be kept in sync with the code in the + CppLinkAction.Builder/CppLinkAction/Link classes which are responsible for generating linker + command line. Ideally we should reuse generated command line for both purposes - selecting a + name of the native library and using it as link action payload. For now, correctness of the + method below is only ensured by validations in the CppLinkAction.Builder.build() method. + """ + + fp = [] + + # join() is faster than concatenating many strings individually + fp += [a.short_path for a in linker_inputs] + fp.append(str(len(link_opts))) + fp += link_opts + fp += [a.short_path for a in linkstamps] + fp += [a.short_path for a in build_info_artifacts] + fp += features + + # Sharing of native dependencies may cause an ActionConflictException when ThinLTO is + # disabled for test and test-only targets that are statically linked, but enabled for other + # statically linked targets. This happens in case the artifacts for the shared native + # dependency are output by actions owned by the non-test and test targets both. To fix + # this, we allow creation of multiple artifacts for the shared native library - one shared + # among the test and test-only targets where ThinLTO is disabled, and the other shared among + # other targets where ThinLTO is enabled. + fp.append("1" if is_test_target_partially_disabled_thin_lto else "0") + + fingerprint = "%x" % hash("".join(fp)) + return "_nativedeps/" + fingerprint + +def _check_and_get_one_version_attribute(ctx, attr): + value = getattr(semantics.find_java_toolchain(ctx), attr) + return value + +def _jar_and_target_arg_mapper(jar): + # Emit pretty labels for targets in the main repository. + label = str(jar.owner) + if label.startswith("@@//"): # buildifier: disable=canonical-repository + label = label.lstrip("@") + return jar.path + "," + label + +def _get_feature_config(ctx): + cc_toolchain = find_cc_toolchain(ctx, mandatory = False) + if not cc_toolchain: + return None + feature_config = cc_common.configure_features( + ctx = ctx, + cc_toolchain = cc_toolchain, + requested_features = ctx.features + ["java_launcher_link", "static_linking_mode"], + unsupported_features = ctx.disabled_features, + ) + return feature_config + +def _should_strip_as_default(ctx, feature_config): + fission_is_active = ctx.fragments.cpp.fission_active_for_current_compilation_mode() + create_per_obj_debug_info = fission_is_active and cc_common.is_enabled( + feature_name = "per_object_debug_info", + feature_configuration = feature_config, + ) + compilation_mode = ctx.var["COMPILATION_MODE"] + strip_as_default = create_per_obj_debug_info and compilation_mode == "opt" + + return strip_as_default + +def _get_coverage_config(ctx, runner): + toolchain = semantics.find_java_toolchain(ctx) + if not ctx.configuration.coverage_enabled: + return None + runner = runner if ctx.attr.create_executable else None + manifest = ctx.actions.declare_file("runtime_classpath_for_coverage/%s/runtime_classpath.txt" % ctx.label.name) + singlejar = toolchain.single_jar + return struct( + runner = runner, + main_class = "com.google.testing.coverage.JacocoCoverageRunner", + manifest = manifest, + env = { + "JAVA_RUNTIME_CLASSPATH_FOR_COVERAGE": manifest.path, + "SINGLE_JAR_TOOL": singlejar.executable.path, + }, + support_files = [manifest, singlejar.executable], + ) + +def _get_java_executable(ctx, java_runtime_toolchain, launcher): + java_executable = launcher.short_path if launcher else java_runtime_toolchain.java_executable_runfiles_path + if not _is_absolute_target_platform_path(ctx, java_executable): + java_executable = ctx.workspace_name + "/" + java_executable + return paths.normalize(java_executable) + +def _is_absolute_target_platform_path(ctx, path): + if helper.is_target_platform_windows(ctx): + return len(path) > 2 and path[1] == ":" + return path.startswith("/") + +def _runfiles_enabled(ctx): + return ctx.configuration.runfiles_enabled() + +def _get_test_support(ctx): + if ctx.attr.create_executable and ctx.attr.use_testrunner: + return ctx.attr._test_support + return None + +def _is_stamping_enabled(ctx, stamp): + if ctx.configuration.is_tool_configuration(): + return 0 + if stamp == 1 or stamp == 0: + return stamp + + # stamp == -1 / auto + return int(ctx.configuration.stamp_binaries()) + +def _get_build_info(ctx, stamp): + if helper.is_stamping_enabled(ctx, stamp): + # Makes the target depend on BUILD_INFO_KEY, which helps to discover stamped targets + # See b/326620485 for more details. + ctx.version_file # buildifier: disable=no-effect + return ctx.attr._build_info_translator[OutputGroupInfo].non_redacted_build_info_files.to_list() + else: + return ctx.attr._build_info_translator[OutputGroupInfo].redacted_build_info_files.to_list() + +helper = struct( + collect_all_targets_as_deps = _collect_all_targets_as_deps, + filter_launcher_for_target = _filter_launcher_for_target, + launcher_artifact_for_target = _launcher_artifact_for_target, + check_and_get_main_class = _check_and_get_main_class, + primary_class = _primary_class, + strip_extension = _strip_extension, + concat = _concat, + get_shared_native_deps_path = _get_shared_native_deps_path, + check_and_get_one_version_attribute = _check_and_get_one_version_attribute, + jar_and_target_arg_mapper = _jar_and_target_arg_mapper, + get_feature_config = _get_feature_config, + should_strip_as_default = _should_strip_as_default, + get_coverage_config = _get_coverage_config, + get_java_executable = _get_java_executable, + is_absolute_target_platform_path = _is_absolute_target_platform_path, + is_target_platform_windows = _loading_phase_helper.is_target_platform_windows, + runfiles_enabled = _runfiles_enabled, + get_test_support = _get_test_support, + create_single_jar = _loading_phase_helper.create_single_jar, + shell_escape = _loading_phase_helper.shell_escape, + detokenize_javacopts = _loading_phase_helper.detokenize_javacopts, + tokenize_javacopts = _loading_phase_helper.tokenize_javacopts, + is_stamping_enabled = _is_stamping_enabled, + get_build_info = _get_build_info, + get_relative = _loading_phase_helper.get_relative, + has_target_constraints = _loading_phase_helper.has_target_constraints, +) diff --git a/java/common/rules/impl/proguard_validation.bzl b/java/common/rules/impl/proguard_validation.bzl new file mode 100644 index 00000000..c31b3575 --- /dev/null +++ b/java/common/rules/impl/proguard_validation.bzl @@ -0,0 +1,72 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Proguard +""" + +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common:proguard_spec_info.bzl", "ProguardSpecInfo") + +visibility("private") + +def _filter_provider(provider, *attrs): + return [dep[provider] for attr in attrs for dep in attr if provider in dep] + +def _validate_spec(ctx, spec_file): + validated_proguard_spec = ctx.actions.declare_file( + "validated_proguard/%s/%s_valid" % (ctx.label.name, spec_file.path), + ) + + toolchain = semantics.find_java_toolchain(ctx) + + args = ctx.actions.args() + args.add("--path", spec_file) + args.add("--output", validated_proguard_spec) + + ctx.actions.run( + mnemonic = "ValidateProguard", + progress_message = "Validating proguard configuration %{input}", + executable = toolchain.proguard_allowlister, + arguments = [args], + inputs = [spec_file], + outputs = [validated_proguard_spec], + toolchain = Label(semantics.JAVA_TOOLCHAIN_TYPE), + use_default_shell_env = True, + ) + + return validated_proguard_spec + +def validate_proguard_specs(ctx, proguard_specs = [], transitive_attrs = []): + """ + Creates actions that validate Proguard specification and returns ProguardSpecProvider. + + Use transtive_attrs parameter to collect Proguard validations from `deps`, + `runtime_deps`, `exports`, `plugins`, and `exported_plugins` attributes. + + Args: + ctx: (RuleContext) Used to register the actions. + proguard_specs: (list[File]) List of Proguard specs files. + transitive_attrs: (list[list[Target]]) Attributes to collect transitive + proguard validations from. + Returns: + (ProguardSpecProvider) A ProguardSpecProvider. + """ + proguard_validations = _filter_provider(ProguardSpecInfo, *transitive_attrs) + return ProguardSpecInfo( + depset( + [_validate_spec(ctx, spec_file) for spec_file in proguard_specs], + transitive = [validation.specs for validation in proguard_validations], + ), + ) diff --git a/java/common/rules/java_binary.bzl b/java/common/rules/java_binary.bzl new file mode 100644 index 00000000..609ca974 --- /dev/null +++ b/java/common/rules/java_binary.bzl @@ -0,0 +1,374 @@ +# Copyright 2022 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" Implementation of java_binary for bazel """ + +load("@bazel_skylib//lib:paths.bzl", "paths") +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") +load("//java/common:java_semantics.bzl", "semantics") +load("//java/private:java_common.bzl", "java_common") +load("//java/private:java_info.bzl", "JavaInfo", "JavaPluginInfo") +load(":basic_java_library.bzl", "BASIC_JAVA_LIBRARY_IMPLICIT_ATTRS") +load(":rule_util.bzl", "merge_attrs") + +# copybara: default visibility + +BootClassPathInfo = java_common.BootClassPathInfo +_PLATFORMS_ROOT = semantics.PLATFORMS_ROOT + +BASIC_JAVA_BINARY_ATTRIBUTES = merge_attrs( + BASIC_JAVA_LIBRARY_IMPLICIT_ATTRS, + # buildifier: disable=attr-licenses + { + "srcs": attr.label_list( + allow_files = [".java", ".srcjar", ".properties"] + semantics.EXTRA_SRCS_TYPES, + flags = ["DIRECT_COMPILE_TIME_INPUT", "ORDER_INDEPENDENT"], + doc = """ +The list of source files that are processed to create the target. +This attribute is almost always required; see exceptions below. +

+Source files of type .java are compiled. In case of generated +.java files it is generally advisable to put the generating rule's name +here instead of the name of the file itself. This not only improves readability but +makes the rule more resilient to future changes: if the generating rule generates +different files in the future, you only need to fix one place: the outs of +the generating rule. You should not list the generating rule in deps +because it is a no-op. +

+

+Source files of type .srcjar are unpacked and compiled. (This is useful if +you need to generate a set of .java files with a genrule.) +

+

+Rules: if the rule (typically genrule or filegroup) generates +any of the files listed above, they will be used the same way as described for source +files. +

+ +

+This argument is almost always required, except if a +main_class attribute specifies a +class on the runtime classpath or you specify the runtime_deps argument. +

+ """, + ), + "deps": attr.label_list( + allow_files = [".jar"], + allow_rules = semantics.ALLOWED_RULES_IN_DEPS + semantics.ALLOWED_RULES_IN_DEPS_WITH_WARNING, + providers = [ + [CcInfo], + [JavaInfo], + ], + flags = ["SKIP_ANALYSIS_TIME_FILETYPE_CHECK"], + doc = """ +The list of other libraries to be linked in to the target. +See general comments about deps at +Typical attributes defined by +most build rules. + """, + ), + "resources": attr.label_list( + allow_files = True, + flags = ["SKIP_CONSTRAINTS_OVERRIDE", "ORDER_INDEPENDENT"], + doc = """ +A list of data files to include in a Java jar. + +

+Resources may be source files or generated files. +

+ """ + semantics.DOCS.for_attribute("resources"), + ), + "runtime_deps": attr.label_list( + allow_files = [".jar"], + allow_rules = semantics.ALLOWED_RULES_IN_DEPS, + providers = [[CcInfo], [JavaInfo]], + flags = ["SKIP_ANALYSIS_TIME_FILETYPE_CHECK"], + doc = """ +Libraries to make available to the final binary or test at runtime only. +Like ordinary deps, these will appear on the runtime classpath, but unlike +them, not on the compile-time classpath. Dependencies needed only at runtime should be +listed here. Dependency-analysis tools should ignore targets that appear in both +runtime_deps and deps. + """, + ), + "data": attr.label_list( + allow_files = True, + flags = ["SKIP_CONSTRAINTS_OVERRIDE"], + doc = """ +The list of files needed by this library at runtime. +See general comments about data +at Typical attributes defined by +most build rules. + """ + semantics.DOCS.for_attribute("data"), + ), + "plugins": attr.label_list( + providers = [JavaPluginInfo], + allow_files = True, + cfg = "exec", + doc = """ +Java compiler plugins to run at compile-time. +Every java_plugin specified in this attribute will be run whenever this rule +is built. A library may also inherit plugins from dependencies that use +exported_plugins. Resources +generated by the plugin will be included in the resulting jar of this rule. + """, + ), + "deploy_env": attr.label_list( + providers = [java_common.JavaRuntimeClasspathInfo], + allow_files = False, + doc = """ +A list of other java_binary targets which represent the deployment +environment for this binary. +Set this attribute when building a plugin which will be loaded by another +java_binary.
Setting this attribute excludes all dependencies from +the runtime classpath (and the deploy jar) of this binary that are shared between this +binary and the targets specified in deploy_env. + """, + ), + "launcher": attr.label( + # TODO(b/295221112): add back CcLauncherInfo + allow_files = False, + doc = """ +Specify a binary that will be used to run your Java program instead of the +normal bin/java program included with the JDK. +The target must be a cc_binary. Any cc_binary that +implements the + +Java Invocation API can be specified as a value for this attribute. + +

By default, Bazel will use the normal JDK launcher (bin/java or java.exe).

+ +

The related +--java_launcher Bazel flag affects only those +java_binary and java_test targets that have +not specified a launcher attribute.

+ +

Note that your native (C++, SWIG, JNI) dependencies will be built differently +depending on whether you are using the JDK launcher or another launcher:

+ +
    +
  • If you are using the normal JDK launcher (the default), native dependencies are +built as a shared library named {name}_nativedeps.so, where +{name} is the name attribute of this java_binary rule. +Unused code is not removed by the linker in this configuration.
  • + +
  • If you are using any other launcher, native (C++) dependencies are statically +linked into a binary named {name}_nativedeps, where {name} +is the name attribute of this java_binary rule. In this case, +the linker will remove any code it thinks is unused from the resulting binary, +which means any C++ code accessed only via JNI may not be linked in unless +that cc_library target specifies alwayslink = True.
  • +
+ +

When using any launcher other than the default JDK launcher, the format +of the *_deploy.jar output changes. See the main +java_binary docs for details.

+ """, + ), + "bootclasspath": attr.label( + providers = [BootClassPathInfo], + flags = ["SKIP_CONSTRAINTS_OVERRIDE"], + doc = "Restricted API, do not use!", + ), + "neverlink": attr.bool(), + "javacopts": attr.string_list( + doc = """ +Extra compiler options for this binary. +Subject to "Make variable" substitution and +Bourne shell tokenization. +

These compiler options are passed to javac after the global compiler options.

+ """, + ), + "add_exports": attr.string_list( + doc = """ +Allow this library to access the given module or package. +

+This corresponds to the javac and JVM --add-exports= flags. + """, + ), + "add_opens": attr.string_list( + doc = """ +Allow this library to reflectively access the given module or +package. +

+This corresponds to the javac and JVM --add-opens= flags. + """, + ), + "main_class": attr.string( + doc = """ +Name of class with main() method to use as entry point. +If a rule uses this option, it does not need a srcs=[...] list. +Thus, with this attribute one can make an executable from a Java library that already +contains one or more main() methods. +

+The value of this attribute is a class name, not a source file. The class must be +available at runtime: it may be compiled by this rule (from srcs) or +provided by direct or transitive dependencies (through runtime_deps or +deps). If the class is unavailable, the binary will fail at runtime; there +is no build-time check. +

+ """, + ), + "jvm_flags": attr.string_list( + doc = """ +A list of flags to embed in the wrapper script generated for running this binary. +Subject to $(location) and +"Make variable" substitution, and +Bourne shell tokenization. + +

The wrapper script for a Java binary includes a CLASSPATH definition +(to find all the dependent jars) and invokes the right Java interpreter. +The command line generated by the wrapper script includes the name of +the main class followed by a "$@" so you can pass along other +arguments after the classname. However, arguments intended for parsing +by the JVM must be specified before the classname on the command +line. The contents of jvm_flags are added to the wrapper +script before the classname is listed.

+ +

Note that this attribute has no effect on *_deploy.jar +outputs.

+ """, + ), + "deploy_manifest_lines": attr.string_list( + doc = """ +A list of lines to add to the META-INF/manifest.mf file generated for the +*_deploy.jar target. The contents of this attribute are not subject +to "Make variable" substitution. + """, + ), + "stamp": attr.int( + default = -1, + values = [-1, 0, 1], + doc = """ +Whether to encode build information into the binary. Possible values: +
    +
  • + stamp = 1: Always stamp the build information into the binary, even in + --nostamp builds. This + setting should be avoided, since it potentially kills remote caching for the + binary and any downstream actions that depend on it. +
  • +
  • + stamp = 0: Always replace build information by constant values. This + gives good build result caching. +
  • +
  • + stamp = -1: Embedding of build information is controlled by the + --[no]stamp flag. +
  • +
+

Stamped binaries are not rebuilt unless their dependencies change.

+ """, + ), + "use_testrunner": attr.bool( + default = False, + doc = semantics.DOCS.for_attribute("use_testrunner") + """ +
+You can use this to override the default +behavior, which is to use test runner for +java_test rules, +and not use it for java_binary rules. It is unlikely +you will want to do this. One use is for AllTest +rules that are invoked by another rule (to set up a database +before running the tests, for example). The AllTest +rule must be declared as a java_binary, but should +still use the test runner as its main entry point. + +The name of a test runner class can be overridden with main_class attribute. + """, + ), + "use_launcher": attr.bool( + default = True, + doc = """ +Whether the binary should use a custom launcher. + +

If this attribute is set to false, the +launcher attribute and the related +--java_launcher flag +will be ignored for this target. + """, + ), + "env": attr.string_dict(), + "classpath_resources": attr.label_list( + allow_files = True, + doc = """ +DO NOT USE THIS OPTION UNLESS THERE IS NO OTHER WAY +

+A list of resources that must be located at the root of the java tree. This attribute's +only purpose is to support third-party libraries that require that their resources be +found on the classpath as exactly "myconfig.xml". It is only allowed on +binaries and not libraries, due to the danger of namespace conflicts. +

+ """, + ), + "licenses": attr.license() if hasattr(attr, "license") else attr.string_list(), + "_stub_template": attr.label( + default = semantics.JAVA_STUB_TEMPLATE_LABEL, + allow_single_file = True, + ), + "_java_toolchain_type": attr.label(default = semantics.JAVA_TOOLCHAIN_TYPE), + "_windows_constraints": attr.label_list( + default = [paths.join(_PLATFORMS_ROOT, "os:windows")], + ), + "_build_info_translator": attr.label(default = semantics.BUILD_INFO_TRANSLATOR_LABEL), + } | ({} if semantics.INCOMPATIBLE_DISABLE_NON_EXECUTABLE_JAVA_BINARY else {"create_executable": attr.bool(default = True, doc = "Deprecated, use java_single_jar instead.")}), +) + +BASE_TEST_ATTRIBUTES = { + "test_class": attr.string( + doc = """ +The Java class to be loaded by the test runner.
+

+ By default, if this argument is not defined then the legacy mode is used and the + test arguments are used instead. Set the --nolegacy_bazel_java_test flag + to not fallback on the first argument. +

+

+ This attribute specifies the name of a Java class to be run by + this test. It is rare to need to set this. If this argument is omitted, + it will be inferred using the target's name and its + source-root-relative path. If the test is located outside a known + source root, Bazel will report an error if test_class + is unset. +

+

+ For JUnit3, the test class needs to either be a subclass of + junit.framework.TestCase or it needs to have a public + static suite() method that returns a + junit.framework.Test (or a subclass of Test). +

+

+ This attribute allows several java_test rules to + share the same Test + (TestCase, TestSuite, ...). Typically + additional information is passed to it + (e.g. via jvm_flags=['-Dkey=value']) so that its + behavior differs in each case, such as running a different + subset of the tests. This attribute also enables the use of + Java tests outside the javatests tree. +

+ """, + ), + "env_inherit": attr.string_list(), + "_apple_constraints": attr.label_list( + default = [ + paths.join(_PLATFORMS_ROOT, "os:ios"), + paths.join(_PLATFORMS_ROOT, "os:macos"), + paths.join(_PLATFORMS_ROOT, "os:tvos"), + paths.join(_PLATFORMS_ROOT, "os:visionos"), + paths.join(_PLATFORMS_ROOT, "os:watchos"), + ], + ), + "_legacy_any_type_attrs": attr.string_list(default = ["stamp"]), +} diff --git a/java/common/rules/java_binary_wrapper.bzl b/java/common/rules/java_binary_wrapper.bzl new file mode 100644 index 00000000..89b423b4 --- /dev/null +++ b/java/common/rules/java_binary_wrapper.bzl @@ -0,0 +1,73 @@ +# Copyright 2022 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Macro encapsulating the java_binary implementation + +This is needed since the `executable` nature of the target must be computed from +the supplied value of the `create_executable` attribute. +""" + +load("//java/common:java_semantics.bzl", "semantics") + +# copybara: default visibility + +def register_legacy_java_binary_rules( + rule_exec, + rule_nonexec, + **kwargs): + """Registers the correct java_binary rule and deploy jar rule + + Args: + rule_exec: (Rule) The executable java_binary rule + rule_nonexec: (Rule) The non-executable java_binary rule + **kwargs: Actual args to instantiate the rule + """ + + create_executable = "create_executable" not in kwargs or kwargs["create_executable"] + + # TODO(hvd): migrate depot to integers / maybe use decompose_select_list() + if "stamp" in kwargs and type(kwargs["stamp"]) == type(True): + kwargs["stamp"] = 1 if kwargs["stamp"] else 0 + if not create_executable: + rule_nonexec(**kwargs) + else: + if "use_launcher" in kwargs and not kwargs["use_launcher"]: + kwargs["launcher"] = None + else: + # If launcher is not set or None, set it to config flag + if "launcher" not in kwargs or not kwargs["launcher"]: + kwargs["launcher"] = semantics.LAUNCHER_FLAG_LABEL + rule_exec(**kwargs) + +def register_java_binary_rules( + java_binary, + **kwargs): + """Creates a java_binary rule and a deploy jar rule + + Args: + java_binary: (Rule) The executable java_binary rule + **kwargs: Actual args to instantiate the rule + """ + + # TODO(hvd): migrate depot to integers / maybe use decompose_select_list() + if "stamp" in kwargs and type(kwargs["stamp"]) == type(True): + kwargs["stamp"] = 1 if kwargs["stamp"] else 0 + + if "use_launcher" in kwargs and not kwargs["use_launcher"]: + kwargs["launcher"] = None + else: + # If launcher is not set or None, set it to config flag + if "launcher" not in kwargs or not kwargs["launcher"]: + kwargs["launcher"] = semantics.LAUNCHER_FLAG_LABEL + java_binary(**kwargs) diff --git a/java/common/rules/java_helper.bzl b/java/common/rules/java_helper.bzl new file mode 100644 index 00000000..ee274ade --- /dev/null +++ b/java/common/rules/java_helper.bzl @@ -0,0 +1,212 @@ +# Copyright 2025 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Common util functions for java_* rules""" + +load("@bazel_skylib//lib:paths.bzl", "paths") +load("//java/common:java_semantics.bzl", "semantics") + +# copybara: rules_java visibility + +def _java_segments(path): + if path.startswith("/"): + fail("path must not be absolute: '%s'" % path) + segments = path.split("/") + root_idx = -1 + for idx, segment in enumerate(segments): + if segment in ["java", "javatests", "src", "testsrc"]: + root_idx = idx + break + if root_idx < 0: + return None + is_src = "src" == segments[root_idx] + check_mvn_idx = root_idx if is_src else -1 + if (root_idx == 0 or is_src): + for i in range(root_idx + 1, len(segments) - 1): + segment = segments[i] + if "src" == segment or (is_src and (segment in ["java", "javatests"])): + next = segments[i + 1] + if next in ["com", "org", "net"]: + root_idx = i + elif "src" == segment: + check_mvn_idx = i + break + + if check_mvn_idx >= 0 and check_mvn_idx < len(segments) - 2: + next = segments[check_mvn_idx + 1] + if next in ["main", "test"]: + next = segments[check_mvn_idx + 2] + if next in ["java", "resources"]: + root_idx = check_mvn_idx + 2 + return segments[(root_idx + 1):] + +def _has_target_constraints(ctx, constraints): + # Constraints is a label_list. + for constraint in constraints: + constraint_value = constraint[platform_common.ConstraintValueInfo] + if ctx.target_platform_has_constraint(constraint_value): + return True + return False + +def _is_target_platform_windows(ctx): + return _has_target_constraints(ctx, ctx.attr._windows_constraints) + +def _resource_mapper(file): + root_relative_path = paths.relativize( + path = file.path, + start = paths.join(file.root.path, file.owner.workspace_root), + ) + return "%s:%s" % ( + file.path, + semantics.get_default_resource_path(root_relative_path, segment_extractor = _java_segments), + ) + +def _create_single_jar( + actions, + toolchain, + output, + sources = depset(), + resources = depset(), + mnemonic = "JavaSingleJar", + progress_message = "Building singlejar jar %{output}", + build_target = None, + output_creator = None): + """Register singlejar action for the output jar. + + Args: + actions: (actions) ctx.actions + toolchain: (JavaToolchainInfo) The java toolchain + output: (File) Output file of the action. + sources: (depset[File]) The jar files to merge into the output jar. + resources: (depset[File]) The files to add to the output jar. + mnemonic: (str) The action identifier + progress_message: (str) The action progress message + build_target: (Label) The target label to stamp in the manifest. Optional. + output_creator: (str) The name of the tool to stamp in the manifest. Optional, + defaults to 'singlejar' + Returns: + (File) Output file which was used for registering the action. + """ + args = actions.args() + args.set_param_file_format("shell").use_param_file("@%s", use_always = True) + args.add("--output", output) + args.add_all( + [ + "--compression", + "--normalize", + "--exclude_build_data", + "--warn_duplicate_resources", + ], + ) + args.add_all("--sources", sources) + args.add_all("--resources", resources, map_each = _resource_mapper) + + args.add("--build_target", build_target) + args.add("--output_jar_creator", output_creator) + + actions.run( + mnemonic = mnemonic, + progress_message = progress_message, + executable = toolchain.single_jar, + toolchain = semantics.JAVA_TOOLCHAIN_TYPE, + inputs = depset(transitive = [resources, sources]), + tools = [toolchain.single_jar], + outputs = [output], + arguments = [args], + use_default_shell_env = True, + ) + return output + +# TODO(hvd): use skylib shell.quote() +def _shell_escape(s): + """Shell-escape a string + + Quotes a word so that it can be used, without further quoting, as an argument + (or part of an argument) in a shell command. + + Args: + s: (str) the string to escape + + Returns: + (str) the shell-escaped string + """ + if not s: + # Empty string is a special case: needs to be quoted to ensure that it + # gets treated as a separate argument. + return "''" + for c in s.elems(): + # We do this positively so as to be sure we don't inadvertently forget + # any unsafe characters. + if not c.isalnum() and c not in "@%-_+:,./": + return "'" + s.replace("'", "'\\''") + "'" + return s + +def _detokenize_javacopts(opts): + """Detokenizes a list of options to a depset. + + Args: + opts: ([str]) the javac options to detokenize + + Returns: + (depset[str]) depset of detokenized options + """ + return depset( + [" ".join([_shell_escape(opt) for opt in opts])], + order = "preorder", + ) + +def _get_relative(path_a, path_b): + if paths.is_absolute(path_b): + return path_b + return paths.normalize(paths.join(path_a, path_b)) + +def _tokenize_javacopts(ctx = None, opts = []): + """Tokenizes a list or depset of options to a list. + + Iff opts is a depset, we reverse the flattened list to ensure right-most + duplicates are preserved in their correct position. + + If the ctx parameter is omitted, a slow, but pure Starlark, implementation + of shell tokenization is used. Otherwise, tokenization is performed using + ctx.tokenize() which has significantly better performance (up to 100x for + large options lists). + + Args: + ctx: (RuleContext|None) the rule context + opts: (depset[str]|[str]) the javac options to tokenize + Returns: + [str] list of tokenized options + """ + if hasattr(opts, "to_list"): + opts = reversed(opts.to_list()) + if ctx: + return [ + token + for opt in opts + for token in ctx.tokenize(opt) + ] + else: + # TODO: optimize and use the pure Starlark implementation in cc_helper + return semantics.tokenize_javacopts(opts) + +helper = struct( + is_target_platform_windows = _is_target_platform_windows, + create_single_jar = _create_single_jar, + shell_escape = _shell_escape, + detokenize_javacopts = _detokenize_javacopts, + tokenize_javacopts = _tokenize_javacopts, + get_relative = _get_relative, + has_target_constraints = _has_target_constraints, + java_segments = _java_segments, +) diff --git a/java/common/rules/java_import.bzl b/java/common/rules/java_import.bzl new file mode 100644 index 00000000..a6e9712f --- /dev/null +++ b/java/common/rules/java_import.bzl @@ -0,0 +1,128 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Definition of java_import rule. +""" + +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") +load("//java/common:java_semantics.bzl", "semantics") +load("//java/private:java_info.bzl", "JavaInfo") + +# copybara: default visibility + +_ALLOWED_RULES_IN_DEPS_FOR_JAVA_IMPORT = [ + "java_library", + "java_import", + "cc_library", + "cc_binary", +] + +# buildifier: disable=attr-licenses +JAVA_IMPORT_ATTRS = { + "data": attr.label_list( + allow_files = True, + flags = ["SKIP_CONSTRAINTS_OVERRIDE"], + doc = """ +The list of files needed by this rule at runtime. + """, + ), + "deps": attr.label_list( + providers = [JavaInfo], + allow_rules = _ALLOWED_RULES_IN_DEPS_FOR_JAVA_IMPORT, + doc = """ +The list of other libraries to be linked in to the target. +See java_library.deps. + """, + ), + "exports": attr.label_list( + providers = [JavaInfo], + allow_rules = _ALLOWED_RULES_IN_DEPS_FOR_JAVA_IMPORT, + doc = """ +Targets to make available to users of this rule. +See java_library.exports. + """, + ), + "runtime_deps": attr.label_list( + allow_files = [".jar"], + allow_rules = _ALLOWED_RULES_IN_DEPS_FOR_JAVA_IMPORT, + providers = [[CcInfo], [JavaInfo]], + flags = ["SKIP_ANALYSIS_TIME_FILETYPE_CHECK"], + doc = """ +Libraries to make available to the final binary or test at runtime only. +See java_library.runtime_deps. + """, + ), + # JavaImportBazeRule attr + "jars": attr.label_list( + allow_files = [".jar"], + mandatory = True, + doc = """ +The list of JAR files provided to Java targets that depend on this target. + """, + ), + "srcjar": attr.label( + allow_single_file = [".srcjar", ".jar"], + flags = ["DIRECT_COMPILE_TIME_INPUT"], + doc = """ +A JAR file that contains source code for the compiled JAR files. + """, + ), + "neverlink": attr.bool( + default = False, + doc = """ +Only use this library for compilation and not at runtime. +Useful if the library will be provided by the runtime environment +during execution. Examples of libraries like this are IDE APIs +for IDE plug-ins or tools.jar for anything running on +a standard JDK. + """, + ), + "constraints": attr.string_list( + doc = """ +Extra constraints imposed on this rule as a Java library. + """, + ), + # ProguardLibraryRule attr + "proguard_specs": attr.label_list( + allow_files = True, + doc = """ +Files to be used as Proguard specification. +These will describe the set of specifications to be used by Proguard. If specified, +they will be added to any android_binary target depending on this library. + +The files included here must only have idempotent rules, namely -dontnote, -dontwarn, +assumenosideeffects, and rules that start with -keep. Other options can only appear in +android_binary's proguard_specs, to ensure non-tautological merges. + """, + ), + # Additional attrs + "add_exports": attr.string_list( + doc = """ +Allow this library to access the given module or package. +

+This corresponds to the javac and JVM --add-exports= flags. + """, + ), + "add_opens": attr.string_list( + doc = """ +Allow this library to reflectively access the given module or +package. +

+This corresponds to the javac and JVM --add-opens= flags. + """, + ), + "licenses": attr.license() if hasattr(attr, "license") else attr.string_list(), + "_java_toolchain_type": attr.label(default = semantics.JAVA_TOOLCHAIN_TYPE), +} diff --git a/java/common/rules/java_library.bzl b/java/common/rules/java_library.bzl new file mode 100644 index 00000000..77861ac5 --- /dev/null +++ b/java/common/rules/java_library.bzl @@ -0,0 +1,272 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Definition of java_library rule. +""" + +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") +load("//java/common:java_semantics.bzl", "semantics") +load("//java/private:java_common.bzl", "java_common") +load("//java/private:java_info.bzl", "JavaInfo", "JavaPluginInfo") +load(":basic_java_library.bzl", "BASIC_JAVA_LIBRARY_IMPLICIT_ATTRS") +load(":rule_util.bzl", "merge_attrs") + +# copybara: default visibility + +BootClassPathInfo = java_common.BootClassPathInfo + +JAVA_LIBRARY_IMPLICIT_ATTRS = BASIC_JAVA_LIBRARY_IMPLICIT_ATTRS + +JAVA_LIBRARY_ATTRS = merge_attrs( + JAVA_LIBRARY_IMPLICIT_ATTRS, + # buildifier: disable=attr-licenses + { + "srcs": attr.label_list( + allow_files = [".java", ".srcjar", ".properties"] + semantics.EXTRA_SRCS_TYPES, + flags = ["DIRECT_COMPILE_TIME_INPUT", "ORDER_INDEPENDENT"], + doc = """ +The list of source files that are processed to create the target. +This attribute is almost always required; see exceptions below. +

+Source files of type .java are compiled. In case of generated +.java files it is generally advisable to put the generating rule's name +here instead of the name of the file itself. This not only improves readability but +makes the rule more resilient to future changes: if the generating rule generates +different files in the future, you only need to fix one place: the outs of +the generating rule. You should not list the generating rule in deps +because it is a no-op. +

+

+Source files of type .srcjar are unpacked and compiled. (This is useful if +you need to generate a set of .java files with a genrule.) +

+

+Rules: if the rule (typically genrule or filegroup) generates +any of the files listed above, they will be used the same way as described for source +files. +

+

+Source files of type .properties are treated as resources. +

+ +

All other files are ignored, as long as there is at least one file of a +file type described above. Otherwise an error is raised.

+ +

+This argument is almost always required, except if you specify the runtime_deps argument. +

+ """, + ), + "data": attr.label_list( + allow_files = True, + flags = ["SKIP_CONSTRAINTS_OVERRIDE"], + doc = """ +The list of files needed by this library at runtime. +See general comments about data at +Typical attributes defined by +most build rules. +

+ When building a java_library, Bazel doesn't put these files anywhere; if the + data files are generated files then Bazel generates them. When building a + test that depends on this java_library Bazel copies or links the + data files into the runfiles area. +

+ """ + semantics.DOCS.for_attribute("data"), + ), + "resources": attr.label_list( + allow_files = True, + flags = ["SKIP_CONSTRAINTS_OVERRIDE", "ORDER_INDEPENDENT"], + doc = """ +A list of data files to include in a Java jar. +

+Resources may be source files or generated files. +

+ """ + semantics.DOCS.for_attribute("resources"), + ), + "plugins": attr.label_list( + providers = [JavaPluginInfo], + allow_files = True, + cfg = "exec", + doc = """ +Java compiler plugins to run at compile-time. +Every java_plugin specified in this attribute will be run whenever this rule +is built. A library may also inherit plugins from dependencies that use +exported_plugins. Resources +generated by the plugin will be included in the resulting jar of this rule. + """, + ), + "deps": attr.label_list( + allow_files = [".jar"], + allow_rules = semantics.ALLOWED_RULES_IN_DEPS + semantics.ALLOWED_RULES_IN_DEPS_WITH_WARNING, + providers = [ + [CcInfo], + [JavaInfo], + ], + flags = ["SKIP_ANALYSIS_TIME_FILETYPE_CHECK"], + doc = """ +The list of libraries to link into this library. +See general comments about deps at +Typical attributes defined by +most build rules. +

+ The jars built by java_library rules listed in deps will be on + the compile-time classpath of this rule. Furthermore the transitive closure of their + deps, runtime_deps and exports will be on the + runtime classpath. +

+

+ By contrast, targets in the data attribute are included in the runfiles but + on neither the compile-time nor runtime classpath. +

+ """, + ), + "runtime_deps": attr.label_list( + allow_files = [".jar"], + allow_rules = semantics.ALLOWED_RULES_IN_DEPS, + providers = [[CcInfo], [JavaInfo]], + flags = ["SKIP_ANALYSIS_TIME_FILETYPE_CHECK"], + doc = """ +Libraries to make available to the final binary or test at runtime only. +Like ordinary deps, these will appear on the runtime classpath, but unlike +them, not on the compile-time classpath. Dependencies needed only at runtime should be +listed here. Dependency-analysis tools should ignore targets that appear in both +runtime_deps and deps. + """, + ), + "exports": attr.label_list( + allow_rules = semantics.ALLOWED_RULES_IN_DEPS, + providers = [[JavaInfo], [CcInfo]], + doc = """ +Exported libraries. +

+ Listing rules here will make them available to parent rules, as if the parents explicitly + depended on these rules. This is not true for regular (non-exported) deps. +

+

+ Summary: a rule X can access the code in Y if there exists a dependency + path between them that begins with a deps edge followed by zero or more + exports edges. Let's see some examples to illustrate this. +

+

+ Assume A depends on B and B depends on C. In this case + C is a transitive dependency of A, so changing C's sources and rebuilding A will + correctly rebuild everything. However A will not be able to use classes in C. To allow + that, either A has to declare C in its deps, or B can make it easier for A + (and anything that may depend on A) by declaring C in its (B's) exports + attribute. +

+

+ The closure of exported libraries is available to all direct parent rules. Take a slightly + different example: A depends on B, B depends on C and D, and also exports C but not D. + Now A has access to C but not to D. Now, if C and D exported some libraries, C' and D' + respectively, A could only access C' but not D'. +

+

+ Important: an exported rule is not a regular dependency. Sticking to the previous example, + if B exports C and wants to also use C, it has to also list it in its own + deps. +

+ """, + ), + "exported_plugins": attr.label_list( + providers = [JavaPluginInfo], + cfg = "exec", + doc = """ +The list of java_plugins (e.g. annotation +processors) to export to libraries that directly depend on this library. +

+ The specified list of java_plugins will be applied to any library which + directly depends on this library, just as if that library had explicitly declared these + labels in plugins. +

+ """, + ), + "bootclasspath": attr.label( + providers = [BootClassPathInfo], + flags = ["SKIP_CONSTRAINTS_OVERRIDE"], + doc = """Restricted API, do not use!""", + ), + "javabuilder_jvm_flags": attr.string_list(doc = """Restricted API, do not use!"""), + "javacopts": attr.string_list( + doc = """ +Extra compiler options for this library. +Subject to "Make variable" substitution and +Bourne shell tokenization. +

These compiler options are passed to javac after the global compiler options.

+ """, + ), + "neverlink": attr.bool( + doc = """ +Whether this library should only be used for compilation and not at runtime. +Useful if the library will be provided by the runtime environment during execution. Examples +of such libraries are the IDE APIs for IDE plug-ins or tools.jar for anything +running on a standard JDK. +

+ Note that neverlink = True does not prevent the compiler from inlining material + from this library into compilation targets that depend on it, as permitted by the Java + Language Specification (e.g., static final constants of String + or of primitive types). The preferred use case is therefore when the runtime library is + identical to the compilation library. +

+

+ If the runtime library differs from the compilation library then you must ensure that it + differs only in places that the JLS forbids compilers to inline (and that must hold for + all future versions of the JLS). +

+ """, + ), + "resource_strip_prefix": attr.string( + doc = """ +The path prefix to strip from Java resources. +

+If specified, this path prefix is stripped from every file in the resources +attribute. It is an error for a resource file not to be under this directory. If not +specified (the default), the path of resource file is determined according to the same +logic as the Java package of source files. For example, a source file at +stuff/java/foo/bar/a.txt will be located at foo/bar/a.txt. +

+ """, + ), + "proguard_specs": attr.label_list( + allow_files = True, + doc = """ +Files to be used as Proguard specification. +These will describe the set of specifications to be used by Proguard. If specified, +they will be added to any android_binary target depending on this library. + +The files included here must only have idempotent rules, namely -dontnote, -dontwarn, +assumenosideeffects, and rules that start with -keep. Other options can only appear in +android_binary's proguard_specs, to ensure non-tautological merges. + """, + ), + "add_exports": attr.string_list( + doc = """ +Allow this library to access the given module or package. +

+This corresponds to the javac and JVM --add-exports= flags. + """, + ), + "add_opens": attr.string_list( + doc = """ +Allow this library to reflectively access the given module or +package. +

+This corresponds to the javac and JVM --add-opens= flags. + """, + ), + "licenses": attr.license() if hasattr(attr, "license") else attr.string_list(), + "_java_toolchain_type": attr.label(default = semantics.JAVA_TOOLCHAIN_TYPE), + }, +) diff --git a/java/common/rules/java_package_configuration.bzl b/java/common/rules/java_package_configuration.bzl new file mode 100644 index 00000000..a1abcd05 --- /dev/null +++ b/java/common/rules/java_package_configuration.bzl @@ -0,0 +1,124 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Implementation for the java_package_configuration rule""" + +load("//java/common/rules:java_helper.bzl", "helper") +load("//java/private:boot_class_path_info.bzl", "BootClassPathInfo") +load("//java/private:native.bzl", "get_internal_java_common") + +# copybara: rules_java visibility + +JavaPackageConfigurationInfo = provider( + "A provider for Java per-package configuration", + fields = [ + "data", + "javac_opts", + "matches", + "package_specs", + "system", + ], +) + +def _matches(package_specs, label): + for spec in package_specs: + if spec.contains(label): + return True + return False + +def _rule_impl(ctx): + javacopts = get_internal_java_common().expand_java_opts(ctx, "javacopts", tokenize = True) + javacopts_depset = helper.detokenize_javacopts(javacopts) + package_specs = [package[PackageSpecificationInfo] for package in ctx.attr.packages] + system = ctx.attr.system[BootClassPathInfo] if ctx.attr.system else None + return [ + DefaultInfo(), + JavaPackageConfigurationInfo( + data = depset(ctx.files.data), + javac_opts = javacopts_depset, + matches = _matches, + package_specs = package_specs, + system = system, + ), + ] + +java_package_configuration = rule( + implementation = _rule_impl, + doc = """ +

+Configuration to apply to a set of packages. +Configurations can be added to +java_toolchain.javacoptss. +

+ +

Example:

+ +
+
+
+java_package_configuration(
+    name = "my_configuration",
+    packages = [":my_packages"],
+    javacopts = ["-Werror"],
+)
+
+package_group(
+    name = "my_packages",
+    packages = [
+        "//com/my/project/...",
+        "-//com/my/project/testing/...",
+    ],
+)
+
+java_toolchain(
+    ...,
+    package_configuration = [
+        ":my_configuration",
+    ]
+)
+
+
+
+ """, + attrs = { + "packages": attr.label_list( + cfg = "exec", + providers = [PackageSpecificationInfo], + doc = """ +The set of package_groups +the configuration should be applied to. + """, + ), + "javacopts": attr.string_list( + doc = """ +Java compiler flags. + """, + ), + "data": attr.label_list( + cfg = "exec", + allow_files = True, + doc = """ +The list of files needed by this configuration at runtime. + """, + ), + "system": attr.label( + providers = [BootClassPathInfo], + doc = """ +Corresponds to javac's --system flag. +""", + ), + # buildifier: disable=attr-licenses + "output_licenses": attr.string_list(), + }, +) diff --git a/java/common/rules/java_plugin.bzl b/java/common/rules/java_plugin.bzl new file mode 100644 index 00000000..569c77ff --- /dev/null +++ b/java/common/rules/java_plugin.bzl @@ -0,0 +1,52 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Definition of java_plugin rule. +""" + +load(":java_library.bzl", "JAVA_LIBRARY_ATTRS") +load(":rule_util.bzl", "merge_attrs") + +# copybara: default visibility + +JAVA_PLUGIN_ATTRS = merge_attrs( + JAVA_LIBRARY_ATTRS, + { + "generates_api": attr.bool(doc = """ +This attribute marks annotation processors that generate API code. +

If a rule uses an API-generating annotation processor, other rules +depending on it can refer to the generated code only if their +compilation actions are scheduled after the generating rule. This +attribute instructs Bazel to introduce scheduling constraints when +--java_header_compilation is enabled. +

WARNING: This attribute affects build +performance, use it only if necessary.

+ """), + "processor_class": attr.string(doc = """ +The processor class is the fully qualified type of the class that the Java compiler should +use as entry point to the annotation processor. If not specified, this rule will not +contribute an annotation processor to the Java compiler's annotation processing, but its +runtime classpath will still be included on the compiler's annotation processor path. (This +is primarily intended for use by +Error Prone plugins, which are loaded +from the annotation processor path using + +java.util.ServiceLoader.) + """), + # buildifier: disable=attr-licenses + "output_licenses": attr.string_list(), + }, + remove_attrs = ["runtime_deps", "exports", "exported_plugins"], +) diff --git a/java/common/rules/java_runtime.bzl b/java/common/rules/java_runtime.bzl new file mode 100644 index 00000000..2433549c --- /dev/null +++ b/java/common/rules/java_runtime.bzl @@ -0,0 +1,260 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Definition of java_runtime rule and JavaRuntimeInfo provider. +""" + +load("@bazel_skylib//lib:paths.bzl", "paths") +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules:java_helper.bzl", "helper") + +# copybara: default visibility + +ToolchainInfo = platform_common.ToolchainInfo + +def _init_java_runtime_info(**_kwargs): + fail("instantiating JavaRuntimeInfo is a private API") + +JavaRuntimeInfo, _new_javaruntimeinfo = provider( + doc = "Information about the Java runtime used by the java rules.", + fields = { + "default_cds": "Returns the JDK default CDS archive.", + "files": "Returns the files in the Java runtime.", + "hermetic_files": "Returns the files in the Java runtime needed for hermetic deployments.", + "hermetic_static_libs": "Returns the JDK static libraries.", + "java_executable_exec_path": "Returns the execpath of the Java executable.", + "java_executable_runfiles_path": """Returns the path of the Java executable in + runfiles trees. This should only be used when one needs to access the + JVM during the execution of a binary or a test built by Bazel. In particular, + when one needs to invoke the JVM during an action, java_executable_exec_path + should be used instead.""", + "java_home": "Returns the execpath of the root of the Java installation.", + "java_home_runfiles_path": """Returns the path of the Java installation in runfiles trees. + This should only be used when one needs to access the JDK during the execution + of a binary or a test built by Bazel. In particular, when one needs the JDK + during an action, java_home should be used instead.""", + "lib_ct_sym": "Returns the lib/ct.sym file.", + "lib_modules": "Returns the lib/modules file.", + "version": "The Java feature version of the runtime. This is 0 if the version is unknown.", + }, + init = _init_java_runtime_info, +) + +def _is_main_repo(label): + return label.repo_name == "" + +def _default_java_home(label): + if _is_main_repo(label): + return label.package + else: + return helper.get_relative(label.workspace_root, label.package) + +def _get_bin_java(ctx): + is_windows = helper.is_target_platform_windows(ctx) + return "bin/java.exe" if is_windows else "bin/java" + +def _get_runfiles_java_executable(ctx, java_home, label): + if paths.is_absolute(java_home) or _is_main_repo(label): + return helper.get_relative(java_home, _get_bin_java(ctx)) + else: + repo_runfiles_path = "" if _is_main_repo(label) else helper.get_relative("..", label.repo_name) + return helper.get_relative(repo_runfiles_path, _get_bin_java(ctx)) + +def _is_java_binary(path): + return path.endswith("bin/java") or path.endswith("bin/java.exe") + +def _get_lib_ct_sym(srcs, explicit_lib_ct_sym): + if explicit_lib_ct_sym: + return explicit_lib_ct_sym + candidates = [src for src in srcs if src.path.endswith("/lib/ct.sym")] + if len(candidates) == 1: + return candidates[0] + else: + return None + +def _java_runtime_rule_impl(ctx): + all_files = [] # [depset[File]] + all_files.append(depset(ctx.files.srcs)) + + java_home = _default_java_home(ctx.label) + if ctx.attr.java_home: + java_home_attr = ctx.expand_make_variables("java_home", ctx.attr.java_home, {}) + if ctx.files.srcs and paths.is_absolute(java_home_attr): + fail("'java_home' with an absolute path requires 'srcs' to be empty.") + java_home = helper.get_relative(java_home, java_home_attr) + + java_binary_exec_path = helper.get_relative(java_home, _get_bin_java(ctx)) + java_binary_runfiles_path = _get_runfiles_java_executable(ctx, java_home, ctx.label) + + java = ctx.file.java + if java: + if paths.is_absolute(java_home): + fail("'java_home' with an absolute path requires 'java' to be empty.") + java_binary_exec_path = java.path + java_binary_runfiles_path = java.short_path + if not _is_java_binary(java_binary_exec_path): + fail("the path to 'java' must end in 'bin/java'.") + java_home = paths.dirname(paths.dirname(java_binary_exec_path)) + all_files.append(depset([java])) + + java_home_runfiles_path = paths.dirname(paths.dirname(java_binary_runfiles_path)) + + hermetic_inputs = depset(ctx.files.hermetic_srcs) + all_files.append(hermetic_inputs) + + lib_ct_sym = _get_lib_ct_sym(ctx.files.srcs, ctx.file.lib_ct_sym) + lib_modules = ctx.file.lib_modules + hermetic_static_libs = [dep[CcInfo] for dep in ctx.attr.hermetic_static_libs] + + # If a runtime does not set default_cds in hermetic mode, it is not fatal. + # We can skip the default CDS in the check below. + default_cds = ctx.file.default_cds + + if (hermetic_inputs or lib_modules or hermetic_static_libs) and ( + not hermetic_inputs or not lib_modules or not hermetic_static_libs + ): + fail("hermetic specified, all of java_runtime.lib_modules, java_runtime.hermetic_srcs and java_runtime.hermetic_static_libs must be specified") + + files = depset(transitive = all_files) + + java_runtime_info = _new_javaruntimeinfo( + default_cds = default_cds, + files = files, + hermetic_files = hermetic_inputs, + hermetic_static_libs = hermetic_static_libs, + java_executable_exec_path = java_binary_exec_path, + java_executable_runfiles_path = java_binary_runfiles_path, + java_home = java_home, + java_home_runfiles_path = java_home_runfiles_path, + lib_ct_sym = lib_ct_sym, + lib_modules = lib_modules, + version = ctx.attr.version, + ) + return [ + DefaultInfo( + files = files, + runfiles = ctx.runfiles(transitive_files = files), + ), + java_runtime_info, + platform_common.TemplateVariableInfo({ + "JAVA": java_binary_exec_path, + "JAVABASE": java_home, + "JAVA_ROOTPATH": java_binary_runfiles_path, + "JAVABASE_ROOTPATH": java_home_runfiles_path, + }), + ToolchainInfo(java_runtime = java_runtime_info), + ] + +java_runtime = rule( + implementation = _java_runtime_rule_impl, + doc = """ +

+Specifies the configuration for a Java runtime. +

+ +

Example:

+ +
+
+
+java_runtime(
+    name = "jdk-9-ea+153",
+    srcs = glob(["jdk9-ea+153/**"]),
+    java_home = "jdk9-ea+153",
+)
+
+
+
+ """, + attrs = { + "default_cds": attr.label( + allow_single_file = True, + executable = True, + cfg = "target", + doc = """ +Default CDS archive for hermetic java_runtime. When hermetic +is enabled for a java_binary target the java_runtime +default CDS is packaged in the hermetic deploy JAR. + """, + ), + "hermetic_srcs": attr.label_list( + allow_files = True, + doc = """ +Files in the runtime needed for hermetic deployments. + """, + ), + "hermetic_static_libs": attr.label_list( + providers = [CcInfo], + doc = """ +The libraries that are statically linked with the launcher for hermetic deployments + """, + ), + "java": attr.label( + allow_single_file = True, + executable = True, + cfg = "target", + doc = """ +The path to the java executable. + """, + ), + "java_home": attr.string( + doc = """ +The path to the root of the runtime. +Subject to "Make" variable substitution. +If this path is absolute, the rule denotes a non-hermetic Java runtime with a well-known +path. In that case, the srcs and java attributes must be empty. + """, + ), + "lib_ct_sym": attr.label( + allow_single_file = True, + doc = """ +The lib/ct.sym file needed for compilation with --release. If not specified and +there is exactly one file in srcs whose path ends with +/lib/ct.sym, that file is used. + """, + ), + "lib_modules": attr.label( + allow_single_file = True, + executable = True, + cfg = "target", + doc = """ +The lib/modules file needed for hermetic deployments. + """, + ), + "srcs": attr.label_list( + allow_files = True, + doc = """ +All files in the runtime. + """, + ), + "version": attr.int( + doc = """ +The feature version of the Java runtime. I.e., the integer returned by +Runtime.version().feature(). + """, + ), + # buildifier: disable=attr-licenses + "output_licenses": attr.string_list(), + "_windows_constraints": attr.label_list( + default = [paths.join(semantics.PLATFORMS_ROOT, "os:windows")], + ), + }, + fragments = ["java"], + provides = [ + JavaRuntimeInfo, + platform_common.TemplateVariableInfo, + ], +) diff --git a/java/common/rules/java_single_jar.bzl b/java/common/rules/java_single_jar.bzl new file mode 100644 index 00000000..ca768873 --- /dev/null +++ b/java/common/rules/java_single_jar.bzl @@ -0,0 +1,190 @@ +# Copyright 2026 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Definition of the java_single_jar rule.""" + +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_info.bzl", "JavaInfo") +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules/impl:java_helper.bzl", "helper") + +# copybara: default visibility + +def _single_jar_inputs(deps, deploy_env): + transitive_inputs = [] + for dep in deps: + if JavaInfo in dep: + info = dep[JavaInfo] + transitive_inputs.append(info.transitive_runtime_jars) + if hasattr(info, "compilation_info"): + compilation_info = info.compilation_info + if hasattr(compilation_info, "runtime_classpath"): + transitive_inputs.append(compilation_info.runtime_classpath) + else: + files = [] + for f in dep[DefaultInfo].files.to_list(): + if not f.extension == "jar": + fail("unexpected file type in java_single_jar.deps: %s" % f.path) + files.append(f) + transitive_inputs.append(depset(files)) + inputs = depset(transitive = transitive_inputs) + + if hasattr(java_common, "JavaRuntimeClasspathInfo"): + deploy_env_jars = depset(transitive = [ + dep[java_common.JavaRuntimeClasspathInfo].runtime_classpath + for dep in deploy_env + ]) + excluded_jars = {jar: None for jar in deploy_env_jars.to_list()} + if excluded_jars: + inputs = depset([jar for jar in inputs.to_list() if jar not in excluded_jars]) + return inputs + +def _bazel_java_single_jar_impl(ctx): + inputs = _single_jar_inputs(ctx.attr.deps, ctx.attr.deploy_env) + + args = ctx.actions.args() + args.add_all("--sources", inputs) + args.use_param_file("@%s") + args.set_param_file_format("multiline") + args.add_all("--deploy_manifest_lines", ctx.attr.deploy_manifest_lines) + args.add("--output", ctx.outputs.output) + args.add("--normalize") + + # Deal with limitation of singlejar flags: tool's default behavior is + # "no", but you get that behavior only by absence of compression flags. + if ctx.attr.compress == "preserve": + args.add("--dont_change_compression") + elif ctx.attr.compress == "yes": + args.add("--compression") + elif ctx.attr.compress == "no": + pass + else: + fail("\"compress\" attribute (%s) must be: yes, no, preserve." % ctx.attr.compress) + + if ctx.attr.exclude_build_data and ctx.attr.stamp == 1: + fail("Enabling stamping has not effect with exclude_build_data enabled") + + build_info_files = [] + if ctx.attr.exclude_build_data: + args.add("--exclude_build_data") + else: + build_info_files = helper.get_build_info(ctx, ctx.attr.stamp) + args.add_all(build_info_files, before_each = "--build_info_file") + if ctx.attr.multi_release: + args.add("--multi_release") + + if ctx.attr.exclude_pattern: + args.add("--exclude_pattern", ctx.attr.exclude_pattern) + + ctx.actions.run( + inputs = depset(build_info_files, transitive = [inputs]), + outputs = [ctx.outputs.output], + arguments = [args], + progress_message = "Merging into %s" % ctx.outputs.output.short_path, + mnemonic = "JavaSingleJar", + executable = ctx.executable._singlejar, + use_default_shell_env = True, + ) + + files = depset([ctx.outputs.output]) + providers = [DefaultInfo( + files = files, + runfiles = ctx.runfiles(transitive_files = files), + )] + if hasattr(java_common, "JavaRuntimeClasspathInfo"): + providers.append(java_common.JavaRuntimeClasspathInfo(runtime_classpath = inputs)) + return providers + +bazel_java_single_jar = rule( + attrs = { + "deps": attr.label_list( + allow_files = True, + doc = """ + The Java targets (including java_import and java_library) to collect + transitive dependencies from. Runtime dependencies are collected via + deps, exports, and runtime_deps. Resources are also collected. + Native cc_library or java_wrap_cc dependencies are not.""", + ), + "deploy_manifest_lines": attr.string_list(doc = """ + A list of lines to add to the META-INF/manifest.mf file."""), + "deploy_env": attr.label_list( + providers = [java_common.JavaRuntimeClasspathInfo] if hasattr(java_common, "JavaRuntimeClasspathInfo") else [], + allow_files = False, + doc = """ + A list of `java_binary` or `java_single_jar` targets which represent + the deployment environment for this binary. + + Set this attribute when building a plugin which will be loaded by another + `java_binary`. + + `deploy_env` dependencies are excluded from the jar built by this rule.""", + ), + "compress": attr.string(default = "preserve", doc = """ + Whether to always deflate ("yes"), always store ("no"), or pass + through unmodified ("preserve"). The default is "preserve", and is the + most efficient option -- no extra work is done to inflate or deflate."""), + "exclude_build_data": attr.bool(default = True, doc = """ + Whether to omit the build-data.properties file generated + by default."""), + "multi_release": attr.bool(default = True, doc = """Whether to enable Multi-Release output jars."""), + "exclude_pattern": attr.string(default = "", doc = """ + A regex pattern of files to exclude from the jar. + """), + "_singlejar": attr.label( + default = Label("//toolchains:singlejar"), + cfg = "exec", + allow_single_file = True, + executable = True, + ), + "output": attr.output(), + "stamp": attr.int( + doc = """ + Whether to embed extra Bazel build information into the build_data.properties file: + * `stamp = 1`: Always embed Bazel build information, even in `--nostamp` builds. + * `stamp = 0`: Embed Bazel build information with constant values, even in `--stamp` builds. + * `stamp = -1`: Embedding of Bazel build information is controlled by the `--[no]stamp` flag. + + Note: whether the output contains the build_data.properties file is controlled + by the `exclude_build_data` attribute. + """, + default = 0, + values = [-1, 0, 1], + ), + "_build_info_translator": attr.label(default = semantics.BUILD_INFO_TRANSLATOR_LABEL), + }, + implementation = _bazel_java_single_jar_impl, + doc = """ +Collects Java dependencies and jar files into a single jar + +`java_single_jar` collects Java dependencies and jar files into a single jar. +This is similar to java_binary with everything related to executables disabled, +and provides an alternative to the java_binary "deploy jar hack". + +## Example + +```skylark +load("//tools/build_defs/java_single_jar:java_single_jar.bzl", "java_single_jar") + +java_single_jar( + name = "my_single_jar", + deps = [ + "//java/com/google/foo", + "//java/com/google/bar", + ], +) +``` + +Outputs: + {name}.jar: A single jar containing all of the inputs. +""", +) diff --git a/java/common/rules/java_toolchain.bzl b/java/common/rules/java_toolchain.bzl new file mode 100644 index 00000000..a24ae4f1 --- /dev/null +++ b/java/common/rules/java_toolchain.bzl @@ -0,0 +1,612 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Definition of java_toolchain rule and JavaToolchainInfo provider. +""" + +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules:java_helper.bzl", "helper") +load("//java/private:boot_class_path_info.bzl", "BootClassPathInfo") +load("//java/private:java_info.bzl", "JavaPluginDataInfo") +load("//java/private:native.bzl", "get_internal_java_common") +load(":java_package_configuration.bzl", "JavaPackageConfigurationInfo") +load(":java_runtime.bzl", "JavaRuntimeInfo") + +# copybara: default visibility + +ToolchainInfo = platform_common.ToolchainInfo + +def _java_toolchain_info_init(**_kwargs): + fail("JavaToolchainInfo instantiation is a private API") + +_PRIVATE_API_DOC_STRING = "internal API, DO NOT USE!" + +JavaToolchainInfo, _new_javatoolchaininfo = provider( + doc = "Information about the JDK used by the java_* rules.", + fields = { + "bootclasspath": "(depset[File]) The Java target bootclasspath entries. Corresponds to javac's -bootclasspath flag.", + "ijar": "(FilesToRunProvider) The ijar executable.", + "jacocorunner": "(FilesToRunProvider) The jacocorunner used by the toolchain.", + "java_runtime": "(JavaRuntimeInfo) The java runtime information.", + "jvm_opt": "(depset[str]) The default options for the JVM running the java compiler and associated tools.", + "label": "(label) The toolchain label.", + "proguard_allowlister": "(FilesToRunProvider) The binary to validate proguard configuration.", + "single_jar": "(FilesToRunProvider) The SingleJar executable.", + "source_version": "(str) The java source version.", + "target_version": "(str) The java target version.", + "tools": "(depset[File]) The compilation tools.", + # private + "_android_linter": _PRIVATE_API_DOC_STRING, + "_bootclasspath_info": _PRIVATE_API_DOC_STRING, + "_bytecode_optimizer": _PRIVATE_API_DOC_STRING, + "_compatible_javacopts": _PRIVATE_API_DOC_STRING, + "_deps_checker": _PRIVATE_API_DOC_STRING, + "_forcibly_disable_header_compilation": _PRIVATE_API_DOC_STRING, + "_gen_class": _PRIVATE_API_DOC_STRING, + "_header_compiler": _PRIVATE_API_DOC_STRING, + "_header_compiler_builtin_processors": _PRIVATE_API_DOC_STRING, + "_header_compiler_direct": _PRIVATE_API_DOC_STRING, + "_javabuilder": _PRIVATE_API_DOC_STRING, + "_javacopts": _PRIVATE_API_DOC_STRING, + "_javacopts_list": _PRIVATE_API_DOC_STRING, + "_javac_supports_workers": _PRIVATE_API_DOC_STRING, + "_javac_supports_multiplex_workers": _PRIVATE_API_DOC_STRING, + "_javac_supports_worker_cancellation": _PRIVATE_API_DOC_STRING, + "_javac_supports_worker_multiplex_sandboxing": _PRIVATE_API_DOC_STRING, + "_jspecify_info": _PRIVATE_API_DOC_STRING, + "_local_java_optimization_config": _PRIVATE_API_DOC_STRING, + "_one_version_tool": _PRIVATE_API_DOC_STRING, + "_one_version_allowlist": _PRIVATE_API_DOC_STRING, + "_one_version_allowlist_for_tests": _PRIVATE_API_DOC_STRING, + "_package_configuration": _PRIVATE_API_DOC_STRING, + "_reduced_classpath_incompatible_processors": _PRIVATE_API_DOC_STRING, + "_timezone_data": _PRIVATE_API_DOC_STRING, + }, + init = _java_toolchain_info_init, +) + +def _java_toolchain_impl(ctx): + javac_opts_list = _get_javac_opts(ctx) + bootclasspath_info = _get_bootclasspath_info(ctx) + java_runtime = _get_java_runtime(ctx) + if java_runtime and java_runtime.lib_ct_sym: + header_compiler_direct_data = [java_runtime.lib_ct_sym] + header_compiler_direct_jvm_opts = ["-Dturbine.ctSymPath=" + java_runtime.lib_ct_sym.path] + elif java_runtime and java_runtime.java_home: + # Turbine finds ct.sym relative to java.home. + header_compiler_direct_data = [] + header_compiler_direct_jvm_opts = ["-Djava.home=" + java_runtime.java_home] + else: + header_compiler_direct_data = [] + header_compiler_direct_jvm_opts = [] + if ctx.attr.oneversion_allowlist and ctx.attr.oneversion_whitelist: + fail("oneversion_allowlist and oneversion_whitelist are mutually exclusive") + oneversion_allowlist = ctx.file.oneversion_allowlist if ctx.file.oneversion_allowlist else ctx.file.oneversion_whitelist + java_toolchain_info = _new_javatoolchaininfo( + bootclasspath = bootclasspath_info.bootclasspath, + ijar = ctx.attr.ijar[DefaultInfo].files_to_run if ctx.attr.ijar else None, + jacocorunner = ctx.attr.jacocorunner[DefaultInfo].files_to_run if ctx.attr.jacocorunner else None, + java_runtime = java_runtime, + jvm_opt = depset(get_internal_java_common().expand_java_opts(ctx, "jvm_opts", tokenize = False, exec_paths = True)), + label = ctx.label, + proguard_allowlister = ctx.attr.proguard_allowlister[DefaultInfo].files_to_run if ctx.attr.proguard_allowlister else None, + single_jar = ctx.attr.singlejar[DefaultInfo].files_to_run, + source_version = ctx.attr.source_version, + target_version = ctx.attr.target_version, + tools = depset(ctx.files.tools), + # private + _android_linter = _get_android_lint_tool(ctx), + _bootclasspath_info = bootclasspath_info, + _bytecode_optimizer = _get_tool_from_executable(ctx, "_bytecode_optimizer"), + _compatible_javacopts = _get_compatible_javacopts(ctx), + _deps_checker = ctx.file.deps_checker, + _forcibly_disable_header_compilation = ctx.attr.forcibly_disable_header_compilation, + _gen_class = ctx.file.genclass, + _header_compiler = _get_tool_from_ctx(ctx, "header_compiler", "turbine_data", "turbine_jvm_opts"), + _header_compiler_builtin_processors = depset(ctx.attr.header_compiler_builtin_processors), + _header_compiler_direct = _get_tool_from_executable( + ctx, + "header_compiler_direct", + data = header_compiler_direct_data, + jvm_opts = header_compiler_direct_jvm_opts, + ), + _javabuilder = _get_tool_from_ctx(ctx, "javabuilder", "javabuilder_data", "javabuilder_jvm_opts"), + _javacopts = helper.detokenize_javacopts(javac_opts_list), + _javacopts_list = javac_opts_list, + _javac_supports_workers = ctx.attr.javac_supports_workers, + _javac_supports_multiplex_workers = ctx.attr.javac_supports_multiplex_workers, + _javac_supports_worker_cancellation = ctx.attr.javac_supports_worker_cancellation, + _javac_supports_worker_multiplex_sandboxing = ctx.attr.javac_supports_worker_multiplex_sandboxing, + _jspecify_info = _get_jspecify_info(ctx), + _local_java_optimization_config = ctx.files._local_java_optimization_configuration, + _one_version_tool = ctx.attr.oneversion[DefaultInfo].files_to_run if ctx.attr.oneversion else None, + _one_version_allowlist = oneversion_allowlist, + _one_version_allowlist_for_tests = ctx.file.oneversion_allowlist_for_tests, + _package_configuration = [dep[JavaPackageConfigurationInfo] for dep in ctx.attr.package_configuration], + _reduced_classpath_incompatible_processors = depset(ctx.attr.reduced_classpath_incompatible_processors, order = "preorder"), + _timezone_data = ctx.file.timezone_data, + ) + toolchain_info = ToolchainInfo(java = java_toolchain_info) + return [java_toolchain_info, toolchain_info, DefaultInfo()] + +def _get_bootclasspath_info(ctx): + bootclasspath_infos = [dep[BootClassPathInfo] for dep in ctx.attr.bootclasspath if BootClassPathInfo in dep] + if bootclasspath_infos: + if len(bootclasspath_infos) != 1: + fail("in attribute 'bootclasspath': expected exactly one entry with a BootClassPathInfo provider") + else: + return bootclasspath_infos[0] + else: + return BootClassPathInfo(bootclasspath = ctx.files.bootclasspath) + +def _get_java_runtime(ctx): + if not ctx.attr.java_runtime: + return None + return ctx.attr.java_runtime[ToolchainInfo].java_runtime + +def _get_javac_opts(ctx): + opts = [] + if ctx.attr.source_version: + opts.extend(["-source", ctx.attr.source_version]) + if ctx.attr.target_version: + opts.extend(["-target", ctx.attr.target_version]) + if ctx.attr.xlint: + opts.append("-Xlint:" + ",".join(ctx.attr.xlint)) + opts.extend(get_internal_java_common().expand_java_opts(ctx, "misc", tokenize = True)) + opts.extend(get_internal_java_common().expand_java_opts(ctx, "javacopts", tokenize = True)) + return opts + +def _get_android_lint_tool(ctx): + if not ctx.attr.android_lint_runner: + return None + files_to_run = ctx.attr.android_lint_runner[DefaultInfo].files_to_run + if not files_to_run or not files_to_run.executable: + fail(ctx.attr.android_lint_runner.label, "does not refer to a valid executable target") + return struct( + tool = files_to_run, + data = depset(ctx.files.android_lint_data), + jvm_opts = depset([ctx.expand_location(opt, ctx.attr.android_lint_data) for opt in ctx.attr.android_lint_jvm_opts]), + lint_opts = [ctx.expand_location(opt, ctx.attr.android_lint_data) for opt in ctx.attr.android_lint_opts], + package_config = [dep[JavaPackageConfigurationInfo] for dep in ctx.attr.android_lint_package_configuration], + ) + +def _get_tool_from_ctx(ctx, tool_attr, data_attr, opts_attr): + dep = getattr(ctx.attr, tool_attr) + if not dep: + return None + files_to_run = dep[DefaultInfo].files_to_run + if not files_to_run or not files_to_run.executable: + fail(dep.label, "does not refer to a valid executable target") + data = getattr(ctx.attr, data_attr) + return struct( + tool = files_to_run, + data = depset(getattr(ctx.files, data_attr)), + jvm_opts = depset([ctx.expand_location(opt, data) for opt in getattr(ctx.attr, opts_attr)]), + ) + +def _get_tool_from_executable(ctx, attr_name, data = [], jvm_opts = []): + dep = getattr(ctx.attr, attr_name) + if not dep: + return None + files_to_run = dep[DefaultInfo].files_to_run + if not files_to_run or not files_to_run.executable: + fail(dep.label, "does not refer to a valid executable target") + return struct(tool = files_to_run, data = depset(data), jvm_opts = depset(jvm_opts)) + +def _get_compatible_javacopts(ctx): + result = {} + for key, opt_list in ctx.attr.compatible_javacopts.items(): + result[key] = helper.detokenize_javacopts([token for opt in opt_list for token in ctx.tokenize(opt)]) + return result + +def _get_jspecify_info(ctx): + if not ctx.attr.jspecify_processor_class: + return None + stubs = ctx.files.jspecify_stubs + javacopts = [] + javacopts.extend(ctx.attr.jspecify_javacopts) + if stubs: + javacopts.append("-Astubs=" + ":".join([file.path for file in stubs])) + return struct( + processor = JavaPluginDataInfo( + processor_classes = depset([ctx.attr.jspecify_processor_class]), + processor_jars = depset([ctx.file.jspecify_processor]), + processor_data = depset(stubs), + ), + implicit_deps = depset([ctx.file.jspecify_implicit_deps]), + javacopts = javacopts, + packages = [target[PackageSpecificationInfo] for target in ctx.attr.jspecify_packages], + ) + +def _extract_singleton_list_value(dict, key): + if key in dict and type(dict[key]) == type([]): + list = dict[key] + if len(list) > 1: + fail("expected a single value for:", key, "got: ", list) + elif len(list) == 1: + dict[key] = dict[key][0] + else: + dict[key] = None + +# TODO: b/463873596 - remove this in Bazel 10 +_LEGACY_ANY_TYPE_ATTRS = [ + "genclass", + "deps_checker", + "header_compiler", + "header_compiler_direct", + "ijar", + "javabuilder", + "singlejar", +] + +def _java_toolchain_initializer(**kwargs): + # these attributes are defined as executable `label_list`s in native but are + # expected to be singleton values. Since this is not supported in Starlark, + # we just inline the value from the list (if present) before invoking the + # rule. + for attr in _LEGACY_ANY_TYPE_ATTRS: + _extract_singleton_list_value(kwargs, attr) + + return kwargs + +java_toolchain = rule( + implementation = _java_toolchain_impl, + initializer = _java_toolchain_initializer, + doc = """ +

+Specifies the configuration for the Java compiler. Which toolchain to be used can be changed through +the --java_toolchain argument. Normally you should not write those kind of rules unless you want to +tune your Java compiler. +

+ +

Examples

+ +

A simple example would be: +

+ +
+
+
+java_toolchain(
+    name = "toolchain",
+    source_version = "7",
+    target_version = "7",
+    bootclasspath = ["//tools/jdk:bootclasspath"],
+    xlint = [ "classfile", "divzero", "empty", "options", "path" ],
+    javacopts = [ "-g" ],
+    javabuilder = ":JavaBuilder_deploy.jar",
+)
+
+
+ """, + # buildifier: disable=attr-licenses + attrs = { + "android_lint_data": attr.label_list( + cfg = "exec", + allow_files = True, + doc = """ +Labels of tools available for label-expansion in android_lint_jvm_opts. + """, + ), + "android_lint_opts": attr.string_list( + default = [], + doc = """ +The list of Android Lint arguments. + """, + ), + "android_lint_jvm_opts": attr.string_list( + default = [], + doc = """ +The list of arguments for the JVM when invoking Android Lint. + """, + ), + "android_lint_package_configuration": attr.label_list( + cfg = "exec", + providers = [JavaPackageConfigurationInfo], + allow_files = True, + doc = """ +Android Lint Configuration that should be applied to the specified package groups. + """, + ), + "android_lint_runner": attr.label( + cfg = "exec", + executable = True, + allow_single_file = True, + doc = """ +Label of the Android Lint runner, if any. + """, + ), + "bootclasspath": attr.label_list( + default = [], + allow_files = True, + doc = """ +The Java target bootclasspath entries. Corresponds to javac's -bootclasspath flag. + """, + ), + "compatible_javacopts": attr.string_list_dict( + doc = """Internal API, do not use!""", + ), + "deps_checker": attr.label( + allow_single_file = True, + cfg = "exec", + executable = True, + doc = """ +Label of the ImportDepsChecker deploy jar. + """, + ), + "forcibly_disable_header_compilation": attr.bool( + default = False, + doc = """ +Overrides --java_header_compilation to disable header compilation on platforms that do not +support it, e.g. JDK 7 Bazel. + """, + ), + "genclass": attr.label( + allow_single_file = True, + cfg = "exec", + executable = True, + doc = """ +Label of the GenClass deploy jar. + """, + ), + "header_compiler": attr.label( + allow_single_file = True, + cfg = "exec", + executable = True, + doc = """ +Label of the header compiler. Required if --java_header_compilation is enabled. + """, + ), + "header_compiler_direct": attr.label( + allow_single_file = True, + cfg = "exec", + executable = True, + doc = """ +Optional label of the header compiler to use for direct classpath actions that do not +include any API-generating annotation processors. + +

This tool does not support annotation processing. + """, + ), + "header_compiler_builtin_processors": attr.string_list( + doc = """Internal API, do not use!""", + ), + "ijar": attr.label( + cfg = "exec", + allow_files = True, + executable = True, + doc = """ +Label of the ijar executable. + """, + ), + "jacocorunner": attr.label( + cfg = "exec", + allow_single_file = True, + executable = True, + doc = """ +Label of the JacocoCoverageRunner deploy jar. + """, + ), + "javabuilder": attr.label( + cfg = "exec", + allow_single_file = True, + executable = True, + doc = """ +Label of the JavaBuilder deploy jar. + """, + ), + "javabuilder_data": attr.label_list( + cfg = "exec", + allow_files = True, + doc = """ +Labels of data available for label-expansion in javabuilder_jvm_opts. + """, + ), + "javabuilder_jvm_opts": attr.string_list( + doc = """ +The list of arguments for the JVM when invoking JavaBuilder. + """, + ), + "java_runtime": attr.label( + cfg = "exec", + providers = [JavaRuntimeInfo], + doc = """ +The java_runtime to use with this toolchain. It defaults to java_runtime +in execution configuration. + """, + ), + "javac_supports_workers": attr.bool( + default = True, + doc = """ +True if JavaBuilder supports running as a persistent worker, false if it doesn't. + """, + ), + "javac_supports_multiplex_workers": attr.bool( + default = True, + doc = """ +True if JavaBuilder supports running as a multiplex persistent worker, false if it doesn't. + """, + ), + "javac_supports_worker_cancellation": attr.bool( + default = True, + doc = """ +True if JavaBuilder supports cancellation of persistent workers, false if it doesn't. + """, + ), + "javac_supports_worker_multiplex_sandboxing": attr.bool( + default = False, + doc = """ +True if JavaBuilder supports running as a multiplex persistent worker with sandboxing, false if it doesn't. + """, + ), + "javacopts": attr.string_list( + default = [], + doc = """ +The list of extra arguments for the Java compiler. Please refer to the Java compiler +documentation for the extensive list of possible Java compiler flags. + """, + ), + "jspecify_implicit_deps": attr.label( + cfg = "exec", + allow_single_file = True, + executable = True, + doc = """Experimental, do not use!""", + ), + "jspecify_javacopts": attr.string_list( + doc = """Experimental, do not use!""", + ), + "jspecify_packages": attr.label_list( + cfg = "exec", + allow_files = True, + providers = [PackageSpecificationInfo], + doc = """Experimental, do not use!""", + ), + "jspecify_processor": attr.label( + cfg = "exec", + allow_single_file = True, + executable = True, + doc = """Experimental, do not use!""", + ), + "jspecify_processor_class": attr.string( + doc = """Experimental, do not use!""", + ), + "jspecify_stubs": attr.label_list( + cfg = "exec", + allow_files = True, + doc = """Experimental, do not use!""", + ), + "jvm_opts": attr.string_list( + default = [], + doc = """ +The list of arguments for the JVM when invoking the Java compiler. Please refer to the Java +virtual machine documentation for the extensive list of possible flags for this option. + """, + ), + "misc": attr.string_list( + default = [], + doc = """Deprecated: use javacopts instead""", + ), + "oneversion": attr.label( + cfg = "exec", + allow_files = True, + executable = True, + doc = """ +Label of the one-version enforcement binary. + """, + ), + "oneversion_whitelist": attr.label( + allow_single_file = True, + doc = """Deprecated: use oneversion_allowlist instead""", + ), + "oneversion_allowlist": attr.label( + allow_single_file = True, + doc = """ +Label of the one-version allowlist. + """, + ), + "oneversion_allowlist_for_tests": attr.label( + allow_single_file = True, + doc = """ +Label of the one-version allowlist for tests. + """, + ), + "package_configuration": attr.label_list( + cfg = "target", + providers = [JavaPackageConfigurationInfo], + doc = """ +Configuration that should be applied to the specified package groups. + """, + ), + "proguard_allowlister": attr.label( + cfg = "exec", + executable = True, + allow_files = True, + default = semantics.PROGUARD_ALLOWLISTER_LABEL, + doc = """ +Label of the Proguard allowlister. + """, + ), + "reduced_classpath_incompatible_processors": attr.string_list( + doc = """Internal API, do not use!""", + ), + "singlejar": attr.label( + cfg = "exec", + allow_files = True, + executable = True, + doc = """ +Label of the SingleJar executable. + """, + ), + "source_version": attr.string( + doc = """ +The Java source version (e.g., '6' or '7'). It specifies which set of code structures +are allowed in the Java source code. + """, + ), + "target_version": attr.string( + doc = """ +The Java target version (e.g., '6' or '7'). It specifies for which Java runtime the class +should be build. + """, + ), + "timezone_data": attr.label( + cfg = "exec", + allow_single_file = True, + doc = """ +Label of a resource jar containing timezone data. If set, the timezone data is added as an +implicitly runtime dependency of all java_binary rules. + """, + ), + "tools": attr.label_list( + cfg = "exec", + allow_files = True, + doc = """ +Labels of tools available for label-expansion in jvm_opts. + """, + ), + "turbine_data": attr.label_list( + cfg = "exec", + allow_files = True, + doc = """ +Labels of data available for label-expansion in turbine_jvm_opts. + """, + ), + "turbine_jvm_opts": attr.string_list( + doc = """ +The list of arguments for the JVM when invoking turbine. + """, + ), + "xlint": attr.string_list( + default = [], + doc = """ +The list of warning to add or removes from default list. Precedes it with a dash to +removes it. Please see the Javac documentation on the -Xlint options for more information. + """, + ), + "licenses": attr.license() if hasattr(attr, "license") else attr.string_list(), + "_bytecode_optimizer": attr.label( + cfg = "exec", + executable = True, + default = configuration_field(fragment = "java", name = "java_toolchain_bytecode_optimizer"), + ), + "_local_java_optimization_configuration": attr.label( + cfg = "exec", + default = configuration_field(fragment = "java", name = "local_java_optimization_configuration"), + allow_files = True, + ), + "_legacy_any_type_attrs": attr.string_list(default = _LEGACY_ANY_TYPE_ATTRS), + }, + fragments = ["java"], +) diff --git a/java/common/rules/rule_util.bzl b/java/common/rules/rule_util.bzl new file mode 100644 index 00000000..724b2784 --- /dev/null +++ b/java/common/rules/rule_util.bzl @@ -0,0 +1,50 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Defines rule utilities.""" + +# copybara: rules_java visibility + +def merge_attrs(*attribute_dicts, override_attrs = {}, remove_attrs = []): + """Merges attributes together. + + Attributes are first merged, then overridden and removed. + + If there are duplicate definitions of an attribute, the last one is used. + (Current API doesn't let us compare) + + Overridden and removed attributes need to be present. + + Args: + *attribute_dicts: (*dict[str,Attribute]) A list of attribute dictionaries + to merge together. + override_attrs: (dict[str,Attribute]) A dictionary of attributes to override + remove_attrs: (list[str]) A list of attributes to remove. + Returns: + (dict[str,Attribute]) The merged attributes dictionary. + """ + all_attributes = {} + for attribute_dict in attribute_dicts: + for key, attr in attribute_dict.items(): + all_attributes.setdefault(key, attr) + for key, attr in override_attrs.items(): + if all_attributes.get(key) == None: + fail("Trying to override attribute %s where there is none." % key) + all_attributes[key] = attr + for key in remove_attrs: + if key in override_attrs: + fail("Trying to remove overridden attribute %s." % key) + if key not in all_attributes: + fail("Trying to remove non-existent attribute %s." % key) + all_attributes.pop(key) + return all_attributes diff --git a/java/defs.bzl b/java/defs.bzl index 64de71a4..8899a705 100644 --- a/java/defs.bzl +++ b/java/defs.bzl @@ -13,6 +13,8 @@ # limitations under the License. """Starlark rules for building Java projects.""" +load("@com_google_protobuf//bazel:java_lite_proto_library.bzl", _java_lite_proto_library = "java_lite_proto_library") +load("@com_google_protobuf//bazel:java_proto_library.bzl", _java_proto_library = "java_proto_library") load("//java:java_binary.bzl", _java_binary = "java_binary") load("//java:java_import.bzl", _java_import = "java_import") load("//java:java_library.bzl", _java_library = "java_library") @@ -41,8 +43,23 @@ java_package_configuration = _java_package_configuration # Proto rules # Deprecated: don't use java proto libraries from here -java_proto_library = native.java_proto_library -java_lite_proto_library = native.java_lite_proto_library +def java_proto_library(**kwargs): + if "deprecation" not in kwargs: + _java_proto_library( + deprecation = "Use java_proto_library from @com_google_protobuf//bazel:java_proto_library.bzl", + **kwargs + ) + else: + _java_proto_library(**kwargs) + +def java_lite_proto_library(**kwargs): + if "deprecation" not in kwargs: + _java_lite_proto_library( + deprecation = "Use java_lite_proto_library from @com_google_protobuf//bazel:java_lite_proto_library.bzl", + **kwargs + ) + else: + _java_lite_proto_library(**kwargs) # Modules and providers diff --git a/java/docs/BUILD.bazel b/java/docs/BUILD.bazel new file mode 100644 index 00000000..f2b59360 --- /dev/null +++ b/java/docs/BUILD.bazel @@ -0,0 +1,41 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load("@stardoc//stardoc:stardoc.bzl", "stardoc") + +package(default_applicable_licenses = ["@rules_java//:license"]) + +exports_files( + ["rules.md"], + visibility = ["//test:__pkg__"], +) + +bzl_library( + name = "rules_bzl", + srcs = ["rules.bzl"], + deps = [ + "//java/bazel/rules", + "//java/common/rules:toolchain_rules", + ], +) + +stardoc( + name = "rules_docs", + out = "rules_docs.out", + input = "rules.bzl", + rule_template = ":rule.vm", + symbol_names = [ + # core rules + "java_binary", + "java_import", + "java_library", + "java_plugin", + "java_test", + + # toolchain rules + "java_package_configuration", + "java_runtime", + "java_toolchain", + ], + table_of_contents_template = "@stardoc//stardoc:templates/markdown_tables/table_of_contents.vm", + visibility = ["//test:__pkg__"], + deps = [":rules_bzl"], +) diff --git a/java/docs/rule.vm b/java/docs/rule.vm new file mode 100644 index 00000000..dd89fdf8 --- /dev/null +++ b/java/docs/rule.vm @@ -0,0 +1,20 @@ + + +#[[##]]# ${ruleName} + +

+${util.ruleSummary($ruleName, $ruleInfo)}
+
+ +${ruleInfo.docString} + +**ATTRIBUTES** + +#if (!$ruleInfo.getAttributeList().isEmpty()) + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +#foreach ($attribute in $ruleInfo.getAttributeList()) +| $attribute.name | #if(!$attribute.docString.isEmpty()) ${util.markdownCellFormat($attribute.docString)} #else - #end | ${util.attributeTypeString($attribute)} | ${util.mandatoryString($attribute)} | #if(!$attribute.defaultValue.isEmpty()) ${util.markdownCodeSpan($attribute.defaultValue)} #end | +#end +#end \ No newline at end of file diff --git a/java/docs/rules.bzl b/java/docs/rules.bzl new file mode 100644 index 00000000..fa6c3dd8 --- /dev/null +++ b/java/docs/rules.bzl @@ -0,0 +1,22 @@ +"""Java rules""" + +load("//java/bazel/rules:bazel_java_binary.bzl", _java_binary = "java_binary") +load("//java/bazel/rules:bazel_java_import.bzl", _java_import = "java_import") +load("//java/bazel/rules:bazel_java_library.bzl", _java_library = "java_library") +load("//java/bazel/rules:bazel_java_plugin.bzl", _java_plugin = "java_plugin") +load("//java/bazel/rules:bazel_java_test.bzl", _java_test = "java_test") +load("//java/common/rules:java_package_configuration.bzl", _java_package_configuration = "java_package_configuration") +load("//java/common/rules:java_runtime.bzl", _java_runtime = "java_runtime") +load("//java/common/rules:java_toolchain.bzl", _java_toolchain = "java_toolchain") + +visibility("private") + +java_binary = _java_binary +java_import = _java_import +java_library = _java_library +java_plugin = _java_plugin +java_test = _java_test + +java_package_configuration = _java_package_configuration +java_runtime = _java_runtime +java_toolchain = _java_toolchain diff --git a/java/docs/rules.md b/java/docs/rules.md new file mode 100644 index 00000000..b6dd015c --- /dev/null +++ b/java/docs/rules.md @@ -0,0 +1,590 @@ + + +Java rules + + +## Rules + +- [java_binary](#java_binary) +- [java_import](#java_import) +- [java_library](#java_library) +- [java_package_configuration](#java_package_configuration) +- [java_plugin](#java_plugin) +- [java_runtime](#java_runtime) +- [java_test](#java_test) +- [java_toolchain](#java_toolchain) + + + + +## java_binary + +
+java_binary(name, deps, srcs, data, resources, add_exports, add_opens, bootclasspath,
+            classpath_resources, create_executable, deploy_env, deploy_manifest_lines, env, javacopts,
+            jvm_flags, launcher, licenses, main_class, neverlink, plugins, resource_strip_prefix,
+            runtime_deps, stamp, use_launcher, use_testrunner)
+
+ +

+ Builds a Java archive ("jar file"), plus a wrapper shell script with the same name as the rule. + The wrapper shell script uses a classpath that includes, among other things, a jar file for each + library on which the binary depends. When running the wrapper shell script, any nonempty + JAVABIN environment variable will take precedence over the version specified via + Bazel's --java_runtime_version flag. +

+

+ The wrapper script accepts several unique flags. Refer to + //src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt + for a list of configurable flags and environment variables accepted by the wrapper. +

+ +

Implicit output targets

+
    +
  • name.jar: A Java archive, containing the class files and other + resources corresponding to the binary's direct dependencies.
  • +
  • name-src.jar: An archive containing the sources ("source + jar").
  • +
  • name_deploy.jar: A Java archive suitable for deployment (only + built if explicitly requested). +

    + Building the <name>_deploy.jar target for your rule + creates a self-contained jar file with a manifest that allows it to be run with the + java -jar command or with the wrapper script's --singlejar + option. Using the wrapper script is preferred to java -jar because it + also passes the JVM flags and the options + to load native libraries. +

    +

    + The deploy jar contains all the classes that would be found by a classloader that + searched the classpath from the binary's wrapper script from beginning to end. It also + contains the native libraries needed for dependencies. These are automatically loaded + into the JVM at runtime. +

    +

    If your target specifies a launcher + attribute, then instead of being a normal JAR file, the _deploy.jar will be a + native binary. This will contain the launcher plus any native (C++) dependencies of + your rule, all linked into a static binary. The actual jar file's bytes will be + appended to that native binary, creating a single binary blob containing both the + executable and the Java code. You can execute the resulting jar file directly + like you would execute any native binary.

    +
  • +
  • name_deploy-src.jar: An archive containing the sources + collected from the transitive closure of the target. These will match the classes in the + deploy.jar except where jars have no matching source jar.
  • +
+ +

+It is good practice to use the name of the source file that is the main entry point of the +application (minus the extension). For example, if your entry point is called +Main.java, then your name could be Main. +

+ +

+ A deps attribute is not allowed in a java_binary rule without + srcs; such a rule requires a + main_class provided by + runtime_deps. +

+ +

The following code snippet illustrates a common mistake:

+ +
+
+java_binary(
+    name = "DontDoThis",
+    srcs = [
+        ...,
+        "GeneratedJavaFile.java",  # a generated .java file
+    ],
+    deps = [":generating_rule",],  # rule that generates that file
+)
+
+
+ +

Do this instead:

+ +
+
+java_binary(
+    name = "DoThisInstead",
+    srcs = [
+        ...,
+        ":generating_rule",
+    ],
+)
+
+
+ +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| deps | The list of other libraries to be linked in to the target. See general comments about deps at Typical attributes defined by most build rules. | List of labels | optional | `[]` | +| srcs | The list of source files that are processed to create the target. This attribute is almost always required; see exceptions below.

Source files of type .java are compiled. In case of generated .java files it is generally advisable to put the generating rule's name here instead of the name of the file itself. This not only improves readability but makes the rule more resilient to future changes: if the generating rule generates different files in the future, you only need to fix one place: the outs of the generating rule. You should not list the generating rule in deps because it is a no-op.

Source files of type .srcjar are unpacked and compiled. (This is useful if you need to generate a set of .java files with a genrule.)

Rules: if the rule (typically genrule or filegroup) generates any of the files listed above, they will be used the same way as described for source files.



This argument is almost always required, except if a main_class attribute specifies a class on the runtime classpath or you specify the runtime_deps argument.

| List of labels | optional | `[]` | +| data | The list of files needed by this library at runtime. See general comments about data at Typical attributes defined by most build rules. | List of labels | optional | `[]` | +| resources | A list of data files to include in a Java jar.

Resources may be source files or generated files.



If resources are specified, they will be bundled in the jar along with the usual .class files produced by compilation. The location of the resources inside of the jar file is determined by the project structure. Bazel first looks for Maven's standard directory layout, (a "src" directory followed by a "resources" directory grandchild). If that is not found, Bazel then looks for the topmost directory named "java" or "javatests" (so, for example, if a resource is at <workspace root>/x/java/y/java/z, the path of the resource will be y/java/z. This heuristic cannot be overridden, however, the resource_strip_prefix attribute can be used to specify a specific alternative directory for resource files. | List of labels | optional | `[]` | +| add_exports | Allow this library to access the given module or package.

This corresponds to the javac and JVM --add-exports= flags. | List of strings | optional | `[]` | +| add_opens | Allow this library to reflectively access the given module or package.

This corresponds to the javac and JVM --add-opens= flags. | List of strings | optional | `[]` | +| bootclasspath | Restricted API, do not use! | Label | optional | `None` | +| classpath_resources | DO NOT USE THIS OPTION UNLESS THERE IS NO OTHER WAY)

A list of resources that must be located at the root of the java tree. This attribute's only purpose is to support third-party libraries that require that their resources be found on the classpath as exactly "myconfig.xml". It is only allowed on binaries and not libraries, due to the danger of namespace conflicts.

| List of labels | optional | `[]` | +| create_executable | Deprecated, use java_single_jar instead. | Boolean | optional | `True` | +| deploy_env | A list of other java_binary targets which represent the deployment environment for this binary. Set this attribute when building a plugin which will be loaded by another java_binary.
Setting this attribute excludes all dependencies from the runtime classpath (and the deploy jar) of this binary that are shared between this binary and the targets specified in deploy_env. | List of labels | optional | `[]` | +| deploy_manifest_lines | A list of lines to add to the META-INF/manifest.mf file generated for the *_deploy.jar target. The contents of this attribute are not subject to "Make variable" substitution. | List of strings | optional | `[]` | +| env | - | Dictionary: String -> String | optional | `{}` | +| javacopts | Extra compiler options for this binary. Subject to "Make variable" substitution and Bourne shell tokenization.

These compiler options are passed to javac after the global compiler options.

| List of strings | optional | `[]` | +| jvm_flags | A list of flags to embed in the wrapper script generated for running this binary. Subject to $(location) and "Make variable" substitution, and Bourne shell tokenization.

The wrapper script for a Java binary includes a CLASSPATH definition (to find all the dependent jars) and invokes the right Java interpreter. The command line generated by the wrapper script includes the name of the main class followed by a "$@" so you can pass along other arguments after the classname. However, arguments intended for parsing by the JVM must be specified before the classname on the command line. The contents of jvm_flags are added to the wrapper script before the classname is listed.



Note that this attribute has no effect on *_deploy.jar outputs.

| List of strings | optional | `[]` | +| launcher | Specify a binary that will be used to run your Java program instead of the normal bin/java program included with the JDK. The target must be a cc_binary. Any cc_binary that implements the Java Invocation API can be specified as a value for this attribute.

By default, Bazel will use the normal JDK launcher (bin/java or java.exe).



The related --java_launcher Bazel flag affects only those java_binary and java_test targets that have not specified a launcher attribute.



Note that your native (C++, SWIG, JNI) dependencies will be built differently depending on whether you are using the JDK launcher or another launcher:



  • If you are using the normal JDK launcher (the default), native dependencies are built as a shared library named {name}_nativedeps.so, where {name} is the name attribute of this java_binary rule. Unused code is not removed by the linker in this configuration.


  • If you are using any other launcher, native (C++) dependencies are statically linked into a binary named {name}_nativedeps, where {name} is the name attribute of this java_binary rule. In this case, the linker will remove any code it thinks is unused from the resulting binary, which means any C++ code accessed only via JNI may not be linked in unless that cc_library target specifies alwayslink = True.


When using any launcher other than the default JDK launcher, the format of the *_deploy.jar output changes. See the main java_binary docs for details.

| Label | optional | `None` | +| licenses | - | List of strings | optional | `[]` | +| main_class | Name of class with main() method to use as entry point. If a rule uses this option, it does not need a srcs=[...] list. Thus, with this attribute one can make an executable from a Java library that already contains one or more main() methods.

The value of this attribute is a class name, not a source file. The class must be available at runtime: it may be compiled by this rule (from srcs) or provided by direct or transitive dependencies (through runtime_deps or deps). If the class is unavailable, the binary will fail at runtime; there is no build-time check.

| String | optional | `""` | +| neverlink | - | Boolean | optional | `False` | +| plugins | Java compiler plugins to run at compile-time. Every java_plugin specified in this attribute will be run whenever this rule is built. A library may also inherit plugins from dependencies that use exported_plugins. Resources generated by the plugin will be included in the resulting jar of this rule. | List of labels | optional | `[]` | +| resource_strip_prefix | The path prefix to strip from Java resources.

If specified, this path prefix is stripped from every file in the resources attribute. It is an error for a resource file not to be under this directory. If not specified (the default), the path of resource file is determined according to the same logic as the Java package of source files. For example, a source file at stuff/java/foo/bar/a.txt will be located at foo/bar/a.txt.

| String | optional | `""` | +| runtime_deps | Libraries to make available to the final binary or test at runtime only. Like ordinary deps, these will appear on the runtime classpath, but unlike them, not on the compile-time classpath. Dependencies needed only at runtime should be listed here. Dependency-analysis tools should ignore targets that appear in both runtime_deps and deps. | List of labels | optional | `[]` | +| stamp | Whether to encode build information into the binary. Possible values:
  • stamp = 1: Always stamp the build information into the binary, even in --nostamp builds. This setting should be avoided, since it potentially kills remote caching for the binary and any downstream actions that depend on it.
  • stamp = 0: Always replace build information by constant values. This gives good build result caching.
  • stamp = -1: Embedding of build information is controlled by the --[no]stamp flag.

Stamped binaries are not rebuilt unless their dependencies change.

| Integer | optional | `-1` | +| use_launcher | Whether the binary should use a custom launcher.

If this attribute is set to false, the launcher attribute and the related --java_launcher flag will be ignored for this target. | Boolean | optional | `True` | +| use_testrunner | Use the test runner (by default com.google.testing.junit.runner.BazelTestRunner) class as the main entry point for a Java program, and provide the test class to the test runner as a value of bazel.test_suite system property.


You can use this to override the default behavior, which is to use test runner for java_test rules, and not use it for java_binary rules. It is unlikely you will want to do this. One use is for AllTest rules that are invoked by another rule (to set up a database before running the tests, for example). The AllTest rule must be declared as a java_binary, but should still use the test runner as its main entry point.

The name of a test runner class can be overridden with main_class attribute. | Boolean | optional | `False` | + + + + +## java_import + +

+java_import(name, deps, data, add_exports, add_opens, constraints, exports, jars, licenses,
+            neverlink, proguard_specs, runtime_deps, srcjar)
+
+ +

+ This rule allows the use of precompiled .jar files as + libraries for java_library and + java_binary rules. +

+ +

Examples

+ +
+
+    java_import(
+        name = "maven_model",
+        jars = [
+            "maven_model/maven-aether-provider-3.2.3.jar",
+            "maven_model/maven-model-3.2.3.jar",
+            "maven_model/maven-model-builder-3.2.3.jar",
+        ],
+    )
+
+
+ +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| deps | The list of other libraries to be linked in to the target. See java_library.deps. | List of labels | optional | `[]` | +| data | The list of files needed by this rule at runtime. | List of labels | optional | `[]` | +| add_exports | Allow this library to access the given module or package.

This corresponds to the javac and JVM --add-exports= flags. | List of strings | optional | `[]` | +| add_opens | Allow this library to reflectively access the given module or package.

This corresponds to the javac and JVM --add-opens= flags. | List of strings | optional | `[]` | +| constraints | Extra constraints imposed on this rule as a Java library. | List of strings | optional | `[]` | +| exports | Targets to make available to users of this rule. See java_library.exports. | List of labels | optional | `[]` | +| jars | The list of JAR files provided to Java targets that depend on this target. | List of labels | required | | +| licenses | - | List of strings | optional | `[]` | +| neverlink | Only use this library for compilation and not at runtime. Useful if the library will be provided by the runtime environment during execution. Examples of libraries like this are IDE APIs for IDE plug-ins or tools.jar for anything running on a standard JDK. | Boolean | optional | `False` | +| proguard_specs | Files to be used as Proguard specification. These will describe the set of specifications to be used by Proguard. If specified, they will be added to any android_binary target depending on this library.

The files included here must only have idempotent rules, namely -dontnote, -dontwarn, assumenosideeffects, and rules that start with -keep. Other options can only appear in android_binary's proguard_specs, to ensure non-tautological merges. | List of labels | optional | `[]` | +| runtime_deps | Libraries to make available to the final binary or test at runtime only. See java_library.runtime_deps. | List of labels | optional | `[]` | +| srcjar | A JAR file that contains source code for the compiled JAR files. | Label | optional | `None` | + + + + +## java_library + +

+java_library(name, deps, srcs, data, resources, add_exports, add_opens, bootclasspath,
+             exported_plugins, exports, javabuilder_jvm_flags, javacopts, licenses, neverlink,
+             plugins, proguard_specs, resource_strip_prefix, runtime_deps)
+
+ +

This rule compiles and links sources into a .jar file.

+ +

Implicit outputs

+
    +
  • libname.jar: A Java archive containing the class files.
  • +
  • libname-src.jar: An archive containing the sources ("source + jar").
  • +
+ +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| deps | The list of libraries to link into this library. See general comments about deps at Typical attributes defined by most build rules.

The jars built by java_library rules listed in deps will be on the compile-time classpath of this rule. Furthermore the transitive closure of their deps, runtime_deps and exports will be on the runtime classpath.

By contrast, targets in the data attribute are included in the runfiles but on neither the compile-time nor runtime classpath.

| List of labels | optional | `[]` | +| srcs | The list of source files that are processed to create the target. This attribute is almost always required; see exceptions below.

Source files of type .java are compiled. In case of generated .java files it is generally advisable to put the generating rule's name here instead of the name of the file itself. This not only improves readability but makes the rule more resilient to future changes: if the generating rule generates different files in the future, you only need to fix one place: the outs of the generating rule. You should not list the generating rule in deps because it is a no-op.

Source files of type .srcjar are unpacked and compiled. (This is useful if you need to generate a set of .java files with a genrule.)

Rules: if the rule (typically genrule or filegroup) generates any of the files listed above, they will be used the same way as described for source files.

Source files of type .properties are treated as resources.



All other files are ignored, as long as there is at least one file of a file type described above. Otherwise an error is raised.



This argument is almost always required, except if you specify the runtime_deps argument.

| List of labels | optional | `[]` | +| data | The list of files needed by this library at runtime. See general comments about data at Typical attributes defined by most build rules.

When building a java_library, Bazel doesn't put these files anywhere; if the data files are generated files then Bazel generates them. When building a test that depends on this java_library Bazel copies or links the data files into the runfiles area.

| List of labels | optional | `[]` | +| resources | A list of data files to include in a Java jar.

Resources may be source files or generated files.



If resources are specified, they will be bundled in the jar along with the usual .class files produced by compilation. The location of the resources inside of the jar file is determined by the project structure. Bazel first looks for Maven's standard directory layout, (a "src" directory followed by a "resources" directory grandchild). If that is not found, Bazel then looks for the topmost directory named "java" or "javatests" (so, for example, if a resource is at <workspace root>/x/java/y/java/z, the path of the resource will be y/java/z. This heuristic cannot be overridden, however, the resource_strip_prefix attribute can be used to specify a specific alternative directory for resource files. | List of labels | optional | `[]` | +| add_exports | Allow this library to access the given module or package.

This corresponds to the javac and JVM --add-exports= flags. | List of strings | optional | `[]` | +| add_opens | Allow this library to reflectively access the given module or package.

This corresponds to the javac and JVM --add-opens= flags. | List of strings | optional | `[]` | +| bootclasspath | Restricted API, do not use! | Label | optional | `None` | +| exported_plugins | The list of java_plugins (e.g. annotation processors) to export to libraries that directly depend on this library.

The specified list of java_plugins will be applied to any library which directly depends on this library, just as if that library had explicitly declared these labels in plugins.

| List of labels | optional | `[]` | +| exports | Exported libraries.

Listing rules here will make them available to parent rules, as if the parents explicitly depended on these rules. This is not true for regular (non-exported) deps.

Summary: a rule X can access the code in Y if there exists a dependency path between them that begins with a deps edge followed by zero or more exports edges. Let's see some examples to illustrate this.

Assume A depends on B and B depends on C. In this case C is a transitive dependency of A, so changing C's sources and rebuilding A will correctly rebuild everything. However A will not be able to use classes in C. To allow that, either A has to declare C in its deps, or B can make it easier for A (and anything that may depend on A) by declaring C in its (B's) exports attribute.

The closure of exported libraries is available to all direct parent rules. Take a slightly different example: A depends on B, B depends on C and D, and also exports C but not D. Now A has access to C but not to D. Now, if C and D exported some libraries, C' and D' respectively, A could only access C' but not D'.

Important: an exported rule is not a regular dependency. Sticking to the previous example, if B exports C and wants to also use C, it has to also list it in its own deps.

| List of labels | optional | `[]` | +| javabuilder_jvm_flags | Restricted API, do not use! | List of strings | optional | `[]` | +| javacopts | Extra compiler options for this library. Subject to "Make variable" substitution and Bourne shell tokenization.

These compiler options are passed to javac after the global compiler options.

| List of strings | optional | `[]` | +| licenses | - | List of strings | optional | `[]` | +| neverlink | Whether this library should only be used for compilation and not at runtime. Useful if the library will be provided by the runtime environment during execution. Examples of such libraries are the IDE APIs for IDE plug-ins or tools.jar for anything running on a standard JDK.

Note that neverlink = True does not prevent the compiler from inlining material from this library into compilation targets that depend on it, as permitted by the Java Language Specification (e.g., static final constants of String or of primitive types). The preferred use case is therefore when the runtime library is identical to the compilation library.

If the runtime library differs from the compilation library then you must ensure that it differs only in places that the JLS forbids compilers to inline (and that must hold for all future versions of the JLS).

| Boolean | optional | `False` | +| plugins | Java compiler plugins to run at compile-time. Every java_plugin specified in this attribute will be run whenever this rule is built. A library may also inherit plugins from dependencies that use exported_plugins. Resources generated by the plugin will be included in the resulting jar of this rule. | List of labels | optional | `[]` | +| proguard_specs | Files to be used as Proguard specification. These will describe the set of specifications to be used by Proguard. If specified, they will be added to any android_binary target depending on this library.

The files included here must only have idempotent rules, namely -dontnote, -dontwarn, assumenosideeffects, and rules that start with -keep. Other options can only appear in android_binary's proguard_specs, to ensure non-tautological merges. | List of labels | optional | `[]` | +| resource_strip_prefix | The path prefix to strip from Java resources.

If specified, this path prefix is stripped from every file in the resources attribute. It is an error for a resource file not to be under this directory. If not specified (the default), the path of resource file is determined according to the same logic as the Java package of source files. For example, a source file at stuff/java/foo/bar/a.txt will be located at foo/bar/a.txt.

| String | optional | `""` | +| runtime_deps | Libraries to make available to the final binary or test at runtime only. Like ordinary deps, these will appear on the runtime classpath, but unlike them, not on the compile-time classpath. Dependencies needed only at runtime should be listed here. Dependency-analysis tools should ignore targets that appear in both runtime_deps and deps. | List of labels | optional | `[]` | + + + + +## java_package_configuration + +
+java_package_configuration(name, data, javacopts, output_licenses, packages, system)
+
+ +

+Configuration to apply to a set of packages. +Configurations can be added to +java_toolchain.javacoptss. +

+ +

Example:

+ +
+
+
+java_package_configuration(
+    name = "my_configuration",
+    packages = [":my_packages"],
+    javacopts = ["-Werror"],
+)
+
+package_group(
+    name = "my_packages",
+    packages = [
+        "//com/my/project/...",
+        "-//com/my/project/testing/...",
+    ],
+)
+
+java_toolchain(
+    ...,
+    package_configuration = [
+        ":my_configuration",
+    ]
+)
+
+
+
+ +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| data | The list of files needed by this configuration at runtime. | List of labels | optional | `[]` | +| javacopts | Java compiler flags. | List of strings | optional | `[]` | +| output_licenses | - | List of strings | optional | `[]` | +| packages | The set of package_groups the configuration should be applied to. | List of labels | optional | `[]` | +| system | Corresponds to javac's --system flag. | Label | optional | `None` | + + + + +## java_plugin + +
+java_plugin(name, deps, srcs, data, resources, add_exports, add_opens, bootclasspath, generates_api,
+            javabuilder_jvm_flags, javacopts, licenses, neverlink, output_licenses, plugins,
+            processor_class, proguard_specs, resource_strip_prefix)
+
+ +

+ java_plugin defines plugins for the Java compiler run by Bazel. The + only supported kind of plugins are annotation processors. A java_library or + java_binary rule can run plugins by depending on them via the plugins + attribute. A java_library can also automatically export plugins to libraries that + directly depend on it using + exported_plugins. +

+ +

Implicit output targets

+
    +
  • libname.jar: A Java archive.
  • +
+ +

+ Arguments are identical to java_library, except + for the addition of the processor_class argument. +

+ +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| deps | The list of libraries to link into this library. See general comments about deps at Typical attributes defined by most build rules.

The jars built by java_library rules listed in deps will be on the compile-time classpath of this rule. Furthermore the transitive closure of their deps, runtime_deps and exports will be on the runtime classpath.

By contrast, targets in the data attribute are included in the runfiles but on neither the compile-time nor runtime classpath.

| List of labels | optional | `[]` | +| srcs | The list of source files that are processed to create the target. This attribute is almost always required; see exceptions below.

Source files of type .java are compiled. In case of generated .java files it is generally advisable to put the generating rule's name here instead of the name of the file itself. This not only improves readability but makes the rule more resilient to future changes: if the generating rule generates different files in the future, you only need to fix one place: the outs of the generating rule. You should not list the generating rule in deps because it is a no-op.

Source files of type .srcjar are unpacked and compiled. (This is useful if you need to generate a set of .java files with a genrule.)

Rules: if the rule (typically genrule or filegroup) generates any of the files listed above, they will be used the same way as described for source files.

Source files of type .properties are treated as resources.



All other files are ignored, as long as there is at least one file of a file type described above. Otherwise an error is raised.



This argument is almost always required, except if you specify the runtime_deps argument.

| List of labels | optional | `[]` | +| data | The list of files needed by this library at runtime. See general comments about data at Typical attributes defined by most build rules.

When building a java_library, Bazel doesn't put these files anywhere; if the data files are generated files then Bazel generates them. When building a test that depends on this java_library Bazel copies or links the data files into the runfiles area.

| List of labels | optional | `[]` | +| resources | A list of data files to include in a Java jar.

Resources may be source files or generated files.



If resources are specified, they will be bundled in the jar along with the usual .class files produced by compilation. The location of the resources inside of the jar file is determined by the project structure. Bazel first looks for Maven's standard directory layout, (a "src" directory followed by a "resources" directory grandchild). If that is not found, Bazel then looks for the topmost directory named "java" or "javatests" (so, for example, if a resource is at <workspace root>/x/java/y/java/z, the path of the resource will be y/java/z. This heuristic cannot be overridden, however, the resource_strip_prefix attribute can be used to specify a specific alternative directory for resource files. | List of labels | optional | `[]` | +| add_exports | Allow this library to access the given module or package.

This corresponds to the javac and JVM --add-exports= flags. | List of strings | optional | `[]` | +| add_opens | Allow this library to reflectively access the given module or package.

This corresponds to the javac and JVM --add-opens= flags. | List of strings | optional | `[]` | +| bootclasspath | Restricted API, do not use! | Label | optional | `None` | +| generates_api | This attribute marks annotation processors that generate API code.

If a rule uses an API-generating annotation processor, other rules depending on it can refer to the generated code only if their compilation actions are scheduled after the generating rule. This attribute instructs Bazel to introduce scheduling constraints when --java_header_compilation is enabled.

WARNING: This attribute affects build performance, use it only if necessary.

| Boolean | optional | `False` | +| javabuilder_jvm_flags | Restricted API, do not use! | List of strings | optional | `[]` | +| javacopts | Extra compiler options for this library. Subject to "Make variable" substitution and Bourne shell tokenization.

These compiler options are passed to javac after the global compiler options.

| List of strings | optional | `[]` | +| licenses | - | List of strings | optional | `[]` | +| neverlink | Whether this library should only be used for compilation and not at runtime. Useful if the library will be provided by the runtime environment during execution. Examples of such libraries are the IDE APIs for IDE plug-ins or tools.jar for anything running on a standard JDK.

Note that neverlink = True does not prevent the compiler from inlining material from this library into compilation targets that depend on it, as permitted by the Java Language Specification (e.g., static final constants of String or of primitive types). The preferred use case is therefore when the runtime library is identical to the compilation library.

If the runtime library differs from the compilation library then you must ensure that it differs only in places that the JLS forbids compilers to inline (and that must hold for all future versions of the JLS).

| Boolean | optional | `False` | +| output_licenses | - | List of strings | optional | `[]` | +| plugins | Java compiler plugins to run at compile-time. Every java_plugin specified in this attribute will be run whenever this rule is built. A library may also inherit plugins from dependencies that use exported_plugins. Resources generated by the plugin will be included in the resulting jar of this rule. | List of labels | optional | `[]` | +| processor_class | The processor class is the fully qualified type of the class that the Java compiler should use as entry point to the annotation processor. If not specified, this rule will not contribute an annotation processor to the Java compiler's annotation processing, but its runtime classpath will still be included on the compiler's annotation processor path. (This is primarily intended for use by Error Prone plugins, which are loaded from the annotation processor path using java.util.ServiceLoader.) | String | optional | `""` | +| proguard_specs | Files to be used as Proguard specification. These will describe the set of specifications to be used by Proguard. If specified, they will be added to any android_binary target depending on this library.

The files included here must only have idempotent rules, namely -dontnote, -dontwarn, assumenosideeffects, and rules that start with -keep. Other options can only appear in android_binary's proguard_specs, to ensure non-tautological merges. | List of labels | optional | `[]` | +| resource_strip_prefix | The path prefix to strip from Java resources.

If specified, this path prefix is stripped from every file in the resources attribute. It is an error for a resource file not to be under this directory. If not specified (the default), the path of resource file is determined according to the same logic as the Java package of source files. For example, a source file at stuff/java/foo/bar/a.txt will be located at foo/bar/a.txt.

| String | optional | `""` | + + + + +## java_runtime + +
+java_runtime(name, srcs, default_cds, hermetic_srcs, hermetic_static_libs, java, java_home,
+             lib_ct_sym, lib_modules, output_licenses, version)
+
+ +

+Specifies the configuration for a Java runtime. +

+ +

Example:

+ +
+
+
+java_runtime(
+    name = "jdk-9-ea+153",
+    srcs = glob(["jdk9-ea+153/**"]),
+    java_home = "jdk9-ea+153",
+)
+
+
+
+ +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| srcs | All files in the runtime. | List of labels | optional | `[]` | +| default_cds | Default CDS archive for hermetic java_runtime. When hermetic is enabled for a java_binary target and if the target does not provide its own CDS archive by specifying the classlist attribute, the java_runtime default CDS is packaged in the hermetic deploy JAR. | Label | optional | `None` | +| hermetic_srcs | Files in the runtime needed for hermetic deployments. | List of labels | optional | `[]` | +| hermetic_static_libs | The libraries that are statically linked with the launcher for hermetic deployments | List of labels | optional | `[]` | +| java | The path to the java executable. | Label | optional | `None` | +| java_home | The path to the root of the runtime. Subject to "Make" variable substitution. If this path is absolute, the rule denotes a non-hermetic Java runtime with a well-known path. In that case, the srcs and java attributes must be empty. | String | optional | `""` | +| lib_ct_sym | The lib/ct.sym file needed for compilation with --release. If not specified and there is exactly one file in srcs whose path ends with /lib/ct.sym, that file is used. | Label | optional | `None` | +| lib_modules | The lib/modules file needed for hermetic deployments. | Label | optional | `None` | +| output_licenses | - | List of strings | optional | `[]` | +| version | The feature version of the Java runtime. I.e., the integer returned by Runtime.version().feature(). | Integer | optional | `0` | + + + + +## java_test + +
+java_test(name, deps, srcs, data, resources, add_exports, add_opens, bootclasspath,
+          classpath_resources, create_executable, deploy_manifest_lines, env, env_inherit, javacopts,
+          jvm_flags, launcher, licenses, main_class, neverlink, plugins, resource_strip_prefix,
+          runtime_deps, stamp, test_class, use_launcher, use_testrunner)
+
+ +

+A java_test() rule compiles a Java test. A test is a binary wrapper around your +test code. The test runner's main method is invoked instead of the main class being compiled. +

+ +

Implicit output targets

+
    +
  • name.jar: A Java archive.
  • +
  • name_deploy.jar: A Java archive suitable + for deployment. (Only built if explicitly requested.) See the description of the + name_deploy.jar output from + java_binary for more details.
  • +
+ +

+See the section on java_binary() arguments. This rule also +supports all attributes common +to all test rules (*_test). +

+ +

Examples

+ +
+
+
+java_library(
+    name = "tests",
+    srcs = glob(["*.java"]),
+    deps = [
+        "//java/com/foo/base:testResources",
+        "//java/com/foo/testing/util",
+    ],
+)
+
+java_test(
+    name = "AllTests",
+    size = "small",
+    runtime_deps = [
+        ":tests",
+        "//util/mysql",
+    ],
+)
+
+
+ +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| deps | The list of other libraries to be linked in to the target. See general comments about deps at Typical attributes defined by most build rules. | List of labels | optional | `[]` | +| srcs | The list of source files that are processed to create the target. This attribute is almost always required; see exceptions below.

Source files of type .java are compiled. In case of generated .java files it is generally advisable to put the generating rule's name here instead of the name of the file itself. This not only improves readability but makes the rule more resilient to future changes: if the generating rule generates different files in the future, you only need to fix one place: the outs of the generating rule. You should not list the generating rule in deps because it is a no-op.

Source files of type .srcjar are unpacked and compiled. (This is useful if you need to generate a set of .java files with a genrule.)

Rules: if the rule (typically genrule or filegroup) generates any of the files listed above, they will be used the same way as described for source files.



This argument is almost always required, except if a main_class attribute specifies a class on the runtime classpath or you specify the runtime_deps argument.

| List of labels | optional | `[]` | +| data | The list of files needed by this library at runtime. See general comments about data at Typical attributes defined by most build rules. | List of labels | optional | `[]` | +| resources | A list of data files to include in a Java jar.

Resources may be source files or generated files.



If resources are specified, they will be bundled in the jar along with the usual .class files produced by compilation. The location of the resources inside of the jar file is determined by the project structure. Bazel first looks for Maven's standard directory layout, (a "src" directory followed by a "resources" directory grandchild). If that is not found, Bazel then looks for the topmost directory named "java" or "javatests" (so, for example, if a resource is at <workspace root>/x/java/y/java/z, the path of the resource will be y/java/z. This heuristic cannot be overridden, however, the resource_strip_prefix attribute can be used to specify a specific alternative directory for resource files. | List of labels | optional | `[]` | +| add_exports | Allow this library to access the given module or package.

This corresponds to the javac and JVM --add-exports= flags. | List of strings | optional | `[]` | +| add_opens | Allow this library to reflectively access the given module or package.

This corresponds to the javac and JVM --add-opens= flags. | List of strings | optional | `[]` | +| bootclasspath | Restricted API, do not use! | Label | optional | `None` | +| classpath_resources | DO NOT USE THIS OPTION UNLESS THERE IS NO OTHER WAY)

A list of resources that must be located at the root of the java tree. This attribute's only purpose is to support third-party libraries that require that their resources be found on the classpath as exactly "myconfig.xml". It is only allowed on binaries and not libraries, due to the danger of namespace conflicts.

| List of labels | optional | `[]` | +| create_executable | Deprecated, use java_single_jar instead. | Boolean | optional | `True` | +| deploy_manifest_lines | A list of lines to add to the META-INF/manifest.mf file generated for the *_deploy.jar target. The contents of this attribute are not subject to "Make variable" substitution. | List of strings | optional | `[]` | +| env | - | Dictionary: String -> String | optional | `{}` | +| env_inherit | - | List of strings | optional | `[]` | +| javacopts | Extra compiler options for this binary. Subject to "Make variable" substitution and Bourne shell tokenization.

These compiler options are passed to javac after the global compiler options.

| List of strings | optional | `[]` | +| jvm_flags | A list of flags to embed in the wrapper script generated for running this binary. Subject to $(location) and "Make variable" substitution, and Bourne shell tokenization.

The wrapper script for a Java binary includes a CLASSPATH definition (to find all the dependent jars) and invokes the right Java interpreter. The command line generated by the wrapper script includes the name of the main class followed by a "$@" so you can pass along other arguments after the classname. However, arguments intended for parsing by the JVM must be specified before the classname on the command line. The contents of jvm_flags are added to the wrapper script before the classname is listed.



Note that this attribute has no effect on *_deploy.jar outputs.

| List of strings | optional | `[]` | +| launcher | Specify a binary that will be used to run your Java program instead of the normal bin/java program included with the JDK. The target must be a cc_binary. Any cc_binary that implements the Java Invocation API can be specified as a value for this attribute.

By default, Bazel will use the normal JDK launcher (bin/java or java.exe).



The related --java_launcher Bazel flag affects only those java_binary and java_test targets that have not specified a launcher attribute.



Note that your native (C++, SWIG, JNI) dependencies will be built differently depending on whether you are using the JDK launcher or another launcher:



  • If you are using the normal JDK launcher (the default), native dependencies are built as a shared library named {name}_nativedeps.so, where {name} is the name attribute of this java_binary rule. Unused code is not removed by the linker in this configuration.


  • If you are using any other launcher, native (C++) dependencies are statically linked into a binary named {name}_nativedeps, where {name} is the name attribute of this java_binary rule. In this case, the linker will remove any code it thinks is unused from the resulting binary, which means any C++ code accessed only via JNI may not be linked in unless that cc_library target specifies alwayslink = True.


When using any launcher other than the default JDK launcher, the format of the *_deploy.jar output changes. See the main java_binary docs for details.

| Label | optional | `None` | +| licenses | - | List of strings | optional | `[]` | +| main_class | Name of class with main() method to use as entry point. If a rule uses this option, it does not need a srcs=[...] list. Thus, with this attribute one can make an executable from a Java library that already contains one or more main() methods.

The value of this attribute is a class name, not a source file. The class must be available at runtime: it may be compiled by this rule (from srcs) or provided by direct or transitive dependencies (through runtime_deps or deps). If the class is unavailable, the binary will fail at runtime; there is no build-time check.

| String | optional | `""` | +| neverlink | - | Boolean | optional | `False` | +| plugins | Java compiler plugins to run at compile-time. Every java_plugin specified in this attribute will be run whenever this rule is built. A library may also inherit plugins from dependencies that use exported_plugins. Resources generated by the plugin will be included in the resulting jar of this rule. | List of labels | optional | `[]` | +| resource_strip_prefix | The path prefix to strip from Java resources.

If specified, this path prefix is stripped from every file in the resources attribute. It is an error for a resource file not to be under this directory. If not specified (the default), the path of resource file is determined according to the same logic as the Java package of source files. For example, a source file at stuff/java/foo/bar/a.txt will be located at foo/bar/a.txt.

| String | optional | `""` | +| runtime_deps | Libraries to make available to the final binary or test at runtime only. Like ordinary deps, these will appear on the runtime classpath, but unlike them, not on the compile-time classpath. Dependencies needed only at runtime should be listed here. Dependency-analysis tools should ignore targets that appear in both runtime_deps and deps. | List of labels | optional | `[]` | +| stamp | Whether to encode build information into the binary. Possible values:
  • stamp = 1: Always stamp the build information into the binary, even in --nostamp builds. This setting should be avoided, since it potentially kills remote caching for the binary and any downstream actions that depend on it.
  • stamp = 0: Always replace build information by constant values. This gives good build result caching.
  • stamp = -1: Embedding of build information is controlled by the --[no]stamp flag.

Stamped binaries are not rebuilt unless their dependencies change.

| Integer | optional | `0` | +| test_class | The Java class to be loaded by the test runner.

By default, if this argument is not defined then the legacy mode is used and the test arguments are used instead. Set the --nolegacy_bazel_java_test flag to not fallback on the first argument.

This attribute specifies the name of a Java class to be run by this test. It is rare to need to set this. If this argument is omitted, it will be inferred using the target's name and its source-root-relative path. If the test is located outside a known source root, Bazel will report an error if test_class is unset.

For JUnit3, the test class needs to either be a subclass of junit.framework.TestCase or it needs to have a public static suite() method that returns a junit.framework.Test (or a subclass of Test). For JUnit4, the class needs to be annotated with org.junit.runner.RunWith.

This attribute allows several java_test rules to share the same Test (TestCase, TestSuite, ...). Typically additional information is passed to it (e.g. via jvm_flags=['-Dkey=value']) so that its behavior differs in each case, such as running a different subset of the tests. This attribute also enables the use of Java tests outside the javatests tree.

| String | optional | `""` | +| use_launcher | Whether the binary should use a custom launcher.

If this attribute is set to false, the launcher attribute and the related --java_launcher flag will be ignored for this target. | Boolean | optional | `True` | +| use_testrunner | Use the test runner (by default com.google.testing.junit.runner.BazelTestRunner) class as the main entry point for a Java program, and provide the test class to the test runner as a value of bazel.test_suite system property.


You can use this to override the default behavior, which is to use test runner for java_test rules, and not use it for java_binary rules. It is unlikely you will want to do this. One use is for AllTest rules that are invoked by another rule (to set up a database before running the tests, for example). The AllTest rule must be declared as a java_binary, but should still use the test runner as its main entry point.

The name of a test runner class can be overridden with main_class attribute. | Boolean | optional | `True` | + + + + +## java_toolchain + +

+java_toolchain(name, android_lint_data, android_lint_jvm_opts, android_lint_opts,
+               android_lint_package_configuration, android_lint_runner, bootclasspath,
+               compatible_javacopts, deps_checker, forcibly_disable_header_compilation, genclass,
+               header_compiler, header_compiler_builtin_processors, header_compiler_direct, ijar,
+               jacocorunner, java_runtime, javabuilder, javabuilder_data, javabuilder_jvm_opts,
+               javac_supports_multiplex_workers, javac_supports_worker_cancellation,
+               javac_supports_worker_multiplex_sandboxing, javac_supports_workers, javacopts,
+               jspecify_implicit_deps, jspecify_javacopts, jspecify_packages, jspecify_processor,
+               jspecify_processor_class, jspecify_stubs, jvm_opts, licenses, misc, oneversion,
+               oneversion_allowlist, oneversion_allowlist_for_tests, oneversion_whitelist,
+               package_configuration, proguard_allowlister, reduced_classpath_incompatible_processors,
+               singlejar, source_version, target_version, timezone_data, tools, turbine_data,
+               turbine_jvm_opts, xlint)
+
+ +

+Specifies the configuration for the Java compiler. Which toolchain to be used can be changed through +the --java_toolchain argument. Normally you should not write those kind of rules unless you want to +tune your Java compiler. +

+ +

Examples

+ +

A simple example would be: +

+ +
+
+
+java_toolchain(
+    name = "toolchain",
+    source_version = "7",
+    target_version = "7",
+    bootclasspath = ["//tools/jdk:bootclasspath"],
+    xlint = [ "classfile", "divzero", "empty", "options", "path" ],
+    javacopts = [ "-g" ],
+    javabuilder = ":JavaBuilder_deploy.jar",
+)
+
+
+ +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| android_lint_data | Labels of tools available for label-expansion in android_lint_jvm_opts. | List of labels | optional | `[]` | +| android_lint_jvm_opts | The list of arguments for the JVM when invoking Android Lint. | List of strings | optional | `[]` | +| android_lint_opts | The list of Android Lint arguments. | List of strings | optional | `[]` | +| android_lint_package_configuration | Android Lint Configuration that should be applied to the specified package groups. | List of labels | optional | `[]` | +| android_lint_runner | Label of the Android Lint runner, if any. | Label | optional | `None` | +| bootclasspath | The Java target bootclasspath entries. Corresponds to javac's -bootclasspath flag. | List of labels | optional | `[]` | +| compatible_javacopts | Internal API, do not use! | Dictionary: String -> List of strings | optional | `{}` | +| deps_checker | Label of the ImportDepsChecker deploy jar. | Label | optional | `None` | +| forcibly_disable_header_compilation | Overrides --java_header_compilation to disable header compilation on platforms that do not support it, e.g. JDK 7 Bazel. | Boolean | optional | `False` | +| genclass | Label of the GenClass deploy jar. | Label | optional | `None` | +| header_compiler | Label of the header compiler. Required if --java_header_compilation is enabled. | Label | optional | `None` | +| header_compiler_builtin_processors | Internal API, do not use! | List of strings | optional | `[]` | +| header_compiler_direct | Optional label of the header compiler to use for direct classpath actions that do not include any API-generating annotation processors.

This tool does not support annotation processing. | Label | optional | `None` | +| ijar | Label of the ijar executable. | Label | optional | `None` | +| jacocorunner | Label of the JacocoCoverageRunner deploy jar. | Label | optional | `None` | +| java_runtime | The java_runtime to use with this toolchain. It defaults to java_runtime in execution configuration. | Label | optional | `None` | +| javabuilder | Label of the JavaBuilder deploy jar. | Label | optional | `None` | +| javabuilder_data | Labels of data available for label-expansion in javabuilder_jvm_opts. | List of labels | optional | `[]` | +| javabuilder_jvm_opts | The list of arguments for the JVM when invoking JavaBuilder. | List of strings | optional | `[]` | +| javac_supports_multiplex_workers | True if JavaBuilder supports running as a multiplex persistent worker, false if it doesn't. | Boolean | optional | `True` | +| javac_supports_worker_cancellation | True if JavaBuilder supports cancellation of persistent workers, false if it doesn't. | Boolean | optional | `True` | +| javac_supports_worker_multiplex_sandboxing | True if JavaBuilder supports running as a multiplex persistent worker with sandboxing, false if it doesn't. | Boolean | optional | `False` | +| javac_supports_workers | True if JavaBuilder supports running as a persistent worker, false if it doesn't. | Boolean | optional | `True` | +| javacopts | The list of extra arguments for the Java compiler. Please refer to the Java compiler documentation for the extensive list of possible Java compiler flags. | List of strings | optional | `[]` | +| jspecify_implicit_deps | Experimental, do not use! | Label | optional | `None` | +| jspecify_javacopts | Experimental, do not use! | List of strings | optional | `[]` | +| jspecify_packages | Experimental, do not use! | List of labels | optional | `[]` | +| jspecify_processor | Experimental, do not use! | Label | optional | `None` | +| jspecify_processor_class | Experimental, do not use! | String | optional | `""` | +| jspecify_stubs | Experimental, do not use! | List of labels | optional | `[]` | +| jvm_opts | The list of arguments for the JVM when invoking the Java compiler. Please refer to the Java virtual machine documentation for the extensive list of possible flags for this option. | List of strings | optional | `[]` | +| licenses | - | List of strings | optional | `[]` | +| misc | Deprecated: use javacopts instead | List of strings | optional | `[]` | +| oneversion | Label of the one-version enforcement binary. | Label | optional | `None` | +| oneversion_allowlist | Label of the one-version allowlist. | Label | optional | `None` | +| oneversion_allowlist_for_tests | Label of the one-version allowlist for tests. | Label | optional | `None` | +| oneversion_whitelist | Deprecated: use oneversion_allowlist instead | Label | optional | `None` | +| package_configuration | Configuration that should be applied to the specified package groups. | List of labels | optional | `[]` | +| proguard_allowlister | Label of the Proguard allowlister. | Label | optional | `"@bazel_tools//tools/jdk:proguard_whitelister"` | +| reduced_classpath_incompatible_processors | Internal API, do not use! | List of strings | optional | `[]` | +| singlejar | Label of the SingleJar deploy jar. | Label | optional | `None` | +| source_version | The Java source version (e.g., '6' or '7'). It specifies which set of code structures are allowed in the Java source code. | String | optional | `""` | +| target_version | The Java target version (e.g., '6' or '7'). It specifies for which Java runtime the class should be build. | String | optional | `""` | +| timezone_data | Label of a resource jar containing timezone data. If set, the timezone data is added as an implicitly runtime dependency of all java_binary rules. | Label | optional | `None` | +| tools | Labels of tools available for label-expansion in jvm_opts. | List of labels | optional | `[]` | +| turbine_data | Labels of data available for label-expansion in turbine_jvm_opts. | List of labels | optional | `[]` | +| turbine_jvm_opts | The list of arguments for the JVM when invoking turbine. | List of strings | optional | `[]` | +| xlint | The list of warning to add or removes from default list. Precedes it with a dash to removes it. Please see the Javac documentation on the -Xlint options for more information. | List of strings | optional | `[]` | + + diff --git a/java/extensions.bzl b/java/extensions.bzl index f456f3f5..a791d179 100644 --- a/java/extensions.bzl +++ b/java/extensions.bzl @@ -21,8 +21,10 @@ load( "remote_jdk11_repos", "remote_jdk17_repos", "remote_jdk21_repos", + "remote_jdk25_repos", "remote_jdk8_repos", ) +load("//toolchains:extensions.bzl", _java_repository = "java_repository") def _toolchains_impl(module_ctx): java_tools_repos() @@ -31,6 +33,7 @@ def _toolchains_impl(module_ctx): remote_jdk11_repos() remote_jdk17_repos() remote_jdk21_repos() + remote_jdk25_repos() if bazel_features.external_deps.extension_metadata_has_reproducible: return module_ctx.extension_metadata(reproducible = True) @@ -38,3 +41,5 @@ def _toolchains_impl(module_ctx): return None toolchains = module_extension(_toolchains_impl) + +java_repository = _java_repository diff --git a/java/http_jar.bzl b/java/http_jar.bzl new file mode 100644 index 00000000..ed3c3317 --- /dev/null +++ b/java/http_jar.bzl @@ -0,0 +1,27 @@ +"""The http_jar repo rule, for downloading jars over HTTP. + +### Setup + +To use this rule in a module extension, load it in your .bzl file and then call it from your +extension's implementation function. For example: + +```python +load("@rules_java//java:http_jar.bzl", "http_jar") + +def _my_extension_impl(mctx): + http_jar(name = "foo", urls = [...]) + +my_extension = module_extension(implementation = _my_extension_impl) +``` + +Alternatively, you can directly call it your MODULE.bazel file with `use_repo_rule`: + +```python +http_jar = use_repo_rule("@rules_java//java:http_jar.bzl", "http_jar") +http_jar(name = "foo", urls = [...]) +``` +""" + +load("@compatibility_proxy//:proxy.bzl", _http_jar = "http_jar") + +http_jar = _http_jar diff --git a/java/java_binary.bzl b/java/java_binary.bzl index 7184ecbd..3c86ed21 100644 --- a/java/java_binary.bzl +++ b/java/java_binary.bzl @@ -13,6 +13,8 @@ # limitations under the License. """java_binary rule""" +load("@compatibility_proxy//:proxy.bzl", _java_binary = "java_binary") + def java_binary(**attrs): """Bazel java_binary rule. @@ -22,5 +24,4 @@ def java_binary(**attrs): **attrs: Rule attributes """ - # buildifier: disable=native-java - native.java_binary(**attrs) + _java_binary(**attrs) diff --git a/java/java_import.bzl b/java/java_import.bzl index 926c37a6..7dfef834 100644 --- a/java/java_import.bzl +++ b/java/java_import.bzl @@ -13,6 +13,8 @@ # limitations under the License. """java_import rule""" +load("@compatibility_proxy//:proxy.bzl", _java_import = "java_import") + def java_import(**attrs): """Bazel java_import rule. @@ -22,5 +24,4 @@ def java_import(**attrs): **attrs: Rule attributes """ - # buildifier: disable=native-java - native.java_import(**attrs) + _java_import(**attrs) diff --git a/java/java_library.bzl b/java/java_library.bzl index 9308bbc4..81e0ba16 100644 --- a/java/java_library.bzl +++ b/java/java_library.bzl @@ -13,6 +13,8 @@ # limitations under the License. """java_library rule""" +load("@compatibility_proxy//:proxy.bzl", _java_library = "java_library") + def java_library(**attrs): """Bazel java_library rule. @@ -22,5 +24,4 @@ def java_library(**attrs): **attrs: Rule attributes """ - # buildifier: disable=native-java - native.java_library(**attrs) + _java_library(**attrs) diff --git a/java/java_plugin.bzl b/java/java_plugin.bzl index d12e0e18..e73843ab 100644 --- a/java/java_plugin.bzl +++ b/java/java_plugin.bzl @@ -13,6 +13,8 @@ # limitations under the License. """java_plugin rule""" +load("@compatibility_proxy//:proxy.bzl", _java_plugin = "java_plugin") + def java_plugin(**attrs): """Bazel java_plugin rule. @@ -22,5 +24,4 @@ def java_plugin(**attrs): **attrs: Rule attributes """ - # buildifier: disable=native-java - native.java_plugin(**attrs) + _java_plugin(**attrs) diff --git a/java/java_single_jar.bzl b/java/java_single_jar.bzl index 6e85bdef..84771614 100644 --- a/java/java_single_jar.bzl +++ b/java/java_single_jar.bzl @@ -1,143 +1,5 @@ -""" Definition of _java_single_jar. """ +"""The java_single_jar rule""" -load("//java/common:java_common.bzl", "java_common") -load("//java/common:java_info.bzl", "JavaInfo") +load("//java/bazel/rules:bazel_java_single_jar.bzl", _java_single_jar = "java_single_jar") -def _java_single_jar(ctx): - transitive_inputs = [] - for dep in ctx.attr.deps: - if JavaInfo in dep: - info = dep[JavaInfo] - transitive_inputs.append(info.transitive_runtime_jars) - if hasattr(info, "compilation_info"): - compilation_info = info.compilation_info - if hasattr(compilation_info, "runtime_classpath"): - transitive_inputs.append(compilation_info.runtime_classpath) - else: - files = [] - for f in dep[DefaultInfo].files.to_list(): - if not f.extension == "jar": - fail("unexpected file type in java_single_jar.deps: %s" % f.path) - files.append(f) - transitive_inputs.append(depset(files)) - inputs = depset(transitive = transitive_inputs) - - if hasattr(java_common, "JavaRuntimeClasspathInfo"): - deploy_env_jars = depset(transitive = [ - dep[java_common.JavaRuntimeClasspathInfo].runtime_classpath - for dep in ctx.attr.deploy_env - ]) - excluded_jars = {jar: None for jar in deploy_env_jars.to_list()} - if excluded_jars: - inputs = depset([jar for jar in inputs.to_list() if jar not in excluded_jars]) - - args = ctx.actions.args() - args.add_all("--sources", inputs) - args.use_param_file("@%s") - args.set_param_file_format("multiline") - args.add_all("--deploy_manifest_lines", ctx.attr.deploy_manifest_lines) - args.add("--output", ctx.outputs.jar) - args.add("--normalize") - - # Deal with limitation of singlejar flags: tool's default behavior is - # "no", but you get that behavior only by absence of compression flags. - if ctx.attr.compress == "preserve": - args.add("--dont_change_compression") - elif ctx.attr.compress == "yes": - args.add("--compression") - elif ctx.attr.compress == "no": - pass - else: - fail("\"compress\" attribute (%s) must be: yes, no, preserve." % ctx.attr.compress) - - if ctx.attr.exclude_build_data: - args.add("--exclude_build_data") - if ctx.attr.multi_release: - args.add("--multi_release") - - ctx.actions.run( - inputs = inputs, - outputs = [ctx.outputs.jar], - arguments = [args], - progress_message = "Merging into %s" % ctx.outputs.jar.short_path, - mnemonic = "JavaSingleJar", - executable = ctx.executable._singlejar, - ) - - files = depset([ctx.outputs.jar]) - providers = [DefaultInfo( - files = files, - runfiles = ctx.runfiles(transitive_files = files), - )] - if hasattr(java_common, "JavaRuntimeClasspathInfo"): - providers.append(java_common.JavaRuntimeClasspathInfo(runtime_classpath = inputs)) - return providers - -java_single_jar = rule( - attrs = { - "deps": attr.label_list( - allow_files = True, - doc = """ - The Java targets (including java_import and java_library) to collect - transitive dependencies from. Runtime dependencies are collected via - deps, exports, and runtime_deps. Resources are also collected. - Native cc_library or java_wrap_cc dependencies are not.""", - ), - "deploy_manifest_lines": attr.string_list(doc = """ - A list of lines to add to the META-INF/manifest.mf file."""), - "deploy_env": attr.label_list( - providers = [java_common.JavaRuntimeClasspathInfo] if hasattr(java_common, "JavaRuntimeClasspathInfo") else [], - allow_files = False, - doc = """ - A list of `java_binary` or `java_single_jar` targets which represent - the deployment environment for this binary. - - Set this attribute when building a plugin which will be loaded by another - `java_binary`. - - `deploy_env` dependencies are excluded from the jar built by this rule.""", - ), - "compress": attr.string(default = "preserve", doc = """ - Whether to always deflate ("yes"), always store ("no"), or pass - through unmodified ("preserve"). The default is "preserve", and is the - most efficient option -- no extra work is done to inflate or deflate."""), - "exclude_build_data": attr.bool(default = True, doc = """ - Whether to omit the build-data.properties file generated - by default."""), - "multi_release": attr.bool(default = True, doc = """Whether to enable Multi-Release output jars."""), - "_singlejar": attr.label( - default = Label("//toolchains:singlejar"), - cfg = "exec", - allow_single_file = True, - executable = True, - ), - }, - outputs = { - "jar": "%{name}.jar", - }, - implementation = _java_single_jar, - doc = """ -Collects Java dependencies and jar files into a single jar - -`java_single_jar` collects Java dependencies and jar files into a single jar. -This is similar to java_binary with everything related to executables disabled, -and provides an alternative to the java_binary "deploy jar hack". - -## Example - -```skylark -load("//tools/build_defs/java_single_jar:java_single_jar.bzl", "java_single_jar") - -java_single_jar( - name = "my_single_jar", - deps = [ - "//java/com/google/foo", - "//java/com/google/bar", - ], -) -``` - -Outputs: - {name}.jar: A single jar containing all of the inputs. -""", -) +java_single_jar = _java_single_jar diff --git a/java/java_test.bzl b/java/java_test.bzl index 0aeac91b..d11f10bd 100644 --- a/java/java_test.bzl +++ b/java/java_test.bzl @@ -13,6 +13,8 @@ # limitations under the License. """java_test rule""" +load("@compatibility_proxy//:proxy.bzl", _java_test = "java_test") + def java_test(**attrs): """Bazel java_test rule. @@ -22,5 +24,4 @@ def java_test(**attrs): **attrs: Rule attributes """ - # buildifier: disable=native-java - native.java_test(**attrs) + _java_test(**attrs) diff --git a/java/private/BUILD b/java/private/BUILD index 69481997..510c3010 100644 --- a/java/private/BUILD +++ b/java/private/BUILD @@ -1,15 +1,84 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +package(default_applicable_licenses = ["@rules_java//:license"]) + licenses(["notice"]) bzl_library( - name = "private", + name = "native_bzl", srcs = [ "native.bzl", ], visibility = ["//java:__subpackages__"], ) +bzl_library( + name = "legacy_native_bzl", + srcs = [ + "legacy_native.bzl", + ], + visibility = ["@compatibility_proxy//:__pkg__"], +) + +bzl_library( + name = "internals", + srcs = [ + "java_common.bzl", + "java_common_internal.bzl", + "message_bundle_info.bzl", + ], + visibility = [ + "//java:__subpackages__", + "@compatibility_proxy//:__pkg__", + ], + deps = [ + ":boot_class_path_info_bzl", + ":java_info_bzl", + ":native_bzl", + "//java/common:semantics_bzl", + "//java/common/rules:java_helper_bzl", + "//java/common/rules:toolchain_rules", + "@bazel_skylib//lib:paths", + "@bazel_skylib//rules:common_settings", + "@rules_cc//cc:find_cc_toolchain_bzl", + "@rules_cc//cc/common", + ], +) + +bzl_library( + name = "boot_class_path_info_bzl", + srcs = ["boot_class_path_info.bzl"], + visibility = ["//java:__subpackages__"], + deps = ["@bazel_skylib//lib:paths"], +) + +bzl_library( + name = "java_info_bzl", + srcs = ["java_info.bzl"], + visibility = ["//java:__subpackages__"], + deps = [ + ":native_bzl", + "//java/common:semantics_bzl", + "@rules_cc//cc/common", + ], +) + +# Exposed for use by the protobuf. +bzl_library( + name = "proto_support", + srcs = ["proto_support.bzl"], + visibility = ["//visibility:public"], + deps = ["@compatibility_proxy//:proxy_bzl"], +) + +# Exposed for use by the android rules. +bzl_library( + name = "android_support", + srcs = ["android_support.bzl"], + visibility = ["//visibility:public"], + deps = ["@compatibility_proxy//:proxy_bzl"], +) + filegroup( name = "srcs", srcs = glob(["**"]), @@ -21,7 +90,10 @@ filegroup( testonly = 1, srcs = [ "BUILD", - ":private", + ":android_support", + ":internals", + ":native_bzl", + ":proto_support", ], visibility = ["//java:__pkg__"], ) diff --git a/java/bazel/rules/empty.bzl b/java/private/android_support.bzl similarity index 71% rename from java/bazel/rules/empty.bzl rename to java/private/android_support.bzl index 6981db7e..fa80a17b 100644 --- a/java/bazel/rules/empty.bzl +++ b/java/private/android_support.bzl @@ -11,4 +11,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Empty file as a placeholder for migration""" +"""Legacy support for the android rules.""" + +load("@compatibility_proxy//:proxy.bzl", "java_info_to_implicit_exportable") + +android_support = struct( + enable_implicit_sourceless_deps_exports_compatibility = java_info_to_implicit_exportable, +) diff --git a/java/private/boot_class_path_info.bzl b/java/private/boot_class_path_info.bzl new file mode 100644 index 00000000..f742e8fa --- /dev/null +++ b/java/private/boot_class_path_info.bzl @@ -0,0 +1,67 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Definition of the BootClassPathInfo provider. +""" + +load("@bazel_skylib//lib:paths.bzl", "paths") + +visibility( + ["//java/..."], +) + +def _init(bootclasspath = [], auxiliary = [], system = None): + """The BootClassPathInfo constructor. + + Args: + bootclasspath: ([File]) + auxiliary: ([File]) + system: ([File]|File|None) + """ + if not system: # None or [] + system_inputs = depset() + system_path = None + elif type(system) == "File": + system_inputs = depset([system]) + if not system.is_directory: + fail("for system,", system, "is not a directory") + system_path = system.path + elif type(system) == type([]): + system_inputs = depset(system) + system_paths = [input.path for input in system if input.basename == "release"] + if not system_paths: + fail("for system, expected inputs to contain 'release'") + system_path = paths.dirname(system_paths[0]) + else: + fail("for system, got", type(system), ", want File, sequence, or None") + + return { + "bootclasspath": depset(bootclasspath), + "_auxiliary": depset(auxiliary), + "_system_inputs": system_inputs, + "_system_path": system_path, + } + +BootClassPathInfo, _new_bootclasspathinfo = provider( + doc = "Information about the system APIs for a Java compilation.", + fields = [ + "bootclasspath", + # private + "_auxiliary", + "_system_inputs", + "_system_path", + ], + init = _init, +) diff --git a/java/private/java_common.bzl b/java/private/java_common.bzl new file mode 100644 index 00000000..59ba7dfc --- /dev/null +++ b/java/private/java_common.bzl @@ -0,0 +1,333 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" Utilities for Java compilation support in Starlark. """ + +load("@bazel_skylib//lib:paths.bzl", "paths") +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules:java_helper.bzl", "helper") +load("//java/common/rules:java_runtime.bzl", "JavaRuntimeInfo") +load("//java/common/rules:java_toolchain.bzl", "JavaToolchainInfo") +load(":boot_class_path_info.bzl", "BootClassPathInfo") +load( + ":java_common_internal.bzl", + _compile_header_internal = "compile_header", + _compile_internal = "compile", + _run_ijar_internal = "run_ijar", +) +load( + ":java_info.bzl", + "JavaInfo", + "JavaPluginInfo", + _java_info_make_non_strict = "make_non_strict", + _java_info_merge = "merge", + _java_info_set_annotation_processing = "set_annotation_processing", +) +load(":message_bundle_info.bzl", "MessageBundleInfo") +load(":native.bzl", "get_internal_java_common") + +# copybara: default visibility + +JavaRuntimeClasspathInfo = provider( + "Provider for the runtime classpath contributions of a Java binary.", + fields = ["runtime_classpath"], +) + +def _compile( + ctx, + output, + java_toolchain, + source_jars = [], + source_files = [], + output_source_jar = None, + javac_opts = [], + deps = [], + runtime_deps = [], + exports = [], + plugins = [], + exported_plugins = [], + native_libraries = [], + annotation_processor_additional_inputs = [], + annotation_processor_additional_outputs = [], + strict_deps = "ERROR", + bootclasspath = None, + sourcepath = [], + resources = [], + neverlink = False, + enable_annotation_processing = True): + return _compile_internal( + ctx, + output = output, + java_toolchain = java_toolchain, + source_jars = source_jars, + source_files = source_files, + output_source_jar = output_source_jar, + javac_opts = javac_opts, + deps = deps, + runtime_deps = runtime_deps, + exports = exports, + plugins = plugins, + exported_plugins = exported_plugins, + native_libraries = native_libraries, + annotation_processor_additional_inputs = annotation_processor_additional_inputs, + annotation_processor_additional_outputs = annotation_processor_additional_outputs, + strict_deps = strict_deps, + bootclasspath = bootclasspath, + sourcepath = sourcepath, + resources = resources, + neverlink = neverlink, + enable_annotation_processing = enable_annotation_processing, + ) + +def _compile_header( + ctx, + output, + java_toolchain, + source_jars = [], + source_files = [], + javac_opts = [], + deps = [], + plugins = [], + strict_deps = "ERROR", + bootclasspath = None, + enable_annotation_processing = True): + return _compile_header_internal( + ctx, + output = output, + java_toolchain = java_toolchain, + source_jars = source_jars, + source_files = source_files, + javac_opts = javac_opts, + deps = deps, + plugins = plugins, + strict_deps = strict_deps, + bootclasspath = bootclasspath, + enable_annotation_processing = enable_annotation_processing, + ) + +def _run_ijar(actions, jar, java_toolchain, target_label = None): + get_internal_java_common().check_java_toolchain_is_declared_on_rule(actions) + return _run_ijar_internal( + actions = actions, + jar = jar, + java_toolchain = java_toolchain, + target_label = target_label, + ) + +def _stamp_jar(actions, jar, java_toolchain, target_label): + """Stamps a jar with a target label for add_dep support. + + The return value is typically passed to `JavaInfo.compile_jar`. Prefer to use `run_ijar` when + possible. + + Args: + actions: (actions) ctx.actions + jar: (File) The jar to run stamp_jar on. + java_toolchain: (JavaToolchainInfo) The toolchain to used to find the stamp_jar tool. + target_label: (Label) A target label to stamp the jar with. Used for `add_dep` support. + Typically, you would pass `ctx.label` to stamp the jar with the current rule's label. + + Returns: + (File) The output artifact + + """ + get_internal_java_common().check_java_toolchain_is_declared_on_rule(actions) + output = actions.declare_file(paths.replace_extension(jar.basename, "-stamped.jar"), sibling = jar) + args = actions.args() + args.add(jar) + args.add(output) + args.add("--nostrip_jar") + args.add("--target_label", target_label) + actions.run( + mnemonic = "JavaIjar", + inputs = [jar], + outputs = [output], + executable = java_toolchain.ijar, # ijar doubles as a stamping tool + arguments = [args], + progress_message = "Stamping target label into jar %{input}", + toolchain = semantics.JAVA_TOOLCHAIN_TYPE, + use_default_shell_env = True, + ) + return output + +def _pack_sources( + actions, + java_toolchain, + output_source_jar, + sources = [], + source_jars = []): + """Packs sources and source jars into a single source jar file. + + The return value is typically passed to `JavaInfo.source_jar`. + + Args: + actions: (actions) ctx.actions + java_toolchain: (JavaToolchainInfo) The toolchain used to find the ijar tool. + output_source_jar: (File) The output source jar. + sources: ([File]) A list of Java source files to be packed into the source jar. + source_jars: ([File]) A list of source jars to be packed into the source jar. + + Returns: + (File) The output artifact + """ + get_internal_java_common().check_java_toolchain_is_declared_on_rule(actions) + return helper.create_single_jar( + actions, + toolchain = java_toolchain, + output = output_source_jar, + sources = depset(source_jars), + resources = depset(sources), + progress_message = "Building source jar %{output}", + mnemonic = "JavaSourceJar", + ) + +# TODO: b/78512644 - migrate callers to passing explicit javacopts or using custom toolchains, and delete +def _default_javac_opts(java_toolchain): + """Experimental! Get default javacopts from a java toolchain + + Args: + java_toolchain: (JavaToolchainInfo) the toolchain from which to get the javac options. + + Returns: + ([str]) A list of javac options + """ + return java_toolchain._javacopts_list + +# temporary for migration +def _default_javac_opts_depset(java_toolchain): + """Experimental! Get default javacopts from a java toolchain + + Args: + java_toolchain: (JavaToolchainInfo) the toolchain from which to get the javac options. + + Returns: + (depset[str]) A depset of javac options that should be tokenized before passing to javac + """ + return java_toolchain._javacopts + +def _merge(providers): + """Merges the given providers into a single JavaInfo. + + Args: + providers: ([JavaInfo]) The list of providers to merge. + + Returns: + (JavaInfo) The merged JavaInfo + """ + return _java_info_merge(providers) + +def _make_non_strict(java_info): + """Returns a new JavaInfo instance whose direct-jars part is the union of both the direct and indirect jars of the given Java provider. + + Args: + java_info: (JavaInfo) The java info to make non-strict. + + Returns: + (JavaInfo) + """ + return _java_info_make_non_strict(java_info) + +def _get_message_bundle_info(): + return None if semantics.IS_BAZEL else MessageBundleInfo + +def _get_constraints(java_info): + """Returns a set of constraints added. + + Args: + java_info: (JavaInfo) The JavaInfo to get constraints from. + + Returns: + ([str]) The constraints set on the supplied JavaInfo + """ + return [] if semantics.IS_BAZEL else java_info._constraints + +def _set_annotation_processing( + java_info, + enabled = False, + processor_classnames = [], + processor_classpath = None, + class_jar = None, + source_jar = None): + """Returns a copy of the given JavaInfo with the given annotation_processing info. + + Args: + java_info: (JavaInfo) The JavaInfo to enhance. + enabled: (bool) Whether the rule uses annotation processing. + processor_classnames: ([str]) Class names of annotation processors applied. + processor_classpath: (depset[File]) Class names of annotation processors applied. + class_jar: (File) Optional. Jar that is the result of annotation processing. + source_jar: (File) Optional. Source archive resulting from annotation processing. + + Returns: + (JavaInfo) + """ + if semantics.IS_BAZEL: + return None + + return _java_info_set_annotation_processing( + java_info, + enabled = enabled, + processor_classnames = processor_classnames, + processor_classpath = processor_classpath, + class_jar = class_jar, + source_jar = source_jar, + ) + +def _java_toolchain_label(java_toolchain): + """Returns the toolchain's label. + + Args: + java_toolchain: (JavaToolchainInfo) The toolchain. + Returns: + (Label) + """ + if semantics.IS_BAZEL: + # No implementation in Bazel. This method is not callable in Starlark except through + # (discouraged) use of --experimental_google_legacy_api. + return None + + get_internal_java_common().check_provider_instances([java_toolchain], "java_toolchain", JavaToolchainInfo) + return java_toolchain.label + +def _make_java_common(): + methods = { + "provider": JavaInfo, + "compile": _compile, + "compile_header": _compile_header, + "run_ijar": _run_ijar, + "stamp_jar": _stamp_jar, + "pack_sources": _pack_sources, + "default_javac_opts": _default_javac_opts, + "default_javac_opts_depset": _default_javac_opts_depset, + "merge": _merge, + "JavaPluginInfo": JavaPluginInfo, + "JavaToolchainInfo": JavaToolchainInfo, + "JavaRuntimeInfo": JavaRuntimeInfo, + "BootClassPathInfo": BootClassPathInfo, + "JavaRuntimeClasspathInfo": JavaRuntimeClasspathInfo, + } + if get_internal_java_common().google_legacy_api_enabled(): + methods.update( + MessageBundleInfo = _get_message_bundle_info(), # struct field that is None in bazel + get_constraints = _get_constraints, + set_annotation_processing = _set_annotation_processing, + java_toolchain_label = _java_toolchain_label, + ) + if semantics.IS_BAZEL: + methods.update( + make_non_strict = _make_non_strict, + ) + return struct(**methods) + +java_common = _make_java_common() diff --git a/java/private/java_common_internal.bzl b/java/private/java_common_internal.bzl new file mode 100644 index 00000000..30d9533c --- /dev/null +++ b/java/private/java_common_internal.bzl @@ -0,0 +1,639 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" Private utilities for Java compilation support in Starlark. """ + +load("@bazel_skylib//lib:paths.bzl", "paths") +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules:java_helper.bzl", "helper") +load("//java/common/rules:java_toolchain.bzl", "JavaToolchainInfo") +load( + ":java_info.bzl", + "JavaPluginInfo", + "disable_plugin_info_annotation_processing", + "java_info_for_compilation", + "merge_plugin_info_without_outputs", +) +load(":native.bzl", "get_internal_java_common") + +# copybara: default multiline visibility + +_STRICT_DEPS_VALUES = [ + "OFF", # Silently allow referencing transitive dependencies. + "WARN", # Warn about transitive dependencies being used directly. + "ERROR", # Fail the build when transitive dependencies are used directly. + "STRICT", # Transition to strict by default. + "DEFAULT", # When no flag value is specified on the command line. +] + +def _construct_javac_opts(ctx, java_toolchain, plugin_info, javac_opts, bootclasspath, add_exports = []): + all_javac_opts = [] # [depset[str]] + all_javac_opts.append(java_toolchain._javacopts) + all_javac_opts.append(ctx.fragments.java.default_javac_flags_depset) + all_javac_opts.append(semantics.compatible_javac_options(ctx, java_toolchain)) + + if ("com.google.devtools.build.runfiles.AutoBazelRepositoryProcessor" in + plugin_info.plugins.processor_classes.to_list()): + all_javac_opts.append(depset( + ["-Abazel.repository=" + ctx.label.repo_name], + order = "preorder", + )) + system_bootclasspath = None + for package_config in java_toolchain._package_configuration: + if package_config.matches(package_config.package_specs, ctx.label): + all_javac_opts.append(package_config.javac_opts) + if package_config.system: + if system_bootclasspath: + fail("Multiple system package configurations found for %s" % ctx.label) + system_bootclasspath = package_config.system + if not bootclasspath: + bootclasspath = system_bootclasspath + + all_javac_opts.append(depset( + ["--add-exports=%s=ALL-UNNAMED" % x for x in add_exports], + order = "preorder", + )) + + if type(javac_opts) == type([]): + # detokenize target's javacopts, it will be tokenized before compilation + all_javac_opts.append(helper.detokenize_javacopts(helper.tokenize_javacopts(ctx, javac_opts))) + elif type(javac_opts) == type(depset()): + all_javac_opts.append(javac_opts) + else: + fail("Expected javac_opts to be a list or depset, got:", type(javac_opts)) + + # we reverse the list of javacopts depsets, so that we keep the right-most set + # in case it's deduped. When this depset is flattened, we will reverse again, + # and then tokenize before passing to javac. This way, right-most javacopts will + # be retained and "win out". + return depset(order = "preorder", transitive = reversed(all_javac_opts)), bootclasspath + +def _construct_classpaths(deps, strict_deps, classpath_mode): + is_strict_mode = strict_deps != "OFF" + + direct_jars = depset() + if is_strict_mode: + direct_jars = depset(order = "preorder", transitive = [dep.compile_jars for dep in deps]) + + header_compilation_direct_deps = depset() + if is_strict_mode: + header_compilation_direct_deps = depset( + order = "preorder", + transitive = [dep.header_compilation_direct_deps for dep in deps], + ) + + compilation_classpath = depset( + order = "preorder", + transitive = [direct_jars] + [dep.transitive_compile_time_jars for dep in deps], + ) + compile_time_java_deps = depset() + if is_strict_mode and classpath_mode != "OFF": + compile_time_java_deps = depset(transitive = [dep._compile_time_java_dependencies for dep in deps]) + + return struct( + direct_jars = direct_jars, + header_compilation_direct_deps = header_compilation_direct_deps, + compilation_classpath = compilation_classpath, + compile_time_java_deps = compile_time_java_deps, + ) + +def _derive_header_compilation_outputs(ctx, base_output, suffix = ""): + if suffix: + compile_jar = _derive_output_file(ctx, base_output, name_suffix = suffix, extension = "jar") + compile_deps_proto = _derive_output_file(ctx, base_output, name_suffix = suffix, extension = "jdeps") + else: + compile_jar = base_output + compile_deps_proto = _derive_output_file(ctx, base_output, extension = "jdeps") + + # TODO: b/417791104 - remove check after a Bazel release + if ctx.fragments.java.use_header_compilation_direct_deps(): + header_compilation_jar = _derive_output_file(ctx, base_output, name_suffix = "-tjar", extension = "jar") + else: + header_compilation_jar = None + + return struct( + compile_jar = compile_jar, + header_compilation_jar = header_compilation_jar, + compile_deps_proto = compile_deps_proto, + ) + +def _validate_strict_deps(strict_deps): + strict_deps = (strict_deps or "default").upper() + if strict_deps not in _STRICT_DEPS_VALUES: + fail("Got an invalid value for strict_deps:", strict_deps, "must be one of:", _STRICT_DEPS_VALUES) + return strict_deps + +def compile( + ctx, + output, + java_toolchain, + source_jars = [], + source_files = [], + output_source_jar = None, + javac_opts = [], + deps = [], + runtime_deps = [], + exports = [], + plugins = [], + exported_plugins = [], + native_libraries = [], + annotation_processor_additional_inputs = [], + annotation_processor_additional_outputs = [], + strict_deps = "ERROR", + bootclasspath = None, + javabuilder_jvm_flags = None, + sourcepath = [], + resources = [], + add_exports = [], + add_opens = [], + neverlink = False, + enable_annotation_processing = True, + # private to @_builtins: + enable_compile_jar_action = True, + enable_jspecify = True, + include_compilation_info = True, + classpath_resources = [], + resource_jars = [], + injecting_rule_kind = None): + """Compiles Java source files/jars from the implementation of a Starlark rule + + The result is a provider that represents the results of the compilation and can be added to the + set of providers emitted by this rule. + + Args: + ctx: (RuleContext) The rule context + output: (File) The output of compilation + java_toolchain: (JavaToolchainInfo) Toolchain to be used for this compilation. Mandatory. + source_jars: ([File]) A list of the jars to be compiled. At least one of source_jars or + source_files should be specified. + source_files: ([File]) A list of the Java source files to be compiled. At least one of + source_jars or source_files should be specified. + output_source_jar: (File) The output source jar. Optional. Defaults to + `{output_jar}-src.jar` if unset. + javac_opts: ([str]|depset[str]) A list of the desired javac options. Optional. + deps: ([JavaInfo]) A list of dependencies. Optional. + runtime_deps: ([JavaInfo]) A list of runtime dependencies. Optional. + exports: ([JavaInfo]) A list of exports. Optional. + plugins: ([JavaPluginInfo|JavaInfo]) A list of plugins. Optional. + exported_plugins: ([JavaPluginInfo|JavaInfo]) A list of exported plugins. Optional. + native_libraries: ([CcInfo]) CC library dependencies that are needed for this library. + annotation_processor_additional_inputs: ([File]) A list of inputs that the Java compilation + action will take in addition to the Java sources for annotation processing. + annotation_processor_additional_outputs: ([File]) A list of outputs that the Java + compilation action will output in addition to the class jar from annotation processing. + strict_deps: (str) A string that specifies how to handle strict deps. Possible values: + 'OFF', 'ERROR', 'WARN' and 'DEFAULT'. + bootclasspath: (BootClassPathInfo) If present, overrides the bootclasspath associated with + the provided java_toolchain. Optional. + javabuilder_jvm_flags: (list[str]) Additional JVM flags to pass to JavaBuilder. + sourcepath: ([File]) + resources: ([File]) + resource_jars: ([File]) + classpath_resources: ([File]) + neverlink: (bool) + enable_annotation_processing: (bool) Disables annotation processing in this compilation, + causing any annotation processors provided in plugins or in exported_plugins of deps to + be ignored. + enable_compile_jar_action: (bool) Enables header compilation or ijar creation. If set to + False, it forces use of the full class jar in the compilation classpaths of any + dependants. Doing so is intended for use by non-library targets such as binaries that + do not have dependants. + enable_jspecify: (bool) + include_compilation_info: (bool) + injecting_rule_kind: (str|None) + add_exports: ([str]) Allow this library to access the given /. Optional. + add_opens: ([str]) Allow this library to reflectively access the given /. + Optional. + + Returns: + (JavaInfo) + """ + get_internal_java_common().check_provider_instances([java_toolchain], "java_toolchain", JavaToolchainInfo) + get_internal_java_common().check_provider_instances(plugins, "plugins", JavaPluginInfo) + + strict_deps = _validate_strict_deps(strict_deps) + + plugin_info = merge_plugin_info_without_outputs(plugins + deps) + + all_javac_opts, bootclasspath = _construct_javac_opts( + ctx, + java_toolchain, + plugin_info, + javac_opts, + bootclasspath, + add_exports, + ) + + # Optimization: skip this if there are no annotation processors, to avoid unnecessarily + # disabling the direct classpath optimization if `enable_annotation_processor = False` + # but there aren't any annotation processors. + enable_direct_classpath = True + if not enable_annotation_processing and plugin_info.plugins.processor_classes: + plugin_info = disable_plugin_info_annotation_processing(plugin_info) + enable_direct_classpath = False + + all_javac_opts_list = helper.tokenize_javacopts(ctx, all_javac_opts) + uses_annotation_processing = False + if "-processor" in all_javac_opts_list or plugin_info.plugins.processor_classes: + uses_annotation_processing = True + + has_sources = source_files or source_jars + has_resources = resources or resource_jars + + classpath_mode = ctx.fragments.java.reduce_java_classpath() + classpaths = _construct_classpaths(deps, strict_deps, classpath_mode) + + # create compile time jar action + if not has_sources: + compile_jar = None + header_compilation_jar = None + compile_deps_proto = None + elif not enable_compile_jar_action: + compile_jar = output + header_compilation_jar = compile_jar + compile_deps_proto = None + elif _should_use_header_compilation(ctx, java_toolchain): + hdr_outputs = _derive_header_compilation_outputs(ctx, output, suffix = "-hjar") + compile_jar = hdr_outputs.compile_jar + header_compilation_jar = hdr_outputs.header_compilation_jar + compile_deps_proto = hdr_outputs.compile_deps_proto + get_internal_java_common().create_header_compilation_action( + ctx, + java_toolchain, + compile_jar, + compile_deps_proto, + plugin_info, + depset(source_files), + source_jars, + classpaths.compilation_classpath, + classpaths.direct_jars, + bootclasspath, + classpaths.compile_time_java_deps, + all_javac_opts, + strict_deps, + ctx.label, + injecting_rule_kind, + enable_direct_classpath, + annotation_processor_additional_inputs, + header_compilation_jar, + classpaths.header_compilation_direct_deps, + ) + elif ctx.fragments.java.use_ijars(): + compile_jar = run_ijar( + ctx.actions, + output, + java_toolchain, + target_label = ctx.label, + injecting_rule_kind = injecting_rule_kind, + ) + header_compilation_jar = compile_jar + compile_deps_proto = None + else: + compile_jar = output + header_compilation_jar = compile_jar + compile_deps_proto = None + + native_headers_jar = _derive_output_file(ctx, output, name_suffix = "-native-header") + manifest_proto = _derive_output_file(ctx, output, extension_suffix = "_manifest_proto") + deps_proto = None + if ctx.fragments.java.generate_java_deps() and has_sources: + deps_proto = _derive_output_file(ctx, output, extension = "jdeps") + generated_class_jar = None + generated_source_jar = None + if uses_annotation_processing: + generated_class_jar = _derive_output_file(ctx, output, name_suffix = "-gen") + generated_source_jar = _derive_output_file(ctx, output, name_suffix = "-gensrc") + get_internal_java_common().create_compilation_action( + ctx, + java_toolchain, + output, + manifest_proto, + plugin_info, + classpaths.compilation_classpath, + classpaths.direct_jars, + bootclasspath, + depset(javabuilder_jvm_flags), + classpaths.compile_time_java_deps, + all_javac_opts, + strict_deps, + ctx.label, + deps_proto, + generated_class_jar, + generated_source_jar, + native_headers_jar, + depset(source_files), + source_jars, + resources, + depset(resource_jars), + classpath_resources, + sourcepath, + injecting_rule_kind, + enable_jspecify, + enable_direct_classpath, + annotation_processor_additional_inputs, + annotation_processor_additional_outputs, + ) + + create_output_source_jar = len(source_files) > 0 or source_jars != [output_source_jar] + if not output_source_jar: + output_source_jar = _derive_output_file(ctx, output, name_suffix = "-src", extension = "jar") + if create_output_source_jar: + helper.create_single_jar( + ctx.actions, + toolchain = java_toolchain, + output = output_source_jar, + sources = depset(source_jars + ([generated_source_jar] if generated_source_jar else [])), + resources = depset(source_files), + progress_message = "Building source jar %{output}", + mnemonic = "JavaSourceJar", + ) + + if has_sources or has_resources: + direct_runtime_jars = [output] + else: + direct_runtime_jars = [] + + compilation_info = struct( + javac_options = all_javac_opts, + # needs to be flattened because the public API is a list + boot_classpath = (bootclasspath.bootclasspath if bootclasspath else java_toolchain.bootclasspath).to_list(), + # we only add compile time jars from deps, and not exports + compilation_classpath = classpaths.compilation_classpath, + runtime_classpath = depset( + order = "preorder", + direct = direct_runtime_jars, + transitive = [dep.transitive_runtime_jars for dep in runtime_deps + deps], + ), + uses_annotation_processing = uses_annotation_processing, + ) if include_compilation_info else None + + return java_info_for_compilation( + output_jar = output, + compile_jar = compile_jar, + header_compilation_jar = header_compilation_jar, + source_jar = output_source_jar, + generated_class_jar = generated_class_jar, + generated_source_jar = generated_source_jar, + plugin_info = plugin_info, + deps = deps, + runtime_deps = runtime_deps, + exports = exports, + exported_plugins = exported_plugins, + compile_jdeps = compile_deps_proto if compile_deps_proto else deps_proto, + jdeps = deps_proto if include_compilation_info else None, + native_headers_jar = native_headers_jar, + manifest_proto = manifest_proto, + native_libraries = native_libraries, + neverlink = neverlink, + add_exports = add_exports, + add_opens = add_opens, + direct_runtime_jars = direct_runtime_jars, + compilation_info = compilation_info, + ) + +def _derive_output_file(ctx, base_file, *, name_suffix = "", extension = None, extension_suffix = ""): + """Declares a new file whose name is derived from the given file + + This method allows appending a suffix to the name (before extension), changing + the extension or appending a suffix after the extension. The new file is declared + as a sibling of the given base file. At least one of the three options must be + specified. It is an error to specify both `extension` and `extension_suffix`. + + Args: + ctx: (RuleContext) the rule context. + base_file: (File) the file from which to derive the resultant file. + name_suffix: (str) Optional. The suffix to append to the name before the + extension. + extension: (str) Optional. The new extension to use (without '.'). By default, + the base_file's extension is used. + extension_suffix: (str) Optional. The suffix to append to the base_file's extension + + Returns: + (File) the derived file + """ + if not name_suffix and not extension_suffix and not extension: + fail("At least one of name_suffix, extension or extension_suffix is required") + if extension and extension_suffix: + fail("only one of extension or extension_suffix can be specified") + if extension == None: + extension = base_file.extension + new_basename = paths.replace_extension(base_file.basename, name_suffix + "." + extension + extension_suffix) + return ctx.actions.declare_file(new_basename, sibling = base_file) + +def _should_use_header_compilation(ctx, toolchain): + if not ctx.fragments.java.use_header_compilation(): + return False + if toolchain._forcibly_disable_header_compilation: + return False + if not toolchain._header_compiler: + fail( + "header compilation was requested but it is not supported by the " + + "current Java toolchain '" + str(toolchain.label) + + "'; see the java_toolchain.header_compiler attribute", + ) + if not toolchain._header_compiler_direct: + fail( + "header compilation was requested but it is not supported by the " + + "current Java toolchain '" + str(toolchain.label) + + "'; see the java_toolchain.header_compiler_direct attribute", + ) + return True + +def run_ijar( + actions, + jar, + java_toolchain, + target_label = None, + # private to @_builtins: + output = None, + injecting_rule_kind = None): + """Runs ijar on a jar, stripping it of its method bodies. + + This helps reduce rebuilding of dependent jars during any recompiles consisting only of simple + changes to method implementations. The return value is typically passed to JavaInfo.compile_jar + + Args: + actions: (actions) ctx.actions + jar: (File) The jar to run ijar on. + java_toolchain: (JavaToolchainInfo) The toolchain to used to find the ijar tool. + target_label: (Label|None) A target label to stamp the jar with. Used for `add_dep` support. + Typically, you would pass `ctx.label` to stamp the jar with the current rule's label. + output: (File) Optional. + injecting_rule_kind: (str) the rule class of the current target + Returns: + (File) The output artifact + """ + if not output: + output = actions.declare_file(paths.replace_extension(jar.basename, "-ijar.jar"), sibling = jar) + args = actions.args() + args.add(jar) + args.add(output) + if target_label != None: + args.add("--target_label", target_label) + if injecting_rule_kind != None: + args.add("--injecting_rule_kind", injecting_rule_kind) + + actions.run( + mnemonic = "JavaIjar", + inputs = [jar], + outputs = [output], + executable = java_toolchain.ijar, + arguments = [args], + progress_message = "Extracting interface for jar %{input}", + toolchain = semantics.JAVA_TOOLCHAIN_TYPE, + use_default_shell_env = True, + ) + return output + +def target_kind(target): + """Get the rule class string for a target + + Args: + target: (Target) + + Returns: + (str) The rule class string of the target + """ + return get_internal_java_common().target_kind(target) + +def collect_native_deps_dirs(libraries): + """Collect the set of root-relative paths containing native libraries + + Args: + libraries: (depset[LibraryToLink]) set of native libraries + + Returns: + ([String]) A set of root-relative paths as a list + """ + return get_internal_java_common().collect_native_deps_dirs(libraries) + +def get_runtime_classpath_for_archive(jars, excluded_jars): + """Filters a classpath to remove certain entries + + Args + jars: (depset[File]) The classpath to filter + excluded_jars: (depset[File]) The files to remove + + Returns: + (depset[File]) The filtered classpath + """ + return get_internal_java_common().get_runtime_classpath_for_archive( + jars, + excluded_jars, + ) + +def compile_header( + ctx, + output, + java_toolchain, + source_jars = [], + source_files = [], + javac_opts = [], + deps = [], + plugins = [], + strict_deps = "ERROR", + bootclasspath = None, + injecting_rule_kind = None, + enable_annotation_processing = True): + """Compiles Java header jars from the implementation of a Starlark rule. + + Args: + ctx: (RuleContext) The rule context + output: (File) The output header jar (hjar) + java_toolchain: (JavaToolchainInfo) Toolchain to be used. Mandatory. + source_jars: ([File]) A list of the jars to be compiled. + source_files: ([File]) A list of the Java source files to be compiled. + javac_opts: ([str]|depset[str]) A list of the desired javac options. Optional. + deps: ([JavaInfo]) A list of dependencies. Optional. + plugins: ([JavaPluginInfo|JavaInfo]) A list of plugins. Optional. + strict_deps: (str) A string that specifies how to handle strict deps. Possible values: + 'OFF', 'ERROR', 'WARN' and 'DEFAULT'. + bootclasspath: (BootClassPathInfo) If present, overrides the bootclasspath associated with + the provided java_toolchain. Optional. + injecting_rule_kind: (str|None) + enable_annotation_processing: (bool) + + Returns: + (JavaInfo) + """ + get_internal_java_common().check_provider_instances([java_toolchain], "java_toolchain", JavaToolchainInfo) + get_internal_java_common().check_provider_instances(plugins, "plugins", JavaPluginInfo) + + strict_deps = _validate_strict_deps(strict_deps) + + plugin_info = merge_plugin_info_without_outputs(plugins + deps) + + all_javac_opts, bootclasspath = _construct_javac_opts( + ctx, + java_toolchain, + plugin_info, + javac_opts, + bootclasspath, + add_exports = [], + ) + + enable_direct_classpath = True + if not enable_annotation_processing and plugin_info.plugins.processor_classes: + plugin_info = disable_plugin_info_annotation_processing(plugin_info) + enable_direct_classpath = False + + classpaths = _construct_classpaths(deps, strict_deps, ctx.fragments.java.reduce_java_classpath()) + + hdr_outputs = _derive_header_compilation_outputs(ctx, output) + + get_internal_java_common().create_header_compilation_action( + ctx, + java_toolchain, + hdr_outputs.compile_jar, + hdr_outputs.compile_deps_proto, + plugin_info, + depset(source_files), + source_jars, + classpaths.compilation_classpath, + classpaths.direct_jars, + bootclasspath, + classpaths.compile_time_java_deps, + all_javac_opts, + strict_deps, + ctx.label, + injecting_rule_kind, + enable_direct_classpath, + [], # additional_inputs + hdr_outputs.header_compilation_jar, + classpaths.header_compilation_direct_deps, + ) + + return java_info_for_compilation( + output_jar = hdr_outputs.compile_jar, + compile_jar = hdr_outputs.compile_jar, + header_compilation_jar = hdr_outputs.header_compilation_jar, + source_jar = None, + generated_class_jar = None, + generated_source_jar = None, + plugin_info = plugin_info, + deps = deps, + runtime_deps = [], + exports = [], + exported_plugins = [], + compile_jdeps = hdr_outputs.compile_deps_proto, + jdeps = None, + native_headers_jar = None, + manifest_proto = None, + native_libraries = [], + neverlink = True, + add_exports = [], + add_opens = [], + direct_runtime_jars = [], + compilation_info = None, + ) diff --git a/java/private/java_info.bzl b/java/private/java_info.bzl new file mode 100644 index 00000000..e873ee1b --- /dev/null +++ b/java/private/java_info.bzl @@ -0,0 +1,1037 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Definition of JavaInfo and JavaPluginInfo provider. +""" + +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") +load("//java/common:java_semantics.bzl", "semantics") +load(":native.bzl", "get_internal_java_common") + +# copybara: default visibility + +_JavaOutputInfo = provider( + doc = "The outputs of Java compilation.", + fields = { + "class_jar": "(File) A classes jar file.", + "compile_jar": "(File) An interface jar file.", + "header_compilation_jar": "(File) An header compilation jar file.", + "ijar": "Deprecated: Please use compile_jar.", + "compile_jdeps": "(File) Compile time dependencies information (deps.proto file).", + "generated_class_jar": "(File) A jar containing classes generated via annotation processing.", + "generated_source_jar": "(File) The source jar created as a result of annotation processing.", + "native_headers_jar": "(File) A jar of CC header files supporting native method implementation.", + "manifest_proto": "(File) The manifest protobuf file of the manifest generated from JavaBuilder.", + "jdeps": "(File) The jdeps protobuf file of the manifest generated from JavaBuilder.", + "source_jars": "(depset[File]) A depset of sources archive files.", + "source_jar": "Deprecated: Please use source_jars instead.", + }, +) +_ModuleFlagsInfo = provider( + doc = "Provider for the runtime classpath contributions of a Java binary.", + fields = { + "add_exports": "(depset[str]) Add-Exports configuration.", + "add_opens": "(depset[str]) Add-Opens configuration.", + }, +) +_EMPTY_MODULE_FLAGS_INFO = _ModuleFlagsInfo(add_exports = depset(), add_opens = depset()) + +def _create_module_flags_info(*, add_exports, add_opens): + if add_exports or add_opens: + return _ModuleFlagsInfo(add_exports = add_exports, add_opens = add_opens) + return _EMPTY_MODULE_FLAGS_INFO + +_JavaRuleOutputJarsInfo = provider( + doc = "Deprecated: use java_info.java_outputs. Information about outputs of a Java rule.", + fields = { + "jdeps": "Deprecated: Use java_info.java_outputs.", + "native_headers": "Deprecated: Use java_info.java_outputs[i].jdeps.", + "jars": "Deprecated: Use java_info.java_outputs[i].native_headers_jar.", + }, +) +_JavaGenJarsInfo = provider( + doc = "Deprecated: Information about jars that are a result of annotation processing for a Java rule.", + fields = { + "enabled": "Deprecated. Returns true if annotation processing was applied on this target.", + "class_jar": "Deprecated: Please use JavaInfo.java_outputs.generated_class_jar instead.", + "source_jar": "Deprecated: Please use JavaInfo.java_outputs.generated_source_jar instead.", + "transitive_class_jars": "Deprecated. A transitive set of class file jars from annotation " + + "processing of this rule and its dependencies.", + "transitive_source_jars": "Deprecated. A transitive set of source archives from annotation " + + "processing of this rule and its dependencies.", + "processor_classpath": "Deprecated: Please use JavaInfo.plugins instead.", + "processor_classnames": "Deprecated: Please use JavaInfo.plugins instead.", + }, +) + +JavaCompilationInfo = provider( + doc = "Compilation information in Java rules, for perusal of aspects and tools.", + fields = { + "boot_classpath": "Boot classpath for this Java target.", + "javac_options": """Depset of options to the java compiler. To get the + exact list of options passed to javac in the correct order, use the + tokenize_javacopts utility in rules_java""", + "compilation_classpath": "Compilation classpath for this Java target.", + "runtime_classpath": "Run-time classpath for this Java target.", + }, +) + +def merge( + providers, + # private to @_builtins: + merge_java_outputs = True, + merge_source_jars = True): + """Merges the given providers into a single JavaInfo. + + Args: + providers: ([JavaInfo]) The list of providers to merge. + merge_java_outputs: (bool) + merge_source_jars: (bool) + + Returns: + (JavaInfo) The merged JavaInfo + """ + _validate_provider_list(providers, "providers", JavaInfo) + + plugin_info = merge_plugin_info_without_outputs(providers) + + source_jars = [] # [File] + transitive_source_jars = [] # [depset[File]] + java_outputs = [] # [_JavaOutputInfo] + runtime_output_jars = [] # [File] + transitive_runtime_jars = [] # [depset[File]] + transitive_compile_time_jars = [] # [depset[File]] + compile_jars = [] # [depset[File]] + header_compilation_direct_deps = [] # [depset[File]] + full_compile_jars = [] # [depset[File]] + _transitive_full_compile_time_jars = [] # [depset[File]] + _compile_time_java_dependencies = [] # [depset[File]] + add_exports = [] # [depset[str]] + add_opens = [] # [depset[str]] + _neverlink = False + _constraints = [] # [str] + for p in providers: + if merge_source_jars: + source_jars.extend(p.source_jars) + transitive_source_jars.append(p.transitive_source_jars) + if merge_java_outputs: + java_outputs.extend(p.java_outputs) + runtime_output_jars.extend(p.runtime_output_jars) + transitive_runtime_jars.append(p.transitive_runtime_jars) + transitive_compile_time_jars.append(p.transitive_compile_time_jars) + compile_jars.append(p.compile_jars) + header_compilation_direct_deps.append(p.header_compilation_direct_deps) + full_compile_jars.append(p.full_compile_jars) + _transitive_full_compile_time_jars.append(p._transitive_full_compile_time_jars) + _compile_time_java_dependencies.append(p._compile_time_java_dependencies) + add_exports.append(p.module_flags_info.add_exports) + add_opens.append(p.module_flags_info.add_opens) + _neverlink = _neverlink or p._neverlink + _constraints.extend(p._constraints) + + transitive_runtime_jars = depset(order = "preorder", transitive = transitive_runtime_jars) + transitive_compile_time_jars = depset(order = "preorder", transitive = transitive_compile_time_jars) + + # java_outputs is a list so we uniquify to avoid https://github.com/bazelbuild/bazel/issues/17170 + java_outputs = depset(java_outputs).to_list() + result = { + "transitive_runtime_jars": transitive_runtime_jars, + "transitive_compile_time_jars": transitive_compile_time_jars, + "compile_jars": depset(order = "preorder", transitive = compile_jars), + "header_compilation_direct_deps": depset(order = "preorder", transitive = header_compilation_direct_deps), + "full_compile_jars": depset(order = "preorder", transitive = full_compile_jars), + "_transitive_full_compile_time_jars": depset(order = "preorder", transitive = _transitive_full_compile_time_jars), + "_compile_time_java_dependencies": depset(order = "preorder", transitive = _compile_time_java_dependencies), + # runtime_output_jars is a list so we uniquify to avoid https://github.com/bazelbuild/bazel/issues/17170 + "runtime_output_jars": depset(runtime_output_jars).to_list(), + # source_jars is a list so we uniquify to avoid https://github.com/bazelbuild/bazel/issues/17170 + "source_jars": depset(source_jars).to_list(), + "transitive_source_jars": depset(transitive = transitive_source_jars), + "java_outputs": java_outputs, + "outputs": _JavaRuleOutputJarsInfo(jars = java_outputs, jdeps = None, native_headers = None), + "module_flags_info": _create_module_flags_info( + add_exports = depset(transitive = add_exports), + add_opens = depset(transitive = add_opens), + ), + "plugins": plugin_info.plugins, + "api_generating_plugins": plugin_info.api_generating_plugins, + "_neverlink": bool(_neverlink), + "_constraints": depset(_constraints).to_list(), + "annotation_processing": None, + "compilation_info": None, + } + + if get_internal_java_common().google_legacy_api_enabled(): + cc_info = semantics.minimize_cc_info(semantics.merge_cc_infos(cc_infos = [p.cc_link_params_info for p in providers])) + result.update( + cc_link_params_info = cc_info, + transitive_native_libraries = + cc_info._legacy_transitive_native_libraries if hasattr(cc_info, "_legacy_transitive_native_libraries") else cc_info.transitive_native_libraries(), + ) + else: + result.update( + transitive_native_libraries = depset( + order = "topological", + transitive = [p.transitive_native_libraries for p in providers], + ), + ) + return _new_javainfo(**result) + +def to_java_binary_info(java_info, compilation_info): + """Get a copy of the given JavaInfo with minimal info returned by a java_binary + + Args: + java_info: (JavaInfo) A JavaInfo provider instance + compilation_info: (JavaCompilationInfo) + Returns: + (JavaInfo) A JavaInfo instance representing a java_binary target + """ + result = { + "transitive_runtime_jars": depset(), + "transitive_compile_time_jars": depset(), + "compile_jars": depset(), + "header_compilation_direct_deps": depset(), + "full_compile_jars": depset(), + "_transitive_full_compile_time_jars": depset(), + "_compile_time_java_dependencies": depset(), + "runtime_output_jars": [], + "plugins": _EMPTY_PLUGIN_DATA, + "api_generating_plugins": _EMPTY_PLUGIN_DATA, + "module_flags_info": _EMPTY_MODULE_FLAGS_INFO, + "_neverlink": False, + "_constraints": [], + "annotation_processing": java_info.annotation_processing, + "transitive_native_libraries": java_info.transitive_native_libraries, + "source_jars": java_info.source_jars, + "transitive_source_jars": java_info.transitive_source_jars, + } + if hasattr(java_info, "cc_link_params_info"): + result.update(cc_link_params_info = java_info.cc_link_params_info) + + result["compilation_info"] = compilation_info + + java_outputs = [ + _JavaOutputInfo( + compile_jar = None, + header_compilation_jar = None, + ijar = None, # deprecated + compile_jdeps = None, + class_jar = output.class_jar, + generated_class_jar = output.generated_class_jar, + generated_source_jar = output.generated_source_jar, + native_headers_jar = output.native_headers_jar, + manifest_proto = output.manifest_proto, + jdeps = output.jdeps, + source_jars = output.source_jars, + source_jar = output.source_jar, # deprecated + ) + for output in java_info.java_outputs + ] + all_jdeps = [output.jdeps for output in java_info.java_outputs if output.jdeps] + all_native_headers = [output.native_headers_jar for output in java_info.java_outputs if output.native_headers_jar] + result.update( + java_outputs = java_outputs, + outputs = _JavaRuleOutputJarsInfo( + jars = java_outputs, + jdeps = all_jdeps[0] if len(all_jdeps) == 1 else None, + native_headers = all_native_headers[0] if len(all_native_headers) == 1 else None, + ), + ) + + # so that translation into native JavaInfo does not add JavaCompilationArgsProvider + result.update(_is_binary = True) + return _new_javainfo(**result) + +def to_implicit_exportable(java_info, neverlink = False): + result = { + "transitive_runtime_jars": depset() if neverlink else java_info.transitive_runtime_jars, + "transitive_compile_time_jars": java_info.transitive_compile_time_jars, + "compile_jars": java_info.compile_jars, + "header_compilation_direct_deps": java_info.header_compilation_direct_deps, + "full_compile_jars": java_info.full_compile_jars, + "_transitive_full_compile_time_jars": java_info._transitive_full_compile_time_jars, + "_compile_time_java_dependencies": java_info._compile_time_java_dependencies, + "_neverlink": neverlink, + # unset defaults + "source_jars": [], + "outputs": _JavaRuleOutputJarsInfo(jars = [], jdeps = None, native_headers = None), + "annotation_processing": None, + "runtime_output_jars": [], + "transitive_source_jars": depset(), + "transitive_native_libraries": depset(), + "cc_link_params_info": CcInfo(), + "module_flags_info": _EMPTY_MODULE_FLAGS_INFO, + "plugins": _EMPTY_PLUGIN_DATA, + "api_generating_plugins": _EMPTY_PLUGIN_DATA, + "java_outputs": [], + "compilation_info": None, + "_constraints": [], + "_is_binary": getattr(java_info, "_is_binary", False), + } + return _new_javainfo(**result) + +def _to_mutable_dict(java_info): + return { + key: getattr(java_info, key) + for key in dir(java_info) + if key not in ["to_json", "to_proto"] + } + +def add_constraints(java_info, constraints = []): + """Returns a copy of the given JavaInfo with the given constraints added. + + Args: + java_info: (JavaInfo) The JavaInfo to enhance + constraints: ([str]) Constraints to add + + Returns: + (JavaInfo) + """ + result = _to_mutable_dict(java_info) + old_constraints = java_info._constraints if java_info._constraints else [] + result.update( + _constraints = depset(constraints + old_constraints).to_list(), + ) + return _new_javainfo(**result) + +def make_non_strict(java_info): + """Returns a new JavaInfo instance whose direct-jars part is the union of both the direct and indirect jars of the given Java provider. + + Args: + java_info: (JavaInfo) The java info to make non-strict. + + Returns: + (JavaInfo) + """ + result = _to_mutable_dict(java_info) + result.update( + compile_jars = java_info.transitive_compile_time_jars, + full_compile_jars = java_info._transitive_full_compile_time_jars, + header_compilation_direct_deps = java_info.transitive_compile_time_jars, + ) + + # Omit jdeps, which aren't available transitively and aren't useful for reduced classpath + # pruning for non-strict targets: the direct classpath and transitive classpath are the same, + # so there's nothing to prune, and reading jdeps at compile-time isn't free. + result.update( + _compile_time_java_dependencies = depset(), + ) + return _new_javainfo(**result) + +def add_module_flags(java_info, add_exports = [], add_opens = []): + """Returns a new JavaInfo instance with the additional add_exports/add_opens + + Args: + java_info: (JavaInfo) The java info to enhance. + add_exports: ([str]) The /s given access to. + add_opens: ([str]) The /s given reflective access to. + Returns: + (JavaInfo) + """ + if not add_exports and not add_opens: + return java_info + + result = _to_mutable_dict(java_info) + result.update( + module_flags_info = _create_module_flags_info( + add_exports = depset(add_exports, transitive = [java_info.module_flags_info.add_exports]), + add_opens = depset(add_opens, transitive = [java_info.module_flags_info.add_opens]), + ), + ) + return _new_javainfo(**result) + +def set_annotation_processing( + java_info, + enabled = False, + processor_classnames = [], + processor_classpath = None, + class_jar = None, + source_jar = None): + """Returns a copy of the given JavaInfo with the given annotation_processing info. + + Args: + java_info: (JavaInfo) The JavaInfo to enhance. + enabled: (bool) Whether the rule uses annotation processing. + processor_classnames: ([str]) Class names of annotation processors applied. + processor_classpath: (depset[File]) Class names of annotation processors applied. + class_jar: (File) Optional. Jar that is the result of annotation processing. + source_jar: (File) Optional. Source archive resulting from annotation processing. + + Returns: + (JavaInfo) + """ + gen_jars_info = java_info.annotation_processing + if gen_jars_info: + # Existing Jars would be a problem b/c we can't remove them from transitiveXxx sets + if gen_jars_info.class_jar and gen_jars_info.class_jar != class_jar: + fail("Existing gen_class_jar:", gen_jars_info.class_jar) + if gen_jars_info.source_jar and gen_jars_info.source_jar != source_jar: + fail("Existing gen_source_jar:", gen_jars_info.class_jar) + transitive_class_jars = depset([class_jar] if class_jar else [], transitive = [gen_jars_info.transitive_class_jars]) + transitive_source_jars = depset([source_jar] if source_jar else [], transitive = [gen_jars_info.transitive_source_jars]) + else: + transitive_class_jars = depset([class_jar] if class_jar else []) + transitive_source_jars = depset([source_jar] if source_jar else []) + + result = _to_mutable_dict(java_info) + result.update( + annotation_processing = _JavaGenJarsInfo( + enabled = enabled, + class_jar = class_jar, + source_jar = source_jar, + processor_classnames = processor_classnames, + processor_classpath = processor_classpath if processor_classpath else depset(), + transitive_class_jars = transitive_class_jars, + transitive_source_jars = transitive_source_jars, + ), + ) + return _new_javainfo(**result) + +def java_info_for_compilation( + output_jar, + compile_jar, + source_jar, + generated_class_jar, + generated_source_jar, + plugin_info, + deps, + runtime_deps, + exports, + exported_plugins, + compile_jdeps, + jdeps, + native_headers_jar, + manifest_proto, + native_libraries, + neverlink, + add_exports, + add_opens, + direct_runtime_jars, + compilation_info, + header_compilation_jar): + """Creates a JavaInfo instance represiting the result of java compilation. + + Args: + output_jar: (File) The jar that was created as a result of a compilation. + compile_jar: (File) A jar that is the compile-time dependency in lieu of `output_jar`. + header_compilation_jar: (File) A jar that is used for header compilation in lieu of + compile_jar or output_jar. + source_jar: (File) The source jar that was used to create the output jar. + generated_class_jar: (File) A jar file containing class files compiled from sources + generated during annotation processing. + generated_source_jar: (File) The source jar that was created as a result of annotation + processing. + plugin_info: (JavaPluginInfo) Information about annotation processing. + deps: ([JavaInfo]) Compile time dependencies that were used to create the output jar. + runtime_deps: ([JavaInfo]) Runtime dependencies that are needed for this library. + exports: ([JavaInfo]) Libraries to make available for users of this library. + exported_plugins: ([JavaPluginInfo]) A list of exported plugins. + compile_jdeps: (File) jdeps information about compile time dependencies to be consumed by + JavaCompileAction. This should be a binary proto encoded using the deps.proto protobuf + included with Bazel. If available this file is typically produced by a header compiler. + jdeps: (File) jdeps information for the rule output (if available). This should be a binary + proto encoded using the deps.proto protobuf included with Bazel. If available this file + is typically produced by a compiler. IDEs and other tools can use this information for + more efficient processing. + native_headers_jar: (File) A jar containing CC header files supporting native method + implementation (typically output of javac -h). + manifest_proto: (File) Manifest information for the rule output (if available). This should + be a binary proto encoded using the manifest.proto protobuf included with Bazel. IDEs + and other tools can use this information for more efficient processing. + native_libraries: ([CcInfo]) Native library dependencies that are needed for this library. + neverlink: (bool) If true, only use this library for compilation and not at runtime. + add_exports: ([str]) The /s this library was given access to. + add_opens: ([str]) The /s this library was given reflective access to. + direct_runtime_jars: ([File]) The class jars needed directly by this library at runtime. + This is usually just the output_jar or empty if there were no sources/resources. + compilation_info: (struct) Information for IDE/tools + + Returns: + (JavaInfo) the JavaInfo instance + """ + result, concatenated_deps = _javainfo_init_base( + output_jar, + compile_jar, + source_jar, + deps, + runtime_deps, + exports, + exported_plugins, + jdeps, + compile_jdeps, + native_headers_jar, + manifest_proto, + generated_class_jar, + generated_source_jar, + native_libraries, + neverlink, + header_compilation_jar, + ) + + # this differs ever so slightly from the usual JavaInfo in that direct_runtime_jars + # does not contain the output_jar is there were no sources/resources + transitive_runtime_jars = depset() if neverlink else depset( + order = "preorder", + direct = direct_runtime_jars, + transitive = [dep.transitive_runtime_jars for dep in concatenated_deps.exports_deps + runtime_deps], + ) + result.update( + runtime_output_jars = direct_runtime_jars, + transitive_runtime_jars = transitive_runtime_jars, + transitive_source_jars = depset( + direct = [source_jar] if source_jar else [], + # only differs from the usual java_info.transitive_source_jars in the order of deps + transitive = [dep.transitive_source_jars for dep in concatenated_deps.runtimedeps_exports_deps], + ), + # the JavaInfo constructor does not add flags from runtime_deps + module_flags_info = _create_module_flags_info( + add_exports = depset(add_exports, transitive = [ + dep.module_flags_info.add_exports + for dep in concatenated_deps.runtimedeps_exports_deps + ]), + add_opens = depset(add_opens, transitive = [ + dep.module_flags_info.add_opens + for dep in concatenated_deps.runtimedeps_exports_deps + ]), + ), + ) + if compilation_info: + result.update( + compilation_info = JavaCompilationInfo( + javac_options = compilation_info.javac_options, + boot_classpath = compilation_info.boot_classpath, + compilation_classpath = compilation_info.compilation_classpath, + runtime_classpath = compilation_info.runtime_classpath, + ), + annotation_processing = _JavaGenJarsInfo( + enabled = compilation_info.uses_annotation_processing, + class_jar = result["annotation_processing"].class_jar, + source_jar = result["annotation_processing"].source_jar, + processor_classnames = plugin_info.plugins.processor_classes.to_list(), + processor_classpath = plugin_info.plugins.processor_jars, + transitive_class_jars = result["annotation_processing"].transitive_class_jars, + transitive_source_jars = result["annotation_processing"].transitive_source_jars, + ), + ) + else: + result.update( + compilation_info = None, + annotation_processing = None, + ) + return _new_javainfo(**result) + +def _validate_provider_list(provider_list, what, expected_provider_type): + get_internal_java_common().check_provider_instances(provider_list, what, expected_provider_type) + +def _compute_concatenated_deps(deps, runtime_deps, exports): + deps_exports = [] + deps_exports.extend(deps) + deps_exports.extend(exports) + + exports_deps = [] + exports_deps.extend(exports) + exports_deps.extend(deps) + + runtimedeps_exports_deps = [] + runtimedeps_exports_deps.extend(runtime_deps) + runtimedeps_exports_deps.extend(exports_deps) + + return struct( + deps_exports = deps_exports, + exports_deps = exports_deps, + runtimedeps_exports_deps = runtimedeps_exports_deps, + ) + +def _javainfo_init_base( + output_jar, + compile_jar, + source_jar, + deps, + runtime_deps, + exports, + exported_plugins, + jdeps, + compile_jdeps, + native_headers_jar, + manifest_proto, + generated_class_jar, + generated_source_jar, + native_libraries, + neverlink, + header_compilation_jar = None): + _validate_provider_list(deps, "deps", JavaInfo) + _validate_provider_list(runtime_deps, "runtime_deps", JavaInfo) + _validate_provider_list(exports, "exports", JavaInfo) + _validate_provider_list(native_libraries, "native_libraries", CcInfo) + + # For clients that don't create a header_compilation_jar (e.g. java_import), fall back to using + # the compile jar. + if compile_jar and not header_compilation_jar: + header_compilation_jar = compile_jar + + concatenated_deps = _compute_concatenated_deps(deps, runtime_deps, exports) + + source_jars = [source_jar] if source_jar else [] + plugin_info = merge_plugin_info_without_outputs(exported_plugins + exports) + transitive_compile_time_jars = depset( + order = "preorder", + direct = [compile_jar] if compile_jar else [], + transitive = [dep.transitive_compile_time_jars for dep in concatenated_deps.exports_deps], + ) + java_outputs = [_JavaOutputInfo( + class_jar = output_jar, + compile_jar = compile_jar, + header_compilation_jar = header_compilation_jar, + ijar = compile_jar, # deprecated + compile_jdeps = compile_jdeps, + generated_class_jar = generated_class_jar, + generated_source_jar = generated_source_jar, + native_headers_jar = native_headers_jar, + manifest_proto = manifest_proto, + jdeps = jdeps, + source_jars = depset(source_jars), + source_jar = source_jar, # deprecated + )] + result = { + "transitive_compile_time_jars": transitive_compile_time_jars, + "compile_jars": depset( + order = "preorder", + direct = [compile_jar] if compile_jar else [], + transitive = [dep.compile_jars for dep in exports], + ), + "header_compilation_direct_deps": depset( + order = "preorder", + direct = [header_compilation_jar] if header_compilation_jar else [], + transitive = [dep.header_compilation_direct_deps for dep in exports], + ), + "full_compile_jars": depset( + order = "preorder", + direct = [output_jar], + transitive = [ + dep.full_compile_jars + for dep in exports + ], + ), + "source_jars": source_jars, + "runtime_output_jars": [output_jar], + "plugins": plugin_info.plugins, + "api_generating_plugins": plugin_info.api_generating_plugins, + "java_outputs": java_outputs, + # deprecated + "outputs": _JavaRuleOutputJarsInfo( + jars = java_outputs, + jdeps = jdeps, + native_headers = native_headers_jar, + ), + "annotation_processing": _JavaGenJarsInfo( + enabled = False, + class_jar = generated_class_jar, + source_jar = generated_source_jar, + transitive_class_jars = depset( + direct = [generated_class_jar] if generated_class_jar else [], + transitive = [ + dep.annotation_processing.transitive_class_jars + for dep in concatenated_deps.deps_exports + if dep.annotation_processing + ], + ), + transitive_source_jars = depset( + direct = [generated_source_jar] if generated_source_jar else [], + transitive = [ + dep.annotation_processing.transitive_source_jars + for dep in concatenated_deps.deps_exports + if dep.annotation_processing + ], + ), + processor_classnames = [], + processor_classpath = depset(), + ), + "_transitive_full_compile_time_jars": depset( + order = "preorder", + direct = [output_jar], + transitive = [dep._transitive_full_compile_time_jars for dep in concatenated_deps.exports_deps], + ), + "_compile_time_java_dependencies": depset( + order = "preorder", + transitive = [dep._compile_time_java_dependencies for dep in exports] + + ([depset([compile_jdeps])] if compile_jdeps else []), + ), + "_neverlink": bool(neverlink), + "compilation_info": None, + "_constraints": [], + } + + if get_internal_java_common().google_legacy_api_enabled(): + transitive_cc_infos = [dep.cc_link_params_info for dep in concatenated_deps.runtimedeps_exports_deps] + transitive_cc_infos.extend(native_libraries) + cc_info = semantics.minimize_cc_info(semantics.merge_cc_infos(cc_infos = transitive_cc_infos)) + result.update( + cc_link_params_info = cc_info, + transitive_native_libraries = + cc_info._legacy_transitive_native_libraries if hasattr(cc_info, "_legacy_transitive_native_libraries") else cc_info.transitive_native_libraries(), + ) + else: + transitive_native_libraries = [] + if native_libraries: + merged_cc_info = semantics.merge_cc_infos(cc_infos = native_libraries) + if hasattr(merged_cc_info, "_legacy_transitive_native_libraries"): + transitive_native_libraries = [merged_cc_info._legacy_transitive_native_libraries] + else: + transitive_native_libraries = [merged_cc_info.transitive_native_libraries()] + result.update( + transitive_native_libraries = depset( + order = "topological", + transitive = [dep.transitive_native_libraries for dep in concatenated_deps.runtimedeps_exports_deps] + + transitive_native_libraries, + ), + ) + return result, concatenated_deps + +def _javainfo_init( + output_jar, + compile_jar, + header_compilation_jar = None, + source_jar = None, + compile_jdeps = None, + generated_class_jar = None, + generated_source_jar = None, + native_headers_jar = None, + manifest_proto = None, + neverlink = False, + deps = [], + runtime_deps = [], + exports = [], + exported_plugins = [], + jdeps = None, + native_libraries = [], + add_exports = [], + add_opens = []): + """The JavaInfo constructor + + Args: + output_jar: (File) The jar that was created as a result of a compilation. + compile_jar: (File) A jar that is the compile-time dependency in lieu of `output_jar`. + header_compilation_jar: (File) A jar that is used for header compilation in lieu of + compile_jar or output_jar. + source_jar: (File) The source jar that was used to create the output jar. Optional. + compile_jdeps: (File) jdeps information about compile time dependencies to be consumed by + JavaCompileAction. This should be a binary proto encoded using the deps.proto protobuf + included with Bazel. If available this file is typically produced by a header compiler. + Optional. + generated_class_jar: (File) A jar file containing class files compiled from sources + generated during annotation processing. Optional. + generated_source_jar: (File) The source jar that was created as a result of annotation + processing. Optional. + native_headers_jar: (File) A jar containing CC header files supporting native method + implementation (typically output of javac -h). Optional. + manifest_proto: (File) Manifest information for the rule output (if available). This should + be a binary proto encoded using the manifest.proto protobuf included with Bazel. IDEs + and other tools can use this information for more efficient processing. Optional. + neverlink: (bool) If true, only use this library for compilation and not at runtime. + deps: ([JavaInfo]) Compile time dependencies that were used to create the output jar. + runtime_deps: ([JavaInfo]) Runtime dependencies that are needed for this library. + exports: ([JavaInfo]) Libraries to make available for users of this library. + exported_plugins: ([JavaPluginInfo]) Optional. A list of exported plugins. + jdeps: (File) jdeps information for the rule output (if available). This should be a binary + proto encoded using the deps.proto protobuf included with Bazel. If available this file + is typically produced by a compiler. IDEs and other tools can use this information for + more efficient processing. Optional. + native_libraries: ([CcInfo]) Native library dependencies that are needed for this library. + add_exports: ([str]) The /s this library was given access to. + add_opens: ([str]) The /s this library was given reflective access to. + + Returns: + (dict) arguments to the JavaInfo provider constructor + """ + if add_exports or add_opens: + semantics.check_java_info_opens_exports() + + result, concatenated_deps = _javainfo_init_base( + output_jar, + compile_jar, + source_jar, + deps, + runtime_deps, + exports, + exported_plugins, + jdeps, + compile_jdeps, + native_headers_jar, + manifest_proto, + generated_class_jar, + generated_source_jar, + native_libraries, + neverlink, + header_compilation_jar = header_compilation_jar, + ) + + if neverlink: + transitive_runtime_jars = depset() + else: + transitive_runtime_jars = depset( + order = "preorder", + direct = [output_jar], + transitive = [dep.transitive_runtime_jars for dep in concatenated_deps.exports_deps + runtime_deps], + ) + + # For backward compatibility, we use deps_exports for add_exports/add_opens + # for the JavaInfo constructor rather than runtimedeps_exports_deps (used + # by java_info_for_compilation). However, runtimedeps_exports_deps makes + # more sense, since add_exports/add_opens from runtime_deps are needed at + # runtime anyway. + # + # TODO: When this flag is removed, move this logic into _javainfo_init_base + # and remove the special case from java_info_for_compilation. + module_flags_deps = concatenated_deps.deps_exports + if get_internal_java_common().incompatible_java_info_merge_runtime_module_flags(): + module_flags_deps = concatenated_deps.runtimedeps_exports_deps + + result.update( + transitive_runtime_jars = transitive_runtime_jars, + transitive_source_jars = depset( + direct = [source_jar] if source_jar else [], + # TODO(hvd): native also adds source jars from deps, but this should be unnecessary + transitive = [ + dep.transitive_source_jars + for dep in deps + runtime_deps + exports + ], + ), + module_flags_info = _create_module_flags_info( + add_exports = depset(add_exports, transitive = [ + dep.module_flags_info.add_exports + for dep in module_flags_deps + ]), + add_opens = depset(add_opens, transitive = [ + dep.module_flags_info.add_opens + for dep in module_flags_deps + ]), + ), + ) + return result + +JavaInfo, _new_javainfo = provider( + doc = "Info object encapsulating all information by java rules.", + fields = { + "transitive_runtime_jars": """(depset[File]) A transitive set of jars required on the + runtime classpath. +

Note: for binary targets (such as java_binary and java_test), this is empty, since such + targets are not intended to be dependencies of other Java targets. + """, + "transitive_compile_time_jars": """(depset[File]) The transitive set of jars required to + build the target. +

Note: for binary targets (such as java_binary and java_test), this is empty, since such + targets are not intended to be dependencies of other Java targets. + """, + "compile_jars": """(depset[File]) The jars required directly at compile time. They can be interface jars + (ijar or hjar), regular jars or both, depending on whether rule + implementations chose to create interface jars or not.""", + "header_compilation_direct_deps": """(depset[File]) The header compilation jars required + directly at compile time.""", + "full_compile_jars": """(depset[File]) The regular, full compile time Jars required by this target directly. + They can be: + - the corresponding regular Jars of the interface Jars returned by JavaInfo.compile_jars + - the regular (full) Jars returned by JavaInfo.compile_jars + + Note: JavaInfo.compile_jars can return a mix of interface Jars and + regular Jars.

Only use this method if interface Jars don't work with + your rule set(s) (e.g. some Scala targets) If you're working with + Java-only targets it's preferable to use interface Jars via + JavaInfo.compile_jars""", + "source_jars": """([File]) A list of Jars with all the source files (including those generated by + annotations) of the target itself, i.e. NOT including the sources of the + transitive dependencies.""", + "outputs": "Deprecated: use java_outputs.", + "annotation_processing": "Deprecated: Please use plugins instead.", + "runtime_output_jars": "([File]) A list of runtime Jars created by this Java/Java-like target.", + "transitive_source_jars": "(depset[File]) The Jars of all source files in the transitive closure.", + "transitive_native_libraries": """(depset[LibraryToLink]) The transitive set of CC native + libraries required by the target.""", + "cc_link_params_info": "Deprecated. Do not use. C++ libraries to be linked into Java targets.", + "module_flags_info": "(_ModuleFlagsInfo) The Java module flag configuration.", + "plugins": """(_JavaPluginDataInfo) Data about all plugins that a consuming target should + apply. + This is typically either a `java_plugin` itself or a `java_library` exporting + one or more plugins. + A `java_library` runs annotation processing with all plugins from this field + appearing in deps and `plugins` attributes.""", + "api_generating_plugins": """"(_JavaPluginDataInfo) Data about API generating plugins + defined or exported by this target. + Those annotation processors are applied to a Java target before + producing its header jars (which contain method signatures). When + no API plugins are present, header jars are generated from the + sources, reducing critical path. + The `api_generating_plugins` is a subset of `plugins`.""", + "java_outputs": "(_JavaOutputInfo) Information about outputs of this Java/Java-like target.", + "compilation_info": """(java_compilation_info) Compilation information for this + Java/Java-like target.""", + "_transitive_full_compile_time_jars": "internal API, do not use", + "_compile_time_java_dependencies": "internal API, do not use", + "_neverlink": "internal API, do not use", + "_constraints": "internal API, do not use", + "_is_binary": "internal API, do not use", + }, + init = _javainfo_init, +) + +JavaPluginDataInfo = provider( + doc = "Provider encapsulating information about a Java compatible plugin.", + fields = { + "processor_classes": "depset(str) The fully qualified classnames of entry points for the compiler", + "processor_jars": "depset(file) Deps containing an annotation processor", + "processor_data": "depset(file) Files needed during execution", + }, +) + +_EMPTY_PLUGIN_DATA = JavaPluginDataInfo( + processor_classes = depset(), + processor_jars = depset(), + processor_data = depset(), +) + +def _create_plugin_data_info(*, processor_classes, processor_jars, processor_data): + if processor_classes or processor_jars or processor_data: + return JavaPluginDataInfo( + processor_classes = processor_classes, + processor_jars = processor_jars, + processor_data = processor_data, + ) + else: + return _EMPTY_PLUGIN_DATA + +def disable_plugin_info_annotation_processing(plugin_info): + """Returns a copy of the provided JavaPluginInfo without annotation processing info + + Args: + plugin_info: (JavaPluginInfo) the instance to transform + + Returns: + (JavaPluginInfo) a new, transformed instance. + """ + return _new_javaplugininfo( + plugins = _create_plugin_data_info( + processor_classes = depset(order = "preorder"), + # Preserve the processor path, since it may contain Error Prone plugins + # which will be service-loaded by JavaBuilder. + processor_jars = plugin_info.plugins.processor_jars, + # Preserve data, which may be used by Error Prone plugins. + processor_data = plugin_info.plugins.processor_data, + ), + api_generating_plugins = _EMPTY_PLUGIN_DATA, + java_outputs = plugin_info.java_outputs, + ) + +def merge_plugin_info_without_outputs(infos): + """ Merge plugin information from a list of JavaPluginInfo or JavaInfo + + Args: + infos: ([JavaPluginInfo|JavaInfo]) list of providers to merge + + Returns: + (JavaPluginInfo) + """ + plugins = [] + api_generating_plugins = [] + for info in infos: + if _has_plugin_data(info.plugins): + plugins.append(info.plugins) + if _has_plugin_data(info.api_generating_plugins): + api_generating_plugins.append(info.api_generating_plugins) + return _new_javaplugininfo( + plugins = _merge_plugin_data(plugins), + api_generating_plugins = _merge_plugin_data(api_generating_plugins), + java_outputs = [], + ) + +def _has_plugin_data(plugin_data): + return plugin_data and ( + plugin_data.processor_classes or + plugin_data.processor_jars or + plugin_data.processor_data + ) + +def _merge_plugin_data(datas): + return _create_plugin_data_info( + processor_classes = depset(transitive = [p.processor_classes for p in datas]), + processor_jars = depset(transitive = [p.processor_jars for p in datas]), + processor_data = depset(transitive = [p.processor_data for p in datas]), + ) + +def _javaplugininfo_init( + runtime_deps, + processor_class = None, + processor_classes = [], + data = [], + generates_api = False): + """ Constructs JavaPluginInfo + + Args: + runtime_deps: ([JavaInfo]) list of deps containing an annotation + processor. + processor_class: (String) The fully qualified class name that the Java + compiler uses as an entry point to the annotation processor. + processor_classes: ([String]) Fully qualified class names that the + Java compiler uses as an entry point to the annotation processor. + data: (depset[File]) The files needed by this annotation + processor during execution. + generates_api: (boolean) Set to true when this annotation processor + generates API code. Such an annotation processor is applied to a + Java target before producing its header jars (which contains method + signatures). When no API plugins are present, header jars are + generated from the sources, reducing the critical path. + WARNING: This parameter affects build performance, use it only if + necessary. + + Returns: + (JavaPluginInfo) + """ + + java_infos = merge(runtime_deps) + processor_data = data if type(data) == "depset" else depset(data) + plugins = _create_plugin_data_info( + processor_classes = depset([processor_class] + processor_classes) if processor_class else depset(processor_classes), + processor_jars = java_infos.transitive_runtime_jars, + processor_data = processor_data, + ) + return { + "plugins": plugins, + "api_generating_plugins": plugins if generates_api else _EMPTY_PLUGIN_DATA, + "java_outputs": java_infos.java_outputs, + } + +JavaPluginInfo, _new_javaplugininfo = provider( + doc = "Provider encapsulating information about Java plugins.", + fields = { + "plugins": """ + Returns data about all plugins that a consuming target should apply. + This is typically either a java_plugin itself or a + java_library exporting one or more plugins. + A java_library runs annotation processing with all + plugins from this field appearing in deps and + plugins attributes.""", + "api_generating_plugins": """ + Returns data about API generating plugins defined or exported by + this target. + Those annotation processors are applied to a Java target before + producing its header jars (which contain method signatures). When + no API plugins are present, header jars are generated from the + sources, reducing critical path. + The api_generating_plugins is a subset of + plugins.""", + "java_outputs": """ + Returns information about outputs of this Java/Java-like target. + """, + }, + init = _javaplugininfo_init, +) diff --git a/java/private/legacy_native.bzl b/java/private/legacy_native.bzl new file mode 100644 index 00000000..770434bc --- /dev/null +++ b/java/private/legacy_native.bzl @@ -0,0 +1,31 @@ +# Copyright 2022 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Redefine native symbols with a new name as a workaround for +# exporting them in @compatibility_proxy//:proxy.bzl with their original name. + +"""Lovely workaround to be able to expose native constants pretending to be Starlark.""" + +# copybara: default visibility + +# Unused with Bazel@HEAD, only used by the compatibility layer for older Bazel versions + +# buildifier: disable=native-java-common +native_java_common = java_common + +# buildifier: disable=native-java-info +NativeJavaInfo = JavaInfo + +# buildifier: disable=native-java-plugin-info +NativeJavaPluginInfo = JavaPluginInfo diff --git a/java/private/message_bundle_info.bzl b/java/private/message_bundle_info.bzl new file mode 100644 index 00000000..df77fce7 --- /dev/null +++ b/java/private/message_bundle_info.bzl @@ -0,0 +1,26 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Definition of MessageBundleInfo provider. +""" + +visibility("private") + +MessageBundleInfo = provider( + doc = "Marks configured targets that are able to supply message bundles to their dependents.", + fields = { + "messages": "Sequence of message bundles", + }, +) diff --git a/java/private/native.bzl b/java/private/native.bzl index 64a4d5ac..cd45aa7a 100644 --- a/java/private/native.bzl +++ b/java/private/native.bzl @@ -12,20 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Redefine native symbols with a new name as a workaround for -# exporting them in `//third_party/bazel_rules/rules_proto/proto:defs.bzl` with their original name. -# -# While we cannot force users to load these symbol due to the lack of a -# allowlisting mechanism, we can still export them and tell users to -# load it to make a future migration to pure Starlark easier. - -"""Lovely workaround to be able to expose native constants pretending to be Starlark.""" - -# buildifier: disable=native-java -native_java_common = java_common +"""Redirects for private native APIs""" -# buildifier: disable=native-java -NativeJavaInfo = JavaInfo +# copybara: default visibility -# buildifier: disable=native-java -NativeJavaPluginInfo = JavaPluginInfo +# Used for some private native APIs that we can't replicate just yet in Starlark +def get_internal_java_common(): + return java_common.internal_DO_NOT_USE() # buildifier: disable=native-java-common diff --git a/java/private/proto_support.bzl b/java/private/proto_support.bzl new file mode 100644 index 00000000..957dc31f --- /dev/null +++ b/java/private/proto_support.bzl @@ -0,0 +1,37 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Support for Java compilation of protocol buffer generated code.""" + +load("@compatibility_proxy//:proxy.bzl", "java_common", "java_common_internal_compile", "java_info_internal_merge") + +def compile(*, injecting_rule_kind, enable_jspecify, include_compilation_info, **kwargs): + if java_common_internal_compile: + return java_common_internal_compile( + injecting_rule_kind = injecting_rule_kind, + enable_jspecify = enable_jspecify, + include_compilation_info = include_compilation_info, + **kwargs + ) + else: + return java_common.compile(**kwargs) + +def merge(providers, *, merge_java_outputs = True, merge_source_jars = True): + if java_info_internal_merge: + return java_info_internal_merge( + providers, + merge_java_outputs = merge_java_outputs, + merge_source_jars = merge_source_jars, + ) + else: + return java_common.merge(providers) diff --git a/java/proto/BUILD b/java/proto/BUILD index 209a5f52..fa4ecf19 100644 --- a/java/proto/BUILD +++ b/java/proto/BUILD @@ -1,4 +1,7 @@ -package(default_visibility = ["//visibility:public"]) +package( + default_applicable_licenses = ["@rules_java//:license"], + default_visibility = ["//visibility:public"], +) # Toolchain type provided by proto_lang_toolchain rule and used by java_proto_library toolchain_type(name = "toolchain_type") diff --git a/java/repositories.bzl b/java/repositories.bzl index f60a4d0c..df95f86d 100644 --- a/java/repositories.bzl +++ b/java/repositories.bzl @@ -22,33 +22,39 @@ load("//toolchains:remote_java_repository.bzl", "remote_java_repository") # visible for tests JAVA_TOOLS_CONFIG = { - "version": "v13.6.1", + "version": "v19.0", + "source_revision": "61972bfd7fb6f587fc4576b5114e20758b501806", "release": "true", "artifacts": { "java_tools_linux": { - "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v13.6.1/java_tools_linux-v13.6.1.zip", - "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_13.6.1/java_tools_linux-v13.6.1.zip", - "sha": "0d3fcae7ae40d0a25f17c3adc30a3674f526953c55871189e2efe3463fce3496", + "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v19.0/java_tools_linux-v19.0.zip", + "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_v19.0/java_tools_linux-v19.0.zip", + "sha": "7d5b0c01f99ea5596b7901a7e5f9173f3ae3c4f0b480378e87567eaf97d75d25", + }, + "java_tools_linux_aarch64": { + "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v19.0/java_tools_linux_aarch64-v19.0.zip", + "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_v19.0/java_tools_linux_aarch64-v19.0.zip", + "sha": "34b14fdfe8d6e32ed7f80e7c4d34ce79f4006e0d4b621552f507e51b9d6a3d7d", }, "java_tools_windows": { - "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v13.6.1/java_tools_windows-v13.6.1.zip", - "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_13.6.1/java_tools_windows-v13.6.1.zip", - "sha": "5a7d00e42c0b35f08eb5c8577eb115f8f57dd36ef8b6940c2190bd0d0e4ddcf0", + "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v19.0/java_tools_windows-v19.0.zip", + "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_v19.0/java_tools_windows-v19.0.zip", + "sha": "86c5a58ccefdb9e704fddd325f5fbc7867b2cdd128fb8b9abfdf607a75d2ae71", }, "java_tools_darwin_x86_64": { - "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v13.6.1/java_tools_darwin_x86_64-v13.6.1.zip", - "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_13.6.1/java_tools_darwin_x86_64-v13.6.1.zip", - "sha": "465dcb1da77a0c83c49f178c11bad29b3d703df1756722ec42fe5afd7c8129f8", + "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v19.0/java_tools_darwin_x86_64-v19.0.zip", + "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_v19.0/java_tools_darwin_x86_64-v19.0.zip", + "sha": "78ff8ec7038ab5f9cd261cdf9ff75ea0eb2af7579f9aeef9d658459ee37fabac", }, "java_tools_darwin_arm64": { - "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v13.6.1/java_tools_darwin_arm64-v13.6.1.zip", - "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_13.6.1/java_tools_darwin_arm64-v13.6.1.zip", - "sha": "eb54c4e5fa23d6e9e9fc14c106a682dbefc54659d8e389a2f3c0d61d51cae274", + "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v19.0/java_tools_darwin_arm64-v19.0.zip", + "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_v19.0/java_tools_darwin_arm64-v19.0.zip", + "sha": "86f15280dd0ce121a22f061e2e301ecf746597caa4938cf56852b3d311b830d8", }, "java_tools": { - "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v13.6.1/java_tools-v13.6.1.zip", - "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_13.6.1/java_tools-v13.6.1.zip", - "sha": "74c978eab040ad4ec38ce0d0970ac813cc2c6f4f6f4f121c0414719487edc991", + "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v19.0/java_tools-v19.0.zip", + "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_v19.0/java_tools-v19.0.zip", + "sha": "d21d4aad1a18062512bcf4e8f7a09a4f5a042c760daa779e0ddda6e41b170507", }, }, } @@ -64,6 +70,7 @@ def java_tools_repos(): config["mirror_url"], config["github_url"], ], + build_file = config.get("build_file"), ) def local_jdk_repo(): @@ -74,46 +81,46 @@ def local_jdk_repo(): ) # DO NOT MANUALLY UPDATE! Update java/bazel/repositories_util.bzl instead and -# run the java/bazel:dump_remote_jdk_configs target to generate this list +# build the java/bazel:dump_remote_jdk_configs target to generate this list _REMOTE_JDK_CONFIGS_LIST = [ struct( name = "remote_jdk8_linux_aarch64", target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:aarch64"], - sha256 = "82c46c65d57e187ef68fdd125ef760eaeb52ebfe1be1a6a251cf5b43cbebc78a", - strip_prefix = "zulu8.78.0.19-ca-jdk8.0.412-linux_aarch64", - urls = ["https://cdn.azul.com/zulu/bin/zulu8.78.0.19-ca-jdk8.0.412-linux_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.78.0.19-ca-jdk8.0.412-linux_aarch64.tar.gz"], + sha256 = "c372fb26480c052537125013cb0ba7336c404e5190ea8f6e2de247b676432a67", + strip_prefix = "zulu8.90.0.19-ca-jdk8.0.472-linux_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu8.90.0.19-ca-jdk8.0.472-linux_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.90.0.19-ca-jdk8.0.472-linux_aarch64.tar.gz"], version = "8", ), struct( name = "remote_jdk8_linux", target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:x86_64"], - sha256 = "9c0ac5ebffa61520fee78ead52add0f4edd3b1b54b01b6a17429b719515caf90", - strip_prefix = "zulu8.78.0.19-ca-jdk8.0.412-linux_x64", - urls = ["https://cdn.azul.com/zulu/bin/zulu8.78.0.19-ca-jdk8.0.412-linux_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.78.0.19-ca-jdk8.0.412-linux_x64.tar.gz"], + sha256 = "6f9e3fa773829ac2553411fb0cdeb394980627c47c9ab8f8892d4b917b70e2dd", + strip_prefix = "zulu8.90.0.19-ca-jdk8.0.472-linux_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu8.90.0.19-ca-jdk8.0.472-linux_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.90.0.19-ca-jdk8.0.472-linux_x64.tar.gz"], version = "8", ), struct( name = "remote_jdk8_macos_aarch64", target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:aarch64"], - sha256 = "35bc35808379400e4a70e1f7ee379778881799b93c2cc9fe1ae515c03c2fb057", - strip_prefix = "zulu8.78.0.19-ca-jdk8.0.412-macosx_aarch64", - urls = ["https://cdn.azul.com/zulu/bin/zulu8.78.0.19-ca-jdk8.0.412-macosx_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.78.0.19-ca-jdk8.0.412-macosx_aarch64.tar.gz"], + sha256 = "823d547ab9508e3a2b8abd3c5de66a39a50a254dc5835747cf3c2617fbe55600", + strip_prefix = "zulu8.90.0.19-ca-jdk8.0.472-macosx_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu8.90.0.19-ca-jdk8.0.472-macosx_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.90.0.19-ca-jdk8.0.472-macosx_aarch64.tar.gz"], version = "8", ), struct( name = "remote_jdk8_macos", target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:x86_64"], - sha256 = "2bfa0506196962bddb21a604eaa2b0b39eaf3383d0bdad08bdbe7f42f25d8928", - strip_prefix = "zulu8.78.0.19-ca-jdk8.0.412-macosx_x64", - urls = ["https://cdn.azul.com/zulu/bin/zulu8.78.0.19-ca-jdk8.0.412-macosx_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.78.0.19-ca-jdk8.0.412-macosx_x64.tar.gz"], + sha256 = "96fb7bc3a2babd7f807484b1b8f1dfb8cf2c61ea27b2d0630e2237088b0a5100", + strip_prefix = "zulu8.90.0.19-ca-jdk8.0.472-macosx_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu8.90.0.19-ca-jdk8.0.472-macosx_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.90.0.19-ca-jdk8.0.472-macosx_x64.tar.gz"], version = "8", ), struct( name = "remote_jdk8_windows", target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:x86_64"], - sha256 = "ca5499c301d5b42604d8535b8c40a7f928a796247b8c66a600333dd799798ff7", - strip_prefix = "zulu8.78.0.19-ca-jdk8.0.412-win_x64", - urls = ["https://cdn.azul.com/zulu/bin/zulu8.78.0.19-ca-jdk8.0.412-win_x64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.78.0.19-ca-jdk8.0.412-win_x64.zip"], + sha256 = "ff90484103d3fdb9808f737af027586c8dbfa1aa8a310ce99b0b5e0517567aee", + strip_prefix = "zulu8.90.0.19-ca-jdk8.0.472-win_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu8.90.0.19-ca-jdk8.0.472-win_x64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.90.0.19-ca-jdk8.0.472-win_x64.zip"], version = "8", ), struct( @@ -127,194 +134,274 @@ _REMOTE_JDK_CONFIGS_LIST = [ struct( name = "remotejdk11_linux_aarch64", target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:aarch64"], - sha256 = "be7d7574253c893eb58f66e985c75adf48558c41885827d1f02f827e109530e0", - strip_prefix = "zulu11.72.19-ca-jdk11.0.23-linux_aarch64", - urls = ["https://cdn.azul.com/zulu/bin/zulu11.72.19-ca-jdk11.0.23-linux_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.72.19-ca-jdk11.0.23-linux_aarch64.tar.gz"], + sha256 = "5a225a0fe0a92bc6c04c8c5aeb03c697c6fd114465829f23e494a2ad44fa1cc0", + strip_prefix = "zulu11.84.17-ca-jdk11.0.29-linux_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu11.84.17-ca-jdk11.0.29-linux_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.84.17-ca-jdk11.0.29-linux_aarch64.tar.gz"], version = "11", ), struct( name = "remotejdk11_linux", target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:x86_64"], - sha256 = "0a4d1bfc7a96a7f9f5329b72b9801b3c53366417b4753f1b658fa240204c7347", - strip_prefix = "zulu11.72.19-ca-jdk11.0.23-linux_x64", - urls = ["https://cdn.azul.com/zulu/bin/zulu11.72.19-ca-jdk11.0.23-linux_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.72.19-ca-jdk11.0.23-linux_x64.tar.gz"], + sha256 = "681b2e4bf7fedf4d20666fc2a954b83ff5675ccfb916c867267d29c85c2ee310", + strip_prefix = "zulu11.84.17-ca-jdk11.0.29-linux_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu11.84.17-ca-jdk11.0.29-linux_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.84.17-ca-jdk11.0.29-linux_x64.tar.gz"], version = "11", ), struct( name = "remotejdk11_macos_aarch64", target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:aarch64"], - sha256 = "40fb1918385e03814b67b7608c908c7f945ccbeddbbf5ed062cdfb2602e21c83", - strip_prefix = "zulu11.72.19-ca-jdk11.0.23-macosx_aarch64", - urls = ["https://cdn.azul.com/zulu/bin/zulu11.72.19-ca-jdk11.0.23-macosx_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.72.19-ca-jdk11.0.23-macosx_aarch64.tar.gz"], + sha256 = "09ed1734c2d88fadcb75fdbec1ba5467d32e7fa2b10894541aa8e3d3ce78dc2d", + strip_prefix = "zulu11.84.17-ca-jdk11.0.29-macosx_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu11.84.17-ca-jdk11.0.29-macosx_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.84.17-ca-jdk11.0.29-macosx_aarch64.tar.gz"], version = "11", ), struct( name = "remotejdk11_macos", target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:x86_64"], - sha256 = "e5b19b82045826ae09c9d17742691bc9e40312c44be7bd7598ae418a3d4edb1c", - strip_prefix = "zulu11.72.19-ca-jdk11.0.23-macosx_x64", - urls = ["https://cdn.azul.com/zulu/bin/zulu11.72.19-ca-jdk11.0.23-macosx_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.72.19-ca-jdk11.0.23-macosx_x64.tar.gz"], + sha256 = "b7f5a37c47ed94af1c8ecf631b1dc6dec990958f3afd4222a7dd27d6ca1084bd", + strip_prefix = "zulu11.84.17-ca-jdk11.0.29-macosx_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu11.84.17-ca-jdk11.0.29-macosx_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.84.17-ca-jdk11.0.29-macosx_x64.tar.gz"], version = "11", ), struct( name = "remotejdk11_win", target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:x86_64"], - sha256 = "1295b2affe498018c45f6f15187b58c4456d51dce5eb608ee73ef7665d4566d2", - strip_prefix = "zulu11.72.19-ca-jdk11.0.23-win_x64", - urls = ["https://cdn.azul.com/zulu/bin/zulu11.72.19-ca-jdk11.0.23-win_x64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.72.19-ca-jdk11.0.23-win_x64.zip"], + sha256 = "f4002a8090662ff66bf9f3248604be0f9d226e964085bd59bbea4b8535df3de1", + strip_prefix = "zulu11.84.17-ca-jdk11.0.29-win_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu11.84.17-ca-jdk11.0.29-win_x64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.84.17-ca-jdk11.0.29-win_x64.zip"], version = "11", ), struct( name = "remotejdk11_linux_ppc64le", - target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:ppc"], - sha256 = "a8fba686f6eb8ae1d1a9566821dbd5a85a1108b96ad857fdbac5c1e4649fc56f", - strip_prefix = "jdk-11.0.15+10", - urls = ["https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.15+10/OpenJDK11U-jdk_ppc64le_linux_hotspot_11.0.15_10.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.15+10/OpenJDK11U-jdk_ppc64le_linux_hotspot_11.0.15_10.tar.gz"], + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:ppc64le"], + sha256 = "e272abd162b3de68093630929453feba3e63a5ab1bbb912379f6a4aa968ef06a", + strip_prefix = "jdk-11.0.28+6", + urls = ["https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.28+6/OpenJDK11U-jdk_ppc64le_linux_hotspot_11.0.28_6.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.28+6/OpenJDK11U-jdk_ppc64le_linux_hotspot_11.0.28_6.tar.gz"], version = "11", ), struct( name = "remotejdk11_linux_s390x", target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:s390x"], - sha256 = "a58fc0361966af0a5d5a31a2d8a208e3c9bb0f54f345596fd80b99ea9a39788b", - strip_prefix = "jdk-11.0.15+10", - urls = ["https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.15+10/OpenJDK11U-jdk_s390x_linux_hotspot_11.0.15_10.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.15+10/OpenJDK11U-jdk_s390x_linux_hotspot_11.0.15_10.tar.gz"], + sha256 = "ac3f94fdcc5372e90f44fad9cd03ec0e3fd3535fea06c120f85e4a7534c6de04", + strip_prefix = "jdk-11.0.28+6", + urls = ["https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.28+6/OpenJDK11U-jdk_s390x_linux_hotspot_11.0.28_6.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.28+6/OpenJDK11U-jdk_s390x_linux_hotspot_11.0.28_6.tar.gz"], version = "11", ), struct( name = "remotejdk11_win_arm64", target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:arm64"], - sha256 = "b8a28e6e767d90acf793ea6f5bed0bb595ba0ba5ebdf8b99f395266161e53ec2", - strip_prefix = "jdk-11.0.13+8", - urls = ["https://aka.ms/download-jdk/microsoft-jdk-11.0.13.8.1-windows-aarch64.zip", "https://mirror.bazel.build/aka.ms/download-jdk/microsoft-jdk-11.0.13.8.1-windows-aarch64.zip"], + sha256 = "1e7bfad513a1c2930000db1af19c813460c7a788503c39a7f1f27375310880f8", + strip_prefix = "jdk-11.0.28+6", + urls = ["https://aka.ms/download-jdk/microsoft-jdk-11.0.28-windows-aarch64.zip", "https://mirror.bazel.build/aka.ms/download-jdk/microsoft-jdk-11.0.28-windows-aarch64.zip"], version = "11", ), struct( name = "remotejdk17_linux_aarch64", target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:aarch64"], - sha256 = "518cc455c0c7b49c0ae7d809c0bb87ab371bb850d46abb8efad5010c6a06faec", - strip_prefix = "zulu17.50.19-ca-jdk17.0.11-linux_aarch64", - urls = ["https://cdn.azul.com/zulu/bin/zulu17.50.19-ca-jdk17.0.11-linux_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.50.19-ca-jdk17.0.11-linux_aarch64.tar.gz"], + sha256 = "527b76fbd60e8f9644f6b70800d15160cdbd3344bc6fbf30d42e905f540a770c", + strip_prefix = "zulu17.62.17-ca-jdk17.0.17-linux_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu17.62.17-ca-jdk17.0.17-linux_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.62.17-ca-jdk17.0.17-linux_aarch64.tar.gz"], version = "17", ), struct( name = "remotejdk17_linux", target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:x86_64"], - sha256 = "a1e8ac9ae5804b84dc07cf9d8ebe1b18247d70c92c1e0de97ea10109563f4379", - strip_prefix = "zulu17.50.19-ca-jdk17.0.11-linux_x64", - urls = ["https://cdn.azul.com/zulu/bin/zulu17.50.19-ca-jdk17.0.11-linux_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.50.19-ca-jdk17.0.11-linux_x64.tar.gz"], + sha256 = "1dcbbed73e95dc35f5c60402a84936f6830ff43c2a0dc0037a5657dbc25472c1", + strip_prefix = "zulu17.62.17-ca-jdk17.0.17-linux_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu17.62.17-ca-jdk17.0.17-linux_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.62.17-ca-jdk17.0.17-linux_x64.tar.gz"], version = "17", ), struct( name = "remotejdk17_macos_aarch64", target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:aarch64"], - sha256 = "dd1a82d57e80cdefb045066e5c28b5bd41e57eea9c57303ec7e012b57230bb9c", - strip_prefix = "zulu17.50.19-ca-jdk17.0.11-macosx_aarch64", - urls = ["https://cdn.azul.com/zulu/bin/zulu17.50.19-ca-jdk17.0.11-macosx_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.50.19-ca-jdk17.0.11-macosx_aarch64.tar.gz"], + sha256 = "5558f7efe6297ecb20f422f31471555cd43e9499beb304b8f3ddc68796d2874b", + strip_prefix = "zulu17.62.17-ca-jdk17.0.17-macosx_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu17.62.17-ca-jdk17.0.17-macosx_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.62.17-ca-jdk17.0.17-macosx_aarch64.tar.gz"], version = "17", ), struct( name = "remotejdk17_macos", target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:x86_64"], - sha256 = "b384991e93af39abe5229c7f5efbe912a7c5a6480674a6e773f3a9128f96a764", - strip_prefix = "zulu17.50.19-ca-jdk17.0.11-macosx_x64", - urls = ["https://cdn.azul.com/zulu/bin/zulu17.50.19-ca-jdk17.0.11-macosx_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.50.19-ca-jdk17.0.11-macosx_x64.tar.gz"], + sha256 = "e6b2fb0cd3f929225a179c2fb2813abd1834f839a3b4c8cdcb36067aa16b6f83", + strip_prefix = "zulu17.62.17-ca-jdk17.0.17-macosx_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu17.62.17-ca-jdk17.0.17-macosx_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.62.17-ca-jdk17.0.17-macosx_x64.tar.gz"], version = "17", ), struct( name = "remotejdk17_win_arm64", target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:arm64"], - sha256 = "b8833d272eb31f54f8c881139807a28a74de9deae07d2cc37688ff72043e32c9", - strip_prefix = "zulu17.50.19-ca-jdk17.0.11-win_aarch64", - urls = ["https://cdn.azul.com/zulu/bin/zulu17.50.19-ca-jdk17.0.11-win_aarch64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.50.19-ca-jdk17.0.11-win_aarch64.zip"], + sha256 = "c1bba9148907348da93b0de2c9abd56bd180efcb6b1f35068ab9785015fcd74b", + strip_prefix = "zulu17.62.17-ca-jdk17.0.17-win_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu17.62.17-ca-jdk17.0.17-win_aarch64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.62.17-ca-jdk17.0.17-win_aarch64.zip"], version = "17", ), struct( name = "remotejdk17_win", target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:x86_64"], - sha256 = "43f0f1bdecf48ba9763d46ee7784554c95b442ffdd39ebd62dc8b297cc82e116", - strip_prefix = "zulu17.50.19-ca-jdk17.0.11-win_x64", - urls = ["https://cdn.azul.com/zulu/bin/zulu17.50.19-ca-jdk17.0.11-win_x64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.50.19-ca-jdk17.0.11-win_x64.zip"], + sha256 = "bd8a942bb543f109a28d3eadf3ec2f29a3ee28ab53506e31d2858292f63c6949", + strip_prefix = "zulu17.62.17-ca-jdk17.0.17-win_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu17.62.17-ca-jdk17.0.17-win_x64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.62.17-ca-jdk17.0.17-win_x64.zip"], version = "17", ), struct( name = "remotejdk17_linux_ppc64le", - target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:ppc"], - sha256 = "00a4c07603d0218cd678461b5b3b7e25b3253102da4022d31fc35907f21a2efd", - strip_prefix = "jdk-17.0.8.1+1", - urls = ["https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.8.1+1/OpenJDK17U-jdk_ppc64le_linux_hotspot_17.0.8.1_1.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.8.1+1/OpenJDK17U-jdk_ppc64le_linux_hotspot_17.0.8.1_1.tar.gz"], + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:ppc64le"], + sha256 = "eb020f74e00870379522be0b44fc6322c2214e77971c258400c8b5af704d5c0a", + strip_prefix = "jdk-17.0.16+8", + urls = ["https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.16+8/OpenJDK17U-jdk_ppc64le_linux_hotspot_17.0.16_8.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.16+8/OpenJDK17U-jdk_ppc64le_linux_hotspot_17.0.16_8.tar.gz"], version = "17", ), struct( name = "remotejdk17_linux_s390x", target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:s390x"], - sha256 = "ffacba69c6843d7ca70d572489d6cc7ab7ae52c60f0852cedf4cf0d248b6fc37", - strip_prefix = "jdk-17.0.8.1+1", - urls = ["https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.8.1+1/OpenJDK17U-jdk_s390x_linux_hotspot_17.0.8.1_1.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.8.1+1/OpenJDK17U-jdk_s390x_linux_hotspot_17.0.8.1_1.tar.gz"], + sha256 = "03dd99d34d2d1b88395765df3acbec2cb81de286f64b1d9e6df3682bee365168", + strip_prefix = "jdk-17.0.16+8", + urls = ["https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.16+8/OpenJDK17U-jdk_s390x_linux_hotspot_17.0.16_8.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.16+8/OpenJDK17U-jdk_s390x_linux_hotspot_17.0.16_8.tar.gz"], version = "17", ), struct( name = "remotejdk21_linux_aarch64", target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:aarch64"], - sha256 = "c898d4ce365c8926e22859c06be4a81bafb9fd0126088867e15a528fe99b1599", - strip_prefix = "zulu21.34.19-ca-jdk21.0.3-linux_aarch64", - urls = ["https://cdn.azul.com/zulu/bin/zulu21.34.19-ca-jdk21.0.3-linux_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.34.19-ca-jdk21.0.3-linux_aarch64.tar.gz"], + sha256 = "a826a93c18d4388ec8d5d4057f5bb1b5c60f00ffc875ed299dea17aa947555ee", + strip_prefix = "zulu21.46.19-ca-jdk21.0.9-linux_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu21.46.19-ca-jdk21.0.9-linux_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.46.19-ca-jdk21.0.9-linux_aarch64.tar.gz"], version = "21", ), struct( name = "remotejdk21_linux", target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:x86_64"], - sha256 = "ca763d1308a6bcc768382f160733a08e591d5f595a7dd9e51b60d27d54828dcc", - strip_prefix = "zulu21.34.19-ca-jdk21.0.3-linux_x64", - urls = ["https://cdn.azul.com/zulu/bin/zulu21.34.19-ca-jdk21.0.3-linux_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.34.19-ca-jdk21.0.3-linux_x64.tar.gz"], + sha256 = "67e810b31427ac0ff1c249473595066a00bdf0f9265df186c32905d5f75c93b8", + strip_prefix = "zulu21.46.19-ca-jdk21.0.9-linux_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu21.46.19-ca-jdk21.0.9-linux_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.46.19-ca-jdk21.0.9-linux_x64.tar.gz"], version = "21", ), struct( name = "remotejdk21_macos_aarch64", target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:aarch64"], - sha256 = "4f42a561909d71868a700cf2efa1390e1b9e04863f3fa75ea30c4965e5a702f0", - strip_prefix = "zulu21.34.19-ca-jdk21.0.3-macosx_aarch64", - urls = ["https://cdn.azul.com/zulu/bin/zulu21.34.19-ca-jdk21.0.3-macosx_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.34.19-ca-jdk21.0.3-macosx_aarch64.tar.gz"], + sha256 = "b56112ad12d3dfe62802840655ecf198fe4ca48729824c939d65a69e803536c7", + strip_prefix = "zulu21.46.19-ca-jdk21.0.9-macosx_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu21.46.19-ca-jdk21.0.9-macosx_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.46.19-ca-jdk21.0.9-macosx_aarch64.tar.gz"], version = "21", ), struct( name = "remotejdk21_macos", target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:x86_64"], - sha256 = "148006a220a18922d7a9c52ac0bad099c5b4e60334a8d02b11f8c945e9ec9a34", - strip_prefix = "zulu21.34.19-ca-jdk21.0.3-macosx_x64", - urls = ["https://cdn.azul.com/zulu/bin/zulu21.34.19-ca-jdk21.0.3-macosx_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.34.19-ca-jdk21.0.3-macosx_x64.tar.gz"], + sha256 = "3d870d823e3e101189b00ad975188c0321e31056ac8ca8b487bcf4454f3b5cfe", + strip_prefix = "zulu21.46.19-ca-jdk21.0.9-macosx_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu21.46.19-ca-jdk21.0.9-macosx_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.46.19-ca-jdk21.0.9-macosx_x64.tar.gz"], + version = "21", + ), + struct( + name = "remotejdk21_win_arm64", + target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:arm64"], + sha256 = "1210b169db7b40d7305a94d41ab3eb87aaee51108b43f8f7f36f0c2865107790", + strip_prefix = "zulu21.46.19-ca-jdk21.0.9-win_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu21.46.19-ca-jdk21.0.9-win_aarch64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.46.19-ca-jdk21.0.9-win_aarch64.zip"], version = "21", ), struct( name = "remotejdk21_win", target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:x86_64"], - sha256 = "fb9f0dc6a484b0b169b3b3a3c2425d5f62bebfd15cb06c1597e33f77959c72af", - strip_prefix = "zulu21.34.19-ca-jdk21.0.3-win_x64", - urls = ["https://cdn.azul.com/zulu/bin/zulu21.34.19-ca-jdk21.0.3-win_x64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.34.19-ca-jdk21.0.3-win_x64.zip"], + sha256 = "0c9812c0fe527b59f48c70cb527035c8a7abe620b31f776b4ddc21bddc1cd067", + strip_prefix = "zulu21.46.19-ca-jdk21.0.9-win_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu21.46.19-ca-jdk21.0.9-win_x64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.46.19-ca-jdk21.0.9-win_x64.zip"], version = "21", ), struct( name = "remotejdk21_linux_ppc64le", - target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:ppc"], - sha256 = "d08de863499d8851811c893e8915828f2cd8eb67ed9e29432a6b4e222d80a12f", - strip_prefix = "jdk-21.0.2+13", - urls = ["https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2+13/OpenJDK21U-jdk_ppc64le_linux_hotspot_21.0.2_13.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2+13/OpenJDK21U-jdk_ppc64le_linux_hotspot_21.0.2_13.tar.gz"], + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:ppc64le"], + sha256 = "a24e869b8e563fd7b9f7776f6686ca5d737c8d1c3c33c9b72836935709b44a34", + strip_prefix = "jdk-21.0.8+9", + urls = ["https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8+9/OpenJDK21U-jdk_ppc64le_linux_hotspot_21.0.8_9.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8+9/OpenJDK21U-jdk_ppc64le_linux_hotspot_21.0.8_9.tar.gz"], + version = "21", + ), + struct( + name = "remotejdk21_linux_riscv64", + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:riscv64"], + sha256 = "8171d95189e675e297b5cb96c7ac6247ab4e9f48da82b13f491fc46ef5d97836", + strip_prefix = "jdk-21.0.8+9", + urls = ["https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8+9/OpenJDK21U-jdk_riscv64_linux_hotspot_21.0.8_9.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8+9/OpenJDK21U-jdk_riscv64_linux_hotspot_21.0.8_9.tar.gz"], version = "21", ), struct( name = "remotejdk21_linux_s390x", target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:s390x"], - sha256 = "0d5676c50821e0d0b951bf3ffd717e7a13be2a89d8848a5c13b4aedc6f982c78", - strip_prefix = "jdk-21.0.2+13", - urls = ["https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2+13/OpenJDK21U-jdk_s390x_linux_hotspot_21.0.2_13.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2+13/OpenJDK21U-jdk_s390x_linux_hotspot_21.0.2_13.tar.gz"], + sha256 = "a84e3cbf8bb5f8a313e06b790c7bc388687ba00262e981f5e33432ebd4d34356", + strip_prefix = "jdk-21.0.8+9", + urls = ["https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8+9/OpenJDK21U-jdk_s390x_linux_hotspot_21.0.8_9.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8+9/OpenJDK21U-jdk_s390x_linux_hotspot_21.0.8_9.tar.gz"], version = "21", ), struct( - name = "remotejdk21_win_arm64", + name = "remotejdk25_linux_aarch64", + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:aarch64"], + sha256 = "f7295abb1decb2f6e0ea9f760a70917f2d47356db4366d615c7ab9b01c3c6866", + strip_prefix = "zulu25.32.17-ca-jdk25.0.2-linux_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu25.32.17-ca-jdk25.0.2-linux_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu25.32.17-ca-jdk25.0.2-linux_aarch64.tar.gz"], + version = "25", + ), + struct( + name = "remotejdk25_linux", + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:x86_64"], + sha256 = "f1752d0051b6ca233625ddb2c18c9170edbe55c5ee6515bfefd8ea0197ee1c20", + strip_prefix = "zulu25.32.17-ca-jdk25.0.2-linux_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu25.32.17-ca-jdk25.0.2-linux_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu25.32.17-ca-jdk25.0.2-linux_x64.tar.gz"], + version = "25", + ), + struct( + name = "remotejdk25_macos_aarch64", + target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:aarch64"], + sha256 = "537ac74fa1ca2c4dd8f6063ddede0138ae4a896f128bfe10b428a7dcc4aa929f", + strip_prefix = "zulu25.32.17-ca-jdk25.0.2-macosx_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu25.32.17-ca-jdk25.0.2-macosx_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu25.32.17-ca-jdk25.0.2-macosx_aarch64.tar.gz"], + version = "25", + ), + struct( + name = "remotejdk25_macos", + target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:x86_64"], + sha256 = "fe22346c192920c5b84b23f4aa24e3debb0bb94fc428016f7c2b2c7790c82780", + strip_prefix = "zulu25.32.17-ca-jdk25.0.2-macosx_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu25.32.17-ca-jdk25.0.2-macosx_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu25.32.17-ca-jdk25.0.2-macosx_x64.tar.gz"], + version = "25", + ), + struct( + name = "remotejdk25_win_arm64", target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:arm64"], - sha256 = "975603e684f2ec5a525b3b5336d6aa0b09b5b7d2d0d9e271bd6a9892ad550181", - strip_prefix = "jdk-21+35", - urls = ["https://aka.ms/download-jdk/microsoft-jdk-21.0.0-windows-aarch64.zip", "https://mirror.bazel.build/aka.ms/download-jdk/microsoft-jdk-21.0.0-windows-aarch64.zip"], - version = "21", + sha256 = "5f44792a12af2100b4db9b5120c27c42af8080ad63bc9eaadc9ca25e5c7dd590", + strip_prefix = "zulu25.32.17-ca-jdk25.0.2-win_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu25.32.17-ca-jdk25.0.2-win_aarch64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu25.32.17-ca-jdk25.0.2-win_aarch64.zip"], + version = "25", + ), + struct( + name = "remotejdk25_win", + target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:x86_64"], + sha256 = "90bcbbcbe2fb7aec43ce8fd2efa024fcc48f00bbdcd7a76b58f912d6b97e6d56", + strip_prefix = "zulu25.32.17-ca-jdk25.0.2-win_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu25.32.17-ca-jdk25.0.2-win_x64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu25.32.17-ca-jdk25.0.2-win_x64.zip"], + version = "25", + ), + struct( + name = "remotejdk25_linux_ppc64le", + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:ppc64le"], + sha256 = "b262b735b215173003766da36588d5f717dceada0286db41b439f93fb2ada468", + strip_prefix = "jdk-25.0.2+10", + urls = ["https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2+10/OpenJDK25U-jdk_ppc64le_linux_hotspot_25.0.2_10.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2+10/OpenJDK25U-jdk_ppc64le_linux_hotspot_25.0.2_10.tar.gz"], + version = "25", + ), + struct( + name = "remotejdk25_linux_riscv64", + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:riscv64"], + sha256 = "168119e4fba350f4e6b3ca92450a2b90a8502b89a235a04415e9adf9f5d3164e", + strip_prefix = "jdk-25.0.2+10", + urls = ["https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2+10/OpenJDK25U-jdk_riscv64_linux_hotspot_25.0.2_10.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2+10/OpenJDK25U-jdk_riscv64_linux_hotspot_25.0.2_10.tar.gz"], + version = "25", + ), + struct( + name = "remotejdk25_linux_s390x", + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:s390x"], + sha256 = "15e5cbcadcf3d43623c31b825063cdc2817b9f1ba840b51dc6ef70e5d33c84e3", + strip_prefix = "jdk-25.0.2+10", + urls = ["https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2+10/OpenJDK25U-jdk_s390x_linux_hotspot_25.0.2_10.tar.gz", "https://mirror.bazel.build/github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2+10/OpenJDK25U-jdk_s390x_linux_hotspot_25.0.2_10.tar.gz"], + version = "25", ), ] @@ -360,25 +447,28 @@ def remote_jdk21_repos(): """Imports OpenJDK 21 repositories.""" _remote_jdk_repos_for_version("21") +def remote_jdk25_repos(): + """Imports OpenJDK 25 repositories.""" + _remote_jdk_repos_for_version("25") + def rules_java_dependencies(): - """An utility method to load all dependencies of rules_java. + """DEPRECATED: No-op, kept for backwards compatibility""" + print("DEPRECATED: use rules_java_dependencies() from rules_java_deps.bzl") # buildifier: disable=print - Loads the remote repositories used by default in Bazel. - """ +def rules_java_toolchains(name = "toolchains"): + """An utility method to load all Java toolchains. + Args: + name: The name of this macro (not used) + """ local_jdk_repo() remote_jdk8_repos() remote_jdk11_repos() remote_jdk17_repos() remote_jdk21_repos() + remote_jdk25_repos() java_tools_repos() -def rules_java_toolchains(name = "toolchains"): - """An utility method to load all Java toolchains. - - Args: - name: The name of this macro (not used) - """ native.register_toolchains( "//toolchains:all", "@local_jdk//:runtime_toolchain_definition", diff --git a/java/rules_java_deps.bzl b/java/rules_java_deps.bzl new file mode 100644 index 00000000..b2086dc1 --- /dev/null +++ b/java/rules_java_deps.bzl @@ -0,0 +1,236 @@ +"""Module extension for compatibility with previous Bazel versions""" + +load("@bazel_features//private:util.bzl", _bazel_version_ge = "ge") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +def _compatibility_proxy_repo_impl(rctx): + if _bazel_version_ge("8.0.0"): + rctx.file( + "BUILD.bazel", + """ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +exports_files(['proxy.bzl'], visibility = ["@rules_java//test:__pkg__"]) +bzl_library( + name = "proxy_bzl", + srcs = ["proxy.bzl"], + deps = [ + "@rules_java//java/bazel/rules", + "@rules_java//java/common/rules:toolchain_rules", + "@rules_java//java/private:internals", + "@rules_java//java/bazel:http_jar_bzl", + ], + visibility = ["//visibility:public"] +) + """, + ) + rctx.file( + "proxy.bzl", + """ +load("@rules_java//java/bazel/rules:bazel_java_binary_wrapper.bzl", _java_binary = "java_binary") +load("@rules_java//java/bazel/rules:bazel_java_import.bzl", _java_import = "java_import") +load("@rules_java//java/bazel/rules:bazel_java_library.bzl", _java_library = "java_library") +load("@rules_java//java/bazel/rules:bazel_java_plugin.bzl", _java_plugin = "java_plugin") +load("@rules_java//java/bazel/rules:bazel_java_test.bzl", _java_test = "java_test") +load("@rules_java//java/bazel:http_jar.bzl", _http_jar = "http_jar") +load("@rules_java//java/common/rules:java_package_configuration.bzl", _java_package_configuration = "java_package_configuration") +load("@rules_java//java/common/rules:java_runtime.bzl", _java_runtime = "java_runtime") +load("@rules_java//java/common/rules:java_toolchain.bzl", _java_toolchain = "java_toolchain") +load("@rules_java//java/private:java_common.bzl", _java_common = "java_common") +load("@rules_java//java/private:java_common_internal.bzl", _java_common_internal_compile = "compile") +load("@rules_java//java/private:java_info.bzl", _JavaInfo = "JavaInfo", _JavaPluginInfo = "JavaPluginInfo", + _java_info_internal_merge = "merge", _java_info_to_implicit_exportable = "to_implicit_exportable") + +java_binary = _java_binary +java_import = _java_import +java_library = _java_library +java_plugin = _java_plugin +java_test = _java_test +java_package_configuration = _java_package_configuration +java_runtime = _java_runtime +java_toolchain = _java_toolchain +java_common = _java_common +JavaInfo = _JavaInfo +JavaPluginInfo = _JavaPluginInfo +java_common_internal_compile = _java_common_internal_compile +java_info_internal_merge = _java_info_internal_merge +java_info_to_implicit_exportable = _java_info_to_implicit_exportable +http_jar = _http_jar + """, + ) + else: + rctx.file( + "BUILD.bazel", + """ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +exports_files(['proxy.bzl'], visibility = ["@rules_java//test:__pkg__"]) +bzl_library( + name = "proxy_bzl", + srcs = ["proxy.bzl"], + deps = [ + "@rules_java//java/private:legacy_native_bzl", + "@bazel_tools//tools:bzl_srcs", + ], + visibility = ["//visibility:public"] +) + """, + ) + rctx.file( + "proxy.bzl", + """ +load("@bazel_tools//tools/build_defs/repo:http.bzl", _http_jar = "http_jar") +load("@rules_java//java/private:legacy_native.bzl", "native_java_common", "NativeJavaInfo", "NativeJavaPluginInfo") + +java_binary = native.java_binary +java_import = native.java_import +java_library = native.java_library +java_plugin = native.java_plugin +java_test = native.java_test + +java_package_configuration = native.java_package_configuration +java_runtime = native.java_runtime +java_toolchain = native.java_toolchain + +java_common = native_java_common +JavaInfo = NativeJavaInfo +JavaPluginInfo = NativeJavaPluginInfo +java_common_internal_compile = None +java_info_internal_merge = None +# Not available before Bazel 7 +java_info_to_implicit_exportable = getattr(android_common, "enable_implicit_sourceless_deps_exports_compatibility", None) + +http_jar = _http_jar + """, + ) + +_compatibility_proxy_repo_rule = repository_rule( + _compatibility_proxy_repo_impl, + # force reruns on server restarts to use correct native.bazel_version + local = True, +) + +def compatibility_proxy_repo(): + maybe(_compatibility_proxy_repo_rule, name = "compatibility_proxy") + +def _compat_proxy_impl(module_ctx): + compatibility_proxy_repo() + + # module_ctx.extension_metadata has the paramater `reproducible` as of Bazel 7.1.0. We can't + # test for it directly and would ideally use bazel_features to check for it, but don't want + # to add a dependency for as long as WORKSPACE is still around. Thus, test for it by + # checking the availability of another feature introduced in 7.1.0. + if hasattr(module_ctx, "watch"): + return module_ctx.extension_metadata(reproducible = True) + else: + return None + +compatibility_proxy = module_extension(_compat_proxy_impl) + +def protobuf_repo(): + maybe( + http_archive, + name = "com_google_protobuf", + sha256 = "ce5d00b78450a0ca400bf360ac00c0d599cc225f049d986a27e9a4e396c5a84a", + strip_prefix = "protobuf-29.0-rc2", + url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.0-rc2/protobuf-29.0-rc2.tar.gz", + ) + +def rules_cc_repo(): + maybe( + http_archive, + name = "rules_cc", + sha256 = "f4aadd8387f381033a9ad0500443a52a0cea5f8ad1ede4369d3c614eb7b2682e", + strip_prefix = "rules_cc-0.0.15", + urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.0.15/rules_cc-0.0.15.tar.gz"], + ) + +def bazel_skylib_repo(): + maybe( + http_archive, + name = "bazel_skylib", + sha256 = "bc283cdfcd526a52c3201279cda4bc298652efa898b10b4db0837dc51652756f", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz", + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz", + ], + ) + +def platforms_repo(): + maybe( + http_archive, + name = "platforms", + sha256 = "29742e87275809b5e598dc2f04d86960cc7a55b3067d97221c9abbc9926bff0f", + urls = ["https://github.com/bazelbuild/platforms/releases/download/0.0.11/platforms-0.0.11.tar.gz"], + ) + +def zlib_repo(): + maybe( + http_archive, + name = "zlib", + sha256 = "9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23", + urls = ["https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz"], + strip_prefix = "zlib-1.3.1", + build_file_content = """ +cc_library( + name = "zlib", + srcs = glob(["*.c"]), + hdrs = glob(["*.h"]), + copts = select({ + "@platforms//os:windows": [], + "//conditions:default": [ + "-Wno-deprecated-non-prototype", + "-Wno-unused-variable", + "-Wno-implicit-function-declaration", + ], + }), + includes = ["."], + visibility = ["//visibility:public"], +) + """, + ) + +def absl_repo(): + maybe( + http_archive, + name = "com_google_absl", + sha256 = "f50e5ac311a81382da7fa75b97310e4b9006474f9560ac46f54a9967f07d4ae3", + strip_prefix = "abseil-cpp-20240722.0", + urls = ["https://github.com/abseil/abseil-cpp/archive/refs/tags/20240722.0.tar.gz"], + ) + +def rules_license_repo(): + maybe( + http_archive, + name = "rules_license", + sha256 = "26d4021f6898e23b82ef953078389dd49ac2b5618ac564ade4ef87cced147b38", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/rules_license/releases/download/1.0.0/rules_license-1.0.0.tar.gz", + "https://github.com/bazelbuild/rules_license/releases/download/1.0.0/rules_license-1.0.0.tar.gz", + ], + ) + +def re2_repo(): + maybe( + http_archive, + name = "re2", + sha256 = "5bb6875ae1cd1e9fedde98018c346db7260655f86fdb8837e3075103acd3649b", + strip_prefix = "re2-2023-09-01", + urls = [ + "https://github.com/google/re2/releases/download/2023-09-01/re2-2023-09-01.tar.gz", + ], + ) + +def rules_java_dependencies(): + """An utility method to load non-toolchain dependencies of rules_java. + + Loads the remote repositories used by default in Bazel. + """ + compatibility_proxy_repo() + bazel_skylib_repo() + rules_cc_repo() + protobuf_repo() + platforms_repo() + zlib_repo() + absl_repo() + rules_license_repo() + re2_repo() diff --git a/java/runfiles/BUILD b/java/runfiles/BUILD new file mode 100644 index 00000000..ef6b4d03 --- /dev/null +++ b/java/runfiles/BUILD @@ -0,0 +1,15 @@ +package(default_applicable_licenses = ["@rules_java//:license"]) + +alias( + name = "runfiles", + actual = "//java/runfiles/src/main/java/com/google/devtools/build/runfiles", + visibility = ["//visibility:public"], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]) + [ + "//java/runfiles/src/main/java/com/google/devtools/build/runfiles:srcs", + ], + visibility = ["//java:__pkg__"], +) diff --git a/java/runfiles/src/main/java/com/google/devtools/build/runfiles/AutoBazelRepository.java b/java/runfiles/src/main/java/com/google/devtools/build/runfiles/AutoBazelRepository.java new file mode 100644 index 00000000..6dc53300 --- /dev/null +++ b/java/runfiles/src/main/java/com/google/devtools/build/runfiles/AutoBazelRepository.java @@ -0,0 +1,29 @@ +// Copyright 2022 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.runfiles; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotating a class {@code Fooer} with this annotation generates a class {@code + * AutoBazelRepository_Fooer} defining a {@link String} constant {@code NAME} containing the + * canonical name of the repository containing the Bazel target that compiled the annotated class. + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface AutoBazelRepository {} diff --git a/java/runfiles/src/main/java/com/google/devtools/build/runfiles/AutoBazelRepositoryProcessor.java b/java/runfiles/src/main/java/com/google/devtools/build/runfiles/AutoBazelRepositoryProcessor.java new file mode 100644 index 00000000..2b0ce9d2 --- /dev/null +++ b/java/runfiles/src/main/java/com/google/devtools/build/runfiles/AutoBazelRepositoryProcessor.java @@ -0,0 +1,121 @@ +// Copyright 2022 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.runfiles; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedOptions; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic.Kind; + +/** Processor for {@link AutoBazelRepository}. */ +@SupportedAnnotationTypes("com.google.devtools.build.runfiles.AutoBazelRepository") +@SupportedOptions(AutoBazelRepositoryProcessor.BAZEL_REPOSITORY_OPTION) +public final class AutoBazelRepositoryProcessor extends AbstractProcessor { + + static final String BAZEL_REPOSITORY_OPTION = "bazel.repository"; + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + annotations.stream() + .flatMap(element -> roundEnv.getElementsAnnotatedWith(element).stream()) + .map(element -> (TypeElement) element) + .forEach(this::emitClass); + return false; + } + + private void emitClass(TypeElement annotatedClass) { + // This option is always provided by the Java rule implementations. + if (!processingEnv.getOptions().containsKey(BAZEL_REPOSITORY_OPTION)) { + processingEnv + .getMessager() + .printMessage( + Kind.ERROR, + String.format( + "The %1$s annotation processor option is not set. To use this annotation" + + " processor, provide the canonical repository name of the current target as" + + " the value of the -A%1$s flag.", + BAZEL_REPOSITORY_OPTION), + annotatedClass); + return; + } + String repositoryName = processingEnv.getOptions().get(BAZEL_REPOSITORY_OPTION); + if (repositoryName == null) { + // javac translates '-Abazel.repository=' into a null value. + // https://github.com/openjdk/jdk/blob/7a49c9baa1d4ad7df90e7ca626ec48ba76881822/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java#L651 + repositoryName = ""; + } + + // For a nested class Outer.Middle.Inner, generate a class with simple name + // AutoBazelRepository_Outer_Middle_Inner. + // Note: There can be collisions when local classes are involved, but since the definition of a + // class depends only on the containing Bazel target, this does not result in ambiguity. + Deque classNameSegments = new ArrayDeque<>(); + Element element = annotatedClass; + while (element instanceof TypeElement) { + classNameSegments.addFirst(element.getSimpleName().toString()); + element = element.getEnclosingElement(); + } + classNameSegments.addFirst("AutoBazelRepository"); + String generatedClassSimpleName = String.join("_", classNameSegments); + + String generatedClassPackage = + processingEnv.getElementUtils().getPackageOf(annotatedClass).getQualifiedName().toString(); + + String generatedClassName = + generatedClassPackage.isEmpty() + ? generatedClassSimpleName + : generatedClassPackage + "." + generatedClassSimpleName; + + try (PrintWriter out = + new PrintWriter( + processingEnv.getFiler().createSourceFile(generatedClassName).openWriter())) { + if (!generatedClassPackage.isEmpty()) { + // This annotation may exist on a class which is at the root package + out.printf("package %s;\n", generatedClassPackage); + } + out.printf("\n"); + out.printf("class %s {\n", generatedClassSimpleName); + out.printf(" /**\n"); + out.printf(" * The canonical name of the repository containing the Bazel target that\n"); + out.printf(" * compiled {@link %s}.\n", annotatedClass.getQualifiedName().toString()); + out.printf(" */\n"); + out.printf(" static final String NAME = \"%s\";\n", repositoryName); + out.printf("\n"); + out.printf(" private %s() {}\n", generatedClassSimpleName); + out.printf("}\n"); + } catch (IOException e) { + processingEnv + .getMessager() + .printMessage( + Kind.ERROR, + String.format("Failed to generate %s: %s", generatedClassName, e.getMessage()), + annotatedClass); + } + } +} diff --git a/java/runfiles/src/main/java/com/google/devtools/build/runfiles/BUILD b/java/runfiles/src/main/java/com/google/devtools/build/runfiles/BUILD new file mode 100644 index 00000000..11b8ae0f --- /dev/null +++ b/java/runfiles/src/main/java/com/google/devtools/build/runfiles/BUILD @@ -0,0 +1,32 @@ +load("//java:java_library.bzl", "java_library") +load("//java:java_plugin.bzl", "java_plugin") + +package(default_applicable_licenses = ["@rules_java//:license"]) + +java_library( + name = "runfiles", + srcs = [ + "Runfiles.java", + "Util.java", + ], + exported_plugins = [":auto_bazel_repository_processor"], + visibility = ["//java/runfiles:__pkg__"], + exports = [":auto_bazel_repository"], +) + +java_library( + name = "auto_bazel_repository", + srcs = ["AutoBazelRepository.java"], +) + +java_plugin( + name = "auto_bazel_repository_processor", + srcs = ["AutoBazelRepositoryProcessor.java"], + processor_class = "com.google.devtools.build.runfiles.AutoBazelRepositoryProcessor", +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//java/runfiles:__pkg__"], +) diff --git a/java/runfiles/src/main/java/com/google/devtools/build/runfiles/Runfiles.java b/java/runfiles/src/main/java/com/google/devtools/build/runfiles/Runfiles.java new file mode 100644 index 00000000..06221b76 --- /dev/null +++ b/java/runfiles/src/main/java/com/google/devtools/build/runfiles/Runfiles.java @@ -0,0 +1,613 @@ +// Copyright 2018 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.runfiles; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.ref.SoftReference; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.TreeMap; + +/** + * Runfiles lookup library for Bazel-built Java binaries and tests. + * + *

USAGE: + * + *

1. Depend on this runfiles library from your build rule: + * + *

+ *   java_binary(
+ *       name = "my_binary",
+ *       ...
+ *       deps = ["@rules_java//java/runfiles"],
+ *   )
+ * 
+ * + *

2. Import the runfiles library. + * + *

+ *   import com.google.devtools.build.runfiles.Runfiles;
+ * 
+ * + *

3. Create a {@link Runfiles.Preloaded} object: + * + *

+ *   public void myFunction() {
+ *     Runfiles.Preloaded runfiles = Runfiles.preload();
+ *     ...
+ * 
+ * + *

4. To look up a runfile, use either of the following approaches: + * + *

4a. Annotate the class from which runfiles should be looked up with {@link + * AutoBazelRepository} and obtain the name of the Bazel + * repository containing the class from a constant generated by this annotation: + * + *

+ *   import com.google.devtools.build.runfiles.AutoBazelRepository;
+ *   @AutoBazelRepository
+ *   public class MyClass {
+ *     public void myFunction() {
+ *       Runfiles.Preloaded runfiles = Runfiles.preload();
+ *       String path = runfiles.withSourceRepository(AutoBazelRepository_MyClass.NAME)
+ *                             .rlocation("my_workspace/path/to/my/data.txt");
+ *       ...
+ *
+ * 
+ * + *

4b. Let Bazel compute the path passed to rlocation and pass it into a java_binary + * via an argument or an environment variable: + * + *

+ *   java_binary(
+ *       name = "my_binary",
+ *       srcs = ["MyClass.java"],
+ *       data = ["@my_workspace//path/to/my:data.txt"],
+ *       env = {"MY_RUNFILE": "$(rlocationpath @my_workspace//path/to/my:data.txt)"},
+ *   )
+ * 
+ * + *
+ *   public class MyClass {
+ *     public void myFunction() {
+ *       Runfiles.Preloaded runfiles = Runfiles.preload();
+ *       String path = runfiles.unmapped().rlocation(System.getenv("MY_RUNFILE"));
+ *       ...
+ *
+ * 
+ * + *

For more details on why it is required to pass in the current repository name, see {@see + * https://bazel.build/build/bzlmod#repository-names}. + * + *

Subprocesses

+ * + *

If you want to start subprocesses that also need runfiles, you need to set the right + * environment variables for them: + * + *

+ *   String path = r.rlocation("path/to/binary");
+ *   ProcessBuilder pb = new ProcessBuilder(path);
+ *   pb.environment().putAll(r.getEnvVars());
+ *   ...
+ *   Process p = pb.start();
+ * 
+ * + *

{@link Runfiles.Preloaded} vs. {@link Runfiles}

+ * + *

Instances of {@link Runfiles.Preloaded} are meant to be stored and passed around to other + * components that need to access runfiles. They are created by calling {@link Runfiles#preload()} + * {@link Runfiles#preload(java.util.Map)} and immutably encapsulate all data required to look up + * runfiles with the repository mapping of any Bazel repository specified at a later time. + * + *

Creating {@link Runfiles.Preloaded} instances can be costly, so applications should try to + * create as few instances as possible. {@link Runfiles#preload()}, but not {@link + * Runfiles#preload(java.util.Map)}, returns a single global, softly cached instance of {@link + * Runfiles.Preloaded} that is constructed based on the JVM's environment variables. + * + *

Instance of {@link Runfiles} are only meant to be used by code located in a single Bazel + * repository and should not be passed around. They are created by calling {@link + * Runfiles.Preloaded#withSourceRepository(String)} or {@link Runfiles.Preloaded#unmapped()} and in + * addition to the data in {@link Runfiles.Preloaded} also fix a source repository relative to which + * apparent repository names are resolved. + * + *

Creating {@link Runfiles.Preloaded} instances is cheap. + */ +public final class Runfiles { + + /** + * A class that encapsulates all data required to look up runfiles relative to any Bazel + * repository fixed at a later time. + * + *

This class is immutable. + */ + public abstract static class Preloaded { + + /** See {@link com.google.devtools.build.lib.analysis.RepoMappingManifestAction.Entry}. */ + static class RepoMappingKey { + + public final String sourceRepo; + public final String targetRepoApparentName; + + public RepoMappingKey(String sourceRepo, String targetRepoApparentName) { + this.sourceRepo = sourceRepo; + this.targetRepoApparentName = targetRepoApparentName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || !(o instanceof RepoMappingKey)) { + return false; + } + RepoMappingKey that = (RepoMappingKey) o; + return sourceRepo.equals(that.sourceRepo) + && targetRepoApparentName.equals(that.targetRepoApparentName); + } + + @Override + public int hashCode() { + return Objects.hash(sourceRepo, targetRepoApparentName); + } + } + + /** + * Returns a {@link Runfiles} instance that uses the provided source repository's repository + * mapping to translate apparent into canonical repository names. + * + *

{@see https://bazel.build/build/bzlmod#repository-names} + * + * @param sourceRepository the canonical name of the Bazel repository relative to which apparent + * repository names should be resolved. Should generally coincide with the Bazel repository + * that contains the caller of this method, which can be obtained via {@link + * AutoBazelRepository}. + * @return a {@link Runfiles} instance that looks up runfiles relative to the provided source + * repository and shares all other data with this {@link Runfiles.Preloaded} instance. + */ + public final Runfiles withSourceRepository(String sourceRepository) { + Util.checkArgument(sourceRepository != null); + return new Runfiles(this, sourceRepository); + } + + /** + * Returns a {@link Runfiles} instance backed by the preloaded runfiles data that can be used to + * look up runfiles paths with canonical repository names only. + * + * @return a {@link Runfiles} instance that can only look up paths with canonical repository + * names and shared all data with this {@link Runfiles.Preloaded} instance. + */ + public final Runfiles unmapped() { + return new Runfiles(this, null); + } + + protected abstract Map getEnvVars(); + + protected abstract String rlocationChecked(String path); + + protected abstract RepositoryMapping getRepoMapping(); + + // Private constructor, so only nested classes may extend it. + private Preloaded() {} + } + + private static final String MAIN_REPOSITORY = ""; + + private static SoftReference defaultInstance = new SoftReference<>(null); + + private final Preloaded preloadedRunfiles; + private final String sourceRepository; + + private Runfiles(Preloaded preloadedRunfiles, String sourceRepository) { + this.preloadedRunfiles = preloadedRunfiles; + this.sourceRepository = sourceRepository; + } + + /** + * Returns the softly cached global {@link Runfiles.Preloaded} instance, creating it if needed. + * + *

This method passes the JVM's environment variable map to {@link #create(java.util.Map)}. + */ + public static synchronized Preloaded preload() throws IOException { + Preloaded instance = defaultInstance.get(); + if (instance != null) { + return instance; + } + instance = preload(System.getenv()); + defaultInstance = new SoftReference<>(instance); + return instance; + } + + /** + * Returns a new {@link Runfiles.Preloaded} instance. + * + *

The returned object is either: + * + *

    + *
  • manifest-based, meaning it looks up runfile paths from a manifest file, or + *
  • directory-based, meaning it looks up runfile paths under a given directory path + *
+ * + *

If {@code env} contains "RUNFILES_MANIFEST_ONLY" with value "1", this method returns a + * manifest-based implementation. The manifest's path is defined by the "RUNFILES_MANIFEST_FILE" + * key's value in {@code env}. + * + *

Otherwise this method returns a directory-based implementation. The directory's path is + * defined by the value in {@code env} under the "RUNFILES_DIR" key, or if absent, then under the + * "JAVA_RUNFILES" key. + * + *

Note about performance: the manifest-based implementation eagerly reads and caches the whole + * manifest file upon instantiation. + * + * @throws java.io.IOException if RUNFILES_MANIFEST_ONLY=1 is in {@code env} but there's no + * "RUNFILES_MANIFEST_FILE", "RUNFILES_DIR", or "JAVA_RUNFILES" key in {@code env} or their + * values are empty, or some IO error occurs + */ + public static Preloaded preload(Map env) throws IOException { + if (isManifestOnly(env)) { + // On Windows, Bazel sets RUNFILES_MANIFEST_ONLY=1. + // On every platform, Bazel also sets RUNFILES_MANIFEST_FILE, but on Linux and macOS it's + // faster to use RUNFILES_DIR. + return new ManifestBased(getManifestPath(env)); + } else { + return new DirectoryBased(getRunfilesDir(env)); + } + } + + /** + * Returns a new {@link Runfiles} instance. + * + *

This method passes the JVM's environment variable map to {@link #create(java.util.Map)}. + * + * @deprecated Use {@link #preload()} instead. With {@code --enable_bzlmod}, this function does + * not work correctly. + */ + @Deprecated + public static Runfiles create() throws IOException { + return preload().withSourceRepository(MAIN_REPOSITORY); + } + + /** + * Returns a new {@link Runfiles} instance. + * + *

The returned object is either: + * + *

    + *
  • manifest-based, meaning it looks up runfile paths from a manifest file, or + *
  • directory-based, meaning it looks up runfile paths under a given directory path + *
+ * + *

If {@code env} contains "RUNFILES_MANIFEST_ONLY" with value "1", this method returns a + * manifest-based implementation. The manifest's path is defined by the "RUNFILES_MANIFEST_FILE" + * key's value in {@code env}. + * + *

Otherwise this method returns a directory-based implementation. The directory's path is + * defined by the value in {@code env} under the "RUNFILES_DIR" key, or if absent, then under the + * "JAVA_RUNFILES" key. + * + *

Note about performance: the manifest-based implementation eagerly reads and caches the whole + * manifest file upon instantiation. + * + * @throws IOException if RUNFILES_MANIFEST_ONLY=1 is in {@code env} but there's no + * "RUNFILES_MANIFEST_FILE", "RUNFILES_DIR", or "JAVA_RUNFILES" key in {@code env} or their + * values are empty, or some IO error occurs + * @deprecated Use {@link #preload(java.util.Map)} instead. With {@code --enable_bzlmod}, this + * function does not work correctly. + */ + @Deprecated + public static Runfiles create(Map env) throws IOException { + return preload(env).withSourceRepository(MAIN_REPOSITORY); + } + + /** + * Returns the runtime path of a runfile (a Bazel-built binary's/test's data-dependency). + * + *

The returned path may not be valid. The caller should check the path's validity and that the + * path exists. + * + *

The function may return null. In that case the caller can be sure that the rule does not + * know about this data-dependency. + * + * @param path runfiles-root-relative path of the runfile + * @throws IllegalArgumentException if {@code path} fails validation, for example if it's null or + * empty, or not normalized (contains "./", "../", or "//") + */ + public String rlocation(String path) { + Util.checkArgument(path != null); + Util.checkArgument(!path.isEmpty()); + Util.checkArgument( + !path.startsWith("../") + && !path.contains("/..") + && !path.startsWith("./") + && !path.contains("/./") + && !path.endsWith("/.") + && !path.contains("//"), + "path is not normalized: \"%s\"", + path); + Util.checkArgument( + !path.startsWith("\\"), "path is absolute without a drive letter: \"%s\"", path); + if (new File(path).isAbsolute()) { + return path; + } + + if (sourceRepository == null) { + return preloadedRunfiles.rlocationChecked(path); + } + String[] apparentTargetAndRemainder = path.split("/", 2); + if (apparentTargetAndRemainder.length < 2) { + return preloadedRunfiles.rlocationChecked(path); + } + String targetCanonical = getCanonicalRepositoryName(apparentTargetAndRemainder[0]); + return preloadedRunfiles.rlocationChecked( + targetCanonical + "/" + apparentTargetAndRemainder[1]); + } + + /** + * Returns environment variables for subprocesses. + * + *

The caller should add the returned key-value pairs to the environment of subprocesses in + * case those subprocesses are also Bazel-built binaries that need to use runfiles. + */ + public Map getEnvVars() { + return preloadedRunfiles.getEnvVars(); + } + + String getCanonicalRepositoryName(String apparentRepositoryName) { + return preloadedRunfiles + .getRepoMapping() + .getOrDefault( + new Preloaded.RepoMappingKey(sourceRepository, apparentRepositoryName), + apparentRepositoryName); + } + + /** Returns true if the platform supports runfiles only via manifests. */ + private static boolean isManifestOnly(Map env) { + return "1".equals(env.get("RUNFILES_MANIFEST_ONLY")); + } + + private static String getManifestPath(Map env) throws IOException { + String value = env.get("RUNFILES_MANIFEST_FILE"); + if (Util.isNullOrEmpty(value)) { + throw new IOException( + "Cannot load runfiles manifest: $RUNFILES_MANIFEST_ONLY is 1 but" + + " $RUNFILES_MANIFEST_FILE is empty or undefined"); + } + return value; + } + + private static String getRunfilesDir(Map env) throws IOException { + String value = env.get("RUNFILES_DIR"); + if (Util.isNullOrEmpty(value)) { + value = env.get("JAVA_RUNFILES"); + } + if (Util.isNullOrEmpty(value)) { + throw new IOException( + "Cannot find runfiles: $RUNFILES_DIR and $JAVA_RUNFILES are both unset or empty"); + } + return value; + } + + /** {@link Runfiles} implementation that parses a runfiles-manifest file to look up runfiles. */ + private static final class ManifestBased extends Preloaded { + + private final Map runfiles; + private final String manifestPath; + private final RepositoryMapping repoMapping; + + ManifestBased(String manifestPath) throws IOException { + Util.checkArgument(manifestPath != null); + Util.checkArgument(!manifestPath.isEmpty()); + this.manifestPath = manifestPath; + this.runfiles = loadRunfiles(manifestPath); + this.repoMapping = RepositoryMapping.readFromFile(rlocationChecked("_repo_mapping")); + } + + @Override + protected String rlocationChecked(String path) { + String exactMatch = runfiles.get(path); + if (exactMatch != null) { + return exactMatch; + } + // If path references a runfile that lies under a directory that itself is a runfile, then + // only the directory is listed in the manifest. Look up all prefixes of path in the manifest + // and append the relative path from the prefix if there is a match. + int prefixEnd = path.length(); + while ((prefixEnd = path.lastIndexOf('/', prefixEnd - 1)) != -1) { + String prefixMatch = runfiles.get(path.substring(0, prefixEnd)); + if (prefixMatch != null) { + return prefixMatch + '/' + path.substring(prefixEnd + 1); + } + } + return null; + } + + @Override + protected Map getEnvVars() { + HashMap result = new HashMap<>(4); + result.put("RUNFILES_MANIFEST_ONLY", "1"); + result.put("RUNFILES_MANIFEST_FILE", manifestPath); + String runfilesDir = findRunfilesDir(manifestPath); + result.put("RUNFILES_DIR", runfilesDir); + // TODO(laszlocsomor): remove JAVA_RUNFILES once the Java launcher can pick up RUNFILES_DIR. + result.put("JAVA_RUNFILES", runfilesDir); + return result; + } + + @Override + protected RepositoryMapping getRepoMapping() { + return repoMapping; + } + + private static Map loadRunfiles(String path) throws IOException { + HashMap result = new HashMap<>(); + try (BufferedReader r = + new BufferedReader( + new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))) { + String line; + while ((line = r.readLine()) != null) { + String runfile; + String realPath; + if (line.startsWith(" ")) { + // In lines starting with a space, the runfile path contains spaces and backslashes + // escaped with a backslash. The real path is the rest of the line after the first + // unescaped space. + int firstSpace = line.indexOf(' ', 1); + if (firstSpace == -1) { + throw new IOException( + "Invalid runfiles manifest line, expected at least one space after the leading" + + " space: " + + line); + } + runfile = + line.substring(1, firstSpace) + .replace("\\s", " ") + .replace("\\n", "\n") + .replace("\\b", "\\"); + realPath = line.substring(firstSpace + 1).replace("\\n", "\n").replace("\\b", "\\"); + } else { + int firstSpace = line.indexOf(' '); + if (firstSpace == -1) { + throw new IOException( + "Invalid runfiles manifest line, expected at least one space: " + line); + } + runfile = line.substring(0, firstSpace); + realPath = line.substring(firstSpace + 1); + } + result.put(runfile, realPath); + } + } + return Collections.unmodifiableMap(result); + } + + private static String findRunfilesDir(String manifest) { + if (manifest.endsWith("/MANIFEST") + || manifest.endsWith("\\MANIFEST") + || manifest.endsWith(".runfiles_manifest")) { + String path = manifest.substring(0, manifest.length() - 9); + if (new File(path).isDirectory()) { + return path; + } + } + return ""; + } + } + + /** {@link Runfiles} implementation that appends runfiles paths to the runfiles root. */ + private static final class DirectoryBased extends Preloaded { + + private final String runfilesRoot; + private final RepositoryMapping repoMapping; + + DirectoryBased(String runfilesDir) throws IOException { + Util.checkArgument(!Util.isNullOrEmpty(runfilesDir)); + Util.checkArgument(new File(runfilesDir).isDirectory()); + this.runfilesRoot = runfilesDir; + this.repoMapping = RepositoryMapping.readFromFile(rlocationChecked("_repo_mapping")); + } + + @Override + protected String rlocationChecked(String path) { + return runfilesRoot + "/" + path; + } + + @Override + protected RepositoryMapping getRepoMapping() { + return repoMapping; + } + + @Override + protected Map getEnvVars() { + HashMap result = new HashMap<>(2); + result.put("RUNFILES_DIR", runfilesRoot); + // TODO(laszlocsomor): remove JAVA_RUNFILES once the Java launcher can pick up RUNFILES_DIR. + result.put("JAVA_RUNFILES", runfilesRoot); + return result; + } + } + + static Preloaded createManifestBasedForTesting(String manifestPath) throws IOException { + return new ManifestBased(manifestPath); + } + + static Preloaded createDirectoryBasedForTesting(String runfilesDir) throws IOException { + return new DirectoryBased(runfilesDir); + } + + static final class RepositoryMapping { + private final Map exactMappings; + private final NavigableMap> wildcardMappings; + + private RepositoryMapping( + Map exactMappings, + NavigableMap> wildcardMappings) { + this.exactMappings = exactMappings; + this.wildcardMappings = wildcardMappings; + } + + static RepositoryMapping readFromFile(String path) throws IOException { + if (path == null || !new File(path).exists()) { + return new RepositoryMapping(Collections.emptyMap(), Collections.emptyNavigableMap()); + } + + try (BufferedReader r = + new BufferedReader( + new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))) { + Map exactMappings = new HashMap<>(); + NavigableMap> wildcardMappings = new TreeMap<>(); + String line; + while ((line = r.readLine()) != null) { + if (line.isEmpty()) { + continue; + } + String[] split = line.split(","); + if (split.length != 3) { + throw new IllegalArgumentException( + "Invalid line in repository mapping: '" + line + "'"); + } + if (split[0].endsWith("*")) { + Map targetMap = + wildcardMappings.computeIfAbsent( + split[0].substring(0, split[0].length() - 1), k -> new HashMap<>()); + targetMap.put(split[1], split[2]); + } else { + exactMappings.put(new Preloaded.RepoMappingKey(split[0], split[1]), split[2]); + } + } + return new RepositoryMapping(exactMappings, wildcardMappings); + } + } + + String getOrDefault(Preloaded.RepoMappingKey key, String defaultValue) { + String exactMatch = exactMappings.get(key); + if (exactMatch != null) { + return exactMatch; + } + Map.Entry> floorEntry = + wildcardMappings.floorEntry(key.sourceRepo); + if (floorEntry == null || !key.sourceRepo.startsWith(floorEntry.getKey())) { + return defaultValue; + } + return floorEntry.getValue().getOrDefault(key.targetRepoApparentName, defaultValue); + } + } +} diff --git a/java/runfiles/src/main/java/com/google/devtools/build/runfiles/Util.java b/java/runfiles/src/main/java/com/google/devtools/build/runfiles/Util.java new file mode 100644 index 00000000..73f0b98e --- /dev/null +++ b/java/runfiles/src/main/java/com/google/devtools/build/runfiles/Util.java @@ -0,0 +1,49 @@ +// Copyright 2018 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.runfiles; + +/** + * Utilities for the other classes in this package. + * + *

These functions are implementations of some basic utilities in the Guava library. We + * reimplement these functions instead of depending on Guava, so that the Runfiles library has no + * third-party dependencies, thus any Java project can depend on it without the risk of pulling + * unwanted or conflicting dependencies (for example if the project already depends on Guava, or + * wishes not to depend on it at all). + */ +class Util { + private Util() {} + + /** Returns true when {@code s} is null or an empty string. */ + public static boolean isNullOrEmpty(String s) { + return s == null || s.isEmpty(); + } + + /** Throws an {@code IllegalArgumentException} if {@code condition} is false. */ + public static void checkArgument(boolean condition) { + checkArgument(condition, null, null); + } + + /** Throws an {@code IllegalArgumentException} if {@code condition} is false. */ + public static void checkArgument(boolean condition, String error, Object arg1) { + if (!condition) { + if (isNullOrEmpty(error)) { + throw new IllegalArgumentException("argument validation failed"); + } else { + throw new IllegalArgumentException(String.format(error, arg1)); + } + } + } +} diff --git a/java/testutil.bzl b/java/testutil.bzl new file mode 100644 index 00000000..4063ea6f --- /dev/null +++ b/java/testutil.bzl @@ -0,0 +1,18 @@ +"""Exposes some private APIs for tests""" + +# copybara: rules_java visibility + +# TODO: consider eventually upstreaming to rules_cc + +def _cc_info_transitive_native_libraries(cc_info): + if hasattr(cc_info, "_legacy_transitive_native_libraries"): + return cc_info._legacy_transitive_native_libraries + return cc_info.transitive_native_libraries() + +def _cc_library_to_link_static_library(library_to_link): + return library_to_link.static_library or library_to_link.pic_static_library + +testutil = struct( + cc_info_transitive_native_libraries = _cc_info_transitive_native_libraries, + cc_library_to_link_static_library = _cc_library_to_link_static_library, +) diff --git a/java/toolchains/BUILD b/java/toolchains/BUILD index 894cf444..2cb68270 100644 --- a/java/toolchains/BUILD +++ b/java/toolchains/BUILD @@ -1,6 +1,9 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") -package(default_visibility = ["//visibility:public"]) +package( + default_applicable_licenses = ["@rules_java//:license"], + default_visibility = ["//visibility:public"], +) licenses(["notice"]) @@ -14,7 +17,7 @@ bzl_library( name = "toolchain_rules", srcs = glob(["*.bzl"]), visibility = ["//visibility:public"], - deps = ["//java/private"], + deps = ["@compatibility_proxy//:proxy_bzl"], ) filegroup( diff --git a/java/toolchains/java_package_configuration.bzl b/java/toolchains/java_package_configuration.bzl index 09d8e1e9..7ef0728b 100644 --- a/java/toolchains/java_package_configuration.bzl +++ b/java/toolchains/java_package_configuration.bzl @@ -13,6 +13,8 @@ # limitations under the License. """java_package_configuration rule""" +load("@compatibility_proxy//:proxy.bzl", _java_package_configuration = "java_package_configuration") + def java_package_configuration(**attrs): """Bazel java_package_configuration rule. @@ -22,5 +24,4 @@ def java_package_configuration(**attrs): **attrs: Rule attributes """ - # buildifier: disable=native-java - native.java_package_configuration(**attrs) + _java_package_configuration(**attrs) diff --git a/java/toolchains/java_runtime.bzl b/java/toolchains/java_runtime.bzl index 3657a88d..c1d16411 100644 --- a/java/toolchains/java_runtime.bzl +++ b/java/toolchains/java_runtime.bzl @@ -13,6 +13,8 @@ # limitations under the License. """java_runtime rule""" +load("@compatibility_proxy//:proxy.bzl", _java_runtime = "java_runtime") + def java_runtime(**attrs): """Bazel java_runtime rule. @@ -22,5 +24,4 @@ def java_runtime(**attrs): **attrs: Rule attributes """ - # buildifier: disable=native-java - native.java_runtime(**attrs) + _java_runtime(**attrs) diff --git a/java/toolchains/java_toolchain.bzl b/java/toolchains/java_toolchain.bzl index 5b072928..5207f8c9 100644 --- a/java/toolchains/java_toolchain.bzl +++ b/java/toolchains/java_toolchain.bzl @@ -13,6 +13,8 @@ # limitations under the License. """java_toolchain rule""" +load("@compatibility_proxy//:proxy.bzl", _java_toolchain = "java_toolchain") + def java_toolchain(**attrs): """Bazel java_toolchain rule. @@ -22,5 +24,4 @@ def java_toolchain(**attrs): **attrs: Rule attributes """ - # buildifier: disable=native-java - native.java_toolchain(**attrs) + _java_toolchain(**attrs) diff --git a/test/BUILD.bazel b/test/BUILD.bazel index 10919e52..fdc241fb 100644 --- a/test/BUILD.bazel +++ b/test/BUILD.bazel @@ -11,9 +11,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +load("@bazel_skylib//rules:diff_test.bzl", "diff_test") +load("@rules_shell//shell:sh_test.bzl", "sh_test") load("//java:repositories.bzl", "JAVA_TOOLS_CONFIG", "REMOTE_JDK_CONFIGS") load(":check_remotejdk_configs_match.bzl", "validate_configs") +package(default_applicable_licenses = ["@rules_java//:license"]) + sh_test( name = "check_remote_jdk_configs_test", srcs = ["check_remote_jdk_configs.sh"], @@ -28,6 +33,7 @@ sh_test( for configs in REMOTE_JDK_CONFIGS.values() for config in configs ], + tags = ["manual"], # explicitly tested only on Linux ) sh_test( @@ -42,6 +48,44 @@ sh_test( ]) for name, config in JAVA_TOOLS_CONFIG["artifacts"].items() ], + tags = ["manual"], # explicitly tested only on Linux +) + +sh_test( + name = "check_release_notes_test", + srcs = ["check_release_notes_test.sh"], + args = [ + "$(location //distro:relnotes.txt)", + module_version(), + ], + data = ["//distro:relnotes.txt"], + tags = ["manual"], # explicitly tested only on Linux +) + +diff_test( + name = "docs_up_to_date_test", + failure_message = """ + Docs are no longer up to date. Regenerate them by running: + + bazel build //java/docs:rules_docs && \ + cp bazel-bin/java/docs/rules_docs.out java/docs/rules.md + """, + file1 = "//java/docs:rules.md", + file2 = "//java/docs:rules_docs", + # TODO: b/369123329 - re-enable docs after moving them out of https://bazel.build/reference/be/java + tags = ["manual"], ) validate_configs() + +starlark_doc_extract( + name = "proxy_bzl_graph", + src = "@compatibility_proxy//:proxy.bzl", + deps = ["@compatibility_proxy//:proxy_bzl"], +) + +starlark_doc_extract( + name = "java_single_jar_bzl_graph", + src = "//java:java_single_jar.bzl", + deps = ["//java:java_single_jar"], +) diff --git a/test/check_release_notes_test.sh b/test/check_release_notes_test.sh new file mode 100755 index 00000000..86f0288d --- /dev/null +++ b/test/check_release_notes_test.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Copyright 2025 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RELNOTES_FILE=$1 +VERSION=$2 + +function fail() { + echo "ERROR: ${1}" + echo "Release notes content:" + echo "--------------------------------------" + cat ${RELNOTES_FILE} + echo "--------------------------------------" + exit 1 +} + +echo "Checking generated release notes: ${RELNOTES_FILE}" + +grep -q '**Changes since fake-tag-for-tests**' ${RELNOTES_FILE} || fail "No changelog header" +grep -q 'Fake commit message for testing' ${RELNOTES_FILE} || fail "No changelog commit" +grep -q '**MODULE.bazel setup**' ${RELNOTES_FILE} || fail "No bzlmod setup header" +grep -q "bazel_dep(name = \"rules_java\", version = \"${VERSION}\")" ${RELNOTES_FILE} || fail "No bzlmod dep stanza" +grep -q '**WORKSPACE setup**' ${RELNOTES_FILE} || fail "No WORKSPACE setup header" +grep -q '**Using the rules**' ${RELNOTES_FILE} || fail "No using the rules header" diff --git a/test/check_remote_java_tools_configs.sh b/test/check_remote_java_tools_configs.sh index 7e69dc00..957aa788 100755 --- a/test/check_remote_java_tools_configs.sh +++ b/test/check_remote_java_tools_configs.sh @@ -22,7 +22,11 @@ function download_and_check_hash() { TMP_FILE=$(mktemp -q /tmp/remotejavatools.XXXXXX) echo "fetching $name from $url to ${TMP_FILE}" curl --silent -o ${TMP_FILE} -L "$url" - actual_hash=`sha256sum ${TMP_FILE} | cut -d' ' -f1` + if command -v sha256sum &> /dev/null; then + actual_hash=`sha256sum ${TMP_FILE} | cut -d' ' -f1` + else + actual_hash=`shasum -a 256 ${TMP_FILE} | cut -d' ' -f1` + fi if [ "${hash}" != "${actual_hash}" ]; then echo "ERROR: wrong hash for ${name}! wanted: ${hash}, got: ${actual_hash}" exit 1 diff --git a/test/check_remote_jdk_configs.sh b/test/check_remote_jdk_configs.sh index ab42ba11..b1c509fe 100755 --- a/test/check_remote_jdk_configs.sh +++ b/test/check_remote_jdk_configs.sh @@ -21,15 +21,15 @@ for config in "$@"; do IFS=, read -r name url mirror_url hash strip_prefix <<< "${config}" echo "fetching $name from $url to ${TMP_FILE}" curl --silent -o ${TMP_FILE} -L "$url" - actual_hash=`sha256sum ${TMP_FILE} | cut -d' ' -f1` + actual_hash=$(sha256sum ${TMP_FILE} | cut -d' ' -f1) if [ "${hash}" != "${actual_hash}" ]; then echo "ERROR: wrong hash for ${name}! wanted: ${hash}, got: ${actual_hash}" exit 1 fi if [[ -z "${url##*.tar.gz}" ]]; then - root_dir=`tar ztf ${TMP_FILE} --exclude='*/*'` + root_dir=$(tar ztf ${TMP_FILE} --exclude='*/*') elif [[ -z "${url##*.zip}" ]]; then - root_dir=`unzip -Z1 ${TMP_FILE} | head -n1` + root_dir=$(unzip -Z1 ${TMP_FILE} | head -n1) else echo "ERROR: unexpected archive type for ${name}" exit 1 @@ -40,12 +40,12 @@ for config in "$@"; do fi if [[ -n "${mirror_url}" ]]; then echo "checking mirror: ${mirror_url}" - curl --silent --fail -I -L ${mirror_url} > /dev/null || { _MISSING_MIRRORS+="${mirror_url}"; } + curl --silent --fail -I -L ${mirror_url} > /dev/null || { _MISSING_MIRRORS+=("${url}"); } fi done if [[ ${#_MISSING_MIRRORS[@]} -gt 0 ]]; then - echo "Missing mirror URLs:" + echo "URLs that aren't mirrored:" for m in "${_MISSING_MIRRORS[@]}"; do echo " ${m}" done diff --git a/test/java/BUILD b/test/java/BUILD new file mode 100644 index 00000000..c2afbb31 --- /dev/null +++ b/test/java/BUILD @@ -0,0 +1 @@ +package(default_applicable_licenses = ["@rules_java//:license"]) diff --git a/test/java/bazel/BUILD.bazel b/test/java/bazel/BUILD.bazel new file mode 100644 index 00000000..e69de29b diff --git a/test/java/bazel/common/BUILD.bazel b/test/java/bazel/common/BUILD.bazel new file mode 100644 index 00000000..c9cd0668 --- /dev/null +++ b/test/java/bazel/common/BUILD.bazel @@ -0,0 +1,6 @@ +load(":java_common_tests.bzl", "java_common_tests") +load(":java_info_tests.bzl", "java_info_tests") + +java_common_tests(name = "java_common_tests") + +java_info_tests(name = "java_info_tests") diff --git a/test/java/bazel/common/java_common_tests.bzl b/test/java/bazel/common/java_common_tests.bzl new file mode 100644 index 00000000..80fdf442 --- /dev/null +++ b/test/java/bazel/common/java_common_tests.bzl @@ -0,0 +1,41 @@ +"""Bazel tests for java_common APIs""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:util.bzl", "util") +load("//test/java/testutil:rules/custom_java_info_rule.bzl", "custom_java_info_rule") + +def _test_java_common_pack_sources_with_external_resource(name): + util.helper_target( + custom_java_info_rule, + name = name + "/custom", + output_jar = name + "/custom.jar", + sources = [ + ":InternalLib.java", + "@other_repo//:ExternalLib.java", + ], + pack_sources = True, + ) + + analysis_test( + name = name, + impl = _test_java_common_pack_sources_with_external_resource_impl, + target = name + "/custom", + # Bazel 7 names external repos differently + attr_values = {"tags": ["min_bazel_8"]}, + ) + +def _test_java_common_pack_sources_with_external_resource_impl(env, target): + assert_that_action = env.expect.that_target(target).action_generating("{package}/{name}-src.jar") + assert_that_action.argv().contains_at_least([ + "--resources", + "{package}/InternalLib.java:bazel/common/InternalLib.java", + "external/+test_repositories_ext+other_repo/ExternalLib.java:ExternalLib.java", + ]).in_order() + +def java_common_tests(name): + test_suite( + name = name, + tests = [ + _test_java_common_pack_sources_with_external_resource, + ], + ) diff --git a/test/java/bazel/common/java_info_tests.bzl b/test/java/bazel/common/java_info_tests.bzl new file mode 100644 index 00000000..cae1a922 --- /dev/null +++ b/test/java/bazel/common/java_info_tests.bzl @@ -0,0 +1,67 @@ +"""Tests for Bazel JavaInfo.""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_library.bzl", "java_library") +load("//test/java/testutil:java_info_subject.bzl", "java_info_subject") +load("//test/java/testutil:rules/custom_java_info_rule.bzl", "custom_java_info_rule") + +# We can't transition on a Starlark-semantics affecting flag, so this relies on +# --incompatible_java_info_merge_runtime_module_flags set in .bazelrc +def _test_create_java_info_with_module_flags_merge_runtime(name): + util.helper_target( + custom_java_info_rule, + name = name + "/my_starlark_rule", + output_jar = name + "/doesnotmatter.jar", + dep = [name + "/dep"], + dep_runtime = [name + "/runtime"], + dep_exports = [name + "/export"], + add_exports = ["java.base/java.lang.invoke"], + ) + util.helper_target( + java_library, + name = name + "/dep", + srcs = ["java/A.java"], + add_exports = ["java.base/java.lang"], + add_opens = ["java.base/java.lang"], + ) + + util.helper_target( + java_library, + name = name + "/runtime", + srcs = ["java/A.java"], + add_opens = ["java.base/java.util"], + ) + + util.helper_target( + java_library, + name = name + "/export", + srcs = ["java/A.java"], + add_opens = ["java.base/java.math"], + ) + + analysis_test( + name = name, + impl = _test_create_java_info_with_module_flags_merge_runtime_impl, + target = name + "/my_starlark_rule", + ) + +def _test_create_java_info_with_module_flags_merge_runtime_impl(env, target): + assert_module_info = java_info_subject.from_target(env, target).module_flags() + assert_module_info.add_exports().contains_exactly([ + "java.base/java.lang", + "java.base/java.lang.invoke", + ]).in_order() + assert_module_info.add_opens().contains_exactly([ + "java.base/java.util", + "java.base/java.math", + "java.base/java.lang", + ]).in_order() + +def java_info_tests(name): + test_suite( + name = name, + tests = [ + _test_create_java_info_with_module_flags_merge_runtime, + ], + ) diff --git a/test/java/bazel/rules/BUILD.bazel b/test/java/bazel/rules/BUILD.bazel new file mode 100644 index 00000000..6f97d15a --- /dev/null +++ b/test/java/bazel/rules/BUILD.bazel @@ -0,0 +1,12 @@ +load(":java_binary_tests.bzl", "java_binary_tests") +load(":java_library_tests.bzl", "java_library_tests") +load(":java_plugin_tests.bzl", "java_plugin_tests") +load(":java_test_tests.bzl", "java_test_tests") + +java_binary_tests(name = "java_binary_tests") + +java_library_tests(name = "java_library_tests") + +java_plugin_tests(name = "java_plugin_tests") + +java_test_tests(name = "java_test_tests") diff --git a/test/java/bazel/rules/java_binary_tests.bzl b/test/java/bazel/rules/java_binary_tests.bzl new file mode 100644 index 00000000..8c385046 --- /dev/null +++ b/test/java/bazel/rules/java_binary_tests.bzl @@ -0,0 +1,130 @@ +"""Tests for the Bazel java_binary rule""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_binary.bzl", "java_binary") +load("//test/java/testutil:java_info_subject.bzl", "java_info_subject") +load("//test/java/testutil:rules/template_var_info_rule.bzl", "template_var_info_rule") + +def _test_java_binary_cross_compilation_to_unix(name): + # A Unix platform that: + # - has a JDK + # - does not require a launcher + # - is not supported by the default C++ toolchain + util.helper_target( + native.platform, + name = name + "/platform", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:s390x", + ], + ) + + util.helper_target( + java_binary, + name = name + "/bin", + srcs = ["java/C.java"], + main_class = "C", + ) + + analysis_test( + name = name, + impl = _test_java_binary_cross_compilation_to_unix_impl, + target = name + "/bin", + config_settings = { + "//command_line_option:platforms": [Label(name + "/platform")], + }, + # Requires the launcher_maker toolchain. + attr_values = {"tags": ["min_bazel_9"]}, + ) + +def _test_java_binary_cross_compilation_to_unix_impl(env, target): + # The main assertion is that analysis succeeds, but verify the absence of a + # binary launcher for good measure. We do this by checking that the output + # executable is the stub script, and not a bespoke launcher + executable = target[DefaultInfo].files_to_run.executable.short_path + assert_action = env.expect.that_target(target).action_generating(executable) + assert_action.mnemonic().equals("TemplateExpand") + assert_action.substitutions().keys().contains("%jvm_flags%") + assert_action.inputs().contains_exactly(["java/bazel/rules/java_stub_template.txt"]) + +def _test_java_binary_javacopts_make_variable_expansion(name): + util.helper_target( + template_var_info_rule, + name = name + "/vars", + vars = { + "MY_CUSTOM_OPT": "MY_OPT_VALUE", + }, + ) + util.helper_target( + java_binary, + name = name + "/bin", + srcs = ["java/A.java"], + javacopts = ["$(MY_CUSTOM_OPT)"], + toolchains = [name + "/vars"], + ) + + analysis_test( + name = name, + impl = _test_java_binary_javacopts_make_variable_expansion_impl, + target = name + "/bin", + # Broken by Starlarkification in the embedded rules in Bazel 7 + attr_values = {"tags": ["min_bazel_8"]}, + ) + +def _test_java_binary_javacopts_make_variable_expansion_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + assert_java_info.compilation_info().javac_options().not_contains("$(MY_CUSTOM_OPT)") + assert_java_info.compilation_info().javac_options().contains("MY_OPT_VALUE") + +def _test_java_binary_javacopts_location_expansion(name): + util.helper_target( + java_binary, + name = name + "/bin", + srcs = ["A.java"], + javacopts = ["-XepOpt:foo=$(location :A.java)"], + ) + + analysis_test( + name = name, + impl = _test_java_binary_javacopts_location_expansion_impl, + target = name + "/bin", + ) + +def _test_java_binary_javacopts_location_expansion_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + assert_java_info.compilation_info().javac_options().contains( + "-XepOpt:foo={package}/A.java", + ) + +def _test_java_binary_resource_strip_prefix(name): + util.helper_target( + java_binary, + name = name + "/bin", + srcs = ["Foo.java"], + main_class = "Foo", + resource_strip_prefix = native.package_name() + "/path/to/strip", + resources = ["path/to/strip/bar.props"], + ) + + analysis_test( + name = name, + impl = _test_java_binary_resource_strip_prefix_impl, + target = name + "/bin", + ) + +def _test_java_binary_resource_strip_prefix_impl(env, target): + env.expect.that_target(target).action_generating("{package}/{name}.jar").contains_flag_values([ + ("--resources", "{package}/path/to/strip/bar.props:bar.props"), + ]) + +def java_binary_tests(name): + test_suite( + name = name, + tests = [ + _test_java_binary_cross_compilation_to_unix, + _test_java_binary_javacopts_make_variable_expansion, + _test_java_binary_javacopts_location_expansion, + _test_java_binary_resource_strip_prefix, + ], + ) diff --git a/test/java/bazel/rules/java_library_tests.bzl b/test/java/bazel/rules/java_library_tests.bzl new file mode 100644 index 00000000..770fbe60 --- /dev/null +++ b/test/java/bazel/rules/java_library_tests.bzl @@ -0,0 +1,44 @@ +"""Tests for the Bazel java_binary rule""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_library.bzl", "java_library") +load("//test/java/testutil:java_info_subject.bzl", "java_info_subject") +load("//test/java/testutil:rules/template_var_info_rule.bzl", "template_var_info_rule") + +def _test_java_library_javacopts_make_variable_expansion(name): + util.helper_target( + template_var_info_rule, + name = name + "/vars", + vars = { + "MY_CUSTOM_OPT": "MY_OPT_VALUE", + }, + ) + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["java/A.java"], + javacopts = ["$(MY_CUSTOM_OPT)"], + toolchains = [name + "/vars"], + ) + + analysis_test( + name = name, + impl = _test_java_library_javacopts_make_variable_expansion_impl, + target = name + "/lib", + # Broken by Starlarkification in the embedded rules in Bazel 7 + attr_values = {"tags": ["min_bazel_8"]}, + ) + +def _test_java_library_javacopts_make_variable_expansion_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + assert_java_info.compilation_info().javac_options().not_contains("$(MY_CUSTOM_OPT)") + assert_java_info.compilation_info().javac_options().contains("MY_OPT_VALUE") + +def java_library_tests(name): + test_suite( + name = name, + tests = [ + _test_java_library_javacopts_make_variable_expansion, + ], + ) diff --git a/test/java/bazel/rules/java_plugin_tests.bzl b/test/java/bazel/rules/java_plugin_tests.bzl new file mode 100644 index 00000000..4df4bcca --- /dev/null +++ b/test/java/bazel/rules/java_plugin_tests.bzl @@ -0,0 +1,43 @@ +"""Tests for the Bazel java_binary rule""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_plugin.bzl", "java_plugin") +load("//test/java/testutil:rules/template_var_info_rule.bzl", "template_var_info_rule") + +def _test_java_plugin_javacopts_make_variable_expansion(name): + util.helper_target( + template_var_info_rule, + name = name + "/vars", + vars = { + "MY_CUSTOM_OPT": "MY_OPT_VALUE", + }, + ) + util.helper_target( + java_plugin, + name = name + "/plug", + srcs = ["java/A.java"], + javacopts = ["$(MY_CUSTOM_OPT)"], + toolchains = [name + "/vars"], + ) + + analysis_test( + name = name, + impl = _test_java_plugin_javacopts_make_variable_expansion_impl, + target = name + "/plug", + # Broken by Starlarkification in the embedded rules in Bazel 7 + attr_values = {"tags": ["min_bazel_8"]}, + ) + +def _test_java_plugin_javacopts_make_variable_expansion_impl(env, target): + assert_javac = env.expect.that_target(target).action_named("Javac") + assert_javac.not_contains_arg("$(MY_CUSTOM_OPT)") + assert_javac.contains_at_least_args(["MY_OPT_VALUE"]) + +def java_plugin_tests(name): + test_suite( + name = name, + tests = [ + _test_java_plugin_javacopts_make_variable_expansion, + ], + ) diff --git a/test/java/bazel/rules/java_test_tests.bzl b/test/java/bazel/rules/java_test_tests.bzl new file mode 100644 index 00000000..5c76b0ee --- /dev/null +++ b/test/java/bazel/rules/java_test_tests.bzl @@ -0,0 +1,59 @@ +"""Tests for the Bazel java_test rule""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching", "subjects") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_test.bzl", "java_test") + +def _test_deduced_test_class(name): + util.helper_target( + java_test, + name = name + "/foo", + srcs = [name + "/Foo.java"], + ) + + analysis_test( + name = name, + impl = _test_deduced_test_class_impl, + target = name + "/foo", + ) + +def _test_deduced_test_class_impl(env, target): + executable = target[DefaultInfo].files_to_run.executable.short_path + assert_action = env.expect.that_target(target).action_generating(executable) + + if assert_action.actual.substitutions: + # TemplateExpansion action on linux/mac + assert_jvm_flags = assert_action.substitutions().get( + "%jvm_flags%", + factory = lambda v, meta: subjects.collection([v], meta), + ) + else: + # Windows + assert_jvm_flags = assert_action.argv() + assert_jvm_flags.contains_predicate( + matching.str_matches("-Dbazel.test_suite=bazel.rules.test_deduced_test_class.foo"), + ) + +# regression test for https://github.com/bazelbuild/bazel/issues/20378 +def _test_invalid_test_class_at_repo_root(name): + analysis_test( + name = name, + impl = _test_invalid_test_class_at_repo_root_impl, + target = "//:invalid_test_at_repo_root", + expect_failure = True, + ) + +def _test_invalid_test_class_at_repo_root_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("cannot determine test class."), + ) + +def java_test_tests(name): + test_suite( + name = name, + tests = [ + _test_deduced_test_class, + _test_invalid_test_class_at_repo_root, + ], + ) diff --git a/test/java/common/BUILD b/test/java/common/BUILD new file mode 100644 index 00000000..590b11a1 --- /dev/null +++ b/test/java/common/BUILD @@ -0,0 +1,11 @@ +load(":java_common_tests.bzl", "java_common_tests") +load(":java_info_tests.bzl", "java_info_tests") +load(":java_plugin_info_tests.bzl", "java_plugin_info_tests") + +package(default_applicable_licenses = ["@rules_java//:license"]) + +java_common_tests(name = "java_common_tests") + +java_info_tests(name = "java_info_tests") + +java_plugin_info_tests(name = "java_plugin_info_tests") diff --git a/test/java/common/java_common_tests.bzl b/test/java/common/java_common_tests.bzl new file mode 100644 index 00000000..756e3a42 --- /dev/null +++ b/test/java/common/java_common_tests.bzl @@ -0,0 +1,1170 @@ +"""Tests for java_common APIs""" + +load("@bazel_features//:features.bzl", "bazel_features") +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_binary.bzl", "java_binary") +load("//java:java_library.bzl", "java_library") +load("//java:java_plugin.bzl", "java_plugin") +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_info.bzl", "JavaInfo") +load("//java/common:java_plugin_info.bzl", "JavaPluginInfo") +load("//java/toolchains:java_runtime.bzl", "java_runtime") +load("//test/java/testutil:artifact_closure.bzl", "artifact_closure") +load("//test/java/testutil:java_info_subject.bzl", "java_info_subject") +load("//test/java/testutil:javac_action_subject.bzl", "javac_action_subject") +load("//test/java/testutil:rules/custom_library.bzl", "custom_library") +load("//test/java/testutil:rules/custom_library_extended_compile_jdeps.bzl", "CompileJdepsInfo", "custom_library_extended_jdeps") +load("//test/java/testutil:rules/custom_library_with_additional_inputs.bzl", "custom_library_with_additional_inputs") +load("//test/java/testutil:rules/custom_library_with_bootclasspath.bzl", "custom_bootclasspath", "custom_library_with_bootclasspath") +load("//test/java/testutil:rules/custom_library_with_custom_output_source_jar.bzl", "custom_library_with_custom_output_source_jar") +load("//test/java/testutil:rules/custom_library_with_exports.bzl", "custom_library_with_exports") +load("//test/java/testutil:rules/custom_library_with_named_outputs.bzl", "custom_library_with_named_outputs") +load("//test/java/testutil:rules/custom_library_with_sourcepaths.bzl", "custom_library_with_sourcepaths") +load("//test/java/testutil:rules/custom_library_with_strict_deps.bzl", "custom_library_with_strict_deps") +load("//test/java/testutil:rules/custom_library_with_strict_java_deps_provider.bzl", "StrictJavaDepsInfo", "custom_library_with_strict_java_deps_provider") +load("//test/java/testutil:rules/custom_library_with_wrong_java_toolchain_type.bzl", "custom_library_with_wrong_java_toolchain_type") +load("//test/java/testutil:rules/custom_library_with_wrong_plugins_type.bzl", "custom_library_with_wrong_plugins_type") +load("//test/java/testutil:rules/private_api_usage.bzl", "private_compile_api_usage", "private_merge_api_usage", "private_run_ijar_api_usage") + +def _test_compile_default_values(name): + util.helper_target(custom_library, name = name + "/custom", srcs = ["Main.java"]) + + analysis_test(name = name, impl = _test_compile_default_values_impl, target = name + "/custom") + +def _test_compile_default_values_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.compilation_args().transitive_runtime_jars().contains_exactly([ + "{}/lib{}.jar".format(target.label.package, target.label.name), + ]) + +def _test_compile_sourcepath(name): + util.helper_target( + custom_library_with_sourcepaths, + name = "custom", + srcs = ["Main.java"], + sourcepath = [":B.jar"], + ) + + analysis_test( + name = name, + impl = _test_compile_sourcepath_impl, + target = ":custom", + ) + +def _test_compile_sourcepath_impl(env, target): + assert_compile_action = env.expect.that_target(target).action_generating("{package}/libcustom.jar") + + assert_compile_action.contains_flag_values([ + ("--sourcepath", "{package}/B.jar".format(package = target.label.package)), + ]) + +def _test_compile_exports_no_sources(name): + util.helper_target(java_library, name = "jl", srcs = ["Main.java"]) + util.helper_target(custom_library_with_exports, name = "custom2", exports = [":jl"]) + + analysis_test( + name = name, + impl = _test_compile_exports_no_sources_impl, + target = ":custom2", + ) + +def _test_compile_exports_no_sources_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.compilation_args().transitive_runtime_jars().contains_exactly( + ["{package}/libjl.jar"], + ) + +def _test_compile_exports_with_sources(name): + target_name = name + "/custom" + util.helper_target( + custom_library_with_exports, + name = target_name, + srcs = ["Main.java"], + exports = [target_name + "/dep"], + output_name = "amazing", + ) + util.helper_target( + java_library, + name = target_name + "/dep", + srcs = ["Dep.java"], + ) + + analysis_test( + name = name, + impl = _test_compile_exports_with_sources_impl, + target = target_name, + ) + +def _test_compile_exports_with_sources_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.transitive_source_jars().contains_exactly([ + "{package}/{name}/amazing-src.jar", + "{package}/lib{name}/dep-src.jar", + ]) + assert_java_info.compilation_args().compile_jars().contains_exactly([ + "{package}/{name}/amazing-hjar.jar", + "{package}/lib{name}/dep-hjar.jar", + ]) + assert_java_info.compilation_args().compile_time_java_dependencies().contains_exactly([ + "{package}/{name}/amazing-hjar.jdeps", + "{package}/lib{name}/dep-hjar.jdeps", + ]) + +def _test_java_plugin_info(name): + util.helper_target(native.filegroup, name = name + "/dummy") + analysis_test( + name = name, + impl = _test_java_plugin_info_impl, + target = name + "/dummy", # analysis_test always expects a target + ) + +def _test_java_plugin_info_impl(env, _target): + env.expect.that_bool( + java_common.JavaPluginInfo == JavaPluginInfo, + "java_common.JavaPluginInfo == JavaPluginInfo", + ).equals(True) + +# Tests that extended 'compile time jdeps' are consistently updated. +def _test_compile_extend_compile_time_jdeps(name): + util.helper_target( + custom_library_extended_jdeps, + name = name + "/foo", + srcs = ["Foo.java"], + extra_jdeps = "Foo.jdeps", + ) + + analysis_test( + name = name, + impl = _test_compile_extend_compile_time_jdeps_impl, + target = name + "/foo", + ) + +def _test_compile_extend_compile_time_jdeps_impl(env, target): + before = target[CompileJdepsInfo].before.to_list() + assert_that_before = env.expect.that_collection(before) + assert_that_after = env.expect.that_collection(target[CompileJdepsInfo].after.to_list()) + + assert_that_before.has_size(1) + assert_that_after.has_size(2) + assert_that_after.contains_at_least(before) + assert_that_after.contains_exactly(target[JavaInfo]._compile_time_java_dependencies) + +def _test_compile_extend_compile_time_jdeps_rule_outputs(name): + util.helper_target( + custom_library_extended_jdeps, + name = name + "/foo", + srcs = ["Foo.java"], + extra_jdeps = "Foo.jdeps", + ) + util.helper_target( + custom_library_extended_jdeps, + name = name + "/bar", + srcs = ["Bar.java"], + extra_jdeps = "Bar.jdeps", + deps = [name + "/foo"], + ) + util.helper_target( + custom_library_extended_jdeps, + name = name + "/baz", + srcs = ["Baz.java"], + extra_jdeps = "Baz.jdeps", + exports = [name + "/foo"], + ) + + analysis_test( + name = name, + impl = _test_compile_extend_compile_time_jdeps_rule_outputs_impl, + targets = { + "foo": name + "/foo", + "bar": name + "/bar", + "baz": name + "/baz", + }, + ) + +def _test_compile_extend_compile_time_jdeps_rule_outputs_impl(env, targets): + foo = targets.foo + compile_time_jdeps = foo[JavaInfo]._compile_time_java_dependencies + env.expect.that_depset_of_files(compile_time_jdeps).contains_exactly([ + "{}/lib{}-hjar.jdeps".format(foo.label.package, foo.label.name), + "{}/Foo.jdeps".format(foo.label.package), + ]) + + # foo's jdeps shouldn't appear in bar's + bar = targets.bar + compile_time_jdeps = bar[JavaInfo]._compile_time_java_dependencies + env.expect.that_depset_of_files(compile_time_jdeps).contains_exactly([ + "{}/lib{}-hjar.jdeps".format(bar.label.package, bar.label.name), + "{}/Bar.jdeps".format(bar.label.package), + ]) + + # baz exports foo, so we expect jdeps from both targets + baz = targets.baz + compile_time_jdeps = baz[JavaInfo]._compile_time_java_dependencies + env.expect.that_depset_of_files(compile_time_jdeps).contains_exactly([ + "{}/lib{}-hjar.jdeps".format(foo.label.package, foo.label.name), + "{}/Foo.jdeps".format(foo.label.package), + "{}/lib{}-hjar.jdeps".format(baz.label.package, baz.label.name), + "{}/Baz.jdeps".format(baz.label.package), + ]) + +def _test_compile_bootclasspath(name): + files = [ + "custom-system/lib/jrt-fs.jar", + "custom-system/lib/modules", + "custom-system/release", + ] + util.helper_target( + custom_bootclasspath, + name = name + "/bootclasspath", + bootclasspath = files, + system = files, + ) + util.helper_target( + custom_library_with_bootclasspath, + name = name + "/custom", + srcs = ["Main.java"], + bootclasspath = name + "/bootclasspath", + sourcepath = [":B.jar"], + ) + + analysis_test( + name = name, + impl = _test_compile_bootclasspath_impl, + target = name + "/custom", + ) + +def _test_compile_bootclasspath_impl(env, target): + assert_that_javac = env.expect.that_target(target).action_generating( + target[JavaInfo].java_outputs[0].class_jar.short_path, + ) + + assert_that_javac.contains_flag_values([( + "--system", + "{}/custom-system".format(target.label.package), + )]) + +def _test_compile_override_with_empty_bootclasspath(name): + util.helper_target( + custom_bootclasspath, + name = name + "/bootclasspath", + bootclasspath = [], + system = [ + "custom-system/lib/jrt-fs.jar", + "custom-system/lib/modules", + "custom-system/release", + ], + ) + util.helper_target( + custom_library_with_bootclasspath, + name = name + "/custom", + srcs = ["Main.java"], + bootclasspath = name + "/bootclasspath", + ) + + analysis_test( + name = name, + impl = _test_compile_override_with_empty_bootclasspath_impl, + target = name + "/custom", + ) + +def _test_compile_override_with_empty_bootclasspath_impl(env, target): + assert_that_javac = env.expect.that_target(target).action_named("Javac") + + assert_that_javac.contains_flag_values([( + "--system", + "{}/custom-system".format(target.label.package), + )]) + +def _test_exposes_java_info_as_provider(name): + util.helper_target( + java_library, + name = name + "/dep", + srcs = ["Dep.java"], + ) + analysis_test( + name = name, + impl = _test_exposes_java_info_as_provider_impl, + target = name + "/dep", + ) + +def _test_exposes_java_info_as_provider_impl(env, target): + java_info = target[java_common.provider] + assert_java_info = java_info_subject.new( + java_info, + env.expect.meta.derive( + format_str_kwargs = { + "name": target.label.name, + "package": target.label.package, + }, + ), + ) + + assert_java_info.compilation_args().transitive_runtime_jars().contains_exactly([ + "{package}/lib{name}.jar", + ]) + assert_java_info.compilation_args().transitive_compile_time_jars().contains_exactly([ + "{package}/lib{name}-hjar.jar", + ]) + assert_java_info.compilation_args().full_compile_jars().contains_exactly([ + "{package}/lib{name}.jar", + ]) + assert_java_info.source_jars().contains_exactly_predicates([ + matching.file_basename_equals("dep-src.jar"), + ]) + + assert_output = assert_java_info.outputs().jars().singleton() + assert_output.class_jar().short_path_equals("{package}/lib{name}.jar") + assert_output.compile_jar().short_path_equals("{package}/lib{name}-hjar.jar") + assert_output.source_jars().contains_exactly(["{package}/lib{name}-src.jar"]) + assert_output.jdeps().short_path_equals("{package}/lib{name}.jdeps") + assert_output.compile_jdeps().short_path_equals("{package}/lib{name}-hjar.jdeps") + +def _test_compile_exposes_outputs_provider(name): + util.helper_target( + custom_library, + name = name + "/dep", + srcs = ["Main.java"], + ) + + analysis_test( + name = name, + impl = _test_compile_exposes_outputs_provider_impl, + target = name + "/dep", + ) + +def _test_compile_exposes_outputs_provider_impl(env, target): + assert_output = java_info_subject.from_target(env, target).outputs().jars().singleton() + + assert_output.class_jar().short_path_equals("{package}/lib{name}.jar") + assert_output.compile_jar().short_path_equals("{package}/lib{name}-hjar.jar") + assert_output.source_jars().contains_exactly(["{package}/lib{name}-src.jar"]) + assert_output.jdeps().short_path_equals("{package}/lib{name}.jdeps") + assert_output.native_headers_jar().short_path_equals("{package}/lib{name}-native-header.jar") + assert_output.compile_jdeps().short_path_equals("{package}/lib{name}-hjar.jdeps") + +def _test_compile_sets_runtime_deps(name): + target_name = name + "/custom" + util.helper_target( + custom_library, + name = target_name, + srcs = ["Main.java"], + runtime_deps = [target_name + "/runtime"], + deps = [target_name + "/dep"], + ) + util.helper_target( + java_library, + name = target_name + "/dep", + srcs = ["Dep.java"], + ) + util.helper_target( + java_library, + name = target_name + "/runtime", + srcs = ["Runtime.java"], + ) + + analysis_test( + name = name, + impl = _test_compile_sets_runtime_deps_impl, + target = target_name, + ) + +def _test_compile_sets_runtime_deps_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.compilation_args().transitive_runtime_jars_list().contains_exactly_predicates([ + matching.file_basename_equals("custom.jar"), + matching.file_basename_equals("dep.jar"), + matching.file_basename_equals("runtime.jar"), + ]).in_order() + assert_java_info.runtime_output_jars().contains_exactly(["{package}/lib{name}.jar"]) + assert_java_info.compilation_info().compilation_classpath().contains_exactly([ + "{package}/lib{name}/dep-hjar.jar", + ]) + assert_java_info.compilation_info().runtime_classpath_list().contains_exactly_predicates([ + matching.file_basename_equals("custom.jar"), + matching.file_basename_equals("runtime.jar"), + matching.file_basename_equals("dep.jar"), + ]).in_order() + assert_java_info.transitive_source_jars_list().contains_exactly_predicates([ + matching.file_basename_equals("runtime-src.jar"), + matching.file_basename_equals("dep-src.jar"), + matching.file_basename_equals("custom-src.jar"), + ]).in_order() + +def _test_compile_exposes_annotation_processing_info(name): + _test_annotation_processing_info_is_starlark_accessible(name, custom_library) + +def _test_java_library_exposes_annotation_processing_info(name): + _test_annotation_processing_info_is_starlark_accessible(name, java_library) + +def _test_annotation_processing_info_is_starlark_accessible(name, to_be_processed_rule_class): + target_name = name + "/to_be_processed" + util.helper_target( + to_be_processed_rule_class, + name = target_name, + plugins = [target_name + "/plugin"], + srcs = ["ToBeProcessed.java"], + deps = [target_name + "/dep"], + exports = [target_name + "/export"], + ) + util.helper_target( + java_library, + name = target_name + "/plugin_dep", + srcs = ["Processordep.java"], + ) + util.helper_target( + java_plugin, + name = target_name + "/plugin", + srcs = ["AnnotationProcessor.java"], + processor_class = "com.google.process.stuff", + deps = [target_name + "/plugin_dep"], + ) + util.helper_target( + java_library, + name = target_name + "/dep", + srcs = ["Dep.java"], + plugins = [target_name + "/plugin"], + ) + util.helper_target( + java_library, + name = target_name + "/export", + srcs = ["Export.java"], + plugins = [target_name + "/plugin"], + ) + + analysis_test( + name = name, + impl = _test_annotation_processing_info_is_starlark_accessible_impl, + target = target_name, + ) + +def _test_annotation_processing_info_is_starlark_accessible_impl(env, target): + depj = target[JavaInfo] + result = struct( + enabled = depj.annotation_processing.enabled, + class_jar = depj.outputs.jars[0].generated_class_jar, + source_jar = depj.outputs.jars[0].generated_source_jar, + old_class_jar = depj.annotation_processing.class_jar, + old_source_jar = depj.annotation_processing.source_jar, + processor_classpath = depj.annotation_processing.processor_classpath, + processor_classnames = depj.annotation_processing.processor_classnames, + transitive_class_jars = depj.annotation_processing.transitive_class_jars, + transitive_source_jars = depj.annotation_processing.transitive_source_jars, + ) + + env.expect.that_bool(result.enabled).equals(True) + env.expect.that_file(result.class_jar).equals(result.old_class_jar) + env.expect.that_file(result.source_jar).equals(result.old_source_jar) + env.expect.that_collection(result.processor_classnames).contains_exactly([ + "com.google.process.stuff", + ]) + env.expect.that_collection(result.processor_classpath.to_list()).contains_exactly_predicates([ + matching.file_basename_equals("plugin.jar"), + matching.file_basename_equals("plugin_dep.jar"), + ]) + env.expect.that_collection(result.transitive_class_jars.to_list()).has_size(3) + env.expect.that_collection(result.transitive_class_jars.to_list()).contains(result.class_jar) + env.expect.that_collection(result.transitive_source_jars.to_list()).has_size(3) + env.expect.that_collection(result.transitive_source_jars.to_list()).contains(result.source_jar) + +def _test_compile_requires_java_plugin_info(name): + target_name = name + "/to_be_processed" + util.helper_target( + java_library, + name = target_name + "/dep", + srcs = ["ProcessorDep.java"], + ) + util.helper_target( + custom_library_with_wrong_plugins_type, + name = target_name, + srcs = ["ToBeProcessed.java"], + deps = [target_name + "/dep"], + ) + + analysis_test( + name = name, + impl = _test_compile_requires_java_plugin_info_impl, + target = target_name, + expect_failure = True, + ) + +def _test_compile_requires_java_plugin_info_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("at index 0 of plugins, got element of type JavaInfo, want JavaPluginInfo"), + ) + +def _test_compile_compilation_info(name): + target_name = name + "/custom" + util.helper_target( + custom_library, + name = target_name, + srcs = ["Main.java"], + deps = [target_name + "/dep"], + javac_opts = ["-XDone -XDtwo"], + ) + util.helper_target( + java_library, + name = target_name + "/dep", + srcs = ["Dep.java"], + ) + + analysis_test( + name = name, + impl = _test_compile_compilation_info_impl, + target = target_name, + ) + +def _test_compile_compilation_info_impl(env, target): + assert_compilation_info = java_info_subject.from_target(env, target).compilation_info() + + assert_compilation_info.compilation_classpath().contains_exactly([ + "{package}/lib{name}/dep-hjar.jar", + ]) + assert_compilation_info.runtime_classpath().contains_exactly([ + "{package}/lib{name}/dep.jar", + "{package}/lib{name}.jar", + ]) + assert_compilation_info.javac_options().contains("-XDone") + +def _test_compile_transitive_source_jars(name): + target_name = name + "/custom" + util.helper_target( + custom_library, + name = target_name, + srcs = ["Main.java"], + deps = [target_name + "/dep"], + ) + util.helper_target( + java_library, + name = target_name + "/dep", + srcs = ["Dep.java"], + ) + + analysis_test( + name = name, + impl = _test_compile_transitive_source_jars_impl, + target = target_name, + ) + +def _test_compile_transitive_source_jars_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.source_jars().contains_exactly_predicates([ + matching.file_basename_equals("custom-src.jar"), + ]) + assert_java_info.transitive_source_jars().contains_exactly([ + "{package}/lib{name}/dep-src.jar", + "{package}/lib{name}-src.jar", + ]) + +def _test_compile_source_jar_name_derived_from_output_jar(name): + target_name = name + "/custom" + util.helper_target( + custom_library_with_named_outputs, + name = target_name, + srcs = ["Main.java"], + deps = [target_name + "/dep"], + ) + util.helper_target( + java_library, + name = target_name + "/dep", + srcs = ["Dep.java"], + ) + + analysis_test( + name = name, + impl = _test_compile_source_jar_name_derived_from_output_jar_impl, + target = target_name, + ) + +def _test_compile_source_jar_name_derived_from_output_jar_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.source_jars().contains_exactly_predicates([ + matching.file_basename_equals("amazing-src.jar"), + matching.file_basename_equals("wonderful-src.jar"), + ]) + assert_java_info.transitive_source_jars().contains_exactly([ + "{package}/lib{name}/dep-src.jar", + "{package}/{name}/amazing-src.jar", + "{package}/{name}/wonderful-src.jar", + ]) + +def _test_compile_with_only_one_source_jar(name): + util.helper_target( + custom_library, + name = name + "/custom", + source_jars = ["myjar-src.jar"], + ) + analysis_test( + name = name, + impl = _test_compile_with_only_one_source_jar_impl, + target = name + "/custom", + ) + +def _test_compile_with_only_one_source_jar_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.source_jars().contains_exactly_predicates([ + matching.file_basename_equals("custom-src.jar"), + ]) + assert_output = assert_java_info.java_outputs().singleton() + assert_output.class_jar().short_path_equals("{package}/lib{name}.jar") + assert_output.compile_jar().short_path_equals("{package}/lib{name}-hjar.jar") + assert_output.source_jars().contains_exactly(["{package}/lib{name}-src.jar"]) + assert_output.jdeps().short_path_equals("{package}/lib{name}.jdeps") + assert_output.compile_jdeps().short_path_equals("{package}/lib{name}-hjar.jdeps") + +def _test_compile_no_sources(name): + util.helper_target( + custom_library, + name = name + "/custom", + ) + + analysis_test( + name = name, + impl = _test_compile_no_sources_impl, + target = name + "/custom", + ) + +def _test_compile_no_sources_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.source_jars().contains_exactly_predicates([ + matching.file_basename_equals("custom-src.jar"), + ]) + assert_output = assert_java_info.java_outputs().singleton() + assert_output.class_jar().short_path_equals("{package}/lib{name}.jar") + assert_output.source_jars().contains_exactly(["{package}/lib{name}-src.jar"]) + +def _test_compile_custom_output_source_jar(name): + util.helper_target( + custom_library_with_custom_output_source_jar, + name = name + "/custom", + srcs = ["myjar-src.jar"], + ) + + analysis_test( + name = name, + impl = _test_compile_custom_output_source_jar_impl, + target = name + "/custom", + ) + +def _test_compile_custom_output_source_jar_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.source_jars().contains_exactly_predicates([ + matching.file_basename_equals("custom-mysrc.jar"), + ]) + assert_output = assert_java_info.java_outputs().singleton() + assert_output.class_jar().short_path_equals("{package}/lib{name}.jar") + assert_output.compile_jar().short_path_equals("{package}/lib{name}-hjar.jar") + assert_output.source_jars().contains_exactly(["{package}/lib{name}-mysrc.jar"]) + assert_output.jdeps().short_path_equals("{package}/lib{name}.jdeps") + assert_output.compile_jdeps().short_path_equals("{package}/lib{name}-hjar.jdeps") + +def _test_compile_additional_inputs_and_outputs(name): + util.helper_target( + custom_library_with_additional_inputs, + name = name + "/custom", + srcs = ["myjar-src.jar"], + additional_inputs = ["additional_input.bin"], + ) + + analysis_test( + name = name, + impl = _test_compile_additional_inputs_and_outputs_impl, + target = name + "/custom", + ) + +def _test_compile_additional_inputs_and_outputs_impl(env, target): + assert_java_action = env.expect.that_target(target).action_generating( + "{package}/lib{name}.jar", + ) + + assert_java_action.inputs().contains_predicate( + matching.file_basename_equals("additional_input.bin"), + ) + env.expect.that_depset_of_files(assert_java_action.actual.outputs).contains_predicate( + matching.file_basename_equals("custom_additional_output"), + ) + +def _test_compile_neverlink(name): + target_name = name + "/plugin" + util.helper_target( + java_binary, + name = target_name, + srcs = ["Plugin.java"], + main_class = "plugin.start", + deps = [target_name + "/somedep"], + ) + util.helper_target( + custom_library, + name = target_name + "/somedep", + srcs = ["Dependency.java"], + deps = [target_name + "/eclipse"], + ) + util.helper_target( + custom_library, + name = target_name + "/eclipse", + srcs = ["EclipseDependency.java"], + neverlink = 1, + ) + analysis_test( + name = name, + impl = _test_compile_neverlink_impl, + target = target_name, + extra_target_under_test_aspects = [artifact_closure.aspect], + ) + +def _test_compile_neverlink_impl(env, target): + java_source_basenames = [ + f.basename + for f in artifact_closure.of_target(target) + if f.extension == "java" + ] + env.expect.that_collection(java_source_basenames).contains_exactly([ + "Plugin.java", + "Dependency.java", + "EclipseDependency.java", + ]) + jars_in_runfiles = [ + f.basename + for f in target[DefaultInfo].default_runfiles.files.to_list() + if f.extension == "jar" and + f.short_path.startswith(target.label.package) # exclude toolchain + ] + env.expect.that_collection(jars_in_runfiles).contains_exactly([ + "plugin.jar", + "somedep.jar", + ]).in_order() + +def _test_compile_strict_deps_case_sensitivity(name): + util.helper_target( + custom_library_with_strict_deps, + name = name + "/enabled", + strict_deps = "error", + ) + util.helper_target( + custom_library_with_strict_deps, + name = name + "/disabled", + strict_deps = "off", + ) + + analysis_test( + name = name, + impl = _test_compile_strict_deps_case_sensitivity_impl, + targets = { + "enabled": name + "/enabled", + "disabled": name + "/disabled", + }, + ) + +def _test_compile_strict_deps_case_sensitivity_impl(env, targets): + env.expect.that_target(targets.enabled).action_named("Javac").contains_flag_values( + [("--strict_java_deps", "ERROR")], + ) + env.expect.that_target(targets.disabled).action_named("Javac").not_contains_arg( + "--strict_java_deps", + ) + +def _test_compile_strict_deps_enum(name): + util.helper_target( + custom_library_with_strict_deps, + name = name + "/custom", + strict_deps = "foo", + ) + + analysis_test( + name = name, + impl = _test_compile_strict_deps_enum_impl, + target = name + "/custom", + expect_failure = True, + # This is a crash in earlier Bazel versions (i.e. native rules) + attr_values = {"tags": ["min_bazel_8"]}, + ) + +def _test_compile_strict_deps_enum_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("invalid value for strict_deps: FOO"), + ) + +def _test_java_library_collects_coverage_dependencies_from_resources(name): + util.helper_target( + cc_binary, + name = name + "/lib/jni.so", + srcs = [name + "/lib/jni.cc"], + linkshared = 1, + features = ["-supports_pic"], + ) + util.helper_target( + java_library, + name = name + "/lib", + resources = [name + "/lib/jni.so"], + ) + + analysis_test( + name = name, + impl = _test_java_library_collects_coverage_dependencies_from_resources_impl, + target = name + "/lib", + config_settings = { + "//command_line_option:collect_code_coverage": "true", + "//command_line_option:instrumentation_filter": "//test/...", + }, + ) + +def _test_java_library_collects_coverage_dependencies_from_resources_impl(env, target): + env.expect.that_target(target).provider( + InstrumentedFilesInfo, + ).instrumented_files().contains_exactly(["{package}/{name}/jni.cc"]) + + env.expect.that_target(target).provider( + InstrumentedFilesInfo, + ).metadata_files().contains_exactly(["{package}/_objs/{name}/jni.so/jni.gcno"]) + +def _test_skip_annotation_processing(name): + util.helper_target( + java_plugin, + name = name + "/processor", + srcs = [name + "/processor.java"], + data = [name + "/processor_data.txt"], + generates_api = True, # so Turbine would normally run it + processor_class = "Foo", + ) + util.helper_target( + java_library, + name = name + "/exports_processor", + exported_plugins = [":" + name + "/processor"], + ) + util.helper_target( + custom_library, + name = name + "/custom", + srcs = [name + "/custom.java"], + plugins = [":" + name + "/processor"], + deps = [":" + name + "/exports_processor"], + enable_annotation_processing = False, + ) + util.helper_target( + java_library, + name = name + "/custom_noproc", + srcs = [name + "/custom.java"], + ) + + analysis_test( + name = name, + impl = _test_skip_annotation_processing_impl, + targets = { + "custom": ":" + name + "/custom", + "custom_noproc": ":" + name + "/custom_noproc", + }, + ) + +def _test_skip_annotation_processing_impl(env, targets): + javac_action = javac_action_subject.of(env, targets.custom, "{package}/lib{name}.jar") + + javac_action.mnemonic().equals("Javac") + javac_action.argv().not_contains("--processors") + javac_action.argv().contains("--processorpath") + javac_action.inputs().contains_at_least_predicates([ + matching.file_basename_equals("processor_data.txt"), + ]) + + turbine_action = javac_action_subject.of(env, targets.custom, "{package}/lib{name}-hjar.jar") + turbine_action.mnemonic().equals("JavacTurbine") + turbine_action.argv().not_contains("--processors") + + turbine_action_noproc = javac_action_subject.of(env, targets.custom_noproc, "{package}/lib{name}-hjar.jar") + turbine_action_noproc.mnemonic().equals("Turbine") + turbine_action_noproc.argv().not_contains("--processors") + +def _test_compile_direct_native_libraries(name): + target_name = name + "/custom" + util.helper_target( + cc_library, + name = target_name + "/native.so", + srcs = ["a.cc"], + ) + util.helper_target( + custom_library, + name = target_name, + srcs = ["A.java"], + ccdeps = [target_name + "/native.so"], + ) + + analysis_test( + name = name, + impl = _test_compile_direct_native_libraries_impl, + target = target_name, + ) + +def _test_compile_direct_native_libraries_impl(env, target): + assert_native_libs = java_info_subject.from_target(env, target).transitive_native_libraries() + assert_native_libs.static_libraries().contains_exactly_predicates([ + matching.any( + matching.str_matches("libnative.so.a"), # linux / mac + matching.str_matches("native.so.lib"), # windows + ), + ]) + +def _test_strict_java_deps_default(name): + util.helper_target( + custom_library, + name = name + "/unused", + ) + analysis_test( + name = name, + target = name + "/unused", # analysis_test requires setting a target. + impl = _test_strict_java_deps_default_impl, + fragments = ["java"], + ) + +def _test_strict_java_deps_default_impl(env, _unused): + env.expect.that_str(env.ctx.fragments.java.strict_java_deps).equals("default") + +def _test_strict_java_deps_error(name): + util.helper_target( + custom_library_with_strict_java_deps_provider, + name = name + "_strict_java_deps_provider", + ) + + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + analysis_test( + name = name, + impl = lambda env, target: env.expect.that_bool(True).equals(True), + target = name + "_strict_java_deps_provider", + ) + else: + analysis_test( + name = name, + target = name + "_strict_java_deps_provider", + impl = _test_strict_java_deps_error_impl, + fragments = ["java"], + config_settings = {"//command_line_option:experimental_strict_java_deps": "error"}, + ) + +def _test_strict_java_deps_error_impl(env, target): + env.expect.that_str(target[StrictJavaDepsInfo].strict_java_deps).equals("error") + +def _test_compile_output_jar_has_manifest_proto(name): + util.helper_target( + custom_library, + name = name + "/custom", + srcs = ["Main.java"], + ) + + analysis_test( + name = name, + impl = _test_compile_output_jar_has_manifest_proto_impl, + target = name + "/custom", + ) + +def _test_compile_output_jar_has_manifest_proto_impl(env, target): + java_info_subject.from_target(env, target).java_outputs().singleton().manifest_proto().short_path_equals( + "{package}/lib{name}.jar_manifest_proto", + ) + +def _test_compile_with_neverlink_deps(name): + target_name = name + "/custom" + util.helper_target( + java_library, + name = target_name + "/neverlink_dep", + srcs = ["B.java"], + neverlink = True, + ) + util.helper_target( + custom_library, + name = target_name, + srcs = ["A.java"], + deps = [target_name + "/neverlink_dep"], + ) + + analysis_test( + name = name, + impl = _test_compile_with_neverlink_deps_impl, + target = target_name, + ) + +def _test_compile_with_neverlink_deps_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + assert_java_info.compilation_args().transitive_runtime_jars().contains_exactly([ + "{package}/lib{name}.jar", + ]) + assert_java_info.transitive_source_jars().contains_exactly([ + "{package}/lib{name}-src.jar", + "{package}/lib{name}/neverlink_dep-src.jar", + ]) + assert_java_info.compilation_args().transitive_compile_time_jars().contains_exactly([ + "{package}/lib{name}-hjar.jar", + "{package}/lib{name}/neverlink_dep-hjar.jar", + ]) + +def _test_compile_output_jar_not_in_runtime_path_without_sources_defined(name): + target_name = name + "/custom" + util.helper_target( + java_library, + name = target_name + "/export_dep", + srcs = ["B.java"], + ) + util.helper_target( + custom_library, + name = target_name, + srcs = [], + exports = [target_name + "/export_dep"], + ) + + analysis_test( + name = name, + impl = _test_compile_output_jar_not_in_runtime_path_without_sources_defined_impl, + target = target_name, + ) + +def _test_compile_output_jar_not_in_runtime_path_without_sources_defined_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + assert_java_info.compilation_args().transitive_runtime_jars().contains_exactly([ + "{package}/lib{name}/export_dep.jar", + ]) + assert_java_info.compilation_args().transitive_compile_time_jars().contains_exactly([ + "{package}/lib{name}/export_dep-hjar.jar", + ]) + assert_java_info.java_outputs().singleton().class_jar().short_path_equals("{package}/lib{name}.jar") + assert_java_info.java_outputs().singleton().compile_jar().equals(None) + +def _test_java_runtime_provider_files(name): + # Create a rule that extracts JavaRuntimeInfo.files + util.helper_target( + java_runtime, + name = name + "/jvm", + srcs = ["a.txt"], + java_home = "foo/bar", + ) + + analysis_test( + name = name, + impl = _test_java_runtime_provider_files_impl, + target = name + "/jvm", + ) + +def _test_java_runtime_provider_files_impl(env, target): + env.expect.that_target(target).default_outputs().contains_exactly(["{package}/a.txt"]) + +def _test_custom_library_with_wrong_java_toolchain_type(name): + util.helper_target( + custom_library_with_wrong_java_toolchain_type, + name = name + "/custom", + srcs = ["a.java"], + ) + analysis_test( + name = name, + impl = _test_custom_library_with_wrong_java_toolchain_type_impl, + target = name + "/custom", + expect_failure = True, + ) + +def _test_custom_library_with_wrong_java_toolchain_type_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("got element of type ToolchainInfo, want JavaToolchainInfo"), + ) + +def _test_private_api(name, helper_rule, private_attr_name): + util.helper_target( + helper_rule, + name = name + "/custom", + private_attr_name = private_attr_name, + ) + + analysis_test( + name = name, + impl = lambda env, target: _test_private_api_impl(env, target, private_attr_name), + target = name + "/custom", + expect_failure = True, + ) + +def _test_private_api_impl(env, target, private_attr_name): + env.expect.that_target(target).failures().contains_predicate( + matching.contains("got unexpected keyword argument: " + private_attr_name), + ) + +def _test_compile_disabling_compile_jar_is_private_api(name): + _test_private_api(name, private_compile_api_usage, "enable_compile_jar_action") + +def _test_compile_classpath_resources_is_private_api(name): + _test_private_api(name, private_compile_api_usage, "classpath_resources") + +def _test_compile_injecting_rule_kind_is_private_api(name): + _test_private_api(name, private_compile_api_usage, "injecting_rule_kind") + +def _test_compile_enable_jspecify_is_private_api(name): + _test_private_api(name, private_compile_api_usage, "enable_jspecify") + +def _test_merge_java_outputs_is_private_api(name): + _test_private_api(name, private_merge_api_usage, "merge_java_outputs") + +def _test_merge_source_jars_is_private_api(name): + _test_private_api(name, private_merge_api_usage, "merge_source_jars") + +def _test_compile_include_compilation_info_is_private_api(name): + _test_private_api(name, private_compile_api_usage, "include_compilation_info") + +def _test_compile_resource_jars_is_private_api(name): + _test_private_api(name, private_compile_api_usage, "resource_jars") + +def _test_run_ijar_output_is_private_api(name): + _test_private_api(name, private_run_ijar_api_usage, "output") + +def java_common_tests(name): + test_suite( + name = name, + tests = [ + _test_compile_default_values, + _test_compile_sourcepath, + _test_compile_exports_no_sources, + _test_compile_exports_with_sources, + _test_java_plugin_info, + _test_compile_extend_compile_time_jdeps, + _test_compile_extend_compile_time_jdeps_rule_outputs, + _test_compile_bootclasspath, + _test_compile_override_with_empty_bootclasspath, + _test_exposes_java_info_as_provider, + _test_compile_exposes_outputs_provider, + _test_compile_sets_runtime_deps, + _test_compile_exposes_annotation_processing_info, + _test_java_library_exposes_annotation_processing_info, + _test_compile_requires_java_plugin_info, + _test_compile_compilation_info, + _test_compile_transitive_source_jars, + _test_compile_source_jar_name_derived_from_output_jar, + _test_compile_with_only_one_source_jar, + _test_compile_no_sources, + _test_compile_custom_output_source_jar, + _test_compile_additional_inputs_and_outputs, + _test_compile_neverlink, + _test_compile_strict_deps_case_sensitivity, + _test_compile_strict_deps_enum, + _test_java_library_collects_coverage_dependencies_from_resources, + _test_skip_annotation_processing, + _test_compile_direct_native_libraries, + _test_strict_java_deps_default, + _test_strict_java_deps_error, + _test_compile_output_jar_has_manifest_proto, + _test_compile_with_neverlink_deps, + _test_compile_output_jar_not_in_runtime_path_without_sources_defined, + _test_java_runtime_provider_files, + _test_custom_library_with_wrong_java_toolchain_type, + _test_compile_disabling_compile_jar_is_private_api, + _test_compile_classpath_resources_is_private_api, + _test_compile_injecting_rule_kind_is_private_api, + _test_compile_enable_jspecify_is_private_api, + _test_merge_java_outputs_is_private_api, + _test_merge_source_jars_is_private_api, + _test_compile_include_compilation_info_is_private_api, + _test_compile_resource_jars_is_private_api, + _test_run_ijar_output_is_private_api, + ], + ) diff --git a/test/java/common/java_info_tests.bzl b/test/java/common/java_info_tests.bzl new file mode 100644 index 00000000..daea0485 --- /dev/null +++ b/test/java/common/java_info_tests.bzl @@ -0,0 +1,1471 @@ +"""Tests for the JavaInfo provider""" + +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_library.bzl", "java_library") +load("//java:java_plugin.bzl", "java_plugin") +load("//java/common:java_info.bzl", "JavaInfo") +load("//test/java/testutil:java_info_subject.bzl", "java_info_subject") +load("//test/java/testutil:rules/bad_java_info_rules.bzl", "bad_deps", "bad_exports", "bad_libs", "bad_runtime_deps", "compile_jar_not_set", "compile_jar_set_to_none") +load("//test/java/testutil:rules/custom_java_info_rule.bzl", "custom_java_info_rule") +load("//test/java/testutil:rules/custom_library.bzl", "custom_library") +load("//test/java/testutil:rules/forward_java_info.bzl", "java_info_forwarding_rule") +load("//test/java/testutil:rules/java_info_merge.bzl", "java_info_merge_rule") + +def _with_output_jar_only_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + ) + + analysis_test( + name = name, + impl = _with_output_jar_only_test_impl, + target = target_name, + ) + +def _with_output_jar_only_test_impl(env, target): + assert_compilation_args = java_info_subject.from_target(env, target).compilation_args() + + assert_compilation_args.compile_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_compilation_args.full_compile_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_compilation_args.transitive_runtime_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_compilation_args.transitive_compile_time_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + +def _with_output_jar_and_use_ijar_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + use_ijar = True, + ) + + analysis_test( + name = name, + impl = _with_output_jar_and_use_ijar_test_impl, + target = target_name, + ) + +def _with_output_jar_and_use_ijar_test_impl(env, target): + assert_compilation_args = java_info_subject.from_target(env, target).compilation_args() + + assert_compilation_args.compile_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib-ijar.jar"]) + assert_compilation_args.full_compile_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_compilation_args.transitive_runtime_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_compilation_args.transitive_compile_time_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib-ijar.jar"]) + +def _with_output_jar_and_use_ijar_outputs_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + use_ijar = True, + ) + + analysis_test( + name = name, + impl = _with_output_jar_and_use_ijar_outputs_test_impl, + target = target_name, + ) + +def _with_output_jar_and_use_ijar_outputs_test_impl(env, target): + assert_outputs = java_info_subject.from_target(env, target).outputs() + + assert_outputs.source_output_jars().contains_exactly(["{package}/my_starlark_rule_src.jar"]) + assert_outputs.class_output_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_outputs.jars().singleton().compile_jar().short_path_equals("{package}/{name}/my_starlark_rule_lib-ijar.jar") + +def _with_deps_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/my_java_lib_direct", + srcs = ["java/A.java"], + ) + util.helper_target( + custom_java_info_rule, + name = target_name, + dep = [target_name + "/my_java_lib_direct"], + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + ) + + analysis_test( + name = name, + impl = _with_deps_test_impl, + target = target_name, + ) + +def _with_deps_test_impl(env, target): + assert_compilation_args = java_info_subject.from_target(env, target).compilation_args() + + assert_compilation_args.compile_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_compilation_args.full_compile_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_compilation_args.transitive_runtime_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_direct.jar", + ]) + assert_compilation_args.transitive_compile_time_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_direct-hjar.jar", + ]) + +def _with_runtime_deps_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/my_java_lib_direct", + srcs = ["java/A.java"], + ) + util.helper_target( + custom_java_info_rule, + name = target_name, + dep_runtime = [target_name + "/my_java_lib_direct"], + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + ) + + analysis_test( + name = name, + impl = _with_runtime_deps_test_impl, + target = target_name, + ) + +def _with_runtime_deps_test_impl(env, target): + assert_compilation_args = java_info_subject.from_target(env, target).compilation_args() + + assert_compilation_args.compile_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_compilation_args.full_compile_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_compilation_args.transitive_runtime_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_direct.jar", + ]) + assert_compilation_args.transitive_compile_time_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + +def _with_native_libraries_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + cc_library, + name = target_name + "/my_cc_lib_direct", + srcs = ["cc/a.cc"], + ) + util.helper_target( + custom_java_info_rule, + name = target_name, + cc_dep = [target_name + "/my_cc_lib_direct"], + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + ) + + analysis_test( + name = name, + impl = _with_native_libraries_test_impl, + target = target_name, + ) + +def _with_native_libraries_test_impl(env, target): + assert_native_libs = java_info_subject.from_target(env, target).transitive_native_libraries() + + assert_native_libs.static_libraries().contains_exactly_predicates([matching.str_matches("*my_cc_lib_direct*")]) + +def _with_deps_and_neverlink_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/my_java_lib_direct", + srcs = ["java/A.java"], + ) + util.helper_target( + custom_java_info_rule, + name = target_name, + dep = [target_name + "/my_java_lib_direct"], + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + neverlink = True, + ) + + analysis_test( + name = name, + impl = _with_deps_and_neverlink_test_impl, + target = target_name, + ) + +def _with_deps_and_neverlink_test_impl(env, target): + assert_compilation_args = java_info_subject.from_target(env, target).compilation_args() + + assert_compilation_args.compile_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_compilation_args.full_compile_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_compilation_args.transitive_runtime_jars().contains_exactly([]) + assert_compilation_args.transitive_compile_time_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_direct-hjar.jar", + ]) + +def _with_source_jars_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + ) + + analysis_test( + name = name, + impl = _with_source_jars_test_impl, + target = target_name, + ) + +def _with_source_jars_test_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.source_jars().contains_exactly_predicates([ + matching.file_basename_equals("my_starlark_rule_src.jar"), + ]) + assert_java_info.transitive_source_jars().contains_exactly([ + "{package}/my_starlark_rule_src.jar", + ]) + +def _with_packed_sourcejars_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + pack_sources = True, + ) + + analysis_test( + name = name, + impl = _with_packed_sourcejars_test_impl, + target = target_name, + ) + +def _with_packed_sourcejars_test_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.source_jars().contains_exactly_predicates([ + matching.file_basename_equals("my_starlark_rule_lib-src.jar"), + ]) + assert_java_info.transitive_source_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib-src.jar", + ]) + +def _with_packed_sources_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + output_jar = target_name + "/my_starlark_rule_lib.jar", + sources = ["ClassA.java", "ClassB.java", "ClassC.java", "ClassD.java"], + pack_sources = True, + ) + + analysis_test( + name = name, + impl = _with_packed_sources_test_impl, + target = target_name, + ) + +def _with_packed_sources_test_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.outputs().source_output_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib-src.jar", + ]) + assert_java_info.source_jars().contains_exactly_predicates([ + matching.file_basename_equals("my_starlark_rule_lib-src.jar"), + ]) + assert_java_info.transitive_source_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib-src.jar", + ]) + +def _with_packed_sources_and_source_jars_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src-A.jar"], + sources = ["ClassA.java", "ClassB.java", "ClassC.java", "ClassD.java"], + pack_sources = True, + ) + + analysis_test( + name = name, + impl = _with_packed_sources_and_source_jars_test_impl, + target = target_name, + ) + +def _with_packed_sources_and_source_jars_test_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.outputs().source_output_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib-src.jar", + ]) + assert_java_info.source_jars().contains_exactly_predicates([ + matching.file_basename_equals("my_starlark_rule_lib-src.jar"), + ]) + assert_java_info.transitive_source_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib-src.jar", + ]) + +def _with_deps_source_jars_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/my_java_lib_direct", + srcs = ["java/A.java"], + ) + util.helper_target( + custom_java_info_rule, + name = target_name, + dep = [target_name + "/my_java_lib_direct"], + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + ) + + analysis_test( + name = name, + impl = _with_deps_source_jars_test_impl, + target = target_name, + ) + +def _with_deps_source_jars_test_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.source_jars().contains_exactly_predicates([ + matching.file_basename_equals("my_starlark_rule_src.jar"), + ]) + assert_java_info.transitive_source_jars().contains_exactly([ + "{package}/my_starlark_rule_src.jar", + "{package}/lib{name}/my_java_lib_direct-src.jar", + ]) + +def _with_runtime_deps_source_jars_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/my_java_lib_direct", + srcs = ["java/A.java"], + ) + util.helper_target( + custom_java_info_rule, + name = target_name, + dep_runtime = [target_name + "/my_java_lib_direct"], + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + ) + + analysis_test( + name = name, + impl = _with_runtime_deps_source_jars_test_impl, + target = target_name, + ) + +def _with_runtime_deps_source_jars_test_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.source_jars().contains_exactly_predicates([ + matching.file_basename_equals("my_starlark_rule_src.jar"), + ]) + assert_java_info.transitive_source_jars().contains_exactly([ + "{package}/my_starlark_rule_src.jar", + "{package}/lib{name}/my_java_lib_direct-src.jar", + ]) + +def _with_transitive_deps_source_jars_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/my_java_lib_transitive", + srcs = ["java/B.java"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_direct", + srcs = ["java/A.java"], + deps = [target_name + "/my_java_lib_transitive"], + ) + util.helper_target( + custom_java_info_rule, + name = target_name, + dep_runtime = [target_name + "/my_java_lib_direct"], + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + ) + + analysis_test( + name = name, + impl = _with_transitive_deps_source_jars_test_impl, + target = target_name, + ) + +def _with_transitive_deps_source_jars_test_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.source_jars().contains_exactly_predicates([ + matching.file_basename_equals("my_starlark_rule_src.jar"), + ]) + assert_java_info.transitive_source_jars().contains_exactly([ + "{package}/my_starlark_rule_src.jar", + "{package}/lib{name}/my_java_lib_direct-src.jar", + "{package}/lib{name}/my_java_lib_transitive-src.jar", + ]) + +def _with_transitive_runtime_deps_source_jars_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/my_java_lib_transitive", + srcs = ["java/B.java"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_direct", + srcs = ["java/A.java"], + runtime_deps = [target_name + "/my_java_lib_transitive"], + ) + util.helper_target( + custom_java_info_rule, + name = target_name, + dep_runtime = [target_name + "/my_java_lib_direct"], + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + ) + + analysis_test( + name = name, + impl = _with_transitive_runtime_deps_source_jars_test_impl, + target = target_name, + ) + +def _with_transitive_runtime_deps_source_jars_test_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.source_jars().contains_exactly_predicates([ + matching.file_basename_equals("my_starlark_rule_src.jar"), + ]) + assert_java_info.transitive_source_jars().contains_exactly([ + "{package}/my_starlark_rule_src.jar", + "{package}/lib{name}/my_java_lib_direct-src.jar", + "{package}/lib{name}/my_java_lib_transitive-src.jar", + ]) + +def _with_exports_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/my_java_lib_exports", + srcs = ["java/A.java"], + ) + util.helper_target( + custom_java_info_rule, + name = target_name, + dep_exports = [target_name + "/my_java_lib_exports"], + output_jar = target_name + "/my_starlark_rule_lib.jar", + ) + + analysis_test( + name = name, + impl = _with_exports_test_impl, + target = target_name, + ) + +def _with_exports_test_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.source_jars().contains_exactly([]) + + assert_compilation_args = assert_java_info.compilation_args() + assert_compilation_args.compile_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_exports-hjar.jar", + ]) + assert_compilation_args.full_compile_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_exports.jar", + ]) + assert_compilation_args.transitive_runtime_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_exports.jar", + ]) + assert_compilation_args.transitive_compile_time_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_exports-hjar.jar", + ]) + +def _with_transitive_exports_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/my_java_lib_c", + srcs = ["java/C.java"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_b", + srcs = ["java/B.java"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_a", + srcs = ["java/A.java"], + exports = [target_name + "/my_java_lib_b"], + deps = [ + target_name + "/my_java_lib_b", + target_name + "/my_java_lib_c", + ], + ) + util.helper_target( + custom_java_info_rule, + name = target_name, + dep_exports = [target_name + "/my_java_lib_a"], + output_jar = target_name + "/my_starlark_rule_lib.jar", + ) + + analysis_test( + name = name, + impl = _with_transitive_exports_test_impl, + target = target_name, + ) + +def _with_transitive_exports_test_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_compilation_args = assert_java_info.compilation_args() + assert_compilation_args.compile_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_a-hjar.jar", + "{package}/lib{name}/my_java_lib_b-hjar.jar", + ]) + assert_compilation_args.full_compile_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_a.jar", + "{package}/lib{name}/my_java_lib_b.jar", + ]) + assert_compilation_args.transitive_runtime_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_a.jar", + "{package}/lib{name}/my_java_lib_b.jar", + "{package}/lib{name}/my_java_lib_c.jar", + ]) + assert_compilation_args.transitive_compile_time_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_a-hjar.jar", + "{package}/lib{name}/my_java_lib_b-hjar.jar", + "{package}/lib{name}/my_java_lib_c-hjar.jar", + ]) + +def _with_transitive_deps_and_exports_test(name): + # Tests case: my_lib + # / \ + # a c + # || || + # b d + # where single line is normal dependency and double is exports dependency. + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/my_java_lib_b", + srcs = ["java/B.java"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_a", + srcs = ["java/A.java"], + exports = [target_name + "/my_java_lib_b"], + deps = [target_name + "/my_java_lib_b"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_d", + srcs = ["java/D.java"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_c", + srcs = ["java/C.java"], + exports = [target_name + "/my_java_lib_d"], + deps = [target_name + "/my_java_lib_d"], + ) + util.helper_target( + custom_java_info_rule, + name = target_name, + dep = [ + target_name + "/my_java_lib_a", + target_name + "/my_java_lib_c", + ], + dep_exports = [target_name + "/my_java_lib_a"], + output_jar = target_name + "/my_starlark_rule_lib.jar", + ) + + analysis_test( + name = name, + impl = _with_transitive_deps_and_exports_test_impl, + target = target_name, + ) + +def _with_transitive_deps_and_exports_test_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_compilation_args = assert_java_info.compilation_args() + assert_compilation_args.compile_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_a-hjar.jar", + "{package}/lib{name}/my_java_lib_b-hjar.jar", + ]) + assert_compilation_args.full_compile_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_a.jar", + "{package}/lib{name}/my_java_lib_b.jar", + ]) + assert_compilation_args.transitive_runtime_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_a.jar", + "{package}/lib{name}/my_java_lib_b.jar", + "{package}/lib{name}/my_java_lib_c.jar", + "{package}/lib{name}/my_java_lib_d.jar", + ]) + assert_compilation_args.transitive_compile_time_jars().contains_exactly([ + "{package}/{name}/my_starlark_rule_lib.jar", + "{package}/lib{name}/my_java_lib_a-hjar.jar", + "{package}/lib{name}/my_java_lib_b-hjar.jar", + "{package}/lib{name}/my_java_lib_c-hjar.jar", + "{package}/lib{name}/my_java_lib_d-hjar.jar", + ]) + +def _with_plugins_via_exports_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/plugin_dep", + srcs = ["ProcessorDep.java"], + ) + util.helper_target( + java_plugin, + name = target_name + "/plugin", + srcs = ["AnnotationProcessor.java"], + processor_class = "com.google.process.stuff", + deps = [target_name + "/plugin_dep"], + ) + util.helper_target( + java_library, + name = target_name + "/export", + exported_plugins = [target_name + "/plugin"], + ) + util.helper_target( + custom_java_info_rule, + name = target_name, + dep_exports = [target_name + "/export"], + output_jar = target_name + "/my_starlark_rule_lib.jar", + ) + + analysis_test( + name = name, + impl = _with_plugins_via_exports_test_impl, + target = target_name, + ) + +def _with_plugins_via_exports_test_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.plugins().processor_classes().contains_exactly(["com.google.process.stuff"]) + +def _with_plugins_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/plugin_dep", + srcs = ["ProcessorDep.java"], + ) + util.helper_target( + java_plugin, + name = target_name + "/plugin", + srcs = ["AnnotationProcessor.java"], + processor_class = "com.google.process.stuff", + deps = [target_name + "/plugin_dep"], + ) + util.helper_target( + custom_java_info_rule, + name = target_name, + dep_exported_plugins = [target_name + "/plugin"], + output_jar = target_name + "/my_starlark_rule_lib.jar", + ) + + analysis_test( + name = name, + impl = _with_plugins_test_impl, + target = target_name, + ) + +def _with_plugins_test_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.plugins().processor_classes().contains_exactly(["com.google.process.stuff"]) + +def _with_stamped_jar_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + stamp_jar = True, + ) + + analysis_test( + name = name, + impl = _with_stamped_jar_test_impl, + target = target_name, + ) + +def _with_stamped_jar_test_impl(env, target): + assert_compilation_args = java_info_subject.from_target(env, target).compilation_args() + + assert_compilation_args.full_compile_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_compilation_args.compile_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib-stamped.jar"]) + assert_compilation_args.transitive_runtime_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_compilation_args.transitive_compile_time_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib-stamped.jar"]) + +def _with_jdeps_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + jdeps = "my_jdeps.pb", + output_jar = target_name + "/my_starlark_rule_lib.jar", + source_jars = ["my_starlark_rule_src.jar"], + ) + + analysis_test( + name = name, + impl = _with_jdeps_test_impl, + target = target_name, + ) + +def _with_jdeps_test_impl(env, target): + assert_outputs = java_info_subject.from_target(env, target).outputs() + + assert_outputs.class_output_jars().contains_exactly(["{package}/{name}/my_starlark_rule_lib.jar"]) + assert_outputs.source_output_jars().contains_exactly(["{package}/my_starlark_rule_src.jar"]) + assert_outputs.jdeps().contains_exactly(["{package}/my_jdeps.pb"]) + +def _with_generated_jars_outputs_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + generated_class_jar = "generated_class.jar", + generated_source_jar = "generated_srcs.jar", + output_jar = target_name + "/my_starlark_rule_lib.jar", + ) + + analysis_test( + name = name, + impl = _with_generated_jars_outputs_test_impl, + target = target_name, + ) + +def _with_generated_jars_outputs_test_impl(env, target): + assert_outputs = java_info_subject.from_target(env, target).outputs() + + assert_outputs.generated_class_jars().contains_exactly(["{package}/generated_class.jar"]) + assert_outputs.generated_source_jars().contains_exactly(["{package}/generated_srcs.jar"]) + +def _with_generated_jars_annotation_processing_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + generated_class_jar = "generated_class.jar", + generated_source_jar = "generated_srcs.jar", + output_jar = target_name + "/my_starlark_rule_lib.jar", + ) + + analysis_test( + name = name, + impl = _with_generated_jars_annotation_processing_test_impl, + target = target_name, + ) + +def _with_generated_jars_annotation_processing_test_impl(env, target): + assert_annotation_processing = java_info_subject.from_target(env, target).annotation_processing() + + assert_annotation_processing.class_jar().short_path_equals("{package}/generated_class.jar") + assert_annotation_processing.source_jar().short_path_equals("{package}/generated_srcs.jar") + +def _with_compile_jdeps_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + compile_jdeps = "compile.deps", + output_jar = target_name + "/my_starlark_rule_lib.jar", + ) + + analysis_test( + name = name, + impl = _with_compile_jdeps_test_impl, + target = target_name, + ) + +def _with_compile_jdeps_test_impl(env, target): + java_info_subject.from_target(env, target).outputs().compile_jdeps().contains_exactly([ + "{package}/compile.deps", + ]) + +def _with_native_headers_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + native_headers_jar = "native_headers.jar", + output_jar = target_name + "/my_starlark_rule_lib.jar", + ) + + analysis_test( + name = name, + impl = _with_native_headers_test_impl, + target = target_name, + ) + +def _with_native_headers_test_impl(env, target): + java_info_subject.from_target(env, target).outputs().native_headers().contains_exactly([ + "{package}/native_headers.jar", + ]) + +def _with_manifest_proto_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + manifest_proto = "manifest.proto", + output_jar = target_name + "/my_starlark_rule_lib.jar", + ) + + analysis_test( + name = name, + impl = _with_manifest_proto_test_impl, + target = target_name, + ) + +def _with_manifest_proto_test_impl(env, target): + java_info_subject.from_target(env, target).outputs().manifest_protos().contains_exactly([ + "{package}/manifest.proto", + ]) + +def _sequence_parameters_are_type_checked_test(name): + util.helper_target(bad_deps, name = name + "/bad_deps") + util.helper_target(bad_runtime_deps, name = name + "/bad_runtime_deps") + util.helper_target(bad_exports, name = name + "/bad_exports") + util.helper_target(bad_libs, name = name + "/bad_libs") + + analysis_test( + name = name, + impl = _sequence_parameters_are_type_checked_test_impl, + targets = { + "deps": name + "/bad_deps", + "runtime_deps": name + "/bad_runtime_deps", + "exports": name + "/bad_exports", + "libs": name + "/bad_libs", + }, + expect_failure = True, + ) + +def _sequence_parameters_are_type_checked_test_impl(env, targets): + env.expect.that_target(targets.deps).failures().contains_predicate( + matching.str_matches("at index 0 of deps, got element of type File, want JavaInfo"), + ) + env.expect.that_target(targets.runtime_deps).failures().contains_predicate( + matching.str_matches("at index 0 of runtime_deps, got element of type File, want JavaInfo"), + ) + env.expect.that_target(targets.exports).failures().contains_predicate( + matching.str_matches("at index 0 of exports, got element of type File, want JavaInfo"), + ) + env.expect.that_target(targets.libs).failures().contains_predicate( + matching.str_matches("at index 0 of native_libraries, got element of type File, want CcInfo"), + ) + +def _with_compile_jar_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + output_jar = target_name + "/output.jar", + compile_jar = "compile.jar", + ) + + analysis_test( + name = name, + impl = _with_compile_jar_test_impl, + target = target_name, + ) + +def _with_compile_jar_test_impl(env, target): + env.expect.that_depset_of_files(target[JavaInfo].compile_jars).contains_predicate( + matching.file_basename_equals("compile.jar"), + ) + +def _compile_jar_not_set_test(name): + util.helper_target(compile_jar_not_set, name = name + "/only_outputjar") + + analysis_test( + name = name, + impl = _compile_jar_not_set_test_impl, + target = name + "/only_outputjar", + expect_failure = True, + ) + +def _compile_jar_not_set_test_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("missing 1 required positional argument: compile_jar"), + ) + +def _compile_jar_set_to_none_test(name): + util.helper_target(compile_jar_set_to_none, name = name + "/compilejar_none") + + analysis_test( + name = name, + impl = _compile_jar_set_to_none_test_impl, + target = name + "/compilejar_none", + ) + +def _compile_jar_set_to_none_test_impl(env, target): + env.expect.that_depset_of_files(target[JavaInfo].compile_jars).contains_exactly([]) + +def _sources_jars_exposed_test(name): + util.helper_target( + java_library, + name = name + "/my_java_lib_b", + srcs = ["java/B.java"], + ) + util.helper_target( + java_library, + name = name + "/my_java_lib_a", + srcs = ["java/A.java"], + deps = [name + "/my_java_lib_b"], + ) + analysis_test( + name = name, + impl = _sources_jars_exposed_test_impl, + target = name + "/my_java_lib_a", + ) + +def _sources_jars_exposed_test_impl(env, target): + source_jars = target[JavaInfo].source_jars + env.expect.that_collection(source_jars).contains_exactly_predicates([ + matching.file_basename_equals("my_java_lib_a-src.jar"), + ]) + +def _transitive_source_jars_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/my_java_lib_c", + srcs = ["java/C.java"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_b", + srcs = ["java/B.java"], + deps = [target_name + "/my_java_lib_c"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_a", + srcs = ["java/A.java"], + deps = [target_name + "/my_java_lib_b"], + ) + util.helper_target( + java_info_forwarding_rule, + name = target_name, + dep = target_name + "/my_java_lib_a", + ) + analysis_test( + name = name, + impl = _transitive_source_jars_test_impl, + target = target_name, + ) + +def _transitive_source_jars_test_impl(env, target): + assert_transitive_source_jars = java_info_subject.from_target(env, target).transitive_source_jars() + assert_transitive_source_jars.contains_exactly([ + "{package}/lib{name}/my_java_lib_a-src.jar", + "{package}/lib{name}/my_java_lib_b-src.jar", + "{package}/lib{name}/my_java_lib_c-src.jar", + ]) + +def _transitive_compile_time_jars_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/my_java_lib_c", + srcs = ["java/C.java"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_b", + srcs = ["java/B.java"], + deps = [target_name + "/my_java_lib_c"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_a", + srcs = ["java/A.java"], + deps = [target_name + "/my_java_lib_b"], + ) + util.helper_target( + java_info_forwarding_rule, + name = target_name, + dep = target_name + "/my_java_lib_a", + ) + analysis_test( + name = name, + impl = transitive_compile_time_jars_impl, + target = target_name, + ) + +def transitive_compile_time_jars_impl(env, target): + assert_transitive_compile_time_jars = java_info_subject.from_target(env, target).compilation_args().transitive_compile_time_jars() + assert_transitive_compile_time_jars.contains_exactly([ + "{package}/lib{name}/my_java_lib_a-hjar.jar", + "{package}/lib{name}/my_java_lib_b-hjar.jar", + "{package}/lib{name}/my_java_lib_c-hjar.jar", + ]) + +def _transitive_runtime_jars_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + java_library, + name = target_name + "/my_java_lib_c", + srcs = ["java/C.java"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_b", + srcs = ["java/B.java"], + deps = [target_name + "/my_java_lib_c"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_a", + srcs = ["java/A.java"], + deps = [target_name + "/my_java_lib_b"], + ) + util.helper_target( + java_info_forwarding_rule, + name = target_name, + dep = target_name + "/my_java_lib_a", + ) + analysis_test( + name = name, + impl = transitive_runtime_jars_impl, + target = target_name, + ) + +def transitive_runtime_jars_impl(env, target): + assert_transitive_runtime_jars = java_info_subject.from_target(env, target).compilation_args().transitive_runtime_jars() + assert_transitive_runtime_jars.contains_exactly([ + "{package}/lib{name}/my_java_lib_a.jar", + "{package}/lib{name}/my_java_lib_b.jar", + "{package}/lib{name}/my_java_lib_c.jar", + ]) + +def _transitive_native_libraries_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + cc_library, + name = target_name + "/my_cc_lib_c.so", + srcs = ["cc/c.cc"], + ) + util.helper_target( + cc_library, + name = target_name + "/my_cc_lib_b.so", + srcs = ["cc/b.cc"], + ) + util.helper_target( + cc_library, + name = target_name + "/my_cc_lib_a.so", + srcs = ["cc/a.cc"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_c", + srcs = ["java/C.java"], + deps = [target_name + "/my_cc_lib_c.so"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_b", + srcs = ["java/B.java"], + deps = [target_name + "/my_cc_lib_b.so"], + ) + util.helper_target( + java_library, + name = target_name + "/my_java_lib_a", + srcs = ["java/A.java"], + deps = [ + target_name + "/my_cc_lib_a.so", + target_name + "/my_java_lib_b", + target_name + "/my_java_lib_c", + ], + ) + util.helper_target( + java_info_forwarding_rule, + name = target_name, + dep = target_name + "/my_java_lib_a", + ) + analysis_test( + name = name, + impl = _transitive_native_libraries_test_impl, + target = target_name, + # LibraryToLink.library_indentifier only available from Bazel 8 + attr_values = {"tags": ["min_bazel_8"]}, + ) + +def _transitive_native_libraries_test_impl(env, target): + assert_transitive_native_libraries = java_info_subject.from_target(env, target).transitive_native_libraries() + assert_transitive_native_libraries.static_libraries().contains_exactly_predicates([ + matching.str_matches("*my_cc_lib_a.so*"), + matching.str_matches("*my_cc_lib_b.so*"), + matching.str_matches("*my_cc_lib_c.so*"), + ]) + +def _native_libraries_propagation_test(name): + target_name = name + "/custom" + util.helper_target( + custom_java_info_rule, + name = target_name, + sources = ["A.java"], + dep_exports = [target_name + "/lib_exports"], + dep_runtime = [target_name + "/lib_runtime_deps"], + dep = [target_name + "/lib_deps"], + output_jar = target_name + ".out", + ) + util.helper_target( + java_library, + name = target_name + "/lib_deps", + srcs = ["B.java"], + deps = [target_name + "/native_deps1.so"], + ) + util.helper_target( + cc_library, + name = target_name + "/native_deps1.so", + srcs = ["a.cc"], + ) + util.helper_target( + java_library, + name = target_name + "/lib_runtime_deps", + srcs = ["C.java"], + deps = [target_name + "/native_rdeps1.so"], + ) + util.helper_target( + cc_library, + name = target_name + "/native_rdeps1.so", + srcs = ["c.cc"], + ) + util.helper_target( + java_library, + name = target_name + "/lib_exports", + srcs = ["D.java"], + deps = [target_name + "/native_exports1.so"], + ) + util.helper_target( + cc_library, + name = target_name + "/native_exports1.so", + srcs = ["e.cc"], + ) + + analysis_test( + name = name, + impl = _native_libraries_propagation_test_impl, + target = target_name, + # LibraryToLink.library_identifier only available from Bazel 8 + attr_values = {"tags": ["min_bazel_8"]}, + ) + +def _native_libraries_propagation_test_impl(env, target): + assert_transitive_native_libraries = java_info_subject.from_target(env, target).transitive_native_libraries() + assert_transitive_native_libraries.static_libraries().contains_exactly_predicates([ + matching.str_matches("*native_rdeps1.so*"), + matching.str_matches("*native_exports1.so*"), + matching.str_matches("*native_deps1.so*"), + ]).in_order() + +def _annotation_processing_test(name): + target_name = name + "/my_java_lib_a" + util.helper_target( + java_library, + name = target_name, + srcs = ["java/A.java"], + javacopts = ["-processor com.google.process.Processor"], + ) + + analysis_test( + name = name, + impl = _annotation_processing_test_impl, + target = target_name, + ) + +def _annotation_processing_test_impl(env, target): + assert_info = java_info_subject.from_target(env, target).annotation_processing() + + assert_info.class_jar().short_path_equals("{package}/lib{name}-gen.jar") + assert_info.source_jar().short_path_equals("{package}/lib{name}-gensrc.jar") + +def _compilation_info_test(name): + target_name = name + "/my_java_lib_a" + util.helper_target( + java_library, + name = target_name, + srcs = ["java/A.java"], + javacopts = ["opt1", "opt2"], + ) + analysis_test( + name = name, + impl = _compilation_info_test_impl, + target = target_name, + ) + +def _compilation_info_test_impl(env, target): + assert_info = java_info_subject.from_target(env, target).compilation_info() + + assert_info.runtime_classpath().contains_exactly(["{package}/lib{name}.jar"]) + assert_info.javac_options().contains_at_least(["opt1", "opt2"]).in_order() + +def _output_source_jars_returns_depset_test(name): + target_name = name + "/lib" + util.helper_target( + java_library, + name = target_name, + ) + + analysis_test( + name = name, + impl = _output_source_jars_returns_depset_test_impl, + target = target_name, + ) + +def _output_source_jars_returns_depset_test_impl(env, target): + source_jars = target[JavaInfo].java_outputs[0].source_jars + env.expect.that_str(type(source_jars)).equals(type(depset())) + +def _java_info_constructor_with_neverlink_test(name): + target_name = name + "/my_starlark_rule" + util.helper_target( + custom_java_info_rule, + name = target_name, + output_jar = target_name + "/my_starlark_rule_lib.jar", + neverlink = True, + ) + + analysis_test( + name = name, + impl = _java_info_constructor_with_neverlink_test_impl, + target = target_name, + ) + +def _java_info_constructor_with_neverlink_test_impl(env, target): + java_info_subject.from_target(env, target).is_neverlink().equals(True) + +def _java_common_merge_with_neverlink_test(name): + target_name = name + "/merged" + util.helper_target( + custom_java_info_rule, + name = target_name + "/with_neverlink", + output_jar = target_name + "/with_neverlink.jar", + neverlink = True, + ) + util.helper_target( + custom_java_info_rule, + name = target_name + "/without_neverlink", + output_jar = target_name + "/without_neverlink.jar", + neverlink = False, + ) + util.helper_target( + java_info_merge_rule, + name = target_name, + deps = [target_name + "/with_neverlink", target_name + "/without_neverlink"], + ) + + analysis_test( + name = name, + impl = _java_common_merge_with_neverlink_test_impl, + target = target_name, + ) + +def _java_common_merge_with_neverlink_test_impl(env, target): + java_info_subject.from_target(env, target).is_neverlink().equals(True) + +def _java_common_compile_with_neverlink_test(name): + target_name = name + "/compiled" + util.helper_target( + custom_library, + name = target_name, + srcs = ["A.java"], + neverlink = True, + ) + + analysis_test( + name = name, + impl = _java_common_compile_with_neverlink_test_impl, + target = target_name, + ) + +def _java_common_compile_with_neverlink_test_impl(env, target): + java_info_subject.from_target(env, target).is_neverlink().equals(True) + +# Tests that java_common.compile propagates native libraries from deps, +# runtime_deps, and exports. +def _java_common_compile_native_libraries_propagate_test(name): + target_name = name + "/compiled" + + util.helper_target( + cc_library, + name = target_name + "/native_dep", + srcs = ["a.cc"], + ) + util.helper_target( + java_library, + name = target_name + "/lib_dep", + srcs = ["B.java"], + deps = [target_name + "/native_dep"], + ) + + util.helper_target( + cc_library, + name = target_name + "/native_rdep", + srcs = ["c.cc"], + ) + util.helper_target( + java_library, + name = target_name + "/lib_rdep", + srcs = ["D.java"], + deps = [target_name + "/native_rdep"], + ) + + util.helper_target( + cc_library, + name = target_name + "/native_export", + srcs = ["e.cc"], + ) + util.helper_target( + java_library, + name = target_name + "/lib_export", + srcs = ["F.java"], + deps = [target_name + "/native_export"], + ) + + util.helper_target( + custom_library, + name = target_name, + srcs = ["G.java"], + deps = [target_name + "/lib_dep"], + runtime_deps = [target_name + "/lib_rdep"], + exports = [target_name + "/lib_export"], + ) + + analysis_test( + name = name, + impl = _java_common_compile_native_libraries_propagate_test_impl, + target = target_name, + ) + +def _java_common_compile_native_libraries_propagate_test_impl(env, target): + assert_native_libs = java_info_subject.from_target(env, target).transitive_native_libraries() + assert_native_libs.static_libraries().contains_exactly_predicates([ + matching.str_matches("*native_rdep*"), + matching.str_matches("*native_export*"), + matching.str_matches("*native_dep*"), + ]) + +def _test_merge_runtime_output_jars(name): + util.helper_target( + java_library, + name = name + "/a", + srcs = ["A.java"], + ) + util.helper_target( + java_library, + name = name + "/b", + srcs = ["B.java"], + ) + util.helper_target( + java_info_merge_rule, + name = name + "/merged", + deps = [name + "/a", name + "/b"], + ) + analysis_test( + name = name, + impl = _test_merge_runtime_output_jars_impl, + target = name + "/merged", + ) + +def _test_merge_runtime_output_jars_impl(env, target): + java_info_subject.from_target(env, target).runtime_output_jars().contains_exactly([ + "{package}/lib{test_name}/a.jar", + "{package}/lib{test_name}/b.jar", + ]) + +def java_info_tests(name): + test_suite( + name = name, + tests = [ + _with_output_jar_only_test, + _with_output_jar_and_use_ijar_test, + _with_output_jar_and_use_ijar_outputs_test, + _with_deps_test, + _with_runtime_deps_test, + _with_native_libraries_test, + _with_deps_and_neverlink_test, + _with_source_jars_test, + _with_packed_sourcejars_test, + _with_packed_sources_test, + _with_packed_sources_and_source_jars_test, + _with_deps_source_jars_test, + _with_runtime_deps_source_jars_test, + _with_transitive_deps_source_jars_test, + _with_transitive_runtime_deps_source_jars_test, + _with_exports_test, + _with_transitive_exports_test, + _with_transitive_deps_and_exports_test, + _with_plugins_via_exports_test, + _with_plugins_test, + _with_stamped_jar_test, + _with_jdeps_test, + _with_generated_jars_outputs_test, + _with_generated_jars_annotation_processing_test, + _with_compile_jdeps_test, + _with_native_headers_test, + _with_manifest_proto_test, + _with_compile_jar_test, + _sequence_parameters_are_type_checked_test, + _compile_jar_not_set_test, + _compile_jar_set_to_none_test, + _sources_jars_exposed_test, + _transitive_source_jars_test, + _transitive_compile_time_jars_test, + _transitive_runtime_jars_test, + _transitive_native_libraries_test, + _native_libraries_propagation_test, + _annotation_processing_test, + _compilation_info_test, + _output_source_jars_returns_depset_test, + _java_info_constructor_with_neverlink_test, + _java_common_merge_with_neverlink_test, + _java_common_compile_with_neverlink_test, + _java_common_compile_native_libraries_propagate_test, + _test_merge_runtime_output_jars, + ], + ) diff --git a/test/java/common/java_plugin_info_tests.bzl b/test/java/common/java_plugin_info_tests.bzl new file mode 100644 index 00000000..ed742f13 --- /dev/null +++ b/test/java/common/java_plugin_info_tests.bzl @@ -0,0 +1,190 @@ +"""Tests for the JavaPluginInfo provider""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_library.bzl", "java_library") +load("//java:java_plugin.bzl", "java_plugin") +load("//java/common:java_plugin_info.bzl", "JavaPluginInfo") +load("//test/java/testutil:java_info_subject.bzl", "java_plugin_info_subject") +load("//test/java/testutil:rules/custom_plugin.bzl", "custom_plugin") + +def _test_exposes_java_outputs(name): + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["Lib.java"], + ) + util.helper_target( + java_plugin, + name = name + "/dep", + srcs = ["Dep.java"], + deps = [name + "/lib"], + ) + + analysis_test( + name = name, + impl = _test_exposes_java_outputs_impl, + target = name + "/dep", + ) + +def _test_exposes_java_outputs_impl(env, target): + assert_output = java_plugin_info_subject.from_target(env, target).java_outputs().singleton() + + assert_output.class_jar().short_path_equals("{package}/lib{name}.jar") + assert_output.compile_jar().short_path_equals("{package}/lib{name}-hjar.jar") + assert_output.source_jars().contains_exactly(["{package}/lib{name}-src.jar"]) + assert_output.jdeps().short_path_equals("{package}/lib{name}.jdeps") + assert_output.compile_jdeps().short_path_equals("{package}/lib{name}-hjar.jdeps") + +def _test_provider_contstructor(name): + target_name = name + "/plugin" + util.helper_target( + java_library, + name = target_name + "/plugin_dep1", + srcs = ["A.java"], + data = ["depfile1.dat"], + ) + util.helper_target( + custom_plugin, + name = target_name, + data = ["pluginfile1.dat"], + processor_class = "com.google.process.stuff", + deps = [target_name + "/plugin_dep1"], + ) + + analysis_test( + name = name, + impl = _test_provider_contstructor_impl, + target = target_name, + ) + +def _test_provider_contstructor_impl(env, target): + assert_plugin_data = java_plugin_info_subject.from_target(env, target).plugins() + assert_plugin_data.processor_classes().contains_exactly(["com.google.process.stuff"]) + assert_plugin_data.processor_jars().contains_exactly([ + "{package}/{name}/lib.jar", + "{package}/lib{name}/plugin_dep1.jar", + ]) + assert_plugin_data.processor_data().contains_exactly(["{package}/pluginfile1.dat"]) + + assert_api_plugin_data = java_plugin_info_subject.from_target(env, target).api_generating_plugins() + assert_api_plugin_data.processor_classes().contains_exactly([]) + assert_api_plugin_data.processor_jars().contains_exactly([]) + assert_api_plugin_data.processor_data().contains_exactly([]) + +def _test_api_generating_provider_constructor(name): + target_name = name + "/plugin" + util.helper_target( + java_library, + name = target_name + "/plugin_dep1", + srcs = ["A.java"], + data = ["depfile1.dat"], + ) + util.helper_target( + custom_plugin, + name = target_name, + data = ["pluginfile1.dat"], + processor_class = "com.google.process.stuff", + deps = [target_name + "/plugin_dep1"], + generates_api = True, + ) + + analysis_test( + name = name, + impl = _test_api_generating_provider_constructor_impl, + target = target_name, + ) + +def _test_api_generating_provider_constructor_impl(env, target): + assert_api_plugin_data = java_plugin_info_subject.from_target(env, target).api_generating_plugins() + assert_api_plugin_data.processor_classes().contains_exactly(["com.google.process.stuff"]) + assert_api_plugin_data.processor_jars().contains_exactly([ + "{package}/{name}/lib.jar", + "{package}/lib{name}/plugin_dep1.jar", + ]) + assert_api_plugin_data.processor_data().contains_exactly(["{package}/pluginfile1.dat"]) + assert_api_plugin_data.equals(target[JavaPluginInfo].plugins) + +def _test_without_processor_class(name): + target_name = name + "/plugin" + util.helper_target( + java_library, + name = target_name + "/plugin_dep1", + srcs = ["A.java"], + data = ["depfile1.dat"], + ) + util.helper_target( + custom_plugin, + name = target_name, + processor_class = None, + data = ["pluginfile1.dat"], + deps = [target_name + "/plugin_dep1"], + ) + analysis_test( + name = name, + impl = _test_without_processor_class_impl, + target = target_name, + ) + +def _test_without_processor_class_impl(env, target): + assert_plugin_data = java_plugin_info_subject.from_target(env, target).plugins() + assert_plugin_data.processor_classes().contains_exactly([]) + assert_plugin_data.processor_jars().contains_exactly([ + "{package}/{name}/lib.jar", + "{package}/lib{name}/plugin_dep1.jar", + ]) + assert_plugin_data.processor_data().contains_exactly(["{package}/pluginfile1.dat"]) + + assert_api_plugin_data = java_plugin_info_subject.from_target(env, target).api_generating_plugins() + assert_api_plugin_data.processor_classes().contains_exactly([]) + assert_api_plugin_data.processor_jars().contains_exactly([]) + assert_api_plugin_data.processor_data().contains_exactly([]) + +def _test_constructor_with_data_depset(name): + target_name = name + "/plugin" + util.helper_target( + java_library, + name = target_name + "/plugin_dep1", + srcs = ["A.java"], + data = ["depfile1.dat"], + ) + util.helper_target( + custom_plugin, + name = target_name, + processor_class = "com.google.process.stuff", + data = ["pluginfile1.dat"], + deps = [target_name + "/plugin_dep1"], + data_as_depset = True, + ) + + analysis_test( + name = name, + impl = _test_constructor_with_data_depset_impl, + target = target_name, + ) + +def _test_constructor_with_data_depset_impl(env, target): + assert_plugin_data = java_plugin_info_subject.from_target(env, target).plugins() + assert_plugin_data.processor_classes().contains_exactly(["com.google.process.stuff"]) + assert_plugin_data.processor_jars().contains_exactly([ + "{package}/{name}/lib.jar", + "{package}/lib{name}/plugin_dep1.jar", + ]) + assert_plugin_data.processor_data().contains_exactly(["{package}/pluginfile1.dat"]) + + assert_api_plugin_data = java_plugin_info_subject.from_target(env, target).api_generating_plugins() + assert_api_plugin_data.processor_classes().contains_exactly([]) + assert_api_plugin_data.processor_jars().contains_exactly([]) + assert_api_plugin_data.processor_data().contains_exactly([]) + +def java_plugin_info_tests(name): + test_suite( + name = name, + tests = [ + _test_exposes_java_outputs, + _test_provider_contstructor, + _test_api_generating_provider_constructor, + _test_without_processor_class, + _test_constructor_with_data_depset, + ], + ) diff --git a/test/java/common/rules/BUILD b/test/java/common/rules/BUILD new file mode 100644 index 00000000..97589249 --- /dev/null +++ b/test/java/common/rules/BUILD @@ -0,0 +1,59 @@ +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_library.bzl", "java_library") +load(":add_exports_tests.bzl", "add_exports_tests") +load(":common_launcher_java_binary_tests.bzl", "java_binary_launcher_tests") +load(":common_launcher_java_library_tests.bzl", "java_library_launcher_tests") +load(":deploy_archive_builder_tests.bzl", "deploy_archive_builder_test_suite") +load(":java_binary_tests.bzl", "java_binary_tests") +load(":java_import_tests.bzl", "java_import_tests") +load(":java_launcher_tests.bzl", "java_launcher_tests") +load(":java_library_tests.bzl", "java_library_tests") +load(":java_plugin_tests.bzl", "java_plugin_tests") +load(":java_single_jar_tests.bzl", "java_single_jar_tests") +load(":java_test_tests.bzl", "java_test_tests") +load(":merge_attrs_tests.bzl", "merge_attrs_test_suite") + +package(default_applicable_licenses = ["@rules_java//:license"]) + +deploy_archive_builder_test_suite(name = "deploy_archive_builder_tests") + +merge_attrs_test_suite(name = "merge_attrs_tests") + +java_binary_tests(name = "java_binary_tests") + +java_binary_launcher_tests(name = "java_binary_launcher_tests") + +java_plugin_tests(name = "java_plugin_tests") + +java_library_tests(name = "java_library_tests") + +java_library_launcher_tests(name = "java_library_launcher_tests") + +java_launcher_tests(name = "java_launcher_tests") + +java_import_tests(name = "java_import_tests") + +java_single_jar_tests(name = "java_single_jar_tests") + +java_test_tests(name = "java_test_tests") + +add_exports_tests(name = "add_exports_tests") + +# Used for tests. +util.helper_target( + java_library, + name = "module_javacopts_helper", + srcs = [ + "InputFile.java", + "InputFile2.java", + ], + add_exports = [ + "export/one", + "export/two", + ], + add_opens = [ + "open/one", + "open/two", + ], + visibility = ["//test/java:__subpackages__"], +) diff --git a/test/java/common/rules/add_exports_tests.bzl b/test/java/common/rules/add_exports_tests.bzl new file mode 100644 index 00000000..482e7311 --- /dev/null +++ b/test/java/common/rules/add_exports_tests.bzl @@ -0,0 +1,39 @@ +"""Tests for the add_exports attribute""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:util.bzl", "util") +load("//java:defs.bzl", "java_library") +load("//test/java/testutil:java_info_subject.bzl", "java_info_subject") +load("//test/java/testutil:rules/java_info_merge.bzl", "java_info_merge_rule") + +def _test_merge_add_exports(name): + util.helper_target( + java_info_merge_rule, + name = name + "/merge", + deps = [name + "/a"], + ) + util.helper_target( + java_library, + name = name + "/a", + srcs = ["A.java"], + add_exports = ["java.base/java.lang"], + ) + + analysis_test( + name = name, + impl = _test_merge_add_exports_impl, + target = name + "/merge", + ) + +def _test_merge_add_exports_impl(env, target): + java_info_subject.from_target(env, target).module_flags().add_exports().contains_exactly( + ["java.base/java.lang"], + ) + +def add_exports_tests(name): + test_suite( + name = name, + tests = [ + _test_merge_add_exports, + ], + ) diff --git a/test/java/common/rules/common_launcher_java_binary_tests.bzl b/test/java/common/rules/common_launcher_java_binary_tests.bzl new file mode 100644 index 00000000..572f27a0 --- /dev/null +++ b/test/java/common/rules/common_launcher_java_binary_tests.bzl @@ -0,0 +1,32 @@ +"""Parameterized tests for java_binary with --java_launcher""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_binary.bzl", "java_binary") + +def _test_java_binary_non_executable_rule_outputs(name): + util.helper_target( + java_binary, + name = name + "/test_app_noexec", + srcs = ["InputFile.java"], + create_executable = 0, + ) + + analysis_test( + name = name, + impl = _test_java_binary_non_executable_rule_outputs_impl, + target = name + "/test_app_noexec", + ) + +def _test_java_binary_non_executable_rule_outputs_impl(env, target): + env.expect.that_target(target).default_outputs().contains_exactly([ + "{package}/{name}.jar", + ]) + +def java_binary_launcher_tests(name): + test_suite( + name = name, + tests = [ + _test_java_binary_non_executable_rule_outputs, + ], + ) diff --git a/test/java/common/rules/common_launcher_java_library_tests.bzl b/test/java/common/rules/common_launcher_java_library_tests.bzl new file mode 100644 index 00000000..c66256ec --- /dev/null +++ b/test/java/common/rules/common_launcher_java_library_tests.bzl @@ -0,0 +1,1134 @@ +"""Parameterized tests for java_library with --java_launcher""" + +load("@bazel_features//:features.bzl", "bazel_features") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_import.bzl", "java_import") +load("//java:java_library.bzl", "java_library") +load("//java:java_plugin.bzl", "java_plugin") +load("//java/toolchains:java_runtime.bzl", "java_runtime") +load("//java/toolchains:java_toolchain.bzl", "java_toolchain") +load("//test/java/testutil:helper.bzl", "always_passes") +load("//test/java/testutil:java_info_subject.bzl", "java_info_subject") +load("//test/java/testutil:javac_action_subject.bzl", "javac_action_subject") +load("//test/java/testutil:mock_java_toolchain.bzl", "mock_java_toolchain") +load("//test/java/testutil:rules/custom_library_with_bootclasspath.bzl", "custom_bootclasspath") + +def _test_java_library_rule_outputs(name): + util.helper_target( + java_library, + name = name + "/test_lib", + srcs = ["A.java"], + ) + + analysis_test( + name = name, + impl = _test_java_library_rule_outputs_impl, + target = name + "/test_lib", + ) + +def _test_java_library_rule_outputs_impl(env, target): + env.expect.that_target(target).default_outputs().contains_exactly([ + "{package}/lib{name}.jar", + ]) + +def _test_java_library_action_graph(name): + util.helper_target( + java_library, + name = name + "/test_lib", + srcs = [ + "Util.java", + "Util2.java", + ], + javacopts = [ + "-g", + "-encoding", + "utf8", + ], + ) + + analysis_test( + name = name, + impl = _test_java_library_action_graph_impl, + target = name + "/test_lib", + ) + +def _test_java_library_action_graph_impl(env, target): + javac_action = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + javac_action.inputs().contains_at_least([ + "{package}/Util.java", + "{package}/Util2.java", + ]) + javac_action.javacopts().contains_at_least([ + "-g", + "-encoding", + "utf8", + ]) + +def _test_java_library_deps_of_genrule_are_not_on_classpath(name): + util.helper_target( + java_library, + name = name + "/root_dep", + srcs = ["test.java"], + ) + util.helper_target( + native.genrule, + name = name + "/has_java_dep", + outs = [name + "_foo.jar"], + cmd = "echo NOT EXECUTED", + tools = [name + "/root_dep"], + ) + util.helper_target( + java_import, + name = name + "/has_java_dep_import", + jars = [name + "/has_java_dep"], + ) + util.helper_target( + java_library, + name = name + "/library", + srcs = ["dummy.java"], + deps = [name + "/has_java_dep_import"], + ) + + analysis_test( + name = name, + impl = _test_java_library_deps_of_genrule_are_not_on_classpath_impl, + target = name + "/library", + ) + +def _test_java_library_deps_of_genrule_are_not_on_classpath_impl(env, target): + expected_classpath = "{bin_path}/{package}/_ijar/{test_name}/has_java_dep_import/{package}/{test_name}_foo-ijar.jar" + javac_action_subject.of(env, target, "{package}/lib{name}.jar").classpath().contains_exactly([expected_classpath]) + +def _test_java_library_compile_and_run_time_paths(name): + util.helper_target( + java_library, + name = name + "/base", + srcs = ["Base.java"], + ) + util.helper_target( + java_library, + name = name + "/specialization", + srcs = ["Specialization.java"], + deps = [name + "/base"], + ) + + analysis_test( + name = name, + impl = _test_java_library_compile_and_run_time_paths_impl, + targets = { + "base": name + "/base", + "specialization": name + "/specialization", + }, + ) + +def _test_java_library_compile_and_run_time_paths_impl(env, targets): + base_info = java_info_subject.from_target(env, targets.base) + base_info.compilation_args().transitive_runtime_jars().contains_exactly(["{package}/lib{name}.jar"]) + base_info.compilation_args().transitive_compile_time_jars().contains_exactly(["{package}/lib{name}-hjar.jar"]) + base_info.compilation_args().compile_jars().contains_exactly(["{package}/lib{name}-hjar.jar"]) + + base_jar = "{package}/lib{test_name}/base.jar" + base_hjar = "{package}/lib{test_name}/base-hjar.jar" + + specialization_info = java_info_subject.from_target(env, targets.specialization) + specialization_info.compilation_args().transitive_runtime_jars().contains_exactly([ + base_jar, + "{package}/lib{name}.jar", + ]) + specialization_info.compilation_args().transitive_compile_time_jars().contains_exactly([ + base_hjar, + "{package}/lib{name}-hjar.jar", + ]) + specialization_info.compilation_args().compile_jars().contains_exactly(["{package}/lib{name}-hjar.jar"]) + +def _test_java_library_files_to_compile(name): + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["Lib.java"], + ) + + analysis_test( + name = name, + impl = _test_java_library_files_to_compile_impl, + target = name + "/lib", + ) + +def _test_java_library_files_to_compile_impl(env, target): + env.expect.that_target(target).output_group("compilation_outputs").contains_exactly(["{package}/lib{name}.jar"]) + +def _test_java_library_runtime_deps_are_not_on_classpath(name): + util.helper_target( + java_library, + name = name + "/runtime_java_dep", + srcs = ["test.java"], + ) + util.helper_target( + java_library, + name = name + "/compile_dep", + srcs = ["compile.java"], + ) + util.helper_target( + java_library, + name = name + "/depends_on_runtimedep", + srcs = ["dummy.java"], + runtime_deps = [name + "/runtime_java_dep"], + deps = [name + "/compile_dep"], + ) + + analysis_test( + name = name, + impl = _test_java_library_runtime_deps_are_not_on_classpath_impl, + target = name + "/depends_on_runtimedep", + ) + +def _test_java_library_runtime_deps_are_not_on_classpath_impl(env, target): + expected_compile = "{bin_path}/{package}/lib{test_name}/compile_dep-hjar.jar" + javac_action_subject.of(env, target, "{package}/lib{name}.jar").classpath().contains_exactly([expected_compile]) + +def _test_java_library_runtime_deps_are_not_on_classpath_with_header_compilation(name): + util.helper_target( + java_library, + name = name + "/runtime_java_dep", + srcs = ["test.java"], + ) + util.helper_target( + java_library, + name = name + "/compile_dep", + srcs = ["compile.java"], + ) + util.helper_target( + java_library, + name = name + "/depends_on_runtimedep", + srcs = ["dummy.java"], + runtime_deps = [name + "/runtime_java_dep"], + deps = [name + "/compile_dep"], + ) + + analysis_test( + name = name, + config_settings = { + "//command_line_option:java_header_compilation": True, + }, + impl = _test_java_library_runtime_deps_are_not_on_classpath_with_header_compilation_impl, + target = name + "/depends_on_runtimedep", + ) + +def _test_java_library_runtime_deps_are_not_on_classpath_with_header_compilation_impl(env, target): + expected_compile = "{bin_path}/{package}/lib{test_name}/compile_dep-hjar.jar" + javac_action_subject.of(env, target, "{package}/lib{name}.jar").classpath().contains_exactly([expected_compile]) + +def _test_java_library_annotation_processing_using_javacopt(name): + util.helper_target( + java_library, + name = name + "/to_be_processed", + srcs = ["ToBeProcessed.java"], + javacopts = ["-processor com.google.process.Processor"], + ) + + analysis_test( + name = name, + impl = _test_java_library_annotation_processing_using_javacopt_impl, + target = name + "/to_be_processed", + ) + +def _test_java_library_annotation_processing_using_javacopt_impl(env, target): + javac_action = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + javac_action.argv().contains("--generated_sources_output") + javac_action.generated_sources_output().contains("{bin_path}/{package}/lib{name}-gensrc.jar") + javac_action.javacopts().contains("-processor") + javac_action.javacopts().contains("com.google.process.Processor") + + # The compile action should have a gensrc jar output + javac_action.outputs().contains("{package}/lib{name}-gensrc.jar") + + # The gensrc jar should be an input to the source jar action + src_jar_action = env.expect.that_target(target).action_generating("{package}/lib{name}-src.jar") + src_jar_action.inputs().contains("{package}/lib{name}-gensrc.jar") + +def _test_java_library_javacopts_with_location_expansion(name): + util.helper_target( + java_library, + name = name + "/patch", + srcs = ["A.java"], + ) + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["ToBeProcessed.java"], + javacopts = ["--patch $(execpath " + name + "/patch)"], + deps = [name + "/patch"], + ) + + analysis_test( + name = name, + impl = _test_java_library_javacopts_with_location_expansion_impl, + target = name + "/lib", + ) + +def _test_java_library_javacopts_with_location_expansion_impl(env, target): + javac_action = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + javac_action.javacopts().contains_at_least([ + "--patch", + "{bin_path}/{package}/lib{test_name}/patch.jar", + ]) + +def _test_java_library_invalid_plugin(name): + util.helper_target( + java_library, + name = name + "/not_a_plugin", + srcs = ["NotAPlugin.java"], + ) + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["Lib.java"], + plugins = [name + "/not_a_plugin"], + ) + + analysis_test( + name = name, + expect_failure = True, + impl = _test_java_library_invalid_plugin_impl, + target = name + "/lib", + ) + +def _test_java_library_invalid_plugin_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.contains("does not have mandatory providers: 'JavaPluginInfo'"), + ) + +def _test_java_library_plugin_with_runtime_deps(name): + util.helper_target( + java_library, + name = name + "/runtime_lib", + srcs = ["Runtime.java"], + ) + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["Lib.java"], + runtime_deps = [name + "/runtime_lib"], + ) + util.helper_target( + java_plugin, + name = name + "/plugin", + srcs = ["Plugin.java"], + processor_class = "com.example.process.stuff", + deps = [name + "/lib"], + ) + util.helper_target( + java_library, + name = name + "/leaf_lib", + srcs = ["LeafLib.java"], + plugins = [name + "/plugin"], + ) + + analysis_test( + name = name, + impl = _test_java_library_plugin_with_runtime_deps_impl, + target = name + "/leaf_lib", + ) + +def _test_java_library_plugin_with_runtime_deps_impl(env, target): + javac_action = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + javac_action.processorpath().contains_exactly_predicates([ + matching.str_matches("*/plugin.jar"), + matching.str_matches("*/lib.jar"), + matching.str_matches("*/runtime_lib.jar"), + ]) + +def _test_java_library_source_jar_without_annotation_processing(name): + util.helper_target( + java_library, + name = name + "/foo", + srcs = ["Foo.java"], + ) + + analysis_test( + name = name, + impl = _test_java_library_source_jar_without_annotation_processing_impl, + target = name + "/foo", + ) + +def _test_java_library_source_jar_without_annotation_processing_impl(env, target): + javac_action = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + javac_action.argv().not_contains("--generated_sources_output") + javac_action.outputs().contains_exactly([ + "{package}/lib{name}.jar", + "{package}/lib{name}.jdeps", + "{package}/lib{name}-native-header.jar", + "{package}/lib{name}.jar_manifest_proto", + ]) + + src_jar_action = javac_action_subject.of(env, target, "{package}/lib{name}-src.jar") + src_jar_action.outputs().contains_exactly([ + "{package}/lib{name}-src.jar", + ]) + +def _test_java_library_source_jars_with_source_jars(name): + util.helper_target( + java_library, + name = name + "/beatit", + srcs = [ + "Plugin.java", + "Some.srcjar", + ], + ) + + analysis_test( + name = name, + impl = _test_java_library_source_jars_with_source_jars_impl, + target = name + "/beatit", + ) + +def _test_java_library_source_jars_with_source_jars_impl(env, target): + src_jar_action = javac_action_subject.of(env, target, "{package}/lib{name}-src.jar") + src_jar_action.inputs().contains_at_least([ + "{package}/Plugin.java", + "{package}/Some.srcjar", + ]) + src_jar_action.sources().contains_exactly([ + "{package}/Some.srcjar", + ]) + src_jar_action.resources().contains_predicate( + matching.str_matches("*/Plugin.java:*rules/Plugin.java"), + ) + +def _test_java_library_should_set_bootclasspath(name): + boot_jar = util.empty_file(name + "/boot.jar") + util.helper_target( + custom_bootclasspath, + name = name + "/mock_bootclasspath", + bootclasspath = [boot_jar], + ) + + util.helper_target( + java_runtime, + name = name + "/runtime", + ) + + util.helper_target( + java_toolchain, + name = name + "/mock_toolchain_impl", + bootclasspath = [name + "/mock_bootclasspath"], + genclass = name + "/genclass", + header_compiler = name + "/header_compiler", + header_compiler_direct = name + "/header_compiler_direct", + ijar = name + "/ijar", + java_runtime = name + "/runtime", + javabuilder = name + "/javabuilder", + singlejar = name + "/singlejar", + ) + util.helper_target( + native.toolchain, + name = name + "/toolchain", + toolchain = name + "/mock_toolchain_impl", + toolchain_type = "@bazel_tools//tools/jdk:toolchain_type", + ) + util.helper_target( + java_library, + name = name + "/test_lib", + srcs = ["A.java"], + ) + + analysis_test( + name = name, + config_settings = { + "//command_line_option:extra_toolchains": [ + native.package_relative_label(name + "/toolchain"), + ], + }, + impl = _test_java_library_should_set_bootclasspath_impl, + target = name + "/test_lib", + ) + +def _test_java_library_should_set_bootclasspath_impl(env, target): + javac_action = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + + javac_action.bootclasspath().contains_exactly([ + "{bin_path}/{package}/{test_name}/boot.jar", + ]) + +def _test_java_library_command_line_contains_target_label_and_rule_kind(name): + util.helper_target( + java_library, + name = name + "/test_lib", + srcs = ["A.java"], + ) + + analysis_test( + name = name, + impl = _test_java_library_command_line_contains_target_label_and_rule_kind_impl, + target = name + "/test_lib", + ) + +def _test_java_library_command_line_contains_target_label_and_rule_kind_impl(env, target): + javac_action = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + javac_action.target_label().contains_exactly(["//{package}:{name}"]) + +def _test_java_library_propagates_native_libraries(name): + util.helper_target( + cc_library, + name = name + "/native_deps1.so", + srcs = ["a.cc"], + ) + util.helper_target( + java_library, + name = name + "/lib_deps", + srcs = ["B.java"], + deps = [name + "/native_deps1.so"], + ) + util.helper_target( + cc_library, + name = name + "/native_deps2.so", + srcs = ["b.cc"], + ) + util.helper_target( + cc_library, + name = name + "/native_rdeps1.so", + srcs = ["c.cc"], + ) + util.helper_target( + java_library, + name = name + "/lib_runtime_deps", + srcs = ["C.java"], + deps = [name + "/native_rdeps1.so"], + ) + util.helper_target( + cc_library, + name = name + "/native_rdeps2.so", + srcs = ["d.cc"], + ) + util.helper_target( + cc_library, + name = name + "/native_exports1.so", + srcs = ["e.cc"], + ) + util.helper_target( + java_library, + name = name + "/lib_exports", + srcs = ["D.java"], + deps = [name + "/native_exports1.so"], + ) + util.helper_target( + cc_library, + name = name + "/native_exports2.so", + srcs = ["f.cc"], + ) + util.helper_target( + cc_library, + name = name + "/native_data1.so", + srcs = ["g.cc"], + ) + util.helper_target( + java_library, + name = name + "/lib_data", + srcs = ["E.java"], + deps = [name + "/native_data1.so"], + ) + util.helper_target( + cc_library, + name = name + "/native_data2.so", + srcs = ["h.cc"], + ) + util.helper_target( + java_library, + name = name + "/top", + srcs = ["A.java"], + data = [ + name + "/lib_data", + name + "/native_data2.so", + ], + exports = [ + name + "/lib_exports", + name + "/native_exports2.so", + ], + runtime_deps = [ + name + "/lib_runtime_deps", + name + "/native_rdeps2.so", + ], + deps = [ + name + "/lib_deps", + name + "/native_deps2.so", + ], + ) + + analysis_test( + name = name, + impl = _test_java_library_propagates_native_libraries_impl, + target = name + "/top", + ) + +def _test_java_library_propagates_native_libraries_impl(env, target): + java_info_subject.from_target(env, target).transitive_native_libraries().static_libraries().contains_exactly_predicates([ + # Windows platforms use .lib extension for static libraries. + matching.is_in(["libnative_rdeps2.so.a", "native_rdeps2.so.lib"]), + matching.is_in(["libnative_exports2.so.a", "native_exports2.so.lib"]), + matching.is_in(["libnative_deps2.so.a", "native_deps2.so.lib"]), + matching.is_in(["libnative_rdeps1.so.a", "native_rdeps1.so.lib"]), + matching.is_in(["libnative_exports1.so.a", "native_exports1.so.lib"]), + matching.is_in(["libnative_deps1.so.a", "native_deps1.so.lib"]), + ]) + +def _test_java_library_gen_source_no_processor_names(name): + util.helper_target( + java_plugin, + name = name + "/plugin", + srcs = ["AnnotationProcessor.java"], + ) + util.helper_target( + java_library, + name = name + "/to_be_processed", + srcs = ["ToBeProcessed.java"], + plugins = [name + "/plugin"], + ) + + analysis_test( + name = name, + impl = _test_java_library_gen_source_no_processor_names_impl, + target = name + "/to_be_processed", + ) + +def _test_java_library_gen_source_no_processor_names_impl(env, target): + javac_action = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + + # The compile action should not have a gensrc jar output even though it has a plugin, + # since the plugin doesn't define a processor. + javac_action.outputs().contains_exactly([ + "{package}/lib{name}.jar", + "{package}/lib{name}.jdeps", + "{package}/lib{name}-native-header.jar", + "{package}/lib{name}.jar_manifest_proto", + ]) + +def _test_java_library_compilation_info_provider(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + bootclasspath = [name + "/boot.jar"], + ) + + util.helper_target( + java_library, + name = name + "/test_lib", + srcs = ["A.java"], + ) + + analysis_test( + name = name, + config_settings = { + "//command_line_option:extra_toolchains": [ + native.package_relative_label(name + "/toolchain"), + ], + }, + impl = _test_java_library_compilation_info_provider_impl, + target = name + "/test_lib", + ) + +def _test_java_library_compilation_info_provider_impl(env, target): + java_info_subject.from_target(env, target).compilation_info().boot_classpath().contains_exactly([ + "{package}/{test_name}/boot.jar", + ]) + +def _test_java_library_native_header_outputs(name): + util.helper_target( + java_library, + name = name + "/jni", + srcs = [ + "Bar.java", + "Foo.java", + ], + ) + + analysis_test( + name = name, + impl = _test_java_library_native_header_outputs_impl, + target = name + "/jni", + ) + +def _test_java_library_native_header_outputs_impl(env, target): + javac_action = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + javac_action.native_header_output().contains_exactly([ + "{bin_path}/{package}/lib{name}-native-header.jar", + ]) + javac_action.outputs().contains("{package}/lib{name}-native-header.jar") + java_info_subject.from_target(env, target).outputs().native_headers().contains_exactly([ + "{package}/lib{name}-native-header.jar", + ]) + +def _test_java_library_module_javacopts(name): + analysis_test( + name = name, + impl = _test_java_library_module_javacopts_impl, + target = "//test/java/common/rules:module_javacopts_helper", + ) + +def _test_java_library_module_javacopts_impl(env, target): + javac_action = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + javac_action.javacopts().contains_at_least([ + "--add-exports=export/one=ALL-UNNAMED", + "--add-exports=export/two=ALL-UNNAMED", + ]) + + java_info = java_info_subject.from_target(env, target) + java_info.module_flags().add_exports().contains_exactly([ + "export/one", + "export/two", + ]) + java_info.module_flags().add_opens().contains_exactly([ + "open/one", + "open/two", + ]) + +def _test_java_library_forwarded_deps(name): + util.helper_target( + java_library, + name = name + "/a", + srcs = ["a.java"], + ) + util.helper_target( + java_library, + name = name + "/b1", + exports = [name + "/a"], + ) + util.helper_target( + java_library, + name = name + "/b2", + exports = [name + "/a"], + ) + util.helper_target( + java_library, + name = name + "/c1", + srcs = ["c1.java"], + deps = [name + "/b1"], + ) + util.helper_target( + java_library, + name = name + "/c2", + srcs = ["c2.java"], + deps = [name + "/b2"], + ) + + analysis_test( + name = name, + impl = _test_java_library_forwarded_deps_impl, + targets = { + "a": name + "/a", + "c1": name + "/c1", + "c2": name + "/c2", + }, + ) + +def _test_java_library_forwarded_deps_impl(env, targets): + a_info = java_info_subject.from_target(env, targets.a) + c1_info = java_info_subject.from_target(env, targets.c1) + c2_info = java_info_subject.from_target(env, targets.c2) + + a_jars = a_info.compilation_args().actual.compile_jars.to_list() + c1_info.compilation_info().compilation_classpath().contains_at_least(a_jars) + c2_info.compilation_info().compilation_classpath().contains_at_least(a_jars) + +# Regression test for b/5773894 +def _test_java_library_transitive_strict_deps(name): + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + always_passes(name) + return + + util.helper_target( + java_library, + name = name + "/c", + srcs = ["C.java"], + ) + util.helper_target( + java_library, + name = name + "/b", + srcs = ["B.java"], + deps = [name + "/c"], + ) + util.helper_target( + java_library, + name = name + "/a", + exports = [name + "/b"], + ) + + analysis_test( + name = name, + config_settings = { + "//command_line_option:experimental_strict_java_deps": "ERROR", + }, + impl = _test_java_library_transitive_strict_deps_impl, + target = name + "/a", + ) + +def _test_java_library_transitive_strict_deps_impl(env, target): + a_info = java_info_subject.from_target(env, target) + a_info.compilation_args().compile_jars().contains_exactly([ + "{package}/lib{test_name}/b-hjar.jar", + ]) + +def _test_java_library_emit_output_deps(name): + util.helper_target( + java_library, + name = name + "/b", + srcs = ["B.java"], + ) + util.helper_target( + java_library, + name = name + "/a", + exports = [name + "/b"], + ) + + analysis_test( + name = name, + config_settings = { + "//command_line_option:java_deps": True, + }, + impl = _test_java_library_emit_output_deps_impl, + targets = { + "a": name + "/a", + "b": name + "/b", + }, + ) + +def _test_java_library_emit_output_deps_impl(env, targets): + javac_action_subject.of(env, targets.b, "{package}/lib{name}.jar").outputs().contains( + "{package}/lib{name}.jdeps", + ) + + javac_action_subject.of(env, targets.a, "{package}/lib{name}.jar").outputs().contains_exactly([ + "{package}/lib{name}.jar", + "{package}/lib{name}-native-header.jar", + "{package}/lib{name}.jar_manifest_proto", + ]) + +def _test_java_library_deps_without_srcs(name): + util.helper_target( + java_library, + name = name + "/b", + srcs = ["B.java"], + ) + util.helper_target( + java_library, + name = name + "/a", + deps = [name + "/b"], + ) + + analysis_test( + name = name, + expect_failure = True, + impl = _test_java_library_deps_without_srcs_impl, + target = name + "/a", + ) + +def _test_java_library_deps_without_srcs_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.contains("deps not allowed without srcs"), + ) + +def _test_dependency_artifacts_with_exports(name): + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + always_passes(name) + return + + util.helper_target( + java_library, + name = name + "/e", + srcs = ["E.java"], + ) + util.helper_target( + java_library, + name = name + "/d", + srcs = ["D.java"], + ) + util.helper_target( + java_library, + name = name + "/c", + srcs = ["C.java"], + exports = [name + "/e"], + ) + util.helper_target( + java_library, + name = name + "/b", + exports = [name + "/d"], + ) + util.helper_target( + java_library, + name = name + "/a", + srcs = ["A.java"], + deps = [ + name + "/b", + name + "/c", + name + "/d", + ], + ) + + analysis_test( + name = name, + config_settings = { + "//command_line_option:experimental_java_classpath": "javabuilder", + }, + impl = _test_dependency_artifacts_with_exports_impl, + target = name + "/a", + ) + +def _test_dependency_artifacts_with_exports_impl(env, target): + javac_action = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + javac_action.deps_artifacts().contains_exactly([ + "{bin_path}/{package}/lib{test_name}/c-hjar.jdeps", + "{bin_path}/{package}/lib{test_name}/d-hjar.jdeps", + "{bin_path}/{package}/lib{test_name}/e-hjar.jdeps", + ]) + +def _test_exports_are_indirect_not_direct(name): + util.helper_target( + java_library, + name = name + "/a", + srcs = ["a.java"], + ) + util.helper_target( + java_library, + name = name + "/b", + srcs = ["b.java"], + exports = [name + "/a"], + ) + util.helper_target( + java_library, + name = name + "/c", + srcs = ["c.java"], + deps = [name + "/b"], + ) + + analysis_test( + name = name, + impl = _test_exports_are_indirect_not_direct_impl, + targets = { + "b": name + "/b", + "c": name + "/c", + }, + ) + +def _test_exports_are_indirect_not_direct_impl(env, targets): + b_info = java_info_subject.from_target(env, targets.b) + c_info = java_info_subject.from_target(env, targets.c) + + b_info.compilation_info().compilation_classpath().contains_exactly([]) + + c_info.compilation_info().compilation_classpath().contains_at_least_predicates([ + matching.file_basename_equals("a-hjar.jar"), + matching.file_basename_equals("b-hjar.jar"), + ]) + +def _test_exports_runfiles(name): + util.helper_target( + java_library, + name = name + "/a", + srcs = ["a.java"], + data = ["data.txt"], + ) + util.helper_target( + java_library, + name = name + "/b", + srcs = ["b.java"], + exports = [name + "/a"], + ) + + analysis_test( + name = name, + impl = _test_exports_runfiles_impl, + target = name + "/b", + ) + +def _test_exports_runfiles_impl(env, target): + env.expect.that_target(target).runfiles().contains_exactly([ + "{workspace}/{package}/data.txt", + "{workspace}/{package}/lib{test_name}/a.jar", + "{workspace}/{package}/lib{test_name}/b.jar", + ]) + +def _test_exports_collect_source_jars(name): + util.helper_target( + java_library, + name = name + "/exp", + srcs = ["C.java"], + ) + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["B.java"], + exports = [name + "/exp"], + ) + util.helper_target( + java_library, + name = name + "/a", + srcs = ["A.java"], + deps = [name + "/lib"], + ) + + analysis_test( + name = name, + impl = _test_exports_collect_source_jars_impl, + target = name + "/a", + ) + +def _test_exports_collect_source_jars_impl(env, target): + java_info_subject.from_target(env, target).transitive_source_jars().contains_exactly([ + "{package}/lib{test_name}/a-src.jar", + "{package}/lib{test_name}/lib-src.jar", + "{package}/lib{test_name}/exp-src.jar", + ]) + +def _test_exported_plugins_are_inherited(name): + util.helper_target( + java_plugin, + name = name + "/plugin", + srcs = ["Plugin.java"], + processor_class = "com.example.process.stuff", + ) + util.helper_target( + java_library, + name = name + "/exporting_lib", + srcs = ["ExportingLib.java"], + exported_plugins = [name + "/plugin"], + ) + util.helper_target( + java_library, + name = name + "/consuming_lib", + srcs = ["ConsumingLib.java"], + deps = [name + "/exporting_lib"], + ) + util.helper_target( + java_library, + name = name + "/leaf_lib", + srcs = ["LeafLib.java"], + deps = [name + "/consuming_lib"], + ) + + analysis_test( + name = name, + impl = _test_exported_plugins_are_inherited_impl, + targets = { + "consuming": name + "/consuming_lib", + "leaf": name + "/leaf_lib", + }, + ) + +def _test_exported_plugins_are_inherited_impl(env, targets): + # libconsuming_lib should include the plugin, since it directly depends on exporting_lib + javac_action_subject.of( + env, + targets.consuming, + "{package}/lib{test_name}/consuming_lib.jar", + ).processors().contains_exactly(["com.example.process.stuff"]) + + # but libleaf_lib should not, because its dependency is transitive. + javac_action_subject.of( + env, + targets.leaf, + "{package}/lib{test_name}/leaf_lib.jar", + ).processors().contains_exactly([]) + +def _test_exported_plugins_are_propagated_through_exports(name): + util.helper_target( + java_plugin, + name = name + "/plugin", + srcs = ["Plugin.java"], + processor_class = "com.example.process.stuff", + ) + util.helper_target( + java_library, + name = name + "/exporting_plugins_lib", + srcs = ["ExportingLib.java"], + exported_plugins = [name + "/plugin"], + ) + util.helper_target( + java_library, + name = name + "/exporting_lib", + srcs = ["ExportingLib.java"], + exports = [name + "/exporting_plugins_lib"], + ) + util.helper_target( + java_library, + name = name + "/consuming_lib", + srcs = ["ConsumingLib.java"], + exports = [name + "/exporting_lib"], + deps = [name + "/exporting_lib"], + ) + util.helper_target( + java_library, + name = name + "/leaf_lib", + srcs = ["LeafLib.java"], + deps = [name + "/consuming_lib"], + ) + + analysis_test( + name = name, + impl = _test_exported_plugins_are_propagated_through_exports_impl, + targets = { + "exporting": name + "/exporting_lib", + "consuming": name + "/consuming_lib", + "leaf": name + "/leaf_lib", + }, + ) + +def _test_exported_plugins_are_propagated_through_exports_impl(env, targets): + # libexporting_lib should not include the plugin, only export it further. + javac_action_subject.of( + env, + targets.exporting, + "{package}/lib{test_name}/exporting_lib.jar", + ).processors().contains_exactly([]) + + # libconsuming_lib should pick up the plugin through the export on exporting_lib. + javac_action_subject.of( + env, + targets.consuming, + "{package}/lib{test_name}/consuming_lib.jar", + ).processors().contains_exactly(["com.example.process.stuff"]) + + # libleaf_lib should pick up the plugin through the (transitive) export on exporting_lib. + javac_action_subject.of( + env, + targets.leaf, + "{package}/lib{test_name}/leaf_lib.jar", + ).processors().contains_exactly(["com.example.process.stuff"]) + +def java_library_launcher_tests(name): + test_suite( + name = name, + tests = [ + _test_java_library_rule_outputs, + _test_java_library_action_graph, + _test_java_library_deps_of_genrule_are_not_on_classpath, + _test_java_library_compile_and_run_time_paths, + _test_java_library_files_to_compile, + _test_java_library_runtime_deps_are_not_on_classpath, + _test_java_library_runtime_deps_are_not_on_classpath_with_header_compilation, + _test_java_library_propagates_native_libraries, + _test_java_library_gen_source_no_processor_names, + _test_java_library_annotation_processing_using_javacopt, + _test_java_library_javacopts_with_location_expansion, + _test_java_library_invalid_plugin, + _test_java_library_plugin_with_runtime_deps, + _test_java_library_source_jar_without_annotation_processing, + _test_java_library_source_jars_with_source_jars, + _test_java_library_should_set_bootclasspath, + _test_java_library_command_line_contains_target_label_and_rule_kind, + _test_java_library_compilation_info_provider, + _test_java_library_native_header_outputs, + _test_java_library_module_javacopts, + _test_java_library_forwarded_deps, + _test_java_library_transitive_strict_deps, + _test_java_library_emit_output_deps, + _test_java_library_deps_without_srcs, + _test_dependency_artifacts_with_exports, + _test_exports_are_indirect_not_direct, + _test_exports_runfiles, + _test_exports_collect_source_jars, + _test_exported_plugins_are_inherited, + _test_exported_plugins_are_propagated_through_exports, + ], + ) diff --git a/test/java/common/rules/deploy_archive_builder_tests.bzl b/test/java/common/rules/deploy_archive_builder_tests.bzl new file mode 100644 index 00000000..727790f8 --- /dev/null +++ b/test/java/common/rules/deploy_archive_builder_tests.bzl @@ -0,0 +1,47 @@ +"""Tests for DeployArchiveBuilder.""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_binary.bzl", "java_binary") +load("//test/java/testutil:mock_java_toolchain.bzl", "mock_java_toolchain") + +def _test_custom_singlejar(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + ) + util.helper_target( + java_binary, + name = name + "/binary", + srcs = [name + "/main.java"], + main_class = "com.google.test.main", + ) + + analysis_test( + name = name, + impl = _test_custom_singlejar_impl, + target = name + "/binary", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + # Starlark rules are only used with Bazel 8 onwards. + attr_values = {"tags": ["min_bazel_8"]}, + ) + +def _test_custom_singlejar_impl(env, target): + action = env.expect.that_target(target).action_named("JavaDeployJar") + action.inputs().contains_at_least_predicates( + [ + matching.file_path_matches("*/test_custom_singlejar/binary.jar"), + ], + ) + +def deploy_archive_builder_test_suite(name): + """Test suite for java_binary deploy archive.""" + test_suite( + name = name, + tests = [ + _test_custom_singlejar, + ], + ) diff --git a/test/java/common/rules/java_binary_tests.bzl b/test/java/common/rules/java_binary_tests.bzl new file mode 100644 index 00000000..b483516d --- /dev/null +++ b/test/java/common/rules/java_binary_tests.bzl @@ -0,0 +1,422 @@ +"""Tests for the java_binary rule""" + +load("@bazel_features//:features.bzl", "bazel_features") +load("@bazel_skylib//lib:paths.bzl", "paths") +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching", "subjects") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_binary.bzl", "java_binary") +load("//java:java_library.bzl", "java_library") +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules:java_helper.bzl", "helper") +load("//test/java/testutil:helper.bzl", "always_passes") +load("//test/java/testutil:java_info_subject.bzl", "java_info_subject") +load("//test/java/testutil:mock_java_toolchain.bzl", "mock_java_toolchain") +load("//test/java/testutil:rules/custom_java_info_rule.bzl", "custom_java_info_rule") +load("//test/java/testutil:rules/forward_java_info.bzl", "java_info_forwarding_rule") + +def _test_java_binary_provides_binary_java_info(name): + util.helper_target(java_binary, name = "bin", srcs = ["Main.java"]) + + analysis_test( + name = name, + impl = _test_java_binary_provides_binary_java_info_impl, + target = Label(":bin"), + ) + +def _test_java_binary_provides_binary_java_info_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + + assert_java_info.compilation_args().equals(None) + assert_java_info.is_binary().equals(True) + +def _test_stamp_conversion_does_not_override_int(name): + util.helper_target( + java_binary, + name = name + "/bin", + srcs = ["Main.java"], + stamp = -1, + ) + + analysis_test( + name = name, + impl = _test_stamp_conversion_does_not_override_int_impl, + target = name + "/bin", + config_settings = { + "//command_line_option:stamp": False, + }, + # deploy jars are in a separate rule in Bazel 7, Bazel 6 generated build-info differently + attr_values = {"tags": ["min_bazel_8"]}, + ) + +def _test_stamp_conversion_does_not_override_int_impl(env, target): + assert_deploy_jar_action = env.expect.that_target(target).action_generating( + "{package}/{name}_deploy.jar", + ) + + assert_deploy_jar_action.inputs().not_contains_predicate( + matching.file_basename_equals("non_volatile_file.properties"), + ) + assert_deploy_jar_action.inputs().contains_predicate( + matching.file_basename_equals("redacted_file.properties"), + ) + +def _test_java_binary_attributes(name): + util.helper_target( + java_library, + name = name + "/jl_bottom_for_deps", + srcs = ["java/A.java"], + ) + util.helper_target( + java_library, + name = name + "/jl_bottom_for_runtime_deps", + srcs = ["java/A2.java"], + ) + util.helper_target( + java_info_forwarding_rule, + name = name + "/mya", + dep = name + "/jl_bottom_for_deps", + ) + util.helper_target( + java_info_forwarding_rule, + name = name + "/myb", + dep = name + "/jl_bottom_for_runtime_deps", + ) + util.helper_target( + java_binary, + name = name + "/binary", + srcs = ["java/B.java"], + main_class = "foo.A", + deps = [name + "/mya"], + runtime_deps = [name + "/myb"], + ) + + analysis_test( + name = name, + impl = _test_java_binary_attributes_impl, + target = name + "/binary", + ) + +def _test_java_binary_attributes_impl(env, target): + assert_runtime_classpath = java_info_subject.from_target(env, target).compilation_info().runtime_classpath() + + # Test that all bottom jars are on the runtime classpath. + assert_runtime_classpath.contains_at_least_predicates([ + matching.file_basename_equals("jl_bottom_for_deps.jar"), + matching.file_basename_equals("jl_bottom_for_runtime_deps.jar"), + ]) + +def _test_java_binary_propagates_direct_native_libraries(name): + util.helper_target( + cc_library, + name = name + "/cclib", + srcs = ["z.cc"], + ) + util.helper_target( + cc_binary, + name = name + "/native", + srcs = ["cc/x.cc"], + deps = [name + "/cclib"], + linkshared = 1, + linkstatic = 1, + ) + util.helper_target( + java_library, + name = name + "/jl", + srcs = ["java/A.java"], + deps = [name + "/native"], + ) + util.helper_target( + cc_binary, + name = name + "/ccl", + srcs = ["cc/x.cc"], + deps = [name + "/cclib"], + linkshared = 1, + linkstatic = 1, + ) + util.helper_target( + custom_java_info_rule, + name = name + "/r", + output_jar = name + "-out.jar", + cc_dep = [name + "/ccl"], + dep = [name + "/jl"], + ) + util.helper_target( + java_binary, + name = name + "/binary", + srcs = ["java/C.java"], + deps = [name + "/r"], + main_class = "C", + ) + + analysis_test( + name = name, + impl = _test_java_binary_propagates_direct_native_libraries_impl, + target = name + "/binary", + ) + +def _test_java_binary_propagates_direct_native_libraries_impl(env, target): + executable = target[DefaultInfo].files_to_run.executable.short_path + assert_action = env.expect.that_target(target).action_generating(executable) + if assert_action.actual.substitutions: + # TemplateExpansion action on linux/mac + assert_jvm_flags = assert_action.substitutions().get( + "%jvm_flags%", + factory = lambda v, meta: subjects.collection([v], meta), + ) + else: + # windows + assert_jvm_flags = assert_action.argv() + assert_jvm_flags.contains_predicate( + matching.str_matches("-Djava.library.path=${JAVA_RUNFILES}/*/test_java_binary_propagates_direct_native_libraries"), + ) + +def _test_java_compile_only(name): + util.helper_target( + java_library, + name = name + "/hello_library", + srcs = ["HelloLibrary.java"], + ) + util.helper_target( + java_binary, + name = name + "/main", + srcs = ["Main.java"], + main_class = "main.Main", + deps = [name + "/hello_library"], + ) + + analysis_test( + name = name, + impl = _test_java_compile_only_impl, + target = name + "/main", + ) + +def _test_java_compile_only_impl(env, target): + # Assert that the compilation output is exactly main jar, and does not contain a variant of + # hello_library.jar. + env.expect.that_target(target).output_group( + "compilation_outputs", + ).contains_exactly(["{package}/{name}.jar"]) + +def _test_java_binary_can_set_transitive_validation(name): + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + # exit early because this test case would be a loading phase error otherwise + always_passes(name) + return + + env_name = name + "_env" + util.helper_target( + java_library, + name = env_name + "_leaf", + srcs = ["A.java"], + neverlink = True, # add runtime_classpath to validation output group + ) + util.helper_target( + java_binary, + name = env_name, + srcs = ["Env.java"], + deps = [env_name + "_leaf"], + neverlink = True, # add runtime_classpath to validation output group + ) + util.helper_target( + java_binary, + name = name + "_bin", + srcs = ["Bin.java"], + data = [env_name], + deploy_env = [env_name], + create_executable = False, + ) + + analysis_test( + name = name, + impl = _test_java_binary_can_set_transitive_validation_impl, + targets = { + "bin": name + "_bin", + "env": env_name, + }, + # Turn off other validations + config_settings = { + "//command_line_option:experimental_run_android_lint_on_java_rules": False, + "//command_line_option:experimental_one_version_enforcement": "OFF", + }, + ) + +def _test_java_binary_can_set_transitive_validation_impl(env, targets): + # ensure the env target has validation outputs + env.expect.that_target(targets.env).output_group("_validation").contains_at_least([ + "{package}/{name}.jar", + "{package}/lib{name}_leaf.jar", + ]) + + # ensure they don't propagate to `bin` because they're a part of the `deploy_env` + env.expect.that_target(targets.bin).output_group("_validation").contains_exactly([]) + +def _test_one_version_check_action(name): + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + # exit early because this test case would be a loading phase error otherwise + always_passes(name) + return + + util.helper_target( + java_library, + name = name + "/c", + srcs = [name + "/c.java"], + ) + util.helper_target( + java_library, + name = name + "/a", + srcs = [name + "/a.java"], + deps = [name + "/c"], + ) + util.helper_target( + java_binary, + name = name + "/b", + srcs = [name + "/b.java"], + deps = [name + "/a"], + ) + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + oneversion = "one_version_tool", + oneversion_allowlist = "one_version_allowlist", + ) + + analysis_test( + name = name, + impl = _test_one_version_check_action_impl, + target = name + "/b", + config_settings = { + "//command_line_option:experimental_one_version_enforcement": "ERROR", + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + attrs = { + "_windows_constraints": attr.label_list( + default = [paths.join(semantics.PLATFORMS_ROOT, "os:windows")], + ), + }, + ) + +def _test_one_version_check_action_impl(env, target): + assert_target = env.expect.that_target(target) + assert_target.default_outputs().contains_exactly([ + "{package}/{test_name}/b.jar", + "{package}/{test_name}/b" + (".exe" if helper.is_target_platform_windows(env.ctx) else ""), + ]) + assert_target.output_group("_validation").contains( + "{package}/{name}-one-version.txt", + ) + assert_action = assert_target.action_generating("{package}/{name}-one-version.txt") + assert_action.mnemonic().equals("JavaOneVersion") + assert_action.inputs().contains_at_least([ + "{package}/{test_name}/b.jar", + "{package}/lib{test_name}/a.jar", + "{package}/lib{test_name}/c.jar", + ]) + tool = [f for f in assert_action.actual.inputs.to_list() if f.short_path.endswith("one_version_tool")][0] + assert_action.argv().contains_exactly([ + tool.path, + "--output", + "{bindir}/{package}/{name}-one-version.txt", + "--allowlist", + "{package}/one_version_allowlist", + "--inputs", + "{bindir}/{package}/{test_name}/b.jar,//{package}:{test_name}/b", + "{bindir}/{package}/lib{test_name}/a.jar,//{package}:{test_name}/a", + "{bindir}/{package}/lib{test_name}/c.jar,//{package}:{test_name}/c", + ]).in_order() + +def _test_one_version_check_violations_allowed(name): + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + # exit early because this test case would be a loading phase error otherwise + always_passes(name) + return + + util.helper_target( + java_binary, + name = name + "/foo", + srcs = [name + "/foo.java"], + ) + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + oneversion = "one_version_tool", + oneversion_allowlist = "one_version_allowlist", + ) + + analysis_test( + name = name, + impl = _test_one_version_check_violations_allowed_impl, + target = name + "/foo", + config_settings = { + "//command_line_option:experimental_one_version_enforcement": "WARNING", + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_one_version_check_violations_allowed_impl(env, target): + assert_action = env.expect.that_target(target).action_generating( + "{package}/{name}-one-version.txt", + ) + env.expect.that_target(target).output_group("_validation").contains( + "{package}/{name}-one-version.txt", + ) + tool = [f for f in assert_action.actual.inputs.to_list() if f.short_path.endswith("one_version_tool")][0] + assert_action.argv().contains_exactly([ + tool.path, + "--output", + "{bindir}/{package}/{name}-one-version.txt", + "--allowlist", + "{package}/one_version_allowlist", + "--succeed_on_found_violations", + "--inputs", + "{bindir}/{package}/{test_name}/foo.jar,//{package}:{test_name}/foo", + ]).in_order() + +def _test_one_version_check_disabled(name): + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + # exit early because this test case would be a loading phase error otherwise + always_passes(name) + return + + util.helper_target( + java_binary, + name = name + "/foo", + srcs = [name + "/foo.java"], + ) + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + oneversion = "one_version_tool", + ) + + analysis_test( + name = name, + impl = _test_one_version_check_disabled_impl, + target = name + "/foo", + config_settings = { + "//command_line_option:experimental_one_version_enforcement": "OFF", + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_one_version_check_disabled_impl(env, target): + action_mnemonics = [a.mnemonic for a in env.expect.that_target(target).actual.actions] + env.expect.that_collection(action_mnemonics).not_contains("JavaOneVersion") + +def java_binary_tests(name): + test_suite( + name = name, + tests = [ + _test_java_binary_provides_binary_java_info, + _test_stamp_conversion_does_not_override_int, + _test_java_binary_attributes, + _test_java_binary_propagates_direct_native_libraries, + _test_java_compile_only, + _test_java_binary_can_set_transitive_validation, + _test_one_version_check_action, + _test_one_version_check_violations_allowed, + _test_one_version_check_disabled, + ], + ) diff --git a/test/java/common/rules/java_import_tests.bzl b/test/java/common/rules/java_import_tests.bzl new file mode 100644 index 00000000..35b713df --- /dev/null +++ b/test/java/common/rules/java_import_tests.bzl @@ -0,0 +1,989 @@ +"""Tests for the java_import rule""" + +load("@bazel_features//:features.bzl", "bazel_features") +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_binary.bzl", "java_binary") +load("//java:java_import.bzl", "java_import") +load("//java:java_library.bzl", "java_library") +load("//java/common:java_info.bzl", "JavaInfo") +load("//java/common:proguard_spec_info.bzl", "ProguardSpecInfo") +load("//test/java/testutil:helper.bzl", "always_passes") +load("//test/java/testutil:java_info_subject.bzl", "java_info_subject") +load("//test/java/testutil:javac_action_subject.bzl", "javac_action_subject") +load("//test/java/testutil:rules/custom_library.bzl", "custom_library") +load("//test/java/testutil:rules/forward_java_info.bzl", "java_info_forwarding_rule") + +def _test_java_import_attributes(name): + target_name = name + "/import" + util.helper_target( + java_library, + name = target_name + "/jl_bottom_for_deps", + srcs = ["java/A.java"], + ) + util.helper_target( + java_library, + name = target_name + "/jl_bottom_for_runtime_deps", + srcs = ["java/A2.java"], + ) + util.helper_target( + java_info_forwarding_rule, + name = target_name + "/mya", + dep = target_name + "/jl_bottom_for_deps", + ) + util.helper_target( + java_info_forwarding_rule, + name = target_name + "/myb", + dep = target_name + "/jl_bottom_for_runtime_deps", + ) + util.helper_target( + java_import, + name = target_name, + jars = ["B.jar"], + runtime_deps = [target_name + "/myb"], + deps = [target_name + "/mya"], + ) + + analysis_test( + name = name, + impl = _test_java_import_attributes_impl, + target = target_name, + ) + +def _test_java_import_attributes_impl(env, target): + assert_runtime_jars = java_info_subject.from_target(env, target).compilation_args().transitive_runtime_jars() + + # Test that all bottom jars are on the runtime classpath. + assert_runtime_jars.contains_at_least_predicates([ + matching.file_basename_equals("jl_bottom_for_deps.jar"), + matching.file_basename_equals("jl_bottom_for_runtime_deps.jar"), + ]) + +def _test_simple(name): + target_name = name + "/libraryjar" + util.helper_target( + java_import, + name = target_name, + jars = ["library.jar"], + ) + + analysis_test( + name = name, + impl = _test_simple_impl, + target = target_name, + ) + +def _test_simple_impl(env, target): + env.expect.that_target(target).default_outputs().contains_exactly([ + "{package}/library.jar", + ]) + +def _test_with_java_library(name): + target_name = name + "/javalib" + util.helper_target( + java_library, + name = target_name, + srcs = ["Other.java"], + deps = [target_name + "/libraryjar"], + ) + util.helper_target( + java_import, + name = target_name + "/libraryjar", + jars = ["library.jar"], + ) + + analysis_test( + name = name, + impl = _test_with_java_library_impl, + target = target_name, + ) + +def _test_with_java_library_impl(env, target): + assert_compliation_info = java_info_subject.from_target(env, target).compilation_info() + + assert_compliation_info.compilation_classpath().contains_exactly([ + "{package}/_ijar/{name}/libraryjar/{package}/library-ijar.jar", + ]) + assert_compliation_info.runtime_classpath().contains_exactly([ + "{package}/lib{name}.jar", + "{package}/library.jar", + ]) + +def _test_deps(name): + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["Main.java"], + deps = [name + "/import-jar"], + ) + util.helper_target( + java_import, + name = name + "/import-jar", + jars = ["import.jar"], + exports = [name + "/exportjar"], + deps = [name + "/depjar"], + ) + util.helper_target( + java_import, + name = name + "/depjar", + jars = ["depjar.jar"], + ) + util.helper_target( + java_import, + name = name + "/exportjar", + jars = ["exportjar.jar"], + ) + + analysis_test( + name = name, + impl = _test_deps_impl, + targets = { + "importjar": name + "/import-jar", + "lib": name + "/lib", + }, + ) + +def _test_deps_impl(env, targets): + env.expect.that_target(targets.importjar).default_outputs().contains_exactly([ + "{package}/import.jar", + ]) + + assert_import_compilation_args = java_info_subject.from_target(env, targets.importjar).compilation_args() + assert_import_compilation_args.transitive_compile_time_jars().contains_exactly([ + "{package}/_ijar/test_deps/import-jar/{package}/import-ijar.jar", + "{package}/_ijar/test_deps/exportjar/{package}/exportjar-ijar.jar", + "{package}/_ijar/test_deps/depjar/{package}/depjar-ijar.jar", + ]) + assert_import_compilation_args.transitive_runtime_jars().contains_exactly([ + "{package}/import.jar", + "{package}/exportjar.jar", + "{package}/depjar.jar", + ]) + assert_import_compilation_args.compile_jars().contains_exactly([ + "{package}/_ijar/test_deps/import-jar/{package}/import-ijar.jar", + "{package}/_ijar/test_deps/exportjar/{package}/exportjar-ijar.jar", + ]) + + assert_lib_compilation_info = java_info_subject.from_target(env, targets.lib).compilation_info() + assert_lib_compilation_info.compilation_classpath().contains_exactly([ + "{package}/_ijar/test_deps/import-jar/{package}/import-ijar.jar", + "{package}/_ijar/test_deps/exportjar/{package}/exportjar-ijar.jar", + "{package}/_ijar/test_deps/depjar/{package}/depjar-ijar.jar", + ]) + assert_lib_compilation_info.runtime_classpath().contains_exactly([ + "{package}/lib{name}.jar", + "{package}/import.jar", + "{package}/exportjar.jar", + "{package}/depjar.jar", + ]) + +# Regression test for b/262751943. +def _test_commandline_contains_target_label(name): + util.helper_target( + java_import, + name = name + "/java_imp", + jars = ["import.jar"], + ) + + analysis_test( + name = name, + impl = _test_commandline_contains_target_label_impl, + target = name + "/java_imp", + ) + +def _test_commandline_contains_target_label_impl(env, target): + compiled_artifact = target[JavaInfo].compile_jars.to_list()[0].short_path + assert_action = env.expect.that_target(target).action_generating(compiled_artifact) + + assert_action.contains_flag_values([ + ("--target_label", "//{package}:{name}"), + ]) + +# Regression test for b/5868388. +def _test_java_library_allows_import_in_deps(name): + util.helper_target( + java_import, + name = name + "/libraryjar", + jars = ["library.jar"], + ) + util.helper_target( + java_library, + name = name + "/javalib", + srcs = ["Other.java"], + exports = [name + "/libraryjar"], + ) + + analysis_test( + name = name, + impl = _test_java_library_allows_import_in_deps_impl, + target = name + "/javalib", + ) + +def _test_java_library_allows_import_in_deps_impl(_env, _target): + pass # no errors + +def _test_module_flags(name): + if not bazel_features.java.java_info_constructor_module_flags: + # exit early because this test case would be a loading phase error otherwise + always_passes(name) + return + + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["Main.java"], + deps = [name + "/import-jar"], + ) + util.helper_target( + java_import, + name = name + "/import-jar", + jars = ["import.jar"], + exports = [name + "/exportjar"], + deps = [name + "/depjar"], + ) + util.helper_target( + java_import, + name = name + "/depjar", + add_exports = ["java.base/java.lang"], + jars = ["depjar.jar"], + ) + util.helper_target( + java_import, + name = name + "/exportjar", + add_opens = ["java.base/java.util"], + jars = ["exportjar.jar"], + ) + + analysis_test( + name = name, + impl = _test_module_flags_impl, + targets = { + "importjar": name + "/import-jar", + "lib": name + "/lib", + }, + ) + +def _test_module_flags_impl(env, targets): + assert_import_module_flags = java_info_subject.from_target(env, targets.importjar).module_flags() + assert_import_module_flags.add_exports().contains_exactly(["java.base/java.lang"]) + assert_import_module_flags.add_opens().contains_exactly(["java.base/java.util"]) + + assert_lib_module_flags = java_info_subject.from_target(env, targets.lib).module_flags() + assert_lib_module_flags.add_exports().contains_exactly(["java.base/java.lang"]) + assert_lib_module_flags.add_opens().contains_exactly(["java.base/java.util"]) + +def _test_src_jars(name): + util.helper_target( + java_import, + name = name + "/libraryjar_with_srcjar", + jars = ["import.jar"], + srcjar = "library.srcjar", + ) + + analysis_test( + name = name, + impl = _test_src_jars_impl, + target = name + "/libraryjar_with_srcjar", + ) + +def _test_src_jars_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + assert_java_info.outputs().source_output_jars().contains_exactly([ + "{package}/library.srcjar", + ]) + +def _test_srcjar_added_to_validation_output_group(name): + util.helper_target( + java_import, + name = name + "/libraryjar_with_srcjar", + jars = ["import.jar"], + srcjar = "library.srcjar", + ) + + analysis_test( + name = name, + impl = _test_srcjar_added_to_validation_output_group_impl, + target = name + "/libraryjar_with_srcjar", + # Starlark rules are only used with Bazel 8 onwards. + attr_values = {"tags": ["min_bazel_8"]}, + ) + +def _test_srcjar_added_to_validation_output_group_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + assert_java_info.outputs().source_output_jars().contains_exactly([ + "{package}/library.srcjar", + ]) + + # Check that the srcjar is in the _validation output group. + env.expect.that_target(target).output_group("_validation").contains_at_least([ + "{package}/library.srcjar", + ]) + +def _test_from_genrule(name): + target_name = name + "/library-jar" + util.helper_target( + native.genrule, + name = target_name + "/generated_jar", + outs = [target_name + "/generated.jar"], + cmd = "", + ) + util.helper_target( + native.genrule, + name = target_name + "/generated_src_jar", + outs = [target_name + "/generated.srcjar"], + cmd = "", + ) + util.helper_target( + java_import, + name = target_name + "/libraryjar", + jars = ["library.jar"], + ) + util.helper_target( + java_import, + name = target_name, + jars = [target_name + "/generated_jar"], + srcjar = target_name + "/generated.srcjar", + exports = [target_name + "/libraryjar"], + ) + + analysis_test( + name = name, + impl = _test_from_genrule_impl, + targets = { + "lib": target_name, + "gen": target_name + "/generated_jar", + }, + ) + +def _test_from_genrule_impl(env, targets): + assert_compilation_args = java_info_subject.from_target(env, targets.lib).compilation_args() + assert_compilation_args.transitive_compile_time_jars().contains_exactly([ + "{package}/_ijar/{name}/{package}/{name}/generated-ijar.jar", + "{package}/_ijar/{name}/libraryjar/{package}/library-ijar.jar", + ]) + assert_compilation_args.transitive_runtime_jars().contains_exactly([ + "{package}/library.jar", + "{package}/{name}/generated.jar", + ]) + + jar = targets.lib[JavaInfo].transitive_runtime_jars.to_list()[0].short_path + env.expect.that_target(targets.gen).action_generating(jar).mnemonic().equals("Genrule") + +# Regression test for b/13936397: don't flatten transitive dependencies into direct deps. +def _test_transitive_dependencies(name): + target_name = name + "/javalib2" + util.helper_target( + java_import, + name = target_name + "/libraryjar", + jars = ["library.jar"], + ) + util.helper_target( + java_library, + name = target_name + "/lib", + srcs = ["Lib.java"], + deps = [target_name + "/libraryjar"], + ) + util.helper_target( + java_import, + name = target_name + "/library2-jar", + jars = ["library2.jar"], + exports = [target_name + "/lib"], + ) + util.helper_target( + java_library, + name = target_name, + srcs = ["Other.java"], + deps = [target_name + "/library2-jar"], + ) + + analysis_test( + name = name, + impl = _test_transitive_dependencies_impl, + target = target_name, + ) + +def _test_transitive_dependencies_impl(env, target): + assert_javac_action = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + + # Direct jars should NOT include libraryjar-ijar.jar + assert_javac_action.direct_dependencies().contains_exactly([ + "{bin_path}/{package}/_ijar/{name}/library2-jar/{package}/library2-ijar.jar", + "{bin_path}/{package}/lib{name}/lib-hjar.jar", + ]) + +def _test_exposes_java_provider(name): + util.helper_target( + java_import, + name = name + "/libraryjar", + jars = ["library.jar"], + ) + + analysis_test( + name = name, + impl = _test_exposes_java_provider_impl, + target = name + "/libraryjar", + ) + +def _test_exposes_java_provider_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + assert_java_info.compilation_args().transitive_runtime_jars().contains_exactly([ + "{package}/library.jar", + ]) + +def _test_jars_allowed_in_srcjar(name): + util.helper_target( + java_import, + name = name + "/library", + jars = ["somelib.jar"], + srcjar = "somelib-src.jar", + ) + + analysis_test( + name = name, + impl = _test_jars_allowed_in_srcjar_impl, + target = name + "/library", + ) + +def _test_jars_allowed_in_srcjar_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + assert_java_info.outputs().source_output_jars().contains_exactly([ + "{package}/somelib-src.jar", + ]) + +def _test_disallows_empty_jars(name): + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + # exit early because this test case would be a loading phase error otherwise + always_passes(name) + return + + util.helper_target( + java_import, + name = name + "/rule", + jars = [], + ) + + analysis_test( + name = name, + impl = _test_disallows_empty_jars_impl, + target = name + "/rule", + expect_failure = True, + ) + +def _test_disallows_empty_jars_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("empty java_import.jars is not supported"), + ) + +def _test_disallows_files_in_exports(name): + util.helper_target( + java_import, + name = name + "/rule", + jars = ["good.jar"], + # we can't create scratch files, so just use one that we know has a label + exports = ["BUILD"], + ) + + analysis_test( + name = name, + impl = _test_disallows_files_in_exports_impl, + target = name + "/rule", + expect_failure = True, + ) + +def _test_disallows_files_in_exports_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("source file * is misplaced here (expected no files)"), + ) + +def _test_disallows_arbitrary_files(name): + util.helper_target( + java_import, + name = name + "/rule", + jars = ["not-a-jar.txt"], + ) + + analysis_test( + name = name, + impl = _test_disallows_arbitrary_files_impl, + target = name + "/rule", + expect_failure = True, + ) + +def _test_disallows_arbitrary_files_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("file '*:not-a-jar.txt' is misplaced here (expected .jar)"), + ) + +def _test_disallows_arbitrary_files_from_genrule(name): + util.helper_target( + native.genrule, + name = name + "/gen", + outs = ["not-a-jar.txt"], + cmd = "", + ) + util.helper_target( + java_import, + name = name + "/rule", + jars = [name + "/gen"], + ) + + analysis_test( + name = name, + impl = _test_disallows_arbitrary_files_from_genrule_impl, + target = name + "/rule", + expect_failure = True, + ) + +def _test_disallows_arbitrary_files_from_genrule_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("'*/gen' does not produce any java_import jars files (expected .jar)"), + ) + +def _test_disallows_java_rules_in_jars(name): + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["JavaLib.java"], + ) + util.helper_target( + java_import, + name = name + "/rule", + jars = [name + "/lib"], + ) + + analysis_test( + name = name, + impl = _test_disallows_java_rules_in_jars_impl, + target = name + "/rule", + expect_failure = True, + ) + +def _test_disallows_java_rules_in_jars_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.any( + matching.str_matches("'jars' attribute cannot contain labels of Java targets"), + matching.str_matches("should not refer to Java rules"), # Bazel 6 + ), + ) + +def _test_disallows_exports_with_flag(name): + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + # exit early because this test case would be a loading phase error otherwise + always_passes(name) + return + + util.helper_target( + java_library, + name = name + "/dep", + srcs = ["Dep.java"], + ) + util.helper_target( + java_import, + name = name + "/rule", + jars = ["dummy.jar"], + exports = [name + "/dep"], + ) + + analysis_test( + name = name, + impl = _test_disallows_exports_with_flag_impl, + target = name + "/rule", + expect_failure = True, + config_settings = { + "//command_line_option:incompatible_disallow_java_import_exports": True, + }, + ) + +def _test_disallows_exports_with_flag_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("java_import.exports is no longer supported; use java_import.deps instead"), + ) + +def _test_ijar_can_be_disabled(name): + util.helper_target( + java_library, + name = name + "/a", + srcs = ["A.java"], + deps = [name + "/b"], + ) + util.helper_target( + java_import, + name = name + "/b", + jars = ["b.jar"], + ) + + analysis_test( + name = name, + impl = _test_ijar_can_be_disabled_impl, + target = name + "/a", + config_settings = { + "//command_line_option:use_ijars": False, + }, + ) + +def _test_ijar_can_be_disabled_impl(env, target): + assert_jars = java_info_subject.from_target(env, target).compilation_args().transitive_compile_time_jars() + assert_jars.contains_exactly([ + "{package}/lib{name}-hjar.jar", + "{package}/b.jar", + ]) + +def _test_duplicate_jars_through_filegroup(name): + util.helper_target( + native.filegroup, + name = name + "/jars", + srcs = ["a.jar"], + ) + util.helper_target( + java_import, + name = name + "/ji-with-dupe-through-fg", + jars = ["a.jar", name + "/jars"], + ) + + analysis_test( + name = name, + impl = _test_duplicate_jars_through_filegroup_impl, + target = name + "/ji-with-dupe-through-fg", + expect_failure = True, + ) + +def _test_duplicate_jars_through_filegroup_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.any( + matching.str_matches("in jars attribute of java_import rule */ji-with-dupe-through-fg: a.jar is a duplicate"), + matching.str_matches("a.jar is a duplicate"), # Bazel 6 + ), + ) + +def _test_runtime_deps_are_not_on_classpath(name): + target_name = name + "/depends_on_runtimedep" + util.helper_target( + java_import, + name = target_name + "/import_runtime", + jars = ["import_runtime.jar"], + ) + util.helper_target( + java_import, + name = target_name + "/import_dep", + jars = ["import_compile.jar"], + runtime_deps = [target_name + "/import_runtime"], + ) + util.helper_target( + java_library, + name = target_name + "/library_dep", + srcs = ["library_compile.java"], + ) + util.helper_target( + java_library, + name = target_name, + srcs = ["dummy.java"], + deps = [ + target_name + "/import_dep", + target_name + "/library_dep", + ], + ) + + analysis_test( + name = name, + impl = _test_runtime_deps_are_not_on_classpath_impl, + target = name + "/depends_on_runtimedep", + ) + +def _test_runtime_deps_are_not_on_classpath_impl(env, target): + assert_javac = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + + # Direct jars should NOT include import_runtime.jar + assert_javac.direct_dependencies().contains_exactly([ + "{bin_path}/{package}/_ijar/{name}/import_dep/{package}/import_compile-ijar.jar", + "{bin_path}/{package}/lib{name}/library_dep-hjar.jar", + ]) + +def _test_exports_runfile_collection(name): + target_name = name + "/tool" + util.helper_target( + java_import, + name = target_name + "/other_lib", + data = ["foo.txt"], + jars = ["other.jar"], + ) + util.helper_target( + java_import, + name = target_name + "/lib", + jars = ["lib.jar"], + exports = [target_name + "/other_lib"], + ) + util.helper_target( + java_binary, + name = target_name, + data = [target_name + "/lib"], + main_class = "com.google.exports.Launcher", + ) + + analysis_test( + name = name, + impl = _test_exports_runfile_collection_impl, + target = target_name, + ) + +def _test_exports_runfile_collection_impl(env, target): + assert_runfiles = env.expect.that_target(target).runfiles() + assert_runfiles.contains_at_least_predicates([ + matching.str_matches("/lib.jar"), + matching.str_matches("/other.jar"), + matching.str_matches("/foo.txt"), + ]) + +def _test_transitive_source_jars(name): + target_name = name + "/a" + util.helper_target( + java_import, + name = target_name, + jars = ["dummy.jar"], + srcjar = "dummy-src.jar", + exports = [target_name + "/b"], + ) + util.helper_target( + java_library, + name = target_name + "/b", + srcs = ["B.java"], + ) + + analysis_test( + name = name, + impl = _test_transitive_source_jars_impl, + target = target_name, + ) + +def _test_transitive_source_jars_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + assert_java_info.transitive_source_jars().contains_exactly([ + "{package}/dummy-src.jar", + "{package}/lib{name}/b-src.jar", + ]) + +def _test_neverlink_is_populated(name): + target_name = name + "/jar" + util.helper_target( + java_library, + name = target_name + "/lib", + ) + util.helper_target( + java_import, + name = target_name, + jars = ["dummy.jar"], + neverlink = 1, + exports = [target_name + "/lib"], + ) + + analysis_test( + name = name, + impl = _test_neverlink_is_populated_impl, + target = target_name, + ) + +def _test_neverlink_is_populated_impl(env, target): + env.expect.that_bool(target[JavaInfo]._neverlink).equals(True) + +def _test_transitive_proguard_specs_are_validated(name): + target_name = name + "/lib" + util.helper_target( + java_import, + name = target_name + "/transitive", + constraints = ["android"], + jars = ["Transitive.jar"], + proguard_specs = ["transitive.pro"], + ) + util.helper_target( + java_import, + name = target_name, + constraints = ["android"], + jars = ["Lib.jar"], + exports = [target_name + "/transitive"], + ) + + analysis_test( + name = name, + impl = _test_transitive_proguard_specs_are_validated_impl, + targets = { + "lib": target_name, + "dep": target_name + "/transitive", + }, + ) + +def _test_transitive_proguard_specs_are_validated_impl(env, targets): + proguard_out = "{package}/validated_proguard/{name}/transitive/{package}/transitive.pro_valid" + env.expect.that_target(targets.lib).output_group( + "_hidden_top_level_INTERNAL_", + ).contains(proguard_out) + env.expect.that_target(targets.dep).action_named("ValidateProguard").inputs().contains( + "{package}/transitive.pro", + ) + +def _test_proguard_specs_are_validated(name): + target_name = name + "/lib" + util.helper_target( + java_import, + name = target_name, + constraints = ["android"], + jars = ["Lib.jar"], + proguard_specs = ["lib.pro"], + ) + + analysis_test( + name = name, + impl = _test_proguard_specs_are_validated_impl, + target = target_name, + ) + +def _test_proguard_specs_are_validated_impl(env, target): + proguard_out = "{package}/validated_proguard/{name}/{package}/lib.pro_valid" + env.expect.that_target(target).output_group( + "_hidden_top_level_INTERNAL_", + ).contains(proguard_out) + env.expect.that_target(target).action_named("ValidateProguard").inputs().contains( + "{package}/lib.pro", + ) + +def _test_transitive_proguard_specs_are_exported(name): + target_name = name + "/lib" + util.helper_target( + java_import, + name = target_name + "/export", + constraints = ["android"], + jars = ["Export.jar"], + proguard_specs = ["export.pro"], + ) + util.helper_target( + java_import, + name = target_name + "/runtime_dep", + constraints = ["android"], + jars = ["RuntimeDep.jar"], + proguard_specs = ["runtime_dep.pro"], + ) + util.helper_target( + java_import, + name = target_name, + constraints = ["android"], + jars = ["Lib.jar"], + proguard_specs = ["lib.pro"], + exports = [target_name + "/export"], + runtime_deps = [target_name + "/runtime_dep"], + ) + + analysis_test( + name = name, + impl = _test_transitive_proguard_specs_are_exported_impl, + target = target_name, + # Before Bazel 8, native rules use the native ProguardSpecProvider + attr_values = {"tags": ["min_bazel_8"]}, + ) + +def _test_transitive_proguard_specs_are_exported_impl(env, target): + spec_basenames = [f.basename for f in target[ProguardSpecInfo].specs.to_list()] + env.expect.that_collection(spec_basenames).contains_exactly([ + "lib.pro_valid", + "export.pro_valid", + "runtime_dep.pro_valid", + ]) + +def _test_src_jars_output_groups(name): + target_name = name + "/a" + util.helper_target( + java_import, + name = target_name, + jars = ["jar_a.jar"], + srcjar = "src_jar_a.jar", + deps = [target_name + "/b"], + ) + util.helper_target( + java_import, + name = target_name + "/b", + jars = ["jar_b.jar"], + srcjar = "src_jar_b.jar", + ) + + analysis_test( + name = name, + impl = _test_src_jars_output_groups_impl, + target = target_name, + ) + +def _test_src_jars_output_groups_impl(env, target): + env.expect.that_target(target).output_group("_source_jars").contains_exactly([ + "{package}/src_jar_a.jar", + ]) + env.expect.that_target(target).output_group("_direct_source_jars").contains_exactly([ + "{package}/src_jar_a.jar", + ]) + +def _test_with_custom_library(name): + target_name = name + "/javalib" + util.helper_target( + java_library, + name = target_name, + srcs = ["MyClass.java"], + deps = [target_name + "/foo"], + ) + util.helper_target( + java_import, + name = target_name + "/foo", + jars = ["foo.jar"], + runtime_deps = [target_name + "/javacustomlib"], + ) + util.helper_target( + custom_library, + name = target_name + "/javacustomlib", + srcs = ["Other.java"], + ) + + analysis_test( + name = name, + impl = _test_with_custom_library_impl, + target = target_name, + ) + +def _test_with_custom_library_impl(env, target): + assert_java_info = java_info_subject.from_target(env, target) + assert_java_info.compilation_info().runtime_classpath_list().contains_exactly_predicates([ + matching.file_basename_equals("javalib.jar"), + matching.file_basename_equals("foo.jar"), + matching.file_basename_equals("javacustomlib.jar"), + ]).in_order() + +def java_import_tests(name): + test_suite( + name = name, + tests = [ + _test_java_import_attributes, + _test_simple, + _test_with_java_library, + _test_deps, + _test_commandline_contains_target_label, + _test_java_library_allows_import_in_deps, + _test_module_flags, + _test_src_jars, + _test_srcjar_added_to_validation_output_group, + _test_from_genrule, + _test_transitive_dependencies, + _test_exposes_java_provider, + _test_jars_allowed_in_srcjar, + _test_disallows_empty_jars, + _test_disallows_files_in_exports, + _test_disallows_arbitrary_files, + _test_disallows_arbitrary_files_from_genrule, + _test_disallows_java_rules_in_jars, + _test_disallows_exports_with_flag, + _test_ijar_can_be_disabled, + _test_duplicate_jars_through_filegroup, + _test_runtime_deps_are_not_on_classpath, + _test_exports_runfile_collection, + _test_transitive_source_jars, + _test_neverlink_is_populated, + _test_transitive_proguard_specs_are_validated, + _test_proguard_specs_are_validated, + _test_transitive_proguard_specs_are_exported, + _test_src_jars_output_groups, + _test_with_custom_library, + ], + ) diff --git a/test/java/common/rules/java_launcher_tests.bzl b/test/java/common/rules/java_launcher_tests.bzl new file mode 100644 index 00000000..6faaa93f --- /dev/null +++ b/test/java/common/rules/java_launcher_tests.bzl @@ -0,0 +1,71 @@ +"""Tests for the "launcher" attribute and "--java_launcher" flag.""" + +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_binary.bzl", "java_binary") + +def _test_overridden_incompatible_launcher(name): + # Check analysis succeeds even though --java_launcher refers to an incompatible target + # when the "use_launcher" attribute is set to False. + util.helper_target( + rule = cc_binary, + name = name + "/launcher", + srcs = select({ + "@platforms//cpu:ppc": [name + "/launcher.cc"], + }), + ) + + util.helper_target( + rule = java_binary, + name = name + "/bin", + srcs = [name + "/Bin.java"], + use_launcher = False, + ) + + analysis_test( + name = name, + impl = lambda env, target: True, + target = name + "/bin", + config_settings = { + "//command_line_option:java_launcher": Label(name + "/launcher"), + "//command_line_option:cpu": "k8", + }, + ) + +def _test_launcher_with_create_executable_false_fails(name): + util.helper_target( + rule = cc_binary, + name = name + "/launcher", + srcs = [name + "/launcher.cc"], + ) + + util.helper_target( + rule = java_binary, + name = name + "/bin", + srcs = [name + "/Bin.java"], + launcher = name + "/launcher", + create_executable = False, + ) + + analysis_test( + name = name, + impl = _test_launcher_with_create_executable_false_fails_impl, + target = name + "/bin", + expect_failure = True, + ) + +def _test_launcher_with_create_executable_false_fails_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("launcher specified but create_executable is false"), + ) + +def java_launcher_tests(name): + test_suite( + name = name, + tests = [ + _test_overridden_incompatible_launcher, + _test_launcher_with_create_executable_false_fails, + ], + ) diff --git a/test/java/common/rules/java_library_tests.bzl b/test/java/common/rules/java_library_tests.bzl new file mode 100644 index 00000000..4115b121 --- /dev/null +++ b/test/java/common/rules/java_library_tests.bzl @@ -0,0 +1,365 @@ +"""Tests for the java_library rule""" + +load("@bazel_features//:features.bzl", "bazel_features") +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_library.bzl", "java_library") +load("//java:java_plugin.bzl", "java_plugin") +load("//java/common:java_info.bzl", "JavaInfo") +load("//test/java/testutil:helper.bzl", "always_passes") +load("//test/java/testutil:java_info_subject.bzl", "java_info_subject") +load("//test/java/testutil:javac_action_subject.bzl", "javac_action_subject") +load("//test/java/testutil:rules/custom_java_info_rule.bzl", "custom_java_info_rule") +load("//test/java/testutil:rules/forward_java_info.bzl", "java_info_forwarding_rule") +load("//test/java/testutil:rules/wrap_java_info.bzl", "JavaInfoWrappingInfo", "java_info_wrapping_rule") + +def _test_exposes_plugins(name): + target_name = name + "/library" + util.helper_target( + java_library, + name = target_name + "/plugin_dep1", + srcs = ["A.java"], + data = ["depfile1.dat"], + ) + util.helper_target( + java_library, + name = target_name + "/plugin_dep2", + srcs = ["B.java"], + data = ["depfile2.dat"], + ) + util.helper_target( + java_plugin, + name = target_name + "/plugin", + srcs = ["AnnotationProcessor1.java"], + data = ["pluginfile1.dat"], + processor_class = "com.google.process.stuff", + deps = [target_name + "/plugin_dep1"], + ) + util.helper_target( + java_plugin, + name = target_name + "/apiplugin", + srcs = ["AnnotationProcessor2.java"], + data = ["pluginfile2.dat"], + generates_api = True, + processor_class = "com.google.process.apistuff", + deps = [target_name + "/plugin_dep2"], + ) + util.helper_target( + java_library, + name = target_name, + exported_plugins = [ + target_name + "/plugin", + target_name + "/apiplugin", + ], + ) + + analysis_test( + name = name, + impl = _test_exposes_plugins_impl, + target = target_name, + ) + +def _test_exposes_plugins_impl(env, target): + assert_plugin_data = java_info_subject.from_target(env, target).plugins() + assert_plugin_data.processor_classes().contains_exactly([ + "com.google.process.stuff", + "com.google.process.apistuff", + ]) + assert_plugin_data.processor_jars().contains_exactly([ + "{package}/lib{name}/plugin.jar", + "{package}/lib{name}/plugin_dep1.jar", + "{package}/lib{name}/apiplugin.jar", + "{package}/lib{name}/plugin_dep2.jar", + ]) + assert_plugin_data.processor_data().contains_exactly([ + "{package}/pluginfile1.dat", + "{package}/pluginfile2.dat", + ]) + + assert_api_plugin_data = java_info_subject.from_target(env, target).api_generating_plugins() + assert_api_plugin_data.processor_classes().contains_exactly(["com.google.process.apistuff"]) + assert_api_plugin_data.processor_jars().contains_exactly([ + "{package}/lib{name}/apiplugin.jar", + "{package}/lib{name}/plugin_dep2.jar", + ]) + assert_api_plugin_data.processor_data().contains_exactly(["{package}/pluginfile2.dat"]) + +def _test_exposes_java_info(name): + util.helper_target( + java_library, + name = name + "/jl", + srcs = ["java/A.java"], + ) + util.helper_target( + java_info_wrapping_rule, + name = name + "/r", + dep = name + "/jl", + ) + analysis_test( + name = name, + impl = _test_exposes_java_info_impl, + targets = { + "r": name + "/r", + "jl": name + "/jl", + }, + ) + +def _test_exposes_java_info_impl(env, targets): + env.expect.that_bool( + targets.r[JavaInfoWrappingInfo].p == targets.jl[JavaInfo], + ).equals(True) + +def _test_java_info_propagation(name): + util.helper_target( + java_library, + name = name + "/jl", + srcs = ["java/A.java"], + ) + util.helper_target( + java_info_forwarding_rule, + name = name + "/r", + dep = name + "/jl", + ) + util.helper_target( + java_library, + name = name + "/jl_top", + srcs = ["java/C.java"], + deps = [name + "/r"], + ) + + analysis_test( + name = name, + impl = _test_java_info_propagation_impl, + targets = { + "r": name + "/r", + "jl": name + "/jl", + "jl_top": name + "/jl_top", + }, + ) + +def _test_java_info_propagation_impl(env, targets): + env.expect.that_bool(targets.r[JavaInfo] == targets.jl[JavaInfo]).equals(True) + _assert_depsets_have_the_same_parent( + env, + targets.jl[JavaInfo].transitive_compile_time_jars, + targets.jl_top[JavaInfo].transitive_compile_time_jars, + ) + _assert_depsets_have_the_same_parent( + env, + targets.jl[JavaInfo].transitive_runtime_jars, + targets.jl_top[JavaInfo].transitive_runtime_jars, + ) + +def _assert_depsets_have_the_same_parent(env, depset1, depset2): + elements = depset1.to_list() + other_elements = depset2.to_list() + + for e, other_e in zip(elements, other_elements): + env.expect.that_str(e.dirname).equals(other_e.dirname) + +def _test_java_library_attributes(name): + util.helper_target( + java_library, + name = name + "/jl_bottom_for_deps", + srcs = ["java/A.java"], + ) + util.helper_target( + java_library, + name = name + "/jl_bottom_for_exports", + srcs = ["java/A2.java"], + ) + util.helper_target( + java_library, + name = name + "/jl_bottom_for_runtime_deps", + srcs = ["java/A2.java"], + ) + util.helper_target( + java_info_forwarding_rule, + name = name + "/mya", + dep = name + "/jl_bottom_for_deps", + ) + util.helper_target( + java_info_forwarding_rule, + name = name + "/myb", + dep = name + "/jl_bottom_for_exports", + ) + util.helper_target( + java_info_forwarding_rule, + name = name + "/myc", + dep = name + "/jl_bottom_for_runtime_deps", + ) + util.helper_target( + java_library, + name = name + "/lib_exports", + srcs = ["java/B.java"], + exports = [name + "/myb"], + runtime_deps = [name + "/myc"], + deps = [name + "/mya"], + ) + util.helper_target( + java_library, + name = name + "/lib_interm", + srcs = ["java/C.java"], + deps = [name + "/lib_exports"], + ) + util.helper_target( + java_library, + name = name + "/lib_top", + srcs = ["java/D.java"], + deps = [name + "/lib_interm"], + ) + + analysis_test( + name = name, + impl = _test_java_library_attributes_impl, + targets = { + "exports": name + "/lib_exports", + "interm": name + "/lib_interm", + }, + ) + +def _test_java_library_attributes_impl(env, targets): + # all bottom jars are on the runtime classpath of lib_exports. + java_info_subject.from_target(env, targets.exports).compilation_args().transitive_runtime_jars().contains_at_least_predicates([ + matching.file_basename_equals("jl_bottom_for_deps.jar"), + matching.file_basename_equals("jl_bottom_for_runtime_deps.jar"), + matching.file_basename_equals("jl_bottom_for_exports.jar"), + ]) + + # jl_bottom_for_exports.jar is in the recursive java compilation args of lib_top. + java_info_subject.from_target(env, targets.interm).compilation_args().transitive_runtime_jars().contains_predicate( + matching.file_basename_equals("jl_bottom_for_exports.jar"), + ) + +def _test_propagates_direct_native_libraries(name): + target_name = name + "/jl_top" + util.helper_target( + cc_library, + name = target_name + "/native", + srcs = ["cc/x.cc"], + ) + util.helper_target( + java_library, + name = target_name + "/jl", + srcs = ["java/A.java"], + deps = [target_name + "/native"], + ) + util.helper_target( + cc_library, + name = target_name + "/ccl", + srcs = ["cc/x.cc"], + ) + util.helper_target( + custom_java_info_rule, + name = target_name + "/r", + output_jar = target_name + "-out.jar", + cc_dep = [target_name + "/ccl"], + dep = [target_name + "/jl"], + ) + util.helper_target( + java_library, + name = target_name, + srcs = ["java/C.java"], + deps = [target_name + "/r"], + ) + + analysis_test( + name = name, + impl = _test_propagates_direct_native_libraries_impl, + target = target_name, + ) + +def _test_propagates_direct_native_libraries_impl(env, target): + assert_transitive_native_libraries = java_info_subject.from_target(env, target).transitive_native_libraries() + assert_transitive_native_libraries.static_libraries().contains_exactly_predicates([ + matching.str_matches("*native*"), + matching.str_matches("*ccl*"), + ]).in_order() + +def _test_exposes_native_library_info(name): + target_name = name + "/jl" + util.helper_target( + cc_library, + name = target_name + "/mynativedep_lib", + srcs = ["cc/x.cc"], + ) + util.helper_target( + cc_binary, + name = target_name + "/mynativedep_bin", + srcs = ["cc/x.cc"], + linkshared = 1, + ) + util.helper_target( + java_library, + name = target_name, + srcs = ["java/A.java"], + deps = select({ + "@platforms//os:windows": [target_name + "/mynativedep_lib"], + "//conditions:default": [target_name + "/mynativedep_bin"], + }), + ) + analysis_test( + name = name, + impl = _test_exposes_native_library_info_impl, + target = target_name, + ) + +def _test_exposes_native_library_info_impl(env, target): + assert_lib = java_info_subject.from_target(env, target).transitive_native_libraries().singleton() + + assert_lib.dynamic_library().basename().contains("mynativedep") + +def _test_strict_java_deps(name, strict_java_deps): + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + always_passes(name) + return + + util.helper_target( + java_library, + name = name + "/jl", + srcs = ["A.java"], + ) + + analysis_test( + name = name, + impl = _test_strict_java_deps_impl, + target = name + "/jl", + config_settings = {"//command_line_option:experimental_strict_java_deps": strict_java_deps}, + attrs = {"expected_strict_java_deps": attr.string()}, + attr_values = {"expected_strict_java_deps": strict_java_deps}, + ) + +def _test_strict_java_deps_impl(env, target): + # Note that if --experimental_strict_java_deps=OFF, we may not set --strict_java_deps in the + # javac action's argv at all; the "OFF" value (which is equivalent to an unset flag) in that + # case is injected by javac_action_subject for convenience and canonicalization. + expect_that_javac_action = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + expect_that_javac_action.strict_java_deps().contains_exactly([env.ctx.attr.expected_strict_java_deps]) + +def _test_strict_java_deps_off(name): + _test_strict_java_deps(name, "OFF") + +def _test_strict_java_deps_warn(name): + _test_strict_java_deps(name, "WARN") + +def _test_strict_java_deps_error(name): + _test_strict_java_deps(name, "ERROR") + +def java_library_tests(name): + test_suite( + name = name, + tests = [ + _test_exposes_plugins, + _test_exposes_java_info, + _test_java_info_propagation, + _test_java_library_attributes, + _test_propagates_direct_native_libraries, + _test_exposes_native_library_info, + _test_strict_java_deps_off, + _test_strict_java_deps_warn, + _test_strict_java_deps_error, + ], + ) diff --git a/test/java/common/rules/java_plugin_tests.bzl b/test/java/common/rules/java_plugin_tests.bzl new file mode 100644 index 00000000..71c2d156 --- /dev/null +++ b/test/java/common/rules/java_plugin_tests.bzl @@ -0,0 +1,389 @@ +"""Tests for the java_plugin rule""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "subjects") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_library.bzl", "java_library") +load("//java:java_plugin.bzl", "java_plugin") +load("//java/common:java_plugin_info.bzl", "JavaPluginInfo") +load("//java/common:proguard_spec_info.bzl", "ProguardSpecInfo") +load("//test/java/testutil:java_info_subject.bzl", "java_plugin_info_subject") + +def _test_exposes_plugins_to_starlark(name): + target_name = name + "/plugin" + util.helper_target( + java_library, + name = target_name + "/plugin_dep", + srcs = ["ProcessorDep.java"], + data = ["depfile.dat"], + ) + util.helper_target( + java_plugin, + name = target_name, + srcs = ["AnnotationProcessor.java"], + data = ["pluginfile.dat"], + processor_class = "com.google.process.stuff", + deps = [target_name + "/plugin_dep"], + ) + + analysis_test( + name = name, + impl = _test_exposes_plugins_to_starlark_impl, + target = target_name, + ) + +def _test_exposes_plugins_to_starlark_impl(env, target): + assert_plugin_data = java_plugin_info_subject.from_target(env, target).plugins() + assert_plugin_data.processor_classes().contains_exactly(["com.google.process.stuff"]) + assert_plugin_data.processor_jars().contains_exactly([ + "{package}/lib{name}.jar", + "{package}/lib{name}/plugin_dep.jar", + ]) + assert_plugin_data.processor_data().contains_exactly(["{package}/pluginfile.dat"]) + + java_plugin_info_subject.from_target(env, target).api_generating_plugins().is_empty() + +def _test_exposes_api_generating_plugins_to_starlark(name): + target_name = name + "/plugin" + util.helper_target( + java_library, + name = target_name + "/plugin_dep", + srcs = ["ProcessorDep.java"], + data = ["depfile.dat"], + ) + util.helper_target( + java_plugin, + name = target_name, + srcs = ["AnnotationProcessor.java"], + data = ["pluginfile.dat"], + processor_class = "com.google.process.stuff", + deps = [target_name + "/plugin_dep"], + generates_api = True, + ) + + analysis_test( + name = name, + impl = _test_exposes_api_generating_plugins_to_starlark_impl, + target = target_name, + ) + +def _test_exposes_api_generating_plugins_to_starlark_impl(env, target): + assert_api_plugin_data = java_plugin_info_subject.from_target(env, target).api_generating_plugins() + assert_api_plugin_data.processor_classes().contains_exactly(["com.google.process.stuff"]) + assert_api_plugin_data.processor_jars().contains_exactly([ + "{package}/lib{name}.jar", + "{package}/lib{name}/plugin_dep.jar", + ]) + assert_api_plugin_data.processor_data().contains_exactly(["{package}/pluginfile.dat"]) + assert_api_plugin_data.equals(target[JavaPluginInfo].plugins) + +def _test_not_empty_processor_class(name): + util.helper_target( + java_library, + name = name + "/deps", + srcs = ["Deps.java"], + ) + util.helper_target( + java_plugin, + name = name + "/processor", + srcs = ["Processor.java"], + processor_class = "com.google.test.Processor", + deps = [name + "/deps"], + ) + + analysis_test( + name = name, + impl = _test_not_empty_processor_class_impl, + target = name + "/processor", + ) + +def _test_not_empty_processor_class_impl(env, target): + plugin_info = java_plugin_info_subject.from_target(env, target) + plugin_info.plugins().processor_classes().contains_exactly(["com.google.test.Processor"]) + + plugin_info.plugins().processor_jars().contains_exactly([ + "{package}/lib{name}.jar", + "{package}/lib{test_name}/deps.jar", + ]) + +def _test_empty_processor_class(name): + util.helper_target( + java_library, + name = name + "/deps", + srcs = ["Deps.java"], + ) + util.helper_target( + java_plugin, + name = name + "/bugchecker", + srcs = ["BugChecker.java"], + deps = [":" + name + "/deps"], + ) + + analysis_test( + name = name, + impl = _test_empty_processor_class_impl, + target = name + "/bugchecker", + ) + +def _test_empty_processor_class_impl(env, target): + plugin_info = java_plugin_info_subject.from_target(env, target) + plugin_info.plugins().processor_classes().contains_exactly([]) + plugin_info.plugins().processor_jars().contains_exactly([ + "{package}/lib{name}.jar", + "{package}/lib{test_name}/deps.jar", + ]) + +def _test_empty_processor_class_target(name): + util.helper_target( + java_library, + name = name + "/deps", + srcs = ["Deps.java"], + ) + util.helper_target( + java_plugin, + name = name + "/bugchecker", + srcs = ["BugChecker.java"], + deps = [":" + name + "/deps"], + ) + util.helper_target( + java_library, + name = name + "/empty", + plugins = [":" + name + "/bugchecker"], + ) + + analysis_test( + name = name, + impl = _test_empty_processor_class_target_impl, + target = name + "/empty", + ) + +def _test_empty_processor_class_target_impl(env, target): + env.expect.that_target(target).action_generating("{package}/lib{name}.jar").inputs().contains_at_least([ + "{package}/lib{test_name}/bugchecker.jar", + "{package}/lib{test_name}/deps.jar", + ]) + +def _new_proguard_info_subject(info, meta): + return struct( + specs = lambda: subjects.depset_file(info.specs, meta.derive("specs")), + ) + +def _test_java_plugin_exports_transitive_proguard_specs(name): + util.helper_target( + java_plugin, + name = name + "/plugin", + srcs = ["Plugin.java"], + proguard_specs = ["plugin.pro"], + ) + util.helper_target( + java_library, + name = name + "/dep", + srcs = ["Dep.java"], + proguard_specs = ["dep.pro"], + ) + util.helper_target( + java_plugin, + name = name + "/top", + srcs = ["Top.java"], + plugins = [":" + name + "/plugin"], + proguard_specs = ["top.pro"], + deps = [":" + name + "/dep"], + ) + + analysis_test( + name = name, + impl = _test_java_plugin_exports_transitive_proguard_specs_impl, + target = name + "/top", + # Before Bazel 8, native rules use the native ProguardSpecProvider + attr_values = {"tags": ["min_bazel_8"]}, + provider_subject_factories = [struct( + type = ProguardSpecInfo, + name = "ProguardInfo", + factory = _new_proguard_info_subject, + )], + ) + +def _test_java_plugin_exports_transitive_proguard_specs_impl(env, target): + env.expect.that_target(target).provider(ProguardSpecInfo).specs().contains_exactly( + [ + "{package}/validated_proguard/{test_name}/top/{package}/top.pro_valid", + "{package}/validated_proguard/{test_name}/dep/{package}/dep.pro_valid", + ], + ) + +def _test_java_plugin_validates_proguard_specs(name): + util.helper_target( + java_plugin, + name = name + "/plugin", + srcs = ["Plugin.java"], + proguard_specs = ["plugin.pro"], + ) + + analysis_test( + name = name, + impl = _test_java_plugin_validates_proguard_specs_impl, + target = name + "/plugin", + ) + +def _test_java_plugin_validates_proguard_specs_impl(env, target): + output_file = None + for f in target.output_groups["_hidden_top_level_INTERNAL_"].to_list(): + if f.basename == "plugin.pro_valid": + output_file = f + break + env.expect.that_target(target).action_generating( + output_file.short_path, + ).inputs().contains_at_least( + ["{package}/plugin.pro"], + ) + +def _test_java_plugin_validates_transitive_proguard_specs(name): + util.helper_target( + java_library, + name = name + "/transitive", + srcs = ["Transitive.java"], + proguard_specs = ["transitive.pro"], + ) + util.helper_target( + java_plugin, + name = name + "/plugin", + srcs = ["Plugin.java"], + deps = [":" + name + "/transitive"], + ) + + analysis_test( + name = name, + impl = _test_java_plugin_validates_transitive_proguard_specs_impl, + targets = { + "transitive": name + "/transitive", + "plugin": name + "/plugin", + }, + ) + +def _test_java_plugin_validates_transitive_proguard_specs_impl(env, targets): + output_file = None + for f in targets.plugin.output_groups["_hidden_top_level_INTERNAL_"].to_list(): + if f.basename == "transitive.pro_valid": + output_file = f + break + + env.expect.that_target(targets.transitive).action_generating( + output_file.short_path, + ).inputs().contains_at_least(["{package}/transitive.pro"]) + +def _test_generates_api(name): + util.helper_target( + java_plugin, + name = name + "/api_generating", + srcs = ["ApiGeneratingPlugin.java"], + generates_api = True, + processor_class = "ApiGeneratingPlugin", + ) + + analysis_test( + name = name, + impl = _test_generates_api_impl, + target = name + "/api_generating", + ) + +def _test_generates_api_impl(env, target): + plugin_info = java_plugin_info_subject.from_target(env, target) + plugin_info.plugins().processor_classes().contains_exactly(["ApiGeneratingPlugin"]) + plugin_info.api_generating_plugins().processor_classes().contains_exactly(["ApiGeneratingPlugin"]) + plugin_info.plugins().processor_jars().contains_exactly([ + "{package}/lib{name}.jar", + ]) + plugin_info.api_generating_plugins().processor_jars().contains_exactly([ + "{package}/lib{name}.jar", + ]) + +def _test_generates_implementation(name): + util.helper_target( + java_plugin, + name = name + "/impl_generating", + srcs = ["ImplGeneratingPlugin.java"], + generates_api = False, + processor_class = "ImplGeneratingPlugin", + ) + + analysis_test( + name = name, + impl = _test_generates_implementation_impl, + target = name + "/impl_generating", + ) + +def _test_generates_implementation_impl(env, target): + plugin_info = java_plugin_info_subject.from_target(env, target) + plugin_info.plugins().processor_classes().contains_exactly(["ImplGeneratingPlugin"]) + plugin_info.api_generating_plugins().processor_classes().contains_exactly([]) + plugin_info.plugins().processor_jars().contains_exactly([ + "{package}/lib{test_name}/impl_generating.jar", + ]) + plugin_info.api_generating_plugins().processor_jars().contains_exactly([]) + +def _test_plugin_data_in_provider(name): + util.helper_target( + java_plugin, + name = name + "/impl_generating", + srcs = ["ImplGeneratingPlugin.java"], + data = ["data.txt"], + generates_api = False, + processor_class = "ImplGeneratingPlugin", + ) + + analysis_test( + name = name, + impl = _test_plugin_data_in_provider_impl, + target = name + "/impl_generating", + ) + +def _test_plugin_data_in_provider_impl(env, target): + plugin_info = java_plugin_info_subject.from_target(env, target) + plugin_info.plugins().processor_data().contains_exactly([ + "{package}/data.txt", + ]) + +def _test_plugin_data_in_action_inputs(name): + util.helper_target( + java_plugin, + name = name + "/impl_generating_lib", + srcs = ["ImplGeneratingPlugin.java"], + data = ["data.txt"], + generates_api = False, + processor_class = "ImplGeneratingPlugin", + ) + util.helper_target( + java_library, + name = name + "/lib", + plugins = [":" + name + "/impl_generating_lib"], + ) + + analysis_test( + name = name, + impl = _test_plugin_data_in_action_inputs_impl, + target = name + "/lib", + ) + +def _test_plugin_data_in_action_inputs_impl(env, target): + env.expect.that_target(target).action_generating("{package}/lib{name}.jar").inputs().contains_at_least([ + "{package}/data.txt", + ]) + +def java_plugin_tests(name): + test_suite( + name = name, + tests = [ + _test_exposes_plugins_to_starlark, + _test_exposes_api_generating_plugins_to_starlark, + _test_not_empty_processor_class, + _test_empty_processor_class, + _test_empty_processor_class_target, + _test_generates_api, + _test_plugin_data_in_provider, + _test_plugin_data_in_action_inputs, + _test_java_plugin_exports_transitive_proguard_specs, + _test_java_plugin_validates_proguard_specs, + _test_java_plugin_validates_transitive_proguard_specs, + _test_generates_implementation, + ], + ) diff --git a/test/java/common/rules/java_single_jar_tests.bzl b/test/java/common/rules/java_single_jar_tests.bzl new file mode 100644 index 00000000..488aab3f --- /dev/null +++ b/test/java/common/rules/java_single_jar_tests.bzl @@ -0,0 +1,169 @@ +"""Tests for the java_single_jar rule""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_single_jar.bzl", "java_single_jar") +load("//java/common:java_semantics.bzl", "semantics") + +def _test_java_single_jar_basic(name): + util.helper_target( + java_single_jar, + name = name + "/jar", + deps = ["1.jar", "2.jar"], + ) + + analysis_test( + name = name, + impl = _test_java_single_jar_basic_impl, + target = name + "/jar", + ) + +def _test_java_single_jar_basic_impl(env, target): + assert_that_action = env.expect.that_target(target).action_named("JavaSingleJar") + assert_that_action.argv().contains_at_least([ + "--sources", + "{package}/1.jar", + "{package}/2.jar", + "--output", + "{bindir}/{package}/{name}.jar", + "--normalize", + "--dont_change_compression", + "--exclude_build_data", + "--multi_release", + ]).in_order() + +def _test_java_single_jar_force_enable_stamping(name): + util.helper_target( + java_single_jar, + name = name + "/jar", + stamp = 1, + exclude_build_data = False, + ) + + analysis_test( + name = name, + impl = _test_java_single_jar_force_enable_stamping_impl, + targets = { + "jar": name + "/jar", + "build_info": semantics.BUILD_INFO_TRANSLATOR_LABEL, + }, + ) + +def _test_java_single_jar_force_enable_stamping_impl(env, targets): + assert_that_action = env.expect.that_target(targets.jar).action_named("JavaSingleJar") + assert_that_action.contains_flag_values([ + ("--build_info_file", f.path) + for f in targets.build_info[OutputGroupInfo].non_redacted_build_info_files.to_list() + ]) + +def _test_java_single_jar_force_disable_stamping(name): + util.helper_target( + java_single_jar, + name = name + "/jar", + stamp = 0, + exclude_build_data = False, + ) + + analysis_test( + name = name, + impl = _test_java_single_jar_force_disable_stamping_impl, + targets = { + "jar": name + "/jar", + "build_info": semantics.BUILD_INFO_TRANSLATOR_LABEL, + }, + ) + +def _test_java_single_jar_force_disable_stamping_impl(env, targets): + assert_that_action = env.expect.that_target(targets.jar).action_named("JavaSingleJar") + assert_that_action.contains_flag_values([ + ("--build_info_file", f.path) + for f in targets.build_info[OutputGroupInfo].redacted_build_info_files.to_list() + ]) + +def _test_java_single_jar_stamping_enabled_build_data_excluded_fails(name): + util.helper_target( + java_single_jar, + name = name + "/jar", + stamp = 1, + exclude_build_data = True, + ) + + analysis_test( + name = name, + impl = _test_java_single_jar_stamping_enabled_build_data_excluded_fails_impl, + target = name + "/jar", + expect_failure = True, + ) + +def _test_java_single_jar_stamping_enabled_build_data_excluded_fails_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("Enabling stamping has not effect with exclude_build_data enabled"), + ) + +def _test_java_single_jar_stamp_attr_auto_stamp_flag_enabled(name): + util.helper_target( + java_single_jar, + name = name + "/jar", + stamp = -1, + exclude_build_data = False, + ) + + analysis_test( + name = name, + impl = _test_java_single_jar_stamp_attr_auto_stamp_flag_enabled_impl, + targets = { + "jar": name + "/jar", + "build_info": semantics.BUILD_INFO_TRANSLATOR_LABEL, + }, + config_settings = { + "//command_line_option:stamp": True, + }, + ) + +def _test_java_single_jar_stamp_attr_auto_stamp_flag_enabled_impl(env, targets): + assert_that_action = env.expect.that_target(targets.jar).action_named("JavaSingleJar") + assert_that_action.contains_flag_values([ + ("--build_info_file", f.path) + for f in targets.build_info[OutputGroupInfo].non_redacted_build_info_files.to_list() + ]) + +def _test_java_single_jar_stamp_attr_auto_stamp_flag_disabled(name): + util.helper_target( + java_single_jar, + name = name + "/jar", + stamp = -1, + exclude_build_data = False, + ) + + analysis_test( + name = name, + impl = _test_java_single_jar_stamp_attr_auto_stamp_flag_disabled_impl, + targets = { + "jar": name + "/jar", + "build_info": semantics.BUILD_INFO_TRANSLATOR_LABEL, + }, + config_settings = { + "//command_line_option:stamp": False, + }, + ) + +def _test_java_single_jar_stamp_attr_auto_stamp_flag_disabled_impl(env, targets): + assert_that_action = env.expect.that_target(targets.jar).action_named("JavaSingleJar") + assert_that_action.contains_flag_values([ + ("--build_info_file", f.path) + for f in targets.build_info[OutputGroupInfo].redacted_build_info_files.to_list() + ]) + +def java_single_jar_tests(name): + test_suite( + name = name, + tests = [ + _test_java_single_jar_basic, + _test_java_single_jar_force_enable_stamping, + _test_java_single_jar_force_disable_stamping, + _test_java_single_jar_stamping_enabled_build_data_excluded_fails, + _test_java_single_jar_stamp_attr_auto_stamp_flag_enabled, + _test_java_single_jar_stamp_attr_auto_stamp_flag_disabled, + ], + ) diff --git a/test/java/common/rules/java_test_tests.bzl b/test/java/common/rules/java_test_tests.bzl new file mode 100644 index 00000000..995872f1 --- /dev/null +++ b/test/java/common/rules/java_test_tests.bzl @@ -0,0 +1,452 @@ +"""Tests for the java_test rule""" + +load("@bazel_features//:features.bzl", "bazel_features") +load("@bazel_skylib//lib:paths.bzl", "paths") +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching", "subjects") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_binary.bzl", "java_binary") +load("//java:java_library.bzl", "java_library") +load("//java:java_test.bzl", "java_test") +load("//java/common:java_info.bzl", "JavaInfo") +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules:java_helper.bzl", "helper") +load("//test/java/testutil:helper.bzl", "always_passes") +load("//test/java/testutil:mock_cc_toolchain.bzl", "mock_cc_toolchain") +load("//test/java/testutil:mock_java_toolchain.bzl", "mock_java_runtime_toolchain", "mock_java_toolchain") +load("//test/java/testutil:mock_test_toolchain.bzl", "mock_test_toolchains") +load("//test/java/testutil:rules/custom_java_info_rule.bzl", "custom_java_info_rule") + +def _test_java_test_is_test_only(name): + util.helper_target( + java_test, + name = name + "/test", + srcs = [name + "/Test.java"], + ) + + util.helper_target( + java_library, + name = name + "/lib", + srcs = [name + "/Lib.java"], + deps = [name + "/test"], + ) + + analysis_test( + name = name, + impl = _test_java_test_is_test_only_impl, + target = name + "/lib", + expect_failure = True, + ) + +def _test_java_test_is_test_only_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("non-test target '*/lib' depends on testonly target '*/test'"), + ) + +def _test_deps_without_srcs_fails(name): + util.helper_target( + rule = java_library, + name = name + "/lib", + srcs = [name + "/Lib.java"], + ) + + util.helper_target( + rule = java_test, + name = name + "/test", + deps = [name + "/lib"], + ) + + analysis_test( + name = name, + target = name + "/test", + impl = _test_deps_without_srcs_fails_impl, + expect_failure = True, + ) + +def _test_deps_without_srcs_fails_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.contains("deps not allowed without srcs"), + ) + +def _test_java_test_propagates_direct_native_libraries(name): + util.helper_target( + cc_library, + name = name + "/cclib", + srcs = ["z.cc"], + ) + util.helper_target( + cc_binary, + name = name + "/native", + srcs = ["cc/x.cc"], + deps = [name + "/cclib"], + linkshared = 1, + linkstatic = 1, + ) + util.helper_target( + java_library, + name = name + "/jl", + srcs = ["java/A.java"], + deps = [name + "/native"], + ) + util.helper_target( + cc_binary, + name = name + "/ccl", + srcs = ["cc/x.cc"], + deps = [name + "/cclib"], + linkshared = 1, + linkstatic = 1, + ) + util.helper_target( + custom_java_info_rule, + name = name + "/r", + output_jar = name + "-out.jar", + cc_dep = [name + "/ccl"], + dep = [name + "/jl"], + ) + util.helper_target( + java_test, + name = name + "/binary", + srcs = ["java/C.java"], + deps = [name + "/r"], + main_class = "C", + ) + + analysis_test( + name = name, + impl = _test_java_test_propagates_direct_native_libraries_impl, + target = name + "/binary", + ) + +def _test_java_test_propagates_direct_native_libraries_impl(env, target): + executable = target[DefaultInfo].files_to_run.executable.short_path + assert_action = env.expect.that_target(target).action_generating(executable) + if assert_action.actual.substitutions: + # TemplateExpansion action on linux/mac + assert_jvm_flags = assert_action.substitutions().get( + "%jvm_flags%", + factory = lambda v, meta: subjects.collection([v], meta), + ) + else: + # windows + assert_jvm_flags = assert_action.argv() + assert_jvm_flags.contains_predicate( + matching.str_matches("-Djava.library.path=${JAVA_RUNFILES}/*/test_java_test_propagates_direct_native_libraries"), + ) + +def _test_coverage_uses_coverage_runner_for_main(name): + util.helper_target( + rule = java_test, + name = name + "/test", + srcs = [name + "/Test.java"], + ) + + analysis_test( + name = name, + impl = _test_coverage_uses_coverage_runner_for_main_impl, + target = name + "/test", + config_settings = { + "//command_line_option:collect_code_coverage": True, + }, + ) + +def _test_coverage_uses_coverage_runner_for_main_impl(env, target): + executable = target[DefaultInfo].files_to_run.executable.short_path + assert_action = env.expect.that_target(target).action_generating(executable) + if assert_action.actual.substitutions: + assert_java_start_class = assert_action.substitutions().get( + "%java_start_class%", + factory = lambda v, meta: subjects.str(v, meta.derive("java_start_class")), + ) + assert_java_start_class.contains("com.google.testing.coverage.JacocoCoverageRunner") + else: + # Windows + assert_java_start_class = assert_action.argv() + assert_java_start_class.contains("java_start_class=com.google.testing.coverage.JacocoCoverageRunner") + +def _test_stamp_values(name): + util.helper_target( + rule = java_test, + name = name + "/stamp_true", + srcs = [name + "/Test.java"], + stamp = True, + ) + + util.helper_target( + rule = java_test, + name = name + "/stamp_false", + srcs = [name + "/Test.java"], + stamp = False, + ) + + util.helper_target( + rule = java_test, + name = name + "/stamp_auto", + srcs = [name + "/Test.java"], + stamp = -1, + ) + + util.helper_target( + rule = java_test, + name = name + "/stamp_default", + srcs = [name + "/Test.java"], + ) + + analysis_test( + name = name, + targets = { + "stamp": name + "/stamp_true", + "nostamp": name + "/stamp_false", + "autostamp": name + "/stamp_auto", + "defaultstamp": name + "/stamp_default", + }, + impl = _test_stamp_values_impl, + ) + +def _test_stamp_values_impl(env, targets): + env.expect.that_target(targets.stamp).attr("stamp", factory = subjects.int).equals(1) + env.expect.that_target(targets.nostamp).attr("stamp", factory = subjects.int).equals(0) + env.expect.that_target(targets.defaultstamp).attr("stamp", factory = subjects.int).equals(0) + env.expect.that_target(targets.autostamp).attr("stamp", factory = subjects.int).equals(-1) + +def _test_add_test_support_to_compile_time_deps_flag(name): + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + always_passes(name) + return + util.helper_target( + rule = java_test, + name = name + "/test", + srcs = [name + "/Test.java"], + ) + + analysis_test( + name = name, + targets = { + "add_support": name + "/test", + "no_add_support": name + "/test", + }, + attrs = { + "test_runner": attr.label(default = semantics.JAVA_TEST_RUNNER_LABEL), + "add_support": { + "@config_settings": { + "//command_line_option:experimental_add_test_support_to_compile_time_deps": True, + }, + }, + "no_add_support": { + "@config_settings": { + "//command_line_option:experimental_add_test_support_to_compile_time_deps": False, + }, + }, + }, + impl = _test_add_test_support_to_compile_time_deps_flag_impl, + ) + +def _test_add_test_support_to_compile_time_deps_flag_impl(env, targets): + compile_jars = env.ctx.attr.test_runner[JavaInfo].compile_jars + env.expect.that_target(targets.add_support).action_named("Javac").inputs().contains_at_least(compile_jars.to_list()) + env.expect.that_target(targets.no_add_support).action_named("Javac").inputs().contains_none_of(compile_jars.to_list()) + +def _test_mac_requires_darwin_for_execution(name): + util.helper_target( + rule = native.platform, + name = name + "/darwin_x86_64", + constraint_values = [ + "@platforms//os:macos", + "@platforms//cpu:x86_64", + ], + ) + + util.helper_target( + rule = java_test, + name = name + "/test", + srcs = [name + "/Test.java"], + use_launcher = False, + use_testrunner = 0, + ) + + util.helper_target( + rule = mock_cc_toolchain, + name = name + "/cc_toolchain", + cpu = "x86_64", + os = "macos", + ) + + toolchains = [Label(name + "/cc_toolchain")] + mock_test_toolchains( + name = name + "/test_toolchain", + cpu = "x86_64", + os = "macos", + ) + + analysis_test( + name = name, + target = name + "/test", + config_settings = { + "//command_line_option:platforms": [Label(name + "/darwin_x86_64")], + "//command_line_option:extra_toolchains": toolchains, + }, + impl = _test_mac_requires_darwin_for_execution_impl, + ) + +def _test_mac_requires_darwin_for_execution_impl(env, target): + env.expect.that_target(target).provider(testing.ExecutionInfo).requirements().contains_at_least( + {"requires-darwin": ""}, + ) + +def _test_java_test_sets_securiry_manager_property_jdk17(name): + util.helper_target( + java_test, + name = name + "/test", + srcs = ["FooTest.java"], + test_class = "FooTest", + ) + util.helper_target( + mock_java_runtime_toolchain, + name = name + "/toolchain", + version = 17, + ) + + analysis_test( + name = name, + impl = _test_java_test_sets_securiry_manager_property_jdk17_impl, + target = name + "/test", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_java_test_sets_securiry_manager_property_jdk17_impl(env, target): + executable = env.expect.that_target(target).executable().actual.short_path + assert_action = env.expect.that_target(target).action_generating(executable) + if assert_action.actual.substitutions: + # TemplateExpansion action on linux/mac + assert_action.substitutions().get("%jvm_flags%", factory = subjects.str).contains( + "-Djava.security.manager=allow", + ) + else: + # windows + assert_action.argv().contains_predicate( + matching.str_matches("-Djava.security.manager=allow"), + ) + +def _test_one_version_check_java_test(name): + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + # exit early because this test case would be a loading phase error otherwise + always_passes(name) + return + + util.helper_target( + java_library, + name = name + "/foo", + srcs = [name + "/foo.java"], + ) + util.helper_target( + java_test, + name = name + "/foo_test", + srcs = [name + "/foo_test.java"], + deps = [name + "/foo"], + use_testrunner = False, + ) + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + oneversion = "one_version_tool", + oneversion_allowlist = "one_version_allowlist", + oneversion_allowlist_for_tests = "one_version_allowlist_for_tests", + ) + + analysis_test( + name = name, + impl = _test_one_version_check_java_test_impl, + target = name + "/foo_test", + config_settings = { + "//command_line_option:experimental_one_version_enforcement": "ERROR", + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + attrs = { + "_windows_constraints": attr.label_list( + default = [paths.join(semantics.PLATFORMS_ROOT, "os:windows")], + ), + }, + ) + +def _test_one_version_check_java_test_impl(env, target): + assert_target = env.expect.that_target(target) + assert_target.default_outputs().contains_exactly([ + "{package}/{test_name}/foo_test.jar", + "{package}/{test_name}/foo_test" + (".exe" if helper.is_target_platform_windows(env.ctx) else ""), + ]) + assert_action = assert_target.action_generating( + "{package}/{name}-one-version.txt", + ) + tool = [f for f in assert_action.actual.inputs.to_list() if f.short_path.endswith("one_version_tool")][0] + assert_action.argv().contains_exactly([ + tool.path, + "--output", + "{bindir}/{package}/{name}-one-version.txt", + "--allowlist", + "{package}/one_version_allowlist_for_tests", + "--inputs", + "{bindir}/{package}/{test_name}/foo_test.jar,//{package}:{test_name}/foo_test", + "{bindir}/{package}/lib{test_name}/foo.jar,//{package}:{test_name}/foo", + ]).in_order() + +def _test_one_version_check_disabled_for_java_test(name): + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + # exit early because this test case would be a loading phase error otherwise + always_passes(name) + return + + util.helper_target( + java_test, + name = name + "/foo_test", + srcs = [name + "/foo.java"], + use_testrunner = False, + ) + util.helper_target( + java_binary, + name = name + "/foo_binary", + srcs = [name + "/foo.java"], + ) + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + oneversion = "one_version_tool", + ) + + analysis_test( + name = name, + impl = _test_one_version_check_disabled_for_java_test_impl, + targets = { + "bin": name + "/foo_binary", + "test": name + "/foo_test", + }, + config_settings = { + "//command_line_option:experimental_one_version_enforcement": "ERROR", + "//command_line_option:one_version_enforcement_on_java_tests": False, + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_one_version_check_disabled_for_java_test_impl(env, targets): + binary_action_mnemonics = [a.mnemonic for a in env.expect.that_target(targets.bin).actual.actions] + test_action_mnemonics = [a.mnemonic for a in env.expect.that_target(targets.test).actual.actions] + env.expect.that_collection(binary_action_mnemonics).contains("JavaOneVersion") + env.expect.that_collection(test_action_mnemonics).not_contains("JavaOneVersion") + +def java_test_tests(name): + test_suite( + name = name, + tests = [ + _test_java_test_is_test_only, + _test_deps_without_srcs_fails, + _test_java_test_propagates_direct_native_libraries, + _test_coverage_uses_coverage_runner_for_main, + _test_stamp_values, + _test_add_test_support_to_compile_time_deps_flag, + _test_mac_requires_darwin_for_execution, + _test_java_test_sets_securiry_manager_property_jdk17, + _test_one_version_check_java_test, + _test_one_version_check_disabled_for_java_test, + ], + ) diff --git a/test/java/common/rules/merge_attrs_tests.bzl b/test/java/common/rules/merge_attrs_tests.bzl new file mode 100644 index 00000000..bea71adc --- /dev/null +++ b/test/java/common/rules/merge_attrs_tests.bzl @@ -0,0 +1,38 @@ +"""Tests for merge_attrs function""" + +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load( + "//java/common/rules:rule_util.bzl", + "merge_attrs", +) + +_attr_string = attr.string() +_attr_string_different_ref = attr.string() +_attr_string_different = attr.string(default = "Some default") + +def _merge_attrs_merges_impl(ctx): + env = unittest.begin(ctx) + + attrs = merge_attrs( + {"A": _attr_string}, + {"B": _attr_string_different_ref, "C": _attr_string_different}, + override_attrs = {"B": _attr_string_different}, + remove_attrs = ["C"], + ) + + asserts.equals(env, attrs, {"A": _attr_string, "B": _attr_string_different}) + + return unittest.end(env) + +merge_attrs_merges_test = unittest.make(_merge_attrs_merges_impl) + +def merge_attrs_test_suite(name): + """Sets up util test suite + + Args: + name: the name of the test suite target + """ + unittest.suite( + name, + merge_attrs_merges_test, + ) diff --git a/test/java/private/BUILD b/test/java/private/BUILD new file mode 100644 index 00000000..dd2d2ad0 --- /dev/null +++ b/test/java/private/BUILD @@ -0,0 +1,18 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +load(":android_support_tests.bzl", "android_support_tests") + +package(default_applicable_licenses = ["@rules_java//:license"]) + +android_support_tests(name = "android_support_tests") diff --git a/test/java/private/android_support_tests.bzl b/test/java/private/android_support_tests.bzl new file mode 100644 index 00000000..8f9bea64 --- /dev/null +++ b/test/java/private/android_support_tests.bzl @@ -0,0 +1,76 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests for //java/private:android_support.bzl""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_library.bzl", "java_library") +load("//java:java_plugin.bzl", "java_plugin") +load("//java/common:java_info.bzl", "JavaInfo") +load("//java/private:android_support.bzl", "android_support") # buildifier: disable=bzl-visibility +load("//test/java/testutil:java_info_subject.bzl", "java_info_subject") + +def _impl(ctx): + return [ + android_support.enable_implicit_sourceless_deps_exports_compatibility(ctx.attr.dep[JavaInfo]), + ] + +my_rule = rule( + implementation = _impl, + attrs = { + "dep": attr.label(), + }, +) + +def _test_enable_implicit_sourceless_deps_exports_compatibility(name): + util.helper_target( + java_plugin, + name = "my_plugin", + srcs = ["MyPlugin.java"], + ) + util.helper_target( + java_library, + name = "base", + srcs = ["Foo.java"], + exported_plugins = [":my_plugin"], + ) + util.helper_target( + my_rule, + name = "transformed", + dep = ":base", + ) + + analysis_test( + name = name, + impl = _test_enable_implicit_sourceless_deps_exports_compatibility_impl, + targets = { + "base": Label(":base"), + "transformed": Label(":transformed"), + }, + ) + +def _test_enable_implicit_sourceless_deps_exports_compatibility_impl(env, targets): + base_info = java_info_subject.from_target(env, targets.base) + transformed_info = java_info_subject.from_target(env, targets.transformed) + transformed_info.compilation_args().equals_subject(base_info.compilation_args()) + base_info.plugins().processor_jars().contains_exactly(["{package}/libmy_plugin.jar"]) + transformed_info.plugins().processor_jars().contains_exactly([]) + +def android_support_tests(name): + test_suite( + name = name, + tests = [ + _test_enable_implicit_sourceless_deps_exports_compatibility, + ], + ) diff --git a/test/java/runfiles/src/test/java/com/google/devtools/build/runfiles/BUILD.bazel b/test/java/runfiles/src/test/java/com/google/devtools/build/runfiles/BUILD.bazel new file mode 100644 index 00000000..79790df0 --- /dev/null +++ b/test/java/runfiles/src/test/java/com/google/devtools/build/runfiles/BUILD.bazel @@ -0,0 +1,36 @@ +load("@rules_java//java:java_import.bzl", "java_import") +load("@rules_java//java:java_test.bzl", "java_test") + +package(default_applicable_licenses = ["@rules_java//:license"]) + +java_test( + name = "RunfilesTest", + srcs = ["RunfilesTest.java"], + test_class = "com.google.devtools.build.runfiles.RunfilesTest", + deps = [ + ":guava", + ":truth", + "//java/runfiles", + ], +) + +java_test( + name = "UtilTest", + srcs = ["UtilTest.java"], + test_class = "com.google.devtools.build.runfiles.UtilTest", + deps = [ + ":guava", + ":truth", + "//java/runfiles", + ], +) + +java_import( + name = "guava", + jars = ["@guava//file"], +) + +java_import( + name = "truth", + jars = ["@truth//file"], +) diff --git a/test/java/runfiles/src/test/java/com/google/devtools/build/runfiles/RunfilesTest.java b/test/java/runfiles/src/test/java/com/google/devtools/build/runfiles/RunfilesTest.java new file mode 100644 index 00000000..406ebe8f --- /dev/null +++ b/test/java/runfiles/src/test/java/com/google/devtools/build/runfiles/RunfilesTest.java @@ -0,0 +1,658 @@ +// Copyright 2018 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.runfiles; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link Runfiles}. */ +@RunWith(JUnit4.class) +public final class RunfilesTest { + + @Rule + public TemporaryFolder tempDir = new TemporaryFolder(new File(System.getenv("TEST_TMPDIR"))); + + private static boolean isWindows() { + return File.separatorChar == '\\'; + } + + private void assertRlocationArg(Runfiles runfiles, String path, @Nullable String error) { + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> runfiles.rlocation(path)); + if (error != null) { + assertThat(e).hasMessageThat().contains(error); + } + } + + @Test + public void testRlocationArgumentValidation() throws Exception { + Path dir = + Files.createTempDirectory( + FileSystems.getDefault().getPath(System.getenv("TEST_TMPDIR")), null); + + Runfiles r = Runfiles.create(ImmutableMap.of("RUNFILES_DIR", dir.toString())); + assertRlocationArg(r, null, null); + assertRlocationArg(r, "", null); + assertRlocationArg(r, "../foo", "is not normalized"); + assertRlocationArg(r, "foo/..", "is not normalized"); + assertRlocationArg(r, "foo/../bar", "is not normalized"); + assertRlocationArg(r, "./foo", "is not normalized"); + assertRlocationArg(r, "foo/.", "is not normalized"); + assertRlocationArg(r, "foo/./bar", "is not normalized"); + assertRlocationArg(r, "//foobar", "is not normalized"); + assertRlocationArg(r, "foo//", "is not normalized"); + assertRlocationArg(r, "foo//bar", "is not normalized"); + assertRlocationArg(r, "\\foo", "path is absolute without a drive letter"); + } + + @Test + public void testCreatesManifestBasedRunfiles() throws Exception { + Path mf = tempFile("foo.runfiles_manifest", ImmutableList.of("a/b c/d")); + Runfiles r = + Runfiles.create( + ImmutableMap.of( + "RUNFILES_MANIFEST_ONLY", "1", + "RUNFILES_MANIFEST_FILE", mf.toString(), + "RUNFILES_DIR", "ignored when RUNFILES_MANIFEST_ONLY=1", + "JAVA_RUNFILES", "ignored when RUNFILES_DIR has a value", + "TEST_SRCDIR", "should always be ignored")); + assertThat(r.rlocation("a/b")).isEqualTo("c/d"); + assertThat(r.rlocation("foo")).isNull(); + + if (isWindows()) { + assertThat(r.rlocation("c:/foo")).isEqualTo("c:/foo"); + assertThat(r.rlocation("c:\\foo")).isEqualTo("c:\\foo"); + } else { + assertThat(r.rlocation("/foo")).isEqualTo("/foo"); + } + } + + @Test + public void testCreatesDirectoryBasedRunfiles() throws Exception { + Path dir = + Files.createTempDirectory( + FileSystems.getDefault().getPath(System.getenv("TEST_TMPDIR")), null); + + Runfiles r = + Runfiles.create( + ImmutableMap.of( + "RUNFILES_MANIFEST_FILE", "ignored when RUNFILES_MANIFEST_ONLY is not set to 1", + "RUNFILES_DIR", dir.toString(), + "JAVA_RUNFILES", "ignored when RUNFILES_DIR has a value", + "TEST_SRCDIR", "should always be ignored")); + assertThat(r.rlocation("a/b")).endsWith("/a/b"); + assertThat(r.rlocation("foo")).endsWith("/foo"); + + r = + Runfiles.create( + ImmutableMap.of( + "RUNFILES_MANIFEST_FILE", "ignored when RUNFILES_MANIFEST_ONLY is not set to 1", + "RUNFILES_DIR", "", + "JAVA_RUNFILES", dir.toString(), + "TEST_SRCDIR", "should always be ignored")); + assertThat(r.rlocation("a/b")).endsWith("/a/b"); + assertThat(r.rlocation("foo")).endsWith("/foo"); + } + + @Test + public void testIgnoresTestSrcdirWhenJavaRunfilesIsUndefinedAndJustFails() throws Exception { + Path dir = + Files.createTempDirectory( + FileSystems.getDefault().getPath(System.getenv("TEST_TMPDIR")), null); + + Runfiles.create( + ImmutableMap.of( + "RUNFILES_DIR", dir.toString(), + "RUNFILES_MANIFEST_FILE", "ignored when RUNFILES_MANIFEST_ONLY is not set to 1", + "TEST_SRCDIR", "should always be ignored")); + + Runfiles.create( + ImmutableMap.of( + "JAVA_RUNFILES", dir.toString(), + "RUNFILES_MANIFEST_FILE", "ignored when RUNFILES_MANIFEST_ONLY is not set to 1", + "TEST_SRCDIR", "should always be ignored")); + + IOException e = + assertThrows( + IOException.class, + () -> + Runfiles.create( + ImmutableMap.of( + "RUNFILES_DIR", + "", + "JAVA_RUNFILES", + "", + "RUNFILES_MANIFEST_FILE", + "ignored when RUNFILES_MANIFEST_ONLY is not set to 1", + "TEST_SRCDIR", + "should always be ignored"))); + assertThat(e).hasMessageThat().contains("$RUNFILES_DIR and $JAVA_RUNFILES"); + } + + @Test + public void testFailsToCreateManifestBasedBecauseManifestDoesNotExist() { + IOException e = + assertThrows( + IOException.class, + () -> + Runfiles.create( + ImmutableMap.of( + "RUNFILES_MANIFEST_ONLY", "1", + "RUNFILES_MANIFEST_FILE", "non-existing path"))); + assertThat(e).hasMessageThat().contains("non-existing path"); + } + + @Test + public void testManifestBasedEnvVars() throws Exception { + Path mf = tempFile("MANIFEST", ImmutableList.of()); + Map envvars = + Runfiles.create( + ImmutableMap.of( + "RUNFILES_MANIFEST_ONLY", "1", + "RUNFILES_MANIFEST_FILE", mf.toString(), + "RUNFILES_DIR", "ignored when RUNFILES_MANIFEST_ONLY=1", + "JAVA_RUNFILES", "ignored when RUNFILES_DIR has a value", + "TEST_SRCDIR", "should always be ignored")) + .getEnvVars(); + assertThat(envvars.keySet()) + .containsExactly( + "RUNFILES_MANIFEST_ONLY", "RUNFILES_MANIFEST_FILE", "RUNFILES_DIR", "JAVA_RUNFILES"); + assertThat(envvars.get("RUNFILES_MANIFEST_ONLY")).isEqualTo("1"); + assertThat(envvars.get("RUNFILES_MANIFEST_FILE")).isEqualTo(mf.toString()); + assertThat(envvars.get("RUNFILES_DIR")).isEqualTo(tempDir.getRoot().toString()); + assertThat(envvars.get("JAVA_RUNFILES")).isEqualTo(tempDir.getRoot().toString()); + + Path rfDir = tempDir.getRoot().toPath().resolve("foo.runfiles"); + Files.createDirectories(rfDir); + mf = tempFile("foo.runfiles_manifest", ImmutableList.of()); + envvars = + Runfiles.create( + ImmutableMap.of( + "RUNFILES_MANIFEST_ONLY", "1", + "RUNFILES_MANIFEST_FILE", mf.toString(), + "RUNFILES_DIR", "ignored when RUNFILES_MANIFEST_ONLY=1", + "JAVA_RUNFILES", "ignored when RUNFILES_DIR has a value", + "TEST_SRCDIR", "should always be ignored")) + .getEnvVars(); + assertThat(envvars.get("RUNFILES_MANIFEST_ONLY")).isEqualTo("1"); + assertThat(envvars.get("RUNFILES_MANIFEST_FILE")).isEqualTo(mf.toString()); + assertThat(envvars.get("RUNFILES_DIR")).isEqualTo(rfDir.toString()); + assertThat(envvars.get("JAVA_RUNFILES")).isEqualTo(rfDir.toString()); + } + + @Test + public void testDirectoryBasedEnvVars() throws Exception { + Map envvars = + Runfiles.create( + ImmutableMap.of( + "RUNFILES_MANIFEST_FILE", + "ignored when RUNFILES_MANIFEST_ONLY is not set to 1", + "RUNFILES_DIR", + tempDir.getRoot().toString(), + "JAVA_RUNFILES", + "ignored when RUNFILES_DIR has a value", + "TEST_SRCDIR", + "should always be ignored")) + .getEnvVars(); + assertThat(envvars.keySet()).containsExactly("RUNFILES_DIR", "JAVA_RUNFILES"); + assertThat(envvars.get("RUNFILES_DIR")).isEqualTo(tempDir.getRoot().toString()); + assertThat(envvars.get("JAVA_RUNFILES")).isEqualTo(tempDir.getRoot().toString()); + } + + @Test + public void testDirectoryBasedRlocation() throws IOException { + // The DirectoryBased implementation simply joins the runfiles directory and the runfile's path + // on a "/". DirectoryBased does not perform any normalization, nor does it check that the path + // exists. + File dir = new File(System.getenv("TEST_TMPDIR"), "mock/runfiles"); + assertThat(dir.mkdirs()).isTrue(); + Runfiles r = Runfiles.createDirectoryBasedForTesting(dir.toString()).withSourceRepository(""); + // Escaping for "\": once for string and once for regex. + assertThat(r.rlocation("arg")).matches(".*[/\\\\]mock[/\\\\]runfiles[/\\\\]arg"); + } + + @Test + public void testManifestBasedRlocation() throws Exception { + Path mf = + tempFile( + "MANIFEST", + ImmutableList.of( + "Foo/runfile1 C:/Actual Path\\runfile1", + "Foo/Bar/runfile2 D:\\the path\\run file 2.txt", + "Foo/Bar/Dir E:\\Actual Path\\bDirectory", + " h/\\si F:\\bjk", + " dir\\swith\\sspaces F:\\bj k\\bdir with spaces", + " h/\\s\\n\\bi F:\\bjk\\nb")); + Runfiles r = Runfiles.createManifestBasedForTesting(mf.toString()).withSourceRepository(""); + assertThat(r.rlocation("Foo/runfile1")).isEqualTo("C:/Actual Path\\runfile1"); + assertThat(r.rlocation("Foo/Bar/runfile2")).isEqualTo("D:\\the path\\run file 2.txt"); + assertThat(r.rlocation("Foo/Bar/Dir")).isEqualTo("E:\\Actual Path\\bDirectory"); + assertThat(r.rlocation("Foo/Bar/Dir/File")).isEqualTo("E:\\Actual Path\\bDirectory/File"); + assertThat(r.rlocation("Foo/Bar/Dir/Deeply/Nested/File")) + .isEqualTo("E:\\Actual Path\\bDirectory/Deeply/Nested/File"); + assertThat(r.rlocation("Foo/Bar/Dir/Deeply/Nested/File With Spaces")) + .isEqualTo("E:\\Actual Path\\bDirectory/Deeply/Nested/File With Spaces"); + assertThat(r.rlocation("h/ i")).isEqualTo("F:\\jk"); + assertThat(r.rlocation("h/ \n\\i")).isEqualTo("F:\\jk\nb"); + assertThat(r.rlocation("dir with spaces")).isEqualTo("F:\\j k\\dir with spaces"); + assertThat(r.rlocation("dir with spaces/file")).isEqualTo("F:\\j k\\dir with spaces/file"); + assertThat(r.rlocation("unknown")).isNull(); + } + + @Test + public void testManifestBasedRlocationWithRepoMapping_fromMain() throws Exception { + Path rm = + tempFile( + "foo.repo_mapping", + ImmutableList.of( + ",config.json,config.json+1.2.3", + ",my_module,_main", + ",my_protobuf,protobuf+3.19.2", + ",my_workspace,_main", + "protobuf+3.19.2,config.json,config.json+1.2.3", + "protobuf+3.19.2,protobuf,protobuf+3.19.2")); + Path mf = + tempFile( + "foo.runfiles_manifest", + ImmutableList.of( + "_repo_mapping " + rm, + "config.json /etc/config.json", + "protobuf+3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile", + "_main/bar/runfile /the/path/./to/other//other runfile.txt", + "protobuf+3.19.2/bar/dir E:\\Actual Path\\Directory")); + Runfiles r = Runfiles.createManifestBasedForTesting(mf.toString()).withSourceRepository(""); + + assertThat(r.rlocation("my_module/bar/runfile")) + .isEqualTo("/the/path/./to/other//other runfile.txt"); + assertThat(r.rlocation("my_workspace/bar/runfile")) + .isEqualTo("/the/path/./to/other//other runfile.txt"); + assertThat(r.rlocation("my_protobuf/foo/runfile")) + .isEqualTo("C:/Actual Path\\protobuf\\runfile"); + assertThat(r.rlocation("my_protobuf/bar/dir")).isEqualTo("E:\\Actual Path\\Directory"); + assertThat(r.rlocation("my_protobuf/bar/dir/file")) + .isEqualTo("E:\\Actual Path\\Directory/file"); + assertThat(r.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le")) + .isEqualTo("E:\\Actual Path\\Directory/de eply/nes ted/fi+le"); + + assertThat(r.rlocation("protobuf/foo/runfile")).isNull(); + assertThat(r.rlocation("protobuf/bar/dir")).isNull(); + assertThat(r.rlocation("protobuf/bar/dir/file")).isNull(); + assertThat(r.rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi+le")).isNull(); + + assertThat(r.rlocation("_main/bar/runfile")) + .isEqualTo("/the/path/./to/other//other runfile.txt"); + assertThat(r.rlocation("protobuf+3.19.2/foo/runfile")) + .isEqualTo("C:/Actual Path\\protobuf\\runfile"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir")).isEqualTo("E:\\Actual Path\\Directory"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir/file")) + .isEqualTo("E:\\Actual Path\\Directory/file"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le")) + .isEqualTo("E:\\Actual Path\\Directory/de eply/nes ted/fi+le"); + + assertThat(r.rlocation("config.json")).isEqualTo("/etc/config.json"); + assertThat(r.rlocation("_main")).isNull(); + assertThat(r.rlocation("my_module")).isNull(); + assertThat(r.rlocation("protobuf")).isNull(); + } + + @Test + public void testManifestBasedRlocationUnmapped() throws Exception { + Path rm = + tempFile( + "foo.repo_mapping", + ImmutableList.of( + ",config.json,config.json+1.2.3", + ",my_module,_main", + ",my_protobuf,protobuf+3.19.2", + ",my_workspace,_main", + "protobuf+3.19.2,config.json,config.json+1.2.3", + "protobuf+3.19.2,protobuf,protobuf+3.19.2")); + Path mf = + tempFile( + "foo.runfiles_manifest", + ImmutableList.of( + "_repo_mapping " + rm, + "config.json /etc/config.json", + "protobuf+3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile", + "_main/bar/runfile /the/path/./to/other//other runfile.txt", + "protobuf+3.19.2/bar/dir E:\\Actual Path\\Directory")); + Runfiles r = Runfiles.createManifestBasedForTesting(mf.toString()).unmapped(); + + assertThat(r.rlocation("my_module/bar/runfile")).isNull(); + assertThat(r.rlocation("my_workspace/bar/runfile")).isNull(); + assertThat(r.rlocation("my_protobuf/foo/runfile")).isNull(); + assertThat(r.rlocation("my_protobuf/bar/dir")).isNull(); + assertThat(r.rlocation("my_protobuf/bar/dir/file")).isNull(); + assertThat(r.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le")).isNull(); + + assertThat(r.rlocation("protobuf/foo/runfile")).isNull(); + assertThat(r.rlocation("protobuf/bar/dir")).isNull(); + assertThat(r.rlocation("protobuf/bar/dir/file")).isNull(); + assertThat(r.rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi+le")).isNull(); + + assertThat(r.rlocation("_main/bar/runfile")) + .isEqualTo("/the/path/./to/other//other runfile.txt"); + assertThat(r.rlocation("protobuf+3.19.2/foo/runfile")) + .isEqualTo("C:/Actual Path\\protobuf\\runfile"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir")).isEqualTo("E:\\Actual Path\\Directory"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir/file")) + .isEqualTo("E:\\Actual Path\\Directory/file"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le")) + .isEqualTo("E:\\Actual Path\\Directory/de eply/nes ted/fi+le"); + + assertThat(r.rlocation("config.json")).isEqualTo("/etc/config.json"); + assertThat(r.rlocation("_main")).isNull(); + assertThat(r.rlocation("my_module")).isNull(); + assertThat(r.rlocation("protobuf")).isNull(); + } + + @Test + public void testManifestBasedRlocationWithRepoMapping_fromOtherRepo() throws Exception { + Path rm = + tempFile( + "foo.repo_mapping", + ImmutableList.of( + ",config.json,config.json+1.2.3", + ",my_module,_main", + ",my_protobuf,protobuf+3.19.2", + ",my_workspace,_main", + "protobuf+3.19.2,config.json,config.json+1.2.3", + "protobuf+3.19.2,protobuf,protobuf+3.19.2")); + Path mf = + tempFile( + "foo.runfiles/MANIFEST", + ImmutableList.of( + "_repo_mapping " + rm, + "config.json /etc/config.json", + "protobuf+3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile", + "_main/bar/runfile /the/path/./to/other//other runfile.txt", + "protobuf+3.19.2/bar/dir E:\\Actual Path\\Directory")); + Runfiles r = + Runfiles.createManifestBasedForTesting(mf.toString()) + .withSourceRepository("protobuf+3.19.2"); + + assertThat(r.rlocation("protobuf/foo/runfile")).isEqualTo("C:/Actual Path\\protobuf\\runfile"); + assertThat(r.rlocation("protobuf/bar/dir")).isEqualTo("E:\\Actual Path\\Directory"); + assertThat(r.rlocation("protobuf/bar/dir/file")).isEqualTo("E:\\Actual Path\\Directory/file"); + assertThat(r.rlocation("protobuf/bar/dir/de eply/nes ted/fi+le")) + .isEqualTo("E:\\Actual Path\\Directory/de eply/nes ted/fi+le"); + + assertThat(r.rlocation("my_module/bar/runfile")).isNull(); + assertThat(r.rlocation("my_protobuf/foo/runfile")).isNull(); + assertThat(r.rlocation("my_protobuf/bar/dir")).isNull(); + assertThat(r.rlocation("my_protobuf/bar/dir/file")).isNull(); + assertThat(r.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le")).isNull(); + + assertThat(r.rlocation("_main/bar/runfile")) + .isEqualTo("/the/path/./to/other//other runfile.txt"); + assertThat(r.rlocation("protobuf+3.19.2/foo/runfile")) + .isEqualTo("C:/Actual Path\\protobuf\\runfile"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir")).isEqualTo("E:\\Actual Path\\Directory"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir/file")) + .isEqualTo("E:\\Actual Path\\Directory/file"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le")) + .isEqualTo("E:\\Actual Path\\Directory/de eply/nes ted/fi+le"); + + assertThat(r.rlocation("config.json")).isEqualTo("/etc/config.json"); + assertThat(r.rlocation("_main")).isNull(); + assertThat(r.rlocation("my_module")).isNull(); + assertThat(r.rlocation("protobuf")).isNull(); + } + + @Test + public void testManifestBasedRlocationWithRepoMapping_fromExtensionRepo() throws Exception { + Path rm = + tempFile( + "foo.repo_mapping", + ImmutableList.of( + ",config.json,config.json+1.2.3", + ",my_module,_main", + ",my_protobuf,protobuf+3.19.2", + ",my_workspace,_main", + "my_module++ext+*,my_module,my_module+", + "my_module++ext+*,repo1,my_module++ext+repo1")); + Path mf = + tempFile( + "foo.runfiles/MANIFEST", + ImmutableList.of( + "_repo_mapping " + rm, + "config.json /etc/config.json", + "protobuf+3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile", + "_main/bar/runfile /the/path/./to/other//other runfile.txt", + "protobuf+3.19.2/bar/dir E:\\Actual Path\\Directory", + "my_module+/foo/runfile /the/path/to/my_module+/runfile", + "my_module++ext+repo1/foo/runfile /the/path/to/my_module++ext+repo1/runfile", + "repo2+/foo/runfile /the/path/to/repo2+/runfile")); + Runfiles r = + Runfiles.createManifestBasedForTesting(mf.toString()) + .withSourceRepository("my_module++ext+repo1"); + + assertThat(r.rlocation("my_module/foo/runfile")).isEqualTo("/the/path/to/my_module+/runfile"); + assertThat(r.rlocation("repo1/foo/runfile")) + .isEqualTo("/the/path/to/my_module++ext+repo1/runfile"); + assertThat(r.rlocation("repo2+/foo/runfile")).isEqualTo("/the/path/to/repo2+/runfile"); + } + + @Test + public void testDirectoryBasedRlocationWithRepoMapping_fromMain() throws Exception { + Path dir = tempDir.newFolder("foo.runfiles").toPath(); + Path unused = + tempFile( + dir.resolve("_repo_mapping").toString(), + ImmutableList.of( + ",config.json,config.json+1.2.3", + ",my_module,_main", + ",my_protobuf,protobuf+3.19.2", + ",my_workspace,_main", + "protobuf+3.19.2,config.json,config.json+1.2.3", + "protobuf+3.19.2,protobuf,protobuf+3.19.2")); + Runfiles r = Runfiles.createDirectoryBasedForTesting(dir.toString()).withSourceRepository(""); + + assertThat(r.rlocation("my_module/bar/runfile")).isEqualTo(dir + "/_main/bar/runfile"); + assertThat(r.rlocation("my_workspace/bar/runfile")).isEqualTo(dir + "/_main/bar/runfile"); + assertThat(r.rlocation("my_protobuf/foo/runfile")) + .isEqualTo(dir + "/protobuf+3.19.2/foo/runfile"); + assertThat(r.rlocation("my_protobuf/bar/dir")).isEqualTo(dir + "/protobuf+3.19.2/bar/dir"); + assertThat(r.rlocation("my_protobuf/bar/dir/file")) + .isEqualTo(dir + "/protobuf+3.19.2/bar/dir/file"); + assertThat(r.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le")) + .isEqualTo(dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"); + + assertThat(r.rlocation("protobuf/foo/runfile")).isEqualTo(dir + "/protobuf/foo/runfile"); + assertThat(r.rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi+le")) + .isEqualTo(dir + "/protobuf/bar/dir/dir/de eply/nes ted/fi+le"); + + assertThat(r.rlocation("_main/bar/runfile")).isEqualTo(dir + "/_main/bar/runfile"); + assertThat(r.rlocation("protobuf+3.19.2/foo/runfile")) + .isEqualTo(dir + "/protobuf+3.19.2/foo/runfile"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir")).isEqualTo(dir + "/protobuf+3.19.2/bar/dir"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir/file")) + .isEqualTo(dir + "/protobuf+3.19.2/bar/dir/file"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le")) + .isEqualTo(dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"); + + assertThat(r.rlocation("config.json")).isEqualTo(dir + "/config.json"); + } + + @Test + public void testDirectoryBasedRlocationUnmapped() throws Exception { + Path dir = tempDir.newFolder("foo.runfiles").toPath(); + Path unused = + tempFile( + dir.resolve("_repo_mapping").toString(), + ImmutableList.of( + ",config.json,config.json+1.2.3", + ",my_module,_main", + ",my_protobuf,protobuf+3.19.2", + ",my_workspace,_main", + "protobuf+3.19.2,config.json,config.json+1.2.3", + "protobuf+3.19.2,protobuf,protobuf+3.19.2")); + Runfiles r = Runfiles.createDirectoryBasedForTesting(dir.toString()).unmapped(); + + assertThat(r.rlocation("my_module/bar/runfile")).isEqualTo(dir + "/my_module/bar/runfile"); + assertThat(r.rlocation("my_workspace/bar/runfile")) + .isEqualTo(dir + "/my_workspace/bar/runfile"); + assertThat(r.rlocation("my_protobuf/foo/runfile")).isEqualTo(dir + "/my_protobuf/foo/runfile"); + assertThat(r.rlocation("my_protobuf/bar/dir")).isEqualTo(dir + "/my_protobuf/bar/dir"); + assertThat(r.rlocation("my_protobuf/bar/dir/file")) + .isEqualTo(dir + "/my_protobuf/bar/dir/file"); + assertThat(r.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le")) + .isEqualTo(dir + "/my_protobuf/bar/dir/de eply/nes ted/fi+le"); + + assertThat(r.rlocation("protobuf/foo/runfile")).isEqualTo(dir + "/protobuf/foo/runfile"); + assertThat(r.rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi+le")) + .isEqualTo(dir + "/protobuf/bar/dir/dir/de eply/nes ted/fi+le"); + + assertThat(r.rlocation("_main/bar/runfile")).isEqualTo(dir + "/_main/bar/runfile"); + assertThat(r.rlocation("protobuf+3.19.2/foo/runfile")) + .isEqualTo(dir + "/protobuf+3.19.2/foo/runfile"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir")).isEqualTo(dir + "/protobuf+3.19.2/bar/dir"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir/file")) + .isEqualTo(dir + "/protobuf+3.19.2/bar/dir/file"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le")) + .isEqualTo(dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"); + + assertThat(r.rlocation("config.json")).isEqualTo(dir + "/config.json"); + } + + @Test + public void testDirectoryBasedRlocationWithRepoMapping_fromOtherRepo() throws Exception { + Path dir = tempDir.newFolder("foo.runfiles").toPath(); + Path unused = + tempFile( + dir.resolve("_repo_mapping").toString(), + ImmutableList.of( + ",config.json,config.json+1.2.3", + ",my_module,_main", + ",my_protobuf,protobuf+3.19.2", + ",my_workspace,_main", + "protobuf+3.19.2,config.json,config.json+1.2.3", + "protobuf+3.19.2,protobuf,protobuf+3.19.2")); + Runfiles r = + Runfiles.createDirectoryBasedForTesting(dir.toString()) + .withSourceRepository("protobuf+3.19.2"); + + assertThat(r.rlocation("protobuf/foo/runfile")).isEqualTo(dir + "/protobuf+3.19.2/foo/runfile"); + assertThat(r.rlocation("protobuf/bar/dir")).isEqualTo(dir + "/protobuf+3.19.2/bar/dir"); + assertThat(r.rlocation("protobuf/bar/dir/file")) + .isEqualTo(dir + "/protobuf+3.19.2/bar/dir/file"); + assertThat(r.rlocation("protobuf/bar/dir/de eply/nes ted/fi+le")) + .isEqualTo(dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"); + + assertThat(r.rlocation("my_module/bar/runfile")).isEqualTo(dir + "/my_module/bar/runfile"); + assertThat(r.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le")) + .isEqualTo(dir + "/my_protobuf/bar/dir/de eply/nes ted/fi+le"); + + assertThat(r.rlocation("_main/bar/runfile")).isEqualTo(dir + "/_main/bar/runfile"); + assertThat(r.rlocation("protobuf+3.19.2/foo/runfile")) + .isEqualTo(dir + "/protobuf+3.19.2/foo/runfile"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir")).isEqualTo(dir + "/protobuf+3.19.2/bar/dir"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir/file")) + .isEqualTo(dir + "/protobuf+3.19.2/bar/dir/file"); + assertThat(r.rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le")) + .isEqualTo(dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"); + + assertThat(r.rlocation("config.json")).isEqualTo(dir + "/config.json"); + } + + @Test + public void testDirectoryBasedRlocationWithRepoMapping_fromExtensionRepo() throws Exception { + Path dir = tempDir.newFolder("foo.runfiles").toPath(); + Path unused = + tempFile( + dir.resolve("_repo_mapping").toString(), + ImmutableList.of( + ",config.json,config.json+1.2.3", + ",my_module,_main", + ",my_protobuf,protobuf+3.19.2", + ",my_workspace,_main", + "my_module++ext+*,my_module,my_module+", + "my_module++ext+*,repo1,my_module++ext+repo1")); + Runfiles r = + Runfiles.createDirectoryBasedForTesting(dir.toString()) + .withSourceRepository("my_module++ext+repo1"); + + assertThat(r.rlocation("my_module/foo")).isEqualTo(dir + "/my_module+/foo"); + assertThat(r.rlocation("repo1/foo")).isEqualTo(dir + "/my_module++ext+repo1/foo"); + assertThat(r.rlocation("repo2+/foo")).isEqualTo(dir + "/repo2+/foo"); + } + + @Test + public void testDirectoryBasedCtorArgumentValidation() throws IOException { + assertThrows( + IllegalArgumentException.class, + () -> Runfiles.createDirectoryBasedForTesting(null).withSourceRepository("")); + + assertThrows( + IllegalArgumentException.class, + () -> Runfiles.createDirectoryBasedForTesting("").withSourceRepository("")); + + assertThrows( + IllegalArgumentException.class, + () -> + Runfiles.createDirectoryBasedForTesting("non-existent directory is bad") + .withSourceRepository("")); + + Runfiles unused = + Runfiles.createDirectoryBasedForTesting(System.getenv("TEST_TMPDIR")) + .withSourceRepository(""); + } + + @Test + public void testManifestBasedCtorArgumentValidation() throws Exception { + assertThrows( + IllegalArgumentException.class, + () -> Runfiles.createManifestBasedForTesting(null).withSourceRepository("")); + + assertThrows( + IllegalArgumentException.class, + () -> Runfiles.createManifestBasedForTesting("").withSourceRepository("")); + + Path mf = tempFile("foobar", ImmutableList.of("a b")); + Runfiles unused = + Runfiles.createManifestBasedForTesting(mf.toString()).withSourceRepository(""); + } + + @Test + public void testInvalidRepoMapping() throws Exception { + Path rm = tempFile("foo.repo_mapping", ImmutableList.of("a,b,c,d")); + Path mf = tempFile("foo.runfiles/MANIFEST", ImmutableList.of("_repo_mapping " + rm)); + assertThrows( + IllegalArgumentException.class, + () -> Runfiles.createManifestBasedForTesting(mf.toString()).withSourceRepository("")); + } + + private Path tempFile(String path, ImmutableList lines) throws IOException { + Path file = tempDir.getRoot().toPath().resolve(path.replace('/', File.separatorChar)); + Files.createDirectories(file.getParent()); + return Files.write(file, lines, StandardCharsets.UTF_8); + } +} diff --git a/test/java/runfiles/src/test/java/com/google/devtools/build/runfiles/UtilTest.java b/test/java/runfiles/src/test/java/com/google/devtools/build/runfiles/UtilTest.java new file mode 100644 index 00000000..38273261 --- /dev/null +++ b/test/java/runfiles/src/test/java/com/google/devtools/build/runfiles/UtilTest.java @@ -0,0 +1,47 @@ +// Copyright 2018 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.runfiles; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link Util}. */ +@RunWith(JUnit4.class) +public final class UtilTest { + + @Test + public void testIsNullOrEmpty() { + assertThat(Util.isNullOrEmpty(null)).isTrue(); + assertThat(Util.isNullOrEmpty("")).isTrue(); + assertThat(Util.isNullOrEmpty("\0")).isFalse(); + assertThat(Util.isNullOrEmpty("some text")).isFalse(); + } + + @Test + public void testCheckArgument() { + Util.checkArgument(true, null, null); + + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> Util.checkArgument(false, null, null)); + assertThat(e).hasMessageThat().isEqualTo("argument validation failed"); + + e = assertThrows(IllegalArgumentException.class, () -> Util.checkArgument(false, "foo-%s", 42)); + assertThat(e).hasMessageThat().isEqualTo("foo-42"); + } +} diff --git a/test/java/testutil/BUILD b/test/java/testutil/BUILD new file mode 100644 index 00000000..748abb4b --- /dev/null +++ b/test/java/testutil/BUILD @@ -0,0 +1,5 @@ +load(":javac_action_subject_tests.bzl", "javac_action_subject_tests") + +package(default_applicable_licenses = ["@rules_java//:license"]) + +javac_action_subject_tests(name = "javac_action_subject_tests") diff --git a/test/java/testutil/artifact_closure.bzl b/test/java/testutil/artifact_closure.bzl new file mode 100644 index 00000000..129b5884 --- /dev/null +++ b/test/java/testutil/artifact_closure.bzl @@ -0,0 +1,55 @@ +"""Helper for computing the artifact closure of a target""" + +# TODO: consider upstreaming this to @rules_testing + +def _of_target(target): + to_process = target[DefaultInfo].files.to_list() + if _ArtifactActionMapInfo not in target: + fail("Did you forget to add the aspect to analysis_test(extra_target_under_test_aspects = )?") + map = target[_ArtifactActionMapInfo].map + result = [] + visited = {} + for __ in range(len(map)): + if not to_process: + break + next_to_process = [] + for x in to_process: + if x in visited: + continue + visited[x] = None + result.append(x) + if x not in map: + # source file or not visible to us (toolchain?) + continue + a = map[x] + next_to_process.extend([f for f in a.inputs.to_list() if f not in visited]) + to_process = next_to_process + return result + +_ArtifactActionMapInfo = provider( + "Map of artifacts to actions", + fields = ["map"], +) + +def _aspect_impl(target, ctx): + map = {} + for action in target.actions: + for output in action.outputs.to_list(): + map[output] = action + + # Rollup from all dep attributes + for attr_name in dir(ctx.rule.attr): + attr = getattr(ctx.rule.attr, attr_name) + if type(attr) != "list": + attr = [attr] + for val in attr: + if type(val) == "Target" and _ArtifactActionMapInfo in val: + map = map | val[_ArtifactActionMapInfo].map + return _ArtifactActionMapInfo(map = map) + +_aspect = aspect(_aspect_impl, attr_aspects = ["*"]) + +artifact_closure = struct( + aspect = _aspect, + of_target = _of_target, +) diff --git a/test/java/testutil/cc_info_subject.bzl b/test/java/testutil/cc_info_subject.bzl new file mode 100644 index 00000000..b50ff2e6 --- /dev/null +++ b/test/java/testutil/cc_info_subject.bzl @@ -0,0 +1,105 @@ +"""A custom @rules_testing subject for the CcInfo provider""" + +load("@rules_testing//lib:truth.bzl", "subjects") +load("//java:testutil.bzl", "testutil") + +def _new_cc_info_subject(cc_info, meta): + self = struct( + actual = cc_info, + meta = meta, + ) + public = struct( + linking_context = lambda: _new_cc_info_linking_context_subject(self.actual, self.meta), + native_libraries = lambda: subjects.collection(testutil.cc_info_transitive_native_libraries(self.actual), self.meta.derive("transitive_native_libraries()")), + ) + return public + +def _new_cc_info_linking_context_subject(cc_info, meta): + self = struct( + actual = cc_info.linking_context, + meta = meta.derive("linking_context"), + ) + public = struct( + equals = lambda other: _cc_info_linking_context_equals(self.actual, other, self.meta), + library_files = lambda: _new_library_files_subject(self.actual, self.meta), + static_library_files = lambda: _new_static_library_files_subject(self.actual, self.meta), + ) + return public + +def _new_library_files_subject(linking_context, meta): + libs = [] + for input in linking_context.linker_inputs.to_list(): + for lib in input.libraries: + if lib.pic_static_library: + libs.append(lib.pic_static_library) + elif lib.static_library: + libs.append(lib.static_library) + elif lib.interface_library: + libs.append(lib.interface_library) + else: + libs.append(lib.dynamic_library) + + return subjects.depset_file( + depset(libs), + meta = meta.derive("library_files"), + ) + +def _new_static_library_files_subject(linking_context, meta): + static_libraries = [] + for input in linking_context.linker_inputs.to_list(): + for lib in input.libraries: + if lib.static_library: + static_libraries.append(lib.static_library) + return subjects.depset_file( + depset(static_libraries), + meta = meta.derive("static_library_files"), + ) + +def _cc_info_linking_context_equals(actual, expected, meta): + if actual == expected: + return + meta.add_failure( + "expected: {}".format(expected), + "actual: {}".format(actual), + ) + +def _new_cc_info_libraries_to_link_subject(libraries_to_link, meta): + if hasattr(libraries_to_link, "to_list"): + libraries_to_link = libraries_to_link.to_list() + self = struct( + actual = libraries_to_link, + meta = meta, + ) + public = struct( + static_libraries = lambda: _new_library_to_link_static_libraries_subject(self.actual, self.meta), + singleton = lambda: _new_library_to_link_subject(_get_singleton(self.actual), self.meta.derive("[0]")), + ) + return public + +def _new_library_to_link_subject(library_to_link, meta): + public = struct( + dynamic_library = lambda: subjects.file(library_to_link.dynamic_library, meta.derive("dynamic_library")), + ) + return public + +def _new_library_to_link_static_libraries_subject(libraries_to_link, meta): + self = subjects.collection( + [testutil.cc_library_to_link_static_library(lib) for lib in libraries_to_link], + meta = meta.derive("static_library()"), + ).transform(desc = "basename", map_each = lambda file: file.basename) + public = struct( + contains_exactly = lambda expected: self.contains_exactly([meta.format_str(e) for e in expected]), + contains_exactly_predicates = lambda expected: self.contains_exactly_predicates(expected), + ) + return public + +def _get_singleton(seq): + if len(seq) != 1: + fail("expected singleton, got:", seq) + return seq[0] + +cc_info_subject = struct( + new_from_cc_info = _new_cc_info_subject, + new_from_java_info = lambda java_info, meta: _new_cc_info_subject(java_info.cc_link_params_info, meta.derive("cc_link_params_info")), + libraries_to_link = _new_cc_info_libraries_to_link_subject, +) diff --git a/test/java/testutil/helper.bzl b/test/java/testutil/helper.bzl new file mode 100644 index 00000000..eace0f85 --- /dev/null +++ b/test/java/testutil/helper.bzl @@ -0,0 +1,20 @@ +"""Misc helpers for rules_java testing""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test") +load("@rules_testing//lib:util.bzl", "util") + +def always_passes(name): + """Declares a fake, always passing test + + Args: + name: (str) the name of the test + """ + util.helper_target( + native.filegroup, + name = name + "/empty", + ) + analysis_test( + name = name, + impl = lambda *a, **kw: None, + target = name + "/empty", + ) diff --git a/test/java/testutil/java_info_subject.bzl b/test/java/testutil/java_info_subject.bzl new file mode 100644 index 00000000..9a0fb533 --- /dev/null +++ b/test/java/testutil/java_info_subject.bzl @@ -0,0 +1,215 @@ +"""A custom @rules_testing subject for the JavaInfo provider""" + +load("@rules_testing//lib:truth.bzl", "subjects", "truth") +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_info.bzl", "JavaInfo") +load("//java/common:java_plugin_info.bzl", "JavaPluginInfo") +load("//java/common/rules/impl:java_helper.bzl", "helper") +load(":cc_info_subject.bzl", "cc_info_subject") + +def _new_java_info_subject(java_info, meta): + self = struct(actual = java_info, meta = meta.derive("JavaInfo")) + public = struct( + compilation_args = lambda: _new_java_compilation_args_subject(self.actual, self.meta), + compilation_info = lambda: _new_java_compilation_info_subject(self.actual, self.meta), + plugins = lambda: _new_java_plugin_data_subject(self.actual.plugins, self.meta.derive("plugins")), + api_generating_plugins = lambda: _new_java_plugin_data_subject(self.actual.api_generating_plugins, self.meta.derive("api_generating_plugins")), + is_binary = lambda: subjects.bool(getattr(java_info, "_is_binary", False), self.meta.derive("_is_binary")), + has_attr = lambda a: subjects.bool(getattr(java_info, a, None) != None, meta = self.meta.derive("{} != None".format(a))).equals(True), + cc_link_params_info = lambda: cc_info_subject.new_from_java_info(java_info, meta), + transitive_native_libraries = lambda: cc_info_subject.libraries_to_link(java_info.transitive_native_libraries, self.meta.derive("transitive_native_libraries")), + constraints = lambda: subjects.collection(java_common.get_constraints(java_info), self.meta.derive("constraints")), + annotation_processing = lambda: _new_annotation_processing_subject(self.actual, self.meta), + outputs = lambda: _new_rule_output_info_subject(self.actual, self.meta), + java_outputs = lambda: _new_java_outputs_collection_subject(self.actual.java_outputs, self.meta.derive("java_outputs")), + source_jars = lambda: subjects.collection(java_info.source_jars, self.meta.derive("source_jars")), + transitive_source_jars = lambda: subjects.depset_file(java_info.transitive_source_jars, self.meta.derive("transitive_source_jars")), + transitive_source_jars_list = lambda: subjects.collection(java_info.transitive_source_jars.to_list(), self.meta.derive("transitive_source_jars.to_list()")), + runtime_output_jars = lambda: subjects.depset_file(java_info.runtime_output_jars, self.meta.derive("runtime_output_jars")), + module_flags = lambda: _new_java_module_flags_subject(self.actual, self.meta), + is_neverlink = lambda: subjects.bool(getattr(java_info, "_neverlink", False), self.meta.derive("_neverlink")), + ) + return public + +def _java_info_subject_from_target(env, target): + return _new_java_info_subject(target[JavaInfo], meta = truth.expect(env).meta.derive( + format_str_kwargs = { + "name": target.label.name, + "package": target.label.package, + }, + )) + +def _new_java_module_flags_subject(java_info, meta): + self = struct( + actual = java_info.module_flags_info, + meta = meta.derive("module_flags_info"), + ) + public = struct( + add_exports = lambda: subjects.collection(self.actual.add_exports.to_list(), self.meta.derive("add_exports")), + add_opens = lambda: subjects.collection(self.actual.add_opens.to_list(), self.meta.derive("add_opens")), + ) + return public + +def _new_java_compilation_info_subject(java_info, meta): + self = struct( + actual = java_info.compilation_info, + meta = meta.derive("compilation_info"), + ) + public = struct( + compilation_classpath = lambda: subjects.depset_file(self.actual.compilation_classpath, self.meta.derive("compilation_classpath")), + boot_classpath = lambda: subjects.depset_file(self.actual.boot_classpath, self.meta.derive("boot_classpath")), + runtime_classpath = lambda: subjects.depset_file(self.actual.runtime_classpath, self.meta.derive("runtime_classpath")), + runtime_classpath_list = lambda: subjects.collection(self.actual.runtime_classpath.to_list(), self.meta.derive("runtime_classpath.to_list()"), format = True), + javac_options = lambda: subjects.collection(helper.tokenize_javacopts(opts = self.actual.javac_options), self.meta.derive("javac_options"), format = True), + ) + return public + +def _new_rule_output_info_subject(java_info, meta): + actual = java_info.outputs + self = struct( + actual = actual, + meta = meta.derive("outputs"), + ) + + # JavaOutputInfo.source_jars is a list before Bazel 7 + source_jars_depset = depset([f for o in actual.jars for f in (o.source_jars.to_list() if hasattr(o.source_jars, "to_list") else o.source_jars)]) + public = struct( + jars = lambda: _new_java_outputs_collection_subject(actual.jars, self.meta.derive("jars")), + class_output_jars = lambda: subjects.depset_file(depset([o.class_jar for o in actual.jars]), self.meta.derive("class_output_jars")), + source_output_jars = lambda: subjects.depset_file(source_jars_depset, self.meta.derive("source_output_jars")), + generated_class_jars = lambda: subjects.depset_file(depset([o.generated_class_jar for o in actual.jars]), self.meta.derive("generated_class_jars")), + generated_source_jars = lambda: subjects.depset_file(depset([o.generated_source_jar for o in actual.jars]), self.meta.derive("generated_source_jars")), + jdeps = lambda: subjects.depset_file(depset([o.jdeps for o in actual.jars]), self.meta.derive("jdeps")), + compile_jdeps = lambda: subjects.depset_file(depset([o.compile_jdeps for o in actual.jars]), self.meta.derive("compile_jdeps")), + native_headers = lambda: subjects.depset_file(depset([o.native_headers_jar for o in actual.jars]), self.meta.derive("native_headers")), + manifest_protos = lambda: subjects.depset_file(depset([o.manifest_proto for o in actual.jars]), self.meta.derive("manifest_protos")), + ) + return public + +def _new_java_outputs_collection_subject(java_outputs, meta): + public = struct( + singleton = lambda: _new_java_outputs_subject(_get_singleton(java_outputs), meta.derive("[0]")), + ) + return public + +def _new_java_outputs_subject(java_output, meta): + public = struct( + class_jar = lambda: subjects.file(java_output.class_jar, meta.derive("class_jar")), + compile_jar = lambda: subjects.file(java_output.compile_jar, meta.derive("compile_jar")), + source_jars = lambda: subjects.depset_file(java_output.source_jars if hasattr(java_output.source_jars, "to_list") else depset(java_output.source_jars), meta.derive("source_jars")), + jdeps = lambda: subjects.file(java_output.jdeps, meta.derive("jdeps")), + compile_jdeps = lambda: subjects.file(java_output.compile_jdeps, meta.derive("compile_jdeps")), + manifest_proto = lambda: subjects.file(java_output.manifest_proto, meta.derive("manifest_proto")), + native_headers_jar = lambda: subjects.file(java_output.native_headers_jar, meta.derive("native_headers_jar")), + ) + return public + +def _new_java_compilation_args_subject(java_info, meta): + is_binary = getattr(java_info, "_is_binary", False) + actual = struct( + transitive_runtime_jars = java_info.transitive_runtime_jars, + compile_jars = java_info.compile_jars, + transitive_compile_time_jars = java_info.transitive_compile_time_jars, + full_compile_jars = java_info.full_compile_jars, + _transitive_full_compile_time_jars = getattr(java_info, "_transitive_full_compile_time_jars", None), # not in Bazel 6 + _compile_time_java_dependencies = getattr(java_info, "_compile_time_java_dependencies", None), # not in Bazel 6 + ) if not is_binary else None + self = struct( + actual = actual, + meta = meta, + ) + return struct( + equals = lambda other: _java_compilation_args_equals(self, other), + equals_subject = lambda other: _java_compilation_args_equals(self, other.actual), + compile_jars = lambda: subjects.depset_file(actual.compile_jars, self.meta.derive("compile_jars")), + full_compile_jars = lambda: subjects.depset_file(actual.full_compile_jars, self.meta.derive("full_compile_jars")), + transitive_runtime_jars = lambda: subjects.depset_file(actual.transitive_runtime_jars, self.meta.derive("transitive_runtime_jars")), + transitive_compile_time_jars = lambda: subjects.depset_file(actual.transitive_compile_time_jars, self.meta.derive("transitive_compile_time_jars")), + transitive_runtime_jars_list = lambda: subjects.collection(actual.transitive_runtime_jars.to_list(), self.meta.derive("transitive_runtime_jars.to_list()")), + compile_time_java_dependencies = lambda: subjects.depset_file(actual._compile_time_java_dependencies, self.meta.derive("_compile_time_java_dependencies")), + self = self, + actual = actual, + ) + +def _java_compilation_args_equals(self, other): + if self.actual == other: + return + for attr in dir(other): + other_attr = getattr(other, attr) + this_attr = getattr(self.actual, attr) + if this_attr != other_attr: + self.meta.derive(attr).add_failure( + "expected: {}".format(other_attr), + "actual: {}".format(this_attr), + ) + +def _new_java_plugin_data_subject(java_plugin_data, meta): + public = struct( + processor_jars = lambda: subjects.depset_file(java_plugin_data.processor_jars, meta = meta.derive("processor_jars")), + processor_classes = lambda: subjects.collection(java_plugin_data.processor_classes, meta = meta.derive("processor_classes")), + processor_data = lambda: subjects.depset_file(java_plugin_data.processor_data, meta = meta.derive("processor_data")), + is_empty = lambda: _check_plugin_data_empty(java_plugin_data, meta.derive("is_empty()")), + equals = lambda other: subjects.bool(java_plugin_data == other, meta.derive("equals({})".format(other))).equals(True), + ) + return public + +def _check_plugin_data_empty(plugin_data, meta): + for attr in ["processor_jars", "processor_classes", "processor_data"]: + value = getattr(plugin_data, attr) + if value: + meta.add_failure( + "expected: {} to be empty".format(attr), + "actual: {}".format(value), + ) + +def _new_annotation_processing_subject(java_info, meta): + actual = java_info.annotation_processing + meta = meta.derive("annotation_processing") + self = struct( + actual = actual, + meta = meta, + ) + public = struct( + is_enabled = lambda: subjects.bool(actual.enabled, meta = meta.derive("is_enabled")), + processor_classnames = lambda: subjects.collection(actual.processor_classnames, meta = meta.derive("processor_classnames")), + processor_classpath = lambda: subjects.depset_file(actual.processor_classpath, meta = meta.derive("processor_classpath")), + class_jar = lambda: subjects.file(actual.class_jar, meta = meta.derive("class_jar")), + source_jar = lambda: subjects.file(actual.source_jar, meta = meta.derive("source_jar")), + transitive_class_jars = lambda: subjects.depset_file(actual.transitive_class_jars, meta = meta.derive("transitive_class_jars")), + transitive_source_jars = lambda: subjects.depset_file(actual.transitive_source_jars, meta = meta.derive("transitive_source_jars")), + actual = actual, + self = self, + ) + return public + +def _new_java_plugin_info_subject(java_plugin_info, meta): + self = struct(actual = java_plugin_info, meta = meta.derive("JavaPluginInfo")) + public = struct( + java_outputs = lambda: _new_java_outputs_collection_subject(self.actual.java_outputs, self.meta.derive("java_outputs")), + plugins = lambda: _new_java_plugin_data_subject(self.actual.plugins, self.meta.derive("plugins")), + api_generating_plugins = lambda: _new_java_plugin_data_subject(self.actual.api_generating_plugins, self.meta.derive("api_generating_plugins")), + ) + return public + +def _java_plugin_info_subject_from_target(env, target): + return _new_java_plugin_info_subject(target[JavaPluginInfo], meta = truth.expect(env).meta.derive( + format_str_kwargs = { + "name": target.label.name, + "package": target.label.package, + }, + )) + +def _get_singleton(seq): + if len(seq) != 1: + fail("expected singleton, got:", seq) + return seq[0] + +java_info_subject = struct( + new = _new_java_info_subject, + from_target = _java_info_subject_from_target, +) + +java_plugin_info_subject = struct( + new = _new_java_plugin_info_subject, + from_target = _java_plugin_info_subject_from_target, +) diff --git a/test/java/testutil/java_runtime_info_subject.bzl b/test/java/testutil/java_runtime_info_subject.bzl new file mode 100644 index 00000000..bb6ae52c --- /dev/null +++ b/test/java/testutil/java_runtime_info_subject.bzl @@ -0,0 +1,66 @@ +"""Custom @rules_testing subject for the JavaRuntimeInfo provider""" + +load("@rules_testing//lib:truth.bzl", "subjects", "truth") +load("@rules_testing//lib:util.bzl", "TestingAspectInfo") +load("//java/common:java_common.bzl", "java_common") +load(":cc_info_subject.bzl", "cc_info_subject") + +def _new_java_runtime_info_subject(java_runtime_info, meta): + self = struct( + actual = java_runtime_info, + meta = meta.derive("JavaRuntimeInfo"), + ) + public = struct( + hermetic_static_libs = lambda: _new_hermetic_static_libs_subject(self.actual.hermetic_static_libs, self.meta.derive("hermetic_static_libs")), + java_home = lambda: _new_path_string_subject(self.actual.java_home, self.meta.derive("java_home")), + java_home_runfiles_path = lambda: _new_path_string_subject(self.actual.java_home_runfiles_path, self.meta.derive("java_home_runfiles_path")), + java_executable_exec_path = lambda: _new_path_string_subject(self.actual.java_executable_exec_path, self.meta.derive("java_executable_exec_path")), + java_executable_runfiles_path = lambda: _new_path_string_subject(self.actual.java_executable_runfiles_path, self.meta.derive("java_executable_runfiles_path")), + files = lambda: subjects.depset_file(self.actual.files, self.meta.derive("files")), + lib_ct_sym = lambda: subjects.file(self.actual.lib_ct_sym, self.meta.derive("lib_ct_sym")), + ) + return public + +def _new_hermetic_static_libs_subject(hermetic_static_libs, meta): + public = struct( + singleton = lambda: cc_info_subject.new_from_cc_info(_get_singleton(hermetic_static_libs), meta.derive("cc_info")), + ) + return public + +def _new_path_string_subject(str, meta): + public = struct( + equals = lambda other: subjects.str(str, meta).equals(meta.format_str(other)), + is_in = lambda expected: subjects.str(str, meta).is_in([meta.format_str(e) for e in expected]), + starts_with = lambda prefix: _check_str_prefix(str, prefix, meta), + ) + return public + +def _check_str_prefix(actual, prefix, meta): + if not actual.startswith(meta.format_str(prefix)): + meta.add_failure( + "did not start with required prefix: {}".format(prefix), + "actual: {}".format(actual), + ) + +def _from_target(env, target): + return _new_java_runtime_info_subject( + target[java_common.JavaRuntimeInfo], + meta = truth.expect(env).meta.derive( + format_str_kwargs = { + "name": target.label.name, + "package": target.label.package, + "bindir": target[TestingAspectInfo].bin_path, + "gendir": env.ctx.configuration.genfiles_dir.path, + }, + ), + ) + +def _get_singleton(seq): + if len(seq) != 1: + fail("expected singleton, got:", seq) + return seq[0] + +java_runtime_info_subject = struct( + new = _new_java_runtime_info_subject, + from_target = _from_target, +) diff --git a/test/java/testutil/java_toolchain_info_subject.bzl b/test/java/testutil/java_toolchain_info_subject.bzl new file mode 100644 index 00000000..31afe59d --- /dev/null +++ b/test/java/testutil/java_toolchain_info_subject.bzl @@ -0,0 +1,44 @@ +"""Custom @rules_testing subject for the JavaToolchainInfo provider""" + +load("@rules_testing//lib:truth.bzl", "subjects") +load("//java/common:java_common.bzl", "java_common") + +def _new_java_builder_subject(tool_info, meta): + return subjects.struct( + struct( + data = [f.path for f in tool_info.data.to_list()], + jvm_opts = tool_info.jvm_opts.to_list(), + ), + meta = meta, + attrs = { + "data": lambda values, *, meta: subjects.collection(values, meta = meta, format = True), + "jvm_opts": lambda values, *, meta: subjects.collection(values, meta = meta, format = True), + }, + ) + +def _new_java_toolchain_info_subject(info, meta): + public = struct( + jacocorunner = lambda: subjects.file(info.jacocorunner.executable, meta.derive("jacocorunner.executable")), + timezone_data = lambda: subjects.file(info._timezone_data, meta.derive("_timezone_data")), + header_compiler_builtin_processors = lambda: subjects.collection(info._header_compiler_builtin_processors.to_list(), meta.derive("_header_compiler_builtin_processors")), + reduced_classpath_incompatible_processors = lambda: subjects.collection(info._reduced_classpath_incompatible_processors.to_list(), meta.derive("_reduced_classpath_incompatible_processors")), + javabuilder = lambda: _new_java_builder_subject(info._javabuilder, meta.derive("_javabuilder")), + label = lambda: subjects.label(info.label, meta.derive("label")), + # TODO: hvd - Give label_subject predicate matching support so we don't need this str_subject variant. + label_str = lambda: subjects.str(str(info.label), meta.derive("label_str")), + default_javacopts = lambda: subjects.collection(info._javacopts_list, meta.derive("default_javacopts")), + default_javacopts_depset = lambda: subjects.collection(info._javacopts.to_list(), meta.derive("default_javacopts_depset")), + ) + return public + +def _from_target(env, target): + return env.expect.that_target(target).provider( + java_common.JavaToolchainInfo, + factory = _new_java_toolchain_info_subject, + provider_name = "JavaToolchainInfo", + ) + +java_toolchain_info_subject = struct( + new = _new_java_toolchain_info_subject, + from_target = _from_target, +) diff --git a/test/java/testutil/javac_action_subject.bzl b/test/java/testutil/javac_action_subject.bzl new file mode 100644 index 00000000..a887d689 --- /dev/null +++ b/test/java/testutil/javac_action_subject.bzl @@ -0,0 +1,79 @@ +"""Bespoke rules_testing subject for the Java compilation action""" + +load("@rules_testing//lib:truth.bzl", "subjects", "truth") +load("@rules_testing//lib:util.bzl", "TestingAspectInfo") + +def _new_javac_action_subject(env, target, output): + action_subject = env.expect.that_target(target).action_generating(output) + self = struct( + actual = action_subject.actual, + parsed_flags = _parse_flags(action_subject.actual.argv), + meta = truth.expect(env).meta.derive( + "Javac", + format_str_kwargs = { + "name": target.label.name, + "package": target.label.package, + "bin_path": target[TestingAspectInfo].bin_path, + }, + ), + ) + + public = struct( + direct_dependencies = lambda: _create_subject_for_flag("--direct_dependencies", self.parsed_flags, self.meta), + deps_artifacts = lambda: _create_subject_for_flag("--deps_artifacts", self.parsed_flags, self.meta), + javacopts = lambda: _create_subject_for_flag("--javacopts", self.parsed_flags, self.meta), + jar = lambda: _create_subject_for_flag("-jar", self.parsed_flags, self.meta), + # An unset --strict_java_deps is equivalent to "OFF". + strict_java_deps = lambda: _create_subject_for_flag("--strict_java_deps", self.parsed_flags, self.meta, default = ["OFF"]), + sources = lambda: _create_subject_for_flag("--sources", self.parsed_flags, self.meta), + resources = lambda: _create_subject_for_flag("--resources", self.parsed_flags, self.meta), + classpath = lambda: _create_subject_for_flag("--classpath", self.parsed_flags, self.meta), + bootclasspath = lambda: _create_subject_for_flag("--bootclasspath", self.parsed_flags, self.meta), + system = lambda: _create_subject_for_flag("--system", self.parsed_flags, self.meta), + generated_sources_output = lambda: _create_subject_for_flag("--generated_sources_output", self.parsed_flags, self.meta), + processorpath = lambda: _create_subject_for_flag("--processorpath", self.parsed_flags, self.meta), + processors = lambda: _create_subject_for_flag("--processors", self.parsed_flags, self.meta, default = []), + target_label = lambda: _create_subject_for_flag("--target_label", self.parsed_flags, self.meta), + executable_file_name = lambda: subjects.str(action_subject.actual.argv[0], self.meta), + inputs = action_subject.inputs, + outputs = lambda: subjects.collection([o.short_path for o in action_subject.actual.outputs.to_list()], self.meta.derive("outputs()"), format = True), + argv = action_subject.argv, + mnemonic = action_subject.mnemonic, + native_header_output = lambda: _create_subject_for_flag("--native_header_output", self.parsed_flags, self.meta), + ) + return public + +def _parse_flags(argv): + flag_values = {} + current_flag_name = None + for idx, arg in enumerate(argv): + if idx == 0: + continue # java command + + if current_flag_name == "--javacopts" and arg == "--": + current_flag_name = None + continue + + if arg.startswith("-") and current_flag_name != "--javacopts": + if "=" in arg: + parts = arg.split("=", 1) + flag_values.setdefault(parts[0], []).append(parts[1]) + current_flag_name = None + else: + flag_values.setdefault(arg, []) + current_flag_name = arg + else: + if not current_flag_name: + fail("No preceding flag for value:", arg, "at index:", idx, "\nargv:\n", argv) + flag_values[current_flag_name].append(arg) + + return flag_values + +def _create_subject_for_flag(flag_name, parsed_flags, meta, default = None): + """Helper to create a collection subject for a given flag.""" + return subjects.collection(parsed_flags.get(flag_name, default), meta.derive(flag_name), format = True) + +javac_action_subject = struct( + of = _new_javac_action_subject, + parse_flags = _parse_flags, # exposed for testing this method itself +) diff --git a/test/java/testutil/javac_action_subject_tests.bzl b/test/java/testutil/javac_action_subject_tests.bzl new file mode 100644 index 00000000..dc8f3f47 --- /dev/null +++ b/test/java/testutil/javac_action_subject_tests.bzl @@ -0,0 +1,84 @@ +"""Tests for the javac_action_subject""" + +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load(":javac_action_subject.bzl", "javac_action_subject") + +def _parse_flags_test_impl(ctx): + env = unittest.begin(ctx) + flags = javac_action_subject.parse_flags([ + "/usr/bin/java", + "-Xmx1g", + "-XX:SomeProp=SomeVal", + "-Dcom.google.foo=bar", + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "-jar", + "JavaBuilder_deploy.jar", + "--output", + "blaze-out/k8/bin/pkg/libfoo.jar", + "--javacopts", + "-source", + "21", + "-target", + "17", + "-g", + "-parameters", + "-sourcepath", + ":", + "-Xmaxerrs", + "123", + "--", + "--strict_java_deps", + "ERROR", + "--classpath", + "pkg/bar-hjar.jar", + "other/pkg/baz.jar", + "--processors", + "com.example.process.stuff", + "com.example.process.other", + "--deps_artifacts", + "pkg/dep1.jdeps", + "pkg/dep2.jdeps", + ]) + asserts.equals(env, { + "-Xmx1g": [], + "-XX:SomeProp": ["SomeVal"], + "-Dcom.google.foo": ["bar"], + "--add-exports": [ + "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + ], + "--add-opens": [ + "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + ], + "-jar": ["JavaBuilder_deploy.jar"], + "--output": ["blaze-out/k8/bin/pkg/libfoo.jar"], + "--javacopts": [ + "-source", + "21", + "-target", + "17", + "-g", + "-parameters", + "-sourcepath", + ":", + "-Xmaxerrs", + "123", + ], + "--strict_java_deps": ["ERROR"], + "--classpath": ["pkg/bar-hjar.jar", "other/pkg/baz.jar"], + "--processors": ["com.example.process.stuff", "com.example.process.other"], + "--deps_artifacts": ["pkg/dep1.jdeps", "pkg/dep2.jdeps"], + }, flags) + return unittest.end(env) + +_parse_flags_test = unittest.make(_parse_flags_test_impl) + +def javac_action_subject_tests(name): + unittest.suite( + name, + _parse_flags_test, + ) diff --git a/test/java/testutil/mock_cc_toolchain.bzl b/test/java/testutil/mock_cc_toolchain.bzl new file mode 100644 index 00000000..6680938d --- /dev/null +++ b/test/java/testutil/mock_cc_toolchain.bzl @@ -0,0 +1,56 @@ +"""Fake cc_toolchain for testing arbitrary --platforms/--cpu""" + +load("@rules_cc//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_TYPE") +load("@rules_cc//cc/common:cc_common.bzl", "cc_common") +load("@rules_cc//cc/toolchains:cc_toolchain.bzl", "cc_toolchain") +load("@rules_cc//cc/toolchains:cc_toolchain_config_info.bzl", "CcToolchainConfigInfo") + +def _mock_config_impl(ctx): + return [ + cc_common.create_cc_toolchain_config_info( + ctx = ctx, + toolchain_identifier = ctx.attr.id, + compiler = "nothing", + # These are deprecated but are mandatory parameters for older Bazel versions. + target_system_name = "deprecated_system_name", + target_cpu = "deprecated_cpu", + target_libc = "deprecated_libc", + ), + ] + +_mock_config = rule( + implementation = _mock_config_impl, + attrs = { + "id": attr.string(mandatory = True), + }, + provides = [CcToolchainConfigInfo], +) + +def mock_cc_toolchain(*, name, cpu, os, **kwargs): + _mock_config( + name = name + "_config", + id = cpu + "-" + os, + **kwargs + ) + cc_toolchain( + name = name + "_impl", + all_files = ":nothing", + as_files = ":nothing", + compiler_files = ":nothing", + dwp_files = ":nothing", + linker_files = ":nothing", + objcopy_files = ":nothing", + strip_files = ":nothing", + toolchain_config = name + "_config", + **kwargs + ) + native.toolchain( + name = name, + toolchain = name + "_impl", + toolchain_type = CC_TOOLCHAIN_TYPE, + target_compatible_with = [ + "@platforms//cpu:" + cpu, + "@platforms//os:" + os, + ], + **kwargs + ) diff --git a/test/java/testutil/mock_java_toolchain.bzl b/test/java/testutil/mock_java_toolchain.bzl new file mode 100644 index 00000000..aae0afde --- /dev/null +++ b/test/java/testutil/mock_java_toolchain.bzl @@ -0,0 +1,72 @@ +"""Fake java toolchains for testing""" + +load("//java/common:java_semantics.bzl", "semantics") +load("//java/toolchains:java_runtime.bzl", _java_runtime_rule = "java_runtime") +load("//java/toolchains:java_toolchain.bzl", "java_toolchain") + +# buildifier: disable=function-docstring +def mock_java_toolchain( + *, + name, + singlejar = "singlejar", + javabuilder = "JavaBuilder_deploy.jar", + header_compiler = "turbine_canary_deploy.jar", + header_compiler_direct = "turbine_direct", + ijar = "ijar", + genclass = "genclass", + java_runtime = None, + oneversion = None, + oneversion_allowlist = None, + oneversion_allowlist_for_tests = None, + tags = None, # for util.helper_target + **kwargs): + if not java_runtime: + java_runtime = name + "_runtime" + _java_runtime_rule(name = java_runtime) + one_version_args = { + "oneversion": oneversion, + "oneversion_allowlist": oneversion_allowlist, + "oneversion_allowlist_for_tests": oneversion_allowlist_for_tests, + } if semantics.java_toolchain_supports_one_version else {} + java_toolchain( + name = name + "_java", + javabuilder = javabuilder, + singlejar = singlejar, + header_compiler = header_compiler, + header_compiler_direct = header_compiler_direct, + ijar = ijar, + java_runtime = java_runtime, + genclass = genclass, + tags = tags, + **(kwargs | one_version_args) + ) + native.toolchain( + name = name, + toolchain = name + "_java", + toolchain_type = semantics.JAVA_TOOLCHAIN_TYPE, + tags = tags, + ) + +# buildifier: disable=function-docstring +def mock_java_runtime_toolchain( + *, + name, + srcs = [], + java_home = None, + java = None, + version = None, + **kwargs): + _java_runtime_rule( + name = name + "_runtime", + srcs = srcs, + java_home = java_home, + java = java, + version = version, + **kwargs + ) + native.toolchain( + name = name, + toolchain = name + "_runtime", + toolchain_type = semantics.JAVA_RUNTIME_TOOLCHAIN_TYPE, + **kwargs + ) diff --git a/test/java/testutil/mock_test_toolchain.bzl b/test/java/testutil/mock_test_toolchain.bzl new file mode 100644 index 00000000..cf73145e --- /dev/null +++ b/test/java/testutil/mock_test_toolchain.bzl @@ -0,0 +1,29 @@ +"""Fake test toolchain for testing arbitrary --platforms""" + +load("@bazel_features//:features.bzl", "bazel_features") +load("@rules_testing//lib:util.bzl", "util") +load("//java/common:java_semantics.bzl", "semantics") + +def mock_test_toolchains(name, cpu, os): + """Creates and returns a list of mock test toolchains for the given cpu and os if they're required. + + Args: + name: The name of the toolchain. + cpu: The cpu for toolchain should be compatible with. + os: The os the toolchain should be compatible with. + Returns: + A list of toolchain targets. + """ + if not bazel_features.toolchains.has_default_test_toolchain_type: + return [] + util.helper_target( + rule = native.toolchain, + name = name, + toolchain_type = Label(semantics.TOOLS_TEST_DEFAULT_TEST_TOOLCHAIN_TYPE), + toolchain = Label(semantics.TOOLS_TEST_EMPTY_TOOLCHAIN), + target_compatible_with = [ + "@platforms//os:" + os, + "@platforms//cpu:" + cpu, + ], + ) + return [native.package_relative_label(name)] diff --git a/test/java/testutil/rules/bad_java_info_rules.bzl b/test/java/testutil/rules/bad_java_info_rules.bzl new file mode 100644 index 00000000..0f5bb1b5 --- /dev/null +++ b/test/java/testutil/rules/bad_java_info_rules.bzl @@ -0,0 +1,39 @@ +"""Helper rules to test errors in JavaInfo instantiation""" + +load("//java/common:java_info.bzl", "JavaInfo") + +def _make_file(ctx): + f = ctx.actions.declare_file(ctx.label.name + ".out") + ctx.actions.write(f, "out") + return f + +def _deps_impl(ctx): + f = _make_file(ctx) + return JavaInfo(output_jar = f, compile_jar = f, deps = [f]) + +def _runtime_deps_impl(ctx): + f = _make_file(ctx) + return JavaInfo(output_jar = f, compile_jar = f, runtime_deps = [f]) + +def _exports_impl(ctx): + f = _make_file(ctx) + return JavaInfo(output_jar = f, compile_jar = f, exports = [f]) + +def _nativelibs_impl(ctx): + f = _make_file(ctx) + return JavaInfo(output_jar = f, compile_jar = f, native_libraries = [f]) + +def _compile_jar_not_set_impl(ctx): + f = _make_file(ctx) + return JavaInfo(output_jar = f) + +def _compile_jar_set_to_none_impl(ctx): + f = _make_file(ctx) + return JavaInfo(output_jar = f, compile_jar = None) + +bad_deps = rule(_deps_impl) +bad_runtime_deps = rule(_runtime_deps_impl) +bad_exports = rule(_exports_impl) +bad_libs = rule(_nativelibs_impl) +compile_jar_not_set = rule(_compile_jar_not_set_impl) +compile_jar_set_to_none = rule(_compile_jar_set_to_none_impl) diff --git a/test/java/testutil/rules/custom_java_info_rule.bzl b/test/java/testutil/rules/custom_java_info_rule.bzl new file mode 100644 index 00000000..e9a47918 --- /dev/null +++ b/test/java/testutil/rules/custom_java_info_rule.bzl @@ -0,0 +1,95 @@ +"""Helper rule for creating JavaInfo instances""" + +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_info.bzl", "JavaInfo") +load("//java/common:java_plugin_info.bzl", "JavaPluginInfo") +load("//java/common:java_semantics.bzl", "semantics") + +def _impl(ctx): + if ctx.attr.compile_jar and (ctx.attr.use_ijar or ctx.attr.stamp_jar): + fail("Cannot set use_ijar/stamp_jar if compile_jar is set") + if ctx.attr.use_ijar and ctx.attr.stamp_jar: + fail("only one of use_ijar or stamp_jar may be set") + ctx.actions.write(ctx.outputs.output_jar, "JavaInfo API Test", is_executable = False) + dp = [dep[JavaInfo] for dep in ctx.attr.dep] + dp_runtime = [dep[JavaInfo] for dep in ctx.attr.dep_runtime] + dp_exports = [dep[java_common.provider] for dep in ctx.attr.dep_exports] + dp_exported_plugins = [dep[JavaPluginInfo] for dep in ctx.attr.dep_exported_plugins] + source_jar = java_common.pack_sources( + ctx.actions, + output_source_jar = ctx.actions.declare_file(ctx.outputs.output_jar.basename[:-4] + "-src.jar", sibling = ctx.outputs.output_jar), + sources = ctx.files.sources, + source_jars = ctx.files.source_jars, + java_toolchain = semantics.find_java_toolchain(ctx), + ) if ctx.attr.pack_sources else ( + ctx.files.source_jars[0] if ctx.files.source_jars else None + ) + dp_libs = [dep[CcInfo] for dep in ctx.attr.cc_dep] + if ctx.attr.compile_jar: + compile_jar = ctx.file.compile_jar + elif ctx.attr.use_ijar: + compile_jar = java_common.run_ijar( + ctx.actions, + jar = ctx.outputs.output_jar, + java_toolchain = semantics.find_java_toolchain(ctx), + ) + elif ctx.attr.stamp_jar: + compile_jar = java_common.stamp_jar( + ctx.actions, + jar = ctx.outputs.output_jar, + target_label = ctx.label, + java_toolchain = semantics.find_java_toolchain(ctx), + ) + else: + compile_jar = ctx.outputs.output_jar + + return [ + JavaInfo( + output_jar = ctx.outputs.output_jar, + compile_jar = compile_jar, + source_jar = source_jar, + deps = dp, + runtime_deps = dp_runtime, + exports = dp_exports, + exported_plugins = dp_exported_plugins, + native_libraries = dp_libs, + neverlink = ctx.attr.neverlink, + jdeps = ctx.file.jdeps, + compile_jdeps = ctx.file.compile_jdeps, + generated_class_jar = ctx.file.generated_class_jar, + generated_source_jar = ctx.file.generated_source_jar, + native_headers_jar = ctx.file.native_headers_jar, + manifest_proto = ctx.file.manifest_proto, + add_exports = ctx.attr.add_exports, + add_opens = ctx.attr.add_opens, + ), + ] + +custom_java_info_rule = rule( + _impl, + attrs = { + "output_jar": attr.output(mandatory = True), + "source_jars": attr.label_list(allow_files = [".jar"]), + "sources": attr.label_list(allow_files = [".java"]), + "dep": attr.label_list(), + "dep_runtime": attr.label_list(), + "dep_exports": attr.label_list(), + "dep_exported_plugins": attr.label_list(), + "cc_dep": attr.label_list(), + "jdeps": attr.label(allow_single_file = True), + "compile_jdeps": attr.label(allow_single_file = True), + "generated_class_jar": attr.label(allow_single_file = True), + "generated_source_jar": attr.label(allow_single_file = True), + "native_headers_jar": attr.label(allow_single_file = True), + "manifest_proto": attr.label(allow_single_file = True), + "use_ijar": attr.bool(default = False), + "neverlink": attr.bool(default = False), + "pack_sources": attr.bool(default = False), + "stamp_jar": attr.bool(default = False), + "compile_jar": attr.label(allow_single_file = True), + "add_exports": attr.string_list(), + "add_opens": attr.string_list(), + }, + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], +) diff --git a/test/java/testutil/rules/custom_library.bzl b/test/java/testutil/rules/custom_library.bzl new file mode 100644 index 00000000..5ef3ca03 --- /dev/null +++ b/test/java/testutil/rules/custom_library.bzl @@ -0,0 +1,47 @@ +"""Helper rule for testing compilation with default parameter values""" + +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_info.bzl", "JavaInfo") +load("//java/common:java_plugin_info.bzl", "JavaPluginInfo") +load("//java/common:java_semantics.bzl", "semantics") + +def _custom_library_impl(ctx): + output_jar = ctx.actions.declare_file("lib" + ctx.label.name + ".jar") + deps = [dep[JavaInfo] for dep in ctx.attr.deps] + runtime_deps = [dep[JavaInfo] for dep in ctx.attr.runtime_deps] + native_libraries = [dep[CcInfo] for dep in ctx.attr.ccdeps] + compilation_provider = java_common.compile( + ctx, + source_files = ctx.files.srcs, + source_jars = ctx.files.source_jars, + output = output_jar, + neverlink = ctx.attr.neverlink, + deps = deps, + runtime_deps = runtime_deps, + exports = [e[JavaInfo] for e in ctx.attr.exports], + plugins = [p[JavaPluginInfo] for p in ctx.attr.plugins], + javac_opts = ctx.attr.javac_opts, + java_toolchain = semantics.find_java_toolchain(ctx), + enable_annotation_processing = ctx.attr.enable_annotation_processing, + native_libraries = native_libraries, + ) + return [DefaultInfo(files = depset([output_jar])), compilation_provider] + +custom_library = rule( + _custom_library_impl, + attrs = { + "srcs": attr.label_list(allow_files = [".java"]), + "source_jars": attr.label_list(allow_files = [".jar"]), + "deps": attr.label_list(), + "runtime_deps": attr.label_list(), + "ccdeps": attr.label_list(providers = [CcInfo]), + "exports": attr.label_list(), + "plugins": attr.label_list(), + "javac_opts": attr.string_list(), + "neverlink": attr.bool(), + "enable_annotation_processing": attr.bool(default = True), + }, + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], + fragments = ["java"], +) diff --git a/test/java/testutil/rules/custom_library_extended_compile_jdeps.bzl b/test/java/testutil/rules/custom_library_extended_compile_jdeps.bzl new file mode 100644 index 00000000..6cc8284a --- /dev/null +++ b/test/java/testutil/rules/custom_library_extended_compile_jdeps.bzl @@ -0,0 +1,49 @@ +"""Helper rule to test extending compile time jdeps""" + +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_info.bzl", "JavaInfo") +load("//java/common:java_semantics.bzl", "semantics") + +CompileJdepsInfo = provider("Provider to testing compile jdeps", fields = ["before", "after"]) + +def _compile_time_jdeps(info): + return depset([outputs.compile_jdeps for outputs in info.java_outputs if outputs.compile_jdeps != None]) + +def _impl(ctx): + output_jar = ctx.actions.declare_file("lib" + ctx.label.name + ".jar") + info = java_common.compile( + ctx, + source_files = ctx.files.srcs, + output = output_jar, + deps = [d[JavaInfo] for d in ctx.attr.deps], + exports = [e[JavaInfo] for e in ctx.attr.exports], + java_toolchain = semantics.find_java_toolchain(ctx), + ) + jdeps_info = JavaInfo( + output_jar = output_jar, + compile_jar = None, + compile_jdeps = ctx.file.extra_jdeps, + ) + extra_info = java_common.merge([info, jdeps_info]) + return [ + extra_info, + CompileJdepsInfo( + before = _compile_time_jdeps(info), + after = _compile_time_jdeps(extra_info), + ), + ] + +custom_library_extended_jdeps = rule( + implementation = _impl, + outputs = { + "my_output": "lib%{name}.jar", + }, + attrs = { + "srcs": attr.label_list(allow_files = [".java"]), + "extra_jdeps": attr.label(allow_single_file = True), + "deps": attr.label_list(providers = [JavaInfo]), + "exports": attr.label_list(providers = [JavaInfo]), + }, + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], + fragments = ["java"], +) diff --git a/test/java/testutil/rules/custom_library_with_additional_inputs.bzl b/test/java/testutil/rules/custom_library_with_additional_inputs.bzl new file mode 100644 index 00000000..fa29429e --- /dev/null +++ b/test/java/testutil/rules/custom_library_with_additional_inputs.bzl @@ -0,0 +1,29 @@ +"""Custom rule to test java_common.compile() with additional inputs and outputs""" + +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_semantics.bzl", "semantics") + +def _impl(ctx): + output_jar = ctx.actions.declare_file("lib" + ctx.label.name + ".jar") + java_common.compile( + ctx, + source_jars = ctx.files.srcs, + output = output_jar, + annotation_processor_additional_inputs = ctx.files.additional_inputs, + annotation_processor_additional_outputs = [ctx.outputs.additional_output], + java_toolchain = semantics.find_java_toolchain(ctx), + ) + return [DefaultInfo(files = depset([output_jar]))] + +custom_library_with_additional_inputs = rule( + implementation = _impl, + outputs = { + "additional_output": "%{name}_additional_output", + }, + attrs = { + "srcs": attr.label_list(allow_files = [".jar"]), + "additional_inputs": attr.label_list(allow_files = [".bin"]), + }, + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], + fragments = ["java"], +) diff --git a/test/java/testutil/rules/custom_library_with_bootclasspath.bzl b/test/java/testutil/rules/custom_library_with_bootclasspath.bzl new file mode 100644 index 00000000..69289da2 --- /dev/null +++ b/test/java/testutil/rules/custom_library_with_bootclasspath.bzl @@ -0,0 +1,46 @@ +"""Helper rule to test custom bootclasspaths in java_common.compile()""" + +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_semantics.bzl", "semantics") + +def _bootclasspath(ctx): + return [java_common.BootClassPathInfo(bootclasspath = ctx.files.bootclasspath, system = ctx.files.system)] + +custom_bootclasspath = rule( + implementation = _bootclasspath, + attrs = { + "bootclasspath": attr.label_list(allow_files = True), + "system": attr.label_list(allow_files = True), + }, +) + +def _impl(ctx): + output_jar = ctx.actions.declare_file("lib" + ctx.label.name + ".jar") + compilation_provider = java_common.compile( + ctx, + source_files = ctx.files.srcs, + output = output_jar, + deps = [], + sourcepath = ctx.files.sourcepath, + strict_deps = "ERROR", + java_toolchain = semantics.find_java_toolchain(ctx), + bootclasspath = ctx.attr.bootclasspath[java_common.BootClassPathInfo], + ) + return [ + DefaultInfo(files = depset([output_jar])), + compilation_provider, + ] + +custom_library_with_bootclasspath = rule( + implementation = _impl, + outputs = { + "my_output": "lib%{name}.jar", + }, + attrs = { + "srcs": attr.label_list(allow_files = [".java"]), + "sourcepath": attr.label_list(allow_files = [".jar"]), + "bootclasspath": attr.label(), + }, + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], + fragments = ["java"], +) diff --git a/test/java/testutil/rules/custom_library_with_custom_output_source_jar.bzl b/test/java/testutil/rules/custom_library_with_custom_output_source_jar.bzl new file mode 100644 index 00000000..531cb91b --- /dev/null +++ b/test/java/testutil/rules/custom_library_with_custom_output_source_jar.bzl @@ -0,0 +1,30 @@ +"""Custom rule to test java_common.compile() with a custom output source jar""" + +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_semantics.bzl", "semantics") + +def _impl(ctx): + output_jar = ctx.actions.declare_file("lib" + ctx.label.name + ".jar") + output_source_jar = ctx.actions.declare_file("lib" + ctx.label.name + "-mysrc.jar") + compilation_provider = java_common.compile( + ctx, + source_jars = ctx.files.srcs, + output = output_jar, + output_source_jar = output_source_jar, + java_toolchain = semantics.find_java_toolchain(ctx), + ) + return [ + DefaultInfo( + files = depset([output_source_jar]), + ), + compilation_provider, + ] + +custom_library_with_custom_output_source_jar = rule( + implementation = _impl, + attrs = { + "srcs": attr.label_list(allow_files = [".jar"]), + }, + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], + fragments = ["java"], +) diff --git a/test/java/testutil/rules/custom_library_with_exports.bzl b/test/java/testutil/rules/custom_library_with_exports.bzl new file mode 100644 index 00000000..0c180076 --- /dev/null +++ b/test/java/testutil/rules/custom_library_with_exports.bzl @@ -0,0 +1,29 @@ +"""Helper rule for testing compilation with `exports`""" + +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_semantics.bzl", "semantics") + +def _custom_library_with_exports_impl(ctx): + output_name = ( + ctx.label.name + "/" + ctx.attr.output_name + ".jar" + ) if ctx.attr.output_name else "lib" + ctx.label.name + ".jar" + output_jar = ctx.actions.declare_file(output_name) + compilation_provider = java_common.compile( + ctx, + source_files = ctx.files.srcs, + exports = [export[java_common.provider] for export in ctx.attr.exports], + output = output_jar, + java_toolchain = semantics.find_java_toolchain(ctx), + ) + return [DefaultInfo(files = depset([output_jar])), compilation_provider] + +custom_library_with_exports = rule( + _custom_library_with_exports_impl, + attrs = { + "srcs": attr.label_list(allow_files = [".java"]), + "exports": attr.label_list(), + "output_name": attr.string(), + }, + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], + fragments = ["java"], +) diff --git a/test/java/testutil/rules/custom_library_with_named_outputs.bzl b/test/java/testutil/rules/custom_library_with_named_outputs.bzl new file mode 100644 index 00000000..ec0a412b --- /dev/null +++ b/test/java/testutil/rules/custom_library_with_named_outputs.bzl @@ -0,0 +1,40 @@ +"""Custom rule to test that source jar names are derived from the output jar""" + +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_semantics.bzl", "semantics") + +def _impl(ctx): + output_jar = ctx.actions.declare_file(ctx.attr.name + "/amazing.jar") + other_output_jar = ctx.actions.declare_file(ctx.attr.name + "/wonderful.jar") + deps = [dep[java_common.provider] for dep in ctx.attr.deps] + compilation_provider = java_common.compile( + ctx, + source_files = ctx.files.srcs, + output = output_jar, + deps = deps, + java_toolchain = semantics.find_java_toolchain(ctx), + ) + other_compilation_provider = java_common.compile( + ctx, + source_files = ctx.files.srcs, + output = other_output_jar, + deps = deps, + java_toolchain = semantics.find_java_toolchain(ctx), + ) + result_provider = java_common.merge([compilation_provider, other_compilation_provider]) + return [ + DefaultInfo( + files = depset([output_jar]), + ), + result_provider, + ] + +custom_library_with_named_outputs = rule( + implementation = _impl, + attrs = { + "srcs": attr.label_list(allow_files = [".java"]), + "deps": attr.label_list(), + }, + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], + fragments = ["java"], +) diff --git a/test/java/testutil/rules/custom_library_with_sourcepaths.bzl b/test/java/testutil/rules/custom_library_with_sourcepaths.bzl new file mode 100644 index 00000000..05768c4d --- /dev/null +++ b/test/java/testutil/rules/custom_library_with_sourcepaths.bzl @@ -0,0 +1,25 @@ +"""Helper rule for testing compilation with `sourcepath`s""" + +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_semantics.bzl", "semantics") + +def _custom_library_with_sourcepaths_impl(ctx): + output_jar = ctx.actions.declare_file("lib" + ctx.label.name + ".jar") + compilation_provider = java_common.compile( + ctx, + source_files = ctx.files.srcs, + output = output_jar, + sourcepath = ctx.files.sourcepath, + java_toolchain = semantics.find_java_toolchain(ctx), + ) + return [DefaultInfo(files = depset([output_jar])), compilation_provider] + +custom_library_with_sourcepaths = rule( + _custom_library_with_sourcepaths_impl, + attrs = { + "srcs": attr.label_list(allow_files = [".java"]), + "sourcepath": attr.label_list(allow_files = [".jar"]), + }, + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], + fragments = ["java"], +) diff --git a/test/java/testutil/rules/custom_library_with_strict_deps.bzl b/test/java/testutil/rules/custom_library_with_strict_deps.bzl new file mode 100644 index 00000000..d2c931e4 --- /dev/null +++ b/test/java/testutil/rules/custom_library_with_strict_deps.bzl @@ -0,0 +1,23 @@ +"""Helper rule for testing compilation with default parameter values""" + +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_semantics.bzl", "semantics") + +def _custom_library_impl(ctx): + output_jar = ctx.actions.declare_file("lib" + ctx.label.name + ".jar") + compilation_provider = java_common.compile( + ctx, + output = output_jar, + strict_deps = ctx.attr.strict_deps, + java_toolchain = semantics.find_java_toolchain(ctx), + ) + return [DefaultInfo(files = depset([output_jar])), compilation_provider] + +custom_library_with_strict_deps = rule( + _custom_library_impl, + attrs = { + "strict_deps": attr.string(), + }, + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], + fragments = ["java"], +) diff --git a/test/java/testutil/rules/custom_library_with_strict_java_deps_provider.bzl b/test/java/testutil/rules/custom_library_with_strict_java_deps_provider.bzl new file mode 100644 index 00000000..3a0f66a7 --- /dev/null +++ b/test/java/testutil/rules/custom_library_with_strict_java_deps_provider.bzl @@ -0,0 +1,15 @@ +"""Custom libraty that reads --strict_java_deps and returns it from a provider.""" + +StrictJavaDepsInfo = provider( + doc = "Provides args.strict_java_deps for testing", + fields = ["strict_java_deps"], +) + +def _impl(ctx): + return [StrictJavaDepsInfo(strict_java_deps = ctx.fragments.java.strict_java_deps)] + +custom_library_with_strict_java_deps_provider = rule( + implementation = _impl, + attrs = {}, + fragments = ["java"], +) diff --git a/test/java/testutil/rules/custom_library_with_wrong_java_toolchain_type.bzl b/test/java/testutil/rules/custom_library_with_wrong_java_toolchain_type.bzl new file mode 100644 index 00000000..73b4a7f9 --- /dev/null +++ b/test/java/testutil/rules/custom_library_with_wrong_java_toolchain_type.bzl @@ -0,0 +1,24 @@ +"""Custom rule to test java_common.compile(java_toolchain = ...) expects JavaToolchainInfo""" + +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_semantics.bzl", "semantics") + +def _impl(ctx): + output_jar = ctx.actions.declare_file("lib" + ctx.label.name + ".jar") + return java_common.compile( + ctx, + source_files = ctx.files.srcs, + output = output_jar, + java_toolchain = ctx.attr._java_toolchain[platform_common.ToolchainInfo], + ) + +custom_library_with_wrong_java_toolchain_type = rule( + implementation = _impl, + attrs = { + "srcs": attr.label_list(allow_files = [".java"]), + "deps": attr.label_list(), + "_java_toolchain": attr.label(default = semantics.JAVA_TOOLCHAIN_LABEL), + }, + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], + fragments = ["java"], +) diff --git a/test/java/testutil/rules/custom_library_with_wrong_plugins_type.bzl b/test/java/testutil/rules/custom_library_with_wrong_plugins_type.bzl new file mode 100644 index 00000000..dc58cee1 --- /dev/null +++ b/test/java/testutil/rules/custom_library_with_wrong_plugins_type.bzl @@ -0,0 +1,25 @@ +"""Custom rule to test java_common.compile(plugins = ...) expects JavaPluginInfo""" + +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_info.bzl", "JavaInfo") +load("//java/common:java_semantics.bzl", "semantics") + +def _impl(ctx): + output_jar = ctx.actions.declare_file("lib" + ctx.label.name + ".jar") + return java_common.compile( + ctx, + source_files = ctx.files.srcs, + plugins = [p[JavaInfo] for p in ctx.attr.deps], + output = output_jar, + java_toolchain = semantics.find_java_toolchain(ctx), + ) + +custom_library_with_wrong_plugins_type = rule( + implementation = _impl, + attrs = { + "srcs": attr.label_list(allow_files = [".java"]), + "deps": attr.label_list(), + }, + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], + fragments = ["java"], +) diff --git a/test/java/testutil/rules/custom_plugin.bzl b/test/java/testutil/rules/custom_plugin.bzl new file mode 100644 index 00000000..a9afc66d --- /dev/null +++ b/test/java/testutil/rules/custom_plugin.bzl @@ -0,0 +1,34 @@ +"""Custom rule to test the JavaPluginInfo provider""" + +load("//java/common:java_info.bzl", "JavaInfo") +load( + "//java/common:java_plugin_info.bzl", + "JavaPluginInfo", +) + +def _impl(ctx): + output_jar = ctx.actions.declare_file(ctx.label.name + "/lib.jar") + ctx.actions.write(output_jar, "") + dep = JavaInfo( + output_jar = output_jar, + compile_jar = None, + deps = [d[JavaInfo] for d in ctx.attr.deps], + ) + data = depset(ctx.files.data) if ctx.attr.data_as_depset else ctx.files.data + return [JavaPluginInfo( + runtime_deps = [dep], + processor_class = ctx.attr.processor_class, + data = data, + generates_api = ctx.attr.generates_api, + )] + +custom_plugin = rule( + implementation = _impl, + attrs = { + "deps": attr.label_list(), + "processor_class": attr.string(), + "data": attr.label_list(allow_files = True), + "generates_api": attr.bool(default = False), + "data_as_depset": attr.bool(default = False), + }, +) diff --git a/test/java/testutil/rules/forward_java_info.bzl b/test/java/testutil/rules/forward_java_info.bzl new file mode 100644 index 00000000..6e05bfe0 --- /dev/null +++ b/test/java/testutil/rules/forward_java_info.bzl @@ -0,0 +1,9 @@ +"""Custom rule to forward a JavaInfo""" + +load("//java/common:java_info.bzl", "JavaInfo") + +def _impl(ctx): + dep_params = ctx.attr.dep[JavaInfo] + return [dep_params] + +java_info_forwarding_rule = rule(_impl, attrs = {"dep": attr.label()}) diff --git a/test/java/testutil/rules/forward_java_runtime_info.bzl b/test/java/testutil/rules/forward_java_runtime_info.bzl new file mode 100644 index 00000000..217081fe --- /dev/null +++ b/test/java/testutil/rules/forward_java_runtime_info.bzl @@ -0,0 +1,13 @@ +"""Helper rule to test java_runtime and JavaRuntimeInfo""" + +load("//java/common:java_common.bzl", "java_common") + +def _impl(ctx): + return ctx.attr.java_runtime[java_common.JavaRuntimeInfo] + +java_runtime_info_forwarding_rule = rule( + _impl, + attrs = { + "java_runtime": attr.label(), + }, +) diff --git a/test/java/testutil/rules/java_info_merge.bzl b/test/java/testutil/rules/java_info_merge.bzl new file mode 100644 index 00000000..e31201d9 --- /dev/null +++ b/test/java/testutil/rules/java_info_merge.bzl @@ -0,0 +1,17 @@ +"""Helper rule for testing java_common.merge""" + +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_info.bzl", "JavaInfo") + +def _impl(ctx): + java_infos = [dep[JavaInfo] for dep in ctx.attr.deps] + merged_info = java_common.merge(java_infos) + return [merged_info] + +java_info_merge_rule = rule( + implementation = _impl, + attrs = { + "deps": attr.label_list(providers = [JavaInfo]), + }, + provides = [JavaInfo], +) diff --git a/test/java/testutil/rules/private_api_usage.bzl b/test/java/testutil/rules/private_api_usage.bzl new file mode 100644 index 00000000..9ceaf826 --- /dev/null +++ b/test/java/testutil/rules/private_api_usage.bzl @@ -0,0 +1,57 @@ +"""Helper rules to test private API usage""" + +load("//java/common:java_common.bzl", "java_common") +load("//java/common:java_info.bzl", "JavaInfo") +load("//java/common:java_semantics.bzl", "semantics") + +_PRIVATE_ATTR_ATTRS = { + "private_attr_name": attr.string(mandatory = True), +} + +def _private_compile_api_impl(ctx): + out = ctx.actions.declare_file(ctx.label.name + ".jar") + java_common.compile( + ctx = ctx, + output = out, + java_toolchain = semantics.find_java_toolchain(ctx), + **{ctx.attr.private_attr_name: "does_not_matter"} + ) + return [] + +private_compile_api_usage = rule( + _private_compile_api_impl, + attrs = _PRIVATE_ATTR_ATTRS, + fragments = ["java"], + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], +) + +def _private_merge_api_impl(ctx): + out = ctx.actions.declare_file(ctx.label.name + ".jar") + info = JavaInfo(output_jar = out, compile_jar = out) + java_common.merge( + providers = [info], + **{ctx.attr.private_attr_name: "does_not_matter"} + ) + return [] + +private_merge_api_usage = rule( + _private_merge_api_impl, + attrs = _PRIVATE_ATTR_ATTRS, + fragments = ["java"], +) + +def _private_run_ijar_api_impl(ctx): + java_common.run_ijar( + ctx.actions, + java_toolchain = semantics.find_java_toolchain(ctx), + jar = ctx.actions.declare_file(ctx.label.name + "_ijar.jar"), + **{ctx.attr.private_attr_name: "does_not_matter"} + ) + return [] + +private_run_ijar_api_usage = rule( + _private_run_ijar_api_impl, + attrs = _PRIVATE_ATTR_ATTRS, + fragments = ["java"], + toolchains = [semantics.JAVA_TOOLCHAIN_TYPE], +) diff --git a/test/java/testutil/rules/template_var_info_rule.bzl b/test/java/testutil/rules/template_var_info_rule.bzl new file mode 100644 index 00000000..ebdc86cc --- /dev/null +++ b/test/java/testutil/rules/template_var_info_rule.bzl @@ -0,0 +1,13 @@ +"""Test rule to provide TemplateVariableInfo""" + +def _template_var_info_rule_impl(ctx): + return [ + platform_common.TemplateVariableInfo(ctx.attr.vars), + ] + +template_var_info_rule = rule( + _template_var_info_rule_impl, + attrs = { + "vars": attr.string_dict(default = {}), + }, +) diff --git a/test/java/testutil/rules/wrap_java_info.bzl b/test/java/testutil/rules/wrap_java_info.bzl new file mode 100644 index 00000000..ba78d500 --- /dev/null +++ b/test/java/testutil/rules/wrap_java_info.bzl @@ -0,0 +1,14 @@ +"""Custom rule to test wrapping of the JavaInfo provider""" + +load("//java/common:java_info.bzl", "JavaInfo") + +JavaInfoWrappingInfo = provider( + "Simple provider to wrap a JavaInfo", + fields = ["p"], +) + +def _impl(ctx): + dep_params = ctx.attr.dep[JavaInfo] + return [JavaInfoWrappingInfo(p = dep_params)] + +java_info_wrapping_rule = rule(_impl, attrs = {"dep": attr.label()}) diff --git a/test/java/toolchains/BUILD b/test/java/toolchains/BUILD new file mode 100644 index 00000000..1d7af21f --- /dev/null +++ b/test/java/toolchains/BUILD @@ -0,0 +1,10 @@ +load(":java_runtime_tests.bzl", "java_runtime_tests") +load(":java_toolchain_tests.bzl", "java_toolchain_tests") + +package(default_applicable_licenses = ["@rules_java//:license"]) + +java_runtime_tests(name = "java_runtime_tests") + +java_toolchain_tests(name = "java_toolchain_tests") + +exports_files(["java_runtime.src"]) diff --git a/test/java/toolchains/java_runtime_tests.bzl b/test/java/toolchains/java_runtime_tests.bzl new file mode 100644 index 00000000..94d067ea --- /dev/null +++ b/test/java/toolchains/java_runtime_tests.bzl @@ -0,0 +1,426 @@ +"""Tests for the java_runtime rule""" + +load("@rules_cc//cc:defs.bzl", "cc_import") +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching", "subjects") +load("@rules_testing//lib:util.bzl", "util") +load("//java/toolchains:java_runtime.bzl", "java_runtime") +load("//test/java/testutil:java_runtime_info_subject.bzl", "java_runtime_info_subject") +load("//test/java/testutil:mock_java_toolchain.bzl", "mock_java_runtime_toolchain") +load("//test/java/testutil:rules/forward_java_runtime_info.bzl", "java_runtime_info_forwarding_rule") +load("//toolchains:java_toolchain_alias.bzl", "java_runtime_alias") + +def _test_with_absolute_java_home(name): + util.helper_target( + mock_java_runtime_toolchain, + name = name + "/java_runtime_toolchain", + srcs = [], + java_home = "/foo/bar", + ) + util.helper_target( + java_runtime_alias, + name = name + "/alias", + ) + util.helper_target( + java_runtime_info_forwarding_rule, + name = name + "/r", + java_runtime = name + "/alias", + ) + + analysis_test( + name = name, + impl = _test_with_absolute_java_home_impl, + target = name + "/r", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/java_runtime_toolchain")], + }, + ) + +def _test_with_absolute_java_home_impl(env, target): + assert_info = java_runtime_info_subject.from_target(env, target) + + assert_info.java_home().equals("/foo/bar") + assert_info.java_home_runfiles_path().equals("/foo/bar") + assert_info.java_executable_exec_path().starts_with("/foo/bar/bin/java") + assert_info.java_executable_runfiles_path().starts_with("/foo/bar/bin/java") + +def _test_with_hermetic_java_home(name): + util.helper_target( + mock_java_runtime_toolchain, + name = name + "/java_runtime_toolchain", + srcs = [], + java_home = "foo/bar", + ) + util.helper_target( + java_runtime_alias, + name = name + "/alias", + ) + util.helper_target( + java_runtime_info_forwarding_rule, + name = name + "/r", + java_runtime = name + "/alias", + ) + + analysis_test( + name = name, + impl = _test_with_hermetic_java_home_impl, + target = name + "/r", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/java_runtime_toolchain")], + }, + ) + +def _test_with_hermetic_java_home_impl(env, target): + assert_info = java_runtime_info_subject.from_target(env, target) + + assert_info.java_home().equals("{package}/foo/bar") + assert_info.java_home_runfiles_path().equals("{package}/foo/bar") + assert_info.java_executable_exec_path().starts_with("{package}/foo/bar/bin/java") + assert_info.java_executable_runfiles_path().starts_with("{package}/foo/bar/bin/java") + +def _test_with_generated_java_executable(name): + util.helper_target( + native.genrule, + name = name + "/gen", + cmd = "", + outs = ["foo/bar/bin/java"], + output_to_bindir = True, + ) + util.helper_target( + mock_java_runtime_toolchain, + name = name + "/java_runtime_toolchain", + srcs = [], + java = "foo/bar/bin/java", + ) + util.helper_target( + java_runtime_alias, + name = name + "/alias", + ) + util.helper_target( + java_runtime_info_forwarding_rule, + name = name + "/r", + java_runtime = name + "/alias", + ) + + analysis_test( + name = name, + impl = _test_with_generated_java_executable_impl, + target = name + "/r", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/java_runtime_toolchain")], + }, + ) + +def _test_with_generated_java_executable_impl(env, target): + assert_info = java_runtime_info_subject.from_target(env, target) + + assert_info.java_home().equals("{bindir}/{package}/foo/bar") + assert_info.java_home_runfiles_path().equals("{package}/foo/bar") + assert_info.java_executable_exec_path().starts_with("{bindir}/{package}/foo/bar/bin/java") + assert_info.java_executable_runfiles_path().starts_with("{package}/foo/bar/bin/java") + +def _test_runtime_alias(name): + util.helper_target( + java_runtime_alias, + name = name + "/alias", + ) + + analysis_test( + name = name, + impl = _test_runtime_alias_impl, + target = name + "/alias", + ) + +def _test_runtime_alias_impl(env, target): + env.expect.that_target(target).has_provider(platform_common.ToolchainInfo) + env.expect.that_target(target).has_provider(platform_common.TemplateVariableInfo) + +def _test_java_runtime_simple(name): + util.helper_target( + java_runtime, + name = name + "/jvm-foo", + srcs = [ + "foo/a", + "foo/b", + ], + java_home = "foo", + ) + + analysis_test( + name = name, + impl = _test_java_runtime_simple_impl, + target = name + "/jvm-foo", + ) + +def _test_java_runtime_simple_impl(env, target): + java_runtime_info_subject.from_target(env, target).files().contains_exactly([ + "{package}/foo/a", + "{package}/foo/b", + ]) + env.expect.that_target(target).data_runfiles().contains_exactly([ + "{workspace}/{package}/foo/a", + "{workspace}/{package}/foo/b", + ]) + +def _test_absolute_java_home_with_srcs(name): + util.helper_target( + java_runtime, + name = name + "/jvm", + srcs = ["dummy.txt"], + java_home = "/absolute/path", + ) + + analysis_test( + name = name, + impl = _test_absolute_java_home_with_srcs_impl, + target = name + "/jvm", + expect_failure = True, + ) + +def _test_absolute_java_home_with_srcs_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("'java_home' with an absolute path requires 'srcs' to be empty."), + ) + +def _test_absolute_java_home_with_java(name): + util.helper_target( + java_runtime, + name = name + "/jvm", + java = "bin/java", + java_home = "/absolute/path", + ) + + analysis_test( + name = name, + impl = _test_absolute_java_home_with_java_impl, + target = name + "/jvm", + expect_failure = True, + ) + +def _test_absolute_java_home_with_java_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("'java_home' with an absolute path requires 'java' to be empty."), + ) + +def _test_bin_java_path_name(name): + util.helper_target( + java_runtime, + name = name + "/jvm", + java = "java", + ) + + analysis_test( + name = name, + impl = _test_bin_java_path_name_impl, + target = name + "/jvm", + expect_failure = True, + ) + +def _test_bin_java_path_name_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("the path to 'java' must end in 'bin/java'."), + ) + +def _test_absolute_java_home(name): + util.helper_target( + java_runtime, + name = name + "/jvm", + java_home = "/absolute/path", + ) + + analysis_test( + name = name, + impl = _test_absolute_java_home_impl, + target = name + "/jvm", + ) + +def _test_absolute_java_home_impl(env, target): + java_runtime_info_subject.from_target(env, target).java_home().equals("/absolute/path") + +def _test_relative_java_home(name): + util.helper_target( + java_runtime, + name = name + "/jvm", + java_home = "b/c", + ) + + analysis_test( + name = name, + impl = _test_relative_java_home_impl, + target = name + "/jvm", + ) + +def _test_relative_java_home_impl(env, target): + java_runtime_info_subject.from_target(env, target).java_home().equals("{package}/b/c") + +def _test_java_home_with_invalid_make_variables(name): + util.helper_target( + java_runtime, + name = name + "/jvm", + java_home = "/opt/$(WTF)", + ) + + analysis_test( + name = name, + impl = _test_java_home_with_invalid_make_variables_impl, + target = name + "/jvm", + expect_failure = True, + ) + +def _test_java_home_with_invalid_make_variables_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("$(WTF) not defined"), + ) + +def _test_make_variables(name): + util.helper_target( + java_runtime, + name = name + "/jvm", + java_home = "/foo/bar", + ) + + analysis_test( + name = name, + impl = _test_make_variables_impl, + target = name + "/jvm", + ) + +def _test_make_variables_impl(env, target): + env.expect.that_target(target).provider( + platform_common.TemplateVariableInfo, + ).variables().contains_at_least({"JAVABASE": "/foo/bar"}) + env.expect.that_target(target).provider( + platform_common.TemplateVariableInfo, + ).variables().get("JAVA", factory = subjects.str).starts_with("/foo/bar/bin/java") + +def _test_no_srcs(name): + util.helper_target( + java_runtime, + name = name + "/jvm", + java_home = "/opt/jvm", + ) + + analysis_test( + name = name, + impl = _test_no_srcs_impl, + target = name + "/jvm", + ) + +def _test_no_srcs_impl(env, target): + assert_info = java_runtime_info_subject.from_target(env, target) + assert_info.java_home().equals("/opt/jvm") + assert_info.files().contains_exactly([]) + +def _test_java_home_generated(name): + util.helper_target( + native.genrule, + name = name + "/gen", + outs = ["generated_java_home/bin/java"], + cmd = "touch $@", + ) + util.helper_target( + java_runtime, + name = name + "/jvm", + java = "generated_java_home/bin/java", + java_home = "generated_java_home", + ) + + analysis_test( + name = name, + impl = _test_java_home_generated_impl, + target = name + "/jvm", + ) + +def _test_java_home_generated_impl(env, target): + java_runtime_info_subject.from_target(env, target).java_home().equals( + "{gendir}/{package}/generated_java_home", + ) + +def _test_hermetic_static_libs(name): + util.helper_target( + cc_import, + name = name + "/libs", + static_library = "libStatic.a", + ) + util.helper_target( + java_runtime, + name = name + "/jvm", + lib_modules = name + "/gen_lib_modules", + hermetic_srcs = [name + "/hermetic.properties"], + hermetic_static_libs = [name + "/libs"], + ) + + analysis_test( + name = name, + impl = _test_hermetic_static_libs_impl, + target = name + "/jvm", + ) + +def _test_hermetic_static_libs_impl(env, target): + cc_info = java_runtime_info_subject.from_target(env, target).hermetic_static_libs().singleton() + cc_info.linking_context().static_library_files().contains_exactly(["{package}/libStatic.a"]) + +def _test_implicit_lib_ct_sym(name): + util.helper_target( + java_runtime, + name = name + "/jvm", + srcs = [ + name + "/java", + name + "/jvm/implicit/lib/ct.sym", + ], + ) + analysis_test( + name = name, + impl = _test_implicit_lib_ct_sym_impl, + target = name + "/jvm", + ) + +def _test_implicit_lib_ct_sym_impl(env, target): + java_runtime_info_subject.from_target(env, target).lib_ct_sym().short_path_equals( + "{package}/{name}/implicit/lib/ct.sym", + ) + +def _test_explicit_lib_ct_sym(name): + util.helper_target( + java_runtime, + name = name + "/jvm", + srcs = [ + name + "/java", + name + "/jvm/implicit/lib/ct.sym", + ], + lib_ct_sym = name + "/jvm/explicit/lib/ct.sym", + ) + analysis_test( + name = name, + impl = _test_explicit_lib_ct_sym_impl, + target = name + "/jvm", + ) + +def _test_explicit_lib_ct_sym_impl(env, target): + java_runtime_info_subject.from_target(env, target).lib_ct_sym().short_path_equals( + "{package}/{name}/explicit/lib/ct.sym", + ) + +def java_runtime_tests(name): + test_suite( + name = name, + tests = [ + _test_with_absolute_java_home, + _test_with_hermetic_java_home, + _test_with_generated_java_executable, + _test_runtime_alias, + _test_java_runtime_simple, + _test_absolute_java_home_with_srcs, + _test_absolute_java_home_with_java, + _test_bin_java_path_name, + _test_absolute_java_home, + _test_relative_java_home, + _test_java_home_with_invalid_make_variables, + _test_make_variables, + _test_no_srcs, + _test_java_home_generated, + _test_hermetic_static_libs, + _test_implicit_lib_ct_sym, + _test_explicit_lib_ct_sym, + ], + ) diff --git a/test/java/toolchains/java_toolchain_tests.bzl b/test/java/toolchains/java_toolchain_tests.bzl new file mode 100644 index 00000000..793de8ea --- /dev/null +++ b/test/java/toolchains/java_toolchain_tests.bzl @@ -0,0 +1,776 @@ +"""Tests for the java_toolchain rule""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching", "subjects") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_binary.bzl", "java_binary") +load("//java:java_library.bzl", "java_library") +load("//java:java_plugin.bzl", "java_plugin") +load("//java/common:java_common.bzl", "java_common") +load("//test/java/testutil:java_info_subject.bzl", "java_info_subject") +load("//test/java/testutil:java_toolchain_info_subject.bzl", "java_toolchain_info_subject") +load("//test/java/testutil:javac_action_subject.bzl", "javac_action_subject") +load("//test/java/testutil:mock_java_toolchain.bzl", "mock_java_toolchain") +load("//toolchains:java_toolchain_alias.bzl", "java_toolchain_alias") + +def _test_javac_gets_options(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + javabuilder = name + "/JavaBuilder_deploy.jar", + header_compiler_direct = name + "/turbine_direct", + bootclasspath = [name + "/rt.jar"], + source_version = "6", + target_version = "6", + xlint = ["toto"], + javacopts = ["-Xmaxerrs 500"], + ) + util.helper_target( + java_library, + name = name + "/b", + srcs = ["b.java"], + ) + util.helper_target( + java_library, + name = name + "/a", + srcs = ["a.java"], + deps = [Label(name + "/b")], + ) + + analysis_test( + name = name, + impl = _test_javac_gets_options_impl, + targets = { + "a": name + "/a", + "b": name + "/b", + }, + config_settings = { + "//command_line_option:java_header_compilation": "true", + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_javac_gets_options_impl(env, targets): + assert_javac_action = javac_action_subject.of(env, targets.a, "{package}/lib{name}.jar") + assert_javac_action.javacopts().contains_at_least([ + "-source", + "6", + "-target", + "6", + "-Xlint:toto", + "-Xmaxerrs", + "500", + ]) + assert_javac_action.jar().contains_exactly(["{package}/{test_name}/JavaBuilder_deploy.jar"]) + assert_javac_action.inputs().contains("{package}/{test_name}/rt.jar") + + assert_javac_action.javacopts().not_contains("-g") + + assert_header_action = javac_action_subject.of(env, targets.b, "{package}/lib{name}-hjar.jar") + assert_header_action.argv().contains("{package}/{test_name}/turbine_direct") + +def _test_jacocorunner(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + jacocorunner = "myjacocorunner.jar", + ) + util.helper_target( + java_toolchain_alias, + name = name + "/alias", + ) + analysis_test( + name = name, + impl = _test_jacocorunner_impl, + target = name + "/alias", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_jacocorunner_impl(env, target): + assert_toolchain = java_toolchain_info_subject.from_target(env, target) + + assert_toolchain.jacocorunner().short_path_equals("{package}/myjacocorunner.jar") + +def _test_singlejar_get_command_line(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + ) + util.helper_target( + java_binary, + name = name + "/a", + srcs = ["a.java"], + ) + + analysis_test( + name = name, + impl = _test_singlejar_get_command_line_impl, + target = name + "/a", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + # This crashes in earlier Bazel versions where native rules handled deploy jars differently. + attr_values = {"tags": ["min_bazel_8"]}, + ) + +def _test_singlejar_get_command_line_impl(env, target): + assert_javac_action = javac_action_subject.of(env, target, "{package}/{name}_deploy.jar") + assert_javac_action.executable_file_name().equals(target.label.package + "/singlejar") + +def _test_genclass_get_command_line(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + genclass = name + "/GenClass_deploy.jar", + ) + util.helper_target( + java_library, + name = name + "/a", + srcs = ["a.java"], + javacopts = ["-processor NOSUCH"], + ) + + analysis_test( + name = name, + impl = _test_genclass_get_command_line_impl, + target = name + "/a", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_genclass_get_command_line_impl(env, target): + assert_javac_action = javac_action_subject.of(env, target, "{package}/lib{name}-gen.jar") + + assert_javac_action.jar().contains_exactly(["{package}/{test_name}/GenClass_deploy.jar"]) + +def _test_timezone_data_is_correct(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + timezone_data = "tzdata.jar", + ) + util.helper_target( + java_toolchain_alias, + name = name + "/alias", + ) + + analysis_test( + name = name, + impl = _test_timezone_data_is_correct_impl, + target = name + "/alias", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_timezone_data_is_correct_impl(env, target): + java_toolchain_info_subject.from_target(env, target).timezone_data().short_path_equals( + "{package}/tzdata.jar", + ) + +def _test_java_binary_uses_timezone_data(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + timezone_data = name + "/tzdata.jar", + ) + util.helper_target( + java_binary, + name = name + "/a", + srcs = ["a.java"], + ) + + analysis_test( + name = name, + impl = _test_java_binary_uses_timezone_data_impl, + target = name + "/a", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_java_binary_uses_timezone_data_impl(env, target): + assert_action = javac_action_subject.of(env, target, "{package}/{name}.jar") + assert_action.sources().contains("{package}/{test_name}/tzdata.jar") + assert_action.inputs().contains_predicate(matching.file_basename_equals("tzdata.jar")) + +def _test_ijar_get_command_line(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + ) + util.helper_target( + java_library, + name = name + "/a", + srcs = ["a.java"], + ) + + analysis_test( + name = name, + impl = _test_ijar_get_command_line_impl, + target = name + "/a", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + "//command_line_option:java_header_compilation": "false", + }, + ) + +def _test_ijar_get_command_line_impl(env, target): + compile_jar = java_info_subject.from_target(env, target).java_outputs().singleton().compile_jar().actual + env.expect.that_target(target).action_generating(compile_jar.short_path).argv().contains( + "{package}/ijar", + ) + +def _test_no_header_compiler_header_compilation_enabled_fails(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + header_compiler = None, + ) + util.helper_target( + java_library, + name = name + "/a", + srcs = ["a.java"], + ) + + analysis_test( + name = name, + impl = _test_no_header_compiler_header_compilation_enabled_fails_impl, + target = name + "/a", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + "//command_line_option:java_header_compilation": "true", + }, + expect_failure = True, + ) + +def _test_no_header_compiler_header_compilation_enabled_fails_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.contains("header compilation was requested but it is not supported by the " + + "current Java toolchain"), + ) + +def _test_no_header_compiler_direct_header_compilation_enabled_fails(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + header_compiler_direct = None, + ) + util.helper_target( + java_library, + name = name + "/a", + srcs = ["a.java"], + ) + + analysis_test( + name = name, + impl = _test_no_header_compiler_direct_header_compilation_enabled_fails_impl, + target = name + "/a", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + "//command_line_option:java_header_compilation": "true", + }, + expect_failure = True, + ) + +def _test_no_header_compiler_direct_header_compilation_enabled_fails_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.contains("header compilation was requested but it is not supported by the " + + "current Java toolchain"), + ) + +def _test_no_header_compiler_header_compilation_disabled_analyzes_successfully(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + header_compiler = None, + ) + util.helper_target( + java_library, + name = name + "/a", + srcs = ["a.java"], + ) + + analysis_test( + name = name, + impl = _test_no_header_compiler_header_compilation_disabled_analyzes_successfully_impl, + target = name + "/a", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + "//command_line_option:java_header_compilation": "false", + }, + ) + +def _test_no_header_compiler_header_compilation_disabled_analyzes_successfully_impl( + env, # @unused + target): # @unused + # Implicitly succeeds. + pass + +def _test_header_compiler_builtin_processors(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + header_compiler_builtin_processors = ["BuiltinProc1", "BuiltinProc2"], + ) + util.helper_target( + java_toolchain_alias, + name = name + "/alias", + ) + + analysis_test( + name = name, + impl = _test_header_compiler_builtin_processors_impl, + target = name + "/alias", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_header_compiler_builtin_processors_impl(env, target): + java_toolchain_info_subject.from_target(env, target).header_compiler_builtin_processors().contains_exactly([ + "BuiltinProc1", + "BuiltinProc2", + ]) + +def _test_reduced_classpath_incompatible_processors(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + reduced_classpath_incompatible_processors = ["IncompatibleProc1", "IncompatibleProc2"], + ) + util.helper_target( + java_toolchain_alias, + name = name + "/alias", + ) + + analysis_test( + name = name, + impl = _test_reduced_classpath_incompatible_processors_impl, + target = name + "/alias", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_reduced_classpath_incompatible_processors_impl(env, target): + java_toolchain_info_subject.from_target(env, target).reduced_classpath_incompatible_processors().contains_exactly([ + "IncompatibleProc1", + "IncompatibleProc2", + ]) + +def _test_location_expansion_in_jvm_opts(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + tools = [name + "/jsr305.jar", name + "/javac"], + jvm_opts = [ + "--patch-module=jdk.compiler=$(location " + name + "/javac)", + "--patch-module=java.xml.ws.annotation=$(location " + name + "/jsr305.jar)", + ], + javabuilder_jvm_opts = ["-Xshare:auto"], + ) + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["a.java"], + ) + + analysis_test( + name = name, + impl = _test_location_expansion_in_jvm_opts_impl, + target = name + "/lib", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_location_expansion_in_jvm_opts_impl(env, target): + assert_javac_action = env.expect.that_target(target).action_generating("{package}/lib{name}.jar") + assert_javac_action.argv().contains("--patch-module=jdk.compiler={package}/{test_name}/javac") + assert_javac_action.argv().contains("--patch-module=java.xml.ws.annotation={package}/{test_name}/jsr305.jar") + assert_javac_action.argv().contains("-Xshare:auto") + assert_javac_action.inputs().contains("{package}/{test_name}/jsr305.jar") + +def _test_location_expansion_with_multiple_artifacts_fails(name): + util.helper_target( + native.filegroup, + name = name + "/fg", + srcs = ["one", "two"], + ) + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + tools = [name + "/fg"], + javabuilder_jvm_opts = ["$(location " + name + "/fg)"], + ) + + analysis_test( + name = name, + impl = _test_location_expansion_with_multiple_artifacts_fails_impl, + target = name + "/toolchain_java", # the underlying java_toolchain + expect_failure = True, + ) + +def _test_location_expansion_with_multiple_artifacts_fails_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.contains("$(location) expression expands to more than one file"), + ) + +def _test_timezone_data_with_multiple_artifacts_fails(name): + util.helper_target( + native.filegroup, + name = name + "/fg", + srcs = ["one", "two"], + ) + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + timezone_data = name + "/fg", + ) + + analysis_test( + name = name, + impl = _test_timezone_data_with_multiple_artifacts_fails_impl, + target = name + "/toolchain_java", # the underlying java_toolchain + expect_failure = True, + ) + +def _test_timezone_data_with_multiple_artifacts_fails_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.contains("must produce a single file"), + ) + +def _test_java_compile_action_target_gets_javacopts_from_toolchain(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + source_version = "6", + target_version = "6", + xlint = ["toto"], + javacopts = ["-XDtoolchainJavacFlag"], + ) + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["a.java"], + ) + analysis_test( + name = name, + impl = _test_java_compile_action_target_gets_javacopts_from_toolchain_impl, + target = name + "/lib", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + "//command_line_option:javacopt": ["-XDcommandLineJavacFlag"], + "//command_line_option:host_javacopt": ["-XDhostCommandLineJavacFlag"], + }, + ) + +def _test_java_compile_action_target_gets_javacopts_from_toolchain_impl(env, target): + assert_javacopts = javac_action_subject.of(env, target, "{package}/lib{name}.jar").javacopts() + assert_javacopts.contains_exactly([ + "-source", + "6", + "-target", + "6", + "-Xlint:toto", + "-XDtoolchainJavacFlag", + "-XDcommandLineJavacFlag", + ]) + +def _test_java_compile_action_exec_gets_javacopts_from_toolchain(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + source_version = "6", + target_version = "6", + xlint = ["toto"], + javacopts = ["-XDtoolchainJavacFlag"], + ) + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["a.java"], + ) + util.helper_target( + util.force_exec_config, + name = name + "/exec_lib", + tools = [name + "/lib"], + ) + analysis_test( + name = name, + impl = _test_java_compile_action_exec_gets_javacopts_from_toolchain_impl, + target = name + "/exec_lib", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + "//command_line_option:javacopt": ["-XDcommandLineJavacFlag"], + "//command_line_option:host_javacopt": ["-XDhostCommandLineJavacFlag"], + }, + ) + +def _test_java_compile_action_exec_gets_javacopts_from_toolchain_impl(env, target): + lib = env.expect.that_target(target).attr("tools", factory = subjects.collection).actual[0] + assert_javacopts = javac_action_subject.of(env, lib, "{package}/lib{name}.jar").javacopts() + assert_javacopts.contains_exactly([ + "-source", + "6", + "-target", + "6", + "-Xlint:toto", + "-XDtoolchainJavacFlag", + "-XDhostCommandLineJavacFlag", + ]) + +def _test_java_compile_action_uses_tool_specific_jvm_opts(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + jvm_opts = ["-Xbase"], + javabuilder_jvm_opts = ["-DjavabuilderFlag=1"], + turbine_jvm_opts = ["-DturbineFlag=1"], + ) + util.helper_target( + java_plugin, + name = name + "/plugin", + processor_class = "Proc", + generates_api = True, + ) + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["a.java"], + plugins = [name + "/plugin"], + ) + analysis_test( + name = name, + impl = _test_java_compile_action_uses_tool_specific_jvm_opts_impl, + target = name + "/lib", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_java_compile_action_uses_tool_specific_jvm_opts_impl(env, target): + javac_action = javac_action_subject.of(env, target, "{package}/lib{name}.jar") + javac_action.argv().contains("-DjavabuilderFlag=1") + + header_action = env.expect.that_target(target).action_generating("{package}/lib{name}-hjar.jar") + header_action.argv().contains("-DturbineFlag=1") + +def _test_javabuilder_location_expansion_with_multiple_artifacts(name): + util.helper_target( + native.filegroup, + name = name + "/fg1", + srcs = ["a", "b"], + ) + util.helper_target( + native.filegroup, + name = name + "/fg2", + srcs = ["c", "d"], + ) + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + javabuilder_data = [name + "/fg1", name + "/fg2"], + javabuilder_jvm_opts = [ + "$(locations " + name + "/fg1)", + "$(locations " + name + "/fg2)", + ], + ) + util.helper_target( + java_toolchain_alias, + name = name + "/alias", + ) + + analysis_test( + name = name, + impl = _test_javabuilder_location_expansion_with_multiple_artifacts_impl, + target = name + "/alias", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_javabuilder_location_expansion_with_multiple_artifacts_impl(env, target): + assert_javabuilder = java_toolchain_info_subject.from_target(env, target).javabuilder() + assert_javabuilder.data().contains_exactly([ + "{package}/a", + "{package}/b", + "{package}/c", + "{package}/d", + ]).in_order() + assert_javabuilder.jvm_opts().contains_exactly([ + "{package}/a {package}/b", + "{package}/c {package}/d", + ]).in_order() + +def _no_toolchain_rule_impl(ctx): + java_common.pack_sources( + ctx.actions, + output_source_jar = "output_source_jar", + java_toolchain = "java_toolchain", + ) + +_no_toolchain_rule = rule( + implementation = _no_toolchain_rule_impl, +) + +def _test_java_common_without_toolchain_type_fails(name): + util.helper_target( + _no_toolchain_rule, + name = name + "/no_toolchain", + ) + analysis_test( + name = name, + impl = _test_java_common_without_toolchain_type_fails_impl, + target = name + "/no_toolchain", + expect_failure = True, + ) + +def _test_java_common_without_toolchain_type_fails_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("must declare *tools/jdk:toolchain_type' toolchain in order to use java_common"), + ) + +def _test_java_toolchain_flag_default(name): + util.helper_target( + java_toolchain_alias, + name = name + "/toolchain_alias", + ) + + analysis_test( + name = name, + impl = _test_java_toolchain_flag_default_impl, + target = name + "/toolchain_alias", + ) + +def _test_java_toolchain_flag_default_impl(env, target): + assert_toolchain = java_toolchain_info_subject.from_target(env, target) + assert_toolchain.label_str().matches( + matching.any( + matching.str_endswith("jdk:remote_toolchain"), + matching.str_endswith("jdk:toolchain"), + matching.str_endswith("jdk:toolchain_host"), + # buildifier: disable=canonical-repository + matching.str_startswith("@@//toolchains:toolchain_java"), + ), + ) + +def _test_java_toolchain_flag_set(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + ) + util.helper_target( + java_toolchain_alias, + name = name + "/toolchain_alias", + ) + + analysis_test( + name = name, + impl = _test_java_toolchain_flag_set_impl, + targets = { + "alias": name + "/toolchain_alias", + "toolchain": name + "/toolchain_java", # the underlying java_toolchain + }, + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_java_toolchain_flag_set_impl(env, targets): + assert_toolchain = java_toolchain_info_subject.from_target(env, targets.alias) + assert_toolchain.label().equals(targets.toolchain.label) + +def _test_default_javac_opts_depset(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + source_version = "6", + target_version = "6", + xlint = ["toto"], + javacopts = ["-Xmaxerrs 500"], + ) + util.helper_target( + java_toolchain_alias, + name = name + "/alias", + ) + + analysis_test( + name = name, + impl = _test_default_javac_opts_depset_impl, + target = name + "/alias", + attr_values = {"tags": ["min_bazel_8"]}, + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + ) + +def _test_default_javac_opts_depset_impl(env, target): + java_toolchain_info_subject.from_target(env, target).default_javacopts_depset().contains_exactly( + ["-source 6 -target 6 -Xlint:toto -Xmaxerrs 500"], + ) + +def _test_default_javac_opts(name): + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + source_version = "6", + target_version = "6", + ) + util.helper_target( + java_toolchain_alias, + name = name + "/alias", + ) + + analysis_test( + name = name, + impl = _test_default_javac_opts_impl, + target = name + "/alias", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + attr_values = {"tags": ["min_bazel_8"]}, + ) + +def _test_default_javac_opts_impl(env, target): + java_toolchain_info_subject.from_target(env, target).default_javacopts().contains_at_least([ + "-source", + "6", + "-target", + "6", + ]).in_order() + +def java_toolchain_tests(name): + test_suite( + name = name, + tests = [ + _test_jacocorunner, + _test_javac_gets_options, + _test_singlejar_get_command_line, + _test_genclass_get_command_line, + _test_timezone_data_is_correct, + _test_java_binary_uses_timezone_data, + _test_ijar_get_command_line, + _test_no_header_compiler_header_compilation_enabled_fails, + _test_no_header_compiler_direct_header_compilation_enabled_fails, + _test_no_header_compiler_header_compilation_disabled_analyzes_successfully, + _test_header_compiler_builtin_processors, + _test_reduced_classpath_incompatible_processors, + _test_location_expansion_in_jvm_opts, + _test_location_expansion_with_multiple_artifacts_fails, + _test_timezone_data_with_multiple_artifacts_fails, + _test_java_compile_action_target_gets_javacopts_from_toolchain, + _test_java_compile_action_exec_gets_javacopts_from_toolchain, + _test_java_compile_action_uses_tool_specific_jvm_opts, + _test_javabuilder_location_expansion_with_multiple_artifacts, + _test_java_common_without_toolchain_type_fails, + _test_java_toolchain_flag_default, + _test_java_toolchain_flag_set, + _test_default_javac_opts_depset, + _test_default_javac_opts, + ], + ) diff --git a/test/repo/.bazelrc b/test/repo/.bazelrc new file mode 100644 index 00000000..8d0ffca7 --- /dev/null +++ b/test/repo/.bazelrc @@ -0,0 +1,7 @@ +build:bzlmod --experimental_enable_bzlmod + +common --incompatible_disallow_empty_glob + +# Enable modern C++ features, for compiling java_tools from source +build --cxxopt=-std=c++17 +build --host_cxxopt=-std=c++17 diff --git a/test/repo/BUILD.bazel b/test/repo/BUILD.bazel index 1e71e527..a251c7b7 100644 --- a/test/repo/BUILD.bazel +++ b/test/repo/BUILD.bazel @@ -1,9 +1,13 @@ -load("@rules_java//java:java_binary.bzl", "java_binary") # copybara-use-repo-external-label -load("@rules_java//java:java_library.bzl", "java_library") # copybara-use-repo-external-label +load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_test") # copybara-use-repo-external-label +load("@rules_java//java:java_single_jar.bzl", "java_single_jar") # copybara-use-repo-external-label +load("@rules_java//toolchains:default_java_toolchain.bzl", "NONPREBUILT_TOOLCHAIN_CONFIGURATION", "default_java_toolchain") # copybara-use-repo-external-label +load("@rules_shell//shell:sh_test.bzl", "sh_test") + +package(default_applicable_licenses = ["@rules_java//:license"]) java_library( name = "lib", - srcs = glob(["src/*.java"]), + srcs = ["src/Main.java"], ) java_binary( @@ -11,3 +15,57 @@ java_binary( main_class = "Main", runtime_deps = [":lib"], ) + +java_test( + name = "MyTest", + srcs = ["src/MyTest.java"], + data = [ + "src/data.txt", + ], + deps = [ + "@my_jar//jar", + "@rules_java//java/runfiles", + ], +) + +java_single_jar( + name = "uber", + testonly = True, + deps = [ + ":MyTest", + ":bin", + ], +) + +genrule( + name = "MakeVarGenruleTest", + outs = ["MakeVarGenruleTestSuccess"], + cmd = "test -f $(JAVA) && test -d $(JAVABASE) && touch $@", + toolchains = ["@rules_java//toolchains:current_java_runtime"], + tools = ["@rules_java//toolchains:current_java_runtime"], +) + +sh_test( + name = "MakeVarTest", + srcs = ["src/MakeVarTest.sh"], + data = [ + "MakeVarGenruleTestSuccess", + "@rules_java//toolchains:current_java_runtime", + ], + env = { + "JAVA_ROOTPATH": "$(JAVA_ROOTPATH)", + "JAVABASE_ROOTPATH": "$(JAVABASE_ROOTPATH)", + }, + toolchains = ["@rules_java//toolchains:current_java_runtime"], +) + +default_java_toolchain( + name = "my_funky_toolchain", + bootclasspath = ["@bazel_tools//tools/jdk:platformclasspath"], + configuration = NONPREBUILT_TOOLCHAIN_CONFIGURATION, + exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + java_runtime = "@my_funky_jdk//:jdk", +) diff --git a/test/repo/MODULE.bazel b/test/repo/MODULE.bazel index 10460a86..5f300800 100644 --- a/test/repo/MODULE.bazel +++ b/test/repo/MODULE.bazel @@ -1,7 +1,74 @@ -module(name = "fake_repo_for_testing") +module(name = "integration_test_repo") bazel_dep(name = "rules_java", version = "7.5.0") -local_path_override( +archive_override( module_name = "rules_java", - path = "../../", + urls = [ + "file:///tmp/rules_java-HEAD.tar.gz", + "file:///C:/b/rules_java-HEAD.tar.gz", + ], ) + +http_jar = use_repo_rule("@rules_java//java:http_jar.bzl", "http_jar") + +http_jar( + name = "my_jar", + urls = [ + "file:///tmp/my_jar.jar", + "file:///C:/b/my_jar.jar", + ], +) + +java_toolchains = use_extension("@rules_java//java:extensions.bzl", "toolchains") +use_repo( + java_toolchains, + "local_jdk", + "remote_java_tools", + "remote_java_tools_darwin_arm64", + "remote_java_tools_darwin_x86_64", + "remote_java_tools_linux", + "remote_java_tools_linux_aarch64", + "remote_java_tools_windows", + "remotejdk11_linux", + "remotejdk11_linux_aarch64", + "remotejdk11_linux_ppc64le", + "remotejdk11_linux_s390x", + "remotejdk11_macos", + "remotejdk11_macos_aarch64", + "remotejdk11_win", + "remotejdk11_win_arm64", + "remotejdk17_linux", + "remotejdk17_linux_s390x", + "remotejdk17_macos", + "remotejdk17_macos_aarch64", + "remotejdk17_win", + "remotejdk17_win_arm64", + "remotejdk21_linux", + "remotejdk21_linux_riscv64", + "remotejdk21_macos", + "remotejdk21_macos_aarch64", + "remotejdk21_win", +) + +custom_jdk = use_extension("@rules_java//java:extensions.bzl", "java_repository") +custom_jdk.remote( + name = "my_funky_jdk", + prefix = "funky", + strip_prefix = "zulu24.32.13-ca-jdk24.0.2-linux_x64", + target_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + urls = [ + "https://cdn.azul.com/zulu/bin/zulu24.32.13-ca-jdk24.0.2-linux_x64.tar.gz", + ], + version = "24", +) +use_repo(custom_jdk, "my_funky_jdk", "my_funky_jdk_toolchain_config_repo") + +register_toolchains("@my_funky_jdk_toolchain_config_repo//:all") + +register_toolchains("//:all") + +bazel_dep(name = "rules_shell", version = "0.4.0", dev_dependency = True) +bazel_dep(name = "platforms", version = "0.0.11", dev_dependency = True) diff --git a/test/repo/WORKSPACE b/test/repo/WORKSPACE new file mode 100644 index 00000000..32cadb59 --- /dev/null +++ b/test/repo/WORKSPACE @@ -0,0 +1,74 @@ +workspace(name = "integration_test_repo") + +local_repository( + name = "rules_java", + path = "../../", +) + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "bazel_features", + sha256 = "a660027f5a87f13224ab54b8dc6e191693c554f2692fcca46e8e29ee7dabc43b", + strip_prefix = "bazel_features-1.30.0", + url = "https://github.com/bazel-contrib/bazel_features/releases/download/v1.30.0/bazel_features-v1.30.0.tar.gz", +) + +load("@bazel_features//:deps.bzl", "bazel_features_deps") + +bazel_features_deps() + +load("@rules_java//java:rules_java_deps.bzl", "rules_java_dependencies") + +rules_java_dependencies() + +load("@com_google_protobuf//bazel/private:proto_bazel_features.bzl", "proto_bazel_features") # buildifier: disable=bzl-visibility + +proto_bazel_features(name = "proto_bazel_features") + +register_toolchains("//:all") + +load("@rules_java//java:repositories.bzl", "rules_java_toolchains") + +rules_java_toolchains() + +load("@rules_java//java:http_jar.bzl", "http_jar") + +http_jar( + name = "my_jar", + urls = [ + "file:///tmp/my_jar.jar", + "file:///C:/b/my_jar.jar", + ], +) + +http_archive( + name = "rules_shell", + sha256 = "3e114424a5c7e4fd43e0133cc6ecdfe54e45ae8affa14fadd839f29901424043", + strip_prefix = "rules_shell-0.4.0", + url = "https://github.com/bazelbuild/rules_shell/releases/download/v0.4.0/rules_shell-v0.4.0.tar.gz", +) + +load("@rules_shell//shell:repositories.bzl", "rules_shell_dependencies", "rules_shell_toolchains") + +rules_shell_dependencies() + +rules_shell_toolchains() + +load("@rules_java//toolchains:remote_java_repository.bzl", "remote_java_repository") + +remote_java_repository( + name = "my_funky_jdk", + prefix = "funky", + strip_prefix = "zulu24.32.13-ca-jdk24.0.2-linux_x64", + target_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + urls = [ + "https://cdn.azul.com/zulu/bin/zulu24.32.13-ca-jdk24.0.2-linux_x64.tar.gz", + ], + version = "24", +) + +register_toolchains("@my_funky_jdk_toolchain_config_repo//:all") diff --git a/test/repo/WORKSPACE.bzlmod b/test/repo/WORKSPACE.bzlmod index 67a41c3f..e69de29b 100644 --- a/test/repo/WORKSPACE.bzlmod +++ b/test/repo/WORKSPACE.bzlmod @@ -1,5 +0,0 @@ -load("@rules_java//java:repositories.bzl", "rules_java_dependencies", "rules_java_toolchains") - -rules_java_dependencies() - -rules_java_toolchains() diff --git a/test/repo/setup.bat b/test/repo/setup.bat new file mode 100755 index 00000000..634892fa --- /dev/null +++ b/test/repo/setup.bat @@ -0,0 +1,4 @@ +cd ../../ +bazel build //distro:all //test/testdata:my_jar +cp -f ./bazel-bin/distro/*.tar.gz C:/b/rules_java-HEAD.tar.gz +cp -f bazel-bin/test/testdata/libmy_jar.jar C:/b/my_jar.jar diff --git a/test/repo/setup.sh b/test/repo/setup.sh new file mode 100755 index 00000000..7e3b369a --- /dev/null +++ b/test/repo/setup.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +cd ../../ +bazel build //distro:all //test/testdata:my_jar +cp -f bazel-bin/distro/rules_java-*.tar.gz /tmp/rules_java-HEAD.tar.gz +cp -f bazel-bin/test/testdata/libmy_jar.jar /tmp/my_jar.jar + diff --git a/test/repo/src/MakeVarTest.sh b/test/repo/src/MakeVarTest.sh new file mode 100755 index 00000000..7307dfdf --- /dev/null +++ b/test/repo/src/MakeVarTest.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +if [ ! -x "$JAVABASE_ROOTPATH/bin/java" ]; then + echo '$JAVABASE_RUNFILES does not point to a working JRE' && exit 1 +fi + +echo $JAVA_ROOTPATH +if [ ! -x "$JAVA_ROOTPATH" ]; then + echo '$JAVA_ROOTPATH does not exist' && exit 1 +fi \ No newline at end of file diff --git a/test/repo/src/MyTest.java b/test/repo/src/MyTest.java new file mode 100644 index 00000000..d2c21080 --- /dev/null +++ b/test/repo/src/MyTest.java @@ -0,0 +1,29 @@ +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.devtools.build.runfiles.AutoBazelRepository; +import com.google.devtools.build.runfiles.Runfiles; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import mypackage.MyLib; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +@AutoBazelRepository +public class MyTest { + @Test + public void main() { + assertEquals(MyLib.myStr(), "my_string"); + } + + @Test + public void runfiles() throws IOException { + Runfiles runfiles = Runfiles.preload().withSourceRepository(AutoBazelRepository_MyTest.NAME); + Path path = Paths.get(runfiles.rlocation("integration_test_repo/src/data.txt")); + assertTrue(Files.exists(path)); + } +} diff --git a/test/repo/src/data.txt b/test/repo/src/data.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/repositories.bzl b/test/repositories.bzl new file mode 100644 index 00000000..0f0afd53 --- /dev/null +++ b/test/repositories.bzl @@ -0,0 +1,28 @@ +"""Test dependencies for rules_java.""" + +load("@bazel_skylib//lib:modules.bzl", "modules") + +# TODO: Use http_jar from //java:http_jar.bzl once it doesn't refert to cache.bzl from @bazel_tools +# anymore, which isn't available in Bazel 6. +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") +load("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository") + +def test_repositories(): + http_file( + name = "guava", + url = "https://repo1.maven.org/maven2/com/google/guava/guava/33.3.1-jre/guava-33.3.1-jre.jar", + integrity = "sha256-S/Dixa+ORSXJbo/eF6T3MH+X+EePEcTI41oOMpiuTpA=", + downloaded_file_path = "guava.jar", + ) + http_file( + name = "truth", + url = "https://repo1.maven.org/maven2/com/google/truth/truth/1.4.4/truth-1.4.4.jar", + integrity = "sha256-Ushs3a3DG8hFfB4VaJ/Gt14ul84qg9i1S3ldVW1In4w=", + downloaded_file_path = "truth.jar", + ) + local_repository( + name = "other_repo", + path = "test/testdata/other_repo", + ) + +test_repositories_ext = modules.as_extension(test_repositories) diff --git a/test/testdata/BUILD.bazel b/test/testdata/BUILD.bazel new file mode 100644 index 00000000..7a0c7589 --- /dev/null +++ b/test/testdata/BUILD.bazel @@ -0,0 +1,9 @@ +load("//java:java_library.bzl", "java_library") + +package(default_applicable_licenses = ["@rules_java//:license"]) + +# Make a sample jar for the http_jar test. +java_library( + name = "my_jar", + srcs = ["MyLib.java"], +) diff --git a/test/testdata/MyLib.java b/test/testdata/MyLib.java new file mode 100644 index 00000000..48428f12 --- /dev/null +++ b/test/testdata/MyLib.java @@ -0,0 +1,9 @@ +package mypackage; + +/** A simple library for the http_jar test. */ +public class MyLib { + public static String myStr() { + return "my_string"; + } +} + diff --git a/test/testdata/other_repo/BUILD.bazel b/test/testdata/other_repo/BUILD.bazel new file mode 100644 index 00000000..fc25d5fb --- /dev/null +++ b/test/testdata/other_repo/BUILD.bazel @@ -0,0 +1 @@ +exports_files(["ExternalLib.java"]) diff --git a/test/testdata/other_repo/MODULE.bazel b/test/testdata/other_repo/MODULE.bazel new file mode 100644 index 00000000..f90d195a --- /dev/null +++ b/test/testdata/other_repo/MODULE.bazel @@ -0,0 +1 @@ +module(name = "other_repo") diff --git a/test/toolchains/BUILD.bazel b/test/toolchains/BUILD.bazel new file mode 100644 index 00000000..e25b3d8c --- /dev/null +++ b/test/toolchains/BUILD.bazel @@ -0,0 +1,7 @@ +load(":bootclasspath_tests.bzl", "bootclasspath_tests") + +package(default_applicable_licenses = ["@rules_java//:license"]) + +bootclasspath_tests( + name = "bootclasspath_tests", +) diff --git a/test/toolchains/bootclasspath_tests.bzl b/test/toolchains/bootclasspath_tests.bzl new file mode 100644 index 00000000..15ccd4ff --- /dev/null +++ b/test/toolchains/bootclasspath_tests.bzl @@ -0,0 +1,129 @@ +"""Tests for the bootclasspath rule.""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "subjects") +load("//java/common:java_common.bzl", "java_common") + +def _test_utf_8_environment(name): + analysis_test( + name = name, + impl = _test_utf_8_environment_impl, + target = Label("//toolchains:platformclasspath"), + ) + +def _test_utf_8_environment_impl(env, target): + for action in target.actions: + if action.mnemonic == "Ijar": + # ijar isn't sensitive to locales + continue + env_subject = env.expect.where(action = action).that_dict(action.env) + env_subject.keys().contains("LC_CTYPE") + env_subject.get("LC_CTYPE", factory = subjects.str).contains("UTF-8") + +def _test_incompatible_language_version_bootclasspath_disabled(name): + analysis_test( + name = name, + impl = _test_incompatible_language_version_bootclasspath_disabled_impl, + target = Label("//toolchains:platformclasspath"), + config_settings = { + "//command_line_option:java_language_version": "11", + "//command_line_option:java_runtime_version": "remotejdk_17", + str(Label("//toolchains:incompatible_language_version_bootclasspath")): False, + }, + ) + +def _test_incompatible_language_version_bootclasspath_disabled_impl(env, target): + system_path = target[java_common.BootClassPathInfo]._system_path + env.expect.that_str(system_path).contains("remotejdk17_") + +def _test_incompatible_language_version_bootclasspath_enabled_versioned(name): + analysis_test( + name = name, + impl = _test_incompatible_language_version_bootclasspath_enabled_versioned_impl, + target = Label("//toolchains:platformclasspath"), + config_settings = { + "//command_line_option:java_language_version": "11", + "//command_line_option:java_runtime_version": "remotejdk_17", + str(Label("//toolchains:incompatible_language_version_bootclasspath")): True, + }, + ) + +def _test_incompatible_language_version_bootclasspath_enabled_versioned_impl(env, target): + system_path = target[java_common.BootClassPathInfo]._system_path + env.expect.that_str(system_path).contains("remotejdk11_") + +def _test_incompatible_language_version_bootclasspath_enabled_unversioned(name): + analysis_test( + name = name, + impl = _test_incompatible_language_version_bootclasspath_enabled_unversioned_impl, + target = Label("//toolchains:platformclasspath"), + config_settings = { + "//command_line_option:java_language_version": "11", + "//command_line_option:java_runtime_version": "local_jdk", + str(Label("//toolchains:incompatible_language_version_bootclasspath")): True, + }, + ) + +def _test_incompatible_language_version_bootclasspath_enabled_unversioned_impl(env, target): + system_path = target[java_common.BootClassPathInfo]._system_path + env.expect.that_str(system_path).contains("local_jdk") + +def _test_jdk8_uses_tree_artifact(name): + analysis_test( + name = name, + impl = _test_jdk8_uses_tree_artifact_impl, + target = Label("//toolchains:platformclasspath"), + config_settings = { + "//command_line_option:tool_java_runtime_version": "remotejdk_8", + }, + ) + +def _test_jdk8_uses_tree_artifact_impl(env, target): + env.expect.that_target(target).action_named( + "JavaToolchainCompileClasses", + ).argv().contains_at_least([ + "-d", + "{bindir}/{package}/{name}_classes", + "toolchains/DumpPlatformClassPath.java", + ]).in_order() + env.expect.that_target(target).action_named( + "JavaToolchainCompileBootClasspath", + ).argv().contains_at_least([ + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "-cp", + "{bindir}/{package}/{name}_classes", + "DumpPlatformClassPath", + "{bindir}/{package}/{name}_unstripped.jar", + ]).in_order() + +def _test_jdk11_uses_source_launcher(name): + analysis_test( + name = name, + impl = _test_jdk11_uses_source_launcher_impl, + target = Label("//toolchains:platformclasspath"), + config_settings = { + "//command_line_option:tool_java_runtime_version": "remotejdk_11", + }, + ) + +def _test_jdk11_uses_source_launcher_impl(env, target): + env.expect.that_target(target).action_named( + "JavaToolchainCompileBootClasspath", + ).argv().contains_at_least([ + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "toolchains/DumpPlatformClassPath.java", + "{bindir}/{package}/{name}_unstripped.jar", + ]).in_order() + +def bootclasspath_tests(name): + test_suite( + name = name, + tests = [ + _test_utf_8_environment, + _test_incompatible_language_version_bootclasspath_disabled, + _test_incompatible_language_version_bootclasspath_enabled_versioned, + _test_incompatible_language_version_bootclasspath_enabled_unversioned, + _test_jdk8_uses_tree_artifact, + _test_jdk11_uses_source_launcher, + ], + ) diff --git a/third_party/BUILD b/third_party/BUILD new file mode 100644 index 00000000..e69de29b diff --git a/third_party/protobuf_load-cc-rules.patch b/third_party/protobuf_load-cc-rules.patch new file mode 100644 index 00000000..09bd7025 --- /dev/null +++ b/third_party/protobuf_load-cc-rules.patch @@ -0,0 +1,157 @@ +Backported from https://github.com/protocolbuffers/protobuf/pull/23584 to 32.0: + +From 733a0ccf6f53f469352b19440a1d152eccea6bec Mon Sep 17 00:00:00 2001 +From: Keith Smiley +Date: Sat, 20 Sep 2025 09:30:37 -0700 +Subject: [PATCH] bazel: add missing rules_cc loads + +This is required for use with bazel after this commit https://github.com/bazelbuild/bazel/commit/71ca0ed111ff3d842a0d23bc3a46bd2e6745491d + +Many files have these already +--- + lua/BUILD.bazel | 3 +++ + pkg/test/BUILD.bazel | 1 + + ruby/ext/google/protobuf_c/BUILD.bazel | 1 + + ruby/lib/google/BUILD.bazel | 1 + + rust/defs.bzl | 1 + + src/google/protobuf/compiler/cpp/BUILD.bazel | 1 + + src/google/protobuf/compiler/java/full/BUILD.bazel | 1 + + src/google/protobuf/compiler/java/lite/BUILD.bazel | 1 + + src/google/protobuf/compiler/kotlin/BUILD.bazel | 1 + + src/google/protobuf/compiler/php/BUILD.bazel | 1 + + src/google/protobuf/compiler/rust/BUILD.bazel | 2 ++ + toolchain/BUILD.bazel | 2 ++ + 12 files changed, 16 insertions(+) + +diff --git a/lua/BUILD.bazel b/lua/BUILD.bazel +index 389e5da7635ca..db956713bd849 100644 +--- a/lua/BUILD.bazel ++++ b/lua/BUILD.bazel +@@ -5,6 +5,9 @@ + # license that can be found in the LICENSE file or at + # https://developers.google.com/open-source/licenses/bsd + ++load("@rules_cc//cc:cc_binary.bzl", "cc_binary") ++load("@rules_cc//cc:cc_library.bzl", "cc_library") ++load("@rules_cc//cc:cc_test.bzl", "cc_test") + load("//bazel:proto_library.bzl", "proto_library") + load( + "//lua:lua_proto_library.bzl", +diff --git a/pkg/test/BUILD.bazel b/pkg/test/BUILD.bazel +index d0954f0126ba2..4fd01c9759643 100644 +--- a/pkg/test/BUILD.bazel ++++ b/pkg/test/BUILD.bazel +@@ -1,5 +1,6 @@ + # Tests for CMake file list generation + ++load("@rules_cc//cc:cc_library.bzl", "cc_library") + load("@rules_shell//shell:sh_test.bzl", "sh_test") + load("//pkg:build_systems.bzl", "gen_file_lists") + load("//pkg:cc_dist_library.bzl", "cc_dist_library") +diff --git a/ruby/ext/google/protobuf_c/BUILD.bazel b/ruby/ext/google/protobuf_c/BUILD.bazel +index 00458e2eedbe4..63276d538cc5c 100644 +--- a/ruby/ext/google/protobuf_c/BUILD.bazel ++++ b/ruby/ext/google/protobuf_c/BUILD.bazel +@@ -1,4 +1,5 @@ + load("@build_bazel_rules_apple//apple:apple_binary.bzl", "apple_binary") ++load("@rules_cc//cc:cc_library.bzl", "cc_library") + load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") + load("//upb/cmake:build_defs.bzl", "staleness_test") + +diff --git a/ruby/lib/google/BUILD.bazel b/ruby/lib/google/BUILD.bazel +index db0d7cd6b89bc..157df81b6d734 100644 +--- a/ruby/lib/google/BUILD.bazel ++++ b/ruby/lib/google/BUILD.bazel +@@ -1,3 +1,4 @@ ++load("@rules_cc//cc:cc_binary.bzl", "cc_binary") + load("@rules_java//java:java_binary.bzl", "java_binary") + load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") + load("@rules_ruby//ruby:defs.bzl", "rb_library") +diff --git a/rust/defs.bzl b/rust/defs.bzl +index b787d108fc743..54dab26e25048 100644 +--- a/rust/defs.bzl ++++ b/rust/defs.bzl +@@ -1,4 +1,5 @@ + """This file implements rust_proto_library.""" + ++load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") + load("@rules_rust//rust:defs.bzl", "rust_common") + load("//bazel/common:proto_common.bzl", "proto_common") +diff --git a/src/google/protobuf/compiler/cpp/BUILD.bazel b/src/google/protobuf/compiler/cpp/BUILD.bazel +index 4d4e99d774772..ced92c04e235d 100644 +--- a/src/google/protobuf/compiler/cpp/BUILD.bazel ++++ b/src/google/protobuf/compiler/cpp/BUILD.bazel +@@ -2,6 +2,7 @@ + # Protocol Buffers Compiler - C++ code generator + ################################################################################ + ++load("@rules_cc//cc:cc_binary.bzl", "cc_binary") + load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") + load("//bazel:cc_proto_library.bzl", "cc_proto_library") +diff --git a/src/google/protobuf/compiler/java/full/BUILD.bazel b/src/google/protobuf/compiler/java/full/BUILD.bazel +index 6bffaa10fbc87..54b4ae9cd186d 100644 +--- a/src/google/protobuf/compiler/java/full/BUILD.bazel ++++ b/src/google/protobuf/compiler/java/full/BUILD.bazel +@@ -1,6 +1,7 @@ + # We use abbreviated target names in this directory to work around: + # https://github.com/bazelbuild/bazel/issues/18683 + ++load("@rules_cc//cc:cc_library.bzl", "cc_library") + load("//build_defs:cpp_opts.bzl", "COPTS") + + cc_library( +diff --git a/src/google/protobuf/compiler/java/lite/BUILD.bazel b/src/google/protobuf/compiler/java/lite/BUILD.bazel +index 02af1b3f917df..5e04135abbac0 100644 +--- a/src/google/protobuf/compiler/java/lite/BUILD.bazel ++++ b/src/google/protobuf/compiler/java/lite/BUILD.bazel +@@ -1,3 +1,4 @@ ++load("@rules_cc//cc:cc_library.bzl", "cc_library") + load("//build_defs:cpp_opts.bzl", "COPTS") + + cc_library( +diff --git a/src/google/protobuf/compiler/kotlin/BUILD.bazel b/src/google/protobuf/compiler/kotlin/BUILD.bazel +index 4f866ae02e1b7..5a1e1b727092b 100644 +--- a/src/google/protobuf/compiler/kotlin/BUILD.bazel ++++ b/src/google/protobuf/compiler/kotlin/BUILD.bazel +@@ -1,3 +1,4 @@ ++load("@rules_cc//cc:cc_library.bzl", "cc_library") + load("//build_defs:cpp_opts.bzl", "COPTS") + + cc_library( +diff --git a/src/google/protobuf/compiler/php/BUILD.bazel b/src/google/protobuf/compiler/php/BUILD.bazel +index 930db4476125b..d41fe6d122adb 100644 +--- a/src/google/protobuf/compiler/php/BUILD.bazel ++++ b/src/google/protobuf/compiler/php/BUILD.bazel +@@ -2,6 +2,7 @@ + # Protocol Buffers Compiler - PHP code generator + ################################################################################ + ++load("@rules_cc//cc:cc_test.bzl", "cc_test") + load("@rules_cc//cc:defs.bzl", "cc_library") + load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") + load("//build_defs:cpp_opts.bzl", "COPTS") +diff --git a/src/google/protobuf/compiler/rust/BUILD.bazel b/src/google/protobuf/compiler/rust/BUILD.bazel +index e5766c2abd40b..116e44b76df9c 100644 +--- a/src/google/protobuf/compiler/rust/BUILD.bazel ++++ b/src/google/protobuf/compiler/rust/BUILD.bazel +@@ -9,6 +9,8 @@ + # Protocol Buffers Compiler - Rust code generator + ################################################################################ + ++load("@rules_cc//cc:cc_binary.bzl", "cc_binary") ++load("@rules_cc//cc:cc_test.bzl", "cc_test") + load("@rules_cc//cc:defs.bzl", "cc_library") + load("//build_defs:cpp_opts.bzl", "COPTS") + load( +diff --git a/toolchain/BUILD.bazel b/toolchain/BUILD.bazel +index 524fa14390501..4cb056c75ff66 100644 +--- a/toolchain/BUILD.bazel ++++ b/toolchain/BUILD.bazel +@@ -1,4 +1,6 @@ + load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") ++load("@rules_cc//cc/toolchains:cc_toolchain.bzl", "cc_toolchain") ++load("@rules_cc//cc/toolchains:cc_toolchain_suite.bzl", "cc_toolchain_suite") + load(":cc_toolchain_config.bzl", "cc_toolchain_config") + + package(default_visibility = ["//visibility:public"]) diff --git a/toolchains/BUILD b/toolchains/BUILD index 81126c22..0f3ef2bf 100644 --- a/toolchains/BUILD +++ b/toolchains/BUILD @@ -1,10 +1,15 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") -load("@rules_cc//cc:defs.bzl", "cc_library") +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "string_setting") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load( + ":bootclasspath.bzl", + "bootclasspath", + "language_version_bootstrap_runtime", +) load( ":default_java_toolchain.bzl", "DEFAULT_TOOLCHAIN_CONFIGURATION", "PREBUILT_TOOLCHAIN_CONFIGURATION", - "bootclasspath", "default_java_toolchain", "java_runtime_files", ) @@ -15,8 +20,12 @@ load( "java_runtime_version_alias", "java_toolchain_alias", ) +load(":utf8_environment.bzl", "utf8_environment") -package(default_visibility = ["//visibility:public"]) +package( + default_applicable_licenses = ["@rules_java//:license"], + default_visibility = ["//visibility:public"], +) licenses(["notice"]) @@ -30,6 +39,15 @@ filegroup( srcs = glob(["*.bzl"]), ) +# If enabled, the bootclasspath for Java compilation will be extracted from a Java runtime matching +# the version specified with `--java_language_version` rather than the runtime specified with +# `--java_runtime_version`. +bool_flag( + name = "incompatible_language_version_bootclasspath", + build_setting_default = False, + visibility = ["//visibility:private"], +) + # A single binary distribution of a JDK (e.g., OpenJDK 17 for Windows arm64) provides three # different types of toolchains from the perspective of Bazel: @@ -136,9 +154,9 @@ cc_library( "@bazel_tools//src/conditions:darwin": ["include/darwin"], "@bazel_tools//src/conditions:freebsd": ["include/freebsd"], "@bazel_tools//src/conditions:linux_aarch64": ["include/linux"], - "@bazel_tools//src/conditions:linux_mips64": [":include/linux"], + "@bazel_tools//src/conditions:linux_mips64": ["include/linux"], "@bazel_tools//src/conditions:linux_ppc64le": ["include/linux"], - "@bazel_tools//src/conditions:linux_riscv64": [":include/linux"], + "@bazel_tools//src/conditions:linux_riscv64": ["include/linux"], "@bazel_tools//src/conditions:linux_s390x": ["include/linux"], "@bazel_tools//src/conditions:linux_x86_64": ["include/linux"], "@bazel_tools//src/conditions:openbsd": ["include/openbsd"], @@ -160,6 +178,11 @@ cc_library( actual = "@remote_java_tools_%s//:prebuilt_singlejar" % OS, visibility = ["//visibility:private"], ), + alias( + name = "prebuilt_one_version_%s" % OS, + actual = "@remote_java_tools_%s//:prebuilt_one_version" % OS, + visibility = ["//visibility:private"], + ), alias( name = "turbine_direct_graal_%s" % OS, actual = "@remote_java_tools_%s//:turbine_direct_graal" % OS, @@ -167,6 +190,7 @@ cc_library( ) for OS in [ "linux", + "linux_aarch64", "darwin_x86_64", "darwin_arm64", "windows", @@ -184,6 +208,7 @@ alias( "@bazel_tools//src/conditions:darwin_arm64": ":ijar_prebuilt_binary_darwin_arm64", "@bazel_tools//src/conditions:darwin_x86_64": ":ijar_prebuilt_binary_darwin_x86_64", "@bazel_tools//src/conditions:linux_x86_64": ":ijar_prebuilt_binary_linux", + "@bazel_tools//src/conditions:linux_aarch64": ":ijar_prebuilt_binary_linux_aarch64", "@bazel_tools//src/conditions:windows": ":ijar_prebuilt_binary_windows", "//conditions:default": "@remote_java_tools//:ijar_cc_binary", }), @@ -195,6 +220,7 @@ alias( "@bazel_tools//src/conditions:darwin_arm64": ":ijar_prebuilt_binary_darwin_arm64", "@bazel_tools//src/conditions:darwin_x86_64": ":ijar_prebuilt_binary_darwin_x86_64", "@bazel_tools//src/conditions:linux_x86_64": ":ijar_prebuilt_binary_linux", + "@bazel_tools//src/conditions:linux_aarch64": "ijar_prebuilt_binary_linux_aarch64", "@bazel_tools//src/conditions:windows": ":ijar_prebuilt_binary_windows", }), ) @@ -210,6 +236,7 @@ alias( "@bazel_tools//src/conditions:darwin_arm64": ":prebuilt_singlejar_darwin_arm64", "@bazel_tools//src/conditions:darwin_x86_64": ":prebuilt_singlejar_darwin_x86_64", "@bazel_tools//src/conditions:linux_x86_64": ":prebuilt_singlejar_linux", + "@bazel_tools//src/conditions:linux_aarch64": ":prebuilt_singlejar_linux_aarch64", "@bazel_tools//src/conditions:windows": ":prebuilt_singlejar_windows", "//conditions:default": "@remote_java_tools//:singlejar_cc_bin", }), @@ -221,10 +248,39 @@ alias( "@bazel_tools//src/conditions:darwin_arm64": ":prebuilt_singlejar_darwin_arm64", "@bazel_tools//src/conditions:darwin_x86_64": ":prebuilt_singlejar_darwin_x86_64", "@bazel_tools//src/conditions:linux_x86_64": ":prebuilt_singlejar_linux", + "@bazel_tools//src/conditions:linux_aarch64": ":prebuilt_singlejar_linux_aarch64", "@bazel_tools//src/conditions:windows": ":prebuilt_singlejar_windows", }), ) +alias( + name = "one_version", + actual = ":one_version_prebuilt_or_cc_binary", +) + +alias( + name = "one_version_prebuilt_or_cc_binary", + actual = select({ + "@bazel_tools//src/conditions:darwin_arm64": ":prebuilt_one_version_darwin_arm64", + "@bazel_tools//src/conditions:darwin_x86_64": ":prebuilt_one_version_darwin_x86_64", + "@bazel_tools//src/conditions:linux_x86_64": ":prebuilt_one_version_linux", + "@bazel_tools//src/conditions:linux_aarch64": ":prebuilt_one_version_linux_aarch64", + "@bazel_tools//src/conditions:windows": ":prebuilt_one_version_windows", + "//conditions:default": "@remote_java_tools//:one_version_cc_bin", + }), +) + +alias( + name = "prebuilt_one_version", + actual = select({ + "@bazel_tools//src/conditions:darwin_arm64": ":prebuilt_one_version_darwin_arm64", + "@bazel_tools//src/conditions:darwin_x86_64": ":prebuilt_one_version_darwin_x86_64", + "@bazel_tools//src/conditions:linux_x86_64": ":prebuilt_one_version_linux", + "@bazel_tools//src/conditions:linux_aarch64": ":prebuilt_one_version_linux_aarch64", + "@bazel_tools//src/conditions:windows": ":prebuilt_one_version_windows", + }), +) + alias( name = "turbine_direct", actual = ":turbine_direct_graal_or_java", @@ -236,6 +292,7 @@ alias( "@bazel_tools//src/conditions:darwin_arm64": ":turbine_direct_graal_darwin_arm64", "@bazel_tools//src/conditions:darwin_x86_64": ":turbine_direct_graal_darwin_x86_64", "@bazel_tools//src/conditions:linux_x86_64": ":turbine_direct_graal_linux", + "@bazel_tools//src/conditions:linux_aarch64": ":turbine_direct_graal_linux_aarch64", "@bazel_tools//src/conditions:windows": ":turbine_direct_graal_windows", "//conditions:default": "@remote_java_tools//:TurbineDirect", }), @@ -247,14 +304,62 @@ alias( "@bazel_tools//src/conditions:darwin_arm64": ":turbine_direct_graal_darwin_arm64", "@bazel_tools//src/conditions:darwin_x86_64": ":turbine_direct_graal_darwin_x86_64", "@bazel_tools//src/conditions:linux_x86_64": ":turbine_direct_graal_linux", + "@bazel_tools//src/conditions:linux_aarch64": ":turbine_direct_graal_linux_aarch64", "@bazel_tools//src/conditions:windows": ":turbine_direct_graal_windows", }), ) +string_setting( + name = "java_language_version", + build_setting_default = "", + visibility = ["//visibility:private"], +) + +string_setting( + name = "java_runtime_version", + build_setting_default = "", + visibility = ["//visibility:private"], +) + +language_version_bootstrap_runtime( + name = "language_version_bootstrap_runtime", + java_language_version = ":java_language_version", + java_runtime_version = ":java_runtime_version", + visibility = ["//visibility:private"], +) + +utf8_environment( + name = "utf8_environment", + visibility = ["//visibility:private"], +) + +config_setting( + name = "incompatible_language_version_bootclasspath_enabled", + flag_values = { + ":incompatible_language_version_bootclasspath": "True", + }, + visibility = ["//visibility:private"], +) + bootclasspath( name = "platformclasspath", src = "DumpPlatformClassPath.java", java_runtime_alias = ":current_java_runtime", + language_version_bootstrap_runtime = select({ + ":incompatible_language_version_bootclasspath_enabled": ":language_version_bootstrap_runtime", + "//conditions:default": None, + }), +) + +bootclasspath( + name = "platformclasspath_nostrip", + src = "DumpPlatformClassPath.java", + java_runtime_alias = ":current_java_runtime", + language_version_bootstrap_runtime = select({ + ":incompatible_language_version_bootclasspath_enabled": ":language_version_bootstrap_runtime", + "//conditions:default": None, + }), + strip = False, ) default_java_toolchain( @@ -268,12 +373,12 @@ alias( actual = ":toolchain", ) -RELEASES = (8, 9, 10, 11, 17, 21) +RELEASES = (8, 9, 10, 11, 17, 21, 25) [ default_java_toolchain( name = ("toolchain_java%d" if release <= 11 else "toolchain_jdk_%d") % release, - configuration = DEFAULT_TOOLCHAIN_CONFIGURATION, + configuration = DEFAULT_TOOLCHAIN_CONFIGURATION | {"java_runtime": ":remotejdk_25"}, source_version = "%s" % release, target_version = "%s" % release, ) @@ -293,18 +398,6 @@ java_runtime_version_alias( visibility = ["//visibility:public"], ) -java_runtime_version_alias( - name = "remotejdk_15", - runtime_version = "remotejdk_15", - visibility = ["//visibility:public"], -) - -java_runtime_version_alias( - name = "remotejdk_16", - runtime_version = "remotejdk_16", - visibility = ["//visibility:public"], -) - java_runtime_version_alias( name = "remotejdk_17", runtime_version = "remotejdk_17", @@ -317,6 +410,12 @@ java_runtime_version_alias( visibility = ["//visibility:public"], ) +java_runtime_version_alias( + name = "remotejdk_25", + runtime_version = "remotejdk_25", + visibility = ["//visibility:public"], +) + java_runtime_version_alias( name = "jdk_8", runtime_version = "8", diff --git a/toolchains/bootclasspath.bzl b/toolchains/bootclasspath.bzl new file mode 100644 index 00000000..256c032f --- /dev/null +++ b/toolchains/bootclasspath.bzl @@ -0,0 +1,298 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Rules for extracting a platform classpath from Java runtimes.""" + +load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") +load("//java/common:java_common.bzl", "java_common") +load(":utf8_environment.bzl", "Utf8EnvironmentInfo") + +visibility("private") + +# TODO: This provider and is only necessary since --java_{language,runtime}_version +# are not available directly to Starlark. +_JavaVersionsInfo = provider( + "Exposes the --java_{language,runtime}_version value as extracted from a transition to a dependant.", + fields = { + "java_language_version": "The value of --java_language_version", + "java_runtime_version": "The value of --java_runtime_version", + }, +) + +def _language_version_bootstrap_runtime(ctx): + providers = [ + _JavaVersionsInfo( + java_language_version = ctx.attr.java_language_version[BuildSettingInfo].value, + java_runtime_version = ctx.attr.java_runtime_version[BuildSettingInfo].value, + ), + ] + + bootstrap_runtime = ctx.toolchains["@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type"] + if bootstrap_runtime: + providers.append(bootstrap_runtime.java_runtime) + + return providers + +language_version_bootstrap_runtime = rule( + implementation = _language_version_bootstrap_runtime, + attrs = { + "java_language_version": attr.label( + providers = [BuildSettingInfo], + ), + "java_runtime_version": attr.label( + providers = [BuildSettingInfo], + ), + }, + toolchains = [ + config_common.toolchain_type("@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type", mandatory = False), + ], +) + +def _get_bootstrap_runtime_version(*, java_language_version, java_runtime_version): + """Returns the runtime version to use for bootstrapping the given language version. + + If the runtime version is not versioned, e.g. "local_jdk", it is used as is. + Otherwise, the language version replaces the numeric part of the runtime version, e.g., + "remotejdk_17" becomes "remotejdk_8". + """ + prefix, separator, version = java_runtime_version.rpartition("_") + if version and version.isdigit(): + new_version = java_language_version + else: + # The runtime version is not versioned, e.g. "local_jdk". Use it as is. + new_version = version + + return prefix + separator + new_version + +def _bootclasspath_transition_impl(settings, _): + java_language_version = settings["//command_line_option:java_language_version"] + java_runtime_version = settings["//command_line_option:java_runtime_version"] + + return { + "//command_line_option:java_runtime_version": _get_bootstrap_runtime_version( + java_language_version = java_language_version, + java_runtime_version = java_runtime_version, + ), + "//toolchains:java_language_version": java_language_version, + "//toolchains:java_runtime_version": java_runtime_version, + } + +_bootclasspath_transition = transition( + implementation = _bootclasspath_transition_impl, + inputs = [ + "//command_line_option:java_language_version", + "//command_line_option:java_runtime_version", + ], + outputs = [ + "//command_line_option:java_runtime_version", + "//toolchains:java_language_version", + "//toolchains:java_runtime_version", + ], +) + +def _run_ijar(*, actions, label, ijar, input, output): + args = actions.args() + args.add(input) + args.add(output) + args.add("--target_label", label) + actions.run( + inputs = [input], + outputs = [output], + executable = ijar, + arguments = [args], + progress_message = "Extracting interfaces from %{input}", + execution_requirements = _SUPPORTS_PATH_MAPPING, + mnemonic = "Ijar", + ) + +_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE = Label("@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type") + +# Opt the Java bootstrap actions into path mapping: +# https://github.com/bazelbuild/bazel/commit/a239ea84832f18ee8706682145e9595e71b39680 +_SUPPORTS_PATH_MAPPING = {"supports-path-mapping": "1"} + +def _java_home(java_executable): + return java_executable.dirname[:-len("/bin")] + +def _bootclasspath_impl(ctx): + exec_javabase = ctx.attr.java_runtime_alias[java_common.JavaRuntimeInfo] + env = ctx.attr._utf8_environment[Utf8EnvironmentInfo].environment + + # If possible, use JDK 11+'s ability to run a single Java file to avoid a + # separate action to compile DumpPlatformClassPath. + use_source_launcher = exec_javabase.version >= 11 + + class_dir = None + if not use_source_launcher: + class_dir = ctx.actions.declare_directory("%s_classes" % ctx.label.name) + + args = ctx.actions.args() + args.add("-source") + args.add("8") + args.add("-target") + args.add("8") + args.add("-Xlint:-options") + args.add("-J-XX:-UsePerfData") + args.add("-d") + args.add_all([class_dir], expand_directories = False) + args.add(ctx.file.src) + + ctx.actions.run( + executable = "%s/bin/javac" % exec_javabase.java_home, + mnemonic = "JavaToolchainCompileClasses", + inputs = [ctx.file.src] + ctx.files.java_runtime_alias, + outputs = [class_dir], + arguments = [args], + env = env, + execution_requirements = _SUPPORTS_PATH_MAPPING, + use_default_shell_env = True, + ) + + unstripped_bootclasspath = ctx.actions.declare_file("%s_unstripped.jar" % ctx.label.name) + + args = ctx.actions.args() + args.add("-XX:+IgnoreUnrecognizedVMOptions") + args.add("-XX:-UsePerfData") + args.add("--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED") + args.add("--add-exports=jdk.compiler/com.sun.tools.javac.platform=ALL-UNNAMED") + args.add("--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED") + + if use_source_launcher: + args.add(ctx.file.src) + else: + args.add_all("-cp", [class_dir], expand_directories = False) + args.add("DumpPlatformClassPath") + + args.add(unstripped_bootclasspath) + + if ctx.attr.language_version_bootstrap_runtime: + # The attribute is subject to a split transition. + language_version_bootstrap_runtime = ctx.attr.language_version_bootstrap_runtime[0] + if java_common.JavaRuntimeInfo in language_version_bootstrap_runtime: + any_javabase = language_version_bootstrap_runtime[java_common.JavaRuntimeInfo] + else: + java_versions_info = language_version_bootstrap_runtime[_JavaVersionsInfo] + bootstrap_runtime_version = _get_bootstrap_runtime_version( + java_language_version = java_versions_info.java_language_version, + java_runtime_version = java_versions_info.java_runtime_version, + ) + is_exec = "-exec" in ctx.bin_dir.path + tool_prefix = "tool_" if is_exec else "" + fail(""" +No Java runtime found to extract the bootclasspath from for --{tool_prefix}java_language_version={language_version} and --{tool_prefix}java_runtime_version={runtime_version}. +The bootclasspath provides the particular version of the Java standard library to compile against. +You can do one of the following: + + * register a Java runtime with name "{bootstrap_runtime_version}" to provide the bootclasspath + * set --java_language_version=N for an available runtime "{runtime_version}_N" + * remove the suffix from your runtime's name to use it as the bootclasspath + +Rerun with --toolchain_resolution_debug='@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type' to see more details about toolchain resolution. +""".format( + language_version = java_versions_info.java_language_version, + runtime_version = java_versions_info.java_runtime_version, + bootstrap_runtime_version = bootstrap_runtime_version, + tool_prefix = tool_prefix, + )) + else: + any_javabase = ctx.toolchains[_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE].java_runtime + any_javabase_files = any_javabase.files.to_list() + + # If possible, add the Java executable to the command line as a File so that it can be path + # mapped. + java_executable = [f for f in any_javabase_files if f.path == any_javabase.java_executable_exec_path] + if len(java_executable) == 1: + args.add_all(java_executable, map_each = _java_home) + else: + args.add(any_javabase.java_home) + + system_files = ("release", "modules", "jrt-fs.jar") + system = [f for f in any_javabase_files if f.basename in system_files] + if len(system) != len(system_files): + system = None + + classpath_input = ctx.file.src if use_source_launcher else class_dir + inputs = depset([classpath_input] + ctx.files.java_runtime_alias, transitive = [any_javabase.files]) + ctx.actions.run( + executable = str(exec_javabase.java_executable_exec_path), + mnemonic = "JavaToolchainCompileBootClasspath", + inputs = inputs, + outputs = [unstripped_bootclasspath], + arguments = [args], + env = env, + execution_requirements = _SUPPORTS_PATH_MAPPING, + use_default_shell_env = True, + ) + + bootclasspath = ctx.outputs.output_jar + if ctx.attr.strip: + _run_ijar( + actions = ctx.actions, + label = ctx.label, + ijar = ctx.executable._ijar, + input = unstripped_bootclasspath, + output = bootclasspath, + ) + else: + ctx.actions.symlink(output = bootclasspath, target_file = unstripped_bootclasspath) + return [ + DefaultInfo(files = depset([bootclasspath])), + java_common.BootClassPathInfo( + bootclasspath = [bootclasspath], + system = system, + ), + OutputGroupInfo(jar = [bootclasspath]), + ] + +def _compute_ijar(strip): + return Label("//toolchains:ijar") if strip else None + +_bootclasspath = rule( + implementation = _bootclasspath_impl, + attrs = { + "java_runtime_alias": attr.label( + cfg = "exec", + providers = [java_common.JavaRuntimeInfo], + ), + "language_version_bootstrap_runtime": attr.label( + cfg = _bootclasspath_transition, + ), + "output_jar": attr.output(mandatory = True), + "src": attr.label( + cfg = "exec", + allow_single_file = True, + ), + "strip": attr.bool(default = True), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + "_ijar": attr.label( + default = _compute_ijar, + cfg = "exec", + executable = True, + ), + "_utf8_environment": attr.label( + default = ":utf8_environment", + cfg = "exec", + ), + }, + toolchains = [_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE], +) + +def bootclasspath(name, **kwargs): + _bootclasspath( + name = name, + output_jar = name + ".jar", + **kwargs + ) diff --git a/toolchains/default_java_toolchain.bzl b/toolchains/default_java_toolchain.bzl index b3d4d28b..f4f83f66 100644 --- a/toolchains/default_java_toolchain.bzl +++ b/toolchains/default_java_toolchain.bzl @@ -14,8 +14,8 @@ """Rules for defining default_java_toolchain""" -load("//java:defs.bzl", "java_toolchain") -load("//java/common:java_common.bzl", "java_common") +load("//java/toolchains:java_toolchain.bzl", "java_toolchain") +load(":bootclasspath.bzl", _bootclasspath = "bootclasspath") # JVM options, without patching java.compiler and jdk.compiler modules. BASE_JDK9_JVM_OPTS = [ @@ -56,26 +56,32 @@ JDK9_JVM_OPTS = BASE_JDK9_JVM_OPTS DEFAULT_JAVACOPTS = [ "-XDskipDuplicateBridges=true", "-XDcompilePolicy=simple", + "--should-stop=ifError=FLOW", # See b/27049950, https://github.com/google/error-prone/issues/4595 "-g", "-parameters", - # https://github.com/bazelbuild/bazel/issues/15219 - "-Xep:ReturnValueIgnored:OFF", # https://github.com/bazelbuild/bazel/issues/16996 "-Xep:IgnoredPureGetter:OFF", "-Xep:EmptyTopLevelDeclaration:OFF", "-Xep:LenientFormatStringValidation:OFF", "-Xep:ReturnMissingNullable:OFF", "-Xep:UseCorrectAssertInTests:OFF", + # Please see https://github.com/bazelbuild/bazel/issues/25927#issuecomment-2825206105 + # we need to make sure not to filter any warnings so that Bazel can trigger them as errors. + "-Xmaxwarns -1", ] +# If this is changed, the docs for "{,tool_}java_language_version" also +# need to be updated in the Bazel user manual +_DEFAULT_JAVA_LANGUAGE_VERSION = "11" + # Default java_toolchain parameters _BASE_TOOLCHAIN_CONFIGURATION = dict( forcibly_disable_header_compilation = False, - genclass = [Label("@remote_java_tools//:GenClass")], - header_compiler = [Label("@remote_java_tools//:TurbineDirect")], - header_compiler_direct = [Label("//toolchains:turbine_direct")], - ijar = [Label("//toolchains:ijar")], - javabuilder = [Label("@remote_java_tools//:JavaBuilder")], + genclass = Label("@remote_java_tools//:GenClass"), + header_compiler = Label("@remote_java_tools//:TurbineDirect"), + header_compiler_direct = Label("//toolchains:turbine_direct"), + ijar = Label("//toolchains:ijar"), + javabuilder = Label("@remote_java_tools//:JavaBuilder"), javac_supports_workers = True, jacocorunner = Label("@remote_java_tools//:jacoco_coverage_runner_filegroup"), jvm_opts = BASE_JDK9_JVM_OPTS, @@ -84,16 +90,19 @@ _BASE_TOOLCHAIN_CONFIGURATION = dict( "-XX:+UseParallelGC", ], misc = DEFAULT_JAVACOPTS, - singlejar = [Label("//toolchains:singlejar")], + singlejar = Label("//toolchains:singlejar"), # Code to enumerate target JVM boot classpath uses host JVM. Because # java_runtime-s are involved, its implementation is in @bazel_tools. bootclasspath = [Label("//toolchains:platformclasspath")], - source_version = "8", - target_version = "8", + source_version = _DEFAULT_JAVA_LANGUAGE_VERSION, + target_version = _DEFAULT_JAVA_LANGUAGE_VERSION, reduced_classpath_incompatible_processors = [ "dagger.hilt.processor.internal.root.RootProcessor", # see b/21307381 ], + # TODO: Update to JDK 25 after some time has passed - it no longer supports + # targeting JDK 7. java_runtime = Label("//toolchains:remotejdk_21"), + oneversion = Label("//toolchains:one_version"), ) DEFAULT_TOOLCHAIN_CONFIGURATION = _BASE_TOOLCHAIN_CONFIGURATION @@ -112,7 +121,7 @@ DEFAULT_TOOLCHAIN_CONFIGURATION = _BASE_TOOLCHAIN_CONFIGURATION # However it does allow using a wider range of `--host_javabase`s, including # versions newer than the current JDK. VANILLA_TOOLCHAIN_CONFIGURATION = dict( - javabuilder = [Label("@remote_java_tools//:VanillaJavaBuilder")], + javabuilder = Label("@remote_java_tools//:VanillaJavaBuilder"), jvm_opts = [], java_runtime = None, ) @@ -123,21 +132,20 @@ VANILLA_TOOLCHAIN_CONFIGURATION = dict( # same, otherwise the binaries will not work on the execution # platform. PREBUILT_TOOLCHAIN_CONFIGURATION = dict( - ijar = [Label("//toolchains:ijar_prebuilt_binary")], - singlejar = [Label("//toolchains:prebuilt_singlejar")], + ijar = Label("//toolchains:ijar_prebuilt_binary"), + singlejar = Label("//toolchains:prebuilt_singlejar"), + oneversion = Label("//toolchains:prebuilt_one_version"), ) # The new toolchain is using all the tools from sources. NONPREBUILT_TOOLCHAIN_CONFIGURATION = dict( - ijar = [Label("@remote_java_tools//:ijar_cc_binary")], - singlejar = [Label("@remote_java_tools//:singlejar_cc_bin")], - header_compiler_direct = [Label("@remote_java_tools//:TurbineDirect")], + ijar = Label("@remote_java_tools//:ijar_cc_binary"), + singlejar = Label("@remote_java_tools//:singlejar_cc_bin"), + header_compiler_direct = Label("@remote_java_tools//:TurbineDirect"), + oneversion = Label("@remote_java_tools//:one_version_cc_bin"), + bootclasspath = [Label("//toolchains:platformclasspath_nostrip")], ) -# If this is changed, the docs for "{,tool_}java_language_version" also -# need to be updated in the Bazel user manual -_DEFAULT_SOURCE_VERSION = "8" - def default_java_toolchain(name, configuration = DEFAULT_TOOLCHAIN_CONFIGURATION, toolchain_definition = True, exec_compatible_with = [], target_compatible_with = [], **kwargs): """Defines a remote java_toolchain with appropriate defaults for Bazel. @@ -161,7 +169,7 @@ def default_java_toolchain(name, configuration = DEFAULT_TOOLCHAIN_CONFIGURATION ) if toolchain_definition: source_version = toolchain_args["source_version"] - if source_version == _DEFAULT_SOURCE_VERSION: + if source_version == _DEFAULT_JAVA_LANGUAGE_VERSION: native.config_setting( name = name + "_default_version_setting", values = {"java_language_version": ""}, @@ -208,105 +216,4 @@ def java_runtime_files(name, srcs): tags = ["manual"], ) -_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE = Label("@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type") - -# Opt the Java bootstrap actions into path mapping: -# https://github.com/bazelbuild/bazel/commit/a239ea84832f18ee8706682145e9595e71b39680 -_SUPPORTS_PATH_MAPPING = {"supports-path-mapping": "1"} - -def _java_home(java_executable): - return java_executable.dirname[:-len("/bin")] - -def _bootclasspath_impl(ctx): - exec_javabase = ctx.attr.java_runtime_alias[java_common.JavaRuntimeInfo] - - class_dir = ctx.actions.declare_directory("%s_classes" % ctx.label.name) - - args = ctx.actions.args() - args.add("-source") - args.add("8") - args.add("-target") - args.add("8") - args.add("-Xlint:-options") - args.add("-J-XX:-UsePerfData") - args.add("-d") - args.add_all([class_dir], expand_directories = False) - args.add(ctx.file.src) - - ctx.actions.run( - executable = "%s/bin/javac" % exec_javabase.java_home, - mnemonic = "JavaToolchainCompileClasses", - inputs = [ctx.file.src] + ctx.files.java_runtime_alias, - outputs = [class_dir], - arguments = [args], - execution_requirements = _SUPPORTS_PATH_MAPPING, - ) - - bootclasspath = ctx.outputs.output_jar - - args = ctx.actions.args() - args.add("-XX:+IgnoreUnrecognizedVMOptions") - args.add("-XX:-UsePerfData") - args.add("--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED") - args.add("--add-exports=jdk.compiler/com.sun.tools.javac.platform=ALL-UNNAMED") - args.add("--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED") - args.add_all("-cp", [class_dir], expand_directories = False) - args.add("DumpPlatformClassPath") - args.add(bootclasspath) - - any_javabase = ctx.toolchains[_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE].java_runtime - any_javabase_files = any_javabase.files.to_list() - - # If possible, add the Java executable to the command line as a File so that it can be path - # mapped. - java_executable = [f for f in any_javabase_files if f.path == any_javabase.java_executable_exec_path] - if len(java_executable) == 1: - args.add_all(java_executable, map_each = _java_home) - else: - args.add(any_javabase.java_home) - - system_files = ("release", "modules", "jrt-fs.jar") - system = [f for f in any_javabase_files if f.basename in system_files] - if len(system) != len(system_files): - system = None - - inputs = depset([class_dir] + ctx.files.java_runtime_alias, transitive = [any_javabase.files]) - ctx.actions.run( - executable = str(exec_javabase.java_executable_exec_path), - mnemonic = "JavaToolchainCompileBootClasspath", - inputs = inputs, - outputs = [bootclasspath], - arguments = [args], - execution_requirements = _SUPPORTS_PATH_MAPPING, - ) - return [ - DefaultInfo(files = depset([bootclasspath])), - java_common.BootClassPathInfo( - bootclasspath = [bootclasspath], - system = system, - ), - OutputGroupInfo(jar = [bootclasspath]), - ] - -_bootclasspath = rule( - implementation = _bootclasspath_impl, - attrs = { - "java_runtime_alias": attr.label( - cfg = "exec", - providers = [java_common.JavaRuntimeInfo], - ), - "output_jar": attr.output(mandatory = True), - "src": attr.label( - cfg = "exec", - allow_single_file = True, - ), - }, - toolchains = [_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE], -) - -def bootclasspath(name, **kwargs): - _bootclasspath( - name = name, - output_jar = name + ".jar", - **kwargs - ) +bootclasspath = _bootclasspath diff --git a/toolchains/extensions.bzl b/toolchains/extensions.bzl new file mode 100644 index 00000000..1db0cbc9 --- /dev/null +++ b/toolchains/extensions.bzl @@ -0,0 +1,63 @@ +"""Module extensions for local and remote java repositories""" + +load(":local_java_repository.bzl", "local_java_repository") +load(":remote_java_repository.bzl", "remote_java_repository") + +visibility(["//java"]) + +def _java_repository_impl(mctx): + for mod in mctx.modules: + if not mod.is_root: + fail( + """This module extension may only be used in the root module. {name} + must set `dev_dependency` = True on it's usage of this extension, + if {name} can be dependency of other modules.""".format(name = mod.name), + ) + for local in mod.tags.local: + local_java_repository( + local.name, + java_home = local.java_home, + version = local.version, + build_file = local.build_file, + build_file_content = local.build_file_content, + ) + for remote in mod.tags.remote: + remote_java_repository( + remote.name, + remote.version, + target_compatible_with = remote.target_compatible_with, + prefix = remote.prefix, + remote_file_urls = remote.remote_file_urls, + remote_file_integrity = remote.remote_file_integrity, + sha256 = remote.sha256, + strip_prefix = remote.strip_prefix, + urls = remote.urls, + ) + +_local = tag_class(attrs = { + "name": attr.string(mandatory = True), + "build_file": attr.label(default = None), + "build_file_content": attr.string(default = ""), + "java_home": attr.string(default = ""), + "version": attr.string(default = ""), +}) + +_remote = tag_class(attrs = { + "name": attr.string(mandatory = True), + "version": attr.string(mandatory = True), + "urls": attr.string_list(mandatory = True), + "prefix": attr.string(default = ""), + "remote_file_urls": attr.string_list_dict(default = {}), + "remote_file_integrity": attr.string_dict(default = {}), + "sha256": attr.string(default = ""), + "strip_prefix": attr.string(default = ""), + "target_compatible_with": attr.string_list(default = []), +}) + +java_repository = module_extension( + _java_repository_impl, + tag_classes = { + "local": _local, + "remote": _remote, + }, +) diff --git a/toolchains/java_toolchain_alias.bzl b/toolchains/java_toolchain_alias.bzl index 8d8a7e47..54973c0f 100644 --- a/toolchains/java_toolchain_alias.bzl +++ b/toolchains/java_toolchain_alias.bzl @@ -26,6 +26,8 @@ def _java_runtime_alias(ctx): platform_common.TemplateVariableInfo({ "JAVA": str(toolchain.java_executable_exec_path), "JAVABASE": str(toolchain.java_home), + "JAVA_ROOTPATH": str(toolchain.java_executable_runfiles_path), + "JAVABASE_ROOTPATH": str(toolchain.java_home_runfiles_path), }), # See b/65239471 for related discussion of handling toolchain runfiles/data. DefaultInfo( @@ -85,6 +87,9 @@ java_runtime_version_alias = rule( toolchains = ["@bazel_tools//tools/jdk:runtime_toolchain_type"], attrs = { "runtime_version": attr.string(mandatory = True), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), }, cfg = _java_runtime_transition, ) diff --git a/toolchains/jdk_build_file.bzl b/toolchains/jdk_build_file.bzl index 1e08f376..c0ed1608 100644 --- a/toolchains/jdk_build_file.bzl +++ b/toolchains/jdk_build_file.bzl @@ -14,7 +14,7 @@ """A templated BUILD file for Java repositories.""" -JDK_BUILD_TEMPLATE = """load("@rules_java//java:defs.bzl", "java_runtime") +JDK_BUILD_TEMPLATE = """load("@rules_java//java/toolchains:java_runtime.bzl", "java_runtime") package(default_visibility = ["//visibility:public"]) diff --git a/toolchains/local_java_repository.bzl b/toolchains/local_java_repository.bzl index ae5f82f8..769c2d19 100644 --- a/toolchains/local_java_repository.bzl +++ b/toolchains/local_java_repository.bzl @@ -14,7 +14,7 @@ """Rules for importing a local JDK.""" -load("//java:defs.bzl", "java_runtime") +load("//java/toolchains:java_runtime.bzl", "java_runtime") load(":default_java_toolchain.bzl", "default_java_toolchain") def _detect_java_version(repository_ctx, java_bin): @@ -238,7 +238,7 @@ local_java_runtime( repository_ctx.file( "BUILD.bazel", 'load("@rules_java//toolchains:local_java_repository.bzl", "local_java_runtime")\n' + - 'load("@local_config_platform//:constraints.bzl", "HOST_CONSTRAINTS")\n' + + 'load("@platforms//host:constraints.bzl", "HOST_CONSTRAINTS")\n' + build_file + local_java_runtime_macro, ) diff --git a/toolchains/utf8_environment.bzl b/toolchains/utf8_environment.bzl new file mode 100644 index 00000000..5b4d5545 --- /dev/null +++ b/toolchains/utf8_environment.bzl @@ -0,0 +1,48 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Determines the environment required for Java actions to support UTF-8. +""" + +visibility("private") + +Utf8EnvironmentInfo = provider( + doc = "The environment required for Java actions to support UTF-8.", + fields = { + "environment": "The environment to use for Java actions to support UTF-8.", + }, +) + +# The default UTF-8 locale on all recent Linux distributions. It is also available in Cygwin and +# MSYS2, but doesn't matter for determining the JVM's platform encoding on Windows, which always +# uses the active code page. +_DEFAULT_UTF8_ENVIRONMENT = Utf8EnvironmentInfo(environment = {"LC_CTYPE": "C.UTF-8"}) + +# macOS doesn't have the C.UTF-8 locale, but en_US.UTF-8 is available and works the same way. +_MACOS_UTF8_ENVIRONMENT = Utf8EnvironmentInfo(environment = {"LC_CTYPE": "en_US.UTF-8"}) + +def _utf8_environment_impl(ctx): + if ctx.target_platform_has_constraint(ctx.attr._macos_constraint[platform_common.ConstraintValueInfo]): + return _MACOS_UTF8_ENVIRONMENT + else: + return _DEFAULT_UTF8_ENVIRONMENT + +utf8_environment = rule( + _utf8_environment_impl, + attrs = { + "_macos_constraint": attr.label(default = "@platforms//os:macos"), + }, + doc = "Returns a suitable environment for Java actions to support UTF-8.", +)