-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Several Gradle improvements #27
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,31 +15,18 @@ | |
*/ | ||
package org.spdx.sbom.gradle; | ||
|
||
import java.io.File; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Map.Entry; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
import org.gradle.api.Plugin; | ||
import org.gradle.api.Project; | ||
import org.gradle.api.Task; | ||
import org.gradle.api.Transformer; | ||
import org.gradle.api.artifacts.Configuration; | ||
import org.gradle.api.artifacts.Dependency; | ||
import org.gradle.api.artifacts.component.ComponentArtifactIdentifier; | ||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier; | ||
import org.gradle.api.artifacts.repositories.MavenArtifactRepository; | ||
import org.gradle.api.artifacts.result.ArtifactResult; | ||
import org.gradle.api.artifacts.result.ResolvedArtifactResult; | ||
import org.gradle.api.artifacts.result.ResolvedComponentResult; | ||
import org.gradle.api.provider.Provider; | ||
import org.gradle.api.tasks.TaskProvider; | ||
import org.gradle.internal.component.local.model.OpaqueComponentIdentifier; | ||
import org.spdx.sbom.gradle.SpdxSbomExtension.Target; | ||
import org.spdx.sbom.gradle.maven.PomResolver; | ||
import org.spdx.sbom.gradle.project.DocumentInfo; | ||
import org.spdx.sbom.gradle.project.ProjectInfo; | ||
import org.spdx.sbom.gradle.project.ScmInfo; | ||
|
@@ -111,15 +98,6 @@ private void createTaskForTarget( | |
|
||
List<String> configurationNames = target.getConfigurations().get(); | ||
for (var configurationName : configurationNames) { | ||
Provider<Set<ResolvedArtifactResult>> artifacts = | ||
project | ||
.getConfigurations() | ||
.getByName(configurationName) | ||
.getIncoming() | ||
.getArtifacts() | ||
.getResolvedArtifacts(); | ||
t.getResolvedArtifacts().putAll(artifacts.map(new ArtifactTransformer())); | ||
|
||
Provider<ResolvedComponentResult> rootComponent = | ||
project | ||
.getConfigurations() | ||
|
@@ -128,24 +106,6 @@ private void createTaskForTarget( | |
.getResolutionResult() | ||
.getRootComponent(); | ||
|
||
Configuration pomsConfig = | ||
project | ||
.getConfigurations() | ||
.detachedConfiguration( | ||
project | ||
.getConfigurations() | ||
.getByName(configurationName) | ||
.getIncoming() | ||
.getResolutionResult() | ||
.getAllComponents() | ||
.stream() | ||
.filter(rcr -> rcr.getId() instanceof ModuleComponentIdentifier) | ||
.map(rcr -> rcr.getId().getDisplayName() + "@pom") | ||
.map(pom -> project.getDependencies().create(pom)) | ||
.toArray(Dependency[]::new)); | ||
t.getPoms() | ||
.putAll(PomResolver.newPomResolver(project).effectivePoms(pomsConfig)); | ||
Comment on lines
-131
to
-147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was causing resolution issues in my project because of variants and mismatched attributes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can see the information Gradle uses to resolve the variant here - https://repo1.maven.org/maven2/com/eygraber/android-date-time-input-common/0.9.16/android-date-time-input-common-0.9.16.module |
||
|
||
t.getRootComponents().add(rootComponent); | ||
} | ||
t.getMavenRepositories() | ||
|
@@ -160,23 +120,9 @@ private void createTaskForTarget( | |
e -> e, | ||
e -> | ||
((MavenArtifactRepository) | ||
project.getRepositories().getByName(e)) | ||
project.getRepositories().getByName(e)) | ||
.getUrl())))); | ||
}); | ||
aggregate.configure(t -> t.dependsOn(task)); | ||
} | ||
|
||
private static class ArtifactTransformer | ||
implements Transformer< | ||
Map<ComponentArtifactIdentifier, File>, Collection<ResolvedArtifactResult>> { | ||
|
||
@Override | ||
public Map<ComponentArtifactIdentifier, File> transform( | ||
Collection<ResolvedArtifactResult> resolvedArtifactResults) { | ||
return resolvedArtifactResults.stream() | ||
// ignore gradle API components as they cannot be serialized | ||
.filter(x -> !(x.getId().getComponentIdentifier() instanceof OpaqueComponentIdentifier)) | ||
.collect(Collectors.toMap(ArtifactResult::getId, ResolvedArtifactResult::getFile)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,10 +18,22 @@ | |
import java.io.File; | ||
import java.io.FileOutputStream; | ||
import java.net.URI; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
import javax.inject.Inject; | ||
import org.gradle.api.DefaultTask; | ||
import org.gradle.api.artifacts.ConfigurationContainer; | ||
import org.gradle.api.artifacts.component.ComponentArtifactIdentifier; | ||
import org.gradle.api.artifacts.component.ComponentIdentifier; | ||
import org.gradle.api.artifacts.dsl.DependencyHandler; | ||
import org.gradle.api.artifacts.result.DependencyResult; | ||
import org.gradle.api.artifacts.result.ResolvedArtifactResult; | ||
import org.gradle.api.artifacts.result.ResolvedComponentResult; | ||
import org.gradle.api.artifacts.result.ResolvedDependencyResult; | ||
import org.gradle.api.file.DirectoryProperty; | ||
import org.gradle.api.provider.ListProperty; | ||
import org.gradle.api.provider.MapProperty; | ||
|
@@ -31,11 +43,13 @@ | |
import org.gradle.api.tasks.Internal; | ||
import org.gradle.api.tasks.OutputDirectory; | ||
import org.gradle.api.tasks.TaskAction; | ||
import org.gradle.maven.MavenModule; | ||
import org.gradle.maven.MavenPomArtifact; | ||
import org.spdx.jacksonstore.MultiFormatStore; | ||
import org.spdx.jacksonstore.MultiFormatStore.Format; | ||
import org.spdx.library.model.SpdxDocument; | ||
import org.spdx.sbom.gradle.extensions.SpdxSbomTaskExtension; | ||
import org.spdx.sbom.gradle.maven.PomInfo; | ||
import org.spdx.sbom.gradle.maven.PomResolver; | ||
import org.spdx.sbom.gradle.project.DocumentInfo; | ||
import org.spdx.sbom.gradle.project.ProjectInfo; | ||
import org.spdx.sbom.gradle.project.ScmInfo; | ||
|
@@ -45,16 +59,18 @@ | |
import org.spdx.storage.simple.InMemSpdxStore; | ||
|
||
public abstract class SpdxSbomTask extends DefaultTask { | ||
@Inject | ||
protected abstract DependencyHandler getDependencyHandler(); | ||
|
||
@Inject | ||
protected abstract ConfigurationContainer getConfigurations(); | ||
|
||
@Internal | ||
abstract Property<SpdxKnownLicensesService> getSpdxKnownLicensesService(); | ||
|
||
@Input | ||
abstract ListProperty<ResolvedComponentResult> getRootComponents(); | ||
|
||
@Input | ||
abstract MapProperty<ComponentArtifactIdentifier, File> getResolvedArtifacts(); | ||
|
||
@OutputDirectory | ||
public abstract DirectoryProperty getOutputDirectory(); | ||
|
||
|
@@ -64,9 +80,6 @@ public abstract class SpdxSbomTask extends DefaultTask { | |
@Input | ||
abstract MapProperty<String, URI> getMavenRepositories(); | ||
|
||
@Input | ||
abstract MapProperty<String, PomInfo> getPoms(); | ||
|
||
@Input | ||
abstract Property<String> getFilename(); | ||
|
||
|
@@ -84,6 +97,37 @@ public abstract class SpdxSbomTask extends DefaultTask { | |
|
||
@TaskAction | ||
public void generateSbom() throws Exception { | ||
Set<ComponentIdentifier> componentIds = new HashSet<>(); | ||
for (var rootComponent : getRootComponents().get()) { | ||
componentIds.addAll( | ||
gatherSelectedDependencies(rootComponent, new HashSet<>(), new HashSet<>())); | ||
} | ||
|
||
Map<ComponentArtifactIdentifier, File> resolvedArtifactsById = new HashMap<>(); | ||
|
||
@SuppressWarnings("unchecked") | ||
List<ResolvedArtifactResult> resolvedArtifactResults = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess fundamentally, is this okay by gradle's build conventions? Resolving artifacts at task execution time? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been using this in my project for ~8 months and it's been working great. I think it just looks at the result of the resolution that is done before execution time. The javadoc for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So further research is telling me this is not compatible with task configuration avoidance, someone from game recommended against this approach There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm pretty sure Gradle uses this approach themselves, and I've been using it with task configuration avoidance for a while now. I can look for more info from Gradle on this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gotcha, I'll see if I can validate what I've been told too There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From a configuration cache perspective it should be fine Waiting to hear from someone at Gradle conclusively about usage of the API in general, but in this case the existing code is using the same underlying API, and it won't work with projects using variants, so it's probably fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't looked very closely, but Gradle has been thinking about disallowing resolving dependencies at configuration time: gradle/gradle#2298 . Are there any other times at which we might resolve these artifacts? (Resolving dependencies during task execution makes sense to me) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't heard from anyone at gradle directly, but it looks like they're working on a new API too address this. Until that's released this is one of at least two options. The other option is something that CashApp is doing in Licensee where they used detached configurations and copy all the variant information into it. I think the sentiment is that this solution is more "correct" (despite being a little hacky), but it is vastly more complex. |
||
getDependencyHandler() | ||
.createArtifactResolutionQuery() | ||
.forComponents(componentIds) | ||
.withArtifacts(MavenModule.class, MavenPomArtifact.class) | ||
.execute() | ||
.getResolvedComponents() | ||
.stream() | ||
.flatMap( | ||
componentArtifactsResult -> | ||
componentArtifactsResult.getArtifacts(MavenPomArtifact.class).stream()) | ||
.filter(artifactResult -> artifactResult instanceof ResolvedArtifactResult) | ||
.map(artifactResult -> (ResolvedArtifactResult) artifactResult) | ||
.peek( | ||
resolvedArtifactResult -> | ||
resolvedArtifactsById.put( | ||
resolvedArtifactResult.getId(), resolvedArtifactResult.getFile())) | ||
.collect(Collectors.toList()); | ||
|
||
PomResolver pomResolver = | ||
PomResolver.newPomResolver(getDependencyHandler(), getConfigurations(), getLogger()); | ||
|
||
ISerializableModelStore modelStore = | ||
new MultiFormatStore(new InMemSpdxStore(), Format.JSON_PRETTY); | ||
SpdxDocumentBuilder documentBuilder = | ||
|
@@ -92,9 +136,9 @@ public void generateSbom() throws Exception { | |
getAllProjects().get(), | ||
getLogger(), | ||
modelStore, | ||
getResolvedArtifacts().get(), | ||
resolvedArtifactsById, | ||
getMavenRepositories().get(), | ||
getPoms().get(), | ||
pomResolver.effectivePoms(resolvedArtifactResults), | ||
getTaskExtension().getOrNull(), | ||
getDocumentInfo().get(), | ||
getScmInfo().get(), | ||
|
@@ -114,4 +158,21 @@ public void generateSbom() throws Exception { | |
new FileOutputStream(getOutputDirectory().file(getFilename()).get().getAsFile()); | ||
modelStore.serialize(doc.getDocumentUri(), out); | ||
} | ||
|
||
private Set<ComponentIdentifier> gatherSelectedDependencies( | ||
ResolvedComponentResult component, | ||
Set<ResolvedComponentResult> seenComponents, | ||
Set<ComponentIdentifier> componentIds) { | ||
if (seenComponents.add(component)) { | ||
for (DependencyResult dep : component.getDependencies()) { | ||
if (dep instanceof ResolvedDependencyResult) { | ||
ResolvedDependencyResult resolvedDep = (ResolvedDependencyResult) dep; | ||
componentIds.add(resolvedDep.getSelected().getId()); | ||
gatherSelectedDependencies(resolvedDep.getSelected(), seenComponents, componentIds); | ||
} | ||
} | ||
} | ||
|
||
return componentIds; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was causing resolution issues in my project because of variants and mismatched attributes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you describe the issue a little more for us to understand?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wish I could 😅
Gradle has a concept of variants, and for some reason this code causes Gradle to use the wrong attributes when resolving some dependencies from some configurations.
The issue with the
pomsConfig
below is similar, and I think it is related to detached configurations not copying the attributes. I tried manually copying the attributes from the original configuration to the detached one, but there where still issues.This is an area that I don't have the most expertise in with Gradle, so I'm not much help here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm working on a standalone repro, but in the meantime here are some of the failures from my project: