Skip to content

Commit

Permalink
Merge pull request #804 from DroidKaigi/travis/auto_publish
Browse files Browse the repository at this point in the history
Use Travis to publish an apk to alpha track
  • Loading branch information
jmatsu authored Feb 6, 2019
2 parents af22382 + c7bcd4f commit 6b96a4b
Show file tree
Hide file tree
Showing 15 changed files with 350 additions and 5 deletions.
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ matrix:
- openssl aes-256-cbc -K $encrypted_1701aaafcc71_key -iv $encrypted_1701aaafcc71_iv
-in .travis/android/release.zip.enc -out release.zip -d
- "./.travis/android/before_install.bash"
script: ".travis/android/run.bash"
script:
- ".travis/android/run.bash"
- ".travis/android/release_to_alpha.bash"
# - language: objective-c
# os: osx
# osx_image: xcode10.1
Expand Down
14 changes: 14 additions & 0 deletions .travis/android/promote_to_production.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash

set -eu

die() {
echo "$*" 1>&2
exit 1
}

source "$(dirname $0)/../bash.source"

source .release/bash.source

./gradlew promoteProduction -PeditId="$1"
Binary file modified .travis/android/release.zip.enc
Binary file not shown.
22 changes: 22 additions & 0 deletions .travis/android/release_to_alpha.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash

set -eu

if [[ -z "${TRAVIS_TAG:-}" ]]; then
exit 0
fi

die() {
echo "$*" 1>&2
exit 1
}

source "$(dirname $0)/../bash.source"

source .release/bash.source

if [[ ! -d ".transart" ]]; then
transart -f .travis/android/to_github.transart.yml download
fi

./gradlew publishAlpha
46 changes: 46 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import com.android.build.gradle.AppPlugin
import com.android.build.gradle.LibraryPlugin
import dependencies.Dep
import dependencies.Packages
import publish.DistributionTrack
import publish.EditStatus
import publish.PromoteApk
import publish.UploadApk

apply from: file('gradle/dependencyGraph.gradle')

Expand Down Expand Up @@ -190,3 +195,44 @@ task dependencyReport {
file << "}\n"
}
}

task publishAlpha {
doLast {
def uploadApk = new UploadApk(logger)

def apkFile = new File(System.getenv("UNIVERSAL_APK_PATH"))
// FIXME more flexible
def mappingFile = new File(rootProject.projectDir, ".transart/mapping.txt")

uploadApk.execute(
Packages.name,
rootProject.file(".release/service-account.json"),
apkFile,
mappingFile,
DistributionTrack.Alpha.INSTANCE,
"New Alpha Release",
EditStatus.Completed.INSTANCE,
[
"en-US": rootProject.file("publish/release-notes/en-US/default.txt"),
"ja-JP": rootProject.file("publish/release-notes/ja-JP/default.txt")
]
)
}
}

task promoteProduction {
doLast {
def promoteApk = new PromoteApk(logger)
def editId = project.property("editId")

if (!editId) {
throw new GradleScriptException("editId must be privided")
}

promoteApk.execute(
Packages.name,
rootProject.file(".release/service-account.json"),
editId
)
}
}
20 changes: 20 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
buildscript {
repositories {
jcenter()
}
dependencies {
classpath("com.google.apis:google-api-services-androidpublisher:v3-rev46-1.25.0")
classpath("com.google.api-client:google-api-client:1.28.0")
}
}

plugins {
`kotlin-dsl`
}
repositories {
jcenter()
}

dependencies {
implementation("com.google.guava:guava:26.0-jre")
implementation("com.google.apis:google-api-services-androidpublisher:v3-rev46-1.25.0") {
exclude(group = "com.google.guava", module = "guava")
}
implementation("com.google.api-client:google-api-client:1.28.0") {
exclude(group = "com.google.guava", module = "guava")
}
}
6 changes: 6 additions & 0 deletions buildSrc/src/main/java/dependencies/Packages.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package dependencies

object Packages {
const val name = "io.github.droidkaigi.confsched2019"
const val debugNameSuffix = ".debug"
}
21 changes: 21 additions & 0 deletions buildSrc/src/main/java/publish/DistributionTrack.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package publish

sealed class DistributionTrack {
abstract val name: String

object Internal : DistributionTrack() {
override val name: String = "internal"
}

object Alpha : DistributionTrack() {
override val name: String = "alpha"
}

object Beta : DistributionTrack() {
override val name: String = "beta"
}

object Production : DistributionTrack() {
override val name: String = "production"
}
}
21 changes: 21 additions & 0 deletions buildSrc/src/main/java/publish/EditStatus.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package publish

sealed class EditStatus {
abstract val status: String

object InProgress : EditStatus() {
override val status: String = "inProgress"
}

object Draft : EditStatus() {
override val status: String = "draft"
}

object Completed : EditStatus() {
override val status: String = "completed"
}

object Halted : EditStatus() {
override val status: String = "halted"
}
}
49 changes: 49 additions & 0 deletions buildSrc/src/main/java/publish/Extension.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package publish

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport
import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.services.androidpublisher.AndroidPublisher
import com.google.api.services.androidpublisher.AndroidPublisherScopes
import java.io.File

fun androidPublisher(packageName: String, serviceAccountJson: File): AndroidPublisher {
val credential = serviceAccountJson.asCredential()

return AndroidPublisher.Builder(
GoogleNetHttpTransport.newTrustedTransport(),
JacksonFactory.getDefaultInstance(),
credential
)
.setApplicationName(packageName)
.build()
}

fun File.asCredential(): GoogleCredential {
if (!exists()) {
error("$this does not exist")
}

return inputStream().use { credentialStream ->
GoogleCredential.fromStream(credentialStream)
.createScoped(
listOf(AndroidPublisherScopes.ANDROIDPUBLISHER)
)
}
}

fun AndroidPublisher.Edits.runInTransaction(
packageName: String,
editId: String = insert(packageName, null).execute().id,
action: (editId: String) -> Unit = {},
errorHandler: (error: Throwable) -> Unit = {}
) {
try {
action(editId)

commit(packageName, editId).execute()
} catch (th: Throwable) {
errorHandler(th)
}
}

40 changes: 40 additions & 0 deletions buildSrc/src/main/java/publish/PromoteApk.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package publish

import org.slf4j.Logger
import java.io.File

class PromoteApk(
private val logger: Logger
) {
fun execute(
packageName: String,
serviceAccountJson: File,
editId: String
) {
if (packageName.isEmpty()) {
error("packageName must not be empty")
}

val editsService = androidPublisher(packageName, serviceAccountJson).edits()

editsService.runInTransaction(packageName, editId, action = { editId ->
logger.debug("The current edit transaction id is $editId")

// Use `alpha`
val existingTrack =
editsService.tracks().get(packageName, editId, DistributionTrack.Alpha.name)
.execute()

// Use `production`
val updatedTrack =
editsService.tracks()
.update(packageName, editId, DistributionTrack.Production.name, existingTrack)
.execute()

logger.info("Update ${DistributionTrack.Alpha.name} to ${updatedTrack.track}")
}) { th ->
logger.error("while edit transaction", th)
throw th
}
}
}
99 changes: 99 additions & 0 deletions buildSrc/src/main/java/publish/UploadApk.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package publish

import com.google.api.client.http.FileContent
import com.google.api.services.androidpublisher.model.LocalizedText
import com.google.api.services.androidpublisher.model.Track
import com.google.api.services.androidpublisher.model.TrackRelease
import org.slf4j.Logger
import java.io.File

class UploadApk(
private val logger: Logger
) {
fun execute(
packageName: String,
serviceAccountJson: File,
apkFile: File,
mappingFile: File,
track: DistributionTrack,
releaseName: String,
editStatus: EditStatus,
releaseNoteMap: Map<String, File>
) {
if (packageName.isEmpty()) {
error("packageName must not be empty")
}

val notFoundReleaseNotes = releaseNoteMap.filterValues { !it.exists() }

if (notFoundReleaseNotes.isNotEmpty()) {
val message = releaseNoteMap.filterValues { !it.exists() }
.map { (lang, noteFile) ->
"$lang at $noteFile "
}
.joinToString(", ")

error("ReleaseNote not found: $message")
}

if (!apkFile.exists()) {
error("apk file not found: $apkFile")
}

if (!mappingFile.exists()) {
error("mapping file not found: $mappingFile")
}

val editsService = androidPublisher(packageName, serviceAccountJson).edits()

editsService.runInTransaction(packageName, action = { editId ->
logger.warn("New edit transaction id is $editId")

val apkContent = apkFile.asApkContent()

logger.warn("apkContent has been prepared")

val apkResult =
editsService.apks().upload(packageName, editId, apkContent).execute()
val versionCode = apkResult.versionCode.toLong()

logger.warn("$packageName (${versionCode}) has been uploaded")

val releaseContent = TrackRelease().apply {
name = releaseName
versionCodes = listOf(versionCode)
status = editStatus.status
releaseNotes = releaseNoteMap.map { (lang, noteFile) ->
LocalizedText().setLanguage(lang).setText(noteFile.readText())
}
}

val updatedTrack = editsService.tracks().update(
packageName,
editId,
track.name,
Track().setReleases(listOf(releaseContent))
).execute()


val mapping = FileContent("application/octet-stream", mappingFile)
editsService.deobfuscationfiles()
.upload(packageName, editId, versionCode.toInt(), "proguard", mapping)
.execute()

logger.warn("Update ${updatedTrack.track} and the status has been ${editStatus.status}")
logger.warn("New edit id is $editId")
}) { th ->
logger.error("while edit transaction", th)
throw th
}
}

private fun File.asApkContent(): FileContent {
if (!exists()) {
error("$this does not exist")
}

return FileContent("application/vnd.android.package-archive", this)
}
}
Loading

0 comments on commit 6b96a4b

Please sign in to comment.