blob: 0499a75526f1af0d3b989626469ddf3266c3f46a [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 Glass131444f2023-02-23 18:18:04 -070014from u_boot_pylib import command
15from u_boot_pylib 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
Paul HENRYS75203812024-11-25 19:16:53 +0100126def append_input_dirs(dirname):
127 """Append a list of input directories to the current list of input
128 directories
129
130 Args:
131 dirname: a list of paths to input directories to use for obtaining
132 files needed by binman to place in the image.
133 """
134 global indir
135
136 for dir in dirname:
137 if dirname not in indir:
138 indir.append(dirname)
139
140 tout.debug("Updated input directories %s" % indir)
141
Simon Glass80025522022-01-29 14:14:04 -0700142def get_input_filename(fname, allow_missing=False):
Simon Glassbe39f052016-07-25 18:59:08 -0600143 """Return a filename for use as input.
144
145 Args:
146 fname: Filename to use for new file
Simon Glass5d94cc62020-07-09 18:39:38 -0600147 allow_missing: True if the filename can be missing
Simon Glassbe39f052016-07-25 18:59:08 -0600148
149 Returns:
Simon Glasseb751fd2021-03-18 20:25:03 +1300150 fname, if indir is None;
151 full path of the filename, within the input directory;
152 None, if file is missing and allow_missing is True
153
154 Raises:
155 ValueError if file is missing and allow_missing is False
Simon Glassbe39f052016-07-25 18:59:08 -0600156 """
Simon Glass4affd4b2019-08-24 07:22:54 -0600157 if not indir or fname[:1] == '/':
Simon Glassbe39f052016-07-25 18:59:08 -0600158 return fname
159 for dirname in indir:
160 pathname = os.path.join(dirname, fname)
161 if os.path.exists(pathname):
162 return pathname
163
Simon Glass5d94cc62020-07-09 18:39:38 -0600164 if allow_missing:
165 return None
Simon Glassf2eb0542018-07-17 13:25:45 -0600166 raise ValueError("Filename '%s' not found in input path (%s) (cwd='%s')" %
167 (fname, ','.join(indir), os.getcwd()))
Simon Glassbe39f052016-07-25 18:59:08 -0600168
Simon Glass80025522022-01-29 14:14:04 -0700169def get_input_filename_glob(pattern):
Simon Glassac6328c2018-09-14 04:57:28 -0600170 """Return a list of filenames for use as input.
171
172 Args:
173 pattern: Filename pattern to search for
174
175 Returns:
176 A list of matching files in all input directories
177 """
178 if not indir:
Simon Glass547cba62022-02-11 13:23:18 -0700179 return glob.glob(pattern)
Simon Glassac6328c2018-09-14 04:57:28 -0600180 files = []
181 for dirname in indir:
182 pathname = os.path.join(dirname, pattern)
183 files += glob.glob(pathname)
184 return sorted(files)
185
Simon Glass80025522022-01-29 14:14:04 -0700186def align(pos, align):
Simon Glassbe39f052016-07-25 18:59:08 -0600187 if align:
188 mask = align - 1
189 pos = (pos + mask) & ~mask
190 return pos
191
Simon Glass80025522022-01-29 14:14:04 -0700192def not_power_of_two(num):
Simon Glassbe39f052016-07-25 18:59:08 -0600193 return num and (num & (num - 1))
Simon Glass0362cd22018-07-17 13:25:43 -0600194
Simon Glass80025522022-01-29 14:14:04 -0700195def set_tool_paths(toolpaths):
Simon Glass90c3c4c2019-07-08 13:18:27 -0600196 """Set the path to search for tools
197
198 Args:
Simon Glass80025522022-01-29 14:14:04 -0700199 toolpaths: List of paths to search for tools executed by run()
Simon Glass90c3c4c2019-07-08 13:18:27 -0600200 """
201 global tool_search_paths
202
203 tool_search_paths = toolpaths
204
Simon Glass80025522022-01-29 14:14:04 -0700205def path_has_file(path_spec, fname):
Simon Glass778ab842018-09-14 04:57:25 -0600206 """Check if a given filename is in the PATH
207
208 Args:
Simon Glass90c3c4c2019-07-08 13:18:27 -0600209 path_spec: Value of PATH variable to check
Simon Glass778ab842018-09-14 04:57:25 -0600210 fname: Filename to check
211
212 Returns:
213 True if found, False if not
214 """
Simon Glass90c3c4c2019-07-08 13:18:27 -0600215 for dir in path_spec.split(':'):
Simon Glass778ab842018-09-14 04:57:25 -0600216 if os.path.exists(os.path.join(dir, fname)):
217 return True
218 return False
219
Simon Glass547cba62022-02-11 13:23:18 -0700220def get_host_compile_tool(env, name):
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300221 """Get the host-specific version for a compile tool
222
223 This checks the environment variables that specify which version of
224 the tool should be used (e.g. ${HOSTCC}).
225
226 The following table lists the host-specific versions of the tools
227 this function resolves to:
228
229 Compile Tool | Host version
230 --------------+----------------
231 as | ${HOSTAS}
232 ld | ${HOSTLD}
233 cc | ${HOSTCC}
234 cpp | ${HOSTCPP}
235 c++ | ${HOSTCXX}
236 ar | ${HOSTAR}
237 nm | ${HOSTNM}
238 ldr | ${HOSTLDR}
239 strip | ${HOSTSTRIP}
240 objcopy | ${HOSTOBJCOPY}
241 objdump | ${HOSTOBJDUMP}
242 dtc | ${HOSTDTC}
243
244 Args:
245 name: Command name to run
246
247 Returns:
248 host_name: Exact command name to run instead
249 extra_args: List of extra arguments to pass
250 """
251 host_name = None
252 extra_args = []
253 if name in ('as', 'ld', 'cc', 'cpp', 'ar', 'nm', 'ldr', 'strip',
254 'objcopy', 'objdump', 'dtc'):
255 host_name, *host_args = env.get('HOST' + name.upper(), '').split(' ')
256 elif name == 'c++':
257 host_name, *host_args = env.get('HOSTCXX', '').split(' ')
258
259 if host_name:
260 return host_name, extra_args
261 return name, []
262
Simon Glass80025522022-01-29 14:14:04 -0700263def get_target_compile_tool(name, cross_compile=None):
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300264 """Get the target-specific version for a compile tool
265
266 This first checks the environment variables that specify which
267 version of the tool should be used (e.g. ${CC}). If those aren't
268 specified, it checks the CROSS_COMPILE variable as a prefix for the
269 tool with some substitutions (e.g. "${CROSS_COMPILE}gcc" for cc).
270
271 The following table lists the target-specific versions of the tools
272 this function resolves to:
273
274 Compile Tool | First choice | Second choice
275 --------------+----------------+----------------------------
276 as | ${AS} | ${CROSS_COMPILE}as
277 ld | ${LD} | ${CROSS_COMPILE}ld.bfd
278 | | or ${CROSS_COMPILE}ld
279 cc | ${CC} | ${CROSS_COMPILE}gcc
280 cpp | ${CPP} | ${CROSS_COMPILE}gcc -E
281 c++ | ${CXX} | ${CROSS_COMPILE}g++
282 ar | ${AR} | ${CROSS_COMPILE}ar
283 nm | ${NM} | ${CROSS_COMPILE}nm
284 ldr | ${LDR} | ${CROSS_COMPILE}ldr
285 strip | ${STRIP} | ${CROSS_COMPILE}strip
286 objcopy | ${OBJCOPY} | ${CROSS_COMPILE}objcopy
287 objdump | ${OBJDUMP} | ${CROSS_COMPILE}objdump
288 dtc | ${DTC} | (no CROSS_COMPILE version)
289
290 Args:
291 name: Command name to run
292
293 Returns:
294 target_name: Exact command name to run instead
295 extra_args: List of extra arguments to pass
296 """
297 env = dict(os.environ)
298
299 target_name = None
300 extra_args = []
301 if name in ('as', 'ld', 'cc', 'cpp', 'ar', 'nm', 'ldr', 'strip',
302 'objcopy', 'objdump', 'dtc'):
303 target_name, *extra_args = env.get(name.upper(), '').split(' ')
304 elif name == 'c++':
305 target_name, *extra_args = env.get('CXX', '').split(' ')
306
307 if target_name:
308 return target_name, extra_args
309
310 if cross_compile is None:
311 cross_compile = env.get('CROSS_COMPILE', '')
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300312
313 if name in ('as', 'ar', 'nm', 'ldr', 'strip', 'objcopy', 'objdump'):
314 target_name = cross_compile + name
315 elif name == 'ld':
316 try:
Simon Glass80025522022-01-29 14:14:04 -0700317 if run(cross_compile + 'ld.bfd', '-v'):
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300318 target_name = cross_compile + 'ld.bfd'
319 except:
320 target_name = cross_compile + 'ld'
321 elif name == 'cc':
322 target_name = cross_compile + 'gcc'
323 elif name == 'cpp':
324 target_name = cross_compile + 'gcc'
325 extra_args = ['-E']
326 elif name == 'c++':
327 target_name = cross_compile + 'g++'
328 else:
329 target_name = name
330 return target_name, extra_args
331
Simon Glass6d493a52022-01-09 20:13:40 -0700332def get_env_with_path():
333 """Get an updated environment with the PATH variable set correctly
334
335 If there are any search paths set, these need to come first in the PATH so
336 that these override any other version of the tools.
337
338 Returns:
339 dict: New environment with PATH updated, or None if there are not search
340 paths
341 """
342 if tool_search_paths:
343 env = dict(os.environ)
344 env['PATH'] = ':'.join(tool_search_paths) + ':' + env['PATH']
345 return env
346
347def run_result(name, *args, **kwargs):
Simon Glass90c3c4c2019-07-08 13:18:27 -0600348 """Run a tool with some arguments
349
350 This runs a 'tool', which is a program used by binman to process files and
351 perhaps produce some output. Tools can be located on the PATH or in a
352 search path.
353
354 Args:
355 name: Command name to run
356 args: Arguments to the tool
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300357 for_host: True to resolve the command to the version for the host
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300358 for_target: False to run the command as-is, without resolving it
359 to the version for the compile target
Simon Glass6d493a52022-01-09 20:13:40 -0700360 raise_on_error: Raise an error if the command fails (True by default)
Simon Glass90c3c4c2019-07-08 13:18:27 -0600361
362 Returns:
363 CommandResult object
364 """
Simon Glass778ab842018-09-14 04:57:25 -0600365 try:
Simon Glasscc311ac2019-10-31 07:42:50 -0600366 binary = kwargs.get('binary')
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300367 for_host = kwargs.get('for_host', False)
368 for_target = kwargs.get('for_target', not for_host)
Simon Glass6d493a52022-01-09 20:13:40 -0700369 raise_on_error = kwargs.get('raise_on_error', True)
370 env = get_env_with_path()
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300371 if for_target:
Simon Glass80025522022-01-29 14:14:04 -0700372 name, extra_args = get_target_compile_tool(name)
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300373 args = tuple(extra_args) + args
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300374 elif for_host:
Simon Glass547cba62022-02-11 13:23:18 -0700375 name, extra_args = get_host_compile_tool(env, name)
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300376 args = tuple(extra_args) + args
Simon Glass6e892242020-11-09 07:45:02 -0700377 name = os.path.expanduser(name) # Expand paths containing ~
Simon Glass9f5051a2019-08-24 07:22:42 -0600378 all_args = (name,) + args
Simon Glass840be732022-01-29 14:14:05 -0700379 result = command.run_pipe([all_args], capture=True, capture_stderr=True,
Simon Glasscc311ac2019-10-31 07:42:50 -0600380 env=env, raise_on_error=False, binary=binary)
Simon Glass9f5051a2019-08-24 07:22:42 -0600381 if result.return_code:
Simon Glass6d493a52022-01-09 20:13:40 -0700382 if raise_on_error:
383 raise ValueError("Error %d running '%s': %s" %
384 (result.return_code,' '.join(all_args),
385 result.stderr or result.stdout))
386 return result
387 except ValueError:
Simon Glass80025522022-01-29 14:14:04 -0700388 if env and not path_has_file(env['PATH'], name):
Simon Glass90c3c4c2019-07-08 13:18:27 -0600389 msg = "Please install tool '%s'" % name
Simon Glass778ab842018-09-14 04:57:25 -0600390 package = packages.get(name)
391 if package:
392 msg += " (e.g. from package '%s')" % package
393 raise ValueError(msg)
394 raise
Simon Glass0362cd22018-07-17 13:25:43 -0600395
Simon Glass835b0e92022-01-09 20:13:43 -0700396def tool_find(name):
397 """Search the current path for a tool
398
Simon Glass80025522022-01-29 14:14:04 -0700399 This uses both PATH and any value from set_tool_paths() to search for a tool
Simon Glass835b0e92022-01-09 20:13:43 -0700400
401 Args:
402 name (str): Name of tool to locate
403
404 Returns:
405 str: Full path to tool if found, else None
406 """
407 name = os.path.expanduser(name) # Expand paths containing ~
408 paths = []
409 pathvar = os.environ.get('PATH')
410 if pathvar:
411 paths = pathvar.split(':')
412 if tool_search_paths:
413 paths += tool_search_paths
414 for path in paths:
415 fname = os.path.join(path, name)
416 if os.path.isfile(fname) and os.access(fname, os.X_OK):
417 return fname
418
Simon Glass80025522022-01-29 14:14:04 -0700419def run(name, *args, **kwargs):
Simon Glass6d493a52022-01-09 20:13:40 -0700420 """Run a tool with some arguments
421
422 This runs a 'tool', which is a program used by binman to process files and
423 perhaps produce some output. Tools can be located on the PATH or in a
424 search path.
425
426 Args:
427 name: Command name to run
428 args: Arguments to the tool
429 for_host: True to resolve the command to the version for the host
430 for_target: False to run the command as-is, without resolving it
431 to the version for the compile target
432
433 Returns:
434 CommandResult object
435 """
436 result = run_result(name, *args, **kwargs)
437 if result is not None:
438 return result.stdout
439
Simon Glass80025522022-01-29 14:14:04 -0700440def filename(fname):
Simon Glass0362cd22018-07-17 13:25:43 -0600441 """Resolve a file path to an absolute path.
442
443 If fname starts with ##/ and chroot is available, ##/ gets replaced with
444 the chroot path. If chroot is not available, this file name can not be
445 resolved, `None' is returned.
446
447 If fname is not prepended with the above prefix, and is not an existing
448 file, the actual file name is retrieved from the passed in string and the
449 search_paths directories (if any) are searched to for the file. If found -
450 the path to the found file is returned, `None' is returned otherwise.
451
452 Args:
453 fname: a string, the path to resolve.
454
455 Returns:
456 Absolute path to the file or None if not found.
457 """
458 if fname.startswith('##/'):
459 if chroot_path:
460 fname = os.path.join(chroot_path, fname[3:])
461 else:
462 return None
463
464 # Search for a pathname that exists, and return it if found
465 if fname and not os.path.exists(fname):
466 for path in search_paths:
467 pathname = os.path.join(path, os.path.basename(fname))
468 if os.path.exists(pathname):
469 return pathname
470
471 # If not found, just return the standard, unchanged path
472 return fname
473
Simon Glass80025522022-01-29 14:14:04 -0700474def read_file(fname, binary=True):
Simon Glass0362cd22018-07-17 13:25:43 -0600475 """Read and return the contents of a file.
476
477 Args:
478 fname: path to filename to read, where ## signifiies the chroot.
479
480 Returns:
481 data read from file, as a string.
482 """
Simon Glass80025522022-01-29 14:14:04 -0700483 with open(filename(fname), binary and 'rb' or 'r') as fd:
Simon Glass0362cd22018-07-17 13:25:43 -0600484 data = fd.read()
485 #self._out.Info("Read file '%s' size %d (%#0x)" %
486 #(fname, len(data), len(data)))
487 return data
488
Simon Glass80025522022-01-29 14:14:04 -0700489def write_file(fname, data, binary=True):
Simon Glass0362cd22018-07-17 13:25:43 -0600490 """Write data into a file.
491
492 Args:
493 fname: path to filename to write
494 data: data to write to file, as a string
495 """
496 #self._out.Info("Write file '%s' size %d (%#0x)" %
497 #(fname, len(data), len(data)))
Simon Glass80025522022-01-29 14:14:04 -0700498 with open(filename(fname), binary and 'wb' or 'w') as fd:
Simon Glass0362cd22018-07-17 13:25:43 -0600499 fd.write(data)
Simon Glassac0d4952019-05-14 15:53:47 -0600500
Simon Glass80025522022-01-29 14:14:04 -0700501def get_bytes(byte, size):
Simon Glassac0d4952019-05-14 15:53:47 -0600502 """Get a string of bytes of a given size
503
Simon Glassac0d4952019-05-14 15:53:47 -0600504 Args:
505 byte: Numeric byte value to use
506 size: Size of bytes/string to return
507
508 Returns:
509 A bytes type with 'byte' repeated 'size' times
510 """
Simon Glass9dfb3112020-11-08 20:36:18 -0700511 return bytes([byte]) * size
Simon Glassd8f593f2019-05-17 22:00:35 -0600512
Simon Glass80025522022-01-29 14:14:04 -0700513def to_bytes(string):
Simon Glass1cd40082019-05-17 22:00:36 -0600514 """Convert a str type into a bytes type
515
516 Args:
Simon Glasscc311ac2019-10-31 07:42:50 -0600517 string: string to convert
Simon Glass1cd40082019-05-17 22:00:36 -0600518
519 Returns:
Simon Glass9dfb3112020-11-08 20:36:18 -0700520 A bytes type
Simon Glass1cd40082019-05-17 22:00:36 -0600521 """
Simon Glass9dfb3112020-11-08 20:36:18 -0700522 return string.encode('utf-8')
Simon Glassdfd19012019-07-08 13:18:41 -0600523
Simon Glass80025522022-01-29 14:14:04 -0700524def to_string(bval):
Simon Glasscc311ac2019-10-31 07:42:50 -0600525 """Convert a bytes type into a str type
526
527 Args:
528 bval: bytes value to convert
529
530 Returns:
531 Python 3: A bytes type
532 Python 2: A string type
533 """
534 return bval.decode('utf-8')
535
Simon Glass80025522022-01-29 14:14:04 -0700536def to_hex(val):
Simon Glassb6dff4c2019-07-20 12:23:36 -0600537 """Convert an integer value (or None) to a string
538
539 Returns:
540 hex value, or 'None' if the value is None
541 """
542 return 'None' if val is None else '%#x' % val
543
Simon Glass80025522022-01-29 14:14:04 -0700544def to_hex_size(val):
Simon Glassb6dff4c2019-07-20 12:23:36 -0600545 """Return the size of an object in hex
546
547 Returns:
548 hex value of size, or 'None' if the value is None
549 """
550 return 'None' if val is None else '%#x' % len(val)
Paul Barker25ecd972021-09-08 12:38:01 +0100551
Simon Glass80025522022-01-29 14:14:04 -0700552def print_full_help(fname):
Paul Barker25ecd972021-09-08 12:38:01 +0100553 """Print the full help message for a tool using an appropriate pager.
554
555 Args:
556 fname: Path to a file containing the full help message
557 """
Paul Barkerce7efa02021-09-08 12:38:02 +0100558 pager = shlex.split(os.getenv('PAGER', ''))
Paul Barker25ecd972021-09-08 12:38:01 +0100559 if not pager:
Paul Barkerce7efa02021-09-08 12:38:02 +0100560 lesspath = shutil.which('less')
561 pager = [lesspath] if lesspath else None
Paul Barker25ecd972021-09-08 12:38:01 +0100562 if not pager:
Paul Barkerce7efa02021-09-08 12:38:02 +0100563 pager = ['more']
Simon Glass840be732022-01-29 14:14:05 -0700564 command.run(*pager, fname)
Simon Glass25885d12022-01-09 20:13:41 -0700565
Simon Glass80025522022-01-29 14:14:04 -0700566def download(url, tmpdir_pattern='.patman'):
Simon Glass25885d12022-01-09 20:13:41 -0700567 """Download a file to a temporary directory
568
569 Args:
Simon Glass91580602022-01-09 20:13:42 -0700570 url (str): URL to download
571 tmpdir_pattern (str): pattern to use for the temporary directory
572
Simon Glass25885d12022-01-09 20:13:41 -0700573 Returns:
574 Tuple:
Simon Glass25885d12022-01-09 20:13:41 -0700575 Full path to the downloaded archive file in that directory,
576 or None if there was an error while downloading
Simon Glass91580602022-01-09 20:13:42 -0700577 Temporary directory name
Simon Glass25885d12022-01-09 20:13:41 -0700578 """
Simon Glass91580602022-01-09 20:13:42 -0700579 print('- downloading: %s' % url)
Simon Glass25885d12022-01-09 20:13:41 -0700580 leaf = url.split('/')[-1]
Simon Glass91580602022-01-09 20:13:42 -0700581 tmpdir = tempfile.mkdtemp(tmpdir_pattern)
Simon Glass25885d12022-01-09 20:13:41 -0700582 response = urllib.request.urlopen(url)
583 fname = os.path.join(tmpdir, leaf)
584 fd = open(fname, 'wb')
585 meta = response.info()
586 size = int(meta.get('Content-Length'))
587 done = 0
588 block_size = 1 << 16
589 status = ''
590
591 # Read the file in chunks and show progress as we go
592 while True:
593 buffer = response.read(block_size)
594 if not buffer:
595 print(chr(8) * (len(status) + 1), '\r', end=' ')
596 break
597
598 done += len(buffer)
599 fd.write(buffer)
600 status = r'%10d MiB [%3d%%]' % (done // 1024 // 1024,
601 done * 100 // size)
602 status = status + chr(8) * (len(status) + 1)
603 print(status, end=' ')
604 sys.stdout.flush()
Simon Glass91580602022-01-09 20:13:42 -0700605 print('\r', end='')
606 sys.stdout.flush()
Simon Glass25885d12022-01-09 20:13:41 -0700607 fd.close()
608 if done != size:
609 print('Error, failed to download')
610 os.remove(fname)
611 fname = None
Simon Glass91580602022-01-09 20:13:42 -0700612 return fname, tmpdir