scons: only wrap message with positive value
[gem5.git] / site_scons / gem5_scons / __init__.py
1 # Copyright (c) 2013, 2015-2017 ARM Limited
2 # All rights reserved.
3 #
4 # The license below extends only to copyright in the software and shall
5 # not be construed as granting a license to any other intellectual
6 # property including but not limited to intellectual property relating
7 # to a hardware implementation of the functionality of the software
8 # licensed hereunder. You may use the software subject to the license
9 # terms below provided that you ensure that this notice is replicated
10 # unmodified and in its entirety in all distributions of the software,
11 # modified or unmodified, in source code or in binary form.
12 #
13 # Copyright (c) 2011 Advanced Micro Devices, Inc.
14 # Copyright (c) 2009 The Hewlett-Packard Development Company
15 # Copyright (c) 2004-2005 The Regents of The University of Michigan
16 # All rights reserved.
17 #
18 # Redistribution and use in source and binary forms, with or without
19 # modification, are permitted provided that the following conditions are
20 # met: redistributions of source code must retain the above copyright
21 # notice, this list of conditions and the following disclaimer;
22 # redistributions in binary form must reproduce the above copyright
23 # notice, this list of conditions and the following disclaimer in the
24 # documentation and/or other materials provided with the distribution;
25 # neither the name of the copyright holders nor the names of its
26 # contributors may be used to endorse or promote products derived from
27 # this software without specific prior written permission.
28 #
29 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40
41 from __future__ import print_function
42
43 import os
44 import sys
45 import textwrap
46
47 from gem5_scons.util import get_termcap
48 import SCons.Script
49
50 termcap = get_termcap()
51
52 def strip_build_path(path, env):
53 path = str(path)
54 build_base = 'build/'
55 variant_base = env['BUILDROOT'] + os.path.sep
56 if path.startswith(variant_base):
57 path = path[len(variant_base):]
58 elif path.startswith(build_base):
59 path = path[len(build_base):]
60 return path
61
62 # Generate a string of the form:
63 # common/path/prefix/src1, src2 -> tgt1, tgt2
64 # to print while building.
65 class Transform(object):
66 # all specific color settings should be here and nowhere else
67 tool_color = termcap.Normal
68 pfx_color = termcap.Yellow
69 srcs_color = termcap.Yellow + termcap.Bold
70 arrow_color = termcap.Blue + termcap.Bold
71 tgts_color = termcap.Yellow + termcap.Bold
72
73 def __init__(self, tool, max_sources=99):
74 self.format = self.tool_color + (" [%8s] " % tool) \
75 + self.pfx_color + "%s" \
76 + self.srcs_color + "%s" \
77 + self.arrow_color + " -> " \
78 + self.tgts_color + "%s" \
79 + termcap.Normal
80 self.max_sources = max_sources
81
82 def __call__(self, target, source, env, for_signature=None):
83 # truncate source list according to max_sources param
84 source = source[0:self.max_sources]
85 def strip(f):
86 return strip_build_path(str(f), env)
87 if len(source) > 0:
88 srcs = list(map(strip, source))
89 else:
90 srcs = ['']
91 tgts = list(map(strip, target))
92 # surprisingly, os.path.commonprefix is a dumb char-by-char string
93 # operation that has nothing to do with paths.
94 com_pfx = os.path.commonprefix(srcs + tgts)
95 com_pfx_len = len(com_pfx)
96 if com_pfx:
97 # do some cleanup and sanity checking on common prefix
98 if com_pfx[-1] == ".":
99 # prefix matches all but file extension: ok
100 # back up one to change 'foo.cc -> o' to 'foo.cc -> .o'
101 com_pfx = com_pfx[0:-1]
102 elif com_pfx[-1] == "/":
103 # common prefix is directory path: OK
104 pass
105 else:
106 src0_len = len(srcs[0])
107 tgt0_len = len(tgts[0])
108 if src0_len == com_pfx_len:
109 # source is a substring of target, OK
110 pass
111 elif tgt0_len == com_pfx_len:
112 # target is a substring of source, need to back up to
113 # avoid empty string on RHS of arrow
114 sep_idx = com_pfx.rfind(".")
115 if sep_idx != -1:
116 com_pfx = com_pfx[0:sep_idx]
117 else:
118 com_pfx = ''
119 elif src0_len > com_pfx_len and srcs[0][com_pfx_len] == ".":
120 # still splitting at file extension: ok
121 pass
122 else:
123 # probably a fluke; ignore it
124 com_pfx = ''
125 # recalculate length in case com_pfx was modified
126 com_pfx_len = len(com_pfx)
127 def fmt(files):
128 f = list(map(lambda s: s[com_pfx_len:], files))
129 return ', '.join(f)
130 return self.format % (com_pfx, fmt(srcs), fmt(tgts))
131
132 # The width warning and error messages should be wrapped at.
133 text_width = None
134
135 # If stdout is not attached to a terminal, default to 80 columns.
136 if not sys.stdout.isatty():
137 text_width = 80
138
139 # This should work in python 3.3 and above.
140 if text_width is None:
141 try:
142 import shutil
143 text_width = shutil.get_terminal_size().columns
144 except:
145 pass
146
147 # This should work if the curses python module is installed.
148 if text_width is None:
149 try:
150 import curses
151 try:
152 _, text_width = curses.initscr().getmaxyx()
153 finally:
154 curses.endwin()
155 except:
156 pass
157
158 # If all else fails, default to 80 columns.
159 if text_width is None:
160 text_width = 80
161
162 def print_message(prefix, color, message, **kwargs):
163 prefix_len = len(prefix)
164 if text_width > prefix_len:
165 wrap_width = text_width - prefix_len
166 padding = ' ' * prefix_len
167
168 # First split on newlines.
169 lines = message.split('\n')
170 # Then wrap each line to the required width.
171 wrapped_lines = []
172 for line in lines:
173 wrapped_lines.extend(textwrap.wrap(line, wrap_width))
174 # Finally add the prefix and padding on extra lines, and glue it all
175 # back together.
176 message = prefix + ('\n' + padding).join(wrapped_lines)
177 else:
178 # We have very small terminal, indent formatting doesn't help.
179 message = prefix + message
180 # Add in terminal escape sequences.
181 message = color + termcap.Bold + message + termcap.Normal
182 # Actually print the message.
183 print(message, **kwargs)
184 return message
185
186 all_warnings = []
187 def summarize_warnings():
188 if not all_warnings:
189 return
190 print(termcap.Yellow + termcap.Bold +
191 '*** Summary of Warnings ***' +
192 termcap.Normal)
193 list(map(print, all_warnings))
194
195 def warning(*args, **kwargs):
196 message = ' '.join(args)
197 printed = print_message('Warning: ', termcap.Yellow, message, **kwargs)
198 all_warnings.append(printed)
199
200 def error(*args, **kwargs):
201 message = ' '.join(args)
202 print_message('Error: ', termcap.Red, message, **kwargs)
203 SCons.Script.Exit(1)
204
205 __all__ = ['Transform', 'warning', 'error']