diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml
index 5337b2c7f9e32..2c54447b4a690 100644
--- a/bom/deployment/pom.xml
+++ b/bom/deployment/pom.xml
@@ -617,12 +617,6 @@
${project.version}
-
- io.quarkus
- quarkus-development-mode
- ${project.version}
-
-
io.quarkus
quarkus-scala-deployment
diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml
index 0b4bdf88aa966..b1d996b532f84 100644
--- a/bom/runtime/pom.xml
+++ b/bom/runtime/pom.xml
@@ -280,6 +280,18 @@
quarkus-development-mode-spi
${project.version}
+
+ io.quarkus
+ quarkus-ide-launcher
+ ${project.version}
+
+
+
+ *
+ *
+
+
+
io.quarkus
quarkus-arc
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java
index 2431614c067b7..dc0f2f2b1823b 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java
@@ -31,6 +31,7 @@
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.LiveReloadBuildItem;
+import io.quarkus.deployment.builditem.RawCommandLineArgumentsBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.pkg.builditem.BuildSystemTargetBuildItem;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
@@ -99,6 +100,7 @@ public BuildResult run() throws Exception {
.addInitial(DeploymentClassLoaderBuildItem.class)
.addInitial(ArchiveRootBuildItem.class)
.addInitial(ShutdownContextBuildItem.class)
+ .addInitial(RawCommandLineArgumentsBuildItem.class)
.addInitial(LaunchModeBuildItem.class)
.addInitial(LiveReloadBuildItem.class)
.addInitial(AdditionalApplicationArchiveBuildItem.class)
@@ -124,6 +126,7 @@ public BuildResult run() throws Exception {
.produce(liveReloadBuildItem)
.produce(new ArchiveRootBuildItem(root, rootFs == null ? root : rootFs.getPath("/"), excludedFromIndexing))
.produce(new ShutdownContextBuildItem())
+ .produce(new RawCommandLineArgumentsBuildItem())
.produce(new LaunchModeBuildItem(launchMode))
.produce(new BuildSystemTargetBuildItem(targetDir, baseName))
.produce(new DeploymentClassLoaderBuildItem(deploymentClassLoader))
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/RawCommandLineArgumentsBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RawCommandLineArgumentsBuildItem.java
new file mode 100644
index 0000000000000..2b51d86154bd5
--- /dev/null
+++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RawCommandLineArgumentsBuildItem.java
@@ -0,0 +1,31 @@
+package io.quarkus.deployment.builditem;
+
+import java.util.function.Supplier;
+
+import io.quarkus.builder.item.SimpleBuildItem;
+import io.quarkus.deployment.recording.BytecodeRecorderImpl;
+import io.quarkus.runtime.StartupContext;
+
+/**
+ * A build item that represents the raw command line arguments as they were passed to the application.
+ *
+ * No filtering is done on these parameters.
+ */
+public final class RawCommandLineArgumentsBuildItem extends SimpleBuildItem
+ implements BytecodeRecorderImpl.ReturnedProxy, Supplier {
+
+ @Override
+ public String __returned$proxy$key() {
+ return StartupContext.RAW_COMMAND_LINE_ARGS;
+ }
+
+ @Override
+ public boolean __static$$init() {
+ return true;
+ }
+
+ @Override
+ public String[] get() {
+ throw new IllegalStateException("Can only be called at runtime");
+ }
+}
diff --git a/core/devmode/src/main/java/io/quarkus/dev/ClassLoaderCompiler.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/ClassLoaderCompiler.java
similarity index 99%
rename from core/devmode/src/main/java/io/quarkus/dev/ClassLoaderCompiler.java
rename to core/deployment/src/main/java/io/quarkus/deployment/dev/ClassLoaderCompiler.java
index 9c57f61f483a8..77391bc2d59d8 100644
--- a/core/devmode/src/main/java/io/quarkus/dev/ClassLoaderCompiler.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/ClassLoaderCompiler.java
@@ -1,4 +1,4 @@
-package io.quarkus.dev;
+package io.quarkus.deployment.dev;
import java.io.File;
import java.io.IOException;
diff --git a/core/devmode/src/main/java/io/quarkus/dev/CompilationProvider.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/CompilationProvider.java
similarity index 98%
rename from core/devmode/src/main/java/io/quarkus/dev/CompilationProvider.java
rename to core/deployment/src/main/java/io/quarkus/deployment/dev/CompilationProvider.java
index 253494593b6e8..3514b039ccdd7 100644
--- a/core/devmode/src/main/java/io/quarkus/dev/CompilationProvider.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/CompilationProvider.java
@@ -1,4 +1,4 @@
-package io.quarkus.dev;
+package io.quarkus.deployment.dev;
import java.io.File;
import java.nio.charset.Charset;
diff --git a/core/devmode/src/main/java/io/quarkus/dev/CompilerFlags.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/CompilerFlags.java
similarity index 98%
rename from core/devmode/src/main/java/io/quarkus/dev/CompilerFlags.java
rename to core/deployment/src/main/java/io/quarkus/deployment/dev/CompilerFlags.java
index 6d26b999eb073..2cdab4f1b9d66 100644
--- a/core/devmode/src/main/java/io/quarkus/dev/CompilerFlags.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/CompilerFlags.java
@@ -1,4 +1,4 @@
-package io.quarkus.dev;
+package io.quarkus.deployment.dev;
import java.util.ArrayList;
import java.util.Collection;
diff --git a/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeContext.java
similarity index 96%
rename from core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java
rename to core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeContext.java
index e491947f88761..08023a16c9f17 100644
--- a/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeContext.java
@@ -1,4 +1,4 @@
-package io.quarkus.dev;
+package io.quarkus.deployment.dev;
import java.io.File;
import java.io.Serializable;
@@ -27,6 +27,7 @@ public class DevModeContext implements Serializable {
private String sourceEncoding;
private final List classesRoots = new ArrayList<>();
+ private final List additionalClassPathElements = new ArrayList<>();
private File frameworkClassesDir;
private File cacheDir;
private File projectDir;
@@ -80,6 +81,10 @@ public List getClassesRoots() {
return classesRoots;
}
+ public List getAdditionalClassPathElements() {
+ return additionalClassPathElements;
+ }
+
public File getFrameworkClassesDir() {
return frameworkClassesDir;
}
diff --git a/core/devmode/src/main/java/io/quarkus/dev/DevModeMain.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeMain.java
similarity index 96%
rename from core/devmode/src/main/java/io/quarkus/dev/DevModeMain.java
rename to core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeMain.java
index b66e3ef9fb0ff..39f184ed7e8ff 100644
--- a/core/devmode/src/main/java/io/quarkus/dev/DevModeMain.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeMain.java
@@ -1,4 +1,5 @@
-package io.quarkus.dev;
+
+package io.quarkus.deployment.dev;
import java.io.Closeable;
import java.io.DataInputStream;
@@ -20,6 +21,7 @@
import io.quarkus.bootstrap.app.AdditionalDependency;
import io.quarkus.bootstrap.app.CuratedApplication;
import io.quarkus.bootstrap.app.QuarkusBootstrap;
+import io.quarkus.dev.appstate.ApplicationStateNotification;
/**
* The main entry point for the dev mojo execution
@@ -115,5 +117,6 @@ public void close() throws IOException {
if (realCloseable != null) {
realCloseable.close();
}
+ ApplicationStateNotification.waitForApplicationStop();
}
}
diff --git a/core/devmode/src/main/java/io/quarkus/dev/HotDeploymentConfigFileBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/HotDeploymentConfigFileBuildStep.java
similarity index 96%
rename from core/devmode/src/main/java/io/quarkus/dev/HotDeploymentConfigFileBuildStep.java
rename to core/deployment/src/main/java/io/quarkus/deployment/dev/HotDeploymentConfigFileBuildStep.java
index 6f72aa76ff6eb..e227702fecd71 100644
--- a/core/devmode/src/main/java/io/quarkus/dev/HotDeploymentConfigFileBuildStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/HotDeploymentConfigFileBuildStep.java
@@ -1,4 +1,4 @@
-package io.quarkus.dev;
+package io.quarkus.deployment.dev;
import java.util.List;
import java.util.Map;
diff --git a/core/devmode/src/main/java/io/quarkus/dev/IsolatedDevModeMain.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java
similarity index 84%
rename from core/devmode/src/main/java/io/quarkus/dev/IsolatedDevModeMain.java
rename to core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java
index ce921f073d0a6..f5d5fc4715258 100644
--- a/core/devmode/src/main/java/io/quarkus/dev/IsolatedDevModeMain.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java
@@ -1,4 +1,4 @@
-package io.quarkus.dev;
+package io.quarkus.deployment.dev;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -29,8 +29,10 @@
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.deployment.builditem.ApplicationClassPredicateBuildItem;
+import io.quarkus.dev.appstate.ApplicationStateNotification;
import io.quarkus.dev.spi.HotReplacementSetup;
import io.quarkus.runner.bootstrap.AugmentActionImpl;
+import io.quarkus.runtime.ApplicationLifecycleManager;
import io.quarkus.runtime.Timing;
import io.quarkus.runtime.configuration.QuarkusConfigFactory;
import io.quarkus.runtime.logging.InitialConfigurator;
@@ -49,6 +51,7 @@ public class IsolatedDevModeMain implements BiConsumer() {
+ @Override
+ public void accept(Integer integer) {
+ if (restarting || ApplicationLifecycleManager.isVmShuttingDown()
+ || context.isAbortOnFailedStart()) {
+ return;
+ }
+ System.out.println("Quarkus application exited with code " + integer);
+ System.out.println("Press Enter to restart");
+ try {
+ while (System.in.read() != '\n') {
+ }
+ runtimeUpdatesProcessor.checkForChangedClasses();
+ restartApp(runtimeUpdatesProcessor.checkForFileChange());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ runner = start.runMainClass();
} catch (Throwable t) {
deploymentProblem = t;
if (context.isAbortOnFailedStart()) {
@@ -91,7 +118,12 @@ private synchronized void firstStart() {
}
public synchronized void restartApp(Set changedResources) {
+ restarting = true;
stop();
+
+ //this clears any old state
+ ApplicationStateNotification.notifyApplicationStopped();
+ restarting = false;
Timing.restart(curatedApplication.getAugmentClassLoader());
deploymentProblem = null;
ClassLoader old = Thread.currentThread().getContextClassLoader();
@@ -100,11 +132,11 @@ public synchronized void restartApp(Set changedResources) {
//ok, we have resolved all the deps
try {
StartupAction start = augmentAction.reloadExistingApplication(changedResources);
- runner = start.run();
+ runner = start.runMainClass();
+ ApplicationStateNotification.waitForApplicationStart();
} catch (Throwable t) {
deploymentProblem = t;
log.error("Failed to start quarkus", t);
-
}
} finally {
Thread.currentThread().setContextClassLoader(old);
@@ -169,6 +201,8 @@ public void stop() {
}
public void close() {
+ //don't attempt to restart in the exit code handler
+ restarting = true;
try {
stop();
} finally {
diff --git a/core/devmode/src/main/java/io/quarkus/dev/JavaCompilationProvider.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/JavaCompilationProvider.java
similarity index 99%
rename from core/devmode/src/main/java/io/quarkus/dev/JavaCompilationProvider.java
rename to core/deployment/src/main/java/io/quarkus/deployment/dev/JavaCompilationProvider.java
index 40153c61affe9..6e8ebb4210e12 100644
--- a/core/devmode/src/main/java/io/quarkus/dev/JavaCompilationProvider.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/JavaCompilationProvider.java
@@ -1,4 +1,4 @@
-package io.quarkus.dev;
+package io.quarkus.deployment.dev;
import java.io.File;
import java.io.IOException;
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/LauncherMain.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/LauncherMain.java
new file mode 100644
index 0000000000000..09462f6abe909
--- /dev/null
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/LauncherMain.java
@@ -0,0 +1,38 @@
+package io.quarkus.deployment.dev;
+
+import java.io.File;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+
+import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
+
+import io.smallrye.config.SmallRyeConfigProviderResolver;
+
+/**
+ * The entry point when launched from an IDE
+ */
+public class LauncherMain {
+
+ public static void main(Path appClasses, Path wiring, URL[] classPath, String... args) throws Exception {
+ DevModeContext context = new DevModeContext();
+ context.setAbortOnFailedStart(true);
+ context.setTest(false);
+ context.setCacheDir(Files.createTempDirectory("quarkus-cache").toFile());
+ context.setSourceEncoding("UTF-8");
+ File appClassesFile = appClasses.toFile();
+ context.getClassesRoots().add(appClassesFile);
+
+ //TODO: huge hacks
+ File src = new File(appClassesFile, "../../src/main/java");
+ File res = new File(appClassesFile, "../../src/main/resources");
+
+ context.getModules().add(new DevModeContext.ModuleInfo("main", new File("").getAbsolutePath(),
+ Collections.singleton(src.getAbsolutePath()), appClassesFile.getAbsolutePath(), res.getAbsolutePath()));
+ //the loading of this is super wierd, and does its own class loader delegation for some reason
+ ConfigProviderResolver.setInstance(new SmallRyeConfigProviderResolver());
+ DevModeMain main = new DevModeMain(context);
+ main.start();
+ }
+}
diff --git a/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java
similarity index 99%
rename from core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java
rename to core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java
index fa77d6a2c380a..7e1b841b9beab 100644
--- a/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java
@@ -1,4 +1,4 @@
-package io.quarkus.dev;
+package io.quarkus.deployment.dev;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java
index 9efe7e622399b..59544bbf05bcb 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java
@@ -35,8 +35,8 @@ public class PackageConfig {
/**
* The entry point of the application. In most cases this should not be modified.
*/
- @ConfigItem(defaultValue = "io.quarkus.runner.GeneratedMain")
- public String mainClass;
+ @ConfigItem
+ public Optional mainClass;
/**
* Files that should not be copied to the output artifact
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java
index af55e6b87f2c6..886d73461f236 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java
@@ -57,6 +57,7 @@
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.GeneratedNativeImageClassBuildItem;
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
+import io.quarkus.deployment.builditem.MainClassBuildItem;
import io.quarkus.deployment.builditem.TransformedClassesBuildItem;
import io.quarkus.deployment.pkg.PackageConfig;
import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem;
@@ -144,13 +145,15 @@ public JarBuildItem buildRunnerJar(CurateOutcomeBuildItem curateOutcomeBuildItem
PackageConfig packageConfig,
List generatedClasses,
List generatedResources,
- List uberJarRequired) throws Exception {
+ List uberJarRequired,
+ MainClassBuildItem mainClassBuildItem) throws Exception {
if (!uberJarRequired.isEmpty() || packageConfig.uberJar) {
return buildUberJar(curateOutcomeBuildItem, outputTargetBuildItem, transformedClasses, applicationArchivesBuildItem,
- packageConfig, applicationInfo, generatedClasses, generatedResources);
+ packageConfig, applicationInfo, generatedClasses, generatedResources,
+ mainClassBuildItem);
} else {
return buildThinJar(curateOutcomeBuildItem, outputTargetBuildItem, transformedClasses, applicationArchivesBuildItem,
- packageConfig, applicationInfo, generatedClasses, generatedResources);
+ packageConfig, applicationInfo, generatedClasses, generatedResources, mainClassBuildItem);
}
}
@@ -161,7 +164,8 @@ private JarBuildItem buildUberJar(CurateOutcomeBuildItem curateOutcomeBuildItem,
PackageConfig packageConfig,
ApplicationInfoBuildItem applicationInfo,
List generatedClasses,
- List generatedResources) throws Exception {
+ List generatedResources,
+ MainClassBuildItem mainClassBuildItem) throws Exception {
//for uberjars we move the original jar, so there is only a single jar in the output directory
Path standardJar = outputTargetBuildItem.getOutputDirectory().resolve(outputTargetBuildItem.getBaseName() + ".jar");
@@ -194,7 +198,8 @@ private JarBuildItem buildUberJar(CurateOutcomeBuildItem curateOutcomeBuildItem,
AppArtifact appArtifact = curateOutcomeBuildItem.getEffectiveModel().getAppArtifact();
// the manifest needs to be the first entry in the jar, otherwise JarInputStream does not work properly
// see https://bugs.openjdk.java.net/browse/JDK-8031748
- generateManifest(runnerZipFs, classPath.toString(), packageConfig, appArtifact, applicationInfo);
+ generateManifest(runnerZipFs, classPath.toString(), packageConfig, appArtifact, mainClassBuildItem,
+ applicationInfo);
for (AppDependency appDep : appDeps) {
final AppArtifact depArtifact = appDep.getArtifact();
@@ -310,7 +315,8 @@ private JarBuildItem buildThinJar(CurateOutcomeBuildItem curateOutcomeBuildItem,
PackageConfig packageConfig,
ApplicationInfoBuildItem applicationInfo,
List generatedClasses,
- List generatedResources) throws Exception {
+ List generatedResources,
+ MainClassBuildItem mainClassBuildItem) throws Exception {
Path runnerJar = outputTargetBuildItem.getOutputDirectory()
.resolve(outputTargetBuildItem.getBaseName() + packageConfig.runnerSuffix + ".jar");
@@ -324,7 +330,7 @@ private JarBuildItem buildThinJar(CurateOutcomeBuildItem curateOutcomeBuildItem,
log.info("Building thin jar: " + runnerJar);
doThinJarGeneration(curateOutcomeBuildItem, transformedClasses, applicationArchivesBuildItem, applicationInfo,
- packageConfig, generatedResources, libDir, generatedClasses, runnerZipFs);
+ packageConfig, generatedResources, libDir, generatedClasses, runnerZipFs, mainClassBuildItem);
}
runnerJar.toFile().setReadable(true, false);
@@ -344,7 +350,8 @@ public NativeImageSourceJarBuildItem buildNativeImageJar(CurateOutcomeBuildItem
PackageConfig packageConfig,
List generatedClasses,
List nativeImageResources,
- List generatedResources) throws Exception {
+ List generatedResources,
+ MainClassBuildItem mainClassBuildItem) throws Exception {
Path thinJarDirectory = outputTargetBuildItem.getOutputDirectory()
.resolve(outputTargetBuildItem.getBaseName() + "-native-image-source-jar");
IoUtils.recursiveDelete(thinJarDirectory);
@@ -365,7 +372,7 @@ public NativeImageSourceJarBuildItem buildNativeImageJar(CurateOutcomeBuildItem
log.info("Building native image source jar: " + runnerJar);
doThinJarGeneration(curateOutcomeBuildItem, transformedClasses, applicationArchivesBuildItem, applicationInfo,
- packageConfig, generatedResources, libDir, allClasses, runnerZipFs);
+ packageConfig, generatedResources, libDir, allClasses, runnerZipFs, mainClassBuildItem);
}
runnerJar.toFile().setReadable(true, false);
return new NativeImageSourceJarBuildItem(runnerJar, libDir);
@@ -426,7 +433,8 @@ private void doThinJarGeneration(CurateOutcomeBuildItem curateOutcomeBuildItem,
List generatedResources,
Path libDir,
List allClasses,
- FileSystem runnerZipFs)
+ FileSystem runnerZipFs,
+ MainClassBuildItem mainClassBuildItem)
throws BootstrapDependencyProcessingException, AppModelResolverException, IOException {
final Map seen = new HashMap<>();
final StringBuilder classPath = new StringBuilder();
@@ -440,7 +448,7 @@ private void doThinJarGeneration(CurateOutcomeBuildItem curateOutcomeBuildItem,
AppArtifact appArtifact = curateOutcomeBuildItem.getEffectiveModel().getAppArtifact();
// the manifest needs to be the first entry in the jar, otherwise JarInputStream does not work properly
// see https://bugs.openjdk.java.net/browse/JDK-8031748
- generateManifest(runnerZipFs, classPath.toString(), packageConfig, appArtifact, applicationInfo);
+ generateManifest(runnerZipFs, classPath.toString(), packageConfig, appArtifact, mainClassBuildItem, applicationInfo);
copyCommonContent(runnerZipFs, services, applicationArchivesBuildItem, transformedClasses, allClasses,
generatedResources, seen);
}
@@ -606,6 +614,7 @@ private void filterZipFile(Path resolvedDep, Path targetPath, Set transf
* Otherwise this manifest manipulation will be useless.
*/
private void generateManifest(FileSystem runnerZipFs, final String classPath, PackageConfig config, AppArtifact appArtifact,
+ MainClassBuildItem mainClassBuildItem,
ApplicationInfoBuildItem applicationInfo)
throws IOException {
final Path manifestPath = runnerZipFs.getPath("META-INF", "MANIFEST.MF");
@@ -628,11 +637,11 @@ private void generateManifest(FileSystem runnerZipFs, final String classPath, Pa
attributes.put(Attributes.Name.CLASS_PATH, classPath);
if (attributes.containsKey(Attributes.Name.MAIN_CLASS)) {
String existingMainClass = attributes.getValue(Attributes.Name.MAIN_CLASS);
- if (!config.mainClass.equals(existingMainClass)) {
+ if (!mainClassBuildItem.getClassName().equals(existingMainClass)) {
log.warn("Your MANIFEST.MF already defined a MAIN_CLASS entry. Quarkus has overwritten your existing entry.");
}
}
- attributes.put(Attributes.Name.MAIN_CLASS, config.mainClass);
+ attributes.put(Attributes.Name.MAIN_CLASS, mainClassBuildItem.getClassName());
if (config.manifest.addImplementationEntries && !attributes.containsKey(Attributes.Name.IMPLEMENTATION_TITLE)) {
String name = ApplicationInfoBuildItem.UNSET_VALUE.equals(applicationInfo.getName()) ? appArtifact.getArtifactId()
: applicationInfo.getName();
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java
index 472933b724c58..1e81897da9378 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java
@@ -5,20 +5,26 @@
import java.io.File;
import java.lang.reflect.Modifier;
+import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.graalvm.nativeimage.ImageInfo;
+import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.DotName;
import org.jboss.logging.Logger;
import io.quarkus.builder.Version;
import io.quarkus.deployment.GeneratedClassGizmoAdaptor;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem;
import io.quarkus.deployment.builditem.ApplicationClassNameBuildItem;
import io.quarkus.deployment.builditem.ApplicationInfoBuildItem;
import io.quarkus.deployment.builditem.BytecodeRecorderObjectLoaderBuildItem;
+import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.JavaLibraryPathAdditionalPathBuildItem;
@@ -29,7 +35,9 @@
import io.quarkus.deployment.builditem.SslTrustStoreSystemPropertyBuildItem;
import io.quarkus.deployment.builditem.StaticBytecodeRecorderBuildItem;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.configuration.RunTimeConfigurationGenerator;
+import io.quarkus.deployment.pkg.PackageConfig;
import io.quarkus.deployment.recording.BytecodeRecorderImpl;
import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.CatchBlockCreator;
@@ -43,25 +51,27 @@
import io.quarkus.runtime.Application;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.NativeImageRuntimePropertiesRecorder;
+import io.quarkus.runtime.Quarkus;
+import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.StartupContext;
import io.quarkus.runtime.StartupTask;
import io.quarkus.runtime.Timing;
+import io.quarkus.runtime.annotations.DefaultMain;
import io.quarkus.runtime.configuration.ProfileManager;
class MainClassBuildStep {
- private static final String APP_CLASS = "io.quarkus.runner.ApplicationImpl";
- private static final String MAIN_CLASS = "io.quarkus.runner.GeneratedMain";
- private static final String STARTUP_CONTEXT = "STARTUP_CONTEXT";
- private static final String LOG = "LOG";
- private static final String JAVA_LIBRARY_PATH = "java.library.path";
- private static final String JAVAX_NET_SSL_TRUST_STORE = "javax.net.ssl.trustStore";
+ static final String MAIN_CLASS = "io.quarkus.runner.GeneratedMain";
+ static final String STARTUP_CONTEXT = "STARTUP_CONTEXT";
+ static final String LOG = "LOG";
+ static final String JAVA_LIBRARY_PATH = "java.library.path";
+ static final String JAVAX_NET_SSL_TRUST_STORE = "javax.net.ssl.trustStore";
- private static final FieldDescriptor STARTUP_CONTEXT_FIELD = FieldDescriptor.of(APP_CLASS, STARTUP_CONTEXT,
+ private static final FieldDescriptor STARTUP_CONTEXT_FIELD = FieldDescriptor.of(Application.APP_CLASS_NAME, STARTUP_CONTEXT,
StartupContext.class);
@BuildStep
- MainClassBuildItem build(List staticInitTasks,
+ void build(List staticInitTasks,
List substitutions,
List mainMethod,
List properties,
@@ -74,11 +84,11 @@ MainClassBuildItem build(List staticInitTasks,
LaunchModeBuildItem launchMode,
ApplicationInfoBuildItem applicationInfo) {
- appClassNameProducer.produce(new ApplicationClassNameBuildItem(APP_CLASS));
+ appClassNameProducer.produce(new ApplicationClassNameBuildItem(Application.APP_CLASS_NAME));
// Application class
GeneratedClassGizmoAdaptor gizmoOutput = new GeneratedClassGizmoAdaptor(generatedClass, true);
- ClassCreator file = new ClassCreator(gizmoOutput, APP_CLASS, null,
+ ClassCreator file = new ClassCreator(gizmoOutput, Application.APP_CLASS_NAME, null,
Application.class.getName());
// Application class: static init
@@ -176,6 +186,11 @@ MainClassBuildItem build(List staticInitTasks,
mv.invokeStaticMethod(ofMethod(Timing.class, "mainStarted", void.class));
startupContext = mv.readStaticField(scField.getFieldDescriptor());
+ //now set the command line arguments
+ mv.invokeVirtualMethod(
+ MethodDescriptor.ofMethod(StartupContext.class, "setCommandLineArguments", void.class, String[].class),
+ startupContext, mv.getMethodParam(0));
+
tryBlock = mv.tryBlock();
for (MainBytecodeRecorderBuildItem holder : mainMethod) {
@@ -216,31 +231,72 @@ MainClassBuildItem build(List staticInitTasks,
mv.invokeVirtualMethod(ofMethod(StartupContext.class, "close", void.class), startupContext);
mv.returnValue(null);
+ // getName method
+ mv = file.getMethodCreator("getName", String.class);
+ mv.returnValue(mv.load(applicationInfo.getName()));
+
// Finish application class
file.close();
+ }
- // Main class
-
- file = new ClassCreator(gizmoOutput, MAIN_CLASS, null,
- Object.class.getName());
-
- mv = file.getMethodCreator("main", void.class, String[].class);
- mv.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
-
- final ResultHandle appClassInstance = mv.newInstance(ofConstructor(APP_CLASS));
-
- // Set the application name
- mv.invokeVirtualMethod(ofMethod(Application.class, "setName", void.class, String.class), appClassInstance,
- mv.load(applicationInfo.getName()));
-
- // run the app
- mv.invokeVirtualMethod(ofMethod(Application.class, "run", void.class, String[].class), appClassInstance,
- mv.getMethodParam(0));
-
- mv.returnValue(null);
+ @BuildStep
+ public MainClassBuildItem mainClassBuildStep(BuildProducer generatedClass,
+ ApplicationArchivesBuildItem applicationArchivesBuildItem,
+ CombinedIndexBuildItem combinedIndexBuildItem,
+ PackageConfig packageConfig) {
+ String mainClassName = MAIN_CLASS;
+ if (packageConfig.mainClass.isPresent()) {
+ mainClassName = packageConfig.mainClass.get();
+ } else {
+ Collection defaultMains = applicationArchivesBuildItem.getRootArchive().getIndex()
+ .getAnnotations(DotName.createSimple(DefaultMain.class.getName()));
+ if (defaultMains.size() > 1) {
+ throw new RuntimeException(
+ "More than one @DefaultMain method found in application, could not determine main class to use"
+ + defaultMains);
+ } else if (!defaultMains.isEmpty()) {
+ mainClassName = defaultMains.iterator().next().target().asClass().name().toString();
+ }
+ }
+ if (mainClassName.equals(MAIN_CLASS)) {
+ //generate a main that just runs the app, the user has not supplied a main class
+ ClassCreator file = new ClassCreator(new GeneratedClassGizmoAdaptor(generatedClass, true), MAIN_CLASS, null,
+ Object.class.getName());
+
+ MethodCreator mv = file.getMethodCreator("main", void.class, String[].class);
+ mv.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
+ mv.invokeStaticMethod(MethodDescriptor.ofMethod(Quarkus.class, "run", void.class, String[].class),
+ mv.getMethodParam(0));
+ mv.returnValue(null);
+
+ file.close();
+ } else {
+ Collection impls = combinedIndexBuildItem.getIndex()
+ .getAllKnownImplementors(DotName.createSimple(QuarkusApplication.class.getName()));
+ boolean found = false;
+ for (ClassInfo i : impls) {
+ if (i.name().toString().equals(mainClassName)) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ //this is QuarkusApplication, generate a real main to run it
+ ClassCreator file = new ClassCreator(new GeneratedClassGizmoAdaptor(generatedClass, true), MAIN_CLASS, null,
+ Object.class.getName());
+
+ MethodCreator mv = file.getMethodCreator("main", void.class, String[].class);
+ mv.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
+ mv.invokeStaticMethod(MethodDescriptor.ofMethod(Quarkus.class, "run", void.class, Class.class, String[].class),
+ mv.loadClass(mainClassName),
+ mv.getMethodParam(0));
+ mv.returnValue(null);
+ file.close();
+ mainClassName = MAIN_CLASS;
+ }
+ }
- file.close();
- return new MainClassBuildItem(MAIN_CLASS);
+ return new MainClassBuildItem(mainClassName);
}
private void writeRecordedBytecode(BytecodeRecorderImpl recorder, String fallbackGeneratedStartupTaskClassName,
@@ -269,4 +325,13 @@ private void writeRecordedBytecode(BytecodeRecorderImpl recorder, String fallbac
startupContext);
}
+ /**
+ * registers the generated application class for reflection, needed when launching via the Quarkus launcher
+ *
+ */
+ @BuildStep
+ ReflectiveClassBuildItem applicationReflection() {
+ return new ReflectiveClassBuildItem(false, false, Application.APP_CLASS_NAME);
+ }
+
}
diff --git a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java
index 0f98984bfea9c..32e9df5574393 100644
--- a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java
+++ b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java
@@ -32,6 +32,8 @@
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.LiveReloadBuildItem;
+import io.quarkus.deployment.builditem.MainClassBuildItem;
+import io.quarkus.deployment.builditem.RawCommandLineArgumentsBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem;
import io.quarkus.deployment.pkg.builditem.JarBuildItem;
@@ -93,7 +95,8 @@ public StartupActionImpl createInitialRuntimeApplication() {
throw new IllegalStateException("Cannot launch a runtime application with NORMAL launch mode");
}
BuildResult result = runAugment(true, Collections.emptySet(), GeneratedClassBuildItem.class,
- GeneratedResourceBuildItem.class, BytecodeTransformerBuildItem.class, ApplicationClassNameBuildItem.class);
+ GeneratedResourceBuildItem.class, BytecodeTransformerBuildItem.class, ApplicationClassNameBuildItem.class,
+ MainClassBuildItem.class);
return new StartupActionImpl(curatedApplication, result);
}
@@ -134,6 +137,7 @@ public BuildResult runCustomAction(Consumer chainBuild, Consu
.addInitial(ShutdownContextBuildItem.class)
.addInitial(LaunchModeBuildItem.class)
.addInitial(LiveReloadBuildItem.class)
+ .addInitial(RawCommandLineArgumentsBuildItem.class)
.addFinal(ConfigDescriptionBuildItem.class);
chainBuild.accept(chainBuilder);
@@ -142,6 +146,7 @@ public BuildResult runCustomAction(Consumer chainBuild, Consu
BuildExecutionBuilder execBuilder = chain.createExecutionBuilder("main")
.produce(new LaunchModeBuildItem(launchMode))
.produce(new ShutdownContextBuildItem())
+ .produce(new RawCommandLineArgumentsBuildItem())
.produce(new LiveReloadBuildItem());
executionBuild.accept(execBuilder);
return execBuilder
diff --git a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java
index 35f74ffad8ffd..be3d609297a84 100644
--- a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java
+++ b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java
@@ -11,6 +11,7 @@
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
+import java.util.function.Consumer;
import org.jboss.logging.Logger;
import org.objectweb.asm.ClassVisitor;
@@ -26,7 +27,10 @@
import io.quarkus.deployment.builditem.DeploymentClassLoaderBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
+import io.quarkus.deployment.builditem.MainClassBuildItem;
import io.quarkus.deployment.configuration.RunTimeConfigurationGenerator;
+import io.quarkus.dev.appstate.ApplicationStateNotification;
+import io.quarkus.runtime.Quarkus;
public class StartupActionImpl implements StartupAction {
@@ -43,10 +47,75 @@ public StartupActionImpl(CuratedApplication curatedApplication, BuildResult buil
}
/**
- * Runs the application, and returns a handle that can be used to shut it down.
+ * Runs the application by running the main method of the main class. As this is a blocking method a new
+ * thread is created to run this task.
+ *
+ * Before this method is called an appropriate exit handler will likely need to
+ * be set in {@link io.quarkus.runtime.ApplicationLifecycleManager#setDefaultExitCodeHandler(Consumer)}
+ * of the JVM will exit when the app stops.
*/
- public RunningQuarkusApplication run(String... args) throws Exception {
+ public RunningQuarkusApplication runMainClass(String... args) throws Exception {
//first
+ QuarkusClassLoader runtimeClassLoader = createRuntimeClassLoader();
+
+ //we have our class loaders
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(runtimeClassLoader);
+ final String className = buildResult.consume(MainClassBuildItem.class).getClassName();
+ try {
+ // force init here
+ Class> appClass = Class.forName(className, true, runtimeClassLoader);
+ Method start = appClass.getMethod("main", String[].class);
+ Thread t = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Thread.currentThread().setContextClassLoader(runtimeClassLoader);
+ try {
+ start.invoke(null, (Object) args);
+ } catch (Throwable e) {
+ log.error("Error running Quarkus", e);
+ ApplicationStateNotification.notifyStartupComplete(e.getCause());
+ }
+ }
+ }, "Quarkus Main Thread");
+ t.start();
+ ApplicationStateNotification.waitForApplicationStart();
+ return new RunningQuarkusApplicationImpl(new Closeable() {
+ @Override
+ public void close() throws IOException {
+ try {
+ runtimeClassLoader.loadClass(Quarkus.class.getName()).getMethod("blockingExit").invoke(null);
+ } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException
+ | ClassNotFoundException e) {
+ log.error("Failed to stop Quarkus", e);
+ } finally {
+ ForkJoinClassLoading.setForkJoinClassLoader(ClassLoader.getSystemClassLoader());
+ if (curatedApplication.getQuarkusBootstrap().getMode() == QuarkusBootstrap.Mode.TEST) {
+ //for tests we just always shut down the curated application, as it is only used once
+ //dev mode might be about to restart, so we leave it
+ curatedApplication.close();
+ }
+ }
+ }
+ }, runtimeClassLoader);
+ } catch (Throwable t) {
+ // todo: dev mode expects run time config to be available immediately even if static init didn't complete.
+ try {
+ final Class> configClass = Class.forName(RunTimeConfigurationGenerator.CONFIG_CLASS_NAME, true,
+ runtimeClassLoader);
+ configClass.getDeclaredMethod(RunTimeConfigurationGenerator.C_CREATE_BOOTSTRAP_CONFIG.getName())
+ .invoke(null);
+ } catch (Throwable t2) {
+ t.addSuppressed(t2);
+ }
+ throw t;
+ } finally {
+ Thread.currentThread().setContextClassLoader(old);
+ }
+
+ }
+
+ private QuarkusClassLoader createRuntimeClassLoader() {
Map>> bytecodeTransformers = extractTransformers();
QuarkusClassLoader baseClassLoader = curatedApplication.getBaseRuntimeClassLoader();
ClassLoader transformerClassLoader = buildResult.consume(DeploymentClassLoaderBuildItem.class).getClassLoader();
@@ -68,6 +137,15 @@ public RunningQuarkusApplication run(String... args) throws Exception {
runtimeClassLoader = baseClassLoader;
}
ForkJoinClassLoading.setForkJoinClassLoader(runtimeClassLoader);
+ return runtimeClassLoader;
+ }
+
+ /**
+ * Runs the application, and returns a handle that can be used to shut it down.
+ */
+ public RunningQuarkusApplication run(String... args) throws Exception {
+ //first
+ QuarkusClassLoader runtimeClassLoader = createRuntimeClassLoader();
//we have our class loaders
ClassLoader old = Thread.currentThread().getContextClassLoader();
diff --git a/core/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.dev.CompilationProvider b/core/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.dev.CompilationProvider
new file mode 100644
index 0000000000000..2fc24c15b30d0
--- /dev/null
+++ b/core/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.dev.CompilationProvider
@@ -0,0 +1 @@
+io.quarkus.deployment.dev.JavaCompilationProvider
\ No newline at end of file
diff --git a/core/devmode/src/test/java/io/quarkus/dev/CompilerFlagsTest.java b/core/deployment/src/test/java/io/quarkus/deployment/dev/CompilerFlagsTest.java
similarity index 99%
rename from core/devmode/src/test/java/io/quarkus/dev/CompilerFlagsTest.java
rename to core/deployment/src/test/java/io/quarkus/deployment/dev/CompilerFlagsTest.java
index dcd1c83f7f964..e2619a773df4e 100644
--- a/core/devmode/src/test/java/io/quarkus/dev/CompilerFlagsTest.java
+++ b/core/deployment/src/test/java/io/quarkus/deployment/dev/CompilerFlagsTest.java
@@ -1,4 +1,4 @@
-package io.quarkus.dev;
+package io.quarkus.deployment.dev;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
diff --git a/core/devmode-spi/src/main/java/io/quarkus/dev/appstate/ApplicationStateNotification.java b/core/devmode-spi/src/main/java/io/quarkus/dev/appstate/ApplicationStateNotification.java
new file mode 100644
index 0000000000000..d5c4d38baa86d
--- /dev/null
+++ b/core/devmode-spi/src/main/java/io/quarkus/dev/appstate/ApplicationStateNotification.java
@@ -0,0 +1,52 @@
+package io.quarkus.dev.appstate;
+
+/**
+ * A class that allows for access to the application state, even from outside the runtime class loader.
+ *
+ * This should generally only be used by dev mode internals that need information about the current
+ * application state.
+ *
+ * This class makes not attempt to verify that an application is starting/stopping when the
+ * wait methods are called, this should only be called by a client that is controlling the Quarkus
+ * lifecycle, and so knows what the current lifecycle state is.
+ */
+public class ApplicationStateNotification {
+
+ private static boolean started = false;
+ private static Throwable startupProblem;
+
+ public static synchronized void notifyStartupComplete(Throwable sp) {
+ started = startupProblem == null;
+ startupProblem = sp;
+ ApplicationStateNotification.class.notifyAll();
+ }
+
+ public static synchronized void notifyApplicationStopped() {
+ started = false;
+ startupProblem = null;
+ ApplicationStateNotification.class.notifyAll();
+ }
+
+ public static synchronized void waitForApplicationStart() {
+ while (!started && startupProblem == null) {
+ try {
+ ApplicationStateNotification.class.wait();
+ } catch (InterruptedException e) {
+ //ignore
+ }
+ }
+ if (startupProblem != null) {
+ throw new RuntimeException(startupProblem);
+ }
+ }
+
+ public static synchronized void waitForApplicationStop() {
+ while (started) {
+ try {
+ ApplicationStateNotification.class.wait();
+ } catch (InterruptedException e) {
+ //ignore
+ }
+ }
+ }
+}
diff --git a/core/devmode/pom.xml b/core/devmode/pom.xml
deleted file mode 100644
index 2b6bbd6ca895e..0000000000000
--- a/core/devmode/pom.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
- quarkus-build-parent
- io.quarkus
- 999-SNAPSHOT
- ../../build-parent/pom.xml
-
- 4.0.0
-
- quarkus-development-mode
- Quarkus - Development mode
-
-
-
- io.quarkus
- quarkus-core-deployment
-
-
- io.quarkus
- quarkus-development-mode-spi
-
-
- org.jboss.logmanager
- jboss-logmanager-embedded
-
-
-
-
- org.junit.jupiter
- junit-jupiter-api
- test
-
-
- org.junit.jupiter
- junit-jupiter-params
- test
-
-
- org.junit.jupiter
- junit-jupiter-engine
- test
-
-
-
-
-
-
- maven-compiler-plugin
-
-
-
- io.quarkus
- quarkus-extension-processor
- ${project.version}
-
-
-
-
-
-
-
-
diff --git a/core/devmode/src/main/resources/META-INF/services/io.quarkus.dev.CompilationProvider b/core/devmode/src/main/resources/META-INF/services/io.quarkus.dev.CompilationProvider
deleted file mode 100644
index 0fdad837170e6..0000000000000
--- a/core/devmode/src/main/resources/META-INF/services/io.quarkus.dev.CompilationProvider
+++ /dev/null
@@ -1 +0,0 @@
-io.quarkus.dev.JavaCompilationProvider
\ No newline at end of file
diff --git a/core/launcher/pom.xml b/core/launcher/pom.xml
new file mode 100644
index 0000000000000..c134b926c3718
--- /dev/null
+++ b/core/launcher/pom.xml
@@ -0,0 +1,59 @@
+
+
+
+ quarkus-build-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../../build-parent/pom.xml
+
+ 4.0.0
+
+ quarkus-ide-launcher
+ Quarkus - IDE Launcher
+
+
+
+ io.quarkus
+ quarkus-bootstrap-core
+
+
+
+
+
+
+ maven-shade-plugin
+
+
+
+ package
+
+ shade
+
+
+ true
+ true
+
+
+ *:*
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/launcher/src/main/java/io/quarkus/launcher/QuarkusLauncher.java b/core/launcher/src/main/java/io/quarkus/launcher/QuarkusLauncher.java
new file mode 100644
index 0000000000000..b05742e686b38
--- /dev/null
+++ b/core/launcher/src/main/java/io/quarkus/launcher/QuarkusLauncher.java
@@ -0,0 +1,50 @@
+package io.quarkus.launcher;
+
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.function.Consumer;
+
+import io.quarkus.bootstrap.app.CuratedApplication;
+import io.quarkus.bootstrap.app.QuarkusBootstrap;
+
+/**
+ * IDE entry point.
+ *
+ * This has a number of hacks to make it work, and is always going to be a bit fragile, as it is hard to make something
+ * that will work 100% of the time as we just don't have enough information.
+ *
+ * The launcher module has all it's dependencies shaded, so it is effectively self contained. This allows deployment time
+ * code to not leak into runtime code, as the launcher artifact is explicitly excluded from the production build via a
+ * hard coded exclusion.
+ *
+ */
+public class QuarkusLauncher {
+
+ public static void launch(String callingClass, String quarkusApplication, Consumer exitHandler, String... args) {
+
+ String classResource = callingClass.replace(".", "/") + ".class";
+ URL resource = Thread.currentThread().getContextClassLoader().getResource(classResource);
+ String path = resource.getPath();
+ path = path.substring(0, path.length() - classResource.length());
+
+ Path appClasses = Paths.get(path);
+ if (quarkusApplication != null) {
+ System.setProperty("quarkus.package.main-class", quarkusApplication);
+ }
+
+ try {
+ //todo : proper support for everything
+ CuratedApplication app = QuarkusBootstrap.builder(appClasses)
+ .setBaseClassLoader(QuarkusLauncher.class.getClassLoader())
+ .setMode(QuarkusBootstrap.Mode.DEV)
+ .build().bootstrap();
+ app.createAugmentor()
+ .createInitialRuntimeApplication().runMainClass(args);
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/core/pom.xml b/core/pom.xml
index 3f7685bddce81..04a21b47b35a1 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -17,10 +17,10 @@
deployment
runtime
processor
- devmode
builder
test-extension
devmode-spi
+ launcher
diff --git a/core/runtime/pom.xml b/core/runtime/pom.xml
index a959a6da4abdd..7a34e337e8426 100644
--- a/core/runtime/pom.xml
+++ b/core/runtime/pom.xml
@@ -15,6 +15,12 @@
Quarkus - Core - Runtime
+
+
+ io.quarkus
+ quarkus-bootstrap-core
+ provided
+
jakarta.annotation
jakarta.annotation-api
@@ -27,6 +33,14 @@
jakarta.inject
jakarta.inject-api
+
+ io.quarkus
+ quarkus-ide-launcher
+
+
+ io.quarkus
+ quarkus-development-mode-spi
+
io.smallrye.config
smallrye-config
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/Application.java b/core/runtime/src/main/java/io/quarkus/runtime/Application.java
index be4f43fd60764..73547954da406 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/Application.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/Application.java
@@ -3,44 +3,42 @@
import java.io.Closeable;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.LockSupport;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
-import org.graalvm.nativeimage.ImageInfo;
import org.wildfly.common.Assert;
import org.wildfly.common.lock.Locks;
-import com.oracle.svm.core.OS;
-
-import io.quarkus.runtime.graal.DiagnosticPrinter;
+import io.quarkus.dev.appstate.ApplicationStateNotification;
import io.quarkus.runtime.shutdown.ShutdownRecorder;
-import sun.misc.Signal;
-import sun.misc.SignalHandler;
/**
* The application base class, which is extended and implemented by a generated class which implements the application
- * setup logic. The base class does some basic error checking.
+ * setup logic. The base class does some basic error checking, and maintains the application state.
+ *
+ * Note that this class does not manage the application lifecycle in any way, it is solely responsible for starting and
+ * stopping the application.
+ *
*/
@SuppressWarnings("restriction")
public abstract class Application implements Closeable {
// WARNING: do not inject a logger here, it's too early: the log manager has not been properly set up yet
- private static final String DISABLE_SIGNAL_HANDLERS = "DISABLE_SIGNAL_HANDLERS";
+ /**
+ * The name of the generated application class
+ */
+ public static final String APP_CLASS_NAME = "io.quarkus.runner.ApplicationImpl";
private static final int ST_INITIAL = 0;
private static final int ST_STARTING = 1;
private static final int ST_STARTED = 2;
private static final int ST_STOPPING = 3;
private static final int ST_STOPPED = 4;
- private static final int ST_EXIT = 5;
private final Lock stateLock = Locks.reentrantLock();
private final Condition stateCond = stateLock.newCondition();
- private String name;
private int state = ST_INITIAL;
- private volatile boolean shutdownRequested;
private static volatile Application currentApplication;
/**
@@ -96,12 +94,14 @@ public final void start(String[] args) {
} finally {
stateLock.unlock();
}
+ ApplicationStateNotification.notifyStartupComplete(t);
throw t;
}
stateLock.lock();
try {
state = ST_STARTED;
stateCond.signalAll();
+ ApplicationStateNotification.notifyStartupComplete(null);
} finally {
stateLock.unlock();
}
@@ -157,7 +157,6 @@ public final void stop() {
break;
}
case ST_STOPPED:
- case ST_EXIT:
return; // all good
default:
throw Assert.impossibleSwitchCase(state);
@@ -175,8 +174,9 @@ public final void stop() {
stateLock.lock();
try {
state = ST_STOPPED;
- Timing.printStopTime(name);
+ Timing.printStopTime(getName());
stateCond.signalAll();
+ ApplicationStateNotification.notifyApplicationStopped();
} finally {
stateLock.unlock();
}
@@ -189,68 +189,7 @@ public static Application currentApplication() {
protected abstract void doStop();
- public void setName(String name) {
- this.name = name;
- }
-
- /**
- * Run the application as if it were in a standalone JVM.
- */
- public final void run(String[] args) {
- try {
- if (ImageInfo.inImageRuntimeCode() && System.getenv(DISABLE_SIGNAL_HANDLERS) == null) {
- final SignalHandler handler = new SignalHandler() {
- @Override
- public void handle(Signal signal) {
- System.exit(signal.getNumber() + 0x80);
- }
- };
- final SignalHandler quitHandler = new SignalHandler() {
- @Override
- public void handle(Signal signal) {
- DiagnosticPrinter.printDiagnostics(System.out);
- }
- };
- handleSignal("INT", handler);
- handleSignal("TERM", handler);
- // the HUP and QUIT signals are not defined for the Windows OpenJDK implementation:
- // https://hg.openjdk.java.net/jdk8u/jdk8u-dev/hotspot/file/7d5c800dae75/src/os/windows/vm/jvm_windows.cpp
- if (OS.getCurrent() == OS.WINDOWS) {
- handleSignal("BREAK", quitHandler);
- } else {
- handleSignal("HUP", handler);
- handleSignal("QUIT", quitHandler);
- }
- }
-
- final ShutdownHookThread shutdownHookThread = new ShutdownHookThread(Thread.currentThread());
- Runtime.getRuntime().addShutdownHook(shutdownHookThread);
- start(args);
- try {
- while (!shutdownRequested) {
- Thread.interrupted();
- LockSupport.park(shutdownHookThread);
- }
- } finally {
- stop();
- }
- } finally {
- exit();
- }
- }
-
- private void exit() {
- stateLock.lock();
- try {
- System.out.flush();
- System.err.flush();
- state = ST_EXIT;
- stateCond.signalAll();
- // code beyond this point may not run
- } finally {
- stateLock.unlock();
- }
- }
+ public abstract String getName();
private static IllegalStateException interruptedOnAwaitStart() {
return new IllegalStateException("Interrupted while waiting for another thread to start the application");
@@ -260,42 +199,29 @@ private static IllegalStateException interruptedOnAwaitStop() {
return new IllegalStateException("Interrupted while waiting for another thread to stop the application");
}
- private static void handleSignal(final String signal, final SignalHandler handler) {
+ public void awaitShutdown() {
+ final Lock stateLock = this.stateLock;
+ stateLock.lock();
try {
- Signal.handle(new Signal(signal), handler);
- } catch (IllegalArgumentException ignored) {
- // Do nothing
- }
- }
-
- class ShutdownHookThread extends Thread {
- private final Thread mainThread;
-
- ShutdownHookThread(Thread mainThread) {
- super("Shutdown thread");
- this.mainThread = mainThread;
- setDaemon(false);
- }
-
- @Override
- public void run() {
- shutdownRequested = true;
- LockSupport.unpark(mainThread);
- final Lock stateLock = Application.this.stateLock;
- final Condition stateCond = Application.this.stateCond;
- stateLock.lock();
- try {
- while (state != ST_EXIT) {
- stateCond.awaitUninterruptibly();
+ for (;;) {
+ switch (state) {
+ case ST_STOPPED:
+ return; // all good
+ default:
+ try {
+ stateCond.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw interruptedOnAwaitStop();
+ }
}
- } finally {
- stateLock.unlock();
}
+ } finally {
+ stateLock.unlock();
}
+ }
- @Override
- public String toString() {
- return getName();
- }
+ public boolean isStarted() {
+ return state == ST_STARTED;
}
}
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java
new file mode 100644
index 0000000000000..64a4fe949bb30
--- /dev/null
+++ b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java
@@ -0,0 +1,308 @@
+package io.quarkus.runtime;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.function.Consumer;
+
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.Any;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.CDI;
+
+import org.graalvm.nativeimage.ImageInfo;
+import org.jboss.logging.Logger;
+import org.wildfly.common.lock.Locks;
+
+import com.oracle.svm.core.OS;
+
+import io.quarkus.runtime.graal.DiagnosticPrinter;
+import sun.misc.Signal;
+import sun.misc.SignalHandler;
+
+/**
+ * Manages the lifecycle of a Quarkus application.
+ *
+ * The {@link Application} class is responsible for starting and stopping the application,
+ * but nothing else. This class can be used to run both persistent application that will run
+ * till they receive a signal, and command mode applications that will run until the main method
+ * returns. This class registers a shutdown hook to properly shut down the application, and handled
+ * exiting with the supplied exit code.
+ *
+ * This class should be used to run production and dev mode applications, while test use cases will
+ * likely want to just use {@link Application} directly.
+ *
+ * This class is static, there can only every be a single application instance running at any time.
+ *
+ */
+public class ApplicationLifecycleManager {
+
+ private static volatile Consumer defaultExitCodeHandler = new Consumer() {
+ @Override
+ public void accept(Integer integer) {
+ System.exit(integer);
+ }
+ };
+
+ private ApplicationLifecycleManager() {
+
+ }
+ // WARNING: do not inject a logger here, it's too early: the log manager has not been properly set up yet
+
+ private static final String DISABLE_SIGNAL_HANDLERS = "DISABLE_SIGNAL_HANDLERS";
+
+ //guard for all state
+ private static final Lock stateLock = Locks.reentrantLock();
+ private static final Condition stateCond = stateLock.newCondition();
+
+ private static int exitCode = -1;
+ private static boolean shutdownRequested;
+ private static Application currentApplication;
+ private static boolean hooksRegistered;
+ private static boolean vmShuttingDown;
+
+ public static final void run(Application application, String... args) {
+ run(application, null, null, args);
+ }
+
+ public static final void run(Application application, Class extends QuarkusApplication> quarkusApplication,
+ Consumer exitCodeHandler, String... args) {
+ stateLock.lock();
+ //in tests we might pass this method an already started application
+ //in this case we don't shut it down at the end
+ boolean alreadyStarted = application.isStarted();
+ if (!hooksRegistered) {
+ registerHooks();
+ hooksRegistered = true;
+ }
+ if (currentApplication != null && !shutdownRequested) {
+ throw new IllegalStateException("Quarkus already running");
+ }
+ try {
+ exitCode = -1;
+ shutdownRequested = false;
+ currentApplication = application;
+ } finally {
+ stateLock.unlock();
+ }
+ try {
+ application.start(args);
+ //now we are started, we either run the main application or just wait to exit
+ if (quarkusApplication != null) {
+ Set> beans = CDI.current().getBeanManager().getBeans(quarkusApplication, new Any.Literal());
+ Bean> bean = null;
+ for (Bean> i : beans) {
+ if (i.getBeanClass() == quarkusApplication) {
+ bean = i;
+ break;
+ }
+ }
+ QuarkusApplication instance;
+ if (bean == null) {
+ instance = quarkusApplication.newInstance();
+ } else {
+ CreationalContext> ctx = CDI.current().getBeanManager().createCreationalContext(bean);
+ instance = (QuarkusApplication) CDI.current().getBeanManager().getReference(bean,
+ quarkusApplication, ctx);
+ }
+ int result = -1;
+ try {
+ result = instance.run(args);//TODO: argument filtering?
+ } finally {
+ stateLock.lock();
+ try {
+ //now we exit
+ if (exitCode == -1 && result != -1) {
+ exitCode = result;
+ }
+ shutdownRequested = true;
+ stateCond.signalAll();
+ } finally {
+ stateLock.unlock();
+ }
+ }
+ } else {
+ stateLock.lock();
+ try {
+ while (!shutdownRequested) {
+ Thread.interrupted();
+ stateCond.await();
+ }
+ } finally {
+ stateLock.unlock();
+ }
+ }
+ } catch (Exception e) {
+ Logger.getLogger(Application.class).error("Error running Quarkus application", e);
+ stateLock.lock();
+ try {
+ shutdownRequested = true;
+ stateCond.signalAll();
+ } finally {
+ stateLock.unlock();
+ }
+ application.stop();
+ (exitCodeHandler == null ? defaultExitCodeHandler : exitCodeHandler).accept(1);
+ return;
+ }
+ if (!alreadyStarted) {
+ application.stop(); //this could have already been called
+ }
+ (exitCodeHandler == null ? defaultExitCodeHandler : exitCodeHandler).accept(getExitCode()); //this may not be called if shutdown was initiated by a signal
+ }
+
+ private static void registerHooks() {
+ if (ImageInfo.inImageRuntimeCode() && System.getenv(DISABLE_SIGNAL_HANDLERS) == null) {
+ registerSignalHandlers();
+ }
+ final ShutdownHookThread shutdownHookThread = new ShutdownHookThread();
+ Runtime.getRuntime().addShutdownHook(shutdownHookThread);
+ }
+
+ private static void registerSignalHandlers() {
+ final SignalHandler handler = new SignalHandler() {
+ @Override
+ public void handle(Signal signal) {
+ System.exit(signal.getNumber() + 0x80);
+ }
+ };
+ final SignalHandler quitHandler = new SignalHandler() {
+ @Override
+ public void handle(Signal signal) {
+ DiagnosticPrinter.printDiagnostics(System.out);
+ }
+ };
+ handleSignal("INT", handler);
+ handleSignal("TERM", handler);
+ // the HUP and QUIT signals are not defined for the Windows OpenJDK implementation:
+ // https://hg.openjdk.java.net/jdk8u/jdk8u-dev/hotspot/file/7d5c800dae75/src/os/windows/vm/jvm_windows.cpp
+ if (OS.getCurrent() == OS.WINDOWS) {
+ handleSignal("BREAK", quitHandler);
+ } else {
+ handleSignal("HUP", handler);
+ handleSignal("QUIT", quitHandler);
+ }
+ }
+
+ /**
+ *
+ * @return The current exit code that would be reported if the application exits
+ */
+ public static int getExitCode() {
+ return exitCode == -1 ? 0 : exitCode;
+ }
+
+ /**
+ * Exits without supplying an exit code.
+ *
+ * The application will exit with a code of 0 by default, however if this method is called it is still possible
+ * for a different exit code to be set.
+ */
+ public static void exit() {
+ exit(-1);
+ }
+
+ public static Consumer getDefaultExitCodeHandler() {
+ return defaultExitCodeHandler;
+ }
+
+ /**
+ *
+ * @return true
if the VM is shutting down
+ */
+ public static boolean isVmShuttingDown() {
+ return vmShuttingDown;
+ }
+
+ /**
+ * Sets the default exit code handler for application run through the run method
+ * that does not take an exit handler.
+ *
+ * By default this will just call System.exit, however this is not always
+ * what is wanted.
+ *
+ * @param defaultExitCodeHandler
+ */
+ public static void setDefaultExitCodeHandler(Consumer defaultExitCodeHandler) {
+ Objects.requireNonNull(defaultExitCodeHandler);
+ ApplicationLifecycleManager.defaultExitCodeHandler = defaultExitCodeHandler;
+ }
+
+ /**
+ * Signals that the application should exit with the given code.
+ *
+ * Note that the first positive exit code will 'win', so if the exit code
+ * has already been set then the exit code will be ignored.
+ *
+ * @param code The exit code
+ */
+ public static void exit(int code) {
+ stateLock.lock();
+ try {
+ if (code >= 0 && exitCode == -1) {
+ exitCode = code;
+ }
+ if (shutdownRequested) {
+ return;
+ }
+ shutdownRequested = true;
+ stateCond.signalAll();
+ } finally {
+ stateLock.unlock();
+ }
+ }
+
+ /**
+ * Waits for the shutdown process to be initiated.
+ */
+ public static void waitForExit() {
+ stateLock.lock();
+ try {
+ while (!shutdownRequested) {
+ stateCond.awaitUninterruptibly();
+ }
+ } finally {
+ stateLock.unlock();
+ }
+ }
+
+ static class ShutdownHookThread extends Thread {
+
+ ShutdownHookThread() {
+ super("Shutdown thread");
+ setDaemon(false);
+ }
+
+ @Override
+ public void run() {
+ stateLock.lock();
+ vmShuttingDown = true;
+ //we just request shutdown and unblock the main thread
+ //we let the application main thread take care of actually exiting
+ //TODO: if the main thread is not actively waiting to exit should we interrupt it?
+ shutdownRequested = true;
+ try {
+ stateCond.signalAll();
+ } finally {
+ stateLock.unlock();
+ }
+ currentApplication.awaitShutdown();
+ System.out.flush();
+ System.err.flush();
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+ }
+
+ private static void handleSignal(final String signal, final SignalHandler handler) {
+ try {
+ Signal.handle(new Signal(signal), handler);
+ } catch (IllegalArgumentException ignored) {
+ // Do nothing
+ }
+ }
+}
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/Quarkus.java b/core/runtime/src/main/java/io/quarkus/runtime/Quarkus.java
new file mode 100644
index 0000000000000..0d8e7375e3eb3
--- /dev/null
+++ b/core/runtime/src/main/java/io/quarkus/runtime/Quarkus.java
@@ -0,0 +1,168 @@
+package io.quarkus.runtime;
+
+import java.util.function.Consumer;
+
+import org.jboss.logging.Logger;
+
+import io.quarkus.launcher.QuarkusLauncher;
+
+/**
+ * The entry point for applications that use a main method. Quarkus will shut down when the main method returns.
+ *
+ * If this application has already been generated then it will be run directly, otherwise it will be launched
+ * in dev mode and augmentation will be done automatically.
+ *
+ * If an application does not want to immediatly shut down then {@link #waitForExit()} should be called, which
+ * will block until shutdown is initiated, either from an external signal or by a call to one of the exit methods.
+ *
+ * If no main class is specified then one is generated automatically that will simply wait to exit after Quarkus is booted.
+ *
+ */
+public class Quarkus {
+
+ //WARNING: this is too early to inject a logger
+ //private static final Logger log = Logger.getLogger(Quarkus.class);
+
+ /**
+ * Runs a quarkus application, that will run until the provided {@link QuarkusApplication} has completed.
+ *
+ * Note that if this is run from the IDE the application will run in a different class loader to the
+ * calling class. It is recommended that the calling class do no logic, and instead this logic should
+ * go into the QuarkusApplication.
+ *
+ * @param quarkusApplication The application to run, or null
+ * @param args The command line parameters
+ */
+ public static void run(Class extends QuarkusApplication> quarkusApplication, String... args) {
+ run(quarkusApplication, null, args);
+ }
+
+ /**
+ * Runs a quarkus application, that will run until the provided {@link QuarkusApplication} has completed.
+ *
+ * Note that if this is run from the IDE the application will run in a different class loader to the
+ * calling class. It is recommended that the calling class do no logic, and instead this logic should
+ * go into the QuarkusApplication.
+ *
+ * @param quarkusApplication The application to run, or null
+ * @param exitHandler The handler that is called with the exit code when the application has finished
+ * @param args The command line parameters
+ */
+ public static void run(Class extends QuarkusApplication> quarkusApplication, Consumer exitHandler,
+ String... args) {
+ try {
+ //production and common dev mode path
+ //we already have an application, run it directly
+ Class extends Application> appClass = (Class extends Application>) Class.forName(Application.APP_CLASS_NAME,
+ false, Thread.currentThread().getContextClassLoader());
+ Application application = appClass.newInstance();
+ ApplicationLifecycleManager.run(application, quarkusApplication, exitHandler, args);
+ return;
+ } catch (ClassNotFoundException e) {
+ //ignore, this happens when running in dev mode
+ } catch (Exception e) {
+ //TODO: exception mappers
+ Logger.getLogger(Quarkus.class).error("Error running Quarkus", e);
+ if (exitHandler != null) {
+ exitHandler.accept(1);
+ } else {
+ ApplicationLifecycleManager.getDefaultExitCodeHandler().accept(1);
+ }
+ return;
+ }
+
+ //dev mode path, i.e. launching from the IDE
+ //this is not the quarkus:dev path as it will augment before
+ //calling this method
+ launchFromIDE(quarkusApplication, exitHandler, args);
+
+ }
+
+ private static void launchFromIDE(Class extends QuarkusApplication> quarkusApplication, Consumer exitHandler,
+ String... args) {
+ //some trickery, get the class that has invoked us, and use this to figure out the
+ //classes root
+ StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+ int pos = 2;
+ while (stackTrace[pos].getClassName().equals(Quarkus.class.getName())) {
+ pos++;
+ }
+ String callingClass = stackTrace[pos].getClassName();
+ QuarkusLauncher.launch(callingClass, quarkusApplication == null ? null : quarkusApplication.getName(), exitHandler,
+ args);
+ }
+
+ /**
+ * Starts a quarkus application, that will run until it either receives a signal (e.g. user presses ctrl+c)
+ * or one of the exit methods is called.
+ *
+ * This method does not return, as System.exit() is called after the application is finished.
+ *
+ * @param args The command line parameters
+ */
+ public static void run(String... args) {
+ run(null, args);
+ }
+
+ /**
+ * Exits the application in an async manner. Calling this method
+ * will initiate the Quarkus shutdown process, and then immediately return.
+ *
+ * This method will unblock the {@link #waitForExit()} method.
+ *
+ * Note that if the main thread is executing a Quarkus application this will only take
+ * effect if {@link #waitForExit()} has been called, otherwise the application will continue
+ * to execute (i.e. this does not initiate the shutdown process, it just signals the main
+ * thread that the application is done so that shutdown can run when the main thread returns).
+ *
+ * The error code supplied here will override the value returned from the main application.
+ *
+ * @param code The exit code. This may be overridden if an exception occurs on shutdown
+ */
+ public static void asyncExit(int code) {
+ ApplicationLifecycleManager.exit(code);
+ }
+
+ /**
+ * Exits the application in an async manner. Calling this method
+ * will initiate the Quarkus shutdown process, and then immediately return.
+ *
+ * This method will unblock the {@link #waitForExit()} method.
+ *
+ * Note that if the main thread is executing a Quarkus application this will only take
+ * effect if {@link #waitForExit()} has been called, otherwise the application will continue
+ * to execute (i.e. this does not initiate the shutdown process, it just signals the main
+ * thread that the application is done so that shutdown can run when the main thread returns).
+ *
+ */
+ public static void asyncExit() {
+ ApplicationLifecycleManager.exit(-1);
+ }
+
+ /**
+ * Method that will block until the Quarkus shutdown process is initiated.
+ *
+ * Note that this unblocks as soon as the shutdown process starts, not after it
+ * has finished.
+ *
+ * {@link QuarkusApplication} implementations that wish to run some logic on startup, and
+ * then run should call this method.
+ */
+ public static void waitForExit() {
+ ApplicationLifecycleManager.waitForExit();
+
+ }
+
+ /**
+ * Starts the shutdown process, then waits for the application to shut down.
+ *
+ * Must not be called by the main thread, or a deadlock will result.
+ */
+ public static void blockingExit() {
+ Application app = Application.currentApplication();
+ asyncExit();
+ if (app != null) {
+ app.awaitShutdown();
+ }
+ }
+}
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java b/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java
new file mode 100644
index 0000000000000..350685123d513
--- /dev/null
+++ b/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java
@@ -0,0 +1,6 @@
+package io.quarkus.runtime;
+
+public interface QuarkusApplication {
+
+ int run(String... args) throws Exception;
+}
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/StartupContext.java b/core/runtime/src/main/java/io/quarkus/runtime/StartupContext.java
index be10ea5c5e492..bae846efad0ae 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/StartupContext.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/StartupContext.java
@@ -6,11 +6,14 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Supplier;
import org.jboss.logging.Logger;
public class StartupContext implements Closeable {
+ public static final String RAW_COMMAND_LINE_ARGS = StartupContext.class.getName() + ".raw-command-line-args";
+
private static final Logger LOG = Logger.getLogger(StartupContext.class);
private final Map values = new HashMap<>();
@@ -30,9 +33,19 @@ public void addLastShutdownTask(Runnable runnable) {
lastShutdownTasks.add(runnable);
}
};
+ private String[] commandLineArgs;
public StartupContext() {
values.put(ShutdownContext.class.getName(), shutdownContext);
+ values.put(RAW_COMMAND_LINE_ARGS, new Supplier() {
+ @Override
+ public String[] get() {
+ if (commandLineArgs == null) {
+ throw new RuntimeException("Command line arguments not available during static init");
+ }
+ return commandLineArgs;
+ }
+ });
}
public void putValue(String name, Object value) {
@@ -72,4 +85,9 @@ private void runAllInReverseOrder(List tasks) {
}
}
}
+
+ @SuppressWarnings("unused")
+ public void setCommandLineArguments(String[] commandLineArguments) {
+ this.commandLineArgs = commandLineArguments;
+ }
}
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/annotations/CommandLineArguments.java b/core/runtime/src/main/java/io/quarkus/runtime/annotations/CommandLineArguments.java
new file mode 100644
index 0000000000000..0db283ba1ba1a
--- /dev/null
+++ b/core/runtime/src/main/java/io/quarkus/runtime/annotations/CommandLineArguments.java
@@ -0,0 +1,15 @@
+package io.quarkus.runtime.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * A qualifier that can be used to inject the command line arguments.
+ */
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CommandLineArguments {
+
+}
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/annotations/DefaultMain.java b/core/runtime/src/main/java/io/quarkus/runtime/annotations/DefaultMain.java
new file mode 100644
index 0000000000000..3ec1a41a41241
--- /dev/null
+++ b/core/runtime/src/main/java/io/quarkus/runtime/annotations/DefaultMain.java
@@ -0,0 +1,29 @@
+package io.quarkus.runtime.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The default main class of a Quarkus application. This annotation can appear
+ * at most once in a Quarkus application.
+ *
+ * There are two different ways this annotation can be used. The first is to place it
+ * on a class with a Java main method. This main method will be the default entry point of
+ * the application. Note that Quarkus will not be started when this is method is called,
+ * this method must launch Quarkus with the {@link io.quarkus.runtime.Quarkus#run(Class, String...)}
+ * method.
+ *
+ * Alternatively this annotation can be placed on an {@link io.quarkus.runtime.QuarkusApplication}
+ * implementation. In this case a main method is automatically generated that will automatically
+ * call {@link io.quarkus.runtime.Quarkus#run(Class, String...)} with the provided application.
+ *
+ * Note that this can be overridden by the presence of the {@literal quarkus.package.main-class}
+ * configuration key, in which case the annotation is ignored.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface DefaultMain {
+}
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/graal/QuarkusSubstitution.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/QuarkusSubstitution.java
new file mode 100644
index 0000000000000..1cf9e6b8e1062
--- /dev/null
+++ b/core/runtime/src/main/java/io/quarkus/runtime/graal/QuarkusSubstitution.java
@@ -0,0 +1,20 @@
+package io.quarkus.runtime.graal;
+
+import java.util.function.Consumer;
+
+import com.oracle.svm.core.annotate.Substitute;
+import com.oracle.svm.core.annotate.TargetClass;
+
+import io.quarkus.runtime.Quarkus;
+import io.quarkus.runtime.QuarkusApplication;
+
+@TargetClass(Quarkus.class)
+final class QuarkusSubstitution {
+
+ @Substitute
+ private static void launchFromIDE(Class extends QuarkusApplication> quarkusApplication, Consumer exitHandler,
+ String... args) {
+ throw new RuntimeException("Should never be hit");
+ }
+
+}
diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/ExitCodeTestCase.java b/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/ExitCodeTestCase.java
new file mode 100644
index 0000000000000..3e223671654b4
--- /dev/null
+++ b/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/ExitCodeTestCase.java
@@ -0,0 +1,96 @@
+package io.quarkus.commandmode;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.runtime.Application;
+import io.quarkus.runtime.ApplicationLifecycleManager;
+import io.quarkus.runtime.Quarkus;
+import io.quarkus.runtime.QuarkusApplication;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class ExitCodeTestCase {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));
+
+ @Test
+ public void testReturnedExitCode() throws ExecutionException, InterruptedException {
+ CompletableFuture future = new CompletableFuture<>();
+ ApplicationLifecycleManager.run(Application.currentApplication(), ExitCodeApplication.class, new Consumer() {
+ @Override
+ public void accept(Integer integer) {
+ future.complete(integer);
+ }
+ }, "5");
+ Assertions.assertEquals(5, future.get());
+ }
+
+ @Test
+ public void testWaitToExitWithCode() throws ExecutionException, InterruptedException {
+ CompletableFuture future = new CompletableFuture<>();
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ ApplicationLifecycleManager.run(Application.currentApplication(), WaitToExitApplication.class,
+ new Consumer() {
+ @Override
+ public void accept(Integer integer) {
+ future.complete(integer);
+ }
+ });
+ }
+ }).start();
+ Thread.sleep(500);
+ Assertions.assertFalse(future.isDone());
+ Quarkus.asyncExit(10);
+ Assertions.assertEquals(10, future.get());
+ }
+
+ @Test
+ public void testWaitToExitWithNoCode() throws ExecutionException, InterruptedException {
+ CompletableFuture future = new CompletableFuture<>();
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ ApplicationLifecycleManager.run(Application.currentApplication(), WaitToExitApplication.class,
+ new Consumer() {
+ @Override
+ public void accept(Integer integer) {
+ future.complete(integer);
+ }
+ });
+ }
+ }).start();
+ Thread.sleep(500);
+ Assertions.assertFalse(future.isDone());
+ Quarkus.asyncExit();
+ Assertions.assertEquals(1, future.get());
+ }
+
+ public static class ExitCodeApplication implements QuarkusApplication {
+
+ @Override
+ public int run(String... args) throws Exception {
+ return Integer.parseInt(args[0]);
+ }
+ }
+
+ public static class WaitToExitApplication implements QuarkusApplication {
+
+ @Override
+ public int run(String... args) throws Exception {
+ Quarkus.waitForExit();
+ return 1;
+ }
+ }
+
+}
diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/HelloWorldMain.java b/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/HelloWorldMain.java
new file mode 100644
index 0000000000000..1c34788b55fcd
--- /dev/null
+++ b/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/HelloWorldMain.java
@@ -0,0 +1,13 @@
+package io.quarkus.commandmode;
+
+import io.quarkus.runtime.QuarkusApplication;
+import io.quarkus.runtime.annotations.DefaultMain;
+
+@DefaultMain
+public class HelloWorldMain implements QuarkusApplication {
+ @Override
+ public int run(String... args) throws Exception {
+ System.out.println("Hello World");
+ return 10;
+ }
+}
diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/HelloWorldNonDefault.java b/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/HelloWorldNonDefault.java
new file mode 100644
index 0000000000000..9753b91389c27
--- /dev/null
+++ b/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/HelloWorldNonDefault.java
@@ -0,0 +1,11 @@
+package io.quarkus.commandmode;
+
+import io.quarkus.runtime.QuarkusApplication;
+
+public class HelloWorldNonDefault implements QuarkusApplication {
+ @Override
+ public int run(String... args) throws Exception {
+ System.out.println("Hello World");
+ return 10;
+ }
+}
diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/JavaMain.java b/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/JavaMain.java
new file mode 100644
index 0000000000000..ce78963294376
--- /dev/null
+++ b/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/JavaMain.java
@@ -0,0 +1,12 @@
+package io.quarkus.commandmode;
+
+import io.quarkus.runtime.Quarkus;
+import io.quarkus.runtime.annotations.DefaultMain;
+
+@DefaultMain
+public class JavaMain {
+
+ public static void main(String... args) {
+ Quarkus.run(HelloWorldNonDefault.class, args);
+ }
+}
diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/JavaMainTestCase.java b/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/JavaMainTestCase.java
new file mode 100644
index 0000000000000..8b78be44b307c
--- /dev/null
+++ b/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/JavaMainTestCase.java
@@ -0,0 +1,28 @@
+package io.quarkus.commandmode;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusProdModeTest;
+
+public class JavaMainTestCase {
+ @RegisterExtension
+ static final QuarkusProdModeTest config = new QuarkusProdModeTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+ .addAsManifestResource("application.properties", "microprofile-config.properties")
+ .addClasses(JavaMain.class, HelloWorldNonDefault.class))
+ .setApplicationName("run-exit")
+ .setApplicationVersion("0.1-SNAPSHOT")
+ .setExpectExit(true)
+ .setRun(true);
+
+ @Test
+ public void testRun() {
+ Assertions.assertTrue(config.getStartupConsoleOutput().contains("Hello World"));
+ Assertions.assertEquals(10, config.getExitCode());
+ }
+
+}
diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/SimpleCommandModeTestCase.java b/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/SimpleCommandModeTestCase.java
new file mode 100644
index 0000000000000..e4ce37c23980c
--- /dev/null
+++ b/core/test-extension/deployment/src/test/java/io/quarkus/commandmode/SimpleCommandModeTestCase.java
@@ -0,0 +1,28 @@
+package io.quarkus.commandmode;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusProdModeTest;
+
+public class SimpleCommandModeTestCase {
+ @RegisterExtension
+ static final QuarkusProdModeTest config = new QuarkusProdModeTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+ .addAsManifestResource("application.properties", "microprofile-config.properties")
+ .addClasses(HelloWorldMain.class))
+ .setApplicationName("run-exit")
+ .setApplicationVersion("0.1-SNAPSHOT")
+ .setExpectExit(true)
+ .setRun(true);
+
+ @Test
+ public void testRun() {
+ Assertions.assertTrue(config.getStartupConsoleOutput().contains("Hello World"));
+ Assertions.assertEquals(10, config.getExitCode());
+ }
+
+}
diff --git a/devtools/gradle/build.gradle b/devtools/gradle/build.gradle
index 35f0a39e0a4ed..6c967debc75f1 100644
--- a/devtools/gradle/build.gradle
+++ b/devtools/gradle/build.gradle
@@ -24,7 +24,7 @@ dependencies {
implementation "io.quarkus:quarkus-devtools-common:${version}"
implementation "io.quarkus:quarkus-platform-descriptor-json:${version}"
implementation "io.quarkus:quarkus-platform-descriptor-resolver-json:${version}"
- implementation "io.quarkus:quarkus-development-mode:${version}"
+ implementation "io.quarkus:quarkus-core-deployment:${version}"
testImplementation 'org.mockito:mockito-core:3.2.4'
testImplementation 'org.assertj:assertj-core:3.14.0'
diff --git a/devtools/gradle/pom.xml b/devtools/gradle/pom.xml
index 627d81e9563c2..36f9c00743a1f 100644
--- a/devtools/gradle/pom.xml
+++ b/devtools/gradle/pom.xml
@@ -29,11 +29,11 @@
io.quarkus
- quarkus-devtools-common
+ quarkus-core-deployment
io.quarkus
- quarkus-development-mode
+ quarkus-devtools-common
io.quarkus
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 cf719f67bfd92..692394b0e88fa 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
@@ -59,8 +59,8 @@
import io.quarkus.bootstrap.model.AppModel;
import io.quarkus.bootstrap.resolver.AppModelResolver;
import io.quarkus.bootstrap.resolver.AppModelResolverException;
-import io.quarkus.dev.DevModeContext;
-import io.quarkus.dev.DevModeMain;
+import io.quarkus.deployment.dev.DevModeContext;
+import io.quarkus.deployment.dev.DevModeMain;
import io.quarkus.gradle.QuarkusPluginExtension;
import io.quarkus.utilities.JavaBinFinder;
diff --git a/devtools/maven/pom.xml b/devtools/maven/pom.xml
index 1082f1a9e6138..bcdc016ee970d 100644
--- a/devtools/maven/pom.xml
+++ b/devtools/maven/pom.xml
@@ -22,6 +22,16 @@
io.quarkus
quarkus-bootstrap-core
+
+ io.quarkus
+ quarkus-core-deployment
+
+
+ io.quarkus
+ quarkus-ide-launcher
+
+
+
io.quarkus
quarkus-platform-descriptor-json
@@ -71,10 +81,6 @@
maven-plugin-annotations
provided
-
- io.quarkus
- quarkus-development-mode
-
org.apache.maven.shared
maven-artifact-transfer
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java
index c70fecf01233e..0d8948d0ccca2 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java
@@ -61,8 +61,8 @@
import org.twdata.maven.mojoexecutor.MojoExecutor;
import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject;
-import io.quarkus.dev.DevModeContext;
-import io.quarkus.dev.DevModeMain;
+import io.quarkus.deployment.dev.DevModeContext;
+import io.quarkus.deployment.dev.DevModeMain;
import io.quarkus.maven.components.MavenVersionEnforcer;
import io.quarkus.maven.utilities.MojoUtils;
import io.quarkus.utilities.JavaBinFinder;
@@ -581,18 +581,21 @@ void prepare() throws Exception {
pomFiles.add(project.getDir().resolve("pom.xml"));
}
}
-
- final DefaultArtifact devModeJar = new DefaultArtifact("io.quarkus", "quarkus-development-mode", "jar",
+ DefaultArtifact bootstrap = new DefaultArtifact("io.quarkus", "quarkus-core-deployment", "jar",
pluginDef.getVersion());
final DependencyResult cpRes = repoSystem.resolveDependencies(repoSession,
new DependencyRequest()
.setCollectRequest(
new CollectRequest()
- .setRoot(new org.eclipse.aether.graph.Dependency(devModeJar, JavaScopes.RUNTIME))
+ .setRoot(new org.eclipse.aether.graph.Dependency(bootstrap, JavaScopes.RUNTIME))
.setRepositories(repos)));
for (ArtifactResult appDep : cpRes.getArtifactResults()) {
- addToClassPaths(classPathManifest, devModeContext, appDep.getArtifact().getFile());
+ //we only use the launcher for launching from the IDE, we need to exclude it
+ if (!(appDep.getArtifact().getGroupId().equals("io.quarkus")
+ && appDep.getArtifact().getArtifactId().equals("quarkus-ide-launcher"))) {
+ addToClassPaths(classPathManifest, devModeContext, appDep.getArtifact().getFile());
+ }
}
args.add("-Djava.util.logging.manager=org.jboss.logmanager.LogManager");
diff --git a/docs/src/main/asciidoc/command-mode-reference.adoc b/docs/src/main/asciidoc/command-mode-reference.adoc
new file mode 100644
index 0000000000000..ec13ed70b0871
--- /dev/null
+++ b/docs/src/main/asciidoc/command-mode-reference.adoc
@@ -0,0 +1,84 @@
+////
+This guide is maintained in the main Quarkus repository
+and pull requests should be submitted there:
+https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc
+////
+= Quarkus - Command Mode Applications
+
+include::./attributes.adoc[]
+
+This reference covers how to write application that run and then exit.
+
+== Writing Command Mode Applications
+
+There are two different approaches that can be used to implement applications
+that exit.
+
+. Implement `QuarkusApplication` and have Quarkus run this method automatically
+. Implement `QuarkusApplication` and a Java main method, and use the Java main method to launch Quarkus
+
+In this document the `QuarkusApplication` instance is referred to as the application main,
+and a class with a Java main method is the Java main.
+
+The simplest possible command mode application might appear as follows:
+
+[source,java]
+----
+import io.quarkus.runtime.QuarkusApplication;
+import io.quarkus.runtime.annotations.DefaultMain;
+
+@DefaultMain // <1>
+public class HelloWorldMain implements QuarkusApplication {
+ @Override
+ public int run(String... args) throws Exception { // <2>
+ System.out.println("Hello World");
+ return 10;
+ }
+}
+----
+<1> The `@DefaultMain` annotation to tells Quarkus that this is the main entry point. There can only be a single instance of this annotation in the application.
+<2> The `run` method is invoked once Quarkus starts, and the application stops when it finishes.
+
+If we want to use a Java main to run the application main it would look like:
+
+[source,java]
+----
+import io.quarkus.runtime.Quarkus;
+import io.quarkus.runtime.annotations.DefaultMain;
+
+@DefaultMain
+public class JavaMain {
+
+ public static void main(String... args) {
+ Quarkus.run(HelloWorldMain.class, args);
+ }
+}
+----
+
+This is effectively the same as running the `HelloWorldMain` application main directly, but has the advantage it can
+be run from the IDE.
+
+WARNING: It is recommended that a Java main perform very little logic, and just
+launch the application main. In development mode the Java main will run in a
+different ClassLoader to the main application, so may not behave as you would
+expect.
+
+It is possible to specify/override the main by using the `quarkus.package.main-class` build time
+configuration option.
+
+
+
+=== The command mode lifecycle
+
+When running a command mode application the basic lifecycle is as follows:
+
+. Start Quarkus
+. Run the `QuarkusApplication` main method
+. Shutdown Quarkus and exit the JVM after the main method returns
+
+Shutdown is always initiated by the application main thread returning. If you want to run some logic on startup,
+and then run like a normal application (i.e. not exit) then you should call `Quarkus.waitForExit` from the main
+thread (A non-command mode application is essentially just running an application that just calls `waitForExit`).
+
+If you want to shut down a running application and you are not in the main thread then you should call `Quarkus.asyncExit`
+in order to unblock the main thread and initiate the shutdown process.
diff --git a/docs/src/main/asciidoc/index.adoc b/docs/src/main/asciidoc/index.adoc
index 4569ece9570f4..b9fc6870a34f8 100644
--- a/docs/src/main/asciidoc/index.adoc
+++ b/docs/src/main/asciidoc/index.adoc
@@ -59,6 +59,7 @@ include::quarkus-intro.adoc[tag=intro]
* link:jms.html[Using Artemis JMS Client]
* link:reactive-routes.adoc[Using Reactive Routes]
* link:camel.adoc[Apache Camel]
+* link:command-mode-reference.adoc[Command Mode Applications]
* link:faq.html[FAQs]
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
index 1a0c2bb78f706..d8b9065f599a4 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
@@ -15,6 +15,8 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import javax.enterprise.context.ApplicationScoped;
+
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ClassInfo;
@@ -56,6 +58,7 @@
import io.quarkus.deployment.builditem.ApplicationClassPredicateBuildItem;
import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
import io.quarkus.deployment.builditem.CapabilityBuildItem;
+import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.ExecutorBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
@@ -65,6 +68,7 @@
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveFieldBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem;
+import io.quarkus.runtime.QuarkusApplication;
/**
* This class contains build steps that trigger various phases of the bean processing.
@@ -91,6 +95,16 @@ CapabilityBuildItem capability() {
return new CapabilityBuildItem(Capabilities.CDI_ARC);
}
+ @BuildStep
+ AdditionalBeanBuildItem quarkusApplication(CombinedIndexBuildItem combinedIndexBuildItem) {
+ return AdditionalBeanBuildItem.builder().setUnremovable()
+ .setDefaultScope(DotName.createSimple(ApplicationScoped.class.getName()))
+ .addBeanClasses(combinedIndexBuildItem.getIndex()
+ .getAllKnownImplementors(DotName.createSimple(QuarkusApplication.class.getName())).stream()
+ .map(s -> s.name().toString()).toArray(String[]::new))
+ .build();
+ }
+
// PHASE 1 - build BeanProcessor, register custom contexts
@BuildStep
public ContextRegistrationPhaseBuildItem initialize(
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/CommandLineArgumentsProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/CommandLineArgumentsProcessor.java
new file mode 100644
index 0000000000000..25369b165810a
--- /dev/null
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/CommandLineArgumentsProcessor.java
@@ -0,0 +1,26 @@
+package io.quarkus.arc.deployment;
+
+import io.quarkus.arc.runtime.ArcRecorder;
+import io.quarkus.arc.runtime.CommandLineArgumentsProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.ExecutionTime;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.RawCommandLineArgumentsBuildItem;
+import io.quarkus.runtime.annotations.CommandLineArguments;
+
+public class CommandLineArgumentsProcessor {
+
+ @BuildStep
+ @Record(ExecutionTime.STATIC_INIT)
+ BeanContainerListenerBuildItem commandLineArgs(RawCommandLineArgumentsBuildItem rawCommandLineArgumentsBuildItem,
+ ArcRecorder arcRecorder) {
+ //todo: this should be filtered
+ return new BeanContainerListenerBuildItem(arcRecorder.initCommandLineArgs(rawCommandLineArgumentsBuildItem));
+ }
+
+ @BuildStep
+ AdditionalBeanBuildItem qualifier() {
+ return AdditionalBeanBuildItem.builder().setUnremovable()
+ .addBeanClasses(CommandLineArguments.class, CommandLineArgumentsProducer.class).build();
+ }
+}
diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/arguments/CommandLineArgumentsTestCase.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/arguments/CommandLineArgumentsTestCase.java
new file mode 100644
index 0000000000000..3b330988106c4
--- /dev/null
+++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/arguments/CommandLineArgumentsTestCase.java
@@ -0,0 +1,30 @@
+package io.quarkus.arc.test.arguments;
+
+import javax.inject.Inject;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.runtime.annotations.CommandLineArguments;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class CommandLineArgumentsTestCase {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setCommandLineParameters("Hello", "World")
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));
+
+ @Inject
+ @CommandLineArguments
+ String[] args;
+
+ @Test
+ public void testConfigWasInjected() {
+ Assertions.assertArrayEquals(new String[] { "Hello", "World" }, args);
+ }
+
+}
diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcRecorder.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcRecorder.java
index 0d4ac9109fa37..1d3e3125d2067 100644
--- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcRecorder.java
+++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcRecorder.java
@@ -124,6 +124,15 @@ public Object get() {
};
}
+ public BeanContainerListener initCommandLineArgs(Supplier args) {
+ return new BeanContainerListener() {
+ @Override
+ public void created(BeanContainer container) {
+ container.instance(CommandLineArgumentsProducer.class).setCommandLineArgs(args);
+ }
+ };
+ }
+
private static final class DefaultInstanceFactory implements BeanContainer.Factory {
final Class type;
diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/CommandLineArgumentsProducer.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/CommandLineArgumentsProducer.java
new file mode 100644
index 0000000000000..026285d4e8eac
--- /dev/null
+++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/CommandLineArgumentsProducer.java
@@ -0,0 +1,24 @@
+package io.quarkus.arc.runtime;
+
+import java.util.function.Supplier;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Produces;
+
+import io.quarkus.runtime.annotations.CommandLineArguments;
+
+@ApplicationScoped
+public class CommandLineArgumentsProducer {
+
+ private volatile Supplier commandLineArgs;
+
+ @Produces
+ @CommandLineArguments
+ public String[] getCommandLineArgs() {
+ return commandLineArgs.get();
+ }
+
+ public void setCommandLineArgs(Supplier commandLineArgs) {
+ this.commandLineArgs = commandLineArgs;
+ }
+}
diff --git a/extensions/kogito/deployment/pom.xml b/extensions/kogito/deployment/pom.xml
index c3459f8b607a5..8e9b647a86dc4 100644
--- a/extensions/kogito/deployment/pom.xml
+++ b/extensions/kogito/deployment/pom.xml
@@ -14,10 +14,6 @@
-
- io.quarkus
- quarkus-development-mode
-
io.quarkus
quarkus-core-deployment
diff --git a/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoCompilationProvider.java b/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoCompilationProvider.java
index bec9fe278bd53..aa7c781fa34ae 100644
--- a/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoCompilationProvider.java
+++ b/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoCompilationProvider.java
@@ -19,7 +19,7 @@
import org.kie.kogito.codegen.context.QuarkusKogitoBuildContext;
import org.kie.kogito.codegen.di.CDIDependencyInjectionAnnotator;
-import io.quarkus.dev.JavaCompilationProvider;
+import io.quarkus.deployment.dev.JavaCompilationProvider;
public abstract class KogitoCompilationProvider extends JavaCompilationProvider {
diff --git a/extensions/kogito/deployment/src/main/resources/META-INF/services/io.quarkus.dev.CompilationProvider b/extensions/kogito/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.dev.CompilationProvider
similarity index 100%
rename from extensions/kogito/deployment/src/main/resources/META-INF/services/io.quarkus.dev.CompilationProvider
rename to extensions/kogito/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.dev.CompilationProvider
diff --git a/extensions/kotlin/deployment/pom.xml b/extensions/kotlin/deployment/pom.xml
index 67622c54c0030..9362bf3b77e83 100644
--- a/extensions/kotlin/deployment/pom.xml
+++ b/extensions/kotlin/deployment/pom.xml
@@ -24,11 +24,6 @@
quarkus-jackson-spi
-
- io.quarkus
- quarkus-development-mode
-
-
org.jetbrains.kotlin
kotlin-compiler
diff --git a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java
index cb9ed54e5d279..5a32623a5c4b3 100644
--- a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java
+++ b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java
@@ -19,7 +19,7 @@
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
import org.jetbrains.kotlin.config.Services;
-import io.quarkus.dev.CompilationProvider;
+import io.quarkus.deployment.dev.CompilationProvider;
public class KotlinCompilationProvider implements CompilationProvider {
diff --git a/extensions/kotlin/deployment/src/main/resources/META-INF/services/io.quarkus.dev.CompilationProvider b/extensions/kotlin/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.dev.CompilationProvider
similarity index 100%
rename from extensions/kotlin/deployment/src/main/resources/META-INF/services/io.quarkus.dev.CompilationProvider
rename to extensions/kotlin/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.dev.CompilationProvider
diff --git a/extensions/scala/deployment/pom.xml b/extensions/scala/deployment/pom.xml
index 1de5ed080bc47..4855bebbdcbdb 100644
--- a/extensions/scala/deployment/pom.xml
+++ b/extensions/scala/deployment/pom.xml
@@ -24,11 +24,6 @@
quarkus-jackson-spi
-
- io.quarkus
- quarkus-development-mode
-
-
org.scala-lang
scala-compiler
diff --git a/extensions/scala/deployment/src/main/java/io/quarkus/scala/deployment/ScalaCompilationProvider.java b/extensions/scala/deployment/src/main/java/io/quarkus/scala/deployment/ScalaCompilationProvider.java
index 027f14ebb2bbf..f5edd1faf09e4 100644
--- a/extensions/scala/deployment/src/main/java/io/quarkus/scala/deployment/ScalaCompilationProvider.java
+++ b/extensions/scala/deployment/src/main/java/io/quarkus/scala/deployment/ScalaCompilationProvider.java
@@ -6,7 +6,7 @@
import java.util.Set;
import java.util.stream.Collectors;
-import io.quarkus.dev.CompilationProvider;
+import io.quarkus.deployment.dev.CompilationProvider;
import scala.collection.JavaConverters;
import scala.tools.nsc.Global;
import scala.tools.nsc.Settings;
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java
index e95218d0d141e..955231ae65e50 100644
--- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java
+++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java
@@ -205,6 +205,12 @@ void validateFaultToleranceAnnotations(SmallryeFaultToleranceRecorder recorder,
}
private boolean hasFTAnnotations(IndexView index, AnnotationStore annotationStore, ClassInfo info) {
+ if (info == null) {
+ //should not happen, but guard against it
+ //happens in this case due to a bug involving array types
+
+ return false;
+ }
// first check annotations on type
if (annotationStore.hasAnyAnnotation(info, FT_ANNOTATIONS)) {
return true;
diff --git a/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java b/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java
index 7874d5c02408a..d2157cf81992e 100644
--- a/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java
+++ b/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java
@@ -397,7 +397,11 @@ void registerMetricsFromProducers(
BeanArchiveIndexBuildItem beanArchiveIndex) {
IndexView index = beanArchiveIndex.getIndex();
for (io.quarkus.arc.processor.BeanInfo bean : validationPhase.getContext().beans().producers()) {
- MetricType metricType = getMetricType(bean.getImplClazz());
+ ClassInfo implClazz = bean.getImplClazz();
+ if (implClazz == null) {
+ continue;
+ }
+ MetricType metricType = getMetricType(implClazz);
if (metricType != null) {
AnnotationTarget target = bean.getTarget().get();
AnnotationInstance metricAnnotation = null;
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/StartupAction.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/StartupAction.java
index 7712455f96696..d171fbfe1cf66 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/StartupAction.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/StartupAction.java
@@ -1,5 +1,18 @@
package io.quarkus.bootstrap.app;
+import java.util.function.Consumer;
+
public interface StartupAction {
- public RunningQuarkusApplication run(String... args) throws Exception;
+
+ /**
+ * Runs the application by running the main method of the main class. As this is a blocking method a new
+ * thread is created to run this task.
+ *
+ * Before this method is called an appropriate exit handler will likely need to
+ * be set in {@link io.quarkus.runtime.ApplicationLifecycleManager#setDefaultExitCodeHandler(Consumer)}
+ * of the JVM will exit when the app stops.
+ */
+ RunningQuarkusApplication runMainClass(String... args) throws Exception;
+
+ RunningQuarkusApplication run(String... args) throws Exception;
}
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppModel.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppModel.java
index 037310bbe7d69..7ff7b0ca63fb2 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppModel.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppModel.java
@@ -175,9 +175,16 @@ public void handleExtensionProperties(Properties props, String extension) {
}
public AppModel build() {
- Predicate includePredicate = s -> !excludedArtifacts
- .contains(new AppArtifactKey(s.getArtifact().getGroupId(), s.getArtifact().getArtifactId(),
- s.getArtifact().getClassifier(), s.getArtifact().getType()));
+ Predicate includePredicate = s -> {
+ //we never include the ide launcher in the final app model
+ if (s.getArtifact().getGroupId().equals("io.quarkus")
+ && s.getArtifact().getArtifactId().equals("quarkus-ide-launcher")) {
+ return false;
+ }
+ return !excludedArtifacts
+ .contains(new AppArtifactKey(s.getArtifact().getGroupId(), s.getArtifact().getArtifactId(),
+ s.getArtifact().getClassifier(), s.getArtifact().getType()));
+ };
List runtimeDeps = this.runtimeDeps.stream().filter(includePredicate).collect(Collectors.toList());
List deploymentDeps = this.deploymentDeps.stream().filter(includePredicate)
.collect(Collectors.toList());
diff --git a/integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/Main.java b/integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/Main.java
new file mode 100644
index 0000000000000..32412c7483753
--- /dev/null
+++ b/integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/Main.java
@@ -0,0 +1,10 @@
+package io.quarkus.it.hibernate.validator;
+
+import io.quarkus.runtime.Quarkus;
+
+public class Main {
+
+ public static void main(String... args) throws Exception {
+ Quarkus.run(args);
+ }
+}
diff --git a/test-framework/junit5-internal/pom.xml b/test-framework/junit5-internal/pom.xml
index f950897a0a153..2e1a34b24a812 100644
--- a/test-framework/junit5-internal/pom.xml
+++ b/test-framework/junit5-internal/pom.xml
@@ -52,10 +52,6 @@
jakarta.enterprise
jakarta.enterprise.cdi-api
-
- io.quarkus
- quarkus-development-mode
-
io.quarkus
quarkus-devtools-utilities
diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java
index 8ceb0414b72cf..cb39af6301c96 100644
--- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java
+++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java
@@ -31,10 +31,11 @@
import org.junit.jupiter.api.extension.TestInstanceFactoryContext;
import org.junit.jupiter.api.extension.TestInstantiationException;
+import io.quarkus.deployment.dev.CompilationProvider;
+import io.quarkus.deployment.dev.DevModeContext;
+import io.quarkus.deployment.dev.DevModeMain;
import io.quarkus.deployment.util.FileUtil;
-import io.quarkus.dev.CompilationProvider;
-import io.quarkus.dev.DevModeContext;
-import io.quarkus.dev.DevModeMain;
+import io.quarkus.dev.appstate.ApplicationStateNotification;
import io.quarkus.test.common.PathTestHelper;
import io.quarkus.test.common.PropertyTestUtil;
import io.quarkus.test.common.TestResourceManager;
@@ -149,6 +150,7 @@ public void close() throws Throwable {
context.setAbortOnFailedStart(true);
devModeMain = new DevModeMain(context);
devModeMain.start();
+ ApplicationStateNotification.waitForApplicationStart();
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusProdModeTest.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusProdModeTest.java
index 754bd059830d8..a4f56ded94c32 100644
--- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusProdModeTest.java
+++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusProdModeTest.java
@@ -87,6 +87,9 @@ public class QuarkusProdModeTest
private Path logfilePath;
private Optional logfileField = Optional.empty();
private List forcedDependencies = Collections.emptyList();
+ private boolean expectExit;
+ private String startupConsoleOutput;
+ private int exitCode;
public Supplier getArchiveProducer() {
return archiveProducer;
@@ -160,6 +163,29 @@ public QuarkusProdModeTest setForcedDependencies(List forcedDepende
return this;
}
+ /**
+ * If this is true then the quarkus application is expected to exit immediately (i.e. is a command mode app)
+ */
+ public QuarkusProdModeTest setExpectExit(boolean expectExit) {
+ this.expectExit = expectExit;
+ return this;
+ }
+
+ /**
+ * Returns the console output from startup. If {@link #expectExit} is true then this will contain
+ * all the console output.
+ */
+ public String getStartupConsoleOutput() {
+ return startupConsoleOutput;
+ }
+
+ /**
+ * Returns the process exit code, this can only be used if {@link #expectExit} is true
+ */
+ public int getExitCode() {
+ return exitCode;
+ }
+
private void exportArchive(Path deploymentDir, Class> testClass) {
try {
JavaArchive archive = getArchiveProducerOrDefault();
@@ -334,19 +360,33 @@ private void startBuiltResult(Path builtResultArtifact) throws IOException {
private void ensureApplicationStartupOrFailure() throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));
+ StringBuilder sb = new StringBuilder();
while (true) {
- if (!process.isAlive()) {
- in.close();
- throw new RuntimeException(
- "The produced jar could not be launched. Consult the above output for the exact cause.");
- }
String line = in.readLine();
if (line != null) {
System.out.println(line);
- if (line.contains(EXPECTED_OUTPUT_FROM_SUCCESSFULLY_STARTED)) {
+ sb.append(line);
+ sb.append("\n");
+ if (!expectExit && line.contains(EXPECTED_OUTPUT_FROM_SUCCESSFULLY_STARTED)) {
in.close();
+ this.startupConsoleOutput = sb.toString();
break;
}
+ } else {
+ //process has exited
+ this.startupConsoleOutput = sb.toString();
+ in.close();
+ try {
+ process.waitFor();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ exitCode = process.exitValue();
+ if (expectExit) {
+ return;
+ }
+ throw new RuntimeException(
+ "The produced jar could not be launched. Consult the above output for the exact cause.");
}
}
}
diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java
index f03286d9aab12..c0bc78790f2af 100644
--- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java
+++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java
@@ -93,6 +93,7 @@ public class QuarkusUnitTest
private Class> actualTestClass;
private Object actualTestInstance;
+ private String[] commandLineParameters = new String[0];
private boolean allowTestClassOutsideDeployment;
@@ -174,6 +175,15 @@ public QuarkusUnitTest setForcedDependencies(List forcedDependencie
return this;
}
+ public String[] getCommandLineParameters() {
+ return commandLineParameters;
+ }
+
+ public QuarkusUnitTest setCommandLineParameters(String... commandLineParameters) {
+ this.commandLineParameters = commandLineParameters;
+ return this;
+ }
+
/**
* Normally access to any test classes that are not packaged in the deployment will result
* in a ClassNotFoundException. If this is true then access is allowed, which can be useful
@@ -387,7 +397,7 @@ public void execute(BuildContext context) {
runningQuarkusApplication = new AugmentActionImpl(curatedApplication, customizers)
.createInitialRuntimeApplication()
- .run(new String[0]);
+ .run(commandLineParameters);
//we restore the CL at the end of the test
Thread.currentThread().setContextClassLoader(runningQuarkusApplication.getClassLoader());
if (assertException != null) {