From 555e244436687599022ecc80d4d6705b92f77004 Mon Sep 17 00:00:00 2001 From: Matt Mackay Date: Mon, 18 May 2020 19:02:37 -0400 Subject: [PATCH] feat: add IntelliJ IDEA plugin for generating with bzlgen --- WORKSPACE | 16 +++ plugin/BUILD | 16 +++ plugin/plugin.iml | 13 ++ plugin/resources/META-INF/plugin.xml | 53 ++++++++ .../evertz/devtools/bzlgen/ij/BzlgenUtil.java | 75 +++++++++++ .../ij/actions/AbstractBzlgenAction.java | 91 +++++++++++++ .../GenerateContainerLayerRuleAction.java | 9 ++ .../actions/GenerateFilegroupRuleAction.java | 9 ++ .../actions/GenerateNgModuleRuleAction.java | 42 ++++++ .../GenerateNodeJsBinaryRuleAction.java | 24 ++++ .../actions/GenerateTsLibraryRuleAction.java | 19 +++ .../bzlgen/ij/process/BzlgenCommand.java | 123 ++++++++++++++++++ third_party/BUILD | 0 third_party/intellij_sdk/BUILD | 72 ++++++++++ 14 files changed, 562 insertions(+) create mode 100644 plugin/BUILD create mode 100644 plugin/plugin.iml create mode 100644 plugin/resources/META-INF/plugin.xml create mode 100644 plugin/src/com/evertz/devtools/bzlgen/ij/BzlgenUtil.java create mode 100644 plugin/src/com/evertz/devtools/bzlgen/ij/actions/AbstractBzlgenAction.java create mode 100644 plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateContainerLayerRuleAction.java create mode 100644 plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateFilegroupRuleAction.java create mode 100644 plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateNgModuleRuleAction.java create mode 100644 plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateNodeJsBinaryRuleAction.java create mode 100644 plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateTsLibraryRuleAction.java create mode 100644 plugin/src/com/evertz/devtools/bzlgen/ij/process/BzlgenCommand.java create mode 100644 third_party/BUILD create mode 100644 third_party/intellij_sdk/BUILD diff --git a/WORKSPACE b/WORKSPACE index 5bf3e0e..1f154c5 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -4,6 +4,7 @@ workspace( ) load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:jvm.bzl", "jvm_maven_import_external") http_archive( name = "build_bazel_rules_nodejs", @@ -28,3 +29,18 @@ yarn_install( load("@npm//:install_bazel_dependencies.bzl", "install_bazel_dependencies") install_bazel_dependencies() + +jvm_maven_import_external( + name = "error_prone_annotations", + artifact = "com.google.errorprone:error_prone_annotations:2.3.0", + artifact_sha256 = "524b43ea15ca97c68f10d5f417c4068dc88144b620d2203f0910441a769fd42f", + licenses = ["notice"], # Apache 2.0 + server_urls = ["https://repo1.maven.org/maven2"], +) + +http_archive( + name = "intellij_ce_2019_3", + build_file = "@//third_party/intellij_sdk:BUILD", + sha256 = "fb347c3c681328d11e87846950e8c5af6ac2c8d6a7e56946d3a10e6121d322f9", + url = "https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/idea/ideaIC/2019.3.2/ideaIC-2019.3.2.zip", +) diff --git a/plugin/BUILD b/plugin/BUILD new file mode 100644 index 0000000..bb14614 --- /dev/null +++ b/plugin/BUILD @@ -0,0 +1,16 @@ +java_library( + name = "ij_sdk", + neverlink = True, + exports = [ + "@intellij_ce_2019_3//:sdk", + ], +) + +java_binary( + name = "bzlgen_plugin", + srcs = glob(["src/**/*.java"]), + create_executable = False, + resource_strip_prefix = "plugin/resources", + resources = glob(["resources/**"]), + deps = [":ij_sdk"], +) diff --git a/plugin/plugin.iml b/plugin/plugin.iml new file mode 100644 index 0000000..b152f53 --- /dev/null +++ b/plugin/plugin.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/plugin/resources/META-INF/plugin.xml b/plugin/resources/META-INF/plugin.xml new file mode 100644 index 0000000..7300eb2 --- /dev/null +++ b/plugin/resources/META-INF/plugin.xml @@ -0,0 +1,53 @@ + + com.evertz.devtools.bzlgen.ij + Bzlgen + 0.1.0 + Evertz Microsystems + + + + + + + + + com.intellij.modules.platform + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/src/com/evertz/devtools/bzlgen/ij/BzlgenUtil.java b/plugin/src/com/evertz/devtools/bzlgen/ij/BzlgenUtil.java new file mode 100644 index 0000000..2091fa1 --- /dev/null +++ b/plugin/src/com/evertz/devtools/bzlgen/ij/BzlgenUtil.java @@ -0,0 +1,75 @@ +package com.evertz.devtools.bzlgen.ij; + +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.search.FileTypeIndex; +import com.intellij.util.EnvironmentUtil; +import com.intellij.util.indexing.FileBasedIndex; + +import java.io.File; +import java.util.Set; + +public final class BzlgenUtil { + private BzlgenUtil() {} + + public static final String BZLGEN_BINARY = "bzlgen"; + + public static final String MESSAGE_TITLE = "bzlgen"; + + public static File findBzlgenBinaryOnPath() { + String path = EnvironmentUtil.getValue("PATH"); + + if (path == null) { + return null; + } + + for (String entry : path.split(File.pathSeparator)) { + File file = new File(entry, BZLGEN_BINARY); + if (file.exists() && file.isFile() && file.canExecute()) { + return file; + } + } + + return null; + } + + public static boolean directoryContainsFileWithExtension(VirtualFile directory, String type) { + if (!directory.isDirectory()) { + return false; + } + + for (VirtualFile child : VfsUtil.getChildren(directory)) { + if (type.equals(child.getExtension())) { + return true; + } + } + + return false; + } + + public static boolean directoryContainsFileWithAnyExtension(VirtualFile directory, Set types) { + if (!directory.isDirectory()) { + return false; + } + + if (types.isEmpty()) { + // empty set is considered that all files are supported + return true; + } + + for (String type : types) { + if (directoryContainsFileWithExtension(directory, type)) { + return true; + } + } + + return false; + } + + public static boolean isInEv() { + // pathetic check to try and see if we are inside ev, or somewhere else + return EnvironmentUtil.getEnvironmentMap().containsKey("build_tools_dir"); + } +} diff --git a/plugin/src/com/evertz/devtools/bzlgen/ij/actions/AbstractBzlgenAction.java b/plugin/src/com/evertz/devtools/bzlgen/ij/actions/AbstractBzlgenAction.java new file mode 100644 index 0000000..a9a37ea --- /dev/null +++ b/plugin/src/com/evertz/devtools/bzlgen/ij/actions/AbstractBzlgenAction.java @@ -0,0 +1,91 @@ +package com.evertz.devtools.bzlgen.ij.actions; + +import com.evertz.devtools.bzlgen.ij.BzlgenUtil; +import com.evertz.devtools.bzlgen.ij.process.BzlgenCommand; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public abstract class AbstractBzlgenAction extends AnAction { + private final static ExecutorService executor = Executors.newSingleThreadExecutor(); + + @Override + public void update(AnActionEvent event) { + VirtualFile vf = event.getData(CommonDataKeys.VIRTUAL_FILE); + + if (vf.isDirectory() && !supportsDirectories()) { + event.getPresentation().setEnabledAndVisible(false); + + // no point carrying on + return; + } + + String extension = vf.getExtension(); + Set supportedExtensions = getSupportedFileExtensions(); + + boolean containsSupportedFiles = BzlgenUtil.directoryContainsFileWithAnyExtension(vf, supportedExtensions); + + if (containsSupportedFiles || supportedExtensions.contains(extension)) { + setEnabledForRule(event.getPresentation(), vf); + } else { + event.getPresentation().setEnabledAndVisible(false); + } + } + + @Override + public void actionPerformed(AnActionEvent event) { + VirtualFile selected = event.getData(CommonDataKeys.VIRTUAL_FILE); + VirtualFile contentRoot = getContentRoot(event.getProject(), selected); + + BzlgenCommand.Builder builder = new BzlgenCommand.Builder() + .type(getRuleType()) + .path(VfsUtilCore.getRelativePath(selected, contentRoot)) + .workingDirectory(contentRoot.getPath()) + .executor(executor); + + decorateBzlgenCommandBuilder(builder); + + builder.build().run(); + + if (selected.isDirectory()) { + selected.refresh(false, false); + } else { + selected.getParent().refresh(false, false); + } + } + + protected void setEnabledForRule(Presentation presentation, VirtualFile vf) { + String type = getRuleType(); + presentation.setText("Generate " + type + " for " + vf.getName(), false); + presentation.setEnabledAndVisible(true); + } + + protected boolean supportsDirectories() { + return true; + } + + protected Set getSupportedFileExtensions() { + return Collections.emptySet(); + } + + protected VirtualFile getContentRoot(Project project, VirtualFile file) { + ProjectFileIndex index = ProjectFileIndex.getInstance(project); + return index.getContentRootForFile(file); + } + + protected void decorateBzlgenCommandBuilder(BzlgenCommand.Builder builder) { + // allow subclass to override this and add to the builder + }; + + protected abstract String getRuleType(); +} diff --git a/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateContainerLayerRuleAction.java b/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateContainerLayerRuleAction.java new file mode 100644 index 0000000..735abba --- /dev/null +++ b/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateContainerLayerRuleAction.java @@ -0,0 +1,9 @@ +package com.evertz.devtools.bzlgen.ij.actions; + +public class GenerateContainerLayerRuleAction extends AbstractBzlgenAction { + + @Override + protected String getRuleType() { + return "container_layer"; + } +} diff --git a/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateFilegroupRuleAction.java b/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateFilegroupRuleAction.java new file mode 100644 index 0000000..6b768dc --- /dev/null +++ b/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateFilegroupRuleAction.java @@ -0,0 +1,9 @@ +package com.evertz.devtools.bzlgen.ij.actions; + +public class GenerateFilegroupRuleAction extends AbstractBzlgenAction { + + @Override + protected String getRuleType() { + return "filegroup"; + } +} diff --git a/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateNgModuleRuleAction.java b/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateNgModuleRuleAction.java new file mode 100644 index 0000000..db00dab --- /dev/null +++ b/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateNgModuleRuleAction.java @@ -0,0 +1,42 @@ +package com.evertz.devtools.bzlgen.ij.actions; + +import com.evertz.devtools.bzlgen.ij.BzlgenUtil; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; + +import java.util.Arrays; + +public class GenerateNgModuleRuleAction extends AbstractBzlgenAction { + + @Override + public void update(AnActionEvent event) { + VirtualFile vf = event.getData(CommonDataKeys.VIRTUAL_FILE); + + // ng_module is only supported on directories + if (!vf.isDirectory()) { + event.getPresentation().setEnabledAndVisible(false); + return; + } + + // look for a .module.ts, and .component.ts, although this is a convention, it's well followed within ev + // and this somewhat enforces it ;) + boolean containsNgFiles = Arrays.stream(VfsUtil.getChildren(vf)) + .filter(child -> !child.isDirectory() + && (child.getPath().endsWith(".component.ts") || child.getPath().endsWith(".module.ts"))) + .count() == 2; + + if (!containsNgFiles) { + event.getPresentation().setEnabledAndVisible(false); + return; + } + + setEnabledForRule(event.getPresentation(), vf); + } + + @Override + protected String getRuleType() { + return BzlgenUtil.isInEv() ? "ng_bundle" : "ng_module"; + } +} diff --git a/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateNodeJsBinaryRuleAction.java b/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateNodeJsBinaryRuleAction.java new file mode 100644 index 0000000..efd9424 --- /dev/null +++ b/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateNodeJsBinaryRuleAction.java @@ -0,0 +1,24 @@ +package com.evertz.devtools.bzlgen.ij.actions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class GenerateNodeJsBinaryRuleAction extends AbstractBzlgenAction { + private static final Set SUPPORTED_EXTS = new HashSet(Arrays.asList("ts", "js")); + + @Override + protected boolean supportsDirectories() { + return false; + } + + @Override + protected Set getSupportedFileExtensions() { + return SUPPORTED_EXTS; + } + + @Override + protected String getRuleType() { + return "js_binary"; + } +} diff --git a/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateTsLibraryRuleAction.java b/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateTsLibraryRuleAction.java new file mode 100644 index 0000000..e500a9a --- /dev/null +++ b/plugin/src/com/evertz/devtools/bzlgen/ij/actions/GenerateTsLibraryRuleAction.java @@ -0,0 +1,19 @@ +package com.evertz.devtools.bzlgen.ij.actions; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class GenerateTsLibraryRuleAction extends AbstractBzlgenAction { + private static final Set SUPPORTED_EXTS = new HashSet(Collections.singleton("ts")); + + @Override + protected Set getSupportedFileExtensions() { + return SUPPORTED_EXTS; + } + + @Override + protected String getRuleType() { + return "ts_library"; + } +} diff --git a/plugin/src/com/evertz/devtools/bzlgen/ij/process/BzlgenCommand.java b/plugin/src/com/evertz/devtools/bzlgen/ij/process/BzlgenCommand.java new file mode 100644 index 0000000..8a270e6 --- /dev/null +++ b/plugin/src/com/evertz/devtools/bzlgen/ij/process/BzlgenCommand.java @@ -0,0 +1,123 @@ +package com.evertz.devtools.bzlgen.ij.process; + +import com.evertz.devtools.bzlgen.ij.BzlgenUtil; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationType; +import com.intellij.notification.Notifications; +import com.intellij.util.EnvironmentUtil; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +public final class BzlgenCommand { + final ProcessBuilder processBuilder; + final ExecutorService executor; + + protected BzlgenCommand(ProcessBuilder processBuilder, ExecutorService executor) { + this.processBuilder = processBuilder; + this.executor = executor; + } + + public Future run() { + if (executor.isShutdown() || executor.isTerminated()) { + return CompletableFuture.completedFuture(1); + } + + return executor.submit(() -> { + try { + Process process = processBuilder.start(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + StringBuilder builder = new StringBuilder(); + String line = null; + + while ((line = reader.readLine()) != null) { + builder.append(line); + builder.append(System.getProperty("line.separator")); + } + + String result = builder.toString(); + + int code = process.waitFor(); + + NotificationType type = code == 0 ? NotificationType.INFORMATION : NotificationType.ERROR; + String message = code == 0 ? "Generated BUILD file" : "Error generating BUILD file"; + + Notifications.Bus.notify(new Notification("bzlgen", BzlgenUtil.MESSAGE_TITLE, message, type)); + + if (code != 0) { + Notifications.Bus.notify(new Notification("bzlgen", BzlgenUtil.MESSAGE_TITLE, result, type)); + } + } catch (IOException e) { + e.printStackTrace(); + } + + return 1; + }); + } + + public static class Builder { + private String type; + private String path; + private Map flags = new HashMap<>(); + + private String workingDirectory; + + private ExecutorService executor; + + public Builder type(String type) { + this.type = type; + return this; + } + + public Builder path(String path) { + this.path = path; + return this; + } + + public Builder workingDirectory(String workingDirectory) { + this.workingDirectory = workingDirectory; + return this; + } + + public Builder executor(ExecutorService executor) { + this.executor = executor; + return this; + } + + public Builder flag(String flag, String value) { + this.flags.put("--" + flag, value); + return this; + } + + public BzlgenCommand build() { + File binary = BzlgenUtil.findBzlgenBinaryOnPath(); + if (binary == null) { + throw new RuntimeException("bzlgen not found on PATH"); + } + + ArrayList commands = new ArrayList<>(); + commands.add(binary.getAbsolutePath()); + commands.add(type); + commands.add(path); + flags.forEach((flag, value) -> commands.add(flag + "=" + value)); + + ProcessBuilder processBuilder = new ProcessBuilder(commands) + .directory(new File(workingDirectory)) + .redirectErrorStream(true); + + processBuilder.environment() + .putAll(EnvironmentUtil.getEnvironmentMap()); + + return new BzlgenCommand(processBuilder, executor); + } + } +} diff --git a/third_party/BUILD b/third_party/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/third_party/intellij_sdk/BUILD b/third_party/intellij_sdk/BUILD new file mode 100644 index 0000000..d1a92b8 --- /dev/null +++ b/third_party/intellij_sdk/BUILD @@ -0,0 +1,72 @@ +# Adapted from https://github.com/bazelbuild/intellij/blob/master/intellij_platform_sdk/BUILD.idea193 +package(default_visibility = ["//visibility:public"]) + +java_import( + name = "sdk", + jars = glob( + # temporarily include the newly extracted java plugin in the core SDK + # see https://blog.jetbrains.com/platform/2019/06/java-functionality-extracted-as-a-plugin/ + # api#191: expose the java plugin as a separate target + [ + "lib/*.jar", + "plugins/java/lib/*.jar", + ], + ), + deps = ["@error_prone_annotations//jar"], +) + +java_import( + name = "devkit", + jars = glob(["plugins/devkit/lib/devkit.jar"]), +) + +java_import( + name = "guava", + jars = glob([ + "lib/failureaccess-*.jar", + "lib/guava-*.jar", + ]), +) + +java_import( + name = "coverage", + jars = glob(["plugins/coverage/lib/*.jar"]), +) + +java_import( + name = "hg4idea", + jars = glob(["plugins/hg4idea/lib/hg4idea.jar"]), +) + +java_import( + name = "kotlin", + jars = glob(["plugins/Kotlin/lib/*.jar"]), +) + +java_import( + name = "junit", + jars = glob(["plugins/junit/lib/*.jar"]), +) + +java_import( + name = "tasks", + jars = glob([ + "plugins/tasks/lib/tasks-api.jar", + "plugins/tasks/lib/tasks-core.jar", + ]), +) + +java_import( + name = "terminal", + jars = glob(["plugins/terminal/lib/terminal.jar"]), +) + +java_import( + name = "forms_rt", + jars = ["lib/forms_rt.jar"], +) + +filegroup( + name = "application_info_jar", + srcs = glob(["lib/resources.jar"]), +)