boltbuild 0.1.0

BoltBuild is a programmable build system.
Documentation
---@type Context
local context = ...

context:load_tool('internal/compiler_gnu')

BoltGcc = {}

local function load_gcc_compiler(env, compiler, flags, language, var_name)
    env[var_name .. 'FLAGS'] = { '-x', language, '-c', '-fPIC' }
    env:append(var_name .. 'FLAGS', flags)
    env[var_name .. '_COMPILER_NAME'] = 'gcc'
    env[var_name .. '_TGT_F'] = '-o'
    env[var_name .. '_DEFINE_ST'] = '-D'
    env[var_name .. '_INCLUDE_ST'] = '-I'
    env[var_name .. '_SYSTEM_INCLUDE_ST'] = '-isystem%s'
    env[var_name .. '_IDIRAFTER'] = '-isystem'
    env.LINK = env.GCC
    env.LINKFLAGS = {}
    env.LINK_TGT_F = '-o'
    env.LINK_LIB_F = '-l'
    env.LINK_LIBPATH_F = '-L'
    env.LINKFLAGS_shlib = { '-shared', '-Wl,-z,defs' }

    local defines = BoltGnuCompiler.get_specs(compiler, var_name)
    local version = { 0, 0, 0 }

    for name, value in pairs(defines) do
        if name == '__GNUC__' then
            version[1] = tonumber(value)
        elseif name == '__GNUC_MINOR__' then
            version[2] = tonumber(value)
        elseif name == '__GNUC_PATCHLEVEL__' then
            version[3] = tonumber(value)
        end
    end

    env['GCC_' .. var_name .. '_VERSION'] = table.concat(version, '.')

    context:load_tool('internal/' .. language)
    context:load_tool('internal/link')
end

--- Loads the C compiler settings into the environment.
--- This function configures the environment to use the Gcc compiler for C.
--- It sets the necessary flags and libraries required for C compilation.
--- The function uses `context.env.GCC` as the base compiler command.
---
--- An error is raised if the compiler command returns an error code.
---
--- @param c_flags string[] A table containing extra flags that the C compiler should support.
function BoltGcc.load_c(c_flags)
    local env = context.env
    env.CC = env.GCC
    load_gcc_compiler(env, env.CC, c_flags, 'c', 'C')
end

--- Loads the C++ compiler settings into the environment.
--- This function configures the environment to use the Gcc compiler for C++.
--- It sets the necessary flags and libraries required for C++ compilation.
--- The function uses `context.env.GCC` as the base compiler command.
---
--- An error is raised if the compiler command returns an error code.
---
--- @param cxx_flags string[] A table containing extra flags that the C++ compiler should support.
function BoltGcc.load_cxx(cxx_flags)
    local env = context.env
    env.CXX = env.GCC
    env.LIBS = { 'stdc++' }
    load_gcc_compiler(env, env.CXX, cxx_flags, 'c++', 'CXX')
end

--- Loads the Objective-C compiler settings into the environment.
--- This function configures the environment to use the Gcc compiler for Objective-C.
--- It sets the necessary flags and libraries required for Objective-C compilation.
--- The function uses `context.env.GCC` as the base compiler command.
---
--- An error is raised if the compiler command returns an error code.
---
--- @param objc_flags string[] A table containing extra flags that the Objective-C compiler should support.
function BoltGcc.load_objc(objc_flags)
    local env = context.env
    env.OBJC = env.GCC
    env.LIBS = { 'objc' }
    load_gcc_compiler(env, env.OBJC, objc_flags, 'objc', 'OBJC')
end

--- Loads the Objective-C++ compiler settings into the environment.
--- This function configures the environment to use the Gcc compiler for Objective-C++.
--- It sets the necessary flags and libraries required for Objective-C++ compilation.
--- The function uses `context.env.GCC` as the base compiler command.
---
--- An error is raised if the compiler command returns an error code.
---
--- @param objcxx_flags string[] A table containing extra flags that the Objective-C++ compiler should support.
function BoltGcc.load_objcxx(objcxx_flags)
    local env = context.env
    env.OBJCXX = env.GCC
    env.LIBS = { 'stdc++', 'objc' }
    load_gcc_compiler(env, env.OBJCXX, objcxx_flags, 'objc++', 'OBJCXX')
end

local languages = {
    ['c'] = BoltGcc.load_c,
    ['c++'] = BoltGcc.load_cxx,
    ['objc'] = BoltGcc.load_objc,
    ['objc++'] = BoltGcc.load_objcxx,
}

--- Discovers available GCC compilers and configures the environment for each found compiler.
--- This function searches for GCC compilers in the specified paths, configures the environment
--- for each found compiler, and invokes the callback function with the configured environment.
--- It also supports detecting multilib configurations.
---
--- @param callback fun(env:Environment):(boolean|nil) The function to call with the configured environment.
--- @param language_flags table A table containing flags for each language to pass to the compiler.
--- @param global_flags table A table containing global flags to pass to the compiler.
--- @param detect_multilib boolean Whether to detect and configure multilib environments.
function BoltGcc.discover(callback, language_flags, global_flags, detect_multilib)
    context:try('Looking for Gcc compilers', function()
        local seen = {}
        local paths = context.settings.path

        for _, path in ipairs(paths) do
            for _, crtbegin in ipairs(context:search(path.parent, 'lib/gcc*/*/*/crtbegin.o')) do
                crtbegin = crtbegin:canonicalize()
                if not seen[crtbegin:abs_path()] then
                    local node = crtbegin.parent
                    local version = node:name()
                    node = node.parent
                    local target = node:name()
                    for _, gcc in ipairs(context:search(path, target .. '-gcc-' .. version .. context.settings.exe_suffix)) do
                        seen[crtbegin:abs_path()] = crtbegin
                        if pcall(function()
                            local env = context:derive()
                            context:with(env, function()
                                context.env.GCC = { gcc }
                                context.env:append('GCC', global_flags)
                                context.env.GCC_VERSION = version
                                for lang, flags in pairs(language_flags) do
                                    languages[lang](flags)
                                end
                            end)
                            if callback(env) ~= true then
                                return tostring(gcc)
                            end
                        end) and detect_multilib then
                            for _, multilib in ipairs(context:search(crtbegin.parent, '*/crtbegin.o')) do
                                pcall(function()
                                    local env = context:derive()
                                    context:with(env, function()
                                        context.env.CC = { gcc }
                                        context.env:append('GCC', global_flags)
                                        context.env:append('GCC', '-m' .. multilib.parent:name())
                                        context.env.GCC_VERSION = version
                                        for lang, flags in pairs(language_flags) do
                                            languages[lang](flags)
                                        end
                                    end)
                                    if callback(env) ~= true then
                                        return tostring(gcc)
                                    end
                                end)
                            end
                            break
                        end
                    end
                end
            end
        end
        return 'done'
    end)
end