local context = ...
context:load_tool('internal/compiler_gnu')
context:load_tool('utils/string_ext')
BoltClang = {}
local function get_clang_version(defines)
local version = { 0, 0, 0 }
for name, value in pairs(defines) do
if name == '__clang_major__' then
version[1] = tonumber(value)
elseif name == '__clang_minor__' then
version[2] = tonumber(value)
elseif name == '__clang_patchlevel__' then
version[3] = tonumber(value)
end
end
return table.concat(version, '.')
end
local function load_clang(env, compiler, flags, lang, var)
env[var .. 'FLAGS'] = { '-x', lang, '-c', '-fPIC' }
env:append(var .. 'FLAGS', flags)
env[var .. '_COMPILER_NAME'] = 'clang'
env[var .. '_TGT_F'] = '-o'
env[var .. '_DEFINE_ST'] = '-D'
env[var .. '_INCLUDE_ST'] = '-I'
env[var .. '_SYSTEM_INCLUDE_ST'] = '-isystem%s'
env[var .. '_IDIRAFTER'] = '-idirafter'
env.LINK = env.CLANG
env.LINKFLAGS = {}
env.LINK_TGT_F = '-o'
env.LINK_LIB_F = '-l'
env.LINK_LIBPATH_F = '-L'
env.LINKFLAGS_shlib = { '-shared', '-Wl,-z,defs' }
env['CLANG_' .. var .. '_VERSION'] = get_clang_version(BoltGnuCompiler.get_specs(compiler, var))
context:load_tool('internal/' .. lang)
context:load_tool('internal/link')
end
function BoltClang.load_c(c_flags)
local env = context.env
env.CC = env.CLANG
load_clang(env, env.CC, c_flags, 'c', 'C')
end
function BoltClang.load_cxx(cxx_flags)
local env = context.env
env.CXX = env.CLANG
env.LIBS = { 'stdc++' }
load_clang(env, env.CXX, cxx_flags, 'c++', 'CXX')
end
function BoltClang.load_objc(objc_flags)
local env = context.env
env.OBJC = env.CLANG
env.LIBS = { 'objc' }
load_clang(env, env.OBJC, objc_flags, 'objc', 'OBJC')
end
function BoltClang.load_objcxx(objcxx_flags)
local env = context.env
env.OBJCXX = env.CLANG
env.LIBS = { 'objc' }
load_clang(env, env.OBJCXX, objcxx_flags, 'objc++', 'OBJCXX')
end
local languages = {
['c'] = BoltClang.load_c,
['c++'] = BoltClang.load_cxx,
['objc'] = BoltClang.load_objc,
['objc++'] = BoltClang.load_objcxx,
}
local function detect_clang_targets(clang, callback, language_flags, global_flags)
local command
if #global_flags == 0 then
command = { clang, '-v', '-E', '-' }
else
command = { clang, #global_flags, '-v', '-E', '-' }
end
local _, out, err = context:popen(command):communicate()
local search_paths, paths, seen = false, {}, {}
for line in (err + out):lines() do
line = string.trim(line)
if line:find("#include <...>") then
search_paths = true
elseif line:find("ignoring nonexistent directory") then
table.insert(paths, context.path:make_node(line:sub(33, -2)))
elseif search_paths and line:find("End of search list") then
break
elseif search_paths then
table.insert(paths, context.path:make_node(line))
end
end
local default_triple = nil
local triples = { }
for _, path in ipairs(paths) do
local component, relpath, component_count = path:name(), '', 1
while component do
path = path.parent
local component_list = string.split(component, '-')
if #component_list >= 2 then
for _, triple in ipairs(context:search(path, '*-*/' .. relpath .. '/sys', true)) do
default_triple = component
for i = 1, component_count do
triple = triple.parent
end
triple = triple:name()
if not seen[triple] then
seen[triple] = true
table.insert(triples, triple)
end
end
end
relpath = component .. '/' .. relpath
component_count = component_count + 1
component = path:name()
end
end
table.sort(triples, function(a, b)
if a == default_triple then
return true
end
if b == default_triple then
return false
end
return a < b
end)
for _, triple in ipairs(triples) do
context:try('running clang ' .. clang:abs_path() .. ' for target ' .. triple, function()
local env = context:derive()
context:with(env, function()
context.env.CLANG = { clang, '-target', triple, table.unpack(global_flags) }
for lang, flags in pairs(language_flags) do
languages[lang](flags)
end
end)
if callback(env) ~= true then
return false
end
end)
end
return true
end
function BoltClang.discover(callback, language_flags, global_flags, detect_cross_targets)
local seen = {}
local compilers = {}
global_flags = global_flags or {}
context:try('Looking for Clang compilers', function()
for _, path in ipairs(context.settings.path) do
for _, node in ipairs(context:search(path, 'clang*' .. context.settings.exe_suffix)) do
local version = node:name():match("^clang%-?(%d*)" .. context.settings.exe_suffix .. "$")
if version ~= nil and node:is_file() then
node = node:read_link()
local absolute_path = node:abs_path()
if not seen[absolute_path] then
seen[absolute_path] = true
table.insert(compilers, { version, node })
end
end
end
end
table.sort(compilers, function(a, b)
if a[1] == "" then
return true
end
if b[1] == "" then
return false
end
return tonumber(a[1]) > tonumber(b[1])
end)
for _, compiler in ipairs(compilers) do
if detect_cross_targets then
if detect_clang_targets(compiler[2], callback, language_flags, global_flags) ~= true then
return tostring(compiler[2])
end
else
context:try('running clang ' .. compiler[2]:abs_path(), function()
local env = context:derive()
context:with(env, function()
context.env.CLANG = { compiler[2] }
context.env:append('CLANG', global_flags)
for lang, flags in pairs(language_flags) do
languages[lang](flags)
end
end)
if callback(env) ~= true then
return 'done'
end
end)
end
end
return 'done'
end)
end