| # Copyright (c) 2013, 2015-2017 ARM Limited |
| # All rights reserved. |
| # |
| # 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) 2011 Advanced Micro Devices, Inc. |
| # Copyright (c) 2009 The Hewlett-Packard Development Company |
| # Copyright (c) 2004-2005 The Regents of The University of Michigan |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer; |
| # redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution; |
| # neither the name of the copyright holders nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| import os |
| import pickle |
| import sys |
| import tempfile |
| import textwrap |
| |
| from gem5_scons.util import get_termcap |
| from gem5_scons.configure import Configure |
| from gem5_scons.defaults import EnvDefaults |
| import SCons.Node.Python |
| import SCons.Script |
| |
| termcap = get_termcap() |
| |
| def strip_build_path(path, env): |
| path = str(path) |
| build_base = 'build/' |
| variant_base = env['BUILDROOT'] + os.path.sep |
| if path.startswith(variant_base): |
| path = path[len(variant_base):] |
| elif path.startswith(build_base): |
| path = path[len(build_base):] |
| return path |
| |
| def TempFileSpawn(scons_env): |
| old_pspawn = scons_env['PSPAWN'] |
| old_spawn = scons_env['SPAWN'] |
| |
| def wrapper(old, sh, esc, cmd, sh_args, *py_args): |
| with tempfile.NamedTemporaryFile() as temp: |
| temp.write(' '.join(sh_args).encode()) |
| temp.flush() |
| sh_args = [sh, esc(temp.name)] |
| return old(sh, esc, sh, sh_args, *py_args) |
| |
| def new_pspawn(sh, esc, cmd, args, sh_env, stdout, stderr): |
| return wrapper(old_pspawn, sh, esc, cmd, args, sh_env, stdout, stderr) |
| def new_spawn(sh, esc, cmd, args, sh_env): |
| return wrapper(old_spawn, sh, esc, cmd, args, sh_env) |
| |
| scons_env['PSPAWN'] = new_pspawn |
| scons_env['SPAWN'] = new_spawn |
| |
| # Generate a string of the form: |
| # common/path/prefix/src1, src2 -> tgt1, tgt2 |
| # to print while building. |
| class Transform(object): |
| # all specific color settings should be here and nowhere else |
| tool_color = termcap.Normal |
| pfx_color = termcap.Yellow |
| srcs_color = termcap.Yellow + termcap.Bold |
| arrow_color = termcap.Blue + termcap.Bold |
| tgts_color = termcap.Yellow + termcap.Bold |
| |
| def __init__(self, tool, max_sources=99): |
| self.format = self.tool_color + (" [%8s] " % tool) \ |
| + self.pfx_color + "%s" \ |
| + self.srcs_color + "%s" \ |
| + self.arrow_color + " -> " \ |
| + self.tgts_color + "%s" \ |
| + termcap.Normal |
| self.max_sources = max_sources |
| |
| def __call__(self, target, source, env, for_signature=None): |
| # truncate source list according to max_sources param |
| source = source[0:self.max_sources] |
| def strip(f): |
| return strip_build_path(str(f), env) |
| if len(source) > 0: |
| srcs = list(map(strip, source)) |
| else: |
| srcs = [''] |
| tgts = list(map(strip, target)) |
| # surprisingly, os.path.commonprefix is a dumb char-by-char string |
| # operation that has nothing to do with paths. |
| com_pfx = os.path.commonprefix(srcs + tgts) |
| com_pfx_len = len(com_pfx) |
| if com_pfx: |
| # do some cleanup and sanity checking on common prefix |
| if com_pfx[-1] == ".": |
| # prefix matches all but file extension: ok |
| # back up one to change 'foo.cc -> o' to 'foo.cc -> .o' |
| com_pfx = com_pfx[0:-1] |
| elif com_pfx[-1] == "/": |
| # common prefix is directory path: OK |
| pass |
| else: |
| src0_len = len(srcs[0]) |
| tgt0_len = len(tgts[0]) |
| if src0_len == com_pfx_len: |
| # source is a substring of target, OK |
| pass |
| elif tgt0_len == com_pfx_len: |
| # target is a substring of source, need to back up to |
| # avoid empty string on RHS of arrow |
| sep_idx = com_pfx.rfind(".") |
| if sep_idx != -1: |
| com_pfx = com_pfx[0:sep_idx] |
| else: |
| com_pfx = '' |
| elif src0_len > com_pfx_len and srcs[0][com_pfx_len] == ".": |
| # still splitting at file extension: ok |
| pass |
| else: |
| # probably a fluke; ignore it |
| com_pfx = '' |
| # recalculate length in case com_pfx was modified |
| com_pfx_len = len(com_pfx) |
| def fmt(files): |
| f = list(map(lambda s: s[com_pfx_len:], files)) |
| return ', '.join(f) |
| return self.format % (com_pfx, fmt(srcs), fmt(tgts)) |
| |
| # The width warning and error messages should be wrapped at. |
| text_width = None |
| |
| # If stdout is not attached to a terminal, default to 80 columns. |
| if not sys.stdout.isatty(): |
| text_width = 80 |
| |
| # This should work in python 3.3 and above. |
| if text_width is None: |
| try: |
| import shutil |
| text_width = shutil.get_terminal_size().columns |
| except: |
| pass |
| |
| # This should work if the curses python module is installed. |
| if text_width is None: |
| try: |
| import curses |
| try: |
| _, text_width = curses.initscr().getmaxyx() |
| finally: |
| curses.endwin() |
| except: |
| pass |
| |
| # If all else fails, default to 80 columns. |
| if text_width is None: |
| text_width = 80 |
| |
| def print_message(prefix, color, message, **kwargs): |
| prefix_len = len(prefix) |
| if text_width > prefix_len: |
| wrap_width = text_width - prefix_len |
| padding = ' ' * prefix_len |
| |
| # First split on newlines. |
| lines = message.split('\n') |
| # Then wrap each line to the required width. |
| wrapped_lines = [] |
| for line in lines: |
| wrapped_lines.extend(textwrap.wrap(line, wrap_width)) |
| # Finally add the prefix and padding on extra lines, and glue it all |
| # back together. |
| message = prefix + ('\n' + padding).join(wrapped_lines) |
| else: |
| # We have very small terminal, indent formatting doesn't help. |
| message = prefix + message |
| # Add in terminal escape sequences. |
| message = color + termcap.Bold + message + termcap.Normal |
| # Actually print the message. |
| print(message, **kwargs) |
| return message |
| |
| all_warnings = [] |
| def summarize_warnings(): |
| if not all_warnings: |
| return |
| print(termcap.Yellow + termcap.Bold + |
| '*** Summary of Warnings ***' + |
| termcap.Normal) |
| list(map(print, all_warnings)) |
| |
| def warning(*args, **kwargs): |
| message = ' '.join(args) |
| printed = print_message('Warning: ', termcap.Yellow, message, **kwargs) |
| all_warnings.append(printed) |
| |
| def error(*args, **kwargs): |
| message = ' '.join(args) |
| print_message('Error: ', termcap.Red, message, **kwargs) |
| SCons.Script.Exit(1) |
| |
| def parse_build_path(target): |
| path_dirs = target.split('/') |
| |
| # Pop off the target file. |
| path_dirs.pop() |
| |
| # Search backwards for the "build" directory. Whatever was just before it |
| # was the name of the variant. |
| variant_dir = path_dirs.pop() |
| while path_dirs and path_dirs[-1] != 'build': |
| variant_dir = path_dirs.pop() |
| if not path_dirs: |
| error("No non-leaf 'build' dir found on target path.", t) |
| |
| return os.path.join('/', *path_dirs), variant_dir |
| |
| # The MakeAction wrapper, and a SCons tool to set up the *COMSTR variables. |
| if SCons.Script.GetOption('verbose'): |
| def MakeAction(action, string, *args, **kwargs): |
| return SCons.Script.Action(action, *args, **kwargs) |
| |
| def MakeActionTool(env): |
| pass |
| else: |
| MakeAction = SCons.Script.Action |
| |
| def MakeActionTool(env): |
| env['CCCOMSTR'] = Transform("CC") |
| env['CXXCOMSTR'] = Transform("CXX") |
| env['ASCOMSTR'] = Transform("AS") |
| env['ARCOMSTR'] = Transform("AR", 0) |
| env['LINKCOMSTR'] = Transform("LINK", 0) |
| env['SHLINKCOMSTR'] = Transform("SHLINK", 0) |
| env['RANLIBCOMSTR'] = Transform("RANLIB", 0) |
| env['M4COMSTR'] = Transform("M4") |
| env['SHCCCOMSTR'] = Transform("SHCC") |
| env['SHCXXCOMSTR'] = Transform("SHCXX") |
| |
| def ToValue(obj): |
| return SCons.Node.Python.Value(pickle.dumps(obj)) |
| |
| def FromValue(node): |
| return pickle.loads(node.read()) |
| |
| __all__ = ['Configure', 'EnvDefaults', 'Transform', 'warning', 'error', |
| 'MakeAction', 'MakeActionTool', 'ToValue', 'FromValue'] |