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