[Python-checkins] bpo-45573: Introduce extension module flags in Makefile (GH-29594)

tiran webhook-mailer at python.org
Thu Nov 18 03:18:50 EST 2021


https://github.com/python/cpython/commit/25ecc040d007a55e4b5c30fa739054b52c1aacac
commit: 25ecc040d007a55e4b5c30fa739054b52c1aacac
branch: main
author: Christian Heimes <christian at python.org>
committer: tiran <christian at python.org>
date: 2021-11-18T09:18:44+01:00
summary:

bpo-45573: Introduce extension module flags in Makefile (GH-29594)

``configure`` now uses a standardized format to forward state, compiler
flags, and linker flags to ``Makefile``, ``setup.py``, and
``Modules/Setup``. ``makesetup`` use the new variables by default if a
module line does not contain any compiler or linker flags. ``setup.py``
has a new function ``addext()``.

For a module ``egg``, configure adds:

* ``MODULE_EGG`` with value yes, missing, disabled, or n/a
* ``MODULE_EGG_CFLAGS``
* ``MODULE_EGG_LDFLAGS``

``Makefile.pre.in`` may also provide ``MODULE_EGG_DEPS`` that lists
dependencies such as header files and static libs.

Signed-off-by: Christian Heimes <christian at python.org>

files:
A Misc/NEWS.d/next/Build/2021-11-17-19-02-51.bpo-45573.GMNdun.rst
M Makefile.pre.in
M Modules/Setup
M Modules/makesetup
M aclocal.m4
M configure
M configure.ac
M setup.py

diff --git a/Makefile.pre.in b/Makefile.pre.in
index 1535cabdade9c..55336d24ed194 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -201,12 +201,26 @@ RUNSHARED=       @RUNSHARED@
 # ensurepip options
 ENSUREPIP=      @ENSUREPIP@
 
+# Internal static libraries
+LIBMPDEC_A= Modules/_decimal/libmpdec/libmpdec.a
+LIBEXPAT_A= Modules/expat/libexpat.a
+
 # OpenSSL options for setup.py so sysconfig can pick up AC_SUBST() vars.
 OPENSSL_INCLUDES=@OPENSSL_INCLUDES@
 OPENSSL_LIBS=@OPENSSL_LIBS@
 OPENSSL_LDFLAGS=@OPENSSL_LDFLAGS@
 OPENSSL_RPATH=@OPENSSL_RPATH@
 
+# Module compiler and linker flags
+#   yes: module is available
+#   missing: build dependency is missing
+#   disabled: module is disabled
+#   n/a: module is not available on the current platform
+# MODULE_EGG=yes  # yes, missing, disabled, n/a
+# MODULE_EGG_CFLAGS=
+# MODULE_EGG_LDFLAGS=
+ at MODULE_BLOCK@
+
 # Default zoneinfo.TZPATH. Added here to expose it in sysconfig.get_config_var
 TZPATH=@TZPATH@
 
@@ -535,8 +549,6 @@ LIBMPDEC_HEADERS= \
 		$(srcdir)/Modules/_decimal/libmpdec/typearith.h \
 		$(srcdir)/Modules/_decimal/libmpdec/umodarith.h
 
-LIBMPDEC_A= Modules/_decimal/libmpdec/libmpdec.a
-
 ##########################################################################
 # pyexpat's expat library
 
@@ -562,8 +574,6 @@ LIBEXPAT_HEADERS= \
 		Modules/expat/xmltok.h \
 		Modules/expat/xmltok_impl.h
 
-LIBEXPAT_A= Modules/expat/libexpat.a
-
 #########################################################################
 # Rules
 
diff --git a/Misc/NEWS.d/next/Build/2021-11-17-19-02-51.bpo-45573.GMNdun.rst b/Misc/NEWS.d/next/Build/2021-11-17-19-02-51.bpo-45573.GMNdun.rst
new file mode 100644
index 0000000000000..688b22c3e48a8
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2021-11-17-19-02-51.bpo-45573.GMNdun.rst
@@ -0,0 +1,3 @@
+``configure`` now uses a unified format to set state, compiler flags, and
+linker flags in Makefile. The new macro ``PY_STDLIB_MOD`` sets three
+variables that are consumed by ``Modules/Setup`` and ``setup.py``.
diff --git a/Modules/Setup b/Modules/Setup
index 608866d9cedd5..414c6af4b69c2 100644
--- a/Modules/Setup
+++ b/Modules/Setup
@@ -42,6 +42,15 @@
 # You can also use any Make variable that is detected by configure and
 # defined in Makefile.pre.in, e.g. OpenSSL flags $(OPENSSL_INCLUDES).
 #
