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