feat!: use tree-sitter build

This commit is contained in:
Christian Clason 2024-04-22 19:56:30 +02:00
parent c17de56890
commit 214cfcf851
13 changed files with 443 additions and 898 deletions

View file

@ -23,19 +23,8 @@ local uv_symlink = a.wrap(uv.fs_symlink, 4)
--- @type fun(path: string): string?
local uv_unlink = a.wrap(uv.fs_unlink, 2)
local M = {}
local max_jobs = 10
local iswin = uv.os_uname().sysname == 'Windows_NT'
local ismac = uv.os_uname().sysname == 'Darwin'
--- @diagnostic disable-next-line:missing-parameter
M.compilers = { 'cc', 'gcc', 'clang', 'cl', 'zig' }
if uv.os_getenv('CC') then
table.insert(M.compilers, 1, uv.os_getenv('CC'))
end
local function system(cmd, opts)
log.trace('running job: (cwd=%s) %s', opts.cwd, table.concat(cmd, ' '))
local r = a.wrap(vim.system, 3)(cmd, opts) --[[@as vim.SystemCompleted]]
@ -50,6 +39,10 @@ local function system(cmd, opts)
return r
end
local iswin = uv.os_uname().sysname == 'Windows_NT'
local M = {}
---
--- PARSER INFO
---
@ -99,18 +92,6 @@ end
--- PARSER MANAGEMENT FUNCTIONS
---
local function istring(c)
return type(c) == 'string'
end
local function cc_err()
log.error(
'No C compiler found! "'
.. table.concat(vim.tbl_filter(istring, M.compilers), '", "')
.. '" are not executable.'
)
end
--- @param x string
--- @return boolean
local function executable(x)
@ -122,10 +103,6 @@ end
--- @param compile_location string
--- @return string? err
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
logger:info(
string.format(
'Generating parser.c from %s...',
@ -134,7 +111,7 @@ local function do_generate(logger, repo, compile_location)
)
local r = system({
fn.exepath('tree-sitter'),
'tree-sitter',
'generate',
'--no-bindings',
'--abi',
@ -232,10 +209,6 @@ 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({
@ -266,86 +239,6 @@ local function do_download_git(logger, repo, project_name, cache_dir, revision,
end
end
--- @type table<string,table<string,boolean>>
local cc_args_cache = vim.defaulttable()
--- @param cc string
--- @param arg string
--- @return boolean
local function test_cc_arg(cc, arg)
if cc_args_cache[cc][arg] == nil then
cc_args_cache[cc][arg] = system({ cc, '-xc', '-', arg }, {
stdin = 'int main(void) { return 0; }',
}).code == 0
end
return cc_args_cache[cc][arg]
end
---@param executables string[]
---@return string?
function M.select_executable(executables)
return vim.tbl_filter(executable, executables)[1]
end
-- Returns the compiler arguments based on the compiler and OS
---@param repo InstallInfo
---@param compiler string
---@return string[]
local function select_compiler_args(repo, compiler)
if compiler:find('cl$') or compiler:find('cl.exe$') then
return {
'/Fe:',
'parser.so',
'/Isrc',
repo.files,
'-Os',
'/utf-8',
'/LD',
}
end
if compiler:find('zig$') or compiler:find('zig.exe$') then
return {
'cc',
'-o',
'parser.so',
repo.files,
'-lc',
'-Isrc',
'-shared',
'-Os',
}
end
local args = {
'-o',
'parser.so',
'-I./src',
repo.files,
'-Os',
ismac and '-bundle' or '-shared',
}
--- @param arg string
local function add_cc_arg(arg)
if test_cc_arg(compiler, arg) then
args[#args + 1] = arg
end
end
if not iswin then
add_cc_arg('-Wall')
add_cc_arg('-Wextra')
add_cc_arg('-fPIC')
-- Make sure we don't compile in any unresolved symbols, otherwise nvim will
-- just exit (not even crash)
add_cc_arg('-Werror=implicit-function-declaration')
end
return args
end
---@param repo InstallInfo
---@return boolean
local function can_download_tar(repo)
@ -355,21 +248,40 @@ local function can_download_tar(repo)
return can_use_tar and (is_github or is_gitlab) and not iswin
end
-- Returns the compile command based on the OS and user options
---@param logger Logger
---@param repo InstallInfo
---@param cc string
---@param compile_location string
---@return string? err
local function do_compile(logger, repo, cc, compile_location)
local args = vim.iter(select_compiler_args(repo, cc)):flatten():totable()
local cmd = vim.list_extend({ cc }, args)
local function do_compile(logger, compile_location)
logger:info(string.format('Compiling parser'))
logger:info('Compiling parser')
local r = system(cmd, { cwd = compile_location })
local r = system({
'tree-sitter',
'build',
'-o',
'parser.so',
}, { cwd = compile_location })
if r.code > 0 then
return logger:error('Error during compilation: %s', r.stderr)
return logger:error('Error during "tree-sitter build": %s', r.stderr)
end
end
---@param logger Logger
---@param compile_location string
---@param target_location string
---@return string? err
local function do_install(logger, compile_location, target_location)
logger:info(string.format('Installing parser'))
if iswin then -- why can't you just be normal?!
local tempfile = target_location .. tostring(uv.hrtime())
uv_rename(target_location, tempfile) -- parser may be in use: rename...
uv_unlink(tempfile) -- ...and mark for garbage collection
end
local err = uv_copyfile(compile_location, target_location)
a.main()
if err then
return logger:error('Error during parser installation: %s', err)
end
end
@ -383,12 +295,6 @@ local function install_lang0(lang, cache_dir, install_dir, generate)
local repo = get_parser_install_info(lang)
if repo then
local cc = M.select_executable(M.compilers)
if not cc then
cc_err()
return
end
local project_name = 'tree-sitter-' .. lang
local revision = repo.revision
@ -414,7 +320,7 @@ local function install_lang0(lang, cache_dir, install_dir, generate)
compile_location = fs.joinpath(compile_location, repo.location)
end
do
do -- generate parser from grammar
if repo.generate or generate then
local err = do_generate(logger, repo, compile_location)
if err then
@ -423,24 +329,25 @@ local function install_lang0(lang, cache_dir, install_dir, generate)
end
end
do
local err = do_compile(logger, repo, cc, compile_location)
do -- compile parser
local err = do_compile(logger, compile_location)
if err then
return err
end
end
local parser_lib_name = fs.joinpath(install_dir, lang) .. '.so'
do -- install parser
local parser_lib_name = fs.joinpath(compile_location, 'parser.so')
local install_location = fs.joinpath(install_dir, lang) .. '.so'
local err = do_install(logger, parser_lib_name, install_location)
if err then
return err
end
local err = uv_copyfile(fs.joinpath(compile_location, 'parser.so'), parser_lib_name)
a.main()
if err then
return logger:error(err)
local revfile = fs.joinpath(config.get_install_dir('parser-info') or '', lang .. '.revision')
util.write_file(revfile, revision or '')
end
local revfile = fs.joinpath(config.get_install_dir('parser-info') or '', lang .. '.revision')
util.write_file(revfile, revision or '')
if not repo.path then
util.delete(fs.joinpath(cache_dir, project_name))
end