Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

modules/performance: add an option to combine plugins to a single plugin pack #1886

Merged
merged 13 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
./lua-loader.nix
./opts.nix
./output.nix
./performance.nix
./plugins.nix
];
}
63 changes: 63 additions & 0 deletions modules/performance.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{ lib, ... }:
let
inherit (lib) types;
in
{
options.performance = {
combinePlugins = {
enable = lib.mkEnableOption "combinePlugins" // {
description = ''
Whether to enable EXPERIMENTAL option to combine all plugins
into a single plugin pack. It can significantly reduce startup time,
but all your plugins must have unique filenames and doc tags.
Any collision will result in a build failure. To avoid collisions
you can add your plugin to the `standalonePlugins` option.
Only standard neovim runtime directories are linked to the combined plugin.
If some of your plugins contain important files outside of standard
directories, add these paths to `pathsToLink` option.
'';
};
pathsToLink = lib.mkOption {
type = with types; listOf str;
default = [ ];
example = [ "/data" ];
description = "List of paths to link into a combined plugin pack.";
};
standalonePlugins = lib.mkOption {
type = with types; listOf (either str package);
default = [ ];
example = [ "nvim-treesitter" ];
description = "List of plugins (names or packages) to exclude from plugin pack.";
};
};
};

config.performance = {
# Set option value with default priority so that values are appended by default
combinePlugins.pathsToLink = [
# :h rtp
"/autoload"
"/colors"
"/compiler"
"/doc"
"/ftplugin"
"/indent"
"/keymap"
"/lang"
"/lua"
"/pack"
"/parser"
"/plugin"
"/queries"
"/rplugin"
"/spell"
"/syntax"
"/tutor"
"/after"
# ftdetect
"/ftdetect"
# plenary.nvim
"/data/plenary/filetypes"
];
};
}
3 changes: 3 additions & 0 deletions modules/top-level/files/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,8 @@ in
]
) extraFiles}
'';

