blob: ef1aa89713eb7c07bc84c4616c60776f91b7c624 [file] [log] [blame]
/*
* Copyright (C) 2020 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.
*/
@file:JvmName("AndroidLintInputs")
package com.android.build.gradle.internal.lint
import com.android.SdkConstants
import com.android.Version
import com.android.build.api.artifact.ScopedArtifact
import com.android.build.api.artifact.SingleArtifact
import com.android.build.api.dsl.Lint
import com.android.build.api.variant.InternalSources
import com.android.build.api.variant.ResValue
import com.android.build.api.variant.ScopedArtifacts
import com.android.build.api.variant.impl.FlatSourceDirectoriesImpl
import com.android.build.api.variant.impl.LayeredSourceDirectoriesImpl
import com.android.build.gradle.internal.component.DeviceTestCreationConfig
import com.android.build.gradle.internal.component.ApkCreationConfig
import com.android.build.gradle.internal.component.ComponentCreationConfig
import com.android.build.gradle.internal.component.ConsumableCreationConfig
import com.android.build.gradle.internal.component.KmpComponentCreationConfig
import com.android.build.gradle.internal.component.LibraryCreationConfig
import com.android.build.gradle.internal.component.TestFixturesCreationConfig
import com.android.build.gradle.internal.component.HostTestCreationConfig
import com.android.build.gradle.internal.component.VariantCreationConfig
import com.android.build.gradle.internal.dependency.VariantDependencies
import com.android.build.gradle.internal.dsl.LintImpl
import com.android.build.gradle.internal.ide.Utils.getGeneratedResourceFoldersFileCollection
import com.android.build.gradle.internal.ide.Utils.getGeneratedSourceFoldersFileCollection
import com.android.build.gradle.internal.ide.dependencies.ArtifactCollectionsInputs
import com.android.build.gradle.internal.ide.dependencies.ArtifactCollectionsInputsImpl
import com.android.build.gradle.internal.ide.dependencies.ArtifactHandler
import com.android.build.gradle.internal.ide.dependencies.LibraryDependencyCacheBuildService
import com.android.build.gradle.internal.ide.dependencies.MavenCoordinatesCacheBuildService
import com.android.build.gradle.internal.ide.dependencies.getDependencyGraphBuilder
import com.android.build.gradle.internal.publishing.AndroidArtifacts
import com.android.build.gradle.internal.scope.InternalArtifactType
import com.android.build.gradle.internal.scope.ProjectInfo
import com.android.build.gradle.internal.services.LintClassLoaderBuildService
import com.android.build.gradle.internal.services.LintParallelBuildService
import com.android.build.gradle.internal.services.TaskCreationServices
import com.android.build.gradle.internal.services.getBuildService
import com.android.build.gradle.internal.utils.createTargetSdkVersion
import com.android.build.gradle.internal.utils.fromDisallowChanges
import com.android.build.gradle.internal.utils.getDesugaredMethods
import com.android.build.gradle.internal.utils.setDisallowChanges
import com.android.build.gradle.options.BooleanOption
import com.android.build.gradle.options.ProjectOptions
import com.android.build.gradle.options.StringOption
import com.android.builder.core.ComponentType
import com.android.builder.core.ComponentTypeImpl
import com.android.builder.errors.EvalIssueException
import com.android.builder.errors.IssueReporter
import com.android.ide.common.repository.AgpVersion
import com.android.sdklib.AndroidVersion
import com.android.tools.lint.model.DefaultLintModelAndroidArtifact
import com.android.tools.lint.model.DefaultLintModelBuildFeatures
import com.android.tools.lint.model.DefaultLintModelJavaArtifact
import com.android.tools.lint.model.DefaultLintModelLibraryResolver
import com.android.tools.lint.model.DefaultLintModelLintOptions
import com.android.tools.lint.model.DefaultLintModelMavenName
import com.android.tools.lint.model.DefaultLintModelModule
import com.android.tools.lint.model.DefaultLintModelResourceField
import com.android.tools.lint.model.DefaultLintModelSourceProvider
import com.android.tools.lint.model.DefaultLintModelVariant
import com.android.tools.lint.model.LintModelAndroidArtifact
import com.android.tools.lint.model.LintModelArtifactType
import com.android.tools.lint.model.LintModelArtifactType.MAIN
import com.android.tools.lint.model.LintModelArtifactType.UNIT_TEST
import com.android.tools.lint.model.LintModelBuildFeatures
import com.android.tools.lint.model.LintModelDependencies
import com.android.tools.lint.model.LintModelJavaArtifact
import com.android.tools.lint.model.LintModelLibrary
import com.android.tools.lint.model.LintModelLintOptions
import com.android.tools.lint.model.LintModelModule
import com.android.tools.lint.model.LintModelModuleType
import com.android.tools.lint.model.LintModelNamespacingMode
import com.android.tools.lint.model.LintModelSeverity
import com.android.tools.lint.model.LintModelSourceProvider
import com.android.tools.lint.model.LintModelVariant
import com.android.utils.FileUtils
import com.android.utils.PathUtils
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.artifacts.ArtifactCollection
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.jvm.tasks.Jar
import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import java.io.File
import java.nio.file.Files
import java.util.concurrent.Callable
abstract class LintTool {
/** Lint itself */
@get:Classpath
abstract val classpath: ConfigurableFileCollection
/**
* The identity of lint used as keys for caches
*
* Used both for the [lintCacheDirectory] and for the classloader cache in [AndroidLintWorkAction]
*
* For published versions it will include the version of lint from maven e.g. `30.2.0-alpha05`
* and for -dev versions, also a hash of the jars: `30.2.0-dev_920ff9cabfbb40d0318735f9fe403b9/`
*/
@get:Input
abstract val versionKey: Property<String>
@get:Input
abstract val runInProcess: Property<Boolean>
@get:Input
@get:Optional
abstract val workerHeapSize: Property<String>
/**
* The lint cache parent dir for artifacts recomputable by lint that save analysis time
*/
@get:Internal
abstract val lintCacheDirectory: DirectoryProperty
/**
* Computes the lint cache dir, cleaning up if lint version has changed
*
* This is passed to lint invocations using --cache-dir
*
* The lint cache is neither an input nor an output to the lint tasks, so it needs some manual
* handling to avoid lint trying to load cache items written by a different version of lint.
*
* A marker file of lint-cache-version is used, for published versions it will include the
* version of lint, e.g. `30.2.0-alpha05`
*
* And for -dev versions, also a hash of the jars, the same as the classloader hash
* 30.2.0-dev_920ff9cabfbb40d0318735f9fe403b9
*
* Returns the arguments to add to the lint invocation.
*/
fun initializeLintCacheDir(): List<String> {
val directory = lintCacheDirectory.get().asFile.toPath()
val lintVersionMarkerFile = directory.resolve("lint-cache-version.txt")
val currentVersion = "Cache for Android Lint" + versionKey.get()
val previousVersion = lintVersionMarkerFile.takeIf { Files.exists(it) }?.let { Files.readAllLines(it).singleOrNull() }
if (previousVersion != currentVersion) {
PathUtils.deleteRecursivelyIfExists(directory)
Files.createDirectories(directory)
Files.write(lintVersionMarkerFile, listOf(currentVersion))
}
return listOf("--cache-dir", directory.toString())
}
@get:Internal
abstract val lintClassLoaderBuildService: Property<LintClassLoaderBuildService>
fun initialize(taskCreationServices: TaskCreationServices, taskName: String) {
classpath.fromDisallowChanges(taskCreationServices.lintFromMaven.files)
lintClassLoaderBuildService.setDisallowChanges(getBuildService(taskCreationServices.buildServiceRegistry))
versionKey.setDisallowChanges(deriveVersionKey(taskCreationServices, lintClassLoaderBuildService))
val projectOptions = taskCreationServices.projectOptions
runInProcess.setDisallowChanges(projectOptions.getProvider(BooleanOption.RUN_LINT_IN_PROCESS))
workerHeapSize.setDisallowChanges(projectOptions.getProvider(StringOption.LINT_HEAP_SIZE))
lintCacheDirectory.setDisallowChanges(
taskCreationServices.projectInfo
.buildDirectory
.dir("${SdkConstants.FD_INTERMEDIATES}/lint-cache/$taskName")
)
}
private fun deriveVersionKey(
taskCreationServices: TaskCreationServices,
lintClassLoaderBuildService: Provider<LintClassLoaderBuildService>
): Provider<String> {
val lintVersion =
getLintMavenArtifactVersion(
taskCreationServices.projectOptions[StringOption.LINT_VERSION_OVERRIDE]?.trim(),
null
)
val versionProvider = taskCreationServices.provider { lintVersion }
// When using development versions also hash the jar contents to avoid reusing
// the classloader when the jars might change
return when {
lintVersion.endsWith("-dev") || lintVersion.endsWith("SNAPSHOT") -> {
val jarsHash = lintClassLoaderBuildService.zip(classpath.elements, LintClassLoaderBuildService::hashJars)
versionProvider.zip(jarsHash) { version, hash -> "${version}_$hash" }
}
else -> versionProvider
}
}
fun submit(
workerExecutor: WorkerExecutor,
mainClass: String,
arguments: List<String>,
lintMode: LintMode
) {
submit(
workerExecutor,
mainClass,
arguments,
android = true,
fatalOnly = false,
await = false,
hasBaseline = false,
lintMode = lintMode
)
}
fun submit(
workerExecutor: WorkerExecutor,
mainClass: String,
arguments: List<String>,
android: Boolean,
fatalOnly: Boolean,
await: Boolean,
lintMode: LintMode,
hasBaseline: Boolean,
returnValueOutputFile: File? = null
) {
val workQueue = if (runInProcess.get()) {
workerExecutor.noIsolation()
} else {
workerExecutor.processIsolation {
it.classpath.from(classpath)
// Default to using the main Gradle daemon heap size to smooth the transition
// for build authors.
it.forkOptions.maxHeapSize =
LintParallelBuildService.calculateLintHeapSize(
workerHeapSize.orNull,
Runtime.getRuntime().maxMemory()
)
}
}
workQueue.submit(AndroidLintWorkAction::class.java) { parameters ->
parameters.mainClass.set(mainClass)
parameters.arguments.set(arguments)
parameters.classpath.from(classpath)
parameters.versionKey.set(versionKey)
parameters.android.set(android)
parameters.fatalOnly.set(fatalOnly)
parameters.runInProcess.set(runInProcess.get())
parameters.returnValueOutputFile.set(returnValueOutputFile)
parameters.lintMode.set(lintMode)
parameters.hasBaseline.set(hasBaseline)
}
if (await) {
workQueue.await()
}
}
}
abstract class ProjectInputs {
@get:Internal
abstract val projectDirectoryPath: Property<String>
// projectDirectoryPathInput should either be (1) unset if the project directory is not an input
// to the associated task, or (2) set to the same value as projectDirectoryPath otherwise.
@get:Input
@get:Optional
abstract val projectDirectoryPathInput: Property<String>
@get:Input
abstract val projectGradlePath: Property<String>
@get:Input
abstract val projectType: Property<LintModelModuleType>
@get:Input
abstract val mavenGroupId: Property<String>
@get:Input
abstract val mavenArtifactId: Property<String>
@get:Input
abstract val mavenVersion: Property<String>
@get:Internal
abstract val buildDirectoryPath: Property<String>
// buildDirectoryPathInput should either be (1) unset if the build directory is not an input to
// the associated task, or (2) set to the same value as buildDirectoryPath otherwise.
@get:Input
@get:Optional
abstract val buildDirectoryPathInput: Property<String>
@get:Nested
abstract val lintOptions: LintOptionsInput
@get:Input
@get:Optional
abstract val resourcePrefix: Property<String>
@get:Input
abstract val dynamicFeatures: ListProperty<String>
@get:Classpath
abstract val bootClasspath: ConfigurableFileCollection
@get:Input
abstract val javaSourceLevel: Property<JavaVersion>
@get:Input
abstract val compileTarget: Property<String>
/**
* True if none of the build types used by this module have enabled shrinking,
* or false if at least one variant's build type is known to use shrinking.
*/
@get:Input
abstract val neverShrinking: Property<Boolean>
/**
* [lintConfigFiles] contains all possible lint.xml files in the module's directory or any
* parent directories up to the root project directory.
*/
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
abstract val lintConfigFiles: ConfigurableFileCollection
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val buildFile: RegularFileProperty
internal fun initialize(variant: VariantWithTests, lintMode: LintMode) {
initialize(variant.main, lintMode)
}
internal fun initialize(creationConfig: ComponentCreationConfig, lintMode: LintMode) {
val globalConfig = creationConfig.global
initializeFromProject(creationConfig.services.projectInfo, lintMode)
projectType.setDisallowChanges(creationConfig.componentType.toLintModelModuleType())
lintOptions.initialize(globalConfig.lintOptions, lintMode)
resourcePrefix.setDisallowChanges(globalConfig.resourcePrefix)
dynamicFeatures.setDisallowChanges(globalConfig.dynamicFeatures)
bootClasspath.fromDisallowChanges(globalConfig.bootClasspath)
javaSourceLevel.setDisallowChanges(globalConfig.compileOptions.sourceCompatibility)
compileTarget.setDisallowChanges(globalConfig.compileSdkHashString)
// `neverShrinking` is about all variants, so look back to the DSL
neverShrinking.setDisallowChanges(globalConfig.hasNoBuildTypeMinified)
}
internal fun initializeForStandalone(
project: Project,
javaExtension: JavaPluginExtension,
dslLintOptions: Lint,
lintMode: LintMode
) {
initializeFromProject(ProjectInfo(project), lintMode)
projectType.setDisallowChanges(LintModelModuleType.JAVA_LIBRARY)
lintOptions.initialize(dslLintOptions, lintMode)
resourcePrefix.setDisallowChanges("")
dynamicFeatures.setDisallowChanges(setOf())
val javaCompileTask =
javaExtension.sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME)?.let {
project.tasks.named(it.compileJavaTaskName, JavaCompile::class.java)
}
if (javaCompileTask != null) {
bootClasspath.from(
javaCompileTask.map { it.options.bootstrapClasspath ?: project.files() }
)
}
bootClasspath.disallowChanges()
javaSourceLevel.setDisallowChanges(javaExtension.sourceCompatibility)
compileTarget.setDisallowChanges("")
neverShrinking.setDisallowChanges(true)
}
private fun initializeFromProject(projectInfo: ProjectInfo, lintMode: LintMode) {
projectDirectoryPath.setDisallowChanges(projectInfo.projectDirectory.toString())
projectGradlePath.setDisallowChanges(projectInfo.path)
mavenGroupId.setDisallowChanges(projectInfo.group)
mavenArtifactId.setDisallowChanges(projectInfo.name)
mavenVersion.setDisallowChanges(projectInfo.version)
buildDirectoryPath.setDisallowChanges(projectInfo.buildDirectory.map { it.asFile.absolutePath })
if (lintMode != LintMode.ANALYSIS) {
projectDirectoryPathInput.set(projectDirectoryPath)
buildDirectoryPathInput.set(buildDirectoryPath)
}
projectDirectoryPathInput.disallowChanges()
buildDirectoryPathInput.disallowChanges()
initializeLintConfigFiles(projectInfo)
buildFile.set(projectInfo.buildFile)
buildFile.disallowChanges()
}
internal fun convertToLintModelModule(): LintModelModule {
return DefaultLintModelModule(
loader = null,
dir = File(projectDirectoryPath.get()),
modulePath = projectGradlePath.get(),
type = projectType.get(),
mavenName = DefaultLintModelMavenName(
mavenGroupId.get(),
mavenArtifactId.get(),
mavenVersion.get()
),
agpVersion = AgpVersion.tryParse(Version.ANDROID_GRADLE_PLUGIN_VERSION),
buildFolder = File(buildDirectoryPath.get()),
lintOptions = lintOptions.toLintModel(),
lintRuleJars = listOf(),
resourcePrefix = resourcePrefix.orNull,
dynamicFeatures = dynamicFeatures.get(),
bootClassPath = bootClasspath.files.toList(),
javaSourceLevel = javaSourceLevel.get().toString(),
compileTarget = compileTarget.get(),
variants = listOf(),
neverShrinking = neverShrinking.get()
)
}
/**
* Initialize [lintConfigFiles] with all possible lint.xml files in the module's directory or
* any parent directories up to the root project directory.
*/
private fun initializeLintConfigFiles(projectInfo: ProjectInfo) {
var currentDir = projectInfo.projectDirectory.asFile
var currentLintXml = File(currentDir, LINT_XML_CONFIG_FILE_NAME)
while (FileUtils.isFileInDirectory(currentLintXml, projectInfo.rootDir)) {
lintConfigFiles.from(currentLintXml)
currentDir = currentDir.parentFile ?: break
currentLintXml = File(currentDir, LINT_XML_CONFIG_FILE_NAME)
}
lintConfigFiles.disallowChanges()
}
}
internal fun ComponentType.toLintModelModuleType(): LintModelModuleType {
return when (this) {
// FIXME add other types
ComponentTypeImpl.BASE_APK -> LintModelModuleType.APP
ComponentTypeImpl.LIBRARY -> LintModelModuleType.LIBRARY
ComponentTypeImpl.OPTIONAL_APK -> LintModelModuleType.DYNAMIC_FEATURE
ComponentTypeImpl.TEST_APK -> LintModelModuleType.TEST
ComponentTypeImpl.KMP_ANDROID -> LintModelModuleType.LIBRARY
else -> throw RuntimeException("Unsupported ComponentTypeImpl value")
}
}
abstract class LintOptionsInput {
@get:Input
abstract val disable: SetProperty<String>
@get:Input
abstract val enable: SetProperty<String>
@get:Input
abstract val checkOnly: SetProperty<String>
@get:Input
abstract val abortOnError: Property<Boolean>
@get:Input
abstract val absolutePaths: Property<Boolean>
@get:Input
abstract val noLines: Property<Boolean>
@get:Input
abstract val quiet: Property<Boolean>
@get:Input
abstract val checkAllWarnings: Property<Boolean>
@get:Input
abstract val ignoreWarnings: Property<Boolean>
@get:Input
abstract val warningsAsErrors: Property<Boolean>
@get:Input
abstract val checkTestSources: Property<Boolean>
@get:Input
abstract val checkGeneratedSources: Property<Boolean>
@get:Input
abstract val explainIssues: Property<Boolean>
@get:Input
abstract val showAll: Property<Boolean>
@get:Input
abstract val checkDependencies: Property<Boolean>
@get:Optional
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
abstract val lintConfig: RegularFileProperty
@get:Optional
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
abstract val baseline: RegularFileProperty
@get:Input
abstract val severityOverrides: MapProperty<String, LintModelSeverity>
@get:Input
abstract val ignoreTestSources: Property<Boolean>
@get:Input
abstract val ignoreTestFixturesSources: Property<Boolean>
fun initialize(lintOptions: Lint, lintMode: LintMode) {
disable.setDisallowChanges(lintOptions.disable)
enable.setDisallowChanges(lintOptions.enable)
checkOnly.setDisallowChanges(lintOptions.checkOnly)
abortOnError.setDisallowChanges(lintOptions.abortOnError)
absolutePaths.setDisallowChanges(lintOptions.absolutePaths)
noLines.setDisallowChanges(lintOptions.noLines)
quiet.setDisallowChanges(lintOptions.quiet)
checkAllWarnings.setDisallowChanges(lintOptions.checkAllWarnings)
ignoreWarnings.setDisallowChanges(lintOptions.ignoreWarnings)
warningsAsErrors.setDisallowChanges(lintOptions.warningsAsErrors)
checkTestSources.setDisallowChanges(lintOptions.checkTestSources)
checkGeneratedSources.setDisallowChanges(lintOptions.checkGeneratedSources)
explainIssues.setDisallowChanges(lintOptions.explainIssues)
showAll.setDisallowChanges(lintOptions.showAll)
checkDependencies.setDisallowChanges(lintOptions.checkDependencies)
lintOptions.lintConfig?.let { lintConfig.set(it) }
lintConfig.disallowChanges()
// The baseline file does not affect analysis, but otherwise it is an input.
if (lintMode != LintMode.ANALYSIS) {
lintOptions.baseline?.let { baseline.set(it) }
}
baseline.disallowChanges()
severityOverrides.setDisallowChanges((lintOptions as LintImpl).severityOverridesMap)
ignoreTestSources.setDisallowChanges(lintOptions.ignoreTestSources)
ignoreTestFixturesSources.setDisallowChanges(lintOptions.ignoreTestFixturesSources)
}
fun toLintModel(): LintModelLintOptions {
return DefaultLintModelLintOptions(
disable=disable.get(),
enable=enable.get(),
check=checkOnly.get(),
abortOnError=abortOnError.get(),
absolutePaths=absolutePaths.get(),
noLines=noLines.get(),
quiet=quiet.get(),
checkAllWarnings=checkAllWarnings.get(),
ignoreWarnings=ignoreWarnings.get(),
warningsAsErrors=warningsAsErrors.get(),
checkTestSources=checkTestSources.get(),
ignoreTestSources=ignoreTestSources.get(),
ignoreTestFixturesSources=ignoreTestFixturesSources.get(),
checkGeneratedSources=checkGeneratedSources.get(),
explainIssues=explainIssues.get(),
showAll=showAll.get(),
lintConfig=lintConfig.orNull?.asFile,
// Report setup is handled in the invocation
textReport=false,
textOutput=null,
htmlReport=false,
htmlOutput=null,
xmlReport=false,
xmlOutput=null,
sarifReport=false,
sarifOutput=null,
checkReleaseBuilds=true, // Handled in LintTaskManager & LintPlugin
checkDependencies=checkDependencies.get(),
baselineFile = baseline.orNull?.asFile,
severityOverrides=severityOverrides.get(),
)
}
}
/**
* System properties which can affect lint's behavior.
*/
abstract class SystemPropertyInputs {
@get:Input
@get:Optional
abstract val androidLintLogJarProblems: Property<String>
// Use @get:Internal because javaVersion acts as proxy input for javaHome
@get:Internal
abstract val javaHome: Property<String>
@get:Input
@get:Optional
abstract val javaVersion: Property<String>
@get:InputFile
@get:PathSensitive(PathSensitivity.NAME_ONLY)
@get:Optional
abstract val lintApiDatabase: RegularFileProperty
@get:Input
@get:Optional
abstract val lintAutofix: Property<String>
@get:Input
@get:Optional
abstract val lintBaselinesContinue: Property<String>
@get:InputFile
@get:PathSensitive(PathSensitivity.NAME_ONLY)
@get:Optional
abstract val lintConfigurationOverride: RegularFileProperty
@get:Input
@get:Optional
abstract val lintHtmlPrefs: Property<String>
@get:Input
@get:Optional
abstract val lintNullnessIgnoreDeprecated: Property<String>
@get:Input
@get:Optional
abstract val lintUnusedResourcesExcludeTests: Property<String>
@get:Input
@get:Optional
abstract val lintUnusedResourcesIncludeTests: Property<String>
@get:Input
@get:Optional
abstract val userHome: Property<String>
fun initialize(providerFactory: ProviderFactory, lintMode: LintMode) {
if (lintMode == LintMode.ANALYSIS) {
lintAutofix.disallowChanges()
lintBaselinesContinue.disallowChanges()
lintHtmlPrefs.disallowChanges()
userHome.disallowChanges()
} else {
lintAutofix.setDisallowChanges(providerFactory.systemProperty("lint.autofix"))
lintBaselinesContinue.setDisallowChanges(
providerFactory.systemProperty("lint.baselines.continue")
)
lintHtmlPrefs.setDisallowChanges(providerFactory.systemProperty("lint.html.prefs"))
userHome.setDisallowChanges(providerFactory.systemProperty("user.home"))
}
androidLintLogJarProblems.setDisallowChanges(
providerFactory.systemProperty("android.lint.log-jar-problems")
)
javaHome.setDisallowChanges(providerFactory.systemProperty("java.home"))
javaVersion.setDisallowChanges(providerFactory.systemProperty("java.version"))
lintApiDatabase.fileProvider(
providerFactory.systemProperty("LINT_API_DATABASE").map {
// Suppress the warning because the Gradle docs say "May return null"
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
File(it).takeIf { file -> file.isFile }
}
)
lintApiDatabase.disallowChanges()
lintConfigurationOverride.fileProvider(
providerFactory.systemProperty("lint.configuration.override").map {
// Suppress the warning because the Gradle docs say "May return null"
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
File(it).takeIf { file -> file.isFile }
}
)
lintConfigurationOverride.disallowChanges()
lintNullnessIgnoreDeprecated.setDisallowChanges(
providerFactory.systemProperty("lint.nullness.ignore-deprecated")
)
lintUnusedResourcesExcludeTests.setDisallowChanges(
providerFactory.systemProperty("lint.unused-resources.exclude-tests")
)
lintUnusedResourcesIncludeTests.setDisallowChanges(
providerFactory.systemProperty("lint.unused-resources.include-tests")
)
}
}
/**
* Environment variables which can affect lint's behavior.
*/
abstract class EnvironmentVariableInputs {
@get:Input
@get:Optional
abstract val androidLintIncludeLdpi: Property<String>
@get:Input
@get:Optional
abstract val androidLintMaxDepth: Property<String>
@get:Input
@get:Optional
abstract val androidLintMaxViewCount: Property<String>
@get:Input
@get:Optional
abstract val androidLintNullnessIgnoreDeprecated: Property<String>
@get:InputFile
@get:PathSensitive(PathSensitivity.NAME_ONLY)
@get:Optional
abstract val lintApiDatabase: RegularFileProperty
@get:Input
@get:Optional
abstract val lintHtmlPrefs: Property<String>
@get:Input
@get:Optional
abstract val lintXmlRoot: Property<String>
@get:InputFile
@get:PathSensitive(PathSensitivity.NAME_ONLY)
@get:Optional
abstract val lintOverrideConfiguration: RegularFileProperty
fun initialize(providerFactory: ProviderFactory, lintMode: LintMode) {
if (lintMode == LintMode.ANALYSIS) {
lintHtmlPrefs.disallowChanges()
lintXmlRoot.disallowChanges()
} else {
lintHtmlPrefs.setDisallowChanges(providerFactory.environmentVariable("LINT_HTML_PREFS"))
lintXmlRoot.setDisallowChanges(providerFactory.environmentVariable("LINT_XML_ROOT"))
}
androidLintIncludeLdpi.setDisallowChanges(
providerFactory.environmentVariable("ANDROID_LINT_INCLUDE_LDPI")
)
androidLintMaxDepth.setDisallowChanges(
providerFactory.environmentVariable("ANDROID_LINT_MAX_DEPTH")
)
androidLintMaxViewCount.setDisallowChanges(
providerFactory.environmentVariable("ANDROID_LINT_MAX_VIEW_COUNT")
)
androidLintNullnessIgnoreDeprecated.setDisallowChanges(
providerFactory.environmentVariable("ANDROID_LINT_NULLNESS_IGNORE_DEPRECATED")
)
lintApiDatabase.fileProvider(
providerFactory.environmentVariable("LINT_API_DATABASE").map {
// Suppress the warning because the Gradle docs say "May return null"
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
File(it).takeIf { file -> file.isFile }
}
)
lintApiDatabase.disallowChanges()
lintOverrideConfiguration.fileProvider(
providerFactory.environmentVariable("LINT_OVERRIDE_CONFIGURATION").map {
// Suppress the warning because the Gradle docs say "May return null"
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
File(it).takeIf { file -> file.isFile }
}
)
lintOverrideConfiguration.disallowChanges()
}
}
/**
* Inputs for the variant.
*/
abstract class VariantInputs {
@get:Input
abstract val name: Property<String>
/**
* Whether the module dependencies should be modeled as module dependencies (instead of modeled
* as external libraries).
*/
@get:Input
abstract val useModuleDependencyLintModels: Property<Boolean>
@get:Nested
@get:Optional
abstract val mainArtifact: Property<AndroidArtifactInput>
@get:Nested
@get:Optional
abstract val testArtifact: Property<JavaArtifactInput>
@get:Nested
@get:Optional
abstract val androidTestArtifact: Property<AndroidArtifactInput>
@get:Nested
@get:Optional
abstract val testFixturesArtifact: Property<AndroidArtifactInput>
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
@get:Optional
abstract val mergedManifest: RegularFileProperty
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
@get:Optional
abstract val manifestMergeReport: RegularFileProperty
@get:Input
abstract val namespace: Property<String>
@get:Nested
@get:Optional
abstract val minSdkVersion: SdkVersionInput
@get:Nested
@get:Optional
abstract val targetSdkVersion: SdkVersionInput
@get:Input
abstract val resValues: MapProperty<ResValue.Key, ResValue>
@get:Input
abstract val manifestPlaceholders: MapProperty<String, String>
@get:Input
abstract val resourceConfigurations: ListProperty<String>
@get:InputFiles
@get:PathSensitive(PathSensitivity.NAME_ONLY)
@get:Optional
abstract val proguardFiles: ListProperty<RegularFile>
// the extracted proguard files are probably also part of the proguardFiles but we need to set
// the dependency explicitly so Gradle can track it properly.
@get:InputFiles
@get:PathSensitive(PathSensitivity.NAME_ONLY)
@get:Optional
abstract val extractedProguardFiles: DirectoryProperty
@get:InputFiles
@get:PathSensitive(PathSensitivity.NAME_ONLY)
@get:Optional
abstract val consumerProguardFiles: ListProperty<RegularFile>
@get:Nested
@get:Optional
abstract val mainSourceProvider: Property<SourceProviderInput>
/**
* Androidx compose plugin uses this field to add kotlin multiplatform sources sets as there is
* no public API to customize lint sources.
*/
@get:Internal
abstract val sourceProviders: ListProperty<SourceProviderInput>
@get:Nested
@get:Optional
abstract val hostTestSourceProvider: Property<SourceProviderInput>
@get:Nested
@get:Optional
abstract val androidTestSourceProvider: Property<SourceProviderInput>
@get:Nested
@get:Optional
abstract val testFixturesSourceProvider: Property<SourceProviderInput>
@get:Input
abstract val debuggable: Property<Boolean>
@get:Input
abstract val shrinkable: Property<Boolean>
@get:Input
abstract val useSupportLibraryVectorDrawables: Property<Boolean>
@get:Nested
abstract val buildFeatures: BuildFeaturesInput
@get:Internal
abstract val libraryDependencyCacheBuildService: Property<LibraryDependencyCacheBuildService>
@get:Internal
abstract val mavenCoordinatesCache: Property<MavenCoordinatesCacheBuildService>
/**
* Initializes the variant inputs
*
* @param variantWithTests the [VariantWithTests].
* @param useModuleDependencyLintModels whether the module dependencies should be modeled as
* module dependencies (instead of modeled as external libraries).
* @param warnIfProjectTreatedAsExternalDependency whether to warn the user if the standalone
* plugin is not applied to a java module dependency when useModuleDependencyLintModels is
* true.
* @param lintMode the [LintMode] for which this [VariantInputs] is being used.
* @param addBaseModuleLintModel whether the base app module should be modeled as a module
* dependency if useModuleDependencyLintModels is false. This Boolean only affects dynamic
* feature modules, and it has no effect if useModuleDependencyLintModels is true.
* @param fatalOnly whether lint is being invoked with --fatal-only
*/
fun initialize(
variantWithTests: VariantWithTests,
useModuleDependencyLintModels: Boolean,
warnIfProjectTreatedAsExternalDependency: Boolean,
lintMode: LintMode,
addBaseModuleLintModel: Boolean = false,
fatalOnly: Boolean
) {
initialize(
variantWithTests.main,
variantWithTests.unitTest,
variantWithTests.androidTest,
variantWithTests.testFixtures,
variantWithTests.main.services,
variantWithTests.main.name,
useModuleDependencyLintModels,
warnIfProjectTreatedAsExternalDependency,
lintMode,
addBaseModuleLintModel,
fatalOnly,
includeMainArtifact = true,
isPerComponentLintAnalysis = false
)
}
fun initialize(
variantCreationConfig: VariantCreationConfig,
hostTestCreationConfig: HostTestCreationConfig?,
deviceTestCreationConfig: DeviceTestCreationConfig?,
testFixturesCreationConfig: TestFixturesCreationConfig?,
services: TaskCreationServices,
variantName: String,
useModuleDependencyLintModels: Boolean,
warnIfProjectTreatedAsExternalDependency: Boolean,
lintMode: LintMode,
addBaseModuleLintModel: Boolean = false,
fatalOnly: Boolean,
includeMainArtifact: Boolean,
isPerComponentLintAnalysis: Boolean
) {
name.setDisallowChanges(variantName)
this.useModuleDependencyLintModels.setDisallowChanges(useModuleDependencyLintModels)
if (includeMainArtifact) {
mainArtifact.set(
services.newInstance(AndroidArtifactInput::class.java)
.initialize(
variantCreationConfig,
lintMode,
useModuleDependencyLintModels,
addBaseModuleLintModel,
warnIfProjectTreatedAsExternalDependency,
fatalOnly,
isPerComponentLintAnalysis
)
)
}
mainArtifact.disallowChanges()
testArtifact.setDisallowChanges(
hostTestCreationConfig?.let { hostTest ->
services.newInstance(JavaArtifactInput::class.java)
.initialize(
hostTest,
lintMode,
useModuleDependencyLintModels = false,
addBaseModuleLintModel,
warnIfProjectTreatedAsExternalDependency,
// analyzing test bytecode is expensive, without much benefit
includeClassesOutputDirectories = false,
fatalOnly,
isPerComponentLintAnalysis
)
}
)
androidTestArtifact.setDisallowChanges(
deviceTestCreationConfig?.let { androidTest ->
services.newInstance(AndroidArtifactInput::class.java)
.initialize(
androidTest,
lintMode,
useModuleDependencyLintModels = false,
addBaseModuleLintModel,
warnIfProjectTreatedAsExternalDependency,
fatalOnly,
isPerComponentLintAnalysis,
// analyzing test bytecode is expensive, without much benefit
includeClassesOutputDirectories = false,
// analyzing test generated sources is expensive, without much benefit
includeGeneratedSourceFolders = false
)
}
)
testFixturesArtifact.setDisallowChanges(
testFixturesCreationConfig?.let { testFixtures ->
services.newInstance(AndroidArtifactInput::class.java)
.initialize(
testFixtures,
lintMode,
useModuleDependencyLintModels = false,
addBaseModuleLintModel,
warnIfProjectTreatedAsExternalDependency,
fatalOnly,
isPerComponentLintAnalysis
)
}
)
mergedManifest.setDisallowChanges(
variantCreationConfig.artifacts.get(SingleArtifact.MERGED_MANIFEST)
)
// The manifest merge report contains absolute paths, so it's not compatible with the lint
// analysis task being cacheable.
if (lintMode != LintMode.ANALYSIS) {
manifestMergeReport.set(
variantCreationConfig.artifacts.get(InternalArtifactType.MANIFEST_MERGE_REPORT)
)
}
manifestMergeReport.disallowChanges()
namespace.setDisallowChanges(variantCreationConfig.namespace)
minSdkVersion.initialize(variantCreationConfig.minSdk)
val lintOptions = variantCreationConfig.global.lintOptions
when (variantCreationConfig) {
is ApkCreationConfig ->
targetSdkVersion.initialize(variantCreationConfig.targetSdk)
is LibraryCreationConfig ->
targetSdkVersion.initialize(
createTargetSdkVersion(lintOptions.targetSdk,lintOptions.targetSdkPreview) ?: variantCreationConfig.targetSdk
)
is KmpComponentCreationConfig -> targetSdkVersion.initialize(variantCreationConfig.minSdk)
}
resValues.setDisallowChanges(
variantCreationConfig.resValuesCreationConfig?.resValues,
handleNullable = { empty() }
)
if (variantCreationConfig is ApkCreationConfig) {
manifestPlaceholders.setDisallowChanges(
variantCreationConfig.manifestPlaceholdersCreationConfig?.placeholders,
handleNullable = { empty() }
)
}
resourceConfigurations.setDisallowChanges(
variantCreationConfig.androidResourcesCreationConfig?.resourceConfigurations ?: emptyList()
)
if (includeMainArtifact) {
mainSourceProvider.setDisallowChanges(
variantCreationConfig.services
.newInstance(SourceProviderInput::class.java)
.initialize(
variantCreationConfig.sources,
lintMode,
projectDir = variantCreationConfig.services.provider { variantCreationConfig.services.projectInfo.projectDirectory }
)
)
sourceProviders.add(mainSourceProvider)
proguardFiles.set(variantCreationConfig.optimizationCreationConfig.proguardFiles)
extractedProguardFiles.set(
variantCreationConfig.global.globalArtifacts.get(
InternalArtifactType.DEFAULT_PROGUARD_FILES
)
)
consumerProguardFiles.set(
variantCreationConfig.optimizationCreationConfig.consumerProguardFiles
)
}
sourceProviders.disallowChanges()
proguardFiles.disallowChanges()
extractedProguardFiles.disallowChanges()
consumerProguardFiles.disallowChanges()
hostTestCreationConfig?.let {
hostTestSourceProvider.set(
variantCreationConfig.services
.newInstance(SourceProviderInput::class.java)
.initialize(
it.sources,
lintMode,
projectDir = variantCreationConfig.services.provider { variantCreationConfig.services.projectInfo.projectDirectory },
unitTestOnly = true
)
)
}
deviceTestCreationConfig?.let {
androidTestSourceProvider.set(
variantCreationConfig.services
.newInstance(SourceProviderInput::class.java)
.initialize(
it.sources,
lintMode,
projectDir = variantCreationConfig.services.provider { variantCreationConfig.services.projectInfo.projectDirectory },
instrumentationTestOnly = true
)
)
}
testFixturesCreationConfig?.let {
testFixturesSourceProvider.set(
variantCreationConfig.services
.newInstance(SourceProviderInput::class.java)
.initialize(
it.sources,
lintMode,
projectDir = variantCreationConfig.services.provider { variantCreationConfig.services.projectInfo.projectDirectory },
testFixtureOnly = true
)
)
}
hostTestSourceProvider.disallowChanges()
androidTestSourceProvider.disallowChanges()
testFixturesSourceProvider.disallowChanges()
debuggable.setDisallowChanges(variantCreationConfig.debuggable)
shrinkable.setDisallowChanges(
variantCreationConfig.optimizationCreationConfig.minifiedEnabled
)
useSupportLibraryVectorDrawables.setDisallowChanges(
variantCreationConfig.androidResourcesCreationConfig?.vectorDrawables?.useSupportLibrary
?: false
)
buildFeatures.initialize(variantCreationConfig)
libraryDependencyCacheBuildService.setDisallowChanges(getBuildService(variantCreationConfig.services.buildServiceRegistry))
mavenCoordinatesCache.setDisallowChanges(getBuildService(variantCreationConfig.services.buildServiceRegistry))
}
internal fun initializeForStandalone(
project: Project,
javaExtension: JavaPluginExtension,
kotlinExtensionWrapper: KotlinMultiplatformExtensionWrapper?,
projectOptions: ProjectOptions,
fatalOnly: Boolean,
useModuleDependencyLintModels: Boolean,
lintMode: LintMode,
lintModelArtifactType: LintModelArtifactType?,
jvmTargetName: String?,
testCompileClasspath: Configuration?,
testRuntimeClasspath: Configuration?
) {
if (kotlinExtensionWrapper == null) {
initializeForStandalone(
project,
javaExtension,
projectOptions,
fatalOnly,
useModuleDependencyLintModels,
lintMode,
lintModelArtifactType,
testCompileClasspath,
testRuntimeClasspath
)
} else {
initializeForStandaloneWithKotlinMultiplatform(
project,
kotlinExtensionWrapper,
projectOptions,
fatalOnly,
useModuleDependencyLintModels,
lintMode,
lintModelArtifactType!!,
jvmTargetName,
testCompileClasspath,
testRuntimeClasspath
)
}
}
private fun initializeForStandalone(
project: Project,
javaExtension: JavaPluginExtension,
projectOptions: ProjectOptions,
fatalOnly: Boolean,
useModuleDependencyLintModels: Boolean,
lintMode: LintMode,
lintModelArtifactType: LintModelArtifactType?,
testCompileClasspath: Configuration?,
testRuntimeClasspath: Configuration?
) {
val mainSourceSet = javaExtension.sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
val testSourceSet = javaExtension.sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME)
name.setDisallowChanges(mainSourceSet.name)
this.useModuleDependencyLintModels.setDisallowChanges(useModuleDependencyLintModels)
if (lintModelArtifactType == MAIN || lintModelArtifactType == null) {
mainArtifact.set(
project.objects
.newInstance(AndroidArtifactInput::class.java)
.initializeForStandalone(
project,
projectOptions,
mainSourceSet,
lintMode,
useModuleDependencyLintModels,
fatalOnly
)
)
mainSourceProvider.set(
project.objects
.newInstance(SourceProviderInput::class.java)
.initializeForStandalone(
mainSourceSet,
lintMode,
unitTestOnly = false
)
)
}
mainArtifact.disallowChanges()
mainSourceProvider.disallowChanges()
if (lintModelArtifactType == UNIT_TEST || lintModelArtifactType == null) {
testArtifact.set(
project.objects
.newInstance(JavaArtifactInput::class.java)
.initializeForStandalone(
project,
projectOptions,
testSourceSet,
lintMode,
if (lintModelArtifactType == null) {
// false in this case to avoid circular task dependencies (b/291934867)
false
} else {
useModuleDependencyLintModels
},
// analyzing test bytecode is expensive, without much benefit
includeClassesOutputDirectories = false,
fatalOnly,
mainSourceSet,
testCompileClasspath,
testRuntimeClasspath
)
)
if (!fatalOnly) {
hostTestSourceProvider.set(
project.objects
.newInstance(SourceProviderInput::class.java)
.initializeForStandalone(
testSourceSet,
lintMode,
unitTestOnly = true
)
)
}
}
testArtifact.disallowChanges()
hostTestSourceProvider.disallowChanges()
androidTestArtifact.disallowChanges()
testFixturesArtifact.disallowChanges()
namespace.setDisallowChanges("")
minSdkVersion.initializeEmpty()
targetSdkVersion.initializeEmpty()
manifestPlaceholders.disallowChanges()
resourceConfigurations.disallowChanges()
debuggable.setDisallowChanges(true)
shrinkable.setDisallowChanges(false)
useSupportLibraryVectorDrawables.setDisallowChanges(false)
mergedManifest.setDisallowChanges(null)
manifestMergeReport.setDisallowChanges(null)
sourceProviders.add(mainSourceProvider)
sourceProviders.disallowChanges()
androidTestSourceProvider.disallowChanges()
testFixturesSourceProvider.disallowChanges()
buildFeatures.initializeForStandalone()
libraryDependencyCacheBuildService.setDisallowChanges(getBuildService(project.gradle.sharedServices))
mavenCoordinatesCache.setDisallowChanges(getBuildService(project.gradle.sharedServices))
proguardFiles.setDisallowChanges(null)
extractedProguardFiles.setDisallowChanges(null)
consumerProguardFiles.setDisallowChanges(null)
resValues.disallowChanges()
}
private fun initializeForStandaloneWithKotlinMultiplatform(
project: Project,
kotlinExtensionWrapper: KotlinMultiplatformExtensionWrapper,
projectOptions: ProjectOptions,
fatalOnly: Boolean,
useModuleDependencyLintModels: Boolean,
lintMode: LintMode,
lintModelArtifactType: LintModelArtifactType,
jvmTargetName: String?,
testCompileClasspath: Configuration?,
testRuntimeClasspath: Configuration?
) {
val jvmTarget = kotlinExtensionWrapper.kotlinExtension.targets.findByName(jvmTargetName ?: "jvm")
val jvmMainCompilation = jvmTarget?.compilations?.findByName("main")
val jvmTestCompilation = jvmTarget?.compilations?.findByName("test")
name.setDisallowChanges(jvmTarget?.name)
this.useModuleDependencyLintModels.setDisallowChanges(useModuleDependencyLintModels)
if (jvmMainCompilation != null && lintModelArtifactType == MAIN) {
mainArtifact.set(
project.objects
.newInstance(AndroidArtifactInput::class.java)
.initializeForStandaloneWithKotlinMultiplatform(
project,
projectOptions,
KotlinCompilationWrapper(jvmMainCompilation),
lintMode,
useModuleDependencyLintModels,
fatalOnly
)
)
val sourceDirectories =
project.files().also { fileCollection ->
jvmMainCompilation.kotlinSourceSets.forEach {
fileCollection.from(it.kotlin.sourceDirectories)
}
}
mainSourceProvider.set(
project.objects
.newInstance(SourceProviderInput::class.java)
.initializeForStandaloneWithKotlinMultiplatform(
sourceDirectories,
lintMode,
unitTestOnly = false
)
)
}
mainArtifact.disallowChanges()
mainSourceProvider.disallowChanges()
if (jvmTestCompilation != null && lintModelArtifactType == UNIT_TEST) {
testArtifact.set(
project.objects
.newInstance(JavaArtifactInput::class.java)
.initializeForStandaloneWithKotlinMultiplatform(
project,
projectOptions,
KotlinCompilationWrapper(jvmTestCompilation),
lintMode,
useModuleDependencyLintModels,
// analyzing test bytecode is expensive, without much benefit
includeClassesOutputDirectories = false,
fatalOnly,
testCompileClasspath,
testRuntimeClasspath
)
)
val sourceDirectories =
project.files().also { fileCollection ->
jvmTestCompilation.kotlinSourceSets.forEach {
fileCollection.from(it.kotlin.sourceDirectories)
}
}
hostTestSourceProvider.set(
project.objects
.newInstance(SourceProviderInput::class.java)
.initializeForStandaloneWithKotlinMultiplatform(
sourceDirectories,
lintMode,
unitTestOnly = true
)
)
}
testArtifact.disallowChanges()
hostTestSourceProvider.disallowChanges()
androidTestArtifact.disallowChanges()
testFixturesArtifact.disallowChanges()
namespace.setDisallowChanges("")
minSdkVersion.initializeEmpty()
targetSdkVersion.initializeEmpty()
manifestPlaceholders.disallowChanges()
resourceConfigurations.disallowChanges()
debuggable.setDisallowChanges(true)
shrinkable.setDisallowChanges(false)
useSupportLibraryVectorDrawables.setDisallowChanges(false)
mergedManifest.setDisallowChanges(null)
manifestMergeReport.setDisallowChanges(null)
sourceProviders.add(mainSourceProvider)
sourceProviders.disallowChanges()
androidTestSourceProvider.disallowChanges()
testFixturesSourceProvider.disallowChanges()
buildFeatures.initializeForStandalone()
libraryDependencyCacheBuildService.setDisallowChanges(
getBuildService(project.gradle.sharedServices)
)
mavenCoordinatesCache.setDisallowChanges(getBuildService(project.gradle.sharedServices))
proguardFiles.setDisallowChanges(null)
extractedProguardFiles.setDisallowChanges(null)
consumerProguardFiles.setDisallowChanges(null)
resValues.disallowChanges()
}
fun toLintModel(
module: LintModelModule,
partialResultsDir: File? = null,
desugaredMethodsFiles: Collection<File>,
): LintModelVariant {
val dependencyCaches = DependencyCaches(
libraryDependencyCacheBuildService.get().localJarCache,
mavenCoordinatesCache.get())
return DefaultLintModelVariant(
module,
name.get(),
useSupportLibraryVectorDrawables.get(),
mainArtifact.orNull?.toLintModel(dependencyCaches, MAIN),
testArtifact.orNull?.toLintModel(dependencyCaches, UNIT_TEST),
androidTestArtifact.orNull
?.toLintModel(dependencyCaches, LintModelArtifactType.INSTRUMENTATION_TEST),
testFixturesArtifact.orNull
?.toLintModel(dependencyCaches, LintModelArtifactType.TEST_FIXTURES),
mergedManifest = mergedManifest.orNull?.asFile,
manifestMergeReport = manifestMergeReport.orNull?.asFile,
`package` = namespace.get(),
minSdkVersion = minSdkVersion.toLintModel(),
targetSdkVersion = targetSdkVersion.toLintModel(),
resValues =
resValues.get().map {
DefaultLintModelResourceField(
it.key.type,
it.key.name,
it.value.value
)
}.associateBy { it.name },
manifestPlaceholders = manifestPlaceholders.get(),
resourceConfigurations = resourceConfigurations.get(),
proguardFiles = proguardFiles.orNull?.map { it.asFile } ?: listOf(),
consumerProguardFiles = consumerProguardFiles.orNull?.map { it.asFile } ?: listOf(),
sourceProviders = mainSourceProvider.orNull?.toLintModels() ?: emptyList(),
testSourceProviders = listOfNotNull(
hostTestSourceProvider.orNull?.toLintModels(),
androidTestSourceProvider.orNull?.toLintModels(),
).flatten(),
testFixturesSourceProviders = testFixturesSourceProvider.orNull?.toLintModels() ?: emptyList(),
debuggable = debuggable.get(),
shrinkable = shrinkable.get(),
buildFeatures = buildFeatures.toLintModel(),
libraryResolver = DefaultLintModelLibraryResolver(dependencyCaches.libraryMap),
partialResultsDir = partialResultsDir,
desugaredMethodsFiles = desugaredMethodsFiles
)
}
}
abstract class BuildFeaturesInput {
@get:Input
abstract val viewBinding: Property<Boolean>
@get:Input
abstract val coreLibraryDesugaringEnabled: Property<Boolean>
@get:Input
abstract val namespacingMode: Property<LintModelNamespacingMode>
fun initialize(creationConfig: ComponentCreationConfig) {
viewBinding.setDisallowChanges(creationConfig.buildFeatures.viewBinding)
coreLibraryDesugaringEnabled.setDisallowChanges(
(creationConfig as? ConsumableCreationConfig)?.isCoreLibraryDesugaringEnabledLintCheck
?: false
)
namespacingMode.setDisallowChanges(
if (creationConfig.global.namespacedAndroidResources) {
LintModelNamespacingMode.DISABLED
} else {
LintModelNamespacingMode.REQUIRED
}
)
}
fun initializeForStandalone() {
viewBinding.setDisallowChanges(false)
coreLibraryDesugaringEnabled.setDisallowChanges(false)
namespacingMode.setDisallowChanges(LintModelNamespacingMode.DISABLED)
}
fun toLintModel(): LintModelBuildFeatures {
return DefaultLintModelBuildFeatures(
viewBinding.get(),
coreLibraryDesugaringEnabled.get(),
namespacingMode.get(),
)
}
}
abstract class SourceProviderInput {
@get:InputFiles // Note: The file may not be set or may not exist
@get:PathSensitive(PathSensitivity.NAME_ONLY)
@get:Optional
abstract val manifestFilePath: RegularFileProperty
@get:InputFiles // Note: The files may not exist
@get:PathSensitive(PathSensitivity.NAME_ONLY)
abstract val manifestOverlayFilePaths: ListProperty<File>
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val javaDirectories: ConfigurableFileCollection
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val resDirectories: ConfigurableFileCollection
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val assetsDirectories: ConfigurableFileCollection
// Without javaDirectoriesClasspath, the lint analysis task would be UP-TO-DATE after a change
// in the *order* of java source directories, which would be incorrect. We can't get rid of
// javaDirectories entirely because without javaDirectories, the lint analysis task would be
// UP-TO-DATE after the addition or removal of a non-existent java source directory. We need to
// set javaDirectoriesClasspath only for lint analysis tasks because other lint tasks set
// javaDirectoryPaths.
@get:Classpath
@get:Optional
abstract val javaDirectoriesClasspath: ConfigurableFileCollection
// See comment for javaDirectoriesClasspath
@get:Classpath
@get:Optional
abstract val resDirectoriesClasspath: ConfigurableFileCollection
// See comment for javaDirectoriesClasspath
@get:Classpath
@get:Optional
abstract val assetsDirectoriesClasspath: ConfigurableFileCollection
@get:Input // Note: The files may not exist
@get:Optional
abstract val manifestAbsoluteFilePaths: ListProperty<String>
@get:Input
@get:Optional
abstract val javaDirectoryPaths: ListProperty<String>
@get:Input
@get:Optional
abstract val resDirectoryPaths: ListProperty<String>
@get:Input
@get:Optional
abstract val assetsDirectoryPaths: ListProperty<String>
@get:Input
abstract val debugOnly: Property<Boolean>
@get:Input
abstract val unitTestOnly: Property<Boolean>
@get:Input
abstract val instrumentationTestOnly: Property<Boolean>
@get:Input
abstract val testFixtureOnly: Property<Boolean>
internal fun initialize(
sources: InternalSources,
lintMode: LintMode,
projectDir: Provider<Directory>,
unitTestOnly: Boolean = false,
instrumentationTestOnly: Boolean = false,
testFixtureOnly: Boolean = false
): SourceProviderInput {
this.manifestFilePath.fileProvider(sources.manifestFile)
this.manifestFilePath.disallowChanges()
this.manifestOverlayFilePaths.setDisallowChanges(sources.manifestOverlayFiles)
fun FlatSourceDirectoriesImpl.getFilteredSourceProviders(into: ConfigurableFileCollection) {
return getVariantSources()
.filter { dir -> !dir.isGenerated }
.forEach {
into.from(it.asFiles(projectDir))
}
}
fun LayeredSourceDirectoriesImpl.getFilteredSourceProviders(into: ConfigurableFileCollection) {
return getVariantSources().forEach { dirs ->
dirs.directoryEntries.filter { dir ->
!dir.isGenerated
}.forEach {
into.from(it.asFiles(projectDir))
}
}
}
sources.java?.getFilteredSourceProviders(javaDirectories)
sources.kotlin?.getFilteredSourceProviders(javaDirectories)
javaDirectories.disallowChanges()
sources.res?.getFilteredSourceProviders(this.resDirectories)
resDirectories.disallowChanges()
sources.assets?.getFilteredSourceProviders(assetsDirectories)
assetsDirectories.disallowChanges()
if (lintMode == LintMode.ANALYSIS) {
this.javaDirectoriesClasspath.from(javaDirectories)
this.resDirectoriesClasspath.from(resDirectories)
this.assetsDirectoriesClasspath.from(assetsDirectories)
} else {
this.manifestAbsoluteFilePaths.add(manifestFilePath.map { it.asFile.absolutePath })
this.manifestAbsoluteFilePaths.addAll(manifestOverlayFilePaths.map { it.map(File::getAbsolutePath) })
this.javaDirectoryPaths.set(javaDirectories.elements.map { elements ->
elements.map {
it.asFile.absolutePath
}
})
this.resDirectoryPaths.set(resDirectories.elements.map { elements ->
elements.map {
it.asFile.absolutePath
}
})
this.assetsDirectoryPaths.set(assetsDirectories.elements.map { elements ->
elements.map {
it.asFile.absolutePath
}
})
}
this.manifestAbsoluteFilePaths.disallowChanges()
this.javaDirectoryPaths.disallowChanges()
this.resDirectoryPaths.disallowChanges()
this.assetsDirectoryPaths.disallowChanges()
this.javaDirectoriesClasspath.disallowChanges()
this.resDirectoriesClasspath.disallowChanges()
this.assetsDirectoriesClasspath.disallowChanges()
this.debugOnly.setDisallowChanges(false) //TODO
this.unitTestOnly.setDisallowChanges(unitTestOnly)
this.instrumentationTestOnly.setDisallowChanges(instrumentationTestOnly)
this.testFixtureOnly.setDisallowChanges(testFixtureOnly)
return this
}
internal fun initializeForStandalone(
sourceSet: SourceSet,
lintMode: LintMode,
unitTestOnly: Boolean
): SourceProviderInput {
this.manifestFilePath.disallowChanges()
this.manifestOverlayFilePaths.disallowChanges()
this.javaDirectories.fromDisallowChanges(sourceSet.allJava.sourceDirectories)
this.resDirectories.disallowChanges()
this.assetsDirectories.disallowChanges()
if (lintMode == LintMode.ANALYSIS) {
this.javaDirectoriesClasspath.from(sourceSet.allJava.sourceDirectories)
}
this.javaDirectoriesClasspath.disallowChanges()
this.resDirectoriesClasspath.disallowChanges()
this.assetsDirectoriesClasspath.disallowChanges()
this.debugOnly.setDisallowChanges(false)
this.unitTestOnly.setDisallowChanges(unitTestOnly)
this.instrumentationTestOnly.setDisallowChanges(false)
this.testFixtureOnly.setDisallowChanges(false)
return this
}
internal fun initializeForStandaloneWithKotlinMultiplatform(
sourceDirectories: FileCollection,
lintMode: LintMode,
unitTestOnly: Boolean
): SourceProviderInput {
this.manifestFilePath.disallowChanges()
this.manifestOverlayFilePaths.disallowChanges()
this.javaDirectories.fromDisallowChanges(sourceDirectories)
this.resDirectories.disallowChanges()
this.assetsDirectories.disallowChanges()
if (lintMode == LintMode.ANALYSIS) {
this.javaDirectoriesClasspath.from(sourceDirectories)
}
this.javaDirectoriesClasspath.disallowChanges()
this.resDirectoriesClasspath.disallowChanges()
this.assetsDirectoriesClasspath.disallowChanges()
this.debugOnly.setDisallowChanges(false)
this.unitTestOnly.setDisallowChanges(unitTestOnly)
this.instrumentationTestOnly.setDisallowChanges(false)
this.testFixtureOnly.setDisallowChanges(false)
return this
}
internal fun toLintModels(): List<LintModelSourceProvider> {
return listOf(
DefaultLintModelSourceProvider(
// Pass the main manifest file if it is set, without checking whether it exists.
// For overlay manifest files, we pass only those that exist.
manifestFiles = listOfNotNull(manifestFilePath.orNull?.asFile) + manifestOverlayFilePaths.get().filter(File::isFile),
javaDirectories = javaDirectories.files.toList(),
resDirectories = resDirectories.files.toList(),
assetsDirectories = assetsDirectories.files.toList(),
debugOnly = debugOnly.get(),
unitTestOnly = unitTestOnly.get(),
instrumentationTestOnly = instrumentationTestOnly.get(),
testFixture = testFixtureOnly.get()
)
)
}
}
/**
* Inputs for an SdkVersion. This is used by [VariantInputs] for min/target SDK Version
*/
abstract class SdkVersionInput {
@get:Input
abstract val apiLevel: Property<Int>
@get:Input
@get:Optional
abstract val codeName: Property<String?>
internal fun initialize(version: com.android.build.api.variant.AndroidVersion) {
apiLevel.setDisallowChanges(version.apiLevel)
codeName.setDisallowChanges(version.codename)
}
internal fun initialize(apiLeve:Int, codename:String?) {
apiLevel.setDisallowChanges(apiLeve)
codeName.setDisallowChanges(codename)
}
internal fun initializeEmpty() {
apiLevel.setDisallowChanges(-1)
codeName.setDisallowChanges("")
}
internal fun toLintModel(): AndroidVersion? {
val api = apiLevel.get()
if (api <= 0) {
return null
}
return AndroidVersion(api, codeName.orNull)
}
}
/**
* Inputs for an Android Artifact. This is used by [VariantInputs] for the main and AndroidTest
* artifacts.
*/
abstract class AndroidArtifactInput : ArtifactInput() {
@get:Input
abstract val applicationId: Property<String>
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val generatedSourceFolders: ConfigurableFileCollection
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val generatedResourceFolders: ConfigurableFileCollection
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
@get:Optional
abstract val desugaredMethodsFiles: ConfigurableFileCollection
fun initialize(
creationConfig: ComponentCreationConfig,
lintMode: LintMode,
useModuleDependencyLintModels: Boolean,
addBaseModuleLintModel: Boolean,
warnIfProjectTreatedAsExternalDependency: Boolean,
fatalOnly: Boolean,
isPerComponentLintAnalysis: Boolean,
includeClassesOutputDirectories: Boolean = true,
includeGeneratedSourceFolders: Boolean = true
): AndroidArtifactInput {
applicationId.setDisallowChanges(creationConfig.applicationId)
if (includeGeneratedSourceFolders) {
generatedSourceFolders.from(
getGeneratedSourceFoldersFileCollection(creationConfig)
)
}
generatedSourceFolders.disallowChanges()
generatedResourceFolders.fromDisallowChanges(
getGeneratedResourceFoldersFileCollection(creationConfig)
)
if (includeClassesOutputDirectories) {
if (creationConfig is KmpComponentCreationConfig) {
classesOutputDirectories.from(
creationConfig
.artifacts
.forScope(ScopedArtifacts.Scope.PROJECT)
.getFinalArtifacts(ScopedArtifact.CLASSES)
)
} else {
classesOutputDirectories.from(creationConfig.artifacts.get(InternalArtifactType.JAVAC))
}
creationConfig.oldVariantApiLegacySupport?.variantData?.let {
classesOutputDirectories.from(
it.allPreJavacGeneratedBytecode
)
classesOutputDirectories.from(it.allPostJavacGeneratedBytecode)
}
creationConfig.androidResourcesCreationConfig?.let {
classesOutputDirectories.from(
it.getCompiledRClasses(
AndroidArtifacts.ConsumedConfigType.COMPILE_CLASSPATH
)
)
}
}
classesOutputDirectories.disallowChanges()
this.warnIfProjectTreatedAsExternalDependency.setDisallowChanges(warnIfProjectTreatedAsExternalDependency)
this.ignoreUnexpectedArtifactTypes.setDisallowChanges(false)
initializeProjectDependencyLintArtifacts(
useModuleDependencyLintModels,
creationConfig.variantDependencies,
lintMode,
// TODO(b/197322928) Initialize dependency partial results for nested components once
// there is a lint analysis task per component.
isMainArtifact = !creationConfig.componentType.isNestedComponent,
fatalOnly,
isPerComponentLintAnalysis
)
if (!useModuleDependencyLintModels) {
if (addBaseModuleLintModel) {
initializeBaseModuleLintModel(creationConfig.variantDependencies)
}
projectRuntimeExplodedAars =
creationConfig.variantDependencies.getArtifactCollectionForToolingModel(
AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
AndroidArtifacts.ArtifactScope.PROJECT,
AndroidArtifacts.ArtifactType.LOCAL_EXPLODED_AAR_FOR_LINT
)
projectCompileExplodedAars =
creationConfig.variantDependencies.getArtifactCollectionForToolingModel(
AndroidArtifacts.ConsumedConfigType.COMPILE_CLASSPATH,
AndroidArtifacts.ArtifactScope.PROJECT,
AndroidArtifacts.ArtifactType.LOCAL_EXPLODED_AAR_FOR_LINT
)
}
artifactCollectionsInputs.setDisallowChanges(
ArtifactCollectionsInputsImpl(
variantDependencies = creationConfig.variantDependencies,
projectPath = creationConfig.services.projectInfo.path,
variantName = creationConfig.name,
runtimeType = ArtifactCollectionsInputs.RuntimeType.FULL,
)
)
val coreLibDesugaring = (creationConfig as? ConsumableCreationConfig)?.isCoreLibraryDesugaringEnabledLintCheck
?: false
desugaredMethodsFiles.from(
getDesugaredMethods(
creationConfig.services,
coreLibDesugaring,
creationConfig.minSdk,
creationConfig.global
)
).disallowChanges()
return this
}
fun initializeForStandalone(
project: Project,
projectOptions: ProjectOptions,
sourceSet: SourceSet,
lintMode: LintMode,
useModuleDependencyLintModels: Boolean,
fatalOnly: Boolean
): AndroidArtifactInput {
applicationId.setDisallowChanges("")
generatedSourceFolders.disallowChanges()
generatedResourceFolders.disallowChanges()
desugaredMethodsFiles.disallowChanges()
classesOutputDirectories.fromDisallowChanges(sourceSet.output.classesDirs)
warnIfProjectTreatedAsExternalDependency.setDisallowChanges(false)
ignoreUnexpectedArtifactTypes.setDisallowChanges(true)
val variantDependencies = VariantDependencies(
variantName = sourceSet.name,
componentType = ComponentTypeImpl.JAVA_LIBRARY,
compileClasspath = project.configurations.getByName(sourceSet.compileClasspathConfigurationName),
runtimeClasspath = project.configurations.getByName(sourceSet.runtimeClasspathConfigurationName),
sourceSetRuntimeConfigurations = listOf(),
sourceSetImplementationConfigurations = listOf(),
elements = mapOf(),
providedClasspath = project.configurations.getByName(sourceSet.compileOnlyConfigurationName),
annotationProcessorConfiguration = project.configurations.getByName(sourceSet.annotationProcessorConfigurationName),
reverseMetadataValuesConfiguration = null,
wearAppConfiguration = null,
testedVariant = null,
project = project,
projectOptions = projectOptions,
isLibraryConstraintsApplied = false,
isSelfInstrumenting = false,
)
artifactCollectionsInputs.setDisallowChanges(
ArtifactCollectionsInputsImpl(
variantDependencies = variantDependencies,
projectPath = project.path,
variantName = sourceSet.name,
runtimeType = ArtifactCollectionsInputs.RuntimeType.FULL,
)
)
initializeProjectDependencyLintArtifacts(
useModuleDependencyLintModels,
variantDependencies,
lintMode,
isMainArtifact = true,
fatalOnly,
projectOptions[BooleanOption.LINT_ANALYSIS_PER_COMPONENT]
)
return this
}
fun initializeForStandaloneWithKotlinMultiplatform(
project: Project,
projectOptions: ProjectOptions,
kotlinCompilationWrapper: KotlinCompilationWrapper,
lintMode: LintMode,
useModuleDependencyLintModels: Boolean,
fatalOnly: Boolean
): AndroidArtifactInput {
val compilation = kotlinCompilationWrapper.kotlinCompilation
applicationId.setDisallowChanges("")
generatedSourceFolders.disallowChanges()
generatedResourceFolders.disallowChanges()
desugaredMethodsFiles.disallowChanges()
classesOutputDirectories.fromDisallowChanges(compilation.output.classesDirs)
warnIfProjectTreatedAsExternalDependency.setDisallowChanges(false)
ignoreUnexpectedArtifactTypes.setDisallowChanges(true)
val variantDependencies = VariantDependencies(
variantName = compilation.name,
componentType = ComponentTypeImpl.JAVA_LIBRARY,
compileClasspath = project.configurations.getByName(compilation.compileDependencyConfigurationName),
runtimeClasspath = project.configurations.getByName(compilation.runtimeDependencyConfigurationName ?: compilation.compileDependencyConfigurationName),
sourceSetRuntimeConfigurations = listOf(),
sourceSetImplementationConfigurations = listOf(),
elements = mapOf(),
providedClasspath = project.configurations.getByName(compilation.compileOnlyConfigurationName),
annotationProcessorConfiguration = null,
reverseMetadataValuesConfiguration = null,
wearAppConfiguration = null,
testedVariant = null,
project = project,
projectOptions = projectOptions,
isLibraryConstraintsApplied = false,
isSelfInstrumenting = false,
)
artifactCollectionsInputs.setDisallowChanges(
ArtifactCollectionsInputsImpl(
variantDependencies = variantDependencies,
projectPath = project.path,
variantName = compilation.name,
runtimeType = ArtifactCollectionsInputs.RuntimeType.FULL,
)
)
initializeProjectDependencyLintArtifacts(
useModuleDependencyLintModels,
variantDependencies,
lintMode,
isMainArtifact = true,
fatalOnly,
projectOptions[BooleanOption.LINT_ANALYSIS_PER_COMPONENT]
)
return this
}
internal fun toLintModel(
dependencyCaches: DependencyCaches,
type: LintModelArtifactType
): LintModelAndroidArtifact {
return DefaultLintModelAndroidArtifact(
applicationId.get(),
generatedResourceFolders.toList(),
generatedSourceFolders.toList(),
desugaredMethodsFiles.toList(),
computeDependencies(dependencyCaches),
classesOutputDirectories.files.toList(),
type
)
}
}
/**
* Inputs for a Java Artifact. This is used by [VariantInputs] for the unit test artifact.
*/
abstract class JavaArtifactInput : ArtifactInput() {
fun initialize(
creationConfig: HostTestCreationConfig,
lintMode: LintMode,
useModuleDependencyLintModels: Boolean,
addBaseModuleLintModel: Boolean,
warnIfProjectTreatedAsExternalDependency: Boolean,
includeClassesOutputDirectories: Boolean,
fatalOnly: Boolean,
isPerComponentLintAnalysis: Boolean
): JavaArtifactInput {
if (includeClassesOutputDirectories) {
if (creationConfig is KmpComponentCreationConfig) {
classesOutputDirectories.from(
creationConfig
.artifacts
.forScope(ScopedArtifacts.Scope.PROJECT)
.getFinalArtifacts(ScopedArtifact.CLASSES)
)
} else {
classesOutputDirectories.from(creationConfig.artifacts.get(InternalArtifactType.JAVAC))
}
creationConfig.oldVariantApiLegacySupport?.variantData?.let {
classesOutputDirectories.from(
it.allPreJavacGeneratedBytecode
)
classesOutputDirectories.from(it.allPostJavacGeneratedBytecode)
}
creationConfig.androidResourcesCreationConfig?.let {
classesOutputDirectories.from(
it.getCompiledRClasses(
AndroidArtifacts.ConsumedConfigType.COMPILE_CLASSPATH
)
)
}
}
classesOutputDirectories.disallowChanges()
this.warnIfProjectTreatedAsExternalDependency.setDisallowChanges(warnIfProjectTreatedAsExternalDependency)
this.ignoreUnexpectedArtifactTypes.setDisallowChanges(false)
initializeProjectDependencyLintArtifacts(
useModuleDependencyLintModels,
creationConfig.variantDependencies,
lintMode,
// TODO(b/197322928) Initialize dependency partial results for unit test components once
// there is a lint analysis task per component.
isMainArtifact = false,
fatalOnly,
isPerComponentLintAnalysis
)
if (!useModuleDependencyLintModels) {
if (addBaseModuleLintModel) {
initializeBaseModuleLintModel(creationConfig.variantDependencies)
}
projectRuntimeExplodedAars =
creationConfig.variantDependencies.getArtifactCollectionForToolingModel(
AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
AndroidArtifacts.ArtifactScope.PROJECT,
AndroidArtifacts.ArtifactType.LOCAL_EXPLODED_AAR_FOR_LINT
)
projectCompileExplodedAars =
creationConfig.variantDependencies.getArtifactCollectionForToolingModel(
AndroidArtifacts.ConsumedConfigType.COMPILE_CLASSPATH,
AndroidArtifacts.ArtifactScope.PROJECT,
AndroidArtifacts.ArtifactType.LOCAL_EXPLODED_AAR_FOR_LINT
)
}
artifactCollectionsInputs.setDisallowChanges(
ArtifactCollectionsInputsImpl(
variantDependencies = creationConfig.variantDependencies,
projectPath = creationConfig.services.projectInfo.path,
variantName = creationConfig.name,
runtimeType = ArtifactCollectionsInputs.RuntimeType.FULL,
)
)
return this
}
fun initializeForStandalone(
project: Project,
projectOptions: ProjectOptions,
sourceSet: SourceSet,
lintMode: LintMode,
useModuleDependencyLintModels: Boolean,
includeClassesOutputDirectories: Boolean,
fatalOnly: Boolean,
testedSourceSet: SourceSet,
compileClasspath: Configuration?,
runtimeClasspath: Configuration?
): JavaArtifactInput {
if (includeClassesOutputDirectories) {
classesOutputDirectories.from(sourceSet.output.classesDirs)
}
classesOutputDirectories.disallowChanges()
// Only ever used within the model builder in the standalone plugin
warnIfProjectTreatedAsExternalDependency.setDisallowChanges(false)
ignoreUnexpectedArtifactTypes.setDisallowChanges(true)
// Use custom compile and runtime classpath configurations for unit tests for the
// standalone lint plugin because the existing testCompileClasspath and testRuntimeClasspath
// configurations don't include the main source set's jar output in their artifacts.
val mainJarTask = project.tasks.named(testedSourceSet.jarTaskName, Jar::class.java)
compileClasspath?.run {
extendsFrom(
project.configurations.getByName(sourceSet.compileClasspathConfigurationName)
)
project.dependencies
.add(
name,
project.files(Callable { mainJarTask.flatMap { it.archiveFile } })
)
}
runtimeClasspath?.run {
extendsFrom(
project.configurations.getByName(sourceSet.runtimeClasspathConfigurationName)
)
project.dependencies
.add(
name,
project.files(Callable { mainJarTask.flatMap { it.archiveFile } })
)
}
val variantDependencies = VariantDependencies(
variantName = sourceSet.name,
componentType = ComponentTypeImpl.JAVA_LIBRARY,
compileClasspath = compileClasspath ?: project.configurations.getByName(sourceSet.compileClasspathConfigurationName),
runtimeClasspath = runtimeClasspath ?: project.configurations.getByName(sourceSet.runtimeClasspathConfigurationName),
sourceSetRuntimeConfigurations = listOf(),
sourceSetImplementationConfigurations = listOf(),
elements = mapOf(),
providedClasspath = project.configurations.getByName(sourceSet.compileOnlyConfigurationName),
annotationProcessorConfiguration = project.configurations.getByName(sourceSet.annotationProcessorConfigurationName),
reverseMetadataValuesConfiguration = null,
wearAppConfiguration = null,
testedVariant = null,
project = project,
projectOptions = projectOptions,
isLibraryConstraintsApplied = false,
isSelfInstrumenting = false,
)
artifactCollectionsInputs.setDisallowChanges(
ArtifactCollectionsInputsImpl(
variantDependencies = variantDependencies,
projectPath = project.path,
variantName = sourceSet.name,
runtimeType = ArtifactCollectionsInputs.RuntimeType.FULL,
)
)
initializeProjectDependencyLintArtifacts(
useModuleDependencyLintModels,
variantDependencies,
lintMode,
// TODO(b/197322928) Initialize dependency partial results for unit test components once
// there is a lint analysis task per component.
isMainArtifact = false,
fatalOnly,
projectOptions[BooleanOption.LINT_ANALYSIS_PER_COMPONENT]
)
return this
}
fun initializeForStandaloneWithKotlinMultiplatform(
project: Project,
projectOptions: ProjectOptions,
kotlinCompilationWrapper: KotlinCompilationWrapper,
lintMode: LintMode,
useModuleDependencyLintModels: Boolean,
includeClassesOutputDirectories: Boolean,
fatalOnly: Boolean,
compileClasspath: Configuration?,
runtimeClasspath: Configuration?
): JavaArtifactInput {
val compilation = kotlinCompilationWrapper.kotlinCompilation
if (includeClassesOutputDirectories) {
classesOutputDirectories.from(compilation.output.classesDirs)
}
classesOutputDirectories.disallowChanges()
warnIfProjectTreatedAsExternalDependency.setDisallowChanges(false)
ignoreUnexpectedArtifactTypes.setDisallowChanges(true)
// Use custom compile and runtime dependency configurations for unit tests for the
// standalone lint plugin because the existing compile and runtime dependency
// configurations don't include the main jar output in their artifacts.
val jvmTarget = kotlinCompilationWrapper.kotlinCompilation.target
val mainJarTask = project.tasks.named("${jvmTarget.name}Jar", Jar::class.java)
val compileClasspathForLint: Configuration =
compileClasspath?.apply {
this.extendsFrom(
project.configurations.getByName(compilation.compileDependencyConfigurationName)
)
project.dependencies
.add(
this.name,
project.files(Callable { mainJarTask.flatMap { it.archiveFile } })
)
} ?: project.configurations.getByName(compilation.compileDependencyConfigurationName)
val runtimeClasspathForLint: Configuration =
compilation.runtimeDependencyConfigurationName?.let { runtimeConfigName ->
runtimeClasspath?.apply {
this.extendsFrom(project.configurations.getByName(runtimeConfigName))
project.dependencies
.add(
this.name,
project.files(Callable { mainJarTask.flatMap { it.archiveFile } })
)
}
} ?: compileClasspathForLint
val variantDependencies = VariantDependencies(
variantName = compilation.name,
componentType = ComponentTypeImpl.JAVA_LIBRARY,
compileClasspath = compileClasspathForLint,
runtimeClasspath = runtimeClasspathForLint,
sourceSetRuntimeConfigurations = listOf(),
sourceSetImplementationConfigurations = listOf(),
elements = mapOf(),
providedClasspath = project.configurations.getByName(compilation.compileOnlyConfigurationName),
annotationProcessorConfiguration = null,
reverseMetadataValuesConfiguration = null,
wearAppConfiguration = null,
testedVariant = null,
project = project,
projectOptions = projectOptions,
isLibraryConstraintsApplied = false,
isSelfInstrumenting = false,
)
artifactCollectionsInputs.setDisallowChanges(
ArtifactCollectionsInputsImpl(
variantDependencies = variantDependencies,
projectPath = project.path,
variantName = compilation.name,
runtimeType = ArtifactCollectionsInputs.RuntimeType.FULL,
)
)
initializeProjectDependencyLintArtifacts(
useModuleDependencyLintModels,
variantDependencies,
lintMode,
// TODO(b/197322928) Initialize dependency partial results for unit test components once
// there is a lint analysis task per component.
isMainArtifact = false,
fatalOnly,
projectOptions[BooleanOption.LINT_ANALYSIS_PER_COMPONENT]
)
return this
}
internal fun toLintModel(
dependencyCaches: DependencyCaches,
type: LintModelArtifactType
): LintModelJavaArtifact {
return DefaultLintModelJavaArtifact(
computeDependencies(dependencyCaches),
classesOutputDirectories.files.toList(),
type
)
}
}
/**
* Base Inputs for Android/Java artifacts
*/
abstract class ArtifactInput {
@get:Classpath
abstract val classesOutputDirectories: ConfigurableFileCollection
@get:Nested
abstract val artifactCollectionsInputs: Property<ArtifactCollectionsInputs>
@get:Classpath
@get:Optional
val projectRuntimeExplodedAarsFileCollection: FileCollection?
get() = projectRuntimeExplodedAars?.artifactFiles
@get:Internal
var projectRuntimeExplodedAars: ArtifactCollection? = null
@get:Classpath
@get:Optional
val projectCompileExplodedAarsFileCollection: FileCollection?
get() = projectCompileExplodedAars?.artifactFiles
@get:Internal
var projectCompileExplodedAars: ArtifactCollection? = null
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:Optional
abstract val projectRuntimeLintModelsFileCollection: ConfigurableFileCollection
@get:Internal
abstract val projectRuntimeLintModels: Property<ArtifactCollection>
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:Optional
abstract val projectCompileLintModelsFileCollection: ConfigurableFileCollection
@get:Internal
abstract val projectCompileLintModels: Property<ArtifactCollection>
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:Optional
abstract val baseModuleLintModelFileCollection: ConfigurableFileCollection
@get:Internal
abstract val baseModuleLintModel: Property<ArtifactCollection>
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
@get:Optional
abstract val runtimeLintModelMetadataFileCollection: ConfigurableFileCollection
@get:Internal
abstract val runtimeLintModelMetadata: Property<ArtifactCollection>
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
@get:Optional
abstract val compileLintModelMetadataFileCollection: ConfigurableFileCollection
@get:Internal
abstract val compileLintModelMetadata: Property<ArtifactCollection>
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:Optional
abstract val runtimeLintPartialResultsFileCollection: ConfigurableFileCollection
@get:Internal
abstract val runtimeLintPartialResults: Property<ArtifactCollection>
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:Optional
abstract val compileLintPartialResultsFileCollection: ConfigurableFileCollection
@get:Internal
abstract val compileLintPartialResults: Property<ArtifactCollection>
@get:Internal
abstract val warnIfProjectTreatedAsExternalDependency: Property<Boolean>
/**
* Whether to ignore unexpected artifact types when resolving dependencies. This should be true
* when running lint via the standalone plugin (b/198048896).
*/
@get:Internal
abstract val ignoreUnexpectedArtifactTypes: Property<Boolean>
protected fun initializeProjectDependencyLintArtifacts(
useModuleDependencyLintModels: Boolean,
variantDependencies: VariantDependencies,
lintMode: LintMode,
isMainArtifact: Boolean,
fatalOnly: Boolean,
isPerComponentLintAnalysis: Boolean
) {
if (useModuleDependencyLintModels) {
val runtimeArtifacts = variantDependencies.getArtifactCollectionForToolingModel(
AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
AndroidArtifacts.ArtifactScope.PROJECT,
AndroidArtifacts.ArtifactType.LINT_MODEL
)
projectRuntimeLintModels.set(runtimeArtifacts)
projectRuntimeLintModelsFileCollection.from(runtimeArtifacts.artifactFiles)
val compileArtifacts = variantDependencies.getArtifactCollectionForToolingModel(
AndroidArtifacts.ConsumedConfigType.COMPILE_CLASSPATH,
AndroidArtifacts.ArtifactScope.PROJECT,
AndroidArtifacts.ArtifactType.LINT_MODEL
)
projectCompileLintModels.set(compileArtifacts)
projectCompileLintModelsFileCollection.from(compileArtifacts.artifactFiles)
} else {
val runtimeLintModelMetadataArtifacts =
variantDependencies.getArtifactCollectionForToolingModel(
AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
AndroidArtifacts.ArtifactScope.PROJECT,
AndroidArtifacts.ArtifactType.LINT_MODEL_METADATA
)
runtimeLintModelMetadata.set(runtimeLintModelMetadataArtifacts)
runtimeLintModelMetadataFileCollection.from(
runtimeLintModelMetadataArtifacts.artifactFiles
)
val compileLintModelMetadataArtifacts =
variantDependencies.getArtifactCollectionForToolingModel(
AndroidArtifacts.ConsumedConfigType.COMPILE_CLASSPATH,
AndroidArtifacts.ArtifactScope.PROJECT,
AndroidArtifacts.ArtifactType.LINT_MODEL_METADATA
)
compileLintModelMetadata.set(compileLintModelMetadataArtifacts)
compileLintModelMetadataFileCollection.from(
compileLintModelMetadataArtifacts.artifactFiles
)
if (isMainArtifact && lintMode == LintMode.ANALYSIS && isPerComponentLintAnalysis) {
val runtimeLintPartialResultsArtifacts =
variantDependencies.getArtifactCollectionForToolingModel(
AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
AndroidArtifacts.ArtifactScope.PROJECT,
if (fatalOnly) {
AndroidArtifacts.ArtifactType.LINT_VITAL_PARTIAL_RESULTS
} else {
AndroidArtifacts.ArtifactType.LINT_PARTIAL_RESULTS
}
)
runtimeLintPartialResults.set(runtimeLintPartialResultsArtifacts)
runtimeLintPartialResultsFileCollection.from(
runtimeLintPartialResultsArtifacts.artifactFiles
)
val compileLintPartialResultsArtifacts =
variantDependencies.getArtifactCollectionForToolingModel(
AndroidArtifacts.ConsumedConfigType.COMPILE_CLASSPATH,
AndroidArtifacts.ArtifactScope.PROJECT,
if (fatalOnly) {
AndroidArtifacts.ArtifactType.LINT_VITAL_PARTIAL_RESULTS
} else {
AndroidArtifacts.ArtifactType.LINT_PARTIAL_RESULTS
}
)
compileLintPartialResults.set(compileLintPartialResultsArtifacts)
compileLintPartialResultsFileCollection.from(
compileLintPartialResultsArtifacts.artifactFiles
)
}
}
projectRuntimeLintModels.disallowChanges()
projectRuntimeLintModelsFileCollection.disallowChanges()
projectCompileLintModels.disallowChanges()
projectCompileLintModelsFileCollection.disallowChanges()
runtimeLintModelMetadata.disallowChanges()
runtimeLintModelMetadataFileCollection.disallowChanges()
compileLintModelMetadata.disallowChanges()
compileLintModelMetadataFileCollection.disallowChanges()
runtimeLintPartialResults.disallowChanges()
runtimeLintPartialResultsFileCollection.disallowChanges()
compileLintPartialResults.disallowChanges()
compileLintPartialResultsFileCollection.disallowChanges()
}
protected fun initializeBaseModuleLintModel(variantDependencies: VariantDependencies) {
val artifactCollection = variantDependencies.getArtifactCollectionForToolingModel(
AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
AndroidArtifacts.ArtifactScope.PROJECT,
AndroidArtifacts.ArtifactType.BASE_MODULE_LINT_MODEL
)
baseModuleLintModel.setDisallowChanges(artifactCollection)
baseModuleLintModelFileCollection.fromDisallowChanges(artifactCollection.artifactFiles)
}
internal fun computeDependencies(dependencyCaches: DependencyCaches): LintModelDependencies {
val artifactCollectionsInputs = artifactCollectionsInputs.get()
val artifactHandler: ArtifactHandler<LintModelLibrary> =
if (projectRuntimeLintModels.isPresent) {
val thisProject =
ProjectKey(
artifactCollectionsInputs.projectBuildTreePath.get(),
artifactCollectionsInputs.variantName
)
CheckDependenciesLintModelArtifactHandler(
dependencyCaches,
thisProject,
projectRuntimeLintModels.get(),
projectCompileLintModels.get(),
artifactCollectionsInputs.compileClasspath.projectJars,
artifactCollectionsInputs.runtimeClasspath!!.projectJars,
warnIfProjectTreatedAsExternalDependency.get())
} else {
// When not checking dependencies, treat all dependencies as external, with the
// possible exception of the base module dependency. (When writing a dynamic feature
// lint model for publication, we want to model the base module dependency as a
// module dependency, not as an external dependency.)
ExternalLintModelArtifactHandler.create(
dependencyCaches,
projectRuntimeExplodedAars,
projectCompileExplodedAars,
null,
artifactCollectionsInputs.compileClasspath.projectJars,
artifactCollectionsInputs.runtimeClasspath!!.projectJars,
baseModuleLintModel.orNull,
runtimeLintModelMetadata.get(),
compileLintModelMetadata.get(),
runtimeLintPartialResults.orNull,
compileLintPartialResults.orNull,
)
}
val modelBuilder = LintDependencyModelBuilder(
artifactHandler = artifactHandler,
libraryMap = dependencyCaches.libraryMap,
mavenCoordinatesCache = dependencyCaches.mavenCoordinatesCache
)
val graph = getDependencyGraphBuilder()
val issueReporter = object : IssueReporter() {
override fun reportIssue(type: Type, severity: Severity, exception: EvalIssueException) {
if (severity == Severity.ERROR) {
throw exception
}
}
override fun hasIssue(type: Type) = false
}
graph.createDependencies(
modelBuilder = modelBuilder,
artifactCollectionsProvider = artifactCollectionsInputs,
withFullDependency = true,
ignoreUnexpectedArtifactTypes = ignoreUnexpectedArtifactTypes.get(),
issueReporter = issueReporter
)
return modelBuilder.createModel()
}
}
class LintFromMaven(val files: FileCollection, val version: String) {
companion object {
@JvmStatic
fun from(
project: Project,
projectOptions: ProjectOptions,
issueReporter: IssueReporter,
): LintFromMaven {
val lintVersion =
getLintMavenArtifactVersion(
projectOptions[StringOption.LINT_VERSION_OVERRIDE]?.trim(),
issueReporter
)
val config = project.configurations.detachedConfiguration(
project.dependencies.create(
mapOf(
"group" to "com.android.tools.lint",
"name" to "lint-gradle",
"version" to lintVersion,
)
)
)
config.isTransitive = true
config.isCanBeConsumed = false
config.isCanBeResolved = true
return LintFromMaven(config, lintVersion)
}
}
}
/**
* The lint binary uses the same version numbers as AGP (see LintCliClient#getClientRevision()
* which is called when you run lint --version, as well as in release notes, etc etc).
*
* However, for historical reasons, the maven artifacts for its various libraries used in AGP are
* using the older tools-base version numbers, which are 23 higher, so lint 7.0.0 is published
* at com.android.tools.lint:lint-gradle:30.0.0
*
* This function maps from the user-oriented lint version specified by the user to the maven lint
* library version number for the artifact to load.
*
* Returns the actual lint version to use, the given [versionOverride] if valid, otherwise the default,
* reporting any issues as a side effect.
*/
internal fun getLintMavenArtifactVersion(
versionOverride: String?,
reporter: IssueReporter?,
defaultVersion: String = Version.ANDROID_TOOLS_BASE_VERSION,
agpVersion: String = Version.ANDROID_GRADLE_PLUGIN_VERSION
): String {
if (versionOverride == null) {
return defaultVersion
}
// Only verify versions that parse. If it is not valid, it will fail later anyway.
val parsed = AgpVersion.tryParse(versionOverride)
if (parsed == null) {
reporter?.reportError(
IssueReporter.Type.GENERIC,
"""
Could not parse lint version override '$versionOverride'
Recommendation: Remove or update the gradle property ${StringOption.LINT_VERSION_OVERRIDE.propertyName} to be at least $agpVersion
""".trimIndent()
)
return defaultVersion
}
val default = AgpVersion.parse(defaultVersion)
// Heuristic when given an AGP version, find the corresponding lint version (that's 23 higher)
val normalizedOverride: String = (parsed.major + 23).toString() + versionOverride.removePrefix(parsed.major.toString())
val normalizedParsed = AgpVersion.tryParse(normalizedOverride) ?: error("Unexpected parse error")
if (normalizedParsed < default) {
reporter?.reportError(
IssueReporter.Type.GENERIC,
"""
Lint must be at least version $agpVersion
Recommendation: Remove or update the gradle property ${StringOption.LINT_VERSION_OVERRIDE.propertyName} to be at least $agpVersion
""".trimIndent()
)
return defaultVersion
}
return normalizedOverride
}
/**
* A class to wrap [KotlinMultiplatformExtension]. If there are method parameters with type
* [KotlinMultiplatformExtension] in task or task input classes, Gradle will fail at runtime for
* projects without the Kotlin Gradle plugin applied. Using this wrapper class works around that
* constraint.
*
* When using this class, perform a runtime check that the Kotlin Gradle plugin is applied.
*/
class KotlinMultiplatformExtensionWrapper(val kotlinExtension: KotlinMultiplatformExtension)
/**
* A class to wrap [KotlinCompilation]. If there are method parameters with type [KotlinCompilation]
* in task or task input classes, Gradle will fail at runtime for projects without the Kotlin
* Gradle plugin applied. Using this wrapper class works around that constraint.
*
* When using this class, perform a runtime check that the Kotlin Gradle plugin is applied.
*/
class KotlinCompilationWrapper(val kotlinCompilation: KotlinCompilation<KotlinCommonOptions>)
enum class LintMode {
ANALYSIS,
EXTRACT_ANNOTATIONS,
MODEL_WRITING,
REPORTING,
UPDATE_BASELINE,
}
const val LINT_XML_CONFIG_FILE_NAME = "lint.xml"