experimental compact storage for JPS Cache (part 5 - store OutputToTargetMapping)

GitOrigin-RevId: 36feec030cee2cbd5554a4fc0a3b80dd74ea764c
This commit is contained in:
Vladimir Krivosheev
2024-09-17 08:16:38 +02:00
committed by intellij-monorepo-bot
parent 3b63b96418
commit db3b4f7162
26 changed files with 778 additions and 427 deletions

View File

@@ -1883,7 +1883,7 @@ f:org.jetbrains.jps.cmdline.PreloadedData
f:org.jetbrains.jps.cmdline.ProjectDescriptor f:org.jetbrains.jps.cmdline.ProjectDescriptor
- f:dataManager:org.jetbrains.jps.incremental.storage.BuildDataManager - f:dataManager:org.jetbrains.jps.incremental.storage.BuildDataManager
- f:fsState:org.jetbrains.jps.incremental.fs.BuildFSState - f:fsState:org.jetbrains.jps.incremental.fs.BuildFSState
- <init>(org.jetbrains.jps.model.JpsModel,org.jetbrains.jps.incremental.fs.BuildFSState,org.jetbrains.jps.incremental.storage.BuildDataManager,org.jetbrains.jps.builders.logging.BuildLoggingManager,org.jetbrains.jps.indices.ModuleExcludeIndex,org.jetbrains.jps.builders.BuildTargetIndex,org.jetbrains.jps.builders.BuildRootIndex,org.jetbrains.jps.indices.IgnoredFileIndex):V - <init>(org.jetbrains.jps.model.JpsModel,org.jetbrains.jps.incremental.fs.BuildFSState,org.jetbrains.jps.incremental.storage.ProjectStamps,org.jetbrains.jps.incremental.storage.BuildDataManager,org.jetbrains.jps.builders.logging.BuildLoggingManager,org.jetbrains.jps.indices.ModuleExcludeIndex,org.jetbrains.jps.builders.BuildTargetIndex,org.jetbrains.jps.builders.BuildRootIndex,org.jetbrains.jps.indices.IgnoredFileIndex):V
- getBuildRootIndex():org.jetbrains.jps.builders.BuildRootIndex - getBuildRootIndex():org.jetbrains.jps.builders.BuildRootIndex
- getBuildTargetIndex():org.jetbrains.jps.builders.BuildTargetIndex - getBuildTargetIndex():org.jetbrains.jps.builders.BuildTargetIndex
- getEncodingConfiguration():org.jetbrains.jps.incremental.CompilerEncodingConfiguration - getEncodingConfiguration():org.jetbrains.jps.incremental.CompilerEncodingConfiguration
@@ -2608,7 +2608,7 @@ a:org.jetbrains.jps.incremental.storage.AbstractStateStorage
- appendData(java.lang.Object,java.lang.Object):V - appendData(java.lang.Object,java.lang.Object):V
- f:clean():V - f:clean():V
- f:close():V - f:close():V
- flush(Z):V - f:flush(Z):V
- f:force():V - f:force():V
- pf:getKeyIterator(java.util.function.Function):java.util.Iterator - pf:getKeyIterator(java.util.function.Function):java.util.Iterator
- getKeysIterator():java.util.Iterator - getKeysIterator():java.util.Iterator
@@ -2623,14 +2623,13 @@ f:org.jetbrains.jps.incremental.storage.BuildDataManager
- cleanTargetStorages(org.jetbrains.jps.builders.BuildTarget):V - cleanTargetStorages(org.jetbrains.jps.builders.BuildTarget):V
- close():V - close():V
- closeSourceToOutputStorages(org.jetbrains.jps.builders.impl.BuildTargetChunk):V - closeSourceToOutputStorages(org.jetbrains.jps.builders.impl.BuildTargetChunk):V
- createDependencyGraph(java.io.File,Z):V - createDependencyGraph(java.nio.file.Path,Z):V
- flush(Z):V - flush(Z):V
- getDataPaths():org.jetbrains.jps.builders.storage.BuildDataPaths - getDataPaths():org.jetbrains.jps.builders.storage.BuildDataPaths
- getDependencyGraph():org.jetbrains.jps.dependency.GraphConfiguration - getDependencyGraph():org.jetbrains.jps.dependency.GraphConfiguration
- getFileStampService():org.jetbrains.jps.incremental.storage.ProjectStamps - getFileStampService():org.jetbrains.jps.incremental.storage.ProjectStamps
- getFileStampStorage(org.jetbrains.jps.builders.BuildTarget):org.jetbrains.jps.incremental.storage.StampsStorage - getFileStampStorage(org.jetbrains.jps.builders.BuildTarget):org.jetbrains.jps.incremental.storage.StampsStorage
- s:getMappingsRoot(java.io.File):java.io.File - s:getMappingsRoot(java.nio.file.Path):java.nio.file.Path
- getOutputToTargetRegistry():org.jetbrains.jps.incremental.storage.OutputToTargetRegistry
- getRelativizer():org.jetbrains.jps.incremental.relativizer.PathRelativizerService - getRelativizer():org.jetbrains.jps.incremental.relativizer.PathRelativizerService
- getSourceToOutputMap(org.jetbrains.jps.builders.BuildTarget):org.jetbrains.jps.builders.storage.SourceToOutputMapping - getSourceToOutputMap(org.jetbrains.jps.builders.BuildTarget):org.jetbrains.jps.builders.storage.SourceToOutputMapping
- getStorage(org.jetbrains.jps.builders.BuildTarget,org.jetbrains.jps.builders.storage.StorageProvider):org.jetbrains.jps.incremental.storage.StorageOwner - getStorage(org.jetbrains.jps.builders.BuildTarget,org.jetbrains.jps.builders.storage.StorageProvider):org.jetbrains.jps.incremental.storage.StorageOwner
@@ -2695,11 +2694,6 @@ f:org.jetbrains.jps.incremental.storage.OneToManyPathsMapping
- remove(java.lang.String):V - remove(java.lang.String):V
- removeData(java.lang.String,java.lang.String):V - removeData(java.lang.String,java.lang.String):V
- setOutputs(java.lang.String,java.util.List):V - setOutputs(java.lang.String,java.util.List):V
f:org.jetbrains.jps.incremental.storage.OutputToTargetRegistry
- org.jetbrains.jps.incremental.storage.AbstractStateStorage
- getSafeToDeleteOutputs(java.util.Collection,I):java.util.Collection
- removeMapping(java.lang.String,I):V
- removeMapping(java.util.Collection,I):V
f:org.jetbrains.jps.incremental.storage.ProjectStamps f:org.jetbrains.jps.incremental.storage.ProjectStamps
- sf:PORTABLE_CACHES:Z - sf:PORTABLE_CACHES:Z
- sf:PORTABLE_CACHES_PROPERTY:java.lang.String - sf:PORTABLE_CACHES_PROPERTY:java.lang.String

View File

