Repository: conda/conda-build

Issue:
conda skeleton leaves tilde chars from requires.txt
Running conda skeleton pypi fs gives

requirements:
  build:
    - python
    - setuptools
    - appdirs~ 1.4.3
    - pytz
    - setuptools
    - six~ 1.10.0
  run:
    - python
    - setuptools
    - appdirs~ 1.4.3
    - pytz
    - setuptools
    - six~ 1.10.0



Discussion:
Looks like a bug in how package version specs with tilde are being parsed. According to PEP 440 the tilde is for compatible releases: https://www.python.org/dev/peps/pep-0440/#compatible-release

@msarahan should we parse something like ``appdirs~=1.4.3`` as ``appdirs >=1.4.3, <1.5``? 
As I understand it, it would translate more to 

```appdirs >=1.4.3, <2```

which is coincidentally the default ``pin_compatible`` behavior.  I think ideally the build dep should be ``>=(value from ~)`` and the run dep should be ``{{ pin_compatible('depname') }}``
also, note that setuptools dependency is listed twice in both build and run sections
also, test dependencies in the generated meta.yaml that are also run dependencies could be omitted.
You're getting into a lot of nice-to-haves.  Pull requests are very welcome.

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
@@ -614,6 +614,20 @@ def version_compare(package, versions):
         sys.exit()
 
 
+def convert_version(version):
+    """Convert version into a pin-compatible format according to PEP440."""
+    version_parts = version.split('.')
+    suffixes = ('post', 'pre')
+    if any(suffix in version_parts[-1] for suffix in suffixes):
+        version_parts.pop()
+    # the max pin length is n-1, but in terms of index this is n-2
+    max_ver_len = len(version_parts) - 2
+    version_parts[max_ver_len] = int(version_parts[max_ver_len]) + 1
+    max_pin = '.'.join(str(v) for v in version_parts[:max_ver_len + 1])
+    pin_compatible = ' >={},<{}' .format(version, max_pin)
+    return pin_compatible
+
+
 def get_package_metadata(package, d, data, output_dir, python_version, all_extras,
                          recursive, created_recipes, noarch_python, noprompt, packages,
                          extra_specs, config, setup_options):
@@ -698,6 +712,11 @@ def get_package_metadata(package, d, data, output_dir, python_version, all_extra
                 dep = dep.split('#')[0].strip()
                 if dep:  # ... and empty (or comment only) lines
                     spec = spec_from_line(dep)
+                    if '~' in spec:
+                        version = spec.split()[-1]
+                        tilde_version = '~ {}' .format(version)
+                        pin_compatible = convert_version(version)
+                        spec = spec.replace(tilde_version, pin_compatible)
                     if spec is None:
                         sys.exit("Error: Could not parse: %s" % dep)
                     deps.append(spec)


Test patch:
diff --git a/tests/test_pypi_skeleton.py b/tests/test_pypi_skeleton.py
new file mode 100644
--- /dev/null
+++ b/tests/test_pypi_skeleton.py
@@ -0,0 +1,23 @@
+from conda_build.skeletons import pypi
+
+
+def test_version_compare():
+    short_version = '2.2'
+    long_version = '1.4.5'
+    post_version = '2.2.post3'
+    pre_version = '2.2.pre3'
+    alpha_version = '1.4.5a4'
+    beta_version = '1.4.5b4'
+    rc_version = '1.4.5rc4'
+    padding_version_short = '2.2.0'
+    padding_version_long = '1.4.5.0'
+
+    assert pypi.convert_version(short_version) == ' >=2.2,<3'
+    assert pypi.convert_version(long_version) == ' >=1.4.5,<1.5'
+    assert pypi.convert_version(post_version) == ' >=2.2.post3,<3'
+    assert pypi.convert_version(pre_version) == ' >=2.2.pre3,<3'
+    assert pypi.convert_version(alpha_version) == ' >=1.4.5a4,<1.5'
+    assert pypi.convert_version(beta_version) == ' >=1.4.5b4,<1.5'
+    assert pypi.convert_version(rc_version) == ' >=1.4.5rc4,<1.5'
+    assert pypi.convert_version(padding_version_short) == ' >=2.2.0,<2.3'
+    assert pypi.convert_version(padding_version_long) == ' >=1.4.5.0,<1.4.6'