boltbuild 0.1.0

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

BoltPkgConfig = { }

local function extend_path(path, sysroot)
    if path:sub(1, 1) == '=' then
        if sysroot and sysroot ~= '' then
            return sysroot .. path:sub(3)
        else
            return path:sub(2)
        end
    else
        return path
    end
end

local function _run_pkg_config(pkg_name, lib_paths, seen)
    if seen[pkg_name] then
        return
    end
    seen[pkg_name] = true

    local expand = {}
    local configs = {}

    local config_file
    for _, p in ipairs(lib_paths) do
        config_file = p:make_node('pkgconfig/' .. pkg_name .. '.pc')
        if config_file:is_file() then
            break
        end
    end

    if not config_file or not config_file:is_file() then
        context:raise_error('no pkg-config file for library ' .. pkg_name)
    end

    local file = io.open(config_file:abs_path(), 'r')
    local lines = file:lines()
    for line in lines do
        line = line:match('^%s*(.-)%s*$')
        if line == '' or line:sub(1, 1) == '#' then
            goto continue
        end

        local pos = line:find('=')
        local pos2 = line:find(':')
        if pos and (not pos2 or pos2 > pos) then
            local var_name = line:sub(1, pos - 1):match('^%s*(.-)%s*$')
            local value = line:sub(pos + 1):match('^%s*(.-)%s*$')
            if value:sub(1, 1) == '"' and value:sub(-1) == '"' then
                value = value:sub(2, -2)
            end
            --if sysroot ~= '' and value:sub(1, 1) == '/' then
            --    value = sysroot .. value:sub(2)
            --end
            value = value:gsub('%${(%w+)}', function(name)
                return expand[name] or ''
            end)
            expand[var_name] = value
            goto continue
        end

        pos = line:find(':')
        if pos then
            local var_name = line:sub(1, pos - 1):match('^%s*(.-)%s*$')
            local value = line:sub(pos + 1):match('^%s*(.-)%s*$')
            value = value:gsub('%${(%w+)}', function(name)
                return expand[name] or ''
            end)
            configs[var_name] = {}
            for v in value:gmatch('[^%s]+') do
                table.insert(configs[var_name], v)
            end
        end

        :: continue ::
    end
    file:close()

    local cflags, libs, ldflags = {}, {}, {}
    local skip = false
    for _, d in ipairs(configs.Requires or {}) do
        if skip then
            skip = false
        elseif d == '=' or d == '<' or d == '<=' or d == '>' or d == '>=' then
            skip = true
        else
            if not seen[d] then
                local dep_flags = _run_pkg_config(d)
                for _, v in ipairs(dep_flags[1]) do
                    table.insert(cflags, v)
                end
                for _, v in ipairs(dep_flags[2]) do
                    table.insert(libs, v)
                end
                for _, v in ipairs(dep_flags[3]) do
                    table.insert(ldflags, v)
                end
            end
        end
    end

    for _, f in ipairs(configs.Cflags or {}) do
        if f:sub(1, 2) == '-I' then
            local include = f:sub(3)
            --if include:sub(1, 1) == '/' and not include:find(sysroot, 1, true) then
            --    include = sysroot .. include:sub(2)
            --end
            table.insert(cflags, context.env.C_IDIRAFTER .. include)
        else
            table.insert(cflags, f)
        end
    end

    for _, f in ipairs(configs.Libs or {}) do
        if f:sub(1, 2) == '-l' then
            table.insert(libs, f:sub(3))
        elseif f:sub(1, 2) == '-L' then
            local libdir = f:sub(3)
            --if libdir:sub(1, 1) == '/' and not libdir:find(sysroot, 1, true) then
            --    libdir = sysroot .. libdir
            --end
            table.insert(ldflags, context.env.LINK_LIBPATH_F .. libdir)
        else
            table.insert(ldflags, f)
        end
    end

    return cflags, libs, ldflags
end

---@param pkg_name string
---@return string[], string[], string[]
function BoltPkgConfig.run_pkg_config(pkg_name)
    local lib_paths = context.env.C_COMPILER_SYSTEM_LIB_DIRS or {}

    --for _, t in ipairs(context.env.TARGETS or {}) do
    --    table.insert(lib_paths, '=/usr/lib/' .. t)
    --    table.insert(lib_paths, '=/usr/libdata/' .. t)
    --end

    --for i, path in ipairs(lib_paths) do
    --    lib_paths[i] = extend_path(path, sysroot)
    --end

    local seen = {}

    return _run_pkg_config(pkg_name, lib_paths, seen)
end

function BoltPkgConfig.pkg_config(name, var)
    local cflags, libs, ldflags = BoltPkgConfig.run_pkg_config(name)
    context.env['check_' .. var] = true
    context.env['check_' .. var .. '_cflags'] = cflags
    context.env['check_' .. var .. '_cxxflags'] = cflags
    context.env['check_' .. var .. '_libs'] = libs
    context.env['check_' .. var .. '_ldflags'] = ldflags
end