diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelReference.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelReference.java index a3457bb56ea..514747a98db 100644 --- a/base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelReference.java +++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelReference.java @@ -32,9 +32,12 @@ import com.intellij.openapi.vfs.VirtualFileFilter; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; import com.intellij.psi.PsiReferenceBase; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; + import javax.annotation.Nullable; /** Converts a blaze label into an absolute path, then resolves that path to a PsiElements */ diff --git a/base/src/com/google/idea/blaze/base/model/ExternalWorkspaceData.java b/base/src/com/google/idea/blaze/base/model/ExternalWorkspaceData.java index 888b22c99f6..df5dac5a332 100644 --- a/base/src/com/google/idea/blaze/base/model/ExternalWorkspaceData.java +++ b/base/src/com/google/idea/blaze/base/model/ExternalWorkspaceData.java @@ -3,10 +3,13 @@ import com.google.common.base.Functions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; import com.google.devtools.intellij.model.ProjectData; import com.google.idea.blaze.base.ideinfo.ProtoWrapper; import com.google.idea.blaze.base.model.primitives.ExternalWorkspace; +import javax.annotation.Nullable; + public final class ExternalWorkspaceData implements ProtoWrapper { public ImmutableMap workspaces; @@ -22,7 +25,7 @@ public static ExternalWorkspaceData create(ImmutableList work .stream() .collect( ImmutableMap.toImmutableMap( - ExternalWorkspace::name, + ExternalWorkspace::repoName, Functions.identity())) ); } @@ -43,4 +46,9 @@ public ProjectData.ExternalWorkspaceData toProto() { public static ExternalWorkspaceData fromProto(ProjectData.ExternalWorkspaceData proto) { return new ExternalWorkspaceData(proto.getWorkspacesList().stream().map(ExternalWorkspace::fromProto).collect(ImmutableList.toImmutableList())); } + + @Nullable + public ExternalWorkspace getByRepoName(String name) { + return Maps.filterValues(workspaces, w -> w.repoName() != null && w.repoName().equals(name)).values().stream().findFirst().orElse(null); + } } diff --git a/base/src/com/google/idea/blaze/base/sync/workspace/WorkspaceHelper.java b/base/src/com/google/idea/blaze/base/sync/workspace/WorkspaceHelper.java index 0ad3eafe857..0dca30e615c 100644 --- a/base/src/com/google/idea/blaze/base/sync/workspace/WorkspaceHelper.java +++ b/base/src/com/google/idea/blaze/base/sync/workspace/WorkspaceHelper.java @@ -18,6 +18,8 @@ import com.google.common.annotations.VisibleForTesting; import com.google.idea.blaze.base.bazel.BuildSystemProvider; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.ExternalWorkspaceData; +import com.google.idea.blaze.base.model.primitives.ExternalWorkspace; import com.google.idea.blaze.base.model.primitives.Label; import com.google.idea.blaze.base.model.primitives.TargetName; import com.google.idea.blaze.base.model.primitives.WorkspacePath; @@ -169,8 +171,8 @@ private static Label deriveLabel( TargetName.createIfValid( FileUtil.getRelativePath(workspace.root.fileForPath(packagePath), file)); return targetName != null - ? Label.create(workspace.externalWorkspaceName, packagePath, targetName) - : null; + ? Label.create(workspace.externalWorkspaceName, packagePath, targetName) + : null; } private static WorkspacePath getPackagePath( @@ -193,33 +195,41 @@ public static File getExternalSourceRoot(BlazeProjectData projectData) { @Nullable private static synchronized WorkspaceRoot getExternalWorkspaceRootsFile(String workspaceName, - Project project) { + Project project) { if (Blaze.getBuildSystemName(project) == BuildSystemName.Blaze) { return null; } logger.debug("getExternalWorkspaceRootsFile for " + workspaceName); - Map workspaceRootCache = SyncCache.getInstance(project) - .get(WorkspaceHelper.class, (p, data) -> new ConcurrentHashMap()); + Map workspaceRootCache = + SyncCache.getInstance(project) + .get(WorkspaceHelper.class, (p, data) -> new ConcurrentHashMap<>()); //the null cache value case could happen when the blazeProjectData is null. - if(workspaceRootCache == null) { + if (workspaceRootCache == null) { return null; } - WorkspaceRoot root = null; if (workspaceRootCache.containsKey(workspaceName)) { - root = workspaceRootCache.get(workspaceName); - } else if (getBlazeProjectData(project) != null) { - File externalDir = new File(getBlazeProjectData(project).getBlazeInfo().getOutputBase(), - "external/" + workspaceName); + return workspaceRootCache.get(workspaceName); + } + + BlazeProjectData blazeProjectData = getBlazeProjectData(project); + if (blazeProjectData != null) { + File workspaceDir = new File(blazeProjectData.getBlazeInfo().getOutputBase(), "external/" + workspaceName); - if (externalDir.exists() || isInTestMode()) { - root = new WorkspaceRoot(externalDir); + ExternalWorkspace workspace = blazeProjectData.getExternalWorkspaceData().getByRepoName(workspaceName); + if (workspace != null) { + workspaceDir = new File(blazeProjectData.getBlazeInfo().getOutputBase(), "external/" + workspace.name()); + } + + if (workspaceDir.exists() || isInTestMode()) { + WorkspaceRoot root = new WorkspaceRoot(workspaceDir); workspaceRootCache.put(workspaceName, root); + return root; } } - return root; + return null; } //The unit test use the TempFileSystem to create VirtualFile which does not exist on disk. diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/RuleTargetCompletionTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/RuleTargetCompletionTest.java index 5ada9f4a3e3..20328d2a493 100644 --- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/RuleTargetCompletionTest.java +++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/RuleTargetCompletionTest.java @@ -24,6 +24,7 @@ import com.google.idea.blaze.base.lang.buildfile.language.semantics.RuleDefinition; import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile; import com.google.idea.blaze.base.model.primitives.WorkspacePath; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.openapi.editor.Editor; import javax.annotation.Nullable; @@ -188,6 +189,11 @@ public void testLocalPathIgnoredForNonLocalLabels() throws Throwable { assertThat(completionItems).asList().doesNotContain("'//java/com/google:other_rule'"); } + @Test + public void testExternalRepoCompletion() throws Throwable { + + } + private static void setBuildLanguageSpecRules( MockBuildLanguageSpecProvider specProvider, String... ruleNames) { ImmutableMap.Builder rules = ImmutableMap.builder(); diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/ModuleRepositoryCompletionTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/ModuleRepositoryCompletionTest.java new file mode 100644 index 00000000000..fcbb3c6480f --- /dev/null +++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/ModuleRepositoryCompletionTest.java @@ -0,0 +1,85 @@ +package com.google.idea.blaze.base.lang.buildfile.references; + +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase; +import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile; +import com.google.idea.blaze.base.model.ExternalWorkspaceData; +import com.google.idea.blaze.base.model.primitives.ExternalWorkspace; +import com.google.idea.blaze.base.model.primitives.WorkspacePath; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertNotNull; + +@RunWith(JUnit4.class) +public class ModuleRepositoryCompletionTest extends BuildFileIntegrationTestCase { + + final ExternalWorkspaceFixture unmappedWorkspace = + new ExternalWorkspaceFixture(ExternalWorkspace.create("workspace_one", "workspace_one")); + + final ExternalWorkspaceFixture remappedWorkspace = + new ExternalWorkspaceFixture(ExternalWorkspace.create("workspace_one", "com_workspace_one")); + + @Override + protected ExternalWorkspaceData mockExternalWorkspaceData() { + return ExternalWorkspaceData.create(ImmutableList.of(unmappedWorkspace.workspace, remappedWorkspace.workspace)); + } + + @Test + public void testUnmappedExternalWorkspaceCompletion() throws Throwable { + WorkspaceRoot externalWorkspaceRoot = unmappedWorkspace.getWorkspaceRoot(); + assertNotNull(externalWorkspaceRoot); + + BuildFile otherPackage = + unmappedWorkspace.createBuildFile(new WorkspacePath( "p1/p2/BUILD"), "java_library(name = 'rule1')"); + + String targetRule = "@" + unmappedWorkspace.workspace.repoName() + "//p1/p2:rule1"; + + BuildFile file = createBuildFile( + new WorkspacePath("java/BUILD"), + "java_library(", + " name = 'lib',", + " deps = ['" + targetRule + "']"); + + Editor editor = editorTest.openFileInEditor(file); + editorTest.setCaretPosition(editor, 2, (" deps = ['" + targetRule).length()); + + PsiElement target = + GotoDeclarationAction.findTargetElement( + getProject(), editor, editor.getCaretModel().getOffset()); + + assertThat(target).isNotNull(); + } + + @Test + public void testRemappedExternalWorkspaceCompletion() throws Throwable { + WorkspaceRoot externalWorkspaceRoot = remappedWorkspace.getWorkspaceRoot(); + assertNotNull(externalWorkspaceRoot); + + BuildFile otherPackage = + remappedWorkspace.createBuildFile(new WorkspacePath("p1/p2/BUILD"), "java_library(name = 'rule1')"); + + String targetRule = "@" + remappedWorkspace.workspace.repoName() + "//p1/p2:rule1"; + + BuildFile file = createBuildFile( + new WorkspacePath("java/BUILD"), + "java_library(", + " name = 'lib',", + " deps = ['" + targetRule + "']"); + + Editor editor = editorTest.openFileInEditor(file); + editorTest.setCaretPosition(editor, 2, (" deps = ['" + targetRule).length()); + + PsiElement target = + GotoDeclarationAction.findTargetElement( + getProject(), editor, editor.getCaretModel().getOffset()); + + assertThat(target).isNotNull(); + } +} diff --git a/base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java b/base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java index d7bca78144a..072d733c9a6 100644 --- a/base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java +++ b/base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java @@ -16,18 +16,29 @@ package com.google.idea.blaze.base.lang.buildfile; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertNotNull; import com.google.common.base.Joiner; import com.google.idea.blaze.base.BlazeIntegrationTestCase; import com.google.idea.blaze.base.EditorTestHelper; +import com.google.idea.blaze.base.WorkspaceFileSystem; import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.ExternalWorkspaceData; import com.google.idea.blaze.base.model.MockBlazeProjectDataBuilder; import com.google.idea.blaze.base.model.MockBlazeProjectDataManager; +import com.google.idea.blaze.base.model.primitives.ExternalWorkspace; import com.google.idea.blaze.base.model.primitives.WorkspacePath; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; + import org.junit.Before; /** BUILD file specific integration test base */ @@ -40,11 +51,16 @@ public final void doSetup() { new MockBlazeProjectDataManager( MockBlazeProjectDataBuilder.builder(workspaceRoot) .setOutputBase(fileSystem.getRootDir() + "/output_base") + .setExternalWorkspaceData(mockExternalWorkspaceData()) .build()); registerProjectService(BlazeProjectDataManager.class, mockProjectDataManager); editorTest = new EditorTestHelper(getProject(), testFixture); } + protected ExternalWorkspaceData mockExternalWorkspaceData() { + return ExternalWorkspaceData.EMPTY; + } + /** * Creates a file with the specified contents and file path in the test project, and asserts that * it's parsed as a BuildFile @@ -68,4 +84,50 @@ protected void assertFileContents(PsiFile file, List contentLines) { String contents = Joiner.on('\n').join(contentLines); assertThat(file.getText()).isEqualTo(contents); } + + protected final class ExternalWorkspaceFixture { + public final ExternalWorkspace workspace; + WorkspaceFileSystem workspaceFileSystem; + + public ExternalWorkspaceFixture(ExternalWorkspace workspace) { + this.workspace = workspace; + } + + WorkspaceFileSystem getWorkspaceFileSystem() { + if (workspaceFileSystem == null) { + BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData(); + assertNotNull(blazeProjectData); + + File outputBase = blazeProjectData.getBlazeInfo().getOutputBase(); + WorkspaceRoot workspaceRoot = new WorkspaceRoot(Paths.get( + blazeProjectData.getBlazeInfo().getOutputBase().getAbsolutePath(), + "external", workspace.name()).normalize().toFile()); + + File workspaceRootFile = workspaceRoot.directory(); + assertThat(workspaceRootFile).isNotNull(); + workspaceFileSystem = new WorkspaceFileSystem(workspaceRoot, BuildFileIntegrationTestCase.this.fileSystem); + } + + return workspaceFileSystem; + } + + public WorkspaceRoot getWorkspaceRoot() { + BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData(); + assertThat(blazeProjectData).isNotNull(); + + File outputBase = blazeProjectData.getBlazeInfo().getOutputBase(); + + Path workspaceRootPath = Paths.get( + blazeProjectData.getBlazeInfo().getOutputBase().getAbsolutePath(), + "external", workspace.name()); + + return new WorkspaceRoot(workspaceRootPath.normalize().toFile()); + } + + public BuildFile createBuildFile(WorkspacePath workspacePath, String... contentLines) { + PsiFile file = getWorkspaceFileSystem().createPsiFile(workspacePath, contentLines); + assertThat(file).isInstanceOf(BuildFile.class); + return (BuildFile) file; + } + } }