Skip to content

Commit

Permalink
Merge pull request #380 from usefulness/androidcomponents
Browse files Browse the repository at this point in the history
  • Loading branch information
mateuszkwiecinski authored Nov 12, 2022
2 parents 6d7c5c6 + c2b271f commit 0844874
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 199 deletions.
21 changes: 9 additions & 12 deletions .github/workflows/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ jobs:
- uses: gradle/gradle-build-action@v2

- name: Code style check
run: ./gradlew projectCodestyle --stacktrace
run: ./gradlew projectCodestyle

- run: ./gradlew assemble --stacktrace
- run: ./gradlew assemble

- run: ./gradlew validatePlugins --stacktrace
- run: ./gradlew validatePlugins

- run: ./gradlew projectCoverage --stacktrace
- run: ./gradlew projectCoverage

- uses: codecov/codecov-action@v3

Expand Down Expand Up @@ -79,14 +79,11 @@ jobs:
agp: ${{ fromJSON(needs.provide-agp-version-matrix.outputs.agp-versions) }}
include:
- javaVersion: 11
gradle: 7.2
agp: 7.1.0
- javaVersion: 11
gradle: 7.3.3
agp: 7.1.0
gradle: 7.4
agp: 7.2.2
- javaVersion: 17
gradle: 7.4
agp: 7.1.0
agp: 7.2.2

name: Run Gradle-${{ matrix.gradle }}, AGP-${{ matrix.agp }}, Java-${{ matrix.javaVersion }}
steps:
Expand All @@ -109,7 +106,7 @@ jobs:
with:
build-root-directory: sample
gradle-version: ${{ matrix.gradle }}
arguments: assemble lint -PuseMavenLocal -PagpVersion=${{ matrix.agp }} --stacktrace
arguments: assemble lint -PuseMavenLocal -PagpVersion=${{ matrix.agp }}

build-sample-apps-with-confgiuration-cache:
runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -140,7 +137,7 @@ jobs:
with:
build-root-directory: sample
gradle-version: current
arguments: assembleDebug --configuration-cache --stacktrace
arguments: assembleDebug --configuration-cache

