blob: 4351c0f8d5923eb71eae925839e2052f0c8af47e [file] [log] [blame]
Georg Brandlb533e262008-05-25 18:19:30 +00001import sys
2import os
Georg Brandlb533e262008-05-25 18:19:30 +00003import shutil
4from io import StringIO
5
Barry Warsaw8cf4eae2010-10-16 01:04:07 +00006from distutils.core import Distribution
Georg Brandlb533e262008-05-25 18:19:30 +00007from distutils.command.build_ext import build_ext
Tarek Ziadé36797272010-07-22 12:50:05 +00008from distutils import sysconfig
Tarek Ziadéc1375d52009-02-14 14:35:51 +00009from distutils.tests.support import TempdirManager
Tarek Ziadéb2e36f12009-03-31 22:37:55 +000010from distutils.tests.support import LoggingSilencer
11from distutils.extension import Extension
Barry Warsaw8cf4eae2010-10-16 01:04:07 +000012from distutils.errors import (
13 CompileError, DistutilsSetupError, UnknownFileError)
Georg Brandlb533e262008-05-25 18:19:30 +000014
15import unittest
16from test import support
17
Christian Heimes3e7e0692008-11-25 21:21:32 +000018# http://bugs.python.org/issue4373
19# Don't load the xx module more than once.
20ALREADY_TESTED = False
21
Neil Schemenauerd8f63bb2009-02-06 21:42:05 +000022def _get_source_filename():
23 srcdir = sysconfig.get_config_var('srcdir')
24 return os.path.join(srcdir, 'Modules', 'xxmodule.c')
25
Tarek Ziadé36797272010-07-22 12:50:05 +000026class BuildExtTestCase(TempdirManager,
27 LoggingSilencer,
28 unittest.TestCase):
Georg Brandlb533e262008-05-25 18:19:30 +000029 def setUp(self):
30 # Create a simple test environment
31 # Note that we're making changes to sys.path
Tarek Ziadé38e3d512009-02-27 12:58:56 +000032 super(BuildExtTestCase, self).setUp()
Tarek Ziadéc1375d52009-02-14 14:35:51 +000033 self.tmp_dir = self.mkdtemp()
Tarek Ziadé36797272010-07-22 12:50:05 +000034 self.sys_path = sys.path, sys.path[:]
35 sys.path.append(self.tmp_dir)
36 shutil.copy(_get_source_filename(), self.tmp_dir)
Tarek Ziadé38e3d512009-02-27 12:58:56 +000037 if sys.version > "2.6":
38 import site
39 self.old_user_base = site.USER_BASE
40 site.USER_BASE = self.mkdtemp()
41 from distutils.command import build_ext
42 build_ext.USER_BASE = site.USER_BASE
Georg Brandlb533e262008-05-25 18:19:30 +000043
Barry Warsaw8cf4eae2010-10-16 01:04:07 +000044 def _fixup_command(self, cmd):
45 # When Python was build with --enable-shared, -L. is not good enough
46 # to find the libpython<blah>.so. This is because regrtest runs it
47 # under a tempdir, not in the top level where the .so lives. By the
48 # time we've gotten here, Python's already been chdir'd to the
49 # tempdir.
50 #
51 # To further add to the fun, we can't just add library_dirs to the
52 # Extension() instance because that doesn't get plumbed through to the
53 # final compiler command.
54 if not sys.platform.startswith('win'):
55 library_dir = sysconfig.get_config_var('srcdir')
56 cmd.library_dirs = [('.' if library_dir is None else library_dir)]
57
Georg Brandlb533e262008-05-25 18:19:30 +000058 def test_build_ext(self):
Christian Heimes3e7e0692008-11-25 21:21:32 +000059 global ALREADY_TESTED
Georg Brandlb533e262008-05-25 18:19:30 +000060 xx_c = os.path.join(self.tmp_dir, 'xxmodule.c')
61 xx_ext = Extension('xx', [xx_c])
62 dist = Distribution({'name': 'xx', 'ext_modules': [xx_ext]})
63 dist.package_dir = self.tmp_dir
64 cmd = build_ext(dist)
Barry Warsaw8cf4eae2010-10-16 01:04:07 +000065 self._fixup_command(cmd)
Thomas Heller84b7f0c2008-05-26 11:51:44 +000066 if os.name == "nt":
67 # On Windows, we must build a debug version iff running
68 # a debug build of Python
69 cmd.debug = sys.executable.endswith("_d.exe")
Georg Brandlb533e262008-05-25 18:19:30 +000070 cmd.build_lib = self.tmp_dir
71 cmd.build_temp = self.tmp_dir
72
73 old_stdout = sys.stdout
74 if not support.verbose:
75 # silence compiler output
76 sys.stdout = StringIO()
77 try:
78 cmd.ensure_finalized()
79 cmd.run()
80 finally:
81 sys.stdout = old_stdout
82
Christian Heimes3e7e0692008-11-25 21:21:32 +000083 if ALREADY_TESTED:
84 return
85 else:
86 ALREADY_TESTED = True
87
Georg Brandlb533e262008-05-25 18:19:30 +000088 import xx
89
90 for attr in ('error', 'foo', 'new', 'roj'):
Benjamin Petersonc9c0f202009-06-30 23:06:06 +000091 self.assertTrue(hasattr(xx, attr))
Georg Brandlb533e262008-05-25 18:19:30 +000092
93 self.assertEquals(xx.foo(2, 5), 7)
94 self.assertEquals(xx.foo(13,15), 28)
95 self.assertEquals(xx.new().demo(), None)
96 doc = 'This is a template module just for instruction.'
97 self.assertEquals(xx.__doc__, doc)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +000098 self.assertTrue(isinstance(xx.Null(), xx.Null))
99 self.assertTrue(isinstance(xx.Str(), xx.Str))
Georg Brandlb533e262008-05-25 18:19:30 +0000100
Tarek Ziadé36797272010-07-22 12:50:05 +0000101 def tearDown(self):
102 # Get everything back to normal
103 support.unload('xx')
104 sys.path = self.sys_path[0]
105 sys.path[:] = self.sys_path[1]
106 if sys.version > "2.6":
107 import site
108 site.USER_BASE = self.old_user_base
109 from distutils.command import build_ext
110 build_ext.USER_BASE = self.old_user_base
111 super(BuildExtTestCase, self).tearDown()
112
Tarek Ziadé5874ef12009-02-05 22:56:14 +0000113 def test_solaris_enable_shared(self):
114 dist = Distribution({'name': 'xx'})
115 cmd = build_ext(dist)
116 old = sys.platform
117
118 sys.platform = 'sunos' # fooling finalize_options
Tarek Ziadé36797272010-07-22 12:50:05 +0000119 from distutils.sysconfig import _config_vars
120 old_var = _config_vars.get('Py_ENABLE_SHARED')
121 _config_vars['Py_ENABLE_SHARED'] = 1
Tarek Ziadé5874ef12009-02-05 22:56:14 +0000122 try:
123 cmd.ensure_finalized()
124 finally:
125 sys.platform = old
126 if old_var is None:
Tarek Ziadé36797272010-07-22 12:50:05 +0000127 del _config_vars['Py_ENABLE_SHARED']
Tarek Ziadé5874ef12009-02-05 22:56:14 +0000128 else:
Tarek Ziadé36797272010-07-22 12:50:05 +0000129 _config_vars['Py_ENABLE_SHARED'] = old_var
Tarek Ziadé5874ef12009-02-05 22:56:14 +0000130
Tarek Ziadéff0e5002009-05-12 17:14:01 +0000131 # make sure we get some library dirs under solaris
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000132 self.assertTrue(len(cmd.library_dirs) > 0)
Tarek Ziadé5874ef12009-02-05 22:56:14 +0000133
Tarek Ziadé38e3d512009-02-27 12:58:56 +0000134 def test_user_site(self):
135 # site.USER_SITE was introduced in 2.6
136 if sys.version < '2.6':
137 return
138
139 import site
140 dist = Distribution({'name': 'xx'})
141 cmd = build_ext(dist)
142
Tarek Ziadébe720e02009-05-09 11:55:12 +0000143 # making sure the user option is there
Tarek Ziadé38e3d512009-02-27 12:58:56 +0000144 options = [name for name, short, lable in
145 cmd.user_options]
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000146 self.assertTrue('user' in options)
Tarek Ziadé38e3d512009-02-27 12:58:56 +0000147
148 # setting a value
149 cmd.user = 1
150
151 # setting user based lib and include
152 lib = os.path.join(site.USER_BASE, 'lib')
153 incl = os.path.join(site.USER_BASE, 'include')
154 os.mkdir(lib)
155 os.mkdir(incl)
156
157 # let's run finalize
158 cmd.ensure_finalized()
159
160 # see if include_dirs and library_dirs
161 # were set
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000162 self.assertTrue(lib in cmd.library_dirs)
163 self.assertTrue(lib in cmd.rpath)
164 self.assertTrue(incl in cmd.include_dirs)
Tarek Ziadé38e3d512009-02-27 12:58:56 +0000165
Tarek Ziadéb2e36f12009-03-31 22:37:55 +0000166 def test_optional_extension(self):
167
168 # this extension will fail, but let's ignore this failure
169 # with the optional argument.
170 modules = [Extension('foo', ['xxx'], optional=False)]
171 dist = Distribution({'name': 'xx', 'ext_modules': modules})
172 cmd = build_ext(dist)
173 cmd.ensure_finalized()
Tarek Ziadé30911292009-03-31 22:50:54 +0000174 self.assertRaises((UnknownFileError, CompileError),
175 cmd.run) # should raise an error
Tarek Ziadéb2e36f12009-03-31 22:37:55 +0000176
177 modules = [Extension('foo', ['xxx'], optional=True)]
178 dist = Distribution({'name': 'xx', 'ext_modules': modules})
179 cmd = build_ext(dist)
180 cmd.ensure_finalized()
181 cmd.run() # should pass
182
Tarek Ziadé06fbee12009-05-10 10:34:01 +0000183 def test_finalize_options(self):
184 # Make sure Python's include directories (for Python.h, pyconfig.h,
185 # etc.) are in the include search path.
186 modules = [Extension('foo', ['xxx'], optional=False)]
187 dist = Distribution({'name': 'xx', 'ext_modules': modules})
188 cmd = build_ext(dist)
189 cmd.finalize_options()
190
Tarek Ziadé36797272010-07-22 12:50:05 +0000191 from distutils import sysconfig
192 py_include = sysconfig.get_python_inc()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000193 self.assertTrue(py_include in cmd.include_dirs)
Tarek Ziadé06fbee12009-05-10 10:34:01 +0000194
Tarek Ziadé36797272010-07-22 12:50:05 +0000195 plat_py_include = sysconfig.get_python_inc(plat_specific=1)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000196 self.assertTrue(plat_py_include in cmd.include_dirs)
Tarek Ziadé06fbee12009-05-10 10:34:01 +0000197
198 # make sure cmd.libraries is turned into a list
199 # if it's a string
200 cmd = build_ext(dist)
201 cmd.libraries = 'my_lib'
202 cmd.finalize_options()
203 self.assertEquals(cmd.libraries, ['my_lib'])
204
205 # make sure cmd.library_dirs is turned into a list
206 # if it's a string
207 cmd = build_ext(dist)
208 cmd.library_dirs = 'my_lib_dir'
209 cmd.finalize_options()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000210 self.assertTrue('my_lib_dir' in cmd.library_dirs)
Tarek Ziadé06fbee12009-05-10 10:34:01 +0000211
212 # make sure rpath is turned into a list
213 # if it's a list of os.pathsep's paths
214 cmd = build_ext(dist)
215 cmd.rpath = os.pathsep.join(['one', 'two'])
216 cmd.finalize_options()
217 self.assertEquals(cmd.rpath, ['one', 'two'])
218
219 # XXX more tests to perform for win32
220
221 # make sure define is turned into 2-tuples
222 # strings if they are ','-separated strings
223 cmd = build_ext(dist)
224 cmd.define = 'one,two'
225 cmd.finalize_options()
226 self.assertEquals(cmd.define, [('one', '1'), ('two', '1')])
227
228 # make sure undef is turned into a list of
229 # strings if they are ','-separated strings
230 cmd = build_ext(dist)
231 cmd.undef = 'one,two'
232 cmd.finalize_options()
233 self.assertEquals(cmd.undef, ['one', 'two'])
234
235 # make sure swig_opts is turned into a list
236 cmd = build_ext(dist)
237 cmd.swig_opts = None
238 cmd.finalize_options()
239 self.assertEquals(cmd.swig_opts, [])
240
241 cmd = build_ext(dist)
242 cmd.swig_opts = '1 2'
243 cmd.finalize_options()
244 self.assertEquals(cmd.swig_opts, ['1', '2'])
245
246 def test_check_extensions_list(self):
247 dist = Distribution()
248 cmd = build_ext(dist)
249 cmd.finalize_options()
250
251 #'extensions' option must be a list of Extension instances
Barry Warsaw8cf4eae2010-10-16 01:04:07 +0000252 self.assertRaises(DistutilsSetupError,
253 cmd.check_extensions_list, 'foo')
Tarek Ziadé06fbee12009-05-10 10:34:01 +0000254
255 # each element of 'ext_modules' option must be an
256 # Extension instance or 2-tuple
257 exts = [('bar', 'foo', 'bar'), 'foo']
258 self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
259
260 # first element of each tuple in 'ext_modules'
261 # must be the extension name (a string) and match
262 # a python dotted-separated name
263 exts = [('foo-bar', '')]
264 self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
265
266 # second element of each tuple in 'ext_modules'
267 # must be a ary (build info)
268 exts = [('foo.bar', '')]
269 self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
270
271 # ok this one should pass
272 exts = [('foo.bar', {'sources': [''], 'libraries': 'foo',
273 'some': 'bar'})]
274 cmd.check_extensions_list(exts)
275 ext = exts[0]
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000276 self.assertTrue(isinstance(ext, Extension))
Tarek Ziadé06fbee12009-05-10 10:34:01 +0000277
278 # check_extensions_list adds in ext the values passed
279 # when they are in ('include_dirs', 'library_dirs', 'libraries'
280 # 'extra_objects', 'extra_compile_args', 'extra_link_args')
281 self.assertEquals(ext.libraries, 'foo')
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000282 self.assertTrue(not hasattr(ext, 'some'))
Tarek Ziadé06fbee12009-05-10 10:34:01 +0000283
284 # 'macros' element of build info dict must be 1- or 2-tuple
285 exts = [('foo.bar', {'sources': [''], 'libraries': 'foo',
286 'some': 'bar', 'macros': [('1', '2', '3'), 'foo']})]
287 self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
288
289 exts[0][1]['macros'] = [('1', '2'), ('3',)]
290 cmd.check_extensions_list(exts)
291 self.assertEquals(exts[0].undef_macros, ['3'])
292 self.assertEquals(exts[0].define_macros, [('1', '2')])
293
294 def test_get_source_files(self):
295 modules = [Extension('foo', ['xxx'], optional=False)]
296 dist = Distribution({'name': 'xx', 'ext_modules': modules})
297 cmd = build_ext(dist)
298 cmd.ensure_finalized()
299 self.assertEquals(cmd.get_source_files(), ['xxx'])
300
Tarek Ziadéff0e5002009-05-12 17:14:01 +0000301 def test_compiler_option(self):
302 # cmd.compiler is an option and
303 # should not be overriden by a compiler instance
304 # when the command is run
305 dist = Distribution()
306 cmd = build_ext(dist)
307 cmd.compiler = 'unix'
308 cmd.ensure_finalized()
309 cmd.run()
310 self.assertEquals(cmd.compiler, 'unix')
311
Tarek Ziadé06fbee12009-05-10 10:34:01 +0000312 def test_get_outputs(self):
Tarek Ziadéff0e5002009-05-12 17:14:01 +0000313 tmp_dir = self.mkdtemp()
314 c_file = os.path.join(tmp_dir, 'foo.c')
Tarek Ziadé6504c662009-07-11 10:59:56 +0000315 self.write_file(c_file, 'void PyInit_foo(void) {};\n')
Tarek Ziadéff0e5002009-05-12 17:14:01 +0000316 ext = Extension('foo', [c_file], optional=False)
317 dist = Distribution({'name': 'xx',
318 'ext_modules': [ext]})
Tarek Ziadé06fbee12009-05-10 10:34:01 +0000319 cmd = build_ext(dist)
Barry Warsaw8cf4eae2010-10-16 01:04:07 +0000320 self._fixup_command(cmd)
Tarek Ziadé06fbee12009-05-10 10:34:01 +0000321 cmd.ensure_finalized()
322 self.assertEquals(len(cmd.get_outputs()), 1)
323
Tarek Ziadéff0e5002009-05-12 17:14:01 +0000324 if os.name == "nt":
325 cmd.debug = sys.executable.endswith("_d.exe")
326
327 cmd.build_lib = os.path.join(self.tmp_dir, 'build')
328 cmd.build_temp = os.path.join(self.tmp_dir, 'tempt')
329
330 # issue #5977 : distutils build_ext.get_outputs
331 # returns wrong result with --inplace
Tarek Ziadé4210c6e2009-05-14 20:20:47 +0000332 other_tmp_dir = os.path.realpath(self.mkdtemp())
333 old_wd = os.getcwd()
334 os.chdir(other_tmp_dir)
335 try:
336 cmd.inplace = 1
337 cmd.run()
338 so_file = cmd.get_outputs()[0]
339 finally:
340 os.chdir(old_wd)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000341 self.assertTrue(os.path.exists(so_file))
Barry Warsaw35f3a2c2010-09-03 18:30:30 +0000342 so_ext = sysconfig.get_config_var('SO')
343 self.assertTrue(so_file.endswith(so_ext))
Tarek Ziadéff0e5002009-05-12 17:14:01 +0000344 so_dir = os.path.dirname(so_file)
Tarek Ziadé4210c6e2009-05-14 20:20:47 +0000345 self.assertEquals(so_dir, other_tmp_dir)
Tarek Ziadéff0e5002009-05-12 17:14:01 +0000346
347 cmd.inplace = 0
Tarek Ziadé36797272010-07-22 12:50:05 +0000348 cmd.compiler = None
Tarek Ziadéff0e5002009-05-12 17:14:01 +0000349 cmd.run()
350 so_file = cmd.get_outputs()[0]
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000351 self.assertTrue(os.path.exists(so_file))
Barry Warsaw35f3a2c2010-09-03 18:30:30 +0000352 self.assertTrue(so_file.endswith(so_ext))
Tarek Ziadéff0e5002009-05-12 17:14:01 +0000353 so_dir = os.path.dirname(so_file)
354 self.assertEquals(so_dir, cmd.build_lib)
355
Tarek Ziadé822eb842009-05-19 16:22:57 +0000356 # inplace = 0, cmd.package = 'bar'
Tarek Ziadée10d6de2009-07-03 08:33:28 +0000357 build_py = cmd.get_finalized_command('build_py')
358 build_py.package_dir = {'': 'bar'}
Tarek Ziadé822eb842009-05-19 16:22:57 +0000359 path = cmd.get_ext_fullpath('foo')
Tarek Ziadé0156f912009-06-29 16:19:22 +0000360 # checking that the last directory is the build_dir
Tarek Ziadé822eb842009-05-19 16:22:57 +0000361 path = os.path.split(path)[0]
Tarek Ziadé0156f912009-06-29 16:19:22 +0000362 self.assertEquals(path, cmd.build_lib)
Tarek Ziadé822eb842009-05-19 16:22:57 +0000363
364 # inplace = 1, cmd.package = 'bar'
365 cmd.inplace = 1
366 other_tmp_dir = os.path.realpath(self.mkdtemp())
367 old_wd = os.getcwd()
368 os.chdir(other_tmp_dir)
369 try:
370 path = cmd.get_ext_fullpath('foo')
371 finally:
372 os.chdir(old_wd)
373 # checking that the last directory is bar
374 path = os.path.split(path)[0]
375 lastdir = os.path.split(path)[-1]
Tarek Ziadée10d6de2009-07-03 08:33:28 +0000376 self.assertEquals(lastdir, 'bar')
Tarek Ziadé822eb842009-05-19 16:22:57 +0000377
Tarek Ziadée10d6de2009-07-03 08:33:28 +0000378 def test_ext_fullpath(self):
Tarek Ziadéb7815e32009-07-10 09:14:31 +0000379 ext = sysconfig.get_config_vars()['SO']
Tarek Ziadée10d6de2009-07-03 08:33:28 +0000380 # building lxml.etree inplace
381 #etree_c = os.path.join(self.tmp_dir, 'lxml.etree.c')
382 #etree_ext = Extension('lxml.etree', [etree_c])
383 #dist = Distribution({'name': 'lxml', 'ext_modules': [etree_ext]})
384 dist = Distribution()
Tarek Ziadé0156f912009-06-29 16:19:22 +0000385 cmd = build_ext(dist)
386 cmd.inplace = 1
387 cmd.distribution.package_dir = {'': 'src'}
388 cmd.distribution.packages = ['lxml', 'lxml.html']
389 curdir = os.getcwd()
Tarek Ziadéb7815e32009-07-10 09:14:31 +0000390 wanted = os.path.join(curdir, 'src', 'lxml', 'etree' + ext)
Tarek Ziadé0156f912009-06-29 16:19:22 +0000391 path = cmd.get_ext_fullpath('lxml.etree')
392 self.assertEquals(wanted, path)
393
Tarek Ziadée10d6de2009-07-03 08:33:28 +0000394 # building lxml.etree not inplace
395 cmd.inplace = 0
396 cmd.build_lib = os.path.join(curdir, 'tmpdir')
Tarek Ziadéb7815e32009-07-10 09:14:31 +0000397 wanted = os.path.join(curdir, 'tmpdir', 'lxml', 'etree' + ext)
Tarek Ziadée10d6de2009-07-03 08:33:28 +0000398 path = cmd.get_ext_fullpath('lxml.etree')
399 self.assertEquals(wanted, path)
400
401 # building twisted.runner.portmap not inplace
402 build_py = cmd.get_finalized_command('build_py')
403 build_py.package_dir = {}
404 cmd.distribution.packages = ['twisted', 'twisted.runner.portmap']
405 path = cmd.get_ext_fullpath('twisted.runner.portmap')
406 wanted = os.path.join(curdir, 'tmpdir', 'twisted', 'runner',
Tarek Ziadéb7815e32009-07-10 09:14:31 +0000407 'portmap' + ext)
Tarek Ziadée10d6de2009-07-03 08:33:28 +0000408 self.assertEquals(wanted, path)
409
410 # building twisted.runner.portmap inplace
411 cmd.inplace = 1
412 path = cmd.get_ext_fullpath('twisted.runner.portmap')
Tarek Ziadéb7815e32009-07-10 09:14:31 +0000413 wanted = os.path.join(curdir, 'twisted', 'runner', 'portmap' + ext)
Tarek Ziadée10d6de2009-07-03 08:33:28 +0000414 self.assertEquals(wanted, path)
415
Georg Brandlb533e262008-05-25 18:19:30 +0000416def test_suite():
Neil Schemenauerd8f63bb2009-02-06 21:42:05 +0000417 src = _get_source_filename()
418 if not os.path.exists(src):
Georg Brandlb533e262008-05-25 18:19:30 +0000419 if support.verbose:
Neil Schemenauerd8f63bb2009-02-06 21:42:05 +0000420 print('test_build_ext: Cannot find source code (test'
421 ' must run in python build dir)')
Georg Brandlb533e262008-05-25 18:19:30 +0000422 return unittest.TestSuite()
423 else: return unittest.makeSuite(BuildExtTestCase)
424
425if __name__ == '__main__':
426 support.run_unittest(test_suite())