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

ru.vyarus.gradle.plugin.animalsniffer.signature.BuildSignatureTask.groovy Maven / Gradle / Ivy

package ru.vyarus.gradle.plugin.animalsniffer.signature

import groovy.transform.CompileStatic
import groovy.transform.TypeCheckingMode
import org.gradle.api.GradleException
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.ConventionTask
import org.gradle.api.internal.file.collections.SimpleFileCollection
import org.gradle.api.internal.project.IsolatedAntBuilder
import org.gradle.api.tasks.*
import org.gradle.internal.reflect.Instantiator
import org.gradle.util.GFileUtils
import ru.vyarus.gradle.plugin.animalsniffer.AnimalSnifferPlugin

import javax.inject.Inject

/**
 * Task for building animalsniffer signature. Compiled classes, jars and other signatures may be used as source.
 * 

* Task may be used directly or through registered `animalsnifferSignature' configuration closure. *

* AnimalsnifferClasspath will be set from 'animalsniffer' configuration. By default, output file * is `build/animalsniffer/${task.name}/${task.name}.sig`. In some cases multiple signatures could be produces, * so use {@code task.outputFiles} method to get correct task output. *

* Properties may be used for configuration directly, but it will be more convenient to use provided helper * methods instead. *

* Animalsniffer ant task does not allow empty files, but, in some cases, it could happen. * For example, when two or more signatures must be merged. In this case plugin will * specify fake file together with it's exclusion from resulted signature to overcome ant task limitation. * * @author Vyacheslav Rusakov * @since 12.04.2017 * @see AnimalSnifferSignatureExtension for configuration extension * @see * ant task docks */ @CompileStatic @CacheableTask @SuppressWarnings('ConfusingMethodName') class BuildSignatureTask extends ConventionTask { /** * Symbol used when {@link #mergeSignatures} disabled and so multiple signatures produced (according to input * signatures count). This is used for signature caching for check task so it could print real signature name * in output without cache signature name. */ public static final String SIGNATURE_DELIMITER = '_!' private static final String DOT = '.' /** * The class path containing the Animal Sniffer library to be used. * Default set by plugin. */ @Classpath @InputFiles FileCollection animalsnifferClasspath /** * Signature files to extend. */ @InputFiles @Optional FileCollection signatures /** * Compiled classes and jars. * Ant task requires files, but plugin will overcome this with fake files * ({@see # allowEmptyFiles) */ @InputFiles @Optional FileCollection files = new SimpleFileCollection() /** * Include only packages ('com.java.*') */ @Input @Optional Set include = [] /** * Exclude packages ('com.sun.*') */ @Input @Optional Set exclude = [] /** * Output signatures directory. */ @OutputDirectory File outputDirectory /** * Output signature name. If extension is not set then '.sig' extension will be used. By default, equal to * task name (set by plugin). *

* Note: when {@link #mergeSignatures} is false, provided name is used as base to create multiple names * (by adding _!signatureName postfix). */ @Input String outputName /** * Animalsniffer ant task does not allow empty files, but, in some cases, it could happen. * For example, when two or more signatures must be merged. In this case plugin could * specify fake file together with it's exclusion from resulted signature to overcome ant task limitation. *

* Option exists only to be able to switch off empty files workaround. It may be required due to * security restrictions which could lead to source jar resolution failure (not the case for most environments). */ @Input boolean allowEmptyFiles = true /** * When multiple signatures provided they will be merged. If merge disabled, multiple signatures will be provided * for each incoming signature. *

* This is very special case used internally for classpath caching when multiple signatures used for check task. */ @Input boolean mergeSignatures = true @SuppressWarnings('UnnecessaryGetter') BuildSignatureTask() { // task-specific output directory used to avoid clashes when multiple build tasks used // (output files are taken from directory, so if multiple tasks used, last task output will include // all tasks outputs, which is wrong). conventionMapping.map('outputDirectory') { getDefaultTaskOutputDirectory() } } @Inject Instantiator getInstantiator() { throw new UnsupportedOperationException() } @Inject IsolatedAntBuilder getAntBuilder() { throw new UnsupportedOperationException() } @TaskAction void run() { if (handleSimpleCase()) { return } applyFakeFilesIfRequired() if (!getInclude().empty || !getExclude().empty) { logger.info("Building signature with includes=${getInclude()} and excludes=${getExclude()}") } Set signs = getSignatures() ? getSignatures().files : [] as Set if (getMergeSignatures() || signs.size() <= 1) { runSignatureBuild(signs, targetFile) } else { // use each provided signature as base (provide multiple signatures) signs.each { runSignatureBuild([it], getPerSignatureTargetFile(it)) } } } /** * Files to use for signature generation. Includes classes (compiled) or jars. * Internally {@link org.gradle.api.Project#files(java.lang.Object ...)} is used so any definition may be used, * supported by gradle method. *

* Method may be called multiple times - all inputs will aggregate. *

* Common cases: *

    *
  • sourceSets.main.output - all compiled classes of source set
  • *
  • configurations.mycustom - all jars, defined in 'mycustom' configuration (of course, standard * configurations like 'compile' or 'test' could also be used)
  • *
      * * @param input input files (classes ot jars) */ void files(Object input) { if (getFiles() == null) { setFiles(project.files(input)) } else { setFiles(getFiles() + project.files(input)) } } /** * Signatures to extend from (use all definitions from signature). May be used only together with files definition * (for example, you can't just merge multiple signatures; this is ant task limitation). *

      * Method may be called multiple times - all inputs will aggregate. *

      * Plugin register special 'signature' configuration to use for signatures definition and it makes sense to use * it for signatures definition for build task. In this case, use {@code configurations.signature} to add * all defined signatures. *

      * Internally {@link org.gradle.api.Project#files(java.lang.Object ...)} is used so any definition may be used, * supported by gradle method. * * @param signature base signatures */ void signatures(Object signature) { if (getSignatures() == null) { setSignatures(project.files(signature)) } else { setSignatures(getSignatures() + project.files(signature)) } } /** * Use to include only defined classes in resulted signature. * For example: specify 'com.java.*' to include only 'com.java' package and all it's sub packages. *

      * Method may be called multiple times - all inputs will aggregate. * * @param path class names or package mask */ void include(String... path) { Set include = getInclude() include.addAll(path) setInclude(include) } /** * Use to exclude classes in resulted signature. * For example: specify 'com.sun.*' to exclude 'com.sun' package and all it's sub packages. *

      * Method may be called multiple times - all inputs will aggregate. * * @param path class names or package mask */ void exclude(String... path) { Set exclude = getExclude() exclude.addAll(path) setExclude(exclude) } /** * It is intentional that property is not annotated with {@code @OutputFiles}! *

      * Task output depends on inputs (number of produced signatures and their names rely on * configured signatures). Since gradle 4 task output property can't force dependency resolution, * but in most cases input signatures are configured with 'signature' configuration. This makes impossible * computation of exact output file names before execution (such computation was implemented in plugin version 1.4). *

      * Due to this limitation, task declares only output directory as task output. But that means that * {@code task.outputs.files} can't be used, because it will contain only output directory (itself). * But it's a very handy to rely on task output files, and this method should be used instead of * outputs mechanism ({@code task.outputFiles}) to configure produced signatures in other tasks. * * @return lazy collection of resulted signatures to use as input for other tasks */ FileCollection getOutputFiles() { return project.fileTree(getOutputDirectory()) { builtBy this } } /** * Note that in case of check task, this will never be true because exclusions are always applied for cache * task (see {@link ru.vyarus.gradle.plugin.animalsniffer.CheckCacheExtension#exclude}). * * @return true when no processing required for configured signatures */ private boolean handleSimpleCase() { if (getFiles().empty && (getSignatures().size() == 1 || !getMergeSignatures()) && getExclude().empty && getInclude().empty) { getSignatures().each { GFileUtils.copyFile(it, getMergeSignatures() ? targetFile : getPerSignatureTargetFile(it)) } return true } return false } private void applyFakeFilesIfRequired() { boolean signaturesConfigured = getSignatures() != null && !getSignatures().empty // situation: files specified, but collection is empty (for example, empty configuration) if (getFiles().empty && signaturesConfigured && isAllowEmptyFiles()) { logger.info('Workaround empty files for signature build by adding plugin jar as file ' + 'with package exclusion') // if this will fail due to security restrictions, simply disable allowEmptyFiles config files(BuildSignatureTask.protectionDomain.codeSource.location) exclude('ru.vyarus.gradle.plugin.animalsniffer.*') } else if (getFiles().empty) { throw new GradleException("No files found in: ${getFiles()}") } } /** * By default task name used for output directory. * For cache tasks, 'animalsniffer' prefix cut off. * * @return default output directory */ private File getDefaultTaskOutputDirectory() { String subDir = name if (subDir.startsWith(AnimalSnifferPlugin.ANIMALSNIFFER_CACHE)) { subDir = "cache${subDir[AnimalSnifferPlugin.ANIMALSNIFFER_CACHE.length()..-1]}" } return new File(project.buildDir, "animalsniffer/$subDir") } private String getSignatureFileName() { String out = getOutputName() if (outputName.indexOf(DOT) < 0) { out += '.sig' } return out } /** * @return output signature file for merge mode ({@link #mergeSignatures} enabled) */ private File getTargetFile() { return new File(getOutputDirectory(), signatureFileName) } /** * When {@link #mergeSignatures} is disabled, task may produce multiple signatures * for each source signature. Output file name is build by convention: * configured signature name + special delimiter (to be able to recognize it) + * source signature name. * * @param sig signature file * @return output signature file name for specific source signature */ private File getPerSignatureTargetFile(File sig) { // use each provided signature as base (provide multiple signatures) String out = signatureFileName String baseName = out[0..out.lastIndexOf(DOT) - 1] String ext = out[out.lastIndexOf(DOT) + 1..-1] String name = sig.name[0..sig.name.lastIndexOf(DOT) - 1] return new File(getOutputDirectory(), "${baseName}${SIGNATURE_DELIMITER}$name.$ext") } @CompileStatic(TypeCheckingMode.SKIP) private void runSignatureBuild(Collection signatures, File dest) { antBuilder.withClasspath(getAnimalsnifferClasspath()).execute { a -> ant.taskdef(name: 'buildSignature', classname: 'org.codehaus.mojo.animal_sniffer.ant.BuildSignaturesTask') ant.buildSignature(destfile: dest.absolutePath) { if (getFiles() && !getFiles().empty) { path(path: getFiles().asPath) } signatures.each { signature(src: it.absolutePath) } getInclude().each { includeClasses(className: it) } getExclude().each { excludeClasses(className: it) } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy