Support distro specific child templates

Support jinja child templates in renderspec/dist-templates which are
automatically used with corresponding --spec-style and allow different
output for each spec style (distro) using jinja `{% block %}` syntax.

Also enable trim_blocks to keep resulting specs clean.

Change-Id: I7c082e56836f06fb98881adc4cdf63d8626f4d7b
This commit is contained in:
Jakub Ruzicka 2016-07-26 17:11:07 +02:00
parent c49392043a
commit 563ba570a5
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()