forked from pool/spec-cleaner
Accepting request 49565 from home:vuntz
OBS-URL: https://build.opensuse.org/request/show/49565 OBS-URL: https://build.opensuse.org/package/show/openSUSE:Tools/spec-cleaner?expand=0&rev=1
This commit is contained in:
commit
1403f7b963
23
.gitattributes
vendored
Normal file
23
.gitattributes
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
## Default LFS
|
||||
*.7z filter=lfs diff=lfs merge=lfs -text
|
||||
*.bsp filter=lfs diff=lfs merge=lfs -text
|
||||
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
||||
*.gem filter=lfs diff=lfs merge=lfs -text
|
||||
*.gz filter=lfs diff=lfs merge=lfs -text
|
||||
*.jar filter=lfs diff=lfs merge=lfs -text
|
||||
*.lz filter=lfs diff=lfs merge=lfs -text
|
||||
*.lzma filter=lfs diff=lfs merge=lfs -text
|
||||
*.obscpio filter=lfs diff=lfs merge=lfs -text
|
||||
*.oxt filter=lfs diff=lfs merge=lfs -text
|
||||
*.pdf filter=lfs diff=lfs merge=lfs -text
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
*.rpm filter=lfs diff=lfs merge=lfs -text
|
||||
*.tbz filter=lfs diff=lfs merge=lfs -text
|
||||
*.tbz2 filter=lfs diff=lfs merge=lfs -text
|
||||
*.tgz filter=lfs diff=lfs merge=lfs -text
|
||||
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||
*.txz filter=lfs diff=lfs merge=lfs -text
|
||||
*.whl filter=lfs diff=lfs merge=lfs -text
|
||||
*.xz filter=lfs diff=lfs merge=lfs -text
|
||||
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||
*.zst filter=lfs diff=lfs merge=lfs -text
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.osc
|
908
spec-cleaner
Normal file
908
spec-cleaner
Normal file
@ -0,0 +1,908 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: set ts=4 sw=4 et: coding=UTF-8
|
||||
|
||||
#
|
||||
# Copyright (c) 2009, Novell, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of the <ORGANIZATION> nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
#
|
||||
# (Licensed under the simplified BSD license)
|
||||
#
|
||||
# Authors:
|
||||
# Vincent Untz <vuntz@novell.com>
|
||||
# Pavol Rusnak <prusnak@opensuse.org>
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import cStringIO
|
||||
import optparse
|
||||
import re
|
||||
import time
|
||||
|
||||
#######################################################################
|
||||
|
||||
VERSION = '0.1'
|
||||
|
||||
re_comment = re.compile('^$|^\s*#')
|
||||
re_define = re.compile('^\s*%define', re.IGNORECASE)
|
||||
|
||||
re_bindir = re.compile('%{_prefix}/bin([/\s$])')
|
||||
re_sbindir = re.compile('%{_prefix}/sbin([/\s$])')
|
||||
re_includedir = re.compile('%{_prefix}/include([/\s$])')
|
||||
re_datadir = re.compile('%{_prefix}/share([/\s$])')
|
||||
re_mandir = re.compile('%{_datadir}/man([/\s$])')
|
||||
re_infodir = re.compile('%{_datadir}/info([/\s$])')
|
||||
|
||||
|
||||
def strip_useless_spaces(s):
|
||||
return ' '.join(s.split())
|
||||
|
||||
|
||||
def replace_known_dirs(s):
|
||||
s = s.replace('%_prefix', '%{_prefix}')
|
||||
s = s.replace('%_bindir', '%{_bindir}')
|
||||
s = s.replace('%_sbindir', '%{_sbindir}')
|
||||
s = s.replace('%_includedir', '%{_includedir}')
|
||||
s = s.replace('%_datadir', '%{_datadir}')
|
||||
s = s.replace('%_mandir', '%{_mandir}')
|
||||
s = s.replace('%_infodir', '%{_infodir}')
|
||||
s = s.replace('%_libdir', '%{_libdir}')
|
||||
s = s.replace('%_libexecdir', '%{_libexecdir}')
|
||||
s = s.replace('%_lib', '%{_lib}')
|
||||
s = s.replace('%{_prefix}/%{_lib}', '%{_libdir}')
|
||||
s = s.replace('%_sysconfdir', '%{_sysconfdir}')
|
||||
s = s.replace('%_localstatedir', '%{_localstatedir}')
|
||||
s = s.replace('%_initddir', '%{_initddir}')
|
||||
# old typo in rpm macro
|
||||
s = s.replace('%_initrddir', '%{_initddir}')
|
||||
s = s.replace('%{_initrddir}', '%{_initddir}')
|
||||
|
||||
s = re_bindir.sub(r'%{_bindir}\1', s)
|
||||
s = re_sbindir.sub(r'%{_sbindir}\1', s)
|
||||
s = re_includedir.sub(r'%{_includedir}\1', s)
|
||||
s = re_datadir.sub(r'%{_datadir}\1', s)
|
||||
s = re_mandir.sub(r'%{_mandir}\1', s)
|
||||
s = re_infodir.sub(r'%{_infodir}\1', s)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def replace_buildroot(s):
|
||||
s = s.replace('${RPM_BUILD_ROOT}', '%{buildroot}')
|
||||
s = s.replace('$RPM_BUILD_ROOT', '%{buildroot}')
|
||||
s = s.replace('%buildroot', '%{buildroot}')
|
||||
s = s.replace('%{buildroot}/usr', '%{buildroot}%{_prefix}')
|
||||
s = s.replace('%{buildroot}/', '%{buildroot}')
|
||||
s = s.replace('%{buildroot}etc/init.d/', '%{buildroot}%{_initddir}/')
|
||||
s = s.replace('%{buildroot}etc/', '%{buildroot}%{_sysconfdir}/')
|
||||
s = s.replace('%{buildroot}usr/', '%{buildroot}%{_prefix}/')
|
||||
s = s.replace('%{buildroot}var/', '%{buildroot}%{_localstatedir}/')
|
||||
s = s.replace('"%{buildroot}"', '%{buildroot}')
|
||||
return s
|
||||
|
||||
|
||||
def replace_optflags(s):
|
||||
s = s.replace('${RPM_OPT_FLAGS}', '%{optflags}')
|
||||
s = s.replace('$RPM_OPT_FLAGS', '%{optflags}')
|
||||
s = s.replace('%optflags', '%{optflags}')
|
||||
return s
|
||||
|
||||
|
||||
def replace_remove_la(s):
|
||||
cmp_line = strip_useless_spaces(s)
|
||||
if cmp_line in [ 'find %{buildroot} -type f -name "*.la" -exec %{__rm} -fv {} +', 'find %{buildroot} -type f -name "*.la" -delete' ]:
|
||||
s = 'find %{buildroot} -type f -name "*.la" -delete -print'
|
||||
return s
|
||||
|
||||
|
||||
def replace_all(s):
|
||||
s = replace_buildroot(s)
|
||||
s = replace_optflags(s)
|
||||
s = replace_known_dirs(s)
|
||||
s = replace_remove_la(s)
|
||||
return s
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
class RpmException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
class RpmSection(object):
|
||||
'''
|
||||
Basic cleanup: we remove trailing spaces.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
self.lines = []
|
||||
self.previous_line = None
|
||||
|
||||
def add(self, line):
|
||||
line = line.rstrip()
|
||||
line = replace_all(line)
|
||||
self.lines.append(line)
|
||||
self.previous_line = line
|
||||
|
||||
def output(self, fout):
|
||||
for line in self.lines:
|
||||
fout.write(line + '\n')
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
class RpmCopyright(RpmSection):
|
||||
'''
|
||||
Adds default copyright notice if needed.
|
||||
Remove initial empty lines.
|
||||
Remove norootforbuild.
|
||||
'''
|
||||
|
||||
|
||||
def _add_default_copyright(self):
|
||||
self.lines.append(time.strftime('''#
|
||||
# spec file for package
|
||||
#
|
||||
# Copyright (c) %Y SUSE LINUX Products GmbH, Nuernberg, Germany.
|
||||
#
|
||||
# All modifications and additions to the file contributed by third parties
|
||||
# remain the property of their copyright owners, unless otherwise agreed
|
||||
# upon. The license for this file, and modifications and additions to the
|
||||
# file, is the same license as for the pristine package itself (unless the
|
||||
# license for the pristine package is not an Open Source License, in which
|
||||
# case the license is the MIT License). An "Open Source License" is a
|
||||
# license that conforms to the Open Source Definition (Version 1.9)
|
||||
# published by the Open Source Initiative.
|
||||
|
||||
# Please submit bugfixes or comments via http://bugs.opensuse.org/
|
||||
#
|
||||
|
||||
'''))
|
||||
|
||||
|
||||
def add(self, line):
|
||||
if not self.lines and not line:
|
||||
return
|
||||
|
||||
if line == '# norootforbuild':
|
||||
return
|
||||
|
||||
RpmSection.add(self, line)
|
||||
|
||||
|
||||
def output(self, fout):
|
||||
if not self.lines:
|
||||
self._add_default_copyright()
|
||||
RpmSection.output(self, fout)
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
class RpmPreamble(RpmSection):
|
||||
'''
|
||||
Only keep one empty line for many consecutive ones.
|
||||
Reorder lines.
|
||||
Fix bad licenses.
|
||||
Use one line per BuildRequires/Requires/etc.
|
||||
Use %{version} instead of %{version}-%{release} for BuildRequires/etc.
|
||||
Remove AutoReqProv.
|
||||
Standardize BuildRoot.
|
||||
|
||||
This one is a bit tricky since we reorder things. We have a notion of
|
||||
paragraphs, categories, and groups.
|
||||
|
||||
A paragraph is a list of non-empty lines. Conditional directives like
|
||||
%if/%else/%endif also mark paragraphs. It contains categories.
|
||||
A category is a list of lines on the same topic. It contains a list of
|
||||
groups.
|
||||
A group is a list of lines where the first few ones are either %define
|
||||
or comment lines, and the last one is a normal line.
|
||||
|
||||
This means that the %define and comments will stay attached to one
|
||||
line, even if we reorder the lines.
|
||||
'''
|
||||
|
||||
re_if = re.compile('^\s*(?:%if\s|%ifarch\s|%ifnarch\s|%else\s*$|%endif\s*$)', re.IGNORECASE)
|
||||
|
||||
re_name = re.compile('^Name:\s*(\S*)', re.IGNORECASE)
|
||||
re_version = re.compile('^Version:\s*(\S*)', re.IGNORECASE)
|
||||
re_release = re.compile('^Release:\s*(\S*)', re.IGNORECASE)
|
||||
re_license = re.compile('^License:\s*(.*)', re.IGNORECASE)
|
||||
re_summary = re.compile('^Summary:\s*(.*)', re.IGNORECASE)
|
||||
re_url = re.compile('^Url:\s*(\S*)', re.IGNORECASE)
|
||||
re_group = re.compile('^Group:\s*(.*)', re.IGNORECASE)
|
||||
re_source = re.compile('^Source(\d*):\s*(\S*)', re.IGNORECASE)
|
||||
re_patch = re.compile('^((?:#[#\s]*)?)Patch(\d*):\s*(\S*)', re.IGNORECASE)
|
||||
re_buildrequires = re.compile('^BuildRequires:\s*(.*)', re.IGNORECASE)
|
||||
re_prereq = re.compile('^PreReq:\s*(.*)', re.IGNORECASE)
|
||||
re_requires = re.compile('^Requires:\s*(.*)', re.IGNORECASE)
|
||||
re_recommends = re.compile('^Recommends:\s*(.*)', re.IGNORECASE)
|
||||
re_suggests = re.compile('^Suggests:\s*(.*)', re.IGNORECASE)
|
||||
re_supplements = re.compile('^Supplements:\s*(.*)', re.IGNORECASE)
|
||||
re_provides = re.compile('^Provides:\s*(.*)', re.IGNORECASE)
|
||||
re_obsoletes = re.compile('^Obsoletes:\s*(.*)', re.IGNORECASE)
|
||||
re_buildroot = re.compile('^\s*BuildRoot:', re.IGNORECASE)
|
||||
re_buildarch = re.compile('^\s*BuildArch:\s*(.*)', re.IGNORECASE)
|
||||
|
||||
re_requires_token = re.compile('(\s*(\S+(?:\s*(?:[<>]=?|=)\s*[^\s,]+)?),?)')
|
||||
|
||||
category_to_re = {
|
||||
'name': re_name,
|
||||
'version': re_version,
|
||||
'release': re_release,
|
||||
'license': re_license,
|
||||
'summary': re_summary,
|
||||
'url': re_url,
|
||||
'group': re_group,
|
||||
# for source, we have a special match to keep the source number
|
||||
# for patch, we have a special match to keep the patch number
|
||||
'buildrequires': re_buildrequires,
|
||||
'prereq': re_prereq,
|
||||
'requires': re_requires,
|
||||
'recommends': re_recommends,
|
||||
'suggests': re_suggests,
|
||||
'supplements': re_supplements,
|
||||
# for provides/obsoletes, we have a special case because we group them
|
||||
# for build root, we have a special match because we force its value
|
||||
'buildarch': re_buildarch
|
||||
}
|
||||
|
||||
category_to_key = {
|
||||
'name': 'Name',
|
||||
'version': 'Version',
|
||||
'release': 'Release',
|
||||
'license': 'License',
|
||||
'summary': 'Summary',
|
||||
'url': 'Url',
|
||||
'group': 'Group',
|
||||
'source': 'Source',
|
||||
'patch': 'Patch',
|
||||
'buildrequires': 'BuildRequires',
|
||||
'prereq': 'PreReq',
|
||||
'requires': 'Requires',
|
||||
'recommends': 'Recommends',
|
||||
'suggests': 'Suggests',
|
||||
'supplements': 'Supplements',
|
||||
# Provides/Obsoletes cannot be part of this since we want to keep them
|
||||
# mixed, so we'll have to specify the key when needed
|
||||
'buildroot': 'BuildRoot',
|
||||
'buildarch': 'BuildArch'
|
||||
}
|
||||
|
||||
category_to_fixer = {
|
||||
}
|
||||
|
||||
license_fixes = {
|
||||
'LGPL v2.0 only': 'LGPLv2.0',
|
||||
'LGPL v2.0 or later': 'LGPLv2.0+',
|
||||
'LGPL v2.1 only': 'LGPLv2.1',
|
||||
'LGPL v2.1 or later': 'LGPLv2.1+',
|
||||
'LGPL v3 only': 'LGPLv3',
|
||||
'LGPL v3 or later': 'LGPLv3+',
|
||||
'GPL v2 only': 'GPLv2',
|
||||
'GPL v2 or later': 'GPLv2+',
|
||||
'GPL v3 only': 'GPLv3',
|
||||
'GPL v3 or later': 'GPLv3+'
|
||||
}
|
||||
|
||||
categories_order = [ 'name', 'version', 'release', 'license', 'summary', 'url', 'group', 'source', 'patch', 'buildrequires', 'prereq', 'requires', 'recommends', 'suggests', 'supplements', 'provides_obsoletes', 'buildroot', 'buildarch', 'misc' ]
|
||||
|
||||
categories_with_sorted_package_tokens = [ 'buildrequires', 'prereq', 'requires', 'recommends', 'suggests', 'supplements' ]
|
||||
categories_with_package_tokens = categories_with_sorted_package_tokens[:]
|
||||
categories_with_package_tokens.append('provides_obsoletes')
|
||||
|
||||
re_autoreqprov = re.compile('^\s*AutoReqProv:\s*on\s*$', re.IGNORECASE)
|
||||
|
||||
|
||||
def __init__(self):
|
||||
RpmSection.__init__(self)
|
||||
self._start_paragraph()
|
||||
|
||||
|
||||
def _start_paragraph(self):
|
||||
self.paragraph = {}
|
||||
for i in self.categories_order:
|
||||
self.paragraph[i] = []
|
||||
self.current_group = []
|
||||
|
||||
|
||||
def _add_group(self, group):
|
||||
t = type(group)
|
||||
|
||||
if t == str:
|
||||
RpmSection.add(self, group)
|
||||
elif t == list:
|
||||
for subgroup in group:
|
||||
self._add_group(subgroup)
|
||||
else:
|
||||
raise RpmException('Unknown type of group in preamble: %s' % t)
|
||||
|
||||
|
||||
def _end_paragraph(self):
|
||||
def sort_helper_key(a):
|
||||
t = type(a)
|
||||
if t == str:
|
||||
return a
|
||||
elif t == list:
|
||||
return a[-1]
|
||||
else:
|
||||
raise RpmException('Unknown type during sort: %s' % t)
|
||||
|
||||
for i in self.categories_order:
|
||||
if i in self.categories_with_sorted_package_tokens:
|
||||
self.paragraph[i].sort(key=sort_helper_key)
|
||||
for group in self.paragraph[i]:
|
||||
self._add_group(group)
|
||||
if self.current_group:
|
||||
# the current group was not added to any category. It's just some
|
||||
# random stuff that should be at the end anyway.
|
||||
self._add_group(self.current_group)
|
||||
|
||||
self._start_paragraph()
|
||||
|
||||
|
||||
def _fix_license(self, value):
|
||||
licenses = value.split(';')
|
||||
for (index, license) in enumerate(licenses):
|
||||
license = strip_useless_spaces(license)
|
||||
if self.license_fixes.has_key(license):
|
||||
license = self.license_fixes[license]
|
||||
licenses[index] = license
|
||||
|
||||
return [ ' ; '.join(licenses) ]
|
||||
|
||||
category_to_fixer['license'] = _fix_license
|
||||
|
||||
|
||||
def _fix_list_of_packages(self, value):
|
||||
if self.re_requires_token.match(value):
|
||||
tokens = [ item[1] for item in self.re_requires_token.findall(value) ]
|
||||
for (index, token) in enumerate(tokens):
|
||||
token = token.replace('%{version}-%{release}', '%{version}')
|
||||
tokens[index] = token
|
||||
|
||||
tokens.sort()
|
||||
return tokens
|
||||
else:
|
||||
return [ value ]
|
||||
|
||||
for i in categories_with_package_tokens:
|
||||
category_to_fixer[i] = _fix_list_of_packages
|
||||
|
||||
|
||||
def _add_line_value_to(self, category, value, key = None):
|
||||
"""
|
||||
Change a key-value line, to make sure we have the right spacing.
|
||||
|
||||
Note: since we don't have a key <-> category matching, we need to
|
||||
redo one. (Eg: Provides and Obsoletes are in the same category)
|
||||
"""
|
||||
keylen = len('BuildRequires: ')
|
||||
|
||||
if key:
|
||||
pass
|
||||
elif self.category_to_key.has_key(category):
|
||||
key = self.category_to_key[category]
|
||||
else:
|
||||
raise RpmException('Unhandled category in preamble: %s' % category)
|
||||
|
||||
key += ':'
|
||||
while len(key) < keylen:
|
||||
key += ' '
|
||||
|
||||
if self.category_to_fixer.has_key(category):
|
||||
values = self.category_to_fixer[category](self, value)
|
||||
else:
|
||||
values = [ value ]
|
||||
|
||||
for value in values:
|
||||
line = key + value
|
||||
self._add_line_to(category, line)
|
||||
|
||||
|
||||
def _add_line_to(self, category, line):
|
||||
if self.current_group:
|
||||
self.current_group.append(line)
|
||||
self.paragraph[category].append(self.current_group)
|
||||
self.current_group = []
|
||||
else:
|
||||
self.paragraph[category].append(line)
|
||||
|
||||
self.previous_line = line
|
||||
|
||||
|
||||
def add(self, line):
|
||||
if len(line) == 0:
|
||||
if not self.previous_line or len(self.previous_line) == 0:
|
||||
return
|
||||
|
||||
# we put the empty line in the current group (so we don't list it),
|
||||
# and write the paragraph
|
||||
self.current_group.append(line)
|
||||
self._end_paragraph()
|
||||
self.previous_line = line
|
||||
return
|
||||
|
||||
elif self.re_if.match(line):
|
||||
# %if/%else/%endif marks the end of the previous paragraph
|
||||
# We append the line at the end of the previous paragraph, though,
|
||||
# since it will stay at the end there. If putting it at the
|
||||
# beginning of the next paragraph, it will likely move (with the
|
||||
# misc category).
|
||||
self.current_group.append(line)
|
||||
self._end_paragraph()
|
||||
self.previous_line = line
|
||||
return
|
||||
|
||||
elif re_comment.match(line) or re_define.match(line):
|
||||
self.current_group.append(line)
|
||||
self.previous_line = line
|
||||
return
|
||||
|
||||
elif self.re_autoreqprov.match(line):
|
||||
return
|
||||
|
||||
elif self.re_source.match(line):
|
||||
match = self.re_source.match(line)
|
||||
self._add_line_value_to('source', match.group(2), key = 'Source%s' % match.group(1))
|
||||
return
|
||||
|
||||
elif self.re_patch.match(line):
|
||||
# FIXME: this is not perfect, but it's good enough for most cases
|
||||
if not self.previous_line or not re_comment.match(self.previous_line):
|
||||
self.current_group.append('# PATCH-MISSING-TAG -- See http://en.opensuse.org/Packaging/Patches')
|
||||
|
||||
match = self.re_patch.match(line)
|
||||
self._add_line_value_to('source', match.group(3), key = '%sPatch%s' % (match.group(1), match.group(2)))
|
||||
return
|
||||
|
||||
elif self.re_provides.match(line):
|
||||
match = self.re_provides.match(line)
|
||||
self._add_line_value_to('provides_obsoletes', match.group(1), key = 'Provides')
|
||||
return
|
||||
|
||||
elif self.re_obsoletes.match(line):
|
||||
match = self.re_obsoletes.match(line)
|
||||
self._add_line_value_to('provides_obsoletes', match.group(1), key = 'Obsoletes')
|
||||
return
|
||||
|
||||
elif self.re_buildroot.match(line):
|
||||
if len(self.paragraph['buildroot']) == 0:
|
||||
self._add_line_value_to('buildroot', '%{_tmppath}/%{name}-%{version}-build')
|
||||
return
|
||||
|
||||
else:
|
||||
for (category, regexp) in self.category_to_re.iteritems():
|
||||
match = regexp.match(line)
|
||||
if match:
|
||||
self._add_line_value_to(category, match.group(1))
|
||||
return
|
||||
|
||||
self._add_line_to('misc', line)
|
||||
|
||||
|
||||
def output(self, fout):
|
||||
self._end_paragraph()
|
||||
RpmSection.output(self, fout)
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
class RpmPackage(RpmPreamble):
|
||||
'''
|
||||
We handle this the same was as the preamble.
|
||||
'''
|
||||
|
||||
def add(self, line):
|
||||
# The first line (%package) should always be added and is different
|
||||
# from the lines we handle in RpmPreamble.
|
||||
if self.previous_line is None:
|
||||
RpmSection.add(self, line)
|
||||
return
|
||||
|
||||
RpmPreamble.add(self, line)
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
class RpmDescription(RpmSection):
|
||||
'''
|
||||
Only keep one empty line for many consecutive ones.
|
||||
Remove Authors from description.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
RpmSection.__init__(self)
|
||||
self.removing_authors = False
|
||||
# Tracks the use of a macro. When this happens and we're still in a
|
||||
# description, we actually don't know where we are so we just put all
|
||||
# the following lines blindly, without trying to fix anything.
|
||||
self.unknown_line = False
|
||||
|
||||
def add(self, line):
|
||||
lstrip = line.lstrip()
|
||||
if self.previous_line != None and len(lstrip) > 0 and lstrip[0] == '%':
|
||||
self.unknown_line = True
|
||||
|
||||
if self.removing_authors and not self.unknown_line:
|
||||
return
|
||||
|
||||
if len(line) == 0:
|
||||
if not self.previous_line or len(self.previous_line) == 0:
|
||||
return
|
||||
|
||||
if line == 'Authors:':
|
||||
self.removing_authors = True
|
||||
return
|
||||
|
||||
RpmSection.add(self, line)
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
class RpmPrep(RpmSection):
|
||||
'''
|
||||
Try to simplify to %setup -q when possible.
|
||||
'''
|
||||
|
||||
def add(self, line):
|
||||
if line.startswith('%setup'):
|
||||
cmp_line = line.replace(' -q', '')
|
||||
cmp_line = cmp_line.replace(' -n %{name}-%{version}', '')
|
||||
cmp_line = strip_useless_spaces(cmp_line)
|
||||
if cmp_line == '%setup':
|
||||
line = '%setup -q'
|
||||
|
||||
RpmSection.add(self, line)
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
class RpmBuild(RpmSection):
|
||||
'''
|
||||
Replace %{?jobs:-j%jobs} (suse-ism) with %{?_smp_mflags}
|
||||
'''
|
||||
|
||||
def add(self, line):
|
||||
if not re_comment.match(line):
|
||||
line = line.replace('%{?jobs:-j%jobs}' , '%{?_smp_mflags}')
|
||||
line = line.replace('%{?jobs: -j%jobs}', '%{?_smp_mflags}')
|
||||
|
||||
RpmSection.add(self, line)
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
class RpmInstall(RpmSection):
|
||||
'''
|
||||
Remove commands that wipe out the build root.
|
||||
Use %makeinstall macro.
|
||||
'''
|
||||
|
||||
re_autoreqprov = re.compile('^\s*AutoReqProv:\s*on\s*$', re.IGNORECASE)
|
||||
|
||||
def add(self, line):
|
||||
# remove double spaces when comparing the line
|
||||
cmp_line = strip_useless_spaces(line)
|
||||
cmp_line = replace_buildroot(cmp_line)
|
||||
|
||||
if cmp_line.find('DESTDIR=%{buildroot}') != -1:
|
||||
buf = cmp_line.replace('DESTDIR=%{buildroot}', '')
|
||||
buf = strip_useless_spaces(buf)
|
||||
if buf == 'make install':
|
||||
line = '%makeinstall'
|
||||
elif cmp_line == 'rm -rf %{buildroot}':
|
||||
return
|
||||
|
||||
if self.re_autoreqprov.match(line):
|
||||
return
|
||||
|
||||
RpmSection.add(self, line)
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
class RpmClean(RpmSection):
|
||||
pass
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
class RpmScriptlets(RpmSection):
|
||||
'''
|
||||
Do %post -p /sbin/ldconfig when possible.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
RpmSection.__init__(self)
|
||||
self.cache = []
|
||||
|
||||
|
||||
def add(self, line):
|
||||
if len(self.lines) == 0:
|
||||
if not self.cache:
|
||||
if line.find(' -p ') == -1 and line.find(' -f ') == -1:
|
||||
self.cache.append(line)
|
||||
return
|
||||
else:
|
||||
if line in ['', '/sbin/ldconfig' ]:
|
||||
self.cache.append(line)
|
||||
return
|
||||
else:
|
||||
for cached in self.cache:
|
||||
RpmSection.add(self, cached)
|
||||
self.cache = None
|
||||
|
||||
RpmSection.add(self, line)
|
||||
|
||||
|
||||
def output(self, fout):
|
||||
if self.cache:
|
||||
RpmSection.add(self, self.cache[0] + ' -p /sbin/ldconfig')
|
||||
RpmSection.add(self, '')
|
||||
|
||||
RpmSection.output(self, fout)
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
class RpmFiles(RpmSection):
|
||||
"""
|
||||
Replace additional /usr, /etc and /var because we're sure we can use
|
||||
macros there.
|
||||
|
||||
Replace '%dir %{_includedir}/mux' and '%{_includedir}/mux/*' with
|
||||
'%{_includedir}/mux/'
|
||||
"""
|
||||
|
||||
re_etcdir = re.compile('(^|\s)/etc/')
|
||||
re_usrdir = re.compile('(^|\s)/usr/')
|
||||
re_vardir = re.compile('(^|\s)/var/')
|
||||
|
||||
re_dir = re.compile('^\s*%dir\s*(\S+)\s*')
|
||||
|
||||
def __init__(self):
|
||||
RpmSection.__init__(self)
|
||||
self.dir_on_previous_line = None
|
||||
|
||||
|
||||
def add(self, line):
|
||||
line = self.re_etcdir.sub(r'\1%{_sysconfdir}/', line)
|
||||
line = self.re_usrdir.sub(r'\1%{_prefix}/', line)
|
||||
line = self.re_vardir.sub(r'\1%{_localstatedir}/', line)
|
||||
|
||||
if self.dir_on_previous_line:
|
||||
if line == self.dir_on_previous_line + '/*':
|
||||
RpmSection.add(self, self.dir_on_previous_line + '/')
|
||||
self.dir_on_previous_line = None
|
||||
return
|
||||
else:
|
||||
RpmSection.add(self, '%dir ' + self.dir_on_previous_line)
|
||||
self.dir_on_previous_line = None
|
||||
|
||||
match = self.re_dir.match(line)
|
||||
if match:
|
||||
self.dir_on_previous_line = match.group(1)
|
||||
return
|
||||
|
||||
RpmSection.add(self, line)
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
class RpmChangelog(RpmSection):
|
||||
'''
|
||||
Remove changelog entries.
|
||||
'''
|
||||
|
||||
def add(self, line):
|
||||
# only add the first line (%changelog)
|
||||
if len(self.lines) == 0:
|
||||
RpmSection.add(self, line)
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
class RpmSpecCleaner:
|
||||
|
||||
specfile = None
|
||||
fin = None
|
||||
fout = None
|
||||
current_section = None
|
||||
|
||||
|
||||
re_spec_package = re.compile('^%package\s*', re.IGNORECASE)
|
||||
re_spec_description = re.compile('^%description\s*', re.IGNORECASE)
|
||||
re_spec_prep = re.compile('^%prep\s*$', re.IGNORECASE)
|
||||
re_spec_build = re.compile('^%build\s*$', re.IGNORECASE)
|
||||
re_spec_install = re.compile('^%install\s*$', re.IGNORECASE)
|
||||
re_spec_clean = re.compile('^%clean\s*$', re.IGNORECASE)
|
||||
re_spec_scriptlets = re.compile('(?:^%pretrans\s*)|(?:^%pre\s*)|(?:^%post\s*)|(?:^%preun\s*)|(?:^%postun\s*)|(?:^%posttrans\s*)', re.IGNORECASE)
|
||||
re_spec_files = re.compile('^%files\s*', re.IGNORECASE)
|
||||
re_spec_changelog = re.compile('^%changelog\s*$', re.IGNORECASE)
|
||||
|
||||
|
||||
section_starts = [
|
||||
(re_spec_package, RpmPackage),
|
||||
(re_spec_description, RpmDescription),
|
||||
(re_spec_prep, RpmPrep),
|
||||
(re_spec_build, RpmBuild),
|
||||
(re_spec_install, RpmInstall),
|
||||
(re_spec_clean, RpmClean),
|
||||
(re_spec_scriptlets, RpmScriptlets),
|
||||
(re_spec_files, RpmFiles),
|
||||
(re_spec_changelog, RpmChangelog)
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, specfile, output, inline, force):
|
||||
if not specfile.endswith('.spec'):
|
||||
raise RpmException('%s does not appear to be a spec file.' % specfile)
|
||||
|
||||
if not os.path.exists(specfile):
|
||||
raise RpmException('%s does not exist.' % specfile)
|
||||
|
||||
self.specfile = specfile
|
||||
self.output = output
|
||||
self.inline = inline
|
||||
|
||||
self.fin = open(self.specfile)
|
||||
|
||||
if self.output:
|
||||
if not force and os.path.exists(self.output):
|
||||
raise RpmException('%s already exists.' % self.output)
|
||||
self.fout = open(self.output, 'w')
|
||||
elif self.inline:
|
||||
io = cStringIO.StringIO()
|
||||
while True:
|
||||
bytes = self.fin.read(500 * 1024)
|
||||
if len(bytes) == 0:
|
||||
break
|
||||
io.write(bytes)
|
||||
|
||||
self.fin.close()
|
||||
io.seek(0)
|
||||
self.fin = io
|
||||
self.fout = open(self.specfile, 'w')
|
||||
else:
|
||||
self.fout = sys.stdout
|
||||
|
||||
|
||||
def run(self):
|
||||
if not self.specfile or not self.fin:
|
||||
raise RpmException('No spec file.')
|
||||
|
||||
def _line_for_new_section(self, line):
|
||||
if isinstance(self.current_section, RpmCopyright):
|
||||
if not re_comment.match(line):
|
||||
return RpmPreamble
|
||||
|
||||
for (regexp, newclass) in self.section_starts:
|
||||
if regexp.match(line):
|
||||
return newclass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
self.current_section = RpmCopyright()
|
||||
|
||||
while True:
|
||||
line = self.fin.readline()
|
||||
if len(line) == 0:
|
||||
break
|
||||
# Remove \n to make it easier to parse things
|
||||
line = line[:-1]
|
||||
|
||||
new_class = _line_for_new_section(self, line)
|
||||
if new_class:
|
||||
self.current_section.output(self.fout)
|
||||
self.current_section = new_class()
|
||||
|
||||
self.current_section.add(line)
|
||||
|
||||
self.current_section.output(self.fout)
|
||||
|
||||
|
||||
def __del__(self):
|
||||
if self.fin:
|
||||
self.fin.close()
|
||||
self.fin = None
|
||||
if self.fout:
|
||||
self.fout.close()
|
||||
self.fout = None
|
||||
|
||||
|
||||
#######################################################################
|
||||
|
||||
|
||||
def main(args):
|
||||
parser = optparse.OptionParser(epilog='This script cleans spec file according to some arbitrary style guide. The results it produces should always be checked by someone since it is not and will never be perfect.')
|
||||
|
||||
parser.add_option("-i", "--inline", action="store_true", dest="inline",
|
||||
default=False, help="edit the file inline")
|
||||
parser.add_option("-o", "--output", dest="output",
|
||||
help="output file")
|
||||
parser.add_option("-f", "--force", action="store_true", dest="force",
|
||||
default=False, help="overwrite output file if already existing")
|
||||
parser.add_option("-v", "--version", action="store_true", dest="version",
|
||||
default=False, help="display version (" + VERSION + ")")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if options.version:
|
||||
print 'spec-cleaner ' + VERSION
|
||||
return 0
|
||||
|
||||
if len(args) != 1:
|
||||
print >> sys.stderr, '\nUsage:\n\tspec-cleaner file.spec\n'
|
||||
return 1
|
||||
|
||||
spec = os.path.expanduser(args[0])
|
||||
if options.output:
|
||||
options.output = os.path.expanduser(options.output)
|
||||
|
||||
if options.output == spec:
|
||||
options.output = ''
|
||||
options.inline = True
|
||||
|
||||
if options.output and options.inline:
|
||||
print >> sys.stderr, 'Conflicting options: --inline and --output.'
|
||||
return 1
|
||||
|
||||
try:
|
||||
cleaner = RpmSpecCleaner(spec, options.output, options.inline, options.force)
|
||||
cleaner.run()
|
||||
except RpmException, e:
|
||||
print >> sys.stderr, '%s' % e
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
res = main(sys.argv)
|
||||
sys.exit(res)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
5
spec-cleaner.changes
Normal file
5
spec-cleaner.changes
Normal file
@ -0,0 +1,5 @@
|
||||
-------------------------------------------------------------------
|
||||
Thu Sep 30 16:04:03 CEST 2010 - vuntz@opensuse.org
|
||||
|
||||
- Initial package (version 0.1)
|
||||
|
49
spec-cleaner.spec
Normal file
49
spec-cleaner.spec
Normal file
@ -0,0 +1,49 @@
|
||||
#
|
||||
# spec file for package spec-cleaner (version 0.1)
|
||||
#
|
||||
# Copyright (c) 2010 Vincent Untz <vuntz@opensuse.org>
|
||||
#
|
||||
# All modifications and additions to the file contributed by third parties
|
||||
# remain the property of their copyright owners, unless otherwise agreed
|
||||
# upon. The license for this file, and modifications and additions to the
|
||||
# file, is the same license as for the pristine package itself (unless the
|
||||
# license for the pristine package is not an Open Source License, in which
|
||||
# case the license is the MIT License). An "Open Source License" is a
|
||||
# license that conforms to the Open Source Definition (Version 1.9)
|
||||
# published by the Open Source Initiative.
|
||||
|
||||
# Please submit bugfixes or comments via http://bugs.opensuse.org/
|
||||
#
|
||||
|
||||
Name: spec-cleaner
|
||||
Version: 0.1
|
||||
Release: 1
|
||||
License: BSD 3-Clause
|
||||
Summary: .spec file cleaner
|
||||
Url: http://gitorious.org/opensuse/spec-cleaner
|
||||
Group: Development/Tools/Other
|
||||
Source0: spec-cleaner
|
||||
Requires: python-base
|
||||
BuildRoot: %{_tmppath}/%{name}-%{version}-build
|
||||
BuildArch: noarch
|
||||
|
||||
%description
|
||||
This script cleans spec file according to some arbitrary style guide. The
|
||||
results it produces should always be checked by someone since it is not and
|
||||
will never be perfect.
|
||||
|
||||
%prep
|
||||
|
||||
%build
|
||||
|
||||
%install
|
||||
install -D -m0755 %{S:0} %{buildroot}%{_bindir}/spec-cleaner
|
||||
|
||||
%clean
|
||||
%{__rm} -rf %{buildroot}
|
||||
|
||||
%files
|
||||
%defattr(-, root, root)
|
||||
%{_bindir}/spec-cleaner
|
||||
|
||||
%changelog
|
Loading…
Reference in New Issue
Block a user