diff --git a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/GraalVMReachabilityMetadataRepository.java b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/GraalVMReachabilityMetadataRepository.java index 692a2a2c8..15c12e33c 100644 --- a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/GraalVMReachabilityMetadataRepository.java +++ b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/GraalVMReachabilityMetadataRepository.java @@ -66,6 +66,18 @@ public interface GraalVMReachabilityMetadataRepository { */ Set findConfigurationsFor(Consumer queryBuilder); + /** + * Returns whether the repository covers the requested artifacts. Some repository + * entries intentionally do not provide configuration files, for example artifacts + * marked as not applicable to native-image. + * + * @param queryBuilder the query builder + * @return true if the repository has configuration files or another coverage marker + */ + default boolean isCoveredByRepository(Consumer queryBuilder) { + return !findConfigurationsFor(queryBuilder).isEmpty(); + } + /** * Returns a list of configuration directories for the specified artifact. * There may be more than one configuration directory for a given artifact, @@ -78,6 +90,16 @@ default Set findConfigurationsFor(String gavCoordinates) return findConfigurationsFor(q -> q.forArtifacts(gavCoordinates)); } + /** + * Returns whether the repository covers the specified artifact. + * + * @param gavCoordinates the artifact GAV coordinates (group:artifact:version) + * @return true if the repository has configuration files or another coverage marker + */ + default boolean isCoveredByRepository(String gavCoordinates) { + return isCoveredByRepository(q -> q.forArtifacts(gavCoordinates)); + } + /** * Returns the set of configuration directories for all the modules supplied * as an argument. diff --git a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/MissingMetadataCommandSupport.java b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/MissingMetadataCommandSupport.java index 64bf82dad..883b63bac 100644 --- a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/MissingMetadataCommandSupport.java +++ b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/MissingMetadataCommandSupport.java @@ -116,7 +116,7 @@ public static Report run(Collection dependencies, List results = new ArrayList<>(candidates.size()); for (DependencyCoordinate dependency : candidates) { try { - Set configurations = repository.findConfigurationsFor(query -> { + boolean covered = repository.isCoveredByRepository(query -> { query.forArtifact(artifact -> { artifact.gav(dependency.coordinates()); String forcedVersion = effectiveForcedVersions.get(dependency.groupAndArtifact()); @@ -127,7 +127,7 @@ public static Report run(Collection dependencies, } }); }); - if (!configurations.isEmpty()) { + if (covered) { results.add(Result.supported(dependency)); continue; } diff --git a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/FileSystemRepository.java b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/FileSystemRepository.java index 85e8963af..8fb69fc62 100644 --- a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/FileSystemRepository.java +++ b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/FileSystemRepository.java @@ -105,7 +105,8 @@ public Set findConfigurationsFor(Consumer return moduleIndex.findConfigurationDirectories(groupId, artifactId) .stream() .map(dir -> { - VersionToConfigDirectoryIndex index = artifactIndexes.computeIfAbsent(dir, SingleModuleJsonVersionToConfigDirectoryIndex::new); + VersionToConfigDirectoryIndex index = artifactIndexes.computeIfAbsent(dir, + SingleModuleJsonVersionToConfigDirectoryIndex::new); if (artifactQuery.getForcedConfig().isPresent()) { String configVersion = artifactQuery.getForcedConfig().get(); logger.log(groupId, artifactId, version, "Configuration is forced to version " + configVersion); @@ -135,6 +136,52 @@ public Set findConfigurationsFor(Consumer .collect(Collectors.toSet()); } + @Override + public boolean isCoveredByRepository(Consumer queryBuilder) { + DefaultQuery query = new DefaultQuery(); + queryBuilder.accept(query); + return query.getArtifacts() + .stream() + .anyMatch(artifactQuery -> { + String groupId = artifactQuery.getGroupId(); + String artifactId = artifactQuery.getArtifactId(); + String version = artifactQuery.getVersion(); + return moduleIndex.findConfigurationDirectories(groupId, artifactId) + .stream() + .anyMatch(dir -> { + VersionToConfigDirectoryIndex index = artifactIndexes.computeIfAbsent(dir, SingleModuleJsonVersionToConfigDirectoryIndex::new); + Optional configuration; + if (artifactQuery.getForcedConfig().isPresent()) { + String configVersion = artifactQuery.getForcedConfig().get(); + logger.log(groupId, artifactId, version, "Configuration is forced to version " + configVersion); + configuration = index.findConfiguration(groupId, artifactId, configVersion); + } else { + configuration = index.findConfiguration(groupId, artifactId, version); + if (!configuration.isPresent() && artifactQuery.isUseLatestVersion()) { + logger.log(groupId, artifactId, version, + "Configuration directory not found. Trying latest version."); + configuration = index.findLatestConfigurationFor(groupId, artifactId, version); + if (!configuration.isPresent()) { + logger.log(groupId, artifactId, version, "Latest version not found!"); + } + } + } + if (configuration.isPresent()) { + Path path = configuration.get().getDirectory(); + logger.log(groupId, artifactId, version, + "Configuration directory is " + rootDirectory.relativize(path)); + return true; + } + if (index.isNotForNativeImage(groupId, artifactId, version)) { + logger.log(groupId, artifactId, version, "Artifact is marked as not for native-image."); + return true; + } + logger.log(groupId, artifactId, version, "missing."); + return false; + }); + }); + } + public Path getRootDirectory() { return rootDirectory; } diff --git a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/Artifact.java b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/Artifact.java index e7f95d670..943938d64 100644 --- a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/Artifact.java +++ b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/Artifact.java @@ -48,14 +48,16 @@ public class Artifact { private final String directory; private final boolean latest; private final boolean override; + private final boolean notForNativeImage; private final Pattern defaultForPattern; public Artifact(Set versions, String directory, - boolean latest, boolean override, String defaultFor) { + boolean latest, boolean override, String defaultFor, boolean notForNativeImage) { this.versions = versions; this.directory = directory; this.latest = latest; this.override = override; + this.notForNativeImage = notForNativeImage; this.defaultForPattern = (defaultFor == null ? null : Pattern.compile(defaultFor)); } @@ -78,4 +80,14 @@ public boolean isOverride() { public boolean isDefaultFor(String version) { return defaultForPattern != null && defaultForPattern.matcher(version).matches(); } + + public boolean isNotForNativeImage(String version) { + if (!notForNativeImage) { + return false; + } + return versions.isEmpty() && defaultForPattern == null + || versions.contains(version) + || isDefaultFor(version) + || latest; + } } diff --git a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndex.java b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndex.java index af1fc5c71..0b44e6028 100644 --- a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndex.java +++ b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndex.java @@ -116,6 +116,11 @@ public Optional findLatestConfigurationFor(String groupI findConfigurationFor(groupId, artifactId, version, Artifact::isLatest); } + @Override + public boolean isNotForNativeImage(String groupId, String artifactId, String version) { + return artifacts.stream().anyMatch(artifact -> artifact.isNotForNativeImage(version)); + } + private Optional findConfigurationFor(String groupId, String artifactId, String version, Predicate predicate) { if (artifacts.isEmpty()) { @@ -134,7 +139,8 @@ private Artifact fromJson(JSONObject json) { boolean latest = json.optBoolean("latest"); boolean override = json.optBoolean("override"); String defaultFor = json.optString("default-for", null); - return new Artifact(testVersions, directory, latest, override, defaultFor); + boolean notForNativeImage = json.optBoolean("not-for-native-image"); + return new Artifact(testVersions, directory, latest, override, defaultFor, notForNativeImage); } private Set readTestedVersions(JSONArray array) { diff --git a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/VersionToConfigDirectoryIndex.java b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/VersionToConfigDirectoryIndex.java index 6f7d96113..3630f7e09 100644 --- a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/VersionToConfigDirectoryIndex.java +++ b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/VersionToConfigDirectoryIndex.java @@ -73,4 +73,16 @@ public interface VersionToConfigDirectoryIndex { * @return a configuration, or empty if no configuration directory is available */ Optional findLatestConfigurationFor(String groupId, String artifactId, String version); + + /** + * Returns whether the artifact is marked as not applicable to native-image. + * + * @param groupId the group ID of the artifact + * @param artifactId the artifact ID of the artifact + * @param version the version of the artifact + * @return true if the artifact is intentionally not covered by configuration files + */ + default boolean isNotForNativeImage(String groupId, String artifactId, String version) { + return false; + } } diff --git a/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/MissingMetadataCommandSupportTest.java b/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/MissingMetadataCommandSupportTest.java index 87a752865..5e3855c33 100644 --- a/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/MissingMetadataCommandSupportTest.java +++ b/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/MissingMetadataCommandSupportTest.java @@ -44,6 +44,7 @@ import com.github.openjson.JSONObject; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; +import org.graalvm.reachability.internal.FileSystemRepository; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -139,6 +140,36 @@ void reportsSupportedAndMissingDependenciesAndGeneratesPrefilledLinks() { "console should list the prefilled issue URL as a numbered footnote, was:\n" + console); } + @Test + void treatsNotForNativeImageIndexEntriesAsSupported() throws Exception { + Path repoPath = Path.of(MissingMetadataCommandSupportTest.class + .getResource("/repos/repo-not-for-native-image") + .toURI()); + MissingMetadataCommandSupport.Report report = MissingMetadataCommandSupport.run( + List.of(new MissingMetadataCommandSupport.DependencyCoordinate("com.foo", "native-only", "1.0")), + new FileSystemRepository(repoPath), + Set.of(), + java.util.Map.of(), + new MissingMetadataCommandSupport.Options( + "maven", + "demo-app", + repoPath.toUri().toString(), + false, + null, + MissingMetadataCommandSupport.DEFAULT_TARGET_REPOSITORY, + "http://127.0.0.1:9/api/v3", + Clock.fixed(Instant.parse("2026-04-09T10:00:00Z"), ZoneOffset.UTC) + ) + ); + + JSONObject json = new JSONObject(report.toJsonString()); + JSONObject summary = json.getJSONObject("summary"); + JSONObject result = json.getJSONArray("results").getJSONObject(0); + assertEquals(1, summary.getInt("supported")); + assertEquals(0, summary.getInt("missing")); + assertEquals("supported", result.getString("status")); + } + @Test void reusesExistingOpenIssueAndSuppressesDuplicateSearchesByGroupAndArtifact() throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress(0), 0); diff --git a/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/internal/FileSystemRepositoryTest.java b/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/internal/FileSystemRepositoryTest.java index 06ecd3c28..968f82c64 100644 --- a/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/internal/FileSystemRepositoryTest.java +++ b/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/internal/FileSystemRepositoryTest.java @@ -171,6 +171,17 @@ void canUseLatestConfigDir() { result.isEmpty(); } + @Test + void notForNativeImageIndexCoversArtifactWithoutConfigurationFiles() { + // when: + withRepo("repo-not-for-native-image"); + + // then: + assertTrue(repository.isCoveredByRepository("com.foo:native-only:1.0")); + lookup("com.foo:native-only:1.0"); + result.isEmpty(); + } + private void lookup(Consumer builder) { result = new Result(repository.findConfigurationsFor(builder), repoPath); } diff --git a/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndexTest.java b/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndexTest.java index 93830d8af..8cbddfa6c 100644 --- a/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndexTest.java +++ b/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndexTest.java @@ -140,6 +140,15 @@ void checkIndexWithDefaultFor() throws URISyntaxException { assertEquals(repoPath.resolve("2.0"), latest.get().getDirectory()); } + @Test + void checkNotForNativeImageIndex() throws URISyntaxException { + withIndex("artifact-not-for-native-image/com.foo/native-only"); + + Optional config = index.findConfiguration("com.foo", "native-only", "1.0"); + assertFalse(config.isPresent()); + assertTrue(index.isNotForNativeImage("com.foo", "native-only", "1.0")); + } + private void withIndex(String json) throws URISyntaxException { repoPath = new File(SingleModuleJsonVersionToConfigDirectoryIndexTest.class.getResource("/json/" + json).toURI()).toPath(); index = new SingleModuleJsonVersionToConfigDirectoryIndex(repoPath); diff --git a/common/graalvm-reachability-metadata/src/test/resources/json/artifact-not-for-native-image/com.foo/native-only/index.json b/common/graalvm-reachability-metadata/src/test/resources/json/artifact-not-for-native-image/com.foo/native-only/index.json new file mode 100644 index 000000000..99ffa06f4 --- /dev/null +++ b/common/graalvm-reachability-metadata/src/test/resources/json/artifact-not-for-native-image/com.foo/native-only/index.json @@ -0,0 +1,7 @@ +[ + { + "not-for-native-image": true, + "reason": "This artifact is not applicable to native-image.", + "replacement": "com.foo:native-classes:1.0" + } +] diff --git a/common/graalvm-reachability-metadata/src/test/resources/repos/repo-not-for-native-image/com.foo/native-only/index.json b/common/graalvm-reachability-metadata/src/test/resources/repos/repo-not-for-native-image/com.foo/native-only/index.json new file mode 100644 index 000000000..99ffa06f4 --- /dev/null +++ b/common/graalvm-reachability-metadata/src/test/resources/repos/repo-not-for-native-image/com.foo/native-only/index.json @@ -0,0 +1,7 @@ +[ + { + "not-for-native-image": true, + "reason": "This artifact is not applicable to native-image.", + "replacement": "com.foo:native-classes:1.0" + } +] diff --git a/common/graalvm-reachability-metadata/src/test/resources/repos/repo-not-for-native-image/schemas/library-and-framework-list-schema-v1.0.0.json b/common/graalvm-reachability-metadata/src/test/resources/repos/repo-not-for-native-image/schemas/library-and-framework-list-schema-v1.0.0.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/common/graalvm-reachability-metadata/src/test/resources/repos/repo-not-for-native-image/schemas/library-and-framework-list-schema-v1.0.0.json @@ -0,0 +1 @@ +{} diff --git a/common/graalvm-reachability-metadata/src/test/resources/repos/repo-not-for-native-image/schemas/metadata-library-index-schema-v2.0.0.json b/common/graalvm-reachability-metadata/src/test/resources/repos/repo-not-for-native-image/schemas/metadata-library-index-schema-v2.0.0.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/common/graalvm-reachability-metadata/src/test/resources/repos/repo-not-for-native-image/schemas/metadata-library-index-schema-v2.0.0.json @@ -0,0 +1 @@ +{} diff --git a/common/graalvm-reachability-metadata/src/test/resources/repos/repo-not-for-native-image/schemas/reachability-metadata-schema-v1.2.0.json b/common/graalvm-reachability-metadata/src/test/resources/repos/repo-not-for-native-image/schemas/reachability-metadata-schema-v1.2.0.json new file mode 100644 index 000000000..935b2491e --- /dev/null +++ b/common/graalvm-reachability-metadata/src/test/resources/repos/repo-not-for-native-image/schemas/reachability-metadata-schema-v1.2.0.json @@ -0,0 +1 @@ +{"version":"1.2.0"} diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java index 07e9518fd..74207729f 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java @@ -195,6 +195,11 @@ public Set findConfigurationsFor(Consumer return repository.findConfigurationsFor(queryBuilder); } + @Override + public boolean isCoveredByRepository(Consumer queryBuilder) { + return repository.isCoveredByRepository(queryBuilder); + } + /** * Returns a list of configuration directories for the specified artifact. * There may be more than one configuration directory for a given artifact,