-- Execute this file to configure the build system.
--
+-- Define project, version, tarname, website, and contact.
project = "Yoink"
version = "0.1"
-bugreport = "onefriedrice@brokenzipper.com"
+tarname = project:lower():gsub("%s+", "-")
+website = "http://www.dogcows.com/yoink"
+contact = "onefriedrice@brokenzipper.com"
-function ShowHelp()
+-- 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.
end
+--
+-- Setup a temporary file to collect error messages.
+--
+
+error_log = "config.log"
+os.remove(error_log)
+
+
--
-- Define some useful functions.
--
-function Die(...)
+-- Return true if a file exists, false otherwise.
+function file_exists(file)
+ return os.execute("test -f "..file) == 0
+end
+
+-- Print an error message and exit with an error.
+function die(...)
for _,value in ipairs(arg) do
print("fatal: "..tostring(value))
end
+ if file_exists(error_log) then
+ print()
+ print("Look through the file `config.log' for more information:\n")
+ os.execute("tail "..error_log)
+ end
os.exit(1)
end
-function FileExists(file)
- return os.execute("test -f "..file) == 0
-end
-
-function ReadCommand(command)
- local fd = io.popen(command)
+-- Execute a command and return its output or nil if the command failed to
+-- run.
+function backtick_run(command)
+ local fd = io.popen(command.." 2>>"..error_log)
if fd then
local stdout = fd:read("*l")
fd:close()
return nil
end
-function TryCommand(command)
- return os.execute(command.." 2>&1 >/dev/null") == 0
+-- Try to execute a command and return true if the command finished
+-- successfully (with an exit code of zero).
+function try_run(command)
+ return os.execute(command.." >/dev/null 2>>"..error_log) == 0
end
-function Trim(str)
+-- Remove the whitespace surrounding a string.
+function trim(str)
str = str:gsub("^%s+", "")
return str:gsub("%s+$", "")
end
-function Reduce(str)
+-- Trim the string and convert all sequences of whitespace to a single
+-- space.
+function reduce_whitespace(str)
str = str:gsub("%s+", " ")
- return Trim(str)
+ 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 _,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 FileExists("configure") or not FileExists("Makefile") then
- Die("You must `cd' to the project root where the Makefile is.")
+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 directories = {}
- local definitions = {}
+ local handlers = {}
+
+
+ -- Define the option-parsing rules and handlers.
+
+ handlers["--help"] = function()
+ show_help()
+ os.exit(0)
+ end
+ handlers["-h"] = handlers["--help"]
- local function AddFeature(feature, value)
+ 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 function AddPackage(package, value)
+ 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
- local function AddDirectory(directory, path)
- directories[directory] = path
+ 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
- local function AddDefinition(key, value)
- definitions[key] = value
+ handlers["([%w_]+)=(.*)"] = function(key, value)
+ _G[key] = value
end
- local handlers = {
- ["--help"] = function() ShowHelp() os.exit(0) end,
- ["--host=(.+)"] = function(arg) host = arg cross_compile = true end,
- ["--enable%-([%w_-]+)=?(.*)"] = AddFeature,
- ["--disable%-([%w_-]+)"] = function(arg) AddFeature(arg, "no") end,
- ["--with%-([%w_-]+)=?(.*)"] = AddPackage,
- ["--without%-([%w_-]+)"] = function(arg) AddPackage(arg, "no") end,
- ["--(%l+)dir=(.+)"] = AddDirectory,
- ["--prefix=(.+)"] = function(arg) prefix = arg end,
- ["--exec-prefix=(.+)"] = function(arg) eprefix = arg end,
- ["([%w_]+)=(.*)"] = AddDefinition,
- }
- handlers["-h"] = handlers["--help"]
-
- local function ParseArg(arg)
+
+ -- 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
+ if matches[1] then value(unpack(matches)) return end
end
print("warning: unknown or incomplete argument "..arg)
end
-
- -- Define some default values.
-
- prefix = "/usr/local"
-
- CC = ""
- CXX = ""
- AR = ""
- RANLIB = ""
- WINDRES = ""
-
- CFLAGS = "-g -O2"
- CXXFLAGS = CFLAGS
- LDFLAGS = ""
- LIBS = ""
-
- features["dependency-tracking"] = true
-
-
- -- Read the arguments from the command-line.
for _,arg in ipairs(arg) do
- ParseArg(arg)
+ parse_arg(arg)
end
- function GetFeature(feature) return features[feature] end
- function GetPackage(package) return packages[package] end
+
+ -- Define default values for significant variables.
- for key,value in pairs(directories) do
- _G[key.."dir"] = value
- end
- for key,value in pairs(definitions) do
- _G[key] = value
+ local function define(symbol, value)
+ if not _G[symbol] then _G[symbol] = value end
end
-
- -- Define the dependent values.
+ 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 not features["dependency-tracking"] then
+ features["dependency-tracking"] = true
+ end
- package = project:lower()
- tarname = package.."-"..version
-
- if not host then host = ReadCommand("tools/config.guess") end
- alt_host = ReadCommand("tools/config.sub "..host)
-
- if not eprefix then eprefix = prefix end
- if not bindir then bindir = eprefix.."/bin" end
- if not sbindir then sbindir = eprefix.."/sbin" end
- if not libexecdir then libexecdir = eprefix.."/libexec" end
- if not sysconfdir then sysconfdir = prefix.."/etc" end
- if not localstatedir then localstatedir = prefix.."/var" end
- if not libdir then libdir = eprefix.."/lib" end
- if not includedir then includedir = prefix.."/include" end
- if not datarootdir then datarootdir = prefix.."/share" end
- if not datadir then datadir = datarootdir.."/"..package end
- if not infodir then infodir = datarootdir.."/info" end
- if not localedir then localedir = datarootdir.."/locale" end
- if not mandir then mandir = datarootdir.."/man" end
- if not docdir then docdir = datarootdir.."/doc/"..package end
-
- cflags = ""
- config = {}
- define = {}
- export = {}
-
- define.DEP_TRACKING = GetFeature("dependency-tracking")
+ define("config", {})
+ define("define", {})
+ define("export", {})
end
--
--- Determine the target platform.
+-- Determine special target platforms.
--
-if host:match("mingw32") then platform = "win32"
-elseif host:match("netbsd") then platform = "netbsd" end
-
-
---
--- Define the check function.
---
-
-do
- local path = os.getenv("PATH")
-
- -- 1. List of possible command names.
- -- 2. Command arguments.
- function FindCommand(commands, args)
- if not args then args = "" end
- for _,command in ipairs(commands) do
- if command then
- for dir in path:gmatch("[^:]+") do
- if FileExists(dir.."/"..command) and TryCommand(command.." "..args) then
- return command
- end
- end
- end
- end
- return nil
- end
-end
+-- Win32 has some special cases related to its resource file, src/yoinkrc.
+if host:match("mingw32") then platform = "win32" end
--
tmpfile:close()
function extra() if not cross_compile then return "gcc", "cc" end end
- CC = FindCommand({
+ CC = find_command({
CC,
- host.."-gcc",
- host.."-cc",
- alt_host.."-gcc",
- alt_host.."-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
+ if not CC then die("Can't find a working C compiler.") end
else
- Die("failed to create temporary file: "..err)
+ die("failed to create temporary file: "..err)
end
-- Check for CXX.
tmpfile:close()
function extra() if not cross_compile then return "g++", "c++" end end
- CXX = FindCommand({
+ CXX = find_command({
CXX,
- host.."-g++",
- host.."-c++",
- alt_host.."-g++",
- alt_host.."-c++",
+ 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
+ if not CXX then die("Can't find a working C++ compiler.") end
else
- Die("failed to create temporary file: "..err)
+ die("failed to create temporary file: "..err)
end
-- Check for AR.
do
function extra() if not cross_compile then return "ar" end end
- AR = FindCommand({
+ AR = find_command({
AR,
host.."-ar",
alt_host.."-ar",
extra()}, "--version")
- if not AR then Die("Can't find a working archiver.") end
+ 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 = FindCommand({
+ RANLIB = find_command({
RANLIB,
host.."-ranlib",
alt_host.."-ranlib",
extra()}, "--version")
- if not RANLIB then Die("Can't find a working library indexer.") end
+ 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 = FindCommand({
+ WINDRES = find_command({
WINDRES,
host.."-windres",
alt_host.."-windres",
extra()}, "--version")
- if not WINDRES then Die("Can't find a working win32 resource compiler.") end
+ if not WINDRES then die("Can't find a working resource compiler.") end
end
-- Configure the features and packages.
--
-if GetFeature("debug") then
- cflags = cflags.." -O0 -Wall -Wno-uninitialized"
+if get_feature("debug") then
+ set_cflag("-O0")
+ add_cflag("-Wall -Wno-uninitialized")
config.DEBUG = true
else
config.NDEBUG = true
end
-if GetFeature("double-precision") then
- config.USE_DOUBLE_PRECISION = true
-end
-
-if GetFeature("profile") then
- cflags = cflags.." -pg"
- config.PROFILING_ENABLED = true
-end
-
-if GetFeature("extra-warnings") then
- cflags = cflags.." -Wextra -Wno-unused-parameter"
+if get_feature("extra-warnings") then
+ add_cflag("-Wextra -Wno-unused-parameter")
end
-if GetFeature("link-sh") then
- -- TODO
-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 GetFeature("clock_gettime") then
+if get_feature("link-sh") then
-- TODO
end
-if GetFeature("threads") then
- config.USE_THREADS = true
-end
-
-if GetFeature("hotloading") then
- print("FYI: Hotloading is very experimental and only works on Linux.");
- config.USE_HOTLOADING = true
-end
-
-if GetPackage("gtk") then
+if get_package("gtk") then
-- TODO
end
-if GetPackage("qt4") then
+if get_package("qt4") then
-- TODO
end
--
do
- local command = "PKG_CONFIG_PATH="..libdir.."/pkgconfig:$PKG_CONFIG_PATH pkg-config"
- local deps = "sdl gl glu libpng openal vorbisfile lua"
+ local dependencies = "sdl gl glu libpng openal vorbisfile lua"
- if GetPackage("gtk") then
- deps = deps.." gtk+-2.0"
- elseif GetPackage("qt4") then
- deps = deps.." QtGui"
+ if get_package("gtk") then
+ dependencies = dependencies.." gtk+-2.0"
+ elseif get_package("qt4") then
+ dependencies = dependencies.." QtGui"
end
- local pc_cflags = ReadCommand(command.." --cflags "..deps)
- if not pc_cflags then Die("Couldn't determine CFLAGS.") end
- CFLAGS = CFLAGS.." "..pc_cflags
- CXXFLAGS = CXXFLAGS.." "..pc_cflags
-
- local pc_libs = " "..ReadCommand(command.." --libs "..deps)
- if not pc_libs then Die("Couldn't determine LDFLAGS or LIBS.") end
- for lib in pc_libs:gmatch("%s%-l%S+") do
- LIBS = LIBS.." "..lib
- end
- for ldflag in pc_libs:gmatch("%s%-[^l]%S*") do
- LDFLAGS = LDFLAGS.." "..ldflag
- 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"
end
end
-CFLAGS = Reduce(CFLAGS.." "..cflags)
-CXXFLAGS = Reduce(CXXFLAGS.." "..cflags)
-LDFLAGS = Reduce(LDFLAGS)
-LIBS = Reduce(LIBS)
-
--
-- Define the exports and definitions.
--
-config.YOINK_DATADIR = datadir
-export.YOINK_DATADIR = datadir
-
-do
- local vmajor = ReadCommand("v=$(echo "..version.." | cut -d. -f1); echo ${v:-0}")
- local vminor = ReadCommand("v=$(echo "..version.." | cut -d. -f2); echo ${v:-0}")
- local vrevis = ReadCommand("v=$(echo "..version.." | cut -d. -f3); echo ${v:-0}")
-
- config.VERSION_MAJOR = tonumber(vmajor)
- config.VERSION_MINOR = tonumber(vminor)
- config.VERSION_REVISION = tonumber(vrevis)
-end
-
-do
- -- Determine and define the git revision.
- local head = ReadCommand("git describe master")
- config.YOINK_GITHEAD = head
+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 = project
+config.PACKAGE = tarname
config.PACKAGE_NAME = project
-config.VERSION = version
config.PACKAGE_VERSION = version
config.PACKAGE_STRING = project.." "..version
-config.PACKAGE_BUGREPORT = bugreport
+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
+define.TARNAME = tarname.."-"..version
define.TARGET = host
define.PLATFORM = platform
define.CC = CC
define.AR = AR
define.RANLIB = RANLIB
define.WINDRES = WINDRES
-define.CFLAGS = CFLAGS
-define.CXXFLAGS = CXXFLAGS
-define.LDFLAGS = LDFLAGS
-define.LIBS = LIBS
+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")
-export.PACKAGE_BUGREPORT = bugreport
+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.
--
--- Print the program options.
+-- 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)
+ output:write("#define "..key.." 1")
else
output:write("#undef "..key)
end
end
output:close()
--- Print the make definitions.
+-- Output the make definitions.
output = io.open("config.mk", "w")
for key,value in pairs(define) do
key = tostring(key)
output:close()
--- Print the exported variables.
+-- Output the exported variables.
output = io.open("config.sed", "w")
for key,value in pairs(export) do
key = key:gsub("/", "\\/")