kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/extractors/openjdk11/extract/extract.go (about)

     1  /*
     2   * Copyright 2019 The Kythe Authors. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *   http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  // Binary extract extracts a pre-configured OpenJDK11 source tree to produce Kythe compilation units.
    18  package main
    19  
    20  import (
    21  	"bufio"
    22  	"context"
    23  	"errors"
    24  	"flag"
    25  	"io"
    26  	"os"
    27  	"os/exec"
    28  	"os/user"
    29  	"path/filepath"
    30  	"regexp"
    31  	"strings"
    32  
    33  	"kythe.io/kythe/go/util/flagutil"
    34  	"kythe.io/kythe/go/util/log"
    35  
    36  	"github.com/bazelbuild/rules_go/go/tools/bazel"
    37  )
    38  
    39  const (
    40  	javaCommandVar      = "KYTHE_JAVA_COMMAND"
    41  	wrapperExtractorVar = "KYTHE_JAVA_EXTRACTOR_JAR"
    42  
    43  	kytheRootVar    = "KYTHE_ROOT_DIRECTORY"
    44  	kytheOutputVar  = "KYTHE_OUTPUT_DIRECTORY"
    45  	kytheCorpusVar  = "KYTHE_CORPUS"
    46  	kytheVNameVar   = "KYTHE_VNAMES"
    47  	kytheExcludeVar = "KYTHE_OPENJDK11_EXCLUDE_MODULES"
    48  
    49  	javaMakeVar           = "JAVA_CMD"
    50  	runfilesPrefix        = "${RUNFILES}"
    51  	runfilesWrapperPath   = "kythe/extractors/openjdk11/java_wrapper/java_wrapper"
    52  	runfilesVNamesPath    = "kythe/extractors/openjdk11/vnames.json"
    53  	runfilesExtractorPath = "kythe/java/com/google/devtools/kythe/extractors/java/standalone/javac_extractor_deploy.jar"
    54  )
    55  
    56  var (
    57  	sourceDir      string
    58  	buildDir       string
    59  	makeTargets    = targetList{"clean", "jdk"}
    60  	outputDir      string
    61  	excludeModules flagutil.StringSet
    62  	vNameRules     = defaultVNamesPath()
    63  	wrapperPath    = defaultWrapperPath()
    64  	extractorPath  = defaultExtractorPath()
    65  	errorPattern   = regexp.MustCompile("ERROR: extractor failure for module ([^:]*):")
    66  )
    67  
    68  // targetList is a simple comma-separated list of strings used
    69  // for the -targets flag.
    70  // flagutil.StringList always accumulates values, which we don't want.
    71  type targetList []string
    72  
    73  // Set implements part of the flag.Getter interface for targetList and will
    74  // set the new value from s.
    75  func (tl *targetList) Set(s string) error {
    76  	*tl = strings.Split(s, ",")
    77  	return nil
    78  }
    79  
    80  // String implements part of the flag.Getter interface for targetList and will
    81  // return the value as a comma-separate string.
    82  func (tl *targetList) String() string {
    83  	if tl == nil {
    84  		return ""
    85  	}
    86  	return strings.Join(*tl, ",")
    87  }
    88  
    89  // Get implements part of the flag.Getter interface for targetList.
    90  func (tl *targetList) Get() interface{} {
    91  	return []string(*tl)
    92  }
    93  
    94  // runfilePath is a simple flag wrapping a possibly runfiles-relative path.
    95  type runfilePath struct{ value string }
    96  
    97  // Set implements part of the flag.Getter interface for targetList and will
    98  // set the new value from s.
    99  func (rp *runfilePath) Set(s string) error {
   100  	rp.value = s
   101  	return nil
   102  }
   103  
   104  // String implements part of the flag.Getter interface for runfilePath
   105  // and will return the value.
   106  func (rp *runfilePath) String() string {
   107  	return rp.value
   108  }
   109  
   110  // Get implements part of the flag.Getter interface for runfilePath
   111  // and will return the value after replacing a ${RUNFILES} prefix.
   112  func (rp *runfilePath) Get() interface{} {
   113  	return rp.String()
   114  }
   115  
   116  // Expand returns the expanded runfile path from its argument.
   117  func (rp *runfilePath) Expand() string {
   118  	if path := strings.TrimPrefix(rp.value, "${RUNFILES}"); path != rp.value {
   119  		path, err := bazel.Runfile(path)
   120  		if err != nil {
   121  			panic(err)
   122  		}
   123  		return path
   124  	}
   125  	return rp.value
   126  }
   127  
   128  func setupRunfiles() error {
   129  	if os.Getenv("RUNFILES_DIR") != "" || os.Getenv("RUNFILES_MANIFEST_FILE") != "" {
   130  		return nil
   131  	}
   132  	for _, base := range []string{os.Args[0] + ".runfiles", "."} {
   133  		root, err := filepath.Abs(base)
   134  		if err != nil {
   135  			continue
   136  		} else if _, err := os.Stat(root); err != nil {
   137  			continue
   138  		}
   139  		os.Setenv("RUNFILES_DIR", root)
   140  		manifest := filepath.Join(root, "MANIFEST")
   141  		if _, err := os.Stat(manifest); err == nil {
   142  			os.Setenv("RUNFILES_MANIFEST_FILE", manifest)
   143  		}
   144  		return nil
   145  	}
   146  	return errors.New("unable to setup runfiles")
   147  }
   148  
   149  func defaultWrapperPath() runfilePath {
   150  	return runfilePath{filepath.Join(runfilesPrefix, runfilesWrapperPath)}
   151  }
   152  
   153  func defaultVNamesPath() runfilePath {
   154  	return runfilePath{filepath.Join(runfilesPrefix, runfilesVNamesPath)}
   155  }
   156  
   157  func defaultExtractorPath() runfilePath {
   158  	return runfilePath{filepath.Join(runfilesPrefix, runfilesExtractorPath)}
   159  }
   160  
   161  func defaultOutputDir() string {
   162  	val := os.Getenv(kytheOutputVar)
   163  	if val == "" {
   164  		usr, err := user.Current()
   165  		if err != nil {
   166  			log.Fatalf("ERROR: unable to determine current user: %v", err)
   167  		}
   168  		val = filepath.Join(usr.HomeDir, "kythe-openjdk11-output")
   169  	}
   170  	return val
   171  }
   172  
   173  func findJavaCommand() (string, error) {
   174  	ctx, cancel := context.WithCancel(context.Background())
   175  	defer cancel()
   176  	cmd := exec.CommandContext(ctx, "make", "-n", "-p")
   177  	cmd.Dir = buildDir
   178  	cmd.Stderr = os.Stderr
   179  	stdout, err := cmd.StdoutPipe()
   180  	if err != nil {
   181  		return "", err
   182  	}
   183  	if err := cmd.Start(); err != nil {
   184  		return "", err
   185  	}
   186  	const prefix = javaMakeVar + " := "
   187  	scanner := bufio.NewScanner(stdout)
   188  	for scanner.Scan() {
   189  		if strings.HasPrefix(scanner.Text(), prefix) {
   190  			cancel() // Safe to call repeatedly.
   191  			cmd.Wait()
   192  			return strings.TrimPrefix(scanner.Text(), prefix), nil
   193  		}
   194  	}
   195  	return "", cmd.Wait()
   196  }
   197  
   198  func mustFindJavaCommand() string {
   199  	java, err := findJavaCommand()
   200  	if err != nil {
   201  		log.Fatalf("unable to determine %s: %v", javaCommandVar, err)
   202  	}
   203  	return java
   204  }
   205  
   206  func setEnvDefaultFunc(env []string, key string, value func() string) []string {
   207  	if val := os.Getenv(key); val == "" {
   208  		env = append(env, key+"="+value())
   209  	}
   210  	return env
   211  }
   212  
   213  func setEnvDefault(env []string, key, value string) []string {
   214  	return setEnvDefaultFunc(env, key, func() string { return value })
   215  }
   216  
   217  func makeEnv() []string {
   218  	env := os.Environ()
   219  	env = setEnvDefaultFunc(env, javaCommandVar, mustFindJavaCommand)
   220  	env = setEnvDefault(env, kytheCorpusVar, "openjdk11")
   221  	if path := vNameRules.Expand(); path != "" {
   222  		env = setEnvDefault(env, kytheVNameVar, path)
   223  	}
   224  	if len(excludeModules) > 0 {
   225  		env = append(env, kytheExcludeVar+"="+excludeModules.String())
   226  	}
   227  	env = append(env,
   228  		kytheRootVar+"="+sourceDir,
   229  		kytheOutputVar+"="+outputDir,
   230  		wrapperExtractorVar+"="+extractorPath.Expand())
   231  	return env
   232  }
   233  
   234  func init() {
   235  	setupRunfiles()
   236  	flag.StringVar(&sourceDir, "jdk", "", "path to the OpenJDK11 source tree (required)")
   237  	flag.StringVar(&buildDir, "build", "", "path to the OpenJDK11 build tree (defaults to -jdk)")
   238  	flag.StringVar(&outputDir, "output", defaultOutputDir(), "path to which the compilations and errors should be written (optional)")
   239  	flag.Var(&vNameRules, "rules", "path of vnames.json file (optional)")
   240  	flag.Var(&wrapperPath, "java_wrapper", "path to the java_wrapper executable (optional)")
   241  	flag.Var(&extractorPath, "extractor_jar", "path to the javac_extractor_deploy.jar (optional)")
   242  	flag.Var(&makeTargets, "targets", "comma-separated list of make targets to build")
   243  	flag.Var(&excludeModules, "exclude_modules", "comma-separated set of module names to skip")
   244  	flag.Usage = flagutil.SimpleUsage("Extract a configured openjdk11 source directory", "[--java_wrapper=] [path]")
   245  }
   246  
   247  func main() {
   248  	flag.Parse()
   249  	if sourceDir == "" {
   250  		flagutil.UsageError("missing -jdk")
   251  	}
   252  	if wrapperPath.String() == "" {
   253  		flagutil.UsageError("missing -java_wrapper")
   254  	}
   255  	if _, err := os.Stat(wrapperPath.Expand()); err != nil {
   256  		flagutil.UsageErrorf("java_wrapper not found: %v", err)
   257  	}
   258  	if buildDir == "" {
   259  		buildDir = sourceDir
   260  	}
   261  
   262  	cmd := exec.Command("make", append([]string{javaMakeVar + "=" + wrapperPath.Expand(), "ENABLE_JAVAC_SERVER=no"}, makeTargets...)...)
   263  	cmd.Dir = buildDir
   264  	cmd.Env = makeEnv()
   265  	cmd.Stdout = nil // Quiet, you
   266  	stderr, err := cmd.StderrPipe()
   267  
   268  	if err != nil {
   269  		log.Fatal(err)
   270  	}
   271  
   272  	if err := cmd.Start(); err != nil {
   273  		log.Fatal(err)
   274  	}
   275  
   276  	logCollectedErrors(io.TeeReader(stderr, os.Stderr))
   277  
   278  	if err := cmd.Wait(); err != nil {
   279  		log.Fatal(err)
   280  	}
   281  }
   282  
   283  func collectExtractionErrors(stderr io.Reader) ([]string, error) {
   284  	var result []string
   285  	scanner := bufio.NewScanner(stderr)
   286  	for scanner.Scan() {
   287  		if subs := errorPattern.FindSubmatch(scanner.Bytes()); subs != nil {
   288  			result = append(result, string(subs[1]))
   289  		}
   290  	}
   291  	return result, scanner.Err()
   292  }
   293  
   294  func logCollectedErrors(stderr io.Reader) {
   295  	errors, err := collectExtractionErrors(stderr)
   296  	if err != nil {
   297  		log.Error(err)
   298  	}
   299  	if len(errors) > 0 {
   300  		log.Error("Error extracting modules:\n\t", strings.Join(errors, "\n\t"))
   301  	}
   302  }