/* * Copyright 2019 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.sync.libraries; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.idea.blaze.base.model.BlazeProjectData; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import com.google.idea.blaze.base.projectview.ProjectViewSet; import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.sync.BlazeSyncPlugin; import com.google.idea.blaze.base.sync.SyncListener; import com.google.idea.blaze.base.sync.SyncMode; import com.google.idea.blaze.base.sync.SyncResult; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.google.idea.blaze.base.vcs.VcsSyncListener; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.AdditionalLibraryRootsProvider; import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent; import com.intellij.openapi.vfs.newvfs.events.VFileEvent; import com.intellij.vfs.AsyncVfsEventsPostProcessor; import java.io.File; import java.util.Map; import java.util.Objects; import javax.annotation.Nullable; /** * External library manager that rebuilds {@link BlazeExternalSyntheticLibrary}s during sync, and * updates individual {@link VirtualFile} entries in response to VFS events. */ public class ExternalLibraryManager { private final Project project; private volatile boolean duringBlazeSync; private volatile ImmutableMap< Class<? extends BlazeExternalLibraryProvider>, BlazeExternalSyntheticLibrary> libraries; public static ExternalLibraryManager getInstance(Project project) { return ServiceManager.getService(project, ExternalLibraryManager.class); } ExternalLibraryManager(Project project) { this.project = project; this.duringBlazeSync = false; this.libraries = ImmutableMap.of(); AsyncVfsEventsPostProcessor.getInstance() .addListener( events -> { if (duringBlazeSync || libraries.isEmpty()) { return; } ImmutableList<VirtualFile> deletedFiles = events.stream() .filter(VFileDeleteEvent.class::isInstance) .map(VFileEvent::getFile) .collect(toImmutableList()); if (!deletedFiles.isEmpty()) { libraries.values().forEach(library -> library.removeInvalidFiles(deletedFiles)); } }, project); } @Nullable public BlazeExternalSyntheticLibrary getLibrary( Class<? extends BlazeExternalLibraryProvider> providerClass) { return duringBlazeSync ? null : libraries.get(providerClass); } private void initialize(BlazeProjectData projectData) { this.libraries = AdditionalLibraryRootsProvider.EP_NAME .extensions() .filter(BlazeExternalLibraryProvider.class::isInstance) .map(BlazeExternalLibraryProvider.class::cast) .map( provider -> { ImmutableList<File> files = provider.getLibraryFiles(project, projectData); return !files.isEmpty() ? Maps.immutableEntry( provider.getClass(), new BlazeExternalSyntheticLibrary(provider.getLibraryName(), files)) : null; }) .filter(Objects::nonNull) .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); } /** * Sync listener to prevent external libraries from being accessed during sync to avoid spamming * {@link VirtualFile#isValid()} errors. */ static class StartSyncListener implements SyncListener { @Override public void onSyncStart(Project project, BlazeContext context, SyncMode syncMode) { ExternalLibraryManager.getInstance(project).duringBlazeSync = true; } @Override public void afterSync( Project project, BlazeContext context, SyncMode syncMode, SyncResult syncResult, ImmutableSet<Integer> buildIds) { ExternalLibraryManager manager = ExternalLibraryManager.getInstance(project); if (syncMode == SyncMode.STARTUP) { BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); if (blazeProjectData != null) { manager.initialize(blazeProjectData); } } manager.duringBlazeSync = false; } } /** * Sync plugin to rebuild external libraries during sync to be included in the reindexing * operation. */ static class SyncPlugin implements BlazeSyncPlugin { @Override public void updateProjectStructure( Project project, BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, BlazeProjectData blazeProjectData, @Nullable BlazeProjectData oldBlazeProjectData, ModuleEditor moduleEditor, Module workspaceModule, ModifiableRootModel workspaceModifiableModel) { ExternalLibraryManager manager = ExternalLibraryManager.getInstance(project); manager.initialize(blazeProjectData); manager.duringBlazeSync = false; } } static class VcsListener implements VcsSyncListener { @Override public void onVcsSync(Project project) { ExternalLibraryManager.getInstance(project) .libraries .values() .forEach(BlazeExternalSyntheticLibrary::restoreMissingFiles); } } }