diff --git a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java index 0f29e3f3c2e51..891651742faa8 100644 --- a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java +++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java @@ -1,10 +1,13 @@ package io.quarkus.gradle; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; +import static org.junit.jupiter.api.Assertions.*; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -16,12 +19,18 @@ import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskContainer; import org.gradle.testfixtures.ProjectBuilder; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import io.quarkus.gradle.extension.QuarkusPluginExtension; public class QuarkusPluginTest { + @TempDir + Path testProjectDir; + @Test public void shouldCreateTasks() { Project project = ProjectBuilder.builder().build(); @@ -67,7 +76,7 @@ public void shouldMakeQuarkusDevAndQuarkusBuildDependOnClassesTask() { } @Test - public void shouldReturnMutlipleOutputSourceDirectories() { + public void shouldReturnMultipleOutputSourceDirectories() { Project project = ProjectBuilder.builder().build(); project.getPluginManager().apply(QuarkusPlugin.ID); project.getPluginManager().apply("java"); @@ -83,8 +92,61 @@ public void shouldReturnMutlipleOutputSourceDirectories() { new File(project.getBuildDir(), "classes/scala/test")); } + + @Test + public void shouldNotFailOnProjectDependenciesWithoutMain() throws IOException { + var settingFile = testProjectDir.resolve("settings.gradle.kts"); + var mppProjectDir = testProjectDir.resolve("mpp"); + var quarkusProjectDir = testProjectDir.resolve("quarkus"); + var mppBuild = mppProjectDir.resolve("build.gradle.kts"); + var quarkusBuild = quarkusProjectDir.resolve("build.gradle.kts"); + Files.createDirectory(mppProjectDir); + Files.createDirectory(quarkusProjectDir); + Files.writeString(settingFile, + "rootProject.name = \"quarkus-mpp-sample\"\n" + + "\n" + + "include(\n" + + " \"mpp\",\n" + + " \"quarkus\"\n" + + ")"); + + Files.writeString(mppBuild, + "plugins {\n" + + " kotlin(\"multiplatform\") version \"1.7.10\"\n" + + "}\n" + + "\n" + + "repositories {\n" + + " mavenCentral()\n" + + "}\n" + + "\n" + + "kotlin {\n" + + " jvm()\n" + + "}"); + + Files.writeString(quarkusBuild, + "plugins {\n" + + " id(\"io.quarkus\")\n" + + "}\n" + + "\n" + + "repositories {\n" + + " mavenCentral()\n" + + "}\n" + + "\n" + + "dependencies {\n" + + " implementation(project(\":mpp\"))\n" + + "}" + ); + + BuildResult result = GradleRunner.create() + .withPluginClasspath() + .withProjectDir(testProjectDir.toFile()) + .withArguments("quarkusGenerateCode") + .build(); + + assertEquals(SUCCESS, result.task(":quarkus:quarkusGenerateCode").getOutcome()); + } - private static final List getDependantProvidedTaskName(Task task) { + private static List getDependantProvidedTaskName(Task task) { List dependantTaskNames = new ArrayList<>(); for (Object t : task.getDependsOn()) { try { diff --git a/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/dependency/DeploymentClasspathBuilder.java b/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/dependency/DeploymentClasspathBuilder.java index 5508c2e8e1a7f..1c01060da4c65 100644 --- a/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/dependency/DeploymentClasspathBuilder.java +++ b/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/dependency/DeploymentClasspathBuilder.java @@ -43,9 +43,7 @@ public void exportDeploymentClasspath(String configurationName) { dependencies); } else { DependencyUtils.requireDeploymentDependency(deploymentConfigurationName, extension, dependencies); - if (!alreadyProcessed.add(extension.getExtensionId())) { - continue; - } + alreadyProcessed.add(extension.getExtensionId()); } } }); @@ -73,9 +71,9 @@ private Set collectQuarkusExtensions(ResolvedDependency dep } Set extensions = new LinkedHashSet<>(); for (ResolvedArtifact moduleArtifact : dependency.getModuleArtifacts()) { - ExtensionDependency extension = DependencyUtils.getExtensionInfoOrNull(project, moduleArtifact); - if (extension != null) { - extensions.add(extension); + var optionalExtension = DependencyUtils.getOptionalExtensionInfo(project, moduleArtifact); + if (optionalExtension.isPresent()) { + extensions.add(optionalExtension.get()); return extensions; } } diff --git a/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/tasks/ValidateExtensionTask.java b/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/tasks/ValidateExtensionTask.java index be96802daf78d..a30e4fb444aa3 100644 --- a/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/tasks/ValidateExtensionTask.java +++ b/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/tasks/ValidateExtensionTask.java @@ -2,7 +2,9 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -18,7 +20,6 @@ import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.extension.gradle.QuarkusExtensionConfiguration; import io.quarkus.gradle.tooling.dependency.DependencyUtils; -import io.quarkus.gradle.tooling.dependency.ExtensionDependency; public class ValidateExtensionTask extends DefaultTask { @@ -62,13 +63,7 @@ public void validateExtension() { deploymentModuleKeys); deploymentModuleKeys.removeAll(existingDeploymentModuleKeys); - boolean hasErrors = false; - if (!invalidRuntimeArtifacts.isEmpty()) { - hasErrors = true; - } - if (!deploymentModuleKeys.isEmpty()) { - hasErrors = true; - } + boolean hasErrors = !invalidRuntimeArtifacts.isEmpty() || !deploymentModuleKeys.isEmpty(); if (hasErrors) { printValidationErrors(invalidRuntimeArtifacts, deploymentModuleKeys); @@ -76,15 +71,12 @@ public void validateExtension() { } private List collectRuntimeExtensionsDeploymentKeys(Set runtimeArtifacts) { - List runtimeExtensions = new ArrayList<>(); - for (ResolvedArtifact resolvedArtifact : runtimeArtifacts) { - ExtensionDependency extension = DependencyUtils.getExtensionInfoOrNull(getProject(), resolvedArtifact); - if (extension != null) { - runtimeExtensions.add(new AppArtifactKey(extension.getDeploymentModule().getGroupId(), - extension.getDeploymentModule().getArtifactId())); - } - } - return runtimeExtensions; + return runtimeArtifacts.stream() + .map(resolvedArtifact->DependencyUtils.getOptionalExtensionInfo(getProject(), resolvedArtifact)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(extension -> new AppArtifactKey(extension.getDeploymentModule().getGroupId(), extension.getDeploymentModule().getArtifactId())) + .collect(Collectors.toList()); } private List findExtensionInConfiguration(Set deploymentArtifacts, diff --git a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java index 6ab5650d7b5a9..fbb7882b5058a 100644 --- a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java +++ b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java @@ -6,7 +6,10 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; @@ -37,8 +40,7 @@ public class ConditionalDependenciesEnabler { private final Set existingArtifacts = new HashSet<>(); private final List unsatisfiedConditionalDeps = new ArrayList<>(); - public ConditionalDependenciesEnabler(Project project, LaunchMode mode, - Configuration platforms) { + public ConditionalDependenciesEnabler(Project project, LaunchMode mode, Configuration platforms) { this.project = project; this.enforcedPlatforms = platforms; @@ -59,7 +61,7 @@ public ConditionalDependenciesEnabler(Project project, LaunchMode mode, final Dependency conditionalDep = unsatisfiedConditionalDeps.get(i); // Try to resolve it with the latest evolved graph available if (resolveConditionalDependency(conditionalDep)) { - // Mark the resolution as a success so we know the graph evolved + // Mark the resolution as a success, so we know the graph has evolved satisfiedConditionalDeps = true; unsatisfiedConditionalDeps.remove(i); } else { @@ -88,23 +90,24 @@ private void reset() { } private void collectConditionalDependencies(Set runtimeArtifacts) { - // For every artifact in the dependency graph: - for (ResolvedArtifact artifact : runtimeArtifacts) { - // Add to master list of artifacts: - existingArtifacts.add(getKey(artifact)); - ExtensionDependency extension = DependencyUtils.getExtensionInfoOrNull(project, artifact); - // If this artifact represents an extension: - if (extension != null) { - // Add to master list of accepted extensions: - allExtensions.put(extension.getExtensionId(), extension); - for (Dependency conditionalDep : extension.getConditionalDependencies()) { - // If the dependency is not present yet in the graph, queue it for resolution later - if (!exists(conditionalDep)) { - queueConditionalDependency(extension, conditionalDep); - } - } - } - } + addToMasterList(runtimeArtifacts); + var artifactExtensions = getArtifactExtensions(runtimeArtifacts); + allExtensions.putAll(artifactExtensions); + artifactExtensions.forEach((ignored, extension) -> queueAbsentExtensionConditionalDependencies(extension)); + } + + private void addToMasterList(Set artifacts) { + artifacts.stream().map(ConditionalDependenciesEnabler::getKey).forEach(existingArtifacts::add); + } + + private Map getArtifactExtensions(Set runtimeArtifacts) { + return runtimeArtifacts.stream() + .flatMap(artifact -> DependencyUtils.getOptionalExtensionInfo(project, artifact).stream()) + .collect(Collectors.toMap(ExtensionDependency::getExtensionId, Function.identity())); + } + + private void queueAbsentExtensionConditionalDependencies(ExtensionDependency extension) { + extension.getConditionalDependencies().stream().filter(dep -> !exists(dep)).forEach(dep -> queueConditionalDependency(extension, dep)); } private boolean resolveConditionalDependency(Dependency conditionalDep) { @@ -112,51 +115,30 @@ private boolean resolveConditionalDependency(Dependency conditionalDep) { final Configuration conditionalDeps = createConditionalDependenciesConfiguration(project, conditionalDep); Set resolvedArtifacts = conditionalDeps.getResolvedConfiguration().getResolvedArtifacts(); - boolean satisfied = false; - // Resolved artifacts don't have great linking back to the original artifact, so I think - // this loop is trying to find the artifact that represents the original conditional - // dependency - for (ResolvedArtifact artifact : resolvedArtifacts) { - if (conditionalDep.getName().equals(artifact.getName()) - && conditionalDep.getVersion().equals(artifact.getModuleVersion().getId().getVersion()) - && artifact.getModuleVersion().getId().getGroup().equals(conditionalDep.getGroup())) { - // Once the dependency is found, reload the extension info from within - final ExtensionDependency extensionDependency = DependencyUtils.getExtensionInfoOrNull(project, artifact); - // Now check if this conditional dependency is resolved given the latest graph evolution - if (extensionDependency != null && (extensionDependency.getDependencyConditions().isEmpty() - || exist(extensionDependency.getDependencyConditions()))) { - satisfied = true; - enableConditionalDependency(extensionDependency.getExtensionId()); - break; - } - } + boolean isConditionalDependencyResolved = resolvedArtifacts.stream() + .filter(artifact -> areEquals(conditionalDep, artifact)) + .flatMap(artifact -> DependencyUtils.getOptionalExtensionInfo(project, artifact).stream()) + .filter(extension -> extension.getDependencyConditions().isEmpty() || exist(extension.getDependencyConditions())) + .findFirst().map(extension -> { + enableConditionalDependency(extension.getExtensionId()); + return true; + }).orElse(false); + + if (isConditionalDependencyResolved) { + addToMasterList(resolvedArtifacts); + var artifactExtensions = getArtifactExtensions(resolvedArtifacts); + artifactExtensions.forEach((id, extension) -> extension.setConditional(true)); + allExtensions.putAll(artifactExtensions); + artifactExtensions.forEach((ignored, extension) -> queueAbsentExtensionConditionalDependencies(extension)); } - // No resolution (yet); give up. - if (!satisfied) { - return false; - } + return isConditionalDependencyResolved; + } - // The conditional dependency resolved! Let's now add all of /its/ dependencies - for (ResolvedArtifact artifact : resolvedArtifacts) { - // First add the artifact to the master list - existingArtifacts.add(getKey(artifact)); - ExtensionDependency extensionDependency = DependencyUtils.getExtensionInfoOrNull(project, artifact); - if (extensionDependency == null) { - continue; - } - // If this artifact represents an extension, mark this one as a conditional extension - extensionDependency.setConditional(true); - // Add to the master list of accepted extensions - allExtensions.put(extensionDependency.getExtensionId(), extensionDependency); - for (Dependency cd : extensionDependency.getConditionalDependencies()) { - // Add any unsatisfied/unresolved conditional dependencies of this dependency to the queue - if (!exists(cd)) { - queueConditionalDependency(extensionDependency, cd); - } - } - } - return satisfied; + private boolean areEquals(Dependency dependency, ResolvedArtifact artifact) { + return dependency.getName().equals(artifact.getName()) + && Objects.equals(dependency.getVersion(), artifact.getModuleVersion().getId().getVersion()) + && artifact.getModuleVersion().getId().getGroup().equals(dependency.getGroup()); } private void queueConditionalDependency(ExtensionDependency extension, Dependency conditionalDep) { diff --git a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/dependency/DependencyUtils.java b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/dependency/DependencyUtils.java index 5d0f37b1793e3..f5871aa4e63b7 100644 --- a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/dependency/DependencyUtils.java +++ b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/dependency/DependencyUtils.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Optional; import java.util.List; import java.util.Properties; @@ -73,48 +74,56 @@ public static String asDependencyNotation(ArtifactCoords artifactCoords) { return String.join(":", artifactCoords.getGroupId(), artifactCoords.getArtifactId(), artifactCoords.getVersion()); } - public static ExtensionDependency getExtensionInfoOrNull(Project project, ResolvedArtifact artifact) { - ModuleVersionIdentifier artifactId = artifact.getModuleVersion().getId(); - File artifactFile = artifact.getFile(); - - if (artifact.getId().getComponentIdentifier() instanceof ProjectComponentIdentifier) { - final Project projectDep = project.getRootProject().findProject( - ((ProjectComponentIdentifier) artifact.getId().getComponentIdentifier()).getProjectPath()); - SourceSetContainer sourceSets = projectDep == null ? null - : projectDep.getExtensions().findByType(SourceSetContainer.class); - if (sourceSets != null) { - SourceSet mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); - File resourcesDir = mainSourceSet.getOutput().getResourcesDir(); - Path descriptorPath = resourcesDir.toPath().resolve(BootstrapConstants.DESCRIPTOR_PATH); - if (Files.exists(descriptorPath)) { - return loadExtensionInfo(project, descriptorPath, artifactId, projectDep); - } - } - } + public static Optional getOptionalExtensionInfo(Project project, ResolvedArtifact artifact) { + return loadExtensionDependencyFromProject(artifact, project) + .or(() -> loadExtensionDependencyFromDir(artifact, project)) + .or(() -> loadExtensionDependencyFromJar(artifact, project)); + } - if (!artifactFile.exists()) { - return null; - } - if (artifactFile.isDirectory()) { - Path descriptorPath = artifactFile.toPath().resolve(BootstrapConstants.DESCRIPTOR_PATH); - if (Files.exists(descriptorPath)) { - return loadExtensionInfo(project, descriptorPath, artifactId, null); - } - } else if (ArtifactCoords.TYPE_JAR.equals(artifact.getExtension())) { - try (FileSystem artifactFs = ZipUtils.newFileSystem(artifactFile.toPath())) { - Path descriptorPath = artifactFs.getPath(BootstrapConstants.DESCRIPTOR_PATH); - if (Files.exists(descriptorPath)) { - return loadExtensionInfo(project, descriptorPath, artifactId, null); - } - } catch (IOException e) { - throw new GradleException("Failed to read " + artifactFile, e); - } - } - return null; + private static Optional loadExtensionDependencyFromProject(ResolvedArtifact artifact, Project project) { + Optional projectDep = Optional.of(artifact.getId().getComponentIdentifier()) + .filter(ProjectComponentIdentifier.class::isInstance) + .map(ProjectComponentIdentifier.class::cast) + .map(ProjectComponentIdentifier::getProjectPath) + .map(projectPath -> project.getRootProject().findProject(projectPath)); + + return projectDep + .map(Project::getExtensions) + .map(container -> container.findByType(SourceSetContainer.class)) + .map(container -> container.findByName(SourceSet.MAIN_SOURCE_SET_NAME)) + .map(it -> it.getOutput().getResourcesDir()) + .map(File::toPath) + .flatMap(resourceDir -> loadOptionalExtensionInfo(project, resourceDir, artifact.getModuleVersion().getId(), projectDep.get())); + } + + private static Optional loadExtensionDependencyFromDir(ResolvedArtifact artifact, Project project) { + return Optional.of(artifact.getFile().toPath()).filter(Files::exists) + .flatMap(path -> loadOptionalExtensionInfo(project, path, artifact.getModuleVersion().getId(), null)); + } + + private static Optional loadExtensionDependencyFromJar(ResolvedArtifact artifact, Project project) { + return Optional.of(artifact) + .filter(it -> ArtifactCoords.TYPE_JAR.equals(it.getExtension())) + .flatMap(it -> { + File file = it.getFile(); + try (FileSystem artifactFs = ZipUtils.newFileSystem(file.toPath())) { + return loadOptionalExtensionInfo(project, artifactFs.getPath(""), artifact.getModuleVersion().getId(), null); + } catch (IOException e) { + throw new GradleException("Failed to read " + file, e); + } + }); + } + + private static Optional loadOptionalExtensionInfo(Project project, Path resourcePath + , ModuleVersionIdentifier extensionId, Project extensionProject) { + return Optional.of(resourcePath) + .map(path -> path.resolve(BootstrapConstants.DESCRIPTOR_PATH)) + .filter(Files::exists) + .map(descriptorPath -> loadExtensionInfo(project, descriptorPath, extensionId, extensionProject)); } private static ExtensionDependency loadExtensionInfo(Project project, Path descriptorPath, - ModuleVersionIdentifier exentionId, Project extensionProject) { + ModuleVersionIdentifier extensionId, Project extensionProject) { final Properties extensionProperties = new Properties(); try (BufferedReader reader = Files.newBufferedReader(descriptorPath)) { extensionProperties.load(reader); @@ -138,10 +147,10 @@ private static ExtensionDependency loadExtensionInfo(Project project, Path descr final ArtifactKey[] constraints = BootstrapUtils .parseDependencyCondition(extensionProperties.getProperty(BootstrapConstants.DEPENDENCY_CONDITION)); if (extensionProject != null) { - return new LocalExtensionDependency(extensionProject, exentionId, deploymentModule, conditionalDependencies, + return new LocalExtensionDependency(extensionProject, extensionId, deploymentModule, conditionalDependencies, constraints == null ? Collections.emptyList() : Arrays.asList(constraints)); } - return new ExtensionDependency(exentionId, deploymentModule, conditionalDependencies, + return new ExtensionDependency(extensionId, deploymentModule, conditionalDependencies, constraints == null ? Collections.emptyList() : Arrays.asList(constraints)); } @@ -152,13 +161,13 @@ public static Dependency create(DependencyHandler dependencies, String condition } public static void addLocalDeploymentDependency(String deploymentConfigurationName, LocalExtensionDependency extension, - DependencyHandler dependencies) { + DependencyHandler dependencies) { dependencies.add(deploymentConfigurationName, dependencies.project(Collections.singletonMap("path", extension.findDeploymentModulePath()))); } public static void requireDeploymentDependency(String deploymentConfigurationName, ExtensionDependency extension, - DependencyHandler dependencies) { + DependencyHandler dependencies) { dependencies.add(deploymentConfigurationName, extension.getDeploymentModule().getGroupId() + ":" + extension.getDeploymentModule().getArtifactId() + ":" + extension.getDeploymentModule().getVersion());