Skip to content

Commit 086cfdf

Browse files
Add fallback for jarless artifacts in the native-maven-plugin (#824)
Add fallback for jarless artifacts Co-authored-by: linghengqian <linghengqian@outlook.com>
1 parent f8eb50d commit 086cfdf

7 files changed

Lines changed: 306 additions & 14 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved.
4+
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5+
6+
The Universal Permissive License (UPL), Version 1.0
7+
-->
8+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
9+
http://maven.apache.org/xsd/maven-4.0.0.xsd">
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<parent>
13+
<groupId>org.graalvm.buildtools.examples</groupId>
14+
<artifactId>issue-727</artifactId>
15+
<version>1.0.0-SNAPSHOT</version>
16+
</parent>
17+
18+
<artifactId>main-module</artifactId>
19+
20+
<dependencies>
21+
<dependency>
22+
<groupId>org.junit.jupiter</groupId>
23+
<artifactId>junit-jupiter</artifactId>
24+
<version>${junit.jupiter.version}</version>
25+
<scope>test</scope>
26+
</dependency>
27+
<dependency>
28+
<groupId>org.graalvm.buildtools.examples</groupId>
29+
<artifactId>module-without-source</artifactId>
30+
<version>${project.version}</version>
31+
<scope>test</scope>
32+
</dependency>
33+
</dependencies>
34+
35+
<profiles>
36+
<profile>
37+
<id>native</id>
38+
<build>
39+
<plugins>
40+
<plugin>
41+
<groupId>org.apache.maven.plugins</groupId>
42+
<artifactId>maven-surefire-plugin</artifactId>
43+
<version>3.0.0-M5</version>
44+
</plugin>
45+
<plugin>
46+
<groupId>org.graalvm.buildtools</groupId>
47+
<artifactId>native-maven-plugin</artifactId>
48+
<version>${native.maven.plugin.version}</version>
49+
<extensions>true</extensions>
50+
<executions>
51+
<execution>
52+
<id>test-native</id>
53+
<goals>
54+
<goal>test</goal>
55+
</goals>
56+
<phase>test</phase>
57+
</execution>
58+
<execution>
59+
<id>build-native</id>
60+
<goals>
61+
<goal>compile-no-fork</goal>
62+
</goals>
63+
<phase>package</phase>
64+
</execution>
65+
</executions>
66+
<configuration>
67+
<skip>false</skip>
68+
<fallback>false</fallback>
69+
</configuration>
70+
</plugin>
71+
</plugins>
72+
</build>
73+
</profile>
74+
</profiles>
75+
</project>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright (c) 2026, 2026, Oracle and/or its affiliates.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*/
7+
package org.graalvm.buildtools.examples;
8+
9+
import org.junit.jupiter.api.Test;
10+
11+
import static org.junit.jupiter.api.Assertions.assertTrue;
12+
13+
class SimpleTest {
14+
@Test
15+
void succeeds() {
16+
assertTrue(true);
17+
}
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved.
4+
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5+
6+
The Universal Permissive License (UPL), Version 1.0
7+
-->
8+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
9+
http://maven.apache.org/xsd/maven-4.0.0.xsd">
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<parent>
13+
<groupId>org.graalvm.buildtools.examples</groupId>
14+
<artifactId>issue-727</artifactId>
15+
<version>1.0.0-SNAPSHOT</version>
16+
</parent>
17+
18+
<artifactId>module-without-source</artifactId>
19+
<packaging>jar</packaging>
20+
</project>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved.
4+
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5+
6+
The Universal Permissive License (UPL), Version 1.0
7+
8+
Subject to the condition set forth below, permission is hereby granted to any
9+
person obtaining a copy of this software, associated documentation and/or
10+
data (collectively the "Software"), free of charge and under any and all
11+
copyright rights in the Software, and any and all patent rights owned or
12+
freely licensable by each licensor hereunder covering either (i) the
13+
unmodified Software as contributed to or provided by such licensor, or (ii)
14+
the Larger Works (as defined below), to deal in both
15+
16+
(a) the Software, and
17+
18+
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
19+
one is included with the Software each a "Larger Work" to which the Software
20+
is contributed by such licensors),
21+
22+
without restriction, including without limitation the rights to copy, create
23+
derivative works of, display, perform, and distribute the Software and make,
24+
use, sell, offer for sale, import, export, have made, and have sold the
25+
Software and the Larger Work(s), and to sublicense the foregoing rights on
26+
either these or other terms.
27+
28+
This license is subject to the following condition:
29+
30+
The above copyright notice and either this complete permission notice or at a
31+
minimum a reference to the UPL must be included in all copies or substantial
32+
portions of the Software.
33+
34+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
39+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
40+
SOFTWARE.
41+
-->
42+
43+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
44+
http://maven.apache.org/xsd/maven-4.0.0.xsd">
45+
<modelVersion>4.0.0</modelVersion>
46+
47+
<groupId>org.graalvm.buildtools.examples</groupId>
48+
<artifactId>issue-727</artifactId>
49+
<version>1.0.0-SNAPSHOT</version>
50+
<packaging>pom</packaging>
51+
52+
<modules>
53+
<module>module-without-source</module>
54+
<module>main-module</module>
55+
</modules>
56+
57+
<properties>
58+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
59+
<java.version>1.8</java.version>
60+
<junit.jupiter.version>5.13.0</junit.jupiter.version>
61+
<native.maven.plugin.version>0.11.4-SNAPSHOT</native.maven.plugin.version>
62+
<junit.platform.native.version>0.11.4-SNAPSHOT</junit.platform.native.version>
63+
</properties>
64+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (c) 2026, 2026, Oracle and/or its affiliates.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
42+
package org.graalvm.buildtools.maven.issues
43+
44+
import org.graalvm.buildtools.maven.AbstractGraalVMMavenFunctionalTest
45+
import spock.lang.Issue
46+
47+
class ModuleWithoutSourcesFunctionalTest extends AbstractGraalVMMavenFunctionalTest {
48+
@Issue("https://github.com/graalvm/native-build-tools/issues/727")
49+
def "native tests handle reactor modules without sources or packaged jars"() {
50+
withReproducer("issue-727")
51+
52+
when:
53+
mvn '-DquickBuild', '-Pnative', 'test'
54+
55+
then:
56+
buildSucceeded
57+
outputContains """
58+
[ 2 containers found ]
59+
[ 0 containers skipped ]
60+
[ 2 containers started ]
61+
[ 0 containers aborted ]
62+
[ 2 containers successful ]
63+
[ 0 containers failed ]
64+
[ 1 tests found ]
65+
[ 0 tests skipped ]
66+
[ 1 tests started ]
67+
[ 0 tests aborted ]
68+
[ 1 tests successful ]
69+
[ 0 tests failed ]
70+
""".trim()
71+
}
72+
}

native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.apache.maven.plugin.descriptor.PluginDescriptor;
5050
import org.apache.maven.plugins.annotations.Component;
5151
import org.apache.maven.plugins.annotations.Parameter;
52+
import org.apache.maven.project.MavenProject;
5253
import org.apache.maven.toolchain.ToolchainManager;
5354
import org.codehaus.plexus.logging.Logger;
5455
import org.graalvm.buildtools.maven.config.ExcludeConfigConfiguration;
@@ -76,6 +77,7 @@
7677
import java.util.HashSet;
7778
import java.util.List;
7879
import java.util.Map;
80+
import java.util.Objects;
7981
import java.util.Optional;
8082
import java.util.Set;
8183
import java.util.function.Consumer;
@@ -306,16 +308,26 @@ protected Path processArtifact(Artifact artifact, String... artifactTypes) throw
306308

307309
if (artifactFile == null) {
308310
logger.debug("Missing artifact file for artifact " + artifact + " (type: " + artifact.getType() + ")");
309-
return null;
311+
return resolveClassesDirectoryFromReactor(artifact).filter(Files::exists).orElse(null);
310312
}
311313

312314
if (Arrays.stream(artifactTypes).noneMatch(a -> a.equals(artifact.getType()))) {
313315
logger.warn("Ignoring ImageClasspath Entry '" + artifact + "' with unsupported type '" + artifact.getType() + "'");
314316
return null;
315317
}
318+
316319
if (!artifactFile.exists()) {
317-
throw new MojoExecutionException("Missing jar-file for " + artifact + ". " +
318-
"Ensure that " + plugin.getArtifactId() + " runs in package phase.");
320+
Optional<Path> reactorOutput = resolveClassesDirectoryFromReactor(artifact);
321+
if (reactorOutput.isPresent()) {
322+
Path reactorPath = reactorOutput.get();
323+
if (Files.exists(reactorPath)) {
324+
logger.debug("ImageClasspath Entry: " + artifact + " (" + reactorPath.toUri() + ")");
325+
return reactorPath;
326+
} else {
327+
logger.debug("Skipping artifact " + artifact + " because no packaged file or classes directory was found.");
328+
return null;
329+
}
330+
}
319331
}
320332

321333
Path jarFilePath = artifactFile.toPath();
@@ -325,6 +337,30 @@ protected Path processArtifact(Artifact artifact, String... artifactTypes) throw
325337
return jarFilePath;
326338
}
327339

