Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/numpy/testing/_private/extbuild.py: 14%

93 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-23 06:06 +0000

1""" 

2Build a c-extension module on-the-fly in tests. 

3See build_and_import_extensions for usage hints 

4 

5""" 

6 

7import os 

8import pathlib 

9import sys 

10import sysconfig 

11 

12__all__ = ['build_and_import_extension', 'compile_extension_module'] 

13 

14 

15def build_and_import_extension( 

16 modname, functions, *, prologue="", build_dir=None, 

17 include_dirs=[], more_init=""): 

18 """ 

19 Build and imports a c-extension module `modname` from a list of function 

20 fragments `functions`. 

21 

22 

23 Parameters 

24 ---------- 

25 functions : list of fragments 

26 Each fragment is a sequence of func_name, calling convention, snippet. 

27 prologue : string 

28 Code to precede the rest, usually extra ``#include`` or ``#define`` 

29 macros. 

30 build_dir : pathlib.Path 

31 Where to build the module, usually a temporary directory 

32 include_dirs : list 

33 Extra directories to find include files when compiling 

34 more_init : string 

35 Code to appear in the module PyMODINIT_FUNC 

36 

37 Returns 

38 ------- 

39 out: module 

40 The module will have been loaded and is ready for use 

41 

42 Examples 

43 -------- 

44 >>> functions = [("test_bytes", "METH_O", \"\"\" 

45 if ( !PyBytesCheck(args)) { 

46 Py_RETURN_FALSE; 

47 } 

48 Py_RETURN_TRUE; 

49 \"\"\")] 

50 >>> mod = build_and_import_extension("testme", functions) 

51 >>> assert not mod.test_bytes(u'abc') 

52 >>> assert mod.test_bytes(b'abc') 

53 """ 

54 from distutils.errors import CompileError 

55 

56 body = prologue + _make_methods(functions, modname) 

57 init = """PyObject *mod = PyModule_Create(&moduledef); 

58 """ 

59 if not build_dir: 

60 build_dir = pathlib.Path('.') 

61 if more_init: 

62 init += """#define INITERROR return NULL 

63 """ 

64 init += more_init 

65 init += "\nreturn mod;" 

66 source_string = _make_source(modname, init, body) 

67 try: 

68 mod_so = compile_extension_module( 

69 modname, build_dir, include_dirs, source_string) 

70 except CompileError as e: 

71 # shorten the exception chain 

72 raise RuntimeError(f"could not compile in {build_dir}:") from e 

73 import importlib.util 

74 spec = importlib.util.spec_from_file_location(modname, mod_so) 

75 foo = importlib.util.module_from_spec(spec) 

76 spec.loader.exec_module(foo) 

77 return foo 

78 

79 

80def compile_extension_module( 

81 name, builddir, include_dirs, 

82 source_string, libraries=[], library_dirs=[]): 

83 """ 

84 Build an extension module and return the filename of the resulting 

85 native code file. 

86 

87 Parameters 

88 ---------- 

89 name : string 

90 name of the module, possibly including dots if it is a module inside a 

91 package. 

92 builddir : pathlib.Path 

93 Where to build the module, usually a temporary directory 

94 include_dirs : list 

95 Extra directories to find include files when compiling 

96 libraries : list 

97 Libraries to link into the extension module 

98 library_dirs: list 

99 Where to find the libraries, ``-L`` passed to the linker 

100 """ 

101 modname = name.split('.')[-1] 

102 dirname = builddir / name 

103 dirname.mkdir(exist_ok=True) 

104 cfile = _convert_str_to_file(source_string, dirname) 

105 include_dirs = include_dirs + [sysconfig.get_config_var('INCLUDEPY')] 

106 

107 return _c_compile( 

108 cfile, outputfilename=dirname / modname, 

109 include_dirs=include_dirs, libraries=[], library_dirs=[], 

110 ) 

111 

112 

113def _convert_str_to_file(source, dirname): 

114 """Helper function to create a file ``source.c`` in `dirname` that contains 

115 the string in `source`. Returns the file name 

116 """ 

117 filename = dirname / 'source.c' 

118 with filename.open('w') as f: 

119 f.write(str(source)) 

120 return filename 

121 

122 

123def _make_methods(functions, modname): 

124 """ Turns the name, signature, code in functions into complete functions 

125 and lists them in a methods_table. Then turns the methods_table into a 

126 ``PyMethodDef`` structure and returns the resulting code fragment ready 

127 for compilation 

128 """ 

