cmd/gogio: [android] add support for AAB

That patch makes possible to generate Android App Bundle (`.aab`) instead
of APK. In order to generate AAB use `-o outputfile.aab`.

Signed-off-by: Inkeliz <inkeliz@inkeliz.com>
This commit is contained in:
Inkeliz
2021-03-24 14:54:10 +00:00
committed by Elias Naur
parent 238dd1aa86
commit b7dc407dbe
2 changed files with 128 additions and 30 deletions
+127 -29
View File
@@ -153,10 +153,27 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
case "archive": case "archive":
return archiveAndroid(tmpDir, bi, perms) return archiveAndroid(tmpDir, bi, perms)
case "exe": case "exe":
if err := exeAndroid(tmpDir, tools, bi, extraJars, perms); err != nil { file := *destPath
if file == "" {
file = fmt.Sprintf("%s.apk", bi.name)
}
isBundle := false
switch filepath.Ext(file) {
case ".apk":
case ".aab":
isBundle = true
default:
return fmt.Errorf("the specified output %q does not end in '.apk' or '.aab'", file)
}
if err := exeAndroid(tmpDir, tools, bi, extraJars, perms, isBundle); err != nil {
return err return err
} }
return signAPK(tmpDir, tools, bi) if isBundle {
return signAAB(tmpDir, file, tools, bi)
}
return signAPK(tmpDir, file, tools, bi)
default: default:
panic("unreachable") panic("unreachable")
} }
@@ -316,7 +333,7 @@ func archiveAndroid(tmpDir string, bi *buildInfo, perms []string) (err error) {
return aarw.Close() return aarw.Close()
} }
func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, perms []string) (err error) { func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, perms []string, isBundle bool) (err error) {
classes := filepath.Join(tmpDir, "classes") classes := filepath.Join(tmpDir, "classes")
var classFiles []string var classFiles []string
err = filepath.Walk(classes, func(path string, f os.FileInfo, err error) error { err = filepath.Walk(classes, func(path string, f os.FileInfo, err error) error {
@@ -441,20 +458,25 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
} }
linkAPK := filepath.Join(tmpDir, "link.apk") linkAPK := filepath.Join(tmpDir, "link.apk")
link := exec.Command(
aapt2, args := []string{
"link", "link",
"--manifest", manifest, "--manifest", manifest,
"-I", tools.androidjar, "-I", tools.androidjar,
"-o", linkAPK, "-o", linkAPK,
resZip, }
) if isBundle {
if _, err := runCmd(link); err != nil { args = append(args, "--proto-format")
}
args = append(args, resZip)
if _, err := runCmd(exec.Command(aapt2, args...)); err != nil {
return err return err
} }
// The Go standard library archive/zip doesn't support appending to zip // The Go standard library archive/zip doesn't support appending to zip
// files. Copy files from `link.apk` (generated by aapt2) along with classes.dex and // files. Copy files from `link.apk` (generated by aapt2) along with classes.dex and
// the Go libraries to a new `app.ap_` file. // the Go libraries to a new `app.zip` file.
// Load link.apk as zip. // Load link.apk as zip.
linkAPKZip, err := zip.OpenReader(linkAPK) linkAPKZip, err := zip.OpenReader(linkAPK)
@@ -464,7 +486,7 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
defer linkAPKZip.Close() defer linkAPKZip.Close()
// Create new "APK". // Create new "APK".
unsignedAPK := filepath.Join(tmpDir, "app.ap_") unsignedAPK := filepath.Join(tmpDir, "app.zip")
unsignedAPKFile, err := os.Create(unsignedAPK) unsignedAPKFile, err := os.Create(unsignedAPK)
if err != nil { if err != nil {
return err return err
@@ -484,6 +506,14 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
Method: f.FileHeader.Method, Method: f.FileHeader.Method,
} }
if isBundle {
// AAB have pre-defined folders.
switch header.Name {
case "AndroidManifest.xml":
header.Name = "manifest/AndroidManifest.xml"
}
}
w, err := unsignedAPKZip.CreateHeader(&header) w, err := unsignedAPKZip.CreateHeader(&header)
if err != nil { if err != nil {
return err return err
@@ -525,28 +555,19 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
} }
// Append classes.dex. // Append classes.dex.
if err := appendToZip("classes.dex", filepath.Join(dexDir, "classes.dex")); err != nil { classesFolder := "classes.dex"
if isBundle {
classesFolder = "dex/classes.dex"
}
if err := appendToZip(classesFolder, filepath.Join(dexDir, "classes.dex")); err != nil {
return err return err
} }
return unsignedAPKZip.Close() return unsignedAPKZip.Close()
} }
func signAPK(tmpDir string, tools *androidTools, bi *buildInfo) error { func signAPK(tmpDir string, apkFile string, tools *androidTools, bi *buildInfo) error {
apkFile := *destPath if err := zipalign(tools, filepath.Join(tmpDir, "app.zip"), apkFile); err != nil {
if apkFile == "" {
apkFile = fmt.Sprintf("%s.apk", bi.name)
}
if filepath.Ext(apkFile) != ".apk" {
return fmt.Errorf("the specified output %q does not end in '.apk'", apkFile)
}
_, err := runCmd(exec.Command(
filepath.Join(tools.buildtools, "zipalign"),
"-f",
"4", // 32-bit alignment.
filepath.Join(tmpDir, "app.ap_"),
apkFile,
))
if err != nil {
return err return err
} }
@@ -556,17 +577,94 @@ func signAPK(tmpDir string, tools *androidTools, bi *buildInfo) error {
} }
} }
_, err = runCmd(exec.Command( _, err := runCmd(exec.Command(
filepath.Join(tools.buildtools, "apksigner"), filepath.Join(tools.buildtools, "apksigner"),
"sign", "sign",
"--ks-pass", "pass:"+bi.password, "--ks-pass", "pass:"+bi.password,
"--ks", bi.key, "--ks", bi.key,
apkFile, apkFile,
)) ))
return err
}
func signAAB(tmpDir string, aabFile string, tools *androidTools, bi *buildInfo) error {
allBundleTools, err := filepath.Glob(filepath.Join(tools.buildtools, "bundletool*.jar"))
if err != nil { if err != nil {
return err return err
} }
return nil
bundletool := ""
for _, v := range allBundleTools {
bundletool = v
break
}
if bundletool == "" {
return fmt.Errorf("bundletool was not found at %s. Download it from https://github.com/google/bundletool/releases and move to the respective folder", tools.buildtools)
}
_, err = runCmd(exec.Command(
"java",
"-jar", bundletool,
"build-bundle",
"--modules="+filepath.Join(tmpDir, "app.zip"),
"--output="+filepath.Join(tmpDir, "app.aab"),
))
if err != nil {
return err
}
if err := zipalign(tools, filepath.Join(tmpDir, "app.aab"), aabFile); err != nil {
return err
}
if bi.key == "" {
if err := defaultAndroidKeystore(tmpDir, bi); err != nil {
return err
}
}
keytoolList, err := runCmd(exec.Command(
"keytool",
"-keystore", bi.key,
"-list",
"-keypass", bi.password,
"-v",
))
if err != nil {
return err
}
var alias string
for _, t := range strings.Split(keytoolList, "\n") {
if i, _ := fmt.Sscanf(t, "Alias name: %s", &alias); i > 0 {
break
}
}
_, err = runCmd(exec.Command(
filepath.Join("jarsigner"),
"-sigalg", "SHA256withRSA",
"-digestalg", "SHA-256",
"-keystore", bi.key,
"-storepass", bi.password,
aabFile,
strings.TrimSpace(alias),
))
return err
}
func zipalign(tools *androidTools, input, output string) error {
_, err := runCmd(exec.Command(
filepath.Join(tools.buildtools, "zipalign"),
"-f",
"4", // 32-bit alignment.
input,
output,
))
return err
} }
func defaultAndroidKeystore(tmpDir string, bi *buildInfo) error { func defaultAndroidKeystore(tmpDir string, bi *buildInfo) error {
+1 -1
View File
@@ -60,7 +60,7 @@ its deletion.
The -x flag will print all the external commands executed by the gogio tool. The -x flag will print all the external commands executed by the gogio tool.
The -signkey flag specifies the path of the keystore, used for signing Android apk files. The -signkey flag specifies the path of the keystore, used for signing Android apk/aab files.
The -signpass flag specifies the password of the keystore, ignored if -signkey is not provided. The -signpass flag specifies the password of the keystore, ignored if -signkey is not provided.
` `