+# Rules generated by makesetup use additional variables:
+#
+# - All source file rules have a dependency on $(PYTHON_HEADERS) and on
+#   optional variable $(MODULES_{mod_upper}_DEPS).
+# - If no <cpparg> and no <library> arguments are given, then makesetup
+#   defaults to $(MODULES_{mod_upper}_CFLAGS) cppargs and
+#   $(MODULES_{mod_upper}_LDFLAGS) libraries. The variables are typically
+#   defined by configure.
+#
 # The build process works like this:
 #
 # 1. Build all modules that are declared as static in Modules/Setup,
@@ -149,7 +158,7 @@ time timemodule.c
 #_contextvars _contextvarsmodule.c
 #_csv _csv.c
 #_datetime _datetimemodule.c
-#_decimal _decimal/_decimal.c $(DECIMAL_CFLAGS) $(DECIMAL_LDFLAGS)
+#_decimal _decimal/_decimal.c
 #_heapq _heapqmodule.c
 #_json _json.c
 #_lsprof _lsprof.c rotatingtree.c
@@ -172,8 +181,8 @@ time timemodule.c
 #select selectmodule.c
 
 # XML
-#_elementtree _elementtree.c $(EXPAT_CFLAGS)
-#pyexpat pyexpat.c $(EXPAT_CFLAGS) $(EXPAT_LDFLAGS)
+#_elementtree _elementtree.c
+#pyexpat pyexpat.c
 
 # hashing builtins
 #_blake2 _blake2/blake2module.c _blake2/blake2b_impl.c _blake2/blake2s_impl.c
diff --git a/Modules/makesetup b/Modules/makesetup
index a8817fffb7c84..2335724e804cc 100755
--- a/Modules/makesetup
+++ b/Modules/makesetup
@@ -154,6 +154,7 @@ sed -e 's/[ 	]*#.*//' -e '/^[ 	]*$/d' |
 		cpps=
 		libs=
 		mods=
+		mods_upper=
 		skip=
 		for arg in $line
 		do
@@ -194,11 +195,17 @@ sed -e 's/[ 	]*#.*//' -e '/^[ 	]*$/d' |
 			*.*)		echo 1>&2 "bad word $arg in $line"
 					exit 1;;
 			-u)		skip=libs; libs="$libs -u";;
-			[a-zA-Z_]*)	mods="$mods $arg";;
+			[a-zA-Z_]*)
+					mods="$mods $arg"
+					mods_upper=$(echo $mods | tr '[a-z]' '[A-Z]');;
 			*)		echo 1>&2 "bad word $arg in $line"
 					exit 1;;
 			esac
 		done
+		if test -z "$cpps" -a -z "$libs"; then
+			cpps="\$(MODULE_${mods_upper}_CFLAGS)"
+			libs="\$(MODULE_${mods_upper}_LDFLAGS)"
+		fi
 		case $doconfig in
 		yes)
 			LIBS="$LIBS $libs"
@@ -245,7 +252,6 @@ sed -e 's/[ 	]*#.*//' -e '/^[ 	]*$/d' |
 			*)
 				cc="$cc \$(PY_BUILTIN_MODULE_CFLAGS)";;
 			esac
-			mods_upper=$(echo $mods | tr '[a-z]' '[A-Z]')
 			# force rebuild when header file or module build flavor (static/shared) is changed
 			rule="$obj: $src \$(MODULE_${mods_upper}_DEPS) \$(PYTHON_HEADERS) Modules/config.c; $cc $cpps -c $src -o $obj"
 			echo "$rule" >>$rulesf
diff --git a/aclocal.m4 b/aclocal.m4
index 2f1bd37528c85..6a33c0cc9d9e8 100644
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -619,3 +619,53 @@ AS_IF([test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"],
         [AC_DEFINE([HAVE_][$1], 1, [Enable ]m4_tolower([$1])[ support])])
 ])dnl PKG_HAVE_DEFINE_WITH_MODULES
 
