Repository: spyder-ide/spyder

Issue:
setuptools 61.0.0 breaks macOS installer
The latest release of `setuptools` 61.0.0 breaks the macOS installer.
Must constrain to <61.0.0.

I don't know what the underlying issue is, and it may require an update to `py2app`.

The observed symptoms are:
- The application bundle is named "spyder.app" rather than "Spyder.app"
- The application startup script is named "spyder.app/Contents/MacOS/spyder" rather than "Spyder.app/Contents/MacOS/Spyder"
- The `spyder` source code is included in _both_ "Spyder.app/Contents/Resources/lib/python39.zip" and "Spyder.app/Contents/Resources/lib/python3.9"


Discussion:
Sorry for closing this one. I thought it was fixed by PR #17547, but now I understand that you opened it so we don't forget to solve it in the future. The only thing is that it can't be tagged for 5.3.0 because our aim is to release that version next Wednesday and I don't think the solution could be found so soon.

Resolving patch:
diff --git a/installers/macOS/packages.py b/installers/macOS/packages.py
--- a/installers/macOS/packages.py
+++ b/installers/macOS/packages.py
@@ -20,10 +20,10 @@
     ImportError: The 'more_itertools' package is required; normally this is
     bundled with this package so if you get this warning, consult the
     packager of your distribution.
-pylsp_black :
-    Mandatory: python-pyls-black >=1.0.0 : None (NOK)
 pyls_spyder :
     Mandatory: pyls_spyder >=0.1.1 : None (NOK)
+pylsp_black :
+    Mandatory: python-pyls-black >=1.0.0 : None (NOK)
 setuptools :
     Mandatory: setuptools >=49.6.0 : None (NOK)
 spyder :
@@ -39,8 +39,8 @@
 PACKAGES = [
     'humanfriendly',
     'pkg_resources',
-    'pylsp_black',
     'pyls_spyder',
+    'pylsp_black',
     'setuptools',
     'spyder',
     'spyder_kernels',
diff --git a/installers/macOS/setup.py b/installers/macOS/setup.py
--- a/installers/macOS/setup.py
+++ b/installers/macOS/setup.py
@@ -11,18 +11,20 @@
 $ python setup.py
 """
 
-import os
 import sys
 import shutil
 from logging import getLogger, StreamHandler, Formatter
 from pathlib import Path
 from setuptools import setup
 
+from spyder import __version__ as SPYVER
+from spyder.config.base import MAC_APP_NAME
+
 # Setup logger
 fmt = Formatter('%(asctime)s [%(levelname)s] [%(name)s] -> %(message)s')
 h = StreamHandler()
 h.setFormatter(fmt)
-logger = getLogger('spyder-macOS')
+logger = getLogger('macOS-installer')
 logger.addHandler(h)
 logger.setLevel('INFO')
 
@@ -30,76 +32,15 @@
 THISDIR = Path(__file__).resolve().parent
 SPYREPO = (THISDIR / '..' / '..').resolve()
 ICONFILE = SPYREPO / 'img_src' / 'spyder.icns'
-SPYLINK = THISDIR / 'spyder'
+APPSCRIPT = SPYREPO / 'scripts' / 'spyder'
 
-sys.path.append(SPYREPO.as_posix())
-
-from spyder import __version__ as SPYVER
-from spyder.config.base import MAC_APP_NAME
+APP_BASE_NAME = MAC_APP_NAME[:-4]
 
 # Python version
 PYVER = [sys.version_info.major, sys.version_info.minor,
          sys.version_info.micro]
 
 
-def fix_zip_entry_points(zfile):
-    """
-    Fix zip archive so that pkg_resources will find entry points.
-    Remove if a better solution emerges.
-
-    Parameters
-    ----------
-    zfile : pathlib.Path
-        Path to zip archive.
-    """
-    import os
-    from zipfile import ZipFile
-
-    logger.info('Converting zip file...')
-
-    file = zfile.parent / 'temp'
-    ZipFile(zfile).extractall(file)
-    os.remove(zfile)
-    file.replace(zfile)
-
-
-def patch_py2app():
-    """
-    Patch py2app PyQt recipe and site.py for version 0.27.
-    Remove after version 0.28 is available.
-    """
-    from importlib.util import find_spec
-    from importlib.metadata import version
-    from packaging.version import parse
-
-    logger.info('Patching py2app...')
-
-    py2app_ver = version('py2app')
-    if parse(py2app_ver) > parse('0.27'):
-        raise DeprecationWarning(f'py2app version {py2app_ver} > 0.27; '
-                                 'stop using patch_py2app.')
-
-    root = Path(find_spec('py2app').origin).parent
-
-    # Patch site.py
-    site_file = root / 'apptemplate' / 'lib' / 'site.py'
-    append_text = ("builtins.quit = "
-                   "_sitebuiltins.Quitter('quit', 'Ctrl-D (i.e. EOF)')\n"
-                   "builtins.exit = "
-                   "_sitebuiltins.Quitter('exit', 'Ctrl-D (i.e. EOF)')\n")
-    text = site_file.read_text()
-    if append_text not in text:
-        site_file.write_text(text + append_text)
-
-    # Patch qt5.py
-    qt5_file = root / 'recipes' / 'qt5.py'
-    search_text = "if qtdir != os.path.dirname(PyQt5.__file__):"
-    replace_text = "if os.path.dirname(PyQt5.__file__) not in qtdir:"
-    text = qt5_file.read_text()
-    if replace_text not in text:
-        qt5_file.write_text(text.replace(search_text, replace_text))
-
-
 def make_app_bundle(dist_dir, make_lite=False):
     """
     Make macOS application bundle.
@@ -144,28 +85,9 @@ def make_app_bundle(dist_dir, make_lite=False):
         }
     }
 
-    # Copy main application script
-    app_script_name = MAC_APP_NAME.replace('.app', '.py')
-    app_script_path = SPYREPO / 'scripts' / app_script_name
-    shutil.copy2(SPYREPO / 'scripts' / 'spyder', app_script_path)
-
     # Build the application
-    try:
-        patch_py2app()
-        os.symlink(SPYREPO / 'spyder', SPYLINK)
-        setup(app=[app_script_path.as_posix()], options={'py2app': OPTIONS})
-        fix_zip_entry_points(
-            dist_dir / MAC_APP_NAME / 'Contents' / 'Resources' / 'lib' /
-            'python{}{}.zip'.format(*PYVER[:2]))
-    finally:
-        os.remove(app_script_path)
-        os.remove(SPYLINK)
-
-    # Copy Spyder egg-info
-    egg = SPYREPO / 'spyder.egg-info'
-    dest = (dist_dir / MAC_APP_NAME / 'Contents' / 'Resources' / 'lib' /
-            'python{}.{}'.format(*PYVER) / 'spyder.egg-info')
-    shutil.copytree(egg, dest)
+    setup(name=APP_BASE_NAME, app=[APPSCRIPT.as_posix()],
+          options={'py2app': OPTIONS})
 
     return
 
@@ -188,7 +110,7 @@ def make_disk_image(dist_dir, make_lite=False):
     from dmgbuild import build_dmg
     from dmgbuild.core import DMGError
 
-    volume_name = '{}-{} Py-{}.{}.{}'.format(MAC_APP_NAME[:-4], SPYVER, *PYVER)
+    volume_name = '{}-{} Py-{}.{}.{}'.format(APP_BASE_NAME, SPYVER, *PYVER)
     dmg_name = 'Spyder'
     if make_lite:
         volume_name += ' Lite'


Test patch: