Skip to content
Commits on Source (40)
'''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
# Dependency on debhelper is intended
blends-dev: binary-package-depends-on-toolchain-package depends: debhelper (>= 9)
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@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@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@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@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@debian.org> Sat, 24 Mar 2018 10:41:20 +0100
blends (0.6.100) unstable; urgency=medium
......
......@@ -4,25 +4,34 @@ Uploaders: Petter Reinholdtsen <pere@debian.org>,
Andreas Tille <tille@debian.org>,
Jonas Smedegaard <dr@jones.dk>,
Ole Streicher <olebole@debian.org>,
Mike Gabriel <sunweaver@debian.org>,
Mike Gabriel <sunweaver@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
......@@ -78,3 +86,18 @@ Description: Debian Pure Blends tasks for new installations
.
The package is intended to be installed in the base system. Later
(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.
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@debian.org>
License: GPL-2+
Files: devtools/blend-gen-control
Copyright: © 2003-2007 Petter Reinholdtsen <pere@debian.org>
© 2007-2012 Andreas Tille <tille@debian.org>
Copyright: © 2018 Ole Streicher <olebole@debian.org>
License: GPL-2+
Files: share/blends/*
......
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
sphinxdoc/_build/html/*
......@@ -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:
......
......@@ -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
......
This diff is collapsed.
#!/usr/bin/python3
# Copyright 2013: Emmanouil Kiagias <e.kiagias@gmail.com>
# Converted via 2to3 by Andreas Tille <tille@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)
# 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
# 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
# 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
# -*- 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}
Python module debian.blends
===========================
.. automodule:: blends
:members:
.. toctree::
:maxdepth: 2
:caption: Contents: