Repository: pex-tool/pex

Issue:
wheel `safer_name` for locating scripts
https://github.com/pantsbuild/pex/commit/fe895b2e24a1ed0bc9574cdd2cf83164983a9b61#diff-3ffb3bc5eb80cbb36374158a2dbbc433R251 uses `dist.key` but should use `pkg_resources.to_filename(dist.key)` or similar for listing directories within the wheel.



Discussion:
expected: using `-c` option finds scripts for package names with a `-` in them, this fails


Resolving patch:
diff --git a/pex/finders.py b/pex/finders.py
--- a/pex/finders.py
+++ b/pex/finders.py
@@ -245,15 +245,19 @@ def get_script_from_egg(name, dist):
   return None, None
 
 
+def safer_name(name):
+  return name.replace('-', '_')
+
+
 def get_script_from_whl(name, dist):
   # This is true as of at least wheel==0.24.  Might need to take into account the
   # metadata version bundled with the wheel.
-  wheel_scripts_dir = '%s-%s.data/scripts' % (dist.key, dist.version)
+  wheel_scripts_dir = '%s-%s.data/scripts' % (safer_name(dist.key), dist.version)
   if dist.resource_isdir(wheel_scripts_dir) and name in dist.resource_listdir(wheel_scripts_dir):
     script_path = os.path.join(wheel_scripts_dir, name)
     return (
         os.path.join(dist.egg_info, script_path),
-        dist.get_resource_string('', script_path).replace('\r\n', '\n').replace('\r', '\n'))
+        dist.get_resource_string('', script_path).replace(b'\r\n', b'\n').replace(b'\r', b'\n'))
   return None, None
 
 
diff --git a/pex/pex.py b/pex/pex.py
--- a/pex/pex.py
+++ b/pex/pex.py
@@ -312,15 +312,16 @@ def execute_script(self, script_name):
         script_name, self._env.activate())
     if not dist:
       raise self.NotFound('Could not find script %s in pex!' % script_name)
-    if not script_content.startswith('#!/usr/bin/env python'):
-      die('Cannot execute non-Python script within PEX environment!')
     TRACER.log('Found script %s in %s' % (script_name, dist))
     self.execute_content(script_path, script_content, argv0=script_name)
 
   @classmethod
   def execute_content(cls, name, content, argv0=None):
     argv0 = argv0 or name
-    ast = compile(content, name, 'exec', flags=0, dont_inherit=1)
+    try:
+      ast = compile(content, name, 'exec', flags=0, dont_inherit=1)
+    except SyntaxError:
+      die('Unable to parse %s.  PEX script support only supports Python scripts.')
     old_name, old_file = globals().get('__name__'), globals().get('__file__')
     try:
       old_argv0 = sys.argv[0]


Test patch:
diff --git a/pex/testing.py b/pex/testing.py
--- a/pex/testing.py
+++ b/pex/testing.py
@@ -72,12 +72,18 @@ def write_zipfile(directory, dest, reverse=False):
           version='0.0.0',
           zip_safe=%(zip_safe)r,
           packages=['my_package'],
+          scripts=[
+              'scripts/hello_world',
+              'scripts/shell_script',
+          ],
           package_data={'my_package': ['package_data/*.dat']},
       )
   '''),
   'MANIFEST.in': dedent('''
   include setup.py
   '''),
+  'scripts/hello_world': '#!/usr/bin/env python\nprint("hello world!")\n',
+  'scripts/shell_script': '#!/usr/bin/env bash\necho hello world\n',
   'my_package/__init__.py': 0,
   'my_package/my_module.py': 'def do_something():\n  print("hello world!")\n',
   'my_package/package_data/resource1.dat': 1000,
diff --git a/tests/test_pex.py b/tests/test_pex.py
--- a/tests/test_pex.py
+++ b/tests/test_pex.py
@@ -7,8 +7,10 @@
 
 import pytest
 
+from pex.installer import EggInstaller, WheelInstaller
 from pex.pex import PEX
-from pex.testing import run_simple_pex_test
+from pex.testing import make_installer, run_simple_pex_test
+from pex.util import DistributionHelper
 
 
 @pytest.mark.skipif('sys.version_info > (3,)')
@@ -94,3 +96,25 @@ def test_minimum_sys_modules():
   new_modules = PEX.minimum_sys_modules(['bad_path'], modules)
   assert new_modules == modules
   assert tainted_module.__path__ == ['good_path']
+
+
+@pytest.mark.parametrize('project_name', ('my_project', 'my-project'))
+@pytest.mark.parametrize('installer_impl', (EggInstaller, WheelInstaller))
+def test_pex_script(installer_impl, project_name):
+  with make_installer(name=project_name, installer_impl=installer_impl) as installer:
+    bdist = DistributionHelper.distribution_from_path(installer.bdist())
+
+    env_copy = os.environ.copy()
+    env_copy['PEX_SCRIPT'] = 'hello_world'
+    so, rc = run_simple_pex_test('', env=env_copy)
+    assert rc == 1, so.decode('utf-8')
+    assert b'Could not find' in so
+
+    so, rc = run_simple_pex_test('', env=env_copy, dists=[bdist])
+    assert rc == 0, so.decode('utf-8')
+    assert b'hello world' in so
+
+    env_copy['PEX_SCRIPT'] = 'shell_script'
+    so, rc = run_simple_pex_test('', env=env_copy, dists=[bdist])
+    assert rc == 1, so.decode('utf-8')
+    assert b'Unable to parse' in so