2 * Copyright 2000-2010 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.packaging.impl.compiler;
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;
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;
67 public ArtifactsCompilerInstance(CompileContext context) {
73 public List<ArtifactBuildTarget> getAllTargets() {
74 return getArtifactTargets(false);
79 public List<ArtifactBuildTarget> getSelectedTargets() {
80 return getArtifactTargets(true);
83 private List<ArtifactBuildTarget> getArtifactTargets(final boolean selectedOnly) {
84 final List<ArtifactBuildTarget> targets = new ArrayList<ArtifactBuildTarget>();
86 protected void run(final Result result) {
87 final Set<Artifact> artifacts;
89 artifacts = ArtifactCompileScope.getArtifactsToBuild(getProject(), myContext.getCompileScope());
92 artifacts = new HashSet<Artifact>(Arrays.asList(ArtifactManager.getInstance(getProject()).getArtifacts()));
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);
103 if (LOG.isDebugEnabled() && !additionalArtifacts.isEmpty()) {
104 LOG.debug("additional artifacts to build: " + additionalArtifacts);
106 artifacts.addAll(additionalArtifacts);
108 for (Artifact artifact : artifacts) {
109 targets.add(new ArtifactBuildTarget(artifact));
112 myContext.putUserData(ArtifactsCompiler.AFFECTED_ARTIFACTS, artifacts);
120 public void processObsoleteTarget(@NotNull String targetId, @NotNull List<Pair<String, ArtifactPackagingItemOutputState>> obsoleteItems) {
121 deleteFiles(obsoleteItems, Collections.<Pair<ArtifactCompilerCompileItem, ArtifactPackagingItemOutputState>>emptyList());
126 public List<ArtifactCompilerCompileItem> getItems(@NotNull ArtifactBuildTarget target) {
127 myBuilderContext = new ArtifactsProcessingItemsBuilderContext(myContext);
128 final Artifact artifact = target.getArtifact();
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());
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();
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",
144 return Collections.emptyList();
148 protected void run(final Result result) {
149 collectItems(artifact, outputPath);
152 return new ArrayList<ArtifactCompilerCompileItem>(myBuilderContext.getProcessingItems());
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());
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();
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);
176 onBuildStartedOrFinished(false);
177 if (myContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
182 for (final Pair<ArtifactCompilerCompileItem, ArtifactPackagingItemOutputState> item : changedItems) {
183 final ArtifactCompilerCompileItem sourceItem = item.getFirst();
184 myContext.getProgressIndicator().checkCanceled();
186 final Ref<IOException> exception = Ref.create(null);
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()));
195 if (sourceFile.isInLocalFileSystem()) {
196 final File ioFromFile = VfsUtil.virtualToIoFile(sourceFile);
197 if (ioFromFile.exists()) {
198 deploymentUtil.copyFile(ioFromFile, toFile, myContext, writtenPaths, fileFilter);
202 extractFile(sourceFile, toFile, writtenPaths, fileFilter);
205 catch (IOException e) {
211 changedJars.add(((JarDestinationInfo)destination).getJarInfo());
216 if (exception.get() != null) {
217 throw exception.get();
220 myContext.getProgressIndicator().setFraction(++i * 1.0 / changedItems.size());
221 processedItems.add(sourceItem);
223 CompilerManagerImpl.addRecompiledPath(FileUtil.toSystemDependentName(sourceItem.getFile().getPath()));
227 JarsBuilder builder = new JarsBuilder(changedJars, fileFilter, myContext);
228 final boolean processed = builder.buildJars(writtenPaths);
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());
239 for (VirtualFile source : recompiledSources) {
240 ArtifactCompilerCompileItem item = myBuilderContext.getItemBySource(source);
241 LOG.assertTrue(item != null, source);
242 processedItems.add(item);
244 CompilerManagerImpl.addRecompiledPath(FileUtil.toSystemDependentName(item.getFile().getPath()));
248 onBuildStartedOrFinished(true);
250 catch (ProcessCanceledException e) {
253 catch (Exception e) {
255 myContext.addMessage(CompilerMessageCategory.ERROR, e.getLocalizedMessage(), null, -1, -1);
261 private void extractFile(VirtualFile sourceFile, File toFile, Set<String> writtenPaths, FileFilter fileFilter) throws IOException {
262 if (!writtenPaths.add(toFile.getPath())) {
266 if (!FileUtil.createParentDirs(toFile)) {
267 myContext.addMessage(CompilerMessageCategory.ERROR, "Cannot create directory for '" + toFile.getAbsolutePath() + "' file", null, -1, -1);
271 final BufferedInputStream input = ArtifactCompilerUtil.getJarEntryInputStream(sourceFile, myContext);
272 if (input == null) return;
273 final BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(toFile));
275 FileUtil.copy(input, output);
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);
290 properties.onBuildFinished(artifact, myContext);
293 properties.onBuildStarted(artifact, myContext);
300 private static THashSet<String> createPathsHashSet() {
301 return SystemInfo.isFileSystemCaseSensitive
302 ? new THashSet<String>()
303 : new THashSet<String>(CaseInsensitiveStringHashingStrategy.INSTANCE);
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) {
311 final THashSet<String> deletedJars = deleteFiles(obsoleteItems, changedItems);
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));
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));
330 for (ArtifactCompilerCompileItem item : processedItems) {
331 consumer.addProcessedItem(item);
333 myContext.putUserData(ArtifactsCompiler.WRITTEN_PATHS_KEY, writtenPaths);
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"));
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...");
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());
356 for (Pair<ArtifactCompilerCompileItem, ArtifactPackagingItemOutputState> item : changedItems) {
357 for (DestinationInfo destination : item.getFirst().getDestinations()) {
358 pathToDelete.remove(destination.getOutputPath());
361 for (Pair<String, ArtifactPackagingItemOutputState> item : obsoleteItems) {
362 for (Pair<String, Long> destination : item.getSecond().myDestinations) {
363 pathToDelete.add(destination.getFirst());
367 int notDeletedFilesCount = 0;
368 List<File> filesToRefresh = new ArrayList<File>();
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;
376 if (notDeletedJars.contains(filePath)) {
379 deleted = deletedJars.contains(filePath);
382 File file = new File(FileUtil.toSystemDependentName(filePath));
384 filesToRefresh.add(file);
385 deleted = FileUtil.delete(file);
390 deletedJars.add(filePath);
393 CompilerManagerImpl.addDeletedPath(file.getAbsolutePath());
398 notDeletedJars.add(filePath);
400 if (notDeletedFilesCount++ > 50) {
401 myContext.addMessage(CompilerMessageCategory.WARNING, "Deletion of outdated files stopped because too many files cannot be deleted", null, -1, -1);
404 myContext.addMessage(CompilerMessageCategory.WARNING, "Cannot delete file '" + filePath + "'", null, -1, -1);
405 if (LOG.isDebugEnabled()) {
406 LOG.debug("Cannot delete file " + file);
411 CompilerUtil.refreshIOFiles(filesToRefresh);