Mark as 'deleted' all non-existent paths obtained from dependency caches. This will automatically clean stale non-existent paths from dependency data in situations when deleted paths info for build targets is lost or not available (fixes problem when old target caches are lost and non-existing files from dependency caches are sent to compilation: IDEA-185750)

This commit is contained in:
Eugene Zhuravlev
2018-02-04 17:25:03 +01:00
parent e21d6c984e
commit 204a4b5abd
20 changed files with 183 additions and 37 deletions
@@ -0,0 +1,15 @@
Compiling files:
moduleB/src/ppp/Impl1.java
End of files
Cleaning output files:
out/production/moduleA/ppp/Iface.class
End of files
Compiling files:
moduleA/src/ppp/Iface.java
End of files
Cleaning output files:
out/production/moduleB_renamed/ppp/Impl1.class
End of files
Compiling files:
moduleB/src/ppp/Impl1.java
End of files
@@ -0,0 +1,4 @@
package ppp;
public interface Iface {
}
@@ -0,0 +1,5 @@
package ppp;
public interface Iface {
void foo();
}
@@ -0,0 +1,4 @@
package ppp;
public class Impl1 implements Iface{
}
@@ -0,0 +1,5 @@
package ppp;
public class Impl1 implements Iface{
public void foo() {}
}
@@ -0,0 +1,4 @@
package ppp;
public class Impl2 implements Iface{
}
@@ -0,0 +1,4 @@
package ppp;
public class Impl2 implements Iface{
}
@@ -0,0 +1,10 @@
Compiling files:
moduleA/src/ppp/Iface.java
moduleA/src/ppp/Impl1.java
End of files
Cleaning output files:
out/production/moduleA_renamed/ppp/Impl1.class
End of files
Compiling files:
moduleA/src/ppp/Impl1.java
End of files
@@ -0,0 +1,4 @@
package ppp;
public interface Iface {
}
@@ -0,0 +1,5 @@
package ppp;
public interface Iface {
void foo();
}
@@ -0,0 +1,4 @@
package ppp;
public class Impl1 implements Iface{
}
@@ -0,0 +1,5 @@
package ppp;
public class Impl1 implements Iface{
public void foo() {}
}
@@ -0,0 +1,4 @@
package ppp;
public class Impl2 implements Iface{
}
@@ -0,0 +1,4 @@
package ppp;
public class Impl2 implements Iface{
}
@@ -272,7 +272,7 @@ public class JavaBuilderUtil {
FSOperations.markDirty(context, markDirtyRound, chunk, null);
}
}
additionalPassRequired = compilingIncrementally && chunkContainsAffectedFiles(context, chunk, newlyAffectedFiles);
additionalPassRequired = compilingIncrementally && moduleBasedFilter.containsFilesFromCurrentTargetChunk(newlyAffectedFiles);
}
}
else {
@@ -354,13 +354,11 @@ public class JavaBuilderUtil {
}
private static void removeFilesAcceptedByFilter(@NotNull Set<File> files, @Nullable FileFilter filter) {
if (filter == null) return;
Iterator<File> iterator = files.iterator();
while (iterator.hasNext()) {
File next = iterator.next();
if (filter.accept(next)) {
iterator.remove();
if (filter != null) {
for (final Iterator<File> it = files.iterator(); it.hasNext();) {
if (filter.accept(it.next())) {
it.remove();
}
}
}
}
@@ -376,38 +374,21 @@ public class JavaBuilderUtil {
return scope.isBuildIncrementally(JavaModuleBuildTargetType.PRODUCTION) || scope.isBuildIncrementally(JavaModuleBuildTargetType.TEST);
}
private static List<Pair<File, JpsModule>> checkAffectedFilesInCorrectModules(CompileContext context,
Collection<File> affected,
ModulesBasedFileFilter moduleBasedFilter) {
private static List<Pair<File, JpsModule>> checkAffectedFilesInCorrectModules(CompileContext context, Collection<File> affected, ModulesBasedFileFilter moduleBasedFilter) {
if (affected.isEmpty()) {
return Collections.emptyList();
}
final List<Pair<File, JpsModule>> result = new ArrayList<>();
final BuildRootIndex rootIndex = context.getProjectDescriptor().getBuildRootIndex();
for (File file : affected) {
if (!moduleBasedFilter.accept(file)) {
final JavaSourceRootDescriptor moduleAndRoot = context.getProjectDescriptor().getBuildRootIndex().findJavaRootDescriptor(context,
file);
final JavaSourceRootDescriptor moduleAndRoot = rootIndex.findJavaRootDescriptor(context, file);
result.add(Pair.create(file, moduleAndRoot != null ? moduleAndRoot.target.getModule() : null));
}
}
return result;
}
private static boolean chunkContainsAffectedFiles(CompileContext context, ModuleChunk chunk, final Set<File> affected)
throws IOException {
final Set<JpsModule> chunkModules = chunk.getModules();
if (!chunkModules.isEmpty()) {
for (File file : affected) {
final JavaSourceRootDescriptor moduleAndRoot = context.getProjectDescriptor().getBuildRootIndex().findJavaRootDescriptor(context,
file);
if (moduleAndRoot != null && chunkModules.contains(moduleAndRoot.target.getModule())) {
return true;
}
}
}
return false;
}
@NotNull
private static Set<File> getFilesContainer(CompileContext context, final Key<Set<File>> dataKey) {
Set<File> files = dataKey.get(context);
@@ -510,6 +491,15 @@ public class JavaBuilderUtil {
final JavaSourceRootDescriptor rd = myBuildRootIndex.findJavaRootDescriptor(myContext, file);
return rd != null && myChunkTargets.contains(rd.target);
}
public boolean containsFilesFromCurrentTargetChunk(Collection<File> files) {
for (File file : files) {
if (belongsToCurrentTargetChunk(file)) {
return true;
}
}
return false;
}
}
private static class NegationFileFilter implements FileFilter {
@@ -76,7 +76,6 @@ public class BuildOperations {
// 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();
@@ -188,8 +188,20 @@ public final class FilesDelta {
public boolean markRecompileIfNotDeleted(BuildRootDescriptor root, File file) {
lockData();
try {
final boolean isMarkedDeleted = !myDeletedPaths.isEmpty() && myDeletedPaths.contains(FileUtil.toCanonicalPath(file.getPath()));
String path = null;
final boolean isMarkedDeleted = !myDeletedPaths.isEmpty() && myDeletedPaths.contains(path = FileUtil.toCanonicalPath(file.getPath()));
if (!isMarkedDeleted) {
if (!file.exists()) {
// incorrect paths data recovery, so that the next make should not contain non-existing sources in 'recompile' list
if (path == null) {
path = FileUtil.toCanonicalPath(file.getPath());
}
if (Utils.IS_TEST_MODE) {
LOG.info("Marking deleted: " + path);
}
myDeletedPaths.add(path);
return false;
}
_addToRecompiled(root, file);
return true;
}
@@ -315,13 +315,13 @@ public class JavaBuilder extends ModuleLevelBuilder {
compiledOk = compileJava(context, chunk, files, classpath, platformCp, srcPath, diagnosticSink, outputSink, compilingTool, hasModules);
}
finally {
// heuristic: incorrect paths data recovery, so that the next make should not contain non-existing sources in 'recompile' list
filesWithErrors = diagnosticSink.getFilesWithErrors();
for (File file : filesWithErrors) {
if (!file.exists()) {
FSOperations.markDeleted(context, file);
}
}
// heuristic: incorrect paths data recovery, so that the next make should not contain non-existing sources in 'recompile' list
//for (File file : filesWithErrors) {
// if (!file.exists()) {
// FSOperations.markDeleted(context, file);
// }
//}
}
}
@@ -156,16 +156,34 @@ public abstract class IncrementalTestCase extends JpsBuildTestCase {
return addModule(moduleName, new String[]{srcPath}, null, null, getOrCreateJdk());
}
/**
* If the test changes project model between makes (e.g. module renamed), the descriptor should be re-created before each build session
* in order to ensure the project is loaded correctly. If project model is unchanged, teh project descriptor can be reused from the previous build session
* @return true if project descriptor from previous session can be reused, false otherwise
*/
protected boolean useCachedProjectDescriptorOnEachMake() {
return true;
}
protected BuildResult doTestBuild(int makesCount) {
StringBuilder log = new StringBuilder();
String rootPath = FileUtil.toSystemIndependentName(workDir.getAbsolutePath()) + "/";
final ProjectDescriptor pd = createProjectDescriptor(new BuildLoggingManager(new StringProjectBuilderLogger(rootPath, log)));
ProjectDescriptor pd = createProjectDescriptor(new BuildLoggingManager(new StringProjectBuilderLogger(rootPath, log)));
BuildResult result = null;
try {
final boolean reuseProjectId = useCachedProjectDescriptorOnEachMake();
doBuild(pd, CompileScopeTestBuilder.rebuild().allModules()).assertSuccessful();
for (int idx = 0; idx < makesCount; idx++) {
// this will save the current build data state before any changes were done to the project model
if (!reuseProjectId) {
pd.release();
}
modify(idx);
if (!reuseProjectId) {
pd = createProjectDescriptor(new BuildLoggingManager(new StringProjectBuilderLogger(rootPath, log)));
}
result = doBuild(pd, CompileScopeTestBuilder.make().allModules());
}
@@ -0,0 +1,50 @@
// Copyright 2000-2018 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.
package org.jetbrains.ether;
import org.jetbrains.jps.model.JpsModuleRootModificationUtil;
import org.jetbrains.jps.model.module.JpsModule;
public class RenameModuleTest extends IncrementalTestCase {
JpsModule myModuleToRename;
public RenameModuleTest() {
super("renameModule");
}
@Override
protected void tearDown() throws Exception {
myModuleToRename = null;
super.tearDown();
}
@Override
protected void modify(int stage) {
if (stage == 0) {
final JpsModule toRename = myModuleToRename;
if (toRename != null) {
myModuleToRename = null;
final String name = toRename.getName();
toRename.setName(name + "_renamed");
}
}
super.modify(stage);
}
@Override
protected boolean useCachedProjectDescriptorOnEachMake() {
return false;
}
public void testDeleteClassSameModule() {
myModuleToRename = addModule("moduleA", "moduleA/src");
doTestBuild(1).assertSuccessful();
}
public void testDeleteClassDependentModule() {
JpsModule moduleA = addModule("moduleA", "moduleA/src");
JpsModule moduleB = addModule("moduleB", "moduleB/src");
JpsModuleRootModificationUtil.addDependency(moduleB, moduleA);
myModuleToRename = moduleB;
doTestBuild(2).assertSuccessful();
}
}