+# AM_CONDITIONAL                                            -*- Autoconf -*-
+
+# Copyright (C) 1997-2020 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_CONDITIONAL(NAME, SHELL-CONDITION)
+# -------------------------------------
+# Define a conditional.
+AC_DEFUN([AM_CONDITIONAL],
+[AC_PREREQ([2.52])dnl
+ m4_if([$1], [TRUE],  [AC_FATAL([$0: invalid condition: $1])],
+       [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl
+AC_SUBST([$1_TRUE])dnl
+AC_SUBST([$1_FALSE])dnl
+_AM_SUBST_NOTMAKE([$1_TRUE])dnl
+_AM_SUBST_NOTMAKE([$1_FALSE])dnl
+m4_define([_AM_COND_VALUE_$1], [$2])dnl
+if $2; then
+  $1_TRUE=
+  $1_FALSE='#'
+else
+  $1_TRUE='#'
+  $1_FALSE=
+fi
+AC_CONFIG_COMMANDS_PRE(
+[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then
+  AC_MSG_ERROR([[conditional "$1" was never defined.
+Usually this means the macro was only invoked conditionally.]])
+fi])])
+
+# Copyright (C) 2006-2020 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_SUBST_NOTMAKE(VARIABLE)
+# ---------------------------
+# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in.
+# This macro is traced by Automake.
+AC_DEFUN([_AM_SUBST_NOTMAKE])
+
+# AM_SUBST_NOTMAKE(VARIABLE)
+# --------------------------
+# Public sister of _AM_SUBST_NOTMAKE.
+AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
+
diff --git a/configure b/configure
index 53dc4a23f8063..ab5afbf0b8aef 100755
--- a/configure
+++ b/configure
@@ -623,6 +623,13 @@ ac_includes_default="\
 #endif"
 
 ac_subst_vars='LTLIBOBJS
+MODULE_BLOCK
+MODULE__DECIMAL_FALSE
+MODULE__DECIMAL_TRUE
+MODULE__ELEMENTTREE_FALSE
+MODULE__ELEMENTTREE_TRUE
+MODULE_PYEXPAT_FALSE
+MODULE_PYEXPAT_TRUE
 TEST_MODULES
 LIBRARY_DEPS
 STATIC_LIBPYTHON
@@ -19196,6 +19203,183 @@ $as_echo "no" >&6; }
 fi
 
 
+case $ac_sys_system in #(
+  AIX) :
+    py_stdlib_not_available="_scproxy spwd" ;; #(
+  VxWorks*) :
+    py_stdlib_not_available="_scproxy _crypt termios grp" ;; #(
+  Darwin) :
+    py_stdlib_not_available="ossaudiodev spwd" ;; #(
+  CYGWIN*) :
+    py_stdlib_not_available="_scproxy nis" ;; #(
+  QNX*) :
+    py_stdlib_not_available="_scproxy nis" ;; #(
+  FreeBSD*) :
+    py_stdlib_not_available="_scproxy spwd" ;; #(
+  *) :
+    py_stdlib_not_available="_scproxy"
+ ;;
+esac
+
+
+MODULE_BLOCK=
+
+
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module pyexpat" >&5
+$as_echo_n "checking for stdlib extension module pyexpat... " >&6; }
+      case $py_stdlib_not_available in #(
+  *pyexpat*) :
+    py_cv_module_pyexpat=n/a ;; #(
+  *) :
+
+      if true; then :
+  if true; then :
+  py_cv_module_pyexpat=yes
+else
+  py_cv_module_pyexpat=missing
+fi
+else
+  py_cv_module_pyexpat=disabled
+
+fi
+
+   ;;
+esac
+  as_fn_append MODULE_BLOCK "MODULE_PYEXPAT=$py_cv_module_pyexpat$as_nl"
+  if test "x$py_cv_module_pyexpat" = xyes; then :
+
+    as_fn_append MODULE_BLOCK "MODULE_PYEXPAT_CFLAGS=$LIBEXPAT_CFLAGS$as_nl"
+    as_fn_append MODULE_BLOCK "MODULE_PYEXPAT_LDFLAGS=$LIBEXPAT_LDFLAGS$as_nl"
+     if true; then
+  MODULE_PYEXPAT_TRUE=
+  MODULE_PYEXPAT_FALSE='#'
+else
+  MODULE_PYEXPAT_TRUE='#'
+  MODULE_PYEXPAT_FALSE=
+fi
+
+
+else
+
+     if false; then
+  MODULE_PYEXPAT_TRUE=
+  MODULE_PYEXPAT_FALSE='#'
+else
+  MODULE_PYEXPAT_TRUE='#'
+  MODULE_PYEXPAT_FALSE=
+fi
+
+
+fi
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $py_cv_module_pyexpat" >&5
+$as_echo "$py_cv_module_pyexpat" >&6; }
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module _elementtree" >&5
+$as_echo_n "checking for stdlib extension module _elementtree... " >&6; }
+      case $py_stdlib_not_available in #(
+  *_elementtree*) :
+    py_cv_module__elementtree=n/a ;; #(
+  *) :
+
+      if true; then :
+  if true; then :
+  py_cv_module__elementtree=yes
+else
+  py_cv_module__elementtree=missing
+fi
+else
+  py_cv_module__elementtree=disabled
+
+fi
+
+   ;;
+esac
+  as_fn_append MODULE_BLOCK "MODULE__ELEMENTTREE=$py_cv_module__elementtree$as_nl"
+  if test "x$py_cv_module__elementtree" = xyes; then :
+
+    as_fn_append MODULE_BLOCK "MODULE__ELEMENTTREE_CFLAGS=$LIBEXPAT_CFLAGS$as_nl"
+    as_fn_append MODULE_BLOCK "MODULE__ELEMENTTREE_LDFLAGS=$as_nl"
+     if true; then
+  MODULE__ELEMENTTREE_TRUE=
+  MODULE__ELEMENTTREE_FALSE='#'
+else
+  MODULE__ELEMENTTREE_TRUE='#'
+  MODULE__ELEMENTTREE_FALSE=
+fi
+
+
+else
+
+     if false; then
+  MODULE__ELEMENTTREE_TRUE=
+  MODULE__ELEMENTTREE_FALSE='#'
+else
+  MODULE__ELEMENTTREE_TRUE='#'
+  MODULE__ELEMENTTREE_FALSE=
+fi
+
+
+fi
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $py_cv_module__elementtree" >&5
+$as_echo "$py_cv_module__elementtree" >&6; }
+
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module _decimal" >&5
+$as_echo_n "checking for stdlib extension module _decimal... " >&6; }
+      case $py_stdlib_not_available in #(
+  *_decimal*) :
+    py_cv_module__decimal=n/a ;; #(
+  *) :
+
+      if true; then :
+  if true; then :
+  py_cv_module__decimal=yes
+else
+  py_cv_module__decimal=missing
+fi
+else
+  py_cv_module__decimal=disabled
+
+fi
+
+   ;;
+esac
+  as_fn_append MODULE_BLOCK "MODULE__DECIMAL=$py_cv_module__decimal$as_nl"
+  if test "x$py_cv_module__decimal" = xyes; then :
+
+    as_fn_append MODULE_BLOCK "MODULE__DECIMAL_CFLAGS=$LIBMPDEC_CFLAGS$as_nl"
+    as_fn_append MODULE_BLOCK "MODULE__DECIMAL_LDFLAGS=$LIBMPDEC_LDFLAGS$as_nl"
+     if true; then
+  MODULE__DECIMAL_TRUE=
+  MODULE__DECIMAL_FALSE='#'
+else
+  MODULE__DECIMAL_TRUE='#'
+  MODULE__DECIMAL_FALSE=
+fi
+
+
+else
+
+     if false; then
+  MODULE__DECIMAL_TRUE=
+  MODULE__DECIMAL_FALSE='#'
+else
+  MODULE__DECIMAL_TRUE='#'
+  MODULE__DECIMAL_FALSE=
+fi
+
+
+fi
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $py_cv_module__decimal" >&5
+$as_echo "$py_cv_module__decimal" >&6; }
+
+
+# substitute multiline block, must come after last PY_STDLIB_MOD()
+
 
 # generate output files
 ac_config_files="$ac_config_files Makefile.pre Misc/python.pc Misc/python-embed.pc Misc/python-config.sh"
