Subject: bug#35322: AM_PATH_PYTHON incompatible with Python frameworks on macOS

From: Joshua Root <[email-masked]>
Date: Sat, 27 Feb 2021 06:18:41 +1100

How about this? It bases the default values of PYTHON_PREFIX and 
PYTHON_EXEC_PREFIX on sys.prefix and sys.exec_prefix respectively, which 
the documentation has long said might happen. Behaviour should not 
change in the case that sys.prefix = $prefix. The variables can still be 
overridden; e.g. PYTHON_PREFIX is defined with reference to $prefix if 
the former is a subdir of (or the same as) the latter, and pythondir is 
defined with reference to $PYTHON_PREFIX.

There's also a fix for an apparent bug where $am_py_prefix was used 
instead of $am_py_exec_prefix when setting up the value of pyexecdir.

- Josh
diff --git a/doc/automake.texi b/doc/automake.texi
index 8f5a9fcee..f71876016 100644
--- a/doc/automake.texi
+++ b/doc/automake.texi
@@ -7882,14 +7882,14 @@ The Python version number, in the form @var{major}.@var{minor}
 @samp{'%u.%u' % sys.version_info[:2]}.
 
 @item PYTHON_PREFIX
-The string @samp{$@{prefix@}}.  This term may be used in future work
-that needs the contents of Python's @samp{sys.prefix}, but general
-consensus is to always use the value from @command{configure}.
+The value of Python's @samp{sys.prefix} variable. Often the same as
+@samp{$@{prefix@}}, but may differ on some platforms such as macOS
+(where Python is usually installed as a Framework).
 
 @item PYTHON_EXEC_PREFIX
-The string @samp{$@{exec_prefix@}}.  This term may be used in future work
-that needs the contents of Python's @samp{sys.exec_prefix}, but general
-consensus is to always use the value from @command{configure}.
+The value of Python's @samp{sys.exec_prefix} variable. Often the same
+as @samp{$@{exec_prefix@}}, but may differ on some platforms such as
+macOS (where Python is usually installed as a Framework).
 
 @item PYTHON_PLATFORM
 The canonical name used by Python to describe the operating system, as
diff --git a/m4/python.m4 b/m4/python.m4
index b2302baa3..294095a3e 100644
--- a/m4/python.m4
+++ b/m4/python.m4
@@ -93,16 +93,57 @@ AC_DEFUN([AM_PATH_PYTHON],
   dnl version is not of interest.
 
   AC_CACHE_CHECK([for $am_display_PYTHON version], [am_cv_python_version],
-    [am_cv_python_version=`$PYTHON -c "import sys; print('%u.%u' % sys.version_info[[:2]])"`])
+    [am_cv_python_version=`$PYTHON -c "import sys; print ('%u.%u' % sys.version_info[[:2]])"`])
   AC_SUBST([PYTHON_VERSION], [$am_cv_python_version])
 
-  dnl Use the values of $prefix and $exec_prefix for the corresponding
+  dnl Use the values of sys.prefix and sys.exec_prefix for the corresponding
   dnl values of PYTHON_PREFIX and PYTHON_EXEC_PREFIX.  These are made
   dnl distinct variables so they can be overridden if need be.  However,
   dnl general consensus is that you shouldn't need this ability.
 
