scons: allow embedding arbitrary blobs into the gem5 executable

The initial motivation for this is to embed the GDB XML target
description files into the executable.

Change-Id: I721e8dd37119d8e6eb376d7e9050b1094282bacc
Reviewed-on: https://gem5-review.googlesource.com/c/15136
Reviewed-by: Andreas Sandberg <andreas.sandberg@arm.com>
Reviewed-by: Gabe Black <gabeblack@google.com>
Maintainer: Gabe Black <gabeblack@google.com>
diff --git a/src/SConscript b/src/SConscript
index dc284b1..5f8a2d2 100755
--- a/src/SConscript
+++ b/src/SConscript
@@ -1,5 +1,16 @@
 # -*- mode:python -*-
 
+# Copyright (c) 2018 ARM Limited
+#
+# The license below extends only to copyright in the software and shall
+# not be construed as granting a license to any other intellectual
+# property including but not limited to intellectual property relating
+# to a hardware implementation of the functionality of the software
+# licensed hereunder.  You may use the software subject to the license
+# terms below provided that you ensure that this notice is replicated
+# unmodified and in its entirety in all distributions of the software,
+# modified or unmodified, in source code or in binary form.
+#
 # Copyright (c) 2004-2005 The Regents of The University of Michigan
 # All rights reserved.
 #
@@ -212,6 +223,76 @@
     def __eq__(self, other): return self.filename == other.filename
     def __ne__(self, other): return self.filename != other.filename
 
+def blobToCpp(data, symbol, cpp_code, hpp_code=None, namespace=None):
+    '''
+    Convert bytes data into C++ .cpp and .hh uint8_t byte array
+    code containing that binary data.
+
+    :param data: binary data to be converted to C++
+    :param symbol: name of the symbol
+    :param cpp_code: append the generated cpp_code to this object
+    :param hpp_code: append the generated hpp_code to this object
+                     If None, ignore it. Otherwise, also include it
+                     in the .cpp file.
+    :param namespace: namespace to put the symbol into. If None,
+                      don't put the symbols into any namespace.
+    '''
+    symbol_len_declaration = 'const std::size_t {}_len'.format(symbol)
+    symbol_declaration = 'const std::uint8_t {}[]'.format(symbol)
+    if hpp_code is not None:
+        cpp_code('''\
+#include "blobs/{}.hh"
+'''.format(symbol))
+        hpp_code('''\
+#include <cstddef>
+#include <cstdint>
+''')
+        if namespace is not None:
+            hpp_code('namespace {} {{'.format(namespace))
+        hpp_code('extern ' + symbol_len_declaration + ';')
+        hpp_code('extern ' + symbol_declaration + ';')
+        if namespace is not None:
+            hpp_code('}')
+    if namespace is not None:
+        cpp_code('namespace {} {{'.format(namespace))
+    cpp_code(symbol_len_declaration + ' = {};'.format(len(data)))
+    cpp_code(symbol_declaration + ' = {')
+    cpp_code.indent()
+    step = 16
+    for i in xrange(0, len(data), step):
+        x = array.array('B', data[i:i+step])
+        cpp_code(''.join('%d,' % d for d in x))
+    cpp_code.dedent()
+    cpp_code('};')
+    if namespace is not None:
+        cpp_code('}')
+
+def Blob(blob_path, symbol):
+    '''
+    Embed an arbitrary blob into the gem5 executable,
+    and make it accessible to C++ as a byte array.
+    '''
+    blob_path = os.path.abspath(blob_path)
+    blob_out_dir = os.path.join(env['BUILDDIR'], 'blobs')
+    path_noext = joinpath(blob_out_dir, symbol)
+    cpp_path = path_noext + '.cc'
+    hpp_path = path_noext + '.hh'
+    def embedBlob(target, source, env):
+        data = file(str(source[0]), 'r').read()
+        cpp_code = code_formatter()
+        hpp_code = code_formatter()
+        blobToCpp(data, symbol, cpp_code, hpp_code, namespace='Blobs')
+        cpp_path = str(target[0])
+        hpp_path = str(target[1])
+        cpp_dir = os.path.split(cpp_path)[0]
+        if not os.path.exists(cpp_dir):
+            os.makedirs(cpp_dir)
+        cpp_code.write(cpp_path)
+        hpp_code.write(hpp_path)
+    env.Command([cpp_path, hpp_path], blob_path,
+                MakeAction(embedBlob, Transform("EMBED BLOB")))
+    Source(cpp_path)
+
 class Source(SourceFile):
     ungrouped_tag = 'No link group'
     source_groups = set()
@@ -440,6 +521,7 @@
 
 
 # Children should have access
+Export('Blob')
 Export('Source')
 Export('PySource')
 Export('SimObject')
@@ -1069,16 +1151,10 @@
 
 namespace {
 
-const uint8_t data_${sym}[] = {
 ''')
-    code.indent()
-    step = 16
-    for i in xrange(0, len(data), step):
-        x = array.array('B', data[i:i+step])
-        code(''.join('%d,' % d for d in x))
-    code.dedent()
+    blobToCpp(data, 'data_' + sym, code)
+    code('''\
 
-    code('''};
 
 EmbeddedPython embedded_${sym}(
     ${{c_str(pysource.arcname)}},