DEV Community

Cover image for Get NeoVim plugins with build processes working on Windows
Kai Walter
Kai Walter

Posted on

Get NeoVim plugins with build processes working on Windows

TL;DR

In this post I show or share

  • a PowerShell script to install LLVM / mingw / make toolchains on Windows to be used by NeoVim plugin build processes
  • how to install Microsoft Build Tools with winget (that in the end did not qualify for all build cases and was discarded)
  • a sample plugin configurations with Lazy plugin manager
  • an observations I made with plugin managers not working smoothly through corporate proxy configurations

Motivation

I just want to have equal editing experience on Windows and Linux - not having myself to adapt when flipping back and forth. With an ecosystem like Visual Studio Code that is practically given without the need to care. But that's not how I am wired - I want to understand what's going on. Here imho NeoVim with Lua plugins is easier to digest and understand.

Background

In a previous post I was showing how I was sharing one configuration for NeoVim based on one version of dotfiles in Linux and Windows.

That works pretty well for Lua-only plugins, as long as underlying command line tools like e.g. ripgrep or lazygit are also avaible on Windows. As soon as plugins require a tool chain to build those command line tools from source, some more work is required.

Selecting the right build tool chain

MS Build Tools and Gnu Make

With this combination I was able to get Treesitter installed and built on Windows. It requires an installation like ...

# install make
$makePath = Join-Path ${env:ProgramFiles(x86)} "GnuWin32" "bin"
if(!(Test-Path $makePath -PathType Container)) {
  winget install GnuWin32.Make
}

# install MS Build Tools
$msbtProgramFolder = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio" "2022" "BuildTools"
if(!(Test-Path $msbtProgramFolder -PathType Container)) {
    winget install Microsoft.VisualStudio.2022.BuildTools
    winget install --id Microsoft.VisualStudio.2022.BuildTools --override $("--passive --config " + (Join-Path $PSScriptRoot "BuildTools.vsconfig"))
}
Enter fullscreen mode Exit fullscreen mode

... with an accompaning BuildTools.vsconfig to define components to be installed ...

{
  "version": "1.0",
  "components": [
    "Microsoft.VisualStudio.Component.Roslyn.Compiler",
    "Microsoft.Component.MSBuild",
    "Microsoft.VisualStudio.Component.CoreBuildTools",
    "Microsoft.VisualStudio.Workload.MSBuildTools",
    "Microsoft.VisualStudio.Component.Windows10SDK",
    "Microsoft.VisualStudio.Component.VC.CoreBuildTools",
    "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
    "Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
    "Microsoft.VisualStudio.Component.Windows11SDK.22000",
    "Microsoft.VisualStudio.Component.TextTemplating",
    "Microsoft.VisualStudio.Component.VC.CoreIde",
    "Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
    "Microsoft.VisualStudio.Workload.VCTools",
    "Microsoft.VisualStudio.Component.VC.14.35.17.5.ATL.Spectre",
    "Microsoft.VisualStudio.Component.VC.14.35.17.5.MFC.Spectre",
    "Microsoft.VisualStudio.Component.VC.14.36.17.6.ATL.Spectre",
    "Microsoft.VisualStudio.Component.VC.14.36.17.6.MFC.Spectre"
  ]
}
Enter fullscreen mode Exit fullscreen mode

... and that, on plugin-installation, NeoVim is started from "Visual Studio Developer PowerShell" command prompt while adding Gnu Make to the path:

$makePath = Join-Path ${env:ProgramFiles(x86)} "GnuWin32" "bin"
$env:Path += ";" + $makePath
nvim
Enter fullscreen mode Exit fullscreen mode

Yet this setup was not sufficient for Telescope/fzf, as only a clang but obviously no gcc compiler seemed to be available with MS Build Tools. I succeeded when adding a gcc into the mix, but was not really happy with the extra "Visual Studio Developer PowerShell" command prompt required.

LLVM/Clang/LLD based mingw-w64

In this toolchain I found clang and gcc compilers. Also it allowed me to just add it to the path (see script NeoVimPluginInstall.ps1 below) of my current shell when directing NeoVim into a plugin installation - without an extra command prompt like above.

