diff --git a/src/main/groovy/com/netflix/nebula/lint/plugin/GradleLintReportTask.groovy b/src/main/groovy/com/netflix/nebula/lint/plugin/GradleLintReportTask.groovy index 669c0f7e..de8d49f6 100644 --- a/src/main/groovy/com/netflix/nebula/lint/plugin/GradleLintReportTask.groovy +++ b/src/main/groovy/com/netflix/nebula/lint/plugin/GradleLintReportTask.groovy @@ -15,54 +15,77 @@ */ package com.netflix.nebula.lint.plugin -import com.netflix.nebula.interop.GradleKt import com.netflix.nebula.lint.GradleLintPatchAction import com.netflix.nebula.lint.StyledTextService -import com.netflix.nebula.lint.utils.DeprecationLoggerUtils +import com.netflix.nebula.lint.plugin.report.LintReport +import com.netflix.nebula.lint.plugin.report.internal.LintHtmlReport +import com.netflix.nebula.lint.plugin.report.internal.LintTextReport +import com.netflix.nebula.lint.plugin.report.internal.LintXmlReport import org.codenarc.AnalysisContext -import org.codenarc.report.HtmlReportWriter -import org.codenarc.report.ReportWriter -import org.codenarc.report.TextReportWriter -import org.codenarc.report.XmlReportWriter import org.codenarc.results.Results import org.codenarc.rule.Violation import org.gradle.api.Action import org.gradle.api.DefaultTask import org.gradle.api.GradleException -import org.gradle.api.plugins.quality.CodeNarcReports -import org.gradle.api.plugins.quality.internal.CodeNarcReportsImpl +import org.gradle.api.InvalidUserDataException +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property -import org.gradle.api.reporting.Report -import org.gradle.api.reporting.Reporting import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.Nested import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.VerificationTask import org.gradle.internal.reflect.Instantiator -import org.gradle.util.GradleVersion import javax.inject.Inject import static com.netflix.nebula.lint.StyledTextService.Styling.Bold -abstract class GradleLintReportTask extends DefaultTask implements VerificationTask, Reporting { - - @Nested - private final CodeNarcReportsImpl reports +abstract class GradleLintReportTask extends DefaultTask implements VerificationTask { @Input abstract Property getReportOnlyFixableViolations() - GradleLintReportTask() { - reports = project.objects.newInstance(CodeNarcReportsImpl.class, this) + @Internal + final Property projectName + + @Internal + final DirectoryProperty reportsDir + + @Internal + final NamedDomainObjectContainer reports + + @Inject + GradleLintReportTask(ObjectFactory objects) { + projectName = objects.property(String).convention(project.name) + reportsDir = objects.directoryProperty() + reports = + objects.domainObjectContainer( + LintReport, { name -> + switch (name) { + case "html": + return objects.newInstance(LintHtmlReport, objects, this) + case "xml": + return objects.newInstance(LintXmlReport, objects, this) + case "text": + return objects.newInstance(LintTextReport, objects, this) + default: + throw new InvalidUserDataException(name + " is invalid as the report name") + } + }) + reports.create('text', { + it.required.set(true) + }) + reports.create('xml') + reports.create('html') outputs.upToDateWhen { false } group = 'lint' } @TaskAction void generateReport() { - if (reports.enabled) { + if (reports.any { it.required.isPresent() && it.required.get()}) { def lintService = new LintService() def results = lintService.lint(project, false) filterOnlyFixableViolations(results) @@ -73,24 +96,10 @@ abstract class GradleLintReportTask extends DefaultTask implements VerificationT textOutput.withStyle(Bold).text("$violationCount lint violation${violationCount == 1 ? '' : 's'}") textOutput.println(' in this project') - reports.enabled.each { Report r -> - ReportWriter writer = null - - if (GradleKt.versionCompareTo(project.gradle, '7.1') >= 0) { - switch (r.name) { - case 'xml': writer = new XmlReportWriter(outputFile: r.outputLocation.get().asFile); break - case 'html': writer = new HtmlReportWriter(outputFile: r.outputLocation.get().asFile); break - case 'text': writer = new TextReportWriter(outputFile: r.outputLocation.get().asFile); break - } - } else { - switch (r.name) { - case 'xml': writer = new XmlReportWriter(outputFile: r.destination); break - case 'html': writer = new HtmlReportWriter(outputFile: r.destination); break - case 'text': writer = new TextReportWriter(outputFile: r.destination); break - } + reports.each { + if(it.required.isPresent() && it.required.get()) { + it.write(new AnalysisContext(ruleSet: lintService.ruleSet(project)), results) } - - writer.writeReport(new AnalysisContext(ruleSet: lintService.ruleSet(project)), results) } int errors = results.violations.count { Violation v -> v.rule.priority == 1 } @@ -101,6 +110,7 @@ abstract class GradleLintReportTask extends DefaultTask implements VerificationT } + @Inject Instantiator getInstantiator() { null // see http://gradle.1045684.n5.nabble.com/injecting-dependencies-into-task-instances-td5712637.html @@ -109,20 +119,21 @@ abstract class GradleLintReportTask extends DefaultTask implements VerificationT /** * Returns the reports to be generated by this task. */ - @Override - CodeNarcReports getReports() { + NamedDomainObjectContainer getReports() { reports } /** * Configures the reports to be generated by this task. */ - @Override - CodeNarcReports reports(Closure closure) { + NamedDomainObjectContainer reports(Closure closure) { reports.configure(closure) } - CodeNarcReports reports(Action action) { + /** + * Configures the reports to be generated by this task. + */ + NamedDomainObjectContainer reports(Action action) { return action.execute(reports) } diff --git a/src/main/groovy/com/netflix/nebula/lint/plugin/report/LintReport.groovy b/src/main/groovy/com/netflix/nebula/lint/plugin/report/LintReport.groovy new file mode 100644 index 00000000..7828cb06 --- /dev/null +++ b/src/main/groovy/com/netflix/nebula/lint/plugin/report/LintReport.groovy @@ -0,0 +1,168 @@ +package com.netflix.nebula.lint.plugin.report + +import com.netflix.nebula.lint.plugin.GradleLintReportTask +import org.codehaus.groovy.runtime.GeneratedClosure +import org.codenarc.AnalysisContext +import org.codenarc.report.AbstractReportWriter +import org.codenarc.results.Results +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.reporting.CustomizableHtmlReport +import org.gradle.api.reporting.Report +import org.gradle.api.reporting.SingleFileReport +import org.gradle.api.resources.TextResource +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.internal.metaobject.ConfigureDelegate +import org.gradle.util.internal.ClosureBackedAction + +import javax.annotation.Nullable +import javax.inject.Inject + +abstract class LintReport implements SingleFileReport, + CustomizableHtmlReport { + private final RegularFileProperty destination + private final Property isEnabled + private final Property isRequired + private final GradleLintReportTask task + + @Inject + LintReport(ObjectFactory objects, GradleLintReportTask task) { + this.destination = objects.fileProperty() + this.isEnabled = objects.property(Boolean.class) + this.isRequired = objects.property(Boolean.class).value(Boolean.TRUE) + this.task = task + } + + /** + * @deprecated use {@link #getOutputLocation()} instead. + */ + @Deprecated + @Internal + File getDestination() { + return destination.get().getAsFile() + } + + // @Override // New API from 6.1 see https://github.com/gradle/gradle/issues/11923 + @Override + public RegularFileProperty getOutputLocation() { + return destination + } + + @Override + @Internal("This property returns always same value") + public OutputType getOutputType() { + return OutputType.FILE + } + + // @Override // New API from 6.1 see https://github.com/gradle/gradle/issues/11923 + @Input + Property getRequired() { + return isRequired + } + + /** + * @deprecated use {@link #getRequired()} instead. + */ + @Deprecated + @Internal + boolean isEnabled() { + return isRequired.get() + } + + /** + * @deprecated use {@code getRequired().set(value)} instead. + */ + @Deprecated + void setEnabled(boolean b) { + isRequired.set(b) + } + + /** + * @deprecated use {@code getRequired().set(provider)} instead. + */ + @Deprecated + void setEnabled(Provider provider) { + isRequired.set(provider) + } + + /** + * @deprecated use {@code getOutputLocation().set(provider)} instead. + */ + @Deprecated + void setDestination(File file) { + destination.set(file) + } + + /** + * @deprecated use {@code getOutputLocation().set(provider)} instead. + */ + @Deprecated + void setDestination(Provider provider) { + destination.set(this.task.getProject().getLayout().file(provider)) + } + + @Override + Report configure(Closure closure) { + configureSelf(closure, this) + return this + } + + @Override + @Internal("This property provides only a human readable name.") + String getDisplayName() { + return String.format("%s type report generated by the task %s", getName(), getTask().getPath()) + } + + @Override + TextResource getStylesheet() { + return null + } + + @Override + void setStylesheet(@Nullable TextResource textResource) { + throw new UnsupportedOperationException( + String.format("stylesheet property is not available in the %s type report", getName())) + } + + void setStylesheet(@Nullable String path) { + throw new UnsupportedOperationException( + String.format("stylesheet property is not available in the %s type report", getName())) + } + + @Internal + protected final GradleLintReportTask getTask() { + return task + } + + abstract AbstractReportWriter getWriter() + + void write(AnalysisContext analysisContext, Results results) { + writer.writeReport(analysisContext, results) + } + + /** + * Called from an object's {@link org.gradle.util.Configurable#configure} method. + */ + private static T configureSelf(@Nullable Closure configureClosure, T target) { + if (configureClosure == null) { + return target + } + + configureTarget(configureClosure, target, new ConfigureDelegate(configureClosure, target)) + return target + } + + private static void configureTarget(Closure configureClosure, T target, ConfigureDelegate closureDelegate) { + if (!(configureClosure instanceof GeneratedClosure)) { + new ClosureBackedAction(configureClosure, Closure.DELEGATE_FIRST, false).execute(target) + return + } + + // Hackery to make closure execution faster, by short-circuiting the expensive property and method lookup on Closure + Closure withNewOwner = configureClosure.rehydrate(target, closureDelegate, configureClosure.getThisObject()) + new ClosureBackedAction(withNewOwner, Closure.OWNER_ONLY, false).execute(target) + } +} diff --git a/src/main/groovy/com/netflix/nebula/lint/plugin/report/internal/LintHtmlReport.groovy b/src/main/groovy/com/netflix/nebula/lint/plugin/report/internal/LintHtmlReport.groovy new file mode 100644 index 00000000..1a5daac9 --- /dev/null +++ b/src/main/groovy/com/netflix/nebula/lint/plugin/report/internal/LintHtmlReport.groovy @@ -0,0 +1,27 @@ +package com.netflix.nebula.lint.plugin.report.internal + +import com.netflix.nebula.lint.plugin.GradleLintReportTask +import com.netflix.nebula.lint.plugin.report.LintReport +import org.codenarc.report.AbstractReportWriter +import org.codenarc.report.HtmlReportWriter +import org.gradle.api.file.RegularFile +import org.gradle.api.model.ObjectFactory + +import javax.inject.Inject + +abstract class LintHtmlReport extends LintReport { + @Inject + LintHtmlReport(ObjectFactory objects, GradleLintReportTask task) { + super(objects, task) + } + + @Override + String getName() { + return "html" + } + + @Override + AbstractReportWriter getWriter() { + return new HtmlReportWriter(outputFile: outputLocation.get().asFile) + } +} diff --git a/src/main/groovy/com/netflix/nebula/lint/plugin/report/internal/LintTextReport.groovy b/src/main/groovy/com/netflix/nebula/lint/plugin/report/internal/LintTextReport.groovy new file mode 100644 index 00000000..eefdeb3a --- /dev/null +++ b/src/main/groovy/com/netflix/nebula/lint/plugin/report/internal/LintTextReport.groovy @@ -0,0 +1,27 @@ +package com.netflix.nebula.lint.plugin.report.internal + +import com.netflix.nebula.lint.plugin.GradleLintReportTask +import com.netflix.nebula.lint.plugin.report.LintReport +import org.codenarc.report.AbstractReportWriter +import org.codenarc.report.TextReportWriter +import org.gradle.api.file.RegularFile +import org.gradle.api.model.ObjectFactory + +import javax.inject.Inject + +abstract class LintTextReport extends LintReport { + @Inject + LintTextReport(ObjectFactory objects, GradleLintReportTask task) { + super(objects, task) + } + + @Override + String getName() { + return "text" + } + + @Override + AbstractReportWriter getWriter() { + return new TextReportWriter(outputFile: outputLocation.get().asFile) + } +} \ No newline at end of file diff --git a/src/main/groovy/com/netflix/nebula/lint/plugin/report/internal/LintXmlReport.groovy b/src/main/groovy/com/netflix/nebula/lint/plugin/report/internal/LintXmlReport.groovy new file mode 100644 index 00000000..80f441ef --- /dev/null +++ b/src/main/groovy/com/netflix/nebula/lint/plugin/report/internal/LintXmlReport.groovy @@ -0,0 +1,28 @@ +package com.netflix.nebula.lint.plugin.report.internal + +import com.netflix.nebula.lint.plugin.GradleLintReportTask +import com.netflix.nebula.lint.plugin.report.LintReport +import org.codenarc.report.AbstractReportWriter +import org.codenarc.report.HtmlReportWriter +import org.codenarc.report.XmlReportWriter +import org.gradle.api.file.RegularFile +import org.gradle.api.model.ObjectFactory + +import javax.inject.Inject + +abstract class LintXmlReport extends LintReport { + @Inject + LintXmlReport(ObjectFactory objects, GradleLintReportTask task) { + super(objects, task) + } + + @Override + String getName() { + return "xml" + } + + @Override + AbstractReportWriter getWriter() { + return new XmlReportWriter(outputFile: outputLocation.get().asFile) + } +} \ No newline at end of file diff --git a/src/test/groovy/com/netflix/nebula/lint/plugin/GradleLintReportTaskSpec.groovy b/src/test/groovy/com/netflix/nebula/lint/plugin/GradleLintReportTaskSpec.groovy index 16e4ff16..4d69e05d 100644 --- a/src/test/groovy/com/netflix/nebula/lint/plugin/GradleLintReportTaskSpec.groovy +++ b/src/test/groovy/com/netflix/nebula/lint/plugin/GradleLintReportTaskSpec.groovy @@ -1,12 +1,14 @@ package com.netflix.nebula.lint.plugin import com.netflix.nebula.lint.BaseIntegrationTestKitSpec -import nebula.test.IntegrationTestKitSpec import spock.lang.Issue -import spock.lang.Unroll class GradleLintReportTaskSpec extends BaseIntegrationTestKitSpec { + def setup() { + disableConfigurationCache() + } + def 'generate a report'() { when: buildFile.text = """ diff --git a/src/test/groovy/com/netflix/nebula/lint/rule/dependency/DependencyClassVisitorSpec.groovy b/src/test/groovy/com/netflix/nebula/lint/rule/dependency/DependencyClassVisitorSpec.groovy index f79eeac7..14ca0bab 100644 --- a/src/test/groovy/com/netflix/nebula/lint/rule/dependency/DependencyClassVisitorSpec.groovy +++ b/src/test/groovy/com/netflix/nebula/lint/rule/dependency/DependencyClassVisitorSpec.groovy @@ -18,6 +18,7 @@ package com.netflix.nebula.lint.rule.dependency import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier import org.gradle.api.internal.artifacts.DefaultResolvedDependency +import org.gradle.util.GradleVersion import spock.lang.Specification import spock.lang.Unroll @@ -255,6 +256,11 @@ class DependencyClassVisitorSpec extends Specification { static DefaultResolvedDependency gav(String g, String a, String v) { def mvid = new DefaultModuleVersionIdentifier(g, a, v) - new DefaultResolvedDependency("compile", mvid, null) + + if(GradleVersion.current().compareTo(GradleVersion.version('8.11-milestone-1')) >= 0) { + new DefaultResolvedDependency("compile", mvid, null, null) + } else { + new DefaultResolvedDependency("compile", mvid, null) + } } }