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 }