jetbrains: drop bundled plugins (#470147)
This commit is contained in:
@@ -31,6 +31,11 @@
|
||||
|
||||
- All Log4Shell vulnerability scanners were removed, as they were all unmaintained upstream and are no longer relevant given that the vulnerability has been fixed upstream for several years.
|
||||
|
||||
- Plugins for the JetBrains IDEs have been removed from Nixpkgs.
|
||||
|
||||
- `jetbrains.plugins.addPlugins` no longer supports plugin names or ID strings.
|
||||
You can still use `addPlugins` with plugin derivations, such as plugins packaged outside of Nixpkgs.
|
||||
|
||||
- `asio` (standalone version of `boost::asio`) has been updated from 1.24.0 to 1.36.0. Some breaking changes were introduced between these
|
||||
two versions, and the one affected most was the removal of `asio::io_service` in favor of `asio::io_context` in 1.33.0. `asio_1_32_0` is
|
||||
retained for packages that have not completed migration. `asio_1_10` has been removed as no packages depend on it anymore.
|
||||
|
||||
@@ -145,7 +145,3 @@ for name in toVersions.keys():
|
||||
# Commit the result
|
||||
logging.info("#### Committing changes... ####")
|
||||
subprocess.run(['git', 'commit', f'-m{commitMessage}', '--', f'{versions_file_path}'], check=True)
|
||||
|
||||
logging.info("#### Updating plugins ####")
|
||||
plugin_script = current_path.joinpath("../plugins/update_plugins.py").resolve()
|
||||
subprocess.call(plugin_script)
|
||||
|
||||
@@ -8,77 +8,7 @@
|
||||
glib,
|
||||
darwin,
|
||||
}:
|
||||
|
||||
let
|
||||
pluginsJson = builtins.fromJSON (builtins.readFile ./plugins.json);
|
||||
specialPluginsInfo = callPackage ./specialPlugins.nix { };
|
||||
fetchPluginSrc =
|
||||
url: hash:
|
||||
let
|
||||
isJar = lib.hasSuffix ".jar" url;
|
||||
fetcher = if isJar then fetchurl else fetchzip;
|
||||
in
|
||||
fetcher {
|
||||
executable = isJar;
|
||||
inherit url hash;
|
||||
};
|
||||
files = builtins.mapAttrs (key: value: fetchPluginSrc key value) pluginsJson.files;
|
||||
ids = builtins.attrNames pluginsJson.plugins;
|
||||
|
||||
mkPlugin =
|
||||
id: file:
|
||||
if !specialPluginsInfo ? "${id}" then
|
||||
files."${file}"
|
||||
else
|
||||
stdenv.mkDerivation (
|
||||
{
|
||||
name = "jetbrains-plugin-${id}";
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out && cp -r . $out
|
||||
runHook postInstall
|
||||
'';
|
||||
src = files."${file}";
|
||||
}
|
||||
// specialPluginsInfo."${id}"
|
||||
);
|
||||
|
||||
selectFile =
|
||||
id: ide: build:
|
||||
let
|
||||
# Allow all PyCharm/IDEA plugins for PyCharm/IDEA Community
|
||||
# - TODO: Remove this special case once PyCharm/IDEA Community is removed
|
||||
communityCheck =
|
||||
(id != "pycharm-community" || builtins.elem ide pluginsJson.plugins.pycharm.compatible)
|
||||
|| (id != "idea-community" || builtins.elem ide pluginsJson.plugins.idea.compatible);
|
||||
in
|
||||
if !communityCheck && !builtins.elem ide pluginsJson.plugins."${id}".compatible then
|
||||
throw "Plugin with id ${id} does not support IDE ${ide}"
|
||||
else if !pluginsJson.plugins."${id}".builds ? "${build}" then
|
||||
throw "Jetbrains IDEs with build ${build} are not in nixpkgs. Try update_plugins.py with --with-build?"
|
||||
else if pluginsJson.plugins."${id}".builds."${build}" == null then
|
||||
throw "Plugin with id ${id} does not support build ${build}"
|
||||
else
|
||||
pluginsJson.plugins."${id}".builds."${build}";
|
||||
|
||||
byId = builtins.listToAttrs (
|
||||
map (id: {
|
||||
name = id;
|
||||
value = ide: build: mkPlugin id (selectFile id ide build);
|
||||
}) ids
|
||||
);
|
||||
|
||||
byName = builtins.listToAttrs (
|
||||
map (id: {
|
||||
name = pluginsJson.plugins."${id}".name;
|
||||
value = byId."${id}";
|
||||
}) ids
|
||||
);
|
||||
in
|
||||
{
|
||||
# Only use if you know what youre doing
|
||||
raw = { inherit files byId byName; };
|
||||
|
||||
tests = callPackage ./tests.nix { };
|
||||
|
||||
addPlugins =
|
||||
@@ -86,14 +16,10 @@ in
|
||||
let
|
||||
processPlugin =
|
||||
plugin:
|
||||
if lib.isDerivation plugin then
|
||||
plugin
|
||||
else if byId ? "${plugin}" then
|
||||
byId."${plugin}" ide.pname ide.buildNumber
|
||||
else if byName ? "${plugin}" then
|
||||
byName."${plugin}" ide.pname ide.buildNumber
|
||||
else
|
||||
throw "Could not resolve plugin ${plugin}";
|
||||
# We can remove this check and just asume plugins to be derivations starting with 26.11.
|
||||
lib.throwIfNot (lib.isDerivation plugin)
|
||||
"addPlugins no longer supports resolving plugins by name or id strings. Please supply a derivation instead"
|
||||
plugin;
|
||||
|
||||
plugins = map processPlugin unprocessedPlugins;
|
||||
in
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,119 +0,0 @@
|
||||
{
|
||||
delve,
|
||||
autoPatchelfHook,
|
||||
stdenv,
|
||||
lib,
|
||||
glibc,
|
||||
gcc-unwrapped,
|
||||
}:
|
||||
# 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 = lib.optional stdenv.hostPlatform.isLinux autoPatchelfHook;
|
||||
buildInputs = [ (lib.getLib stdenv.cc.cc) ];
|
||||
};
|
||||
"7322" = {
|
||||
# Python community edition
|
||||
nativeBuildInputs = lib.optional stdenv.hostPlatform.isLinux autoPatchelfHook;
|
||||
buildInputs = [ (lib.getLib stdenv.cc.cc) ];
|
||||
};
|
||||
"8182" = {
|
||||
# Rust (deprecated)
|
||||
nativeBuildInputs = lib.optional stdenv.hostPlatform.isLinux autoPatchelfHook;
|
||||
buildInputs = [ (lib.getLib stdenv.cc.cc) ];
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
chmod +x -R bin
|
||||
runHook postBuild
|
||||
'';
|
||||
};
|
||||
"9568" = {
|
||||
# Go
|
||||
buildInputs = [ delve ];
|
||||
buildPhase =
|
||||
let
|
||||
arch =
|
||||
(if stdenv.hostPlatform.isLinux then "linux" else "mac")
|
||||
+ (if stdenv.hostPlatform.isAarch64 then "arm" else "");
|
||||
in
|
||||
''
|
||||
runHook preBuild
|
||||
ln -sf ${delve}/bin/dlv lib/dlv/${arch}/dlv
|
||||
runHook postBuild
|
||||
'';
|
||||
};
|
||||
"17718" = {
|
||||
# Github Copilot
|
||||
# Modified version of https://github.com/ktor/nixos/commit/35f4071faab696b2a4d86643726c9dd3e4293964
|
||||
buildPhase = ''
|
||||
agent='copilot-agent/native/${lib.toLower stdenv.hostPlatform.uname.system}${
|
||||
{
|
||||
x86_64 = "-x64";
|
||||
aarch64 = "-arm64";
|
||||
}
|
||||
.${stdenv.hostPlatform.uname.processor} or ""
|
||||
}/copilot-language-server'
|
||||
orig_size=$(stat --printf=%s $agent)
|
||||
|
||||
find_payload_offset() {
|
||||
grep -aobUam1 -f <(printf '\x1f\x8b\x08\x00') "$agent" | cut -d: -f1
|
||||
}
|
||||
|
||||
# Helper: find the offset of the prelude by searching for function string start
|
||||
find_prelude_offset() {
|
||||
local prelude_string='(function(process, require, console, EXECPATH_FD, PAYLOAD_POSITION, PAYLOAD_SIZE) {'
|
||||
grep -obUa -- "$prelude_string" "$agent" | cut -d: -f1
|
||||
}
|
||||
|
||||
before_payload_position="$(find_payload_offset)"
|
||||
before_prelude_position="$(find_prelude_offset)"
|
||||
|
||||
patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" $agent
|
||||
patchelf --set-rpath ${
|
||||
lib.makeLibraryPath [
|
||||
glibc
|
||||
gcc-unwrapped
|
||||
]
|
||||
} $agent
|
||||
chmod +x $agent
|
||||
new_size=$(stat --printf=%s $agent)
|
||||
var_skip=20
|
||||
var_select=22
|
||||
shift_by=$(($new_size-$orig_size))
|
||||
function fix_offset {
|
||||
# $1 = name of variable to adjust
|
||||
location=$(grep -obUam1 "$1" $agent | cut -d: -f1)
|
||||
location=$(expr $location + $var_skip)
|
||||
value=$(dd if=$agent iflag=count_bytes,skip_bytes skip=$location \
|
||||
bs=1 count=$var_select status=none)
|
||||
value=$(expr $shift_by + $value)
|
||||
echo -n $value | dd of=$agent bs=1 seek=$location conv=notrunc
|
||||
}
|
||||
|
||||
after_payload_position="$(find_payload_offset)"
|
||||
after_prelude_position="$(find_prelude_offset)"
|
||||
|
||||
if [ "${stdenv.hostPlatform.system}" == "aarch64-linux" ]
|
||||
then
|
||||
fix_offset PAYLOAD_POSITION
|
||||
fix_offset PRELUDE_POSITION
|
||||
else
|
||||
# There are hardcoded positions in the binary, then it replaces the placeholders by himself
|
||||
sed -i -e "s/$before_payload_position/$after_payload_position/g" "$agent"
|
||||
sed -i -e "s/$before_prelude_position/$after_prelude_position/g" "$agent"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
"22407" = {
|
||||
# Rust
|
||||
nativeBuildInputs = lib.optional stdenv.hostPlatform.isLinux autoPatchelfHook;
|
||||
buildInputs = [ (lib.getLib stdenv.cc.cc) ];
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
chmod +x -R bin
|
||||
runHook postBuild
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -3,16 +3,13 @@
|
||||
symlinkJoin,
|
||||
lib,
|
||||
runCommand,
|
||||
fetchzip,
|
||||
fetchurl,
|
||||
# If not set, all IDEs are tested.
|
||||
ideName ? null,
|
||||
}:
|
||||
|
||||
let
|
||||
|
||||
# Known broken plugins, PLEASE remove entries here whenever possible.
|
||||
broken-plugins = [
|
||||
];
|
||||
|
||||
ides =
|
||||
if ideName == null then
|
||||
with jetbrains;
|
||||
@@ -53,65 +50,34 @@ in
|
||||
paths = (map modify-ide ides);
|
||||
};
|
||||
|
||||
# Test all plugins. This will only build plugins compatible with the IDE and version. It will fail if the plugin is marked
|
||||
# as compatible, but the build version is somehow not in the "builds" map (as that would indicate that something with update_plugins.py went wrong).
|
||||
all =
|
||||
let
|
||||
plugins-json = builtins.fromJSON (builtins.readFile ./plugins.json);
|
||||
plugins-for =
|
||||
with lib.asserts;
|
||||
ide:
|
||||
map (plugin: plugin.name) (
|
||||
builtins.filter (
|
||||
plugin:
|
||||
let
|
||||
# Allow all PyCharm/IDEA plugins for PyCharm/IDEA Community - TODO: Remove this special case once PyCharm/IDEA Community is removed
|
||||
communityCheck =
|
||||
(ide.pname == "pycharm-community" && builtins.elem "pycharm" plugin.compatible)
|
||||
|| (ide.pname == "idea-community" && builtins.elem "idea" plugin.compatible);
|
||||
in
|
||||
(
|
||||
# Plugin has to not be broken
|
||||
(!builtins.elem plugin.name broken-plugins)
|
||||
# IDE has to be compatible
|
||||
&& (communityCheck || builtins.elem ide.pname plugin.compatible)
|
||||
# Assert: The build number needs to be included (if marked compatible)
|
||||
&& (assertMsg (builtins.elem ide.buildNumber (builtins.attrNames plugin.builds)) "For plugin ${plugin.name} no entry for IDE build ${ide.buildNumber} is defined, even though ${ide.pname} is on that build.")
|
||||
# The plugin has to exist for the build
|
||||
&& (plugin.builds.${ide.buildNumber} != null)
|
||||
)
|
||||
) (builtins.attrValues plugins-json.plugins)
|
||||
);
|
||||
modify-ide = ide: jetbrains.plugins.addPlugins ide (plugins-for ide);
|
||||
in
|
||||
symlinkJoin {
|
||||
name = "jetbrains-test-plugins-all";
|
||||
paths = (map modify-ide ides);
|
||||
};
|
||||
|
||||
# This test builds the IDEs with some plugins and checks that they can be discovered by the IDE.
|
||||
# Test always succeeds on IDEs that the tested plugins don't support.
|
||||
# We ignore IDE compatibility here so we don't have to maintain the plugin versions used below,
|
||||
# the only thing we care about is that they are properly placed.
|
||||
stored-correctly =
|
||||
let
|
||||
plugins-json = builtins.fromJSON (builtins.readFile ./plugins.json);
|
||||
plugin-ids = [
|
||||
# This is a "normal plugin", it's output must be linked into /${pname}/plugins.
|
||||
"8607" # nixidea
|
||||
# This is a plugin where the output contains a single JAR file. This JAR file needs to be linked directly in /${pname}/plugins.
|
||||
"7425" # wakatime
|
||||
];
|
||||
check-if-supported =
|
||||
# This is a "normal plugin", it's output must be linked into /${pname}/plugins.
|
||||
nixidea = fetchzip {
|
||||
url = "https://plugins.jetbrains.com/files/8607/786671/NixIDEA-0.4.0.18.zip";
|
||||
hash = "sha256-JShheBoOBiWM9HubMUJvBn4H3DnWykvqPyrmetaCZiM=";
|
||||
};
|
||||
|
||||
# This is a plugin where the output contains a single JAR file. This JAR file needs to be linked directly in /${pname}/plugins.
|
||||
wakatime = fetchurl {
|
||||
executable = true;
|
||||
url = "https://plugins.jetbrains.com/files/7425/760442/WakaTime.jar";
|
||||
hash = "sha256-DobKZKokueqq0z75d2Fo3BD8mWX9+LpGdT9C7Eu2fHc=";
|
||||
};
|
||||
|
||||
modify-ide =
|
||||
ide:
|
||||
builtins.all (
|
||||
plugin:
|
||||
(builtins.elem ide.pname plugins-json.plugins.${plugin}.compatible)
|
||||
&& (plugins-json.plugins.${plugin}.builds.${ide.buildNumber} != null)
|
||||
) plugin-ids;
|
||||
modify-ide = ide: jetbrains.plugins.addPlugins ide plugin-ids;
|
||||
jetbrains.plugins.addPlugins ide [
|
||||
nixidea
|
||||
wakatime
|
||||
];
|
||||
in
|
||||
runCommand "test-jetbrains-plugins-stored-correctly"
|
||||
{
|
||||
idePaths = (map modify-ide (builtins.filter check-if-supported ides));
|
||||
idePaths = (map modify-ide ides);
|
||||
}
|
||||
# TODO: instead of globbing using $ide/*/plugins we could probably somehow get the package name here properly.
|
||||
''
|
||||
|
||||
@@ -1,411 +0,0 @@
|
||||
#! /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
|
||||
|
||||
# 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
|
||||
}
|
||||
SNAPSHOT_VALUE = 99999
|
||||
PLUGINS_FILE = Path(__file__).parent.joinpath("plugins.json").resolve()
|
||||
IDES_BIN_FILE = Path(__file__).parent.joinpath("../bin/versions.json").resolve()
|
||||
IDES_SOURCE_FILE = Path(__file__).parent.joinpath("../source/sources.json").resolve()
|
||||
# The plugin compatibility system uses a different naming scheme to the ide update system.
|
||||
# These dicts convert between them
|
||||
FRIENDLY_TO_PLUGIN = {
|
||||
"clion": "CLION",
|
||||
"datagrip": "DBE",
|
||||
"goland": "GOLAND",
|
||||
"idea-oss": "IDEA", # This was "IDEA_COMMUNITY" before, but this product doesn't exist anymore.
|
||||
"idea": "IDEA",
|
||||
"mps": "MPS",
|
||||
"phpstorm": "PHPSTORM",
|
||||
"pycharm-oss": "PYCHARM", # This was "PYCHARM_COMMUNITY" before, but this product doesn't exist anymore.
|
||||
"pycharm": "PYCHARM",
|
||||
"rider": "RIDER",
|
||||
"ruby-mine": "RUBYMINE",
|
||||
"rust-rover": "RUST",
|
||||
"webstorm": "WEBSTORM"
|
||||
}
|
||||
PLUGIN_TO_FRIENDLY = {j: i for i, j in FRIENDLY_TO_PLUGIN.items()}
|
||||
|
||||
|
||||
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 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_string(version_string: str):
|
||||
return list(tokenize_stream(split(version_string)))
|
||||
|
||||
|
||||
def pick_newest(ver1: str, ver2: str) -> str:
|
||||
if ver1 is None or ver1 == ver2:
|
||||
return ver2
|
||||
|
||||
if ver2 is None:
|
||||
return ver1
|
||||
|
||||
presort = [tokenize_string(ver1), tokenize_string(ver2)]
|
||||
postsort = sorted(presort)
|
||||
if presort == postsort:
|
||||
return ver2
|
||||
else:
|
||||
return ver1
|
||||
|
||||
|
||||
def is_build_older(ver1: str, ver2: str) -> int:
|
||||
ver1 = [int(i) for i in ver1.replace("*", str(SNAPSHOT_VALUE)).split(".")]
|
||||
ver2 = [int(i) for i in ver2.replace("*", str(SNAPSHOT_VALUE)).split(".")]
|
||||
|
||||
for i in range(min(len(ver1), len(ver2))):
|
||||
if ver1[i] == ver2[i] and ver1[i] == SNAPSHOT_VALUE:
|
||||
return 0
|
||||
if ver1[i] == SNAPSHOT_VALUE:
|
||||
return 1
|
||||
if ver2[i] == SNAPSHOT_VALUE:
|
||||
return -1
|
||||
result = ver1[i] - ver2[i]
|
||||
if result != 0:
|
||||
return result
|
||||
|
||||
return len(ver1) - len(ver2)
|
||||
|
||||
|
||||
def is_compatible(build, since, until) -> bool:
|
||||
return (not since or is_build_older(since, build) < 0) and (not until or 0 < is_build_older(until, build))
|
||||
|
||||
|
||||
def get_newest_compatible(pid: str, build: str, plugin_infos: dict, quiet: bool) -> [None, str]:
|
||||
newest_ver = None
|
||||
newest_index = None
|
||||
for index, info in enumerate(plugin_infos):
|
||||
if pick_newest(newest_ver, info["version"]) != newest_ver and \
|
||||
is_compatible(build, info["since"], info["until"]):
|
||||
newest_ver = info["version"]
|
||||
newest_index = index
|
||||
|
||||
if newest_ver is not None:
|
||||
return "https://plugins.jetbrains.com/files/" + plugin_infos[newest_index]["file"]
|
||||
else:
|
||||
if not quiet:
|
||||
print(f"WARNING: Could not find version of plugin {pid} compatible with build {build}")
|
||||
return None
|
||||
|
||||
|
||||
def flatten(main_list: list[list]) -> list:
|
||||
return [item for sublist in main_list for item in sublist]
|
||||
|
||||
|
||||
def get_compatible_ides(pid: str) -> list[str]:
|
||||
int_id = pid.split("-", 1)[0]
|
||||
url = f"https://plugins.jetbrains.com/api/plugins/{int_id}/compatible-products"
|
||||
result = get(url).json()
|
||||
return sorted([PLUGIN_TO_FRIENDLY[i] for i in result if i in PLUGIN_TO_FRIENDLY])
|
||||
|
||||
|
||||
def id_to_name(pid: str, channel="") -> str:
|
||||
channel_ext = "-" + channel if channel else ""
|
||||
|
||||
resp = get("https://plugins.jetbrains.com/api/plugins/" + pid).json()
|
||||
return resp["link"].split("-", 1)[1] + channel_ext
|
||||
|
||||
|
||||
def sort_dict(to_sort: dict) -> dict:
|
||||
return {i: to_sort[i] for i in sorted(to_sort.keys())}
|
||||
|
||||
|
||||
def make_name_mapping(infos: dict) -> dict[str, str]:
|
||||
return sort_dict({i: id_to_name(*i.split("-", 1)) for i in infos.keys()})
|
||||
|
||||
|
||||
def make_plugin_files(plugin_infos: dict, ide_versions: dict, quiet: bool, extra_builds: list[str]) -> dict:
|
||||
result = {}
|
||||
names = make_name_mapping(plugin_infos)
|
||||
for pid in plugin_infos:
|
||||
plugin_versions = {
|
||||
"compatible": get_compatible_ides(pid),
|
||||
"builds": {},
|
||||
"name": names[pid]
|
||||
}
|
||||
relevant_builds = [
|
||||
builds for ide, builds
|
||||
in ide_versions.items()
|
||||
if (
|
||||
ide in plugin_versions["compatible"]
|
||||
# TODO: Remove this once we removed pycharm-community
|
||||
or (ide == "pycharm-community" and "pycharm" in plugin_versions["compatible"])
|
||||
# TODO: Remove this once we removed idea-community
|
||||
or (ide == "idea-community" and "idea" in plugin_versions["compatible"])
|
||||
)
|
||||
] + [extra_builds]
|
||||
relevant_builds = sorted(list(set(flatten(relevant_builds)))) # Flatten, remove duplicates and sort
|
||||
for build in relevant_builds:
|
||||
plugin_versions["builds"][build] = get_newest_compatible(pid, build, plugin_infos[pid], quiet)
|
||||
result[pid] = plugin_versions
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_old_file_hashes() -> dict[str, str]:
|
||||
return load(open(PLUGINS_FILE))["files"]
|
||||
|
||||
|
||||
def get_hash(url):
|
||||
print(f"Downloading {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 print_file_diff(old, new):
|
||||
added = new.copy()
|
||||
removed = old.copy()
|
||||
to_delete = []
|
||||
|
||||
for file in added:
|
||||
if file in removed:
|
||||
to_delete.append(file)
|
||||
|
||||
for file in to_delete:
|
||||
added.remove(file)
|
||||
removed.remove(file)
|
||||
|
||||
if removed:
|
||||
print("\nRemoved:")
|
||||
for file in removed:
|
||||
print(" - " + file)
|
||||
print()
|
||||
|
||||
if added:
|
||||
print("\nAdded:")
|
||||
for file in added:
|
||||
print(" + " + file)
|
||||
print()
|
||||
|
||||
|
||||
def get_file_hashes(file_list: list[str], refetch_all: bool) -> dict[str, str]:
|
||||
old = {} if refetch_all else get_old_file_hashes()
|
||||
print_file_diff(list(old.keys()), file_list)
|
||||
|
||||
file_hashes = {}
|
||||
for file in sorted(file_list):
|
||||
if file in old:
|
||||
file_hashes[file] = old[file]
|
||||
else:
|
||||
file_hashes[file] = get_hash(file)
|
||||
return file_hashes
|
||||
|
||||
|
||||
def get_args() -> tuple[list[str], list[str], bool, bool, bool, list[str]]:
|
||||
parser = ArgumentParser(
|
||||
description="Add/remove/update entries in plugins.json",
|
||||
epilog="To update all plugins, run with no args.\n"
|
||||
"To add a version of a plugin from a different channel, append -[channel] to the id.\n"
|
||||
"The id of a plugin is the number before the name in the address of its page on https://plugins.jetbrains.com/"
|
||||
)
|
||||
parser.add_argument("-r", "--refetch-all", action="store_true",
|
||||
help="don't use previously collected hashes, redownload all")
|
||||
parser.add_argument("-l", "--list", action="store_true",
|
||||
help="list plugin ids")
|
||||
parser.add_argument("-q", "--quiet", action="store_true",
|
||||
help="suppress warnings about not being able to find compatible plugin versions")
|
||||
parser.add_argument("-w", "--with-build", action="append", default=[],
|
||||
help="append [builds] to the list of builds to fetch plugin versions for")
|
||||
sub = parser.add_subparsers(dest="action")
|
||||
sub.add_parser("add").add_argument("ids", type=str, nargs="+", help="plugin(s) to add")
|
||||
sub.add_parser("remove").add_argument("ids", type=str, nargs="+", help="plugin(s) to remove")
|
||||
|
||||
args = parser.parse_args()
|
||||
add = []
|
||||
remove = []
|
||||
|
||||
if args.action == "add":
|
||||
add = args.ids
|
||||
elif args.action == "remove":
|
||||
remove = args.ids
|
||||
|
||||
return add, remove, args.refetch_all, args.list, args.quiet, args.with_build
|
||||
|
||||
|
||||
def sort_ids(ids: list[str]) -> list[str]:
|
||||
sortable_ids = []
|
||||
for pid in ids:
|
||||
if "-" in pid:
|
||||
split_pid = pid.split("-", 1)
|
||||
sortable_ids.append((int(split_pid[0]), split_pid[1]))
|
||||
else:
|
||||
sortable_ids.append((int(pid), ""))
|
||||
sorted_ids = sorted(sortable_ids)
|
||||
return [(f"{i}-{j}" if j else str(i)) for i, j in sorted_ids]
|
||||
|
||||
|
||||
def get_plugin_ids(add: list[str], remove: list[str]) -> list[str]:
|
||||
ids = list(load(open(PLUGINS_FILE))["plugins"].keys())
|
||||
|
||||
for pid in add:
|
||||
if pid in ids:
|
||||
raise ValueError(f"ID {pid} already in JSON file")
|
||||
ids.append(pid)
|
||||
|
||||
for pid in remove:
|
||||
try:
|
||||
ids.remove(pid)
|
||||
except ValueError:
|
||||
raise ValueError(f"ID {pid} not in JSON file")
|
||||
return sort_ids(ids)
|
||||
|
||||
|
||||
def get_plugin_info(pid: str, channel: str) -> dict:
|
||||
url = f"https://plugins.jetbrains.com/api/plugins/{pid}/updates?channel={channel}"
|
||||
resp = get(url)
|
||||
decoded = resp.json()
|
||||
|
||||
if resp.status_code != 200:
|
||||
print(f"Server gave non-200 code {resp.status_code} with message " + decoded["message"])
|
||||
exit(1)
|
||||
|
||||
return decoded
|
||||
|
||||
|
||||
def ids_to_infos(ids: list[str]) -> dict:
|
||||
result = {}
|
||||
for pid in ids:
|
||||
if "-" in pid:
|
||||
int_id, channel = pid.split("-", 1)
|
||||
else:
|
||||
channel = ""
|
||||
int_id = pid
|
||||
|
||||
result[pid] = get_plugin_info(int_id, channel)
|
||||
return result
|
||||
|
||||
|
||||
def get_ide_versions() -> dict:
|
||||
result = {}
|
||||
|
||||
# Bin IDEs
|
||||
ide_data = load(open(IDES_BIN_FILE))
|
||||
for platform in ide_data:
|
||||
for product in ide_data[platform]:
|
||||
version = ide_data[platform][product]["build_number"]
|
||||
if product not in result:
|
||||
result[product] = [version]
|
||||
elif version not in result[product]:
|
||||
result[product].append(version)
|
||||
|
||||
# Source IDEs
|
||||
ide_source_data = load(open(IDES_SOURCE_FILE))
|
||||
for product, ide_info in ide_source_data.items():
|
||||
version = ide_info["buildNumber"]
|
||||
if product not in result:
|
||||
result[product] = [version]
|
||||
elif version not in result[product]:
|
||||
result[product].append(version)
|
||||
|
||||
# Gateway isn't a normal IDE, so it doesn't use the same plugins system
|
||||
del result["gateway"]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_file_names(plugins: dict[str, dict]) -> list[str]:
|
||||
result = []
|
||||
for plugin_info in plugins.values():
|
||||
for url in plugin_info["builds"].values():
|
||||
if url is not None:
|
||||
result.append(url)
|
||||
|
||||
return list(set(result))
|
||||
|
||||
|
||||
def dump(obj, file):
|
||||
file.write(dumps(obj, indent=2))
|
||||
file.write("\n")
|
||||
|
||||
|
||||
def write_result(to_write):
|
||||
dump(to_write, open(PLUGINS_FILE, "w"))
|
||||
|
||||
|
||||
def main():
|
||||
add, remove, refetch_all, list_ids, quiet, extra_builds = get_args()
|
||||
result = {}
|
||||
|
||||
print("Fetching plugin info")
|
||||
ids = get_plugin_ids(add, remove)
|
||||
if list_ids:
|
||||
print(*ids)
|
||||
plugin_infos = ids_to_infos(ids)
|
||||
|
||||
print("Working out which plugins need which files")
|
||||
ide_versions = get_ide_versions()
|
||||
result["plugins"] = make_plugin_files(plugin_infos, ide_versions, quiet, extra_builds)
|
||||
|
||||
print("Getting file hashes")
|
||||
file_list = get_file_names(result["plugins"])
|
||||
result["files"] = get_file_hashes(file_list, refetch_all)
|
||||
|
||||
write_result(result)
|
||||
|
||||
# Commit the result
|
||||
commitMessage = "jetbrains.plugins: update"
|
||||
print("#### Committing changes... ####")
|
||||
run(['git', 'commit', f'-m{commitMessage}', '--', f'{PLUGINS_FILE}'], check=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -3,26 +3,40 @@ The jdk is in `pkgs/development/compilers/jetbrains-jdk`.
|
||||
|
||||
## Tests:
|
||||
- To test the build process of every IDE (as well as the process for adding plugins), build `jetbrains.plugins.tests.empty`.
|
||||
- To test the build process with all plugins\* supported by all IDEs, build `jetbrains.plugins.tests.all`.
|
||||
- To test only plugins for a specific IDE\*, build `jetbrains.ide-name.tests.plugins.all`.
|
||||
- To test that plugins are correctly stored in the plugins directory, build `jetbrains.plugins.tests.stored-correctly`.
|
||||
|
||||
\*: Plugins marked as broken in nixpkgs are skipped: When updating/fixing plugins, please check the `broken-plugins` in `plugins/tests.nix` and update it if needed.
|
||||
|
||||
## How to use plugins:
|
||||
- Get the ide you want and call `jetbrains.plugins.addPlugins` with a list of plugins you want to add.
|
||||
- The list of plugins can be a list of ids or names (as in `plugins/plugins.json`)
|
||||
- Example: `jetbrains.plugins.addPlugins jetbrains.pycharm-professional [ "nixidea" ]`
|
||||
- The list can also contain drvs giving the directory contents of the plugin (this is how you use a plugin not added to nixpkgs) or a single `.jar` (executable). For an example, look at the implementation of `fetchPluginSrc` in `plugins/default.nix`.
|
||||
- Pass your IDE package and a list of plugin packages to `jetbrains.plugins.addPlugins`.
|
||||
E.g. `pkgs.jetbrains.plugins.addPlugins pkgs.jetbrains.idea [ ideavim ]`
|
||||
- The list has to contain contain drvs giving the directory contents of the plugin or a single `.jar` (executable).
|
||||
|
||||
### How to add a new plugin to nixpkgs
|
||||
- Find the page for the plugin on https://plugins.jetbrains.com
|
||||
- Find the id (it's the number after https://plugins.jetbrains.com/plugin/)
|
||||
- Run `plugins/update_plugins.py` add (plugin id)
|
||||
- If binaries need patch or some other special treatment, add an entry to `plugins/specialPlugins.nix`
|
||||
Nixpkgs does not package Jetbrains plugins, however you can use third-party sources, such as
|
||||
[nix-jetbrains-plugins](https://github.com/nix-community/nix-jetbrains-plugins).
|
||||
Note that some plugins may not work without modification, if they are packaged in a way that is incompatible with NixOS.
|
||||
You can try installing such plugins from within the IDE instead.
|
||||
|
||||
### Example derivations:
|
||||
|
||||
#### "Normal" plugin
|
||||
|
||||
```nix
|
||||
fetchzip {
|
||||
url = "https://plugins.jetbrains.com/files/8607/786671/NixIDEA-0.4.0.18.zip";
|
||||
hash = "sha256-JShheBoOBiWM9HubMUJvBn4H3DnWykvqPyrmetaCZiM=";
|
||||
}
|
||||
```
|
||||
#### "Single JAR file" plugin
|
||||
|
||||
```nix
|
||||
fetchurl {
|
||||
executable = true;
|
||||
url = "https://plugins.jetbrains.com/files/7425/760442/WakaTime.jar";
|
||||
hash = "sha256-DobKZKokueqq0z75d2Fo3BD8mWX9+LpGdT9C7Eu2fHc=";
|
||||
}
|
||||
```
|
||||
|
||||
## How to update stuff:
|
||||
- Run ./bin/update_bin.py, this will update binary IDEs and plugins, and automatically commit them
|
||||
- Run ./bin/update_bin.py, this will update binary IDEs, and automatically commit them
|
||||
- Source builds need a bit more effort, as they **aren't automated at the moment**:
|
||||
- Run ./source/update.py ./source/sources.json ./bin/versions.json. This will update the source version to the version of their corresponding binary packages.
|
||||
- Run these commands respectively:
|
||||
@@ -33,7 +47,6 @@ The jdk is in `pkgs/development/compilers/jetbrains-jdk`.
|
||||
- Notice that sometimes a newer Kotlin version is required to build from source, if build fails, first check the recommended Kotlin version in `.idea/kotlinc.xml` in the IDEA source root
|
||||
- Feel free to update the Kotlin version to a compatible one
|
||||
- If it succeeds, make a commit
|
||||
- Run ./plugins/update_plugins.py, this will update plugins and automatically commit them
|
||||
- make a PR/merge
|
||||
- If it fails, ping/message GenericNerdyUsername or the nixpkgs Jetbrains maintainer team
|
||||
|
||||
@@ -44,7 +57,6 @@ The jdk is in `pkgs/development/compilers/jetbrains-jdk`.
|
||||
- Add an entry in `default.nix`
|
||||
|
||||
### TODO:
|
||||
- move/copy plugin docs to nixpkgs manual
|
||||
- replace `libxcrypt-legacy` with `libxcrypt` when supported
|
||||
- make `jetbrains-remote-dev.patch` cleaner
|
||||
- is extraLdPath needed for IDEA?
|
||||
@@ -63,12 +75,9 @@ The jdk is in `pkgs/development/compilers/jetbrains-jdk`.
|
||||
- make `buildPhase` respect `$NIX_BUILD_CORES`
|
||||
- automated update script?
|
||||
- on `aarch64-linux`:
|
||||
- test plugins
|
||||
- from source build
|
||||
- see if build (binary or source) works without expat
|
||||
- on `x86_64-darwin`:
|
||||
- test plugins
|
||||
- from source build
|
||||
- on `aarch64-darwin`:
|
||||
- test plugins
|
||||
- from source build
|
||||
|
||||
Reference in New Issue
Block a user