1 /* 2 * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 package toolbox; 25 26 import java.io.File; 27 import java.io.IOException; 28 import java.io.PrintWriter; 29 import java.nio.file.Path; 30 import java.nio.file.Paths; 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.Collections; 34 import java.util.HashMap; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.function.Consumer; 38 import java.util.stream.Collectors; 39 import java.util.stream.Stream; 40 import javax.tools.JavaCompiler; 41 import javax.tools.JavaFileManager; 42 import javax.tools.JavaFileObject; 43 import javax.tools.StandardJavaFileManager; 44 import javax.tools.StandardLocation; 45 46 import com.sun.tools.javac.api.JavacTaskImpl; 47 import com.sun.tools.javac.api.JavacTool; 48 49 /** 50 * A task to configure and run the Java compiler, javac. 51 */ 52 public class JavacTask extends AbstractTask<JavacTask> { 53 private boolean includeStandardOptions; 54 private List<Path> classpath; 55 private List<Path> sourcepath; 56 private Path outdir; 57 private List<String> options; 58 private List<String> classes; 59 private List<String> files; 60 private List<JavaFileObject> fileObjects; 61 private JavaFileManager fileManager; 62 private Consumer<com.sun.source.util.JavacTask> callback; 63 64 private JavaCompiler compiler; 65 private StandardJavaFileManager internalFileManager; 66 67 /** 68 * Creates a task to execute {@code javac} using API mode. 69 * @param toolBox the {@code ToolBox} to use 70 */ 71 public JavacTask(ToolBox toolBox) { 72 super(toolBox, Task.Mode.API); 73 } 74 75 /** 76 * Creates a task to execute {@code javac} in a specified mode. 77 * @param toolBox the {@code ToolBox} to use 78 * @param mode the mode to be used 79 */ 80 public JavacTask(ToolBox toolBox, Task.Mode mode) { 81 super(toolBox, mode); 82 } 83 84 /** 85 * Sets the classpath. 86 * @param classpath the classpath 87 * @return this task object 88 */ 89 public JavacTask classpath(String classpath) { 90 this.classpath = Stream.of(classpath.split(File.pathSeparator)) 91 .filter(s -> !s.isEmpty()) 92 .map(s -> Paths.get(s)) 93 .collect(Collectors.toList()); 94 return this; 95 } 96 97 /** 98 * Sets the classpath. 99 * @param classpath the classpath 100 * @return this task object 101 */ 102 public JavacTask classpath(Path... classpath) { 103 this.classpath = Arrays.asList(classpath); 104 return this; 105 } 106 107 /** 108 * Sets the classpath. 109 * @param classpath the classpath 110 * @return this task object 111 */ 112 public JavacTask classpath(List<Path> classpath) { 113 this.classpath = classpath; 114 return this; 115 } 116 117 /** 118 * Sets the sourcepath. 119 * @param sourcepath the sourcepath 120 * @return this task object 121 */ 122 public JavacTask sourcepath(String sourcepath) { 123 this.sourcepath = Stream.of(sourcepath.split(File.pathSeparator)) 124 .filter(s -> !s.isEmpty()) 125 .map(s -> Paths.get(s)) 126 .collect(Collectors.toList()); 127 return this; 128 } 129 130 /** 131 * Sets the sourcepath. 132 * @param sourcepath the sourcepath 133 * @return this task object 134 */ 135 public JavacTask sourcepath(Path... sourcepath) { 136 this.sourcepath = Arrays.asList(sourcepath); 137 return this; 138 } 139 140 /** 141 * Sets the sourcepath. 142 * @param sourcepath the sourcepath 143 * @return this task object 144 */ 145 public JavacTask sourcepath(List<Path> sourcepath) { 146 this.sourcepath = sourcepath; 147 return this; 148 } 149 150 /** 151 * Sets the output directory. 152 * @param outdir the output directory 153 * @return this task object 154 */ 155 public JavacTask outdir(String outdir) { 156 this.outdir = Paths.get(outdir); 157 return this; 158 } 159 160 /** 161 * Sets the output directory. 162 * @param outdir the output directory 163 * @return this task object 164 */ 165 public JavacTask outdir(Path outdir) { 166 this.outdir = outdir; 167 return this; 168 } 169 170 /** 171 * Sets the options. 172 * @param options the options 173 * @return this task object 174 */ 175 public JavacTask options(String... options) { 176 this.options = Arrays.asList(options); 177 return this; 178 } 179 180 /** 181 * Sets the options. 182 * @param options the options 183 * @return this task object 184 */ 185 public JavacTask options(List<String> options) { 186 this.options = options; 187 return this; 188 } 189 190 /** 191 * Sets the classes to be analyzed. 192 * @param classes the classes 193 * @return this task object 194 */ 195 public JavacTask classes(String... classes) { 196 this.classes = Arrays.asList(classes); 197 return this; 198 } 199 200 /** 201 * Sets the files to be compiled or analyzed. 202 * @param files the files 203 * @return this task object 204 */ 205 public JavacTask files(String... files) { 206 this.files = Arrays.asList(files); 207 return this; 208 } 209 210 /** 211 * Sets the files to be compiled or analyzed. 212 * @param files the files 213 * @return this task object 214 */ 215 public JavacTask files(Path... files) { 216 this.files = Stream.of(files) 217 .map(Path::toString) 218 .collect(Collectors.toList()); 219 return this; 220 } 221 222 /** 223 * Sets the files to be compiled or analyzed. 224 * @param files the files 225 * @return this task object 226 */ 227 public JavacTask files(List<Path> files) { 228 this.files = files.stream() 229 .map(Path::toString) 230 .collect(Collectors.toList()); 231 return this; 232 } 233 234 /** 235 * Sets the sources to be compiled or analyzed. 236 * Each source string is converted into an in-memory object that 237 * can be passed directly to the compiler. 238 * @param sources the sources 239 * @return this task object 240 */ 241 public JavacTask sources(String... sources) { 242 fileObjects = Stream.of(sources) 243 .map(s -> new ToolBox.JavaSource(s)) 244 .collect(Collectors.toList()); 245 return this; 246 } 247 248 /** 249 * Sets the file manager to be used by this task. 250 * @param fileManager the file manager 251 * @return this task object 252 */ 253 public JavacTask fileManager(JavaFileManager fileManager) { 254 this.fileManager = fileManager; 255 return this; 256 } 257 258 /** 259 * Set a callback to be used by this task. 260 * @param callback the callback 261 * @return this task object 262 */ 263 public JavacTask callback(Consumer<com.sun.source.util.JavacTask> callback) { 264 this.callback = callback; 265 return this; 266 } 267 268 /** 269 * {@inheritDoc} 270 * @return the name "javac" 271 */ 272 @Override 273 public String name() { 274 return "javac"; 275 } 276 277 /** 278 * Calls the compiler with the arguments as currently configured. 279 * @return a Result object indicating the outcome of the compilation 280 * and the content of any output written to stdout, stderr, or the 281 * main stream by the compiler. 282 * @throws TaskError if the outcome of the task is not as expected. 283 */ 284 @Override 285 public Task.Result run() { 286 if (mode == Task.Mode.EXEC) 287 return runExec(); 288 289 AbstractTask.WriterOutput direct = new AbstractTask.WriterOutput(); 290 // The following are to catch output to System.out and System.err, 291 // in case these are used instead of the primary (main) stream 292 AbstractTask.StreamOutput sysOut = new AbstractTask.StreamOutput(System.out, System::setOut); 293 AbstractTask.StreamOutput sysErr = new AbstractTask.StreamOutput(System.err, System::setErr); 294 int rc; 295 Map<Task.OutputKind, String> outputMap = new HashMap<>(); 296 try { 297 switch (mode == null ? Task.Mode.API : mode) { 298 case API: 299 rc = runAPI(direct.pw); 300 break; 301 case CMDLINE: 302 if (fileManager != null) { 303 throw new IllegalStateException("file manager set in CMDLINE mode"); 304 } 305 if (callback != null) { 306 throw new IllegalStateException("callback set in CMDLINE mode"); 307 } 308 rc = runCommand(direct.pw); 309 break; 310 default: 311 throw new IllegalStateException("unknown mode " + mode); 312 } 313 } catch (IOException e) { 314 toolBox.out.println("Exception occurred: " + e); 315 rc = 99; 316 } finally { 317 outputMap.put(Task.OutputKind.STDOUT, sysOut.close()); 318 outputMap.put(Task.OutputKind.STDERR, sysErr.close()); 319 outputMap.put(Task.OutputKind.DIRECT, direct.close()); 320 } 321 return checkExit(new Task.Result(toolBox, this, rc, outputMap)); 322 } 323 324 private int runAPI(PrintWriter pw) throws IOException { 325 try { 326 // if (compiler == null) { 327 // TODO: allow this to be set externally 328 // compiler = ToolProvider.getSystemJavaCompiler(); 329 compiler = JavacTool.create(); 330 // } 331 332 if (fileManager == null) 333 fileManager = internalFileManager = compiler.getStandardFileManager(null, null, null); 334 if (outdir != null) 335 setLocationFromPaths(StandardLocation.CLASS_OUTPUT, Collections.singletonList(outdir)); 336 if (classpath != null) 337 setLocationFromPaths(StandardLocation.CLASS_PATH, classpath); 338 if (sourcepath != null) 339 setLocationFromPaths(StandardLocation.SOURCE_PATH, sourcepath); 340 List<String> allOpts = new ArrayList<>(); 341 if (options != null) 342 allOpts.addAll(options); 343 344 Iterable<? extends JavaFileObject> allFiles = joinFiles(files, fileObjects); 345 JavaCompiler.CompilationTask task = compiler.getTask(pw, 346 fileManager, 347 null, // diagnostic listener; should optionally collect diags 348 allOpts, 349 classes, 350 allFiles); 351 JavacTaskImpl taskImpl = (JavacTaskImpl) task; 352 if (callback != null) { 353 callback.accept(taskImpl); 354 } 355 return taskImpl.doCall().exitCode; 356 } finally { 357 if (internalFileManager != null) 358 internalFileManager.close(); 359 } 360 } 361 362 private void setLocationFromPaths(StandardLocation location, List<Path> files) throws IOException { 363 if (!(fileManager instanceof StandardJavaFileManager)) 364 throw new IllegalStateException("not a StandardJavaFileManager"); 365 ((StandardJavaFileManager) fileManager).setLocationFromPaths(location, files); 366 } 367 368 private int runCommand(PrintWriter pw) { 369 List<String> args = getAllArgs(); 370 String[] argsArray = args.toArray(new String[args.size()]); 371 return com.sun.tools.javac.Main.compile(argsArray, pw); 372 } 373 374 private Task.Result runExec() { 375 List<String> args = new ArrayList<>(); 376 Path javac = toolBox.getJDKTool("javac"); 377 args.add(javac.toString()); 378 if (includeStandardOptions) { 379 args.addAll(toolBox.split(System.getProperty("test.tool.vm.opts"), " +")); 380 args.addAll(toolBox.split(System.getProperty("test.compiler.opts"), " +")); 381 } 382 args.addAll(getAllArgs()); 383 384 String[] argsArray = args.toArray(new String[args.size()]); 385 ProcessBuilder pb = getProcessBuilder(); 386 pb.command(argsArray); 387 try { 388 return runProcess(toolBox, this, pb.start()); 389 } catch (IOException | InterruptedException e) { 390 throw new Error(e); 391 } 392 } 393 394 private List<String> getAllArgs() { 395 List<String> args = new ArrayList<>(); 396 if (options != null) 397 args.addAll(options); 398 if (outdir != null) { 399 args.add("-d"); 400 args.add(outdir.toString()); 401 } 402 if (classpath != null) { 403 args.add("-classpath"); 404 args.add(toSearchPath(classpath)); 405 } 406 if (sourcepath != null) { 407 args.add("-sourcepath"); 408 args.add(toSearchPath(sourcepath)); 409 } 410 if (classes != null) 411 args.addAll(classes); 412 if (files != null) 413 args.addAll(files); 414 415 return args; 416 } 417 418 private String toSearchPath(List<Path> files) { 419 return files.stream() 420 .map(Path::toString) 421 .collect(Collectors.joining(File.pathSeparator)); 422 } 423 424 private Iterable<? extends JavaFileObject> joinFiles( 425 List<String> files, List<JavaFileObject> fileObjects) { 426 if (files == null) 427 return fileObjects; 428 if (internalFileManager == null) 429 internalFileManager = compiler.getStandardFileManager(null, null, null); 430 Iterable<? extends JavaFileObject> filesAsFileObjects = 431 internalFileManager.getJavaFileObjectsFromStrings(files); 432 if (fileObjects == null) 433 return filesAsFileObjects; 434 List<JavaFileObject> combinedList = new ArrayList<>(); 435 for (JavaFileObject o : filesAsFileObjects) 436 combinedList.add(o); 437 combinedList.addAll(fileObjects); 438 return combinedList; 439 } 440 }