Skip to content

Commit b6755e8

Browse files
committed
GH-853: Fix minimum version for Sisu, add missing converters
The version of Sisu used before Maven 3.9.6 is incompatible with Java 17 bytecode, so we must force a minimum version of 3.9.6. It also appears that prior to 3.9.8, the Path and URI plexus converters are missing from Sisu, so reintroduce those.
1 parent ce07960 commit b6755e8

File tree

7 files changed

+452
-1
lines changed

7 files changed

+452
-1
lines changed

.github/workflows/build.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ jobs:
6969
fail-fast: false
7070
matrix:
7171
include:
72+
- title: lnx/x86/mvn3.9.6/jdk17
73+
os-name: ubuntu-24.04
74+
java-version: 17
75+
maven-version: 3.9.6 # Minimun version
76+
7277
# Note: this runner is expensive!
7378
- title: osx/arm/mvn${{ needs.validate.outputs.default_maven_version }}/jdk17
7479
os-name: macos-15

protobuf-maven-plugin/pom.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
<packaging>maven-plugin</packaging>
3232

3333
<prerequisites>
34-
<maven>3.9</maven>
34+
<!-- Versions before 3.9.6 cannot handle Java 17 bytecode in Eclipse Sisu. -->
35+
<maven>3.9.6</maven>
3536
</prerequisites>
3637

3738
<dependencies>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright (C) 2023 - 2025, Ashley Scopes.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.ascopes.protobufmavenplugin.fs;
17+
18+
import java.io.File;
19+
import java.nio.file.Path;
20+
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
21+
import org.codehaus.plexus.component.configurator.ConfigurationListener;
22+
import org.codehaus.plexus.component.configurator.converters.basic.FileConverter;
23+
import org.codehaus.plexus.component.configurator.converters.lookup.ConverterLookup;
24+
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
25+
import org.codehaus.plexus.configuration.PlexusConfiguration;
26+
import org.jspecify.annotations.Nullable;
27+
28+
29+
/**
30+
* Plexus/Sisu parameter converter for Path objects on the root file system.
31+
*
32+
* <p>We provide this to avoid using the URL and File APIs in the Mojo interface.
33+
*
34+
* <p>Newer versions of Plexus/Sisu provide this for us, so in the future, we can remove these
35+
* components (looks to be supported from Maven 3.9.8).
36+
*
37+
* @author Ashley Scopes
38+
* @since 3.1.3
39+
*/
40+
public final class PathPlexusConverter extends FileConverter {
41+
42+
@Override
43+
public boolean canConvert(Class<?> type) {
44+
return Path.class.equals(type);
45+
}
46+
47+
@Override
48+
public Object fromConfiguration(
49+
ConverterLookup lookup,
50+
PlexusConfiguration configuration,
51+
Class<?> type,
52+
@Nullable Class<?> enclosingType,
53+
@Nullable ClassLoader loader,
54+
ExpressionEvaluator evaluator,
55+
@Nullable ConfigurationListener listener
56+
) throws ComponentConfigurationException {
57+
// GH-689: we need to consider paths as relative to the Maven project directory rather
58+
// than relative to the current working directory. This is important when running nested
59+
// Maven modules that are expected to assume the submodule directory rather than the launch
60+
// site directory.
61+
//
62+
// For now, we do the same thing that Sisu is doing in Maven 3.9.x to retain backwards and
63+
// forwards compatibility. This handles any other niche cases we might miss as well.
64+
// See https://github.com/eclipse-sisu/sisu-project/blob/e86e5005ff03b57aab8c7eb7f54f41e85914e2dd/org.eclipse.sisu.plexus/src/main/java/org/codehaus/plexus/component/configurator/converters/basic/PathConverter.java#L33
65+
66+
var result = super.fromConfiguration(
67+
lookup, configuration, type, enclosingType, loader, evaluator, listener
68+
);
69+
70+
return result instanceof File
71+
? ((File) result).toPath()
72+
: result;
73+
}
74+
}

protobuf-maven-plugin/src/main/java/io/github/ascopes/protobufmavenplugin/mojo/ProtobufMavenPluginConfigurator.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package io.github.ascopes.protobufmavenplugin.mojo;
1717

