diff -r 7a50e549bd11 -r ca53ff77ce6f Doc/library/packaging.compiler.rst --- a/Doc/library/packaging.compiler.rst Thu Sep 29 20:01:55 2011 +0200 +++ b/Doc/library/packaging.compiler.rst Thu Sep 29 16:15:10 2011 -0400 @@ -306,6 +306,27 @@ ``None`` if *lib* wasn't found in any of the specified directories. + .. method:: CCompiler.get_compiler_version() + + Return version string associated with the current compiler executable. + This is primarily used by build_ext to identify compiler-specific options + within :attr:`Extension.specific_compile_args`. + + * If possible, this returns a string with the format + ``'{cmd} {vnum}'`` (eg: ``'gcc 4.5.2'``). + * If version information is available, but not parseable, + this returns a string with the format ``'{cmd} {vstr}'`` + (eg: ``'gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2'``). + * Failing that, if the compiler is known, + this returns the compiler name by itself (eg ``'gcc'``). + * Finally, if no information is available, this returns ``None``. + + Example return values for the builtin compilers include: + + * ``bcc32 5.5.1`` for the Borland C++ CCompiler + * ``msvc 9.0`` for the Microsoft Visual Studio CCompiler + * ``gcc 4.5.2`` for GCC (via the Unix, Cygwin, or Mingw CCompilers) + .. method:: CCompiler.has_function(funcname [, includes=None, include_dirs=None, libraries=None, library_dirs=None]) Return a boolean indicating whether *funcname* is supported on the current @@ -653,6 +674,39 @@ | | Similar interpretation as for | | | | 'extra_compile_args'. | | +------------------------+--------------------------------+---------------------------+ + | *specific_compile_args*| List of compiler-specific | list of tuples with format| + | | options to use when compiling | (regex string, | + | | source files; unlike | list of strings) | + | | *extra_compile_args*, these are| | + | | conditionally included | | + | | depending on the name and | | + | | version of the active compiler.| | + | | Each entry is a 2-tuple | | + | | ``(regex_string, arglist)``. | | + | | The first (if any) entry whose | | + | | ``regex_string`` matches the | | + | | return value from | | + | | ``get_compiler_version()`` | | + | | will have it's ``arglist`` | | + | | appended to the list of | | + | | *extra_compile_args*. | | + | | If one of the elements in | | + | | ``arglist`` is the special | | + | | string ``"{common_args}"``, | | + | | *extra_compile_args* will | | + | | be inserted at that location | | + | | instead of at the beginning. | | + +------------------------+--------------------------------+---------------------------+ + | *specific_link_args* | List of compiler-specific | list of tuples with format| + | | options to use when linking | (regex string, | + | | object files; unlike | list of strings) | + | | *extra_link_args*, these are | | + | | conditionally included | | + | | depending on the name and | | + | | version of the active compiler.| | + | | Similar interpretation as for | | + | | *specific_compile_args*. | | + +------------------------+--------------------------------+---------------------------+ | *export_symbols* | list of symbols to be exported | list of strings | | | from a shared extension. Not | | | | used on all platforms, and not | | diff -r 7a50e549bd11 -r ca53ff77ce6f Doc/packaging/setupcfg.rst --- a/Doc/packaging/setupcfg.rst Thu Sep 29 20:01:55 2011 +0200 +++ b/Doc/packaging/setupcfg.rst Thu Sep 29 16:15:10 2011 -0400 @@ -766,6 +766,8 @@ -fPIC -O2 -DGECODE_VERSION=$(./gecode_version) -- sys.platform != 'win32' /DGECODE_VERSION='win32' -- sys.platform == 'win32' + specific_compile_args = + gcc [4-9]\. = -m3dnow The section name must start with ``extension:``; the right-hand part is used as the full name (including a parent package, if any) of the extension. Whitespace diff -r 7a50e549bd11 -r ca53ff77ce6f Lib/packaging/command/build_ext.py --- a/Lib/packaging/command/build_ext.py Thu Sep 29 20:01:55 2011 +0200 +++ b/Lib/packaging/command/build_ext.py Thu Sep 29 16:15:10 2011 -0400 @@ -25,6 +25,9 @@ extension_name_re = re.compile \ (r'^[a-zA-Z_][a-zA-Z_0-9]*(\.[a-zA-Z_][a-zA-Z_0-9]*)*$') +# constant optionally used to indicate where in specific_compile_args +# the extra_compile_args should be inserted. +COMMON_ARGS_CONST = "{common_args}" class build_ext(Command): @@ -386,15 +389,19 @@ # CCompiler API needs to change to accommodate this, and I # want to do one thing at a time! - # Two possible sources for extra compiler arguments: + # Three possible sources for extra compiler arguments: # - 'extra_compile_args' in Extension object + # - 'specific_compile_args' in Extension object # - CFLAGS environment variable (not particularly # elegant, but people seem to expect it and I # guess it's useful) # The environment variable should take precedence, and # any sensible compiler will give precedence to later # command-line args. Hence we combine them in order: - extra_args = ext.extra_compile_args or [] + extra_args = self._combine_extension_args( + common_args=ext.extra_compile_args or [], + specific_args=ext.specific_compile_args or [], + ) macros = ext.define_macros[:] for undef in ext.undef_macros: @@ -424,7 +431,10 @@ # that go into the mix. if ext.extra_objects: objects.extend(ext.extra_objects) - extra_args = ext.extra_link_args or [] + extra_args = self._combine_extension_args( + common_args=ext.extra_link_args or [], + specific_args=ext.specific_link_args or [], + ) # Detect target language, if not provided language = ext.language or self.compiler_obj.detect_language(sources) @@ -440,6 +450,44 @@ build_temp=self.build_temp, target_lang=language) + def _combine_extension_args(self, common_args, specific_args): + """Return compiler/link args appropriate for compiler, + given extra_compile/link_args and specific_compile/link_args + + 'common_args' should be list of options (e.g. extra_compile_args). + 'specific_args' should be list of [(pattern, options), ...] + entries (e.g. specific_compile_args). + + Only the first entry in 'specific_args' whose pattern matches + get_compiler_version() will be used. The pattern should + be a regex string. + + 'specific_args' options may contain the magic string "{common_args}", + which specifies the insertion point for 'common_args' values; + by default they are inserted before any matching 'specific_args'. + """ + + if not specific_args: + return common_args + + # locate compiler-specific options, if any + vstr = self.compiler_obj.get_compiler_version() or 'unknown' + logger.info("using compiler class %r, with compiler version %r", + self.compiler_obj.name, vstr) + for pattern, options in specific_args: + if re.match(pattern, vstr): + break + else: + return common_args + + # combine with compiler-nonspecific options (common_args) + if COMMON_ARGS_CONST in options: + idx = options.index(COMMON_ARGS_CONST) + options = options[:idx] + common_args + options[idx+1:] + elif common_args: + options = common_args + options + + return options def swig_sources(self, sources, extension): """Walk the list of source files in 'sources', looking for SWIG diff -r 7a50e549bd11 -r ca53ff77ce6f Lib/packaging/compiler/bcppcompiler.py --- a/Lib/packaging/compiler/bcppcompiler.py Thu Sep 29 20:01:55 2011 +0200 +++ b/Lib/packaging/compiler/bcppcompiler.py Thu Sep 29 16:15:10 2011 -0400 @@ -8,13 +8,13 @@ # WindowsCCompiler! --GPW import os +import re from packaging.errors import (PackagingExecError, CompileError, LibError, LinkError, UnknownFileError) from packaging.compiler.ccompiler import CCompiler from packaging.compiler import gen_preprocess_options -from packaging.file_util import write_file -from packaging.dep_util import newer +from packaging.util import get_executable_result, write_file, newer from packaging import logger @@ -68,6 +68,21 @@ self.ldflags_exe = ['/Gn', '/q', '/x'] self.ldflags_exe_debug = ['/Gn', '/q', '/x','/r'] + def get_compiler_version(self): + rc, result = get_executable_result([self.cc], firstline=True) + if rc or not result: + return "bcc32" + + # first line should have format such as + # "Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland" + m = re.search(r"(\d+(?:\.\d+)+)", result) + if m: + return "bcc32 " + m.group(1) + + if result.startswith("Borland C++ "): + result = result[12:] + return "bcc32 " + result + # -- Worker methods ------------------------------------------------ diff -r 7a50e549bd11 -r ca53ff77ce6f Lib/packaging/compiler/ccompiler.py --- a/Lib/packaging/compiler/ccompiler.py Thu Sep 29 20:01:55 2011 +0200 +++ b/Lib/packaging/compiler/ccompiler.py Thu Sep 29 16:15:10 2011 -0400 @@ -176,6 +176,23 @@ "must be tuple (string,), (string, string), or " + \ "(string, None)") + def get_compiler_version(self): + """Return version string associated with compiler executable. + + This is used by the build_ext command to select compiler-specific + options contained in Extension.specific_compile_args. + Subclasses should provide an implementation of this method. + + * If possible, this returns a string with the format + "{cmd} {vnum}" (e.g. "gcc 4.5.2"), to ease pattern matching. + * If version information is available, but not parseable, this returns + a string with the format "{cmd} {vstr}" + (e.g. "gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"). + * Failing that, if the name of the compiler is known, this returns + a string containing just "{cmd}" by itself (e.g. "gcc"). + * Finally, if no information is available, this returns None. + """ + return None # -- Bookkeeping methods ------------------------------------------- diff -r 7a50e549bd11 -r ca53ff77ce6f Lib/packaging/compiler/extension.py --- a/Lib/packaging/compiler/extension.py Thu Sep 29 20:01:55 2011 +0200 +++ b/Lib/packaging/compiler/extension.py Thu Sep 29 16:15:10 2011 -0400 @@ -60,6 +60,20 @@ when linking object files together to create the extension (or to create a new static Python interpreter). Similar interpretation as for 'extra_compile_args'. + specific_compile_args : [(regex_string, [string])] + similar to 'extra_compile_args', this is a list of (regex_string, + args) entries. If present, the first entry whose regex matches + compiler.get_compiler_version() will have it's args appended to + 'extra_compile_args'. If one of the elements of args is the magic + string "{common_args}", the values in 'extra_compile_args' will be + inserted at that location instead of at the beginning. + specific_link_args : [(regex_string, [string])] + similar to 'extra_link_args', this is a list of (regex_string, + args) entries. If present, the first entry whose regex matches + compiler.get_compiler_version() will have it's args appended to + 'extra_link_args'. If one of the elements of args is the magic + string "{common_args}", the values in 'extra_link_args' will be + inserted at that location instead of at the beginning. export_symbols : [string] list of symbols to be exported from a shared extension. Not used on all platforms, and not generally necessary for Python @@ -84,6 +98,7 @@ undef_macros=None, library_dirs=None, libraries=None, runtime_library_dirs=None, extra_objects=None, extra_compile_args=None, extra_link_args=None, + specific_compile_args=None, specific_link_args=None, export_symbols=None, swig_opts=None, depends=None, language=None, optional=None, **kw): if not isinstance(name, str): @@ -107,6 +122,8 @@ self.extra_objects = extra_objects or [] self.extra_compile_args = extra_compile_args or [] self.extra_link_args = extra_link_args or [] + self.specific_compile_args = specific_compile_args or [] + self.specific_link_args = specific_link_args or [] self.export_symbols = export_symbols or [] self.swig_opts = swig_opts or [] self.depends = depends or [] diff -r 7a50e549bd11 -r ca53ff77ce6f Lib/packaging/compiler/msvc9compiler.py --- a/Lib/packaging/compiler/msvc9compiler.py Thu Sep 29 20:01:55 2011 +0200 +++ b/Lib/packaging/compiler/msvc9compiler.py Thu Sep 29 16:15:10 2011 -0400 @@ -320,6 +320,9 @@ self.__arch = None # deprecated name self.initialized = False + def get_compiler_version(self): + return "msvc %s" % (self.__version,) + def initialize(self, plat_name=None): # multi-init means we would need to check platform same each time... assert not self.initialized, "don't init multiple times" diff -r 7a50e549bd11 -r ca53ff77ce6f Lib/packaging/compiler/msvccompiler.py --- a/Lib/packaging/compiler/msvccompiler.py Thu Sep 29 20:01:55 2011 +0200 +++ b/Lib/packaging/compiler/msvccompiler.py Thu Sep 29 16:15:10 2011 -0400 @@ -254,6 +254,12 @@ self.initialized = False + def get_compiler_version(self): + if self.__version: + return "msvc %s" % (self.__version,) + else: + return "msvc" + def initialize(self): self.__paths = [] if ("DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and diff -r 7a50e549bd11 -r ca53ff77ce6f Lib/packaging/compiler/unixccompiler.py --- a/Lib/packaging/compiler/unixccompiler.py Thu Sep 29 20:01:55 2011 +0200 +++ b/Lib/packaging/compiler/unixccompiler.py Thu Sep 29 16:15:10 2011 -0400 @@ -12,10 +12,10 @@ * link static library handled by 'ar' command (possibly with 'ranlib') * link shared library handled by 'cc -shared' """ - +import re import os, sys -from packaging.util import newer +from packaging.util import newer, get_executable_result from packaging.compiler.ccompiler import CCompiler from packaging.compiler import gen_preprocess_options, gen_lib_options from packaging.errors import (PackagingExecError, CompileError, @@ -141,6 +141,61 @@ if sys.platform == "cygwin": exe_extension = ".exe" + def get_compiler_version(self): + """Return version string associated with compiler executable. + + The format of the return value is documented + in CCompiler.get_compiler_version. + + This method provides a common implementation shared by UnixCCompiler + and its subclasses (CygwinCCompiler and Mingw32CCompiler). + Based on the name of the compiler executable, it tries to dispatch + the job to a compiler-specific method named + '_get_compiler_version_{cmd}', if it exists. This allows users and + subclasses to add in handler methods for compilers whose version + strings are not properly parsed by the existing code. + """ + cmd = self.compiler[0] + name = "_get_compiler_version_%s" % (cmd.replace("-", "_"),) + meth = getattr(self, name, self._get_compiler_version_fallback) + + result = meth(cmd) + if result: + return result + return cmd + + def _get_compiler_version_gcc(self, cmd): + # for gcc, -dumpversion returns "4.5.2" or similar. + rc, result = get_executable_result([cmd, "-dumpversion"], firstline=True) + if not rc and result: + return "%s %s" % (cmd, result) + + _get_compiler_version_clang = _get_compiler_version_gcc + + def _get_compiler_version_fallback(self, cmd): + rc, result = get_executable_result([cmd, "--version"], firstline=True) + if rc or not result: + return + + # check for gcc-style version string + # e.g. "cc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2" + m = re.match(re.escape(cmd) + + r" \([^)]+\) (?P\d+(?:\.\d+)+)", result) + if m: + return "%s %s" % (cmd, m.group("ver")) + + # check for a generic version string + # e.g. "cc version 2.8" + m = re.search(r"(?P\d+(?:\.\d+)+)", result) + if m: + return "%s %s" % (cmd, m.group("ver")) + + # check if cmd name already included in result. + if result.startswith(cmd + " "): + return result + else: + return "%s %s" % (cmd, result) + def preprocess(self, source, output_file=None, macros=None, include_dirs=None, extra_preargs=None, extra_postargs=None): diff -r 7a50e549bd11 -r ca53ff77ce6f Lib/packaging/config.py --- a/Lib/packaging/config.py Thu Sep 29 20:01:55 2011 +0200 +++ b/Lib/packaging/config.py Thu Sep 29 16:15:10 2011 -0400 @@ -46,6 +46,27 @@ if vals: return vals +def _pop_value_pairs(values_dct, key): + """Remove values from the dictionary and convert them to a list of + (key, args) pairs. + """ + rows = values_dct.pop(key, None) + if not rows: + return None + fields = [] + for row in rows.split('\n'): + if ';;' in row: + row, marker = row.rsplit(";;", 1) + if not interpret(marker): + continue + row = row.strip() + if not row: + continue + key, vals = row.split("=", 1) + # parse vals into arglist using shlex POSIX rules + pair = (key.strip(), split(vals)) + fields.append(pair) + return fields def _rel_path(base, path): # normalizes and returns a lstripped-/-separated path @@ -288,6 +309,8 @@ _pop_values(values_dct, 'extra_objects'), _pop_values(values_dct, 'extra_compile_args'), _pop_values(values_dct, 'extra_link_args'), + _pop_value_pairs(values_dct, 'specific_compile_args'), + _pop_value_pairs(values_dct, 'specific_link_args'), _pop_values(values_dct, 'export_symbols'), _pop_values(values_dct, 'swig_opts'), _pop_values(values_dct, 'depends'), diff -r 7a50e549bd11 -r ca53ff77ce6f Lib/packaging/tests/test_bcppcompiler.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/packaging/tests/test_bcppcompiler.py Thu Sep 29 16:15:10 2011 -0400 @@ -0,0 +1,59 @@ +"""Tests for packaging.compiler.bcppcompiler""" + +import packaging.compiler.bcppcompiler as bcppcompiler +from packaging.compiler.bcppcompiler import BCPPCompiler +from packaging.tests import unittest + +class BCPPCompilerTestCase(unittest.TestCase): + + def setUp(self): + self.addCleanup(setattr, bcppcompiler, 'get_executable_result', + bcppcompiler.get_executable_result) + self.cc = BCPPCompiler() + + def test_active_get_compiler_version(self): + cc = self.cc + cmd = cc.cc + if cmd.endswith(".exe"): + cmd = cmd[:-4] + value = cc.get_compiler_version() + self.assertTrue(value is None or value.startswith(cmd + " ") or + value == cmd) + + def test_get_compiler_version(self): + cc = self.cc + + def set_fake_return(rc, value): + """replace copy of get_executable_result() used by BCPPCompiler + with mock version that returns specified result""" + def fake_ger(args, firstline=False): + self.assertEqual(args, ["bcc32.exe"]) + return rc, value + bcppcompiler.get_executable_result = fake_ger + + # test when bcc32 returns proper version string + set_fake_return(0, + "Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland") + self.assertEqual(cc.get_compiler_version(), "bcc32 5.5.1") + + # test when bcc32 returns unexpected but parseable version string + set_fake_return(0, "C++ compiler 5.5.1 2010-11-05") + self.assertEqual(cc.get_compiler_version(), "bcc32 5.5.1") + + # test when bcc32 returns unparseable version string + set_fake_return(0, "Borland C++ for Win32") + self.assertEqual(cc.get_compiler_version(), "bcc32 for Win32") + + # test when bcc32 returns a bad value + set_fake_return(0, "") + self.assertEqual(cc.get_compiler_version(), "bcc32") + + # test when bcc32 returns an error + set_fake_return(-1, None) + self.assertEqual(cc.get_compiler_version(), "bcc32") + +def test_suite(): + return unittest.makeSuite(BCPPCompilerTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff -r 7a50e549bd11 -r ca53ff77ce6f Lib/packaging/tests/test_command_build_ext.py --- a/Lib/packaging/tests/test_command_build_ext.py Thu Sep 29 20:01:55 2011 +0200 +++ b/Lib/packaging/tests/test_command_build_ext.py Thu Sep 29 16:15:10 2011 -0400 @@ -8,11 +8,44 @@ from packaging.errors import (UnknownFileError, CompileError, PackagingPlatformError) from packaging.command.build_ext import build_ext +from packaging.compiler import set_compiler from packaging.compiler.extension import Extension +from packaging.compiler.ccompiler import CCompiler from test.script_helper import assert_python_ok from packaging.tests import support, unittest, verbose +class FakeTestCompiler(CCompiler): + """This is a mock compiler which stores the arguments of all compile() + and link() calls made by build_ext. + """ + + name = "FakeTestCompiler" + executables = {} + + # modified by test_get_compiler_version() + # to test different version strings + version = None + + def __init__(self, *args, **kwds): + self.compile_calls = [] + self.link_calls = [] + CCompiler.__init__(self, *args, **kwds) + + def get_compiler_version(self): + return self.version + + def compile(self, *args, **kwds): + self.compile_calls.append((args, kwds)) + return [] + + def link(self, *args, **kwds): + self.link_calls.append((args, kwds)) + + def link_shared_object(self, *args, **kwds): + self.link_calls.append((args, kwds)) + +set_compiler(__name__ + ".FakeTestCompiler") class BuildExtTestCase(support.TempdirManager, support.LoggingCatcher, @@ -88,6 +121,99 @@ # make sure we get some library dirs under solaris self.assertGreater(len(cmd.library_dirs), 0) + def test_specific_compile_args(self): + fake_ext = Extension( + 'fake', ['fake.c'], + extra_compile_args=['COMMON', 'ARGS'], + specific_compile_args=[ + (r"aaa \d+\.\d+", ["aaa-compiler", "specific"]), + (r"bbb 1\.\d+", ["bbb-compiler", "{common_args}", + "specific"]), + (r"bbb", ["bbb-compiler", "generic", "{common_args}"]), + ] + ) + dist = Distribution({'name': 'fake', 'ext_modules': [fake_ext]}) + cmd = build_ext(dist) + support.fixup_build_ext(cmd) + cmd.compiler = 'FakeTestCompiler' + cmd.ensure_finalized() + + def get_postargs(): + return cmd.compiler_obj.compile_calls[0][1]["extra_postargs"] + + # test specific compile args are appended + FakeTestCompiler.version = "aaa 1.10" + cmd.run() + self.assertEqual(get_postargs(), + ['COMMON', 'ARGS', "aaa-compiler", "specific"]) + + # test first matching pattern is used, + # and that {common_args} is honored + FakeTestCompiler.version = "bbb 1.5" + cmd.run() + self.assertEqual(get_postargs(), + ["bbb-compiler", 'COMMON', 'ARGS', "specific"]) + + # test fallback pattern is used, + # and that {common_args} is honored + FakeTestCompiler.version = "bbb 2.3" + cmd.run() + self.assertEqual(get_postargs(), + ["bbb-compiler", "generic", 'COMMON', 'ARGS']) + + # test no matches just uses extra_compile_args + FakeTestCompiler.version = "gcc 4.5.2" + cmd.run() + self.assertEqual(get_postargs(), + ['COMMON', 'ARGS']) + + def test_specific_link_args(self): + fake_ext = Extension( + 'fake', ['fake.c'], + extra_link_args=['COMMON', 'ARGS'], + specific_link_args=[ + (r"aaa \d+\.\d+", ["aaa-compiler", "specific"]), + (r"bbb 1\.\d+", ["bbb-compiler", "{common_args}", + "specific"]), + (r"bbb", ["bbb-compiler", "generic", "{common_args}"]), + ] + ) + dist = Distribution({'name': 'fake', 'ext_modules': [fake_ext]}) + cmd = build_ext(dist) + support.fixup_build_ext(cmd) + cmd.compiler = 'FakeTestCompiler' + cmd.ensure_finalized() + def get_postargs(): + return cmd.compiler_obj.link_calls[0][1]["extra_postargs"] + + # test specific link args are appended + FakeTestCompiler.version = "aaa 1.10" + cmd.run() + self.assertEqual(get_postargs(), + ['COMMON', 'ARGS', "aaa-compiler", "specific"]) + + # test first matching pattern is used, and that {common_args} is honored + FakeTestCompiler.version = "bbb 1.5" + cmd.run() + self.assertEqual(get_postargs(), + ["bbb-compiler", 'COMMON', 'ARGS', "specific"]) + + # test fallback pattern is used, and that {common_args} is honored + FakeTestCompiler.version = "bbb 2.3" + cmd.run() + self.assertEqual(get_postargs(), + ["bbb-compiler", "generic", 'COMMON', 'ARGS']) + + # test no matches just uses extra_link_args + FakeTestCompiler.version = "gcc 4.5.2" + cmd.run() + self.assertEqual(get_postargs(), ['COMMON', 'ARGS']) + + # test no version works as well + FakeTestCompiler.version = None + cmd.run() + self.assertEqual(get_postargs(), ['COMMON', 'ARGS']) + def test_user_site(self): dist = Distribution({'name': 'xx'}) cmd = build_ext(dist) diff -r 7a50e549bd11 -r ca53ff77ce6f Lib/packaging/tests/test_config.py --- a/Lib/packaging/tests/test_config.py Thu Sep 29 20:01:55 2011 +0200 +++ b/Lib/packaging/tests/test_config.py Thu Sep 29 16:15:10 2011 -0400 @@ -110,6 +110,10 @@ [extension:one.speed_coconuts] sources = c_src/speed_coconuts.c extra_link_args = "`gcc -print-file-name=libgcc.a`" -shared +specific_link_args = + msvc [89]\.\d = -DPKGTESTFLAG='msvc' + gcc.* = -DPKGTESTFLAG='gcc-other' ;; sys.platform != 'win32' + gcc.* = -DPKGTESTFLAG='gcc-win32' ;; sys.platform == 'win32' define_macros = HAVE_CAIRO HAVE_GTK2 libraries = gecodeint gecodekernel -- sys.platform != 'win32' GecodeInt GecodeKernel -- sys.platform == 'win32' @@ -122,6 +126,9 @@ extra_compile_args = -fPIC -O2 -DGECODE_VERSION=$(./gecode_version) -- sys.platform != 'win32' /DGECODE_VERSION='win32' -- sys.platform == 'win32' +specific_compile_args = + msvc [89]\.\d = -DPKGTESTFLAG='msvc' -DOTHERPKGTESTFLAG + gcc [4-9]\. = -m3dnow language = cxx # corner case: if the parent package of an extension is declared but @@ -346,6 +353,13 @@ self.assertEqual(ext.extra_link_args, ['`gcc -print-file-name=libgcc.a`', '-shared']) + self.assertEqual(ext.specific_link_args, [ + ('msvc [89]\\.\\d', ['-DPKGTESTFLAG=msvc']), + ('gcc.*', ['-DPKGTESTFLAG=gcc-' + + ('win32' if sys.platform == 'win32' else 'other') + ]), + ]) + ext = ext_modules.get('two.fast_taunt') self.assertEqual(ext.sources, ['cxx_src/utils_taunt.cxx', 'cxx_src/python_module.cxx']) @@ -357,6 +371,10 @@ else: cargs.append('-DGECODE_VERSION=$(./gecode_version)') self.assertEqual(ext.extra_compile_args, cargs) + self.assertEqual(ext.specific_compile_args, [ + ('msvc [89]\\.\\d', ['-DPKGTESTFLAG=msvc', '-DOTHERPKGTESTFLAG']), + ('gcc [4-9]\\.', ['-m3dnow']), + ]) self.assertEqual(ext.language, 'cxx') self.write_file('setup.cfg', EXT_SETUP_CFG_BUGGY_1) diff -r 7a50e549bd11 -r ca53ff77ce6f Lib/packaging/tests/test_unixccompiler.py --- a/Lib/packaging/tests/test_unixccompiler.py Thu Sep 29 20:01:55 2011 +0200 +++ b/Lib/packaging/tests/test_unixccompiler.py Thu Sep 29 16:15:10 2011 -0400 @@ -2,6 +2,8 @@ import sys import sysconfig +from packaging.compiler import customize_compiler +import packaging.compiler.unixccompiler as unixccompiler from packaging.compiler.unixccompiler import UnixCCompiler from packaging.tests import unittest @@ -9,6 +11,8 @@ class UnixCCompilerTestCase(unittest.TestCase): def setUp(self): + self.addCleanup(setattr, unixccompiler, 'get_executable_result', + unixccompiler.get_executable_result) self._backup_platform = sys.platform self._backup_get_config_var = sysconfig.get_config_var @@ -124,6 +128,88 @@ sysconfig.get_config_var = gcv self.assertEqual(self.cc.rpath_foo(), '-blibpath:/foo') + @unittest.skipIf(sys.platform == 'win32', 'irrelevant on win32') + def test_active_get_compiler_version(self): + cc = self.cc + customize_compiler(cc) + cmd = cc.compiler[0] + value = cc.get_compiler_version() + self.assertTrue(value is None or value.startswith(cmd + " ") or + value == cmd) + if value and cmd == "gcc": + self.assertRegex(value, r"^gcc \d+\.\d+") + + def test_get_compiler_version(self): + + cc = self.cc + + ger_call_args = [] + def set_fake_return(rc, value): + """replace copy of get_executable_result() used by UnixCCompiler + with mock version that returns specified result""" + del ger_call_args[:] + def fake_ger(args, firstline=False): + ger_call_args[:] = args + return rc, value + unixccompiler.get_executable_result = fake_ger + + # test when gcc returns a proper -dumpversion value + cc.compiler = ["gcc"] + set_fake_return(0, "4.5.2") + self.assertEqual(cc.get_compiler_version(), "gcc 4.5.2") + self.assertEqual(ger_call_args, ["gcc", "-dumpversion"]) + + # test when gcc returns an error + cc.compiler = ["gcc"] + set_fake_return(-1, None) + self.assertEqual(cc.get_compiler_version(), "gcc") + self.assertEqual(ger_call_args, ["gcc", "-dumpversion"]) + + # test when gcc returns a bad value + cc.compiler = ["gcc"] + set_fake_return(0, "") + self.assertEqual(cc.get_compiler_version(), "gcc") + self.assertEqual(ger_call_args, ["gcc", "-dumpversion"]) + + # test when clang returns a proper -dumpversion value + # (should use same code as gcc) + cc.compiler = ["clang"] + set_fake_return(0, "2.8") + self.assertEqual(cc.get_compiler_version(), "clang 2.8") + self.assertEqual(ger_call_args, ["clang", "-dumpversion"]) + + # test other compiler returning gcc-style version string + cc.compiler = ["fake"] + set_fake_return(0, "fake (Debian/Squeeze 1.1) 4.4") + self.assertEqual(cc.get_compiler_version(), "fake 4.4") + self.assertEqual(ger_call_args, ["fake", "--version"]) + + # test other compiler returning generic version string + cc.compiler = ["fake"] + set_fake_return(0, "fake version 4.4 2011.08.01") + self.assertEqual(cc.get_compiler_version(), "fake 4.4") + self.assertEqual(ger_call_args, ["fake", "--version"]) + + # test other compiler returning unparseable version string + cc.compiler = ["fake"] + set_fake_return(0, "fake compiler for unix") + self.assertEqual(cc.get_compiler_version(), "fake compiler for unix") + self.assertEqual(ger_call_args, ["fake", "--version"]) + + # test other compiler returning error + cc.compiler = ["fake"] + set_fake_return(-1, None) + self.assertEqual(cc.get_compiler_version(), "fake") + self.assertEqual(ger_call_args, ["fake", "--version"]) + + # test other compiler with custom handler inserted by user. + cc.compiler = ["fake"] + def helper(cmd): + return cmd + "-helper 1.2" + cc._get_compiler_version_fake = helper + set_fake_return(-1, None) + self.assertEqual(cc.get_compiler_version(), "fake-helper 1.2") + self.assertEqual(ger_call_args, []) def test_suite(): return unittest.makeSuite(UnixCCompilerTestCase) diff -r 7a50e549bd11 -r ca53ff77ce6f Lib/packaging/util.py --- a/Lib/packaging/util.py Thu Sep 29 20:01:55 2011 +0200 +++ b/Lib/packaging/util.py Thu Sep 29 16:15:10 2011 -0400 @@ -448,6 +448,27 @@ else: return _find_exe_version('ld -v') +def get_executable_result(args, encoding="utf-8", firstline=False): + "helper for quickly retrieving result of cmd, used by get_compiler_version()" + if not find_executable(args[0]): + # NOTE: returning -1 so that bool(rc) is False only when rc==0 + return -1, None + from subprocess import Popen, PIPE + proc = Popen(args, stdout=PIPE, stderr=PIPE) + try: + stdout, stderr = proc.communicate() + finally: + proc.stdout.close() + proc.stderr.close() + # some commands like ld under MacOS X, will give the + # output in the stderr, rather than stdout. + if not stdout: + stdout = stderr + if encoding: + stdout = stdout.decode(encoding) + if firstline: + stdout = stdout.partition(os.linesep)[0].strip() + return proc.returncode, stdout def _find_exe_version(cmd, pattern=_RE_VERSION): """Find the version of an executable by running `cmd` in the shell.