Skip to content

Commit 9c786af

Browse files
manusaclaude
andauthored
fix(httpclient): add explicit Automatic-Module-Name to all httpclient modules
The auto-derived JPMS module names from JAR filenames don't match the actual Java package names and cause a collision between httpclient-vertx and httpclient-vertx-5 (both derive "kubernetes.httpclient.vertx"). Add a file-activated profile in the parent POM that sets the module name from a jpms.module.name property, and add a test module to verify the manifest entries and JPMS module resolution. Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Marc Nuri <[email protected]>
1 parent c0d6691 commit 9c786af

10 files changed

Lines changed: 266 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
### 7.7-SNAPSHOT
44

55
#### Bugs
6-
* Fix #7460: Add explicit Automatic-Module-Name to httpclient modules to fix invalid JPMS module name for kubernetes-httpclient-vertx-5
6+
* Fix #7460: Add explicit Automatic-Module-Name to all httpclient modules to fix invalid auto-derived JPMS module names and vertx/vertx-5 collision
77

88
#### Improvements
99

httpclient-jdk/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<name>Fabric8 :: Kubernetes :: HttpClient :: JDK</name>
3030

3131
<properties>
32+
<jpms.module.name>io.fabric8.kubernetes.client.jdkhttp</jpms.module.name>
3233
<osgi.require-capability>
3334
osgi.extender;
3435
filter:="(osgi.extender=osgi.serviceloader.registrar)",

httpclient-jetty/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<name>Fabric8 :: Kubernetes :: HttpClient :: Jetty</name>
3030

3131
<properties>
32+
<jpms.module.name>io.fabric8.kubernetes.client.jetty</jpms.module.name>
3233
<osgi.require-capability>
3334
osgi.extender;
3435
filter:="(osgi.extender=osgi.serviceloader.registrar)",

httpclient-okhttp/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<name>Fabric8 :: Kubernetes :: HttpClient :: OkHttp</name>
3030

3131
<properties>
32+
<jpms.module.name>io.fabric8.kubernetes.client.okhttp</jpms.module.name>
3233
<osgi.require-capability>
3334
osgi.extender;
3435
filter:="(osgi.extender=osgi.serviceloader.registrar)",

httpclient-vertx-5/pom.xml

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<name>Fabric8 :: Kubernetes :: HttpClient :: Vert.x 5</name>
3030

3131
<properties>
32+
<jpms.module.name>io.fabric8.kubernetes.client.vertx5</jpms.module.name>
3233
<vertx.version>${vertx5.version}</vertx.version>
3334
<osgi.require-capability>
3435
osgi.extender;
@@ -162,17 +163,6 @@
162163

163164
<build>
164165
<plugins>
165-
<plugin>
166-
<groupId>org.apache.maven.plugins</groupId>
167-
<artifactId>maven-jar-plugin</artifactId>
168-
<configuration>
169-
<archive>
170-
<manifestEntries>
171-
<Automatic-Module-Name>io.fabric8.kubernetes.httpclient.vertx5</Automatic-Module-Name>
172-
</manifestEntries>
173-
</archive>
174-
</configuration>
175-
</plugin>
176166
<plugin>
177167
<groupId>org.apache.maven.plugins</groupId>
178168
<artifactId>maven-surefire-plugin</artifactId>

httpclient-vertx/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<name>Fabric8 :: Kubernetes :: HttpClient :: Vert.x</name>
3030

