5 from StringIO import StringIO
7 from distutils.core import Extension, Distribution
8 from distutils.command.build_ext import build_ext
9 from distutils import sysconfig
10 from distutils.tests import support
11 from distutils.errors import DistutilsSetupError
14 from test import test_support
16 # http://bugs.python.org/issue4373
17 # Don't load the xx module more than once.
18 ALREADY_TESTED = False
20 def _get_source_filename():
21 srcdir = sysconfig.get_config_var('srcdir')
23 return os.path.join(sysconfig.project_base, 'Modules', 'xxmodule.c')
24 return os.path.join(srcdir, 'Modules', 'xxmodule.c')
26 _XX_MODULE_PATH = _get_source_filename()
28 class BuildExtTestCase(support.TempdirManager,
29 support.LoggingSilencer,
32 # Create a simple test environment
33 # Note that we're making changes to sys.path
34 super(BuildExtTestCase, self).setUp()
35 self.tmp_dir = tempfile.mkdtemp(prefix="pythontest_")
36 if os.path.exists(_XX_MODULE_PATH):
37 self.sys_path = sys.path[:]
38 sys.path.append(self.tmp_dir)
39 shutil.copy(_XX_MODULE_PATH, self.tmp_dir)
42 # Get everything back to normal
43 if os.path.exists(_XX_MODULE_PATH):
44 test_support.unload('xx')
45 sys.path[:] = self.sys_path
46 # XXX on Windows the test leaves a directory
47 # with xx module in TEMP
48 shutil.rmtree(self.tmp_dir, os.name == 'nt' or
49 sys.platform == 'cygwin')
50 super(BuildExtTestCase, self).tearDown()
52 def _fixup_command(self, cmd):
53 # When Python was build with --enable-shared, -L. is not good enough
54 # to find the libpython<blah>.so. This is because regrtest runs it
55 # under a tempdir, not in the top level where the .so lives. By the
56 # time we've gotten here, Python's already been chdir'd to the
59 # To further add to the fun, we can't just add library_dirs to the
60 # Extension() instance because that doesn't get plumbed through to the
61 # final compiler command.
62 if (sysconfig.get_config_var('Py_ENABLE_SHARED') and
63 not sys.platform.startswith('win')):
64 runshared = sysconfig.get_config_var('RUNSHARED')
66 cmd.library_dirs = ['.']
68 name, equals, value = runshared.partition('=')
69 cmd.library_dirs = value.split(os.pathsep)
71 @unittest.skipIf(not os.path.exists(_XX_MODULE_PATH),
72 'xxmodule.c not found')
73 def test_build_ext(self):
75 xx_c = os.path.join(self.tmp_dir, 'xxmodule.c')
76 xx_ext = Extension('xx', [xx_c])
77 dist = Distribution({'name': 'xx', 'ext_modules': [xx_ext]})
78 dist.package_dir = self.tmp_dir
80 self._fixup_command(cmd)
82 # On Windows, we must build a debug version iff running
83 # a debug build of Python
84 cmd.debug = sys.executable.endswith("_d.exe")
85 cmd.build_lib = self.tmp_dir
86 cmd.build_temp = self.tmp_dir
88 old_stdout = sys.stdout
89 if not test_support.verbose:
90 # silence compiler output
91 sys.stdout = StringIO()
93 cmd.ensure_finalized()
96 sys.stdout = old_stdout
101 ALREADY_TESTED = True
105 for attr in ('error', 'foo', 'new', 'roj'):
106 self.assertTrue(hasattr(xx, attr))
108 self.assertEqual(xx.foo(2, 5), 7)
109 self.assertEqual(xx.foo(13,15), 28)
110 self.assertEqual(xx.new().demo(), None)
111 doc = 'This is a template module just for instruction.'
112 self.assertEqual(xx.__doc__, doc)
113 self.assertTrue(isinstance(xx.Null(), xx.Null))
114 self.assertTrue(isinstance(xx.Str(), xx.Str))
116 def test_solaris_enable_shared(self):
117 dist = Distribution({'name': 'xx'})
118 cmd = build_ext(dist)
121 sys.platform = 'sunos' # fooling finalize_options
122 from distutils.sysconfig import _config_vars
123 old_var = _config_vars.get('Py_ENABLE_SHARED')
124 _config_vars['Py_ENABLE_SHARED'] = 1
126 cmd.ensure_finalized()
130 del _config_vars['Py_ENABLE_SHARED']
132 _config_vars['Py_ENABLE_SHARED'] = old_var
134 # make sure we get some library dirs under solaris
135 self.assertTrue(len(cmd.library_dirs) > 0)
137 def test_finalize_options(self):
138 # Make sure Python's include directories (for Python.h, pyconfig.h,
139 # etc.) are in the include search path.
140 modules = [Extension('foo', ['xxx'])]
141 dist = Distribution({'name': 'xx', 'ext_modules': modules})
142 cmd = build_ext(dist)
143 cmd.finalize_options()
145 from distutils import sysconfig
146 py_include = sysconfig.get_python_inc()
147 self.assertTrue(py_include in cmd.include_dirs)
149 plat_py_include = sysconfig.get_python_inc(plat_specific=1)
150 self.assertTrue(plat_py_include in cmd.include_dirs)
152 # make sure cmd.libraries is turned into a list
154 cmd = build_ext(dist)
155 cmd.libraries = 'my_lib'
156 cmd.finalize_options()
157 self.assertEqual(cmd.libraries, ['my_lib'])
159 # make sure cmd.library_dirs is turned into a list
161 cmd = build_ext(dist)
162 cmd.library_dirs = 'my_lib_dir'
163 cmd.finalize_options()
164 self.assertTrue('my_lib_dir' in cmd.library_dirs)
166 # make sure rpath is turned into a list
167 # if it's a list of os.pathsep's paths
168 cmd = build_ext(dist)
169 cmd.rpath = os.pathsep.join(['one', 'two'])
170 cmd.finalize_options()
171 self.assertEqual(cmd.rpath, ['one', 'two'])
173 # XXX more tests to perform for win32
175 # make sure define is turned into 2-tuples
176 # strings if they are ','-separated strings
177 cmd = build_ext(dist)
178 cmd.define = 'one,two'
179 cmd.finalize_options()
180 self.assertEqual(cmd.define, [('one', '1'), ('two', '1')])
182 # make sure undef is turned into a list of
183 # strings if they are ','-separated strings
184 cmd = build_ext(dist)
185 cmd.undef = 'one,two'
186 cmd.finalize_options()
187 self.assertEqual(cmd.undef, ['one', 'two'])
189 # make sure swig_opts is turned into a list
190 cmd = build_ext(dist)
192 cmd.finalize_options()
193 self.assertEqual(cmd.swig_opts, [])
195 cmd = build_ext(dist)
196 cmd.swig_opts = '1 2'
197 cmd.finalize_options()
198 self.assertEqual(cmd.swig_opts, ['1', '2'])
200 def test_check_extensions_list(self):
201 dist = Distribution()
202 cmd = build_ext(dist)
203 cmd.finalize_options()
205 #'extensions' option must be a list of Extension instances
206 self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, 'foo')
208 # each element of 'ext_modules' option must be an
209 # Extension instance or 2-tuple
210 exts = [('bar', 'foo', 'bar'), 'foo']
211 self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
213 # first element of each tuple in 'ext_modules'
214 # must be the extension name (a string) and match
215 # a python dotted-separated name
216 exts = [('foo-bar', '')]
217 self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
219 # second element of each tuple in 'ext_modules'
220 # must be a ary (build info)
221 exts = [('foo.bar', '')]
222 self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
224 # ok this one should pass
225 exts = [('foo.bar', {'sources': [''], 'libraries': 'foo',
227 cmd.check_extensions_list(exts)
229 self.assertTrue(isinstance(ext, Extension))
231 # check_extensions_list adds in ext the values passed
232 # when they are in ('include_dirs', 'library_dirs', 'libraries'
233 # 'extra_objects', 'extra_compile_args', 'extra_link_args')
234 self.assertEqual(ext.libraries, 'foo')
235 self.assertTrue(not hasattr(ext, 'some'))
237 # 'macros' element of build info dict must be 1- or 2-tuple
238 exts = [('foo.bar', {'sources': [''], 'libraries': 'foo',
239 'some': 'bar', 'macros': [('1', '2', '3'), 'foo']})]
240 self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts)
242 exts[0][1]['macros'] = [('1', '2'), ('3',)]
243 cmd.check_extensions_list(exts)
244 self.assertEqual(exts[0].undef_macros, ['3'])
245 self.assertEqual(exts[0].define_macros, [('1', '2')])
247 def test_get_source_files(self):
248 modules = [Extension('foo', ['xxx'])]
249 dist = Distribution({'name': 'xx', 'ext_modules': modules})
250 cmd = build_ext(dist)
251 cmd.ensure_finalized()
252 self.assertEqual(cmd.get_source_files(), ['xxx'])
254 def test_compiler_option(self):
255 # cmd.compiler is an option and
256 # should not be overriden by a compiler instance
257 # when the command is run
258 dist = Distribution()
259 cmd = build_ext(dist)
260 cmd.compiler = 'unix'
261 cmd.ensure_finalized()
263 self.assertEqual(cmd.compiler, 'unix')
265 def test_get_outputs(self):
266 tmp_dir = self.mkdtemp()
267 c_file = os.path.join(tmp_dir, 'foo.c')
268 self.write_file(c_file, 'void initfoo(void) {};\n')
269 ext = Extension('foo', [c_file])
270 dist = Distribution({'name': 'xx',
271 'ext_modules': [ext]})
272 cmd = build_ext(dist)
273 self._fixup_command(cmd)
274 cmd.ensure_finalized()
275 self.assertEqual(len(cmd.get_outputs()), 1)
278 cmd.debug = sys.executable.endswith("_d.exe")
280 cmd.build_lib = os.path.join(self.tmp_dir, 'build')
281 cmd.build_temp = os.path.join(self.tmp_dir, 'tempt')
283 # issue #5977 : distutils build_ext.get_outputs
284 # returns wrong result with --inplace
285 other_tmp_dir = os.path.realpath(self.mkdtemp())
287 os.chdir(other_tmp_dir)
291 so_file = cmd.get_outputs()[0]
294 self.assertTrue(os.path.exists(so_file))
295 self.assertEqual(os.path.splitext(so_file)[-1],
296 sysconfig.get_config_var('SO'))
297 so_dir = os.path.dirname(so_file)
298 self.assertEqual(so_dir, other_tmp_dir)
302 so_file = cmd.get_outputs()[0]
303 self.assertTrue(os.path.exists(so_file))
304 self.assertEqual(os.path.splitext(so_file)[-1],
305 sysconfig.get_config_var('SO'))
306 so_dir = os.path.dirname(so_file)
307 self.assertEqual(so_dir, cmd.build_lib)
309 # inplace = 0, cmd.package = 'bar'
310 build_py = cmd.get_finalized_command('build_py')
311 build_py.package_dir = {'': 'bar'}
312 path = cmd.get_ext_fullpath('foo')
313 # checking that the last directory is the build_dir
314 path = os.path.split(path)[0]
315 self.assertEqual(path, cmd.build_lib)
317 # inplace = 1, cmd.package = 'bar'
319 other_tmp_dir = os.path.realpath(self.mkdtemp())
321 os.chdir(other_tmp_dir)
323 path = cmd.get_ext_fullpath('foo')
326 # checking that the last directory is bar
327 path = os.path.split(path)[0]
328 lastdir = os.path.split(path)[-1]
329 self.assertEqual(lastdir, 'bar')
331 def test_ext_fullpath(self):
332 ext = sysconfig.get_config_vars()['SO']
333 dist = Distribution()
334 cmd = build_ext(dist)
336 cmd.distribution.package_dir = {'': 'src'}
337 cmd.distribution.packages = ['lxml', 'lxml.html']
339 wanted = os.path.join(curdir, 'src', 'lxml', 'etree' + ext)
340 path = cmd.get_ext_fullpath('lxml.etree')
341 self.assertEqual(wanted, path)
343 # building lxml.etree not inplace
345 cmd.build_lib = os.path.join(curdir, 'tmpdir')
346 wanted = os.path.join(curdir, 'tmpdir', 'lxml', 'etree' + ext)
347 path = cmd.get_ext_fullpath('lxml.etree')
348 self.assertEqual(wanted, path)
350 # building twisted.runner.portmap not inplace
351 build_py = cmd.get_finalized_command('build_py')
352 build_py.package_dir = {}
353 cmd.distribution.packages = ['twisted', 'twisted.runner.portmap']
354 path = cmd.get_ext_fullpath('twisted.runner.portmap')
355 wanted = os.path.join(curdir, 'tmpdir', 'twisted', 'runner',
357 self.assertEqual(wanted, path)
359 # building twisted.runner.portmap inplace
361 path = cmd.get_ext_fullpath('twisted.runner.portmap')
362 wanted = os.path.join(curdir, 'twisted', 'runner', 'portmap' + ext)
363 self.assertEqual(wanted, path)
365 def test_build_ext_inplace(self):
366 etree_c = os.path.join(self.tmp_dir, 'lxml.etree.c')
367 etree_ext = Extension('lxml.etree', [etree_c])
368 dist = Distribution({'name': 'lxml', 'ext_modules': [etree_ext]})
369 cmd = build_ext(dist)
370 cmd.ensure_finalized()
372 cmd.distribution.package_dir = {'': 'src'}
373 cmd.distribution.packages = ['lxml', 'lxml.html']
375 ext = sysconfig.get_config_var("SO")
376 wanted = os.path.join(curdir, 'src', 'lxml', 'etree' + ext)
377 path = cmd.get_ext_fullpath('lxml.etree')
378 self.assertEqual(wanted, path)
380 def test_setuptools_compat(self):
381 import distutils.core, distutils.extension, distutils.command.build_ext
382 saved_ext = distutils.extension.Extension
384 # on some platforms, it loads the deprecated "dl" module
385 test_support.import_module('setuptools_build_ext', deprecated=True)
387 # theses import patch Distutils' Extension class
388 from setuptools_build_ext import build_ext as setuptools_build_ext
389 from setuptools_extension import Extension
391 etree_c = os.path.join(self.tmp_dir, 'lxml.etree.c')
392 etree_ext = Extension('lxml.etree', [etree_c])
393 dist = Distribution({'name': 'lxml', 'ext_modules': [etree_ext]})
394 cmd = setuptools_build_ext(dist)
395 cmd.ensure_finalized()
397 cmd.distribution.package_dir = {'': 'src'}
398 cmd.distribution.packages = ['lxml', 'lxml.html']
400 ext = sysconfig.get_config_var("SO")
401 wanted = os.path.join(curdir, 'src', 'lxml', 'etree' + ext)
402 path = cmd.get_ext_fullpath('lxml.etree')
403 self.assertEqual(wanted, path)
405 # restoring Distutils' Extension class otherwise its broken
406 distutils.extension.Extension = saved_ext
407 distutils.core.Extension = saved_ext
408 distutils.command.build_ext.Extension = saved_ext
410 def test_build_ext_path_with_os_sep(self):
411 dist = Distribution({'name': 'UpdateManager'})
412 cmd = build_ext(dist)
413 cmd.ensure_finalized()
414 ext = sysconfig.get_config_var("SO")
415 ext_name = os.path.join('UpdateManager', 'fdsend')
416 ext_path = cmd.get_ext_fullpath(ext_name)
417 wanted = os.path.join(cmd.build_lib, 'UpdateManager', 'fdsend' + ext)
418 self.assertEqual(ext_path, wanted)
420 def test_build_ext_path_cross_platform(self):
421 if sys.platform != 'win32':
423 dist = Distribution({'name': 'UpdateManager'})
424 cmd = build_ext(dist)
425 cmd.ensure_finalized()
426 ext = sysconfig.get_config_var("SO")
427 # this needs to work even under win32
428 ext_name = 'UpdateManager/fdsend'
429 ext_path = cmd.get_ext_fullpath(ext_name)
430 wanted = os.path.join(cmd.build_lib, 'UpdateManager', 'fdsend' + ext)
431 self.assertEqual(ext_path, wanted)
434 return unittest.makeSuite(BuildExtTestCase)
436 if __name__ == '__main__':
437 test_support.run_unittest(test_suite())