third_party/waf: upgrade to waf 2.0.8
[samba.git] / third_party / waf / waflib / extras / pyqt5.py
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Federico Pellegrin, 2016-2018 (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} ${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} ${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_pyside2', True):
215                 self.find_program(['pyside2-uic'], var='QT_PYUIC')
216                 self.find_program(['pyside2-rcc'], var='QT_PYRCC')
217                 self.find_program(['pyside2-lupdate'], var='QT_PYLUPDATE')
218         elif getattr(Options.options, 'want_pyqt4', True):
219                 self.find_program(['pyuic4'], var='QT_PYUIC')
220                 self.find_program(['pyrcc4'], var='QT_PYRCC')
221                 self.find_program(['pylupdate4'], var='QT_PYLUPDATE')
222         else:
223                 self.find_program(['pyuic5','pyside2-uic','pyuic4'], var='QT_PYUIC')
224                 self.find_program(['pyrcc5','pyside2-rcc','pyrcc4'], var='QT_PYRCC')
225                 self.find_program(['pylupdate5', 'pyside2-lupdate','pylupdate4'], var='QT_PYLUPDATE')
226
227         if not env.QT_PYUIC:
228                 self.fatal('cannot find the uic compiler for python for qt5')
229
230         if not env.QT_PYUIC:
231                 self.fatal('cannot find the rcc compiler for python for qt5')
232
233         self.find_program(['lrelease-qt5', 'lrelease'], var='QT_LRELEASE')
234
235 def options(opt):
236         """
237         Command-line options
238         """
239         pyqt5opt=opt.add_option_group("Python QT5 Options")
240         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)')
241         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)')