From: Gabe Black Date: Thu, 7 Jun 2018 04:01:29 +0000 (-0700) Subject: scons: Generalize building binaries. X-Git-Tag: v19.0.0.0~2022 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=6df90da9fb701eea8381a35423d0c9e7bc940850;p=gem5.git scons: Generalize building binaries. Building gem5 binaries or regression test binaries needs to be done from within the make_env function which builds an environment for each flavor of build (opt, fast, debug, etc.). That makes it impossible to add new types of binaries without modifying the central SConscript. This change refactors how binaries are set up so that the class that represents them handles the details of how the binary should be built. Also, a metaclass and some lists track types of binaries and individual instances of binaries so that they can be iterated over automatically in make_env. Each new executable class can define a declare_all class function which calls declare() on individual instances. declare_all is a place to do any processing that only has to happen once (for instance specializing the environment) for a particular family of executables. Change-Id: I8a6ee9438280cd67e6c0b92ca28738a53cb16950 Reviewed-on: https://gem5-review.googlesource.com/10915 Reviewed-by: Andreas Sandberg Reviewed-by: Jason Lowe-Power Maintainer: Andreas Sandberg --- diff --git a/src/SConscript b/src/SConscript index b76e075ee..361479d57 100755 --- a/src/SConscript +++ b/src/SConscript @@ -311,13 +311,31 @@ class ProtoBuf(SourceFile): self.cc_file = File(modname + '.pb.cc') self.hh_file = File(modname + '.pb.h') -class UnitTest(object): - '''Create a UnitTest''' +exectuable_classes = [] +class ExecutableMeta(type): + '''Meta class for Executables.''' all = [] - def __init__(self, target, *srcs_and_filts, **kwargs): + + def __init__(cls, name, bases, d): + if not d.pop('abstract', False): + ExecutableMeta.all.append(cls) + super(ExecutableMeta, cls).__init__(name, bases, d) + + cls.all = [] + +class Executable(object): + '''Base class for creating an executable from sources.''' + __metaclass__ = ExecutableMeta + + abstract = True + + def __init__(self, target, *srcs_and_filts): '''Specify the target name and any sources. Sources that are not SourceFiles are evalued with Source().''' + super(Executable, self).__init__() + self.all.append(self) + self.target = target isFilter = lambda arg: isinstance(arg, SourceFilter) self.filters = filter(isFilter, srcs_and_filts) @@ -330,23 +348,103 @@ class UnitTest(object): srcs.append(src) self.sources = srcs - self.target = target - self.main = kwargs.get('main', False) - self.all.append(self) self.dir = Dir('.') -class GTest(UnitTest): + def path(self, env): + return self.dir.File(self.target + '.' + env['EXE_SUFFIX']) + + def srcs_to_objs(self, env, sources): + return list([ s.static(env) for s in sources ]) + + @classmethod + def declare_all(cls, env): + return list([ instance.declare(env) for instance in cls.all ]) + + def declare(self, env, objs=None): + if objs is None: + objs = self.srcs_to_objs(env, self.sources) + + if env['STRIP_EXES']: + stripped = self.path(env) + unstripped = env.File(str(stripped) + '.unstripped') + if sys.platform == 'sunos5': + cmd = 'cp $SOURCE $TARGET; strip $TARGET' + else: + cmd = 'strip $SOURCE -o $TARGET' + env.Program(unstripped, objs) + return env.Command(stripped, unstripped, + MakeAction(cmd, Transform("STRIP"))) + else: + return env.Program(self.path(env), objs) + +class UnitTest(Executable): + '''Create a UnitTest''' + def __init__(self, target, *srcs_and_filts, **kwargs): + super(UnitTest, self).__init__(target, *srcs_and_filts) + + self.main = kwargs.get('main', False) + + def declare(self, env): + sources = list(self.sources) + for f in self.filters: + sources = Source.all.apply_filter(f) + objs = self.srcs_to_objs(env, sources) + env['STATIC_OBJS'] + if self.main: + objs += env['MAIN_OBJS'] + return super(UnitTest, self).declare(env, objs) + +class GTest(Executable): '''Create a unit test based on the google test framework.''' all = [] - def __init__(self, *args, **kwargs): - super(GTest, self).__init__(*args, **kwargs) + def __init__(self, *srcs_and_filts, **kwargs): + super(GTest, self).__init__(*srcs_and_filts) + self.skip_lib = kwargs.pop('skip_lib', False) + @classmethod + def declare_all(cls, env): + env = env.Clone() + env.Append(LIBS=env['GTEST_LIBS']) + env.Append(CPPFLAGS=env['GTEST_CPPFLAGS']) + env['GTEST_LIB_SOURCES'] = Source.all.with_tag('gtest lib') + env['GTEST_OUT_DIR'] = \ + Dir(env['BUILDDIR']).Dir('unittests.' + env['EXE_SUFFIX']) + return super(GTest, cls).declare_all(env) + + def declare(self, env): + sources = list(self.sources) + if not self.skip_lib: + sources += env['GTEST_LIB_SOURCES'] + for f in self.filters: + sources += Source.all.apply_filter(f) + objs = self.srcs_to_objs(env, sources) + + binary = super(GTest, self).declare(env, objs) + + out_dir = env['GTEST_OUT_DIR'] + xml_file = out_dir.Dir(str(self.dir)).File(self.target + '.xml') + AlwaysBuild(env.Command(xml_file, binary, + "${SOURCES[0]} --gtest_output=xml:${TARGETS[0]}")) + + return binary + +class Gem5(Executable): + '''Create a gem5 executable.''' + + def __init__(self, target): + super(Gem5, self).__init__(target) + + def declare(self, env): + objs = env['MAIN_OBJS'] + env['STATIC_OBJS'] + return super(Gem5, self).declare(env, objs) + + # Children should have access Export('Source') Export('PySource') Export('SimObject') Export('ProtoBuf') +Export('Executable') Export('UnitTest') Export('GTest') @@ -1008,6 +1106,8 @@ for source in PySource.all: # List of constructed environments to pass back to SConstruct date_source = Source('base/date.cc', tags=[]) +gem5_binary = Gem5('gem5') + # Function to create a new build environment as clone of current # environment 'env' with modified object suffix and optional stripped # binary. Additional keyword arguments are appended to corresponding @@ -1016,7 +1116,6 @@ def makeEnv(env, label, objsfx, strip=False, disable_partial=False, **kwargs): # SCons doesn't know to append a library suffix when there is a '.' in the # name. Use '_' instead. libname = 'gem5_' + label - exename = 'gem5.' + label secondary_exename = 'm5.' + label new_env = env.Clone(OBJSUFFIX=objsfx, SHOBJSUFFIX=objsfx + 's') @@ -1072,61 +1171,33 @@ def makeEnv(env, label, objsfx, strip=False, disable_partial=False, **kwargs): new_env.Depends(shared_date, shared_objs) shared_objs.extend(shared_date) + main_objs = [ s.static(new_env) for s in Source.all.with_tag('main') ] + # First make a library of everything but main() so other programs can # link against m5. static_lib = new_env.StaticLibrary(libname, static_objs) shared_lib = new_env.SharedLibrary(libname, shared_objs) - # Now link a stub with main() and the static library. - main_objs = [ s.static(new_env) for s in Source.all.with_tag('main') ] + # Keep track of the object files generated so far so Executables can + # include them. + new_env['STATIC_OBJS'] = static_objs + new_env['SHARED_OBJS'] = shared_objs + new_env['MAIN_OBJS'] = main_objs - for test in UnitTest.all: - test_sources = list(test.sources) - for f in test.filters: - test_sources += Source.all.apply_filter(f) - test_objs = [ s.static(new_env) for s in test_sources ] - if test.main: - test_objs += main_objs - new_env.Program(test.dir.File('%s.%s' % (test.target, label)), - test_objs + static_objs) - - gtest_env = new_env.Clone() - gtest_env.Append(LIBS=gtest_env['GTEST_LIBS']) - gtest_env.Append(CPPFLAGS=gtest_env['GTEST_CPPFLAGS']) - gtestlib_sources = Source.all.with_tag('gtest lib') - gtest_out_dir = Dir(new_env['BUILDDIR']).Dir('unittests.%s' % label) - for test in GTest.all: - test_sources = list(test.sources) - if not test.skip_lib: - test_sources += gtestlib_sources - for f in test.filters: - test_sources += Source.all.apply_filter(f) - test_objs = [ s.static(gtest_env) for s in test_sources ] - test_binary = gtest_env.Program( - test.dir.File('%s.%s' % (test.target, label)), test_objs) - - AlwaysBuild(gtest_env.Command( - gtest_out_dir.File("%s/%s.xml" % (test.dir, test.target)), - test_binary, "${SOURCES[0]} --gtest_output=xml:${TARGETS[0]}")) - - progname = exename - if strip: - progname += '.unstripped' - - targets = new_env.Program(progname, main_objs + static_objs) - - if strip: - if sys.platform == 'sunos5': - cmd = 'cp $SOURCE $TARGET; strip $TARGET' - else: - cmd = 'strip $SOURCE -o $TARGET' - targets = new_env.Command(exename, progname, - MakeAction(cmd, Transform("STRIP"))) + new_env['STATIC_LIB'] = static_lib + new_env['SHARED_LIB'] = shared_lib - new_env.Command(secondary_exename, exename, - MakeAction('ln $SOURCE $TARGET', Transform("HARDLINK"))) + # Record some settings for building Executables. + new_env['EXE_SUFFIX'] = label + new_env['STRIP_EXES'] = strip + + for cls in ExecutableMeta.all: + cls.declare_all(new_env) - new_env.M5Binary = targets[0] + new_env.M5Binary = File(gem5_binary.path(new_env)) + + new_env.Command(secondary_exename, new_env.M5Binary, + MakeAction('ln $SOURCE $TARGET', Transform("HARDLINK"))) # Set up regression tests. SConscript(os.path.join(env.root.abspath, 'tests', 'SConscript'),