That helped me to use the same build process configuration for Telescope/fzf on Linux and Windows without any changes:

return {
    "nvim-telescope/telescope.nvim",
    branch = "0.1.x",
    dependencies = {
        "nvim-lua/plenary.nvim",
        {
            "nvim-telescope/telescope-fzf-native.nvim",
            build = "make", -- <<===== make initiates build process on Windows and Linux
        },
        "nvim-tree/nvim-web-devicons",
    },
    config = function() 
...
Enter fullscreen mode Exit fullscreen mode

In the case of this plugin I tried to modify the build string from make to cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release && cmake --build build --config Release && cmake --install build --prefix build when running on Windows, but even with this small patch I was not able to cleanly succeed with MS Build Tools.

NeoVim installation script

Since my previous post I extended and hardened the installation script a bit. It now additionally installs

[CmdletBinding()]
param (
    [Parameter()]
    [switch]
    $ResetState
)

# install NeoVim with WinGet, if not already present on system
if (!$(Get-Command nvim -ErrorAction SilentlyContinue)) {
    winget install Neovim.Neovim
}

# install ripgrep
if (!$(Get-Command rg -ErrorAction SilentlyContinue)) {
  winget install BurntSushi.ripgrep.MSVC
}

# install lazygit
if (!$(Get-Command lazygit -ErrorAction SilentlyContinue)) {
  winget install JesseDuffield.lazygit
}

# install make
$makePath = Join-Path ${env:ProgramFiles(x86)} "GnuWin32" "bin"
if(!(Test-Path $makePath -PathType Container)) {
  winget install GnuWin32.Make
}

$llvmFolder = Get-ChildItem -Path $env:LOCALAPPDATA -Filter "llvm*x86_64" | Select-Object -ExpandProperty FullName
if(!$llvmFolder -or !(Test-Path $llvmFolder -PathType Container)) {
  $downloadFile = "llvm-mingw.zip"
  . .\getLatestGithubRepo.ps1 -Repository "mstorsjo/llvm-mingw" -DownloadFilePattern "llvm-mingw-.*-msvcrt-x86_64.zip" -DownloadFile $downloadFile
  Expand-Archive -Path $(Join-Path $env:TEMP $downloadFile) -DestinationPath $env:LOCALAPPDATA
}

# clone my Dotfiles repo
$dotFilesRoot = Join-Path $HOME "dotfiles"

if (!(Test-Path $dotFilesRoot -PathType Container)) {
    git clone git@github.com:KaiWalter/dotfiles.git $dotFilesRoot
}

# link NeoVim configuration
$localConfiguration = Join-Path $env:LOCALAPPDATA "nvim"
$dotfilesConfiguration = Join-Path $dotFilesRoot ".config" "nvim"

if (!(Test-Path $localConfiguration -PathType Container)) { 
    Start-Process -FilePath "pwsh" -ArgumentList "-c New-Item -Path $localConfiguration -ItemType SymbolicLink -Value $dotfilesConfiguration".Split(" ") -Verb runas
}

# reset local state if required
$localState = Join-Path $env:LOCALAPPDATA "nvim-data"

if($ResetState) {
    if(Test-Path $localState -PathType Container) {
        Remove-Item $localState -Recurse -Force
        New-Item $localState -ItemType Directory
    }
}
Enter fullscreen mode Exit fullscreen mode

ATTENTION: git@github.com:KaiWalter/dotfiles.git is my private Dotfiles repo - if you want to replicate my approach you would need to work from your own version;
script .\getLatestGithubRepo.ps1 downloads the latest binary / installation file from a GitHub's repo release page and is shown below

Lua configuration

When I started with my NeoVim journey a few months back, I followed the suggestions from the NeoVim main protagonists and looked into some of the NeoVim distros like LazyVim, LunarVim, AstroVim, and NVChad to make life easier (coming fresh from Visual Studio Code even to make life even bearable). Having no knowledge in the NeoVim plugin ecosystem I struggled and stopped to try to get these distros working in parallel on Linux and Windows. Hence I decided to build a configuration with Packer from scratch to find and understand the spots, where it breaks.

While succeeding in getting NeoVim working smoothly with plugins on my own Windows machines, I struggled on my company laptop. Plugin installation with Packer was lagging at best sometimes even hanging. Digging deeper I was able to pin the problem to our companies proxy which was interfering in the package downloads.

Anyway as of August'23 it is announced on the Packer repo README, that it is not maintained anymore and suggested to move to another package manager.

lazy.nvim seemed to be the next best one package manager for me - also LazyVim distro which is based on that package manager and which I checked out earlier best related to what I was looking for. Additionally the download problems with our company proxy did not manifest here.

When converting from Packer to Lazy I wanted to clean up my configuraton file structure and follow some good practise (which is always subjective, I know) and hence I leaned on the NeoVim configuration of Josean Martinez which he explains in this video:

~/.config/nvim $ tree -n --charset UTF-16
|-- init.lua
|-- lazy-lock.json
`-- lua
    `-- kws
        |-- init.lua
        |-- lazy.lua
        |-- plugins
        |   |-- colorschema.lua
        |   |-- comment.lua
        |   |-- dap.lua
        |   |-- dressing.lua
        |   |-- harpoon.lua
        |   |-- init.lua
        |   |-- lsp
        |   |   |-- lspconfig.lua
        |   |   |-- mason.lua
        |   |   `-- null-ls.lua
        |   |-- lualine.lua
        |   |-- nvim-cmp.lua
        |   |-- nvim-tree.lua
        |   |-- nvim-treesitter.lua
        |   |-- nvim-treesitter-text-objects.lua
        |   |-- telescope.lua
        |   `-- which-key.lua
        |-- remap.lua
        `-- utils.lua
Enter fullscreen mode Exit fullscreen mode

So basically lazy.lua just bootstraps the package manager itself and then pulls in the plugin specifications from plugins and plugins/lsp folders.

utility scripts

NeoVimPluginInstall.ps1

While the setup PowerShell script above installs all the tools, I created another script which I use when starting NeoVim with the intention to install or update plugins. I did not want to put these folders on my search path permanently to not pollute the search path too much.

$llvmPath = Join-Path $(Get-ChildItem -Path $env:LOCALAPPDATA -Filter "llvm*x86_64" | Select-Object -ExpandProperty FullName) "bin"
$makePath = Join-Path ${env:ProgramFiles(x86)} "GnuWin32" "bin"
$env:Path += ";" + $llvmPath + ";" + $makePath
nvim $args
Enter fullscreen mode Exit fullscreen mode

getLatestGithubRepo.ps1

This script is a generalization of another post to find and download a file with a given complete file name or a file pattern from a GitHub repo's latest releases:

[CmdletBinding()]
param (
    [Parameter(Mandatory = $true, Position = 1)]
    [string] $Repository,
    [Parameter(Position = 2)]
    [string] $DownloadFile,
    [string] $DownloadFilePattern = "NOT_TO_BE_FOUND"
)

$latestRelease = Invoke-RestMethod -Method Get -Uri https://api.github.com/repos/$Repository/releases/latest -StatusCodeVariable sc

if ($sc -eq 200) {
    Write-Host $latestRelease.tag_name $latestRelease.published_at
    foreach ($asset in $latestRelease.assets) {
        Write-Host $asset.name $asset.size
        if($asset.name -eq $DownloadFile -or $asset.name -match $DownloadFilePattern) {
            $target = Join-Path $env:TEMP $DownloadFile
            Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $target
            Write-Host "downloaded" $asset.browser_download_url "to" $target
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The End

With this setup I am content for the moment. From here I will add LSP servers, stylers, linters and other plugins I expect to improve my productivity. Working exclusively with NeoVim for my very few coding workloads now for ~4 months gives me enough proficiency to really enjoy editing code in my spare time. If I just could have VIM motions in MS Outlook and MS Word 😏 I would not need to reconfigure my brain when switching from day job to spare time activity.

Top comments (0)