1// Copyright 2018 The Bazel Authors. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package dex provides a thin wrapper around d8 to handle corner cases 16package dex 17 18import ( 19 "archive/zip" 20 "bufio" 21 "flag" 22 "fmt" 23 "io/ioutil" 24 "log" 25 "os" 26 "os/exec" 27 "path/filepath" 28 "strings" 29 "sync" 30 31 "src/common/golang/flags" 32 "src/common/golang/shard" 33 "src/common/golang/ziputils" 34 "src/tools/ak/types" 35) 36 37var ( 38 // Cmd defines the command to run 39 Cmd = types.Command{ 40 Init: Init, 41 Run: Run, 42 Desc: desc, 43 Flags: []string{ 44 "desugar", 45 "android_jar", 46 "desugar_core_libs", 47 "classpath", 48 "d8", 49 "intermediate", 50 "in", 51 "out", 52 }, 53 } 54 55 tmp struct { 56 Dir string 57 } 58 59 // Flag variables 60 desugar, androidJar, d8, in string 61 classpaths, outs, outputDir flags.StringList 62 desugarCoreLibs, intermediate bool 63 64 initOnce sync.Once 65) 66 67// Init initializes manifest flags 68func Init() { 69 initOnce.Do(func() { 70 flag.StringVar(&desugar, "desugar", "", "Path to desugar tool") 71 flag.StringVar(&androidJar, "android_jar", "", "Required for desugar, path to android.jar") 72 flag.Var(&classpaths, "classpath", "(Optional) Path to library resource(s) for desugar") 73 flag.BoolVar(&desugarCoreLibs, "desugar_core_libs", false, "Desugar Java 8 core libs, default false") 74 flag.StringVar(&d8, "d8", "", "Path to d8 dexer") 75 flag.BoolVar(&intermediate, "intermediate", false, "Compile for later merging, default false") 76 flag.StringVar(&in, "in", "", "Path to input") 77 flag.Var(&outs, "out", "Path to output, if more than one specified, output is sharded across files.") 78 }) 79} 80 81func desc() string { 82 return "Dex converts Java byte code to Dex code." 83} 84 85// Run is the main entry point 86func Run() { 87 if desugar != "" && androidJar == "" { 88 log.Fatal("--android_jar is required for desugaring") 89 } 90 if d8 == "" || in == "" || outs == nil { 91 log.Fatal("Missing required flags. Must specify --d8 --in --out") 92 } 93 sc := len(outs) 94 if sc > 256 { 95 log.Fatalf("%d: is an unreasonable shard count (want [1 to 256])", sc) 96 } 97 98 var err error 99 tmp.Dir, err = ioutil.TempDir("", "dex") 100 if err != nil { 101 log.Fatalf("Error creating temp dir: %v", err) 102 } 103 defer os.RemoveAll(tmp.Dir) 104 105 notEmpty, err := hasCode(in) 106 if err != nil { 107 log.Fatal(err) 108 } 109 110 if notEmpty { 111 jar := in 112 if desugar != "" { 113 jar = filepath.Join(tmp.Dir, "desugared.jar") 114 if err = desugarJar(in, jar); err != nil { 115 log.Fatalf("Error desugaring %v: %v", in, err) 116 } 117 } 118 if sc == 1 { 119 if err = dex(jar, outs[0]); err != nil { 120 log.Fatalf("Dex error: %v", err) 121 } 122 } else { 123 out := filepath.Join(tmp.Dir, "dexed.zip") 124 if err = dex(jar, out); err != nil { 125 log.Fatalf("Dex error: %v", err) 126 } 127 if err = zipShard(out, outs); err != nil { 128 log.Fatalf("ZipShard error: %v", err) 129 } 130 } 131 } else { 132 for _, out := range outs { 133 if err := ziputils.EmptyZip(out); err != nil { 134 log.Fatalf("Error creating empty zip archive: %v", err) 135 } 136 } 137 } 138} 139 140func createFlagFile(args []string) (string, error) { 141 f, err := ioutil.TempFile(tmp.Dir, "flags") 142 if err != nil { 143 return "", err 144 } 145 for _, arg := range args { 146 if _, err := f.WriteString(arg + "\n"); err != nil { 147 return "", err 148 } 149 } 150 if err := f.Close(); err != nil { 151 return "", err 152 } 153 return f.Name(), nil 154} 155 156func hasCode(f string) (bool, error) { 157 reader, err := zip.OpenReader(f) 158 if err != nil { 159 return false, fmt.Errorf("Opening zip %q failed: %v", f, err) 160 } 161 defer reader.Close() 162 163 for _, file := range reader.File { 164 ext := filepath.Ext(file.Name) 165 if ext == ".class" || ext == ".dex" { 166 return true, nil 167 } 168 } 169 return false, nil 170} 171 172func desugarJar(in, out string) error { 173 args := []string{ 174 "--input", 175 in, 176 "--bootclasspath_entry", 177 androidJar, 178 "--output", 179 out, 180 } 181 if desugarCoreLibs { 182 args = append(args, "--desugar_supported_core_libs") 183 } 184 for _, cp := range classpaths { 185 args = append(args, "--classpath_entry", cp) 186 } 187 return runCmd(desugar, args) 188} 189 190func dex(in, out string) error { 191 args := []string{ 192 "--min-api", 193 "21", 194 "--no-desugaring", 195 "--output", 196 out, 197 } 198 if intermediate { 199 args = append(args, "--file-per-class") 200 args = append(args, "--intermediate") 201 } 202 args = append(args, in) 203 return runCmd(d8, args) 204} 205 206func runCmd(cmd string, args []string) error { 207 flagFile, err := createFlagFile(args) 208 if err != nil { 209 return fmt.Errorf("Error creating flag file: %v", err) 210 } 211 output, err := exec.Command(cmd, "@"+flagFile).CombinedOutput() 212 if err != nil { 213 return fmt.Errorf("%v:\n%s", err, output) 214 } 215 return nil 216} 217 218func zipShard(input string, outs []string) error { 219 zr, err := zip.OpenReader(input) 220 if err != nil { 221 return fmt.Errorf("%s: cannot open for input: %v", input, err) 222 } 223 defer zr.Close() 224 225 if len(outs) < 2 { 226 log.Fatalf("Need at least two output shards)") 227 } 228 229 zws := make([]*zip.Writer, len(outs)) 230 for i, out := range outs { 231 outDir := filepath.Dir(out) 232 if _, err := os.Stat(outDir); os.IsNotExist(err) { 233 if err := os.MkdirAll(outDir, 0755); err != nil { 234 return fmt.Errorf("%s: could not make dir: %v", input, outDir) 235 } 236 } 237 outF, err := os.Create(out) 238 if err != nil { 239 return fmt.Errorf("%s: could not create output file: %s %v", out, outDir, err) 240 } 241 w := bufio.NewWriterSize(outF, 2<<16) 242 zw := zip.NewWriter(w) 243 defer func() error { 244 if err := zw.Close(); err != nil { 245 return fmt.Errorf("%s: closing zip failed: %v", out, err) 246 } 247 if err := w.Flush(); err != nil { 248 return fmt.Errorf("%s: flushing output file failed: %v", out, err) 249 } 250 if err := outF.Close(); err != nil { 251 return fmt.Errorf("%s: closing output file failed: %v", out, err) 252 } 253 return nil 254 }() 255 zws[i] = zw 256 } 257 258 err = shard.ZipShard(&zr.Reader, zws, shardFn) 259 if err != nil { 260 return fmt.Errorf("%s: sharder failed: %v", input, err) 261 } 262 return nil 263} 264 265func shardFn(name string, shardCount int) int { 266 // Sharding function which ensures that a class and all its inner classes are 267 // placed in the same shard. An important side effect of this is that all D8 268 // synthetics are in the same shard as their context, as a synthetic is named 269 // <context>$$ExternalSyntheticXXXN. 270 index := len(name) 271 if strings.HasSuffix(name, ".dex") { 272 index -= 4 273 } else { 274 log.Fatalf("Name expected to end with '.dex', was: %s", name) 275 } 276 trimIndex := strings.IndexAny(name, "$-") 277 if trimIndex > -1 { 278 index = trimIndex 279 } 280 return shard.FNV(name[:index], shardCount) 281} 282