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