| /* |
| * Copyright 2016 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; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.collect.ImmutableMultimap; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| import com.google.common.util.concurrent.ListenableFuture; |
| import com.google.common.util.concurrent.ListeningExecutorService; |
| import com.google.idea.blaze.base.async.FutureUtil; |
| import com.google.idea.blaze.base.async.executor.BlazeExecutor; |
| import com.google.idea.blaze.base.command.BlazeCommandName; |
| import com.google.idea.blaze.base.command.BlazeFlags; |
| import com.google.idea.blaze.base.command.BlazeInvocationContext; |
| import com.google.idea.blaze.base.command.info.BlazeConfigurationHandler; |
| import com.google.idea.blaze.base.command.info.BlazeInfo; |
| import com.google.idea.blaze.base.command.info.BlazeInfoRunner; |
| import com.google.idea.blaze.base.experiments.ExperimentScope; |
| import com.google.idea.blaze.base.filecache.FileCaches; |
| import com.google.idea.blaze.base.ideinfo.TargetKey; |
| import com.google.idea.blaze.base.ideinfo.TargetMap; |
| import com.google.idea.blaze.base.io.FileAttributeProvider; |
| import com.google.idea.blaze.base.io.VirtualFileSystemProvider; |
| import com.google.idea.blaze.base.model.BlazeLibrary; |
| import com.google.idea.blaze.base.model.BlazeProjectData; |
| import com.google.idea.blaze.base.model.BlazeVersionData; |
| import com.google.idea.blaze.base.model.SyncState; |
| import com.google.idea.blaze.base.model.SyncState.Builder; |
| import com.google.idea.blaze.base.model.primitives.TargetExpression; |
| import com.google.idea.blaze.base.model.primitives.WorkspacePath; |
| import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; |
| import com.google.idea.blaze.base.plugin.BuildSystemVersionChecker; |
| import com.google.idea.blaze.base.prefetch.PrefetchService; |
| import com.google.idea.blaze.base.projectview.ProjectViewManager; |
| import com.google.idea.blaze.base.projectview.ProjectViewSet; |
| import com.google.idea.blaze.base.projectview.ProjectViewVerifier; |
| import com.google.idea.blaze.base.projectview.section.sections.TargetSection; |
| import com.google.idea.blaze.base.scope.BlazeContext; |
| import com.google.idea.blaze.base.scope.Scope; |
| import com.google.idea.blaze.base.scope.output.IssueOutput; |
| import com.google.idea.blaze.base.scope.output.PrintOutput; |
| import com.google.idea.blaze.base.scope.output.StatusOutput; |
| import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope; |
| import com.google.idea.blaze.base.scope.scopes.IdeaLogScope; |
| import com.google.idea.blaze.base.scope.scopes.IssuesScope; |
| import com.google.idea.blaze.base.scope.scopes.NotificationScope; |
| import com.google.idea.blaze.base.scope.scopes.PerformanceWarningScope; |
| import com.google.idea.blaze.base.scope.scopes.ProgressIndicatorScope; |
| import com.google.idea.blaze.base.scope.scopes.TimingScope; |
| import com.google.idea.blaze.base.settings.Blaze; |
| import com.google.idea.blaze.base.settings.BlazeImportSettings; |
| import com.google.idea.blaze.base.settings.BlazeUserSettings; |
| import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode; |
| import com.google.idea.blaze.base.sync.BlazeSyncPlugin.ModuleEditor; |
| import com.google.idea.blaze.base.sync.SyncListener.SyncResult; |
| import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface; |
| import com.google.idea.blaze.base.sync.aspects.BuildResult; |
| import com.google.idea.blaze.base.sync.data.BlazeDataStorage; |
| import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; |
| import com.google.idea.blaze.base.sync.data.BlazeProjectDataManagerImpl; |
| import com.google.idea.blaze.base.sync.libraries.BlazeLibraryCollector; |
| import com.google.idea.blaze.base.sync.libraries.LibraryEditor; |
| import com.google.idea.blaze.base.sync.projectstructure.ContentEntryEditor; |
| import com.google.idea.blaze.base.sync.projectstructure.DirectoryStructure; |
| import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorImpl; |
| import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProvider; |
| import com.google.idea.blaze.base.sync.projectstructure.ModuleFinder; |
| import com.google.idea.blaze.base.sync.projectview.ImportRoots; |
| import com.google.idea.blaze.base.sync.projectview.LanguageSupport; |
| import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings; |
| import com.google.idea.blaze.base.sync.sharding.BlazeBuildTargetSharder; |
| import com.google.idea.blaze.base.sync.sharding.BlazeBuildTargetSharder.ShardedTargetsResult; |
| import com.google.idea.blaze.base.sync.sharding.ShardedTargetList; |
| import com.google.idea.blaze.base.sync.sharding.SuggestBuildShardingNotification; |
| import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; |
| import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoderImpl; |
| import com.google.idea.blaze.base.sync.workspace.WorkingSet; |
| import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; |
| import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl; |
| import com.google.idea.blaze.base.targetmaps.ReverseDependencyMap; |
| import com.google.idea.blaze.base.util.SaveUtil; |
| import com.google.idea.blaze.base.vcs.BlazeVcsHandler; |
| import com.google.idea.sdkcompat.transactions.Transactions; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleType; |
| import com.intellij.openapi.progress.ProcessCanceledException; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.Progressive; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.ContentEntry; |
| import com.intellij.openapi.roots.ModifiableRootModel; |
| import com.intellij.openapi.roots.ex.ProjectRootManagerEx; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.vfs.VirtualFileManager; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| import javax.annotation.Nullable; |
| |
| /** Syncs the project with blaze. */ |
| final class BlazeSyncTask implements Progressive { |
| |
| private static final Logger logger = Logger.getInstance(BlazeSyncTask.class); |
| |
| private final Project project; |
| private final BlazeImportSettings importSettings; |
| private final WorkspaceRoot workspaceRoot; |
| private final BlazeSyncParams syncParams; |
| private final boolean showPerformanceWarnings; |
| |
| BlazeSyncTask( |
| Project project, BlazeImportSettings importSettings, final BlazeSyncParams syncParams) { |
| this.project = project; |
| this.importSettings = importSettings; |
| this.workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings); |
| this.syncParams = syncParams; |
| this.showPerformanceWarnings = BlazeUserSettings.getInstance().getShowPerformanceWarnings(); |
| } |
| |
| @Override |
| public void run(final ProgressIndicator indicator) { |
| Scope.root( |
| (BlazeContext context) -> { |
| context.push(new ExperimentScope()); |
| if (showPerformanceWarnings) { |
| context.push(new PerformanceWarningScope()); |
| } |
| context.push(new ProgressIndicatorScope(indicator)).push(new TimingScope("Sync")); |
| |
| if (!syncParams.backgroundSync) { |
| context |
| .push( |
| new BlazeConsoleScope.Builder(project, indicator) |
| .setPopupBehavior( |
| BlazeUserSettings.getInstance().getShowBlazeConsoleOnSync()) |
| .build()) |
| .push(new IssuesScope(project)) |
| .push(new IdeaLogScope()) |
| .push( |
| new NotificationScope( |
| project, "Sync", "Sync project", "Sync successful", "Sync failed")); |
| } |
| |
| context.output(new StatusOutput("Syncing project...")); |
| syncProject(context); |
| }); |
| } |
| |
| @Nullable |
| private BlazeProjectData getOldProjectData(BlazeContext context) { |
| try { |
| return BlazeProjectDataManagerImpl.getImpl(project).loadProjectRoot(importSettings); |
| } catch (IOException e) { |
| logger.info(e); |
| context.output( |
| new StatusOutput( |
| String.format( |
| "Couldn't load previously cached project data; full sync will be needed"))); |
| return null; |
| } |
| } |
| |
| /** Returns true if sync successfully completed */ |
| @VisibleForTesting |
| boolean syncProject(BlazeContext context) { |
| SyncResult syncResult = SyncResult.FAILURE; |
| SyncMode syncMode = syncParams.syncMode; |
| try { |
| SaveUtil.saveAllFiles(); |
| BlazeProjectData oldBlazeProjectData = |
| syncMode != SyncMode.FULL ? getOldProjectData(context) : null; |
| if (oldBlazeProjectData == null) { |
| syncMode = SyncMode.FULL; |
| } |
| |
| onSyncStart(project, context, syncMode); |
| if (syncMode != SyncMode.STARTUP) { |
| syncResult = doSyncProject(context, syncMode, oldBlazeProjectData); |
| } else { |
| syncResult = SyncResult.SUCCESS; |
| } |
| if (syncResult.successful()) { |
| ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet(); |
| BlazeProjectData blazeProjectData = |
| BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); |
| updateInMemoryState(project, context, projectViewSet, blazeProjectData); |
| onSyncComplete(project, context, projectViewSet, blazeProjectData, syncMode, syncResult); |
| } |
| } catch (Throwable e) { |
| Throwable rootCause = e; |
| while (rootCause.getCause() != null) { |
| rootCause = rootCause.getCause(); |
| } |
| if (!(rootCause instanceof ProcessCanceledException)) { |
| logger.error(e); |
| IssueOutput.error("Internal error: " + e.getMessage()).submit(context); |
| } |
| } finally { |
| afterSync(project, context, syncMode, syncResult); |
| } |
| return syncResult == SyncResult.SUCCESS || syncResult == SyncResult.PARTIAL_SUCCESS; |
| } |
| |
| /** @return true if sync successfully completed */ |
| private SyncResult doSyncProject( |
| BlazeContext context, SyncMode syncMode, @Nullable BlazeProjectData oldBlazeProjectData) { |
| long syncStartTime = System.currentTimeMillis(); |
| |
| if (!FileAttributeProvider.getInstance().exists(workspaceRoot.directory())) { |
| IssueOutput.error(String.format("Workspace '%s' doesn't exist.", workspaceRoot.directory())) |
| .submit(context); |
| return SyncResult.FAILURE; |
| } |
| |
| BlazeVcsHandler vcsHandler = BlazeVcsHandler.vcsHandlerForProject(project); |
| if (vcsHandler == null) { |
| IssueOutput.error("Could not find a VCS handler").submit(context); |
| return SyncResult.FAILURE; |
| } |
| |
| ListeningExecutorService executor = BlazeExecutor.getInstance().getExecutor(); |
| WorkspacePathResolverAndProjectView workspacePathResolverAndProjectView = |
| computeWorkspacePathResolverAndProjectView(context, vcsHandler, executor); |
| if (workspacePathResolverAndProjectView == null) { |
| return SyncResult.FAILURE; |
| } |
| ProjectViewSet projectViewSet = workspacePathResolverAndProjectView.projectViewSet; |
| |
| ListenableFuture<BlazeInfo> blazeInfoFuture = |
| BlazeInfoRunner.getInstance() |
| .runBlazeInfo( |
| context, |
| importSettings.getBuildSystem(), |
| Blaze.getBuildSystemProvider(project).getSyncBinaryPath(), |
| workspaceRoot, |
| BlazeFlags.blazeFlags( |
| project, projectViewSet, BlazeCommandName.INFO, BlazeInvocationContext.Sync)); |
| |
| ListenableFuture<WorkingSet> workingSetFuture = |
| vcsHandler.getWorkingSet(project, context, workspaceRoot, executor); |
| |
| BlazeInfo blazeInfo = |
| FutureUtil.waitForFuture(context, blazeInfoFuture) |
| .timed(Blaze.buildSystemName(project) + "Info") |
| .withProgressMessage( |
| String.format("Running %s info...", Blaze.buildSystemName(project))) |
| .onError(String.format("Could not run %s info", Blaze.buildSystemName(project))) |
| .run() |
| .result(); |
| if (blazeInfo == null) { |
| return SyncResult.FAILURE; |
| } |
| BlazeVersionData blazeVersionData = |
| BlazeVersionData.build(importSettings.getBuildSystem(), workspaceRoot, blazeInfo); |
| |
| if (!BuildSystemVersionChecker.verifyVersionSupported(context, blazeVersionData)) { |
| return SyncResult.FAILURE; |
| } |
| |
| WorkspacePathResolver workspacePathResolver = |
| workspacePathResolverAndProjectView.workspacePathResolver; |
| ArtifactLocationDecoder artifactLocationDecoder = |
| new ArtifactLocationDecoderImpl(blazeInfo, workspacePathResolver); |
| |
| WorkspaceLanguageSettings workspaceLanguageSettings = |
| LanguageSupport.createWorkspaceLanguageSettings(projectViewSet); |
| |
| for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) { |
| syncPlugin.installSdks(context); |
| } |
| |
| if (!ProjectViewVerifier.verifyProjectView( |
| project, context, workspacePathResolver, projectViewSet, workspaceLanguageSettings)) { |
| return SyncResult.FAILURE; |
| } |
| |
| final BlazeProjectData newBlazeProjectData; |
| |
| WorkingSet workingSet = |
| FutureUtil.waitForFuture(context, workingSetFuture) |
| .timed("WorkingSet") |
| .withProgressMessage("Computing VCS working set...") |
| .onError("Could not compute working set") |
| .run() |
| .result(); |
| if (context.isCancelled()) { |
| return SyncResult.CANCELLED; |
| } |
| if (context.hasErrors()) { |
| return SyncResult.FAILURE; |
| } |
| |
| if (workingSet != null) { |
| printWorkingSet(context, workingSet); |
| } |
| |
| SyncState.Builder syncStateBuilder = new SyncState.Builder(); |
| SyncState previousSyncState = |
| oldBlazeProjectData != null ? oldBlazeProjectData.syncState : null; |
| |
| List<TargetExpression> targets = Lists.newArrayList(); |
| if (syncParams.addProjectViewTargets || oldBlazeProjectData == null) { |
| Collection<TargetExpression> projectViewTargets = projectViewSet.listItems(TargetSection.KEY); |
| if (!projectViewTargets.isEmpty()) { |
| targets.addAll(projectViewTargets); |
| printTargets(context, "project view", projectViewTargets); |
| } |
| } |
| if (syncParams.addWorkingSet && workingSet != null) { |
| Collection<? extends TargetExpression> workingSetTargets = |
| getWorkingSetTargets(projectViewSet, workingSet); |
| if (!workingSetTargets.isEmpty()) { |
| targets.addAll(workingSetTargets); |
| printTargets(context, "working set", workingSetTargets); |
| } |
| } |
| if (!syncParams.targetExpressions.isEmpty()) { |
| targets.addAll(syncParams.targetExpressions); |
| printTargets(context, syncParams.title, syncParams.targetExpressions); |
| } |
| |
| ShardedTargetsResult shardedTargetsResult = |
| BlazeBuildTargetSharder.expandAndShardTargets( |
| project, context, workspaceRoot, projectViewSet, workspacePathResolver, targets); |
| if (shardedTargetsResult.buildResult.status == BuildResult.Status.FATAL_ERROR) { |
| return SyncResult.FAILURE; |
| } |
| ShardedTargetList shardedTargets = shardedTargetsResult.shardedTargets; |
| |
| BlazeConfigurationHandler configHandler = new BlazeConfigurationHandler(blazeInfo); |
| boolean mergeWithOldState = !syncParams.addProjectViewTargets; |
| BlazeIdeInterface.IdeResult ideQueryResult = |
| getIdeQueryResult( |
| project, |
| context, |
| projectViewSet, |
| blazeVersionData, |
| configHandler, |
| shardedTargets, |
| workspaceLanguageSettings, |
| artifactLocationDecoder, |
| syncStateBuilder, |
| previousSyncState, |
| mergeWithOldState); |
| if (context.isCancelled()) { |
| return SyncResult.CANCELLED; |
| } |
| if (ideQueryResult.targetMap == null |
| || ideQueryResult.buildResult.status == BuildResult.Status.FATAL_ERROR) { |
| context.setHasError(); |
| if (ideQueryResult.buildResult.outOfMemory()) { |
| SuggestBuildShardingNotification.syncOutOfMemoryError(project, context); |
| } |
| return SyncResult.FAILURE; |
| } |
| |
| TargetMap targetMap = ideQueryResult.targetMap; |
| BuildResult ideInfoResult = ideQueryResult.buildResult; |
| |
| ListenableFuture<ImmutableMultimap<TargetKey, TargetKey>> reverseDependenciesFuture = |
| BlazeExecutor.getInstance().submit(() -> ReverseDependencyMap.createRdepsMap(targetMap)); |
| |
| BuildResult ideResolveResult = |
| resolveIdeArtifacts( |
| project, |
| context, |
| workspaceRoot, |
| projectViewSet, |
| blazeVersionData, |
| workspaceLanguageSettings, |
| shardedTargets); |
| if (ideResolveResult.status == BuildResult.Status.FATAL_ERROR) { |
| context.setHasError(); |
| if (ideResolveResult.outOfMemory()) { |
| SuggestBuildShardingNotification.syncOutOfMemoryError(project, context); |
| } |
| return SyncResult.FAILURE; |
| } |
| if (context.isCancelled()) { |
| return SyncResult.CANCELLED; |
| } |
| |
| Scope.push( |
| context, |
| (childContext) -> { |
| childContext.push(new TimingScope("UpdateSyncState")); |
| for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) { |
| syncPlugin.updateSyncState( |
| project, |
| childContext, |
| workspaceRoot, |
| projectViewSet, |
| workspaceLanguageSettings, |
| blazeInfo, |
| workingSet, |
| workspacePathResolver, |
| artifactLocationDecoder, |
| targetMap, |
| syncStateBuilder, |
| previousSyncState); |
| } |
| }); |
| |
| ImmutableMultimap<TargetKey, TargetKey> reverseDependencies = |
| FutureUtil.waitForFuture(context, reverseDependenciesFuture) |
| .timed("ReverseDependencies") |
| .onError("Failed to compute reverse dependency map") |
| .run() |
| .result(); |
| if (reverseDependencies == null) { |
| return SyncResult.FAILURE; |
| } |
| |
| newBlazeProjectData = |
| new BlazeProjectData( |
| syncStartTime, |
| targetMap, |
| blazeInfo, |
| blazeVersionData, |
| workspacePathResolver, |
| artifactLocationDecoder, |
| workspaceLanguageSettings, |
| syncStateBuilder.build(), |
| reverseDependencies); |
| |
| FileCaches.onSync(project, context, projectViewSet, newBlazeProjectData, syncMode); |
| ListenableFuture<?> prefetch = |
| PrefetchService.getInstance() |
| .prefetchProjectFiles(project, projectViewSet, newBlazeProjectData); |
| FutureUtil.waitForFuture(context, prefetch) |
| .withProgressMessage("Prefetching files...") |
| .timed("PrefetchFiles") |
| .onError("Prefetch failed") |
| .run(); |
| |
| ListenableFuture<DirectoryStructure> directoryStructureFuture = |
| DirectoryStructure.getRootDirectoryStructure(project, workspaceRoot, projectViewSet); |
| |
| refreshVirtualFileSystem(context, newBlazeProjectData); |
| |
| DirectoryStructure directoryStructure = |
| FutureUtil.waitForFuture(context, directoryStructureFuture) |
| .withProgressMessage("Computing directory structure...") |
| .timed("DirectoryStructure") |
| .onError("Directory structure computation failed") |
| .run() |
| .result(); |
| if (directoryStructure == null) { |
| return SyncResult.FAILURE; |
| } |
| |
| boolean success = |
| updateProject( |
| context, |
| projectViewSet, |
| blazeVersionData, |
| directoryStructure, |
| oldBlazeProjectData, |
| newBlazeProjectData); |
| if (!success) { |
| return SyncResult.FAILURE; |
| } |
| |
| SyncResult syncResult = SyncResult.SUCCESS; |
| |
| if (ideInfoResult.status == BuildResult.Status.BUILD_ERROR |
| || ideResolveResult.status == BuildResult.Status.BUILD_ERROR) { |
| final String errorType = |
| ideInfoResult.status == BuildResult.Status.BUILD_ERROR |
| ? "BUILD file errors" |
| : "compilation errors"; |
| |
| String message = |
| String.format( |
| "Sync was successful, but there were %s. " |
| + "The project may not be fully updated or resolve until fixed. " |
| + "If the errors are from your working set, please uncheck " |
| + "'Blaze > Sync > Expand Sync to Working Set' and try again.", |
| errorType); |
| context.output(PrintOutput.error(message)); |
| IssueOutput.warn(message).submit(context); |
| syncResult = SyncResult.PARTIAL_SUCCESS; |
| } |
| |
| return syncResult; |
| } |
| |
| private static void refreshVirtualFileSystem( |
| BlazeContext context, BlazeProjectData blazeProjectData) { |
| Transactions.submitWriteActionTransactionAndWait( |
| () -> |
| Scope.push( |
| context, |
| (childContext) -> { |
| childContext.push(new TimingScope("RefreshVirtualFileSystem")); |
| for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) { |
| syncPlugin.refreshVirtualFileSystem(blazeProjectData); |
| } |
| })); |
| } |
| |
| static class WorkspacePathResolverAndProjectView { |
| final WorkspacePathResolver workspacePathResolver; |
| final ProjectViewSet projectViewSet; |
| |
| public WorkspacePathResolverAndProjectView( |
| WorkspacePathResolver workspacePathResolver, ProjectViewSet projectViewSet) { |
| this.workspacePathResolver = workspacePathResolver; |
| this.projectViewSet = projectViewSet; |
| } |
| } |
| |
| private WorkspacePathResolverAndProjectView computeWorkspacePathResolverAndProjectView( |
| BlazeContext context, BlazeVcsHandler vcsHandler, ListeningExecutorService executor) { |
| context.output(new StatusOutput("Updating VCS...")); |
| |
| for (int i = 0; i < 3; ++i) { |
| WorkspacePathResolver vcsWorkspacePathResolver = null; |
| BlazeVcsHandler.BlazeVcsSyncHandler vcsSyncHandler = |
| vcsHandler.createSyncHandler(project, workspaceRoot); |
| if (vcsSyncHandler != null) { |
| boolean ok = |
| Scope.push( |
| context, |
| (childContext) -> { |
| childContext.push(new TimingScope("UpdateVcs")); |
| return vcsSyncHandler.update(context, executor); |
| }); |
| if (!ok) { |
| return null; |
| } |
| vcsWorkspacePathResolver = vcsSyncHandler.getWorkspacePathResolver(); |
| } |
| |
| WorkspacePathResolver workspacePathResolver = |
| vcsWorkspacePathResolver != null |
| ? vcsWorkspacePathResolver |
| : new WorkspacePathResolverImpl(workspaceRoot); |
| |
| ProjectViewSet projectViewSet = |
| ProjectViewManager.getInstance(project).reloadProjectView(context, workspacePathResolver); |
| if (projectViewSet == null) { |
| return null; |
| } |
| |
| if (vcsSyncHandler != null) { |
| BlazeVcsHandler.BlazeVcsSyncHandler.ValidationResult validationResult = |
| vcsSyncHandler.validateProjectView(context, projectViewSet); |
| switch (validationResult) { |
| case OK: |
| // Fall-through and return |
| break; |
| case Error: |
| return null; |
| case RestartSync: |
| continue; |
| default: |
| // Cannot happen |
| return null; |
| } |
| } |
| |
| return new WorkspacePathResolverAndProjectView(workspacePathResolver, projectViewSet); |
| } |
| return null; |
| } |
| |
| private void printWorkingSet(BlazeContext context, WorkingSet workingSet) { |
| List<String> messages = Lists.newArrayList(); |
| messages.addAll( |
| workingSet |
| .addedFiles |
| .stream() |
| .map(file -> file.relativePath() + " (added)") |
| .collect(Collectors.toList())); |
| messages.addAll( |
| workingSet |
| .modifiedFiles |
| .stream() |
| .map(file -> file.relativePath() + " (modified)") |
| .collect(Collectors.toList())); |
| Collections.sort(messages); |
| |
| if (messages.isEmpty()) { |
| context.output(PrintOutput.log("Your working set is empty")); |
| return; |
| } |
| int maxFiles = 20; |
| for (String message : Iterables.limit(messages, maxFiles)) { |
| context.output(PrintOutput.log(" " + message)); |
| } |
| if (messages.size() > maxFiles) { |
| context.output(PrintOutput.log(String.format(" (and %d more)", messages.size() - maxFiles))); |
| } |
| context.output(PrintOutput.output("")); |
| } |
| |
| private void printTargets( |
| BlazeContext context, String owner, Collection<? extends TargetExpression> targets) { |
| StringBuilder sb = new StringBuilder("Sync targets from "); |
| sb.append(owner).append(':').append('\n'); |
| for (TargetExpression targetExpression : targets) { |
| sb.append(" ").append(targetExpression).append('\n'); |
| } |
| context.output(PrintOutput.log(sb.toString())); |
| } |
| |
| private Collection<? extends TargetExpression> getWorkingSetTargets( |
| ProjectViewSet projectViewSet, WorkingSet workingSet) { |
| ImportRoots importRoots = |
| ImportRoots.builder(workspaceRoot, importSettings.getBuildSystem()) |
| .add(projectViewSet) |
| .build(); |
| BuildTargetFinder buildTargetFinder = |
| new BuildTargetFinder(project, workspaceRoot, importRoots); |
| |
| Set<TargetExpression> result = Sets.newHashSet(); |
| for (WorkspacePath workspacePath : |
| Iterables.concat(workingSet.addedFiles, workingSet.modifiedFiles)) { |
| File file = workspaceRoot.fileForPath(workspacePath); |
| TargetExpression targetExpression = buildTargetFinder.findTargetForFile(file); |
| if (targetExpression != null) { |
| result.add(targetExpression); |
| } |
| } |
| return result; |
| } |
| |
| private BlazeIdeInterface.IdeResult getIdeQueryResult( |
| Project project, |
| BlazeContext parentContext, |
| ProjectViewSet projectViewSet, |
| BlazeVersionData blazeVersionData, |
| BlazeConfigurationHandler configHandler, |
| ShardedTargetList shardedTargets, |
| WorkspaceLanguageSettings workspaceLanguageSettings, |
| ArtifactLocationDecoder artifactLocationDecoder, |
| Builder syncStateBuilder, |
| @Nullable SyncState previousSyncState, |
| boolean mergeWithOldState) { |
| |
| return Scope.push( |
| parentContext, |
| context -> { |
| context.push(new TimingScope("IdeQuery")); |
| context.output(new StatusOutput("Building IDE info files...")); |
| context.setPropagatesErrors(false); |
| |
| BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance(); |
| return blazeIdeInterface.updateTargetMap( |
| project, |
| context, |
| workspaceRoot, |
| projectViewSet, |
| blazeVersionData, |
| configHandler, |
| shardedTargets, |
| workspaceLanguageSettings, |
| artifactLocationDecoder, |
| syncStateBuilder, |
| previousSyncState, |
| mergeWithOldState); |
| }); |
| } |
| |
| private static BuildResult resolveIdeArtifacts( |
| Project project, |
| BlazeContext parentContext, |
| WorkspaceRoot workspaceRoot, |
| ProjectViewSet projectViewSet, |
| BlazeVersionData blazeVersionData, |
| WorkspaceLanguageSettings workspaceLanguageSettings, |
| ShardedTargetList shardedTargets) { |
| return Scope.push( |
| parentContext, |
| context -> { |
| context.push(new TimingScope(Blaze.buildSystemName(project) + "Build")); |
| context.output(new StatusOutput("Building IDE resolve files...")); |
| |
| // We don't want IDE resolve errors to fail the whole sync |
| context.setPropagatesErrors(false); |
| |
| if (shardedTargets.isEmpty()) { |
| return BuildResult.SUCCESS; |
| } |
| BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance(); |
| return blazeIdeInterface.resolveIdeArtifacts( |
| project, |
| context, |
| workspaceRoot, |
| projectViewSet, |
| blazeVersionData, |
| workspaceLanguageSettings, |
| shardedTargets); |
| }); |
| } |
| |
| private boolean updateProject( |
| BlazeContext parentContext, |
| ProjectViewSet projectViewSet, |
| BlazeVersionData blazeVersionData, |
| DirectoryStructure directoryStructure, |
| @Nullable BlazeProjectData oldBlazeProjectData, |
| BlazeProjectData newBlazeProjectData) { |
| return Scope.push( |
| parentContext, |
| context -> { |
| context.push(new TimingScope("UpdateProjectStructure")); |
| context.output(new StatusOutput("Committing project structure...")); |
| |
| try { |
| Transactions.submitWriteActionTransactionAndWait( |
| () -> |
| ProjectRootManagerEx.getInstanceEx(this.project) |
| .mergeRootsChangesDuring( |
| () -> |
| updateProjectStructure( |
| context, |
| importSettings, |
| projectViewSet, |
| blazeVersionData, |
| directoryStructure, |
| newBlazeProjectData, |
| oldBlazeProjectData))); |
| } catch (ProcessCanceledException e) { |
| context.setCancelled(); |
| throw e; |
| } catch (Throwable e) { |
| IssueOutput.error("Internal error. Error: " + e).submit(context); |
| logger.error(e); |
| return false; |
| } |
| |
| BlazeProjectDataManagerImpl.getImpl(project) |
| .saveProject(importSettings, newBlazeProjectData); |
| return true; |
| }); |
| } |
| |
| private void updateProjectStructure( |
| BlazeContext context, |
| BlazeImportSettings importSettings, |
| ProjectViewSet projectViewSet, |
| BlazeVersionData blazeVersionData, |
| DirectoryStructure directoryStructure, |
| BlazeProjectData newBlazeProjectData, |
| @Nullable BlazeProjectData oldBlazeProjectData) { |
| |
| for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) { |
| syncPlugin.updateProjectSdk( |
| project, context, projectViewSet, blazeVersionData, newBlazeProjectData); |
| } |
| |
| ModuleEditorImpl moduleEditor = |
| ModuleEditorProvider.getInstance().getModuleEditor(project, importSettings); |
| |
| ModuleType workspaceModuleType = null; |
| for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) { |
| workspaceModuleType = |
| syncPlugin.getWorkspaceModuleType( |
| newBlazeProjectData.workspaceLanguageSettings.getWorkspaceType()); |
| if (workspaceModuleType != null) { |
| break; |
| } |
| } |
| if (workspaceModuleType == null) { |
| workspaceModuleType = ModuleType.EMPTY; |
| IssueOutput.warn("Could not set module type for workspace module.").submit(context); |
| } |
| |
| Module workspaceModule = |
| moduleEditor.createModule(BlazeDataStorage.WORKSPACE_MODULE_NAME, workspaceModuleType); |
| ModifiableRootModel workspaceModifiableModel = moduleEditor.editModule(workspaceModule); |
| |
| ContentEntryEditor.createContentEntries( |
| project, |
| workspaceRoot, |
| projectViewSet, |
| newBlazeProjectData, |
| directoryStructure, |
| workspaceModifiableModel); |
| |
| List<BlazeLibrary> libraries = |
| BlazeLibraryCollector.getLibraries(projectViewSet, newBlazeProjectData); |
| LibraryEditor.updateProjectLibraries( |
| project, context, projectViewSet, newBlazeProjectData, libraries); |
| LibraryEditor.configureDependencies(workspaceModifiableModel, libraries); |
| |
| for (BlazeSyncPlugin blazeSyncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) { |
| blazeSyncPlugin.updateProjectStructure( |
| project, |
| context, |
| workspaceRoot, |
| projectViewSet, |
| newBlazeProjectData, |
| oldBlazeProjectData, |
| moduleEditor, |
| workspaceModule, |
| workspaceModifiableModel); |
| } |
| |
| createProjectDataDirectoryModule( |
| moduleEditor, new File(importSettings.getProjectDataDirectory()), workspaceModuleType); |
| |
| moduleEditor.commitWithGc(context); |
| } |
| |
| private void updateInMemoryState( |
| Project project, |
| BlazeContext parentContext, |
| ProjectViewSet projectViewSet, |
| BlazeProjectData blazeProjectData) { |
| Scope.push( |
| parentContext, |
| context -> { |
| context.push(new TimingScope("UpdateInMemoryState")); |
| context.output(new StatusOutput("Updating in-memory state...")); |
| ApplicationManager.getApplication() |
| .runReadAction( |
| () -> { |
| Module workspaceModule = |
| ModuleFinder.getInstance(project) |
| .findModuleByName(BlazeDataStorage.WORKSPACE_MODULE_NAME); |
| for (BlazeSyncPlugin blazeSyncPlugin : |
| BlazeSyncPlugin.EP_NAME.getExtensions()) { |
| blazeSyncPlugin.updateInMemoryState( |
| project, |
| context, |
| workspaceRoot, |
| projectViewSet, |
| blazeProjectData, |
| workspaceModule); |
| } |
| }); |
| }); |
| } |
| |
| /** |
| * Creates a module that includes the user's data directory. |
| * |
| * <p>This is useful to be able to edit the project view without IntelliJ complaining it's outside |
| * the project. |
| */ |
| private void createProjectDataDirectoryModule( |
| ModuleEditor moduleEditor, File projectDataDirectory, ModuleType moduleType) { |
| Module module = moduleEditor.createModule(".project-data-dir", moduleType); |
| ModifiableRootModel modifiableModel = moduleEditor.editModule(module); |
| ContentEntry rootContentEntry = |
| modifiableModel.addContentEntry(pathToUrl(projectDataDirectory)); |
| rootContentEntry.addExcludeFolder(pathToUrl(new File(projectDataDirectory, ".idea"))); |
| rootContentEntry.addExcludeFolder( |
| pathToUrl(BlazeDataStorage.getProjectDataDir(importSettings))); |
| } |
| |
| private static String pathToUrl(File path) { |
| String filePath = FileUtil.toSystemIndependentName(path.getPath()); |
| return VirtualFileManager.constructUrl( |
| VirtualFileSystemProvider.getInstance().getSystem().getProtocol(), filePath); |
| } |
| |
| private static void onSyncStart(Project project, BlazeContext context, SyncMode syncMode) { |
| final SyncListener[] syncListeners = SyncListener.EP_NAME.getExtensions(); |
| for (SyncListener syncListener : syncListeners) { |
| syncListener.onSyncStart(project, context, syncMode); |
| } |
| } |
| |
| private static void afterSync( |
| Project project, BlazeContext context, SyncMode syncMode, SyncResult syncResult) { |
| final SyncListener[] syncListeners = SyncListener.EP_NAME.getExtensions(); |
| for (SyncListener syncListener : syncListeners) { |
| syncListener.afterSync(project, context, syncMode, syncResult); |
| } |
| } |
| |
| private void onSyncComplete( |
| Project project, |
| BlazeContext context, |
| ProjectViewSet projectViewSet, |
| BlazeProjectData blazeProjectData, |
| SyncMode syncMode, |
| SyncResult syncResult) { |
| validate(project, context, blazeProjectData); |
| |
| final SyncListener[] syncListeners = SyncListener.EP_NAME.getExtensions(); |
| for (SyncListener syncListener : syncListeners) { |
| syncListener.onSyncComplete( |
| project, context, importSettings, projectViewSet, blazeProjectData, syncMode, syncResult); |
| } |
| } |
| |
| private static void validate( |
| Project project, BlazeContext context, BlazeProjectData blazeProjectData) { |
| for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) { |
| syncPlugin.validate(project, context, blazeProjectData); |
| } |
| } |
| } |