I don't think this is working on macOS. The configuration step is generating error messages like,

Ignored: [Errno 2] No such file or directory: <command path>

In lines 1640-1643,

self.write_dispatcher(
    source_file=libtbx.env.under_base(os.path.join('bin', ep.name)),
    target_file=os.path.join('bin', 'libtbx.' + ep.name),
)

the source_file argument is always being constructed as <base>/bin/<command> instead of using the directory from self.get_setuptools_script_dir(). Should it be the bin_directory variable instead (e.g. source_file=os.path.join(bin_directory, ep.name))?

As a side note, conda will put everything in bin because there is no framework. For access to the window manager, you would call pythonw, which points to a python inside a .app bundle.


--
Billy K. Poon
Research Scientist, Molecular Biophysics and Integrated Bioimaging
Lawrence Berkeley National Laboratory
1 Cyclotron Road, M/S 33R0345
Berkeley, CA 94720
Tel: (510) 486-5709
Fax: (510) 486-5909


On Fri, Jun 1, 2018 at 3:15 AM CCTBX commit <diamondlightsource.jenkins@gmail.com> wrote:
Repository : ssh://g18-sc-serv-04.diamond.ac.uk/cctbx
On branch  : master




commit ad736e2c8e65407615a7e6604b0544687ed8058c
Author: Markus Gerstel <markus.gerstel@diamond.ac.uk>
Date:   Thu May 31 08:27:09 2018 +0100

    Generate libtbx.* wrappers from correct directory
    
    Python console scripts don't necessarily land in base/bin, specifically
    on MacOS this is not the case. Use setuptools etc. to find the actual
    Python console script location and use that directory instead. Fixes #177
    
    Add a function to regenerate all Python console scripts which can be
    called from an installer to relocate paths.





ad736e2c8e65407615a7e6604b0544687ed8058c
libtbx/env_config.py      |  74 +++++++++++++++++++++++++-----
libtbx/fastentrypoints.py | 112 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 176 insertions(+), 10 deletions(-)

diff --git a/libtbx/env_config.py b/libtbx/env_config.py
index de5fc72dd..25daa2cb3 100644
--- a/libtbx/env_config.py
+++ b/libtbx/env_config.py
@@ -1562,23 +1562,77 @@ selfx:
           source_file=source_file,
           target_file=module_name+"."+command)

+  def get_setuptools_script_dir():
+    '''
+    Find the location of python entry point console_scripts, ie. things like
+    'pip', 'pytest', ...
+    This is different from simple /base/bin, eg. on MacOS.
+
+    https://stackoverflow.com/questions/25066084/get-entry-point-script-file-location-in-setuputils-package
+    '''
+    from setuptools import Distribution
+    from setuptools.command.install import install
+    class OnlyGetScriptPath(install):
+      def run(self):
+        # does not call install.run() by design
+        self.distribution.install_scripts = self.install_scripts
+    dist = Distribution({'cmdclass': {'install': OnlyGetScriptPath}})
+    dist.dry_run = True  # not sure if necessary, but to be safe
+    dist.parse_config_files()
+    command = dist.get_command_obj('install')
+    command.ensure_finalized()
+    command.run()
+    return dist.install_scripts
+
+  def regenerate_entry_point_console_scripts(self, verbose=True):
+    '''
+    Creates all console_scripts entry point scripts from scratch and overwrites existing ones.
+    This is intended to be used by installers to relocate the entry point script paths.
+    '''
+    try:
+      import distutils.dist
+      import libtbx.fastentrypoints # monkeypatches setuptools
+      import pkg_resources
+      import setuptools.command.easy_install
+    except ImportError:
+      return
+
+    # Prepare generic script generator
+    distribution = distutils.dist.Distribution({'name': 'setuptools'})
+    command = setuptools.command.easy_install.easy_install(distribution)
+    command.args = ['wheel']  # dummy argument
+    command.finalize_options()
+
+    # Force regeneration of all known console_scripts
+    for pkg_resources_dist in pkg_resources.working_set:
+      console_scripts = pkg_resources_dist.get_entry_map().get('console_scripts')
+      if console_scripts:
+        if verbose:
+          print("Regenerating commands for %s: %s" % (
+              pkg_resources_dist,
+              list(console_scripts),
+          ))
+        command.install_wrapper_scripts(pkg_resources_dist)
+
   def generate_entry_point_dispatchers(self):
