How to access variant.outputFileName in Kotlin
Asked Answered
P

6

53

We've been using a snippet like this one to rename the APK file generated by our Gradle build:

android.applicationVariants.all { variant ->
    variant.outputs.all {
        outputFileName = "${variant.name}-${variant.versionName}.apk"
    }
}

Source: https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration#variant_output

I am now in the process of converting my build.gradle to build.gradle.kts, i. e. to the Gradle Kotlin DSL. This is one of the last missing pieces: I can't figure out how to access outputFileName.

According to the API docs it does not even seem to exist:

  • BaseVariant.getOutputs() returns a DomainObjectCollection<BaseVariantOutput> which provides the all method used in the snippet.
  • BaseVariantOutput extends OutputFile which extends VariantOutput but none of these has an outputFileName or any getters or setters of a matching name.

So, I suspect there is some advanced Groovy magic at work to make this work - but how do I get there in Kotlin?

Planer answered 11/6, 2018 at 7:28 Comment(2)
To the downvoter: Care to explain?Planer
Any update for build.gradle.kts?Moyra
C
62

A little simplified version of @david.mihola answer:

android {

    /**
    * Notes Impl: Use DomainObjectCollection#all
    */
    applicationVariants.all {
        val variant = this
        variant.outputs
            .map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
            .forEach { output ->
                val outputFileName = "YourAppName - ${variant.baseName} - ${variant.versionName} ${variant.versionCode}.apk"
                println("OutputFileName: $outputFileName")
                output.outputFileName = outputFileName
            }
    }

}
Compunction answered 20/9, 2019 at 23:4 Comment(6)
Could you please explain this synthax/sample and why do we still need to use BaseVariantOutputImpl ?Moorer
because we need to casting it, so we can access the setOuputFileName @Moorer cmiiwUnprincipled
@mochdawi So is this due to some serious deficiency in Kotlin DSL?Moorer
From my experience, applicationVariants.forEach doesn't work (collection is empty), and you have to use applicationVariants.all{}. But not Kotlin's extension Iterable.all(), but DomainObjectCollection.all().Bailable
Instead of .map{..}.forEach{..} I'm using .all { val output = this as BaseVariantOutputImpl; output.outputFileName = "..." }Preferable
unfortunately this doesn't work with split apksTandratandy
P
33

Browsing through the source code of the Android Gradle plugin, I think I found the answer - here we go:

We are actually dealing with objects of type BaseVariantOutputImpl and this class does have both these methods:

public String getOutputFileName() {
    return apkData.getOutputFileName();
}

public void setOutputFileName(String outputFileName) {
    if (new File(outputFileName).isAbsolute()) {
        throw new GradleException("Absolute path are not supported when setting " +
                    "an output file name");
    }
    apkData.setOutputFileName(outputFileName);
}

Using this knowledge we can now:

import com.android.build.gradle.internal.api.BaseVariantOutputImpl

and then cast our target objects like so:

applicationVariants.all(object : Action<ApplicationVariant> {
    override fun execute(variant: ApplicationVariant) {
        println("variant: ${variant}")
        variant.outputs.all(object : Action<BaseVariantOutput> {
            override fun execute(output: BaseVariantOutput) {

                val outputImpl = output as BaseVariantOutputImpl
                val fileName = output.outputFileName
                        .replace("-release", "-release-v${defaultConfig.versionName}-vc${defaultConfig.versionCode}-$gitHash")
                        .replace("-debug", "-debug-v${defaultConfig.versionName}-vc${defaultConfig.versionCode}-$gitHash")
                println("output file name: ${fileName}")
                outputImpl.outputFileName = fileName
            }
        })
    }
})

So, I guess: Yes, there is some Groovy magic at work, namely that Groovy's dynamic type system allows you to just access getOutputFileName and setOutputFileName (by way of the abbreviated outputImpl.outputFileName syntax, as in Kotlin) from your code, hoping they will be there at runtime, even if the compile time interfaces that you know about don't have them.

Planer answered 11/6, 2018 at 16:11 Comment(2)
Thank you very much for this detailed explained + solutions :) @PlanerUnprincipled
Note: you can call simpler applicationVariants.all{} (without creating explicit Action object), and your ApplicationVariant will be passed as receiver parameter. Make sure not to define some lambda parameter: ".all{ variant->", otherwise it will match Kotlin's extension function. Use just plain ".all{", and you will get shorter nicer code.Bailable
B
14

Shorter version using lambdas:

    applicationVariants.all{
        outputs.all {
            if(name.contains("release"))
                (this as BaseVariantOutputImpl).outputFileName = "../../apk/$name-$versionName.apk"
        }
    }

This will place APK into app/apk folder with name made of variant name and version code.
You can change the format of filename as you wish.
Important: it must be done only on release builds, because ".." in path corrupts debug build process with strange errors.

Bailable answered 25/3, 2020 at 12:42 Comment(1)
Just remove ..: outputFileName = "$name-$versionName.apk" or add ./Glassworker
R
4

For libraryVariants it is possible to change output file name without accessing internal api:

    libraryVariants.all {
        outputs.all {
            packageLibraryProvider {
                archiveFileName.set("yourlibrary-${buildType.name}.aar")
            }
        }
    }
Resht answered 17/3, 2022 at 12:24 Comment(0)
M
3

Update 04/12/23: Please proceed with caution, @Mike comment that a configuration like this might affect your project, I don't have any issues with this up to know, still renaming my APK with the configuration previously shared.

Original Answer:

For Kotlin KTS.

NOTE: This is considered a temporal solución, until a proper way to do it in KTS is released by Android team.

Working in AGP v7.1.2 it might work also in lower versions of AGP.

:app build.gradle.kts

android {

// ...

    this.buildOutputs.all {

        val variantOutputImpl = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl

        val variantName: String = variantOutputImpl.name

        val outputFileName = "custom-name-${variantName}.apk"
        
        variantOutputImpl.outputFileName = outputFileName
    }
}
Moyra answered 20/4, 2022 at 23:48 Comment(2)
For latest Android Studio in November 2023, this statement destroyed the project in the IDE. Removing the statements and resync , delete cache, etc after removing it failed. Had to delete project and rebuild from scratch.Armor
@Mike, sorry to read that :/, did you find a way to set a custom version name on new versions of Android Studio?Moyra
S
1

Here is how to access outputFileName with the new Variant API that is due to replace the old API in mid-2024:

androidComponents {
    onVariants { variant ->
        variant.outputs.forEach { output ->
            if (output is com.android.build.api.variant.impl.VariantOutputImpl) {
                output.outputFileName = "${variant.name}-${output.versionName.get()}.apk"
            }
        }
    }
}

Keep in mind that renaming APKs like this is not supported by the Android Gradle Plugin.

Adapted from an anwser to a similar question on StackOverflow.

Shuttering answered 15/4, 2024 at 11:53 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.