SjavacImpl.java revision 3219:aacc4ceb35c9
1321936Shselasky/*
2321936Shselasky * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
3321936Shselasky * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4321936Shselasky *
5321936Shselasky * This code is free software; you can redistribute it and/or modify it
6321936Shselasky * under the terms of the GNU General Public License version 2 only, as
7321936Shselasky * published by the Free Software Foundation.  Oracle designates this
8321936Shselasky * particular file as subject to the "Classpath" exception as provided
9321936Shselasky * by Oracle in the LICENSE file that accompanied this code.
10321936Shselasky *
11321936Shselasky * This code is distributed in the hope that it will be useful, but WITHOUT
12321936Shselasky * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13321936Shselasky * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14321936Shselasky * version 2 for more details (a copy is included in the LICENSE file that
15321936Shselasky * accompanied this code).
16321936Shselasky *
17321936Shselasky * You should have received a copy of the GNU General Public License version
18321936Shselasky * 2 along with this work; if not, write to the Free Software Foundation,
19321936Shselasky * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20321936Shselasky *
21321936Shselasky * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22321936Shselasky * or visit www.oracle.com if you need additional information or have any
23321936Shselasky * questions.
24321936Shselasky */
25321936Shselasky
26321936Shselaskypackage com.sun.tools.sjavac.comp;
27321936Shselasky
28321936Shselaskyimport java.io.IOException;
29321936Shselaskyimport java.io.PrintWriter;
30321936Shselaskyimport java.io.Writer;
31321936Shselaskyimport java.nio.file.Files;
32321936Shselaskyimport java.nio.file.Path;
33321936Shselaskyimport java.util.ArrayList;
34321936Shselaskyimport java.util.Collections;
35321936Shselaskyimport java.util.HashMap;
36321936Shselaskyimport java.util.HashSet;
37321936Shselaskyimport java.util.List;
38321936Shselaskyimport java.util.Map;
39321936Shselaskyimport java.util.Set;
40321936Shselaskyimport java.util.stream.Stream;
41321936Shselasky
42321936Shselaskyimport com.sun.tools.javac.file.JavacFileManager;
43321936Shselaskyimport com.sun.tools.javac.main.Main;
44321936Shselaskyimport com.sun.tools.javac.util.Context;
45321936Shselaskyimport com.sun.tools.sjavac.JavacState;
46321936Shselaskyimport com.sun.tools.sjavac.Log;
47325522Snpimport com.sun.tools.sjavac.Module;
48325522Snpimport com.sun.tools.sjavac.ProblemException;
49325522Snpimport com.sun.tools.sjavac.Source;
50321936Shselaskyimport com.sun.tools.sjavac.Transformer;
51325522Snpimport com.sun.tools.sjavac.Util;
52325522Snpimport com.sun.tools.sjavac.options.Option;
53325522Snpimport com.sun.tools.sjavac.options.Options;
54321936Shselaskyimport com.sun.tools.sjavac.options.SourceLocation;
55321936Shselaskyimport com.sun.tools.sjavac.server.Sjavac;
56321936Shselasky
57321936Shselaskyimport javax.tools.JavaFileManager;
58321936Shselasky
59321936Shselasky/**
60321936Shselasky * The sjavac implementation that interacts with javac and performs the actual
61321936Shselasky * compilation.
62321936Shselasky *
63321936Shselasky *  <p><b>This is NOT part of any supported API.
64325522Snp *  If you write code that depends on this, you do so at your own risk.
65325522Snp *  This code and its internal interfaces are subject to change or
66325522Snp *  deletion without notice.</b>
67325522Snp */
68325522Snppublic class SjavacImpl implements Sjavac {
69325522Snp
70325522Snp    @Override
71325522Snp    public int compile(String[] args, Writer out, Writer err) {
72325522Snp        Options options;
73325522Snp        try {
74325522Snp            options = Options.parseArgs(args);
75321936Shselasky        } catch (IllegalArgumentException e) {
76321936Shselasky            Log.error(e.getMessage());
77321936Shselasky            return RC_FATAL;
78321936Shselasky        }
79321936Shselasky
80321936Shselasky        Log.setLogLevel(options.getLogLevel());
81321936Shselasky
82321936Shselasky        if (!validateOptions(options))
83325522Snp            return RC_FATAL;
84325522Snp
85325522Snp        if (!createIfMissing(options.getDestDir()))
86321936Shselasky            return RC_FATAL;
87325522Snp
88325522Snp        Path stateDir = options.getStateDir();
89325522Snp        if (stateDir != null && !createIfMissing(options.getStateDir()))
90325522Snp            return RC_FATAL;
91325522Snp
92325522Snp        Path gensrc = options.getGenSrcDir();
93325522Snp        if (gensrc != null && !createIfMissing(gensrc))
94325522Snp            return RC_FATAL;
95325522Snp
96325522Snp        Path hdrdir = options.getHeaderDir();
97325522Snp        if (hdrdir != null && !createIfMissing(hdrdir))
98325522Snp            return RC_FATAL;
99325522Snp
100321936Shselasky        if (stateDir == null) {
101321936Shselasky            // Prepare context. Direct logging to our byte array stream.
102321936Shselasky            Context context = new Context();
103321936Shselasky            PrintWriter writer = new PrintWriter(err);
104321936Shselasky            com.sun.tools.javac.util.Log.preRegister(context, writer);
105321936Shselasky            JavacFileManager.preRegister(context);
106321936Shselasky
107321936Shselasky            // Prepare arguments
108321936Shselasky            String[] passThroughArgs = Stream.of(args)
109321936Shselasky                                             .filter(arg -> !arg.startsWith(Option.SERVER.arg))
110321936Shselasky                                             .toArray(String[]::new);
111321936Shselasky
112321936Shselasky            // Compile
113321936Shselasky            com.sun.tools.javac.main.Main compiler = new com.sun.tools.javac.main.Main("javac", writer);
114321936Shselasky            Main.Result result = compiler.compile(passThroughArgs, context);
115321936Shselasky
116321936Shselasky            // Clean up
117321936Shselasky            JavaFileManager fileManager = context.get(JavaFileManager.class);
118321936Shselasky            if (fileManager instanceof JavacFileManager) {
119321936Shselasky                try {
120321936Shselasky                    ((JavacFileManager) fileManager).close();
121321936Shselasky                } catch (IOException e) {
122321936Shselasky                    return RC_FATAL;
123321936Shselasky                }
124321936Shselasky            }
125321936Shselasky            return result.exitCode;
126321936Shselasky
127321936Shselasky        } else {
128321936Shselasky            // Load the prev build state database.
129321936Shselasky            JavacState javac_state = JavacState.load(options, out, err);
130321936Shselasky
131321936Shselasky            // Setup the suffix rules from the command line.
132321936Shselasky            Map<String, Transformer> suffixRules = new HashMap<>();
133321936Shselasky
134321936Shselasky            // Handling of .java-compilation
135321936Shselasky            suffixRules.putAll(javac_state.getJavaSuffixRule());
136321936Shselasky
137321936Shselasky            // Handling of -copy and -tr
138321936Shselasky            suffixRules.putAll(options.getTranslationRules());
139321936Shselasky
140321936Shselasky            // All found modules are put here.
141321936Shselasky            Map<String,Module> modules = new HashMap<>();
142321936Shselasky            // We start out in the legacy empty no-name module.
143321936Shselasky            // As soon as we stumble on a module-info.java file we change to that module.
144321936Shselasky            Module current_module = new Module("", "");
145321936Shselasky            modules.put("", current_module);
146321936Shselasky
147321936Shselasky            try {
148321936Shselasky                // Find all sources, use the suffix rules to know which files are sources.
149321936Shselasky                Map<String,Source> sources = new HashMap<>();
150321936Shselasky
151321936Shselasky                // Find the files, this will automatically populate the found modules
152321936Shselasky                // with found packages where the sources are found!
153321936Shselasky                findSourceFiles(options.getSources(),
154321936Shselasky                                suffixRules.keySet(),
155321936Shselasky                                sources,
156321936Shselasky                                modules,
157321936Shselasky                                current_module,
158321936Shselasky                                options.isDefaultPackagePermitted(),
159321936Shselasky                                false);
160321936Shselasky
161321936Shselasky                if (sources.isEmpty()) {
162321936Shselasky                    Log.error("Found nothing to compile!");
163321936Shselasky                    return RC_FATAL;
164321936Shselasky                }
165321936Shselasky
166321936Shselasky
167321936Shselasky                // Create a map of all source files that are available for linking. Both -src and
168321936Shselasky                // -sourcepath point to such files. It is possible to specify multiple
169321936Shselasky                // -sourcepath options to enable different filtering rules. If the
170321936Shselasky                // filters are the same for multiple sourcepaths, they may be concatenated
171321936Shselasky                // using :(;). Before sending the list of sourcepaths to javac, they are
172321936Shselasky                // all concatenated. The list created here is used by the SmartFileWrapper to
173321936Shselasky                // make sure only the correct sources are actually available.
174321936Shselasky                // We might find more modules here as well.
175321936Shselasky                Map<String,Source> sources_to_link_to = new HashMap<>();
176321936Shselasky
177321936Shselasky                List<SourceLocation> sourceResolutionLocations = new ArrayList<>();
178321936Shselasky                sourceResolutionLocations.addAll(options.getSources());
179321936Shselasky                sourceResolutionLocations.addAll(options.getSourceSearchPaths());
180321936Shselasky                findSourceFiles(sourceResolutionLocations,
181321936Shselasky                                Collections.singleton(".java"),
182321936Shselasky                                sources_to_link_to,
183321936Shselasky                                modules,
184321936Shselasky                                current_module,
185321936Shselasky                                options.isDefaultPackagePermitted(),
186321936Shselasky                                true);
187321936Shselasky
188321936Shselasky                // Add the set of sources to the build database.
189321936Shselasky                javac_state.now().flattenPackagesSourcesAndArtifacts(modules);
190321936Shselasky                javac_state.now().checkInternalState("checking sources", false, sources);
191321936Shselasky                javac_state.now().checkInternalState("checking linked sources", true, sources_to_link_to);
192321936Shselasky                javac_state.setVisibleSources(sources_to_link_to);
193321936Shselasky
194321936Shselasky                int round = 0;
195321936Shselasky                printRound(round);
196321936Shselasky
197321936Shselasky                // If there is any change in the source files, taint packages
198321936Shselasky                // and mark the database in need of saving.
199321936Shselasky                javac_state.checkSourceStatus(false);
200321936Shselasky
201321936Shselasky                // Find all existing artifacts. Their timestamp will match the last modified timestamps stored
202321936Shselasky                // in javac_state, simply because loading of the JavacState will clean out all artifacts
203321936Shselasky                // that do not match the javac_state database.
204321936Shselasky                javac_state.findAllArtifacts();
205321936Shselasky
206321936Shselasky                // Remove unidentified artifacts from the bin, gensrc and header dirs.
207321936Shselasky                // (Unless we allow them to be there.)
208321936Shselasky                // I.e. artifacts that are not known according to the build database (javac_state).
209321936Shselasky                // For examples, files that have been manually copied into these dirs.
210321936Shselasky                // Artifacts with bad timestamps (ie the on disk timestamp does not match the timestamp
211321936Shselasky                // in javac_state) have already been removed when the javac_state was loaded.
212321936Shselasky                if (!options.areUnidentifiedArtifactsPermitted()) {
213321936Shselasky                    javac_state.removeUnidentifiedArtifacts();
214321936Shselasky                }
215321936Shselasky                // Go through all sources and taint all packages that miss artifacts.
216321936Shselasky                javac_state.taintPackagesThatMissArtifacts();
217321936Shselasky
218321936Shselasky                // Check recorded classpath public apis. Taint packages that depend on
219321936Shselasky                // classpath classes whose public apis have changed.
220321936Shselasky                javac_state.taintPackagesDependingOnChangedClasspathPackages();
221321936Shselasky
222321936Shselasky                // Now clean out all known artifacts belonging to tainted packages.
223321936Shselasky                javac_state.deleteClassArtifactsInTaintedPackages();
224321936Shselasky                // Copy files, for example property files, images files, xml files etc etc.
225321936Shselasky                javac_state.performCopying(Util.pathToFile(options.getDestDir()), suffixRules);
226321936Shselasky                // Translate files, for example compile properties or compile idls.
227321936Shselasky                javac_state.performTranslation(Util.pathToFile(gensrc), suffixRules);
228321936Shselasky                // Add any potentially generated java sources to the tobe compiled list.
229321936Shselasky                // (Generated sources must always have a package.)
230321936Shselasky                Map<String,Source> generated_sources = new HashMap<>();
231321936Shselasky
232321936Shselasky                Source.scanRoot(Util.pathToFile(options.getGenSrcDir()),
233321936Shselasky                                Util.set(".java"),
234321936Shselasky                                Collections.emptyList(),
235321936Shselasky                                Collections.emptyList(),
236321936Shselasky                                generated_sources,
237321936Shselasky                                modules,
238321936Shselasky                                current_module,
239321936Shselasky                                false,
240321936Shselasky                                true,
241321936Shselasky                                false);
242321936Shselasky                javac_state.now().flattenPackagesSourcesAndArtifacts(modules);
243321936Shselasky                // Recheck the the source files and their timestamps again.
244321936Shselasky                javac_state.checkSourceStatus(true);
245321936Shselasky
246321936Shselasky                // Now do a safety check that the list of source files is identical
247321936Shselasky                // to the list Make believes we are compiling. If we do not get this
248321936Shselasky                // right, then incremental builds will fail with subtility.
249321936Shselasky                // If any difference is detected, then we will fail hard here.
250321936Shselasky                // This is an important safety net.
251321936Shselasky                javac_state.compareWithMakefileList(Util.pathToFile(options.getSourceReferenceList()));
252321936Shselasky
253321936Shselasky                // Do the compilations, repeatedly until no tainted packages exist.
254321936Shselasky                boolean again;
255321936Shselasky                // Collect the name of all compiled packages.
256321936Shselasky                Set<String> recently_compiled = new HashSet<>();
257321936Shselasky                boolean[] rc = new boolean[1];
258321936Shselasky
259321936Shselasky                CompilationService compilationService = new CompilationService();
260321936Shselasky                do {
261321936Shselasky                    if (round > 0)
262321936Shselasky                        printRound(round);
263321936Shselasky                    // Clean out artifacts in tainted packages.
264321936Shselasky                    javac_state.deleteClassArtifactsInTaintedPackages();
265321936Shselasky                    again = javac_state.performJavaCompilations(compilationService,
266321936Shselasky                                                                options,
267321936Shselasky                                                                recently_compiled,
268321936Shselasky                                                                rc);
269321936Shselasky                    if (!rc[0]) {
270321936Shselasky                        Log.debug("Compilation failed.");
271321936Shselasky                        break;
272321936Shselasky                    }
273321936Shselasky                    if (!again) {
274321936Shselasky                        Log.debug("Nothing left to do.");
275321936Shselasky                    }
276321936Shselasky                    round++;
277321936Shselasky                } while (again);
278321936Shselasky                Log.debug("No need to do another round.");
279321936Shselasky
280321936Shselasky                // Only update the state if the compile went well.
281321936Shselasky                if (rc[0]) {
282321936Shselasky                    javac_state.save();
283321936Shselasky                    // Reflatten only the artifacts.
284321936Shselasky                    javac_state.now().flattenArtifacts(modules);
285321936Shselasky                    // Remove artifacts that were generated during the last compile, but not this one.
286321936Shselasky                    javac_state.removeSuperfluousArtifacts(recently_compiled);
287321936Shselasky                }
288321936Shselasky
289321936Shselasky                return rc[0] ? RC_OK : RC_FATAL;
290321936Shselasky            } catch (ProblemException e) {
291321936Shselasky                Log.error(e.getMessage());
292321936Shselasky                return RC_FATAL;
293321936Shselasky            } catch (Exception e) {
294321936Shselasky                e.printStackTrace(new PrintWriter(err));
295321936Shselasky                return RC_FATAL;
296321936Shselasky            }
297321936Shselasky        }
298321936Shselasky    }
299321936Shselasky
300321936Shselasky    @Override
301321936Shselasky    public void shutdown() {
302321936Shselasky        // Nothing to clean up
303321936Shselasky    }
304321936Shselasky
305321936Shselasky    private static boolean validateOptions(Options options) {
306321936Shselasky
307321936Shselasky        String err = null;
308321936Shselasky
309321936Shselasky        if (options.getDestDir() == null) {
310321936Shselasky            err = "Please specify output directory.";
311321936Shselasky        } else if (options.isJavaFilesAmongJavacArgs()) {
312321936Shselasky            err = "Sjavac does not handle explicit compilation of single .java files.";
313321936Shselasky        } else if (!options.getImplicitPolicy().equals("none")) {
314321936Shselasky            err = "The only allowed setting for sjavac is -implicit:none";
315321936Shselasky        } else if (options.getSources().isEmpty() && options.getStateDir() != null) {
316321936Shselasky            err = "You have to specify -src when using --state-dir.";
317321936Shselasky        } else if (options.getTranslationRules().size() > 1
318321936Shselasky                && options.getGenSrcDir() == null) {
319321936Shselasky            err = "You have translators but no gensrc dir (-s) specified!";
320321936Shselasky        }
321321936Shselasky
322321936Shselasky        if (err != null)
323321936Shselasky            Log.error(err);
324321936Shselasky
325321936Shselasky        return err == null;
326321936Shselasky
327321936Shselasky    }
328321936Shselasky
329321936Shselasky    private static boolean createIfMissing(Path dir) {
330321936Shselasky
331321936Shselasky        if (Files.isDirectory(dir))
332321936Shselasky            return true;
333321936Shselasky
334321936Shselasky        if (Files.exists(dir)) {
335321936Shselasky            Log.error(dir + " is not a directory.");
336321936Shselasky            return false;
337321936Shselasky        }
338321936Shselasky
339321936Shselasky        try {
340321936Shselasky            Files.createDirectories(dir);
341321936Shselasky        } catch (IOException e) {
342321936Shselasky            Log.error("Could not create directory: " + e.getMessage());
343321936Shselasky            return false;
344321936Shselasky        }
345321936Shselasky
346321936Shselasky        return true;
347321936Shselasky    }
348321936Shselasky
349321936Shselasky    /** Find source files in the given source locations. */
350321936Shselasky    public static void findSourceFiles(List<SourceLocation> sourceLocations,
351321936Shselasky                                       Set<String> sourceTypes,
352321936Shselasky                                       Map<String,Source> foundFiles,
353321936Shselasky                                       Map<String, Module> foundModules,
354321936Shselasky                                       Module currentModule,
355321936Shselasky                                       boolean permitSourcesInDefaultPackage,
356321936Shselasky                                       boolean inLinksrc)
357321936Shselasky                                               throws IOException {
358321936Shselasky
359321936Shselasky        for (SourceLocation source : sourceLocations) {
360321936Shselasky            source.findSourceFiles(sourceTypes,
361321936Shselasky                                   foundFiles,
362321936Shselasky                                   foundModules,
363321936Shselasky                                   currentModule,
364321936Shselasky                                   permitSourcesInDefaultPackage,
365321936Shselasky                                   inLinksrc);
366321936Shselasky        }
367321936Shselasky    }
368321936Shselasky
369321936Shselasky    private static void printRound(int round) {
370321936Shselasky        Log.debug("****************************************");
371321936Shselasky        Log.debug("* Round " + round + "                              *");
372321936Shselasky        Log.debug("****************************************");
373321936Shselasky    }
374321936Shselasky}
375321936Shselasky