| /* |
| * 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 org.jetbrains.jps.incremental; |
| |
| import com.intellij.openapi.util.io.FileUtil; |
| import gnu.trove.THashSet; |
| import gnu.trove.TObjectIntHashMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.jps.builders.*; |
| import org.jetbrains.jps.builders.impl.BuildTargetChunk; |
| import org.jetbrains.jps.builders.logging.ProjectBuilderLogger; |
| import org.jetbrains.jps.builders.storage.SourceToOutputMapping; |
| import org.jetbrains.jps.cmdline.ProjectDescriptor; |
| import org.jetbrains.jps.incremental.fs.BuildFSState; |
| import org.jetbrains.jps.incremental.messages.DoneSomethingNotification; |
| import org.jetbrains.jps.incremental.messages.FileDeletedEvent; |
| import org.jetbrains.jps.incremental.storage.BuildDataManager; |
| import org.jetbrains.jps.incremental.storage.BuildTargetConfiguration; |
| import org.jetbrains.jps.incremental.storage.Timestamps; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.*; |
| |
| /** |
| * @author Eugene Zhuravlev |
| * Date: 10/30/12 |
| */ |
| public class BuildOperations { |
| private BuildOperations() { |
| } |
| |
| public static void ensureFSStateInitialized(CompileContext context, BuildTarget<?> target) throws IOException { |
| final ProjectDescriptor pd = context.getProjectDescriptor(); |
| final Timestamps timestamps = pd.timestamps.getStorage(); |
| final BuildTargetConfiguration configuration = pd.getTargetsState().getTargetConfiguration(target); |
| if (context.isProjectRebuild()) { |
| FSOperations.markDirtyFiles(context, target, timestamps, true, null, null); |
| pd.fsState.markInitialScanPerformed(target); |
| configuration.save(context); |
| } |
| else if (context.getScope().isBuildForced(target) || configuration.isTargetDirty(context) || configuration.outputRootWasDeleted(context)) { |
| initTargetFSState(context, target, true); |
| IncProjectBuilder.clearOutputFiles(context, target); |
| pd.dataManager.cleanTargetStorages(target); |
| configuration.save(context); |
| } |
| else if (!pd.fsState.isInitialScanPerformed(target)) { |
| initTargetFSState(context, target, false); |
| } |
| } |
| |
| private static void initTargetFSState(CompileContext context, BuildTarget<?> target, final boolean forceMarkDirty) throws IOException { |
| final ProjectDescriptor pd = context.getProjectDescriptor(); |
| final Timestamps timestamps = pd.timestamps.getStorage(); |
| final THashSet<File> currentFiles = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY); |
| FSOperations.markDirtyFiles(context, target, timestamps, forceMarkDirty, currentFiles, null); |
| |
| // handle deleted paths |
| final BuildFSState fsState = pd.fsState; |
| fsState.clearDeletedPaths(target); |
| final SourceToOutputMapping sourceToOutputMap = pd.dataManager.getSourceToOutputMap(target); |
| for (final Iterator<String> it = sourceToOutputMap.getSourcesIterator(); it.hasNext(); ) { |
| final String path = it.next(); |
| // can check if the file exists |
| final File file = new File(path); |
| if (!currentFiles.contains(file)) { |
| fsState.registerDeleted(target, file, timestamps); |
| } |
| } |
| pd.fsState.markInitialScanPerformed(target); |
| } |
| |
| public static void markTargetsUpToDate(CompileContext context, BuildTargetChunk chunk) throws IOException { |
| final ProjectDescriptor pd = context.getProjectDescriptor(); |
| final BuildFSState fsState = pd.fsState; |
| for (BuildTarget<?> target : chunk.getTargets()) { |
| pd.getTargetsState().getTargetConfiguration(target).storeNonexistentOutputRoots(context); |
| } |
| if (!Utils.errorsDetected(context) && !context.getCancelStatus().isCanceled()) { |
| boolean marked = dropRemovedPaths(context, chunk); |
| for (BuildTarget<?> target : chunk.getTargets()) { |
| if (target instanceof ModuleBuildTarget) { |
| context.clearNonIncrementalMark((ModuleBuildTarget)target); |
| } |
| final Timestamps timestamps = pd.timestamps.getStorage(); |
| for (BuildRootDescriptor rd : pd.getBuildRootIndex().getTargetRoots(target, context)) { |
| marked |= fsState.markAllUpToDate(context, rd, timestamps); |
| } |
| } |
| |
| if (marked) { |
| context.processMessage(DoneSomethingNotification.INSTANCE); |
| } |
| } |
| } |
| |
| private static boolean dropRemovedPaths(CompileContext context, BuildTargetChunk chunk) throws IOException { |
| final Map<BuildTarget<?>, Collection<String>> map = Utils.REMOVED_SOURCES_KEY.get(context); |
| boolean dropped = false; |
| if (map != null) { |
| for (BuildTarget<?> target : chunk.getTargets()) { |
| final Collection<String> paths = map.remove(target); |
| if (paths != null) { |
| final SourceToOutputMapping storage = context.getProjectDescriptor().dataManager.getSourceToOutputMap(target); |
| for (String path : paths) { |
| storage.remove(path); |
| dropped = true; |
| } |
| } |
| } |
| } |
| return dropped; |
| } |
| |
| public static <R extends BuildRootDescriptor, T extends BuildTarget<R>> |
| Map<T, Set<File>> cleanOutputsCorrespondingToChangedFiles(final CompileContext context, DirtyFilesHolder<R, T> dirtyFilesHolder) throws ProjectBuildException { |
| final BuildDataManager dataManager = context.getProjectDescriptor().dataManager; |
| try { |
| final Map<T, Set<File>> cleanedSources = new java.util.HashMap<T, Set<File>>(); |
| |
| final THashSet<File> dirsToDelete = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY); |
| final Collection<String> deletedPaths = new ArrayList<String>(); |
| |
| dirtyFilesHolder.processDirtyFiles(new FileProcessor<R, T>() { |
| private final Map<T, SourceToOutputMapping> mappingsCache = new java.util.HashMap<T, SourceToOutputMapping>(); // cache the mapping locally |
| private final TObjectIntHashMap<T> idsCache = new TObjectIntHashMap<T>(); |
| |
| @Override |
| public boolean apply(T target, File file, R sourceRoot) throws IOException { |
| SourceToOutputMapping srcToOut = mappingsCache.get(target); |
| if (srcToOut == null) { |
| srcToOut = dataManager.getSourceToOutputMap(target); |
| mappingsCache.put(target, srcToOut); |
| } |
| final int targetId; |
| if (!idsCache.containsKey(target)) { |
| targetId = dataManager.getTargetsState().getBuildTargetId(target); |
| idsCache.put(target, targetId); |
| } |
| else { |
| targetId = idsCache.get(target); |
| } |
| final String srcPath = file.getPath(); |
| final Collection<String> outputs = srcToOut.getOutputs(srcPath); |
| if (outputs != null) { |
| final boolean shouldPruneOutputDirs = target instanceof ModuleBasedTarget; |
| final List<String> deletedForThisSource = new ArrayList<String>(outputs.size()); |
| for (String output : outputs) { |
| deleteRecursively(output, deletedForThisSource, shouldPruneOutputDirs ? dirsToDelete : null); |
| } |
| deletedPaths.addAll(deletedForThisSource); |
| dataManager.getOutputToTargetRegistry().removeMapping(deletedForThisSource, targetId); |
| Set<File> cleaned = cleanedSources.get(target); |
| if (cleaned == null) { |
| cleaned = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY); |
| cleanedSources.put(target, cleaned); |
| } |
| cleaned.add(file); |
| } |
| return true; |
| } |
| |
| }); |
| |
| if (context.isMake()) { |
| final ProjectBuilderLogger logger = context.getLoggingManager().getProjectBuilderLogger(); |
| if (logger.isEnabled()) { |
| logger.logDeletedFiles(deletedPaths); |
| } |
| } |
| |
| if (!deletedPaths.isEmpty()) { |
| context.processMessage(new FileDeletedEvent(deletedPaths)); |
| } |
| // attempting to delete potentially empty directories |
| FSOperations.pruneEmptyDirs(context, dirsToDelete); |
| |
| return cleanedSources; |
| } |
| catch (Exception e) { |
| throw new ProjectBuildException(e); |
| } |
| } |
| |
| public static boolean deleteRecursively(@NotNull String path, @NotNull Collection<String> deletedPaths, @Nullable Set<File> parentDirs) { |
| File file = new File(path); |
| boolean deleted = deleteRecursively(file, deletedPaths); |
| if (deleted && parentDirs != null) { |
| File parent = file.getParentFile(); |
| if (parent != null) { |
| parentDirs.add(parent); |
| } |
| } |
| return deleted; |
| } |
| |
| private static boolean deleteRecursively(File file, Collection<String> deletedPaths) { |
| File[] children = file.listFiles(); |
| if (children != null) { |
| for (File child : children) { |
| deleteRecursively(child, deletedPaths); |
| } |
| } |
| boolean deleted = file.delete(); |
| if (deleted && children == null) { |
| deletedPaths.add(FileUtil.toSystemIndependentName(file.getPath())); |
| } |
| return deleted; |
| } |
| } |