From 7f673bc4e187662649774481152707566b1bd84a Mon Sep 17 00:00:00 2001 From: GenericNerdyUsername Date: Thu, 15 Sep 2022 15:35:46 +0100 Subject: [PATCH] jetbrains: add plugin support --- .../applications/editors/jetbrains/darwin.nix | 3 +- .../editors/jetbrains/default.nix | 4 +- pkgs/applications/editors/jetbrains/linux.nix | 34 ++- .../editors/jetbrains/plugins/default.nix | 72 +++++ .../editors/jetbrains/plugins/plugins.json | 98 +++++++ .../jetbrains/plugins/specialPlugins.nix | 23 ++ .../jetbrains/plugins/update_plugins.py | 246 ++++++++++++++++++ .../jetbrains/{update.py => update_ides.py} | 0 8 files changed, 466 insertions(+), 14 deletions(-) create mode 100644 pkgs/applications/editors/jetbrains/plugins/default.nix create mode 100644 pkgs/applications/editors/jetbrains/plugins/plugins.json create mode 100644 pkgs/applications/editors/jetbrains/plugins/specialPlugins.nix create mode 100755 pkgs/applications/editors/jetbrains/plugins/update_plugins.py rename pkgs/applications/editors/jetbrains/{update.py => update_ides.py} (100%) diff --git a/pkgs/applications/editors/jetbrains/darwin.nix b/pkgs/applications/editors/jetbrains/darwin.nix index f771894f533d7..2b3ccf4bcf354 100644 --- a/pkgs/applications/editors/jetbrains/darwin.nix +++ b/pkgs/applications/editors/jetbrains/darwin.nix @@ -10,6 +10,7 @@ , productShort ? product , src , version +, plugins ? [] , ... }: @@ -17,7 +18,7 @@ let loname = lib.toLower productShort; in stdenvNoCC.mkDerivation { - inherit pname meta src version; + inherit pname meta src version plugins; desktopName = product; installPhase = '' runHook preInstall diff --git a/pkgs/applications/editors/jetbrains/default.nix b/pkgs/applications/editors/jetbrains/default.nix index c04e215b99084..1cdc65c192cad 100644 --- a/pkgs/applications/editors/jetbrains/default.nix +++ b/pkgs/applications/editors/jetbrains/default.nix @@ -213,7 +213,7 @@ let providing you almost everything you need for your comfortable and productive development! ''; - maintainers = with maintainers; [ ]; + maintainers = with maintainers; [ genericnerdyusername ]; }; }).overrideAttrs (finalAttrs: previousAttrs: lib.optionalAttrs cythonSpeedup { buildInputs = with python3.pkgs; [ python3 setuptools ]; @@ -467,4 +467,6 @@ in update-channel = products.webstorm.update-channel; }; + plugins = callPackage ./plugins {}; + } diff --git a/pkgs/applications/editors/jetbrains/linux.nix b/pkgs/applications/editors/jetbrains/linux.nix index 7443842e7bf9c..7f0c7ce777579 100644 --- a/pkgs/applications/editors/jetbrains/linux.nix +++ b/pkgs/applications/editors/jetbrains/linux.nix @@ -3,7 +3,17 @@ , vmopts ? null }: -{ pname, product, productShort ? product, version, src, wmClass, jdk, meta, extraLdPath ? [], extraWrapperArgs ? [] }@args: +{ pname +, product +, productShort ? product +, version +, src +, wmClass +, jdk +, meta +, extraLdPath ? [] +, extraWrapperArgs ? [] +}@args: let loName = lib.toLower productShort; hiName = lib.toUpper productShort; @@ -32,17 +42,17 @@ with stdenv; lib.makeOverridable mkDerivation (rec { nativeBuildInputs = [ makeWrapper patchelf unzip ]; postPatch = '' - get_file_size() { - local fname="$1" - echo $(ls -l $fname | cut -d ' ' -f5) - } - - munge_size_hack() { - local fname="$1" - local size="$2" - strip $fname - truncate --size=$size $fname - } + get_file_size() { + local fname="$1" + echo $(ls -l $fname | cut -d ' ' -f5) + } + + munge_size_hack() { + local fname="$1" + local size="$2" + strip $fname + truncate --size=$size $fname + } rm -rf jbr diff --git a/pkgs/applications/editors/jetbrains/plugins/default.nix b/pkgs/applications/editors/jetbrains/plugins/default.nix new file mode 100644 index 0000000000000..c612d8f88da0f --- /dev/null +++ b/pkgs/applications/editors/jetbrains/plugins/default.nix @@ -0,0 +1,72 @@ +{ fetchurl +, fetchzip +, lib +, stdenv +, callPackage +, autoPatchelfHook +}: with builtins; +# These functions do NOT check for plugin compatibility +# Check by installing manually first + +let + fetchPluginSrc = { hash, url, id, name, special ? false, version ? null, ... }: let + isJar = lib.hasSuffix ".jar" url; + fetcher = if isJar then fetchurl else fetchzip; + in fetcher { + executable = isJar; + inherit url hash; + passthru = { inherit id name special version; }; + }; + + mkPlugin = x: stdenv.mkDerivation ({installPhase = "mkdir $out && cp -r . $out";} // x); + + specialPluginsInfo = callPackage ./specialPlugins.nix {}; + plugins = fromJSON (readFile ./plugins.json); + pluginsWithSpecial = mapAttrs + (id: value: value // { special = specialPluginsInfo ? "${id}"; inherit id; } // (specialPluginsInfo."${id}" or {})) plugins; + pluginsWithSrcs = mapAttrs + (id: value: value // { src = fetchPluginSrc value; }) pluginsWithSpecial; + pluginsWithResult = mapAttrs + (id: value: { result = if value.special then mkPlugin value else value.src; } // value) pluginsWithSrcs; + byId = mapAttrs (id: value: value.result) pluginsWithResult; + byName = lib.mapAttrs' + (key: value: lib.attrsets.nameValuePair value.name value.result) pluginsWithResult; + +in rec { + inherit fetchPluginSrc byId byName; + + addPlugins = ide: plugins: stdenv.mkDerivation rec { + pname = meta.mainProgram + "-with-plugins"; + version = ide.version; + src = ide; + dontInstall = true; + dontFixup = true; + passthru.plugins = plugins ++ (ide.plugins or []); + newPlugins = plugins; + disallowedReferences = [ ide ]; + nativeBuildInputs = [ autoPatchelfHook ] ++ (ide.nativeBuildInputs or []); + buildInputs = ide.buildInputs or []; + + inherit (ide) meta; + + buildPhase = let + pluginCmdsLines = map (plugin: "ln -s ${plugin} \"$out\"/${meta.mainProgram}/plugins/${baseNameOf plugin}") plugins; + pluginCmds = concatStringsSep "\n" pluginCmdsLines; + extraBuildPhase = rec { + clion = '' + sed "s|${ide}|$out|" -i $out/bin/.clion-wrapped + autoPatchelf $out/clion/bin + ''; + }; + in '' + cp -r ${ide} $out + chmod +w -R $out + IFS=' ' read -ra pluginArray <<< "$newPlugins" + for plugin in "''${pluginArray[@]}" + do + ln -s "$plugin" -t $out/${meta.mainProgram}/plugins/ + done + sed "s|${ide.outPath}|$out|" -i $out/bin/${meta.mainProgram} + '' + (extraBuildPhase."${ide.meta.mainProgram}" or ""); + }; +} diff --git a/pkgs/applications/editors/jetbrains/plugins/plugins.json b/pkgs/applications/editors/jetbrains/plugins/plugins.json new file mode 100644 index 0000000000000..420e41f092d04 --- /dev/null +++ b/pkgs/applications/editors/jetbrains/plugins/plugins.json @@ -0,0 +1,98 @@ +{ + "164": { + "url": "https://plugins.jetbrains.com/files/164/245831/IdeaVim-2.0.0.zip", + "hash": "sha256-ufRfFGDLEHSdmXGgeO6EkuERDO1etWtjXS5kYeNthYE=", + "name": "ideavim", + "version": "2.0.0" + }, + "631": { + "url": "https://plugins.jetbrains.com/files/631/252982/python-223.7571.58.zip", + "hash": "sha256-ZzWa3Yzma0JN8dmaLWl2ixiNyDqd4Yr7WilgMPCRq/4=", + "name": "python", + "version": "223.7571.58" + }, + "4230": { + "url": "https://plugins.jetbrains.com/files/4230/92719/BashSupport-1.8.0.202.zip", + "hash": "sha256-c6MOV9RmDPZcg+2reacK3O2BQuMnqxYjvLh5leQ7lkI=", + "name": "bashsupport", + "version": "1.8.0.202" + }, + "6954": { + "url": "https://plugins.jetbrains.com/files/6954/248763/kotlin-plugin-222-1.7.21-release-272-IJ4167.29.zip", + "hash": "sha256-SDDAm4HjhJIEOmb+guKPzvhaUVC3oljd8GviABp0Pv8=", + "name": "kotlin", + "version": "222-1.7.21-release-272-IJ4167.29" + }, + "6981": { + "url": "https://plugins.jetbrains.com/files/6981/255105/ini-223.7571.98.zip", + "hash": "sha256-alzFuJ0cEVL311Y4nXW+JGM6PlKoXu3y5G/Hl17npBQ=", + "name": "ini", + "version": "223.7571.98" + }, + "7322": { + "url": "https://plugins.jetbrains.com/files/7322/253079/python-ce-223.7571.58.zip", + "hash": "sha256-CWLWG4jTOY0MrvGljn54wVu4FT0v4B7TjG5S/m09I0k=", + "name": "python-community-edition", + "version": "223.7571.58" + }, + "8182": { + "url": "https://plugins.jetbrains.com/files/8182/261493/intellij-rust-0.4.184.5057-223.zip", + "hash": "sha256-wRn5UrtvQYKgG5TX+Tq9LKjQHqv8lSWfiQVKSm9FtA0=", + "name": "rust", + "version": "0.4.184.5057-223" + }, + "8182-beta": { + "url": "https://plugins.jetbrains.com/files/8182/261812/intellij-rust-0.4.184.5059-223-beta.zip", + "hash": "sha256-FjIn2EXf4uyGTsN/Hb8ME5EZhhEI/Q1cbTkqLUnJSgg=", + "name": "rust-beta", + "version": "0.4.184.5059-223-beta" + }, + "8554": { + "url": "https://plugins.jetbrains.com/files/8554/255591/featuresTrainer-223.7571.93.zip", + "hash": "sha256-jA+pk7caXpaJVSDGOwlSnsOCp6Kz6szn9kTuLukb+tE=", + "name": "ide-features-trainer", + "version": "223.7571.93" + }, + "8607": { + "url": "https://plugins.jetbrains.com/files/8607/235206/NixIDEA-0.4.0.6.zip", + "hash": "sha256-DZVCygkVVS2r0ZJXqLyuAUOZSs90OLla/DEOpn8xRRw=", + "name": "nixidea", + "version": "0.4.0.6" + }, + "9568": { + "url": "https://plugins.jetbrains.com/files/9568/253077/go-plugin-223.7571.58.zip", + "hash": "sha256-VQidLLtv+kS2S95CsQIxAQbiHjCT0gBD8i9X7mJr7mY=", + "name": "go", + "version": "223.7571.58" + }, + "10037": { + "url": "https://plugins.jetbrains.com/files/10037/250022/CSVEditor-3.0.1-223.zip", + "hash": "sha256-M6vDRwux3TqnHXs1/Te1Nh3ratphwhvBZSa/9ePajTM=", + "name": "csv", + "version": "3.0.1-223" + }, + "12559": { + "url": "https://plugins.jetbrains.com/files/12559/255100/keymap-eclipse-223.7571.98.zip", + "hash": "sha256-BRzZF8WIGX427QdvRkbQKZv26h6kwNZoHQSNPR+v03Q=", + "name": "eclipse-keymap", + "version": "223.7571.98" + }, + "13017": { + "url": "https://plugins.jetbrains.com/files/13017/255112/keymap-visualStudio-223.7571.98.zip", + "hash": "sha256-TEcZaDcFJD9B2ur9GhxBoyC/YQjbYFW2ZCFCI9yeuls=", + "name": "visual-studio-keymap", + "version": "223.7571.98" + }, + "14059": { + "url": "https://plugins.jetbrains.com/files/14059/82616/darcula-pitch-black.jar", + "hash": "sha256-eXInfAqY3yEZRXCAuv3KGldM1pNKEioNwPB0rIGgJFw=", + "name": "darcula-pitch-black", + "version": "1.0.0" + }, + "18444": { + "url": "https://plugins.jetbrains.com/files/18444/163012/NetBeans6.5Keymap.zip", + "hash": "sha256-3oB+XuiQ2wxXSfl4aD3BVu/zucqtP9liFjkr9U7ITCU=", + "name": "netbeans-6-5-keymap", + "version": "221.5080.20" + } +} diff --git a/pkgs/applications/editors/jetbrains/plugins/specialPlugins.nix b/pkgs/applications/editors/jetbrains/plugins/specialPlugins.nix new file mode 100644 index 0000000000000..c983271ad36ab --- /dev/null +++ b/pkgs/applications/editors/jetbrains/plugins/specialPlugins.nix @@ -0,0 +1,23 @@ +{ delve, autoPatchelfHook, stdenv }: +# This is a list of plugins that need special treatment. For example, the go plugin (id is 9568) comes with delve, a +# debugger, but that needs various linking fixes. The changes here replace it with the system one. +{ + "631" = { # Python + nativeBuildInputs = [ autoPatchelfHook ]; + buildInputs = [ stdenv.cc.cc.lib ]; + }; + "7322" = { # Python community edition + nativeBuildInputs = [ autoPatchelfHook ]; + buildInputs = [ stdenv.cc.cc.lib ]; + }; + "8182" = { # Rust + nativeBuildInputs = [ autoPatchelfHook ]; + commands = "chmod +x -R bin"; + }; + "9568" = { # Go + buildInputs = [ delve ]; + commands = let + arch = (if stdenv.isLinux then "linux" else "mac") + (if stdenv.isAarch64 then "arm" else ""); + in "ln -sf ${delve}/bin/dlv lib/dlv/${arch}/dlv"; + }; +} diff --git a/pkgs/applications/editors/jetbrains/plugins/update_plugins.py b/pkgs/applications/editors/jetbrains/plugins/update_plugins.py new file mode 100755 index 0000000000000..6a7cf0615db49 --- /dev/null +++ b/pkgs/applications/editors/jetbrains/plugins/update_plugins.py @@ -0,0 +1,246 @@ +#! /usr/bin/env nix-shell +#! nix-shell -i python3 -p python3 python3.pkgs.requests nix.out + +from json import load, dumps +from pathlib import Path +from requests import get +from subprocess import run +from argparse import ArgumentParser + +PLUGINS_FILE = Path(__file__).parent.joinpath("plugins.json").resolve() + +# Token priorities for version checking +# From https://github.com/JetBrains/intellij-community/blob/94f40c5d77f60af16550f6f78d481aaff8deaca4/platform/util-rt/src/com/intellij/util/text/VersionComparatorUtil.java#L50 +TOKENS = { + "snap": 10, "snapshot": 10, + "m": 20, + "eap": 25, "pre": 25, "preview": 25, + "alpha": 30, "a": 30, + "beta": 40, "betta": 40, "b": 40, + "rc": 50, + "sp": 70, + "rel": 80, "release": 80, "r": 80, "final": 80 +} + +# Helper functions for version checking + + +def split(version_string: str): + prev_type = None + block = "" + for char in version_string: + + if char.isdigit(): + cur_type = "number" + elif char.isalpha(): + cur_type = "letter" + else: + cur_type = "other" + + if cur_type != prev_type and block: + yield block.lower() + block = "" + + if cur_type in ("letter", "number"): + block += char + + prev_type = cur_type + + if block: + yield block + + +def tokenize_stream(stream): + for item in stream: + if item in TOKENS: + yield TOKENS[item], 0 + elif item.isalpha(): + for char in item: + yield 90, ord(char)-96 + elif item.isdigit(): + yield 100, int(item) + + +def tokenize_string(version_string: str): + return list(tokenize_stream(split(version_string))) + + +def is_newer(orig_ver: str, other_ver: str): + orig = tokenize_string(orig_ver) + other = tokenize_string(other_ver) + + if orig == other: + return False + + newest = sorted([orig, other])[1] + return other == newest + + +def dump(obj, file): + file.write(dumps(obj, indent=2)) + file.write("\n") + + +def update_all(): + plugins = load(open(PLUGINS_FILE)) + total = len(plugins) + updated = 0 + result = {} + for id_, current in plugins.items(): + + if "-" in id_: + int_id, channel = id_.split("-", 1) + else: + channel = "" + int_id = id_ + + latest = latest_info(int_id, channel) + updated_hash = None + if is_newer(current["version"], latest["version"]): + updated_hash = get_hash(latest["url"]) + print(f'Updated {current["name"]} from {current["version"]} to {latest["version"]}') + updated += 1 + result[id_] = { + "url": latest["url"], + "hash": updated_hash or current["hash"], + "name": current["name"], + "version": latest["version"] + } + + dump(sort(result), open(PLUGINS_FILE, "w")) + print(f"\n{updated}/{total} plugins updated") + + +def get_hash(url): + args = ["nix-prefetch-url", url, "--print-path"] + if url.endswith(".zip"): + args.append("--unpack") + else: + args.append("--executable") + path_process = run(args, capture_output=True) + path = path_process.stdout.decode().split("\n")[1] + result = run(["nix", "--extra-experimental-features", "nix-command", "hash", "path", path], capture_output=True) + result_contents = result.stdout.decode()[:-1] + if not result_contents: + raise RuntimeError(result.stderr.decode()) + return result_contents + + +def latest_info(id_, channel=""): + url = f"https://plugins.jetbrains.com/api/plugins/{id_}/updates?channel={channel}" + resp = get(url) + decoded = resp.json() + + if resp.status_code != 200: + print("Error from server: " + decoded["message"]) + exit(1) + + target = sorted(decoded, key=lambda x: list(tokenize_stream(x["version"])))[-1] + + return { + "version": target["version"], + "url": "https://plugins.jetbrains.com/files/" + target["file"] + } + + +def get_name(id_): + url = f"https://plugins.jetbrains.com/api/plugins/{id_}" + response = get(url).json() + return response["link"].split("-", 1)[1] + + +def add_plugin(id_, channel=""): + id_ = str(id_) + channel_ext = "" + if channel != "": + channel_ext = "-" + channel + + plugins = load(open(PLUGINS_FILE)) + if id_+channel_ext in plugins: + print(f"{plugins[id_+channel_ext]['name']} already exists!") + return + + info = latest_info(id_, channel) + + name = get_name(id_) + + plugins[id_+channel_ext] = { + "url": info["url"], + "hash": get_hash(info["url"]), + "name": name+channel_ext, + "version": info["version"] + } + + print(f"Added {name}" + + ("-"+channel if channel else "")) + + dump(sort(plugins), open(PLUGINS_FILE, "w")) + + +def sort(dict_): + return {key: val for key, val in sorted(dict_.items(), key=lambda x: int(x[0].split("-")[0]))} + + +def check_plugin(id_, channel): + id_ = str(id_) + channel_ext = "" + if channel != "": + channel_ext = "-" + channel + + plugins = load(open(PLUGINS_FILE)) + + if id_+channel_ext not in plugins: + print("Cannot check nonexistent plugin!") + exit(1) + + old_info = plugins[id_+channel_ext] + + info = latest_info(id_, channel) + name = get_name(id_) + new_info = { + "url": info["url"], + "hash": get_hash(info["url"]), + "name": name + channel_ext, + "version": info["version"] + } + + valid = True + for (key, new), old in zip(new_info.items(), old_info.values()): + if new != old: + print(f"{key} mismatch, old is {old}, new is {new}") + valid = False + if valid: + print("Plugin is ok") + + +def main(): + parser = ArgumentParser() + sub = parser.add_subparsers(dest="action") + + sub.add_parser("update") + + new = sub.add_parser("new") + new.add_argument("id", type=int) + new.add_argument("channel", nargs="?", default="") + + bulk = sub.add_parser("bulk") + bulk.add_argument("id", type=int, nargs="+") + + check = sub.add_parser("check") + check.add_argument("id", type=int) + check.add_argument("channel", nargs="?", default="") + + args = parser.parse_args() + if args.action == "new": + add_plugin(args.id, args.channel) + elif args.action == "bulk": + for plugin in args.id: + add_plugin(plugin) + elif args.action == "check": + check_plugin(args.id, args.channel) + else: + update_all() + + +if __name__ == '__main__': + main() diff --git a/pkgs/applications/editors/jetbrains/update.py b/pkgs/applications/editors/jetbrains/update_ides.py similarity index 100% rename from pkgs/applications/editors/jetbrains/update.py rename to pkgs/applications/editors/jetbrains/update_ides.py