In my application, I manage all Gradle stuff in buildSrc. One of them is buildTypes. I am able to access all the properties that buildTypes provide. However, I cannot access applicationVariants in buildSrc. Is there any way to do so?
Sample code block in buildSrc/BuildTypes.kt
internal object Debug : BuildTypeCreator {
override val name = InternalBuildType.DEBUG
override fun create(
namedDomainObjectContainer: NamedDomainObjectContainer<BuildType>,
project: Project,
isLibrary: Boolean,
closure: BuildType.() -> Unit
): BuildType {
return namedDomainObjectContainer.maybeCreate(name).apply {
if (isLibrary) {
versionNameSuffix = "-dev-${project.gitSha}"
} else {
applicationIdSuffix = ".dev"
}
isTestCoverageEnabled = true
isMinifyEnabled = false
isDebuggable = true
closure.invoke(this)
}
}
}
Sample code block in app.gradle.kts(which I want to achieve the same
in buildSrc)
applicationVariants.all { -> **This is not accessible in buildSrc**
if (buildType.name == getByName("release").name) {
outputs.all { output: BaseVariantOutput ->
val outputImpl = output as BaseVariantOutputImpl
outputImpl.outputFileName = "calendar-view.apk"
true
}
}
}
Thanks in advance.
OK, I solved it passing an argument domainObjectSet: DomainObjectSet<ApplicationVariant>
Related
I'm using easylaucher library Easylaucher library to add a ribbon on the app icon. I'm using kotlin DSL but when I apply the easy laucher configurations it throws EasyLauncherConfig with name 'debug' not found.
Here is my configuration
plugins {
id("kotlin-android")
id(BuildPlugins.androidApplication)
id("com.starter.easylauncher") version "5.1.2"
}
android {
//...
}
dependencies {
//...
}
easylauncher {
buildTypes{
getByName("debug") {
filters(
customRibbon(label = "Debug", ribbonColor = "#FF0000")
)
}
getByName("release") {
filters(
customRibbon(label = "BETA", ribbonColor = "#F18357")
)
}
}
}
Anyone with a good solution to this?
I found the solution. Instead of using getByName(...) I used register(...)
easylauncher {
buildTypes{
register("debug") {
filters(
customRibbon(label = "Debug", ribbonColor = "#FF0000")
)
}
register("release") {
filters(
customRibbon(label = "BETA", ribbonColor = "#F18357")
)
}
}
}
Objective :
I have developed Test for CodeCoveragePluginForAndroidConnectedTest .
Steps to reproduce error : ./gradlew clean build
A problem occurred evaluating root project 'QualityToolsTestProject'.
> Failed to apply plugin 'kotlin-android'.
class com.android.build.gradle.LibraryExtension_Decorated cannot be cast to class com.android.build.gradle.BaseExtension
(com.android.build.gradle.LibraryExtension_Decorated is in unnamed module of loader
org.gradle.internal.classloader.VisitableURLClassLoader #5a8b9853; com.android.build.gradle.BaseExtension is in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader #7685facc)
Note :
So "Failed to apply plugin 'kotlin-android' in my below test case is because of two different type of gradle implementation ?
Project with kts exension (build.gradle.kts)
Test cases with groovy(build.gradle) because in below code i am creating build.gradle for Test cases and corresponding file in vitural gradle memory using groovy.
To understand complete code are below.
Input :
TEST_PROJECT_NAME is QualityToolsTestProject in below code
package com.oujji.sdk.android.gradle.quality
import com.ing.sdk.android.gradle.quality.test.*
import org.gradle.testkit.runner.TaskOutcome
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import java.io.File
class CodeCoveragePluginForAndroidConnectedTest : BaseTest() {
private lateinit var buildFile: File
private lateinit var sourceMainDir: File
private lateinit var sourceAndroidTestDir: File
#Before
fun setup() {
testProjectDir.apply {
createGradleProperties()
createSettingsFile(projectName = TEST_PROJECT_NAME)
buildFile = createProjectFile("build.gradle") {
"""
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30"
}
}
plugins {
id 'code-coverage'
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
repositories {
google()
mavenCentral()
}
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
targetSdkVersion 29
minSdkVersion 21
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
testCoverageEnabled = true
}
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61"
androidTestImplementation "androidx.test.ext:junit:1.1.1"
androidTestImplementation "androidx.test:runner:1.2.0"
testImplementation "junit:junit:4.12"
}
"""
}
createManifestFile()
sourceMainDir = createSourceMainDirectories()
sourceAndroidTestDir = createSourceAndroidTestDirectories()
}
}
#Test
fun `when applying code coverage for android, code coverage tasks should be added`() {
runBuild("tasks") {
checkOutputContains("coverage - Generate Jacoco coverage reports on test builds.")
checkOutputContains("androidTestCoverageDebug - Generate Jacoco coverage reports on the Debug build.")
checkOutputContains("coveragePrintDebug - Print the code coverage of the Debug build using the coverageDebug report.")
checkOutputContains("coveragePrintRelease - Print the code coverage of the Release build using the coverageRelease report.")
checkOutputContains("coverageVerificationDebug - Run Jacoco coverage verification on the Debug build.")
checkOutputContains("coverageVerificationRelease - Run Jacoco coverage verification on the Release build.")
}
}
#Test
fun `when coverage below default minimum, verify task outcome should be FAILED`() {
// Setup with 40% coverage, default is 50%
buildFile {
"""
codeCoverage {
failOnBelowMinimumCoverage true
}
"""
}
"One".also { className ->
sourceMainDir.createSourceKotlinFile(className = className)
sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
}
"Two".also { className ->
sourceMainDir.createSourceJavaFile(className = className)
sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
}
sourceMainDir.createSourceJavaFile(className = "Three")
sourceMainDir.createSourceKotlinFile(className = "Four")
sourceMainDir.createSourceKotlinFile(className = "Five")
runBuildAndFail("androidTestCoverageDebug") {
assertEquals(TaskOutcome.FAILED, task(":coverageVerificationDebug")?.outcome)
checkOutputContains("Code coverage for bundle $TEST_PROJECT_NAME: 40.00%")
checkOutputContains("Rule violated for bundle $TEST_PROJECT_NAME: instructions covered ratio is 0.4, but expected minimum is 0.5")
}
}
#Test
fun `when coverage above default minimum, verify task outcome should be SUCCESS`() {
// Setup with 66% coverage, default is 50%
"One".also { className ->
sourceMainDir.createSourceJavaFile(className = className)
sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
}
"Two".also { className ->
sourceMainDir.createSourceKotlinFile(className = className)
sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
}
sourceMainDir.createSourceJavaFile(className = "Three")
runBuild("androidTestCoverageDebug") {
assertEquals(TaskOutcome.SUCCESS, task(":coverageVerificationDebug")?.outcome)
checkOutputContains("Code coverage for bundle $TEST_PROJECT_NAME: 66.67%")
}
}
#Test
fun `when coverage below configured minimum, verify task outcome should be FAILED`() {
// Setup with 66% coverage, configured to 80%
buildFile {
"""
codeCoverage {
minimumCoverage 0.8
failOnBelowMinimumCoverage true
}
"""
}
"One".also { className ->
sourceMainDir.createSourceKotlinFile(className = className)
sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
}
"Two".also { className ->
sourceMainDir.createSourceJavaFile(className = className)
sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
}
sourceMainDir.createSourceJavaFile(className = "Three")
runBuildAndFail("androidTestCoverageDebug") {
assertEquals(TaskOutcome.FAILED, task(":coverageVerificationDebug")?.outcome)
checkOutputContains("Code coverage for bundle $TEST_PROJECT_NAME: 66.67%")
}
}
#Test
fun `when coverage below configured minimum but allowed, verify task outcome should be SUCCESS with warning`() {
// Setup with 66% coverage, configured to 80%, with only warning
buildFile {
"""
codeCoverage {
minimumCoverage 0.8
failOnBelowMinimumCoverage false
}
"""
}
"One".also { className ->
sourceMainDir.createSourceKotlinFile(className = className)
sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
}
"Two".also { className ->
sourceMainDir.createSourceJavaFile(className = className)
sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
}
sourceMainDir.createSourceJavaFile(className = "Three")
runBuild("androidTestCoverageDebug") {
assertEquals(TaskOutcome.SUCCESS, task(":coverageVerificationDebug")?.outcome)
checkOutputContains("Code coverage for bundle $TEST_PROJECT_NAME: 66.67%")
checkOutputContains("Rule violated for bundle $TEST_PROJECT_NAME: instructions covered ratio is 0.6")
}
}
#Test
fun `when coverage above configured minimum, verify task outcome should be SUCCESS`() {
// Setup with 80% coverage, configured to 75%
buildFile {
"""
codeCoverage {
minimumCoverage 0.75
}
"""
}
"One".also { className ->
sourceMainDir.createSourceKotlinFile(className = className)
sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
}
"Two".also { className ->
sourceMainDir.createSourceJavaFile(className = className)
sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
}
"Three".also { className ->
sourceMainDir.createSourceJavaFile(className = className)
sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
}
"Four".also { className ->
sourceMainDir.createSourceKotlinFile(className = className)
sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
}
sourceMainDir.createSourceJavaFile(className = "Five")
runBuild("androidTestCoverageDebug") {
assertEquals(TaskOutcome.SUCCESS, task(":coverageVerificationDebug")?.outcome)
checkOutputContains("Code coverage for bundle $TEST_PROJECT_NAME: 80.00%")
}
}
#Test
fun `when coverage has configured excludes, verify task should ignore classes matching excludes`() {
// Setup with 50% coverage when excludes are disregarded, configured high but just with warning
buildFile {
"""
codeCoverage {
testExcludes = [
'**/Two*',
'**/Three*',
'**/Four*'
]
minimumCoverage 0.9
failOnBelowMinimumCoverage false
}
"""
}
"One".also { className ->
sourceMainDir.createSourceKotlinFile(className = className)
sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
}
sourceMainDir.createSourceKotlinFile(className = "Two")
sourceMainDir.createSourceJavaFile(className = "Three")
sourceMainDir.createSourceKotlinFile(className = "Four")
sourceMainDir.createSourceJavaFile(className = "Five")
runBuild("androidTestCoverageDebug") {
assertEquals(TaskOutcome.SUCCESS, task(":coverageVerificationDebug")?.outcome)
checkOutputContains("Code coverage for bundle $TEST_PROJECT_NAME: 50.00%")
checkOutputContains("Rule violated for bundle $TEST_PROJECT_NAME: instructions covered ratio is 0.5")
}
}
}
Abstract BaseTest claas
package com.oujji.sdk.android.gradle.quality.test
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.GradleRunner
import org.junit.Rule
import org.junit.rules.TemporaryFolder
abstract class BaseTest {
#Rule
#JvmField
val testProjectDir = TemporaryFolder()
private fun createBuild(arguments: List<String> = listOf()): GradleRunner = GradleRunner.create()
.withPluginClasspath()
.withProjectDir(testProjectDir.root)
.withArguments(arguments)
fun runBuild(vararg arguments: String, withResult: BuildResult.() -> (Unit) = {}): BuildResult =
createBuild(arguments.asList()).build().apply { withResult() }
fun runBuildAndFail(vararg arguments: String, withResult: BuildResult.() -> (Unit) = {}): BuildResult =
createBuild(arguments.asList()).buildAndFail().apply { withResult() }
companion object {
const val TEST_PROJECT_NAME = "QualityToolsTestProject"
}
}
GradleFiles.kt
package com.oujji.sdk.android.gradle.quality.test
import org.junit.rules.TemporaryFolder
import java.net.URL
internal fun TemporaryFolder.createGradleProperties() {
createProjectFile("gradle.properties") {
val proxyProps = System.getenv("https_proxy") ?: System.getenv("http_proxy")
?.let { if(it.startsWith("http")) it else "http://$it" }
?.let { URL(it) }
?.let {
"\nsystemProp.http.proxyHost=${it.host}\nsystemProp.http.proxyPort=${it.port}"
} ?: ""
"android.useAndroidX=true\n$proxyProps"
}
}
SetingsFile.kt
package com.oujji.sdk.android.gradle.quality.test
import org.junit.rules.TemporaryFolder
internal fun TemporaryFolder.createSettingsFile(projectName: String) = createProjectFile("settings.gradle") {
"""
rootProject.name = '${projectName}'
"""
}
ProjectFile.kt
package com.oujji.sdk.android.gradle.quality.test
import org.junit.rules.TemporaryFolder
import java.io.File
operator fun File.invoke(append: () -> String) = appendText(append().trimIndent())
fun File.createProjectFile(fileName: String, contents: () -> String) =
File(this, fileName).also { sourceFile ->
sourceFile.createNewFile()
sourceFile.writeText(contents().trimIndent())
}
fun TemporaryFolder.createProjectFile(fileName: String, contents: () -> String) =
newFile(fileName).apply { writeText(contents().trimIndent()) }
Environement :
------------------------------------------------------------
Gradle 7.1.1
------------------------------------------------------------
Build time: 2021-07-02 12:16:43 UTC
Revision: 774525a055494e0ece39f522ac7ad17498ce032c
Kotlin: 1.4.31
Groovy: 3.0.7
Ant: Apache Ant(TM) version 1.10.9 compiled on September 27 2020
JVM: 11.0.12 (Oracle Corporation 11.0.12+8-LTS-237)
OS: Mac OS X 11.5 x86_64
I had a problem that was giving me the same error message as Kumar states. I can't decern whether my problem and Kumar problem are the same, but I too was trying to manipulate a gradle build. In Kumar's cast, he seems to be trying to generate a build.gradle. In my case, I'm only trying to manipulate the "android" block of a build.gradle. So, there's at least some reason to believe that my solution might be related.
I did two things to solve the problem, but I'm not sure if both are necessary. First, I upgraded gradle from version 7.4 to 7.4.2. The release notes for 7.4.2 indicated numerous fixes and of course recommended developers to upgrade to this version. After doing that, I tried to do the build again. As a result of doing this, the error I was getting shifted. Instead of complaining about the bad cast, it now indicated that I had a problem in my AndroidManifest.xml file. My manifest file had the wrong class name in an application activity section of the manifest. I corrected the name, and the build worked fine.
For the record, I will state that I actual got displayed a different error message in my build, but by debugging my gradle build, I was able to catch the initial exception, and that exception was exactly the same as Kumar exception and that's why I say I got same error message.
I've tried converting our Android app to use the Kotlin DSL for gradle and I can't get AppDistribution to work in my CI build. This is the error I got:
App Distribution found more than 1 output file for this variant. Please contact firebase-support#google.com for help using APK splits with App Distribution.
This is what was working in groovy:
applicationVariants.all { variant ->
variant.outputs.each { output ->
tasks.findAll {
it.name.startsWith(
"appDistributionUpload${variant.name.capitalize()}")
}.each {
it.doFirst {
it.appDistributionProperties.apkPath = output.outputFile.absolutePath
}
}
}
}
I can't find a way to set appDistributionProperties.apkPath in the kotlin dsl:
applicationVariants.forEach { variant ->
variant.outputs.forEach { output ->
tasks.filter {
it.name.startsWith("appDistributionUpload${variant.name.capitalize()}")
}.forEach {
it.doFirst {
it.setProperty("apkPath", output.outputFile.absolutePath)
}
}
}
}
I'm guessing I need a magic string instead of just "apkPath", because there doesn't seem to exist a strongly typed way of saying this.
Kotlin dsl way:
android.applicationVariants.all {
this.outputs.all {
val output = this
tasks.matching {
it.name.startsWith(
"appDistributionUpload${this.name.capitalize()}"
)
}.forEach {
it.doFirst {
if (it is com.google.firebase.appdistribution.gradle.UploadDistributionTask) {
it.appDistributionProperties.get().apkPath = output.outputFile.absolutePath
}
}
}
}
}
I am trying to get https://github.com/Kotlin/kotlinx.serialization to work with my android library I am building using kotlin mpp. This is my gradle file
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.multiplatform")
kotlin("plugin.serialization") version "1.3.61"
}
group = "org.greeting"
version = 1.0
android {
compileSdkVersion(27)
defaultConfig {
minSdkVersion(15)
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}
dependencies {
// Specify Kotlin/JVM stdlib dependency.
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7")
implementation(kotlin("stdlib", org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION)) // or "stdlib-jdk8"
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0") // JVM dependency
testImplementation("junit:junit:4.12")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
androidTestImplementation("junit:junit:4.12")
androidTestImplementation("org.jetbrains.kotlin:kotlin-test")
androidTestImplementation("org.jetbrains.kotlin:kotlin-test-junit")
}
kotlin {
android("androidLib")
val buildForDevice = project.findProperty("device") as? Boolean ?: false
val iosTarget = if (buildForDevice) iosArm64("ios") else iosX64("ios")
iosTarget.binaries {
framework {
// Disable bitcode embedding for the simulator build.
if (!buildForDevice) {
embedBitcode("disable")
}
}
}
sourceSets["androidLibMain"].dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0")
}
sourceSets {
commonMain {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:0.14.0")
}
}
commonTest {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-test-common")
implementation("org.jetbrains.kotlin:kotlin-test-annotations-common")
}
}
}
}
tasks.register("copyFramework") {
val buildType = project.findProperty("kotlin.build.type") as? String ?: "DEBUG"
dependsOn("link${buildType.toLowerCase().capitalize()}FrameworkIos")
doLast {
val srcFile = (kotlin.targets["ios"] as KotlinNativeTarget).binaries.getFramework(buildType).outputFile
val targetDir = project.property("configuration.build.dir")!!
copy {
from(srcFile.parent)
into(targetDir)
include("greeting.framework/**")
include("greeting.framework.dSYM")
}
}
}
tasks.register("iosTest") {
val device = project.findProperty("iosDevice") as? String ?: "iPhone 8"
dependsOn("linkDebugTestIos")
group = JavaBasePlugin.VERIFICATION_GROUP
description = "Runs tests for target 'ios' on an iOS simulator"
doLast {
val binary = (kotlin.targets["ios"] as KotlinNativeTarget).binaries.getTest("DEBUG").outputFile
exec {
commandLine("xcrun", "simctl", "spawn", "--standalone", device, binary.absolutePath)
}
}
}
When I try to run build i get Unresolved reference: serialization on line import kotlinx.serialization.ImplicitReflectionSerializer; But if compile the androidTests the linking works fine. Strangely i have to do sourceSets["androidLibMain"] and can't include it in the sourceSet{} block not sure if this is related. Any ideas on why the build task is not linking my serialization library? Thanks
I want to split my project to multiple sourceSets, so I want use multiple AndroidManifest.xml to declare current sourceSets's component,util build apk merge the main AndroidManifest.xml, but there only support one main AndroidManifest.xml setup, so how can I do it?
Why I want to use multiple sourceSets? Because if using multiple modules it compile so slow.
This is my project settings
def moduleDirs = project.projectDir.listFiles()
.findAll {
def name = it.name
// filter all inner's module
it.isDirectory() && msExtension.modulePrefix.any { name.startsWith(it) }
}
Set<File> manifestSet = new HashSet<>()
Set<String> modules = new HashSet<>()
def sourceSetConf = { AndroidSourceSet sourceSet ->
moduleDirs.each {
def moduleName = it.name
def dir = sourceSet.name
sourceSet.assets.srcDirs "${moduleName}/src/${dir}/assets"
sourceSet.java.srcDirs "${moduleName}/src/${dir}/java"
sourceSet.res.srcDirs "${moduleName}/src/${dir}/res"
sourceSet.aidl.srcDirs "${moduleName}/src/${dir}/aidl"
// each AndroidManifest.xml
def manifestFile = project.file("${moduleName}/AndroidManifest.xml")
if (manifestFile != null && manifestFile.exists()) {
manifestSet.add(manifestFile)
}
modules.add(moduleName)
}
}
// for default main sources
sourceSetConf(android.sourceSets.main)
// for buildType sources
android.buildTypes.each {
def buildType = it.name
android.sourceSets.getByName(buildType) {
sourceSetConf(it)
}
}
// for flavor sources
android.productFlavors.each {
def flavor = it.name
android.sourceSets.getByName(flavor) {
sourceSetConf(it)
}
}
I see some code from gradle what multiple modules merge, but still no idea how to
ManifestMerger2
ProcessManifest
Use multiple modules via Android Studio. Create dependencies between modules. To make ModuleA depend on ModuleB, put the following inside build.gradle of ModuleA:
dependencies {
// ...
compile project(':ModuleB')
// ...
}
This should merge AndroiManifest.xml from both modules when buiding ModuleA
I just use ManifestMerger2 to do it.
private static void merge(File reportFile, File mainManifest, File... libraryManifests) {
if (libraryManifests == null || libraryManifests.length == 0) {
return
}
ILogger logger = new StdLogger(Level.VERBOSE)
Invoker manifestMergerInvoker = ManifestMerger2.newMerger(mainManifest, logger, MergeType.APPLICATION)
manifestMergerInvoker.setMergeReportFile(reportFile)
manifestMergerInvoker.addLibraryManifests(libraryManifests)
println "start merge..."
MergingReport mergingReport = manifestMergerInvoker.merge()
println "end merge..."
if (MergingReport.Result.SUCCESS) {
XmlDocument xmlDocument = mergingReport.getMergedXmlDocument(MergingReport.MergedManifestKind.MERGED)
try {
String annotatedDocument = mergingReport.getActions().blame(xmlDocument)
logger.verbose(annotatedDocument)
} catch (Exception e) {
logger.error(e, "cannot print resulting xml")
}
save(xmlDocument, mainManifest)
}
}
private static void save(XmlDocument xmlDocument, File out) {
try {
Files.write(xmlDocument.prettyPrint(), out, Charsets.UTF_8)
} catch (IOException e) {
throw new RuntimeException(e)
}
}