- Use f-strings - Reformat with black - Don’t attempt to reuse zip object to fix obsoletes management - Don’t expect group tag [skip changelog] Signed-off-by: Nils Philippsen <nils@redhat.com>
276 lines
8 KiB
Python
Executable file
276 lines
8 KiB
Python
Executable file
#!/usr/bin/python3
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# apply-languages.py -- generate language subpackages from LANGUAGES comment
|
|
# in RPM spec files
|
|
# Copyright © 2012, 2014 Red Hat, Inc.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# Author: Nils Philippsen <nils@redhat.com>
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import atexit
|
|
import re
|
|
from stat import S_IMODE
|
|
from itertools import islice
|
|
|
|
|
|
def usage():
|
|
print(f"Usage: {sys.argv[0]} <packagename>.spec", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def cleantmpfile():
|
|
global newfspath
|
|
|
|
try:
|
|
os.unlink(newsfpath)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
if len(sys.argv) != 2:
|
|
usage()
|
|
|
|
sfpath = sys.argv[1]
|
|
if not os.access(sfpath, os.R_OK | os.W_OK):
|
|
print(f"Not readable/writable: {sfpath}", file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
sfdir = os.path.dirname(sfpath)
|
|
sfbase = os.path.basename(sfpath)
|
|
|
|
(fd, newsfpath) = tempfile.mkstemp(prefix=sfbase + ".", dir=sfdir)
|
|
atexit.register(cleantmpfile)
|
|
newsf = os.fdopen(fd, "w")
|
|
|
|
sf = open(sfpath)
|
|
sfcontent = sf.read()
|
|
sf.close()
|
|
|
|
sfmode = S_IMODE(os.stat(sfpath).st_mode)
|
|
|
|
languages_re = re.compile(
|
|
r"^#\s*LANGUAGES:\s*(?P<languages>[^\n\r]+)\s*$", re.MULTILINE
|
|
)
|
|
langsplit_re = re.compile(r"\s+")
|
|
begin_obsoletes_re = re.compile(
|
|
r"^#\s*BEGIN:\s*OBSOLETE\s+LANGUAGES\s*$", re.MULTILINE
|
|
)
|
|
end_obsoletes_re = re.compile(
|
|
r"^#\s*END:\s*OBSOLETE\s+LANGUAGES\s*$", re.MULTILINE
|
|
)
|
|
begin_langpkgs_re = re.compile(
|
|
r"^#\s*BEGIN:\s*LANGUAGE\s+SUB\s+PACKAGES\s*$", re.MULTILINE
|
|
)
|
|
end_langpkgs_re = re.compile(
|
|
r"^#\s*END:\s*LANGUAGE\s+SUB\s+PACKAGES\s*$", re.MULTILINE
|
|
)
|
|
begin_langfiles_re = re.compile(
|
|
r"^#\s*BEGIN:\s*LANGUAGE\s+FILE\s+LISTS\s*$", re.MULTILINE
|
|
)
|
|
end_langfiles_re = re.compile(
|
|
r"^#\s*END:\s*LANGUAGE\s+FILE\s+LISTS\s*$", re.MULTILINE
|
|
)
|
|
|
|
name_re = re.compile(
|
|
r"^name:\s*(?P<name>[^\n\r]+)\s*$", re.MULTILINE | re.IGNORECASE
|
|
)
|
|
version_re = re.compile(
|
|
r"^version:\s*(?P<version>[^\n\r]+)\s*$", re.MULTILINE | re.IGNORECASE
|
|
)
|
|
release_re = re.compile(
|
|
r"^release:\s*(?P<release>[^\n\r]+)\s*$", re.MULTILINE | re.IGNORECASE
|
|
)
|
|
license_re = re.compile(
|
|
r"^license:\s*(?P<license>[^\n\r]+)\s*$", re.MULTILINE | re.IGNORECASE
|
|
)
|
|
pkg_re = re.compile(
|
|
r"^%package\s*(?P<pkg>\S+)\s*$", re.MULTILINE | re.IGNORECASE
|
|
)
|
|
|
|
missing = False
|
|
for what, what_re in (
|
|
("name tag", name_re),
|
|
("license tag", license_re),
|
|
("LANGUAGES comment", languages_re),
|
|
("BEGIN: OBSOLETE LANGUAGES comment", begin_obsoletes_re),
|
|
("END: OBSOLETE LANGUAGES comment", end_obsoletes_re),
|
|
("BEGIN: LANGUAGE SUB PACKAGES comment", begin_langpkgs_re),
|
|
("END: LANGUAGE SUB PACKAGES comment", end_langpkgs_re),
|
|
("BEGIN: LANGUAGE FILE LISTS comment", begin_langfiles_re),
|
|
("END: LANGUAGE FILE LISTS comment", end_langfiles_re),
|
|
):
|
|
found = what_re.search(sfcontent)
|
|
if found is None:
|
|
print(f"{what} not found", file=sys.stderr)
|
|
missing = True
|
|
if missing:
|
|
sys.exit(2)
|
|
|
|
langspecs = langsplit_re.split(
|
|
languages_re.search(sfcontent).group("languages")
|
|
)
|
|
# languages = []
|
|
# for ls in langspecs:
|
|
# langcode, langname = ls.split(",")
|
|
# languages.append((langcode, langname))
|
|
languages = [
|
|
(x.split(",")[0], x.split(",")[1].replace("_", " ")) for x in langspecs
|
|
]
|
|
langcodes = set((x[0] for x in languages))
|
|
name = name_re.search(sfcontent).group("name")
|
|
version = version_re.search(sfcontent).group("version")
|
|
release = release_re.search(sfcontent).group("release")
|
|
license = license_re.search(sfcontent).group("license")
|
|
|
|
obsoletes_re = re.compile(
|
|
fr"^obsoletes:\s*{name}-(?P<lang_ver>(?P<lang>\S+)\s*.*)$",
|
|
re.MULTILINE | re.IGNORECASE,
|
|
)
|
|
conflicts_re = re.compile(
|
|
fr"^conflicts:\s*{name}-(?P<lang_ver>(?P<lang>\S+)\s*.*)$",
|
|
re.MULTILINE | re.IGNORECASE,
|
|
)
|
|
|
|
numlang = len(languages)
|
|
replacing = None
|
|
|
|
sflines = sfcontent.split("\n")
|
|
if sflines[-1] == "":
|
|
del sflines[-1]
|
|
|
|
# handle obsoleting language subpackages
|
|
preprocess_state_transitions = {
|
|
"out": (
|
|
begin_obsoletes_re,
|
|
"in_obsoletes",
|
|
begin_langpkgs_re,
|
|
"in_langpkgs",
|
|
),
|
|
"in_obsoletes": (end_obsoletes_re, "out"),
|
|
"in_langpkgs": (end_langpkgs_re, "out"),
|
|
}
|
|
|
|
state = "out"
|
|
state_change = True
|
|
|
|
found_obsoleted_langs = set()
|
|
found_lang_pkgs = set()
|
|
|
|
for line in sflines:
|
|
if state_change:
|
|
transitions = preprocess_state_transitions[state]
|
|
packed_transitions = list(
|
|
zip(
|
|
islice(transitions, 0, None, 2),
|
|
islice(transitions, 1, None, 2),
|
|
)
|
|
)
|
|
state_change = False
|
|
|
|
for regex, new_state in packed_transitions:
|
|
if regex.match(line):
|
|
state_change = True
|
|
state = new_state
|
|
break
|
|
if state_change:
|
|
continue
|
|
|
|
if state == "in_obsoletes":
|
|
m = obsoletes_re.match(line)
|
|
if m:
|
|
found_obsoleted_langs.add(m.group("lang"))
|
|
elif state == "in_langpkgs":
|
|
m = pkg_re.match(line)
|
|
if m:
|
|
found_lang_pkgs.add(m.group("pkg"))
|
|
|
|
langcodes_to_obsolete = found_lang_pkgs - langcodes
|
|
langcodes_to_unobsolete = found_obsoleted_langs & langcodes
|
|
|
|
gobble = False
|
|
# update spec file
|
|
for line in sflines:
|
|
if not replacing:
|
|
print(line, file=newsf)
|
|
if begin_obsoletes_re.match(line):
|
|
replacing = "obsoletes"
|
|
elif begin_langpkgs_re.match(line):
|
|
replacing = "langpkgs"
|
|
elif begin_langfiles_re.match(line):
|
|
replacing = "langfiles"
|
|
elif replacing == "obsoletes":
|
|
om = obsoletes_re.match(line)
|
|
cm = conflicts_re.match(line)
|
|
em = end_obsoletes_re.match(line)
|
|
if em:
|
|
gobble = False
|
|
replacing = None
|
|
for lang in langcodes_to_obsolete:
|
|
print(
|
|
f"Obsoletes: {name}-{lang} < {version}-{release}\n"
|
|
+ f"Conflicts: {name}-{lang} < {version}-{release}",
|
|
file=newsf,
|
|
)
|
|
print(line, file=newsf)
|
|
elif not (om or cm):
|
|
if not gobble:
|
|
print(line, file=newsf)
|
|
elif (
|
|
om
|
|
and om.group("lang") not in langcodes_to_unobsolete
|
|
or cm
|
|
and cm.group("lang") not in langcodes_to_unobsolete
|
|
):
|
|
gobble = False
|
|
print(line, file=newsf)
|
|
else:
|
|
gobble = True
|
|
elif replacing == "langpkgs":
|
|
if end_langpkgs_re.match(line):
|
|
replacing = None
|
|
for no, lang in enumerate(languages):
|
|
langcode, langname = lang
|
|
print(
|
|
f"""%package {langcode}
|
|
Summary: {langname} ({langcode}) language support for {name}
|
|
Requires: %{{name}} = %{{?epoch:%{{epoch}}:}}%{{version}}-%{{release}}
|
|
Supplements: (%{{name}} = %{{?epoch:%{{epoch}}:}}%{{version}}-%{{release}}"""
|
|
+ f""" and langpacks-{langcode})
|
|
|
|
%description {langcode}
|
|
{langname} language support for {name}.""",
|
|
file=newsf,
|
|
)
|
|
if no < numlang:
|
|
print(file=newsf)
|
|
print(line, file=newsf)
|
|
elif replacing == "langfiles":
|
|
if end_langfiles_re.match(line):
|
|
replacing = None
|
|
for lang in languages:
|
|
langcode, langname = lang
|
|
print(
|
|
f"%files {langcode} -f files.list.{langcode}", file=newsf
|
|
)
|
|
print(line, file=newsf)
|
|
|
|
newsf.close()
|
|
|
|
os.rename(newsfpath, sfpath)
|
|
os.chmod(sfpath, sfmode)
|