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