-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
configure
executable file
·443 lines (350 loc) · 13.7 KB
/
configure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
#!/usr/bin/env python3
# Copyright 2013-2021 the openage authors. See copying.md for legal info.
"""
openage autocancer-like cmake frontend.
Together with the Makefile, ./configure provides an autotools-like build
experience. For more info, see --help and doc/buildsystem.
"""
import argparse
import os
import shlex
import shutil
import subprocess
import sys
if sys.version_info < (3, 9):
print("openage requires Python 3.9 or higher")
exit(1)
# argparsing
DESCRIPTION = """./configure is a convenience script:
it creates the build directory, symlinks it,
and invokes cmake for an out-of-source build.
Nobody is stopping you from skipping ./configure and our Makefile,
and using CMake directly (e.g. when packaging, or using an IDE).
For your convenience, ./configure even prints the direct CMake invocation!"""
EPILOG = """environment variables like CXX, CXXFLAGS, LDFLAGS are honored, \
but overwritten by command-line arguments."""
def getenv(*varnames, default=""):
"""
fetches an environment variable.
tries all given varnames until it finds an existing one.
if none fits, returns default.
"""
for var in varnames:
if var in os.environ:
return os.environ[var]
return default
def getenv_bool(varname):
"""
fetches a "boolean" environment variable.
"""
value = os.environ.get(varname)
if isinstance(value, str):
if value.lower() in {"0", "false", "no", "off", "n"}:
value = False
return bool(value)
# available optional features
# this defines the default activation for those:
# if_available: enable if it was found
# True: enable feature
# False: disable feature
# This 3-state activation allows distros to control the features definitively
# but independent compilations may still have autodetection.
OPTIONS = {
"backtrace": "if_available",
"inotify": "if_available",
"opengl": "if_available",
"vulkan": "if_available",
"gperftools-tcmalloc": False,
"gperftools-profiler": "if_available",
"ncurses": "if_available"
}
def features(args, parser):
"""
Enable or disable optional features.
If a feature is not explicitly enabled/disabled,
the defaults below will be used.
"""
def sanitize_option_name(option):
""" Check if the given feature exists """
if option not in OPTIONS:
parser.error("unknown feature: '{}'.\n"
"available features:\n {}".format(
option, '\n '.join(OPTIONS)))
options = OPTIONS.copy()
if args.with_:
for arg in args.with_:
sanitize_option_name(arg)
options[arg] = True
if args.without:
for arg in args.without:
sanitize_option_name(arg)
options[arg] = False
return options
def build_type(args):
""" Set the cmake build type """
mode = args.mode
if mode == 'debug':
ret = 'Debug'
elif mode == 'release':
ret = 'Release'
elif mode == 'relwithdebinfo':
ret = 'RelWithDebInfo'
elif mode == 'minsizerel':
ret = 'MinSizeRel'
return {
"build_type": ret
}
def get_compiler(args, parser):
"""
Compute the compiler executable name
"""
# determine compiler binaries from args.compiler
if args.compiler:
# map alias -> actual compiler
aliases = {
"clang": "clang++",
"gcc": "g++",
}
cxxver = args.compiler.split('-', maxsplit=1)
cxx = cxxver[0]
# try to replace aliases
if cxx in aliases:
cxx = aliases[cxx]
# we had a version suffix with e.g. -1.2.3
if len(cxxver) == 2:
cxx += "-" + cxxver[1]
else:
# CXX has not been specified
if sys.platform.startswith('darwin'):
cxx = 'clang++'
else:
# default to gnu compiler suite
cxx = 'g++'
# test whether the specified compiler actually exists
if not shutil.which(cxx):
parser.error('could not find c++ compiler executable: %s' % cxx)
return {
"cxx_compiler": cxx,
"cxx_flags": args.flags,
"exe_linker_flags": args.ldflags,
"module_linker_flags": args.ldflags,
"shared_linker_flags": args.ldflags,
}
def get_install_prefixes(args):
"""
Determine the install prefix configuration.
"""
ret = {
"install_prefix": args.prefix,
}
if args.py_prefix is not None:
ret["py_install_prefix"] = args.py_prefix
return ret
def bindir_creation(args, defines):
"""
configuration for the sanitizer addons for gcc and clang.
"""
def sanitize_for_filename(txt, fallback='-'):
"""
sanitizes a string for safe usage in a filename
"""
def yieldsanitizedchars():
""" generator for sanitizing the output folder name """
# False if the previous char was regular.
fallingback = True
for char in txt:
if char == fallback and fallingback:
fallingback = False
elif char.isalnum() or char in "+-_,":
fallingback = False
yield char
elif not fallingback:
fallingback = True
yield fallback
return "".join(yieldsanitizedchars())
bindir = ".bin/%s-%s-%s" % (
sanitize_for_filename(defines["cxx_compiler"]),
sanitize_for_filename(args.mode),
sanitize_for_filename("-O%s -sanitize=%s" % (
args.optimize, args.sanitize)))
if not args.dry_run:
os.makedirs(bindir, exist_ok=True)
def forcesymlink(linkto, name):
""" similar in function to ln -sf """
if args.dry_run:
return
try:
os.unlink(name)
except FileNotFoundError:
pass
os.symlink(linkto, name)
# create the build dir and symlink it to 'bin'
forcesymlink(bindir, 'bin')
return bindir
def invoke_cmake(args, bindir, defines, options):
"""
run cmake.
"""
# the project root directory contains this configure file.
project_root = os.path.dirname(os.path.realpath(__file__))
# calculate cmake invocation from defines dict
invocation = [args.cmake_binary]
maxkeylen = max(len(k) for k in defines)
for key, val in sorted(defines.items()):
print('%s | %s' % (key.rjust(maxkeylen), val))
if key in ('cxx_compiler', ):
# work around this cmake 'feature':
# when run in an existing build directory, if CXX is given,
# all other arguments are ignored... this is retarded.
if os.path.exists(os.path.join(bindir, 'CMakeCache.txt')):
continue
invocation.append('-DCMAKE_%s=%s' % (key.upper(), shlex.quote(val)))
if args.ninja:
invocation.extend(['-G', 'Ninja'])
if args.ccache:
invocation.append('-DENABLE_CCACHE=ON')
if args.clang_tidy:
invocation.append('-DENABLE_CLANG_TIDY=ON')
if args.download_nyan:
invocation.append("-DDOWNLOAD_NYAN=YES")
cxx_options = dict()
if args.iwyu:
cxx_options["CXX_INCLUDE_WHAT_YOU_USE"] = args.iwyu
invocation.append('-DWANT_IWYU=true')
cxx_options["CXX_OPTIMIZATION_LEVEL"] = args.optimize
cxx_options["CXX_SANITIZE_MODE"] = args.sanitize
cxx_options["CXX_SANITIZE_FATAL"] = args.sanitize_fatal
for key, val in sorted(cxx_options.items()):
invocation.append('-D%s=%s' % (key, val))
print("\nconfig options:\n")
maxkeylen = max(len(k) for k in options)
for key, val in sorted(options.items()):
print('%s | %s' % (key.rjust(maxkeylen), val))
invocation.append('-DWANT_%s=%s' % (
key.upper().replace('-', '_'), val))
for raw_cmake_arg in args.raw_cmake_args:
if raw_cmake_arg == "--":
continue
invocation.append(raw_cmake_arg)
invocation.append(project_root)
# look for traces of an in-source build
if os.path.isfile('CMakeCache.txt'):
print("\nwarning: found traces of an in-source build.")
print("CMakeCache.txt was deleted to make building possible.")
print("run 'make cleaninsourcebuild' to fully wipe the traces.")
os.remove('CMakeCache.txt')
# switch to build directory
print('\nbindir:\n%s/\n' % os.path.join(project_root, bindir))
if not args.dry_run:
os.chdir(bindir)
# invoke cmake
try:
print('invocation:\n%s\n' % ' '.join(invocation))
if args.dry_run:
exit(0)
else:
print("(now running cmake:)\n")
exit(subprocess.call(invocation))
except FileNotFoundError:
print("cmake was not found")
exit(1)
def main(args, parser):
"""
Compose the cmake invocation.
Basically does what many distro package managers do as well.
"""
try:
subprocess.call(['cowsay', '--', DESCRIPTION])
print("")
except (FileNotFoundError, PermissionError):
print(DESCRIPTION)
print("")
defines = {}
options = features(args, parser)
defines.update(build_type(args))
defines.update(get_compiler(args, parser))
defines.update(get_install_prefixes(args))
bindir = bindir_creation(args, defines)
invoke_cmake(args, bindir, defines, options)
def parse_args():
""" argument parsing """
cli = argparse.ArgumentParser(
description=DESCRIPTION,
epilog=EPILOG,
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
cli.add_argument("--mode", "-m",
choices=["debug", "release", "relwithdebinfo", "minsizerel"],
default=getenv("BUILDMODE", default="debug"),
help="controls cmake build mode")
cli.add_argument("--optimize", "-O",
choices=["auto", "0", "1", "g", "2", "3", "max"],
default=getenv("OPTIMIZE", default="auto"),
help=("controls optimization-related flags. " +
"is set according to mode if 'auto'. " +
"conflicts with --flags"))
cli.add_argument("--sanitize",
choices=["none", "yes", "mem", "thread"],
default=getenv("SANITIZER", default="none"),
help=("enable one of those (run-time) code sanitizers."
"'yes' enables the address and "
"undefined sanitizers."))
cli.add_argument("--sanitize-fatal", action='store_true',
default=getenv_bool("SANITIZER_FATAL"),
help="With --sanitize, stop execution on first problem.")
cli.add_argument("--compiler", "-c",
default=getenv("CXX"),
help="c++ compiler executable, default=$ENV[CXX]")
cli.add_argument("--iwyu",
choices=["warn", "error"],
default=None,
help="use include-what-you-use tool to check "
"for unnecessary imports")
cli.add_argument("--with", action='append', dest='with_', metavar='OPTION',
help="enable optional functionality. "
"for a list of available features, "
"use --list-options")
cli.add_argument("--without", action='append', metavar='OPTION',
help="disable optional functionality. "
"for a list of available features, "
"use --list-options")
cli.add_argument("--list-options", action="store_true",
help="list available optional feature switches")
cli.add_argument("--flags", "-f",
default=getenv("CXXFLAGS", "CCFLAGS", "CFLAGS"),
help="compiler flags")
cli.add_argument("--ldflags", "-l",
default=getenv("LDFLAGS"),
help="linker flags")
cli.add_argument("--prefix", "-p", default="/usr/local",
help="installation directory prefix")
cli.add_argument("--py-prefix", default=None,
help="python module installation directory prefix")
cli.add_argument("--dry-run", action='store_true',
help="just print the cmake invocation without calling it")
cli.add_argument("--cmake-binary", default="cmake",
help="path to the cmake binary")
cli.add_argument("--ninja", action="store_true",
help="use ninja instead of GNU make")
cli.add_argument("--ccache", action="store_true",
help="activate using the ccache compiler cache")
cli.add_argument("--clang-tidy", action="store_true",
help="emit clang-tidy analysis messages")
cli.add_argument("--download-nyan", action="store_true",
help="enable automatic download of the nyan project")
# arguments after -- are used as raw cmake args
cli.add_argument('raw_cmake_args', nargs=argparse.REMAINDER, default=[],
help="all args after ' -- ' are passed directly to cmake")
args = cli.parse_args()
if args.sanitize == 'none' and args.sanitize_fatal:
cli.error('--sanitize-fatal only valid with --sanitize')
if args.list_options:
header = "{} | Default state".format("Optional features:".ljust(25))
print("{}\n{}".format(header, "-" * len(header)))
for option, state in sorted(OPTIONS.items()):
state_str = (state if not isinstance(state, bool)
else ("on" if state else "off"))
print("{} | {}".format(option.ljust(25), state_str))
exit(0)
return args, cli
if __name__ == "__main__":
main(*parse_args())