blob: d705315480f2b07f27719e9b3b3c2e893672855c [file] [log] [blame]
/*
* Copyright 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.
*/
package androidx.compose.material.icons.generator.tasks
import androidx.compose.material.icons.generator.Icon
import androidx.compose.material.icons.generator.IconProcessor
import com.android.build.gradle.LibraryExtension
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import java.io.File
import java.util.Locale
/**
* Base [org.gradle.api.Task] for tasks relating to icon generation.
*/
@CacheableTask
abstract class IconGenerationTask : DefaultTask() {
/**
* Directory containing raw drawables. These icons will be processed to generate programmatic
* representations.
*/
@PathSensitive(PathSensitivity.RELATIVE)
@InputDirectory
val allIconsDirectory =
project.rootProject.project(GeneratorProject).projectDir.resolve("raw-icons")
/**
* Specific theme to generate icons for, or null to generate all
*/
@Optional
@Input
var themeName: String? = null
/**
* Specific icon directories to use in this task
*/
@Internal
fun getIconDirectories(): List<File> {
val themeName = themeName
if (themeName != null) {
return listOf(allIconsDirectory.resolve(themeName))
} else {
return allIconsDirectory.listFiles()!!.filter { it.isDirectory }
}
}
/**
* Checked-in API file for the generator module, where we will track all the generated icons
*/
@PathSensitive(PathSensitivity.NONE)
@InputFile
val expectedApiFile =
project.rootProject.project(GeneratorProject).projectDir.resolve("api/icons.txt")
/**
* Root build directory for this task, where outputs will be placed into.
*/
@OutputDirectory
lateinit var buildDirectory: File
/**
* Generated API file that will be placed in the build directory. This can be copied manually
* to [expectedApiFile] to confirm that API changes were intended.
*/
@get:OutputFile
val generatedApiFile: File
get() = buildDirectory.resolve("api/icons.txt")
/**
* @return a list of all processed [Icon]s from [getIconDirectories].
*/
fun loadIcons(): List<Icon> {
// material-icons-core loads and verifies all of the icons from all of the themes:
// both that all icons are present in all themes, and also that no icons have been removed.
// So, when we're loading just one theme, we don't need to verify it
val verifyApi = themeName == null
return IconProcessor(
getIconDirectories(),
expectedApiFile,
generatedApiFile,
verifyApi
).process()
}
@get:OutputDirectory
val generatedSrcMainDirectory: File
get() = buildDirectory.resolve(GeneratedSrcMain)
@get:OutputDirectory
val generatedSrcAndroidTestDirectory: File
get() = buildDirectory.resolve(GeneratedSrcAndroidTest)
@get:OutputDirectory
val generatedResourceDirectory: File
get() = buildDirectory.resolve(GeneratedResource)
/**
* The action for this task
*/
@TaskAction
abstract fun run()
companion object {
/**
* Registers the core [project]. The core project contains only the icons defined in
* [androidx.compose.material.icons.generator.CoreIcons], and no tests.
*/
@JvmStatic
fun registerCoreIconProject(
project: Project,
libraryExtension: LibraryExtension,
isMpp: Boolean
) {
if (isMpp) {
CoreIconGenerationTask.register(project, null)
} else {
libraryExtension.libraryVariants.all { variant ->
CoreIconGenerationTask.register(project, variant)
}
}
}
/**
* Registers the extended [project]. The core project contains all icons except for the
* icons defined in [androidx.compose.material.icons.generator.CoreIcons], as well as a
* bitmap comparison test for every icon in both the core and extended project.
*/
@JvmStatic
fun registerExtendedIconThemeProject(
project: Project,
libraryExtension: LibraryExtension
) {
libraryExtension.libraryVariants.all { variant ->
ExtendedIconGenerationTask.register(project, variant)
}
// b/175401659 - disable lint as it takes a long time, and most errors should
// be caught by lint on material-icons-core anyway
project.afterEvaluate {
project.tasks.named("lintAnalyzeDebug") { t ->
t.enabled = false
}
project.tasks.named("lintDebug") { t ->
t.enabled = false
}
}
}
@JvmStatic
fun registerExtendedIconMainProject(
project: Project,
libraryExtension: LibraryExtension
) {
libraryExtension.testVariants.all { variant ->
IconTestingGenerationTask.register(project, variant)
}
}
const val GeneratedSrcMain = "src/commonMain/kotlin"
const val GeneratedSrcAndroidTest = "src/androidAndroidTest/kotlin"
const val GeneratedResource = "generatedIcons/res"
}
}
// Path to the generator project
private const val GeneratorProject = ":compose:material:material:icons:generator"
/**
* Registers a new [T] in [this], and sets [IconGenerationTask.buildDirectory] depending on
* [variant].
*
* @param variant the [com.android.build.gradle.api.BaseVariant] to associate this task with, or
* `null` if this task does not change between variants.
* @return a [Pair] of the created [TaskProvider] of [T] of [IconGenerationTask], and the [File]
* for the directory that files will be generated to
*/
@Suppress("DEPRECATION") // BaseVariant
fun <T : IconGenerationTask> Project.registerGenerationTask(
taskName: String,
taskClass: Class<T>,
variant: com.android.build.gradle.api.BaseVariant? = null
): Pair<TaskProvider<T>, File> {
val variantName = variant?.name ?: "allVariants"
val themeName = if (project.name.contains("material-icons-extended-")) {
project.name.replace("material-icons-extended-", "")
} else {
null
}
val buildDirectory = project.buildDir.resolve("generatedIcons/$variantName")
return tasks.register("$taskName${variantName.capitalize(Locale.getDefault())}", taskClass) {
it.themeName = themeName
it.buildDirectory = buildDirectory
} to buildDirectory
}
fun Project.getMultiplatformSourceSet(name: String): KotlinSourceSet {
val sourceSet = project.multiplatformExtension!!.sourceSets.find { it.name == name }
return requireNotNull(sourceSet) {
"No source sets found matching $name"
}
}
private val Project.multiplatformExtension
get() = extensions.findByType(KotlinMultiplatformExtension::class.java)