Gradle-play-publisher: Fail to upload signed APK

Created on 2 Jul 2019  路  15Comments  路  Source: Triple-T/gradle-play-publisher

Describe the bug

We currently experiencing an issue with uploading signed apk to the Google Play.
We use v1 signing.
This is our configuration:

play {
  serviceAccountCredentials = file("${rootProject.projectDir}/service.json")
  // specifying dir will just upload things and avoid to build apk locally
  // mapping file should be in the same folder
  artifactDir = file("${apkFolder}")
  track = 'alpha'
}

This error response that we see on our CI:

com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
  {
    "code" : 403,
    "errors" : [ {
      "domain" : "androidpublisher",
      "message" : "APK signature is invalid or does not exist. Error from apksigner: ERROR: JAR_SIG_NO_MANIFEST: Missing META-INF/MANIFEST.MF",
      "reason" : "apkNotificationMessageKeyInvalidSignature"
    } ],
    "message" : "APK signature is invalid or does not exist. Error from apksigner: ERROR: JAR_SIG_NO_MANIFEST: Missing META-INF/MANIFEST.MF"
  }

The strange thing - we can still upload file manually.

How To Reproduce

Run our CI pipeline.

Versions

  • Gradle Play Publisher: 2.2.1
  • Gradle Wrapper: 5.4.1
  • Android Gradle Plugin: 3.4.1

Tasks executed

app:publishProductionApk

Expected behavior

We can upload apk with gradle as we can do it manually.

bug apk

All 15 comments

Can you share logs of the task execution? I want to make sure that nothing else is touching the APK. And you only have that one APK that works through the console in the folder (plus an optional mapping file)?

Hey! What kind of logging do you need? Or better what concerns or assumptions do you have?

This our publish gradle file:

apply plugin: 'com.github.triplet.play'

def apkFolder = "${rootProject.projectDir}/app/build/outputs/apk/production"
def mappingFolder = "${rootProject.projectDir}/app/build/outputs/mapping/production"

play {
  serviceAccountCredentials = file("${rootProject.projectDir}/service.json")
  // specifying dir will just upload things and avoid to build apk locally
  // mapping file should be in the same folder
  artifactDir = file("${apkFolder}")
  track = 'alpha'
}

// mapping file should be in the same folder as apk
def copyMappingFilesTask = tasks.register('publishCopyMappingFiles', Copy) {
  from file("${mappingFolder}/mapping.txt")

  into "${apkFolder}"
}

// this will always overwrite file if it exists
def moveApkFilesTask = tasks.register('publishRenameApkFiles', Copy) {
  from file("${apkFolder}/app-production-signed-aligned.apk")

  into file("${apkFolder}")

  rename('app-production-signed-aligned.apk', 'app-production.apk')
}

gradle.projectsEvaluated {
  def publishApkTask = project.tasks.findByName('publishProductionApk')
  if (publishApkTask != null) {
    // we should copy mapping file and rename the signed app to match apk from AGP
    publishApkTask.dependsOn(copyMappingFilesTask, moveApkFilesTask)
  }
}

Sorry for not being clear, I just need the output of executing ./gradlew .... It should only be a few tasks if it's working correctly.

$ ./gradlew publishProductionApk
To honour the JVM settings for this build a new JVM will be forked. Please consider using the daemon: https://docs.gradle.org/5.4.1/userguide/gradle_daemon.html.
Daemon will be stopped at the end of the build stopping after processing
Configuration on demand is an incubating feature.

> Configure project :app
WARNING: API 'variant.getAssemble()' is obsolete and has been replaced with 'variant.getAssembleProvider()'.
It will be removed at the end of 2019.
For more information, see https://d.android.com/r/tools/task-configuration-avoidance.
To determine what is calling variant.getAssemble(), use -Pandroid.debug.obsoleteApi=true on the command line to display more information.
WARNING: API 'variantOutput.getPackageApplication()' is obsolete and has been replaced with 'variant.getPackageApplicationProvider()'.
It will be removed at the end of 2019.
For more information, see https://d.android.com/r/tools/task-configuration-avoidance.
To determine what is calling variantOutput.getPackageApplication(), use -Pandroid.debug.obsoleteApi=true on the command line to display more information.
registerResGeneratingTask is deprecated, use registerGeneratedResFolders(FileCollection)
registerResGeneratingTask is deprecated, use registerGeneratedResFolders(FileCollection)
Signing not ready. Be sure to specify a signingConfig for Production
registerResGeneratingTask is deprecated, use registerGeneratedResFolders(FileCollection)

> Task :app:generateProductionPlayResources NO-SOURCE
> Task :app:publishProductionApkWrapper UP-TO-DATE
> Task :app:publishProductionApk
> Task :app:publishProductionApk FAILED

FAILURE: Build failed with an exception.

Stack overflow is saying you should use both signing v1 and v2. And not the Signing not ready warning鈥擨 think you probably just aren't signing things correctly. Also, can you try manually moving the APK to some other folder and using the --artifact-dir to be 100% that the APK doesn't work both with the CLI and the console?

TBH, I don't know a whole lot about signing, so you'd probably have more luck giving the APK to support people who will have more context about your app.

I also read StackOverflow :)

I came here with hope you can explain why manual upload of the same binary works perfectly.
The warning about signing is correct since we disable signing for the production build and we sign it later with jarsigner.

I wrote to Google but they are not fast. Anyway, thank you!

Do you mind if I added extra info logging to print full paths to apk and mapping file? After a super long conversation with Google, I'm not sure if it is Google Play issue or plugin is uploading the wrong apk.

Good idea, done: #623

Just release GPP v2.3.0. Feel free to use the stable version instead of snapshots.

Oke, the mystery is solved (almost)!

What happened - we download artifacts from two jobs - production unsigned build to get mapping file and production signing to get signed and aligned build. Unfortunately, GitLab doesn't allow yet to download a specific part of artifacts from job yet. This process ends up with having two apks in the production folder. The plugin just uploads all apks in the folder and since we unsigned one it fails.

After one day of reproducing env on the local machine and doing step by step I got it solved.

Since new plugin version has already extended logging I think it will be enough for other people future investigations.

Super thankful to take my mistake serious and helping a lot with the investigation.

As a small addition, it might be great in the future only specify concrete apk(s) to upload or get apk name from the AGP plugin.

Awesome! :tada: Some notes about your reply:

  • There's text at the bottom of this section that says all artifacts in the folder will be uploaded. Not sure if it's noticeable enough though.
  • The reason I need an artifact folder is because you can upload multiple APKs (think splits) and have that be a valid operation. Also, it supports the mapping file. However, I've been annoyed by that too and have sneaked this in for the next release.
  • As for using the name given by AGP, that would kind of defeat the purpose of letting you specify arbitrary artifacts we know nothing about.

I missed the comment :)

Nice addition but also unexpected - ext name is folder but it treats it as file sometimes :)

I put or as two possible solutions to have specific apk names. But I agree with that AGP variant is too inflexible and don't fit the current purpose.

I will create another ticket for discussion if the plugin needs AGP dependency. It would be great to not have it :)

Congrats with joining Google!

馃槢

Yeah, it is a little weird to have dir in the name but also accept files. My hope is that it'll Just Work鈩笍 and people won't even notice it. Otherwise, I'll just revert.

馃憤

馃憤

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aat-antoine picture aat-antoine  路  6Comments

emartynov picture emartynov  路  4Comments

ivoberger picture ivoberger  路  4Comments

bunjix picture bunjix  路  5Comments

ToxicBakery picture ToxicBakery  路  4Comments