From 6dda6d1fc6524782ed46b7209799ab0f6335c27a Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Thu, 22 Oct 2020 18:01:56 +0200 Subject: [PATCH] Common dev mode launcher API for Maven and Gradle --- .../dev/QuarkusDevModeLauncher.java | 454 +++++++++++++ .../gradle/tasks/GradleDevModeLauncher.java | 44 ++ .../io/quarkus/gradle/tasks/QuarkusDev.java | 291 +++----- .../gradle/tasks/QuarkusRemoteDev.java | 9 +- .../main/java/io/quarkus/maven/DevMojo.java | 637 +++++++----------- .../quarkus/maven/MavenDevModeLauncher.java | 44 ++ .../java/io/quarkus/maven/RemoteDevMojo.java | 11 +- extensions/vertx-http/runtime/pom.xml | 4 - 8 files changed, 890 insertions(+), 604 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusDevModeLauncher.java create mode 100644 devtools/gradle/src/main/java/io/quarkus/gradle/tasks/GradleDevModeLauncher.java create mode 100644 devtools/maven/src/main/java/io/quarkus/maven/MavenDevModeLauncher.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusDevModeLauncher.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusDevModeLauncher.java new file mode 100644 index 0000000000000..385e0e14227b3 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusDevModeLauncher.java @@ -0,0 +1,454 @@ +package io.quarkus.deployment.dev; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.URI; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.apache.maven.shared.utils.cli.CommandLineUtils; + +import io.quarkus.bootstrap.app.QuarkusBootstrap; +import io.quarkus.bootstrap.model.AppArtifactKey; +import io.quarkus.deployment.dev.DevModeContext.ModuleInfo; +import io.quarkus.runtime.util.JavaVersionUtil; +import io.quarkus.utilities.JavaBinFinder; + +public abstract class QuarkusDevModeLauncher { + + public class Builder> { + + protected Builder() { + args = new ArrayList<>(); + String javaTool = JavaBinFinder.findBin(); + QuarkusDevModeLauncher.this.debug("Using javaTool: %s", javaTool); + args.add(javaTool); + + } + + @SuppressWarnings("unchecked") + public B preventnoverify(boolean preventnoverify) { + if (!preventnoverify) { + // in Java 13 and up, preventing verification is deprecated - see https://bugs.openjdk.java.net/browse/JDK-8218003 + // this test isn't absolutely correct in the sense that depending on the user setup, the actual Java binary + // that is used might be different that the one running Maven, but given how small of an impact this has + // it's probably better than running an extra command on 'javaTool' just to figure out the version + if (!JavaVersionUtil.isJava13OrHigher()) { + args.add("-Xverify:none"); + } + } + return (B) this; + } + + @SuppressWarnings("unchecked") + public B jvmArgs(String jvmArgs) { + args.add(jvmArgs); + return (B) this; + } + + @SuppressWarnings("unchecked") + public B jvmArgs(List jvmArgs) { + args.addAll(jvmArgs); + return (B) this; + } + + @SuppressWarnings("unchecked") + public B debug(String debug) { + QuarkusDevModeLauncher.this.debug = debug; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B debugPortOk(Boolean debugPortOk) { + QuarkusDevModeLauncher.this.debugPortOk = debugPortOk; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B suspend(String suspend) { + QuarkusDevModeLauncher.this.suspend = suspend; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B projectDir(File projectDir) { + QuarkusDevModeLauncher.this.projectDir = projectDir; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B buildDir(File buildDir) { + QuarkusDevModeLauncher.this.buildDir = buildDir; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B outputDir(File outputDir) { + QuarkusDevModeLauncher.this.outputDir = outputDir; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B buildSystemProperties(Map buildSystemProperties) { + QuarkusDevModeLauncher.this.buildSystemProperties = buildSystemProperties; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B buildSystemProperty(String name, String value) { + QuarkusDevModeLauncher.this.buildSystemProperties.put(name, value); + return (B) this; + } + + @SuppressWarnings("unchecked") + public B applicationName(String appName) { + QuarkusDevModeLauncher.this.applicationName = appName; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B applicationVersion(String appVersion) { + QuarkusDevModeLauncher.this.applicationVersion = appVersion; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B applicationArgs(String appArgs) { + QuarkusDevModeLauncher.this.applicationArgs = appArgs; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B sourceEncoding(String srcEncoding) { + QuarkusDevModeLauncher.this.sourceEncoding = srcEncoding; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B compilerOption(String option) { + compilerOptions.add(option); + return (B) this; + } + + @SuppressWarnings("unchecked") + public B compilerOptions(List options) { + compilerOptions.addAll(options); + return (B) this; + } + + @SuppressWarnings("unchecked") + public B compilerPluginArtifacts(List artifacts) { + compilerPluginArtifacts = artifacts; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B compilerPluginOptions(List options) { + compilerPluginOptions = options; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B sourceJavaVersion(String sourceJavaVersion) { + QuarkusDevModeLauncher.this.sourceJavaVersion = sourceJavaVersion; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B targetJavaVersion(String targetJavaVersion) { + QuarkusDevModeLauncher.this.targetJavaVersion = targetJavaVersion; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B watchedBuildFile(Path buildFile) { + QuarkusDevModeLauncher.this.buildFiles.add(buildFile); + return (B) this; + } + + @SuppressWarnings("unchecked") + public B deleteDevJar(boolean deleteDevJar) { + QuarkusDevModeLauncher.this.deleteDevJar = deleteDevJar; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B baseName(String baseName) { + QuarkusDevModeLauncher.this.baseName = baseName; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B remoteDev(boolean remoteDev) { + QuarkusDevModeLauncher.this.remoteDev = remoteDev; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B localArtifact(AppArtifactKey localArtifact) { + localArtifacts.add(localArtifact); + return (B) this; + } + + public boolean isLocal(AppArtifactKey artifact) { + return localArtifacts.contains(artifact); + } + + @SuppressWarnings("unchecked") + public B mainModule(ModuleInfo mainModule) { + main = mainModule; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B dependency(ModuleInfo module) { + dependencies.add(module); + return (B) this; + } + + @SuppressWarnings("unchecked") + public B classpathEntry(File f) { + classpath.add(f); + return (B) this; + } + + @SuppressWarnings("unchecked") + public R build() throws Exception { + prepare(); + return (R) QuarkusDevModeLauncher.this; + } + } + + private List args = new ArrayList<>(0); + private String debug; + private Boolean debugPortOk; + private String suspend; + private File projectDir; + private File buildDir; + private File outputDir; + private Map buildSystemProperties = new HashMap<>(0); + private String applicationName; + private String applicationVersion; + private String sourceEncoding; + private List compilerOptions = new ArrayList<>(0); + private List compilerPluginArtifacts; + private List compilerPluginOptions; + private String sourceJavaVersion; + private String targetJavaVersion; + private Set buildFiles = new HashSet<>(0); + private boolean deleteDevJar = true; + private String baseName; + private boolean remoteDev; + private String applicationArgs; + private Set localArtifacts = new HashSet<>(); + private ModuleInfo main; + private List dependencies = new ArrayList<>(0); + private List classpath = new ArrayList<>(0); + + protected QuarkusDevModeLauncher() { + } + + /** + * Attempts to prepare the dev mode runner. + */ + protected void prepare() throws Exception { + + // the following flags reduce startup time and are acceptable only for dev purposes + args.add("-XX:TieredStopAtLevel=1"); + + if (suspend != null) { + switch (suspend.toLowerCase(Locale.ENGLISH)) { + case "n": + case "false": { + suspend = "n"; + break; + } + case "y": + case "true": { + suspend = "y"; + break; + } + default: { + warn("Ignoring invalid value \"" + suspend + "\" for \"suspend\" param and defaulting to \"n\""); + suspend = "n"; + break; + } + } + } else { + suspend = "n"; + } + + if (debug == null) { + // debug mode not specified + // make sure 5005 is not used, we don't want to just fail if something else is using it + // we don't check this on restarts, as the previous process is still running + if (debugPortOk == null) { + try (Socket socket = new Socket(InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }), 5005)) { + error("Port 5005 in use, not starting in debug mode"); + debugPortOk = false; + } catch (IOException e) { + debugPortOk = true; + } + } + if (debugPortOk) { + args.add("-Xdebug"); + args.add("-Xrunjdwp:transport=dt_socket,address=0.0.0.0:5005,server=y,suspend=" + suspend); + } + } else if (debug.toLowerCase().equals("client")) { + args.add("-Xdebug"); + args.add("-Xrunjdwp:transport=dt_socket,address=localhost:5005,server=n,suspend=" + suspend); + } else if (debug.toLowerCase().equals("true")) { + args.add("-Xdebug"); + args.add("-Xrunjdwp:transport=dt_socket,address=0.0.0.0:5005,server=y,suspend=" + suspend); + } else if (!debug.toLowerCase().equals("false")) { + try { + int port = Integer.parseInt(debug); + if (port <= 0) { + throw new Exception("The specified debug port must be greater than 0"); + } + args.add("-Xdebug"); + args.add("-Xrunjdwp:transport=dt_socket,address=0.0.0.0:" + port + ",server=y,suspend=" + suspend); + } catch (NumberFormatException e) { + throw new Exception( + "Invalid value for debug parameter: " + debug + " must be true|false|client|{port}"); + } + } + + //build a class-path string for the base platform + //this stuff does not change + // Do not include URIs in the manifest, because some JVMs do not like that + StringBuilder classPathManifest = new StringBuilder(); + final DevModeContext devModeContext = new DevModeContext(); + for (Map.Entry e : System.getProperties().entrySet()) { + devModeContext.getSystemProperties().put(e.getKey().toString(), (String) e.getValue()); + } + devModeContext.setProjectDir(projectDir); + devModeContext.getBuildSystemProperties().putAll(buildSystemProperties); + + // this is a minor hack to allow ApplicationConfig to be populated with defaults + devModeContext.getBuildSystemProperties().putIfAbsent("quarkus.application.name", applicationName); + devModeContext.getBuildSystemProperties().putIfAbsent("quarkus.application.version", applicationVersion); + + devModeContext.setSourceEncoding(sourceEncoding); + devModeContext.setCompilerOptions(compilerOptions); + + if (compilerPluginArtifacts != null) { + devModeContext.setCompilerPluginArtifacts(compilerPluginArtifacts); + } + if (compilerPluginOptions != null) { + devModeContext.setCompilerPluginsOptions(compilerPluginOptions); + } + + devModeContext.setSourceJavaVersion(sourceJavaVersion); + devModeContext.setTargetJvmVersion(targetJavaVersion); + + devModeContext.getLocalArtifacts().addAll(localArtifacts); + devModeContext.setApplicationRoot(main); + devModeContext.getAdditionalModules().addAll(dependencies); + + args.add("-Djava.util.logging.manager=org.jboss.logmanager.LogManager"); + + File tempFile = new File(buildDir, applicationName + "-dev.jar"); + tempFile.delete(); + // Only delete the -dev.jar on exit if requested + if (deleteDevJar) { + tempFile.deleteOnExit(); + } + debug("Executable jar: %s", tempFile.getAbsolutePath()); + + devModeContext.setBaseName(baseName); + devModeContext.setCacheDir(new File(buildDir, "transformer-cache").getAbsoluteFile()); + + // this is the jar file we will use to launch the dev mode main class + devModeContext.setDevModeRunnerJarFile(tempFile); + + if (remoteDev) { + devModeContext.setMode(QuarkusBootstrap.Mode.PROD); + devModeContext.setAlternateEntryPoint(IsolatedRemoteDevModeMain.class.getName()); + } + + try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(tempFile))) { + out.putNextEntry(new ZipEntry("META-INF/")); + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + + if (!classpath.isEmpty()) { + classpath.forEach(file -> { + final URI uri = file.toPath().toAbsolutePath().toUri(); + classPathManifest.append(uri).append(" "); + }); + } + manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, classPathManifest.toString()); + manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, DevModeMain.class.getName()); + out.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); + manifest.write(out); + + out.putNextEntry(new ZipEntry(DevModeMain.DEV_MODE_CONTEXT)); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + ObjectOutputStream obj = new ObjectOutputStream(new DataOutputStream(bytes)); + obj.writeObject(devModeContext); + obj.close(); + out.write(bytes.toByteArray()); + } + + outputDir.mkdirs(); + // if the --enable-preview flag was set, then we need to enable it when launching dev mode as well + if (devModeContext.isEnablePreview()) { + args.add(DevModeContext.ENABLE_PREVIEW_FLAG); + } + + args.add("-jar"); + args.add(tempFile.getAbsolutePath()); + if (applicationArgs != null) { + args.addAll(Arrays.asList(CommandLineUtils.translateCommandline(applicationArgs))); + } + } + + public Collection watchedBuildFiles() { + return buildFiles; + } + + public List args() { + return args; + } + + protected abstract boolean isDebugEnabled(); + + protected void debug(Object msg, Object... args) { + if (!isDebugEnabled()) { + return; + } + if (msg == null) { + return; + } + if (args.length == 0) { + debug(msg); + return; + } + debug(String.format(msg.toString(), args)); + } + + protected abstract void debug(Object msg); + + protected abstract void error(Object msg); + + protected abstract void warn(Object msg); +} diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/GradleDevModeLauncher.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/GradleDevModeLauncher.java new file mode 100644 index 0000000000000..622870452ff3d --- /dev/null +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/GradleDevModeLauncher.java @@ -0,0 +1,44 @@ +package io.quarkus.gradle.tasks; + +import org.gradle.api.logging.Logger; + +import io.quarkus.deployment.dev.QuarkusDevModeLauncher; + +public class GradleDevModeLauncher extends QuarkusDevModeLauncher { + + public static Builder builder(Logger logger) { + return new GradleDevModeLauncher(logger).new Builder(); + } + + public class Builder extends QuarkusDevModeLauncher.Builder { + + private Builder() { + } + } + + private final Logger logger; + + private GradleDevModeLauncher(Logger logger) { + this.logger = logger; + } + + @Override + protected boolean isDebugEnabled() { + return logger.isDebugEnabled(); + } + + @Override + protected void debug(Object msg) { + logger.warn(msg == null ? "null" : msg.toString()); + } + + @Override + protected void error(Object msg) { + logger.error(msg == null ? "null" : msg.toString()); + } + + @Override + protected void warn(Object msg) { + logger.warn(msg == null ? "null" : msg.toString()); + } +} 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 501b8f7dc92fe..e351b59ab460e 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 @@ -1,31 +1,18 @@ package io.quarkus.gradle.tasks; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.ObjectOutputStream; -import java.net.InetAddress; -import java.net.Socket; -import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; -import java.util.jar.Attributes; -import java.util.jar.Manifest; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; import javax.inject.Inject; @@ -55,10 +42,9 @@ import io.quarkus.bootstrap.resolver.AppModelResolver; import io.quarkus.bootstrap.resolver.AppModelResolverException; import io.quarkus.deployment.dev.DevModeContext; -import io.quarkus.deployment.dev.DevModeMain; +import io.quarkus.deployment.dev.QuarkusDevModeLauncher; import io.quarkus.gradle.QuarkusPluginExtension; import io.quarkus.runtime.LaunchMode; -import io.quarkus.utilities.JavaBinFinder; public class QuarkusDev extends QuarkusTask { @@ -194,205 +180,113 @@ public void startDev() { "Does the project have any source files?"); } - DevModeContext context = new DevModeContext(); - context.setProjectDir(project.getProjectDir()); - for (Map.Entry e : System.getProperties().entrySet()) { - context.getSystemProperties().put(e.getKey().toString(), (String) e.getValue()); + try { + QuarkusDevModeLauncher runner = newLauncher(); + project.exec(action -> { + action.commandLine(runner.args()).workingDir(getWorkingDir()); + action.setStandardInput(System.in) + .setErrorOutput(System.out) + .setStandardOutput(System.out); + }); + + } catch (Exception e) { + throw new GradleException("Failed to run", e); + } + } + + private QuarkusDevModeLauncher newLauncher() throws Exception { + final Project project = getProject(); + GradleDevModeLauncher.Builder builder = GradleDevModeLauncher.builder(getLogger()) + .preventnoverify(isPreventnoverify()) + .projectDir(project.getProjectDir()) + .buildDir(getBuildDir()) + .outputDir(getBuildDir()) + .debug(System.getProperty("debug")) + .suspend(System.getProperty("suspend")); + + if (getJvmArgs() != null) { + builder.jvmArgs(getJvmArgs()); } + for (Map.Entry e : project.getProperties().entrySet()) { if (e.getValue() instanceof String) { - context.getBuildSystemProperties().put(e.getKey(), e.getValue().toString()); + builder.buildSystemProperty(e.getKey(), e.getValue().toString()); } } // this is a minor hack to allow ApplicationConfig to be populated with defaults - context.getBuildSystemProperties().putIfAbsent("quarkus.application.name", project.getName()); + builder.applicationName(project.getName()); if (project.getVersion() != null) { - context.getBuildSystemProperties().putIfAbsent("quarkus.application.version", project.getVersion().toString()); - } - - context.setSourceEncoding(getSourceEncoding()); - try { - List args = new ArrayList<>(); - args.add(JavaBinFinder.findBin()); - final String debug = System.getProperty("debug"); - final String suspend = System.getProperty("suspend"); - String debugSuspend = "n"; - if (suspend != null) { - switch (suspend.toLowerCase(Locale.ENGLISH)) { - case "n": - case "false": { - debugSuspend = "n"; - break; - } - case "": - case "y": - case "true": { - debugSuspend = "y"; - break; - } - default: { - System.err.println( - "Ignoring invalid value \"" + suspend + "\" for \"suspend\" param and defaulting to \"n\""); - break; - } - } - } - if (debug == null) { - // debug mode not specified - // make sure 5005 is not used, we don't want to just fail if something else is using it - try (Socket socket = new Socket(InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }), 5005)) { - System.err.println("Port 5005 in use, not starting in debug mode"); - } catch (IOException e) { - args.add("-Xdebug"); - args.add("-Xrunjdwp:transport=dt_socket,address=0.0.0.0:5005,server=y,suspend=" + debugSuspend); - } - } else if (debug.toLowerCase().equals("client")) { - args.add("-Xdebug"); - args.add("-Xrunjdwp:transport=dt_socket,address=localhost:5005,server=n,suspend=" + debugSuspend); - } else if (debug.toLowerCase().equals("true") || debug.isEmpty()) { - args.add("-Xdebug"); - args.add("-Xrunjdwp:transport=dt_socket,address=0.0.0.0:5005,server=y,suspend=" + debugSuspend); - } else if (!debug.toLowerCase().equals("false")) { - try { - int port = Integer.parseInt(debug); - if (port <= 0) { - throw new GradleException("The specified debug port must be greater than 0"); - } - args.add("-Xdebug"); - args.add("-Xrunjdwp:transport=dt_socket,address=0.0.0.0:" + port + ",server=y,suspend=" + debugSuspend); - } catch (NumberFormatException e) { - throw new GradleException( - "Invalid value for debug parameter: " + debug + " must be true|false|client|{port}"); - } - } - if (getJvmArgs() != null) { - args.addAll(getJvmArgs()); - } - - // the following flags reduce startup time and are acceptable only for dev purposes - args.add("-XX:TieredStopAtLevel=1"); - if (!isPreventnoverify()) { - args.add("-Xverify:none"); - } - - //build a class-path string for the base platform - //this stuff does not change - // Do not include URIs in the manifest, because some JVMs do not like that - StringBuilder classPathManifest = new StringBuilder(); - - final AppModel appModel; - final AppModelResolver modelResolver = extension().getAppModelResolver(LaunchMode.DEVELOPMENT); - try { - final AppArtifact appArtifact = extension.getAppArtifact(); - appArtifact.setPaths(QuarkusGradleUtils.getOutputPaths(project)); - appModel = modelResolver.resolveModel(appArtifact); - } catch (AppModelResolverException e) { - throw new GradleException("Failed to resolve application model " + extension.getAppArtifact() + " dependencies", - e); - } + builder.applicationVersion(project.getVersion().toString()); - args.add("-Djava.util.logging.manager=org.jboss.logmanager.LogManager"); - - final Set projectDependencies = new HashSet<>(); - addSelfWithLocalDeps(project, context, new HashSet<>(), projectDependencies, true); + } - for (AppDependency appDependency : appModel.getFullDeploymentDeps()) { - final AppArtifact appArtifact = appDependency.getArtifact(); - if (!projectDependencies.contains(new AppArtifactKey(appArtifact.getGroupId(), appArtifact.getArtifactId()))) { - appArtifact.getPaths().forEach(p -> { - if (Files.exists(p)) { - addToClassPaths(classPathManifest, p.toFile()); - } - }); - } - } + builder.sourceEncoding(getSourceEncoding()); - //we also want to add the maven plugin jar to the class path - //this allows us to just directly use classes, without messing around copying them - //to the runner jar - addGradlePluginDeps(classPathManifest, context); + final AppModel appModel; + final AppModelResolver modelResolver = extension().getAppModelResolver(LaunchMode.DEVELOPMENT); + try { + final AppArtifact appArtifact = extension().getAppArtifact(); + appArtifact.setPaths(QuarkusGradleUtils.getOutputPaths(project)); + appModel = modelResolver.resolveModel(appArtifact); + } catch (AppModelResolverException e) { + throw new GradleException("Failed to resolve application model " + extension().getAppArtifact() + " dependencies", + e); + } - context.setCacheDir(new File(getBuildDir(), "transformer-cache").getAbsoluteFile()); + final Set projectDependencies = new HashSet<>(); + addSelfWithLocalDeps(project, builder, new HashSet<>(), projectDependencies, true); - JavaPluginConvention javaPluginConvention = project.getConvention().findPlugin(JavaPluginConvention.class); - if (javaPluginConvention != null) { - context.setSourceJavaVersion(javaPluginConvention.getSourceCompatibility().toString()); - context.setTargetJvmVersion(javaPluginConvention.getTargetCompatibility().toString()); - } - - if (getCompilerArgs().isEmpty()) { - getJavaCompileTask() - .map(compileTask -> compileTask.getOptions().getCompilerArgs()) - .ifPresent(context::setCompilerOptions); - } else { - context.setCompilerOptions(getCompilerArgs()); + for (AppDependency appDependency : appModel.getFullDeploymentDeps()) { + final AppArtifact appArtifact = appDependency.getArtifact(); + if (!projectDependencies.contains(new AppArtifactKey(appArtifact.getGroupId(), appArtifact.getArtifactId()))) { + appArtifact.getPaths().forEach(p -> { + if (Files.exists(p)) { + addToClassPaths(builder, p.toFile()); + } + }); } + } - // this is the jar file we will use to launch the dev mode main class - final File tempFile = new File(getBuildDir(), extension.finalName() + "-dev.jar"); - tempFile.delete(); - tempFile.deleteOnExit(); - - context.setDevModeRunnerJarFile(tempFile); - modifyDevModeContext(context); - try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(tempFile))) { - out.putNextEntry(new ZipEntry("META-INF/")); - Manifest manifest = new Manifest(); - manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); - manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, classPathManifest.toString()); - manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, DevModeMain.class.getName()); - out.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); - manifest.write(out); - - out.putNextEntry(new ZipEntry(DevModeMain.DEV_MODE_CONTEXT)); - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - try (ObjectOutputStream obj = new ObjectOutputStream(new DataOutputStream(bytes))) { - obj.writeObject(context); - } - out.write(bytes.toByteArray()); + //we also want to add the Gradle plugin to the class path + //this allows us to just directly use classes, without messing around copying them + //to the runner jar + addGradlePluginDeps(builder); - out.putNextEntry(new ZipEntry(BootstrapConstants.SERIALIZED_APP_MODEL)); - bytes = new ByteArrayOutputStream(); - try (ObjectOutputStream obj = new ObjectOutputStream(new DataOutputStream(bytes))) { - obj.writeObject(appModel); - } - out.write(bytes.toByteArray()); - } + JavaPluginConvention javaPluginConvention = project.getConvention().findPlugin(JavaPluginConvention.class); + if (javaPluginConvention != null) { + builder.sourceJavaVersion(javaPluginConvention.getSourceCompatibility().toString()); + builder.targetJavaVersion(javaPluginConvention.getTargetCompatibility().toString()); + } - final Path serializedModel = QuarkusGradleUtils.serializeAppModel(appModel, this); - serializedModel.toFile().deleteOnExit(); - args.add("-D" + BootstrapConstants.SERIALIZED_APP_MODEL + "=" + serializedModel.toAbsolutePath()); + if (getCompilerArgs().isEmpty()) { + getJavaCompileTask() + .map(compileTask -> compileTask.getOptions().getCompilerArgs()) + .ifPresent(builder::compilerOptions); + } else { + builder.compilerOptions(getCompilerArgs()); + } - extension.outputDirectory().mkdirs(); + modifyDevModeContext(builder); - if (context.isEnablePreview()) { - args.add(DevModeContext.ENABLE_PREVIEW_FLAG); - } + final Path serializedModel = QuarkusGradleUtils.serializeAppModel(appModel, this); + serializedModel.toFile().deleteOnExit(); + builder.jvmArgs("-D" + BootstrapConstants.SERIALIZED_APP_MODEL + "=" + serializedModel.toAbsolutePath()); - args.add("-jar"); - args.add(tempFile.getAbsolutePath()); - args.addAll(this.getArgs()); - if (getLogger().isDebugEnabled()) { - getLogger().debug("Launching JVM with command line: {}", String.join(" ", args)); - } + extension().outputDirectory().mkdirs(); - project.exec(action -> { - action.commandLine(args).workingDir(getWorkingDir()); - action.setStandardInput(System.in) - .setErrorOutput(System.out) - .setStandardOutput(System.out); - }); - } catch (Exception e) { - throw new GradleException("Failed to run", e); + if (!args.isEmpty()) { + builder.applicationArgs(String.join(" ", args)); } + + return builder.build(); } - protected void modifyDevModeContext(DevModeContext devModeContext) { + protected void modifyDevModeContext(GradleDevModeLauncher.Builder builder) { } - private void addSelfWithLocalDeps(Project project, DevModeContext context, Set visited, + private void addSelfWithLocalDeps(Project project, GradleDevModeLauncher.Builder builder, Set visited, Set addedDeps, boolean root) { if (!visited.add(project.getPath())) { return; @@ -401,15 +295,16 @@ private void addSelfWithLocalDeps(Project project, DevModeContext context, Set { if (d instanceof ProjectDependency) { - addSelfWithLocalDeps(((ProjectDependency) d).getDependencyProject(), context, visited, addedDeps, false); + addSelfWithLocalDeps(((ProjectDependency) d).getDependencyProject(), builder, visited, addedDeps, false); } }); } - addLocalProject(project, context, addedDeps, root); + addLocalProject(project, builder, addedDeps, root); } - private void addLocalProject(Project project, DevModeContext context, Set addeDeps, boolean root) { + private void addLocalProject(Project project, GradleDevModeLauncher.Builder builder, Set addeDeps, + boolean root) { final AppArtifactKey key = new AppArtifactKey(project.getGroup().toString(), project.getName()); if (addeDeps.contains(key)) { return; @@ -466,9 +361,9 @@ private void addLocalProject(Project project, DevModeContext context, Set d return null; } - private void addGradlePluginDeps(StringBuilder classPathManifest, DevModeContext context) { + private void addGradlePluginDeps(GradleDevModeLauncher.Builder builder) { boolean foundQuarkusPlugin = false; Project prj = getProject(); while (prj != null && !foundQuarkusPlugin) { @@ -515,7 +410,7 @@ private void addGradlePluginDeps(StringBuilder classPathManifest, DevModeContext if (quarkusPluginDependency != null) { quarkusPluginDependency.getAllModuleArtifacts().stream() .map(ResolvedArtifact::getFile) - .forEach(f -> addToClassPaths(classPathManifest, f)); + .forEach(f -> addToClassPaths(builder, f)); foundQuarkusPlugin = true; @@ -542,7 +437,7 @@ private void addGradlePluginDeps(StringBuilder classPathManifest, DevModeContext for (String cpElement : classpath.split(File.pathSeparator)) { final File f = new File(cpElement); if (f.exists()) { - addToClassPaths(classPathManifest, f); + addToClassPaths(builder, f); } } } @@ -552,12 +447,10 @@ private void addGradlePluginDeps(StringBuilder classPathManifest, DevModeContext } } - private void addToClassPaths(StringBuilder classPathManifest, File file) { + private void addToClassPaths(GradleDevModeLauncher.Builder classPathManifest, File file) { if (filesIncludedInClasspath.add(file)) { getProject().getLogger().debug("Adding dependency {}", file); - - final URI uri = file.toPath().toAbsolutePath().toUri(); - classPathManifest.append(uri).append(" "); + classPathManifest.classpathEntry(file); } } } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoteDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoteDev.java index bbd135e97ce15..57843b79e0ff3 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoteDev.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoteDev.java @@ -1,17 +1,12 @@ package io.quarkus.gradle.tasks; -import io.quarkus.bootstrap.app.QuarkusBootstrap; -import io.quarkus.deployment.dev.DevModeContext; -import io.quarkus.deployment.dev.IsolatedRemoteDevModeMain; - public class QuarkusRemoteDev extends QuarkusDev { public QuarkusRemoteDev() { super("Remote development mode: enables hot deployment on remote JVM with background compilation"); } - protected void modifyDevModeContext(DevModeContext devModeContext) { - devModeContext.setMode(QuarkusBootstrap.Mode.PROD); - devModeContext.setAlternateEntryPoint(IsolatedRemoteDevModeMain.class.getName()); + protected void modifyDevModeContext(GradleDevModeLauncher.Builder builder) { + builder.remoteDev(true); } } 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 e2c54c2029a8c..7fcd31802ab78 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -9,37 +9,27 @@ import static org.twdata.maven.mojoexecutor.MojoExecutor.plugin; import static org.twdata.maven.mojoexecutor.MojoExecutor.version; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.ObjectOutputStream; -import java.net.InetAddress; -import java.net.Socket; -import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.Set; -import java.util.jar.Attributes; -import java.util.jar.Manifest; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import java.util.zip.ZipOutputStream; import org.apache.maven.artifact.Artifact; import org.apache.maven.execution.MavenSession; @@ -56,7 +46,6 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; -import org.apache.maven.shared.utils.cli.CommandLineUtils; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; @@ -82,10 +71,9 @@ import io.quarkus.bootstrap.resolver.maven.workspace.LocalWorkspace; import io.quarkus.deployment.dev.DevModeContext; import io.quarkus.deployment.dev.DevModeMain; +import io.quarkus.deployment.dev.QuarkusDevModeLauncher; import io.quarkus.maven.components.MavenVersionEnforcer; import io.quarkus.maven.utilities.MojoUtils; -import io.quarkus.runtime.util.JavaVersionUtil; -import io.quarkus.utilities.JavaBinFinder; /** * The dev mojo, that runs a quarkus app in a forked process. A background compilation process is launched and any changes are @@ -97,6 +85,8 @@ public class DevMojo extends AbstractMojo { private static final String EXT_PROPERTIES_PATH = "META-INF/quarkus-extension.properties"; + private static final String KOTLIN_MAVEN_PLUGIN_GA = "org.jetbrains.kotlin:kotlin-maven-plugin"; + /** * running any one of these phases means the compile phase will have been run, if these have * not been run we manually run compile @@ -300,52 +290,8 @@ public void execute() throws MojoFailureException, MojoExecutionException { } try { - List args = new ArrayList<>(); - String javaTool = JavaBinFinder.findBin(); - getLog().debug("Using javaTool: " + javaTool); - args.add(javaTool); - if (this.suspend != null) { - switch (this.suspend.toLowerCase(Locale.ENGLISH)) { - case "n": - case "false": { - suspend = "n"; - break; - } - case "y": - case "true": { - suspend = "y"; - break; - } - default: { - getLog().warn( - "Ignoring invalid value \"" + suspend + "\" for \"suspend\" param and defaulting to \"n\""); - suspend = "n"; - break; - } - } - } else { - suspend = "n"; - } - if (jvmArgs != null) { - args.addAll(Arrays.asList(CommandLineUtils.translateCommandline(jvmArgs))); - } - - // the following flags reduce startup time and are acceptable only for dev purposes - args.add("-XX:TieredStopAtLevel=1"); - if (!preventnoverify) { - // in Java 13 and up, preventing verification is deprecated - see https://bugs.openjdk.java.net/browse/JDK-8218003 - // this test isn't absolutely correct in the sense that depending on the user setup, the actual Java binary - // that is used might be different that the one running Maven, but given how small of an impact this has - // it's probably better than running an extra command on 'javaTool' just to figure out the version - if (!JavaVersionUtil.isJava13OrHigher()) { - args.add("-Xverify:none"); - } - } - - DevModeRunner runner = new DevModeRunner(args); - - runner.prepare(false); + DevModeRunner runner = new DevModeRunner(); Map pomFiles = readPomFileTimestamps(runner); runner.run(); long nextCheck = System.currentTimeMillis() + 100; @@ -356,8 +302,8 @@ public void execute() throws MojoFailureException, MojoExecutionException { Thread.sleep(sleep); if (System.currentTimeMillis() > nextCheck) { nextCheck = System.currentTimeMillis() + 100; - if (!runner.process.isAlive()) { - if (runner.process.exitValue() != 0) { + if (!runner.alive()) { + if (runner.exitValue() != 0) { throw new MojoExecutionException("Dev mode process did not complete successfully"); } return; @@ -372,9 +318,10 @@ public void execute() throws MojoFailureException, MojoExecutionException { } if (!changed.isEmpty()) { getLog().info("Changes detected to " + changed + ", restarting dev mode"); - DevModeRunner newRunner = new DevModeRunner(args); + final DevModeRunner newRunner; try { - newRunner.prepare(true); + triggerCompile(); + newRunner = new DevModeRunner(); } catch (Exception e) { getLog().info("Could not load changed pom.xml file, changes not applied", e); continue; @@ -509,7 +456,7 @@ private Xpp3Dom getPluginConfig(Plugin plugin) { private Map readPomFileTimestamps(DevModeRunner runner) throws IOException { Map ret = new HashMap<>(); - for (Path i : runner.getPomFiles()) { + for (Path i : runner.pomFiles()) { ret.put(i, Files.getLastModifiedTime(i).toMillis()); } return ret; @@ -523,7 +470,7 @@ private String getSourceEncoding() { return null; } - private void addProject(DevModeContext devModeContext, LocalProject localProject, boolean root) throws Exception { + private void addProject(MavenDevModeLauncher.Builder builder, LocalProject localProject, boolean root) throws Exception { String projectDirectory = null; Set sourcePaths = null; @@ -577,376 +524,296 @@ private void addProject(DevModeContext devModeContext, LocalProject localProject targetDir.resolve("generated-sources").toAbsolutePath().toString(), targetDir.toAbsolutePath().toString()); if (root) { - devModeContext.setApplicationRoot(moduleInfo); + builder.mainModule(moduleInfo); } else { - devModeContext.getAdditionalModules().add(moduleInfo); + builder.dependency(moduleInfo); } } - private void addToClassPaths(StringBuilder classPathManifest, File file) { - final URI uri = file.toPath().toAbsolutePath().toUri(); - classPathManifest.append(uri).append(" "); - } - - class DevModeRunner { + private class DevModeRunner { - private static final String KOTLIN_MAVEN_PLUGIN_GA = "org.jetbrains.kotlin:kotlin-maven-plugin"; - private final List args; + final QuarkusDevModeLauncher launcher; private Process process; - private Set pomFiles = new HashSet<>(); - DevModeRunner(List args) { - this.args = new ArrayList<>(args); + private DevModeRunner() throws Exception { + launcher = newLauncher(); } - /** - * Attempts to prepare the dev mode runner. - */ - void prepare(final boolean triggerCompile) throws Exception { - if (triggerCompile) { - triggerCompile(); + Collection pomFiles() { + return launcher.watchedBuildFiles(); + } + + boolean alive() { + return process == null ? false : process.isAlive(); + } + + int exitValue() { + return process == null ? -1 : process.exitValue(); + } + + void run() throws Exception { + // Display the launch command line in dev mode + if (getLog().isDebugEnabled()) { + getLog().debug("Launching JVM with command line: " + String.join(" ", launcher.args())); } - if (debug == null) { - // debug mode not specified - // make sure 5005 is not used, we don't want to just fail if something else is using it - // we don't check this on restarts, as the previous process is still running - if (debugPortOk == null) { - try (Socket socket = new Socket(InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }), 5005)) { - getLog().error("Port 5005 in use, not starting in debug mode"); - debugPortOk = false; - } catch (IOException e) { - debugPortOk = true; + process = new ProcessBuilder(launcher.args()) + .redirectErrorStream(true) + .inheritIO() + .directory(workingDir == null ? buildDir : workingDir) + .start(); + + //https://github.com/quarkusio/quarkus/issues/232 + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + @Override + public void run() { + process.destroy(); + try { + process.waitFor(); + } catch (InterruptedException ignored) { + getLog().warn("Unable to properly wait for dev-mode end", ignored); } } - if (debugPortOk) { - args.add("-Xdebug"); - args.add("-Xrunjdwp:transport=dt_socket,address=0.0.0.0:5005,server=y,suspend=" + suspend); - } - } else if (debug.toLowerCase().equals("client")) { - args.add("-Xdebug"); - args.add("-Xrunjdwp:transport=dt_socket,address=localhost:5005,server=n,suspend=" + suspend); - } else if (debug.toLowerCase().equals("true")) { - args.add("-Xdebug"); - args.add("-Xrunjdwp:transport=dt_socket,address=0.0.0.0:5005,server=y,suspend=" + suspend); - } else if (!debug.toLowerCase().equals("false")) { - try { - int port = Integer.parseInt(debug); - if (port <= 0) { - throw new MojoFailureException("The specified debug port must be greater than 0"); - } - args.add("-Xdebug"); - args.add("-Xrunjdwp:transport=dt_socket,address=0.0.0.0:" + port + ",server=y,suspend=" + suspend); - } catch (NumberFormatException e) { - throw new MojoFailureException( - "Invalid value for debug parameter: " + debug + " must be true|false|client|{port}"); + }, "Development Mode Shutdown Hook")); + } + + void stop() throws InterruptedException { + process.destroy(); + process.waitFor(); + } + } + + private QuarkusDevModeLauncher newLauncher() throws Exception { + final MavenDevModeLauncher.Builder builder = MavenDevModeLauncher.builder(getLog()) + .preventnoverify(preventnoverify) + .buildDir(buildDir) + .outputDir(outputDirectory) + .suspend(suspend) + .debug(debug) + .debugPortOk(debugPortOk) + .deleteDevJar(deleteDevJar); + + if (jvmArgs != null) { + builder.jvmArgs(jvmArgs); + } + + builder.projectDir(project.getFile().getParentFile()); + builder.buildSystemProperties((Map) project.getProperties()); + + builder.applicationName(project.getArtifactId()); + builder.applicationVersion(project.getVersion()); + + builder.sourceEncoding(getSourceEncoding()); + + // Set compilation flags. Try the explicitly given configuration first. Otherwise, + // refer to the configuration of the Maven Compiler Plugin. + final Optional compilerPluginConfiguration = findCompilerPluginConfiguration(); + if (compilerArgs != null) { + builder.compilerOptions(compilerArgs); + } else if (compilerPluginConfiguration.isPresent()) { + final Xpp3Dom compilerPluginArgsConfiguration = compilerPluginConfiguration.get().getChild("compilerArgs"); + if (compilerPluginArgsConfiguration != null) { + List compilerPluginArgs = new ArrayList<>(); + for (Xpp3Dom argConfiguration : compilerPluginArgsConfiguration.getChildren()) { + compilerPluginArgs.add(argConfiguration.getValue()); } - } - //build a class-path string for the base platform - //this stuff does not change - // Do not include URIs in the manifest, because some JVMs do not like that - StringBuilder classPathManifest = new StringBuilder(); - final DevModeContext devModeContext = new DevModeContext(); - for (Map.Entry e : System.getProperties().entrySet()) { - devModeContext.getSystemProperties().put(e.getKey().toString(), (String) e.getValue()); - } - devModeContext.setProjectDir(project.getFile().getParentFile()); - devModeContext.getBuildSystemProperties().putAll((Map) project.getProperties()); - - // this is a minor hack to allow ApplicationConfig to be populated with defaults - devModeContext.getBuildSystemProperties().putIfAbsent("quarkus.application.name", project.getArtifactId()); - devModeContext.getBuildSystemProperties().putIfAbsent("quarkus.application.version", project.getVersion()); - - devModeContext.setSourceEncoding(getSourceEncoding()); - - // Set compilation flags. Try the explicitly given configuration first. Otherwise, - // refer to the configuration of the Maven Compiler Plugin. - final Optional compilerPluginConfiguration = findCompilerPluginConfiguration(); - if (compilerArgs != null) { - devModeContext.setCompilerOptions(compilerArgs); - } else if (compilerPluginConfiguration.isPresent()) { - final Xpp3Dom compilerPluginArgsConfiguration = compilerPluginConfiguration.get().getChild("compilerArgs"); - if (compilerPluginArgsConfiguration != null) { - List compilerPluginArgs = new ArrayList<>(); - for (Xpp3Dom argConfiguration : compilerPluginArgsConfiguration.getChildren()) { - compilerPluginArgs.add(argConfiguration.getValue()); - } - // compilerArgs can also take a value without using arg - if (compilerPluginArgsConfiguration.getValue() != null - && !compilerPluginArgsConfiguration.getValue().isEmpty()) { - compilerPluginArgs.add(compilerPluginArgsConfiguration.getValue().trim()); - } - devModeContext.setCompilerOptions(compilerPluginArgs); + // compilerArgs can also take a value without using arg + if (compilerPluginArgsConfiguration.getValue() != null + && !compilerPluginArgsConfiguration.getValue().isEmpty()) { + compilerPluginArgs.add(compilerPluginArgsConfiguration.getValue().trim()); } + builder.compilerOptions(compilerPluginArgs); } - if (source != null) { - devModeContext.setSourceJavaVersion(source); - } else if (compilerPluginConfiguration.isPresent()) { - final Xpp3Dom javacSourceVersion = compilerPluginConfiguration.get().getChild("source"); - if (javacSourceVersion != null && javacSourceVersion.getValue() != null - && !javacSourceVersion.getValue().trim().isEmpty()) { - devModeContext.setSourceJavaVersion(javacSourceVersion.getValue().trim()); - } + } + if (source != null) { + builder.sourceJavaVersion(source); + } else if (compilerPluginConfiguration.isPresent()) { + final Xpp3Dom javacSourceVersion = compilerPluginConfiguration.get().getChild("source"); + if (javacSourceVersion != null && javacSourceVersion.getValue() != null + && !javacSourceVersion.getValue().trim().isEmpty()) { + builder.sourceJavaVersion(javacSourceVersion.getValue().trim()); } - if (target != null) { - devModeContext.setTargetJvmVersion(target); - } else if (compilerPluginConfiguration.isPresent()) { - final Xpp3Dom javacTargetVersion = compilerPluginConfiguration.get().getChild("target"); - if (javacTargetVersion != null && javacTargetVersion.getValue() != null - && !javacTargetVersion.getValue().trim().isEmpty()) { - devModeContext.setTargetJvmVersion(javacTargetVersion.getValue().trim()); - } + } + if (target != null) { + builder.targetJavaVersion(target); + } else if (compilerPluginConfiguration.isPresent()) { + final Xpp3Dom javacTargetVersion = compilerPluginConfiguration.get().getChild("target"); + if (javacTargetVersion != null && javacTargetVersion.getValue() != null + && !javacTargetVersion.getValue().trim().isEmpty()) { + builder.targetJavaVersion(javacTargetVersion.getValue().trim()); } + } - setKotlinSpecificFlags(devModeContext); - if (noDeps) { - final LocalProject localProject = LocalProject.load(project.getModel().getPomFile().toPath()); - addProject(devModeContext, localProject, true); - pomFiles.add(localProject.getRawModel().getPomFile().toPath()); - devModeContext.getLocalArtifacts() - .add(new AppArtifactKey(localProject.getGroupId(), localProject.getArtifactId(), null, "jar")); - } else { - final LocalProject localProject = LocalProject.loadWorkspace(project.getModel().getPomFile().toPath()); - for (LocalProject project : filterExtensionDependencies(localProject)) { - addProject(devModeContext, project, project == localProject); - pomFiles.add(project.getRawModel().getPomFile().toPath()); - devModeContext.getLocalArtifacts() - .add(new AppArtifactKey(project.getGroupId(), project.getArtifactId(), null, "jar")); - } + setKotlinSpecificFlags(builder); + if (noDeps) { + final LocalProject localProject = LocalProject.load(project.getModel().getPomFile().toPath()); + addProject(builder, localProject, true); + builder.watchedBuildFile(localProject.getRawModel().getPomFile().toPath()); + builder.localArtifact(new AppArtifactKey(localProject.getGroupId(), localProject.getArtifactId(), null, "jar")); + } else { + final LocalProject localProject = LocalProject.loadWorkspace(project.getModel().getPomFile().toPath()); + for (LocalProject project : filterExtensionDependencies(localProject)) { + addProject(builder, project, project == localProject); + builder.watchedBuildFile(project.getRawModel().getPomFile().toPath()); + builder.localArtifact(new AppArtifactKey(project.getGroupId(), project.getArtifactId(), null, "jar")); } + } - addQuarkusDevModeDeps(classPathManifest); - - args.add("-Djava.util.logging.manager=org.jboss.logmanager.LogManager"); - - //in most cases these are not used, however they need to be present for some - //parent-first cases such as logging - //first we go through and get all the parent first artifacts - Set parentFirstArtifacts = new HashSet<>(); - for (Artifact appDep : project.getArtifacts()) { - if (appDep.getArtifactHandler().getExtension().equals("jar") && appDep.getFile().isFile()) { - try (ZipFile file = new ZipFile(appDep.getFile())) { - ZipEntry entry = file.getEntry(EXT_PROPERTIES_PATH); - if (entry != null) { - Properties p = new Properties(); - try (InputStream inputStream = file.getInputStream(entry)) { - p.load(inputStream); - String parentFirst = p.getProperty(AppModel.PARENT_FIRST_ARTIFACTS); - if (parentFirst != null) { - String[] artifacts = parentFirst.split(","); - for (String artifact : artifacts) { - parentFirstArtifacts.add(new AppArtifactKey(artifact.split(":"))); - } + addQuarkusDevModeDeps(builder); + + //in most cases these are not used, however they need to be present for some + //parent-first cases such as logging + //first we go through and get all the parent first artifacts + Set parentFirstArtifacts = new HashSet<>(); + for (Artifact appDep : project.getArtifacts()) { + if (appDep.getArtifactHandler().getExtension().equals("jar") && appDep.getFile().isFile()) { + try (ZipFile file = new ZipFile(appDep.getFile())) { + ZipEntry entry = file.getEntry(EXT_PROPERTIES_PATH); + if (entry != null) { + Properties p = new Properties(); + try (InputStream inputStream = file.getInputStream(entry)) { + p.load(inputStream); + String parentFirst = p.getProperty(AppModel.PARENT_FIRST_ARTIFACTS); + if (parentFirst != null) { + String[] artifacts = parentFirst.split(","); + for (String artifact : artifacts) { + parentFirstArtifacts.add(new AppArtifactKey(artifact.split(":"))); } } - } + } } } - for (Artifact appDep : project.getArtifacts()) { - // only add the artifact if it's present in the dev mode context - // we need this to avoid having jars on the classpath multiple times - AppArtifactKey key = new AppArtifactKey(appDep.getGroupId(), appDep.getArtifactId(), - appDep.getClassifier(), appDep.getArtifactHandler().getExtension()); - if (!devModeContext.getLocalArtifacts().contains(key) && parentFirstArtifacts.contains(key)) { - addToClassPaths(classPathManifest, appDep.getFile()); - } + } + for (Artifact appDep : project.getArtifacts()) { + // only add the artifact if it's present in the dev mode context + // we need this to avoid having jars on the classpath multiple times + AppArtifactKey key = new AppArtifactKey(appDep.getGroupId(), appDep.getArtifactId(), + appDep.getClassifier(), appDep.getArtifactHandler().getExtension()); + if (!builder.isLocal(key) && parentFirstArtifacts.contains(key)) { + builder.classpathEntry(appDep.getFile()); } + } - //now we need to build a temporary jar to actually run + builder.baseName(project.getBuild().getFinalName()); - File tempFile = new File(buildDir, project.getArtifactId() + "-dev.jar"); - tempFile.delete(); - // Only delete the -dev.jar on exit if requested - if (deleteDevJar) { - tempFile.deleteOnExit(); - } - getLog().debug("Executable jar: " + tempFile.getAbsolutePath()); - - devModeContext.setBaseName(project.getBuild().getFinalName()); - devModeContext.setCacheDir(new File(buildDir, "transformer-cache").getAbsoluteFile()); - - // this is the jar file we will use to launch the dev mode main class - devModeContext.setDevModeRunnerJarFile(tempFile); - - modifyDevModeContext(devModeContext); - - try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(tempFile))) { - out.putNextEntry(new ZipEntry("META-INF/")); - Manifest manifest = new Manifest(); - manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); - manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, classPathManifest.toString()); - manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, DevModeMain.class.getName()); - out.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); - manifest.write(out); - - out.putNextEntry(new ZipEntry(DevModeMain.DEV_MODE_CONTEXT)); - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - ObjectOutputStream obj = new ObjectOutputStream(new DataOutputStream(bytes)); - obj.writeObject(devModeContext); - obj.close(); - out.write(bytes.toByteArray()); - } + modifyDevModeContext(builder); - outputDirectory.mkdirs(); - // if the --enable-preview flag was set, then we need to enable it when launching dev mode as well - if (devModeContext.isEnablePreview()) { - args.add(DevModeContext.ENABLE_PREVIEW_FLAG); - } + propagateUserProperties(builder); - propagateUserProperties(); + return builder.build(); + } - args.add("-jar"); - args.add(tempFile.getAbsolutePath()); - if (argsString != null) { - args.addAll(Arrays.asList(CommandLineUtils.translateCommandline(argsString))); - } + private void propagateUserProperties(MavenDevModeLauncher.Builder builder) { + Properties userProps = BootstrapMavenOptions.newInstance().getSystemProperties(); + if (userProps == null) { + return; } - - private void propagateUserProperties() { - Properties userProps = BootstrapMavenOptions.newInstance().getSystemProperties(); - if (userProps == null) { - return; - } - final StringBuilder buf = new StringBuilder(); - buf.append("-D"); - for (Object o : userProps.keySet()) { - String name = o.toString(); - final String value = userProps.getProperty(name); - buf.setLength(2); - buf.append(name); - if (value != null && !value.isEmpty()) { - buf.append('='); - buf.append(value); - } - args.add(buf.toString()); - } + final StringBuilder buf = new StringBuilder(); + buf.append("-D"); + for (Object o : userProps.keySet()) { + String name = o.toString(); + final String value = userProps.getProperty(name); + buf.setLength(2); + buf.append(name); + if (value != null && !value.isEmpty()) { + buf.append('='); + buf.append(value); + } + builder.jvmArgs(buf.toString()); } + } - private void addQuarkusDevModeDeps(StringBuilder classPathManifest) - throws MojoExecutionException, DependencyResolutionException { - final String pomPropsPath = "META-INF/maven/io.quarkus/quarkus-core-deployment/pom.properties"; - final InputStream devModePomPropsIs = DevModeMain.class.getClassLoader().getResourceAsStream(pomPropsPath); - if (devModePomPropsIs == null) { - throw new MojoExecutionException("Failed to locate " + pomPropsPath + " on the classpath"); - } - final Properties devModeProps = new Properties(); - try (InputStream is = devModePomPropsIs) { - devModeProps.load(is); - } catch (IOException e) { - throw new MojoExecutionException("Failed to load " + pomPropsPath + " from the classpath", e); - } - final String devModeGroupId = devModeProps.getProperty("groupId"); - if (devModeGroupId == null) { - throw new MojoExecutionException("Classpath resource " + pomPropsPath + " is missing groupId"); - } - final String devModeArtifactId = devModeProps.getProperty("artifactId"); - if (devModeArtifactId == null) { - throw new MojoExecutionException("Classpath resource " + pomPropsPath + " is missing artifactId"); - } - final String devModeVersion = devModeProps.getProperty("version"); - if (devModeVersion == null) { - throw new MojoExecutionException("Classpath resource " + pomPropsPath + " is missing version"); - } - - final DefaultArtifact devModeJar = new DefaultArtifact(devModeGroupId, devModeArtifactId, "jar", devModeVersion); - final DependencyResult cpRes = repoSystem.resolveDependencies(repoSession, - new DependencyRequest() - .setCollectRequest( - new CollectRequest() - .setRoot(new org.eclipse.aether.graph.Dependency(devModeJar, JavaScopes.RUNTIME)) - .setRepositories(repos))); - - for (ArtifactResult appDep : cpRes.getArtifactResults()) { - //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, appDep.getArtifact().getFile()); - } - } + private void addQuarkusDevModeDeps(MavenDevModeLauncher.Builder builder) + throws MojoExecutionException, DependencyResolutionException { + final String pomPropsPath = "META-INF/maven/io.quarkus/quarkus-core-deployment/pom.properties"; + final InputStream devModePomPropsIs = DevModeMain.class.getClassLoader().getResourceAsStream(pomPropsPath); + if (devModePomPropsIs == null) { + throw new MojoExecutionException("Failed to locate " + pomPropsPath + " on the classpath"); + } + final Properties devModeProps = new Properties(); + try (InputStream is = devModePomPropsIs) { + devModeProps.load(is); + } catch (IOException e) { + throw new MojoExecutionException("Failed to load " + pomPropsPath + " from the classpath", e); + } + final String devModeGroupId = devModeProps.getProperty("groupId"); + if (devModeGroupId == null) { + throw new MojoExecutionException("Classpath resource " + pomPropsPath + " is missing groupId"); + } + final String devModeArtifactId = devModeProps.getProperty("artifactId"); + if (devModeArtifactId == null) { + throw new MojoExecutionException("Classpath resource " + pomPropsPath + " is missing artifactId"); + } + final String devModeVersion = devModeProps.getProperty("version"); + if (devModeVersion == null) { + throw new MojoExecutionException("Classpath resource " + pomPropsPath + " is missing version"); } - private void setKotlinSpecificFlags(DevModeContext devModeContext) { - Plugin kotlinMavenPlugin = null; - for (Plugin plugin : project.getBuildPlugins()) { - if (plugin.getKey().equals(KOTLIN_MAVEN_PLUGIN_GA)) { - kotlinMavenPlugin = plugin; - break; - } - } - - if (kotlinMavenPlugin == null) { - return; - } - - getLog().debug("Kotlin Maven plugin detected"); + final DefaultArtifact devModeJar = new DefaultArtifact(devModeGroupId, devModeArtifactId, "jar", devModeVersion); + final DependencyResult cpRes = repoSystem.resolveDependencies(repoSession, + new DependencyRequest() + .setCollectRequest( + new CollectRequest() + .setRoot(new org.eclipse.aether.graph.Dependency(devModeJar, JavaScopes.RUNTIME)) + .setRepositories(repos))); - List compilerPluginArtifacts = new ArrayList<>(); - List dependencies = kotlinMavenPlugin.getDependencies(); - for (Dependency dependency : dependencies) { - try { - ArtifactResult resolvedArtifact = repoSystem.resolveArtifact(repoSession, - new ArtifactRequest() - .setArtifact(new DefaultArtifact(dependency.getGroupId(), dependency.getArtifactId(), - dependency.getClassifier(), dependency.getType(), dependency.getVersion())) - .setRepositories(repos)); - - compilerPluginArtifacts.add(resolvedArtifact.getArtifact().getFile().toPath().toAbsolutePath().toString()); - } catch (ArtifactResolutionException e) { - getLog().warn("Unable to properly setup dev-mode for Kotlin", e); - return; - } + for (ArtifactResult appDep : cpRes.getArtifactResults()) { + //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"))) { + builder.classpathEntry(appDep.getArtifact().getFile()); } - devModeContext.setCompilerPluginArtifacts(compilerPluginArtifacts); + } + } - List options = new ArrayList<>(); - Xpp3Dom compilerPluginConfiguration = (Xpp3Dom) kotlinMavenPlugin.getConfiguration(); - if (compilerPluginConfiguration != null) { - Xpp3Dom compilerPluginArgsConfiguration = compilerPluginConfiguration.getChild("pluginOptions"); - if (compilerPluginArgsConfiguration != null) { - for (Xpp3Dom argConfiguration : compilerPluginArgsConfiguration.getChildren()) { - options.add(argConfiguration.getValue()); - } - } + private void setKotlinSpecificFlags(MavenDevModeLauncher.Builder builder) { + Plugin kotlinMavenPlugin = null; + for (Plugin plugin : project.getBuildPlugins()) { + if (plugin.getKey().equals(KOTLIN_MAVEN_PLUGIN_GA)) { + kotlinMavenPlugin = plugin; + break; } - devModeContext.setCompilerPluginsOptions(options); } - public Set getPomFiles() { - return pomFiles; + if (kotlinMavenPlugin == null) { + return; } - public void run() throws Exception { - // Display the launch command line in dev mode - if (getLog().isDebugEnabled()) { - getLog().debug("Launching JVM with command line: " + String.join(" ", args)); + getLog().debug("Kotlin Maven plugin detected"); + + List compilerPluginArtifacts = new ArrayList<>(); + List dependencies = kotlinMavenPlugin.getDependencies(); + for (Dependency dependency : dependencies) { + try { + ArtifactResult resolvedArtifact = repoSystem.resolveArtifact(repoSession, + new ArtifactRequest() + .setArtifact(new DefaultArtifact(dependency.getGroupId(), dependency.getArtifactId(), + dependency.getClassifier(), dependency.getType(), dependency.getVersion())) + .setRepositories(repos)); + + compilerPluginArtifacts.add(resolvedArtifact.getArtifact().getFile().toPath().toAbsolutePath().toString()); + } catch (ArtifactResolutionException e) { + getLog().warn("Unable to properly setup dev-mode for Kotlin", e); + return; } - process = new ProcessBuilder(args) - .inheritIO() - .directory(workingDir) - .start(); - //https://github.com/quarkusio/quarkus/issues/232 - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - @Override - public void run() { - process.destroy(); - try { - process.waitFor(); - } catch (InterruptedException ignored) { - getLog().warn("Unable to properly wait for dev-mode end", ignored); - } - } - }, "Development Mode Shutdown Hook")); } - - public void stop() throws InterruptedException { - process.destroy(); - process.waitFor(); + builder.compilerPluginArtifacts(compilerPluginArtifacts); + + List options = new ArrayList<>(); + Xpp3Dom compilerPluginConfiguration = (Xpp3Dom) kotlinMavenPlugin.getConfiguration(); + if (compilerPluginConfiguration != null) { + Xpp3Dom compilerPluginArgsConfiguration = compilerPluginConfiguration.getChild("pluginOptions"); + if (compilerPluginArgsConfiguration != null) { + for (Xpp3Dom argConfiguration : compilerPluginArgsConfiguration.getChildren()) { + options.add(argConfiguration.getValue()); + } + } } - + builder.compilerPluginOptions(options); } - protected void modifyDevModeContext(DevModeContext devModeContext) { + protected void modifyDevModeContext(MavenDevModeLauncher.Builder builder) { } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/MavenDevModeLauncher.java b/devtools/maven/src/main/java/io/quarkus/maven/MavenDevModeLauncher.java new file mode 100644 index 0000000000000..ed5ec9503fa28 --- /dev/null +++ b/devtools/maven/src/main/java/io/quarkus/maven/MavenDevModeLauncher.java @@ -0,0 +1,44 @@ +package io.quarkus.maven; + +import org.apache.maven.plugin.logging.Log; + +import io.quarkus.deployment.dev.QuarkusDevModeLauncher; + +public class MavenDevModeLauncher extends QuarkusDevModeLauncher { + + public static Builder builder(Log log) { + return new MavenDevModeLauncher(log).new Builder(); + } + + public class Builder extends QuarkusDevModeLauncher.Builder { + + private Builder() { + } + } + + private final Log log; + + private MavenDevModeLauncher(Log log) { + this.log = log; + } + + @Override + protected boolean isDebugEnabled() { + return log.isDebugEnabled(); + } + + @Override + protected void debug(Object msg) { + log.error(msg == null ? "null" : msg.toString()); + } + + @Override + protected void error(Object msg) { + log.error(msg == null ? "null" : msg.toString()); + } + + @Override + protected void warn(Object msg) { + log.warn(msg == null ? "null" : msg.toString()); + } +} diff --git a/devtools/maven/src/main/java/io/quarkus/maven/RemoteDevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/RemoteDevMojo.java index 14b0b1505c986..a2828b3bc2755 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/RemoteDevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/RemoteDevMojo.java @@ -4,20 +4,13 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.ResolutionScope; -import io.quarkus.bootstrap.app.QuarkusBootstrap; -import io.quarkus.deployment.dev.DevModeContext; -import io.quarkus.deployment.dev.IsolatedRemoteDevModeMain; - /** * The dev mojo, that connects to a remote host. */ @Mojo(name = "remote-dev", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) public class RemoteDevMojo extends DevMojo { @Override - protected void modifyDevModeContext(DevModeContext devModeContext) { - //we use prod mode here as we are going to generate a production - //application to sync to the server - devModeContext.setMode(QuarkusBootstrap.Mode.PROD); - devModeContext.setAlternateEntryPoint(IsolatedRemoteDevModeMain.class.getName()); + protected void modifyDevModeContext(MavenDevModeLauncher.Builder builder) { + builder.remoteDev(true); } } diff --git a/extensions/vertx-http/runtime/pom.xml b/extensions/vertx-http/runtime/pom.xml index c99240b785d8f..1aacc30bce94e 100644 --- a/extensions/vertx-http/runtime/pom.xml +++ b/extensions/vertx-http/runtime/pom.xml @@ -22,10 +22,6 @@ io.quarkus quarkus-security-runtime-spi - - io.quarkus - quarkus-development-mode-spi - io.quarkus.security quarkus-security