Repository: conda/conda-build

Issue:
Conda skeleton does not support environment markers
[PEP 508](https://www.python.org/dev/peps/pep-0508) added support for [environment markers](https://www.python.org/dev/peps/pep-0508/#environment-markers) in PyPI version specifications. Specifically, environment markers allow you to have versions like ([example on PyPI here](https://pypi.python.org/pypi/coconut))
```
pygments<2.3,>=2.2;python_version>='2.7'
prompt-toolkit<1.1,>=1.0;python_version>='2.7'
futures<3.1,>=3.0;python_version<'3'
argparse<1.5,>=1.4;python_version<'2.7'
```
where the package on the left is only a dependency if the current Python version meets the requirement on the right. This is a very useful feature for packages that aim to support multiple Python versions.

However, when `conda skeleton pypi` creates `meta.yaml`, it does not respect the environment markers, leaving any dependencies with environment markers _completely out of the dependency structure_. Thus, when one runs `conda build` on such a skeleton, it fails with (full error at https://github.com/evhub/coconut/issues/246)
```
RuntimeError: Setuptools downloading is disabled in conda build. Be sure to add all dependencies in the meta.yaml
```
since `setuptools` tries to respect the environment markers, but `conda` won't let it.

This is a major problem for packages that use environment markers in their dependencies. It'd be great if this could be fixed, but until then, do you have any ideas for a suitable workaround?



Discussion:
I think selectors are the nearest concept in conda-build: https://conda.io/docs/building/meta-yaml.html#preprocessing-selectors

Adding translation of environment markers to selectors to the skeleton generator is definitely desirable, but I don't think I'll be able to get to it any time soon.  PRs welcome - and I'd be happy to answer any questions if anyone wants to tackle it.

Resolving patch:
diff --git a/conda_build/skeletons/pypi.py b/conda_build/skeletons/pypi.py
--- a/conda_build/skeletons/pypi.py
+++ b/conda_build/skeletons/pypi.py
@@ -896,6 +896,33 @@ def get_requirements(package, pkginfo, all_extras=True):
             extras_require = [pkginfo['extras_require'][x] for x in extras]
         except KeyError:
             sys.exit("Error: Invalid extra features: [%s]" % ','.join(extras))
+        # match PEP 508 environment markers; currently only matches the
+        #  subset of environment markers that compare to python_version
+        #  using a single basic Python comparison operator
+        version_marker = re.compile(r'^:python_version(<|<=|!=|==|>=|>)(.+)$')
+        for extra in pkginfo['extras_require']:
+            match_ver_mark = version_marker.match(extra)
+            if match_ver_mark:
+                op, ver = match_ver_mark.groups()
+                try:
+                    ver_tuple = tuple(int(x) for x in ver.strip('\'"').split("."))
+                except ValueError:
+                    pass  # bad match; abort
+                else:
+                    if op == "<":
+                        satisfies_ver = sys.version_info < ver_tuple
+                    elif op == "<=":
+                        satisfies_ver = sys.version_info <= ver_tuple
+                    elif op == "!=":
+                        satisfies_ver = sys.version_info != ver_tuple
+                    elif op == "==":
+                        satisfies_ver = sys.version_info == ver_tuple
+                    elif op == ">=":
+                        satisfies_ver = sys.version_info >= ver_tuple
+                    else:  # op == ">":
+                        satisfies_ver = sys.version_info > ver_tuple
+                    if satisfies_ver:
+                        extras_require += pkginfo['extras_require'][extra]
 
     # ... and collect all needed requirement specs in a single list:
     requires = []


Test patch:
diff --git a/tests/test_api_skeleton.py b/tests/test_api_skeleton.py
--- a/tests/test_api_skeleton.py
+++ b/tests/test_api_skeleton.py
@@ -1,4 +1,5 @@
 import os
+import sys
 
 from pkg_resources import parse_version
 import pytest
@@ -120,3 +121,25 @@ def test_pypi_with_version_inconsistency(testing_workdir):
     with open('mpi4py_test/meta.yaml') as f:
         actual = yaml.load(f)
         assert parse_version(actual['package']['version']) == parse_version("0.0.10")
+
+
+def test_pypi_with_basic_environment_markers(testing_workdir):
+    # regression test for https://github.com/conda/conda-build/issues/1974
+    api.skeletonize('coconut', 'pypi', version='1.2.2')
+    with open('coconut/meta.yaml') as f:
+        actual = yaml.load(f)
+        build_reqs = str(actual['requirements']['build'])
+        run_reqs = str(actual['requirements']['run'])
+        # should include the right dependencies for the right version
+        if sys.version_info < (3,):
+            assert "futures" in build_reqs
+            assert "futures" in run_reqs
+        else:
+            assert "futures" not in build_reqs
+            assert "futures" not in run_reqs
+        if sys.version_info >= (2, 7):
+            assert "pygments" in build_reqs
+            assert "pygments" in run_reqs
+        else:
+            assert "pygments" not in build_reqs
+            assert "pygments" not in run_reqs