pkg-config libs parsing bugfix
[chaz/yoink] / configure
1 #!/usr/bin/env lua
2
3 --
4 -- Yoink
5 -- Execute this file to configure the build system.
6 --
7
8 project = "Yoink"
9 version = "0.1"
10 bugreport = "onefriedrice@brokenzipper.com"
11
12
13 function ShowHelp()
14 print([[
15
16 This script prepares ]]..project..[[ for building on your system.
17 Usage: ./configure [OPTION]... [VAR=VALUE]...
18
19 This is NOT an autoconf-generated configure script, though it was written
20 to be familiar by supporting many of the same options.
21
22 Basic configuration:
23 -h, --help display this help and exit
24 --host=HOST cross-compile the program to run on HOST
25
26 --prefix=DIR base directory to install programs to
27 --bindir=DIR directory to install executables
28 --datadir=DIR directory to install shared data files
29 --mandir=DIR directory to install manual pages
30
31 --disable-dependency-tracking speed up one-time builds
32 --enable-link-sh decrease the number of direct dependencies
33
34 Program options:
35 --enable-debug include debugging symbols and code paths
36 --enable-double-precision use doubles instead of floats
37 --enable-profile compile in gprof profiling instructions
38 --enable-clock_gettime use clock_gettime() for timing
39 --enable-threads use threads for concurrency
40 --enable-hotloading watch assets for automatic reloading
41
42 --with-gtk use the gtk2 toolkit (overrides --with-qt4)
43 --with-qt4 use the qt4 gui toolkit
44 ]])
45 end
46
47
48 --
49 -- Define some useful functions.
50 --
51
52 function Die(...)
53 for _,value in ipairs(arg) do
54 print("fatal: "..tostring(value))
55 end
56 os.exit(1)
57 end
58
59 function FileExists(file)
60 return os.execute("test -f "..file) == 0
61 end
62
63 function ReadCommand(command)
64 local fd = io.popen(command)
65 if fd then
66 local stdout = fd:read("*l")
67 fd:close()
68 return stdout
69 end
70 return nil
71 end
72
73 function TryCommand(command)
74 return os.execute(command.." 2>&1 >/dev/null") == 0
75 end
76
77 function Trim(str)
78 str = str:gsub("^%s+", "")
79 return str:gsub("%s+$", "")
80 end
81
82 function Reduce(str)
83 str = str:gsub("%s+", " ")
84 return Trim(str)
85 end
86
87
88 --
89 -- Perform a quick sanity check.
90 --
91
92 if not FileExists("configure") or not FileExists("Makefile") then
93 Die("You must `cd' to the project root where the Makefile is.")
94 end
95
96
97 --
98 -- Parse the command-line options.
99 --
100
101 do
102 local features = {}
103 local packages = {}
104 local directories = {}
105 local definitions = {}
106
107 local function AddFeature(feature, value)
108 if value == "yes" or value == "" then value = true end
109 if value == "no" then value = false end
110 features[feature] = value
111 end
112
113 local function AddPackage(package, value)
114 if value == "yes" or value == "" then value = true end
115 if value == "no" then value = false end
116 packages[package] = value
117 end
118
119 local function AddDirectory(directory, path)
120 directories[directory] = path
121 end
122
123 local function AddDefinition(key, value)
124 definitions[key] = value
125 end
126
127 local handlers = {
128 ["--help"] = function() ShowHelp() os.exit(0) end,
129 ["--host=(.+)"] = function(arg) host = arg cross_compile = true end,
130 ["--enable%-([%w_-]+)=?(.*)"] = AddFeature,
131 ["--disable%-([%w_-]+)"] = function(arg) AddFeature(arg, "no") end,
132 ["--with%-([%w_-]+)=?(.*)"] = AddPackage,
133 ["--without%-([%w_-]+)"] = function(arg) AddPackage(arg, "no") end,
134 ["--(%l+)dir=(.+)"] = AddDirectory,
135 ["--prefix=(.+)"] = function(arg) prefix = arg end,
136 ["--exec-prefix=(.+)"] = function(arg) eprefix = arg end,
137 ["([%w_]+)=(.*)"] = AddDefinition,
138 }
139 handlers["-h"] = handlers["--help"]
140
141 local function ParseArg(arg)
142 for key,value in pairs(handlers) do
143 local matches = {arg:match(key)}
144 if matches[1] then
145 value(unpack(matches))
146 return
147 end
148 end
149 print("warning: unknown or incomplete argument "..arg)
150 end
151
152
153 -- Define some default values.
154
155 prefix = "/usr/local"
156
157 CC = ""
158 CXX = ""
159 AR = ""
160 RANLIB = ""
161 WINDRES = ""
162
163 CFLAGS = "-g -O2"
164 CXXFLAGS = CFLAGS
165 LDFLAGS = ""
166 LIBS = ""
167
168 features["dependency-tracking"] = true
169
170
171 -- Read the arguments from the command-line.
172 for _,arg in ipairs(arg) do
173 ParseArg(arg)
174 end
175
176 function GetFeature(feature) return features[feature] end
177 function GetPackage(package) return packages[package] end
178
179 for key,value in pairs(directories) do
180 _G[key.."dir"] = value
181 end
182 for key,value in pairs(definitions) do
183 _G[key] = value
184 end
185
186
187 -- Define the dependent values.
188
189 package = project:lower()
190 tarname = package.."-"..version
191
192 if not host then host = ReadCommand("tools/config.guess") end
193 alt_host = ReadCommand("tools/config.sub "..host)
194
195 if not eprefix then eprefix = prefix end
196 if not bindir then bindir = eprefix.."/bin" end
197 if not sbindir then sbindir = eprefix.."/sbin" end
198 if not libexecdir then libexecdir = eprefix.."/libexec" end
199 if not sysconfdir then sysconfdir = prefix.."/etc" end
200 if not localstatedir then localstatedir = prefix.."/var" end
201 if not libdir then libdir = eprefix.."/lib" end
202 if not includedir then includedir = prefix.."/include" end
203 if not datarootdir then datarootdir = prefix.."/share" end
204 if not datadir then datadir = datarootdir.."/"..package end
205 if not infodir then infodir = datarootdir.."/info" end
206 if not localedir then localedir = datarootdir.."/locale" end
207 if not mandir then mandir = datarootdir.."/man" end
208 if not docdir then docdir = datarootdir.."/doc/"..package end
209
210 cflags = ""
211 config = {}
212 define = {}
213 export = {}
214
215 define.DEP_TRACKING = GetFeature("dependency-tracking")
216 end
217
218
219 --
220 -- Determine the target platform.
221 --
222
223 if host:match("mingw32") then platform = "win32"
224 elseif host:match("netbsd") then platform = "netbsd" end
225
226
227 --
228 -- Define the check function.
229 --
230
231 do
232 local path = os.getenv("PATH")
233
234 -- 1. List of possible command names.
235 -- 2. Command arguments.
236 function FindCommand(commands, args)
237 if not args then args = "" end
238 for _,command in ipairs(commands) do
239 if command then
240 for dir in path:gmatch("[^:]+") do
241 if FileExists(dir.."/"..command) and TryCommand(command.." "..args) then
242 return command
243 end
244 end
245 end
246 end
247 return nil
248 end
249 end
250
251
252 --
253 -- Look for a working toolchain.
254 --
255
256 print("Please wait...")
257
258 -- Check for CC.
259 tmpname = os.tmpname()..".c"
260 tmpfile, err = io.open(tmpname, "w")
261 if tmpfile then
262 tmpfile:write([[
263 #include <stdio.h>
264 int main()
265 {
266 printf("Hello world!\n");
267 return 0;
268 }
269 ]])
270 tmpfile:close()
271
272 function extra() if not cross_compile then return "gcc", "cc" end end
273 CC = FindCommand({
274 CC,
275 host.."-gcc",
276 host.."-cc",
277 alt_host.."-gcc",
278 alt_host.."-cc",
279 extra()}, tmpname.." -o "..tmpname..".tmp")
280 os.remove(tmpname)
281 os.remove(tmpname..".tmp")
282 if not CC then Die("Can't find a working C compiler.") end
283 else
284 Die("failed to create temporary file: "..err)
285 end
286
287 -- Check for CXX.
288 tmpname = os.tmpname()..".c"
289 tmpfile, err = io.open(tmpname, "w")
290 if tmpfile then
291 tmpfile:write([[
292 #include <iostream>
293 int main()
294 {
295 std::cout << "Hello world!" << std::endl;
296 return 0;
297 }
298 ]])
299 tmpfile:close()
300
301 function extra() if not cross_compile then return "g++", "c++" end end
302 CXX = FindCommand({
303 CXX,
304 host.."-g++",
305 host.."-c++",
306 alt_host.."-g++",
307 alt_host.."-c++",
308 extra()}, tmpname.." -o "..tmpname..".tmp")
309 os.remove(tmpname)
310 os.remove(tmpname..".tmp")
311 if not CXX then Die("Can't find a working C++ compiler.") end
312 else
313 Die("failed to create temporary file: "..err)
314 end
315
316 -- Check for AR.
317 do
318 function extra() if not cross_compile then return "ar" end end
319 AR = FindCommand({
320 AR,
321 host.."-ar",
322 alt_host.."-ar",
323 extra()}, "--version")
324 if not AR then Die("Can't find a working archiver.") end
325 end
326
327 -- Check for RANLIB.
328 do
329 function extra() if not cross_compile then return "ranlib" end end
330 RANLIB = FindCommand({
331 RANLIB,
332 host.."-ranlib",
333 alt_host.."-ranlib",
334 extra()}, "--version")
335 if not RANLIB then Die("Can't find a working library indexer.") end
336 end
337
338 -- Check for WINDRES.
339 if platform == "win32" then
340 function extra() if not cross_compile then return "windres" end end
341 WINDRES = FindCommand({
342 WINDRES,
343 host.."-windres",
344 alt_host.."-windres",
345 extra()}, "--version")
346 if not WINDRES then Die("Can't find a working win32 resource compiler.") end
347 end
348
349
350 --
351 -- Configure the features and packages.
352 --
353
354 if GetFeature("debug") then
355 cflags = cflags.." -O0 -Wall -Wno-uninitialized"
356 config.DEBUG = true
357 else
358 config.NDEBUG = true
359 end
360
361 if GetFeature("double-precision") then
362 config.USE_DOUBLE_PRECISION = true
363 end
364
365 if GetFeature("profile") then
366 cflags = cflags.." -pg"
367 config.PROFILING_ENABLED = true
368 end
369
370 if GetFeature("extra-warnings") then
371 cflags = cflags.." -Wextra -Wno-unused-parameter"
372 end
373
374 if GetFeature("link-sh") then
375 -- TODO
376 end
377
378 if GetFeature("clock_gettime") then
379 -- TODO
380 end
381
382 if GetFeature("threads") then
383 config.USE_THREADS = true
384 end
385
386 if GetFeature("hotloading") then
387 print("FYI: Hotloading is very experimental and only works on Linux.");
388 config.USE_HOTLOADING = true
389 end
390
391 if GetPackage("gtk") then
392 -- TODO
393 end
394
395 if GetPackage("qt4") then
396 -- TODO
397 end
398
399
400 --
401 -- Check for the libraries we need.
402 --
403
404 do
405 local command = "PKG_CONFIG_PATH="..libdir.."/pkgconfig:$PKG_CONFIG_PATH pkg-config"
406 local deps = "sdl gl glu libpng openal vorbisfile lua"
407
408 if GetPackage("gtk") then
409 deps = deps.." gtk+-2.0"
410 elseif GetPackage("qt4") then
411 deps = deps.." QtGui"
412 end
413
414 local pc_cflags = ReadCommand(command.." --cflags "..deps)
415 if not pc_cflags then Die("Couldn't determine CFLAGS.") end
416 CFLAGS = CFLAGS.." "..pc_cflags
417 CXXFLAGS = CXXFLAGS.." "..pc_cflags
418
419 local pc_libs = " "..ReadCommand(command.." --libs "..deps)
420 if not pc_libs then Die("Couldn't determine LDFLAGS or LIBS.") end
421 for lib in pc_libs:gmatch("%s%-l%S+") do
422 LIBS = LIBS.." "..lib
423 end
424 for ldflag in pc_libs:gmatch("%s%-[^l]%S*") do
425 LDFLAGS = LDFLAGS.." "..ldflag
426 end
427
428 if platform == "win32" then
429 LIBS = LIBS.." -lws2_32"
430 exe_extension = ".exe"
431 else
432 exe_extension = ""
433 end
434 end
435
436 CFLAGS = Reduce(CFLAGS.." "..cflags)
437 CXXFLAGS = Reduce(CXXFLAGS.." "..cflags)
438 LDFLAGS = Reduce(LDFLAGS)
439 LIBS = Reduce(LIBS)
440
441
442 --
443 -- Define the exports and definitions.
444 --
445
446 config.YOINK_DATADIR = datadir
447 export.YOINK_DATADIR = datadir
448
449 do
450 local vmajor = ReadCommand("v=$(echo "..version.." | cut -d. -f1); echo ${v:-0}")
451 local vminor = ReadCommand("v=$(echo "..version.." | cut -d. -f2); echo ${v:-0}")
452 local vrevis = ReadCommand("v=$(echo "..version.." | cut -d. -f3); echo ${v:-0}")
453
454 config.VERSION_MAJOR = tonumber(vmajor)
455 config.VERSION_MINOR = tonumber(vminor)
456 config.VERSION_REVISION = tonumber(vrevis)
457 end
458
459 do
460 -- Determine and define the git revision.
461 local head = ReadCommand("git describe master")
462 config.YOINK_GITHEAD = head
463 end
464
465 config.PACKAGE = project
466 config.PACKAGE_NAME = project
467 config.VERSION = version
468 config.PACKAGE_VERSION = version
469 config.PACKAGE_STRING = project.." "..version
470 config.PACKAGE_BUGREPORT = bugreport
471
472 define.PACKAGE = project
473 define.TARNAME = tarname
474 define.TARGET = host
475 define.PLATFORM = platform
476 define.CC = CC
477 define.CXX = CXX
478 define.AR = AR
479 define.RANLIB = RANLIB
480 define.WINDRES = WINDRES
481 define.CFLAGS = CFLAGS
482 define.CXXFLAGS = CXXFLAGS
483 define.LDFLAGS = LDFLAGS
484 define.LIBS = LIBS
485 define.prefix = prefix
486 define.bindir = bindir
487 define.datadir = datadir
488 define.mandir = mandir
489 define.EXEEXT = exe_extension
490
491 export.PACKAGE_BUGREPORT = bugreport
492
493
494 --
495 -- All done; output the configuration files.
496 --
497
498 -- Print the program options.
499 output = io.open("config.h", "w")
500 for key,value in pairs(config) do
501 key = tostring(key)
502 if type(value) == "boolean" then
503 if value then
504 output:write("#define "..key)
505 else
506 output:write("#undef "..key)
507 end
508 elseif type(value) == "string" then
509 output:write(string.format("#define %s %q", key, tostring(value)))
510 else
511 output:write(string.format("#define %s %s", key, tostring(value)))
512 end
513 output:write("\n")
514 end
515 output:close()
516
517 -- Print the make definitions.
518 output = io.open("config.mk", "w")
519 for key,value in pairs(define) do
520 key = tostring(key)
521 value = tostring(value)
522 output:write(string.format("%s = %s\n", key, value))
523 end
524 output:close()
525
526
527 -- Print the exported variables.
528 output = io.open("config.sed", "w")
529 for key,value in pairs(export) do
530 key = key:gsub("/", "\\/")
531 value = value:gsub("/", "\\/")
532 output:write(string.format("s/@%s@/%s/g\n", key, value))
533 end
534 output:close()
535
536
537 print([[
538
539 Configuration complete! You can review your configuration in `config.mk'.
540
541 To finish the installation, type:
542 make
543 make install
544 ]])
545
This page took 0.05382 seconds and 4 git commands to generate.