-  AC_SUBST([PYTHON_PREFIX], ['${prefix}'])
-  AC_SUBST([PYTHON_EXEC_PREFIX], ['${exec_prefix}'])
+  AC_CACHE_CHECK([for $am_display_PYTHON prefix], [am_cv_python_prefix],
+    [am_cv_python_prefix=`$PYTHON -c "import sys; sys.stdout.write(sys.prefix)"`])
+
+  dnl If sys.prefix is a subdir of $prefix, replace the literal value of $prefix
+  dnl with a variable reference so it can be overridden.
+  if test "x$prefix" = xNONE
+  then
+   am__usable_prefix=$ac_default_prefix
+  else
+   am__usable_prefix=$prefix
+  fi
+  case $am_cv_python_prefix in
+     $am__usable_prefix*)
+       am__strip_prefix=`echo "$am__usable_prefix" | sed 's|.|.|g'`
+       am_python_prefix_subst=`echo "$am_cv_python_prefix" | sed "s,^$am__strip_prefix,\\${prefix},"`
+       ;;
+     *)
+       am_python_prefix_subst=$am_cv_python_prefix
+       ;;
+  esac
+  AC_SUBST([PYTHON_PREFIX], [$am_python_prefix_subst])
+
+  AC_CACHE_CHECK([for $am_display_PYTHON exec_prefix], [am_cv_python_exec_prefix],
+    [am_cv_python_exec_prefix=`$PYTHON -c "import sys; sys.stdout.write(sys.exec_prefix)"`])
+  dnl If sys.exec_prefix is a subdir of $exec_prefix, replace the
+  dnl literal value of $exec_prefix with a variable reference so it can
+  dnl be overridden.
+  if test "x$exec_prefix" = xNONE
+  then
+   am__usable_exec_prefix=$am__usable_prefix
+  else
+   am__usable_exec_prefix=$exec_prefix
+  fi
+  case $am_cv_python_exec_prefix in
+     $am__usable_exec_prefix*)
+       am__strip_prefix=`echo "$am__usable_exec_prefix" | sed 's|.|.|g'`
+       am_python_exec_prefix_subst=`echo "$am_cv_python_exec_prefix" | sed "s,^$am__strip_prefix,\\${exec_prefix},"`
+       ;;
+     *)
+       am_python_exec_prefix_subst=$am_cv_python_exec_prefix
+       ;;
+  esac
+  AC_SUBST([PYTHON_EXEC_PREFIX], [$am_python_exec_prefix_subst])
 
   dnl At times (like when building shared libraries) you may want
   dnl to know which OS platform Python thinks this is.
@@ -140,11 +181,11 @@ except ImportError:
   dnl Query distutils for this directory.
   AC_CACHE_CHECK([for $am_display_PYTHON script directory],
     [am_cv_python_pythondir],
-    [if test "x$prefix" = xNONE
+    [if test "x$am_cv_python_prefix" = x
      then
-       am_py_prefix=$ac_default_prefix
+       am_py_prefix=$am__usable_prefix
      else
-       am_py_prefix=$prefix
+       am_py_prefix=$am_cv_python_prefix
      fi
      am_cv_python_pythondir=`$PYTHON -c "
 $am_python_setup_sysconfig
@@ -157,13 +198,13 @@ sys.stdout.write(sitedir)"`
      case $am_cv_python_pythondir in
      $am_py_prefix*)
        am__strip_prefix=`echo "$am_py_prefix" | sed 's|.|.|g'`
-       am_cv_python_pythondir=`echo "$am_cv_python_pythondir" | sed "s,^$am__strip_prefix,$PYTHON_PREFIX,"`
+       am_cv_python_pythondir=`echo "$am_cv_python_pythondir" | sed "s,^$am__strip_prefix,\\${PYTHON_PREFIX},"`
        ;;
      *)
        case $am_py_prefix in
          /usr|/System*) ;;
          *)
-	  am_cv_python_pythondir=$PYTHON_PREFIX/lib/python$PYTHON_VERSION/site-packages
+	  am_cv_python_pythondir="\${PYTHON_PREFIX}/lib/python$PYTHON_VERSION/site-packages"
 	  ;;
        esac
        ;;
