blob: 2c366f9f957369fd501ee5f6a61f515c49bfe806 [file] [log] [blame]
/*
* 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.plugin.run;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.idea.blaze.base.command.buildresult.BuildResultHelper;
import com.google.idea.blaze.base.ideinfo.JavaIdeInfo;
import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetKey;
import com.google.idea.blaze.base.ideinfo.TargetMap;
import com.google.idea.blaze.base.model.BlazeProjectData;
import com.google.idea.blaze.base.model.primitives.Label;
import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
import com.google.idea.blaze.plugin.IntellijPluginRule;
import com.google.repackaged.devtools.intellij.plugin.IntellijPluginTargetDeployInfo.IntellijPluginDeployFile;
import com.google.repackaged.devtools.intellij.plugin.IntellijPluginTargetDeployInfo.IntellijPluginDeployInfo;
import com.google.repackaged.protobuf.TextFormat;
import com.intellij.execution.ExecutionException;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.BuildNumber;
import com.intellij.openapi.util.Key;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/** Handles finding files to deploy and copying these into the sandbox. */
class BlazeIntellijPluginDeployer {
static final Key<BlazeIntellijPluginDeployer> USER_DATA_KEY =
Key.create(BlazeIntellijPluginDeployer.class.getName());
private final String sandboxHome;
private final String buildNumber;
private final TargetMap targetMap;
private final List<Label> targetsToDeploy = new ArrayList<>();
private final List<File> deployInfoFiles = new ArrayList<>();
private final Map<File, File> filesToDeploy = Maps.newHashMap();
private File executionRoot;
BlazeIntellijPluginDeployer(Project project, String sandboxHome, String buildNumber)
throws ExecutionException {
BlazeProjectData blazeProjectData =
BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
if (blazeProjectData == null) {
throw new ExecutionException("Not synced yet, please sync project");
}
this.sandboxHome = sandboxHome;
this.buildNumber = buildNumber;
this.targetMap = blazeProjectData.targetMap;
}
/** Adds an intellij plugin target to deploy */
void addTarget(Label label) throws ExecutionException {
targetsToDeploy.add(label);
}
void reportBuildComplete(File executionRoot, BuildResultHelper buildResultHelper) {
this.executionRoot = executionRoot;
for (File file : buildResultHelper.getBuildArtifacts()) {
if (file.getName().endsWith(".intellij-plugin-debug-target-deploy-info")) {
deployInfoFiles.add(file);
}
}
}
List<String> deploy() throws ExecutionException {
List<IntellijPluginDeployInfo> deployInfoList = Lists.newArrayList();
if (!deployInfoFiles.isEmpty()) {
for (File deployInfoFile : deployInfoFiles) {
deployInfoList.addAll(readDeployInfoFromFile(deployInfoFile));
}
} else {
for (Label label : targetsToDeploy) {
deployInfoList.addAll(findDeployInfoFromBareIntelliJPluginTargets(label));
}
}
ImmutableMap<File, File> filesToDeploy = getFilesToDeploy(executionRoot, deployInfoList);
this.filesToDeploy.putAll(filesToDeploy);
for (File file : filesToDeploy.keySet()) {
if (!file.exists()) {
throw new ExecutionException(
String.format("Plugin file '%s' not found. Did the build fail?", file.getName()));
}
}
List<String> pluginIds = readPluginIds(filesToDeploy.keySet());
for (Map.Entry<File, File> entry : filesToDeploy.entrySet()) {
copyFileToSandbox(entry.getKey(), entry.getValue());
}
return pluginIds;
}
void deleteDeployment() {
for (File file : filesToDeploy.values()) {
if (file.exists()) {
file.delete();
}
}
}
private static ImmutableList<IntellijPluginDeployInfo> readDeployInfoFromFile(File deployInfoFile)
throws ExecutionException {
ImmutableList.Builder<IntellijPluginDeployInfo> result = ImmutableList.builder();
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(deployInfoFile))) {
IntellijPluginDeployInfo.Builder builder = IntellijPluginDeployInfo.newBuilder();
TextFormat.Parser parser = TextFormat.Parser.newBuilder().setAllowUnknownFields(true).build();
parser.merge(new InputStreamReader(inputStream, UTF_8), builder);
IntellijPluginDeployInfo deployInfo = builder.build();
result.add(deployInfo);
} catch (IOException e) {
throw new ExecutionException(e);
}
return result.build();
}
private ImmutableList<IntellijPluginDeployInfo> findDeployInfoFromBareIntelliJPluginTargets(
Label label) throws ExecutionException {
TargetIdeInfo target = targetMap.get(TargetKey.forPlainTarget(label));
if (target == null) {
throw new ExecutionException("Target '" + label + "' not imported during sync");
}
if (IntellijPluginRule.isSinglePluginTarget(target)) {
return ImmutableList.of(deployInfoForIntellijPlugin(target));
}
throw new ExecutionException("Target is not a supported intellij plugin type.");
}
private static IntellijPluginDeployInfo deployInfoForIntellijPlugin(TargetIdeInfo target)
throws ExecutionException {
JavaIdeInfo javaIdeInfo = target.javaIdeInfo;
if (!IntellijPluginRule.isSinglePluginTarget(target) || javaIdeInfo == null) {
throw new ExecutionException("Target '" + target + "' is not a valid intellij_plugin target");
}
Collection<LibraryArtifact> jars = javaIdeInfo.jars;
if (javaIdeInfo.jars.size() > 1) {
throw new ExecutionException("Invalid IntelliJ plugin target: it has multiple output jars");
}
LibraryArtifact artifact = jars.isEmpty() ? null : jars.iterator().next();
if (artifact == null || artifact.classJar == null) {
throw new ExecutionException("No output plugin jar found for '" + target + "'");
}
return IntellijPluginDeployInfo.newBuilder()
.addDeployFiles(
IntellijPluginDeployFile.newBuilder()
.setExecutionPath(artifact.classJar.getExecutionRootRelativePath())
.setDeployLocation(new File(artifact.classJar.relativePath).getName()))
.build();
}
private ImmutableMap<File, File> getFilesToDeploy(
File executionRoot, Collection<IntellijPluginDeployInfo> deployInfos) {
ImmutableMap.Builder<File, File> result = ImmutableMap.builder();
for (IntellijPluginDeployInfo deployInfo : deployInfos) {
for (IntellijPluginDeployFile deployFile : deployInfo.getDeployFilesList()) {
File src = new File(executionRoot, deployFile.getExecutionPath());
File dest = new File(sandboxPluginDirectory(sandboxHome), deployFile.getDeployLocation());
result.put(src, dest);
}
}
return result.build();
}
private static File sandboxPluginDirectory(String sandboxHome) {
return new File(sandboxHome, "plugins");
}
private List<String> readPluginIds(Collection<File> files) throws ExecutionException {
List<String> pluginIds = Lists.newArrayList();
for (File file : files) {
if (file.getName().endsWith(".jar")) {
String pluginId = readPluginIdFromJar(buildNumber, file);
if (pluginId != null) {
pluginIds.add(pluginId);
}
}
}
return pluginIds;
}
@Nullable
private static String readPluginIdFromJar(String buildNumber, File jar)
throws ExecutionException {
IdeaPluginDescriptor pluginDescriptor = PluginManagerCore.loadDescriptor(jar, "plugin.xml");
if (pluginDescriptor == null) {
return null;
}
if (PluginManagerCore.isIncompatible(pluginDescriptor, BuildNumber.fromString(buildNumber))) {
throw new ExecutionException(
String.format(
"Plugin SDK version '%s' is incompatible with this plugin "
+ "(since: '%s', until: '%s')",
buildNumber, pluginDescriptor.getSinceBuild(), pluginDescriptor.getUntilBuild()));
}
return pluginDescriptor.getPluginId().getIdString();
}
private static void copyFileToSandbox(File src, File dest) throws ExecutionException {
try {
dest.getParentFile().mkdirs();
Files.copy(src.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new ExecutionException("Error copying plugin file to sandbox", e);
}
}
}