diff --git a/java/java-tests/testData/compileServer/incremental/common/moveClassToDependentModuleWithSameOutput.log b/java/java-tests/testData/compileServer/incremental/common/moveClassToDependentModuleWithSameOutput.log new file mode 100644 index 000000000000..1f87c341e730 --- /dev/null +++ b/java/java-tests/testData/compileServer/incremental/common/moveClassToDependentModuleWithSameOutput.log @@ -0,0 +1,15 @@ +Compiling files: +moduleA/src/com/ppp/Inner.java +End of files +Cleaning output files: +out/com/ppp/B.class +End of files +Compiling files: +moduleB/src/com/ppp/B.java +End of files +Cleaning output files: +out/com/ppp/B.class +End of files +Compiling files: +moduleB/src/com/ppp/B.java +End of files diff --git a/java/java-tests/testData/compileServer/incremental/common/moveClassToDependentModuleWithSameOutput/moduleA/src/com/ppp/Inner.java.new b/java/java-tests/testData/compileServer/incremental/common/moveClassToDependentModuleWithSameOutput/moduleA/src/com/ppp/Inner.java.new new file mode 100644 index 000000000000..b27489774bed --- /dev/null +++ b/java/java-tests/testData/compileServer/incremental/common/moveClassToDependentModuleWithSameOutput/moduleA/src/com/ppp/Inner.java.new @@ -0,0 +1,4 @@ +package com.ppp; + +class Inner { +} \ No newline at end of file diff --git a/java/java-tests/testData/compileServer/incremental/common/moveClassToDependentModuleWithSameOutput/moduleB/src/com/ppp/B.java b/java/java-tests/testData/compileServer/incremental/common/moveClassToDependentModuleWithSameOutput/moduleB/src/com/ppp/B.java new file mode 100644 index 000000000000..da7cb3fc9b26 --- /dev/null +++ b/java/java-tests/testData/compileServer/incremental/common/moveClassToDependentModuleWithSameOutput/moduleB/src/com/ppp/B.java @@ -0,0 +1,6 @@ +package com.ppp; + +public class B { + private Inner inner; +} + diff --git a/java/java-tests/testData/compileServer/incremental/common/moveClassToDependentModuleWithSameOutput/moduleB/src/com/ppp/Inner.java b/java/java-tests/testData/compileServer/incremental/common/moveClassToDependentModuleWithSameOutput/moduleB/src/com/ppp/Inner.java new file mode 100644 index 000000000000..5ffa5766c98e --- /dev/null +++ b/java/java-tests/testData/compileServer/incremental/common/moveClassToDependentModuleWithSameOutput/moduleB/src/com/ppp/Inner.java @@ -0,0 +1,5 @@ +package com.ppp; + +class Inner { +} + diff --git a/java/java-tests/testData/compileServer/incremental/common/moveClassToDependentModuleWithSameOutput/moduleB/src/com/ppp/Inner.java.remove b/java/java-tests/testData/compileServer/incremental/common/moveClassToDependentModuleWithSameOutput/moduleB/src/com/ppp/Inner.java.remove new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java index 7cf0a804f7c9..bf27ca27c25c 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java @@ -52,6 +52,7 @@ import org.jetbrains.jps.incremental.java.ExternalJavacDescriptor; import org.jetbrains.jps.incremental.messages.*; import org.jetbrains.jps.incremental.storage.BuildTargetConfiguration; import org.jetbrains.jps.incremental.storage.OneToManyPathsMapping; +import org.jetbrains.jps.incremental.storage.OutputToSourceRegistry; import org.jetbrains.jps.indices.ModuleExcludeIndex; import org.jetbrains.jps.model.java.JpsJavaExtensionService; import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerConfiguration; @@ -987,7 +988,8 @@ public class IncProjectBuilder { final Collection outputs = sourceToOutputStorage.getOutputs(deletedSource); if (outputs != null && !outputs.isEmpty()) { List deletedOutputPaths = new ArrayList(); - for (String output : outputs) { + final OutputToSourceRegistry outputToSourceRegistry = context.getProjectDescriptor().dataManager.getOutputToSourceRegistry(); + for (String output : outputToSourceRegistry.getSafeToDeleteOutputs(outputs, deletedSource)) { final boolean deleted = BuildOperations.deleteRecursively(output, deletedOutputPaths, shouldPruneEmptyDirs ? dirsToDelete : null); if (deleted) { doneSomething = true; diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/BuildDataManager.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/BuildDataManager.java index 846f86639e09..47d6a7c13800 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/BuildDataManager.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/BuildDataManager.java @@ -20,11 +20,11 @@ import com.intellij.openapi.util.AtomicNotNullLazyValue; import com.intellij.openapi.util.io.FileUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jps.builders.storage.BuildDataCorruptedException; import org.jetbrains.jps.builders.BuildTarget; import org.jetbrains.jps.builders.impl.BuildTargetChunk; import org.jetbrains.jps.builders.impl.storage.BuildTargetStorages; import org.jetbrains.jps.builders.java.dependencyView.Mappings; +import org.jetbrains.jps.builders.storage.BuildDataCorruptedException; import org.jetbrains.jps.builders.storage.BuildDataPaths; import org.jetbrains.jps.builders.storage.SourceToOutputMapping; import org.jetbrains.jps.builders.storage.StorageProvider; @@ -42,9 +42,10 @@ import java.util.concurrent.ConcurrentMap; * Date: 10/7/11 */ public class BuildDataManager implements StorageOwner { - private static final int VERSION = 22; + private static final int VERSION = 23; private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.storage.BuildDataManager"); private static final String SRC_TO_FORM_STORAGE = "src-form"; + private static final String OUT_SRC_STORAGE = "out-src"; private static final String MAPPINGS_STORAGE = "mappings"; private static final int CONCURRENCY_LEVEL = BuildRunner.PARALLEL_BUILD_ENABLED? IncProjectBuilder.MAX_BUILDER_THREADS : 1; @@ -57,6 +58,7 @@ public class BuildDataManager implements StorageOwner { private final Mappings myMappings; private final BuildDataPaths myDataPaths; private final BuildTargetsState myTargetsState; + private final OutputToSourceRegistry myOutputToSourceRegistry; private final File myVersionFile; private StorageOwner myTargetStoragesOwner = new CompositeStorageOwner() { @Override @@ -126,12 +128,18 @@ public class BuildDataManager implements StorageOwner { myDataPaths = dataPaths; myTargetsState = targetsState; mySrcToFormMap = new OneToManyPathsMapping(new File(getSourceToFormsRoot(), "data")); + myOutputToSourceRegistry = new OutputToSourceRegistry(new File(getOutputToSourceRegistryRoot(), "data")); myMappings = new Mappings(getMappingsRoot(myDataPaths.getDataStorageRoot()), useMemoryTempCaches); myVersionFile = new File(myDataPaths.getDataStorageRoot(), "version.dat"); } + public OutputToSourceRegistry getOutputToSourceRegistry() { + return myOutputToSourceRegistry; + } + public SourceToOutputMapping getSourceToOutputMap(final BuildTarget target) throws IOException { - return fetchValue(mySourceToOutputs, target, SOURCE_OUTPUT_MAPPING_VALUE_FACTORY); + final SourceToOutputMappingImpl sourceToOutputMapping = fetchValue(mySourceToOutputs, target, SOURCE_OUTPUT_MAPPING_VALUE_FACTORY); + return new SourceToOutputMappingWrapper(sourceToOutputMapping); } @NotNull @@ -183,14 +191,20 @@ public class BuildDataManager implements StorageOwner { wipeStorage(getSourceToFormsRoot(), mySrcToFormMap); } finally { - final Mappings mappings = myMappings; - if (mappings != null) { - synchronized (mappings) { - mappings.clean(); - } + try { + wipeStorage(getOutputToSourceRegistryRoot(), myOutputToSourceRegistry); } - else { - FileUtil.delete(getMappingsRoot(myDataPaths.getDataStorageRoot())); + finally { + final Mappings mappings = myMappings; + if (mappings != null) { + synchronized (mappings) { + mappings.clean(); + } + } + else { + FileUtil.delete(getMappingsRoot(myDataPaths.getDataStorageRoot())); + } + } } } @@ -204,6 +218,7 @@ public class BuildDataManager implements StorageOwner { for (AtomicNotNullLazyValue mapping : mySourceToOutputs.values()) { mapping.getValue().flush(memoryCachesOnly); } + myOutputToSourceRegistry.flush(memoryCachesOnly); mySrcToFormMap.flush(memoryCachesOnly); final Mappings mappings = myMappings; if (mappings != null) { @@ -226,6 +241,7 @@ public class BuildDataManager implements StorageOwner { finally { try { closeSourceToOutputStorages(); + myOutputToSourceRegistry.close(); } finally { try { @@ -304,6 +320,10 @@ public class BuildDataManager implements StorageOwner { return new File(myDataPaths.getDataStorageRoot(), SRC_TO_FORM_STORAGE); } + private File getOutputToSourceRegistryRoot() { + return new File(myDataPaths.getDataStorageRoot(), OUT_SRC_STORAGE); + } + public BuildDataPaths getDataPaths() { return myDataPaths; } @@ -376,4 +396,78 @@ public class BuildDataManager implements StorageOwner { } } } + + private final class SourceToOutputMappingWrapper implements SourceToOutputMapping { + private final SourceToOutputMapping myDelegate; + + SourceToOutputMappingWrapper(SourceToOutputMapping delegate) { + myDelegate = delegate; + } + + public void setOutputs(@NotNull String srcPath, @NotNull Collection outputs) throws IOException { + try { + myDelegate.setOutputs(srcPath, outputs); + } + finally { + myOutputToSourceRegistry.addMapping(outputs, srcPath); + } + } + + public void setOutput(@NotNull String srcPath, @NotNull String outputPath) throws IOException { + try { + myDelegate.setOutput(srcPath, outputPath); + } + finally { + myOutputToSourceRegistry.addMapping(outputPath, srcPath); + } + } + + public void appendOutput(@NotNull String srcPath, @NotNull String outputPath) throws IOException { + try { + myDelegate.appendOutput(srcPath, outputPath); + } + finally { + myOutputToSourceRegistry.addMapping(outputPath, srcPath); + } + } + + public void remove(@NotNull String srcPath) throws IOException { + final Collection outputs = myDelegate.getOutputs(srcPath); + if (outputs == null) { + return; + } + try { + myDelegate.remove(srcPath); + } + finally { + for (String output : outputs) { + myOutputToSourceRegistry.removeMapping(output, srcPath); + } + } + } + + public void removeOutput(@NotNull String sourcePath, @NotNull String outputPath) throws IOException { + try { + myDelegate.removeOutput(sourcePath, outputPath); + } + finally { + myOutputToSourceRegistry.removeMapping(outputPath, sourcePath); + } + } + + @NotNull + public Collection getSources() throws IOException { + return myDelegate.getSources(); + } + + @Nullable + public Collection getOutputs(@NotNull String srcPath) throws IOException { + return myDelegate.getOutputs(srcPath); + } + + @NotNull + public Iterator getSourcesIterator() throws IOException { + return myDelegate.getSourcesIterator(); + } + } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/OutputToSourceRegistry.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/OutputToSourceRegistry.java new file mode 100644 index 000000000000..a4568d8c913d --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/OutputToSourceRegistry.java @@ -0,0 +1,127 @@ +/* + * Copyright 2000-2014 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.storage; + +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.IntInlineKeyDescriptor; +import gnu.trove.TIntHashSet; +import gnu.trove.TIntProcedure; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.io.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +/** + * @author Eugene Zhuravlev + * Date: 10-Apr-14 + */ +public class OutputToSourceRegistry extends AbstractStateStorage{ + private static final DataExternalizer DATA_EXTERNALIZER = new DataExternalizer() { + public void save(@NotNull final DataOutput out, TIntHashSet value) throws IOException { + final Ref exRef = Ref.create(null); + value.forEach(new TIntProcedure() { + public boolean execute(int value) { + try { + out.writeInt(value); + } + catch (IOException e) { + exRef.set(e); + return false; + } + return true; + } + }); + final IOException error = exRef.get(); + if (error != null) { + throw error; + } + } + + public TIntHashSet read(@NotNull DataInput in) throws IOException { + final TIntHashSet result = new TIntHashSet(); + final DataInputStream stream = (DataInputStream)in; + while (stream.available() > 0) { + result.add(in.readInt()); + } + return result; + } + }; + + OutputToSourceRegistry(@NonNls File storePath) throws IOException { + super(storePath, new IntInlineKeyDescriptor(), DATA_EXTERNALIZER); + } + + protected void addMapping(String outputPath, String sourcePath) throws IOException { + addMapping(Collections.singleton(outputPath), sourcePath); + } + + protected void addMapping(Collection outputPaths, String sourcePath) throws IOException { + final TIntHashSet set = new TIntHashSet(); + set.add(FileUtil.pathHashCode(sourcePath)); + for (String outputPath : outputPaths) { + appendData(FileUtil.pathHashCode(outputPath), set); + } + } + + protected boolean removeMapping(String outputPath, String sourcePath) throws IOException { + final int key = FileUtil.pathHashCode(outputPath); + synchronized (myDataLock) { + final TIntHashSet state = getState(key); + if (state != null) { + final int value = FileUtil.pathHashCode(sourcePath); + final boolean removed = state.remove(value); + if (state.isEmpty()) { + remove(key); + } + else { + if (removed) { + update(key, state); + } + } + return removed; + } + } + return false; + } + + public Collection getSafeToDeleteOutputs(Collection outputPaths, String associatedSourcePath) throws IOException { + final int size = outputPaths.size(); + if (size == 0) { + return outputPaths; + } + final Collection result = new ArrayList(size); + Integer cached = null; + for (String outputPath : outputPaths) { + final int key = FileUtil.pathHashCode(outputPath); + synchronized (myDataLock) { + final TIntHashSet associatedSources = getState(key); + if (associatedSources == null || associatedSources.size() != 1) { + continue; + } + final int srcHash = cached == null? (cached = FileUtil.pathHashCode(associatedSourcePath)) : cached.intValue(); + if (associatedSources.contains(srcHash)) { + result.add(outputPath); + } + } + } + return result; + } +} diff --git a/jps/jps-builders/testSrc/org/jetbrains/ether/CommonTest.java b/jps/jps-builders/testSrc/org/jetbrains/ether/CommonTest.java index ee72b356dc62..5373924c84b3 100644 --- a/jps/jps-builders/testSrc/org/jetbrains/ether/CommonTest.java +++ b/jps/jps-builders/testSrc/org/jetbrains/ether/CommonTest.java @@ -15,7 +15,9 @@ */ package org.jetbrains.ether; +import org.jetbrains.jps.model.JpsDummyElement; import org.jetbrains.jps.model.JpsModuleRootModificationUtil; +import org.jetbrains.jps.model.library.sdk.JpsSdk; import org.jetbrains.jps.model.module.JpsModule; /** @@ -132,6 +134,15 @@ public class CommonTest extends IncrementalTestCase { doTestBuild(1).assertSuccessful(); } + public void testMoveClassToDependentModuleWithSameOutput() throws Exception { + final JpsSdk sdk = getOrCreateJdk(); + final String commonOutput = getAbsolutePath("out"); + JpsModule moduleA = addModule("moduleA", new String[]{getAbsolutePath("moduleA/src")}, commonOutput, commonOutput, sdk); + JpsModule moduleB = addModule("moduleB", new String[]{getAbsolutePath("moduleB/src")}, commonOutput, commonOutput, sdk); + JpsModuleRootModificationUtil.addDependency(moduleB, moduleA); + doTestBuild(1).assertSuccessful(); + } + public void testMoveClassFromJavaFileToDependentModule() throws Exception { JpsModule moduleA = addModule("moduleA", "moduleA/src"); JpsModule moduleB = addModule("moduleB", "moduleB/src"); diff --git a/jps/jps-builders/testSrc/org/jetbrains/ether/IncrementalTestCase.java b/jps/jps-builders/testSrc/org/jetbrains/ether/IncrementalTestCase.java index 3fec8d342cbb..24d3deddbd32 100644 --- a/jps/jps-builders/testSrc/org/jetbrains/ether/IncrementalTestCase.java +++ b/jps/jps-builders/testSrc/org/jetbrains/ether/IncrementalTestCase.java @@ -23,9 +23,9 @@ import com.intellij.util.Processor; import org.jetbrains.jps.builders.BuildResult; import org.jetbrains.jps.builders.CompileScopeTestBuilder; import org.jetbrains.jps.builders.JpsBuildTestCase; -import org.jetbrains.jps.cmdline.ProjectDescriptor; -import org.jetbrains.jps.builders.logging.BuildLoggingManager; import org.jetbrains.jps.builders.impl.logging.ProjectBuilderLoggerBase; +import org.jetbrains.jps.builders.logging.BuildLoggingManager; +import org.jetbrains.jps.cmdline.ProjectDescriptor; import org.jetbrains.jps.model.JpsDummyElement; import org.jetbrains.jps.model.java.JavaSourceRootType; import org.jetbrains.jps.model.java.JpsJavaExtensionService; @@ -231,7 +231,7 @@ public abstract class IncrementalTestCase extends JpsBuildTestCase { } } - private JpsSdk getOrCreateJdk() { + protected JpsSdk getOrCreateJdk() { if (myJdk == null) { myJdk = addJdk("IDEA jdk"); }