aboutsummaryrefslogtreecommitdiff
path: root/src/tools/ak/dex/dex.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/ak/dex/dex.go')
-rw-r--r--src/tools/ak/dex/dex.go281
1 files changed, 281 insertions, 0 deletions
diff --git a/src/tools/ak/dex/dex.go b/src/tools/ak/dex/dex.go
new file mode 100644
index 0000000..c024e44
--- /dev/null
+++ b/src/tools/ak/dex/dex.go
@@ -0,0 +1,281 @@
+// 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 dex provides a thin wrapper around d8 to handle corner cases
+package dex
+
+import (
+ "archive/zip"
+ "bufio"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "sync"
+
+ "src/common/golang/flags"
+ "src/common/golang/shard"
+ "src/common/golang/ziputils"
+ "src/tools/ak/types"
+)
+
+var (
+ // Cmd defines the command to run
+ Cmd = types.Command{
+ Init: Init,
+ Run: Run,
+ Desc: desc,
+ Flags: []string{
+ "desugar",
+ "android_jar",
+ "desugar_core_libs",
+ "classpath",
+ "d8",
+ "intermediate",
+ "in",
+ "out",
+ },
+ }
+
+ tmp struct {
+ Dir string
+ }
+
+ // Flag variables
+ desugar, androidJar, d8, in string
+ classpaths, outs, outputDir flags.StringList
+ desugarCoreLibs, intermediate bool
+
+ initOnce sync.Once
+)
+
+// Init initializes manifest flags
+func Init() {
+ initOnce.Do(func() {
+ flag.StringVar(&desugar, "desugar", "", "Path to desugar tool")
+ flag.StringVar(&androidJar, "android_jar", "", "Required for desugar, path to android.jar")
+ flag.Var(&classpaths, "classpath", "(Optional) Path to library resource(s) for desugar")
+ flag.BoolVar(&desugarCoreLibs, "desugar_core_libs", false, "Desugar Java 8 core libs, default false")
+ flag.StringVar(&d8, "d8", "", "Path to d8 dexer")
+ flag.BoolVar(&intermediate, "intermediate", false, "Compile for later merging, default false")
+ flag.StringVar(&in, "in", "", "Path to input")
+ flag.Var(&outs, "out", "Path to output, if more than one specified, output is sharded across files.")
+ })
+}
+
+func desc() string {
+ return "Dex converts Java byte code to Dex code."
+}
+
+// Run is the main entry point
+func Run() {
+ if desugar != "" && androidJar == "" {
+ log.Fatal("--android_jar is required for desugaring")
+ }
+ if d8 == "" || in == "" || outs == nil {
+ log.Fatal("Missing required flags. Must specify --d8 --in --out")
+ }
+ sc := len(outs)
+ if sc > 256 {
+ log.Fatalf("%d: is an unreasonable shard count (want [1 to 256])", sc)
+ }
+
+ var err error
+ tmp.Dir, err = ioutil.TempDir("", "dex")
+ if err != nil {
+ log.Fatalf("Error creating temp dir: %v", err)
+ }
+ defer os.RemoveAll(tmp.Dir)
+
+ notEmpty, err := hasCode(in)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if notEmpty {
+ jar := in
+ if desugar != "" {
+ jar = filepath.Join(tmp.Dir, "desugared.jar")
+ if err = desugarJar(in, jar); err != nil {
+ log.Fatalf("Error desugaring %v: %v", in, err)
+ }
+ }
+ if sc == 1 {
+ if err = dex(jar, outs[0]); err != nil {
+ log.Fatalf("Dex error: %v", err)
+ }
+ } else {
+ out := filepath.Join(tmp.Dir, "dexed.zip")
+ if err = dex(jar, out); err != nil {
+ log.Fatalf("Dex error: %v", err)
+ }
+ if err = zipShard(out, outs); err != nil {
+ log.Fatalf("ZipShard error: %v", err)
+ }
+ }
+ } else {
+ for _, out := range outs {
+ if err := ziputils.EmptyZip(out); err != nil {
+ log.Fatalf("Error creating empty zip archive: %v", err)
+ }
+ }
+ }
+}
+
+func createFlagFile(args []string) (string, error) {
+ f, err := ioutil.TempFile(tmp.Dir, "flags")
+ if err != nil {
+ return "", err
+ }
+ for _, arg := range args {
+ if _, err := f.WriteString(arg + "\n"); err != nil {
+ return "", err
+ }
+ }
+ if err := f.Close(); err != nil {
+ return "", err
+ }
+ return f.Name(), nil
+}
+
+func hasCode(f string) (bool, error) {
+ reader, err := zip.OpenReader(f)
+ if err != nil {
+ return false, fmt.Errorf("Opening zip %q failed: %v", f, err)
+ }
+ defer reader.Close()
+
+ for _, file := range reader.File {
+ ext := filepath.Ext(file.Name)
+ if ext == ".class" || ext == ".dex" {
+ return true, nil
+ }
+ }
+ return false, nil
+}
+
+func desugarJar(in, out string) error {
+ args := []string{
+ "--input",
+ in,
+ "--bootclasspath_entry",
+ androidJar,
+ "--output",
+ out,
+ }
+ if desugarCoreLibs {
+ args = append(args, "--desugar_supported_core_libs")
+ }
+ for _, cp := range classpaths {
+ args = append(args, "--classpath_entry", cp)
+ }
+ return runCmd(desugar, args)
+}
+
+func dex(in, out string) error {
+ args := []string{
+ "--min-api",
+ "21",
+ "--no-desugaring",
+ "--output",
+ out,
+ }
+ if intermediate {
+ args = append(args, "--file-per-class")
+ args = append(args, "--intermediate")
+ }
+ args = append(args, in)
+ return runCmd(d8, args)
+}
+
+func runCmd(cmd string, args []string) error {
+ flagFile, err := createFlagFile(args)
+ if err != nil {
+ return fmt.Errorf("Error creating flag file: %v", err)
+ }
+ output, err := exec.Command(cmd, "@"+flagFile).CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("%v:\n%s", err, output)
+ }
+ return nil
+}
+
+func zipShard(input string, outs []string) error {
+ zr, err := zip.OpenReader(input)
+ if err != nil {
+ return fmt.Errorf("%s: cannot open for input: %v", input, err)
+ }
+ defer zr.Close()
+
+ if len(outs) < 2 {
+ log.Fatalf("Need at least two output shards)")
+ }
+
+ zws := make([]*zip.Writer, len(outs))
+ for i, out := range outs {
+ outDir := filepath.Dir(out)
+ if _, err := os.Stat(outDir); os.IsNotExist(err) {
+ if err := os.MkdirAll(outDir, 0755); err != nil {
+ return fmt.Errorf("%s: could not make dir: %v", input, outDir)
+ }
+ }
+ outF, err := os.Create(out)
+ if err != nil {
+ return fmt.Errorf("%s: could not create output file: %s %v", out, outDir, err)
+ }
+ w := bufio.NewWriterSize(outF, 2<<16)
+ zw := zip.NewWriter(w)
+ defer func() error {
+ if err := zw.Close(); err != nil {
+ return fmt.Errorf("%s: closing zip failed: %v", out, err)
+ }
+ if err := w.Flush(); err != nil {
+ return fmt.Errorf("%s: flushing output file failed: %v", out, err)
+ }
+ if err := outF.Close(); err != nil {
+ return fmt.Errorf("%s: closing output file failed: %v", out, err)
+ }
+ return nil
+ }()
+ zws[i] = zw
+ }
+
+ err = shard.ZipShard(&zr.Reader, zws, shardFn)
+ if err != nil {
+ return fmt.Errorf("%s: sharder failed: %v", input, err)
+ }
+ return nil
+}
+
+func shardFn(name string, shardCount int) int {
+ // Sharding function which ensures that a class and all its inner classes are
+ // placed in the same shard. An important side effect of this is that all D8
+ // synthetics are in the same shard as their context, as a synthetic is named
+ // <context>$$ExternalSyntheticXXXN.
+ index := len(name)
+ if strings.HasSuffix(name, ".dex") {
+ index -= 4
+ } else {
+ log.Fatalf("Name expected to end with '.dex', was: %s", name)
+ }
+ trimIndex := strings.IndexAny(name, "$-")
+ if trimIndex > -1 {
+ index = trimIndex
+ }
+ return shard.FNV(name[:index], shardCount)
+}