icons-test:
runs-on: macos-latest
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,8 @@ See [related issue](https://github.com/usefulness/easylauncher-gradle-plugin/iss

## Requirements
Minimal requirements for the plugin are:
- Gradle: **7.0.4**
- Android Gradle Plugin: **7.1.0**
- Gradle: **7.4**
- Android Gradle Plugin: **7.2.2**
- Java Version: **8**
- minSdkVersion: **21** _(theoretically there should be no lower boundary - it just hasn't been tested)_

Expand Down
2 changes: 2 additions & 0 deletions buildSrc/settings.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import org.gradle.api.initialization.resolve.RepositoriesMode

rootProject.name = "buildSrc"

dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
Expand Down
6 changes: 0 additions & 6 deletions buildSrc/src/main/kotlin/PublishingPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import org.gradle.api.Project
import org.gradle.api.plugins.ExtensionContainer
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication

class PublishingPlugin : Plugin<Project> {

Expand All @@ -24,11 +23,6 @@ class PublishingPlugin : Plugin<Project> {
}
}
}
with(publications) {
register("mavenJava", MavenPublication::class.java) {
it.from(components.getByName("java"))
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,12 @@
package com.project.starter.easylauncher.plugin

import com.android.build.gradle.AppExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.api.BaseVariant
import org.gradle.api.DomainObjectSet
import org.gradle.api.Project
import com.android.build.api.component.analytics.AnalyticsEnabledApplicationVariant
import com.android.build.api.dsl.AndroidSourceDirectorySet
import com.android.build.api.variant.Variant
import com.android.build.gradle.internal.component.ComponentCreationConfig
import org.gradle.api.model.ObjectFactory
import java.io.File

private val supportedPlugins = listOf(
"com.android.application",
"com.android.library",
)

internal fun Project.configureSupportedPlugins(block: (DomainObjectSet<out BaseVariant>) -> Unit) {
supportedPlugins.forEach { pluginId ->
pluginManager.withPlugin(pluginId) { block(findVariants()) }
}
}

internal fun Project.findVariants(): DomainObjectSet<out BaseVariant> =
extensions.findByType(AppExtension::class.java)?.applicationVariants
?: extensions.findByType(LibraryExtension::class.java)?.libraryVariants
?: this.objects.domainObjectSet(BaseVariant::class.java)

internal fun ObjectFactory.getIconFiles(parent: File, iconName: String): Iterable<File> =
fileTree().from(parent).apply {
include(resourceFilePattern(iconName))
Expand All @@ -37,3 +20,17 @@ private fun resourceFilePattern(name: String): String {
name
}
}

/**
* Workaround for https://issuetracker.google.com/issues/197121905
*/
internal val Variant.isDebuggable: Boolean
get() = when (this) {
is AnalyticsEnabledApplicationVariant -> delegate.isDebuggable
is ComponentCreationConfig -> debuggable
else -> false
}

@Suppress("DEPRECATION") // https://issuetracker.google.com/issues/170650362
internal val AndroidSourceDirectorySet.srcDirs
get() = (this as? com.android.build.gradle.api.AndroidSourceDirectorySet)?.srcDirs.orEmpty()
Original file line number Diff line number Diff line change
@@ -1,142 +1,127 @@
package com.project.starter.easylauncher.plugin

import com.android.build.gradle.BaseExtension
import com.android.build.gradle.api.BaseVariant
import com.project.starter.easylauncher.filter.EasyLauncherFilter
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.Variant
import com.android.build.gradle.internal.api.DefaultAndroidSourceFile
import com.android.build.gradle.internal.scope.InternalArtifactType
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.gradle.configurationcache.extensions.capitalized
import java.io.File
import java.util.Locale

@Suppress("UnstableApiUsage")
class EasyLauncherPlugin : Plugin<Project> {

override fun apply(target: Project) = with(target) {
val extension = extensions.create(EasyLauncherExtension.NAME, EasyLauncherExtension::class.java)

logger.info("Running gradle version: ${gradle.gradleVersion}")
log.info { "Running gradle version: ${gradle.gradleVersion}" }

configureSupportedPlugins { variants ->
val android = extensions.getByType(BaseExtension::class.java)
val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class.java)
?: error("'com.starter.easylauncher' has to be applied after Android Gradle Plugin")

val easyLauncherTasks = mutableListOf<TaskProvider<EasyLauncherTask>>()
val manifestBySourceSet = mutableMapOf<String, File>()
val resSourceDirectoriesBySourceSet = mutableMapOf<String, Set<File>>()

variants.configureEach { variant ->
val configs = extension.variants.filter { it.name == variant.name }.takeIf { it.isNotEmpty() }
?: findConfigs(variant, extension.productFlavors, extension.buildTypes)
androidComponents.finalizeDsl { common ->
common.sourceSets
.mapNotNull { sourceSet -> (sourceSet.manifest as? DefaultAndroidSourceFile)?.srcFile?.let { sourceSet.name to it } }
.forEach { manifestBySourceSet[it.first] = it.second }

val enabled = configs.all { it.enabled.get() }
common.sourceSets
.map { sourceSet -> sourceSet.name to sourceSet.res.srcDirs }
.forEach { resSourceDirectoriesBySourceSet[it.first] = it.second }
}

if (enabled) {
val filters = configs.flatMap { it.filters.get() }.toMutableSet()
androidComponents.onVariants { variant ->
val configs = extension.variants.filter { it.name == variant.name }.takeIf { it.isNotEmpty() }
?: findConfigs(variant, extension.productFlavors, extension.buildTypes)

val enabled = configs.all { it.enabled.get() }

if (enabled) {
val filters = configs.flatMap { it.filters.get() }.toMutableSet()

// set default ribbon
if (filters.isEmpty() && variant.isDebuggable) {
val ribbonText = when (extension.isDefaultFlavorNaming.orNull) {
true -> variant.flavorName
false -> variant.buildType
null ->
if (variant.productFlavors.isEmpty()) {
variant.buildType
} else {
variant.flavorName
}
}

// set default ribbon
if (filters.isEmpty() && variant.buildType.isDebuggable) {
val ribbonText = when (extension.isDefaultFlavorNaming.orNull) {
true -> variant.flavorName
false -> variant.buildType.name
null ->
if (variant.productFlavors.isEmpty()) {
variant.buildType.name
} else {
variant.flavorName
}
}
if (ribbonText != null) {
filters.add(EasyLauncherConfig(ribbonText, project.objects).greenRibbonFilter())
}
}

if (filters.isNotEmpty()) {
val customIconNames = provider {
val global = extension.iconNames.orNull.orEmpty()
val variantSpecific = configs.flatMap { config -> config.iconNames.orNull.orEmpty() }
(global + variantSpecific).toSet()
}
log.info { "configuring ${variant.name}, isDebuggable=${variant.isDebuggable}, filters=${filters.size}" }

val task = registerTask(
android = android,
variant = variant,
customIconNames = customIconNames,
filters = filters,
)
if (filters.isNotEmpty()) {
val customIconNames = provider {
val global = extension.iconNames.orNull.orEmpty()
val variantSpecific = configs.flatMap { config -> config.iconNames.orNull.orEmpty() }
(global + variantSpecific).toSet()
}

easyLauncherTasks.add(task)
val relevantSourcesSets = setOfNotNull(
"main",
variant.name,
variant.buildType,
variant.flavorName,
)

val manifests = manifestBySourceSet
.mapNotNull { (name, file) ->
if (relevantSourcesSets.contains(name)) {
file
} else {
null
}
}

tasks.named("generate${variant.name.capitalize(Locale.ROOT)}Resources") { it.dependsOn(task) }
val resSourceDirectories = resSourceDirectoriesBySourceSet
.mapNotNull { (name, files) ->
if (relevantSourcesSets.contains(name)) {
files
} else {
null
}
}
.flatten()

val task = project.tasks.register("easylauncher${variant.name.capitalized()}", EasyLauncherTask::class.java) {
it.manifestFiles.set(manifests)
it.manifestPlaceholders.set(variant.manifestPlaceholders)
it.resourceDirectories.set(resSourceDirectories)
it.filters.set(filters)
it.customIconNames.set(customIconNames)
it.minSdkVersion.set(variant.minSdkVersion.apiLevel)
}
} else {
logger.info("disabled for ${variant.name}")
}
}

tasks.register(EasyLauncherTask.NAME) { it.dependsOn(easyLauncherTasks) }
}
}

private fun Project.registerTask(
android: BaseExtension,
variant: BaseVariant,
customIconNames: Provider<Set<String>>,
filters: Set<EasyLauncherFilter>,
): TaskProvider<EasyLauncherTask> {
val generatedResDir = getGeneratedResDir(variant)
android.sourceSets.getByName(variant.name).res.srcDir(generatedResDir)

val icons = provider {
val names = (customIconNames.get().takeIf { it.isNotEmpty() } ?: android.getLauncherIconNames(variant)).toSet()
logger.info("will process icons: ${names.joinToString()}")

variant.getAllResDirectories(except = generatedResDir).flatMap { resDir ->
names.flatMap { objects.getIconFiles(parent = resDir, iconName = it) }
variant
.artifacts
.use(task)
.wiredWith(EasyLauncherTask::outputDir)
.toCreate(InternalArtifactType.GENERATED_RES)
}
} else {
log.info { "disabled for ${variant.name}" }
}
}
val minSdkVersion = provider {
(variant.mergedFlavor.minSdkVersion ?: android.defaultConfig.minSdkVersion)?.apiLevel ?: 1
}

val name = "${EasyLauncherTask.NAME}${variant.name.capitalize(Locale.ROOT)}"

// They don't care: https://issuetracker.google.com/issues/187096666 🤷‍
// Discussion: https://github.com/usefulness/easylauncher-gradle-plugin/issues/165
val extractDeeplinksTask = name.replace("easylauncher", "extractDeepLinks")
// Workaround for: https://github.com/gradle/gradle/issues/8057
if (tasks.names.contains(extractDeeplinksTask)) {
tasks.named(extractDeeplinksTask).configure { it.dependsOn(name) }
}

return tasks.register(name, EasyLauncherTask::class.java) {
it.outputDir.set(generatedResDir)
it.filters.set(filters)
it.minSdkVersion.set(minSdkVersion)
it.icons.from(icons)
it.resourceDirectories.from(variant.getAllResDirectories(except = generatedResDir))
}
}

private fun findConfigs(
variant: BaseVariant,
variant: Variant,
ribbonProductFlavors: Iterable<EasyLauncherConfig>,
ribbonBuildTypes: Iterable<EasyLauncherConfig>,
): List<EasyLauncherConfig> =
ribbonProductFlavors.filter { config -> variant.productFlavors.any { config.name == it.name } } +
ribbonBuildTypes.filter { it.name == variant.buildType.name }

private fun Project.getGeneratedResDir(variant: BaseVariant) =
File(project.buildDir, "generated/easylauncher/res/${variant.name}")

private fun BaseExtension.getLauncherIconNames(variant: BaseVariant) =
getAndroidManifestFiles(variant)
.flatMap { manifestFile -> manifestFile.getLauncherIcons(variant.mergedFlavor.manifestPlaceholders) }

private fun BaseVariant.getAllResDirectories(except: File) =
sourceSets.flatMap { sourceSet -> sourceSet.resDirectories }
.filterNot { resDirectory -> resDirectory == except }

private fun BaseExtension.getAndroidManifestFiles(variant: BaseVariant): Iterable<File> {
return listOf("main", variant.name, variant.buildType.name, variant.flavorName)
.filter { it.isNotEmpty() }
.distinct()
.map { name -> sourceSets.getByName(name).manifest.srcFile }
.filter { it.exists() }
): List<EasyLauncherConfig> {
return ribbonProductFlavors.filter { config -> variant.productFlavors.any { config.name == it.second } } +
ribbonBuildTypes.filter { it.name == variant.buildType }
}
}
Loading

0 comments on commit 0844874

Please sign in to comment.