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 rjar generated R.jar. 16package rjar 17 18import ( 19 "bufio" 20 "flag" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "log" 25 "os" 26 "os/exec" 27 "path/filepath" 28 "strings" 29 "sync" 30 31 "src/common/golang/ziputils" 32 "src/tools/ak/types" 33) 34 35var ( 36 // Cmd defines the command. 37 Cmd = types.Command{ 38 Init: Init, 39 Run: Run, 40 Desc: desc, 41 Flags: []string{"rjava", "pkgs", "rjar", "jdk", "jartool", "target_label"}, 42 } 43 44 // Variables to hold flag values. 45 rjava string 46 pkgs string 47 rjar string 48 jdk string 49 jartool string 50 targetLabel string 51 52 initOnce sync.Once 53 54 javaReserved = map[string]bool{ 55 "abstract": true, 56 "assert": true, 57 "boolean": true, 58 "break": true, 59 "byte": true, 60 "case": true, 61 "catch": true, 62 "char": true, 63 "class": true, 64 "const": true, 65 "continue": true, 66 "default": true, 67 "do": true, 68 "double": true, 69 "else": true, 70 "enum": true, 71 "extends": true, 72 "false": true, 73 "final": true, 74 "finally": true, 75 "float": true, 76 "for": true, 77 "goto": true, 78 "if": true, 79 "implements": true, 80 "import": true, 81 "instanceof": true, 82 "int": true, 83 "interface": true, 84 "long": true, 85 "native": true, 86 "new": true, 87 "null": true, 88 "package": true, 89 "private": true, 90 "protected": true, 91 "public": true, 92 "return": true, 93 "short": true, 94 "static": true, 95 "strictfp": true, 96 "super": true, 97 "switch": true, 98 "synchronized": true, 99 "this": true, 100 "throw": true, 101 "throws": true, 102 "transient": true, 103 "true": true, 104 "try": true, 105 "void": true, 106 "volatile": true, 107 "while": true} 108) 109 110// Init initiailizes rjar action. Must be called before google.Init. 111func Init() { 112 initOnce.Do(func() { 113 flag.StringVar(&rjava, "rjava", "", "Input R.java path") 114 flag.StringVar(&pkgs, "pkgs", "", "Packages file path") 115 flag.StringVar(&rjar, "rjar", "", "Output R.jar path") 116 flag.StringVar(&jdk, "jdk", "", "Jdk path") 117 flag.StringVar(&jartool, "jartool", "", "Jartool path") 118 flag.StringVar(&targetLabel, "target_label", "", "The target label") 119 }) 120} 121 122func desc() string { 123 return "rjar creates the R.jar" 124} 125 126// Run is the entry point for rjar. Will exit on error. 127func Run() { 128 if err := doWork(rjava, pkgs, rjar, jdk, jartool, targetLabel); err != nil { 129 log.Fatalf("Error creating R.jar: %v", err) 130 } 131} 132 133func doWork(rjava, pkgs, rjar, jdk, jartool string, targetLabel string) error { 134 f, err := os.Stat(rjava) 135 if os.IsNotExist(err) || (err == nil && f.Size() == 0) { 136 // If we don't have an input r_java or have an empty r_java just write 137 // an empty jar apps might not define resources and in some cases (aar 138 // files) its not possible to know during analysis phase, so this action 139 // gets executed regardless. 140 return ziputils.EmptyZip(rjar) 141 } 142 if err != nil { 143 return fmt.Errorf("os.Stat(%s) failed: %v", rjava, err) 144 } 145 146 srcDir, err := ioutil.TempDir("", "rjar") 147 if err != nil { 148 return err 149 } 150 defer os.RemoveAll(srcDir) 151 152 var parentPkg, subclassTmpl string 153 var srcs []string 154 155 filteredPkgs, err := getPkgs(pkgs) 156 if err != nil { 157 return err 158 } 159 for _, pkg := range filteredPkgs { 160 pkgParts := strings.Split(pkg, ".") 161 if hasInvalid(pkgParts) { 162 continue 163 } 164 pkgDir := filepath.Join(append([]string{srcDir}, pkgParts...)...) 165 err = os.MkdirAll(pkgDir, 0777) 166 if err != nil { 167 return err 168 } 169 outRJava := filepath.Join(pkgDir, "R.java") 170 srcs = append(srcs, outRJava) 171 if parentPkg == "" { 172 parentPkg = pkg 173 var classes []string 174 out, err := os.Create(outRJava) 175 if err != nil { 176 return err 177 } 178 defer out.Close() 179 in, err := os.Open(rjava) 180 if err != nil { 181 return err 182 } 183 defer in.Close() 184 if _, err := fmt.Fprintf(out, "package %s;", pkg); err != nil { 185 return err 186 } 187 if _, err := io.Copy(out, in); err != nil { 188 return err 189 } 190 if _, err := in.Seek(0, 0); err != nil { 191 return err 192 } 193 scanner := bufio.NewScanner(in) 194 for scanner.Scan() { 195 line := scanner.Text() 196 if strings.Contains(line, "public static class ") { 197 classes = append(classes, strings.Split(strings.Split(line, "public static class ")[1], " ")[0]) 198 } 199 } 200 subclassPts := []string{"package %s;", fmt.Sprintf("public class R extends %s.R {", pkg)} 201 for _, t := range classes { 202 subclassPts = append(subclassPts, fmt.Sprintf(" public static class %s extends %s.R.%s {}", t, pkg, t)) 203 } 204 subclassPts = append(subclassPts, "}") 205 subclassTmpl = strings.Join(subclassPts, "\n") 206 } else { 207 out, err := os.Create(outRJava) 208 if err != nil { 209 return err 210 } 211 defer out.Close() 212 fmt.Fprintf(out, subclassTmpl, pkg) 213 } 214 } 215 if _, err := os.Lstat(rjar); err == nil { 216 if err := os.Remove(rjar); err != nil { 217 return err 218 } 219 } 220 if err = os.MkdirAll(filepath.Dir(rjar), 0777); err != nil { 221 return err 222 } 223 return compileRJar(srcs, rjar, jdk, jartool, targetLabel) 224} 225 226func compileRJar(srcs []string, rjar, jdk, jartool string, targetLabel string) error { 227 control, err := ioutil.TempFile("", "control") 228 if err != nil { 229 return err 230 } 231 defer os.Remove(control.Name()) 232 233 args := []string{"--javacopts", 234 "-source", "8", 235 "-target", "8", 236 "-nowarn", "--", "--sources"} 237 args = append(args, srcs...) 238 args = append(args, []string{ 239 "--strict_java_deps", "ERROR", 240 "--output", rjar, 241 }...) 242 if len(targetLabel) > 0 { 243 // Deal with "@//"-prefixed labels (in Bazel) 244 if strings.HasPrefix(targetLabel, "@//") { 245 targetLabel = strings.Replace(targetLabel, "@//", "//", 1) 246 } 247 248 args = append(args, []string{ 249 "--target_label", targetLabel, 250 }...) 251 } 252 if _, err := fmt.Fprint(control, strings.Join(args, "\n")); err != nil { 253 return err 254 } 255 if err := control.Sync(); err != nil { 256 return err 257 } 258 c, err := exec.Command(jdk, "-jar", jartool, fmt.Sprintf("@%s", control.Name())).CombinedOutput() 259 if err != nil { 260 return fmt.Errorf("%v:\n%s", err, c) 261 } 262 return nil 263} 264 265func getPkgs(pkgs string) ([]string, error) { 266 var filteredPkgs []string 267 seenPkgs := map[string]bool{} 268 269 f, err := os.Open(pkgs) 270 if err != nil { 271 return nil, err 272 } 273 defer f.Close() 274 275 scanner := bufio.NewScanner(f) 276 for scanner.Scan() { 277 pkg := strings.TrimSpace(scanner.Text()) 278 if strings.ContainsAny(pkg, "-$/") || pkg == "" { 279 continue 280 } 281 if seenPkgs[pkg] { 282 continue 283 } 284 filteredPkgs = append(filteredPkgs, pkg) 285 seenPkgs[pkg] = true 286 } 287 if err := scanner.Err(); err != nil { 288 return nil, err 289 } 290 return filteredPkgs, nil 291} 292 293func hasInvalid(parts []string) bool { 294 for _, p := range parts { 295 if javaReserved[p] { 296 return true 297 } 298 } 299 return false 300} 301