blob: 976670ef0068f879a3044ef9755946c9850e9d3d [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass1f1864b2016-07-25 18:59:08 -06002#
3# Copyright (c) 2016 Google, Inc
4#
Simon Glass1f1864b2016-07-25 18:59:08 -06005
Simon Glassaeffc5e2018-07-17 13:25:43 -06006import command
Simon Glass0a98b282018-09-14 04:57:28 -06007import glob
Simon Glass1f1864b2016-07-25 18:59:08 -06008import os
9import shutil
Simon Glasse6d85ff2019-05-14 15:53:47 -060010import sys
Simon Glass1f1864b2016-07-25 18:59:08 -060011import tempfile
12
13import tout
14
Simon Glassaeffc5e2018-07-17 13:25:43 -060015# Output directly (generally this is temporary)
Simon Glass1f1864b2016-07-25 18:59:08 -060016outdir = None
Simon Glassaeffc5e2018-07-17 13:25:43 -060017
18# True to keep the output directory around after exiting
Simon Glass1f1864b2016-07-25 18:59:08 -060019preserve_outdir = False
20
Simon Glassaeffc5e2018-07-17 13:25:43 -060021# Path to the Chrome OS chroot, if we know it
22chroot_path = None
23
24# Search paths to use for Filename(), used to find files
25search_paths = []
26
Simon Glass04187a82018-09-14 04:57:25 -060027# Tools and the packages that contain them, on debian
28packages = {
29 'lz4': 'liblz4-tool',
30 }
Simon Glassaeffc5e2018-07-17 13:25:43 -060031
Simon Glass1fda1822018-10-01 21:12:44 -060032# List of paths to use when looking for an input file
33indir = []
34
Simon Glass1f1864b2016-07-25 18:59:08 -060035def 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
68def _RemoveOutputDir():
69 global outdir
70
71 shutil.rmtree(outdir)
72 tout.Debug("Deleted temporary directory '%s'" % outdir)
73 outdir = None
74
75def 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
82def 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
93def _FinaliseForTest():
94 """Remove the output directory (for use by tests)"""
95 global outdir
96
97 if outdir:
98 _RemoveOutputDir()
99
100def 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
112def 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
Simon Glass4f5dea42018-07-17 13:25:45 -0600128 raise ValueError("Filename '%s' not found in input path (%s) (cwd='%s')" %
129 (fname, ','.join(indir), os.getcwd()))
Simon Glass1f1864b2016-07-25 18:59:08 -0600130
Simon Glass0a98b282018-09-14 04:57:28 -0600131def 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
Simon Glass1f1864b2016-07-25 18:59:08 -0600148def Align(pos, align):
149 if align:
150 mask = align - 1
151 pos = (pos + mask) & ~mask
152 return pos
153
154def NotPowerOfTwo(num):
155 return num and (num & (num - 1))
Simon Glassaeffc5e2018-07-17 13:25:43 -0600156
Simon Glass04187a82018-09-14 04:57:25 -0600157def 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
Simon Glassa92939a2019-05-14 15:53:44 -0600171def Run(name, *args, **kwargs):
Simon Glass04187a82018-09-14 04:57:25 -0600172 try:
Simon Glassa92939a2019-05-14 15:53:44 -0600173 return command.Run(name, *args, cwd=outdir, capture=True, **kwargs)
Simon Glass04187a82018-09-14 04:57:25 -0600174 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
Simon Glassaeffc5e2018-07-17 13:25:43 -0600182
183def 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
217def ReadFile(fname):
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), 'rb') 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
232def 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)
Simon Glasse6d85ff2019-05-14 15:53:47 -0600243
244def 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
Simon Glass513eace2019-05-14 15:53:50 -0600261
262def 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
278def 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')
Simon Glass2b6ed5e2019-05-17 22:00:35 -0600293
294def 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
309def 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