Repository: quarkusio/quarkus
Issue: QuarkusDev does not start with Kotlin subprojects

To see the full disucssion on this subject, please refer to this chat: https://quarkusio.zulipchat.com/#narrow/stream/187030-users/topic/Quarkus.20Dev.20not.20working.20with.20Kotlin.20and.20Subprojects

**Describe the bug**
I have a Quarkus gradle project with two modules `project-a` and `project-a-models`. `project-a` is a quarkus project having a dependency on the plain Kotlin `project-a-models` module.
I can compile this configuration (see sample project linked below). However, when I run `gradlew quarkusDev`, it fails searching a Java path. This is error fixed with the PR #6551. However, when I now run `gradlew quarkusDev`, the server never starts up but hangs after starting the debugger port. 

I ran jstack on gradlew: https://gist.github.com/andreas-eberle/28c3d1438292ab636f6c374988f95f22

And jstack on the built and executed project-a-1.0.0-SNAPSHOT-dev.jar: https://gist.github.com/andreas-eberle/7393cf44da1952d622c0d6383ea09a65

The last part of the output of `gradlew quarkusDev --stacktrace --debug` is here: https://gist.github.com/andreas-eberle/6c074e8f10c2a7514b323430b9f504ed

**Expected behavior**
Server starts up without an exception.

**Actual behavior**
Dev server hangs with PR #6551 or crashes with quarkus 1.1.1-Final


