kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/extractors/openjdk11/java_wrapper/java_wrapper.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 java_wrapper wraps the real java command to invoke the standalone java extractor
    18  // in parallel with the genuine compilation command.
    19  // As it interjects itself between make and a real java command, all options are
    20  // provided via environment variables.  See the corresponding consts and java_extractor for details.
    21  package main
    22  
    23  import (
    24  	"fmt"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  	"regexp"
    29  	"strings"
    30  
    31  	"kythe.io/kythe/go/util/log"
    32  
    33  	"bitbucket.org/creachadair/shell"
    34  	"bitbucket.org/creachadair/stringset"
    35  	"golang.org/x/sys/unix"
    36  )
    37  
    38  const (
    39  	javaCommandVar    = "KYTHE_JAVA_COMMAND"              // Path to the real java command, required.
    40  	extractorJarVar   = "KYTHE_JAVA_EXTRACTOR_JAR"        // Path to the javac_extractor jar, required.
    41  	kytheOutputVar    = "KYTHE_OUTPUT_DIRECTORY"          // Extraction output directory, required.
    42  	kytheTargetVar    = "KYTHE_ANALYSIS_TARGET"           // Extraction analysis target to set, defaults to the java module name.
    43  	excludeModulesVar = "KYTHE_OPENJDK11_EXCLUDE_MODULES" // Names of for which to skip extraction.
    44  )
    45  
    46  var (
    47  	modulePattern = regexp.MustCompile("^@/.*/_the.(.*)_batch.tmp$")
    48  )
    49  
    50  func moduleName() string {
    51  	path := os.Args[len(os.Args)-1]
    52  	// Hackish way to determine the likely module being compiled.
    53  	repl := modulePattern.ReplaceAllString(path, "$1")
    54  	if repl == path { // No match, use dirname and basename to form the module.
    55  		return filepath.Base(filepath.Dir(path)) + "#" + filepath.Base(path)
    56  	}
    57  	return repl
    58  }
    59  
    60  func outputDir() string {
    61  	if val := os.Getenv(kytheOutputVar); val != "" {
    62  		return val
    63  	}
    64  	log.Fatal("ERROR: KYTHE_OUTPUT_DIRECTORY not set")
    65  	return ""
    66  }
    67  
    68  func mustGetEnvPath(key string) string {
    69  	if val := os.Getenv(key); val != "" {
    70  		if _, err := os.Stat(val); err != nil {
    71  			log.Fatalf("invalid %s: %v", key, err)
    72  		}
    73  		return val
    74  	}
    75  	log.Fatal(key + " not set")
    76  	return ""
    77  }
    78  
    79  func loadExclusions() stringset.Set {
    80  	if value := os.Getenv(excludeModulesVar); value != "" {
    81  		return stringset.FromKeys(strings.Split(value, ","))
    82  	}
    83  	return stringset.Set{}
    84  }
    85  
    86  func javaCommand() string {
    87  	return mustGetEnvPath(javaCommandVar)
    88  }
    89  
    90  func extractorJar() string {
    91  	return mustGetEnvPath(extractorJarVar)
    92  }
    93  
    94  func extractorArgs(args []string, jar string) []string {
    95  	isJavac := false
    96  	var result []string
    97  	for len(args) > 0 {
    98  		var a string
    99  		var v string
   100  		switch a, args = shift(args); a {
   101  		case "-m", "--module":
   102  			v, args = shift(args)
   103  			if !strings.HasSuffix(v, ".javac.Main") {
   104  				isJavac = false
   105  				break
   106  			}
   107  			isJavac = true
   108  			result = append(result,
   109  				"--add-modules=java.logging,java.sql",
   110  				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.main=ALL-UNNAMED",
   111  				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.util=ALL-UNNAMED",
   112  				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.file=ALL-UNNAMED",
   113  				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.api=ALL-UNNAMED",
   114  				"--add-exports=jdk.compiler.interim/com.sun.tools.javac.code=ALL-UNNAMED",
   115  				"-jar", jar, "-Xprefer:source")
   116  		case "--doclint-format":
   117  			_, args = shift(args)
   118  		case "-Werror":
   119  		default:
   120  			switch {
   121  			case strings.HasPrefix(a, "-Xplugin:depend"), strings.HasPrefix(a, "-Xlint:"), strings.HasPrefix(a, "-Xdoclint"):
   122  			case strings.HasPrefix(a, "-Xmx"):
   123  				result = append(result, "-Xmx3G")
   124  			case !isJavac && strings.HasPrefix(a, "--") && len(args) > 0 && !strings.HasPrefix(args[0], "-"):
   125  				// Add an = separator between "--arg" and its value for JVM arguments
   126  				// in order to be friendlier to Bazel Java "launchers".
   127  				// The JVM generally accepts either form, but the generic argument processing
   128  				// in many launchers requires the "=" separator.
   129  				v, args = shift(args)
   130  				result = append(result, a+"="+v)
   131  			default:
   132  				result = append(result, a)
   133  			}
   134  		}
   135  	}
   136  	// As we can only do anything meaningful with Java compilations,
   137  	// but wrap the java binary, don't attempt to extract other invocations.
   138  	if !isJavac {
   139  		return nil
   140  	}
   141  	return result
   142  }
   143  
   144  func extractorEnv() []string {
   145  	env := os.Environ()
   146  	env = setEnvDefault(env, kytheTargetVar, moduleName())
   147  	return env
   148  }
   149  
   150  func setEnvDefault(env []string, key, def string) []string {
   151  	if val := os.Getenv(key); val == "" {
   152  		env = append(env, key+"="+def)
   153  	}
   154  	return env
   155  }
   156  
   157  func shift(args []string) (string, []string) {
   158  	if len(args) > 0 {
   159  		return args[0], args[1:]
   160  	}
   161  	return "", nil
   162  }
   163  
   164  func main() {
   165  	excludeModules := loadExclusions()
   166  	java := javaCommand()
   167  	jar := extractorJar()
   168  	if args := extractorArgs(os.Args[1:], jar); len(args) > 0 {
   169  		if excludeModules.Contains(moduleName()) {
   170  			log.Infof("*** Skipping: %s", moduleName())
   171  		} else {
   172  			cmd := exec.Command(java, args...)
   173  			cmd.Env = extractorEnv()
   174  			log.Infof("*** Extracting: %s", moduleName())
   175  			if output, err := cmd.CombinedOutput(); err != nil {
   176  				w, err := os.Create(filepath.Join(outputDir(), moduleName()+".err"))
   177  				if err != nil {
   178  					log.Fatalf("Error creating error log for module %s: %v", moduleName(), err)
   179  				}
   180  				fmt.Fprintf(w, "--- %s\n", shell.Join(args))
   181  				w.Write(output)
   182  				w.Close()
   183  
   184  				// Log, but don't abort, on extraction failures.
   185  				log.Errorf("extractor failure for module %s: %v", moduleName(), err)
   186  			}
   187  		}
   188  	}
   189  	// Always end by running the java command directly, as "java".
   190  	os.Args[0] = "java"
   191  	log.Fatal(unix.Exec(java, os.Args, os.Environ())) // If exec returns at all, it's an error.
   192  }