1818
import io.github.ascopes.protobufmavenplugin.digests.DigestPlexusConverter;
19+
import io.github.ascopes.protobufmavenplugin.fs.PathPlexusConverter;
20+
import io.github.ascopes.protobufmavenplugin.urls.UriPlexusConverter;
1921
import javax.inject.Named;
2022
import javax.inject.Singleton;
2123
import org.codehaus.plexus.component.configurator.BasicComponentConfigurator;
@@ -37,5 +39,7 @@ public class ProtobufMavenPluginConfigurator extends BasicComponentConfigurator
3739

3840
ProtobufMavenPluginConfigurator() {
3941
converterLookup.registerConverter(new DigestPlexusConverter());
42+
converterLookup.registerConverter(new PathPlexusConverter());
43+
converterLookup.registerConverter(new UriPlexusConverter());
4044
}
4145
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright (C) 2023 - 2025, Ashley Scopes.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.ascopes.protobufmavenplugin.urls;
17+
18+
import java.net.URI;
19+
import java.net.URISyntaxException;
20+
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
21+
import org.codehaus.plexus.component.configurator.converters.basic.AbstractBasicConverter;
22+
23+
24+
/**
25+
* Plexus/Sisu parameter converter for URIs.
26+
*
27+
* <p>We provide this to avoid using the URL and File APIs in the Mojo interface. URLs do an
28+
* immediate lookup for the URL scheme's appropriate URLStreamHandlerProvider upon construction, and
29+
* we have no control over how that works. We have no ability to inject custom URL handlers at this
30+
* point because the URL class is hardcoded to only consider the system classloader. Since Maven
31+
* uses ClassWorlds to run multiple classloaders for each plugin and component, we will not be
32+
* loaded as part of that default classloader. By deferring this operation to as late as possible
33+
* (i.e. in {@link UriResourceFetcher}), we can
34+
* ensure we provide the desired URL handler directly instead. This allows us to hook custom URL
35+
* handlers in via {@link java.util.ServiceLoader} dynamically, like we would be able to outside a
36+
* Maven plugin running in Plexus.
37+
*
38+
* <p>Newer versions of Plexus/Sisu provide this for us, so in the future, we can remove these
39+
* components (looks to be supported from Maven 3.9.8).
40+
*
41+
* @author Ashley Scopes
42+
* @since 3.1.3
43+
*/
44+
public final class UriPlexusConverter extends AbstractBasicConverter {
45+
46+
@Override
47+
public boolean canConvert(Class<?> type) {
48+
return URI.class.equals(type);
49+
}
50+
51+
@Override
52+
protected Object fromString(String str) throws ComponentConfigurationException {
53+
try {
54+
return new URI(str);
55+
} catch (URISyntaxException ex) {
56+
// GH-689: align with the same error format as Sisu provides in Maven 3.9.x for
57+
// forwards compatibility.
58+
throw new ComponentConfigurationException("Cannot convert '" + str + "' to URI", ex);
59+
}
60+
}
61+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright (C) 2023 - 2025, Ashley Scopes.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.ascopes.protobufmavenplugin.fs;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import java.io.File;
21+
import java.nio.file.Path;
22+
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
23+
import org.codehaus.plexus.component.configurator.converters.lookup.DefaultConverterLookup;
24+
import org.codehaus.plexus.component.configurator.expression.DefaultExpressionEvaluator;
25+
import org.codehaus.plexus.configuration.DefaultPlexusConfiguration;
26+
import org.junit.jupiter.api.BeforeEach;
27+
import org.junit.jupiter.api.DisplayName;
28+
import org.junit.jupiter.api.Test;
29+
import org.junit.jupiter.api.io.TempDir;
30+
import org.junit.jupiter.params.ParameterizedTest;
31+
import org.junit.jupiter.params.provider.CsvSource;
32+
33+
@DisplayName("PathPlexusConverter tests")
34+
class PathPlexusConverterTest {
35+
36+
PathPlexusConverter converter;
37+
38+
@BeforeEach
39+
void setUp() {
40+
converter = new PathPlexusConverter();
41+
}
42+
43+
@DisplayName("only the expected types are convertible")
44+
@CsvSource({
45+
" java.nio.file.Path, true",
46+
" java.net.URI, false",
47+
" java.lang.Object, false",
48+
" java.lang.Integer, false",
49+
" java.lang.Class, false",
50+
" java.lang.String, false",
51+
"java.lang.StringBuilder, false",
52+
" java.io.File, false",
53+
" java.net.URL, false",
54+
})
55+
@ParameterizedTest(name = "for {0}, expect {1}")
56+
void onlyTheExpectedTypesAreConvertible(Class<?> type, boolean expectedResult) {
57+
// Then
58+
assertThat(converter.canConvert(type))
59+
.isEqualTo(expectedResult);
60+
}
61+
62+
@DisplayName("Relative paths can be parsed successfully")
63+
@Test
64+
void relativePathsCanBeParsedSuccessfully(
65+
@TempDir Path baseDir
66+
) throws ComponentConfigurationException {
67+
// Given
68+
var expectedAbsolutePath = baseDir.resolve("foo").resolve("bar").resolve("baz.txt")
69+
.toAbsolutePath();
70+
var expectedPath = baseDir
71+
.relativize(expectedAbsolutePath);
72+
var converterLookup = new DefaultConverterLookup();
73+
var configuration = new DefaultPlexusConfiguration("path", expectedPath.toString());
74+
var evaluator = new SomeDirectoryRelativeExpressionEvaluator(baseDir);
75+
76+
// When
77+
var result = converter.fromConfiguration(
78+
converterLookup,
79+
configuration,
80+
Path.class,
81+
null,
82+
getClass().getClassLoader(),
83+
evaluator,
84+
null
85+
);
86+
87+
// Then
88+
assertThat(result).isInstanceOf(Path.class);
89+
var resultPath = (Path) result;
90+
91+
assertThat(resultPath.toAbsolutePath())
92+
.hasToString("%s", expectedAbsolutePath);
93+
}
94+
95+
@DisplayName("Absolute paths can be parsed successfully")
96+
@Test
97+
void absolutePathsCanBeParsedSuccessfully(
98+
@TempDir Path baseDir
99+
) throws ComponentConfigurationException {
100+
// Given
101+
var expectedPath = baseDir.resolve("foo").resolve("bar").resolve("baz.txt")
102+
.toAbsolutePath();
103+
var converterLookup = new DefaultConverterLookup();
104+
var configuration = new DefaultPlexusConfiguration("path", expectedPath.toString());
105+
var evaluator = new SomeDirectoryRelativeExpressionEvaluator(baseDir);
106+
107+
// When
108+
var result = converter.fromConfiguration(
109+
converterLookup,
110+
configuration,
111+
Path.class,
112+
null,
113+
getClass().getClassLoader(),
114+
evaluator,
115+
null
116+
);
117+
118+
// Then
119+
assertThat(result).isInstanceOf(Path.class);
120+
var resultPath = (Path) result;
121+
122+
assertThat(resultPath.toAbsolutePath())
123+
.hasToString("%s", expectedPath.toAbsolutePath());
124+
}
125+
126+
@DisplayName("Null values are returned directly")
127+
@Test
128+
void nullValuesAreReturnedDirectly(
129+
@TempDir Path baseDir
130+
) throws ComponentConfigurationException {
131+
// Given
132+
var converterLookup = new DefaultConverterLookup();
133+
var configuration = new DefaultPlexusConfiguration("path", null);
134+
var evaluator = new SomeDirectoryRelativeExpressionEvaluator(baseDir);
135+
136+
// When
137+
var result = converter.fromConfiguration(
138+
converterLookup,
139+
configuration,
140+
Path.class,
141+
null,
142+
getClass().getClassLoader(),
143+
evaluator,
144+
null
145+
);
146+
147+
// Then
148+
assertThat(result).isNull();
149+
}
150+
151+
// Roughly equivalent to what Maven does, for the sake of this test.
152+
static final class SomeDirectoryRelativeExpressionEvaluator extends DefaultExpressionEvaluator {
153+
154+
private final Path baseDir;
155+
156+
SomeDirectoryRelativeExpressionEvaluator(Path baseDir) {
157+
this.baseDir = baseDir.toAbsolutePath();
158+
}
159+
160+
@Override
161+
public File alignToBaseDirectory(File file) {
162+
if (file.isAbsolute()) {
163+
return file;
164+
}
165+
166+
var path = file.toPath();
167+
var fullPath = baseDir;
168+
for (var part : path) {
169+
fullPath = fullPath.resolve(part);
170+
}
171+
return fullPath.toFile();
172+
}
173+
}
174+
}

0 commit comments

Comments
 (0)