SConscript revision 11988
1# -*- mode:python -*-
2
3# Copyright (c) 2004-2005 The Regents of The University of Michigan
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are
8# met: redistributions of source code must retain the above copyright
9# notice, this list of conditions and the following disclaimer;
10# redistributions in binary form must reproduce the above copyright
11# notice, this list of conditions and the following disclaimer in the
12# documentation and/or other materials provided with the distribution;
13# neither the name of the copyright holders nor the names of its
14# contributors may be used to endorse or promote products derived from
15# this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28#
29# Authors: Nathan Binkert
30
31import array
32import bisect
33import imp
34import marshal
35import os
36import re
37import subprocess
38import sys
39import zlib
40
41from os.path import basename, dirname, exists, isdir, isfile, join as joinpath
42
43import SCons
44
45# This file defines how to build a particular configuration of gem5
46# based on variable settings in the 'env' build environment.
47
48Import('*')
49
50# Children need to see the environment
51Export('env')
52
53build_env = [(opt, env[opt]) for opt in export_vars]
54
55from m5.util import code_formatter, compareVersions
56
57########################################################################
58# Code for adding source files of various types
59#
60# When specifying a source file of some type, a set of guards can be
61# specified for that file.  When get() is used to find the files, if
62# get specifies a set of filters, only files that match those filters
63# will be accepted (unspecified filters on files are assumed to be
64# false).  Current filters are:
65#     main -- specifies the gem5 main() function
66#     skip_lib -- do not put this file into the gem5 library
67#     skip_no_python -- do not put this file into a no_python library
68#       as it embeds compiled Python
69#     <unittest> -- unit tests use filters based on the unit test name
70#
71# A parent can now be specified for a source file and default filter
72# values will be retrieved recursively from parents (children override
73# parents).
74#
75def guarded_source_iterator(sources, **guards):
76    '''Iterate over a set of sources, gated by a set of guards.'''
77    for src in sources:
78        for flag,value in guards.iteritems():
79            # if the flag is found and has a different value, skip
80            # this file
81            if src.all_guards.get(flag, False) != value:
82                break
83        else:
84            yield src
85
86class SourceMeta(type):
87    '''Meta class for source files that keeps track of all files of a
88    particular type and has a get function for finding all functions
89    of a certain type that match a set of guards'''
90    def __init__(cls, name, bases, dict):
91        super(SourceMeta, cls).__init__(name, bases, dict)
92        cls.all = []
93
94    def get(cls, **guards):
95        '''Find all files that match the specified guards.  If a source
96        file does not specify a flag, the default is False'''
97        for s in guarded_source_iterator(cls.all, **guards):
98            yield s
99
100class SourceFile(object):
101    '''Base object that encapsulates the notion of a source file.
102    This includes, the source node, target node, various manipulations
103    of those.  A source file also specifies a set of guards which
104    describing which builds the source file applies to.  A parent can
105    also be specified to get default guards from'''
106    __metaclass__ = SourceMeta
107    def __init__(self, source, parent=None, **guards):
108        self.guards = guards
109        self.parent = parent
110
111        tnode = source
112        if not isinstance(source, SCons.Node.FS.File):
113            tnode = File(source)
114
115        self.tnode = tnode
116        self.snode = tnode.srcnode()
117
118        for base in type(self).__mro__:
119            if issubclass(base, SourceFile):
120                base.all.append(self)
121
122    @property
123    def filename(self):
124        return str(self.tnode)
125
126    @property
127    def dirname(self):
128        return dirname(self.filename)
129
130    @property
131    def basename(self):
132        return basename(self.filename)
133
134    @property
135    def extname(self):
136        index = self.basename.rfind('.')
137        if index <= 0:
138            # dot files aren't extensions
139            return self.basename, None
140
141        return self.basename[:index], self.basename[index+1:]
142
143    @property
144    def all_guards(self):
145        '''find all guards for this object getting default values
146        recursively from its parents'''
147        guards = {}
148        if self.parent:
149            guards.update(self.parent.guards)
150        guards.update(self.guards)
151        return guards
152
153    def __lt__(self, other): return self.filename < other.filename
154    def __le__(self, other): return self.filename <= other.filename
155    def __gt__(self, other): return self.filename > other.filename
156    def __ge__(self, other): return self.filename >= other.filename
157    def __eq__(self, other): return self.filename == other.filename
158    def __ne__(self, other): return self.filename != other.filename
159
160    @staticmethod
161    def done():
162        def disabled(cls, name, *ignored):
163            raise RuntimeError("Additional SourceFile '%s'" % name,\
164                  "declared, but targets deps are already fixed.")
165        SourceFile.__init__ = disabled
166
167
168class Source(SourceFile):
169    current_group = None
170    source_groups = { None : [] }
171
172    @classmethod
173    def set_group(cls, group):
174        if not group in Source.source_groups:
175            Source.source_groups[group] = []
176        Source.current_group = group
177
178    '''Add a c/c++ source file to the build'''
179    def __init__(self, source, Werror=True, **guards):
180        '''specify the source file, and any guards'''
181        super(Source, self).__init__(source, **guards)
182
183        self.Werror = Werror
184
185        Source.source_groups[Source.current_group].append(self)
186
187class PySource(SourceFile):
188    '''Add a python source file to the named package'''
189    invalid_sym_char = re.compile('[^A-z0-9_]')
190    modules = {}
191    tnodes = {}
192    symnames = {}
193
194    def __init__(self, package, source, **guards):
195        '''specify the python package, the source file, and any guards'''
196        super(PySource, self).__init__(source, **guards)
197
198        modname,ext = self.extname
199        assert ext == 'py'
200
201        if package:
202            path = package.split('.')
203        else:
204            path = []
205
206        modpath = path[:]
207        if modname != '__init__':
208            modpath += [ modname ]
209        modpath = '.'.join(modpath)
210
211        arcpath = path + [ self.basename ]
212        abspath = self.snode.abspath
213        if not exists(abspath):
214            abspath = self.tnode.abspath
215
216        self.package = package
217        self.modname = modname
218        self.modpath = modpath
219        self.arcname = joinpath(*arcpath)
220        self.abspath = abspath
221        self.compiled = File(self.filename + 'c')
222        self.cpp = File(self.filename + '.cc')
223        self.symname = PySource.invalid_sym_char.sub('_', modpath)
224
225        PySource.modules[modpath] = self
226        PySource.tnodes[self.tnode] = self
227        PySource.symnames[self.symname] = self
228
229class SimObject(PySource):
230    '''Add a SimObject python file as a python source object and add
231    it to a list of sim object modules'''
232
233    fixed = False
234    modnames = []
235
236    def __init__(self, source, **guards):
237        '''Specify the source file and any guards (automatically in
238        the m5.objects package)'''
239        super(SimObject, self).__init__('m5.objects', source, **guards)
240        if self.fixed:
241            raise AttributeError, "Too late to call SimObject now."
242
243        bisect.insort_right(SimObject.modnames, self.modname)
244
245class ProtoBuf(SourceFile):
246    '''Add a Protocol Buffer to build'''
247
248    def __init__(self, source, **guards):
249        '''Specify the source file, and any guards'''
250        super(ProtoBuf, self).__init__(source, **guards)
251
252        # Get the file name and the extension
253        modname,ext = self.extname
254        assert ext == 'proto'
255
256        # Currently, we stick to generating the C++ headers, so we
257        # only need to track the source and header.
258        self.cc_file = File(modname + '.pb.cc')
259        self.hh_file = File(modname + '.pb.h')
260
261class UnitTest(object):
262    '''Create a UnitTest'''
263
264    all = []
265    def __init__(self, target, *sources, **kwargs):
266        '''Specify the target name and any sources.  Sources that are
267        not SourceFiles are evalued with Source().  All files are
268        guarded with a guard of the same name as the UnitTest
269        target.'''
270
271        srcs = []
272        for src in sources:
273            if not isinstance(src, SourceFile):
274                src = Source(src, skip_lib=True)
275            src.guards[target] = True
276            srcs.append(src)
277
278        self.sources = srcs
279        self.target = target
280        self.main = kwargs.get('main', False)
281        UnitTest.all.append(self)
282
283# Children should have access
284Export('Source')
285Export('PySource')
286Export('SimObject')
287Export('ProtoBuf')
288Export('UnitTest')
289
290########################################################################
291#
292# Debug Flags
293#
294debug_flags = {}
295def DebugFlag(name, desc=None):
296    if name in debug_flags:
297        raise AttributeError, "Flag %s already specified" % name
298    debug_flags[name] = (name, (), desc)
299
300def CompoundFlag(name, flags, desc=None):
301    if name in debug_flags:
302        raise AttributeError, "Flag %s already specified" % name
303
304    compound = tuple(flags)
305    debug_flags[name] = (name, compound, desc)
306
307Export('DebugFlag')
308Export('CompoundFlag')
309
310########################################################################
311#
312# Set some compiler variables
313#
314
315# Include file paths are rooted in this directory.  SCons will
316# automatically expand '.' to refer to both the source directory and
317# the corresponding build directory to pick up generated include
318# files.
319env.Append(CPPPATH=Dir('.'))
320
321for extra_dir in extras_dir_list:
322    env.Append(CPPPATH=Dir(extra_dir))
323
324# Workaround for bug in SCons version > 0.97d20071212
325# Scons bug id: 2006 gem5 Bug id: 308
326for root, dirs, files in os.walk(base_dir, topdown=True):
327    Dir(root[len(base_dir) + 1:])
328
329########################################################################
330#
331# Walk the tree and execute all SConscripts in subdirectories
332#
333
334here = Dir('.').srcnode().abspath
335for root, dirs, files in os.walk(base_dir, topdown=True):
336    if root == here:
337        # we don't want to recurse back into this SConscript
338        continue
339
340    if 'SConscript' in files:
341        build_dir = joinpath(env['BUILDDIR'], root[len(base_dir) + 1:])
342        Source.set_group(build_dir)
343        SConscript(joinpath(root, 'SConscript'), variant_dir=build_dir)
344
345for extra_dir in extras_dir_list:
346    prefix_len = len(dirname(extra_dir)) + 1
347
348    # Also add the corresponding build directory to pick up generated
349    # include files.
350    env.Append(CPPPATH=Dir(joinpath(env['BUILDDIR'], extra_dir[prefix_len:])))
351
352    for root, dirs, files in os.walk(extra_dir, topdown=True):
353        # if build lives in the extras directory, don't walk down it
354        if 'build' in dirs:
355            dirs.remove('build')
356
357        if 'SConscript' in files:
358            build_dir = joinpath(env['BUILDDIR'], root[prefix_len:])
359            SConscript(joinpath(root, 'SConscript'), variant_dir=build_dir)
360
361for opt in export_vars:
362    env.ConfigFile(opt)
363
364def makeTheISA(source, target, env):
365    isas = [ src.get_contents() for src in source ]
366    target_isa = env['TARGET_ISA']
367    def define(isa):
368        return isa.upper() + '_ISA'
369
370    def namespace(isa):
371        return isa[0].upper() + isa[1:].lower() + 'ISA'
372
373
374    code = code_formatter()
375    code('''\
376#ifndef __CONFIG_THE_ISA_HH__
377#define __CONFIG_THE_ISA_HH__
378
379''')
380
381    # create defines for the preprocessing and compile-time determination
382    for i,isa in enumerate(isas):
383        code('#define $0 $1', define(isa), i + 1)
384    code()
385
386    # create an enum for any run-time determination of the ISA, we
387    # reuse the same name as the namespaces
388    code('enum class Arch {')
389    for i,isa in enumerate(isas):
390        if i + 1 == len(isas):
391            code('  $0 = $1', namespace(isa), define(isa))
392        else:
393            code('  $0 = $1,', namespace(isa), define(isa))
394    code('};')
395
396    code('''
397
398#define THE_ISA ${{define(target_isa)}}
399#define TheISA ${{namespace(target_isa)}}
400#define THE_ISA_STR "${{target_isa}}"
401
402#endif // __CONFIG_THE_ISA_HH__''')
403
404    code.write(str(target[0]))
405
406env.Command('config/the_isa.hh', map(Value, all_isa_list),
407            MakeAction(makeTheISA, Transform("CFG ISA", 0)))
408
409def makeTheGPUISA(source, target, env):
410    isas = [ src.get_contents() for src in source ]
411    target_gpu_isa = env['TARGET_GPU_ISA']
412    def define(isa):
413        return isa.upper() + '_ISA'
414
415    def namespace(isa):
416        return isa[0].upper() + isa[1:].lower() + 'ISA'
417
418
419    code = code_formatter()
420    code('''\
421#ifndef __CONFIG_THE_GPU_ISA_HH__
422#define __CONFIG_THE_GPU_ISA_HH__
423
424''')
425
426    # create defines for the preprocessing and compile-time determination
427    for i,isa in enumerate(isas):
428        code('#define $0 $1', define(isa), i + 1)
429    code()
430
431    # create an enum for any run-time determination of the ISA, we
432    # reuse the same name as the namespaces
433    code('enum class GPUArch {')
434    for i,isa in enumerate(isas):
435        if i + 1 == len(isas):
436            code('  $0 = $1', namespace(isa), define(isa))
437        else:
438            code('  $0 = $1,', namespace(isa), define(isa))
439    code('};')
440
441    code('''
442
443#define THE_GPU_ISA ${{define(target_gpu_isa)}}
444#define TheGpuISA ${{namespace(target_gpu_isa)}}
445#define THE_GPU_ISA_STR "${{target_gpu_isa}}"
446
447#endif // __CONFIG_THE_GPU_ISA_HH__''')
448
449    code.write(str(target[0]))
450
451env.Command('config/the_gpu_isa.hh', map(Value, all_gpu_isa_list),
452            MakeAction(makeTheGPUISA, Transform("CFG ISA", 0)))
453
454########################################################################
455#
456# Prevent any SimObjects from being added after this point, they
457# should all have been added in the SConscripts above
458#
459SimObject.fixed = True
460
461class DictImporter(object):
462    '''This importer takes a dictionary of arbitrary module names that
463    map to arbitrary filenames.'''
464    def __init__(self, modules):
465        self.modules = modules
466        self.installed = set()
467
468    def __del__(self):
469        self.unload()
470
471    def unload(self):
472        import sys
473        for module in self.installed:
474            del sys.modules[module]
475        self.installed = set()
476
477    def find_module(self, fullname, path):
478        if fullname == 'm5.defines':
479            return self
480
481        if fullname == 'm5.objects':
482            return self
483
484        if fullname.startswith('_m5'):
485            return None
486
487        source = self.modules.get(fullname, None)
488        if source is not None and fullname.startswith('m5.objects'):
489            return self
490
491        return None
492
493    def load_module(self, fullname):
494        mod = imp.new_module(fullname)
495        sys.modules[fullname] = mod
496        self.installed.add(fullname)
497
498        mod.__loader__ = self
499        if fullname == 'm5.objects':
500            mod.__path__ = fullname.split('.')
501            return mod
502
503        if fullname == 'm5.defines':
504            mod.__dict__['buildEnv'] = m5.util.SmartDict(build_env)
505            return mod
506
507        source = self.modules[fullname]
508        if source.modname == '__init__':
509            mod.__path__ = source.modpath
510        mod.__file__ = source.abspath
511
512        exec file(source.abspath, 'r') in mod.__dict__
513
514        return mod
515
516import m5.SimObject
517import m5.params
518from m5.util import code_formatter
519
520m5.SimObject.clear()
521m5.params.clear()
522
523# install the python importer so we can grab stuff from the source
524# tree itself.  We can't have SimObjects added after this point or
525# else we won't know about them for the rest of the stuff.
526importer = DictImporter(PySource.modules)
527sys.meta_path[0:0] = [ importer ]
528
529# import all sim objects so we can populate the all_objects list
530# make sure that we're working with a list, then let's sort it
531for modname in SimObject.modnames:
532    exec('from m5.objects import %s' % modname)
533
534# we need to unload all of the currently imported modules so that they
535# will be re-imported the next time the sconscript is run
536importer.unload()
537sys.meta_path.remove(importer)
538
539sim_objects = m5.SimObject.allClasses
540all_enums = m5.params.allEnums
541
542for name,obj in sorted(sim_objects.iteritems()):
543    for param in obj._params.local.values():
544        # load the ptype attribute now because it depends on the
545        # current version of SimObject.allClasses, but when scons
546        # actually uses the value, all versions of
547        # SimObject.allClasses will have been loaded
548        param.ptype
549
550########################################################################
551#
552# calculate extra dependencies
553#
554module_depends = ["m5", "m5.SimObject", "m5.params"]
555depends = [ PySource.modules[dep].snode for dep in module_depends ]
556depends.sort(key = lambda x: x.name)
557
558########################################################################
559#
560# Commands for the basic automatically generated python files
561#
562
563# Generate Python file containing a dict specifying the current
564# buildEnv flags.
565def makeDefinesPyFile(target, source, env):
566    build_env = source[0].get_contents()
567
568    code = code_formatter()
569    code("""
570import _m5.core
571import m5.util
572
573buildEnv = m5.util.SmartDict($build_env)
574
575compileDate = _m5.core.compileDate
576_globals = globals()
577for key,val in _m5.core.__dict__.iteritems():
578    if key.startswith('flag_'):
579        flag = key[5:]
580        _globals[flag] = val
581del _globals
582""")
583    code.write(target[0].abspath)
584
585defines_info = Value(build_env)
586# Generate a file with all of the compile options in it
587env.Command('python/m5/defines.py', defines_info,
588            MakeAction(makeDefinesPyFile, Transform("DEFINES", 0)))
589PySource('m5', 'python/m5/defines.py')
590
591# Generate python file containing info about the M5 source code
592def makeInfoPyFile(target, source, env):
593    code = code_formatter()
594    for src in source:
595        data = ''.join(file(src.srcnode().abspath, 'r').xreadlines())
596        code('$src = ${{repr(data)}}')
597    code.write(str(target[0]))
598
599# Generate a file that wraps the basic top level files
600env.Command('python/m5/info.py',
601            [ '#/COPYING', '#/LICENSE', '#/README', ],
602            MakeAction(makeInfoPyFile, Transform("INFO")))
603PySource('m5', 'python/m5/info.py')
604
605########################################################################
606#
607# Create all of the SimObject param headers and enum headers
608#
609
610def createSimObjectParamStruct(target, source, env):
611    assert len(target) == 1 and len(source) == 1
612
613    name = str(source[0].get_contents())
614    obj = sim_objects[name]
615
616    code = code_formatter()
617    obj.cxx_param_decl(code)
618    code.write(target[0].abspath)
619
620def createSimObjectCxxConfig(is_header):
621    def body(target, source, env):
622        assert len(target) == 1 and len(source) == 1
623
624        name = str(source[0].get_contents())
625        obj = sim_objects[name]
626
627        code = code_formatter()
628        obj.cxx_config_param_file(code, is_header)
629        code.write(target[0].abspath)
630    return body
631
632def createEnumStrings(target, source, env):
633    assert len(target) == 1 and len(source) == 1
634
635    name = str(source[0].get_contents())
636    obj = all_enums[name]
637
638    code = code_formatter()
639    obj.cxx_def(code)
640    if env['USE_PYTHON']:
641        obj.pybind_def(code)
642    code.write(target[0].abspath)
643
644def createEnumDecls(target, source, env):
645    assert len(target) == 1 and len(source) == 1
646
647    name = str(source[0].get_contents())
648    obj = all_enums[name]
649
650    code = code_formatter()
651    obj.cxx_decl(code)
652    code.write(target[0].abspath)
653
654def createSimObjectPyBindWrapper(target, source, env):
655    name = source[0].get_contents()
656    obj = sim_objects[name]
657
658    code = code_formatter()
659    obj.pybind_decl(code)
660    code.write(target[0].abspath)
661
662# Generate all of the SimObject param C++ struct header files
663params_hh_files = []
664for name,simobj in sorted(sim_objects.iteritems()):
665    py_source = PySource.modules[simobj.__module__]
666    extra_deps = [ py_source.tnode ]
667
668    hh_file = File('params/%s.hh' % name)
669    params_hh_files.append(hh_file)
670    env.Command(hh_file, Value(name),
671                MakeAction(createSimObjectParamStruct, Transform("SO PARAM")))
672    env.Depends(hh_file, depends + extra_deps)
673
674# C++ parameter description files
675if GetOption('with_cxx_config'):
676    for name,simobj in sorted(sim_objects.iteritems()):
677        py_source = PySource.modules[simobj.__module__]
678        extra_deps = [ py_source.tnode ]
679
680        cxx_config_hh_file = File('cxx_config/%s.hh' % name)
681        cxx_config_cc_file = File('cxx_config/%s.cc' % name)
682        env.Command(cxx_config_hh_file, Value(name),
683                    MakeAction(createSimObjectCxxConfig(True),
684                    Transform("CXXCPRHH")))
685        env.Command(cxx_config_cc_file, Value(name),
686                    MakeAction(createSimObjectCxxConfig(False),
687                    Transform("CXXCPRCC")))
688        env.Depends(cxx_config_hh_file, depends + extra_deps +
689                    [File('params/%s.hh' % name), File('sim/cxx_config.hh')])
690        env.Depends(cxx_config_cc_file, depends + extra_deps +
691                    [cxx_config_hh_file])
692        Source(cxx_config_cc_file)
693
694    cxx_config_init_cc_file = File('cxx_config/init.cc')
695
696    def createCxxConfigInitCC(target, source, env):
697        assert len(target) == 1 and len(source) == 1
698
699        code = code_formatter()
700
701        for name,simobj in sorted(sim_objects.iteritems()):
702            if not hasattr(simobj, 'abstract') or not simobj.abstract:
703                code('#include "cxx_config/${name}.hh"')
704        code()
705        code('void cxxConfigInit()')
706        code('{')
707        code.indent()
708        for name,simobj in sorted(sim_objects.iteritems()):
709            not_abstract = not hasattr(simobj, 'abstract') or \
710                not simobj.abstract
711            if not_abstract and 'type' in simobj.__dict__:
712                code('cxx_config_directory["${name}"] = '
713                     '${name}CxxConfigParams::makeDirectoryEntry();')
714        code.dedent()
715        code('}')
716        code.write(target[0].abspath)
717
718    py_source = PySource.modules[simobj.__module__]
719    extra_deps = [ py_source.tnode ]
720    env.Command(cxx_config_init_cc_file, Value(name),
721        MakeAction(createCxxConfigInitCC, Transform("CXXCINIT")))
722    cxx_param_hh_files = ["cxx_config/%s.hh" % simobj
723        for name,simobj in sorted(sim_objects.iteritems())
724        if not hasattr(simobj, 'abstract') or not simobj.abstract]
725    Depends(cxx_config_init_cc_file, cxx_param_hh_files +
726            [File('sim/cxx_config.hh')])
727    Source(cxx_config_init_cc_file)
728
729# Generate all enum header files
730for name,enum in sorted(all_enums.iteritems()):
731    py_source = PySource.modules[enum.__module__]
732    extra_deps = [ py_source.tnode ]
733
734    cc_file = File('enums/%s.cc' % name)
735    env.Command(cc_file, Value(name),
736                MakeAction(createEnumStrings, Transform("ENUM STR")))
737    env.Depends(cc_file, depends + extra_deps)
738    Source(cc_file)
739
740    hh_file = File('enums/%s.hh' % name)
741    env.Command(hh_file, Value(name),
742                MakeAction(createEnumDecls, Transform("ENUMDECL")))
743    env.Depends(hh_file, depends + extra_deps)
744
745# Generate SimObject Python bindings wrapper files
746if env['USE_PYTHON']:
747    for name,simobj in sorted(sim_objects.iteritems()):
748        py_source = PySource.modules[simobj.__module__]
749        extra_deps = [ py_source.tnode ]
750        cc_file = File('python/_m5/param_%s.cc' % name)
751        env.Command(cc_file, Value(name),
752                    MakeAction(createSimObjectPyBindWrapper,
753                               Transform("SO PyBind")))
754        env.Depends(cc_file, depends + extra_deps)
755        Source(cc_file)
756
757# Build all protocol buffers if we have got protoc and protobuf available
758if env['HAVE_PROTOBUF']:
759    for proto in ProtoBuf.all:
760        # Use both the source and header as the target, and the .proto
761        # file as the source. When executing the protoc compiler, also
762        # specify the proto_path to avoid having the generated files
763        # include the path.
764        env.Command([proto.cc_file, proto.hh_file], proto.tnode,
765                    MakeAction('$PROTOC --cpp_out ${TARGET.dir} '
766                               '--proto_path ${SOURCE.dir} $SOURCE',
767                               Transform("PROTOC")))
768
769        # Add the C++ source file
770        Source(proto.cc_file, **proto.guards)
771elif ProtoBuf.all:
772    print 'Got protobuf to build, but lacks support!'
773    Exit(1)
774
775#
776# Handle debug flags
777#
778def makeDebugFlagCC(target, source, env):
779    assert(len(target) == 1 and len(source) == 1)
780
781    code = code_formatter()
782
783    # delay definition of CompoundFlags until after all the definition
784    # of all constituent SimpleFlags
785    comp_code = code_formatter()
786
787    # file header
788    code('''
789/*
790 * DO NOT EDIT THIS FILE! Automatically generated by SCons.
791 */
792
793#include "base/debug.hh"
794
795namespace Debug {
796
797''')
798
799    for name, flag in sorted(source[0].read().iteritems()):
800        n, compound, desc = flag
801        assert n == name
802
803        if not compound:
804            code('SimpleFlag $name("$name", "$desc");')
805        else:
806            comp_code('CompoundFlag $name("$name", "$desc",')
807            comp_code.indent()
808            last = len(compound) - 1
809            for i,flag in enumerate(compound):
810                if i != last:
811                    comp_code('&$flag,')
812                else:
813                    comp_code('&$flag);')
814            comp_code.dedent()
815
816    code.append(comp_code)
817    code()
818    code('} // namespace Debug')
819
820    code.write(str(target[0]))
821
822def makeDebugFlagHH(target, source, env):
823    assert(len(target) == 1 and len(source) == 1)
824
825    val = eval(source[0].get_contents())
826    name, compound, desc = val
827
828    code = code_formatter()
829
830    # file header boilerplate
831    code('''\
832/*
833 * DO NOT EDIT THIS FILE! Automatically generated by SCons.
834 */
835
836#ifndef __DEBUG_${name}_HH__
837#define __DEBUG_${name}_HH__
838
839namespace Debug {
840''')
841
842    if compound:
843        code('class CompoundFlag;')
844    code('class SimpleFlag;')
845
846    if compound:
847        code('extern CompoundFlag $name;')
848        for flag in compound:
849            code('extern SimpleFlag $flag;')
850    else:
851        code('extern SimpleFlag $name;')
852
853    code('''
854}
855
856#endif // __DEBUG_${name}_HH__
857''')
858
859    code.write(str(target[0]))
860
861for name,flag in sorted(debug_flags.iteritems()):
862    n, compound, desc = flag
863    assert n == name
864
865    hh_file = 'debug/%s.hh' % name
866    env.Command(hh_file, Value(flag),
867                MakeAction(makeDebugFlagHH, Transform("TRACING", 0)))
868
869env.Command('debug/flags.cc', Value(debug_flags),
870            MakeAction(makeDebugFlagCC, Transform("TRACING", 0)))
871Source('debug/flags.cc')
872
873# version tags
874tags = \
875env.Command('sim/tags.cc', None,
876            MakeAction('util/cpt_upgrader.py --get-cc-file > $TARGET',
877                       Transform("VER TAGS")))
878env.AlwaysBuild(tags)
879
880# Embed python files.  All .py files that have been indicated by a
881# PySource() call in a SConscript need to be embedded into the M5
882# library.  To do that, we compile the file to byte code, marshal the
883# byte code, compress it, and then generate a c++ file that
884# inserts the result into an array.
885def embedPyFile(target, source, env):
886    def c_str(string):
887        if string is None:
888            return "0"
889        return '"%s"' % string
890
891    '''Action function to compile a .py into a code object, marshal
892    it, compress it, and stick it into an asm file so the code appears
893    as just bytes with a label in the data section'''
894
895    src = file(str(source[0]), 'r').read()
896
897    pysource = PySource.tnodes[source[0]]
898    compiled = compile(src, pysource.abspath, 'exec')
899    marshalled = marshal.dumps(compiled)
900    compressed = zlib.compress(marshalled)
901    data = compressed
902    sym = pysource.symname
903
904    code = code_formatter()
905    code('''\
906#include "sim/init.hh"
907
908namespace {
909
910const uint8_t data_${sym}[] = {
911''')
912    code.indent()
913    step = 16
914    for i in xrange(0, len(data), step):
915        x = array.array('B', data[i:i+step])
916        code(''.join('%d,' % d for d in x))
917    code.dedent()
918
919    code('''};
920
921EmbeddedPython embedded_${sym}(
922    ${{c_str(pysource.arcname)}},
923    ${{c_str(pysource.abspath)}},
924    ${{c_str(pysource.modpath)}},
925    data_${sym},
926    ${{len(data)}},
927    ${{len(marshalled)}});
928
929} // anonymous namespace
930''')
931    code.write(str(target[0]))
932
933for source in PySource.all:
934    env.Command(source.cpp, source.tnode,
935                MakeAction(embedPyFile, Transform("EMBED PY")))
936    Source(source.cpp, skip_no_python=True)
937
938########################################################################
939#
940# Define binaries.  Each different build type (debug, opt, etc.) gets
941# a slightly different build environment.
942#
943
944# List of constructed environments to pass back to SConstruct
945date_source = Source('base/date.cc', skip_lib=True)
946
947# Capture this directory for the closure makeEnv, otherwise when it is
948# called, it won't know what directory it should use.
949variant_dir = Dir('.').path
950def variant(*path):
951    return os.path.join(variant_dir, *path)
952def variantd(*path):
953    return variant(*path)+'/'
954
955# Function to create a new build environment as clone of current
956# environment 'env' with modified object suffix and optional stripped
957# binary.  Additional keyword arguments are appended to corresponding
958# build environment vars.
959def makeEnv(env, label, objsfx, strip = False, **kwargs):
960    # SCons doesn't know to append a library suffix when there is a '.' in the
961    # name.  Use '_' instead.
962    libname = variant('gem5_' + label)
963    exename = variant('gem5.' + label)
964    secondary_exename = variant('m5.' + label)
965
966    new_env = env.Clone(OBJSUFFIX=objsfx, SHOBJSUFFIX=objsfx + 's')
967    new_env.Label = label
968    new_env.Append(**kwargs)
969
970    if env['GCC']:
971        # The address sanitizer is available for gcc >= 4.8
972        if GetOption('with_asan'):
973            if GetOption('with_ubsan') and \
974                    compareVersions(env['GCC_VERSION'], '4.9') >= 0:
975                new_env.Append(CCFLAGS=['-fsanitize=address,undefined',
976                                        '-fno-omit-frame-pointer'])
977                new_env.Append(LINKFLAGS='-fsanitize=address,undefined')
978            else:
979                new_env.Append(CCFLAGS=['-fsanitize=address',
980                                        '-fno-omit-frame-pointer'])
981                new_env.Append(LINKFLAGS='-fsanitize=address')
982        # Only gcc >= 4.9 supports UBSan, so check both the version
983        # and the command-line option before adding the compiler and
984        # linker flags.
985        elif GetOption('with_ubsan') and \
986                compareVersions(env['GCC_VERSION'], '4.9') >= 0:
987            new_env.Append(CCFLAGS='-fsanitize=undefined')
988            new_env.Append(LINKFLAGS='-fsanitize=undefined')
989
990
991    if env['CLANG']:
992        # We require clang >= 3.1, so there is no need to check any
993        # versions here.
994        if GetOption('with_ubsan'):
995            if GetOption('with_asan'):
996                new_env.Append(CCFLAGS=['-fsanitize=address,undefined',
997                                        '-fno-omit-frame-pointer'])
998                new_env.Append(LINKFLAGS='-fsanitize=address,undefined')
999            else:
1000                new_env.Append(CCFLAGS='-fsanitize=undefined')
1001                new_env.Append(LINKFLAGS='-fsanitize=undefined')
1002
1003        elif GetOption('with_asan'):
1004            new_env.Append(CCFLAGS=['-fsanitize=address',
1005                                    '-fno-omit-frame-pointer'])
1006            new_env.Append(LINKFLAGS='-fsanitize=address')
1007
1008    werror_env = new_env.Clone()
1009    # Treat warnings as errors but white list some warnings that we
1010    # want to allow (e.g., deprecation warnings).
1011    werror_env.Append(CCFLAGS=['-Werror',
1012                               '-Wno-error=deprecated-declarations',
1013                               '-Wno-error=deprecated',
1014                               ])
1015
1016    def make_obj(source, static, extra_deps = None):
1017        '''This function adds the specified source to the correct
1018        build environment, and returns the corresponding SCons Object
1019        nodes'''
1020
1021        if source.Werror:
1022            env = werror_env
1023        else:
1024            env = new_env
1025
1026        if static:
1027            obj = env.StaticObject(source.tnode)
1028        else:
1029            obj = env.SharedObject(source.tnode)
1030
1031        if extra_deps:
1032            env.Depends(obj, extra_deps)
1033
1034        return obj
1035
1036    lib_guards = {'main': False, 'skip_lib': False}
1037
1038    # Without Python, leave out all Python content from the library
1039    # builds.  The option doesn't affect gem5 built as a program
1040    if GetOption('without_python'):
1041        lib_guards['skip_no_python'] = False
1042
1043    static_objs = []
1044    shared_objs = []
1045    for s in guarded_source_iterator(Source.source_groups[None], **lib_guards):
1046        static_objs.append(make_obj(s, True))
1047        shared_objs.append(make_obj(s, False))
1048
1049    partial_objs = []
1050    for group, all_srcs in Source.source_groups.iteritems():
1051        # If these are the ungrouped source files, skip them.
1052        if not group:
1053            continue
1054
1055        # Get a list of the source files compatible with the current guards.
1056        srcs = [ s for s in guarded_source_iterator(all_srcs, **lib_guards) ]
1057        # If there aren't any left, skip this group.
1058        if not srcs:
1059            continue
1060
1061        # Set up the static partially linked objects.
1062        source_objs = [ make_obj(s, True) for s in srcs ]
1063        file_name = new_env.subst("${OBJPREFIX}lib${OBJSUFFIX}.partial")
1064        target = File(joinpath(group, file_name))
1065        partial = env.PartialStatic(target=target, source=source_objs)
1066        static_objs.append(partial)
1067
1068        # Set up the shared partially linked objects.
1069        source_objs = [ make_obj(s, False) for s in srcs ]
1070        file_name = new_env.subst("${SHOBJPREFIX}lib${SHOBJSUFFIX}.partial")
1071        target = File(joinpath(group, file_name))
1072        partial = env.PartialShared(target=target, source=source_objs)
1073        shared_objs.append(partial)
1074
1075    static_date = make_obj(date_source, static=True, extra_deps=static_objs)
1076    static_objs.append(static_date)
1077
1078    shared_date = make_obj(date_source, static=False, extra_deps=shared_objs)
1079    shared_objs.append(shared_date)
1080
1081    # First make a library of everything but main() so other programs can
1082    # link against m5.
1083    static_lib = new_env.StaticLibrary(libname, static_objs)
1084    shared_lib = new_env.SharedLibrary(libname, shared_objs)
1085
1086    # Now link a stub with main() and the static library.
1087    main_objs = [ make_obj(s, True) for s in Source.get(main=True) ]
1088
1089    for test in UnitTest.all:
1090        flags = { test.target : True }
1091        test_sources = Source.get(**flags)
1092        test_objs = [ make_obj(s, static=True) for s in test_sources ]
1093        if test.main:
1094            test_objs += main_objs
1095        path = variant('unittest/%s.%s' % (test.target, label))
1096        new_env.Program(path, test_objs + static_objs)
1097
1098    progname = exename
1099    if strip:
1100        progname += '.unstripped'
1101
1102    targets = new_env.Program(progname, main_objs + static_objs)
1103
1104    if strip:
1105        if sys.platform == 'sunos5':
1106            cmd = 'cp $SOURCE $TARGET; strip $TARGET'
1107        else:
1108            cmd = 'strip $SOURCE -o $TARGET'
1109        targets = new_env.Command(exename, progname,
1110                    MakeAction(cmd, Transform("STRIP")))
1111
1112    new_env.Command(secondary_exename, exename,
1113            MakeAction('ln $SOURCE $TARGET', Transform("HARDLINK")))
1114
1115    new_env.M5Binary = targets[0]
1116    return new_env
1117
1118# Start out with the compiler flags common to all compilers,
1119# i.e. they all use -g for opt and -g -pg for prof
1120ccflags = {'debug' : [], 'opt' : ['-g'], 'fast' : [], 'prof' : ['-g', '-pg'],
1121           'perf' : ['-g']}
1122
1123# Start out with the linker flags common to all linkers, i.e. -pg for
1124# prof, and -lprofiler for perf. The -lprofile flag is surrounded by
1125# no-as-needed and as-needed as the binutils linker is too clever and
1126# simply doesn't link to the library otherwise.
1127ldflags = {'debug' : [], 'opt' : [], 'fast' : [], 'prof' : ['-pg'],
1128           'perf' : ['-Wl,--no-as-needed', '-lprofiler', '-Wl,--as-needed']}
1129
1130# For Link Time Optimization, the optimisation flags used to compile
1131# individual files are decoupled from those used at link time
1132# (i.e. you can compile with -O3 and perform LTO with -O0), so we need
1133# to also update the linker flags based on the target.
1134if env['GCC']:
1135    if sys.platform == 'sunos5':
1136        ccflags['debug'] += ['-gstabs+']
1137    else:
1138        ccflags['debug'] += ['-ggdb3']
1139    ldflags['debug'] += ['-O0']
1140    # opt, fast, prof and perf all share the same cc flags, also add
1141    # the optimization to the ldflags as LTO defers the optimization
1142    # to link time
1143    for target in ['opt', 'fast', 'prof', 'perf']:
1144        ccflags[target] += ['-O3']
1145        ldflags[target] += ['-O3']
1146
1147    ccflags['fast'] += env['LTO_CCFLAGS']
1148    ldflags['fast'] += env['LTO_LDFLAGS']
1149elif env['CLANG']:
1150    ccflags['debug'] += ['-g', '-O0']
1151    # opt, fast, prof and perf all share the same cc flags
1152    for target in ['opt', 'fast', 'prof', 'perf']:
1153        ccflags[target] += ['-O3']
1154else:
1155    print 'Unknown compiler, please fix compiler options'
1156    Exit(1)
1157
1158
1159# To speed things up, we only instantiate the build environments we
1160# need.  We try to identify the needed environment for each target; if
1161# we can't, we fall back on instantiating all the environments just to
1162# be safe.
1163target_types = ['debug', 'opt', 'fast', 'prof', 'perf']
1164obj2target = {'do': 'debug', 'o': 'opt', 'fo': 'fast', 'po': 'prof',
1165              'gpo' : 'perf'}
1166
1167def identifyTarget(t):
1168    ext = t.split('.')[-1]
1169    if ext in target_types:
1170        return ext
1171    if obj2target.has_key(ext):
1172        return obj2target[ext]
1173    match = re.search(r'/tests/([^/]+)/', t)
1174    if match and match.group(1) in target_types:
1175        return match.group(1)
1176    return 'all'
1177
1178needed_envs = [identifyTarget(target) for target in BUILD_TARGETS]
1179if 'all' in needed_envs:
1180    needed_envs += target_types
1181
1182def makeEnvirons(target, source, env):
1183    # cause any later Source() calls to be fatal, as a diagnostic.
1184    Source.done()
1185
1186    envList = []
1187
1188    # Debug binary
1189    if 'debug' in needed_envs:
1190        envList.append(
1191            makeEnv(env, 'debug', '.do',
1192                    CCFLAGS = Split(ccflags['debug']),
1193                    CPPDEFINES = ['DEBUG', 'TRACING_ON=1'],
1194                    LINKFLAGS = Split(ldflags['debug'])))
1195
1196    # Optimized binary
1197    if 'opt' in needed_envs:
1198        envList.append(
1199            makeEnv(env, 'opt', '.o',
1200                    CCFLAGS = Split(ccflags['opt']),
1201                    CPPDEFINES = ['TRACING_ON=1'],
1202                    LINKFLAGS = Split(ldflags['opt'])))
1203
1204    # "Fast" binary
1205    if 'fast' in needed_envs:
1206        envList.append(
1207            makeEnv(env, 'fast', '.fo', strip = True,
1208                    CCFLAGS = Split(ccflags['fast']),
1209                    CPPDEFINES = ['NDEBUG', 'TRACING_ON=0'],
1210                    LINKFLAGS = Split(ldflags['fast'])))
1211
1212    # Profiled binary using gprof
1213    if 'prof' in needed_envs:
1214        envList.append(
1215            makeEnv(env, 'prof', '.po',
1216                    CCFLAGS = Split(ccflags['prof']),
1217                    CPPDEFINES = ['NDEBUG', 'TRACING_ON=0'],
1218                    LINKFLAGS = Split(ldflags['prof'])))
1219
1220    # Profiled binary using google-pprof
1221    if 'perf' in needed_envs:
1222        envList.append(
1223            makeEnv(env, 'perf', '.gpo',
1224                    CCFLAGS = Split(ccflags['perf']),
1225                    CPPDEFINES = ['NDEBUG', 'TRACING_ON=0'],
1226                    LINKFLAGS = Split(ldflags['perf'])))
1227
1228    # Set up the regression tests for each build.
1229    for e in envList:
1230        SConscript(os.path.join(env.root.abspath, 'tests', 'SConscript'),
1231                   variant_dir = variantd('tests', e.Label),
1232                   exports = { 'env' : e }, duplicate = False)
1233
1234# The MakeEnvirons Builder defers the full dependency collection until
1235# after processing the ISA definition (due to dynamically generated
1236# source files).  Add this dependency to all targets so they will wait
1237# until the environments are completely set up.  Otherwise, a second
1238# process (e.g. -j2 or higher) will try to compile the requested target,
1239# not know how, and fail.
1240env.Append(BUILDERS = {'MakeEnvirons' :
1241                        Builder(action=MakeAction(makeEnvirons,
1242                                                  Transform("ENVIRONS", 1)))})
1243
1244isa_target = env['PHONY_BASE'] + '-deps'
1245environs   = env['PHONY_BASE'] + '-environs'
1246env.Depends('#all-deps',     isa_target)
1247env.Depends('#all-environs', environs)
1248env.ScanISA(isa_target, File('arch/%s/generated/inc.d' % env['TARGET_ISA']))
1249envSetup = env.MakeEnvirons(environs, isa_target)
1250
1251# make sure no -deps targets occur before all ISAs are complete
1252env.Depends(isa_target, '#all-isas')
1253# likewise for -environs targets and all the -deps targets
1254env.Depends(environs, '#all-deps')
1255