@@ -101,7 +101,8 @@ public final class BuildRunner {
storageManager = createStorageManager(dataStorageRoot); storageManager = createStorageManager(dataStorageRoot);
fileStampService = initProjectStampStorage(dataStorageRoot, targetsState); fileStampService = initProjectStampStorage(dataStorageRoot, targetsState);
dataManager = new BuildDataManager(dataPaths, targetsState, relativizer, fileStampService, storageManager); dataManager = new BuildDataManager(dataPaths, targetsState, relativizer, storageManager);
dataManager.fileStampService = fileStampService;
if (dataManager.versionDiffers()) { if (dataManager.versionDiffers()) {
myForceCleanCaches = true; myForceCleanCaches = true;
msgHandler.processMessage(new CompilerMessage(getRootCompilerName(), BuildMessage.Kind.INFO, msgHandler.processMessage(new CompilerMessage(getRootCompilerName(), BuildMessage.Kind.INFO,
@@ -129,20 +130,20 @@ public final class BuildRunner {
storageManager = createStorageManager(dataStorageRoot); storageManager = createStorageManager(dataStorageRoot);
targetsState = new BuildTargetsState(dataPaths, jpsModel, buildRootIndex); targetsState = new BuildTargetsState(dataPaths, jpsModel, buildRootIndex);
fileStampService = initProjectStampStorage(dataStorageRoot, targetsState); fileStampService = initProjectStampStorage(dataStorageRoot, targetsState);
dataManager = new BuildDataManager(dataPaths, targetsState, relativizer, fileStampService, storageManager); dataManager = new BuildDataManager(dataPaths, targetsState, relativizer, storageManager);
// the second attempt succeeded // the second attempt succeeded
msgHandler.processMessage(new CompilerMessage(getRootCompilerName(), BuildMessage.Kind.INFO, msgHandler.processMessage(new CompilerMessage(getRootCompilerName(), BuildMessage.Kind.INFO,
JpsBuildBundle.message("build.message.project.rebuild.forced.0", e.getMessage()))); JpsBuildBundle.message("build.message.project.rebuild.forced.0", e.getMessage())));
} }
return new ProjectDescriptor( return new ProjectDescriptor(
jpsModel, fsState, dataManager, BuildLoggingManager.DEFAULT, index, targetIndex, buildRootIndex, ignoredFileIndex jpsModel, fsState, fileStampService, dataManager, BuildLoggingManager.DEFAULT, index, targetIndex, buildRootIndex, ignoredFileIndex
); );
} }
private static @Nullable StorageManager createStorageManager(@NotNull Path dataStorageRoot) { private static @Nullable StorageManager createStorageManager(@NotNull Path dataStorageRoot) {
if (USE_EXPERIMENTAL_STORAGE || ProjectStamps.PORTABLE_CACHES) { if (USE_EXPERIMENTAL_STORAGE || ProjectStamps.PORTABLE_CACHES) {
StorageManager manager = new StorageManager(dataStorageRoot.resolve("jps-portable-cache.db"), 10_000); StorageManager manager = new StorageManager(dataStorageRoot.resolve("jps-portable-cache.db"));
manager.open(); manager.open();
return manager; return manager;
} }

View File

@@ -1,8 +1,6 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.cmdline; package org.jetbrains.jps.cmdline;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.jps.builders.BuildRootIndex; import org.jetbrains.jps.builders.BuildRootIndex;
import org.jetbrains.jps.builders.BuildTarget; import org.jetbrains.jps.builders.BuildTarget;
import org.jetbrains.jps.builders.BuildTargetIndex; import org.jetbrains.jps.builders.BuildTargetIndex;
@@ -30,10 +28,10 @@ import java.util.Set;
public final class ProjectDescriptor { public final class ProjectDescriptor {
private final JpsProject myProject; private final JpsProject myProject;
private final JpsModel myModel; private final JpsModel myModel;
@TestOnly
private ProjectStamps deprecatedStamps;
public final BuildFSState fsState; public final BuildFSState fsState;
public final BuildDataManager dataManager; public final BuildDataManager dataManager;
private final ProjectStamps myProjectStamps;
private final BuildLoggingManager myLoggingManager; private final BuildLoggingManager myLoggingManager;
private final ModuleExcludeIndex myModuleExcludeIndex; private final ModuleExcludeIndex myModuleExcludeIndex;
private int myUseCounter = 1; private int myUseCounter = 1;
@@ -43,11 +41,6 @@ public final class ProjectDescriptor {
private final BuildTargetIndex myBuildTargetIndex; private final BuildTargetIndex myBuildTargetIndex;
private final IgnoredFileIndex myIgnoredFileIndex; private final IgnoredFileIndex myIgnoredFileIndex;
/**
* @deprecated Use {@link ProjectDescriptor#ProjectDescriptor(JpsModel, BuildFSState, BuildDataManager, BuildLoggingManager, ModuleExcludeIndex, BuildTargetIndex, BuildRootIndex, IgnoredFileIndex)}
*/
@Deprecated(forRemoval = true)
@ApiStatus.Internal
public ProjectDescriptor(JpsModel model, public ProjectDescriptor(JpsModel model,
BuildFSState fsState, BuildFSState fsState,
ProjectStamps projectStamps, ProjectStamps projectStamps,
@@ -57,22 +50,12 @@ public final class ProjectDescriptor {
BuildTargetIndex buildTargetIndex, BuildTargetIndex buildTargetIndex,
BuildRootIndex buildRootIndex, BuildRootIndex buildRootIndex,
IgnoredFileIndex ignoredFileIndex) { IgnoredFileIndex ignoredFileIndex) {
this(model, fsState, dataManager, loggingManager, moduleExcludeIndex, buildTargetIndex, buildRootIndex, ignoredFileIndex);
deprecatedStamps = projectStamps;
}
public ProjectDescriptor(JpsModel model,
BuildFSState fsState,
BuildDataManager dataManager,
BuildLoggingManager loggingManager,
ModuleExcludeIndex moduleExcludeIndex,
BuildTargetIndex buildTargetIndex,
BuildRootIndex buildRootIndex,
IgnoredFileIndex ignoredFileIndex) {
myModel = model; myModel = model;
myIgnoredFileIndex = ignoredFileIndex; myIgnoredFileIndex = ignoredFileIndex;
myProject = model.getProject(); myProject = model.getProject();
this.fsState = fsState; this.fsState = fsState;
myProjectStamps = projectStamps;
dataManager.fileStampService = projectStamps;
this.dataManager = dataManager; this.dataManager = dataManager;
myBuildTargetIndex = buildTargetIndex; myBuildTargetIndex = buildTargetIndex;
myBuildRootIndex = buildRootIndex; myBuildRootIndex = buildRootIndex;
@@ -154,10 +137,6 @@ public final class ProjectDescriptor {
*/ */
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
public ProjectStamps getProjectStamps() { public ProjectStamps getProjectStamps() {
//noinspection TestOnlyProblems
if (deprecatedStamps != null) {
return deprecatedStamps;
}
//noinspection removal //noinspection removal
return dataManager.getFileStampService(); return dataManager.getFileStampService();
} }

View File

@@ -116,17 +116,17 @@ public final class BuildOperations {
} }
private static boolean dropRemovedPaths(CompileContext context, BuildTargetChunk chunk) throws IOException { private static boolean dropRemovedPaths(CompileContext context, BuildTargetChunk chunk) throws IOException {
final Map<BuildTarget<?>, Collection<String>> map = Utils.REMOVED_SOURCES_KEY.get(context); Map<BuildTarget<?>, Collection<String>> map = Utils.REMOVED_SOURCES_KEY.get(context);
boolean dropped = false; boolean dropped = false;
if (map != null) { if (map != null) {
for (BuildTarget<?> target : chunk.getTargets()) { for (BuildTarget<?> target : chunk.getTargets()) {
final Collection<String> paths = map.remove(target); Collection<String> paths = map.remove(target);
if (paths != null) { if (paths != null) {
final SourceToOutputMapping storage = context.getProjectDescriptor().dataManager.getSourceToOutputMap(target); SourceToOutputMapping storage = context.getProjectDescriptor().dataManager.getSourceToOutputMap(target);
for (String path : paths) { for (String path : paths) {
storage.remove(path); storage.remove(path);
dropped = true;
} }
dropped = true;
} }
} }
} }
@@ -161,26 +161,28 @@ public final class BuildOperations {
else { else {
targetId = idsCache.getInt(target); targetId = idsCache.getInt(target);
} }
final String srcPath = file.getPath();
final Collection<String> outputs = srcToOut.getOutputs(srcPath); Collection<String> outputs = srcToOut.getOutputs(file.getPath());
if (outputs != null) { if (outputs == null) {
final boolean shouldPruneOutputDirs = target instanceof ModuleBasedTarget; return true;
final List<String> deletedForThisSource = new ArrayList<>(outputs.size());
for (String output : outputs) {
deleteRecursively(output, deletedForThisSource, shouldPruneOutputDirs ? dirsToDelete : null);
}
deletedPaths.addAll(deletedForThisSource);
dataManager.getOutputToTargetRegistry().removeMapping(deletedForThisSource, targetId);
Set<File> cleaned = cleanedSources.get(target);
if (cleaned == null) {
cleaned = FileCollectionFactory.createCanonicalFileSet();
cleanedSources.put(target, cleaned);
}
cleaned.add(file);
} }
boolean shouldPruneOutputDirs = target instanceof ModuleBasedTarget;
List<String> deletedForThisSource = new ArrayList<>(outputs.size());
for (String output : outputs) {
deleteRecursively(output, deletedForThisSource, shouldPruneOutputDirs ? dirsToDelete : null);
}
deletedPaths.addAll(deletedForThisSource);
dataManager.getOutputToTargetMapping().removeMappings(deletedForThisSource, targetId, srcToOut);
Set<File> cleaned = cleanedSources.get(target);
if (cleaned == null) {
cleaned = FileCollectionFactory.createCanonicalFileSet();
cleanedSources.put(target, cleaned);
}
cleaned.add(file);
return true; return true;
} }
}); });
if (!deletedPaths.isEmpty()) { if (!deletedPaths.isEmpty()) {

View File

@@ -712,29 +712,96 @@ public final class IncProjectBuilder {
registerTargetsWithClearedOutput(context, Collections.singletonList(target)); registerTargetsWithClearedOutput(context, Collections.singletonList(target));
} }
private static void clearOutputFiles(CompileContext context, private boolean processDeletedPaths(CompileContext context, final Set<? extends BuildTarget<?>> targets) throws ProjectBuildException {
SourceToOutputMapping mapping, boolean doneSomething = false;
BuildTargetType<?> targetType, try {
int targetId) throws IOException { // cleanup outputs
Set<File> dirsToDelete = targetType instanceof ModuleBasedBuildTargetType<?> ? FileCollectionFactory.createCanonicalFileSet() : null; final Map<BuildTarget<?>, Collection<String>> targetToRemovedSources = new HashMap<>();
OutputToTargetRegistry outputToTargetRegistry = context.getProjectDescriptor().dataManager.getOutputToTargetRegistry();
for (SourceToOutputMappingCursor cursor = mapping.cursor(); cursor.hasNext(); ) { Set<File> dirsToDelete = FileCollectionFactory.createCanonicalFileSet();
cursor.next(); for (BuildTarget<?> target : targets) {
String [] outs = cursor.getOutputPaths(); Collection<String> deletedPaths = myProjectDescriptor.fsState.getAndClearDeletedPaths(target);
if (outs.length > 0) { if (deletedPaths.isEmpty()) {
List<String> deletedPaths = new ArrayList<>(); continue;
for (String out : outs) {
BuildOperations.deleteRecursively(out, deletedPaths, dirsToDelete);
} }
outputToTargetRegistry.removeMapping(Arrays.asList(outs), targetId);
if (!deletedPaths.isEmpty()) { targetToRemovedSources.put(target, deletedPaths);
context.processMessage(new FileDeletedEvent(deletedPaths)); if (isTargetOutputCleared(context, target)) {
continue;
}
final int buildTargetId = context.getProjectDescriptor().getTargetsState().getBuildTargetId(target);
final boolean shouldPruneEmptyDirs = target instanceof ModuleBasedTarget;
BuildDataManager dataManager = context.getProjectDescriptor().dataManager;
final SourceToOutputMapping sourceToOutputStorage = dataManager.getSourceToOutputMap(target);
final ProjectBuilderLogger logger = context.getLoggingManager().getProjectBuilderLogger();
// actually delete outputs associated with removed paths
final Collection<String> pathsForIteration;
if (myIsTestMode) {
// ensure predictable order in test logs
pathsForIteration = new ArrayList<>(deletedPaths);
Collections.sort((List<String>)pathsForIteration);
}
else {
pathsForIteration = deletedPaths;
}
for (String deletedSource : pathsForIteration) {
// deleting outputs corresponding to a non-existing source
Collection<String> outputs = sourceToOutputStorage.getOutputs(deletedSource);
if (outputs != null && !outputs.isEmpty()) {
List<String> deletedOutputPaths = new ArrayList<>();
OutputToTargetMapping outputToSourceRegistry = dataManager.getOutputToTargetMapping();
for (String output : outputToSourceRegistry.removeTargetAndGetSafeToDeleteOutputs(outputs, buildTargetId, sourceToOutputStorage)) {
final boolean deleted = BuildOperations.deleteRecursively(output, deletedOutputPaths, shouldPruneEmptyDirs ? dirsToDelete : null);
if (deleted) {
doneSomething = true;
}
}
if (!deletedOutputPaths.isEmpty()) {
if (logger.isEnabled()) {
logger.logDeletedFiles(deletedOutputPaths);
}
context.processMessage(new FileDeletedEvent(deletedOutputPaths));
}
}
if (target instanceof ModuleBuildTarget) {
// check if the deleted source was associated with a form
OneToManyPathMapping sourceToFormMap = dataManager.getSourceToFormMap(target);
Collection<String> boundForms = sourceToFormMap.getOutputs(deletedSource);
if (boundForms != null) {
for (String formPath : boundForms) {
final File formFile = new File(formPath);
if (formFile.exists()) {
FSOperations.markDirty(context, CompilationRound.CURRENT, formFile);
}
}
sourceToFormMap.remove(deletedSource);
}
}
} }
} }
} if (!targetToRemovedSources.isEmpty()) {
if (dirsToDelete != null) { final Map<BuildTarget<?>, Collection<String>> existing = Utils.REMOVED_SOURCES_KEY.get(context);
if (existing != null) {
for (Map.Entry<BuildTarget<?>, Collection<String>> entry : existing.entrySet()) {
final Collection<String> paths = targetToRemovedSources.get(entry.getKey());
if (paths != null) {
paths.addAll(entry.getValue());
}
else {
targetToRemovedSources.put(entry.getKey(), entry.getValue());
}
}
}
Utils.REMOVED_SOURCES_KEY.set(context, targetToRemovedSources);
}
FSOperations.pruneEmptyDirs(context, dirsToDelete); FSOperations.pruneEmptyDirs(context, dirsToDelete);
} }
catch (IOException e) {
throw new ProjectBuildException(e);
}
return doneSomething;
} }
private static void registerTargetsWithClearedOutput(CompileContext context, Collection<? extends BuildTarget<?>> targets) { private static void registerTargetsWithClearedOutput(CompileContext context, Collection<? extends BuildTarget<?>> targets) {
@@ -1458,98 +1525,29 @@ public final class IncProjectBuilder {
myMessageDispatcher.processMessage(new BuildingTargetProgressMessage(targets, event)); myMessageDispatcher.processMessage(new BuildingTargetProgressMessage(targets, event));
} }
private boolean processDeletedPaths(CompileContext context, final Set<? extends BuildTarget<?>> targets) throws ProjectBuildException { private static void clearOutputFiles(CompileContext context,
boolean doneSomething = false; SourceToOutputMapping mapping,
try { BuildTargetType<?> targetType,
// cleanup outputs int targetId) throws IOException {
final Map<BuildTarget<?>, Collection<String>> targetToRemovedSources = new HashMap<>(); Set<File> dirsToDelete = targetType instanceof ModuleBasedBuildTargetType<?> ? FileCollectionFactory.createCanonicalFileSet() : null;
OutputToTargetMapping outputToTargetRegistry = context.getProjectDescriptor().dataManager.getOutputToTargetMapping();
Set<File> dirsToDelete = FileCollectionFactory.createCanonicalFileSet(); for (SourceToOutputMappingCursor cursor = mapping.cursor(); cursor.hasNext(); ) {
for (BuildTarget<?> target : targets) { cursor.next();
final Collection<String> deletedPaths = myProjectDescriptor.fsState.getAndClearDeletedPaths(target); String [] outs = cursor.getOutputPaths();
if (deletedPaths.isEmpty()) { if (outs.length > 0) {
continue; List<String> deletedPaths = new ArrayList<>();
for (String out : outs) {
BuildOperations.deleteRecursively(out, deletedPaths, dirsToDelete);
} }
targetToRemovedSources.put(target, deletedPaths); outputToTargetRegistry.removeMappings(Arrays.asList(outs), targetId, mapping);
if (isTargetOutputCleared(context, target)) { if (!deletedPaths.isEmpty()) {
continue; context.processMessage(new FileDeletedEvent(deletedPaths));
}
final int buildTargetId = context.getProjectDescriptor().getTargetsState().getBuildTargetId(target);
final boolean shouldPruneEmptyDirs = target instanceof ModuleBasedTarget;
BuildDataManager dataManager = context.getProjectDescriptor().dataManager;
final SourceToOutputMapping sourceToOutputStorage = dataManager.getSourceToOutputMap(target);
final ProjectBuilderLogger logger = context.getLoggingManager().getProjectBuilderLogger();
// actually delete outputs associated with removed paths
final Collection<String> pathsForIteration;
if (myIsTestMode) {
// ensure predictable order in test logs
pathsForIteration = new ArrayList<>(deletedPaths);
Collections.sort((List<String>)pathsForIteration);
}
else {
pathsForIteration = deletedPaths;
}
for (String deletedSource : pathsForIteration) {
// deleting outputs corresponding to non-existing source
final Collection<String> outputs = sourceToOutputStorage.getOutputs(deletedSource);
if (outputs != null && !outputs.isEmpty()) {
List<String> deletedOutputPaths = new ArrayList<>();
final OutputToTargetRegistry outputToSourceRegistry = dataManager.getOutputToTargetRegistry();
for (String output : outputToSourceRegistry.getSafeToDeleteOutputs(outputs, buildTargetId)) {
final boolean deleted = BuildOperations.deleteRecursively(output, deletedOutputPaths, shouldPruneEmptyDirs ? dirsToDelete : null);
if (deleted) {
doneSomething = true;
}
}
for (String outputPath : outputs) {
outputToSourceRegistry.removeMapping(outputPath, buildTargetId);
}
if (!deletedOutputPaths.isEmpty()) {
if (logger.isEnabled()) {
logger.logDeletedFiles(deletedOutputPaths);
}
context.processMessage(new FileDeletedEvent(deletedOutputPaths));
}
}
if (target instanceof ModuleBuildTarget) {
// check if the deleted source was associated with a form
OneToManyPathMapping sourceToFormMap = dataManager.getSourceToFormMap(target);
Collection<String> boundForms = sourceToFormMap.getOutputs(deletedSource);
if (boundForms != null) {
for (String formPath : boundForms) {
final File formFile = new File(formPath);
if (formFile.exists()) {
FSOperations.markDirty(context, CompilationRound.CURRENT, formFile);
}
}
sourceToFormMap.remove(deletedSource);
}
}
} }
} }
if (!targetToRemovedSources.isEmpty()) { }
final Map<BuildTarget<?>, Collection<String>> existing = Utils.REMOVED_SOURCES_KEY.get(context); if (dirsToDelete != null) {
if (existing != null) {
for (Map.Entry<BuildTarget<?>, Collection<String>> entry : existing.entrySet()) {
final Collection<String> paths = targetToRemovedSources.get(entry.getKey());
if (paths != null) {
paths.addAll(entry.getValue());
}
else {
targetToRemovedSources.put(entry.getKey(), entry.getValue());
}
}
}
Utils.REMOVED_SOURCES_KEY.set(context, targetToRemovedSources);
}
FSOperations.pruneEmptyDirs(context, dirsToDelete); FSOperations.pruneEmptyDirs(context, dirsToDelete);
} }
catch (IOException e) {
throw new ProjectBuildException(e);
}
return doneSomething;
} }
// return true if changed something, false otherwise // return true if changed something, false otherwise

View File

@@ -194,11 +194,8 @@ public final class BuildFSState {
} }
public Collection<String> getAndClearDeletedPaths(BuildTarget<?> target) { public Collection<String> getAndClearDeletedPaths(BuildTarget<?> target) {
final FilesDelta delta = myDeltas.get(target); FilesDelta delta = myDeltas.get(target);
if (delta != null) { return delta == null ? List.of() : delta.getAndClearDeletedPaths();
return delta.getAndClearDeletedPaths();
}
return Collections.emptyList();
} }
private @NotNull FilesDelta getDelta(BuildTarget<?> buildTarget) { private @NotNull FilesDelta getDelta(BuildTarget<?> buildTarget) {
@@ -238,10 +235,10 @@ public final class BuildFSState {
*/ */
public boolean markDirty(@Nullable CompileContext context, public boolean markDirty(@Nullable CompileContext context,
File file, File file,
final BuildRootDescriptor rd, BuildRootDescriptor buildRootDescriptor,
@Nullable StampsStorage<?> stampStorage, @Nullable StampsStorage<?> stampStorage,
boolean saveEventStamp) throws IOException { boolean saveEventStamp) throws IOException {
return markDirty(context, CompilationRound.NEXT, file, rd, stampStorage, saveEventStamp); return markDirty(context, CompilationRound.NEXT, file, buildRootDescriptor, stampStorage, saveEventStamp);
} }
public boolean markDirty(@Nullable CompileContext context, public boolean markDirty(@Nullable CompileContext context,
@@ -384,7 +381,7 @@ public final class BuildFSState {
*/ */
public boolean markAllUpToDate(@NotNull CompileContext context, public boolean markAllUpToDate(@NotNull CompileContext context,
@NotNull BuildRootDescriptor buildRootDescriptor, @NotNull BuildRootDescriptor buildRootDescriptor,
@NotNull StampsStorage<?> stampStorage, @Nullable StampsStorage<?> stampStorage,
long targetBuildStartStamp) throws IOException { long targetBuildStartStamp) throws IOException {
boolean marked = false; boolean marked = false;
final BuildTarget<?> target = buildRootDescriptor.getTarget(); final BuildTarget<?> target = buildRootDescriptor.getTarget();
@@ -415,7 +412,9 @@ public final class BuildFSState {
} }
else { else {
marked = true; marked = true;
stampStorage.updateStamp(nioFile, target, currentFileTimestamp); if (stampStorage != null) {
stampStorage.updateStamp(nioFile, target, currentFileTimestamp);
}
} }
} }
else { else {

View File

@@ -244,13 +244,15 @@ public final class FilesDelta {
} }
} }
public Set<String> getAndClearDeletedPaths() { public @NotNull Set<String> getAndClearDeletedPaths() {
lockData(); lockData();
try { try {
if (myDeletedPaths.isEmpty()) {
return Set.of();
}
try { try {
Set<String> result = CollectionFactory.createFilePathLinkedSet(); return CollectionFactory.createFilePathLinkedSet(myDeletedPaths);
result.addAll(myDeletedPaths);
return result;
} }
finally { finally {
myDeletedPaths.clear(); myDeletedPaths.clear();

View File

@@ -6,15 +6,13 @@ import com.intellij.util.io.*;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function; import java.util.function.Function;
public abstract class AbstractStateStorage<Key, T> implements StorageOwner { public abstract class AbstractStateStorage<Key, T> implements StorageOwner {
@@ -107,11 +105,28 @@ public abstract class AbstractStateStorage<Key, T> implements StorageOwner {
} }
} }
/**
* @deprecated Use {@link #getKeysIterator()}
*/
@TestOnly
@ApiStatus.Internal
@Deprecated(forRemoval = true)
public final Collection<Key> getKeys() throws IOException {
return getAllKeys();
}
public @NotNull Iterator<Key> getKeysIterator() throws IOException { public @NotNull Iterator<Key> getKeysIterator() throws IOException {
//noinspection TestOnlyProblems
return getAllKeys().iterator();
}
@TestOnly
@ApiStatus.Internal
public final @NotNull List<Key> getAllKeys() throws IOException {
synchronized (dataLock) { synchronized (dataLock) {
List<Key> result = new ArrayList<>(); List<Key> result = new ArrayList<>();
map.processExistingKeys(new CommonProcessors.CollectProcessor<>(result)); map.processExistingKeys(new CommonProcessors.CollectProcessor<>(result));
return result.iterator(); return result.isEmpty() ? List.of() : result;
} }
} }
@@ -132,7 +147,7 @@ public abstract class AbstractStateStorage<Key, T> implements StorageOwner {
} }
@Override @Override
public void flush(boolean memoryCachesOnly) { public final void flush(boolean memoryCachesOnly) {
if (!memoryCachesOnly) { if (!memoryCachesOnly) {
force(); force();
} }

View File

@@ -42,7 +42,6 @@ import java.util.concurrent.Future;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
@@ -61,10 +60,11 @@ public final class BuildDataManager {
private final @NotNull ConcurrentMap<BuildTarget<?>, BuildTargetStorages> myTargetStorages = new ConcurrentHashMap<>(); private final @NotNull ConcurrentMap<BuildTarget<?>, BuildTargetStorages> myTargetStorages = new ConcurrentHashMap<>();
private final @NotNull ConcurrentMap<BuildTarget<?>, SourceToOutputMappingWrapper> buildTargetToSourceToOutputMapping = new ConcurrentHashMap<>(); private final @NotNull ConcurrentMap<BuildTarget<?>, SourceToOutputMappingWrapper> buildTargetToSourceToOutputMapping = new ConcurrentHashMap<>();
// only for new experimental storage private final @Nullable ExperimentalBuildDataManager newDataManager;
private final @NotNull ConcurrentMap<BuildTarget<?>, PerTargetMapManager> targetToMapManager = new ConcurrentHashMap<>();
private final @Nullable ProjectStamps fileStampService; // make private after updating bootstrap for Kotlin tests
@ApiStatus.Internal
public @Nullable ProjectStamps fileStampService;
private final @Nullable OneToManyPathsMapping sourceToFormMap; private final @Nullable OneToManyPathsMapping sourceToFormMap;
private final Mappings myMappings; private final Mappings myMappings;
@@ -73,8 +73,7 @@ public final class BuildDataManager {
private final NodeSourcePathMapper myDepGraphPathMapper; private final NodeSourcePathMapper myDepGraphPathMapper;
private final BuildDataPaths myDataPaths; private final BuildDataPaths myDataPaths;
private final BuildTargetsState myTargetsState; private final BuildTargetsState myTargetsState;
@Nullable private final StorageManager storageManager; private final @Nullable OutputToTargetRegistry outputToTargetMapping;
private final OutputToTargetRegistry myOutputToTargetRegistry;
private final File myVersionFile; private final File myVersionFile;
private final PathRelativizerService myRelativizer; private final PathRelativizerService myRelativizer;
private boolean myProcessConstantsIncrementally = !Boolean.parseBoolean(System.getProperty(PROCESS_CONSTANTS_NON_INCREMENTAL_PROPERTY, "false")); private boolean myProcessConstantsIncrementally = !Boolean.parseBoolean(System.getProperty(PROCESS_CONSTANTS_NON_INCREMENTAL_PROPERTY, "false"));
@@ -84,33 +83,33 @@ public final class BuildDataManager {
public BuildDataManager(BuildDataPaths dataPaths, public BuildDataManager(BuildDataPaths dataPaths,
BuildTargetsState targetsState, BuildTargetsState targetsState,
@NotNull PathRelativizerService relativizer) throws IOException { @NotNull PathRelativizerService relativizer) throws IOException {
this(dataPaths, targetsState, relativizer, null, null); this(dataPaths, targetsState, relativizer, null);
} }
@ApiStatus.Internal @ApiStatus.Internal
public BuildDataManager(BuildDataPaths dataPaths, public BuildDataManager(BuildDataPaths dataPaths,
BuildTargetsState targetsState, BuildTargetsState targetsState,
@NotNull PathRelativizerService relativizer, @NotNull PathRelativizerService relativizer,
@Nullable ProjectStamps fileStampService,
@Nullable StorageManager storageManager) throws IOException { @Nullable StorageManager storageManager) throws IOException {
this.fileStampService = fileStampService; newDataManager = storageManager == null ? null :new ExperimentalBuildDataManager(storageManager, relativizer);
myDataPaths = dataPaths; myDataPaths = dataPaths;
myTargetsState = targetsState; myTargetsState = targetsState;
this.storageManager = storageManager; Path dataStorageRoot = myDataPaths.getDataStorageRoot().toPath();
try { try {
sourceToFormMap = storageManager == null ? new OneToManyPathsMapping(getSourceToFormsRoot().resolve("data"), relativizer) : null; sourceToFormMap = storageManager == null ? new OneToManyPathsMapping(getSourceToFormsRoot().resolve("data"), relativizer) : null;
myOutputToTargetRegistry = new OutputToTargetRegistry(getOutputToSourceRegistryRoot().resolve("data"), relativizer); outputToTargetMapping = storageManager == null ? new OutputToTargetRegistry(getOutputToSourceRegistryRoot().resolve("data"), relativizer) : null;
File mappingsRoot = getMappingsRoot(myDataPaths.getDataStorageRoot()); Path mappingsRoot = getMappingsRoot(dataStorageRoot);
if (JavaBuilderUtil.isDepGraphEnabled()) { if (JavaBuilderUtil.isDepGraphEnabled()) {
myMappings = null; myMappings = null;
createDependencyGraph(mappingsRoot, false); createDependencyGraph(mappingsRoot, false);
FileUtilRt.delete(getMappingsRoot(myDataPaths.getDataStorageRoot(), false)); // delete older mappings data if available // delete older mappings data if available
FileUtilRt.deleteRecursively(getMappingsRoot(dataStorageRoot, false));
LOG.info("Using DependencyGraph-based build incremental analysis"); LOG.info("Using DependencyGraph-based build incremental analysis");
} }
else { else {
myMappings = new Mappings(mappingsRoot, relativizer); myMappings = new Mappings(mappingsRoot.toFile(), relativizer);
FileUtil.delete(getMappingsRoot(myDataPaths.getDataStorageRoot(), true)); // delete dep-graph data if available FileUtilRt.deleteRecursively(getMappingsRoot(dataStorageRoot, true)); // delete dep-graph data if available
myMappings.setProcessConstantsIncrementally(isProcessConstantsIncrementally()); myMappings.setProcessConstantsIncrementally(isProcessConstantsIncrementally());
} }
} }
@@ -122,15 +121,15 @@ public final class BuildDataManager {
} }
throw e; throw e;
} }
myVersionFile = new File(myDataPaths.getDataStorageRoot(), "version.dat"); myVersionFile = dataStorageRoot.resolve("version.dat").toFile();
myDepGraphPathMapper = new PathSourceMapper(relativizer::toFull, relativizer::toRelative); myDepGraphPathMapper = new PathSourceMapper(relativizer::toFull, relativizer::toRelative);
myRelativizer = relativizer; myRelativizer = relativizer;
} }
@ApiStatus.Internal @ApiStatus.Internal
public void clearCache() { public void clearCache() {
if (storageManager != null) { if (newDataManager != null) {
storageManager.clearCache(); newDataManager.clearCache();
} }
} }
@@ -153,8 +152,8 @@ public final class BuildDataManager {
public void cleanStaleTarget(@NotNull BuildTargetType<?> targetType, @NotNull String targetId) throws IOException { public void cleanStaleTarget(@NotNull BuildTargetType<?> targetType, @NotNull String targetId) throws IOException {
try { try {
FileUtilRt.deleteRecursively(getDataPaths().getTargetDataRoot(targetType, targetId)); FileUtilRt.deleteRecursively(getDataPaths().getTargetDataRoot(targetType, targetId));
if (storageManager != null) { if (newDataManager != null) {
storageManager.removeMaps(targetId, targetType.getTypeId()); newDataManager.removeStaleTarget(targetId, targetType.getTypeId());
} }
} }
finally { finally {
@@ -162,12 +161,23 @@ public final class BuildDataManager {
} }
} }
public OutputToTargetRegistry getOutputToTargetRegistry() { @ApiStatus.Internal
return myOutputToTargetRegistry; public @NotNull OutputToTargetMapping getOutputToTargetMapping() {
return newDataManager == null ? Objects.requireNonNull(outputToTargetMapping) : newDataManager.getOutputToTargetMapping();
}
/**
* @deprecated Use {@link #getOutputToTargetMapping()}
* @return
*/
@ApiStatus.Internal
@Deprecated(forRemoval = true)
public @NotNull OutputToTargetRegistry getOutputToTargetRegistry() {
return Objects.requireNonNull(outputToTargetMapping);
} }
public @NotNull SourceToOutputMapping getSourceToOutputMap(@NotNull BuildTarget<?> target) throws IOException { public @NotNull SourceToOutputMapping getSourceToOutputMap(@NotNull BuildTarget<?> target) throws IOException {
if (storageManager == null) { if (newDataManager == null) {
try { try {
return buildTargetToSourceToOutputMapping.computeIfAbsent(target, this::createSourceToOutputMap); return buildTargetToSourceToOutputMapping.computeIfAbsent(target, this::createSourceToOutputMap);
} }
@@ -177,12 +187,12 @@ public final class BuildDataManager {
} }
} }
else { else {
return getPerTargetMapManager(target).getSourceToOutputMapping(); return newDataManager.getSourceToOutputMapping(target);
} }
} }
private @NotNull SourceToOutputMappingWrapper createSourceToOutputMap(@NotNull BuildTarget<?> target) { private @NotNull SourceToOutputMappingWrapper createSourceToOutputMap(@NotNull BuildTarget<?> target) {
SourceToOutputMapping map; SourceToOutputMappingImpl map;
try { try {
Path file = myDataPaths.getTargetDataRootDir(target).resolve(SRC_TO_OUTPUT_STORAGE).resolve(SRC_TO_OUTPUT_FILE_NAME); Path file = myDataPaths.getTargetDataRootDir(target).resolve(SRC_TO_OUTPUT_STORAGE).resolve(SRC_TO_OUTPUT_FILE_NAME);
map = new SourceToOutputMappingImpl(file, myRelativizer); map = new SourceToOutputMappingImpl(file, myRelativizer);
@@ -191,21 +201,16 @@ public final class BuildDataManager {
LOG.info(e); LOG.info(e);
throw new BuildDataCorruptedException(e); throw new BuildDataCorruptedException(e);
} }
return new SourceToOutputMappingWrapper(map, myTargetsState.getBuildTargetId(target)); return new SourceToOutputMappingWrapper(map, myTargetsState.getBuildTargetId(target), outputToTargetMapping);
} }
public @Nullable StampsStorage<?> getFileStampStorage(@NotNull BuildTarget<?> target) { public @Nullable StampsStorage<?> getFileStampStorage(@NotNull BuildTarget<?> target) {
if (storageManager == null) { if (newDataManager == null) {
return fileStampService == null ? null : fileStampService.getStampStorage(); return fileStampService == null ? null : fileStampService.getStampStorage();
} }
return getPerTargetMapManager(target).stamp; else {
} return newDataManager.getFileStampStorage(target);
}
private @NotNull PerTargetMapManager getPerTargetMapManager(@NotNull BuildTarget<?> target) {
return targetToMapManager.computeIfAbsent(target, it -> {
assert storageManager != null;
return new PerTargetMapManager(storageManager, myRelativizer, it, mapping -> new SourceToOutputMappingWrapper(mapping, myTargetsState.getBuildTargetId(it)));
});
} }
/** /**
@@ -228,7 +233,7 @@ public final class BuildDataManager {
@ApiStatus.Internal @ApiStatus.Internal
public @NotNull OneToManyPathMapping getSourceToFormMap(@NotNull BuildTarget<?> target) { public @NotNull OneToManyPathMapping getSourceToFormMap(@NotNull BuildTarget<?> target) {
return sourceToFormMap == null ? getPerTargetMapManager(target).getSourceToForm() : sourceToFormMap; return newDataManager == null ? Objects.requireNonNull(sourceToFormMap) : newDataManager.getSourceToForm(target);
} }
@ApiStatus.Internal @ApiStatus.Internal
@@ -263,11 +268,13 @@ public final class BuildDataManager {
} }
finally { finally {
SourceToOutputMappingWrapper sourceToOutput = buildTargetToSourceToOutputMapping.remove(target); SourceToOutputMappingWrapper sourceToOutput = buildTargetToSourceToOutputMapping.remove(target);
if (sourceToOutput != null && sourceToOutput.myDelegate instanceof StorageOwner) { if (sourceToOutput != null && sourceToOutput.myDelegate != null) {
((StorageOwner)sourceToOutput.myDelegate).close(); ((StorageOwner)sourceToOutput.myDelegate).close();
} }
targetToMapManager.remove(target); if (newDataManager != null) {
newDataManager.closeTargetMaps(target);
}
} }
} }
finally { finally {
@@ -285,7 +292,7 @@ public final class BuildDataManager {
} }
public void clean(@NotNull Consumer<Future<?>> asyncTaskCollector) throws IOException { public void clean(@NotNull Consumer<Future<?>> asyncTaskCollector) throws IOException {
if (fileStampService != null) { if (newDataManager == null && fileStampService != null) {
try { try {
((StorageOwner)fileStampService.getStampStorage()).clean(); ((StorageOwner)fileStampService.getStampStorage()).clean();
} }
@@ -297,10 +304,9 @@ public final class BuildDataManager {
try { try {
allTargetStorages(asyncTaskCollector).clean(); allTargetStorages(asyncTaskCollector).clean();
buildTargetToSourceToOutputMapping.clear(); buildTargetToSourceToOutputMapping.clear();
targetToMapManager.clear();
myTargetStorages.clear(); myTargetStorages.clear();
if (storageManager != null) { if (newDataManager != null) {
storageManager.clean(); newDataManager.removeAllMaps();
} }
} }
finally { finally {
@@ -311,18 +317,20 @@ public final class BuildDataManager {
} }
finally { finally {
try { try {
wipeStorage(getOutputToSourceRegistryRoot(), myOutputToTargetRegistry); if (outputToTargetMapping != null) {
wipeStorage(getOutputToSourceRegistryRoot(), outputToTargetMapping);
}
} }
finally { finally {
File mappingsRoot = getMappingsRoot(myDataPaths.getDataStorageRoot()); Path mappingsRoot = getMappingsRoot(myDataPaths.getDataStorageRoot().toPath());
final Mappings mappings = myMappings; Mappings mappings = myMappings;
if (mappings != null) { if (mappings != null) {
synchronized (mappings) { synchronized (mappings) {
mappings.clean(); mappings.clean();
} }
} }
else { else {
FileUtil.delete(mappingsRoot); FileUtilRt.deleteRecursively(mappingsRoot);
} }
if (JavaBuilderUtil.isDepGraphEnabled()) { if (JavaBuilderUtil.isDepGraphEnabled()) {
@@ -335,7 +343,7 @@ public final class BuildDataManager {
saveVersion(); saveVersion();
} }
public void createDependencyGraph(File mappingsRoot, boolean deleteExisting) throws IOException { public void createDependencyGraph(@NotNull Path mappingsRoot, boolean deleteExisting) throws IOException {
try { try {
synchronized (myGraphManagementLock) { synchronized (myGraphManagementLock) {
DependencyGraph depGraph = myDepGraph; DependencyGraph depGraph = myDepGraph;
@@ -343,7 +351,7 @@ public final class BuildDataManager {
if (deleteExisting) { if (deleteExisting) {
FileUtil.delete(mappingsRoot); FileUtil.delete(mappingsRoot);
} }
myDepGraph = asSynchronizedGraph(new DependencyGraphImpl(Containers.createPersistentContainerFactory(mappingsRoot.getAbsolutePath()))); myDepGraph = asSynchronizedGraph(new DependencyGraphImpl(Containers.createPersistentContainerFactory(mappingsRoot.toString())));
} }
else { else {
try { try {
@@ -353,7 +361,7 @@ public final class BuildDataManager {
if (deleteExisting) { if (deleteExisting) {
FileUtil.delete(mappingsRoot); FileUtil.delete(mappingsRoot);
} }
myDepGraph = asSynchronizedGraph(new DependencyGraphImpl(Containers.createPersistentContainerFactory(mappingsRoot.getAbsolutePath()))); myDepGraph = asSynchronizedGraph(new DependencyGraphImpl(Containers.createPersistentContainerFactory(mappingsRoot.toString())));
} }
} }
} }
@@ -368,20 +376,21 @@ public final class BuildDataManager {
} }
public void flush(boolean memoryCachesOnly) { public void flush(boolean memoryCachesOnly) {
if (storageManager == null) { if (newDataManager == null) {
if (fileStampService != null) { if (fileStampService != null) {
((StorageOwner)fileStampService.getStampStorage()).flush(false); ((StorageOwner)fileStampService.getStampStorage()).flush(false);
} }
assert outputToTargetMapping != null;
outputToTargetMapping.flush(memoryCachesOnly);
assert sourceToFormMap != null;
sourceToFormMap.flush(memoryCachesOnly);
} }
else if (!memoryCachesOnly) { else if (!memoryCachesOnly) {
storageManager.commit(); newDataManager.commit();
} }
allTargetStorages().flush(memoryCachesOnly); allTargetStorages().flush(memoryCachesOnly);
myOutputToTargetRegistry.flush(memoryCachesOnly);
if (sourceToFormMap != null) {
sourceToFormMap.flush(memoryCachesOnly);
}
Mappings mappings = myMappings; Mappings mappings = myMappings;
if (mappings != null) { if (mappings != null) {
@@ -402,18 +411,12 @@ public final class BuildDataManager {
buildTargetToSourceToOutputMapping.clear(); buildTargetToSourceToOutputMapping.clear();
} }
if (storageManager != null) { if (newDataManager == null) {
targetToMapManager.clear(); flushOldStorages();
try {
storageManager.close();
}
catch (Throwable e) {
LOG.error(e);
}
} }
else if (fileStampService != null) { else {
try { try {
fileStampService.close(); newDataManager.close();
} }
catch (Throwable e) { catch (Throwable e) {
LOG.error(e); LOG.error(e);
@@ -421,47 +424,62 @@ public final class BuildDataManager {
} }
} }
finally { finally {
try { Mappings mappings = myMappings;
myOutputToTargetRegistry.close(); if (mappings != null) {
}
finally {
try { try {
if (sourceToFormMap != null) { mappings.close();
synchronized (sourceToFormMap) {
sourceToFormMap.close();
}
}
} }
finally { catch (BuildDataCorruptedException e) {
final Mappings mappings = myMappings; throw e.getCause();
if (mappings != null) { }
try { }
mappings.close();
}
catch (BuildDataCorruptedException e) {
throw e.getCause();
}
}
synchronized (myGraphManagementLock) { synchronized (myGraphManagementLock) {
DependencyGraph depGraph = myDepGraph; DependencyGraph depGraph = myDepGraph;
if (depGraph != null) { if (depGraph != null) {
myDepGraph = null; myDepGraph = null;
try { try {
depGraph.close(); depGraph.close();
} }
catch (BuildDataCorruptedException e) { catch (BuildDataCorruptedException e) {
throw e.getCause(); throw e.getCause();
}
}
} }
} }
} }
} }
} }
private void flushOldStorages() {
if (fileStampService != null) {
try {
fileStampService.close();
}
catch (Throwable e) {
LOG.error(e);
}
}
try {
assert outputToTargetMapping != null;
outputToTargetMapping.close();
}
catch (Throwable e) {
LOG.error(e);
}
try {
assert sourceToFormMap != null;
synchronized (sourceToFormMap) {
sourceToFormMap.close();
}
}
catch (Throwable e) {
LOG.error(e);
}
}
public void closeSourceToOutputStorages(@NotNull BuildTargetChunk chunk) throws IOException { public void closeSourceToOutputStorages(@NotNull BuildTargetChunk chunk) throws IOException {
if (storageManager != null) { if (newDataManager != null) {
for (BuildTarget<?> target : chunk.getTargets()) { for (BuildTarget<?> target : chunk.getTargets()) {
buildTargetToSourceToOutputMapping.remove(target); buildTargetToSourceToOutputMapping.remove(target);
} }
@@ -476,7 +494,7 @@ public final class BuildDataManager {
continue; continue;
} }
StorageOwner delegate = (StorageOwner)sourceToOutputMapping.myDelegate; StorageOwner delegate = sourceToOutputMapping.myDelegate;
try { try {
delegate.close(); delegate.close();
} }
@@ -518,12 +536,12 @@ public final class BuildDataManager {
return myRelativizer; return myRelativizer;
} }
public static File getMappingsRoot(final File dataStorageRoot) { public static @NotNull Path getMappingsRoot(@NotNull Path dataStorageRoot) {
return getMappingsRoot(dataStorageRoot, JavaBuilderUtil.isDepGraphEnabled()); return getMappingsRoot(dataStorageRoot, JavaBuilderUtil.isDepGraphEnabled());
} }
private static File getMappingsRoot(final File dataStorageRoot, boolean forDepGraph) { private static Path getMappingsRoot(@NotNull Path dataStorageRoot, boolean forDepGraph) {
return new File(dataStorageRoot, forDepGraph? MAPPINGS_STORAGE + "-graph" : MAPPINGS_STORAGE); return dataStorageRoot.resolve(forDepGraph? MAPPINGS_STORAGE + "-graph" : MAPPINGS_STORAGE);
} }
private static void wipeStorage(@NotNull Path root, @Nullable StorageOwner storage) { private static void wipeStorage(@NotNull Path root, @Nullable StorageOwner storage) {
@@ -587,18 +605,48 @@ public final class BuildDataManager {
myRelativizer.reportUnhandledPaths(); myRelativizer.reportUnhandledPaths();
} }
final class SourceToOutputMappingWrapper implements SourceToOutputMapping, Supplier<@Nullable StorageOwner> { private @NotNull StorageOwner allTargetStorages(@NotNull Consumer<Future<?>> asyncTaskCollector) {
private final SourceToOutputMapping myDelegate; return new CompositeStorageOwner() {
private final int myBuildTargetId; @Override
public void clean() throws IOException {
try {
close();
}
finally {
asyncTaskCollector.accept(FileUtil.asyncDelete(myDataPaths.getTargetsDataRoot()));
}
}
SourceToOutputMappingWrapper(SourceToOutputMapping delegate, int buildTargetId) { @Override
protected Iterable<? extends StorageOwner> getChildStorages() {
return () -> {
return Stream.concat(
myTargetStorages.values().stream(),
buildTargetToSourceToOutputMapping.values().stream()
.map(wrapper -> {
SourceToOutputMapping delegate = wrapper.myDelegate;
return delegate == null ? null : (StorageOwner)delegate;
})
.filter(o -> o != null)
).iterator();
};
}
};
}
private @NotNull StorageOwner allTargetStorages() {
return allTargetStorages(f -> {});
}
private static final class SourceToOutputMappingWrapper implements SourceToOutputMapping {
private final SourceToOutputMappingImpl myDelegate;
private final int myBuildTargetId;
private final OutputToTargetRegistry outputToTargetMapping;
SourceToOutputMappingWrapper(SourceToOutputMappingImpl delegate, int buildTargetId, OutputToTargetRegistry outputToTargetMapping) {
myDelegate = delegate; myDelegate = delegate;
myBuildTargetId = buildTargetId; myBuildTargetId = buildTargetId;
} this.outputToTargetMapping = outputToTargetMapping;
@Override
public @Nullable StorageOwner get() {
return myDelegate instanceof StorageOwner? (StorageOwner)myDelegate : null;
} }
@Override @Override
@@ -607,7 +655,7 @@ public final class BuildDataManager {
myDelegate.setOutputs(srcPath, outputs); myDelegate.setOutputs(srcPath, outputs);
} }
finally { finally {
myOutputToTargetRegistry.addMapping(outputs, myBuildTargetId); outputToTargetMapping.addMappings(outputs, myBuildTargetId);
} }
} }
@@ -617,7 +665,7 @@ public final class BuildDataManager {
myDelegate.setOutput(srcPath, outputPath); myDelegate.setOutput(srcPath, outputPath);
} }
finally { finally {
myOutputToTargetRegistry.addMapping(outputPath, myBuildTargetId); outputToTargetMapping.addMapping(outputPath, myBuildTargetId);
} }
} }
@@ -627,7 +675,7 @@ public final class BuildDataManager {
myDelegate.appendOutput(srcPath, outputPath); myDelegate.appendOutput(srcPath, outputPath);
} }
finally { finally {
myOutputToTargetRegistry.addMapping(outputPath, myBuildTargetId); outputToTargetMapping.addMapping(outputPath, myBuildTargetId);
} }
} }
@@ -657,39 +705,6 @@ public final class BuildDataManager {
} }
} }
private @NotNull StorageOwner allTargetStorages() {
return allTargetStorages(f -> {});
}
private @NotNull StorageOwner allTargetStorages(@NotNull Consumer<Future<?>> asyncTaskCollector) {
return new CompositeStorageOwner() {
@Override
public void clean() throws IOException {
try {
close();
}
finally {
asyncTaskCollector.accept(FileUtil.asyncDelete(myDataPaths.getTargetsDataRoot()));
}
}
@Override
protected Iterable<? extends StorageOwner> getChildStorages() {
return () -> {
return Stream.concat(
myTargetStorages.values().stream(),
buildTargetToSourceToOutputMapping.values().stream()
.map(wrapper -> {
SourceToOutputMapping delegate = wrapper.myDelegate;
return delegate instanceof StorageOwner ? (StorageOwner)delegate : null;
})
.filter(o -> o != null)
).iterator();
};
}
};
}
private static DependencyGraph asSynchronizedGraph(DependencyGraph graph) { private static DependencyGraph asSynchronizedGraph(DependencyGraph graph) {
//noinspection IOResourceOpenedButNotSafelyClosed //noinspection IOResourceOpenedButNotSafelyClosed
DependencyGraph delegate = new LoggingDependencyGraph(graph, msg -> LOG.info(msg)); DependencyGraph delegate = new LoggingDependencyGraph(graph, msg -> LOG.info(msg));

View File

@@ -0,0 +1,74 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.incremental.storage
import com.intellij.util.concurrency.SynchronizedClearableLazy
import org.jetbrains.jps.builders.BuildTarget
import org.jetbrains.jps.builders.storage.SourceToOutputMapping
import org.jetbrains.jps.incremental.relativizer.PathRelativizerService
import java.util.concurrent.ConcurrentHashMap
internal class ExperimentalBuildDataManager(
private val storageManager: StorageManager,
private val relativizer: PathRelativizerService,
) {
// only for new experimental storage
private val targetToMapManager = ConcurrentHashMap<BuildTarget<*>, PerTargetMapManager>()
/**
* A map not scoped to a target is problematic because we cannot transfer built target bytecode with JPS build data.
* Normally, a source file in a module isn't expected to compile into multiple output directories.
* We can address this later, but for now, let's support it as the old storage did.
*/
private val outputToTargetMapping = SynchronizedClearableLazy {
ExperimentalOutputToTargetMapping(storageManager)
}
fun clearCache() {
storageManager.clearCache()
}
fun removeStaleTarget(targetId: String, targetTypeId: String) {
storageManager.removeMaps(targetId, targetTypeId)
outputToTargetMapping.value.removeTarget(targetId, targetTypeId)
}
private fun getPerTargetMapManager(target: BuildTarget<*>): PerTargetMapManager {
return targetToMapManager.computeIfAbsent(target) {
PerTargetMapManager(storageManager, relativizer, it, outputToTargetMapping)
}
}
fun getFileStampStorage(target: BuildTarget<*>): StampsStorage<*> {
return getPerTargetMapManager(target).stamp
}
fun getSourceToOutputMapping(target: BuildTarget<*>): SourceToOutputMapping {
return getPerTargetMapManager(target).sourceToOutputMapping
}
fun getOutputToTargetMapping(): OutputToTargetMapping = outputToTargetMapping.value
fun getSourceToForm(target: BuildTarget<*>): ExperimentalOneToManyPathMapping {
return getPerTargetMapManager(target).sourceToForm
}
fun closeTargetMaps(target: BuildTarget<*>) {
targetToMapManager.remove(target)
}
fun removeAllMaps() {
outputToTargetMapping.drop()
targetToMapManager.clear()
storageManager.clean()
}
fun commit() {
storageManager.commit()
}
fun close() {
outputToTargetMapping.drop()
targetToMapManager.clear()
storageManager.close()
}
}

View File

@@ -10,7 +10,7 @@ import org.jetbrains.jps.incremental.storage.dataTypes.stringTo128BitHash
@ApiStatus.Internal @ApiStatus.Internal
open class ExperimentalOneToManyPathMapping( open class ExperimentalOneToManyPathMapping(
@JvmField protected val mapHandle: MapHandle<LongArray, Array<String>>, @JvmField protected val mapHandle: MapHandle<LongArray, Array<String>>,
@JvmField protected val relativizer: PathRelativizerService, @JvmField internal val relativizer: PathRelativizerService,
private val valueOffset: Int = 0, private val valueOffset: Int = 0,
) : OneToManyPathMapping { ) : OneToManyPathMapping {
protected fun getKey(path: String): LongArray = stringTo128BitHash(relativizer.toRelative(path)) protected fun getKey(path: String): LongArray = stringTo128BitHash(relativizer.toRelative(path))
@@ -22,23 +22,31 @@ open class ExperimentalOneToManyPathMapping(
return Array<String>(list.size - valueOffset) { relativizer.toFull(list.get(it + valueOffset)) }.asList() return Array<String>(list.size - valueOffset) { relativizer.toFull(list.get(it + valueOffset)) }.asList()
} }
final override fun setOutputs(path: String, outPaths: List<String>) { protected fun normalizeOutputPaths(outPaths: List<String>, relativeSourcePath: String?): Array<String>? {
return when {
outPaths.isEmpty() -> null
relativeSourcePath != null -> {
Array(outPaths.size + 1) {
if (it == 0) relativeSourcePath else relativizer.toRelative(outPaths.get(it - 1))
}
}
else -> {
Array(outPaths.size) {
relativizer.toRelative(outPaths.get(it))
}
}
}
}
override fun setOutputs(path: String, outPaths: List<String>) {
val relativeSourcePath = relativizer.toRelative(path) val relativeSourcePath = relativizer.toRelative(path)
val key = stringTo128BitHash(relativeSourcePath) val key = stringTo128BitHash(relativeSourcePath)
if (outPaths.isEmpty()) { val normalizeOutputPaths = normalizeOutputPaths(outPaths, null)
if (normalizeOutputPaths == null) {
mapHandle.map.remove(key) mapHandle.map.remove(key)
} }
else if (valueOffset == 1) {
val listWithRelativePaths = Array(outPaths.size + 1) {
if (it == 0) relativeSourcePath else relativizer.toRelative(outPaths.get(it - 1))
}
mapHandle.map.put(key, listWithRelativePaths)
}
else { else {
val listWithRelativePaths = Array(outPaths.size) { mapHandle.map.put(key, normalizeOutputPaths)
relativizer.toRelative(outPaths.get(it))
}
mapHandle.map.put(key, listWithRelativePaths)
} }
} }

View File

@@ -0,0 +1,163 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.incremental.storage
import com.dynatrace.hash4j.hashing.Hashing
import org.h2.mvstore.MVMap.Decision
import org.h2.mvstore.MVMap.DecisionMaker
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.jps.builders.storage.SourceToOutputMapping
import org.jetbrains.jps.incremental.storage.dataTypes.LongListKeyDataType
import org.jetbrains.jps.incremental.storage.dataTypes.LongPairKeyDataType
import org.jetbrains.jps.incremental.storage.dataTypes.stringTo128BitHash
@ApiStatus.Internal
class ExperimentalOutputToTargetMapping(
storageManager: StorageManager,
) : OutputToTargetMapping {
private val mapHandle = storageManager.openMap("out-to-target-v1", LongPairKeyDataType, LongListKeyDataType)
override fun removeTargetAndGetSafeToDeleteOutputs(
outputPaths: Collection<String>,
currentTargetId: Int,
srcToOut: SourceToOutputMapping,
): Collection<String> {
val size = outputPaths.size
if (size == 0) {
return emptyList()
}
srcToOut as ExperimentalSourceToOutputMapping
val decisionMaker = LongListRemoveItemDecisionMaker(srcToOut.targetHashId)
val relativizer = srcToOut.relativizer
val result = ArrayList<String>(size)
for (outPath in outputPaths) {
val key = stringTo128BitHash(relativizer.toRelative(outPath))
mapHandle.map.operate(key, null, decisionMaker)
if (!decisionMaker.outStillUsed) {
result.add(outPath)
}
}
return result
}
override fun removeMappings(outputPaths: Collection<String>, buildTargetId: Int, srcToOut: SourceToOutputMapping) {
srcToOut as ExperimentalSourceToOutputMapping
val relativizer = srcToOut.relativizer
val decisionMaker = LongListRemoveItemDecisionMaker(srcToOut.targetHashId)
for (outPath in outputPaths) {
val key = stringTo128BitHash(relativizer.toRelative(outPath))
mapHandle.map.operate(key, null, decisionMaker)
}
}
fun addMappings(normalizeOutputPaths: Array<String>, targetHashId: Long) {
val decisionMaker = LongListAddItemDecisionMaker(targetHashId)
for (outPath in normalizeOutputPaths) {
mapHandle.map.operate(stringTo128BitHash(outPath), null, decisionMaker)
}
}
fun addMapping(normalizeOutputPath: String, targetHashId: Long) {
val decisionMaker = LongListAddItemDecisionMaker(targetHashId)
mapHandle.map.operate(stringTo128BitHash(normalizeOutputPath), null, decisionMaker)
}
fun removeTarget(targetId: String, targetTypeId: String) {
val map = mapHandle.map
val iterator = map.cursor(null)
val decisionMaker = LongListRemoveItemDecisionMaker(targetToHash(targetId, targetTypeId))
while (iterator.hasNext()) {
mapHandle.map.operate(iterator.next(), null, decisionMaker)
}
}
}
private class LongListAddItemDecisionMaker(private val toAdd: Long) : DecisionMaker<LongArray>() {
override fun decide(existingValue: LongArray?, providedValue: LongArray?): Decision {
return when {
existingValue == null || existingValue.isEmpty() -> Decision.PUT
existingValue.contains(toAdd) -> Decision.ABORT
else -> Decision.PUT
}
}
// it is called when lock is obtained, so, we can assume that we will not lose a new value if appendOutput is called in parallel
override fun <T : LongArray?> selectValue(existingValue: T?, ignore: T?): T? {
if (existingValue == null || existingValue.isEmpty()) {
@Suppress("UNCHECKED_CAST")
return longArrayOf(toAdd) as T
}
else {
// we checked `contains` in `decide`
@Suppress("UNCHECKED_CAST")
return addElementToEnd(existingValue, toAdd) as T?
}
}
}
private class LongListRemoveItemDecisionMaker(private val toRemove: Long) : DecisionMaker<LongArray>() {
private var indexToRemove: Int = -1
@JvmField
var outStillUsed = false
override fun reset() {
indexToRemove = -1
outStillUsed = false
}
override fun decide(existingValue: LongArray?, ignore: LongArray?): Decision {
return when {
existingValue == null -> Decision.ABORT
// empty value list is not normal, recover - just delete record
existingValue.isEmpty() -> Decision.REMOVE
else -> {
indexToRemove = existingValue.indexOf(toRemove)
when {
indexToRemove == -1 -> {
outStillUsed = true
Decision.ABORT
}
existingValue.size == 1 -> Decision.REMOVE
else -> {
outStillUsed = true
Decision.PUT
}
}
}
}
}
// it is called when lock is obtained, so, we can assume that we will not lose a new value if appendOutput is called in parallel
override fun <T : LongArray?> selectValue(existingValue: T, providedValue: T?): T {
assert(indexToRemove != -1)
@Suppress("UNCHECKED_CAST")
return removeElementAtIndex(existingValue!!, indexToRemove) as T
}
}
private fun removeElementAtIndex(old: LongArray, index: Int): LongArray {
val newSize = old.size - 1
val result = LongArray(newSize)
System.arraycopy(old, 0, result, 0, index)
if (index < newSize) {
System.arraycopy(old, index + 1, result, index, newSize - index)
}
return result
}
private fun addElementToEnd(old: LongArray, element: Long): LongArray {
val result = LongArray(old.size + 1)
System.arraycopy(old, 0, result, 0, old.size)
result[old.size] = element
return result
}
internal fun targetToHash(targetId: String, targetTypeId: String): Long {
val b1 = targetId.toByteArray()
val b2 = targetTypeId.toByteArray()
return Hashing.xxh3_64().hashStream()
.putByteArray(b1)
.putByteArray(b2)
.asLong
}

View File

@@ -7,7 +7,6 @@ import org.h2.mvstore.MVMap.DecisionMaker
import org.jetbrains.annotations.ApiStatus.Internal import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.jetbrains.annotations.VisibleForTesting import org.jetbrains.annotations.VisibleForTesting
import org.jetbrains.jps.builders.BuildTarget
import org.jetbrains.jps.builders.storage.SourceToOutputMapping import org.jetbrains.jps.builders.storage.SourceToOutputMapping
import org.jetbrains.jps.incremental.relativizer.PathRelativizerService import org.jetbrains.jps.incremental.relativizer.PathRelativizerService
import org.jetbrains.jps.incremental.storage.dataTypes.LongPairKeyDataType import org.jetbrains.jps.incremental.storage.dataTypes.LongPairKeyDataType
@@ -18,6 +17,8 @@ import org.jetbrains.jps.incremental.storage.dataTypes.stringTo128BitHash
class ExperimentalSourceToOutputMapping private constructor( class ExperimentalSourceToOutputMapping private constructor(
mapHandle: MapHandle<LongArray, Array<String>>, mapHandle: MapHandle<LongArray, Array<String>>,
relativizer: PathRelativizerService, relativizer: PathRelativizerService,
private val outputToTargetMapping: ExperimentalOutputToTargetMapping?,
@JvmField internal val targetHashId: Long,
) : SourceToOutputMapping, ExperimentalOneToManyPathMapping(mapHandle = mapHandle, relativizer = relativizer, valueOffset = 1) { ) : SourceToOutputMapping, ExperimentalOneToManyPathMapping(mapHandle = mapHandle, relativizer = relativizer, valueOffset = 1) {
companion object { companion object {
// we have a lot of targets - reduce GC and reuse map builder // we have a lot of targets - reduce GC and reuse map builder
@@ -26,19 +27,6 @@ class ExperimentalSourceToOutputMapping private constructor(
it.setValueType(StringListDataType) it.setValueType(StringListDataType)
} }
fun createSourceToOutputMap(
storageManager: StorageManager,
relativizer: PathRelativizerService,
target: BuildTarget<*>,
): ExperimentalSourceToOutputMapping {
return createSourceToOutputMap(
storageManager = storageManager,
relativizer = relativizer,
targetId = target.id,
targetTypeId = target.targetType.typeId,
)
}
@VisibleForTesting @VisibleForTesting
@Internal @Internal
fun createSourceToOutputMap( fun createSourceToOutputMap(
@@ -46,6 +34,7 @@ class ExperimentalSourceToOutputMapping private constructor(
relativizer: PathRelativizerService, relativizer: PathRelativizerService,
targetId: String, targetId: String,
targetTypeId: String, targetTypeId: String,
outputToTargetMapping: ExperimentalOutputToTargetMapping?,
): ExperimentalSourceToOutputMapping { ): ExperimentalSourceToOutputMapping {
// we can use composite key and sort by target id, but as we compile targets in parallel: // we can use composite key and sort by target id, but as we compile targets in parallel:
// * avoid blocking - in-memory lock per map root, // * avoid blocking - in-memory lock per map root,
@@ -54,32 +43,39 @@ class ExperimentalSourceToOutputMapping private constructor(
return ExperimentalSourceToOutputMapping( return ExperimentalSourceToOutputMapping(
mapHandle = storageManager.openMap(mapName, mapBuilder), mapHandle = storageManager.openMap(mapName, mapBuilder),
relativizer = relativizer, relativizer = relativizer,
outputToTargetMapping = outputToTargetMapping,
targetHashId = targetToHash(targetId, targetTypeId),
) )
} }
} }
//override fun getKey(sourcePath: String): LongArray { override fun setOutputs(path: String, outPaths: List<String>) {
// val stringKey = relativizer.toRelative(sourcePath).toByteArray() val relativeSourcePath = super.relativizer.toRelative(path)
// val key = stringTo128BitHash(relativeSourcePath)
// // We should sort by target id for data locality - val normalizeOutputPaths = super.normalizeOutputPaths(outPaths, relativeSourcePath)
// // the high integer (high) is shifted to the higher 32 bits of the long value, if (normalizeOutputPaths == null) {
// // while the low integer (low) occupies the lower 32 bits. mapHandle.map.remove(key)
// // When we do compare two long values, the higher 32 bits (which contain the high integer) dictate the order. }
// val low = Hashing.komihash5_0().hashBytesToInt(stringKey) else {
// val high = targetId mapHandle.map.put(key, normalizeOutputPaths)
// return longArrayOf((high.toLong() shl 32) or (low.toLong() and 0xFFFFFFFFL), Hashing.xxh3_64().hashBytesToLong(stringKey)) outputToTargetMapping?.addMappings(normalizeOutputPaths, targetHashId)
//} }
}
override fun setOutput(sourcePath: String, outputPath: String) { override fun setOutput(sourcePath: String, outputPath: String) {
val relativeSourcePath = relativizer.toRelative(sourcePath) val relativeSourcePath = relativizer.toRelative(sourcePath)
mapHandle.map.put(stringTo128BitHash(relativeSourcePath), arrayOf(relativeSourcePath, relativizer.toRelative(outputPath))) val relativeOutputPath = relativizer.toRelative(outputPath)
mapHandle.map.put(stringTo128BitHash(relativeSourcePath), arrayOf(relativeSourcePath, relativeOutputPath))
outputToTargetMapping?.addMapping(relativeOutputPath, targetHashId)
} }
override fun appendOutput(sourcePath: String, outputPath: String) { override fun appendOutput(sourcePath: String, outputPath: String) {
val relativeSourcePath = relativizer.toRelative(sourcePath) val relativeSourcePath = relativizer.toRelative(sourcePath)
val relativeOutputPath = relativizer.toRelative(outputPath)
mapHandle.map.operate(stringTo128BitHash(relativeSourcePath), mapHandle.map.operate(stringTo128BitHash(relativeSourcePath),
null, null,
AddItemDecisionMaker(sourcePath = relativeSourcePath, toAdd = relativizer.toRelative(outputPath))) AddItemDecisionMaker(sourcePath = relativeSourcePath, toAdd = relativeOutputPath))
outputToTargetMapping?.addMapping(relativeOutputPath, targetHashId)
} }
override fun removeOutput(sourcePath: String, outputPath: String) { override fun removeOutput(sourcePath: String, outputPath: String) {
@@ -157,6 +153,10 @@ private class AddItemDecisionMaker(private val sourcePath: String, private val t
private class RemoveItemDecisionMaker(private val toRemove: String) : DecisionMaker<Array<String>>() { private class RemoveItemDecisionMaker(private val toRemove: String) : DecisionMaker<Array<String>>() {
private var indexToRemove: Int = -1 private var indexToRemove: Int = -1
override fun reset() {
indexToRemove = -1
}
override fun decide(existingValue: Array<String>?, ignore: Array<String>?): Decision { override fun decide(existingValue: Array<String>?, ignore: Array<String>?): Decision {
return when { return when {
existingValue == null -> Decision.ABORT existingValue == null -> Decision.ABORT
@@ -197,8 +197,8 @@ private fun removeElementAtIndex(old: Array<String>, index: Int): Array<String?>
} }
private fun addElementToEnd(old: Array<String>, element: String): Array<String?> { private fun addElementToEnd(old: Array<String>, element: String): Array<String?> {
val result = arrayOfNulls<String>(old.size + 1) val result = arrayOfNulls<String>(old.size + 1)
System.arraycopy(old, 0, result, 0, old.size) System.arraycopy(old, 0, result, 0, old.size)
result[old.size] = element result[old.size] = element
return result return result
} }

View File

@@ -2,6 +2,7 @@
package org.jetbrains.jps.incremental.storage package org.jetbrains.jps.incremental.storage
import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.ApiStatus
import org.jetbrains.jps.builders.storage.SourceToOutputMapping
import java.io.IOException import java.io.IOException
internal interface OneToManyPathMapping { internal interface OneToManyPathMapping {
@@ -19,4 +20,17 @@ internal interface OneToManyPathMapping {
interface SourceToOutputMappingCursor : Iterator<String> { interface SourceToOutputMappingCursor : Iterator<String> {
/** [next] must be called beforehand */ /** [next] must be called beforehand */
val outputPaths: Array<String> val outputPaths: Array<String>
}
@ApiStatus.Internal
interface OutputToTargetMapping {
@Throws(IOException::class)
fun removeTargetAndGetSafeToDeleteOutputs(
outputPaths: Collection<String>,
currentTargetId: Int,
srcToOut: SourceToOutputMapping,
): Collection<String>
@Throws(IOException::class)
fun removeMappings(outputPaths: Collection<String>, buildTargetId: Int, srcToOut: SourceToOutputMapping)
} }

View File

@@ -10,7 +10,9 @@ import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.ints.IntSets; import it.unimi.dsi.fastutil.ints.IntSets;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.builders.storage.SourceToOutputMapping;
import org.jetbrains.jps.incremental.relativizer.PathRelativizerService; import org.jetbrains.jps.incremental.relativizer.PathRelativizerService;
import java.io.DataInput; import java.io.DataInput;
@@ -21,10 +23,10 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
public final class OutputToTargetRegistry extends AbstractStateStorage<Integer, IntSet> { @ApiStatus.Internal
public final class OutputToTargetRegistry extends AbstractStateStorage<Integer, IntSet> implements OutputToTargetMapping {
private static final DataExternalizer<IntSet> DATA_EXTERNALIZER = new DataExternalizer<>() { private static final DataExternalizer<IntSet> DATA_EXTERNALIZER = new DataExternalizer<>() {
@Override @Override
public void save(@NotNull DataOutput out, IntSet value) throws IOException { public void save(@NotNull DataOutput out, IntSet value) throws IOException {
@@ -56,27 +58,25 @@ public final class OutputToTargetRegistry extends AbstractStateStorage<Integer,
appendData(pathHashCode(outputPath), IntSets.singleton(buildTargetId)); appendData(pathHashCode(outputPath), IntSets.singleton(buildTargetId));
} }
void addMapping(@NotNull Collection<String> outputPaths, int buildTargetId) throws IOException { void addMappings(@NotNull Collection<String> outputPaths, int buildTargetId) throws IOException {
IntSet set = IntSets.singleton(buildTargetId); IntSet set = IntSets.singleton(buildTargetId);
for (String outputPath : outputPaths) { for (String outputPath : outputPaths) {
appendData(pathHashCode(outputPath), set); appendData(pathHashCode(outputPath), set);
} }
} }
public void removeMapping(String outputPath, int buildTargetId) throws IOException { @Override
removeMapping(Collections.singleton(outputPath), buildTargetId); public void removeMappings(@NotNull Collection<String> outputPaths, int buildTargetId, @NotNull SourceToOutputMapping srcToOut) throws IOException {
}
public void removeMapping(Collection<String> outputPaths, int buildTargetId) throws IOException {
if (outputPaths.isEmpty()) { if (outputPaths.isEmpty()) {
return; return;
} }
for (String outputPath : outputPaths) { for (String outputPath : outputPaths) {
final int key = pathHashCode(outputPath); int key = pathHashCode(outputPath);
synchronized (dataLock) { synchronized (dataLock) {
final IntSet state = getState(key); IntSet state = getState(key);
if (state != null) { if (state != null) {
final boolean removed = state.remove(buildTargetId); boolean removed = state.remove(buildTargetId);
if (state.isEmpty()) { if (state.isEmpty()) {
remove(key); remove(key);
} }
@@ -88,7 +88,10 @@ public final class OutputToTargetRegistry extends AbstractStateStorage<Integer,
} }
} }
public @NotNull Collection<String> getSafeToDeleteOutputs(Collection<String> outputPaths, int currentTargetId) throws IOException { @Override
public @NotNull Collection<String> removeTargetAndGetSafeToDeleteOutputs(Collection<String> outputPaths,
int currentTargetId,
@NotNull SourceToOutputMapping srcToOut) throws IOException {
int size = outputPaths.size(); int size = outputPaths.size();
if (size == 0) { if (size == 0) {
return outputPaths; return outputPaths;
@@ -98,11 +101,22 @@ public final class OutputToTargetRegistry extends AbstractStateStorage<Integer,
for (String outputPath : outputPaths) { for (String outputPath : outputPaths) {
int key = pathHashCode(outputPath); int key = pathHashCode(outputPath);
synchronized (dataLock) { synchronized (dataLock) {
IntSet associatedTargets = getState(key); IntSet state = getState(key);
if (associatedTargets == null || associatedTargets.size() != 1) { boolean isSafeToDelete = false;
continue; if (state == null) {
isSafeToDelete = true;
} }
if (associatedTargets.contains(currentTargetId)) { else {
boolean removed = state.remove(currentTargetId);
if (state.isEmpty()) {
remove(key);
isSafeToDelete = true;
}
else if (removed) {
update(key, state);
}
}
if (isSafeToDelete) {
result.add(outputPath); result.add(outputPath);
} }
} }

View File

@@ -6,13 +6,13 @@ import org.jetbrains.jps.builders.storage.SourceToOutputMapping
import org.jetbrains.jps.incremental.relativizer.PathRelativizerService import org.jetbrains.jps.incremental.relativizer.PathRelativizerService
import org.jetbrains.jps.incremental.storage.dataTypes.LongPairKeyDataType import org.jetbrains.jps.incremental.storage.dataTypes.LongPairKeyDataType
import org.jetbrains.jps.incremental.storage.dataTypes.StringListDataType import org.jetbrains.jps.incremental.storage.dataTypes.StringListDataType
import java.util.function.UnaryOperator import java.util.function.Supplier
internal class PerTargetMapManager( internal class PerTargetMapManager(
storageManager: StorageManager, storageManager: StorageManager,
relativizer: PathRelativizerService, relativizer: PathRelativizerService,
target: BuildTarget<*>, target: BuildTarget<*>,
sourceToOutputMappingWrapper: UnaryOperator<SourceToOutputMapping>, outputToTargetMapping: Supplier<ExperimentalOutputToTargetMapping>,
) { ) {
@JvmField @JvmField
val stamp: StampsStorage<*> = if (ProjectStamps.PORTABLE_CACHES) { val stamp: StampsStorage<*> = if (ProjectStamps.PORTABLE_CACHES) {
@@ -33,11 +33,13 @@ internal class PerTargetMapManager(
} }
val sourceToOutputMapping: SourceToOutputMapping by lazy { val sourceToOutputMapping: SourceToOutputMapping by lazy {
sourceToOutputMappingWrapper.apply(ExperimentalSourceToOutputMapping.createSourceToOutputMap( ExperimentalSourceToOutputMapping.createSourceToOutputMap(
storageManager = storageManager, storageManager = storageManager,
relativizer = relativizer, relativizer = relativizer,
target = target, targetId = target.id,
)) targetTypeId = target.targetType.typeId,
outputToTargetMapping = outputToTargetMapping.get(),
)
} }
val sourceToForm: ExperimentalOneToManyPathMapping by lazy { val sourceToForm: ExperimentalOneToManyPathMapping by lazy {

View File

@@ -19,7 +19,7 @@ private val MV_STORE_CACHE_SIZE_IN_MB = System.getProperty("jps.new.storage.cach
private val LOG = logger<StorageManager>() private val LOG = logger<StorageManager>()
@ApiStatus.Internal @ApiStatus.Internal
class StorageManager(@JvmField val file: Path, private val allowedCompactionTimeOnClose: Int) { class StorageManager constructor(@JvmField val file: Path) {
private val storeValue = SynchronizedClearableLazy { private val storeValue = SynchronizedClearableLazy {
LOG.debug { "Opening storage $file" } LOG.debug { "Opening storage $file" }
createOrResetMvStore(file = file, readOnly = false, logSupplier = { LOG }) createOrResetMvStore(file = file, readOnly = false, logSupplier = { LOG })
@@ -61,8 +61,8 @@ class StorageManager(@JvmField val file: Path, private val allowedCompactionTime
storeValue.valueIfInitialized?.let { storeValue.valueIfInitialized?.let {
storeValue.drop() storeValue.drop()
val isCompactOnClose = System.getProperty("jps.new.storage.compact.on.close", "true").toBoolean() val isCompactOnClose = System.getProperty("jps.new.storage.compact.on.close", "false").toBoolean()
it.close(if (isCompactOnClose) 0 else allowedCompactionTimeOnClose) it.close()
if (isCompactOnClose && Files.exists(file)) { if (isCompactOnClose && Files.exists(file)) {
val time = measureTime { val time = measureTime {
MVStoreTool.compact(file.toString(), false) MVStoreTool.compact(file.toString(), false)
@@ -94,10 +94,10 @@ class StorageManager(@JvmField val file: Path, private val allowedCompactionTime
} }
} }
fun removeMaps(targetId: String, typeId: String) { fun removeMaps(targetId: String, targetTypeId: String) {
val store = storeValue.value val store = storeValue.value
for (mapName in store.mapNames) { for (mapName in store.mapNames) {
if (mapName.startsWith(getMapName(targetId = targetId, targetTypeId = typeId, suffix = ""))) { if (mapName.startsWith(getMapName(targetId = targetId, targetTypeId = targetTypeId, suffix = ""))) {
store.removeMap(mapName) store.removeMap(mapName)
} }
} }
@@ -166,20 +166,29 @@ private fun tryOpenMvStore(file: Path?, readOnly: Boolean, logSupplier: () -> Lo
val store = MVStore.Builder() val store = MVStore.Builder()
.fileName(file?.toAbsolutePath()?.toString()) .fileName(file?.toAbsolutePath()?.toString())
.backgroundExceptionHandler(storeErrorHandler) .backgroundExceptionHandler(storeErrorHandler)
// We do not disable auto-commit as JPS doesn't use Kotlin coroutines, so it's okay to use a separate daemon thread.
// Additionally, we ensure that the write operation will not slow down any tasks,
// as the actual save will be done in a background thread.
// Use an 8MB threshold for auto-commit instead of the default 1MB -
// if writes are performed too often, do not save intermediate B-Tree pages to disk.
.autoCommitBufferSize(8192)
.cacheSize(MV_STORE_CACHE_SIZE_IN_MB) .cacheSize(MV_STORE_CACHE_SIZE_IN_MB)
.let { .let {
if (readOnly) it.readOnly() else it if (readOnly) it.readOnly() else it
} }
// We do not disable auto-commit as JPS doesn't use Kotlin coroutines, so it's okay to use a separate daemon thread.
// Additionally, we ensure that the write operation will not slow down any tasks,
// as the actual save will be done in a background thread.
// Use a 16MB BUFFER for auto-commit instead of the default 1MB -
// if writes are performed too often, do not save intermediate B-Tree pages to disk.
// Or... just disable auto-commit based on the size of unsaved data and save once in 1 minute
.autoCommitBufferSize(0)
.open() .open()
storeErrorHandler.isStoreOpened = true storeErrorHandler.isStoreOpened = true
// versioning isn't required, otherwise the file size will be larger than needed // versioning isn't required, otherwise the file size will be larger than needed
store.setVersionsToKeep(0) store.setVersionsToKeep(0)
// We do not disable auto-commit as JPS doesn't use Kotlin coroutines, so it's okay to use a separate daemon thread.
// Additionally, we ensure that the write operation will not slow down any tasks,
// as the actual save will be done in a background thread.
// Use a 16MB BUFFER for auto-commit instead of the default 1MB -
// if writes are performed too often, do not save intermediate B-Tree pages to disk.
// Or... just disable auto-commit based on the size of unsaved data and save once in 1 minute
store.autoCommitDelay = 60_000
return store return store
} }

View File

@@ -0,0 +1,45 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.incremental.storage.dataTypes
import org.h2.mvstore.DataUtils.readVarInt
import org.h2.mvstore.WriteBuffer
import org.h2.mvstore.type.DataType
import java.nio.ByteBuffer
internal object LongListKeyDataType : DataType<LongArray> {
override fun isMemoryEstimationAllowed() = true
// don't care about non-ASCII strings for memory estimation
override fun getMemory(obj: LongArray): Int = obj.size * Long.SIZE_BYTES
override fun createStorage(size: Int): Array<LongArray?> = arrayOfNulls(size)
override fun write(buff: WriteBuffer, storage: Any, len: Int) {
@Suppress("UNCHECKED_CAST")
for (value in (storage as Array<LongArray>)) {
buff.putVarInt(value.size)
for (item in value) {
buff.putLong(item)
}
}
}
override fun write(buff: WriteBuffer, obj: LongArray) = throw IllegalStateException("Must not be called")
override fun read(buff: ByteBuffer, storage: Any, len: Int) {
@Suppress("UNCHECKED_CAST")
storage as Array<LongArray>
for (i in 0 until len) {
storage[i] = LongArray(readVarInt(buff)) {
buff.getLong()
}
}
}
override fun read(buff: ByteBuffer) = throw IllegalStateException("Must not be called")
override fun binarySearch(key: LongArray, storage: Any, size: Int, initialGuess: Int): Int = throw IllegalStateException("Must not be called")
@Suppress("DuplicatedCode")
override fun compare(a: LongArray, b: LongArray): Int = throw IllegalStateException("Must not be called")
}

View File

@@ -17,15 +17,15 @@ internal object LongPairKeyDataType : DataType<LongArray> {
override fun isMemoryEstimationAllowed() = true override fun isMemoryEstimationAllowed() = true
// don't care about non-ASCII strings for memory estimation // don't care about non-ASCII strings for memory estimation
override fun getMemory(obj: LongArray): Int = 16 override fun getMemory(obj: LongArray): Int = 2 * Long.SIZE_BYTES
override fun createStorage(size: Int): Array<LongArray?> = arrayOfNulls(size) override fun createStorage(size: Int): Array<LongArray?> = arrayOfNulls(size)
override fun write(buff: WriteBuffer, storage: Any, len: Int) { override fun write(buff: WriteBuffer, storage: Any, len: Int) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
for (key in (storage as Array<LongArray>)) { for (value in (storage as Array<LongArray>)) {
buff.putLong(key[0]) buff.putLong(value[0])
buff.putLong(key[1]) buff.putLong(value[1])
} }
} }

View File

@@ -6,7 +6,6 @@ import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.text.StringUtil;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -81,19 +80,24 @@ public final class BuildResult implements MessageHandler {
} }
OutputToTargetRegistry registry = pd.dataManager.getOutputToTargetRegistry(); OutputToTargetRegistry registry = (OutputToTargetRegistry)pd.dataManager.getOutputToTargetMapping();
List<Integer> keys = new IntArrayList(registry.getKeysIterator()); List<Integer> keys = registry.getAllKeys();
Collections.sort(keys); if (keys.size() > 1) {
keys.sort(null);
}
stream.println("Begin Of OutputToTarget"); stream.println("Begin Of OutputToTarget");
for (Integer key : keys) { for (Integer key : keys) {
IntSet targetsIds = registry.getState(key); IntSet targetsIds = registry.getState(key);
if (targetsIds == null) continue; if (targetsIds == null) {
final List<String> targetsNames = new ArrayList<>(); continue;
}
List<String> targetsNames = new ArrayList<>();
targetsIds.forEach(value -> { targetsIds.forEach(value -> {
BuildTarget<?> target = id2Target.get(value); BuildTarget<?> target = id2Target.get(value);
targetsNames.add(target != null ? getTargetIdWithTypeId(target) : "<unknown " + value + ">"); targetsNames.add(target != null ? getTargetIdWithTypeId(target) : "<unknown " + value + ">");
}); });
Collections.sort(targetsNames); targetsNames.sort(null);
stream.println(hashCodeToOutputPath.get(key.intValue()) + " -> " + targetsNames); stream.println(hashCodeToOutputPath.get(key.intValue()) + " -> " + targetsNames);
} }
stream.println("End Of OutputToTarget"); stream.println("End Of OutputToTarget");

View File

@@ -201,8 +201,8 @@ public abstract class JpsBuildTestCase extends UsefulTestCase {
BuildTargetsState targetsState = new BuildTargetsState(dataPaths, myModel, buildRootIndex); BuildTargetsState targetsState = new BuildTargetsState(dataPaths, myModel, buildRootIndex);
PathRelativizerService relativizer = new PathRelativizerService(myModel.getProject()); PathRelativizerService relativizer = new PathRelativizerService(myModel.getProject());
ProjectStamps projectStamps = new ProjectStamps(myDataStorageRoot.toPath(), targetsState); ProjectStamps projectStamps = new ProjectStamps(myDataStorageRoot.toPath(), targetsState);
BuildDataManager dataManager = new BuildDataManager(dataPaths, targetsState, relativizer, projectStamps, null); BuildDataManager dataManager = new BuildDataManager(dataPaths, targetsState, relativizer, null);
return new ProjectDescriptor(myModel, new BuildFSState(true), dataManager, buildLoggingManager, index, return new ProjectDescriptor(myModel, new BuildFSState(true), projectStamps, dataManager, buildLoggingManager, index,
targetIndex, buildRootIndex, ignoredFileIndex); targetIndex, buildRootIndex, ignoredFileIndex);
} }
catch (IOException e) { catch (IOException e) {

View File

@@ -25,7 +25,7 @@ class HashStampStorageFuzzTest {
@BeforeProperty @BeforeProperty
fun setUp() { fun setUp() {
file = Files.createTempFile("mvstore", ".db") file = Files.createTempFile("mvstore", ".db")
storageManager = StorageManager(file!!, 0) storageManager = StorageManager(file!!)
hashStampStorage = HashStampStorage.createSourceToStampMap( hashStampStorage = HashStampStorage.createSourceToStampMap(
storageManager = storageManager, storageManager = storageManager,
relativizer = PathRelativizerService(), relativizer = PathRelativizerService(),

View File

@@ -18,18 +18,21 @@ class SourceToOutputMappingFuzzTest {
} }
private lateinit var mapping: ExperimentalSourceToOutputMapping private lateinit var mapping: ExperimentalSourceToOutputMapping
private lateinit var targetMapping: ExperimentalOutputToTargetMapping
private lateinit var storageManager: StorageManager private lateinit var storageManager: StorageManager
private var file: Path? = null private var file: Path? = null
@BeforeProperty @BeforeProperty
fun setUp() { fun setUp() {
file = Files.createTempFile("mvstore", ".db") file = Files.createTempFile("mvstore", ".db")
storageManager = StorageManager(file!!, 0) storageManager = StorageManager(file!!)
targetMapping = ExperimentalOutputToTargetMapping(storageManager)
mapping = ExperimentalSourceToOutputMapping.createSourceToOutputMap( mapping = ExperimentalSourceToOutputMapping.createSourceToOutputMap(
storageManager = storageManager, storageManager = storageManager,
relativizer = PathRelativizerService(), relativizer = PathRelativizerService(),
targetId = "test-module", targetId = "test-module",
targetTypeId = "java" targetTypeId = "java",
outputToTargetMapping = targetMapping,
) )
} }
@@ -118,6 +121,9 @@ class SourceToOutputMappingFuzzTest {
} }
assertThat(actualMap).isEqualTo(expectedMap) assertThat(actualMap).isEqualTo(expectedMap)
for (outputPaths in actualMap.values) {
assertThat(targetMapping.removeTargetAndGetSafeToDeleteOutputs(outputPaths, -1, mapping)).isEqualTo(outputPaths)
}
} }
private fun checkCursorAndSourceIterator(source: String, outputs: List<String>) { private fun checkCursorAndSourceIterator(source: String, outputs: List<String>) {

View File

@@ -33,19 +33,20 @@ class StorageManagerTest {
@Test @Test
fun staleMap(@TempDir tempDir: Path) { fun staleMap(@TempDir tempDir: Path) {
val file = tempDir.resolve("jps-cache.db") val file = tempDir.resolve("jps-cache.db")
val storageManager = StorageManager(file, 0) val storageManager = StorageManager(file)
try { try {
val mapping = ExperimentalSourceToOutputMapping.createSourceToOutputMap( val mapping = ExperimentalSourceToOutputMapping.createSourceToOutputMap(
storageManager = storageManager, storageManager = storageManager,
relativizer = PathRelativizerService(), relativizer = PathRelativizerService(),
targetId = "test-module", targetId = "test-module",
targetTypeId = "java" targetTypeId = "java",
outputToTargetMapping = null,
) )
mapping.appendOutput("foo/bar/Baz.java", "out/bar/Baz.class") mapping.appendOutput("foo/bar/Baz.java", "out/bar/Baz.class")
assertThat(mapping.getOutputs("foo/bar/Baz.java")).containsExactly("out/bar/Baz.class") assertThat(mapping.getOutputs("foo/bar/Baz.java")).containsExactly("out/bar/Baz.class")
storageManager.removeMaps(targetId = "test-module", typeId = "java") storageManager.removeMaps(targetId = "test-module", targetTypeId = "java")
assertThat(mapping.getOutputs("foo/bar/Baz.java")).isNull() assertThat(mapping.getOutputs("foo/bar/Baz.java")).isNull()
} }
finally { finally {

View File

@@ -420,6 +420,7 @@ f:com.intellij.util.containers.CollectionFactory
- s:createCustomHashingStrategySet(com.intellij.util.containers.HashingStrategy):java.util.Set - s:createCustomHashingStrategySet(com.intellij.util.containers.HashingStrategy):java.util.Set
- s:createFilePathLinkedMap():java.util.Map - s:createFilePathLinkedMap():java.util.Map
- s:createFilePathLinkedSet():java.util.Set - s:createFilePathLinkedSet():java.util.Set
- s:createFilePathLinkedSet(java.util.Set):java.util.Set
- s:createFilePathMap():java.util.Map - s:createFilePathMap():java.util.Map
- s:createFilePathMap(I):java.util.Map - s:createFilePathMap(I):java.util.Map
- s:createFilePathMap(I,Z):java.util.Map - s:createFilePathMap(I,Z):java.util.Map

View File

@@ -279,17 +279,22 @@ public final class CollectionFactory {
public static @NotNull Set<String> createFilePathLinkedSet() { public static @NotNull Set<String> createFilePathLinkedSet() {
return SystemInfoRt.isFileSystemCaseSensitive return SystemInfoRt.isFileSystemCaseSensitive
? new ObjectLinkedOpenHashSet<>() ? new LinkedHashSet<>()
: new ObjectLinkedOpenCustomHashSet<>(FastUtilHashingStrategies.getCaseInsensitiveStringStrategy()); : new ObjectLinkedOpenCustomHashSet<>(FastUtilHashingStrategies.getCaseInsensitiveStringStrategy());
} }
public static @NotNull Set<String> createFilePathLinkedSet(@NotNull Set<String> source) {
return SystemInfoRt.isFileSystemCaseSensitive
? new LinkedHashSet<>(source)
: new ObjectLinkedOpenCustomHashSet<>(source, FastUtilHashingStrategies.getCaseInsensitiveStringStrategy());
}
/** /**
* Create a linked map with key hash strategy according to file system path case sensitivity. * Create a linked map with key hash strategy according to file system path case sensitivity.
*/ */
public static @NotNull <V> Map<String, V> createFilePathLinkedMap() { public static @NotNull <V> Map<String, V> createFilePathLinkedMap() {
//noinspection SSBasedInspection
return SystemInfoRt.isFileSystemCaseSensitive return SystemInfoRt.isFileSystemCaseSensitive
? new Object2ObjectLinkedOpenHashMap<>() ? new LinkedHashMap<>()
: new Object2ObjectLinkedOpenCustomHashMap<>(FastUtilHashingStrategies.getCaseInsensitiveStringStrategy()); : new Object2ObjectLinkedOpenCustomHashMap<>(FastUtilHashingStrategies.getCaseInsensitiveStringStrategy());
} }