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