Skip to content

Commits on Source 2

blends (0.6.102) experimental; urgency=low
* blend-gen-control: Bugfixes, slight refactorization, documentation
- Don't lower Recommends to Suggests in format 1.0
- Fail on backslashes in dependency lists
-- Ole Streicher <olebole@debian.org> Wed, 28 Mar 2018 08:49:42 +0200
blends (0.6.101) experimental; urgency=low
[ Andreas Tille ]
......
......@@ -40,21 +40,31 @@ from debian.deb822 import Deb822
class Blend:
'''Representation of a Debian Pure Blend.
'''
def __init__(self, basedir = '.', suppress_empty = True):
def __init__(self, basedir = '.'):
with open(os.path.join(basedir, 'debian', 'control.stub'), encoding="UTF-8") as fp:
self.control_stub = Deb822List(Deb822.iter_paragraphs(fp))
self.short_name = self.control_stub[0]['Source'].split('-', 1)[-1]
self.name = self.control_stub[0]['Source']
'''Full (package) name of the blend (``debian-astro``)'''
self.short_name = self.name.split('-', 1)[-1]
'''Short name of the blend (``astro``)'''
self.title = 'Debian ' + self.short_name.capitalize()
if len(self.control_stub) > 1:
self.prefix = self.control_stub[1]['Package'].split('-', 1)[0]
else:
'''Blends title (``Debian Astro``)'''
base_deps = [ "${misc:Depends}", ]
self.prefix = self.short_name
'''Prefix for tasks (``astro``)'''
for pkg in self.control_stub[1:]:
p = pkg['Package'].split('-', 1)
if len(p) > 1 and p[1] == 'tasks':
self.prefix = p[0]
base_deps.append("{Package} (= ${{source:Version}})".format(**pkg))
break
base_deps = [
"${misc:Depends}",
"{}-tasks (= ${{source:Version}})".format(self.prefix),
]
try:
with open(os.path.join(basedir, 'config', 'control'), encoding="UTF-8") as fp:
self.control_stub.append(Deb822(fp))
......@@ -64,12 +74,11 @@ class Blend:
pass
self.tasks = []
self.suppress_empty = suppress_empty
'''``Task`` list'''
for name in sorted(filter(lambda n: n[-1] != '~',
os.listdir(os.path.join(basedir, 'tasks')))):
with open(os.path.join(basedir, 'tasks', name), encoding="UTF-8") as fp:
task = Task(self.name, self.prefix, name, fp,
base_deps = base_deps)
task = Task(self, name, fp, base_deps = base_deps)
self.tasks.append(task)
def update(self, cache):
......@@ -102,21 +111,16 @@ class Blend:
missing += task.fix_dependencies()
return missing
@property
def control(self):
def gen_control(self):
'''Return the task as list of ``Deb822`` objects suitable for
``debian/control``
'''
tasks = list(filter(lambda task: task.is_metapackage
and (not self.suppress_empty
or len(task.recommends) > 0
or len(task.dependencies) > 0),
self.tasks))
tasks = list(filter(lambda task: task.is_metapackage, self.tasks))
# Create the special 'all' task recommending all tasks that
# shall be installed by default
all_task = Task(
self.name, self.prefix, "all",
self, "all",
'''Description: Default selection of tasks for {task.title}
This package is part of the {task.title} Pure Blend and installs all
tasks for a default installation of this blend.'''.format(task=self),
......@@ -131,42 +135,23 @@ class Blend:
if len(all_task.recommends) > 0:
tasks.insert(0, all_task)
return Deb822List(self.control_stub + [ task.control for task in tasks ])
return Deb822List(self.control_stub
+ [ task.gen_control() for task in tasks ])
@property
def task_desc(self):
def gen_task_desc(self, udeb = False):
'''Return the task as list of ``Deb822`` objects suitable for
``blends-task.desc``
'''
tasks = list(filter(lambda task:
task.is_metapackage
and task.is_leaf
and (not self.suppress_empty
or len(task.recommends) > 0
or len(task.dependencies) > 0),
tasks = list(filter(lambda task: task.is_metapackage and task.is_leaf,
self.tasks))
header = Deb822({
header = [ Deb822({
'Task': self.name,
'Relevance': '7',
'Section': self.name,
'Description': '{} Pure Blend\n .'.format(self.title),
})
return Deb822List([ header ] + [ task.task_desc for task in tasks ])
@property
def udeb_task_desc(self):
'''Return the task as list of ``Deb822`` objects suitable for
``blends-task.desc`` in case the blend uses udebs
'''
tasks = list(filter(lambda task:
task.is_metapackage
and task.is_leaf
and (not self.suppress_empty
or len(task.recommends) > 0
or len(task.dependencies) > 0),
self.tasks))
return Deb822List([ task.udeb_task_desc for task in tasks ])
}) ] if not udeb else []
return Deb822List(header + [ task.gen_task_desc(udeb) for task in tasks ])
class Task:
......@@ -175,9 +160,7 @@ class Task:
The Version class contains all information related to a
specific package version of a blends task.
:param blend: Name of the blend
:param prefix: Prefix for the package name
:param blend: ``Blend`` object, or Blend name
:param name: Name of the task
......@@ -198,7 +181,7 @@ class Task:
Example:
>>> with open('education') as fp:
... task = Task('debian-astro', 'astro', 'education', fp)
... task = Task('debian-astro', 'education', fp)
>>> print(task.name)
education
>>> print(task.package_name)
......@@ -223,16 +206,33 @@ class Task:
xtide
'''
def __init__(self, blend, prefix, name, sequence, base_deps = None):
def __init__(self, blend, name, sequence, base_deps = None):
if isinstance(blend, str):
self.blend = blend
self.prefix = prefix
'''Blend name'''
self.prefix = blend[len('debian-'):] if blend.startswith('debian-') else blend
'''Metapackage prefix'''
else:
self.blend = blend.name
self.prefix = blend.prefix
self.name = name
'''Task name'''
self.content = Deb822List(Deb822.iter_paragraphs(sequence))
'''Deb822List content of the task'''
self.header = self.content[0]
'''Deb822 header'''
self.base_deps = base_deps or []
'''Base dependencies'''
# Check for the format version, and upgrade if not actual
self.format_upgraded = False
'''``True`` if the format was upgraded from an older version'''
if 'Format' in self.header:
self.format_version = self.header['Format'].strip().rsplit('/', 1)[-1]
else:
......@@ -241,6 +241,7 @@ class Task:
self.content = Task.upgrade_from_1_0(self.content)
self.format_upgraded = True
# Create package dependencies
dep_types = ["Depends", "Recommends", "Suggests"]
dep_attrs = [ "dependencies", "recommends", "suggests" ]
for dep_type, dep_attr in zip(dep_types, dep_attrs):
......@@ -248,6 +249,10 @@ class Task:
*(list(Dependency(dep_type, s.strip(), par)
for s in par.get(dep_type, '').split(",") if s)
for par in self.content[1:]))))
self.enhances = [
Dependency('Enhances', s.strip(), self.header)
for s in self.header.get('Enhances', '').split(",") if s
]
@property
def install(self):
......@@ -308,13 +313,6 @@ class Task:
'''
return self.header.get('Architecture', 'all')
@property
def enhances(self):
'''Return the task name this task is an enhancement of,
or `None`.
'''
return self.header.get('Enhances')
@property
def tests(self):
'''Return all tests for this task when included in tasksel
......@@ -335,13 +333,12 @@ class Task:
self.recommends,
self.suggests)))
@property
def control(self):
def gen_control(self):
'''Return the task as ``Deb822`` object suitable for ``debian/control``
>>> with open('education') as fp:
... task = Task('debian-astro', 'astro', 'education', fp)
>>> print(task.control.dump())
... task = Task('debian-astro', 'education', fp)
>>> print(task.gen_control().dump())
Package: astro-education
Section: metapackages
Architecture: all
......@@ -377,13 +374,15 @@ class Task:
"\n ".join(self.description.replace("\n\n", "\n.\n").split("\n"))
return d
@property
def task_desc(self):
def gen_task_desc(self, udeb = False):
'''Return the task as ``Deb822`` object suitable for ``blends-task.desc``.
:parameter udeb: if ``True``, generate ```blends-task.desc``
suitable for udebs
>>> with open('education') as fp:
... task = Task('debian-astro', 'astro', 'education', fp)
>>> print(task.task_desc.dump())
... task = Task('debian-astro', 'education', fp)
>>> print(task.gen_task_desc().dump())
Task: astro-education
Parent: debian-astro
Section: debian-astro
......@@ -395,17 +394,7 @@ class Task:
Key:
astro-education
<BLANKLINE>
'''
return self._get_task_desc(udeb = False)
@property
def udeb_task_desc(self):
'''Return the task as ``Deb822`` object suitable for ``blends-task.desc``
in case the blend uses udebs
>>> with open('education') as fp:
... task = Task('debian-astro', 'astro', 'education', fp)
>>> print(task.udeb_task_desc.dump())
>>> print(task.gen_task_desc(udeb=True).dump())
Task: astro-education
Section: debian-astro
Description: Educational astronomy applications
......@@ -422,10 +411,8 @@ class Task:
gravit
starplot
<BLANKLINE>
'''
return self._get_task_desc(udeb = True)
def _get_task_desc(self, udeb = False):
'''
d = Deb822()
d['Task'] = self.package_name
if not udeb:
......@@ -436,7 +423,8 @@ class Task:
if udeb:
d['Relevance'] = '10'
if self.enhances:
d['Enhances'] = self.enhances
d['Enhances'] = ', '.join(sorted(d.name for d in itertools.chain(
*self.enhances)))
for key, value in self.tests.items():
d['Test-' + key] = value
d['Key'] = '\n {}'.format(self.package_name)
......@@ -458,7 +446,7 @@ class Task:
>>> import apt
>>> with open('education') as fp:
... task = Task('debian-astro', 'astro', 'education', fp)
... task = Task('debian-astro', 'education', fp)
>>> dep = task.recommends[1][0]
>>> print(dep.name + ": ", dep.summary)
starplot: None
......@@ -485,7 +473,7 @@ class Task:
>>> import apt
>>> with open('education') as fp:
... task = Task('debian-astro', 'astro', 'education', fp)
... task = Task('debian-astro', 'education', fp)
>>> for dep in task.recommends:
... print(dep.rawstr)
celestia-gnome | celestia-glut
......@@ -508,17 +496,17 @@ class Task:
celestia-gnome | celestia-glut
'''
missing = list()
for dep in self.recommends[:]:
sdep = Dependency("Suggests")
for odep in dep[:]:
if len(odep.target_versions) == 0:
dep.remove(odep)
sdep.append(odep)
missing.append(odep)
if len(dep) == 0:
self.recommends.remove(dep)
if len(sdep) > 0:
self.suggests.append(sdep)
for recommended in self.recommends[:]:
suggested = Dependency("Suggests")
for dep in recommended[:]:
if len(dep.target_versions) == 0:
recommended.remove(dep)
suggested.append(dep)
missing.append(dep)
if len(recommended) == 0:
self.recommends.remove(recommended)
if len(suggested) > 0:
self.suggests.append(suggested)
return missing
@staticmethod
......@@ -529,9 +517,7 @@ class Task:
for p in content[1:]:
q = []
for key, value in p.items():
if key == 'Recommends':
key = 'Suggests'
elif key == 'Depends':
if key == 'Depends' and 'Recommends' not in p:
key = 'Recommends'
# Remove backslashes, which are not DEB822 compliant
value = re.sub(r'\s*\\', '', value)
......@@ -546,7 +532,7 @@ class Dependency(list):
Example:
>>> with open('education') as fp:
... task = Task('debian-astro', 'astro', 'education', fp)
... task = Task('debian-astro', 'education', fp)
>>> dep = task.recommends[0]
>>> print(dep.rawstr)
celestia-gnome | celestia-glut
......@@ -586,7 +572,7 @@ class BaseDependency:
Example:
>>> with open('education') as fp:
... task = Task('debian-astro', 'astro', 'education', fp)
... task = Task('debian-astro', 'education', fp)
>>> dep = task.recommends[2][0]
>>> print(dep.rawstr)
gravit
......@@ -604,6 +590,10 @@ class BaseDependency:
'''
def __init__(self, s, content = None):
r = re.compile(r'([a-z0-9][a-z0-9+-\.]+)')
m = r.match(s)
if m is None or m.string != s:
raise ValueError('"{}" is not a valid package name'.format(s))
self.name = s
self.content = content or dict()
self.target_versions = []
......@@ -681,10 +671,10 @@ class Deb822List(list):
def dump(self, fd=None, encoding=None, text_mode=False):
'''Dump the the contents in the original format
If ``fd`` is ``None``, returns a ``unicode`` object.
Otherwise, ``fd`` is assumed to be a ``file``-like object, and
this method will write the data to it instead of returning a
``unicode`` object.
If ``fd`` is ``None``, returns a ``str`` object. Otherwise,
``fd`` is assumed to be a ``file``-like object, and this
method will write the data to it instead of returning an
``str`` object.
If ``fd`` is not ``None`` and ``text_mode`` is ``False``, the
data will be encoded to a byte string before writing to the
......@@ -784,7 +774,7 @@ if __name__ == '__main__':
if args.release == "current":
args.release = None
blend = Blend(basedir=args.dir, suppress_empty = args.suppressempty)
blend = Blend(basedir=args.dir)
# For better performance, remove all tasks that will not create metapackages
for task in blend.tasks[:]:
......@@ -794,6 +784,11 @@ if __name__ == '__main__':
blend += aptcache(args.release)
missing = blend.fix_dependencies()
if args.suppressempty:
for task in blend.tasks[:]:
if len(task.recommends) == 0 and len(task.dependencies) > 0:
blend.tasks.remove(task)
if missing and args.missing:
missing = sorted(set(d.name for d in missing))
print('Missing {} packages downgraded to `suggests`:\n '
......@@ -811,21 +806,13 @@ if __name__ == '__main__':
if args.gencontrol:
with open(os.path.join(args.dir,'debian', 'control'), 'w') as fp:
fp.write('# This file is autogenerated. Do not edit!\n')
blend.control.dump(fp, text_mode=True)
blend.gen_control().dump(fp, text_mode=True)
if args.taskdesc and not args.udebs:
if args.taskdesc:
with open(os.path.join(args.dir,'{}-tasks.desc'.format(blend.name)), 'w') as fp:
blend.task_desc.dump(fp, text_mode=True)
if args.taskdesc and args.udebs:
with open(os.path.join(args.dir,'{}-tasks.desc'.format(blend.name)), 'w') as fp:
blend.udeb_task_desc.dump(fp, text_mode=True)
upgraded_tasks = []
for task in blend.tasks:
if task.format_upgraded:
upgraded_tasks.append(task)
blend.gen_task_desc(udeb = args.udebs).dump(fp, text_mode=True)
upgraded_tasks = list(filter(lambda task: task.format_upgraded, blend.tasks))
if upgraded_tasks:
if args.upgrade_tasks:
print('Upgrading {} tasks from format version {}'
......