3131
<properties>
32+
<jpms.module.name>io.fabric8.kubernetes.client.vertx</jpms.module.name>
3233
<osgi.require-capability>
3334
osgi.extender;
3435
filter:="(osgi.extender=osgi.serviceloader.registrar)",
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Copyright (C) 2015 Red Hat, Inc.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
18+
-->
19+
<project xmlns="http://maven.apache.org/POM/4.0.0"
20+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
21+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
22+
<modelVersion>4.0.0</modelVersion>
23+
<parent>
24+
<groupId>io.fabric8</groupId>
25+
<artifactId>kubernetes-client-deps-compatibility-tests</artifactId>
26+
<version>7.7-SNAPSHOT</version>
27+
<relativePath>../pom.xml</relativePath>
28+
</parent>
29+
30+
<name>Fabric8 :: Kubernetes :: HttpClient JPMS Module Name :: Test</name>
31+
<artifactId>kubernetes-client-httpclient-jpms</artifactId>
32+
33+
<properties>
34+
<maven.deploy.skip>true</maven.deploy.skip>
35+
</properties>
36+
37+
<dependencies>
38+
<dependency>
39+
<groupId>org.junit.jupiter</groupId>
40+
<artifactId>junit-jupiter-engine</artifactId>
41+
<scope>test</scope>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.assertj</groupId>
45+
<artifactId>assertj-core</artifactId>
46+
<scope>test</scope>
47+
</dependency>
48+
</dependencies>
49+
50+
<build>
51+
<plugins>
52+
<plugin>
53+
<groupId>org.apache.maven.plugins</groupId>
54+
<artifactId>maven-dependency-plugin</artifactId>
55+
<executions>
56+
<execution>
57+
<id>copy-httpclient-jars</id>
58+
<phase>generate-test-resources</phase>
59+
<goals>
60+
<goal>copy</goal>
61+
</goals>
62+
<configuration>
63+
<artifactItems>
64+
<artifactItem>
65+
<groupId>io.fabric8</groupId>
66+
<artifactId>kubernetes-httpclient-jdk</artifactId>
67+
<version>${project.version}</version>
68+
<outputDirectory>${project.build.directory}/httpclient-jars</outputDirectory>
69+
</artifactItem>
70+
<artifactItem>
71+
<groupId>io.fabric8</groupId>
72+
<artifactId>kubernetes-httpclient-jetty</artifactId>
73+
<version>${project.version}</version>
74+
<outputDirectory>${project.build.directory}/httpclient-jars</outputDirectory>
75+
</artifactItem>
76+
<artifactItem>
77+
<groupId>io.fabric8</groupId>
78+
<artifactId>kubernetes-httpclient-okhttp</artifactId>
79+
<version>${project.version}</version>
80+
<outputDirectory>${project.build.directory}/httpclient-jars</outputDirectory>
81+
</artifactItem>
82+
<artifactItem>
83+
<groupId>io.fabric8</groupId>
84+
<artifactId>kubernetes-httpclient-vertx</artifactId>
85+
<version>${project.version}</version>
86+
<outputDirectory>${project.build.directory}/httpclient-jars</outputDirectory>
87+
</artifactItem>
88+
<artifactItem>
89+
<groupId>io.fabric8</groupId>
90+
<artifactId>kubernetes-httpclient-vertx-5</artifactId>
91+
<version>${project.version}</version>
92+
<outputDirectory>${project.build.directory}/httpclient-jars</outputDirectory>
93+
</artifactItem>
94+
</artifactItems>
95+
</configuration>
96+
</execution>
97+
</executions>
98+
</plugin>
99+
<plugin>
100+
<groupId>org.apache.maven.plugins</groupId>
101+
<artifactId>maven-surefire-plugin</artifactId>
102+
<configuration>
103+
<systemPropertyVariables>
104+
<httpclient.jars.dir>${project.build.directory}/httpclient-jars</httpclient.jars.dir>
105+
</systemPropertyVariables>
106+
</configuration>
107+
</plugin>
108+
</plugins>
109+
</build>
110+
111+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright (C) 2015 Red Hat, Inc.
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.fabric8.deps.compatibility.tests;
17+
18+
import org.junit.jupiter.api.BeforeAll;
19+
import org.junit.jupiter.api.Test;
20+
21+
import java.io.IOException;
22+
import java.lang.module.ModuleFinder;
23+
import java.lang.module.ModuleReference;
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
26+
import java.nio.file.Paths;
27+
import java.util.Comparator;
28+
import java.util.LinkedHashMap;
29+
import java.util.Map;
30+
import java.util.jar.JarFile;
31+
import java.util.jar.Manifest;
32+
import java.util.stream.Stream;
33+
34+
import static org.assertj.core.api.Assertions.assertThat;
35+
36+
class HttpClientAutomaticModuleNameTest {
37+
38+
private static final Map<String, String> EXPECTED_MODULE_NAMES = new LinkedHashMap<>();
39+
40+
static {
41+
EXPECTED_MODULE_NAMES.put("kubernetes-httpclient-jdk", "io.fabric8.kubernetes.client.jdkhttp");
42+
EXPECTED_MODULE_NAMES.put("kubernetes-httpclient-jetty", "io.fabric8.kubernetes.client.jetty");
43+
EXPECTED_MODULE_NAMES.put("kubernetes-httpclient-okhttp", "io.fabric8.kubernetes.client.okhttp");
44+
EXPECTED_MODULE_NAMES.put("kubernetes-httpclient-vertx-5", "io.fabric8.kubernetes.client.vertx5");
45+
EXPECTED_MODULE_NAMES.put("kubernetes-httpclient-vertx", "io.fabric8.kubernetes.client.vertx");
46+
}
47+
48+
private static Path jarsDir;
49+
50+
@BeforeAll
51+
static void setUp() {
52+
String jarsDirProperty = System.getProperty("httpclient.jars.dir");
53+
assertThat(jarsDirProperty)
54+
.as("System property 'httpclient.jars.dir' must be set")
55+
.isNotNull();
56+
jarsDir = Paths.get(jarsDirProperty);
57+
assertThat(jarsDir).isDirectory();
58+
}
59+
60+
@Test
61+
void httpClientModulesHaveCorrectAutomaticModuleName() throws IOException {
62+
try (Stream<Path> jarFiles = Files.list(jarsDir)) {
63+
Map<String, String> actualModuleNames = new LinkedHashMap<>();
64+
jarFiles.filter(p -> p.toString().endsWith(".jar")).forEach(jarPath -> {
65+
String fileName = jarPath.getFileName().toString();
66+
Map.Entry<String, String> match = findMatchingArtifact(fileName);
67+
if (match != null) {
68+
try (JarFile jarFile = new JarFile(jarPath.toFile())) {
69+
Manifest manifest = jarFile.getManifest();
70+
assertThat(manifest)
71+
.as("Manifest for %s", fileName)
72+
.isNotNull();
73+
String actualModuleName = manifest.getMainAttributes().getValue("Automatic-Module-Name");
74+
assertThat(actualModuleName)
75+
.as("Automatic-Module-Name in %s", fileName)
76+
.isEqualTo(match.getValue());
77+
actualModuleNames.put(match.getKey(), actualModuleName);
78+
} catch (IOException e) {
79+
throw new RuntimeException("Failed to read JAR: " + jarPath, e);
80+
}
81+
}
82+
});
83+
assertThat(actualModuleNames)
84+
.as("All httpclient modules should have been verified")
85+
.hasSize(EXPECTED_MODULE_NAMES.size());
86+
}
87+
}
88+
89+
@Test
90+
void httpClientModulesHaveValidJpmsModuleNames() throws IOException {
91+
try (Stream<Path> jarFiles = Files.list(jarsDir)) {
92+
Map<String, String> resolvedModuleNames = new LinkedHashMap<>();
93+
jarFiles.filter(p -> p.toString().endsWith(".jar")).forEach(jarPath -> {
94+
String fileName = jarPath.getFileName().toString();
95+
Map.Entry<String, String> match = findMatchingArtifact(fileName);
96+
if (match != null) {
97+
ModuleFinder finder = ModuleFinder.of(jarPath);
98+
assertThat(finder.findAll())
99+
.as("ModuleFinder should resolve module from %s", fileName)
100+
.hasSize(1);
101+
ModuleReference moduleRef = finder.findAll().iterator().next();
102+
String resolvedName = moduleRef.descriptor().name();
103+
assertThat(resolvedName)
104+
.as("JPMS module name resolved from %s", fileName)
105+
.isEqualTo(match.getValue());
106+
resolvedModuleNames.put(match.getKey(), resolvedName);
107+
}
108+
});
109+
assertThat(resolvedModuleNames)
110+
.as("All httpclient modules should have been verified via ModuleFinder")
111+
.hasSize(EXPECTED_MODULE_NAMES.size());
112+
}
113+
}
114+
115+
/**
116+
* Finds the best matching artifact prefix for the given filename.
117+
* Uses longest-prefix matching to distinguish e.g. "kubernetes-httpclient-vertx-5" from "kubernetes-httpclient-vertx".
118+
*/
119+
private static Map.Entry<String, String> findMatchingArtifact(String fileName) {
120+
return EXPECTED_MODULE_NAMES.entrySet().stream()
121+
.filter(entry -> fileName.startsWith(entry.getKey() + "-"))
122+
.max(Comparator.comparingInt(entry -> entry.getKey().length()))
123+
.orElse(null);
124+
}
125+
}