@@ -182,30 +223,30 @@ sys.stdout.write(sitedir)"`
   dnl Query distutils for this directory.
   AC_CACHE_CHECK([for $am_display_PYTHON extension module directory],
     [am_cv_python_pyexecdir],
-    [if test "x$exec_prefix" = xNONE
+    [if test "x$am_cv_python_exec_prefix" = x
      then
-       am_py_exec_prefix=$am_py_prefix
+       am_py_exec_prefix=$am__usable_exec_prefix
      else
-       am_py_exec_prefix=$exec_prefix
+       am_py_exec_prefix=$am_cv_python_exec_prefix
      fi
      am_cv_python_pyexecdir=`$PYTHON -c "
 $am_python_setup_sysconfig
 if can_use_sysconfig:
-    sitedir = sysconfig.get_path('platlib', vars={'platbase':'$am_py_prefix'})
+    sitedir = sysconfig.get_path('platlib', vars={'platbase':'$am_py_exec_prefix'})
 else:
     from distutils import sysconfig
-    sitedir = sysconfig.get_python_lib(1, 0, prefix='$am_py_prefix')
+    sitedir = sysconfig.get_python_lib(1, 0, prefix='$am_py_exec_prefix')
 sys.stdout.write(sitedir)"`
      case $am_cv_python_pyexecdir in
      $am_py_exec_prefix*)
        am__strip_prefix=`echo "$am_py_exec_prefix" | sed 's|.|.|g'`
-       am_cv_python_pyexecdir=`echo "$am_cv_python_pyexecdir" | sed "s,^$am__strip_prefix,$PYTHON_EXEC_PREFIX,"`
+       am_cv_python_pyexecdir=`echo "$am_cv_python_pyexecdir" | sed "s,^$am__strip_prefix,\\${PYTHON_EXEC_PREFIX},"`
        ;;
      *)
        case $am_py_exec_prefix in
          /usr|/System*) ;;
          *)
-	   am_cv_python_pyexecdir=$PYTHON_EXEC_PREFIX/lib/python$PYTHON_VERSION/site-packages
+	   am_cv_python_pyexecdir="\${PYTHON_EXEC_PREFIX}/lib/python$PYTHON_VERSION/site-packages"
 	   ;;
        esac
        ;;
diff --git a/t/instmany-python.sh b/t/instmany-python.sh
index dce3594b5..3b6d48589 100644
--- a/t/instmany-python.sh
+++ b/t/instmany-python.sh
@@ -119,18 +119,18 @@ cd build
 test -n "$orig_INSTALL"
 $MAKE
 # Try whether native install (or install-sh) works.
-$MAKE install
+$MAKE install PYTHON_PREFIX="$instdir"
 test -n "$(find "$instdir" -name python1.py)"
 # Multiple uninstall should work, too.
-$MAKE uninstall
-$MAKE uninstall
+$MAKE uninstall PYTHON_PREFIX="$instdir"
+$MAKE uninstall PYTHON_PREFIX="$instdir"
 test $(find "$instdir" -type f -print | wc -l) -eq 0
 
 # Try whether we don't exceed the low limit.
 PATH=$nPATH; export PATH
-run_make INSTALL=my-install install
+run_make INSTALL=my-install PYTHON_PREFIX="$instdir" install
 test -n "$(find "$instdir" -name python1.py)"
-run_make INSTALL=my-install uninstall
+run_make INSTALL=my-install PYTHON_PREFIX="$instdir" uninstall
 test $(find "$instdir" -type f -print | wc -l) -eq 0
 PATH=$oPATH; export PATH
 
@@ -143,14 +143,14 @@ for file in python3.py python$nfiles.py
 do
   chmod a-r $srcdir/$file
   test ! -r $srcdir/$file || skip_ "cannot drop file read permissions"
-  $MAKE install && exit 1
+  $MAKE install PYTHON_PREFIX="$instdir" && exit 1
   chmod u+r $srcdir/$file
 done
 
 for file in npython3.py npython$nfiles.py
 do
   chmod a-r $srcdir/$file
-  $MAKE install && exit 1
+  $MAKE install PYTHON_PREFIX="$instdir" && exit 1
   chmod u+r $srcdir/$file
 done
 
