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