Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
utils.py
00001 """ 00002 mbed SDK 00003 Copyright (c) 2011-2013 ARM Limited 00004 00005 Licensed under the Apache License, Version 2.0 (the "License"); 00006 you may not use this file except in compliance with the License. 00007 You may obtain a copy of the License at 00008 00009 http://www.apache.org/licenses/LICENSE-2.0 00010 00011 Unless required by applicable law or agreed to in writing, software 00012 distributed under the License is distributed on an "AS IS" BASIS, 00013 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 00014 See the License for the specific language governing permissions and 00015 limitations under the License. 00016 """ 00017 from __future__ import print_function, division, absolute_import 00018 import sys 00019 import inspect 00020 import os 00021 import argparse 00022 import math 00023 from os import listdir, remove, makedirs 00024 from shutil import copyfile 00025 from os.path import isdir, join, exists, split, relpath, splitext, abspath 00026 from os.path import commonprefix, normpath, dirname 00027 from subprocess import Popen, PIPE, STDOUT, call 00028 from math import ceil 00029 import json 00030 from collections import OrderedDict 00031 import logging 00032 from intelhex import IntelHex 00033 00034 try: 00035 unicode 00036 except NameError: 00037 unicode = str 00038 00039 def remove_if_in(lst, thing): 00040 if thing in lst: 00041 lst.remove(thing) 00042 00043 def compile_worker (job): 00044 """Standard task runner used for compiling 00045 00046 Positional argumets: 00047 job - a dict containing a list of commands and the remaining arguments 00048 to run_cmd 00049 """ 00050 results = [] 00051 for command in job['commands']: 00052 try: 00053 _, _stderr, _rc = run_cmd(command, work_dir=job['work_dir'], 00054 chroot=job['chroot']) 00055 except KeyboardInterrupt: 00056 raise ToolException 00057 00058 results.append({ 00059 'code': _rc, 00060 'output': _stderr, 00061 'command': command 00062 }) 00063 00064 return { 00065 'source': job['source'], 00066 'object': job['object'], 00067 'commands': job['commands'], 00068 'results': results 00069 } 00070 00071 def cmd (command, check=True, verbose=False, shell=False, cwd=None): 00072 """A wrapper to run a command as a blocking job""" 00073 text = command if shell else ' '.join(command) 00074 if verbose: 00075 print(text) 00076 return_code = call(command, shell=shell, cwd=cwd) 00077 if check and return_code != 0: 00078 raise Exception('ERROR %d: "%s"' % (return_code, text)) 00079 00080 00081 def run_cmd (command, work_dir=None, chroot=None, redirect=False): 00082 """Run a command in the foreground 00083 00084 Positional arguments: 00085 command - the command to run 00086 00087 Keyword arguments: 00088 work_dir - the working directory to run the command in 00089 chroot - the chroot to run the command in 00090 redirect - redirect the stderr to a pipe to be used later 00091 """ 00092 if chroot: 00093 # Conventions managed by the web team for the mbed.org build system 00094 chroot_cmd = [ 00095 '/usr/sbin/chroot', '--userspec=33:33', chroot 00096 ] 00097 for element in command: 00098 chroot_cmd += [element.replace(chroot, '')] 00099 00100 logging.debug("Running command %s", ' '.join(chroot_cmd)) 00101 command = chroot_cmd 00102 work_dir = None 00103 00104 try: 00105 process = Popen(command, stdout=PIPE, 00106 stderr=STDOUT if redirect else PIPE, cwd=work_dir) 00107 _stdout, _stderr = process.communicate() 00108 except OSError: 00109 print("[OS ERROR] Command: "+(' '.join(command))) 00110 raise 00111 00112 return _stdout, _stderr, process.returncode 00113 00114 00115 def run_cmd_ext (command): 00116 """ A version of run command that checks if the command exists befor running 00117 00118 Positional arguments: 00119 command - the command line you are trying to invoke 00120 """ 00121 assert is_cmd_valid(command[0]) 00122 process = Popen(command, stdout=PIPE, stderr=PIPE) 00123 _stdout, _stderr = process.communicate() 00124 return _stdout, _stderr, process.returncode 00125 00126 00127 def is_cmd_valid (command): 00128 """ Verify that a command exists and is executable 00129 00130 Positional arguments: 00131 command - the command to check 00132 """ 00133 caller = get_caller_name() 00134 cmd_path = find_cmd_abspath(command) 00135 if not cmd_path: 00136 error("%s: Command '%s' can't be found" % (caller, command)) 00137 if not is_exec(cmd_path): 00138 error("%s: Command '%s' resolves to file '%s' which is not executable" 00139 % (caller, command, cmd_path)) 00140 return True 00141 00142 00143 def is_exec (path): 00144 """A simple check to verify that a path to an executable exists 00145 00146 Positional arguments: 00147 path - the executable 00148 """ 00149 return os.access(path, os.X_OK) or os.access(path+'.exe', os.X_OK) 00150 00151 00152 def find_cmd_abspath (command): 00153 """ Returns the absolute path to a command. 00154 None is returned if no absolute path was found. 00155 00156 Positional arguhments: 00157 command - the command to find the path of 00158 """ 00159 if exists(command) or exists(command + '.exe'): 00160 return os.path.abspath(command) 00161 if not 'PATH' in os.environ: 00162 raise Exception("Can't find command path for current platform ('%s')" 00163 % sys.platform) 00164 path_env = os.environ['PATH'] 00165 for path in path_env.split(os.pathsep): 00166 cmd_path = '%s/%s' % (path, command) 00167 if exists(cmd_path) or exists(cmd_path + '.exe'): 00168 return cmd_path 00169 00170 00171 def mkdir (path): 00172 """ a wrapped makedirs that only tries to create a directory if it does not 00173 exist already 00174 00175 Positional arguments: 00176 path - the path to maybe create 00177 """ 00178 if not exists(path): 00179 makedirs(path) 00180 00181 00182 def copy_file (src, dst): 00183 """ Implement the behaviour of "shutil.copy(src, dst)" without copying the 00184 permissions (this was causing errors with directories mounted with samba) 00185 00186 Positional arguments: 00187 src - the source of the copy operation 00188 dst - the destination of the copy operation 00189 """ 00190 if isdir(dst): 00191 _, base = split(src) 00192 dst = join(dst, base) 00193 copyfile(src, dst) 00194 00195 00196 def delete_dir_files (directory): 00197 """ A function that does rm -rf 00198 00199 Positional arguments: 00200 directory - the directory to remove 00201 """ 00202 if not exists(directory): 00203 return 00204 00205 for element in listdir(directory): 00206 to_remove = join(directory, element) 00207 if not isdir(to_remove): 00208 remove(to_remove) 00209 00210 00211 def get_caller_name (steps=2): 00212 """ 00213 When called inside a function, it returns the name 00214 of the caller of that function. 00215 00216 Keyword arguments: 00217 steps - the number of steps up the stack the calling function is 00218 """ 00219 return inspect.stack()[steps][3] 00220 00221 00222 def error (msg): 00223 """Fatal error, abort hard 00224 00225 Positional arguments: 00226 msg - the message to print before crashing 00227 """ 00228 print("ERROR: %s" % msg) 00229 sys.exit(1) 00230 00231 00232 def rel_path (path, base, dot=False): 00233 """Relative path calculation that optionaly always starts with a dot 00234 00235 Positional arguments: 00236 path - the path to make relative 00237 base - what to make the path relative to 00238 00239 Keyword arguments: 00240 dot - if True, the path will always start with a './' 00241 """ 00242 final_path = relpath(path, base) 00243 if dot and not final_path.startswith('.'): 00244 final_path = './' + final_path 00245 return final_path 00246 00247 00248 class ToolException (Exception): 00249 """A class representing an exception throw by the tools""" 00250 pass 00251 00252 class NotSupportedException(Exception): 00253 """A class a toolchain not supporting a particular target""" 00254 pass 00255 00256 class InvalidReleaseTargetException(Exception): 00257 pass 00258 00259 def split_path (path): 00260 """spilt a file name into it's directory name, base name, and extension 00261 00262 Positional arguments: 00263 path - the file name to split 00264 """ 00265 base, has_ext = split(path) 00266 name, ext = splitext(has_ext) 00267 return base, name, ext 00268 00269 00270 def get_path_depth (path): 00271 """ Given a path, return the number of directory levels present. 00272 This roughly translates to the number of path separators (os.sep) + 1. 00273 Ex. Given "path/to/dir", this would return 3 00274 Special cases: "." and "/" return 0 00275 00276 Positional arguments: 00277 path - the path to calculate the depth of 00278 """ 00279 normalized_path = normpath(path) 00280 path_depth = 0 00281 head, tail = split(normalized_path) 00282 00283 while tail and tail != '.': 00284 path_depth += 1 00285 head, tail = split(head) 00286 00287 return path_depth 00288 00289 00290 def args_error (parser, message): 00291 """Abort with an error that was generated by the arguments to a CLI program 00292 00293 Positional arguments: 00294 parser - the ArgumentParser object that parsed the command line 00295 message - what went wrong 00296 """ 00297 parser.error(message) 00298 sys.exit(2) 00299 00300 00301 def construct_enum (**enums): 00302 """ Create your own pseudo-enums 00303 00304 Keyword arguments: 00305 * - a member of the Enum you are creating and it's value 00306 """ 00307 return type('Enum', (), enums) 00308 00309 00310 def check_required_modules (required_modules, verbose=True): 00311 """ Function checks for Python modules which should be "importable" 00312 before test suite can be used. 00313 @return returns True if all modules are installed already 00314 """ 00315 import imp 00316 not_installed_modules = [] 00317 for module_name in required_modules: 00318 try: 00319 imp.find_module(module_name) 00320 except ImportError: 00321 # We also test against a rare case: module is an egg file 00322 try: 00323 __import__(module_name) 00324 except ImportError as exc: 00325 not_installed_modules.append(module_name) 00326 if verbose: 00327 print("Error: %s" % exc) 00328 00329 if verbose: 00330 if not_installed_modules: 00331 print("Warning: Module(s) %s not installed. Please install " 00332 "required module(s) before using this script." 00333 % (', '.join(not_installed_modules))) 00334 00335 if not_installed_modules: 00336 return False 00337 else: 00338 return True 00339 00340 def json_file_to_dict (fname): 00341 """ Read a JSON file and return its Python representation, transforming all 00342 the strings from Unicode to ASCII. The order of keys in the JSON file is 00343 preserved. 00344 00345 Positional arguments: 00346 fname - the name of the file to parse 00347 """ 00348 try: 00349 with open(fname, "r") as file_obj: 00350 return json.loads(file_obj.read().encode('ascii', 'ignore'), 00351 object_pairs_hook=OrderedDict) 00352 except (ValueError, IOError): 00353 sys.stderr.write("Error parsing '%s':\n" % fname) 00354 raise 00355 00356 # Wowza, double closure 00357 def argparse_type(casedness, prefer_hyphen=False): 00358 def middle(lst, type_name): 00359 def parse_type(string): 00360 """ validate that an argument passed in (as string) is a member of 00361 the list of possible arguments. Offer a suggestion if the case of 00362 the string, or the hyphens/underscores do not match the expected 00363 style of the argument. 00364 """ 00365 if not isinstance(string, unicode): 00366 string = string.decode() 00367 if prefer_hyphen: 00368 newstring = casedness(string).replace("_", "-") 00369 else: 00370 newstring = casedness(string).replace("-", "_") 00371 if string in lst: 00372 return string 00373 elif string not in lst and newstring in lst: 00374 raise argparse.ArgumentTypeError( 00375 "{0} is not a supported {1}. Did you mean {2}?".format( 00376 string, type_name, newstring)) 00377 else: 00378 raise argparse.ArgumentTypeError( 00379 "{0} is not a supported {1}. Supported {1}s are:\n{2}". 00380 format(string, type_name, columnate(lst))) 00381 return parse_type 00382 return middle 00383 00384 # short cuts for the argparse_type versions 00385 argparse_uppercase_type = argparse_type(unicode.upper, False) 00386 argparse_lowercase_type = argparse_type(unicode.lower, False) 00387 argparse_uppercase_hyphen_type = argparse_type(unicode.upper, True) 00388 argparse_lowercase_hyphen_type = argparse_type(unicode.lower, True) 00389 00390 def argparse_force_type (case): 00391 """ validate that an argument passed in (as string) is a member of the list 00392 of possible arguments after converting it's case. 00393 """ 00394 def middle(lst, type_name): 00395 """ The parser type generator""" 00396 if not isinstance(lst[0], unicode): 00397 lst = [o.decode() for o in lst] 00398 def parse_type(string): 00399 """ The parser type""" 00400 if not isinstance(string, unicode): 00401 string = string.decode() 00402 for option in lst: 00403 if case(string) == case(option): 00404 return option 00405 raise argparse.ArgumentTypeError( 00406 "{0} is not a supported {1}. Supported {1}s are:\n{2}". 00407 format(string, type_name, columnate(lst))) 00408 return parse_type 00409 return middle 00410 00411 # these two types convert the case of their arguments _before_ validation 00412 argparse_force_uppercase_type = argparse_force_type(unicode.upper) 00413 argparse_force_lowercase_type = argparse_force_type(unicode.lower) 00414 00415 def argparse_many (func): 00416 """ An argument parser combinator that takes in an argument parser and 00417 creates a new parser that accepts a comma separated list of the same thing. 00418 """ 00419 def wrap(string): 00420 """ The actual parser""" 00421 return [func(s) for s in string.split(",")] 00422 return wrap 00423 00424 def argparse_filestring_type (string): 00425 """ An argument parser that verifies that a string passed in corresponds 00426 to a file""" 00427 if exists(string): 00428 return string 00429 else: 00430 raise argparse.ArgumentTypeError( 00431 "{0}"" does not exist in the filesystem.".format(string)) 00432 00433 def argparse_profile_filestring_type (string): 00434 """ An argument parser that verifies that a string passed in is either 00435 absolute path or a file name (expanded to 00436 mbed-os/tools/profiles/<fname>.json) of a existing file""" 00437 fpath = join(dirname(__file__), "profiles/{}.json".format(string)) 00438 if exists(string): 00439 return string 00440 elif exists(fpath): 00441 return fpath 00442 else: 00443 raise argparse.ArgumentTypeError( 00444 "{0} does not exist in the filesystem.".format(string)) 00445 00446 def columnate (strings, separator=", ", chars=80): 00447 """ render a list of strings as a in a bunch of columns 00448 00449 Positional arguments: 00450 strings - the strings to columnate 00451 00452 Keyword arguments; 00453 separator - the separation between the columns 00454 chars - the maximum with of a row 00455 """ 00456 col_width = max(len(s) for s in strings) 00457 total_width = col_width + len(separator) 00458 columns = math.floor(chars / total_width) 00459 output = "" 00460 for i, string in zip(range(len(strings)), strings): 00461 append = string 00462 if i != len(strings) - 1: 00463 append += separator 00464 if i % columns == columns - 1: 00465 append += "\n" 00466 else: 00467 append = append.ljust(total_width) 00468 output += append 00469 return output 00470 00471 def argparse_dir_not_parent (other): 00472 """fail if argument provided is a parent of the specified directory""" 00473 def parse_type(not_parent): 00474 """The parser type""" 00475 abs_other = abspath(other) 00476 abs_not_parent = abspath(not_parent) 00477 if abs_not_parent == commonprefix([abs_not_parent, abs_other]): 00478 raise argparse.ArgumentTypeError( 00479 "{0} may not be a parent directory of {1}".format( 00480 not_parent, other)) 00481 else: 00482 return not_parent 00483 return parse_type 00484 00485 def argparse_deprecate (replacement_message): 00486 """fail if argument is provided with deprecation warning""" 00487 def parse_type(_): 00488 """The parser type""" 00489 raise argparse.ArgumentTypeError("Deprecated." + replacement_message) 00490 return parse_type 00491 00492 def print_large_string (large_string): 00493 """ Breaks a string up into smaller pieces before print them 00494 00495 This is a limitation within Windows, as detailed here: 00496 https://bugs.python.org/issue11395 00497 00498 Positional arguments: 00499 large_string - the large string to print 00500 """ 00501 string_limit = 1000 00502 large_string_len = len(large_string) 00503 num_parts = int(ceil(float(large_string_len) / float(string_limit))) 00504 for string_part in range(num_parts): 00505 start_index = string_part * string_limit 00506 if string_part == num_parts - 1: 00507 sys.stdout.write(large_string[start_index:]) 00508 else: 00509 sys.stdout.write(large_string[start_index: 00510 start_index + string_limit]) 00511 sys.stdout.write("\n") 00512 00513 def intelhex_offset (filename, offset): 00514 """Load a hex or bin file at a particular offset""" 00515 _, inteltype = splitext(filename) 00516 ih = IntelHex() 00517 if inteltype == ".bin": 00518 ih.loadbin(filename, offset=offset) 00519 elif inteltype == ".hex": 00520 ih.loadhex(filename) 00521 else: 00522 raise ToolException("File %s does not have a known binary file type" 00523 % filename) 00524 return ih 00525 00526 00527 def integer (maybe_string, base): 00528 """Make an integer of a number or a string""" 00529 if isinstance(maybe_string, int): 00530 return maybe_string 00531 else: 00532 return int(maybe_string, base)
Generated on Tue Jul 12 2022 14:25:22 by
