1 /* 2 * Copyright (c) 2014, 2016, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.tools.sjavac.comp; 27 28 import java.io.IOException; 29 import java.io.PrintWriter; 30 import java.io.StringWriter; 31 import java.nio.file.Files; 32 import java.nio.file.Path; 33 import java.util.ArrayList; 34 import java.util.Collections; 35 import java.util.HashMap; 36 import java.util.HashSet; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Set; 40 import java.util.stream.Stream; 41 42 import com.sun.tools.javac.file.JavacFileManager; 43 import com.sun.tools.javac.main.Main; 44 import com.sun.tools.javac.main.Main.Result; 45 import com.sun.tools.javac.util.Context; 46 import com.sun.tools.sjavac.JavacState; 47 import com.sun.tools.sjavac.Log; 48 import com.sun.tools.sjavac.Module; 49 import com.sun.tools.sjavac.ProblemException; 50 import com.sun.tools.sjavac.Source; 51 import com.sun.tools.sjavac.Transformer; 52 import com.sun.tools.sjavac.Util; 53 import com.sun.tools.sjavac.options.Option; 54 import com.sun.tools.sjavac.options.Options; 55 import com.sun.tools.sjavac.options.SourceLocation; 56 import com.sun.tools.sjavac.server.Sjavac; 57 import java.io.UncheckedIOException; 58 59 import javax.tools.JavaFileManager; 60 61 /** 62 * The sjavac implementation that interacts with javac and performs the actual 63 * compilation. 64 * 65 * <p><b>This is NOT part of any supported API. 66 * If you write code that depends on this, you do so at your own risk. 67 * This code and its internal interfaces are subject to change or 68 * deletion without notice.</b> 69 */ 70 public class SjavacImpl implements Sjavac { 71 72 @Override compile(String[] args)73 public Result compile(String[] args) { 74 Options options; 75 try { 76 options = Options.parseArgs(args); 77 } catch (IllegalArgumentException e) { 78 Log.error(e.getMessage()); 79 return Result.CMDERR; 80 } 81 82 if (!validateOptions(options)) 83 return Result.CMDERR; 84 85 if (srcDstOverlap(options.getSources(), options.getDestDir())) { 86 return Result.CMDERR; 87 } 88 89 if (!createIfMissing(options.getDestDir())) 90 return Result.ERROR; 91 92 Path stateDir = options.getStateDir(); 93 if (stateDir != null && !createIfMissing(options.getStateDir())) 94 return Result.ERROR; 95 96 Path gensrc = options.getGenSrcDir(); 97 if (gensrc != null && !createIfMissing(gensrc)) 98 return Result.ERROR; 99 100 Path hdrdir = options.getHeaderDir(); 101 if (hdrdir != null && !createIfMissing(hdrdir)) 102 return Result.ERROR; 103 104 if (stateDir == null) { 105 // Prepare context. Direct logging to our byte array stream. 106 Context context = new Context(); 107 StringWriter strWriter = new StringWriter(); 108 PrintWriter printWriter = new PrintWriter(strWriter); 109 com.sun.tools.javac.util.Log.preRegister(context, printWriter); 110 JavacFileManager.preRegister(context); 111 112 // Prepare arguments 113 String[] passThroughArgs = Stream.of(args) 114 .filter(arg -> !arg.startsWith(Option.SERVER.arg)) 115 .toArray(String[]::new); 116 // Compile 117 Result result = new Main("javac", printWriter).compile(passThroughArgs, context); 118 119 // Process compiler output (which is always errors) 120 printWriter.flush(); 121 Util.getLines(strWriter.toString()).forEach(Log::error); 122 123 // Clean up 124 JavaFileManager fileManager = context.get(JavaFileManager.class); 125 if (fileManager instanceof JavacFileManager) { 126 try { 127 ((JavacFileManager) fileManager).close(); 128 } catch (IOException es) { 129 throw new UncheckedIOException(es); 130 } 131 } 132 return result; 133 134 } else { 135 // Load the prev build state database. 136 JavacState javac_state = JavacState.load(options); 137 138 // Setup the suffix rules from the command line. 139 Map<String, Transformer> suffixRules = new HashMap<>(); 140 141 // Handling of .java-compilation 142 suffixRules.putAll(javac_state.getJavaSuffixRule()); 143 144 // Handling of -copy and -tr 145 suffixRules.putAll(options.getTranslationRules()); 146 147 // All found modules are put here. 148 Map<String,Module> modules = new HashMap<>(); 149 // We start out in the legacy empty no-name module. 150 // As soon as we stumble on a module-info.java file we change to that module. 151 Module current_module = new Module("", ""); 152 modules.put("", current_module); 153 154 try { 155 // Find all sources, use the suffix rules to know which files are sources. 156 Map<String,Source> sources = new HashMap<>(); 157 158 // Find the files, this will automatically populate the found modules 159 // with found packages where the sources are found! 160 findSourceFiles(options.getSources(), 161 suffixRules.keySet(), 162 sources, 163 modules, 164 current_module, 165 options.isDefaultPackagePermitted(), 166 false); 167 168 if (sources.isEmpty()) { 169 Log.error("Found nothing to compile!"); 170 return Result.ERROR; 171 } 172 173 174 // Create a map of all source files that are available for linking. Both -src and 175 // -sourcepath point to such files. It is possible to specify multiple 176 // -sourcepath options to enable different filtering rules. If the 177 // filters are the same for multiple sourcepaths, they may be concatenated 178 // using :(;). Before sending the list of sourcepaths to javac, they are 179 // all concatenated. The list created here is used by the SmartFileWrapper to 180 // make sure only the correct sources are actually available. 181 // We might find more modules here as well. 182 Map<String,Source> sources_to_link_to = new HashMap<>(); 183 184 List<SourceLocation> sourceResolutionLocations = new ArrayList<>(); 185 sourceResolutionLocations.addAll(options.getSources()); 186 sourceResolutionLocations.addAll(options.getSourceSearchPaths()); 187 findSourceFiles(sourceResolutionLocations, 188 Collections.singleton(".java"), 189 sources_to_link_to, 190 modules, 191 current_module, 192 options.isDefaultPackagePermitted(), 193 true); 194 195 // Add the set of sources to the build database. 196 javac_state.now().flattenPackagesSourcesAndArtifacts(modules); 197 javac_state.now().checkInternalState("checking sources", false, sources); 198 javac_state.now().checkInternalState("checking linked sources", true, sources_to_link_to); 199 javac_state.setVisibleSources(sources_to_link_to); 200 201 int round = 0; 202 printRound(round); 203 204 // If there is any change in the source files, taint packages 205 // and mark the database in need of saving. 206 javac_state.checkSourceStatus(false); 207 208 // Find all existing artifacts. Their timestamp will match the last modified timestamps stored 209 // in javac_state, simply because loading of the JavacState will clean out all artifacts 210 // that do not match the javac_state database. 211 javac_state.findAllArtifacts(); 212 213 // Remove unidentified artifacts from the bin, gensrc and header dirs. 214 // (Unless we allow them to be there.) 215 // I.e. artifacts that are not known according to the build database (javac_state). 216 // For examples, files that have been manually copied into these dirs. 217 // Artifacts with bad timestamps (ie the on disk timestamp does not match the timestamp 218 // in javac_state) have already been removed when the javac_state was loaded. 219 if (!options.areUnidentifiedArtifactsPermitted()) { 220 javac_state.removeUnidentifiedArtifacts(); 221 } 222 // Go through all sources and taint all packages that miss artifacts. 223 javac_state.taintPackagesThatMissArtifacts(); 224 225 // Check recorded classpath public apis. Taint packages that depend on 226 // classpath classes whose public apis have changed. 227 javac_state.taintPackagesDependingOnChangedClasspathPackages(); 228 229 // Now clean out all known artifacts belonging to tainted packages. 230 javac_state.deleteClassArtifactsInTaintedPackages(); 231 // Copy files, for example property files, images files, xml files etc etc. 232 javac_state.performCopying(Util.pathToFile(options.getDestDir()), suffixRules); 233 // Translate files, for example compile properties or compile idls. 234 javac_state.performTranslation(Util.pathToFile(gensrc), suffixRules); 235 // Add any potentially generated java sources to the tobe compiled list. 236 // (Generated sources must always have a package.) 237 Map<String,Source> generated_sources = new HashMap<>(); 238 239 Source.scanRoot(Util.pathToFile(options.getGenSrcDir()), 240 Util.set(".java"), 241 Collections.emptyList(), 242 Collections.emptyList(), 243 generated_sources, 244 modules, 245 current_module, 246 false, 247 true, 248 false); 249 javac_state.now().flattenPackagesSourcesAndArtifacts(modules); 250 // Recheck the the source files and their timestamps again. 251 javac_state.checkSourceStatus(true); 252 253 // Now do a safety check that the list of source files is identical 254 // to the list Make believes we are compiling. If we do not get this 255 // right, then incremental builds will fail with subtility. 256 // If any difference is detected, then we will fail hard here. 257 // This is an important safety net. 258 javac_state.compareWithMakefileList(Util.pathToFile(options.getSourceReferenceList())); 259 260 // Do the compilations, repeatedly until no tainted packages exist. 261 boolean again; 262 // Collect the name of all compiled packages. 263 Set<String> recently_compiled = new HashSet<>(); 264 boolean[] rc = new boolean[1]; 265 266 CompilationService compilationService = new CompilationService(); 267 do { 268 if (round > 0) 269 printRound(round); 270 // Clean out artifacts in tainted packages. 271 javac_state.deleteClassArtifactsInTaintedPackages(); 272 again = javac_state.performJavaCompilations(compilationService, 273 options, 274 recently_compiled, 275 rc); 276 if (!rc[0]) { 277 Log.debug("Compilation failed."); 278 break; 279 } 280 if (!again) { 281 Log.debug("Nothing left to do."); 282 } 283 round++; 284 } while (again); 285 Log.debug("No need to do another round."); 286 287 // Only update the state if the compile went well. 288 if (rc[0]) { 289 javac_state.save(); 290 // Reflatten only the artifacts. 291 javac_state.now().flattenArtifacts(modules); 292 // Remove artifacts that were generated during the last compile, but not this one. 293 javac_state.removeSuperfluousArtifacts(recently_compiled); 294 } 295 296 return rc[0] ? Result.OK : Result.ERROR; 297 } catch (ProblemException e) { 298 // For instance make file list mismatch. 299 Log.error(e.getMessage()); 300 Log.debug(e); 301 return Result.ERROR; 302 } catch (Exception e) { 303 Log.error(e); 304 return Result.ERROR; 305 } 306 } 307 } 308 309 @Override shutdown()310 public void shutdown() { 311 // Nothing to clean up 312 } 313 validateOptions(Options options)314 private static boolean validateOptions(Options options) { 315 316 String err = null; 317 318 if (options.getDestDir() == null) { 319 err = "Please specify output directory."; 320 } else if (options.isJavaFilesAmongJavacArgs()) { 321 err = "Sjavac does not handle explicit compilation of single .java files."; 322 } else if (!options.getImplicitPolicy().equals("none")) { 323 err = "The only allowed setting for sjavac is -implicit:none"; 324 } else if (options.getSources().isEmpty() && options.getStateDir() != null) { 325 err = "You have to specify -src when using --state-dir."; 326 } else if (options.getTranslationRules().size() > 1 327 && options.getGenSrcDir() == null) { 328 err = "You have translators but no gensrc dir (-s) specified!"; 329 } 330 331 if (err != null) 332 Log.error(err); 333 334 return err == null; 335 336 } 337 srcDstOverlap(List<SourceLocation> locs, Path dest)338 private static boolean srcDstOverlap(List<SourceLocation> locs, Path dest) { 339 for (SourceLocation loc : locs) { 340 if (isOverlapping(loc.getPath(), dest)) { 341 Log.error("Source location " + loc.getPath() + " overlaps with destination " + dest); 342 return true; 343 } 344 } 345 return false; 346 } 347 isOverlapping(Path p1, Path p2)348 private static boolean isOverlapping(Path p1, Path p2) { 349 p1 = p1.toAbsolutePath().normalize(); 350 p2 = p2.toAbsolutePath().normalize(); 351 return p1.startsWith(p2) || p2.startsWith(p1); 352 } 353 createIfMissing(Path dir)354 private static boolean createIfMissing(Path dir) { 355 356 if (Files.isDirectory(dir)) 357 return true; 358 359 if (Files.exists(dir)) { 360 Log.error(dir + " is not a directory."); 361 return false; 362 } 363 364 try { 365 Files.createDirectories(dir); 366 } catch (IOException e) { 367 Log.error("Could not create directory: " + e.getMessage()); 368 return false; 369 } 370 371 return true; 372 } 373 374 /** Find source files in the given source locations. */ findSourceFiles(List<SourceLocation> sourceLocations, Set<String> sourceTypes, Map<String,Source> foundFiles, Map<String, Module> foundModules, Module currentModule, boolean permitSourcesInDefaultPackage, boolean inLinksrc)375 public static void findSourceFiles(List<SourceLocation> sourceLocations, 376 Set<String> sourceTypes, 377 Map<String,Source> foundFiles, 378 Map<String, Module> foundModules, 379 Module currentModule, 380 boolean permitSourcesInDefaultPackage, 381 boolean inLinksrc) 382 throws IOException { 383 384 for (SourceLocation source : sourceLocations) { 385 source.findSourceFiles(sourceTypes, 386 foundFiles, 387 foundModules, 388 currentModule, 389 permitSourcesInDefaultPackage, 390 inLinksrc); 391 } 392 } 393 printRound(int round)394 private static void printRound(int round) { 395 Log.debug("****************************************"); 396 Log.debug("* Round " + round + " *"); 397 Log.debug("****************************************"); 398 } 399 } 400