SConscript revision 12302
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 tags can be
63# specified for that file.
64
65class SourceList(list):
66    def with_tags_that(self, predicate):
67        '''Return a list of sources with tags that satisfy a predicate.'''
68        def match(source):
69            return predicate(source.tags)
70        return SourceList(filter(match, self))
71
72    def with_any_tags(self, *tags):
73        '''Return a list of sources with any of the supplied tags.'''
74        return self.with_tags_that(lambda stags: len(tags & stags) > 0)
75
76    def with_all_tags(self, *tags):
77        '''Return a list of sources with all of the supplied tags.'''
78        return self.with_tags_that(lambda stags: tags <= stags)
79
80    def with_tag(self, tag):
81        '''Return a list of sources with the supplied tag.'''
82        return self.with_tags_that(lambda stags: tag in stags)
83
84    def without_tags(self, *tags):
85        '''Return a list of sources without any of the supplied tags.'''
86        return self.with_tags_that(lambda stags: len(tags & stags) == 0)
87
88    def without_tag(self, tag):
89        '''Return a list of sources with the supplied tag.'''
90        return self.with_tags_that(lambda stags: tag not in stags)
91
92class SourceMeta(type):
93    '''Meta class for source files that keeps track of all files of a
94    particular type.'''
95    def __init__(cls, name, bases, dict):
96        super(SourceMeta, cls).__init__(name, bases, dict)
97        cls.all = SourceList()
98
99class SourceFile(object):
100    '''Base object that encapsulates the notion of a source file.
101    This includes, the source node, target node, various manipulations
102    of those.  A source file also specifies a set of tags which
103    describing arbitrary properties of the source file.'''
104    __metaclass__ = SourceMeta
105    def __init__(self, source, tags=None, add_tags=None):
106        if tags is None:
107            tags='gem5 lib'
108        if isinstance(tags, basestring):
109            tags = set([tags])
110        if isinstance(add_tags, basestring):
111            add_tags = set([add_tags])
112        if add_tags:
113            tags = tags | add_tags
114        self.tags = set(tags)
115
116        tnode = source
117        if not isinstance(source, SCons.Node.FS.File):
118            tnode = File(source)
119
120        self.tnode = tnode
121        self.snode = tnode.srcnode()
122
123        for base in type(self).__mro__:
124            if issubclass(base, SourceFile):
125                base.all.append(self)
126
127    @property
128    def filename(self):
129        return str(self.tnode)
130
131    @property
132    def dirname(self):
133        return dirname(self.filename)
134
135    @property
136    def basename(self):
137        return basename(self.filename)
138
139    @property
140    def extname(self):
141        index = self.basename.rfind('.')
142        if index <= 0:
143            # dot files aren't extensions
144            return self.basename, None
145
146        return self.basename[:index], self.basename[index+1:]
147
148    def __lt__(self, other): return self.filename < other.filename
149    def __le__(self, other): return self.filename <= other.filename
150    def __gt__(self, other): return self.filename > other.filename
151    def __ge__(self, other): return self.filename >= other.filename
152    def __eq__(self, other): return self.filename == other.filename
153    def __ne__(self, other): return self.filename != other.filename
154
155    @staticmethod
156    def done():
157        def disabled(cls, name, *ignored):
158            raise RuntimeError("Additional SourceFile '%s'" % name,\
159                  "declared, but targets deps are already fixed.")
160        SourceFile.__init__ = disabled
161
162
163class Source(SourceFile):
164    ungrouped_tag = 'No link group'
165    source_groups = set()
166
167    _current_group_tag = ungrouped_tag
168
169    @staticmethod
170    def link_group_tag(group):
171        return 'link group: %s' % group
172
173    @classmethod
174    def set_group(cls, group):
175        new_tag = Source.link_group_tag(group)
176        Source._current_group_tag = new_tag
177        Source.source_groups.add(group)
178
179    def _add_link_group_tag(self):
180        self.tags.add(Source._current_group_tag)
181
182    '''Add a c/c++ source file to the build'''
183    def __init__(self, source, tags=None, add_tags=None, Werror=True):
184        '''specify the source file, and any tags'''
185        super(Source, self).__init__(source, tags, add_tags)
186        self._add_link_group_tag()
187        self.Werror = Werror
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, tags=None, add_tags=None):
197        '''specify the python package, the source file, and any tags'''
198        super(PySource, self).__init__(source, tags, add_tags)
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, tags=None, add_tags=None):
239        '''Specify the source file and any tags (automatically in
240        the m5.objects package)'''
241        super(SimObject, self).__init__('m5.objects', source, tags, add_tags)
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, tags=None, add_tags=None):
251        '''Specify the source file, and any tags'''
252        super(ProtoBuf, self).__init__(source, tags, add_tags)
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        tagged with the name of the UnitTest target.'''
271
272        srcs = SourceList()
273        for src in sources:
274            if not isinstance(src, SourceFile):
275                src = Source(src, tags=str(target))
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 = source[0].get_text_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) == 2
634
635    name = source[0].get_text_contents()
636    use_python = source[1].read()
637    obj = all_enums[name]
638
639    code = code_formatter()
640    obj.cxx_def(code)
641    if use_python:
642        obj.pybind_def(code)
643    code.write(target[0].abspath)
644
645def createEnumDecls(target, source, env):
646    assert len(target) == 1 and len(source) == 1
647
648    name = source[0].get_text_contents()
649    obj = all_enums[name]
650
651    code = code_formatter()
652    obj.cxx_decl(code)
653    code.write(target[0].abspath)
654
655def createSimObjectPyBindWrapper(target, source, env):
656    name = source[0].get_text_contents()
657    obj = sim_objects[name]
658
659    code = code_formatter()
660    obj.pybind_decl(code)
661    code.write(target[0].abspath)
662
663# Generate all of the SimObject param C++ struct header files
664params_hh_files = []
665for name,simobj in sorted(sim_objects.iteritems()):
666    py_source = PySource.modules[simobj.__module__]
667    extra_deps = [ py_source.tnode ]
668
669    hh_file = File('params/%s.hh' % name)
670    params_hh_files.append(hh_file)
671    env.Command(hh_file, Value(name),
672                MakeAction(createSimObjectParamStruct, Transform("SO PARAM")))
673    env.Depends(hh_file, depends + extra_deps)
674
675# C++ parameter description files
676if GetOption('with_cxx_config'):
677    for name,simobj in sorted(sim_objects.iteritems()):
678        py_source = PySource.modules[simobj.__module__]
679        extra_deps = [ py_source.tnode ]
680
681        cxx_config_hh_file = File('cxx_config/%s.hh' % name)
682        cxx_config_cc_file = File('cxx_config/%s.cc' % name)
683        env.Command(cxx_config_hh_file, Value(name),
684                    MakeAction(createSimObjectCxxConfig(True),
685                    Transform("CXXCPRHH")))
686        env.Command(cxx_config_cc_file, Value(name),
687                    MakeAction(createSimObjectCxxConfig(False),
688                    Transform("CXXCPRCC")))
689        env.Depends(cxx_config_hh_file, depends + extra_deps +
690                    [File('params/%s.hh' % name), File('sim/cxx_config.hh')])
691        env.Depends(cxx_config_cc_file, depends + extra_deps +
692                    [cxx_config_hh_file])
693        Source(cxx_config_cc_file)
694
695    cxx_config_init_cc_file = File('cxx_config/init.cc')
696
697    def createCxxConfigInitCC(target, source, env):
698        assert len(target) == 1 and len(source) == 1
699
700        code = code_formatter()
701
702        for name,simobj in sorted(sim_objects.iteritems()):
703            if not hasattr(simobj, 'abstract') or not simobj.abstract:
704                code('#include "cxx_config/${name}.hh"')
705        code()
706        code('void cxxConfigInit()')
707        code('{')
708        code.indent()
709        for name,simobj in sorted(sim_objects.iteritems()):
710            not_abstract = not hasattr(simobj, 'abstract') or \
711                not simobj.abstract
712            if not_abstract and 'type' in simobj.__dict__:
713                code('cxx_config_directory["${name}"] = '
714                     '${name}CxxConfigParams::makeDirectoryEntry();')
715        code.dedent()
716        code('}')
717        code.write(target[0].abspath)
718
719    py_source = PySource.modules[simobj.__module__]
720    extra_deps = [ py_source.tnode ]
721    env.Command(cxx_config_init_cc_file, Value(name),
722        MakeAction(createCxxConfigInitCC, Transform("CXXCINIT")))
723    cxx_param_hh_files = ["cxx_config/%s.hh" % simobj
724        for name,simobj in sorted(sim_objects.iteritems())
725        if not hasattr(simobj, 'abstract') or not simobj.abstract]
726    Depends(cxx_config_init_cc_file, cxx_param_hh_files +
727            [File('sim/cxx_config.hh')])
728    Source(cxx_config_init_cc_file)
729
730# Generate all enum header files
731for name,enum in sorted(all_enums.iteritems()):
732    py_source = PySource.modules[enum.__module__]
733    extra_deps = [ py_source.tnode ]
734
735    cc_file = File('enums/%s.cc' % name)
736    env.Command(cc_file, [Value(name), Value(env['USE_PYTHON'])],
737                MakeAction(createEnumStrings, Transform("ENUM STR")))
738    env.Depends(cc_file, depends + extra_deps)
739    Source(cc_file)
740
741    hh_file = File('enums/%s.hh' % name)
742    env.Command(hh_file, Value(name),
743                MakeAction(createEnumDecls, Transform("ENUMDECL")))
744    env.Depends(hh_file, depends + extra_deps)
745
746# Generate SimObject Python bindings wrapper files
747if env['USE_PYTHON']:
748    for name,simobj in sorted(sim_objects.iteritems()):
749        py_source = PySource.modules[simobj.__module__]
750        extra_deps = [ py_source.tnode ]
751        cc_file = File('python/_m5/param_%s.cc' % name)
752        env.Command(cc_file, Value(name),
753                    MakeAction(createSimObjectPyBindWrapper,
754                               Transform("SO PyBind")))
755        env.Depends(cc_file, depends + extra_deps)
756        Source(cc_file)
757
758# Build all protocol buffers if we have got protoc and protobuf available
759if env['HAVE_PROTOBUF']:
760    for proto in ProtoBuf.all:
761        # Use both the source and header as the target, and the .proto
762        # file as the source. When executing the protoc compiler, also
763        # specify the proto_path to avoid having the generated files
764        # include the path.
765        env.Command([proto.cc_file, proto.hh_file], proto.tnode,
766                    MakeAction('$PROTOC --cpp_out ${TARGET.dir} '
767                               '--proto_path ${SOURCE.dir} $SOURCE',
768                               Transform("PROTOC")))
769
770        # Add the C++ source file
771        Source(proto.cc_file, tags=proto.tags)
772elif ProtoBuf.all:
773    print 'Got protobuf to build, but lacks support!'
774    Exit(1)
775
776#
777# Handle debug flags
778#
779def makeDebugFlagCC(target, source, env):
780    assert(len(target) == 1 and len(source) == 1)
781
782    code = code_formatter()
783
784    # delay definition of CompoundFlags until after all the definition
785    # of all constituent SimpleFlags
786    comp_code = code_formatter()
787
788    # file header
789    code('''
790/*
791 * DO NOT EDIT THIS FILE! Automatically generated by SCons.
792 */
793
794#include "base/debug.hh"
795
796namespace Debug {
797
798''')
799
800    for name, flag in sorted(source[0].read().iteritems()):
801        n, compound, desc = flag
802        assert n == name
803
804        if not compound:
805            code('SimpleFlag $name("$name", "$desc");')
806        else:
807            comp_code('CompoundFlag $name("$name", "$desc",')
808            comp_code.indent()
809            last = len(compound) - 1
810            for i,flag in enumerate(compound):
811                if i != last:
812                    comp_code('&$flag,')
813                else:
814                    comp_code('&$flag);')
815            comp_code.dedent()
816
817    code.append(comp_code)
818    code()
819    code('} // namespace Debug')
820
821    code.write(str(target[0]))
822
823def makeDebugFlagHH(target, source, env):
824    assert(len(target) == 1 and len(source) == 1)
825
826    val = eval(source[0].get_contents())
827    name, compound, desc = val
828
829    code = code_formatter()
830
831    # file header boilerplate
832    code('''\
833/*
834 * DO NOT EDIT THIS FILE! Automatically generated by SCons.
835 */
836
837#ifndef __DEBUG_${name}_HH__
838#define __DEBUG_${name}_HH__
839
840namespace Debug {
841''')
842
843    if compound:
844        code('class CompoundFlag;')
845    code('class SimpleFlag;')
846
847    if compound:
848        code('extern CompoundFlag $name;')
849        for flag in compound:
850            code('extern SimpleFlag $flag;')
851    else:
852        code('extern SimpleFlag $name;')
853
854    code('''
855}
856
857#endif // __DEBUG_${name}_HH__
858''')
859
860    code.write(str(target[0]))
861
862for name,flag in sorted(debug_flags.iteritems()):
863    n, compound, desc = flag
864    assert n == name
865
866    hh_file = 'debug/%s.hh' % name
867    env.Command(hh_file, Value(flag),
868                MakeAction(makeDebugFlagHH, Transform("TRACING", 0)))
869
870env.Command('debug/flags.cc', Value(debug_flags),
871            MakeAction(makeDebugFlagCC, Transform("TRACING", 0)))
872Source('debug/flags.cc')
873
874# version tags
875tags = \
876env.Command('sim/tags.cc', None,
877            MakeAction('util/cpt_upgrader.py --get-cc-file > $TARGET',
878                       Transform("VER TAGS")))
879env.AlwaysBuild(tags)
880
881# Embed python files.  All .py files that have been indicated by a
882# PySource() call in a SConscript need to be embedded into the M5
883# library.  To do that, we compile the file to byte code, marshal the
884# byte code, compress it, and then generate a c++ file that
885# inserts the result into an array.
886def embedPyFile(target, source, env):
887    def c_str(string):
888        if string is None:
889            return "0"
890        return '"%s"' % string
891
892    '''Action function to compile a .py into a code object, marshal
893    it, compress it, and stick it into an asm file so the code appears
894    as just bytes with a label in the data section'''
895
896    src = file(str(source[0]), 'r').read()
897
898    pysource = PySource.tnodes[source[0]]
899    compiled = compile(src, pysource.abspath, 'exec')
900    marshalled = marshal.dumps(compiled)
901    compressed = zlib.compress(marshalled)
902    data = compressed
903    sym = pysource.symname
904
905    code = code_formatter()
906    code('''\
907#include "sim/init.hh"
908
909namespace {
910
911const uint8_t data_${sym}[] = {
912''')
913    code.indent()
914    step = 16
915    for i in xrange(0, len(data), step):
916        x = array.array('B', data[i:i+step])
917        code(''.join('%d,' % d for d in x))
918    code.dedent()
919
920    code('''};
921
922EmbeddedPython embedded_${sym}(
923    ${{c_str(pysource.arcname)}},
924    ${{c_str(pysource.abspath)}},
925    ${{c_str(pysource.modpath)}},
926    data_${sym},
927    ${{len(data)}},
928    ${{len(marshalled)}});
929
930} // anonymous namespace
931''')
932    code.write(str(target[0]))
933
934for source in PySource.all:
935    env.Command(source.cpp, source.tnode,
936                MakeAction(embedPyFile, Transform("EMBED PY")))
937    Source(source.cpp, tags=source.tags, add_tags='python')
938
939########################################################################
940#
941# Define binaries.  Each different build type (debug, opt, etc.) gets
942# a slightly different build environment.
943#
944
945# List of constructed environments to pass back to SConstruct
946date_source = Source('base/date.cc', tags=[])
947
948# Function to create a new build environment as clone of current
949# environment 'env' with modified object suffix and optional stripped
950# binary.  Additional keyword arguments are appended to corresponding
951# build environment vars.
952def makeEnv(env, label, objsfx, strip=False, disable_partial=False, **kwargs):
953    # SCons doesn't know to append a library suffix when there is a '.' in the
954    # name.  Use '_' instead.
955    libname = 'gem5_' + label
956    exename = 'gem5.' + label
957    secondary_exename = 'm5.' + label
958
959    new_env = env.Clone(OBJSUFFIX=objsfx, SHOBJSUFFIX=objsfx + 's')
960    new_env.Label = label
961    new_env.Append(**kwargs)
962
963    if env['GCC']:
964        # The address sanitizer is available for gcc >= 4.8
965        if GetOption('with_asan'):
966            if GetOption('with_ubsan') and \
967                    compareVersions(env['GCC_VERSION'], '4.9') >= 0:
968                new_env.Append(CCFLAGS=['-fsanitize=address,undefined',
969                                        '-fno-omit-frame-pointer'])
970                new_env.Append(LINKFLAGS='-fsanitize=address,undefined')
971            else:
972                new_env.Append(CCFLAGS=['-fsanitize=address',
973                                        '-fno-omit-frame-pointer'])
974                new_env.Append(LINKFLAGS='-fsanitize=address')
975        # Only gcc >= 4.9 supports UBSan, so check both the version
976        # and the command-line option before adding the compiler and
977        # linker flags.
978        elif GetOption('with_ubsan') and \
979                compareVersions(env['GCC_VERSION'], '4.9') >= 0:
980            new_env.Append(CCFLAGS='-fsanitize=undefined')
981            new_env.Append(LINKFLAGS='-fsanitize=undefined')
982
983
984    if env['CLANG']:
985        # We require clang >= 3.1, so there is no need to check any
986        # versions here.
987        if GetOption('with_ubsan'):
988            if GetOption('with_asan'):
989                new_env.Append(CCFLAGS=['-fsanitize=address,undefined',
990                                        '-fno-omit-frame-pointer'])
991                new_env.Append(LINKFLAGS='-fsanitize=address,undefined')
992            else:
993                new_env.Append(CCFLAGS='-fsanitize=undefined')
994                new_env.Append(LINKFLAGS='-fsanitize=undefined')
995
996        elif GetOption('with_asan'):
997            new_env.Append(CCFLAGS=['-fsanitize=address',
998                                    '-fno-omit-frame-pointer'])
999            new_env.Append(LINKFLAGS='-fsanitize=address')
1000
1001    werror_env = new_env.Clone()
1002    # Treat warnings as errors but white list some warnings that we
1003    # want to allow (e.g., deprecation warnings).
1004    werror_env.Append(CCFLAGS=['-Werror',
1005                               '-Wno-error=deprecated-declarations',
1006                               '-Wno-error=deprecated',
1007                               ])
1008
1009    def make_obj(source, static, extra_deps = None):
1010        '''This function adds the specified source to the correct
1011        build environment, and returns the corresponding SCons Object
1012        nodes'''
1013
1014        if source.Werror:
1015            env = werror_env
1016        else:
1017            env = new_env
1018
1019        if static:
1020            obj = env.StaticObject(source.tnode)
1021        else:
1022            obj = env.SharedObject(source.tnode)
1023
1024        if extra_deps:
1025            env.Depends(obj, extra_deps)
1026
1027        return obj
1028
1029    lib_sources = Source.all.with_tag('gem5 lib')
1030
1031    # Without Python, leave out all Python content from the library
1032    # builds.  The option doesn't affect gem5 built as a program
1033    if GetOption('without_python'):
1034        lib_sources = lib_sources.without_tag('python')
1035
1036    static_objs = []
1037    shared_objs = []
1038
1039    for s in lib_sources.with_tag(Source.ungrouped_tag):
1040        static_objs.append(make_obj(s, True))
1041        shared_objs.append(make_obj(s, False))
1042
1043    partial_objs = []
1044
1045    for group in Source.source_groups:
1046        srcs = lib_sources.with_tag(Source.link_group_tag(group))
1047        if not srcs:
1048            continue
1049
1050        # If partial linking is disabled, add these sources to the build
1051        # directly, and short circuit this loop.
1052        if disable_partial:
1053            for s in srcs:
1054                static_objs.append(make_obj(s, True))
1055                shared_objs.append(make_obj(s, False))
1056            continue
1057
1058        # Set up the static partially linked objects.
1059        source_objs = [ make_obj(s, True) for s in srcs ]
1060        file_name = new_env.subst("${OBJPREFIX}lib${OBJSUFFIX}.partial")
1061        target = File(joinpath(group, file_name))
1062        partial = env.PartialStatic(target=target, source=source_objs)
1063        static_objs.append(partial)
1064
1065        # Set up the shared partially linked objects.
1066        source_objs = [ make_obj(s, False) for s in srcs ]
1067        file_name = new_env.subst("${SHOBJPREFIX}lib${SHOBJSUFFIX}.partial")
1068        target = File(joinpath(group, file_name))
1069        partial = env.PartialShared(target=target, source=source_objs)
1070        shared_objs.append(partial)
1071
1072    static_date = make_obj(date_source, static=True, extra_deps=static_objs)
1073    static_objs.append(static_date)
1074
1075    shared_date = make_obj(date_source, static=False, extra_deps=shared_objs)
1076    shared_objs.append(shared_date)
1077
1078    # First make a library of everything but main() so other programs can
1079    # link against m5.
1080    static_lib = new_env.StaticLibrary(libname, static_objs)
1081    shared_lib = new_env.SharedLibrary(libname, shared_objs)
1082
1083    # Now link a stub with main() and the static library.
1084    main_objs = [ make_obj(s, True) for s in Source.all.with_tag('main') ]
1085
1086    for test in UnitTest.all:
1087        test_sources = Source.all.with_tag(str(test.target))
1088        test_objs = [ make_obj(s, static=True) for s in test_sources ]
1089        if test.main:
1090            test_objs += main_objs
1091        path = 'unittest/%s.%s' % (test.target, label)
1092        new_env.Program(path, test_objs + static_objs)
1093
1094    progname = exename
1095    if strip:
1096        progname += '.unstripped'
1097
1098    targets = new_env.Program(progname, main_objs + static_objs)
1099
1100    if strip:
1101        if sys.platform == 'sunos5':
1102            cmd = 'cp $SOURCE $TARGET; strip $TARGET'
1103        else:
1104            cmd = 'strip $SOURCE -o $TARGET'
1105        targets = new_env.Command(exename, progname,
1106                    MakeAction(cmd, Transform("STRIP")))
1107
1108    new_env.Command(secondary_exename, exename,
1109            MakeAction('ln $SOURCE $TARGET', Transform("HARDLINK")))
1110
1111    new_env.M5Binary = targets[0]
1112
1113    # Set up regression tests.
1114    SConscript(os.path.join(env.root.abspath, 'tests', 'SConscript'),
1115               variant_dir=Dir('tests').Dir(new_env.Label),
1116               exports={ 'env' : new_env }, duplicate=False)
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
1182# Debug binary
1183if 'debug' in needed_envs:
1184    makeEnv(env, 'debug', '.do',
1185            CCFLAGS = Split(ccflags['debug']),
1186            CPPDEFINES = ['DEBUG', 'TRACING_ON=1'],
1187            LINKFLAGS = Split(ldflags['debug']))
1188
1189# Optimized binary
1190if 'opt' in needed_envs:
1191    makeEnv(env, 'opt', '.o',
1192            CCFLAGS = Split(ccflags['opt']),
1193            CPPDEFINES = ['TRACING_ON=1'],
1194            LINKFLAGS = Split(ldflags['opt']))
1195
1196# "Fast" binary
1197if 'fast' in needed_envs:
1198    disable_partial = \
1199            env.get('BROKEN_INCREMENTAL_LTO', False) and \
1200            GetOption('force_lto')
1201    makeEnv(env, 'fast', '.fo', strip = True,
1202            CCFLAGS = Split(ccflags['fast']),
1203            CPPDEFINES = ['NDEBUG', 'TRACING_ON=0'],
1204            LINKFLAGS = Split(ldflags['fast']),
1205            disable_partial=disable_partial)
1206
1207# Profiled binary using gprof
1208if 'prof' in needed_envs:
1209    makeEnv(env, 'prof', '.po',
1210            CCFLAGS = Split(ccflags['prof']),
1211            CPPDEFINES = ['NDEBUG', 'TRACING_ON=0'],
1212            LINKFLAGS = Split(ldflags['prof']))
1213
1214# Profiled binary using google-pprof
1215if 'perf' in needed_envs:
1216    makeEnv(env, 'perf', '.gpo',
1217            CCFLAGS = Split(ccflags['perf']),
1218            CPPDEFINES = ['NDEBUG', 'TRACING_ON=0'],
1219            LINKFLAGS = Split(ldflags['perf']))
1220