<div dir="ltr">I don&#39;t think this is working on macOS. The configuration step is generating error messages like,<div><br></div><div><font face="monospace, monospace">Ignored: [Errno 2] No such file or directory: &lt;command path&gt;</font></div><div><br></div><div>In lines 1640-1643,</div><div><br></div><div><div><font face="monospace, monospace">self.write_dispatcher(</font></div><div><font face="monospace, monospace">    source_file=libtbx.env.under_base(os.path.join(&#39;bin&#39;, <a href="http://ep.name" target="_blank">ep.name</a>)),</font></div><div><font face="monospace, monospace">    target_file=os.path.join(&#39;bin&#39;, &#39;libtbx.&#39; + <a href="http://ep.name" target="_blank">ep.name</a>),</font></div><div><font face="monospace, monospace">)</font></div></div><div><br></div><div>the source_file argument is always being constructed as &lt;base&gt;/bin/&lt;command&gt; instead of using the directory from self.get_setuptools_script_dir(). Should it be the bin_directory variable instead (e.g. <font face="monospace, monospace">source_file=os.path.join(bin_directory, <a href="http://ep.name">ep.name</a>)</font>)?</div><div><br></div><div>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.<br><div><br></div><div><br clear="all"><div><div dir="ltr" class="m_-494864551476270440gmail_signature"><div dir="ltr"><div><div dir="ltr"><div><div dir="ltr"><div><div dir="ltr"><div><div dir="ltr"><div dir="ltr"><div dir="ltr"><div><div>--</div><div><span style="font-size:12.8000001907349px">Billy K. Poon</span><br></div></div><div>Research Scientist, Molecular Biophysics and Integrated Bioimaging</div><div>Lawrence Berkeley National Laboratory</div><div>1 Cyclotron Road, M/S 33R0345</div><div>Berkeley, CA 94720</div><div>Tel: (510) 486-5709</div><div>Fax: (510) 486-5909</div><div>Web: <a href="https://phenix-online.org" target="_blank">https://phenix-online.org</a></div></div></div></div></div></div></div></div></div></div></div></div></div></div><br></div></div></div><br><div class="gmail_quote"><div dir="ltr">On Fri, Jun 1, 2018 at 3:15 AM CCTBX commit &lt;<a href="mailto:diamondlightsource.jenkins@gmail.com" target="_blank">diamondlightsource.jenkins@gmail.com</a>&gt; wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div><tt>Repository : ssh://<a href="http://g18-sc-serv-04.diamond.ac.uk/cctbx" target="_blank">g18-sc-serv-04.diamond.ac.uk/cctbx</a><br>On branch  : master<br><br><hr><br><br>commit ad736e2c8e65407615a7e6604b0544687ed8058c<br>Author: Markus Gerstel &lt;<a href="mailto:markus.gerstel@diamond.ac.uk" target="_blank">markus.gerstel@diamond.ac.uk</a>&gt;<br>Date:   Thu May 31 08:27:09 2018 +0100<br><br>    Generate libtbx.* wrappers from correct directory<br>    <br>    Python console scripts don&#39;t necessarily land in base/bin, specifically<br>    on MacOS this is not the case. Use setuptools etc. to find the actual<br>    Python console script location and use that directory instead. Fixes #177<br>    <br>    Add a function to regenerate all Python console scripts which can be<br>    called from an installer to relocate paths.<br><br><br><hr><br><br>ad736e2c8e65407615a7e6604b0544687ed8058c<br> libtbx/env_config.py      |  74 +++++++++++++++++++++++++-----<br> libtbx/fastentrypoints.py | 112 ++++++++++++++++++++++++++++++++++++++++++++++<br> 2 files changed, 176 insertions(+), 10 deletions(-)<br><br>diff --git a/libtbx/env_config.py b/libtbx/env_config.py<br>index de5fc72dd..25daa2cb3 100644<br><tt style="color:#800">--- a/libtbx/env_config.py</tt><br><tt style="color:#008">+++ b/libtbx/env_config.py</tt><br>@@ -1562,23 +1562,77 @@ selfx:<br>           source_file=source_file,<br>           target_file=module_name+&quot;.&quot;+command)<br> <br><tt style="color:#008">+  def get_setuptools_script_dir():</tt><br><tt style="color:#008">+    &#39;&#39;&#39;</tt><br><tt style="color:#008">+    Find the location of python entry point console_scripts, ie. things like</tt><br><tt style="color:#008">+    &#39;pip&#39;, &#39;pytest&#39;, ...</tt><br><tt style="color:#008">+    This is different from simple /base/bin, eg. on MacOS.</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+    <a href="https://stackoverflow.com/questions/25066084/get-entry-point-script-file-location-in-setuputils-package" target="_blank">https://stackoverflow.com/questions/25066084/get-entry-point-script-file-location-in-setuputils-package</a></tt><br><tt style="color:#008">+    &#39;&#39;&#39;</tt><br><tt style="color:#008">+    from setuptools import Distribution</tt><br><tt style="color:#008">+    from setuptools.command.install import install</tt><br><tt style="color:#008">+    class OnlyGetScriptPath(install):</tt><br><tt style="color:#008">+      def run(self):</tt><br><tt style="color:#008">+        # does not call install.run() by design</tt><br><tt style="color:#008">+        self.distribution.install_scripts = self.install_scripts</tt><br><tt style="color:#008">+    dist = Distribution({&#39;cmdclass&#39;: {&#39;install&#39;: OnlyGetScriptPath}})</tt><br><tt style="color:#008">+    dist.dry_run = True  # not sure if necessary, but to be safe</tt><br><tt style="color:#008">+    dist.parse_config_files()</tt><br><tt style="color:#008">+    command = dist.get_command_obj(&#39;install&#39;)</tt><br><tt style="color:#008">+    command.ensure_finalized()</tt><br><tt style="color:#008">+    command.run()</tt><br><tt style="color:#008">+    return dist.install_scripts</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+  def regenerate_entry_point_console_scripts(self, verbose=True):</tt><br><tt style="color:#008">+    &#39;&#39;&#39;</tt><br><tt style="color:#008">+    Creates all console_scripts entry point scripts from scratch and overwrites existing ones.</tt><br><tt style="color:#008">+    This is intended to be used by installers to relocate the entry point script paths.</tt><br><tt style="color:#008">+    &#39;&#39;&#39;</tt><br><tt style="color:#008">+    try:</tt><br><tt style="color:#008">+      import distutils.dist</tt><br><tt style="color:#008">+      import libtbx.fastentrypoints # monkeypatches setuptools</tt><br><tt style="color:#008">+      import pkg_resources</tt><br><tt style="color:#008">+      import setuptools.command.easy_install</tt><br><tt style="color:#008">+    except ImportError:</tt><br><tt style="color:#008">+      return</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+    # Prepare generic script generator</tt><br><tt style="color:#008">+    distribution = distutils.dist.Distribution({&#39;name&#39;: &#39;setuptools&#39;})</tt><br><tt style="color:#008">+    command = setuptools.command.easy_install.easy_install(distribution)</tt><br><tt style="color:#008">+    command.args = [&#39;wheel&#39;]  # dummy argument</tt><br><tt style="color:#008">+    command.finalize_options()</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+    # Force regeneration of all known console_scripts</tt><br><tt style="color:#008">+    for pkg_resources_dist in pkg_resources.working_set:</tt><br><tt style="color:#008">+      console_scripts = pkg_resources_dist.get_entry_map().get(&#39;console_scripts&#39;)</tt><br><tt style="color:#008">+      if console_scripts:</tt><br><tt style="color:#008">+        if verbose:</tt><br><tt style="color:#008">+          print(&quot;Regenerating commands for %s: %s&quot; % (</tt><br><tt style="color:#008">+              pkg_resources_dist,</tt><br><tt style="color:#008">+              list(console_scripts),</tt><br><tt style="color:#008">+          ))</tt><br><tt style="color:#008">+        command.install_wrapper_scripts(pkg_resources_dist)</tt><br><tt style="color:#008">+</tt><br>   def generate_entry_point_dispatchers(self):<br><tt style="color:#800">-    # Write indirect dispatcher scripts for all console_scripts entry points</tt><br><tt style="color:#800">-    # that have existing dispatcher scripts in the base/bin directory, but</tt><br><tt style="color:#800">-    # add a &#39;libtbx.&#39; prefix.</tt><br><tt style="color:#800">-    base_bin_directory = libtbx.env.under_base(&#39;bin&#39;)</tt><br><tt style="color:#800">-    if not os.path.isdir(base_bin_directory):</tt><br><tt style="color:#008">+    &#39;&#39;&#39;</tt><br><tt style="color:#008">+    Write indirect dispatcher scripts for all console_scripts entry points</tt><br><tt style="color:#008">+    that have existing dispatcher scripts in the base/bin directory, but</tt><br><tt style="color:#008">+    add a &#39;libtbx.&#39; prefix.</tt><br><tt style="color:#008">+    &#39;&#39;&#39;</tt><br><tt style="color:#008">+    try:</tt><br><tt style="color:#008">+      import pkg_resources</tt><br><tt style="color:#008">+      bin_directory = get_setuptools_script_dir()</tt><br><tt style="color:#008">+    except ImportError:</tt><br><tt style="color:#008">+      return</tt><br><tt style="color:#008">+    if not os.path.isdir(bin_directory):</tt><br>       return # do not create console_scripts dispatchers, only point to them<br> <br><tt style="color:#800">-    base_bin_dispatchers = set(os.listdir(base_bin_directory))</tt><br><tt style="color:#008">+    base_bin_dispatchers = set(os.listdir(bin_directory))</tt><br>     existing_dispatchers = filter(lambda f: f.startswith(&#39;libtbx.&#39;), self.bin_path.listdir())<br>     existing_dispatchers = set(map(lambda f: f[7:], existing_dispatchers))<br>     entry_point_candidates = base_bin_dispatchers - existing_dispatchers<br> <br><tt style="color:#800">-    try:</tt><br><tt style="color:#800">-      import pkg_resources</tt><br><tt style="color:#800">-    except ImportError:</tt><br><tt style="color:#800">-      return</tt><br>     entry_points = pkg_resources.iter_entry_points(&#39;console_scripts&#39;)<br>     entry_points = filter(lambda ep: <a href="http://ep.name" target="_blank">ep.name</a> in entry_point_candidates, entry_points)<br>     for ep in entry_points:<br>diff --git a/libtbx/fastentrypoints.py b/libtbx/fastentrypoints.py<br>new file mode 100644<br>index 000000000..9707f74a3<br><tt style="color:#800">--- /dev/null</tt><br><tt style="color:#008">+++ b/libtbx/fastentrypoints.py</tt><br>@@ -0,0 +1,112 @@<br><tt style="color:#008">+# noqa: D300,D400</tt><br><tt style="color:#008">+# Copyright (c) 2016, Aaron Christianson</tt><br><tt style="color:#008">+# All rights reserved.</tt><br><tt style="color:#008">+#</tt><br><tt style="color:#008">+# Redistribution and use in source and binary forms, with or without</tt><br><tt style="color:#008">+# modification, are permitted provided that the following conditions are</tt><br><tt style="color:#008">+# met:</tt><br><tt style="color:#008">+#</tt><br><tt style="color:#008">+# 1. Redistributions of source code must retain the above copyright</tt><br><tt style="color:#008">+#    notice, this list of conditions and the following disclaimer.</tt><br><tt style="color:#008">+#</tt><br><tt style="color:#008">+# 2. Redistributions in binary form must reproduce the above copyright</tt><br><tt style="color:#008">+#    notice, this list of conditions and the following disclaimer in the</tt><br><tt style="color:#008">+#    documentation and/or other materials provided with the distribution.</tt><br><tt style="color:#008">+#</tt><br><tt style="color:#008">+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS</tt><br><tt style="color:#008">+# IS&quot; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED</tt><br><tt style="color:#008">+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A</tt><br><tt style="color:#008">+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT</tt><br><tt style="color:#008">+# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,</tt><br><tt style="color:#008">+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED</tt><br><tt style="color:#008">+# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR</tt><br><tt style="color:#008">+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF</tt><br><tt style="color:#008">+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING</tt><br><tt style="color:#008">+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS</tt><br><tt style="color:#008">+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</tt><br><tt style="color:#008">+&#39;&#39;&#39;</tt><br><tt style="color:#008">+Monkey patch setuptools to write faster console_scripts with this format:</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+    import sys</tt><br><tt style="color:#008">+    from mymodule import entry_function</tt><br><tt style="color:#008">+    sys.exit(entry_function())</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+This is better.</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+(c) 2016, Aaron Christianson</tt><br><tt style="color:#008">+<a href="http://github.com/ninjaaron/fast-entry_points" target="_blank">http://github.com/ninjaaron/fast-entry_points</a></tt><br><tt style="color:#008">+&#39;&#39;&#39;</tt><br><tt style="color:#008">+from setuptools.command import easy_install</tt><br><tt style="color:#008">+import re</tt><br><tt style="color:#008">+TEMPLATE = &#39;&#39;&#39;\</tt><br><tt style="color:#008">+# -*- coding: utf-8 -*-</tt><br><tt style="color:#008">+# EASY-INSTALL-ENTRY-SCRIPT: &#39;{3}&#39;,&#39;{4}&#39;,&#39;{5}&#39;</tt><br><tt style="color:#008">+__requires__ = &#39;{3}&#39;</tt><br><tt style="color:#008">+import re</tt><br><tt style="color:#008">+import sys</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+from {0} import {1}</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+if __name__ == &#39;__main__&#39;:</tt><br><tt style="color:#008">+    sys.argv[0] = re.sub(r&#39;(-script\.pyw?|\.exe)?$&#39;, &#39;&#39;, sys.argv[0])</tt><br><tt style="color:#008">+    sys.exit({2}())&#39;&#39;&#39;</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+@classmethod</tt><br><tt style="color:#008">+def get_args(cls, dist, header=None):  # noqa: D205,D400</tt><br><tt style="color:#008">+    &quot;&quot;&quot;</tt><br><tt style="color:#008">+    Yield write_script() argument tuples for a distribution&#39;s</tt><br><tt style="color:#008">+    console_scripts and gui_scripts entry points.</tt><br><tt style="color:#008">+    &quot;&quot;&quot;</tt><br><tt style="color:#008">+    if header is None:</tt><br><tt style="color:#008">+        # pylint: disable=E1101</tt><br><tt style="color:#008">+        header = cls.get_header()</tt><br><tt style="color:#008">+    spec = str(dist.as_requirement())</tt><br><tt style="color:#008">+    for type_ in &#39;console&#39;, &#39;gui&#39;:</tt><br><tt style="color:#008">+        group = type_ + &#39;_scripts&#39;</tt><br><tt style="color:#008">+        for name, ep in dist.get_entry_map(group).items():</tt><br><tt style="color:#008">+            # ensure_safe_name</tt><br><tt style="color:#008">+            if re.search(r&#39;[\\/]&#39;, name):</tt><br><tt style="color:#008">+                raise ValueError(&quot;Path separators not allowed in script names&quot;)</tt><br><tt style="color:#008">+            script_text = TEMPLATE.format(</tt><br><tt style="color:#008">+                ep.module_name, ep.attrs[0], &#39;.&#39;.join(ep.attrs),</tt><br><tt style="color:#008">+                spec, group, name)</tt><br><tt style="color:#008">+            # pylint: disable=E1101</tt><br><tt style="color:#008">+            args = cls._get_script_args(type_, name, header, script_text)</tt><br><tt style="color:#008">+            for res in args:</tt><br><tt style="color:#008">+                yield res</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+# pylint: disable=E1101</tt><br><tt style="color:#008">+easy_install.ScriptWriter.get_args = get_args</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+def main():</tt><br><tt style="color:#008">+    import os</tt><br><tt style="color:#008">+    import re</tt><br><tt style="color:#008">+    import shutil</tt><br><tt style="color:#008">+    import sys</tt><br><tt style="color:#008">+    dests = sys.argv[1:] or [&#39;.&#39;]</tt><br><tt style="color:#008">+    filename = re.sub(&#39;\.pyc$&#39;, &#39;.py&#39;, __file__)</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+    for dst in dests:</tt><br><tt style="color:#008">+        shutil.copy(filename, dst)</tt><br><tt style="color:#008">+        manifest_path = os.path.join(dst, &#39;MANIFEST.in&#39;)</tt><br><tt style="color:#008">+        setup_path = os.path.join(dst, &#39;setup.py&#39;)</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+        # Insert the include statement to MANIFEST.in if not present</tt><br><tt style="color:#008">+        with open(manifest_path, &#39;a+&#39;) as manifest:</tt><br><tt style="color:#008">+            manifest.seek(0)</tt><br><tt style="color:#008">+            manifest_content = manifest.read()</tt><br><tt style="color:#008">+            if &#39;include fastentrypoints.py&#39; not in manifest_content:</tt><br><tt style="color:#008">+                manifest.write((&#39;\n&#39; if manifest_content else &#39;&#39;) +</tt><br><tt style="color:#008">+                               &#39;include fastentrypoints.py&#39;)</tt><br><tt style="color:#008">+</tt><br><tt style="color:#008">+        # Insert the import statement to setup.py if not present</tt><br><tt style="color:#008">+        with open(setup_path, &#39;a+&#39;) as setup:</tt><br><tt style="color:#008">+            setup.seek(0)</tt><br><tt style="color:#008">+            setup_content = setup.read()</tt><br><tt style="color:#008">+            if &#39;import fastentrypoints&#39; not in setup_content:</tt><br><tt style="color:#008">+                setup.seek(0)</tt><br><tt style="color:#008">+                setup.truncate()</tt><br><tt style="color:#008">+                setup.write(&#39;import fastentrypoints\n&#39; + setup_content)</tt><br></tt></div>
<br>
<hr>
<p align="center">To unsubscribe from the CCTBX-COMMIT list, click the following link:<br>
<a href="https://www.jiscmail.ac.uk/cgi-bin/webadmin?SUBED1=CCTBX-COMMIT&amp;A=1" target="_blank">https://www.jiscmail.ac.uk/cgi-bin/webadmin?SUBED1=CCTBX-COMMIT&amp;A=1</a>
</p></blockquote></div>