1 /*
   2  * Copyright (c) 2012, 2014, 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;
  27 
  28 import java.io.IOException;
  29 import java.io.PrintStream;
  30 import java.util.*;
  31 import java.nio.file.Path;
  32 import java.nio.file.Files;
  33 
  34 import com.sun.tools.sjavac.options.Options;
  35 import com.sun.tools.sjavac.options.SourceLocation;
  36 import com.sun.tools.sjavac.server.JavacService;
  37 import com.sun.tools.sjavac.server.JavacServer;
  38 import com.sun.tools.sjavac.server.JavacServiceClient;
  39 
  40 /**
  41  * The main class of the smart javac wrapper tool.
  42  *
  43  * <p><b>This is NOT part of any supported API.
  44  * If you write code that depends on this, you do so at your own
  45  * risk.  This code and its internal interfaces are subject to change
  46  * or deletion without notice.</b></p>
  47  */
  48 public class Main {
  49 
  50     /*  This is a smart javac wrapper primarily used when building the OpenJDK,
  51         though other projects are welcome to use it too. But please be aware
  52         that it is not an official api and will change in the future.
  53         (We really mean it!)
  54 
  55         Goals:
  56 
  57         ** Create a state file, containing information about the build, so
  58            that incremental builds only rebuild what is necessary. Also the
  59            state file can be used by make/ant to detect when to trigger
  60            a call to the smart javac wrapper.
  61 
  62            This file is called bin/javac_state (assuming that you specified "-d bin")
  63            Thus the simplest makefile is:
  64 
  65            SJAVAC=java -cp .../tools.jar com.sun.tools.sjavac.Main
  66            SRCS=$(shell find src -name "*.java")
  67            bin/javac_state : $(SRCS)
  68                   $(SJAVAC) src -d bin
  69 
  70            This makefile will run very fast and detect properly when Java code needs to
  71            be recompiled. The smart javac wrapper will then use the information in java_state
  72            to do an efficient incremental compile.
  73 
  74            Previously it was near enough impossible to write an efficient makefile for Java
  75            with support for incremental builds and dependency tracking.
  76 
  77         ** Separate java sources to be compiled from java
  78            sources used >only< for linking. The options:
  79 
  80            "dir" points to root dir with sources to be compiled
  81            "-sourcepath dir" points to root dir with sources used only for linking
  82            "-classpath dir" points to dir with classes used only for linking (as before)
  83 
  84         ** Use all cores for compilation by default.
  85            "-j 4" limit the number of cores to 4.
  86            For the moment, the sjavac server additionally limits the number of cores to three.
  87            This will improve in the future when more sharing is performed between concurrent JavaCompilers.
  88 
  89         ** Basic translation support from other sources to java, and then compilation of the generated java.
  90            This functionality might be moved into annotation processors instead.
  91            Again this is driven by the OpenJDK sources where properties and a few other types of files
  92            are converted into Java sources regularily. The javac_state embraces copy and tr, and perform
  93            incremental recompiles and copying for these as well. META-INF will be a special copy rule
  94            that will copy any files found below any META-INF dir in src to the bin/META-INF dir.
  95            "-copy .gif"
  96            "-copy META-INF"
  97            "-tr .prop=com.sun.tools.javac.smart.CompileProperties
  98            "-tr .propp=com.sun.tools.javac.smart.CompileProperties,java.util.ListResourceBundle
  99            "-tr .proppp=com.sun.tools.javac.smart.CompileProperties,sun.util.resources.LocaleNamesBundle
 100 
 101         ** Control which classes in the src,sourcepath and classpath that javac is allowed to see.
 102            Again, this is necessary to deal with the source code structure of the OpenJDK which is
 103            intricate (read messy).
 104 
 105            "-i tools.*" to include the tools package and all its subpackages in the build.
 106            "-x tools.net.aviancarrier.*" to exclude the aviancarrier package and all its sources and subpackages.
 107            "-x tools.net.drums" to exclude the drums package only, keep its subpackages.
 108            "-xf tools/net/Bar.java" // Do not compile this file...
 109            "-xf *Bor.java" // Do not compile Bor.java wherever it is found, BUT do compile ABor.java!
 110            "-if tools/net/Bor.java" // Only compile this file...odd, but sometimes used.
 111 
 112         ** The smart javac wrapper is driven by the modification time on the source files and compared
 113            to the modification times written into the javac_state file.
 114 
 115            It does not compare the modification time of the source with the modification time of the artifact.
 116            However it will detect if the modification time of an artifact has changed compared to the java_state,
 117            and this will trigger a delete of the artifact and a subsequent recompile of the source.
 118 
 119            The smart javac wrapper is not a generic makefile/ant system. Its purpose is to compile java source
 120            as the final step before the output dir is finalized and immediately jared, or jmodded. The output
 121            dir should be considered opaque. Do not write into the outputdir yourself!
 122            Any artifacts found in the outputdir that javac_state does not know of, will be deleted!
 123            This can however be prevented, using the switch --permit-unidentified-artifacts
 124            This switch is necessary when build the OpenJDK because its makefiles still write directly to
 125            the output classes dirs.
 126 
 127            Any makefile/ant rules that want to put contents into the outputdir should put the content
 128            in one of several source roots. Static content that is under version control, can be put in the same source
 129            code tree as the Java sources. Dynamic content that is generated by make/ant on the fly, should
 130            be put in a separate gensrc_stuff root. The smart javac wrapper call will then take the arguments:
 131            "gensrc_stuff src -d bin"
 132 
 133         The command line:
 134         java -cp tools.jar com.sun.tools.sjavac.Main \
 135              -i "com.bar.*" -x "com.bar.foo.*" \
 136              first_root \
 137              -i "com.bar.foo.*" \
 138              second_root \
 139              -x "org.net.*" \
 140              -sourcepath link_root_sources \
 141              -classpath link_root_classes \
 142              -d bin
 143 
 144         Will compile all sources for package com.bar and its subpackages, found below first_root,
 145         except the package com.bar.foo (and its subpackages), for which the sources are picked
 146         from second_root instead. It will link against classes in link_root_classes and against
 147         sources in link_root_sources, but will not see (try to link against) sources matching org.net.*
 148         but will link against org.net* classes (if they exist) in link_root_classes.
 149 
 150         (If you want a set of complex filter rules to be applied to several source directories, without
 151          having to repeat the the filter rules for each root. You can use the explicit -src option. For example:
 152          sjavac -x "com.foo.*" -src root1:root2:root3  )
 153 
 154         The resulting classes are written into bin.
 155     */
 156 
 157     private JavacState javac_state;
 158 
 159     public static void main(String... args)  {
 160         if (args.length > 0 && args[0].startsWith("--startserver:")) {
 161             if (args.length>1) {
 162                 Log.error("When spawning a background server, only a single --startserver argument is allowed.");
 163                 return;
 164             }
 165             // Spawn a background server.
 166             int rc = JavacServer.startServer(args[0], System.err);
 167             System.exit(rc);
 168         }
 169         Main main = new Main();
 170         int rc = main.go(args, System.out, System.err);
 171         // Remove the portfile, but only if this background=false was used.
 172         JavacServer.cleanup(args);
 173         System.exit(rc);
 174     }
 175 
 176     private void printHelp() {
 177         System.out.println("Usage: sjavac <options>\n"+
 178                            "where required options are:\n"+
 179                            "dir                        Compile all sources in dir recursively\n"+
 180                            "-d dir                     Store generated classes here and the javac_state file\n"+
 181                            "--server:portfile=/tmp/abc Use a background sjavac server\n\n"+
 182                            "All other arguments as javac, except -implicit:none which is forced by default.\n"+
 183                            "No java source files can be supplied on the command line, nor can an @file be supplied.\n\n"+
 184                            "Warning!\n"+
 185                            "This tool might disappear at any time, and its command line options might change at any time!");
 186     }
 187 
 188     public int go(String[] args, PrintStream out, PrintStream err) {
 189 
 190         Log.initializeLog(out, err);
 191 
 192         Options options;
 193         try {
 194             options = Options.parseArgs(args);
 195         } catch (IllegalArgumentException e) {
 196             Log.error(e.getMessage());
 197             return -1;
 198         }
 199 
 200         Log.setLogLevel(options.getLogLevel());
 201 
 202         if (!validateOptions(options))
 203             return -1;
 204 
 205         if (!createIfMissing(options.getDestDir()))
 206             return -1;
 207 
 208         if (!createIfMissing(options.getStateDir()))
 209             return -1;
 210 
 211         Path gensrc = options.getGenSrcDir();
 212         if (gensrc != null && !createIfMissing(gensrc))
 213             return -1;
 214 
 215         Path hdrdir = options.getHeaderDir();
 216         if (hdrdir != null && !createIfMissing(hdrdir))
 217             return -1;
 218 
 219         // Load the prev build state database.
 220         javac_state = JavacState.load(options, out, err);
 221 
 222         // Setup the suffix rules from the command line.
 223         Map<String, Transformer> suffixRules = new HashMap<>();
 224 
 225         // Handling of .java-compilation
 226         suffixRules.putAll(javac_state.getJavaSuffixRule());
 227 
 228         // Handling of -copy and -tr
 229         suffixRules.putAll(options.getTranslationRules());
 230 
 231         // All found modules are put here.
 232         Map<String,Module> modules = new HashMap<>();
 233         // We start out in the legacy empty no-name module.
 234         // As soon as we stumble on a module-info.java file we change to that module.
 235         Module current_module = new Module("", "");
 236         modules.put("", current_module);
 237 
 238         // Find all sources, use the suffix rules to know which files are sources.
 239         Map<String,Source> sources = new HashMap<>();
 240 
 241         // Find the files, this will automatically populate the found modules
 242         // with found packages where the sources are found!
 243         findSourceFiles(options.getSources(),
 244                         suffixRules.keySet(),
 245                         sources,
 246                         modules,
 247                         current_module,
 248                         options.isDefaultPackagePermitted(),
 249                         false);
 250 
 251         if (sources.isEmpty()) {
 252             Log.error("Found nothing to compile!");
 253             return -1;
 254         }
 255 
 256         // Create a map of all source files that are available for linking. Both -src and
 257         // -sourcepath point to such files. It is possible to specify multiple
 258         // -sourcepath options to enable different filtering rules. If the
 259         // filters are the same for multiple sourcepaths, they may be concatenated
 260         // using :(;). Before sending the list of sourcepaths to javac, they are
 261         // all concatenated. The list created here is used by the SmartFileWrapper to
 262         // make sure only the correct sources are actually available.
 263         // We might find more modules here as well.
 264         Map<String,Source> sources_to_link_to = new HashMap<>();
 265 
 266         List<SourceLocation> sourceResolutionLocations = new ArrayList<>();
 267         sourceResolutionLocations.addAll(options.getSources());
 268         sourceResolutionLocations.addAll(options.getSourceSearchPaths());
 269         findSourceFiles(sourceResolutionLocations,
 270                         Collections.singleton(".java"),
 271                         sources_to_link_to,
 272                         modules,
 273                         current_module,
 274                         options.isDefaultPackagePermitted(),
 275                         true);
 276 
 277         // Find all class files allowable for linking.
 278         // And pickup knowledge of all modules found here.
 279         // This cannot currently filter classes inside jar files.
 280 //      Map<String,Source> classes_to_link_to = new HashMap<String,Source>();
 281 //      findFiles(args, "-classpath", Util.set(".class"), classes_to_link_to, modules, current_module, true);
 282 
 283         // Find all module sources allowable for linking.
 284 //      Map<String,Source> modules_to_link_to = new HashMap<String,Source>();
 285 //      findFiles(args, "-modulepath", Util.set(".class"), modules_to_link_to, modules, current_module, true);
 286 
 287         // Add the set of sources to the build database.
 288         javac_state.now().flattenPackagesSourcesAndArtifacts(modules);
 289         javac_state.now().checkInternalState("checking sources", false, sources);
 290         javac_state.now().checkInternalState("checking linked sources", true, sources_to_link_to);
 291         javac_state.setVisibleSources(sources_to_link_to);
 292 
 293         // If there is any change in the source files, taint packages
 294         // and mark the database in need of saving.
 295         javac_state.checkSourceStatus(false);
 296 
 297         // Find all existing artifacts. Their timestamp will match the last modified timestamps stored
 298         // in javac_state, simply because loading of the JavacState will clean out all artifacts
 299         // that do not match the javac_state database.
 300         javac_state.findAllArtifacts();
 301 
 302         // Remove unidentified artifacts from the bin, gensrc and header dirs.
 303         // (Unless we allow them to be there.)
 304         // I.e. artifacts that are not known according to the build database (javac_state).
 305         // For examples, files that have been manually copied into these dirs.
 306         // Artifacts with bad timestamps (ie the on disk timestamp does not match the timestamp
 307         // in javac_state) have already been removed when the javac_state was loaded.
 308         if (!options.isUnidentifiedArtifactPermitted()) {
 309             javac_state.removeUnidentifiedArtifacts();
 310         }
 311         // Go through all sources and taint all packages that miss artifacts.
 312         javac_state.taintPackagesThatMissArtifacts();
 313 
 314         // Now clean out all known artifacts belonging to tainted packages.
 315         javac_state.deleteClassArtifactsInTaintedPackages();
 316         // Copy files, for example property files, images files, xml files etc etc.
 317         javac_state.performCopying(Util.pathToFile(options.getDestDir()), suffixRules);
 318         // Translate files, for example compile properties or compile idls.
 319         javac_state.performTranslation(Util.pathToFile(gensrc), suffixRules);
 320         // Add any potentially generated java sources to the tobe compiled list.
 321         // (Generated sources must always have a package.)
 322         Map<String,Source> generated_sources = new HashMap<>();
 323 
 324         try {
 325 
 326             Source.scanRoot(Util.pathToFile(options.getGenSrcDir()), Util.set(".java"), null, null, null, null,
 327                     generated_sources, modules, current_module, false, true, false);
 328             javac_state.now().flattenPackagesSourcesAndArtifacts(modules);
 329             // Recheck the the source files and their timestamps again.
 330             javac_state.checkSourceStatus(true);
 331 
 332             // Now do a safety check that the list of source files is identical
 333             // to the list Make believes we are compiling. If we do not get this
 334             // right, then incremental builds will fail with subtility.
 335             // If any difference is detected, then we will fail hard here.
 336             // This is an important safety net.
 337             javac_state.compareWithMakefileList(Util.pathToFile(options.getSourceReferenceList()));
 338 
 339             // Do the compilations, repeatedly until no tainted packages exist.
 340             boolean again;
 341             // Collect the name of all compiled packages.
 342             Set<String> recently_compiled = new HashSet<>();
 343             boolean[] rc = new boolean[1];
 344             do {
 345                 // Clean out artifacts in tainted packages.
 346                 javac_state.deleteClassArtifactsInTaintedPackages();
 347                 // Create a JavacService to delegate the actual compilation to.
 348                 // Currently sjavac always connects to a server through a socket
 349                 // regardless if sjavac runs as a background service or not.
 350                 // This will most likely change in the future.
 351                 JavacService javacService = new JavacServiceClient(options);
 352                 again = javac_state.performJavaCompilations(javacService, options, recently_compiled, rc);
 353                 if (!rc[0]) break;
 354             } while (again);
 355             // Only update the state if the compile went well.
 356             if (rc[0]) {
 357                 javac_state.save();
 358                 // Reflatten only the artifacts.
 359                 javac_state.now().flattenArtifacts(modules);
 360                 // Remove artifacts that were generated during the last compile, but not this one.
 361                 javac_state.removeSuperfluousArtifacts(recently_compiled);
 362             }
 363             return rc[0] ? 0 : -1;
 364         } catch (ProblemException e) {
 365             Log.error(e.getMessage());
 366             return -1;
 367         } catch (Exception e) {
 368             e.printStackTrace(err);
 369             return -1;
 370         }
 371     }
 372 
 373     private static boolean validateOptions(Options options) {
 374 
 375         String err = null;
 376 
 377         if (options.getDestDir() == null) {
 378             err = "Please specify output directory.";
 379         } else if (options.isJavaFilesAmongJavacArgs()) {
 380             err = "Sjavac does not handle explicit compilation of single .java files.";
 381         } else if (options.getServerConf() == null) {
 382             err = "No server configuration provided.";
 383         } else if (!options.getImplicitPolicy().equals("none")) {
 384             err = "The only allowed setting for sjavac is -implicit:none";
 385         } else if (options.getSources().isEmpty()) {
 386             err = "You have to specify -src.";
 387         } else if (options.getTranslationRules().size() > 1
 388                 && options.getGenSrcDir() == null) {
 389             err = "You have translators but no gensrc dir (-s) specified!";
 390         }
 391 
 392         if (err != null)
 393             Log.error(err);
 394 
 395         return err == null;
 396 
 397     }
 398 
 399     private static boolean createIfMissing(Path dir) {
 400 
 401         if (Files.isDirectory(dir))
 402             return true;
 403 
 404         if (Files.exists(dir)) {
 405             Log.error(dir + " is not a directory.");
 406             return false;
 407         }
 408 
 409         try {
 410             Files.createDirectories(dir);
 411         } catch (IOException e) {
 412             Log.error("Could not create directory: " + e.getMessage());
 413             return false;
 414         }
 415 
 416         return true;
 417     }
 418 
 419 
 420     /** Find source files in the given source locations. */
 421     public static void findSourceFiles(List<SourceLocation> sourceLocations,
 422                                        Set<String> sourceTypes,
 423                                        Map<String,Source> foundFiles,
 424                                        Map<String, Module> foundModules,
 425                                        Module currentModule,
 426                                        boolean permitSourcesInDefaultPackage,
 427                                        boolean inLinksrc) {
 428 
 429         for (SourceLocation source : sourceLocations) {
 430             source.findSourceFiles(sourceTypes,
 431                                    foundFiles,
 432                                    foundModules,
 433                                    currentModule,
 434                                    permitSourcesInDefaultPackage,
 435                                    inLinksrc);
 436         }
 437     }
 438 }