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