@@ -19312,6 +19496,30 @@ LTLIBOBJS=$ac_ltlibobjs
 
 
 
+if test -z "${MODULE_PYEXPAT_TRUE}" && test -z "${MODULE_PYEXPAT_FALSE}"; then
+  as_fn_error $? "conditional \"MODULE_PYEXPAT\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${MODULE_PYEXPAT_TRUE}" && test -z "${MODULE_PYEXPAT_FALSE}"; then
+  as_fn_error $? "conditional \"MODULE_PYEXPAT\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${MODULE__ELEMENTTREE_TRUE}" && test -z "${MODULE__ELEMENTTREE_FALSE}"; then
+  as_fn_error $? "conditional \"MODULE__ELEMENTTREE\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${MODULE__ELEMENTTREE_TRUE}" && test -z "${MODULE__ELEMENTTREE_FALSE}"; then
+  as_fn_error $? "conditional \"MODULE__ELEMENTTREE\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${MODULE__DECIMAL_TRUE}" && test -z "${MODULE__DECIMAL_FALSE}"; then
+  as_fn_error $? "conditional \"MODULE__DECIMAL\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${MODULE__DECIMAL_TRUE}" && test -z "${MODULE__DECIMAL_FALSE}"; then
+  as_fn_error $? "conditional \"MODULE__DECIMAL\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
 
 : "${CONFIG_STATUS=./config.status}"
 ac_write_fail=0
diff --git a/configure.ac b/configure.ac
index f43030e481068..43c8f768c18f8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -5936,6 +5936,68 @@ else
 fi
 AC_SUBST(TEST_MODULES)
 
+dnl Modules that are not available on some platforms
+dnl AIX has shadow passwords, but access is not via getspent()
+dnl VxWorks does not provide crypt() function
+AS_CASE([$ac_sys_system],
+  [AIX], [py_stdlib_not_available="_scproxy spwd"],
+  [VxWorks*], [py_stdlib_not_available="_scproxy _crypt termios grp"],
+  [Darwin], [py_stdlib_not_available="ossaudiodev spwd"],
+  [CYGWIN*], [py_stdlib_not_available="_scproxy nis"],
+  [QNX*], [py_stdlib_not_available="_scproxy nis"],
+  [FreeBSD*], [py_stdlib_not_available="_scproxy spwd"],
+  [py_stdlib_not_available="_scproxy"]
+)
+
+dnl _MODULE_BLOCK_ADD([VAR], [VALUE])
+dnl internal: adds $1=quote($2) to MODULE_BLOCK
+AC_DEFUN([_MODULE_BLOCK_ADD], [AS_VAR_APPEND([MODULE_BLOCK], ["$1=_AS_QUOTE([$2])$as_nl"])])
+MODULE_BLOCK=
+
+dnl Check for stdlib extension modules
+dnl PY_STDLIB_MOD([NAME], [ENABLED-TEST], [SUPPORTED-TEST], [CFLAGS], [LDFLAGS])
+dnl sets MODULE_$NAME based on $py_stdlib_not_available, ENABLED-TEST,
+dnl and SUPPORTED_TEST. ENABLED-TEST and SUPPORTED-TEST default to true if
+dnl empty.
+dnl    n/a: $NAME in $py_stdlib_not_available (not available on platform)
+dnl    yes: enabled and supported
+dnl    missing: enabled and not supported
+dnl    disabled: not enabled
+dnl sets MODULE_$NAME_CFLAGS and MODULE_$NAME_LDFLAGS
+AC_DEFUN([PY_STDLIB_MOD], [
+  AC_MSG_CHECKING([for stdlib extension module $1])
+  m4_pushdef([modcond], [MODULE_]m4_toupper([$1]))dnl
+  m4_pushdef([modstate], [py_cv_module_$1])dnl
+  AS_CASE([$py_stdlib_not_available],
+    [*$1*], [modstate=n/a],
+    [
+      AS_IF(m4_ifblank([$2], [true], [$2]),
+        [AS_IF([m4_ifblank([$3], [true], [$3])], [modstate=yes], [modstate=missing])],
+        [modstate=disabled]
+      )
+    ]
+  )
+  _MODULE_BLOCK_ADD(modcond, [$modstate])
+  AS_VAR_IF([modstate], [yes], [
+    _MODULE_BLOCK_ADD([MODULE_]m4_toupper([$1])[_CFLAGS], [$4])
+    _MODULE_BLOCK_ADD([MODULE_]m4_toupper([$1])[_LDFLAGS], [$5])
+    AM_CONDITIONAL(modcond, [true])
+  ], [
+    AM_CONDITIONAL(modcond, [false])
+  ])
+  AC_MSG_RESULT([$modstate])
+  m4_popdef([modcond])dnl
+  m4_popdef([modstate])dnl
+])
+
+dnl _elementtree loads libexpat via CAPI hook in pyexpat
+PY_STDLIB_MOD([pyexpat], [], [], [$LIBEXPAT_CFLAGS], [$LIBEXPAT_LDFLAGS])
+PY_STDLIB_MOD([_elementtree], [], [], [$LIBEXPAT_CFLAGS], [])
+
+PY_STDLIB_MOD([_decimal], [], [], [$LIBMPDEC_CFLAGS], [$LIBMPDEC_LDFLAGS])
+
+# substitute multiline block, must come after last PY_STDLIB_MOD()
+AC_SUBST([MODULE_BLOCK])
 
 # generate output files
 AC_CONFIG_FILES(Makefile.pre Misc/python.pc Misc/python-embed.pc Misc/python-config.sh)
