Merge "Support distro specific child templates"

This commit is contained in:
Jenkins
2016-08-18 15:35:24 +00:00
committed by Gerrit Code Review
6 changed files with 161 additions and 5 deletions

View File

@@ -163,6 +163,37 @@ With the `suse` spec-style::
License: Apache-2.0
distribution specific blocks & child templates
**********************************************
To properly handle differences between individual .spec styles, renderspec
contains child templates in `renderspec/dist-templates` which are
automatically used with corresponding `--spec-style`. These allow different
output for each spec style (distro) using jinja `{% block %}` syntax.
For example consider simple `renderspec/dist-templates/fedora.spec.j2`:
{% extends ".spec" %}
{% block build_requires %}
BuildRequires: {{ py2pkg('setuptools') }}
{% endblock %}
allows following in a spec template:
{% block build_requires %}{% endblock %}
to render into
BuildRequires: python-setuptools
with `fedora` spec style, while `renderspec/dist-templates/suse.spec.j2` might
define other result for `suse` spec style.
For more information, see current `renderspec/dist-templates` and usage in
`openstack/rpm-packaging`_ project.
.. _Jinja2: http://jinja.pocoo.org/docs/dev/
.. _openstack/rpm-packaging: https://git.openstack.org/cgit/openstack/rpm-packaging/
.. _pymod2pkg: https://git.openstack.org/cgit/openstack/pymod2pkg

View File

@@ -24,11 +24,11 @@ import sys
from jinja2 import contextfilter
from jinja2 import contextfunction
from jinja2 import Environment
from jinja2 import FileSystemLoader
import pymod2pkg
import yaml
from renderspec.distloader import RenderspecLoader
from renderspec import versions
@@ -147,12 +147,17 @@ def _env_register_filters_and_globals(env):
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)))
env = Environment(loader=RenderspecLoader(
template_fn=input_template_path),
trim_blocks=True)
_env_register_filters_and_globals(env)
template = env.get_template(os.path.basename(input_template_path))
template_name = '.spec'
if spec_style in env.loader.list_templates():
template_name = spec_style
template = env.get_template(template_name)
return template.render(spec_style=spec_style, epochs=epochs,
requirements=requirements)

View File

@@ -0,0 +1,4 @@
{% extends ".spec" %}
{% block build_requires %}
BuildRequires: {{ py2pkg('setuptools') }}
{% endblock %}

View File

@@ -0,0 +1,4 @@
{% extends ".spec" %}
{% block build_requires %}
BuildRequires: openstack-macros
{% endblock %}

76
renderspec/distloader.py Normal file
View File

@@ -0,0 +1,76 @@
#!/usr/bin/python
# Copyright (c) 2016 Red Hat
#
# 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.
import jinja2
from jinja2.loaders import TemplateNotFound
from jinja2.utils import open_if_exists
import os
def get_dist_templates_path():
return os.path.join(os.path.dirname(__file__), 'dist-templates')
class RenderspecLoader(jinja2.BaseLoader):
"""A special template loader which allows rendering supplied .spec template
with distro specific blocks maintained as part of renderspec.
'.spec' returns the spec template (which you need to supply during init)
while other strings map to corresponding child templates included
in renderspec which simply extend the '.spec' template.
"""
base_ref = '.spec'
template_postfix = '.spec.j2'
def __init__(self, template_fn, encoding='utf-8'):
self.base_fn = template_fn
self.encoding = encoding
self.disttemp_path = get_dist_templates_path()
def get_source(self, environment, template):
if template == self.base_ref:
fn = self.base_fn
else:
fn = os.path.join(self.disttemp_path,
template + self.template_postfix)
f = open_if_exists(fn)
if not f:
return TemplateNotFound(template)
try:
contents = f.read().decode(self.encoding)
finally:
f.close()
mtime = os.path.getmtime(self.base_fn)
def uptodate():
try:
return os.path.getmtime(self.base_fn) == mtime
except OSError:
return False
return contents, fn, uptodate
def list_templates(self):
found = set([self.base_ref])
walk_dir = os.walk(self.disttemp_path)
for _, _, filenames in walk_dir:
for fn in filenames:
if fn.endswith(self.template_postfix):
template = fn[:-len(self.template_postfix)]
found.add(template)
return sorted(found)

View File

@@ -24,7 +24,7 @@ from ddt import data, ddt, unpack
from jinja2 import Environment
from mock import Mock
from mock import Mock, patch
import os
import renderspec
import renderspec.versions
@@ -297,5 +297,41 @@ class RenderspecDistroDetection(unittest.TestCase):
self.assertEqual(renderspec._get_default_distro(), "unknown")
class RenderspecDistTeamplatesTests(unittest.TestCase):
@patch('renderspec.distloader.get_dist_templates_path')
def test_dist_templates(self, mock_dt_path):
base_txt = ('Line before block\n'
'{% block footest %}{% endblock %}\n'
'Line after block\n')
dt_txt = ('{% extends ".spec" %}'
'{% block footest %}'
'foo block\n'
'macro: {{ py2pkg("test") }}\n'
'{% endblock %}')
expected_out = ('Line before block\n'
'foo block\n'
'macro: python-test\n'
'Line after block')
tmpdir = tempfile.mkdtemp(prefix='renderspec-test_')
try:
# create .spec template
base_path = os.path.join(tmpdir, 'foo.spec.j2')
with open(base_path, 'w+') as f:
f.write(base_txt)
# create custom dist template
dt_dir = os.path.join(tmpdir, 'dist-templates')
os.mkdir(dt_dir)
dt_path = os.path.join(dt_dir, 'loldistro.spec.j2')
with open(dt_path, 'w+') as f:
f.write(dt_txt)
# mock this to use testing dist-tempaltes folder
mock_dt_path.return_value = dt_dir
out = renderspec.generate_spec('loldistro', {}, {}, base_path)
self.assertEqual(out, expected_out)
finally:
shutil.rmtree(tmpdir)
if __name__ == '__main__':
unittest.main()