**To Reproduce**
Steps to reproduce the behavior:
1. Download the reproducer project [quarkus-experiments.zip](https://github.com/quarkusio/quarkus/files/4064951/quarkus-experiments.zip)
2. Install quarkus locally from PR https://github.com/quarkusio/quarkus/pull/6551/files
2. run `./gradlew :project-a:quarkusDev --stacktrace --info`
3. the server won't start up


**Platform**
- Windows 10 x64
- Oracle JDK 64bit Version 1.8.0_231

Changed files:
devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java
independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java
core/devmode/src/main/java/io/quarkus/dev/IsolatedDevModeMain.java
core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java
independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapAppModelFactory.java
core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java

diff --git a/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java b/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java
index 6d2b7199949..e491947f887 100644
--- a/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java
+++ b/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java
@@ -12,8 +12,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import io.quarkus.bootstrap.model.AppModel;
-
 /**
  * Object that is used to pass context data from the plugin doing the invocation
  * into the dev mode process using java serialization.
@@ -45,8 +43,6 @@ public class DevModeContext implements Serializable {
     private List<String> compilerPluginArtifacts;
     private List<String> compilerPluginsOptions;
 
-    private AppModel appModel;
-
     public boolean isLocalProjectDiscovery() {
         return localProjectDiscovery;
     }
@@ -173,15 +169,6 @@ public DevModeContext setProjectDir(File projectDir) {
         return this;
     }
 
-    public AppModel getAppModel() {
-        return appModel;
-    }
-
-    public DevModeContext setAppModel(AppModel appModel) {
-        this.appModel = appModel;
-        return this;
-    }
-
     public static class ModuleInfo implements Serializable {
 
         private final String name;
diff --git a/core/devmode/src/main/java/io/quarkus/dev/IsolatedDevModeMain.java b/core/devmode/src/main/java/io/quarkus/dev/IsolatedDevModeMain.java
index 756bbf9ca52..56e0351d19f 100644
--- a/core/devmode/src/main/java/io/quarkus/dev/IsolatedDevModeMain.java
+++ b/core/devmode/src/main/java/io/quarkus/dev/IsolatedDevModeMain.java
@@ -154,11 +154,16 @@ public void stop() {
             }
         }
         QuarkusConfigFactory.setConfig(null);
-        final ConfigProviderResolver cpr = ConfigProviderResolver.instance();
+
+        ClassLoader old = Thread.currentThread().getContextClassLoader();
+        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
         try {
+            final ConfigProviderResolver cpr = ConfigProviderResolver.instance();
             cpr.releaseConfig(cpr.getConfig());
         } catch (Throwable ignored) {
             // just means no config was installed, which is fine
+        } finally {
+            Thread.currentThread().setContextClassLoader(old);
         }
         runner = null;
     }
@@ -242,7 +247,7 @@ public void run() {
                     synchronized (DevModeMain.class) {
                         if (runner != null) {
                             try {
-                                runner.close();
+                                stop();
                             } catch (Exception e) {
                                 e.printStackTrace();
                             }
diff --git a/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java b/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java
index 5e4e5672e86..fa77d6a2c38 100644
--- a/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java
+++ b/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java
@@ -171,7 +171,11 @@ boolean checkForChangedClasses() throws IOException {
 
             for (String sourcePath : module.getSourcePaths()) {
                 final Set<File> changedSourceFiles;
-                try (final Stream<Path> sourcesStream = Files.walk(Paths.get(sourcePath))) {
+                Path start = Paths.get(sourcePath);
+                if (!Files.exists(start)) {
+                    continue;
+                }
+                try (final Stream<Path> sourcesStream = Files.walk(start)) {
                     changedSourceFiles = sourcesStream
                             .parallel()
                             .filter(p -> matchingHandledExtension(p).isPresent()
@@ -218,6 +222,9 @@ private boolean checkForClassFilesChangesInModule(DevModeContext.ModuleInfo modu
         try {
             for (String folder : module.getClassesPath().split(File.pathSeparator)) {
                 final Path moduleClassesPath = Paths.get(folder);
+                if (!Files.exists(moduleClassesPath)) {
+                    continue;
+                }
                 try (final Stream<Path> classesStream = Files.walk(moduleClassesPath)) {
                     final Set<Path> classFilePaths = classesStream
                             .parallel()
diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java
index ed1c105125a..162cfe2ce88 100644
--- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java
+++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java
@@ -17,6 +17,8 @@
 import java.net.URI;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -28,6 +30,7 @@
 import java.util.concurrent.Executors;
 import java.util.jar.Attributes;
 import java.util.jar.Manifest;
+import java.util.stream.Stream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
@@ -39,6 +42,7 @@
 import org.gradle.api.artifacts.DependencySet;
 import org.gradle.api.artifacts.ProjectDependency;
 import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.api.file.FileCollection;
 import org.gradle.api.plugins.Convention;
 import org.gradle.api.plugins.JavaPlugin;
 import org.gradle.api.plugins.JavaPluginConvention;
@@ -51,6 +55,7 @@
 import org.gradle.api.tasks.compile.JavaCompile;
 import org.gradle.api.tasks.options.Option;
 
+import io.quarkus.bootstrap.BootstrapConstants;
 import io.quarkus.bootstrap.model.AppArtifact;
 import io.quarkus.bootstrap.model.AppArtifactKey;
 import io.quarkus.bootstrap.model.AppDependency;
@@ -293,8 +298,45 @@ public void startDev() {
                     sourcePaths.add(sourceDir.getAbsolutePath());
                 }
 
-                String classesPaths = mainSourceSet.getOutput().getClassesDirs().getAsPath();
-                String resourcePaths = mainSourceSet.getResources().getSourceDirectories().getAsPath();
+                FileCollection classesDirs = mainSourceSet.getOutput().getClassesDirs();
+                final String classesPaths;
+                Set<File> classDirFiles = classesDirs.getFiles();
+                if (classDirFiles.size() == 1) {
+                    classesPaths = classesDirs.getAsPath();
+                } else {
+                    //there does not seem to be any sane way of dealing with multiple output dirs, as there does not seem
+                    //to be a way to map them. We will need to address this at some point, but for now we just stick them
+                    //all in a temp dir
+
+                    Path path = getTemporaryDir().toPath();
+                    classesPaths = path.toAbsolutePath().toString();
+                    for (File c : classDirFiles) {
+                        Path cd = c.toPath();
+                        if (!Files.exists(cd)) {
+                            continue;
+                        }
+                        try (Stream<Path> stream = Files.walk(cd)) {
+                            stream.forEach(s -> {
+                                try {
+                                    if (Files.isDirectory(s)) {
+                                        return;
+                                    }
+                                    final Path file = cd.relativize(path);
+                                    final Path targetPath = path.resolve(file.toString());
+                                    Files.createDirectories(targetPath.getParent());
+                                    byte[] data = Files.readAllBytes(s);
+                                    Files.write(targetPath, data);
+                                } catch (IOException e) {
+                                    throw new RuntimeException(e);
+                                }
+
+                            });
+                        }
+                    }
+
+                }
+
+                String resourcePaths = mainSourceSet.getResources().getSourceDirectories().getSingleFile().getAbsolutePath(); //TODO: multiple resource directories
 
                 DevModeContext.ModuleInfo wsModuleInfo = new DevModeContext.ModuleInfo(
                         dependencyProject.getName(),
@@ -351,6 +393,13 @@ public void startDev() {
                 obj.writeObject(context);
                 obj.close();
                 out.write(bytes.toByteArray());
+
+                out.putNextEntry(new ZipEntry(BootstrapConstants.SERIALIZED_APP_MODEL));
+                bytes = new ByteArrayOutputStream();
+                obj = new ObjectOutputStream(new DataOutputStream(bytes));
+                obj.writeObject(appModel);
+                obj.close();
+                out.write(bytes.toByteArray());
             }
 
             extension.outputDirectory().mkdirs();
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapAppModelFactory.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapAppModelFactory.java
index 121f7e88393..f415ced24da 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapAppModelFactory.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapAppModelFactory.java
@@ -213,10 +213,10 @@ public AppModelResolver getAppModelResolver() {
     }
 
     public CurationResult resolveAppModel() throws BootstrapException {
-        if (test) {
-            //gradle tests encode the result on the class path
+        if (test || devMode) {
+            //gradle tests and dev encode the result on the class path
 
-            try (InputStream existing = getClass().getResourceAsStream(BootstrapConstants.SERIALIZED_APP_MODEL)){
+            try (InputStream existing = getClass().getClassLoader().getResourceAsStream(BootstrapConstants.SERIALIZED_APP_MODEL)){
                 if(existing != null ) {
                     AppModel appModel = (AppModel) new ObjectInputStream(existing).readObject();
                     return new CurationResult(appModel);
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java
index c135e67c660..14ad9834b79 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java
@@ -26,7 +26,6 @@
 import io.quarkus.bootstrap.model.AppArtifactKey;
 import io.quarkus.bootstrap.model.AppDependency;
 import io.quarkus.bootstrap.model.AppModel;
-import io.quarkus.bootstrap.resolver.AppModelResolver;
 
 /**
  * The result of the curate step that is done by QuarkusBootstrap.