Skip to content

Commit

Permalink
send android artifact's module, product flavour and build type (#100)
Browse files Browse the repository at this point in the history
* send android artifct's module, product flavour and build type

* new tests

* parse multiple falvours

* PR fix

* better example for parseAppPath

* handle -unsigned suffix in artifact path

* parse android artifacts, include split meta

* artifact info models

* mark bundle of the split

* aab support in artifact meta

* refactors + dep update

* introduce Artifact

* always return split meta

* pr fix, bundle's universal apk name fix, bundle's universal apk name test, loging updates

* wire in renameUniversalAPK func

* generating apk from aab mooved to the main package; generate or find universal apk pairs for aabs; android artifact path parsing logic moved to the androidartifact package

* improved FindSameArtifact and tests

* new tests

* pr fixes

* pr fixes

* BundleTool fixes

* PR fixes
  • Loading branch information
godrei authored Sep 19, 2019
1 parent 9049fdf commit f21a87f
Show file tree
Hide file tree
Showing 16 changed files with 1,599 additions and 257 deletions.
22 changes: 12 additions & 10 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

100 changes: 100 additions & 0 deletions aabutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package main

import (
"errors"
"fmt"
"os"
"path/filepath"

"github.com/bitrise-io/go-utils/command"
"github.com/bitrise-io/go-utils/errorutil"
"github.com/bitrise-io/go-utils/pathutil"
"github.com/bitrise-steplib/steps-deploy-to-bitrise-io/androidartifact"
"github.com/bitrise-steplib/steps-deploy-to-bitrise-io/bundletool"
)

// handleError creates error with layout: `<cmd> failed (status: <status_code>): <cmd output>`.
func handleError(cmd, out string, err error) error {
if err == nil {
return nil
}

msg := fmt.Sprintf("%s failed", cmd)
if status, exitCodeErr := errorutil.CmdExitCodeFromError(err); exitCodeErr == nil {
msg += fmt.Sprintf(" (status: %d)", status)
}
if len(out) > 0 {
msg += fmt.Sprintf(": %s", out)
}
return errors.New(msg)
}

// run executes a given command.
func run(cmd *command.Model) error {
out, err := cmd.RunAndReturnTrimmedCombinedOutput()
return handleError(cmd.PrintableCommandArgs(), out, err)
}

// generateKeystore creates a debug keystore.
func generateKeystore(tmpPth string) (string, error) {
pth := filepath.Join(tmpPth, "debug.keystore")
return pth, run(command.New("keytool", "-genkey", "-v",
"-keystore", pth,
"-storepass", "android",
"-alias", "androiddebugkey",
"-keypass", "android",
"-keyalg", "RSA",
"-keysize", "2048",
"-validity", "10000",
"-dname", "C=US, O=Android, CN=Android Debug",
))
}

// buildApksArchive generates universal apks from an aab file.
func buildApksArchive(bundleTool bundletool.Path, tmpPth, aabPth, keystorePath string) (string, error) {
pth := filepath.Join(tmpPth, "universal.apks")
return pth, run(bundleTool.Command("build-apks", "--mode=universal",
"--bundle", aabPth,
"--output", pth,
"--ks", keystorePath,
"--ks-pass", "pass:android",
"--ks-key-alias", "androiddebugkey",
"--key-pass", "pass:android",
))
}

// unzipUniversalAPKsArchive unzips an universal apks archive.
func unzipUniversalAPKsArchive(archive, destDir string) (string, error) {
return filepath.Join(destDir, "universal.apk"), run(command.New("unzip", archive, "-d", destDir))
}

// GenerateUniversalAPK generates universal apks from an aab file.
func GenerateUniversalAPK(aabPth string) (string, error) {
r, err := bundletool.New()
if err != nil {
return "", err
}

tmpPth, err := pathutil.NormalizedOSTempDirPath("aab-bundle")
if err != nil {
return "", err
}

keystorePath, err := generateKeystore(tmpPth)
if err != nil {
return "", err
}

apksPth, err := buildApksArchive(r, tmpPth, aabPth, keystorePath)
if err != nil {
return "", err
}

universalAPKPath, err := unzipUniversalAPKsArchive(apksPth, tmpPth)
if err != nil {
return "", err
}

renamedUniversalAPKPath := filepath.Join(tmpPth, androidartifact.UniversalAPKBase(aabPth))
return renamedUniversalAPKPath, os.Rename(universalAPKPath, renamedUniversalAPKPath)
}
111 changes: 111 additions & 0 deletions androidartifact/apk_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package androidartifact

