-
Notifications
You must be signed in to change notification settings - Fork 5
/
SConstruct
367 lines (309 loc) · 12.7 KB
/
SConstruct
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
#
# Copyright (C) 2009-2021 Alex Smith
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# Version number.
version = 20151220
# Build flags for both host and target.
build_flags = {
'CCFLAGS': [
'-Wall',
'-Wextra',
'-Wwrite-strings',
'-Wmissing-declarations',
'-Wno-variadic-macros',
'-Wno-unused-parameter',
'-Wno-redundant-decls',
'-Wno-format',
'-Werror',
'-Wno-error=unused',
'-Wno-error=unused-function',
],
'CFLAGS': ['-std=gnu99'],
'ASFLAGS': ['-D__ASM__'],
}
# GCC-specific build flags.
gcc_flags = {
'CCFLAGS': ['-Wno-unused-but-set-variable', '-Wno-cast-function-type'],
}
# Clang-specific build flags.
clang_flags = {
# Clang's integrated assembler doesn't support 16-bit code.
'ASFLAGS': ['-no-integrated-as'],
}
# Build flags for host.
host_flags = {
'CPPDEFINES': {
'KBOOT_PREFIX': '\\"${PREFIX}\\"',
'KBOOT_LIBDIR': '\\"${LIBDIR}\\"',
'KBOOT_LOADER_VERSION': '\\"${VERSION}\\"',
}
}
# Build flags for target.
target_flags = {
'CCFLAGS': [
'-gdwarf-2', '-pipe', '-nostdlib', '-nostdinc', '-ffreestanding',
'-fno-stack-protector', '-Os', '-fno-omit-frame-pointer',
'-fno-optimize-sibling-calls',
],
'ASFLAGS': ['-nostdinc'],
'LINKFLAGS': ['-nostdlib', '-Wl,--build-id=none', '-Wl,-z,noexecstack', '-Wl,--no-warn-rwx-segments'],
}
###############
# Build setup #
###############
import os, sys
from subprocess import Popen, PIPE
sys.path = [os.path.abspath(os.path.join('dev', 'build'))] + sys.path
import util, vcs
# Create symlinks instead of hardlinks in the build tree, which stops VSCode
# from finding files in the build tree instead of the main source.
SetOption('duplicate', 'soft-hard-copy')
# Get revision.
revision = vcs.revision_id()
# Compile for debug by default if building from git.
debug_default = 1 if revision is not None else 0
# Configurable build options.
opts = Variables('.options.cache')
opts.AddVariables(
('CONFIG', 'Target system configuration name.'),
('CROSS_COMPILE', 'Cross compiler tool prefix (prepended to all tool names).', ''),
('PREFIX', 'Installation prefix.', '/usr/local'),
BoolVariable('DEBUG', 'Whether to compile with debugging features.', debug_default),
)
# Create the build environment.
env = Environment(ENV = os.environ, variables = opts, tools = ['default', 'textfile'])
opts.Save('.options.cache', env)
env.Tool('compilation_db')
# Setting COMPILATION_DBCOMSTR to None or empty results in the function name
# being printed, this silences it completely.
env.CompilationDatabase.builder.action.cmdstr = None
# Define the version string.
env['VERSION'] = '%d+%s' % (version, revision) if revision is not None else '%d' % (version)
# Get a list of known configurations.
configs = SConscript('config/SConscript')
# Generate help text.
helptext = \
'To build KBoot, a target system configuration must be specified on the command\n' + \
'line with the CONFIG option. The following configurations are available:\n' + \
'\n' + \
''.join([' %-12s - %s\n' % (c, configs[c]['description']) for c in sorted(iter(configs))]) + \
'\n' + \
'The following build options can be set on the command line. These will be saved\n' + \
'for later invocations of SCons, so you do not need to specify them every time:\n' + \
opts.GenerateHelpText(env) + \
'\n' + \
'For information on how to build KBoot, please refer to documentation/guide/building.md.\n'
Help(helptext)
# Check if the configuration specified is invalid.
if not 'CONFIG' in env:
util.StopError("No target system configuration specified. See 'scons -h'.")
elif not env['CONFIG'] in configs:
util.StopError("Unknown configuration '%s'." % (env['CONFIG']))
# Make build output nice.
verbose = ARGUMENTS.get('V') == '1'
def compile_str(msg):
return '\033[0;32m%8s\033[0m $TARGET' % (msg)
def compile_str_func(msg, target, source, env):
return '\033[0;32m%8s\033[0m %s' % (msg, str(target[0]))
if not verbose:
env['INSTALLSTR'] = compile_str('INSTALL')
# Substfile doesn't provide a method to override the output. Hack around.
env['BUILDERS']['Substfile'].action.strfunction = lambda t, s, e: compile_str_func('GEN', t, s, e)
# Merge in build flags.
for (k, v) in build_flags.items():
env[k] = v
# Add a builder to preprocess linker scripts.
env['BUILDERS']['LDScript'] = Builder(action = Action(
'$CC $_CCCOMCOM $ASFLAGS -E -x c $SOURCE | grep -v "^#" > $TARGET',
'$GENCOMSTR'))
# Define installation paths.
env['BINDIR'] = os.path.join(env['PREFIX'], 'bin')
env['LIBDIR'] = os.path.join(env['PREFIX'], 'lib', 'kboot')
env['TARGETDIR'] = os.path.join(env['LIBDIR'], env['CONFIG'])
# Define real installation paths taking DESTDIR into account.
env['DESTDIR'] = ARGUMENTS.get('DESTDIR', '/')
env['DESTBINDIR'] = os.path.join(env['DESTDIR'], os.path.relpath(env['BINDIR'], '/'))
env['DESTLIBDIR'] = os.path.join(env['DESTDIR'], os.path.relpath(env['LIBDIR'], '/'))
env['DESTTARGETDIR'] = os.path.join(env['DESTDIR'], os.path.relpath(env['TARGETDIR'], '/'))
################################
# Host build environment setup #
################################
# Set up the host build environment.
host_env = env.Clone()
# Make build output nice.
if not verbose:
host_env['ARCOMSTR'] = compile_str('HOSTAR')
host_env['ASCOMSTR'] = compile_str('HOSTAS')
host_env['ASPPCOMSTR'] = compile_str('HOSTAS')
host_env['CCCOMSTR'] = compile_str('HOSTCC')
host_env['CXXCOMSTR'] = compile_str('HOSTCXX')
host_env['LINKCOMSTR'] = compile_str('HOSTLINK')
host_env['RANLIBCOMSTR'] = compile_str('HOSTRL')
host_env['GENCOMSTR'] = compile_str('HOSTGEN')
host_env['STRIPCOMSTR'] = compile_str('HOSTSTRIP')
# Merge in build flags.
for (k, v) in host_flags.items():
if k in host_env:
if type(v) == dict:
host_env[k].update(v)
else:
host_env[k] += v
else:
host_env[k] = v
# Add compiler-specific flags.
output = Popen([host_env['CC'], '--version'], stdout=PIPE, stderr=PIPE).communicate()[0].decode('utf-8').strip()
host_env['IS_CLANG'] = output.find('clang') >= 0
if host_env['IS_CLANG']:
for (k, v) in clang_flags.items():
host_env[k] += v
else:
for (k, v) in gcc_flags.items():
host_env[k] += v
# We place the final output binaries in a single directory.
host_env['OUTDIR'] = Dir('build/host/bin')
# Build host system utilities.
SConscript('utilities/SConscript',
variant_dir = os.path.join('build', 'host', 'utilities'),
exports = {'env': host_env})
# Create an alias to build the utilities.
utilities = host_env.Glob('${OUTDIR}/*')
Alias('utilities', utilities)
# Create installation targets for the utilities.
for target in utilities:
install = host_env.Install(host_env['DESTBINDIR'], target)
Alias('install-utilities', install)
##################################
# Target build environment setup #
##################################
config = configs[env['CONFIG']]['config']
# Set the debug flag in the configuration.
if env['DEBUG']:
config['DEBUG'] = True
# Make build output nice.
if not verbose:
env['ARCOMSTR'] = compile_str('AR')
env['ASCOMSTR'] = compile_str('AS')
env['ASPPCOMSTR'] = compile_str('AS')
env['CCCOMSTR'] = compile_str('CC')
env['CXXCOMSTR'] = compile_str('CXX')
env['LINKCOMSTR'] = compile_str('LINK')
env['RANLIBCOMSTR'] = compile_str('RL')
env['GENCOMSTR'] = compile_str('GEN')
env['STRIPCOMSTR'] = compile_str('STRIP')
env['COMPILATIONDB_COMSTR'] = compile_str('DB')
# Detect which compiler to use.
def guess_compiler(name):
if name in os.environ:
compilers = [os.environ[name]]
else:
compilers = [env['CROSS_COMPILE'] + x for x in ['cc', 'gcc', 'clang']]
for compiler in compilers:
if util.which(compiler):
return compiler
util.StopError('Toolchain has no usable compiler available.')
if 'CC' in os.environ and os.path.basename(os.environ['CC']) == 'ccc-analyzer':
# Support the clang static analyzer.
env['CC'] = os.environ['CC']
env['ENV']['CCC_CC'] = guess_compiler('CCC_CC')
else:
env['CC'] = guess_compiler('CC')
# Set paths to other build utilities.
env['AS'] = env['CROSS_COMPILE'] + 'as'
env['OBJDUMP'] = env['CROSS_COMPILE'] + 'objdump'
env['READELF'] = env['CROSS_COMPILE'] + 'readelf'
env['NM'] = env['CROSS_COMPILE'] + 'nm'
env['STRIP'] = env['CROSS_COMPILE'] + 'strip'
env['AR'] = env['CROSS_COMPILE'] + 'ar'
env['RANLIB'] = env['CROSS_COMPILE'] + 'ranlib'
env['OBJCOPY'] = env['CROSS_COMPILE'] + 'objcopy'
env['LD'] = env['CROSS_COMPILE'] + 'ld'
# Override default assembler - it uses as directly, we want to use GCC.
env['ASCOM'] = '$CC $_CCCOMCOM $ASFLAGS -c -o $TARGET $SOURCES'
# Merge in build flags.
for (k, v) in target_flags.items():
env[k] += v
# Add compiler-specific flags.
output = Popen([env['CC'], '--version'], stdout=PIPE, stderr=PIPE).communicate()[0].decode('utf-8').strip()
env['IS_CLANG'] = output.find('clang') >= 0
if env['IS_CLANG']:
for (k, v) in clang_flags.items():
env[k] += v
else:
for (k, v) in gcc_flags.items():
env[k] += v
# Test if the compiler supports the -no-pie option.
output = Popen([env['CC'], '-no-pie'], stdout=PIPE, stderr=PIPE).communicate()[0].strip().decode('utf-8')
if output.find('unrecognized') >= 0 or output.find('unknown') >= 0:
env['NO_PIE'] = ''
else:
output = Popen([env['CC'], '-dumpspecs'], stdout=PIPE, stderr=PIPE).communicate()[0].strip().decode('utf-8')
# Clang doesn't support dumpspecs.
if output.find('unsupported option') >= 0 or output.find('no-pie') >= 0:
env['NO_PIE'] = '-no-pie'
# Add the compiler include directory for some standard headers.
incdir = Popen([env['CC'], '-print-file-name=include'], stdout=PIPE).communicate()[0].strip().decode('utf-8')
env['CCFLAGS'] += ['-isystem%s' % (incdir)]
env['ASFLAGS'] += ['-isystem%s' % (incdir)]
# Add emitters to handle command line -include arguments.
from SCons.Script import *
static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
def cmdline_deps_emitter(target, source, env):
for idx, flag in enumerate(env['CCFLAGS']):
if flag == '-include':
next = env.subst(env['CCFLAGS'][idx + 1], conv = lambda x: x)
Depends(target[0], File(next))
return (target, source)
suffixes = ['.c', '.S']
for suffix in suffixes:
emitter = static_obj.emitter.get(suffix, False)
static_obj.emitter[suffix] = SCons.Builder.ListEmitter([emitter, cmdline_deps_emitter])
# Change the Decider to MD5-timestamp to speed up the build a bit.
Decider('MD5-timestamp')
# We place the final output binaries in a single directory.
env['OUTDIR'] = Dir('build/%s/bin' % (env['CONFIG']))
# Build the loader itself.
SConscript('source/SConscript',
variant_dir = os.path.join('build', env['CONFIG'], 'source'),
exports = ['config', 'env'])
# Default targets are all output binaries.
targets = env.Glob('${OUTDIR}/*')
Default(Alias('loader', targets))
# Install all files in the output directory.
for target in targets:
install = env.Install(env['DESTTARGETDIR'], target)
Alias('install', install)
# Add a debug install target to install the debug binary.
install = env.Install(env['DESTTARGETDIR'], env['KBOOT'])
Alias('install-debug', install)
################
# Test targets #
################
SConscript('test/SConscript',
variant_dir = os.path.join('build', env['CONFIG'], 'test'),
exports = ['config', 'env'])
# Get QEMU script to run.
qemu = ARGUMENTS.get('QEMU', '')
if len(qemu):
qemu = '%s-%s.sh' % (env['CONFIG'], qemu)
else:
qemu = '%s.sh' % (env['CONFIG'])
# Add a target to run the test script for this configuration (if it exists).
script = os.path.join('test', 'qemu', qemu)
if os.path.exists(script):
Alias('qemu', env.Command('__qemu', ['loader', 'test', 'utilities'], Action(script, None)))
# Generation compilation database.
compile_commands = env.CompilationDatabase(os.path.join('build', 'compile_commands.json'))
env.Default(compile_commands)
env.Alias("compiledb", compile_commands)