Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,11 @@
<artifactId>quarkus-devservices-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-deployment</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,9 @@ public RunningQuarkusApplication run(String... args) throws Exception {

Method start = appClass.getMethod("start", String[].class);
Object application = appClass.getDeclaredConstructor().newInstance();

start.invoke(application, (Object) args);

Closeable closeTask = (Closeable) application;
return new RunningQuarkusApplicationImpl(new Closeable() {
@Override
Expand Down
13 changes: 13 additions & 0 deletions devtools/bom-descriptor-json/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devui</artifactId>
Expand Down
13 changes: 13 additions & 0 deletions docs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devui-deployment</artifactId>
Expand Down
4 changes: 4 additions & 0 deletions extensions/amazon-lambda/common-runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
Expand Down
6 changes: 5 additions & 1 deletion extensions/devservices/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@
<name>Quarkus - DevServices - Deployment</name>

<dependencies>
<dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-common</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@
import io.quarkus.deployment.util.ContainerRuntimeUtil;
import io.quarkus.deployment.util.ContainerRuntimeUtil.ContainerRuntime;
import io.quarkus.dev.spi.DevModeType;
import io.quarkus.devservice.runtime.config.DevServicesConfigBuilder;
import io.quarkus.devservices.common.ContainerUtil;
import io.quarkus.devservices.common.Labels;
import io.quarkus.devservices.common.StartableContainer;
Expand Down
1 change: 1 addition & 0 deletions extensions/devservices/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<module>oracle</module>
<module>common</module>
<module>deployment</module>
<module>runtime</module>
<module>keycloak</module>
<module>oidc</module>
</modules>
Expand Down
56 changes: 56 additions & 0 deletions extensions/devservices/runtime/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-devservices-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-devservices</artifactId>
<name>Quarkus - DevServices - Runtime</name>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>nativeimage</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package io.quarkus.devservices.crossclassloader.runtime;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.smallrye.config.ConfigMapping;

/**
* @param globalConfig should be a io.quarkus.deployment.dev.devservices.DevServicesConfig, but is not that type to avoid
* the dependency on the devservices module
* @param identifyingConfig a config object specific to the extension's dev services configuration
*/
public record ComparableDevServicesConfig(UUID applicationInstanceId,
DevServiceOwner owner,
Object globalConfig,
Object identifyingConfig) {

@Override
public boolean equals(Object o) {
if (!(o instanceof ComparableDevServicesConfig that))
return false;
return Objects.equals(owner, that.owner)
&& reflectiveEquals(globalConfig, that.globalConfig)
&& reflectiveEquals(identifyingConfig, that.identifyingConfig)
&& Objects.equals(applicationInstanceId, that.applicationInstanceId);
}

private static boolean reflectiveEquals(Object config, Object otherConfig) {

// This could be expensive, so we might wish to shove everything into a map, reflectively, and just compare that
// The externals won't change if we do that, so we can do it later if we want to
// We can assume config mapping is immutable
// We need to compare across classloaders, so we cannot use equals()

if (config == null || otherConfig == null) {
// If they're both null, they're equal
return config == otherConfig;
}

Class<?> clazz = config.getClass();
Class<?> otherClazz = otherConfig.getClass();

// We can't compare classes because of multiple classloaders, but we can compare class names
if (!clazz.getName().equals(otherClazz.getName())) {
return false;
}

try {
while (clazz != null) {
// Get all interfaces implemented by the class
for (Class<?> iface : clazz.getInterfaces()) {
// Check if the interface is a config one

if (isConfigInterface(iface)) {
// For each method in the interface
// In the future, if we wanted some methods to be ignored, we could use a marker annotation in the config object
for (Method method : iface.getMethods()) {

int modifiers = method.getModifiers();
if (isInvokableMethod(method, modifiers)) {
Method otherMethod = clazz == otherClazz ? method : otherClazz.getMethod(method.getName());
otherMethod.setAccessible(true);

Object thisValue = method.invoke(config);
Object otherValue = otherMethod.invoke(otherConfig);

// Assume the objects in the config use types from the parent classloader, or we just declare them non-equal if they're not in the same classloader

if (!Objects.deepEquals(thisValue, otherValue)) {
return false;
}
}
}
}
}
clazz = clazz.getSuperclass();
otherClazz = otherClazz.getSuperclass();

}
return true;
} catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}

/**
* We can't just use clazz.isAnnotationPresent(), because the annotation itself is likely to be proxied
*/
private static boolean containsAnnotation(Class<?> iface, Class<? extends Annotation> configAnnotation) {
return Arrays.stream(iface.getAnnotations())
.anyMatch(a -> a.annotationType().getName().equals(configAnnotation.getName()));
}

private static boolean isInvokableMethod(Method method, int modifiers) {
if (Modifier.isStatic(modifiers) ||
Modifier.isTransient(modifiers)) {
return false;
}

if (Modifier.isPrivate(modifiers)) {
return false;
}

if (method.getParameterCount() > 0) {
return false;
}

method.setAccessible(true);
return true;
}

private static boolean isConfigInterface(Class<?> iface) {
return containsAnnotation(iface, ConfigMapping.class) || containsAnnotation(iface, ConfigGroup.class);
}

}
14 changes: 14 additions & 0 deletions extensions/devservices/runtime/src/main/java/DevServiceOwner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.quarkus.devservices.crossclassloader.runtime;

/**
* We store the launch mode as a string to make cross-classloader comparisons easier
*
* @param featureName the name of the feature, e.g. "redis-client" or "kafka-client"
* @param launchMode use launchMode.name()
* @param configName the name of the config, e.g. "redis"
*/

public record DevServiceOwner(String featureName, String launchMode, String configName) {

// Ideally we'd have a constructor that takes in a LaunchMode and calls launchMode.name(), but because this loads parent-first, we can't pass in Quarkus classes
}
Loading
Loading