From 4e40c6f2ed74776b1e46d368199e10c7dc18c7b8 Mon Sep 17 00:00:00 2001 From: erorcun Date: Sun, 15 Aug 2021 21:24:20 +0300 Subject: [PATCH] Auto-detect X11 dependency --- autoconf/LICENSE.txt | 27 ++++ autoconf/api.lua | 305 +++++++++++++++++++++++++++++++++++++++++ autoconf/autoconf.lua | 18 +++ autoconf/clang.lua | 27 ++++ autoconf/gcc.lua | 27 ++++ autoconf/msc.lua | 62 +++++++++ premake5.lua | 21 ++- src/CMakeLists.txt | 16 +++ src/skel/glfw/glfw.cpp | 6 +- 9 files changed, 501 insertions(+), 8 deletions(-) create mode 100644 autoconf/LICENSE.txt create mode 100644 autoconf/api.lua create mode 100644 autoconf/autoconf.lua create mode 100644 autoconf/clang.lua create mode 100644 autoconf/gcc.lua create mode 100644 autoconf/msc.lua diff --git a/autoconf/LICENSE.txt b/autoconf/LICENSE.txt new file mode 100644 index 00000000..eb1b1720 --- /dev/null +++ b/autoconf/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2016 Blizzard Entertainment and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of Premake nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/autoconf/api.lua b/autoconf/api.lua new file mode 100644 index 00000000..064ea795 --- /dev/null +++ b/autoconf/api.lua @@ -0,0 +1,305 @@ +--- +-- Autoconfiguration. +-- Copyright (c) 2016 Blizzard Entertainment +-- Enhanced by re3 +--- +local p = premake +local autoconf = p.modules.autoconf +autoconf.cache = {} +autoconf.parameters = "" + + +--- +-- register autoconfigure api. +--- +p.api.register { + name = "autoconfigure", + scope = "config", + kind = "table" +} + +--- +-- Check for a particular include file. +-- +-- @cfg : Current config. +-- @variable : The variable to store the result, such as 'HAVE_STDINT_H'. +-- @filename : The header file to check for. +--- +function check_include(cfg, variable, filename) + local res = autoconf.cache_compile(cfg, variable, function () + p.outln('#include <' .. filename .. '>') + p.outln('int main(void) { return 0; }') + end) + + if res.value then + autoconf.set_value(cfg, variable, 1) + end +end + + +--- +-- Check for size of a particular type. +-- +-- @cfg : Current config. +-- @variable : The variable to use, such as 'SIZEOF_SIZE_T', this method will also add "'HAVE_' .. variable". +-- @type : The type to check. +-- @headers : An optional array of header files to include. +-- @defines : An optional array of defines to define. +--- +function check_type_size(cfg, variable, type, headers, defines) + check_include(cfg, 'HAVE_SYS_TYPES_H', 'sys/types.h') + check_include(cfg, 'HAVE_STDINT_H', 'stdint.h') + check_include(cfg, 'HAVE_STDDEF_H', 'stddef.h') + + local res = autoconf.cache_compile(cfg, variable .. cfg.platform, + function () + if cfg.autoconf['HAVE_SYS_TYPES_H'] then + p.outln('#include ') + end + + if cfg.autoconf['HAVE_STDINT_H'] then + p.outln('#include ') + end + + if cfg.autoconf['HAVE_STDDEF_H'] then + p.outln('#include ') + end + + autoconf.include_defines(defines) + autoconf.include_headers(headers) + p.outln("") + p.outln("#define SIZE (sizeof(" .. type .. "))") + p.outln("char info_size[] = {'I', 'N', 'F', 'O', ':', 's','i','z','e','[',") + p.outln(" ('0' + ((SIZE / 10000)%10)),") + p.outln(" ('0' + ((SIZE / 1000)%10)),") + p.outln(" ('0' + ((SIZE / 100)%10)),") + p.outln(" ('0' + ((SIZE / 10)%10)),") + p.outln(" ('0' + (SIZE %10)),") + p.outln(" ']', '\\0'};") + p.outln("") + p.outln("int main(int argc, char *argv[]) {") + p.outln(" int require = 0;") + p.outln(" require += info_size[argc];") + p.outln(" (void)argv;") + p.outln(" return require;") + p.outln("}") + end, + function (e) + -- if the compile step succeeded, we should have a binary with 'INFO:size[*****]' + -- somewhere in there. + local content = io.readfile(e.binary) + if content then + local size = string.find(content, 'INFO:size') + if size then + e.size = tonumber(string.sub(content, size+10, size+14)) + end + end + end + ) + + if res.size then + autoconf.set_value(cfg, 'HAVE_' .. variable, 1) + autoconf.set_value(cfg, variable, res.size) + end +end + + +--- +-- Check if the given struct or class has the specified member variable +-- +-- @cfg : current config. +-- @variable : variable to store the result. +-- @type : the name of the struct or class you are interested in +-- @member : the member which existence you want to check +-- @headers : an optional array of header files to include. +-- @defines : An optional array of defines to define. +--- +function check_struct_has_member(cfg, variable, type, member, headers, defines) + local res = autoconf.cache_compile(cfg, variable, function () + autoconf.include_defines(defines) + autoconf.include_headers(headers) + p.outln('int main(void) {') + p.outln(' (void)sizeof(((' .. type .. '*)0)->' .. member ..');') + p.outln(' return 0;') + p.outln('}') + end) + + if res.value then + autoconf.set_value(cfg, variable, 1) + end +end + + +--- +-- Check if a symbol exists as a function, variable, or macro +-- +-- @cfg : current config. +-- @variable : variable to store the result. +-- @symbol : The symbol to check for. +-- @headers : an optional array of header files to include. +-- @defines : An optional array of defines to define. +--- +function check_symbol_exists(cfg, variable, symbol, headers, defines) + local h = headers + local res = autoconf.cache_compile(cfg, variable, function () + autoconf.include_defines(defines) + autoconf.include_headers(headers) + p.outln('int main(int argc, char** argv) {') + p.outln(' (void)argv;') + p.outln('#ifndef ' .. symbol) + p.outln(' return ((int*)(&' .. symbol .. '))[argc];') + p.outln('#else') + p.outln(' (void)argc;') + p.outln(' return 0;') + p.outln('#endif') + p.outln('}') + end) + + if res.value then + autoconf.set_value(cfg, variable, 1) + end +end + + +--- +-- try compiling a piece of c/c++ +--- +function autoconf.try_compile(cfg, cpp) + local ts = autoconf.toolset(cfg) + if ts then + return ts.try_compile(cfg, cpp, autoconf.parameters) + else + p.warnOnce('autoconf', 'no toolset found, autoconf always failing.') + end +end + + +function autoconf.cache_compile(cfg, entry, func, post) + if not autoconf.cache[entry] then + local cpp = p.capture(func) + local res = autoconf.try_compile(cfg, cpp) + if res then + local e = { binary = res, value = true } + if post then + post(e) + end + autoconf.cache[entry] = e + else + autoconf.cache[entry] = { } + end + end + return autoconf.cache[entry] +end + + +--- +-- get the current configured toolset, or the default. +--- +function autoconf.toolset(cfg) + local ts = p.config.toolset(cfg) + if not ts then + local tools = { + -- Actually we always return nil on msc. see msc.lua + ['vs2010'] = p.tools.msc, + ['vs2012'] = p.tools.msc, + ['vs2013'] = p.tools.msc, + ['vs2015'] = p.tools.msc, + ['vs2017'] = p.tools.msc, + ['vs2019'] = p.tools.msc, + ['gmake'] = premake.tools.gcc, + ['gmake2'] = premake.tools.gcc, + ['codelite'] = premake.tools.gcc, + ['xcode4'] = premake.tools.clang, + } + ts = tools[_ACTION] + end + return ts +end + + +--- +-- store the value of the variable in the configuration +--- +function autoconf.set_value(cfg, variable, value) + cfg.autoconf[variable] = value +end + + +--- +-- write the cfg.autoconf table to the file +--- +function autoconf.writefile(cfg, filename) + if cfg.autoconf then + local file = io.open(filename, "w+") + for variable, value in pairs(cfg.autoconf) do + file:write('#define ' .. variable .. ' ' .. tostring(value) .. (_eol or '\n')) + end + file:close() + end +end + + +--- +-- Utility method to add a table of headers. +--- +function autoconf.include_headers(headers) + if headers ~= nil then + if type(headers) == "table" then + for _, v in ipairs(headers) do + p.outln('#include <' .. v .. '>') + end + else + p.outln('#include <' .. headers .. '>') + end + end +end + +function autoconf.include_defines(defines) + if defines ~= nil then + if type(defines) == "table" then + for _, v in ipairs(defines) do + p.outln('#define ' .. v) + end + else + p.outln('#define ' .. defines) + end + end +end + +--- +-- attach ourselfs to the running action. +--- +p.override(p.action, 'call', function (base, name) + local a = p.action.get(name) + + -- store the old callback. + local onBaseProject = a.onProject or a.onproject + + -- override it with our own. + a.onProject = function(prj) + -- go through each configuration, and call the setup configuration methods. + for cfg in p.project.eachconfig(prj) do + cfg.autoconf = {} + if cfg.autoconfigure then + verbosef('Running auto config steps for "%s/%s".', prj.name, cfg.name) + for file, func in pairs(cfg.autoconfigure) do + func(cfg) + + if not (file ~= "dontWrite") then + os.mkdir(cfg.objdir) + local filename = path.join(cfg.objdir, file) + autoconf.writefile(cfg, filename) + end + end + end + end + + -- then call the old onProject. + if onBaseProject then + onBaseProject(prj) + end + end + + -- now call the original action.call methods + base(name) +end) diff --git a/autoconf/autoconf.lua b/autoconf/autoconf.lua new file mode 100644 index 00000000..6c99f9da --- /dev/null +++ b/autoconf/autoconf.lua @@ -0,0 +1,18 @@ +--- +-- Autoconfiguration. +-- Copyright (c) 2016 Blizzard Entertainment +--- + local p = premake + + if not premake.modules.autoconf then + p.modules.autoconf = {} + p.modules.autoconf._VERSION = p._VERSION + + verbosef('Loading autoconf module...') + include('api.lua') + include('msc.lua') + include('clang.lua') + include('gcc.lua') + end + + return p.modules.autoconf diff --git a/autoconf/clang.lua b/autoconf/clang.lua new file mode 100644 index 00000000..fdb5f405 --- /dev/null +++ b/autoconf/clang.lua @@ -0,0 +1,27 @@ +--- +-- Autoconfiguration. +-- Copyright (c) 2016 Blizzard Entertainment +--- +local p = premake +local clang = p.tools.clang + +function clang.try_compile(cfg, text, parameters) + -- write the text to a temporary file. + local cppFile = path.join(cfg.objdir, "temp.cpp") + if not io.writefile(cppFile, text) then + return nil + end + + if parameters == nil then + parameters = "" + end + + local outFile = path.join(cfg.objdir, "temp.out") + + -- compile that text file. + if os.execute('clang "' .. cppFile .. '" ' .. parameters .. ' -o "' .. outFile ..'" &> /dev/null') then + return outFile + else + return nil + end +end diff --git a/autoconf/gcc.lua b/autoconf/gcc.lua new file mode 100644 index 00000000..34520139 --- /dev/null +++ b/autoconf/gcc.lua @@ -0,0 +1,27 @@ +--- +-- Autoconfiguration. +-- Copyright (c) 2016 Blizzard Entertainment +--- +local p = premake +local gcc = p.tools.gcc + +function gcc.try_compile(cfg, text, parameters) + -- write the text to a temporary file. + local cppFile = path.join(cfg.objdir, "temp.cpp") + if not io.writefile(cppFile, text) then + return nil + end + + if parameters == nil then + parameters = "" + end + + local outFile = path.join(cfg.objdir, "temp.out") + + -- compile that text file. + if os.execute('gcc "' .. cppFile .. '" ' .. parameters .. ' -o "' .. outFile ..'" &> /dev/null') then + return outFile + else + return nil + end +end diff --git a/autoconf/msc.lua b/autoconf/msc.lua new file mode 100644 index 00000000..b96a82ec --- /dev/null +++ b/autoconf/msc.lua @@ -0,0 +1,62 @@ +--- +-- Autoconfiguration. +-- Copyright (c) 2016 Blizzard Entertainment +--- +local p = premake +local msc = p.tools.msc + +-- "parameters" is unused, matter of fact this file is unused - re3 +function msc.try_compile(cfg, text, parameters) + + return nil +--[[ + -- write the text to a temporary file. + local cppFile = path.join(cfg.objdir, "temp.cpp") + if not io.writefile(cppFile, text) then + return nil + end + + -- write out a batch file. + local batch = p.capture(function () + p.outln('@echo off') + p.outln('SET mypath=%~dp0') + p.outln('pushd %mypath%') + + local map = { + vs2010 = 'VS100COMNTOOLS', + vs2012 = 'VS110COMNTOOLS', + vs2013 = 'VS120COMNTOOLS', + vs2015 = 'VS140COMNTOOLS', + vs2017 = 'VS141COMNTOOLS', + vs2019 = 'VS142COMNTOOLS', + } + + local a = map[_ACTION] + if a then + a = path.translate(os.getenv(a), '/') + a = path.join(a, '../../VC/vcvarsall.bat') + + if cfg.platform == 'x86' then + p.outln('call "' .. a .. '" > NUL') + else + p.outln('call "' .. a .. '" amd64 > NUL') + end + + p.outln('cl.exe /nologo temp.cpp > NUL') + else + error('Unsupported Visual Studio version: ' .. _ACTION) + end + end) + + local batchFile = path.join(cfg.objdir, "compile.bat") + if not io.writefile(batchFile, batch) then + return nil + end + + if os.execute(batchFile) then + return path.join(cfg.objdir, "temp.exe") + else + return nil + end +--]] +end diff --git a/premake5.lua b/premake5.lua index 4223b395..68e7aa0c 100644 --- a/premake5.lua +++ b/premake5.lua @@ -42,6 +42,8 @@ newoption { description = "Don't print full paths into binary" } +require("autoconf") + if(_OPTIONS["with-librw"]) then Librw = "vendor/librw" else @@ -364,6 +366,19 @@ project "reVC" filter "platforms:win*glfw*" staticruntime "off" + + filter "platforms:*glfw*" + premake.modules.autoconf.parameters = "-lglfw -lX11" + autoconfigure { + -- iterates all configs and runs on them + ["dontWrite"] = function (cfg) + check_symbol_exists(cfg, "haveX11", "glfwGetX11Display", { "X11/Xlib.h", "X11/XKBlib.h", "GLFW/glfw3.h", "GLFW/glfw3native.h" }, "GLFW_EXPOSE_NATIVE_X11") + if cfg.autoconf["haveX11"] then + table.insert(cfg.links, "X11") + table.insert(cfg.defines, "GET_KEYBOARD_INPUT_FROM_X11") + end + end + } filter "platforms:win*oal" includedirs { "vendor/openal-soft/include" } @@ -381,10 +396,10 @@ project "reVC" libdirs { "vendor/openal-soft/libs/Win64" } filter "platforms:linux*oal" - links { "openal", "mpg123", "sndfile", "pthread", "X11" } - + links { "openal", "mpg123", "sndfile", "pthread" } + filter "platforms:bsd*oal" - links { "openal", "mpg123", "sndfile", "pthread", "X11" } + links { "openal", "mpg123", "sndfile", "pthread" } filter "platforms:macosx*oal" links { "openal", "mpg123", "sndfile", "pthread" } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7afdfa4d..b605d45b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -130,6 +130,22 @@ else() set(${PROJECT}_C_CXX_EXTENSIONS OFF) endif() +if(LIBRW_PLATFORM_GL3 AND LIBRW_GL3_GFXLIB STREQUAL "GLFW") + include(CheckSymbolExists) + + set(CMAKE_REQUIRED_LIBRARIES glfw) + set(CMAKE_REQUIRED_DEFINITIONS -DGLFW_EXPOSE_NATIVE_X11) + check_symbol_exists(glfwGetX11Display "GLFW/glfw3.h;GLFW/glfw3native.h" GLFW_HAS_X11) + unset(CMAKE_REQUIRED_DEFINITIONS) + unset(CMAKE_REQUIRED_LIBRARIES) + + if (GLFW_HAS_X11) + find_package(X11 REQUIRED) + target_link_libraries(${EXECUTABLE} PRIVATE X11::X11) + target_compile_definitions(${EXECUTABLE} PRIVATE GET_KEYBOARD_INPUT_FROM_X11) + endif (GLFW_HAS_X11) +endif() + set_target_properties(${EXECUTABLE} PROPERTIES C_STANDARD 11 diff --git a/src/skel/glfw/glfw.cpp b/src/skel/glfw/glfw.cpp index f8beee64..f1f97ee3 100644 --- a/src/skel/glfw/glfw.cpp +++ b/src/skel/glfw/glfw.cpp @@ -52,11 +52,7 @@ long _dwOperatingSystemVersion; #include "Font.h" #include "MemoryMgr.h" -// We found out that GLFW's keyboard input handling is still pretty delayed/not stable, so now we fetch input from X11 directly on Linux. -#if !defined _WIN32 && !defined __APPLE__ && !defined GTA_HANDHELD // && !defined WAYLAND -#define GET_KEYBOARD_INPUT_FROM_X11 -#endif - +// This is defined on project-level, via premake5 or cmake #ifdef GET_KEYBOARD_INPUT_FROM_X11 #include #include