Index top-level kotlin functions, properties and type aliases properly. (#1599)
Top-level Kotlin functions, properties, and type aliases compile into a synthetic <File>Kt facade, so they were invisible to the class-name index and could not be attributed to an artifact in split packages.
Read them from the @kotlin.Metadata annotation via kotlin-metadata-jvm and fold the public ones into the class index under their Kotlin source names.
diff --git a/MODULE.bazel b/MODULE.bazel
index 7f1a3ca..eb4c180 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -69,6 +69,9 @@
"org.codehaus.plexus:plexus-xml:3.0.1",
"org.fusesource.jansi:jansi:2.4.2",
"org.gradle:gradle-tooling-api:%s" % _GRADLE_VERSION,
+ "org.jetbrains.kotlin:kotlin-metadata-jvm:2.2.0",
+ "org.jetbrains.kotlin:kotlin-stdlib:2.2.0",
+ "org.ow2.asm:asm:9.8",
"org.slf4j:jul-to-slf4j:2.0.17",
"org.slf4j:log4j-over-slf4j:2.0.17",
"org.slf4j:slf4j-simple:2.0.17",
diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/BUILD b/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/BUILD
index c131bb2..c83d521 100644
--- a/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/BUILD
+++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/BUILD
@@ -22,6 +22,7 @@
name = "IndexJar-lib",
srcs = [
"IndexJar.java",
+ "KotlinTopLevel.java",
"PerJarIndexResults.java",
],
visibility = [
@@ -33,6 +34,18 @@
"com.google.code.gson:gson",
repository_name = "rules_jvm_external_deps",
),
+ artifact(
+ "org.jetbrains.kotlin:kotlin-metadata-jvm",
+ repository_name = "rules_jvm_external_deps",
+ ),
+ artifact(
+ "org.jetbrains.kotlin:kotlin-stdlib",
+ repository_name = "rules_jvm_external_deps",
+ ),
+ artifact(
+ "org.ow2.asm:asm",
+ repository_name = "rules_jvm_external_deps",
+ ),
],
)
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 f147723..23077e5 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
@@ -108,8 +108,18 @@
if (isInnerClass(entry.getName())) {
continue;
}
- packages.add(extractPackageName(entry.getName()));
+ String packageName = extractPackageName(entry.getName());
+ packages.add(packageName);
classes.add(extractClassName(entry.getName()));
+
+ // Kotlin top-level functions, properties, and type aliases are compiled into a synthetic
+ // file facade class and are otherwise invisible in the index. Fold their names in so they
+ // can be attributed to this artifact (notably for split packages).
+ if (isKotlinFileFacade(entry.getName())) {
+ for (String declaration : KotlinTopLevel.topLevelDeclarationNames(zis.readAllBytes())) {
+ classes.add(packageName.isEmpty() ? declaration : packageName + "." + declaration);
+ }
+ }
}
} catch (ZipException e) {
System.err.printf("Caught ZipException: %s%n", e);
@@ -177,6 +187,14 @@
return IS_NUMERIC_VERSION.test(part);
}
+ private boolean isKotlinFileFacade(String zipEntryName) {
+ // Kotlin names the file facade for top-level declarations "<FileName>Kt.class" by default. We
+ // only inspect the bytecode of these candidates; KotlinTopLevel confirms the Kotlin metadata.
+ int lastSlash = zipEntryName.lastIndexOf('/');
+ String fileName = lastSlash == -1 ? zipEntryName : zipEntryName.substring(lastSlash + 1);
+ return fileName.endsWith("Kt.class");
+ }
+
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
diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/KotlinTopLevel.java b/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/KotlinTopLevel.java
new file mode 100644
index 0000000..5b3a092
--- /dev/null
+++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/jar/KotlinTopLevel.java
@@ -0,0 +1,249 @@
+// 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.
+
+package com.github.bazelbuild.rules_jvm_external.jar;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import kotlin.Metadata;
+import kotlin.metadata.Attributes;
+import kotlin.metadata.KmFunction;
+import kotlin.metadata.KmPackage;
+import kotlin.metadata.KmProperty;
+import kotlin.metadata.KmTypeAlias;
+import kotlin.metadata.Visibility;
+import kotlin.metadata.jvm.KotlinClassMetadata;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Extracts the simple names of top-level Kotlin declarations (functions, properties, and type
+ * aliases) from a compiled class.
+ *
+ * <p>Kotlin compiles file-level declarations into a synthetic "file facade" class (named {@code
+ * <FileName>Kt} by default). The declarations themselves are not visible as classes, so they are
+ * invisible to a JAR index that only records class file names. We read the {@code kotlin.Metadata}
+ * annotation that the compiler stamps onto the facade and recover the declared names so callers can
+ * attribute imports such as {@code com.example.someTopLevelFunction} to the providing artifact.
+ */
+class KotlinTopLevel {
+
+ private static final int ASM_API = Opcodes.ASM9;
+
+ private KotlinTopLevel() {}
+
+ /**
+ * Returns the simple names of top-level declarations if {@code classBytes} is a Kotlin file
+ * facade or multi-file class part, otherwise an empty set. Never throws: any class that cannot be
+ * parsed (for example one compiled for a newer bytecode version than the bundled ASM understands)
+ * yields an empty set.
+ */
+ static SortedSet<String> topLevelDeclarationNames(byte[] classBytes) {
+ SortedSet<String> names = new TreeSet<>();
+ try {
+ Metadata metadata = readMetadata(classBytes);
+ if (metadata == null) {
+ return names;
+ }
+
+ // readLenient tolerates metadata produced by a Kotlin compiler newer than this library.
+ KotlinClassMetadata parsed = KotlinClassMetadata.readLenient(metadata);
+
+ KmPackage kmPackage = null;
+ if (parsed instanceof KotlinClassMetadata.FileFacade) {
+ kmPackage = ((KotlinClassMetadata.FileFacade) parsed).getKmPackage();
+ } else if (parsed instanceof KotlinClassMetadata.MultiFileClassPart) {
+ kmPackage = ((KotlinClassMetadata.MultiFileClassPart) parsed).getKmPackage();
+ }
+ if (kmPackage == null) {
+ return names;
+ }
+
+ // Only public declarations can be imported from another artifact; internal and private ones
+ // would just add unreferenceable noise to the index. We emit the Kotlin source names (what an
+ // import statement uses), which can differ from the JVM method names when @JvmName is
+ // applied.
+ for (KmFunction function : kmPackage.getFunctions()) {
+ if (Attributes.getVisibility(function) == Visibility.PUBLIC) {
+ names.add(function.getName());
+ }
+ }
+ for (KmProperty property : kmPackage.getProperties()) {
+ if (Attributes.getVisibility(property) == Visibility.PUBLIC) {
+ names.add(property.getName());
+ }
+ }
+ for (KmTypeAlias typeAlias : kmPackage.getTypeAliases()) {
+ if (Attributes.getVisibility(typeAlias) == Visibility.PUBLIC) {
+ names.add(typeAlias.getName());
+ }
+ }
+ } catch (RuntimeException e) {
+ // Unsupported class file version, malformed metadata, etc. Skip rather than fail the index.
+ return new TreeSet<>();
+ }
+ return names;
+ }
+
+ private static Metadata readMetadata(byte[] classBytes) {
+ MetadataReader reader = new MetadataReader();
+ new ClassReader(classBytes)
+ .accept(reader, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+ return reader.toMetadata();
+ }
+
+ /**
+ * Collects the fields of a {@code kotlin.Metadata} annotation as ASM visits them.
+ *
+ * <p>The terse member names (k, mv, bv, d1, d2, xs, pn, xi) below are the annotation's
+ * {@code @JvmName} accessors, derived from
+ * https://github.com/JetBrains/kotlin/blob/v2.2.0/libraries/stdlib/jvm/runtime/kotlin/Metadata.kt
+ */
+ private static final class MetadataReader extends ClassVisitor {
+ private boolean isKotlin = false;
+ private int kind = 1;
+ private int extraInt = 0;
+ private String extraString = "";
+ private String packageName = "";
+ // ASM delivers primitive arrays (the int[] mv and bv) directly to visit(); only the String[]
+ // values d1 and d2 arrive element-by-element through visitArray().
+ private int[] metadataVersion = new int[0];
+ private int[] bytecodeVersion = new int[0];
+ private final List<String> data1 = new ArrayList<>();
+ private final List<String> data2 = new ArrayList<>();
+
+ private MetadataReader() {
+ super(ASM_API);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ if (!"Lkotlin/Metadata;".equals(descriptor)) {
+ return null;
+ }
+ isKotlin = true;
+ return new AnnotationVisitor(ASM_API) {
+ @Override
+ public void visit(String name, Object value) {
+ switch (name) {
+ case "k":
+ kind = (Integer) value;
+ break;
+ case "xi":
+ extraInt = (Integer) value;
+ break;
+ case "xs":
+ extraString = (String) value;
+ break;
+ case "pn":
+ packageName = (String) value;
+ break;
+ case "mv":
+ metadataVersion = (int[]) value;
+ break;
+ case "bv":
+ bytecodeVersion = (int[]) value;
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String arrayName) {
+ return new AnnotationVisitor(ASM_API) {
+ @Override
+ public void visit(String elementName, Object value) {
+ switch (arrayName) {
+ case "d1":
+ data1.add((String) value);
+ break;
+ case "d2":
+ data2.add((String) value);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+ }
+ };
+ }
+
+ // kotlin-metadata-jvm reads header data from a kotlin.Metadata instance, so we synthesize one
+ // from the values ASM read off the class. The parser only calls the accessors below, so the
+ // equals/hashCode that the annotation contract would otherwise require are unnecessary here.
+ @SuppressWarnings("BadAnnotationImplementation")
+ private Metadata toMetadata() {
+ if (!isKotlin) {
+ return null;
+ }
+ int[] mv = metadataVersion;
+ int[] bv = bytecodeVersion;
+ String[] d1 = data1.toArray(new String[0]);
+ String[] d2 = data2.toArray(new String[0]);
+ return new Metadata() {
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return Metadata.class;
+ }
+
+ @Override
+ public int k() {
+ return kind;
+ }
+
+ @Override
+ public int[] mv() {
+ return mv;
+ }
+
+ @Override
+ public int[] bv() {
+ return bv;
+ }
+
+ @Override
+ public String[] d1() {
+ return d1;
+ }
+
+ @Override
+ public String[] d2() {
+ return d2;
+ }
+
+ @Override
+ public String xs() {
+ return extraString;
+ }
+
+ @Override
+ public String pn() {
+ return packageName;
+ }
+
+ @Override
+ public int xi() {
+ return extraInt;
+ }
+ };
+ }
+ }
+}
diff --git a/private/tools/prebuilt/index_jar_deploy.jar b/private/tools/prebuilt/index_jar_deploy.jar
index 86e4532..f9f4d19 100755
--- a/private/tools/prebuilt/index_jar_deploy.jar
+++ b/private/tools/prebuilt/index_jar_deploy.jar
Binary files differ
diff --git a/rules_jvm_external_deps_install.json b/rules_jvm_external_deps_install.json
index 3ac8a48..ce127c8 100644
--- a/rules_jvm_external_deps_install.json
+++ b/rules_jvm_external_deps_install.json
@@ -34,6 +34,9 @@
"org.codehaus.plexus:plexus-xml": -877788702,
"org.fusesource.jansi:jansi": -1410834038,
"org.gradle:gradle-tooling-api": 313997634,
+ "org.jetbrains.kotlin:kotlin-metadata-jvm": -124260926,
+ "org.jetbrains.kotlin:kotlin-stdlib": -1504421277,
+ "org.ow2.asm:asm": 1585548667,
"org.slf4j:jul-to-slf4j": -387828120,
"org.slf4j:log4j-over-slf4j": -46153368,
"org.slf4j:slf4j-simple": -487947767,
@@ -297,6 +300,12 @@
"org.fusesource.jansi:jansi:jar:sources": 1953984195,
"org.gradle:gradle-tooling-api": -1223301096,
"org.gradle:gradle-tooling-api:jar:sources": 1645634195,
+ "org.jetbrains.kotlin:kotlin-metadata-jvm": 2067904078,
+ "org.jetbrains.kotlin:kotlin-metadata-jvm:jar:sources": -331016853,
+ "org.jetbrains.kotlin:kotlin-stdlib": -550485556,
+ "org.jetbrains.kotlin:kotlin-stdlib:jar:sources": -2122508904,
+ "org.jetbrains:annotations": 554168982,
+ "org.jetbrains:annotations:jar:sources": 1226441379,
"org.jspecify:jspecify": 117231129,
"org.jspecify:jspecify:jar:sources": -2134060298,
"org.ow2.asm:asm": 611381606,
@@ -1279,6 +1288,27 @@
},
"version": "9.5.0"
},
+ "org.jetbrains.kotlin:kotlin-metadata-jvm": {
+ "shasums": {
+ "jar": "501222ae7d63527e55f2e5e645f18d4eff2c19074c85b9bc055866e3ed2b17bf",
+ "sources": "2ab97ab16e203e715633593c480bcf0ac1cc151e54cd2adaaa7dca1085835ede"
+ },
+ "version": "2.2.0"
+ },
+ "org.jetbrains.kotlin:kotlin-stdlib": {
+ "shasums": {
+ "jar": "65d12d85a3b865c160db9147851712a64b10dadd68b22eea22a95bf8a8670dca",
+ "sources": "967ad9599254e3a60d96d6c789547cc35c22d770d9c8fb1e3f15fac3b4c3b65d"
+ },
+ "version": "2.2.0"
+ },
+ "org.jetbrains:annotations": {
+ "shasums": {
+ "jar": "ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478",
+ "sources": "42a5e144b8e81d50d6913d1007b695e62e614705268d8cf9f13dbdc478c2c68e"
+ },
+ "version": "13.0"
+ },
"org.jspecify:jspecify": {
"shasums": {
"jar": "1fad6e6be7557781e4d33729d49ae1cdc8fdda6fe477bb0cc68ce351eafdfbab",
@@ -1941,6 +1971,12 @@
"org.gradle:gradle-tooling-api": [
"org.slf4j:slf4j-api"
],
+ "org.jetbrains.kotlin:kotlin-metadata-jvm": [
+ "org.jetbrains.kotlin:kotlin-stdlib"
+ ],
+ "org.jetbrains.kotlin:kotlin-stdlib": [
+ "org.jetbrains:annotations"
+ ],
"org.slf4j:jcl-over-slf4j": [
"org.slf4j:slf4j-api"
],
@@ -3599,6 +3635,74 @@
"org.gradle.util.internal",
"org.gradle.wrapper"
],
+ "org.jetbrains.kotlin:kotlin-metadata-jvm": [
+ "kotlin.metadata",
+ "kotlin.metadata.internal",
+ "kotlin.metadata.internal.common",
+ "kotlin.metadata.internal.extensions",
+ "kotlin.metadata.internal.metadata",
+ "kotlin.metadata.internal.metadata.builtins",
+ "kotlin.metadata.internal.metadata.deserialization",
+ "kotlin.metadata.internal.metadata.jvm",
+ "kotlin.metadata.internal.metadata.jvm.deserialization",
+ "kotlin.metadata.internal.metadata.jvm.serialization",
+ "kotlin.metadata.internal.metadata.serialization",
+ "kotlin.metadata.internal.protobuf",
+ "kotlin.metadata.jvm",
+ "kotlin.metadata.jvm.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.concurrent.atomics",
+ "kotlin.concurrent.internal",
+ "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",
+ "kotlin.uuid"
+ ],
+ "org.jetbrains:annotations": [
+ "org.intellij.lang.annotations",
+ "org.jetbrains.annotations"
+ ],
"org.jspecify:jspecify": [
"org.jspecify.annotations"
],
@@ -4189,6 +4293,12 @@
"org.fusesource.jansi:jansi:jar:sources",
"org.gradle:gradle-tooling-api",
"org.gradle:gradle-tooling-api:jar:sources",
+ "org.jetbrains.kotlin:kotlin-metadata-jvm",
+ "org.jetbrains.kotlin:kotlin-metadata-jvm:jar:sources",
+ "org.jetbrains.kotlin:kotlin-stdlib",
+ "org.jetbrains.kotlin:kotlin-stdlib:jar:sources",
+ "org.jetbrains:annotations",
+ "org.jetbrains:annotations:jar:sources",
"org.jspecify:jspecify",
"org.jspecify:jspecify:jar:sources",
"org.ow2.asm:asm",
@@ -4526,6 +4636,12 @@
"org.fusesource.jansi:jansi:jar:sources",
"org.gradle:gradle-tooling-api",
"org.gradle:gradle-tooling-api:jar:sources",
+ "org.jetbrains.kotlin:kotlin-metadata-jvm",
+ "org.jetbrains.kotlin:kotlin-metadata-jvm:jar:sources",
+ "org.jetbrains.kotlin:kotlin-stdlib",
+ "org.jetbrains.kotlin:kotlin-stdlib:jar:sources",
+ "org.jetbrains:annotations",
+ "org.jetbrains:annotations:jar:sources",
"org.jspecify:jspecify",
"org.jspecify:jspecify:jar:sources",
"org.ow2.asm:asm",
@@ -4818,6 +4934,16 @@
"org.gradle.internal.logging.slf4j.StaticLoggerProvider"
]
},
+ "org.jetbrains.kotlin:kotlin-metadata-jvm": {
+ "kotlin.metadata.internal.extensions.MetadataExtensions": [
+ "kotlin.metadata.jvm.internal.JvmMetadataExtensions"
+ ]
+ },
+ "org.jetbrains.kotlin:kotlin-metadata-jvm:jar:sources": {
+ "kotlin.metadata.internal.extensions.MetadataExtensions": [
+ "kotlin.metadata.jvm.internal.JvmMetadataExtensions"
+ ]
+ },
"org.slf4j:jcl-over-slf4j": {
"org.apache.commons.logging.LogFactory": [
"org.apache.commons.logging.impl.SLF4JLogFactory"
diff --git a/tests/com/github/bazelbuild/rules_jvm_external/jar/BUILD b/tests/com/github/bazelbuild/rules_jvm_external/jar/BUILD
index a2b1345..947c96d 100644
--- a/tests/com/github/bazelbuild/rules_jvm_external/jar/BUILD
+++ b/tests/com/github/bazelbuild/rules_jvm_external/jar/BUILD
@@ -1,6 +1,13 @@
load("@rules_java//java:java_test.bzl", "java_test")
+load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
load("//:defs.bzl", "artifact")
+# Fixture jar exercising IndexJar's extraction of Kotlin top-level declarations.
+kt_jvm_library(
+ name = "kotlin_top_level_fixture",
+ srcs = ["testdata/TopLevel.kt"],
+)
+
java_test(
name = "AddJarManifestEntryTest",
srcs = ["AddJarManifestEntryTest.java"],
@@ -21,6 +28,7 @@
name = "IndexJarTest",
srcs = ["IndexJarTest.java"],
data = [
+ ":kotlin_top_level_fixture",
"//tests/custom_maven_install:service_indexing_testing.json",
"@google_api_services_compute_javadoc_for_test//file",
"@gson_for_test//file",
diff --git a/tests/com/github/bazelbuild/rules_jvm_external/jar/IndexJarTest.java b/tests/com/github/bazelbuild/rules_jvm_external/jar/IndexJarTest.java
index 3f5bee4..107bdaa 100644
--- a/tests/com/github/bazelbuild/rules_jvm_external/jar/IndexJarTest.java
+++ b/tests/com/github/bazelbuild/rules_jvm_external/jar/IndexJarTest.java
@@ -15,6 +15,8 @@
package com.github.bazelbuild.rules_jvm_external.jar;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import com.google.devtools.build.runfiles.Runfiles;
import com.google.gson.Gson;
@@ -133,6 +135,31 @@
new TreeMap<>());
}
+ @Test
+ public void kotlinTopLevelDeclarations() throws Exception {
+ Path jar =
+ Paths.get(
+ Runfiles.create()
+ .rlocation(
+ "rules_jvm_external/tests/com/github/bazelbuild/rules_jvm_external/jar/kotlin_top_level_fixture.jar"));
+ SortedSet<String> classes = new IndexJar().index(jar).getClasses();
+
+ // The file facade and the regular class are still indexed as classes.
+ assertTrue(classes.toString(), classes.contains("com.example.kotlin.TopLevelKt"));
+ assertTrue(classes.toString(), classes.contains("com.example.kotlin.RegularClass"));
+
+ // Public top-level declarations from the facade are folded in as package-qualified symbols,
+ // using their Kotlin source names.
+ assertTrue(classes.toString(), classes.contains("com.example.kotlin.topLevelFunction"));
+ assertTrue(classes.toString(), classes.contains("com.example.kotlin.topLevelProperty"));
+ assertTrue(classes.toString(), classes.contains("com.example.kotlin.TopLevelAlias"));
+
+ // Non-public top-level declarations cannot be imported from another artifact, so they're
+ // excluded.
+ assertFalse(classes.toString(), classes.contains("com.example.kotlin.internalFunction"));
+ assertFalse(classes.toString(), classes.contains("com.example.kotlin.privateFunction"));
+ }
+
private static class Lockfile {
public TreeMap<String, TreeMap<String, TreeSet<String>>> services;
}
diff --git a/tests/com/github/bazelbuild/rules_jvm_external/jar/testdata/TopLevel.kt b/tests/com/github/bazelbuild/rules_jvm_external/jar/testdata/TopLevel.kt
new file mode 100644
index 0000000..bd35924
--- /dev/null
+++ b/tests/com/github/bazelbuild/rules_jvm_external/jar/testdata/TopLevel.kt
@@ -0,0 +1,32 @@
+// 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.
+
+// A fixture exercising IndexJar's handling of Kotlin top-level declarations. The compiler emits
+// these into a "TopLevelKt" file facade; the function, property, and type alias are otherwise
+// invisible to a class-name-only index.
+package com.example.kotlin
+
+fun topLevelFunction(): String = internalFunction() + privateFunction()
+
+val topLevelProperty: Int = 42
+
+typealias TopLevelAlias = Map<String, Int>
+
+// Non-public top-level declarations: not importable from another artifact, so excluded from the
+// index. (Referenced above so the compiler doesn't flag them as unused.)
+internal fun internalFunction(): String = "internal"
+
+private fun privateFunction(): String = "private"
+
+class RegularClass
diff --git a/tests/custom_maven_install/regression_testing_gradle_index.json b/tests/custom_maven_install/regression_testing_gradle_index.json
index 6c4961a..af7e9c4 100644
--- a/tests/custom_maven_install/regression_testing_gradle_index.json
+++ b/tests/custom_maven_install/regression_testing_gradle_index.json
@@ -156,7 +156,21 @@
"ArraySetKt",
"LongSparseArrayKt",
"LruCacheKt",
- "SparseArrayKt"
+ "SparseArrayKt",
+ "arrayMapOf",
+ "arraySetOf",
+ "contains",
+ "forEach",
+ "getOrDefault",
+ "getOrElse",
+ "isNotEmpty",
+ "keyIterator",
+ "lruCache",
+ "plus",
+ "remove",
+ "set",
+ "size",
+ "valueIterator"
]
}
},
diff --git a/tests/custom_maven_install/regression_testing_gradle_install.json b/tests/custom_maven_install/regression_testing_gradle_install.json
index 985f7ae..8359800 100644
--- a/tests/custom_maven_install/regression_testing_gradle_install.json
+++ b/tests/custom_maven_install/regression_testing_gradle_install.json
@@ -10,77 +10,77 @@
"repositories": 1670674627
},
"__RESOLVED_ARTIFACTS_HASH": {
- "androidx.activity:activity-ktx:aar": -153180512,
- "androidx.activity:activity:aar": 1713938981,
- "androidx.annotation:annotation": 667415047,
+ "androidx.activity:activity-ktx:aar": -1558999484,
+ "androidx.activity:activity:aar": -908258995,
+ "androidx.annotation:annotation": 1555328318,
"androidx.annotation:annotation-experimental:aar": -1941653327,
"androidx.annotation:annotation-jvm": 1383664268,
- "androidx.arch.core:core-common": 709211552,
- "androidx.arch.core:core-runtime:aar": 687545908,
- "androidx.autofill:autofill:aar": 2146174381,
- "androidx.collection:collection": -120143990,
- "androidx.collection:collection-ktx": -1280696106,
- "androidx.compose.animation:animation-core:aar": 708815105,
- "androidx.compose.foundation:foundation-layout": 2013222215,
- "androidx.compose.foundation:foundation-layout-android:aar": 1467161012,
- "androidx.compose.runtime:runtime": 264891575,
- "androidx.compose.runtime:runtime-android:aar": -1060485085,
- "androidx.compose.runtime:runtime-saveable": 1939592521,
- "androidx.compose.runtime:runtime-saveable-android:aar": 341911742,
- "androidx.compose.ui:ui": -349533802,
- "androidx.compose.ui:ui-android:aar": -1873460053,
- "androidx.compose.ui:ui-geometry": 1746339192,
- "androidx.compose.ui:ui-geometry-android:aar": -763304155,
- "androidx.compose.ui:ui-graphics": 1584845106,
- "androidx.compose.ui:ui-graphics-android:aar": 1303130679,
- "androidx.compose.ui:ui-text": 964342214,
- "androidx.compose.ui:ui-text-android:aar": 986989818,
- "androidx.compose.ui:ui-unit": -1166993326,
- "androidx.compose.ui:ui-unit-android:aar": -845501513,
- "androidx.compose.ui:ui-util": 1566211359,
+ "androidx.arch.core:core-common": 1298806327,
+ "androidx.arch.core:core-runtime:aar": 2043517815,
+ "androidx.autofill:autofill:aar": 111068017,
+ "androidx.collection:collection": 1358588301,
+ "androidx.collection:collection-ktx": -1952037448,
+ "androidx.compose.animation:animation-core:aar": -39968942,
+ "androidx.compose.foundation:foundation-layout": 1357449876,
+ "androidx.compose.foundation:foundation-layout-android:aar": 1090775137,
+ "androidx.compose.runtime:runtime": 1028530821,
+ "androidx.compose.runtime:runtime-android:aar": -1452485036,
+ "androidx.compose.runtime:runtime-saveable": -2088887423,
+ "androidx.compose.runtime:runtime-saveable-android:aar": 137089878,
+ "androidx.compose.ui:ui": -1339106231,
+ "androidx.compose.ui:ui-android:aar": 1593834615,
+ "androidx.compose.ui:ui-geometry": 709260272,
+ "androidx.compose.ui:ui-geometry-android:aar": 1443361196,
+ "androidx.compose.ui:ui-graphics": 811518900,
+ "androidx.compose.ui:ui-graphics-android:aar": -171515520,
+ "androidx.compose.ui:ui-text": 29943541,
+ "androidx.compose.ui:ui-text-android:aar": 73768208,
+ "androidx.compose.ui:ui-unit": -1269067791,
+ "androidx.compose.ui:ui-unit-android:aar": 1000778918,
+ "androidx.compose.ui:ui-util": 1764488778,
"androidx.compose.ui:ui-util-android:aar": 932111463,
- "androidx.concurrent:concurrent-futures": -29282510,
- "androidx.core:core-ktx:aar": 195074162,
- "androidx.core:core:aar": -2000523608,
- "androidx.customview:customview-poolingcontainer:aar": -1255248764,
- "androidx.customview:customview:aar": -2016250665,
- "androidx.emoji2:emoji2:aar": 2123960815,
- "androidx.fragment:fragment-ktx:aar": 1459956490,
- "androidx.fragment:fragment:aar": 600855953,
- "androidx.interpolator:interpolator:aar": -599785500,
- "androidx.lifecycle:lifecycle-common": -926737699,
- "androidx.lifecycle:lifecycle-livedata-core-ktx:aar": -71647219,
- "androidx.lifecycle:lifecycle-livedata-core:aar": 989641272,
- "androidx.lifecycle:lifecycle-livedata:aar": 886855673,
- "androidx.lifecycle:lifecycle-process:aar": 279906566,
- "androidx.lifecycle:lifecycle-runtime-ktx:aar": 400772299,
- "androidx.lifecycle:lifecycle-runtime:aar": 1452827583,
- "androidx.lifecycle:lifecycle-viewmodel-ktx:aar": 1349750239,
- "androidx.lifecycle:lifecycle-viewmodel-savedstate:aar": 409123745,
- "androidx.lifecycle:lifecycle-viewmodel:aar": -409558106,
- "androidx.loader:loader:aar": 1505382399,
- "androidx.profileinstaller:profileinstaller:aar": 1515269790,
- "androidx.savedstate:savedstate-ktx:aar": -267648164,
- "androidx.savedstate:savedstate:aar": -482715045,
- "androidx.startup:startup-runtime:aar": -348187889,
- "androidx.tracing:tracing:aar": 787388592,
- "androidx.versionedparcelable:versionedparcelable:aar": -1367393968,
- "androidx.viewpager:viewpager:aar": 965285725,
+ "androidx.concurrent:concurrent-futures": -255701605,
+ "androidx.core:core-ktx:aar": -1725871189,
+ "androidx.core:core:aar": -99152888,
+ "androidx.customview:customview-poolingcontainer:aar": 118145965,
+ "androidx.customview:customview:aar": 255900322,
+ "androidx.emoji2:emoji2:aar": -363462401,
+ "androidx.fragment:fragment-ktx:aar": -1615545703,
+ "androidx.fragment:fragment:aar": 43733579,
+ "androidx.interpolator:interpolator:aar": -625396621,
+ "androidx.lifecycle:lifecycle-common": -1390856972,
+ "androidx.lifecycle:lifecycle-livedata-core-ktx:aar": -1489761206,
+ "androidx.lifecycle:lifecycle-livedata-core:aar": 572647604,
+ "androidx.lifecycle:lifecycle-livedata:aar": -1136867590,
+ "androidx.lifecycle:lifecycle-process:aar": 1116184680,
+ "androidx.lifecycle:lifecycle-runtime-ktx:aar": 1605076094,
+ "androidx.lifecycle:lifecycle-runtime:aar": 492832458,
+ "androidx.lifecycle:lifecycle-viewmodel-ktx:aar": -296400356,
+ "androidx.lifecycle:lifecycle-viewmodel-savedstate:aar": -1924654270,
+ "androidx.lifecycle:lifecycle-viewmodel:aar": 1629550837,
+ "androidx.loader:loader:aar": -372546176,
+ "androidx.profileinstaller:profileinstaller:aar": -213707558,
+ "androidx.savedstate:savedstate-ktx:aar": 147201655,
+ "androidx.savedstate:savedstate:aar": 624301752,
+ "androidx.startup:startup-runtime:aar": 565247007,
+ "androidx.tracing:tracing:aar": -572672729,
+ "androidx.versionedparcelable:versionedparcelable:aar": 1535293911,
+ "androidx.viewpager:viewpager:aar": -1511341228,
"ch.qos.reload4j:reload4j": 1533684795,
"com.almworks.sqlite4java:libsqlite4java-linux-i386:so": -691800296,
"com.almworks.sqlite4java:sqlite4java": 1585971300,
"com.google.guava:listenablefuture": -1728764445,
- "com.squareup.okhttp3:okhttp": -1808606243,
- "com.squareup.okio:okio": 1576753490,
+ "com.squareup.okhttp3:okhttp": 6046369,
+ "com.squareup.okio:okio": 135837552,
"com.squareup.okio:okio-jvm": 2073967287,
"org.jetbrains.kotlin:kotlin-stdlib": -879725260,
"org.jetbrains.kotlin:kotlin-stdlib-common": 393704220,
"org.jetbrains.kotlin:kotlin-stdlib-jdk7": 1242909803,
"org.jetbrains.kotlin:kotlin-stdlib-jdk8": -597817274,
- "org.jetbrains.kotlinx:kotlinx-coroutines-android": -460650939,
- "org.jetbrains.kotlinx:kotlinx-coroutines-bom": -1867070274,
- "org.jetbrains.kotlinx:kotlinx-coroutines-core": 1447302966,
- "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm": -1419896446,
+ "org.jetbrains.kotlinx:kotlinx-coroutines-android": 1935006230,
+ "org.jetbrains.kotlinx:kotlinx-coroutines-bom": -1691209052,
+ "org.jetbrains.kotlinx:kotlinx-coroutines-core": -1650539916,
+ "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm": 1368106659,
"org.jetbrains:annotations": 554168982,
"org.slf4j:slf4j-api": 160121336,
"org.slf4j:slf4j-reload4j": 618181616
@@ -100,7 +100,7 @@
},
"androidx.annotation:annotation": {
"shasums": {
- "jar": "dd882b4c1945ad13121ecdf651eba664a9b226b55db84fa755392de5e10d1e5b"
+ "jar": null
},
"version": "1.6.0"
},
@@ -154,7 +154,7 @@
},
"androidx.compose.foundation:foundation-layout": {
"shasums": {
- "jar": "79af00811d834b966f383fb39454cff06e4a0045e5180eac5394d72fab9a1c97"
+ "jar": null
},
"version": "1.5.0-beta01"
},
@@ -166,7 +166,7 @@
},
"androidx.compose.runtime:runtime": {
"shasums": {
- "jar": "b72dfdbb22399d36dacdfa96710f6c7dbf885123a63dc714c55e3b3836d684bb"
+ "jar": null
},
"version": "1.5.0-beta01"
},
@@ -178,7 +178,7 @@
},
"androidx.compose.runtime:runtime-saveable": {
"shasums": {
- "jar": "41a844c666bd4b9fedd365fedbe3af6bbb01589ea00d36520910471eb568a4eb"
+ "jar": null
},
"version": "1.5.0-beta01"
},
@@ -190,7 +190,7 @@
},
"androidx.compose.ui:ui": {
"shasums": {
- "jar": "4e923a7b886a27d27fbcfa4eae33602ae6a719651371f656d923407046b31704"
+ "jar": null
},
"version": "1.5.0-beta01"
},
@@ -202,7 +202,7 @@
},
"androidx.compose.ui:ui-geometry": {
"shasums": {
- "jar": "2e842e4f603f56b7dca7c792b044da2de6b9f9855adfc2d918dda55b379e46e1"
+ "jar": null
},
"version": "1.5.0-beta01"
},
@@ -214,7 +214,7 @@
},
"androidx.compose.ui:ui-graphics": {
"shasums": {
- "jar": "a72f34b560d1401dd3e9d3334d642e512314deedceebe30b223e015d923d25d3"
+ "jar": null
},
"version": "1.5.0-beta01"
},
@@ -226,7 +226,7 @@
},
"androidx.compose.ui:ui-text": {
"shasums": {
- "jar": "f3abaefaefe83b1bc9b3b3351a0de57904495dbf9c55b872aa5e25f125befb2e"
+ "jar": null
},
"version": "1.5.0-beta01"
},
@@ -238,7 +238,7 @@
},
"androidx.compose.ui:ui-unit": {
"shasums": {
- "jar": "e6ae55913d92a1456a171caf65879ed890b30f2d32143dd8068c296a623017a2"
+ "jar": null
},
"version": "1.5.0-beta01"
},
@@ -250,7 +250,7 @@
},
"androidx.compose.ui:ui-util": {
"shasums": {
- "jar": "00d8f7cc0d5dce370b83e3a14cd468fa444fcb6b91ab0878117a2c3c82e9e0a1"
+ "jar": null
},
"version": "1.5.0-beta01"
},
@@ -454,7 +454,7 @@
},
"com.squareup.okio:okio": {
"shasums": {
- "jar": "aeb3b70a24c103ed0c54541f35f5c511d27ce60cae376a596d7d653697f8cc95"
+ "jar": null
},
"version": "3.6.0"
},
@@ -502,7 +502,7 @@
},
"org.jetbrains.kotlinx:kotlinx-coroutines-core": {
"shasums": {
- "jar": "136b4edb87a47e15251f0842f3189fa31979eb4ab51acfb4239b4bf8d16d4588"
+ "jar": null
},
"version": "1.6.4"
},
@@ -927,15 +927,12 @@
"com.almworks.sqlite4java:sqlite4java",
"com.google.guava:listenablefuture",
"com.squareup.okhttp3:okhttp",
- "com.squareup.okio:okio",
"com.squareup.okio:okio-jvm",
"org.jetbrains.kotlin:kotlin-stdlib",
"org.jetbrains.kotlin:kotlin-stdlib-common",
"org.jetbrains.kotlin:kotlin-stdlib-jdk7",
"org.jetbrains.kotlin:kotlin-stdlib-jdk8",
"org.jetbrains.kotlinx:kotlinx-coroutines-android",
- "org.jetbrains.kotlinx:kotlinx-coroutines-bom",
- "org.jetbrains.kotlinx:kotlinx-coroutines-core",
"org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm",
"org.jetbrains:annotations",
"org.slf4j:slf4j-api",
@@ -944,7 +941,6 @@
"https://maven.google.com/": [
"androidx.activity:activity-ktx:aar",
"androidx.activity:activity:aar",
- "androidx.annotation:annotation",
"androidx.annotation:annotation-experimental:aar",
"androidx.annotation:annotation-jvm",
"androidx.arch.core:core-common",
@@ -953,23 +949,14 @@
"androidx.collection:collection",
"androidx.collection:collection-ktx",
"androidx.compose.animation:animation-core:aar",
- "androidx.compose.foundation:foundation-layout",
"androidx.compose.foundation:foundation-layout-android:aar",
- "androidx.compose.runtime:runtime",
"androidx.compose.runtime:runtime-android:aar",
- "androidx.compose.runtime:runtime-saveable",
"androidx.compose.runtime:runtime-saveable-android:aar",
- "androidx.compose.ui:ui",
"androidx.compose.ui:ui-android:aar",
- "androidx.compose.ui:ui-geometry",
"androidx.compose.ui:ui-geometry-android:aar",
- "androidx.compose.ui:ui-graphics",
"androidx.compose.ui:ui-graphics-android:aar",
- "androidx.compose.ui:ui-text",
"androidx.compose.ui:ui-text-android:aar",
- "androidx.compose.ui:ui-unit",
"androidx.compose.ui:ui-unit-android:aar",
- "androidx.compose.ui:ui-util",
"androidx.compose.ui:ui-util-android:aar",
"androidx.concurrent:concurrent-futures",
"androidx.core:core-ktx:aar",
@@ -1016,7 +1003,19 @@
}
},
"skipped": [
- "org.jetbrains.kotlinx:kotlinx-coroutines-bom"
+ "androidx.annotation:annotation",
+ "androidx.compose.foundation:foundation-layout",
+ "androidx.compose.runtime:runtime",
+ "androidx.compose.runtime:runtime-saveable",
+ "androidx.compose.ui:ui",
+ "androidx.compose.ui:ui-geometry",
+ "androidx.compose.ui:ui-graphics",
+ "androidx.compose.ui:ui-text",
+ "androidx.compose.ui:ui-unit",
+ "androidx.compose.ui:ui-util",
+ "com.squareup.okio:okio",
+ "org.jetbrains.kotlinx:kotlinx-coroutines-bom",
+ "org.jetbrains.kotlinx:kotlinx-coroutines-core"
],
"version": "3"
}