X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=tools%2Fpatman%2Ftools.py;h=05b1a1d4b08541c83b9d9e43835692beaa09a9a9;hb=5a1a8a63be8f7262a300eddafb18020926b12fb6;hp=bf099798e65006fcae290cc0cc95ae0244d78bd3;hpb=1fda18205ff425d00faad57dc2ad69995cdbd15d;p=platform%2Fkernel%2Fu-boot.git diff --git a/tools/patman/tools.py b/tools/patman/tools.py index bf09979..05b1a1d 100644 --- a/tools/patman/tools.py +++ b/tools/patman/tools.py @@ -3,13 +3,15 @@ # Copyright (c) 2016 Google, Inc # -import command import glob import os import shutil +import struct +import sys import tempfile -import tout +from patman import command +from patman import tout # Output directly (generally this is temporary) outdir = None @@ -23,6 +25,8 @@ chroot_path = None # Search paths to use for Filename(), used to find files search_paths = [] +tool_search_paths = [] + # Tools and the packages that contain them, on debian packages = { 'lz4': 'liblz4-tool', @@ -77,6 +81,7 @@ def FinaliseOutputDir(): """Tidy up: delete output directory if temporary and not preserved.""" if outdir and not preserve_outdir: _RemoveOutputDir() + outdir = None def GetOutputFilename(fname): """Return a filename within the output directory. @@ -95,6 +100,7 @@ def _FinaliseForTest(): if outdir: _RemoveOutputDir() + outdir = None def SetInputDirs(dirname): """Add a list of input directories, where input files are kept. @@ -108,22 +114,26 @@ def SetInputDirs(dirname): indir = dirname tout.Debug("Using input directories %s" % indir) -def GetInputFilename(fname): +def GetInputFilename(fname, allow_missing=False): """Return a filename for use as input. Args: fname: Filename to use for new file + allow_missing: True if the filename can be missing Returns: - The full path of the filename, within the input directory + The full path of the filename, within the input directory, or + None on error """ - if not indir: + if not indir or fname[:1] == '/': return fname for dirname in indir: pathname = os.path.join(dirname, fname) if os.path.exists(pathname): return pathname + if allow_missing: + return None raise ValueError("Filename '%s' not found in input path (%s) (cwd='%s')" % (fname, ','.join(indir), os.getcwd())) @@ -153,26 +163,188 @@ def Align(pos, align): def NotPowerOfTwo(num): return num and (num & (num - 1)) -def PathHasFile(fname): +def SetToolPaths(toolpaths): + """Set the path to search for tools + + Args: + toolpaths: List of paths to search for tools executed by Run() + """ + global tool_search_paths + + tool_search_paths = toolpaths + +def PathHasFile(path_spec, fname): """Check if a given filename is in the PATH Args: + path_spec: Value of PATH variable to check fname: Filename to check Returns: True if found, False if not """ - for dir in os.environ['PATH'].split(':'): + for dir in path_spec.split(':'): if os.path.exists(os.path.join(dir, fname)): return True return False -def Run(name, *args): +def GetHostCompileTool(name): + """Get the host-specific version for a compile tool + + This checks the environment variables that specify which version of + the tool should be used (e.g. ${HOSTCC}). + + The following table lists the host-specific versions of the tools + this function resolves to: + + Compile Tool | Host version + --------------+---------------- + as | ${HOSTAS} + ld | ${HOSTLD} + cc | ${HOSTCC} + cpp | ${HOSTCPP} + c++ | ${HOSTCXX} + ar | ${HOSTAR} + nm | ${HOSTNM} + ldr | ${HOSTLDR} + strip | ${HOSTSTRIP} + objcopy | ${HOSTOBJCOPY} + objdump | ${HOSTOBJDUMP} + dtc | ${HOSTDTC} + + Args: + name: Command name to run + + Returns: + host_name: Exact command name to run instead + extra_args: List of extra arguments to pass + """ + host_name = None + extra_args = [] + if name in ('as', 'ld', 'cc', 'cpp', 'ar', 'nm', 'ldr', 'strip', + 'objcopy', 'objdump', 'dtc'): + host_name, *host_args = env.get('HOST' + name.upper(), '').split(' ') + elif name == 'c++': + host_name, *host_args = env.get('HOSTCXX', '').split(' ') + + if host_name: + return host_name, extra_args + return name, [] + +def GetTargetCompileTool(name, cross_compile=None): + """Get the target-specific version for a compile tool + + This first checks the environment variables that specify which + version of the tool should be used (e.g. ${CC}). If those aren't + specified, it checks the CROSS_COMPILE variable as a prefix for the + tool with some substitutions (e.g. "${CROSS_COMPILE}gcc" for cc). + + The following table lists the target-specific versions of the tools + this function resolves to: + + Compile Tool | First choice | Second choice + --------------+----------------+---------------------------- + as | ${AS} | ${CROSS_COMPILE}as + ld | ${LD} | ${CROSS_COMPILE}ld.bfd + | | or ${CROSS_COMPILE}ld + cc | ${CC} | ${CROSS_COMPILE}gcc + cpp | ${CPP} | ${CROSS_COMPILE}gcc -E + c++ | ${CXX} | ${CROSS_COMPILE}g++ + ar | ${AR} | ${CROSS_COMPILE}ar + nm | ${NM} | ${CROSS_COMPILE}nm + ldr | ${LDR} | ${CROSS_COMPILE}ldr + strip | ${STRIP} | ${CROSS_COMPILE}strip + objcopy | ${OBJCOPY} | ${CROSS_COMPILE}objcopy + objdump | ${OBJDUMP} | ${CROSS_COMPILE}objdump + dtc | ${DTC} | (no CROSS_COMPILE version) + + Args: + name: Command name to run + + Returns: + target_name: Exact command name to run instead + extra_args: List of extra arguments to pass + """ + env = dict(os.environ) + + target_name = None + extra_args = [] + if name in ('as', 'ld', 'cc', 'cpp', 'ar', 'nm', 'ldr', 'strip', + 'objcopy', 'objdump', 'dtc'): + target_name, *extra_args = env.get(name.upper(), '').split(' ') + elif name == 'c++': + target_name, *extra_args = env.get('CXX', '').split(' ') + + if target_name: + return target_name, extra_args + + if cross_compile is None: + cross_compile = env.get('CROSS_COMPILE', '') + if not cross_compile: + return name, [] + + if name in ('as', 'ar', 'nm', 'ldr', 'strip', 'objcopy', 'objdump'): + target_name = cross_compile + name + elif name == 'ld': + try: + if Run(cross_compile + 'ld.bfd', '-v'): + target_name = cross_compile + 'ld.bfd' + except: + target_name = cross_compile + 'ld' + elif name == 'cc': + target_name = cross_compile + 'gcc' + elif name == 'cpp': + target_name = cross_compile + 'gcc' + extra_args = ['-E'] + elif name == 'c++': + target_name = cross_compile + 'g++' + else: + target_name = name + return target_name, extra_args + +def Run(name, *args, **kwargs): + """Run a tool with some arguments + + This runs a 'tool', which is a program used by binman to process files and + perhaps produce some output. Tools can be located on the PATH or in a + search path. + + Args: + name: Command name to run + args: Arguments to the tool + for_host: True to resolve the command to the version for the host + for_target: False to run the command as-is, without resolving it + to the version for the compile target + + Returns: + CommandResult object + """ try: - return command.Run(name, *args, cwd=outdir, capture=True) + binary = kwargs.get('binary') + for_host = kwargs.get('for_host', False) + for_target = kwargs.get('for_target', not for_host) + env = None + if tool_search_paths: + env = dict(os.environ) + env['PATH'] = ':'.join(tool_search_paths) + ':' + env['PATH'] + if for_target: + name, extra_args = GetTargetCompileTool(name) + args = tuple(extra_args) + args + elif for_host: + name, extra_args = GetHostCompileTool(name) + args = tuple(extra_args) + args + name = os.path.expanduser(name) # Expand paths containing ~ + all_args = (name,) + args + result = command.RunPipe([all_args], capture=True, capture_stderr=True, + env=env, raise_on_error=False, binary=binary) + if result.return_code: + raise Exception("Error %d running '%s': %s" % + (result.return_code,' '.join(all_args), + result.stderr)) + return result.stdout except: - if not PathHasFile(name): - msg = "Plesae install tool '%s'" % name + if env and not PathHasFile(env['PATH'], name): + msg = "Please install tool '%s'" % name package = packages.get(name) if package: msg += " (e.g. from package '%s')" % package @@ -213,7 +385,7 @@ def Filename(fname): # If not found, just return the standard, unchanged path return fname -def ReadFile(fname): +def ReadFile(fname, binary=True): """Read and return the contents of a file. Args: @@ -222,13 +394,13 @@ def ReadFile(fname): Returns: data read from file, as a string. """ - with open(Filename(fname), 'rb') as fd: + with open(Filename(fname), binary and 'rb' or 'r') as fd: data = fd.read() #self._out.Info("Read file '%s' size %d (%#0x)" % #(fname, len(data), len(data))) return data -def WriteFile(fname, data): +def WriteFile(fname, data, binary=True): """Write data into a file. Args: @@ -237,5 +409,238 @@ def WriteFile(fname, data): """ #self._out.Info("Write file '%s' size %d (%#0x)" % #(fname, len(data), len(data))) - with open(Filename(fname), 'wb') as fd: + with open(Filename(fname), binary and 'wb' or 'w') as fd: fd.write(data) + +def GetBytes(byte, size): + """Get a string of bytes of a given size + + This handles the unfortunate different between Python 2 and Python 2. + + Args: + byte: Numeric byte value to use + size: Size of bytes/string to return + + Returns: + A bytes type with 'byte' repeated 'size' times + """ + if sys.version_info[0] >= 3: + data = bytes([byte]) * size + else: + data = chr(byte) * size + return data + +def ToUnicode(val): + """Make sure a value is a unicode string + + This allows some amount of compatibility between Python 2 and Python3. For + the former, it returns a unicode object. + + Args: + val: string or unicode object + + Returns: + unicode version of val + """ + if sys.version_info[0] >= 3: + return val + return val if isinstance(val, unicode) else val.decode('utf-8') + +def FromUnicode(val): + """Make sure a value is a non-unicode string + + This allows some amount of compatibility between Python 2 and Python3. For + the former, it converts a unicode object to a string. + + Args: + val: string or unicode object + + Returns: + non-unicode version of val + """ + if sys.version_info[0] >= 3: + return val + return val if isinstance(val, str) else val.encode('utf-8') + +def ToByte(ch): + """Convert a character to an ASCII value + + This is useful because in Python 2 bytes is an alias for str, but in + Python 3 they are separate types. This function converts the argument to + an ASCII value in either case. + + Args: + ch: A string (Python 2) or byte (Python 3) value + + Returns: + integer ASCII value for ch + """ + return ord(ch) if type(ch) == str else ch + +def ToChar(byte): + """Convert a byte to a character + + This is useful because in Python 2 bytes is an alias for str, but in + Python 3 they are separate types. This function converts an ASCII value to + a value with the appropriate type in either case. + + Args: + byte: A byte or str value + """ + return chr(byte) if type(byte) != str else byte + +def ToChars(byte_list): + """Convert a list of bytes to a str/bytes type + + Args: + byte_list: List of ASCII values representing the string + + Returns: + string made by concatenating all the ASCII values + """ + return ''.join([chr(byte) for byte in byte_list]) + +def ToBytes(string): + """Convert a str type into a bytes type + + Args: + string: string to convert + + Returns: + Python 3: A bytes type + Python 2: A string type + """ + if sys.version_info[0] >= 3: + return string.encode('utf-8') + return string + +def ToString(bval): + """Convert a bytes type into a str type + + Args: + bval: bytes value to convert + + Returns: + Python 3: A bytes type + Python 2: A string type + """ + return bval.decode('utf-8') + +def Compress(indata, algo, with_header=True): + """Compress some data using a given algorithm + + Note that for lzma this uses an old version of the algorithm, not that + provided by xz. + + This requires 'lz4' and 'lzma_alone' tools. It also requires an output + directory to be previously set up, by calling PrepareOutputDir(). + + Args: + indata: Input data to compress + algo: Algorithm to use ('none', 'gzip', 'lz4' or 'lzma') + + Returns: + Compressed data + """ + if algo == 'none': + return indata + fname = GetOutputFilename('%s.comp.tmp' % algo) + WriteFile(fname, indata) + if algo == 'lz4': + data = Run('lz4', '--no-frame-crc', '-c', fname, binary=True) + # cbfstool uses a very old version of lzma + elif algo == 'lzma': + outfname = GetOutputFilename('%s.comp.otmp' % algo) + Run('lzma_alone', 'e', fname, outfname, '-lc1', '-lp0', '-pb0', '-d8') + data = ReadFile(outfname) + elif algo == 'gzip': + data = Run('gzip', '-c', fname, binary=True) + else: + raise ValueError("Unknown algorithm '%s'" % algo) + if with_header: + hdr = struct.pack('