#!/usr/bin/env python3 # # generate python wrappers from the XML API description # import os import re import sys import xml.sax from contextlib import closing from collections import defaultdict from typing import Dict, IO, List, Optional, Set, Tuple, Union # noqa F401 ArgumentType = Tuple[str, str, str] FunctionType = Tuple[str, ArgumentType, List[ArgumentType], str, str, str] EnumValue = Union[str, int] EnumType = Dict[str, EnumValue] functions = {} # type: Dict[str, FunctionType] enums = defaultdict(dict) # type: Dict[str, EnumType] # { enumType: { enumConstant: enumValue } } event_ids = [] # type: List[str] params = [] # type: List[Tuple[str, str]] # [ (paramName, paramValue)... ] quiet = True ####################################################################### # # That part if purely the API acquisition phase from the # libvirt API description # ####################################################################### debug = 0 onlyOverrides = False libvirt_headers = [ "libvirt", "libvirt-common", "libvirt-domain", "libvirt-domain-checkpoint", "libvirt-domain-snapshot", "libvirt-event", "libvirt-host", "libvirt-interface", "libvirt-network", "libvirt-nodedev", "libvirt-nwfilter", "libvirt-secret", "libvirt-storage", "libvirt-stream", ] def parse(data: IO[str]) -> None: target = docParser() with closing(xml.sax.make_parser()) as parser: parser.setContentHandler(target) parser.parse(data) class docParser(xml.sax.handler.ContentHandler): def __init__(self) -> None: self._data = [] # type: List[str] self.in_function = False def characters(self, text: str) -> None: if debug: print("data %s" % text) self._data.append(text) def startElement(self, tag: str, attrs: Dict[str, str]) -> None: if debug: print("start %s, %s" % (tag, attrs)) if tag == 'function': self._data = [] self.in_function = True self.function_cond = '' self.function_args = [] # type: List[ArgumentType] self.function_descr = '' self.function_return = None # type: Optional[ArgumentType] self.function = attrs.get('name', '') self.function_file = attrs.get('file', '') self.function_module = attrs.get('module', '') elif tag == 'cond': self._data = [] elif tag == 'info': self._data = [] elif tag == 'arg': if self.in_function: self.function_arg_name = attrs.get('name', '') if self.function_arg_name == 'from': self.function_arg_name = 'frm' self.function_arg_type = attrs.get('type', '') self.function_arg_info = attrs.get('info', '') elif tag == 'return': if self.in_function: self.function_return_type = attrs.get('type', '') self.function_return_info = attrs.get('info', '') self.function_return_field = attrs.get('field', '') elif tag == 'enum': # enums come from header files, hence virterror.h files = libvirt_headers + ["virerror", "virterror", "libvirt-lxc", "libvirt-qemu"] if attrs['file'] in files: enum(attrs['type'], attrs['name'], attrs['value']) elif tag == "macro": if "string" in attrs: params.append((attrs['name'], attrs['string'])) def endElement(self, tag: str) -> None: if debug: print("end %s" % tag) if tag == 'function': # functions come from source files, hence 'virerror.c' if self.function: assert self.function_return modules = libvirt_headers + ["event", "virevent", "virerror", "virterror", "libvirt-lxc", "libvirt-qemu"] files = ["python", "python-lxc", "python-qemu"] if (self.function_module in modules or self.function_file in files): function(self.function, self.function_descr, self.function_return, self.function_args, self.function_file, self.function_module, self.function_cond) self.in_function = False elif tag == 'arg': if self.in_function: self.function_args.append((self.function_arg_name, self.function_arg_type, self.function_arg_info)) elif tag == 'return': if self.in_function: self.function_return = (self.function_return_type, self.function_return_info, self.function_return_field) elif tag == 'info': str = ''.join(self._data) if self.in_function: self.function_descr = str elif tag == 'cond': str = ''.join(self._data) if self.in_function: self.function_cond = str def function(name: str, desc: str, ret: ArgumentType, args: List[ArgumentType], file: str, module: str, cond: str) -> None: if onlyOverrides and name not in functions: return if name == "virConnectListDomains": name = "virConnectListDomainsID" functions[name] = (desc, ret, args, file, module, cond) def enum(type: str, name: str, value: EnumValue) -> None: if (name.startswith('VIR_DOMAIN_EVENT_ID_') or name.startswith('VIR_NETWORK_EVENT_ID_')): event_ids.append(name) if value == 'VIR_TYPED_PARAM_INT': value = 1 elif value == 'VIR_TYPED_PARAM_UINT': value = 2 elif value == 'VIR_TYPED_PARAM_LLONG': value = 3 elif value == 'VIR_TYPED_PARAM_ULLONG': value = 4 elif value == 'VIR_TYPED_PARAM_DOUBLE': value = 5 elif value == 'VIR_TYPED_PARAM_BOOLEAN': value = 6 elif value == 'VIR_DOMAIN_AFFECT_CURRENT': value = 0 elif value == 'VIR_DOMAIN_AFFECT_LIVE': value = 1 elif value == 'VIR_DOMAIN_AFFECT_CONFIG': value = 2 elif value == 'VIR_DOMAIN_AGENT_RESPONSE_TIMEOUT_BLOCK': value = -2 elif value == 'VIR_DOMAIN_AGENT_RESPONSE_TIMEOUT_DEFAULT': value = -1 elif value == 'VIR_DOMAIN_AGENT_RESPONSE_TIMEOUT_NOWAIT': value = 0 if onlyOverrides and name not in enums[type]: return enums[type][name] = value ####################################################################### # # Some filtering rules to drop functions/types which should not # be exposed as-is on the Python interface # ####################################################################### skipped_types = { # 'int *': "usually a return type", 'virConnectDomainEventCallback': "No function types in python", 'virConnectDomainEventGenericCallback': "No function types in python", 'virConnectDomainEventRTCChangeCallback': "No function types in python", 'virConnectDomainEventWatchdogCallback': "No function types in python", 'virConnectDomainEventIOErrorCallback': "No function types in python", 'virConnectDomainEventGraphicsCallback': "No function types in python", 'virConnectDomainQemuMonitorEventCallback': "No function types in python", 'virStreamEventCallback': "No function types in python", 'virEventHandleCallback': "No function types in python", 'virEventTimeoutCallback': "No function types in python", 'virDomainBlockJobInfoPtr': "Not implemented yet", } ####################################################################### # # Table of remapping to/from the python type or class to the C # counterpart. # ####################################################################### py_types = { 'void': ('', '', '', ''), 'int': ('i', '', "int", "int"), 'long': ('l', '', "long", "long"), 'double': ('d', '', "double", "double"), 'unsigned int': ('I', '', "int", "int"), 'unsigned long': ('l', '', "long", "long"), 'long long': ('L', '', "longlong", "long long"), 'unsigned long long': ('L', '', "longlong", "long long"), 'unsigned char *': ('z', '', "charPtr", "char *"), 'char *': ('z', '', "charPtr", "char *"), 'const char *': ('z', '', "constcharPtr", "const char *"), 'size_t': ('n', '', "size_t", "size_t"), 'virDomainPtr': ('O', "virDomain", "virDomainPtr", "virDomainPtr"), 'virDomain *': ('O', "virDomain", "virDomainPtr", "virDomainPtr"), 'const virDomain *': ('O', "virDomain", "virDomainPtr", "virDomainPtr"), 'virNetworkPtr': ('O', "virNetwork", "virNetworkPtr", "virNetworkPtr"), 'virNetwork *': ('O', "virNetwork", "virNetworkPtr", "virNetworkPtr"), 'const virNetwork *': ('O', "virNetwork", "virNetworkPtr", "virNetworkPtr"), 'virNetworkPortPtr': ('O', "virNetworkPort", "virNetworkPortPtr", "virNetworkPortPtr"), 'virNetworkPort *': ('O', "virNetworkPort", "virNetworkPortPtr", "virNetworkPortPtr"), 'const virNetworkPort *': ('O', "virNetworkPort", "virNetworkPortPtr", "virNetworkPortPtr"), 'virInterfacePtr': ('O', "virInterface", "virInterfacePtr", "virInterfacePtr"), 'virInterface *': ('O', "virInterface", "virInterfacePtr", "virInterfacePtr"), 'const virInterface *': ('O', "virInterface", "virInterfacePtr", "virInterfacePtr"), 'virStoragePoolPtr': ('O', "virStoragePool", "virStoragePoolPtr", "virStoragePoolPtr"), 'virStoragePool *': ('O', "virStoragePool", "virStoragePoolPtr", "virStoragePoolPtr"), 'const virStoragePool *': ('O', "virStoragePool", "virStoragePoolPtr", "virStoragePoolPtr"), 'virStorageVolPtr': ('O', "virStorageVol", "virStorageVolPtr", "virStorageVolPtr"), 'virStorageVol *': ('O', "virStorageVol", "virStorageVolPtr", "virStorageVolPtr"), 'const virStorageVol *': ('O', "virStorageVol", "virStorageVolPtr", "virStorageVolPtr"), 'virConnectPtr': ('O', "virConnect", "virConnectPtr", "virConnectPtr"), 'virConnect *': ('O', "virConnect", "virConnectPtr", "virConnectPtr"), 'const virConnect *': ('O', "virConnect", "virConnectPtr", "virConnectPtr"), 'virNodeDevicePtr': ('O', "virNodeDevice", "virNodeDevicePtr", "virNodeDevicePtr"), 'virNodeDevice *': ('O', "virNodeDevice", "virNodeDevicePtr", "virNodeDevicePtr"), 'const virNodeDevice *': ('O', "virNodeDevice", "virNodeDevicePtr", "virNodeDevicePtr"), 'virSecretPtr': ('O', "virSecret", "virSecretPtr", "virSecretPtr"), 'virSecret *': ('O', "virSecret", "virSecretPtr", "virSecretPtr"), 'const virSecret *': ('O', "virSecret", "virSecretPtr", "virSecretPtr"), 'virNWFilterPtr': ('O', "virNWFilter", "virNWFilterPtr", "virNWFilterPtr"), 'virNWFilter *': ('O', "virNWFilter", "virNWFilterPtr", "virNWFilterPtr"), 'const virNWFilter *': ('O', "virNWFilter", "virNWFilterPtr", "virNWFilterPtr"), 'virNWFilterBindingPtr': ('O', "virNWFilterBinding", "virNWFilterBindingPtr", "virNWFilterBindingPtr"), 'virNWFilterBinding *': ('O', "virNWFilterBinding", "virNWFilterBindingPtr", "virNWFilterBindingPtr"), 'const virNWFilterBinding *': ('O', "virNWFilterBinding", "virNWFilterBindingPtr", "virNWFilterBindingPtr"), 'virStreamPtr': ('O', "virStream", "virStreamPtr", "virStreamPtr"), 'virStream *': ('O', "virStream", "virStreamPtr", "virStreamPtr"), 'const virStream *': ('O', "virStream", "virStreamPtr", "virStreamPtr"), 'virDomainCheckpointPtr': ('O', "virDomainCheckpoint", "virDomainCheckpointPtr", "virDomainCheckpointPtr"), 'virDomainCheckpoint *': ('O', "virDomainCheckpoint", "virDomainCheckpointPtr", "virDomainCheckpointPtr"), 'const virDomainCheckpoint *': ('O', "virDomainCheckpoint", "virDomainCheckpointPtr", "virDomainCheckpointPtr"), 'virDomainSnapshotPtr': ('O', "virDomainSnapshot", "virDomainSnapshotPtr", "virDomainSnapshotPtr"), 'virDomainSnapshot *': ('O', "virDomainSnapshot", "virDomainSnapshotPtr", "virDomainSnapshotPtr"), 'const virDomainSnapshot *': ('O', "virDomainSnapshot", "virDomainSnapshotPtr", "virDomainSnapshotPtr"), } # type: Dict[str, Tuple[str, str, str, str]] ####################################################################### # # This part writes the C <-> Python stubs libvirt.[ch] and # the table libvirt-export.c to add when registering the Python module # ####################################################################### # Class methods which are written by hand in libvirt.c but the Python-level # code is still automatically generated (so they are not in skip_function()). skip_impl = { 'virConnectGetVersion', 'virConnectGetLibVersion', 'virConnectListDomainsID', 'virConnectListDefinedDomains', 'virConnectListNetworks', 'virConnectListDefinedNetworks', 'virConnectListSecrets', 'virConnectListInterfaces', 'virConnectListStoragePools', 'virConnectListDefinedStoragePools', 'virConnectListStorageVols', 'virConnectListDefinedStorageVols', 'virConnectListDefinedInterfaces', 'virConnectListNWFilters', 'virDomainSnapshotListNames', 'virDomainSnapshotListChildrenNames', 'virConnGetLastError', 'virGetLastError', 'virDomainGetInfo', 'virDomainGetState', 'virDomainGetControlInfo', 'virDomainGetBlockInfo', 'virDomainGetJobInfo', 'virDomainGetJobStats', 'virNodeGetInfo', 'virNodeGetSecurityModel', 'virDomainGetSecurityLabel', 'virDomainGetSecurityLabelList', 'virDomainGetUUID', 'virDomainGetUUIDString', 'virDomainLookupByUUID', 'virNetworkGetUUID', 'virNetworkGetUUIDString', 'virNetworkLookupByUUID', 'virNetworkPortGetUUID', 'virNetworkPortGetUUIDString', 'virNetworkPortLookupByUUID', 'virDomainGetAutostart', 'virNetworkGetAutostart', 'virDomainBlockStats', 'virDomainInterfaceStats', 'virDomainMemoryStats', 'virNodeGetCellsFreeMemory', 'virDomainGetSchedulerType', 'virDomainGetSchedulerParameters', 'virDomainGetSchedulerParametersFlags', 'virDomainSetSchedulerParameters', 'virDomainSetSchedulerParametersFlags', 'virDomainSetBlkioParameters', 'virDomainGetBlkioParameters', 'virDomainSetMemoryParameters', 'virDomainGetMemoryParameters', 'virDomainSetNumaParameters', 'virDomainGetNumaParameters', 'virDomainGetVcpus', 'virDomainPinVcpu', 'virDomainPinVcpuFlags', 'virDomainGetVcpuPinInfo', 'virDomainGetEmulatorPinInfo', 'virDomainPinEmulator', 'virDomainGetIOThreadInfo', 'virDomainPinIOThread', 'virDomainSetIOThreadParams', 'virSecretGetValue', 'virSecretSetValue', 'virSecretGetUUID', 'virSecretGetUUIDString', 'virSecretLookupByUUID', 'virNWFilterGetUUID', 'virNWFilterGetUUIDString', 'virNWFilterLookupByUUID', 'virStoragePoolGetUUID', 'virStoragePoolGetUUIDString', 'virStoragePoolLookupByUUID', 'virStoragePoolGetInfo', 'virStorageVolGetInfo', 'virStorageVolGetInfoFlags', 'virStoragePoolGetAutostart', 'virStoragePoolListVolumes', 'virDomainBlockPeek', 'virDomainMemoryPeek', 'virEventRegisterImpl', 'virNodeListDevices', 'virNodeDeviceListCaps', 'virConnectBaselineCPU', 'virDomainRevertToSnapshot', 'virDomainSendKey', 'virNodeGetCPUStats', 'virNodeGetMemoryStats', 'virDomainGetBlockJobInfo', 'virDomainMigrateGetCompressionCache', 'virDomainMigrateGetMaxSpeed', 'virDomainMigrateGetMaxDowntime', 'virDomainBlockStatsFlags', 'virDomainSetBlockIoTune', 'virDomainGetBlockIoTune', 'virDomainSetInterfaceParameters', 'virDomainGetInterfaceParameters', 'virDomainGetCPUStats', 'virDomainGetDiskErrors', 'virNodeGetMemoryParameters', 'virNodeSetMemoryParameters', 'virConnectSetIdentity', 'virNodeGetCPUMap', 'virDomainMigrate3', 'virDomainMigrateToURI3', 'virConnectGetCPUModelNames', 'virNodeGetFreePages', 'virNetworkGetDHCPLeases', 'virDomainBlockCopy', 'virNodeAllocPages', 'virDomainGetFSInfo', 'virDomainInterfaceAddresses', 'virDomainGetPerfEvents', 'virDomainSetPerfEvents', 'virDomainGetGuestVcpus', 'virConnectBaselineHypervisorCPU', 'virDomainGetLaunchSecurityInfo', 'virDomainSetLaunchSecurityState', 'virNodeGetSEVInfo', 'virNetworkPortGetParameters', 'virNetworkPortSetParameters', 'virDomainGetGuestInfo', 'virDomainAuthorizedSSHKeysGet', 'virDomainAuthorizedSSHKeysSet', 'virDomainGetMessages', 'virNodeDeviceGetAutostart', 'virDomainSaveParams', 'virDomainRestoreParams', 'virDomainLxcOpenNamespace', 'virDomainQemuMonitorCommand', 'virDomainQemuAgentCommand', } # These are functions which the generator skips completely - no python # or C code is generated. Generally should not be used for any more # functions than those already listed skip_function = { 'virConnectListDomains', # Python API is called virConnectListDomainsID for unknown reasons 'virConnSetErrorFunc', # Not used in Python API XXX is this a bug ? 'virResetError', # Not used in Python API XXX is this a bug ? 'virGetVersion', # Python C code is manually written 'virSetErrorFunc', # Python API is called virRegisterErrorHandler for unknown reasons 'virConnCopyLastError', # Python API is called virConnGetLastError instead 'virCopyLastError', # Python API is called virGetLastError instead 'virConnectOpenAuth', # Python C code is manually written 'virDefaultErrorFunc', # Python virErrorFuncHandler impl calls this from C 'virConnectDomainEventRegister', # overridden in virConnect.py 'virConnectDomainEventDeregister', # overridden in virConnect.py 'virConnectDomainEventRegisterAny', # overridden in virConnect.py 'virConnectDomainEventDeregisterAny', # overridden in virConnect.py 'virConnectNetworkEventRegisterAny', # overridden in virConnect.py 'virConnectNetworkEventDeregisterAny', # overridden in virConnect.py 'virConnectStoragePoolEventRegisterAny', # overridden in virConnect.py 'virConnectStoragePoolEventDeregisterAny', # overridden in virConnect.py 'virConnectNodeDeviceEventRegisterAny', # overridden in virConnect.py 'virConnectNodeDeviceEventDeregisterAny', # overridden in virConnect.py 'virConnectSecretEventRegisterAny', # overridden in virConnect.py 'virConnectSecretEventDeregisterAny', # overridden in virConnect.py 'virSaveLastError', # We have our own python error wrapper 'virFreeError', # Only needed if we use virSaveLastError 'virConnectListAllDomains', # overridden in virConnect.py 'virDomainListAllCheckpoints', # overridden in virDomain.py 'virDomainCheckpointListAllChildren', # overridden in virDomainCheckpoint.py 'virDomainListAllSnapshots', # overridden in virDomain.py 'virDomainSnapshotListAllChildren', # overridden in virDomainSnapshot.py 'virConnectListAllStoragePools', # overridden in virConnect.py 'virStoragePoolListAllVolumes', # overridden in virStoragePool.py 'virConnectListAllNetworks', # overridden in virConnect.py 'virNetworkListAllPorts', # overridden in virConnect.py 'virConnectListAllInterfaces', # overridden in virConnect.py 'virConnectListAllNodeDevices', # overridden in virConnect.py 'virConnectListAllNWFilters', # overridden in virConnect.py 'virConnectListAllNWFilterBindings', # overridden in virConnect.py 'virConnectListAllSecrets', # overridden in virConnect.py 'virConnectGetAllDomainStats', # overridden in virConnect.py 'virDomainListGetStats', # overridden in virConnect.py 'virDomainFDAssociate', # overridden in virDomain.py 'virStreamRecvAll', # Pure python libvirt-override-virStream.py 'virStreamSendAll', # Pure python libvirt-override-virStream.py 'virStreamRecv', # overridden in libvirt-override-virStream.py 'virStreamSend', # overridden in libvirt-override-virStream.py 'virStreamRecvHole', # overridden in libvirt-override-virStream.py 'virStreamSendHole', # overridden in libvirt-override-virStream.py 'virStreamRecvFlags', # overridden in libvirt-override-virStream.py 'virStreamSparseRecvAll', # overridden in libvirt-override-virStream.py 'virStreamSparseSendAll', # overridden in libvirt-override-virStream.py 'virConnectUnregisterCloseCallback', # overridden in virConnect.py 'virConnectRegisterCloseCallback', # overridden in virConnect.py 'virDomainCreateXMLWithFiles', # overridden in virConnect.py 'virDomainCreateWithFiles', # overridden in virDomain.py 'virDomainFSFreeze', # overridden in virDomain.py 'virDomainFSThaw', # overridden in virDomain.py 'virDomainGetTime', # overridden in virDomain.py 'virDomainSetTime', # overridden in virDomain.py # 'Ref' functions have no use for bindings users. "virConnectRef", "virDomainRef", "virInterfaceRef", "virNetworkRef", "virNetworkPortRef", "virNodeDeviceRef", "virSecretRef", "virNWFilterRef", "virNWFilterBindingRef", "virStoragePoolRef", "virStorageVolRef", "virStreamRef", "virDomainCheckpointRef", "virDomainSnapshotRef", # This functions shouldn't be called via the bindings (and even the docs # contain an explicit warning to that effect). The equivalent should be # implemented in pure python for each class "virDomainGetConnect", "virInterfaceGetConnect", "virNetworkGetConnect", "virNetworkPortGetNetwork", "virSecretGetConnect", "virNWFilterGetConnect", "virStoragePoolGetConnect", "virStorageVolGetConnect", "virDomainCheckpointGetConnect", "virDomainCheckpointGetDomain", "virDomainSnapshotGetConnect", "virDomainSnapshotGetDomain", # only useful in C code, python code uses dict for typed parameters "virTypedParamsAddBoolean", "virTypedParamsAddDouble", "virTypedParamsAddFromString", "virTypedParamsAddInt", "virTypedParamsAddLLong", "virTypedParamsAddString", "virTypedParamsAddUInt", "virTypedParamsAddULLong", "virTypedParamsClear", "virTypedParamsFree", "virTypedParamsGet", "virTypedParamsGetBoolean", "virTypedParamsGetDouble", "virTypedParamsGetInt", "virTypedParamsGetLLong", "virTypedParamsGetString", "virTypedParamsGetUInt", "virTypedParamsGetULLong", 'virNetworkDHCPLeaseFree', # only useful in C, python code uses list 'virDomainStatsRecordListFree', # only useful in C, python uses dict 'virDomainFSInfoFree', # only useful in C, python code uses list 'virDomainIOThreadInfoFree', # only useful in C, python code uses list 'virDomainInterfaceFree', # only useful in C, python code uses list "virDomainLxcEnterNamespace", "virDomainLxcEnterSecurityLabel", # "virDomainQemuAttach", 'virConnectDomainQemuMonitorEventRegister', # overridden in -qemu.py 'virConnectDomainQemuMonitorEventDeregister', # overridden in -qemu.py 'virDomainQemuMonitorCommandWithFiles', # overridden in -qemu.py } # Generate C code, but skip python impl function_skip_python_impl = { "virStreamFree", # Needed in custom virStream __del__, but free shouldn't # be exposed in bindings } function_skip_index_one = { "virDomainRevertToSnapshot", } def validate_function(name): (desc, ret, args, file, mod, cond) = functions[name] if name in skip_function: return [] if name in skip_impl: return [] failed = False unknown = [] for a_name, a_type, a_info in args: # This should be correct if a_type[0:6] == "const ": a_type = a_type[6:] if a_type in skipped_types: return [] if a_type not in py_types: unknown.append(a_type) r_type, r_info, r_field = ret if r_type in skipped_types: return [] if r_type != 'void' and r_type not in py_types: unknown.append(r_type) return unknown def validate_functions(): unknown_types = defaultdict(list) # type: Dict[str, List[str]] funcs_failed = [] # type: List[str] for name in sorted(functions): unknown = validate_function(name) if unknown: funcs_failed.append(name) for thetype in unknown: unknown_types[thetype].append(name) if unknown_types: print("Missing type converters: ") for type, count in unknown_types.items(): print("%s:%d " % (type, len(count))) for f in funcs_failed: print("ERROR: failed %s" % f) if funcs_failed: return -1 return 0 def skip_both_impl(name: str) -> bool: if name in skip_function: return True (desc, ret, args, file, mod, cond) = functions[name] for a_name, a_type, a_info in args: # This should be correct if a_type[0:6] == "const ": a_type = a_type[6:] if a_type in skipped_types: return True r_type, r_info, r_field = ret if r_type in skipped_types: return True return False def skip_c_impl(name: str) -> bool: if skip_both_impl(name): return True if name in skip_impl: return True return False def skip_py_impl(name: str) -> bool: if skip_both_impl(name): return True if name in function_skip_python_impl: return True return False def print_function_wrapper(package: str, name: str, output: IO[str], export: IO[str], include: IO[str]) -> bool: """ :returns: True if generated, False if skipped """ (desc, ret, args, file, mod, cond) = functions[name] if skip_c_impl(name): return False c_call = "" format = "" format_args = "" c_args = "" c_return = "" c_convert = "" num_bufs = 0 for a_name, a_type, a_info in args: # This should be correct if a_type[0:6] == "const ": a_type = a_type[6:] c_args += " %s %s;\n" % (a_type, a_name) if a_type in py_types: (f, t, n, c) = py_types[a_type] if f: format += f if t: format_args += ", &pyobj_%s" % (a_name) c_args += " PyObject *pyobj_%s;\n" % (a_name) c_convert += \ " %s = (%s) Py%s_Get(pyobj_%s);\n" % ( a_name, a_type, t, a_name) else: format_args += ", &%s" % (a_name) if f == 't#': format_args += ", &py_buffsize%d" % num_bufs c_args += " int py_buffsize%d;\n" % num_bufs num_bufs += 1 if c_call: c_call += ", " c_call += "%s" % (a_name) else: raise Exception("Unexpected type %s in function %s" % (a_type, name)) if format: format += ":%s" % (name) r_type, r_info, r_field = ret if r_type == 'void': if file == "python_accessor": if args[1][1] == "char *": c_call = "\n VIR_FREE(%s->%s);\n" % ( args[0][0], args[1][0]) c_call += " %s->%s = (%s)strdup((const xmlChar *)%s);\n" % ( args[0][0], args[1][0], args[1][1], args[1][0]) else: c_call = "\n %s->%s = %s;\n" % (args[0][0], args[1][0], args[1][0]) else: c_call = "\n %s(%s);\n" % (name, c_call) ret_convert = " Py_INCREF(Py_None);\n return Py_None;\n" elif r_type in py_types: (f, t, n, c) = py_types[r_type] c_return = " %s c_retval;\n" % (r_type) if file == "python_accessor" and r_field: c_call = "\n c_retval = %s->%s;\n" % (args[0][0], r_field) else: c_call = "\n c_retval = %s(%s);\n" % (name, c_call) ret_convert = " py_retval = libvirt_%sWrap((%s) c_retval);\n" % (n, c) if n == "charPtr": ret_convert += " free(c_retval);\n" ret_convert += " return py_retval;\n" else: raise Exception("Unexpected type %s in function %s" % (r_type, name)) if cond: include.write("#if %s\n" % cond) export.write("#if %s\n" % cond) output.write("#if %s\n" % cond) include.write("PyObject * ") include.write("%s_%s(PyObject *self, PyObject *args);\n" % (package, name)) export.write(" { (char *)\"%s\", %s_%s, METH_VARARGS, NULL },\n" % (name, package, name)) if file == "python": # Those have been manually generated if cond: include.write("#endif\n") export.write("#endif\n") output.write("#endif\n") return True if file == "python_accessor" and r_type != "void" and not r_field: # Those have been manually generated if cond: include.write("#endif\n") export.write("#endif\n") output.write("#endif\n") return True output.write("PyObject *\n") output.write("%s_%s(PyObject *self ATTRIBUTE_UNUSED," % (package, name)) output.write(" PyObject *args") if format == "": output.write(" ATTRIBUTE_UNUSED") output.write(") {\n") if r_type != 'void': output.write(" PyObject *py_retval;\n") if c_return: output.write(c_return) if c_args: output.write(c_args) if format: output.write("\n if (!PyArg_ParseTuple(args, (char *)\"%s\"%s))\n" % (format, format_args)) output.write(" return NULL;\n") if c_convert: output.write(c_convert + "\n") output.write(" LIBVIRT_BEGIN_ALLOW_THREADS;") output.write(c_call) output.write(" LIBVIRT_END_ALLOW_THREADS;\n") output.write(ret_convert) output.write("}\n\n") if cond: include.write("#endif /* %s */\n" % cond) export.write("#endif /* %s */\n" % cond) output.write("#endif /* %s */\n" % cond) return True def print_c_pointer(classname: str, output: IO[str], export: IO[str], include: IO[str]) -> None: output.write("PyObject *\n") output.write("libvirt_%s_pointer(PyObject *self ATTRIBUTE_UNUSED, PyObject *args)\n" % classname) output.write("{\n") output.write(" %sPtr ptr;\n" % classname) output.write(" PyObject *pyptr;\n") output.write(" PyObject *pylong;\n") output.write("\n") output.write(" if (!PyArg_ParseTuple(args, (char *) \"O\", &pyptr))\n") output.write(" return NULL;\n") output.write(" ptr = (%sPtr) Py%s_Get(pyptr);\n" % (classname, classname)) output.write(" pylong = PyLong_FromVoidPtr(ptr);\n") output.write(" return pylong;\n") output.write("}\n") output.write("\n") include.write("PyObject *libvirt_%s_pointer(PyObject *self, PyObject *args);\n" % classname) export.write(" { (char *)\"%s_pointer\", libvirt_%s_pointer, METH_VARARGS, NULL },\n" % (classname, classname)) def load_apis(module: str, api_xml: str): global onlyOverrides try: onlyOverrides = False with open(api_xml) as stream: parse(stream) except IOError as msg: print(api_xml, ":", msg) sys.exit(1) n = len(functions) if not quiet: print("Found %d functions in %s" % ((n), api_xml)) override_api_xml = "%s-override-api.xml" % module py_types['pythonObject'] = ('O', "pythonObject", "pythonObject", "pythonObject") try: onlyOverrides = True with open(override_api_xml) as stream: parse(stream) except IOError as msg: print(override_api_xml, ":", msg) if not quiet: # XXX: This is not right, same function already in @functions # will be overwritten. print("Found %d functions in %s" % (len(functions) - n, override_api_xml)) def emit_c_code(module: str) -> None: package = module.replace('-', '_') nb_wrap = 0 header_file = "build/%s.h" % module export_file = "build/%s-export.c" % module wrapper_file = "build/%s.c" % module include = open(header_file, "w") include.write("/* Generated by generator.py */\n\n") export = open(export_file, "w") export.write("/* Generated by generator.py */\n\n") wrapper = open(wrapper_file, "w") wrapper.write("/* Generated by generator.py */\n\n") wrapper.write("#include \n") wrapper.write("#include \n" % (module,)) wrapper.write("#include \"typewrappers.h\"\n") wrapper.write("#include \"build/%s.h\"\n\n" % (module,)) for function in sorted(functions): if print_function_wrapper(package, function, wrapper, export, include): nb_wrap += 1 if module == "libvirt": # Write C pointer conversion functions. for classname in primary_classes: print_c_pointer(classname, wrapper, export, include) # Write define wrappers around event id enums, so that the # preprocessor can see which enums were available. for event_id in event_ids: include.write("#define %s %s\n" % (event_id, event_id)) include.close() export.close() wrapper.close() if not quiet: print("Generated %d wrapper functions" % nb_wrap) ####################################################################### # # This part writes part of the Python front-end classes based on # mapping rules between types and classes and also based on function # renaming to get consistent function names at the Python level # ####################################################################### # # The type automatically remapped to generated classes # "C-type" -> (accessor, create, class, parent-class) # classes_type = { "virDomainPtr": ("._o", "virDomain(%(p)s, _obj=%(o)s)", "virDomain", "virConnect"), "virDomain *": ("._o", "virDomain(%(p)s, _obj=%(o)s)", "virDomain", "virConnect"), "virNetworkPtr": ("._o", "virNetwork(%(p)s, _obj=%(o)s)", "virNetwork", "virConnect"), "virNetwork *": ("._o", "virNetwork(%(p)s, _obj=%(o)s)", "virNetwork", "virConnect"), "virNetworkPortPtr": ("._o", "virNetworkPort(%(p)s, _obj=%(o)s)", "virNetworkPort", "virNetwork"), "virNetworkPort *": ("._o", "virNetworkPort(%(p)s, _obj=%(o)s)", "virNetworkPort", "virNetwork"), "virInterfacePtr": ("._o", "virInterface(%(p)s, _obj=%(o)s)", "virInterface", "virConnect"), "virInterface *": ("._o", "virInterface(%(p)s, _obj=%(o)s)", "virInterface", "virConnect"), "virStoragePoolPtr": ("._o", "virStoragePool(%(p)s, _obj=%(o)s)", "virStoragePool", "virConnect"), "virStoragePool *": ("._o", "virStoragePool(%(p)s, _obj=%(o)s)", "virStoragePool", "virConnect"), "virStorageVolPtr": ("._o", "virStorageVol(%(p)s, _obj=%(o)s)", "virStorageVol", "virConnect"), "virStorageVol *": ("._o", "virStorageVol(%(p)s, _obj=%(o)s)", "virStorageVol", "virConnect"), "virNodeDevicePtr": ("._o", "virNodeDevice(%(p)s, _obj=%(o)s)", "virNodeDevice", "virConnect"), "virNodeDevice *": ("._o", "virNodeDevice(%(p)s, _obj=%(o)s)", "virNodeDevice", "virConnect"), "virSecretPtr": ("._o", "virSecret(%(p)s, _obj=%(o)s)", "virSecret", "virConnect"), "virSecret *": ("._o", "virSecret(%(p)s, _obj=%(o)s)", "virSecret", "virConnect"), "virNWFilterPtr": ("._o", "virNWFilter(%(p)s, _obj=%(o)s)", "virNWFilter", "virConnect"), "virNWFilter *": ("._o", "virNWFilter(%(p)s, _obj=%(o)s)", "virNWFilter", "virConnect"), "virNWFilterBindingPtr": ("._o", "virNWFilterBinding(%(p)s, _obj=%(o)s)", "virNWFilterBinding", "virConnect"), "virNWFilterBinding *": ("._o", "virNWFilterBinding(%(p)s, _obj=%(o)s)", "virNWFilterBinding", "virConnect"), "virStreamPtr": ("._o", "virStream(%(p)s, _obj=%(o)s)", "virStream", "virConnect"), "virStream *": ("._o", "virStream(%(p)s, _obj=%(o)s)", "virStream", "virConnect"), "virConnectPtr": ("._o", "virConnect(_obj=%(o)s)", "virConnect", ""), "virConnect *": ("._o", "virConnect(_obj=%(o)s)", "virConnect", ""), "virDomainCheckpointPtr": ("._o", "virDomainCheckpoint(%(p)s, _obj=%(o)s)", "virDomainCheckpoint", "virDomain"), "virDomainCheckpoint *": ("._o", "virDomainCheckpoint(%(p)s, _obj=%(o)s)", "virDomainCheckpoint", "virDomain"), "virDomainSnapshotPtr": ("._o", "virDomainSnapshot(%(p)s, _obj=%(o)s)", "virDomainSnapshot", "virDomain"), "virDomainSnapshot *": ("._o", "virDomainSnapshot(%(p)s, _obj=%(o)s)", "virDomainSnapshot", "virDomain"), } primary_classes = ["virDomain", "virNetwork", "virNetworkPort", "virInterface", "virStoragePool", "virStorageVol", "virConnect", "virNodeDevice", "virSecret", "virNWFilter", "virNWFilterBinding", "virStream", "virDomainCheckpoint", "virDomainSnapshot"] classes_destructors = { "virDomain": "virDomainFree", "virNetwork": "virNetworkFree", "virNetworkPort": "virNetworkPortFree", "virInterface": "virInterfaceFree", "virStoragePool": "virStoragePoolFree", "virStorageVol": "virStorageVolFree", "virNodeDevice": "virNodeDeviceFree", "virSecret": "virSecretFree", "virNWFilter": "virNWFilterFree", "virNWFilterBinding": "virNWFilterBindingFree", "virDomainCheckpoint": "virDomainCheckpointFree", "virDomainSnapshot": "virDomainSnapshotFree", # We hand-craft __del__ for this one # "virStream": "virStreamFree", } class_skip_connect_impl = { "virConnect", } class_domain_impl = { "virDomainCheckpoint", "virDomainSnapshot", } class_network_impl = { "virNetworkPort", } functions_noexcept = { 'virDomainGetID', 'virDomainGetName', 'virNetworkGetName', 'virInterfaceGetName', 'virStoragePoolGetName', 'virStorageVolGetName', 'virStorageVolGetkey', 'virNodeDeviceGetName', 'virNodeDeviceGetParent', 'virSecretGetUsageType', 'virSecretGetUsageID', 'virNWFilterGetName', 'virNWFilterBindingGetFilterName', 'virNWFilterBindingGetPortDev', } function_classes = { "None": [] } # type: Dict[str, List[Tuple[int, str, str, ArgumentType, List[ArgumentType], str, str]]] # Functions returning an integral type which need special rules to # check for errors and raise exceptions. functions_int_exception_test = { 'virDomainGetMaxMemory': "%s == 0", } functions_int_default_test = "%s == -1" def is_integral_type(name: str) -> bool: return re.search("^(unsigned)? ?(int|long)$", name) is not None def is_optional_arg(info: str) -> bool: return re.search(r"^\(?optional\)?", info) is not None def is_python_noninteger_type(name: str) -> bool: return name[-1:] == "*" def nameFixup(name: str, classe: str, type: str, file: str) -> str: # avoid a disastrous clash listname = classe + "List" l = len(classe) if name[0:l] == listname: func = name[l:] func = func[0:1].lower() + func[1:] elif name[0:16] == "virNetworkDefine": func = name[3:] func = func[0:1].lower() + func[1:] elif name[0:19] == "virNetworkCreateXML": func = name[3:] func = func[0:1].lower() + func[1:] elif name[0:16] == "virNetworkLookup": func = name[3:] func = func[0:1].lower() + func[1:] elif name[0:23] == "virNetworkPortCreateXML": func = name[10:] func = func[0:1].lower() + func[1:] elif name[0:20] == "virNetworkPortLookup": func = name[10:] func = func[0:1].lower() + func[1:] elif name[0:18] == "virInterfaceDefine": func = name[3:] func = func[0:1].lower() + func[1:] elif name[0:21] == "virInterfaceCreateXML": func = name[3:] func = func[0:1].lower() + func[1:] elif name[0:18] == "virInterfaceLookup": func = name[3:] func = func[0:1].lower() + func[1:] elif name[0:15] == "virSecretDefine": func = name[3:] func = func[0:1].lower() + func[1:] elif name[0:15] == "virSecretLookup": func = name[3:] func = func[0:1].lower() + func[1:] elif name[0:27] == "virNWFilterBindingCreateXML": func = name[3:] func = func[0:3].lower() + func[3:] elif name[0:24] == "virNWFilterBindingLookup": func = name[3:] func = func[0:3].lower() + func[3:] elif name[0:24] == "virNWFilterBindingDefine": func = name[3:] func = func[0:3].lower() + func[3:] elif name[0:24] == "virNWFilterBindingLookup": func = name[3:] func = func[0:3].lower() + func[3:] elif name[0:17] == "virNWFilterDefine": func = name[3:] func = func[0:3].lower() + func[3:] elif name[0:17] == "virNWFilterLookup": func = name[3:] func = func[0:3].lower() + func[3:] elif name[0:20] == "virStoragePoolDefine": func = name[3:] func = func[0:1].lower() + func[1:] elif name[0:23] == "virStoragePoolCreateXML": func = name[3:] func = func[0:1].lower() + func[1:] elif name[0:20] == "virStoragePoolLookup": func = name[3:] func = func[0:1].lower() + func[1:] elif name[0:19] == "virStorageVolDefine": func = name[3:] func = func[0:1].lower() + func[1:] elif name[0:19] == "virStorageVolLookup": func = name[3:] func = func[0:1].lower() + func[1:] elif name[0:20] == "virDomainGetCPUStats": func = name[9:] func = func[0:1].lower() + func[1:] elif name[0:24] == "virDomainGetIOThreadInfo": func = name[12:] func = func[0:2].lower() + func[2:] elif name[0:18] == "virDomainGetFSInfo": func = name[12:] func = func[0:2].lower() + func[2:] elif name[0:12] == "virDomainGet": func = name[12:] func = func[0:1].lower() + func[1:] elif name[0:31] == "virDomainCheckpointLookupByName": func = name[9:] func = func[0:1].lower() + func[1:] elif name[0:28] == "virDomainCheckpointCreateXML": func = name[9:] func = func[0:1].lower() + func[1:] elif name[0:19] == "virDomainCheckpoint": func = name[19:] func = func[0:1].lower() + func[1:] elif name[0:29] == "virDomainSnapshotLookupByName": func = name[9:] func = func[0:1].lower() + func[1:] elif name[0:26] == "virDomainSnapshotListNames": func = name[9:] func = func[0:1].lower() + func[1:] elif name[0:28] == "virDomainSnapshotNumChildren": func = name[17:] func = func[0:1].lower() + func[1:] elif name[0:20] == "virDomainSnapshotNum": func = name[9:] func = func[0:1].lower() + func[1:] elif name[0:26] == "virDomainSnapshotCreateXML": func = name[9:] func = func[0:1].lower() + func[1:] elif name[0:24] == "virDomainSnapshotCurrent": func = name[9:] func = func[0:1].lower() + func[1:] elif name[0:17] == "virDomainSnapshot": func = name[17:] func = func[0:1].lower() + func[1:] elif name[0:9] == "virDomain": func = name[9:] func = func[0:1].lower() + func[1:] elif name[0:17] == "virNetworkPortGet": func = name[17:] func = func[0:1].lower() + func[1:] elif name[0:13] == "virNetworkGet": func = name[13:] func = func[0:1].lower() + func[1:] func = func.replace("dHCP", "DHCP") elif name[0:14] == "virNetworkPort": func = name[14:] func = func[0:1].lower() + func[1:] elif name[0:10] == "virNetwork": func = name[10:] func = func[0:1].lower() + func[1:] elif name[0:15] == "virInterfaceGet": func = name[15:] func = func[0:1].lower() + func[1:] elif name[0:12] == "virInterface": func = name[12:] func = func[0:1].lower() + func[1:] elif name[0:12] == 'virSecretGet': func = name[12:] func = func[0:1].lower() + func[1:] elif name[0:9] == 'virSecret': func = name[9:] func = func[0:1].lower() + func[1:] elif name[0:21] == 'virNWFilterBindingGet': func = name[21:] func = func[0:1].lower() + func[1:] elif name[0:18] == 'virNWFilterBinding': func = name[18:] func = func[0:1].lower() + func[1:] elif name[0:14] == 'virNWFilterGet': func = name[14:] func = func[0:1].lower() + func[1:] elif name[0:11] == 'virNWFilter': func = name[11:] func = func[0:1].lower() + func[1:] elif name[0:12] == 'virStreamNew': func = "newStream" elif name[0:9] == 'virStream': func = name[9:] func = func[0:1].lower() + func[1:] elif name[0:17] == "virStoragePoolGet": func = name[17:] func = func[0:1].lower() + func[1:] elif name[0:14] == "virStoragePool": func = name[14:] func = func[0:1].lower() + func[1:] elif name[0:16] == "virStorageVolGet": func = name[16:] func = func[0:1].lower() + func[1:] elif name[0:13] == "virStorageVol": func = name[13:] func = func[0:1].lower() + func[1:] elif name[0:16] == "virNodeDeviceGet": func = name[16].lower() + name[17:] elif name[0:19] == "virNodeDeviceLookup": func = name[3].lower() + name[4:] elif name[0:22] == "virNodeDeviceCreateXML": func = name[3].lower() + name[4:] elif name[0:19] == "virNodeDeviceDefine": func = name[3].lower() + name[4:] elif name[0:13] == "virNodeDevice": func = name[13].lower() + name[14:] elif name[0:7] == "virNode": func = name[7:] func = func[0:1].lower() + func[1:] elif name[0:10] == "virConnect": func = name[10:] func = func[0:1].lower() + func[1:] elif name[0:3] == "xml": func = name[3:] func = func[0:1].lower() + func[1:] else: func = name if func == "iD": func = "ID" if func == "uUID": func = "UUID" if func == "uUIDString": func = "UUIDString" if func == "oSType": func = "OSType" if func == "xMLDesc": func = "XMLDesc" if func == "mACString": func = "MACString" return func def functionSortKey(info: Tuple) -> Tuple[str, str]: (index, func, name, ret, args, filename, mod) = info return func, filename def writeDoc(module: str, name: str, args: List[ArgumentType], indent: str, output: IO) -> None: if not functions[name][0]: return val = functions[name][0] val = val.replace("NULL", "None") sep = '\n%s' % (indent,) output.write('%s"""%s """\n' % (indent, sep.join(val.splitlines()))) def emit_py_code(module: str) -> None: package = module.replace('-', '_') if module == "libvirt": pymod = "libvirtmod" cygmod = "cygvirtmod" elif module == "libvirt-lxc": pymod = "libvirtmod_lxc" cygmod = "cygvirtmod_lxc" elif module == "libvirt-qemu": pymod = "libvirtmod_qemu" cygmod = "cygvirtmod_qemu" else: raise Exception("Unknown module '%s'" % module) for tinfo in classes_type.values(): function_classes[tinfo[2]] = [] # # Build the list of C types to look for ordered to start # with primary classes # ctypes = [] # type: List[str] classes_list = [] # type: List[str] ctypes_processed = set() # type: Set[str] classes_processed = set() # type: Set[str] for classe in primary_classes: classes_list.append(classe) classes_processed.add(classe) for type, tinfo in classes_type.items(): if tinfo[2] == classe: ctypes.append(type) ctypes_processed.add(type) for type, tinfo in classes_type.items(): if type in ctypes_processed: continue if tinfo[2] not in classes_processed: classes_list.append(tinfo[2]) classes_processed.add(tinfo[2]) ctypes.append(type) ctypes_processed.add(type) for name, (desc, ret, args, file, mod, cond) in functions.items(): if skip_py_impl(name): continue for type in ctypes: classe = classes_type[type][2] if name[0:3] == "vir" and len(args) >= 1 and args[0][1] == type: func = nameFixup(name, classe, type, file) info = (0, func, name, ret, args, file, mod) function_classes[classe].append(info) break elif name[0:3] == "vir" and len(args) >= 2 and args[1][1] == type \ and file != "python_accessor" and name not in function_skip_index_one: func = nameFixup(name, classe, type, file) info = (1, func, name, ret, args, file, mod) function_classes[classe].append(info) break else: func = nameFixup(name, "None", file, file) info = (0, func, name, ret, args, file, mod) function_classes['None'].append(info) classes_file = "build/%s.py" % package extra_file = "%s-override.py" % module extra = None classes = open(classes_file, "w") if os.path.exists(extra_file): extra = open(extra_file, "r") classes.write("#\n") classes.write("# WARNING WARNING WARNING WARNING\n") classes.write("#\n") classes.write("# This file is automatically written by generator.py. Any changes\n") classes.write("# made here will be lost.\n") classes.write("#\n") classes.write("# To change the manually written methods edit %s-override.py\n" % (module,)) classes.write("# To change the automatically written methods edit generator.py\n") classes.write("#\n") classes.write("# WARNING WARNING WARNING WARNING\n") classes.write("#\n") classes.write("try:\n") classes.write(" import %s # type: ignore\n" % pymod) classes.write("except ImportError as lib_e:\n") classes.write(" try:\n") classes.write(" import %s as %s # type: ignore\n" % (cygmod, pymod)) classes.write(" except ImportError as cyg_e:\n") classes.write(" if \"No module named\" in str(cyg_e):\n") classes.write(" raise lib_e\n\n") if module != "libvirt": classes.write("import libvirt\n") classes.write("\n") if extra: classes.write("# WARNING WARNING WARNING WARNING\n") classes.write("#\n") classes.write("# Manually written part of python bindings for %s\n" % module) classes.writelines(extra.readlines()) classes.write("#\n") classes.write("# WARNING WARNING WARNING WARNING\n") classes.write("#\n") classes.write("# Automatically written part of python bindings for %s\n" % module) classes.write("#\n") classes.write("# WARNING WARNING WARNING WARNING\n") if extra: extra.close() if "None" in function_classes: flist = function_classes["None"] oldfile = "" for (index, func, name, ret, args, file, mod) in sorted(flist, key=functionSortKey): if file != oldfile: classes.write("#\n# Functions from module %s\n#\n\n" % file) oldfile = file classes.write("def %s(" % func) for n, (a_name, a_type, a_info) in enumerate(args): if n != 0: classes.write(", ") classes.write("%s" % a_name) if a_name == "flags" or is_optional_arg(a_info): if is_integral_type(a_type): classes.write("=0") else: classes.write("=None") classes.write("):\n") writeDoc(module, name, args, ' ', classes) for a_name, a_type, a_info in args: if a_type in classes_type: classes.write(" if %s is None:\n" " %s__o = None\n" % (a_name, a_name)) classes.write(" else:\n" " %s__o = %s%s\n" % (a_name, a_name, classes_type[a_type][0])) r_type, r_info, r_field = ret if r_type != "void": classes.write(" ret = ") else: classes.write(" ") classes.write("%s.%s(" % (pymod, name)) for n, (a_name, a_type, a_info) in enumerate(args): if n != 0: classes.write(", ") classes.write("%s" % a_name) if a_type in classes_type: classes.write("__o") classes.write(")\n") if r_type != "void": if r_type in classes_type: # # Raise an exception # if name in functions_noexcept: classes.write(" if ret is None:\n" " return None\n") else: classes.write( " if ret is None:\n" " raise libvirtError('%s() failed')\n" % (name)) tinfo = classes_type[r_type] classes.write(" return ") classes.write(tinfo[1] % {"o": "ret"}) classes.write("\n") # For functions returning an integral type there are # several things that we can do, depending on the # contents of functions_int_*: elif is_integral_type(r_type): if name not in functions_noexcept: test = functions_int_exception_test.get(name, functions_int_default_test) % ("ret",) classes.write( " if %s:\n" " raise libvirtError('%s() failed')\n" % (test, name)) classes.write(" return ret\n") elif is_python_noninteger_type(r_type): if name not in functions_noexcept: classes.write( " if ret is None:\n" " raise libvirtError('%s() failed')\n" % (name,)) classes.write(" return ret\n") else: classes.write(" return ret\n") classes.write("\n") modclasses = [] if module == "libvirt": modclasses = classes_list for classname in modclasses: PARENTS = { "virConnect": "self._conn", "virDomain": "self._dom", "virNetwork": "self._net", classname: "self", } if classname == "None": pass else: classes.write("class %s(object):\n" % (classname)) if classname == "virStorageVol": classes.write(" # The size (in bytes) of buffer used in sendAll(),\n") classes.write(" # recvAll(), sparseSendAll() and sparseRecvAll()\n") classes.write(" # methods. This corresponds to the size of payload\n") classes.write(" # of a stream packet.\n") classes.write(" streamBufSize = 262120\n\n") if classname in ["virDomain", "virNetwork", "virInterface", "virStoragePool", "virStorageVol", "virNodeDevice", "virSecret", "virStream", "virNWFilter", "virNWFilterBinding"]: classes.write(" def __init__(self, conn, _obj=None):\n") classes.write(" self._conn = conn\n") elif classname in ["virDomainCheckpoint", "virDomainSnapshot"]: classes.write(" def __init__(self, dom, _obj=None):\n") classes.write(" self._dom = dom\n") classes.write(" self._conn = dom.connect()\n") elif classname in ["virNetworkPort"]: classes.write(" def __init__(self, net, _obj=None) -> None:\n") classes.write(" self._net = net\n") classes.write(" self._conn = net.connect()\n") else: classes.write(" def __init__(self, _obj=None):\n") classes.write(" if type(_obj).__name__ not in [\"PyCapsule\", \"PyCObject\"]:\n") classes.write(" raise Exception(\"Expected a wrapped C Object but got %s\" % type(_obj))\n") classes.write(" self._o = _obj\n\n") destruct = None if classname in classes_destructors: classes.write(" def __del__(self):\n") classes.write(" if self._o is not None:\n") classes.write(" %s.%s(self._o)\n" % (pymod, classes_destructors[classname])) classes.write(" self._o = None\n\n") destruct = classes_destructors[classname] if classname not in class_skip_connect_impl: # Build python safe 'connect' method classes.write(" def connect(self):\n") classes.write(" return self._conn\n\n") if classname in class_domain_impl: classes.write(" def domain(self):\n") classes.write(" return self._dom\n\n") if classname in class_network_impl: classes.write(" def network(self):\n") classes.write(" return self._net\n\n") classes.write(" def c_pointer(self):\n") classes.write(" \"\"\"Get C pointer to underlying object\"\"\"\n") classes.write(" return %s.%s_pointer(self._o)\n\n" % (pymod, classname)) flist = function_classes[classname] oldfile = "" for (index, func, name, ret, args, file, mod) in sorted(flist, key=functionSortKey): # # Do not provide as method the destructors for the class # to avoid double free # if name == destruct: continue if file != oldfile: if file == "python_accessor": classes.write(" # accessors for %s\n" % (classname)) else: classes.write(" #\n") classes.write(" # %s functions from module %s\n" % ( classname, file)) classes.write(" #\n\n") oldfile = file classes.write(" def %s(self" % func) for n, (a_name, a_type, a_info) in enumerate(args): if n != index: classes.write(", %s" % a_name) if a_name == "flags" or is_optional_arg(a_info): if is_integral_type(a_type): classes.write("=0") else: classes.write("=None") classes.write("):\n") writeDoc(module, name, args, ' ', classes) for n, (a_name, a_type, a_info) in enumerate(args): if a_type in classes_type: if n != index: classes.write(" if %s is None:\n" " %s__o = None\n" % (a_name, a_name)) classes.write(" else:\n" " %s__o = %s%s\n" % (a_name, a_name, classes_type[a_type][0])) r_type, r_info, r_field = ret if r_type != "void": classes.write(" ret = ") else: classes.write(" ") classes.write("%s.%s(" % (pymod, name)) for n, (a_name, a_type, a_info) in enumerate(args): if n != 0: classes.write(", ") if n != index: classes.write("%s" % a_name) if a_type in classes_type: classes.write("__o") else: classes.write("self") if a_type in classes_type: classes.write(classes_type[a_type][0]) classes.write(")\n") if name == "virConnectClose": classes.write(" self._o = None\n") # For functions returning object types: if r_type != "void": if r_type in classes_type: # # Raise an exception # if name in functions_noexcept: classes.write( " if ret is None:\n" " return None\n") else: classes.write( " if ret is None:\n" " raise libvirtError('%s() failed')\n" % (name,)) # # generate the returned class wrapper for the object # tinfo = classes_type[r_type] classes.write(" __tmp = ") classes.write(tinfo[1] % {"o": "ret", "p": PARENTS[tinfo[3]]}) classes.write("\n") # # return the class # classes.write(" return __tmp\n") # For functions returning an integral type there # are several things that we can do, depending on # the contents of functions_int_*: elif is_integral_type(r_type): if name not in functions_noexcept: test = functions_int_exception_test.get(name, functions_int_default_test) % ("ret",) classes.write( " if %s:\n" " raise libvirtError('%s() failed')\n" % (test, name)) classes.write(" return ret\n") elif is_python_noninteger_type(r_type): if name not in functions_noexcept: classes.write( " if ret is None:\n" " raise libvirtError('%s() failed')\n" % (name,)) classes.write(" return ret\n") else: classes.write(" return ret\n") classes.write("\n") # Append ".py" to class def, iff it exists class_override = "%s-override-%s.py" % (module, classname) if os.path.exists(class_override): extra = open(class_override, "r") classes.write(" #\n") classes.write(" # %s methods from %s.py (hand coded)\n" % (classname, classname)) classes.write(" #\n") cached = None # Since we compile with older libvirt, we don't want to pull # in manually written python methods which call C methods # that don't exist. This code attempts to detect which # methods to skip by looking at the libvirtmod.XXXX calls def shouldSkip(lines: List[str]) -> bool: for line in lines: offset = line.find(pymod + ".") if offset != -1: func = line[offset + 11:] offset = func.find("(") func = func[0:offset] if not skip_c_impl(func) and func != "virConnectListDomains": return True return False for line in extra.readlines(): offset = line.find(" def ") if offset != -1: name = line[offset + 5:] offset = name.find("(") name = name[0:offset] if cached: if not shouldSkip(cached): classes.writelines(cached) if name == "__del__": cached = None classes.write(line) else: cached = [line] else: if cached: cached.append(line) else: classes.write(line) if cached is not None and not shouldSkip(cached): classes.writelines(cached) classes.write("\n") extra.close() direct_functions = {} if module != "libvirt": direct_functions = functions classes.write("#\n# Functions from module %s\n#\n\n" % module) # # Generate functions directly, no classes # for name, (desc, ret, args, file, mod, cond) in sorted(direct_functions.items()): if skip_py_impl(name): continue func = nameFixup(name, 'None', '', '') classes.write("def %s(" % func) for n, (a_name, a_type, a_info) in enumerate(args): if n != 0: classes.write(", ") classes.write("%s" % a_name) classes.write("):\n") writeDoc(module, name, args, ' ', classes) r_type, r_info, r_field = ret if r_type != "void": classes.write(" ret = ") else: classes.write(" ") classes.write("%s.%s(" % (pymod, name)) conn = None for n, (a_name, a_type, a_info) in enumerate(args): if a_type == "virConnectPtr": conn = a_name if n != 0: classes.write(", ") if a_type in ["virDomainPtr", "virConnectPtr"]: # FIXME: This might have problem if the function # has multiple args which are objects. classes.write("%s.%s" % (a_name, "_o")) else: classes.write("%s" % a_name) classes.write(")\n") if r_type != "void": classes.write(" if ret is None:\n" " raise libvirt.libvirtError('%s() failed')\n" % (name,)) if r_type == "virDomainPtr": classes.write(" __tmp = libvirt.virDomain(%s, _obj=ret)\n" % (conn,)) classes.write(" return __tmp\n") else: classes.write(" return ret\n") classes.write("\n") # # Generate enum constants # def enumsSortKey(data: Tuple[str, EnumValue]) -> Tuple[Union[int, float], str]: try: value = int(data[1]) # type: Union[int, float] except ValueError: value = float('inf') return value, data[0] # Resolve only one level of reference def resolveEnum(enum: EnumType, data: EnumType) -> EnumType: for name, val in enum.items(): try: int(val) except ValueError: enum[name] = data[val] # type: ignore return enum enumvals = list(enums.items()) # convert list of dicts to one dict enumData = {} # type: EnumType for type, enum in enumvals: enumData.update(enum) for type, enum in sorted(enumvals): classes.write("# %s\n" % type) items = sorted(resolveEnum(enum, enumData).items(), key=enumsSortKey) if items[-1][0].endswith('_LAST'): del items[-1] for name, value in items: classes.write("%s = %s\n" % (name, value)) classes.write("\n") if params: classes.write("# typed parameter names\n") for name, value in params: classes.write("%s = \"%s\"\n" % (name, value)) classes.close() if sys.argv[1] not in ["libvirt", "libvirt-lxc", "libvirt-qemu"]: print("ERROR: unknown module %s" % sys.argv[1]) sys.exit(1) load_apis(sys.argv[1], sys.argv[2]) if validate_functions() < 0: sys.exit(1) quiet = False if not os.path.exists("build"): os.mkdir("build") output = None if len(sys.argv) == 4: output = sys.argv[3] if output == "c" or output is None: emit_c_code(sys.argv[1]) if output == "py" or output is None: emit_py_code(sys.argv[1]) sys.exit(0)