diff --git a/.bazel_checkout/__README b/.bazel_checkout/__README new file mode 100644 index 00000000..6de50f30 --- /dev/null +++ b/.bazel_checkout/__README @@ -0,0 +1,4 @@ +This directory exists just so we can clone Bazel sources and run tests on presubmit + +At some point, we should migrate the tests we care about, out of Bazel and into +rules_java. \ No newline at end of file diff --git a/.bazel_checkout/setup.sh b/.bazel_checkout/setup.sh new file mode 100644 index 00000000..f1981a66 --- /dev/null +++ b/.bazel_checkout/setup.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +set -e +set -x + +FAKE_BCR_ROOT=$(mktemp -d --tmpdir fake-bcr.XXX) +FAKE_RULES_JAVA_ROOT=${FAKE_BCR_ROOT}/modules/rules_java +FAKE_MODULE_VERSION=9999 +FAKE_MODULE_ROOT=${FAKE_RULES_JAVA_ROOT}/${FAKE_MODULE_VERSION} +FAKE_ARCHIVE=${FAKE_MODULE_ROOT}/rules_java.tar.gz +mkdir -p ${FAKE_MODULE_ROOT} + +# relying on the line number is not great, but :shrugs: +sed -e "3 c version = \"${FAKE_MODULE_VERSION}\"," ../MODULE.bazel > ${FAKE_MODULE_ROOT}/MODULE.bazel + +tar zcf ${FAKE_ARCHIVE} ../ +RULES_JAVA_INTEGRITY_SHA256=`cat ${FAKE_ARCHIVE} | openssl dgst -sha256 -binary | base64` +cat << EOF > ${FAKE_MODULE_ROOT}/source.json +{ + "integrity": "sha256-${RULES_JAVA_INTEGRITY_SHA256}", + "strip_prefix": "", + "url": "file://${FAKE_ARCHIVE}" +} +EOF + +# fetch and setup bazel sources +git init +git remote add origin https://github.com/bazelbuild/bazel.git +git pull origin master +sed -i.bak -e 's/^# android_sdk_repository/android_sdk_repository/' \ + -e 's/^# android_ndk_repository/android_ndk_repository/' \ + WORKSPACE.bzlmod +rm -f WORKSPACE.bzlmod.bak +rm -rf $HOME/bazeltest +mkdir $HOME/bazeltest + +echo "common --registry=https://bcr.bazel.build" >> .bazelrc +echo "common --registry=file://${FAKE_BCR_ROOT}" >> .bazelrc +echo "add_to_bazelrc \"common --registry=https://bcr.bazel.build\"" >> src/test/shell/testenv.sh.tmpl +echo "add_to_bazelrc \"common --registry=file://${FAKE_BCR_ROOT}\"" >> src/test/shell/testenv.sh.tmpl + +SED_CMD="s/bazel_dep(name = \"rules_java\".*/bazel_dep(name = \"rules_java\", version = \"${FAKE_MODULE_VERSION}\")/" +sed -i "${SED_CMD}" MODULE.bazel +sed -i "${SED_CMD}" src/MODULE.tools + +BAZEL_QUIET_MODE_ARGS="--ui_event_filters=error,fail" + +bazel run ${BAZEL_QUIET_MODE_ARGS} //src/test/tools/bzlmod:update_default_lock_file -- \ + --registry="https://bcr.bazel.build" --registry="file://${FAKE_BCR_ROOT}" ${BAZEL_QUIET_MODE_ARGS} +bazel mod deps --lockfile_mode=update +# populate repo cache so tests don't need to access network +bazel fetch --config=ci-linux --all ${BAZEL_QUIET_MODE_ARGS} diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index cd906513..9c186288 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -1,55 +1,90 @@ --- build_targets: &build_targets - "//..." - - "@remote_java_tools//java_tools/..." - # TODO: Look into broken targets in //toolchains - - "-//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" -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/..." - -buildifier: - version: latest - warnings: "all" + - "//: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", "ubuntu2404", "macos", "macos_arm64", "windows"] + bazel: ["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 - macos: + test_targets: *test_targets +# Bazel 8.x + build_and_test_bazel8: + name: "Bazel 8.x" + bazel: "8.x" + platform: ${{ all_platforms }} build_targets: *build_targets - windows: - build_targets: *build_targets - ubuntu2004_head: - bazel: last_green - platform: ubuntu2004 - build_targets: *build_targets - macos_head: - bazel: last_green - platform: macos - build_targets: *build_targets - windows_head: - bazel: last_green - platform: windows - build_targets: *build_targets - ubuntu2004_bzlmod: - bazel: last_green - platform: ubuntu2004 - build_flags: - - "--config=bzlmod" - build_targets: *build_targets_bzlmod - macos_bzlmod: - bazel: last_green - platform: macos - build_flags: - - "--config=bzlmod" - build_targets: *build_targets_bzlmod - windows_bzlmod: - bazel: last_green - platform: windows - build_flags: - - "--config=bzlmod" - build_targets: *build_targets_bzlmod + test_targets: *test_targets + test_flags: + - "--test_tag_filters=-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: "ubuntu2404" + 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 4fadfce8..12d684fc 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1 +1,22 @@ -build:bzlmod --experimental_enable_bzlmod +common --incompatible_disallow_empty_glob + +# 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 293346f8..00000000 --- a/.bazelversion +++ /dev/null @@ -1 +0,0 @@ -7.0.0-pre.20230710.5 diff --git a/.bcr/metadata.template.json b/.bcr/metadata.template.json index 41ba4c2b..8ef2f6a7 100644 --- a/.bcr/metadata.template.json +++ b/.bcr/metadata.template.json @@ -1,6 +1,13 @@ { "homepage": "https://github.com/bazelbuild/rules_java", - "maintainers": [], + "maintainers": [ + { + "email": "hvd@google.com", + "github": "hvadehra", + "name": "Hemanshu Vadehra", + "github_user_id": 2486163 + } + ], "versions": [], "yanked_versions": {}, "repository": [ diff --git a/.bcr/presubmit.yml b/.bcr/presubmit.yml index 52f713e6..c26fb618 100644 --- a/.bcr/presubmit.yml +++ b/.bcr/presubmit.yml @@ -1,8 +1,17 @@ matrix: - platform: ["centos7", "debian10", "macos", "ubuntu2004", "windows"] + platform: + - rockylinux8 + - debian10 + - macos + - ubuntu2004 + - windows + bazel: + - 8.x + - 9.x tasks: verify_build_targets: - name: "Verify build targets" + name: "Verify build targets w/ Bazel {bazel}" platform: ${{ platform }} + bazel: ${{ bazel }} build_targets: - - "@rules_java//java/..." + - "@rules_java//java/..." diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md new file mode 100644 index 00000000..088ed071 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release.md @@ -0,0 +1,18 @@ +--- +name: 'Release tracker' +about: Use this template to request for a new rules_java release +title: 'Release: rules_java X.Y.Z' +labels: ['release','P1'] +assignees: + - iancha1992 + - sgowroji +--- + +**Link to relevant issue and/or commit:** + +**Other details:** + + + + +cc @bazelbuild/triage 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 3dd89065..13d44ed5 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,16 +1,29 @@ module( name = "rules_java", - version = "6.5.0", - compatibility_level = 1, + version = "0.0.0", + bazel_compatibility = [">=8.0.0"], ) -bazel_dep(name = "platforms", version = "0.0.4") -bazel_dep(name = "rules_cc", version = "0.0.2") -bazel_dep(name = "bazel_skylib", version = "1.2.0") +bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_cc", version = "0.2.25") +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") @@ -19,6 +32,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") @@ -33,6 +47,15 @@ register_toolchains( # Declare all remote jdk toolchain config repos JDKS = { + # Must match JDK repos defined in remote_jdk8_repos() + "8": [ + "linux", + "linux_aarch64", + "linux_s390x", + "macos", + "macos_aarch64", + "windows", + ], # Must match JDK repos defined in remote_jdk11_repos() "11": [ "linux", @@ -55,17 +78,32 @@ JDKS = { "win", "win_arm64", ], - # Must match JDK repos defined in remote_jdk20_repos() - "20": [ + # Must match JDK repos defined in remote_jdk21_repos() + "21": [ + "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", "win", + "win_arm64", ], } -REMOTE_JDK_REPOS = [("remotejdk" + version + "_" + platform) for version in JDKS for platform in JDKS[version]] +REMOTE_JDK_REPOS = [(("remote_jdk" if version == "8" else "remotejdk") + version + "_" + platform) for version in JDKS for platform in JDKS[version]] [use_repo( toolchains, @@ -74,5 +112,21 @@ REMOTE_JDK_REPOS = [("remotejdk" + version + "_" + platform) for version in JDKS [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.5.1", dev_dependency = True) +bazel_dep(name = "rules_pkg", version = "0.9.1", dev_dependency = True) +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-UlGXv0mAHt6sQtadxJ97TeqrJVIaKP7X+M8SrNpqM7s=", + strip_prefix = "rules_testing-2529012de853e1147460363c04dbe911c1025a6b", + urls = ["https://github.com/bazelbuild/rules_testing/archive/2529012de853e1147460363c04dbe911c1025a6b.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 059716f7..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 = "af87959afe497dc8dfd4c6cb66e1279cb98ccc84284619ebfec27d9c09a903de", + sha256 = "bc283cdfcd526a52c3201279cda4bc298652efa898b10b4db0837dc51652756f", urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.2.0/bazel-skylib-1.2.0.tar.gz", - "https://github.com/bazelbuild/bazel-skylib/releases/download/1.2.0/bazel-skylib-1.2.0.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", ], ) @@ -17,13 +17,60 @@ bazel_skylib_workspace() http_archive( name = "rules_pkg", - sha256 = "a89e203d3cf264e564fcb96b6e06dd70bc0557356eb48400ce4b5d97c2c3720d", + sha256 = "8f9ee2dc10c1ae514ee599a8b42ed99fa262b757058f65ad3c384289ff70c4b8", urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/rules_pkg/releases/download/0.5.1/rules_pkg-0.5.1.tar.gz", - "https://github.com/bazelbuild/rules_pkg/releases/download/0.5.1/rules_pkg-0.5.1.tar.gz", + "https://mirror.bazel.build/github.com/bazelbuild/rules_pkg/releases/download/0.9.1/rules_pkg-0.9.1.tar.gz", + "https://github.com/bazelbuild/rules_pkg/releases/download/0.9.1/rules_pkg-0.9.1.tar.gz", ], ) 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 d136ca12..0aca7dc3 100644 --- a/distro/BUILD.bazel +++ b/distro/BUILD.bazel @@ -1,14 +1,14 @@ -load("@rules_pkg//:pkg.bzl", "pkg_tar") -load("@rules_pkg//releasing:defs.bzl", "print_rel_notes") -load("//java:defs.bzl", "version") +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") +load(":relnotes.bzl", "print_rel_notes") package( + default_applicable_licenses = ["@rules_java//:license"], default_visibility = ["//visibility:private"], ) # Build the artifact to put on the github release page. pkg_tar( - name = "rules_java-%s" % version, + name = "rules_java-%s" % module_version(), srcs = ["//:distribution"], extension = "tar.gz", # It is all source code, so make it read-only. @@ -21,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", - version = version, + archive = ":rules_java-%s" % module_version(), + version = module_version(), ) diff --git a/distro/README.md b/distro/README.md index 428f4eb1..7cac8923 100644 --- a/distro/README.md +++ b/distro/README.md @@ -1,7 +1,6 @@ # Releasing rules_java -1. Update version in [java/defs.bzl](/java/defs.bzl), - [MODULE.bazel](/MODULE.bazel) and merge it +1. Update version in [MODULE.bazel](/MODULE.bazel) and merge it 2. Build the release running `bazel build //distro:rules_java-{version}` 3. Prepare release notes running `bazel build //distro:relnotes` 4. Create a new release on GitHub 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 > $@ < $@ +while read -r config; do + TMP_FILE=$$(mktemp -q /tmp/remotejdk.XXXXXX) + IFS=\\| read -r name version urls strip_prefix target_compatible_with primary_url patch_cmds <<< "$$config" + echo "fetching: $$primary_url to $$TMP_FILE" > /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\\"," + echo " target_compatible_with = $$target_compatible_with," + echo " sha256 = \\"$$sha256\\"," + echo " strip_prefix = \\"$$strip_prefix\\"," + echo " urls = $$urls," + echo " version = \\"$$version\\"," + if [[ "$$patch_cmds" != "[]" ]]; then + echo " patch_cmds = $$patch_cmds," + fi + echo ")," + rm -f "$$TMP_FILE" +done <<< '{configs}' >> $@ + """.format(configs = "\n".join([ + "|".join([ + config.name, + config.version, + json.encode(config.urls), + config.strip_prefix, + json.encode(config.target_compatible_with), + config.urls[0], + json.encode(config.patch_cmds), + ]) + for config in FLAT_CONFIGS + ])), + tags = [ + "local", + "manual", + ], + 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 new file mode 100644 index 00000000..813f5f13 --- /dev/null +++ b/java/bazel/repositories_util.bzl @@ -0,0 +1,210 @@ +# 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. +"""Helper functions to register remote jdk repos""" + +visibility(["//test"]) + +_RELEASE_CONFIGS = { + "8": { + "zulu": { + "release": "8.96.0.19-ca-jdk8.0.502", + "platforms": { + "linux": ["aarch64", "x86_64"], + "macos": ["aarch64", "x86_64"], + "windows": ["x86_64"], + }, + }, + "adoptopenjdk": { + "release": "8u292-b10", + "platforms": { + "linux": ["s390x"], + }, + }, + }, + "11": { + "zulu": { + "release": "11.90.19-ca-jdk11.0.32", + "platforms": { + "linux": ["aarch64", "x86_64"], + "macos": ["aarch64", "x86_64"], + "windows": ["x86_64"], + }, + }, + "adoptium": { + "release": "11.0.28+6", + "platforms": { + "linux": ["ppc64le", "s390x"], + }, + }, + "microsoft": { + "release": "11.0.28", + "platforms": { + "windows": ["arm64"], + }, + }, + }, + "17": { + "zulu": { + "release": "17.68.17-ca-jdk17.0.20", + "platforms": { + "linux": ["aarch64", "x86_64"], + "macos": ["aarch64", "x86_64"], + "windows": ["arm64", "x86_64"], + }, + }, + "adoptium": { + "release": "17.0.16+8", + "platforms": { + "linux": ["ppc64le", "s390x"], + }, + }, + }, + "21": { + "zulu": { + "release": "21.52.15-ca-jdk21.0.12", + "platforms": { + "linux": ["aarch64", "x86_64"], + "macos": ["aarch64", "x86_64"], + "windows": ["arm64", "x86_64"], + }, + }, + "adoptium": { + "release": "21.0.8+9", + "platforms": { + "linux": ["ppc64le", "riscv64", "s390x"], + }, + }, + }, + "25": { + "zulu": { + "release": "25.36.15-ca-jdk25.0.4", + "platforms": { + "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.28+6", +} + +def _name_for_remote_jdk(version, os, cpu): + prefix = "remote_jdk" if version == "8" else "remotejdk" + os_part = "win" if (os == "windows" and version != "8") else os + if cpu == "x86_64": + suffix = "" + else: + suffix = "_" + cpu + return prefix + version + "_" + os_part + suffix + +def _zulu_remote_jdk_repo(os, cpu, release): + arch = cpu + if cpu == "x86_64": + arch = "x64" + platform = os + ext = ".tar.gz" + if os == "macos": + platform = "macosx" + elif os == "windows": + ext = ".zip" + platform = "win" + arch = "aarch64" if arch == "arm64" else arch + archive_name = "zulu" + release + "-" + platform + "_" + arch + primary_url = "cdn.azul.com/zulu/bin/" + archive_name + ext + urls = [ + "https://" + primary_url, + "https://mirror.bazel.build/" + primary_url, + ] + patch_cmds = [] + if os == "macos": + # Recent Zulu macOS archives are application bundles: the JDK lives + # under Contents/Home and the top-level bin/lib/... symlinks that + # earlier releases shipped are gone. strip_prefix stays at the + # archive root (uniform with the other platforms); recreate the + # top-level symlinks so the shared JDK BUILD template still finds + # bin/, lib/, etc. + patch_cmds = ["ln -s Contents/Home/* ."] + return urls, archive_name, patch_cmds + +def _adoptium_linux_remote_jdk_repo(version, cpu, release): + os = "linux" + arch = cpu + 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 = [ + "https://" + primary_url, + "https://mirror.bazel.build/" + primary_url, + ] + return urls, "jdk-" + release + +def _microsoft_windows_arm64_remote_jdk_repo(release): + primary_url = "aka.ms/download-jdk/microsoft-jdk-" + release + "-windows-aarch64.zip" + urls = [ + "https://" + primary_url, + "https://mirror.bazel.build/" + primary_url, + ] + return urls, "" + +def _adoptopenjdk_remote_jdk_repo(version, os, cpu, release): + archive = "OpenJDK" + version + "U-jdk_" + cpu + "_" + os + "_hotspot_" + release.replace("-", "") + ".tar.gz" + primary_url = "github.com/AdoptOpenJDK/openjdk" + version + "-binaries/releases/download/jdk" + release + "/" + archive + urls = [ + "https://" + primary_url, + "https://mirror.bazel.build/" + primary_url, + ] + return urls, "jdk" + release + +def _flatten_configs(): + result = [] + for version, all_for_version in _RELEASE_CONFIGS.items(): + for distrib, distrib_cfg in all_for_version.items(): + release = distrib_cfg["release"] + for os, cpus in distrib_cfg["platforms"].items(): + for cpu in cpus: + name = _name_for_remote_jdk(version, os, cpu) + patch_cmds = [] + if distrib == "zulu": + urls, strip_prefix, patch_cmds = _zulu_remote_jdk_repo(os, cpu, release) + elif distrib == "adoptium": + if os != "linux": + fail("adoptium jdk configured but not linux") + urls, strip_prefix = _adoptium_linux_remote_jdk_repo(version, cpu, release) + elif distrib == "microsoft": + if os != "windows" or cpu != "arm64": + fail("only windows_arm64 config for microsoft is expected") + urls, strip_prefix = _microsoft_windows_arm64_remote_jdk_repo(release) + elif distrib == "adoptopenjdk": + urls, strip_prefix = _adoptopenjdk_remote_jdk_repo(version, os, cpu, release) + else: + fail("unexpected distribution:", distrib) + result.append(struct( + name = name, + version = version, + urls = urls, + strip_prefix = _STRIP_PREFIX_OVERRIDES.get(name, strip_prefix), + target_compatible_with = ["@platforms//os:" + os, "@platforms//cpu:" + cpu], + patch_cmds = patch_cmds, + )) + return result + +FLAT_CONFIGS = _flatten_configs() diff --git a/java/bazel/rules/BUILD.bazel b/java/bazel/rules/BUILD.bazel new file mode 100644 index 00000000..108a3f9c --- /dev/null +++ b/java/bazel/rules/BUILD.bazel @@ -0,0 +1,68 @@ +# 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. +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +package(default_applicable_licenses = ["@rules_java//:license"]) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//java:__pkg__"], +) + +bzl_library( + name = "rules", + 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( + name = "for_bazel_tests", + 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..998ac898 --- /dev/null +++ b/java/bazel/rules/bazel_java_binary.bzl @@ -0,0 +1,487 @@ +# 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_classes = helper.test_classes(ctx) + if not test_classes: + fail("cannot determine test class. You might want to add a " + + "'test_class' or 'test_classes' attribute.") + jvm_flags.extend([ + "-ea", + "-D{prop_name}={prop_value}".format( + prop_name = semantics.TEST_SUITE_PROPERTY_NAME, + prop_value = helper.shell_escape(",".join(test_classes)), + ), + ]) + + 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, + exclude_build_data = ctx.attr.exclude_build_data, + 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): + java_runtime_toolchain = semantics.find_java_runtime_toolchain(ctx) + 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(java_runtime_toolchain.java_home_runfiles_path, format = "jar_bin_path=%s/bin/jar.exe") + launch_info.add(java_runtime_toolchain.version, format = "java_version=%s") + + # 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, + { + "exclude_build_data": attr.bool( + default = False, + doc = """ +Whether to omit the build-data.properties file from the +*_deploy.jar output. + """, + ), + "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..8dddd4a3 --- /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 new file mode 100644 index 00000000..096f77b8 --- /dev/null +++ b/java/common/BUILD @@ -0,0 +1,61 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +package( + default_applicable_licenses = ["@rules_java//:license"], + default_visibility = ["//visibility:public"], +) + +licenses(["notice"]) + +filegroup( + name = "srcs", + srcs = glob(["**"]) + [ + "//java/common/rules:srcs", + ], + visibility = ["//java:__pkg__"], +) + +bzl_library( + name = "common", + 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:action_names_bzl", + "@rules_cc//cc/common:cc_helper_bzl", + ], +) + +bzl_library( + name = "proguard_spec_info_bzl", + srcs = ["proguard_spec_info.bzl"], + visibility = ["//visibility:public"], +) + +filegroup( + name = "for_bazel_tests", + testonly = 1, + 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 new file mode 100644 index 00000000..ed55bdb7 --- /dev/null +++ b/java/common/java_common.bzl @@ -0,0 +1,18 @@ +# 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. +"""java_common module""" + +load("@compatibility_proxy//:proxy.bzl", _java_common = "java_common") + +java_common = _java_common diff --git a/java/common/java_info.bzl b/java/common/java_info.bzl new file mode 100644 index 00000000..2748f6d6 --- /dev/null +++ b/java/common/java_info.bzl @@ -0,0 +1,18 @@ +# 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. +"""JavaInfo provider""" + +load("@compatibility_proxy//:proxy.bzl", _JavaInfo = "JavaInfo") + +JavaInfo = _JavaInfo diff --git a/java/common/java_plugin_info.bzl b/java/common/java_plugin_info.bzl new file mode 100644 index 00000000..b43dfd54 --- /dev/null +++ b/java/common/java_plugin_info.bzl @@ -0,0 +1,18 @@ +# 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. +"""JavaPluginInfo provider""" + +load("@compatibility_proxy//:proxy.bzl", _JavaPluginInfo = "JavaPluginInfo") + +JavaPluginInfo = _JavaPluginInfo diff --git a/java/common/java_semantics.bzl b/java/common/java_semantics.bzl new file mode 100644 index 00000000..c85ec048 --- /dev/null +++ b/java/common/java_semantics.bzl @@ -0,0 +1,132 @@ +# 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 + TEST_SUITE_PROPERTY_NAME = "bazel.test_suite", +) diff --git a/java/common/proguard_spec_info.bzl b/java/common/proguard_spec_info.bzl new file mode 100644 index 00000000..bf006e80 --- /dev/null +++ b/java/common/proguard_spec_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. +"""ProguardSpecInfo provider""" + +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..fa181d26 --- /dev/null +++ b/java/common/rules/BUILD @@ -0,0 +1,102 @@ +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_helper_bzl", + ":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..d07bb5e3 --- /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, linter.tools] + 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, linter.tools] + 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..ed1eb2f7 --- /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", + "@rules_cc//cc:action_names_bzl", # TODO: remove after https://github.com/bazelbuild/rules_cc/issues/913 is fixed + "@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..aa829d1f --- /dev/null +++ b/java/common/rules/impl/bazel_java_import_impl.bzl @@ -0,0 +1,193 @@ +# 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) + + merged_java_info = java_common.merge(all_deps) + + # import_deps_check is always run to generate the jdeps proto for downstream compile actions, + # but may be silenced. + has_incomplete_deps = "incomplete-deps" in ctx.attr.tags + if skip_incomplete_deps_check or has_incomplete_deps: + checking_mode = "silence" + else: + checking_mode = "error" + jdeps_artifact = import_deps_check( + ctx, + collected_jars, + merged_java_info.compile_jars, + merged_java_info.transitive_compile_time_jars, + "java_import", + checking_mode = checking_mode, + ) + + 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], + compile_jdeps = jdeps_artifact, + 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 and checking_mode != "silence": + 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..f5a950f7 --- /dev/null +++ b/java/common/rules/impl/import_deps_check.bzl @@ -0,0 +1,87 @@ +# 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, + checking_mode = "error"): + """ + 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. + checking_mode: (String) One of "error", "warning" or "silence". + + 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, format = "--checking_mode=%s") + args.add("--jdeps_output", jdeps_output) + + # ctx.label can start with an @ and must not be understood as a flagfile. + args.add(ctx.label, format = "--rule_label=%s") + + 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..41cc9bf6 --- /dev/null +++ b/java/common/rules/impl/java_binary_deploy_jar.bzl @@ -0,0 +1,249 @@ +# 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 _map_hermetic_resource(f): + return "%s:%s" % (f.path, f.short_path) + +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 = [], + exclude_build_data = False): + """ 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 + exclude_build_data: (bool) Whether to omit build-data.properties + """ + 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 + if exclude_build_data and ctx.attr.stamp == 1: + fail("Enabling stamping has no effect with exclude_build_data enabled") + + build_info_files = [] if exclude_build_data else 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, + classpath_resources, + runtime_classpath, + manifest_lines, + build_info_files, + build_target, + output = ctx.outputs.deployjar, + exclude_build_data = exclude_build_data, + 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, + classpath_resources, + runtime_classpath, + manifest_lines, + build_info_files, + build_target, + output = ctx.outputs.unstrippeddeployjar, + exclude_build_data = exclude_build_data, + 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, + 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 = [], + exclude_build_data = False): + """ 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 + 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 + exclude_build_data: (bool) Whether to omit build-data.properties + """ + input_files = [] + input_files.extend(build_info_files) + + transitive_input_files = [ + 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) + if exclude_build_data: + args.add("--exclude_build_data") + else: + 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_runfiles_path + 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, + map_each = _map_hermetic_resource, + ) + 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, + execution_requirements = { + "supports-path-mapping": "1", + }, + 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..6308ec32 --- /dev/null +++ b/java/common/rules/impl/java_binary_impl.bzl @@ -0,0 +1,516 @@ +# 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("@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", + "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 is_test_rule_class: + if hasattr(ctx.attr, "test_classes") and ctx.attr.test_classes: + fail("in %s: 'test_classes' is not allowed on java_binary" % ctx.label) + if hasattr(ctx.attr, "non_test_srcs") and ctx.attr.non_test_srcs: + fail("in %s: 'non_test_srcs' is not allowed on java_binary" % ctx.label) + + srcs = ctx.files.srcs + [src for src in getattr(ctx.files, "non_test_srcs", []) if src not in ctx.files.srcs] + if not 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 = 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 + "/" + separator = ";" if helper.is_target_platform_windows(ctx) else ":" + jvm_flags.append("-Djava.library.path=%s" % ( + separator.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] + + 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]), + ) + +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, + 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 _collect_native_deps_dirs(libraries): + result = {} + for library_to_link in libraries.to_list(): + candidate = None + if library_to_link.interface_library: + candidate = library_to_link.interface_library + elif library_to_link.dynamic_library: + candidate = library_to_link.dynamic_library + if candidate: + ext = "." + candidate.extension + if ext in cc_helper.extensions.INTERFACE_SHARED_LIBRARY: + continue + parent_dir = candidate.short_path[:-(1 + len(candidate.basename))] + result[parent_dir] = None + return result.keys() + +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..c7fb8693 --- /dev/null +++ b/java/common/rules/impl/java_helper.bzl @@ -0,0 +1,312 @@ +# 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 _canonical_class_from_target_name(ctx): + return _loading_phase_helper.full_classname(helper.get_relative(ctx.label.package, ctx.label.name)) + +def _primary_class(ctx): + if ctx.attr.srcs: + main = ctx.label.name + ".java" + for src in _test_srcs(ctx): + if src.basename == main: + return _loading_phase_helper.full_classname(_strip_extension(src)) + return _canonical_class_from_target_name(ctx) + +def _test_srcs(ctx): + """Returns ctx.files.srcs excluding any files listed in non_test_srcs.""" + if not hasattr(ctx.files, "non_test_srcs") or not ctx.files.non_test_srcs: + return ctx.files.srcs + non_test_srcs = {src: None for src in ctx.files.non_test_srcs} + return [src for src in ctx.files.srcs if src not in non_test_srcs] + +def _strip_extension(file): + return file.dirname + "/" + ( + file.basename[:-(1 + len(file.extension))] if file.extension else file.basename + ) + +def _test_classes(ctx): + has_test_class = hasattr(ctx.attr, "test_class") and bool(ctx.attr.test_class) + has_test_classes = hasattr(ctx.attr, "test_classes") and bool(ctx.attr.test_classes) + if has_test_class and has_test_classes: + fail("in %s: cannot specify both 'test_class' and 'test_classes'" % ctx.label) + if has_test_class: + return [ctx.attr.test_class] + if has_test_classes: + return ctx.attr.test_classes + if ctx.attr.srcs: + classes = [] + for src in _test_srcs(ctx): + if src.extension == "java": + cls = _loading_phase_helper.full_classname(_strip_extension(src)) + if cls == None: + return [] + classes.append(cls) + if classes: + return classes + canonical = _canonical_class_from_target_name(ctx) + return [canonical] if canonical != None else [] + +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, + test_classes = _test_classes, + 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, + java_segments = _loading_phase_helper.java_segments, + java_path = _loading_phase_helper.java_path, + java_root = _loading_phase_helper.java_root, + full_classname = _loading_phase_helper.full_classname, +) 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..8c0e00ed --- /dev/null +++ b/java/common/rules/java_binary.bzl @@ -0,0 +1,396 @@ +# 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, + the test classes will be inferred from srcs if present, or + otherwise inferred using the target's name and its + source-root-relative path (which is discouraged; when srcs is + empty, prefer setting test_class explicitly). 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. +

