diff --git a/README.md b/README.md
index 630e4ff..fe491e2 100644
--- a/README.md
+++ b/README.md
@@ -99,9 +99,7 @@ Example projects (with intentional errors to see output):
#### Compatibility
-**IMPORTANT**: Plugin only works when `java-base` plugin (activated by any java-related plugin like `java-library`, `groovy`, `scala`, `org.jetbrains.kotlin.jvm`, etc.) is enabled,
-otherwise nothing will be registered.
-There is **no support for the Android plugin** (`java-base` plugin must be used to perform animalsniffer check).
+Support for the Android plugin requires Android plugin version `7.4.0` or greater.
For *kotlin multiplatform* plugin enable java support:
diff --git a/examples/android-app/README.md b/examples/android-app/README.md
new file mode 100644
index 0000000..4658ed2
--- /dev/null
+++ b/examples/android-app/README.md
@@ -0,0 +1,21 @@
+# Android library project with animalsniffer check
+
+Output:
+
+```
+> Task :android-app:animalsnifferDebug
+
+4 AnimalSniffer violations were found in 2 files. See the report at: file:////{PATH_FROM_ROOT}/gradle-animalsniffer-plugin/examples/android-app/build/reports/animalsniffer/debug.text
+
+[Undefined reference | java18-1.0] invalid.(Sample.java:9)
+ >> String String.repeat(int)
+
+[Undefined reference | android-api-level-21-5.0.1_r2] invalid.(Sample.java:9)
+ >> String String.repeat(int)
+
+[Undefined reference | android-api-level-21-5.0.1_r2] invalid.(Sample.java:15)
+ >> java.nio.file.Path java.io.File.toPath()
+
+[Undefined reference | android-api-level-21-5.0.1_r2] invalid.(SampleKotlin.kt:9)
+ >> java.nio.file.Path java.io.File.toPath()
+```
\ No newline at end of file
diff --git a/examples/android-app/build.gradle b/examples/android-app/build.gradle
new file mode 100644
index 0000000..7acf440
--- /dev/null
+++ b/examples/android-app/build.gradle
@@ -0,0 +1,34 @@
+plugins {
+ id 'com.android.application' version '7.4.0'
+ id 'org.jetbrains.kotlin.android' version '1.9.23'
+ id 'ru.vyarus.animalsniffer'
+}
+
+android {
+ compileSdk 33
+ namespace 'com.example.namespace'
+ def javaVersion = JavaVersion.VERSION_1_8
+
+ compileOptions {
+ sourceCompatibility(javaVersion)
+ targetCompatibility(javaVersion)
+ }
+
+ kotlinOptions {
+ jvmTarget = javaVersion.toString()
+ }
+}
+
+animalsniffer {
+// debug = true
+ // only show errors
+ ignoreFailures = true
+}
+
+repositories { mavenCentral(); google() }
+dependencies {
+ signature 'org.codehaus.mojo.signature:java18:1.0@signature'
+ signature 'net.sf.androidscents.signature:android-api-level-21:5.0.1_r2@signature'
+
+ implementation 'org.slf4j:slf4j-api:1.7.25'
+}
\ No newline at end of file
diff --git a/examples/android-app/src/main/AndroidManifest.xml b/examples/android-app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5c3d365
--- /dev/null
+++ b/examples/android-app/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/examples/android-app/src/main/java/invalid/Sample.java b/examples/android-app/src/main/java/invalid/Sample.java
new file mode 100644
index 0000000..53b2068
--- /dev/null
+++ b/examples/android-app/src/main/java/invalid/Sample.java
@@ -0,0 +1,17 @@
+package invalid;
+
+import java.io.File;
+
+public class Sample {
+
+ public static void main(String[] args) {
+ // method added in 11
+ "".repeat(5);
+ }
+
+ public void someth() {
+ // not available in android
+ File file = new File("");
+ file.toPath();
+ }
+}
\ No newline at end of file
diff --git a/examples/android-app/src/main/java/invalid/SampleKotlin.kt b/examples/android-app/src/main/java/invalid/SampleKotlin.kt
new file mode 100644
index 0000000..e593537
--- /dev/null
+++ b/examples/android-app/src/main/java/invalid/SampleKotlin.kt
@@ -0,0 +1,11 @@
+package invalid
+
+import java.io.File
+
+class SampleKotlin {
+ fun someth() {
+ // not available in android
+ val file = File("")
+ file.toPath()
+ }
+}
\ No newline at end of file
diff --git a/examples/android-lib/README.md b/examples/android-lib/README.md
new file mode 100644
index 0000000..a7b479e
--- /dev/null
+++ b/examples/android-lib/README.md
@@ -0,0 +1,21 @@
+# Android application project with animalsniffer check
+
+Output:
+
+```
+> Task :android-lib:animalsnifferDebug
+
+4 AnimalSniffer violations were found in 2 files. See the report at: file:////{PATH_FROM_ROOT}/gradle-animalsniffer-plugin/examples/android-lib/build/reports/animalsniffer/debug.text
+
+[Undefined reference | java18-1.0] invalid.(Sample.java:9)
+ >> String String.repeat(int)
+
+[Undefined reference | android-api-level-21-5.0.1_r2] invalid.(Sample.java:9)
+ >> String String.repeat(int)
+
+[Undefined reference | android-api-level-21-5.0.1_r2] invalid.(Sample.java:15)
+ >> java.nio.file.Path java.io.File.toPath()
+
+[Undefined reference | android-api-level-21-5.0.1_r2] invalid.(SampleKotlin.kt:9)
+ >> java.nio.file.Path java.io.File.toPath()
+```
\ No newline at end of file
diff --git a/examples/android-lib/build.gradle b/examples/android-lib/build.gradle
new file mode 100644
index 0000000..292987c
--- /dev/null
+++ b/examples/android-lib/build.gradle
@@ -0,0 +1,34 @@
+plugins {
+ id 'com.android.library' version '7.4.0'
+ id 'org.jetbrains.kotlin.android' version '1.9.23'
+ id 'ru.vyarus.animalsniffer'
+}
+
+android {
+ compileSdk 33
+ namespace 'com.example.namespace'
+ def javaVersion = JavaVersion.VERSION_1_8
+
+ compileOptions {
+ sourceCompatibility(javaVersion)
+ targetCompatibility(javaVersion)
+ }
+
+ kotlinOptions {
+ jvmTarget = javaVersion.toString()
+ }
+}
+
+animalsniffer {
+// debug = true
+ // only show errors
+ ignoreFailures = true
+}
+
+repositories { mavenCentral() }
+dependencies {
+ signature 'org.codehaus.mojo.signature:java18:1.0@signature'
+ signature 'net.sf.androidscents.signature:android-api-level-21:5.0.1_r2@signature'
+
+ implementation 'org.slf4j:slf4j-api:1.7.25'
+}
\ No newline at end of file
diff --git a/examples/android-lib/src/main/AndroidManifest.xml b/examples/android-lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5c3d365
--- /dev/null
+++ b/examples/android-lib/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/examples/android-lib/src/main/java/invalid/Sample.java b/examples/android-lib/src/main/java/invalid/Sample.java
new file mode 100644
index 0000000..53b2068
--- /dev/null
+++ b/examples/android-lib/src/main/java/invalid/Sample.java
@@ -0,0 +1,17 @@
+package invalid;
+
+import java.io.File;
+
+public class Sample {
+
+ public static void main(String[] args) {
+ // method added in 11
+ "".repeat(5);
+ }
+
+ public void someth() {
+ // not available in android
+ File file = new File("");
+ file.toPath();
+ }
+}
\ No newline at end of file
diff --git a/examples/android-lib/src/main/java/invalid/SampleKotlin.kt b/examples/android-lib/src/main/java/invalid/SampleKotlin.kt
new file mode 100644
index 0000000..e593537
--- /dev/null
+++ b/examples/android-lib/src/main/java/invalid/SampleKotlin.kt
@@ -0,0 +1,11 @@
+package invalid
+
+import java.io.File
+
+class SampleKotlin {
+ fun someth() {
+ // not available in android
+ val file = File("")
+ file.toPath()
+ }
+}
\ No newline at end of file
diff --git a/examples/settings.gradle b/examples/settings.gradle
index 1eb29c2..652334d 100644
--- a/examples/settings.gradle
+++ b/examples/settings.gradle
@@ -9,6 +9,7 @@ pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
+ google()
maven { url 'https://jitpack.io' }
}
}
@@ -16,6 +17,7 @@ pluginManagement {
enableFeaturePreview "STABLE_CONFIGURATION_CACHE"
include 'java', 'groovy', 'kotlin', 'scala',
+ 'android-lib', 'android-app',
'buildSignature:fromClasses',
'buildSignature:fromJars',
'buildSignature:fromSignatures',
diff --git a/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/AnimalSniffer.groovy b/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/AnimalSniffer.groovy
index 7f32b5f..649f5ce 100644
--- a/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/AnimalSniffer.groovy
+++ b/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/AnimalSniffer.groovy
@@ -66,7 +66,7 @@ class AnimalSniffer extends SourceTask implements VerificationTask, Reporting sourcesDirs
+ FileCollection sourcesDirs
/**
* Annotation class name to avoid check
@@ -298,7 +298,7 @@ class AnimalSniffer extends SourceTask implements VerificationTask, Reporting collectSourceDirs() {
Set res = [] as Set
- res.addAll(getSourcesDirs())
+ res.addAll(getSourcesDirs().getFiles())
// 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')) {
diff --git a/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/AnimalSnifferPlugin.groovy b/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/AnimalSnifferPlugin.groovy
index c376d26..6ccfa4b 100644
--- a/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/AnimalSnifferPlugin.groovy
+++ b/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/AnimalSnifferPlugin.groovy
@@ -3,16 +3,19 @@ package ru.vyarus.gradle.plugin.animalsniffer
import groovy.transform.CompileStatic
import groovy.transform.Memoized
import groovy.transform.TypeCheckingMode
+import kotlin.jvm.functions.Function1
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
+import org.gradle.api.file.Directory
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.plugins.ReportingBasePlugin
+import org.gradle.api.provider.ListProperty
import org.gradle.api.reporting.ReportingExtension
import org.gradle.api.specs.NotSpec
import org.gradle.api.tasks.SourceSet
@@ -22,6 +25,7 @@ import org.gradle.util.GradleVersion
import ru.vyarus.gradle.plugin.animalsniffer.info.SignatureInfoTask
import ru.vyarus.gradle.plugin.animalsniffer.signature.AnimalSnifferSignatureExtension
import ru.vyarus.gradle.plugin.animalsniffer.signature.BuildSignatureTask
+import ru.vyarus.gradle.plugin.animalsniffer.util.AndroidClassesCollector
import ru.vyarus.gradle.plugin.animalsniffer.util.ContainFilesSpec
import ru.vyarus.gradle.plugin.animalsniffer.util.ExcludeFilePatternSpec
@@ -56,17 +60,22 @@ class AnimalSnifferPlugin implements Plugin {
@Override
void apply(Project project) {
- // activated only when java plugin is enabled
- project.plugins.withType(JavaBasePlugin) {
- this.project = project
- project.plugins.apply(ReportingBasePlugin)
+ this.project = project
+ project.plugins.apply(ReportingBasePlugin)
- checkGradleCompatibility()
- registerShortcuts()
- registerConfigurations()
- registerExtensions()
- registerCheckTasks()
- registerBuildTasks()
+ checkGradleCompatibility()
+ registerShortcuts()
+ registerConfigurations()
+ registerExtensions()
+ registerBuildTasks()
+ project.plugins.withType(JavaBasePlugin) {
+ registerJavaCheckTasks()
+ }
+ project.plugins.withId("com.android.library") {
+ registerAndroidCheckTasks()
+ }
+ project.plugins.withId("com.android.application") {
+ registerAndroidCheckTasks()
}
}
@@ -119,7 +128,7 @@ class AnimalSnifferPlugin implements Plugin {
@SuppressWarnings(['Indentation', 'NestedBlockDepth'])
@CompileStatic(TypeCheckingMode.SKIP)
- private void registerCheckTasks() {
+ private void registerJavaCheckTasks() {
// create tasks for each source set
project.sourceSets.all { SourceSet sourceSet ->
String sourceSetName = sourceSet.name
@@ -137,7 +146,11 @@ class AnimalSnifferPlugin implements Plugin {
})
}
}
- configureCheckTask(checkTask, sourceSet)
+ configureCheckTask(checkTask,
+ project.files(sourceSet.allJava.srcDirs),
+ sourceSet.getTaskName(ANIMALSNIFFER_CACHE, null),
+ sourceSet.classesTaskName,
+ sourceSet.compileClasspath)
}
// include required animalsniffer tasks in check lifecycle
@@ -148,16 +161,70 @@ class AnimalSnifferPlugin implements Plugin {
}
}
+ @SuppressWarnings('GroovyAssignabilityCheck')
+ @CompileStatic(TypeCheckingMode.SKIP)
+ void registerAndroidCheckTasks() {
+ def androidComponents = project.androidComponents
+ androidComponents.onVariants(androidComponents.selector().all(), { variant ->
+ String sourceSetName = variant.name
+ String capitalizedSourceSetName = sourceSetName.capitalize()
+ String classesCollectorTaskName = sourceSetName + "AnimalSnifferClassesCollector"
+ TaskProvider classesCollector = createAndroidClassesCollector(classesCollectorTaskName, variant)
+ TaskProvider checkTask = project.tasks
+ . register(CHECK_SIGNATURE + capitalizedSourceSetName,
+ AnimalSniffer) {
+ description = "Run AnimalSniffer checks for ${sourceSetName} classes"
+ // task operates on classes instead of sources
+ source = classesCollector.flatMap { it.outputDirectory }
+ reports.all { report ->
+ report.required.convention(true)
+ report.outputLocation.convention(project.provider {
+ { ->
+ new File(extension.reportsDir, "${sourceSetName}.${report.name}")
+ } as RegularFile
+ })
+ }
+ }
+
+ configureCheckTask(checkTask,
+ project.files(variant.sources.java.all, variant.sources.kotlin.all),
+ ANIMALSNIFFER_CACHE + capitalizedSourceSetName,
+ classesCollectorTaskName,
+ variant.compileClasspath)
+
+ })
+
+ }
+
+ @CompileStatic(TypeCheckingMode.SKIP)
+ private TaskProvider createAndroidClassesCollector(String taskName, Object variant) {
+ TaskProvider collectClasses = project.tasks.register(taskName, AndroidClassesCollector)
+ def scopedArtifactsScopeType = Class.forName("com.android.build.api.variant.ScopedArtifacts\$Scope")
+ def scopedArtifactTypeClasses = Class.forName("com.android.build.api.artifact.ScopedArtifact\$CLASSES")
+ variant.artifacts.forScope(scopedArtifactsScopeType.PROJECT).use(collectClasses)
+ .toGet(scopedArtifactTypeClasses.INSTANCE, new Function1>() {
+ @Override
+ ListProperty invoke(AndroidClassesCollector task) {
+ return task.jarFiles
+ }
+ }, new Function1>() {
+ @Override
+ ListProperty invoke(AndroidClassesCollector task) {
+ return task.classesDirs
+ }
+ })
+ return collectClasses
+ }
+
@SuppressWarnings(['Indentation', 'MethodSize', 'UnnecessaryGetter'])
@CompileStatic(TypeCheckingMode.SKIP)
- private void configureCheckTask(TaskProvider checkTask, SourceSet sourceSet) {
+ private void configureCheckTask(TaskProvider checkTask, FileCollection srcDirs, String signatureTaskName, String classesTaskName, FileCollection compileClasspath) {
Configuration animalsnifferConfiguration = project.configurations[CHECK_SIGNATURE]
// build special signature from provided signatures and all jars to be able to cache it
// and perform much faster checks after the first run
TaskProvider signatureTask = project.tasks
- . register(sourceSet.getTaskName(ANIMALSNIFFER_CACHE, null),
- BuildSignatureTask) {
+ . register(signatureTaskName, BuildSignatureTask) {
// this special task can be skipped if animalsniffer check supposed to be skipped
// note that task is still created because signatures could be registered dynamically
onlyIf { !extension.signatures.empty && extension.cache.enabled }
@@ -165,7 +232,7 @@ class AnimalSnifferPlugin implements Plugin {
conventionMapping.with {
animalsnifferClasspath = { animalsnifferConfiguration }
signatures = { extension.signatures }
- files = { excludeJars(getClasspathWithoutModules(sourceSet)) }
+ files = { excludeJars(getClasspathWithoutModules(compileClasspath)) }
exclude = { extension.cache.exclude as Set }
mergeSignatures = { extension.cache.mergeSignatures }
// debug for cache tasks controlled by check debug
@@ -173,19 +240,19 @@ class AnimalSnifferPlugin implements Plugin {
}
}
checkTask.configure {
- dependsOn(sourceSet.classesTaskName)
+ dependsOn(classesTaskName)
// skip if no signatures configured or no sources to check
onlyIf { !getAnimalsnifferSignatures().empty && getSource().size() > 0 }
conventionMapping.with {
classpath = {
extension.cache.enabled ?
- getModulesFromClasspath(sourceSet) : excludeJars(sourceSet.compileClasspath)
+ getModulesFromClasspath(compileClasspath) : excludeJars(compileClasspath)
}
animalsnifferSignatures = {
extension.cache.enabled ? signatureTask.get().outputFiles : extension.signatures
}
animalsnifferClasspath = { animalsnifferConfiguration }
- sourcesDirs = { sourceSet.allJava.srcDirs }
+ sourcesDirs = srcDirs
ignoreFailures = { extension.ignoreFailures }
annotation = { extension.annotation }
ignoreClasses = { extension.ignore }
@@ -246,12 +313,12 @@ class AnimalSnifferPlugin implements Plugin {
*/
@Memoized
@CompileStatic(TypeCheckingMode.SKIP)
- private FileCollection getClasspathWithoutModules(SourceSet sourceSet) {
+ private FileCollection getClasspathWithoutModules(FileCollection compileClasspath) {
Set excludeJars = moduleJars
if (excludeJars.empty) {
- return sourceSet.compileClasspath
+ return compileClasspath
}
- sourceSet.compileClasspath.filter new NotSpec(new ContainFilesSpec(excludeJars))
+ compileClasspath.filter new NotSpec(new ContainFilesSpec(excludeJars))
}
/**
@@ -263,12 +330,12 @@ class AnimalSnifferPlugin implements Plugin {
*/
@Memoized
@CompileStatic(TypeCheckingMode.SKIP)
- private FileCollection getModulesFromClasspath(SourceSet sourceSet) {
+ private FileCollection getModulesFromClasspath(FileCollection compileClasspath) {
Set includeJars = moduleJars
if (includeJars.empty) {
return null
}
- sourceSet.compileClasspath.filter new ContainFilesSpec(includeJars)
+ compileClasspath.filter new ContainFilesSpec(includeJars)
}
/**
diff --git a/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/util/AndroidClassesCollector.groovy b/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/util/AndroidClassesCollector.groovy
new file mode 100644
index 0000000..9ce23e6
--- /dev/null
+++ b/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/util/AndroidClassesCollector.groovy
@@ -0,0 +1,35 @@
+package ru.vyarus.gradle.plugin.animalsniffer.util
+
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.Directory
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFile
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+
+abstract class AndroidClassesCollector extends DefaultTask {
+
+ @InputFiles
+ abstract ListProperty getJarFiles()
+
+ @InputFiles
+ abstract ListProperty getClassesDirs()
+
+ @OutputDirectory
+ abstract DirectoryProperty getOutputDirectory()
+
+ AndroidClassesCollector() {
+ getOutputDirectory().value(project.layout.buildDirectory.dir("intermediates/animal_sniffer/" + name))
+ }
+
+ @TaskAction
+ void execute() {
+ project.sync {
+ from(classesDirs)
+ into(outputDirectory)
+ }
+ }
+}