Source code for pyIPCMI.Base.Executable

# EMACS settings: -*-	tab-width: 2; indent-tabs-mode: t; python-indent-offset: 2 -*-
# vim: tabstop=2:shiftwidth=2:noexpandtab
# kate: tab-width 2; replace-tabs off; indent-width 2;
#
# ==============================================================================
# Authors:          Patrick Lehmann
#
# Python Module:    Basic abstraction layer for executables.
#
# License:
# ==============================================================================
# Copyright 2017-2018 Patrick Lehmann - Bötzingen, Germany
# Copyright 2007-2016 Technische Universität Dresden - Germany
#                     Chair of VLSI-Design, Diagnostics and Architecture
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
#
# load dependencies
from pathlib                import Path
from subprocess             import Popen				as Subprocess_Popen
from subprocess             import PIPE					as Subprocess_Pipe
from subprocess             import STDOUT				as Subprocess_StdOut

from pyIPCMI.Base.Exceptions        import CommonException, ExceptionBase
from pyIPCMI.Base.Logging           import ILogable, Logger


__api__ = [
	'ExecutableException',
	'CommandLineArgument',
	'ExecutableArgument',
	'NamedCommandLineArgument',
	'CommandArgument',        'ShortCommandArgument',         'LongCommandArgument',        'WindowsCommandArgument',
	'StringArgument',
	'StringListArgument',
	'PathArgument',
	'FlagArgument',           'ShortFlagArgument',            'LongFlagArgument',           'WindowsFlagArgument',
	'ValuedFlagArgument',     'ShortValuedFlagArgument',      'LongValuedFlagArgument',     'WindowsValuedFlagArgument',
	'ValuedFlagListArgument', 'ShortValuedFlagListArgument',  'LongValuedFlagListArgument', 'WindowsValuedFlagListArgument',
	'TupleArgument',          'ShortTupleArgument',           'LongTupleArgument',          'WindowsTupleArgument',
	'CommandLineArgumentList',
	'Environment',
	'Executable'
]
__all__ = __api__


