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