Gradle Play Publisher (GPP) is Android's unofficial release automation Gradle Plugin. It can doanything from building, uploading, and then promoting your App Bundle or APK to publishing applistings and other metadata.
signingConfig
The first APK or App Bundle needs to be uploaded via the Google Play Console because registering theapp with the Play Store cannot be done using the Play Developer API. For all subsequent uploads andchanges, GPP may be used.
To successfully upload apps to the Play Store, they must be signed with your developer key. Makesure you havea valid signing configuration.
To use GPP, you must create a service account with access to the Play Developer API:
project
query param in theURL)New service account
Invite new user
./gradlew bootstrapListing
or some other GPP task to validate your setupApply the plugin to each individual com.android.application
module where you want to use GPPthrough the plugins {}
DSL:
plugins {
id("com.android.application")
id("com.github.triplet.play") version "3.6.0"
}
plugins {
id 'com.android.application'
id 'com.github.triplet.play' version '3.6.0'
}
If you're prepared to cut yourself on the bleeding edge of GPP development, snapshot builds areavailable fromSonatype's snapshots
repository:
buildscript {
repositories {
// ...
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
// ...
classpath("com.github.triplet.gradle:play-publisher:3.7.0-SNAPSHOT")
}
}
buildscript {
repositories {
// ...
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
}
dependencies {
// ...
classpath 'com.github.triplet.gradle:play-publisher:3.7.0-SNAPSHOT'
}
}
After you've gone through the Service Account setup, you should have a JSON filewith your private key. Add a play
block alongside your android
one with the file's location:
android { ... }
play {
serviceAccountCredentials.set(file("your-key.json"))
}
Note: If you commit unencrypted Service Account keys to source, you run the risk of letting anyoneaccess your Google Play account. To circumvent this issue, put the contents of your JSON file inthe
ANDROID_PUBLISHER_CREDENTIALS
environment variable and don't specify theserviceAccountCredentials
property.
GPP follows the Android Gradle Plugin's (AGP) naming convention: [action][Variant][Thing]
. Forexample, publishPaidReleaseBundle
will be generated if have a paid
product flavor.
Lifecycle tasks to publish multiple product flavors at once are also available. For example,publishBundle
publishes all variants.
To find available tasks, run ./gradlew tasks --group publishing
and use./gradlew help --task [task]
where task
is something like publishBundle
to get more detaileddocumentation for a specific task.
GPP supports uploading both the App Bundle and APK. Once uploaded, GPP also supports promoting thoseartifacts to different tracks.
Several options are available to customize how your artifacts are published:
track
is the target stage for an artifact, i.e. internal
/alpha
/beta
/production
or anycustom track
internal
releaseStatus
is the type of release, i.e. ReleaseStatus.[COMPLETED/DRAFT/HALTED/IN_PROGRESS]
ReleaseStatus.COMPLETED
userFraction
is the percentage of users who will receive a staged release
0.1
aka 10%userFraction
is only applicable where releaseStatus=[IN_PROGRESS/HALTED]
updatePriority
sets the update priority for a new release. SeeGoogle's documentation on consumingthis value.
Example configuration:
import com.github.triplet.gradle.androidpublisher.ReleaseStatus
play {
// Overrides defaults
track.set("production")
userFraction.set(0.5)
updatePriority.set(2)
releaseStatus.set(ReleaseStatus.IN_PROGRESS)
// ...
}
While GPP can automatically build and find your artifact, you'll need to tell the plugin where tofind your release notes.
Add a file under src/[sourceSet]/play/release-notes/[language]/[track].txt
where sourceSet
is a full variant name,language
is one of thePlay Store supported codes,and track
is the channel you want these release notes to apply to (or default
if unspecified).
As an example, let's assume you have these two different release notes:
src/main/play/release-notes/en-US/default.txt
.../beta.txt
When you publish to the beta channel, the beta.txt
release notes will be uploaded. For any otherchannel, default.txt
will be uploaded.
Note: the Play Store limits your release notes to a maximum of 500 characters.
The Play Console supports customizing release names. These aren't visible to users, but may beuseful for internal processes. Similar to release notes, release names may be specified by placinga [track].txt
file in the release-names
directory under your play
folder. For example, here'sa custom release name for the alpha track in the play/release-names/alpha.txt
file:
My custom release name
If it makes more sense to specify the release name in your build script, the releaseName
propertyis available:
play {
// ...
releaseName.set("My custom release name")
}
Note: the
play.releaseName
property takes precedence over the resource files.
There is also a --release-name
CLI option for quick access. For example,./gradlew publishBundle --release-name "Hello World!"
.
Note: the Play Store limits your release names to a maximum of 50 characters.
By default, GPP will build your artifact from source. In advanced use cases, this might not be thedesired behavior. For example, if you need to inject translations into your APK or App Bundle afterbuilding it but before publishing it. Or perhaps you simply already have an artifact you wish topublish. GPP supports this class of use cases by letting you specify a directory in whichpublishable artifacts may be found:
play {
// ...
artifactDir.set(file("path/to/apk-or-app-bundle/dir"))
}
For quick access, you can also use the --artifact-dir
CLI option:
./gradlew publishBundle --artifact-dir path/to/app-bundle/dir
Note: all artifacts in the specified directory will be published.
Note: mapping files aren't applicable to App Bundles since the mapping file is contained withinthe bundle.
By default, GPP will look for a file called mapping.txt
in your artifact directory. If you needmore granularity, you can prefix mapping.txt
with your APK file name. For example:
artifact-dir/
├── mapping.txt
├── my-first-app.apk
├── my-second-app.apk
└── my-second-app.mapping.txt
my-second-app.apk
will use my-second-app.mapping.txt
and my-first-app.apk
will use thedefault mapping.txt
because no specific mapping file was specified.
GPP supports keeping around old artifacts such as OBB files or WearOS APKs:
play {
// ...
retain {
artifacts.set(listOf(123)) // Old APK version code
mainObb.set(123) // Old main OBB version code
patchObb.set(123) // Old patch OBB version code
}
}
Run ./gradlew publishBundle
.
You'll notice that if you run ./gradlew publish
, it uploads an APK by default. To change this,default to the App Bundle:
play {
// ...
defaultToAppBundles.set(true)
}
Run ./gradlew publishApk
. Splits will be uploaded if available.
Run ./gradlew uploadReleasePrivateBundle
for App Bundles and ./gradlew uploadReleasePrivateApk
for APKs. To upload an existing artifact, read abouthow to do so.
After running an Internal Sharing task, the output of the API response will be stored in thefollowing directory: build/outputs/internal-sharing/[bundle/apk]/[variant]/
. Each file will benamed [apk/aab name].json
.
For example, here are the contentsof app/build/outputs/internal-sharing/bundle/release/app-release.json
:
{
"certificateFingerprint": "...",
"downloadUrl": "...",
"sha256": "..."
}
To accelerate development, GPP supports uploading and then immediately installing Internal Sharingartifacts. This is similar to the AGP's install[Variant]
task.
Run ./gradlew installReleasePrivateArtifact
to install an artifact built on-the-fly and./gradlew uploadReleasePrivateBundle --artifact-dir path/to/artifact installReleasePrivateArtifact
to install an existing artifact.
Existing releases can be promoted and/or updated to the configured trackwith ./gradlew promoteArtifact
.
By default, the track from which to promote a release is determined by the most unstable channelthat contains a release. Example: if the alpha channel has no releases, but the beta and prodchannels do, the beta channel will be picked. To configure this manually, use the fromTrack
property:
play {
// ...
fromTrack.set("alpha")
}
Similarly, the track to which to promote a release defaults to the promoteTrack
property. Ifunspecified, the resolved fromTrack
property will be used instead and an in-place update will beperformed. Example configuration:
play {
// ...
promoteTrack.set("beta")
}
If you need to execute a one-time promotion, you can use the CLI args. For example, this is how youwould promote an artifact from the alpha
./gradlew promoteArtifact \
--from-track alpha --promote-track beta \
--release-status inProgress --user-fraction .25
If you have an ongoing inProgress
release and would like to perform a full rollout, simply changethe release status to completed
. A user fraction of 1.0
is invalid and will be rejected.
If an artifact already exists with a version code greater than or equal to the one you're trying toupload, an error will be thrown when attempting to publish the new artifact. You have two options:
ResolutionStrategy.IGNORE
)ResolutionStrategy.AUTO
)Example configuration:
import com.github.triplet.gradle.androidpublisher.ResolutionStrategy
play {
// ...
resolutionStrategy.set(ResolutionStrategy.IGNORE)
}
For example, you could update you app's version name based on the new version code:
import com.github.triplet.gradle.androidpublisher.ResolutionStrategy
play {
// ...
resolutionStrategy.set(ResolutionStrategy.AUTO)
}
android {
onVariantProperties {
for (output in outputs) {
val processedVersionCode = output.versionCode.map { playVersionCode ->
// Do something to the version code...
// In this example, version names will look like `myCustomVersionName.123`
"myCustomVersionName.$playVersionCode"
}
output.versionName.set(processedVersionCode)
}
}
}
GPP supports uploading any metadata you might want to change with each release, from screenshots anddescriptions to in-app purchases and subscriptions.
GPP includes a bootstrap task that pulls down your existing listing and initializes everything foryou. To use it, run ./gradlew bootstrapListing
.
Note: if you have a pre-existing
play
folder, it will be reset.
GPP follows the Android Gradle Plugin's source setguidelines and priorities.src/[sourceSet]/play
is the base directory for Play Store metadata. Since main
is the mostcommon source set, it will be assumed in all following examples.
In addition to merging metadata across variants, GPP merges translations. That is, if a resources isprovided in a default language such as en-US
but not in fr-FR
, the resource will be copied overwhen uploading French metadata.
Run ./gradlew publishListing
.
Base directory: play
File | Description |
---|---|
contact-email.txt |
Developer email |
contact-phone.txt |
Developer phone |
contact-website.txt |
Developer website |
default-language.txt |
The default language for both your Play Store listing and translation merging as described above |
Base directory: play/listings/[language]
where language
is one of thePlay Store supported codes
File | Description | Character limit |
---|---|---|
title.txt |
App title | 50 |
short-description.txt |
Tagline | 80 |
full-description.txt |
Full description | 4000 |
video-url.txt |
Youtube product video | N/A |
Directory: play/listings/[language]/graphics
where language
is defined as in the previoussection
Image files are organized a bit differently than in previous sections. Instead of the file name, theparent directory's name is used as the media type. This is because multiple images may be providedfor the same media type. While file names are arbitrary, they will be uploaded in alphabetical orderand presented on the Play Store as such. Therefore, we recommend using a number as the file name(1.png
for example). Both PNG and JPEG images are supported.
Directory | Max # of images | Image dimension constraints (px) |
---|---|---|
icon |
1 | 512x512 |
feature-graphic |
1 | 1024x500 |
phone-screenshots |
8 | [320..3840]x[320..3840] |
tablet-screenshots |
8 | [320..3840]x[320..3840] |
large-tablet-screenshots |
8 | [320..3840]x[320..3840] |
tv-banner |
1 | 1280x720 |
tv-screenshots |
8 | [320..3840]x[320..3840] |
wear-screenshots |
8 | [320..3840]x[320..3840] |
Run ./gradlew publishProducts
.
Manually setting up in-app purchase files is not recommended. Bootstrap them insteadwith ./gradlew bootstrapListing --products
.
When working with product flavors, granular configuration is key. GPP provides varying levels ofgranularity to best support your needs, all through the playConfigs
block:
play {
// In a simple app, this play block is all you'll need. However, in an app with product flavors,
// the play block becomes a place to store default configurations. Anything configured in here
// will apply to all product flavors, that is, unless an override is supplied in the playConfigs
// block.
}
android {
// Suppose we have the following flavors
flavorDimensions("customer", "type")
productFlavors {
register("firstCustomer") { setDimension("customer") }
register("secondCustomer") { setDimension("customer") }
register("demo") { setDimension("type") }
register("full") { setDimension("type") }
}
playConfigs {
// Now, we can configure GPP however precisely is required.
// Configuration overrides occur in a cascading manner from most to least specific. That is,
// a property configured in a build type + flavor combo overrides that same property
// configured in a flavor combo, which overrides a build type combo, which in turn overrides
// the play block. Properties not configured are inherited.
register("firstCustomerFullRelease") { ... } // Build type + flavor
register("firstCustomer") { ... } // Flavor
register("release") { ... } // Build type
}
}
play {
// In a simple app, this play block is all you'll need. However, in an app with product flavors,
// the play block becomes a place to store default configurations. Anything configured in here
// will apply to all product flavors, that is, unless an override is supplied in the playConfigs
// block.
}
android {
// Suppose we have the following flavors
flavorDimensions 'customer', 'type'
productFlavors {
firstCustomer { dimension 'customer' }
secondCustomer { dimension 'customer' }
demo { dimension 'type' }
full { dimension 'type' }
}
playConfigs {
// Now, we can configure GPP however precisely is required.
// Configuration overrides occur in a cascading manner from most to least specific. That is,
// a property configured in a build type + flavor combo overrides that same property
// configured in a flavor combo, which overrides a build type combo, which in turn overrides
// the play block. Properties not configured are inherited.
firstCustomerFullRelease { ... } // Build type + flavor
firstCustomer { ... } // Flavor
release { ... } // Build type
}
}
Sometimes, you may not want to publish all variants of your app. Or maybe you don't want publishingenabled on CI or local dev machines. Whatever the case may be, GPP can be disabled with theenabled
property:
android {
// ...
playConfigs {
register("myCustomVariantOrProductFlavor") {
enabled.set(true)
}
// ...
}
}
play {
enabled.set(false) // This disables GPP by default. It could be the other way around.
// ...
}
android {
// ...
playConfigs {
myCustomVariantOrProductFlavor {
enabled.set(true)
}
// ...
}
}
play {
enabled.set(false) // This disables GPP by default. It could be the other way around.
// ...
}
By default, GPP assumes every product flavor consists of a separate, independent app. To tell GPPthis isn't the case, you must use the commit
property:
// Don't commit any changes by default
play.commit.set(false)
android {
// ...
playConfigs {
register("someFlavorThatWillCommit") {
// Pick a flavor that will commit the changes prepared by all flavors
commit.set(true)
}
}
}
afterEvaluate {
// Now make sure the tasks execute in the right order
val intermediateTasks = listOf(
"publishSomeFlavor2Release[Apk/Bundle]",
"publishSomeFlavor3Release[Apk/Bundle]",
...
)
// The commit flavor task must run last so the other tasks can build up changes to commit
tasks.named("someFlavorThatWillCommitRelease[Apk/Bundle]").configure {
mustRunAfter(intermediateTasks)
}
}
// Don't commit any changes by default
play.commit.set(false)
android {
// ...
playConfigs {
someFlavorThatWillCommit {
// Pick a flavor that will commit the changes prepared by all flavors
commit.set(true)
}
}
}
afterEvaluate {
// Now make sure the tasks execute in the right order
def intermediateTasks = [
"publishSomeFlavor2Release[Apk/Bundle]",
"publishSomeFlavor3Release[Apk/Bundle]",
...
]
// The commit flavor task must run last so the other tasks can build up changes to commit
tasks.named("someFlavorThatWillCommitRelease[Apk/Bundle]").configure {
mustRunAfter(intermediateTasks)
}
}
If you need to publish each build flavor to a separate Play Store account, simply provide separatecredentials per product flavor.
android {
// ...
playConfigs {
register("firstCustomer") {
serviceAccountCredentials.set(file("customer-one-key.json"))
}
register("secondCustomer") {
serviceAccountCredentials.set(file("customer-two-key.json"))
}
}
}
android {
// ...
playConfigs {
firstCustomer {
serviceAccountCredentials.set(file('customer-one-key.json'))
}
secondCustomer {
serviceAccountCredentials.set(file('customer-two-key.json'))
}
}
}
All configuration options available in the play
block are also available as CLI options so youdon't have to update your build file when making one-time changes. For example, to configureplay.track
on demand, use the --track
option. camelCase
options are converted tokebab-case
ones.
To get a list of options and their quick documentation, use ./gradlew help --task [task]
wheretask
is something like publishBundle
.
If you need to use GPP behind an HTTPS-proxy, but it fails with an SSLHandshakeException
, you canprovide your own truststore via the javax.net.ssl.trustStore
property in your project'sgradle.properties
:
systemProp.javax.net.ssl.trustStore=/path/to/your/truststore.ks
systemProp.javax.net.ssl.trustStorePassword=YourTruststorePassword
GPP will automatically pick it up and use your proxy.
null 错误:找不到com.google.android.gms:play-services:6.5.87.在以下位置进行了搜索:file:/c:/users/myname/.m2/repository/com/google/android/gms/play-services/6.5.87.pom file:/c:/users/maname/.m2/repository/com/google/a
我正在编写一个java jar库。我只想为Gradle的项目添加游戏服务依赖项。我的gradle构建脚本: Gradle sync完成得很好,android studio告诉我一切都好,但当我试图构建: 我的gradle版本2.13。另外,我使用了最新的gradle版本候选版本。没什么。
这是一个Gradle build,建议在这里使用play web framework。 它在构建,启动等时工作正常...从命令行,但一旦项目导入到intellij(使用的项目文件)中,依赖项(来自播放插件)不会显示在项目视图/外部库中(即使在gradle面板中点击“刷新所有gradle项目”之后)。 谢谢:) PS:intellij 15 . 0 . 2/gradle 2.6/play插件
我试图在我的Play Framework应用程序中使用Play-Morphia。到目前为止,我已经用安装了该模块,我的应用程序依赖项。yml如下所示: 最后。
今天突然,我得到了一个错误,当我试图运行我的应用程序在Android Studio。 它是: 我没有改变gradle文件中的任何内容,但它突然出现了。几分钟前,我的上一个构建成功执行。 为什么它找不到