| // 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 rjar generated R.jar. |
| package rjar |
| |
| import ( |
| "bufio" |
| "flag" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "log" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "strings" |
| "sync" |
| |
| "src/common/golang/ziputils" |
| "src/tools/ak/types" |
| ) |
| |
| var ( |
| // Cmd defines the command. |
| Cmd = types.Command{ |
| Init: Init, |
| Run: Run, |
| Desc: desc, |
| Flags: []string{"rjava", "pkgs", "rjar", "jdk", "jartool", "target_label", "jvm_opts"}, |
| } |
| |
| // Variables to hold flag values. |
| rjava string |
| pkgs string |
| rjar string |
| jdk string |
| jartool string |
| targetLabel string |
| jvmOpts string |
| |
| initOnce sync.Once |
| |
| javaReserved = map[string]bool{ |
| "abstract": true, |
| "assert": true, |
| "boolean": true, |
| "break": true, |
| "byte": true, |
| "case": true, |
| "catch": true, |
| "char": true, |
| "class": true, |
| "const": true, |
| "continue": true, |
| "default": true, |
| "do": true, |
| "double": true, |
| "else": true, |
| "enum": true, |
| "extends": true, |
| "false": true, |
| "final": true, |
| "finally": true, |
| "float": true, |
| "for": true, |
| "goto": true, |
| "if": true, |
| "implements": true, |
| "import": true, |
| "instanceof": true, |
| "int": true, |
| "interface": true, |
| "long": true, |
| "native": true, |
| "new": true, |
| "null": true, |
| "package": true, |
| "private": true, |
| "protected": true, |
| "public": true, |
| "return": true, |
| "short": true, |
| "static": true, |
| "strictfp": true, |
| "super": true, |
| "switch": true, |
| "synchronized": true, |
| "this": true, |
| "throw": true, |
| "throws": true, |
| "transient": true, |
| "true": true, |
| "try": true, |
| "void": true, |
| "volatile": true, |
| "while": true} |
| ) |
| |
| // Init initiailizes rjar action. Must be called before google.Init. |
| func Init() { |
| initOnce.Do(func() { |
| flag.StringVar(&rjava, "rjava", "", "Input R.java path") |
| flag.StringVar(&pkgs, "pkgs", "", "Packages file path") |
| flag.StringVar(&rjar, "rjar", "", "Output R.jar path") |
| flag.StringVar(&jdk, "jdk", "", "Jdk path") |
| flag.StringVar(&jartool, "jartool", "", "Jartool path") |
| flag.StringVar(&targetLabel, "target_label", "", "The target label") |
| flag.StringVar(&jvmOpts, "jvm_opts", "", "JVM options to pass to underlying tool") |
| }) |
| } |
| |
| func desc() string { |
| return "rjar creates the R.jar" |
| } |
| |
| // Run is the entry point for rjar. Will exit on error. |
| func Run() { |
| if err := doWork(rjava, pkgs, rjar, jdk, jartool, targetLabel, jvmOpts); err != nil { |
| log.Fatalf("Error creating R.jar: %v", err) |
| } |
| } |
| |
| func doWork(rjava, pkgs, rjar, jdk, jartool string, targetLabel string, jvmOpts string) error { |
| f, err := os.Stat(rjava) |
| if os.IsNotExist(err) || (err == nil && f.Size() == 0) { |
| // If we don't have an input r_java or have an empty r_java just write |
| // an empty jar apps might not define resources and in some cases (aar |
| // files) its not possible to know during analysis phase, so this action |
| // gets executed regardless. |
| return ziputils.EmptyZip(rjar) |
| } |
| if err != nil { |
| return fmt.Errorf("os.Stat(%s) failed: %v", rjava, err) |
| } |
| |
| srcDir, err := ioutil.TempDir("", "rjar") |
| if err != nil { |
| return err |
| } |
| defer os.RemoveAll(srcDir) |
| |
| var parentPkg, subclassTmpl string |
| var srcs []string |
| |
| filteredPkgs, err := getPkgs(pkgs) |
| if err != nil { |
| return err |
| } |
| for _, pkg := range filteredPkgs { |
| pkgParts := strings.Split(pkg, ".") |
| if hasInvalid(pkgParts) { |
| continue |
| } |
| pkgDir := filepath.Join(append([]string{srcDir}, pkgParts...)...) |
| err = os.MkdirAll(pkgDir, 0777) |
| if err != nil { |
| return err |
| } |
| outRJava := filepath.Join(pkgDir, "R.java") |
| srcs = append(srcs, outRJava) |
| if parentPkg == "" { |
| parentPkg = pkg |
| var classes []string |
| out, err := os.Create(outRJava) |
| if err != nil { |
| return err |
| } |
| defer out.Close() |
| in, err := os.Open(rjava) |
| if err != nil { |
| return err |
| } |
| defer in.Close() |
| if _, err := fmt.Fprintf(out, "package %s;", pkg); err != nil { |
| return err |
| } |
| if _, err := io.Copy(out, in); err != nil { |
| return err |
| } |
| if _, err := in.Seek(0, 0); err != nil { |
| return err |
| } |
| scanner := bufio.NewScanner(in) |
| for scanner.Scan() { |
| line := scanner.Text() |
| if strings.Contains(line, "public static class ") { |
| classes = append(classes, strings.Split(strings.Split(line, "public static class ")[1], " ")[0]) |
| } |
| } |
| subclassPts := []string{"package %s;", fmt.Sprintf("public class R extends %s.R {", pkg)} |
| for _, t := range classes { |
| subclassPts = append(subclassPts, fmt.Sprintf(" public static class %s extends %s.R.%s {}", t, pkg, t)) |
| } |
| subclassPts = append(subclassPts, "}") |
| subclassTmpl = strings.Join(subclassPts, "\n") |
| } else { |
| out, err := os.Create(outRJava) |
| if err != nil { |
| return err |
| } |
| defer out.Close() |
| fmt.Fprintf(out, subclassTmpl, pkg) |
| } |
| } |
| if _, err := os.Lstat(rjar); err == nil { |
| if err := os.Remove(rjar); err != nil { |
| return err |
| } |
| } |
| if err = os.MkdirAll(filepath.Dir(rjar), 0777); err != nil { |
| return err |
| } |
| return compileRJar(srcs, rjar, jdk, jartool, targetLabel, jvmOpts) |
| } |
| |
| func compileRJar(srcs []string, rjar, jdk, jartool string, targetLabel string, jvmOpts string) error { |
| control, err := ioutil.TempFile("", "control") |
| if err != nil { |
| return err |
| } |
| defer os.Remove(control.Name()) |
| |
| args := []string{"--javacopts", |
| "-source", "8", |
| "-target", "8", |
| "-nowarn", "--", "--sources"} |
| args = append(args, srcs...) |
| args = append(args, []string{ |
| "--strict_java_deps", "ERROR", |
| "--output", rjar, |
| }...) |
| if len(targetLabel) > 0 { |
| // Deal with "@//"-prefixed labels (in Bazel) |
| if strings.HasPrefix(targetLabel, "@//") { |
| targetLabel = strings.Replace(targetLabel, "@//", "//", 1) |
| } |
| |
| args = append(args, []string{ |
| "--target_label", targetLabel, |
| }...) |
| } |
| if _, err := fmt.Fprint(control, strings.Join(args, "\n")); err != nil { |
| return err |
| } |
| if err := control.Sync(); err != nil { |
| return err |
| } |
| jvmArgs := []string{jdk} |
| if len(jvmOpts) > 0 { |
| // Only append jvmOpts to the arg list if it's populated. Appending empty entries to the arg |
| // list can cause the command line to be parsed incorrectly. |
| jvmArgs = append(jvmArgs, strings.Split(jvmOpts, " ")...) |
| } |
| jvmArgs = append(jvmArgs, []string{ |
| "-jar", jartool, fmt.Sprintf("@%s", control.Name()), |
| }...) |
| c, err := exec.Command(jvmArgs[0], jvmArgs[1:]...).CombinedOutput() |
| if err != nil { |
| return fmt.Errorf("%v:\n%s", err, c) |
| } |
| return nil |
| } |
| |
| func getPkgs(pkgs string) ([]string, error) { |
| var filteredPkgs []string |
| seenPkgs := map[string]bool{} |
| |
| f, err := os.Open(pkgs) |
| if err != nil { |
| return nil, err |
| } |
| defer f.Close() |
| |
| scanner := bufio.NewScanner(f) |
| for scanner.Scan() { |
| pkg := strings.TrimSpace(scanner.Text()) |
| if strings.ContainsAny(pkg, "-$/") || pkg == "" { |
| continue |
| } |
| if seenPkgs[pkg] { |
| continue |
| } |
| filteredPkgs = append(filteredPkgs, pkg) |
| seenPkgs[pkg] = true |
| } |
| if err := scanner.Err(); err != nil { |
| return nil, err |
| } |
| return filteredPkgs, nil |
| } |
| |
| func hasInvalid(parts []string) bool { |
| for _, p := range parts { |
| if javaReserved[p] { |
| return true |
| } |
| } |
| return false |
| } |