[Blends-commit] [Git][blends-team/blends][master] 40 commits: Replace blend-gen-control with a Python implementation
Ole Streicher
gitlab at salsa.debian.org
Fri Apr 13 19:51:40 BST 2018
Ole Streicher pushed to branch master at Debian Blends Team / blends
Commits:
3add4f13 by Ole Streicher at 2018-03-24T16:54:34+01:00
Replace blend-gen-control with a Python implementation
- - - - -
35533d90 by Ole Streicher at 2018-03-24T16:54:34+01:00
Update changelog for 0.6.101 release
- - - - -
f4c0f012 by Ole Streicher at 2018-03-28T08:46:02+02:00
Fix some problems, and refactorization of blend-gen-control
* Don't lower Recommends to Suggests on format 1.0
* Don't allow backslashes in Recommends/Suggests/Depends in format 1.1
- - - - -
3120dc33 by Ole Streicher at 2018-03-28T08:52:24+02:00
Update changelog for 0.6.102 release
- - - - -
6caa8085 by Andreas Tille at 2018-03-28T16:23:53+02:00
Try hard to get lintian error "blends-dev: python-script-but-no-python-dep usr/share/blends-dev/blend-gen-control" solved - no idea why this failed.
- - - - -
da90c543 by Andreas Tille at 2018-03-28T16:24:41+02:00
ignore false lintian warning binary-package-depends-on-toolchain-package
- - - - -
a43a2bac by Andreas Tille at 2018-03-28T16:49:06+02:00
cme fix dpkg-control
- - - - -
61c13e4c by Andreas Tille at 2018-03-28T17:12:12+02:00
Take over tasks_diff from blends-gsoc to create dependency_data
- - - - -
eee797ae by Andreas Tille at 2018-03-28T19:58:29+02:00
Fix path of dependency_data for changelog creation
- - - - -
4fae955d by Andreas Tille at 2018-03-28T20:25:49+02:00
Use Deb822List to evaluate taskprefix rather than the old error prone method.
- - - - -
c611dc44 by Andreas Tille at 2018-03-29T08:44:07+02:00
Sort tasks alphabethically in changelog
- - - - -
dd6e6fe9 by Andreas Tille at 2018-03-29T08:45:14+02:00
Spacing
- - - - -
0fb78e41 by Ole Streicher at 2018-03-29T21:33:17+02:00
Add UDD query options to blend-gen-control
- - - - -
24faa7f3 by Ole Streicher at 2018-03-29T21:35:14+02:00
Update changelog for 0.6.103 release
- - - - -
ed526453 by Ole Streicher at 2018-03-29T21:41:18+02:00
Add python3 dep, and use python3 instead of python3-all in build-dep
- - - - -
4879c3f3 by Ole Streicher at 2018-03-29T21:42:10+02:00
Remove trailing whitespaces in d/changelog
- - - - -
3df36e29 by Ole Streicher at 2018-03-29T21:43:50+02:00
Update d/copyright of blends-gen-control (rewritten from scratch)
- - - - -
de5e5a03 by Andreas Tille at 2018-04-03T15:51:37+02:00
Do not mention subversion any more
- - - - -
e2c1bc3f by Andreas Tille at 2018-04-03T16:04:29+02:00
s/alioth/salsa/
- - - - -
3228dff2 by Andreas Tille at 2018-04-03T16:56:27+02:00
Description how Blends relevant data are gathered and stored
- - - - -
204bedb2 by Ole Streicher at 2018-04-09T16:43:14+02:00
Fix help for --taskdesc option
- - - - -
67e426ba by Ole Streicher at 2018-04-10T12:54:21+02:00
Fix documentation of lowering priorities
- - - - -
876a6be3 by Ole Streicher at 2018-04-10T13:15:49+02:00
Recommend python3-psycopg2 and note it in the help
- - - - -
fb15ffe0 by Ole Streicher at 2018-04-10T15:10:50+02:00
Create and use python3-blends package
- - - - -
fc9fb8db by Andreas Tille at 2018-04-10T16:08:47+02:00
Standards-Version: 4.1.4
- - - - -
10de8200 by Andreas Tille at 2018-04-10T16:31:34+02:00
Use blends.py
- - - - -
dc6f7bc9 by Ole Streicher at 2018-04-10T17:00:51+02:00
blends-gen-control: read defaults from GENCONTROL_OPTS env var
- - - - -
e39204f2 by Ole Streicher at 2018-04-10T17:51:27+02:00
Fix suppression of empty tasks
- - - - -
b2bd2dff by Ole Streicher at 2018-04-10T21:33:51+02:00
Restrict UDD query to "main" component
- - - - -
61fcc481 by Ole Streicher at 2018-04-11T08:50:20+02:00
Move distribution name fix to blends-dev Makefile
The idea is to have a consistent and transparent behaviour between udd
and apt update methods.
- - - - -
1a649bf1 by Ole Streicher at 2018-04-11T11:40:40+02:00
Oops. Forgot to reset distribution for 'unstable'
- - - - -
41c7deb4 by Ole Streicher at 2018-04-12T14:55:43+02:00
blends.py: Normalize "provides" column in UDD query
- - - - -
1c01f7f7 by Ole Streicher at 2018-04-12T16:32:35+02:00
blends.py: Handle versionized provides
Since some time, provides may be versionized. They are handled here by
just ignoring them.
- - - - -
af3096a3 by Ole Streicher at 2018-04-13T09:27:57+02:00
blends.py: escape backslashes in sql query
- - - - -
76f535f5 by Ole Streicher at 2018-04-13T09:28:39+02:00
blend-gen-control: help output fix
- - - - -
96643c65 by Ole Streicher at 2018-04-13T12:58:51+02:00
Remove empty GENCONTROL_OPTS definition from blends-dev/Makefile
- - - - -
dc702a0a by Ole Streicher at 2018-04-13T17:35:21+02:00
Improve program output
- - - - -
43ca1b83 by Ole Streicher at 2018-04-13T17:37:14+02:00
Fix file name in tasks gen stdout
- - - - -
d15041a4 by Ole Streicher at 2018-04-13T20:48:07+02:00
Merge branch 'experimental'
- - - - -
d79e49e0 by Ole Streicher at 2018-04-13T20:48:24+02:00
Update changelog for 0.7.0 release
- - - - -
17 changed files:
- + blends.py
- + debian/blends-dev.lintian-overrides
- debian/changelog
- debian/control
- debian/copyright
- + debian/python3-blends.doc-base
- + debian/python3-blends.docs
- + debian/python3-blends.pyinstall
- debian/rules
- devtools/Makefile
- devtools/blend-gen-control
- + devtools/tasks_diff
- − sources.list.UNRELEASED
- sources.list.unstable
- + sphinxdoc/Makefile
- + sphinxdoc/conf.py
- + sphinxdoc/index.rst
Changes:
=====================================
blends.py
=====================================
--- /dev/null
+++ b/blends.py
@@ -0,0 +1,836 @@
+'''A module to handle Debian Pure Blends tasks, modelled after apt.package.
+
+The examples use the following sample tasks file:
+
+>>> sample_task = """Format: https://blends.debian.org/blends/1.1
+... Task: Education
+... Install: true
+... Description: Educational astronomy applications
+... Various applications that can be used to teach astronomy.
+... .
+... This is however incomplete.
+...
+... Recommends: celestia-gnome | celestia-glut, starplot
+...
+... Recommends: gravit
+... WNPP: 743379
+... Homepage: http://gravit.slowchop.com/
+... Pkg-Description: Visually stunning gravity simulator
+... Gravit is a free, visually stunning gravity simulator.
+... .
+... You can spend endless time experimenting with various
+... configurations of simulated universes.
+... Why: Useful package
+... Remark: Entered Debian in 2014
+...
+... Suggests: sunclock, xtide
+... """
+>>> with open('education', 'w') as fp:
+... nbytes = fp.write(sample_task)
+'''
+import io
+import os
+import itertools
+import re
+import shutil
+from debian.deb822 import Deb822
+
+
+class Blend:
+ '''Representation of a Debian Pure Blend.
+ '''
+ 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.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()
+ '''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
+
+ try:
+ with open(os.path.join(basedir, 'config', 'control'),
+ encoding="UTF-8") as fp:
+ self.control_stub.append(Deb822(fp))
+ base_deps.append("{}-config (= ${{source:Version}})"
+ .format(self.prefix))
+ except IOError:
+ pass
+
+ self.tasks = []
+ '''``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, fp, base_deps=base_deps)
+ self.tasks.append(task)
+
+ def update(self, cache):
+ '''Update from cache
+
+ :param cache: ``apt.Cache`` like object
+
+ This adds the available versions to all dependencies. It
+ updates descriptions, summaries etc. available to all
+ BaseDependencies in all tasks.
+
+ Instead of using ``update()``, also the ``+=`` operator can be used.
+
+ '''
+ for task in self.tasks:
+ task += cache
+
+ def __iadd__(self, cache):
+ self.update(cache)
+ return self
+
+ @property
+ def all(self):
+ '''All Base Dependencies of this task
+ '''
+ return list(itertools.chain(*(t.all for t in self.tasks)))
+
+ def fix_dependencies(self):
+ '''Fix the dependencies according to available packages
+
+ This lowers all unavailable ``recommended`` dependencies to
+ ``suggested``.
+ '''
+ missing = []
+ for task in self.tasks:
+ missing += task.fix_dependencies()
+ return missing
+
+ def gen_control(self):
+ '''Return the task as list of ``Deb822`` objects suitable for
+ ``debian/control``
+ '''
+ 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, "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),
+ base_deps=['${misc:Depends}'])
+ for task in tasks:
+ if task.install:
+ all_task.recommends.append(Dependency("Recommends",
+ task.package_name))
+ else:
+ all_task.suggests.append(Dependency("Suggests",
+ task.package_name))
+ if len(all_task.recommends) > 0:
+ tasks.insert(0, all_task)
+
+ return Deb822List(self.control_stub
+ + [task.gen_control() for task in tasks])
+
+ 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,
+ self.tasks))
+
+ header = [Deb822({
+ 'Task': self.name,
+ 'Relevance': '7',
+ 'Section': self.name,
+ 'Description': '{} Pure Blend\n .'.format(self.title),
+ })] if not udeb else []
+ return Deb822List(header + [task.gen_task_desc(udeb)
+ for task in tasks])
+
+
+class Task:
+ '''Representation of a Blends task. Modelled after apt.package.Version.
+
+ The Version class contains all information related to a
+ specific package version of a blends task.
+
+ :param blend: ``Blend`` object, or Blend name
+
+ :param name: Name of the task
+
+ :param sequence: ``str`` or ``file`` containing the ``Deb822``
+ description of the task
+
+ :param base_deps: List of dependencies to add to the task (``str``)
+
+ When the header does not contain a line
+
+ ``Format: https://blends.debian.org/blends/1.1``
+
+ then the ``Depends`` priorities will be lowered to ``Recommends``
+ when read.
+
+ Example:
+
+ >>> with open('education') as fp:
+ ... task = Task('debian-astro', 'education', fp)
+ >>> print(task.name)
+ education
+ >>> print(task.package_name)
+ astro-education
+ >>> print(task.description)
+ Various applications that can be used to teach astronomy.
+ <BLANKLINE>
+ This is however incomplete.
+ >>> print(task.summary)
+ Educational astronomy applications
+ >>> print(task.section)
+ metapackages
+ >>> print(task.architecture)
+ all
+ >>> for p in task.all:
+ ... print(p.name)
+ celestia-gnome
+ celestia-glut
+ starplot
+ gravit
+ sunclock
+ xtide
+
+ '''
+ def __init__(self, blend, name, sequence, base_deps=None):
+ if isinstance(blend, str):
+ self.blend = blend
+ '''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:
+ self.format_version = '1'
+ if self.format_version.split('.') < ['1', '1']:
+ 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):
+ setattr(self, dep_attr, list(itertools.chain(
+ *(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):
+ '''``True`` if the task is installed as a default package
+ '''
+ return self.header.get("Install") == "true"
+
+ @property
+ def index(self):
+ '''``True`` if the task shall appear in the tasks index in the
+ web senitel
+ '''
+ return self.header.get("index", "true") == "true"
+
+ @property
+ def is_leaf(self):
+ return self.header.get("leaf", "true") == "true"
+
+ @property
+ def is_metapackage(self):
+ '''``True`` if the tasks has a Debian metapackage
+ '''
+ return self.header.get("metapackage", "true") == "true"
+
+ @property
+ def package_name(self):
+ return '{task.prefix}-{task.name}'.format(task=self)
+
+ @property
+ def description(self):
+ '''Return the formatted long description.
+ '''
+ desc = self.header.get("Pkg-Description",
+ self.header.get("Description"))
+ if not desc:
+ return None
+ else:
+ return "\n".join(line[1:] if line != ' .' else ''
+ for line in desc.split("\n")[1:])
+
+ @property
+ def summary(self):
+ '''Return the short description (one line summary).
+ '''
+ desc = self.header.get("Pkg-Description",
+ self.header.get("Description"))
+ return desc.split('\n')[0] if desc else None
+
+ @property
+ def section(self):
+ '''Return the section of the package.
+ '''
+ return 'metapackages'
+
+ @property
+ def architecture(self):
+ '''Return the architecture of the package version.
+ '''
+ return self.header.get('Architecture', 'all')
+
+ @property
+ def tests(self):
+ '''Return all tests for this task when included in tasksel
+ '''
+ tests = dict((key.split('-', 1)[1], value)
+ for key, value in self.header.items()
+ if key.startswith('Test-'))
+ if self.install:
+ tests['new-install'] = 'mark show'
+ return tests
+
+ @property
+ def all(self):
+ '''All Base Dependencies of this task
+ '''
+ return list(itertools.chain(
+ *itertools.chain(self.dependencies,
+ self.recommends,
+ self.suggests)))
+
+ def gen_control(self):
+ '''Return the task as ``Deb822`` object suitable for ``debian/control``
+
+ >>> with open('education') as fp:
+ ... task = Task('debian-astro', 'education', fp)
+ >>> print(task.gen_control().dump())
+ Package: astro-education
+ Section: metapackages
+ Architecture: all
+ Recommends: celestia-gnome | celestia-glut,
+ gravit,
+ starplot
+ Suggests: sunclock,
+ xtide
+ Description: Educational astronomy applications
+ Various applications that can be used to teach astronomy.
+ .
+ This is however incomplete.
+ <BLANKLINE>
+ '''
+ d = Deb822()
+ d['Package'] = self.package_name
+ d['Section'] = self.section
+ d['Architecture'] = self.architecture
+ if self.dependencies or self.base_deps:
+ d['Depends'] = ",\n ".join(sorted(
+ self.base_deps
+ + list(set(d.rawstr for d in self.dependencies))
+ ))
+ if self.recommends:
+ d['Recommends'] = ",\n ".join(sorted(
+ set(d.rawstr for d in self.recommends)
+ ))
+ if self.suggests:
+ d['Suggests'] = ",\n ".join(sorted(
+ set(d.rawstr for d in self.suggests)
+ ))
+ d['Description'] = self.summary + '\n ' + \
+ "\n ".join(self.description.replace("\n\n", "\n.\n").split("\n"))
+ return d
+
+ 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', 'education', fp)
+ >>> print(task.gen_task_desc().dump())
+ Task: astro-education
+ Parent: debian-astro
+ Section: debian-astro
+ Description: Educational astronomy applications
+ Various applications that can be used to teach astronomy.
+ .
+ This is however incomplete.
+ Test-new-install: mark show
+ Key:
+ astro-education
+ <BLANKLINE>
+ >>> print(task.gen_task_desc(udeb=True).dump())
+ Task: astro-education
+ Section: debian-astro
+ Description: Educational astronomy applications
+ Various applications that can be used to teach astronomy.
+ .
+ This is however incomplete.
+ Relevance: 10
+ Test-new-install: mark show
+ Key:
+ astro-education
+ Packages: list
+ celestia-glut
+ celestia-gnome
+ gravit
+ starplot
+ <BLANKLINE>
+
+ '''
+ d = Deb822()
+ d['Task'] = self.package_name
+ if not udeb:
+ d['Parent'] = self.blend
+ d['Section'] = self.blend
+ d['Description'] = self.summary + '\n ' + \
+ "\n ".join(self.description.replace("\n\n", "\n.\n").split("\n"))
+ if udeb:
+ d['Relevance'] = '10'
+ if 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)
+ if udeb:
+ d['Packages'] = 'list\n ' + \
+ '\n '.join(sorted(d.name for d in itertools.chain(
+ *(self.recommends + self.dependencies))))
+ return d
+
+ def update(self, cache):
+ '''Update from cache
+
+ This adds the available versions to all dependencies. It updates
+ descriptions, summaries etc. available to all BaseDependencies.
+
+ :param cache: ``apt.Cache`` like object
+
+ Instead of using ``update()``, also the ``+=`` operator can be used:
+
+ >>> import apt
+ >>> with open('education') as fp:
+ ... task = Task('debian-astro', 'education', fp)
+ >>> dep = task.recommends[1][0]
+ >>> print(dep.name + ": ", dep.summary)
+ starplot: None
+ >>> task += apt.Cache()
+ >>> print(dep.name + ": ", dep.summary)
+ starplot: 3-dimensional perspective star map viewer
+ '''
+ for dep in self.all:
+ pkg = cache.get(dep.name)
+ if pkg is not None:
+ dep.target_versions += pkg.versions
+ if hasattr(cache, 'get_providing_packages'):
+ for pkg in cache.get_providing_packages(dep.name):
+ dep.target_versions += pkg.versions
+
+ def __iadd__(self, cache):
+ self.update(cache)
+ return self
+
+ def fix_dependencies(self):
+ '''Fix the dependencies according to available packages
+
+ This lowers all unavailable ``recommended`` dependencies to
+ ``suggested``.
+
+ >>> import apt
+ >>> with open('education') as fp:
+ ... task = Task('debian-astro', 'education', fp)
+ >>> for dep in task.recommends:
+ ... print(dep.rawstr)
+ celestia-gnome | celestia-glut
+ starplot
+ gravit
+ >>> for dep in task.suggests:
+ ... print(dep.rawstr)
+ sunclock
+ xtide
+ >>> task += apt.Cache()
+ >>> missing = task.fix_dependencies()
+ >>> for dep in task.recommends:
+ ... print(dep.rawstr)
+ starplot
+ gravit
+ >>> for dep in task.suggests:
+ ... print(dep.rawstr)
+ sunclock
+ xtide
+ celestia-gnome | celestia-glut
+ '''
+ missing = list()
+ 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
+ def upgrade_from_1_0(content):
+ header = [("Format", "https://blends.debian.org/blends/1.1")]
+ header += list(filter(lambda x: x[0] != "Format", content[0].items()))
+ res = [dict(header)]
+ for p in content[1:]:
+ q = []
+ for key, value in p.items():
+ if key == 'Depends' and 'Recommends' not in p:
+ key = 'Recommends'
+ # Remove backslashes, which are not DEB822 compliant
+ value = re.sub(r'\s*\\', '', value)
+ q.append((key, value))
+ res.append(dict(q))
+ return Deb822List(res)
+
+
+class Dependency(list):
+ '''Represent an Or-group of dependencies.
+
+ Example:
+
+ >>> with open('education') as fp:
+ ... task = Task('debian-astro', 'education', fp)
+ >>> dep = task.recommends[0]
+ >>> print(dep.rawstr)
+ celestia-gnome | celestia-glut
+ '''
+
+ def __init__(self, rawtype, s=None, content=None):
+ super(Dependency, self).__init__(BaseDependency(bs.strip(), content)
+ for bs in (s.split("|") if s else []))
+ self.rawtype = rawtype
+ '''The type of the dependencies in the Or-group'''
+
+ @property
+ def or_dependencies(self):
+ return self
+
+ @property
+ def rawstr(self):
+ '''String represenation of the Or-group of dependencies.
+
+ Returns the string representation of the Or-group of
+ dependencies as it would be written in the ``debian/control``
+ file. The string representation does not include the type of
+ the Or-group of dependencies.
+ '''
+ return ' | '.join(bd.rawstr for bd in self)
+
+ @property
+ def target_versions(self):
+ '''A list of all Version objects which satisfy this Or-group of deps.
+ '''
+ return list(itertools.chain(bd.target_versions for bd in self))
+
+
+class BaseDependency:
+ '''A single dependency.
+
+ Example:
+
+ >>> with open('education') as fp:
+ ... task = Task('debian-astro', 'education', fp)
+ >>> dep = task.recommends[2][0]
+ >>> print(dep.rawstr)
+ gravit
+ >>> print(dep.wnpp)
+ 743379
+ >>> print(dep.homepage)
+ http://gravit.slowchop.com/
+ >>> print(dep.description)
+ Gravit is a free, visually stunning gravity simulator.
+ <BLANKLINE>
+ You can spend endless time experimenting with various
+ configurations of simulated universes.
+ >>> print(dep.summary)
+ Visually stunning gravity simulator
+ '''
+
+ 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 = []
+
+ def _get_from_target_versions(self, key):
+ for v in self.target_versions:
+ if v.package.name == self.name:
+ return getattr(v, key)
+
+ @property
+ def rawstr(self):
+ '''String represenation of the dependency.
+
+ Returns the string representation of the dependency as it
+ would be written in the ``debian/control`` file. The string
+ representation does not include the type of the dependency.
+ '''
+ return self.name
+
+ @property
+ def wnpp(self):
+ '''The WNPP bug number, if available, or None
+ '''
+ return self.content.get("WNPP")
+
+ @property
+ def homepage(self):
+ '''Return the homepage for the package.
+ '''
+ return self._get_from_target_versions("homepage") or \
+ self.content.get("Homepage")
+
+ @property
+ def description(self):
+ '''Return the formatted long description.
+ '''
+ desc = self._get_from_target_versions("description")
+ if desc is not None:
+ return desc
+ desc = self.content.get("Pkg-Description",
+ self.content.get("Description"))
+ if desc:
+ return "\n".join(line[1:] if line != ' .' else ''
+ for line in desc.split("\n")[1:])
+
+ @property
+ def summary(self):
+ '''Return the short description (one line summary).
+ '''
+ summary = self._get_from_target_versions("summary")
+ if summary:
+ return summary
+
+ desc = self.content.get("Pkg-Description",
+ self.content.get("Description"))
+ if desc:
+ return desc.split('\n')[0]
+
+ @property
+ def why(self):
+ return self.content.get("Why")
+
+ @property
+ def remark(self):
+ return self.content.get("Remark")
+
+
+class Deb822List(list):
+ '''A list of ``Deb822`` paragraphs
+ '''
+ def __init__(self, paragraphs):
+ list.__init__(self, (p if isinstance(p, Deb822) else Deb822(p)
+ for p in paragraphs))
+
+ def dump(self, fd=None, encoding=None, text_mode=False):
+ '''Dump the the contents in the original format
+
+ 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
+ file. The encoding used is chosen via the encoding parameter;
+ None means to use the encoding the object was initialized with
+ (utf-8 by default). This will raise ``UnicodeEncodeError`` if
+ the encoding can't support all the characters in the
+ ``Deb822Dict`` values.
+
+ '''
+ if fd is None:
+ fd = io.StringIO()
+ return_string = True
+ else:
+ return_string = False
+
+ for p in self:
+ p.dump(fd, encoding, text_mode)
+ fd.write("\n")
+
+ if return_string:
+ return fd.getvalue()
+
+
+def aptcache(release=None, srcdirs=['/etc/blends']):
+ '''Open and update a (temporary) apt cache for the specified distribution.
+
+ :param release: Distribution name
+
+ :param srcdirs: List of directories to search for
+ ``sources.list.<<release>>``
+
+ If the distribution is not given, use the system's cache without update.
+ '''
+ import tempfile
+ import apt
+
+ if release is None:
+ return apt.Cache()
+ rootdir = tempfile.mkdtemp()
+ try:
+ os.makedirs(os.path.join(rootdir, 'etc', 'apt'))
+ shutil.copytree('/etc/apt/trusted.gpg.d',
+ os.path.join(rootdir, 'etc', 'apt', 'trusted.gpg.d'))
+ for src_dir in srcdirs:
+ sources_list = os.path.join(src_dir,
+ 'sources.list.{}'.format(release))
+ if os.path.exists(sources_list):
+ shutil.copy(sources_list,
+ os.path.join(rootdir, 'etc/apt/sources.list'))
+ break
+ else:
+ raise OSError("sources.list not found in " + str(srcdirs))
+ cache = apt.Cache(rootdir=rootdir, memonly=True)
+ cache.update()
+ cache.open()
+ finally:
+ shutil.rmtree(rootdir)
+ return cache
+
+
+def uddcache(packages, release, components=['main'], **db_args):
+ '''Create a ``dict`` from UDD that is roughly modelled after ``apt.Cache``.
+
+ The ``dict`` just resolves the version number and archs for the packages.
+ For performance reasons, an initial package list needs to be given.
+
+ :param release: Distribution name
+ :param packages: Initial package list
+ :param db_args: UDD connection parameters
+
+ ``Provided`` dependencies are integrated in the returned ``dict``.
+ '''
+ import collections
+ import psycopg2
+
+ pkgtuple = tuple(set(p.name for p in packages))
+
+ componenttuple = tuple(components)
+
+ Package = collections.namedtuple('Package',
+ ('name', 'versions',))
+ Version = collections.namedtuple('Version',
+ ('package', 'architecture', 'version'))
+
+ # To make the SELECT statements easier, we create a virtual view
+ # of the "packages" table that is restricted to the release and
+ # the component(s)
+ pkg_view_stmt = '''
+ CREATE TEMPORARY VIEW pkg
+ AS SELECT packages.package,
+ packages.version,
+ packages.architecture,
+ packages.provides
+ FROM packages, releases
+ WHERE packages.release=releases.release
+ AND (releases.release=%s OR releases.role=%s)
+ AND packages.component IN %s;
+ '''
+
+ # Since the "provides" are in a comma separated list, we create a
+ # normalized view
+ provides_view_stmt = '''
+ CREATE TEMPORARY VIEW provides
+ AS SELECT DISTINCT
+ package,
+ version,
+ architecture,
+ regexp_split_to_table(regexp_replace(provides,
+ E'\\\\s*\\\\([^)]*\\\\)',
+ '', 'g'),
+ E',\\\\s*') AS provides
+ FROM pkg;
+ '''
+
+ # Query all packages that have one of the specified names either as
+ # package name, or as one of the provides
+ query_stmt = '''
+ SELECT package,
+ version,
+ architecture,
+ provides
+ FROM pkg
+ WHERE package IN %s
+ UNION
+ SELECT package,
+ version,
+ architecture,
+ provides
+ FROM provides
+ WHERE provides IN %s;
+ '''
+
+ with psycopg2.connect(**db_args) as conn:
+ cursor = conn.cursor()
+ cursor.execute(pkg_view_stmt, (release, release, componenttuple))
+ cursor.execute(provides_view_stmt)
+ cursor.execute(query_stmt, (pkgtuple, pkgtuple))
+
+ cache = dict()
+ for package, version, arch, provides in cursor:
+ p = cache.setdefault(package, Package(package, []))
+ p.versions.append(Version(p, arch, version))
+ if provides:
+ for prv in provides.split(','):
+ pp = cache.setdefault(prv, Package(package, []))
+ pp.versions.append(Version(p, arch, version))
+ return cache
=====================================
debian/blends-dev.lintian-overrides
=====================================
--- /dev/null
+++ b/debian/blends-dev.lintian-overrides
@@ -0,0 +1,2 @@
+# Dependency on debhelper is intended
+blends-dev: binary-package-depends-on-toolchain-package depends: debhelper (>= 9)
=====================================
debian/changelog
=====================================
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,11 +1,61 @@
-blends (0.6.101) UNRELEASED; urgency=medium
+blends (0.7.0) unstable; urgency=low
+ [ Andreas Tille ]
+ * Do not mention subversion any more
+ * s/alioth/salsa/
+ * Description how Blends relevant data are gathered and stored
+ * Standards-Version: 4.1.4
+
+ [ Ole Streicher ]
+ * Switch back to unstable
+ * Create and use python3-blends package
+ * Move distribution name fix to blends-dev Makefile
+ * blends.py: Several UDD query fixes
+ * blends-gen-control: read defaults from GENCONTROL_OPTS env var
+ * blend-gen-control: Improve program output
+
+ -- Ole Streicher <olebole at debian.org> Fri, 13 Apr 2018 20:40:52 +0200
+
+blends (0.6.103) experimental; urgency=low
+
+ [ Andreas Tille ]
+ * d/control:
+ - Build-Depends: dh-python, python3-all
+ - blends-dev: Depends: ${python3:Depends}
+ * d/rules: --with python3
+ * ignore false lintian warning binary-package-depends-on-toolchain-package
+ * cme fix dpkg-control
+ * Take over tasks_diff from blends-gsoc to create dependency_data
+ * Use Deb822List to evaluate taskprefix in tasks_diff
+
+ [ Ole Streicher ]
+ * Add UDD query options to blend-gen-control
+
+ -- Ole Streicher <olebole at debian.org> Thu, 29 Mar 2018 21:33:29 +0200
+
+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 at debian.org> Wed, 28 Mar 2018 08:49:42 +0200
+
+blends (0.6.101) experimental; urgency=low
+
+ [ Andreas Tille ]
* Moved to Salsa
- * Degrade packages from contrib/non-free to Suggests even when enforcing
- strict depends
- Closes: #891188
- -- Andreas Tille <tille at debian.org> Fri, 02 Mar 2018 14:44:16 +0100
+ [ Ole Streicher ]
+ * Switch to experimental
+ * Replace blend-gen-control with a Python implementation
+ - Degrade packages from contrib/non-free to Suggests even when enforcing
+ strict depends. Closes: #891188
+ - Do not drop virtual packages in alternatives. Closes: #785678
+ - Use Python deb822 to parse tasks files.
+ - Recognize Format field in tasks file header. Closes: #825161
+
+ -- Ole Streicher <olebole at debian.org> Sat, 24 Mar 2018 10:41:20 +0100
blends (0.6.100) unstable; urgency=medium
@@ -422,7 +472,7 @@ blends (0.6.3) unstable; urgency=low
* Section for cdd-common misc instead of devel
* Updated mailing list address of Debian Pure Blends team
- * devtools/blends-gen-control:
+ * devtools/blends-gen-control:
- If a task lists not a single package which is available in
the target distribution the metapackage will be suppressed
if option '-S' is set; there is one exception which is needed
@@ -709,7 +759,7 @@ cdd (0.3.10) unstable; urgency=low
* Otavio Salvador
- cdd-gen-control:
o Add -D suport to cdd-gen-control to degrade dependencies to
- recommends;
+ recommends;
o Add support for architecture dependent packages;
o Add fallback to architecture independent packages so allow
backward compatibility;
=====================================
debian/control
=====================================
--- a/debian/control
+++ b/debian/control
@@ -4,25 +4,34 @@ Uploaders: Petter Reinholdtsen <pere at debian.org>,
Andreas Tille <tille at debian.org>,
Jonas Smedegaard <dr at jones.dk>,
Ole Streicher <olebole at debian.org>,
- Mike Gabriel <sunweaver at debian.org>,
+ Mike Gabriel <sunweaver at debian.org>
Section: devel
Priority: optional
Build-Depends: debhelper (>= 10)
-Build-Depends-Indep: xmlto,
- dblatex,
- w3m
-Standards-Version: 4.0.0
+Build-Depends-Indep: dblatex,
+ dh-python,
+ python3-all,
+ python3-apt,
+ python3-debian,
+ python3-pytest,
+ python3-sphinx,
+ w3m,
+ xmlto
+Standards-Version: 4.1.4
Vcs-Browser: https://salsa.debian.org/blends-team/blends
Vcs-Git: https://salsa.debian.org/blends-team/blends.git
Package: blends-dev
Architecture: all
Depends: debconf,
- make | build-essential,
debhelper (>= 9),
+ make | build-essential,
+ python3,
+ python3-apt,
+ python3-blends,
${misc:Depends}
Suggests: blends-doc
-Replaces: cdd-dev
+Recommends: python3-psycopg2
Description: Debian Pure Blends common files for developing metapackages
This package makes life easier when packaging metapackages. Perhaps
this will also encourage other people to build metapackages if there are
@@ -33,8 +42,8 @@ Package: blends-common
Architecture: all
Section: misc
Depends: adduser,
- menu,
debconf,
+ menu,
${misc:Depends}
Suggests: blends-doc
Description: Debian Pure Blends common package
@@ -49,9 +58,8 @@ Package: blends-doc
Architecture: all
Section: doc
Depends: ${misc:Depends}
-Suggests: www-browser,
- postscript-viewer
-Replaces: cdd-doc
+Suggests: postscript-viewer,
+ www-browser
Description: Debian Pure Blends documentation
This paper is intended to people who are interested in the philosophy
of Debian Pure Blends and the technique which is used to
@@ -66,10 +74,10 @@ Description: Debian Pure Blends documentation
Package: blends-tasks
Architecture: all
-Priority: important
Section: misc
-Depends: ${misc:Depends},
- tasksel
+Priority: important
+Depends: tasksel,
+ ${misc:Depends}
Description: Debian Pure Blends tasks for new installations
This package installs a choice of a default installation for each
Debian Pure Blend when run from the Debian installer. The
@@ -77,4 +85,19 @@ Description: Debian Pure Blends tasks for new installations
invocation of tasksel enables the choice of individual tasks.
.
The package is intended to be installed in the base system. Later
- (un)installation is harmless, but has no effect.
\ No newline at end of file
+ (un)installation is harmless, but has no effect.
+
+Package: python3-blends
+Architecture: all
+Section: python
+Depends: python3-debian,
+ ${misc:Depends},
+ ${python3:Depends},
+ ${sphinxdoc:Depends}
+Suggests: python3-psycopg2,
+ python3-apt
+Description: Python 3 module for Debian Pure Blends support
+ This package installs a module to handle Debian Pure Blends tasks.
+ It reads the tasks description from unpacked Blend Metapackages
+ sources. It is directly possible to create the debian/control file
+ and the tasks definition files from it.
=====================================
debian/copyright
=====================================
--- a/debian/copyright
+++ b/debian/copyright
@@ -1,12 +1,11 @@
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Files: *
Copyright: © 2003-2012 Andreas Tille <tille at debian.org>
License: GPL-2+
Files: devtools/blend-gen-control
-Copyright: © 2003-2007 Petter Reinholdtsen <pere at debian.org>
- © 2007-2012 Andreas Tille <tille at debian.org>
+Copyright: © 2018 Ole Streicher <olebole at debian.org>
License: GPL-2+
Files: share/blends/*
=====================================
debian/python3-blends.doc-base
=====================================
--- /dev/null
+++ b/debian/python3-blends.doc-base
@@ -0,0 +1,10 @@
+Document: python-blends
+Title: Debian Pure Blends Python package
+Author: Ole Streicher
+Abstract: This document describes the Debian Pure Blends
+ Python package API.
+Section: Debian
+
+Format: HTML
+Index: /usr/share/doc/python3-blends/index.html
+Files: /usr/share/doc/python3-blends/*.html
=====================================
debian/python3-blends.docs
=====================================
--- /dev/null
+++ b/debian/python3-blends.docs
@@ -0,0 +1 @@
+sphinxdoc/_build/html/*
=====================================
debian/python3-blends.pyinstall
=====================================
--- /dev/null
+++ b/debian/python3-blends.pyinstall
@@ -0,0 +1 @@
+blends.py
=====================================
debian/rules
=====================================
--- a/debian/rules
+++ b/debian/rules
@@ -8,12 +8,19 @@ include /usr/share/dpkg/default.mk
DISTDIR := $(DEB_SOURCE)-$(DEB_VERSION)
%:
- dh $@
+ dh $@ --with python3,sphinxdoc
override_dh_auto_build:
- cd doc; $(MAKE) html; $(MAKE) txt; $(MAKE) pdf
+ $(MAKE) -C doc html txt pdf
+ $(MAKE) -C sphinxdoc html
dh_auto_build
+override_dh_auto_test:
+ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
+ pytest-3 --doctest-modules
+ rm -f education # clean up test remnants
+endif
+
override_dh_installchangelogs:
for pkgnews in $(DEB_SOURCE)-common $(DEB_SOURCE)-dev ; do \
cp -a debian/$$pkgnews.NEWS.Debian debian/$$pkgnews/usr/share/doc/$$pkgnews/NEWS.Debian ; \
@@ -21,7 +28,8 @@ override_dh_installchangelogs:
dh_installchangelogs
override_dh_auto_clean:
- cd doc; $(MAKE) clean
+ $(MAKE) -C doc clean
+ $(MAKE) -C sphinxdoc clean
dh_auto_clean
override_dh_compress:
=====================================
devtools/Makefile
=====================================
--- a/devtools/Makefile
+++ b/devtools/Makefile
@@ -9,32 +9,92 @@
# TARGET_DIST is one of stable, sarge, etch, unstable, or any other available
# sources.list file available
TARGET_DIST := $(shell head -1 debian/changelog |awk '{print $$3}'|tr -d ';')
+
+# using unstable as target distribution for the meta package dependencies
+# does actually not sound reasonable. The idea is to enable a smooth transition
+# to testing for all meta packages and thus here testing is used as target
+# distribution.
+ifeq "$(TARGET_DIST)" "unstable"
+ TARGET_DIST := "testing"
+endif
+
+# It is a good practice to use UNRELEASED in the changelog as target
+# distribution for not yet finished packages and blends-dev should
+# also work in this case.
+ifeq "$(TARGET_DIST)" "UNRELEASED"
+ TARGET_DIST := "unstable"
+endif
+
BLEND := $(shell /usr/share/blends-dev/blend-get-names blendname)
+VERSION := $(shell dpkg-parsechangelog -ldebian/changelog | grep Version: | cut -f2 -d' ' | cut -f1 -d- )
GENCONTROL := /usr/share/blends-dev/blend-gen-control
-GENCONTROL_OPTS := $(shell grep -q -E '^GENCONTROL_DEPENDS\s*=\s*(T|t)(R|r)(U|u)(E|e)\s*$$' Makefile || echo "-D")
+TASKSDIFF := /usr/share/blends-dev/tasks_diff
+DEPENDENCIES_DATA := dependency_data
TASKSELOPTS := $(shell grep TASKSELOPTS Makefile | cut -d '=' -f2)
# Verify whether config/control exists, if yes, add it to the depends of debian/control
CONFIGCONTROL := $(shell if [ -d config -a -e config/control ] ; then echo config/control; fi)
+#get two latest releases
+RELEASES := $(shell grep '^$(BLEND)' debian/changelog | head -2 | awk '{print $$2}' | tr -d '[(,)]')
+LATEST := $(shell echo "$(RELEASES)" | cut -d ' ' -f1)
+PREVIOUS := $(shell echo $(RELEASES) | cut -d ' ' -f2)
+
+ifneq "$(LATEST)" "$(PREVIOUS)"
+ LINEEND := $(shell lineend=`grep '^$(BLEND)' debian/changelog -n | sed -n 2p | cut -d ':' -f1`; echo "$$lineend - 3" | bc)
+ LINESTART := $(shell linestart=`grep "* start of automatic changelog entry *" debian/changelog -n | head -1 | awk '{print $$1}' | tr -d ':'`; if [ -z "$$linestart" ]; then echo "0"; else echo "$$linestart - 1" | bc; fi)
+
+ ISGREATER := $(shell expr $(LINESTART) \> $(LINEEND))
+
+ ifeq "$(LINESTART)" "0"
+ LINESTART := $(LINEEND)
+ else ifeq "$(ISGREATER)" "1"
+ LINESTART := $(LINEEND)
+ endif
+endif
+
all: $(BLEND)-tasks.desc debian/control
debian/control: debian/control.stub debian/changelog tasks/* $(CONFIGCONTROL)
- (export LC_ALL=C;\
- echo "# This file is autogenerated via "make -f debian/rules dist". Do not edit!"; \
- cat debian/control.stub; \
- test -f config/control && ( cat config/control; echo ) ; \
- $(GENCONTROL) -s $(TARGET_DIST) -S $(GENCONTROL_OPTS) -c -m -i -A) > $@.new && mv $@.new $@
+ $(GENCONTROL) -r $(TARGET_DIST) -S -c -m
tasksel: $(BLEND)-tasks.desc
$(BLEND)-tasks.desc: tasks/* debian/changelog
- LC_ALL=C $(GENCONTROL) -s $(TARGET_DIST) $(TASKSELOPTS) -S -t -A > $(BLEND)-tasks.desc.new && mv $(BLEND)-tasks.desc.new $(BLEND)-tasks.desc
+ $(GENCONTROL) $(TASKSELOPTS) -r $(TARGET_DIST) -S -t
+
+dependency_data:
+ if [ ! -d $(DEPENDENCIES_DATA) ]; then \
+ echo "$(DEPENDENCIES_DATA) directory does not exist, creating it now."; \
+ mkdir $(DEPENDENCIES_DATA); \
+ fi
+
+statusdump: dependency_data
+ $(TASKSDIFF) --status-dump --tasks . --output $(DEPENDENCIES_DATA)/$(BLEND)_$(VERSION).json
+
+#update changelog with dependencies changes
+changelogentry: debian/changelog statusdump
+ifneq "$(LATEST)" "$(PREVIOUS)"
+ if [ ! -f $(DEPENDENCIES_DATA)/$(BLEND)_$(LATEST).json ]; then \
+ echo "$(DEPENDENCIES_DATA)/$(BLEND)_$(LATEST).json does not exist, can not generate changelog dependencies-changes entry"; \
+ exit -1; \
+ fi
+ if [ ! -f $(DEPENDENCIES_DATA)/$(BLEND)_$(PREVIOUS).json ]; then \
+ echo "$(DEPENDENCIES_DATA)/$(BLEND)_$(PREVIOUS).json does not exist, can not generate changelog dependencies-changes entry"; \
+ exit -1; \
+ fi
+
+ (sed $(LINESTART)q debian/changelog; \
+ $(TASKSDIFF) --compare $(DEPENDENCIES_DATA)/$(BLEND)_$(LATEST).json,$(DEPENDENCIES_DATA)/$(BLEND)_$(PREVIOUS).json | sed 's/^/ /'; \
+ sed -n '$(LINEEND),$$p' debian/changelog; ) > debian/changelog.new && mv debian/changelog.new debian/changelog
+else
+ echo "It is the first release, skip the changelog entry"
+endif
packages.txt: tasks/*
- $(GENCONTROL) -s $(TARGET_DIST) -a > packages.txt.$$$$ && mv packages.txt.$$$$ packages.txt
+ $(GENCONTROL) -r $(TARGET_DIST) -a > packages.txt.$$$$ && mv packages.txt.$$$$ packages.txt
avoidpackages.txt: tasks/* sources.list.$(TARGET_DIST)
- $(GENCONTROL) -s $(TARGET_DIST) -e > avoidpackages.txt.$$$$ && mv avoidpackages.txt.$$$$ avoidpackages.txt
+ $(GENCONTROL) -r $(TARGET_DIST) -e > avoidpackages.txt.$$$$ && mv avoidpackages.txt.$$$$ avoidpackages.txt
by_vote:
rm -f by_vote
=====================================
devtools/blend-gen-control
=====================================
--- a/devtools/blend-gen-control
+++ b/devtools/blend-gen-control
@@ -1,812 +1,149 @@
-#!/usr/bin/perl
-#
-# Authors:
-# Petter Reinholdtsen <pere at hungry.com>
-# Andreas Tille <tille at debian.org>
-# Date: 2001-08-23
-#
-# Generate the control file used by the Blend task package.
+#!/usr/bin/python3
-use warnings;
-use strict;
+import os
+import sys
+import argparse
+import itertools
-use Getopt::Std;
-use File::Path;
+from blends import Blend, aptcache, uddcache
-use vars qw(%opts %available %excluded %included @wanted %missing
- @tasks $debug $suppressempty);
-my @arch = qw(alpha arm i386 ia64 m68k mips mipsel powerpc s390 sparc hppa);
+default_release = "testing"
-my $debug = 0;
-my $nodepends = 0;
-my $ignoreapterrors = 0;
-my $suppressempty = 0;
+parser = argparse.ArgumentParser()
-my %taskinfo = ();
-my $tasksdir = "tasks" ;
-my $taskcontrolfile = "tasks.ctl" ;
+parser.add_argument("-d", "--dir", dest="dir", type=str,
+ default=".",
+ help="Base directory of the tasks source package"
+ + " (default: current directory)")
-my $aptsourcesdefaultlocation = "/etc/blends";
-my $aptsources = $aptsourcesdefaultlocation . "/sources.list";
-my $blend_dev_dir = "/usr/share/blends-dev";
-
-my %commondepends ;
-$commondepends{"adduser"} = "0" ;
-$commondepends{"debconf"} = "1.2" ;
-$commondepends{"menu"} = "2.1.14" ;
-
-my $CommonPackage = "" ;
-my $prefix = "test-" ;
-my $blendname = "" ;
-my $blendshortname = "" ;
-my $blendtitle = "" ;
-my $tasksname = "" ;
-my $hasconfig = 0 ;
-
-sub usage() {
- print STDERR << "EOF";
-blend-gen-control help screen
-usage: $0 [options]
-
- -a : print wanted packages
- -A : ignore APT errors
- -c : create new debian/control file
- -d : print debug information
- -D : lower all Depends: to Recommends:
- -e : print excluded packages
- -h : print this help screen and exit
- -i :
- -m : print missing packages
- -s <sourcefile> : specify which sources.list file to use
- -S : suppress tasks without any recommended package
- -t : print task descriptions and package list for task
- -u : modify tasks desc file in case the blend uses udebs
-
-example: $0 -s sources.list.etch -D -c -m -i
-EOF
-}
-
-getopts("cdaemis:StuDhA", \%opts);
-
-usage() and exit if $opts{'h'};
-
-my $aptsourcesinput = $opts{'s'} if ($opts{'s'});
-$aptsources = $aptsourcesinput ;
-if ( $aptsources !~ m%^/% && $aptsources !~ /^sources\.list\./ ) {
- my $cwd ;
- chomp($cwd = `pwd`) ;
- $aptsources = $cwd . "/sources.list." . $aptsources ;
-}
-if ( ! -e $aptsources ) {
- $aptsources = $aptsourcesdefaultlocation . "/sources.list." . $aptsourcesinput;
- if ( ! -e $aptsources ) {
- die "Apt sources.list $aptsources not found.\n" ;
- }
-}
-
-$debug = 1 if ($opts{'d'});
-$nodepends = 1 if ($opts{'D'});
-$ignoreapterrors = 1 if ($opts{'A'});
-$suppressempty = 1 if ($opts{'S'});
-
-blend_init();
-
-# print "$prefix ; $blendshortname ; $blendname ; $tasksname \n";
-
-load_available_packages();
-
-load_tasks();
-
-# An ordered list of Blend tasks, in priority order of which are
-# most needed on the CD. Only leaf tasks need be listed.
-my @priorityorder = get_priorities("priority-high", 1);
-my @medpriorder = get_priorities("priority-med", 0);
-
-# print "high: @priorityorder\nmed: @medpriorder\n" ;
-
-if ($opts{'c'}) {
- gen_control();
-} elsif ($opts{'e'}) {
- print_excluded_packages();
-} elsif ($opts{'a'}) {
- print_all_packages();
-} elsif ($opts{'t'}) {
- print_task_desc();
-} else {
- print_available_packages();
-}
-print_missing_packages() if ($opts{'m'});
-
-sub apt {
- my $op = shift;
-
- my $aptdir = "../tmp/apt";
- # FIXME: For propper apt configuration see
- # https://lists.debian.org/debian-mentors/2014/11/msg00032.html
- # https://lists.debian.org/debian-mentors/2014/11/msg00033.html
- # For the moment to do only minimal changes in freeze time we set
- # Dir::Etc::sourceparts=none
- # to prevent including random sources from users sources.list.d
- my @aptopts = ("Dir::Etc::sourcelist=$aptsources",
- "Dir::Etc::sourceparts=none",
- "Dir::State=$aptdir/state",
- "Dir::Cache=$aptdir/cache",
- "Dir::State::Status=/dev/null",
- "Debug::NoLocking=true",
- "APT::Get::AllowUnauthenticated=true");
-
- # Stupid apt-get and apt-cache do not understand the same arguments!
- # I have to map them to different formats to get both working.
-
- if ("update" eq $op) {
- mkpath "$aptdir/state/lists/partial";
- mkpath "$aptdir/cache/archives/partial";
-
- my $aptget = "apt-get --assume-yes -o " . join(" -o ", @aptopts);
-
- print STDERR "aptget: $aptget\n" if $debug;
- if (system("$aptget update 1>&2")) {
- print STDERR "error: updating apt package lists failed\n";
- exit 1 unless $ignoreapterrors;
- }
- } elsif ("apt-cache" eq "$op") {
- my $aptcache = "apt-cache -o=" . join(" -o=", @aptopts);
- print STDERR "aptcache: $aptcache\n" if $debug;
- return $aptcache;
- }
-}
-
-sub sort_uniq {
- my $seen = shift;
- my @list;
- for my $entry (sort @_) {
- push @list,$entry unless $seen->{$entry};
- $seen->{$entry} = 1;
- }
- return @list;
-}
-
-sub uniq {
- my $seen = shift;
- my @list;
- for my $entry (@_) {
- push @list,$entry unless $seen->{$entry};
- $seen->{$entry} = 1;
- }
- return @list;
-}
-
-sub gen_control {
- my $task;
-
- my @recommends;
- my @suggests;
- for $task (sort keys %taskinfo) {
- next if (exists $taskinfo{$task}{'Metapackage'} &&
- $taskinfo{$task}{'Metapackage'} eq 'false');
- next if ( $suppressempty && $taskinfo{$task}{'haspackages'} == 0 );
- if (exists $taskinfo{$task}{'Install'} &&
- $taskinfo{$task}{'Install'} eq 'true') {
- push (@recommends, $task)
- } else {
- push (@suggests, $task)
- }
- }
- if (@recommends) {
- print "Package: " . $prefix . "all\n";
- print "Section: metapackages\n" ;
- print "Architecture: all\n";
- print("Recommends: ", join(",\n ", @recommends),"\n");
- print("Suggests: ", join(",\n ", @suggests),"\n") if @suggests;
- print "Description: Default selection of tasks for $blendtitle\n";
- print " This package is part of the $blendtitle Pure Blend and installs all\n";
- print " tasks for a default installation of this blend.\n\n";
- }
-
- for $task (sort keys %taskinfo) {
- next if (exists $taskinfo{$task}{'Metapackage'} &&
- $taskinfo{$task}{'Metapackage'} eq 'false');
-
- print STDERR "$task: $taskinfo{$task}{'haspackages'}\n" if $debug;
- # if no package was found in the target distribution suppress this task at all
- if ( $suppressempty && $taskinfo{$task}{'haspackages'} == 0 ) {
- print STDERR "The metapackage $task will not be created because $taskinfo{$task}{'haspackages'} dependant are in the pool and suppressempty was set ($suppressempty)\n" if $debug;
- next ;
- }
- print "Package: $task\n";
-
- # metapackages should not be Section misc -> see #720199
- if (defined $taskinfo{$task}{'Section'} && $taskinfo{$task}{'Section'} ne 'misc') {
- print "Section: $taskinfo{$task}{'Section'}\n" ;
- } else {
- print "Section: metapackages\n" ;
- }
- my $header;
- for $header (qw(Architecture Priority)) {
- print "$header: $taskinfo{$task}{$header}\n"
- if (defined $taskinfo{$task}{$header});
- }
- my %seenlist;
- if ($nodepends) {
- # degrade dependencies to recommends
- if ( $tasksname ) {
- print "Depends: $tasksname";
- if ( $tasksname =~ /-tasks$/ ) {
- print ' (= ${source:Version}), ${misc:Depends}';
- }
- if ( $hasconfig ) {
- print ', ' . $prefix . 'config (= ${source:Version})';
- }
- print "\n" ;
- }
- # Use common %seenlist, as it is no use listing
- # packages both in recommends and suggest
- my @list;
- for $header (qw(Depends Recommends)) {
- push (@list, @{$taskinfo{$task}{$header}})
- if defined $taskinfo{$task}{$header};
- }
- my ($pkglist, $missinglist) = process_pkglist(join(",", at list));
- my (@recommends, @suggests);
- push @recommends, @{$pkglist};
- push @suggests, @{$missinglist};
- push(@suggests, @{$taskinfo{$task}{Suggests}})
- if defined $taskinfo{$task}{Suggests};
- my @recommends_sorted = sort_uniq(\%seenlist, @recommends);
- my @suggests_sorted = sort_uniq(\%seenlist, @suggests);
- print("Recommends: ",
- join(",\n ", @recommends_sorted),"\n")
- if @recommends_sorted;
- print("Suggests: ",
- join(",\n ", @suggests_sorted),"\n")
- if @suggests_sorted;
- }
- else {
- my $pkglist;
- my $missinglist;
- my $pkgcandidate;
- if (defined $taskinfo{$task}{Depends})
- {
- ($pkglist, $missinglist) = process_pkglist(join(",",@{$taskinfo{$task}{Depends}}));
- }
- # make sure that $missinglist will not remain empty if there are no Depends defined
- if (defined $taskinfo{$task}{Recommends})
- {
- ($pkglist, $missinglist) = process_pkglist(join(",",@{$taskinfo{$task}{Recommends}}));
- }
-
- my (@depends, @recommends, @suggests);
-
- push @depends, $tasksname.' (= ${source:Version})';
- push @depends, '${misc:Depends}';
- if ( defined $pkglist ) {
- for $pkgcandidate (@{$pkglist}) {
- unless ( grep( /^$pkgcandidate$/, $missinglist ) ) { push @depends, $pkgcandidate };
- }
- }
- if ( defined $taskinfo{$task}{Recommends} ) {
- for $pkgcandidate (@{$taskinfo{$task}{Recommends}}) {
- unless ( grep( /^$pkgcandidate$/, $missinglist ) ) { push @recommends, $pkgcandidate };
- }
- }
-
- push @suggests, @{$missinglist}
- if defined $missinglist;
- push @suggests, @{$taskinfo{$task}{Suggests}}
- if defined $taskinfo{$task}{Suggests};
-
- my @depends_sorted = sort_uniq(\%seenlist, @depends);
- my @recommends_sorted = sort_uniq(\%seenlist, @recommends);
- my @suggests_sorted = sort_uniq(\%seenlist, @suggests);
-
- print("Depends: ",
- join(",\n ", @depends_sorted),"\n")
- if @depends_sorted;
- print("Recommends: ",
- join(",\n ", @recommends_sorted),"\n")
- if @recommends_sorted;
- print("Suggests: ",
- join(",\n ", @suggests_sorted),"\n")
- if @suggests_sorted;
- }
-
- # Description Description-long
- print "Description: $taskinfo{$task}{Description}\n";
- print "$taskinfo{$task}{'Description-long'}"; # Already contain newline
-
- print "\n";
- }
-}
-
-# List all depends, recommends and suggests packages as task packages.
-# Optionally, list depends as key packages, and the rest as task
-# packages.
-# Enable to list all dependencies as key packages
-my $task_depends_are_keys = 0;
-sub print_task_desc {
- if (! $opts{'u'} ) {
- print "Task: $blendname\n";
- print "Relevance: 7\n";
- print "Section: $blendname\n";
-# print "Key: \n";
-# print " $blendshortname-tasks\n";
- print "Description: $blendtitle Pure Blend\n";
- print " .\n"; # no long description available
- print "\n";
- }
- foreach my $task (sort keys %taskinfo) {
- next if (exists $taskinfo{$task}{'Leaf'} &&
- $taskinfo{$task}{'Leaf'} eq 'false');
- next if (exists $taskinfo{$task}{'Metapackage'} &&
- $taskinfo{$task}{'Metapackage'} eq 'false');
- my $header;
- if ( $suppressempty && $taskinfo{$task}{'haspackages'} == 0 ) {
- # Check for Test-always-lang header. If this field exists the
- # task should be created even if there are no explicite dependencies
- # This is a request of Debian Edu (see the thread at
- # http://lists.debian.org/debian-blends/2009/04/msg00008.html
- my $foundflag = 0;
- for $header (keys %{$taskinfo{$task}}) {
- if ($header =~ m/Test-always-lang/) {
- print STDERR "Print empty task $task because Test-always-lang is set\n" if $debug;
- $foundflag = 1;
- last;
- }
- }
- if ( $foundflag == 0 ) {
- print STDERR "The metapackage $task will not be created because $taskinfo{$task}{'haspackages'} dependant are in the pool and suppressempty was set ($suppressempty)\n" if $debug;
- next ;
- }
- }
- print "Task: $task\n";
- if (! $opts{'u'}) {
- print "Parent: $blendname\n";
- }
- print "Section: $blendname\n";
- print "Description: $taskinfo{$task}{Description}\n";
- print "$taskinfo{$task}{'Description-long'}"; # Already contain newline
- if ($opts{'u'}) {
- print "Relevance: 10\n";
- }
- print "Enhances: $taskinfo{$task}{Enhances}\n"
- if exists $taskinfo{$task}{Enhances};
- if (exists $taskinfo{$task}{'Install'} &&
- $taskinfo{$task}{'Install'} eq 'true') {
- if (! $opts{'u'}) {
- print "Test-new-install: mark show\n";
- }
- }
- for $header (keys %{$taskinfo{$task}}) {
- if ($header =~ m/test-.+/i) {
- print "$header: $taskinfo{$task}{$header}\n";
- }
- }
- unless (exists $taskinfo{$task}{'Metapackage'} &&
- $taskinfo{$task}{'Metapackage'} eq 'false') {
- # No use listing a metapackage as a key package, if no metapackage exist.
- print "Key: \n";
- print " $task\n";
- }
- my %seen;
- $seen{$task} = 1;
- if ($task_depends_are_keys) {
- foreach my $package (task_packages($task, "Depends")) {
- print " $package\n" unless $seen{$package};
- $seen{$package} = 1;
- }
- }
-
- if ($opts{'u'}) {
- print "Packages: list\n";
- for my $header (qw(Depends Recommends)) {
- foreach my $package (task_packages($task, $header, 1)) {
- print " $package\n" unless $seen{$package};
- $seen{$package} = 1;
- }
- }
- }
- print "\n";
- }
-}
-
-sub select_alternative {
- my $pkglist = shift;
- return $pkglist;
-}
-
-sub task_packages {
- my ($task, $header, $includealldeps) = @_;
- my @packages = $task;
- foreach my $package (@{$taskinfo{$task}{$header}}) {
- if ($package=~/\|/) {
- # Tasksel doesn't allow boolean or-ing of
- # dependencies. Just take the first one that is
- # available.
- my $ok=0;
- foreach my $alternative (split(' | ', $package)) {
- if (! exists $taskinfo{$alternative} &&
- ! exists $available{$alternative}) {
- if (! exists $missing{$alternative}) {
- $missing{$alternative} = 1;
- }
- }
- else {
- print STDERR "task_packages: choosing $alternative from $package\n" if $debug;
- $package=$alternative;
- $ok=1;
- last;
- }
- }
- if (! $ok) {
- next;
- }
- }
- if (exists $taskinfo{$package}) {
- # Add packages from task recursively, since
- # tasksel does not support dependent tasks of
- # the type used by Blend
- if (defined $includealldeps && $includealldeps) {
- for my $h (qw(Depends Recommends)) {
-
- push(@packages, $package,
- task_packages($package, $h, 1));
- }
- } else {
- push(@packages, $package,
- task_packages($package, $header));
- }
- }
- else {
- push @packages, $package;
- }
- }
- return @packages; ### FIXME!: insert sort here to enable sorted list inside tasks control file (tested on 2014-11-03 but do not touch in freeze time)
-}
-
-#
-# Check the APT cache, and find the packages currently available.
-#
-sub load_available_packages
-{
- apt("update");
- my $aptcache = apt("apt-cache");
- open(APT, "$aptcache dump |") || die "Unable to start apt-cache";
- my $pkg;
- while (<APT>) {
- chomp;
- if (/^Package: (.+)$/) {
- $pkg = $1;
- print STDERR "Found pkg '$pkg'\n" if $debug;
- }
- if (/^\s+Version:\s+(.+)/) {
- print STDERR " pkg $pkg = ver $1\n" if $debug;
-# print "C: $pkg $available{$pkg} lt $1\n" if ( exists $available{$pkg});
- $available{$pkg} = $1 if ( ! exists $available{$pkg} ||
- $available{$pkg} lt $1 );
- }
- }
-}
-
-#
-# Load all tasks
-#
-sub load_tasks {
- my $taskfile;
-
- # First document their existence, so they can depend on each other.
- for $taskfile (<tasks/*>) {
- next if (($taskfile eq "tasks/CVS") || ($taskfile eq "tasks/.svn"));
- next if ($taskfile =~ m/~$/);
-
- my $curpkg = $taskfile;
- $curpkg =~ s%tasks/%$prefix%;
- $available{$curpkg} = "n/a";
-
- push(@tasks, "$taskfile:$curpkg");
- }
-
- # Next, load their content.
- my $foo;
- for $foo (@tasks) {
- my ($taskfile, $curpkg) = $foo =~ m/^(.+):(.+)$/;
- next if ("tasks/CVS" eq $taskfile);
-
- load_task($taskfile, $curpkg);
- }
-}
-
-sub process_pkglist {
- my $pkgstring = shift;
- my @pkglist = ();
- my @missinglist = ();
- my $packages;
- for $packages (split(/\s*,\s*/, $pkgstring)) {
- print "E: double comma?: $_\n" if ($packages =~ /^\s*$/ && $debug);
- my $package;
- my @alternates=split(/\s*\|\s*/, $packages);
- my $alternatecount=0;
- for $package (@alternates) {
- print STDERR "Loading pkg '$package'\n" if $debug;
- if ($package =~ /[A-Z]/) {
- print STDERR "Packages may not contain upper case letters (policy 5.6.7) $package. Name will be turned into ";
- $package = lc($package);
- print STDERR "$package\n";
- }
- if ($package =~ /^-(.+)$/) {
- $excluded{$1} = 1;
- } elsif ( !exists $available{$package} ) {
- if ( !exists $missing{$package}) {
- $missing{$package} = 1;
- }
- push(@missinglist, $package);
- } else {
- if ($alternatecount == 0) {
- #push(@pkglist, $package) if (! exists $pkglist[$package]);
- push(@pkglist, $package);
- }
- else {
- $pkglist[-1].=" | $package";
- }
- $alternatecount++;
-
- if ( ! $included{$package} ) {
- push(@wanted, $package);
- $included{$package} = 1;
- }
- }
- }
- }
- return (\@pkglist, \@missinglist);
-}
-
-sub load_task {
- my ($taskfile, $curpkg) = @_;
- open(TASKFILE, "<$taskfile") || die "Unable to open $taskfile";
- my $line;
-
- $taskinfo{$curpkg} = ();
-
- print STDERR "Loading task $curpkg\n" if $debug;
-
- my $haspackages = 0;
- while (<TASKFILE>) {
- chomp;
- next if (m/^\#/); # Skip comments
- $line = $_;
-
- # Append multi-line
- while ($line =~ /\\$/) {
- $line =~ s/\s*\\//;
- $_ = <TASKFILE>;
- chomp;
- $line .= $_;
- }
- # Remove trailing space
- $line =~ s/\s+$//;
-
-
- $_ = $line;
- for my $header (qw(Section Architecture Priority Leaf Enhances Metapackage Install)) {
- $taskinfo{$curpkg}{$header} = $1 if (m/^$header:\s+(.+)$/);
- }
- $taskinfo{$curpkg}{$1} = $2 if (m/^(test-.+):\s+(.+)$/i);
-
- if (m/^Description:\s+(.+)$/) {
- $taskinfo{$curpkg}{'Description'} = $1;
- $taskinfo{$curpkg}{'Description-long'} = "";
- while (<TASKFILE>) {
- # End of description, pass next line to pattern matching
- last if (m/^\S+/ || m/^\s*$/);
-
- $taskinfo{$curpkg}{'Description-long'} .= $_;
- }
- }
-
- next unless defined $_;
-
- my $header;
- for $header (qw(Depends Recommends Suggests)) {
- last if !defined $_;
-
- my $pkgs = '';
-
- # first package comes after header ('<header>: <package>')
- if (m/^$header:\s+(.+)$/ && $1 !~ /^\s*$/) {
- $pkgs .= $1;
-
- # remove blanks near kommas and pipe symbols
- $pkgs =~ s/\s*,\s*/,/g;
- $pkgs =~ s/\s*\|\s*/|/g;
-
- # reliable whitespace and backslash cleanup
- $pkgs =~ s/^\s*([a-z0-9\_\.\|+,-]+)\s*(|\\\s*)$/$1/;
- }
-
- # or no package in the header line ('<header>':<LF>)
- if (($pkgs) || (m/^$header:\s*$/)) {
- while (<TASKFILE>) {
- last if (m/^\S+/ || m/^\s*$/);
-
- # remove blanks near kommas and pipe symbols
- $_ =~ s/\s*,\s*/,/g;
- $_ =~ s/\s*\|\s*/|/g;
-
- # reliable whitespace and backslash cleanup around
- # package name
- $_ =~ s/^\s*([a-zA-Z0-9\_\.\|+,-]+)\s*(|\\\s*)$/$1/;
- $pkgs .= $_;
- }
-
- # strip trailing komma after last package
- $pkgs =~ s/\s*,$//;
-
- $taskinfo{$curpkg}{$header} = ()
- if (! exists $taskinfo{$curpkg}{$header});
- my ($pkglist, $missinglist) = process_pkglist($pkgs);
- push(@{$taskinfo{$curpkg}{$header}}, @{$pkglist});
-
- $haspackages += $#{$taskinfo{$curpkg}{$header}} + 1;
- print STDERR "$curpkg $header:", @{$taskinfo{$curpkg}{$header}}, "($haspackages)\n" if $debug;
- # Avoid missing packages in Depends lists, allow them
- # in the two others. Insert missing depends in
- # suggests list.
- if (@{$missinglist}) {
- print STDERR "$curpkg: missing = ", @{$missinglist}, "\n" if $debug;
- if ("Depends" eq $header) {
- push(@{$taskinfo{$curpkg}{'Suggests'}}, @{$missinglist});
- } else {
- push(@{$taskinfo{$curpkg}{$header}}, @{$missinglist});
- }
- }
- }
- }
-
- next unless defined $_;
-
- if (/^Avoid:\s+(.+)$/) {
- my @pkgs = split(/\s*,\s*/, $1);
- my $packages;
- for $packages (@pkgs) {
- my $package;
- for $package (split(/\s*\|\s*/, $packages)) {
- $excluded{$package} = 1;
- }
- }
- }
-
- if (/^Ignore:\s+(.+)$/) {
- my @pkgs = split(/\s*,\s*/, $1);
- my $packages;
- for $packages (@pkgs) {
- my $package;
- for $package (split(/\s*\|\s*/, $packages)) {
- # Remove explanations, ie the paranteses at the end.
- $package =~ s/\s*\([^\)]*\)\s*$//;
- $missing{$package} = 1;
- }
- }
- }
- }
- close(TASKFILE);
- unless ( $taskinfo{$curpkg}{'Architecture'} ) { $taskinfo{$curpkg}{'Architecture'} = "all" ; }
- $taskinfo{$curpkg}{'haspackages'} = $haspackages;
- print STDERR "$curpkg: haspackages = ", $taskinfo{$curpkg}{'haspackages'}, "\n" if $debug;
-}
-
-sub print_excluded_packages {
- print join("\n", sort keys %excluded),"\n";
-}
-
-sub print_available_packages {
- print join("\n", @wanted),"\n";
-}
-
-sub print_all_pkgs_tasks {
- my ($seenref, $headerlistref, @tasks) = @_;
-
- my @headers;
- if ( $headerlistref ) {
- @headers = @{$headerlistref};
- } else {
- @headers = qw(Depends Recommends Suggests)
- }
-
- for my $header (@headers) {
- print STDERR " Processing $header\n" if $debug;
- my %seentask;
- for my $task (@tasks) {
- next if $seentask{$task};
- $seentask{$task} = 1;
-
- print "# printing $header in $task\n";
- print STDERR " Printing $task\n" if $debug;
-
- # Pick the first available if there are alternatives
- my @pkgs = uniq($seenref, task_packages($task, $header), $task);
- print join("\n", @pkgs), "\n" if @pkgs;
- }
- }
-}
-
-sub print_all_packages {
- print STDERR "Printing all packages\n" if $debug;
-# print join("\n", @wanted, keys %missing),"\n";
-
- print "# First process the high priority tasks\n";
- my %seenlist;
- print_all_pkgs_tasks(\%seenlist, [qw(Depends Recommends)], @priorityorder );
-
- print "# Next, medium priority tasks tasks\n";
- print_all_pkgs_tasks(\%seenlist, [qw(Depends Recommends)], @medpriorder );
-
- print "# Next process all the others, in alphabetic order\n";
- print_all_pkgs_tasks(\%seenlist, undef, sort keys %taskinfo);
-
- print "# And last, the alternatives we dropped above\n";
- print join("\n", uniq(\%seenlist, @wanted, sort keys %missing)),"\n";
-}
-
-sub print_missing_packages {
- if (%missing) {
- print STDERR "Missing or avoided packages:\n";
- my $package;
- for $package (sort keys %missing) {
- if (exists $available{$package}) {
- print STDERR " $package ($available{$package} available)\n";
- } else {
- print STDERR " $package\n";
- }
- }
- exit 1 unless $opts{'i'};
- }
-}
-
-## Additions by Andreas Tille
-
-sub get_priorities {
- my ($prio, $default) = @_;
- my @list = () ;
-
- # if there is no taskcontrolfile every task has the same priority
- if ( ! stat($taskcontrolfile) ) {
- if ( ! $default ) { return (); }
- print STDERR "No task control file found - setting all tasks priority high.\n" if $debug;
- opendir(DIR, $tasksdir) || die("No tasks directory found.");
- @list = grep { !/^\./ } readdir(DIR);
- closedir DIR;
- return @list;
- }
- # read taskcontrolfile and find priorities
- print STDERR "Reading task control file.\n" if $debug;
- open(PRIO,$taskcontrolfile) || die("Unable to read task control file.");
- while (<PRIO>) {
- chomp ;
- if ( $_=~/^$prio\s*:\s*([-\w]+)/) {
- push @list,$1;
- }
- }
- close PRIO;
-
- return @list;
-}
-
-sub blend_init {
- # initialise blend name and other basic stuff
- unless ( -d "debian" ) {
- mkdir("debian") || die "mkdir debian: $!";
- }
-
- unless ( -e "debian/control.stub" ) {
- print STDERR "No template debian/control.stub. Use test prefix.\n" ;
- } else {
- chomp($prefix = `$blend_dev_dir/blend-get-names metapackageprefix`) ;
- $prefix = $prefix . "-" ;
- $tasksname = $prefix . "tasks";
- chomp($blendshortname = `$blend_dev_dir/blend-get-names blendshortname`);
- chomp($blendname = `$blend_dev_dir/blend-get-names blendname`);
- chomp($blendtitle = `$blend_dev_dir/blend-get-names blendtitle`);
- }
- if ( -d "config" && -e "config/control" ) {
- $hasconfig = 1;
- }
-}
+parser.add_argument("-r", "--release", dest="release", type=str,
+ default=default_release,
+ help="Target release, eg: stable, testing etc."
+ + " (default: {})".format(default_release))
+
+parser.add_argument("-S", '--supress-empty', dest="suppressempty",
+ action="store_true", default=False,
+ help="suppress tasks without any recommended package")
+
+parser.add_argument("-a", '--wanted', dest="wanted",
+ action="store_true", default=False,
+ help="print all wanted packages")
+
+parser.add_argument("-m", '--missing', dest="missing",
+ action="store_true", default=False,
+ help="print missing packages")
+
+parser.add_argument("-c", '--control', dest="gencontrol",
+ action="store_true", default=False,
+ help="Create new debian/control file.")
+
+parser.add_argument("-t", '--taskdesc', dest="taskdesc",
+ action="store_true", default=False,
+ help="Generate task descriptions and package list for task")
+
+parser.add_argument("-u", '--udebs', dest="udebs",
+ action="store_true", default=False,
+ help="Modify tasks desc file in case the blend uses udebs")
+
+parser.add_argument("-F", '--upgrade-task-format', dest="upgrade_tasks",
+ action="store_true", default=False,
+ help="Upgrade tasks files to the latest format version")
+
+parser.add_argument("-U", "--udd", dest="udd",
+ action="store_true", default=False,
+ help="Query UDD instead of apt (needs python3-psycopg2)")
+
+parser.add_argument("--udd-host", dest="udd_host",
+ default="udd-mirror.debian.net",
+ help="UDD host name (default: udd-mirror.debian.net)")
+
+parser.add_argument("--udd-user", dest="udd_user",
+ default="udd-mirror",
+ help="UDD user (default: udd-mirror)")
+
+parser.add_argument("--udd-password", dest="udd_password",
+ default="udd-mirror",
+ help="UDD password (default: udd-mirror)")
+
+parser.add_argument("--udd-database", dest="udd_database",
+ default="udd",
+ help="UDD database name (default: udd)")
+
+if 'GENCONTROL_OPTS' in os.environ:
+ opts = parser.parse_args(os.environ['GENCONTROL_OPTS'].split())
+ parser.set_defaults(**opts.__dict__)
+
+args = parser.parse_args()
+
+if args.release == "current":
+ args.release = None
+
+blend = Blend(basedir=args.dir)
+
+# For better performance, remove all tasks that will not create metapackages
+for task in blend.tasks[:]:
+ if not task.is_metapackage:
+ blend.tasks.remove(task)
+
+if args.udd:
+ blend += uddcache(blend.all, args.release, host=args.udd_host,
+ user=args.udd_user, password=args.udd_password,
+ database=args.udd_database)
+else:
+ blend += aptcache(args.release, [args.dir, '/etc/blends'])
+
+missing = sorted(set(d.name for d in blend.fix_dependencies()))
+wanted = sorted(set(d.name for d in itertools.chain(*(
+ itertools.chain(*(t.recommends + t.dependencies))
+ for t in blend.tasks))))
+
+if args.suppressempty:
+ for task in blend.tasks[:]:
+ if len(task.recommends) == 0 and len(task.dependencies) == 0:
+ blend.tasks.remove(task)
+
+if missing:
+ if args.missing:
+ print('Downgraded {} missing packages to Suggests:\n '
+ .format(len(missing))
+ + '\n '.join(missing))
+ else:
+ print('Downgraded {} missing packages to Suggests'
+ .format(len(missing)))
+
+if args.wanted:
+ print("Total {} packages in Depends or Recommend:\n "
+ .format(len(wanted))
+ + '\n '.join(wanted))
+
+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.gen_control().dump(fp, text_mode=True)
+ print("Generated {} for {} with {} using {} Depends+Recommends"
+ .format('debian/control',
+ args.release,
+ 'udd' if args.udd else 'apt',
+ len(wanted)))
+
+if args.taskdesc:
+ with open(os.path.join(args.dir, '{}-tasks.desc'.format(blend.name)), 'w') as fp:
+ blend.gen_task_desc(udeb=args.udebs).dump(fp, text_mode=True)
+ print("Generated {} for {} with {} using {} Depends+Recommends"
+ .format(blend.name + '-tasks.desc',
+ args.release,
+ 'udd' if args.udd else 'apt',
+ len(wanted)))
+
+upgraded_tasks = list(filter(lambda task: task.format_upgraded, blend.tasks))
+if upgraded_tasks:
+ if args.upgrade_tasks:
+ print('Upgrading {} tasks from format version {}'
+ .format(len(upgraded_tasks), upgraded_tasks[0].format_version))
+ for task in upgraded_tasks:
+ with open(os.path.join(args.dir, 'tasks', task.name), 'w') as fp:
+ task.content.dump(fp, text_mode=True)
+ else:
+ print('Warning: {} tasks use the old format {}'
+ .format(len(upgraded_tasks), upgraded_tasks[0].format_version))
+ print('Please consider upgrading the task files with the `-F` flag.')
=====================================
devtools/tasks_diff
=====================================
--- /dev/null
+++ b/devtools/tasks_diff
@@ -0,0 +1,248 @@
+#!/usr/bin/python3
+
+# Copyright 2013: Emmanouil Kiagias <e.kiagias at gmail.com>
+# Converted via 2to3 by Andreas Tille <tille at debian.org>
+# License: GPL
+
+"""
+no documentation for the moment
+"""
+
+import os
+import re
+import sys
+import json
+import pprint
+import logging
+import argparse
+import subprocess
+from debian import deb822
+
+# I shamelessly copied class Deb822List into a file blends_helper.py to test using it here
+# That's so hackish that I do not even commit this - needs further discussion
+from blends import Deb822List
+from debian.deb822 import Deb822
+
+#with this we distinguish the start of automatic entry in the changelog so we
+#can replace the entry if needed
+START_FLAG = "* start of automatic changelog entry *"
+
+def clean_up_packages(packages):
+ logger = logging.getLogger(__name__)
+ # Hack: Debian Edu tasks files are using '\' at EOL which is broken
+ # in RFC 822 files, but blend-gen-control from blends-dev relies
+ # on this. So remove this stuff here for the Moment
+ pkgs = re.sub('\\\\\n\s+', '', packages)
+
+ # Remove versions from versioned depends
+ pkgs = re.sub(' *\([ ><=\.0-9]+\) *', '', pkgs)
+
+ # temporary strip spaces from alternatives ('|') to enable erroneous space handling as it was done before
+ pkgs = re.sub('\s*\|\s*', '|', pkgs)
+
+ # turn alternatives ('|') into real depends for this purpose
+ # because we are finally interested in all alternatives
+ pkgslist = pkgs.split(',')
+ # Collect all dependencies in one line first,
+ # create an object for each later
+ pkgs_in_one_line = []
+ for depl in pkgslist:
+ dl = depl.strip()
+ if dl != '': # avoid confusion when ',' is at end of line
+ if re.search('\s', dl):
+ #logger.error("Blend %s task %s: Syntax error '%s'" % (blend, task, dl))
+ # trying to fix the syntax error after issuing error message
+ dlspaces = re.sub('\s+', ',', dl).split(',')
+ for dls in dlspaces:
+ pkgs_in_one_line.append(dls.strip())
+ #logger.info("Blend %s task %s: Found '%s' package inside broken syntax string - please fix task file anyway" % (blend, task, dls.strip()))
+ else:
+ # in case we have to deal with a set of alternatives
+ if re.search('\|', dl):
+ #for da in dl.split('|'):
+ # deps_in_one_line.append(da)
+ dl = re.sub('\|', ' | ', dl)
+ pkgs_in_one_line.append(dl)
+ # self.inject_package_alternatives(blend, task, strength, dl)
+
+ return pkgs_in_one_line
+
+def load_task(path_to_task):
+ """
+ parses a task file and return a dictionary containing all its package headers elements
+ (depends, suggests etc)
+ """
+ ftask = open(path_to_task, 'r')
+ task = os.path.basename(path_to_task)
+ taskinfo = {}
+
+ for header in ["depends", "suggests", "recommends", "ignore", "avoid"]:
+ taskinfo[header] = []
+
+ for paragraph in deb822.Sources.iter_paragraphs(ftask, shared_storage=False):
+ if "depends" in paragraph:
+ taskinfo["depends"] += clean_up_packages(paragraph["depends"])
+
+ if "suggests" in paragraph:
+ taskinfo["suggests"] += clean_up_packages(paragraph["suggests"])
+
+ if "recommends" in paragraph:
+ taskinfo["recommends"] += clean_up_packages(paragraph["recommends"])
+
+ if "ignore" in paragraph:
+ taskinfo["ignore"] += clean_up_packages(paragraph["ignore"])
+
+ if "avoid" in paragraph:
+ taskinfo["avoid"] += clean_up_packages(paragraph["avoid"])
+
+ return task, taskinfo
+
+def compare_tasks(tasks, tasks_compare, taskprefix):
+ """
+ This function will dump in stdout the package differences between
+ the given tasks1 and tasks2
+ """
+
+ first_print = True
+
+ for task in sorted(tasks):
+ if not task in tasks_compare:
+ continue
+
+ task_first = True
+ first_add = True
+ for header in ["depends", "recommends", "suggests", "ignore", "avoid"]:
+ added = set(tasks[task][header]) - set(tasks_compare[task][header])
+ if added:
+ if first_print:
+ print(START_FLAG, "\n")
+ print("* Changes in metapackage dependencies")
+ first_print = False
+ if task_first:
+ print(" -{0}-{1}".format(taskprefix,task))
+ task_first = False
+ if first_add:
+ print(" added:")
+ first_add = False
+ print(" {0}: ".format(header.capitalize()), end=' ')
+ print(", ".join(added))
+
+ first_remove = True
+ for header in ["depends", "recommends", "suggests", "ignore", "avoid"]:
+ removed = set(tasks_compare[task][header]) - set(tasks[task][header])
+ if removed:
+ if first_print:
+ print(START_FLAG, "\n")
+ print("* Changes in metapackage dependencies")
+ first_print = False
+ if task_first:
+ print(" -{0}-{1}".format(taskprefix,task))
+ task_first = False
+ if first_remove:
+ print(" removed:")
+ first_remove = False
+ print(" {0}: ".format(header.capitalize()), end=' ')
+ print(", ".join(removed))
+
+ removed_tasks = set(tasks_compare.keys()) - set(tasks.keys())
+ added_tasks = set(tasks.keys()) - set(tasks_compare.keys())
+ if added_tasks:
+ if first_print:
+ print(START_FLAG, "\n")
+ print("* Changes in metapackage dependencies")
+ first_print = False
+ print("* New metapackages:")
+ for newtask in added_tasks:
+ print(" -{0}-{1}".format(taskprefix, newtask))
+
+ if removed_tasks:
+ if first_print:
+ print(START_FLAG, "\n")
+ print("* Changes in metapackage dependencies")
+ first_print = False
+ print("* Removed metapackages:")
+ for removedtask in removed_tasks:
+ print(" - {0}-{1}".format(taskprefix, removedtask))
+
+def load_tasks(tasks_path):
+ tasks = {}
+
+ for taskpath in tasks_path:
+ taskname, taskinfo = load_task(taskpath)
+ tasks[taskname] = taskinfo
+
+ return tasks
+
+if __name__ == "__main__":
+ blend_dev_dir = "/usr/share/blends-dev/"
+ default_json = "tasks.json"
+
+ ##TODO add proper epilog giving example usage
+ parser = argparse.ArgumentParser(epilog="")
+
+ parser.add_argument("-t", "--tasks", dest="tasks", type=str,
+ help="Path to task files", default=".")
+ parser.add_argument("-s", "--status-dump", dest="statusdump", action="store_true",
+ help="Dump dependencies status into a json file")
+ parser.add_argument("-o", "--output", dest="output", type=str, default=default_json,
+ help="Output file where to store the dependencies json file(when -s/--status-dump is provided)")
+ parser.add_argument("-c", "--compare", dest="compare", type=str,
+ help="Provide two comma separated(without spaces) paths to json files to be compared")
+ parser.add_argument("-d", "--debug", dest="debug", action="store_true", default=False,
+ help="Print debug information")
+ #parse the command line arguments
+ args = parser.parse_args()
+
+ if args.debug:
+ logging.basicConfig(level=logging.DEBUG)
+ else:
+ logging.basicConfig()
+ logger = logging.getLogger(__name__)
+
+ #load the taskprefix
+ with open(os.path.join('debian', 'control.stub'), encoding="UTF-8") as fp:
+ control_stub = Deb822List(Deb822.iter_paragraphs(fp))
+ taskprefix = control_stub[0]['Source'].split('-', 1)[-1]
+
+ if not args.statusdump and not args.compare:
+ logger.error("At least -s/--statusdump or -c/--compare argument must be provided")
+ sys.exit(-1)
+
+ path_to_tasks = os.path.join(args.tasks, "tasks")
+ if not os.path.isdir(path_to_tasks):
+ logger.error("tasks directory could not be found in given path. aborting...")
+ sys.exit(-1)
+
+ logger.debug("Reading task files from directory {0}".format(path_to_tasks))
+ tasks = [ os.path.join(path_to_tasks, fold) for fold in os.listdir(path_to_tasks) if not fold.startswith('.') ]
+ giventasks = load_tasks(tasks)
+
+ if args.statusdump:
+ logger.debug("Status dump was selected")
+
+ with open(args.output, "w") as fout:
+ logger.debug("Dumping json dependencies file into {0}".format(args.output))
+ json.dump(giventasks, fout)
+
+ sys.exit(0)
+
+ if args.compare:
+ if not ',' in args.compare:
+ logger.error("For --compare two comma separated paths to json files should be provided.")
+ sys.exit(-1)
+
+ latest, previous = [ x.strip() for x in args.compare.split(',') ]
+
+ if not os.path.isfile(previous) or not os.path.isfile(latest):
+ logger.error("Please provide existing json files to be compared.")
+ sys.exit(-1)
+
+ logger.debug("Comparing json files:")
+ logger.debug("{0} with {1}".format(latest, previous))
+
+ latest_tasks = json.load(open(latest))
+ previous_tasks = json.load(open(previous))
+
+ logger.debug("Comparing releases...")
+ compare_tasks(latest_tasks, previous_tasks, taskprefix)
+
=====================================
sources.list.UNRELEASED deleted
=====================================
--- a/sources.list.UNRELEASED
+++ /dev/null
@@ -1,5 +0,0 @@
-# For testing purposes this sources.list might be useful. It is a
-# good practice to use UNRELEASED in the changelog as target distribution
-# for not yet finished packages and blends-dev should also work in this
-# case
-deb http://ftp.debian.org/debian unstable main
=====================================
sources.list.unstable
=====================================
--- a/sources.list.unstable
+++ b/sources.list.unstable
@@ -1,7 +1 @@
-# using unstable as target distribution for the meta package dependencies
-# does actually not sound reasonable. The idea is to enable a smooth transition
-# to testing for all meta packages and thus here testing is used as target
-# distribution. You are free to provide your own source.list.unstable
-# in the source of your meta package building code to force unstable as
-# target or alternatively you could change this file (/etc/blends/sources.list.unstable).
-deb http://ftp.debian.org/debian testing main
+deb http://ftp.debian.org/debian unstable main
=====================================
sphinxdoc/Makefile
=====================================
--- /dev/null
+++ b/sphinxdoc/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+SPHINXPROJ = blends
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
=====================================
sphinxdoc/conf.py
=====================================
--- /dev/null
+++ b/sphinxdoc/conf.py
@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+#
+# blends documentation build configuration file, created by
+# sphinx-quickstart on Tue Mar 20 22:23:34 2018.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import sys
+sys.path.insert(0, os.path.abspath('..'))
+import blends
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = ['sphinx.ext.autodoc',
+ 'sphinx.ext.doctest',
+ 'sphinx.ext.intersphinx']
+
+autodoc_member_order = 'bysource'
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'blends'
+copyright = u'2018, Debian Blends Team'
+author = u'Debian Blends Team'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = u'0.7'
+# The full version, including alpha/beta/rc tags.
+release = u'0.7'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This patterns also effect to html_static_path and html_extra_path
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'alabaster'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# This is required for the alabaster theme
+# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
+html_sidebars = {
+ '**': [
+ 'relations.html', # needs 'show_related': True theme option to display
+ 'searchbox.html',
+ ]
+}
+
+
+# -- Options for HTMLHelp output ------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'blendsdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'blends.tex', u'blends Documentation',
+ u'Debian Blends Team', 'manual'),
+]
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ (master_doc, 'blends', u'blends Documentation',
+ [author], 1)
+]
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'blends', u'blends Documentation',
+ author, 'blends', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'https://docs.python.org/': None}
=====================================
sphinxdoc/index.rst
=====================================
--- /dev/null
+++ b/sphinxdoc/index.rst
@@ -0,0 +1,9 @@
+Python module debian.blends
+===========================
+
+.. automodule:: blends
+ :members:
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents:
View it on GitLab: https://salsa.debian.org/blends-team/blends/compare/9c630f3d4a2facae7d75e44f905ebb195b897dda...d79e49e0ed0f1be5f1b875c2aaf668fa6610c122
---
View it on GitLab: https://salsa.debian.org/blends-team/blends/compare/9c630f3d4a2facae7d75e44f905ebb195b897dda...d79e49e0ed0f1be5f1b875c2aaf668fa6610c122
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/blends-commit/attachments/20180413/ca2842c3/attachment-0001.html>
More information about the Blends-commit
mailing list