mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
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:
+127
-29
@@ -153,10 +153,27 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
|
||||
case "archive":
|
||||
return archiveAndroid(tmpDir, bi, perms)
|
||||
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 signAPK(tmpDir, tools, bi)
|
||||
if isBundle {
|
||||
return signAAB(tmpDir, file, tools, bi)
|
||||
}
|
||||
return signAPK(tmpDir, file, tools, bi)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
@@ -316,7 +333,7 @@ func archiveAndroid(tmpDir string, bi *buildInfo, perms []string) (err error) {
|
||||
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")
|
||||
var classFiles []string
|
||||
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")
|
||||
link := exec.Command(
|
||||
aapt2,
|
||||
|
||||
args := []string{
|
||||
"link",
|
||||
"--manifest", manifest,
|
||||
"-I", tools.androidjar,
|
||||
"-o", linkAPK,
|
||||
resZip,
|
||||
)
|
||||
if _, err := runCmd(link); err != nil {
|
||||
}
|
||||
if isBundle {
|
||||
args = append(args, "--proto-format")
|
||||
}
|
||||
args = append(args, resZip)
|
||||
|
||||
if _, err := runCmd(exec.Command(aapt2, args...)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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
|
||||
// the Go libraries to a new `app.ap_` file.
|
||||
// the Go libraries to a new `app.zip` file.
|
||||
|
||||
// Load link.apk as zip.
|
||||
linkAPKZip, err := zip.OpenReader(linkAPK)
|
||||
@@ -464,7 +486,7 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
|
||||
defer linkAPKZip.Close()
|
||||
|
||||
// Create new "APK".
|
||||
unsignedAPK := filepath.Join(tmpDir, "app.ap_")
|
||||
unsignedAPK := filepath.Join(tmpDir, "app.zip")
|
||||
unsignedAPKFile, err := os.Create(unsignedAPK)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -484,6 +506,14 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -525,28 +555,19 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
|
||||
}
|
||||
|
||||
// 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 unsignedAPKZip.Close()
|
||||
}
|
||||
|
||||
func signAPK(tmpDir string, tools *androidTools, bi *buildInfo) error {
|
||||
apkFile := *destPath
|
||||
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 {
|
||||
func signAPK(tmpDir string, apkFile string, tools *androidTools, bi *buildInfo) error {
|
||||
if err := zipalign(tools, filepath.Join(tmpDir, "app.zip"), apkFile); err != nil {
|
||||
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"),
|
||||
"sign",
|
||||
"--ks-pass", "pass:"+bi.password,
|
||||
"--ks", bi.key,
|
||||
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 {
|
||||
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 {
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ its deletion.
|
||||
|
||||
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.
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user