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