Files
nuwiki/scripts/download_bin.vim
T
gffranco 95c054ef80
CI / editor tests (push) Successful in 1m28s
fix(install): prefer the musl asset on NixOS and other non-FHS hosts
NixOS keeps glibc in the store, so a generic `-gnu` release binary cannot
find its dynamic loader. The old detection missed this twice over: `ldd
--version` reports plain GNU libc there, and `/lib64/ld-linux-x86-64.so.2`
does exist — as a stub that only prints an error and exits 127. The
installer downloaded the gnu asset, `executable()` returned 1, and the
binary could never start.

Detect non-FHS distributions (`/etc/NIXOS`, `ID=nixos`/`ID=guix` in
os-release) and pick the statically linked musl asset there. Turn the
single target triple into an ordered candidate list so glibc hosts fall
back to musl too, and verify the staged binary actually execs — exit 126
or 127 means the kernel or loader refused it — before it lands in `bin/`.

Applied to both installers (Lua and the Vim build hook, which the shell
test harnesses source). Adds `g:nuwiki_ls_target` to force a triple.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qPCSWXxATFvjEpVxQMhG9
2026-08-07 14:58:48 -03:00

161 lines
5.7 KiB
VimL

" scripts/download_bin.vim — install `nuwiki-ls` from a release tarball,
" falling back to `cargo build --release` from the nuwiki-rs repo.
"
" Invoked by Dein / vim-plug build hooks:
" vim -e -s -c "source scripts/download_bin.vim" -c "q"
"
" That invocation runs in 'compatible' mode, where leading-backslash line
" continuations aren't recognised (they'd raise E10, and any error in silent
" Ex mode makes Vim exit non-zero — a spurious build failure). Reset
" 'cpoptions' to the Vim default for the duration so the script parses, and
" restore it at the end.
let s:cpo_save = &cpo
set cpo&vim
let s:plugin_dir = fnamemodify(resolve(expand('<sfile>:p')), ':h:h')
let s:bin_dir = s:plugin_dir . '/bin'
let s:suffix = has('win32') ? '.exe' : ''
let s:bin = s:bin_dir . '/nuwiki-ls' . s:suffix
let s:rs_repo = fnamemodify(s:plugin_dir, ':h:h') . '/nuwiki-rs'
call mkdir(s:bin_dir, 'p')
function! s:is_musl(arch) abort
if filereadable('/lib/ld-musl-' . a:arch . '.so.1')
return 1
endif
if !executable('ldd')
return 0
endif
" musl's ldd writes its banner to stderr.
return stridx(system('ldd --version 2>&1'), 'musl') >= 0
endfunction
" NixOS and Guix System are not FHS distributions: glibc lives in the store,
" so the /lib64/ld-linux-*.so.2 a generic -gnu binary is linked against is
" either absent or — on NixOS — a stub that only prints an error and exits
" 127. `ldd --version` still reports GNU libc there, so libc detection alone
" picks the wrong asset. The musl builds are statically linked and need no
" loader at all, so they run on these systems unchanged.
function! s:is_non_fhs_linux() abort
if filereadable('/etc/NIXOS')
return 1
endif
if !filereadable('/etc/os-release')
return 0
endif
for l:line in readfile('/etc/os-release')
let l:id = matchstr(l:line, '^ID=\s*"\?\zs[[:alnum:]._-]\+')
if l:id ==# 'nixos' || l:id ==# 'guix'
return 1
endif
endfor
return 0
endfunction
" Release triples to try, best first. More than one entry means the later ones
" are fallbacks for a host that turns out not to be able to run the first.
function! s:targets() abort
if get(g:, 'nuwiki_ls_target', '') !=# ''
return [g:nuwiki_ls_target]
endif
if has('win32')
return ['x86_64-pc-windows-msvc']
elseif has('mac')
return [has('arm64') ? 'aarch64-apple-darwin' : 'x86_64-apple-darwin']
elseif has('linux')
let l:uname_m = trim(system('uname -m'))
let l:arch = (l:uname_m ==# 'aarch64' || l:uname_m ==# 'arm64')
\ ? 'aarch64' : 'x86_64'
let l:musl = l:arch . '-unknown-linux-musl'
if s:is_musl(l:arch) || s:is_non_fhs_linux()
return [l:musl]
endif
" Static musl runs anywhere, so keep it as a last resort on glibc hosts
" too (old glibc, exotic loader layouts).
return [l:arch . '-unknown-linux-gnu', l:musl]
endif
return []
endfunction
" Can this host actually start the binary? Exit codes 126 and 127 mean the
" kernel or the dynamic loader refused to exec it (missing/stub loader, wrong
" architecture); any other code means it ran and exited on its own terms.
" system() gives the child an empty stdin, so the LSP loop sees EOF and stops.
function! s:can_exec(path) abort
call system(shellescape(a:path) . ' --version')
return v:shell_error != 126 && v:shell_error != 127
endfunction
function! s:build_from_source() abort
if !executable('cargo')
echohl ErrorMsg | echom 'nuwiki: cargo not found, cannot build from source' | echohl None
return 0
endif
echom 'nuwiki: cloning nuwiki-rs …'
if !filereadable(s:rs_repo . '/Cargo.toml')
call system('git clone --depth 1 https://code.gfran.co/gffranco/nuwiki-rs.git ' . shellescape(s:rs_repo))
if v:shell_error != 0
echohl ErrorMsg | echom 'nuwiki: failed to clone nuwiki-rs' | echohl None
return 0
endif
endif
echom 'nuwiki: cargo build --release …'
call system('cargo build --release -p nuwiki-ls --manifest-path ' . shellescape(s:rs_repo . '/Cargo.toml'))
if v:shell_error != 0
echohl ErrorMsg | echom 'nuwiki: cargo build failed' | echohl None
return 0
endif
let l:built = s:rs_repo . '/target/release/nuwiki-ls' . s:suffix
call system('cp ' . shellescape(l:built) . ' ' . shellescape(s:bin))
call system('chmod +x ' . shellescape(s:bin))
return executable(s:bin)
endfunction
function! s:download_release(target) abort
if !executable('curl') || !executable('tar')
return 0
endif
let l:url = 'https://code.gfran.co/gffranco/nuwiki-rs/releases/download/latest/nuwiki-ls-'
\ . a:target . '.tar.gz'
let l:archive = tempname() . '.tar.gz'
call system('curl -fsSL -o ' . shellescape(l:archive) . ' ' . shellescape(l:url))
if v:shell_error != 0
return 0
endif
let l:extract = tempname()
call mkdir(l:extract, 'p')
call system('tar -xzf ' . shellescape(l:archive) . ' -C ' . shellescape(l:extract))
if v:shell_error != 0
return 0
endif
" Vet the staged copy before it becomes the installed one, so a binary this
" host cannot exec never lands at s:bin for the LSP client to pick up.
let l:staged = l:extract . '/nuwiki-ls' . s:suffix
call system('chmod +x ' . shellescape(l:staged))
if !executable(l:staged) || !s:can_exec(l:staged)
return 0
endif
call system('cp ' . shellescape(l:staged) . ' ' . shellescape(s:bin))
call system('chmod +x ' . shellescape(s:bin))
return executable(s:bin)
endfunction
let s:installed = 0
for s:t in s:targets()
if s:download_release(s:t)
echom 'nuwiki: installed ' . s:bin . ' (' . s:t . ')'
let s:installed = 1
break
endif
endfor
if !s:installed
echohl WarningMsg | echom 'nuwiki: download failed, falling back to source build' | echohl None
if !s:build_from_source()
echohl ErrorMsg | echom 'nuwiki: install failed' | echohl None
endif
endif
let &cpo = s:cpo_save