preprocess.lua 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. -- helps managing loading different headers into the LuaJIT ffi. Untested on
  2. -- windows, will probably need quite a bit of adjustment to run there.
  3. local ffi = require("ffi")
  4. local global_helpers = require('test.helpers')
  5. local argss_to_cmd = global_helpers.argss_to_cmd
  6. local repeated_read_cmd = global_helpers.repeated_read_cmd
  7. local ccs = {}
  8. local env_cc = os.getenv("CC")
  9. if env_cc then
  10. table.insert(ccs, {path = {"/usr/bin/env", env_cc}, type = "gcc"})
  11. end
  12. if ffi.os == "Windows" then
  13. table.insert(ccs, {path = {"cl"}, type = "msvc"})
  14. end
  15. table.insert(ccs, {path = {"/usr/bin/env", "cc"}, type = "gcc"})
  16. table.insert(ccs, {path = {"/usr/bin/env", "gcc"}, type = "gcc"})
  17. table.insert(ccs, {path = {"/usr/bin/env", "gcc-4.9"}, type = "gcc"})
  18. table.insert(ccs, {path = {"/usr/bin/env", "gcc-4.8"}, type = "gcc"})
  19. table.insert(ccs, {path = {"/usr/bin/env", "gcc-4.7"}, type = "gcc"})
  20. table.insert(ccs, {path = {"/usr/bin/env", "clang"}, type = "clang"})
  21. table.insert(ccs, {path = {"/usr/bin/env", "icc"}, type = "gcc"})
  22. -- parse Makefile format dependencies into a Lua table
  23. local function parse_make_deps(deps)
  24. -- remove line breaks and line concatenators
  25. deps = deps:gsub("\n", ""):gsub("\\", "")
  26. -- remove the Makefile "target:" element
  27. deps = deps:gsub(".+:", "")
  28. -- remove redundant spaces
  29. deps = deps:gsub(" +", " ")
  30. -- split according to token (space in this case)
  31. local headers = {}
  32. for token in deps:gmatch("[^%s]+") do
  33. -- headers[token] = true
  34. headers[#headers + 1] = token
  35. end
  36. -- resolve path redirections (..) to normalize all paths
  37. for i, v in ipairs(headers) do
  38. -- double dots (..)
  39. headers[i] = v:gsub("/[^/%s]+/%.%.", "")
  40. -- single dot (.)
  41. headers[i] = v:gsub("%./", "")
  42. end
  43. return headers
  44. end
  45. -- will produce a string that represents a meta C header file that includes
  46. -- all the passed in headers. I.e.:
  47. --
  48. -- headerize({"stdio.h", "math.h"}, true)
  49. -- produces:
  50. -- #include <stdio.h>
  51. -- #include <math.h>
  52. --
  53. -- headerize({"vim.h", "memory.h"}, false)
  54. -- produces:
  55. -- #include "vim.h"
  56. -- #include "memory.h"
  57. local function headerize(headers, global)
  58. local pre = '"'
  59. local post = pre
  60. if global then
  61. pre = "<"
  62. post = ">"
  63. end
  64. local formatted = {}
  65. for _, hdr in ipairs(headers) do
  66. formatted[#formatted + 1] = "#include " ..
  67. tostring(pre) ..
  68. tostring(hdr) ..
  69. tostring(post)
  70. end
  71. return table.concat(formatted, "\n")
  72. end
  73. local Gcc = {
  74. preprocessor_extra_flags = {},
  75. get_defines_extra_flags = {'-std=c99', '-dM', '-E'},
  76. get_declarations_extra_flags = {'-std=c99', '-P', '-E'},
  77. }
  78. function Gcc:define(name, args, val)
  79. local define = '-D' .. name
  80. if args ~= nil then
  81. define = define .. '(' .. table.concat(args, ',') .. ')'
  82. end
  83. if val ~= nil then
  84. define = define .. '=' .. val
  85. end
  86. self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = define
  87. end
  88. function Gcc:undefine(name)
  89. self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = (
  90. '-U' .. name)
  91. end
  92. function Gcc:init_defines()
  93. -- preprocessor flags that will hopefully make the compiler produce C
  94. -- declarations that the LuaJIT ffi understands.
  95. self:define('aligned', {'ARGS'}, '')
  96. self:define('__attribute__', {'ARGS'}, '')
  97. self:define('__asm', {'ARGS'}, '')
  98. self:define('__asm__', {'ARGS'}, '')
  99. self:define('__inline__', nil, '')
  100. self:define('EXTERN', nil, 'extern')
  101. self:define('INIT', {'...'}, '')
  102. self:define('_GNU_SOURCE')
  103. self:define('INCLUDE_GENERATED_DECLARATIONS')
  104. self:define('UNIT_TESTING')
  105. self:define('UNIT_TESTING_LUA_PREPROCESSING')
  106. -- Needed for FreeBSD
  107. self:define('_Thread_local', nil, '')
  108. -- Needed for macOS Sierra
  109. self:define('_Nullable', nil, '')
  110. self:define('_Nonnull', nil, '')
  111. self:undefine('__BLOCKS__')
  112. end
  113. function Gcc:new(obj)
  114. obj = obj or {}
  115. setmetatable(obj, self)
  116. self.__index = self
  117. self:init_defines()
  118. return obj
  119. end
  120. function Gcc:add_to_include_path(...)
  121. for i = 1, select('#', ...) do
  122. local path = select(i, ...)
  123. local ef = self.preprocessor_extra_flags
  124. ef[#ef + 1] = '-I' .. path
  125. end
  126. end
  127. -- returns a list of the headers files upon which this file relies
  128. function Gcc:dependencies(hdr)
  129. local cmd = argss_to_cmd(self.path, {'-M', hdr}) .. ' 2>&1'
  130. local out = io.popen(cmd)
  131. local deps = out:read("*a")
  132. out:close()
  133. if deps then
  134. return parse_make_deps(deps)
  135. else
  136. return nil
  137. end
  138. end
  139. function Gcc:filter_standard_defines(defines)
  140. if not self.standard_defines then
  141. local pseudoheader_fname = 'tmp_empty_pseudoheader.h'
  142. local pseudoheader_file = io.open(pseudoheader_fname, 'w')
  143. pseudoheader_file:close()
  144. local standard_defines = repeated_read_cmd(self.path,
  145. self.preprocessor_extra_flags,
  146. self.get_defines_extra_flags,
  147. {pseudoheader_fname})
  148. os.remove(pseudoheader_fname)
  149. self.standard_defines = {}
  150. for line in standard_defines:gmatch('[^\n]+') do
  151. self.standard_defines[line] = true
  152. end
  153. end
  154. local ret = {}
  155. for line in defines:gmatch('[^\n]+') do
  156. if not self.standard_defines[line] then
  157. ret[#ret + 1] = line
  158. end
  159. end
  160. return table.concat(ret, "\n")
  161. end
  162. -- returns a stream representing a preprocessed form of the passed-in headers.
  163. -- Don't forget to close the stream by calling the close() method on it.
  164. function Gcc:preprocess(previous_defines, ...)
  165. -- create pseudo-header
  166. local pseudoheader = headerize({...}, false)
  167. local pseudoheader_fname = 'tmp_pseudoheader.h'
  168. local pseudoheader_file = io.open(pseudoheader_fname, 'w')
  169. pseudoheader_file:write(previous_defines)
  170. pseudoheader_file:write("\n")
  171. pseudoheader_file:write(pseudoheader)
  172. pseudoheader_file:flush()
  173. pseudoheader_file:close()
  174. local defines = repeated_read_cmd(self.path, self.preprocessor_extra_flags,
  175. self.get_defines_extra_flags,
  176. {pseudoheader_fname})
  177. defines = self:filter_standard_defines(defines)
  178. -- lfs = require("lfs")
  179. -- print("CWD: #{lfs.currentdir!}")
  180. -- print("CMD: #{cmd}")
  181. -- io.stderr\write("CWD: #{lfs.currentdir!}\n")
  182. -- io.stderr\write("CMD: #{cmd}\n")
  183. local declarations = repeated_read_cmd(self.path,
  184. self.preprocessor_extra_flags,
  185. self.get_declarations_extra_flags,
  186. {pseudoheader_fname})
  187. os.remove(pseudoheader_fname)
  188. assert(declarations and defines)
  189. return declarations, defines
  190. end
  191. local Clang = Gcc:new()
  192. local Msvc = Gcc:new()
  193. local type_to_class = {
  194. ["gcc"] = Gcc,
  195. ["clang"] = Clang,
  196. ["msvc"] = Msvc
  197. }
  198. -- find the best cc. If os.exec causes problems on windows (like popping up
  199. -- a console window) we might consider using something like this:
  200. -- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec
  201. local function find_best_cc(compilers)
  202. for _, meta in pairs(compilers) do
  203. local version = io.popen(tostring(meta.path) .. " -v 2>&1")
  204. version:close()
  205. if version then
  206. return type_to_class[meta.type]:new({path = meta.path})
  207. end
  208. end
  209. return nil
  210. end
  211. -- find the best cc. If os.exec causes problems on windows (like popping up
  212. -- a console window) we might consider using something like this:
  213. -- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec
  214. local cc = nil
  215. if cc == nil then
  216. cc = find_best_cc(ccs)
  217. end
  218. return {
  219. includes = function(hdr)
  220. return cc:dependencies(hdr)
  221. end,
  222. preprocess = function(...)
  223. return cc:preprocess(...)
  224. end,
  225. add_to_include_path = function(...)
  226. return cc:add_to_include_path(...)
  227. end
  228. }