1 /*
2 * The transitional Python bindings for the C parts of the sip code generator.
3 *
4 * Copyright (c) 2019 Riverbank Computing Limited <info@riverbankcomputing.com>
5 *
6 * This file is part of SIP.
7 *
8 * This copy of SIP is licensed for use under the terms of the SIP License
9 * Agreement. See the file LICENSE for more details.
10 *
11 * This copy of SIP may also used under the terms of the GNU General Public
12 * License v2 or v3 as published by the Free Software Foundation which can be
13 * found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
14 *
15 * SIP is supplied WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 */
18
19
20 #include <assert.h>
21 #include <setjmp.h>
22 #include <stdarg.h>
23 #include <stdio.h>
24 #include <string.h>
25
26 #include <Python.h>
27
28 #include "sip.h"
29
30
31 /* Globals - see sip.h for their meanings. */
32 unsigned sipVersion;
33 const char *sipVersionStr;
34 unsigned abiMajor;
35 unsigned abiMinor;
36 stringList *includeDirList;
37
38 /* Support for fatal error handling. */
39 #define NO_EXCEPTION 0 /* No exception has been set. */
40 #define EXCEPTION_SET 1 /* An exception has been set. */
41 #define EXCEPTION_NEEDED 2 /* An exception needs to be set. */
42
43 static char error_text[1000];
44 static jmp_buf on_fatal_error;
45 static PyObject *exception_type;
46 static void raise_exception(int action);
47
48 /* Forward declarations. */
49 static PyObject *py_set_globals(PyObject *self, PyObject *args);
50 static PyObject *py_parse(PyObject *self, PyObject *args);
51 static PyObject *py_generateCode(PyObject *self, PyObject *args);
52 static PyObject *py_generateExtracts(PyObject *self, PyObject *args);
53 static PyObject *py_generateAPI(PyObject *self, PyObject *args);
54 static PyObject *py_generateXML(PyObject *self, PyObject *args);
55 static PyObject *py_generateTypeHints(PyObject *self, PyObject *args);
56
57 static int fs_convertor(PyObject *obj, char **fsp);
58 static int sipSpec_convertor(PyObject *obj, sipSpec **ptp);
59 static int stringList_convertor(PyObject *obj, stringList **slp);
60 static PyObject *stringList_convert_from(stringList *sl);
61 static int extend_stringList(stringList **slp, PyObject *py_list);
62 static void exception_set(void);
63
64
65 /*
66 * The sipbuild.code_generator module initialisation function.
67 */
PyInit_code_generator(void)68 PyMODINIT_FUNC PyInit_code_generator(void)
69 {
70 static PyMethodDef methods[] = {
71 {"set_globals", py_set_globals, METH_VARARGS, NULL},
72 {"parse", py_parse, METH_VARARGS, NULL},
73 {"generateCode", py_generateCode, METH_VARARGS, NULL},
74 {"generateExtracts", py_generateExtracts, METH_VARARGS, NULL},
75 {"generateAPI", py_generateAPI, METH_VARARGS, NULL},
76 {"generateXML", py_generateXML, METH_VARARGS, NULL},
77 {"generateTypeHints", py_generateTypeHints, METH_VARARGS, NULL},
78 {NULL, NULL, 0, NULL},
79 };
80
81 static PyModuleDef module_def = {
82 PyModuleDef_HEAD_INIT,
83 "sipbuild.code_generator", /* m_name */
84 NULL, /* m_doc */
85 -1, /* m_size */
86 methods, /* m_methods */
87 NULL, /* m_reload */
88 NULL, /* m_traverse */
89 NULL, /* m_clear */
90 NULL, /* m_free */
91 };
92
93 return PyModule_Create(&module_def);
94 }
95
96
97 /*
98 * Set the globals.
99 */
py_set_globals(PyObject * self,PyObject * args)100 static PyObject *py_set_globals(PyObject *self, PyObject *args)
101 {
102 if (!PyArg_ParseTuple(args, "IsIIOO&",
103 &sipVersion,
104 &sipVersionStr,
105 &abiMajor,
106 &abiMinor,
107 &exception_type,
108 stringList_convertor, &includeDirList))
109 return NULL;
110
111 Py_INCREF(exception_type);
112
113 Py_RETURN_NONE;
114 }
115
116
117 /*
118 * Wrapper around parse().
119 */
py_parse(PyObject * self,PyObject * args)120 static PyObject *py_parse(PyObject *self, PyObject *args)
121 {
122 sipSpec *pt;
123 FILE *file;
124 char *filename;
125 stringList *versions, *backstops, *xfeatures, *sip_files;
126 int strict, protHack, action;
127
128 if (!PyArg_ParseTuple(args, "O&pO&O&O&p",
129 fs_convertor, &filename,
130 &strict,
131 stringList_convertor, &versions,
132 stringList_convertor, &backstops,
133 stringList_convertor, &xfeatures,
134 &protHack))
135 return NULL;
136
137 pt = sipMalloc(sizeof (sipSpec));
138
139 if (filename != NULL)
140 {
141 file = NULL;
142 }
143 else
144 {
145 file = stdin;
146 filename = "stdin";
147 }
148
149 if ((action = setjmp(on_fatal_error)) != NO_EXCEPTION)
150 {
151 raise_exception(action);
152 return NULL;
153 }
154
155 sip_files = NULL;
156
157 parse(pt, file, filename, strict, &versions, backstops, &xfeatures,
158 protHack, &sip_files);
159
160 transform(pt, strict);
161
162 return Py_BuildValue("(NsiNNN)", PyCapsule_New(pt, NULL, NULL),
163 pt->module->fullname->text,
164 (isComposite(pt->module) || useLimitedAPI(pt->module)),
165 stringList_convert_from(sip_files),
166 stringList_convert_from(versions),
167 stringList_convert_from(xfeatures));
168 }
169
170
171 /*
172 * Wrapper around generateCode().
173 */
py_generateCode(PyObject * self,PyObject * args)174 static PyObject *py_generateCode(PyObject *self, PyObject *args)
175 {
176 sipSpec *pt;
177 char *codeDir, *srcSuffix, *sipName;
178 const char *api_header;
179 int exceptions, tracing, releaseGIL, parts, docs, py_debug, action;
180 stringList *versions, *xfeatures, *sources;
181
182 if (!PyArg_ParseTuple(args, "O&O&O&pppiO&O&ppz",
183 sipSpec_convertor, &pt,
184 fs_convertor, &codeDir,
185 fs_convertor, &srcSuffix,
186 &exceptions,
187 &tracing,
188 &releaseGIL,
189 &parts,
190 stringList_convertor, &versions,
191 stringList_convertor, &xfeatures,
192 &docs,
193 &py_debug,
194 &sipName))
195 return NULL;
196
197 if ((action = setjmp(on_fatal_error)) != NO_EXCEPTION)
198 {
199 raise_exception(action);
200 return NULL;
201 }
202
203 if (sipName != NULL && sipName[0] == '\0')
204 sipName = NULL;
205
206 sources = generateCode(pt, codeDir, srcSuffix, exceptions, tracing,
207 releaseGIL, parts, versions, xfeatures, docs, py_debug, sipName,
208 &api_header);
209
210 return Py_BuildValue("(sN)", api_header, stringList_convert_from(sources));
211 }
212
213
214 /*
215 * Wrapper around generateExtracts().
216 */
py_generateExtracts(PyObject * self,PyObject * args)217 static PyObject *py_generateExtracts(PyObject *self, PyObject *args)
218 {
219 sipSpec *pt;
220 stringList *extracts;
221 int action;
222
223 if (!PyArg_ParseTuple(args, "O&O&",
224 sipSpec_convertor, &pt,
225 stringList_convertor, &extracts))
226 return NULL;
227
228 if ((action = setjmp(on_fatal_error)) != NO_EXCEPTION)
229 {
230 raise_exception(action);
231 return NULL;
232 }
233
234 generateExtracts(pt, extracts);
235
236 Py_RETURN_NONE;
237 }
238
239
240 /*
241 * Wrapper around generateAPI().
242 */
py_generateAPI(PyObject * self,PyObject * args)243 static PyObject *py_generateAPI(PyObject *self, PyObject *args)
244 {
245 sipSpec *pt;
246 char *apiFile;
247 int action;
248
249 if (!PyArg_ParseTuple(args, "O&O&",
250 sipSpec_convertor, &pt,
251 fs_convertor, &apiFile))
252 return NULL;
253
254 if ((action = setjmp(on_fatal_error)) != NO_EXCEPTION)
255 {
256 raise_exception(action);
257 return NULL;
258 }
259
260 generateAPI(pt, pt->module, apiFile);
261
262 Py_RETURN_NONE;
263 }
264
265
266 /*
267 * Wrapper around generateXML().
268 */
py_generateXML(PyObject * self,PyObject * args)269 static PyObject *py_generateXML(PyObject *self, PyObject *args)
270 {
271 sipSpec *pt;
272 char *xmlFile;
273 int action;
274
275 if (!PyArg_ParseTuple(args, "O&O&",
276 sipSpec_convertor, &pt,
277 fs_convertor, &xmlFile))
278 return NULL;
279
280 if ((action = setjmp(on_fatal_error)) != NO_EXCEPTION)
281 {
282 raise_exception(action);
283 return NULL;
284 }
285
286 generateXML(pt, pt->module, xmlFile);
287
288 Py_RETURN_NONE;
289 }
290
291
292 /*
293 * Wrapper around generateTypeHints().
294 */
py_generateTypeHints(PyObject * self,PyObject * args)295 static PyObject *py_generateTypeHints(PyObject *self, PyObject *args)
296 {
297 sipSpec *pt;
298 char *pyiFile;
299 int action;
300
301 if (!PyArg_ParseTuple(args, "O&O&",
302 sipSpec_convertor, &pt,
303 fs_convertor, &pyiFile))
304 return NULL;
305
306 if ((action = setjmp(on_fatal_error)) != NO_EXCEPTION)
307 {
308 raise_exception(action);
309 return NULL;
310 }
311
312 generateTypeHints(pt, pt->module, pyiFile);
313
314 Py_RETURN_NONE;
315 }
316
317
318 /*
319 * Convert a callable argument to a filesystem name.
320 */
fs_convertor(PyObject * obj,char ** fsp)321 static int fs_convertor(PyObject *obj, char **fsp)
322 {
323 PyObject *bytes;
324
325 if (obj == Py_None)
326 {
327 *fsp = NULL;
328 return 1;
329 }
330
331 if ((bytes = PyUnicode_EncodeFSDefault(obj)) == NULL)
332 return 0;
333
334 /* Leak the bytes object rather than strdup() its contents. */
335 *fsp = PyBytes_AS_STRING(bytes);
336
337 return 1;
338 }
339
340
341 /*
342 * Convert a callable argument to a sipSpec.
343 */
sipSpec_convertor(PyObject * obj,sipSpec ** ptp)344 static int sipSpec_convertor(PyObject *obj, sipSpec **ptp)
345 {
346 if (!PyCapsule_CheckExact(obj))
347 {
348 PyErr_SetString(PyExc_TypeError, "parse tree expected");
349 return 0;
350 }
351
352 if ((*ptp = (sipSpec *)PyCapsule_GetPointer(obj, NULL)) == NULL)
353 return 0;
354
355 return 1;
356 }
357
358
359 /*
360 * Convert a callable argument to a stringList.
361 */
stringList_convertor(PyObject * obj,stringList ** slp)362 static int stringList_convertor(PyObject *obj, stringList **slp)
363 {
364 *slp = NULL;
365
366 if (obj == Py_None)
367 return 1;
368
369 if (!PyList_Check(obj))
370 {
371 PyErr_SetString(PyExc_TypeError, "list of str expected");
372 return 0;
373 }
374
375 return extend_stringList(slp, obj);
376 }
377
378
379 /*
380 * Convert a stringList to a Python list of strings.
381 */
stringList_convert_from(stringList * sl)382 static PyObject *stringList_convert_from(stringList *sl)
383 {
384 PyObject *pyl;
385
386 if ((pyl = PyList_New(0)) == NULL)
387 return NULL;
388
389 while (sl != NULL)
390 {
391 PyObject *s;
392
393 if ((s = PyUnicode_DecodeLocale(sl->s, NULL)) == NULL)
394 {
395 Py_DECREF(pyl);
396 return NULL;
397 }
398
399 if (PyList_Append(pyl, s) < 0)
400 {
401 Py_DECREF(s);
402 Py_DECREF(pyl);
403 return NULL;
404 }
405
406 Py_DECREF(s);
407
408 sl = sl->next;
409 }
410
411 return pyl;
412 }
413
414
415 /*
416 * Extend a stringList by the contents fo a Python list of strings.
417 */
extend_stringList(stringList ** slp,PyObject * py_list)418 static int extend_stringList(stringList **slp, PyObject *py_list)
419 {
420 Py_ssize_t i;
421
422 for (i = 0; i < PyList_GET_SIZE(py_list); ++i)
423 {
424 PyObject *el = PyUnicode_EncodeLocale(PyList_GET_ITEM(py_list, i),
425 NULL);
426
427 if (el == NULL)
428 return 0;
429
430 appendString(slp, sipStrdup(PyBytes_AS_STRING(el)));
431 }
432
433 return 1;
434 }
435
436
437 /*
438 * Display a one line error message describing a fatal error. This does not
439 * return.
440 */
fatal(const char * fmt,...)441 void fatal(const char *fmt, ...)
442 {
443 va_list ap;
444 size_t used = strlen(error_text);
445 size_t room = sizeof (error_text) - used - 1;
446
447 va_start(ap,fmt);
448 vsnprintf(&error_text[used], room, fmt, ap);
449 va_end(ap);
450
451 /* Raise an exception. */
452 longjmp(on_fatal_error, EXCEPTION_NEEDED);
453 }
454
455
456 /*
457 * Append to the current error message.
458 */
fatalAppend(const char * fmt,...)459 void fatalAppend(const char *fmt, ...)
460 {
461 va_list ap;
462 size_t used = strlen(error_text);
463 size_t room = sizeof (error_text) - used - 1;
464
465 va_start(ap, fmt);
466 vsnprintf(&error_text[used], room, fmt, ap);
467 va_end(ap);
468 }
469
470
471 /*
472 * Raise an exception if needed.
473 */
raise_exception(int action)474 static void raise_exception(int action)
475 {
476 if (action == EXCEPTION_NEEDED)
477 PyErr_SetString(exception_type, error_text);
478
479 /*
480 * The error text buffer may be used more than once as the Python exception
481 * could be ignored.
482 */
483 error_text[0] = '\0';
484 }
485
486
487 /*
488 * Return to the Python interpreter after an exception has been set.
489 */
exception_set(void)490 static void exception_set(void)
491 {
492 longjmp(on_fatal_error, EXCEPTION_SET);
493 }
494
495
496 /*
497 * Display a warning message.
498 */
warning(Warning w,const char * fmt,...)499 void warning(Warning w, const char *fmt, ...)
500 {
501 static char warning_text[1000];
502
503 va_list ap;
504 size_t used = strlen(warning_text);
505 size_t room = sizeof (warning_text) - used - 1;
506
507 va_start(ap, fmt);
508 vsnprintf(&warning_text[used], room, fmt, ap);
509 va_end(ap);
510
511 if (strchr(fmt, '\n') != NULL)
512 {
513 int ret;
514
515 ret = PyErr_WarnEx(
516 (w == DeprecationWarning ? PyExc_FutureWarning :
517 PyExc_UserWarning),
518 warning_text, 1);
519
520 warning_text[0] = '\0';
521
522 if (ret < 0)
523 exception_set();
524 }
525 }
526
527
528 /*
529 * Get the configuration of a set of bindings and update the list of tags and
530 * disabled features.
531 */
get_bindings_configuration(const char * sip_file,stringList ** tags,stringList ** disabled)532 void get_bindings_configuration(const char *sip_file, stringList **tags,
533 stringList **disabled)
534 {
535 static PyObject *get_bindings_configuration = NULL;
536
537 PyObject *res, *py_tags, *py_disabled;
538
539 /* Get the Python helper. */
540 if (get_bindings_configuration == NULL)
541 {
542 PyObject *mod;
543
544 if ((mod = PyImport_ImportModule("sipbuild.helpers")) == NULL)
545 exception_set();
546
547 get_bindings_configuration = PyObject_GetAttrString(mod,
548 "get_bindings_configuration");
549 Py_DECREF(mod);
550
551 if (get_bindings_configuration == NULL)
552 exception_set();
553 }
554
555 /* Call the helper. */
556 res = PyObject_CallFunction(get_bindings_configuration, "IIsN", abiMajor,
557 abiMinor, sip_file, stringList_convert_from(includeDirList));
558
559 if (res == NULL)
560 exception_set();
561
562 /* The result should be a 2-tuple of lists of strings. */
563 assert(PyTuple_Check(res));
564 assert(PyTuple_GET_SIZE(res) == 2);
565
566 py_tags = PyTuple_GET_ITEM(res, 0);
567 assert(PyList_Check(py_tags));
568
569 if (!extend_stringList(tags, py_tags))
570 {
571 Py_DECREF(res);
572 exception_set();
573 }
574
575 py_disabled = PyTuple_GET_ITEM(res, 1);
576 assert(PyList_Check(py_disabled));
577
578 if (!extend_stringList(disabled, py_disabled))
579 {
580 Py_DECREF(res);
581 exception_set();
582 }
583
584 /* Tidy up. */
585 Py_DECREF(res);
586 }
587