renderspec/renderspec/__init__.py

240 lines
8.5 KiB
Python

#!/usr/bin/python
# Copyright (c) 2015 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.
import argparse
import os
import platform
import string
import sys
from jinja2 import Environment
import yaml
from renderspec.distloader import RenderspecLoader
from renderspec import versions
from renderspec import contextfuncs
def generate_spec(spec_style, epochs, requirements, skip_pyversion,
input_template_format, input_template_path, output_path):
"""generate a spec file with the given style and input template"""
if input_template_format == 'spec.j2':
return _renderer_input_template_format_spec(
spec_style, epochs, requirements, skip_pyversion,
input_template_path, output_path)
else:
raise Exception('Unknown input-template-format "%s"' %
input_template_format)
def _renderer_input_template_format_spec(spec_style, epochs, requirements,
skip_pyversion,
input_template_path, output_path):
"""render a 'traditional' .spec.j2 template into a .spec file"""
env = Environment(loader=RenderspecLoader(
template_fn=input_template_path),
trim_blocks=True)
contextfuncs.env_register_filters_and_globals(env)
template_name = '.spec'
if spec_style in env.loader.list_templates():
template_name = spec_style
template = env.get_template(template_name)
input_template_dir = os.path.dirname(os.path.abspath(input_template_path))
if output_path:
output_dir = os.path.dirname(
os.path.abspath(output_path))
else:
output_dir = None
return template.render(spec_style=spec_style, epochs=epochs,
requirements=requirements,
skip_pyversion=skip_pyversion,
input_template_dir=input_template_dir,
output_dir=output_dir)
def _is_fedora(distname):
"""detect Fedora-based distro (e.g Fedora, CentOS, RHEL)"""
distname = distname.lower()
for x in ["fedora", "centos", "red hat"]:
if x in distname:
return True
return False
def _get_default_distro():
distname = None
# Python 3.8 or newer does no longer provide this function
if hasattr(platform, 'linux_distribution'):
distname, _, _ = platform.linux_distribution()
# newer distros only have /etc/os-release and then platform doesn't work
# anymore and upstream does not want to fix it:
# https://bugs.python.org/issue1322
if not distname and 'Linux' in platform.system():
try:
with open('/etc/os-release', 'r') as lsb_release:
for line in lsb_release:
if line.startswith('ID_LIKE='):
distname = line.partition('=')[2].strip(
string.punctuation + string.whitespace)
break
# Later Fedora versions (e.g. Fedora 32) do not include ID_LIKE
# in /etc/os-release, so we need to rely on ID
if not distname:
with open('/etc/os-release', 'r') as lsb_release:
for line in lsb_release:
if line.startswith('ID='):
distname = line.partition('=')[2].strip(
string.punctuation + string.whitespace)
break
except OSError:
print('WARN: Unable to determine Linux distribution')
if not distname:
print('WARN: Unable to determine Linux distribution')
if "suse" in distname.lower():
return "suse"
elif _is_fedora(distname):
return "fedora"
else:
return "unknown"
def _get_default_pyskips(distro):
# py3 building is all complicated on CentOS 7.x
if distro == 'fedora':
# Python 3.8 or newer does no longer provide this function
# CentOS 7 does not have Python 3.8
if hasattr(platform, 'linux_distribution'):
distname, distver, _ = platform.linux_distribution()
if 'CentOS' in distname and distver.startswith('7'):
return 'py3'
return None
def _get_default_template():
fns = [f for f in os.listdir('.')
if os.path.isfile(f) and f.endswith('.spec.j2')]
if not fns:
return None, ("No *.spec.j2 templates found. "
"See `renderspec -h` for usage.")
elif len(fns) > 1:
return None, ("Multiple *.spec.j2 templates found, "
"please specify one.\n"
"See `renderspec -h` for usage.")
else:
return fns[0], None
def _get_epochs(filename):
"""get a dictionary with pkg-name->epoch mapping"""
epochs = {}
if filename is not None:
with open(filename, 'r') as f:
data = yaml.safe_load(f.read())
epochs.update(data['epochs'])
return epochs
def _get_requirements(filenames):
"""get a dictionary with pkg-name->min-version mapping"""
reqs = {}
for filename in filenames:
with open(filename, 'r') as f:
reqs.update(versions.get_requirements(f.readlines()))
return reqs
def process_args():
distro = _get_default_distro()
parser = argparse.ArgumentParser(
description="Convert a .spec.j2 template into a .spec")
parser.add_argument("-o", "--output",
help="output filename or '-' for stdout. "
"default: autodetect")
parser.add_argument("--spec-style", help="distro style you want to use. "
"default: %s" % (distro), default=distro,
choices=['suse', 'suse_py39', 'suse_py311', 'fedora'])
parser.add_argument("--skip-pyversion",
help='Skip requirements for this pyversion',
default=_get_default_pyskips(distro),
choices=['py2', 'py3'])
parser.add_argument("--epochs", help="yaml file with epochs listed.")
parser.add_argument("input-template", nargs='?',
help="specfile jinja2 template to render. "
"default: *.spec.j2")
parser.add_argument("-f", "--input-template-format", help="Format of the "
"input-template file. default: %(default)s",
default="spec.j2", choices=["spec.j2"])
parser.add_argument("--requirements", help="file(s) which contain "
"PEP0508 compatible requirement lines. Last mentioned "
"file has highest priority. default: %(default)s",
action='append', default=[])
return vars(parser.parse_args())
def main():
args = process_args()
# autodetect input/output fns if possible
input_template = args['input-template']
if not input_template:
input_template, errmsg = _get_default_template()
if not input_template:
print(errmsg)
return 1
output_filename = args['output']
if not output_filename:
if not input_template.endswith('.spec.j2'):
print("Failed to autodetect output file name. "
"Please specify using `-o/--output`.")
return 2
output_filename, _, _ = input_template.rpartition('.')
try:
epochs = _get_epochs(args['epochs'])
requirements = _get_requirements(args['requirements'])
except IOError as e:
print(e)
return 3
if output_filename and output_filename != '-':
output_path = os.path.abspath(output_filename)
else:
output_path = None
spec = generate_spec(args['spec_style'], epochs, requirements,
args['skip_pyversion'],
args['input_template_format'],
input_template, output_path)
if output_path:
print("Rendering: %s -> %s" % (input_template, output_path))
with open(output_path, "w") as o:
o.write(spec)
else:
print(spec)
return 0
if __name__ == '__main__':
sys.exit(main())