All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.vyarus.gradle.plugin.animalsniffer.AnimalSniffer.groovy Maven / Gradle / Ivy

There is a newer version: 1.7.2
Show newest version
package ru.vyarus.gradle.plugin.animalsniffer

import groovy.transform.CompileStatic
import groovy.transform.TypeCheckingMode
import org.apache.tools.ant.BuildListener
import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileTree
import org.gradle.api.internal.CollectionCallbackActionDecorator
import org.gradle.api.internal.project.IsolatedAntBuilder
import org.gradle.api.reporting.Report
import org.gradle.api.reporting.Reporting
import org.gradle.api.tasks.*
import org.gradle.internal.reflect.Instantiator
import org.gradle.util.ClosureBackedAction
import org.gradle.util.GradleVersion
import ru.vyarus.gradle.plugin.animalsniffer.report.AnimalSnifferReports
import ru.vyarus.gradle.plugin.animalsniffer.report.AnimalSnifferReportsImpl
import ru.vyarus.gradle.plugin.animalsniffer.report.ReportCollector

import javax.inject.Inject
import java.lang.reflect.Proxy

/**
 * AnimalSniffer task is created for each registered source set.
 * 

* Task is SourceTask, but with root in compile classes dir of current source set. This means that include/exclude * may be used, but they wil be applied to compiled classes and not sources. * * @author Vyacheslav Rusakov * @since 14.12.2015 */ @SuppressWarnings(['UnnecessaryGetter', 'Println']) @CompileStatic @CacheableTask class AnimalSniffer extends SourceTask implements VerificationTask, Reporting { private static final String NL = '\n' /** * The class path containing the Animal Sniffer library to be used. */ @Classpath @InputFiles FileCollection animalsnifferClasspath /** * Signature files used for checks. */ @SkipWhenEmpty @Classpath @InputFiles FileCollection animalsnifferSignatures /** * Classpath used for compilation (if not included in signature) */ @CompileClasspath @InputFiles @Optional FileCollection classpath /** * Source directories */ @InputFiles @PathSensitive(PathSensitivity.RELATIVE) Set sourcesDirs /** * Annotation class name to avoid check */ @Input @Optional String annotation /** * Extra allowed classes, not mentioned in signature, but allowed for usage. * Most commonly, when some classes target newer jdk version. *

* See * docs for more info. */ @Input @Optional Iterable ignoreClasses = [] /** * Whether or not the build should break when the verifications performed by this task fail. */ @Console boolean ignoreFailures /** * Configuration debug output */ @Console boolean debug private final AnimalSnifferReportsImpl reports /** * Due to many classloaders used by AntBuilder, have to avoid ant classes in custom listener. * Use jdk proxy to register custom listener. * * @param project ant project * @param handler proxy handler */ @CompileStatic(TypeCheckingMode.SKIP) static void replaceBuildListener(Object project, ReportCollector handler) { // use original to redirect other ant tasks output (not expected, but just in case) handler.originalListener = project.buildListeners.first() // cleanup default gradle listener listener to avoid console output project.buildListeners.each { project.removeBuildListener(it) } ClassLoader cl = project.class.classLoader Object listener = Proxy.newProxyInstance(cl, [cl.loadClass(BuildListener.name)] as Class[], handler) project.addBuildListener(listener) } /** * Recover original listener after execution. * * @param project ant project * @param original original listener */ @CompileStatic(TypeCheckingMode.SKIP) static void recoverOriginalListener(Object project, Object original) { if (original != null) { project.buildListeners.each { project.removeBuildListener(it) } project.addBuildListener(original) } } @SuppressWarnings('ThisReferenceEscapesConstructor') AnimalSniffer() { reports = instantiator.newInstance(AnimalSnifferReportsImpl, this, getCallbackActionDecorator()) } @Inject Instantiator getInstantiator() { throw new UnsupportedOperationException() } @Inject CollectionCallbackActionDecorator getCallbackActionDecorator() { throw new UnsupportedOperationException() } @Inject IsolatedAntBuilder getAntBuilder() { throw new UnsupportedOperationException() } @TaskAction @SuppressWarnings(['CatchException', 'VariableName']) @CompileStatic(TypeCheckingMode.SKIP) void run() { String sortedPath = preparePath(getSource()) if (getDebug()) { printTaskConfig(sortedPath) } Set _sourceDirs = collectSourceDirs() antBuilder.withClasspath(getAnimalsnifferClasspath()).execute { ant.taskdef(name: 'animalsniffer', classname: 'org.codehaus.mojo.animal_sniffer.ant.CheckSignatureTask') ReportCollector collector = new ReportCollector(_sourceDirs) replaceBuildListener(project, collector) getAnimalsnifferSignatures().each { signature -> try { collector.contextSignature(signature.name) ant.animalsniffer(signature: signature.absolutePath, classpath: getClasspath()?.asPath) { // the same as getSource().asPath, but have to apply sorting because otherwise // enclosing class could be parsed after inlined and so ignoring annotation on enclosing class // would be ignored (actually, this problem appears only on windows) path(path: sortedPath) _sourceDirs.each { sourcepath(path: it.absoluteFile) } annotation(className: 'org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement') if (getAnnotation()) { annotation(className: getAnnotation()) } getIgnoreClasses().each { ignore(className: it) } } } catch (Exception ex) { // rethrow not expected ant exceptions if (!ex.message.startsWith('Signature errors found')) { throw ex } } } collector.printSignatureNames = getAnimalsnifferSignatures().size() > 1 processErrors(collector) // it should be useless as completely custom ant used for execution, but just in case recoverOriginalListener(project, collector.originalListener) } } /** * Returns the reports to be generated by this task. */ @Nested @Override AnimalSnifferReports getReports() { return reports } @Override @SuppressWarnings('ConfusingMethodName') AnimalSnifferReports reports( @DelegatesTo(value = AnimalSnifferReports, strategy = Closure.DELEGATE_FIRST) Closure closure) { reports(new ClosureBackedAction(closure)) } @Override @SuppressWarnings('ConfusingMethodName') AnimalSnifferReports reports(Action action) { action.execute(reports) return reports } @Override @PathSensitive(PathSensitivity.RELATIVE) FileTree getSource() { return super.getSource() } void processErrors(ReportCollector collector) { if (collector.errorsCnt() > 0) { String message = "${collector.errorsCnt()} AnimalSniffer violations were found " + "in ${collector.filesCnt()} files." Report report = reports.firstEnabled if (report) { File target = GradleVersion.current() < GradleVersion.version('7.0') ? report.destination : report.outputLocation.get().asFile collector.writeToFile(target) String reportUrl = "file:///${target.canonicalPath.replaceAll('\\\\', '/')}" message += " See the report at: $reportUrl" } if (getIgnoreFailures()) { String nl = String.format('%n') logger.error(nl + message + nl) collector.writeToConsole(logger) } else { logger.error('') collector.writeToConsole(logger) throw new GradleException(message) } } } void printTaskConfig(String path) { String rootDir = "${project.rootDir.canonicalPath}${File.separator}" StringBuilder res = new StringBuilder() .append('\n\tsignatures:\n') .append(getAnimalsnifferSignatures().files.collect { "\t\t$it.name" }.join(NL)) .append(NL) .append('\n\tsources:\n') .append(collectSourceDirs().sort().collect { "\t\t${project.relativePath(it)}" }.join(NL)) .append(NL) .append('\n\tfiles:\n') .append(path.split(File.pathSeparator).collect { "\t\t${it.replace(rootDir, '')}" }.join(NL)) .append(NL) if (!getIgnoreClasses().empty) { res.append('\n\tignored:\n') .append(getIgnoreClasses().collect { "\t\t$it" }.join(NL)) .append(NL) } println res.toString() } @SuppressWarnings(['Indentation', 'UnnecessaryCollectCall', 'UnnecessarySubstring']) private static String preparePath(FileTree source) { int clsExtSize = '.class'.length() String innerIndicator = '$' List sortedPath = source .collect { it.toString() } .toSorted { a, b -> if (a.contains(innerIndicator) || b.contains(innerIndicator)) { String a1 = a.substring(0, a.length() - clsExtSize) // - .class String b1 = b.substring(0, b.length() - clsExtSize) // trick is to compare names without extension, so inner class would // become longer and go last automatically; // compare: Some.class < Some$1.class, but Some > Some$1 return a1 <=> b1 } return a <=> b } // lambda case (Some$$Lambda$1). Ant removes every odd $ in a row return sortedPath.join(File.pathSeparator).replace('$$', '$$$') } @CompileStatic(TypeCheckingMode.SKIP) private Set collectSourceDirs() { Set res = [] as Set res.addAll(getSourcesDirs()) // HACK to support kotlin multiplatform source path for jvm case (when withJava() active) // this MUST BE rewritten into separate support for multiplatform if (project.plugins.findPlugin('org.jetbrains.kotlin.multiplatform')) { project.kotlin.sourceSets.each { println it.kotlin.sourceDirectories.files res.addAll(it.kotlin.sourceDirectories.files) } } return res } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy