Repository: spyder-ide/spyder

Issue:
Problems with Spyder on Mac with Conda-forge and `applaunchservices`
<!--- **PLEASE READ:** When submitting here, please ensure you've completed the following checklist and checked the boxes to confirm. Issue reports without it may be closed. Thanks! --->

### Issue Report Checklist

* [X] Searched the [issues page](https://github.com/spyder-ide/spyder/issues?q=is%3Aissue) for similar reports
* [ ] Read the relevant sections of the [Spyder Troubleshooting Guide](https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ) and followed its advice
* [ ] Reproduced the issue after updating with ``conda update spyder`` (or ``pip``, if not using Anaconda)
* [ ] Could not reproduce inside ``jupyter qtconsole`` (if console-related)
* [ ] Tried basic troubleshooting (if a bug/error)
    * [ ] Restarted Spyder
    * [ ] Reset preferences with ``spyder --reset``
    * [ ] Reinstalled the latest version of [Anaconda](https://www.anaconda.com/download/)
    * [ ] Tried the other applicable steps from the Troubleshooting Guide
* [ ] Completed the **Problem Description**, **Steps to Reproduce** and **Version** sections below

## Problem Description
We want to release a plugin to spyder on conda-forge. 
Our package must be a `noarch` type with the twist that python >= 3.8.
conda-forge insists  and we run `pip check` in the recipe (which makes a lot of sense), however this fails because spyder relays on `applaunchservices` for OSX, but not all it's dependencies are in conda-forge :frowning_face: and thus the check fails as follows: 

![image](https://user-images.githubusercontent.com/3516972/167175197-a5ccc718-c9d1-4891-904c-adbf4fa72f40.png)
 
Fortunately for us, @goanpeca is the conda-forge reviewer, so I asked him if he could approve our feedstock when I do the following in or recipe: 

![image](https://user-images.githubusercontent.com/3516972/167175538-163117f6-c356-4d43-bdab-68c1b92d41d2.png)

This will get me up and running, however the cleaner way would be if we take the `applaunchservices` out of spyder's conda-forge recipe, this way others (like this guy from `PROCITEC` ?) will not have the same problem as us.

All plugins can then enable the normal `pip check` without selectors (and discussions with conda-forge) and once the `applauchservices` work again, then we can enable it again and support for spyder on OSX under CONDA, the plugin recipies don't have to do anything anymore (presumed that the plugins are `noarch`)

Could this be done in the upcomming 5.3.1 release already ?



Discussion:
Thanks for reporting @nerohmot! We'll take a look at this in our next release.
:pray: 
@impact27 is looking into this already. This is not a Spyder issue but an `applaunchservices` conda packaging issue, AFIK. See https://github.com/impact27/applaunchservices/issues/1
We can fix this on our side by requiring a more recent version of `applaunchservices`.

Resolving patch:
diff --git a/install_dev_repos.py b/install_dev_repos.py
--- a/install_dev_repos.py
+++ b/install_dev_repos.py
@@ -58,11 +58,12 @@
 
 def get_python_lsp_version():
     """Get current version to pass it to setuptools-scm."""
-    req_file = DEVPATH / 'requirements' / 'conda.txt'
+    req_file = DEVPATH / 'requirements' / 'main.yml'
     with open(req_file, 'r', encoding='utf-8') as f:
         for line in f:
             if 'python-lsp-server' not in line:
                 continue
+            line = line.split('-')[-1]
             specifiers = Requirement(line).specifier
             break
         else:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -202,7 +202,7 @@ def run(self):
 
 
 install_requires = [
-    'applaunchservices>=0.1.7;platform_system=="Darwin"',
+    'applaunchservices>=0.3.0;platform_system=="Darwin"',
     'atomicwrites>=1.2.0',
     'chardet>=2.0.0',
     'cloudpickle>=0.5.0',
diff --git a/spyder/dependencies.py b/spyder/dependencies.py
--- a/spyder/dependencies.py
+++ b/spyder/dependencies.py
@@ -29,7 +29,7 @@
 # Versions
 # =============================================================================
 # Hard dependencies
-APPLAUNCHSERVICES_REQVER = '>=0.1.7'
+APPLAUNCHSERVICES_REQVER = '>=0.3.0'
 ATOMICWRITES_REQVER = '>=1.2.0'
 CHARDET_REQVER = '>=2.0.0'
 CLOUDPICKLE_REQVER = '>=0.5.0'


Test patch:
diff --git a/requirements/tests.txt b/requirements/tests.txt
deleted file mode 100644
--- a/requirements/tests.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-# We can not refer to an environment.yml file from another
-# So to get performant launches on mybinder.org, we have copied
-# the contents of this file to binder/environment.yml. If you
-# make changes here, please copy them over there too.
-coverage
-cython
-flaky
-matplotlib
-pandas
-pillow
-pytest <7.0
-pytest-cov
-pytest-lazy-fixture
-pytest-mock
-pytest-order
-pytest-qt
-pytest-timeout
-pyyaml
-scipy
-sympy
diff --git a/requirements/tests.yml b/requirements/tests.yml
new file mode 100644
--- /dev/null
+++ b/requirements/tests.yml
@@ -0,0 +1,23 @@
+# We can not refer to an environment.yml file from another
+# So to get performant launches on mybinder.org, we have copied
+# the contents of this file to binder/environment.yml. If you
+# make changes here, please copy them over there too.
+channels:
+  - conda-forge
+dependencies:
+  - coverage
+  - cython
+  - flaky
+  - matplotlib
+  - pandas
+  - pillow
+  - pytest <7.0
+  - pytest-cov
+  - pytest-lazy-fixture
+  - pytest-mock
+  - pytest-order
+  - pytest-qt
+  - pytest-timeout
+  - pyyaml
+  - scipy
+  - sympy
diff --git a/spyder/tests/test_dependencies_in_sync.py b/spyder/tests/test_dependencies_in_sync.py
--- a/spyder/tests/test_dependencies_in_sync.py
+++ b/spyder/tests/test_dependencies_in_sync.py
@@ -6,7 +6,7 @@
 
 # Standard library imports
 from ast import literal_eval
-import os
+import os.path as osp
 
 # Third party imports
 import yaml
@@ -15,37 +15,15 @@
 from spyder.dependencies import DESCRIPTIONS, OPTIONAL
 
 # Constants
-HERE = os.path.abspath(os.path.dirname(__file__))
-ROOT_PATH = os.path.dirname(os.path.dirname(HERE))
-ENV_FPATH = os.path.join(ROOT_PATH, 'binder', 'environment.yml')
-REQ_FPATH = os.path.join(ROOT_PATH, 'requirements', 'conda.txt')
-REQ_TEST_FPATH = os.path.join(ROOT_PATH, 'requirements', 'tests.txt')
-SETUP_FPATH = os.path.join(ROOT_PATH, 'setup.py')
-
-
-def parse_requirements(fpath):
-    """
-    Parse a requirements file and return a dict of deps and versions.
-    """
-    with open(fpath, 'r') as fh:
-        data = fh.read()
-
-    lines = data.split('\n')
-    lines = [line.strip() for line in lines if line and line[0] != '#']
-
-    deps = {}
-    for line in lines:
-        parts = line.split(' ')
-        if len(parts) > 1:
-            ver = parts[-1]
-            if ver[0] == '=':
-                ver = '=' + ver
-
-            deps[parts[0].lower()] = ver
-        else:
-            deps[parts[0].lower()] = None
-
-    return deps
+HERE = osp.abspath(osp.dirname(__file__))
+ROOT_PATH = osp.dirname(osp.dirname(HERE))
+ENV_FPATH = osp.join(ROOT_PATH, 'binder', 'environment.yml')
+REQ_FPATH = osp.join(ROOT_PATH, 'requirements', 'main.yml')
+REQ_WINDOWS_FPATH = osp.join(ROOT_PATH, 'requirements', 'windows.yml')
+REQ_MAC_FPATH = osp.join(ROOT_PATH, 'requirements', 'macos.yml')
+REQ_LINUX_FPATH = osp.join(ROOT_PATH, 'requirements', 'linux.yml')
+REQ_TEST_FPATH = osp.join(ROOT_PATH, 'requirements', 'tests.yml')
+SETUP_FPATH = osp.join(ROOT_PATH, 'setup.py')
 
 
 def parse_environment_yaml(fpath):
@@ -192,55 +170,73 @@ def parse_setup_extra_requires(fpath):
 
 def test_dependencies_for_binder_in_sync():
     """
-    Binder environment yaml should be the sum of conda.txt and tests.txt
+    Binder environment yaml should be the sum of main.yml and tests.yml
     requirements.
     """
     spyder_env = parse_environment_yaml(ENV_FPATH)
-    spyder_reqs = parse_requirements(REQ_FPATH)
-    test_reqs = parse_requirements(REQ_TEST_FPATH)
-
-    # xvfb is only available on linux (which binder runs on)
-    if 'pytest-xvfb' in spyder_env:
-        spyder_env.pop('pytest-xvfb')
+    main_reqs = parse_environment_yaml(REQ_FPATH)
+    test_reqs = parse_environment_yaml(REQ_TEST_FPATH)
+    linux_reqs = parse_environment_yaml(REQ_LINUX_FPATH)
 
     # Check that the requirement files match the environment yaml file
     full_reqs = {}
+    full_reqs.update(main_reqs)
+    full_reqs.update(linux_reqs)
     full_reqs.update(test_reqs)
-    full_reqs.update(spyder_reqs)
 
     assert spyder_env == full_reqs
 
 
 def test_dependencies_for_spyder_dialog_in_sync():
     """
-    Spyder dependencies dialog should share deps with conda.txt.
+    Spyder dependencies dialog should share deps with main.yml.
     """
     spyder_deps = parse_spyder_dependencies()
-    spyder_reqs = parse_requirements(REQ_FPATH)
+    main_reqs = parse_environment_yaml(REQ_FPATH)
+    windows_reqs = parse_environment_yaml(REQ_WINDOWS_FPATH)
+    mac_reqs = parse_environment_yaml(REQ_MAC_FPATH)
+    linux_reqs = parse_environment_yaml(REQ_LINUX_FPATH)
 
-    if 'pyqt' in spyder_reqs:
-        spyder_reqs.pop('pyqt')
-    if 'pyqtwebengine' in spyder_reqs:
-        spyder_reqs.pop('pyqtwebengine')
+    full_reqs = {}
+    full_reqs.update(main_reqs)
+    full_reqs.update(windows_reqs)
+    full_reqs.update(mac_reqs)
+    full_reqs.update(linux_reqs)
+
+    # These packages are not declared in our dependencies dialog
+    for dep in ['pyqt', 'pyqtwebengine', 'python.app']:
+        full_reqs.pop(dep)
 
-    assert spyder_deps == spyder_reqs
+    assert spyder_deps == full_reqs
 
 
 def test_dependencies_for_spyder_setup_install_requires_in_sync():
     """
-    Spyder setup.py should share deps with conda.txt.
+    Spyder setup.py should share deps with main.yml.
     """
     spyder_setup = parse_setup_install_requires(SETUP_FPATH)
-    spyder_reqs = parse_requirements(REQ_FPATH)
+    main_reqs = parse_environment_yaml(REQ_FPATH)
+    windows_reqs = parse_environment_yaml(REQ_WINDOWS_FPATH)
+    mac_reqs = parse_environment_yaml(REQ_MAC_FPATH)
+    linux_reqs = parse_environment_yaml(REQ_LINUX_FPATH)
+
+    full_reqs = {}
+    full_reqs.update(main_reqs)
+    full_reqs.update(windows_reqs)
+    full_reqs.update(mac_reqs)
+    full_reqs.update(linux_reqs)
+
+    # We don't declare python.app as a dependency in other places
+    full_reqs.pop('python.app')
 
-    assert spyder_setup == spyder_reqs
+    assert spyder_setup == full_reqs
 
 
 def test_dependencies_for_spyder_setup_extras_requires_in_sync():
     """
-    Spyder setup.py extra_requires should share deps with tests.txt.
+    Spyder setup.py extra_requires should share deps with tests.yml.
     """
     spyder_extras_setup = parse_setup_extra_requires(SETUP_FPATH)
-    spyder_test_reqs = parse_requirements(REQ_TEST_FPATH)
+    spyder_test_reqs = parse_environment_yaml(REQ_TEST_FPATH)
 
     assert spyder_extras_setup == spyder_test_reqs