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