8e9f22afe8af8b2313c056df065888fbc6e5c148
[platform/kernel/u-boot.git] / tools / patman / tools.py
1 # SPDX-License-Identifier: GPL-2.0+
2 #
3 # Copyright (c) 2016 Google, Inc
4 #
5
6 import command
7 import glob
8 import os
9 import shutil
10 import sys
11 import tempfile
12
13 import tout
14
15 # Output directly (generally this is temporary)
16 outdir = None
17
18 # True to keep the output directory around after exiting
19 preserve_outdir = False
20
21 # Path to the Chrome OS chroot, if we know it
22 chroot_path = None
23
24 # Search paths to use for Filename(), used to find files
25 search_paths = []
26
27 # Tools and the packages that contain them, on debian
28 packages = {
29     'lz4': 'liblz4-tool',
30     }
31
32 # List of paths to use when looking for an input file
33 indir = []
34
35 def PrepareOutputDir(dirname, preserve=False):
36     """Select an output directory, ensuring it exists.
37
38     This either creates a temporary directory or checks that the one supplied
39     by the user is valid. For a temporary directory, it makes a note to
40     remove it later if required.
41
42     Args:
43         dirname: a string, name of the output directory to use to store
44                 intermediate and output files. If is None - create a temporary
45                 directory.
46         preserve: a Boolean. If outdir above is None and preserve is False, the
47                 created temporary directory will be destroyed on exit.
48
49     Raises:
50         OSError: If it cannot create the output directory.
51     """
52     global outdir, preserve_outdir
53
54     preserve_outdir = dirname or preserve
55     if dirname:
56         outdir = dirname
57         if not os.path.isdir(outdir):
58             try:
59                 os.makedirs(outdir)
60             except OSError as err:
61                 raise CmdError("Cannot make output directory '%s': '%s'" %
62                                 (outdir, err.strerror))
63         tout.Debug("Using output directory '%s'" % outdir)
64     else:
65         outdir = tempfile.mkdtemp(prefix='binman.')
66         tout.Debug("Using temporary directory '%s'" % outdir)
67
68 def _RemoveOutputDir():
69     global outdir
70
71     shutil.rmtree(outdir)
72     tout.Debug("Deleted temporary directory '%s'" % outdir)
73     outdir = None
74
75 def FinaliseOutputDir():
76     global outdir, preserve_outdir
77
78     """Tidy up: delete output directory if temporary and not preserved."""
79     if outdir and not preserve_outdir:
80         _RemoveOutputDir()
81
82 def GetOutputFilename(fname):
83     """Return a filename within the output directory.
84
85     Args:
86         fname: Filename to use for new file
87
88     Returns:
89         The full path of the filename, within the output directory
90     """
91     return os.path.join(outdir, fname)
92
93 def _FinaliseForTest():
94     """Remove the output directory (for use by tests)"""
95     global outdir
96
97     if outdir:
98         _RemoveOutputDir()
99
100 def SetInputDirs(dirname):
101     """Add a list of input directories, where input files are kept.
102
103     Args:
104         dirname: a list of paths to input directories to use for obtaining
105                 files needed by binman to place in the image.
106     """
107     global indir
108
109     indir = dirname
110     tout.Debug("Using input directories %s" % indir)
111
112 def GetInputFilename(fname):
113     """Return a filename for use as input.
114
115     Args:
116         fname: Filename to use for new file
117
118     Returns:
119         The full path of the filename, within the input directory
120     """
121     if not indir:
122         return fname
123     for dirname in indir:
124         pathname = os.path.join(dirname, fname)
125         if os.path.exists(pathname):
126             return pathname
127
128     raise ValueError("Filename '%s' not found in input path (%s) (cwd='%s')" %
129                      (fname, ','.join(indir), os.getcwd()))
130
131 def GetInputFilenameGlob(pattern):
132     """Return a list of filenames for use as input.
133
134     Args:
135         pattern: Filename pattern to search for
136
137     Returns:
138         A list of matching files in all input directories
139     """
140     if not indir:
141         return glob.glob(fname)
142     files = []
143     for dirname in indir:
144         pathname = os.path.join(dirname, pattern)
145         files += glob.glob(pathname)
146     return sorted(files)
147
148 def Align(pos, align):
149     if align:
150         mask = align - 1
151         pos = (pos + mask) & ~mask
152     return pos
153
154 def NotPowerOfTwo(num):
155     return num and (num & (num - 1))
156
157 def PathHasFile(fname):
158     """Check if a given filename is in the PATH
159
160     Args:
161         fname: Filename to check
162
163     Returns:
164         True if found, False if not
165     """
166     for dir in os.environ['PATH'].split(':'):
167         if os.path.exists(os.path.join(dir, fname)):
168             return True
169     return False
170
171 def Run(name, *args, **kwargs):
172     try:
173         return command.Run(name, *args, cwd=outdir, capture=True, **kwargs)
174     except:
175         if not PathHasFile(name):
176             msg = "Plesae install tool '%s'" % name
177             package = packages.get(name)
178             if package:
179                  msg += " (e.g. from package '%s')" % package
180             raise ValueError(msg)
181         raise
182
183 def Filename(fname):
184     """Resolve a file path to an absolute path.
185
186     If fname starts with ##/ and chroot is available, ##/ gets replaced with
187     the chroot path. If chroot is not available, this file name can not be
188     resolved, `None' is returned.
189
190     If fname is not prepended with the above prefix, and is not an existing
191     file, the actual file name is retrieved from the passed in string and the
192     search_paths directories (if any) are searched to for the file. If found -
193     the path to the found file is returned, `None' is returned otherwise.
194
195     Args:
196       fname: a string,  the path to resolve.
197
198     Returns:
199       Absolute path to the file or None if not found.
200     """
201     if fname.startswith('##/'):
202       if chroot_path:
203         fname = os.path.join(chroot_path, fname[3:])
204       else:
205         return None
206
207     # Search for a pathname that exists, and return it if found
208     if fname and not os.path.exists(fname):
209         for path in search_paths:
210             pathname = os.path.join(path, os.path.basename(fname))
211             if os.path.exists(pathname):
212                 return pathname
213
214     # If not found, just return the standard, unchanged path
215     return fname
216
217 def ReadFile(fname, binary=True):
218     """Read and return the contents of a file.
219
220     Args:
221       fname: path to filename to read, where ## signifiies the chroot.
222
223     Returns:
224       data read from file, as a string.
225     """
226     with open(Filename(fname), binary and 'rb' or 'r') as fd:
227         data = fd.read()
228     #self._out.Info("Read file '%s' size %d (%#0x)" %
229                    #(fname, len(data), len(data)))
230     return data
231
232 def WriteFile(fname, data):
233     """Write data into a file.
234
235     Args:
236         fname: path to filename to write
237         data: data to write to file, as a string
238     """
239     #self._out.Info("Write file '%s' size %d (%#0x)" %
240                    #(fname, len(data), len(data)))
241     with open(Filename(fname), 'wb') as fd:
242         fd.write(data)
243
244 def GetBytes(byte, size):
245     """Get a string of bytes of a given size
246
247     This handles the unfortunate different between Python 2 and Python 2.
248
249     Args:
250         byte: Numeric byte value to use
251         size: Size of bytes/string to return
252
253     Returns:
254         A bytes type with 'byte' repeated 'size' times
255     """
256     if sys.version_info[0] >= 3:
257         data = bytes([byte]) * size
258     else:
259         data = chr(byte) * size
260     return data
261
262 def ToUnicode(val):
263     """Make sure a value is a unicode string
264
265     This allows some amount of compatibility between Python 2 and Python3. For
266     the former, it returns a unicode object.
267
268     Args:
269         val: string or unicode object
270
271     Returns:
272         unicode version of val
273     """
274     if sys.version_info[0] >= 3:
275         return val
276     return val if isinstance(val, unicode) else val.decode('utf-8')
277
278 def FromUnicode(val):
279     """Make sure a value is a non-unicode string
280
281     This allows some amount of compatibility between Python 2 and Python3. For
282     the former, it converts a unicode object to a string.
283
284     Args:
285         val: string or unicode object
286
287     Returns:
288         non-unicode version of val
289     """
290     if sys.version_info[0] >= 3:
291         return val
292     return val if isinstance(val, str) else val.encode('utf-8')
293
294 def ToByte(ch):
295     """Convert a character to an ASCII value
296
297     This is useful because in Python 2 bytes is an alias for str, but in
298     Python 3 they are separate types. This function converts the argument to
299     an ASCII value in either case.
300
301     Args:
302         ch: A string (Python 2) or byte (Python 3) value
303
304     Returns:
305         integer ASCII value for ch
306     """
307     return ord(ch) if type(ch) == str else ch
308
309 def ToChar(byte):
310     """Convert a byte to a character
311
312     This is useful because in Python 2 bytes is an alias for str, but in
313     Python 3 they are separate types. This function converts an ASCII value to
314     a value with the appropriate type in either case.
315
316     Args:
317         byte: A byte or str value
318     """
319     return chr(byte) if type(byte) != str else byte
320
321 def ToChars(byte_list):
322     """Convert a list of bytes to a str/bytes type
323
324     Args:
325         byte_list: List of ASCII values representing the string
326
327     Returns:
328         string made by concatenating all the ASCII values
329     """
330     return ''.join([chr(byte) for byte in byte_list])
331
332 def ToBytes(string):
333     """Convert a str type into a bytes type
334
335     Args:
336         string: string to convert value
337
338     Returns:
339         Python 3: A bytes type
340         Python 2: A string type
341     """
342     if sys.version_info[0] >= 3:
343         return string.encode('utf-8')
344     return string