Allow an optional index of dep -> class to be created (#1492)
When the index is generated, the information that used to be in the lock file is moved to the index. This will reduce the size of the lock file.
diff --git a/MODULE.bazel b/MODULE.bazel
index fad546c..ca9ae99 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -745,6 +745,7 @@
"org.slf4j:slf4j-log4j12:2.0.0",
],
generate_compat_repositories = True,
+ index_file = "//tests/custom_maven_install:regression_testing_gradle_index.json",
lock_file = "//tests/custom_maven_install:regression_testing_gradle_install.json",
repositories = [
"https://repo1.maven.org/maven2",
diff --git a/docs/api.md b/docs/api.md
index de01aef..c0fbf1e 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -230,8 +230,8 @@
| <a id="maven_install-additional_netrc_lines"></a>additional_netrc_lines | Additional lines prepended to the netrc file used by `http_file` (with `maven_install_json` only). | `[]` |
| <a id="maven_install-use_credentials_from_home_netrc_file"></a>use_credentials_from_home_netrc_file | Whether to pass machine login credentials from the ~/.netrc file to coursier. | `False` |
| <a id="maven_install-fail_if_repin_required"></a>fail_if_repin_required | Whether to fail the build if the required maven artifacts have been changed but not repinned. Requires the `maven_install_json` to have been set. | `True` |
-| <a id="maven_install-use_starlark_android_rules"></a>use_starlark_android_rules | Whether to use the native or Starlark version of the Android rules. Default is False. | `False` |
-| <a id="maven_install-aar_import_bzl_label"></a>aar_import_bzl_label | The label (as a string) to use to import aar_import from. This is usually needed only if the top-level workspace file does not use the typical default repository name to import the Android Starlark rules. Default is "@build_bazel_rules_android//rules:rules.bzl". | `"@build_bazel_rules_android//android:rules.bzl"` |
+| <a id="maven_install-use_starlark_android_rules"></a>use_starlark_android_rules | Whether to use the native or Starlark version of the Android rules. Default is False if the running version of Bazel supports native aar_import. If the running version of Bazel does not support native aar_import, this parameter is ignored and the Starlark Android rules is used. | `False` |
+| <a id="maven_install-aar_import_bzl_label"></a>aar_import_bzl_label | The label (as a string) to use to import aar_import from. This is usually needed only if the top-level workspace file does not use the typical default repository name to import the Android Starlark rules. Default is "@rules_android//rules:rules.bzl". | `"@rules_android//rules:rules.bzl"` |
| <a id="maven_install-duplicate_version_warning"></a>duplicate_version_warning | What to do if an artifact is specified multiple times. If "error" then fail the build, if "warn" then print a message and continue, if "none" then do nothing. The default is "warn". | `"warn"` |
| <a id="maven_install-repin_instructions"></a>repin_instructions | Instructions to re-pin dependencies in your repository. Will be shown when re-pinning is required. | `None` |
| <a id="maven_install-ignore_empty_files"></a>ignore_empty_files | Treat jars that are empty as if they were not found. | `False` |
diff --git a/docs/bzlmod-api.md b/docs/bzlmod-api.md
index cebc65d..e4bcead 100644
--- a/docs/bzlmod-api.md
+++ b/docs/bzlmod-api.md
@@ -13,12 +13,13 @@
<a href="#maven.artifact-testonly">testonly</a>, <a href="#maven.artifact-version">version</a>)
maven.from_toml(<a href="#maven.from_toml-name">name</a>, <a href="#maven.from_toml-bom_modules">bom_modules</a>, <a href="#maven.from_toml-libs_versions_toml">libs_versions_toml</a>)
maven.install(<a href="#maven.install-name">name</a>, <a href="#maven.install-aar_import_bzl_label">aar_import_bzl_label</a>, <a href="#maven.install-additional_coursier_options">additional_coursier_options</a>, <a href="#maven.install-additional_netrc_lines">additional_netrc_lines</a>,
- <a href="#maven.install-artifacts">artifacts</a>, <a href="#maven.install-boms">boms</a>, <a href="#maven.install-duplicate_version_warning">duplicate_version_warning</a>, <a href="#maven.install-excluded_artifacts">excluded_artifacts</a>, <a href="#maven.install-exclusions">exclusions</a>,
- <a href="#maven.install-fail_if_repin_required">fail_if_repin_required</a>, <a href="#maven.install-fail_on_missing_checksum">fail_on_missing_checksum</a>, <a href="#maven.install-fetch_javadoc">fetch_javadoc</a>, <a href="#maven.install-fetch_sources">fetch_sources</a>,
- <a href="#maven.install-generate_compat_repositories">generate_compat_repositories</a>, <a href="#maven.install-ignore_empty_files">ignore_empty_files</a>, <a href="#maven.install-known_contributing_modules">known_contributing_modules</a>, <a href="#maven.install-lock_file">lock_file</a>,
- <a href="#maven.install-repin_instructions">repin_instructions</a>, <a href="#maven.install-repositories">repositories</a>, <a href="#maven.install-resolve_timeout">resolve_timeout</a>, <a href="#maven.install-resolver">resolver</a>, <a href="#maven.install-strict_visibility">strict_visibility</a>,
- <a href="#maven.install-strict_visibility_value">strict_visibility_value</a>, <a href="#maven.install-use_credentials_from_home_netrc_file">use_credentials_from_home_netrc_file</a>,
- <a href="#maven.install-use_starlark_android_rules">use_starlark_android_rules</a>, <a href="#maven.install-version_conflict_policy">version_conflict_policy</a>)
+ <a href="#maven.install-artifacts">artifacts</a>, <a href="#maven.install-boms">boms</a>, <a href="#maven.install-dependency_index">dependency_index</a>, <a href="#maven.install-duplicate_version_warning">duplicate_version_warning</a>, <a href="#maven.install-excluded_artifacts">excluded_artifacts</a>,
+ <a href="#maven.install-exclusions">exclusions</a>, <a href="#maven.install-fail_if_repin_required">fail_if_repin_required</a>, <a href="#maven.install-fail_on_missing_checksum">fail_on_missing_checksum</a>, <a href="#maven.install-fetch_javadoc">fetch_javadoc</a>,
+ <a href="#maven.install-fetch_sources">fetch_sources</a>, <a href="#maven.install-generate_compat_repositories">generate_compat_repositories</a>, <a href="#maven.install-ignore_empty_files">ignore_empty_files</a>,
+ <a href="#maven.install-known_contributing_modules">known_contributing_modules</a>, <a href="#maven.install-lock_file">lock_file</a>, <a href="#maven.install-repin_instructions">repin_instructions</a>, <a href="#maven.install-repositories">repositories</a>,
+ <a href="#maven.install-resolve_timeout">resolve_timeout</a>, <a href="#maven.install-resolver">resolver</a>, <a href="#maven.install-strict_visibility">strict_visibility</a>, <a href="#maven.install-strict_visibility_value">strict_visibility_value</a>,
+ <a href="#maven.install-use_credentials_from_home_netrc_file">use_credentials_from_home_netrc_file</a>, <a href="#maven.install-use_starlark_android_rules">use_starlark_android_rules</a>,
+ <a href="#maven.install-version_conflict_policy">version_conflict_policy</a>)
maven.override(<a href="#maven.override-name">name</a>, <a href="#maven.override-coordinates">coordinates</a>, <a href="#maven.override-target">target</a>)
</pre>
@@ -88,11 +89,12 @@
| Name | Description | Type | Mandatory | Default |
| :------------- | :------------- | :------------- | :------------- | :------------- |
| <a id="maven.install-name"></a>name | - | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | optional | `"maven"` |
-| <a id="maven.install-aar_import_bzl_label"></a>aar_import_bzl_label | The label (as a string) to use to import aar_import from | String | optional | `"@build_bazel_rules_android//android:rules.bzl"` |
+| <a id="maven.install-aar_import_bzl_label"></a>aar_import_bzl_label | The label (as a string) to use to import aar_import from | String | optional | `"@rules_android//rules:rules.bzl"` |
| <a id="maven.install-additional_coursier_options"></a>additional_coursier_options | Additional options that will be passed to coursier. | List of strings | optional | `[]` |
| <a id="maven.install-additional_netrc_lines"></a>additional_netrc_lines | Additional lines prepended to the netrc file used by `http_file` (with `maven_install_json` only). | List of strings | optional | `[]` |
| <a id="maven.install-artifacts"></a>artifacts | Maven artifact tuples, in `artifactId:groupId:version` format | List of strings | optional | `[]` |
| <a id="maven.install-boms"></a>boms | Maven BOM tuples, in `artifactId:groupId:version` format | List of strings | optional | `[]` |
+| <a id="maven.install-dependency_index"></a>dependency_index | If present, when dependencies are resolved this file will contain information the java gazelle plugin can use to more accurately construct build files. The default name should be `maven_index.json`. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
| <a id="maven.install-duplicate_version_warning"></a>duplicate_version_warning | What to do if there are duplicate artifacts<br><br>If "error", then print a message and fail the build. If "warn", then print a warning and continue. If "none", then do nothing. | String | optional | `"warn"` |
| <a id="maven.install-excluded_artifacts"></a>excluded_artifacts | Artifacts to exclude, in `artifactId:groupId` format. Only used on unpinned installs | List of strings | optional | `[]` |
| <a id="maven.install-exclusions"></a>exclusions | Maven artifact tuples, in `artifactId:groupId` format | List of strings | optional | `[]` |
diff --git a/private/extensions/maven.bzl b/private/extensions/maven.bzl
index 2edc587..c42a051 100644
--- a/private/extensions/maven.bzl
+++ b/private/extensions/maven.bzl
@@ -91,6 +91,7 @@
),
"fail_if_repin_required": attr.bool(doc = "Whether to fail the build if the maven_artifact inputs have changed but the lock file has not been repinned.", default = True),
"lock_file": attr.label(),
+ "index_file": attr.label(doc = "If present, when dependencies are resolved this file will contain information the java gazelle plugin can use to more accurately construct build files. The default name should be `maven_index.json`."),
"repositories": attr.string_list(default = DEFAULT_REPOSITORIES),
"generate_compat_repositories": attr.bool(
doc = "Additionally generate repository aliases in a .bzl file for all JAR artifacts. For example, `@maven//:com_google_guava_guava` can also be referenced as `@com_google_guava_guava//jar`.",
@@ -649,6 +650,7 @@
# We will always have a lock file, so this is fine
repo_to_lock_file[install.name] = [install.lock_file]
+ repo["dependency_index"] = install.index_file
repo["fail_if_repin_required"] = install.fail_if_repin_required
repo["fail_on_missing_checksum"] = install.fail_on_missing_checksum
repo["fetch_javadoc"] = install.fetch_javadoc
@@ -714,6 +716,7 @@
strict_visibility_value = repo.get("strict_visibility_value"),
use_credentials_from_home_netrc_file = repo.get("use_credentials_from_home_netrc_file"),
maven_install_json = repo.get("lock_file"),
+ dependency_index = repo.get("dependency_index"),
resolve_timeout = repo.get("resolve_timeout"),
use_starlark_android_rules = repo.get("use_starlark_android_rules"),
aar_import_bzl_label = repo.get("aar_import_bzl_label"),
@@ -770,6 +773,7 @@
resolver = repo.get("resolver", _DEFAULT_RESOLVER),
generate_compat_repositories = False,
maven_install_json = repo.get("lock_file"),
+ dependency_index = repo.get("dependency_index"),
override_targets = overrides.get(name),
override_target_visibilities = override_visibilities.get(name, {}),
strict_visibility = repo.get("strict_visibility"),
diff --git a/private/rules/coursier.bzl b/private/rules/coursier.bzl
index 73d54cf..f6abf5c 100644
--- a/private/rules/coursier.bzl
+++ b/private/rules/coursier.bzl
@@ -124,6 +124,7 @@
fetch_sources = {fetch_sources},
fetch_javadocs = {fetch_javadocs},
lock_file = {lock_file},
+ dependency_index = {dependency_index},
jvm_flags = {jvm_flags},
visibility = ["//visibility:public"],
resolver = {resolver},
@@ -812,6 +813,15 @@
else:
fail("Unknown resolver")
+ dependency_index_location = None
+ if repository_ctx.attr.dependency_index:
+ dep_index_package_path = repository_ctx.attr.dependency_index.package
+ dep_index_file_name = repository_ctx.attr.dependency_index.name
+ if dep_index_package_path == "":
+ dependency_index_location = dep_index_file_name
+ else:
+ dependency_index_location = "/".join([dep_index_package_path, dep_index_file_name])
+
return _IN_REPO_PIN.format(
boms = repr(repository_ctx.attr.boms),
artifacts = repr(repository_ctx.attr.artifacts),
@@ -821,6 +831,7 @@
fetch_sources = repr(repository_ctx.attr.fetch_sources),
fetch_javadocs = repr(repository_ctx.attr.fetch_javadoc),
lock_file = repr(lock_file_location),
+ dependency_index = repr(dependency_index_location),
resolver = repr(str(resolver_target)),
)
@@ -1553,6 +1564,7 @@
"fetch_javadoc": attr.bool(default = False),
"generate_compat_repositories": attr.bool(default = False), # generate a compatible layer with repositories for each artifact
"maven_install_json": attr.label(allow_single_file = True),
+ "dependency_index": attr.label(allow_single_file = True),
"override_targets": attr.string_dict(default = {}),
"override_target_visibilities": attr.string_list_dict(default = {}),
"strict_visibility": attr.bool(
@@ -1624,6 +1636,7 @@
],
),
"maven_install_json": attr.label(allow_single_file = True),
+ "dependency_index": attr.label(allow_single_file = True),
"override_targets": attr.string_dict(default = {}),
"override_target_visibilities": attr.string_list_dict(default = {}),
"strict_visibility": attr.bool(
diff --git a/private/rules/pin_dependencies.bzl b/private/rules/pin_dependencies.bzl
index e500359..4029902 100644
--- a/private/rules/pin_dependencies.bzl
+++ b/private/rules/pin_dependencies.bzl
@@ -15,7 +15,7 @@
_TEMPLATE = """#!/usr/bin/env bash
-{resolver_cmd} --jvm_flags={jvm_flags} --argsfile {config} --input_hash '{input_hash}' --output {output}
+{resolver_cmd} --jvm_flags={jvm_flags} --argsfile {config} --input_hash '{input_hash}' --output {output}{dependency_index_output}
"""
def _stringify_exclusions(exclusions):
@@ -70,6 +70,10 @@
excluded_artifacts = ctx.attr.excluded_artifacts,
)
+ dependency_index_output = ""
+ if ctx.attr.dependency_index:
+ dependency_index_output = " --dependency-index-output $BUILD_WORKSPACE_DIRECTORY/" + ctx.attr.dependency_index
+
script = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
script,
@@ -78,6 +82,7 @@
input_hash = input_hash[0],
resolver_cmd = ctx.executable.resolver.short_path,
output = "$BUILD_WORKSPACE_DIRECTORY/" + ctx.attr.lock_file,
+ dependency_index_output = dependency_index_output,
jvm_flags = ctx.attr.jvm_flags,
),
is_executable = True,
@@ -113,6 +118,9 @@
doc = "Location of the generated lock file",
mandatory = True,
),
+ "dependency_index": attr.string(
+ doc = "Location of the generated dependency index file",
+ ),
"jvm_flags": attr.string(
doc = "JVM flags to pass to resolver",
),
diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/coursier/LockFileConverter.java b/private/tools/java/com/github/bazelbuild/rules_jvm_external/coursier/LockFileConverter.java
index 5899e05..3e6fa98 100644
--- a/private/tools/java/com/github/bazelbuild/rules_jvm_external/coursier/LockFileConverter.java
+++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/coursier/LockFileConverter.java
@@ -100,7 +100,7 @@
Set<DependencyInfo> infos = converter.getDependencies();
Set<Conflict> conflicts = converter.getConflicts();
- Map<String, Object> rendered = new V2LockFile(repositories, infos, conflicts).render();
+ Map<String, Object> rendered = new V2LockFile(repositories, infos, conflicts, true).render();
String converted =
new GsonBuilder().setPrettyPrinting().serializeNulls().create().toJson(rendered);
@@ -213,6 +213,7 @@
Optional.ofNullable((String) coursierDep.get("sha256")),
directDeps,
packages,
+ Set.of(),
services));
}
diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/IndexJar.java b/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/IndexJar.java
index c098656..f147723 100644
--- a/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/IndexJar.java
+++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/IndexJar.java
@@ -84,6 +84,7 @@
public PerJarIndexResults index(Path path) throws IOException {
SortedSet<String> packages = new TreeSet<>();
+ SortedSet<String> classes = new TreeSet<>();
SortedMap<String, SortedSet<String>> serviceImplementations = new TreeMap<>();
try (InputStream fis = new BufferedInputStream(Files.newInputStream(path));
ZipInputStream zis = new ZipInputStream(fis)) {
@@ -103,12 +104,17 @@
|| entry.getName().endsWith("/module-info.class")) {
continue;
}
+ // Skip inner classes, anonymous classes, and local classes (contain $)
+ if (isInnerClass(entry.getName())) {
+ continue;
+ }
packages.add(extractPackageName(entry.getName()));
+ classes.add(extractClassName(entry.getName()));
}
} catch (ZipException e) {
System.err.printf("Caught ZipException: %s%n", e);
}
- return new PerJarIndexResults(packages, serviceImplementations);
+ return new PerJarIndexResults(packages, classes, serviceImplementations);
}
}
@@ -151,7 +157,31 @@
return Arrays.stream(parts).skip(skip).limit(limit).collect(Collectors.joining("."));
}
+ private String extractClassName(String zipEntryName) {
+ String[] parts = zipEntryName.split("/");
+ int skip = 0;
+ // As per https://docs.oracle.com/en/java/javase/13/docs/specs/jar/jar.html
+ if (parts.length > 3
+ && "META-INF".equals(parts[0])
+ && "versions".equals(parts[1])
+ && isNumericVersion(parts[2])) {
+ skip = 3;
+ }
+
+ // Remove .class suffix and join with dots
+ String className = Arrays.stream(parts).skip(skip).collect(Collectors.joining("."));
+ return className.substring(0, className.length() - ".class".length());
+ }
+
private boolean isNumericVersion(String part) {
return IS_NUMERIC_VERSION.test(part);
}
+
+ private boolean isInnerClass(String zipEntryName) {
+ // Inner classes, anonymous classes, and local classes all contain '$' in the class file name
+ // Examples: Outer$Inner.class, Outer$1.class, Outer$1LocalClass.class
+ int lastSlash = zipEntryName.lastIndexOf('/');
+ String fileName = lastSlash == -1 ? zipEntryName : zipEntryName.substring(lastSlash + 1);
+ return fileName.contains("$");
+ }
}
diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/PerJarIndexResults.java b/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/PerJarIndexResults.java
index 6c96a8f..13a7a53 100644
--- a/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/PerJarIndexResults.java
+++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/PerJarIndexResults.java
@@ -5,11 +5,15 @@
public class PerJarIndexResults {
private final SortedSet<String> packages;
+ private final SortedSet<String> classes;
private final SortedMap<String, SortedSet<String>> serviceImplementations;
public PerJarIndexResults(
- SortedSet<String> packages, SortedMap<String, SortedSet<String>> serviceImplementations) {
+ SortedSet<String> packages,
+ SortedSet<String> classes,
+ SortedMap<String, SortedSet<String>> serviceImplementations) {
this.packages = packages;
+ this.classes = classes;
this.serviceImplementations = serviceImplementations;
}
@@ -17,6 +21,10 @@
return this.packages;
}
+ public SortedSet<String> getClasses() {
+ return this.classes;
+ }
+
public SortedMap<String, SortedSet<String>> getServiceImplementations() {
return this.serviceImplementations;
}
diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/DependencyInfo.java b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/DependencyInfo.java
index 7bdd874..be7bab1 100644
--- a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/DependencyInfo.java
+++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/DependencyInfo.java
@@ -34,6 +34,7 @@
private final Optional<String> sha256;
private final Set<Coordinates> dependencies;
private final Set<String> packages;
+ private final Set<String> classes;
private final SortedMap<String, SortedSet<String>> services;
public DependencyInfo(
@@ -43,6 +44,7 @@
Optional<String> sha256,
Set<Coordinates> dependencies,
Set<String> packages,
+ Set<String> classes,
SortedMap<String, SortedSet<String>> services) {
this.coordinates = coordinates;
this.repos = ImmutableSet.copyOf(repos);
@@ -51,6 +53,7 @@
this.dependencies = ImmutableSet.copyOf(new TreeSet<>(dependencies));
this.packages = ImmutableSet.copyOf(new TreeSet<>(packages));
+ this.classes = ImmutableSet.copyOf(new TreeSet<>(classes));
this.services = services;
}
@@ -74,6 +77,10 @@
return packages;
}
+ public Set<String> getClasses() {
+ return classes;
+ }
+
public Optional<Path> getPath() {
return path;
}
@@ -104,11 +111,12 @@
&& Objects.equals(sha256, that.sha256)
&& Objects.equals(dependencies, that.dependencies)
&& Objects.equals(packages, that.packages)
+ && Objects.equals(classes, that.classes)
&& Objects.equals(services, that.services);
}
@Override
public int hashCode() {
- return Objects.hash(coordinates, sha256, dependencies, packages, services);
+ return Objects.hash(coordinates, sha256, dependencies, packages, classes, services);
}
}
diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/AbstractMain.java b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/AbstractMain.java
index 2420111..55dd995 100644
--- a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/AbstractMain.java
+++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/AbstractMain.java
@@ -27,6 +27,7 @@
import com.github.bazelbuild.rules_jvm_external.resolver.Resolver;
import com.github.bazelbuild.rules_jvm_external.resolver.events.EventListener;
import com.github.bazelbuild.rules_jvm_external.resolver.events.PhaseEvent;
+import com.github.bazelbuild.rules_jvm_external.resolver.lockfile.DependencyIndex;
import com.github.bazelbuild.rules_jvm_external.resolver.lockfile.V2LockFile;
import com.github.bazelbuild.rules_jvm_external.resolver.netrc.Netrc;
import com.github.bazelbuild.rules_jvm_external.resolver.remote.DownloadResult;
@@ -73,6 +74,7 @@
infos = fulfillDependencyInfos(resolver, listener, config, resolutionResult);
writeLockFile(listener, config, request, infos, resolutionResult.getConflicts());
+ writeDependencyIndex(config, infos);
System.exit(0);
} catch (Exception e) {
@@ -201,7 +203,7 @@
throw new UncheckedIOException(e);
}
} else {
- indexResults = new PerJarIndexResults(new TreeSet<>(), new TreeMap<>());
+ indexResults = new PerJarIndexResults(new TreeSet<>(), new TreeSet<>(), new TreeMap<>());
}
toReturn.add(
@@ -212,6 +214,7 @@
result.getSha256(),
dependencies,
indexResults.getPackages(),
+ indexResults.getClasses(),
indexResults.getServiceImplementations()));
if (fetchSources) {
@@ -226,6 +229,7 @@
source.getSha256(),
ImmutableSet.of(),
ImmutableSet.of(),
+ ImmutableSet.of(),
ImmutableSortedMap.of()));
}
}
@@ -242,6 +246,7 @@
javadoc.getSha256(),
ImmutableSet.of(),
ImmutableSet.of(),
+ ImmutableSet.of(),
ImmutableSortedMap.of()));
}
}
@@ -261,8 +266,11 @@
listener.close();
+ // If a dependency index is being generated, we can omit packages from the lock file
+ // since that information is available in the index file
+ boolean includePackages = config.getDependencyIndexOutput() == null;
Map<String, Object> rendered =
- new V2LockFile(request.getRepositories(), infos, conflicts).render();
+ new V2LockFile(request.getRepositories(), infos, conflicts, includePackages).render();
Map<Object, Object> toReturn = new TreeMap<>(rendered);
// We don't need this, and having it will cause problems
@@ -287,6 +295,24 @@
}
}
+ private static void writeDependencyIndex(ResolverConfig config, Set<DependencyInfo> infos)
+ throws IOException {
+ Path output = config.getDependencyIndexOutput();
+ if (output == null) {
+ return;
+ }
+
+ Map<String, Object> rendered = new DependencyIndex(infos).render();
+
+ String converted =
+ new GsonBuilder().setPrettyPrinting().serializeNulls().create().toJson(rendered) + "\n";
+
+ try (OutputStream os = Files.newOutputStream(output);
+ BufferedOutputStream bos = new BufferedOutputStream(os)) {
+ bos.write(converted.getBytes(UTF_8));
+ }
+ }
+
private static int calculateArtifactHash(Map<String, Object> rendered) {
LinkedHashMap<Object, Object> toHash = new LinkedHashMap<>();
diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/ResolverConfig.java b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/ResolverConfig.java
index a4dba95..a296702 100644
--- a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/ResolverConfig.java
+++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/ResolverConfig.java
@@ -38,6 +38,7 @@
private final boolean fetchJavadoc;
private final Netrc netrc;
private final Path output;
+ private final Path dependencyIndexOutput;
private final String inputHash;
private final int maxThreads;
@@ -50,6 +51,7 @@
boolean fetchJavadoc = false;
int maxThreads = DEFAULT_MAX_THREADS;
Path output = null;
+ Path dependencyIndexOutput = null;
String inputHash = null;
if (System.getenv("RJE_MAX_THREADS") != null) {
@@ -94,6 +96,16 @@
}
break;
+ case "--dependency-index-output":
+ i++;
+ String workspaceDir = System.getenv("BUILD_WORKSPACE_DIRECTORY");
+ if (workspaceDir == null) {
+ dependencyIndexOutput = Paths.get(args[i]);
+ } else {
+ dependencyIndexOutput = Paths.get(workspaceDir).resolve(args[i]);
+ }
+ break;
+
case "--sources":
fetchSources = true;
break;
@@ -183,6 +195,7 @@
this.inputHash = inputHash;
this.maxThreads = maxThreads;
this.output = output;
+ this.dependencyIndexOutput = dependencyIndexOutput;
}
public ResolutionRequest getResolutionRequest() {
@@ -212,4 +225,8 @@
public Path getOutput() {
return output;
}
+
+ public Path getDependencyIndexOutput() {
+ return dependencyIndexOutput;
+ }
}
diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/DependencyIndex.java b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/DependencyIndex.java
new file mode 100644
index 0000000..f6a6a88
--- /dev/null
+++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/DependencyIndex.java
@@ -0,0 +1,168 @@
+// 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.
+
+package com.github.bazelbuild.rules_jvm_external.resolver.lockfile;
+
+import com.github.bazelbuild.rules_jvm_external.Coordinates;
+import com.github.bazelbuild.rules_jvm_external.resolver.DependencyInfo;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * Renders a dependency index containing class-level information for each artifact. This is stored
+ * separately from the lock file since class information can be large and is not needed for
+ * resolution.
+ *
+ * <p>To minimize file size:
+ *
+ * <ul>
+ * <li>Packages that appear in only one artifact are listed in the "packages" section (just
+ * package names)
+ * <li>Packages that appear in multiple artifacts (collisions) are listed in the "classes" section
+ * with full class listings
+ * </ul>
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * {
+ * "version": 1,
+ * "packages": {
+ * "com.google.guava:guava": ["com.google.common.base", "com.google.common.collect"]
+ * },
+ * "classes": {
+ * "com.google.guava:guava": {
+ * "javax.annotation": ["Nullable"]
+ * },
+ * "com.google.code.findbugs:jsr305": {
+ * "javax.annotation": ["Nonnull", "Nullable"]
+ * }
+ * }
+ * }
+ * </pre>
+ */
+public class DependencyIndex {
+
+ private static final int VERSION = 1;
+
+ private final Set<DependencyInfo> infos;
+
+ public DependencyIndex(Set<DependencyInfo> infos) {
+ this.infos = infos;
+ }
+
+ public Map<String, Object> render() {
+ // Step 1: Collect all data and track which packages appear in multiple artifacts
+ // Map: package -> set of artifact keys that contain this package
+ Map<String, Set<String>> packageToArtifacts = new TreeMap<>();
+ // Map: artifact key -> (package -> set of simple class names)
+ Map<String, Map<String, Set<String>>> artifactToPackageToClasses = new TreeMap<>();
+
+ for (DependencyInfo info : infos) {
+ // Skip sources and javadoc artifacts - they don't have classes
+ String classifier = info.getCoordinates().getClassifier();
+ if ("sources".equals(classifier) || "javadoc".equals(classifier)) {
+ continue;
+ }
+
+ Set<String> infoClasses = info.getClasses();
+ if (infoClasses == null || infoClasses.isEmpty()) {
+ continue;
+ }
+
+ String artifactKey = asKey(info.getCoordinates());
+ Map<String, Set<String>> packageToClasses = new TreeMap<>();
+
+ for (String fqcn : infoClasses) {
+ String packageName = extractPackage(fqcn);
+ String simpleClassName = extractSimpleClassName(fqcn);
+
+ packageToClasses.computeIfAbsent(packageName, k -> new TreeSet<>()).add(simpleClassName);
+ packageToArtifacts.computeIfAbsent(packageName, k -> new TreeSet<>()).add(artifactKey);
+ }
+
+ artifactToPackageToClasses.put(artifactKey, packageToClasses);
+ }
+
+ // Step 2: Identify colliding packages (appear in more than one artifact)
+ Set<String> collidingPackages = new TreeSet<>();
+ for (Map.Entry<String, Set<String>> entry : packageToArtifacts.entrySet()) {
+ if (entry.getValue().size() > 1) {
+ collidingPackages.add(entry.getKey());
+ }
+ }
+
+ // Step 3: Build the packages and classes sections
+ // packages: artifact -> [package names] (for unique packages)
+ Map<String, Set<String>> packages = new TreeMap<>();
+ // classes: artifact -> {package -> [class names]} (for colliding packages)
+ Map<String, Map<String, Set<String>>> classes = new TreeMap<>();
+
+ for (Map.Entry<String, Map<String, Set<String>>> entry :
+ artifactToPackageToClasses.entrySet()) {
+ String artifactKey = entry.getKey();
+ Map<String, Set<String>> pkgToClasses = entry.getValue();
+
+ Set<String> uniquePackages = new TreeSet<>();
+ Map<String, Set<String>> collidingClasses = new TreeMap<>();
+
+ for (Map.Entry<String, Set<String>> pkgEntry : pkgToClasses.entrySet()) {
+ String pkg = pkgEntry.getKey();
+ if (collidingPackages.contains(pkg)) {
+ collidingClasses.put(pkg, pkgEntry.getValue());
+ } else {
+ uniquePackages.add(pkg);
+ }
+ }
+
+ if (!uniquePackages.isEmpty()) {
+ packages.put(artifactKey, uniquePackages);
+ }
+ if (!collidingClasses.isEmpty()) {
+ classes.put(artifactKey, collidingClasses);
+ }
+ }
+
+ Map<String, Object> index = new TreeMap<>();
+ index.put(
+ "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY", "THERE_IS_NO_DATA_ONLY_ZUUL");
+ index.put("split_package_classes", classes);
+ index.put("packages", packages);
+ index.put("version", VERSION);
+
+ return index;
+ }
+
+ private static String asKey(Coordinates coords) {
+ return coords.asKey().toString();
+ }
+
+ private static String extractPackage(String fqcn) {
+ int lastDot = fqcn.lastIndexOf('.');
+ if (lastDot == -1) {
+ return "";
+ }
+ return fqcn.substring(0, lastDot);
+ }
+
+ private static String extractSimpleClassName(String fqcn) {
+ int lastDot = fqcn.lastIndexOf('.');
+ if (lastDot == -1) {
+ return fqcn;
+ }
+ return fqcn.substring(lastDot + 1);
+ }
+}
diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/V2LockFile.java b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/V2LockFile.java
index a85d9b7..018aa2f 100644
--- a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/V2LockFile.java
+++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/V2LockFile.java
@@ -43,12 +43,17 @@
private final Collection<URI> allRepos;
private final Set<DependencyInfo> infos;
private final Set<Conflict> conflicts;
+ private final boolean renderPackages;
public V2LockFile(
- Collection<URI> repositories, Set<DependencyInfo> infos, Set<Conflict> conflicts) {
+ Collection<URI> repositories,
+ Set<DependencyInfo> infos,
+ Set<Conflict> conflicts,
+ boolean renderPackages) {
this.allRepos = repositories;
this.infos = infos;
this.conflicts = conflicts;
+ this.renderPackages = renderPackages;
}
public Collection<URI> getRepositories() {
@@ -156,6 +161,7 @@
Optional.of(entry.getValue()),
dependencies.getOrDefault(coords, Set.of()),
Set.of(),
+ Set.of(),
new TreeMap<>()));
}
@@ -171,10 +177,9 @@
conflicts.add(new Conflict(resolved, requested));
}
- return new V2LockFile(repos, infos, conflicts);
+ return new V2LockFile(repos, infos, conflicts, true);
}
- /** "Render" the resolution result to a `Map` suitable for printing as JSON. */
public Map<String, Object> render() {
Set<URI> repositories = new LinkedHashSet<>(allRepos);
@@ -242,7 +247,9 @@
.map(Coordinates::asKey)
.map(Object::toString)
.collect(Collectors.toCollection(TreeSet::new)));
- packages.put(key, info.getPackages());
+ if (renderPackages) {
+ packages.put(key, info.getPackages());
+ }
services.put(key, info.getServices());
if (info.getPath().isPresent()) {
@@ -254,7 +261,9 @@
Map<String, Object> lock = new LinkedHashMap<>();
lock.put("artifacts", ensureArtifactsAllHaveAtLeastOneShaSum(artifacts));
lock.put("dependencies", removeEmptyItems(deps));
- lock.put("packages", removeEmptyItems(packages));
+ if (renderPackages) {
+ lock.put("packages", removeEmptyItems(packages));
+ }
lock.put("services", removeEmptyItemsMap(services));
if (isUsingM2Local) {
lock.put("m2local", true);
diff --git a/tests/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/BUILD b/tests/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/BUILD
index a53c0cc..0c26ea3 100644
--- a/tests/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/BUILD
+++ b/tests/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/BUILD
@@ -2,6 +2,23 @@
load("//:defs.bzl", "artifact")
java_test(
+ name = "DependencyIndexTest",
+ srcs = ["DependencyIndexTest.java"],
+ test_class = "com.github.bazelbuild.rules_jvm_external.resolver.lockfile.DependencyIndexTest",
+ deps = [
+ "//private/tools/java/com/github/bazelbuild/rules_jvm_external",
+ "//private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver",
+ "//private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/lockfile",
+ artifact(
+ "junit:junit",
+ repository_name = "regression_testing_coursier",
+ ),
+ artifact("org.hamcrest:hamcrest"),
+ artifact("org.hamcrest:hamcrest_core"),
+ ],
+)
+
+java_test(
name = "V2LockFileTest",
srcs = ["V2LockFileTest.java"],
test_class = "com.github.bazelbuild.rules_jvm_external.resolver.lockfile.V2LockFileTest",
diff --git a/tests/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/DependencyIndexTest.java b/tests/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/DependencyIndexTest.java
new file mode 100644
index 0000000..ec84b9d
--- /dev/null
+++ b/tests/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/DependencyIndexTest.java
@@ -0,0 +1,311 @@
+// 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.
+
+package com.github.bazelbuild.rules_jvm_external.resolver.lockfile;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.github.bazelbuild.rules_jvm_external.Coordinates;
+import com.github.bazelbuild.rules_jvm_external.resolver.DependencyInfo;
+import java.net.URI;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import org.junit.Test;
+
+public class DependencyIndexTest {
+
+ private final URI defaultRepo = URI.create("http://localhost/m2/repository/");
+ private final Set<URI> repos = Set.of(defaultRepo);
+
+ @Test
+ public void shouldIncludeVersionNumber() {
+ DependencyIndex index = new DependencyIndex(Set.of());
+ Map<String, Object> rendered = index.render();
+
+ assertEquals(1, rendered.get("version"));
+ }
+
+ @Test
+ public void shouldRenderEmptySectionsForEmptyInfos() {
+ DependencyIndex index = new DependencyIndex(Set.of());
+ Map<String, Object> rendered = index.render();
+
+ @SuppressWarnings("unchecked")
+ Map<String, Set<String>> packages = (Map<String, Set<String>>) rendered.get("packages");
+ assertTrue(packages.isEmpty());
+
+ @SuppressWarnings("unchecked")
+ Map<String, Map<String, Set<String>>> classes =
+ (Map<String, Map<String, Set<String>>>) rendered.get("split_package_classes");
+ assertTrue(classes.isEmpty());
+ }
+
+ @Test
+ public void shouldPutUniquePackagesInPackagesSection() {
+ Set<String> artifactClasses = new TreeSet<>();
+ artifactClasses.add("com.example.Foo");
+ artifactClasses.add("com.example.Bar");
+ artifactClasses.add("com.example.sub.Baz");
+
+ DependencyInfo info =
+ new DependencyInfo(
+ new Coordinates("com.example:item:1.0.0"),
+ repos,
+ Optional.empty(),
+ Optional.of("abc123"),
+ Set.of(),
+ Set.of("com.example", "com.example.sub"),
+ artifactClasses,
+ new TreeMap<>());
+
+ DependencyIndex index = new DependencyIndex(Set.of(info));
+ Map<String, Object> rendered = index.render();
+
+ // Unique packages go to "packages" section
+ @SuppressWarnings("unchecked")
+ Map<String, Set<String>> packages = (Map<String, Set<String>>) rendered.get("packages");
+ assertEquals(1, packages.size());
+ assertEquals(Set.of("com.example", "com.example.sub"), packages.get("com.example:item"));
+
+ // No collisions, so "split_package_classes" section should be empty for this artifact
+ @SuppressWarnings("unchecked")
+ Map<String, Map<String, Set<String>>> classes =
+ (Map<String, Map<String, Set<String>>>) rendered.get("split_package_classes");
+ assertTrue(classes.isEmpty());
+ }
+
+ @Test
+ public void shouldPutCollidingPackagesInClassesSection() {
+ // Two artifacts share the same package "javax.annotation"
+ Set<String> guavaClasses = new TreeSet<>();
+ guavaClasses.add("javax.annotation.Nullable");
+ guavaClasses.add("com.google.common.base.Optional");
+
+ Set<String> jsr305Classes = new TreeSet<>();
+ jsr305Classes.add("javax.annotation.Nonnull");
+ jsr305Classes.add("javax.annotation.Nullable");
+
+ DependencyInfo guava =
+ new DependencyInfo(
+ new Coordinates("com.google.guava:guava:33.0.0"),
+ repos,
+ Optional.empty(),
+ Optional.of("abc123"),
+ Set.of(),
+ Set.of("javax.annotation", "com.google.common.base"),
+ guavaClasses,
+ new TreeMap<>());
+
+ DependencyInfo jsr305 =
+ new DependencyInfo(
+ new Coordinates("com.google.code.findbugs:jsr305:3.0.2"),
+ repos,
+ Optional.empty(),
+ Optional.of("def456"),
+ Set.of(),
+ Set.of("javax.annotation"),
+ jsr305Classes,
+ new TreeMap<>());
+
+ DependencyIndex index = new DependencyIndex(Set.of(guava, jsr305));
+ Map<String, Object> rendered = index.render();
+
+ // Unique packages go to "packages" section
+ @SuppressWarnings("unchecked")
+ Map<String, Set<String>> packages = (Map<String, Set<String>>) rendered.get("packages");
+ assertEquals(1, packages.size());
+ assertEquals(Set.of("com.google.common.base"), packages.get("com.google.guava:guava"));
+ assertNull(packages.get("com.google.code.findbugs:jsr305")); // jsr305 has no unique packages
+
+ // Colliding package goes to "split_package_classes" section with full class listings
+ @SuppressWarnings("unchecked")
+ Map<String, Map<String, Set<String>>> classes =
+ (Map<String, Map<String, Set<String>>>) rendered.get("split_package_classes");
+ assertEquals(2, classes.size());
+
+ Map<String, Set<String>> guavaCollisions = classes.get("com.google.guava:guava");
+ assertEquals(1, guavaCollisions.size());
+ assertEquals(Set.of("Nullable"), guavaCollisions.get("javax.annotation"));
+
+ Map<String, Set<String>> jsr305Collisions = classes.get("com.google.code.findbugs:jsr305");
+ assertEquals(1, jsr305Collisions.size());
+ assertEquals(Set.of("Nonnull", "Nullable"), jsr305Collisions.get("javax.annotation"));
+ }
+
+ @Test
+ public void shouldHandleArtifactWithBothUniqueAndCollidingPackages() {
+ Set<String> artifact1Classes = new TreeSet<>();
+ artifact1Classes.add("com.shared.Foo");
+ artifact1Classes.add("com.unique1.Bar");
+
+ Set<String> artifact2Classes = new TreeSet<>();
+ artifact2Classes.add("com.shared.Baz");
+ artifact2Classes.add("com.unique2.Qux");
+
+ DependencyInfo artifact1 =
+ new DependencyInfo(
+ new Coordinates("com.example:artifact1:1.0.0"),
+ repos,
+ Optional.empty(),
+ Optional.of("abc123"),
+ Set.of(),
+ Set.of("com.shared", "com.unique1"),
+ artifact1Classes,
+ new TreeMap<>());
+
+ DependencyInfo artifact2 =
+ new DependencyInfo(
+ new Coordinates("com.example:artifact2:1.0.0"),
+ repos,
+ Optional.empty(),
+ Optional.of("def456"),
+ Set.of(),
+ Set.of("com.shared", "com.unique2"),
+ artifact2Classes,
+ new TreeMap<>());
+
+ DependencyIndex index = new DependencyIndex(Set.of(artifact1, artifact2));
+ Map<String, Object> rendered = index.render();
+
+ // Unique packages in "packages" section
+ @SuppressWarnings("unchecked")
+ Map<String, Set<String>> packages = (Map<String, Set<String>>) rendered.get("packages");
+ assertEquals(2, packages.size());
+ assertEquals(Set.of("com.unique1"), packages.get("com.example:artifact1"));
+ assertEquals(Set.of("com.unique2"), packages.get("com.example:artifact2"));
+
+ // Colliding package in "split_package_classes" section
+ @SuppressWarnings("unchecked")
+ Map<String, Map<String, Set<String>>> classes =
+ (Map<String, Map<String, Set<String>>>) rendered.get("split_package_classes");
+ assertEquals(2, classes.size());
+
+ assertEquals(Set.of("Foo"), classes.get("com.example:artifact1").get("com.shared"));
+ assertEquals(Set.of("Baz"), classes.get("com.example:artifact2").get("com.shared"));
+ }
+
+ @Test
+ public void shouldHandleDefaultPackage() {
+ Set<String> artifactClasses = new TreeSet<>();
+ artifactClasses.add("Foo");
+
+ DependencyInfo info =
+ new DependencyInfo(
+ new Coordinates("com.example:item:1.0.0"),
+ repos,
+ Optional.empty(),
+ Optional.of("abc123"),
+ Set.of(),
+ Set.of(""),
+ artifactClasses,
+ new TreeMap<>());
+
+ DependencyIndex index = new DependencyIndex(Set.of(info));
+ Map<String, Object> rendered = index.render();
+
+ // Default package (empty string) should be in packages section since it's unique
+ @SuppressWarnings("unchecked")
+ Map<String, Set<String>> packages = (Map<String, Set<String>>) rendered.get("packages");
+ assertEquals(Set.of(""), packages.get("com.example:item"));
+ }
+
+ @Test
+ public void shouldSkipSourcesArtifacts() {
+ Set<String> artifactClasses = new TreeSet<>();
+ artifactClasses.add("com.example.Foo");
+
+ DependencyInfo sources =
+ new DependencyInfo(
+ new Coordinates("com.example:item:jar:sources:1.0.0"),
+ repos,
+ Optional.empty(),
+ Optional.of("abc123"),
+ Set.of(),
+ Set.of(),
+ artifactClasses,
+ new TreeMap<>());
+
+ DependencyIndex index = new DependencyIndex(Set.of(sources));
+ Map<String, Object> rendered = index.render();
+
+ @SuppressWarnings("unchecked")
+ Map<String, Set<String>> packages = (Map<String, Set<String>>) rendered.get("packages");
+ assertTrue(packages.isEmpty());
+
+ @SuppressWarnings("unchecked")
+ Map<String, Map<String, Set<String>>> classes =
+ (Map<String, Map<String, Set<String>>>) rendered.get("split_package_classes");
+ assertTrue(classes.isEmpty());
+ }
+
+ @Test
+ public void shouldSkipJavadocArtifacts() {
+ Set<String> artifactClasses = new TreeSet<>();
+ artifactClasses.add("com.example.Foo");
+
+ DependencyInfo javadoc =
+ new DependencyInfo(
+ new Coordinates("com.example:item:jar:javadoc:1.0.0"),
+ repos,
+ Optional.empty(),
+ Optional.of("abc123"),
+ Set.of(),
+ Set.of(),
+ artifactClasses,
+ new TreeMap<>());
+
+ DependencyIndex index = new DependencyIndex(Set.of(javadoc));
+ Map<String, Object> rendered = index.render();
+
+ @SuppressWarnings("unchecked")
+ Map<String, Set<String>> packages = (Map<String, Set<String>>) rendered.get("packages");
+ assertTrue(packages.isEmpty());
+
+ @SuppressWarnings("unchecked")
+ Map<String, Map<String, Set<String>>> classes =
+ (Map<String, Map<String, Set<String>>>) rendered.get("split_package_classes");
+ assertTrue(classes.isEmpty());
+ }
+
+ @Test
+ public void shouldSkipArtifactsWithNoClasses() {
+ DependencyInfo info =
+ new DependencyInfo(
+ new Coordinates("com.example:item:1.0.0"),
+ repos,
+ Optional.empty(),
+ Optional.of("abc123"),
+ Set.of(),
+ Set.of("com.example"),
+ Set.of(),
+ new TreeMap<>());
+
+ DependencyIndex index = new DependencyIndex(Set.of(info));
+ Map<String, Object> rendered = index.render();
+
+ @SuppressWarnings("unchecked")
+ Map<String, Set<String>> packages = (Map<String, Set<String>>) rendered.get("packages");
+ assertTrue(packages.isEmpty());
+
+ @SuppressWarnings("unchecked")
+ Map<String, Map<String, Set<String>>> classes =
+ (Map<String, Map<String, Set<String>>>) rendered.get("split_package_classes");
+ assertTrue(classes.isEmpty());
+ }
+}
diff --git a/tests/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/V2LockFileTest.java b/tests/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/V2LockFileTest.java
index e1c1038..b02734a 100644
--- a/tests/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/V2LockFileTest.java
+++ b/tests/com/github/bazelbuild/rules_jvm_external/resolver/lockfile/V2LockFileTest.java
@@ -15,6 +15,9 @@
package com.github.bazelbuild.rules_jvm_external.resolver.lockfile;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import com.github.bazelbuild.rules_jvm_external.Coordinates;
import com.github.bazelbuild.rules_jvm_external.resolver.Conflict;
@@ -43,9 +46,11 @@
Optional.empty(),
Set.of(),
Set.of(),
+ Set.of(),
new TreeMap<>());
- Map<String, Object> rendered = new V2LockFile(repos, Set.of(aggregator), Set.of()).render();
+ Map<String, Object> rendered =
+ new V2LockFile(repos, Set.of(aggregator), Set.of(), true).render();
Map<?, ?> artifacts = (Map<?, ?>) rendered.get("artifacts");
Map<?, ?> data = (Map<?, ?>) artifacts.get("com.example:aggregator");
@@ -58,7 +63,7 @@
@Test
public void shouldRoundTripASimpleSetOfDependencies() {
- V2LockFile roundTripped = roundTrip(new V2LockFile(repos, Set.of(), Set.of()));
+ V2LockFile roundTripped = roundTrip(new V2LockFile(repos, Set.of(), Set.of(), true));
assertEquals(repos, roundTripped.getRepositories());
assertEquals(Set.of(), roundTripped.getDependencyInfos());
@@ -67,7 +72,7 @@
@Test
public void shouldRoundTripM2Local() {
- V2LockFile lockFile = new V2LockFile(repos, Set.of(), Set.of());
+ V2LockFile lockFile = new V2LockFile(repos, Set.of(), Set.of(), true);
Map<String, Object> rendered = lockFile.render();
rendered.put("m2local", true);
@@ -88,9 +93,10 @@
Optional.of("c2c97a708be197aae5fee64dcc8b5e8a09c76c79a44c0e8e5b48b235084ec395"),
Set.of(),
Set.of(),
+ Set.of(),
new TreeMap<>());
- V2LockFile lockFile = roundTrip(new V2LockFile(repos, Set.of(info), Set.of()));
+ V2LockFile lockFile = roundTrip(new V2LockFile(repos, Set.of(info), Set.of(), true));
assertEquals(Set.of(info), lockFile.getDependencyInfos());
}
@@ -107,6 +113,7 @@
Optional.of("cafebad08be197aae5fee64dcc8b5e8a09c76c79a44c0e8e5b48b235084ec395"),
Set.of(),
Set.of(),
+ Set.of(),
new TreeMap<>());
DependencyInfo info =
@@ -117,9 +124,10 @@
Optional.of("c2c97a708be197aae5fee64dcc8b5e8a09c76c79a44c0e8e5b48b235084ec395"),
Set.of(depCoords),
Set.of(),
+ Set.of(),
new TreeMap<>());
- V2LockFile lockFile = roundTrip(new V2LockFile(repos, Set.of(info, dep), Set.of()));
+ V2LockFile lockFile = roundTrip(new V2LockFile(repos, Set.of(info, dep), Set.of(), true));
assertEquals(Set.of(info, dep), lockFile.getDependencyInfos());
}
@@ -133,11 +141,77 @@
new Conflict(
new Coordinates("com.foo:bar:1.2.3"), new Coordinates("com.foo:bar:1.2.1")));
- V2LockFile lockFile = roundTrip(new V2LockFile(repos, Set.of(), conflicts));
+ V2LockFile lockFile = roundTrip(new V2LockFile(repos, Set.of(), conflicts, true));
assertEquals(conflicts, lockFile.getConflicts());
}
+ @Test
+ public void shouldIncludePackagesWhenIncludePackagesIsTrue() {
+ DependencyInfo info =
+ new DependencyInfo(
+ new Coordinates("com.example:item:1.0.0"),
+ repos,
+ Optional.empty(),
+ Optional.of("abc123"),
+ Set.of(),
+ Set.of("com.example", "com.example.sub"),
+ Set.of(),
+ new TreeMap<>());
+
+ Map<String, Object> rendered = new V2LockFile(repos, Set.of(info), Set.of(), true).render();
+
+ assertNotNull(rendered.get("packages"));
+ @SuppressWarnings("unchecked")
+ Map<String, Set<String>> packages = (Map<String, Set<String>>) rendered.get("packages");
+ assertFalse(packages.isEmpty());
+ assertEquals(Set.of("com.example", "com.example.sub"), packages.get("com.example:item"));
+ }
+
+ @Test
+ public void shouldExcludePackagesWhenIncludePackagesIsFalse() {
+ DependencyInfo info =
+ new DependencyInfo(
+ new Coordinates("com.example:item:1.0.0"),
+ repos,
+ Optional.empty(),
+ Optional.of("abc123"),
+ Set.of(),
+ Set.of("com.example", "com.example.sub"),
+ Set.of(),
+ new TreeMap<>());
+
+ Map<String, Object> rendered = new V2LockFile(repos, Set.of(info), Set.of(), false).render();
+
+ assertNull(rendered.get("packages"));
+ }
+
+ @Test
+ public void shouldStillIncludeOtherFieldsWhenPackagesExcluded() {
+ DependencyInfo info =
+ new DependencyInfo(
+ new Coordinates("com.example:item:1.0.0"),
+ repos,
+ Optional.empty(),
+ Optional.of("abc123"),
+ Set.of(),
+ Set.of("com.example"),
+ Set.of(),
+ new TreeMap<>());
+
+ Map<String, Object> rendered = new V2LockFile(repos, Set.of(info), Set.of(), false).render();
+
+ // Packages should be excluded
+ assertNull(rendered.get("packages"));
+
+ // But other fields should still be present
+ assertNotNull(rendered.get("artifacts"));
+ assertNotNull(rendered.get("dependencies"));
+ assertNotNull(rendered.get("services"));
+ assertNotNull(rendered.get("repositories"));
+ assertEquals("2", rendered.get("version"));
+ }
+
private V2LockFile roundTrip(V2LockFile lockFile) {
Map<String, Object> rendered = lockFile.render();
String converted =
diff --git a/tests/custom_maven_install/regression_testing_gradle_index.json b/tests/custom_maven_install/regression_testing_gradle_index.json
new file mode 100644
index 0000000..6c4961a
--- /dev/null
+++ b/tests/custom_maven_install/regression_testing_gradle_index.json
@@ -0,0 +1,164 @@
+{
+ "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
+ "packages": {
+ "androidx.annotation:annotation-jvm": [
+ "androidx.annotation"
+ ],
+ "androidx.arch.core:core-common": [
+ "androidx.arch.core.internal",
+ "androidx.arch.core.util"
+ ],
+ "androidx.concurrent:concurrent-futures": [
+ "androidx.concurrent.futures"
+ ],
+ "androidx.lifecycle:lifecycle-common": [
+ "androidx.lifecycle"
+ ],
+ "ch.qos.reload4j:reload4j": [
+ "org.apache.log4j",
+ "org.apache.log4j.chainsaw",
+ "org.apache.log4j.config",
+ "org.apache.log4j.helpers",
+ "org.apache.log4j.jdbc",
+ "org.apache.log4j.net",
+ "org.apache.log4j.or",
+ "org.apache.log4j.or.jms",
+ "org.apache.log4j.or.sax",
+ "org.apache.log4j.pattern",
+ "org.apache.log4j.rewrite",
+ "org.apache.log4j.spi",
+ "org.apache.log4j.varia",
+ "org.apache.log4j.xml"
+ ],
+ "com.almworks.sqlite4java:sqlite4java": [
+ "com.almworks.sqlite4java",
+ "javolution.util.stripped"
+ ],
+ "com.google.guava:listenablefuture": [
+ "com.google.common.util.concurrent"
+ ],
+ "com.squareup.okhttp3:okhttp": [
+ "okhttp3",
+ "okhttp3.internal",
+ "okhttp3.internal.authenticator",
+ "okhttp3.internal.cache",
+ "okhttp3.internal.cache2",
+ "okhttp3.internal.concurrent",
+ "okhttp3.internal.connection",
+ "okhttp3.internal.http",
+ "okhttp3.internal.http1",
+ "okhttp3.internal.http2",
+ "okhttp3.internal.io",
+ "okhttp3.internal.platform",
+ "okhttp3.internal.platform.android",
+ "okhttp3.internal.proxy",
+ "okhttp3.internal.publicsuffix",
+ "okhttp3.internal.tls",
+ "okhttp3.internal.ws"
+ ],
+ "com.squareup.okio:okio-jvm": [
+ "okio",
+ "okio.internal"
+ ],
+ "org.jetbrains.kotlin:kotlin-stdlib": [
+ "kotlin",
+ "kotlin.annotation",
+ "kotlin.collections",
+ "kotlin.collections.builders",
+ "kotlin.collections.jdk8",
+ "kotlin.collections.unsigned",
+ "kotlin.comparisons",
+ "kotlin.concurrent",
+ "kotlin.contracts",
+ "kotlin.coroutines",
+ "kotlin.coroutines.cancellation",
+ "kotlin.coroutines.intrinsics",
+ "kotlin.coroutines.jvm.internal",
+ "kotlin.enums",
+ "kotlin.experimental",
+ "kotlin.internal",
+ "kotlin.internal.jdk7",
+ "kotlin.internal.jdk8",
+ "kotlin.io",
+ "kotlin.io.encoding",
+ "kotlin.io.path",
+ "kotlin.jdk7",
+ "kotlin.js",
+ "kotlin.jvm",
+ "kotlin.jvm.functions",
+ "kotlin.jvm.internal",
+ "kotlin.jvm.internal.markers",
+ "kotlin.jvm.internal.unsafe",
+ "kotlin.jvm.jdk8",
+ "kotlin.jvm.optionals",
+ "kotlin.math",
+ "kotlin.properties",
+ "kotlin.random",
+ "kotlin.random.jdk8",
+ "kotlin.ranges",
+ "kotlin.reflect",
+ "kotlin.sequences",
+ "kotlin.streams.jdk8",
+ "kotlin.system",
+ "kotlin.text",
+ "kotlin.text.jdk8",
+ "kotlin.time",
+ "kotlin.time.jdk8"
+ ],
+ "org.jetbrains.kotlinx:kotlinx-coroutines-android": [
+ "kotlinx.coroutines.android"
+ ],
+ "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm": [
+ "kotlinx.coroutines",
+ "kotlinx.coroutines.channels",
+ "kotlinx.coroutines.debug",
+ "kotlinx.coroutines.debug.internal",
+ "kotlinx.coroutines.flow",
+ "kotlinx.coroutines.flow.internal",
+ "kotlinx.coroutines.internal",
+ "kotlinx.coroutines.intrinsics",
+ "kotlinx.coroutines.scheduling",
+ "kotlinx.coroutines.selects",
+ "kotlinx.coroutines.sync"
+ ],
+ "org.jetbrains:annotations": [
+ "org.intellij.lang.annotations",
+ "org.jetbrains.annotations"
+ ],
+ "org.slf4j:slf4j-api": [
+ "org.slf4j",
+ "org.slf4j.event",
+ "org.slf4j.helpers",
+ "org.slf4j.spi"
+ ],
+ "org.slf4j:slf4j-reload4j": [
+ "org.slf4j.reload4j"
+ ]
+ },
+ "split_package_classes": {
+ "androidx.collection:collection": {
+ "androidx.collection": [
+ "ArrayMap",
+ "ArraySet",
+ "CircularArray",
+ "CircularIntArray",
+ "ContainerHelpers",
+ "LongSparseArray",
+ "LruCache",
+ "MapCollections",
+ "SimpleArrayMap",
+ "SparseArrayCompat"
+ ]
+ },
+ "androidx.collection:collection-ktx": {
+ "androidx.collection": [
+ "ArrayMapKt",
+ "ArraySetKt",
+ "LongSparseArrayKt",
+ "LruCacheKt",
+ "SparseArrayKt"
+ ]
+ }
+ },
+ "version": 1
+}
diff --git a/tests/custom_maven_install/regression_testing_gradle_install.json b/tests/custom_maven_install/regression_testing_gradle_install.json
index 29e297f..0476c34 100644
--- a/tests/custom_maven_install/regression_testing_gradle_install.json
+++ b/tests/custom_maven_install/regression_testing_gradle_install.json
@@ -952,147 +952,6 @@
"org.slf4j:slf4j-api"
]
},
- "packages": {
- "androidx.annotation:annotation-jvm": [
- "androidx.annotation"
- ],
- "androidx.arch.core:core-common": [
- "androidx.arch.core.internal",
- "androidx.arch.core.util"
- ],
- "androidx.collection:collection": [
- "androidx.collection"
- ],
- "androidx.collection:collection-ktx": [
- "androidx.collection"
- ],
- "androidx.concurrent:concurrent-futures": [
- "androidx.concurrent.futures"
- ],
- "androidx.lifecycle:lifecycle-common": [
- "androidx.lifecycle"
- ],
- "ch.qos.reload4j:reload4j": [
- "org.apache.log4j",
- "org.apache.log4j.chainsaw",
- "org.apache.log4j.config",
- "org.apache.log4j.helpers",
- "org.apache.log4j.jdbc",
- "org.apache.log4j.net",
- "org.apache.log4j.or",
- "org.apache.log4j.or.jms",
- "org.apache.log4j.or.sax",
- "org.apache.log4j.pattern",
- "org.apache.log4j.rewrite",
- "org.apache.log4j.spi",
- "org.apache.log4j.varia",
- "org.apache.log4j.xml"
- ],
- "com.almworks.sqlite4java:sqlite4java": [
- "com.almworks.sqlite4java",
- "javolution.util.stripped"
- ],
- "com.google.guava:listenablefuture": [
- "com.google.common.util.concurrent"
- ],
- "com.squareup.okhttp3:okhttp": [
- "okhttp3",
- "okhttp3.internal",
- "okhttp3.internal.authenticator",
- "okhttp3.internal.cache",
- "okhttp3.internal.cache2",
- "okhttp3.internal.concurrent",
- "okhttp3.internal.connection",
- "okhttp3.internal.http",
- "okhttp3.internal.http1",
- "okhttp3.internal.http2",
- "okhttp3.internal.io",
- "okhttp3.internal.platform",
- "okhttp3.internal.platform.android",
- "okhttp3.internal.proxy",
- "okhttp3.internal.publicsuffix",
- "okhttp3.internal.tls",
- "okhttp3.internal.ws"
- ],
- "com.squareup.okio:okio-jvm": [
- "okio",
- "okio.internal"
- ],
- "org.jetbrains.kotlin:kotlin-stdlib": [
- "kotlin",
- "kotlin.annotation",
- "kotlin.collections",
- "kotlin.collections.builders",
- "kotlin.collections.jdk8",
- "kotlin.collections.unsigned",
- "kotlin.comparisons",
- "kotlin.concurrent",
- "kotlin.contracts",
- "kotlin.coroutines",
- "kotlin.coroutines.cancellation",
- "kotlin.coroutines.intrinsics",
- "kotlin.coroutines.jvm.internal",
- "kotlin.enums",
- "kotlin.experimental",
- "kotlin.internal",
- "kotlin.internal.jdk7",
- "kotlin.internal.jdk8",
- "kotlin.io",
- "kotlin.io.encoding",
- "kotlin.io.path",
- "kotlin.jdk7",
- "kotlin.js",
- "kotlin.jvm",
- "kotlin.jvm.functions",
- "kotlin.jvm.internal",
- "kotlin.jvm.internal.markers",
- "kotlin.jvm.internal.unsafe",
- "kotlin.jvm.jdk8",
- "kotlin.jvm.optionals",
- "kotlin.math",
- "kotlin.properties",
- "kotlin.random",
- "kotlin.random.jdk8",
- "kotlin.ranges",
- "kotlin.reflect",
- "kotlin.sequences",
- "kotlin.streams.jdk8",
- "kotlin.system",
- "kotlin.text",
- "kotlin.text.jdk8",
- "kotlin.time",
- "kotlin.time.jdk8"
- ],
- "org.jetbrains.kotlinx:kotlinx-coroutines-android": [
- "kotlinx.coroutines.android"
- ],
- "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm": [
- "kotlinx.coroutines",
- "kotlinx.coroutines.channels",
- "kotlinx.coroutines.debug",
- "kotlinx.coroutines.debug.internal",
- "kotlinx.coroutines.flow",
- "kotlinx.coroutines.flow.internal",
- "kotlinx.coroutines.internal",
- "kotlinx.coroutines.intrinsics",
- "kotlinx.coroutines.scheduling",
- "kotlinx.coroutines.selects",
- "kotlinx.coroutines.sync"
- ],
- "org.jetbrains:annotations": [
- "org.intellij.lang.annotations",
- "org.jetbrains.annotations"
- ],
- "org.slf4j:slf4j-api": [
- "org.slf4j",
- "org.slf4j.event",
- "org.slf4j.helpers",
- "org.slf4j.spi"
- ],
- "org.slf4j:slf4j-reload4j": [
- "org.slf4j.reload4j"
- ]
- },
"repositories": {
"https://repo1.maven.org/maven2/": [
"ch.qos.reload4j:reload4j",