new compiler api: persistent state splitted to source and output state
[idea/community.git] / java / compiler / impl / src / com / intellij / packaging / impl / compiler / ArtifactsCompilerInstance.java
1 /*
2  * Copyright 2000-2010 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.packaging.impl.compiler;
17
18 import com.intellij.compiler.CompilerManagerImpl;
19 import com.intellij.compiler.impl.CompilerUtil;
20 import com.intellij.compiler.impl.newApi.CompilerInstance;
21 import com.intellij.compiler.impl.newApi.NewCompilerItemState;
22 import com.intellij.compiler.impl.newApi.VirtualFilePersistentState;
23 import com.intellij.compiler.impl.packagingCompiler.*;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.application.ReadAction;
26 import com.intellij.openapi.application.Result;
27 import com.intellij.openapi.compiler.CompileContext;
28 import com.intellij.openapi.compiler.CompilerBundle;
29 import com.intellij.openapi.compiler.CompilerMessageCategory;
30 import com.intellij.openapi.compiler.make.BuildParticipant;
31 import com.intellij.openapi.compiler.make.BuildParticipantProvider;
32 import com.intellij.openapi.deployment.DeploymentUtil;
33 import com.intellij.openapi.diagnostic.Logger;
34 import com.intellij.openapi.module.Module;
35 import com.intellij.openapi.module.ModuleManager;
36 import com.intellij.openapi.progress.ProcessCanceledException;
37 import com.intellij.openapi.util.Pair;
38 import com.intellij.openapi.util.Ref;
39 import com.intellij.openapi.util.SystemInfo;
40 import com.intellij.openapi.util.io.FileUtil;
41 import com.intellij.openapi.vfs.JarFileSystem;
42 import com.intellij.openapi.vfs.LocalFileSystem;
43 import com.intellij.openapi.vfs.VfsUtil;
44 import com.intellij.openapi.vfs.VirtualFile;
45 import com.intellij.packaging.artifacts.Artifact;
46 import com.intellij.packaging.artifacts.ArtifactManager;
47 import com.intellij.packaging.artifacts.ArtifactProperties;
48 import com.intellij.packaging.artifacts.ArtifactPropertiesProvider;
49 import com.intellij.packaging.elements.CompositePackagingElement;
50 import com.intellij.packaging.elements.PackagingElementResolvingContext;
51 import com.intellij.packaging.impl.artifacts.ArtifactValidationUtil;
52 import com.intellij.util.ThrowableRunnable;
53 import com.intellij.util.containers.ContainerUtil;
54 import com.intellij.util.text.CaseInsensitiveStringHashingStrategy;
55 import gnu.trove.THashSet;
56 import org.jetbrains.annotations.NotNull;
57
58 import java.io.*;
59 import java.util.*;
60
61 /**
62  * @author nik
63  */
64 public class ArtifactsCompilerInstance extends CompilerInstance<ArtifactBuildTarget, ArtifactCompilerCompileItem,
65   String, VirtualFilePersistentState, ArtifactPackagingItemOutputState> {
66   private static final Logger LOG = Logger.getInstance("#com.intellij.packaging.impl.compiler.ArtifactsCompilerInstance");
67   private ArtifactsProcessingItemsBuilderContext myBuilderContext;
68
69   public ArtifactsCompilerInstance(CompileContext context) {
70     super(context);
71   }
72
73   @NotNull
74   @Override
75   public List<ArtifactBuildTarget> getAllTargets() {
76     return getArtifactTargets(false);
77   }
78
79   @NotNull
80   @Override
81   public List<ArtifactBuildTarget> getSelectedTargets() {
82     return getArtifactTargets(true);
83   }
84
85   private List<ArtifactBuildTarget> getArtifactTargets(final boolean selectedOnly) {
86     final List<ArtifactBuildTarget> targets = new ArrayList<ArtifactBuildTarget>();
87     new ReadAction() {
88       protected void run(final Result result) {
89         final Set<Artifact> artifacts;
90         if (selectedOnly) {
91           artifacts = ArtifactCompileScope.getArtifactsToBuild(getProject(), myContext.getCompileScope());
92         }
93         else {
94           artifacts = new HashSet<Artifact>(Arrays.asList(ArtifactManager.getInstance(getProject()).getArtifacts()));
95         }
96         List<Artifact> additionalArtifacts = new ArrayList<Artifact>();
97         for (BuildParticipantProvider provider : BuildParticipantProvider.EXTENSION_POINT_NAME.getExtensions()) {
98           for (Module module : ModuleManager.getInstance(getProject()).getModules()) {
99             final Collection<? extends BuildParticipant> participants = provider.getParticipants(module);
100             for (BuildParticipant participant : participants) {
101               ContainerUtil.addIfNotNull(participant.createArtifact(myContext), additionalArtifacts);
102             }
103           }
104         }
105         if (LOG.isDebugEnabled() && !additionalArtifacts.isEmpty()) {
106           LOG.debug("additional artifacts to build: " + additionalArtifacts);
107         }
108         artifacts.addAll(additionalArtifacts);
109
110         for (Artifact artifact : artifacts) {
111           targets.add(new ArtifactBuildTarget(artifact));
112         }
113         if (selectedOnly) {
114           myContext.putUserData(ArtifactsCompiler.AFFECTED_ARTIFACTS, artifacts);
115         }
116       }
117     }.execute();
118     return targets;
119   }
120
121   @Override
122   public void processObsoleteTarget(@NotNull String targetId,
123                                     @NotNull List<NewCompilerItemState<String, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> obsoleteItems) {
124     deleteFiles(obsoleteItems, Collections.<NewCompilerItemState<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState>>emptyList());
125   }
126
127   @NotNull
128   @Override
129   public List<ArtifactCompilerCompileItem> getItems(@NotNull ArtifactBuildTarget target) {
130     myBuilderContext = new ArtifactsProcessingItemsBuilderContext(myContext);
131     final Artifact artifact = target.getArtifact();
132
133     final Set<Artifact> selfIncludingArtifacts = new ReadAction<Set<Artifact>>() {
134       protected void run(final Result<Set<Artifact>> result) {
135         result.setResult(ArtifactValidationUtil.getInstance(getProject()).getSelfIncludingArtifacts());
136       }
137     }.execute().getResultObject();
138     if (selfIncludingArtifacts.contains(artifact)) {
139       myContext.addMessage(CompilerMessageCategory.ERROR, "Artifact '" + artifact.getName() + "' includes itself in the output layout", null, -1, -1);
140       return Collections.emptyList();
141     }
142
143     final String outputPath = artifact.getOutputPath();
144     if (outputPath == null || outputPath.length() == 0) {
145       myContext.addMessage(CompilerMessageCategory.ERROR, "Cannot build '" + artifact.getName() + "' artifact: output path is not specified",
146                       null, -1, -1);
147       return Collections.emptyList();
148     }
149
150     new ReadAction() {
151       protected void run(final Result result) {
152         collectItems(artifact, outputPath);
153       }
154     }.execute();
155     return new ArrayList<ArtifactCompilerCompileItem>(myBuilderContext.getProcessingItems());
156   }
157
158   private void collectItems(@NotNull Artifact artifact, @NotNull String outputPath) {
159     final CompositePackagingElement<?> rootElement = artifact.getRootElement();
160     final VirtualFile outputFile = LocalFileSystem.getInstance().findFileByPath(outputPath);
161     final CopyToDirectoryInstructionCreator instructionCreator = new CopyToDirectoryInstructionCreator(myBuilderContext, outputPath, outputFile);
162     final PackagingElementResolvingContext resolvingContext = ArtifactManager.getInstance(getProject()).getResolvingContext();
163     rootElement.computeIncrementalCompilerInstructions(instructionCreator, resolvingContext, myBuilderContext, artifact.getArtifactType());
164   }
165
166   private boolean doBuild(final List<NewCompilerItemState<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> changedItems,
167                           final Set<ArtifactCompilerCompileItem> processedItems,
168                           final @NotNull Set<String> writtenPaths, final Set<String> deletedJars) {
169     final boolean testMode = ApplicationManager.getApplication().isUnitTestMode();
170
171     final DeploymentUtil deploymentUtil = DeploymentUtil.getInstance();
172     final FileFilter fileFilter = new IgnoredFileFilter();
173     final Set<JarInfo> changedJars = new THashSet<JarInfo>();
174     for (String deletedJar : deletedJars) {
175       ContainerUtil.addIfNotNull(myBuilderContext.getJarInfo(deletedJar), changedJars);
176     }
177
178     try {
179       onBuildStartedOrFinished(false);
180       if (myContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
181         return false;
182       }
183
184       int i = 0;
185       for (final NewCompilerItemState<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState> item : changedItems) {
186         final ArtifactCompilerCompileItem sourceItem = item.getItem();
187         myContext.getProgressIndicator().checkCanceled();
188
189         final Ref<IOException> exception = Ref.create(null);
190         new ReadAction() {
191           protected void run(final Result result) {
192             final VirtualFile sourceFile = sourceItem.getFile();
193             for (DestinationInfo destination : sourceItem.getDestinations()) {
194               if (destination instanceof ExplodedDestinationInfo) {
195                 final ExplodedDestinationInfo explodedDestination = (ExplodedDestinationInfo)destination;
196                 File toFile = new File(FileUtil.toSystemDependentName(explodedDestination.getOutputPath()));
197                 try {
198                   if (sourceFile.isInLocalFileSystem()) {
199                     final File ioFromFile = VfsUtil.virtualToIoFile(sourceFile);
200                     if (ioFromFile.exists()) {
201                       deploymentUtil.copyFile(ioFromFile, toFile, myContext, writtenPaths, fileFilter);
202                     }
203                   }
204                   else {
205                     extractFile(sourceFile, toFile, writtenPaths, fileFilter);
206                   }
207                 }
208                 catch (IOException e) {
209                   exception.set(e);
210                   return;
211                 }
212               }
213               else {
214                 changedJars.add(((JarDestinationInfo)destination).getJarInfo());
215               }
216             }
217           }
218         }.execute();
219         if (exception.get() != null) {
220           throw exception.get();
221         }
222
223         myContext.getProgressIndicator().setFraction(++i * 1.0 / changedItems.size());
224         processedItems.add(sourceItem);
225         if (testMode) {
226           CompilerManagerImpl.addRecompiledPath(FileUtil.toSystemDependentName(sourceItem.getFile().getPath()));
227         }
228       }
229
230       JarsBuilder builder = new JarsBuilder(changedJars, fileFilter, myContext);
231       final boolean processed = builder.buildJars(writtenPaths);
232       if (!processed) {
233         return false;
234       }
235
236       Set<VirtualFile> recompiledSources = new HashSet<VirtualFile>();
237       for (JarInfo info : builder.getJarsToBuild()) {
238         for (Pair<String, VirtualFile> pair : info.getPackedFiles()) {
239           recompiledSources.add(pair.getSecond());
240         }
241       }
242       for (VirtualFile source : recompiledSources) {
243         ArtifactCompilerCompileItem item = myBuilderContext.getItemBySource(source);
244         LOG.assertTrue(item != null, source);
245         processedItems.add(item);
246         if (testMode) {
247           CompilerManagerImpl.addRecompiledPath(FileUtil.toSystemDependentName(item.getFile().getPath()));
248         }
249       }
250
251       onBuildStartedOrFinished(true);
252     }
253     catch (ProcessCanceledException e) {
254       throw e;
255     }
256     catch (Exception e) {
257       LOG.info(e);
258       myContext.addMessage(CompilerMessageCategory.ERROR, e.getLocalizedMessage(), null, -1, -1);
259       return false;
260     }
261     return true;
262   }
263
264   private void extractFile(VirtualFile sourceFile, File toFile, Set<String> writtenPaths, FileFilter fileFilter) throws IOException {
265     if (!writtenPaths.add(toFile.getPath())) {
266       return;
267     }
268
269     if (!FileUtil.createParentDirs(toFile)) {
270       myContext.addMessage(CompilerMessageCategory.ERROR, "Cannot create directory for '" + toFile.getAbsolutePath() + "' file", null, -1, -1);
271       return;
272     }
273
274     final BufferedInputStream input = ArtifactCompilerUtil.getJarEntryInputStream(sourceFile, myContext);
275     if (input == null) return;
276     final BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(toFile));
277     try {
278       FileUtil.copy(input, output);
279     }
280     finally {
281       input.close();
282       output.close();
283     }
284   }
285
286   private void onBuildStartedOrFinished(final boolean finished) throws Exception {
287     final Set<Artifact> artifacts = myContext.getUserData(ArtifactsCompiler.AFFECTED_ARTIFACTS);
288     if (artifacts != null) {
289       for (Artifact artifact : artifacts) {
290         for (ArtifactPropertiesProvider provider : artifact.getPropertiesProviders()) {
291           final ArtifactProperties<?> properties = artifact.getProperties(provider);
292           if (finished) {
293             properties.onBuildFinished(artifact, myContext);
294           }
295           else {
296             properties.onBuildStarted(artifact, myContext);
297           }
298         }
299       }
300     }
301   }
302
303   private static THashSet<String> createPathsHashSet() {
304     return SystemInfo.isFileSystemCaseSensitive
305            ? new THashSet<String>()
306            : new THashSet<String>(CaseInsensitiveStringHashingStrategy.INSTANCE);
307   }
308
309   @Override
310   public void processItems(@NotNull ArtifactBuildTarget target,
311                            @NotNull final List<NewCompilerItemState<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> changedItems,
312                            @NotNull List<NewCompilerItemState<String, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> obsoleteItems,
313                            @NotNull OutputConsumer<ArtifactCompilerCompileItem> consumer) {
314
315     final THashSet<String> deletedJars = deleteFiles(obsoleteItems, changedItems);
316
317     final Set<String> writtenPaths = createPathsHashSet();
318     final Ref<Boolean> built = Ref.create(false);
319     final Set<ArtifactCompilerCompileItem> processedItems = new HashSet<ArtifactCompilerCompileItem>();
320     CompilerUtil.runInContext(myContext, "Copying files", new ThrowableRunnable<RuntimeException>() {
321       public void run() throws RuntimeException {
322         built.set(doBuild(changedItems, processedItems, writtenPaths, deletedJars));
323       }
324     });
325     if (!built.get()) {
326       return;
327     }
328
329     myContext.getProgressIndicator().setText(CompilerBundle.message("packaging.compiler.message.updating.caches"));
330     myContext.getProgressIndicator().setText2("");
331     for (String path : writtenPaths) {
332       consumer.addFileToRefresh(new File(path));
333     }
334     for (ArtifactCompilerCompileItem item : processedItems) {
335       consumer.addProcessedItem(item);
336     }
337     myContext.putUserData(ArtifactsCompiler.WRITTEN_PATHS_KEY, writtenPaths);
338   }
339
340   private THashSet<String> deleteFiles(List<NewCompilerItemState<String, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> obsoleteItems,
341                                        List<NewCompilerItemState<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> changedItems) {
342     myContext.getProgressIndicator().setText(CompilerBundle.message("packaging.compiler.message.deleting.outdated.files"));
343
344     final boolean testMode = ApplicationManager.getApplication().isUnitTestMode();
345     final THashSet<String> deletedJars = new THashSet<String>();
346     final THashSet<String> notDeletedJars = new THashSet<String>();
347     if (LOG.isDebugEnabled()) {
348       LOG.debug("Deleting outdated files...");
349     }
350
351     Set<String> pathToDelete = new THashSet<String>();
352     for (NewCompilerItemState<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState> item : changedItems) {
353       final ArtifactPackagingItemOutputState cached = item.getOutputState();
354       if (cached != null) {
355         for (Pair<String, Long> destination : cached.myDestinations) {
356           pathToDelete.add(destination.getFirst());
357         }
358       }
359     }
360     for (NewCompilerItemState<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState> item : changedItems) {
361       for (DestinationInfo destination : item.getItem().getDestinations()) {
362         pathToDelete.remove(destination.getOutputPath());
363       }
364     }
365     for (NewCompilerItemState<String, VirtualFilePersistentState, ArtifactPackagingItemOutputState> item : obsoleteItems) {
366       for (Pair<String, Long> destination : item.getOutputState().myDestinations) {
367         pathToDelete.add(destination.getFirst());
368       }
369     }
370
371     int notDeletedFilesCount = 0;
372     List<File> filesToRefresh = new ArrayList<File>();
373
374     for (String fullPath : pathToDelete) {
375       int end = fullPath.indexOf(JarFileSystem.JAR_SEPARATOR);
376       boolean isJar = end != -1;
377       String filePath = isJar ? fullPath.substring(0, end) : fullPath;
378       boolean deleted = false;
379       if (isJar) {
380       if (notDeletedJars.contains(filePath)) {
381         continue;
382       }
383       deleted = deletedJars.contains(filePath);
384     }
385
386       File file = new File(FileUtil.toSystemDependentName(filePath));
387       if (!deleted) {
388       filesToRefresh.add(file);
389       deleted = FileUtil.delete(file);
390     }
391
392       if (deleted) {
393       if (isJar) {
394         deletedJars.add(filePath);
395       }
396       if (testMode) {
397         CompilerManagerImpl.addDeletedPath(file.getAbsolutePath());
398       }
399     }
400     else {
401       if (isJar) {
402         notDeletedJars.add(filePath);
403       }
404       if (notDeletedFilesCount++ > 50) {
405         myContext.addMessage(CompilerMessageCategory.WARNING, "Deletion of outdated files stopped because too many files cannot be deleted", null, -1, -1);
406         break;
407       }
408       myContext.addMessage(CompilerMessageCategory.WARNING, "Cannot delete file '" + filePath + "'", null, -1, -1);
409       if (LOG.isDebugEnabled()) {
410         LOG.debug("Cannot delete file " + file);
411       }
412     }
413     }
414
415     CompilerUtil.refreshIOFiles(filesToRefresh);
416     return deletedJars;
417   }
418
419 }