blob: 2ac814d476ffeb96c5e6a53d63057929765a5c6c [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassbe39f052016-07-25 18:59:08 -06002#
3# Copyright (c) 2016 Google, Inc
4#
Simon Glassbe39f052016-07-25 18:59:08 -06005
Simon Glassac6328c2018-09-14 04:57:28 -06006import glob
Simon Glassbe39f052016-07-25 18:59:08 -06007import os
Paul Barkerce7efa02021-09-08 12:38:02 +01008import shlex
Simon Glassbe39f052016-07-25 18:59:08 -06009import shutil
Simon Glassac0d4952019-05-14 15:53:47 -060010import sys
Simon Glassbe39f052016-07-25 18:59:08 -060011import tempfile
Simon Glass25885d12022-01-09 20:13:41 -070012import urllib.request
Simon Glassbe39f052016-07-25 18:59:08 -060013
Simon Glassa997ea52020-04-17 18:09:04 -060014from patman import command
15from patman import tout
Simon Glassbe39f052016-07-25 18:59:08 -060016
Simon Glass0362cd22018-07-17 13:25:43 -060017# Output directly (generally this is temporary)
Simon Glassbe39f052016-07-25 18:59:08 -060018outdir = None
Simon Glass0362cd22018-07-17 13:25:43 -060019
20# True to keep the output directory around after exiting
Simon Glassbe39f052016-07-25 18:59:08 -060021preserve_outdir = False
22
Simon Glass0362cd22018-07-17 13:25:43 -060023# Path to the Chrome OS chroot, if we know it
24chroot_path = None
25
Simon Glass80025522022-01-29 14:14:04 -070026# Search paths to use for filename(), used to find files
Simon Glass0362cd22018-07-17 13:25:43 -060027search_paths = []
28
Simon Glass90c3c4c2019-07-08 13:18:27 -060029tool_search_paths = []
30
Simon Glass778ab842018-09-14 04:57:25 -060031# Tools and the packages that contain them, on debian
32packages = {
33 'lz4': 'liblz4-tool',
34 }
Simon Glass0362cd22018-07-17 13:25:43 -060035
Simon Glass867b9062018-10-01 21:12:44 -060036# List of paths to use when looking for an input file
37indir = []
38
Simon Glass80025522022-01-29 14:14:04 -070039def prepare_output_dir(dirname, preserve=False):
Simon Glassbe39f052016-07-25 18:59:08 -060040 """Select an output directory, ensuring it exists.
41
42 This either creates a temporary directory or checks that the one supplied
43 by the user is valid. For a temporary directory, it makes a note to
44 remove it later if required.
45
46 Args:
47 dirname: a string, name of the output directory to use to store
48 intermediate and output files. If is None - create a temporary
49 directory.
50 preserve: a Boolean. If outdir above is None and preserve is False, the
51 created temporary directory will be destroyed on exit.
52
53 Raises:
54 OSError: If it cannot create the output directory.
55 """
56 global outdir, preserve_outdir
57
58 preserve_outdir = dirname or preserve
59 if dirname:
60 outdir = dirname
61 if not os.path.isdir(outdir):
62 try:
63 os.makedirs(outdir)
64 except OSError as err:
Simon Glass547cba62022-02-11 13:23:18 -070065 raise ValueError(
66 f"Cannot make output directory 'outdir': 'err.strerror'")
Simon Glass011f1b32022-01-29 14:14:15 -070067 tout.debug("Using output directory '%s'" % outdir)
Simon Glassbe39f052016-07-25 18:59:08 -060068 else:
69 outdir = tempfile.mkdtemp(prefix='binman.')
Simon Glass011f1b32022-01-29 14:14:15 -070070 tout.debug("Using temporary directory '%s'" % outdir)
Simon Glassbe39f052016-07-25 18:59:08 -060071
Simon Glass80025522022-01-29 14:14:04 -070072def _remove_output_dir():
Simon Glassbe39f052016-07-25 18:59:08 -060073 global outdir
74
75 shutil.rmtree(outdir)
Simon Glass011f1b32022-01-29 14:14:15 -070076 tout.debug("Deleted temporary directory '%s'" % outdir)
Simon Glassbe39f052016-07-25 18:59:08 -060077 outdir = None
78
Simon Glass80025522022-01-29 14:14:04 -070079def finalise_output_dir():
Simon Glassbe39f052016-07-25 18:59:08 -060080 global outdir, preserve_outdir
81
82 """Tidy up: delete output directory if temporary and not preserved."""
83 if outdir and not preserve_outdir:
Simon Glass80025522022-01-29 14:14:04 -070084 _remove_output_dir()
Simon Glass7f47e082019-07-20 12:24:07 -060085 outdir = None
Simon Glassbe39f052016-07-25 18:59:08 -060086
Simon Glass80025522022-01-29 14:14:04 -070087def get_output_filename(fname):
Simon Glassbe39f052016-07-25 18:59:08 -060088 """Return a filename within the output directory.
89
90 Args:
91 fname: Filename to use for new file
92
93 Returns:
94 The full path of the filename, within the output directory
95 """
96 return os.path.join(outdir, fname)
97
Simon Glass80025522022-01-29 14:14:04 -070098def get_output_dir():
Simon Glass4e8e8462020-12-28 20:34:52 -070099 """Return the current output directory
100
101 Returns:
102 str: The output directory
103 """
104 return outdir
105
Simon Glass80025522022-01-29 14:14:04 -0700106def _finalise_for_test():
Simon Glassbe39f052016-07-25 18:59:08 -0600107 """Remove the output directory (for use by tests)"""
108 global outdir
109
110 if outdir:
Simon Glass80025522022-01-29 14:14:04 -0700111 _remove_output_dir()
Simon Glass7f47e082019-07-20 12:24:07 -0600112 outdir = None
Simon Glassbe39f052016-07-25 18:59:08 -0600113
Simon Glass80025522022-01-29 14:14:04 -0700114def set_input_dirs(dirname):
Simon Glassbe39f052016-07-25 18:59:08 -0600115 """Add a list of input directories, where input files are kept.
116
117 Args:
118 dirname: a list of paths to input directories to use for obtaining
119 files needed by binman to place in the image.
120 """
121 global indir
122
123 indir = dirname
Simon Glass011f1b32022-01-29 14:14:15 -0700124 tout.debug("Using input directories %s" % indir)
Simon Glassbe39f052016-07-25 18:59:08 -0600125
Simon Glass80025522022-01-29 14:14:04 -0700126def get_input_filename(fname, allow_missing=False):
Simon Glassbe39f052016-07-25 18:59:08 -0600127 """Return a filename for use as input.
128
129 Args:
130 fname: Filename to use for new file
Simon Glass5d94cc62020-07-09 18:39:38 -0600131 allow_missing: True if the filename can be missing
Simon Glassbe39f052016-07-25 18:59:08 -0600132
133 Returns:
Simon Glasseb751fd2021-03-18 20:25:03 +1300134 fname, if indir is None;
135 full path of the filename, within the input directory;
136 None, if file is missing and allow_missing is True
137
138 Raises:
139 ValueError if file is missing and allow_missing is False
Simon Glassbe39f052016-07-25 18:59:08 -0600140 """
Simon Glass4affd4b2019-08-24 07:22:54 -0600141 if not indir or fname[:1] == '/':
Simon Glassbe39f052016-07-25 18:59:08 -0600142 return fname
143 for dirname in indir:
144 pathname = os.path.join(dirname, fname)
145 if os.path.exists(pathname):
146 return pathname
147
Simon Glass5d94cc62020-07-09 18:39:38 -0600148 if allow_missing:
149 return None
Simon Glassf2eb0542018-07-17 13:25:45 -0600150 raise ValueError("Filename '%s' not found in input path (%s) (cwd='%s')" %
151 (fname, ','.join(indir), os.getcwd()))
Simon Glassbe39f052016-07-25 18:59:08 -0600152
Simon Glass80025522022-01-29 14:14:04 -0700153def get_input_filename_glob(pattern):
Simon Glassac6328c2018-09-14 04:57:28 -0600154 """Return a list of filenames for use as input.
155
156 Args:
157 pattern: Filename pattern to search for
158
159 Returns:
160 A list of matching files in all input directories
161 """
162 if not indir:
Simon Glass547cba62022-02-11 13:23:18 -0700163 return glob.glob(pattern)
Simon Glassac6328c2018-09-14 04:57:28 -0600164 files = []
165 for dirname in indir:
166 pathname = os.path.join(dirname, pattern)
167 files += glob.glob(pathname)
168 return sorted(files)
169
Simon Glass80025522022-01-29 14:14:04 -0700170def align(pos, align):
Simon Glassbe39f052016-07-25 18:59:08 -0600171 if align:
172 mask = align - 1
173 pos = (pos + mask) & ~mask
174 return pos
175
Simon Glass80025522022-01-29 14:14:04 -0700176def not_power_of_two(num):
Simon Glassbe39f052016-07-25 18:59:08 -0600177 return num and (num & (num - 1))
Simon Glass0362cd22018-07-17 13:25:43 -0600178
Simon Glass80025522022-01-29 14:14:04 -0700179def set_tool_paths(toolpaths):
Simon Glass90c3c4c2019-07-08 13:18:27 -0600180 """Set the path to search for tools
181
182 Args:
Simon Glass80025522022-01-29 14:14:04 -0700183 toolpaths: List of paths to search for tools executed by run()
Simon Glass90c3c4c2019-07-08 13:18:27 -0600184 """
185 global tool_search_paths
186
187 tool_search_paths = toolpaths
188
Simon Glass80025522022-01-29 14:14:04 -0700189def path_has_file(path_spec, fname):
Simon Glass778ab842018-09-14 04:57:25 -0600190 """Check if a given filename is in the PATH
191
192 Args:
Simon Glass90c3c4c2019-07-08 13:18:27 -0600193 path_spec: Value of PATH variable to check
Simon Glass778ab842018-09-14 04:57:25 -0600194 fname: Filename to check
195
196 Returns:
197 True if found, False if not
198 """
Simon Glass90c3c4c2019-07-08 13:18:27 -0600199 for dir in path_spec.split(':'):
Simon Glass778ab842018-09-14 04:57:25 -0600200 if os.path.exists(os.path.join(dir, fname)):
201 return True
202 return False
203
Simon Glass547cba62022-02-11 13:23:18 -0700204def get_host_compile_tool(env, name):
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300205 """Get the host-specific version for a compile tool
206
207 This checks the environment variables that specify which version of
208 the tool should be used (e.g. ${HOSTCC}).
209
210 The following table lists the host-specific versions of the tools
211 this function resolves to:
212
213 Compile Tool | Host version
214 --------------+----------------
215 as | ${HOSTAS}
216 ld | ${HOSTLD}
217 cc | ${HOSTCC}
218 cpp | ${HOSTCPP}
219 c++ | ${HOSTCXX}
220 ar | ${HOSTAR}
221 nm | ${HOSTNM}
222 ldr | ${HOSTLDR}
223 strip | ${HOSTSTRIP}
224 objcopy | ${HOSTOBJCOPY}
225 objdump | ${HOSTOBJDUMP}
226 dtc | ${HOSTDTC}
227
228 Args:
229 name: Command name to run
230
231 Returns:
232 host_name: Exact command name to run instead
233 extra_args: List of extra arguments to pass
234 """
235 host_name = None
236 extra_args = []
237 if name in ('as', 'ld', 'cc', 'cpp', 'ar', 'nm', 'ldr', 'strip',
238 'objcopy', 'objdump', 'dtc'):
239 host_name, *host_args = env.get('HOST' + name.upper(), '').split(' ')
240 elif name == 'c++':
241 host_name, *host_args = env.get('HOSTCXX', '').split(' ')
242
243 if host_name:
244 return host_name, extra_args
245 return name, []
246
Simon Glass80025522022-01-29 14:14:04 -0700247def get_target_compile_tool(name, cross_compile=None):
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300248 """Get the target-specific version for a compile tool
249
250 This first checks the environment variables that specify which
251 version of the tool should be used (e.g. ${CC}). If those aren't
252 specified, it checks the CROSS_COMPILE variable as a prefix for the
253 tool with some substitutions (e.g. "${CROSS_COMPILE}gcc" for cc).
254
255 The following table lists the target-specific versions of the tools
256 this function resolves to:
257
258 Compile Tool | First choice | Second choice
259 --------------+----------------+----------------------------
260 as | ${AS} | ${CROSS_COMPILE}as
261 ld | ${LD} | ${CROSS_COMPILE}ld.bfd
262 | | or ${CROSS_COMPILE}ld
263 cc | ${CC} | ${CROSS_COMPILE}gcc
264 cpp | ${CPP} | ${CROSS_COMPILE}gcc -E
265 c++ | ${CXX} | ${CROSS_COMPILE}g++
266 ar | ${AR} | ${CROSS_COMPILE}ar
267 nm | ${NM} | ${CROSS_COMPILE}nm
268 ldr | ${LDR} | ${CROSS_COMPILE}ldr
269 strip | ${STRIP} | ${CROSS_COMPILE}strip
270 objcopy | ${OBJCOPY} | ${CROSS_COMPILE}objcopy
271 objdump | ${OBJDUMP} | ${CROSS_COMPILE}objdump
272 dtc | ${DTC} | (no CROSS_COMPILE version)
273
274 Args:
275 name: Command name to run
276
277 Returns:
278 target_name: Exact command name to run instead
279 extra_args: List of extra arguments to pass
280 """
281 env = dict(os.environ)
282
283 target_name = None
284 extra_args = []
285 if name in ('as', 'ld', 'cc', 'cpp', 'ar', 'nm', 'ldr', 'strip',
286 'objcopy', 'objdump', 'dtc'):
287 target_name, *extra_args = env.get(name.upper(), '').split(' ')
288 elif name == 'c++':
289 target_name, *extra_args = env.get('CXX', '').split(' ')
290
291 if target_name:
292 return target_name, extra_args
293
294 if cross_compile is None:
295 cross_compile = env.get('CROSS_COMPILE', '')
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300296
297 if name in ('as', 'ar', 'nm', 'ldr', 'strip', 'objcopy', 'objdump'):
298 target_name = cross_compile + name
299 elif name == 'ld':
300 try:
Simon Glass80025522022-01-29 14:14:04 -0700301 if run(cross_compile + 'ld.bfd', '-v'):
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300302 target_name = cross_compile + 'ld.bfd'
303 except:
304 target_name = cross_compile + 'ld'
305 elif name == 'cc':
306 target_name = cross_compile + 'gcc'
307 elif name == 'cpp':
308 target_name = cross_compile + 'gcc'
309 extra_args = ['-E']
310 elif name == 'c++':
311 target_name = cross_compile + 'g++'
312 else:
313 target_name = name
314 return target_name, extra_args
315
Simon Glass6d493a52022-01-09 20:13:40 -0700316def get_env_with_path():
317 """Get an updated environment with the PATH variable set correctly
318
319 If there are any search paths set, these need to come first in the PATH so
320 that these override any other version of the tools.
321
322 Returns:
323 dict: New environment with PATH updated, or None if there are not search
324 paths
325 """
326 if tool_search_paths:
327 env = dict(os.environ)
328 env['PATH'] = ':'.join(tool_search_paths) + ':' + env['PATH']
329 return env
330
331def run_result(name, *args, **kwargs):
Simon Glass90c3c4c2019-07-08 13:18:27 -0600332 """Run a tool with some arguments
333
334 This runs a 'tool', which is a program used by binman to process files and
335 perhaps produce some output. Tools can be located on the PATH or in a
336 search path.
337
338 Args:
339 name: Command name to run
340 args: Arguments to the tool
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300341 for_host: True to resolve the command to the version for the host
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300342 for_target: False to run the command as-is, without resolving it
343 to the version for the compile target
Simon Glass6d493a52022-01-09 20:13:40 -0700344 raise_on_error: Raise an error if the command fails (True by default)
Simon Glass90c3c4c2019-07-08 13:18:27 -0600345
346 Returns:
347 CommandResult object
348 """
Simon Glass778ab842018-09-14 04:57:25 -0600349 try:
Simon Glasscc311ac2019-10-31 07:42:50 -0600350 binary = kwargs.get('binary')
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300351 for_host = kwargs.get('for_host', False)
352 for_target = kwargs.get('for_target', not for_host)
Simon Glass6d493a52022-01-09 20:13:40 -0700353 raise_on_error = kwargs.get('raise_on_error', True)
354 env = get_env_with_path()
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300355 if for_target:
Simon Glass80025522022-01-29 14:14:04 -0700356 name, extra_args = get_target_compile_tool(name)
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300357 args = tuple(extra_args) + args
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300358 elif for_host:
Simon Glass547cba62022-02-11 13:23:18 -0700359 name, extra_args = get_host_compile_tool(env, name)
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300360 args = tuple(extra_args) + args
Simon Glass6e892242020-11-09 07:45:02 -0700361 name = os.path.expanduser(name) # Expand paths containing ~
Simon Glass9f5051a2019-08-24 07:22:42 -0600362 all_args = (name,) + args
Simon Glass840be732022-01-29 14:14:05 -0700363 result = command.run_pipe([all_args], capture=True, capture_stderr=True,
Simon Glasscc311ac2019-10-31 07:42:50 -0600364 env=env, raise_on_error=False, binary=binary)
Simon Glass9f5051a2019-08-24 07:22:42 -0600365 if result.return_code:
Simon Glass6d493a52022-01-09 20:13:40 -0700366 if raise_on_error:
367 raise ValueError("Error %d running '%s': %s" %
368 (result.return_code,' '.join(all_args),
369 result.stderr or result.stdout))
370 return result
371 except ValueError:
Simon Glass80025522022-01-29 14:14:04 -0700372 if env and not path_has_file(env['PATH'], name):
Simon Glass90c3c4c2019-07-08 13:18:27 -0600373 msg = "Please install tool '%s'" % name
Simon Glass778ab842018-09-14 04:57:25 -0600374 package = packages.get(name)
375 if package:
376 msg += " (e.g. from package '%s')" % package
377 raise ValueError(msg)
378 raise
Simon Glass0362cd22018-07-17 13:25:43 -0600379
Simon Glass835b0e92022-01-09 20:13:43 -0700380def tool_find(name):
381 """Search the current path for a tool
382
Simon Glass80025522022-01-29 14:14:04 -0700383 This uses both PATH and any value from set_tool_paths() to search for a tool
Simon Glass835b0e92022-01-09 20:13:43 -0700384
385 Args:
386 name (str): Name of tool to locate
387
388 Returns:
389 str: Full path to tool if found, else None
390 """
391 name = os.path.expanduser(name) # Expand paths containing ~
392 paths = []
393 pathvar = os.environ.get('PATH')
394 if pathvar:
395 paths = pathvar.split(':')
396 if tool_search_paths:
397 paths += tool_search_paths
398 for path in paths:
399 fname = os.path.join(path, name)
400 if os.path.isfile(fname) and os.access(fname, os.X_OK):
401 return fname
402
Simon Glass80025522022-01-29 14:14:04 -0700403def run(name, *args, **kwargs):
Simon Glass6d493a52022-01-09 20:13:40 -0700404 """Run a tool with some arguments
405
406 This runs a 'tool', which is a program used by binman to process files and
407 perhaps produce some output. Tools can be located on the PATH or in a
408 search path.
409
410 Args:
411 name: Command name to run
412 args: Arguments to the tool
413 for_host: True to resolve the command to the version for the host
414 for_target: False to run the command as-is, without resolving it
415 to the version for the compile target
416
417 Returns:
418 CommandResult object
419 """
420 result = run_result(name, *args, **kwargs)
421 if result is not None:
422 return result.stdout
423
Simon Glass80025522022-01-29 14:14:04 -0700424def filename(fname):
Simon Glass0362cd22018-07-17 13:25:43 -0600425 """Resolve a file path to an absolute path.
426
427 If fname starts with ##/ and chroot is available, ##/ gets replaced with
428 the chroot path. If chroot is not available, this file name can not be
429 resolved, `None' is returned.
430
431 If fname is not prepended with the above prefix, and is not an existing
432 file, the actual file name is retrieved from the passed in string and the
433 search_paths directories (if any) are searched to for the file. If found -
434 the path to the found file is returned, `None' is returned otherwise.
435
436 Args:
437 fname: a string, the path to resolve.
438
439 Returns:
440 Absolute path to the file or None if not found.
441 """
442 if fname.startswith('##/'):
443 if chroot_path:
444 fname = os.path.join(chroot_path, fname[3:])
445 else:
446 return None
447
448 # Search for a pathname that exists, and return it if found
449 if fname and not os.path.exists(fname):
450 for path in search_paths:
451 pathname = os.path.join(path, os.path.basename(fname))
452 if os.path.exists(pathname):
453 return pathname
454
455 # If not found, just return the standard, unchanged path
456 return fname
457
Simon Glass80025522022-01-29 14:14:04 -0700458def read_file(fname, binary=True):
Simon Glass0362cd22018-07-17 13:25:43 -0600459 """Read and return the contents of a file.
460
461 Args:
462 fname: path to filename to read, where ## signifiies the chroot.
463
464 Returns:
465 data read from file, as a string.
466 """
Simon Glass80025522022-01-29 14:14:04 -0700467 with open(filename(fname), binary and 'rb' or 'r') as fd:
Simon Glass0362cd22018-07-17 13:25:43 -0600468 data = fd.read()
469 #self._out.Info("Read file '%s' size %d (%#0x)" %
470 #(fname, len(data), len(data)))
471 return data
472
Simon Glass80025522022-01-29 14:14:04 -0700473def write_file(fname, data, binary=True):
Simon Glass0362cd22018-07-17 13:25:43 -0600474 """Write data into a file.
475
476 Args:
477 fname: path to filename to write
478 data: data to write to file, as a string
479 """
480 #self._out.Info("Write file '%s' size %d (%#0x)" %
481 #(fname, len(data), len(data)))
Simon Glass80025522022-01-29 14:14:04 -0700482 with open(filename(fname), binary and 'wb' or 'w') as fd:
Simon Glass0362cd22018-07-17 13:25:43 -0600483 fd.write(data)
Simon Glassac0d4952019-05-14 15:53:47 -0600484
Simon Glass80025522022-01-29 14:14:04 -0700485def get_bytes(byte, size):
Simon Glassac0d4952019-05-14 15:53:47 -0600486 """Get a string of bytes of a given size
487
Simon Glassac0d4952019-05-14 15:53:47 -0600488 Args:
489 byte: Numeric byte value to use
490 size: Size of bytes/string to return
491
492 Returns:
493 A bytes type with 'byte' repeated 'size' times
494 """
Simon Glass9dfb3112020-11-08 20:36:18 -0700495 return bytes([byte]) * size
Simon Glassd8f593f2019-05-17 22:00:35 -0600496
Simon Glass80025522022-01-29 14:14:04 -0700497def to_bytes(string):
Simon Glass1cd40082019-05-17 22:00:36 -0600498 """Convert a str type into a bytes type
499
500 Args:
Simon Glasscc311ac2019-10-31 07:42:50 -0600501 string: string to convert
Simon Glass1cd40082019-05-17 22:00:36 -0600502
503 Returns:
Simon Glass9dfb3112020-11-08 20:36:18 -0700504 A bytes type
Simon Glass1cd40082019-05-17 22:00:36 -0600505 """
Simon Glass9dfb3112020-11-08 20:36:18 -0700506 return string.encode('utf-8')
Simon Glassdfd19012019-07-08 13:18:41 -0600507
Simon Glass80025522022-01-29 14:14:04 -0700508def to_string(bval):
Simon Glasscc311ac2019-10-31 07:42:50 -0600509 """Convert a bytes type into a str type
510
511 Args:
512 bval: bytes value to convert
513
514 Returns:
515 Python 3: A bytes type
516 Python 2: A string type
517 """
518 return bval.decode('utf-8')
519
Simon Glass80025522022-01-29 14:14:04 -0700520def to_hex(val):
Simon Glassb6dff4c2019-07-20 12:23:36 -0600521 """Convert an integer value (or None) to a string
522
523 Returns:
524 hex value, or 'None' if the value is None
525 """
526 return 'None' if val is None else '%#x' % val
527
Simon Glass80025522022-01-29 14:14:04 -0700528def to_hex_size(val):
Simon Glassb6dff4c2019-07-20 12:23:36 -0600529 """Return the size of an object in hex
530
531 Returns:
532 hex value of size, or 'None' if the value is None
533 """
534 return 'None' if val is None else '%#x' % len(val)
Paul Barker25ecd972021-09-08 12:38:01 +0100535
Simon Glass80025522022-01-29 14:14:04 -0700536def print_full_help(fname):
Paul Barker25ecd972021-09-08 12:38:01 +0100537 """Print the full help message for a tool using an appropriate pager.
538
539 Args:
540 fname: Path to a file containing the full help message
541 """
Paul Barkerce7efa02021-09-08 12:38:02 +0100542 pager = shlex.split(os.getenv('PAGER', ''))
Paul Barker25ecd972021-09-08 12:38:01 +0100543 if not pager:
Paul Barkerce7efa02021-09-08 12:38:02 +0100544 lesspath = shutil.which('less')
545 pager = [lesspath] if lesspath else None
Paul Barker25ecd972021-09-08 12:38:01 +0100546 if not pager:
Paul Barkerce7efa02021-09-08 12:38:02 +0100547 pager = ['more']
Simon Glass840be732022-01-29 14:14:05 -0700548 command.run(*pager, fname)
Simon Glass25885d12022-01-09 20:13:41 -0700549
Simon Glass80025522022-01-29 14:14:04 -0700550def download(url, tmpdir_pattern='.patman'):
Simon Glass25885d12022-01-09 20:13:41 -0700551 """Download a file to a temporary directory
552
553 Args:
Simon Glass91580602022-01-09 20:13:42 -0700554 url (str): URL to download
555 tmpdir_pattern (str): pattern to use for the temporary directory
556
Simon Glass25885d12022-01-09 20:13:41 -0700557 Returns:
558 Tuple:
Simon Glass25885d12022-01-09 20:13:41 -0700559 Full path to the downloaded archive file in that directory,
560 or None if there was an error while downloading
Simon Glass91580602022-01-09 20:13:42 -0700561 Temporary directory name
Simon Glass25885d12022-01-09 20:13:41 -0700562 """
Simon Glass91580602022-01-09 20:13:42 -0700563 print('- downloading: %s' % url)
Simon Glass25885d12022-01-09 20:13:41 -0700564 leaf = url.split('/')[-1]
Simon Glass91580602022-01-09 20:13:42 -0700565 tmpdir = tempfile.mkdtemp(tmpdir_pattern)
Simon Glass25885d12022-01-09 20:13:41 -0700566 response = urllib.request.urlopen(url)
567 fname = os.path.join(tmpdir, leaf)
568 fd = open(fname, 'wb')
569 meta = response.info()
570 size = int(meta.get('Content-Length'))
571 done = 0
572 block_size = 1 << 16
573 status = ''
574
575 # Read the file in chunks and show progress as we go
576 while True:
577 buffer = response.read(block_size)
578 if not buffer:
579 print(chr(8) * (len(status) + 1), '\r', end=' ')
580 break
581
582 done += len(buffer)
583 fd.write(buffer)
584 status = r'%10d MiB [%3d%%]' % (done // 1024 // 1024,
585 done * 100 // size)
586 status = status + chr(8) * (len(status) + 1)
587 print(status, end=' ')
588 sys.stdout.flush()
Simon Glass91580602022-01-09 20:13:42 -0700589 print('\r', end='')
590 sys.stdout.flush()
Simon Glass25885d12022-01-09 20:13:41 -0700591 fd.close()
592 if done != size:
593 print('Error, failed to download')
594 os.remove(fname)
595 fname = None
Simon Glass91580602022-01-09 20:13:42 -0700596 return fname, tmpdir