+ """, + ), + "test_classes": attr.string_list( + doc = """ +The Java classes to be loaded by the test runner. +

+ By default, test classes are automatically inferred from source file names in + srcs. Setting test_classes is generally discouraged: + prefer listing test sources directly in srcs and omitting + test_classes. In particular, specifying test_classes + when srcs is non-empty is discouraged, and placing test sources in + a separate java_library with test_classes risks + silently skipping newly added test files unless they are also manually listed + in test_classes. +

+

+ Specify test_classes only when srcs is empty and the + test target runs multiple pre-compiled test classes from deps or + runtime_deps. +

+ """, + ), + "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..0c36be11 --- /dev/null +++ b/java/common/rules/java_helper.bzl @@ -0,0 +1,241 @@ +# 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_segment_index(path): + if path.startswith("/"): + fail("path must not be absolute: '%s'" % path) + segments = path.split("/") + if path.startswith("third_party/java/"): + # For third_party/java, try skipping past the first two segments and looking for a nested + # source root, to relativize e.g. third_party/java/foo/java/resource into 'resource', + # not 'foo/java/resource'. + idx = _java_segment_index_impl(segments[2:]) + if idx >= 0: + return idx + 2 + return _java_segment_index_impl(segments) + +def _java_segment_index_impl(segments): + 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 -1 + 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 root_idx + +def _java_segments(path): + idx = _java_segment_index(path) + return path.split("/")[(idx + 1):] if idx >= 0 else None + +def _java_path(path): + java_segments = _java_segments(path) + return "/".join(java_segments) if java_segments != None else None + +def _java_root(path): + idx = _java_segment_index(path) + return "/".join(path.split("/")[:(idx + 1)]) if idx >= 0 else None + +def _full_classname(path): + java_segments = _java_segments(path) + return ".".join(java_segments) if java_segments != None else None + +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, + java_path = _java_path, + java_root = _java_root, + full_classname = _full_classname, +) 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..e36c1b70 --- /dev/null +++ b/java/common/rules/java_single_jar.bzl @@ -0,0 +1,194 @@ +# 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)) + all_inputs = depset(transitive = transitive_inputs) + inputs = all_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, all_inputs + +def _bazel_java_single_jar_impl(ctx): + inputs, all_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("shell") + 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, + execution_requirements = { + "supports-path-mapping": "1", + }, + 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 = all_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..12050839 --- /dev/null +++ b/java/common/rules/java_toolchain.bzl @@ -0,0 +1,620 @@ +# 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, + tools = depset(ctx.files.android_lint_tools), + data = depset(ctx.files.android_lint_data), + jvm_opts = depset([ctx.expand_location(opt, ctx.attr.android_lint_tools) 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 files available for label-expansion in android_lint_opts. + """, + ), + "android_lint_tools": 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 0f678368..2034bf45 100644 --- a/java/defs.bzl +++ b/java/defs.bzl @@ -13,142 +13,34 @@ # limitations under the License. """Starlark rules for building Java projects.""" -load("//java/private:native.bzl", "NativeJavaInfo", "NativeJavaPluginInfo", "native_java_common") - -# Do not touch: This line marks the end of loads; needed for PR importing. - -_MIGRATION_TAG = "__JAVA_RULES_MIGRATION_DO_NOT_USE_WILL_BREAK__" -version = "6.5.0" - -def _add_tags(attrs): - if "tags" in attrs and attrs["tags"] != None: - attrs["tags"] = attrs["tags"] + [_MIGRATION_TAG] - else: - attrs["tags"] = [_MIGRATION_TAG] - return attrs - -def java_binary(**attrs): - """Bazel java_binary rule. - - https://docs.bazel.build/versions/master/be/java.html#java_binary - - Args: - **attrs: Rule attributes - """ - - # buildifier: disable=native-java - native.java_binary(**_add_tags(attrs)) - -def java_import(**attrs): - """Bazel java_import rule. - - https://docs.bazel.build/versions/master/be/java.html#java_import - - Args: - **attrs: Rule attributes - """ - - # buildifier: disable=native-java - native.java_import(**_add_tags(attrs)) - -def java_library(**attrs): - """Bazel java_library rule. - - https://docs.bazel.build/versions/master/be/java.html#java_library - - Args: - **attrs: Rule attributes - """ - - # buildifier: disable=native-java - native.java_library(**_add_tags(attrs)) - -def java_lite_proto_library(**attrs): - """Bazel java_lite_proto_library rule. - - https://docs.bazel.build/versions/master/be/java.html#java_lite_proto_library - - Args: - **attrs: Rule attributes - """ - - # buildifier: disable=native-java - native.java_lite_proto_library(**_add_tags(attrs)) - -def java_proto_library(**attrs): - """Bazel java_proto_library rule. - - https://docs.bazel.build/versions/master/be/java.html#java_proto_library - - Args: - **attrs: Rule attributes - """ - - # buildifier: disable=native-java - native.java_proto_library(**_add_tags(attrs)) - -def java_test(**attrs): - """Bazel java_test rule. - - https://docs.bazel.build/versions/master/be/java.html#java_test - - Args: - **attrs: Rule attributes - """ - - # buildifier: disable=native-java - native.java_test(**_add_tags(attrs)) - -def java_package_configuration(**attrs): - """Bazel java_package_configuration rule. - - https://docs.bazel.build/versions/master/be/java.html#java_package_configuration - - Args: - **attrs: Rule attributes - """ - - # buildifier: disable=native-java - native.java_package_configuration(**_add_tags(attrs)) - -def java_plugin(**attrs): - """Bazel java_plugin rule. - - https://docs.bazel.build/versions/master/be/java.html#java_plugin - - Args: - **attrs: Rule attributes - """ - - # buildifier: disable=native-java - native.java_plugin(**_add_tags(attrs)) - -def java_runtime(**attrs): - """Bazel java_runtime rule. - - https://docs.bazel.build/versions/master/be/java.html#java_runtime - - Args: - **attrs: Rule attributes - """ - - # buildifier: disable=native-java - native.java_runtime(**_add_tags(attrs)) - -def java_toolchain(**attrs): - """Bazel java_toolchain rule. - - https://docs.bazel.build/versions/master/be/java.html#java_toolchain - - Args: - **attrs: Rule attributes - """ - - # buildifier: disable=native-java - native.java_toolchain(**_add_tags(attrs)) - -java_common = native_java_common - -JavaInfo = NativeJavaInfo - -JavaPluginInfo = NativeJavaPluginInfo +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") +load("//java:java_plugin.bzl", _java_plugin = "java_plugin") +load("//java:java_test.bzl", _java_test = "java_test") +load("//java/common:java_common.bzl", _java_common = "java_common") +load("//java/common:java_info.bzl", _JavaInfo = "JavaInfo") +load("//java/common:java_plugin_info.bzl", _JavaPluginInfo = "JavaPluginInfo") +load("//java/toolchains:java_package_configuration.bzl", _java_package_configuration = "java_package_configuration") +load("//java/toolchains:java_runtime.bzl", _java_runtime = "java_runtime") +load("//java/toolchains:java_toolchain.bzl", _java_toolchain = "java_toolchain") + +# Language rules + +java_binary = _java_binary +java_test = _java_test +java_library = _java_library +java_plugin = _java_plugin +java_import = _java_import + +# Toolchain rules + +java_runtime = _java_runtime +java_toolchain = _java_toolchain +java_package_configuration = _java_package_configuration + +# Modules and providers + +JavaInfo = _JavaInfo +JavaPluginInfo = _JavaPluginInfo +java_common = _java_common diff --git a/java/extensions.bzl b/java/extensions.bzl index 5e456eda..a791d179 100644 --- a/java/extensions.bzl +++ b/java/extensions.bzl @@ -13,13 +13,33 @@ # limitations under the License. """Module extensions for rules_java.""" -load("//java:repositories.bzl", "java_tools_repos", "local_jdk_repo", "remote_jdk11_repos", "remote_jdk17_repos", "remote_jdk20_repos") +load("@bazel_features//:features.bzl", "bazel_features") +load( + "//java:repositories.bzl", + "java_tools_repos", + "local_jdk_repo", + "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(_ctx): +def _toolchains_impl(module_ctx): java_tools_repos() local_jdk_repo() + remote_jdk8_repos() remote_jdk11_repos() remote_jdk17_repos() - remote_jdk20_repos() + remote_jdk21_repos() + remote_jdk25_repos() -toolchains = module_extension(implementation = _toolchains_impl) + if bazel_features.external_deps.extension_metadata_has_reproducible: + return module_ctx.extension_metadata(reproducible = True) + else: + 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 new file mode 100644 index 00000000..3c86ed21 --- /dev/null +++ b/java/java_binary.bzl @@ -0,0 +1,27 @@ +# 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. +"""java_binary rule""" + +load("@compatibility_proxy//:proxy.bzl", _java_binary = "java_binary") + +def java_binary(**attrs): + """Bazel java_binary rule. + + https://docs.bazel.build/versions/master/be/java.html#java_binary + + Args: + **attrs: Rule attributes + """ + + _java_binary(**attrs) diff --git a/java/java_import.bzl b/java/java_import.bzl new file mode 100644 index 00000000..7dfef834 --- /dev/null +++ b/java/java_import.bzl @@ -0,0 +1,27 @@ +# 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. +"""java_import rule""" + +load("@compatibility_proxy//:proxy.bzl", _java_import = "java_import") + +def java_import(**attrs): + """Bazel java_import rule. + + https://docs.bazel.build/versions/master/be/java.html#java_import + + Args: + **attrs: Rule attributes + """ + + _java_import(**attrs) diff --git a/java/java_library.bzl b/java/java_library.bzl new file mode 100644 index 00000000..81e0ba16 --- /dev/null +++ b/java/java_library.bzl @@ -0,0 +1,27 @@ +# 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. +"""java_library rule""" + +load("@compatibility_proxy//:proxy.bzl", _java_library = "java_library") + +def java_library(**attrs): + """Bazel java_library rule. + + https://docs.bazel.build/versions/master/be/java.html#java_library + + Args: + **attrs: Rule attributes + """ + + _java_library(**attrs) diff --git a/java/java_plugin.bzl b/java/java_plugin.bzl new file mode 100644 index 00000000..e73843ab --- /dev/null +++ b/java/java_plugin.bzl @@ -0,0 +1,27 @@ +# 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. +"""java_plugin rule""" + +load("@compatibility_proxy//:proxy.bzl", _java_plugin = "java_plugin") + +def java_plugin(**attrs): + """Bazel java_plugin rule. + + https://docs.bazel.build/versions/master/be/java.html#java_plugin + + Args: + **attrs: Rule attributes + """ + + _java_plugin(**attrs) diff --git a/java/java_single_jar.bzl b/java/java_single_jar.bzl new file mode 100644 index 00000000..84771614 --- /dev/null +++ b/java/java_single_jar.bzl @@ -0,0 +1,5 @@ +"""The java_single_jar rule""" + +load("//java/bazel/rules:bazel_java_single_jar.bzl", _java_single_jar = "java_single_jar") + +java_single_jar = _java_single_jar diff --git a/java/java_test.bzl b/java/java_test.bzl new file mode 100644 index 00000000..d11f10bd --- /dev/null +++ b/java/java_test.bzl @@ -0,0 +1,27 @@ +# 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. +"""java_test rule""" + +load("@compatibility_proxy//:proxy.bzl", _java_test = "java_test") + +def java_test(**attrs): + """Bazel java_test rule. + + https://docs.bazel.build/versions/master/be/java.html#java_test + + Args: + **attrs: Rule attributes + """ + + _java_test(**attrs) diff --git a/java/java_utils.bzl b/java/java_utils.bzl new file mode 100644 index 00000000..a90a0012 --- /dev/null +++ b/java/java_utils.bzl @@ -0,0 +1,25 @@ +"""Utility methods for interacting with the java rules""" + +def _tokenize_javacopts(ctx, 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. + + Args: + ctx: (RuleContext) 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()) + return [ + token + for opt in opts + for token in ctx.tokenize(opt) + ] + +utils = struct( + tokenize_javacopts = _tokenize_javacopts, +) diff --git a/java/private/BUILD b/java/private/BUILD index 9f509952..244de292 100644 --- a/java/private/BUILD +++ b/java/private/BUILD @@ -1,11 +1,82 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +package(default_applicable_licenses = ["@rules_java//:license"]) + licenses(["notice"]) bzl_library( - name = "private", - srcs = ["native.bzl"], - visibility = ["//java:__pkg__"], + 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 = [":internals"], ) filegroup( @@ -13,3 +84,16 @@ filegroup( srcs = glob(["**"]), visibility = ["//java:__pkg__"], ) + +filegroup( + name = "for_bazel_tests", + testonly = 1, + srcs = [ + "BUILD", + ":android_support", + ":internals", + ":native_bzl", + ":proto_support", + ], + visibility = ["//java:__pkg__"], +) diff --git a/java/private/android_support.bzl b/java/private/android_support.bzl new file mode 100644 index 00000000..8301234d --- /dev/null +++ b/java/private/android_support.bzl @@ -0,0 +1,22 @@ +# 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. +"""Legacy support for the android rules.""" + +load(":java_info.bzl", _java_info_to_implicit_exportable = "to_implicit_exportable") + +# copybara: android_support visibility + +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..4c5b6035 --- /dev/null +++ b/java/private/java_common_internal.bzl @@ -0,0 +1,628 @@ +# 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 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 new file mode 100644 index 00000000..fa4ecf19 --- /dev/null +++ b/java/proto/BUILD @@ -0,0 +1,16 @@ +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") + +# Toolchain type provided by proto_lang_toolchain rule and used by java_lite_proto_library +toolchain_type(name = "lite_toolchain_type") + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//java:__pkg__"], +) diff --git a/java/repositories.bzl b/java/repositories.bzl index a7cf3bd5..bfa229a7 100644 --- a/java/repositories.bzl +++ b/java/repositories.bzl @@ -20,57 +20,58 @@ load("//toolchains:jdk_build_file.bzl", "JDK_BUILD_TEMPLATE") load("//toolchains:local_java_repository.bzl", "local_java_repository") load("//toolchains:remote_java_repository.bzl", "remote_java_repository") +# visible for tests +JAVA_TOOLS_CONFIG = { + "version": "v22.0", + "source_revision": "c9b10ec53b5fa49a1bb9c593f08f73ff52f60d95", + "release": "true", + "artifacts": { + "java_tools_linux": { + "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v22.0/java_tools_linux-v22.0.zip", + "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_v22.0/java_tools_linux-v22.0.zip", + "sha": "b0b3c2873f30c1123cdd77a74f89fcc4d4debc09a70c8619a4fe8e322f7fda96", + }, + "java_tools_linux_aarch64": { + "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v22.0/java_tools_linux_aarch64-v22.0.zip", + "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_v22.0/java_tools_linux_aarch64-v22.0.zip", + "sha": "f0899527a8784ea0510801a3ee27b3276e2bb78e599473999fe88585a505ddd3", + }, + "java_tools_windows": { + "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v22.0/java_tools_windows-v22.0.zip", + "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_v22.0/java_tools_windows-v22.0.zip", + "sha": "1b302daf293b9ec15eaa2705581976e8c5fd91153fabc6a939a043e6c3d9cf90", + }, + "java_tools_darwin_x86_64": { + "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v22.0/java_tools_darwin_x86_64-v22.0.zip", + "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_v22.0/java_tools_darwin_x86_64-v22.0.zip", + "sha": "fd5d51572f9e2911ae1564849e011805b328f982587e3aaed034f363d646452f", + }, + "java_tools_darwin_arm64": { + "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v22.0/java_tools_darwin_arm64-v22.0.zip", + "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_v22.0/java_tools_darwin_arm64-v22.0.zip", + "sha": "0c78a0d00f09867d56a5064857c936370dab257e5d29dc8d875825509460a456", + }, + "java_tools": { + "mirror_url": "https://mirror.bazel.build/bazel_java_tools/releases/java/v22.0/java_tools-v22.0.zip", + "github_url": "https://github.com/bazelbuild/java_tools/releases/download/java_v22.0/java_tools-v22.0.zip", + "sha": "b13ee1eaa8681d3ec4faffab4251902a0c1b5135b1f5c718831fc5798db82f9b", + }, + }, +} + def java_tools_repos(): """ Declares the remote java_tools repositories """ - maybe( - http_archive, - name = "remote_java_tools", - sha256 = "f58a358ca694a41416a9b6a92b852935ad301d8882e5d22f4f11134f035317d5", - urls = [ - "https://mirror.bazel.build/bazel_java_tools/releases/java/v12.6/java_tools-v12.6.zip", - "https://github.com/bazelbuild/java_tools/releases/download/java_v12.6/java_tools-v12.6.zip", - ], - ) - - maybe( - http_archive, - name = "remote_java_tools_linux", - sha256 = "64294e91fe940c77e6d35818b4c3a1f07d78e33add01e330188d907032687066", - urls = [ - "https://mirror.bazel.build/bazel_java_tools/releases/java/v12.6/java_tools_linux-v12.6.zip", - "https://github.com/bazelbuild/java_tools/releases/download/java_v12.6/java_tools_linux-v12.6.zip", - ], - ) - - maybe( - http_archive, - name = "remote_java_tools_windows", - sha256 = "63f727d44011b8c504bb4e6d89c2cd982278efb34dae8629687e9483d8f7d62d", - urls = [ - "https://mirror.bazel.build/bazel_java_tools/releases/java/v12.6/java_tools_windows-v12.6.zip", - "https://github.com/bazelbuild/java_tools/releases/download/java_v12.6/java_tools_windows-v12.6.zip", - ], - ) - - maybe( - http_archive, - name = "remote_java_tools_darwin_x86_64", - sha256 = "c6545e82e543cb5775d3b8909d6270b5f481864b5ff083d20bfa5dcf77ac3ef7", - urls = [ - "https://mirror.bazel.build/bazel_java_tools/releases/java/v12.6/java_tools_darwin_x86_64-v12.6.zip", - "https://github.com/bazelbuild/java_tools/releases/download/java_v12.6/java_tools_darwin_x86_64-v12.6.zip", - ], - ) - - maybe( - http_archive, - name = "remote_java_tools_darwin_arm64", - sha256 = "c6ffcaf87965c436cc86fc0e9673dafc97c0761efae8225ad2691cf6cfe3d87a", - urls = [ - "https://mirror.bazel.build/bazel_java_tools/releases/java/v12.6/java_tools_darwin_arm64-v12.6.zip", - "https://github.com/bazelbuild/java_tools/releases/download/java_v12.6/java_tools_darwin_arm64-v12.6.zip", - ], - ) + for name, config in JAVA_TOOLS_CONFIG["artifacts"].items(): + maybe( + http_archive, + name = "remote_" + name, + sha256 = config["sha"], + urls = [ + config["mirror_url"], + config["github_url"], + ], + build_file = config.get("build_file"), + ) def local_jdk_repo(): maybe( @@ -79,460 +80,391 @@ def local_jdk_repo(): build_file_content = JDK_BUILD_TEMPLATE, ) -def remote_jdk8_repos(name = ""): - """Imports OpenJDK 8 repositories. - - Args: - name: The name of this macro (not used) - """ - maybe( - remote_java_repository, +# DO NOT MANUALLY UPDATE! Update java/bazel/repositories_util.bzl instead and +# 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 = "f4072e82faa5a09fab2accf2892d4684324fc999d614583c3ff785e87c03963f", - strip_prefix = "zulu8.50.51.263-ca-jdk8.0.275-linux_aarch64", - urls = [ - "https://mirror.bazel.build/openjdk/azul-zulu-8.50.0.51-ca-jdk8.0.275/zulu8.50.51.263-ca-jdk8.0.275-linux_aarch64.tar.gz", - "https://cdn.azul.com/zulu-embedded/bin/zulu8.50.51.263-ca-jdk8.0.275-linux_aarch64.tar.gz", - ], - version = "8", - ) - maybe( - remote_java_repository, - name = "remote_jdk8_linux_s390x", - target_compatible_with = [ - "@platforms//os:linux", - "@platforms//cpu:s390x", - ], - sha256 = "276a431c79b7e94bc1b1b4fd88523383ae2d635ea67114dfc8a6174267f8fb2c", - strip_prefix = "jdk8u292-b10", - urls = [ - "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u292-b10/OpenJDK8U-jdk_s390x_linux_hotspot_8u292b10.tar.gz", - ], + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:aarch64"], + sha256 = "b23296caf10d0c3db054d4a58b9dd168976991472f2203335d7c4820f98c4a4e", + strip_prefix = "zulu8.96.0.19-ca-jdk8.0.502-linux_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu8.96.0.19-ca-jdk8.0.502-linux_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.96.0.19-ca-jdk8.0.502-linux_aarch64.tar.gz"], version = "8", - ) - maybe( - remote_java_repository, + ), + struct( name = "remote_jdk8_linux", - target_compatible_with = [ - "@platforms//os:linux", - "@platforms//cpu:x86_64", - ], - sha256 = "1db6b2fa642950ee1b4b1ec2b6bc8a9113d7a4cd723f79398e1ada7dab1c981c", - strip_prefix = "zulu8.50.0.51-ca-jdk8.0.275-linux_x64", - urls = [ - "https://mirror.bazel.build/openjdk/azul-zulu-8.50.0.51-ca-jdk8.0.275/zulu8.50.0.51-ca-jdk8.0.275-linux_x64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu8.50.0.51-ca-jdk8.0.275-linux_x64.tar.gz", - ], + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:x86_64"], + sha256 = "5923daaf12fd0b87e60e437aaae7b2e5b257846cdb8ac15065258fb59a1da70a", + strip_prefix = "zulu8.96.0.19-ca-jdk8.0.502-linux_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu8.96.0.19-ca-jdk8.0.502-linux_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.96.0.19-ca-jdk8.0.502-linux_x64.tar.gz"], version = "8", - ) - maybe( - remote_java_repository, + ), + struct( name = "remote_jdk8_macos_aarch64", - target_compatible_with = [ - "@platforms//os:macos", - "@platforms//cpu:aarch64", - ], - sha256 = "e5c84a46bbd985c3a53358db9c97a6fd4930f92b833c3163a0d1e47dab59768c", - strip_prefix = "zulu8.62.0.19-ca-jdk8.0.332-macosx_aarch64", - urls = [ - "https://cdn.azul.com/zulu/bin/zulu8.62.0.19-ca-jdk8.0.332-macosx_aarch64.tar.gz", - ], + target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:aarch64"], + sha256 = "9f9e5038c638e415e507e8b5118a774822f553a56e76bdf4b042c3fbe7b69083", + strip_prefix = "zulu8.96.0.19-ca-jdk8.0.502-macosx_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu8.96.0.19-ca-jdk8.0.502-macosx_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.96.0.19-ca-jdk8.0.502-macosx_aarch64.tar.gz"], version = "8", - ) - maybe( - remote_java_repository, + patch_cmds = ["ln -s Contents/Home/* ."], + ), + struct( name = "remote_jdk8_macos", - target_compatible_with = [ - "@platforms//os:macos", - "@platforms//cpu:x86_64", - ], - sha256 = "b03176597734299c9a15b7c2cc770783cf14d121196196c1248e80c026b59c17", - strip_prefix = "zulu8.50.0.51-ca-jdk8.0.275-macosx_x64", - urls = [ - "https://mirror.bazel.build/openjdk/azul-zulu-8.50.0.51-ca-jdk8.0.275/zulu8.50.0.51-ca-jdk8.0.275-macosx_x64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu8.50.0.51-ca-jdk8.0.275-macosx_x64.tar.gz", - ], + target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:x86_64"], + sha256 = "b29088ddb00f81db1e01d6d5bfddd44a58e46ef44b50c372cff3eb1bf5b23173", + strip_prefix = "zulu8.96.0.19-ca-jdk8.0.502-macosx_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu8.96.0.19-ca-jdk8.0.502-macosx_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.96.0.19-ca-jdk8.0.502-macosx_x64.tar.gz"], version = "8", - ) - maybe( - remote_java_repository, + patch_cmds = ["ln -s Contents/Home/* ."], + ), + struct( name = "remote_jdk8_windows", - target_compatible_with = [ - "@platforms//os:windows", - "@platforms//cpu:x86_64", - ], - sha256 = "49759b2bd2ab28231a21ff3a3bb45824ddef55d89b5b1a05a62e26a365da0774", - strip_prefix = "zulu8.50.0.51-ca-jdk8.0.275-win_x64", - urls = [ - "https://mirror.bazel.build/openjdk/azul-zulu-8.50.0.51-ca-jdk8.0.275/zulu8.50.0.51-ca-jdk8.0.275-win_x64.zip", - "https://cdn.azul.com/zulu/bin/zulu8.50.0.51-ca-jdk8.0.275-win_x64.zip", - ], + target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:x86_64"], + sha256 = "0d64e817ba56962d447339040f9e7bd1119f98b813d0a968e3485aab0c530a47", + strip_prefix = "zulu8.96.0.19-ca-jdk8.0.502-win_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu8.96.0.19-ca-jdk8.0.502-win_x64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu8.96.0.19-ca-jdk8.0.502-win_x64.zip"], version = "8", - ) - REMOTE_JDK8_REPOS = [ - "remote_jdk8_linux_aarch64", - "remote_jdk8_linux_s390x", - "remote_jdk8_linux", - "remote_jdk8_macos_aarch64", - "remote_jdk8_macos", - "remote_jdk8_windows", - ] - for name in REMOTE_JDK8_REPOS: - native.register_toolchains("@" + name + "_toolchain_config_repo//:toolchain") - -def remote_jdk11_repos(): - """Imports OpenJDK 11 repositories.""" - maybe( - remote_java_repository, - name = "remotejdk11_linux", - target_compatible_with = [ - "@platforms//os:linux", - "@platforms//cpu:x86_64", - ], - sha256 = "a34b404f87a08a61148b38e1416d837189e1df7a040d949e743633daf4695a3c", - strip_prefix = "zulu11.66.15-ca-jdk11.0.20-linux_x64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.66.15-ca-jdk11.0.20-linux_x64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu11.66.15-ca-jdk11.0.20-linux_x64.tar.gz", - ], - version = "11", - ) - - maybe( - remote_java_repository, + ), + struct( + name = "remote_jdk8_linux_s390x", + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:s390x"], + sha256 = "276a431c79b7e94bc1b1b4fd88523383ae2d635ea67114dfc8a6174267f8fb2c", + strip_prefix = "jdk8u292-b10", + urls = ["https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u292-b10/OpenJDK8U-jdk_s390x_linux_hotspot_8u292b10.tar.gz", "https://mirror.bazel.build/github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u292-b10/OpenJDK8U-jdk_s390x_linux_hotspot_8u292b10.tar.gz"], + version = "8", + ), + struct( name = "remotejdk11_linux_aarch64", - target_compatible_with = [ - "@platforms//os:linux", - "@platforms//cpu:aarch64", - ], - sha256 = "54174439f2b3fddd11f1048c397fe7bb45d4c9d66d452d6889b013d04d21c4de", - strip_prefix = "zulu11.66.15-ca-jdk11.0.20-linux_aarch64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.66.15-ca-jdk11.0.20-linux_aarch64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu11.66.15-ca-jdk11.0.20-linux_aarch64.tar.gz", - ], + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:aarch64"], + sha256 = "31fb32a1c9d842f96e8e79c683996e217d087b3aeac0cc6bfc39f028eb860146", + strip_prefix = "zulu11.90.19-ca-jdk11.0.32-linux_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu11.90.19-ca-jdk11.0.32-linux_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.90.19-ca-jdk11.0.32-linux_aarch64.tar.gz"], version = "11", - ) - - maybe( - remote_java_repository, - name = "remotejdk11_linux_ppc64le", - target_compatible_with = [ - "@platforms//os:linux", - "@platforms//cpu:ppc", - ], - sha256 = "a8fba686f6eb8ae1d1a9566821dbd5a85a1108b96ad857fdbac5c1e4649fc56f", - strip_prefix = "jdk-11.0.15+10", - urls = [ - "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", - "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.15+10/OpenJDK11U-jdk_ppc64le_linux_hotspot_11.0.15_10.tar.gz", - ], + ), + struct( + name = "remotejdk11_linux", + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:x86_64"], + sha256 = "d4832d97886444346ed75fd1059c68c5e38795098e3273e4a0929fe2eb2abf76", + strip_prefix = "zulu11.90.19-ca-jdk11.0.32-linux_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu11.90.19-ca-jdk11.0.32-linux_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.90.19-ca-jdk11.0.32-linux_x64.tar.gz"], version = "11", - ) - - maybe( - remote_java_repository, - name = "remotejdk11_linux_s390x", - target_compatible_with = [ - "@platforms//os:linux", - "@platforms//cpu:s390x", - ], - sha256 = "a58fc0361966af0a5d5a31a2d8a208e3c9bb0f54f345596fd80b99ea9a39788b", - strip_prefix = "jdk-11.0.15+10", - urls = [ - "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", - "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.15+10/OpenJDK11U-jdk_s390x_linux_hotspot_11.0.15_10.tar.gz", - ], + ), + struct( + name = "remotejdk11_macos_aarch64", + target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:aarch64"], + sha256 = "a552338385fa27026ced01a3c56a3b3a2de048ed4b79907d876e99be80547551", + strip_prefix = "zulu11.90.19-ca-jdk11.0.32-macosx_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu11.90.19-ca-jdk11.0.32-macosx_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.90.19-ca-jdk11.0.32-macosx_aarch64.tar.gz"], version = "11", - ) - - maybe( - remote_java_repository, + patch_cmds = ["ln -s Contents/Home/* ."], + ), + struct( name = "remotejdk11_macos", - target_compatible_with = [ - "@platforms//os:macos", - "@platforms//cpu:x86_64", - ], - sha256 = "bcaab11cfe586fae7583c6d9d311c64384354fb2638eb9a012eca4c3f1a1d9fd", - strip_prefix = "zulu11.66.15-ca-jdk11.0.20-macosx_x64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.66.15-ca-jdk11.0.20-macosx_x64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu11.66.15-ca-jdk11.0.20-macosx_x64.tar.gz", - ], - version = "11", - ) - - maybe( - remote_java_repository, - name = "remotejdk11_macos_aarch64", - target_compatible_with = [ - "@platforms//os:macos", - "@platforms//cpu:aarch64", - ], - sha256 = "7632bc29f8a4b7d492b93f3bc75a7b61630894db85d136456035ab2a24d38885", - strip_prefix = "zulu11.66.15-ca-jdk11.0.20-macosx_aarch64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.66.15-ca-jdk11.0.20-macosx_aarch64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu11.66.15-ca-jdk11.0.20-macosx_aarch64.tar.gz", - ], + target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:x86_64"], + sha256 = "5ba1b39cc08999645944bcfd54da48dd6c9a0891785d56cff95e92324fa841f6", + strip_prefix = "zulu11.90.19-ca-jdk11.0.32-macosx_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu11.90.19-ca-jdk11.0.32-macosx_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.90.19-ca-jdk11.0.32-macosx_x64.tar.gz"], version = "11", - ) - - maybe( - remote_java_repository, + patch_cmds = ["ln -s Contents/Home/* ."], + ), + struct( name = "remotejdk11_win", - target_compatible_with = [ - "@platforms//os:windows", - "@platforms//cpu:x86_64", - ], - sha256 = "43408193ce2fa0862819495b5ae8541085b95660153f2adcf91a52d3a1710e83", - strip_prefix = "zulu11.66.15-ca-jdk11.0.20-win_x64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.66.15-ca-jdk11.0.20-win_x64.zip", - "https://cdn.azul.com/zulu/bin/zulu11.66.15-ca-jdk11.0.20-win_x64.zip", - ], + target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:x86_64"], + sha256 = "700a42d5764b5f41ab43adbfe3e03deac329cc10886f2312a5908fd5a3c78d46", + strip_prefix = "zulu11.90.19-ca-jdk11.0.32-win_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu11.90.19-ca-jdk11.0.32-win_x64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu11.90.19-ca-jdk11.0.32-win_x64.zip"], version = "11", - ) - - maybe( - remote_java_repository, + ), + struct( + name = "remotejdk11_linux_ppc64le", + 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 = "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://mirror.bazel.build/aka.ms/download-jdk/microsoft-jdk-11.0.13.8.1-windows-aarch64.zip", - ], + target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:arm64"], + 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", - ) - -def remote_jdk17_repos(): - """Imports OpenJDK 17 repositories.""" - maybe( - remote_java_repository, - name = "remotejdk17_linux", - target_compatible_with = [ - "@platforms//os:linux", - "@platforms//cpu:x86_64", - ], - sha256 = "20c91a922eec795f3181eaa70def8b99d8eac56047c9a14bfb257c85b991df1b", - strip_prefix = "zulu17.38.21-ca-jdk17.0.5-linux_x64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.38.21-ca-jdk17.0.5-linux_x64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu17.38.21-ca-jdk17.0.5-linux_x64.tar.gz", - ], - version = "17", - ) - - maybe( - remote_java_repository, + ), + struct( name = "remotejdk17_linux_aarch64", - target_compatible_with = [ - "@platforms//os:linux", - "@platforms//cpu:aarch64", - ], - sha256 = "dbc6ae9163e7ff469a9ab1f342cd1bc1f4c1fb78afc3c4f2228ee3b32c4f3e43", - strip_prefix = "zulu17.38.21-ca-jdk17.0.5-linux_aarch64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.38.21-ca-jdk17.0.5-linux_aarch64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu17.38.21-ca-jdk17.0.5-linux_aarch64.tar.gz", - ], + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:aarch64"], + sha256 = "46f1dc7678760d37d38699c5a5a34dea8ed451dcdf6d27890a7b61f025e7ff60", + strip_prefix = "zulu17.68.17-ca-jdk17.0.20-linux_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu17.68.17-ca-jdk17.0.20-linux_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.68.17-ca-jdk17.0.20-linux_aarch64.tar.gz"], version = "17", - ) - - maybe( - remote_java_repository, - name = "remotejdk17_linux_s390x", - target_compatible_with = [ - "@platforms//os:linux", - "@platforms//cpu:s390x", - ], - sha256 = "fdc82f4b06c880762503b0cb40e25f46cf8190d06011b3b768f4091d3334ef7f", - strip_prefix = "jdk-17.0.4.1+1", - urls = [ - "https://mirror.bazel.build/github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.4.1%2B1/OpenJDK17U-jdk_s390x_linux_hotspot_17.0.4.1_1.tar.gz", - "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.4.1%2B1/OpenJDK17U-jdk_s390x_linux_hotspot_17.0.4.1_1.tar.gz", - ], + ), + struct( + name = "remotejdk17_linux", + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:x86_64"], + sha256 = "32c5efedf69f4a95635ea2923f6a6ee90ce6ca83df0bd43ba55dd662d5af429a", + strip_prefix = "zulu17.68.17-ca-jdk17.0.20-linux_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu17.68.17-ca-jdk17.0.20-linux_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.68.17-ca-jdk17.0.20-linux_x64.tar.gz"], version = "17", - ) - - maybe( - remote_java_repository, - name = "remotejdk17_linux_ppc64le", - target_compatible_with = [ - "@platforms//os:linux", - "@platforms//cpu:ppc", - ], - sha256 = "cbedd0a1428b3058d156e99e8e9bc8769e0d633736d6776a4c4d9136648f2fd1", - strip_prefix = "jdk-17.0.4.1+1", - urls = [ - "https://mirror.bazel.build/github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.4.1%2B1/OpenJDK17U-jdk_ppc64le_linux_hotspot_17.0.4.1_1.tar.gz", - "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.4.1%2B1/OpenJDK17U-jdk_ppc64le_linux_hotspot_17.0.4.1_1.tar.gz", - ], + ), + struct( + name = "remotejdk17_macos_aarch64", + target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:aarch64"], + sha256 = "0da52534760b74ba8a42660384b2f4e44311a47ae52faa45fcbd829b2797b244", + strip_prefix = "zulu17.68.17-ca-jdk17.0.20-macosx_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu17.68.17-ca-jdk17.0.20-macosx_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.68.17-ca-jdk17.0.20-macosx_aarch64.tar.gz"], version = "17", - ) - - maybe( - remote_java_repository, + patch_cmds = ["ln -s Contents/Home/* ."], + ), + struct( name = "remotejdk17_macos", - target_compatible_with = [ - "@platforms//os:macos", - "@platforms//cpu:x86_64", - ], - sha256 = "e6317cee4d40995f0da5b702af3f04a6af2bbd55febf67927696987d11113b53", - strip_prefix = "zulu17.38.21-ca-jdk17.0.5-macosx_x64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.38.21-ca-jdk17.0.5-macosx_x64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu17.38.21-ca-jdk17.0.5-macosx_x64.tar.gz", - ], + target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:x86_64"], + sha256 = "b5bace4a346a7af0fe4f50904c46770e66d4b465800b038693c35e5d5b9bd52a", + strip_prefix = "zulu17.68.17-ca-jdk17.0.20-macosx_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu17.68.17-ca-jdk17.0.20-macosx_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.68.17-ca-jdk17.0.20-macosx_x64.tar.gz"], version = "17", - ) - - maybe( - remote_java_repository, - name = "remotejdk17_macos_aarch64", - target_compatible_with = [ - "@platforms//os:macos", - "@platforms//cpu:aarch64", - ], - sha256 = "515dd56ec99bb5ae8966621a2088aadfbe72631818ffbba6e4387b7ee292ab09", - strip_prefix = "zulu17.38.21-ca-jdk17.0.5-macosx_aarch64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.38.21-ca-jdk17.0.5-macosx_aarch64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu17.38.21-ca-jdk17.0.5-macosx_aarch64.tar.gz", - ], + patch_cmds = ["ln -s Contents/Home/* ."], + ), + struct( + name = "remotejdk17_win_arm64", + target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:arm64"], + sha256 = "e125b3b64c9b5a0a2f2737e4a94ba275b541d8cc85d61c95ff875a3463ce0d8e", + strip_prefix = "zulu17.68.17-ca-jdk17.0.20-win_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu17.68.17-ca-jdk17.0.20-win_aarch64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.68.17-ca-jdk17.0.20-win_aarch64.zip"], version = "17", - ) - maybe( - remote_java_repository, + ), + struct( name = "remotejdk17_win", - target_compatible_with = [ - "@platforms//os:windows", - "@platforms//cpu:x86_64", - ], - sha256 = "9972c5b62a61b45785d3d956c559e079d9e91f144ec46225f5deeda214d48f27", - strip_prefix = "zulu17.38.21-ca-jdk17.0.5-win_x64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.38.21-ca-jdk17.0.5-win_x64.zip", - "https://cdn.azul.com/zulu/bin/zulu17.38.21-ca-jdk17.0.5-win_x64.zip", - ], + target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:x86_64"], + sha256 = "d207d8cec299f4d216bef1c7c081ba9504e79ba1cbc740de312d4a91b2d46436", + strip_prefix = "zulu17.68.17-ca-jdk17.0.20-win_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu17.68.17-ca-jdk17.0.20-win_x64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.68.17-ca-jdk17.0.20-win_x64.zip"], version = "17", - ) - maybe( - remote_java_repository, - name = "remotejdk17_win_arm64", - target_compatible_with = [ - "@platforms//os:windows", - "@platforms//cpu:arm64", - ], - sha256 = "bc3476f2161bf99bc9a243ff535b8fc033b34ce9a2fa4b62fb8d79b6bfdc427f", - strip_prefix = "zulu17.38.21-ca-jdk17.0.5-win_aarch64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu17.38.21-ca-jdk17.0.5-win_aarch64.zip", - "https://cdn.azul.com/zulu/bin/zulu17.38.21-ca-jdk17.0.5-win_aarch64.zip", - ], + ), + struct( + name = "remotejdk17_linux_ppc64le", + 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 = "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 = "dc7ed9ab7dfd33f2ddb1cd8311d3b00738497961a420533d81088051eac3f195", + strip_prefix = "zulu21.52.15-ca-jdk21.0.12-linux_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu21.52.15-ca-jdk21.0.12-linux_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.52.15-ca-jdk21.0.12-linux_aarch64.tar.gz"], + version = "21", + ), + struct( + name = "remotejdk21_linux", + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:x86_64"], + sha256 = "b1a9df12e798770d1b2db43b402a80f1e6080cff6d5d1d1fbe5c768fb4225f6a", + strip_prefix = "zulu21.52.15-ca-jdk21.0.12-linux_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu21.52.15-ca-jdk21.0.12-linux_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.52.15-ca-jdk21.0.12-linux_x64.tar.gz"], + version = "21", + ), + struct( + name = "remotejdk21_macos_aarch64", + target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:aarch64"], + sha256 = "84d38c4bf04f73d585bd319b949758d00abde50149a9522c2d9deef46b9a3ec6", + strip_prefix = "zulu21.52.15-ca-jdk21.0.12-macosx_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu21.52.15-ca-jdk21.0.12-macosx_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.52.15-ca-jdk21.0.12-macosx_aarch64.tar.gz"], + version = "21", + patch_cmds = ["ln -s Contents/Home/* ."], + ), + struct( + name = "remotejdk21_macos", + target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:x86_64"], + sha256 = "14e05cb1299c27cd26d3c5c6815723f63018df548dc7278c810767607902b4f4", + strip_prefix = "zulu21.52.15-ca-jdk21.0.12-macosx_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu21.52.15-ca-jdk21.0.12-macosx_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.52.15-ca-jdk21.0.12-macosx_x64.tar.gz"], + version = "21", + patch_cmds = ["ln -s Contents/Home/* ."], + ), + struct( + name = "remotejdk21_win_arm64", + target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:arm64"], + sha256 = "d75658d371e025bb6378231551b4d53d1f5312cb0fccf7aa3b039d91ce1f0b7e", + strip_prefix = "zulu21.52.15-ca-jdk21.0.12-win_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu21.52.15-ca-jdk21.0.12-win_aarch64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.52.15-ca-jdk21.0.12-win_aarch64.zip"], + version = "21", + ), + struct( + name = "remotejdk21_win", + target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:x86_64"], + sha256 = "3c06e6693fd6fa725b985e66798a6a8293c75f52b793490754ad3c54d3d8b5a6", + strip_prefix = "zulu21.52.15-ca-jdk21.0.12-win_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu21.52.15-ca-jdk21.0.12-win_x64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu21.52.15-ca-jdk21.0.12-win_x64.zip"], + version = "21", + ), + struct( + name = "remotejdk21_linux_ppc64le", + 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 = "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 = "remotejdk25_linux_aarch64", + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:aarch64"], + sha256 = "ae04e25b16116ddd0f72cb387742d5122bb29c4b94f0fed08ff2bccc395f9daa", + strip_prefix = "zulu25.36.15-ca-jdk25.0.4-linux_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-linux_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-linux_aarch64.tar.gz"], + version = "25", + ), + struct( + name = "remotejdk25_linux", + target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:x86_64"], + sha256 = "e476f5c98952cb365ca77a814dbe3c74341e71ae76d1a87d1c0a69c7d2b1b2d0", + strip_prefix = "zulu25.36.15-ca-jdk25.0.4-linux_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-linux_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-linux_x64.tar.gz"], + version = "25", + ), + struct( + name = "remotejdk25_macos_aarch64", + target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:aarch64"], + sha256 = "c422f22713510992c764a7d577ae4f5add223529ab356993f38f8df2d4b247bd", + strip_prefix = "zulu25.36.15-ca-jdk25.0.4-macosx_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-macosx_aarch64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-macosx_aarch64.tar.gz"], + version = "25", + patch_cmds = ["ln -s Contents/Home/* ."], + ), + struct( + name = "remotejdk25_macos", + target_compatible_with = ["@platforms//os:macos", "@platforms//cpu:x86_64"], + sha256 = "63273c6b9e2d9b250a0009ffdaa6d9f27af6ba98772516c391276106fb9f2820", + strip_prefix = "zulu25.36.15-ca-jdk25.0.4-macosx_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-macosx_x64.tar.gz", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-macosx_x64.tar.gz"], + version = "25", + patch_cmds = ["ln -s Contents/Home/* ."], + ), + struct( + name = "remotejdk25_win_arm64", + target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:arm64"], + sha256 = "2e8468229aea447152343fff87dbebe5c339e1fbab143a8f227f6b2f388c0a8c", + strip_prefix = "zulu25.36.15-ca-jdk25.0.4-win_aarch64", + urls = ["https://cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-win_aarch64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-win_aarch64.zip"], + version = "25", + ), + struct( + name = "remotejdk25_win", + target_compatible_with = ["@platforms//os:windows", "@platforms//cpu:x86_64"], + sha256 = "a91265f0f0fb40155befe8f1cae7d1a0ae2ab4b7760f52733160d206637b50d1", + strip_prefix = "zulu25.36.15-ca-jdk25.0.4-win_x64", + urls = ["https://cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-win_x64.zip", "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-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", + ), +] + +def _make_version_to_remote_jdks(): + result = {} + for cfg in _REMOTE_JDK_CONFIGS_LIST: + result.setdefault(cfg.version, []) + result[cfg.version].append(cfg) + return result + +# visible for testing +REMOTE_JDK_CONFIGS = _make_version_to_remote_jdks() + +def _remote_jdk_repos_for_version(version): + for item in REMOTE_JDK_CONFIGS[version]: + maybe( + remote_java_repository, + name = item.name, + target_compatible_with = item.target_compatible_with, + sha256 = item.sha256, + strip_prefix = item.strip_prefix, + urls = item.urls, + version = item.version, + patch_cmds = getattr(item, "patch_cmds", []), + ) -def remote_jdk20_repos(): - """Imports OpenJDK 20 repositories.""" - maybe( - remote_java_repository, - name = "remotejdk20_linux", - target_compatible_with = [ - "@platforms//os:linux", - "@platforms//cpu:x86_64", - ], - sha256 = "0386418db7f23ae677d05045d30224094fc13423593ce9cd087d455069893bac", - strip_prefix = "zulu20.28.85-ca-jdk20.0.0-linux_x64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu20.28.85-ca-jdk20.0.0-linux_x64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu20.28.85-ca-jdk20.0.0-linux_x64.tar.gz", - ], - version = "20", - ) +def remote_jdk8_repos(name = ""): + """Imports OpenJDK 8 repositories. - maybe( - remote_java_repository, - name = "remotejdk20_linux_aarch64", - target_compatible_with = [ - "@platforms//os:linux", - "@platforms//cpu:aarch64", - ], - sha256 = "47ce58ead9a05d5d53b96706ff6fa0eb2e46755ee67e2b416925e28f5b55038a", - strip_prefix = "zulu20.28.85-ca-jdk20.0.0-linux_aarch64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu20.28.85-ca-jdk20.0.0-linux_aarch64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu20.28.85-ca-jdk20.0.0-linux_aarch64.tar.gz", - ], - version = "20", - ) + Args: + name: The name of this macro (not used) + """ + _remote_jdk_repos_for_version("8") - maybe( - remote_java_repository, - name = "remotejdk20_macos", - target_compatible_with = [ - "@platforms//os:macos", - "@platforms//cpu:x86_64", - ], - sha256 = "fde6cc17a194ea0d9b0c6c0cb6178199d8edfc282d649eec2c86a9796e843f86", - strip_prefix = "zulu20.28.85-ca-jdk20.0.0-macosx_x64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu20.28.85-ca-jdk20.0.0-macosx_x64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu20.28.85-ca-jdk20.0.0-macosx_x64.tar.gz", - ], - version = "20", - ) +def remote_jdk11_repos(): + """Imports OpenJDK 11 repositories.""" + _remote_jdk_repos_for_version("11") - maybe( - remote_java_repository, - name = "remotejdk20_macos_aarch64", - target_compatible_with = [ - "@platforms//os:macos", - "@platforms//cpu:aarch64", - ], - sha256 = "a2eff6a940c2df3a2352278027e83f5959f34dcfc8663034fe92be0f1b91ce6f", - strip_prefix = "zulu20.28.85-ca-jdk20.0.0-macosx_aarch64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu20.28.85-ca-jdk20.0.0-macosx_aarch64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu20.28.85-ca-jdk20.0.0-macosx_aarch64.tar.gz", - ], - version = "20", - ) - maybe( - remote_java_repository, - name = "remotejdk20_win", - target_compatible_with = [ - "@platforms//os:windows", - "@platforms//cpu:x86_64", - ], - sha256 = "ac5f6a7d84dbbb0bb4d376feb331cc4c49a9920562f2a5e85b7a6b4863b10e1e", - strip_prefix = "zulu20.28.85-ca-jdk20.0.0-win_x64", - urls = [ - "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu20.28.85-ca-jdk20.0.0-win_x64.zip", - "https://cdn.azul.com/zulu/bin/zulu20.28.85-ca-jdk20.0.0-win_x64.zip", - ], - version = "20", - ) +def remote_jdk17_repos(): + """Imports OpenJDK 17 repositories.""" + _remote_jdk_repos_for_version("17") -def rules_java_dependencies(): - """An utility method to load all dependencies of rules_java. +def remote_jdk21_repos(): + """Imports OpenJDK 21 repositories.""" + _remote_jdk_repos_for_version("21") - Loads the remote repositories used by default in Bazel. - """ +def remote_jdk25_repos(): + """Imports OpenJDK 25 repositories.""" + _remote_jdk_repos_for_version("25") - local_jdk_repo() - remote_jdk11_repos() - remote_jdk17_repos() - remote_jdk20_repos() - java_tools_repos() +def rules_java_dependencies(): + """DEPRECATED: No-op, kept for backwards compatibility""" + print("DEPRECATED: use rules_java_dependencies() from rules_java_deps.bzl") # buildifier: disable=print def rules_java_toolchains(name = "toolchains"): """An utility method to load all Java toolchains. @@ -540,24 +472,22 @@ def rules_java_toolchains(name = "toolchains"): Args: name: The name of this macro (not used) """ - JDKS = { - # Must match JDK repos defined in remote_jdk11_repos() - "11": ["linux", "linux_aarch64", "linux_ppc64le", "linux_s390x", "macos", "macos_aarch64", "win", "win_arm64"], - # Must match JDK repos defined in remote_jdk17_repos() - "17": ["linux", "linux_aarch64", "linux_ppc64le", "linux_s390x", "macos", "macos_aarch64", "win", "win_arm64"], - # Must match JDK repos defined in remote_jdk20_repos() - "20": ["linux", "linux_aarch64", "macos", "macos_aarch64", "win"], - } - - REMOTE_JDK_REPOS = [("remotejdk" + version + "_" + platform) for version in JDKS for platform in JDKS[version]] + local_jdk_repo() + remote_jdk8_repos() + remote_jdk11_repos() + remote_jdk17_repos() + remote_jdk21_repos() + remote_jdk25_repos() + java_tools_repos() native.register_toolchains( "//toolchains:all", "@local_jdk//:runtime_toolchain_definition", "@local_jdk//:bootstrap_runtime_toolchain_definition", ) - for name in REMOTE_JDK_REPOS: - native.register_toolchains( - "@" + name + "_toolchain_config_repo//:toolchain", - "@" + name + "_toolchain_config_repo//:bootstrap_runtime_toolchain", - ) + for items in REMOTE_JDK_CONFIGS.values(): + for item in items: + native.register_toolchains( + "@" + item.name + "_toolchain_config_repo//:toolchain", + "@" + item.name + "_toolchain_config_repo//:bootstrap_runtime_toolchain", + ) diff --git a/java/rules_java_deps.bzl b/java/rules_java_deps.bzl new file mode 100644 index 00000000..c9742f94 --- /dev/null +++ b/java/rules_java_deps.bzl @@ -0,0 +1,240 @@ +"""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 + """, + ) + + if hasattr(rctx, "repo_metadata"): + return rctx.repo_metadata(reproducible = True) + return None + +_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..cc7c2ec5 --- /dev/null +++ b/java/testutil.bzl @@ -0,0 +1,22 @@ +"""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 + +def _cc_library_to_link_dynamic_library(library_to_link): + return library_to_link.interface_library or library_to_link.dynamic_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, + cc_library_to_link_dynamic_library = _cc_library_to_link_dynamic_library, +) diff --git a/java/toolchains/BUILD b/java/toolchains/BUILD new file mode 100644 index 00000000..2cb68270 --- /dev/null +++ b/java/toolchains/BUILD @@ -0,0 +1,29 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +package( + default_applicable_licenses = ["@rules_java//:license"], + default_visibility = ["//visibility:public"], +) + +licenses(["notice"]) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//java:__pkg__"], +) + +bzl_library( + name = "toolchain_rules", + srcs = glob(["*.bzl"]), + visibility = ["//visibility:public"], + deps = ["@compatibility_proxy//:proxy_bzl"], +) + +filegroup( + name = "for_bazel_tests", + srcs = [ + "BUILD", + "java_toolchain.bzl", + ], +) diff --git a/java/toolchains/java_package_configuration.bzl b/java/toolchains/java_package_configuration.bzl new file mode 100644 index 00000000..7ef0728b --- /dev/null +++ b/java/toolchains/java_package_configuration.bzl @@ -0,0 +1,27 @@ +# 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. +"""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. + + https://docs.bazel.build/versions/master/be/java.html#java_package_configuration + + Args: + **attrs: Rule attributes + """ + + _java_package_configuration(**attrs) diff --git a/java/toolchains/java_runtime.bzl b/java/toolchains/java_runtime.bzl new file mode 100644 index 00000000..c1d16411 --- /dev/null +++ b/java/toolchains/java_runtime.bzl @@ -0,0 +1,27 @@ +# 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. +"""java_runtime rule""" + +load("@compatibility_proxy//:proxy.bzl", _java_runtime = "java_runtime") + +def java_runtime(**attrs): + """Bazel java_runtime rule. + + https://docs.bazel.build/versions/master/be/java.html#java_runtime + + Args: + **attrs: Rule attributes + """ + + _java_runtime(**attrs) diff --git a/java/toolchains/java_toolchain.bzl b/java/toolchains/java_toolchain.bzl new file mode 100644 index 00000000..5207f8c9 --- /dev/null +++ b/java/toolchains/java_toolchain.bzl @@ -0,0 +1,27 @@ +# 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. +"""java_toolchain rule""" + +load("@compatibility_proxy//:proxy.bzl", _java_toolchain = "java_toolchain") + +def java_toolchain(**attrs): + """Bazel java_toolchain rule. + + https://docs.bazel.build/versions/master/be/java.html#java_toolchain + + Args: + **attrs: Rule attributes + """ + + _java_toolchain(**attrs) diff --git a/test/BUILD.bazel b/test/BUILD.bazel new file mode 100644 index 00000000..9469fb99 --- /dev/null +++ b/test/BUILD.bazel @@ -0,0 +1,76 @@ +# 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. + +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"], + args = [ + ",".join([ + config.name, + config.urls[0], + config.urls[1] if len(config.urls) > 1 else "", + config.sha256, + config.strip_prefix, + ]) + for configs in REMOTE_JDK_CONFIGS.values() + for config in configs + ], + tags = ["manual"], # explicitly tested only on Linux +) + +sh_test( + name = "check_remote_java_tools_configs_test", + srcs = ["check_remote_java_tools_configs.sh"], + args = [ + ",".join([ + name, + config["mirror_url"], + config["github_url"], + config["sha"], + ]) + 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 = [ + "$(rootpath //distro:relnotes.txt)", + module_version(), + ], + data = ["//distro:relnotes.txt"], + tags = ["manual"], # explicitly tested only on Linux +) + +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/bazel/BUILD.bazel b/test/bazel/BUILD.bazel new file mode 100644 index 00000000..50fff509 --- /dev/null +++ b/test/bazel/BUILD.bazel @@ -0,0 +1,11 @@ +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_binary.bzl", "java_binary") + +# Used in test/java/bazel/rules/java_binary_tests.bzl%_test_java_binary_empty_srcs +util.helper_target( + java_binary, + name = "bin_without_srcs", + visibility = [ + "//test/java/bazel/rules:__pkg__", + ], +) 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 new file mode 100755 index 00000000..957aa788 --- /dev/null +++ b/test/check_remote_java_tools_configs.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# 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. + +echo "Checking hashes for $# configs" + +function download_and_check_hash() { + name=$1 + url=$2 + hash=$3 + TMP_FILE=$(mktemp -q /tmp/remotejavatools.XXXXXX) + echo "fetching $name from $url to ${TMP_FILE}" + curl --silent -o ${TMP_FILE} -L "$url" + 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 + fi +} + +for config in "$@"; do + IFS=, read -r name mirror_url gh_url hash <<< "${config}" + download_and_check_hash ${name} ${mirror_url} ${hash} + download_and_check_hash ${name} ${gh_url} ${hash} +done \ No newline at end of file diff --git a/test/check_remote_jdk_configs.sh b/test/check_remote_jdk_configs.sh new file mode 100755 index 00000000..e7f27201 --- /dev/null +++ b/test/check_remote_jdk_configs.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# 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. + +function fail() { + echo "$@" + exit 1 +} + +function debug_log() { + # bazel test --test_env=DEBUG=1 ... + [[ -z "${DEBUG}" ]] || echo "$@" +} + +echo "Checking hashes and strip_prefix for $# configs" + +_MISSING_MIRRORS=() +for config in "$@"; do + TMP_FILE=$(mktemp -q /tmp/remotejdk.XXXXXX) + IFS=, read -r name url mirror_url hash strip_prefix <<< "${config}" + debug_log "fetching $name from $url to ${TMP_FILE}" + curl --silent -o ${TMP_FILE} -L "$url" + actual_hash=$(sha256sum ${TMP_FILE} | cut -d' ' -f1) + if [ "${hash}" != "${actual_hash}" ]; then + fail "ERROR: wrong hash for ${name}! wanted: ${hash}, got: ${actual_hash}" + fi + if [[ -z "${url##*.tar.gz}" ]]; then + root_dir=$(tar ztf ${TMP_FILE} --exclude='*/*') + elif [[ -z "${url##*.zip}" ]]; then + root_dir=$(unzip -Z1 ${TMP_FILE} | head -n1) + else + fail "ERROR: unexpected archive type for ${name}" + fi + if [ "${root_dir}" != "${strip_prefix}/" ]; then + fail "ERROR: bad strip_prefix for ${name}, wanted: ${strip_prefix}/, got: ${root_dir}" + fi + if [[ -n "${mirror_url}" ]]; then + debug_log "checking mirror: ${mirror_url}" + curl --silent --fail -I -L ${mirror_url} > /dev/null || { _MISSING_MIRRORS+=("${url}"); } + fi +done + +if [[ ${#_MISSING_MIRRORS[@]} -gt 0 ]]; then + echo "URLs that aren't mirrored:" + for m in "${_MISSING_MIRRORS[@]}"; do + echo " ${m}" + done + exit 1 +fi \ No newline at end of file diff --git a/test/check_remotejdk_configs_match.bzl b/test/check_remotejdk_configs_match.bzl new file mode 100644 index 00000000..5060cda0 --- /dev/null +++ b/test/check_remotejdk_configs_match.bzl @@ -0,0 +1,27 @@ +# 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. +"""Checks for keeping repository_util.bzl and repositories.bzl in sync""" + +load("//java:repositories.bzl", "REMOTE_JDK_CONFIGS") +load("//java/bazel:repositories_util.bzl", "FLAT_CONFIGS") + +def validate_configs(): + """Ensures repository_util.bzl and repositories.bzl are in sync""" + for expected in FLAT_CONFIGS: + actual = [cfg for cfg in REMOTE_JDK_CONFIGS[expected.version] if cfg.name == expected.name] + if len(actual) != 1: + fail("Expected to find exactly one configuration for:", expected.name, "found: ", actual) + actual = actual[0] + if expected.urls != actual.urls or expected.strip_prefix != actual.strip_prefix: + fail("config mismatch! wanted:", expected, "got:", actual) 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..9702f735 --- /dev/null +++ b/test/java/bazel/common/java_common_tests.bzl @@ -0,0 +1,39 @@ +"""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", + ) + +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..09c1f1a0 --- /dev/null +++ b/test/java/bazel/rules/BUILD.bazel @@ -0,0 +1,15 @@ +load(":java_binary_tests.bzl", "java_binary_tests") +load(":java_import_tests.bzl", "java_import_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_import_tests(name = "java_import_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..39bd787d --- /dev/null +++ b/test/java/bazel/rules/java_binary_tests.bzl @@ -0,0 +1,205 @@ +"""Tests for the Bazel java_binary rule""" + +load("@bazel_features//private:util.bzl", _bazel_version_ge = "ge") +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:helper.bzl", "always_passes") +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_excludes_build_data(name): + if not _bazel_version_ge("8.0.0"): + always_passes(name) + return + + util.helper_target( + java_binary, + name = name + "/bin", + srcs = ["Main.java"], + exclude_build_data = True, + ) + + analysis_test( + name = name, + impl = _test_java_binary_excludes_build_data_impl, + target = name + "/bin", + ) + +def _test_java_binary_excludes_build_data_impl(env, target): + assert_deploy_jar_action = env.expect.that_target(target).action_generating( + "{package}/{name}_deploy.jar", + ) + + assert_deploy_jar_action.argv().contains("--normalize") + assert_deploy_jar_action.argv().contains("--exclude_build_data") + assert_deploy_jar_action.argv().not_contains("--build_info_file") + assert_deploy_jar_action.inputs().not_contains_predicate( + matching.file_basename_equals("non_volatile_file.properties"), + ) + assert_deploy_jar_action.inputs().not_contains_predicate( + matching.file_basename_equals("redacted_file.properties"), + ) + +def _test_java_binary_stamping_enabled_build_data_excluded_fails(name): + if not _bazel_version_ge("8.0.0"): + always_passes(name) + return + + util.helper_target( + java_binary, + name = name + "/bin", + srcs = ["Main.java"], + exclude_build_data = True, + stamp = 1, + ) + + analysis_test( + name = name, + impl = _test_java_binary_stamping_enabled_build_data_excluded_fails_impl, + target = name + "/bin", + expect_failure = True, + ) + +def _test_java_binary_stamping_enabled_build_data_excluded_fails_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("Enabling stamping has no effect with exclude_build_data enabled"), + ) + +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", + ) + +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 _test_java_binary_empty_srcs(name): + analysis_test( + name = name, + impl = _test_java_binary_empty_srcs_impl, + target = "//test/bazel:bin_without_srcs", # need a target not under a `/java/` segment + expect_failure = True, + ) + +def _test_java_binary_empty_srcs_impl(env, target): + env.expect.that_target(target).failures().contains_predicate(matching.str_matches( + "need at least one of 'main_class' or Java source files", + )) + +def java_binary_tests(name): + test_suite( + name = name, + tests = [ + _test_java_binary_cross_compilation_to_unix, + _test_java_binary_excludes_build_data, + _test_java_binary_stamping_enabled_build_data_excluded_fails, + _test_java_binary_javacopts_make_variable_expansion, + _test_java_binary_javacopts_location_expansion, + _test_java_binary_resource_strip_prefix, + _test_java_binary_empty_srcs, + ], + ) diff --git a/test/java/bazel/rules/java_import_tests.bzl b/test/java/bazel/rules/java_import_tests.bzl new file mode 100644 index 00000000..e368b14a --- /dev/null +++ b/test/java/bazel/rules/java_import_tests.bzl @@ -0,0 +1,40 @@ +"""Tests for the Bazel java_import rule""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_import.bzl", "java_import") + +def _test_import_deps_checker_checking_mode(name): + util.helper_target( + java_import, + name = name + "/import-jar", + jars = ["import.jar"], + deps = [name + "/depjar"], + ) + util.helper_target( + java_import, + name = name + "/depjar", + jars = ["depjar.jar"], + ) + + analysis_test( + name = name, + impl = _test_import_deps_checker_checking_mode_impl, + target = name + "/import-jar", + ) + +def _test_import_deps_checker_checking_mode_impl(env, target): + # java_import only generates the jdeps proto, it never fails the build on incomplete deps. + assert_action = env.expect.that_target(target).action_named("ImportDepsChecker") + assert_action.contains_flag_values([ + ("--checking_mode", "silence"), + ("--rule_label", "//{package}:{name}"), + ]) + +def java_import_tests(name): + test_suite( + name = name, + tests = [ + _test_import_deps_checker_checking_mode, + ], + ) 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..fd20980d --- /dev/null +++ b/test/java/bazel/rules/java_library_tests.bzl @@ -0,0 +1,42 @@ +"""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", + ) + +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..9015ebf1 --- /dev/null +++ b/test/java/bazel/rules/java_plugin_tests.bzl @@ -0,0 +1,41 @@ +"""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", + ) + +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..5955a92e --- /dev/null +++ b/test/java/bazel/rules/java_test_tests.bzl @@ -0,0 +1,125 @@ +"""Tests for the Bazel java_test rule""" + +load("@bazel_features//private:util.bzl", _bazel_version_ge = "ge") +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") +load("//test/java/testutil:helper.bzl", "always_passes") + +def _test_deduced_test_class(name): + if not _bazel_version_ge("8.0.0"): + always_passes(name) + return + + 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 _test_test_classes(name): + if not _bazel_version_ge("8.0.0"): + always_passes(name) + return + + util.helper_target( + java_test, + name = name + "/foo", + test_classes = ["com.example.FooTest", "com.example.BarTest"], + ) + + analysis_test( + name = name, + impl = _test_test_classes_impl, + target = name + "/foo", + ) + +def _test_test_classes_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=com.example.FooTest,com.example.BarTest"), + ) + +def _test_both_test_class_and_test_classes_fails(name): + if not _bazel_version_ge("8.0.0"): + always_passes(name) + return + + util.helper_target( + java_test, + name = name + "/foo", + test_class = "FooTest", + test_classes = ["FooTest"], + ) + + analysis_test( + name = name, + impl = _test_both_test_class_and_test_classes_fails_impl, + target = name + "/foo", + expect_failure = True, + ) + +def _test_both_test_class_and_test_classes_fails_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("cannot specify both 'test_class' and 'test_classes'"), + ) + +def java_test_tests(name): + test_suite( + name = name, + tests = [ + _test_deduced_test_class, + _test_invalid_test_class_at_repo_root, + _test_test_classes, + _test_both_test_class_and_test_classes_fails, + ], + ) 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..e9942abd --- /dev/null +++ b/test/java/common/java_common_tests.bzl @@ -0,0 +1,1163 @@ +"""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): + artifact_closure.of_target(env, target, extensions = ["java"]).contains_exactly([ + "{package}/Plugin.java", + "{package}/Dependency.java", + "{package}/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, + ) + +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..8f4c2c13 --- /dev/null +++ b/test/java/common/java_info_tests.bzl @@ -0,0 +1,1467 @@ +"""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, + ) + +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, + ) + +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..b115dbb3 --- /dev/null +++ b/test/java/common/rules/BUILD @@ -0,0 +1,62 @@ +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_helper_tests.bzl", "java_helper_test_suite") +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") + +java_helper_test_suite(name = "java_helper_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 = ["//visibility:private"], +) diff --git a/test/java/common/rules/a.foo b/test/java/common/rules/a.foo new file mode 100644 index 00000000..e69de29b 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..0a83d9c6 --- /dev/null +++ b/test/java/common/rules/common_launcher_java_binary_tests.bzl @@ -0,0 +1,644 @@ +"""Parameterized tests for java_binary with --java_launcher""" + +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", "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/rules:java_helper.bzl", "helper") +load("//test/java/testutil:artifact_closure.bzl", "artifact_closure") +load("//test/java/testutil:binary_executable_subject.bzl", "expect_that_executable") +load("//test/java/testutil:helper.bzl", "always_passes") +load("//test/java/testutil:javac_action_subject.bzl", "javac_action_subject") +load("//test/java/testutil:mock_java_toolchain.bzl", "mock_java_toolchain") + +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 _test_java_binary_resources_only(name): + util.helper_target( + java_binary, + name = name + "/bin", + main_class = "doesnotmatter", + resources = [ + "someFile.xml", + "someOtherFile.xml", + ], + runtime_deps = [name + "/lib"], + ) + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["Xml.java"], + ) + util.helper_target( + mock_java_toolchain, + name = name + "/toolchain", + ) + + analysis_test( + name = name, + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + }, + extra_target_under_test_aspects = [artifact_closure.aspect], + impl = _test_java_binary_resources_only_impl, + target = name + "/bin", + ) + +def _test_java_binary_resources_only_impl(env, target): + deploy_jar = env.expect.that_target(target).action_named("JavaDeployJar").actual.outputs.to_list()[0] + env.expect.that_file(deploy_jar).basename().equals("bin_deploy.jar") + + # check that we do have a jar file build for bin, although + # it does not contain any source files + artifact_closure.of_target(env, target, extensions = ["jar"], initial = deploy_jar).contains_exactly([ + "{package}/JavaBuilder_deploy.jar", + "{package}/lib{test_name}/lib.jar", + "{package}/{test_name}/bin-class.jar", + "{package}/{test_name}/bin.jar", + "{package}/{test_name}/bin_deploy.jar", + ]) + artifact_closure.of_target(env, target, extensions = ["xml"], initial = deploy_jar).contains_exactly([ + "{package}/someFile.xml", + "{package}/someOtherFile.xml", + ]) + +def _test_java_binary_deploy_jar_coverage_setup(name): + util.helper_target( + java_binary, + name = name + "/app", + main_class = "com.google.app", + ) + + analysis_test( + name = name, + config_settings = { + "//command_line_option:collect_code_coverage": True, + }, + impl = _test_java_binary_deploy_jar_coverage_setup_impl, + target = name + "/app", + ) + +def _test_java_binary_deploy_jar_coverage_setup_impl(env, target): + assert_that_action = env.expect.that_target(target).action_generating("{package}/{name}_deploy.jar") + assert_that_action.argv().contains("Coverage-Main-Class: com.google.app") + +def _test_java_binary_transitive_dependency_from_java_library(name): + util.helper_target( + java_binary, + name = name + "/Binary", + srcs = ["Binary.java"], + deps = [name + "/somedep"], + ) + util.helper_target( + java_library, + name = name + "/somedep", + srcs = ["Dependency.java"], + deps = [name + "/otherdep"], + ) + util.helper_target( + java_library, + name = name + "/otherdep", + srcs = ["OtherDependency.java"], + ) + + analysis_test( + name = name, + extra_target_under_test_aspects = [artifact_closure.aspect], + impl = _test_java_binary_transitive_dependency_from_java_library_impl, + target = name + "/Binary", + ) + +def _test_java_binary_transitive_dependency_from_java_library_impl(env, target): + artifact_closure.of_target(env, target, extensions = ["java"]).contains_exactly([ + "{package}/Binary.java", + "{package}/Dependency.java", + "{package}/OtherDependency.java", + ]) + +def _test_java_binary_explicit_main_class(name): + util.helper_target( + java_binary, + name = name + "/bin", + main_class = "foo.bar.baz", + ) + + analysis_test( + name = name, + impl = _test_java_binary_explicit_main_class_impl, + target = name + "/bin", + ) + +def _test_java_binary_explicit_main_class_impl(env, target): + expect_that_executable.of_target(env, target).java_start_class().equals( + "foo.bar.baz", + ) + +def _test_java_binary_implicit_main_class(name): + util.helper_target( + java_binary, + name = name + "/Binary", + srcs = ["Binary.java"], + ) + + analysis_test( + name = name, + impl = _test_java_binary_implicit_main_class_impl, + target = name + "/Binary", + ) + +def _test_java_binary_implicit_main_class_impl(env, target): + expect_that_executable.of_target(env, target).java_start_class().equals( + "common.rules.{test_name}.Binary".format(test_name = env.ctx.label.name), + ) + +def _test_java_test_main_class(name): + util.helper_target( + java_test, + name = name + "/Test", + ) + + analysis_test( + name = name, + impl = _test_java_test_main_class_impl, + target = name + "/Test", + ) + +def _test_java_test_main_class_impl(env, target): + expect_that_executable.of_target(env, target).test_suite().equals( + "common.rules.{test_name}.Test".format(test_name = env.ctx.label.name), + ) + +def _test_java_test_main_class_with_dot(name): + util.helper_target( + java_test, + name = name + "/withdot.Test", + ) + + analysis_test( + name = name, + impl = _test_java_test_main_class_with_dot_impl, + target = name + "/withdot.Test", + ) + +def _test_java_test_main_class_with_dot_impl(env, target): + expect_that_executable.of_target(env, target).test_suite().equals( + "common.rules.{test_name}.withdot.Test".format(test_name = env.ctx.label.name), + ) + +def _test_java_test_has_assertions_enabled(name): + util.helper_target( + java_test, + name = name + "/testea", + ) + + analysis_test( + name = name, + impl = _test_java_test_has_assertions_enabled_impl, + target = name + "/testea", + ) + +def _test_java_test_has_assertions_enabled_impl(env, target): + expect_that_executable.of_target(env, target).jvm_flags().contains("-ea") + +# Tests that the native library path computed from a ".so" cc_binary dependency +# includes the transitive closure of that dependency. +def _test_java_binary_native_library_path_includes_transitive_deps(name): + util.helper_target( + java_binary, + name = name + "/app", + srcs = ["DoesNotMatter.java"], + deps = [name + "/jni.so"], + ) + util.helper_target( + java_binary, + name = name + "/runtime_app", + srcs = ["AlsoDoesNotMatter.java"], + runtime_deps = [name + "/jni.so"], + ) + util.helper_target( + cc_binary, + name = name + "/jni.so", + srcs = ["lib1.so"], + deps = [name + "/helper_lib"], + ) + util.helper_target( + cc_library, + name = name + "/helper_lib", + srcs = ["lib2.so"], + ) + + analysis_test( + name = name, + attrs = { + "_windows_constraints": attr.label_list(default = ["@platforms//os:windows"]), + "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")), + }, + impl = _test_java_binary_native_library_path_includes_transitive_deps_impl, + target = [ + name + "/app", + name + "/runtime_app", + ], + ) + +def _test_java_binary_native_library_path_includes_transitive_deps_impl(env, targets): + for target in targets: + if helper.is_target_platform_windows(env.ctx): + expect_that_executable.of_target(env, target).native_library_paths().contains_exactly([ + "${{JAVA_RUNFILES}}/{workspace}/{package}", + ]) + else: + expect_that_executable.of_target(env, target).native_library_paths().contains_exactly([ + "${{JAVA_RUNFILES}}/{workspace}/_solib_{cpu}/_//{package}:{test_name}/helper_lib___{package}", + "${{JAVA_RUNFILES}}/{workspace}/_solib_{cpu}/_//{package}:{test_name}/jni.so___{package}", + ]) + +# Regression test: the native library path separator on Windows is ";" instead of ":". +def _test_java_binary_native_library_path_separator(name): + util.helper_target( + java_binary, + name = name + "/app", + srcs = ["DoesNotMatter.java"], + deps = [ + name + "/lib1", + name + "/lib2", + ], + ) + + # Define two native libs (in different directories so java.library.path has multiple entries). + util.helper_target( + cc_binary, + name = name + "/lib1", + srcs = ["lib1.so"], + ) + util.helper_target( + cc_binary, + name = name + "/lib2", + srcs = ["subdir/lib2.so"], + ) + + analysis_test( + name = name, + attrs = { + "_windows_constraints": attr.label_list(default = ["@platforms//os:windows"]), + "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")), + }, + impl = _test_java_binary_native_library_path_separator_impl, + target = name + "/app", + ) + +def _test_java_binary_native_library_path_separator_impl(env, target): + # We should find two libraries on java.library.path (if the correct path separator was used). + native_library_paths = expect_that_executable.of_target(env, target).native_library_paths() + native_library_paths.has_size(2) + +# Regression test for bug 2774317: Problems with inner classes as main class. +def _test_java_binary_inner_class(name): + util.helper_target( + java_binary, + name = name + "/inner", + srcs = ["Main.java"], + main_class = "Main$Inner", + ) + + analysis_test( + name = name, + attrs = {"_windows_constraints": attr.label_list(default = ["@platforms//os:windows"])}, + impl = _test_java_binary_inner_class_impl, + target = name + "/inner", + ) + +def _test_java_binary_inner_class_impl(env, target): + if helper.is_target_platform_windows(env.ctx): + expected = "Main$Inner" # unquoted on windows + else: + expected = "'Main$Inner'" # shell-quoted + expect_that_executable.of_target(env, target).java_start_class().equals(expected) + +def _test_java_test_inner_class(name): + util.helper_target( + java_test, + name = name + "/inner", + test_class = "Outer$Inner", + ) + + analysis_test( + name = name, + attrs = {"_windows_constraints": attr.label_list(default = ["@platforms//os:windows"])}, + impl = _test_java_test_inner_class_impl, + target = name + "/inner", + ) + +def _test_java_test_inner_class_impl(env, target): + if helper.is_target_platform_windows(env.ctx): + expected = "Outer$Inner" # unquoted on windows + else: + expected = "'Outer$Inner'" # shell-quoted + expect_that_executable.of_target(env, target).test_suite().equals(expected) + +def _test_java_binary_strict_java_deps_flag(name): + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + always_passes(name) + return + + util.helper_target( + java_binary, + name = name + "/app", + srcs = ["App.java"], + deps = [ + name + "/alpha", + name + "/bravo", + ], + ) + util.helper_target( + java_library, + name = name + "/alpha", + srcs = ["Alpha.java"], + ) + util.helper_target( + java_library, + name = name + "/bravo", + srcs = ["Bravo.java"], + exports = [name + "/delta"], + deps = [name + "/charlie"], + ) + util.helper_target( + java_library, + name = name + "/charlie", + srcs = ["Charlie.java"], + ) + util.helper_target( + java_library, + name = name + "/delta", + srcs = ["Delta.java"], + ) + + analysis_test( + name = name, + attrs = { + "off": {"@config_settings": {"//command_line_option:experimental_strict_java_deps": "OFF"}}, + "warn": {"@config_settings": {"//command_line_option:experimental_strict_java_deps": "WARN"}}, + "error": {"@config_settings": {"//command_line_option:experimental_strict_java_deps": "ERROR"}}, + "strict": {"@config_settings": {"//command_line_option:experimental_strict_java_deps": "STRICT"}}, + }, + impl = _test_java_binary_strict_java_deps_flag_impl, + targets = { + "off": name + "/app", + "warn": name + "/app", + "error": name + "/app", + "strict": name + "/app", + }, + ) + +def _test_java_binary_strict_java_deps_flag_impl(env, targets): + # off + assert_that_javac = javac_action_subject.of(env, targets.off, desc = "off") + assert_that_javac.direct_dependencies().contains_exactly([]) + assert_that_javac.strict_java_deps().equals("OFF") + + for mode, target, expected_flag_value in [ + ("WARN", targets.warn, "WARN"), + ("ERROR", targets.error, "ERROR"), + ("STRICT", targets.strict, "ERROR"), + ]: + assert_that_javac = javac_action_subject.of(env, target, desc = "mode=" + mode) + assert_that_javac.strict_java_deps().equals(expected_flag_value) + assert_that_javac.direct_dependencies().contains_exactly([ + "{bindir}/{package}/lib{test_name}/alpha-hjar.jar", + "{bindir}/{package}/lib{test_name}/bravo-hjar.jar", + "{bindir}/{package}/lib{test_name}/delta-hjar.jar", + ]).in_order() + +def _test_java_binary_runtime_deps_transitivity(name): + util.helper_target( + java_library, + name = name + "/l0", + srcs = ["l0.java"], + ) + util.helper_target( + java_library, + name = name + "/l1", + srcs = ["l1.java"], + ) + util.helper_target( + java_library, + name = name + "/l2", + srcs = ["l2.java"], + runtime_deps = [name + "/l1"], + deps = [name + "/l0"], + ) + util.helper_target( + java_library, + name = name + "/l3", + srcs = ["l3.java"], + deps = [name + "/l2"], + ) + util.helper_target( + java_library, + name = name + "/l4", + srcs = ["l4.java"], + deps = [name + "/l1"], + ) + util.helper_target( + java_binary, + name = name + "/b1", + srcs = ["b1.java"], + deps = [name + "/l2"], + ) + util.helper_target( + java_binary, + name = name + "/b2", + srcs = ["b2.java"], + runtime_deps = [name + "/l3"], + ) + + analysis_test( + name = name, + impl = _test_java_binary_runtime_deps_transitivity_impl, + targets = { + "b1": name + "/b1", + "b2": name + "/b2", + }, + ) + +def _expect_that_deploy_jar_action_inputs(env, target, extension): + action = env.expect.that_target(target).action_generating("{package}/{name}_deploy.jar") + return subjects.collection( + action.actual.inputs.to_list(), + meta = action.meta, + ).transform( + desc = extension + " inputs", + filter = lambda f: f.extension == extension, + format = True, + map_each = lambda f: f.short_path, + ) + +def _test_java_binary_runtime_deps_transitivity_impl(env, targets): + _expect_that_deploy_jar_action_inputs(env, targets.b1, "jar").contains_exactly([ + "{package}/lib{test_name}/l0.jar", + "{package}/lib{test_name}/l1.jar", + "{package}/lib{test_name}/l2.jar", + "{package}/{test_name}/b1.jar", + ]) + _expect_that_deploy_jar_action_inputs(env, targets.b2, "jar").contains_exactly([ + "{package}/lib{test_name}/l0.jar", + "{package}/lib{test_name}/l1.jar", + "{package}/lib{test_name}/l2.jar", + "{package}/lib{test_name}/l3.jar", + "{package}/{test_name}/b2.jar", + ]) + +def _test_java_binary_runtime_deps_with_transitive_data(name): + util.helper_target( + java_binary, + name = name + "/bin", + srcs = ["Bin.java"], + runtime_deps = [name + "/lib"], + ) + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["Lib.java"], + deps = [name + "/bundle"], + ) + util.helper_target( + java_library, + name = name + "/bundle", + data = [name + "/fava"], + exports = [name + "/bundle2"], + ) + util.helper_target( + java_library, + name = name + "/bundle2", + data = [name + "/extra"], + ) + util.helper_target( + native.filegroup, + name = name + "/fava", + srcs = ["some.js"], + ) + util.helper_target( + native.filegroup, + name = name + "/extra", + srcs = ["some.gss"], + ) + + analysis_test( + name = name, + impl = _test_java_binary_runtime_deps_with_transitive_data_impl, + target = name + "/bin", + ) + +def _test_java_binary_runtime_deps_with_transitive_data_impl(env, target): + env.expect.that_target(target).runfiles().contains_at_least([ + "{workspace}/{package}/some.js", + "{workspace}/{package}/some.gss", + ]) + +def _test_java_binary_runtime_deps_with_genrule(name): + util.helper_target( + java_binary, + name = name + "/bin", + srcs = ["Bin.java"], + runtime_deps = [name + "/langtools"], + ) + util.helper_target( + native.genrule, + name = name + "/langtools", + outs = [name + "/langtools.jar"], + cmd = "", + ) + + analysis_test( + name = name, + impl = _test_java_binary_runtime_deps_with_genrule_impl, + target = name + "/bin", + ) + +def _test_java_binary_runtime_deps_with_genrule_impl(env, target): + env.expect.that_target(target).runfiles().contains( + "{workspace}/{package}/{test_name}/langtools.jar", + ) + +def _test_java_binary_runtime_deps_with_native_libraries(name): + util.helper_target( + java_binary, + name = name + "/bin", + srcs = ["Bin.java"], + runtime_deps = [name + "/lib"], + ) + util.helper_target( + java_library, + name = name + "/lib", + srcs = ["Foo.java"], + deps = [name + "/jni.so"], + ) + util.helper_target( + cc_binary, + name = name + "/jni.so", + srcs = ["lib1.so"], + deps = [name + "/helper_library"], + ) + util.helper_target( + cc_library, + name = name + "/helper_library", + srcs = ["lib2.so"], + ) + + analysis_test( + name = name, + impl = _test_java_binary_runtime_deps_with_native_libraries_impl, + target = name + "/bin", + ) + +def _test_java_binary_runtime_deps_with_native_libraries_impl(env, target): + env.expect.that_target(target).runfiles().paths().transform( + desc = "basenames", + map_each = lambda f: reversed(f.rsplit("/"))[0], + ).contains_at_least(["lib1.so", "lib2.so"]) + +def java_binary_launcher_tests(name): + test_suite( + name = name, + tests = [ + _test_java_binary_non_executable_rule_outputs, + _test_java_binary_resources_only, + _test_java_binary_deploy_jar_coverage_setup, + _test_java_binary_transitive_dependency_from_java_library, + _test_java_binary_explicit_main_class, + _test_java_binary_implicit_main_class, + _test_java_test_main_class, + _test_java_test_main_class_with_dot, + _test_java_test_has_assertions_enabled, + _test_java_binary_native_library_path_includes_transitive_deps, + _test_java_binary_native_library_path_separator, + _test_java_binary_inner_class, + _test_java_test_inner_class, + _test_java_binary_strict_java_deps_flag, + _test_java_binary_runtime_deps_transitivity, + _test_java_binary_runtime_deps_with_transitive_data, + _test_java_binary_runtime_deps_with_genrule, + _test_java_binary_runtime_deps_with_native_libraries, + ], + ) 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..57d3035a --- /dev/null +++ b/test/java/common/rules/deploy_archive_builder_tests.bzl @@ -0,0 +1,45 @@ +"""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")], + }, + ) + +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..6ee32ba3 --- /dev/null +++ b/test/java/common/rules/java_binary_tests.bzl @@ -0,0 +1,626 @@ +"""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, + }, + ) + +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.argv().contains("--normalize") + assert_deploy_jar_action.argv().not_contains("--exclude_build_data") + assert_deploy_jar_action.argv().contains("--build_info_file") + 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 _test_java_binary_no_launcher_dep_if_not_executable(name): + util.helper_target( + java_binary, + name = name + "/bin", + srcs = ["bin.java"], + create_executable = False, + ) + util.helper_target( + cc_binary, + name = name + "/broken", + srcs = ["launcher.cc"], + deps = [":doesnotexist"], + ) + + analysis_test( + name = name, + impl = lambda *args, **kwargs: None, # no error: good + target = name + "/bin", + config_settings = { + "//command_line_option:java_launcher": Label(name + "/broken"), + }, + ) + +# regression test for #887069 +def _test_java_binary_java_package(name): + analysis_test( + name = name, + impl = _test_java_binary_java_package_impl, + target = "//test/testdata:non_java", + expect_failure = True, + ) + +def _test_java_binary_java_package_impl(env, target): + env.expect.that_target(target).failures().contains_predicate(matching.str_matches( + "main_class was not provided and cannot be inferred", + )) + +# Regression test for CL 7314966. +def _test_java_binary_rule_in_sub_directory(name): + util.helper_target( + java_binary, + name = "subdir/MyBinary", + srcs = ["subdir/MyBinary.java"], + ) + + analysis_test( + name = name, + impl = lambda *args, **kwargs: None, # should not be an error + target = "subdir/MyBinary", + ) + +def _test_java_binary_duplicate_classpath_resources(name): + util.helper_target( + native.filegroup, + name = name + "/a", + srcs = ["a/same.txt"], + ) + util.helper_target( + native.filegroup, + name = name + "/b", + srcs = ["b/same.txt"], + ) + util.helper_target( + java_binary, + name = name + "/app", + srcs = ["App.java"], + classpath_resources = [ + name + "/a", + name + "/b", + ], + ) + + analysis_test( + name = name, + impl = _test_java_binary_duplicate_classpath_resources_impl, + target = name + "/app", + expect_failure = True, + ) + +def _test_java_binary_duplicate_classpath_resources_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("entries must have different file names (duplicate: same.txt)"), + ) + +def _test_java_binary_jdeps(name): + util.helper_target( + java_binary, + name = name + "/app", + srcs = ["App.java"], + resources = ["app.properties"], + ) + + analysis_test( + name = name, + impl = _test_java_binary_jdeps_impl, + target = name + "/app", + ) + +def _test_java_binary_jdeps_impl(env, target): + # should produce an app.jdeps file, not an app-class.jdeps file. + env.expect.that_target(target).action_generating("{package}/{name}-class.jar").contains_flag_values([ + ("--output_deps_proto", "{bindir}/{package}/{name}.jdeps"), + ]) + +def _test_java_binary_deps_without_srcs(name): + util.helper_target( + java_binary, + name = name + "/a", + srcs = [], + deps = [name + "/b"], + main_class = "B", + ) + util.helper_target( + java_library, + name = name + "/b", + srcs = ["B.java"], + ) + + analysis_test( + name = name, + impl = _test_java_binary_deps_without_srcs_impl, + target = name + "/a", + expect_failure = True, + ) + +def _test_java_binary_deps_without_srcs_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("deps not allowed without srcs"), + ) + +def _test_java_binary_srcs_filetypes(name): + util.helper_target( + java_binary, + name = name + "/valid", + srcs = [ + "a.java", + name + "/gmix", + name + "/gvalid", + ], + ) + util.helper_target( + java_binary, + name = name + "/invalid", + srcs = [ + "a.foo", + name + "/ginvalid", + ], + ) + util.helper_target( + java_binary, + name = name + "/mix", + srcs = [ + "a.foo", + "a.java", + ], + ) + util.helper_target( + native.genrule, + name = name + "/gvalid", + outs = [name + "/b.java"], + cmd = "", + ) + util.helper_target( + native.genrule, + name = name + "/ginvalid", + outs = [name + "/b.foo"], + cmd = "", + ) + util.helper_target( + native.genrule, + name = name + "/gmix", + outs = [name + "/c.java", name + "/c.foo"], + cmd = "", + ) + + analysis_test( + name = name, + impl = _test_java_binary_srcs_filetypes_impl, + targets = { + "valid": name + "/valid", + "invalid": name + "/invalid", + "mix": name + "/mix", + }, + expect_failure = True, + ) + +def _test_java_binary_srcs_filetypes_impl(env, targets): + env.expect.that_target(targets.valid).failures().contains_exactly([]) + env.expect.that_target(targets.invalid).failures().contains_at_least_predicates([ + matching.str_matches("'*/ginvalid' does not produce any java_binary srcs files"), + matching.str_matches("source file '*:a.foo' is misplaced here"), + ]) + env.expect.that_target(targets.mix).failures().contains_at_least_predicates([ + matching.str_matches("'*:a.foo' does not produce any java_binary srcs files"), + ]) + +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, + _test_java_binary_no_launcher_dep_if_not_executable, + _test_java_binary_java_package, + _test_java_binary_rule_in_sub_directory, + _test_java_binary_duplicate_classpath_resources, + _test_java_binary_jdeps, + _test_java_binary_deps_without_srcs, + _test_java_binary_srcs_filetypes, + ], + ) diff --git a/test/java/common/rules/java_helper_tests.bzl b/test/java/common/rules/java_helper_tests.bzl new file mode 100644 index 00000000..89a44d24 --- /dev/null +++ b/test/java/common/rules/java_helper_tests.bzl @@ -0,0 +1,134 @@ +"""Tests for java_helper functions""" + +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load( + "//java/common/rules:java_helper.bzl", + "helper", +) + +def _java_segments_test_impl(ctx): + env = unittest.begin(ctx) + + asserts.equals( + env, + ["com", "google", "foo", "Foo"], + helper.java_segments("java/com/google/foo/Foo"), + ) + asserts.equals( + env, + ["com", "google", "foo", "FooTest"], + helper.java_segments("javatests/com/google/foo/FooTest"), + ) + asserts.equals( + env, + ["com", "google", "foo", "Foo"], + helper.java_segments("src/main/java/com/google/foo/Foo"), + ) + asserts.equals( + env, + ["com", "google", "foo", "FooTest"], + helper.java_segments("src/test/java/com/google/foo/FooTest"), + ) + asserts.equals( + env, + ["com", "google", "foo", "Foo"], + helper.java_segments("third_party/java/foo/java/com/google/foo/Foo"), + ) + asserts.equals( + env, + ["com", "google", "foo", "FooTest"], + helper.java_segments("third_party/java/foo/javatests/com/google/foo/FooTest"), + ) + asserts.equals( + env, + ["com", "google", "foo", "Foo"], + helper.java_segments("third_party/java/foo/src/main/java/com/google/foo/Foo"), + ) + asserts.equals( + env, + ["com", "google", "foo", "FooTest"], + helper.java_segments("third_party/java/foo/src/test/java/com/google/foo/FooTest"), + ) + asserts.equals( + env, + ["foo", "org", "foo", "Foo"], + helper.java_segments("third_party/java/foo/org/foo/Foo"), + ) + asserts.equals( + env, + None, + helper.java_segments("foo/bar/Baz"), + ) + + asserts.equals( + env, + "com/google/foo/Foo", + helper.java_path("java/com/google/foo/Foo"), + ) + asserts.equals( + env, + "com/google/foo/Foo", + helper.java_path("third_party/java/foo/java/com/google/foo/Foo"), + ) + asserts.equals( + env, + None, + helper.java_path("foo/bar/Baz"), + ) + + asserts.equals( + env, + "java", + helper.java_root("java/com/google/foo/Foo"), + ) + asserts.equals( + env, + "javatests", + helper.java_root("javatests/com/google/foo/FooTest"), + ) + asserts.equals( + env, + "foo/java", + helper.java_root("foo/java/com/google/foo/Foo"), + ) + asserts.equals( + env, + "third_party/java/foo/java", + helper.java_root("third_party/java/foo/java/com/google/foo/Foo"), + ) + asserts.equals( + env, + None, + helper.java_root("foo/bar/Baz"), + ) + + asserts.equals( + env, + "com.google.foo.Foo", + helper.full_classname("java/com/google/foo/Foo"), + ) + asserts.equals( + env, + "com.google.foo.Foo", + helper.full_classname("third_party/java/foo/java/com/google/foo/Foo"), + ) + asserts.equals( + env, + None, + helper.full_classname("foo/bar/Baz"), + ) + + return unittest.end(env) + +java_segments_test = unittest.make(_java_segments_test_impl) + +def java_helper_test_suite(name): + """Sets up java_helper test suite + + Args: + name: the name of the test suite target + """ + unittest.suite( + name, + java_segments_test, + ) 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..ead39e4c --- /dev/null +++ b/test/java/common/rules/java_import_tests.bzl @@ -0,0 +1,1029 @@ +"""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 https://github.com/bazelbuild/rules_java/issues/362: every java_import +# records a jdeps proto as its compile_jdeps so that reduced classpaths of downstream compilations +# are not missing its transitive dependencies. +def _test_compile_jdeps_propagated_for_deps(name): + util.helper_target( + java_import, + name = name + "/import-jar", + jars = ["import.jar"], + deps = [name + "/depjar"], + ) + util.helper_target( + java_import, + name = name + "/incomplete-jar", + jars = ["incomplete.jar"], + deps = [name + "/depjar"], + tags = ["incomplete-deps"], + ) + util.helper_target( + java_import, + name = name + "/depjar", + jars = ["depjar.jar"], + ) + + analysis_test( + name = name, + impl = _test_compile_jdeps_propagated_for_deps_impl, + targets = { + "incomplete_deps": name + "/incomplete-jar", + "with_deps": name + "/import-jar", + "without_deps": name + "/depjar", + }, + ) + +def _test_compile_jdeps_propagated_for_deps_impl(env, targets): + for name in ["with_deps", "without_deps", "incomplete_deps"]: + java_info_subject.from_target( + env, + getattr(targets, name), + expr = name, + ).compilation_args().compile_time_java_dependencies().contains_exactly([ + "{package}/_java_import/{name}/jdeps.proto", + ]) + +# 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", + ) + +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, + ) + +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_compile_jdeps_propagated_for_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..ec5500ce --- /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().equals(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..b6e48610 --- /dev/null +++ b/test/java/common/rules/java_plugin_tests.bzl @@ -0,0 +1,387 @@ +"""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", + 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..87c92463 --- /dev/null +++ b/test/java/common/rules/java_single_jar_tests.bzl @@ -0,0 +1,346 @@ +"""Tests for the java_single_jar rule""" + +load("@bazel_features//:features.bzl", "bazel_features") +load("@rules_testing//lib:analysis_test.bzl", _analysis_test = "analysis_test") +load("@rules_testing//lib:truth.bzl", "matching") +load("@rules_testing//lib:util.bzl", "util") +load("@rules_testing//lib/private:util.bzl", "get_test_name_from_function") # buildifier: disable=bzl-visibility +load("//java:java_binary.bzl", "java_binary") +load("//java:java_library.bzl", "java_library") +load("//java:java_single_jar.bzl", "java_single_jar") +load("//java/common:java_semantics.bzl", "semantics") + +def _path_stripping_analysis_test(name, impl, config_settings = {}, **kwargs): + if name.endswith("_off"): + output_path_mode = "off" + elif name.endswith("_strip"): + output_path_mode = "strip" + else: + fail("expected test name to end with one of [_strip, _off]") + + # TODO: remove once Bazel 7 is no longer supported + if bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + config_settings = config_settings | { + "//command_line_option:experimental_output_paths": output_path_mode, + } + + _analysis_test( + name = name, + impl = lambda env, target: impl(env, target, output_path_mode), + config_settings = config_settings, + **kwargs + ) + +def _path_stripping_test_suite(name, *, tests = [], test_kwargs = {}): + """Expands each test into 'off' and 'strip' output path mode variants""" + test_targets = [] + for suffix in ["_off", "_strip"]: + for setup_func in tests: + test_name = get_test_name_from_function(setup_func) + suffix + setup_func(name = test_name, **test_kwargs) + test_targets.append(test_name) + native.test_suite( + name = name, + tests = test_targets, + ) + +def _get_bindir_for_output_path_mode(output_path_mode): + # TODO: remove once Bazel 7 is no longer supported + if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: + return "{bindir}" + + if output_path_mode == "off": + return "{bindir}" + elif output_path_mode == "strip": + return "{cfg_stripped_bindir}" + fail("unexpected output path mode: " + output_path_mode) + +def _test_java_single_jar_basic(name): + util.helper_target( + java_single_jar, + name = name + "/jar", + deps = ["1.jar", "2.jar"], + ) + + _path_stripping_analysis_test( + name = name, + impl = _test_java_single_jar_basic_impl, + target = name + "/jar", + ) + +def _test_java_single_jar_basic_impl(env, target, output_path_mode): + 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", + _get_bindir_for_output_path_mode(output_path_mode) + "/{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, + ) + + _path_stripping_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 _assert_build_info_files(env, jar_target, build_info_files, output_path_mode): + bin_dir = _get_bindir_for_output_path_mode(output_path_mode) + assert_that_action = env.expect.that_target(jar_target).action_named("JavaSingleJar") + assert_that_action.contains_flag_values([ + ("--build_info_file", bin_dir + f.path[len(f.root.path):]) + for f in build_info_files.to_list() + ]) + +def _test_java_single_jar_force_enable_stamping_impl(env, targets, output_path_mode): + _assert_build_info_files( + env, + targets.jar, + targets.build_info[OutputGroupInfo].non_redacted_build_info_files, + output_path_mode, + ) + +def _test_java_single_jar_force_disable_stamping(name): + util.helper_target( + java_single_jar, + name = name + "/jar", + stamp = 0, + exclude_build_data = False, + ) + + _path_stripping_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, output_path_mode): + _assert_build_info_files( + env, + targets.jar, + targets.build_info[OutputGroupInfo].redacted_build_info_files, + output_path_mode, + ) + +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, + ) + + _path_stripping_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, output_path_mode): + _assert_build_info_files( + env, + targets.jar, + targets.build_info[OutputGroupInfo].non_redacted_build_info_files, + output_path_mode, + ) + +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, + ) + + _path_stripping_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, output_path_mode): + _assert_build_info_files( + env, + targets.jar, + targets.build_info[OutputGroupInfo].redacted_build_info_files, + output_path_mode, + ) + +def _test_java_single_jar_deploy_manifest_lines(name): + util.helper_target( + java_single_jar, + name = name + "/jar", + deps = ["1.jar"], + deploy_manifest_lines = [ + "Manifest-Entry-A: valueA", + "Manifest-Entry-B: line1,\n line2", + ], + ) + + _analysis_test( + name = name, + impl = _test_java_single_jar_deploy_manifest_lines_impl, + target = name + "/jar", + ) + +def _test_java_single_jar_deploy_manifest_lines_impl(env, target): + assert_that_action = env.expect.that_target(target).action_named("JavaSingleJar") + assert_that_action.argv().contains_at_least([ + "--deploy_manifest_lines", + "Manifest-Entry-A: valueA", + "Manifest-Entry-B: line1,\n line2", + ]) + +def _test_java_single_jar_transitive_deploy_env(name): + util.helper_target( + java_library, + name = name + "_lib_a", + srcs = ["A.java"], + ) + util.helper_target( + java_library, + name = name + "_lib_b", + srcs = ["B.java"], + ) + util.helper_target( + java_library, + name = name + "_lib_c", + srcs = ["C.java"], + ) + util.helper_target( + java_single_jar, + name = name + "_env1", + deps = [name + "_lib_a"], + ) + util.helper_target( + java_single_jar, + name = name + "_inner", + deps = [name + "_lib_a", name + "_lib_b"], + deploy_env = [name + "_env1"], + ) + util.helper_target( + java_single_jar, + name = name + "_outer", + deps = [name + "_lib_a", name + "_lib_b", name + "_lib_c"], + deploy_env = [name + "_inner"], + ) + + _path_stripping_analysis_test( + name = name, + impl = _test_java_single_jar_transitive_deploy_env_impl, + target = name + "_outer", + ) + +def _test_java_single_jar_transitive_deploy_env_impl(env, target, output_path_mode): + bin_dir = _get_bindir_for_output_path_mode(output_path_mode) + assert_that_action = env.expect.that_target(target).action_named("JavaSingleJar") + assert_that_action.argv().contains_at_least([ + "--sources", + bin_dir + "/{package}/lib{test_name}_lib_c.jar", + "--output", + ]) + assert_that_action.argv().not_contains(bin_dir + "/{package}/lib{test_name}_lib_a.jar") + assert_that_action.argv().not_contains(bin_dir + "/{package}/lib{test_name}_lib_b.jar") + +def _test_java_binary_deploy_env_with_java_single_jar(name): + util.helper_target( + java_library, + name = name + "_lib_a", + srcs = ["A.java"], + ) + util.helper_target( + java_library, + name = name + "_lib_b", + srcs = ["B.java"], + ) + util.helper_target( + java_single_jar, + name = name + "_single_jar", + deps = [name + "_lib_a"], + ) + util.helper_target( + java_binary, + name = name + "_bin", + main_class = "Main", + runtime_deps = [name + "_lib_a", name + "_lib_b"], + deploy_env = [name + "_single_jar"], + ) + + _analysis_test( + name = name, + impl = _test_java_binary_deploy_env_with_java_single_jar_impl, + target = name + "_bin", + ) + +def _test_java_binary_deploy_env_with_java_single_jar_impl(env, target): + assert_that_action = env.expect.that_target(target).action_named("JavaDeployJar") + assert_that_action.inputs().contains_at_least([ + "{package}/lib{test_name}_lib_b.jar", + ]) + assert_that_action.inputs().not_contains("{package}/lib{test_name}_lib_a.jar") + +def java_single_jar_tests(name): + _path_stripping_test_suite( + name = name, + tests = [ + _test_java_single_jar_basic, + _test_java_single_jar_deploy_manifest_lines, + _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, + _test_java_single_jar_transitive_deploy_env, + _test_java_binary_deploy_env_with_java_single_jar, + ], + ) 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..04674ee7 --- /dev/null +++ b/test/java/common/rules/java_test_tests.bzl @@ -0,0 +1,480 @@ +"""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_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_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 _test_java_test_srcs_filetypes(name): + util.helper_target( + java_test, + name = name + "/valid", + srcs = [ + "a.java", + name + "/gmix", + name + "/gvalid", + ], + ) + util.helper_target( + java_test, + name = name + "/invalid", + srcs = [ + "a.foo", + name + "/ginvalid", + ], + ) + util.helper_target( + java_test, + name = name + "/mix", + srcs = [ + "a.foo", + "a.java", + ], + ) + util.helper_target( + native.genrule, + name = name + "/gvalid", + outs = [name + "/b.java"], + cmd = "", + ) + util.helper_target( + native.genrule, + name = name + "/ginvalid", + outs = [name + "/b.foo"], + cmd = "", + ) + util.helper_target( + native.genrule, + name = name + "/gmix", + outs = [name + "/c.java", name + "/c.foo"], + cmd = "", + ) + + analysis_test( + name = name, + impl = _test_java_test_srcs_filetypes_impl, + targets = { + "valid": name + "/valid", + "invalid": name + "/invalid", + "mix": name + "/mix", + }, + expect_failure = True, + ) + +def _test_java_test_srcs_filetypes_impl(env, targets): + env.expect.that_target(targets.valid).failures().contains_exactly([]) + env.expect.that_target(targets.invalid).failures().contains_at_least_predicates([ + matching.str_matches("'*/ginvalid' does not produce any java_test srcs files"), + matching.str_matches("source file '*:a.foo' is misplaced here"), + ]) + env.expect.that_target(targets.mix).failures().contains_at_least_predicates([ + matching.str_matches("'*:a.foo' does not produce any java_test srcs files"), + ]) + +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_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, + _test_java_test_srcs_filetypes, + ], + ) 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..48353582 --- /dev/null +++ b/test/java/testutil/artifact_closure.bzl @@ -0,0 +1,94 @@ +"""Helper for computing the artifact closure of a target""" + +load("@rules_testing//lib:truth.bzl", "subjects") + +# TODO: consider upstreaming this to @rules_testing + +def _compute(target, initial = None): + if not initial: + to_process = target[DefaultInfo].files.to_list() + else: + to_process = [initial] + 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 = ["*"]) + +def _of_target( + env, + target, + initial = None, + as_paths = True, + extensions = None, + package_local_only = True): + meta = env.expect.meta.derive(format_str_kwargs = { + "name": target.label.name, + "package": target.label.package, + }) + result = subjects.collection( + _compute(target, initial = initial), + meta = meta, + container_name = "artifact clousre of {}".format(target.label), + format = True, + ) + if extensions: + result = result.transform( + filter = lambda f: f.extension in extensions, + desc = "with extensions: {exts}".format(exts = extensions), + ) + if package_local_only: + result = result.transform( + filter = lambda f: f.short_path.startswith(target.label.package), + desc = "in package: {package}".format(package = target.label.package), + ) + if as_paths: + result = result.transform( + desc = "short_path", + map_each = lambda f: f.short_path, + ) + return result + +artifact_closure = struct( + aspect = _aspect, + of_target = _of_target, +) diff --git a/test/java/testutil/binary_executable_subject.bzl b/test/java/testutil/binary_executable_subject.bzl new file mode 100644 index 00000000..29df32d0 --- /dev/null +++ b/test/java/testutil/binary_executable_subject.bzl @@ -0,0 +1,67 @@ +"""Custom rules_testing subject for java_binary/java_test executable output""" + +load("@rules_cc//cc/common:cc_common.bzl", "cc_common") +load("@rules_testing//lib:truth.bzl", "subjects") +load("//java/common:java_semantics.bzl", "semantics") +load("//java/common/rules:java_helper.bzl", "helper") + +def _of_target(env, target): + executable = target[DefaultInfo].files_to_run.executable.short_path + action_subject = env.expect.that_target(target).action_generating(executable) + public = struct( + java_start_class = lambda: _java_start_class_subject(action_subject), + test_suite = lambda: _test_suite_subject(action_subject), + jvm_flags = lambda: _jvm_flags_subject(action_subject), + native_library_paths = lambda: _native_library_paths_subject(env, action_subject), + ) + return public + +def _java_start_class_subject(action): + if action.actual.substitutions: + return action.substitutions().get("%java_start_class%", factory = subjects.str) + else: + # Windows + return action.argv().transform( + filter = lambda e: e.startswith("java_start_class="), + map_each = lambda e: e.split("=", 1)[1], + desc = "java_start_class", + ).offset(0, factory = subjects.str) + +def _test_suite_subject(action): + return _jvm_flags_subject(action).transform( + filter = lambda e: e.startswith("-D" + semantics.TEST_SUITE_PROPERTY_NAME + "="), + map_each = lambda e: e.split("=", 1)[1], + desc = semantics.TEST_SUITE_PROPERTY_NAME, + ).offset(0, factory = subjects.str) + +def _jvm_flags_subject(action): + if action.actual.substitutions: + return action.substitutions().get("%jvm_flags%", factory = subjects.str).split(" ") + else: + # Windows + return action.argv().transform( + filter = lambda e: e.startswith("jvm_flags="), + map_each = lambda e: e.split("=", 1)[1], + desc = "jvm_flags", + ).offset(0, factory = subjects.str).split("\t") + +def _native_library_paths_subject(env, action): + separator = ";" if helper.is_target_platform_windows(env.ctx) else ":" + return _jvm_flags_subject(action).transform( + filter = lambda e: e.startswith("-Djava.library.path="), + map_each = lambda e: e.split("=", 1)[1], + desc = "native library paths", + ).offset( + 0, + factory = lambda actual, meta: subjects.str(actual, meta.derive( + format_str_kwargs = {"cpu": meta.ctx.attr._cc_toolchain[cc_common.CcToolchainInfo].cpu}, + )), + ).split(separator).transform( + map_each = lambda e: e.replace("_U", "_").replace("_S", "/").replace("_C", ":"), + desc = "pretty", + format = True, + ) + +expect_that_executable = struct( + 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..3fd8f860 --- /dev/null +++ b/test/java/testutil/cc_info_subject.bzl @@ -0,0 +1,112 @@ +"""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), + dynamic_libraries = lambda: _new_library_to_link_dynamic_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 _new_library_to_link_dynamic_libraries_subject(libraries_to_link, meta): + return subjects.depset_file( + [testutil.cc_library_to_link_dynamic_library(lib) for lib in libraries_to_link], + meta = meta.derive("dynamic_library()"), + ) + +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..8df4f786 --- /dev/null +++ b/test/java/testutil/java_info_subject.bzl @@ -0,0 +1,216 @@ +"""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, expr = None): + return _new_java_info_subject(target[JavaInfo], meta = truth.expect(env).meta.derive( + expr = expr, + 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..41bf2e35 --- /dev/null +++ b/test/java/testutil/javac_action_subject.bzl @@ -0,0 +1,84 @@ +"""Bespoke rules_testing subject for the Java compilation action""" + +load("@rules_testing//lib:truth.bzl", "subjects") +load("@rules_testing//lib:util.bzl", "TestingAspectInfo") + +def _new_javac_action_subject(env, target, output = None, desc = ""): + target_subject = env.expect.that_target(target) + if output: + action_subject = target_subject.action_generating(output) + else: + action_subject = target_subject.action_named("Javac") + self = struct( + actual = action_subject.actual, + parsed_flags = _parse_flags(action_subject.actual.argv), + meta = target_subject.meta.derive( + desc + ".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, default = []), + 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"]).offset(0, factory = subjects.str), + 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), + fix_deps_tool = lambda: _create_subject_for_flag("--experimental_fix_deps_tool", self.parsed_flags, self.meta).offset(0, factory = subjects.str), + ) + 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..af4e69c3 --- /dev/null +++ b/test/java/toolchains/java_toolchain_tests.bzl @@ -0,0 +1,766 @@ +"""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")], + }, + ) + +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 = lambda unused_env, unused_target: None, # Implicitly succeeds. + target = name + "/a", + config_settings = { + "//command_line_option:extra_toolchains": [Label(name + "/toolchain")], + "//command_line_option:java_header_compilation": "false", + }, + ) + +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", + 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")], + }, + ) + +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 new file mode 100644 index 00000000..a251c7b7 --- /dev/null +++ b/test/repo/BUILD.bazel @@ -0,0 +1,71 @@ +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 = ["src/Main.java"], +) + +java_binary( + name = "bin", + 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 new file mode 100644 index 00000000..5fdac278 --- /dev/null +++ b/test/repo/MODULE.bazel @@ -0,0 +1,74 @@ +module(name = "integration_test_repo") + +bazel_dep(name = "rules_java", version = "7.5.0") +archive_override( + module_name = "rules_java", + 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 = "zulu25.36.205-ca-jdk25.0.4.1-linux_x64", + target_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + urls = [ + "https://cdn.azul.com/zulu/bin/zulu25.36.205-ca-jdk25.0.4.1-linux_x64.tar.gz", + ], + version = "25", +) +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..a100961e --- /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 = "zulu25.36.205-ca-jdk25.0.4.1-linux_x64", + target_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + urls = [ + "https://cdn.azul.com/zulu/bin/zulu25.36.205-ca-jdk25.0.4.1-linux_x64.tar.gz", + ], + version = "25", +) + +register_toolchains("@my_funky_jdk_toolchain_config_repo//:all") diff --git a/test/repo/WORKSPACE.bzlmod b/test/repo/WORKSPACE.bzlmod new file mode 100644 index 00000000..e69de29b 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/Main.java b/test/repo/src/Main.java new file mode 100644 index 00000000..6029b2a1 --- /dev/null +++ b/test/repo/src/Main.java @@ -0,0 +1 @@ +public class Main {} 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 b/test/testdata/BUILD new file mode 100644 index 00000000..2de4a356 --- /dev/null +++ b/test/testdata/BUILD @@ -0,0 +1,21 @@ +load("@rules_testing//lib:util.bzl", "util") +load("//java:java_binary.bzl", "java_binary") +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"], +) + +# Used by //test/java/common/rules/java_binary_tests.bzl%_test_java_binary_java_package +util.helper_target( + java_binary, + name = "non_java", + srcs = ["Foo.java"], + visibility = [ + "//test/java/common/rules:__pkg__", + ], +) 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 b208e7b7..e6056073 100644 --- a/toolchains/BUILD +++ b/toolchains/BUILD @@ -1,9 +1,15 @@ -load("@rules_cc//cc:defs.bzl", "cc_library") +load("@bazel_skylib//:bzl_library.bzl", "bzl_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", ) @@ -14,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"]) @@ -29,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: @@ -68,17 +87,6 @@ filegroup( # TODO: migrate away from using @bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type ? # toolchain_type(name = "bootstrap_runtime_toolchain_type") -# The Java runtime to extract the bootclasspath from that is then used to compile Java sources. -# -# As the bootclasspath is platform independent, toolchains of this type may have no constraints. -# Purely as an optimization to prevent unnecessary fetches of remote runtimes for other -# architectures, toolchains of this type may have constraints on the execution platform that match -# those on the corresponding compilation toolchain. -# -# Toolchains of this type are only consumed internally by the bootclasspath rule and should not be -# accessed from Starlark. -# toolchain_type(name = "bootstrap_runtime_toolchain_type") - # Points to toolchain[":runtime_toolchain_type"] (was :legacy_current_java_runtime) java_runtime_alias(name = "current_java_runtime") @@ -121,37 +129,40 @@ java_runtime_files( srcs = ["include/openbsd/jni_md.h"], ) +java_runtime_files( + name = "jni_md_header-netbsd", + srcs = ["include/netbsd/jni_md.h"], +) + +# Defined here rather than taken from @bazel_tools//src/conditions so that it +# resolves with every Bazel version: @platforms//os:netbsd has been in the +# platforms module since 0.0.11. +config_setting( + name = "netbsd", + constraint_values = ["@platforms//os:netbsd"], +) + # The Java native interface. Depend on this package if you #include . # # See test_jni in third_party/bazel/src/test/shell/bazel/bazel_java_test.sh for # an example of using Bazel to build a Java program that calls a C function. -# -# TODO(ilist): use //src:condition:linux when released in Bazel cc_library( name = "jni", hdrs = [":jni_header"] + select({ "@bazel_tools//src/conditions:darwin": [":jni_md_header-darwin"], "@bazel_tools//src/conditions:freebsd": [":jni_md_header-freebsd"], - "@bazel_tools//src/conditions:linux_aarch64": [":jni_md_header-linux"], - "@bazel_tools//src/conditions:linux_mips64": [":jni_md_header-linux"], - "@bazel_tools//src/conditions:linux_ppc64le": [":jni_md_header-linux"], - "@bazel_tools//src/conditions:linux_riscv64": [":jni_md_header-linux"], - "@bazel_tools//src/conditions:linux_s390x": [":jni_md_header-linux"], - "@bazel_tools//src/conditions:linux_x86_64": [":jni_md_header-linux"], + "@bazel_tools//src/conditions:linux": [":jni_md_header-linux"], "@bazel_tools//src/conditions:openbsd": [":jni_md_header-openbsd"], + ":netbsd": [":jni_md_header-netbsd"], "@bazel_tools//src/conditions:windows": [":jni_md_header-windows"], "//conditions:default": [], }), includes = ["include"] + select({ "@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_ppc64le": ["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:linux": ["include/linux"], "@bazel_tools//src/conditions:openbsd": ["include/openbsd"], + ":netbsd": ["include/netbsd"], "@bazel_tools//src/conditions:windows": ["include/win32"], "//conditions:default": [], }), @@ -170,18 +181,34 @@ 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"], + ), ) for OS in [ "linux", + "linux_aarch64", "darwin_x86_64", "darwin_arm64", "windows", ] ] -# On Windows, executables end in ".exe", but the label we reach it through -# must be platform-independent. Thus, we create a little filegroup that -# contains the appropriate platform-dependent file. +[ + alias( + name = "turbine_direct_graal_%s" % OS, + actual = "@remote_java_tools_%s//:turbine_direct_graal" % OS, + ) + for OS in [ + "linux", + "linux_aarch64", + "darwin_arm64", + "windows", + ] +] + alias( name = "ijar", actual = ":ijar_prebuilt_binary_or_cc_binary", @@ -193,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", "//conditions:default": "@remote_java_tools//:ijar_cc_binary", }), @@ -204,16 +232,11 @@ 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", }), ) -# On Windows, Java implementation of singlejar is used. We create a little -# filegroup that contains the appropriate platform-dependent file. -# Once https://github.com/bazelbuild/bazel/issues/2241 is fixed (that is, -# the native singlejar is used on windows), this file group can be reused since -# on Windows, executables end in ".exe", but the label we reach it through -# must be platform-independent. alias( name = "singlejar", actual = ":singlejar_prebuilt_or_cc_binary", @@ -225,6 +248,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", }), @@ -236,14 +260,117 @@ 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", +) + +alias( + name = "turbine_direct_graal_or_java", + actual = select({ + "@bazel_tools//src/conditions:darwin_arm64": ":turbine_direct_graal_darwin_arm64", + "@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", + }), +) + +alias( + name = "turbine_direct_graal", + actual = select({ + "@bazel_tools//src/conditions:darwin_arm64": ":turbine_direct_graal_darwin_arm64", + "@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", + }), + tags = ["manual"], # So we can build //... on CI platforms that don't match +) + +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( @@ -257,11 +384,11 @@ alias( actual = ":toolchain", ) -RELEASES = (8, 9, 10, 11) +RELEASES = (8, 9, 10, 11, 17, 21, 25) [ default_java_toolchain( - name = "toolchain_java%d" % release, + name = ("toolchain_java%d" if release <= 11 else "toolchain_jdk_%d") % release, configuration = DEFAULT_TOOLCHAIN_CONFIGURATION, source_version = "%s" % release, target_version = "%s" % release, @@ -269,51 +396,6 @@ RELEASES = (8, 9, 10, 11) for release in RELEASES ] -# A toolchain that targets java 14. -default_java_toolchain( - name = "toolchain_jdk_14", - configuration = dict(), - java_runtime = "//toolchains:remotejdk_14", - source_version = "14", - target_version = "14", -) - -# A toolchain that targets java 15. -default_java_toolchain( - name = "toolchain_jdk_15", - configuration = dict(), - java_runtime = "//toolchains:remotejdk_15", - source_version = "15", - target_version = "15", -) - -# A toolchain that targets java 16. -default_java_toolchain( - name = "toolchain_jdk_16", - configuration = dict(), - java_runtime = "//toolchains:remotejdk_16", - source_version = "16", - target_version = "16", -) - -# A toolchain that targets java 17. -default_java_toolchain( - name = "toolchain_jdk_17", - configuration = dict(), - java_runtime = "//toolchains:remotejdk_17", - source_version = "17", - target_version = "17", -) - -# A toolchain that targets java 20. -default_java_toolchain( - name = "toolchain_jdk_20", - configuration = dict(), - java_runtime = "//toolchains:remotejdk_20", - source_version = "20", - target_version = "20", -) - default_java_toolchain( name = "prebuilt_toolchain", configuration = PREBUILT_TOOLCHAIN_CONFIGURATION, @@ -328,31 +410,32 @@ java_runtime_version_alias( ) java_runtime_version_alias( - name = "remotejdk_15", - runtime_version = "remotejdk_15", + name = "remotejdk_17", + runtime_version = "remotejdk_17", visibility = ["//visibility:public"], ) java_runtime_version_alias( - name = "remotejdk_16", - runtime_version = "remotejdk_16", + name = "remotejdk_21", + runtime_version = "remotejdk_21", visibility = ["//visibility:public"], ) java_runtime_version_alias( - name = "remotejdk_17", - runtime_version = "remotejdk_17", + name = "remotejdk_25", + runtime_version = "remotejdk_25", visibility = ["//visibility:public"], ) java_runtime_version_alias( - name = "remotejdk_20", - runtime_version = "remotejdk_20", + name = "jdk_8", + runtime_version = "8", visibility = ["//visibility:public"], ) -java_runtime_version_alias( - name = "jdk_8", - runtime_version = "8", +bzl_library( + name = "toolchain_utils", + srcs = ["toolchain_utils.bzl"], visibility = ["//visibility:public"], + deps = ["//java/common"], ) diff --git a/toolchains/DumpPlatformClassPath.java b/toolchains/DumpPlatformClassPath.java index fafb8437..17b014a3 100644 --- a/toolchains/DumpPlatformClassPath.java +++ b/toolchains/DumpPlatformClassPath.java @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import com.sun.tools.javac.api.JavacTool; -import com.sun.tools.javac.util.Context; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -21,13 +19,19 @@ import java.io.OutputStream; import java.io.UncheckedIOException; import java.lang.reflect.Method; +import java.net.URI; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.EnumSet; import java.util.GregorianCalendar; import java.util.List; import java.util.Map; @@ -38,11 +42,6 @@ import java.util.jar.JarOutputStream; import java.util.zip.CRC32; import java.util.zip.ZipEntry; -import javax.tools.JavaFileManager; -import javax.tools.JavaFileObject; -import javax.tools.JavaFileObject.Kind; -import javax.tools.StandardJavaFileManager; -import javax.tools.StandardLocation; /** * Output a jar file containing all classes on the platform classpath of the given JDK release. @@ -98,50 +97,35 @@ static boolean dumpJDK9AndNewerBootClassPath( return true; } - // Initialize a FileManager to process the --system argument, and then read the - // initialized bootclasspath data back out. - - Context context = new Context(); - try { - JavacTool.create() - .getTask( - /* out = */ null, - /* fileManager = */ null, - /* diagnosticListener = */ null, - /* options = */ Arrays.asList("--system", String.valueOf(targetJavabase)), - /* classes = */ null, - /* compilationUnits = */ null, - context); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException( - String.format( - "Failed to collect system class path. Please ensure that the configured Java runtime" - + " ('%s') is a complete JDK. There are known issues with Homebrew versions of" - + " the Java runtime.", - targetJavabase.toRealPath()), - e); - } - StandardJavaFileManager fileManager = - (StandardJavaFileManager) context.get(JavaFileManager.class); - - SortedMap entries = new TreeMap<>(); - for (JavaFileObject fileObject : - fileManager.list( - StandardLocation.PLATFORM_CLASS_PATH, - "", - EnumSet.of(Kind.CLASS), - /* recurse= */ true)) { - String binaryName = - fileManager.inferBinaryName(StandardLocation.PLATFORM_CLASS_PATH, fileObject); - entries.put(binaryName.replace('.', '/') + ".class", fileObject.openInputStream()); + // Read the bootclasspath data using the JRT filesystem + Map entries = new TreeMap<>(); + Map env = new TreeMap<>(); + env.put("java.home", String.valueOf(targetJavabase)); + try (FileSystem fileSystem = FileSystems.newFileSystem(URI.create("jrt:/"), env)) { + Path modules = fileSystem.getPath("/modules"); + try (DirectoryStream ms = Files.newDirectoryStream(modules)) { + for (Path m : ms) { + Files.walkFileTree( + m, + new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + if (file.getFileName().toString().endsWith(".class")) { + entries.put(m.relativize(file).toString(), Files.readAllBytes(file)); + } + return super.visitFile(file, attrs); + } + }); + } + } + writeEntries(output, entries); } - writeEntries(output, entries); return true; } /** Writes the given entry names and data to a jar archive at the given path. */ - private static void writeEntries(Path output, Map entries) - throws IOException { + private static void writeEntries(Path output, Map entries) throws IOException { if (!entries.containsKey("java/lang/Object.class")) { throw new AssertionError( "\nCould not find java.lang.Object on bootclasspath; something has gone terribly wrong.\n" @@ -168,14 +152,14 @@ private static void writeClassPathJars(Path output, Collection paths) thro for (Path path : paths) { jars.add(new JarFile(path.toFile())); } - SortedMap entries = new TreeMap<>(); + SortedMap entries = new TreeMap<>(); for (JarFile jar : jars) { jar.stream() .filter(p -> p.getName().endsWith(".class")) .forEachOrdered( entry -> { try { - entries.put(entry.getName(), jar.getInputStream(entry)); + entries.put(entry.getName(), toByteArray(jar.getInputStream(entry))); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -203,6 +187,10 @@ private static List getBootClassPathJars(Path javaHome) throws IOException jars.add(path); } } + Path toolsJar = javaHome.resolve("lib/tools.jar"); + if (Files.exists(toolsJar)) { + jars.add(toolsJar); + } return jars; } @@ -214,12 +202,10 @@ private static List getBootClassPathJars(Path javaHome) throws IOException * Add a jar entry to the given {@link JarOutputStream}, normalizing the entry timestamps to * ensure deterministic build output. */ - private static void addEntry(JarOutputStream jos, String name, InputStream input) - throws IOException { + private static void addEntry(JarOutputStream jos, String name, byte[] bytes) throws IOException { JarEntry je = new JarEntry(name); je.setTime(FIXED_TIMESTAMP); je.setMethod(ZipEntry.STORED); - byte[] bytes = toByteArray(input); // When targeting JDK >= 10, patch the major version so it will be accepted by javac 9 // TODO(cushon): remove this after updating javac if (bytes[7] > 53) { @@ -266,4 +252,4 @@ static int hostMajorVersion() { throw new IllegalStateException( "Unknown Java version: " + System.getProperty("java.specification.version")); } -} +} \ No newline at end of file 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 e6969884..f131a620 100644 --- a/toolchains/default_java_toolchain.bzl +++ b/toolchains/default_java_toolchain.bzl @@ -14,7 +14,8 @@ """Rules for defining default_java_toolchain""" -load("//java:defs.bzl", "java_toolchain") +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 = [ @@ -55,26 +56,33 @@ 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( + deps_checker = Label("@remote_java_tools//:ImportDepsChecker"), forcibly_disable_header_compilation = False, - genclass = [Label("@remote_java_tools//:GenClass")], - header_compiler = [Label("@remote_java_tools//:TurbineDirect")], - header_compiler_direct = [Label("@remote_java_tools//:TurbineDirect")], - 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, @@ -83,16 +91,17 @@ _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 ], - java_runtime = Label("//toolchains:remotejdk_17"), + java_runtime = Label("//toolchains:remotejdk_25"), + oneversion = Label("//toolchains:one_version"), ) DEFAULT_TOOLCHAIN_CONFIGURATION = _BASE_TOOLCHAIN_CONFIGURATION @@ -111,7 +120,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, ) @@ -122,18 +131,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")], + 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")], ) -_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. @@ -157,7 +168,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": ""}, @@ -204,86 +215,4 @@ def java_runtime_files(name, srcs): tags = ["manual"], ) -_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE = Label("@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type") - -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("-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], - ) - - bootclasspath = ctx.outputs.output_jar - - args = ctx.actions.args() - args.add("-XX:+IgnoreUnrecognizedVMOptions") - 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 - args.add(any_javabase.java_home) - - system_files = ("release", "modules", "jrt-fs.jar") - system = [f for f in any_javabase.files.to_list() 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], - ) - 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 21fae7eb..54973c0f 100644 --- a/toolchains/java_toolchain_alias.bzl +++ b/toolchains/java_toolchain_alias.bzl @@ -14,6 +14,8 @@ """Experimental re-implementations of Java toolchain aliases using toolchain resolution.""" +load("//java/common:java_common.bzl", "java_common") + def _java_runtime_alias(ctx): """An experimental implementation of java_runtime_alias using toolchain resolution.""" toolchain_info = ctx.toolchains["@bazel_tools//tools/jdk:runtime_toolchain_type"] @@ -24,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( @@ -35,7 +39,6 @@ def _java_runtime_alias(ctx): java_runtime_alias = rule( implementation = _java_runtime_alias, toolchains = ["@bazel_tools//tools/jdk:runtime_toolchain_type"], - incompatible_use_toolchain_transition = True, ) def _java_host_runtime_alias(ctx): @@ -82,7 +85,6 @@ _java_runtime_transition = transition( java_runtime_version_alias = rule( implementation = _java_runtime_alias, toolchains = ["@bazel_tools//tools/jdk:runtime_toolchain_type"], - incompatible_use_toolchain_transition = True, attrs = { "runtime_version": attr.string(mandatory = True), "_allowlist_function_transition": attr.label( @@ -97,18 +99,12 @@ def _java_toolchain_alias(ctx): toolchain_info = ctx.toolchains["@bazel_tools//tools/jdk:toolchain_type"] toolchain = toolchain_info.java - # buildifier: disable=rule-impl-return - return struct( - providers = [ - toolchain_info, - toolchain, - ], - # Use the legacy provider syntax for compatibility with the native rules. - java_toolchain = toolchain, - ) + return [ + toolchain_info, + toolchain, + ] java_toolchain_alias = rule( implementation = _java_toolchain_alias, toolchains = ["@bazel_tools//tools/jdk:toolchain_type"], - incompatible_use_toolchain_transition = True, ) diff --git a/toolchains/jdk_build_file.bzl b/toolchains/jdk_build_file.bzl index 71b615aa..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"]) @@ -83,6 +83,35 @@ java_runtime( ":jdk-lib", ":jre", ], + # Provide the 'java` binary explicitly so that the correct path is used by + # Bazel even when the host platform differs from the execution platform. + # Exactly one of the two globs will be empty depending on the host platform. + # When --incompatible_disallow_empty_glob is enabled, each individual empty + # glob will fail without allow_empty = True, even if the overall result is + # non-empty. + java = glob(["bin/java.exe", "bin/java"], allow_empty = True)[0], + version = {RUNTIME_VERSION}, +) + +filegroup( + name = "jdk-jmods", + srcs = glob( + ["jmods/**"], + allow_empty = True, + ), +) + +java_runtime( + name = "jdk-with-jmods", + srcs = [ + ":jdk-bin", + ":jdk-conf", + ":jdk-include", + ":jdk-lib", + ":jdk-jmods", + ":jre", + ], + java = glob(["bin/java.exe", "bin/java"], allow_empty = True)[0], version = {RUNTIME_VERSION}, ) """ diff --git a/toolchains/local_java_repository.bzl b/toolchains/local_java_repository.bzl index 64991852..88115bb7 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): @@ -176,10 +176,13 @@ def _local_java_repository_impl(repository_ctx): repository_ctx: repository context """ + repository_metadata = None + if hasattr(repository_ctx, "repo_metadata"): + repository_metadata = repository_ctx.repo_metadata(reproducible = True) + java_home = _determine_java_home(repository_ctx) - # When Bzlmod is enabled, the Java runtime name should be the last segment of the repository name. - local_java_runtime_name = repository_ctx.name.split("~")[-1] + local_java_runtime_name = repository_ctx.attr.runtime_name repository_ctx.file( "WORKSPACE", @@ -191,19 +194,28 @@ def _local_java_repository_impl(repository_ctx): if not java_bin.exists: # Java binary does not exist - repository_ctx.file( - "BUILD.bazel", - _NOJDK_BUILD_TPL.format( - local_jdk = local_java_runtime_name, - java_binary = _with_os_extension(repository_ctx, "bin/java"), - java_home = java_home, - ), - False, + _create_auto_config_error_build_file( + repository_ctx, + local_java_runtime_name = local_java_runtime_name, + java_home = java_home, + message = "Cannot find Java binary {java_binary} in {java_home}; " + + "either correct your JAVA_HOME, PATH or specify Java from " + + "remote repository (e.g. --java_runtime_version=remotejdk_11)", ) - return + return repository_metadata # Detect version version = repository_ctx.attr.version if repository_ctx.attr.version != "" else _detect_java_version(repository_ctx, java_bin) + if version == None: + # Java version could not be detected + _create_auto_config_error_build_file( + repository_ctx, + local_java_runtime_name = local_java_runtime_name, + java_home = java_home, + message = "Cannot detect Java version of {java_binary} in {java_home}; " + + "make sure it points to a valid Java executable", + ) + return repository_metadata # Prepare BUILD file using "local_java_runtime" macro if repository_ctx.attr.build_file_content and repository_ctx.attr.build_file: @@ -230,7 +242,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, ) @@ -239,14 +251,14 @@ local_java_runtime( for file in repository_ctx.path(java_home).readdir(): repository_ctx.symlink(file, file.basename) -# Build file template, when JDK does not exist -_NOJDK_BUILD_TPL = '''load("@rules_java//toolchains:fail_rule.bzl", "fail_rule") + return repository_metadata + +# Build file template, when JDK could not be detected +_AUTO_CONFIG_ERROR_BUILD_TPL = '''load("@rules_java//toolchains:fail_rule.bzl", "fail_rule") fail_rule( name = "jdk", header = "Auto-Configuration Error:", - message = ("Cannot find Java binary {java_binary} in {java_home}; either correct your JAVA_HOME, " + - "PATH or specify Java from remote repository (e.g. " + - "--java_runtime_version=remotejdk_11)") + message = {message}, ) config_setting( name = "localjdk_setting", @@ -267,12 +279,26 @@ toolchain( ) ''' +def _create_auto_config_error_build_file(repository_ctx, *, local_java_runtime_name, java_home, message): + repository_ctx.file( + "BUILD.bazel", + _AUTO_CONFIG_ERROR_BUILD_TPL.format( + local_jdk = local_java_runtime_name, + message = repr(message.format( + java_binary = _with_os_extension(repository_ctx, "bin/java"), + java_home = java_home, + )), + ), + False, + ) + _local_java_repository_rule = repository_rule( implementation = _local_java_repository_impl, local = True, configure = True, environ = ["JAVA_HOME"], attrs = { + "runtime_name": attr.string(), "build_file": attr.label(), "build_file_content": attr.string(), "java_home": attr.string(), @@ -283,8 +309,17 @@ _local_java_repository_rule = repository_rule( def local_java_repository(name, java_home = "", version = "", build_file = None, build_file_content = None, **kwargs): """Defines runtime and compile toolchains for a local JDK. - Register the toolchains defined by this macro via `register_toolchains("@//:all")`, where - `` is the value of the `name` parameter. + Register the toolchains defined by this macro as follows (where `` is the value of the + `name` parameter): + * Runtime toolchains only (recommended) + ``` + register_toolchains("@//:runtime_toolchain_definition") + register_toolchains("@//:bootstrap_runtime_toolchain_definition") + ``` + * Runtime and compilation toolchains: + ``` + register_toolchains("@//:all") + ``` Toolchain resolution is constrained with --java_runtime_version flag having value of the "name" or "version" parameter. @@ -303,4 +338,4 @@ def local_java_repository(name, java_home = "", version = "", build_file = None, version: optionally java version **kwargs: additional arguments for repository rule """ - _local_java_repository_rule(name = name, java_home = java_home, version = version, build_file = build_file, build_file_content = build_file_content, **kwargs) + _local_java_repository_rule(name = name, runtime_name = name, java_home = java_home, version = version, build_file = build_file, build_file_content = build_file_content, **kwargs) diff --git a/toolchains/remote_java_repository.bzl b/toolchains/remote_java_repository.bzl index 65bbe482..500beb7b 100644 --- a/toolchains/remote_java_repository.bzl +++ b/toolchains/remote_java_repository.bzl @@ -23,6 +23,9 @@ load("//toolchains:jdk_build_file.bzl", "JDK_BUILD_TEMPLATE") def _toolchain_config_impl(ctx): ctx.file("WORKSPACE", "workspace(name = \"{name}\")\n".format(name = ctx.name)) ctx.file("BUILD.bazel", ctx.attr.build_file) + if hasattr(ctx, "repo_metadata"): + return ctx.repo_metadata(reproducible = True) + return None _toolchain_config = repository_rule( local = True, diff --git a/toolchains/toolchain_utils.bzl b/toolchains/toolchain_utils.bzl index 71770926..8a33293b 100644 --- a/toolchains/toolchain_utils.bzl +++ b/toolchains/toolchain_utils.bzl @@ -19,6 +19,8 @@ Returns the toolchain if enabled, and falls back to a toolchain constructed from legacy toolchain selection. """ +load("//java/common:java_common.bzl", "java_common") + def find_java_toolchain(ctx, target): """ Finds the Java toolchain. 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.", +)