Skip to content
This repository has been archived by the owner on Aug 19, 2020. It is now read-only.

Using apply(from...) partial files android #1287

Closed
mtrakal opened this issue Dec 7, 2018 · 38 comments
Closed

Using apply(from...) partial files android #1287

mtrakal opened this issue Dec 7, 2018 · 38 comments

Comments

@mtrakal
Copy link

mtrakal commented Dec 7, 2018

Main file, which use apply from

build.gradle.kts

plugins {
    id("com.android.library")
    kotlin("android")
    kotlin("android.extensions")
    kotlin("kapt")
}
apply {
    from("$rootDir/config/include.gradle.kts")
}
android {
...
}

second file which includes single files for apply
include.gradle.kts

apply {
    from("$rootDir/config/exclude.gradle.kts")
    from("$rootDir/config/default.gradle.kts")
    from("$rootDir/config/testing/testing.gradle")
}

and in exclude.gradle.kts I have:

android {
  packagingOptions {
        exclude ("META-INF/DEPENDENCIES.TXT")
        exclude ("META-INF/LICENSE.TXT")
}}

But it failed on:

Script compilation errors:

  Line 01: android {
           ^ Unresolved reference: android

  Line 02:   packagingOptions {
             ^ Unresolved reference: packagingOptions

  Line 03:         exclude ("META-INF/DEPENDENCIES.TXT")
                   ^ Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: 
                       public fun Configuration.exclude(group: String? = ..., module: String? = ...): Configuration defined in org.gradle.kotlin.dsl

When I didn't use KTS file, but .gradle and Groovy and proper syntax, it works as expected. All excludes from the file are "included" into main build.gradle.kts into android and work as expected without any error.

Expected Behavior

Work same as when use Groovy

Current Behavior

Failed on: Unresolved reference: android

Your Environment

------------------------------------------------------------
Gradle 5.0
------------------------------------------------------------

Build time:   2018-11-26 11:48:43 UTC
Revision:     7fc6e5abf2fc5fe0824aec8a0f5462664dbcd987

Kotlin DSL:   1.0.4
Kotlin:       1.3.10
Groovy:       2.5.4
Ant:          Apache Ant(TM) version 1.9.13 compiled on July 10 2018
JVM:          1.8.0_181 (Oracle Corporation 25.181-b13)
OS:           Windows 10 10.0 amd64

Android Studio 3.4 Canary 7
Build #AI-183.4284.148.34.5159543, built on December 3, 2018
JRE: 1.8.0_152-release-1248-b01 amd64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Windows 10 10.0

kotlin: 1.3.11
using buildSrc
@JLLeitschuh
Copy link
Contributor

The accessors aren't generated for script files that aren't the project script file.

@gkylafas
Copy link

Yes, I have the same experience with a multi-module project. I have put some common android {} declarations into a common-android.gradle file and apply it from each module's build.gradle.kts file. Its contents are:

android {
    compileSdkVersion 28
    buildToolsVersion "28.0.3"

    defaultConfig {
        minSdkVersion    15
        targetSdkVersion 28

        multiDexEnabled true
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

Unfortunately, I cannot convert this common file to Kotlin or I get the same Unresolved reference: android error as @mtrakal. On the other hand, if I keep the file to Groovy and apply it as such, it works correctly.

@JLLeitschuh
Copy link
Contributor

You can declare the extension manually in your exclude.gradle.kts.

Full disclosure, I'm not an android developer.
However, if you put this logic in your build.gradle.kts file:

android {
  packagingOptions {
        exclude ("META-INF/DEPENDENCIES.TXT")
        exclude ("META-INF/LICENSE.TXT")
}}

From there, you should be able to jump to the declaration of the android variable.
It should look something like this:

/**
 * Configures the [android][[some long import here].AndroidExtension] project extension.
 */
fun Project.android(configure: [some long import here].AndroidExtension.() -> Unit) =
    extensions.configure("checkstyle", configure)

You can copy and paste that logic into the bottom of your exclude.gradle.kts and everything should work from there.

@mtrakal
Copy link
Author

mtrakal commented Dec 12, 2018

When we put everything from exclude.gradle.kts / common-android.gradle.kts to build.gradle.kts we don't need these files (exclude.gradle.kts / common-android.gradle.kts).
The reason, why we move some common parts to a single file is, that we use the same code in all modules...

like:
/mobile/build.gradle.kts contains apply(from("exclude.gradle.kts"))
/wear/build.gradle.kts contains apply(from("exclude.gradle.kts"))
/common/build.gradle.kts contains apply(from("exclude.gradle.kts"))
/sub-mobule1/build.gradle.kts contains apply(from("exclude.gradle.kts"))
/sub-mobule2/build.gradle.kts contains apply(from("exclude.gradle.kts"))
etc...

All modules use the same code and we don't want to edit on all submodules, so we use one file, which is (shared) applied in all modules.

In this files are just part of android code, every build.gradle.kts contains different parts inside and shared part from exclude.gradle.kts / common-android.gradle.kts

@fuzzyweapon
Copy link

@mtrakal - please check out this documentation. When you are working in an applied script (aka a script plugin), type-safe accessors are not yet available (for standard built-ins and for applied plugins).

The way kotlin-dsl makes these available (android { }, etc) is by generating them and including them on the main build script's classpath automatically. For plugins applied through the plugins block, it also generates accessors so that they can be used just-in-time (I'm probably over-simplifying).

So when you are not in a main build script (settings, init, applied) or when you apply a plugin outside of the plugins block (i.e. apply()), you will not have access to type-safe accessors of the DSL.

What @JLLeitschuh is suggesting is a way to manually pull in the code for the accessors you want.
You do this by putting some sample code of what you want in the main build file (or the target code itself, temporarily), use your IDE to go to where the accessors are written/generated, and then manually paste the accessor code into the script where type-safe accessors are not automatically on the classpath.

After doing this, you will be able to run the configuration code using those accessors (aka, using the the DSL, android { }).

Note that just because you pull in an accessor for android { }, it will not automatically give you access to all the parts configurable in android. In the case of the exclude.gradle.kts script, you would need to pull in the accessors for both android { } and android.packingOptions { } (using the same method).

@matejdro
Copy link

matejdro commented Jan 21, 2019

One problem I see with this is that when iterating through all subprojects, top script has no way of statically knowing of what type of project is each project in extensions. There is different extension class for library projects and app projects for example, but both are named android.

@mkobit
Copy link
Contributor

mkobit commented Jan 23, 2019

Also related to #427

@kiratheone
Copy link

I've tried what @JLLeitschuh suggest but didn't work

val Project.`android`: AppExtension
    get() =
    (this as ExtensionAware).extensions.getByName("android") as AppExtension

fun Project.`android`(configure: AppExtension.() -> Unit): Unit =
    (this as ExtensionAware).extensions.configure("android", configure)

@JLLeitschuh
Copy link
Contributor

@kiratheone A bit more information would be useful.

@Smouking
Copy link

Hellow!
Use Gradle 5.2.1 and simple scripts:
build.gradle.kts

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm") version "1.3.11"
}

group = "com.smouking"
version = "1.0-SNAPSHOT"

repositories {
    jcenter()
}

dependencies {
    compile(kotlin("stdlib-jdk8"))
}

tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = "1.8"
}

apply(from ="ktlint.gradle.kts")

ktlint.gradle.kts

import org.jlleitschuh.gradle.ktlint.KtlintPlugin

buildscript {
    repositories {
        gradlePluginPortal()
    }

    dependencies {
        //classpath(kotlin("gradle-plugin", version = "1.3.21"))
        classpath("org.jlleitschuh.gradle:ktlint-gradle:7.1.0")
    }
}
//The plugins {} block must not be used here

apply<KtlintPlugin>()

It failed on:

* Exception is:
java.lang.NoClassDefFoundError: org/jetbrains/kotlin/gradle/plugin/KotlinSourceSet

If uncomment classpath(kotlin("gradle-plugin", version = "1.3.21"))
It failed on:

* Exception is:
Caused by: java.lang.IllegalStateException: Could not find any convention object of type KotlinSourceSet.

because KotlinSourceSet. loaded diferent Classloader.

How to fix it?

@matejdro
Copy link

This has nothing to do with the issue at hand...

@Smouking
Copy link

Why not? I can not use apply(from=) for configure my project.
Should I do another issue?

@BulatMukhutdinov
Copy link

@JLLeitschuh my external gradle.kts file

apply(plugin = "com.android.application")

/**
 * Configures the [android][com.android.build.gradle.internal.dsl.BaseAppModuleExtension] extension.
 */
fun org.gradle.api.Project.`android`(configure: com.android.build.gradle.internal.dsl.BaseAppModuleExtension.() -> Unit): Unit =
        (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("android", configure)

I get an error

Script compilation errors:

  Line 11: fun org.gradle.api.Project.`android`(configure: com.android.build.gradle.internal.dsl.BaseAppModuleExtension.() -> Unit): Unit =
                                                               ^ Unresolved reference: android

  Line 12:         (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("android", configure)
                                                                              ^ None of the following functions can be called with the arguments supplied: 
                                                                                  public abstract fun <T : Any!> configure(p0: Class<TypeVariable(T)!>!, p1: Action<in TypeVariable(T)!>!): Unit defined in org.gradle.api.plugins.ExtensionContainer
                                                                                  @Incubating public abstract fun <T : Any!> configure(p0: String!, p1: Action<in TypeVariable(T)!>!): Unit defined in org.gradle.api.plugins.ExtensionContainer
                                                                                  public abstract fun <T : Any!> configure(p0: TypeOf<TypeVariable(T)!>!, p1: Action<in TypeVariable(T)!>!): Unit defined in org.gradle.api.plugins.ExtensionContainer

2 errors

@nicusorflorin
Copy link

any update on this? when can we expect a proper fix for the accessors?

@eskatos
Copy link
Member

eskatos commented Apr 24, 2019

As @JLLeitschuh, @fuzzyweapon mentioned, android {} isn't available in the applied script because limitations documented at https://docs.gradle.org/current/userguide/kotlin_dsl.html#type-safe-accessors

The issue pointed by @BulatMukhutdinov above is due to applied script plugins being evaluated in an isolated classloader. In other words they don't share the classpath of the plugins applied to the project build script, hence they cannot see com.android.* in the example. This is also a current limitation. FWIW, Groovy scripts are subject to the same limitation, but Groovy then rely on dynamic reflection for the better or worse.

If you'd use Kotlin DSL precompiled script plugins in buildSrc instead, then you'll get the type safe accessors and no classpath issues, see https://docs.gradle.org/current/userguide/kotlin_dsl.html#kotdsl:precompiled_plugins

@BOMBeR369
Copy link

BOMBeR369 commented Apr 24, 2019

Thanks for the answer @eskatos

Tried to use precompiled script, but with no luck
buildSrc/src/main/kotlin/common-feature-android-library.gradle.kts:

plugins {
    id("com.android.library")
    kotlin("android")
    kotlin("kapt")
    kotlin("android.extensions")
}

android {
    compileSdkVersion(Versions.compileSdk)

    defaultConfig {
        minSdkVersion(Versions.minSdk)
        targetSdkVersion(Versions.targetSdk)
        versionCode = Releases.versionCode
        versionName = Releases.versionName

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }

    compileOptions {
        sourceCompatibility = Versions.sourceCompat
        targetCompatibility = Versions.targetCompat
    }
}

dependencies {
    implementation(project(":core"))

    kapt(Google.daggerCompiler)

    testImplementation("junit:junit:4.12")
    androidTestImplementation("androidx.test.ext:junit:1.1.0")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.1.1")
}

Am i doing something wrong? Got the same error Unresolved reference: android, compileVersionSdk, defaultConfig, etc
According to documentation (https://docs.gradle.org/current/userguide/kotlin_dsl.html#kotdsl:precompiled_plugins) it got to work

@eskatos
Copy link
Member

eskatos commented Apr 24, 2019

What version of Gradle are you using?
I forgot to mention that type-safe accessors were added to precompiled script plugins in Gradle 5.3 https://docs.gradle.org/5.3/release-notes.html#kotlin-dsl

@BOMBeR369
Copy link

Gradle version 5.3.1

@eskatos
Copy link
Member

eskatos commented Apr 24, 2019

@BOMBeR369, I can't see anything obvious in the snippet above, maybe you miss the kotlin-dsl plugin applied in buildSrc/build.gradle.kts? Please use the forums for support questions https://discuss.gradle.org/

@eskatos eskatos closed this as completed Apr 25, 2019
@nicusorflorin
Copy link

@eskatos it still doesn't work for me either, same unresolved reference errors

@MichalDanielDobrzanski
Copy link

Same for me, unresolved reference errors

@bartekpacia
Copy link

I have the same, can't make it work. Annoying that there's so little documentation about it.

@ibcurly
Copy link

ibcurly commented Jun 2, 2019

Same.

@sunny0092
Copy link

Same

1 similar comment
@goutham106
Copy link

Same

@eskatos
Copy link
Member

eskatos commented Jun 25, 2019

The solution described above works for me.
If you can't make it work, please open another issue with details and a reproducer project so we can help you.

@JavierSegoviaCordoba
Copy link

@eskatos can you publish a demo with Android? I can't solve this problem too...

@personshelldon
Copy link

Ok, the same problem here. Is there any demo how to resolve this problem?

@personshelldon
Copy link

Found the solution that worked for me. The only thing I need to do is to add android-gradle-plugin into buildSrc/build.gradle.kts. My buildSrc/build.gradle.kts (for example):

buildscript {

    repositories {
        jcenter()
        google()
    }

    dependencies {
        classpath("com.android.tools.build:gradle:3.5.0")
    }
}

plugins {
    `kotlin-dsl`
}

allprojects {
    repositories {
        jcenter()
        google()
    }
}

gradlePlugin {
    plugins {
        register("symbols-plugin") {
            id = "symbols"
            implementationClass = "com.don11995.build.Symbols"
        }
    }
}

kotlinDslPluginOptions {
    experimentalWarning.set(false)
}

dependencies {
    implementation("com.android.tools.build:gradle:3.5.0")
}

@Teja-Konjeti
Copy link

@personshelldon @eskatos This isn't working in the same scenario..

I changed my build script of buildSrc to the one @personshelldon suggested and tried moving the file with the android config closure into the buildSrc folder.. It still doesn't work..

Can you please give more instructions? A lot of us have trouble converting our groovy gradle scripts to kotlin

@Teja-Konjeti
Copy link

I want to have a common file for com.android.application and com.android.library, so I can't have either plugin in the plugins declaration closure. Things work and build as a plugin if I have any id in the plugins closure while keeping the common gradle.kts file in the buildSrc module though.

Is it possible to have kotlin in such a case?

@JavierSegoviaCordoba
Copy link

@Sai-Teja usually the app module is the application, so you can do an if else to check the name, if it is app, you use application, if not, library.

@Teja-Konjeti
Copy link

Teja-Konjeti commented Dec 7, 2019

@JavierSegoviaCordoba That is true. But I wanted to have something generic so that apps with multiple targets such as ones that have the same code base but different app modules for wear OS and store app can use.

After spending about two days on this, I feel that it is better to stick with groovy for gradle scripting. It is just more flexible in allowing to split the code among files and better support online.

I have 3 files right now.
android-app.gradle, android-lib.gradle, android-common.gradle

With kotlin scripting, I need to package these into a plugin.. which needs to have the android plugin compulsorily in the file for getting the android object. @eskatos correct me if I am wrong.

Apart from these I have some protobuf and grpc gradle files as well.. Groovy just looks simpler as of today.

@JavierSegoviaCordoba
Copy link

@Sai-Teja I have multiple kts files: for Android modules, for Kotlin only modules, etc. You should can get the same behavior with kts. Have you tried to create your own plugin?

@ghost
Copy link

ghost commented Feb 18, 2020

Same issue here. Please add sample project on how to solve this problem. Also could you reopen this issue? It's actually not solved. @eskatos

@Coronel-B
Copy link

My solution: https://stackoverflow.com/a/60482635/5279996

@android10
Copy link

Context

We want to apply specific android block using pre-compiled scripts with Kotlin DSL in order to favor re-usability and modularization.

Solution

1 - We have to compile the com.android.tools.build:gradle:x.x.x by adding the dependency in the build.gradle.kts:

plugins {
    `kotlin-dsl`
    `kotlin-dsl-precompiled-script-plugins`
}

repositories {
    jcenter()
    google()
}

dependencies {
    implementation("com.android.tools.build:gradle:4.0.0")
}

2 - In our script file, add the plugin com.android.application or com.android.library and do not apply it: apply false. Here is an example, in my case called variants.gradle.kts.

package scripts

plugins { id("com.android.application") apply false }

android {
    buildTypes {
        getByName("debug") {
            isMinifyEnabled = false
            applicationIdSuffix = ".debug"
            isDebuggable = true
        }
        getByName("release") {
            isMinifyEnabled = true
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), 
           "proguard-rules.pro")
        }
    }

    flavorDimensions("version")
    productFlavors {
        create("dev") {
            dimension = "version"
            applicationIdSuffix = ".dev"
            versionNameSuffix = "-dev"
        }
        create("internal") {
            dimension = "version"
            applicationIdSuffix = ".internal"
            versionNameSuffix = "-internal"
        }
        create("public") {
            dimension = "version"
        }
    }
}

3 - Apply the pre-compiled script plugin in your android application/module:

plugins {
    // Application Specific plugins
    id(BuildPlugins.androidApplication)
    id(BuildPlugins.kotlinAndroid)
    id(BuildPlugins.kotlinAndroidExtensions)

    // Internal Script plugins
    id(ScriptPlugins.variants)
}

I hope that helps.
Cheers!

@Teja-Konjeti
Copy link

@android10 Is the first script inside buildSrc?

And I am assuming BuildPlugins.androidApplication is not auto-generated so, I am using strings like "com.android.application" instead.

I am still getting a "Unresolved reference: android" inside my "android-common.gradle.kts"

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests