Skip to content

Commit

Permalink
Ignore project dependencies without main sourceSet
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 14, 2022
1 parent c19be6d commit 5f90888
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 131 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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");
Expand All @@ -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<String> getDependantProvidedTaskName(Task task) {
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,20 @@ 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
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,55 @@ 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 5f90888

Please sign in to comment.