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