Include wsgi_scripts in generated wheels
Downstream consumers, such as OpenStack Ansible, generate wheels for all packages (including services). More and more services are moving to use the wsgi_scripts entry-points provided and handled by pbr. Unfortunately, these scripts are not generated during wheel creation unless we force them to be generated because Setuptools and Distutils will only generate console_scripts entry-points. This also fixes the C extension on Python 3 because it was previously broken. Change-Id: Icecc8474028436e8b2fb752d576204d9439fb0e7 Closes-bug: #1542383
This commit is contained in:
parent
64699d79be
commit
139110c89b
|
@ -308,22 +308,39 @@ ENTRY_POINTS_MAP = {
|
|||
}
|
||||
|
||||
|
||||
def generate_script(group, entry_point, header, template):
|
||||
"""Generate the script based on the template.
|
||||
|
||||
:param str group:
|
||||
The entry-point group name, e.g., "console_scripts".
|
||||
:param str header:
|
||||
The first line of the script, e.g., "!#/usr/bin/env python".
|
||||
:param str template:
|
||||
The script template.
|
||||
:returns:
|
||||
The templated script content
|
||||
:rtype:
|
||||
str
|
||||
"""
|
||||
if not entry_point.attrs or len(entry_point.attrs) > 2:
|
||||
raise ValueError("Script targets must be of the form "
|
||||
"'func' or 'Class.class_method'.")
|
||||
script_text = template % dict(
|
||||
group=group,
|
||||
module_name=entry_point.module_name,
|
||||
import_target=entry_point.attrs[0],
|
||||
invoke_target='.'.join(entry_point.attrs),
|
||||
)
|
||||
return header + script_text
|
||||
|
||||
|
||||
def override_get_script_args(
|
||||
dist, executable=os.path.normpath(sys.executable), is_wininst=False):
|
||||
"""Override entrypoints console_script."""
|
||||
header = easy_install.get_script_header("", executable, is_wininst)
|
||||
for group, template in ENTRY_POINTS_MAP.items():
|
||||
for name, ep in dist.get_entry_map(group).items():
|
||||
if not ep.attrs or len(ep.attrs) > 2:
|
||||
raise ValueError("Script targets must be of the form "
|
||||
"'func' or 'Class.class_method'.")
|
||||
script_text = template % dict(
|
||||
group=group,
|
||||
module_name=ep.module_name,
|
||||
import_target=ep.attrs[0],
|
||||
invoke_target='.'.join(ep.attrs),
|
||||
)
|
||||
yield (name, header + script_text)
|
||||
yield (name, generate_script(group, ep, header, template))
|
||||
|
||||
|
||||
class LocalDevelop(develop.develop):
|
||||
|
@ -342,6 +359,14 @@ class LocalInstallScripts(install_scripts.install_scripts):
|
|||
"""Intercepts console scripts entry_points."""
|
||||
command_name = 'install_scripts'
|
||||
|
||||
def _make_wsgi_scripts_only(self, dist, executable, is_wininst):
|
||||
header = easy_install.get_script_header("", executable, is_wininst)
|
||||
wsgi_script_template = ENTRY_POINTS_MAP['wsgi_scripts']
|
||||
for name, ep in dist.get_entry_map('wsgi_scripts').items():
|
||||
content = generate_script(
|
||||
'wsgi_scripts', ep, header, wsgi_script_template)
|
||||
self.write_script(name, content)
|
||||
|
||||
def run(self):
|
||||
import distutils.command.install_scripts
|
||||
|
||||
|
@ -351,9 +376,6 @@ class LocalInstallScripts(install_scripts.install_scripts):
|
|||
distutils.command.install_scripts.install_scripts.run(self)
|
||||
else:
|
||||
self.outfiles = []
|
||||
if self.no_ep:
|
||||
# don't install entry point scripts into .egg file!
|
||||
return
|
||||
|
||||
ei_cmd = self.get_finalized_command("egg_info")
|
||||
dist = pkg_resources.Distribution(
|
||||
|
@ -368,6 +390,19 @@ class LocalInstallScripts(install_scripts.install_scripts):
|
|||
self.get_finalized_command("bdist_wininst"), '_is_running', False
|
||||
)
|
||||
|
||||
if 'bdist_wheel' in self.distribution.have_run:
|
||||
# We're building a wheel which has no way of generating mod_wsgi
|
||||
# scripts for us. Let's build them.
|
||||
# NOTE(sigmavirus24): This needs to happen here because, as the
|
||||
# comment below indicates, no_ep is True when building a wheel.
|
||||
self._make_wsgi_scripts_only(dist, executable, is_wininst)
|
||||
|
||||
if self.no_ep:
|
||||
# no_ep is True if we're installing into an .egg file or building
|
||||
# a .whl file, in those cases, we do not want to build all of the
|
||||
# entry-points listed for this package.
|
||||
return
|
||||
|
||||
if os.name != 'nt':
|
||||
get_script_args = override_get_script_args
|
||||
else:
|
||||
|
|
|
@ -38,8 +38,10 @@
|
|||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
|
||||
import imp
|
||||
import os
|
||||
import re
|
||||
import sysconfig
|
||||
import tempfile
|
||||
import textwrap
|
||||
|
||||
|
@ -47,8 +49,10 @@ import fixtures
|
|||
import mock
|
||||
import pkg_resources
|
||||
import six
|
||||
import testtools
|
||||
from testtools import matchers
|
||||
import virtualenv
|
||||
import wheel.install
|
||||
|
||||
from pbr import git
|
||||
from pbr import packaging
|
||||
|
@ -318,6 +322,94 @@ class TestPackagingInGitRepoWithoutCommit(base.BaseTestCase):
|
|||
self.assertEqual(body, 'CHANGES\n=======\n\n')
|
||||
|
||||
|
||||
class TestPackagingWheels(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPackagingWheels, self).setUp()
|
||||
self.useFixture(TestRepo(self.package_dir))
|
||||
# Build the wheel
|
||||
self.run_setup('bdist_wheel', allow_fail=False)
|
||||
# Slowly construct the path to the generated whl
|
||||
dist_dir = os.path.join(self.package_dir, 'dist')
|
||||
relative_wheel_filename = os.listdir(dist_dir)[0]
|
||||
absolute_wheel_filename = os.path.join(
|
||||
dist_dir, relative_wheel_filename)
|
||||
wheel_file = wheel.install.WheelFile(absolute_wheel_filename)
|
||||
wheel_name = wheel_file.parsed_filename.group('namever')
|
||||
# Create a directory path to unpack the wheel to
|
||||
self.extracted_wheel_dir = os.path.join(dist_dir, wheel_name)
|
||||
# Extract the wheel contents to the directory we just created
|
||||
wheel_file.zipfile.extractall(self.extracted_wheel_dir)
|
||||
wheel_file.zipfile.close()
|
||||
|
||||
def test_data_directory_has_wsgi_scripts(self):
|
||||
# Build the path to the scripts directory
|
||||
scripts_dir = os.path.join(
|
||||
self.extracted_wheel_dir, 'pbr_testpackage-0.0.data/scripts')
|
||||
self.assertTrue(os.path.exists(scripts_dir))
|
||||
scripts = os.listdir(scripts_dir)
|
||||
|
||||
self.assertIn('pbr_test_wsgi', scripts)
|
||||
self.assertIn('pbr_test_wsgi_with_class', scripts)
|
||||
self.assertNotIn('pbr_test_cmd', scripts)
|
||||
self.assertNotIn('pbr_test_cmd_with_class', scripts)
|
||||
|
||||
def test_generates_c_extensions(self):
|
||||
built_package_dir = os.path.join(
|
||||
self.extracted_wheel_dir, 'pbr_testpackage')
|
||||
static_object_filename = 'testext.so'
|
||||
soabi = get_soabi()
|
||||
if soabi:
|
||||
static_object_filename = 'testext.{0}.so'.format(soabi)
|
||||
static_object_path = os.path.join(
|
||||
built_package_dir, static_object_filename)
|
||||
|
||||
self.assertTrue(os.path.exists(built_package_dir))
|
||||
self.assertTrue(os.path.exists(static_object_path))
|
||||
|
||||
|
||||
class TestPackagingHelpers(testtools.TestCase):
|
||||
|
||||
def test_generate_script(self):
|
||||
group = 'console_scripts'
|
||||
entry_point = pkg_resources.EntryPoint(
|
||||
name='test-ep',
|
||||
module_name='pbr.packaging',
|
||||
attrs=('LocalInstallScripts',))
|
||||
header = '#!/usr/bin/env fake-header\n'
|
||||
template = ('%(group)s %(module_name)s %(import_target)s '
|
||||
'%(invoke_target)s')
|
||||
|
||||
generated_script = packaging.generate_script(
|
||||
group, entry_point, header, template)
|
||||
|
||||
expected_script = (
|
||||
'#!/usr/bin/env fake-header\nconsole_scripts pbr.packaging '
|
||||
'LocalInstallScripts LocalInstallScripts'
|
||||
)
|
||||
self.assertEqual(expected_script, generated_script)
|
||||
|
||||
def test_generate_script_validates_expectations(self):
|
||||
group = 'console_scripts'
|
||||
entry_point = pkg_resources.EntryPoint(
|
||||
name='test-ep',
|
||||
module_name='pbr.packaging')
|
||||
header = '#!/usr/bin/env fake-header\n'
|
||||
template = ('%(group)s %(module_name)s %(import_target)s '
|
||||
'%(invoke_target)s')
|
||||
self.assertRaises(
|
||||
ValueError, packaging.generate_script, group, entry_point, header,
|
||||
template)
|
||||
|
||||
entry_point = pkg_resources.EntryPoint(
|
||||
name='test-ep',
|
||||
module_name='pbr.packaging',
|
||||
attrs=('attr1', 'attr2', 'attr3'))
|
||||
self.assertRaises(
|
||||
ValueError, packaging.generate_script, group, entry_point, header,
|
||||
template)
|
||||
|
||||
|
||||
class TestPackagingInPlainDirectory(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -618,3 +710,19 @@ class TestRequirementParsing(base.BaseTestCase):
|
|||
pkg_resources.split_sections(requires))
|
||||
|
||||
self.assertEqual(expected_requirements, generated_requirements)
|
||||
|
||||
|
||||
def get_soabi():
|
||||
try:
|
||||
return sysconfig.get_config_var('SOABI')
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
if 'pypy' in sysconfig.get_scheme_names():
|
||||
# NOTE(sigmavirus24): PyPy only added support for the SOABI config var
|
||||
# to sysconfig in 2015. That was well after 2.2.1 was published in the
|
||||
# Ubuntu 14.04 archive.
|
||||
for suffix, _, _ in imp.get_suffixes():
|
||||
if suffix.startswith('.pypy') and suffix.endswith('.so'):
|
||||
return suffix.split('.')[1]
|
||||
return None
|
||||
|
|
|
@ -8,10 +8,11 @@ static PyMethodDef TestextMethods[] = {
|
|||
|
||||
#if PY_MAJOR_VERSION >=3
|
||||
static struct PyModuleDef testextmodule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"testext",
|
||||
-1,
|
||||
TestextMethods
|
||||
PyModuleDef_HEAD_INIT, /* This should correspond to a PyModuleDef_Base type */
|
||||
"testext", /* This is the module name */
|
||||
"Test extension module", /* This is the module docstring */
|
||||
-1, /* This defines the size of the module and says everything is global */
|
||||
TestextMethods /* This is the method definition */
|
||||
};
|
||||
|
||||
PyObject*
|
||||
|
|
Loading…
Reference in New Issue