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