Support package versions from global requirements file
renderspec can now get a global-requirements.txt file to insert versions into rendered spec files. Example: renderspec --requirements global-requirements.txt example.spec.j2 If a version is explicitly given via the py2pkg() method, this version is used. If no version is given, renderspec tries to find a version from the global-requirements.txt file. Also update the documentation and for the new feature and simplify the unittest by using ddt. Change-Id: Ic0058f8aa9c6a1e7250e0e08f262bb3e2663d68b
This commit is contained in:
parent
049a9e6459
commit
5f46f91121
@ -44,6 +44,22 @@ Rendering the `example.spec.j2` file and also use the epochs can be done with::
|
||||
The epoch file is optional. If a package name is not in the epochs file,
|
||||
epoch for that package is not used.
|
||||
|
||||
Handling requirements
|
||||
*********************
|
||||
|
||||
Updating versions for `Requires` and `BuildRequires` takes a lot of time.
|
||||
:program:`renderspec` has the ability to insert versions from a given
|
||||
`global-requirements.txt` file. The file must contain lines following `PEP0508`_
|
||||
|
||||
.. note:: For OpenStack, the `global-requirements.txt`_ can be used.
|
||||
|
||||
To render a `example.spec.j2` file with a given requirements file, do::
|
||||
|
||||
renderspec --requirements global-requirements.txt example.spec.j2
|
||||
|
||||
.. _PEP0508: https://www.python.org/dev/peps/pep-0508/
|
||||
.. _global-requirements.txt: https://git.openstack.org/cgit/openstack/requirements/tree/global-requirements.txt
|
||||
|
||||
Template features
|
||||
=================
|
||||
|
||||
@ -79,6 +95,11 @@ rendered on Fedora to::
|
||||
|
||||
BuildRequires: python-oslo-config >= 2:3.4.0
|
||||
|
||||
It's also possible to skip adding required versions and handle that with a
|
||||
`global-requirements.txt` file. Given that this file contains `oslo.config>=4.3.0` and
|
||||
rendering with `--requirements`, the rendered spec would contain::
|
||||
|
||||
BuildRequires: python-oslo-config >= 4.3.0
|
||||
|
||||
context filter `license`
|
||||
************************
|
||||
|
@ -29,17 +29,24 @@ import pymod2pkg
|
||||
|
||||
import yaml
|
||||
|
||||
from renderspec import versions
|
||||
|
||||
|
||||
def _context_epoch(context, pkg_name):
|
||||
"""get the epoch (or 0 if unknown) for the given pkg name"""
|
||||
return context['epochs'].get(pkg_name, 0)
|
||||
|
||||
|
||||
def _context_py2pkg(context, pkg_name, pkg_version=None):
|
||||
def _context_py2pkg(context, pkg_name, pkg_version=None, old_filter=False):
|
||||
"""generate a distro specific package name with optional version tuple."""
|
||||
# package name handling
|
||||
name = pymod2pkg.module2package(pkg_name, context['spec_style'])
|
||||
|
||||
# if no pkg_version is given, look in the requirements and set one
|
||||
if not pkg_version and not old_filter:
|
||||
if pkg_name in context['requirements']:
|
||||
pkg_version = ('>=', context['requirements'][pkg_name])
|
||||
|
||||
# pkg_version is a tuple with comparator and number, i.e. "('>=', '1.2.3')"
|
||||
if pkg_version:
|
||||
# epoch handling
|
||||
@ -103,7 +110,7 @@ def _filter_epoch(context, value):
|
||||
|
||||
@contextfilter
|
||||
def _filter_python_module2package(context, pkg_name, pkg_version=None):
|
||||
return _context_py2pkg(context, pkg_name, pkg_version)
|
||||
return _context_py2pkg(context, pkg_name, pkg_version, old_filter=True)
|
||||
|
||||
|
||||
################
|
||||
@ -134,7 +141,7 @@ def _env_register_filters_and_globals(env):
|
||||
env.globals['license'] = _globals_license_spdx
|
||||
|
||||
|
||||
def generate_spec(spec_style, epochs, input_template_path):
|
||||
def generate_spec(spec_style, epochs, requirements, input_template_path):
|
||||
"""generate a spec file with the given style and the given template"""
|
||||
env = Environment(loader=FileSystemLoader(
|
||||
os.path.dirname(input_template_path)))
|
||||
@ -142,7 +149,8 @@ def generate_spec(spec_style, epochs, input_template_path):
|
||||
_env_register_filters_and_globals(env)
|
||||
|
||||
template = env.get_template(os.path.basename(input_template_path))
|
||||
return template.render(spec_style=spec_style, epochs=epochs)
|
||||
return template.render(spec_style=spec_style, epochs=epochs,
|
||||
requirements=requirements)
|
||||
|
||||
|
||||
def _get_default_distro():
|
||||
@ -178,6 +186,14 @@ def _get_epochs(filename):
|
||||
return {}
|
||||
|
||||
|
||||
def _get_requirements(filename):
|
||||
"""get a dictionary with pkg-name->min-version mapping"""
|
||||
if os.path.exists(filename):
|
||||
with open(filename, 'r') as f:
|
||||
return versions.get_requirements(f.readlines())
|
||||
return {}
|
||||
|
||||
|
||||
def process_args():
|
||||
distro = _get_default_distro()
|
||||
parser = argparse.ArgumentParser(
|
||||
@ -194,6 +210,11 @@ def process_args():
|
||||
parser.add_argument("input-template", nargs='?',
|
||||
help="specfile jinja2 template to render. "
|
||||
"default: *.spec.j2")
|
||||
parser.add_argument("--requirements", help="file which contains "
|
||||
"PEP0508 compatible requirement lines."
|
||||
"default: global-requirements.txt",
|
||||
default="global-requirements.txt")
|
||||
|
||||
return vars(parser.parse_args())
|
||||
|
||||
|
||||
@ -216,7 +237,9 @@ def main():
|
||||
output_fn, _, _ = input_template.rpartition('.')
|
||||
|
||||
epochs = _get_epochs(args['epochs'])
|
||||
spec = generate_spec(args['spec_style'], epochs, input_template)
|
||||
requirements = _get_requirements(args['requirements'])
|
||||
spec = generate_spec(args['spec_style'], epochs, requirements,
|
||||
input_template)
|
||||
if output_fn and output_fn != '-':
|
||||
print("Rendering: %s -> %s" % (input_template, output_fn))
|
||||
with open(output_fn, "w") as o:
|
||||
|
54
renderspec/versions.py
Normal file
54
renderspec/versions.py
Normal file
@ -0,0 +1,54 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright (c) 2016 SUSE Linux GmbH
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from packaging.requirements import Requirement
|
||||
|
||||
|
||||
def get_requirements(lines):
|
||||
"""parse the given lines and return a dict with pkg_name->version.
|
||||
lines must follow PEP0508"""
|
||||
requires = {}
|
||||
for l in lines:
|
||||
# skip comments and empty lines
|
||||
if l.startswith('#') or len(l.strip()) == 0:
|
||||
continue
|
||||
# remove trailing comments
|
||||
l = l.split('#')[0].rstrip(' ')
|
||||
r = Requirement(l)
|
||||
# check if we need the requirement
|
||||
if r.marker:
|
||||
# TODO (toabctl): currently we hardcode python 2.7 and linux2
|
||||
# see https://www.python.org/dev/peps/pep-0508/#environment-markers
|
||||
marker_env = {'python_version': '2.7', 'sys_platform': 'linux'}
|
||||
if not r.marker.evaluate(environment=marker_env):
|
||||
continue
|
||||
if r.specifier:
|
||||
# we want the lowest possible version
|
||||
# NOTE(toabctl): "min(r.specifier)" doesn't work.
|
||||
# see https://github.com/pypa/packaging/issues/69
|
||||
lowest = None
|
||||
for s in r.specifier:
|
||||
# we don't want a lowest version which is not allowed
|
||||
if s.operator == '!=':
|
||||
continue
|
||||
if not lowest or s.version < lowest.version:
|
||||
lowest = s
|
||||
|
||||
if lowest:
|
||||
requires[r.name] = lowest.version
|
||||
return requires
|
@ -1,3 +1,4 @@
|
||||
Jinja2>=2.6
|
||||
pymod2pkg>=0.3
|
||||
PyYAML>=3.1.0 # MIT
|
||||
packaging>=16.5
|
||||
|
@ -2,6 +2,7 @@ flake8
|
||||
testrepository>=0.0.18
|
||||
testresources>=0.2.4
|
||||
testtools>=1.4.0
|
||||
ddt
|
||||
|
||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
|
||||
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
||||
|
238
tests.py
238
tests.py
@ -20,11 +20,15 @@ try:
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
from ddt import data, ddt, unpack
|
||||
|
||||
from jinja2 import Environment
|
||||
|
||||
import renderspec
|
||||
import renderspec.versions
|
||||
|
||||
|
||||
@ddt
|
||||
class RenderspecContextFunctionTests(unittest.TestCase):
|
||||
"""test functions which do some calculation based on the context"""
|
||||
def test_context_license_spdx(self):
|
||||
@ -39,112 +43,88 @@ class RenderspecContextFunctionTests(unittest.TestCase):
|
||||
'ASL 2.0'
|
||||
)
|
||||
|
||||
def test_context_py2pkg_pgkname_only(self):
|
||||
@data(
|
||||
# without version
|
||||
({'spec_style': 'suse', 'epochs': {}, 'requirements': {}},
|
||||
'oslo.config', None, 'python-oslo.config'),
|
||||
({'spec_style': 'fedora', 'epochs': {}, 'requirements': {}},
|
||||
'oslo.config', None, 'python-oslo-config'),
|
||||
# with version
|
||||
({'spec_style': 'suse', 'epochs': {}, 'requirements': {}},
|
||||
'oslo.config', ('>=', '1.2.3'), 'python-oslo.config >= 1.2.3'),
|
||||
({'spec_style': 'fedora', 'epochs': {}, 'requirements': {}},
|
||||
'oslo.config', ('==', '1.2.3~a0'), 'python-oslo-config == 1.2.3~a0'),
|
||||
# with version, with epoch
|
||||
({'spec_style': 'suse', 'epochs': {'oslo.config': 4},
|
||||
'requirements': {}},
|
||||
'oslo.config', ('>=', '1.2.3'), 'python-oslo.config >= 4:1.2.3'),
|
||||
# without version, with epoch
|
||||
({'spec_style': 'suse', 'epochs': {'oslo.config': 4},
|
||||
'requirements': {}},
|
||||
'oslo.config', None, 'python-oslo.config'),
|
||||
# with version, with requirements
|
||||
({'spec_style': 'suse', 'epochs': {},
|
||||
'requirements': {'oslo.config' '1.2.3'}},
|
||||
'oslo.config', ('>=', '4.5.6'), 'python-oslo.config >= 4.5.6'),
|
||||
# without version, with requirements
|
||||
({'spec_style': 'suse', 'epochs': {},
|
||||
'requirements': {'oslo.config': '1.2.3'}},
|
||||
'oslo.config', None, 'python-oslo.config >= 1.2.3'),
|
||||
# without version, with requirements, with epoch
|
||||
({'spec_style': 'suse', 'epochs': {'oslo.config': 4},
|
||||
'requirements': {'oslo.config': '1.2.3'}},
|
||||
'oslo.config', None, 'python-oslo.config >= 4:1.2.3'),
|
||||
# with version, with requirements, with epoch
|
||||
({'spec_style': 'suse', 'epochs': {'oslo.config': 4},
|
||||
'requirements': {'oslo.config' '1.2.3'}},
|
||||
'oslo.config', ('>=', '4.5.6'), 'python-oslo.config >= 4:4.5.6'),
|
||||
)
|
||||
@unpack
|
||||
def test_context_py2pkg(self, context, pkg_name, pkg_version,
|
||||
expected_result):
|
||||
self.assertEqual(
|
||||
renderspec._context_py2pkg(
|
||||
{'spec_style': 'suse', 'epochs': {}}, 'oslo.config'),
|
||||
'python-oslo.config'
|
||||
)
|
||||
self.assertEqual(
|
||||
renderspec._context_py2pkg(
|
||||
{'spec_style': 'fedora', 'epochs': {}}, 'oslo.config'),
|
||||
'python-oslo-config'
|
||||
)
|
||||
|
||||
def test_context_py2pkg_pgkname_and_version(self):
|
||||
self.assertEqual(
|
||||
renderspec._context_py2pkg(
|
||||
{'spec_style': 'suse', 'epochs': {}},
|
||||
'oslo.config', ('>=', '1.2.3')),
|
||||
'python-oslo.config >= 1.2.3'
|
||||
)
|
||||
self.assertEqual(
|
||||
renderspec._context_py2pkg(
|
||||
{'spec_style': 'fedora', 'epochs': {}},
|
||||
'oslo.config', ('==', '1.2.3~a0')),
|
||||
'python-oslo-config == 1.2.3~a0'
|
||||
)
|
||||
|
||||
def test_context_py2pkg_pgkname_and_version_and_epoch(self):
|
||||
self.assertEqual(
|
||||
renderspec._context_py2pkg(
|
||||
{'spec_style': 'suse', 'epochs': {'oslo.config': '4'}},
|
||||
'oslo.config', ('>=', '1.2.3')),
|
||||
'python-oslo.config >= 4:1.2.3'
|
||||
)
|
||||
|
||||
def test_context_py2pkg_pgkname_and_epoch_no_version(self):
|
||||
self.assertEqual(
|
||||
renderspec._context_py2pkg(
|
||||
{'spec_style': 'suse', 'epochs': {'oslo.config': '4'}},
|
||||
'oslo.config'),
|
||||
'python-oslo.config'
|
||||
)
|
||||
renderspec._context_py2pkg(context, pkg_name, pkg_version),
|
||||
expected_result)
|
||||
|
||||
def test_context_epoch_without_epochs(self):
|
||||
self.assertEqual(
|
||||
renderspec._context_epoch(
|
||||
{'spec_style': 'suse', 'epochs': {}}, 'oslo.config'),
|
||||
0
|
||||
)
|
||||
{'spec_style': 'suse', 'epochs': {}, 'requirements': {}},
|
||||
'oslo.config'), 0)
|
||||
|
||||
def test_context_epoch_with_epochs(self):
|
||||
self.assertEqual(
|
||||
renderspec._context_epoch(
|
||||
{'spec_style': 'suse', 'epochs': {'oslo.config': 4}},
|
||||
'oslo.config'),
|
||||
4
|
||||
)
|
||||
{'spec_style': 'suse', 'epochs': {'oslo.config': 4},
|
||||
'requirements': {}}, 'oslo.config'), 4)
|
||||
|
||||
|
||||
@ddt
|
||||
class RenderspecTemplateFilterTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""create a Jinja2 environment and register the standard filters"""
|
||||
self.env = Environment()
|
||||
renderspec._env_register_filters_and_globals(self.env)
|
||||
|
||||
def test_render_filter_py2pkg_oldstyle(self):
|
||||
template = self.env.from_string("{{ 'requests' | py2pkg }} >= 2.8.1")
|
||||
@data(
|
||||
# old style
|
||||
({'spec_style': 'suse', 'epochs': {}, 'requirements': {}},
|
||||
"{{ 'requests' | py2pkg }}", "python-requests"),
|
||||
({'spec_style': 'suse', 'epochs': {}, 'requirements': {}},
|
||||
"{{ 'requests' | py2pkg }} >= 2.8.1", "python-requests >= 2.8.1"),
|
||||
({'spec_style': 'suse', 'epochs': {},
|
||||
'requirements': {'requests': '1.2.3'}},
|
||||
"{{ 'requests' | py2pkg }} >= 2.8.1", "python-requests >= 2.8.1"),
|
||||
)
|
||||
@unpack
|
||||
def test_render_filter_py2pkg(self, context, string, expected_result):
|
||||
template = self.env.from_string(string)
|
||||
self.assertEqual(
|
||||
template.render(spec_style='suse', epochs={}),
|
||||
'python-requests >= 2.8.1')
|
||||
|
||||
def test_render_filter_py2pkg(self):
|
||||
template = self.env.from_string(
|
||||
"{{ 'requests' | py2pkg }}")
|
||||
self.assertEqual(
|
||||
template.render(spec_style='suse', epochs={}),
|
||||
'python-requests')
|
||||
|
||||
def test_render_filter_py2pkg_with_version(self):
|
||||
template = self.env.from_string(
|
||||
"{{ 'requests' | py2pkg(('>=', '2.8.1')) }}")
|
||||
self.assertEqual(
|
||||
template.render(spec_style='suse', epochs={}),
|
||||
'python-requests >= 2.8.1')
|
||||
|
||||
def test_render_filter_py2pkg_with_version_and_epoch(self):
|
||||
template = self.env.from_string(
|
||||
"{{ 'requests' | py2pkg(('>=', '2.8.1')) }}")
|
||||
self.assertEqual(
|
||||
template.render(spec_style='suse', epochs={'requests': '1'}),
|
||||
'python-requests >= 1:2.8.1')
|
||||
|
||||
def test_render_filter_epoch_without_epochs(self):
|
||||
template = self.env.from_string(
|
||||
"{{ 'requests' | epoch }}")
|
||||
self.assertEqual(
|
||||
template.render(spec_style='suse', epochs={}),
|
||||
'0')
|
||||
|
||||
def test_render_filter_epoch_with_epochs(self):
|
||||
template = self.env.from_string(
|
||||
"{{ 'requests' | epoch }}")
|
||||
self.assertEqual(
|
||||
template.render(spec_style='suse', epochs={'requests': '1'}),
|
||||
'1')
|
||||
template.render(**context),
|
||||
expected_result)
|
||||
|
||||
|
||||
@ddt
|
||||
class RenderspecTemplateFunctionTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""create a Jinja2 environment and register the standard filters"""
|
||||
@ -155,44 +135,88 @@ class RenderspecTemplateFunctionTests(unittest.TestCase):
|
||||
template = self.env.from_string(
|
||||
"{{ license('Apache-2.0') }}")
|
||||
self.assertEqual(
|
||||
template.render(spec_style='fedora', epochs={}),
|
||||
template.render(spec_style='fedora', epochs={}, requirements={}),
|
||||
'ASL 2.0')
|
||||
|
||||
def test_render_func_py2pkg(self):
|
||||
template = self.env.from_string(
|
||||
"{{ py2pkg('requests') }}")
|
||||
self.assertEqual(
|
||||
template.render(spec_style='suse', epochs={}),
|
||||
'python-requests')
|
||||
@data(
|
||||
# plain
|
||||
({'spec_style': 'suse', 'epochs': {}, 'requirements': {}},
|
||||
"{{ py2pkg('requests') }}", "python-requests"),
|
||||
# with version
|
||||
({'spec_style': 'suse', 'epochs': {}, 'requirements': {}},
|
||||
"{{ py2pkg('requests', ('>=', '2.8.1')) }}",
|
||||
"python-requests >= 2.8.1"),
|
||||
# with version, with epoch
|
||||
({'spec_style': 'suse', 'epochs': {'requests': 4}, 'requirements': {}},
|
||||
"{{ py2pkg('requests', ('>=', '2.8.1')) }}",
|
||||
"python-requests >= 4:2.8.1"),
|
||||
# with version, with epoch, with requirements
|
||||
({'spec_style': 'suse', 'epochs': {'requests': 4},
|
||||
'requirements': {'requests': '1.2.3'}},
|
||||
"{{ py2pkg('requests', ('>=', '2.8.1')) }}",
|
||||
"python-requests >= 4:2.8.1"),
|
||||
# without version, with epoch, with requirements
|
||||
({'spec_style': 'suse', 'epochs': {'requests': 4},
|
||||
'requirements': {'requests': '1.2.3'}},
|
||||
"{{ py2pkg('requests') }}",
|
||||
"python-requests >= 4:1.2.3"),
|
||||
|
||||
def test_render_func_py2pkg_with_version(self):
|
||||
template = self.env.from_string(
|
||||
"{{ py2pkg('requests', ('>=', '2.8.1')) }}")
|
||||
)
|
||||
@unpack
|
||||
def test_render_func_py2pkg(self, context, string, expected_result):
|
||||
template = self.env.from_string(string)
|
||||
self.assertEqual(
|
||||
template.render(spec_style='suse', epochs={}),
|
||||
'python-requests >= 2.8.1')
|
||||
|
||||
def test_render_func_py2pkg_with_version_and_epoch(self):
|
||||
template = self.env.from_string(
|
||||
"{{ py2pkg('requests', ('>=', '2.8.1')) }}")
|
||||
self.assertEqual(
|
||||
template.render(spec_style='suse', epochs={'requests': '1'}),
|
||||
'python-requests >= 1:2.8.1')
|
||||
template.render(**context),
|
||||
expected_result)
|
||||
|
||||
def test_render_func_epoch_without_epochs(self):
|
||||
template = self.env.from_string(
|
||||
"Epoch: {{ epoch('requests') }}")
|
||||
self.assertEqual(
|
||||
template.render(spec_style='suse', epochs={}),
|
||||
template.render(spec_style='suse', epochs={}, requirements={}),
|
||||
'Epoch: 0')
|
||||
|
||||
def test_render_func_epoch_with_epochs(self):
|
||||
template = self.env.from_string(
|
||||
"Epoch: {{ epoch('requests') }}")
|
||||
self.assertEqual(
|
||||
template.render(spec_style='suse', epochs={'requests': 1}),
|
||||
template.render(spec_style='suse', epochs={'requests': 1},
|
||||
requirements={}),
|
||||
'Epoch: 1')
|
||||
|
||||
|
||||
class RenderspecVersionsTests(unittest.TestCase):
|
||||
def test_without_version(self):
|
||||
requires = renderspec.versions.get_requirements(
|
||||
['# a comment', '', ' ', 'pyasn1 # BSD', 'Paste'])
|
||||
self.assertEqual(requires, {})
|
||||
|
||||
def test_with_single_version(self):
|
||||
requires = renderspec.versions.get_requirements(
|
||||
['paramiko>=1.16.0 # LGPL'])
|
||||
self.assertEqual(requires, {'paramiko': '1.16.0'})
|
||||
|
||||
def test_with_multiple_versions(self):
|
||||
requires = renderspec.versions.get_requirements(
|
||||
['sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 # BSD'])
|
||||
self.assertEqual(requires, {'sphinx': '1.1.2'})
|
||||
|
||||
def test_with_multiple_versions_and_invalid_lowest(self):
|
||||
requires = renderspec.versions.get_requirements(
|
||||
['sphinx>=1.1.2,!=1.1.0,!=1.3b1,<1.3 # BSD'])
|
||||
self.assertEqual(requires, {'sphinx': '1.1.2'})
|
||||
|
||||
def test_with_single_marker(self):
|
||||
requires = renderspec.versions.get_requirements(
|
||||
["pywin32>=1.0;sys_platform=='win32' # PSF"])
|
||||
self.assertEqual(requires, {})
|
||||
|
||||
def test_with_multiple_markers(self):
|
||||
requires = renderspec.versions.get_requirements(
|
||||
["""pyinotify>=0.9.6;sys_platform!='win32' and \
|
||||
sys_platform!='darwin' and sys_platform!='sunos5' # MIT"""])
|
||||
self.assertEqual(requires, {'pyinotify': '0.9.6'})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user