feat(install)!: generate from json instead of requiring node

Problem: Many parsers require node/npm to evaluate the `grammar.js`
before being able to generate a parser from it.

Solution: Generate from `grammar.json` instead, which is fully resolved.
Drops `node` and `npm` as (optional) requirements for nvim-treesitter.

Note that this requires parsers to commit the generated json iff the
grammar requires evaluation (which is currently the case for all tracked
languages).
This commit is contained in:
Christian Clason 2024-04-18 09:44:38 +02:00
parent 8f8cf7144d
commit 5a38df5627
10 changed files with 502 additions and 484 deletions

View file

@ -37,16 +37,8 @@ local function install_health()
)
end
if vim.fn.executable('node') == 0 then
health.warn('`node` executable not found (only needed for `:TSInstallFromGrammar`.')
else
local result = assert(vim.system({ 'node', '--version' }):wait().stdout)
local version = vim.split(result, '\n')[1]
health.ok('`node` found ' .. version .. ' (only needed for `:TSInstallFromGrammar`)')
end
if vim.fn.executable('git') == 0 then
health.error(
health.warn(
'`git` executable not found.',
'Install it with your package manager and check that your `$PATH` is set correctly.'
)

View file

@ -31,7 +31,7 @@ local M = {}
---@type table<string, LockfileInfo>
local lockfile = {}
local max_jobs = 50
local max_jobs = 10
local iswin = uv.os_uname().sysname == 'Windows_NT'
local ismac = uv.os_uname().sysname == 'Darwin'
@ -145,24 +145,17 @@ end
--- @param repo InstallInfo
--- @param compile_location string
--- @return string? err
local function do_generate_from_grammar(logger, repo, compile_location)
local function do_generate(logger, repo, compile_location)
if not executable('tree-sitter') then
return logger:error('tree-sitter CLI not found: `tree-sitter` is not executable')
end
if repo.generate_requires_npm then
if not executable('npm') then
return logger:error('NPM requires to be installed from grammar.js')
end
logger:info('Installing NPM dependencies')
local r = system({ 'npm', 'install' }, { cwd = compile_location })
if r.code > 0 then
return logger:error('Error during `npm install`: %s', r.stderr)
end
end
logger:info('Generating source files from grammar.js...')
logger:info(
string.format(
'Generating parser.c from %s...',
repo.generate_from_json and 'grammar.json' or 'grammar.js'
)
)
local r = system({
fn.exepath('tree-sitter'),
@ -170,6 +163,7 @@ local function do_generate_from_grammar(logger, repo, compile_location)
'--no-bindings',
'--abi',
tostring(vim.treesitter.language_version),
repo.generate_from_json and 'src/grammar.json',
}, { cwd = compile_location })
if r.code > 0 then
return logger:error('Error during "tree-sitter generate": %s', r.stderr)
@ -262,6 +256,10 @@ end
---@param project_dir string
---@return string? err
local function do_download_git(logger, repo, project_name, cache_dir, revision, project_dir)
if not executable('git') then
return logger:error('git not found!')
end
logger:info('Downloading ' .. project_name .. '...')
local r = system({
@ -386,7 +384,7 @@ end
---@param repo InstallInfo
---@param cc string
---@param compile_location string
--- @return string? err
---@return string? err
local function do_compile(logger, repo, cc, compile_location)
local args = vim.tbl_flatten(select_compiler_args(repo, cc))
local cmd = vim.list_extend({ cc }, args)
@ -442,7 +440,7 @@ local function install_lang0(lang, cache_dir, install_dir, generate)
do
if repo.generate or generate then
local err = do_generate_from_grammar(logger, repo, compile_location)
local err = do_generate(logger, repo, compile_location)
if err then
return err
end
@ -497,9 +495,9 @@ local INSTALL_TIMEOUT = 60000
---@param cache_dir string
---@param install_dir string
---@param force? boolean
---@param generate_from_grammar? boolean
---@param generate? boolean
---@return InstallStatus status
local function install_lang(lang, cache_dir, install_dir, force, generate_from_grammar)
local function install_lang(lang, cache_dir, install_dir, force, generate)
if not force and vim.list_contains(config.installed_parsers(), lang) then
local yesno = fn.input(lang .. ' parser already available: would you like to reinstall ? y/n: ')
print('\n ')
@ -518,7 +516,7 @@ local function install_lang(lang, cache_dir, install_dir, force, generate_from_g
end
else
install_status[lang] = 'installing'
local err = install_lang0(lang, cache_dir, install_dir, generate_from_grammar)
local err = install_lang0(lang, cache_dir, install_dir, generate)
install_status[lang] = err and 'failed' or 'installed'
end
@ -529,7 +527,7 @@ end
---@class InstallOptions
---@field force? boolean
---@field generate_from_grammar? boolean
---@field generate? boolean
---@field skip? table
--- Install a parser
@ -539,7 +537,7 @@ end
local function install(languages, options, _callback)
options = options or {}
local force = options.force
local generate_from_grammar = options.generate_from_grammar
local generate = options.generate
local skip = options.skip
local cache_dir = vim.fs.normalize(fn.stdpath('cache'))
@ -560,7 +558,7 @@ local function install(languages, options, _callback)
for _, lang in ipairs(languages) do
tasks[#tasks + 1] = a.sync(function()
a.main()
local status = install_lang(lang, cache_dir, install_dir, force, generate_from_grammar)
local status = install_lang(lang, cache_dir, install_dir, force, generate)
if status ~= 'failed' then
done = done + 1
end

File diff suppressed because it is too large Load diff