diff --git a/t/python-vars.sh b/t/python-vars.sh
index b53018b37..9e2287e36 100644
--- a/t/python-vars.sh
+++ b/t/python-vars.sh
@@ -30,10 +30,10 @@ CONFIG_SITE=/dev/null; export CONFIG_SITE
 # on them should be done in the 'python-virtualenv.sh' test.
 # 
 # This version identification is duplicated in python.m4 (and the manual).
-PYTHON_VERSION=$($PYTHON -c 'import sys; print("%u.%u" % sys.version_info[:2])') || exit 1
-PYTHON_PLATFORM=$($PYTHON -c 'import sys; print(sys.platform)') || exit 1
-PYTHON_EXEC_PREFIX='${exec_prefix}'
-PYTHON_PREFIX='${prefix}'
+PYTHON_VERSION=$($PYTHON -c 'import sys; print ("%u.%u" % sys.version_info[:2])') || exit 1
+PYTHON_PLATFORM=$($PYTHON -c 'import sys; print (sys.platform)') || exit 1
+PYTHON_EXEC_PREFIX=$($PYTHON -c 'import sys; print (sys.exec_prefix)') || exit 1
+PYTHON_PREFIX=$($PYTHON -c 'import sys; print (sys.prefix)') || exit 1
 pkgpythondir="\${pythondir}/$me"
 pkgpyexecdir="\${pyexecdir}/$me"
 
@@ -62,17 +62,17 @@ check-local: test-in test-am
 
 test-in:
 	cat pythondir
-	case `cat pythondir` in '$${prefix}'/*);; *) exit 1;; esac
+	case `cat pythondir` in '$${PYTHON_PREFIX}'/*);; *) exit 1;; esac
 	cat pyexecdir
-	case `cat pyexecdir` in '$${exec_prefix}'/*);; *) exit 1;; esac
+	case `cat pyexecdir` in '$${PYTHON_EXEC_PREFIX}'/*);; *) exit 1;; esac
 	cat $(srcdir)/vars-exp
 	cat $(builddir)/vars-got
 	diff $(srcdir)/vars-exp $(builddir)/vars-got
 
 ## Note: this target's rules will be extended in the "for" loop below.
 test-am:
-	case '$(pythondir)' in '$(prefix)'/*);; *) exit 1;; esac
-	case '$(pyexecdir)' in '$(exec_prefix)'/*);; *) exit 1;; esac
+	case '$(pythondir)' in '$(PYTHON_PREFIX)'/*);; *) exit 1;; esac
+	case '$(pyexecdir)' in '$(PYTHON_EXEC_PREFIX)'/*);; *) exit 1;; esac
 END
 
 echo @pythondir@ > pythondir.in
@@ -85,7 +85,7 @@ for var in $pyvars; do
   eval val=\$$var
   echo "var=$val" >> vars-exp
   echo "var=@$var@" >> vars-got.in
-  echo "${tab}test x'\$($var)' = x'$val'" >> Makefile.am
+  echo "${tab}test x'\$($var)' = x'$val' || test \"\$NO_CHECK_PYTHON_PREFIX\"" >> Makefile.am
 done
 
 cat Makefile.am
@@ -98,10 +98,15 @@ for var in pythondir pyexecdir $pyvars; do
   grep "^$var *=" Makefile.in
 done
 
+instdir=$(pwd)/inst
+
 $AUTOCONF
-./configure PYTHON="$PYTHON"
+./configure --prefix="$instdir" PYTHON="$PYTHON"
 
 $MAKE test-in test-am
-$MAKE distcheck
+# This tries to install to $PYTHON_PREFIX, which may not be writable.
+# Override it to something safe, but then of course we have to skip
+# checking that it is what we originally set it to.
+$MAKE distcheck PYTHON_PREFIX="$instdir" NO_CHECK_PYTHON_PREFIX=1
 
 :