f49ae3bd151140a56f63fdb3c52205a92d29642b
[idea/community.git] / jps / jps-builders / src / org / jetbrains / jps / incremental / BuildOperations.java
1 /*
2  * Copyright 2000-2016 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 org.jetbrains.jps.incremental;
17
18 import com.intellij.openapi.util.io.FileUtil;
19 import gnu.trove.THashSet;
20 import gnu.trove.TObjectIntHashMap;
21 import org.jetbrains.annotations.NotNull;
22 import org.jetbrains.annotations.Nullable;
23 import org.jetbrains.jps.builders.*;
24 import org.jetbrains.jps.builders.impl.BuildTargetChunk;
25 import org.jetbrains.jps.builders.java.JavaBuilderUtil;
26 import org.jetbrains.jps.builders.logging.ProjectBuilderLogger;
27 import org.jetbrains.jps.builders.storage.SourceToOutputMapping;
28 import org.jetbrains.jps.cmdline.ProjectDescriptor;
29 import org.jetbrains.jps.incremental.fs.BuildFSState;
30 import org.jetbrains.jps.incremental.fs.CompilationRound;
31 import org.jetbrains.jps.incremental.messages.DoneSomethingNotification;
32 import org.jetbrains.jps.incremental.messages.FileDeletedEvent;
33 import org.jetbrains.jps.incremental.storage.BuildDataManager;
34 import org.jetbrains.jps.incremental.storage.BuildTargetConfiguration;
35 import org.jetbrains.jps.incremental.storage.StampsStorage;
36
37 import java.io.File;
38 import java.io.IOException;
39 import java.nio.file.*;
40 import java.nio.file.attribute.BasicFileAttributes;
41 import java.util.*;
42
43 /**
44  * @author Eugene Zhuravlev
45  */
46 public class BuildOperations {
47   private BuildOperations() { }
48
49   public static void ensureFSStateInitialized(CompileContext context, BuildTarget<?> target, boolean readOnly) throws IOException {
50     final ProjectDescriptor pd = context.getProjectDescriptor();
51     final StampsStorage<? extends StampsStorage.Stamp> stampsStorage = pd.getProjectStamps().getStampStorage();
52     final BuildTargetConfiguration configuration = pd.getTargetsState().getTargetConfiguration(target);
53     if (JavaBuilderUtil.isForcedRecompilationAllJavaModules(context)) {
54       FSOperations.markDirtyFiles(context, target, CompilationRound.CURRENT, stampsStorage, true, null, null);
55       pd.fsState.markInitialScanPerformed(target);
56       if (!readOnly) {
57         configuration.save(context);
58       }
59     }
60     else {
61       boolean isTargetDirty = false;
62       if (context.getScope().isBuildForced(target) || (isTargetDirty = configuration.isTargetDirty(context.getProjectDescriptor())) || configuration.outputRootWasDeleted(context)) {
63         if (isTargetDirty) {
64           configuration.logDiagnostics(context);
65         }
66         initTargetFSState(context, target, true);
67         if (!readOnly) {
68           if (!context.getScope().isBuildForced(target)) {
69             // case when target build is forced, is handled separately
70             IncProjectBuilder.clearOutputFiles(context, target);
71           }
72           pd.dataManager.cleanTargetStorages(target);
73           configuration.save(context);
74         }
75       }
76       else if (!pd.fsState.isInitialScanPerformed(target)) {
77         initTargetFSState(context, target, false);
78       }
79     }
80   }
81
82   private static void initTargetFSState(CompileContext context, BuildTarget<?> target, final boolean forceMarkDirty) throws IOException {
83     final ProjectDescriptor pd = context.getProjectDescriptor();
84     final StampsStorage<? extends StampsStorage.Stamp> stampsStorage = pd.getProjectStamps().getStampStorage();
85     final THashSet<File> currentFiles = new THashSet<>(FileUtil.FILE_HASHING_STRATEGY);
86     FSOperations.markDirtyFiles(context, target, CompilationRound.CURRENT, stampsStorage, forceMarkDirty, currentFiles, null);
87
88     // handle deleted paths
89     final BuildFSState fsState = pd.fsState;
90     final SourceToOutputMapping sourceToOutputMap = pd.dataManager.getSourceToOutputMap(target);
91     for (final Iterator<String> it = sourceToOutputMap.getSourcesIterator(); it.hasNext(); ) {
92       final String path = it.next();
93       // can check if the file exists
94       final File file = new File(path);
95       if (!currentFiles.contains(file)) {
96         fsState.registerDeleted(context, target, file, stampsStorage);
97       }
98     }
99     pd.fsState.markInitialScanPerformed(target);
100   }
101
102   public static void markTargetsUpToDate(CompileContext context, BuildTargetChunk chunk) throws IOException {
103     final ProjectDescriptor pd = context.getProjectDescriptor();
104     final BuildFSState fsState = pd.fsState;
105     for (BuildTarget<?> target : chunk.getTargets()) {
106       pd.getTargetsState().getTargetConfiguration(target).storeNonexistentOutputRoots(context);
107     }
108     if (!Utils.errorsDetected(context) && !context.getCancelStatus().isCanceled()) {
109       boolean marked = dropRemovedPaths(context, chunk);
110       for (BuildTarget<?> target : chunk.getTargets()) {
111         if (target instanceof ModuleBuildTarget) {
112           context.clearNonIncrementalMark((ModuleBuildTarget)target);
113         }
114         final StampsStorage<? extends StampsStorage.Stamp>  stampsStorage = pd.getProjectStamps().getStampStorage();
115         for (BuildRootDescriptor rd : pd.getBuildRootIndex().getTargetRoots(target, context)) {
116           marked |= fsState.markAllUpToDate(context, rd, stampsStorage);
117         }
118       }
119
120       if (marked) {
121         context.processMessage(DoneSomethingNotification.INSTANCE);
122       }
123     }
124   }
125
126   private static boolean dropRemovedPaths(CompileContext context, BuildTargetChunk chunk) throws IOException {
127     final Map<BuildTarget<?>, Collection<String>> map = Utils.REMOVED_SOURCES_KEY.get(context);
128     boolean dropped = false;
129     if (map != null) {
130       for (BuildTarget<?> target : chunk.getTargets()) {
131         final Collection<String> paths = map.remove(target);
132         if (paths != null) {
133           final SourceToOutputMapping storage = context.getProjectDescriptor().dataManager.getSourceToOutputMap(target);
134           for (String path : paths) {
135             storage.remove(path);
136             dropped = true;
137           }
138         }
139       }
140     }
141     return dropped;
142   }
143
144   public static <R extends BuildRootDescriptor, T extends BuildTarget<R>>
145   Map<T, Set<File>> cleanOutputsCorrespondingToChangedFiles(final CompileContext context, DirtyFilesHolder<R, T> dirtyFilesHolder) throws ProjectBuildException {
146     final BuildDataManager dataManager = context.getProjectDescriptor().dataManager;
147     try {
148       final Map<T, Set<File>> cleanedSources = new HashMap<>();
149
150       final THashSet<File> dirsToDelete = new THashSet<>(FileUtil.FILE_HASHING_STRATEGY);
151       final Collection<String> deletedPaths = new ArrayList<>();
152
153       dirtyFilesHolder.processDirtyFiles(new FileProcessor<R, T>() {
154         private final Map<T, SourceToOutputMapping> mappingsCache = new HashMap<>(); // cache the mapping locally
155         private final TObjectIntHashMap<T> idsCache = new TObjectIntHashMap<>();
156
157         @Override
158         public boolean apply(T target, File file, R sourceRoot) throws IOException {
159           SourceToOutputMapping srcToOut = mappingsCache.get(target);
160           if (srcToOut == null) {
161             srcToOut = dataManager.getSourceToOutputMap(target);
162             mappingsCache.put(target, srcToOut);
163           }
164           final int targetId;
165           if (!idsCache.containsKey(target)) {
166             targetId = dataManager.getTargetsState().getBuildTargetId(target);
167             idsCache.put(target, targetId);
168           }
169           else {
170             targetId = idsCache.get(target);
171           }
172           final String srcPath = file.getPath();
173           final Collection<String> outputs = srcToOut.getOutputs(srcPath);
174           if (outputs != null) {
175             final boolean shouldPruneOutputDirs = target instanceof ModuleBasedTarget;
176             final List<String> deletedForThisSource = new ArrayList<>(outputs.size());
177             for (String output : outputs) {
178               deleteRecursively(output, deletedForThisSource, shouldPruneOutputDirs ? dirsToDelete : null);
179             }
180             deletedPaths.addAll(deletedForThisSource);
181             dataManager.getOutputToTargetRegistry().removeMapping(deletedForThisSource, targetId);
182             Set<File> cleaned = cleanedSources.get(target);
183             if (cleaned == null) {
184               cleaned = new THashSet<>(FileUtil.FILE_HASHING_STRATEGY);
185               cleanedSources.put(target, cleaned);
186             }
187             cleaned.add(file);
188           }
189           return true;
190         }
191
192       });
193
194       if (JavaBuilderUtil.isCompileJavaIncrementally(context)) {
195         final ProjectBuilderLogger logger = context.getLoggingManager().getProjectBuilderLogger();
196         if (logger.isEnabled()) {
197           logger.logDeletedFiles(deletedPaths);
198         }
199       }
200
201       if (!deletedPaths.isEmpty()) {
202         context.processMessage(new FileDeletedEvent(deletedPaths));
203       }
204       // attempting to delete potentially empty directories
205       FSOperations.pruneEmptyDirs(context, dirsToDelete);
206
207       return cleanedSources;
208     }
209     catch (Exception e) {
210       throw new ProjectBuildException(e);
211     }
212   }
213
214   public static boolean deleteRecursively(@NotNull String path, @NotNull Collection<? super String> deletedPaths, @Nullable Set<? super File> parentDirs) {
215     File file = new File(path);
216     boolean deleted = deleteRecursively(file, deletedPaths);
217     if (deleted && parentDirs != null) {
218       File parent = file.getParentFile();
219       if (parent != null) {
220         parentDirs.add(parent);
221       }
222     }
223     return deleted;
224   }
225
226   private static boolean deleteRecursively(final File file, final Collection<? super String> deletedPaths) {
227     try {
228       Files.walkFileTree(file.toPath(), EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
229         @Override
230         public FileVisitResult visitFile(Path f, BasicFileAttributes attrs) throws IOException {
231           try {
232             Files.delete(f);
233           }
234           catch (AccessDeniedException e) {
235             if (!f.toFile().delete()) { // fallback
236               throw e;
237             }
238           }
239           deletedPaths.add(FileUtil.toSystemIndependentName(f.toString()));
240           return FileVisitResult.CONTINUE;
241         }
242
243         @Override
244         public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
245           try {
246             Files.delete(dir);
247           }
248           catch (AccessDeniedException e) {
249             if (!dir.toFile().delete()) { // fallback
250               throw e;
251             }
252           }
253           return FileVisitResult.CONTINUE;
254         }
255
256       });
257       return true;
258     }
259     catch (IOException e) {
260       return false;
261     }
262   }
263 }