kubernetes-client-deps-compatibility-tests/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
<modules>
3838
<module>kubernetes-client-init-bc-fips</module>
39+
<module>kubernetes-client-httpclient-jpms</module>
3940
</modules>
4041

4142
<build>

pom.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1663,6 +1663,29 @@
16631663
</plugins>
16641664
</build>
16651665
</profile>
1666+
<profile>
1667+
<id>jpms-module-name</id>
1668+
<activation>
1669+
<file>
1670+
<exists>${basedir}/src/main/resources/META-INF/services/io.fabric8.kubernetes.client.http.HttpClient$Factory</exists>
1671+
</file>
1672+
</activation>
1673+
<build>
1674+
<plugins>
1675+
<plugin>
1676+
<groupId>org.apache.maven.plugins</groupId>
1677+
<artifactId>maven-jar-plugin</artifactId>
1678+
<configuration>
1679+
<archive>
1680+
<manifestEntries>
1681+
<Automatic-Module-Name>${jpms.module.name}</Automatic-Module-Name>
1682+
</manifestEntries>
1683+
</archive>
1684+
</configuration>
1685+
</plugin>
1686+
</plugins>
1687+
</build>
1688+
</profile>
16661689
<profile>
16671690
<id>revapi-compare-jars</id>
16681691
<activation>

0 commit comments

Comments
 (0)