[docs]class ExecutableException(ExceptionBase): """This exception is raised by all executable abstraction classes.""" def __init__(self, message=""): super().__init__(message) self.message = message
class DryRunException(ExecutableException): """This exception is raised if a simulator runs in dry-run mode."""
[docs]class CommandLineArgument(type): """Base class (and meta class) for all Arguments classes.""" _value = None
# def __new__(mcls, name, bases, nmspc): # print("CommandLineArgument.new: %s - %s" % (name, nmspc)) # return super(CommandLineArgument, mcls).__new__(mcls, name, bases, nmspc)
[docs]class ExecutableArgument(CommandLineArgument): """Represents the executable.""" @property def Value(self): return self._value @Value.setter def Value(self, value): if isinstance(value, str): self._value = value elif isinstance(value, Path): self._value = str(value) else: raise ValueError("Parameter 'value' is not of type str or Path.") def __str__(self): if (self._value is None): return "" else: return self._value
[docs] def AsArgument(self): if (self._value is None): raise ValueError("Executable argument is still empty.") else: return self._value
[docs]class NamedCommandLineArgument(CommandLineArgument): """Base class for all command line arguments with a name.""" _name = None # set in sub-classes @property def Name(self): return self._name
[docs]class CommandArgument(NamedCommandLineArgument): """Represents a command name. It is usually used to select a sub parser in a CLI argument parser or to hand over all following parameters to a separate tool. An example for a command is 'checkout' in ``git.exe checkout``, which calls ``git-checkout.exe``. """ _pattern = "{0}" @property def Value(self): return self._value @Value.setter def Value(self, value): if (value is None): self._value = None elif isinstance(value, bool): self._value = value else: raise ValueError("Parameter 'value' is not of type bool.") def __str__(self): if (self._value is None): return "" elif self._value: return self._pattern.format(self._name) else: return ""
[docs] def AsArgument(self): if (self._value is None): return None elif self._value: return self._pattern.format(self._name) else: return None
[docs]class ShortCommandArgument(CommandArgument): """Represents a command name with a single dash.""" _pattern = "-{0}"
[docs]class LongCommandArgument(CommandArgument): """Represents a command name with a double dash.""" _pattern = "--{0}"
[docs]class WindowsCommandArgument(CommandArgument): """Represents a command name with a single slash.""" _pattern = "/{0}"
[docs]class StringArgument(CommandLineArgument): """Represents a simple string argument.""" _pattern = "{0}" @property def Value(self): return self._value @Value.setter def Value(self, value): if (value is None): self._value = None elif isinstance(value, str): self._value = value else: try: self._value = str(value) except Exception as ex: raise ValueError("Parameter 'value' cannot be converted to type str.") from ex def __str__(self): if (self._value is None): return "" elif self._value: return self._pattern.format(self._value) else: return ""
[docs] def AsArgument(self): if (self._value is None): return None elif self._value: return self._pattern.format(self._value) else: return None
[docs]class StringListArgument(CommandLineArgument): """Represents a list of string arguments.""" _pattern = "{0}" @property def Value(self): return self._value @Value.setter def Value(self, value): if (value is None): self._value = None elif isinstance(value, (tuple, list)): self._value = [] try: for item in value: self._value.append(str(item)) except TypeError as ex: raise ValueError("Item '{0}' in parameter 'value' cannot be converted to type str.".format(item)) from ex else: raise ValueError("Parameter 'value' is no list or tuple.") def __str__(self): if (self._value is None): return "" elif self._value: return " ".join([self._pattern.format(item) for item in self._value]) else: return ""
[docs] def AsArgument(self): if (self._value is None): return None elif self._value: return [self._pattern.format(item) for item in self._value] else: return None
[docs]class PathArgument(CommandLineArgument): """Represents a path argument. The output format can be forced to the POSIX format with :py:data:`_PosixFormat`. """ _PosixFormat = False @property def Value(self): return self._value @Value.setter def Value(self, value): if (value is None): self._value = None elif isinstance(value, Path): self._value = value else: raise ValueError("Parameter 'value' is not of type Path.") def __str__(self): if (self._value is None): return "" elif (self._PosixFormat): return "\"" + self._value.as_posix() + "\"" else: return "\"" + str(self._value) + "\""
[docs] def AsArgument(self): if (self._value is None): return None elif (self._PosixFormat): return self._value.as_posix() else: return str(self._value)
[docs]class FlagArgument(NamedCommandLineArgument): """Base class for all FlagArgument classes, which represents a simple flag argument. A simple flag is a single boolean value (absent/present or off/on) with no data. """ _pattern = "{0}" @property def Value(self): return self._value @Value.setter def Value(self, value): if (value is None): self._value = None elif isinstance(value, bool): self._value = value else: raise ValueError("Parameter 'value' is not of type bool.") def __str__(self): if (self._value is None): return "" elif self._value: return self._pattern.format(self._name) else: return ""
[docs] def AsArgument(self): if (self._value is None): return None elif self._value: return self._pattern.format(self._name) else: return None
[docs]class ShortFlagArgument(FlagArgument): """Represents a flag argument with a single dash. Example: ``-optimize`` """ _pattern = "-{0}"
[docs]class LongFlagArgument(FlagArgument): """Represents a flag argument with a double dash. Example: ``--optimize`` """ _pattern = "--{0}"
[docs]class WindowsFlagArgument(FlagArgument): """Represents a flag argument with a single slash. Example: ``/optimize`` """ _pattern = "/{0}"
[docs]class ValuedFlagArgument(NamedCommandLineArgument): """Class and base class for all ValuedFlagArgument classes, which represents a flag argument with data. A valued flag is a flag name followed by a value. The default delimiter sign is equal (``=``). Name and value are passed as one arguments to the executable even if the delimiter sign is a whitespace character. Example: ``width=100`` """ _pattern = "{0}={1}" @property def Value(self): return self._value @Value.setter def Value(self, value): if (value is None): self._value = None elif isinstance(value, str): self._value = value else: try: self._value = str(value) except Exception as ex: raise ValueError("Parameter 'value' cannot be converted to type str.") from ex def __str__(self): if (self._value is None): return "" elif self._value: return self._pattern.format(self._name, self._value) else: return ""
[docs] def AsArgument(self): if (self._value is None): return None elif self._value: return self._pattern.format(self._name, self._value) else: return None
[docs]class ShortValuedFlagArgument(ValuedFlagArgument): """Represents a :py:class:`ValuedFlagArgument` with a single dash. Example: ``-optimizer=on`` """ _pattern = "-{0}={1}"
[docs]class LongValuedFlagArgument(ValuedFlagArgument): """Represents a :py:class:`ValuedFlagArgument` with a double dash. Example: ``--optimizer=on`` """ _pattern = "--{0}={1}"
[docs]class WindowsValuedFlagArgument(ValuedFlagArgument): """Represents a :py:class:`ValuedFlagArgument` with a single slash. Example: ``/optimizer:on`` """ _pattern = "/{0}:{1}"
class OptionalValuedFlagArgument(NamedCommandLineArgument): """Class and base class for all OptionalValuedFlagArgument classes, which represents a flag argument with data. An optional valued flag is a flag name followed by a value. The default delimiter sign is equal (``=``). Name and value are passed as one arguments to the executable even if the delimiter sign is a whitespace character. If the value is None, no delimiter sign and value is passed. Example: ``width=100`` """ _pattern = "{0}" _patternWithValue = "{0}={1}" @property def Value(self): return self._value @Value.setter def Value(self, value): if (value is None): self._value = None elif isinstance(value, str): self._value = value else: try: self._value = str(value) except Exception as ex: raise ValueError("Parameter 'value' cannot be converted to type str.") from ex def __str__(self): if (self._value is None): return "" elif self._value: return self._pattern.format(self._name, self._value) else: return "" def AsArgument(self): if (self._value is None): return None elif self._value: return self._pattern.format(self._name, self._value) else: return None class ShortOptionalValuedFlagArgument(OptionalValuedFlagArgument): """Represents a :py:class:`OptionalValuedFlagArgument` with a single dash. Example: ``-optimizer=on`` """ _pattern = "-{0}" _patternWithValue = "-{0}={1}" class LongOptionalValuedFlagArgument(OptionalValuedFlagArgument): """Represents a :py:class:`OptionalValuedFlagArgument` with a double dash. Example: ``--optimizer=on`` """ _pattern = "--{0}" _patternWithValue = "--{0}={1}" class WindowsOptionalValuedFlagArgument(OptionalValuedFlagArgument): """Represents a :py:class:`OptionalValuedFlagArgument` with a single slash. Example: ``/optimizer:on`` """ _pattern = "/{0}" _patternWithValue = "/{0}={1}"
[docs]class ValuedFlagListArgument(NamedCommandLineArgument): """Class and base class for all ValuedFlagListArgument classes, which represents a list of :py:class:`ValuedFlagArgument` instances. Each list item gets translated into a :py:class:`ValuedFlagArgument`, with the same flag name, but differing values. Each :py:class:`ValuedFlagArgument` is passed as a single argument to the executable, even if the delimiter sign is a whitespace character. Example: ``file=file1.txt file=file2.txt`` """ _pattern = "{0}={1}" @property def Value(self): return self._value @Value.setter def Value(self, value): if (value is None): self._value = None elif isinstance(value, (tuple,list)): self._value = value else: raise ValueError("Parameter 'value' is not of type tuple or list.") def __str__(self): if (self._value is None): return "" elif (len(self._value) > 0): return " ".join([self._pattern.format(self._name, item) for item in self._value]) else: return ""
[docs] def AsArgument(self): if (self._value is None): return None elif (len(self._value) > 0): return [self._pattern.format(self._name, item) for item in self._value] else: return None
[docs]class ShortValuedFlagListArgument(ValuedFlagListArgument): """Represents a :py:class:`ValuedFlagListArgument` with a single dash. Example: ``-file=file1.txt -file=file2.txt`` """ _pattern = "-{0}={1}"
[docs]class LongValuedFlagListArgument(ValuedFlagListArgument): """Represents a :py:class:`ValuedFlagListArgument` with a double dash. Example: ``--file=file1.txt --file=file2.txt`` """ _pattern = "--{0}={1}"
[docs]class WindowsValuedFlagListArgument(ValuedFlagListArgument): """Represents a :py:class:`ValuedFlagListArgument` with a single slash. Example: ``/file:file1.txt /file:file2.txt`` """ _pattern = "/{0}:{1}"
[docs]class TupleArgument(NamedCommandLineArgument): """Class and base class for all TupleArgument classes, which represents a switch with separate data. A tuple switch is a command line argument followed by a separate value. Name and value are passed as two arguments to the executable. Example: ``width 100`` """ _switchPattern = "{0}" _valuePattern = "{0}" @property def Value(self): return self._value @Value.setter def Value(self, value): if (value is None): self._value = None elif isinstance(value, str): self._value = value else: try: self._value = str(value) except TypeError as ex: raise ValueError("Parameter 'value' cannot be converted to type str.") from ex def __str__(self): if (self._value is None): return "" elif self._value: return self._switchPattern.format(self._name) + " \"" + self._valuePattern.format(self._value) + "\"" else: return ""
[docs] def AsArgument(self): if (self._value is None): return None elif self._value: return [self._switchPattern.format(self._name), self._valuePattern.format(self._value)] else: return None
[docs]class ShortTupleArgument(TupleArgument): """Represents a :py:class:`TupleArgument` with a single dash in front of the switch name. Example: ``-file file1.txt`` """ _switchPattern = "-{0}"
[docs]class LongTupleArgument(TupleArgument): """Represents a :py:class:`TupleArgument` with a double dash in front of the switch name. Example: ``--file file1.txt`` """ _switchPattern = "--{0}"
[docs]class WindowsTupleArgument(TupleArgument): """Represents a :py:class:`TupleArgument` with a single slash in front of the switch name. Example: ``/file file1.txt`` """ _switchPattern = "/{0}"
[docs]class CommandLineArgumentList(list): """Represent a list of all available commands, flags and switch of an executable.""" def __init__(self, *args): super().__init__() for arg in args: self.append(arg) def __getitem__(self, key): i = self.index(key) return super().__getitem__(i).Value def __setitem__(self, key, value): i = self.index(key) super().__getitem__(i).Value = value def __delitem__(self, key): i = self.index(key) super().__getitem__(i).Value = None
[docs] def ToArgumentList(self): result = [] for item in self: arg = item.AsArgument() if (arg is None): pass elif isinstance(arg, str): result.append(arg) elif isinstance(arg, list): result += arg else: raise TypeError() return result
[docs]class Environment: def __init__(self): self.Variables = {}
[docs]class Executable(ILogable): """Represent an executable.""" _pyIPCMI_BOUNDARY = "====== pyIPCMI BOUNDARY ======" def __init__(self, platform : str, dryrun : bool, executablePath : Path, environment : Environment = None, logger : Logger =None): super().__init__(logger) self._platform = platform self._dryrun = dryrun self._environment = environment #if (environment is not None) else Environment() self._process = None if isinstance(executablePath, str): executablePath = Path(executablePath) elif (not isinstance(executablePath, Path)): raise ValueError("Parameter 'executablePath' is not of type str or Path.") if (not executablePath.exists()): if dryrun: self.LogDryRun("File check for '{0!s}' failed. [SKIPPING]".format(executablePath)) else: raise CommonException("Executable '{0!s}' not found.".format(executablePath)) from FileNotFoundError(str(executablePath)) # prepend the executable self._executablePath = executablePath self._iterator = None @property def Path(self): return self._executablePath
[docs] def StartProcess(self, parameterList): # start child process # parameterList.insert(0, str(self._executablePath)) if (not self._dryrun): if (self._environment is not None): envVariables = self._environment.Variables else: envVariables = None try: self._process = Subprocess_Popen( parameterList, stdin=Subprocess_Pipe, stdout=Subprocess_Pipe, stderr=Subprocess_StdOut, env=envVariables, universal_newlines=True, bufsize=256 ) except OSError as ex: raise CommonException("Error while accessing '{0!s}'.".format(self._executablePath)) from ex else: self.LogDryRun("Start process: {0}".format(" ".join(parameterList)))
[docs] def Send(self, line, end="\n"): self._process.stdin.write(line + end) self._process.stdin.flush()
[docs] def SendBoundary(self): self.Send("puts \"{0}\"".format(self._pyIPCMI_BOUNDARY))
[docs] def Terminate(self): self._process.terminate()
[docs] def GetReader(self): if (not self._dryrun): try: for line in iter(self._process.stdout.readline, ""): yield line[:-1] except Exception as ex: raise ex # finally: # self._process.terminate() else: raise DryRunException()
[docs] def ReadUntilBoundary(self, indent=0): __indent = " " * indent if (self._iterator is None): self._iterator = iter(self.GetReader()) for line in self._iterator: print(__indent + line) if (self._pyIPCMI_BOUNDARY in line): break self.LogDebug("Quartus II is ready")