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