# Never combine user files with the rest of the plugins
performance.combinePlugins.standalonePlugins = [ config.filesPlugin ];
};
}
124 changes: 110 additions & 14 deletions modules/top-level/output.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
helpers,
...
}:
with lib;
let
inherit (lib) types mkOption;
inherit (lib) optional optionalString optionalAttrs;
in
{
options = {
viAlias = mkOption {
Expand Down Expand Up @@ -71,15 +74,106 @@ with lib;

config =
let
defaultPlugin = {
plugin = null;
config = "";
optional = false;
};

normalizedPlugins = map (
x: defaultPlugin // (if x ? plugin then x else { plugin = x; })
) config.extraPlugins;
# Plugin normalization
normalize =
p:
let
defaultPlugin = {
plugin = null;
config = null;
optional = false;
};
in
defaultPlugin // (if p ? plugin then p else { plugin = p; });
normalizePluginList = plugins: map normalize plugins;

# Normalized plugin list
normalizedPlugins = normalizePluginList config.extraPlugins;
traxys marked this conversation as resolved.
Show resolved Hide resolved

# Plugin list extended with dependencies
allPlugins =
let
pluginWithItsDeps =
p: [ p ] ++ builtins.concatMap pluginWithItsDeps (normalizePluginList p.plugin.dependencies or [ ]);
in
lib.unique (builtins.concatMap pluginWithItsDeps normalizedPlugins);

# Remove dependencies from all plugins in a list
removeDependencies = ps: map (p: p // { plugin = removeAttrs p.plugin [ "dependencies" ]; }) ps;

# Separated start and opt plugins
partitionedOptStartPlugins = builtins.partition (p: p.optional) allPlugins;
startPlugins = partitionedOptStartPlugins.wrong;
# Remove opt plugin dependencies since they are already available in start plugins
optPlugins = removeDependencies partitionedOptStartPlugins.right;

# Test if plugin shouldn't be included in plugin pack
isStandalone =
p:
builtins.elem p.plugin config.performance.combinePlugins.standalonePlugins
|| builtins.elem (lib.getName p.plugin) config.performance.combinePlugins.standalonePlugins;

# Separated standalone and combined start plugins
partitionedStandaloneStartPlugins = builtins.partition isStandalone startPlugins;
toCombinePlugins = partitionedStandaloneStartPlugins.wrong;
# Remove standalone plugin dependencies since they are already available in start plugins
standaloneStartPlugins = removeDependencies partitionedStandaloneStartPlugins.right;

# Combine start plugins into a single pack
pluginPack =
let
# Every plugin has its own generated help tags (doc/tags)
# Remove them to avoid collisions, new help tags
# will be generate for the entire pack later on
overriddenPlugins = map (
plugin:
plugin.plugin.overrideAttrs (prev: {
nativeBuildInputs = lib.remove pkgs.vimUtils.vimGenDocHook prev.nativeBuildInputs or [ ];
configurePhase = ''
${prev.configurePhase or ""}
rm -vf doc/tags'';
})
) toCombinePlugins;

# Python3 dependencies
python3Dependencies =
let
deps = map (p: p.plugin.python3Dependencies or (_: [ ])) toCombinePlugins;
in
ps: builtins.concatMap (f: f ps) deps;

# Combined plugin
combinedPlugin = pkgs.vimUtils.toVimPlugin (
pkgs.buildEnv {
name = "plugin-pack";
paths = overriddenPlugins;
inherit (config.performance.combinePlugins) pathsToLink;
# Remove empty directories and activate vimGenDocHook
postBuild = ''
find $out -type d -empty -delete
runHook preFixup
'';
passthru = {
inherit python3Dependencies;
};
}
);

# Combined plugin configs
combinedConfig = builtins.concatStringsSep "\n" (
builtins.concatMap (x: lib.optional (x.config != null && x.config != "") x.config) toCombinePlugins
);
in
normalize {
plugin = combinedPlugin;
config = combinedConfig;
};

# Combined plugins
combinedPlugins = [ pluginPack ] ++ standaloneStartPlugins ++ optPlugins;

# Plugins to use in finalPackage
plugins = if config.performance.combinePlugins.enable then combinedPlugins else normalizedPlugins;

neovimConfig = pkgs.neovimUtils.makeNeovimConfig (
{
Expand All @@ -92,15 +186,15 @@ with lib;
withNodeJs
;
# inherit customRC;
plugins = normalizedPlugins;
inherit plugins;
}
# Necessary to make sure the runtime path is set properly in NixOS 22.05,
# or more generally before the commit:
# cda1f8ae468 - neovim: pass packpath via the wrapper
// optionalAttrs (functionArgs pkgs.neovimUtils.makeNeovimConfig ? configure) {
// optionalAttrs (lib.functionArgs pkgs.neovimUtils.makeNeovimConfig ? configure) {
configure.packages = {
nixvim = {
start = map (x: x.plugin) normalizedPlugins;
start = map (x: x.plugin) plugins;
opt = [ ];
};
};
Expand All @@ -115,7 +209,9 @@ with lib;
init = helpers.writeLua "init.lua" customRC;

extraWrapperArgs = builtins.concatStringsSep " " (
(optional (config.extraPackages != [ ]) ''--prefix PATH : "${makeBinPath config.extraPackages}"'')
(optional (
config.extraPackages != [ ]
) ''--prefix PATH : "${lib.makeBinPath config.extraPackages}"'')
stasjok marked this conversation as resolved.
Show resolved Hide resolved
++ (optional config.wrapRc ''--add-flags -u --add-flags "${init}"'')
);

Expand Down
5 changes: 5 additions & 0 deletions plugins/languages/treesitter/treesitter.nix
Original file line number Diff line number Diff line change
Expand Up @@ -328,5 +328,10 @@ helpers.neovim-plugin.mkNeovimPlugin config {
foldmethod = mkDefault "expr";
foldexpr = mkDefault "nvim_treesitter#foldexpr()";
};

# Since https://github.com/NixOS/nixpkgs/pull/321550 upstream queries are added
# to grammar plugins. Exclude nvim-treesitter itself from combining to avoid
# collisions with grammar's queries
performance.combinePlugins.standalonePlugins = [ cfg.package ];
};
}
3 changes: 3 additions & 0 deletions plugins/telescope/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ helpers.neovim-plugin.mkNeovimPlugin config {
require('telescope').load_extension(extension)
end
'';

# planets picker requires files in data/memes/planets
performance.combinePlugins.pathsToLink = [ "/data/memes/planets" ];
Comment on lines +116 to +117
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having to define this explicitly feels fragile... What would happen if we add another plugin module but don't notice we need to define addition paths to link?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would happen if we add another plugin module but don't notice we need to define addition paths to link?

Additional paths will be missing from user runtime if combinePlugins is enabled, so it depends on what files are missing. For telescope here, it pretty much doesn't matter, because planets is a picker that is only for testing. Most plugins doesn't need any paths except standard.

I think in a description of this options should be clear, that this option is experimental, doesn't give any guaranties etc. So if it works for user's config, he'll get a performance boost, if not — sorry. I've added EXPERIMENTAL to the description for that purpose. But English is not native for me, if you have better description, we can update it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the description is good. I think we can support this feature with less guarantees on breaking changes, i.e we won't add tests for all the plugins trying to enable the option, as this might be a too large maintenance burden, but we'll gladly include workarounds if people suggest them

};

settingsOptions = {
Expand Down
5 changes: 5 additions & 0 deletions plugins/telescope/extensions/fzf-native.nix
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,9 @@
override_file_sorter = false;
case_mode = "ignore_case";
};

extraConfig = cfg: {
# Native library is in build/libfzf.so
performance.combinePlugins.pathsToLink = [ "/build" ];
};
}
5 changes: 5 additions & 0 deletions plugins/telescope/extensions/fzy-native.nix
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@
override_file_sorter = true;
override_generic_sorter = false;
};

extraConfig = cfg: {
# fzy-native itself is in deps directory
performance.combinePlugins.pathsToLink = [ "/deps/fzy-lua-native" ];
};
}
Loading