-    # Write indirect dispatcher scripts for all console_scripts entry points
-    # that have existing dispatcher scripts in the base/bin directory, but
-    # add a 'libtbx.' prefix.
-    base_bin_directory = libtbx.env.under_base('bin')
-    if not os.path.isdir(base_bin_directory):
+    '''
+    Write indirect dispatcher scripts for all console_scripts entry points
+    that have existing dispatcher scripts in the base/bin directory, but
+    add a 'libtbx.' prefix.
+    '''
+    try:
+      import pkg_resources
+      bin_directory = get_setuptools_script_dir()
+    except ImportError:
+      return
+    if not os.path.isdir(bin_directory):
       return # do not create console_scripts dispatchers, only point to them

-    base_bin_dispatchers = set(os.listdir(base_bin_directory))
+    base_bin_dispatchers = set(os.listdir(bin_directory))
     existing_dispatchers = filter(lambda f: f.startswith('libtbx.'), self.bin_path.listdir())
     existing_dispatchers = set(map(lambda f: f[7:], existing_dispatchers))
     entry_point_candidates = base_bin_dispatchers - existing_dispatchers

-    try:
-      import pkg_resources
-    except ImportError:
-      return
     entry_points = pkg_resources.iter_entry_points('console_scripts')
     entry_points = filter(lambda ep: ep.name in entry_point_candidates, entry_points)
     for ep in entry_points:
diff --git a/libtbx/fastentrypoints.py b/libtbx/fastentrypoints.py
new file mode 100644
index 000000000..9707f74a3
--- /dev/null
+++ b/libtbx/fastentrypoints.py
@@ -0,0 +1,112 @@
+# noqa: D300,D400
+# Copyright (c) 2016, Aaron Christianson
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+'''
+Monkey patch setuptools to write faster console_scripts with this format:
+
+    import sys
+    from mymodule import entry_function
+    sys.exit(entry_function())
+
+This is better.
+
+(c) 2016, Aaron Christianson
+http://github.com/ninjaaron/fast-entry_points
+'''
+from setuptools.command import easy_install
+import re
+TEMPLATE = '''\
+# -*- coding: utf-8 -*-
+# EASY-INSTALL-ENTRY-SCRIPT: '{3}','{4}','{5}'
+__requires__ = '{3}'
+import re
+import sys
+
+from {0} import {1}
+
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
+    sys.exit({2}())'''
+
+
+@classmethod
+def get_args(cls, dist, header=None):  # noqa: D205,D400
+    """
+    Yield write_script() argument tuples for a distribution's
+    console_scripts and gui_scripts entry points.
+    """
+    if header is None:
+        # pylint: disable=E1101
+        header = cls.get_header()
+    spec = str(dist.as_requirement())
+    for type_ in 'console', 'gui':
+        group = type_ + '_scripts'
+        for name, ep in dist.get_entry_map(group).items():
+            # ensure_safe_name
+            if re.search(r'[\\/]', name):
+                raise ValueError("Path separators not allowed in script names")
+            script_text = TEMPLATE.format(
+                ep.module_name, ep.attrs[0], '.'.join(ep.attrs),
+                spec, group, name)
+            # pylint: disable=E1101
+            args = cls._get_script_args(type_, name, header, script_text)
+            for res in args:
+                yield res
+
+
+# pylint: disable=E1101
+easy_install.ScriptWriter.get_args = get_args
+
+
+def main():
+    import os
+    import re
+    import shutil
+    import sys
+    dests = sys.argv[1:] or ['.']
+    filename = re.sub('\.pyc$', '.py', __file__)
+
+    for dst in dests:
+        shutil.copy(filename, dst)
+        manifest_path = os.path.join(dst, 'MANIFEST.in')
+        setup_path = os.path.join(dst, 'setup.py')
+
+        # Insert the include statement to MANIFEST.in if not present
+        with open(manifest_path, 'a+') as manifest:
+            manifest.seek(0)
+            manifest_content = manifest.read()
+            if 'include fastentrypoints.py' not in manifest_content:
+                manifest.write(('\n' if manifest_content else '') +
+                               'include fastentrypoints.py')
+
+        # Insert the import statement to setup.py if not present
+        with open(setup_path, 'a+') as setup:
+            setup.seek(0)
+            setup_content = setup.read()
+            if 'import fastentrypoints' not in setup_content:
+                setup.seek(0)
+                setup.truncate()
+                setup.write('import fastentrypoints\n' + setup_content)


To unsubscribe from the CCTBX-COMMIT list, click the following link:
https://www.jiscmail.ac.uk/cgi-bin/webadmin?SUBED1=CCTBX-COMMIT&A=1