Skip to content

Commit bb040b4

Browse files
Allow to set attributes to match a variant after a conflict (sonatype-nexus-community#136)
1 parent 23f48e5 commit bb040b4

13 files changed

+225
-71
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ target/
66
.classpath
77
.project
88
.idea
9+
.DS_Store
910

1011
# ci config for local ci build
1112
.circleci/local-config.yml

README.md

+74-2
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ Gradle can be used to build projects developed in various programming languages.
5757

5858
```
5959
plugins {
60-
id 'org.sonatype.gradle.plugins.scan' version '2.5.4' // Update the version as needed
60+
id 'org.sonatype.gradle.plugins.scan' version '2.5.5' // Update the version as needed
6161
}
6262
```
6363

6464
- Or `build.gradle.kts`:
6565
```
6666
plugins {
67-
id ("org.sonatype.gradle.plugins.scan") version "2.5.4" // Update the version as needed
67+
id ("org.sonatype.gradle.plugins.scan") version "2.5.5" // Update the version as needed
6868
}
6969
```
7070

@@ -96,6 +96,9 @@ ossIndexAudit {
9696
modulesIncluded = ['module-1', 'module-2'] // Optional. For multi-module projects, the names of the sub-modules to include for auditing. If not specified all modules are included.
9797
modulesExcluded = ['module-1', 'module-2'] // Optional. For multi-module projects, the names of the sub-modules to exclude from auditing. If not specified no modules are excluded. This value is processed after 'modulesIncluded' if both are specified.
9898
99+
// For projects using multiple custom variants for the release distribution, a Map can be set with the attributes names and values to match the specific variant. See more at the section "How to Deal with Multiple Release Variants" below in this doc.
100+
variantAttributes = ['com.android.build.api.attributes.ProductFlavor:version': 'prod', 'other.attribute': 'other value'] // Optional, use it only when the plugin can't match a variant on its own
101+
99102
// ossIndexAudit can be configured to exclude vulnerabilities from matching
100103
excludeVulnerabilityIds = ['39d74cc8-457a-4e57-89ef-a258420138c5'] // list containing ids of vulnerabilities to be ignored
101104
excludeCoordinates = ['commons-fileupload:commons-fileupload:1.3'] // list containing coordinate of components which if vulnerable should be ignored
@@ -130,6 +133,9 @@ ossIndexAudit {
130133
modulesIncluded = listOf("module-1", "module-2") // Optional. For multi-module projects, the names of the sub-modules to include for auditing. If not specified all modules are included.
131134
modulesExcluded = listOf("module-1", "module-2") // Optional. For multi-module projects, the names of the sub-modules to exclude from auditing. If not specified no modules are excluded. This value is processed after 'modulesIncluded' if both are specified.
132135

136+
// For projects using multiple custom variants for the release distribution, a Map can be set with the attributes names and values to match the specific variant. See more at the section "How to Deal with Multiple Release Variants" below in this doc.
137+
variantAttributes = mapOf("com.android.build.api.attributes.ProductFlavor:version" to "prod", "other.attribute" to "other value") // Optional, use it only when the plugin can't match a variant on its own
138+
133139
// ossIndexAudit can be configured to exclude vulnerabilities from matching
134140
excludeVulnerabilityIds =
135141
listOf("39d74cc8-457a-4e57-89ef-a258420138c5") // list containing ids of vulnerabilities to be ignored
@@ -166,6 +172,9 @@ nexusIQScan {
166172
modulesExcluded = ['module-1', 'module-2'] // Optional. For multi-module projects, the names of the sub-modules to exclude from scanning and evaluation.
167173
dirExcludes = 'some-ant-pattern' // Optional. Comma separated ant-like glob patterns to select directories/archives that should be excluded. For Android projects we suggest using '**/classes.jar,**/annotations.zip,**/lint.jar,**/internal_impl-*.jar'
168174
dirIncludes = 'some-ant-pattern' // Optional. Comma separated ant-like glob patterns to select directories/archives that should be examined
175+
176+
// For projects using multiple custom variants for the release distribution, a Map can be set with the attributes names and values to match the specific variant. See more at the section "How to Deal with Multiple Release Variants" below in this doc.
177+
variantAttributes = ['com.android.build.api.attributes.ProductFlavor:version': 'prod', 'other.attribute': 'other value'] // Optional, use it only when the plugin can't match a variant on its own
169178
}
170179
```
171180

@@ -183,6 +192,9 @@ nexusIQScan {
183192
modulesExcluded = listOf("module-1", "module-2") // Optional. For multi-module projects, the names of the sub-modules to exclude from scanning and evaluation.
184193
dirExcludes = "some-ant-pattern" // Optional. Comma separated ant-like glob patterns to select directories/archives that should be excluded. For Android projects we suggest using "**/classes.jar,**/annotations.zip,**/lint.jar,**/internal_impl-*.jar"
185194
dirIncludes = "some-ant-pattern" // Optional. Comma separated ant-like glob patterns to select directories/archives that should be examined
195+
196+
// For projects using multiple custom variants for the release distribution, a Map can be set with the attributes names and values to match the specific variant. See more at the section "How to Deal with Multiple Release Variants" below in this doc.
197+
variantAttributes = mapOf("com.android.build.api.attributes.ProductFlavor:version" to "prod", "other.attribute" to "other value") // Optional, use it only when the plugin can't match a variant on its own
186198
}
187199
```
188200

@@ -306,6 +318,66 @@ ossIndexAudit {
306318
Just apply the plugin on the root project and all sub-modules will be processed and the output will be a single report
307319
with all components found in each module. This includes Android projects.
308320

321+
## How to Deal with Multiple Release Variants
322+
This plugin makes its best effort to find the release (production) configuration and variant to get the dependencies to analyze.
323+
324+
However, a Gradle project can have multiple custom release variants and the plugin might not be able to tell Gradle which one to pick, resulting in an error like this:
325+
326+
```
327+
> Could not resolve all dependencies for configuration 'sonatypeCopyConfiguration0'.
328+
> Could not resolve project :common-lib.
329+
Required by:
330+
project :app
331+
> The consumer was configured to find a runtime of a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release'. However we cannot choose between the following variants of project :baseapp:
332+
- ciReleaseRuntimeElements
333+
- prodReleaseRuntimeElements
334+
All of them match the consumer attributes:
335+
- Variant 'ciReleaseRuntimeElements' capability common-lib:1.0.0 declares a runtime of a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release':
336+
- Unmatched attributes:
337+
- Provides attribute 'com.android.build.api.attributes.AgpVersionAttr' with value '7.2.2' but the consumer didn't ask for it
338+
- Provides attribute 'com.android.build.api.attributes.ProductFlavor:version' with value 'ci' but the consumer didn't ask for it
339+
- Provides attribute 'com.android.build.gradle.internal.attributes.VariantAttr' with value 'ciRelease' but the consumer didn't ask for it
340+
- Provides a library but the consumer didn't ask for it
341+
- Provides attribute 'org.gradle.jvm.environment' with value 'android' but the consumer didn't ask for it
342+
- Variant 'prodReleaseRuntimeElements' capability common-lib:1.0.0 declares a runtime of a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release':
343+
- Unmatched attributes:
344+
- Provides attribute 'com.android.build.api.attributes.AgpVersionAttr' with value '7.2.2' but the consumer didn't ask for it
345+
- Provides attribute 'com.android.build.api.attributes.ProductFlavor:version' with value 'prod' but the consumer didn't ask for it
346+
- Provides attribute 'com.android.build.gradle.internal.attributes.VariantAttr' with value 'prodRelease' but the consumer didn't ask for it
347+
- Provides a library but the consumer didn't ask for it
348+
- Provides attribute 'org.gradle.jvm.environment' with value 'android' but the consumer didn't ask for it
349+
```
350+
351+
From that output we can see the value of the attribute `com.android.build.api.attributes.ProductFlavor:version` can be used to distinguish between the available variants.
352+
353+
Since attribute names and values can be customized on each project, this plugin allows to set the attributes needed to match the right variant using the property `variantAttributes`.
354+
355+
In the example above, the following configuration would allow the plugin to choose the `prodReleaseRuntimeElements` variant:
356+
357+
*Groovy*
358+
```
359+
nexusIQScan {
360+
variantAttributes = ['com.android.build.api.attributes.ProductFlavor:version': 'prod']
361+
}
362+
363+
ossIndexAudit {
364+
variantAttributes = ['com.android.build.api.attributes.ProductFlavor:version': 'prod']
365+
}
366+
```
367+
368+
*Kotlin*
369+
```
370+
nexusIQScan {
371+
variantAttributes = mapOf("com.android.build.api.attributes.ProductFlavor:version" to "prod")
372+
}
373+
374+
ossIndexAudit {
375+
variantAttributes = mapOf("com.android.build.api.attributes.ProductFlavor:version" to "prod")
376+
}
377+
```
378+
379+
See more information about attributes matching for variant selection see https://docs.gradle.org/current/userguide/variant_model.html#sec:variant-select-errors
380+
309381
## Contributing
310382

311383
We care a lot about making the world a safer place, and that's why we created this `scan-gradle-plugin`. If you as well want to speed up the pace of software development by working on this project, jump on in! Before you start work, create a new issue, or comment on an existing issue, to let others know you are!

src/main/java/org/sonatype/gradle/plugins/scan/common/AndroidAttributeDisambiguationRule.java src/main/java/org/sonatype/gradle/plugins/scan/common/AndroidArtifactTypeAttributeDisambiguationRule.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
import static org.gradle.api.artifacts.type.ArtifactTypeDefinition.JAR_TYPE;
2222

23-
public class AndroidAttributeDisambiguationRule
23+
public class AndroidArtifactTypeAttributeDisambiguationRule
2424
implements AttributeDisambiguationRule<String>
2525
{
2626
private static final String AAR_TYPE = "aar";

src/main/java/org/sonatype/gradle/plugins/scan/common/DependenciesFinder.java

+41-20
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.LinkedHashSet;
2222
import java.util.List;
2323
import java.util.Locale;
24+
import java.util.Map;
2425
import java.util.Set;
2526
import java.util.function.Function;
2627
import java.util.stream.Collector;
@@ -83,30 +84,31 @@ public class DependenciesFinder
8384

8485
private static final String ATTRIBUTES_SUPPORTED_GRADLE_VERSION = "4.0";
8586

86-
public Set<ResolvedDependency> findResolvedDependencies(Project project, boolean allConfigurations) {
87+
public Set<ResolvedDependency> findResolvedDependencies(
88+
Project project,
89+
boolean allConfigurations,
90+
Map<String, String> variantAttributes)
91+
{
8792
return new LinkedHashSet<>(new LinkedHashSet<>(project.getConfigurations()).stream()
8893
.filter(configuration -> isAcceptableConfiguration(configuration, allConfigurations))
89-
.flatMap(configuration -> getDependencies(project, configuration,
94+
.flatMap(configuration -> getDependencies(project, configuration, variantAttributes,
9095
resolvedConfiguration -> resolvedConfiguration.getFirstLevelModuleDependencies().stream()))
9196
.collect(collectResolvedDependencies()).values());
9297
}
9398

94-
public Set<ResolvedArtifact> findResolvedArtifacts(Project project, boolean allConfigurations) {
95-
return new LinkedHashSet<>(project.getConfigurations()).stream()
96-
.filter(configuration -> isAcceptableConfiguration(configuration, allConfigurations))
97-
.flatMap(configuration -> getDependencies(project, configuration,
98-
resolvedConfiguration -> resolvedConfiguration.getResolvedArtifacts().stream()))
99-
.collect(Collectors.toSet());
100-
}
101-
102-
public List<Module> findModules(Project rootProject, boolean allConfigurations, Set<String> modulesExcluded) {
99+
public List<Module> findModules(
100+
Project rootProject,
101+
boolean allConfigurations,
102+
Set<String> modulesExcluded,
103+
Map<String, String> variantAttributes)
104+
{
103105
List<Module> modules = new ArrayList<>();
104106

105107
rootProject.allprojects(project -> {
106108
if (!modulesExcluded.contains(project.getName())) {
107109
Module module = buildModule(project);
108110

109-
findResolvedArtifacts(project, allConfigurations).forEach(resolvedArtifact -> {
111+
findResolvedArtifacts(project, allConfigurations, variantAttributes).forEach(resolvedArtifact -> {
110112
ModuleVersionIdentifier artifactId = resolvedArtifact.getModuleVersion().getId();
111113

112114
Artifact artifact = new Artifact()
@@ -117,8 +119,8 @@ public List<Module> findModules(Project rootProject, boolean allConfigurations,
117119
module.addConsumedArtifact(artifact);
118120
});
119121

120-
findResolvedDependencies(project, allConfigurations).forEach(resolvedDependency ->
121-
module.addDependency(processDependency(resolvedDependency, true, new HashSet<>()))
122+
findResolvedDependencies(project, allConfigurations, variantAttributes).forEach(
123+
resolvedDependency -> module.addDependency(processDependency(resolvedDependency, true, new HashSet<>()))
122124
);
123125

124126
modules.add(module);
@@ -128,6 +130,19 @@ public List<Module> findModules(Project rootProject, boolean allConfigurations,
128130
return modules;
129131
}
130132

133+
@VisibleForTesting
134+
Set<ResolvedArtifact> findResolvedArtifacts(
135+
Project project,
136+
boolean allConfigurations,
137+
Map<String, String> variantAttributes)
138+
{
139+
return new LinkedHashSet<>(project.getConfigurations()).stream()
140+
.filter(configuration -> isAcceptableConfiguration(configuration, allConfigurations))
141+
.flatMap(configuration -> getDependencies(project, configuration, variantAttributes,
142+
resolvedConfiguration -> resolvedConfiguration.getResolvedArtifacts().stream()))
143+
.collect(Collectors.toSet());
144+
}
145+
131146
@VisibleForTesting
132147
Module buildModule(Project project) {
133148
Module module = new Module()
@@ -158,7 +173,7 @@ String getId(Project project) {
158173
}
159174

160175
@VisibleForTesting
161-
Configuration createCopyConfiguration(Project project) {
176+
Configuration createCopyConfiguration(Project project, Map<String, String> variantAttributes) {
162177
String configurationName = COPY_CONFIGURATION_NAME;
163178
for (int i = 0; project.getConfigurations().findByName(configurationName) != null; i++) {
164179
configurationName += i;
@@ -171,13 +186,17 @@ Configuration createCopyConfiguration(Project project) {
171186
if (isAndroidProject) {
172187
AttributeMatchingStrategy<String> artifactTypeMatchingStrategy =
173188
project.getDependencies().getAttributesSchema().attribute(Attribute.of("artifactType", String.class));
174-
artifactTypeMatchingStrategy.getDisambiguationRules().add(AndroidAttributeDisambiguationRule.class);
189+
artifactTypeMatchingStrategy.getDisambiguationRules().add(AndroidArtifactTypeAttributeDisambiguationRule.class);
175190
}
176191

177192
copyConfiguration.attributes(attributeContainer -> {
178193
ObjectFactory factory = project.getObjects();
179194
attributeContainer.attribute(Usage.USAGE_ATTRIBUTE, factory.named(Usage.class, Usage.JAVA_RUNTIME));
180195

196+
variantAttributes.forEach((attributeName, value) -> {
197+
attributeContainer.attribute(Attribute.of(attributeName, String.class), value);
198+
});
199+
181200
if (isAndroidProject) {
182201
addReleaseBuildTypeAttribute(project, attributeContainer);
183202
}
@@ -233,19 +252,20 @@ boolean isAndroidProject(Project project) {
233252
<T> Stream<T> getDependencies(
234253
Project project,
235254
Configuration originalConfiguration,
255+
Map<String, String> variantAttributes,
236256
Function<ResolvedConfiguration, Stream<T>> function)
237257
{
238258
try {
239259
return function.apply(originalConfiguration.getResolvedConfiguration());
240260
}
241261
catch (ResolveException e) {
242-
Configuration copyConfiguration = copyDependencies(project, originalConfiguration, false);
262+
Configuration copyConfiguration = copyDependencies(project, originalConfiguration, false, variantAttributes);
243263

244264
try {
245265
return function.apply(copyConfiguration.getResolvedConfiguration());
246266
}
247267
catch (ResolveException lastResortException) {
248-
copyConfiguration = copyDependencies(project, originalConfiguration, true);
268+
copyConfiguration = copyDependencies(project, originalConfiguration, true, variantAttributes);
249269
return function.apply(copyConfiguration.getResolvedConfiguration());
250270
}
251271
}
@@ -254,9 +274,10 @@ <T> Stream<T> getDependencies(
254274
private Configuration copyDependencies(
255275
Project project,
256276
Configuration originalConfiguration,
257-
boolean skipUnresolvableDependencies)
277+
boolean skipUnresolvableDependencies,
278+
Map<String, String> variantAttributes)
258279
{
259-
Configuration copyConfiguration = createCopyConfiguration(project);
280+
Configuration copyConfiguration = createCopyConfiguration(project, variantAttributes);
260281

261282
originalConfiguration.getAllDependencies().all(dependency -> {
262283
copyConfiguration.getDependencies().add(dependency);

src/main/java/org/sonatype/gradle/plugins/scan/nexus/iq/index/NexusIqIndexTask.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ public NexusIqIndexTask() {
5959
@TaskAction
6060
public void saveModule() {
6161
try {
62-
List<Module> modules =
63-
dependenciesFinder.findModules(getProject(), extension.isAllConfigurations(), extension.getModulesExcluded());
62+
List<Module> modules = dependenciesFinder.findModules(getProject(), extension.isAllConfigurations(),
63+
extension.getModulesExcluded(), extension.getVariantAttributes());
6464
List<File> files = new ArrayList<>(modules.size());
6565

6666
for (Module module : modules) {

src/main/java/org/sonatype/gradle/plugins/scan/nexus/iq/index/NexusIqPluginIndexExtension.java

+12
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.sonatype.gradle.plugins.scan.nexus.iq.index;
1717

1818
import java.util.Collections;
19+
import java.util.Map;
1920
import java.util.Set;
2021

2122
import org.gradle.api.Project;
@@ -26,8 +27,11 @@ public class NexusIqPluginIndexExtension
2627

2728
private Set<String> modulesExcluded;
2829

30+
private Map<String, String> variantAttributes;
31+
2932
public NexusIqPluginIndexExtension(Project project) {
3033
modulesExcluded = Collections.emptySet();
34+
variantAttributes = Collections.emptyMap();
3135
}
3236

3337
public boolean isAllConfigurations() {
@@ -45,4 +49,12 @@ public Set<String> getModulesExcluded() {
4549
public void setModulesExcluded(Set<String> modulesExcluded) {
4650
this.modulesExcluded = modulesExcluded;
4751
}
52+
53+
public Map<String, String> getVariantAttributes() {
54+
return variantAttributes;
55+
}
56+
57+
public void setVariantAttributes(Map<String, String> variantAttributes) {
58+
this.variantAttributes = variantAttributes;
59+
}
4860
}

src/main/java/org/sonatype/gradle/plugins/scan/nexus/iq/scan/NexusIqPluginScanExtension.java

+12
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.sonatype.gradle.plugins.scan.nexus.iq.scan;
1717

1818
import java.util.Collections;
19+
import java.util.Map;
1920
import java.util.Set;
2021

2122
import com.sonatype.clm.dto.model.policy.Stage;
@@ -55,6 +56,8 @@ public class NexusIqPluginScanExtension
5556

5657
private String dirExcludes;
5758

59+
private Map<String, String> variantAttributes;
60+
5861
public NexusIqPluginScanExtension(Project project) {
5962
stage = Stage.ID_BUILD;
6063
organizationId = "";
@@ -64,6 +67,7 @@ public NexusIqPluginScanExtension(Project project) {
6467
modulesExcluded = Collections.emptySet();
6568
dirIncludes = "";
6669
dirExcludes = "";
70+
variantAttributes = Collections.emptyMap();
6771
}
6872

6973
public String getUsername() {
@@ -178,4 +182,12 @@ public String getDirExcludes() {
178182
public void setDirExcludes(String dirExcludes) {
179183
this.dirExcludes = dirExcludes;
180184
}
185+
186+
public Map<String, String> getVariantAttributes() {
187+
return variantAttributes;
188+
}
189+
190+
public void setVariantAttributes(Map<String, String> variantAttributes) {
191+
this.variantAttributes = variantAttributes;
192+
}
181193
}

0 commit comments

Comments
 (0)