# HG changeset patch # User Eli Collins # Date 1308857559 14400 # Node ID 43d0f15a54bb4a2b318a610a8472dfd41d3db5d7 # Parent 54fb77e0762c97571ad55e0427b06e8c602fb617 Issue #12242: add build_ext support for compiler-specific flags diff -r 54fb77e0762c -r 43d0f15a54bb Lib/packaging/command/build_ext.py --- a/Lib/packaging/command/build_ext.py Thu Jun 23 14:22:59 2011 +0200 +++ b/Lib/packaging/command/build_ext.py Thu Jun 23 15:32:39 2011 -0400 @@ -396,15 +396,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: @@ -434,7 +438,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) @@ -450,6 +457,44 @@ build_temp=self.build_temp, target_lang=language) + def _combine_extension_args(self, common_args, specific_args): + """returns compiler/link args appropriate for compiler, + given extra_compile/link_args and specific_compile/link_args + + common_args should be list of options (eg extra_compile_args). + specific_args should be list of [ (pattern, options) ... ] + entries (eg specific_compile_args). + + only the first entry in specific_args whose pattern matches + get_compiler_version() will be used. pattern should + be regexp object or string. + """ + #skip specific if empty + if not specific_args: + return common_args + + #find best set of options + 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: + #no specific args chosen, so return common args unchanged. + return common_args + + #insert common options from extra_args. + #look for element in options "{common_args}", + #and insert them there - defaults to beginning + MAGIC = "{common_args}" + if MAGIC in options: + idx = options.index(MAGIC) + 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 54fb77e0762c -r 43d0f15a54bb Lib/packaging/compiler/bcppcompiler.py --- a/Lib/packaging/compiler/bcppcompiler.py Thu Jun 23 14:22:59 2011 +0200 +++ b/Lib/packaging/compiler/bcppcompiler.py Thu Jun 23 15:32:39 2011 -0400 @@ -68,6 +68,9 @@ self.ldflags_exe = ['/Gn', '/q', '/x'] self.ldflags_exe_debug = ['/Gn', '/q', '/x','/r'] + def get_compiler_version(self): + #TODO: implement proper version numbering for Borland. + return "bcc32" # -- Worker methods ------------------------------------------------ diff -r 54fb77e0762c -r 43d0f15a54bb Lib/packaging/compiler/ccompiler.py --- a/Lib/packaging/compiler/ccompiler.py Thu Jun 23 14:22:59 2011 +0200 +++ b/Lib/packaging/compiler/ccompiler.py Thu Jun 23 15:32:39 2011 -0400 @@ -177,6 +177,22 @@ "must be tuple (string,), (string, string), or " + \ "(string, None)") + def get_compiler_version(self): + """Returns version string associated with compiler. + This is used to filter for compiler-specific options. + + Subclasses should provide implementation of this. + + * If possible, this should return something with the format + "{cmd} {vnum}" (eg: "gcc 4.5.2"), to make pattern matching easy. + * If version information is available, but not parseable, + this should return "{cmd} {vstr}" + (eg: "gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"). + * Failing that, this should return "{cmd}" (eg "gcc"). + if the name of the compiler is known. + * Finally, if no information is available, this should return None. + """ + return None # -- Bookkeeping methods ------------------------------------------- diff -r 54fb77e0762c -r 43d0f15a54bb Lib/packaging/compiler/extension.py --- a/Lib/packaging/compiler/extension.py Thu Jun 23 14:22:59 2011 +0200 +++ b/Lib/packaging/compiler/extension.py Thu Jun 23 15:32:39 2011 -0400 @@ -60,6 +60,22 @@ 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: [(regexp string,[string])] + similar to extra_compile_args, this is a list of (regexp pattern, args) + entries. if present, the first entry whose regexp matches + compiler.get_compiler_version() will have it's args + prepended after extra_compile_args. if one of elements + of args is the magic string "{common_args}", + the value of extra_compile_args will be inserted at that location + instead of at the beginning. + specific_link_args: [(regexp string,[string])] + similar to extra_link_args, this is a list of (regexp pattern, args) + entries. if present, the first entry whose regexp matches + compiler.get_compiler_version() will have it's args + prepended after extra_link_args. if one of elements + of args is the magic string "{common_args}", + the value of 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 +100,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 +124,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 54fb77e0762c -r 43d0f15a54bb Lib/packaging/compiler/msvc9compiler.py --- a/Lib/packaging/compiler/msvc9compiler.py Thu Jun 23 14:22:59 2011 +0200 +++ b/Lib/packaging/compiler/msvc9compiler.py Thu Jun 23 15:32:39 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 54fb77e0762c -r 43d0f15a54bb Lib/packaging/compiler/msvccompiler.py --- a/Lib/packaging/compiler/msvccompiler.py Thu Jun 23 14:22:59 2011 +0200 +++ b/Lib/packaging/compiler/msvccompiler.py Thu Jun 23 15:32:39 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 54fb77e0762c -r 43d0f15a54bb Lib/packaging/compiler/unixccompiler.py --- a/Lib/packaging/compiler/unixccompiler.py Thu Jun 23 14:22:59 2011 +0200 +++ b/Lib/packaging/compiler/unixccompiler.py Thu Jun 23 15:32:39 2011 -0400 @@ -15,7 +15,7 @@ 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,38 @@ if sys.platform == "cygwin": exe_extension = ".exe" + def get_compiler_version(self): + #NOTE: this is used by the cygwin & mingw subclasses as well. + + #try to dispatch to submethod that handles specific compiler. + #failing that, try to use fallback submethod. + 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): + "fallback for unknown compilers" + rc, result = get_executable_result([cmd, "--version"], firstline=True) + if not rc and result: + #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 54fb77e0762c -r 43d0f15a54bb Lib/packaging/config.py --- a/Lib/packaging/config.py Thu Jun 23 14:22:59 2011 +0200 +++ b/Lib/packaging/config.py Thu Jun 23 15:32:39 2011 -0400 @@ -33,6 +33,24 @@ if vals: return vals +def _pop_value_pairs(values_dct, key): + """Remove values from the dictionary and convert them to 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) + pair = (key.strip(), split(vals)) + fields.append(pair) + return fields def _rel_path(base, path): # normalizes and returns a lstripped-/-separated path @@ -268,6 +286,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 54fb77e0762c -r 43d0f15a54bb Lib/packaging/util.py --- a/Lib/packaging/util.py Thu Jun 23 14:22:59 2011 +0200 +++ b/Lib/packaging/util.py Thu Jun 23 15:32:39 2011 -0400 @@ -456,6 +456,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.