Skip to content

Commit

Permalink
Merge pull request #185 from nebula-plugins/VerifyPublicationTask-avo…
Browse files Browse the repository at this point in the history
…id-project-usage

Refactor Publishing Verification to avoid using project object during task execution
  • Loading branch information
rpalcolea authored Mar 6, 2023
2 parents a41f2f2 + eaed082 commit 2098059
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import org.gradle.api.artifacts.CacheableRule
import org.gradle.api.artifacts.ComponentMetadataContext
import org.gradle.api.artifacts.ComponentMetadataDetails
import org.gradle.api.artifacts.ComponentMetadataRule
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.attributes.Attribute
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.publish.ivy.tasks.PublishToIvyRepository
Expand Down Expand Up @@ -49,12 +51,20 @@ class PublishVerificationPlugin implements Plugin<Project> {
if (!sourceSet) return
TaskProvider<VerifyPublicationTask> verificationTask = project.tasks.register("verifyPublication", VerifyPublicationTask)
TaskProvider<VerificationReportTask> reportTask = getOrCreateReportTask(project, verificationTask)
VerificationViolationsCollectorHolderExtension verificationViolationsCollectorHolderExtension = project.rootProject.extensions.findByType(VerificationViolationsCollectorHolderExtension)
verificationTask.configure(new Action<VerifyPublicationTask>() {
@Override
void execute(VerifyPublicationTask verifyPublicationTask) {
verifyPublicationTask.ignore = extension.ignore
verifyPublicationTask.ignoreGroups = extension.ignoreGroups
verifyPublicationTask.sourceSet = sourceSet
verifyPublicationTask.projectName.set(project.name)
verifyPublicationTask.targetStatus.set(project.status.toString())
verifyPublicationTask.runtimeClasspath.set(project.configurations.named(sourceSet.getRuntimeClasspathConfigurationName()))
verifyPublicationTask.ignore.set(extension.ignore)
verifyPublicationTask.ignoreGroups.set(extension.ignoreGroups)
verifyPublicationTask.verificationViolationsCollectorHolderExtension.set(verificationViolationsCollectorHolderExtension)
verifyPublicationTask.definedDependencies.set(project.configurations.collect { Configuration configuration ->
configuration.dependencies
}.flatten() as List<Dependency>)

}
})

Expand Down Expand Up @@ -95,37 +105,28 @@ class PublishVerificationPlugin implements Plugin<Project> {
} else {
verificationReportTask = project.rootProject.tasks.named('verifyPublicationReport', VerificationReportTask)
}
VerificationViolationsCollectorHolderExtension verificationViolationsCollectorHolderExtension = project.rootProject.extensions.findByType(VerificationViolationsCollectorHolderExtension)
verificationReportTask.configure(new Action<VerificationReportTask>() {
@Override
void execute(VerificationReportTask reportTask) {
reportTask.targetStatus.set(project.status.toString())
reportTask.verificationViolationsCollectorHolderExtension.set(verificationViolationsCollectorHolderExtension)
reportTask.dependsOn(verificationTask)
}
})
return verificationReportTask
}

private void configureHooks(Project project, TaskProvider<VerificationReportTask> reportTask) {
project.tasks.withType(PublishToIvyRepository) { Task task ->
project.tasks.withType(PublishToIvyRepository).configureEach { Task task ->
task.dependsOn(reportTask)
}
project.tasks.withType(PublishToMavenRepository) { Task task ->
project.tasks.withType(PublishToMavenRepository).configureEach { Task task ->
task.dependsOn(reportTask)
}
project.plugins.withId('com.jfrog.artifactory') {
def artifactoryPublishTask = project.tasks.findByName('artifactoryPublish')
if (artifactoryPublishTask) {
artifactoryPublishTask.dependsOn(reportTask)
}
//newer version of artifactory plugin introduced this task to do actual publishing, so we have to
//hook even for this one.
def artifactoryDeployTask = project.tasks.findByName("artifactoryDeploy")
if (artifactoryDeployTask) {
artifactoryDeployTask.dependsOn(reportTask)
}
}
}

static class VerificationViolationsCollectorHolderExtension {
Map<Project, ViolationsContainer> collector = new ConcurrentHashMap<>()
Map<String, ViolationsContainer> collector = new ConcurrentHashMap<>()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,38 @@ package nebula.plugin.publishing.verification

import org.gradle.api.BuildCancelledException
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.gradle.work.DisableCachingByDefault

@DisableCachingByDefault
class VerificationReportTask extends DefaultTask {
abstract class VerificationReportTask extends DefaultTask {

protected VerificationReportGenerator verificationReportGenerator = new VerificationReportGenerator()

@Internal
abstract Property<PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension> getVerificationViolationsCollectorHolderExtension()

@Input
abstract Property<String> getTargetStatus()

@TaskAction
void reportViolatingDependencies() {
if (project.rootProject == project) {
reportErrors(getViolations())
reportErrors(verificationViolationsCollectorHolderExtension.get().collector)
}
}

private Map<Project, ViolationsContainer> getViolations() {
PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension extension = project.rootProject.extensions
.findByType(PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension)
extension.collector
}

void reportErrors(Map<Project, ViolationsContainer> violationsPerProject) {
void reportErrors(Map<String, ViolationsContainer> violationsPerProject) {
if (violationsPerProject.any { it.value.hasViolations() } ) {
throw new BuildCancelledException(generateReportMessage(violationsPerProject))
}
}


private String generateReportMessage(Map<Project, ViolationsContainer> violationsPerProject){
verificationReportGenerator.generateReport(violationsPerProject.collectEntries { [it.key.toString(), it.value] } as Map<String, ViolationsContainer> , project.status.toString())
private String generateReportMessage(Map<String, ViolationsContainer> violationsPerProject){
verificationReportGenerator.generateReport(violationsPerProject.collectEntries { [it.key, it.value] } as Map<String, ViolationsContainer> , targetStatus.get())
}
}
Original file line number Diff line number Diff line change
@@ -1,42 +1,57 @@
package nebula.plugin.publishing.verification

import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.artifacts.*
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.result.DependencyResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.artifacts.result.UnresolvedDependencyResult
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.gradle.work.DisableCachingByDefault

@DisableCachingByDefault
class VerifyPublicationTask extends DefaultTask {
abstract class VerifyPublicationTask extends DefaultTask {

@Input
Set<ModuleIdentifier> ignore
abstract SetProperty<ModuleIdentifier> getIgnore()

@Input
abstract SetProperty<String> getIgnoreGroups()

@Input
abstract Property<String> getTargetStatus()

@InputFiles
@Classpath
abstract Property<Configuration> getRuntimeClasspath()

@Input
Set<String> ignoreGroups
abstract Property<String> getProjectName()

@Input
SourceSet sourceSet
abstract ListProperty<Dependency> getDefinedDependencies()

@Internal
abstract Property<PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension> getVerificationViolationsCollectorHolderExtension()

@TaskAction
void verifyDependencies() {
if (sourceSet == null) throw new IllegalStateException('sourceSet must be configured')
Configuration runtimeClasspath = project.configurations.getByName(sourceSet.getRuntimeClasspathConfigurationName())
Set<ResolvedDependencyResult> firstLevel = getNonProjectDependencies(runtimeClasspath)
List<StatusVerificationViolation> violations = new StatusVerification(ignore, ignoreGroups, project.status).verify(firstLevel)
Set<ResolvedDependencyResult> firstLevel = getNonProjectDependencies(runtimeClasspath.get())
List<StatusVerificationViolation> violations = new StatusVerification(ignore.get(), ignoreGroups.get(), targetStatus.get()).verify(firstLevel)

List<Dependency> definedDependencies = getDefinedDependencies()
List<VersionSelectorVerificationViolation> versionViolations = new VersionSelectorVerification(ignore, ignoreGroups).verify(definedDependencies)
List<VersionSelectorVerificationViolation> versionViolations = new VersionSelectorVerification(ignore.get(), ignoreGroups.get()).verify(definedDependencies.get())

getViolations().put(project,
new ViolationsContainer(statusViolations: violations, versionSelectorViolations: versionViolations))
verificationViolationsCollectorHolderExtension.get().collector.put(projectName.get(), new ViolationsContainer(statusViolations: violations, versionSelectorViolations: versionViolations))
}

private Set<ResolvedDependencyResult> getNonProjectDependencies(Configuration runtimeClasspath) {
private static Set<ResolvedDependencyResult> getNonProjectDependencies(Configuration runtimeClasspath) {
Set<? extends DependencyResult> firstLevelDependencies = runtimeClasspath.incoming.resolutionResult.root.getDependencies()
.findAll { !it.constraint }
List<UnresolvedDependencyResult> unresolvedDependencies = firstLevelDependencies.findAll { it instanceof UnresolvedDependencyResult } as List<UnresolvedDependencyResult>
Expand All @@ -48,16 +63,4 @@ class VerifyPublicationTask extends DefaultTask {
result instanceof ResolvedDependencyResult && ! (result.selected.id instanceof ProjectComponentIdentifier)
} as Set<ResolvedDependencyResult>
}

private Map<Project, ViolationsContainer> getViolations() {
PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension extension = project.rootProject.extensions
.findByType(PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension)
extension.collector
}

private List<Dependency> getDefinedDependencies() {
project.configurations.collect { Configuration configuration ->
configuration.dependencies
}.flatten() as List<Dependency>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -329,39 +329,6 @@ class PublishVerificationPluginIntegrationSpec extends IntegrationSpec {
assertStatusFailureMessage(result, expectedFailureDependency, projectStatus)
}

def 'should work with artifactoryPublish'() {
given:
def expectedFailureDependency = 'foo:bar:1.0-SNAPSHOT'
def projectStatus = 'release'
DependencyGraphBuilder builder = new DependencyGraphBuilder()
builder.addModule(expectedFailureDependency)
def dependencies = "implementation '$expectedFailureDependency'"

buildFile << createBuildFileFromTemplate(projectStatus, dependencies, builder)

when:
def result = runTasksWithFailure('build', 'artifactoryPublish')

then:
assertStatusFailureMessage(result, expectedFailureDependency, projectStatus)
}

def 'should work with artifactoryDeploy'() {
given:
def expectedFailureDependency = 'foo:bar:1.0-SNAPSHOT'
def projectStatus = 'release'
DependencyGraphBuilder builder = new DependencyGraphBuilder()
builder.addModule(expectedFailureDependency)
def dependencies = "implementation '$expectedFailureDependency'"

buildFile << createBuildFileFromTemplate(projectStatus, dependencies, builder)

when:
def result = runTasksWithFailure('build', 'artifactoryDeploy')

then:
assertStatusFailureMessage(result, expectedFailureDependency, projectStatus)
}

def 'should work with forced dependency'() {
given:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ class VerificationReportTaskSpec extends Specification {
def 'build is unaffected when there is no violation'() {
given:
Project project = ProjectBuilder.builder().build()
project.extensions.create('collectorExtension', PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension)
PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension extension = project.extensions.create('collectorExtension', PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension)
VerificationReportTask task = project.tasks.create('report', VerificationReportTask)
def generator = Mock(VerificationReportGenerator)
task.verificationReportGenerator = generator
task.targetStatus.set(project.status.toString())
task.verificationViolationsCollectorHolderExtension.set(extension)

when:
task.reportViolatingDependencies()
Expand All @@ -37,6 +39,8 @@ class VerificationReportTaskSpec extends Specification {
VerificationReportTask task = project.tasks.create('report', VerificationReportTask)
def generator = Mock(VerificationReportGenerator)
task.verificationReportGenerator = generator
task.targetStatus.set(project.status.toString())
task.verificationViolationsCollectorHolderExtension.set(extension)

when:
task.reportViolatingDependencies()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import nebula.test.dependencies.GradleDependencyGenerator
import nebula.test.dependencies.ModuleBuilder
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.internal.artifacts.DefaultModuleIdentifier
import org.gradle.api.plugins.JavaPlugin
import org.gradle.testfixtures.ProjectBuilder
Expand All @@ -29,7 +31,7 @@ class VerifyPublicationTaskSpec extends Specification {
noExceptionThrown()
def holderExtension = project.extensions.findByType(PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension)
holderExtension.collector.size() == 1
def violations = holderExtension.collector[project]
def violations = holderExtension.collector[project.name]
violations.statusViolations.size() == 0

where:
Expand All @@ -56,7 +58,7 @@ class VerifyPublicationTaskSpec extends Specification {
noExceptionThrown()
def holderExtension = project.extensions.findByType(PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension)
holderExtension.collector.size() == 1
def violations = holderExtension.collector[project]
def violations = holderExtension.collector[project.name]
violations.statusViolations.size() == 1
def violation = violations.statusViolations.first()
violation.id.group == 'foo'
Expand Down Expand Up @@ -88,7 +90,7 @@ class VerifyPublicationTaskSpec extends Specification {
noExceptionThrown()
def holderExtension = project.extensions.findByType(PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension)
holderExtension.collector.size() == 1
def violations = holderExtension.collector[project]
def violations = holderExtension.collector[project.name]
violations.statusViolations.size() == 0
violations.versionSelectorViolations.size() == 0
}
Expand All @@ -111,36 +113,13 @@ class VerifyPublicationTaskSpec extends Specification {
noExceptionThrown()
def holderExtension = project.extensions.findByType(PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension)
holderExtension.collector.size() == 1
def violations = holderExtension.collector[project]
def violations = holderExtension.collector[project.name]
violations.statusViolations.size() == 0
violations.versionSelectorViolations.size() == 0
}

def 'test error collection when incorrect version is used'() {
given:
Project project = ProjectBuilder.builder().build()
def task = setupProjectAndTask(project, 'release', 'release')
project.dependencies {
runtimeOnly 'foo:bar:1.0+'
}

when:
task.verifyDependencies()

then:
noExceptionThrown()
def holderExtension = project.extensions.findByType(PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension)
holderExtension.collector.size() == 1
def violations = holderExtension.collector[project]
violations.versionSelectorViolations.size() == 1
def violation = violations.versionSelectorViolations.first()
violation.dependency.group == 'foo'
violation.dependency.name == 'bar'
}


Task setupProjectAndTask(Project project, String libraryStatus, String projectStatus) {
project.extensions.create('collectorExtension', PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension)
def extension = project.extensions.create('collectorExtension', PublishVerificationPlugin.VerificationViolationsCollectorHolderExtension)
project.plugins.apply(JavaPlugin)
project.status = projectStatus

Expand All @@ -149,9 +128,15 @@ class VerifyPublicationTaskSpec extends Specification {

def task = project.task('verify', type: VerifyPublicationTask)
task.configure {
ignore = Collections.emptySet()
ignoreGroups = Collections.emptySet()
sourceSet = project.sourceSets.main
ignore.set(Collections.emptySet())
ignoreGroups.set(Collections.emptySet())
runtimeClasspath.set(project.configurations.getByName(project.sourceSets.main.getRuntimeClasspathConfigurationName()))
definedDependencies.set(project.configurations.collect { Configuration configuration ->
configuration.dependencies
}.flatten() as List<Dependency>)
projectName.set(project.name)
targetStatus.set(project.status.toString())
verificationViolationsCollectorHolderExtension.set(extension)
}
}

Expand Down

0 comments on commit 2098059

Please sign in to comment.