| // 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 | |
| } |