blob: 1767d6b9a96af17e83efe86c8f3f5db3b3bdcc28 [file] [log] [blame]
/*
* Copyright 2017 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.golang.resolve;
import com.goide.psi.GoImportSpec;
import com.goide.psi.impl.imports.GoImportResolver;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetKey;
import com.google.idea.blaze.base.io.VirtualFileSystemProvider;
import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager;
import com.google.idea.blaze.base.model.BlazeProjectData;
import com.google.idea.blaze.base.model.primitives.Kind;
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;
import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
import com.google.idea.blaze.base.settings.Blaze;
import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
import com.google.idea.blaze.golang.BlazeGoSupport;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import java.io.File;
import javax.annotation.Nullable;
/**
* Resolves go imports in a blaze workspace, of the form:
*
* <p>"[workspace_name]/path/to/blaze/package/[go_library target]"
*
* <p>Only the first non-null import candidate is considered, so all blaze-specific import handling
* is done in this {@link GoImportResolver}, to more easily manage priority.
*/
public class BlazeGoImportResolver implements GoImportResolver {
@Nullable
@Override
public PsiDirectory resolve(GoImportSpec goImportSpec) {
Project project = goImportSpec.getProject();
if (!Blaze.isBlazeProject(project) || !BlazeGoSupport.blazeGoSupportEnabled.getValue()) {
return null;
}
// TODO: Handle go packages whose sources are in multiple directories (requires upstream change)
String pathString = goImportSpec.getPath();
String packageName = getPackageName(pathString);
GoTarget target = findGoTarget(project, pathString, packageName);
if (target == null) {
return null;
}
switch (target.kind) {
case GO_LIBRARY:
case GO_APPENGINE_LIBRARY:
return resolveFile(PsiManager.getInstance(project), target.buildFile.getParentFile());
case PROTO_LIBRARY:
case GO_WRAP_CC:
return resolveGenfilesPath(project, target.label.blazePackage().relativePath());
default:
return null;
}
}
private static String getPackageName(String pathString) {
int ix = pathString.lastIndexOf('/');
return ix == -1 ? pathString : pathString.substring(ix + 1);
}
@Nullable
private static GoTarget findGoTarget(Project project, String importPath, String packageName) {
BlazeProjectData projectData =
BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
if (projectData == null) {
return null;
}
TargetName targetName = TargetName.createIfValid(packageName);
if (targetName == null) {
return null;
}
WorkspacePath workspacePath = blazePackageWorkspacePath(project, importPath);
if (workspacePath == null) {
return null;
}
Label label = Label.create(workspacePath, targetName);
GoTarget goTarget = GoTarget.fromTargetIdeInfo(projectData, label);
if (goTarget != null) {
return goTarget;
}
// if the target wasn't indexed, try parsing the BUILD file manually
return GoTarget.manuallyParseBuildFile(project, label);
}
@Nullable
private static PsiDirectory resolveGenfilesPath(Project project, String relativePath) {
BlazeProjectData projectData =
BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
if (projectData == null) {
return null;
}
File genfiles = projectData.blazeInfo.getGenfilesDirectory();
return resolveFile(PsiManager.getInstance(project), new File(genfiles, relativePath));
}
@Nullable
private static PsiDirectory resolveFile(PsiManager manager, File file) {
VirtualFile vf =
VirtualFileSystemProvider.getInstance().getSystem().findFileByPath(file.getPath());
return vf != null && vf.isDirectory() ? manager.findDirectory(vf) : null;
}
@Nullable
private static WorkspacePath blazePackageWorkspacePath(Project project, String importPath) {
String workspaceName = WorkspaceRoot.fromProject(project).directory().getName();
if (!importPath.startsWith(workspaceName + "/")) {
return null;
}
// strip first and last path components (workspace name, go package name)
importPath = importPath.substring(workspaceName.length() + 1);
int lastSeparator = importPath.lastIndexOf('/');
if (lastSeparator <= 0) {
return null;
}
return WorkspacePath.createIfValid(importPath.substring(0, lastSeparator));
}
private static class GoTarget {
final File buildFile;
final Kind kind;
final Label label;
GoTarget(File buildFile, Kind kind, Label label) {
this.buildFile = buildFile;
this.kind = kind;
this.label = label;
}
@Nullable
static GoTarget fromTargetIdeInfo(BlazeProjectData projectData, Label label) {
TargetIdeInfo target = projectData.targetMap.get(TargetKey.forPlainTarget(label));
if (target == null) {
return null;
}
File buildFile = projectData.artifactLocationDecoder.decode(target.buildFile);
return new GoTarget(buildFile, target.kind, target.key.label);
}
@Nullable
static GoTarget manuallyParseBuildFile(Project project, Label label) {
PsiElement psiElement = BuildReferenceManager.getInstance(project).resolveLabel(label);
if (!(psiElement instanceof FuncallExpression)) {
return null;
}
FuncallExpression funcall = (FuncallExpression) psiElement;
Kind kind = funcall.getRuleKind();
BuildFile parentFile = funcall.getContainingFile();
return kind == null || parentFile == null
? null
: new GoTarget(parentFile.getFile(), kind, label);
}
}
}