79104473

Date: 2024-10-19 08:19:51
Score: 1.5
Natty:
Report link

Update for Oct, 2024

Following Gabriel's neat answer, I wanted to automate this process as well. However, that solution wasn't working for me. After two days of trial & error, I realized that that solution had been silently failing (i.e. no errors logged) in my case because the suggested from and destinationDirectory paths were different in my (Flutter) project. So if you are facing similar problems, the following is an expanded solution that adds some checks & logs, allowing you to easier isolate and address any issues specific to your setup now or in the future.

My project setup

Note: mine is a Flutter project but the below solution should be similar in other projects.

A) Update your android/app/build.gradle file

Note: to learn about "tasks" in Gradle, check out the official docs. Also, in the below solution, as suggested by one of the comments in one of the other answers, I am also checking for (and removing) some hidden files that are generated by macOS, in case you are on a Mac. That step seems redundant when the zip is generated programmatically as it is in this solution, but I left it in as a precaution.

Add import java.nio.file.Paths to the top of the android/app/build.gradle file. Then add everything else after the flutter {...} block here:

import java.nio.file.Paths

plugins {
    ...
}

android {
    ...
}

flutter {
    source = "../.."
}

// STEP 1: Create and register the `zipNativeDebugSymbols` task to zip debug symbol files for manual upload to the Google Play store.
def zipNativeDebugSymbols = tasks.register('zipNativeDebugSymbols', Zip) {

    // Optional: this sets some info about the task (to verify, run `./gradlew app:tasks` in the /android folder to see this task listed)
    group = 'Build'
    description = 'Zips debug symbols for upload to Google Play store.'

    // Set the input source directory (this is where the native libs should be after the `bundleRelease` process has finished)
    def libDir = file('../../build/app/intermediates/merged_native_libs/release/out/lib')
    from libDir

    // Include all subfiles and directories
    include '**/*'

    // Set the name for the output ZIP file
    archiveFileName = 'native-debug-symbols.zip'

    // Set the destination directory for the output ZIP file
    def destDir = file('../../build/app/outputs/bundle/release')
    destinationDirectory = destDir

    doFirst {
        // Ensure the paths are correct for the required directories and that they indeed were created / exist (the preceding task `bundleRelease` creates these directories)
        checkDirectoryExists(libDir, 'Library directory')
        checkDirectoryExists(destDir, 'Destination directory')
    }

    doLast {
        println '✅  zipNativeDebugSymbols: created native-debug-symbols.zip file'
        // Optional: if running on macOS, clean up unwanted files like '__MACOSX' and '.DS_Store' in the now created zip file
        // The '__MACOSX' and '.DS_Store' files seem to be added only when manually creating ZIPs on macOS, not programmatically like here. Nevertheless, no harm in leaving this in as a precaution.
        if (System.properties['os.name'].toLowerCase().contains('mac')) {
            println '   running on Mac...'

            // Combine destination path and zip file name
            def zipPath = Paths.get(destinationDirectory.get().asFile.path, archiveFileName.get()).toString()

            // Ensure the zip file exists
            if (new File(zipPath).exists()) {
                println "   removing any '__MACOSX' and '.DS_Store' files from the zip..."
                checkAndRemoveUnwantedFiles(zipPath, '__MACOSX*')
                checkAndRemoveUnwantedFiles(zipPath, '*.DS_Store')
            } else {
                println '❌  zip file does not exist: $zipPath'
            }
        }
        println '✅  zipNativeDebugSymbols: finished creating & cleaning native-debug-symbols.zip'
    }

    // Optional: force the task to run even if considered up-to-date
    outputs.upToDateWhen { false }

    println '✅  zipNativeDebugSymbols: task registered and configured'
}

// STEP 2: Configure the `zipNativeDebugSymbols` task to run after `bundleRelease`
tasks.whenTaskAdded { task ->
    if (task.name == 'bundleRelease') {
        // `finalizedBy` ensures `zipNativeDebugSymbols` runs after `bundleRelease` is complete
        task.finalizedBy zipNativeDebugSymbols
    }
}

//// ----------- HELPER METHODS -------------- ////

// Helper method to check if a directory exists
def checkDirectoryExists(File dir, String description) {
    if (dir.exists()) {
        println '✅  zipNativeDebugSymbols: found ${description} ${dir}'
    } else {
        println '❌  ${description} does not exist: ${dir}'
    }
}

// Helper method to check for unwanted files and remove them
def checkAndRemoveUnwantedFiles(String zipPath, String pattern) {
    def output = new ByteArrayOutputStream()

    exec {
        commandLine 'sh', '-c', "zipinfo $zipPath | grep '$pattern'"
        standardOutput = output
        errorOutput = new ByteArrayOutputStream()
        ignoreExitValue = true
    }

    if (output.toString().trim()) {
        println "✅  zipNativeDebugSymbols: found '$pattern' in the zip. Removing it..."
        exec {
            commandLine 'sh', '-c', "zip -d $zipPath '$pattern' || true"
        }
    }
}

B) Generate files and upload to Google Play store

1. Generate the files for upload:

2. Upload to Google Play store:

Voila! That is it. Hope this saves you days of scratching your head like I did.


Helpful debugging info

In case you want to play around a little to understand Gradle and the above tasks better, here are some useful CLI commands. Note that they have to be run from your android/ directory. Also, for a Flutter project the base command here is ./gradlew but I'm guessing it is e.g. just gradlew for other projects.

Reasons:
  • Long answer (-1):
  • Has code block (-0.5):
  • Me too answer (2.5): facing similar problem
  • Low reputation (0.5):
Posted by: Camron