Re: [cctbxbb] [git/cctbx] master: Generate libtbx.* wrappers from correct directory (ad736e2c8)
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 Web: https://phenix-online.org On Fri, Jun 1, 2018 at 3:15 AM CCTBX commit < [email protected]> wrote:
Repository : ssh://g18-sc-serv-04.diamond.ac.uk/cctbx On branch : master
------------------------------
commit ad736e2c8e65407615a7e6604b0544687ed8058c Author: Markus Gerstel
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-loc... + ''' + 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
participants (1)
-
Billy Poon