Skip to content

Commit ccd0c3c

Browse files
committed
more
1 parent adb8da9 commit ccd0c3c

File tree

1 file changed

+72
-58
lines changed

1 file changed

+72
-58
lines changed

core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/PurgeProcessor.java

Lines changed: 72 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,16 @@
3939
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
4040
import io.quarkus.deployment.pkg.builditem.PurgeClassesBuildItem;
4141
import io.quarkus.maven.dependency.ArtifactKey;
42+
import io.quarkus.maven.dependency.DependencyFlags;
4243
import io.quarkus.maven.dependency.ResolvedDependency;
4344

4445
public class PurgeProcessor {
4546

4647
private static final Logger log = Logger.getLogger(PurgeProcessor.class);
4748
private static final String SERVICE_LOADER_INTERNAL = "java/util/ServiceLoader";
4849
private static final String SISU_NAMED_RESOURCE = "META-INF/sisu/javax.inject.Named";
50+
private static final String META_INF_VERSIONS = "META-INF/versions";
51+
private static final String META_INF_SERVICES = "META-INF/services/";
4952

5053
@BuildStep
5154
void analyzeReachableClasses(
@@ -98,46 +101,50 @@ void analyzeReachableClasses(
98101
// For multi-release entries, we pick the highest version <= appJavaVersion,
99102
// matching what JarFile.runtimeVersion() resolves at runtime.
100103
dep.getContentTree().walkRaw(visit -> {
101-
String relative = visit.getRelativePath();
104+
String entry = visit.getResourceName();
102105
// Handle multi-release version entries (META-INF/versions/N/...)
103-
if (relative.startsWith("META-INF/versions/")) {
104-
String afterVersions = relative.substring("META-INF/versions/".length());
105-
int slash = afterVersions.indexOf('/');
106-
if (slash > 0) {
107-
int version;
108-
try {
109-
version = Integer.parseInt(afterVersions.substring(0, slash));
110-
} catch (NumberFormatException e) {
111-
return;
112-
}
113-
// Skip versions newer than the app's target — they won't be loaded at runtime
114-
if (version > appJavaVersion) {
115-
return;
116-
}
117-
String classPath = afterVersions.substring(slash + 1);
118-
if (isClassEntry(classPath)) {
119-
String className = classNameOf(classPath);
120-
// Replace existing bytecode only if this version is higher
121-
// (higher version = closer to runtime resolution)
122-
int currentVersion = depBytecodeVersion.getOrDefault(className, 0);
123-
if (version > currentVersion) {
124-
classToDep.put(className, key);
125-
if (currentVersion == 0 && !depBytecode.containsKey(className)) {
126-
classCount[0]++;
127-
}
128-
try (InputStream is = Files.newInputStream(visit.getPath())) {
129-
depBytecode.put(className, is.readAllBytes());
130-
depBytecodeVersion.put(className, version);
131-
} catch (IOException e) {
132-
log.debugf(e, "Failed to read bytecode: %s", visit.getPath());
133-
}
106+
if (entry.startsWith(META_INF_VERSIONS)) {
107+
if (entry.length() == META_INF_VERSIONS.length() || entry.charAt(META_INF_VERSIONS.length()) != '/') {
108+
// if it does not start with META-INF/versions/
109+
return;
110+
}
111+
// META-INF/versions/N/
112+
final int javaVersionSeparator = entry.indexOf('/', META_INF_VERSIONS.length() + 1);
113+
if (javaVersionSeparator == -1) {
114+
return;
115+
}
116+
int version;
117+
try {
118+
version = Integer.parseInt(entry.substring(META_INF_VERSIONS.length() + 1, javaVersionSeparator));
119+
} catch (NumberFormatException e) {
120+
return;
121+
}
122+
// Skip versions newer than the app's target — they won't be loaded at runtime
123+
if (version > appJavaVersion) {
124+
return;
125+
}
126+
if (isClassEntry(entry)) {
127+
String className = classNameOf(entry, javaVersionSeparator + 1);
128+
// Replace existing bytecode only if this version is higher
129+
// (higher version = closer to runtime resolution)
130+
int currentVersion = depBytecodeVersion.getOrDefault(className, 0);
131+
if (version > currentVersion) {
132+
classToDep.put(className, key);
133+
if (currentVersion == 0 && !depBytecode.containsKey(className)) {
134+
classCount[0]++;
135+
}
136+
try (InputStream is = Files.newInputStream(visit.getPath())) {
137+
depBytecode.put(className, is.readAllBytes());
138+
depBytecodeVersion.put(className, version);
139+
} catch (IOException e) {
140+
log.debugf(e, "Failed to read bytecode: %s", visit.getPath());
134141
}
135142
}
136143
}
137144
return;
138145
}
139-
if (isClassEntry(relative)) {
140-
String className = classNameOf(relative);
146+
if (isClassEntry(entry)) {
147+
String className = classNameOf(entry);
141148
classToDep.put(className, key);
142149
classCount[0]++;
143150
// Base class: only store if no versioned entry was seen yet
@@ -150,13 +157,15 @@ void analyzeReachableClasses(
150157
}
151158
}
152159
detectServiceLoaderCalls(visit.getPath(), className, serviceLoaderCalls);
160+
return;
153161
}
154-
if (relative.startsWith("META-INF/services/") && !relative.endsWith("/")) {
155-
parseServiceFile(visit.getPath(), relative, serviceProviders);
162+
if (entry.startsWith(META_INF_SERVICES) && !entry.endsWith("/")) {
163+
parseServiceFile(visit.getPath(), entry, serviceProviders);
164+
return;
156165
}
157166
// Collect sisu named components (META-INF/sisu/javax.inject.Named).
158167
// These are only included if ClassLoader.getResources() for this path is detected.
159-
if (SISU_NAMED_RESOURCE.equals(relative)) {
168+
if (SISU_NAMED_RESOURCE.equals(entry)) {
160169
parseSisuNamedFile(visit.getPath(), sisuNamedClasses);
161170
}
162171
});
@@ -184,18 +193,19 @@ void analyzeReachableClasses(
184193
// to dependency classes (e.g. app code using commons-io IOUtils).
185194
final Map<String, byte[]> appBytecode = new HashMap<>();
186195
appModel.getAppArtifact().getContentTree().walk(visit -> {
187-
String relative = visit.getRelativePath();
188-
if (isClassEntry(relative)) {
189-
String className = classNameOf(relative);
196+
String entry = visit.getResourceName();
197+
if (isClassEntry(entry)) {
198+
String className = classNameOf(entry);
190199
detectServiceLoaderCalls(visit.getPath(), className, serviceLoaderCalls);
191200
try (InputStream is = Files.newInputStream(visit.getPath())) {
192201
appBytecode.put(className, is.readAllBytes());
193202
} catch (IOException e) {
194203
log.debugf(e, "Failed to read app bytecode: %s", visit.getPath());
195204
}
205+
return;
196206
}
197-
if (relative.startsWith("META-INF/services/") && !relative.endsWith("/")) {
198-
parseServiceFile(visit.getPath(), relative, serviceProviders);
207+
if (entry.startsWith(META_INF_SERVICES) && !entry.endsWith("/")) {
208+
parseServiceFile(visit.getPath(), entry, serviceProviders);
199209
}
200210
});
201211

@@ -209,12 +219,12 @@ void analyzeReachableClasses(
209219
final Set<String> roots = new HashSet<>();
210220
roots.add(mainClass.getClassName());
211221
roots.addAll(generatedBytecode.keySet());
212-
for (ResolvedDependency dep : appModel.getRuntimeDependencies()) {
222+
for (ResolvedDependency dep : appModel.getDependencies(DependencyFlags.RUNTIME_CP)) {
213223
if ("quarkus-bootstrap-runner".equals(dep.getArtifactId())) {
214224
dep.getContentTree().walk(visit -> {
215-
String relative = visit.getRelativePath();
216-
if (isClassEntry(relative)) {
217-
roots.add(classNameOf(relative));
225+
String entry = visit.getResourceName();
226+
if (isClassEntry(entry)) {
227+
roots.add(classNameOf(entry));
218228
}
219229
});
220230
break;
@@ -836,19 +846,23 @@ private static String formatSize(long bytes) {
836846
}
837847
}
838848

839-
private static boolean isClassEntry(String relativePath) {
840-
return relativePath.endsWith(".class")
841-
&& !relativePath.equals("module-info.class")
842-
&& !relativePath.endsWith("package-info.class");
849+
private static boolean isClassEntry(String resourceName) {
850+
return resourceName.endsWith(".class")
851+
&& !resourceName.equals("module-info.class")
852+
&& !resourceName.endsWith("package-info.class");
853+
}
854+
855+
private static String classNameOf(String classResourceName) {
856+
return classNameOf(classResourceName, 0);
843857
}
844858

845-
private static String classNameOf(String relativePath) {
846-
return relativePath.substring(0, relativePath.length() - 6).replace('/', '.');
859+
private static String classNameOf(String resourceName, int classNameStartIndex) {
860+
return resourceName.substring(classNameStartIndex, resourceName.length() - 6).replace('/', '.');
847861
}
848862

849863
private void parseServiceFile(Path file, String relativePath,
850864
Map<String, Set<String>> serviceProviders) {
851-
String serviceInterface = relativePath.substring("META-INF/services/".length());
865+
String serviceInterface = relativePath.substring(META_INF_SERVICES.length());
852866
if (serviceInterface.isEmpty() || serviceInterface.contains("/")) {
853867
return;
854868
}
@@ -967,16 +981,16 @@ public void visitMethodInsn(int opcode, String owner, String mname,
967981
private static int detectAppJavaVersion(ApplicationModel appModel) {
968982
int[] majorVersion = new int[1];
969983
appModel.getAppArtifact().getContentTree().walk(visit -> {
970-
if (majorVersion[0] > 0) {
971-
return;
972-
}
973-
String relative = visit.getRelativePath();
974-
if (isClassEntry(relative)) {
984+
String entry = visit.getResourceName();
985+
if (isClassEntry(entry)) {
975986
try (InputStream is = Files.newInputStream(visit.getPath())) {
976987
byte[] header = new byte[8];
977988
if (is.read(header) == 8) {
978989
// Class file: magic (4 bytes) + minor (2 bytes) + major (2 bytes)
979990
majorVersion[0] = ((header[6] & 0xFF) << 8) | (header[7] & 0xFF);
991+
if (majorVersion[0] > 0) {
992+
visit.stopWalking();
993+
}
980994
}
981995
} catch (IOException e) {
982996
// ignore, will try next class

0 commit comments

Comments
 (0)