Archie G. Quiñones
Archie G. Quiñones

Reputation: 13668

How to change the generated filename for App Bundles with Gradle?

So to change the generated APK filename inside gradle android I could do something like:

applicationVariants.output.all {
    outputFileName = "the_file_name_that_i_want.apk"
}

Is there a similar thing for the generated App Bundle file? How can I change the generated App Bundle filename?

Upvotes: 66

Views: 35909

Answers (8)

Martin Zeitler
Martin Zeitler

Reputation: 76669

For the sake of simplicity, one should better use the base plugin.
This Kotlin script reads String from gradle/libs.versions.toml:

[versions]
app_versionName = "1.0.0"

And applies it to all the output packages, no matter if APK or AAB ...

base {
    val versionName: String = libs.versions.app.versionName.get()
    archivesName = "SomeApp_$versionName"
}
android { ... }

Which basically means, that it is not required to rename anything.
This also works fine with build-types and product-flavors, by default.
One can avoid some moving, when changing the value of buildDir.


However, one can as well move and rename files, in case this should be required. I've wrote an Exec task in Groovy for cross-platform CLI execution, no matter what the commandLine is. My RenameTask can detect Linux, Mac & Windows, as well as release & debug.

Property archivesBaseName needs to be defined in defaultConfig:

android {
    defaultConfig {
        setProperty("archivesBaseName", "SomeApp_" + "1.0.0")
    }
}

RenameTask extends Exec performs the renaming (not to be confused with type: Rename):

import javax.inject.Inject

/**
 * App Bundle RenameTask
 * @author Martin Zeitler
**/
class RenameTask extends Exec {
    private String buildType
    @Inject RenameTask(String value) {this.setBuildType(value)}
    @Input String getBuildType() {return this.buildType}
    void setBuildType(String value) {this.buildType = value}
    @Override
    @TaskAction
    void exec() {
        def baseName = getProject().getProperty('archivesBaseName')
        def basePath = getProject().getProjectDir().getAbsolutePath()
        def bundlePath = "${basePath}/build/outputs/bundle/${this.getBuildType()}"
        def srcFile = "${bundlePath}/${baseName}-${this.getBuildType()}.aab"
        def dstFile = "${bundlePath}/${baseName}.aab"
        def os = org.gradle.internal.os.OperatingSystem.current()
        if (os.isUnix() || os.isLinux() || os.isMacOsX()) {
            commandLine "mv -v ${srcFile} ${dstFile}".split(" ")
        } else if (os.isWindows()) {
            commandLine "ren ${srcFile} ${dstFile}".split(" ")
        } else {
            throw new GradleException("Cannot move AAB with ${os.getName()}.")
        }
        super.exec()
    }
}

And it finalizes two other tasks:

// it defines tasks :renameBundleRelease & :renameBundleDebug
task renameBundleRelease(type: RenameTask, constructorArgs: ['release'])
task renameBundleDebug(type: RenameTask, constructorArgs: ['debug'])

// it sets finalizedBy for :bundleRelease & :bundleDebug
tasks.whenTaskAdded { task ->
    switch (task.name) {
        case 'bundleRelease': task.finalizedBy renameBundleRelease; break
        case   'bundleDebug': task.finalizedBy renameBundleDebug; break
    }
}

Upvotes: 9

TWiStErRob
TWiStErRob

Reputation: 46480

All the answers to date are using hardcoded paths and potential hacks. Here's a variant-aware solution using public APIs.

There's an API since at least AGP 4.2, but we can use the latest approach from this official recipe: https://github.com/android/gradle-recipes/tree/agp-8.4/getSingleArtifact

Based on this, we can use the following snippet to do the renaming using only Public APIs:

androidComponents.onVariants { variant ->
    val copy = tasks.register<Copy>("copy${variant.name.capitalize()}Bundle") {
        from(variant.artifacts.get(SingleArtifact.BUNDLE))
        into(project.layout.buildDirectory.dir("outputs/bundle-variant"))
        rename { "app-${variant.name}.aab" }
    }
    // TODO replace with tasks.named { it == ... }.configure { ... } in Gradle 8.6.
    tasks.configureEach { if (this.name == "bundle${variant.name.capitalize()}") finalizedBy(copy) }
}

Upvotes: 0

petrnohejl
petrnohejl

Reputation: 7759

Solution from @SaXXuM works great! Task is not necessary for renaming artifact. You can call setProperty() directly in the android {} block. I prefer to have in the file name:

  • app id
  • module name
  • version name
  • version code
  • date
  • build type

This is how I use it in my projects:

build.gradle:

apply from: "../utils.gradle"

android {
    ...
    setProperty("archivesBaseName", getArtifactName(defaultConfig))
}

utils.gradle:

ext.getArtifactName = {
    defaultConfig ->
        def date = new Date().format("yyyyMMdd")
        return defaultConfig.applicationId + "-" + project.name + "-" + defaultConfig.versionName + "-" + defaultConfig.versionCode + "-" + date
}

The result is:

com.example-app-1.2.0-10200000-20191206-release.aab

It works for both - APK and AAB.

Upvotes: 16

don11995
don11995

Reputation: 825

Why no one is using existing gradle tasks for this?

There is a gradle task with the type FinalizeBundleTask and it is called as the last step of bundle generation and it is doing two things:

  • Signing generated AAB package
  • Move and rename AAB package where was requested

All You need to do is just to change the "output" of this task to any that You want. This task contains a property finalBundleFile - full path to the final AAB package.