diff --git a/setup.py b/setup.py
index 915169e68630d..8b9566e26bffd 100644
--- a/setup.py
+++ b/setup.py
@@ -364,57 +364,6 @@ def find_module_file(module, dirlist):
     return os.path.abspath(os.path.join(dirs[0], module))
 
 
-def parse_cflags(flags):
-    """Parse a string with compiler flags (-I, -D, -U, extra)
-    
-    Distutils appends extra args to the compiler arguments. Some flags like
-    -I must appear earlier. Otherwise the pre-processor picks up files
-    from system inclue directories.
-    """
-    include_dirs = []
-    define_macros = []
-    undef_macros = []
-    extra_compile_args = []
-    if flags is not None:
-        # shlex.split(None) reads from stdin
-        for token in shlex.split(flags):
-            switch = token[0:2]
-            value = token[2:]
-            if switch == '-I':
-                include_dirs.append(value)
-            elif switch == '-D':
-                key, _, val = value.partition("=")
-                if not val:
-                    val = None
-                define_macros.append((key, val))
-            elif switch == '-U':
-                undef_macros.append(value)
-            else:
-                extra_compile_args.append(token)
-
-    return include_dirs, define_macros, undef_macros, extra_compile_args
-
-
-def parse_ldflags(flags):
-    """Parse a string with linker flags (-L, -l, extra)"""
-    library_dirs = []
-    libraries = []
-    extra_link_args = []
-    if flags is not None:
-        # shlex.split(None) reads from stdin
-        for token in shlex.split(flags):
-            switch = token[0:2]
-            value = token[2:]
-            if switch == '-L':
-                library_dirs.append(value)
-            elif switch == '-l':
-                libraries.append(value)
-            else:
-                extra_link_args.append(token)
-
-    return library_dirs, libraries, extra_link_args
-
-
 class PyBuildExt(build_ext):
 
     def __init__(self, dist):
@@ -433,6 +382,74 @@ def __init__(self, dist):
     def add(self, ext):
         self.extensions.append(ext)
 
+    def addext(self, ext, *, update_flags=True):
+        """Add extension with Makefile MODULE_{name} support
+        """
+        if update_flags:
+            self.update_extension_flags(ext)
+
+        state = sysconfig.get_config_var(f"MODULE_{ext.name.upper()}")
+        if state == "yes":
+            self.extensions.append(ext)
+        elif state == "disabled":
+            self.disabled_configure.append(ext.name)
+        elif state == "missing":
+            self.missing.append(ext.name)
+        elif state == "n/a":
+            # not available on current platform
+            pass
+        else:
+            # not migrated to MODULE_{name} yet.
+            self.extensions.append(ext)
+
+    def update_extension_flags(self, ext):
+        """Update extension flags with module CFLAGS and LDFLAGS
+
+        Reads MODULE_{name}_CFLAGS and _LDFLAGS
+
+        Distutils appends extra args to the compiler arguments. Some flags like
+        -I must appear earlier, otherwise the pre-processor picks up files
+        from system inclue directories.
+        """
+        upper_name = ext.name.upper()
+        # Parse compiler flags (-I, -D, -U, extra args)
+        cflags = sysconfig.get_config_var(f"MODULE_{upper_name}_CFLAGS")
+        if cflags:
+            for token in shlex.split(cflags):
+                switch = token[0:2]
+                value = token[2:]
+                if switch == '-I':
+                    ext.include_dirs.append(value)
+                elif switch == '-D':
+                    key, _, val = value.partition("=")
+                    if not val:
+                        val = None
+                    ext.define_macros.append((key, val))
+                elif switch == '-U':
+                    ext.undef_macros.append(value)
+                else:
+                    ext.extra_compile_args.append(token)
+
+        # Parse linker flags (-L, -l, extra objects, extra args)
+        ldflags = sysconfig.get_config_var(f"MODULE_{upper_name}_LDFLAGS")
+        if ldflags:
+            for token in shlex.split(ldflags):
+                switch = token[0:2]
+                value = token[2:]
+                if switch == '-L':
+                    ext.library_dirs.append(value)
+                elif switch == '-l':
+                    ext.libraries.append(value)
+                elif (
+                    token[0] != '-' and
+                    token.endswith(('.a', '.o', '.so', '.sl', '.dylib'))
+                ):
+                    ext.extra_objects.append(token)
+                else:
+                    ext.extra_link_args.append(token)
+
+        return ext
+
     def set_srcdir(self):
         self.srcdir = sysconfig.get_config_var('srcdir')
         if not self.srcdir:
