diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateExtensionMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateExtensionMojo.java index d79ffbc02..93ea97e25 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateExtensionMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateExtensionMojo.java @@ -1,5 +1,6 @@ package io.quarkus.maven; +import java.io.File; import java.io.IOException; import java.io.Reader; import java.io.Writer; @@ -19,15 +20,21 @@ import java.util.stream.Collectors; import javax.lang.model.SourceVersion; import org.apache.maven.model.Model; -import org.apache.maven.model.Plugin; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactDescriptorResult; import org.jboss.logging.Logger; import freemarker.cache.ClassTemplateLoader; @@ -38,6 +45,11 @@ import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import freemarker.template.TemplateExceptionHandler; +import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; +import io.quarkus.bootstrap.resolver.maven.MavenRepoInitializer; +import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; +import io.quarkus.bootstrap.resolver.maven.workspace.LocalWorkspace; +import io.quarkus.maven.utilities.MojoUtils; import io.quarkus.maven.utilities.PomTransformer; import io.quarkus.maven.utilities.PomTransformer.Gavtcs; import io.quarkus.maven.utilities.PomTransformer.Transformation; @@ -66,20 +78,46 @@ public class CreateExtensionMojo extends AbstractMojo { private static final String CLASSPATH_PREFIX = "classpath:"; private static final String FILE_PREFIX = "file:"; + private static final String QUARKUS_VERSION_PROP = "quarkus.version"; + static final String DEFAULT_ENCODING = "utf-8"; - static final String DEFAULT_QUARKUS_VERSION = "@{quarkus.version}"; + static final String DEFAULT_QUARKUS_VERSION = "@{" + QUARKUS_VERSION_PROP + "}"; + static final String QUARKUS_VERSION_POM_EXPR = "${" + QUARKUS_VERSION_PROP + "}"; static final String DEFAULT_BOM_ENTRY_VERSION = "@{project.version}"; static final String DEFAULT_TEMPLATES_URI_BASE = "classpath:/create-extension-templates"; static final String DEFAULT_NAME_SEGMENT_DELIMITER = " - "; static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("@\\{([^\\}]+)\\}"); + static final String PLATFORM_DEFAULT_GROUP_ID = "io.quarkus"; + static final String PLATFORM_DEFAULT_ARTIFACT_ID = "quarkus-bom-deployment"; + + static final String COMPILER_PLUGIN_VERSION_PROP = "compiler-plugin.version"; + static final String COMPILER_PLUGIN_VERSION_POM_EXPR = "${" + COMPILER_PLUGIN_VERSION_PROP + "}"; + static final String COMPILER_PLUGIN_DEFAULT_VERSION = "3.8.1"; + private static final String COMPILER_PLUGIN_GROUP_ID = "org.apache.maven.plugins"; + private static final String COMPILER_PLUGIN_ARTIFACT_ID = "maven-compiler-plugin"; + private static final String COMPILER_PLUGIN_KEY = COMPILER_PLUGIN_GROUP_ID + ":" + + COMPILER_PLUGIN_ARTIFACT_ID; + /** * Directory where the changes should be performed. Default is the current directory of the current Java process. * * @since 0.20.0 */ @Parameter(property = "quarkus.basedir") - Path basedir; + File basedir; + + /** + * The {@code groupId} of the Quarkus platform's BOM containing deployment dependencies. + */ + @Parameter(property = "platformGroupId", defaultValue = PLATFORM_DEFAULT_GROUP_ID) + String platformGroupId; + + /** + * The {@code artifactId} of the Quarkus platform's BOM containing deployment dependencies. + */ + @Parameter(property = "platformArtifactId", defaultValue = PLATFORM_DEFAULT_ARTIFACT_ID) + String platformArtifactId; /** * The {@code groupId} for the newly created Maven modules. If {@code groupId} is left unset, the {@code groupId} @@ -87,9 +125,19 @@ public class CreateExtensionMojo extends AbstractMojo { * * @since 0.20.0 */ - @Parameter(property = "quarkus.groupId") + @Parameter(property = "groupId") String groupId; + /** + * This parameter was introduced to change the property of {@code groupId} parameter from {@code quarkus.groupId} to + * {@code groupId} + * + * @since 1.3.0 + */ + @Deprecated + @Parameter(property = "quarkus.groupId") + String deprecatedGroupId; + /** * {@code artifactId} of the runtime module. The {@code artifactId}s of the extension parent * (${artifactId}-parent) and deployment (${artifactId}-deployment) modules will be based @@ -104,21 +152,31 @@ public class CreateExtensionMojo extends AbstractMojo { * * @since 0.20.0 */ - @Parameter(property = "quarkus.artifactId") + @Parameter(property = "artifactId") String artifactId; + /** + * This parameter was introduced to change the property of {@code artifactId} parameter from {@code quarkus.artifactId} to + * {@code artifactId} + * + * @since 1.3.0 + */ + @Deprecated + @Parameter(property = "quarkus.artifactId") + String deprecatedArtifactId; + /** * A prefix common to all extension artifactIds in the current source tree. If you set {@link #artifactIdPrefix}, * set also {@link #artifactIdBase}, but do not set {@link #artifactId}. * * @since 0.20.0 */ - @Parameter(property = "quarkus.artifactIdPrefix") + @Parameter(property = "quarkus.artifactIdPrefix", defaultValue = "") String artifactIdPrefix; /** - * The unique part of the {@link #artifactId}. If you set {@link #artifactIdBase}, set also - * {@link #artifactIdPrefix}, but do not set {@link #artifactId}. + * The unique part of the {@link #artifactId}. If you set {@link #artifactIdBase}, {@link #artifactIdPrefix} + * may also be set, but not {@link #artifactId}. * * @since 0.20.0 */ @@ -131,9 +189,19 @@ public class CreateExtensionMojo extends AbstractMojo { * * @since 0.20.0 */ - @Parameter(property = "quarkus.artifactVersion") + @Parameter(property = "version") String version; + /** + * This parameter was introduced to change the property of {@code version} parameter from {@code quarkus.artifactVersion} to + * {@code version} + * + * @since 1.3.0 + */ + @Deprecated + @Parameter(property = "quarkus.artifactVersion") + String deprecatedVersion; + /** * The {@code name} of the runtime module. The {@code name}s of the extension parent and deployment modules will be * based on this {@code name} too. @@ -253,17 +321,25 @@ public class CreateExtensionMojo extends AbstractMojo { * * @since 0.20.0 */ - @Parameter(defaultValue = DEFAULT_QUARKUS_VERSION, required = true, property = "quarkus.quarkusVersion") + @Parameter(defaultValue = DEFAULT_QUARKUS_VERSION, required = true, property = "quarkus.version") String quarkusVersion; + /** + * This parameter was introduced to change the property of {@code artifactId} parameter from {@code quarkus.artifactId} to + * {@code artifactId} + * + * @since 1.3.0 + */ + @Deprecated + @Parameter(property = "quarkus.quarkusVersion") + String deprecatedQuarkusVersion; + /** * If {@code true} the Maven dependencies in Runtime and Deployment modules will not have their versions set and the * {@code quarkus-bootstrap-maven-plugin} in the Runtime module will not have its version set and it will have no * executions configured. If {@code false} the version set in {@link #quarkusVersion} will be used where applicable * and {@code quarkus-bootstrap-maven-plugin} in the Runtime module will be configured explicitly. If the value is - * {@code null} the mojo attempts to autodetect the value inspecting the POM hierarchy of the current project: The - * value is {@code true} if {@code quarkus-bootstrap-maven-plugin} is defined in the {@code pluginManagement} - * section of the effective model of the current Maven module; otherwise the value is {@code false}. + * {@code null} the parameter will be treated as it was initialized to {@code false}. * * @since 0.20.0 */ @@ -307,7 +383,7 @@ public class CreateExtensionMojo extends AbstractMojo { /** * Path relative to {@link #basedir} pointing at a {@code pom.xml} file containing the BOM (Bill of Materials) that * manages runtime extension artifacts. If set, the newly created Runtime module will be added to - * {@code } section of this bom; otherwise the newly created Runtime module will not be added + * {@code } section of this bom; otherwise the newly created Runtime module will not be added * to any BOM. * * @since 0.21.0 @@ -318,7 +394,7 @@ public class CreateExtensionMojo extends AbstractMojo { /** * Path relative to {@link #basedir} pointing at a {@code pom.xml} file containing the BOM (Bill of Materials) that * manages deployment time extension artifacts. If set, the newly created Deployment module will be added to - * {@code } section of this bom; otherwise the newly created Deployment module will not be + * {@code } section of this bom; otherwise the newly created Deployment module will not be * added to any BOM. * * @since 0.21.0 @@ -370,18 +446,89 @@ public class CreateExtensionMojo extends AbstractMojo { @Parameter(defaultValue = "${project}", readonly = true) MavenProject project; + /** + * The entry point to Aether, i.e. the component doing all the work. + * + * @component + */ + @Component + private RepositorySystem repoSystem; + + /** + * The current repository/network configuration of Maven. + * + * @parameter default-value="${repositorySystemSession}" + * @readonly + */ + @Parameter(defaultValue = "${repositorySystemSession}", readonly = true) + private RepositorySystemSession repoSession; + + /** + * The project's remote repositories to use for the resolution of artifacts and their dependencies. + * + * @parameter default-value="${project.remoteProjectRepositories}" + * @readonly + */ + @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true) + private List repos; + + /** + * The version of {@code org.apache.maven.plugins:maven-compiler-plugin} that should be used for + * the extension project. + */ + @Parameter(defaultValue = COMPILER_PLUGIN_DEFAULT_VERSION, required = true, property = "quarkus.mavenCompilerPluginVersion") + String compilerPluginVersion; + + boolean currentProjectIsBaseDir; + + Charset charset; + @Override public void execute() throws MojoExecutionException, MojoFailureException { if (this.basedir == null) { - this.basedir = Paths.get(".").toAbsolutePath().normalize(); + currentProjectIsBaseDir = true; + this.basedir = new File(".").getAbsoluteFile(); } + + if (deprecatedGroupId != null) { + if (groupId != null) { + throw new MojoExecutionException("Either groupId or deprecatedGroupId can be set at a time but got groupId=" + + groupId + " and deprecatedGroupId=" + deprecatedGroupId); + } + groupId = deprecatedGroupId; + } + if (deprecatedArtifactId != null) { + if (artifactId != null) { + throw new MojoExecutionException( + "Either artifactId or deprecatedArtifactId can be set at a time but got artifactId=" + artifactId + + " and deprecatedArtifactId=" + deprecatedArtifactId); + } + artifactId = deprecatedArtifactId; + } + if (deprecatedVersion != null) { + if (version != null) { + throw new MojoExecutionException("Either version or deprecatedVersion can be set at a time but got version=" + + version + " and deprecatedVersion=" + deprecatedVersion); + } + version = deprecatedVersion; + } + if (deprecatedQuarkusVersion != null) { + if (quarkusVersion != null && !DEFAULT_QUARKUS_VERSION.equals(quarkusVersion)) { + throw new MojoExecutionException( + "Either quarkusVersion or deprecatedQuarkusVersion can be set at a time but got quarkusVersion=" + + quarkusVersion + " and deprecatedQuarkusVersion=" + deprecatedQuarkusVersion); + } + quarkusVersion = deprecatedQuarkusVersion; + } + if (artifactId != null) { artifactIdBase = artifactIdBase(artifactId); artifactIdPrefix = artifactId.substring(0, artifactId.length() - artifactIdBase.length()); artifactId = BRACKETS_PATTERN.matcher(artifactId).replaceAll(""); - } else if (artifactIdPrefix != null && artifactIdBase != null) { - artifactId = artifactIdPrefix + artifactIdBase; + } else if (artifactIdBase != null) { + artifactId = artifactIdPrefix == null || artifactIdPrefix.isEmpty() ? artifactIdBase + : artifactIdPrefix + artifactIdBase; } else { throw new MojoFailureException(String.format( "Either artifactId or both artifactIdPrefix and artifactIdBase must be specified; found: artifactId=[%s], artifactIdPrefix=[%s], artifactIdBase[%s]", @@ -411,123 +558,318 @@ public class CreateExtensionMojo extends AbstractMojo { } if (runtimeBomPath != null) { - runtimeBomPath = basedir.resolve(runtimeBomPath); + runtimeBomPath = basedir.toPath().resolve(runtimeBomPath); if (!Files.exists(runtimeBomPath)) { throw new MojoFailureException("runtimeBomPath does not exist: " + runtimeBomPath); } } if (deploymentBomPath != null) { - deploymentBomPath = basedir.resolve(deploymentBomPath); + deploymentBomPath = basedir.toPath().resolve(deploymentBomPath); if (!Files.exists(deploymentBomPath)) { throw new MojoFailureException("deploymentBomPath does not exist: " + deploymentBomPath); } } - final Charset charset = Charset.forName(encoding); + charset = Charset.forName(encoding); - final Path basePomXml = basedir.resolve("pom.xml"); - if (Files.exists(basePomXml)) { - try (Reader r = Files.newBufferedReader(basePomXml, charset)) { - Model basePom = new MavenXpp3Reader().read(r); - if (!"pom".equals(basePom.getPackaging())) { + try { + File rootPom = null; + Model rootModel = null; + boolean importDeploymentBom = true; + boolean setCompilerPluginVersion = true; + boolean setQuarkusVersionProp = true; + if (isCurrentProjectExists()) { + rootPom = getCurrentProjectPom(); + rootModel = MojoUtils.readPom(rootPom); + if (!"pom".equals(rootModel.getPackaging())) { throw new MojoFailureException( "Can add extension modules only under a project with packaging 'pom'; found: " - + basePom.getPackaging() + ""); + + rootModel.getPackaging() + ""); + } + + if (rootPom.equals(project.getFile())) { + importDeploymentBom = !hasQuarkusDeploymentBom(); + setCompilerPluginVersion = !project.getPluginManagement().getPluginsAsMap() + .containsKey(COMPILER_PLUGIN_KEY); + setQuarkusVersionProp = !project.getProperties().containsKey(QUARKUS_VERSION_PROP); + } else { + // aloubyansky: not sure we should support this case and not sure it ever worked properly + // this is about creating an extension project not in the context of the current project from the Maven's plugin perspective + // kind of a pathological use-case from the Maven's perspective, imo + final DefaultArtifact rootArtifact = new DefaultArtifact(getGroupId(rootModel), + rootModel.getArtifactId(), null, rootModel.getPackaging(), getVersion(rootModel)); + try { + final LocalWorkspace ws = LocalProject.loadWorkspace(rootPom.getParentFile().toPath()).getWorkspace(); + final MavenArtifactResolver mvn = MavenArtifactResolver.builder() + .setRepositorySystem(MavenRepoInitializer.getRepositorySystem(repoSession.isOffline(), ws)) + .setRepositorySystemSession(repoSession) + .setRemoteRepositories(repos) + .setWorkspace(LocalProject.loadWorkspace(rootPom.getParentFile().toPath()).getWorkspace()) + .build(); + final ArtifactDescriptorResult rootDescr = mvn.resolveDescriptor(rootArtifact); + importDeploymentBom = !hasQuarkusDeploymentBom(rootDescr.getManagedDependencies()); + // TODO determine whether the compiler plugin is configured for the project + setQuarkusVersionProp = !rootDescr.getProperties().containsKey(QUARKUS_VERSION_PROP); + } catch (Exception e) { + throw new MojoExecutionException("Failed to resolve " + rootArtifact + " descriptor", e); + } + } + } else if (this.grandParentRelativePath != null) { + // aloubyansky: not sure we should support this case, same as above + final File gpPom = getExtensionProjectBaseDir().resolve(this.grandParentRelativePath).normalize() + .toAbsolutePath().toFile(); + if (gpPom.exists()) { + rootPom = gpPom; + rootModel = MojoUtils.readPom(gpPom); + final DefaultArtifact rootArtifact = new DefaultArtifact(getGroupId(rootModel), + rootModel.getArtifactId(), null, rootModel.getPackaging(), getVersion(rootModel)); + try { + final LocalWorkspace ws = LocalProject.loadWorkspace(rootPom.getParentFile().toPath()).getWorkspace(); + final MavenArtifactResolver mvn = MavenArtifactResolver.builder() + .setRepositorySystem(MavenRepoInitializer.getRepositorySystem(repoSession.isOffline(), ws)) + .setRepositorySystemSession(repoSession) + .setRemoteRepositories(repos) + .setWorkspace(ws) + .build(); + final ArtifactDescriptorResult rootDescr = mvn.resolveDescriptor(rootArtifact); + importDeploymentBom = !hasQuarkusDeploymentBom(rootDescr.getManagedDependencies()); + // TODO determine whether the compiler plugin is configured for the project + setQuarkusVersionProp = !rootDescr.getProperties().containsKey(QUARKUS_VERSION_PROP); + } catch (Exception e) { + throw new MojoExecutionException("Failed to resolve " + rootArtifact + " descriptor", e); + } } - addModules(basePomXml, basePom, charset); - } catch (IOException e) { - throw new MojoExecutionException(String.format("Could not read %s", basePomXml), e); - } catch (XmlPullParserException e) { - throw new MojoExecutionException(String.format("Could not parse %s", basePomXml), e); - } catch (TemplateException e) { - throw new MojoExecutionException(String.format("Could not process a FreeMarker template"), e); } - } else { - newParent(basedir); + + final TemplateParams templateParams = getTemplateParams(rootModel); + final Configuration cfg = getTemplateConfig(); + + generateExtensionProjects(cfg, templateParams); + if (setQuarkusVersionProp) { + setQuarkusVersionProp(getExtensionProjectBaseDir().resolve("pom.xml").toFile()); + } + if (importDeploymentBom) { + addQuarkusDeploymentBom(getExtensionProjectBaseDir().resolve("pom.xml").toFile()); + } + if (setCompilerPluginVersion) { + setCompilerPluginVersion(getExtensionProjectBaseDir().resolve("pom.xml").toFile()); + } + if (rootModel != null) { + addModules(rootPom.toPath(), templateParams, rootModel); + } + + if (runtimeBomPath != null) { + getLog().info( + String.format("Adding [%s] to dependencyManagement in [%s]", templateParams.artifactId, + runtimeBomPath)); + List transformations = new ArrayList(); + transformations + .add(Transformation.addManagedDependency(templateParams.groupId, templateParams.artifactId, + templateParams.bomEntryVersion)); + for (Gavtcs gavtcs : templateParams.additionalRuntimeDependencies) { + getLog().info(String.format("Adding [%s] to dependencyManagement in [%s]", gavtcs, runtimeBomPath)); + transformations.add(Transformation.addManagedDependency(gavtcs)); + } + pomTransformer(runtimeBomPath).transform(transformations); + } + if (deploymentBomPath != null) { + final String aId = templateParams.artifactId + "-deployment"; + getLog().info(String.format("Adding [%s] to dependencyManagement in [%s]", aId, deploymentBomPath)); + pomTransformer(deploymentBomPath) + .transform(Transformation.addManagedDependency(templateParams.groupId, aId, + templateParams.bomEntryVersion)); + } + if (itestParentPath != null) { + generateItest(cfg, templateParams); + } + } catch (IOException e) { + throw new MojoExecutionException(String.format("Could not read %s", project.getFile()), e); + } catch (TemplateException e) { + throw new MojoExecutionException(String.format("Could not process a FreeMarker template"), e); } } - void addModules(Path basePomXml, Model basePom, Charset charset) + private void setQuarkusVersionProp(File pom) throws IOException, MojoExecutionException { + pomTransformer(pom.toPath()).transform(Transformation.addProperty(QUARKUS_VERSION_PROP, + quarkusVersion.equals(DEFAULT_QUARKUS_VERSION) ? getPluginVersion() : quarkusVersion)); + } + + private void setCompilerPluginVersion(File pom) throws IOException { + pomTransformer(pom.toPath()).transform(Transformation.addProperty(COMPILER_PLUGIN_VERSION_PROP, compilerPluginVersion)); + pomTransformer(pom.toPath()) + .transform(Transformation.addManagedPlugin( + MojoUtils.plugin(COMPILER_PLUGIN_GROUP_ID, COMPILER_PLUGIN_ARTIFACT_ID, + COMPILER_PLUGIN_VERSION_POM_EXPR))); + } + + private void addQuarkusDeploymentBom(File pom) throws IOException, MojoExecutionException { + addQuarkusDeploymentBom(MojoUtils.readPom(pom), pom); + } + + private void addQuarkusDeploymentBom(Model model, File file) throws IOException, MojoExecutionException { + pomTransformer(file.toPath()) + .transform(Transformation.addManagedDependency( + new Gavtcs(platformGroupId, platformArtifactId, QUARKUS_VERSION_POM_EXPR, "pom", null, "import"))); + } + + private String getPluginVersion() throws MojoExecutionException { + return CreateUtils.resolvePluginInfo(CreateExtensionMojo.class).getVersion(); + } + + private boolean hasQuarkusDeploymentBom() { + if (project.getDependencyManagement() == null) { + return false; + } + for (org.apache.maven.model.Dependency dep : project.getDependencyManagement().getDependencies()) { + if (dep.getArtifactId().equals("quarkus-core-deployment") + && dep.getGroupId().equals("io.quarkus")) { + // this is not a 100% accurate check but practically valid + return true; + } + } + return false; + } + + private boolean hasQuarkusDeploymentBom(List deps) { + if (deps == null) { + return false; + } + for (Dependency dep : deps) { + if (dep.getArtifact().getArtifactId().equals("quarkus-core-deployment") + && dep.getArtifact().getGroupId().equals("io.quarkus")) { + // this is not a 100% accurate check but practically valid + return true; + } + } + return false; + } + + /** + * @return true if the goal is executed in an existing project + */ + private boolean isCurrentProjectExists() { + return currentProjectIsBaseDir ? project.getFile() != null + : Files.exists(basedir.toPath().resolve("pom.xml")); + } + + private File getCurrentProjectPom() { + if (currentProjectIsBaseDir) { + return project.getFile() == null ? new File(project.getBasedir(), "pom.xml") : project.getFile(); + } + return new File(basedir, "pom.xml"); + } + + private Path getExtensionProjectBaseDir() { + if (currentProjectIsBaseDir) { + return project.getBasedir() == null ? basedir.toPath().resolve(artifactIdBase) + : project.getBasedir().toPath().resolve(artifactIdBase); + } + return new File(basedir, artifactIdBase).toPath(); + } + + private Path getExtensionRuntimeBaseDir() { + return getExtensionProjectBaseDir().resolve("runtime"); + } + + private Path getExtensionDeploymentBaseDir() { + return getExtensionProjectBaseDir().resolve("deployment"); + } + + void addModules(Path basePomXml, TemplateParams templateParams, Model basePom) throws IOException, TemplateException, MojoFailureException, MojoExecutionException { - - final Configuration cfg = new Configuration(Configuration.VERSION_2_3_28); - cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); - cfg.setTemplateLoader(createTemplateLoader(basedir, templatesUriBase)); - cfg.setDefaultEncoding(charset.name()); - cfg.setInterpolationSyntax(Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX); - cfg.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX); - - TemplateParams model = new TemplateParams(); - - model.artifactId = artifactId; - model.artifactIdPrefix = artifactIdPrefix; - model.artifactIdBase = artifactIdBase; - model.artifactIdBaseCamelCase = toCapCamelCase(model.artifactIdBase); - - model.groupId = this.groupId != null ? this.groupId : getGroupId(basePom); - model.version = this.version != null ? this.version : getVersion(basePom); - - model.namePrefix = namePrefix; - model.nameBase = nameBase; - model.nameSegmentDelimiter = nameSegmentDelimiter; - model.assumeManaged = detectAssumeManaged(); - model.quarkusVersion = quarkusVersion.replace('@', '$'); - model.bomEntryVersion = bomEntryVersion.replace('@', '$'); - - model.grandParentGroupId = grandParentGroupId != null ? grandParentGroupId : getGroupId(basePom); - model.grandParentArtifactId = grandParentArtifactId != null ? grandParentArtifactId : basePom.getArtifactId(); - model.grandParentVersion = grandParentVersion != null ? grandParentVersion : getVersion(basePom); - model.grandParentRelativePath = grandParentRelativePath != null ? grandParentRelativePath : "../pom.xml"; - model.javaPackageBase = javaPackageBase != null ? javaPackageBase - : getJavaPackage(model.groupId, javaPackageInfix, artifactId); - model.additionalRuntimeDependencies = getAdditionalRuntimeDependencies(); - model.runtimeBomPathSet = runtimeBomPath != null; - - evalTemplate(cfg, "parent-pom.xml", basedir.resolve(model.artifactIdBase + "/pom.xml"), charset, model); - - Files.createDirectories(basedir - .resolve(model.artifactIdBase + "/runtime/src/main/java/" + model.javaPackageBase.replace('.', '/'))); - evalTemplate(cfg, "runtime-pom.xml", basedir.resolve(model.artifactIdBase + "/runtime/pom.xml"), charset, - model); - - evalTemplate(cfg, "deployment-pom.xml", basedir.resolve(model.artifactIdBase + "/deployment/pom.xml"), charset, - model); - final Path processorPath = basedir - .resolve(model.artifactIdBase + "/deployment/src/main/java/" + model.javaPackageBase.replace('.', '/') - + "/deployment/" + model.artifactIdBaseCamelCase + "Processor.java"); - evalTemplate(cfg, "Processor.java", processorPath, charset, model); - - if (!basePom.getModules().contains(model.artifactIdBase)) { - getLog().info(String.format("Adding module [%s] to [%s]", model.artifactIdBase, basePomXml)); - new PomTransformer(basePomXml, charset).transform(Transformation.addModule(model.artifactIdBase)); + if (!basePom.getModules().contains(templateParams.artifactIdBase)) { + getLog().info(String.format("Adding module [%s] to [%s]", templateParams.artifactIdBase, basePomXml)); + pomTransformer(basePomXml).transform(Transformation.addModule(templateParams.artifactIdBase)); } - if (runtimeBomPath != null) { - getLog().info( - String.format("Adding [%s] to dependencyManagement in [%s]", model.artifactId, runtimeBomPath)); - List transformations = new ArrayList(); - transformations - .add(Transformation.addManagedDependency(model.groupId, model.artifactId, model.bomEntryVersion)); - for (Gavtcs gavtcs : model.additionalRuntimeDependencies) { - getLog().info(String.format("Adding [%s] to dependencyManagement in [%s]", gavtcs, runtimeBomPath)); - transformations.add(Transformation.addManagedDependency(gavtcs)); - } - new PomTransformer(runtimeBomPath, charset).transform(transformations); - } - if (deploymentBomPath != null) { - final String aId = model.artifactId + "-deployment"; - getLog().info(String.format("Adding [%s] to dependencyManagement in [%s]", aId, deploymentBomPath)); - new PomTransformer(deploymentBomPath, charset) - .transform(Transformation.addManagedDependency(model.groupId, aId, model.bomEntryVersion)); - } - if (itestParentPath != null) { - generateItest(cfg, charset, model); - } - } - void generateItest(Configuration cfg, Charset charset, TemplateParams model) - throws MojoFailureException, MojoExecutionException, TemplateException { - final Path itestParentAbsPath = basedir.resolve(itestParentPath).toAbsolutePath(); + private void generateExtensionProjects(Configuration cfg, TemplateParams templateParams) + throws IOException, TemplateException, MojoExecutionException { + evalTemplate(cfg, "parent-pom.xml", getExtensionProjectBaseDir().resolve("pom.xml"), templateParams); + + Files.createDirectories( + getExtensionRuntimeBaseDir().resolve("src/main/java") + .resolve(templateParams.javaPackageBase.replace('.', '/'))); + evalTemplate(cfg, "runtime-pom.xml", getExtensionRuntimeBaseDir().resolve("pom.xml"), + templateParams); + + evalTemplate(cfg, "deployment-pom.xml", getExtensionDeploymentBaseDir().resolve("pom.xml"), + templateParams); + final Path processorPath = getExtensionDeploymentBaseDir() + .resolve("src/main/java") + .resolve(templateParams.javaPackageBase.replace('.', '/')) + .resolve("deployment") + .resolve(templateParams.artifactIdBaseCamelCase + "Processor.java"); + evalTemplate(cfg, "Processor.java", processorPath, templateParams); + } + + private PomTransformer pomTransformer(Path basePomXml) { + return new PomTransformer(basePomXml, charset); + } + + private TemplateParams getTemplateParams(Model basePom) throws MojoExecutionException { + final TemplateParams templateParams = new TemplateParams(); + + templateParams.artifactId = artifactId; + templateParams.artifactIdPrefix = artifactIdPrefix; + templateParams.artifactIdBase = artifactIdBase; + templateParams.artifactIdBaseCamelCase = toCapCamelCase(templateParams.artifactIdBase); + + if (groupId == null) { + if (basePom == null) { + throw new MojoExecutionException( + "Please provide the desired groupId for the project by setting groupId parameter"); + } + templateParams.groupId = getGroupId(basePom); + } else { + templateParams.groupId = groupId; + } + + if (version == null) { + if (basePom == null) { + throw new MojoExecutionException( + "Please provide the desired version for the project by setting version parameter"); + } + templateParams.version = getVersion(basePom); + } else { + templateParams.version = version; + } + + templateParams.namePrefix = namePrefix; + templateParams.nameBase = nameBase; + templateParams.nameSegmentDelimiter = nameSegmentDelimiter; + templateParams.assumeManaged = detectAssumeManaged(); + templateParams.quarkusVersion = QUARKUS_VERSION_POM_EXPR; + templateParams.bomEntryVersion = bomEntryVersion.replace('@', '$'); + + if (basePom != null) { + templateParams.grandParentGroupId = grandParentGroupId != null ? grandParentGroupId : getGroupId(basePom); + templateParams.grandParentArtifactId = grandParentArtifactId != null ? grandParentArtifactId + : basePom.getArtifactId(); + templateParams.grandParentVersion = grandParentVersion != null ? grandParentVersion : getVersion(basePom); + templateParams.grandParentRelativePath = grandParentRelativePath != null ? grandParentRelativePath : "../pom.xml"; + } + + templateParams.javaPackageBase = javaPackageBase != null ? javaPackageBase + : getJavaPackage(templateParams.groupId, javaPackageInfix, artifactId); + templateParams.additionalRuntimeDependencies = getAdditionalRuntimeDependencies(); + templateParams.runtimeBomPathSet = runtimeBomPath != null; + return templateParams; + } + + private Configuration getTemplateConfig() throws IOException { + final Configuration templateCfg = new Configuration(Configuration.VERSION_2_3_28); + templateCfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + templateCfg.setTemplateLoader(createTemplateLoader(basedir, templatesUriBase)); + templateCfg.setDefaultEncoding(encoding); + templateCfg.setInterpolationSyntax(Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX); + templateCfg.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX); + return templateCfg; + } + + void generateItest(Configuration cfg, TemplateParams model) + throws MojoFailureException, MojoExecutionException, TemplateException, IOException { + final Path itestParentAbsPath = basedir.toPath().resolve(itestParentPath); try (Reader r = Files.newBufferedReader(itestParentAbsPath, charset)) { final Model itestParent = new MavenXpp3Reader().read(r); if (!"pom".equals(itestParent.getPackaging())) { @@ -541,20 +883,20 @@ public class CreateExtensionMojo extends AbstractMojo { model.itestParentRelativePath = "../pom.xml"; final Path itestDir = itestParentAbsPath.getParent().resolve(model.artifactIdBase); - evalTemplate(cfg, "integration-test-pom.xml", itestDir.resolve("pom.xml"), charset, model); + evalTemplate(cfg, "integration-test-pom.xml", itestDir.resolve("pom.xml"), model); final Path testResourcePath = itestDir.resolve("src/main/java/" + model.javaPackageBase.replace('.', '/') + "/it/" + model.artifactIdBaseCamelCase + "Resource.java"); - evalTemplate(cfg, "TestResource.java", testResourcePath, charset, model); + evalTemplate(cfg, "TestResource.java", testResourcePath, model); final Path testClassDir = itestDir .resolve("src/test/java/" + model.javaPackageBase.replace('.', '/') + "/it"); - evalTemplate(cfg, "Test.java", testClassDir.resolve(model.artifactIdBaseCamelCase + "Test.java"), charset, + evalTemplate(cfg, "Test.java", testClassDir.resolve(model.artifactIdBaseCamelCase + "Test.java"), model); - evalTemplate(cfg, "IT.java", testClassDir.resolve(model.artifactIdBaseCamelCase + "IT.java"), charset, + evalTemplate(cfg, "IT.java", testClassDir.resolve(model.artifactIdBaseCamelCase + "IT.java"), model); getLog().info(String.format("Adding module [%s] to [%s]", model.artifactIdBase, itestParentAbsPath)); - new PomTransformer(itestParentAbsPath, charset).transform(Transformation.addModule(model.artifactIdBase)); + pomTransformer(itestParentAbsPath).transform(Transformation.addModule(model.artifactIdBase)); } catch (IOException e) { throw new MojoExecutionException(String.format("Could not read %s", itestParentAbsPath), e); @@ -605,20 +947,7 @@ public class CreateExtensionMojo extends AbstractMojo { } boolean detectAssumeManaged() { - if (assumeManaged != null) { - return assumeManaged.booleanValue(); - } else { - if (project != null && project.getPluginManagement() != null - && project.getPluginManagement().getPlugins() != null) { - for (Plugin plugin : project.getPluginManagement().getPlugins()) { - if ("io.quarkus".equals(plugin.getGroupId()) - && "quarkus-bootstrap-maven-plugin".equals(plugin.getArtifactId())) { - return true; - } - } - } - return false; - } + return assumeManaged == null ? false : assumeManaged; } static String getGroupId(Model basePom) { @@ -683,12 +1012,7 @@ public class CreateExtensionMojo extends AbstractMojo { .collect(Collectors.joining(".")); } - void newParent(Path path) { - throw new UnsupportedOperationException( - "Creating standalone extension projects is not supported yet. Only adding modules under and existing pom.xml file is supported."); - } - - static TemplateLoader createTemplateLoader(Path basedir, String templatesUriBase) throws IOException { + static TemplateLoader createTemplateLoader(File basedir, String templatesUriBase) throws IOException { final TemplateLoader defaultLoader = new ClassTemplateLoader(CreateExtensionMojo.class, DEFAULT_TEMPLATES_URI_BASE.substring(CLASSPATH_PREFIX.length())); if (DEFAULT_TEMPLATES_URI_BASE.equals(templatesUriBase)) { @@ -704,7 +1028,7 @@ public class CreateExtensionMojo extends AbstractMojo { return new MultiTemplateLoader( // new TemplateLoader[] { // new FileTemplateLoader( - basedir.resolve(templatesUriBase.substring(FILE_PREFIX.length())).toFile()), // + new File(basedir, templatesUriBase.substring(FILE_PREFIX.length()))), // defaultLoader // }); } else { @@ -714,7 +1038,7 @@ public class CreateExtensionMojo extends AbstractMojo { } } - static void evalTemplate(Configuration cfg, String templateUri, Path dest, Charset charset, TemplateParams model) + static void evalTemplate(Configuration cfg, String templateUri, Path dest, TemplateParams model) throws IOException, TemplateException { log.infof("Adding '%s'", dest); final Template template = cfg.getTemplate(templateUri); @@ -746,6 +1070,12 @@ public class CreateExtensionMojo extends AbstractMojo { this.itestParentPath = Paths.get(itestParentPath); } + private void debug(String format, Object... args) { + if (getLog().isDebugEnabled()) { + getLog().debug(String.format(format, args)); + } + } + public static class TemplateParams { String grandParentRelativePath; String grandParentVersion; diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateUtils.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateUtils.java index 2c4ffe167..e7bf3840e 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateUtils.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateUtils.java @@ -94,7 +94,7 @@ public final class CreateUtils { try { final Path classOrigin = MojoUtils.getClassOrigin(cls); if (Files.isDirectory(classOrigin)) { - return resolvePluginInfo(classOrigin); + return resolvePluginInfo(classOrigin.resolve("META-INF").resolve("maven").resolve("plugin.xml")); } try (FileSystem fs = ZipUtils.newFileSystem(classOrigin)) { return resolvePluginInfo(fs.getPath("META-INF", "maven", "plugin.xml")); diff --git a/devtools/maven/src/main/resources/create-extension-templates/deployment-pom.xml b/devtools/maven/src/main/resources/create-extension-templates/deployment-pom.xml index 13933a831..9ef285fd3 100644 --- a/devtools/maven/src/main/resources/create-extension-templates/deployment-pom.xml +++ b/devtools/maven/src/main/resources/create-extension-templates/deployment-pom.xml @@ -18,8 +18,6 @@ io.quarkus quarkus-core-deployment -[#if !assumeManaged ] [=quarkusVersion] -[/#if] [=groupId] diff --git a/devtools/maven/src/main/resources/create-extension-templates/parent-pom.xml b/devtools/maven/src/main/resources/create-extension-templates/parent-pom.xml index cdd46a5a0..1255350a5 100644 --- a/devtools/maven/src/main/resources/create-extension-templates/parent-pom.xml +++ b/devtools/maven/src/main/resources/create-extension-templates/parent-pom.xml @@ -11,10 +11,10 @@ [/#if] -[#if groupId?? && groupId != grandParentGroupId ] [=groupId] +[#if grandParentGroupId?? ][#if groupId != grandParentGroupId ] [=groupId][/#if][#else ] [=groupId] [/#if] [=artifactId]-parent -[#if groupId?? && groupId != grandParentGroupId && version?? && version != grandParentVersion ] [=version] +[#if groupId?? && (!grandParentGroupId?? || groupId != grandParentGroupId) && version?? && (!grandParentVersion?? || version != grandParentVersion) ] [=version] [/#if] [#if nameBase?? ] [=namePrefix][=nameBase][=nameSegmentDelimiter]Parent [/#if] diff --git a/devtools/maven/src/main/resources/create-extension-templates/runtime-pom.xml b/devtools/maven/src/main/resources/create-extension-templates/runtime-pom.xml index cdfe44c85..bee5fd63f 100644 --- a/devtools/maven/src/main/resources/create-extension-templates/runtime-pom.xml +++ b/devtools/maven/src/main/resources/create-extension-templates/runtime-pom.xml @@ -13,9 +13,13 @@ [=artifactId] [#if nameBase?? ] [=namePrefix][=nameBase][=nameSegmentDelimiter]Runtime [/#if] -[#if additionalRuntimeDependencies?size > 0 ] + + io.quarkus + quarkus-core + +[#if additionalRuntimeDependencies?size > 0 ] [#list additionalRuntimeDependencies as dep] [=dep.groupId] @@ -30,8 +34,8 @@ [/#if] [/#list] - [/#if] + diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/BuildFile.java b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/BuildFile.java index 5169fea4f..85934c147 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/BuildFile.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/BuildFile.java @@ -63,7 +63,7 @@ public abstract class BuildFile implements Closeable { protected abstract boolean hasDependency(Extension extension) throws IOException; public boolean addExtensionAsGAV(String query) throws IOException { - Dependency parsed = MojoUtils.parse(query.trim().toLowerCase()); + Dependency parsed = MojoUtils.parse(query.trim()); boolean alreadyThere = getDependencies().stream() .anyMatch(d -> d.getManagementKey().equalsIgnoreCase(parsed.getManagementKey())); if (!alreadyThere) { diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java b/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java index a7c37e39a..ff9445c4e 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java @@ -76,13 +76,13 @@ public class MojoUtils { Dependency res = new Dependency(); String[] segments = dependency.split(":"); if (segments.length >= 2) { - res.setGroupId(segments[0]); - res.setArtifactId(segments[1]); + res.setGroupId(segments[0].toLowerCase()); + res.setArtifactId(segments[1].toLowerCase()); if (segments.length >= 3 && !segments[2].isEmpty()) { res.setVersion(segments[2]); } if (segments.length >= 4) { - res.setClassifier(segments[3]); + res.setClassifier(segments[3].toLowerCase()); } return res; } else { diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/PomTransformer.java b/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/PomTransformer.java index 245bb1c51..c105f8f6e 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/PomTransformer.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/PomTransformer.java @@ -26,6 +26,7 @@ import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import org.apache.maven.model.Plugin; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -304,6 +305,59 @@ public class PomTransformer { }; } + public static Transformation addProperty(String name, String value) { + return (Document document, TransformationContext context) -> { + try { + Node props = (Node) context.getXPath().evaluate(anyNs("project", "properties"), document, + XPathConstants.NODE); + if (props == null) { + final Node propsIndent = context.indent(1); + props = document.createElement("properties"); + props.appendChild(context.indent(1)); + + final Node project = (Node) context.getXPath().evaluate(anyNs("project"), document, + XPathConstants.NODE); + if (project == null) { + throw new IllegalStateException( + String.format("No in file [%s]", context.getPomXmlPath())); + } + /* ideally before modules */ + Node refNode = (Node) context.getXPath().evaluate(anyNs("project", "modules"), + document, XPathConstants.NODE); + Node ws; + if (refNode != null) { + ws = refNode.getPreviousSibling(); + if (ws == null || ws.getNodeType() != Node.TEXT_NODE) { + project.insertBefore(ws = context.indent(1), refNode); + } + } else { + ws = project.getLastChild(); + if (ws == null || ws.getNodeType() != Node.TEXT_NODE) { + project.appendChild(ws = context.indent(0)); + } + } + project.insertBefore(propsIndent, ws); + project.insertBefore(props, ws); + } + + final Node propNode = document.createElement(name); + propNode.appendChild(document.createTextNode(value)); + + final NodeList modulesChildren = props.getChildNodes(); + final int len = modulesChildren.getLength(); + Node ws; + if (len == 0 || (ws = modulesChildren.item(len - 1)).getNodeType() != Node.TEXT_NODE) { + ws = context.indent(1); + props.appendChild(ws); + } + props.insertBefore(context.indent(2), ws); + props.insertBefore(propNode, ws); + } catch (XPathExpressionException | DOMException e) { + throw new RuntimeException(e); + } + }; + } + public static Transformation addDependencyManagementIfNeeded() { return (Document document, TransformationContext context) -> { try { @@ -358,6 +412,55 @@ public class PomTransformer { }; } + public static Transformation addPluginManagementIfNeeded() { + return (Document document, TransformationContext context) -> { + try { + Node plugins = (Node) context.getXPath().evaluate( + anyNs("project", "build", "pluginManagement", "plugins"), document, XPathConstants.NODE); + if (plugins == null) { + Node build = (Node) context.getXPath() + .evaluate(anyNs("project", "build"), document, XPathConstants.NODE); + if (build == null) { + Node project = (Node) context.getXPath().evaluate(anyNs("project"), document, + XPathConstants.NODE); + if (project == null) { + throw new IllegalStateException( + String.format("//project not found in [%s]", context.getPomXmlPath())); + } + build = document.createElement("build"); + Node ws = project.getLastChild(); + if (ws == null || ws.getNodeType() != Node.TEXT_NODE) { + project.appendChild(ws = context.indent(0)); + } + project.insertBefore(build, ws); + project.insertBefore(context.indent(1), build); + } + Node pluginManagement = (Node) context.getXPath() + .evaluate(anyNs("project", "build", "pluginManagement"), document, XPathConstants.NODE); + if (pluginManagement == null) { + pluginManagement = document.createElement("pluginManagement"); + Node ws = build.getLastChild(); + if (ws == null || ws.getNodeType() != Node.TEXT_NODE) { + build.appendChild(ws = context.indent(1)); + } + build.insertBefore(pluginManagement, ws); + build.insertBefore(context.indent(2), pluginManagement); + } + plugins = document.createElement("plugins"); + plugins.appendChild(context.indent(3)); + Node ws = pluginManagement.getLastChild(); + if (ws == null || ws.getNodeType() != Node.TEXT_NODE) { + pluginManagement.appendChild(ws = context.indent(2)); + } + pluginManagement.insertBefore(plugins, ws); + pluginManagement.insertBefore(context.indent(3), plugins); + } + } catch (XPathExpressionException | DOMException e) { + throw new RuntimeException(e); + } + }; + } + public static Transformation addManagedDependency(String groupId, String artifactId, String version) { return addManagedDependency(new Gavtcs(groupId, artifactId, version, null, null, null)); } @@ -406,6 +509,38 @@ public class PomTransformer { }; } + public static Transformation addManagedPlugin(Plugin plugin) { + return (Document document, TransformationContext context) -> { + try { + addPluginManagementIfNeeded().perform(document, context); + Node managedPlugins = (Node) context.getXPath().evaluate( + anyNs("project", "build", "pluginManagement", "plugins"), document, XPathConstants.NODE); + final NodeList pluginsChildren = managedPlugins.getChildNodes(); + Node ws = null; + if (pluginsChildren.getLength() > 0) { + ws = pluginsChildren.item(pluginsChildren.getLength() - 1); + } + if (ws == null || ws.getNodeType() != Node.TEXT_NODE) { + ws = context.indent(4); + managedPlugins.appendChild(ws); + } + + managedPlugins.insertBefore(context.indent(4), ws); + final Node dep = document.createElement("plugin"); + dep.appendChild(context.indent(5)); + dep.appendChild(context.textElement("groupId", plugin.getGroupId())); + dep.appendChild(context.indent(5)); + dep.appendChild(context.textElement("artifactId", plugin.getArtifactId())); + dep.appendChild(context.indent(5)); + dep.appendChild(context.textElement("version", plugin.getVersion())); + dep.appendChild(context.indent(4)); + managedPlugins.insertBefore(dep, ws); + } catch (XPathExpressionException | DOMException e) { + throw new RuntimeException(e); + } + }; + } + /** * Perform this {@link Transformation} on the given {@code document} * diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/CreateExtensionMojoTest.java b/integration-tests/maven/src/test/java/io/quarkus/maven/CreateExtensionMojoTest.java index ced952f9e..bb06b65c9 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/CreateExtensionMojoTest.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/CreateExtensionMojoTest.java @@ -3,6 +3,7 @@ package io.quarkus.maven; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; +import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -10,49 +11,108 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import org.apache.maven.model.Build; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Model; +import org.apache.maven.model.PluginManagement; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.project.MavenProject; import org.junit.jupiter.api.Test; +import io.quarkus.maven.utilities.MojoUtils; + public class CreateExtensionMojoTest { - static CreateExtensionMojo createMojo(String testProjectName) throws IllegalArgumentException, - IllegalAccessException, IOException, NoSuchFieldException, SecurityException { - final Path srcDir = Paths.get("src/test/resources/projects/" + testProjectName); - /* - * We want to run on the same project multiple times with different args so let's create a copy with a random - * suffix - */ - final Path copyDir = getCopyDir(testProjectName); - Files.walk(srcDir).forEach(source -> { - try { - final Path dest = copyDir.resolve(srcDir.relativize(source)); - Files.copy(source, dest); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - + private static CreateExtensionMojo initMojo(final Path projectDir) throws IOException { final CreateExtensionMojo mojo = new CreateExtensionMojo(); - mojo.basedir = copyDir; + mojo.project = new MavenProject(); + mojo.basedir = projectDir.toFile(); + + final File pom = new File(projectDir.toFile(), "pom.xml"); + if (pom.exists()) { + mojo.project.setFile(pom); + final Model rawModel = MojoUtils.readPom(pom); + // the project would have an interpolated model at runtime, which we can't fully init here + // here are just some key parts + if (rawModel.getDependencyManagement() != null) { + List deps = rawModel.getDependencyManagement().getDependencies(); + if (deps != null && !deps.isEmpty()) { + Dependency deploymentBom = null; + for (Dependency dep : deps) { + if (dep.getArtifactId().equals("quarkus-bom-deployment") && dep.getGroupId().equals("io.quarkus")) { + deploymentBom = dep; + } + } + if (deploymentBom != null) { + String version = deploymentBom.getVersion(); + if (CreateExtensionMojo.QUARKUS_VERSION_POM_EXPR.equals(version)) { + version = rawModel.getProperties().getProperty(version.substring(2, version.length() - 1)); + if (version == null) { + throw new IllegalStateException( + "Failed to resolve " + deploymentBom.getVersion() + " from " + pom); + } + } + Dependency dep = new Dependency(); + dep.setGroupId("io.quarkus"); + dep.setArtifactId("quarkus-core-deployment"); + dep.setType("jar"); + dep.setVersion(version); + deps.add(dep); + } + } + } + mojo.project.setModel(rawModel); + } + + Build build = mojo.project.getBuild(); + if (build.getPluginManagement() == null) { + build.setPluginManagement(new PluginManagement()); + } + mojo.encoding = CreateExtensionMojo.DEFAULT_ENCODING; mojo.templatesUriBase = CreateExtensionMojo.DEFAULT_TEMPLATES_URI_BASE; mojo.quarkusVersion = CreateExtensionMojo.DEFAULT_QUARKUS_VERSION; mojo.bomEntryVersion = CreateExtensionMojo.DEFAULT_BOM_ENTRY_VERSION; mojo.assumeManaged = true; mojo.nameSegmentDelimiter = CreateExtensionMojo.DEFAULT_NAME_SEGMENT_DELIMITER; + mojo.platformGroupId = CreateExtensionMojo.PLATFORM_DEFAULT_GROUP_ID; + mojo.platformArtifactId = CreateExtensionMojo.PLATFORM_DEFAULT_ARTIFACT_ID; + mojo.compilerPluginVersion = CreateExtensionMojo.COMPILER_PLUGIN_DEFAULT_VERSION; return mojo; } - private static Path getCopyDir(String testProjectName) { + private static Path createProjectFromTemplate(String testProjectName) throws IOException { + final Path srcDir = Paths.get("src/test/resources/projects/" + testProjectName); + /* + * We want to run on the same project multiple times with different args so let's create a copy with a random + * suffix + */ + final Path copyDir = newProjectDir(testProjectName); + Files.walk(srcDir).forEach(source -> { + final Path dest = copyDir.resolve(srcDir.relativize(source)); + try { + Files.copy(source, dest); + } catch (IOException e) { + if (!Files.isDirectory(dest)) { + throw new RuntimeException(e); + } + } + }); + return copyDir; + } + + private static Path newProjectDir(String testProjectName) throws IOException { int count = 0; while (count < 100) { Path path = Paths.get("target/test-classes/projects/" + testProjectName + "-" + UUID.randomUUID()); if (!Files.exists(path)) { + Files.createDirectories(path); return path; } count++; @@ -65,19 +125,19 @@ public class CreateExtensionMojoTest { @Test void createExtensionUnderExistingPomMinimal() throws MojoExecutionException, MojoFailureException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, IOException { - final CreateExtensionMojo mojo = createMojo("create-extension-pom"); + final CreateExtensionMojo mojo = initMojo(createProjectFromTemplate("create-extension-pom")); mojo.artifactId = "my-project-(minimal-extension)"; mojo.assumeManaged = false; mojo.execute(); assertTreesMatch(Paths.get("src/test/resources/expected/create-extension-pom-minimal"), - mojo.basedir); + mojo.basedir.toPath()); } @Test void createExtensionUnderExistingPomWithAdditionalRuntimeDependencies() throws MojoExecutionException, MojoFailureException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, IOException { - final CreateExtensionMojo mojo = createMojo("create-extension-pom"); + final CreateExtensionMojo mojo = initMojo(createProjectFromTemplate("create-extension-pom")); mojo.artifactId = "my-project-(add-to-bom)"; mojo.assumeManaged = false; mojo.runtimeBomPath = Paths.get("boms/runtime/pom.xml"); @@ -86,38 +146,50 @@ public class CreateExtensionMojoTest { mojo.execute(); assertTreesMatch(Paths.get("src/test/resources/expected/create-extension-pom-add-to-bom"), - mojo.basedir); + mojo.basedir.toPath()); } @Test void createExtensionUnderExistingPomWithItest() throws MojoExecutionException, MojoFailureException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, IOException { - final CreateExtensionMojo mojo = createMojo("create-extension-pom"); + final CreateExtensionMojo mojo = initMojo(createProjectFromTemplate("create-extension-pom")); mojo.artifactId = "my-project-(itest)"; mojo.assumeManaged = false; mojo.itestParentPath = Paths.get("integration-tests/pom.xml"); mojo.execute(); assertTreesMatch(Paths.get("src/test/resources/expected/create-extension-pom-itest"), - mojo.basedir); + mojo.basedir.toPath()); } @Test void createExtensionUnderExistingPomCustomGrandParent() throws MojoExecutionException, MojoFailureException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, IOException { - final CreateExtensionMojo mojo = createMojo("create-extension-pom"); + final CreateExtensionMojo mojo = initMojo(createProjectFromTemplate("create-extension-pom")); mojo.artifactId = "myproject-(with-grand-parent)"; - mojo.grandParentArtifactId = "build-bom"; - mojo.grandParentRelativePath = "../../build-bom/pom.xml"; + mojo.grandParentArtifactId = "grand-parent"; + mojo.grandParentRelativePath = "../pom.xml"; mojo.templatesUriBase = "file:templates"; mojo.runtimeBomPath = Paths.get("boms/runtime/pom.xml"); mojo.deploymentBomPath = Paths.get("boms/deployment/pom.xml"); mojo.execute(); - assertTreesMatch( Paths.get("src/test/resources/expected/create-extension-pom-with-grand-parent"), - mojo.basedir); + mojo.basedir.toPath()); + } + + @Test + void createNewExtensionProject() throws Exception { + final CreateExtensionMojo mojo = initMojo(newProjectDir("new-ext-project")); + mojo.groupId = "org.acme"; + mojo.artifactId = "my-ext"; + mojo.version = "1.0-SNAPSHOT"; + mojo.assumeManaged = null; + mojo.execute(); + assertTreesMatch( + Paths.get("src/test/resources/expected/new-extension-project"), + mojo.basedir.toPath()); } static void assertTreesMatch(Path expected, Path actual) throws IOException { diff --git a/integration-tests/maven/src/test/resources/expected/create-extension-pom-add-to-bom/add-to-bom/deployment/pom.xml b/integration-tests/maven/src/test/resources/expected/create-extension-pom-add-to-bom/add-to-bom/deployment/pom.xml index 07b184bee..9635c6798 100644 --- a/integration-tests/maven/src/test/resources/expected/create-extension-pom-add-to-bom/add-to-bom/deployment/pom.xml +++ b/integration-tests/maven/src/test/resources/expected/create-extension-pom-add-to-bom/add-to-bom/deployment/pom.xml @@ -17,7 +17,6 @@ io.quarkus quarkus-core-deployment - ${quarkus.version} org.acme diff --git a/integration-tests/maven/src/test/resources/expected/create-extension-pom-add-to-bom/add-to-bom/runtime/pom.xml b/integration-tests/maven/src/test/resources/expected/create-extension-pom-add-to-bom/add-to-bom/runtime/pom.xml index 8e5c26dc7..5311c743f 100644 --- a/integration-tests/maven/src/test/resources/expected/create-extension-pom-add-to-bom/add-to-bom/runtime/pom.xml +++ b/integration-tests/maven/src/test/resources/expected/create-extension-pom-add-to-bom/add-to-bom/runtime/pom.xml @@ -14,6 +14,10 @@ Add To Bom - Runtime + + io.quarkus + quarkus-core + org.example example-1 diff --git a/integration-tests/maven/src/test/resources/expected/create-extension-pom-add-to-bom/pom.xml b/integration-tests/maven/src/test/resources/expected/create-extension-pom-add-to-bom/pom.xml index b8c90525b..8e23f9c65 100644 --- a/integration-tests/maven/src/test/resources/expected/create-extension-pom-add-to-bom/pom.xml +++ b/integration-tests/maven/src/test/resources/expected/create-extension-pom-add-to-bom/pom.xml @@ -9,10 +9,34 @@ 0.19.0 3.3.0 + 3.8.1 integration-tests add-to-bom + + + + + io.quarkus + quarkus-bom-deployment + ${quarkus.version} + pom + import + + + + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + + diff --git a/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/itest/deployment/pom.xml b/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/itest/deployment/pom.xml index 5c24622b2..f0cc10e74 100644 --- a/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/itest/deployment/pom.xml +++ b/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/itest/deployment/pom.xml @@ -17,7 +17,6 @@ io.quarkus quarkus-core-deployment - ${quarkus.version} org.acme diff --git a/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/itest/runtime/pom.xml b/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/itest/runtime/pom.xml index 1b157ce50..2ac672f68 100644 --- a/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/itest/runtime/pom.xml +++ b/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/itest/runtime/pom.xml @@ -13,6 +13,13 @@ my-project-itest Itest - Runtime + + + io.quarkus + quarkus-core + + + diff --git a/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/pom.xml b/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/pom.xml index 696158f85..70a6610f4 100644 --- a/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/pom.xml +++ b/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/pom.xml @@ -9,10 +9,34 @@ 0.19.0 3.3.0 + 3.8.1 integration-tests itest + + + + + io.quarkus + quarkus-bom-deployment + ${quarkus.version} + pom + import + + + + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + + diff --git a/integration-tests/maven/src/test/resources/expected/create-extension-pom-minimal/minimal-extension/deployment/pom.xml b/integration-tests/maven/src/test/resources/expected/create-extension-pom-minimal/minimal-extension/deployment/pom.xml index cccab7d27..7eac593db 100644 --- a/integration-tests/maven/src/test/resources/expected/create-extension-pom-minimal/minimal-extension/deployment/pom.xml +++ b/integration-tests/maven/src/test/resources/expected/create-extension-pom-minimal/minimal-extension/deployment/pom.xml @@ -17,7 +17,6 @@ io.quarkus quarkus-core-deployment - ${quarkus.version} org.acme diff --git a/integration-tests/maven/src/test/resources/expected/create-extension-pom-minimal/minimal-extension/runtime/pom.xml b/integration-tests/maven/src/test/resources/expected/create-extension-pom-minimal/minimal-extension/runtime/pom.xml index 487e07fb6..0d1169881 100644 --- a/integration-tests/maven/src/test/resources/expected/create-extension-pom-minimal/minimal-extension/runtime/pom.xml +++ b/integration-tests/maven/src/test/resources/expected/create-extension-pom-minimal/minimal-extension/runtime/pom.xml @@ -13,6 +13,13 @@ my-project-minimal-extension Minimal Extension - Runtime + + + io.quarkus + quarkus-core + + + diff --git a/integration-tests/maven/src/test/resources/expected/create-extension-pom-minimal/pom.xml b/integration-tests/maven/src/test/resources/expected/create-extension-pom-minimal/pom.xml index bbee27ef6..bea4052c9 100644 --- a/integration-tests/maven/src/test/resources/expected/create-extension-pom-minimal/pom.xml +++ b/integration-tests/maven/src/test/resources/expected/create-extension-pom-minimal/pom.xml @@ -9,10 +9,34 @@ 0.19.0 3.3.0 + 3.8.1 integration-tests minimal-extension + + + + + io.quarkus + quarkus-bom-deployment + ${quarkus.version} + pom + import + + + + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + + diff --git a/integration-tests/maven/src/test/resources/expected/create-extension-pom-with-grand-parent/pom.xml b/integration-tests/maven/src/test/resources/expected/create-extension-pom-with-grand-parent/pom.xml index 6e6238e1a..a058eaa67 100644 --- a/integration-tests/maven/src/test/resources/expected/create-extension-pom-with-grand-parent/pom.xml +++ b/integration-tests/maven/src/test/resources/expected/create-extension-pom-with-grand-parent/pom.xml @@ -9,10 +9,34 @@ 0.19.0 3.3.0 + 3.8.1 integration-tests with-grand-parent + + + + + io.quarkus + quarkus-bom-deployment + ${quarkus.version} + pom + import + + + + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + + diff --git a/integration-tests/maven/src/test/resources/expected/create-extension-pom-with-grand-parent/with-grand-parent/pom.xml b/integration-tests/maven/src/test/resources/expected/create-extension-pom-with-grand-parent/with-grand-parent/pom.xml index daa32c114..b5041c44c 100644 --- a/integration-tests/maven/src/test/resources/expected/create-extension-pom-with-grand-parent/with-grand-parent/pom.xml +++ b/integration-tests/maven/src/test/resources/expected/create-extension-pom-with-grand-parent/with-grand-parent/pom.xml @@ -5,9 +5,9 @@ 4.0.0 org.acme - build-bom + grand-parent 0.1-SNAPSHOT - ../../build-bom/pom.xml + ../pom.xml myproject-with-grand-parent-parent diff --git a/integration-tests/maven/src/test/resources/expected/create-extension-pom-with-grand-parent/with-grand-parent/runtime/pom.xml b/integration-tests/maven/src/test/resources/expected/create-extension-pom-with-grand-parent/with-grand-parent/runtime/pom.xml index 35b4d7f69..2e6adfc80 100644 --- a/integration-tests/maven/src/test/resources/expected/create-extension-pom-with-grand-parent/with-grand-parent/runtime/pom.xml +++ b/integration-tests/maven/src/test/resources/expected/create-extension-pom-with-grand-parent/with-grand-parent/runtime/pom.xml @@ -13,6 +13,13 @@ myproject-with-grand-parent With Grand Parent - Runtime + + + io.quarkus + quarkus-core + + + diff --git a/integration-tests/maven/src/test/resources/expected/new-extension-project/my-ext/deployment/pom.xml b/integration-tests/maven/src/test/resources/expected/new-extension-project/my-ext/deployment/pom.xml new file mode 100644 index 000000000..4e5b0a537 --- /dev/null +++ b/integration-tests/maven/src/test/resources/expected/new-extension-project/my-ext/deployment/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + org.acme + my-ext-parent + 1.0-SNAPSHOT + ../pom.xml + + + my-ext-deployment + My Ext - Deployment + + + + io.quarkus + quarkus-core-deployment + + + org.acme + my-ext + ${project.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + diff --git a/integration-tests/maven/src/test/resources/expected/new-extension-project/my-ext/deployment/src/main/java/org/acme/my/ext/deployment/MyExtProcessor.java b/integration-tests/maven/src/test/resources/expected/new-extension-project/my-ext/deployment/src/main/java/org/acme/my/ext/deployment/MyExtProcessor.java new file mode 100644 index 000000000..0b9fa976a --- /dev/null +++ b/integration-tests/maven/src/test/resources/expected/new-extension-project/my-ext/deployment/src/main/java/org/acme/my/ext/deployment/MyExtProcessor.java @@ -0,0 +1,15 @@ +package org.acme.my.ext.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +class MyExtProcessor { + + private static final String FEATURE = "my-ext"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + +} diff --git a/integration-tests/maven/src/test/resources/expected/new-extension-project/my-ext/pom.xml b/integration-tests/maven/src/test/resources/expected/new-extension-project/my-ext/pom.xml new file mode 100644 index 000000000..14e70f0c6 --- /dev/null +++ b/integration-tests/maven/src/test/resources/expected/new-extension-project/my-ext/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + org.acme + my-ext-parent + 1.0-SNAPSHOT + My Ext - Parent + + pom + + 999-SNAPSHOT + 3.8.1 + + + deployment + runtime + + + + + io.quarkus + quarkus-bom-deployment + ${quarkus.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + + + + diff --git a/integration-tests/maven/src/test/resources/expected/new-extension-project/my-ext/runtime/pom.xml b/integration-tests/maven/src/test/resources/expected/new-extension-project/my-ext/runtime/pom.xml new file mode 100644 index 000000000..6c51dbff6 --- /dev/null +++ b/integration-tests/maven/src/test/resources/expected/new-extension-project/my-ext/runtime/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + org.acme + my-ext-parent + 1.0-SNAPSHOT + ../pom.xml + + + my-ext + My Ext - Runtime + + + + io.quarkus + quarkus-core + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.version} + + + + extension-descriptor + + compile + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/create-extension-pom/pom.xml b/integration-tests/maven/src/test/resources/projects/create-extension-pom/pom.xml index efbce18ef..dfaaaed1d 100644 --- a/integration-tests/maven/src/test/resources/projects/create-extension-pom/pom.xml +++ b/integration-tests/maven/src/test/resources/projects/create-extension-pom/pom.xml @@ -9,9 +9,33 @@ 0.19.0 3.3.0 + 3.8.1 integration-tests + + + + + io.quarkus + quarkus-bom-deployment + ${quarkus.version} + pom + import + + + + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + +