/* * Copyright 2000-2012 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.packaging.impl.compiler; import com.intellij.compiler.impl.CompilerUtil; import com.intellij.compiler.impl.packagingCompiler.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.application.Result; import com.intellij.openapi.compiler.CompileContext; import com.intellij.openapi.compiler.CompilerBundle; import com.intellij.openapi.compiler.CompilerMessageCategory; import com.intellij.openapi.compiler.generic.GenericCompilerCacheState; import com.intellij.openapi.compiler.generic.GenericCompilerInstance; import com.intellij.openapi.compiler.generic.GenericCompilerProcessingItem; import com.intellij.openapi.compiler.generic.VirtualFilePersistentState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.packaging.artifacts.Artifact; import com.intellij.packaging.artifacts.ArtifactManager; import com.intellij.packaging.artifacts.ArtifactProperties; import com.intellij.packaging.artifacts.ArtifactPropertiesProvider; import com.intellij.packaging.elements.CompositePackagingElement; import com.intellij.packaging.elements.PackagingElementResolvingContext; import com.intellij.packaging.impl.artifacts.ArtifactSortingUtil; import consulo.packaging.impl.util.DeploymentUtilImpl; import com.intellij.util.ExceptionUtil; import com.intellij.util.ThrowableRunnable; import com.intellij.util.containers.ContainerUtil; import consulo.vfs.ArchiveFileSystem; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import java.io.*; import java.util.*; /** * @author nik */ public class ArtifactsCompilerInstance extends GenericCompilerInstance<ArtifactBuildTarget, ArtifactCompilerCompileItem, String, VirtualFilePersistentState, ArtifactPackagingItemOutputState> { private static final Logger LOG = Logger.getInstance("#com.intellij.packaging.impl.compiler.ArtifactsCompilerInstance"); public static final Logger FULL_LOG = Logger.getInstance("#com.intellij.full-artifacts-compiler-log"); private ArtifactsProcessingItemsBuilderContext myBuilderContext; public ArtifactsCompilerInstance(CompileContext context) { super(context); } @NotNull @Override public List<ArtifactBuildTarget> getAllTargets() { return getArtifactTargets(false); } @NotNull @Override public List<ArtifactBuildTarget> getSelectedTargets() { return getArtifactTargets(true); } private List<ArtifactBuildTarget> getArtifactTargets(final boolean selectedOnly) { final List<ArtifactBuildTarget> targets = new ArrayList<ArtifactBuildTarget>(); new ReadAction() { @Override protected void run(final Result result) { final Set<Artifact> artifacts; if (selectedOnly) { artifacts = ArtifactCompileScope.getArtifactsToBuild(getProject(), myContext.getCompileScope(), true); } else { artifacts = new HashSet<Artifact>(Arrays.asList(ArtifactManager.getInstance(getProject()).getArtifacts())); } Map<String, Artifact> artifactsMap = new HashMap<String, Artifact>(); for (Artifact artifact : artifacts) { artifactsMap.put(artifact.getName(), artifact); } for (String name : ArtifactSortingUtil.getInstance(getProject()).getArtifactsSortedByInclusion()) { Artifact artifact = artifactsMap.get(name); if (artifact != null) { targets.add(new ArtifactBuildTarget(artifact)); } } } }.execute(); return targets; } @Override public void processObsoleteTarget(@NotNull String targetId, @NotNull List<GenericCompilerCacheState<String, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> obsoleteItems) { deleteFiles(obsoleteItems, Collections.<GenericCompilerProcessingItem<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState>>emptyList()); } @NotNull @Override public List<ArtifactCompilerCompileItem> getItems(@NotNull ArtifactBuildTarget target) { myBuilderContext = new ArtifactsProcessingItemsBuilderContext(myContext); final Artifact artifact = target.getArtifact(); final Map<String, String> selfIncludingArtifacts = new ReadAction<Map<String, String>>() { @Override protected void run(final Result<Map<String, String>> result) { result.setResult(ArtifactSortingUtil.getInstance(getProject()).getArtifactToSelfIncludingNameMap()); } }.execute().getResultObject(); final String selfIncludingName = selfIncludingArtifacts.get(artifact.getName()); if (selfIncludingName != null) { String name = selfIncludingName.equals(artifact.getName()) ? "it" : "'" + selfIncludingName + "' artifact"; myContext.addMessage(CompilerMessageCategory.ERROR, "Cannot build '" + artifact.getName() + "' artifact: " + name + " includes itself in the output layout", null, -1, -1); return Collections.emptyList(); } final String outputPath = artifact.getOutputPath(); if (outputPath == null || outputPath.length() == 0) { myContext.addMessage(CompilerMessageCategory.ERROR, "Cannot build '" + artifact.getName() + "' artifact: output path is not specified", null, -1, -1); return Collections.emptyList(); } DumbService.getInstance(getProject()).waitForSmartMode(); new ReadAction() { @Override protected void run(final Result result) { collectItems(artifact, outputPath); } }.execute(); return new ArrayList<ArtifactCompilerCompileItem>(myBuilderContext.getProcessingItems()); } private void collectItems(@NotNull Artifact artifact, @NotNull String outputPath) { final CompositePackagingElement<?> rootElement = artifact.getRootElement(); final VirtualFile outputFile = LocalFileSystem.getInstance().findFileByPath(outputPath); final CopyToDirectoryInstructionCreator instructionCreator = new CopyToDirectoryInstructionCreator(myBuilderContext, outputPath, outputFile); final PackagingElementResolvingContext resolvingContext = ArtifactManager.getInstance(getProject()).getResolvingContext(); FULL_LOG.debug("Collecting items for " + artifact.getName()); rootElement.computeIncrementalCompilerInstructions(instructionCreator, resolvingContext, myBuilderContext, artifact.getArtifactType()); } private boolean doBuild(@NotNull Artifact artifact, final List<GenericCompilerProcessingItem<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> changedItems, final Set<ArtifactCompilerCompileItem> processedItems, final @NotNull Set<String> writtenPaths, final Set<String> deletedJars) { FULL_LOG.debug("Building " + artifact.getName()); final boolean testMode = ApplicationManager.getApplication().isUnitTestMode(); final FileFilter fileFilter = new IgnoredFileFilter(); final Set<ArchivePackageInfo> changedJars = new THashSet<ArchivePackageInfo>(); for (String deletedJar : deletedJars) { ContainerUtil.addIfNotNull(myBuilderContext.getJarInfo(deletedJar), changedJars); } try { onBuildStartedOrFinished(artifact, false); if (myContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) { return false; } int i = 0; for (final GenericCompilerProcessingItem<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState> item : changedItems) { final ArtifactCompilerCompileItem sourceItem = item.getItem(); myContext.getProgressIndicator().checkCanceled(); final Ref<IOException> exception = Ref.create(null); new ReadAction() { @Override protected void run(final Result result) { final VirtualFile sourceFile = sourceItem.getFile(); for (DestinationInfo destination : sourceItem.getDestinations()) { if (destination instanceof ExplodedDestinationInfo) { final ExplodedDestinationInfo explodedDestination = (ExplodedDestinationInfo)destination; File toFile = new File(FileUtil.toSystemDependentName(explodedDestination.getOutputPath())); try { if (sourceFile.isInLocalFileSystem()) { final File ioFromFile = VfsUtilCore.virtualToIoFile(sourceFile); if (ioFromFile.exists()) { DeploymentUtilImpl.copyFile(ioFromFile, toFile, myContext, writtenPaths, fileFilter); } else { LOG.debug("Cannot copy " + ioFromFile.getAbsolutePath() + ": file doesn't exist"); } } else { extractFile(sourceFile, toFile, writtenPaths, fileFilter); } } catch (IOException e) { exception.set(e); return; } } else { changedJars.add(((ArchiveDestinationInfo)destination).getArchivePackageInfo()); } } } }.execute(); if (exception.get() != null) { throw exception.get(); } myContext.getProgressIndicator().setFraction(++i * 1.0 / changedItems.size()); processedItems.add(sourceItem); if (testMode) { //FIXME [VISTALL] CompilerManagerImpl.addRecompiledPath(FileUtil.toSystemDependentName(sourceItem.getFile().getPath())); } } ArchivesBuilder builder = new ArchivesBuilder(changedJars, fileFilter, myContext); final boolean processed = builder.buildArchives(writtenPaths); if (!processed) { return false; } Set<VirtualFile> recompiledSources = new HashSet<VirtualFile>(); for (ArchivePackageInfo info : builder.getArchivesToBuild()) { for (Pair<String, VirtualFile> pair : info.getPackedFiles()) { recompiledSources.add(pair.getSecond()); } } for (VirtualFile source : recompiledSources) { ArtifactCompilerCompileItem item = myBuilderContext.getItemBySource(source); LOG.assertTrue(item != null, source); processedItems.add(item); if (testMode) { //FIXME [VISTALL] CompilerManagerImpl.addRecompiledPath(FileUtil.toSystemDependentName(item.getFile().getPath())); } } onBuildStartedOrFinished(artifact, true); } catch (ProcessCanceledException e) { throw e; } catch (Exception e) { LOG.info(e); myContext.addMessage(CompilerMessageCategory.ERROR, ExceptionUtil.getThrowableText(e), null, -1, -1); return false; } return true; } private void extractFile(VirtualFile sourceFile, File toFile, Set<String> writtenPaths, FileFilter fileFilter) throws IOException { if (!writtenPaths.add(toFile.getPath())) { return; } if (!FileUtil.createParentDirs(toFile)) { myContext.addMessage(CompilerMessageCategory.ERROR, "Cannot create directory for '" + toFile.getAbsolutePath() + "' file", null, -1, -1); return; } InputStream input = ArtifactCompilerUtil.getArchiveEntryInputStream(sourceFile, myContext).getFirst(); if (input == null) return; final BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(toFile)); try { FileUtil.copy(input, output); } finally { input.close(); output.close(); } } private void onBuildStartedOrFinished(@NotNull Artifact artifact, final boolean finished) throws Exception { for (ArtifactPropertiesProvider provider : artifact.getPropertiesProviders()) { final ArtifactProperties<?> properties = artifact.getProperties(provider); if (finished) { properties.onBuildFinished(artifact, myContext); } else { properties.onBuildStarted(artifact, myContext); } } } private static THashSet<String> createPathsHashSet() { return new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY); } @Override public void processItems(@NotNull final ArtifactBuildTarget target, @NotNull final List<GenericCompilerProcessingItem<ArtifactCompilerCompileItem,VirtualFilePersistentState,ArtifactPackagingItemOutputState>> changedItems, @NotNull List<GenericCompilerCacheState<String, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> obsoleteItems, @NotNull OutputConsumer<ArtifactCompilerCompileItem> consumer) { final THashSet<String> deletedJars = deleteFiles(obsoleteItems, changedItems); final Set<String> writtenPaths = createPathsHashSet(); final Ref<Boolean> built = Ref.create(false); final Set<ArtifactCompilerCompileItem> processedItems = new HashSet<ArtifactCompilerCompileItem>(); CompilerUtil.runInContext(myContext, "Copying files", new ThrowableRunnable<RuntimeException>() { @Override public void run() throws RuntimeException { built.set(doBuild(target.getArtifact(), changedItems, processedItems, writtenPaths, deletedJars)); } }); if (!built.get()) { return; } myContext.getProgressIndicator().setText(CompilerBundle.message("packaging.compiler.message.updating.caches")); myContext.getProgressIndicator().setText2(""); for (String path : writtenPaths) { consumer.addFileToRefresh(new File(path)); } for (ArtifactCompilerCompileItem item : processedItems) { consumer.addProcessedItem(item); } ArtifactsCompiler.addWrittenPaths(myContext, writtenPaths); ArtifactsCompiler.addChangedArtifact(myContext, target.getArtifact()); } private THashSet<String> deleteFiles(List<GenericCompilerCacheState<String, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> obsoleteItems, List<GenericCompilerProcessingItem<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> changedItems) { myContext.getProgressIndicator().setText(CompilerBundle.message("packaging.compiler.message.deleting.outdated.files")); final boolean testMode = ApplicationManager.getApplication().isUnitTestMode(); final THashSet<String> deletedJars = new THashSet<String>(); final THashSet<String> notDeletedJars = new THashSet<String>(); if (LOG.isDebugEnabled()) { LOG.debug("Deleting outdated files..."); } Set<String> pathToDelete = new THashSet<String>(); for (GenericCompilerProcessingItem<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState> item : changedItems) { final ArtifactPackagingItemOutputState cached = item.getCachedOutputState(); if (cached != null) { for (Pair<String, Long> destination : cached.myDestinations) { pathToDelete.add(destination.getFirst()); } } } for (GenericCompilerProcessingItem<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState> item : changedItems) { for (DestinationInfo destination : item.getItem().getDestinations()) { pathToDelete.remove(destination.getOutputPath()); } } for (GenericCompilerCacheState<String, VirtualFilePersistentState, ArtifactPackagingItemOutputState> item : obsoleteItems) { for (Pair<String, Long> destination : item.getOutputState().myDestinations) { pathToDelete.add(destination.getFirst()); } } int notDeletedFilesCount = 0; List<File> filesToRefresh = new ArrayList<File>(); for (String fullPath : pathToDelete) { int end = fullPath.indexOf(ArchiveFileSystem.ARCHIVE_SEPARATOR); boolean isJar = end != -1; String filePath = isJar ? fullPath.substring(0, end) : fullPath; boolean deleted = false; if (isJar) { if (notDeletedJars.contains(filePath)) { continue; } deleted = deletedJars.contains(filePath); } File file = new File(FileUtil.toSystemDependentName(filePath)); if (!deleted) { filesToRefresh.add(file); deleted = FileUtil.delete(file); } if (deleted) { if (isJar) { deletedJars.add(filePath); } if (testMode) { //FIXME [VISTALL] CompilerManagerImpl.addDeletedPath(file.getAbsolutePath()); } } else { if (isJar) { notDeletedJars.add(filePath); } if (notDeletedFilesCount++ > 50) { myContext.addMessage(CompilerMessageCategory.WARNING, "Deletion of outdated files stopped because too many files cannot be deleted", null, -1, -1); break; } myContext.addMessage(CompilerMessageCategory.WARNING, "Cannot delete file '" + filePath + "'", null, -1, -1); if (LOG.isDebugEnabled()) { LOG.debug("Cannot delete file " + file); } } } CompilerUtil.refreshIOFiles(filesToRefresh); return deletedJars; } }