mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-10-01 19:02:13 +07:00
jps build: correctly allow skipping file from marking dirty (IDEA-192059)
Existing filters should be cleared when compilation round completes, not after executing builders of some category. Also test is added which checks JavaBuilderUtil.registerFilterToSkipMarkingAffectedFileDirty method via a mock builder.
This commit is contained in:
@@ -79,7 +79,8 @@ public class JavaBuilderUtil {
|
||||
|
||||
/**
|
||||
* The files accepted by {@code filter} won't be marked dirty by {@link #updateMappings} method when this compilation round finishes.
|
||||
* Use this method to register a filter accepting files of your language if you compute and mark as dirty affected files yourself in your builder.
|
||||
* Call this method from {@link ModuleLevelBuilder#build} to register a filter accepting files of your language if you compute and mark
|
||||
* as dirty affected files yourself.
|
||||
*/
|
||||
public static void registerFilterToSkipMarkingAffectedFileDirty(@NotNull CompileContext context, @NotNull FileFilter filter) {
|
||||
List<FileFilter> filters = SKIP_MARKING_DIRTY_FILTERS_KEY.get(context);
|
||||
@@ -119,10 +120,14 @@ public class JavaBuilderUtil {
|
||||
final Set<File> successfullyCompiled = getFilesContainer(context, SUCCESSFULLY_COMPILED_FILES_KEY);
|
||||
SUCCESSFULLY_COMPILED_FILES_KEY.set(context, null);
|
||||
FileFilter filter = createOrFilter(SKIP_MARKING_DIRTY_FILTERS_KEY.get(context));
|
||||
SKIP_MARKING_DIRTY_FILTERS_KEY.set(context, null);
|
||||
return updateMappings(context, delta, dirtyFilesHolder, chunk, compiledFiles, successfullyCompiled, CompilationRound.NEXT, filter);
|
||||
}
|
||||
|
||||
public static void clearDataOnRoundCompletion(CompileContext context) {
|
||||
//during next compilation round ModuleLevelBuilders may register filters again so we need to remove old ones to avoid duplicating instances
|
||||
SKIP_MARKING_DIRTY_FILTERS_KEY.set(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated this method isn't supposed to be called by plugins anymore, the mappings are updated
|
||||
* by the build process infrastructure automatically. Use {@link #getDependenciesRegistrar(CompileContext)},
|
||||
|
||||
@@ -1296,78 +1296,83 @@ public class IncProjectBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
BUILDER_CATEGORY_LOOP:
|
||||
for (BuilderCategory category : BuilderCategory.values()) {
|
||||
final List<ModuleLevelBuilder> builders = myBuilderRegistry.getBuilders(category);
|
||||
if (category == BuilderCategory.CLASS_POST_PROCESSOR) {
|
||||
// ensure changes from instrumenters are visible to class post-processors
|
||||
saveInstrumentedClasses(outputConsumer);
|
||||
}
|
||||
if (builders.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
BUILDER_CATEGORY_LOOP:
|
||||
for (BuilderCategory category : BuilderCategory.values()) {
|
||||
final List<ModuleLevelBuilder> builders = myBuilderRegistry.getBuilders(category);
|
||||
if (category == BuilderCategory.CLASS_POST_PROCESSOR) {
|
||||
// ensure changes from instrumenters are visible to class post-processors
|
||||
saveInstrumentedClasses(outputConsumer);
|
||||
}
|
||||
if (builders.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
for (ModuleLevelBuilder builder : builders) {
|
||||
processDeletedPaths(context, chunk.getTargets());
|
||||
long start = System.nanoTime();
|
||||
int processedSourcesBefore = outputConsumer.getNumberOfProcessedSources();
|
||||
final ModuleLevelBuilder.ExitCode buildResult = builder.build(context, chunk, dirtyFilesHolder, outputConsumer);
|
||||
storeBuilderStatistics(builder, System.nanoTime() - start,
|
||||
outputConsumer.getNumberOfProcessedSources() - processedSourcesBefore);
|
||||
|
||||
doneSomething |= (buildResult != ModuleLevelBuilder.ExitCode.NOTHING_DONE);
|
||||
|
||||
if (buildResult == ModuleLevelBuilder.ExitCode.ABORT) {
|
||||
throw new StopBuildException("Builder " + builder.getPresentableName() + " requested build stop");
|
||||
try {
|
||||
for (ModuleLevelBuilder builder : builders) {
|
||||
processDeletedPaths(context, chunk.getTargets());
|
||||
long start = System.nanoTime();
|
||||
int processedSourcesBefore = outputConsumer.getNumberOfProcessedSources();
|
||||
final ModuleLevelBuilder.ExitCode buildResult = builder.build(context, chunk, dirtyFilesHolder, outputConsumer);
|
||||
storeBuilderStatistics(builder, System.nanoTime() - start,
|
||||
outputConsumer.getNumberOfProcessedSources() - processedSourcesBefore);
|
||||
|
||||
doneSomething |= (buildResult != ModuleLevelBuilder.ExitCode.NOTHING_DONE);
|
||||
|
||||
if (buildResult == ModuleLevelBuilder.ExitCode.ABORT) {
|
||||
throw new StopBuildException("Builder " + builder.getPresentableName() + " requested build stop");
|
||||
}
|
||||
context.checkCanceled();
|
||||
if (buildResult == ModuleLevelBuilder.ExitCode.ADDITIONAL_PASS_REQUIRED) {
|
||||
nextPassRequired = true;
|
||||
}
|
||||
else if (buildResult == ModuleLevelBuilder.ExitCode.CHUNK_REBUILD_REQUIRED) {
|
||||
if (!rebuildFromScratchRequested && !JavaBuilderUtil.isForcedRecompilationAllJavaModules(context)) {
|
||||
notifyChunkRebuildRequested(context, chunk, builder);
|
||||
// allow rebuild from scratch only once per chunk
|
||||
rebuildFromScratchRequested = true;
|
||||
try {
|
||||
// forcibly mark all files in the chunk dirty
|
||||
context.getProjectDescriptor().fsState.clearContextRoundData(context);
|
||||
FSOperations.markDirty(context, CompilationRound.NEXT, chunk, null);
|
||||
// reverting to the beginning
|
||||
myTargetsProcessed -= (buildersPassed * modulesInChunk) / stageCount;
|
||||
stageCount = myTotalModuleLevelBuilderCount;
|
||||
buildersPassed = 0;
|
||||
nextPassRequired = true;
|
||||
outputConsumer.clear();
|
||||
break BUILDER_CATEGORY_LOOP;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new ProjectBuildException(e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG.debug("Builder " + builder.getPresentableName() + " requested second chunk rebuild");
|
||||
}
|
||||
}
|
||||
|
||||
buildersPassed++;
|
||||
updateDoneFraction(context, modulesInChunk / (stageCount));
|
||||
}
|
||||
context.checkCanceled();
|
||||
if (buildResult == ModuleLevelBuilder.ExitCode.ADDITIONAL_PASS_REQUIRED) {
|
||||
}
|
||||
finally {
|
||||
final boolean moreToCompile = JavaBuilderUtil.updateMappingsOnRoundCompletion(context, dirtyFilesHolder, chunk);
|
||||
if (moreToCompile) {
|
||||
nextPassRequired = true;
|
||||
}
|
||||
else if (buildResult == ModuleLevelBuilder.ExitCode.CHUNK_REBUILD_REQUIRED) {
|
||||
if (!rebuildFromScratchRequested && !JavaBuilderUtil.isForcedRecompilationAllJavaModules(context)) {
|
||||
notifyChunkRebuildRequested(context, chunk, builder);
|
||||
// allow rebuild from scratch only once per chunk
|
||||
rebuildFromScratchRequested = true;
|
||||
try {
|
||||
// forcibly mark all files in the chunk dirty
|
||||
context.getProjectDescriptor().fsState.clearContextRoundData(context);
|
||||
FSOperations.markDirty(context, CompilationRound.NEXT, chunk, null);
|
||||
// reverting to the beginning
|
||||
myTargetsProcessed -= (buildersPassed * modulesInChunk) / stageCount;
|
||||
stageCount = myTotalModuleLevelBuilderCount;
|
||||
buildersPassed = 0;
|
||||
nextPassRequired = true;
|
||||
outputConsumer.clear();
|
||||
break BUILDER_CATEGORY_LOOP;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new ProjectBuildException(e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG.debug("Builder " + builder.getPresentableName() + " requested second chunk rebuild");
|
||||
}
|
||||
if (nextPassRequired && !rebuildFromScratchRequested) {
|
||||
// recalculate basis
|
||||
myTargetsProcessed -= (buildersPassed * modulesInChunk) / stageCount;
|
||||
stageCount += myTotalModuleLevelBuilderCount;
|
||||
myTargetsProcessed += (buildersPassed * modulesInChunk) / stageCount;
|
||||
}
|
||||
|
||||
buildersPassed++;
|
||||
updateDoneFraction(context, modulesInChunk / (stageCount));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
final boolean moreToCompile = JavaBuilderUtil.updateMappingsOnRoundCompletion(context, dirtyFilesHolder, chunk);
|
||||
if (moreToCompile) {
|
||||
nextPassRequired = true;
|
||||
}
|
||||
if (nextPassRequired && !rebuildFromScratchRequested) {
|
||||
// recalculate basis
|
||||
myTargetsProcessed -= (buildersPassed * modulesInChunk) / stageCount;
|
||||
stageCount += myTotalModuleLevelBuilderCount;
|
||||
myTargetsProcessed += (buildersPassed * modulesInChunk) / stageCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
JavaBuilderUtil.clearDataOnRoundCompletion(context);
|
||||
}
|
||||
}
|
||||
while (nextPassRequired);
|
||||
}
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
Cleaning output files:
|
||||
out/production/m/A.class
|
||||
End of files
|
||||
Compiling files:
|
||||
src/A.java
|
||||
End of files
|
||||
Cleaning output files:
|
||||
out/production/m/C.class
|
||||
End of files
|
||||
Compiling files:
|
||||
src/C.java
|
||||
End of files
|
||||
+1
-1
@@ -1 +1 @@
|
||||
org.jetbrains.jps.builders.java.MockPackageFacadeBuilderService
|
||||
org.jetbrains.jps.builders.java.TestBuildersService
|
||||
+4
-39
@@ -15,17 +15,13 @@
|
||||
*/
|
||||
package org.jetbrains.jps.builders.java
|
||||
|
||||
import org.jetbrains.jps.builders.JpsBuildTestCase
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.application.ex.PathManagerEx
|
||||
import java.io.File
|
||||
import java.util.ArrayList
|
||||
import org.jetbrains.jps.builders.BuildResult
|
||||
|
||||
/**
|
||||
* @author nik
|
||||
*/
|
||||
class ClassesWithMultipleSourcesTest: JpsBuildTestCase() {
|
||||
class ClassesWithMultipleSourcesTest: IncrementalBuildTestCase() {
|
||||
override val testDataDirectoryName: String
|
||||
get() = "multipleSources"
|
||||
|
||||
fun testAddFile() {
|
||||
doTest {
|
||||
createFile("src/a.p")
|
||||
@@ -81,35 +77,4 @@ class ClassesWithMultipleSourcesTest: JpsBuildTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun doTest(actions: BuildTestActions.() -> Unit) {
|
||||
addModule("m", createDir("src"))
|
||||
val testActions = BuildTestActions()
|
||||
testActions.actions()
|
||||
rebuildAllModules()
|
||||
var result: BuildResult? = null
|
||||
testActions.modifyActions.forEach { action ->
|
||||
action()
|
||||
result = buildAllModules()
|
||||
result!!.assertSuccessful()
|
||||
}
|
||||
checkLog()
|
||||
checkMappingsAreSameAfterRebuild(result)
|
||||
}
|
||||
|
||||
override fun getTestDataRootPath(): String {
|
||||
return FileUtil.toCanonicalPath(PathManagerEx.findFileUnderCommunityHome("jps/jps-builders/testData/incremental/multipleSources").absolutePath, '/')
|
||||
}
|
||||
|
||||
private fun checkLog() {
|
||||
val testName = getTestName(true)
|
||||
checkFullLog(File(testDataRootPath, "$testName.log"))
|
||||
}
|
||||
|
||||
private class BuildTestActions {
|
||||
val modifyActions = ArrayList<() -> Unit>()
|
||||
|
||||
fun modify(action: () -> Unit) {
|
||||
modifyActions.add(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// 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.jps.builders.java
|
||||
|
||||
import com.intellij.openapi.application.ex.PathManagerEx
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.jps.builders.BuildResult
|
||||
import org.jetbrains.jps.builders.JpsBuildTestCase
|
||||
import java.io.File
|
||||
|
||||
abstract class IncrementalBuildTestCase : JpsBuildTestCase() {
|
||||
protected fun doTest(checkMappingAfterRebuild: Boolean = true,
|
||||
actions: BuildTestActions.() -> Unit) {
|
||||
addModule("m", createDir("src"))
|
||||
val testActions = BuildTestActions()
|
||||
testActions.actions()
|
||||
rebuildAllModules()
|
||||
var result: BuildResult? = null
|
||||
testActions.modifyActions.forEach { action ->
|
||||
action()
|
||||
result = buildAllModules()
|
||||
result!!.assertSuccessful()
|
||||
}
|
||||
checkLog()
|
||||
if (checkMappingAfterRebuild) {
|
||||
checkMappingsAreSameAfterRebuild(result)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract val testDataDirectoryName: String
|
||||
|
||||
override fun getTestDataRootPath(): String {
|
||||
return FileUtil.toCanonicalPath(
|
||||
PathManagerEx.findFileUnderCommunityHome("jps/jps-builders/testData/incremental/$testDataDirectoryName").absolutePath, '/')
|
||||
}
|
||||
|
||||
private fun checkLog() {
|
||||
checkFullLog(File(testDataRootPath, "${getTestName(true)}.log"))
|
||||
}
|
||||
|
||||
protected class BuildTestActions {
|
||||
val modifyActions = ArrayList<() -> Unit>()
|
||||
|
||||
fun modify(action: () -> Unit) {
|
||||
modifyActions.add(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// 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.jps.builders.java
|
||||
|
||||
class SkipMarkingAffectedFilesDirtyFilterTest : IncrementalBuildTestCase() {
|
||||
override val testDataDirectoryName: String
|
||||
get() = "markingAffectedFilesDirtyFilter"
|
||||
|
||||
fun testDoNotMarkDirty() {
|
||||
doTest(checkMappingAfterRebuild = false) {
|
||||
createFile("src/A.java", "class A { void foo() {} }")
|
||||
createFile("src/B$DO_NOT_MARK_DIRTY_SUFFIX.java", "class B$DO_NOT_MARK_DIRTY_SUFFIX { { new A().foo(); } }")
|
||||
createFile("src/C.java", "class C { { new A().foo(); } }")
|
||||
modify {
|
||||
changeFile("src/A.java", "class A { int foo() {return 0;} }")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// 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.jps.builders.java
|
||||
|
||||
import org.jetbrains.jps.ModuleChunk
|
||||
import org.jetbrains.jps.builders.DirtyFilesHolder
|
||||
import org.jetbrains.jps.incremental.BuilderCategory
|
||||
import org.jetbrains.jps.incremental.CompileContext
|
||||
import org.jetbrains.jps.incremental.ModuleBuildTarget
|
||||
import org.jetbrains.jps.incremental.ModuleLevelBuilder
|
||||
|
||||
internal val DO_NOT_MARK_DIRTY_SUFFIX = "___DoNotMarkDirty"
|
||||
|
||||
class SkipMarkingSomeFilesAsDirtyBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
override fun getPresentableName() = "Skip marking some files as dirty"
|
||||
|
||||
override fun build(context: CompileContext,
|
||||
chunk: ModuleChunk,
|
||||
dirtyFilesHolder: DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget>,
|
||||
outputConsumer: OutputConsumer): ExitCode {
|
||||
//need to call this to set JavaBuilderUtil.MAPPINGS_DELTA_KEY key otherwise JavaBuilderUtil.updateMappingsOnRoundCompletion won't to anything
|
||||
JavaBuilderUtil.getDependenciesRegistrar(context)
|
||||
|
||||
JavaBuilderUtil.registerFilterToSkipMarkingAffectedFileDirty(context, { it.nameWithoutExtension.endsWith(DO_NOT_MARK_DIRTY_SUFFIX)})
|
||||
return ExitCode.OK
|
||||
}
|
||||
}
|
||||
+2
-5
@@ -16,13 +16,10 @@
|
||||
package org.jetbrains.jps.builders.java
|
||||
|
||||
import org.jetbrains.jps.incremental.BuilderService
|
||||
import org.jetbrains.jps.incremental.ModuleLevelBuilder
|
||||
|
||||
import java.util.Collections
|
||||
|
||||
/**
|
||||
* @author nik
|
||||
*/
|
||||
class MockPackageFacadeBuilderService : BuilderService() {
|
||||
override fun createModuleLevelBuilders() = listOf(MockPackageFacadeGenerator())
|
||||
class TestBuildersService : BuilderService() {
|
||||
override fun createModuleLevelBuilders() = listOf(MockPackageFacadeGenerator(), SkipMarkingSomeFilesAsDirtyBuilder())
|
||||
}
|
||||
Reference in New Issue
Block a user