Add the repo rule `http_jar` Copybara Import from https://github.com/bazelbuild/rules_java/pull/238 BEGIN_PUBLIC Add the repo rule `http_jar` (#238) The repo rule `http_jar` currently lives in `@bazel_tools`, which results in an implicit dependency on `rules_java` because it creates a repo with a BUILD file that uses `java_import`. So this repo rule really belongs in `rules_java`. This PR copies it over from `@bazel_tools//tools/build_defs/repo:http.bzl` with minimal edits. Closes #238 END_PUBLIC COPYBARA_INTEGRATE_REVIEW=https://github.com/bazelbuild/rules_java/pull/238 from bazelbuild:wyv-http-jar 74d325a744cd7333e8669685d3ea1d3112a5078c PiperOrigin-RevId: 693270198 Change-Id: I44f6d60301f27db8515243af54f79cb0ab4a7c0c
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index f89bc94..8971a40 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml
@@ -42,6 +42,8 @@ - "//..." - "//:bin_deploy.jar" - "@rules_java//java/..." + test_targets: + - "//:MyTest" macos: name: "Bazel 7.x" bazel: "7.4.0" @@ -67,6 +69,8 @@ - "//..." - "//:bin_deploy.jar" - "@rules_java//java/..." + test_targets: + - "//:MyTest" macos_head: name: "Bazel@HEAD" bazel: last_green @@ -94,6 +98,7 @@ build_targets: - "//..." - "//:bin_deploy.jar" + - "-//:MyTest" macos_bazel6: name: "Bazel 6.x" bazel: 6.5.0
diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 0000000..457eb2a --- /dev/null +++ b/.bazelignore
@@ -0,0 +1 @@ +test/repo
diff --git a/java/http_jar.bzl b/java/http_jar.bzl new file mode 100644 index 0000000..8865482 --- /dev/null +++ b/java/http_jar.bzl
@@ -0,0 +1,200 @@ +"""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("@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: <code><login></code> and +<code><password></code>, 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 <code>Authorization</code> field of the HTTP request. + +Example attribute and netrc for a http download to an oauth2 enabled API using a bearer token: + +<pre> +auth_patterns = { + "storage.cloudprovider.com": "Bearer <password>" +} +</pre> + +netrc: + +<pre> +machine storage.cloudprovider.com + password RANDOM-TOKEN +</pre> + +The final HTTP request would have the following header: + +<pre> +Authorization: Bearer RANDOM-TOKEN +</pre> +""" + +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/test/BUILD.bazel b/test/BUILD.bazel index cea33a5..00506f8 100644 --- a/test/BUILD.bazel +++ b/test/BUILD.bazel
@@ -1,6 +1,3 @@ -load("@bazel_skylib//rules:diff_test.bzl", "diff_test") -load("@rules_shell//shell:sh_test.bzl", "sh_test") - # Copyright 2024 The Bazel Authors. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +load("@bazel_skylib//rules:diff_test.bzl", "diff_test") +load("@rules_shell//shell:sh_test.bzl", "sh_test") load("//java:repositories.bzl", "JAVA_TOOLS_CONFIG", "REMOTE_JDK_CONFIGS") load(":check_remotejdk_configs_match.bzl", "validate_configs")
diff --git a/test/repo/BUILD.bazel b/test/repo/BUILD.bazel index 6b7da40..20deeae 100644 --- a/test/repo/BUILD.bazel +++ b/test/repo/BUILD.bazel
@@ -1,9 +1,9 @@ -load("@rules_java//java:defs.bzl", "java_binary", "java_library") # copybara-use-repo-external-label +load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_test") # copybara-use-repo-external-label load("@rules_java//toolchains:default_java_toolchain.bzl", "default_java_toolchain") # copybara-use-repo-external-label java_library( name = "lib", - srcs = glob(["src/*.java"]), + srcs = ["src/Main.java"], ) java_binary( @@ -12,6 +12,12 @@ runtime_deps = [":lib"], ) +java_test( + name = "MyTest", + srcs = ["src/MyTest.java"], + deps = ["@my_jar//jar"], +) + default_java_toolchain( name = "my_funky_toolchain", )
diff --git a/test/repo/MODULE.bazel b/test/repo/MODULE.bazel index c9729b4..5a4d611 100644 --- a/test/repo/MODULE.bazel +++ b/test/repo/MODULE.bazel
@@ -6,6 +6,13 @@ urls = ["file:///tmp/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"], +) + java_toolchains = use_extension("@rules_java//java:extensions.bzl", "toolchains") use_repo( java_toolchains,
diff --git a/test/repo/setup.sh b/test/repo/setup.sh index 4c268fc..7e3b369 100644 --- a/test/repo/setup.sh +++ b/test/repo/setup.sh
@@ -1,5 +1,7 @@ #!/usr/bin/env bash cd ../../ -bazel build //distro:all -cp -f bazel-bin/distro/rules_java-*.tar.gz /tmp/rules_java-HEAD.tar.gz \ No newline at end of file +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/MyTest.java b/test/repo/src/MyTest.java new file mode 100644 index 0000000..2de08a0 --- /dev/null +++ b/test/repo/src/MyTest.java
@@ -0,0 +1,15 @@ +import static org.junit.Assert.assertEquals; + +import mypackage.MyLib; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class MyTest { + @Test + public void main() { + assertEquals(MyLib.myStr(), "my_string"); + } +} +
diff --git a/test/testdata/BUILD.bazel b/test/testdata/BUILD.bazel new file mode 100644 index 0000000..ffcc5c3 --- /dev/null +++ b/test/testdata/BUILD.bazel
@@ -0,0 +1,7 @@ +load("//java:java_library.bzl", "java_library") + +# Make a sample jar for the http_jar test. +java_library( + name = "my_jar", + srcs = ["MyLib.java"], +)
diff --git a/test/testdata/MyLib.java b/test/testdata/MyLib.java new file mode 100644 index 0000000..48428f1 --- /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"; + } +} +