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