blob: f2a008370536c809e557b6f701ee11b0759016ca [file]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package shellapk creates a minimal shell apk.
package shellapk
import (
"archive/zip"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"sync"
"src/common/golang/fileutils"
"src/common/golang/flags"
"src/common/golang/ziputils"
"src/tools/ak/types"
)
const (
swigdepsFileName = "nativedeps.txt"
jarManifestFileName = "META-INF/MANIFEST.MF"
// see https://docs.oracle.com/javase/tutorial/deployment/jar/defman.html
defJarManifestContents = "Manifest-Version: 1.0\nCreated-By: ak"
)
var (
// Cmd defines the command to run shellapk.
Cmd = types.Command{
Init: Init,
Run: Run,
Desc: desc,
Flags: []string{"java_resources", "dummy_native_libs", "jdk", "dex_file", "manifest_package_name", "in_application_class_name", "shell_app", "android_manifest", "arsc_path", "android_resources_zip", "linked_native_library"},
}
// Variables to hold flag values.
javaRes flags.StringList
dummyNativeLibs string
jdk string
dexFile string
manifestPkgName string
inAppClassName string
out string
manifest string
arsc string
resZip string
linkedNativeLib string
initOnce sync.Once
)
// Init initializes shellapk.
func Init() {
initOnce.Do(func() {
flag.Var(&javaRes, "java_resources", "Path to java resource file.")
flag.StringVar(&dummyNativeLibs, "dummy_native_libs", "", "Native libraries files.")
flag.StringVar(&jdk, "jdk", "", "JDK path.")
flag.StringVar(&dexFile, "dex_file", "", "Dex file with stubby classes for apk.")
flag.StringVar(&manifestPkgName, "manifest_package_name", "", "Manifest package name.")
flag.StringVar(&inAppClassName, "in_application_class_name", "", "Path for the application class name.")
flag.StringVar(&out, "shell_app", "", "Output path for the shell apk.")
flag.StringVar(&manifest, "android_manifest", "", "Binary AndroidManifest.xml artifact.")
flag.StringVar(&arsc, "arsc_path", "", "Path to the arsc table.")
flag.StringVar(&resZip, "android_resources_zip", "", "Android resources files.")
flag.StringVar(&linkedNativeLib, "linked_native_library", "", "Linked native lib name that needs to be outputed on the swigdeps file.")
})
}
func desc() string {
return "Shellapk creates a minimal shell apk."
}
// Run is the entry point for shellapk.
func Run() {
if err := doWork(
out,
javaRes,
dummyNativeLibs,
jdk,
dexFile,
manifestPkgName,
inAppClassName,
manifest,
arsc,
resZip,
linkedNativeLib); err != nil {
log.Fatalf("Error creating shellapk: %v", err)
}
}
func doWork(out string, javaRes []string, dummyNativeLibs, jdk, dexFile, manifestPkgName, inAppClassName, manifest, arsc, resZip, linkedNativeLib string) error {
// Create temp dir for all apk files
apkDir, err := ioutil.TempDir("", "shellapk")
if err != nil {
return err
}
defer os.RemoveAll(apkDir)
// Exctract dexes
if err := extractDexes(apkDir, dexFile); err != nil {
return err
}
// Extract java resources
for _, f := range javaRes {
if err := ziputils.Unzip(f, apkDir); err != nil {
return err
}
}
// Copy manifest
if err := fileutils.Copy(manifest, filepath.Join(apkDir, "AndroidManifest.xml")); err != nil {
return err
}
// Write output zip
w, err := os.Create(out)
if err != nil {
return err
}
defer w.Close()
zipOut := zip.NewWriter(w)
defer zipOut.Close()
// Write all files in apkDir
err = filepath.Walk(apkDir, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if !f.IsDir() {
// Filter out META-INF/MANIFEST.MF. If this file is not correct
// the apk signer will refuse to touch the apk.
if path[len(apkDir)+1:] == jarManifestFileName {
return nil
}
n := strings.TrimLeft(strings.TrimPrefix(path, apkDir), "/")
if err := ziputils.WriteFile(zipOut, path, n); err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
// Copy all files from android resources zip
r, err := zip.OpenReader(resZip)
if err != nil {
return err
}
for _, f := range r.File {
if strings.HasSuffix(f.Name, "AndroidManifest.xml") {
continue
}
fR, err := f.Open()
if err != nil {
return err
}
if err := ziputils.WriteReader(zipOut, fR, f.Name); err != nil {
return err
}
}
r.Close()
// Copy all native files from dummy native libs
if dummyNativeLibs != "" {
r, err := zip.OpenReader(dummyNativeLibs)
if err != nil {
return err
}
for _, f := range r.File {
if !strings.HasSuffix(f.Name, ".so") {
continue
}
fR, err := f.Open()
if err != nil {
return err
}
ziputils.WriteReader(zipOut, fR, f.Name)
}
r.Close()
}
if err := ziputils.WriteReader(zipOut, strings.NewReader(defJarManifestContents), jarManifestFileName); err != nil {
return err
}
if err := ziputils.WriteFile(zipOut, manifestPkgName, "package_name.txt"); err != nil {
return err
}
if err := ziputils.WriteFile(zipOut, inAppClassName, "app_name.txt"); err != nil {
return err
}
if linkedNativeLib != "" {
if err = ziputils.WriteFile(zipOut, linkedNativeLib, swigdepsFileName); err != nil {
return err
}
}
if arsc != "" {
if err = ziputils.WriteFile(zipOut, arsc, "resources.arsc"); err != nil {
return err
}
}
return nil
}
func extractDexes(dir, zipFile string) error {
r, err := zip.OpenReader(zipFile)
if err != nil {
return err
}
defer r.Close()
dexOut, err := os.Create(filepath.Join(dir, "classes.dex"))
if err != nil {
return err
}
defer dexOut.Close()
dexCount := 0
for _, f := range r.File {
if strings.HasSuffix(f.Name, ".dex") {
dexCount++
dR, err := f.Open()
if err != nil {
return err
}
io.Copy(dexOut, dR)
}
}
if dexCount != 1 {
return fmt.Errorf("expected 1 dex in %s, actually %d", dexFile, dexCount)
}
return nil
}