waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

pyqt5.py (7343B)


      1 #!/usr/bin/env python
      2 # encoding: utf-8
      3 # Federico Pellegrin, 2016-2022 (fedepell) adapted for Python
      4 
      5 """
      6 This tool helps with finding Python Qt5 tools and libraries,
      7 and provides translation from QT5 files to Python code.
      8 
      9 The following snippet illustrates the tool usage::
     10 
     11 	def options(opt):
     12 		opt.load('py pyqt5')
     13 
     14 	def configure(conf):
     15 		conf.load('py pyqt5')
     16 
     17 	def build(bld):
     18 		bld(
     19 			features = 'py pyqt5',
     20 			source   = 'main.py textures.qrc aboutDialog.ui',
     21 		)
     22 
     23 Here, the UI description and resource files will be processed
     24 to generate code.
     25 
     26 Usage
     27 =====
     28 
     29 Load the "pyqt5" tool.
     30 
     31 Add into the sources list also the qrc resources files or ui5
     32 definition files and they will be translated into python code
     33 with the system tools (PyQt5, PySide2, PyQt4 are searched in this
     34 order) and then compiled
     35 """
     36 
     37 try:
     38 	from xml.sax import make_parser
     39 	from xml.sax.handler import ContentHandler
     40 except ImportError:
     41 	has_xml = False
     42 	ContentHandler = object
     43 else:
     44 	has_xml = True
     45 
     46 import os
     47 from waflib.Tools import python
     48 from waflib import Task, Options
     49 from waflib.TaskGen import feature, extension
     50 from waflib.Configure import conf
     51 from waflib import Logs
     52 
     53 EXT_RCC = ['.qrc']
     54 """
     55 File extension for the resource (.qrc) files
     56 """
     57 
     58 EXT_UI  = ['.ui']
     59 """
     60 File extension for the user interface (.ui) files
     61 """
     62 
     63 
     64 class XMLHandler(ContentHandler):
     65 	"""
     66 	Parses ``.qrc`` files
     67 	"""
     68 	def __init__(self):
     69 		self.buf = []
     70 		self.files = []
     71 	def startElement(self, name, attrs):
     72 		if name == 'file':
     73 			self.buf = []
     74 	def endElement(self, name):
     75 		if name == 'file':
     76 			self.files.append(str(''.join(self.buf)))
     77 	def characters(self, cars):
     78 		self.buf.append(cars)
     79 
     80 @extension(*EXT_RCC)
     81 def create_pyrcc_task(self, node):
     82 	"Creates rcc and py task for ``.qrc`` files"
     83 	rcnode = node.change_ext('.py')
     84 	self.create_task('pyrcc', node, rcnode)
     85 	if getattr(self, 'install_from', None):
     86 		self.install_from = self.install_from.get_bld()
     87 	else:
     88 		self.install_from = self.path.get_bld()
     89 	self.install_path = getattr(self, 'install_path', '${PYTHONDIR}')
     90 	self.process_py(rcnode)
     91 
     92 @extension(*EXT_UI)
     93 def create_pyuic_task(self, node):
     94 	"Create uic tasks and py for user interface ``.ui`` definition files"
     95 	uinode = node.change_ext('.py')
     96 	self.create_task('ui5py', node, uinode)
     97 	if getattr(self, 'install_from', None):
     98 		self.install_from = self.install_from.get_bld()
     99 	else:
    100 		self.install_from = self.path.get_bld()
    101 	self.install_path = getattr(self, 'install_path', '${PYTHONDIR}')
    102 	self.process_py(uinode)
    103 
    104 @extension('.ts')
    105 def add_pylang(self, node):
    106 	"""Adds all the .ts file into ``self.lang``"""
    107 	self.lang = self.to_list(getattr(self, 'lang', [])) + [node]
    108 
    109 @feature('pyqt5')
    110 def apply_pyqt5(self):
    111 	"""
    112 	The additional parameters are:
    113 
    114 	:param lang: list of translation files (\\*.ts) to process
    115 	:type lang: list of :py:class:`waflib.Node.Node` or string without the .ts extension
    116 	:param langname: if given, transform the \\*.ts files into a .qrc files to include in the binary file
    117 	:type langname: :py:class:`waflib.Node.Node` or string without the .qrc extension
    118 	"""
    119 	if getattr(self, 'lang', None):
    120 		qmtasks = []
    121 		for x in self.to_list(self.lang):
    122 			if isinstance(x, str):
    123 				x = self.path.find_resource(x + '.ts')
    124 			qmtasks.append(self.create_task('ts2qm', x, x.change_ext('.qm')))
    125 
    126 
    127 		if getattr(self, 'langname', None):
    128 			qmnodes = [k.outputs[0] for k in qmtasks]
    129 			rcnode = self.langname
    130 			if isinstance(rcnode, str):
    131 				rcnode = self.path.find_or_declare(rcnode + '.qrc')
    132 			t = self.create_task('qm2rcc', qmnodes, rcnode)
    133 			create_pyrcc_task(self, t.outputs[0])
    134 
    135 class pyrcc(Task.Task):
    136 	"""
    137 	Processes ``.qrc`` files
    138 	"""
    139 	color   = 'BLUE'
    140 	run_str = '${QT_PYRCC} ${QT_PYRCC_FLAGS} ${SRC} -o ${TGT}'
    141 	ext_out = ['.py']
    142 
    143 	def rcname(self):
    144 		return os.path.splitext(self.inputs[0].name)[0]
    145 
    146 	def scan(self):
    147 		"""Parse the *.qrc* files"""
    148 		if not has_xml:
    149 			Logs.error('No xml.sax support was found, rcc dependencies will be incomplete!')
    150 			return ([], [])
    151 
    152 		parser = make_parser()
    153 		curHandler = XMLHandler()
    154 		parser.setContentHandler(curHandler)
    155 		fi = open(self.inputs[0].abspath(), 'r')
    156 		try:
    157 			parser.parse(fi)
    158 		finally:
    159 			fi.close()
    160 
    161 		nodes = []
    162 		names = []
    163 		root = self.inputs[0].parent
    164 		for x in curHandler.files:
    165 			nd = root.find_resource(x)
    166 			if nd:
    167 				nodes.append(nd)
    168 			else:
    169 				names.append(x)
    170 		return (nodes, names)
    171 
    172 
    173 class ui5py(Task.Task):
    174 	"""
    175 	Processes ``.ui`` files for python
    176 	"""
    177 	color   = 'BLUE'
    178 	run_str = '${QT_PYUIC} ${QT_PYUIC_FLAGS} ${SRC} -o ${TGT}'
    179 	ext_out = ['.py']
    180 
    181 class ts2qm(Task.Task):
    182 	"""
    183 	Generates ``.qm`` files from ``.ts`` files
    184 	"""
    185 	color   = 'BLUE'
    186 	run_str = '${QT_LRELEASE} ${QT_LRELEASE_FLAGS} ${SRC} -qm ${TGT}'
    187 
    188 class qm2rcc(Task.Task):
    189 	"""
    190 	Generates ``.qrc`` files from ``.qm`` files
    191 	"""
    192 	color = 'BLUE'
    193 	after = 'ts2qm'
    194 	def run(self):
    195 		"""Create a qrc file including the inputs"""
    196 		txt = '\n'.join(['<file>%s</file>' % k.path_from(self.outputs[0].parent) for k in self.inputs])
    197 		code = '<!DOCTYPE RCC><RCC version="1.0">\n<qresource>\n%s\n</qresource>\n</RCC>' % txt
    198 		self.outputs[0].write(code)
    199 
    200 def configure(self):
    201 	self.find_pyqt5_binaries()
    202 
    203 	# warn about this during the configuration too
    204 	if not has_xml:
    205 		Logs.error('No xml.sax support was found, rcc dependencies will be incomplete!')
    206 
    207 @conf
    208 def find_pyqt5_binaries(self):
    209 	"""
    210 	Detects PyQt5 or PySide2 programs such as pyuic5/pyside2-uic, pyrcc5/pyside2-rcc
    211 	"""
    212 	env = self.env
    213 
    214 	if getattr(Options.options, 'want_pyqt5', True):
    215 		self.find_program(['pyuic5'], var='QT_PYUIC')
    216 		self.find_program(['pyrcc5'], var='QT_PYRCC')
    217 		self.find_program(['pylupdate5'], var='QT_PYLUPDATE')
    218 	elif getattr(Options.options, 'want_pyside2', True):
    219 		self.find_program(['pyside2-uic','uic-qt5'], var='QT_PYUIC')
    220 		self.find_program(['pyside2-rcc','rcc-qt5'], var='QT_PYRCC')
    221 		self.find_program(['pyside2-lupdate','lupdate-qt5'], var='QT_PYLUPDATE')
    222 	elif getattr(Options.options, 'want_pyqt4', True):
    223 		self.find_program(['pyuic4'], var='QT_PYUIC')
    224 		self.find_program(['pyrcc4'], var='QT_PYRCC')
    225 		self.find_program(['pylupdate4'], var='QT_PYLUPDATE')
    226 	else:
    227 		self.find_program(['pyuic5','pyside2-uic','pyuic4','uic-qt5'], var='QT_PYUIC')
    228 		self.find_program(['pyrcc5','pyside2-rcc','pyrcc4','rcc-qt5'], var='QT_PYRCC')
    229 		self.find_program(['pylupdate5', 'pyside2-lupdate','pylupdate4','lupdate-qt5'], var='QT_PYLUPDATE')
    230 
    231 	if not env.QT_PYUIC:
    232 		self.fatal('cannot find the uic compiler for python for qt5')
    233 
    234 	if not env.QT_PYRCC:
    235 		self.fatal('cannot find the rcc compiler for python for qt5')
    236 
    237 	self.find_program(['lrelease-qt5', 'lrelease'], var='QT_LRELEASE')
    238 
    239 def options(opt):
    240 	"""
    241 	Command-line options
    242 	"""
    243 	pyqt5opt=opt.add_option_group("Python QT5 Options")
    244 	pyqt5opt.add_option('--pyqt5-pyqt5', action='store_true', default=False, dest='want_pyqt5', help='use PyQt5 bindings as python QT5 bindings (default PyQt5 is searched first, PySide2 after, PyQt4 last)')
    245 	pyqt5opt.add_option('--pyqt5-pyside2', action='store_true', default=False, dest='want_pyside2', help='use PySide2 bindings as python QT5 bindings (default PyQt5 is searched first, PySide2 after, PyQt4 last)')
    246 	pyqt5opt.add_option('--pyqt5-pyqt4', action='store_true', default=False, dest='want_pyqt4', help='use PyQt4 bindings as python QT5 bindings (default PyQt5 is searched first, PySide2 after, PyQt4 last)')