I'm using it something like that:

    applicationVariants.all {
        outputs.all {
            // AAB file name that You want. Falvor name also can be accessed here.
            val aabPackageName = "$App-v$versionName($versionCode).aab"
            // Get final bundle task name for this variant
            val bundleFinalizeTaskName = StringBuilder("sign").run {
                // Add each flavor dimension for this variant here
                productFlavors.forEach {
                    append(it.name.capitalizeAsciiOnly())
                }
                // Add build type of this variant
                append(buildType.name.capitalizeAsciiOnly())
                append("Bundle")
                toString()
            }
            tasks.named(bundleFinalizeTaskName, FinalizeBundleTask::class.java) {
                val file = finalBundleFile.asFile.get()
                val finalFile = File(file.parentFile, aabPackageName)
                finalBundleFile.set(finalFile)
            }
        }
    }

It works perfectly with any flavors, dimensions, and buildTypes. No any additional tasks, works with any path set for output in Toolbar -> Generate signed Bundle, a unique name can be set for any flavor.

Upvotes: 13

3c71
3c71

Reputation: 4511

Based on Martin Zeitler's answer I did this on Windows:

Please note that on my setup, .aab files are created in release folder and it deletes everything else in that folder as per this bug report.

In my app's module gradle:

apply from: "../utils.gradle"

...

tasks.whenTaskAdded { task ->
    switch (task.name) {
        case 'bundleRelease':
            task.finalizedBy renameBundle
            break
    }
}

And in utils.gradle:

task renameBundle (type: Exec) {
    def baseName = getProperty('archivesBaseName')

    def stdout = new ByteArrayOutputStream()
    def stderr = new ByteArrayOutputStream()

    commandLine "copy.bat", rootProject.getProjectDir().getAbsolutePath() + "\\release\\${baseName}-release.aab", "<MY_AAB_PATH>\\${baseName}.aab", "D:\\Android\\studio\\release"
    workingDir = rootProject.getProjectDir().getAbsolutePath()
    ignoreExitValue true
    standardOutput stdout
    errorOutput stderr

    doLast {
        if (execResult.getExitValue() == 0) {
            println ":${project.name}:${name} > ${stdout.toString()}"
        } else {
            println ":${project.name}:${name} > ${stderr.toString()}"
        }
    }
}

The copy.bat is created in project's folder and contains this:

COPY %1 %2
RMDIR /Q/S %3

Be careful with 3rd argument to make sure you don't use a folder that's important to you.

EDIT: Why a .BAT for 2 commands you might ask. If you try commandLine "copy", ... on Windows it results in "system does not recognize the command copy". Put anything, like COPY, REN, RENAME, etc, won't work.

Upvotes: 0

kabayaba
kabayaba

Reputation: 204

I've found a much better option to auto increment your app versioning and auto renaming when you generate an apk / aab. Solution as below (do remember to create "version.properties" file on your root folder:

android {
     ...
     ...
    Properties versionProps = new Properties()
    def versionPropsFile = file("${project.rootDir}/version.properties")
    versionProps.load(new FileInputStream(versionPropsFile))
    def value = 0
    def runTasks = gradle.startParameter.taskNames
    if ('assemble' in runTasks || 'assembleRelease' in runTasks) {
        value = 1
    }
    def versionMajor = 1
    def versionPatch = versionProps['VERSION_PATCH'].toInteger() + value
    def versionBuild = versionProps['VERSION_BUILD'].toInteger() + 1
    def versionNumber = versionProps['VERSION_NUMBER'].toInteger() + value
    versionProps['VERSION_PATCH'] = versionPatch.toString()
    versionProps['VERSION_BUILD'] = versionBuild.toString()
    versionProps['VERSION_NUMBER'] = versionNumber.toString()
    versionProps.store(versionPropsFile.newWriter(), null)

    defaultConfig {
    applicationId "com.your.applicationname"
    versionCode versionNumber
    versionName "${versionMajor}.${versionPatch}.${versionBuild}(${versionNumber})"
    archivesBaseName = versionName
    minSdkVersion 26
    targetSdkVersion 29
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    signingConfig signingConfigs.release
    setProperty("archivesBaseName","${applicationId}-v${versionName}")

    ...
}

Credits to this website and this post

Upvotes: 2

David Medenjak
David Medenjak

Reputation: 34532

As a more generic way to Martin Zeitlers answer the following will listen for added tasks, then insert rename tasks for any bundle* task that gets added.

Just add it to the bottom of your build.gradle file.

Note: It will add more tasks than necessary, but those tasks will be skipped since they don't match any folder. e.g. > Task :app:renameBundleDevelopmentDebugResourcesAab NO-SOURCE

tasks.whenTaskAdded { task ->
    if (task.name.startsWith("bundle")) {
        def renameTaskName = "rename${task.name.capitalize()}Aab"
        def flavor = task.name.substring("bundle".length()).uncapitalize()
        tasks.create(renameTaskName, Copy) {
            def path = "${buildDir}/outputs/bundle/${flavor}/"
            from(path)
            include "app.aab"
            destinationDir file("${buildDir}/outputs/renamedBundle/")
            rename "app.aab", "${flavor}.aab"
        }

        task.finalizedBy(renameTaskName)
    }
}

Upvotes: 31

SaXXuM
SaXXuM

Reputation: 1029

You could use something like this:

defaultConfig {
  applicationId "com.test.app"
  versionCode 1
  versionName "1.0"
  setProperty("archivesBaseName", applicationId + "-v" + versionCode + "(" + versionName + ")")
}

Upvotes: 92

Related Questions