configuration cleanup and bugfixes
[chaz/yoink] / configure
1 #!/usr/bin/env lua
2
3 --
4 -- Yoink
5 -- Execute this file to configure the build system.
6 --
7
8 -- Define project, version, tarname, website, and contact.
9 project = "Yoink"
10 version = "0.1"
11 tarname = project:lower():gsub("%s+", "-")
12 website = "http://www.dogcows.com/yoink"
13 contact = "onefriedrice@brokenzipper.com"
14
15
16 -- This script will create three different config files from three tables
17 -- with values determined by the host and configuration options:
18 --
19 -- The table `config' contains key-value pairs to be written to the file
20 -- `config.h' which is included by sources files. Boolean values will
21 -- either #define or #undef the key with no associated values. String
22 -- values will be defined as quoted strings while any other value,
23 -- particularly numbers, will be defined unquoted.
24 --
25 -- The table `define' contains key-value pairs to be written to the file
26 -- `config.mk' which is included by the Makefile. Values are assigned to
27 -- their keys as they are, unquoted.
28 --
29 -- The table `export' contains key-value pairs to be written to the file
30 -- `config.sed' which is used by sed to perform search-and-replace on files
31 -- containing @REPLACE_ME@-style keywords. When used with sed, such
32 -- keyworded keys will be replaced by their values as they are, unquoted.
33
34
35 function show_help()
36 print([[
37
38 This script prepares ]]..project..[[ for building on your system.
39 Usage: ./configure [OPTION]... [VAR=VALUE]...
40
41 This is NOT an autoconf-generated configure script, though it was written
42 to be familiar by supporting many of the same options.
43
44 Basic configuration:
45 -h, --help display this help and exit
46 --host=HOST cross-compile the program to run on HOST
47
48 --prefix=DIR base directory to install programs to
49 --bindir=DIR directory to install executables
50 --datadir=DIR directory to install shared data files
51 --mandir=DIR directory to install manual pages
52
53 --disable-dependency-tracking speed up one-time builds
54 --enable-link-sh decrease the number of direct dependencies
55
56 Program options:
57 --enable-debug include debugging symbols and code paths
58 --enable-double-precision use doubles instead of floats
59 --enable-profile compile in gprof profiling instructions
60 --enable-clock_gettime use clock_gettime() for timing
61 --enable-threads use threads for concurrency
62 --enable-hotloading watch assets for automatic reloading
63
64 --with-gtk use the gtk2 toolkit (overrides --with-qt4)
65 --with-qt4 use the qt4 gui toolkit
66 ]])
67 end
68
69
70 --
71 -- Setup a temporary file to collect error messages.
72 --
73
74 error_log = "config.log"
75 os.remove(error_log)
76
77
78 --
79 -- Define some useful functions.
80 --
81
82 -- Return true if a file exists, false otherwise.
83 function file_exists(file)
84 return os.execute("test -f "..file) == 0
85 end
86
87 -- Print an error message and exit with an error.
88 function die(...)
89 for _,value in ipairs(arg) do
90 print("fatal: "..tostring(value))
91 end
92 if file_exists(error_log) then
93 print()
94 print("Look through the file `config.log' for more information:\n")
95 os.execute("tail "..error_log)
96 end
97 os.exit(1)
98 end
99
100 -- Execute a command and return its output or nil if the command failed to
101 -- run.
102 function backtick_run(command)
103 local fd = io.popen(command.." 2>>"..error_log)
104 if fd then
105 local stdout = fd:read("*l")
106 fd:close()
107 return stdout
108 end
109 return nil
110 end
111
112 -- Try to execute a command and return true if the command finished
113 -- successfully (with an exit code of zero).
114 function try_run(command)
115 return os.execute(command.." >/dev/null 2>>"..error_log) == 0
116 end
117
118 -- Remove the whitespace surrounding a string.
119 function trim(str)
120 str = str:gsub("^%s+", "")
121 return str:gsub("%s+$", "")
122 end
123
124 -- Trim the string and convert all sequences of whitespace to a single
125 -- space.
126 function reduce_whitespace(str)
127 str = str:gsub("%s+", " ")
128 return trim(str)
129 end
130
131 -- Get the CFLAGS from pkg-config for the passed libraries.
132 function pkg_config_cflags(libs)
133 local env = "PKG_CONFIG_PATH="..libdir.."/pkgconfig:$PKG_CONFIG_PATH"
134 local cmd = env.." pkg-config"
135 return backtick_run(cmd.." --cflags "..libs)
136 end
137
138 -- Get the LDFLAGS from pkg-config for the passed libraries.
139 function pkg_config_ldflags(libs)
140 local env = "PKG_CONFIG_PATH="..libdir.."/pkgconfig:$PKG_CONFIG_PATH"
141 local cmd = env.." pkg-config"
142 return (" "..backtick_run(cmd.." --libs "..libs)):gsub("%s%-l%S*", "")
143 end
144
145 -- Get the LIBS flags from pkg-config for the passed libraries.
146 function pkg_config_libs(libs)
147 local env = "PKG_CONFIG_PATH="..libdir.."/pkgconfig:$PKG_CONFIG_PATH"
148 local cmd = env.." pkg-config"
149 return backtick_run(cmd.." --libs-only-l "..libs)
150 end
151
152 -- Add a flag to the CFLAGS and CXXFLAGS variables.
153 function add_cflag(flag)
154 if CFLAGS == "" then
155 CFLAGS = flag
156 else
157 CFLAGS = string.format("%s %s", CFLAGS, flag)
158 end
159 if CXXFLAGS == "" then
160 CXXFLAGS = flag
161 else
162 CXXFLAGS = string.format("%s %s", CXXFLAGS, flag)
163 end
164 end
165
166 -- Replace a flag in the CFLAGS and CXXFLAGS variables.
167 function set_cflag(flag)
168 local cflag_set, cxxflag_set = false, false
169 CFLAGS = CFLAGS:gsub("%"..flag:sub(1, 2).."%S+", function()
170 cflag_set = true
171 return flag
172 end)
173 CXXFLAGS = CXXFLAGS:gsub("%"..flag:sub(1, 2).."%S+", function()
174 cxxflag_set = true
175 return flag
176 end)
177 if not cflag_set or not cxxflag_set then add_cflag(flag) end
178 end
179
180 -- Look for a command from a list that can complete successfully (with exit
181 -- code zero) given some arguments. Returns nil if none were successful.
182 function find_command(commands, args)
183 if not args then args = "" end
184 for _,command in ipairs(commands) do
185 if try_run(command.." "..args) then return command end
186 end
187 return nil
188 end
189
190
191 --
192 -- Perform a quick sanity check.
193 --
194
195 if not file_exists("configure") or not file_exists("Makefile") then
196 -- This script doesn't support out-of-tree builds.
197 die("You must `cd' to the project root where the Makefile is.")
198 end
199
200
201 --
202 -- Parse the command-line options.
203 --
204
205 -- This script supports many of the options provided by autoconf-generated
206 -- scripts. In particular, all the directory-related options are
207 -- supported, including --prefix, --exec-prefix, and all the --dirDIR
208 -- options for configuring installation paths. If passed, the option
209 -- parsing routine below will place these options in the global namespace
210 -- (i.e. prefix, eprefix, bindir, datadir, etc).
211 --
212 -- Feature and package options are also supported. The value of any option
213 -- of the form --enable-OPT= and --with-OPT= can be obtained with the
214 -- functions get_feature and get_package, respectively. Like
215 -- autoconf-generated scripts, passing --disable-OPT or --without-OPT is
216 -- equivalent to passing --enable-OPT=no and --without-OPT=no,
217 -- respectively. Values that are either yes or no are interpreted as
218 -- booleans. Any other values are interpreted as strings.
219 --
220 -- Definitions of the form KEY=VALUE are also supported. If passed, the
221 -- option parsing routine below will place these options in the global
222 -- namespace.
223 --
224 -- Finally, the options -h, --help, and --host are also supported, the
225 -- former two calling the show_help function and the latter setting the
226 -- host global variable.
227
228 do
229 local features = {}
230 local packages = {}
231 local handlers = {}
232
233
234 -- Define the option-parsing rules and handlers.
235
236 handlers["--help"] = function()
237 show_help()
238 os.exit(0)
239 end
240 handlers["-h"] = handlers["--help"]
241
242 handlers["--host=(.+)"] = function(triplet)
243 host = triplet
244 cross_compile = true
245 end
246
247 local add_feature = function(feature, value)
248 if value == "yes" or value == "" then value = true end
249 if value == "no" then value = false end
250 features[feature] = value
251 end
252 handlers["--enable%-([%w_-]+)=?(.*)"] = add_feature
253 handlers["--disable%-([%w_-]+)"] = function(feature)
254 add_feature(feature, "no")
255 end
256
257 local add_package = function(package, value)
258 if value == "yes" or value == "" then value = true end
259 if value == "no" then value = false end
260 packages[package] = value
261 end
262 handlers["--with%-([%w_-]+)=?(.*)"] = add_package
263 handlers["--without%-([%w_-]+)"] = function(package)
264 add_package(package, "no")
265 end
266
267 handlers["--(%l+)dir=(.+)"] = function(dir, path)
268 _G[dir.."dir"] = path
269 end
270 handlers["--prefix=(.+)"] = function(path)
271 prefix = path
272 end
273 handlers["--exec-prefix=(.+)"] = function(path)
274 eprefix = path
275 end
276
277 handlers["([%w_]+)=(.*)"] = function(key, value)
278 _G[key] = value
279 end
280
281
282 -- Define the feature and package accessors.
283
284 function get_feature(feature) return features[feature] end
285 function get_package(package) return packages[package] end
286
287
288 -- Now read and parse the command-line options.
289
290 local function parse_arg(arg)
291 for key,value in pairs(handlers) do
292 local matches = {arg:match(key)}
293 if matches[1] then value(unpack(matches)) return end
294 end
295 print("warning: unknown or incomplete argument "..arg)
296 end
297
298 for _,arg in ipairs(arg) do
299 parse_arg(arg)
300 end
301
302
303 -- Define default values for significant variables.
304
305 local function define(symbol, value)
306 if not _G[symbol] then _G[symbol] = value end
307 end
308
309 define("CC", "")
310 define("CXX", "")
311 define("AR", "")
312 define("RANLIB", "")
313 define("WINDRES", "")
314
315 define("CFLAGS", "-g -O2")
316 define("CXXFLAGS", CFLAGS)
317 define("LDFLAGS", "")
318 define("LIBS", "")
319
320 define("host", backtick_run("tools/config.guess"))
321 define("alt_host", backtick_run("tools/config.sub "..host))
322
323 define("prefix", "/usr/local")
324 define("eprefix", prefix)
325 define("bindir", eprefix.."/bin")
326 define("sbindir", eprefix.."/sbin")
327 define("libexecdir", eprefix.."/libexec")
328 define("sysconfdir", prefix.."/etc")
329 define("localstatedir", prefix.."/var")
330 define("libdir", eprefix.."/lib")
331 define("includedir", prefix.."/include")
332 define("datarootdir", prefix.."/share")
333 define("datadir", datarootdir.."/"..tarname)
334 define("infodir", datarootdir.."/info")
335 define("localedir", datarootdir.."/locale")
336 define("mandir", datarootdir.."/man")
337 define("docdir", datarootdir.."/doc/"..tarname)
338
339 if not features["dependency-tracking"] then
340 features["dependency-tracking"] = true
341 end
342
343 define("config", {})
344 define("define", {})
345 define("export", {})
346 end
347
348
349 --
350 -- Determine special target platforms.
351 --
352
353 -- Win32 has some special cases related to its resource file, src/yoinkrc.
354 if host:match("mingw32") then platform = "win32" end
355
356
357 --
358 -- Look for a working toolchain.
359 --
360
361 print("Please wait...")
362
363 -- Check for CC.
364 tmpname = os.tmpname()..".c"
365 tmpfile, err = io.open(tmpname, "w")
366 if tmpfile then
367 tmpfile:write([[
368 #include <stdio.h>
369 int main()
370 {
371 printf("Hello world!\n");
372 return 0;
373 }
374 ]])
375 tmpfile:close()
376
377 function extra() if not cross_compile then return "gcc", "cc" end end
378 CC = find_command({
379 CC,
380 host.."-gcc", host.."-cc",
381 alt_host.."-gcc", alt_host.."-cc",
382 extra()}, tmpname.." -o "..tmpname..".tmp")
383 os.remove(tmpname)
384 os.remove(tmpname..".tmp")
385 if not CC then die("Can't find a working C compiler.") end
386 else
387 die("failed to create temporary file: "..err)
388 end
389
390 -- Check for CXX.
391 tmpname = os.tmpname()..".c"
392 tmpfile, err = io.open(tmpname, "w")
393 if tmpfile then
394 tmpfile:write([[
395 #include <iostream>
396 int main()
397 {
398 std::cout << "Hello world!" << std::endl;
399 return 0;
400 }
401 ]])
402 tmpfile:close()
403
404 function extra() if not cross_compile then return "g++", "c++" end end
405 CXX = find_command({
406 CXX,
407 host.."-g++", host.."-c++",
408 alt_host.."-g++", alt_host.."-c++",
409 extra()}, tmpname.." -o "..tmpname..".tmp")
410 os.remove(tmpname)
411 os.remove(tmpname..".tmp")
412 if not CXX then die("Can't find a working C++ compiler.") end
413 else
414 die("failed to create temporary file: "..err)
415 end
416
417 -- Check for AR.
418 do
419 function extra() if not cross_compile then return "ar" end end
420 AR = find_command({
421 AR,
422 host.."-ar",
423 alt_host.."-ar",
424 extra()}, "--version")
425 if not AR then die("Can't find a working archiver.") end
426 end
427
428 -- Check for RANLIB.
429 do
430 function extra() if not cross_compile then return "ranlib" end end
431 RANLIB = find_command({
432 RANLIB,
433 host.."-ranlib",
434 alt_host.."-ranlib",
435 extra()}, "--version")
436 if not RANLIB then die("Can't find a working library indexer.") end
437 end
438
439 -- Check for WINDRES.
440 if platform == "win32" then
441 function extra() if not cross_compile then return "windres" end end
442 WINDRES = find_command({
443 WINDRES,
444 host.."-windres",
445 alt_host.."-windres",
446 extra()}, "--version")
447 if not WINDRES then die("Can't find a working resource compiler.") end
448 end
449
450
451 --
452 -- Configure the features and packages.
453 --
454
455 if get_feature("debug") then
456 set_cflag("-O0")
457 add_cflag("-Wall -Wno-uninitialized")
458 config.DEBUG = true
459 else
460 config.NDEBUG = true
461 end
462
463 if get_feature("extra-warnings") then
464 add_cflag("-Wextra -Wno-unused-parameter")
465 end
466
467 config.USE_CLOCK_GETTIME = get_feature("clock_gettime")
468 config.USE_DOUBLE_PRECISION = get_feature("double-precision")
469 config.USE_HOTLOADING = get_feature("hotloading")
470 config.USE_THREADS = get_feature("threads")
471 config.PROFILING_ENABLED = get_feature("profile") and add_cflag("-pg")
472
473 if get_feature("link-sh") then
474 -- TODO
475 end
476
477 if get_package("gtk") then
478 -- TODO
479 end
480
481 if get_package("qt4") then
482 -- TODO
483 end
484
485
486 --
487 -- Check for the libraries we need.
488 --
489
490 do
491 local dependencies = "sdl gl glu libpng openal vorbisfile lua"
492
493 if get_package("gtk") then
494 dependencies = dependencies.." gtk+-2.0"
495 elseif get_package("qt4") then
496 dependencies = dependencies.." QtGui"
497 end
498
499 add_cflag(pkg_config_cflags(dependencies))
500 LDFLAGS = LDFLAGS .." "..pkg_config_ldflags(dependencies)
501 LIBS = LIBS .." "..pkg_config_libs(dependencies)
502
503 if platform == "win32" then
504 LIBS = LIBS.." -lws2_32"
505 exe_extension = ".exe"
506 else
507 exe_extension = ""
508 end
509 end
510
511
512 --
513 -- Define the exports and definitions.
514 --
515
516 if platform == "win32" then
517 -- These are used in src/yoink.rc.
518 local vmajor, vminor, vrevis = version:match("^(%d*)%.?(%d*)%.?(%d*)")
519 config.VERSION_MAJOR = tonumber(vmajor) or 0
520 config.VERSION_MINOR = tonumber(vminor) or 0
521 config.VERSION_REVISION = tonumber(vrevis) or 0
522 end
523
524 config.PACKAGE = tarname
525 config.PACKAGE_NAME = project
526 config.PACKAGE_VERSION = version
527 config.PACKAGE_STRING = project.." "..version
528 config.PACKAGE_TARNAME = tarname
529 config.PACKAGE_URL = website
530 config.PACKAGE_BUGREPORT = contact
531 config.YOINK_GITHEAD = backtick_run("git describe master")
532 config.YOINK_DATADIR = datadir
533
534 define.PACKAGE = project
535 define.TARNAME = tarname.."-"..version
536 define.TARGET = host
537 define.PLATFORM = platform
538 define.CC = CC
539 define.CXX = CXX
540 define.AR = AR
541 define.RANLIB = RANLIB
542 define.WINDRES = WINDRES
543 define.CFLAGS = reduce_whitespace(CFLAGS)
544 define.CXXFLAGS = reduce_whitespace(CXXFLAGS)
545 define.LDFLAGS = reduce_whitespace(LDFLAGS)
546 define.LIBS = reduce_whitespace(LIBS)
547 define.prefix = prefix
548 define.bindir = bindir
549 define.datadir = datadir
550 define.mandir = mandir
551 define.EXEEXT = exe_extension
552 define.DEP_TRACKING = get_feature("dependency-tracking")
553
554 export.datadir = datadir -- Used in doc/yoink.6.in.
555 export.PACKAGE_BUGREPORT = contact -- Used in doc/yoink.6.in.
556
557
558 --
559 -- All done; output the configuration files.
560 --
561
562 -- Output the program options.
563 output = io.open("config.h", "w")
564 for key,value in pairs(config) do
565 key = tostring(key)
566 if type(value) == "boolean" then
567 if value then
568 output:write("#define "..key.." 1")
569 else
570 output:write("#undef "..key)
571 end
572 elseif type(value) == "string" then
573 output:write(string.format("#define %s %q", key, tostring(value)))
574 else
575 output:write(string.format("#define %s %s", key, tostring(value)))
576 end
577 output:write("\n")
578 end
579 output:close()
580
581 -- Output the make definitions.
582 output = io.open("config.mk", "w")
583 for key,value in pairs(define) do
584 key = tostring(key)
585 value = tostring(value)
586 output:write(string.format("%s = %s\n", key, value))
587 end
588 output:close()
589
590
591 -- Output the exported variables.
592 output = io.open("config.sed", "w")
593 for key,value in pairs(export) do
594 key = key:gsub("/", "\\/")
595 value = value:gsub("/", "\\/")
596 output:write(string.format("s/@%s@/%s/g\n", key, value))
597 end
598 output:close()
599
600
601 print([[
602
603 Configuration complete! You can review your configuration in `config.mk'.
604
605 To finish the installation, type:
606 make
607 make install
608 ]])
609
This page took 0.056246 seconds and 4 git commands to generate.