@@ -1527,32 +1544,11 @@ def detect_expat_elementtree(self):
         #
         # More information on Expat can be found at www.libexpat.org.
         #
-        cflags = parse_cflags(sysconfig.get_config_var("EXPAT_CFLAGS"))
-        include_dirs, define_macros, undef_macros, extra_compile_args = cflags
-        # ldflags includes either system libexpat or full path to
-        # our static libexpat.a.
-        ldflags = parse_ldflags(sysconfig.get_config_var("EXPAT_LDFLAGS"))
-        library_dirs, libraries, extra_link_args = ldflags
-
-        self.add(Extension('pyexpat',
-                           include_dirs=include_dirs,
-                           define_macros=define_macros,
-                           undef_macros=undef_macros,
-                           extra_compile_args=extra_compile_args,
-                           library_dirs=library_dirs,
-                           libraries=libraries,
-                           extra_link_args=extra_link_args,
-                           sources=['pyexpat.c']))
+        self.addext(Extension('pyexpat', sources=['pyexpat.c']))
 
         # Fredrik Lundh's cElementTree module.  Note that this also
         # uses expat (via the CAPI hook in pyexpat).
-        self.add(Extension('_elementtree',
-                           include_dirs=include_dirs,
-                           define_macros=define_macros,
-                           undef_macros=undef_macros,
-                           extra_compile_args=extra_compile_args,
-                           # no EXPAT_LDFLAGS
-                           sources=['_elementtree.c']))
+        self.addext(Extension('_elementtree', sources=['_elementtree.c']))
 
     def detect_multibytecodecs(self):
         # Hye-Shik Chang's CJKCodecs modules.
@@ -2046,26 +2042,14 @@ def detect_ctypes(self):
 
     def detect_decimal(self):
         # Stefan Krah's _decimal module
-        sources = ['_decimal/_decimal.c']
-
-        cflags = parse_cflags(sysconfig.get_config_var("DECIMAL_CFLAGS"))
-        include_dirs, define_macros, undef_macros, extra_compile_args = cflags
-        # ldflags includes either system libmpdec or full path to
-        # our static libmpdec.a.
-        ldflags = parse_ldflags(sysconfig.get_config_var("DECIMAL_LDFLAGS"))
-        library_dirs, libraries, extra_link_args = ldflags
-
-        # Uncomment for extra functionality:
-        #define_macros.append(('EXTRA_FUNCTIONALITY', 1))
-        self.add(Extension('_decimal',
-                           include_dirs=include_dirs,
-                           define_macros=define_macros,
-                           undef_macros=undef_macros,
-                           extra_compile_args=extra_compile_args,
-                           library_dirs=library_dirs,
-                           libraries=libraries,
-                           extra_link_args=extra_link_args,
-                           sources=sources))
+        self.addext(
+            Extension(
+                '_decimal',
+                ['_decimal/_decimal.c'],
+                # Uncomment for extra functionality:
+                # define_macros=[('EXTRA_FUNCTIONALITY', 1)]
+            )
+        )
 
     def detect_openssl_hashlib(self):
         # Detect SSL support for the socket module (via _ssl)



More information about the Python-checkins mailing list