mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-05 17:35:36 +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":
|
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
@@ -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.
|
||||||
`
|
`
|
||||||
|
|||||||
Reference in New Issue
Block a user