Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce freeze in ProjectUpdater #7199

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 94 additions & 79 deletions base/src/com/google/idea/blaze/base/qsync/ProjectUpdater.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@
*/
package com.google.idea.blaze.base.qsync;

import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static java.util.Arrays.stream;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
Expand All @@ -37,7 +33,6 @@
import com.google.idea.blaze.qsync.project.ProjectPath;
import com.google.idea.blaze.qsync.project.ProjectProto;
import com.google.idea.blaze.qsync.project.ProjectProto.LibrarySource;
import com.google.idea.common.util.Transactions;
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider;
import com.intellij.openapi.externalSystem.service.project.ProjectDataManager;
import com.intellij.openapi.module.Module;
Expand All @@ -54,20 +49,30 @@
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.Library.ModifiableModel;
import com.intellij.openapi.vfs.VfsUtil;
import org.jetbrains.jps.model.java.JavaSourceRootProperties;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import org.jetbrains.jps.model.java.JpsJavaExtensionService;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.AbstractMap;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import org.jetbrains.jps.model.java.JavaSourceRootProperties;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import org.jetbrains.jps.model.java.JpsJavaExtensionService;

/** An object that monitors the build graph and applies the changes to the project structure. */
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static java.util.Arrays.stream;

/**
* An object that monitors the build graph and applies the changes to the project structure.
*/
public class ProjectUpdater implements QuerySyncProjectListener {

/** Entry point for instantiating {@link ProjectUpdater}. */
/**
* Entry point for instantiating {@link ProjectUpdater}.
*/
public static class Provider implements QuerySyncProjectListenerProvider {
@Override
public QuerySyncProjectListener createListener(QuerySyncProject querySyncProject) {
Expand Down Expand Up @@ -116,14 +121,16 @@ public void onNewProjectSnapshot(Context<?> context, QuerySyncProjectSnapshot gr

private void updateProjectModel(ProjectProto.Project spec, Context<?> context) {
File imlDirectory = new File(BlazeDataStorage.getProjectDataDir(importSettings), "modules");
Transactions.submitWriteActionTransactionAndWait(
ProjectUpdaterThreadingUtils.performWriteAction(() -> {
for (BlazeQuerySyncPlugin syncPlugin : BlazeQuerySyncPlugin.EP_NAME.getExtensions()) {
syncPlugin.updateProjectSettingsForQuerySync(project, context, projectViewSet);
}
});
ProjectUpdaterThreadingUtils.readWriteAction(
() -> {
IdeModifiableModelsProvider models =
ProjectDataManager.getInstance().createModifiableModelsProvider(project);

for (BlazeQuerySyncPlugin syncPlugin : BlazeQuerySyncPlugin.EP_NAME.getExtensions()) {
syncPlugin.updateProjectSettingsForQuerySync(project, context, projectViewSet);
}
int removedLibCount = removeUnusedLibraries(models, spec.getLibraryList());
if (removedLibCount > 0) {
context.output(PrintOutput.output("Removed " + removedLibCount + " libs"));
Expand All @@ -135,90 +142,98 @@ private void updateProjectModel(ProjectProto.Project spec, Context<?> context) {
}
ImmutableMap<String, Library> libMap = libMapBuilder.buildOrThrow();

for (ProjectProto.Module moduleSpec : spec.getModulesList()) {
Module module =
models.newModule(
imlDirectory.toPath().resolve(moduleSpec.getName() + ".iml").toString(),
mapModuleType(moduleSpec.getType()).getId());
List<AbstractMap.SimpleImmutableEntry<Module, ProjectProto.Module>> modules =
spec.getModulesList().stream().map(moduleSpec -> {
Module module =
models.newModule(
imlDirectory.toPath().resolve(moduleSpec.getName() + ".iml").toString(),
mapModuleType(moduleSpec.getType()).getId());

ModifiableRootModel roots = models.getModifiableRootModel(module);
ImmutableList<OrderEntry> existingLibraryOrderEntries =
stream(roots.getOrderEntries())
.filter(it -> it instanceof LibraryOrderEntry)
.collect(toImmutableList());
for (OrderEntry entry : existingLibraryOrderEntries) {
roots.removeOrderEntry(entry);
}
// TODO: should this be encapsulated in ProjectProto.Module?
roots.inheritSdk();

// TODO instead of removing all content entries and re-adding, we should calculate the
// diff.
for (ContentEntry entry : roots.getContentEntries()) {
roots.removeContentEntry(entry);
}
for (ProjectProto.ContentEntry ceSpec : moduleSpec.getContentEntriesList()) {
ProjectPath projectPath = ProjectPath.create(ceSpec.getRoot());
ModifiableRootModel roots = models.getModifiableRootModel(module);
ImmutableList<OrderEntry> existingLibraryOrderEntries =
stream(roots.getOrderEntries())
.filter(it -> it instanceof LibraryOrderEntry)
.collect(toImmutableList());
for (OrderEntry entry : existingLibraryOrderEntries) {
roots.removeOrderEntry(entry);
}
// TODO: should this be encapsulated in ProjectProto.Module?
roots.inheritSdk();

ContentEntry contentEntry =
roots.addContentEntry(
UrlUtil.pathToUrl(projectPathResolver.resolve(projectPath).toString()));
for (ProjectProto.SourceFolder sfSpec : ceSpec.getSourcesList()) {
ProjectPath sourceFolderProjectPath = ProjectPath.create(sfSpec.getProjectPath());
// TODO instead of removing all content entries and re-adding, we should calculate the
// diff.
for (ContentEntry entry : roots.getContentEntries()) {
roots.removeContentEntry(entry);
}
for (ProjectProto.ContentEntry ceSpec : moduleSpec.getContentEntriesList()) {
ProjectPath projectPath = ProjectPath.create(ceSpec.getRoot());

JavaSourceRootProperties properties =
JpsJavaExtensionService.getInstance()
.createSourceRootProperties(
sfSpec.getPackagePrefix(), sfSpec.getIsGenerated());
JavaSourceRootType rootType =
sfSpec.getIsTest() ? JavaSourceRootType.TEST_SOURCE : JavaSourceRootType.SOURCE;
String url =
UrlUtil.pathToUrl(
projectPathResolver.resolve(sourceFolderProjectPath).toString(),
sourceFolderProjectPath.innerJarPath());
SourceFolder unused = contentEntry.addSourceFolder(url, rootType, properties);
}
for (String exclude : ceSpec.getExcludesList()) {
contentEntry.addExcludeFolder(
UrlUtil.pathToIdeaDirectoryUrl(workspaceRoot.absolutePathFor(exclude)));
}
}
ContentEntry contentEntry =
roots.addContentEntry(
UrlUtil.pathToUrl(projectPathResolver.resolve(projectPath).toString()));
for (ProjectProto.SourceFolder sfSpec : ceSpec.getSourcesList()) {
ProjectPath sourceFolderProjectPath = ProjectPath.create(sfSpec.getProjectPath());

for (String lib : moduleSpec.getLibraryNameList()) {
Library library = libMap.get(lib);
if (library == null) {
throw new IllegalStateException(
"Module refers to library " + lib + " not present in the project spec");
}
LibraryOrderEntry entry = roots.addLibraryEntry(library);
// TODO should this stuff be specified by the Module proto too?
entry.setScope(DependencyScope.COMPILE);
entry.setExported(false);
}
JavaSourceRootProperties properties =
JpsJavaExtensionService.getInstance()
.createSourceRootProperties(
sfSpec.getPackagePrefix(), sfSpec.getIsGenerated());
JavaSourceRootType rootType =
sfSpec.getIsTest() ? JavaSourceRootType.TEST_SOURCE : JavaSourceRootType.SOURCE;
String url =
UrlUtil.pathToUrl(
projectPathResolver.resolve(sourceFolderProjectPath).toString(),
sourceFolderProjectPath.innerJarPath());
SourceFolder unused = contentEntry.addSourceFolder(url, rootType, properties);
}
for (String exclude : ceSpec.getExcludesList()) {
contentEntry.addExcludeFolder(
UrlUtil.pathToIdeaDirectoryUrl(workspaceRoot.absolutePathFor(exclude)));
}
}

WorkspaceLanguageSettings workspaceLanguageSettings =
LanguageSupport.createWorkspaceLanguageSettings(projectViewSet);
for (String lib : moduleSpec.getLibraryNameList()) {
Library library = libMap.get(lib);
if (library == null) {
throw new IllegalStateException(
"Module refers to library " + lib + " not present in the project spec");
}
LibraryOrderEntry entry = roots.addLibraryEntry(library);
// TODO should this stuff be specified by the Module proto too?
entry.setScope(DependencyScope.COMPILE);
entry.setExported(false);
}
return new AbstractMap.SimpleImmutableEntry<>(module, moduleSpec);
}).toList();
return new AbstractMap.SimpleImmutableEntry<>(models, modules);
},
readValue -> {
IdeModifiableModelsProvider models = readValue.getKey();
WorkspaceLanguageSettings workspaceLanguageSettings =
LanguageSupport.createWorkspaceLanguageSettings(projectViewSet);

for (BlazeQuerySyncPlugin syncPlugin : BlazeQuerySyncPlugin.EP_NAME.getExtensions()) {
// TODO update ProjectProto.Module and updateProjectStructure() to allow a more
// suitable
// data type to be passed in here instead of androidResourceDirectories and
// androidSourcePackages
for (BlazeQuerySyncPlugin syncPlugin : BlazeQuerySyncPlugin.EP_NAME.getExtensions()) {
// TODO update ProjectProto.Module and updateProjectStructure() to allow a more
// suitable
// data type to be passed in here instead of androidResourceDirectories and
// androidSourcePackages
for (AbstractMap.SimpleImmutableEntry<Module, ProjectProto.Module> moduleEntry : readValue.getValue()) {
ProjectProto.Module moduleSpec = moduleEntry.getValue();
syncPlugin.updateProjectStructureForQuerySync(
project,
context,
models,
workspaceRoot,
module,
moduleEntry.getKey(),
ImmutableSet.copyOf(moduleSpec.getAndroidResourceDirectoriesList()),
ImmutableSet.<String>builder()
.addAll(moduleSpec.getAndroidSourcePackagesList())
.addAll(moduleSpec.getAndroidCustomPackagesList())
.build(),
workspaceLanguageSettings);
}
models.commit();
}
models.commit();
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2025 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.idea.blaze.base.qsync
sellophane marked this conversation as resolved.
Show resolved Hide resolved

import com.intellij.openapi.application.readAndWriteAction
import com.intellij.openapi.application.writeAction
import com.intellij.openapi.diagnostic.Logger
import kotlinx.coroutines.runBlocking
import java.util.concurrent.Callable
import java.util.function.Consumer

class ProjectUpdaterThreadingUtils {
companion object {
val logger = Logger.getInstance(ProjectUpdaterThreadingUtils::class.java)

@JvmStatic
fun <T> readWriteAction(readPart: Callable<T>, commit: Consumer<T>) {
runBlocking {
readAndWriteAction {
logger.info("Starting read operation")
val ret = readPart.call();
writeAction {
commit.accept(ret)
}
}
}
}

@JvmStatic
fun performWriteAction(action: Runnable) {
runBlocking {
writeAction<Unit> {
action.run()
}
}
}
}
}