import (
"errors"
"fmt"
"os"
"regexp"
"strings"

"github.com/bitrise-io/go-android/sdk"
"github.com/bitrise-io/go-utils/command"
)

// ApkInfo ...
type ApkInfo struct {
AppName string
PackageName string
VersionCode string
VersionName string
MinSDKVersion string
RawPackageContent string
}

// parseAppName parses the application name from `aapt dump badging` command output.
func parseAppName(aaptOut string) string {
pattern := `application: label=\'(?P<label>.+)\' icon=`
re := regexp.MustCompile(pattern)
if matches := re.FindStringSubmatch(aaptOut); len(matches) == 2 {
return matches[1]
}

pattern = `application-label:\'(?P<label>.*)\'`
re = regexp.MustCompile(pattern)
if matches := re.FindStringSubmatch(aaptOut); len(matches) == 2 {
return matches[1]
}

return ""
}

// parseMinSDKVersion parses the min sdk version from `aapt dump badging` command output.
func parseMinSDKVersion(aaptOut string) string {
pattern := `sdkVersion:\'(?P<min_sdk_version>.*)\'`
re := regexp.MustCompile(pattern)
if matches := re.FindStringSubmatch(aaptOut); len(matches) == 2 {
return matches[1]
}
return ""
}

// parsePackageField parses fields from `aapt dump badging` command output.
func parsePackageField(aaptOut, key string) string {
pattern := fmt.Sprintf(`%s=['"](.*?)['"]`, key)

re := regexp.MustCompile(pattern)
if matches := re.FindStringSubmatch(aaptOut); len(matches) == 2 {
return matches[1]
}

return ""
}

// ParsePackageInfos parses package name, version code and name from `aapt dump badging` command output.
func ParsePackageInfos(aaptOut string) (string, string, string) {
return parsePackageField(aaptOut, "name"),
parsePackageField(aaptOut, "versionCode"),
parsePackageField(aaptOut, "versionName")
}

// GetAPKInfo returns infos about the APK.
func GetAPKInfo(apkPth string) (ApkInfo, error) {
androidHome := os.Getenv("ANDROID_HOME")
if androidHome == "" {
return ApkInfo{}, errors.New("ANDROID_HOME environment not set")
}

sdkModel, err := sdk.New(androidHome)
if err != nil {
return ApkInfo{}, fmt.Errorf("failed to create sdk model, error: %s", err)
}

aaptPth, err := sdkModel.LatestBuildToolPath("aapt")
if err != nil {
return ApkInfo{}, fmt.Errorf("failed to find latest aapt binary, error: %s", err)
}

aaptOut, err := command.New(aaptPth, "dump", "badging", apkPth).RunAndReturnTrimmedCombinedOutput()
if err != nil {
return ApkInfo{}, fmt.Errorf("failed to get apk infos, output: %s, error: %s", aaptOut, err)
}

appName := parseAppName(aaptOut)
packageName, versionCode, versionName := ParsePackageInfos(aaptOut)
minSDKVersion := parseMinSDKVersion(aaptOut)

packageContent := ""
for _, line := range strings.Split(aaptOut, "\n") {
if strings.HasPrefix(line, "package:") {
packageContent = line
}
}

return ApkInfo{
AppName: appName,
PackageName: packageName,
VersionCode: versionCode,
VersionName: versionName,
MinSDKVersion: minSDKVersion,
RawPackageContent: packageContent,
}, nil
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package uploaders
package androidartifact

import "testing"

func Test_filterPackageInfos(t *testing.T) {
func TestParsePackageInfos(t *testing.T) {

tests := []struct {
name string
Expand Down Expand Up @@ -63,15 +63,15 @@ func Test_filterPackageInfos(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1, got2 := filterPackageInfos(tt.aaptOut)
got, got1, got2 := ParsePackageInfos(tt.aaptOut)
if got != tt.want {
t.Errorf("filterPackageInfos() got = %v, want %v", got, tt.want)
t.Errorf("ParsePackageInfos() got = %v, want %v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("filterPackageInfos() got1 = %v, want %v", got1, tt.want1)
t.Errorf("ParsePackageInfos() got1 = %v, want %v", got1, tt.want1)
}
if got2 != tt.want2 {
t.Errorf("filterPackageInfos() got2 = %v, want %v", got2, tt.want2)
t.Errorf("ParsePackageInfos() got2 = %v, want %v", got2, tt.want2)
}
})
}
Expand Down
Loading

0 comments on commit f21a87f

Please sign in to comment.