#!/usr/bin/env lua -- -- Yoink -- Execute this file to configure the build system. -- project = "Yoink" version = "0.1" bugreport = "chaz@dogcows.com" function ShowHelp() 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 --enable-link-sh decrease the number of direct dependencies Program options: --enable-debug include debugging symbols and code paths --enable-double-precision use doubles instead of floats --enable-profile compile in gprof profiling instructions --enable-clock_gettime use clock_gettime() for timing --enable-threads use threads for concurrency --enable-hotloading watch assets for automatic reloading --with-gtk use the gtk2 toolkit (overrides --with-qt4) --with-qt4 use the qt4 gui toolkit ]]) end -- -- Define some useful functions. -- function Die(...) for _,value in ipairs(arg) do print("fatal: "..tostring(value)) end os.exit(1) end function FileExists(file) return os.execute("test -f "..file) == 0 end function ReadCommand(command) local fd = io.popen(command) if fd then local stdout = fd:read("*l") fd:close() return stdout end return nil end function TryCommand(command) return os.execute(command.." 2>&1 >/dev/null") == 0 end function Trim(str) str = str:gsub("^%s+", "") return str:gsub("%s+$", "") end function Reduce(str) str = str:gsub("%s+", " ") return Trim(str) 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.") end -- -- Parse the command-line options. -- do local features = {} local packages = {} local directories = {} local definitions = {} local function AddFeature(feature, value) if value == "yes" or value == "" then value = true end if value == "no" then value = false end features[feature] = value end local function AddPackage(package, value) if value == "yes" or value == "" then value = true end if value == "no" then value = false end packages[package] = value end local function AddDirectory(directory, path) directories[directory] = path end local function AddDefinition(key, value) definitions[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) 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 -- 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) end function GetFeature(feature) return features[feature] end function GetPackage(package) return packages[package] end for key,value in pairs(directories) do _G[key.."dir"] = value end for key,value in pairs(definitions) do _G[key] = value end -- Define the dependent values. 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") end -- -- Determine the target platform. -- 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 -- -- 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 = FindCommand({ 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()..".c" 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 = FindCommand({ 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 = FindCommand({ 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 = FindCommand({ 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 = FindCommand({ WINDRES, host.."-windres", alt_host.."-windres", extra()}, "--version") if not WINDRES then Die("Can't find a working win32 resource compiler.") end end -- -- Configure the features and packages. -- if GetFeature("debug") then cflags = cflags.." -O0 -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" end if GetFeature("link-sh") then -- TODO end if GetFeature("clock_gettime") 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 -- TODO end if GetPackage("qt4") then -- TODO end -- -- Check for the libraries we need. -- do local command = "PKG_CONFIG_PATH="..libdir.."/pkgconfig:$PKG_CONFIG_PATH pkg-config" local deps = "sdl gl glu libpng openal vorbisfile lua" if GetPackage("gtk") then deps = deps.." gtk+-2.0" elseif GetPackage("qt4") then deps = deps.." 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("%-l%S+") do LIBS = LIBS.." "..lib end for ldflag in pc_libs:gmatch("%-[^l]%S*") do LDFLAGS = LDFLAGS.." "..ldflag end if platform == "win32" then LIBS = LIBS.." -lws2_32" exe_extension = ".exe" else exe_extension = "" 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 end config.PACKAGE = project config.PACKAGE_NAME = project config.VERSION = version config.PACKAGE_VERSION = version config.PACKAGE_STRING = project.." "..version config.PACKAGE_BUGREPORT = bugreport define.PACKAGE = project define.TARNAME = tarname define.TARGET = host define.PLATFORM = platform define.CC = CC define.CXX = CXX define.AR = AR define.RANLIB = RANLIB define.WINDRES = WINDRES define.CFLAGS = CFLAGS define.CXXFLAGS = CXXFLAGS define.LDFLAGS = LDFLAGS define.LIBS = LIBS define.prefix = prefix define.bindir = bindir define.datadir = datadir define.mandir = mandir define.EXEEXT = exe_extension export.PACKAGE_BUGREPORT = bugreport -- -- All done; output the configuration files. -- -- Print 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) 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() -- Print 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() -- Print 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 ]])