From 9ec3aeedddf809313e423321ff452aac1f139326 Mon Sep 17 00:00:00 2001 From: rhenwinch Date: Tue, 24 Sep 2024 00:36:29 +0800 Subject: [PATCH] feat(plugin): support dependency packaging thru AGP application plugin --- build.gradle.kts | 12 ++- .../kotlin/FlixclusiveProviderAppPlugin.kt | 44 ++++++++ ...kt => FlixclusiveProviderLibraryPlugin.kt} | 22 +++- .../gradle/FlixclusiveProviderExtension.kt | 6 +- .../flixclusive/gradle/task/CompileDexTask.kt | 39 ++----- .../com/flixclusive/gradle/task/Tasks.kt | 102 ++++++++++++++---- .../gradle/util/AndroidProjectType.kt | 17 +++ .../flixclusive/gradle/util/KotlinAndroid.kt | 47 ++++++++ .../flixclusive/gradle/util/ProjectHelper.kt | 32 ++++++ 9 files changed, 265 insertions(+), 56 deletions(-) create mode 100644 src/main/kotlin/FlixclusiveProviderAppPlugin.kt rename src/main/kotlin/{com/flixclusive/gradle/FlixclusiveProvider.kt => FlixclusiveProviderLibraryPlugin.kt} (59%) create mode 100644 src/main/kotlin/com/flixclusive/gradle/util/AndroidProjectType.kt create mode 100644 src/main/kotlin/com/flixclusive/gradle/util/KotlinAndroid.kt diff --git a/build.gradle.kts b/build.gradle.kts index 1342f45..119dedb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -42,9 +42,13 @@ dependencies { gradlePlugin { plugins { - create("com.flixclusive.gradle") { - id = "com.flixclusive.gradle" - implementationClass = "com.flixclusive.gradle.FlixclusiveProvider" + register("flixclusiveProvider") { + id = "flixclusive.provider" + implementationClass = "FlixclusiveProviderLibraryPlugin" + } + register("flixclusiveProviderApp") { + id = "flixclusive.provider.app" + implementationClass = "FlixclusiveProviderAppPlugin" } } } @@ -55,7 +59,7 @@ val sourcesJar = tasks.register("sourcesJar") { } group = "com.github.flixclusive" -version = "1.1.4" +version = "1.2.0" publishing { repositories { diff --git a/src/main/kotlin/FlixclusiveProviderAppPlugin.kt b/src/main/kotlin/FlixclusiveProviderAppPlugin.kt new file mode 100644 index 0000000..86ff05f --- /dev/null +++ b/src/main/kotlin/FlixclusiveProviderAppPlugin.kt @@ -0,0 +1,44 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +import com.android.build.api.dsl.ApplicationExtension +import com.flixclusive.gradle.FLX_PROVIDER_EXTENSION_NAME +import com.flixclusive.gradle.FlixclusiveProviderExtension +import com.flixclusive.gradle.configuration.registerConfigurations +import com.flixclusive.gradle.task.registerTasks +import com.flixclusive.gradle.util.configureAndroid +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure + +@Suppress("unused") +class FlixclusiveProviderAppPlugin : Plugin { + override fun apply(project: Project) { + with(project) { + with(pluginManager) { + apply("com.android.application") + } + + extensions.create(FLX_PROVIDER_EXTENSION_NAME, FlixclusiveProviderExtension::class.java, project) + + extensions.configure { + configureAndroid(commonExtension = this@configure) + } + } + + registerTasks(project) + registerConfigurations(project) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/flixclusive/gradle/FlixclusiveProvider.kt b/src/main/kotlin/FlixclusiveProviderLibraryPlugin.kt similarity index 59% rename from src/main/kotlin/com/flixclusive/gradle/FlixclusiveProvider.kt rename to src/main/kotlin/FlixclusiveProviderLibraryPlugin.kt index 5d1940f..9f663b1 100644 --- a/src/main/kotlin/com/flixclusive/gradle/FlixclusiveProvider.kt +++ b/src/main/kotlin/FlixclusiveProviderLibraryPlugin.kt @@ -13,17 +13,31 @@ * along with this program. If not, see . */ -package com.flixclusive.gradle - +import com.android.build.api.dsl.LibraryExtension +import com.flixclusive.gradle.FLX_PROVIDER_EXTENSION_NAME +import com.flixclusive.gradle.FlixclusiveProviderExtension import com.flixclusive.gradle.configuration.registerConfigurations import com.flixclusive.gradle.task.registerTasks +import com.flixclusive.gradle.util.configureAndroid import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure @Suppress("unused") -abstract class FlixclusiveProvider : Plugin { +class FlixclusiveProviderLibraryPlugin : Plugin { override fun apply(project: Project) { - project.extensions.create("flxProvider", FlixclusiveProviderExtension::class.java, project) + with(project) { + with(pluginManager) { + apply("com.android.library") + } + + extensions.create(FLX_PROVIDER_EXTENSION_NAME, FlixclusiveProviderExtension::class.java, project) + + + extensions.configure { + configureAndroid(commonExtension = this@configure) + } + } registerTasks(project) registerConfigurations(project) diff --git a/src/main/kotlin/com/flixclusive/gradle/FlixclusiveProviderExtension.kt b/src/main/kotlin/com/flixclusive/gradle/FlixclusiveProviderExtension.kt index 907c352..ebfc712 100644 --- a/src/main/kotlin/com/flixclusive/gradle/FlixclusiveProviderExtension.kt +++ b/src/main/kotlin/com/flixclusive/gradle/FlixclusiveProviderExtension.kt @@ -30,6 +30,8 @@ import javax.inject.Inject internal const val APK_STUBS_DEPRECATED_MESSAGE = "This class is deprecated. See https://github.com/flixclusiveorg/core-stubs for more details." +const val FLX_PROVIDER_EXTENSION_NAME = "flxProvider" + @Suppress("unused", "MemberVisibilityCanBePrivate") abstract class FlixclusiveProviderExtension @Inject constructor(val project: Project) { /** @@ -215,9 +217,9 @@ class Stubs( } fun ExtensionContainer.getFlixclusive(): FlixclusiveProviderExtension { - return getByName("flxProvider") as FlixclusiveProviderExtension + return getByName(FLX_PROVIDER_EXTENSION_NAME) as FlixclusiveProviderExtension } fun ExtensionContainer.findFlixclusive(): FlixclusiveProviderExtension? { - return findByName("flxProvider") as FlixclusiveProviderExtension? + return findByName(FLX_PROVIDER_EXTENSION_NAME) as FlixclusiveProviderExtension? } \ No newline at end of file diff --git a/src/main/kotlin/com/flixclusive/gradle/task/CompileDexTask.kt b/src/main/kotlin/com/flixclusive/gradle/task/CompileDexTask.kt index 544bd28..af69865 100644 --- a/src/main/kotlin/com/flixclusive/gradle/task/CompileDexTask.kt +++ b/src/main/kotlin/com/flixclusive/gradle/task/CompileDexTask.kt @@ -23,17 +23,20 @@ import com.android.builder.dexing.DexArchiveBuilder import com.android.builder.dexing.DexParameters import com.android.builder.dexing.r8.ClassFileProviderFactory import com.flixclusive.gradle.getFlixclusive +import com.flixclusive.gradle.util.findProviderClassName import com.google.common.io.Closer import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.* -import org.objectweb.asm.ClassReader -import org.objectweb.asm.tree.ClassNode +import org.gradle.api.tasks.IgnoreEmptyDirectories +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.SkipWhenEmpty +import org.gradle.api.tasks.TaskAction import org.slf4j.LoggerFactory import java.io.File import java.nio.file.Path -import java.util.* +import java.util.Arrays import java.util.stream.Collectors abstract class CompileDexTask : DefaultTask() { @@ -90,30 +93,10 @@ abstract class CompileDexTask : DefaultTask() { dexOutput = dexOutputDir.toPath() ) - for (file in files) { - val reader = ClassReader(file.readAllBytes()) - - val classNode = ClassNode() - reader.accept(classNode, 0) - - for (annotation in classNode.visibleAnnotations.orEmpty() + classNode.invisibleAnnotations.orEmpty()) { - if (annotation.desc == "Lcom/flixclusive/provider/FlixclusiveProvider;") { - val flixclusive = project.extensions.getFlixclusive() - - require(flixclusive.providerClassName == null) { - "Only 1 active provider class per project is supported" - } - - for (method in classNode.methods) { - if (method.name == "getManifest" && method.desc == "()Lcom/flixclusive/provider/ProviderManifest;") { - throw IllegalArgumentException("Provider class cannot override getManifest, use manifest.json system!") - } - } - - flixclusive.providerClassName = classNode.name.replace('/', '.') - .also { providerClassFile.asFile.orNull?.writeText(it) } - } - } + val className = project.findProviderClassName(files = files) + if (className != null) { + extensions.getFlixclusive().providerClassName = className + providerClassFile.asFile.orNull?.writeText(className) } } } diff --git a/src/main/kotlin/com/flixclusive/gradle/task/Tasks.kt b/src/main/kotlin/com/flixclusive/gradle/task/Tasks.kt index 29b73c8..fb10182 100644 --- a/src/main/kotlin/com/flixclusive/gradle/task/Tasks.kt +++ b/src/main/kotlin/com/flixclusive/gradle/task/Tasks.kt @@ -17,17 +17,25 @@ package com.flixclusive.gradle.task import com.android.build.gradle.BaseExtension import com.android.build.gradle.tasks.ProcessLibraryManifest +import com.android.builder.dexing.ClassFileInputs +import com.flixclusive.gradle.FLX_PROVIDER_EXTENSION_NAME import com.flixclusive.gradle.FlixclusiveProviderExtension import com.flixclusive.gradle.getFlixclusive +import com.flixclusive.gradle.util.AndroidProjectType import com.flixclusive.gradle.util.createProviderManifest +import com.flixclusive.gradle.util.findProviderClassName +import com.flixclusive.gradle.util.getAndroidProjectType import com.flixclusive.gradle.util.isValidFilename import groovy.json.JsonBuilder import groovy.json.JsonGenerator import org.gradle.api.Project +import org.gradle.api.file.DuplicatesStrategy import org.gradle.api.tasks.AbstractCopyTask import org.gradle.api.tasks.bundling.Zip import org.gradle.api.tasks.compile.AbstractCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import java.util.Arrays +import java.util.stream.Collectors const val TASK_GROUP = "flixclusive" @@ -93,27 +101,87 @@ fun registerTasks(project: Project) { } } + val androidManifestPath = project.file("src/main/AndroidManifest.xml") + val generateAndroidManifest = project.tasks.register("generateAndroidManifest") { + androidManifestPath.writeText(""" + + + + + + + + + """.trimIndent()) + } + project.afterEvaluate { val make = project.tasks.register("make", Zip::class.java) { group = TASK_GROUP - val compileDexTask = compileDex.get() - dependsOn(compileDexTask) - val manifestFile = intermediates.resolve("manifest.json") + val generateAndroidManifestTask = generateAndroidManifest.get() + + val compileKotlinTask = project.tasks.findByName("compileDebugKotlin") as KotlinCompile? + + when (project.getAndroidProjectType()) { + AndroidProjectType.APPLICATION -> { + dependsOn(generateAndroidManifestTask) + + val assembleTask = project.tasks.getByName("assembleDebug") + dependsOn(assembleTask) + + val apkFile = project.file("build/outputs/apk/debug/${project.name}-debug.apk") + from(project.zipTree(apkFile)) { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + } + } + AndroidProjectType.LIBRARY -> { + val compileDexTask = compileDex.get() + dependsOn(compileDexTask) + + from(compileDexTask.outputFile) + + if (extension.requiresResources.get()) { + dependsOn(compileResources.get()) + } + } + AndroidProjectType.UNKNOWN -> throw IllegalStateException("Non-android providers are not supported YET!") + } - from(manifestFile) doFirst { + val manifestFile = intermediates.resolve("manifest.json") + from(manifestFile) + val (versionCode, _) = extension.getVersionDetails() require(versionCode > 0L) { "No version is set" } - if (extension.providerClassName == null) { - if (providerClassFile.exists()) { - extension.providerClassName = providerClassFile.readText() - } + if (project.getAndroidProjectType() == AndroidProjectType.APPLICATION && compileKotlinTask != null) { + val fileStreams = compileKotlinTask.destinationDirectory.asFile.get().listFiles() + ?.map { input -> + ClassFileInputs.fromPath(input.toPath()) + .use { it.entries { _, _ -> true } } + }?.toTypedArray() + + logger.lifecycle("Finding annotated provider class...") + Arrays.stream(fileStreams).flatMap { it } + .use { classesInput -> + val files = classesInput.collect(Collectors.toList()) + val className = findProviderClassName(files) + if (className != null) { + logger.lifecycle("Annotated provider class: $className") + extension.providerClassName = className + } + } + } else if (project.getAndroidProjectType() == AndroidProjectType.LIBRARY && extension.providerClassName == null && providerClassFile.exists()) { + extension.providerClassName = providerClassFile.readText() } + require(extension.providerClassName != null) { "No provider class found, make sure your provider class is annotated with @FlixclusiveProvider" } @@ -128,14 +196,7 @@ fun registerTasks(project: Project) { ) } - from(compileDexTask.outputFile) - - if (extension.requiresResources.get()) { - dependsOn(compileResources.get()) - } - - - val flxProvider = project.extensions.getByName("flxProvider") as FlixclusiveProviderExtension + val flxProvider = project.extensions.getByName(FLX_PROVIDER_EXTENSION_NAME) as FlixclusiveProviderExtension val projectName = flxProvider.providerName.get() if (!isValidFilename(projectName)) { @@ -149,14 +210,19 @@ fun registerTasks(project: Project) { destinationDirectory.set(project.buildDir) doLast { + if (androidManifestPath.exists()) { + androidManifestPath.delete() + } + logger.lifecycle("Provider package ${projectName}.flx created at ${outputs.files.singleFile}") } } - project.rootProject.tasks.getByName("generateUpdaterJson").dependsOn(make) + project.rootProject.tasks.getByName("generateUpdaterJson") + .dependsOn(make.get()) project.tasks.register("deployWithAdb", DeployWithAdbTask::class.java) { group = TASK_GROUP - dependsOn("make") + dependsOn(make.get()) dependsOn(":generateUpdaterJson") } diff --git a/src/main/kotlin/com/flixclusive/gradle/util/AndroidProjectType.kt b/src/main/kotlin/com/flixclusive/gradle/util/AndroidProjectType.kt new file mode 100644 index 0000000..580f437 --- /dev/null +++ b/src/main/kotlin/com/flixclusive/gradle/util/AndroidProjectType.kt @@ -0,0 +1,17 @@ +package com.flixclusive.gradle.util + +import org.gradle.api.Project + +enum class AndroidProjectType { + APPLICATION, + LIBRARY, + UNKNOWN +} + +fun Project.getAndroidProjectType(): AndroidProjectType { + return when { + plugins.hasPlugin("com.android.application") -> AndroidProjectType.APPLICATION + plugins.hasPlugin("com.android.library") -> AndroidProjectType.LIBRARY + else -> AndroidProjectType.UNKNOWN + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/flixclusive/gradle/util/KotlinAndroid.kt b/src/main/kotlin/com/flixclusive/gradle/util/KotlinAndroid.kt new file mode 100644 index 0000000..e5cd219 --- /dev/null +++ b/src/main/kotlin/com/flixclusive/gradle/util/KotlinAndroid.kt @@ -0,0 +1,47 @@ +package com.flixclusive.gradle.util + +import com.android.build.api.dsl.ApplicationExtension +import com.android.build.api.dsl.CommonExtension +import org.gradle.api.JavaVersion +import org.gradle.api.Project +import org.gradle.kotlin.dsl.withType + +fun Project.configureAndroid( + commonExtension: CommonExtension<*, *, *, *, *>, +) { + commonExtension.apply { + compileSdk = 34 + + defaultConfig { + minSdk = 21 + + vectorDrawables { + useSupportLibrary = true + } + } + + if (this@apply is ApplicationExtension) { + defaultConfig.versionCode = 1 + defaultConfig.versionName = "provider_apk_v1" + } + + buildFeatures.buildConfig = true + + compileOptions { + isCoreLibraryDesugaringEnabled = true + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + tasks.withType().configureEach { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() // Required + } + } + + packaging { + resources.excludes.add("META-INF/*") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/flixclusive/gradle/util/ProjectHelper.kt b/src/main/kotlin/com/flixclusive/gradle/util/ProjectHelper.kt index 7734f9e..a5b23ac 100644 --- a/src/main/kotlin/com/flixclusive/gradle/util/ProjectHelper.kt +++ b/src/main/kotlin/com/flixclusive/gradle/util/ProjectHelper.kt @@ -1,11 +1,14 @@ package com.flixclusive.gradle.util +import com.android.builder.dexing.ClassFileEntry import com.flixclusive.model.provider.Language import com.flixclusive.model.provider.ProviderData import com.flixclusive.model.provider.ProviderManifest import com.flixclusive.model.provider.ProviderType import com.flixclusive.gradle.getFlixclusive import org.gradle.api.Project +import org.objectweb.asm.ClassReader +import org.objectweb.asm.tree.ClassNode fun Project.createProviderManifest(): ProviderManifest { val extension = this.extensions.getFlixclusive() @@ -44,4 +47,33 @@ fun Project.createProviderData(): ProviderData { providerType = extension.providerType.getOrElse(ProviderType(type = "Unknown")), changelog = extension.changelog.orNull ) +} + +fun Project.findProviderClassName(files: List): String? { + for (file in files) { + val reader = ClassReader(file.readAllBytes()) + + val classNode = ClassNode() + reader.accept(classNode, 0) + + for (annotation in classNode.visibleAnnotations.orEmpty() + classNode.invisibleAnnotations.orEmpty()) { + if (annotation.desc == "Lcom/flixclusive/provider/FlixclusiveProvider;") { + val flixclusive = project.extensions.getFlixclusive() + + require(flixclusive.providerClassName == null) { + "Only 1 active provider class per project is supported" + } + + for (method in classNode.methods) { + if (method.name == "getManifest" && method.desc == "()Lcom/flixclusive/provider/ProviderManifest;") { + throw IllegalArgumentException("Provider class cannot override getManifest, use manifest.json system!") + } + } + + return classNode.name.replace('/', '.') + } + } + } + + return null } \ No newline at end of file