340+
/**
341+
* Resolves the physical output directory (classes or test-classes) for a given artifact
342+
* by searching the current Maven reactor.
343+
* <p>
344+
* This is used as a fallback when an artifact's JAR file has not yet been created,
345+
* allowing the plugin to use compiled class folders instead.
346+
*
347+
* @param artifact The dependency artifact to resolve within the reactor.
348+
* @return An Optional containing the {@link Path} to the output directory if found,
349+
* otherwise an empty Optional.
350+
*/
351+
private Optional<Path> resolveClassesDirectoryFromReactor(Artifact artifact) {
352+
return Optional.ofNullable(session)
353+
.flatMap(s -> s.getAllProjects().stream()
354+
.filter(project -> Objects.equals(project.getGroupId(), artifact.getGroupId())
355+
&& Objects.equals(project.getArtifactId(), artifact.getArtifactId())
356+
&& Objects.equals(project.getVersion(), artifact.getVersion()))
357+
.map(MavenProject::getBuild)
358+
.map(build -> "test-jar".equals(artifact.getType()) ? build.getTestOutputDirectory() : build.getOutputDirectory())
359+
.filter(dir -> dir != null && !dir.isEmpty())
360+
.map(Paths::get)
361+
.findFirst());
362+
}
363+
328364
protected void addArtifactToClasspath(Artifact artifact) throws MojoExecutionException {
329365
if (!isExcluded(artifact)) {
330366
Optional.ofNullable(processSupportedArtifacts(artifact)).ifPresent(imageClasspath::add);
@@ -378,13 +414,18 @@ protected void addDependenciesToClasspath() throws MojoExecutionException {
378414
Set<Artifact> collected = new HashSet<>();
379415
// Must keep classpath order is the same with surefire test
380416
for (Artifact dependency : project.getArtifacts()) {
381-
if (getDependencyScopes().contains(dependency.getScope()) && collected.add(dependency)) {
382-
addArtifactToClasspath(dependency);
383-
maybeAddDependencyMetadata(dependency, file -> {
384-
buildArgs.add("--exclude-config");
385-
buildArgs.add(Pattern.quote(dependency.getFile().getAbsolutePath()));
386-
buildArgs.add("^/META-INF/native-image/");
387-
});
417+
if (getDependencyScopes().contains(dependency.getScope()) && collected.add(dependency) && !isExcluded(dependency)) {
418+
Path dependencyPath = processSupportedArtifacts(dependency);
419+
if (dependencyPath != null) {
420+
imageClasspath.add(dependencyPath);
421+
if (dependency.getFile() != null) {
422+
maybeAddDependencyMetadata(dependency, file -> {
423+
buildArgs.add("--exclude-config");
424+
buildArgs.add(Pattern.quote(dependency.getFile().getAbsolutePath()));
425+
buildArgs.add("^/META-INF/native-image/");
426+
});
427+
}
428+
}
388429
}
389430
}
390431
}

native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -376,10 +376,12 @@ private void addMissingDependencies(CollectRequest collectRequest) {
376376
}
377377
});
378378
var currentClasspath = current.getArtifactResults().stream()
379-
.map(result -> new JUnitPlatformNativeDependenciesHelper.DependencyNotation(
380-
result.getArtifact().getGroupId(),
381-
result.getArtifact().getArtifactId(),
382-
result.getArtifact().getVersion()
379+
.map(ArtifactResult::getArtifact)
380+
.filter(Objects::nonNull)
381+
.map(artifact -> new JUnitPlatformNativeDependenciesHelper.DependencyNotation(
382+
artifact.getGroupId(),
383+
artifact.getArtifactId(),
384+
artifact.getVersion()
383385
)).toList();
384386
var deps = JUnitPlatformNativeDependenciesHelper.inferMissingDependenciesForTestRuntime(currentClasspath);
385387
for (var missing : deps) {

0 commit comments

Comments
 (0)