Access gradle Android plugin inside custom plugin written in Kotlin - android

I'm writing a custom Gradle plugin which should be able to access the configuration parameters from the Android plugin.
I can do certainly do this from a groovy plugin (which is my current code):
MyPlugin.groovy:
class MyPlugin implements Plugin<Project> {
void apply(Project project) {
project.android.applicationVariants.all { variant ->
variant.registerJavaGeneratingTask( // all the right parameters
My problem is that I don't want the plugin to be in Groovy, I want it using Kotlin.
Simply typing project.android in Kotlin does not work, as far as I understand I would need to write something similar to what's below:
MyPlugin.kt
import com.android.build.gradle.AndroidBasePlugin
class MyPlugin : Plugin<Project> {
override fun apply(target: Project) {
val android = target.plugins.findPlugin(AndroidBasePlugin::class.java)
// and call android here
My main problem is that that import import com.android. does not exist.
I tried adding lots of different dependecies on build.gradle.kts, like example implementation("com.android.tools.build:gradle:3.6.1") but nothing gives me access to it.
questions:
is that the right approach?
case:
(yes) 2. How do I import and use it?
(no) 2. What is the right approach?
tl;dr:
how to write project.android.applicationVariants.all { variant -> inside a Gradle Kotlin plugin
edit:
#tim_yates answer worked perfectly. To whom is interested, here is the final code.
build.gradle.kts:
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
jcenter()
google()
}
dependencies {
implementation("com.android.tools.build:gradle:3.6.1")
... my other dependencies
}
package-name/MyPlugin.kt:
import com.android.build.gradle.AppExtension
import com.android.build.gradle.api.ApplicationVariant
(...)
override fun apply(target: Project) {
val android = target.extensions.findByType(AppExtension::class.java)
android?.applicationVariants?.configureEach {
configureVariant(target, this)
}
private fun configureVariant(...) { // register my taks there
}

What you'll need is something like this:
project.extensions.configure<AppExtension>("android") {
it.applicationVariants.all {
it.registerJavaGeneratingTask( // all the right parameters
}
}
And you'll need the com.android.tools.build:gradle dependency as you say.
Why it's not being recognized, I'm not sure... Are you writing a standalone plugin? If so, what you have works... If it's an inline plugin in a buildSrc folder, then you'll need to add the lib to the buildSrc build

Related

Android: Add maven-publish configuration in a separate kotlin dsl script

I've written a .gradle script named publish.gradle which configures publishing {} for releasing my artifact.
Why on a separate script? I have multiple modules and by doing this every releasable module simply defines some variables.
Module build.gradle.kts:
// Module's blah blah
apply(from = "../publish.gradle")
publish.gradle:
apply plugin: 'maven-publish'
publishing {
publications {
// configure release process
}
}
I've recently decided to migrate to Gradle Kotlin DSL. However, there's an issue:
Adding publication {} like this:
plugins {
`maven-publish`
}
publication {
}
Lead to this error:
Expression 'publishing' cannot be invoked as a function. The function 'invoke()' is not found
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public val PluginDependenciesSpec.publishing: PluginDependencySpec defined in org.gradle.kotlin.ds
Which is summarized to
PluginDependenciesSpec is not present as a receiver
What is the difference?
TL; DR
I've added publishing {} config to a separate script which works when in .gradle groovy format but I can not convert to .gradle.kts kotlin format. The publishing is extension of PluginDependenciesSpec class which is not present in the script.
Here's what worked for me:
plugins {
id("maven-publish")
}
configure<PublishingExtension> {
publications.create<MavenPublication>("myPlugin") {
groupId = "com.myCompany.android"
artifactId = "MyPlugin"
version = "1.0.0"
pom.packaging = "jar"
artifact("$buildDir/libs/MyPlugin.jar")
}
repositories {
mavenLocal()
}
}
I understand where you're coming from, converting from groovy to kotlin script is not a simple one to one translation, and most of us, including myself, code by example. In other words, you just need to see a simple example and you can figure out the rest. This works great when examples are readily available. What you need to do when you don't find an example is to turn to the API document. For example, https://docs.gradle.org/current/dsl/org.gradle.api.publish.PublishingExtension.html shows you the available properties for the PublishingExtension and you can keep drilling in to see what properties you have at the next level. This is especially important when examples may be working with an older version and may no longer be applicable. I will say that it wasn't as obvious is that for accessing extensions in kotlin script, requires the configure block. That's a big difference, but I like that approach, because it makes it clearer what the extension properties are a part of. And by the way, kotlin wants double quote, single quotes are no longer acceptable.

Kotlin Native compile jar and framework

I'm building a multiplatform library for Android and iOS. My gradle file looks like this:
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.4.0'
}
repositories {
mavenCentral()
}
group 'com.example'
version '0.0.1'
apply plugin: 'maven-publish'
kotlin {
jvm()
// This is for iPhone simulator
// Switch here to iosArm64 (or iosArm32) to build library for iPhone device
ios {
binaries {
framework()
}
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib-common')
implementation("com.ionspin.kotlin:bignum:0.2.2")
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmMain {
dependencies {
implementation("com.ionspin.kotlin:bignum:0.2.2")
}
}
jvmTest {
dependencies {
implementation kotlin('test')
implementation kotlin('test-junit')
}
}
iosMain {
}
iosTest {
}
}
}
configurations {
compileClasspath
}
Im using a third party library and I'm using it like this:
fun test(value: String): Int {
return BigDecimal.parseString(value).toBigInteger().intValue()
}
The problem is when I build the .jar the bignum library isn't included, and when I use the lib in an Android project I get an exception ClassNotFoundException: Didn't find class "com.ionspin.kotlin.bignum.decimal.BigDecimal".
Is there a way to include third party libs in the .jar for Android and .framework for iOS?
JVM
So, the only way I've found to generate a Fat JAR that works like you expect is by adding two custom gradle tasks in project:build.gradle.kts of your KMP library after appling the java plugin.
plugins {
[...]
id("java")
}
[...]
kotlin {
jvm {
[...]
compilations {
val main = getByName("main")
tasks {
register<Copy>("unzip") {
group = "library"
val targetDir = File(buildDir, "3rd-libs")
project.delete(files(targetDir))
main.compileDependencyFiles.forEach {
println(it)
if (it.path.contains("com.")) {
from(zipTree(it))
into(targetDir)
}
}
}
register<Jar>("fatJar") {
group = "library"
manifest {
attributes["Implementation-Title"] = "Fat Jar"
attributes["Implementation-Version"] = archiveVersion
}
archiveBaseName.set("${project.name}-fat")
val thirdLibsDir = File(buildDir, "3rd-libs")
from(main.output.classesDirs, thirdLibsDir)
with(jar.get() as CopySpec)
}
}
tasks.getByName("fatJar").dependsOn("unzip")
}
}
[...]
}
You then must launch the fatJar gradle task that generate a .jar file with the 3rd libraries classes extracted from they corresponding jar archives.
You can customize the two custom gradle scripts even more in order to better fit your needs (here I only included com. package name starting deps).
Then in your Android app app:build.gradle file you can use it as you did or simply
implementation files('libs/KMLibraryTest001-fat-1.0-SNAPSHOT.jar')
iOS
As you ask also for the iOS part in your title (even if it's a second citizen in the main topic of your question) you need only to use api instead of implementation for your 3rd party library along with the export option of the framework.
ios() {
binaries {
framework() {
transitiveExport = true // all libraries
//export(project(":LibA")) // this library project in a trainsitive way
//export("your 3rd party lib") // this 3rd party lib in a transitive way
}
}
}
And you can find a full reference here.
If you see the Krypto library, it has
androidMain
jsMain
jvmMain
mingwX64Main
nativPosixMain
Which means 5 kind of binaries are generated to support 5 platforms
Convincingly, this explains that each platform expects its own binary
for example,
windows -- DLL file
linux -- so file
java -- JAR file
mac -- dylib file
A JAR gets loaded into JVM, but IOS does not use JVM
Separate your Utility functions which has a common logic and write gradle to target multiple platforms
If you want to start with pure multiplatform, you can try this Official Example
Or create a sub gradle module and create a library project which is common to IOS as well as Android
The possible targets are properly documented here
I have created a application which publishes the binary to local repository and re-uses in the MainActivity -- you can get the code here
modify the local.properties for android SDK location and use
gradlew assemble
to build the APK and test it yourself
open the mylib\build.gradle.kts folder and you can see the targets jvm and iosX64 , jvm is used for android
If I'm correct using api instead of implementation should fix your problem, though I didn't try it out yet on the Native part
See Api and implementation separation

Unresolved reference: Platform, in multiplatform project

I have created a kotlin shared library project (using Android Studio on Windows), the android side of things work fine, but for some reason when writing iOS specific code in Kotlin, I can't seem to import the platform libraries. I was following the instructions from here to get started.
I did put include ':Shared' in my settings.gradle and also this implementation project(':Shared') in my app build.gradle
My end goal is to have the library just have business logic that will be shared between iOS and Android. I'm just trying to get an example project running, so that I know it works.
This is how my file structure is:
My build.gradle for the Shared module:
apply plugin: 'java-library'
apply plugin: 'kotlin-multiplatform'
/*We are doing three things in the codebase below:
1. Listing out the target for the shared code. For Android, JVM target.
For iOS, target depends on the device type, i.e. simulator or a real device.
2. We have defined iOS, Android and common source sets, which will allow different
configuration for each source set.
3. We have created a task for Xcode to generate framework and add it to our iOS project.
*/
kotlin{
targets{
// //Xcode sets SDK_NAME environment variable - based on whether the
// //target device is a simulator or a real device, the preset should vary
final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \
? presets.iosArm64 : presets.iosX64
//outputKinds - FRAMEWORK would mean that the shared code would be exported as a FRAMEWORK
// EXECUTABLE - produces a standalone executable that can be used to run as an app
fromPreset(iOSTarget, 'ios'){
binaries{
framework('Shared')
}
}
//create a target for Android from presets.jvm
fromPreset(presets.jvm, 'android')
}
//we have 3 different sourceSets for common, android and iOS.
//each sourceSet can have their own set of dependencies and configurations
sourceSets{
commonMain.dependencies{
api 'org.jetbrains.kotlin:kotlin-stdlib-common'
}
androidMain.dependencies{
api 'org.jetbrains.kotlin:kotlin-stdlib'
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
iosMain{
}
}
}
configurations{
compileClasspath
}
// This task attaches native framework built from ios module to Xcode project
// Don't run this task directly,
// Xcode runs this task itself during its build process when we configure it.
// make sure all Gradle infrastructure exists (gradle.wrapper, gradlew)
//and gradlew is in executable mode (chmod +x gradlew)
task packForXCode(type: Sync) {
final File frameworkDir = new File(buildDir, "xcode-frameworks")
final String mode = project.findProperty("XCODE_CONFIGURATION")?.toUpperCase() ?: 'DEBUG'
final def framework = kotlin.targets.ios.binaries.getFramework("Shared", mode)
inputs.property "mode", mode
dependsOn framework.linkTask
from { framework.outputFile.parentFile }
into frameworkDir
doLast {
new File(frameworkDir, 'gradlew').with {
text = "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$#\n"
setExecutable(true)
}
}
}
tasks.build.dependsOn packForXCode
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
commonMain code:
//This function will be the general function declaration that will be used as
//actual in our platform specific code.
expect fun getCurrentDate() : String
//This is the common function that will be called by Android and iOS app
fun getDate():String{
return "Current Date is ${getCurrentDate()}"
}
androidMain code:
import java.util.*
actual fun getCurrentDate(): String{
return Date().toString()
}
iosMain code (this is where the issue is):
//can't get this import to work.
//import platform.Foundation.NSDate
actual fun getCurrentDate(): String{
//return NSDate().toString()
return ""
}
My android MainActivity (this works) :
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import getDate// function from our Shared Module
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<TextView>(R.id.dateLbl).text = getDate()
}
}
Any help or advice as to why the platform libraries won't work will be highly appreciated.
I just tried this on Windows, and the iOS platforms don't appear to be available, even just for importing. I don't think you'll be able to edit Apple targets on Windows. The available targets locally are:
android_arm32/
android_arm64/
android_x64/
android_x86/
linux_arm32_hfp/
linux_arm64/
linux_x64/
mingw_x64/
mingw_x86/
You can see local targets here: ~/.konan/kotlin-native-windows-1.3.72/klib/platform

How to access Android's "dynamicFeatures" property from a custom Gradle plugin in buildSrc

In my project, I'd like to generate a class that contains information about my dynamic features. Dynamic features are added this way:
// In the base module’s build.gradle file.
android {
...
// Specifies dynamic feature modules that have a dependency on
// this base module.
dynamicFeatures = [":dynamic_feature", ":dynamic_feature2"]
}
Source: https://developer.android.com/guide/app-bundle/at-install-delivery#base_feature_relationship
I've been searching for solutions since a few days now, and I didn't find much. Currently, my plugin looks like this:
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
if (project == rootProject) {
throw Exception("This plugin cannot be applied to root project")
}
val parent = project.parent ?: throw Exception("Parent of project cannot be null")
val extension = project.extensions.getByName("android") as BaseAppModuleExtension?
?: throw Exception("Android extension cannot be null")
extension.dynamicFeatures
}
}
Unfortunately, extension.dynamicFeatures is empty even if my plugin is applied to the build.gradle file having dynamic features.
It's empty, because you are trying to get extension value at the gradle lifecycle configuration stage, all gradle properties have not configured yet.
Use afterEvaluate closure. In this block dynamicFeatures has already configured and not empty.
project.afterEvaluate {
val extension = project.extensions.getByType(BaseAppModuleExtension::class.java)
?: throw Exception("Android extension cannot be null")
extension.dynamicFeatures
}

how to get Dagger2 Compiler Options working?

I'm trying to use 3 of the dagger2 compiler options in my android project.
but it seems none of them actually work.
I have pasted the code from here to my gradle.properties and even compiler options of AS settings.
the 3 that I'm interested in are:
-Adagger.fastInit=enabled
-Adagger.formatGeneratedSource=disabled
-Adagger.gradle.incremental
the fastinit and codeformatting just don't work (judging by the code that is generated) but the incremental cause a compile error saying:
no compiler option found.
the versions that I'm using are:
dagger : 2.18
gradle : 5.2.1
kotlin : 1.3.21
androidPlugin : 3.3.1
For projects with multiple modules, the top build.gradle can be updated with this
allprojects {
repositories {
...
}
afterEvaluate {
extensions.findByName('kapt')?.arguments {
arg( "dagger.formatGeneratedSource", "disabled" )
}
}
}
Perhaps you should try without "A"
dagger.fastInit=enabled
dagger.formatGeneratedSource=disabled
dagger.gradle.incremental=enabled
Also can try directly in build.gradle, but this should be done for each project.
kapt {
arguments {
arg('dagger.fastInit', 'enabled')
arg('dagger.formatGeneratedSource', 'disabled')
arg('dagger.gradle.incremental', 'enabled')
}
}

Categories

Resources