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