blob: fd988f2ead62aaba7c2ac9ecd27f3c4ca06c7ea9 [file]
// 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
}