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