129 methods_table = [] 

130 codes = [] 

131 for funcname, flags, code in functions: 

132 cfuncname = "%s_%s" % (modname, funcname) 

133 if 'METH_KEYWORDS' in flags: 

134 signature = '(PyObject *self, PyObject *args, PyObject *kwargs)' 

135 else: 

136 signature = '(PyObject *self, PyObject *args)' 

137 methods_table.append( 

138 "{\"%s\", (PyCFunction)%s, %s}," % (funcname, cfuncname, flags)) 

139 func_code = """ 

140 static PyObject* {cfuncname}{signature} 

141 {{ 

142 {code} 

143 }} 

144 """.format(cfuncname=cfuncname, signature=signature, code=code) 

145 codes.append(func_code) 

146 

147 body = "\n".join(codes) + """ 

148 static PyMethodDef methods[] = { 

149 %(methods)s 

150 { NULL } 

151 }; 

152 static struct PyModuleDef moduledef = { 

153 PyModuleDef_HEAD_INIT, 

154 "%(modname)s", /* m_name */ 

155 NULL, /* m_doc */ 

156 -1, /* m_size */ 

157 methods, /* m_methods */ 

158 }; 

159 """ % dict(methods='\n'.join(methods_table), modname=modname) 

160 return body 

161 

162 

163def _make_source(name, init, body): 

164 """ Combines the code fragments into source code ready to be compiled 

165 """ 

166 code = """ 

167 #include <Python.h> 

168 

169 %(body)s 

170 

171 PyMODINIT_FUNC 

172 PyInit_%(name)s(void) { 

173 %(init)s 

174 } 

175 """ % dict( 

176 name=name, init=init, body=body, 

177 ) 

178 return code 

179 

180 

181def _c_compile(cfile, outputfilename, include_dirs=[], libraries=[], 

182 library_dirs=[]): 

183 if sys.platform == 'win32': 

184 compile_extra = ["/we4013"] 

185 link_extra = ["/LIBPATH:" + os.path.join(sys.base_prefix, 'libs')] 

186 elif sys.platform.startswith('linux'): 

187 compile_extra = [ 

188 "-O0", "-g", "-Werror=implicit-function-declaration", "-fPIC"] 

189 link_extra = None 

190 else: 

191 compile_extra = link_extra = None 

192 pass 

193 if sys.platform == 'win32': 

194 link_extra = link_extra + ['/DEBUG'] # generate .pdb file 

195 if sys.platform == 'darwin': 

196 # support Fink & Darwinports 

197 for s in ('/sw/', '/opt/local/'): 

198 if (s + 'include' not in include_dirs 

199 and os.path.exists(s + 'include')): 

200 include_dirs.append(s + 'include') 

201 if s + 'lib' not in library_dirs and os.path.exists(s + 'lib'): 

202 library_dirs.append(s + 'lib') 

203 

204 outputfilename = outputfilename.with_suffix(get_so_suffix()) 

205 saved_environ = os.environ.copy() 

206 try: 

207 build( 

208 cfile, outputfilename, 

209 compile_extra, link_extra, 

210 include_dirs, libraries, library_dirs) 

211 finally: 

212 # workaround for a distutils bugs where some env vars can 

213 # become longer and longer every time it is used 

214 for key, value in saved_environ.items(): 

215 if os.environ.get(key) != value: 

216 os.environ[key] = value 

217 return outputfilename 

218 

219 

220def build(cfile, outputfilename, compile_extra, link_extra, 

221 include_dirs, libraries, library_dirs): 

222 "cd into the directory where the cfile is, use distutils to build" 

223 from numpy.distutils.ccompiler import new_compiler 

224 

225 compiler = new_compiler(force=1, verbose=2) 

226 compiler.customize('') 

227 objects = [] 

228 

229 old = os.getcwd() 

230 os.chdir(cfile.parent) 

231 try: 

232 res = compiler.compile( 

233 [str(cfile.name)], 

234 include_dirs=include_dirs, 

235 extra_preargs=compile_extra 

236 ) 

237 objects += [str(cfile.parent / r) for r in res] 

238 finally: 

239 os.chdir(old) 

240 

241 compiler.link_shared_object( 

242 objects, str(outputfilename), 

243 libraries=libraries, 

244 extra_preargs=link_extra, 

245 library_dirs=library_dirs) 

246 

247 

248def get_so_suffix(): 

249 ret = sysconfig.get_config_var('EXT_SUFFIX') 

250 assert ret 

251 return ret