blob: e258fb0ddb2e035c866f922243bd3bc87086e3dd [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.build.gradle.internal.tasks
import com.google.common.reflect.ClassPath
import com.google.common.reflect.TypeToken
import com.google.common.truth.Truth
import org.gradle.api.Task
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.CompileClasspath
import org.gradle.api.tasks.Console
import org.gradle.api.tasks.Destroys
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.LocalState
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectories
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.OutputFiles
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.SkipWhenEmpty
import org.junit.Test
import java.lang.reflect.AnnotatedElement
import java.lang.reflect.Modifier
class TaskMethodModifiersAndAnnotationsTest {
@Test
fun `check for non-public methods with gradle input or output annotations`() {
val classPath = ClassPath.from(this.javaClass.classLoader)
val nonPublicMethods =
classPath
.getTopLevelClassesRecursive("com.android.build")
.map { classInfo -> classInfo.load() as Class<*> }
.flatMap { it.declaredMethods.asIterable() }
.filter {
it.hasGradleInputOrOutputAnnotation() && !Modifier.isPublic(it.modifiers)
}
if (nonPublicMethods.isEmpty()) {
return
}
// Otherwise generate a descriptive error message.
val error =
StringBuilder().append("The following gradle-annotated methods are not public:\n")
for (nonPublicMethod in nonPublicMethods) {
error.append(nonPublicMethod.declaringClass.toString().substringAfter(" "))
.append("::${nonPublicMethod.name}\n")
}
throw AssertionError(error.toString())
}
@Test
fun `check for fields with gradle input or output annotations`() {
val classPath = ClassPath.from(this.javaClass.classLoader)
val annotatedFields =
classPath
.getTopLevelClassesRecursive("com.android.build")
.map { classInfo -> classInfo.load() as Class<*> }
.flatMap { it.declaredFields.asIterable() }
.filter { it.hasGradleInputOrOutputAnnotation() }
if (annotatedFields.isEmpty()) {
return
}
// Otherwise generate a descriptive error message.
val error =
StringBuilder().append(
"The following fields are annotated with gradle input/output annotations, which "
+ "should only be used on methods (e.g., the corresponding getters):\n")
for (annotatedField in annotatedFields) {
error.append(annotatedField.declaringClass.toString().substringAfter(" "))
.append(".${annotatedField.name}\n")
}
throw AssertionError(error.toString())
}
@Test
fun `check for public task setters`() {
val whiteListedSetters =
listOf(
"com.android.build.gradle.internal.coverage.JacocoReportTask::setCoverageFile",
"com.android.build.gradle.internal.coverage.JacocoReportTask::setJacocoClasspath",
"com.android.build.gradle.internal.coverage.JacocoReportTask::setReportDir",
"com.android.build.gradle.internal.coverage.JacocoReportTask::setReportName",
"com.android.build.gradle.internal.coverage.JacocoReportTask::setTabWidth",
"com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask::setAaptMainDexListProguardOutputFile",
"com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask::setAaptOptions",
"com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask::setDebuggable",
"com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask::setMergeBlameLogFolder",
"com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask::setProguardOutputFile",
"com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask::setSourceOutputDir",
"com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask::setType",
"com.android.build.gradle.internal.res.namespaced.CompileRClassTask::setVariantName",
"com.android.build.gradle.internal.tasks.AndroidBuilderTask::setAndroidBuilder",
"com.android.build.gradle.internal.tasks.AndroidReportTask::setIgnoreFailures",
"com.android.build.gradle.internal.tasks.AndroidReportTask::setReportType",
"com.android.build.gradle.internal.tasks.AndroidReportTask::setReportsDir",
"com.android.build.gradle.internal.tasks.AndroidReportTask::setResultsDir",
"com.android.build.gradle.internal.tasks.AndroidReportTask::setWillRun",
"com.android.build.gradle.internal.tasks.AndroidVariantTask::setVariantName",
"com.android.build.gradle.internal.tasks.CheckManifest::setManifest",
"com.android.build.gradle.internal.tasks.CheckManifest::setOptional",
"com.android.build.gradle.internal.tasks.DependencyReportTask::setVariants",
"com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask::setCoverageDir",
"com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask::setDeviceProvider",
"com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask::setFlavorName",
"com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask::setIgnoreFailures",
"com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask::setInstallOptions",
"com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask::setProcessExecutor",
"com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask::setReportsDir",
"com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask::setResultsDir",
"com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask::setTestData",
"com.android.build.gradle.internal.tasks.GenerateApkDataTask::setMinSdkVersion",
"com.android.build.gradle.internal.tasks.GenerateApkDataTask::setResOutputDir",
"com.android.build.gradle.internal.tasks.GenerateApkDataTask::setTargetSdkVersion",
"com.android.build.gradle.internal.tasks.IncrementalTask::setIncrementalFolder",
"com.android.build.gradle.internal.tasks.InstallVariantTask::setApkDirectory",
"com.android.build.gradle.internal.tasks.InstallVariantTask::setInstallOptions",
"com.android.build.gradle.internal.tasks.InstallVariantTask::setProcessExecutor",
"com.android.build.gradle.internal.tasks.InstallVariantTask::setProjectName",
"com.android.build.gradle.internal.tasks.InstallVariantTask::setTimeOutInMs",
"com.android.build.gradle.internal.tasks.InstallVariantTask::setVariantData",
"com.android.build.gradle.internal.tasks.LintCompile::setOutputDirectory",
"com.android.build.gradle.internal.tasks.LintStandaloneTask::setAutoFix",
"com.android.build.gradle.internal.tasks.LintStandaloneTask::setFatalOnly",
"com.android.build.gradle.internal.tasks.LintStandaloneTask::setLintChecks",
"com.android.build.gradle.internal.tasks.LintStandaloneTask::setLintOptions",
"com.android.build.gradle.internal.tasks.LintStandaloneTask::setReportDir",
"com.android.build.gradle.internal.tasks.MergeFileTask::setInputFiles",
"com.android.build.gradle.internal.tasks.MergeFileTask::setOutputFile",
"com.android.build.gradle.internal.tasks.NdkTask::setNdkConfig",
"com.android.build.gradle.internal.tasks.PackageRenderscriptTask::setVariantName",
"com.android.build.gradle.internal.tasks.ProcessJavaResTask::setVariantName",
"com.android.build.gradle.internal.tasks.SigningReportTask::setVariants",
"com.android.build.gradle.internal.tasks.SourceSetsTask::setConfig",
"com.android.build.gradle.internal.tasks.TestServerTask::setTestApks",
"com.android.build.gradle.internal.tasks.TestServerTask::setTestServer",
"com.android.build.gradle.internal.tasks.TestServerTask::setTestedApks",
"com.android.build.gradle.internal.tasks.UninstallTask::setTimeOutInMs",
"com.android.build.gradle.internal.tasks.UninstallTask::setVariant",
"com.android.build.gradle.tasks.factory.AndroidUnitTest::setVariantName",
"com.android.build.gradle.tasks.AidlCompile::setPackageWhitelist",
"com.android.build.gradle.tasks.AidlCompile::setPackagedDir",
"com.android.build.gradle.tasks.AidlCompile::setSourceOutputDir",
"com.android.build.gradle.tasks.AndroidJavaCompile::setVariantName",
"com.android.build.gradle.tasks.BundleAar::setVariantName",
"com.android.build.gradle.tasks.CleanBuildCache::setBuildCache",
"com.android.build.gradle.tasks.ExtractAnnotations::setBootClasspath",
"com.android.build.gradle.tasks.ExtractAnnotations::setClassDir",
"com.android.build.gradle.tasks.ExtractAnnotations::setEncoding",
"com.android.build.gradle.tasks.ExtractAnnotations::setOutput",
"com.android.build.gradle.tasks.GenerateBuildConfig::setBuildTypeName",
"com.android.build.gradle.tasks.GenerateBuildConfig::setSourceOutputDir",
"com.android.build.gradle.tasks.GenerateResValues::setItems",
"com.android.build.gradle.tasks.GenerateResValues::setResOutputDir",
"com.android.build.gradle.tasks.InvokeManifestMerger::setMainManifestFile",
"com.android.build.gradle.tasks.InvokeManifestMerger::setOutputFile",
"com.android.build.gradle.tasks.InvokeManifestMerger::setSecondaryManifestFiles",
"com.android.build.gradle.tasks.ManifestProcessorTask::setReportFile",
"com.android.build.gradle.tasks.LintPerVariantTask::setVariantName",
"com.android.build.gradle.tasks.MergeResources::setBlameLogFolder",
"com.android.build.gradle.tasks.MergeResources::setOutputDir",
"com.android.build.gradle.tasks.MergeResources::setPublicFile",
"com.android.build.gradle.tasks.MergeSourceSetFolders::setLibraries",
"com.android.build.gradle.tasks.PackageAndroidArtifact::setAbiFilters",
"com.android.build.gradle.tasks.PackageAndroidArtifact::setDebugBuild",
"com.android.build.gradle.tasks.PackageAndroidArtifact::setJniDebugBuild",
"com.android.build.gradle.tasks.PackageAndroidArtifact::setSigningConfig",
"com.android.build.gradle.tasks.ProcessAnnotationsTask::setVariantName",
"com.android.build.gradle.tasks.ProcessApplicationManifest::setVariantConfiguration",
"com.android.build.gradle.tasks.ProcessLibraryManifest::setVariantConfiguration",
"com.android.build.gradle.tasks.ProcessTestManifest::setTestManifestFile",
"com.android.build.gradle.tasks.ProcessTestManifest::setTmpDir",
"com.android.build.gradle.tasks.RenderscriptCompile::setDebugBuild",
"com.android.build.gradle.tasks.RenderscriptCompile::setImportDirs",
"com.android.build.gradle.tasks.RenderscriptCompile::setNdkMode",
"com.android.build.gradle.tasks.RenderscriptCompile::setObjOutputDir",
"com.android.build.gradle.tasks.RenderscriptCompile::setOptimLevel",
"com.android.build.gradle.tasks.RenderscriptCompile::setResOutputDir",
"com.android.build.gradle.tasks.RenderscriptCompile::setSourceOutputDir",
"com.android.build.gradle.tasks.RenderscriptCompile::setSupportMode",
"com.android.build.gradle.tasks.ShaderCompile::setDefaultArgs",
"com.android.build.gradle.tasks.ShaderCompile::setOutputDir",
"com.android.build.gradle.tasks.ShaderCompile::setScopedArgs",
"com.android.build.gradle.tasks.ir.FastDeployRuntimeExtractorTask::setOutputFile"
)
val classPath = ClassPath.from(this.javaClass.classLoader)
val taskInterface = TypeToken.of(Task::class.java)
val publicSetters =
classPath
.getTopLevelClassesRecursive("com.android.build")
.map { classInfo -> classInfo.load() as Class<*> }
.filter { clazz -> TypeToken.of(clazz).types.contains(taskInterface) }
.flatMap { it.declaredMethods.asIterable() }
.filter {
it.name.startsWith("set")
&& !it.name.contains('$')
&& Modifier.isPublic(it.modifiers)
}
.map { "${it.declaringClass.toString().substringAfter(" ")}::${it.name}" }
Truth.assertThat(publicSetters)
.named("Task public setters")
.containsExactlyElementsIn(whiteListedSetters)
}
private fun AnnotatedElement.hasGradleInputOrOutputAnnotation(): Boolean {
// look for all org.gradle.api.tasks annotations, except @CacheableTask, @Internal, and
// @TaskAction.
return getAnnotation(Classpath::class.java) != null
|| getAnnotation(CompileClasspath::class.java) != null
|| getAnnotation(Console::class.java) != null
|| getAnnotation(Destroys::class.java) != null
|| getAnnotation(Input::class.java) != null
|| getAnnotation(InputDirectory::class.java) != null
|| getAnnotation(InputFile::class.java) != null
|| getAnnotation(InputFiles::class.java) != null
|| getAnnotation(LocalState::class.java) != null
|| getAnnotation(Nested::class.java) != null
|| getAnnotation(Optional::class.java) != null
|| getAnnotation(OutputDirectories::class.java) != null
|| getAnnotation(OutputDirectory::class.java) != null
|| getAnnotation(OutputFile::class.java) != null
|| getAnnotation(OutputFiles::class.java) != null
|| getAnnotation(PathSensitive::class.java) != null
|| getAnnotation(SkipWhenEmpty::class.java) != null
}
}