123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- -- helps managing loading different headers into the LuaJIT ffi. Untested on
- -- windows, will probably need quite a bit of adjustment to run there.
- local ffi = require('ffi')
- local global_t = require('test.testutil')
- local argss_to_cmd = global_t.argss_to_cmd
- local repeated_read_cmd = global_t.repeated_read_cmd
- --- @alias Compiler {path: string[], type: string}
- --- @type Compiler[]
- local ccs = {}
- local env_cc = os.getenv('CC')
- if env_cc then
- table.insert(ccs, { path = { '/usr/bin/env', env_cc }, type = 'gcc' })
- end
- if ffi.os == 'Windows' then
- table.insert(ccs, { path = { 'cl' }, type = 'msvc' })
- end
- table.insert(ccs, { path = { '/usr/bin/env', 'cc' }, type = 'gcc' })
- table.insert(ccs, { path = { '/usr/bin/env', 'gcc' }, type = 'gcc' })
- table.insert(ccs, { path = { '/usr/bin/env', 'gcc-4.9' }, type = 'gcc' })
- table.insert(ccs, { path = { '/usr/bin/env', 'gcc-4.8' }, type = 'gcc' })
- table.insert(ccs, { path = { '/usr/bin/env', 'gcc-4.7' }, type = 'gcc' })
- table.insert(ccs, { path = { '/usr/bin/env', 'clang' }, type = 'clang' })
- table.insert(ccs, { path = { '/usr/bin/env', 'icc' }, type = 'gcc' })
- -- parse Makefile format dependencies into a Lua table
- --- @param deps string
- --- @return string[]
- local function parse_make_deps(deps)
- -- remove line breaks and line concatenators
- deps = deps:gsub('\n', ''):gsub('\\', '')
- -- remove the Makefile "target:" element
- deps = deps:gsub('.+:', '')
- -- remove redundant spaces
- deps = deps:gsub(' +', ' ')
- -- split according to token (space in this case)
- local headers = {} --- @type string[]
- for token in deps:gmatch('[^%s]+') do
- -- headers[token] = true
- headers[#headers + 1] = token
- end
- -- resolve path redirections (..) to normalize all paths
- for i, v in ipairs(headers) do
- -- double dots (..)
- headers[i] = v:gsub('/[^/%s]+/%.%.', '')
- -- single dot (.)
- headers[i] = v:gsub('%./', '')
- end
- return headers
- end
- --- will produce a string that represents a meta C header file that includes
- --- all the passed in headers. I.e.:
- ---
- --- headerize({"stdio.h", "math.h"}, true)
- --- produces:
- --- #include <stdio.h>
- --- #include <math.h>
- ---
- --- headerize({"vim_defs.h", "memory.h"}, false)
- --- produces:
- --- #include "vim_defs.h"
- --- #include "memory.h"
- --- @param headers string[]
- --- @param global? boolean
- --- @return string
- local function headerize(headers, global)
- local fmt = global and '#include <%s>' or '#include "%s"'
- local formatted = {} --- @type string[]
- for _, hdr in ipairs(headers) do
- formatted[#formatted + 1] = string.format(fmt, hdr)
- end
- return table.concat(formatted, '\n')
- end
- --- @class Gcc
- --- @field path string
- --- @field preprocessor_extra_flags string[]
- --- @field get_defines_extra_flags string[]
- --- @field get_declarations_extra_flags string[]
- local Gcc = {
- preprocessor_extra_flags = {},
- get_defines_extra_flags = { '-std=c99', '-dM', '-E' },
- get_declarations_extra_flags = { '-std=c99', '-P', '-E' },
- }
- --- @param name string
- --- @param args string[]?
- --- @param val string?
- function Gcc:define(name, args, val)
- local define = string.format('-D%s', name)
- if args then
- define = string.format('%s(%s)', define, table.concat(args, ','))
- end
- if val then
- define = string.format('%s=%s', define, val)
- end
- self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = define
- end
- function Gcc:undefine(name)
- self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = '-U' .. name
- end
- function Gcc:init_defines()
- -- preprocessor flags that will hopefully make the compiler produce C
- -- declarations that the LuaJIT ffi understands.
- self:define('aligned', { 'ARGS' }, '')
- self:define('__attribute__', { 'ARGS' }, '')
- self:define('__asm', { 'ARGS' }, '')
- self:define('__asm__', { 'ARGS' }, '')
- self:define('__inline__', nil, '')
- self:define('EXTERN', nil, 'extern')
- self:define('INIT', { '...' }, '')
- self:define('_GNU_SOURCE')
- self:define('INCLUDE_GENERATED_DECLARATIONS')
- self:define('UNIT_TESTING')
- self:define('UNIT_TESTING_LUA_PREPROCESSING')
- -- Needed for FreeBSD
- self:define('_Thread_local', nil, '')
- -- Needed for macOS Sierra
- self:define('_Nullable', nil, '')
- self:define('_Nonnull', nil, '')
- self:undefine('__BLOCKS__')
- end
- --- @param obj? Compiler
- --- @return Gcc
- function Gcc:new(obj)
- obj = obj or {}
- setmetatable(obj, self)
- self.__index = self
- self:init_defines()
- return obj
- end
- --- @param ... string
- function Gcc:add_to_include_path(...)
- for i = 1, select('#', ...) do
- local path = select(i, ...)
- local ef = self.preprocessor_extra_flags
- ef[#ef + 1] = '-I' .. path
- end
- end
- -- returns a list of the headers files upon which this file relies
- --- @param hdr string
- --- @return string[]?
- function Gcc:dependencies(hdr)
- --- @type string
- local cmd = argss_to_cmd(self.path, { '-M', hdr }) .. ' 2>&1'
- local out = assert(io.popen(cmd))
- local deps = out:read('*a')
- out:close()
- if deps then
- return parse_make_deps(deps)
- end
- end
- --- @param defines string
- --- @return string
- function Gcc:filter_standard_defines(defines)
- if not self.standard_defines then
- local pseudoheader_fname = 'tmp_empty_pseudoheader.h'
- local pseudoheader_file = assert(io.open(pseudoheader_fname, 'w'))
- pseudoheader_file:close()
- local standard_defines = assert(
- repeated_read_cmd(
- self.path,
- self.preprocessor_extra_flags,
- self.get_defines_extra_flags,
- { pseudoheader_fname }
- )
- )
- os.remove(pseudoheader_fname)
- self.standard_defines = {} --- @type table<string,true>
- for line in standard_defines:gmatch('[^\n]+') do
- self.standard_defines[line] = true
- end
- end
- local ret = {} --- @type string[]
- for line in defines:gmatch('[^\n]+') do
- if not self.standard_defines[line] then
- ret[#ret + 1] = line
- end
- end
- return table.concat(ret, '\n')
- end
- --- returns a stream representing a preprocessed form of the passed-in headers.
- --- Don't forget to close the stream by calling the close() method on it.
- --- @param previous_defines string
- --- @param ... string
- --- @return string, string
- function Gcc:preprocess(previous_defines, ...)
- -- create pseudo-header
- local pseudoheader = headerize({ ... }, false)
- local pseudoheader_fname = 'tmp_pseudoheader.h'
- local pseudoheader_file = assert(io.open(pseudoheader_fname, 'w'))
- pseudoheader_file:write(previous_defines)
- pseudoheader_file:write('\n')
- pseudoheader_file:write(pseudoheader)
- pseudoheader_file:flush()
- pseudoheader_file:close()
- local defines = assert(
- repeated_read_cmd(
- self.path,
- self.preprocessor_extra_flags,
- self.get_defines_extra_flags,
- { pseudoheader_fname }
- )
- )
- defines = self:filter_standard_defines(defines)
- local declarations = assert(
- repeated_read_cmd(
- self.path,
- self.preprocessor_extra_flags,
- self.get_declarations_extra_flags,
- { pseudoheader_fname }
- )
- )
- os.remove(pseudoheader_fname)
- return declarations, defines
- end
- -- find the best cc. If os.exec causes problems on windows (like popping up
- -- a console window) we might consider using something like this:
- -- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec
- --- @param compilers Compiler[]
- --- @return Gcc?
- local function find_best_cc(compilers)
- for _, meta in pairs(compilers) do
- local version = assert(io.popen(tostring(meta.path) .. ' -v 2>&1'))
- version:close()
- if version then
- return Gcc:new({ path = meta.path })
- end
- end
- end
- -- find the best cc. If os.exec causes problems on windows (like popping up
- -- a console window) we might consider using something like this:
- -- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec
- local cc = assert(find_best_cc(ccs))
- local M = {}
- --- @param hdr string
- --- @return string[]?
- function M.includes(hdr)
- return cc:dependencies(hdr)
- end
- --- @param ... string
- --- @return string, string
- function M.preprocess(...)
- return cc:preprocess(...)
- end
- --- @param ... string
- function M.add_to_include_path(...)
- return cc:add_to_include_path(...)
- end
- return M
|