1# Copyright (C) 2014-2018 Intel Corporation.   All Rights Reserved.
2#
3# Permission is hereby granted, free of charge, to any person obtaining a
4# copy of this software and associated documentation files (the "Software"),
5# to deal in the Software without restriction, including without limitation
6# the rights to use, copy, modify, merge, publish, distribute, sublicense,
7# and/or sell copies of the Software, and to permit persons to whom the
8# Software is furnished to do so, subject to the following conditions:
9#
10# The above copyright notice and this permission notice (including the next
11# paragraph) shall be included in all copies or substantial portions of the
12# Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20# IN THE SOFTWARE.
21
22# Python source
23import os
24import errno
25import sys
26import argparse
27import tempfile
28import filecmp
29import shutil
30from mako.template import Template
31from mako.exceptions import RichTraceback
32
33#==============================================================================
34def ConcatLists(list_of_lists):
35    output = []
36    for l in list_of_lists: output += l
37    return output
38
39#==============================================================================
40def MakeTmpDir(suffix=''):
41    '''
42        Create temporary directory for use in codegen scripts.
43    '''
44    return tempfile.mkdtemp(suffix)
45
46#==============================================================================
47def MakeDir(dir_path):
48    '''
49        Create a directory if it doesn't exist
50
51        returns 0 on success, non-zero on failure
52    '''
53    dir_path = os.path.abspath(dir_path)
54
55    if not os.path.exists(dir_path):
56        try:
57            os.makedirs(dir_path)
58        except OSError as err:
59            if err.errno != errno.EEXIST:
60                return 1
61    else:
62        if not os.path.isdir(dir_path):
63            return 1
64
65    return 0
66
67#==============================================================================
68def DeleteDirTree(dir_path):
69    '''
70        Delete directory tree.
71
72        returns 0 on success, non-zero on failure
73    '''
74    rval = 0
75    try:
76        shutil.rmtree(dir_path, False)
77    except:
78        rval = 1
79    return rval
80
81#==============================================================================
82def CopyFileIfDifferent(src, dst, verbose = False):
83    '''
84        Copy <src> file to <dst> file if the <dst>
85        file either doesn't contain the file or the file
86        contents are different.
87
88        returns 0 on success, non-zero on failure
89    '''
90
91    assert os.path.isfile(src)
92    assert (False == os.path.exists(dst) or os.path.isfile(dst))
93
94    need_copy = not os.path.exists(dst)
95    if not need_copy:
96        need_copy = not filecmp.cmp(src, dst)
97
98    if need_copy:
99        try:
100            shutil.copy2(src, dst)
101        except:
102            print('ERROR: Could not copy %s to %s' % (src, dst), file=sys.stderr)
103            return 1
104
105        if verbose:
106            print(src, '-->', dst)
107
108    return 0
109
110#==============================================================================
111def CopyDirFilesIfDifferent(src, dst, recurse = True, verbose = False, orig_dst = None):
112    '''
113        Copy files <src> directory to <dst> directory if the <dst>
114        directory either doesn't contain the file or the file
115        contents are different.
116
117        Optionally recurses into subdirectories
118
119        returns 0 on success, non-zero on failure
120    '''
121
122    assert os.path.isdir(src)
123    assert os.path.isdir(dst)
124
125    src = os.path.abspath(src)
126    dst = os.path.abspath(dst)
127
128    if not orig_dst:
129        orig_dst = dst
130
131    for f in os.listdir(src):
132        src_path = os.path.join(src, f)
133        dst_path = os.path.join(dst, f)
134
135        # prevent recursion
136        if src_path == orig_dst:
137            continue
138
139        if os.path.isdir(src_path):
140            if recurse:
141                if MakeDir(dst_path):
142                    print('ERROR: Could not create directory:', dst_path, file=sys.stderr)
143                    return 1
144
145                if verbose:
146                    print('mkdir', dst_path)
147                rval = CopyDirFilesIfDifferent(src_path, dst_path, recurse, verbose, orig_dst)
148        else:
149            rval = CopyFileIfDifferent(src_path, dst_path, verbose)
150
151        if rval:
152            return rval
153
154    return 0
155
156#==============================================================================
157class MakoTemplateWriter:
158    '''
159        MakoTemplateWriter - Class (namespace) for functions to generate strings
160        or files using the Mako template module.
161
162        See http://docs.makotemplates.org/en/latest/ for
163        mako documentation.
164   '''
165
166    @staticmethod
167    def to_string(template_filename, **kwargs):
168        '''
169            Write template data to a string object and return the string
170        '''
171        from mako.template      import Template
172        from mako.exceptions    import RichTraceback
173
174        try:
175            template = Template(filename=template_filename)
176            # Split + Join fixes line-endings for whatever platform you are using
177            return '\n'.join(template.render(**kwargs).splitlines())
178        except:
179            traceback = RichTraceback()
180            for (filename, lineno, function, line) in traceback.traceback:
181                print('File %s, line %s, in %s' % (filename, lineno, function))
182                print(line, '\n')
183            print('%s: %s' % (str(traceback.error.__class__.__name__), traceback.error))
184            raise
185
186    @staticmethod
187    def to_file(template_filename, output_filename, **kwargs):
188        '''
189            Write template data to a file
190        '''
191        if MakeDir(os.path.dirname(output_filename)):
192            return 1
193        with open(output_filename, 'w') as outfile:
194            print(MakoTemplateWriter.to_string(template_filename, **kwargs), file=outfile)
195        return 0
196
197
198#==============================================================================
199class ArgumentParser(argparse.ArgumentParser):
200    '''
201    Subclass of argparse.ArgumentParser
202
203    Allow parsing from command files that start with @
204    Example:
205      >bt run @myargs.txt
206
207    Contents of myargs.txt:
208      -m <machine>
209      --target cdv_win7
210
211    The below function allows multiple args to be placed on the same text-file line.
212    The default is one token per line, which is a little cumbersome.
213
214    Also allow all characters after a '#' character to be ignored.
215    '''
216
217    #==============================================================================
218    class _HelpFormatter(argparse.RawTextHelpFormatter):
219        ''' Better help formatter for argument parser '''
220
221        def _split_lines(self, text, width):
222            ''' optimized split lines algorithm, indents split lines '''
223            lines = text.splitlines()
224            out_lines = []
225            if len(lines):
226                out_lines.append(lines[0])
227                for line in lines[1:]:
228                    out_lines.append('  ' + line)
229            return out_lines
230
231    #==============================================================================
232    def __init__(self, *args, **kwargs):
233        ''' Constructor.  Compatible with argparse.ArgumentParser(),
234            but with some modifications for better usage and help display.
235        '''
236        super(ArgumentParser, self).__init__(
237                *args,
238                fromfile_prefix_chars='@',
239                formatter_class=ArgumentParser._HelpFormatter,
240                **kwargs)
241
242    #==========================================================================
243    def convert_arg_line_to_args(self, arg_line):
244        ''' convert one line of parsed file to arguments '''
245        arg_line = arg_line.split('#', 1)[0]
246        if sys.platform == 'win32':
247            arg_line = arg_line.replace('\\', '\\\\')
248        for arg in shlex.split(arg_line):
249            if not arg.strip():
250                continue
251            yield arg
252
253    #==========================================================================
254    def _read_args_from_files(self, arg_strings):
255        ''' read arguments from files '''
256        # expand arguments referencing files
257        new_arg_strings = []
258        for arg_string in arg_strings:
259
260            # for regular arguments, just add them back into the list
261            if arg_string[0] not in self.fromfile_prefix_chars:
262                new_arg_strings.append(arg_string)
263
264            # replace arguments referencing files with the file content
265            else:
266                filename = arg_string[1:]
267
268                # Search in sys.path
269                if not os.path.exists(filename):
270                    for path in sys.path:
271                        filename = os.path.join(path, arg_string[1:])
272                        if os.path.exists(filename):
273                            break
274
275                try:
276                    args_file = open(filename)
277                    try:
278                        arg_strings = []
279                        for arg_line in args_file.read().splitlines():
280                            for arg in self.convert_arg_line_to_args(arg_line):
281                                arg_strings.append(arg)
282                        arg_strings = self._read_args_from_files(arg_strings)
283                        new_arg_strings.extend(arg_strings)
284                    finally:
285                        args_file.close()
286                except IOError:
287                    err = sys.exc_info()[1]
288                    self.error(str(err))
289
290        # return the modified argument list
291        return new_arg_strings
292