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";
+ }
+}
+