#!/usr/bin/env lua -- -- Yoink -- Execute this file to configure the build system. -- -- Define project, version, tarname, website, and contact. project = "Yoink" version = "0.1" tarname = project:lower():gsub("%s+", "-") website = "http://www.dogcows.com/yoink" contact = "onefriedrice@brokenzipper.com" -- This script will create three different config files from three tables -- with values determined by the host and configuration options: -- -- The table `config' contains key-value pairs to be written to the file -- `config.h' which is included by sources files. Boolean values will -- either #define or #undef the key with no associated values. String -- values will be defined as quoted strings while any other value, -- particularly numbers, will be defined unquoted. -- -- The table `define' contains key-value pairs to be written to the file -- `config.mk' which is included by the Makefile. Values are assigned to -- their keys as they are, unquoted. -- -- The table `export' contains key-value pairs to be written to the file -- `config.sed' which is used by sed to perform search-and-replace on files -- containing @REPLACE_ME@-style keywords. When used with sed, such -- keyworded keys will be replaced by their values as they are, unquoted. function show_help() print([[ This script prepares ]]..project..[[ for building on your system. Usage: ./configure [OPTION]... [VAR=VALUE]... This is NOT an autoconf-generated configure script, though it was written to be familiar by supporting many of the same options. Basic configuration: -h, --help display this help and exit --host=HOST cross-compile the program to run on HOST --prefix=DIR base directory to install programs to --bindir=DIR directory to install executables --datadir=DIR directory to install shared data files --mandir=DIR directory to install manual pages --disable-dependency-tracking speed up one-time builds (maybe) --disable-special-linking do not use direct dependency minimizer --enable-profile compile in gprof profiling instructions Program options: --enable-clock_gettime use a very accurate timing function --enable-debug compile in assertion checks and other debug helps --enable-double-precision use larger floating-point numbers --enable-hotloading watch assets and automatically reload them --enable-threads use threads for concurrency where appropriate --with-gtk use the gtk2 toolkit (overrides --with-qt4) --with-qt4 use the qt4 gui toolkit ]]) end -- -- Setup a temporary file to collect error messages. -- config_log = "config.log" os.remove(config_log) -- -- Define some useful functions. -- -- Return true if a file exists, false otherwise. function file_exists(path) return os.execute(string.format("test -f %q", path)) == 0 end -- Print an error message and exit with an error. function die(...) for i,value in ipairs(arg) do print("fatal: "..tostring(value)) end if file_exists(config_log) then print() print("Look through the file `config.log' for more information:\n") os.execute("tail "..config_log) end os.exit(1) end -- Execute a command and return its output or nil if the command failed to -- run. function backtick_run(command) os.execute("echo '# "..command.."' >>"..config_log) local fd = io.popen(command.." 2>>"..config_log) if fd then local stdout = fd:read("*l") fd:close() return stdout end return nil end -- Try to execute a command and return true if the command finished -- successfully (with an exit code of zero). function try_run(command) os.execute("echo '# "..command.."' >>"..config_log) return os.execute(command.." >/dev/null 2>>"..config_log) == 0 end -- Remove the whitespace surrounding a string. function trim(str) str = str:gsub("^%s+", "") return str:gsub("%s+$", "") end -- Trim the string and convert all sequences of whitespace to a single -- space. function reduce_whitespace(str) str = str:gsub("%s+", " ") return trim(str) end -- Get the CFLAGS from pkg-config for the passed libraries. function pkg_config_cflags(libs) local env = "PKG_CONFIG_PATH="..libdir.."/pkgconfig:$PKG_CONFIG_PATH" local cmd = env.." pkg-config" return backtick_run(cmd.." --cflags "..libs) end -- Get the LDFLAGS from pkg-config for the passed libraries. function pkg_config_ldflags(libs) local env = "PKG_CONFIG_PATH="..libdir.."/pkgconfig:$PKG_CONFIG_PATH" local cmd = env.." pkg-config" return (" "..backtick_run(cmd.." --libs "..libs)):gsub("%s%-l%S*", "") end -- Get the LIBS flags from pkg-config for the passed libraries. function pkg_config_libs(libs) local env = "PKG_CONFIG_PATH="..libdir.."/pkgconfig:$PKG_CONFIG_PATH" local cmd = env.." pkg-config" return backtick_run(cmd.." --libs-only-l "..libs) end -- Add a flag to the CFLAGS and CXXFLAGS variables. function add_cflag(flag) if CFLAGS == "" then CFLAGS = flag else CFLAGS = string.format("%s %s", CFLAGS, flag) end if CXXFLAGS == "" then CXXFLAGS = flag else CXXFLAGS = string.format("%s %s", CXXFLAGS, flag) end end -- Replace a flag in the CFLAGS and CXXFLAGS variables. function set_cflag(flag) local cflag_set, cxxflag_set = false, false CFLAGS = CFLAGS:gsub("%"..flag:sub(1, 2).."%S+", function() cflag_set = true return flag end) CXXFLAGS = CXXFLAGS:gsub("%"..flag:sub(1, 2).."%S+", function() cxxflag_set = true return flag end) if not cflag_set or not cxxflag_set then add_cflag(flag) end end -- Look for a command from a list that can complete successfully (with exit -- code zero) given some arguments. Returns nil if none were successful. function find_command(commands, args) if not args then args = "" end for i,command in ipairs(commands) do if try_run(command.." "..args) then return command end end return nil end -- -- Perform a quick sanity check. -- if not file_exists("configure") or not file_exists("Makefile") then -- This script doesn't support out-of-tree builds. die("You must `cd' to the project root where the Makefile is.") end -- -- Parse the command-line options. -- -- This script supports many of the options provided by autoconf-generated -- scripts. In particular, all the directory-related options are -- supported, including --prefix, --exec-prefix, and all the --dirDIR -- options for configuring installation paths. If passed, the option -- parsing routine below will place these options in the global namespace -- (i.e. prefix, eprefix, bindir, datadir, etc). -- -- Feature and package options are also supported. The value of any option -- of the form --enable-OPT= and --with-OPT= can be obtained with the -- functions get_feature and get_package, respectively. Like -- autoconf-generated scripts, passing --disable-OPT or --without-OPT is -- equivalent to passing --enable-OPT=no and --without-OPT=no, -- respectively. Values that are either yes or no are interpreted as -- booleans. Any other values are interpreted as strings. -- -- Definitions of the form KEY=VALUE are also supported. If passed, the -- option parsing routine below will place these options in the global -- namespace. -- -- Finally, the options -h, --help, and --host are also supported, the -- former two calling the show_help function and the latter setting the -- host global variable. do local features = {} local packages = {} local handlers = {} -- Define the option-parsing rules and handlers. handlers["--help"] = function() show_help() os.exit(0) end handlers["-h"] = handlers["--help"] handlers["--host=(.+)"] = function(triplet) host = triplet cross_compile = true end local add_feature = function(feature, value) if value == "yes" or value == "" then value = true end if value == "no" then value = false end features[feature] = value end handlers["--enable%-([%w_-]+)=?(.*)"] = add_feature handlers["--disable%-([%w_-]+)"] = function(feature) add_feature(feature, "no") end local add_package = function(package, value) if value == "yes" or value == "" then value = true end if value == "no" then value = false end packages[package] = value end handlers["--with%-([%w_-]+)=?(.*)"] = add_package handlers["--without%-([%w_-]+)"] = function(package) add_package(package, "no") end handlers["--(%l+)dir=(.+)"] = function(dir, path) _G[dir.."dir"] = path end handlers["--prefix=(.+)"] = function(path) prefix = path end handlers["--exec-prefix=(.+)"] = function(path) eprefix = path end handlers["([%w_]+)=(.*)"] = function(key, value) _G[key] = value end -- Define the feature and package accessors. function get_feature(feature) return features[feature] end function get_package(package) return packages[package] end -- Now read and parse the command-line options. local function parse_arg(arg) for key,value in pairs(handlers) do local matches = {arg:match(key)} if matches[1] then value(unpack(matches)) return end end print("warning: unknown or incomplete argument "..arg) end for i,arg in ipairs(arg) do parse_arg(arg) end -- Define default values for significant variables. local function define(symbol, value) if not _G[symbol] then _G[symbol] = value end end define("CC", "") define("CXX", "") define("AR", "") define("RANLIB", "") define("WINDRES", "") define("CFLAGS", "-g -O2") define("CXXFLAGS", CFLAGS) define("LDFLAGS", "") define("LIBS", "") define("host", backtick_run("tools/config.guess")) define("alt_host", backtick_run("tools/config.sub "..host)) define("prefix", "/usr/local") define("eprefix", prefix) define("bindir", eprefix.."/bin") define("sbindir", eprefix.."/sbin") define("libexecdir", eprefix.."/libexec") define("sysconfdir", prefix.."/etc") define("localstatedir", prefix.."/var") define("libdir", eprefix.."/lib") define("includedir", prefix.."/include") define("datarootdir", prefix.."/share") define("datadir", datarootdir.."/"..tarname) define("infodir", datarootdir.."/info") define("localedir", datarootdir.."/locale") define("mandir", datarootdir.."/man") define("docdir", datarootdir.."/doc/"..tarname) if features["dependency-tracking"] == nil then features["dependency-tracking"] = true end if features["special-linking"] == nil then features["special-linking"] = true end define("config", {}) define("define", {}) define("export", {}) end -- -- Determine special target platforms. -- -- Win32 has some special cases related to its resource file, src/yoinkrc. if host:match("mingw32") then platform = "win32" end -- -- Look for a working toolchain. -- print("Please wait...") -- Check for CC. tmpname = os.tmpname()..".c" tmpfile, err = io.open(tmpname, "w") if tmpfile then tmpfile:write([[ #include int main() { printf("Hello world!\n"); return 0; } ]]) tmpfile:close() function extra() if not cross_compile then return "gcc", "cc" end end CC = find_command({ CC, host.."-gcc", host.."-cc", alt_host.."-gcc", alt_host.."-cc", extra()}, tmpname.." -o "..tmpname..".tmp") os.remove(tmpname) os.remove(tmpname..".tmp") if not CC then die("Can't find a working C compiler.") end else die("failed to create temporary file: "..err) end -- Check for CXX. tmpname = os.tmpname()..".cpp" tmpfile, err = io.open(tmpname, "w") if tmpfile then tmpfile:write([[ #include int main() { std::cout << "Hello world!" << std::endl; return 0; } ]]) tmpfile:close() function extra() if not cross_compile then return "g++", "c++" end end CXX = find_command({ CXX, host.."-g++", host.."-c++", alt_host.."-g++", alt_host.."-c++", extra()}, tmpname.." -o "..tmpname..".tmp") os.remove(tmpname) os.remove(tmpname..".tmp") if not CXX then die("Can't find a working C++ compiler.") end else die("failed to create temporary file: "..err) end -- Check for AR. do function extra() if not cross_compile then return "ar" end end AR = find_command({ AR, host.."-ar", alt_host.."-ar", extra()}, "--version") if not AR then die("Can't find a working archiver.") end end -- Check for RANLIB. do function extra() if not cross_compile then return "ranlib" end end RANLIB = find_command({ RANLIB, host.."-ranlib", alt_host.."-ranlib", extra()}, "--version") if not RANLIB then die("Can't find a working library indexer.") end end -- Check for WINDRES. if platform == "win32" then function extra() if not cross_compile then return "windres" end end WINDRES = find_command({ WINDRES, host.."-windres", alt_host.."-windres", extra()}, "--version") if not WINDRES then die("Can't find a working resource compiler.") end end -- -- Configure the features and packages. -- if get_feature("debug") then set_cflag("-O0") add_cflag("-Wall -Wno-uninitialized") config.DEBUG = true else config.NDEBUG = true end config.USE_CLOCK_GETTIME = get_feature("clock_gettime") config.USE_DOUBLE_PRECISION = get_feature("double-precision") config.USE_HOTLOADING = get_feature("hotloading") config.USE_THREADS = get_feature("threads") config.PROFILING_ENABLED = get_feature("profile") and add_cflag("-pg") if get_package("gtk") then -- TODO end if get_package("qt4") then -- TODO end -- -- Check for the libraries we need. -- do local dependencies = "sdl gl glu libpng openal vorbisfile lua" if get_package("gtk") then dependencies = dependencies.." gtk+-2.0" elseif get_package("qt4") then dependencies = dependencies.." QtGui" end add_cflag(pkg_config_cflags(dependencies)) LDFLAGS = LDFLAGS .." "..pkg_config_ldflags(dependencies) LIBS = LIBS .." "..pkg_config_libs(dependencies) if platform == "win32" then LIBS = LIBS.." -lws2_32" exe_extension = ".exe" else exe_extension = "" end end -- -- Define the exports and definitions. -- if platform == "win32" then -- These are used in src/yoink.rc. local vmajor, vminor, vrevis = version:match("^(%d*)%.?(%d*)%.?(%d*)") config.VERSION_MAJOR = tonumber(vmajor) or 0 config.VERSION_MINOR = tonumber(vminor) or 0 config.VERSION_REVISION = tonumber(vrevis) or 0 end config.PACKAGE = tarname config.PACKAGE_NAME = project config.PACKAGE_VERSION = version config.PACKAGE_STRING = project.." "..version config.PACKAGE_TARNAME = tarname config.PACKAGE_URL = website config.PACKAGE_BUGREPORT = contact config.YOINK_GITHEAD = backtick_run("git describe master") config.YOINK_DATADIR = datadir define.PACKAGE = project define.TARNAME = tarname.."-"..version define.TARGET = host define.PLATFORM = platform define.CC = CC define.CXX = CXX define.AR = AR define.RANLIB = RANLIB define.WINDRES = WINDRES define.CFLAGS = reduce_whitespace(CFLAGS) define.CXXFLAGS = reduce_whitespace(CXXFLAGS) define.LDFLAGS = reduce_whitespace(LDFLAGS) define.LIBS = reduce_whitespace(LIBS) define.prefix = prefix define.bindir = bindir define.datadir = datadir define.mandir = mandir define.EXEEXT = exe_extension define.DEP_TRACKING = get_feature("dependency-tracking") define.DEP_MINIMIZING = get_feature("special-linking") export.datadir = datadir -- Used in doc/yoink.6.in. export.PACKAGE_BUGREPORT = contact -- Used in doc/yoink.6.in. -- -- All done; output the configuration files. -- -- Output the program options. output = io.open("config.h", "w") for key,value in pairs(config) do key = tostring(key) if type(value) == "boolean" then if value then output:write("#define "..key.." 1") else output:write("#undef "..key) end elseif type(value) == "string" then output:write(string.format("#define %s %q", key, tostring(value))) else output:write(string.format("#define %s %s", key, tostring(value))) end output:write("\n") end output:close() -- Output the make definitions. output = io.open("config.mk", "w") for key,value in pairs(define) do key = tostring(key) value = tostring(value) output:write(string.format("%s = %s\n", key, value)) end output:close() -- Output the exported variables. output = io.open("config.sed", "w") for key,value in pairs(export) do key = key:gsub("/", "\\/") value = value:gsub("/", "\\/") output:write(string.format("s/@%s@/%s/g\n", key, value)) end output:close() print([[ Configuration complete! You can review your configuration in `config.mk'. To finish the installation, type: make make install ]])