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