Skip to content

Commit

Permalink
Load main sourceSet only if present for project dependencies
Browse files Browse the repository at this point in the history
Treating Extension Dependencies as optional

Fixes quarkusio#27950

Signed-off-by: Adler Fleurant <[email protected]>
  • Loading branch information
AdlerFleurant committed Sep 19, 2022
1 parent d06ad47 commit a603f26
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 127 deletions.
12 changes: 12 additions & 0 deletions devtools/gradle/gradle-application-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@
<artifactId>quarkus-devmode-test-utils</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-gradle-plugin</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package io.quarkus.gradle;

import static org.assertj.core.api.Assertions.assertThat;
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

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;
Expand All @@ -16,12 +21,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();
Expand Down Expand Up @@ -67,7 +78,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");
Expand All @@ -84,7 +95,67 @@ public void shouldReturnMutlipleOutputSourceDirectories() {

}

private static final List<String> getDependantProvidedTaskName(Task task) {
@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,
"buildscript {\n" +
" repositories {\n" +
" mavenLocal()\n" +
" mavenCentral()\n" +
" }\n" +
" dependencies {\n" +
" classpath(\"org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10\")\n" +
" }\n" +
"}\n" +
"\n" +
"apply(plugin = \"org.jetbrains.kotlin.multiplatform\")\n" +
"\n" +
"repositories {\n" +
" mavenCentral()\n" +
"}\n" +
"\n" +
"configure<org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension>{\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 List<String> getDependantProvidedTaskName(Task task) {
List<String> dependantTaskNames = new ArrayList<>();
for (Object t : task.getDependsOn()) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
});
Expand Down Expand Up @@ -73,9 +71,9 @@ private Set<ExtensionDependency> collectQuarkusExtensions(ResolvedDependency dep
}
Set<ExtensionDependency> 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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 {

Expand Down Expand Up @@ -62,29 +63,21 @@ 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);
}
}

private List<AppArtifactKey> collectRuntimeExtensionsDeploymentKeys(Set<ResolvedArtifact> runtimeArtifacts) {
List<AppArtifactKey> 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<AppArtifactKey> findExtensionInConfiguration(Set<ResolvedArtifact> deploymentArtifacts,
Expand Down
1 change: 1 addition & 0 deletions devtools/gradle/gradle-model/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ dependencies {
implementation "io.quarkus:quarkus-bootstrap-gradle-resolver:${version}"
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api:${kotlin_version}"
testImplementation "io.quarkus:quarkus-devtools-testing:${version}"
testImplementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}"
}

task sourcesJar(type: Jar, dependsOn: classes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -37,8 +40,7 @@ public class ConditionalDependenciesEnabler {
private final Set<ArtifactKey> existingArtifacts = new HashSet<>();
private final List<Dependency> 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;

Expand All @@ -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 {
Expand Down Expand Up @@ -88,75 +90,57 @@ private void reset() {
}

private void collectConditionalDependencies(Set<ResolvedArtifact> 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<ResolvedArtifact> artifacts) {
artifacts.stream().map(ConditionalDependenciesEnabler::getKey).forEach(existingArtifacts::add);
}

private Map<ModuleVersionIdentifier, ExtensionDependency> getArtifactExtensions(Set<ResolvedArtifact> 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) {

final Configuration conditionalDeps = createConditionalDependenciesConfiguration(project, conditionalDep);
Set<ResolvedArtifact> 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) {
Expand Down
Loading

0 comments on commit a603f26

Please sign in to comment.